From fb18fdf5b7ed1d4b9d18f82d5e04932061c35cf4 Mon Sep 17 00:00:00 2001 From: JordanTheToaster Date: Sun, 16 Mar 2025 14:50:44 +0000 Subject: [PATCH 001/162] VMManager: Enable file logging by default --- pcsx2/VMManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pcsx2/VMManager.cpp b/pcsx2/VMManager.cpp index cb088aeac9..90e9fa33b7 100644 --- a/pcsx2/VMManager.cpp +++ b/pcsx2/VMManager.cpp @@ -474,7 +474,7 @@ void VMManager::UpdateLoggingSettings(SettingsInterface& si) const bool system_console_enabled = !s_log_block_system_console && si.GetBoolValue("Logging", "EnableSystemConsole", false); const bool log_window_enabled = !s_log_block_system_console && si.GetBoolValue("Logging", "EnableLogWindow", false); - const bool file_logging_enabled = s_log_force_file_log || si.GetBoolValue("Logging", "EnableFileLogging", false); + const bool file_logging_enabled = s_log_force_file_log || si.GetBoolValue("Logging", "EnableFileLogging", true); if (system_console_enabled != Log::IsConsoleOutputEnabled()) Log::SetConsoleOutputLevel(system_console_enabled ? level : LOGLEVEL_NONE); From fd983946f5513aa21ddd8abe4f8f4505a41b4e8a Mon Sep 17 00:00:00 2001 From: TheLastRar Date: Wed, 12 Mar 2025 23:43:43 +0000 Subject: [PATCH 002/162] Qt: Fix display of LED colours on Windows native themes --- pcsx2-qt/ColorPickerButton.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pcsx2-qt/ColorPickerButton.cpp b/pcsx2-qt/ColorPickerButton.cpp index 1f1b6d1c7c..cc9e6f0cb8 100644 --- a/pcsx2-qt/ColorPickerButton.cpp +++ b/pcsx2-qt/ColorPickerButton.cpp @@ -29,7 +29,7 @@ void ColorPickerButton::setColor(u32 rgb) void ColorPickerButton::updateBackgroundColor() { - setStyleSheet(QStringLiteral("background-color: #%1;").arg(static_cast(m_color), 8, 16, QChar('0'))); + setStyleSheet(QStringLiteral("background-color: #%1;").arg(static_cast(m_color), 6, 16, QChar('0'))); } void ColorPickerButton::onClicked() From dd91c0aac22657cd7190ec7a8537b13de312e2df Mon Sep 17 00:00:00 2001 From: Ty Date: Sun, 16 Mar 2025 11:21:46 -0400 Subject: [PATCH 003/162] CI: Pin some third-party actions to a full length commit SHA --- .github/workflows/cron_update_base_translation.yml | 2 +- .github/workflows/cron_update_controller_db.yml | 2 +- .github/workflows/macos_build.yml | 2 +- .github/workflows/release_cut_new.yml | 6 +++--- .github/workflows/triage_pr.yml | 2 +- .github/workflows/windows_build_qt.yml | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/cron_update_base_translation.yml b/.github/workflows/cron_update_base_translation.yml index 15706c166e..0baeb23377 100644 --- a/.github/workflows/cron_update_base_translation.yml +++ b/.github/workflows/cron_update_base_translation.yml @@ -17,7 +17,7 @@ jobs: run: ./.github/workflows/scripts/common/update_base_translation.sh - name: Create Pull Request - uses: peter-evans/create-pull-request@v7 + uses: peter-evans/create-pull-request@4320041ed380b20e97d388d56a7fb4f9b8c20e79 with: title: "Qt: Update Base Translation" commit-message: "[ci skip] Qt: Update Base Translation." diff --git a/.github/workflows/cron_update_controller_db.yml b/.github/workflows/cron_update_controller_db.yml index d3cdf2605f..126e3739bf 100644 --- a/.github/workflows/cron_update_controller_db.yml +++ b/.github/workflows/cron_update_controller_db.yml @@ -19,7 +19,7 @@ jobs: mv ./game_controller_db.txt ${{github.workspace}}/bin/resources/game_controller_db.txt - name: Create Pull Request - uses: peter-evans/create-pull-request@v7 + uses: peter-evans/create-pull-request@4320041ed380b20e97d388d56a7fb4f9b8c20e79 with: title: "PAD: Update to latest controller database" commit-message: "[ci skip] PAD: Update to latest controller database." diff --git a/.github/workflows/macos_build.yml b/.github/workflows/macos_build.yml index cf42de647b..c595f2a653 100644 --- a/.github/workflows/macos_build.yml +++ b/.github/workflows/macos_build.yml @@ -160,7 +160,7 @@ jobs: - name: Sign the Application if: ${{ inputs.sign_and_notarize == true && env.SIGN_KEY }} - uses: indygreg/apple-code-sign-action@v1.1 + uses: indygreg/apple-code-sign-action@44d0985b7f4363198e80b6fea63ac3e9dd3e9957 with: input_path: 'PCSX2.app' p12_file: cert.p12 diff --git a/.github/workflows/release_cut_new.yml b/.github/workflows/release_cut_new.yml index a6a8abd91e..61280bde10 100644 --- a/.github/workflows/release_cut_new.yml +++ b/.github/workflows/release_cut_new.yml @@ -40,7 +40,7 @@ jobs: # Docs - https://github.com/mathieudutour/github-tag-action - name: Bump Version and Push Tag id: tag_version - uses: mathieudutour/github-tag-action@v6.2 + uses: mathieudutour/github-tag-action@a22cf08638b34d5badda920f9daf6e72c477b07b with: github_token: ${{ github.token }} tag_prefix: v @@ -68,7 +68,7 @@ jobs: mv ./release-notes.md ${GITHUB_WORKSPACE}/release-notes.md - name: Create a GitHub Release (Manual) - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda if: steps.tag_version.outputs.new_tag && github.event_name == 'workflow_dispatch' with: body_path: ./release-notes.md @@ -77,7 +77,7 @@ jobs: tag_name: ${{ steps.tag_version.outputs.new_tag }} - name: Create a GitHub Release (Push) - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda if: steps.tag_version.outputs.new_tag && github.event_name != 'workflow_dispatch' with: body_path: ./release-notes.md diff --git a/.github/workflows/triage_pr.yml b/.github/workflows/triage_pr.yml index 62a93ec4bc..7a433e7870 100644 --- a/.github/workflows/triage_pr.yml +++ b/.github/workflows/triage_pr.yml @@ -12,7 +12,7 @@ jobs: with: repo-token: ${{ secrets.GITHUB_TOKEN }} - - uses: xTVaser/first-interaction@v1.2.4 + - uses: xTVaser/first-interaction@d62d6eb3c1215eae9f9d6dbfabf12d6725834cb3 with: repo-token: ${{ secrets.GITHUB_TOKEN }} debug-mode: false diff --git a/.github/workflows/windows_build_qt.yml b/.github/workflows/windows_build_qt.yml index aa8d0a9498..4d9d808553 100644 --- a/.github/workflows/windows_build_qt.yml +++ b/.github/workflows/windows_build_qt.yml @@ -168,7 +168,7 @@ jobs: !./bin/**/*.lib - name: Install the Breakpad Symbol Generator - uses: baptiste0928/cargo-install@v3 + uses: baptiste0928/cargo-install@91c5da15570085bcde6f4d7aed98cb82d6769fd3 with: crate: dump_syms From b4deb6a4e213a907d18557757794397b900429b6 Mon Sep 17 00:00:00 2001 From: JordanTheToaster Date: Sun, 16 Mar 2025 18:18:04 +0000 Subject: [PATCH 004/162] VMManager: Actually enable file logging by default for real this time --- pcsx2-qt/MainWindow.cpp | 2 +- pcsx2/ImGui/FullscreenUI.cpp | 2 +- pcsx2/VMManager.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pcsx2-qt/MainWindow.cpp b/pcsx2-qt/MainWindow.cpp index 1b7d1804be..80c05d515e 100644 --- a/pcsx2-qt/MainWindow.cpp +++ b/pcsx2-qt/MainWindow.cpp @@ -411,7 +411,7 @@ void MainWindow::connectSignals() SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionEnableEEConsoleLogging, "Logging", "EnableEEConsole", true); SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionEnableIOPConsoleLogging, "Logging", "EnableIOPConsole", true); SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionEnableLogWindow, "Logging", "EnableLogWindow", false); - SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionEnableFileLogging, "Logging", "EnableFileLogging", false); + SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionEnableFileLogging, "Logging", "EnableFileLogging", true); SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionEnableLogTimestamps, "Logging", "EnableTimestamps", true); SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionEnableCDVDVerboseReads, "EmuCore", "CdvdVerboseReads", false); SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionSaveBlockDump, "EmuCore", "CdvdDumpBlocks", false); diff --git a/pcsx2/ImGui/FullscreenUI.cpp b/pcsx2/ImGui/FullscreenUI.cpp index 938ff9afb5..0db1579b8a 100644 --- a/pcsx2/ImGui/FullscreenUI.cpp +++ b/pcsx2/ImGui/FullscreenUI.cpp @@ -5019,7 +5019,7 @@ void FullscreenUI::DrawAdvancedSettingsPage() DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_TERMINAL, "System Console"), FSUI_CSTR("Writes log messages to the system console (console window/standard output)."), "Logging", "EnableSystemConsole", false); DrawToggleSetting( - bsi, FSUI_ICONSTR(ICON_FA_SCROLL, "File Logging"), FSUI_CSTR("Writes log messages to emulog.txt."), "Logging", "EnableFileLogging", false); + bsi, FSUI_ICONSTR(ICON_FA_SCROLL, "File Logging"), FSUI_CSTR("Writes log messages to emulog.txt."), "Logging", "EnableFileLogging", true); DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_SCROLL, "Verbose Logging"), FSUI_CSTR("Writes dev log messages to log sinks."), "Logging", "EnableVerbose", false, !IsDevBuild); diff --git a/pcsx2/VMManager.cpp b/pcsx2/VMManager.cpp index 90e9fa33b7..824ce5c050 100644 --- a/pcsx2/VMManager.cpp +++ b/pcsx2/VMManager.cpp @@ -518,7 +518,7 @@ void VMManager::UpdateLoggingSettings(SettingsInterface& si) void VMManager::SetDefaultLoggingSettings(SettingsInterface& si) { si.SetBoolValue("Logging", "EnableSystemConsole", false); - si.SetBoolValue("Logging", "EnableFileLogging", false); + si.SetBoolValue("Logging", "EnableFileLogging", true); si.SetBoolValue("Logging", "EnableTimestamps", true); si.SetBoolValue("Logging", "EnableVerbose", false); si.SetBoolValue("Logging", "EnableEEConsole", false); From 34dabc77c166b1e71f4da376903fdf513f12ab58 Mon Sep 17 00:00:00 2001 From: JordanTheToaster Date: Wed, 12 Mar 2025 19:58:30 +0000 Subject: [PATCH 005/162] Deps: Swap to libjpeg-turbo 3.1.0 --- .github/workflows/macos_build.yml | 2 +- .../scripts/linux/build-dependencies-qt.sh | 24 +- .../macos/build-dependencies-universal.sh | 27 +- .../scripts/macos/build-dependencies.sh | 24 +- .../windows/build-dependencies-arm64.bat | 13 +- .../scripts/windows/build-dependencies.bat | 13 +- .../scripts/windows/libjpeg-cmake.patch | 422 ------------------ common/vsprops/LinkPCSX2Deps.props | 4 +- pcsx2/CMakeLists.txt | 2 +- 9 files changed, 47 insertions(+), 484 deletions(-) delete mode 100644 .github/workflows/scripts/windows/libjpeg-cmake.patch diff --git a/.github/workflows/macos_build.yml b/.github/workflows/macos_build.yml index c595f2a653..7a0898d4d2 100644 --- a/.github/workflows/macos_build.yml +++ b/.github/workflows/macos_build.yml @@ -35,7 +35,7 @@ jobs: name: ${{ inputs.jobName }} runs-on: ${{ inputs.os }} # Set some sort of timeout in the event of run-away builds. We are limited on concurrent jobs so, get rid of them. - timeout-minutes: 90 + timeout-minutes: 120 env: POWERSHELL_TELEMETRY_OPTOUT: 1 CCACHE_BASEDIR: ${{ github.workspace }} diff --git a/.github/workflows/scripts/linux/build-dependencies-qt.sh b/.github/workflows/scripts/linux/build-dependencies-qt.sh index cc854588fb..aabaa694e7 100755 --- a/.github/workflows/scripts/linux/build-dependencies-qt.sh +++ b/.github/workflows/scripts/linux/build-dependencies-qt.sh @@ -15,7 +15,7 @@ if [ "${INSTALLDIR:0:1}" != "/" ]; then fi LIBBACKTRACE=ad106d5fdd5d960bd33fae1c48a351af567fd075 -LIBJPEG=9f +LIBJPEGTURBO=3.1.0 LIBPNG=1.6.45 LIBWEBP=1.5.0 LZ4=b8fd2d15309dd4e605070bd4486e26b6ef814e29 @@ -34,7 +34,7 @@ cd deps-build cat > SHASUMS < -+ $ -+ ) -+ set_target_properties(libjpeg -+ PROPERTIES -+ VERSION ${PROJECT_VERSION_MAJOR} -+ POSITION_INDEPENDENT_CODE ON -+ CLEAN_DIRECT_OUTPUT ON -+ PUBLIC_HEADER "${PUBLIC_HDRS}" -+ ) -+ install(TARGETS libjpeg -+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} -+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} -+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} -+ PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} -+ ) -+endif() -+ -+if(BUILD_STATIC_LIBS) -+ add_library(libjpeg_static STATIC $) -+ target_include_directories(libjpeg_static -+ PUBLIC -+ $ -+ $ -+ ) -+ set_target_properties(libjpeg_static -+ PROPERTIES -+ OUTPUT_NAME jpeg -+ VERSION ${PROJECT_VERSION_MAJOR} -+ POSITION_INDEPENDENT_CODE ON -+ CLEAN_DIRECT_OUTPUT ON -+ PUBLIC_HEADER "${PUBLIC_HDRS}" -+ ) -+ install(TARGETS libjpeg_static -+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} -+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} -+ PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} -+ ) -+endif() -+ -+# Configure and install pkg-config and libtool files -+if(BUILD_STATIC_LIBS OR BUILD_SHARED_LIBS) -+ # Compute the la file's weird version number -+ math(EXPR JPEG_CONF_VER_MAJOR "${PROJECT_VERSION_MAJOR} + ${PROJECT_VERSION_MINOR}") -+ set(JPEG_LIB_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) -+ set(JPEG_LIB_VERSION_MINOR ${PROJECT_VERSION_MINOR}) -+ -+ # Configure and install -+ configure_file(libjpeg.pc.cmakein libjpeg.pc @ONLY) -+ install(FILES -+ ${CMAKE_CURRENT_BINARY_DIR}/libjpeg.pc -+ DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/pkgconfig -+ ) -+endif() -+ -diff -ruN jpeg-9f/ConfigureJConfig.cmake jpeg-9f-new/ConfigureJConfig.cmake ---- jpeg-9f/ConfigureJConfig.cmake 1970-01-01 10:00:00.000000000 +1000 -+++ jpeg-9f-new/ConfigureJConfig.cmake 2024-03-23 21:09:37.223882900 +1000 -@@ -0,0 +1,95 @@ -+include(CheckIncludeFile) -+include(CheckSymbolExists) -+include(CheckTypeSize) -+ -+# Define this if your system has an ANSI-conforming file. -+check_include_file(stddef.h HAVE_STDDEF_H) -+ -+# Define this if your system has an ANSI-conforming file. -+check_include_file(stdlib.h HAVE_STDLIB_H) -+ -+# Does your compiler support function prototypes? -+# (If not, you also need to use ansi2knr, see install.txt) -+set(HAVE_PROTOTYPES true CACHE BOOL "Does your compiler support function prototypes?") -+ -+# Does your compiler support the declaration "unsigned char" ? -+# How about "unsigned short" ? -+check_type_size("unsigned char" UNSIGNED_CHAR LANGUAGE C) -+check_type_size("unsigned short" UNSIGNED_SHORT LANGUAGE C) -+ -+# Define "void" as "char" if your compiler doesn't know about type void. -+# NOTE: be sure to define void such that "void *" represents the most general -+# pointer type, e.g., that returned by malloc(). -+# NOT IMPLEMENTED: Modify in jconfig.h.in # -+ -+# Define "const" as empty if your compiler doesn't know the "const" keyword. -+# NOT IMPLEMENTED: Modify in jconfig.h.in # -+ -+# Define this if an ordinary "char" type is unsigned. -+# If you're not sure, leaving it undefined will work at some cost in speed. -+# If you defined HAVE_UNSIGNED_CHAR then the speed difference is minimal. -+set(CHAR_IS_UNSIGNED false CACHE BOOL "char type is unsigned") -+ -+# Define this if your system does not have an ANSI/SysV , -+# but does have a BSD-style . -+set(NEED_BSD_STRINGS false CACHE BOOL "Use BSD . Use only if system lacks ANSI/SysV ") -+ -+# Define this if your system does not provide typedef size_t in any of the -+# ANSI-standard places (stddef.h, stdlib.h, or stdio.h), but places it in -+# instead. -+set(NEED_SYS_TYPES_H false CACHE BOOL "size_t defined in ") -+ -+# For 80x86 machines, you need to define NEED_FAR_POINTERS, -+# unless you are using a large-data memory model or 80386 flat-memory mode. -+# On less brain-damaged CPUs this symbol must not be defined. -+# (Defining this symbol causes large data structures to be referenced through -+# "far" pointers and to be allocated with a special version of malloc.) -+set(NEED_FAR_POINTERS false CACHE BOOL "Reference large data structures through 'far' pointers allocated with a special version of malloc") -+ -+# Define this if your linker needs global names to be unique in less -+# than the first 15 characters. -+set(NEED_SHORT_EXTERNAL_NAMES false CACHE BOOL "Global names must be unique in less than the first 15 characters") -+ -+# Although a real ANSI C compiler can deal perfectly well with pointers to -+# unspecified structures (see "incomplete types" in the spec), a few pre-ANSI -+# and pseudo-ANSI compilers get confused. To keep one of these bozos happy, -+# define INCOMPLETE_TYPES_BROKEN. This is not recommended unless you -+# actually get "missing structure definition" warnings or errors while -+# compiling the JPEG code. -+set(INCOMPLETE_TYPES_BROKEN false CACHE BOOL "Disable pointers to unspecified structures") -+ -+# Define "boolean" as unsigned char, not enum, on Windows systems. -+# NOT IMPLEMENTED: Modify in jconfig.h.in # -+ -+# The following options affect code selection within the JPEG library, -+# but they don't need to be visible to applications using the library. -+# To minimize application namespace pollution, the symbols won't be -+# defined unless JPEG_INTERNALS has been defined. -+# -+ -+# Define this if your compiler implements ">>" on signed values as a logical -+# (unsigned) shift; leave it undefined if ">>" is a signed (arithmetic) shift, -+# which is the normal and rational definition. -+set(RIGHT_SHIFT_IS_UNSIGNED false CACHE BOOL "Compiler implements >> on signed values as a logical (unsigned) shift") -+ -+# The remaining options do not affect the JPEG library proper, -+# but only the sample applications cjpeg/djpeg (see cjpeg.c, djpeg.c). -+# Other applications can ignore these. -+# -+ -+mark_as_advanced(FORCE -+ HAVE_PROTOTYPES -+ HAVE_UNSIGNED_CHAR -+ HAVE_UNSIGNED_SHORT -+ CHAR_IS_UNSIGNED -+ HAVE_STDDEF_H -+ HAVE_STDLIB_H -+ NEED_BSD_STRINGS -+ NEED_SYS_TYPES_H -+ NEED_FAR_POINTERS -+ NEED_SHORT_EXTERNAL_NAMES -+ INCOMPLETE_TYPES_BROKEN -+ RIGHT_SHIFT_IS_UNSIGNED -+) -+ -+configure_file(jconfig.h.in ${CMAKE_CURRENT_SOURCE_DIR}/jconfig.h) -diff -ruN jpeg-9f/jconfig.h.in jpeg-9f-new/jconfig.h.in ---- jpeg-9f/jconfig.h.in 1970-01-01 10:00:00.000000000 +1000 -+++ jpeg-9f-new/jconfig.h.in 2024-03-23 21:06:05.204994600 +1000 -@@ -0,0 +1,173 @@ -+/* -+ * jconfig.h.in -+ * -+ * Copyright (C) 1991-1994, Thomas G. Lane. -+ * Modified 2009-2013 by Guido Vollbeding. -+ * This file is part of the Independent JPEG Group's software. -+ * For conditions of distribution and use, see the accompanying README file. -+ * -+ * This file is a modification of jconfig.txt from libjpeg. In addition to -+ * documenting the configuration options that are required to customize the -+ * JPEG software for a particular system, it is used by jpeg-cmake to configure -+ * jconfig.h -+ */ -+ -+ -+/* -+ * These symbols indicate the properties of your machine or compiler. -+ * #define the symbol if yes, #undef it if no. -+ */ -+ -+/* Does your compiler support function prototypes? -+ * (If not, you also need to use ansi2knr, see install.txt) -+ */ -+#cmakedefine HAVE_PROTOTYPES -+ -+/* Does your compiler support the declaration "unsigned char" ? -+ * How about "unsigned short" ? -+ */ -+#cmakedefine HAVE_UNSIGNED_CHAR -+#cmakedefine HAVE_UNSIGNED_SHORT -+ -+/* Define "void" as "char" if your compiler doesn't know about type void. -+ * NOTE: be sure to define void such that "void *" represents the most general -+ * pointer type, e.g., that returned by malloc(). -+ */ -+/* #define void char */ -+ -+/* Define "const" as empty if your compiler doesn't know the "const" keyword. -+ */ -+/* #define const */ -+ -+/* Define this if an ordinary "char" type is unsigned. -+ * If you're not sure, leaving it undefined will work at some cost in speed. -+ * If you defined HAVE_UNSIGNED_CHAR then the speed difference is minimal. -+ */ -+#cmakedefine CHAR_IS_UNSIGNED -+ -+/* Define this if your system has an ANSI-conforming file. -+ */ -+#cmakedefine HAVE_STDDEF_H -+ -+/* Define this if your system has an ANSI-conforming file. -+ */ -+#cmakedefine HAVE_STDLIB_H -+ -+/* Define this if your system does not have an ANSI/SysV , -+ * but does have a BSD-style . -+ */ -+#cmakedefine NEED_BSD_STRINGS -+ -+/* Define this if your system does not provide typedef size_t in any of the -+ * ANSI-standard places (stddef.h, stdlib.h, or stdio.h), but places it in -+ * instead. -+ */ -+#cmakedefine NEED_SYS_TYPES_H -+ -+/* For 80x86 machines, you need to define NEED_FAR_POINTERS, -+ * unless you are using a large-data memory model or 80386 flat-memory mode. -+ * On less brain-damaged CPUs this symbol must not be defined. -+ * (Defining this symbol causes large data structures to be referenced through -+ * "far" pointers and to be allocated with a special version of malloc.) -+ */ -+#cmakedefine NEED_FAR_POINTERS -+ -+/* Define this if your linker needs global names to be unique in less -+ * than the first 15 characters. -+ */ -+#cmakedefine NEED_SHORT_EXTERNAL_NAMES -+ -+/* Although a real ANSI C compiler can deal perfectly well with pointers to -+ * unspecified structures (see "incomplete types" in the spec), a few pre-ANSI -+ * and pseudo-ANSI compilers get confused. To keep one of these bozos happy, -+ * define INCOMPLETE_TYPES_BROKEN. This is not recommended unless you -+ * actually get "missing structure definition" warnings or errors while -+ * compiling the JPEG code. -+ */ -+#cmakedefine INCOMPLETE_TYPES_BROKEN -+ -+/* Define "boolean" as unsigned char, not enum, on Windows systems. -+ */ -+#ifdef _WIN32 -+#ifndef __RPCNDR_H__ /* don't conflict if rpcndr.h already read */ -+typedef unsigned char boolean; -+#endif -+#ifndef FALSE /* in case these macros already exist */ -+#define FALSE 0 /* values of boolean */ -+#endif -+#ifndef TRUE -+#define TRUE 1 -+#endif -+#define HAVE_BOOLEAN /* prevent jmorecfg.h from redefining it */ -+#endif -+ -+ -+/* -+ * The following options affect code selection within the JPEG library, -+ * but they don't need to be visible to applications using the library. -+ * To minimize application namespace pollution, the symbols won't be -+ * defined unless JPEG_INTERNALS has been defined. -+ */ -+ -+#ifdef JPEG_INTERNALS -+ -+/* Define this if your compiler implements ">>" on signed values as a logical -+ * (unsigned) shift; leave it undefined if ">>" is a signed (arithmetic) shift, -+ * which is the normal and rational definition. -+ */ -+#cmakedefine RIGHT_SHIFT_IS_UNSIGNED -+ -+ -+#endif /* JPEG_INTERNALS */ -+ -+ -+/* -+ * The remaining options do not affect the JPEG library proper, -+ * but only the sample applications cjpeg/djpeg (see cjpeg.c, djpeg.c). -+ * Other applications can ignore these. -+ */ -+ -+#ifdef JPEG_CJPEG_DJPEG -+ -+/* These defines indicate which image (non-JPEG) file formats are allowed. */ -+ -+#cmakedefine BMP_SUPPORTED /* BMP image file format */ -+#cmakedefine GIF_SUPPORTED /* GIF image file format */ -+#cmakedefine PPM_SUPPORTED /* PBMPLUS PPM/PGM image file format */ -+#cmakedefine RLE_SUPPORTED /* Utah RLE image file format */ -+#cmakedefine TARGA_SUPPORTED /* Targa image file format */ -+ -+/* -+ * This defines the default output format for djpeg. Must be one of the FMT_* -+ * enums found in djpeg.c or djpegalt.c -+ */ -+#cmakedefine DEFAULT_FMT @DEFAULT_FMT@ -+ -+/* Define this if you want to name both input and output files on the command -+ * line, rather than using stdout and optionally stdin. You MUST do this if -+ * your system can't cope with binary I/O to stdin/stdout. See comments at -+ * head of cjpeg.c or djpeg.c. -+ */ -+#cmakedefine TWO_FILE_COMMANDLINE -+ -+/* Define this if your system needs explicit cleanup of temporary files. -+ * This is crucial under MS-DOS, where the temporary "files" may be areas -+ * of extended memory; on most other systems it's not as important. -+ */ -+#cmakedefine NEED_SIGNAL_CATCHER -+ -+/* By default, we open image files with fopen(...,"rb") or fopen(...,"wb"). -+ * This is necessary on systems that distinguish text files from binary files, -+ * and is harmless on most systems that don't. If you have one of the rare -+ * systems that complains about the "b" spec, define this symbol. -+ */ -+#cmakedefine DONT_USE_B_MODE -+ -+/* Define this if you want percent-done progress reports from cjpeg/djpeg. -+ */ -+#cmakedefine PROGRESS_REPORT -+ -+/* Define this if you *don't* want overwrite confirmation */ -+#cmakedefine NO_OVERWRITE_CHECK -+ -+#endif /* JPEG_CJPEG_DJPEG */ -diff -ruN jpeg-9f/jmorecfg.h jpeg-9f-new/jmorecfg.h ---- jpeg-9f/jmorecfg.h 2022-03-31 19:41:26.000000000 +1000 -+++ jpeg-9f-new/jmorecfg.h 2024-03-23 21:20:25.514814400 +1000 -@@ -244,8 +244,13 @@ - #define LOCAL(type) static type - /* a function referenced thru EXTERNs: */ - #define GLOBAL(type) type -+ - /* a reference to a GLOBAL function: */ --#define EXTERN(type) extern type -+#ifdef COMPILING_LIBJPEG -+#define EXTERN(type) __declspec(dllexport) extern type -+#else -+#define EXTERN(type) __declspec(dllimport) extern type -+#endif - - - /* This macro is used to declare a "method", that is, a function pointer. -diff -ruN jpeg-9f/libjpeg.pc.cmakein jpeg-9f-new/libjpeg.pc.cmakein ---- jpeg-9f/libjpeg.pc.cmakein 1970-01-01 10:00:00.000000000 +1000 -+++ jpeg-9f-new/libjpeg.pc.cmakein 2024-03-23 21:06:13.922695100 +1000 -@@ -0,0 +1,10 @@ -+prefix=@CMAKE_INSTALL_PREFIX@ -+exec_prefix=${prefix} -+libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@ -+includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ -+ -+Name: libjpeg -+Description: Reads and writes JPEG files -+Version: @JPEG_LIB_VERSION_MAJOR@.@JPEG_LIB_VERSION_MINOR@.0 -+Libs: -L${libdir} -ljpeg -+Cflags: -I${includedir} diff --git a/common/vsprops/LinkPCSX2Deps.props b/common/vsprops/LinkPCSX2Deps.props index 562be5a6d1..7cb1add662 100644 --- a/common/vsprops/LinkPCSX2Deps.props +++ b/common/vsprops/LinkPCSX2Deps.props @@ -4,13 +4,13 @@ $(DepsLibDir);%(AdditionalLibraryDirectories) - %(AdditionalDependencies);freetype.lib;libjpeg.lib;libpng16.lib;libwebp.lib;lz4.lib;SDL3.lib;zlib.lib;zstd.lib;kddockwidgets-qt62.lib + %(AdditionalDependencies);freetype.lib;jpeg.lib;libpng16.lib;libwebp.lib;lz4.lib;SDL3.lib;zlib.lib;zstd.lib;kddockwidgets-qt62.lib - + diff --git a/pcsx2/CMakeLists.txt b/pcsx2/CMakeLists.txt index 20560112e5..93467e202f 100644 --- a/pcsx2/CMakeLists.txt +++ b/pcsx2/CMakeLists.txt @@ -1282,7 +1282,7 @@ function(setup_main_executable target) # Copy dependency libraries. set(DEPS_BINDIR "${CMAKE_SOURCE_DIR}/deps/bin") - set(DEPS_TO_COPY freetype.dll harfbuzz.dll libjpeg.dll libpng16.dll libsharpyuv.dll libwebp.dll lz4.dll SDL3.dll shaderc_shared.dll zlib1.dll zstd.dll kddockwidgets-qt62.dll) + set(DEPS_TO_COPY freetype.dll harfbuzz.dll jpeg62.dll libpng16.dll libsharpyuv.dll libwebp.dll lz4.dll SDL3.dll shaderc_shared.dll zlib1.dll zstd.dll kddockwidgets-qt62.dll) foreach(DEP_TO_COPY ${DEPS_TO_COPY}) install(FILES "${DEPS_BINDIR}/${DEP_TO_COPY}" DESTINATION "${CMAKE_SOURCE_DIR}/bin") endforeach() From c049a7ac76a3c53ae966321b692e5d6ac6965ca2 Mon Sep 17 00:00:00 2001 From: JordanTheToaster Date: Fri, 14 Mar 2025 14:13:05 +0000 Subject: [PATCH 006/162] Docs: Update thirdpartylicenses --- bin/docs/ThirdPartyLicenses.html | 55 +++++++++++++------------------- 1 file changed, 22 insertions(+), 33 deletions(-) diff --git a/bin/docs/ThirdPartyLicenses.html b/bin/docs/ThirdPartyLicenses.html index c52ebf5a8e..c8e5434196 100644 --- a/bin/docs/ThirdPartyLicenses.html +++ b/bin/docs/ThirdPartyLicenses.html @@ -1221,43 +1221,32 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -

libjpeg - https://ijg.org/

+

libjpeg-turbo - https://github.com/libjpeg-turbo/libjpeg-turbo/

-The authors make NO WARRANTY or representation, either express or implied,
-with respect to this software, its quality, accuracy, merchantability, or
-fitness for a particular purpose.  This software is provided "AS IS", and you,
-its user, assume the entire risk as to its quality and accuracy.
+Copyright (C)2009-2024 D. R. Commander. All Rights Reserved.
+Copyright (C)2015 Viktor Szathmáry. All Rights Reserved.
 
-This software is copyright (C) 1991-2024, Thomas G. Lane, Guido Vollbeding.
-All Rights Reserved except as specified below.
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
 
-Permission is hereby granted to use, copy, modify, and distribute this
-software (or portions thereof) for any purpose, without fee, subject to these
-conditions:
-(1) If any part of the source code for this software is distributed, then this
-README file must be included, with this copyright and no-warranty notice
-unaltered; and any additions, deletions, or changes to the original files
-must be clearly indicated in accompanying documentation.
-(2) If only executable code is distributed, then the accompanying
-documentation must state that "this software is based in part on the work of
-the Independent JPEG Group".
-(3) Permission for use of this software is granted only if the user accepts
-full responsibility for any undesirable consequences; the authors accept
-NO LIABILITY for damages of any kind.
+Redistributions of source code must retain the above copyright notice, this list of
+conditions and the following disclaimer.
+Redistributions in binary form must reproduce the above copyright notice, this list
+of conditions and the following disclaimer in the documentation and/or other materials
+provided with the distribution. Neither the name of the libjpeg-turbo Project nor the
+names of its contributors may be used to endorse or promote products derived from this
+software without specific prior written permission.
 
-These conditions apply to any software derived from or based on the IJG code,
-not just to the unmodified library.  If you use our work, you ought to
-acknowledge us.
-
-Permission is NOT granted for the use of any IJG author's name or company name
-in advertising or publicity relating to this software or products derived from
-it.  This software may be referred to only as "the Independent JPEG Group's
-software".
-
-We specifically permit and encourage the use of this software as the basis of
-commercial products, provided that all warranty or liability claims are
-assumed by the product vendor.
-
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

LLVM - https://github.com/llvm/llvm-project

                                 Apache License

From 96599f26a5d32d18f6b73a164888edebf609575c Mon Sep 17 00:00:00 2001
From: refractionpcsx2 
Date: Sun, 16 Mar 2025 22:16:45 +0000
Subject: [PATCH 007/162] GS/HW: Fix width prediction on texture shuffles

---
 pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp
index f30f906f00..7d307f797a 100644
--- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp
+++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp
@@ -504,7 +504,7 @@ void GSRendererHW::ConvertSpriteTextureShuffle(u32& process_rg, u32& process_ba,
 
 				const int width_diff = static_cast(m_env.CTXT[m_env.PRIM.CTXT].TEX0.TBW) - static_cast((m_cached_ctx.FRAME.FBW + 1) >> 1);
 				// We can check the future for a clue as this can be more accurate, be careful of different draws like channel shuffles or single page draws.
-				if (m_env.CTXT[m_env.PRIM.CTXT].TEX0.TBP0 == m_cached_ctx.FRAME.Block() && GSLocalMemory::m_psm[m_env.CTXT[m_env.PRIM.CTXT].TEX0.PSM].bpp == 32 && width_diff >= 0)
+				if (m_env.PRIM.TME && m_env.CTXT[m_env.PRIM.CTXT].TEX0.TBP0 == m_cached_ctx.FRAME.Block() && GSLocalMemory::m_psm[m_env.CTXT[m_env.PRIM.CTXT].TEX0.PSM].bpp == 32 && width_diff >= 0)
 				{
 					// width_diff will be zero is both are BW == 1, so be careful of that.
 					const bool same_width = width_diff > 0 || (m_cached_ctx.FRAME.FBW == 1 && width_diff == 0);

From b53dfed032c7b5a60367ff1ee4183dc54815970a Mon Sep 17 00:00:00 2001
From: PCSX2 Bot 
Date: Mon, 17 Mar 2025 16:01:35 +0000
Subject: [PATCH 008/162] [ci skip] PAD: Update to latest controller database.

---
 bin/resources/game_controller_db.txt | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/bin/resources/game_controller_db.txt b/bin/resources/game_controller_db.txt
index e695cf9a5e..779ce0b06c 100644
--- a/bin/resources/game_controller_db.txt
+++ b/bin/resources/game_controller_db.txt
@@ -1246,6 +1246,8 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
 05000000bc2000000055000001000000,Betop AX1 BFM,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
 03000000bc2000006412000011010000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b30,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,
 030000006b1400000209000011010000,Bigben,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
+03000000120c0000300e000011010000,Brook Audio Fighting Board PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
+03000000120c0000310e000011010000,Brook Audio Fighting Board PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,
 03000000120c0000200e000011010000,Brook Mars PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,
 03000000120c0000210e000011010000,Brook Mars PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,
 03000000120c0000f70e000011010000,Brook Universal Fighting Board,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,

From ec0760c03eacfa917b65933ddb8b72ccf0e1dbfb Mon Sep 17 00:00:00 2001
From: chaoticgd <43898262+chaoticgd@users.noreply.github.com>
Date: Thu, 13 Mar 2025 17:07:40 +0000
Subject: [PATCH 009/162] Deps: Add an extra include path for KDDockWidgets

---
 cmake/SearchForStuff.cmake | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/cmake/SearchForStuff.cmake b/cmake/SearchForStuff.cmake
index 2e4dacefa9..a182056a5b 100644
--- a/cmake/SearchForStuff.cmake
+++ b/cmake/SearchForStuff.cmake
@@ -119,6 +119,12 @@ add_subdirectory(3rdparty/ccc EXCLUDE_FROM_ALL)
 
 # The docking system for the debugger.
 find_package(KDDockWidgets-qt6 REQUIRED)
+# Add an extra include path to work around a broken include directive.
+# TODO: Remove this the next time we update KDDockWidgets.
+get_target_property(KDDOCKWIDGETS_INCLUDE_DIRECTORY KDAB::kddockwidgets INTERFACE_INCLUDE_DIRECTORIES)
+target_include_directories(KDAB::kddockwidgets INTERFACE
+	${KDDOCKWIDGETS_INCLUDE_DIRECTORY}/kddockwidgets
+)
 
 # Architecture-specific.
 if(_M_X86)

From 08edc8dfab75f10b576862cd07bf111260752760 Mon Sep 17 00:00:00 2001
From: JordanTheToaster 
Date: Tue, 18 Mar 2025 09:37:19 +0000
Subject: [PATCH 010/162] Deps: Update Mac to Qt 6.7.3

---
 .../scripts/macos/build-dependencies-universal.sh    | 12 ++++++------
 .../workflows/scripts/macos/build-dependencies.sh    | 12 ++++++------
 cmake/SearchForStuff.cmake                           |  2 +-
 3 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/.github/workflows/scripts/macos/build-dependencies-universal.sh b/.github/workflows/scripts/macos/build-dependencies-universal.sh
index 7dd7d98fec..66adff76b7 100755
--- a/.github/workflows/scripts/macos/build-dependencies-universal.sh
+++ b/.github/workflows/scripts/macos/build-dependencies-universal.sh
@@ -48,7 +48,7 @@ LIBJPEGTURBO=3.1.0
 LIBWEBP=1.5.0
 FFMPEG=6.0
 MOLTENVK=1.2.9
-QT=6.7.2
+QT=6.7.3
 KDDOCKWIDGETS=2.2.1
 
 SHADERC=2024.1
@@ -85,11 +85,11 @@ eb33e51f49a15e023950cd7825ca74a4a2b43db8354825ac24fc1b7ee09e6fa3  zstd-$ZSTD.tar
 9564c72b1dfd1d6fe6274c5f95a8d989b59854575d4bbee44ade7bc17aa9bc93  libjpeg-turbo-$LIBJPEGTURBO.tar.gz
 57be87c22d9b49c112b6d24bc67d42508660e6b718b3db89c44e47e289137082  ffmpeg-$FFMPEG.tar.xz
 f415a09385030c6510a936155ce211f617c31506db5fbc563e804345f1ecf56e  v$MOLTENVK.tar.gz
-c5f22a5e10fb162895ded7de0963328e7307611c688487b5d152c9ee64767599  qtbase-everywhere-src-$QT.tar.xz
-e1a1d8785fae67d16ad0a443b01d5f32663a6b68d275f1806ebab257485ce5d6  qtimageformats-everywhere-src-$QT.tar.xz
-fb0d1286a35be3583fee34aeb5843c94719e07193bdf1d4d8b0dc14009caef01  qtsvg-everywhere-src-$QT.tar.xz
-58e855ad1b2533094726c8a425766b63a04a0eede2ed85086860e54593aa4b2a  qttools-everywhere-src-$QT.tar.xz
-9845780b5dc1b7279d57836db51aeaf2e4a1160c42be09750616f39157582ca9  qttranslations-everywhere-src-$QT.tar.xz
+8ccbb9ab055205ac76632c9eeddd1ed6fc66936fc56afc2ed0fd5d9e23da3097  qtbase-everywhere-src-$QT.tar.xz
+9fd58144081654c3373768dd96ead294023830927b14fe3d3c1ef641fb324753  qtimageformats-everywhere-src-$QT.tar.xz
+40142cb71fb1e07ad612bc361b67f5d54cd9367f9979ae6b86124a064deda06b  qtsvg-everywhere-src-$QT.tar.xz
+f03bb7df619cd9ac9dba110e30b7bcab5dd88eb8bdc9cc752563b4367233203f  qttools-everywhere-src-$QT.tar.xz
+dcc762acac043b9bb5e4d369b6d6f53e0ecfcf76a408fe0db5f7ef071c9d6dc8  qttranslations-everywhere-src-$QT.tar.xz
 eb3b5f0c16313d34f208d90c2fa1e588a23283eed63b101edd5422be6165d528  shaderc-$SHADERC.tar.gz
 aa27e4454ce631c5a17924ce0624eac736da19fc6f5a2ab15a6c58da7b36950f  shaderc-glslang-$SHADERC_GLSLANG.tar.gz
 5d866ce34a4b6908e262e5ebfffc0a5e11dd411640b5f24c85a80ad44c0d4697  shaderc-spirv-headers-$SHADERC_SPIRVHEADERS.tar.gz
diff --git a/.github/workflows/scripts/macos/build-dependencies.sh b/.github/workflows/scripts/macos/build-dependencies.sh
index 40cdf1a2b9..7bb3baad3c 100755
--- a/.github/workflows/scripts/macos/build-dependencies.sh
+++ b/.github/workflows/scripts/macos/build-dependencies.sh
@@ -30,7 +30,7 @@ LIBJPEGTURBO=3.1.0
 LIBWEBP=1.5.0
 FFMPEG=6.0
 MOLTENVK=1.2.9
-QT=6.7.2
+QT=6.7.3
 KDDOCKWIDGETS=2.2.1
 
 SHADERC=2024.1
@@ -65,11 +65,11 @@ eb33e51f49a15e023950cd7825ca74a4a2b43db8354825ac24fc1b7ee09e6fa3  zstd-$ZSTD.tar
 9564c72b1dfd1d6fe6274c5f95a8d989b59854575d4bbee44ade7bc17aa9bc93  libjpeg-turbo-$LIBJPEGTURBO.tar.gz
 57be87c22d9b49c112b6d24bc67d42508660e6b718b3db89c44e47e289137082  ffmpeg-$FFMPEG.tar.xz
 f415a09385030c6510a936155ce211f617c31506db5fbc563e804345f1ecf56e  v$MOLTENVK.tar.gz
-c5f22a5e10fb162895ded7de0963328e7307611c688487b5d152c9ee64767599  qtbase-everywhere-src-$QT.tar.xz
-e1a1d8785fae67d16ad0a443b01d5f32663a6b68d275f1806ebab257485ce5d6  qtimageformats-everywhere-src-$QT.tar.xz
-fb0d1286a35be3583fee34aeb5843c94719e07193bdf1d4d8b0dc14009caef01  qtsvg-everywhere-src-$QT.tar.xz
-58e855ad1b2533094726c8a425766b63a04a0eede2ed85086860e54593aa4b2a  qttools-everywhere-src-$QT.tar.xz
-9845780b5dc1b7279d57836db51aeaf2e4a1160c42be09750616f39157582ca9  qttranslations-everywhere-src-$QT.tar.xz
+8ccbb9ab055205ac76632c9eeddd1ed6fc66936fc56afc2ed0fd5d9e23da3097  qtbase-everywhere-src-$QT.tar.xz
+9fd58144081654c3373768dd96ead294023830927b14fe3d3c1ef641fb324753  qtimageformats-everywhere-src-$QT.tar.xz
+40142cb71fb1e07ad612bc361b67f5d54cd9367f9979ae6b86124a064deda06b  qtsvg-everywhere-src-$QT.tar.xz
+f03bb7df619cd9ac9dba110e30b7bcab5dd88eb8bdc9cc752563b4367233203f  qttools-everywhere-src-$QT.tar.xz
+dcc762acac043b9bb5e4d369b6d6f53e0ecfcf76a408fe0db5f7ef071c9d6dc8  qttranslations-everywhere-src-$QT.tar.xz
 eb3b5f0c16313d34f208d90c2fa1e588a23283eed63b101edd5422be6165d528  shaderc-$SHADERC.tar.gz
 aa27e4454ce631c5a17924ce0624eac736da19fc6f5a2ab15a6c58da7b36950f  shaderc-glslang-$SHADERC_GLSLANG.tar.gz
 5d866ce34a4b6908e262e5ebfffc0a5e11dd411640b5f24c85a80ad44c0d4697  shaderc-spirv-headers-$SHADERC_SPIRVHEADERS.tar.gz
diff --git a/cmake/SearchForStuff.cmake b/cmake/SearchForStuff.cmake
index a182056a5b..01fbe240f1 100644
--- a/cmake/SearchForStuff.cmake
+++ b/cmake/SearchForStuff.cmake
@@ -105,7 +105,7 @@ disable_compiler_warnings_for_target(cubeb)
 disable_compiler_warnings_for_target(speex)
 
 # Find the Qt components that we need.
-find_package(Qt6 6.7.2 COMPONENTS CoreTools Core GuiTools Gui WidgetsTools Widgets LinguistTools REQUIRED)
+find_package(Qt6 6.7.3 COMPONENTS CoreTools Core GuiTools Gui WidgetsTools Widgets LinguistTools REQUIRED)
 
 if(WIN32)
   add_subdirectory(3rdparty/rainterface EXCLUDE_FROM_ALL)

From f6e2185c9cedfa179d043426b61ca8fbfb5584e1 Mon Sep 17 00:00:00 2001
From: chaoticgd <43898262+chaoticgd@users.noreply.github.com>
Date: Tue, 18 Mar 2025 00:11:17 +0000
Subject: [PATCH 011/162] Debugger: Don't jump to PC if the breakpoint code
 paused the core

---
 pcsx2-qt/Debugger/DebuggerWindow.cpp    | 7 +++++++
 pcsx2-qt/Debugger/DebuggerWindow.h      | 5 +++++
 pcsx2-qt/Debugger/DisassemblyWidget.cpp | 6 ++++--
 3 files changed, 16 insertions(+), 2 deletions(-)

diff --git a/pcsx2-qt/Debugger/DebuggerWindow.cpp b/pcsx2-qt/Debugger/DebuggerWindow.cpp
index 01903fdac4..9f4f55c683 100644
--- a/pcsx2-qt/Debugger/DebuggerWindow.cpp
+++ b/pcsx2-qt/Debugger/DebuggerWindow.cpp
@@ -323,6 +323,13 @@ void DebuggerWindow::onVMPaused()
 			CBreakPoints::SetSkipFirst(BREAKPOINT_IOP, r3000Debug.getPC());
 		});
 	}
+
+	// Stops us from telling the disassembly widget to jump somwhere because
+	// breakpoint code paused the core.
+	if (!CBreakPoints::GetCorePaused())
+		emit onVMActuallyPaused();
+	else
+		CBreakPoints::SetCorePaused(false);
 }
 
 void DebuggerWindow::onVMResumed()
diff --git a/pcsx2-qt/Debugger/DebuggerWindow.h b/pcsx2-qt/Debugger/DebuggerWindow.h
index 40cd195216..a35748b8dd 100644
--- a/pcsx2-qt/Debugger/DebuggerWindow.h
+++ b/pcsx2-qt/Debugger/DebuggerWindow.h
@@ -51,6 +51,11 @@ public slots:
 	void onStepOver();
 	void onStepOut();
 
+Q_SIGNALS:
+	// Only emitted if the pause wasn't a temporary one triggered by the
+	// breakpoint code.
+	void onVMActuallyPaused();
+
 protected:
 	void closeEvent(QCloseEvent* event);
 
diff --git a/pcsx2-qt/Debugger/DisassemblyWidget.cpp b/pcsx2-qt/Debugger/DisassemblyWidget.cpp
index 8edf1c262e..6e2b1eb30d 100644
--- a/pcsx2-qt/Debugger/DisassemblyWidget.cpp
+++ b/pcsx2-qt/Debugger/DisassemblyWidget.cpp
@@ -3,8 +3,9 @@
 
 #include "DisassemblyWidget.h"
 
-#include "Debugger/Breakpoints/BreakpointModel.h"
+#include "Debugger/DebuggerWindow.h"
 #include "Debugger/JsonValueWrapper.h"
+#include "Debugger/Breakpoints/BreakpointModel.h"
 
 #include "DebugTools/DebugInterface.h"
 #include "DebugTools/DisassemblyManager.h"
@@ -35,7 +36,8 @@ DisassemblyWidget::DisassemblyWidget(const DebuggerWidgetParameters& parameters)
 	setContextMenuPolicy(Qt::CustomContextMenu);
 	connect(this, &DisassemblyWidget::customContextMenuRequested, this, &DisassemblyWidget::openContextMenu);
 
-	connect(g_emu_thread, &EmuThread::onVMPaused, this, &DisassemblyWidget::gotoProgramCounterOnPause);
+	connect(g_debugger_window, &DebuggerWindow::onVMActuallyPaused,
+		this, &DisassemblyWidget::gotoProgramCounterOnPause);
 
 	receiveEvent([this](const DebuggerEvents::Refresh& event) -> bool {
 		update();

From aa6471235474a29c214edd3070aaa6126fdc8fd3 Mon Sep 17 00:00:00 2001
From: PCSX2 Bot 
Date: Thu, 20 Mar 2025 00:01:53 +0000
Subject: [PATCH 012/162] [ci skip] Qt: Update Base Translation.

---
 pcsx2-qt/Translations/pcsx2-qt_en.ts | 70 ++++++++++++++--------------
 1 file changed, 35 insertions(+), 35 deletions(-)

diff --git a/pcsx2-qt/Translations/pcsx2-qt_en.ts b/pcsx2-qt/Translations/pcsx2-qt_en.ts
index 8ce7c2ca7c..e297420c2b 100644
--- a/pcsx2-qt/Translations/pcsx2-qt_en.ts
+++ b/pcsx2-qt/Translations/pcsx2-qt_en.ts
@@ -5110,7 +5110,7 @@ Do you want to overwrite?
         
     
     
-        
+        
         Pause
         
     
@@ -5123,160 +5123,160 @@ Do you want to overwrite?
         
     
     
-        
+        
         Copy Address
         
     
     
-        
+        
         Copy Instruction Hex
         
     
     
-        
+        
         NOP Instruction(s)
         
     
     
-        
+        
         Run to Cursor
         
     
     
-        
+        
         Follow Branch
         
     
     
-        
+        
         Add Function
         
     
     
-        
-        
+        
+        
         Rename Function
         
     
     
-        
+        
         Remove Function
         
     
     
-        
-        
+        
+        
         Assemble Error
         
     
     
-        
+        
         Unable to change assembly while core is running
         
     
     
-        
+        
         Assemble Instruction
         
     
     
-        
+        
         Function name
         
     
     
-        
-        
+        
+        
         Rename Function Error
         
     
     
-        
+        
         Function name cannot be nothing.
         
     
     
-        
+        
         No function / symbol is currently selected.
         
     
     
-        
+        
         Go To In Disassembly
         
     
     
-        
+        
         Cannot Go To
         
     
     
-        
+        
         Restore Function Error
         
     
     
-        
+        
         Unable to stub selected address.
         
     
     
-        
+        
         &Copy Instruction Text
         
     
     
-        
+        
         Copy Function Name
         
     
     
-        
+        
         Restore Instruction(s)
         
     
     
-        
+        
         Asse&mble new Instruction(s)
         
     
     
-        
+        
         &Jump to Cursor
         
     
     
-        
+        
         Toggle &Breakpoint
         
     
     
-        
+        
         &Go to Address
         
     
     
-        
+        
         Go to PC on Pause
         
     
     
-        
+        
         Restore Function
         
     
     
-        
+        
         Stub (NOP) Function
         
     
     
-        
+        
         Show &Instruction Bytes
         
     
     
-        
+        
         %1 NOT VALID ADDRESS
         
     

From f6675808fc26ae38a918c9abf813fb9d83542903 Mon Sep 17 00:00:00 2001
From: refractionpcsx2 
Date: Thu, 20 Mar 2025 14:17:01 +0000
Subject: [PATCH 013/162] GS/Runner: Set the screenshot compression low to stop
 slow dump times

---
 pcsx2-gsrunner/Main.cpp | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/pcsx2-gsrunner/Main.cpp b/pcsx2-gsrunner/Main.cpp
index b1d4497c6a..39f6f319ea 100644
--- a/pcsx2-gsrunner/Main.cpp
+++ b/pcsx2-gsrunner/Main.cpp
@@ -113,6 +113,10 @@ bool GSRunner::InitializeConfig()
 	si.SetBoolValue("EmuCore/GS", "FrameLimitEnable", false);
 	si.SetIntValue("EmuCore/GS", "VsyncEnable", false);
 
+	// Force screenshot quality settings to something more performant, overriding any defaults good for users.
+	si.SetIntValue("EmuCore/GS", "ScreenshotFormat", static_cast(GSScreenshotFormat::PNG));
+	si.SetIntValue("EmuCore/GS", "ScreenshotQuality", 10);
+
 	// ensure all input sources are disabled, we're not using them
 	si.SetBoolValue("InputSources", "SDL", false);
 	si.SetBoolValue("InputSources", "XInput", false);

From 58f195fc0481e158e1fb1052decb4a78e16b67eb Mon Sep 17 00:00:00 2001
From: lightningterror <18107717+lightningterror@users.noreply.github.com>
Date: Thu, 20 Mar 2025 17:32:35 +0100
Subject: [PATCH 014/162] GS/HW: Adjust AA1 draw behavior.

Make sure we use coverage alpha on aa1 draw when there's no blending.

Make sure we set coverage alpha to 128 in vertex trace alpha min max.

Some const and cast cleanup.
---
 pcsx2/GS/Renderers/Common/GSVertexTrace.cpp | 23 ++++++++++++++-------
 pcsx2/GS/Renderers/HW/GSRendererHW.cpp      |  1 +
 2 files changed, 16 insertions(+), 8 deletions(-)

diff --git a/pcsx2/GS/Renderers/Common/GSVertexTrace.cpp b/pcsx2/GS/Renderers/Common/GSVertexTrace.cpp
index c431c86d3f..469ec14207 100644
--- a/pcsx2/GS/Renderers/Common/GSVertexTrace.cpp
+++ b/pcsx2/GS/Renderers/Common/GSVertexTrace.cpp
@@ -20,10 +20,10 @@ void GSVertexTrace::Update(const void* vertex, const u16* index, int v_count, in
 
 	m_primclass = primclass;
 
-	u32 iip = m_state->PRIM->IIP;
-	u32 tme = m_state->PRIM->TME;
-	u32 fst = m_state->PRIM->FST;
-	u32 color = !(m_state->PRIM->TME && m_state->m_context->TEX0.TFX == TFX_DECAL && m_state->m_context->TEX0.TCC);
+	const u32 iip = m_state->PRIM->IIP;
+	const u32 tme = m_state->PRIM->TME;
+	const u32 fst = m_state->PRIM->FST;
+	const u32 color = !(m_state->PRIM->TME && m_state->m_context->TEX0.TFX == TFX_DECAL && m_state->m_context->TEX0.TCC);
 
 	m_fmm[color][fst][tme][iip][primclass](*this, vertex, index, i_count);
 
@@ -36,6 +36,13 @@ void GSVertexTrace::Update(const void* vertex, const u16* index, int v_count, in
 		m_accurate_stq = true;
 	}
 
+	// AA1: Set alpha min max to coverage 128 when there is no alpha blending.
+	if (!m_state->PRIM->ABE && m_state->PRIM->AA1 && (m_primclass == GS_LINE_CLASS || m_primclass == GS_TRIANGLE_CLASS))
+	{
+		m_min.c.a = 128;
+		m_max.c.a = 128;
+	}
+
 	m_eq.value = (m_min.c == m_max.c).mask() | ((m_min.p == m_max.p).mask() << 16) | ((m_min.t == m_max.t).mask() << 20);
 
 	m_alpha.valid = false;
@@ -46,7 +53,7 @@ void GSVertexTrace::Update(const void* vertex, const u16* index, int v_count, in
 		CorrectDepthTrace(vertex, v_count);
 	}
 
-	if (m_state->PRIM->TME)
+	if (tme)
 	{
 		const GIFRegTEX1& TEX1 = m_state->m_context->TEX1;
 
@@ -59,17 +66,17 @@ void GSVertexTrace::Update(const void* vertex, const u16* index, int v_count, in
 		}
 		else
 		{
-			float K = (float)TEX1.K / 16;
+			const float K = static_cast(TEX1.K) / 16;
 
 			if (TEX1.LCM == 0 && m_state->PRIM->FST == 0) // FST == 1 => Q is not interpolated
 			{
 				// LOD = log2(1/|Q|) * (1 << L) + K
 
-				GSVector4::storel(&m_lod, m_max.t.uph(m_min.t).log2(3).neg() * (float)(1 << TEX1.L) + K);
+				GSVector4::storel(&m_lod, m_max.t.uph(m_min.t).log2(3).neg() * static_cast(1 << TEX1.L) + K);
 
 				if (m_lod.x > m_lod.y)
 				{
-					float tmp = m_lod.x;
+					const float tmp = m_lod.x;
 					m_lod.x = m_lod.y;
 					m_lod.y = tmp;
 				}
diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp
index 7d307f797a..981be4bff7 100644
--- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp
+++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp
@@ -6177,6 +6177,7 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
 	{
 		m_conf.blend = {}; // No blending please
 		m_conf.ps.no_color1 = true;
+		m_conf.ps.fixed_one_a = IsCoverageAlpha();
 
 		if (can_scale_rt_alpha && !new_scale_rt_alpha && m_conf.colormask.wa)
 		{

From d373cb602db0ac756ac141494e03543c185fa471 Mon Sep 17 00:00:00 2001
From: Ziemas 
Date: Fri, 21 Mar 2025 22:58:44 +0100
Subject: [PATCH 015/162] IOP Debug: allow reading ROM

---
 pcsx2/DebugTools/DebugInterface.cpp | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/pcsx2/DebugTools/DebugInterface.cpp b/pcsx2/DebugTools/DebugInterface.cpp
index 99b4b7ab8f..403208f6f2 100644
--- a/pcsx2/DebugTools/DebugInterface.cpp
+++ b/pcsx2/DebugTools/DebugInterface.cpp
@@ -1029,6 +1029,11 @@ bool R3000DebugInterface::isValidAddress(u32 addr)
 		return true;
 	}
 
+	if (addr >= 0x1FC00000 && addr < 0x20000000)
+	{
+		return true;
+	}
+
 	if (addr < 0x200000)
 	{
 		return true;

From c8b1e4c4e6e5cbeaaaf23f5d5862cfe2b72c2652 Mon Sep 17 00:00:00 2001
From: JordanTheToaster 
Date: Thu, 20 Mar 2025 15:01:24 +0000
Subject: [PATCH 016/162] 3rdparty: Update ImGui to v1.91.9b

---
 3rdparty/imgui/include/imconfig.h       |    3 +-
 3rdparty/imgui/include/imgui.h          |  119 +-
 3rdparty/imgui/include/imgui_internal.h |  116 +-
 3rdparty/imgui/src/imgui.cpp            |  511 ++--
 3rdparty/imgui/src/imgui_demo.cpp       | 3112 ++++++++++++-----------
 3rdparty/imgui/src/imgui_draw.cpp       |  188 +-
 3rdparty/imgui/src/imgui_freetype.cpp   |   64 +-
 3rdparty/imgui/src/imgui_tables.cpp     |   94 +-
 3rdparty/imgui/src/imgui_widgets.cpp    |  173 +-
 9 files changed, 2413 insertions(+), 1967 deletions(-)

diff --git a/3rdparty/imgui/include/imconfig.h b/3rdparty/imgui/include/imconfig.h
index 83f1b849e2..66eed8f767 100644
--- a/3rdparty/imgui/include/imconfig.h
+++ b/3rdparty/imgui/include/imconfig.h
@@ -88,8 +88,7 @@
 
 //---- Use FreeType + plutosvg or lunasvg to render OpenType SVG fonts (SVGinOT)
 // Only works in combination with IMGUI_ENABLE_FREETYPE.
-// - lunasvg is currently easier to acquire/install, as e.g. it is part of vcpkg.
-// - plutosvg will support more fonts and may load them faster. It currently requires to be built manually but it is fairly easy. See misc/freetype/README for instructions.
+// - plutosvg is currently easier to install, as e.g. it is part of vcpkg. It will support more fonts and may load them faster. See misc/freetype/README for instructions.
 // - Both require headers to be available in the include path + program to be linked with the library code (not provided).
 // - (note: lunasvg implementation is based on Freetype's rsvg-port.c which is licensed under CeCILL-C Free Software License Agreement)
 //#define IMGUI_ENABLE_FREETYPE_PLUTOSVG
diff --git a/3rdparty/imgui/include/imgui.h b/3rdparty/imgui/include/imgui.h
index 7b037a5840..75750ae109 100644
--- a/3rdparty/imgui/include/imgui.h
+++ b/3rdparty/imgui/include/imgui.h
@@ -1,4 +1,4 @@
-// dear imgui, v1.91.8
+// dear imgui, v1.91.9b
 // (headers)
 
 // Help:
@@ -28,8 +28,8 @@
 
 // Library Version
 // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345')
-#define IMGUI_VERSION       "1.91.8"
-#define IMGUI_VERSION_NUM   19180
+#define IMGUI_VERSION       "1.91.9b"
+#define IMGUI_VERSION_NUM   19191
 #define IMGUI_HAS_TABLE
 
 /*
@@ -37,6 +37,7 @@
 Index of this file:
 // [SECTION] Header mess
 // [SECTION] Forward declarations and basic types
+// [SECTION] Texture identifier (ImTextureID)
 // [SECTION] Dear ImGui end-user API functions
 // [SECTION] Flags & Enumerations
 // [SECTION] Tables API flags and structures (ImGuiTableFlags, ImGuiTableColumnFlags, ImGuiTableRowFlags, ImGuiTableBgTarget, ImGuiTableSortSpecs, ImGuiTableColumnSortSpecs)
@@ -250,22 +251,6 @@ typedef int ImGuiTreeNodeFlags;     // -> enum ImGuiTreeNodeFlags_   // Flags: f
 typedef int ImGuiViewportFlags;     // -> enum ImGuiViewportFlags_   // Flags: for ImGuiViewport
 typedef int ImGuiWindowFlags;       // -> enum ImGuiWindowFlags_     // Flags: for Begin(), BeginChild()
 
-// ImTexture: user data for renderer backend to identify a texture [Compile-time configurable type]
-// - To use something else than an opaque void* pointer: override with e.g. '#define ImTextureID MyTextureType*' in your imconfig.h file.
-// - This can be whatever to you want it to be! read the FAQ about ImTextureID for details.
-// - You can make this a structure with various constructors if you need. You will have to implement ==/!= operators.
-// - (note: before v1.91.4 (2024/10/08) the default type for ImTextureID was void*. Use intermediary intptr_t cast and read FAQ if you have casting warnings)
-#ifndef ImTextureID
-typedef ImU64 ImTextureID;          // Default: store a pointer or an integer fitting in a pointer (most renderer backends are ok with that)
-#endif
-
-// ImDrawIdx: vertex index. [Compile-time configurable type]
-// - To use 16-bit indices + allow large meshes: backend need to set 'io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset' and handle ImDrawCmd::VtxOffset (recommended).
-// - To use 32-bit indices: override with '#define ImDrawIdx unsigned int' in your imconfig.h file.
-#ifndef ImDrawIdx
-typedef unsigned short ImDrawIdx;   // Default: 16-bit (for maximum compatibility with renderer backends)
-#endif
-
 // Character types
 // (we generally use UTF-8 encoded string in the API. This is storage specifically for a decoded character used for keyboard input and display)
 typedef unsigned int ImWchar32;     // A single decoded U32 character/code point. We encode them as multi bytes UTF-8 when used in strings.
@@ -315,6 +300,19 @@ struct ImVec4
 };
 IM_MSVC_RUNTIME_CHECKS_RESTORE
 
+//-----------------------------------------------------------------------------
+// [SECTION] Texture identifier (ImTextureID)
+//-----------------------------------------------------------------------------
+
+// ImTexture: user data for renderer backend to identify a texture [Compile-time configurable type]
+// - To use something else than an opaque void* pointer: override with e.g. '#define ImTextureID MyTextureType*' in your imconfig.h file.
+// - This can be whatever to you want it to be! read the FAQ about ImTextureID for details.
+// - You can make this a structure with various constructors if you need. You will have to implement ==/!= operators.
+// - (note: before v1.91.4 (2024/10/08) the default type for ImTextureID was void*. Use intermediary intptr_t cast and read FAQ if you have casting warnings)
+#ifndef ImTextureID
+typedef ImU64 ImTextureID;          // Default: store a pointer or an integer fitting in a pointer (most renderer backends are ok with that)
+#endif
+
 //-----------------------------------------------------------------------------
 // [SECTION] Dear ImGui end-user API functions
 // (Note that ImGui:: being a namespace, you can add extra ImGui:: functions in your own separate file. Please don't modify imgui source files!)
@@ -544,7 +542,7 @@ namespace ImGui
     IMGUI_API void          LabelTextV(const char* label, const char* fmt, va_list args)    IM_FMTLIST(2);
     IMGUI_API void          BulletText(const char* fmt, ...)                                IM_FMTARGS(1); // shortcut for Bullet()+Text()
     IMGUI_API void          BulletTextV(const char* fmt, va_list args)                      IM_FMTLIST(1);
-    IMGUI_API void          SeparatorText(const char* label);                               // currently: formatted text with an horizontal line
+    IMGUI_API void          SeparatorText(const char* label);                               // currently: formatted text with a horizontal line
 
     // Widgets: Main
     // - Most widgets return true when the value has been changed or when pressed/selected
@@ -566,9 +564,10 @@ namespace ImGui
     // Widgets: Images
     // - Read about ImTextureID here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples
     // - 'uv0' and 'uv1' are texture coordinates. Read about them from the same link above.
-    // - Note that Image() may add +2.0f to provided size if a border is visible, ImageButton() adds style.FramePadding*2.0f to provided size.
+    // - Image() pads adds style.ImageBorderSize on each side, ImageButton() adds style.FramePadding on each side.
     // - ImageButton() draws a background based on regular Button() color + optionally an inner background if specified.
-    IMGUI_API void          Image(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1), const ImVec4& tint_col = ImVec4(1, 1, 1, 1), const ImVec4& border_col = ImVec4(0, 0, 0, 0));
+    IMGUI_API void          Image(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1));
+    IMGUI_API void          ImageWithBg(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1), const ImVec4& bg_col = ImVec4(0, 0, 0, 0), const ImVec4& tint_col = ImVec4(1, 1, 1, 1));
     IMGUI_API bool          ImageButton(const char* str_id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1), const ImVec4& bg_col = ImVec4(0, 0, 0, 0), const ImVec4& tint_col = ImVec4(1, 1, 1, 1));
 
     // Widgets: Combo Box (Dropdown)
@@ -906,7 +905,7 @@ namespace ImGui
     IMGUI_API void          PopClipRect();
 
     // Focus, Activation
-    IMGUI_API void          SetItemDefaultFocus();                                              // make last item the default focused item of of a newly appearing window.
+    IMGUI_API void          SetItemDefaultFocus();                                              // make last item the default focused item of a newly appearing window.
     IMGUI_API void          SetKeyboardFocusHere(int offset = 0);                               // focus keyboard on the next widget. Use positive 'offset' to access sub components of a multiple component widget. Use -1 to access previous widget.
 
     // Keyboard/Gamepad Navigation
@@ -974,7 +973,7 @@ namespace ImGui
     IMGUI_API bool          IsKeyReleased(ImGuiKey key);                                        // was key released (went from Down to !Down)?
     IMGUI_API bool          IsKeyChordPressed(ImGuiKeyChord key_chord);                         // was key chord (mods + key) pressed, e.g. you can pass 'ImGuiMod_Ctrl | ImGuiKey_S' as a key-chord. This doesn't do any routing or focus check, please consider using Shortcut() function instead.
     IMGUI_API int           GetKeyPressedAmount(ImGuiKey key, float repeat_delay, float rate);  // uses provided repeat rate/delay. return a count, most often 0 or 1 but might be >1 if RepeatRate is small enough that DeltaTime > RepeatRate
-    IMGUI_API const char*   GetKeyName(ImGuiKey key);                                           // [DEBUG] returns English name of the key. Those names a provided for debugging purpose and are not meant to be saved persistently not compared.
+    IMGUI_API const char*   GetKeyName(ImGuiKey key);                                           // [DEBUG] returns English name of the key. Those names are provided for debugging purpose and are not meant to be saved persistently nor compared.
     IMGUI_API void          SetNextFrameWantCaptureKeyboard(bool want_capture_keyboard);        // Override io.WantCaptureKeyboard flag next frame (said flag is left for your application to handle, typically when true it instructs your app to ignore inputs). e.g. force capture keyboard when your widget is being hovered. This is equivalent to setting "io.WantCaptureKeyboard = want_capture_keyboard"; after the next NewFrame() call.
 
     // Inputs Utilities: Shortcut Testing & Routing [BETA]
@@ -1177,7 +1176,7 @@ enum ImGuiInputTextFlags_
     ImGuiInputTextFlags_NoUndoRedo          = 1 << 16,  // Disable undo/redo. Note that input text owns the text data while active, if you want to provide your own undo/redo stack you need e.g. to call ClearActiveID().
 
     // Elide display / Alignment
-    ImGuiInputTextFlags_ElideLeft			= 1 << 17,	// When text doesn't fit, elide left side to ensure right side stays visible. Useful for path/filenames. Single-line only!
+    ImGuiInputTextFlags_ElideLeft           = 1 << 17,  // When text doesn't fit, elide left side to ensure right side stays visible. Useful for path/filenames. Single-line only!
 
     // Callback features
     ImGuiInputTextFlags_CallbackCompletion  = 1 << 18,  // Callback on pressing TAB (for completion handling)
@@ -1492,6 +1491,7 @@ enum ImGuiKey : int
     ImGuiKey_KeypadEqual,
     ImGuiKey_AppBack,               // Available on some keyboard/mouses. Often referred as "Browser Back"
     ImGuiKey_AppForward,
+    ImGuiKey_Oem102,                // Non-US backslash.
 
     // Gamepad (some of those are analog values, 0.0f to 1.0f)                          // NAVIGATION ACTION
     // (download controller mapping PNG/PSD at http://dearimgui.com/controls_sheets)
@@ -1714,6 +1714,7 @@ enum ImGuiStyleVar_
     ImGuiStyleVar_ScrollbarRounding,        // float     ScrollbarRounding
     ImGuiStyleVar_GrabMinSize,              // float     GrabMinSize
     ImGuiStyleVar_GrabRounding,             // float     GrabRounding
+    ImGuiStyleVar_ImageBorderSize,          // float     ImageBorderSize
     ImGuiStyleVar_TabRounding,              // float     TabRounding
     ImGuiStyleVar_TabBorderSize,            // float     TabBorderSize
     ImGuiStyleVar_TabBarBorderSize,         // float     TabBarBorderSize
@@ -1833,6 +1834,8 @@ enum ImGuiMouseCursor_
     ImGuiMouseCursor_ResizeNESW,        // When hovering over the bottom-left corner of a window
     ImGuiMouseCursor_ResizeNWSE,        // When hovering over the bottom-right corner of a window
     ImGuiMouseCursor_Hand,              // (Unused by Dear ImGui functions. Use for e.g. hyperlinks)
+    ImGuiMouseCursor_Wait,              // When waiting for something to process/load.
+    ImGuiMouseCursor_Progress,          // When waiting for something to process/load, but application is still interactive.
     ImGuiMouseCursor_NotAllowed,        // When hovering something with disallowed interaction. Usually a crossed circle.
     ImGuiMouseCursor_COUNT
 };
@@ -2146,6 +2149,7 @@ struct ImGuiStyle
     ImVec2      WindowPadding;              // Padding within a window.
     float       WindowRounding;             // Radius of window corners rounding. Set to 0.0f to have rectangular windows. Large values tend to lead to variety of artifacts and are not recommended.
     float       WindowBorderSize;           // Thickness of border around windows. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly).
+    float       WindowBorderHoverPadding;   // Hit-testing extent outside/inside resizing border. Also extend determination of hovered window. Generally meaningfully larger than WindowBorderSize to make it easy to reach borders.
     ImVec2      WindowMinSize;              // Minimum window size. This is a global setting. If you want to constrain individual windows, use SetNextWindowSizeConstraints().
     ImVec2      WindowTitleAlign;           // Alignment for title bar text. Defaults to (0.0f,0.5f) for left-aligned,vertically centered.
     ImGuiDir    WindowMenuButtonPosition;   // Side of the collapsing/docking button in the title bar (None/Left/Right). Defaults to ImGuiDir_Left.
@@ -2167,9 +2171,11 @@ struct ImGuiStyle
     float       GrabMinSize;                // Minimum width/height of a grab box for slider/scrollbar.
     float       GrabRounding;               // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs.
     float       LogSliderDeadzone;          // The size in pixels of the dead-zone around zero on logarithmic sliders that cross zero.
+    float       ImageBorderSize;            // Thickness of border around Image() calls.
     float       TabRounding;                // Radius of upper corners of a tab. Set to 0.0f to have rectangular tabs.
     float       TabBorderSize;              // Thickness of border around tabs.
-    float       TabMinWidthForCloseButton;  // Minimum width for close button to appear on an unselected tab when hovered. Set to 0.0f to always show when hovering, set to FLT_MAX to never show close button unless selected.
+    float       TabCloseButtonMinWidthSelected;     // -1: always visible. 0.0f: visible when hovered. >0.0f: visible when hovered if minimum width.
+    float       TabCloseButtonMinWidthUnselected;   // -1: always visible. 0.0f: visible when hovered. >0.0f: visible when hovered if minimum width. FLT_MAX: never show close button when unselected.
     float       TabBarBorderSize;           // Thickness of tab-bar separator, which takes on the tab active color to denote focus.
     float       TabBarOverlineSize;         // Thickness of tab-bar overline, which highlights the selected tab-bar.
     float       TableAngledHeadersAngle;    // Angle of angled headers (supported values range from -50.0f degrees to +50.0f degrees).
@@ -2188,6 +2194,8 @@ struct ImGuiStyle
     bool        AntiAliasedFill;            // Enable anti-aliased edges around filled shapes (rounded rectangles, circles, etc.). Disable if you are really tight on CPU/GPU. Latched at the beginning of the frame (copied to ImDrawList).
     float       CurveTessellationTol;       // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality.
     float       CircleTessellationMaxError; // Maximum error (in pixels) allowed when using AddCircle()/AddCircleFilled() or drawing rounded corner rectangles with no explicit segment count specified. Decrease for higher quality but more geometry.
+
+    // Colors
     ImVec4      Colors[ImGuiCol_COUNT];
 
     // Behaviors
@@ -2200,6 +2208,11 @@ struct ImGuiStyle
 
     IMGUI_API ImGuiStyle();
     IMGUI_API void ScaleAllSizes(float scale_factor);
+
+    // Obsolete names
+#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
+    // TabMinWidthForCloseButton = TabCloseButtonMinWidthUnselected // Renamed in 1.91.9.
+#endif
 };
 
 //-----------------------------------------------------------------------------
@@ -2310,7 +2323,8 @@ struct ImGuiIO
     // - Code should use PushID()/PopID() in loops, or append "##xx" to same-label identifiers.
     // - Empty label e.g. Button("") == same ID as parent widget/node. Use Button("##xx") instead!
     // - See FAQ https://github.com/ocornut/imgui/blob/master/docs/FAQ.md#q-about-the-id-stack-system
-    bool        ConfigDebugHighlightIdConflicts;// = true           // Highlight and show an error message when multiple items have conflicting identifiers.
+    bool        ConfigDebugHighlightIdConflicts;// = true           // Highlight and show an error message popup when multiple items have conflicting identifiers.
+    bool        ConfigDebugHighlightIdConflictsShowItemPicker;//=true // Show "Item Picker" button in aforementioned popup.
 
     // Tools to test correct Begin/End and BeginChild/EndChild behaviors.
     // - Presently Begin()/End() and BeginChild()/EndChild() needs to ALWAYS be called in tandem, regardless of return value of BeginXXX()
@@ -2420,7 +2434,7 @@ struct ImGuiIO
     bool        MouseDownOwned[5];                  // Track if button was clicked inside a dear imgui window or over void blocked by a popup. We don't request mouse capture from the application if click started outside ImGui bounds.
     bool        MouseDownOwnedUnlessPopupClose[5];  // Track if button was clicked inside a dear imgui window.
     bool        MouseWheelRequestAxisSwap;          // On a non-Mac system, holding SHIFT requests WheelY to perform the equivalent of a WheelX event. On a Mac system this is already enforced by the system.
-    bool        MouseCtrlLeftAsRightClick;          // (OSX) Set to true when the current click was a ctrl-click that spawned a simulated right click
+    bool        MouseCtrlLeftAsRightClick;          // (OSX) Set to true when the current click was a Ctrl+click that spawned a simulated right click
     float       MouseDownDuration[5];               // Duration the mouse button has been down (0.0f == just clicked)
     float       MouseDownDurationPrev[5];           // Previous time the mouse button has been down
     float       MouseDragMaxDistanceSqr[5];         // Squared maximum distance of how much mouse has traveled from the clicking point (used for moving thresholds)
@@ -2585,10 +2599,11 @@ struct ImGuiTextBuffer
     ImGuiTextBuffer()   { }
     inline char         operator[](int i) const { IM_ASSERT(Buf.Data != NULL); return Buf.Data[i]; }
     const char*         begin() const           { return Buf.Data ? &Buf.front() : EmptyString; }
-    const char*         end() const             { return Buf.Data ? &Buf.back() : EmptyString; }   // Buf is zero-terminated, so end() will point on the zero-terminator
+    const char*         end() const             { return Buf.Data ? &Buf.back() : EmptyString; } // Buf is zero-terminated, so end() will point on the zero-terminator
     int                 size() const            { return Buf.Size ? Buf.Size - 1 : 0; }
     bool                empty() const           { return Buf.Size <= 1; }
     void                clear()                 { Buf.clear(); }
+    void                resize(int size)        { if (Buf.Size > size) Buf.Data[size] = 0; Buf.resize(size ? size + 1 : 0, 0); } // Similar to resize(0) on ImVector: empty string but don't free buffer.
     void                reserve(int capacity)   { Buf.reserve(capacity); }
     const char*         c_str() const           { return Buf.Data ? Buf.Data : EmptyString; }
     IMGUI_API void      append(const char* str, const char* str_end = NULL);
@@ -2940,6 +2955,13 @@ struct ImGuiSelectionExternalStorage
 #define IM_DRAWLIST_TEX_LINES_WIDTH_MAX     (32)
 #endif
 
+// ImDrawIdx: vertex index. [Compile-time configurable type]
+// - To use 16-bit indices + allow large meshes: backend need to set 'io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset' and handle ImDrawCmd::VtxOffset (recommended).
+// - To use 32-bit indices: override with '#define ImDrawIdx unsigned int' in your imconfig.h file.
+#ifndef ImDrawIdx
+typedef unsigned short ImDrawIdx;   // Default: 16-bit (for maximum compatibility with renderer backends)
+#endif
+
 // ImDrawCallback: Draw callbacks for advanced uses [configurable type: override in imconfig.h]
 // NB: You most likely do NOT need to use draw callbacks just to create your own widget or customized UI rendering,
 // you can poke into the draw list for that! Draw callback may be useful for example to:
@@ -3131,7 +3153,7 @@ struct ImDrawList
 
     // General polygon
     // - Only simple polygons are supported by filling functions (no self-intersections, no holes).
-    // - Concave polygon fill is more expensive than convex one: it has O(N^2) complexity. Provided as a convenience fo user but not used by main library.
+    // - Concave polygon fill is more expensive than convex one: it has O(N^2) complexity. Provided as a convenience for the user but not used by the main library.
     IMGUI_API void  AddPolyline(const ImVec2* points, int num_points, ImU32 col, ImDrawFlags flags, float thickness);
     IMGUI_API void  AddConvexPolyFilled(const ImVec2* points, int num_points, ImU32 col);
     IMGUI_API void  AddConcavePolyFilled(const ImVec2* points, int num_points, ImU32 col);
@@ -3257,11 +3279,12 @@ struct ImFontConfig
     int             OversampleH;            // 0 (2)    // Rasterize at higher quality for sub-pixel positioning. 0 == auto == 1 or 2 depending on size. Note the difference between 2 and 3 is minimal. You can reduce this to 1 for large glyphs save memory. Read https://github.com/nothings/stb/blob/master/tests/oversample/README.md for details.
     int             OversampleV;            // 0 (1)    // Rasterize at higher quality for sub-pixel positioning. 0 == auto == 1. This is not really useful as we don't use sub-pixel positions on the Y axis.
     float           SizePixels;             //          // Size in pixels for rasterizer (more or less maps to the resulting font height).
-    ImVec2          GlyphExtraSpacing;      // 0, 0     // Extra spacing (in pixels) between glyphs when rendered: essentially add to glyph->AdvanceX. Only X axis is supported for now.
+    //ImVec2        GlyphExtraSpacing;      // 0, 0     // (REMOVED IN 1.91.9: use GlyphExtraAdvanceX)
     ImVec2          GlyphOffset;            // 0, 0     // Offset all glyphs from this font input.
     const ImWchar*  GlyphRanges;            // NULL     // THE ARRAY DATA NEEDS TO PERSIST AS LONG AS THE FONT IS ALIVE. Pointer to a user-provided list of Unicode range (2 value per range, values are inclusive, zero-terminated list).
     float           GlyphMinAdvanceX;       // 0        // Minimum AdvanceX for glyphs, set Min to align font icons, set both Min/Max to enforce mono-space font
     float           GlyphMaxAdvanceX;       // FLT_MAX  // Maximum AdvanceX for glyphs
+    float           GlyphExtraAdvanceX;     // 0        // Extra spacing (in pixels) between glyphs. Please contact us if you are using this.
     unsigned int    FontBuilderFlags;       // 0        // Settings for custom font builder. THIS IS BUILDER IMPLEMENTATION DEPENDENT. Leave as zero if unsure.
     float           RasterizerMultiply;     // 1.0f     // Linearly brighten (>1.0f) or darken (<1.0f) font output. Brightening small fonts may be a good workaround to make them more readable. This is a silly thing we may remove in the future.
     float           RasterizerDensity;      // 1.0f     // DPI scale for rasterization, not altering other font metrics: make it easy to swap between e.g. a 100% and a 400% fonts for a zooming display. IMPORTANT: If you increase this it is expected that you increase font scale accordingly, otherwise quality may look lowered.
@@ -3281,7 +3304,7 @@ struct ImFontGlyph
     unsigned int    Colored : 1;        // Flag to indicate glyph is colored and should generally ignore tinting (make it usable with no shift on little-endian as this is used in loops)
     unsigned int    Visible : 1;        // Flag to indicate glyph has no visible pixels (e.g. space). Allow early out when rendering.
     unsigned int    Codepoint : 30;     // 0x0000..0x10FFFF
-    float           AdvanceX;           // Distance to next character (= data from font + ImFontConfig::GlyphExtraSpacing.x baked in)
+    float           AdvanceX;           // Horizontal distance to advance layout with
     float           X0, Y0, X1, Y1;     // Glyph corners
     float           U0, V0, U1, V1;     // Texture coordinates
 };
@@ -3405,12 +3428,12 @@ struct ImFontAtlas
 
     // [Internal]
     IMGUI_API void              CalcCustomRectUV(const ImFontAtlasCustomRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max) const;
-    IMGUI_API bool              GetMouseCursorTexData(ImGuiMouseCursor cursor, ImVec2* out_offset, ImVec2* out_size, ImVec2 out_uv_border[2], ImVec2 out_uv_fill[2]);
 
     //-------------------------------------------
     // Members
     //-------------------------------------------
 
+    // Input
     ImFontAtlasFlags            Flags;              // Build flags (see ImFontAtlasFlags_)
     ImTextureID                 TexID;              // User data to refer to the texture once it has been uploaded to user's graphic systems. It is passed back to you during rendering via the ImDrawCmd structure.
     int                         TexDesiredWidth;    // Texture width desired by user before Build(). Must be a power-of-two. If have many glyphs your graphics API have texture size restrictions you may want to increase texture width to decrease height.
@@ -3430,7 +3453,7 @@ struct ImFontAtlas
     ImVec2                      TexUvWhitePixel;    // Texture coordinates to a white pixel
     ImVector           Fonts;              // Hold all the fonts returned by AddFont*. Fonts[0] is the default font upon calling ImGui::NewFrame(), use ImGui::PushFont()/PopFont() to change the current font.
     ImVector CustomRects;    // Rectangles for packing custom texture data into the atlas.
-    ImVector      ConfigData;         // Configuration data
+    ImVector      Sources;            // Source/configuration data
     ImVec4                      TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1];  // UVs for baked anti-aliased lines
 
     // [Internal] Font builder
@@ -3442,8 +3465,8 @@ struct ImFontAtlas
     int                         PackIdLines;        // Custom texture rectangle ID for baked anti-aliased lines
 
     // [Obsolete]
-    //typedef ImFontAtlasCustomRect    CustomRect;         // OBSOLETED in 1.72+
-    //typedef ImFontGlyphRangesBuilder GlyphRangesBuilder; // OBSOLETED in 1.67+
+    //typedef ImFontAtlasCustomRect    CustomRect;              // OBSOLETED in 1.72+
+    //typedef ImFontGlyphRangesBuilder GlyphRangesBuilder;      // OBSOLETED in 1.67+
 };
 
 // Font runtime data and rendering
@@ -3458,13 +3481,13 @@ struct ImFont
     // [Internal] Members: Hot ~28/40 bytes (for RenderText loop)
     ImVector             IndexLookup;        // 12-16 // out // Sparse. Index glyphs by Unicode code-point.
     ImVector       Glyphs;             // 12-16 // out // All glyphs.
-    const ImFontGlyph*          FallbackGlyph;      // 4-8   // out // = FindGlyph(FontFallbackChar)
+    ImFontGlyph*                FallbackGlyph;      // 4-8   // out // = FindGlyph(FontFallbackChar)
 
     // [Internal] Members: Cold ~32/40 bytes
-    // Conceptually ConfigData[] is the list of font sources merged to create this font.
+    // Conceptually Sources[] is the list of font sources merged to create this font.
     ImFontAtlas*                ContainerAtlas;     // 4-8   // out // What we has been loaded into
-    const ImFontConfig*         ConfigData;         // 4-8   // in  // Pointer within ContainerAtlas->ConfigData to ConfigDataCount instances
-    short                       ConfigDataCount;    // 2     // in  // Number of ImFontConfig involved in creating this font. Usually 1, or >1 when merging multiple font sources into one ImFont.
+    ImFontConfig*               Sources;            // 4-8   // in  // Pointer within ContainerAtlas->Sources[], to SourcesCount instances
+    short                       SourcesCount;       // 2     // in  // Number of ImFontConfig involved in creating this font. Usually 1, or >1 when merging multiple font sources into one ImFont.
     short                       EllipsisCharCount;  // 1     // out // 1 or 3
     ImWchar                     EllipsisChar;       // 2-4   // out // Character used for ellipsis rendering ('...').
     ImWchar                     FallbackChar;       // 2-4   // out // Character used if a glyph isn't found (U+FFFD, '?')
@@ -3479,12 +3502,13 @@ struct ImFont
     // Methods
     IMGUI_API ImFont();
     IMGUI_API ~ImFont();
-    IMGUI_API const ImFontGlyph*FindGlyph(ImWchar c);
-    IMGUI_API const ImFontGlyph*FindGlyphNoFallback(ImWchar c);
+    IMGUI_API ImFontGlyph*      FindGlyph(ImWchar c);
+    IMGUI_API ImFontGlyph*      FindGlyphNoFallback(ImWchar c);
     float                       GetCharAdvance(ImWchar c)       { return ((int)c < IndexAdvanceX.Size) ? IndexAdvanceX[(int)c] : FallbackAdvanceX; }
     bool                        IsLoaded() const                { return ContainerAtlas != NULL; }
-    const char*                 GetDebugName() const            { return ConfigData ? ConfigData->Name : ""; }
+    const char*                 GetDebugName() const            { return Sources ? Sources->Name : ""; }
 
+    // [Internal] Don't use!
     // 'max_width' stops rendering after a certain width (could be turned into a 2d size). FLT_MAX to disable.
     // 'wrap_width' enable automatic word-wrapping across multiple lines to fit into given width. 0.0f to disable.
     IMGUI_API ImVec2            CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end = NULL, const char** remaining = NULL); // utf8
@@ -3498,7 +3522,6 @@ struct ImFont
     IMGUI_API void              GrowIndex(int new_size);
     IMGUI_API void              AddGlyph(const ImFontConfig* src_cfg, ImWchar c, float x0, float y0, float x1, float y1, float u0, float v0, float u1, float v1, float advance_x);
     IMGUI_API void              AddRemapChar(ImWchar dst, ImWchar src, bool overwrite_dst = true); // Makes 'dst' character/glyph points to 'src' character/glyph. Currently needs to be called AFTER fonts have been built.
-    IMGUI_API void              SetGlyphVisible(ImWchar c, bool visible);
     IMGUI_API bool              IsGlyphRangeUnused(unsigned int c_begin, unsigned int c_last);
 };
 
@@ -3552,7 +3575,7 @@ struct ImGuiPlatformIO
     IMGUI_API ImGuiPlatformIO();
 
     //------------------------------------------------------------------
-    // Interface with OS and Platform backend
+    // Input - Interface with OS and Platform backend (most common stuff)
     //------------------------------------------------------------------
 
     // Optional: Access OS clipboard
@@ -3562,7 +3585,7 @@ struct ImGuiPlatformIO
     void*       Platform_ClipboardUserData;
 
     // Optional: Open link/folder/file in OS Shell
-    // (default to use ShellExecuteA() on Windows, system() on Linux/Mac)
+    // (default to use ShellExecuteW() on Windows, system() on Linux/Mac)
     bool        (*Platform_OpenInShellFn)(ImGuiContext* ctx, const char* path);
     void*       Platform_OpenInShellUserData;
 
@@ -3577,7 +3600,7 @@ struct ImGuiPlatformIO
     ImWchar     Platform_LocaleDecimalPoint;     // '.'
 
     //------------------------------------------------------------------
-    // Interface with Renderer Backend
+    // Input - Interface with Renderer Backend
     //------------------------------------------------------------------
 
     // Written by some backends during ImGui_ImplXXXX_RenderDrawData() call to point backend_specific ImGui_ImplXXXX_RenderState* structure.
@@ -3603,6 +3626,8 @@ struct ImGuiPlatformImeData
 #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
 namespace ImGui
 {
+    // OBSOLETED in 1.91.9 (from February 2025)
+    IMGUI_API void      Image(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col); // <-- border_col was removed in favor of ImGuiCol_ImageBorder.
     // OBSOLETED in 1.91.0 (from July 2024)
     static inline void  PushButtonRepeat(bool repeat)                           { PushItemFlag(ImGuiItemFlags_ButtonRepeat, repeat); }
     static inline void  PopButtonRepeat()                                       { PopItemFlag(); }
diff --git a/3rdparty/imgui/include/imgui_internal.h b/3rdparty/imgui/include/imgui_internal.h
index af52d0feae..ddf9ea3e9a 100644
--- a/3rdparty/imgui/include/imgui_internal.h
+++ b/3rdparty/imgui/include/imgui_internal.h
@@ -1,4 +1,4 @@
-// dear imgui, v1.91.8
+// dear imgui, v1.91.9b
 // (internal structures/api)
 
 // You may use this file to debug, understand or extend Dear ImGui features but we don't provide any guarantee of forward compatibility.
@@ -14,6 +14,7 @@ Index of this file:
 // [SECTION] Macros
 // [SECTION] Generic helpers
 // [SECTION] ImDrawList support
+// [SECTION] Style support
 // [SECTION] Data types support
 // [SECTION] Widgets support: flags, enums, data structures
 // [SECTION] Popup support
@@ -145,7 +146,6 @@ struct ImGuiBoxSelectState;         // Box-selection state (currently used by mu
 struct ImGuiColorMod;               // Stacked color modifier, backup of modified data so we can restore it
 struct ImGuiContext;                // Main Dear ImGui context
 struct ImGuiContextHook;            // Hook for extensions like ImGuiTestEngine
-struct ImGuiDataVarInfo;            // Variable information (e.g. to access style variables from an enum)
 struct ImGuiDataTypeInfo;           // Type information associated to a ImGuiDataType enum
 struct ImGuiDeactivatedItemData;    // Data for IsItemDeactivated()/IsItemDeactivatedAfterEdit() function.
 struct ImGuiErrorRecoveryState;     // Storage of stack sizes for error handling and recovery
@@ -166,6 +166,7 @@ struct ImGuiOldColumns;             // Storage data for a columns set for legacy
 struct ImGuiPopupData;              // Storage for current popup stack
 struct ImGuiSettingsHandler;        // Storage for one type registered in the .ini file
 struct ImGuiStyleMod;               // Stacked style modifier, backup of modified data so we can restore it
+struct ImGuiStyleVarInfo;           // Style variable information (e.g. to access style variables from an enum)
 struct ImGuiTabBar;                 // Storage for a tab bar
 struct ImGuiTabItem;                // Storage for a tab item (within a tab bar)
 struct ImGuiTable;                  // Storage for a table
@@ -366,11 +367,14 @@ static inline void      ImQsort(void* base, size_t count, size_t size_of_element
 IMGUI_API ImU32         ImAlphaBlendColors(ImU32 col_a, ImU32 col_b);
 
 // Helpers: Bit manipulation
-static inline bool      ImIsPowerOfTwo(int v)           { return v != 0 && (v & (v - 1)) == 0; }
-static inline bool      ImIsPowerOfTwo(ImU64 v)         { return v != 0 && (v & (v - 1)) == 0; }
-static inline int       ImUpperPowerOfTwo(int v)        { v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; return v; }
+static inline bool      ImIsPowerOfTwo(int v)               { return v != 0 && (v & (v - 1)) == 0; }
+static inline bool      ImIsPowerOfTwo(ImU64 v)             { return v != 0 && (v & (v - 1)) == 0; }
+static inline int       ImUpperPowerOfTwo(int v)            { v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; return v; }
+static inline unsigned int ImCountSetBits(unsigned int v)   { unsigned int count = 0; while (v > 0) { v = v & (v - 1); count++; } return count; }
 
 // Helpers: String
+#define ImStrlen strlen
+#define ImMemchr memchr
 IMGUI_API int           ImStricmp(const char* str1, const char* str2);                      // Case insensitive compare.
 IMGUI_API int           ImStrnicmp(const char* str1, const char* str2, size_t count);       // Case insensitive compare to a certain count.
 IMGUI_API void          ImStrncpy(char* dst, const char* src, size_t count);                // Copy to a certain count and always zero terminate (strncpy doesn't).
@@ -786,8 +790,9 @@ struct IMGUI_API ImDrawListSharedData
     float           FontScale;                  // Current/default font scale (== FontSize / Font->FontSize)
     float           CurveTessellationTol;       // Tessellation tolerance when using PathBezierCurveTo()
     float           CircleSegmentMaxError;      // Number of circle segments to use per pixel of radius for AddCircle() etc
-    ImVec4          ClipRectFullscreen;         // Value for PushClipRectFullscreen()
+    float           InitialFringeScale;         // Initial scale to apply to AA fringe
     ImDrawListFlags InitialFlags;               // Initial flags at the beginning of the frame (it is possible to alter flags on a per-drawlist basis afterwards)
+    ImVec4          ClipRectFullscreen;         // Value for PushClipRectFullscreen()
     ImVector TempBuffer;                // Temporary write buffer
 
     // Lookup tables
@@ -808,17 +813,38 @@ struct ImDrawDataBuilder
 };
 
 //-----------------------------------------------------------------------------
-// [SECTION] Data types support
+// [SECTION] Style support
 //-----------------------------------------------------------------------------
 
-struct ImGuiDataVarInfo
+struct ImGuiStyleVarInfo
 {
-    ImGuiDataType   Type;
-    ImU32           Count;      // 1+
-    ImU32           Offset;     // Offset in parent structure
+    ImU32           Count : 8;      // 1+
+    ImGuiDataType   DataType : 8;
+    ImU32           Offset : 16;    // Offset in parent structure
     void* GetVarPtr(void* parent) const { return (void*)((unsigned char*)parent + Offset); }
 };
 
+// Stacked color modifier, backup of modified data so we can restore it
+struct ImGuiColorMod
+{
+    ImGuiCol        Col;
+    ImVec4          BackupValue;
+};
+
+// Stacked style modifier, backup of modified data so we can restore it. Data type inferred from the variable.
+struct ImGuiStyleMod
+{
+    ImGuiStyleVar   VarIdx;
+    union           { int BackupInt[2]; float BackupFloat[2]; };
+    ImGuiStyleMod(ImGuiStyleVar idx, int v)     { VarIdx = idx; BackupInt[0] = v; }
+    ImGuiStyleMod(ImGuiStyleVar idx, float v)   { VarIdx = idx; BackupFloat[0] = v; }
+    ImGuiStyleMod(ImGuiStyleVar idx, ImVec2 v)  { VarIdx = idx; BackupFloat[0] = v.x; BackupFloat[1] = v.y; }
+};
+
+//-----------------------------------------------------------------------------
+// [SECTION] Data types support
+//-----------------------------------------------------------------------------
+
 struct ImGuiDataTypeStorage
 {
     ImU8        Data[8];        // Opaque storage to fit any data up to ImGuiDataType_COUNT
@@ -836,7 +862,7 @@ struct ImGuiDataTypeInfo
 // Extend ImGuiDataType_
 enum ImGuiDataTypePrivate_
 {
-    ImGuiDataType_Pointer = ImGuiDataType_COUNT + 1,
+    ImGuiDataType_Pointer = ImGuiDataType_COUNT,
     ImGuiDataType_ID,
 };
 
@@ -1037,23 +1063,6 @@ enum ImGuiPlotType
     ImGuiPlotType_Histogram,
 };
 
-// Stacked color modifier, backup of modified data so we can restore it
-struct ImGuiColorMod
-{
-    ImGuiCol        Col;
-    ImVec4          BackupValue;
-};
-
-// Stacked style modifier, backup of modified data so we can restore it. Data type inferred from the variable.
-struct ImGuiStyleMod
-{
-    ImGuiStyleVar   VarIdx;
-    union           { int BackupInt[2]; float BackupFloat[2]; };
-    ImGuiStyleMod(ImGuiStyleVar idx, int v)     { VarIdx = idx; BackupInt[0] = v; }
-    ImGuiStyleMod(ImGuiStyleVar idx, float v)   { VarIdx = idx; BackupFloat[0] = v; }
-    ImGuiStyleMod(ImGuiStyleVar idx, ImVec2 v)  { VarIdx = idx; BackupFloat[0] = v.x; BackupFloat[1] = v.y; }
-};
-
 // Storage data for BeginComboPreview()/EndComboPreview()
 struct IMGUI_API ImGuiComboPreviewData
 {
@@ -1194,14 +1203,17 @@ enum ImGuiNextWindowDataFlags_
     ImGuiNextWindowDataFlags_HasFocus           = 1 << 5,
     ImGuiNextWindowDataFlags_HasBgAlpha         = 1 << 6,
     ImGuiNextWindowDataFlags_HasScroll          = 1 << 7,
-    ImGuiNextWindowDataFlags_HasChildFlags      = 1 << 8,
-    ImGuiNextWindowDataFlags_HasRefreshPolicy   = 1 << 9,
+    ImGuiNextWindowDataFlags_HasWindowFlags     = 1 << 8,
+    ImGuiNextWindowDataFlags_HasChildFlags      = 1 << 9,
+    ImGuiNextWindowDataFlags_HasRefreshPolicy   = 1 << 10,
 };
 
 // Storage for SetNexWindow** functions
 struct ImGuiNextWindowData
 {
-    ImGuiNextWindowDataFlags    Flags;
+    ImGuiNextWindowDataFlags    HasFlags;
+
+    // Members below are NOT cleared. Always rely on HasFlags.
     ImGuiCond                   PosCond;
     ImGuiCond                   SizeCond;
     ImGuiCond                   CollapsedCond;
@@ -1210,6 +1222,7 @@ struct ImGuiNextWindowData
     ImVec2                      SizeVal;
     ImVec2                      ContentSizeVal;
     ImVec2                      ScrollVal;
+    ImGuiWindowFlags            WindowFlags;            // Only honored by BeginTable()
     ImGuiChildFlags             ChildFlags;
     bool                        CollapsedVal;
     ImRect                      SizeConstraintRect;
@@ -1220,7 +1233,7 @@ struct ImGuiNextWindowData
     ImGuiWindowRefreshFlags     RefreshFlagsVal;
 
     ImGuiNextWindowData()       { memset(this, 0, sizeof(*this)); }
-    inline void ClearFlags()    { Flags = ImGuiNextWindowDataFlags_None; }
+    inline void ClearFlags()    { HasFlags = ImGuiNextWindowDataFlags_None; }
 };
 
 enum ImGuiNextItemDataFlags_
@@ -1237,7 +1250,8 @@ struct ImGuiNextItemData
 {
     ImGuiNextItemDataFlags      HasFlags;           // Called HasFlags instead of Flags to avoid mistaking this
     ImGuiItemFlags              ItemFlags;          // Currently only tested/used for ImGuiItemFlags_AllowOverlap and ImGuiItemFlags_HasSelectionUserData.
-    // Non-flags members are NOT cleared by ItemAdd() meaning they are still valid during NavProcessItem()
+
+    // Members below are NOT cleared by ItemAdd() meaning they are still valid during e.g. NavProcessItem(). Always rely on HasFlags.
     ImGuiID                     FocusScopeId;       // Set by SetNextItemSelectionUserData()
     ImGuiSelectionUserData      SelectionUserData;  // Set by SetNextItemSelectionUserData() (note that NULL/0 is a valid value, we use -1 == ImGuiSelectionUserData_Invalid to mark invalid values)
     float                       Width;              // Set by SetNextItemWidth()
@@ -1305,6 +1319,7 @@ struct ImGuiWindowStackData
     ImGuiLastItemData       ParentLastItemDataBackup;
     ImGuiErrorRecoveryState StackSizesInBegin;          // Store size of various stacks for asserting
     bool                    DisabledOverrideReenable;   // Non-child window override disabled flag
+    float                   DisabledOverrideReenableAlphaBackup;
 };
 
 struct ImGuiShrinkWidthItem
@@ -1992,7 +2007,6 @@ struct ImGuiMetricsConfig
     bool        ShowDrawCmdMesh = true;
     bool        ShowDrawCmdBoundingBoxes = true;
     bool        ShowTextEncodingViewer = false;
-    bool        ShowAtlasTintedWithTextColor = false;
     int         ShowWindowsRectsType = -1;
     int         ShowTablesRectsType = -1;
     int         HighlightMonitorIdx = -1;
@@ -2019,6 +2033,7 @@ struct ImGuiIDStackTool
     ImVector Results;
     bool                    CopyToClipboardOnCtrlC;
     float                   CopyToClipboardLastTime;
+    ImGuiTextBuffer         ResultPathBuf;
 
     ImGuiIDStackTool()      { memset(this, 0, sizeof(*this)); CopyToClipboardLastTime = -FLT_MAX; }
 };
@@ -2083,7 +2098,7 @@ struct ImGuiContext
     ImVector CurrentWindowStack;
     ImGuiStorage            WindowsById;                        // Map window's ImGuiID to ImGuiWindow*
     int                     WindowsActiveCount;                 // Number of unique windows submitted by frame
-    ImVec2                  WindowsHoverPadding;                // Padding around resizable windows for which hovering on counts as hovering the window == ImMax(style.TouchExtraPadding, WINDOWS_HOVER_PADDING).
+    float                   WindowsBorderHoverPadding;          // Padding around resizable windows for which hovering on counts as hovering the window == ImMax(style.TouchExtraPadding, style.WindowBorderHoverPadding). This isn't so multi-dpi friendly.
     ImGuiID                 DebugBreakInWindow;                 // Set to break in Begin() call.
     ImGuiWindow*            CurrentWindow;                      // Window being drawn into
     ImGuiWindow*            HoveredWindow;                      // Window the mouse is hovering. Will typically catch mouse inputs.
@@ -2500,6 +2515,8 @@ struct IMGUI_API ImGuiWindow
     ImVec2                  ScrollTargetEdgeSnapDist;           // 0.0f = no snapping, >0.0f snapping threshold
     ImVec2                  ScrollbarSizes;                     // Size taken by each scrollbars on their smaller axis. Pay attention! ScrollbarSizes.x == width of the vertical scrollbar, ScrollbarSizes.y = height of the horizontal scrollbar.
     bool                    ScrollbarX, ScrollbarY;             // Are scrollbars visible?
+    bool                    ScrollbarXStabilizeEnabled;         // Was ScrollbarX previously auto-stabilized?
+    ImU8                    ScrollbarXStabilizeToggledHistory;  // Used to stabilize scrollbar visibility in case of feedback loops
     bool                    Active;                             // Set to true on Begin(), unless Collapsed
     bool                    WasActive;
     bool                    WriteAccessed;                      // Set to true when any widget access the current window
@@ -2787,7 +2804,7 @@ struct IMGUI_API ImGuiTable
 {
     ImGuiID                     ID;
     ImGuiTableFlags             Flags;
-    void*                       RawData;                    // Single allocation to hold Columns[], DisplayOrderToIndex[] and RowCellData[]
+    void*                       RawData;                    // Single allocation to hold Columns[], DisplayOrderToIndex[], and RowCellData[]
     ImGuiTableTempData*         TempData;                   // Transient data while table is active. Point within g.CurrentTableStack[]
     ImSpan    Columns;                    // Point within RawData[]
     ImSpan DisplayOrderToIndex;        // Point within RawData[]. Store display order of columns (when not reordered, the values are 0...Count-1)
@@ -2801,7 +2818,7 @@ struct IMGUI_API ImGuiTable
     int                         ColumnsCount;               // Number of columns declared in BeginTable()
     int                         CurrentRow;
     int                         CurrentColumn;
-    ImS16                       InstanceCurrent;            // Count of BeginTable() calls with same ID in the same frame (generally 0). This is a little bit similar to BeginCount for a window, but multiple table with same ID look are multiple tables, they are just synched.
+    ImS16                       InstanceCurrent;            // Count of BeginTable() calls with same ID in the same frame (generally 0). This is a little bit similar to BeginCount for a window, but multiple tables with the same ID are multiple tables, they are just synced.
     ImS16                       InstanceInteracted;         // Mark which instance (generally 0) of the same ID is being interacted with
     float                       RowPosY1;
     float                       RowPosY2;
@@ -2833,7 +2850,7 @@ struct IMGUI_API ImGuiTable
     float                       AngledHeadersHeight;        // Set by TableAngledHeadersRow(), used in TableUpdateLayout()
     float                       AngledHeadersSlope;         // Set by TableAngledHeadersRow(), used in TableUpdateLayout()
     ImRect                      OuterRect;                  // Note: for non-scrolling table, OuterRect.Max.y is often FLT_MAX until EndTable(), unless a height has been specified in BeginTable().
-    ImRect                      InnerRect;                  // InnerRect but without decoration. As with OuterRect, for non-scrolling tables, InnerRect.Max.y is
+    ImRect                      InnerRect;                  // InnerRect but without decoration. As with OuterRect, for non-scrolling tables, InnerRect.Max.y is "
     ImRect                      WorkRect;
     ImRect                      InnerClipRect;
     ImRect                      BgClipRect;                 // We use this to cpu-clip cell background color fill, evolve during the frame as we cross frozen rows boundaries
@@ -2930,7 +2947,7 @@ struct IMGUI_API ImGuiTableTempData
     ImGuiTableTempData()        { memset(this, 0, sizeof(*this)); LastTimeActive = -1.0f; }
 };
 
-// sizeof() ~ 12
+// sizeof() ~ 16
 struct ImGuiTableColumnSettings
 {
     float                   WidthOrWeight;
@@ -2939,7 +2956,7 @@ struct ImGuiTableColumnSettings
     ImGuiTableColumnIdx     DisplayOrder;
     ImGuiTableColumnIdx     SortOrder;
     ImU8                    SortDirection : 2;
-    ImU8                    IsEnabled : 1; // "Visible" in ini file
+    ImS8                    IsEnabled : 2; // "Visible" in ini file
     ImU8                    IsStretch : 1;
 
     ImGuiTableColumnSettings()
@@ -2949,7 +2966,7 @@ struct ImGuiTableColumnSettings
         Index = -1;
         DisplayOrder = SortOrder = -1;
         SortDirection = ImGuiSortDirection_None;
-        IsEnabled = 1;
+        IsEnabled = -1;
         IsStretch = 0;
     }
 };
@@ -2980,7 +2997,8 @@ namespace ImGui
     // If this ever crashes because g.CurrentWindow is NULL, it means that either:
     // - ImGui::NewFrame() has never been called, which is illegal.
     // - You are calling ImGui functions after ImGui::EndFrame()/ImGui::Render() and before the next ImGui::NewFrame(), which is also illegal.
-    IMGUI_API ImGuiIO&      GetIOEx(ImGuiContext* ctx);
+    IMGUI_API ImGuiIO&         GetIO(ImGuiContext* ctx);
+    IMGUI_API ImGuiPlatformIO& GetPlatformIO(ImGuiContext* ctx);
     inline    ImGuiWindow*  GetCurrentWindowRead()      { ImGuiContext& g = *GImGui; return g.CurrentWindow; }
     inline    ImGuiWindow*  GetCurrentWindow()          { ImGuiContext& g = *GImGui; g.CurrentWindow->WriteAccessed = true; return g.CurrentWindow; }
     IMGUI_API ImGuiWindow*  FindWindowByID(ImGuiID id);
@@ -3108,7 +3126,7 @@ namespace ImGui
     IMGUI_API void          ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess);
 
     // Parameter stacks (shared)
-    IMGUI_API const ImGuiDataVarInfo* GetStyleVarInfo(ImGuiStyleVar idx);
+    IMGUI_API const ImGuiStyleVarInfo* GetStyleVarInfo(ImGuiStyleVar idx);
     IMGUI_API void          BeginDisabledOverrideReenable();
     IMGUI_API void          EndDisabledOverrideReenable();
 
@@ -3123,6 +3141,7 @@ namespace ImGui
 
     // Popups, Modals
     IMGUI_API bool          BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_window_flags);
+    IMGUI_API bool          BeginPopupMenuEx(ImGuiID id, const char* label, ImGuiWindowFlags extra_window_flags);
     IMGUI_API void          OpenPopupEx(ImGuiID id, ImGuiPopupFlags popup_flags = ImGuiPopupFlags_None);
     IMGUI_API void          ClosePopupToLevel(int remaining, bool restore_focus_to_window_under_popup);
     IMGUI_API void          ClosePopupsOverWindow(ImGuiWindow* ref_window, bool restore_focus_to_window_under_popup);
@@ -3474,6 +3493,7 @@ namespace ImGui
     inline bool             TempInputIsActive(ImGuiID id)       { ImGuiContext& g = *GImGui; return (g.ActiveId == id && g.TempInputId == id); }
     inline ImGuiInputTextState* GetInputTextState(ImGuiID id)   { ImGuiContext& g = *GImGui; return (id != 0 && g.InputTextState.ID == id) ? &g.InputTextState : NULL; } // Get input text state if active
     IMGUI_API void          SetNextItemRefVal(ImGuiDataType data_type, void* p_data);
+    inline bool             IsItemActiveAsInputText() { ImGuiContext& g = *GImGui; return g.ActiveId != 0 && g.ActiveId == g.LastItemData.ID && g.InputTextState.ID == g.LastItemData.ID; } // This may be useful to apply workaround that a based on distinguish whenever an item is active as a text input field.
 
     // Color
     IMGUI_API void          ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags);
@@ -3569,16 +3589,18 @@ struct ImFontBuilderIO
 #ifdef IMGUI_ENABLE_STB_TRUETYPE
 IMGUI_API const ImFontBuilderIO* ImFontAtlasGetBuilderForStbTruetype();
 #endif
-IMGUI_API void      ImFontAtlasUpdateConfigDataPointers(ImFontAtlas* atlas);
+IMGUI_API void      ImFontAtlasUpdateSourcesPointers(ImFontAtlas* atlas);
 IMGUI_API void      ImFontAtlasBuildInit(ImFontAtlas* atlas);
-IMGUI_API void      ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* font_config, float ascent, float descent);
+IMGUI_API void      ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src, float ascent, float descent);
 IMGUI_API void      ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* stbrp_context_opaque);
 IMGUI_API void      ImFontAtlasBuildFinish(ImFontAtlas* atlas);
 IMGUI_API void      ImFontAtlasBuildRender8bppRectFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char, unsigned char in_marker_pixel_value);
 IMGUI_API void      ImFontAtlasBuildRender32bppRectFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char, unsigned int in_marker_pixel_value);
 IMGUI_API void      ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], float in_multiply_factor);
 IMGUI_API void      ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, int w, int h, int stride);
-IMGUI_API void      ImFontAtlasBuildGetOversampleFactors(const ImFontConfig* cfg, int* out_oversample_h, int* out_oversample_v);
+IMGUI_API void      ImFontAtlasBuildGetOversampleFactors(const ImFontConfig* src, int* out_oversample_h, int* out_oversample_v);
+
+IMGUI_API bool      ImFontAtlasGetMouseCursorTexData(ImFontAtlas* atlas, ImGuiMouseCursor cursor_type, ImVec2* out_offset, ImVec2* out_size, ImVec2 out_uv_border[2], ImVec2 out_uv_fill[2]);
 
 //-----------------------------------------------------------------------------
 // [SECTION] Test Engine specific hooks (imgui_test_engine)
diff --git a/3rdparty/imgui/src/imgui.cpp b/3rdparty/imgui/src/imgui.cpp
index 9be594c8b0..39a5e9c444 100644
--- a/3rdparty/imgui/src/imgui.cpp
+++ b/3rdparty/imgui/src/imgui.cpp
@@ -1,4 +1,4 @@
-// dear imgui, v1.91.8
+// dear imgui, v1.91.9b
 // (main code and documentation)
 
 // Help:
@@ -142,7 +142,8 @@ CODE
    - CTRL+Shift+Left/Right:         Select words.
    - CTRL+A or Double-Click:        Select All.
    - CTRL+X, CTRL+C, CTRL+V:        Use OS clipboard.
-   - CTRL+Z, CTRL+Y:                Undo, Redo.
+   - CTRL+Z                         Undo.
+   - CTRL+Y or CTRL+Shift+Z:        Redo.
    - ESCAPE:                        Revert text to its original value.
    - On OSX, controls are automatically adjusted to match standard OSX text editing 2ts and behaviors.
 
@@ -430,6 +431,18 @@ CODE
  When you are not sure about an old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all imgui files.
  You can read releases logs https://github.com/ocornut/imgui/releases for more details.
 
+ - 2025/03/05 (1.91.9) - BeginMenu(): Internals: reworked mangling of menu windows to use "###Menu_00" etc. instead of "##Menu_00", allowing them to also store the menu name before it. This shouldn't affect code unless directly accessing menu window from their mangled name.
+ - 2025/02/27 (1.91.9) - Image(): removed 'tint_col' and 'border_col' parameter from Image() function. Added ImageWithBg() replacement. (#8131, #8238)
+                            - old: void Image      (ImTextureID tex_id, ImVec2 image_size, ImVec2 uv0 = (0,0), ImVec2 uv1 = (1,1), ImVec4 tint_col = (1,1,1,1), ImVec4 border_col = (0,0,0,0));
+                            - new: void Image      (ImTextureID tex_id, ImVec2 image_size, ImVec2 uv0 = (0,0), ImVec2 uv1 = (1,1));
+                            - new: void ImageWithBg(ImTextureID tex_id, ImVec2 image_size, ImVec2 uv0 = (0,0), ImVec2 uv1 = (1,1), ImVec4 bg_col = (0,0,0,0), ImVec4 tint_col = (1,1,1,1));
+                            - TL;DR: 'border_col' had misleading side-effect on layout, 'bg_col' was missing, parameter order couldn't be consistent with ImageButton().
+                            - new behavior always use ImGuiCol_Border color + style.ImageBorderSize / ImGuiStyleVar_ImageBorderSize.
+                            - old behavior altered border size (and therefore layout) based on border color's alpha, which caused variety of problems + old behavior a fixed 1.0f for border size which was not tweakable.
+                            - kept legacy signature (will obsolete), which mimics the old behavior,  but uses Max(1.0f, style.ImageBorderSize) when border_col is specified.
+                            - added ImageWithBg() function which has both 'bg_col' (which was missing) and 'tint_col'. It was impossible to add 'bg_col' to Image() with a parameter order consistent with other functions, so we decided to remove 'tint_col' and introduce ImageWithBg().
+ - 2025/02/25 (1.91.9) - internals: fonts: ImFontAtlas::ConfigData[] has been renamed to ImFontAtlas::Sources[]. ImFont::ConfigData[], ConfigDataCount has been renamed to Sources[], SourcesCount.
+ - 2025/02/06 (1.91.9) - renamed ImFontConfig::GlyphExtraSpacing.x to ImFontConfig::GlyphExtraAdvanceX.
  - 2025/01/22 (1.91.8) - removed ImGuiColorEditFlags_AlphaPreview (made value 0): it is now the default behavior.
                          prior to 1.91.8: alpha was made opaque in the preview by default _unless_ using ImGuiColorEditFlags_AlphaPreview. We now display the preview as transparent by default. You can use ImGuiColorEditFlags_AlphaOpaque to use old behavior.
                          the new flags (ImGuiColorEditFlags_AlphaOpaque, ImGuiColorEditFlags_AlphaNoBg + existing ImGuiColorEditFlags_AlphaPreviewHalf) may be combined better and allow finer controls:
@@ -643,7 +656,7 @@ CODE
  - 2022/04/05 (1.88) - inputs: renamed ImGuiKeyModFlags to ImGuiModFlags. Kept inline redirection enums (will obsolete). This was never used in public API functions but technically present in imgui.h and ImGuiIO.
  - 2022/01/20 (1.87) - inputs: reworded gamepad IO.
                         - Backend writing to io.NavInputs[]            -> backend should call io.AddKeyEvent()/io.AddKeyAnalogEvent() with ImGuiKey_GamepadXXX values.
- - 2022/01/19 (1.87) - sliders, drags: removed support for legacy arithmetic operators (+,+-,*,/) when inputing text. This doesn't break any api/code but a feature that used to be accessible by end-users (which seemingly no one used).
+ - 2022/01/19 (1.87) - sliders, drags: removed support for legacy arithmetic operators (+,+-,*,/) when inputting text. This doesn't break any api/code but a feature that used to be accessible by end-users (which seemingly no one used).
  - 2022/01/17 (1.87) - inputs: reworked mouse IO.
                         - Backend writing to io.MousePos               -> backend should call io.AddMousePosEvent()
                         - Backend writing to io.MouseDown[]            -> backend should call io.AddMouseButtonEvent()
@@ -1133,17 +1146,19 @@ CODE
 #pragma clang diagnostic ignored "-Wunknown-pragmas"                // warning: unknown warning group 'xxx'
 #pragma clang diagnostic ignored "-Wold-style-cast"                 // warning: use of old-style cast                            // yes, they are more terse.
 #pragma clang diagnostic ignored "-Wfloat-equal"                    // warning: comparing floating point with == or != is unsafe // storing and comparing against same constants (typically 0.0f) is ok.
+#pragma clang diagnostic ignored "-Wformat"                         // warning: format specifies type 'int' but the argument has type 'unsigned int'
 #pragma clang diagnostic ignored "-Wformat-nonliteral"              // warning: format string is not a string literal            // passing non-literal to vsnformat(). yes, user passing incorrect format strings can crash the code.
+#pragma clang diagnostic ignored "-Wformat-pedantic"                // warning: format specifies type 'void *' but the argument has type 'xxxx *' // unreasonable, would lead to casting every %p arg to void*. probably enabled by -pedantic.
 #pragma clang diagnostic ignored "-Wexit-time-destructors"          // warning: declaration requires an exit-time destructor     // exit-time destruction order is undefined. if MemFree() leads to users code that has been disabled before exit it might cause problems. ImGui coding style welcomes static/globals.
 #pragma clang diagnostic ignored "-Wglobal-constructors"            // warning: declaration requires a global destructor         // similar to above, not sure what the exact difference is.
 #pragma clang diagnostic ignored "-Wsign-conversion"                // warning: implicit conversion changes signedness
-#pragma clang diagnostic ignored "-Wformat-pedantic"                // warning: format specifies type 'void *' but the argument has type 'xxxx *' // unreasonable, would lead to casting every %p arg to void*. probably enabled by -pedantic.
 #pragma clang diagnostic ignored "-Wint-to-void-pointer-cast"       // warning: cast to 'void *' from smaller integer type 'int'
 #pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant"  // warning: zero as null pointer constant                    // some standard header variations use #define NULL 0
 #pragma clang diagnostic ignored "-Wdouble-promotion"               // warning: implicit conversion from 'float' to 'double' when passing argument to function  // using printf() is a misery with this as C++ va_arg ellipsis changes float to double.
 #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion"  // warning: implicit conversion from 'xxx' to 'float' may lose precision
 #pragma clang diagnostic ignored "-Wunsafe-buffer-usage"            // warning: 'xxx' is an unsafe pointer used for buffer access
 #pragma clang diagnostic ignored "-Wnontrivial-memaccess"           // warning: first argument in call to 'memset' is a pointer to non-trivially copyable type
+#pragma clang diagnostic ignored "-Wswitch-default"                 // warning: 'switch' missing 'default' label
 #elif defined(__GNUC__)
 // We disable -Wpragmas because GCC doesn't provide a has_warning equivalent and some forks/patches may not follow the warning/version association.
 #pragma GCC diagnostic ignored "-Wpragmas"                          // warning: unknown option after '#pragma GCC diagnostic' kind
@@ -1166,11 +1181,7 @@ CODE
 // When using CTRL+TAB (or Gamepad Square+L/R) we delay the visual a little in order to reduce visual noise doing a fast switch.
 static const float NAV_WINDOWING_HIGHLIGHT_DELAY            = 0.20f;    // Time before the highlight and screen dimming starts fading in
 static const float NAV_WINDOWING_LIST_APPEAR_DELAY          = 0.15f;    // Time before the window list starts to appear
-
 static const float NAV_ACTIVATE_HIGHLIGHT_TIMER             = 0.10f;    // Time to highlight an item activated by a shortcut.
-
-// Window resizing from edges (when io.ConfigWindowsResizeFromEdges = true and ImGuiBackendFlags_HasMouseCursors is set in io.BackendFlags by backend)
-static const float WINDOWS_HOVER_PADDING                    = 4.0f;     // Extend outside window for hovering/resizing (maxxed with TouchPadding) and inside windows for borders. Affect FindHoveredWindow().
 static const float WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER = 0.04f;    // Reduce visual noise by only highlighting the border after a certain time.
 static const float WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER    = 0.70f;    // Lock scrolled window (so it doesn't pick child windows that are scrolling through) for a certain time, unless mouse moved.
 
@@ -1214,6 +1225,7 @@ static void             UpdateWindowInFocusOrderList(ImGuiWindow* window, bool j
 // Navigation
 static void             NavUpdate();
 static void             NavUpdateWindowing();
+static void             NavUpdateWindowingApplyFocus(ImGuiWindow* window);
 static void             NavUpdateWindowingOverlay();
 static void             NavUpdateCancelRequest();
 static void             NavUpdateCreateMoveRequest();
@@ -1317,6 +1329,7 @@ ImGuiStyle::ImGuiStyle()
     WindowPadding               = ImVec2(8,8);      // Padding within a window
     WindowRounding              = 0.0f;             // Radius of window corners rounding. Set to 0.0f to have rectangular windows. Large values tend to lead to variety of artifacts and are not recommended.
     WindowBorderSize            = 1.0f;             // Thickness of border around windows. Generally set to 0.0f or 1.0f. Other values not well tested.
+    WindowBorderHoverPadding    = 4.0f;             // Hit-testing extent outside/inside resizing border. Also extend determination of hovered window. Generally meaningfully larger than WindowBorderSize to make it easy to reach borders.
     WindowMinSize               = ImVec2(32,32);    // Minimum window size
     WindowTitleAlign            = ImVec2(0.0f,0.5f);// Alignment for title bar text
     WindowMenuButtonPosition    = ImGuiDir_Left;    // Position of the collapsing/docking button in the title bar (left/right). Defaults to ImGuiDir_Left.
@@ -1338,9 +1351,11 @@ ImGuiStyle::ImGuiStyle()
     GrabMinSize                 = 12.0f;            // Minimum width/height of a grab box for slider/scrollbar
     GrabRounding                = 0.0f;             // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs.
     LogSliderDeadzone           = 4.0f;             // The size in pixels of the dead-zone around zero on logarithmic sliders that cross zero.
+    ImageBorderSize             = 0.0f;             // Thickness of border around tabs.
     TabRounding                 = 5.0f;             // Radius of upper corners of a tab. Set to 0.0f to have rectangular tabs.
     TabBorderSize               = 0.0f;             // Thickness of border around tabs.
-    TabMinWidthForCloseButton   = 0.0f;             // Minimum width for close button to appear on an unselected tab when hovered. Set to 0.0f to always show when hovering, set to FLT_MAX to never show close button unless selected.
+    TabCloseButtonMinWidthSelected   = -1.0f;       // -1: always visible. 0.0f: visible when hovered. >0.0f: visible when hovered if minimum width.
+    TabCloseButtonMinWidthUnselected = 0.0f;        // -1: always visible. 0.0f: visible when hovered. >0.0f: visible when hovered if minimum width. FLT_MAX: never show close button when unselected.
     TabBarBorderSize            = 1.0f;             // Thickness of tab-bar separator, which takes on the tab active color to denote focus.
     TabBarOverlineSize          = 1.0f;             // Thickness of tab-bar overline, which highlights the selected tab-bar.
     TableAngledHeadersAngle     = 35.0f * (IM_PI / 180.0f); // Angle of angled headers (supported values range from -50 degrees to +50 degrees).
@@ -1378,6 +1393,7 @@ void ImGuiStyle::ScaleAllSizes(float scale_factor)
     WindowPadding = ImTrunc(WindowPadding * scale_factor);
     WindowRounding = ImTrunc(WindowRounding * scale_factor);
     WindowMinSize = ImTrunc(WindowMinSize * scale_factor);
+    WindowBorderHoverPadding = ImTrunc(WindowBorderHoverPadding * scale_factor);
     ChildRounding = ImTrunc(ChildRounding * scale_factor);
     PopupRounding = ImTrunc(PopupRounding * scale_factor);
     FramePadding = ImTrunc(FramePadding * scale_factor);
@@ -1393,8 +1409,10 @@ void ImGuiStyle::ScaleAllSizes(float scale_factor)
     GrabMinSize = ImTrunc(GrabMinSize * scale_factor);
     GrabRounding = ImTrunc(GrabRounding * scale_factor);
     LogSliderDeadzone = ImTrunc(LogSliderDeadzone * scale_factor);
+    ImageBorderSize = ImTrunc(ImageBorderSize * scale_factor);
     TabRounding = ImTrunc(TabRounding * scale_factor);
-    TabMinWidthForCloseButton = (TabMinWidthForCloseButton != FLT_MAX) ? ImTrunc(TabMinWidthForCloseButton * scale_factor) : FLT_MAX;
+    TabCloseButtonMinWidthSelected = (TabCloseButtonMinWidthSelected > 0.0f && TabCloseButtonMinWidthSelected != FLT_MAX) ? ImTrunc(TabCloseButtonMinWidthSelected * scale_factor) : TabCloseButtonMinWidthSelected;
+    TabCloseButtonMinWidthUnselected = (TabCloseButtonMinWidthUnselected > 0.0f && TabCloseButtonMinWidthUnselected != FLT_MAX) ? ImTrunc(TabCloseButtonMinWidthUnselected * scale_factor) : TabCloseButtonMinWidthUnselected;
     TabBarOverlineSize = ImTrunc(TabBarOverlineSize * scale_factor);
     SeparatorTextPadding = ImTrunc(SeparatorTextPadding * scale_factor);
     DisplayWindowPadding = ImTrunc(DisplayWindowPadding * scale_factor);
@@ -1451,6 +1469,7 @@ ImGuiIO::ImGuiIO()
     ConfigMemoryCompactTimer = 60.0f;
     ConfigDebugIsDebuggerPresent = false;
     ConfigDebugHighlightIdConflicts = true;
+    ConfigDebugHighlightIdConflictsShowItemPicker = true;
     ConfigDebugBeginReturnValueOnce = false;
     ConfigDebugBeginReturnValueLoop = false;
 
@@ -1970,15 +1989,15 @@ void ImStrncpy(char* dst, const char* src, size_t count)
 
 char* ImStrdup(const char* str)
 {
-    size_t len = strlen(str);
+    size_t len = ImStrlen(str);
     void* buf = IM_ALLOC(len + 1);
     return (char*)memcpy(buf, (const void*)str, len + 1);
 }
 
 char* ImStrdupcpy(char* dst, size_t* p_dst_size, const char* src)
 {
-    size_t dst_buf_size = p_dst_size ? *p_dst_size : strlen(dst) + 1;
-    size_t src_size = strlen(src) + 1;
+    size_t dst_buf_size = p_dst_size ? *p_dst_size : ImStrlen(dst) + 1;
+    size_t src_size = ImStrlen(src) + 1;
     if (dst_buf_size < src_size)
     {
         IM_FREE(dst);
@@ -1991,7 +2010,7 @@ char* ImStrdupcpy(char* dst, size_t* p_dst_size, const char* src)
 
 const char* ImStrchrRange(const char* str, const char* str_end, char c)
 {
-    const char* p = (const char*)memchr(str, (int)c, str_end - str);
+    const char* p = (const char*)ImMemchr(str, (int)c, str_end - str);
     return p;
 }
 
@@ -2006,12 +2025,13 @@ int ImStrlenW(const ImWchar* str)
 // Find end-of-line. Return pointer will point to either first \n, either str_end.
 const char* ImStreolRange(const char* str, const char* str_end)
 {
-    const char* p = (const char*)memchr(str, '\n', str_end - str);
+    const char* p = (const char*)ImMemchr(str, '\n', str_end - str);
     return p ? p : str_end;
 }
 
 const char* ImStrbol(const char* buf_mid_line, const char* buf_begin) // find beginning-of-line
 {
+    IM_ASSERT_PARANOID(buf_mid_line >= buf_begin && buf_mid_line <= buf_begin + ImStrlen(buf_begin));
     while (buf_mid_line > buf_begin && buf_mid_line[-1] != '\n')
         buf_mid_line--;
     return buf_mid_line;
@@ -2020,7 +2040,7 @@ const char* ImStrbol(const char* buf_mid_line, const char* buf_begin) // find be
 const char* ImStristr(const char* haystack, const char* haystack_end, const char* needle, const char* needle_end)
 {
     if (!needle_end)
-        needle_end = needle + strlen(needle);
+        needle_end = needle + ImStrlen(needle);
 
     const char un0 = (char)ImToUpper(*needle);
     while ((!haystack_end && *haystack) || (haystack_end && haystack < haystack_end))
@@ -2141,7 +2161,7 @@ void ImFormatStringToTempBufferV(const char** out_buf, const char** out_buf_end,
         if (buf == NULL)
             buf = "(null)";
         *out_buf = buf;
-        if (out_buf_end) { *out_buf_end = buf + strlen(buf); }
+        if (out_buf_end) { *out_buf_end = buf + ImStrlen(buf); }
     }
     else if (fmt[0] == '%' && fmt[1] == '.' && fmt[2] == '*' && fmt[3] == 's' && fmt[4] == 0)
     {
@@ -2399,7 +2419,7 @@ int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char*
     int e = 0;
     e  = (*out_char < mins[len]) << 6; // non-canonical encoding
     e |= ((*out_char >> 11) == 0x1b) << 7;  // surrogate half?
-    e |= (*out_char > IM_UNICODE_CODEPOINT_MAX) << 8;  // out of range?
+    e |= (*out_char > IM_UNICODE_CODEPOINT_MAX) << 8;  // out of range we can store in ImWchar (FIXME: May evolve)
     e |= (s[1] & 0xc0) >> 2;
     e |= (s[2] & 0xc0) >> 4;
     e |= (s[3]       ) >> 6;
@@ -2550,11 +2570,11 @@ const char* ImTextFindPreviousUtf8Codepoint(const char* in_text_start, const cha
 int ImTextCountLines(const char* in_text, const char* in_text_end)
 {
     if (in_text_end == NULL)
-        in_text_end = in_text + strlen(in_text); // FIXME-OPT: Not optimal approach, discourage use for now.
+        in_text_end = in_text + ImStrlen(in_text); // FIXME-OPT: Not optimal approach, discourage use for now.
     int count = 0;
     while (in_text < in_text_end)
     {
-        const char* line_end = (const char*)memchr(in_text, '\n', in_text_end - in_text);
+        const char* line_end = (const char*)ImMemchr(in_text, '\n', in_text_end - in_text);
         in_text = line_end ? line_end + 1 : in_text_end;
         count++;
     }
@@ -2835,7 +2855,7 @@ void ImGuiTextFilter::ImGuiTextRange::split(char separator, ImVectorDisplayStart = ImMax(data->Ranges[data->StepNo].Min, already_submitted);
         clipper->DisplayEnd = ImMin(data->Ranges[data->StepNo].Max, clipper->ItemsCount);
-        if (clipper->DisplayStart > already_submitted) //-V1051
-            clipper->SeekCursorForItem(clipper->DisplayStart);
         data->StepNo++;
-        if (clipper->DisplayStart == clipper->DisplayEnd && data->StepNo < data->Ranges.Size)
+        if (clipper->DisplayStart >= clipper->DisplayEnd)
             continue;
+        if (clipper->DisplayStart > already_submitted)
+            clipper->SeekCursorForItem(clipper->DisplayStart);
         return true;
     }
 
@@ -3263,7 +3283,7 @@ bool ImGuiListClipper::Step()
     ImGuiContext& g = *Ctx;
     bool need_items_height = (ItemsHeight <= 0.0f);
     bool ret = ImGuiListClipper_StepInternal(this);
-    if (ret && (DisplayStart == DisplayEnd))
+    if (ret && (DisplayStart >= DisplayEnd))
         ret = false;
     if (g.CurrentTable && g.CurrentTable->IsUnfrozenRows == false)
         IMGUI_DEBUG_LOG_CLIPPER("Clipper: Step(): inside frozen table row.\n");
@@ -3364,55 +3384,56 @@ void ImGui::PopStyleColor(int count)
     }
 }
 
-static const ImGuiDataVarInfo GStyleVarInfo[] =
+static const ImGuiStyleVarInfo GStyleVarsInfo[] =
 {
-    { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, Alpha) },                     // ImGuiStyleVar_Alpha
-    { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, DisabledAlpha) },             // ImGuiStyleVar_DisabledAlpha
-    { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, WindowPadding) },             // ImGuiStyleVar_WindowPadding
-    { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, WindowRounding) },            // ImGuiStyleVar_WindowRounding
-    { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, WindowBorderSize) },          // ImGuiStyleVar_WindowBorderSize
-    { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, WindowMinSize) },             // ImGuiStyleVar_WindowMinSize
-    { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, WindowTitleAlign) },          // ImGuiStyleVar_WindowTitleAlign
-    { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, ChildRounding) },             // ImGuiStyleVar_ChildRounding
-    { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, ChildBorderSize) },           // ImGuiStyleVar_ChildBorderSize
-    { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, PopupRounding) },             // ImGuiStyleVar_PopupRounding
-    { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, PopupBorderSize) },           // ImGuiStyleVar_PopupBorderSize
-    { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, FramePadding) },              // ImGuiStyleVar_FramePadding
-    { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, FrameRounding) },             // ImGuiStyleVar_FrameRounding
-    { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, FrameBorderSize) },           // ImGuiStyleVar_FrameBorderSize
-    { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, ItemSpacing) },               // ImGuiStyleVar_ItemSpacing
-    { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, ItemInnerSpacing) },          // ImGuiStyleVar_ItemInnerSpacing
-    { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, IndentSpacing) },             // ImGuiStyleVar_IndentSpacing
-    { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, CellPadding) },               // ImGuiStyleVar_CellPadding
-    { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, ScrollbarSize) },             // ImGuiStyleVar_ScrollbarSize
-    { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, ScrollbarRounding) },         // ImGuiStyleVar_ScrollbarRounding
-    { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, GrabMinSize) },               // ImGuiStyleVar_GrabMinSize
-    { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, GrabRounding) },              // ImGuiStyleVar_GrabRounding
-    { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, TabRounding) },               // ImGuiStyleVar_TabRounding
-    { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, TabBorderSize) },             // ImGuiStyleVar_TabBorderSize
-    { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, TabBarBorderSize) },          // ImGuiStyleVar_TabBarBorderSize
-    { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, TabBarOverlineSize) },        // ImGuiStyleVar_TabBarOverlineSize
-    { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, TableAngledHeadersAngle)},    // ImGuiStyleVar_TableAngledHeadersAngle
-    { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, TableAngledHeadersTextAlign)},// ImGuiStyleVar_TableAngledHeadersTextAlign
-    { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, ButtonTextAlign) },           // ImGuiStyleVar_ButtonTextAlign
-    { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, SelectableTextAlign) },       // ImGuiStyleVar_SelectableTextAlign
-    { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, SeparatorTextBorderSize)},    // ImGuiStyleVar_SeparatorTextBorderSize
-    { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, SeparatorTextAlign) },        // ImGuiStyleVar_SeparatorTextAlign
-    { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, SeparatorTextPadding) },      // ImGuiStyleVar_SeparatorTextPadding
+    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, Alpha) },                     // ImGuiStyleVar_Alpha
+    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, DisabledAlpha) },             // ImGuiStyleVar_DisabledAlpha
+    { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, WindowPadding) },             // ImGuiStyleVar_WindowPadding
+    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, WindowRounding) },            // ImGuiStyleVar_WindowRounding
+    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, WindowBorderSize) },          // ImGuiStyleVar_WindowBorderSize
+    { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, WindowMinSize) },             // ImGuiStyleVar_WindowMinSize
+    { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, WindowTitleAlign) },          // ImGuiStyleVar_WindowTitleAlign
+    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ChildRounding) },             // ImGuiStyleVar_ChildRounding
+    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ChildBorderSize) },           // ImGuiStyleVar_ChildBorderSize
+    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, PopupRounding) },             // ImGuiStyleVar_PopupRounding
+    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, PopupBorderSize) },           // ImGuiStyleVar_PopupBorderSize
+    { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, FramePadding) },              // ImGuiStyleVar_FramePadding
+    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, FrameRounding) },             // ImGuiStyleVar_FrameRounding
+    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, FrameBorderSize) },           // ImGuiStyleVar_FrameBorderSize
+    { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ItemSpacing) },               // ImGuiStyleVar_ItemSpacing
+    { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ItemInnerSpacing) },          // ImGuiStyleVar_ItemInnerSpacing
+    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, IndentSpacing) },             // ImGuiStyleVar_IndentSpacing
+    { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, CellPadding) },               // ImGuiStyleVar_CellPadding
+    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ScrollbarSize) },             // ImGuiStyleVar_ScrollbarSize
+    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ScrollbarRounding) },         // ImGuiStyleVar_ScrollbarRounding
+    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, GrabMinSize) },               // ImGuiStyleVar_GrabMinSize
+    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, GrabRounding) },              // ImGuiStyleVar_GrabRounding
+    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ImageBorderSize) },           // ImGuiStyleVar_ImageBorderSize
+    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TabRounding) },               // ImGuiStyleVar_TabRounding
+    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TabBorderSize) },             // ImGuiStyleVar_TabBorderSize
+    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TabBarBorderSize) },          // ImGuiStyleVar_TabBarBorderSize
+    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TabBarOverlineSize) },        // ImGuiStyleVar_TabBarOverlineSize
+    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TableAngledHeadersAngle)},    // ImGuiStyleVar_TableAngledHeadersAngle
+    { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TableAngledHeadersTextAlign)},// ImGuiStyleVar_TableAngledHeadersTextAlign
+    { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ButtonTextAlign) },           // ImGuiStyleVar_ButtonTextAlign
+    { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, SelectableTextAlign) },       // ImGuiStyleVar_SelectableTextAlign
+    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, SeparatorTextBorderSize)},    // ImGuiStyleVar_SeparatorTextBorderSize
+    { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, SeparatorTextAlign) },        // ImGuiStyleVar_SeparatorTextAlign
+    { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, SeparatorTextPadding) },      // ImGuiStyleVar_SeparatorTextPadding
 };
 
-const ImGuiDataVarInfo* ImGui::GetStyleVarInfo(ImGuiStyleVar idx)
+const ImGuiStyleVarInfo* ImGui::GetStyleVarInfo(ImGuiStyleVar idx)
 {
     IM_ASSERT(idx >= 0 && idx < ImGuiStyleVar_COUNT);
-    IM_STATIC_ASSERT(IM_ARRAYSIZE(GStyleVarInfo) == ImGuiStyleVar_COUNT);
-    return &GStyleVarInfo[idx];
+    IM_STATIC_ASSERT(IM_ARRAYSIZE(GStyleVarsInfo) == ImGuiStyleVar_COUNT);
+    return &GStyleVarsInfo[idx];
 }
 
 void ImGui::PushStyleVar(ImGuiStyleVar idx, float val)
 {
     ImGuiContext& g = *GImGui;
-    const ImGuiDataVarInfo* var_info = GetStyleVarInfo(idx);
-    if (var_info->Type != ImGuiDataType_Float || var_info->Count != 1)
+    const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx);
+    if (var_info->DataType != ImGuiDataType_Float || var_info->Count != 1)
     {
         IM_ASSERT_USER_ERROR(0, "Calling PushStyleVar() variant with wrong type!");
         return;
@@ -3425,8 +3446,8 @@ void ImGui::PushStyleVar(ImGuiStyleVar idx, float val)
 void ImGui::PushStyleVarX(ImGuiStyleVar idx, float val_x)
 {
     ImGuiContext& g = *GImGui;
-    const ImGuiDataVarInfo* var_info = GetStyleVarInfo(idx);
-    if (var_info->Type != ImGuiDataType_Float || var_info->Count != 2)
+    const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx);
+    if (var_info->DataType != ImGuiDataType_Float || var_info->Count != 2)
     {
         IM_ASSERT_USER_ERROR(0, "Calling PushStyleVar() variant with wrong type!");
         return;
@@ -3439,8 +3460,8 @@ void ImGui::PushStyleVarX(ImGuiStyleVar idx, float val_x)
 void ImGui::PushStyleVarY(ImGuiStyleVar idx, float val_y)
 {
     ImGuiContext& g = *GImGui;
-    const ImGuiDataVarInfo* var_info = GetStyleVarInfo(idx);
-    if (var_info->Type != ImGuiDataType_Float || var_info->Count != 2)
+    const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx);
+    if (var_info->DataType != ImGuiDataType_Float || var_info->Count != 2)
     {
         IM_ASSERT_USER_ERROR(0, "Calling PushStyleVar() variant with wrong type!");
         return;
@@ -3453,8 +3474,8 @@ void ImGui::PushStyleVarY(ImGuiStyleVar idx, float val_y)
 void ImGui::PushStyleVar(ImGuiStyleVar idx, const ImVec2& val)
 {
     ImGuiContext& g = *GImGui;
-    const ImGuiDataVarInfo* var_info = GetStyleVarInfo(idx);
-    if (var_info->Type != ImGuiDataType_Float || var_info->Count != 2)
+    const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx);
+    if (var_info->DataType != ImGuiDataType_Float || var_info->Count != 2)
     {
         IM_ASSERT_USER_ERROR(0, "Calling PushStyleVar() variant with wrong type!");
         return;
@@ -3476,10 +3497,10 @@ void ImGui::PopStyleVar(int count)
     {
         // We avoid a generic memcpy(data, &backup.Backup.., GDataTypeSize[info->Type] * info->Count), the overhead in Debug is not worth it.
         ImGuiStyleMod& backup = g.StyleVarStack.back();
-        const ImGuiDataVarInfo* info = GetStyleVarInfo(backup.VarIdx);
-        void* data = info->GetVarPtr(&g.Style);
-        if (info->Type == ImGuiDataType_Float && info->Count == 1)      { ((float*)data)[0] = backup.BackupFloat[0]; }
-        else if (info->Type == ImGuiDataType_Float && info->Count == 2) { ((float*)data)[0] = backup.BackupFloat[0]; ((float*)data)[1] = backup.BackupFloat[1]; }
+        const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(backup.VarIdx);
+        void* data = var_info->GetVarPtr(&g.Style);
+        if (var_info->DataType == ImGuiDataType_Float && var_info->Count == 1)      { ((float*)data)[0] = backup.BackupFloat[0]; }
+        else if (var_info->DataType == ImGuiDataType_Float && var_info->Count == 2) { ((float*)data)[0] = backup.BackupFloat[0]; ((float*)data)[1] = backup.BackupFloat[1]; }
         g.StyleVarStack.pop_back();
         count--;
     }
@@ -3585,7 +3606,7 @@ void ImGui::RenderText(ImVec2 pos, const char* text, const char* text_end, bool
     else
     {
         if (!text_end)
-            text_end = text + strlen(text); // FIXME-OPT
+            text_end = text + ImStrlen(text); // FIXME-OPT
         text_display_end = text_end;
     }
 
@@ -3603,7 +3624,7 @@ void ImGui::RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end
     ImGuiWindow* window = g.CurrentWindow;
 
     if (!text_end)
-        text_end = text + strlen(text); // FIXME-OPT
+        text_end = text + ImStrlen(text); // FIXME-OPT
 
     if (text != text_end)
     {
@@ -3790,7 +3811,7 @@ void ImGui::RenderMouseCursor(ImVec2 base_pos, float base_scale, ImGuiMouseCurso
     {
         // We scale cursor with current viewport/monitor, however Windows 10 for its own hardware cursor seems to be using a different scale factor.
         ImVec2 offset, size, uv[4];
-        if (!font_atlas->GetMouseCursorTexData(mouse_cursor, &offset, &size, &uv[0], &uv[2]))
+        if (!ImFontAtlasGetMouseCursorTexData(font_atlas, mouse_cursor, &offset, &size, &uv[0], &uv[2]))
             continue;
         const ImVec2 pos = base_pos - offset;
         const float scale = base_scale;
@@ -3803,6 +3824,13 @@ void ImGui::RenderMouseCursor(ImVec2 base_pos, float base_scale, ImGuiMouseCurso
         draw_list->AddImage(tex_id, pos + ImVec2(2, 0) * scale, pos + (ImVec2(2, 0) + size) * scale, uv[2], uv[3], col_shadow);
         draw_list->AddImage(tex_id, pos,                        pos + size * scale,                  uv[2], uv[3], col_border);
         draw_list->AddImage(tex_id, pos,                        pos + size * scale,                  uv[0], uv[1], col_fill);
+        if (mouse_cursor == ImGuiMouseCursor_Wait || mouse_cursor == ImGuiMouseCursor_Progress)
+        {
+            float a_min = ImFmod((float)g.Time * 5.0f, 2.0f * IM_PI);
+            float a_max = a_min + IM_PI * 1.65f;
+            draw_list->PathArcTo(pos + ImVec2(14, -1) * scale, 6.0f * scale, a_min, a_max);
+            draw_list->PathStroke(col_fill, ImDrawFlags_None, 3.0f * scale);
+        }
         draw_list->PopTextureID();
     }
 }
@@ -3903,6 +3931,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas)
     InputEventsNextEventId = 1;
 
     WindowsActiveCount = 0;
+    WindowsBorderHoverPadding = 0.0f;
     CurrentWindow = NULL;
     HoveredWindow = NULL;
     HoveredWindowUnderMovingWindow = NULL;
@@ -4268,7 +4297,7 @@ ImGuiWindow::ImGuiWindow(ImGuiContext* ctx, const char* name) : DrawListInst(NUL
     memset(this, 0, sizeof(*this));
     Ctx = ctx;
     Name = ImStrdup(name);
-    NameBufLen = (int)strlen(name) + 1;
+    NameBufLen = (int)ImStrlen(name) + 1;
     ID = ImHashStr(name);
     IDStack.push_back(ID);
     MoveId = GetID("#MOVE");
@@ -4440,8 +4469,11 @@ void ImGui::MarkItemEdited(ImGuiID id)
         return;
     if (g.ActiveId == id || g.ActiveId == 0)
     {
+        // FIXME: Can't we fully rely on LastItemData yet?
         g.ActiveIdHasBeenEditedThisFrame = true;
         g.ActiveIdHasBeenEditedBefore = true;
+        if (g.DeactivatedItemData.ID == id)
+            g.DeactivatedItemData.HasBeenEditedBefore = true;
     }
 
     // We accept a MarkItemEdited() on drag and drop targets (see https://github.com/ocornut/imgui/issues/1875#issuecomment-978243343)
@@ -4814,7 +4846,7 @@ ImGuiIO& ImGui::GetIO()
 }
 
 // This variant exists to facilitate backends experimenting with multi-threaded parallel context. (#8069, #6293, #5856)
-ImGuiIO& ImGui::GetIOEx(ImGuiContext* ctx)
+ImGuiIO& ImGui::GetIO(ImGuiContext* ctx)
 {
     IM_ASSERT(ctx != NULL);
     return ctx->IO;
@@ -4826,6 +4858,13 @@ ImGuiPlatformIO& ImGui::GetPlatformIO()
     return GImGui->PlatformIO;
 }
 
+// This variant exists to facilitate backends experimenting with multi-threaded parallel context. (#8069, #6293, #5856)
+ImGuiPlatformIO& ImGui::GetPlatformIO(ImGuiContext* ctx)
+{
+    IM_ASSERT(ctx != NULL);
+    return ctx->PlatformIO;
+}
+
 // Pass this to your backend rendering function! Valid after Render() and until the next call to NewFrame()
 ImDrawData* ImGui::GetDrawData()
 {
@@ -4918,7 +4957,7 @@ void ImGui::StartMouseMovingWindow(ImGuiWindow* window)
 
 // Handle mouse moving window
 // Note: moving window with the navigation keys (Square + d-pad / CTRL+TAB + Arrows) are processed in NavUpdateWindowing()
-// FIXME: We don't have strong guarantee that g.MovingWindow stay synched with g.ActiveId == g.MovingWindow->MoveId.
+// FIXME: We don't have strong guarantee that g.MovingWindow stay synced with g.ActiveId == g.MovingWindow->MoveId.
 // This is currently enforced by the fact that BeginDragDropSource() is setting all g.ActiveIdUsingXXXX flags to inhibit navigation inputs,
 // but if we should more thoroughly test cases where g.ActiveId or g.MovingWindow gets changed and not the other.
 void ImGui::UpdateMouseMovingWindowNewFrame()
@@ -5025,7 +5064,7 @@ void ImGui::UpdateHoveredWindowAndCaptureFlags()
 
     // FIXME-DPI: This storage was added on 2021/03/31 for test engine, but if we want to multiply WINDOWS_HOVER_PADDING
     // by DpiScale, we need to make this window-agnostic anyhow, maybe need storing inside ImGuiWindow.
-    g.WindowsHoverPadding = ImMax(g.Style.TouchExtraPadding, ImVec2(WINDOWS_HOVER_PADDING, WINDOWS_HOVER_PADDING));
+    g.WindowsBorderHoverPadding = ImMax(ImMax(g.Style.TouchExtraPadding.x, g.Style.TouchExtraPadding.y), g.Style.WindowBorderHoverPadding);
 
     // Find the window hovered by mouse:
     // - Child windows can extend beyond the limit of their parent so we need to derive HoveredRootWindow from HoveredWindow.
@@ -5122,6 +5161,7 @@ static void SetupDrawListSharedData()
         g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedFill;
     if (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset)
         g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AllowVtxOffset;
+    g.DrawListSharedData.InitialFringeScale = 1.0f; // FIXME-DPI: Change this for some DPI scaling experiments.
 }
 
 void ImGui::NewFrame()
@@ -5788,7 +5828,7 @@ void ImGui::FindHoveredWindowEx(const ImVec2& pos, bool find_first_and_in_any_vi
         hovered_window = g.MovingWindow;
 
     ImVec2 padding_regular = g.Style.TouchExtraPadding;
-    ImVec2 padding_for_resize = g.IO.ConfigWindowsResizeFromEdges ? g.WindowsHoverPadding : padding_regular;
+    ImVec2 padding_for_resize = ImMax(g.Style.TouchExtraPadding, ImVec2(g.Style.WindowBorderHoverPadding, g.Style.WindowBorderHoverPadding));
     for (int i = g.Windows.Size - 1; i >= 0; i--)
     {
         ImGuiWindow* window = g.Windows[i];
@@ -6058,7 +6098,7 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, I
     // A SetNextWindowSize() call always has priority (#8020)
     // (since the code in Begin() never supported SizeVal==0.0f aka auto-resize via SetNextWindowSize() call, we don't support it here for now)
     // FIXME: We only support ImGuiCond_Always in this path. Supporting other paths would requires to obtain window pointer.
-    if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize) != 0 && (g.NextWindowData.SizeCond & ImGuiCond_Always) != 0)
+    if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSize) != 0 && (g.NextWindowData.SizeCond & ImGuiCond_Always) != 0)
     {
         if (g.NextWindowData.SizeVal.x > 0.0f)
         {
@@ -6074,11 +6114,11 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, I
     SetNextWindowSize(size);
 
     // Forward child flags (we allow prior settings to merge but it'll only work for adding flags)
-    if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasChildFlags)
+    if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasChildFlags)
         g.NextWindowData.ChildFlags |= child_flags;
     else
         g.NextWindowData.ChildFlags = child_flags;
-    g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasChildFlags;
+    g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasChildFlags;
 
     // Build up name. If you need to append to a same child from multiple location in the ID stack, use BeginChild(ImGuiID id) with a stable value.
     // FIXME: 2023/11/14: commented out shorted version. We had an issue with multiple ### in child window path names, which the trailing hash helped workaround.
@@ -6292,7 +6332,7 @@ static ImVec2 CalcWindowSizeAfterConstraint(ImGuiWindow* window, const ImVec2& s
 {
     ImGuiContext& g = *GImGui;
     ImVec2 new_size = size_desired;
-    if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint)
+    if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSizeConstraint)
     {
         // See comments in SetNextWindowSizeConstraints() for details about setting size_min an size_max.
         ImRect cr = g.NextWindowData.SizeConstraintRect;
@@ -6490,7 +6530,7 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si
     int ret_auto_fit_mask = 0x00;
     const float grip_draw_size = IM_TRUNC(ImMax(g.FontSize * 1.35f, window->WindowRounding + 1.0f + g.FontSize * 0.2f));
     const float grip_hover_inner_size = (resize_grip_count > 0) ? IM_TRUNC(grip_draw_size * 0.75f) : 0.0f;
-    const float grip_hover_outer_size = g.IO.ConfigWindowsResizeFromEdges ? WINDOWS_HOVER_PADDING : 0.0f;
+    const float grip_hover_outer_size = g.WindowsBorderHoverPadding;
 
     ImRect clamp_rect = visibility_rect;
     const bool window_move_from_title_bar = g.IO.ConfigWindowsMoveFromTitleBarOnly && !(window->Flags & ImGuiWindowFlags_NoTitleBar);
@@ -6526,7 +6566,7 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si
         {
             // Auto-fit when double-clicking
             size_target = CalcWindowSizeAfterConstraint(window, size_auto_fit);
-            ret_auto_fit_mask = 0x03; // Both axises
+            ret_auto_fit_mask = 0x03; // Both axes
             ClearActiveID();
         }
         else if (held)
@@ -6558,7 +6598,7 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si
         const ImGuiAxis axis = (border_n == ImGuiDir_Left || border_n == ImGuiDir_Right) ? ImGuiAxis_X : ImGuiAxis_Y;
 
         bool hovered, held;
-        ImRect border_rect = GetResizeBorderRect(window, border_n, grip_hover_inner_size, WINDOWS_HOVER_PADDING);
+        ImRect border_rect = GetResizeBorderRect(window, border_n, grip_hover_inner_size, g.WindowsBorderHoverPadding);
         ImGuiID border_id = window->GetID(border_n + 4); // == GetWindowResizeBorderID()
         ItemAdd(border_rect, border_id, NULL, ImGuiItemFlags_NoNav);
         ButtonBehavior(border_rect, border_id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_NoNavFocus);
@@ -6596,7 +6636,7 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si
 
             const ImVec2 border_curr = (window->Pos + ImMin(def.SegmentN1, def.SegmentN2) * window->Size);
             const float border_target_rel_mode_for_axis = border_curr[axis] + g.IO.MouseDelta[axis];
-            const float border_target_abs_mode_for_axis = g.IO.MousePos[axis] - g.ActiveIdClickOffset[axis] + WINDOWS_HOVER_PADDING; // Match ButtonBehavior() padding above.
+            const float border_target_abs_mode_for_axis = g.IO.MousePos[axis] - g.ActiveIdClickOffset[axis] + g.WindowsBorderHoverPadding; // Match ButtonBehavior() padding above.
 
             // Use absolute mode position
             ImVec2 border_target = window->Pos;
@@ -6685,7 +6725,7 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si
 
     // Recalculate next expected border expected coordinates
     if (*border_held != -1)
-        g.WindowResizeBorderExpectedRect = GetResizeBorderRect(window, *border_held, grip_hover_inner_size, WINDOWS_HOVER_PADDING);
+        g.WindowResizeBorderExpectedRect = GetResizeBorderRect(window, *border_held, grip_hover_inner_size, g.WindowsBorderHoverPadding);
 
     return ret_auto_fit_mask;
 }
@@ -6770,7 +6810,7 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar
             ImU32 bg_col = GetColorU32(GetWindowBgColorIdx(window));
             bool override_alpha = false;
             float alpha = 1.0f;
-            if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasBgAlpha)
+            if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasBgAlpha)
             {
                 alpha = g.NextWindowData.BgAlphaVal;
                 override_alpha = true;
@@ -6940,7 +6980,7 @@ void ImGui::UpdateWindowSkipRefresh(ImGuiWindow* window)
 {
     ImGuiContext& g = *GImGui;
     window->SkipRefresh = false;
-    if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasRefreshPolicy) == 0)
+    if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasRefreshPolicy) == 0)
         return;
     if (g.NextWindowData.RefreshFlagsVal & ImGuiWindowRefreshFlags_TryToAvoidRefresh)
     {
@@ -7021,7 +7061,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
     {
         UpdateWindowInFocusOrderList(window, window_just_created, flags);
         window->Flags = (ImGuiWindowFlags)flags;
-        window->ChildFlags = (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasChildFlags) ? g.NextWindowData.ChildFlags : 0;
+        window->ChildFlags = (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasChildFlags) ? g.NextWindowData.ChildFlags : 0;
         window->LastFrameActive = current_frame;
         window->LastTimeActive = (float)g.Time;
         window->BeginOrderWithinParent = 0;
@@ -7048,6 +7088,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
     window_stack_data.Window = window;
     window_stack_data.ParentLastItemDataBackup = g.LastItemData;
     window_stack_data.DisabledOverrideReenable = (flags & ImGuiWindowFlags_Tooltip) && (g.CurrentItemFlags & ImGuiItemFlags_Disabled);
+    window_stack_data.DisabledOverrideReenableAlphaBackup = 0.0f;
     ErrorRecoveryStoreState(&window_stack_data.StackSizesInBegin);
     g.StackSizesInBeginForCurrentWindow = &window_stack_data.StackSizesInBegin;
     if (flags & ImGuiWindowFlags_ChildMenu)
@@ -7085,7 +7126,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
     // (FIXME: Consider splitting the HasXXX flags into X/Y components
     bool window_pos_set_by_api = false;
     bool window_size_x_set_by_api = false, window_size_y_set_by_api = false;
-    if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasPos)
+    if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasPos)
     {
         window_pos_set_by_api = (window->SetWindowPosAllowFlags & g.NextWindowData.PosCond) != 0;
         if (window_pos_set_by_api && ImLengthSqr(g.NextWindowData.PosPivotVal) > 0.00001f)
@@ -7101,7 +7142,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
             SetWindowPos(window, g.NextWindowData.PosVal, g.NextWindowData.PosCond);
         }
     }
-    if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize)
+    if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSize)
     {
         window_size_x_set_by_api = (window->SetWindowSizeAllowFlags & g.NextWindowData.SizeCond) != 0 && (g.NextWindowData.SizeVal.x > 0.0f);
         window_size_y_set_by_api = (window->SetWindowSizeAllowFlags & g.NextWindowData.SizeCond) != 0 && (g.NextWindowData.SizeVal.y > 0.0f);
@@ -7111,7 +7152,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
             g.NextWindowData.SizeVal.y = window->SizeFull.y;
         SetWindowSize(window, g.NextWindowData.SizeVal, g.NextWindowData.SizeCond);
     }
-    if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasScroll)
+    if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasScroll)
     {
         if (g.NextWindowData.ScrollVal.x >= 0.0f)
         {
@@ -7124,13 +7165,13 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
             window->ScrollTargetCenterRatio.y = 0.0f;
         }
     }
-    if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasContentSize)
+    if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasContentSize)
         window->ContentSizeExplicit = g.NextWindowData.ContentSizeVal;
     else if (first_begin_of_the_frame)
         window->ContentSizeExplicit = ImVec2(0.0f, 0.0f);
-    if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasCollapsed)
+    if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasCollapsed)
         SetWindowCollapsed(window, g.NextWindowData.CollapsedVal, g.NextWindowData.CollapsedCond);
-    if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasFocus)
+    if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasFocus)
         FocusWindow(window);
     if (window->Appearing)
         SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, false);
@@ -7165,7 +7206,9 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
         // Update stored window name when it changes (which can _only_ happen with the "###" operator, so the ID would stay unchanged).
         // The title bar always display the 'name' parameter, so we only update the string storage if it needs to be visible to the end-user elsewhere.
         bool window_title_visible_elsewhere = false;
-        if (g.NavWindowingListWindow != NULL && (window->Flags & ImGuiWindowFlags_NoNavFocus) == 0)   // Window titles visible when using CTRL+TAB
+        if (g.NavWindowingListWindow != NULL && (flags & ImGuiWindowFlags_NoNavFocus) == 0)   // Window titles visible when using CTRL+TAB
+            window_title_visible_elsewhere = true;
+        if (flags & ImGuiWindowFlags_ChildMenu)
             window_title_visible_elsewhere = true;
         if (window_title_visible_elsewhere && !window_just_created && strcmp(name, window->Name) != 0)
         {
@@ -7416,9 +7459,23 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
             ImVec2 needed_size_from_last_frame = window_just_created ? ImVec2(0, 0) : window->ContentSize + window->WindowPadding * 2.0f;
             float size_x_for_scrollbars = use_current_size_for_scrollbar_x ? avail_size_from_current_frame.x : avail_size_from_last_frame.x;
             float size_y_for_scrollbars = use_current_size_for_scrollbar_y ? avail_size_from_current_frame.y : avail_size_from_last_frame.y;
+            bool scrollbar_x_prev = window->ScrollbarX;
             //bool scrollbar_y_from_last_frame = window->ScrollbarY; // FIXME: May want to use that in the ScrollbarX expression? How many pros vs cons?
             window->ScrollbarY = (flags & ImGuiWindowFlags_AlwaysVerticalScrollbar) || ((needed_size_from_last_frame.y > size_y_for_scrollbars) && !(flags & ImGuiWindowFlags_NoScrollbar));
             window->ScrollbarX = (flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar) || ((needed_size_from_last_frame.x > size_x_for_scrollbars - (window->ScrollbarY ? style.ScrollbarSize : 0.0f)) && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar));
+
+            // Track when ScrollbarX visibility keeps toggling, which is a sign of a feedback loop, and stabilize by enforcing visibility (#3285, #8488)
+            // (Feedback loops of this sort can manifest in various situations, but combining horizontal + vertical scrollbar + using a clipper with varying width items is one frequent cause.
+            //  The better solution is to, either (1) enforce visibility by using ImGuiWindowFlags_AlwaysHorizontalScrollbar or (2) declare stable contents width with SetNextWindowContentSize(), if you can compute it)
+            window->ScrollbarXStabilizeToggledHistory <<= 1;
+            window->ScrollbarXStabilizeToggledHistory |= (scrollbar_x_prev != window->ScrollbarX) ? 0x01 : 0x00;
+            const bool scrollbar_x_stabilize = (window->ScrollbarXStabilizeToggledHistory != 0) && ImCountSetBits(window->ScrollbarXStabilizeToggledHistory) >= 4; // 4 == half of bits in our U8 history.
+            if (scrollbar_x_stabilize)
+                window->ScrollbarX = true;
+            //if (scrollbar_x_stabilize && !window->ScrollbarXStabilizeEnabled)
+            //    IMGUI_DEBUG_LOG("[scroll] Stabilize ScrollbarX for Window '%s'\n", window->Name);
+            window->ScrollbarXStabilizeEnabled = scrollbar_x_stabilize;
+
             if (window->ScrollbarX && !window->ScrollbarY)
                 window->ScrollbarY = (needed_size_from_last_frame.y > size_y_for_scrollbars - style.ScrollbarSize) && !(flags & ImGuiWindowFlags_NoScrollbar);
             window->ScrollbarSizes = ImVec2(window->ScrollbarY ? style.ScrollbarSize : 0.0f, window->ScrollbarX ? style.ScrollbarSize : 0.0f);
@@ -7692,7 +7749,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
             // Hide along with parent or if parent is collapsed
             if (parent_window && (parent_window->Collapsed || parent_window->HiddenFramesCanSkipItems > 0))
                 window->HiddenFramesCanSkipItems = 1;
-            if (parent_window && (parent_window->Collapsed || parent_window->HiddenFramesCannotSkipItems > 0))
+            if (parent_window && parent_window->HiddenFramesCannotSkipItems > 0)
                 window->HiddenFramesCannotSkipItems = 1;
         }
 
@@ -7914,6 +7971,7 @@ void ImGui::BeginDisabledOverrideReenable()
 {
     ImGuiContext& g = *GImGui;
     IM_ASSERT(g.CurrentItemFlags & ImGuiItemFlags_Disabled);
+    g.CurrentWindowStack.back().DisabledOverrideReenableAlphaBackup = g.Style.Alpha;
     g.Style.Alpha = g.DisabledAlphaBackup;
     g.CurrentItemFlags &= ~ImGuiItemFlags_Disabled;
     g.ItemFlagsStack.push_back(g.CurrentItemFlags);
@@ -7927,7 +7985,7 @@ void ImGui::EndDisabledOverrideReenable()
     IM_ASSERT(g.DisabledStackSize > 0);
     g.ItemFlagsStack.pop_back();
     g.CurrentItemFlags = g.ItemFlagsStack.back();
-    g.Style.Alpha = g.DisabledAlphaBackup * g.Style.DisabledAlpha;
+    g.Style.Alpha = g.CurrentWindowStack.back().DisabledOverrideReenableAlphaBackup;
 }
 
 void ImGui::PushTextWrapPos(float wrap_pos_x)
@@ -8213,7 +8271,7 @@ void ImGui::SetNextWindowPos(const ImVec2& pos, ImGuiCond cond, const ImVec2& pi
 {
     ImGuiContext& g = *GImGui;
     IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.
-    g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasPos;
+    g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasPos;
     g.NextWindowData.PosVal = pos;
     g.NextWindowData.PosPivotVal = pivot;
     g.NextWindowData.PosCond = cond ? cond : ImGuiCond_Always;
@@ -8223,7 +8281,7 @@ void ImGui::SetNextWindowSize(const ImVec2& size, ImGuiCond cond)
 {
     ImGuiContext& g = *GImGui;
     IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.
-    g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasSize;
+    g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasSize;
     g.NextWindowData.SizeVal = size;
     g.NextWindowData.SizeCond = cond ? cond : ImGuiCond_Always;
 }
@@ -8235,7 +8293,7 @@ void ImGui::SetNextWindowSize(const ImVec2& size, ImGuiCond cond)
 void ImGui::SetNextWindowSizeConstraints(const ImVec2& size_min, const ImVec2& size_max, ImGuiSizeCallback custom_callback, void* custom_callback_user_data)
 {
     ImGuiContext& g = *GImGui;
-    g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasSizeConstraint;
+    g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasSizeConstraint;
     g.NextWindowData.SizeConstraintRect = ImRect(size_min, size_max);
     g.NextWindowData.SizeCallback = custom_callback;
     g.NextWindowData.SizeCallbackUserData = custom_callback_user_data;
@@ -8246,14 +8304,14 @@ void ImGui::SetNextWindowSizeConstraints(const ImVec2& size_min, const ImVec2& s
 void ImGui::SetNextWindowContentSize(const ImVec2& size)
 {
     ImGuiContext& g = *GImGui;
-    g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasContentSize;
+    g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasContentSize;
     g.NextWindowData.ContentSizeVal = ImTrunc(size);
 }
 
 void ImGui::SetNextWindowScroll(const ImVec2& scroll)
 {
     ImGuiContext& g = *GImGui;
-    g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasScroll;
+    g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasScroll;
     g.NextWindowData.ScrollVal = scroll;
 }
 
@@ -8261,7 +8319,7 @@ void ImGui::SetNextWindowCollapsed(bool collapsed, ImGuiCond cond)
 {
     ImGuiContext& g = *GImGui;
     IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.
-    g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasCollapsed;
+    g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasCollapsed;
     g.NextWindowData.CollapsedVal = collapsed;
     g.NextWindowData.CollapsedCond = cond ? cond : ImGuiCond_Always;
 }
@@ -8269,7 +8327,7 @@ void ImGui::SetNextWindowCollapsed(bool collapsed, ImGuiCond cond)
 void ImGui::SetNextWindowBgAlpha(float alpha)
 {
     ImGuiContext& g = *GImGui;
-    g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasBgAlpha;
+    g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasBgAlpha;
     g.NextWindowData.BgAlphaVal = alpha;
 }
 
@@ -8277,7 +8335,7 @@ void ImGui::SetNextWindowBgAlpha(float alpha)
 void ImGui::SetNextWindowRefreshPolicy(ImGuiWindowRefreshFlags flags)
 {
     ImGuiContext& g = *GImGui;
-    g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasRefreshPolicy;
+    g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasRefreshPolicy;
     g.NextWindowData.RefreshFlagsVal = flags;
 }
 
@@ -8731,7 +8789,7 @@ ImGuiKeyData* ImGui::GetKeyData(ImGuiContext* ctx, ImGuiKey key)
     return &g.IO.KeysData[key - ImGuiKey_NamedKey_BEGIN];
 }
 
-// Those names a provided for debugging purpose and are not meant to be saved persistently not compared.
+// Those names are provided for debugging purpose and are not meant to be saved persistently nor compared.
 static const char* const GKeyNames[] =
 {
     "Tab", "LeftArrow", "RightArrow", "UpArrow", "DownArrow", "PageUp", "PageDown",
@@ -8746,7 +8804,7 @@ static const char* const GKeyNames[] =
     "Pause", "Keypad0", "Keypad1", "Keypad2", "Keypad3", "Keypad4", "Keypad5", "Keypad6",
     "Keypad7", "Keypad8", "Keypad9", "KeypadDecimal", "KeypadDivide", "KeypadMultiply",
     "KeypadSubtract", "KeypadAdd", "KeypadEnter", "KeypadEqual",
-    "AppBack", "AppForward",
+    "AppBack", "AppForward", "Oem102",
     "GamepadStart", "GamepadBack",
     "GamepadFaceLeft", "GamepadFaceRight", "GamepadFaceUp", "GamepadFaceDown",
     "GamepadDpadLeft", "GamepadDpadRight", "GamepadDpadUp", "GamepadDpadDown",
@@ -8788,7 +8846,7 @@ const char* ImGui::GetKeyChordName(ImGuiKeyChord key_chord)
         (key != ImGuiKey_None || key_chord == ImGuiKey_None) ? GetKeyName(key) : "");
     size_t len;
     if (key == ImGuiKey_None && key_chord != 0)
-        if ((len = strlen(g.TempKeychordName)) != 0) // Remove trailing '+'
+        if ((len = ImStrlen(g.TempKeychordName)) != 0) // Remove trailing '+'
             g.TempKeychordName[len - 1] = 0;
     return g.TempKeychordName;
 }
@@ -9660,7 +9718,7 @@ void ImGui::UpdateMouseWheel()
     if (g.IO.MouseWheelRequestAxisSwap)
         wheel = ImVec2(wheel.y, 0.0f);
 
-    // Maintain a rough average of moving magnitude on both axises
+    // Maintain a rough average of moving magnitude on both axes
     // FIXME: should by based on wall clock time rather than frame-counter
     g.WheelingAxisAvg.x = ImExponentialMovingAverage(g.WheelingAxisAvg.x, ImAbs(wheel.x), 30);
     g.WheelingAxisAvg.y = ImExponentialMovingAverage(g.WheelingAxisAvg.y, ImAbs(wheel.y), 30);
@@ -9673,7 +9731,7 @@ void ImGui::UpdateMouseWheel()
 
     // Mouse wheel scrolling: find target and apply
     // - don't renew lock if axis doesn't apply on the window.
-    // - select a main axis when both axises are being moved.
+    // - select a main axis when both axes are being moved.
     if (ImGuiWindow* window = (g.WheelingWindow ? g.WheelingWindow : FindBestWheelingWindow(wheel)))
         if (!(window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs))
         {
@@ -10172,7 +10230,8 @@ static void ImGui::ErrorCheckNewFrameSanityChecks()
     IM_ASSERT(g.Style.CurveTessellationTol > 0.0f                       && "Invalid style setting!");
     IM_ASSERT(g.Style.CircleTessellationMaxError > 0.0f                 && "Invalid style setting!");
     IM_ASSERT(g.Style.Alpha >= 0.0f && g.Style.Alpha <= 1.0f            && "Invalid style setting!"); // Allows us to avoid a few clamps in color computations
-    IM_ASSERT(g.Style.WindowMinSize.x >= 1.0f && g.Style.WindowMinSize.y >= 1.0f && "Invalid style setting.");
+    IM_ASSERT(g.Style.WindowMinSize.x >= 1.0f && g.Style.WindowMinSize.y >= 1.0f && "Invalid style setting!");
+    IM_ASSERT(g.Style.WindowBorderHoverPadding > 0.0f                   && "Invalid style setting!"); // Required otherwise cannot resize from borders.
     IM_ASSERT(g.Style.WindowMenuButtonPosition == ImGuiDir_None || g.Style.WindowMenuButtonPosition == ImGuiDir_Left || g.Style.WindowMenuButtonPosition == ImGuiDir_Right);
     IM_ASSERT(g.Style.ColorButtonPosition == ImGuiDir_Left || g.Style.ColorButtonPosition == ImGuiDir_Right);
 
@@ -10421,15 +10480,24 @@ void ImGui::ErrorCheckEndFrameFinalizeErrorTooltip()
         //BulletText("Code intending to use duplicate ID may use e.g. PushItemFlag(ImGuiItemFlags_AllowDuplicateId, true); ... PopItemFlag()"); // Not making this too visible for fear of it being abused.
         BulletText("Set io.ConfigDebugHighlightIdConflicts=false to disable this warning in non-programmers builds.");
         Separator();
-        Text("(Hold CTRL to: use");
-        SameLine();
-        if (SmallButton("Item Picker"))
-            DebugStartItemPicker();
-        SameLine();
-        Text("to break in item call-stack, or");
-        SameLine();
+        if (g.IO.ConfigDebugHighlightIdConflictsShowItemPicker)
+        {
+            Text("(Hold CTRL to: use ");
+            SameLine(0.0f, 0.0f);
+            if (SmallButton("Item Picker"))
+                DebugStartItemPicker();
+            SameLine(0.0f, 0.0f);
+            Text(" to break in item call-stack, or ");
+        }
+        else
+        {
+            Text("(Hold CTRL to ");
+        }
+        SameLine(0.0f, 0.0f);
         if (SmallButton("Open FAQ->About ID Stack System") && g.PlatformIO.Platform_OpenInShellFn != NULL)
             g.PlatformIO.Platform_OpenInShellFn(&g, "https://github.com/ocornut/imgui/blob/master/docs/FAQ.md#qa-usage");
+        SameLine(0.0f, 0.0f);
+        Text(")");
         EndErrorTooltip();
     }
 
@@ -11290,7 +11358,7 @@ bool ImGui::BeginTooltipEx(ImGuiTooltipFlags tooltip_flags, ImGuiWindowFlags ext
         // See FindBestWindowPosForPopup() for positionning logic of other tooltips (not drag and drop ones).
         //ImVec2 tooltip_pos = g.IO.MousePos - g.ActiveIdClickOffset - g.Style.WindowPadding;
         const bool is_touchscreen = (g.IO.MouseSource == ImGuiMouseSource_TouchScreen);
-        if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasPos) == 0)
+        if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasPos) == 0)
         {
             ImVec2 tooltip_pos = is_touchscreen ? (g.IO.MousePos + TOOLTIP_DEFAULT_OFFSET_TOUCH * g.Style.MouseCursorScale) : (g.IO.MousePos + TOOLTIP_DEFAULT_OFFSET_MOUSE * g.Style.MouseCursorScale);
             ImVec2 tooltip_pivot = is_touchscreen ? TOOLTIP_DEFAULT_PIVOT_TOUCH : ImVec2(0.0f, 0.0f);
@@ -11666,17 +11734,32 @@ bool ImGui::BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_window_flags)
     }
 
     char name[20];
-    if (extra_window_flags & ImGuiWindowFlags_ChildMenu)
-        ImFormatString(name, IM_ARRAYSIZE(name), "##Menu_%02d", g.BeginMenuDepth); // Recycle windows based on depth
-    else
-        ImFormatString(name, IM_ARRAYSIZE(name), "##Popup_%08x", id); // Not recycling, so we can close/open during the same frame
+    IM_ASSERT((extra_window_flags & ImGuiWindowFlags_ChildMenu) == 0); // Use BeginPopupMenuEx()
+    ImFormatString(name, IM_ARRAYSIZE(name), "##Popup_%08x", id); // No recycling, so we can close/open during the same frame
 
     bool is_open = Begin(name, NULL, extra_window_flags | ImGuiWindowFlags_Popup);
     if (!is_open) // NB: Begin can return false when the popup is completely clipped (e.g. zero size display)
         EndPopup();
-
     //g.CurrentWindow->FocusRouteParentWindow = g.CurrentWindow->ParentWindowInBeginStack;
+    return is_open;
+}
 
+bool ImGui::BeginPopupMenuEx(ImGuiID id, const char* label, ImGuiWindowFlags extra_window_flags)
+{
+    ImGuiContext& g = *GImGui;
+    if (!IsPopupOpen(id, ImGuiPopupFlags_None))
+    {
+        g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
+        return false;
+    }
+
+    char name[128];
+    IM_ASSERT(extra_window_flags & ImGuiWindowFlags_ChildMenu);
+    ImFormatString(name, IM_ARRAYSIZE(name), "%s###Menu_%02d", label, g.BeginMenuDepth); // Recycle windows based on depth
+    bool is_open = Begin(name, NULL, extra_window_flags | ImGuiWindowFlags_Popup);
+    if (!is_open) // NB: Begin can return false when the popup is completely clipped (e.g. zero size display)
+        EndPopup();
+    //g.CurrentWindow->FocusRouteParentWindow = g.CurrentWindow->ParentWindowInBeginStack;
     return is_open;
 }
 
@@ -11713,7 +11796,7 @@ bool ImGui::BeginPopupModal(const char* name, bool* p_open, ImGuiWindowFlags fla
     // Center modal windows by default for increased visibility
     // (this won't really last as settings will kick in, and is mostly for backward compatibility. user may do the same themselves)
     // FIXME: Should test for (PosCond & window->SetWindowPosAllowFlags) with the upcoming window.
-    if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasPos) == 0)
+    if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasPos) == 0)
     {
         const ImGuiViewport* viewport = GetMainViewport();
         SetNextWindowPos(viewport->GetCenter(), ImGuiCond_FirstUseEver, ImVec2(0.5f, 0.5f));
@@ -12006,7 +12089,7 @@ void ImGui::SetWindowFocus(const char* name)
 void ImGui::SetNextWindowFocus()
 {
     ImGuiContext& g = *GImGui;
-    g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasFocus;
+    g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasFocus;
 }
 
 // Similar to IsWindowHovered()
@@ -13628,6 +13711,33 @@ static void NavUpdateWindowingTarget(int focus_change_dir)
     g.NavWindowingToggleLayer = false;
 }
 
+// Apply focus and close overlay
+static void ImGui::NavUpdateWindowingApplyFocus(ImGuiWindow* apply_focus_window)
+{
+    ImGuiContext& g = *GImGui;
+    if (g.NavWindow == NULL || apply_focus_window != g.NavWindow->RootWindow)
+    {
+        ClearActiveID();
+        SetNavCursorVisibleAfterMove();
+        ClosePopupsOverWindow(apply_focus_window, false);
+        FocusWindow(apply_focus_window, ImGuiFocusRequestFlags_RestoreFocusedChild);
+        apply_focus_window = g.NavWindow;
+        if (apply_focus_window->NavLastIds[0] == 0)
+            NavInitWindow(apply_focus_window, false);
+
+        // If the window has ONLY a menu layer (no main layer), select it directly
+        // Use NavLayersActiveMaskNext since windows didn't have a chance to be Begin()-ed on this frame,
+        // so CTRL+Tab where the keys are only held for 1 frame will be able to use correct layers mask since
+        // the target window as already been previewed once.
+        // FIXME-NAV: This should be done in NavInit.. or in FocusWindow... However in both of those cases,
+        // we won't have a guarantee that windows has been visible before and therefore NavLayersActiveMask*
+        // won't be valid.
+        if (apply_focus_window->DC.NavLayersActiveMaskNext == (1 << ImGuiNavLayer_Menu))
+            g.NavLayer = ImGuiNavLayer_Menu;
+    }
+    g.NavWindowingTarget = NULL;
+}
+
 // Windowing management mode
 // Keyboard: CTRL+Tab (change focus/move/resize), Alt (toggle menu layer)
 // Gamepad:  Hold Menu/Square (change focus/move/resize), Tap Menu/Square (toggle menu layer)
@@ -13654,7 +13764,7 @@ static void ImGui::NavUpdateWindowing()
 
     // Start CTRL+Tab or Square+L/R window selection
     // (g.ConfigNavWindowingKeyNext/g.ConfigNavWindowingKeyPrev defaults are ImGuiMod_Ctrl|ImGuiKey_Tab and ImGuiMod_Ctrl|ImGuiMod_Shift|ImGuiKey_Tab)
-    const ImGuiID owner_id = ImHashStr("###NavUpdateWindowing");
+    const ImGuiID owner_id = ImHashStr("##NavUpdateWindowing");
     const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;
     const bool nav_keyboard_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0;
     const bool keyboard_next_window = allow_windowing && g.ConfigNavWindowingKeyNext && Shortcut(g.ConfigNavWindowingKeyNext, ImGuiInputFlags_Repeat | ImGuiInputFlags_RouteAlways, owner_id);
@@ -13681,6 +13791,7 @@ static void ImGui::NavUpdateWindowing()
                 SetKeyOwnersForKeyChord((g.ConfigNavWindowingKeyNext | g.ConfigNavWindowingKeyPrev) & ImGuiMod_Mask_, owner_id);
         }
 */
+
     // Gamepad update
     g.NavWindowingTimer += io.DeltaTime;
     if (g.NavWindowingTarget && g.NavInputSource == ImGuiInputSource_Gamepad)
@@ -13690,7 +13801,7 @@ static void ImGui::NavUpdateWindowing()
 
         // Select window to focus
         const int focus_change_dir = (int)IsKeyPressed(ImGuiKey_GamepadL1) - (int)IsKeyPressed(ImGuiKey_GamepadR1);
-        if (focus_change_dir != 0 && !false /*just_started_windowing_from_null_focus*/)
+		if (focus_change_dir != 0 && !false /*just_started_windowing_from_null_focus*/)
         {
             NavUpdateWindowingTarget(focus_change_dir);
             g.NavWindowingHighlightAlpha = 1.0f;
@@ -13782,28 +13893,8 @@ static void ImGui::NavUpdateWindowing()
     }
 
     // Apply final focus
-    if (apply_focus_window && (g.NavWindow == NULL || apply_focus_window != g.NavWindow->RootWindow))
-    {
-        ClearActiveID();
-        SetNavCursorVisibleAfterMove();
-        ClosePopupsOverWindow(apply_focus_window, false);
-        FocusWindow(apply_focus_window, ImGuiFocusRequestFlags_RestoreFocusedChild);
-        apply_focus_window = g.NavWindow;
-        if (apply_focus_window->NavLastIds[0] == 0)
-            NavInitWindow(apply_focus_window, false);
-
-        // If the window has ONLY a menu layer (no main layer), select it directly
-        // Use NavLayersActiveMaskNext since windows didn't have a chance to be Begin()-ed on this frame,
-        // so CTRL+Tab where the keys are only held for 1 frame will be able to use correct layers mask since
-        // the target window as already been previewed once.
-        // FIXME-NAV: This should be done in NavInit.. or in FocusWindow... However in both of those cases,
-        // we won't have a guarantee that windows has been visible before and therefore NavLayersActiveMask*
-        // won't be valid.
-        if (apply_focus_window->DC.NavLayersActiveMaskNext == (1 << ImGuiNavLayer_Menu))
-            g.NavLayer = ImGuiNavLayer_Menu;
-    }
     if (apply_focus_window)
-        g.NavWindowingTarget = NULL;
+        NavUpdateWindowingApplyFocus(apply_focus_window);
 
     // Apply menu/layer toggle
     if (apply_toggle_layer && g.NavWindow)
@@ -13857,12 +13948,12 @@ void ImGui::NavUpdateWindowingOverlay()
         return;
 
     if (g.NavWindowingListWindow == NULL)
-        g.NavWindowingListWindow = FindWindowByName("###NavWindowingOverlay");
+        g.NavWindowingListWindow = FindWindowByName("##NavWindowingOverlay");
     const ImGuiViewport* viewport = GetMainViewport();
     SetNextWindowSizeConstraints(ImVec2(viewport->Size.x * 0.20f, viewport->Size.y * 0.20f), ImVec2(FLT_MAX, FLT_MAX));
     SetNextWindowPos(viewport->GetCenter(), ImGuiCond_Always, ImVec2(0.5f, 0.5f));
     PushStyleVar(ImGuiStyleVar_WindowPadding, g.Style.WindowPadding * 2.0f);
-    Begin("###NavWindowingOverlay", NULL, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings);
+    Begin("##NavWindowingOverlay", NULL, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings);
     if (g.ContextName[0] != 0)
         SeparatorText(g.ContextName);
     for (int n = g.WindowsFocusOrder.Size - 1; n >= 0; n--)
@@ -14064,7 +14155,7 @@ bool ImGui::SetDragDropPayload(const char* type, const void* data, size_t data_s
         cond = ImGuiCond_Always;
 
     IM_ASSERT(type != NULL);
-    IM_ASSERT(strlen(type) < IM_ARRAYSIZE(payload.DataType) && "Payload type can be at most 32 characters long");
+    IM_ASSERT(ImStrlen(type) < IM_ARRAYSIZE(payload.DataType) && "Payload type can be at most 32 characters long");
     IM_ASSERT((data != NULL && data_size > 0) || (data == NULL && data_size == 0));
     IM_ASSERT(cond == ImGuiCond_Always || cond == ImGuiCond_Once);
     IM_ASSERT(payload.SourceId != 0); // Not called between BeginDragDropSource() and EndDragDropSource()
@@ -14308,7 +14399,7 @@ void ImGui::LogRenderedText(const ImVec2* ref_pos, const char* text, const char*
     }
 
     if (prefix)
-        LogRenderedText(ref_pos, prefix, prefix + strlen(prefix)); // Calculate end ourself to ensure "##" are included here.
+        LogRenderedText(ref_pos, prefix, prefix + ImStrlen(prefix)); // Calculate end ourself to ensure "##" are included here.
 
     // Re-adjust padding if we have popped out of our starting depth
     if (g.LogDepthRef > window->DC.TreeDepth)
@@ -14341,7 +14432,7 @@ void ImGui::LogRenderedText(const ImVec2* ref_pos, const char* text, const char*
     }
 
     if (suffix)
-        LogRenderedText(ref_pos, suffix, suffix + strlen(suffix));
+        LogRenderedText(ref_pos, suffix, suffix + ImStrlen(suffix));
 }
 
 // Start logging/capturing text output
@@ -14607,7 +14698,7 @@ void ImGui::LoadIniSettingsFromMemory(const char* ini_data, size_t ini_size)
     // For user convenience, we allow passing a non zero-terminated string (hence the ini_size parameter).
     // For our convenience and to make the code simpler, we'll also write zero-terminators within the buffer. So let's create a writable copy..
     if (ini_size == 0)
-        ini_size = strlen(ini_data);
+        ini_size = ImStrlen(ini_data);
     g.SettingsIniData.Buf.resize((int)ini_size + 1);
     char* const buf = g.SettingsIniData.Buf.Data;
     char* const buf_end = buf + ini_size;
@@ -14708,7 +14799,7 @@ ImGuiWindowSettings* ImGui::CreateNewWindowSettings(const char* name)
         if (const char* p = strstr(name, "###"))
             name = p;
     }
-    const size_t name_len = strlen(name);
+    const size_t name_len = ImStrlen(name);
 
     // Allocate chunk
     const size_t chunk_size = sizeof(ImGuiWindowSettings) + name_len + 1;
@@ -15000,7 +15091,7 @@ static void Platform_SetClipboardTextFn_DefaultImpl(ImGuiContext*, const char* t
     if (!main_clipboard)
         PasteboardCreate(kPasteboardClipboard, &main_clipboard);
     PasteboardClear(main_clipboard);
-    CFDataRef cf_data = CFDataCreate(kCFAllocatorDefault, (const UInt8*)text, strlen(text));
+    CFDataRef cf_data = CFDataCreate(kCFAllocatorDefault, (const UInt8*)text, ImStrlen(text));
     if (cf_data)
     {
         PasteboardPutItemFlavor(main_clipboard, (PasteboardItemID)1, CFSTR("public.utf8-plain-text"), cf_data, 0);
@@ -15054,7 +15145,7 @@ static void Platform_SetClipboardTextFn_DefaultImpl(ImGuiContext* ctx, const cha
 {
     ImGuiContext& g = *ctx;
     g.ClipboardHandlerData.clear();
-    const char* text_end = text + strlen(text);
+    const char* text_end = text + ImStrlen(text);
     g.ClipboardHandlerData.resize((int)(text_end - text) + 1);
     memcpy(&g.ClipboardHandlerData[0], text, (size_t)(text_end - text));
     g.ClipboardHandlerData[(int)(text_end - text)] = 0;
@@ -15068,11 +15159,13 @@ static void Platform_SetClipboardTextFn_DefaultImpl(ImGuiContext* ctx, const cha
 #if defined(__APPLE__) && TARGET_OS_IPHONE
 #define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS
 #endif
-
+#if defined(__3DS__)
+#define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS
+#endif
 #if defined(_WIN32) && defined(IMGUI_DISABLE_WIN32_FUNCTIONS)
 #define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS
 #endif
-#endif
+#endif // #ifndef IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS
 
 #ifndef IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS
 #ifdef _WIN32
@@ -15082,7 +15175,11 @@ static void Platform_SetClipboardTextFn_DefaultImpl(ImGuiContext* ctx, const cha
 #endif
 static bool Platform_OpenInShellFn_DefaultImpl(ImGuiContext*, const char* path)
 {
-    return (INT_PTR)::ShellExecuteA(NULL, "open", path, NULL, NULL, SW_SHOWDEFAULT) > 32;
+    const int path_wsize = ::MultiByteToWideChar(CP_UTF8, 0, path, -1, NULL, 0);
+    ImVector path_wbuf;
+    path_wbuf.resize(path_wsize);
+    ::MultiByteToWideChar(CP_UTF8, 0, path, -1, path_wbuf.Data, path_wsize);
+    return (INT_PTR)::ShellExecuteW(NULL, L"open", path_wbuf.Data, NULL, NULL, SW_SHOWDEFAULT) > 32;
 }
 #else
 #include 
@@ -15380,11 +15477,9 @@ void ImGui::ShowFontAtlas(ImFontAtlas* atlas)
     if (TreeNode("Font Atlas", "Font Atlas (%dx%d pixels)", atlas->TexWidth, atlas->TexHeight))
     {
         ImGuiContext& g = *GImGui;
-        ImGuiMetricsConfig* cfg = &g.DebugMetricsConfig;
-        Checkbox("Tint with Text Color", &cfg->ShowAtlasTintedWithTextColor); // Using text color ensure visibility of core atlas data, but will alter custom colored icons
-        ImVec4 tint_col = cfg->ShowAtlasTintedWithTextColor ? GetStyleColorVec4(ImGuiCol_Text) : ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
-        ImVec4 border_col = GetStyleColorVec4(ImGuiCol_Border);
-        Image(atlas->TexID, ImVec2((float)atlas->TexWidth, (float)atlas->TexHeight), ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), tint_col, border_col);
+        PushStyleVar(ImGuiStyleVar_ImageBorderSize, ImMax(1.0f, g.Style.ImageBorderSize));
+        ImageWithBg(atlas->TexID, ImVec2((float)atlas->TexWidth, (float)atlas->TexHeight), ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), ImVec4(0.0f, 0.0f, 0.0f, 1.0f));
+        PopStyleVar();
         TreePop();
     }
 }
@@ -16173,7 +16268,7 @@ void ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, co
 void ImGui::DebugNodeFont(ImFont* font)
 {
     bool opened = TreeNode(font, "Font: \"%s\": %.2f px, %d glyphs, %d sources(s)",
-        font->ConfigData ? font->ConfigData[0].Name : "", font->FontSize, font->Glyphs.Size, font->ConfigDataCount);
+        font->Sources ? font->Sources[0].Name : "", font->FontSize, font->Glyphs.Size, font->SourcesCount);
 
     // Display preview text
     if (!opened)
@@ -16206,14 +16301,14 @@ void ImGui::DebugNodeFont(ImFont* font)
     Text("Ellipsis character: '%s' (U+%04X)", ImTextCharToUtf8(c_str, font->EllipsisChar), font->EllipsisChar);
     const int surface_sqrt = (int)ImSqrt((float)font->MetricsTotalSurface);
     Text("Texture Area: about %d px ~%dx%d px", font->MetricsTotalSurface, surface_sqrt, surface_sqrt);
-    for (int config_i = 0; config_i < font->ConfigDataCount; config_i++)
-        if (font->ConfigData)
+    for (int config_i = 0; config_i < font->SourcesCount; config_i++)
+        if (font->Sources)
         {
-            const ImFontConfig* cfg = &font->ConfigData[config_i];
+            const ImFontConfig* src = &font->Sources[config_i];
             int oversample_h, oversample_v;
-            ImFontAtlasBuildGetOversampleFactors(cfg, &oversample_h, &oversample_v);
+            ImFontAtlasBuildGetOversampleFactors(src, &oversample_h, &oversample_v);
             BulletText("Input %d: \'%s\', Oversample: (%d=>%d,%d=>%d), PixelSnapH: %d, Offset: (%.1f,%.1f)",
-                config_i, cfg->Name, cfg->OversampleH, oversample_h, cfg->OversampleV, oversample_v, cfg->PixelSnapH, cfg->GlyphOffset.x, cfg->GlyphOffset.y);
+                config_i, src->Name, src->OversampleH, oversample_h, src->OversampleV, oversample_v, src->PixelSnapH, src->GlyphOffset.x, src->GlyphOffset.y);
         }
 
     // Display all glyphs of the fonts in separate pages of 256 characters
@@ -16540,7 +16635,7 @@ static void ShowDebugLogFlag(const char* name, ImGuiDebugLogFlags flags)
 void ImGui::ShowDebugLogWindow(bool* p_open)
 {
     ImGuiContext& g = *GImGui;
-    if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize) == 0)
+    if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSize) == 0)
         SetNextWindowSize(ImVec2(0.0f, GetFontSize() * 12.0f), ImGuiCond_FirstUseEver);
     if (!Begin("Dear ImGui Debug Log", p_open) || GetCurrentWindow()->BeginCount > 1)
     {
@@ -16822,7 +16917,7 @@ void ImGui::DebugHookIdInfo(ImGuiID id, ImGuiDataType data_type, const void* dat
         ImFormatString(info->Desc, IM_ARRAYSIZE(info->Desc), "%d", (int)(intptr_t)data_id);
         break;
     case ImGuiDataType_String:
-        ImFormatString(info->Desc, IM_ARRAYSIZE(info->Desc), "%.*s", data_id_end ? (int)((const char*)data_id_end - (const char*)data_id) : (int)strlen((const char*)data_id), (const char*)data_id);
+        ImFormatString(info->Desc, IM_ARRAYSIZE(info->Desc), "%.*s", data_id_end ? (int)((const char*)data_id_end - (const char*)data_id) : (int)ImStrlen((const char*)data_id), (const char*)data_id);
         break;
     case ImGuiDataType_Pointer:
         ImFormatString(info->Desc, IM_ARRAYSIZE(info->Desc), "(void*)0x%p", data_id);
@@ -16860,7 +16955,7 @@ static int StackToolFormatLevelInfo(ImGuiIDStackTool* tool, int n, bool format_f
 void ImGui::ShowIDStackToolWindow(bool* p_open)
 {
     ImGuiContext& g = *GImGui;
-    if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize) == 0)
+    if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSize) == 0)
         SetNextWindowSize(ImVec2(0.0f, GetFontSize() * 8.0f), ImGuiCond_FirstUseEver);
     if (!Begin("Dear ImGui ID Stack Tool", p_open) || GetCurrentWindow()->BeginCount > 1)
     {
@@ -16870,42 +16965,44 @@ void ImGui::ShowIDStackToolWindow(bool* p_open)
 
     // Display hovered/active status
     ImGuiIDStackTool* tool = &g.DebugIDStackTool;
-    const ImGuiID hovered_id = g.HoveredIdPreviousFrame;
-    const ImGuiID active_id = g.ActiveId;
-#ifdef IMGUI_ENABLE_TEST_ENGINE
-    Text("HoveredId: 0x%08X (\"%s\"), ActiveId:  0x%08X (\"%s\")", hovered_id, hovered_id ? ImGuiTestEngine_FindItemDebugLabel(&g, hovered_id) : "", active_id, active_id ? ImGuiTestEngine_FindItemDebugLabel(&g, active_id) : "");
-#else
-    Text("HoveredId: 0x%08X, ActiveId:  0x%08X", hovered_id, active_id);
-#endif
+
+    // Build and display path
+    tool->ResultPathBuf.resize(0);
+    for (int stack_n = 0; stack_n < tool->Results.Size; stack_n++)
+    {
+        char level_desc[256];
+        StackToolFormatLevelInfo(tool, stack_n, false, level_desc, IM_ARRAYSIZE(level_desc));
+        tool->ResultPathBuf.append(stack_n == 0 ? "//" : "/");
+        for (int n = 0; level_desc[n]; n++)
+        {
+            if (level_desc[n] == '/')
+                tool->ResultPathBuf.append("\\");
+            tool->ResultPathBuf.append(level_desc + n, level_desc + n + 1);
+        }
+    }
+    Text("0x%08X", tool->QueryId);
     SameLine();
     MetricsHelpMarker("Hover an item with the mouse to display elements of the ID Stack leading to the item's final ID.\nEach level of the stack correspond to a PushID() call.\nAll levels of the stack are hashed together to make the final ID of a widget (ID displayed at the bottom level of the stack).\nRead FAQ entry about the ID stack for details.");
 
     // CTRL+C to copy path
     const float time_since_copy = (float)g.Time - tool->CopyToClipboardLastTime;
-    Checkbox("Ctrl+C: copy path to clipboard", &tool->CopyToClipboardOnCtrlC);
+    SameLine();
+    PushStyleVarY(ImGuiStyleVar_FramePadding, 0.0f); Checkbox("Ctrl+C: copy path", &tool->CopyToClipboardOnCtrlC); PopStyleVar();
     SameLine();
     TextColored((time_since_copy >= 0.0f && time_since_copy < 0.75f && ImFmod(time_since_copy, 0.25f) < 0.25f * 0.5f) ? ImVec4(1.f, 1.f, 0.3f, 1.f) : ImVec4(), "*COPIED*");
     if (tool->CopyToClipboardOnCtrlC && Shortcut(ImGuiMod_Ctrl | ImGuiKey_C, ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteOverFocused))
     {
         tool->CopyToClipboardLastTime = (float)g.Time;
-        char* p = g.TempBuffer.Data;
-        char* p_end = p + g.TempBuffer.Size;
-        for (int stack_n = 0; stack_n < tool->Results.Size && p + 3 < p_end; stack_n++)
-        {
-            *p++ = '/';
-            char level_desc[256];
-            StackToolFormatLevelInfo(tool, stack_n, false, level_desc, IM_ARRAYSIZE(level_desc));
-            for (int n = 0; level_desc[n] && p + 2 < p_end; n++)
-            {
-                if (level_desc[n] == '/')
-                    *p++ = '\\';
-                *p++ = level_desc[n];
-            }
-        }
-        *p = '\0';
-        SetClipboardText(g.TempBuffer.Data);
+        SetClipboardText(tool->ResultPathBuf.c_str());
     }
 
+    Text("- Path \"%s\"", tool->ResultPathBuf.c_str());
+#ifdef IMGUI_ENABLE_TEST_ENGINE
+    Text("- Label \"%s\"", tool->QueryId ? ImGuiTestEngine_FindItemDebugLabel(&g, tool->QueryId) : "");
+#endif
+
+    Separator();
+
     // Display decorated stack
     tool->LastActiveFrame = g.FrameCount;
     if (tool->Results.Size > 0 && BeginTable("##table", 3, ImGuiTableFlags_Borders))
diff --git a/3rdparty/imgui/src/imgui_demo.cpp b/3rdparty/imgui/src/imgui_demo.cpp
index 03ce4710ad..10fa434dca 100644
--- a/3rdparty/imgui/src/imgui_demo.cpp
+++ b/3rdparty/imgui/src/imgui_demo.cpp
@@ -1,4 +1,4 @@
-// dear imgui, v1.91.8
+// dear imgui, v1.91.9b
 // (demo code)
 
 // Help:
@@ -70,15 +70,38 @@ Index of this file:
 
 // [SECTION] Forward Declarations
 // [SECTION] Helpers
-// [SECTION] Helpers: ExampleTreeNode, ExampleMemberInfo (for use by Property Editor & Multi-Select demos)
 // [SECTION] Demo Window / ShowDemoWindow()
-// [SECTION] ShowDemoWindowMenuBar()
-// [SECTION] ShowDemoWindowWidgets()
-// [SECTION] ShowDemoWindowMultiSelect()
-// [SECTION] ShowDemoWindowLayout()
-// [SECTION] ShowDemoWindowPopups()
-// [SECTION] ShowDemoWindowTables()
-// [SECTION] ShowDemoWindowInputs()
+// [SECTION] DemoWindowMenuBar()
+// [SECTION] Helpers: ExampleTreeNode, ExampleMemberInfo (for use by Property Editor & Multi-Select demos)
+// [SECTION] DemoWindowWidgetsBasic()
+// [SECTION] DemoWindowWidgetsBullets()
+// [SECTION] DemoWindowWidgetsCollapsingHeaders()
+// [SECTION] DemoWindowWidgetsComboBoxes()
+// [SECTION] DemoWindowWidgetsColorAndPickers()
+// [SECTION] DemoWindowWidgetsDataTypes()
+// [SECTION] DemoWindowWidgetsDisableBlocks()
+// [SECTION] DemoWindowWidgetsDragAndDrop()
+// [SECTION] DemoWindowWidgetsDragsAndSliders()
+// [SECTION] DemoWindowWidgetsImages()
+// [SECTION] DemoWindowWidgetsListBoxes()
+// [SECTION] DemoWindowWidgetsMultiComponents()
+// [SECTION] DemoWindowWidgetsPlotting()
+// [SECTION] DemoWindowWidgetsProgressBars()
+// [SECTION] DemoWindowWidgetsQueryingStatuses()
+// [SECTION] DemoWindowWidgetsSelectables()
+// [SECTION] DemoWindowWidgetsSelectionAndMultiSelect()
+// [SECTION] DemoWindowWidgetsTabs()
+// [SECTION] DemoWindowWidgetsText()
+// [SECTION] DemoWindowWidgetsTextFilter()
+// [SECTION] DemoWindowWidgetsTextInput()
+// [SECTION] DemoWindowWidgetsTooltips()
+// [SECTION] DemoWindowWidgetsTreeNodes()
+// [SECTION] DemoWindowWidgetsVerticalSliders()
+// [SECTION] DemoWindowWidgets()
+// [SECTION] DemoWindowLayout()
+// [SECTION] DemoWindowPopups()
+// [SECTION] DemoWindowTables()
+// [SECTION] DemoWindowInputs()
 // [SECTION] About Window / ShowAboutWindow()
 // [SECTION] Style Editor / ShowStyleEditor()
 // [SECTION] User Guide / ShowUserGuide()
@@ -136,6 +159,7 @@ Index of this file:
 #pragma clang diagnostic ignored "-Wold-style-cast"                 // warning: use of old-style cast                           // yes, they are more terse.
 #pragma clang diagnostic ignored "-Wdeprecated-declarations"        // warning: 'xx' is deprecated: The POSIX name for this..   // for strdup used in demo code (so user can copy & paste the code)
 #pragma clang diagnostic ignored "-Wint-to-void-pointer-cast"       // warning: cast to 'void *' from smaller integer type
+#pragma clang diagnostic ignored "-Wformat"                         // warning: format specifies type 'int' but the argument has type 'unsigned int'
 #pragma clang diagnostic ignored "-Wformat-security"                // warning: format string is not a string literal
 #pragma clang diagnostic ignored "-Wexit-time-destructors"          // warning: declaration requires an exit-time destructor    // exit-time destruction order is undefined. if MemFree() leads to users code that has been disabled before exit it might cause problems. ImGui coding style welcomes static/globals.
 #pragma clang diagnostic ignored "-Wunused-macros"                  // warning: macro is not used                               // we define snprintf/vsnprintf on Windows so they are available, but not always used.
@@ -144,6 +168,7 @@ Index of this file:
 #pragma clang diagnostic ignored "-Wreserved-id-macro"              // warning: macro name is a reserved identifier
 #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion"  // warning: implicit conversion from 'xxx' to 'float' may lose precision
 #pragma clang diagnostic ignored "-Wunsafe-buffer-usage"            // warning: 'xxx' is an unsafe pointer used for buffer access
+#pragma clang diagnostic ignored "-Wswitch-default"                 // warning: 'switch' missing 'default' label
 #elif defined(__GNUC__)
 #pragma GCC diagnostic ignored "-Wpragmas"                          // warning: unknown option after '#pragma GCC diagnostic' kind
 #pragma GCC diagnostic ignored "-Wfloat-equal"                      // warning: comparing floating-point with '==' or '!=' is unsafe
@@ -225,14 +250,18 @@ static void ShowExampleMenuFile();
 
 // We split the contents of the big ShowDemoWindow() function into smaller functions
 // (because the link time of very large functions tends to grow non-linearly)
-static void ShowDemoWindowMenuBar(ImGuiDemoWindowData* demo_data);
-static void ShowDemoWindowWidgets(ImGuiDemoWindowData* demo_data);
-static void ShowDemoWindowMultiSelect(ImGuiDemoWindowData* demo_data);
-static void ShowDemoWindowLayout();
-static void ShowDemoWindowPopups();
-static void ShowDemoWindowTables();
-static void ShowDemoWindowColumns();
-static void ShowDemoWindowInputs();
+static void DemoWindowMenuBar(ImGuiDemoWindowData* demo_data);
+static void DemoWindowWidgets(ImGuiDemoWindowData* demo_data);
+static void DemoWindowLayout();
+static void DemoWindowPopups();
+static void DemoWindowTables();
+static void DemoWindowColumns();
+static void DemoWindowInputs();
+
+// Helper tree functions used by Property Editor & Multi-Select demos
+struct ExampleTreeNode;
+static ExampleTreeNode* ExampleTree_CreateNode(const char* name, int uid, ExampleTreeNode* parent);
+static void             ExampleTree_DestroyNode(ExampleTreeNode* node);
 
 //-----------------------------------------------------------------------------
 // [SECTION] Helpers
@@ -260,97 +289,6 @@ ImGuiDemoMarkerCallback             GImGuiDemoMarkerCallback = NULL;
 void*                               GImGuiDemoMarkerCallbackUserData = NULL;
 #define IMGUI_DEMO_MARKER(section)  do { if (GImGuiDemoMarkerCallback != NULL) GImGuiDemoMarkerCallback(__FILE__, __LINE__, section, GImGuiDemoMarkerCallbackUserData); } while (0)
 
-//-----------------------------------------------------------------------------
-// [SECTION] Helpers: ExampleTreeNode, ExampleMemberInfo (for use by Property Editor etc.)
-//-----------------------------------------------------------------------------
-
-// Simple representation for a tree
-// (this is designed to be simple to understand for our demos, not to be fancy or efficient etc.)
-struct ExampleTreeNode
-{
-    // Tree structure
-    char                        Name[28] = "";
-    int                         UID = 0;
-    ExampleTreeNode*            Parent = NULL;
-    ImVector  Childs;
-    unsigned short              IndexInParent = 0;  // Maintaining this allows us to implement linear traversal more easily
-
-    // Leaf Data
-    bool                        HasData = false;    // All leaves have data
-    bool                        DataMyBool = true;
-    int                         DataMyInt = 128;
-    ImVec2                      DataMyVec2 = ImVec2(0.0f, 3.141592f);
-};
-
-// Simple representation of struct metadata/serialization data.
-// (this is a minimal version of what a typical advanced application may provide)
-struct ExampleMemberInfo
-{
-    const char*     Name;       // Member name
-    ImGuiDataType   DataType;   // Member type
-    int             DataCount;  // Member count (1 when scalar)
-    int             Offset;     // Offset inside parent structure
-};
-
-// Metadata description of ExampleTreeNode struct.
-static const ExampleMemberInfo ExampleTreeNodeMemberInfos[]
-{
-    { "MyName",     ImGuiDataType_String,  1, offsetof(ExampleTreeNode, Name) },
-    { "MyBool",     ImGuiDataType_Bool,    1, offsetof(ExampleTreeNode, DataMyBool) },
-    { "MyInt",      ImGuiDataType_S32,     1, offsetof(ExampleTreeNode, DataMyInt) },
-    { "MyVec2",     ImGuiDataType_Float,   2, offsetof(ExampleTreeNode, DataMyVec2) },
-};
-
-static ExampleTreeNode* ExampleTree_CreateNode(const char* name, int uid, ExampleTreeNode* parent)
-{
-    ExampleTreeNode* node = IM_NEW(ExampleTreeNode);
-    snprintf(node->Name, IM_ARRAYSIZE(node->Name), "%s", name);
-    node->UID = uid;
-    node->Parent = parent;
-    node->IndexInParent = parent ? (unsigned short)parent->Childs.Size : 0;
-    if (parent)
-        parent->Childs.push_back(node);
-    return node;
-}
-
-static void ExampleTree_DestroyNode(ExampleTreeNode* node)
-{
-    for (ExampleTreeNode* child_node : node->Childs)
-        ExampleTree_DestroyNode(child_node);
-    IM_DELETE(node);
-}
-
-// Create example tree data
-// (this allocates _many_ more times than most other code in either Dear ImGui or others demo)
-static ExampleTreeNode* ExampleTree_CreateDemoTree()
-{
-    static const char* root_names[] = { "Apple", "Banana", "Cherry", "Kiwi", "Mango", "Orange", "Pear", "Pineapple", "Strawberry", "Watermelon" };
-    const size_t NAME_MAX_LEN = sizeof(ExampleTreeNode::Name);
-    char name_buf[NAME_MAX_LEN];
-    int uid = 0;
-    ExampleTreeNode* node_L0 = ExampleTree_CreateNode("", ++uid, NULL);
-    const int root_items_multiplier = 2;
-    for (int idx_L0 = 0; idx_L0 < IM_ARRAYSIZE(root_names) * root_items_multiplier; idx_L0++)
-    {
-        snprintf(name_buf, IM_ARRAYSIZE(name_buf), "%s %d", root_names[idx_L0 / root_items_multiplier], idx_L0 % root_items_multiplier);
-        ExampleTreeNode* node_L1 = ExampleTree_CreateNode(name_buf, ++uid, node_L0);
-        const int number_of_childs = (int)strlen(node_L1->Name);
-        for (int idx_L1 = 0; idx_L1 < number_of_childs; idx_L1++)
-        {
-            snprintf(name_buf, IM_ARRAYSIZE(name_buf), "Child %d", idx_L1);
-            ExampleTreeNode* node_L2 = ExampleTree_CreateNode(name_buf, ++uid, node_L1);
-            node_L2->HasData = true;
-            if (idx_L1 == 0)
-            {
-                snprintf(name_buf, IM_ARRAYSIZE(name_buf), "Sub-child %d", 0);
-                ExampleTreeNode* node_L3 = ExampleTree_CreateNode(name_buf, ++uid, node_L2);
-                node_L3->HasData = true;
-            }
-        }
-    }
-    return node_L0;
-}
-
 //-----------------------------------------------------------------------------
 // [SECTION] Demo Window / ShowDemoWindow()
 //-----------------------------------------------------------------------------
@@ -382,6 +320,7 @@ struct ImGuiDemoWindowData
     bool ShowAbout = false;
 
     // Other data
+    bool DisableSections = false;
     ExampleTreeNode* DemoTree = NULL;
 
     ~ImGuiDemoWindowData() { if (DemoTree) ExampleTree_DestroyNode(DemoTree); }
@@ -475,7 +414,7 @@ void ImGui::ShowDemoWindow(bool* p_open)
     //ImGui::PushItemWidth(-ImGui::GetWindowWidth() * 0.35f);   // e.g. Use 2/3 of the space for widgets and 1/3 for labels (right align)
 
     // Menu Bar
-    ShowDemoWindowMenuBar(&demo_data);
+    DemoWindowMenuBar(&demo_data);
 
     ImGui::Text("dear imgui says hello! (%s) (%d)", IMGUI_VERSION, IMGUI_VERSION_NUM);
     ImGui::Spacing();
@@ -584,7 +523,7 @@ void ImGui::ShowDemoWindow(bool* p_open)
                 "- Error recovery is not perfect nor guaranteed! It is a feature to ease development.\n"
                 "- You not are not supposed to rely on it in the course of a normal application run.\n"
                 "- Possible usage: facilitate recovery from errors triggered from a scripting language or after specific exceptions handlers.\n"
-                "- Always ensure that on programmers seat you have at minimum Asserts or Tooltips enabled when making direct imgui API call!"
+                "- Always ensure that on programmers seat you have at minimum Asserts or Tooltips enabled when making direct imgui API call! "
                 "Otherwise it would severely hinder your ability to catch and correct mistakes!");
             ImGui::Checkbox("io.ConfigErrorRecoveryEnableAssert", &io.ConfigErrorRecoveryEnableAssert);
             ImGui::Checkbox("io.ConfigErrorRecoveryEnableDebugLog", &io.ConfigErrorRecoveryEnableDebugLog);
@@ -683,11 +622,11 @@ void ImGui::ShowDemoWindow(bool* p_open)
     }
 
     // All demo contents
-    ShowDemoWindowWidgets(&demo_data);
-    ShowDemoWindowLayout();
-    ShowDemoWindowPopups();
-    ShowDemoWindowTables();
-    ShowDemoWindowInputs();
+    DemoWindowWidgets(&demo_data);
+    DemoWindowLayout();
+    DemoWindowPopups();
+    DemoWindowTables();
+    DemoWindowInputs();
 
     // End of ShowDemoWindow()
     ImGui::PopItemWidth();
@@ -695,10 +634,10 @@ void ImGui::ShowDemoWindow(bool* p_open)
 }
 
 //-----------------------------------------------------------------------------
-// [SECTION] ShowDemoWindowMenuBar()
+// [SECTION] DemoWindowMenuBar()
 //-----------------------------------------------------------------------------
 
-static void ShowDemoWindowMenuBar(ImGuiDemoWindowData* demo_data)
+static void DemoWindowMenuBar(ImGuiDemoWindowData* demo_data)
 {
     IMGUI_DEMO_MARKER("Menu");
     if (ImGui::BeginMenuBar())
@@ -770,20 +709,102 @@ static void ShowDemoWindowMenuBar(ImGuiDemoWindowData* demo_data)
 }
 
 //-----------------------------------------------------------------------------
-// [SECTION] ShowDemoWindowWidgets()
+// [SECTION] Helpers: ExampleTreeNode, ExampleMemberInfo (for use by Property Editor & Multi-Select demos)
 //-----------------------------------------------------------------------------
 
-static void ShowDemoWindowWidgets(ImGuiDemoWindowData* demo_data)
+// Simple representation for a tree
+// (this is designed to be simple to understand for our demos, not to be fancy or efficient etc.)
+struct ExampleTreeNode
 {
-    IMGUI_DEMO_MARKER("Widgets");
-    //ImGui::SetNextItemOpen(true, ImGuiCond_Once);
-    if (!ImGui::CollapsingHeader("Widgets"))
-        return;
+    // Tree structure
+    char                        Name[28] = "";
+    int                         UID = 0;
+    ExampleTreeNode* Parent = NULL;
+    ImVector  Childs;
+    unsigned short              IndexInParent = 0;  // Maintaining this allows us to implement linear traversal more easily
 
-    static bool disable_all = false; // The Checkbox for that is inside the "Disabled" section at the bottom
-    if (disable_all)
-        ImGui::BeginDisabled();
+    // Leaf Data
+    bool                        HasData = false;    // All leaves have data
+    bool                        DataMyBool = true;
+    int                         DataMyInt = 128;
+    ImVec2                      DataMyVec2 = ImVec2(0.0f, 3.141592f);
+};
 
+// Simple representation of struct metadata/serialization data.
+// (this is a minimal version of what a typical advanced application may provide)
+struct ExampleMemberInfo
+{
+    const char* Name;       // Member name
+    ImGuiDataType   DataType;   // Member type
+    int             DataCount;  // Member count (1 when scalar)
+    int             Offset;     // Offset inside parent structure
+};
+
+// Metadata description of ExampleTreeNode struct.
+static const ExampleMemberInfo ExampleTreeNodeMemberInfos[]
+{
+    { "MyName",     ImGuiDataType_String,  1, offsetof(ExampleTreeNode, Name) },
+    { "MyBool",     ImGuiDataType_Bool,    1, offsetof(ExampleTreeNode, DataMyBool) },
+    { "MyInt",      ImGuiDataType_S32,     1, offsetof(ExampleTreeNode, DataMyInt) },
+    { "MyVec2",     ImGuiDataType_Float,   2, offsetof(ExampleTreeNode, DataMyVec2) },
+};
+
+static ExampleTreeNode* ExampleTree_CreateNode(const char* name, int uid, ExampleTreeNode* parent)
+{
+    ExampleTreeNode* node = IM_NEW(ExampleTreeNode);
+    snprintf(node->Name, IM_ARRAYSIZE(node->Name), "%s", name);
+    node->UID = uid;
+    node->Parent = parent;
+    node->IndexInParent = parent ? (unsigned short)parent->Childs.Size : 0;
+    if (parent)
+        parent->Childs.push_back(node);
+    return node;
+}
+
+static void ExampleTree_DestroyNode(ExampleTreeNode* node)
+{
+    for (ExampleTreeNode* child_node : node->Childs)
+        ExampleTree_DestroyNode(child_node);
+    IM_DELETE(node);
+}
+
+// Create example tree data
+// (this allocates _many_ more times than most other code in either Dear ImGui or others demo)
+static ExampleTreeNode* ExampleTree_CreateDemoTree()
+{
+    static const char* root_names[] = { "Apple", "Banana", "Cherry", "Kiwi", "Mango", "Orange", "Pear", "Pineapple", "Strawberry", "Watermelon" };
+    const size_t NAME_MAX_LEN = sizeof(ExampleTreeNode::Name);
+    char name_buf[NAME_MAX_LEN];
+    int uid = 0;
+    ExampleTreeNode* node_L0 = ExampleTree_CreateNode("", ++uid, NULL);
+    const int root_items_multiplier = 2;
+    for (int idx_L0 = 0; idx_L0 < IM_ARRAYSIZE(root_names) * root_items_multiplier; idx_L0++)
+    {
+        snprintf(name_buf, IM_ARRAYSIZE(name_buf), "%s %d", root_names[idx_L0 / root_items_multiplier], idx_L0 % root_items_multiplier);
+        ExampleTreeNode* node_L1 = ExampleTree_CreateNode(name_buf, ++uid, node_L0);
+        const int number_of_childs = (int)strlen(node_L1->Name);
+        for (int idx_L1 = 0; idx_L1 < number_of_childs; idx_L1++)
+        {
+            snprintf(name_buf, IM_ARRAYSIZE(name_buf), "Child %d", idx_L1);
+            ExampleTreeNode* node_L2 = ExampleTree_CreateNode(name_buf, ++uid, node_L1);
+            node_L2->HasData = true;
+            if (idx_L1 == 0)
+            {
+                snprintf(name_buf, IM_ARRAYSIZE(name_buf), "Sub-child %d", 0);
+                ExampleTreeNode* node_L3 = ExampleTree_CreateNode(name_buf, ++uid, node_L2);
+                node_L3->HasData = true;
+            }
+        }
+    }
+    return node_L0;
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] DemoWindowWidgetsBasic()
+//-----------------------------------------------------------------------------
+
+static void DemoWindowWidgetsBasic()
+{
     IMGUI_DEMO_MARKER("Widgets/Basic");
     if (ImGui::TreeNode("Basic"))
     {
@@ -861,8 +882,8 @@ static void ShowDemoWindowWidgets(ImGuiDemoWindowData* demo_data)
                 "Hold SHIFT or use mouse to select text.\n"
                 "CTRL+Left/Right to word jump.\n"
                 "CTRL+A or Double-Click to select all.\n"
-                "CTRL+X,CTRL+C,CTRL+V clipboard.\n"
-                "CTRL+Z,CTRL+Y undo/redo.\n"
+                "CTRL+X,CTRL+C,CTRL+V for clipboard.\n"
+                "CTRL+Z to undo, CTRL+Y/CTRL+SHIFT+Z to redo.\n"
                 "ESCAPE to revert.\n\n"
                 "PROGRAMMER:\n"
                 "You can use the ImGuiInputTextFlags_CallbackResize facility if you need to wire InputText() "
@@ -979,233 +1000,44 @@ static void ShowDemoWindowWidgets(ImGuiDemoWindowData* demo_data)
                 "Refer to the \"List boxes\" section below for an explanation of how to use the more flexible and general BeginListBox/EndListBox API.");
         }
 
-        ImGui::TreePop();
-    }
-
-    IMGUI_DEMO_MARKER("Widgets/Tooltips");
-    if (ImGui::TreeNode("Tooltips"))
-    {
-        // Tooltips are windows following the mouse. They do not take focus away.
-        ImGui::SeparatorText("General");
-
-        // Typical use cases:
-        // - Short-form (text only):      SetItemTooltip("Hello");
-        // - Short-form (any contents):   if (BeginItemTooltip()) { Text("Hello"); EndTooltip(); }
-
-        // - Full-form (text only):       if (IsItemHovered(...)) { SetTooltip("Hello"); }
-        // - Full-form (any contents):    if (IsItemHovered(...) && BeginTooltip()) { Text("Hello"); EndTooltip(); }
-
-        HelpMarker(
-            "Tooltip are typically created by using a IsItemHovered() + SetTooltip() sequence.\n\n"
-            "We provide a helper SetItemTooltip() function to perform the two with standards flags.");
-
-        ImVec2 sz = ImVec2(-FLT_MIN, 0.0f);
-
-        ImGui::Button("Basic", sz);
-        ImGui::SetItemTooltip("I am a tooltip");
-
-        ImGui::Button("Fancy", sz);
-        if (ImGui::BeginItemTooltip())
-        {
-            ImGui::Text("I am a fancy tooltip");
-            static float arr[] = { 0.6f, 0.1f, 1.0f, 0.5f, 0.92f, 0.1f, 0.2f };
-            ImGui::PlotLines("Curve", arr, IM_ARRAYSIZE(arr));
-            ImGui::Text("Sin(time) = %f", sinf((float)ImGui::GetTime()));
-            ImGui::EndTooltip();
-        }
-
-        ImGui::SeparatorText("Always On");
-
-        // Showcase NOT relying on a IsItemHovered() to emit a tooltip.
-        // Here the tooltip is always emitted when 'always_on == true'.
-        static int always_on = 0;
-        ImGui::RadioButton("Off", &always_on, 0);
-        ImGui::SameLine();
-        ImGui::RadioButton("Always On (Simple)", &always_on, 1);
-        ImGui::SameLine();
-        ImGui::RadioButton("Always On (Advanced)", &always_on, 2);
-        if (always_on == 1)
-            ImGui::SetTooltip("I am following you around.");
-        else if (always_on == 2 && ImGui::BeginTooltip())
-        {
-            ImGui::ProgressBar(sinf((float)ImGui::GetTime()) * 0.5f + 0.5f, ImVec2(ImGui::GetFontSize() * 25, 0.0f));
-            ImGui::EndTooltip();
-        }
-
-        ImGui::SeparatorText("Custom");
-
-        HelpMarker(
-            "Passing ImGuiHoveredFlags_ForTooltip to IsItemHovered() is the preferred way to standardize"
-            "tooltip activation details across your application. You may however decide to use custom"
-            "flags for a specific tooltip instance.");
-
-        // The following examples are passed for documentation purpose but may not be useful to most users.
-        // Passing ImGuiHoveredFlags_ForTooltip to IsItemHovered() will pull ImGuiHoveredFlags flags values from
-        // 'style.HoverFlagsForTooltipMouse' or 'style.HoverFlagsForTooltipNav' depending on whether mouse or keyboard/gamepad is being used.
-        // With default settings, ImGuiHoveredFlags_ForTooltip is equivalent to ImGuiHoveredFlags_DelayShort + ImGuiHoveredFlags_Stationary.
-        ImGui::Button("Manual", sz);
-        if (ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip))
-            ImGui::SetTooltip("I am a manually emitted tooltip.");
-
-        ImGui::Button("DelayNone", sz);
-        if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNone))
-            ImGui::SetTooltip("I am a tooltip with no delay.");
-
-        ImGui::Button("DelayShort", sz);
-        if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort | ImGuiHoveredFlags_NoSharedDelay))
-            ImGui::SetTooltip("I am a tooltip with a short delay (%0.2f sec).", ImGui::GetStyle().HoverDelayShort);
-
-        ImGui::Button("DelayLong", sz);
-        if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal | ImGuiHoveredFlags_NoSharedDelay))
-            ImGui::SetTooltip("I am a tooltip with a long delay (%0.2f sec).", ImGui::GetStyle().HoverDelayNormal);
-
-        ImGui::Button("Stationary", sz);
-        if (ImGui::IsItemHovered(ImGuiHoveredFlags_Stationary))
-            ImGui::SetTooltip("I am a tooltip requiring mouse to be stationary before activating.");
-
-        // Using ImGuiHoveredFlags_ForTooltip will pull flags from 'style.HoverFlagsForTooltipMouse' or 'style.HoverFlagsForTooltipNav',
-        // which default value include the ImGuiHoveredFlags_AllowWhenDisabled flag.
-        ImGui::BeginDisabled();
-        ImGui::Button("Disabled item", sz);
-        if (ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip))
-            ImGui::SetTooltip("I am a a tooltip for a disabled item.");
-        ImGui::EndDisabled();
+        // Testing ImGuiOnceUponAFrame helper.
+        //static ImGuiOnceUponAFrame once;
+        //for (int i = 0; i < 5; i++)
+        //    if (once)
+        //        ImGui::Text("This will be displayed only once.");
 
         ImGui::TreePop();
     }
+}
 
-    // Testing ImGuiOnceUponAFrame helper.
-    //static ImGuiOnceUponAFrame once;
-    //for (int i = 0; i < 5; i++)
-    //    if (once)
-    //        ImGui::Text("This will be displayed only once.");
+//-----------------------------------------------------------------------------
+// [SECTION] DemoWindowWidgetsBullets()
+//-----------------------------------------------------------------------------
 
-    IMGUI_DEMO_MARKER("Widgets/Tree Nodes");
-    if (ImGui::TreeNode("Tree Nodes"))
+static void DemoWindowWidgetsBullets()
+{
+    IMGUI_DEMO_MARKER("Widgets/Bullets");
+    if (ImGui::TreeNode("Bullets"))
     {
-        IMGUI_DEMO_MARKER("Widgets/Tree Nodes/Basic trees");
-        if (ImGui::TreeNode("Basic trees"))
+        ImGui::BulletText("Bullet point 1");
+        ImGui::BulletText("Bullet point 2\nOn multiple lines");
+        if (ImGui::TreeNode("Tree node"))
         {
-            for (int i = 0; i < 5; i++)
-            {
-                // Use SetNextItemOpen() so set the default state of a node to be open. We could
-                // also use TreeNodeEx() with the ImGuiTreeNodeFlags_DefaultOpen flag to achieve the same thing!
-                if (i == 0)
-                    ImGui::SetNextItemOpen(true, ImGuiCond_Once);
-
-                // Here we use PushID() to generate a unique base ID, and then the "" used as TreeNode id won't conflict.
-                // An alternative to using 'PushID() + TreeNode("", ...)' to generate a unique ID is to use 'TreeNode((void*)(intptr_t)i, ...)',
-                // aka generate a dummy pointer-sized value to be hashed. The demo below uses that technique. Both are fine.
-                ImGui::PushID(i);
-                if (ImGui::TreeNode("", "Child %d", i))
-                {
-                    ImGui::Text("blah blah");
-                    ImGui::SameLine();
-                    if (ImGui::SmallButton("button")) {}
-                    ImGui::TreePop();
-                }
-                ImGui::PopID();
-            }
-            ImGui::TreePop();
-        }
-
-        IMGUI_DEMO_MARKER("Widgets/Tree Nodes/Advanced, with Selectable nodes");
-        if (ImGui::TreeNode("Advanced, with Selectable nodes"))
-        {
-            HelpMarker(
-                "This is a more typical looking tree with selectable nodes.\n"
-                "Click to select, CTRL+Click to toggle, click on arrows or double-click to open.");
-            static ImGuiTreeNodeFlags base_flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_SpanAvailWidth;
-            static bool align_label_with_current_x_position = false;
-            static bool test_drag_and_drop = false;
-            ImGui::CheckboxFlags("ImGuiTreeNodeFlags_OpenOnArrow",       &base_flags, ImGuiTreeNodeFlags_OpenOnArrow);
-            ImGui::CheckboxFlags("ImGuiTreeNodeFlags_OpenOnDoubleClick", &base_flags, ImGuiTreeNodeFlags_OpenOnDoubleClick);
-            ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanAvailWidth",    &base_flags, ImGuiTreeNodeFlags_SpanAvailWidth); ImGui::SameLine(); HelpMarker("Extend hit area to all available width instead of allowing more items to be laid out after the node.");
-            ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanFullWidth",     &base_flags, ImGuiTreeNodeFlags_SpanFullWidth);
-            ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanLabelWidth",    &base_flags, ImGuiTreeNodeFlags_SpanLabelWidth); ImGui::SameLine(); HelpMarker("Reduce hit area to the text label and a bit of margin.");
-            ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanAllColumns",    &base_flags, ImGuiTreeNodeFlags_SpanAllColumns); ImGui::SameLine(); HelpMarker("For use in Tables only.");
-            ImGui::CheckboxFlags("ImGuiTreeNodeFlags_AllowOverlap",      &base_flags, ImGuiTreeNodeFlags_AllowOverlap);
-            ImGui::CheckboxFlags("ImGuiTreeNodeFlags_Framed",            &base_flags, ImGuiTreeNodeFlags_Framed); ImGui::SameLine(); HelpMarker("Draw frame with background (e.g. for CollapsingHeader)");
-            ImGui::CheckboxFlags("ImGuiTreeNodeFlags_NavLeftJumpsBackHere", &base_flags, ImGuiTreeNodeFlags_NavLeftJumpsBackHere);
-            ImGui::Checkbox("Align label with current X position", &align_label_with_current_x_position);
-            ImGui::Checkbox("Test tree node as drag source", &test_drag_and_drop);
-            ImGui::Text("Hello!");
-            if (align_label_with_current_x_position)
-                ImGui::Unindent(ImGui::GetTreeNodeToLabelSpacing());
-
-            // 'selection_mask' is dumb representation of what may be user-side selection state.
-            //  You may retain selection state inside or outside your objects in whatever format you see fit.
-            // 'node_clicked' is temporary storage of what node we have clicked to process selection at the end
-            /// of the loop. May be a pointer to your own node type, etc.
-            static int selection_mask = (1 << 2);
-            int node_clicked = -1;
-            for (int i = 0; i < 6; i++)
-            {
-                // Disable the default "open on single-click behavior" + set Selected flag according to our selection.
-                // To alter selection we use IsItemClicked() && !IsItemToggledOpen(), so clicking on an arrow doesn't alter selection.
-                ImGuiTreeNodeFlags node_flags = base_flags;
-                const bool is_selected = (selection_mask & (1 << i)) != 0;
-                if (is_selected)
-                    node_flags |= ImGuiTreeNodeFlags_Selected;
-                if (i < 3)
-                {
-                    // Items 0..2 are Tree Node
-                    bool node_open = ImGui::TreeNodeEx((void*)(intptr_t)i, node_flags, "Selectable Node %d", i);
-                    if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen())
-                        node_clicked = i;
-                    if (test_drag_and_drop && ImGui::BeginDragDropSource())
-                    {
-                        ImGui::SetDragDropPayload("_TREENODE", NULL, 0);
-                        ImGui::Text("This is a drag and drop source");
-                        ImGui::EndDragDropSource();
-                    }
-                    if (i == 2 && (base_flags & ImGuiTreeNodeFlags_SpanLabelWidth))
-                    {
-                        // Item 2 has an additional inline button to help demonstrate SpanLabelWidth.
-                        ImGui::SameLine();
-                        if (ImGui::SmallButton("button")) {}
-                    }
-                    if (node_open)
-                    {
-                        ImGui::BulletText("Blah blah\nBlah Blah");
-                        ImGui::SameLine();
-                        ImGui::SmallButton("Button");
-                        ImGui::TreePop();
-                    }
-                }
-                else
-                {
-                    // Items 3..5 are Tree Leaves
-                    // The only reason we use TreeNode at all is to allow selection of the leaf. Otherwise we can
-                    // use BulletText() or advance the cursor by GetTreeNodeToLabelSpacing() and call Text().
-                    node_flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; // ImGuiTreeNodeFlags_Bullet
-                    ImGui::TreeNodeEx((void*)(intptr_t)i, node_flags, "Selectable Leaf %d", i);
-                    if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen())
-                        node_clicked = i;
-                    if (test_drag_and_drop && ImGui::BeginDragDropSource())
-                    {
-                        ImGui::SetDragDropPayload("_TREENODE", NULL, 0);
-                        ImGui::Text("This is a drag and drop source");
-                        ImGui::EndDragDropSource();
-                    }
-                }
-            }
-            if (node_clicked != -1)
-            {
-                // Update selection state
-                // (process outside of tree loop to avoid visual inconsistencies during the clicking frame)
-                if (ImGui::GetIO().KeyCtrl)
-                    selection_mask ^= (1 << node_clicked);          // CTRL+click to toggle
-                else //if (!(selection_mask & (1 << node_clicked))) // Depending on selection behavior you want, may want to preserve selection when clicking on item that is part of the selection
-                    selection_mask = (1 << node_clicked);           // Click to single-select
-            }
-            if (align_label_with_current_x_position)
-                ImGui::Indent(ImGui::GetTreeNodeToLabelSpacing());
+            ImGui::BulletText("Another bullet point");
             ImGui::TreePop();
         }
+        ImGui::Bullet(); ImGui::Text("Bullet point 3 (two calls)");
+        ImGui::Bullet(); ImGui::SmallButton("Button");
         ImGui::TreePop();
     }
+}
 
+//-----------------------------------------------------------------------------
+// [SECTION] DemoWindowWidgetsCollapsingHeaders()
+//-----------------------------------------------------------------------------
+
+static void DemoWindowWidgetsCollapsingHeaders()
+{
     IMGUI_DEMO_MARKER("Widgets/Collapsing Headers");
     if (ImGui::TreeNode("Collapsing Headers"))
     {
@@ -1229,890 +1061,14 @@ static void ShowDemoWindowWidgets(ImGuiDemoWindowData* demo_data)
         */
         ImGui::TreePop();
     }
+}
 
-    IMGUI_DEMO_MARKER("Widgets/Bullets");
-    if (ImGui::TreeNode("Bullets"))
-    {
-        ImGui::BulletText("Bullet point 1");
-        ImGui::BulletText("Bullet point 2\nOn multiple lines");
-        if (ImGui::TreeNode("Tree node"))
-        {
-            ImGui::BulletText("Another bullet point");
-            ImGui::TreePop();
-        }
-        ImGui::Bullet(); ImGui::Text("Bullet point 3 (two calls)");
-        ImGui::Bullet(); ImGui::SmallButton("Button");
-        ImGui::TreePop();
-    }
-
-    IMGUI_DEMO_MARKER("Widgets/Text");
-    if (ImGui::TreeNode("Text"))
-    {
-        IMGUI_DEMO_MARKER("Widgets/Text/Colored Text");
-        if (ImGui::TreeNode("Colorful Text"))
-        {
-            // Using shortcut. You can use PushStyleColor()/PopStyleColor() for more flexibility.
-            ImGui::TextColored(ImVec4(1.0f, 0.0f, 1.0f, 1.0f), "Pink");
-            ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "Yellow");
-            ImGui::TextDisabled("Disabled");
-            ImGui::SameLine(); HelpMarker("The TextDisabled color is stored in ImGuiStyle.");
-            ImGui::TreePop();
-        }
-
-        IMGUI_DEMO_MARKER("Widgets/Text/Word Wrapping");
-        if (ImGui::TreeNode("Word Wrapping"))
-        {
-            // Using shortcut. You can use PushTextWrapPos()/PopTextWrapPos() for more flexibility.
-            ImGui::TextWrapped(
-                "This text should automatically wrap on the edge of the window. The current implementation "
-                "for text wrapping follows simple rules suitable for English and possibly other languages.");
-            ImGui::Spacing();
-
-            static float wrap_width = 200.0f;
-            ImGui::SliderFloat("Wrap width", &wrap_width, -20, 600, "%.0f");
-
-            ImDrawList* draw_list = ImGui::GetWindowDrawList();
-            for (int n = 0; n < 2; n++)
-            {
-                ImGui::Text("Test paragraph %d:", n);
-                ImVec2 pos = ImGui::GetCursorScreenPos();
-                ImVec2 marker_min = ImVec2(pos.x + wrap_width, pos.y);
-                ImVec2 marker_max = ImVec2(pos.x + wrap_width + 10, pos.y + ImGui::GetTextLineHeight());
-                ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + wrap_width);
-                if (n == 0)
-                    ImGui::Text("The lazy dog is a good dog. This paragraph should fit within %.0f pixels. Testing a 1 character word. The quick brown fox jumps over the lazy dog.", wrap_width);
-                else
-                    ImGui::Text("aaaaaaaa bbbbbbbb, c cccccccc,dddddddd. d eeeeeeee   ffffffff. gggggggg!hhhhhhhh");
-
-                // Draw actual text bounding box, following by marker of our expected limit (should not overlap!)
-                draw_list->AddRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), IM_COL32(255, 255, 0, 255));
-                draw_list->AddRectFilled(marker_min, marker_max, IM_COL32(255, 0, 255, 255));
-                ImGui::PopTextWrapPos();
-            }
-
-            ImGui::TreePop();
-        }
-
-        IMGUI_DEMO_MARKER("Widgets/Text/UTF-8 Text");
-        if (ImGui::TreeNode("UTF-8 Text"))
-        {
-            // UTF-8 test with Japanese characters
-            // (Needs a suitable font? Try "Google Noto" or "Arial Unicode". See docs/FONTS.md for details.)
-            // - From C++11 you can use the u8"my text" syntax to encode literal strings as UTF-8
-            // - For earlier compiler, you may be able to encode your sources as UTF-8 (e.g. in Visual Studio, you
-            //   can save your source files as 'UTF-8 without signature').
-            // - FOR THIS DEMO FILE ONLY, BECAUSE WE WANT TO SUPPORT OLD COMPILERS, WE ARE *NOT* INCLUDING RAW UTF-8
-            //   CHARACTERS IN THIS SOURCE FILE. Instead we are encoding a few strings with hexadecimal constants.
-            //   Don't do this in your application! Please use u8"text in any language" in your application!
-            // Note that characters values are preserved even by InputText() if the font cannot be displayed,
-            // so you can safely copy & paste garbled characters into another application.
-            ImGui::TextWrapped(
-                "CJK text will only appear if the font was loaded with the appropriate CJK character ranges. "
-                "Call io.Fonts->AddFontFromFileTTF() manually to load extra character ranges. "
-                "Read docs/FONTS.md for details.");
-            ImGui::Text("Hiragana: \xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91\xe3\x81\x93 (kakikukeko)");
-            ImGui::Text("Kanjis: \xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e (nihongo)");
-            static char buf[32] = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e";
-            //static char buf[32] = u8"NIHONGO"; // <- this is how you would write it with C++11, using real kanjis
-            ImGui::InputText("UTF-8 input", buf, IM_ARRAYSIZE(buf));
-            ImGui::TreePop();
-        }
-        ImGui::TreePop();
-    }
-
-    IMGUI_DEMO_MARKER("Widgets/Images");
-    if (ImGui::TreeNode("Images"))
-    {
-        ImGuiIO& io = ImGui::GetIO();
-        ImGui::TextWrapped(
-            "Below we are displaying the font texture (which is the only texture we have access to in this demo). "
-            "Use the 'ImTextureID' type as storage to pass pointers or identifier to your own texture data. "
-            "Hover the texture for a zoomed view!");
-
-        // Below we are displaying the font texture because it is the only texture we have access to inside the demo!
-        // Remember that ImTextureID is just storage for whatever you want it to be. It is essentially a value that
-        // will be passed to the rendering backend via the ImDrawCmd structure.
-        // If you use one of the default imgui_impl_XXXX.cpp rendering backend, they all have comments at the top
-        // of their respective source file to specify what they expect to be stored in ImTextureID, for example:
-        // - The imgui_impl_dx11.cpp renderer expect a 'ID3D11ShaderResourceView*' pointer
-        // - The imgui_impl_opengl3.cpp renderer expect a GLuint OpenGL texture identifier, etc.
-        // More:
-        // - If you decided that ImTextureID = MyEngineTexture*, then you can pass your MyEngineTexture* pointers
-        //   to ImGui::Image(), and gather width/height through your own functions, etc.
-        // - You can use ShowMetricsWindow() to inspect the draw data that are being passed to your renderer,
-        //   it will help you debug issues if you are confused about it.
-        // - Consider using the lower-level ImDrawList::AddImage() API, via ImGui::GetWindowDrawList()->AddImage().
-        // - Read https://github.com/ocornut/imgui/blob/master/docs/FAQ.md
-        // - Read https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples
-        ImTextureID my_tex_id = io.Fonts->TexID;
-        float my_tex_w = (float)io.Fonts->TexWidth;
-        float my_tex_h = (float)io.Fonts->TexHeight;
-        {
-            static bool use_text_color_for_tint = false;
-            ImGui::Checkbox("Use Text Color for Tint", &use_text_color_for_tint);
-            ImGui::Text("%.0fx%.0f", my_tex_w, my_tex_h);
-            ImVec2 pos = ImGui::GetCursorScreenPos();
-            ImVec2 uv_min = ImVec2(0.0f, 0.0f);                 // Top-left
-            ImVec2 uv_max = ImVec2(1.0f, 1.0f);                 // Lower-right
-            ImVec4 tint_col = use_text_color_for_tint ? ImGui::GetStyleColorVec4(ImGuiCol_Text) : ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // No tint
-            ImVec4 border_col = ImGui::GetStyleColorVec4(ImGuiCol_Border);
-            ImGui::Image(my_tex_id, ImVec2(my_tex_w, my_tex_h), uv_min, uv_max, tint_col, border_col);
-            if (ImGui::BeginItemTooltip())
-            {
-                float region_sz = 32.0f;
-                float region_x = io.MousePos.x - pos.x - region_sz * 0.5f;
-                float region_y = io.MousePos.y - pos.y - region_sz * 0.5f;
-                float zoom = 4.0f;
-                if (region_x < 0.0f) { region_x = 0.0f; }
-                else if (region_x > my_tex_w - region_sz) { region_x = my_tex_w - region_sz; }
-                if (region_y < 0.0f) { region_y = 0.0f; }
-                else if (region_y > my_tex_h - region_sz) { region_y = my_tex_h - region_sz; }
-                ImGui::Text("Min: (%.2f, %.2f)", region_x, region_y);
-                ImGui::Text("Max: (%.2f, %.2f)", region_x + region_sz, region_y + region_sz);
-                ImVec2 uv0 = ImVec2((region_x) / my_tex_w, (region_y) / my_tex_h);
-                ImVec2 uv1 = ImVec2((region_x + region_sz) / my_tex_w, (region_y + region_sz) / my_tex_h);
-                ImGui::Image(my_tex_id, ImVec2(region_sz * zoom, region_sz * zoom), uv0, uv1, tint_col, border_col);
-                ImGui::EndTooltip();
-            }
-        }
-
-        IMGUI_DEMO_MARKER("Widgets/Images/Textured buttons");
-        ImGui::TextWrapped("And now some textured buttons..");
-        static int pressed_count = 0;
-        for (int i = 0; i < 8; i++)
-        {
-            // UV coordinates are often (0.0f, 0.0f) and (1.0f, 1.0f) to display an entire textures.
-            // Here are trying to display only a 32x32 pixels area of the texture, hence the UV computation.
-            // Read about UV coordinates here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples
-            ImGui::PushID(i);
-            if (i > 0)
-                ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(i - 1.0f, i - 1.0f));
-            ImVec2 size = ImVec2(32.0f, 32.0f);                         // Size of the image we want to make visible
-            ImVec2 uv0 = ImVec2(0.0f, 0.0f);                            // UV coordinates for lower-left
-            ImVec2 uv1 = ImVec2(32.0f / my_tex_w, 32.0f / my_tex_h);    // UV coordinates for (32,32) in our texture
-            ImVec4 bg_col = ImVec4(0.0f, 0.0f, 0.0f, 1.0f);             // Black background
-            ImVec4 tint_col = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);           // No tint
-            if (ImGui::ImageButton("", my_tex_id, size, uv0, uv1, bg_col, tint_col))
-                pressed_count += 1;
-            if (i > 0)
-                ImGui::PopStyleVar();
-            ImGui::PopID();
-            ImGui::SameLine();
-        }
-        ImGui::NewLine();
-        ImGui::Text("Pressed %d times.", pressed_count);
-        ImGui::TreePop();
-    }
-
-    IMGUI_DEMO_MARKER("Widgets/Combo");
-    if (ImGui::TreeNode("Combo"))
-    {
-        // Combo Boxes are also called "Dropdown" in other systems
-        // Expose flags as checkbox for the demo
-        static ImGuiComboFlags flags = 0;
-        ImGui::CheckboxFlags("ImGuiComboFlags_PopupAlignLeft", &flags, ImGuiComboFlags_PopupAlignLeft);
-        ImGui::SameLine(); HelpMarker("Only makes a difference if the popup is larger than the combo");
-        if (ImGui::CheckboxFlags("ImGuiComboFlags_NoArrowButton", &flags, ImGuiComboFlags_NoArrowButton))
-            flags &= ~ImGuiComboFlags_NoPreview;     // Clear incompatible flags
-        if (ImGui::CheckboxFlags("ImGuiComboFlags_NoPreview", &flags, ImGuiComboFlags_NoPreview))
-            flags &= ~(ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_WidthFitPreview); // Clear incompatible flags
-        if (ImGui::CheckboxFlags("ImGuiComboFlags_WidthFitPreview", &flags, ImGuiComboFlags_WidthFitPreview))
-            flags &= ~ImGuiComboFlags_NoPreview;
-
-        // Override default popup height
-        if (ImGui::CheckboxFlags("ImGuiComboFlags_HeightSmall", &flags, ImGuiComboFlags_HeightSmall))
-            flags &= ~(ImGuiComboFlags_HeightMask_ & ~ImGuiComboFlags_HeightSmall);
-        if (ImGui::CheckboxFlags("ImGuiComboFlags_HeightRegular", &flags, ImGuiComboFlags_HeightRegular))
-            flags &= ~(ImGuiComboFlags_HeightMask_ & ~ImGuiComboFlags_HeightRegular);
-        if (ImGui::CheckboxFlags("ImGuiComboFlags_HeightLargest", &flags, ImGuiComboFlags_HeightLargest))
-            flags &= ~(ImGuiComboFlags_HeightMask_ & ~ImGuiComboFlags_HeightLargest);
-
-        // Using the generic BeginCombo() API, you have full control over how to display the combo contents.
-        // (your selection data could be an index, a pointer to the object, an id for the object, a flag intrusively
-        // stored in the object itself, etc.)
-        const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIII", "JJJJ", "KKKK", "LLLLLLL", "MMMM", "OOOOOOO" };
-        static int item_selected_idx = 0; // Here we store our selection data as an index.
-
-        // Pass in the preview value visible before opening the combo (it could technically be different contents or not pulled from items[])
-        const char* combo_preview_value = items[item_selected_idx];
-
-        if (ImGui::BeginCombo("combo 1", combo_preview_value, flags))
-        {
-            for (int n = 0; n < IM_ARRAYSIZE(items); n++)
-            {
-                const bool is_selected = (item_selected_idx == n);
-                if (ImGui::Selectable(items[n], is_selected))
-                    item_selected_idx = n;
-
-                // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
-                if (is_selected)
-                    ImGui::SetItemDefaultFocus();
-            }
-            ImGui::EndCombo();
-        }
-
-        ImGui::Spacing();
-        ImGui::SeparatorText("One-liner variants");
-        HelpMarker("Flags above don't apply to this section.");
-
-        // Simplified one-liner Combo() API, using values packed in a single constant string
-        // This is a convenience for when the selection set is small and known at compile-time.
-        static int item_current_2 = 0;
-        ImGui::Combo("combo 2 (one-liner)", &item_current_2, "aaaa\0bbbb\0cccc\0dddd\0eeee\0\0");
-
-        // Simplified one-liner Combo() using an array of const char*
-        // This is not very useful (may obsolete): prefer using BeginCombo()/EndCombo() for full control.
-        static int item_current_3 = -1; // If the selection isn't within 0..count, Combo won't display a preview
-        ImGui::Combo("combo 3 (array)", &item_current_3, items, IM_ARRAYSIZE(items));
-
-        // Simplified one-liner Combo() using an accessor function
-        static int item_current_4 = 0;
-        ImGui::Combo("combo 4 (function)", &item_current_4, [](void* data, int n) { return ((const char**)data)[n]; }, items, IM_ARRAYSIZE(items));
-
-        ImGui::TreePop();
-    }
-
-    IMGUI_DEMO_MARKER("Widgets/List Boxes");
-    if (ImGui::TreeNode("List boxes"))
-    {
-        // BeginListBox() is essentially a thin wrapper to using BeginChild()/EndChild()
-        // using the ImGuiChildFlags_FrameStyle flag for stylistic changes + displaying a label.
-        // You may be tempted to simply use BeginChild() directly. However note that BeginChild() requires EndChild()
-        // to always be called (inconsistent with BeginListBox()/EndListBox()).
-
-        // Using the generic BeginListBox() API, you have full control over how to display the combo contents.
-        // (your selection data could be an index, a pointer to the object, an id for the object, a flag intrusively
-        // stored in the object itself, etc.)
-        const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIII", "JJJJ", "KKKK", "LLLLLLL", "MMMM", "OOOOOOO" };
-        static int item_selected_idx = 0; // Here we store our selected data as an index.
-
-        static bool item_highlight = false;
-        int item_highlighted_idx = -1; // Here we store our highlighted data as an index.
-        ImGui::Checkbox("Highlight hovered item in second listbox", &item_highlight);
-
-        if (ImGui::BeginListBox("listbox 1"))
-        {
-            for (int n = 0; n < IM_ARRAYSIZE(items); n++)
-            {
-                const bool is_selected = (item_selected_idx == n);
-                if (ImGui::Selectable(items[n], is_selected))
-                    item_selected_idx = n;
-
-                if (item_highlight && ImGui::IsItemHovered())
-                    item_highlighted_idx = n;
-
-                // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
-                if (is_selected)
-                    ImGui::SetItemDefaultFocus();
-            }
-            ImGui::EndListBox();
-        }
-        ImGui::SameLine(); HelpMarker("Here we are sharing selection state between both boxes.");
-
-        // Custom size: use all width, 5 items tall
-        ImGui::Text("Full-width:");
-        if (ImGui::BeginListBox("##listbox 2", ImVec2(-FLT_MIN, 5 * ImGui::GetTextLineHeightWithSpacing())))
-        {
-            for (int n = 0; n < IM_ARRAYSIZE(items); n++)
-            {
-                bool is_selected = (item_selected_idx == n);
-                ImGuiSelectableFlags flags = (item_highlighted_idx == n) ? ImGuiSelectableFlags_Highlight : 0;
-                if (ImGui::Selectable(items[n], is_selected, flags))
-                    item_selected_idx = n;
-
-                // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
-                if (is_selected)
-                    ImGui::SetItemDefaultFocus();
-            }
-            ImGui::EndListBox();
-        }
-
-        ImGui::TreePop();
-    }
-
-    IMGUI_DEMO_MARKER("Widgets/Selectables");
-    //ImGui::SetNextItemOpen(true, ImGuiCond_Once);
-    if (ImGui::TreeNode("Selectables"))
-    {
-        // Selectable() has 2 overloads:
-        // - The one taking "bool selected" as a read-only selection information.
-        //   When Selectable() has been clicked it returns true and you can alter selection state accordingly.
-        // - The one taking "bool* p_selected" as a read-write selection information (convenient in some cases)
-        // The earlier is more flexible, as in real application your selection may be stored in many different ways
-        // and not necessarily inside a bool value (e.g. in flags within objects, as an external list, etc).
-        IMGUI_DEMO_MARKER("Widgets/Selectables/Basic");
-        if (ImGui::TreeNode("Basic"))
-        {
-            static bool selection[5] = { false, true, false, false };
-            ImGui::Selectable("1. I am selectable", &selection[0]);
-            ImGui::Selectable("2. I am selectable", &selection[1]);
-            ImGui::Selectable("3. I am selectable", &selection[2]);
-            if (ImGui::Selectable("4. I am double clickable", selection[3], ImGuiSelectableFlags_AllowDoubleClick))
-                if (ImGui::IsMouseDoubleClicked(0))
-                    selection[3] = !selection[3];
-            ImGui::TreePop();
-        }
-
-        IMGUI_DEMO_MARKER("Widgets/Selectables/Rendering more items on the same line");
-        if (ImGui::TreeNode("Rendering more items on the same line"))
-        {
-            // (1) Using SetNextItemAllowOverlap()
-            // (2) Using the Selectable() override that takes "bool* p_selected" parameter, the bool value is toggled automatically.
-            static bool selected[3] = { false, false, false };
-            ImGui::SetNextItemAllowOverlap(); ImGui::Selectable("main.c",    &selected[0]); ImGui::SameLine(); ImGui::SmallButton("Link 1");
-            ImGui::SetNextItemAllowOverlap(); ImGui::Selectable("Hello.cpp", &selected[1]); ImGui::SameLine(); ImGui::SmallButton("Link 2");
-            ImGui::SetNextItemAllowOverlap(); ImGui::Selectable("Hello.h",   &selected[2]); ImGui::SameLine(); ImGui::SmallButton("Link 3");
-            ImGui::TreePop();
-        }
-
-        IMGUI_DEMO_MARKER("Widgets/Selectables/In Tables");
-        if (ImGui::TreeNode("In Tables"))
-        {
-            static bool selected[10] = {};
-
-            if (ImGui::BeginTable("split1", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_Borders))
-            {
-                for (int i = 0; i < 10; i++)
-                {
-                    char label[32];
-                    sprintf(label, "Item %d", i);
-                    ImGui::TableNextColumn();
-                    ImGui::Selectable(label, &selected[i]); // FIXME-TABLE: Selection overlap
-                }
-                ImGui::EndTable();
-            }
-            ImGui::Spacing();
-            if (ImGui::BeginTable("split2", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_Borders))
-            {
-                for (int i = 0; i < 10; i++)
-                {
-                    char label[32];
-                    sprintf(label, "Item %d", i);
-                    ImGui::TableNextRow();
-                    ImGui::TableNextColumn();
-                    ImGui::Selectable(label, &selected[i], ImGuiSelectableFlags_SpanAllColumns);
-                    ImGui::TableNextColumn();
-                    ImGui::Text("Some other contents");
-                    ImGui::TableNextColumn();
-                    ImGui::Text("123456");
-                }
-                ImGui::EndTable();
-            }
-            ImGui::TreePop();
-        }
-
-        IMGUI_DEMO_MARKER("Widgets/Selectables/Grid");
-        if (ImGui::TreeNode("Grid"))
-        {
-            static char selected[4][4] = { { 1, 0, 0, 0 }, { 0, 1, 0, 0 }, { 0, 0, 1, 0 }, { 0, 0, 0, 1 } };
-
-            // Add in a bit of silly fun...
-            const float time = (float)ImGui::GetTime();
-            const bool winning_state = memchr(selected, 0, sizeof(selected)) == NULL; // If all cells are selected...
-            if (winning_state)
-                ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.5f + 0.5f * cosf(time * 2.0f), 0.5f + 0.5f * sinf(time * 3.0f)));
-
-            for (int y = 0; y < 4; y++)
-                for (int x = 0; x < 4; x++)
-                {
-                    if (x > 0)
-                        ImGui::SameLine();
-                    ImGui::PushID(y * 4 + x);
-                    if (ImGui::Selectable("Sailor", selected[y][x] != 0, 0, ImVec2(50, 50)))
-                    {
-                        // Toggle clicked cell + toggle neighbors
-                        selected[y][x] ^= 1;
-                        if (x > 0) { selected[y][x - 1] ^= 1; }
-                        if (x < 3) { selected[y][x + 1] ^= 1; }
-                        if (y > 0) { selected[y - 1][x] ^= 1; }
-                        if (y < 3) { selected[y + 1][x] ^= 1; }
-                    }
-                    ImGui::PopID();
-                }
-
-            if (winning_state)
-                ImGui::PopStyleVar();
-            ImGui::TreePop();
-        }
-        IMGUI_DEMO_MARKER("Widgets/Selectables/Alignment");
-        if (ImGui::TreeNode("Alignment"))
-        {
-            HelpMarker(
-                "By default, Selectables uses style.SelectableTextAlign but it can be overridden on a per-item "
-                "basis using PushStyleVar(). You'll probably want to always keep your default situation to "
-                "left-align otherwise it becomes difficult to layout multiple items on a same line");
-            static bool selected[3 * 3] = { true, false, true, false, true, false, true, false, true };
-            for (int y = 0; y < 3; y++)
-            {
-                for (int x = 0; x < 3; x++)
-                {
-                    ImVec2 alignment = ImVec2((float)x / 2.0f, (float)y / 2.0f);
-                    char name[32];
-                    sprintf(name, "(%.1f,%.1f)", alignment.x, alignment.y);
-                    if (x > 0) ImGui::SameLine();
-                    ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, alignment);
-                    ImGui::Selectable(name, &selected[3 * y + x], ImGuiSelectableFlags_None, ImVec2(80, 80));
-                    ImGui::PopStyleVar();
-                }
-            }
-            ImGui::TreePop();
-        }
-        ImGui::TreePop();
-    }
-
-    ShowDemoWindowMultiSelect(demo_data);
-
-    // To wire InputText() with std::string or any other custom string type,
-    // see the "Text Input > Resize Callback" section of this demo, and the misc/cpp/imgui_stdlib.h file.
-    IMGUI_DEMO_MARKER("Widgets/Text Input");
-    if (ImGui::TreeNode("Text Input"))
-    {
-        IMGUI_DEMO_MARKER("Widgets/Text Input/Multi-line Text Input");
-        if (ImGui::TreeNode("Multi-line Text Input"))
-        {
-            // Note: we are using a fixed-sized buffer for simplicity here. See ImGuiInputTextFlags_CallbackResize
-            // and the code in misc/cpp/imgui_stdlib.h for how to setup InputText() for dynamically resizing strings.
-            static char text[1024 * 16] =
-                "/*\n"
-                " The Pentium F00F bug, shorthand for F0 0F C7 C8,\n"
-                " the hexadecimal encoding of one offending instruction,\n"
-                " more formally, the invalid operand with locked CMPXCHG8B\n"
-                " instruction bug, is a design flaw in the majority of\n"
-                " Intel Pentium, Pentium MMX, and Pentium OverDrive\n"
-                " processors (all in the P5 microarchitecture).\n"
-                "*/\n\n"
-                "label:\n"
-                "\tlock cmpxchg8b eax\n";
-
-            static ImGuiInputTextFlags flags = ImGuiInputTextFlags_AllowTabInput;
-            HelpMarker("You can use the ImGuiInputTextFlags_CallbackResize facility if you need to wire InputTextMultiline() to a dynamic string type. See misc/cpp/imgui_stdlib.h for an example. (This is not demonstrated in imgui_demo.cpp because we don't want to include  in here)");
-            ImGui::CheckboxFlags("ImGuiInputTextFlags_ReadOnly", &flags, ImGuiInputTextFlags_ReadOnly);
-            ImGui::CheckboxFlags("ImGuiInputTextFlags_AllowTabInput", &flags, ImGuiInputTextFlags_AllowTabInput);
-            ImGui::SameLine(); HelpMarker("When _AllowTabInput is set, passing through the widget with Tabbing doesn't automatically activate it, in order to also cycling through subsequent widgets.");
-            ImGui::CheckboxFlags("ImGuiInputTextFlags_CtrlEnterForNewLine", &flags, ImGuiInputTextFlags_CtrlEnterForNewLine);
-            ImGui::InputTextMultiline("##source", text, IM_ARRAYSIZE(text), ImVec2(-FLT_MIN, ImGui::GetTextLineHeight() * 16), flags);
-            ImGui::TreePop();
-        }
-
-        IMGUI_DEMO_MARKER("Widgets/Text Input/Filtered Text Input");
-        if (ImGui::TreeNode("Filtered Text Input"))
-        {
-            struct TextFilters
-            {
-                // Modify character input by altering 'data->Eventchar' (ImGuiInputTextFlags_CallbackCharFilter callback)
-                static int FilterCasingSwap(ImGuiInputTextCallbackData* data)
-                {
-                    if (data->EventChar >= 'a' && data->EventChar <= 'z')       { data->EventChar -= 'a' - 'A'; } // Lowercase becomes uppercase
-                    else if (data->EventChar >= 'A' && data->EventChar <= 'Z')  { data->EventChar += 'a' - 'A'; } // Uppercase becomes lowercase
-                    return 0;
-                }
-
-                // Return 0 (pass) if the character is 'i' or 'm' or 'g' or 'u' or 'i', otherwise return 1 (filter out)
-                static int FilterImGuiLetters(ImGuiInputTextCallbackData* data)
-                {
-                    if (data->EventChar < 256 && strchr("imgui", (char)data->EventChar))
-                        return 0;
-                    return 1;
-                }
-            };
-
-            static char buf1[32] = ""; ImGui::InputText("default",     buf1, 32);
-            static char buf2[32] = ""; ImGui::InputText("decimal",     buf2, 32, ImGuiInputTextFlags_CharsDecimal);
-            static char buf3[32] = ""; ImGui::InputText("hexadecimal", buf3, 32, ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase);
-            static char buf4[32] = ""; ImGui::InputText("uppercase",   buf4, 32, ImGuiInputTextFlags_CharsUppercase);
-            static char buf5[32] = ""; ImGui::InputText("no blank",    buf5, 32, ImGuiInputTextFlags_CharsNoBlank);
-            static char buf6[32] = ""; ImGui::InputText("casing swap", buf6, 32, ImGuiInputTextFlags_CallbackCharFilter, TextFilters::FilterCasingSwap); // Use CharFilter callback to replace characters.
-            static char buf7[32] = ""; ImGui::InputText("\"imgui\"",   buf7, 32, ImGuiInputTextFlags_CallbackCharFilter, TextFilters::FilterImGuiLetters); // Use CharFilter callback to disable some characters.
-            ImGui::TreePop();
-        }
-
-        IMGUI_DEMO_MARKER("Widgets/Text Input/Password input");
-        if (ImGui::TreeNode("Password Input"))
-        {
-            static char password[64] = "password123";
-            ImGui::InputText("password", password, IM_ARRAYSIZE(password), ImGuiInputTextFlags_Password);
-            ImGui::SameLine(); HelpMarker("Display all characters as '*'.\nDisable clipboard cut and copy.\nDisable logging.\n");
-            ImGui::InputTextWithHint("password (w/ hint)", "", password, IM_ARRAYSIZE(password), ImGuiInputTextFlags_Password);
-            ImGui::InputText("password (clear)", password, IM_ARRAYSIZE(password));
-            ImGui::TreePop();
-        }
-
-        IMGUI_DEMO_MARKER("Widgets/Text Input/Completion, History, Edit Callbacks");
-        if (ImGui::TreeNode("Completion, History, Edit Callbacks"))
-        {
-            struct Funcs
-            {
-                static int MyCallback(ImGuiInputTextCallbackData* data)
-                {
-                    if (data->EventFlag == ImGuiInputTextFlags_CallbackCompletion)
-                    {
-                        data->InsertChars(data->CursorPos, "..");
-                    }
-                    else if (data->EventFlag == ImGuiInputTextFlags_CallbackHistory)
-                    {
-                        if (data->EventKey == ImGuiKey_UpArrow)
-                        {
-                            data->DeleteChars(0, data->BufTextLen);
-                            data->InsertChars(0, "Pressed Up!");
-                            data->SelectAll();
-                        }
-                        else if (data->EventKey == ImGuiKey_DownArrow)
-                        {
-                            data->DeleteChars(0, data->BufTextLen);
-                            data->InsertChars(0, "Pressed Down!");
-                            data->SelectAll();
-                        }
-                    }
-                    else if (data->EventFlag == ImGuiInputTextFlags_CallbackEdit)
-                    {
-                        // Toggle casing of first character
-                        char c = data->Buf[0];
-                        if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) data->Buf[0] ^= 32;
-                        data->BufDirty = true;
-
-                        // Increment a counter
-                        int* p_int = (int*)data->UserData;
-                        *p_int = *p_int + 1;
-                    }
-                    return 0;
-                }
-            };
-            static char buf1[64];
-            ImGui::InputText("Completion", buf1, 64, ImGuiInputTextFlags_CallbackCompletion, Funcs::MyCallback);
-            ImGui::SameLine(); HelpMarker(
-                "Here we append \"..\" each time Tab is pressed. "
-                "See 'Examples>Console' for a more meaningful demonstration of using this callback.");
-
-            static char buf2[64];
-            ImGui::InputText("History", buf2, 64, ImGuiInputTextFlags_CallbackHistory, Funcs::MyCallback);
-            ImGui::SameLine(); HelpMarker(
-                "Here we replace and select text each time Up/Down are pressed. "
-                "See 'Examples>Console' for a more meaningful demonstration of using this callback.");
-
-            static char buf3[64];
-            static int edit_count = 0;
-            ImGui::InputText("Edit", buf3, 64, ImGuiInputTextFlags_CallbackEdit, Funcs::MyCallback, (void*)&edit_count);
-            ImGui::SameLine(); HelpMarker(
-                "Here we toggle the casing of the first character on every edit + count edits.");
-            ImGui::SameLine(); ImGui::Text("(%d)", edit_count);
-
-            ImGui::TreePop();
-        }
-
-        IMGUI_DEMO_MARKER("Widgets/Text Input/Resize Callback");
-        if (ImGui::TreeNode("Resize Callback"))
-        {
-            // To wire InputText() with std::string or any other custom string type,
-            // you can use the ImGuiInputTextFlags_CallbackResize flag + create a custom ImGui::InputText() wrapper
-            // using your preferred type. See misc/cpp/imgui_stdlib.h for an implementation of this using std::string.
-            HelpMarker(
-                "Using ImGuiInputTextFlags_CallbackResize to wire your custom string type to InputText().\n\n"
-                "See misc/cpp/imgui_stdlib.h for an implementation of this for std::string.");
-            struct Funcs
-            {
-                static int MyResizeCallback(ImGuiInputTextCallbackData* data)
-                {
-                    if (data->EventFlag == ImGuiInputTextFlags_CallbackResize)
-                    {
-                        ImVector* my_str = (ImVector*)data->UserData;
-                        IM_ASSERT(my_str->begin() == data->Buf);
-                        my_str->resize(data->BufSize); // NB: On resizing calls, generally data->BufSize == data->BufTextLen + 1
-                        data->Buf = my_str->begin();
-                    }
-                    return 0;
-                }
-
-                // Note: Because ImGui:: is a namespace you would typically add your own function into the namespace.
-                // For example, you code may declare a function 'ImGui::InputText(const char* label, MyString* my_str)'
-                static bool MyInputTextMultiline(const char* label, ImVector* my_str, const ImVec2& size = ImVec2(0, 0), ImGuiInputTextFlags flags = 0)
-                {
-                    IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0);
-                    return ImGui::InputTextMultiline(label, my_str->begin(), (size_t)my_str->size(), size, flags | ImGuiInputTextFlags_CallbackResize, Funcs::MyResizeCallback, (void*)my_str);
-                }
-            };
-
-            // For this demo we are using ImVector as a string container.
-            // Note that because we need to store a terminating zero character, our size/capacity are 1 more
-            // than usually reported by a typical string class.
-            static ImVector my_str;
-            if (my_str.empty())
-                my_str.push_back(0);
-            Funcs::MyInputTextMultiline("##MyStr", &my_str, ImVec2(-FLT_MIN, ImGui::GetTextLineHeight() * 16));
-            ImGui::Text("Data: %p\nSize: %d\nCapacity: %d", (void*)my_str.begin(), my_str.size(), my_str.capacity());
-            ImGui::TreePop();
-        }
-
-        IMGUI_DEMO_MARKER("Widgets/Text Input/Eliding, Alignment");
-        if (ImGui::TreeNode("Eliding, Alignment"))
-        {
-            static char buf1[128] = "/path/to/some/folder/with/long/filename.cpp";
-            static ImGuiInputTextFlags flags = ImGuiInputTextFlags_ElideLeft;
-            ImGui::CheckboxFlags("ImGuiInputTextFlags_ElideLeft", &flags, ImGuiInputTextFlags_ElideLeft);
-            ImGui::InputText("Path", buf1, IM_ARRAYSIZE(buf1), flags);
-            ImGui::TreePop();
-        }
-
-        IMGUI_DEMO_MARKER("Widgets/Text Input/Miscellaneous");
-        if (ImGui::TreeNode("Miscellaneous"))
-        {
-            static char buf1[16];
-            static ImGuiInputTextFlags flags = ImGuiInputTextFlags_EscapeClearsAll;
-            ImGui::CheckboxFlags("ImGuiInputTextFlags_EscapeClearsAll", &flags, ImGuiInputTextFlags_EscapeClearsAll);
-            ImGui::CheckboxFlags("ImGuiInputTextFlags_ReadOnly", &flags, ImGuiInputTextFlags_ReadOnly);
-            ImGui::CheckboxFlags("ImGuiInputTextFlags_NoUndoRedo", &flags, ImGuiInputTextFlags_NoUndoRedo);
-            ImGui::InputText("Hello", buf1, IM_ARRAYSIZE(buf1), flags);
-            ImGui::TreePop();
-        }
-
-        ImGui::TreePop();
-    }
-
-    // Tabs
-    IMGUI_DEMO_MARKER("Widgets/Tabs");
-    if (ImGui::TreeNode("Tabs"))
-    {
-        IMGUI_DEMO_MARKER("Widgets/Tabs/Basic");
-        if (ImGui::TreeNode("Basic"))
-        {
-            ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_None;
-            if (ImGui::BeginTabBar("MyTabBar", tab_bar_flags))
-            {
-                if (ImGui::BeginTabItem("Avocado"))
-                {
-                    ImGui::Text("This is the Avocado tab!\nblah blah blah blah blah");
-                    ImGui::EndTabItem();
-                }
-                if (ImGui::BeginTabItem("Broccoli"))
-                {
-                    ImGui::Text("This is the Broccoli tab!\nblah blah blah blah blah");
-                    ImGui::EndTabItem();
-                }
-                if (ImGui::BeginTabItem("Cucumber"))
-                {
-                    ImGui::Text("This is the Cucumber tab!\nblah blah blah blah blah");
-                    ImGui::EndTabItem();
-                }
-                ImGui::EndTabBar();
-            }
-            ImGui::Separator();
-            ImGui::TreePop();
-        }
-
-        IMGUI_DEMO_MARKER("Widgets/Tabs/Advanced & Close Button");
-        if (ImGui::TreeNode("Advanced & Close Button"))
-        {
-            // Expose a couple of the available flags. In most cases you may just call BeginTabBar() with no flags (0).
-            static ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_Reorderable;
-            ImGui::CheckboxFlags("ImGuiTabBarFlags_Reorderable", &tab_bar_flags, ImGuiTabBarFlags_Reorderable);
-            ImGui::CheckboxFlags("ImGuiTabBarFlags_AutoSelectNewTabs", &tab_bar_flags, ImGuiTabBarFlags_AutoSelectNewTabs);
-            ImGui::CheckboxFlags("ImGuiTabBarFlags_TabListPopupButton", &tab_bar_flags, ImGuiTabBarFlags_TabListPopupButton);
-            ImGui::CheckboxFlags("ImGuiTabBarFlags_NoCloseWithMiddleMouseButton", &tab_bar_flags, ImGuiTabBarFlags_NoCloseWithMiddleMouseButton);
-            ImGui::CheckboxFlags("ImGuiTabBarFlags_DrawSelectedOverline", &tab_bar_flags, ImGuiTabBarFlags_DrawSelectedOverline);
-            if ((tab_bar_flags & ImGuiTabBarFlags_FittingPolicyMask_) == 0)
-                tab_bar_flags |= ImGuiTabBarFlags_FittingPolicyDefault_;
-            if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyResizeDown", &tab_bar_flags, ImGuiTabBarFlags_FittingPolicyResizeDown))
-                tab_bar_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyResizeDown);
-            if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyScroll", &tab_bar_flags, ImGuiTabBarFlags_FittingPolicyScroll))
-                tab_bar_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyScroll);
-
-            // Tab Bar
-            ImGui::AlignTextToFramePadding();
-            ImGui::Text("Opened:");
-            const char* names[4] = { "Artichoke", "Beetroot", "Celery", "Daikon" };
-            static bool opened[4] = { true, true, true, true }; // Persistent user state
-            for (int n = 0; n < IM_ARRAYSIZE(opened); n++)
-            {
-                ImGui::SameLine();
-                ImGui::Checkbox(names[n], &opened[n]);
-            }
-
-            // Passing a bool* to BeginTabItem() is similar to passing one to Begin():
-            // the underlying bool will be set to false when the tab is closed.
-            if (ImGui::BeginTabBar("MyTabBar", tab_bar_flags))
-            {
-                for (int n = 0; n < IM_ARRAYSIZE(opened); n++)
-                    if (opened[n] && ImGui::BeginTabItem(names[n], &opened[n], ImGuiTabItemFlags_None))
-                    {
-                        ImGui::Text("This is the %s tab!", names[n]);
-                        if (n & 1)
-                            ImGui::Text("I am an odd tab.");
-                        ImGui::EndTabItem();
-                    }
-                ImGui::EndTabBar();
-            }
-            ImGui::Separator();
-            ImGui::TreePop();
-        }
-
-        IMGUI_DEMO_MARKER("Widgets/Tabs/TabItemButton & Leading-Trailing flags");
-        if (ImGui::TreeNode("TabItemButton & Leading/Trailing flags"))
-        {
-            static ImVector active_tabs;
-            static int next_tab_id = 0;
-            if (next_tab_id == 0) // Initialize with some default tabs
-                for (int i = 0; i < 3; i++)
-                    active_tabs.push_back(next_tab_id++);
-
-            // TabItemButton() and Leading/Trailing flags are distinct features which we will demo together.
-            // (It is possible to submit regular tabs with Leading/Trailing flags, or TabItemButton tabs without Leading/Trailing flags...
-            // but they tend to make more sense together)
-            static bool show_leading_button = true;
-            static bool show_trailing_button = true;
-            ImGui::Checkbox("Show Leading TabItemButton()", &show_leading_button);
-            ImGui::Checkbox("Show Trailing TabItemButton()", &show_trailing_button);
-
-            // Expose some other flags which are useful to showcase how they interact with Leading/Trailing tabs
-            static ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_FittingPolicyResizeDown;
-            ImGui::CheckboxFlags("ImGuiTabBarFlags_TabListPopupButton", &tab_bar_flags, ImGuiTabBarFlags_TabListPopupButton);
-            if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyResizeDown", &tab_bar_flags, ImGuiTabBarFlags_FittingPolicyResizeDown))
-                tab_bar_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyResizeDown);
-            if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyScroll", &tab_bar_flags, ImGuiTabBarFlags_FittingPolicyScroll))
-                tab_bar_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyScroll);
-
-            if (ImGui::BeginTabBar("MyTabBar", tab_bar_flags))
-            {
-                // Demo a Leading TabItemButton(): click the "?" button to open a menu
-                if (show_leading_button)
-                    if (ImGui::TabItemButton("?", ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_NoTooltip))
-                        ImGui::OpenPopup("MyHelpMenu");
-                if (ImGui::BeginPopup("MyHelpMenu"))
-                {
-                    ImGui::Selectable("Hello!");
-                    ImGui::EndPopup();
-                }
-
-                // Demo Trailing Tabs: click the "+" button to add a new tab.
-                // (In your app you may want to use a font icon instead of the "+")
-                // We submit it before the regular tabs, but thanks to the ImGuiTabItemFlags_Trailing flag it will always appear at the end.
-                if (show_trailing_button)
-                    if (ImGui::TabItemButton("+", ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_NoTooltip))
-                        active_tabs.push_back(next_tab_id++); // Add new tab
-
-                // Submit our regular tabs
-                for (int n = 0; n < active_tabs.Size; )
-                {
-                    bool open = true;
-                    char name[16];
-                    snprintf(name, IM_ARRAYSIZE(name), "%04d", active_tabs[n]);
-                    if (ImGui::BeginTabItem(name, &open, ImGuiTabItemFlags_None))
-                    {
-                        ImGui::Text("This is the %s tab!", name);
-                        ImGui::EndTabItem();
-                    }
-
-                    if (!open)
-                        active_tabs.erase(active_tabs.Data + n);
-                    else
-                        n++;
-                }
-
-                ImGui::EndTabBar();
-            }
-            ImGui::Separator();
-            ImGui::TreePop();
-        }
-        ImGui::TreePop();
-    }
-
-    // Plot/Graph widgets are not very good.
-    // Consider using a third-party library such as ImPlot: https://github.com/epezent/implot
-    // (see others https://github.com/ocornut/imgui/wiki/Useful-Extensions)
-    IMGUI_DEMO_MARKER("Widgets/Plotting");
-    if (ImGui::TreeNode("Plotting"))
-    {
-        static bool animate = true;
-        ImGui::Checkbox("Animate", &animate);
-
-        // Plot as lines and plot as histogram
-        static float arr[] = { 0.6f, 0.1f, 1.0f, 0.5f, 0.92f, 0.1f, 0.2f };
-        ImGui::PlotLines("Frame Times", arr, IM_ARRAYSIZE(arr));
-        ImGui::PlotHistogram("Histogram", arr, IM_ARRAYSIZE(arr), 0, NULL, 0.0f, 1.0f, ImVec2(0, 80.0f));
-        //ImGui::SameLine(); HelpMarker("Consider using ImPlot instead!");
-
-        // Fill an array of contiguous float values to plot
-        // Tip: If your float aren't contiguous but part of a structure, you can pass a pointer to your first float
-        // and the sizeof() of your structure in the "stride" parameter.
-        static float values[90] = {};
-        static int values_offset = 0;
-        static double refresh_time = 0.0;
-        if (!animate || refresh_time == 0.0)
-            refresh_time = ImGui::GetTime();
-        while (refresh_time < ImGui::GetTime()) // Create data at fixed 60 Hz rate for the demo
-        {
-            static float phase = 0.0f;
-            values[values_offset] = cosf(phase);
-            values_offset = (values_offset + 1) % IM_ARRAYSIZE(values);
-            phase += 0.10f * values_offset;
-            refresh_time += 1.0f / 60.0f;
-        }
-
-        // Plots can display overlay texts
-        // (in this example, we will display an average value)
-        {
-            float average = 0.0f;
-            for (int n = 0; n < IM_ARRAYSIZE(values); n++)
-                average += values[n];
-            average /= (float)IM_ARRAYSIZE(values);
-            char overlay[32];
-            sprintf(overlay, "avg %f", average);
-            ImGui::PlotLines("Lines", values, IM_ARRAYSIZE(values), values_offset, overlay, -1.0f, 1.0f, ImVec2(0, 80.0f));
-        }
-
-        // Use functions to generate output
-        // FIXME: This is actually VERY awkward because current plot API only pass in indices.
-        // We probably want an API passing floats and user provide sample rate/count.
-        struct Funcs
-        {
-            static float Sin(void*, int i) { return sinf(i * 0.1f); }
-            static float Saw(void*, int i) { return (i & 1) ? 1.0f : -1.0f; }
-        };
-        static int func_type = 0, display_count = 70;
-        ImGui::SeparatorText("Functions");
-        ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);
-        ImGui::Combo("func", &func_type, "Sin\0Saw\0");
-        ImGui::SameLine();
-        ImGui::SliderInt("Sample count", &display_count, 1, 400);
-        float (*func)(void*, int) = (func_type == 0) ? Funcs::Sin : Funcs::Saw;
-        ImGui::PlotLines("Lines##2", func, NULL, display_count, 0, NULL, -1.0f, 1.0f, ImVec2(0, 80));
-        ImGui::PlotHistogram("Histogram##2", func, NULL, display_count, 0, NULL, -1.0f, 1.0f, ImVec2(0, 80));
-        ImGui::Separator();
-
-        ImGui::Text("Need better plotting and graphing? Consider using ImPlot:");
-        ImGui::TextLinkOpenURL("https://github.com/epezent/implot");
-        ImGui::Separator();
-
-        ImGui::TreePop();
-    }
-
-    IMGUI_DEMO_MARKER("Widgets/Progress Bars");
-    if (ImGui::TreeNode("Progress Bars"))
-    {
-        // Animate a simple progress bar
-        static float progress = 0.0f, progress_dir = 1.0f;
-        progress += progress_dir * 0.4f * ImGui::GetIO().DeltaTime;
-        if (progress >= +1.1f) { progress = +1.1f; progress_dir *= -1.0f; }
-        if (progress <= -0.1f) { progress = -0.1f; progress_dir *= -1.0f; }
-
-        // Typically we would use ImVec2(-1.0f,0.0f) or ImVec2(-FLT_MIN,0.0f) to use all available width,
-        // or ImVec2(width,0.0f) for a specified width. ImVec2(0.0f,0.0f) uses ItemWidth.
-        ImGui::ProgressBar(progress, ImVec2(0.0f, 0.0f));
-        ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
-        ImGui::Text("Progress Bar");
-
-        float progress_saturated = IM_CLAMP(progress, 0.0f, 1.0f);
-        char buf[32];
-        sprintf(buf, "%d/%d", (int)(progress_saturated * 1753), 1753);
-        ImGui::ProgressBar(progress, ImVec2(0.f, 0.f), buf);
-
-        // Pass an animated negative value, e.g. -1.0f * (float)ImGui::GetTime() is the recommended value.
-        // Adjust the factor if you want to adjust the animation speed.
-        ImGui::ProgressBar(-1.0f * (float)ImGui::GetTime(), ImVec2(0.0f, 0.0f), "Searching..");
-        ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
-        ImGui::Text("Indeterminate");
-
-        ImGui::TreePop();
-    }
+//-----------------------------------------------------------------------------
+// [SECTION] DemoWindowWidgetsColorAndPickers()
+//-----------------------------------------------------------------------------
 
+static void DemoWindowWidgetsColorAndPickers()
+{
     IMGUI_DEMO_MARKER("Widgets/Color");
     if (ImGui::TreeNode("Color/Picker Widgets"))
     {
@@ -2271,8 +1227,8 @@ static void ShowDemoWindowWidgets(ImGuiDemoWindowData* demo_data)
         ImGui::Text("Set defaults in code:");
         ImGui::SameLine(); HelpMarker(
             "SetColorEditOptions() is designed to allow you to set boot-time default.\n"
-            "We don't have Push/Pop functions because you can force options on a per-widget basis if needed,"
-            "and the user can change non-forced ones with the options menu.\nWe don't have a getter to avoid"
+            "We don't have Push/Pop functions because you can force options on a per-widget basis if needed, "
+            "and the user can change non-forced ones with the options menu.\nWe don't have a getter to avoid "
             "encouraging you to persistently save values that aren't forward-compatible.");
         if (ImGui::Button("Default: Uint8 + HSV + Hue Bar"))
             ImGui::SetColorEditOptions(ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_DisplayHSV | ImGuiColorEditFlags_PickerHueBar);
@@ -2295,8 +1251,8 @@ static void ShowDemoWindowWidgets(ImGuiDemoWindowData* demo_data)
         ImGui::Spacing();
         ImGui::Text("HSV encoded colors");
         ImGui::SameLine(); HelpMarker(
-            "By default, colors are given to ColorEdit and ColorPicker in RGB, but ImGuiColorEditFlags_InputHSV"
-            "allows you to store colors as HSV and pass them to ColorEdit and ColorPicker as HSV. This comes with the"
+            "By default, colors are given to ColorEdit and ColorPicker in RGB, but ImGuiColorEditFlags_InputHSV "
+            "allows you to store colors as HSV and pass them to ColorEdit and ColorPicker as HSV. This comes with the "
             "added benefit that you can manipulate hue values with the picker even when saturation or value are zero.");
         ImGui::Text("Color widget with InputHSV:");
         ImGui::ColorEdit4("HSV shown as RGB##1", (float*)&color_hsv, ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_InputHSV | ImGuiColorEditFlags_Float);
@@ -2305,62 +1261,111 @@ static void ShowDemoWindowWidgets(ImGuiDemoWindowData* demo_data)
 
         ImGui::TreePop();
     }
+}
 
-    IMGUI_DEMO_MARKER("Widgets/Drag and Slider Flags");
-    if (ImGui::TreeNode("Drag/Slider Flags"))
+//-----------------------------------------------------------------------------
+// [SECTION] DemoWindowWidgetsComboBoxes()
+//-----------------------------------------------------------------------------
+
+static void DemoWindowWidgetsComboBoxes()
+{
+    IMGUI_DEMO_MARKER("Widgets/Combo");
+    if (ImGui::TreeNode("Combo"))
     {
-        // Demonstrate using advanced flags for DragXXX and SliderXXX functions. Note that the flags are the same!
-        static ImGuiSliderFlags flags = ImGuiSliderFlags_None;
-        ImGui::CheckboxFlags("ImGuiSliderFlags_AlwaysClamp", &flags, ImGuiSliderFlags_AlwaysClamp);
-        ImGui::CheckboxFlags("ImGuiSliderFlags_ClampOnInput", &flags, ImGuiSliderFlags_ClampOnInput);
-        ImGui::SameLine(); HelpMarker("Clamp value to min/max bounds when input manually with CTRL+Click. By default CTRL+Click allows going out of bounds.");
-        ImGui::CheckboxFlags("ImGuiSliderFlags_ClampZeroRange", &flags, ImGuiSliderFlags_ClampZeroRange);
-        ImGui::SameLine(); HelpMarker("Clamp even if min==max==0.0f. Otherwise DragXXX functions don't clamp.");
-        ImGui::CheckboxFlags("ImGuiSliderFlags_Logarithmic", &flags, ImGuiSliderFlags_Logarithmic);
-        ImGui::SameLine(); HelpMarker("Enable logarithmic editing (more precision for small values).");
-        ImGui::CheckboxFlags("ImGuiSliderFlags_NoRoundToFormat", &flags, ImGuiSliderFlags_NoRoundToFormat);
-        ImGui::SameLine(); HelpMarker("Disable rounding underlying value to match precision of the format string (e.g. %.3f values are rounded to those 3 digits).");
-        ImGui::CheckboxFlags("ImGuiSliderFlags_NoInput", &flags, ImGuiSliderFlags_NoInput);
-        ImGui::SameLine(); HelpMarker("Disable CTRL+Click or Enter key allowing to input text directly into the widget.");
-        ImGui::CheckboxFlags("ImGuiSliderFlags_NoSpeedTweaks", &flags, ImGuiSliderFlags_NoSpeedTweaks);
-        ImGui::SameLine(); HelpMarker("Disable keyboard modifiers altering tweak speed. Useful if you want to alter tweak speed yourself based on your own logic.");
-        ImGui::CheckboxFlags("ImGuiSliderFlags_WrapAround", &flags, ImGuiSliderFlags_WrapAround);
-        ImGui::SameLine(); HelpMarker("Enable wrapping around from max to min and from min to max (only supported by DragXXX() functions)");
+        // Combo Boxes are also called "Dropdown" in other systems
+        // Expose flags as checkbox for the demo
+        static ImGuiComboFlags flags = 0;
+        ImGui::CheckboxFlags("ImGuiComboFlags_PopupAlignLeft", &flags, ImGuiComboFlags_PopupAlignLeft);
+        ImGui::SameLine(); HelpMarker("Only makes a difference if the popup is larger than the combo");
+        if (ImGui::CheckboxFlags("ImGuiComboFlags_NoArrowButton", &flags, ImGuiComboFlags_NoArrowButton))
+            flags &= ~ImGuiComboFlags_NoPreview;     // Clear incompatible flags
+        if (ImGui::CheckboxFlags("ImGuiComboFlags_NoPreview", &flags, ImGuiComboFlags_NoPreview))
+            flags &= ~(ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_WidthFitPreview); // Clear incompatible flags
+        if (ImGui::CheckboxFlags("ImGuiComboFlags_WidthFitPreview", &flags, ImGuiComboFlags_WidthFitPreview))
+            flags &= ~ImGuiComboFlags_NoPreview;
 
-        // Drags
-        static float drag_f = 0.5f;
-        static int drag_i = 50;
-        ImGui::Text("Underlying float value: %f", drag_f);
-        ImGui::DragFloat("DragFloat (0 -> 1)", &drag_f, 0.005f, 0.0f, 1.0f, "%.3f", flags);
-        ImGui::DragFloat("DragFloat (0 -> +inf)", &drag_f, 0.005f, 0.0f, FLT_MAX, "%.3f", flags);
-        ImGui::DragFloat("DragFloat (-inf -> 1)", &drag_f, 0.005f, -FLT_MAX, 1.0f, "%.3f", flags);
-        ImGui::DragFloat("DragFloat (-inf -> +inf)", &drag_f, 0.005f, -FLT_MAX, +FLT_MAX, "%.3f", flags);
-        //ImGui::DragFloat("DragFloat (0 -> 0)", &drag_f, 0.005f, 0.0f, 0.0f, "%.3f", flags);           // To test ClampZeroRange
-        //ImGui::DragFloat("DragFloat (100 -> 100)", &drag_f, 0.005f, 100.0f, 100.0f, "%.3f", flags);
-        ImGui::DragInt("DragInt (0 -> 100)", &drag_i, 0.5f, 0, 100, "%d", flags);
+        // Override default popup height
+        if (ImGui::CheckboxFlags("ImGuiComboFlags_HeightSmall", &flags, ImGuiComboFlags_HeightSmall))
+            flags &= ~(ImGuiComboFlags_HeightMask_ & ~ImGuiComboFlags_HeightSmall);
+        if (ImGui::CheckboxFlags("ImGuiComboFlags_HeightRegular", &flags, ImGuiComboFlags_HeightRegular))
+            flags &= ~(ImGuiComboFlags_HeightMask_ & ~ImGuiComboFlags_HeightRegular);
+        if (ImGui::CheckboxFlags("ImGuiComboFlags_HeightLargest", &flags, ImGuiComboFlags_HeightLargest))
+            flags &= ~(ImGuiComboFlags_HeightMask_ & ~ImGuiComboFlags_HeightLargest);
 
-        // Sliders
-        static float slider_f = 0.5f;
-        static int slider_i = 50;
-        const ImGuiSliderFlags flags_for_sliders = flags & ~ImGuiSliderFlags_WrapAround;
-        ImGui::Text("Underlying float value: %f", slider_f);
-        ImGui::SliderFloat("SliderFloat (0 -> 1)", &slider_f, 0.0f, 1.0f, "%.3f", flags_for_sliders);
-        ImGui::SliderInt("SliderInt (0 -> 100)", &slider_i, 0, 100, "%d", flags_for_sliders);
+        // Using the generic BeginCombo() API, you have full control over how to display the combo contents.
+        // (your selection data could be an index, a pointer to the object, an id for the object, a flag intrusively
+        // stored in the object itself, etc.)
+        const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIII", "JJJJ", "KKKK", "LLLLLLL", "MMMM", "OOOOOOO" };
+        static int item_selected_idx = 0; // Here we store our selection data as an index.
+
+        // Pass in the preview value visible before opening the combo (it could technically be different contents or not pulled from items[])
+        const char* combo_preview_value = items[item_selected_idx];
+        if (ImGui::BeginCombo("combo 1", combo_preview_value, flags))
+        {
+            for (int n = 0; n < IM_ARRAYSIZE(items); n++)
+            {
+                const bool is_selected = (item_selected_idx == n);
+                if (ImGui::Selectable(items[n], is_selected))
+                    item_selected_idx = n;
+
+                // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
+                if (is_selected)
+                    ImGui::SetItemDefaultFocus();
+            }
+            ImGui::EndCombo();
+        }
+
+        // Show case embedding a filter using a simple trick: displaying the filter inside combo contents.
+        // See https://github.com/ocornut/imgui/issues/718 for advanced/esoteric alternatives.
+        if (ImGui::BeginCombo("combo 2 (w/ filter)", combo_preview_value, flags))
+        {
+            static ImGuiTextFilter filter;
+            if (ImGui::IsWindowAppearing())
+            {
+                ImGui::SetKeyboardFocusHere();
+                filter.Clear();
+            }
+            ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_F);
+            filter.Draw("##Filter", -FLT_MIN);
+
+            for (int n = 0; n < IM_ARRAYSIZE(items); n++)
+            {
+                const bool is_selected = (item_selected_idx == n);
+                if (filter.PassFilter(items[n]))
+                    if (ImGui::Selectable(items[n], is_selected))
+                        item_selected_idx = n;
+            }
+            ImGui::EndCombo();
+        }
+
+        ImGui::Spacing();
+        ImGui::SeparatorText("One-liner variants");
+        HelpMarker("The Combo() function is not greatly useful apart from cases were you want to embed all options in a single strings.\nFlags above don't apply to this section.");
+
+        // Simplified one-liner Combo() API, using values packed in a single constant string
+        // This is a convenience for when the selection set is small and known at compile-time.
+        static int item_current_2 = 0;
+        ImGui::Combo("combo 3 (one-liner)", &item_current_2, "aaaa\0bbbb\0cccc\0dddd\0eeee\0\0");
+
+        // Simplified one-liner Combo() using an array of const char*
+        // This is not very useful (may obsolete): prefer using BeginCombo()/EndCombo() for full control.
+        static int item_current_3 = -1; // If the selection isn't within 0..count, Combo won't display a preview
+        ImGui::Combo("combo 4 (array)", &item_current_3, items, IM_ARRAYSIZE(items));
+
+        // Simplified one-liner Combo() using an accessor function
+        static int item_current_4 = 0;
+        ImGui::Combo("combo 5 (function)", &item_current_4, [](void* data, int n) { return ((const char**)data)[n]; }, items, IM_ARRAYSIZE(items));
 
         ImGui::TreePop();
     }
+}
 
-    IMGUI_DEMO_MARKER("Widgets/Range Widgets");
-    if (ImGui::TreeNode("Range Widgets"))
-    {
-        static float begin = 10, end = 90;
-        static int begin_i = 100, end_i = 1000;
-        ImGui::DragFloatRange2("range float", &begin, &end, 0.25f, 0.0f, 100.0f, "Min: %.1f %%", "Max: %.1f %%", ImGuiSliderFlags_AlwaysClamp);
-        ImGui::DragIntRange2("range int", &begin_i, &end_i, 5, 0, 1000, "Min: %d units", "Max: %d units");
-        ImGui::DragIntRange2("range int (no bounds)", &begin_i, &end_i, 5, 0, 0, "Min: %d units", "Max: %d units");
-        ImGui::TreePop();
-    }
+//-----------------------------------------------------------------------------
+// [SECTION] DemoWindowWidgetsDataTypes()
+//-----------------------------------------------------------------------------
 
+static void DemoWindowWidgetsDataTypes()
+{
     IMGUI_DEMO_MARKER("Widgets/Data Types");
     if (ImGui::TreeNode("Data Types"))
     {
@@ -2488,105 +1493,29 @@ static void ShowDemoWindowWidgets(ImGuiDemoWindowData* demo_data)
 
         ImGui::TreePop();
     }
+}
 
-    IMGUI_DEMO_MARKER("Widgets/Multi-component Widgets");
-    if (ImGui::TreeNode("Multi-component Widgets"))
+//-----------------------------------------------------------------------------
+// [SECTION] DemoWindowWidgetsDisableBlocks()
+//-----------------------------------------------------------------------------
+
+static void DemoWindowWidgetsDisableBlocks(ImGuiDemoWindowData* demo_data)
+{
+    IMGUI_DEMO_MARKER("Widgets/Disable Blocks");
+    if (ImGui::TreeNode("Disable Blocks"))
     {
-        static float vec4f[4] = { 0.10f, 0.20f, 0.30f, 0.44f };
-        static int vec4i[4] = { 1, 5, 100, 255 };
-
-        ImGui::SeparatorText("2-wide");
-        ImGui::InputFloat2("input float2", vec4f);
-        ImGui::DragFloat2("drag float2", vec4f, 0.01f, 0.0f, 1.0f);
-        ImGui::SliderFloat2("slider float2", vec4f, 0.0f, 1.0f);
-        ImGui::InputInt2("input int2", vec4i);
-        ImGui::DragInt2("drag int2", vec4i, 1, 0, 255);
-        ImGui::SliderInt2("slider int2", vec4i, 0, 255);
-
-        ImGui::SeparatorText("3-wide");
-        ImGui::InputFloat3("input float3", vec4f);
-        ImGui::DragFloat3("drag float3", vec4f, 0.01f, 0.0f, 1.0f);
-        ImGui::SliderFloat3("slider float3", vec4f, 0.0f, 1.0f);
-        ImGui::InputInt3("input int3", vec4i);
-        ImGui::DragInt3("drag int3", vec4i, 1, 0, 255);
-        ImGui::SliderInt3("slider int3", vec4i, 0, 255);
-
-        ImGui::SeparatorText("4-wide");
-        ImGui::InputFloat4("input float4", vec4f);
-        ImGui::DragFloat4("drag float4", vec4f, 0.01f, 0.0f, 1.0f);
-        ImGui::SliderFloat4("slider float4", vec4f, 0.0f, 1.0f);
-        ImGui::InputInt4("input int4", vec4i);
-        ImGui::DragInt4("drag int4", vec4i, 1, 0, 255);
-        ImGui::SliderInt4("slider int4", vec4i, 0, 255);
-
+        ImGui::Checkbox("Disable entire section above", &demo_data->DisableSections);
+        ImGui::SameLine(); HelpMarker("Demonstrate using BeginDisabled()/EndDisabled() across other sections.");
         ImGui::TreePop();
     }
+}
 
-    IMGUI_DEMO_MARKER("Widgets/Vertical Sliders");
-    if (ImGui::TreeNode("Vertical Sliders"))
-    {
-        const float spacing = 4;
-        ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(spacing, spacing));
-
-        static int int_value = 0;
-        ImGui::VSliderInt("##int", ImVec2(18, 160), &int_value, 0, 5);
-        ImGui::SameLine();
-
-        static float values[7] = { 0.0f, 0.60f, 0.35f, 0.9f, 0.70f, 0.20f, 0.0f };
-        ImGui::PushID("set1");
-        for (int i = 0; i < 7; i++)
-        {
-            if (i > 0) ImGui::SameLine();
-            ImGui::PushID(i);
-            ImGui::PushStyleColor(ImGuiCol_FrameBg, (ImVec4)ImColor::HSV(i / 7.0f, 0.5f, 0.5f));
-            ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, (ImVec4)ImColor::HSV(i / 7.0f, 0.6f, 0.5f));
-            ImGui::PushStyleColor(ImGuiCol_FrameBgActive, (ImVec4)ImColor::HSV(i / 7.0f, 0.7f, 0.5f));
-            ImGui::PushStyleColor(ImGuiCol_SliderGrab, (ImVec4)ImColor::HSV(i / 7.0f, 0.9f, 0.9f));
-            ImGui::VSliderFloat("##v", ImVec2(18, 160), &values[i], 0.0f, 1.0f, "");
-            if (ImGui::IsItemActive() || ImGui::IsItemHovered())
-                ImGui::SetTooltip("%.3f", values[i]);
-            ImGui::PopStyleColor(4);
-            ImGui::PopID();
-        }
-        ImGui::PopID();
-
-        ImGui::SameLine();
-        ImGui::PushID("set2");
-        static float values2[4] = { 0.20f, 0.80f, 0.40f, 0.25f };
-        const int rows = 3;
-        const ImVec2 small_slider_size(18, (float)(int)((160.0f - (rows - 1) * spacing) / rows));
-        for (int nx = 0; nx < 4; nx++)
-        {
-            if (nx > 0) ImGui::SameLine();
-            ImGui::BeginGroup();
-            for (int ny = 0; ny < rows; ny++)
-            {
-                ImGui::PushID(nx * rows + ny);
-                ImGui::VSliderFloat("##v", small_slider_size, &values2[nx], 0.0f, 1.0f, "");
-                if (ImGui::IsItemActive() || ImGui::IsItemHovered())
-                    ImGui::SetTooltip("%.3f", values2[nx]);
-                ImGui::PopID();
-            }
-            ImGui::EndGroup();
-        }
-        ImGui::PopID();
-
-        ImGui::SameLine();
-        ImGui::PushID("set3");
-        for (int i = 0; i < 4; i++)
-        {
-            if (i > 0) ImGui::SameLine();
-            ImGui::PushID(i);
-            ImGui::PushStyleVar(ImGuiStyleVar_GrabMinSize, 40);
-            ImGui::VSliderFloat("##v", ImVec2(40, 160), &values[i], 0.0f, 1.0f, "%.2f\nsec");
-            ImGui::PopStyleVar();
-            ImGui::PopID();
-        }
-        ImGui::PopID();
-        ImGui::PopStyleVar();
-        ImGui::TreePop();
-    }
+//-----------------------------------------------------------------------------
+// [SECTION] DemoWindowWidgetsDragAndDrop()
+//-----------------------------------------------------------------------------
 
+static void DemoWindowWidgetsDragAndDrop()
+{
     IMGUI_DEMO_MARKER("Widgets/Drag and drop");
     if (ImGui::TreeNode("Drag and Drop"))
     {
@@ -2737,7 +1666,378 @@ static void ShowDemoWindowWidgets(ImGuiDemoWindowData* demo_data)
 
         ImGui::TreePop();
     }
+}
 
+//-----------------------------------------------------------------------------
+// [SECTION] DemoWindowWidgetsDragsAndSliders()
+//-----------------------------------------------------------------------------
+
+static void DemoWindowWidgetsDragsAndSliders()
+{
+    IMGUI_DEMO_MARKER("Widgets/Drag and Slider Flags");
+    if (ImGui::TreeNode("Drag/Slider Flags"))
+    {
+        // Demonstrate using advanced flags for DragXXX and SliderXXX functions. Note that the flags are the same!
+        static ImGuiSliderFlags flags = ImGuiSliderFlags_None;
+        ImGui::CheckboxFlags("ImGuiSliderFlags_AlwaysClamp", &flags, ImGuiSliderFlags_AlwaysClamp);
+        ImGui::CheckboxFlags("ImGuiSliderFlags_ClampOnInput", &flags, ImGuiSliderFlags_ClampOnInput);
+        ImGui::SameLine(); HelpMarker("Clamp value to min/max bounds when input manually with CTRL+Click. By default CTRL+Click allows going out of bounds.");
+        ImGui::CheckboxFlags("ImGuiSliderFlags_ClampZeroRange", &flags, ImGuiSliderFlags_ClampZeroRange);
+        ImGui::SameLine(); HelpMarker("Clamp even if min==max==0.0f. Otherwise DragXXX functions don't clamp.");
+        ImGui::CheckboxFlags("ImGuiSliderFlags_Logarithmic", &flags, ImGuiSliderFlags_Logarithmic);
+        ImGui::SameLine(); HelpMarker("Enable logarithmic editing (more precision for small values).");
+        ImGui::CheckboxFlags("ImGuiSliderFlags_NoRoundToFormat", &flags, ImGuiSliderFlags_NoRoundToFormat);
+        ImGui::SameLine(); HelpMarker("Disable rounding underlying value to match precision of the format string (e.g. %.3f values are rounded to those 3 digits).");
+        ImGui::CheckboxFlags("ImGuiSliderFlags_NoInput", &flags, ImGuiSliderFlags_NoInput);
+        ImGui::SameLine(); HelpMarker("Disable CTRL+Click or Enter key allowing to input text directly into the widget.");
+        ImGui::CheckboxFlags("ImGuiSliderFlags_NoSpeedTweaks", &flags, ImGuiSliderFlags_NoSpeedTweaks);
+        ImGui::SameLine(); HelpMarker("Disable keyboard modifiers altering tweak speed. Useful if you want to alter tweak speed yourself based on your own logic.");
+        ImGui::CheckboxFlags("ImGuiSliderFlags_WrapAround", &flags, ImGuiSliderFlags_WrapAround);
+        ImGui::SameLine(); HelpMarker("Enable wrapping around from max to min and from min to max (only supported by DragXXX() functions)");
+
+        // Drags
+        static float drag_f = 0.5f;
+        static int drag_i = 50;
+        ImGui::Text("Underlying float value: %f", drag_f);
+        ImGui::DragFloat("DragFloat (0 -> 1)", &drag_f, 0.005f, 0.0f, 1.0f, "%.3f", flags);
+        ImGui::DragFloat("DragFloat (0 -> +inf)", &drag_f, 0.005f, 0.0f, FLT_MAX, "%.3f", flags);
+        ImGui::DragFloat("DragFloat (-inf -> 1)", &drag_f, 0.005f, -FLT_MAX, 1.0f, "%.3f", flags);
+        ImGui::DragFloat("DragFloat (-inf -> +inf)", &drag_f, 0.005f, -FLT_MAX, +FLT_MAX, "%.3f", flags);
+        //ImGui::DragFloat("DragFloat (0 -> 0)", &drag_f, 0.005f, 0.0f, 0.0f, "%.3f", flags);           // To test ClampZeroRange
+        //ImGui::DragFloat("DragFloat (100 -> 100)", &drag_f, 0.005f, 100.0f, 100.0f, "%.3f", flags);
+        ImGui::DragInt("DragInt (0 -> 100)", &drag_i, 0.5f, 0, 100, "%d", flags);
+
+        // Sliders
+        static float slider_f = 0.5f;
+        static int slider_i = 50;
+        const ImGuiSliderFlags flags_for_sliders = flags & ~ImGuiSliderFlags_WrapAround;
+        ImGui::Text("Underlying float value: %f", slider_f);
+        ImGui::SliderFloat("SliderFloat (0 -> 1)", &slider_f, 0.0f, 1.0f, "%.3f", flags_for_sliders);
+        ImGui::SliderInt("SliderInt (0 -> 100)", &slider_i, 0, 100, "%d", flags_for_sliders);
+
+        ImGui::TreePop();
+    }
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] DemoWindowWidgetsImages()
+//-----------------------------------------------------------------------------
+
+static void DemoWindowWidgetsImages()
+{
+    IMGUI_DEMO_MARKER("Widgets/Images");
+    if (ImGui::TreeNode("Images"))
+    {
+        ImGuiIO& io = ImGui::GetIO();
+        ImGui::TextWrapped(
+            "Below we are displaying the font texture (which is the only texture we have access to in this demo). "
+            "Use the 'ImTextureID' type as storage to pass pointers or identifier to your own texture data. "
+            "Hover the texture for a zoomed view!");
+
+        // Below we are displaying the font texture because it is the only texture we have access to inside the demo!
+        // Remember that ImTextureID is just storage for whatever you want it to be. It is essentially a value that
+        // will be passed to the rendering backend via the ImDrawCmd structure.
+        // If you use one of the default imgui_impl_XXXX.cpp rendering backend, they all have comments at the top
+        // of their respective source file to specify what they expect to be stored in ImTextureID, for example:
+        // - The imgui_impl_dx11.cpp renderer expect a 'ID3D11ShaderResourceView*' pointer
+        // - The imgui_impl_opengl3.cpp renderer expect a GLuint OpenGL texture identifier, etc.
+        // More:
+        // - If you decided that ImTextureID = MyEngineTexture*, then you can pass your MyEngineTexture* pointers
+        //   to ImGui::Image(), and gather width/height through your own functions, etc.
+        // - You can use ShowMetricsWindow() to inspect the draw data that are being passed to your renderer,
+        //   it will help you debug issues if you are confused about it.
+        // - Consider using the lower-level ImDrawList::AddImage() API, via ImGui::GetWindowDrawList()->AddImage().
+        // - Read https://github.com/ocornut/imgui/blob/master/docs/FAQ.md
+        // - Read https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples
+        ImTextureID my_tex_id = io.Fonts->TexID;
+        float my_tex_w = (float)io.Fonts->TexWidth;
+        float my_tex_h = (float)io.Fonts->TexHeight;
+        {
+            ImGui::Text("%.0fx%.0f", my_tex_w, my_tex_h);
+            ImVec2 pos = ImGui::GetCursorScreenPos();
+            ImVec2 uv_min = ImVec2(0.0f, 0.0f);                 // Top-left
+            ImVec2 uv_max = ImVec2(1.0f, 1.0f);                 // Lower-right
+            ImGui::PushStyleVar(ImGuiStyleVar_ImageBorderSize, IM_MAX(1.0f, ImGui::GetStyle().ImageBorderSize));
+            ImGui::ImageWithBg(my_tex_id, ImVec2(my_tex_w, my_tex_h), uv_min, uv_max, ImVec4(0.0f, 0.0f, 0.0f, 1.0f));
+            if (ImGui::BeginItemTooltip())
+            {
+                float region_sz = 32.0f;
+                float region_x = io.MousePos.x - pos.x - region_sz * 0.5f;
+                float region_y = io.MousePos.y - pos.y - region_sz * 0.5f;
+                float zoom = 4.0f;
+                if (region_x < 0.0f) { region_x = 0.0f; }
+                else if (region_x > my_tex_w - region_sz) { region_x = my_tex_w - region_sz; }
+                if (region_y < 0.0f) { region_y = 0.0f; }
+                else if (region_y > my_tex_h - region_sz) { region_y = my_tex_h - region_sz; }
+                ImGui::Text("Min: (%.2f, %.2f)", region_x, region_y);
+                ImGui::Text("Max: (%.2f, %.2f)", region_x + region_sz, region_y + region_sz);
+                ImVec2 uv0 = ImVec2((region_x) / my_tex_w, (region_y) / my_tex_h);
+                ImVec2 uv1 = ImVec2((region_x + region_sz) / my_tex_w, (region_y + region_sz) / my_tex_h);
+                ImGui::ImageWithBg(my_tex_id, ImVec2(region_sz * zoom, region_sz * zoom), uv0, uv1, ImVec4(0.0f, 0.0f, 0.0f, 1.0f));
+                ImGui::EndTooltip();
+            }
+            ImGui::PopStyleVar();
+        }
+
+        IMGUI_DEMO_MARKER("Widgets/Images/Textured buttons");
+        ImGui::TextWrapped("And now some textured buttons..");
+        static int pressed_count = 0;
+        for (int i = 0; i < 8; i++)
+        {
+            // UV coordinates are often (0.0f, 0.0f) and (1.0f, 1.0f) to display an entire textures.
+            // Here are trying to display only a 32x32 pixels area of the texture, hence the UV computation.
+            // Read about UV coordinates here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples
+            ImGui::PushID(i);
+            if (i > 0)
+                ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(i - 1.0f, i - 1.0f));
+            ImVec2 size = ImVec2(32.0f, 32.0f);                         // Size of the image we want to make visible
+            ImVec2 uv0 = ImVec2(0.0f, 0.0f);                            // UV coordinates for lower-left
+            ImVec2 uv1 = ImVec2(32.0f / my_tex_w, 32.0f / my_tex_h);    // UV coordinates for (32,32) in our texture
+            ImVec4 bg_col = ImVec4(0.0f, 0.0f, 0.0f, 1.0f);             // Black background
+            ImVec4 tint_col = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);           // No tint
+            if (ImGui::ImageButton("", my_tex_id, size, uv0, uv1, bg_col, tint_col))
+                pressed_count += 1;
+            if (i > 0)
+                ImGui::PopStyleVar();
+            ImGui::PopID();
+            ImGui::SameLine();
+        }
+        ImGui::NewLine();
+        ImGui::Text("Pressed %d times.", pressed_count);
+        ImGui::TreePop();
+    }
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] DemoWindowWidgetsListBoxes()
+//-----------------------------------------------------------------------------
+
+static void DemoWindowWidgetsListBoxes()
+{
+    IMGUI_DEMO_MARKER("Widgets/List Boxes");
+    if (ImGui::TreeNode("List Boxes"))
+    {
+        // BeginListBox() is essentially a thin wrapper to using BeginChild()/EndChild()
+        // using the ImGuiChildFlags_FrameStyle flag for stylistic changes + displaying a label.
+        // You may be tempted to simply use BeginChild() directly. However note that BeginChild() requires EndChild()
+        // to always be called (inconsistent with BeginListBox()/EndListBox()).
+
+        // Using the generic BeginListBox() API, you have full control over how to display the combo contents.
+        // (your selection data could be an index, a pointer to the object, an id for the object, a flag intrusively
+        // stored in the object itself, etc.)
+        const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIII", "JJJJ", "KKKK", "LLLLLLL", "MMMM", "OOOOOOO" };
+        static int item_selected_idx = 0; // Here we store our selected data as an index.
+
+        static bool item_highlight = false;
+        int item_highlighted_idx = -1; // Here we store our highlighted data as an index.
+        ImGui::Checkbox("Highlight hovered item in second listbox", &item_highlight);
+
+        if (ImGui::BeginListBox("listbox 1"))
+        {
+            for (int n = 0; n < IM_ARRAYSIZE(items); n++)
+            {
+                const bool is_selected = (item_selected_idx == n);
+                if (ImGui::Selectable(items[n], is_selected))
+                    item_selected_idx = n;
+
+                if (item_highlight && ImGui::IsItemHovered())
+                    item_highlighted_idx = n;
+
+                // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
+                if (is_selected)
+                    ImGui::SetItemDefaultFocus();
+            }
+            ImGui::EndListBox();
+        }
+        ImGui::SameLine(); HelpMarker("Here we are sharing selection state between both boxes.");
+
+        // Custom size: use all width, 5 items tall
+        ImGui::Text("Full-width:");
+        if (ImGui::BeginListBox("##listbox 2", ImVec2(-FLT_MIN, 5 * ImGui::GetTextLineHeightWithSpacing())))
+        {
+            for (int n = 0; n < IM_ARRAYSIZE(items); n++)
+            {
+                bool is_selected = (item_selected_idx == n);
+                ImGuiSelectableFlags flags = (item_highlighted_idx == n) ? ImGuiSelectableFlags_Highlight : 0;
+                if (ImGui::Selectable(items[n], is_selected, flags))
+                    item_selected_idx = n;
+
+                // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
+                if (is_selected)
+                    ImGui::SetItemDefaultFocus();
+            }
+            ImGui::EndListBox();
+        }
+
+        ImGui::TreePop();
+    }
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] DemoWindowWidgetsMultiComponents()
+//-----------------------------------------------------------------------------
+
+static void DemoWindowWidgetsMultiComponents()
+{
+    IMGUI_DEMO_MARKER("Widgets/Multi-component Widgets");
+    if (ImGui::TreeNode("Multi-component Widgets"))
+    {
+        static float vec4f[4] = { 0.10f, 0.20f, 0.30f, 0.44f };
+        static int vec4i[4] = { 1, 5, 100, 255 };
+
+        ImGui::SeparatorText("2-wide");
+        ImGui::InputFloat2("input float2", vec4f);
+        ImGui::DragFloat2("drag float2", vec4f, 0.01f, 0.0f, 1.0f);
+        ImGui::SliderFloat2("slider float2", vec4f, 0.0f, 1.0f);
+        ImGui::InputInt2("input int2", vec4i);
+        ImGui::DragInt2("drag int2", vec4i, 1, 0, 255);
+        ImGui::SliderInt2("slider int2", vec4i, 0, 255);
+
+        ImGui::SeparatorText("3-wide");
+        ImGui::InputFloat3("input float3", vec4f);
+        ImGui::DragFloat3("drag float3", vec4f, 0.01f, 0.0f, 1.0f);
+        ImGui::SliderFloat3("slider float3", vec4f, 0.0f, 1.0f);
+        ImGui::InputInt3("input int3", vec4i);
+        ImGui::DragInt3("drag int3", vec4i, 1, 0, 255);
+        ImGui::SliderInt3("slider int3", vec4i, 0, 255);
+
+        ImGui::SeparatorText("4-wide");
+        ImGui::InputFloat4("input float4", vec4f);
+        ImGui::DragFloat4("drag float4", vec4f, 0.01f, 0.0f, 1.0f);
+        ImGui::SliderFloat4("slider float4", vec4f, 0.0f, 1.0f);
+        ImGui::InputInt4("input int4", vec4i);
+        ImGui::DragInt4("drag int4", vec4i, 1, 0, 255);
+        ImGui::SliderInt4("slider int4", vec4i, 0, 255);
+
+        ImGui::SeparatorText("Ranges");
+        static float begin = 10, end = 90;
+        static int begin_i = 100, end_i = 1000;
+        ImGui::DragFloatRange2("range float", &begin, &end, 0.25f, 0.0f, 100.0f, "Min: %.1f %%", "Max: %.1f %%", ImGuiSliderFlags_AlwaysClamp);
+        ImGui::DragIntRange2("range int", &begin_i, &end_i, 5, 0, 1000, "Min: %d units", "Max: %d units");
+        ImGui::DragIntRange2("range int (no bounds)", &begin_i, &end_i, 5, 0, 0, "Min: %d units", "Max: %d units");
+
+        ImGui::TreePop();
+    }
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] DemoWindowWidgetsPlotting()
+//-----------------------------------------------------------------------------
+
+static void DemoWindowWidgetsPlotting()
+{
+    // Plot/Graph widgets are not very good.
+// Consider using a third-party library such as ImPlot: https://github.com/epezent/implot
+// (see others https://github.com/ocornut/imgui/wiki/Useful-Extensions)
+    IMGUI_DEMO_MARKER("Widgets/Plotting");
+    if (ImGui::TreeNode("Plotting"))
+    {
+        ImGui::Text("Need better plotting and graphing? Consider using ImPlot:");
+        ImGui::TextLinkOpenURL("https://github.com/epezent/implot");
+        ImGui::Separator();
+
+        static bool animate = true;
+        ImGui::Checkbox("Animate", &animate);
+
+        // Plot as lines and plot as histogram
+        static float arr[] = { 0.6f, 0.1f, 1.0f, 0.5f, 0.92f, 0.1f, 0.2f };
+        ImGui::PlotLines("Frame Times", arr, IM_ARRAYSIZE(arr));
+        ImGui::PlotHistogram("Histogram", arr, IM_ARRAYSIZE(arr), 0, NULL, 0.0f, 1.0f, ImVec2(0, 80.0f));
+        //ImGui::SameLine(); HelpMarker("Consider using ImPlot instead!");
+
+        // Fill an array of contiguous float values to plot
+        // Tip: If your float aren't contiguous but part of a structure, you can pass a pointer to your first float
+        // and the sizeof() of your structure in the "stride" parameter.
+        static float values[90] = {};
+        static int values_offset = 0;
+        static double refresh_time = 0.0;
+        if (!animate || refresh_time == 0.0)
+            refresh_time = ImGui::GetTime();
+        while (refresh_time < ImGui::GetTime()) // Create data at fixed 60 Hz rate for the demo
+        {
+            static float phase = 0.0f;
+            values[values_offset] = cosf(phase);
+            values_offset = (values_offset + 1) % IM_ARRAYSIZE(values);
+            phase += 0.10f * values_offset;
+            refresh_time += 1.0f / 60.0f;
+        }
+
+        // Plots can display overlay texts
+        // (in this example, we will display an average value)
+        {
+            float average = 0.0f;
+            for (int n = 0; n < IM_ARRAYSIZE(values); n++)
+                average += values[n];
+            average /= (float)IM_ARRAYSIZE(values);
+            char overlay[32];
+            sprintf(overlay, "avg %f", average);
+            ImGui::PlotLines("Lines", values, IM_ARRAYSIZE(values), values_offset, overlay, -1.0f, 1.0f, ImVec2(0, 80.0f));
+        }
+
+        // Use functions to generate output
+        // FIXME: This is actually VERY awkward because current plot API only pass in indices.
+        // We probably want an API passing floats and user provide sample rate/count.
+        struct Funcs
+        {
+            static float Sin(void*, int i) { return sinf(i * 0.1f); }
+            static float Saw(void*, int i) { return (i & 1) ? 1.0f : -1.0f; }
+        };
+        static int func_type = 0, display_count = 70;
+        ImGui::SeparatorText("Functions");
+        ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);
+        ImGui::Combo("func", &func_type, "Sin\0Saw\0");
+        ImGui::SameLine();
+        ImGui::SliderInt("Sample count", &display_count, 1, 400);
+        float (*func)(void*, int) = (func_type == 0) ? Funcs::Sin : Funcs::Saw;
+        ImGui::PlotLines("Lines##2", func, NULL, display_count, 0, NULL, -1.0f, 1.0f, ImVec2(0, 80));
+        ImGui::PlotHistogram("Histogram##2", func, NULL, display_count, 0, NULL, -1.0f, 1.0f, ImVec2(0, 80));
+
+        ImGui::TreePop();
+    }
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] DemoWindowWidgetsProgressBars()
+//-----------------------------------------------------------------------------
+
+static void DemoWindowWidgetsProgressBars()
+{
+    IMGUI_DEMO_MARKER("Widgets/Progress Bars");
+    if (ImGui::TreeNode("Progress Bars"))
+    {
+        // Animate a simple progress bar
+        static float progress = 0.0f, progress_dir = 1.0f;
+        progress += progress_dir * 0.4f * ImGui::GetIO().DeltaTime;
+        if (progress >= +1.1f) { progress = +1.1f; progress_dir *= -1.0f; }
+        if (progress <= -0.1f) { progress = -0.1f; progress_dir *= -1.0f; }
+
+        // Typically we would use ImVec2(-1.0f,0.0f) or ImVec2(-FLT_MIN,0.0f) to use all available width,
+        // or ImVec2(width,0.0f) for a specified width. ImVec2(0.0f,0.0f) uses ItemWidth.
+        ImGui::ProgressBar(progress, ImVec2(0.0f, 0.0f));
+        ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
+        ImGui::Text("Progress Bar");
+
+        float progress_saturated = IM_CLAMP(progress, 0.0f, 1.0f);
+        char buf[32];
+        sprintf(buf, "%d/%d", (int)(progress_saturated * 1753), 1753);
+        ImGui::ProgressBar(progress, ImVec2(0.f, 0.f), buf);
+
+        // Pass an animated negative value, e.g. -1.0f * (float)ImGui::GetTime() is the recommended value.
+        // Adjust the factor if you want to adjust the animation speed.
+        ImGui::ProgressBar(-1.0f * (float)ImGui::GetTime(), ImVec2(0.0f, 0.0f), "Searching..");
+        ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
+        ImGui::Text("Indeterminate");
+
+        ImGui::TreePop();
+    }
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] DemoWindowWidgetsQueryingStatuses()
+//-----------------------------------------------------------------------------
+
+static void DemoWindowWidgetsQueryingStatuses()
+{
     IMGUI_DEMO_MARKER("Widgets/Querying Item Status (Edited,Active,Hovered etc.)");
     if (ImGui::TreeNode("Querying Item Status (Edited/Active/Hovered etc.)"))
     {
@@ -2752,7 +2052,7 @@ static void ShowDemoWindowWidgets(ImGuiDemoWindowData* demo_data)
         ImGui::Combo("Item Type", &item_type, item_names, IM_ARRAYSIZE(item_names), IM_ARRAYSIZE(item_names));
         ImGui::SameLine();
         HelpMarker("Testing how various types of items are interacting with the IsItemXXX functions. Note that the bool return value of most ImGui function is generally equivalent to calling ImGui::IsItemHovered().");
-        ImGui::Checkbox("Item Disabled",  &item_disabled);
+        ImGui::Checkbox("Item Disabled", &item_disabled);
 
         // Submit selected items so we can query their status in the code following it.
         bool ret = false;
@@ -2771,12 +2071,12 @@ static void ShowDemoWindowWidgets(ImGuiDemoWindowData* demo_data)
         if (item_type == 7) { ret = ImGui::InputFloat("ITEM: InputFloat", col4f, 1.0f); }               // Testing +/- buttons on scalar input
         if (item_type == 8) { ret = ImGui::InputFloat3("ITEM: InputFloat3", col4f); }                   // Testing multi-component items (IsItemXXX flags are reported merged)
         if (item_type == 9) { ret = ImGui::ColorEdit4("ITEM: ColorEdit4", col4f); }                     // Testing multi-component items (IsItemXXX flags are reported merged)
-        if (item_type == 10){ ret = ImGui::Selectable("ITEM: Selectable"); }                            // Testing selectable item
-        if (item_type == 11){ ret = ImGui::MenuItem("ITEM: MenuItem"); }                                // Testing menu item (they use ImGuiButtonFlags_PressedOnRelease button policy)
-        if (item_type == 12){ ret = ImGui::TreeNode("ITEM: TreeNode"); if (ret) ImGui::TreePop(); }     // Testing tree node
-        if (item_type == 13){ ret = ImGui::TreeNodeEx("ITEM: TreeNode w/ ImGuiTreeNodeFlags_OpenOnDoubleClick", ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_NoTreePushOnOpen); } // Testing tree node with ImGuiButtonFlags_PressedOnDoubleClick button policy.
-        if (item_type == 14){ const char* items[] = { "Apple", "Banana", "Cherry", "Kiwi" }; static int current = 1; ret = ImGui::Combo("ITEM: Combo", ¤t, items, IM_ARRAYSIZE(items)); }
-        if (item_type == 15){ const char* items[] = { "Apple", "Banana", "Cherry", "Kiwi" }; static int current = 1; ret = ImGui::ListBox("ITEM: ListBox", ¤t, items, IM_ARRAYSIZE(items), IM_ARRAYSIZE(items)); }
+        if (item_type == 10) { ret = ImGui::Selectable("ITEM: Selectable"); }                            // Testing selectable item
+        if (item_type == 11) { ret = ImGui::MenuItem("ITEM: MenuItem"); }                                // Testing menu item (they use ImGuiButtonFlags_PressedOnRelease button policy)
+        if (item_type == 12) { ret = ImGui::TreeNode("ITEM: TreeNode"); if (ret) ImGui::TreePop(); }     // Testing tree node
+        if (item_type == 13) { ret = ImGui::TreeNodeEx("ITEM: TreeNode w/ ImGuiTreeNodeFlags_OpenOnDoubleClick", ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_NoTreePushOnOpen); } // Testing tree node with ImGuiButtonFlags_PressedOnDoubleClick button policy.
+        if (item_type == 14) { const char* items[] = { "Apple", "Banana", "Cherry", "Kiwi" }; static int current = 1; ret = ImGui::Combo("ITEM: Combo", ¤t, items, IM_ARRAYSIZE(items)); }
+        if (item_type == 15) { const char* items[] = { "Apple", "Banana", "Cherry", "Kiwi" }; static int current = 1; ret = ImGui::ListBox("ITEM: ListBox", ¤t, items, IM_ARRAYSIZE(items), IM_ARRAYSIZE(items)); }
 
         bool hovered_delay_none = ImGui::IsItemHovered();
         bool hovered_delay_stationary = ImGui::IsItemHovered(ImGuiHoveredFlags_Stationary);
@@ -2931,41 +2231,152 @@ static void ShowDemoWindowWidgets(ImGuiDemoWindowData* demo_data)
 
         ImGui::TreePop();
     }
+}
 
-    // Demonstrate BeginDisabled/EndDisabled using a checkbox located at the bottom of the section (which is a bit odd:
-    // logically we'd have this checkbox at the top of the section, but we don't want this feature to steal that space)
-    if (disable_all)
-        ImGui::EndDisabled();
+//-----------------------------------------------------------------------------
+// [SECTION] DemoWindowWidgetsSelectables()
+//-----------------------------------------------------------------------------
 
-    IMGUI_DEMO_MARKER("Widgets/Disable Block");
-    if (ImGui::TreeNode("Disable block"))
+static void DemoWindowWidgetsSelectables()
+{
+    IMGUI_DEMO_MARKER("Widgets/Selectables");
+    //ImGui::SetNextItemOpen(true, ImGuiCond_Once);
+    if (ImGui::TreeNode("Selectables"))
     {
-        ImGui::Checkbox("Disable entire section above", &disable_all);
-        ImGui::SameLine(); HelpMarker("Demonstrate using BeginDisabled()/EndDisabled() across this section.");
-        ImGui::TreePop();
-    }
+        // Selectable() has 2 overloads:
+        // - The one taking "bool selected" as a read-only selection information.
+        //   When Selectable() has been clicked it returns true and you can alter selection state accordingly.
+        // - The one taking "bool* p_selected" as a read-write selection information (convenient in some cases)
+        // The earlier is more flexible, as in real application your selection may be stored in many different ways
+        // and not necessarily inside a bool value (e.g. in flags within objects, as an external list, etc).
+        IMGUI_DEMO_MARKER("Widgets/Selectables/Basic");
+        if (ImGui::TreeNode("Basic"))
+        {
+            static bool selection[5] = { false, true, false, false };
+            ImGui::Selectable("1. I am selectable", &selection[0]);
+            ImGui::Selectable("2. I am selectable", &selection[1]);
+            ImGui::Selectable("3. I am selectable", &selection[2]);
+            if (ImGui::Selectable("4. I am double clickable", selection[3], ImGuiSelectableFlags_AllowDoubleClick))
+                if (ImGui::IsMouseDoubleClicked(0))
+                    selection[3] = !selection[3];
+            ImGui::TreePop();
+        }
 
-    IMGUI_DEMO_MARKER("Widgets/Text Filter");
-    if (ImGui::TreeNode("Text Filter"))
-    {
-        // Helper class to easy setup a text filter.
-        // You may want to implement a more feature-full filtering scheme in your own application.
-        HelpMarker("Not a widget per-se, but ImGuiTextFilter is a helper to perform simple filtering on text strings.");
-        static ImGuiTextFilter filter;
-        ImGui::Text("Filter usage:\n"
-            "  \"\"         display all lines\n"
-            "  \"xxx\"      display lines containing \"xxx\"\n"
-            "  \"xxx,yyy\"  display lines containing \"xxx\" or \"yyy\"\n"
-            "  \"-xxx\"     hide lines containing \"xxx\"");
-        filter.Draw();
-        const char* lines[] = { "aaa1.c", "bbb1.c", "ccc1.c", "aaa2.cpp", "bbb2.cpp", "ccc2.cpp", "abc.h", "hello, world" };
-        for (int i = 0; i < IM_ARRAYSIZE(lines); i++)
-            if (filter.PassFilter(lines[i]))
-                ImGui::BulletText("%s", lines[i]);
+        IMGUI_DEMO_MARKER("Widgets/Selectables/Rendering more items on the same line");
+        if (ImGui::TreeNode("Rendering more items on the same line"))
+        {
+            // (1) Using SetNextItemAllowOverlap()
+            // (2) Using the Selectable() override that takes "bool* p_selected" parameter, the bool value is toggled automatically.
+            static bool selected[3] = { false, false, false };
+            ImGui::SetNextItemAllowOverlap(); ImGui::Selectable("main.c", &selected[0]); ImGui::SameLine(); ImGui::SmallButton("Link 1");
+            ImGui::SetNextItemAllowOverlap(); ImGui::Selectable("Hello.cpp", &selected[1]); ImGui::SameLine(); ImGui::SmallButton("Link 2");
+            ImGui::SetNextItemAllowOverlap(); ImGui::Selectable("Hello.h", &selected[2]); ImGui::SameLine(); ImGui::SmallButton("Link 3");
+            ImGui::TreePop();
+        }
+
+        IMGUI_DEMO_MARKER("Widgets/Selectables/In Tables");
+        if (ImGui::TreeNode("In Tables"))
+        {
+            static bool selected[10] = {};
+
+            if (ImGui::BeginTable("split1", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_Borders))
+            {
+                for (int i = 0; i < 10; i++)
+                {
+                    char label[32];
+                    sprintf(label, "Item %d", i);
+                    ImGui::TableNextColumn();
+                    ImGui::Selectable(label, &selected[i]); // FIXME-TABLE: Selection overlap
+                }
+                ImGui::EndTable();
+            }
+            ImGui::Spacing();
+            if (ImGui::BeginTable("split2", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_Borders))
+            {
+                for (int i = 0; i < 10; i++)
+                {
+                    char label[32];
+                    sprintf(label, "Item %d", i);
+                    ImGui::TableNextRow();
+                    ImGui::TableNextColumn();
+                    ImGui::Selectable(label, &selected[i], ImGuiSelectableFlags_SpanAllColumns);
+                    ImGui::TableNextColumn();
+                    ImGui::Text("Some other contents");
+                    ImGui::TableNextColumn();
+                    ImGui::Text("123456");
+                }
+                ImGui::EndTable();
+            }
+            ImGui::TreePop();
+        }
+
+        IMGUI_DEMO_MARKER("Widgets/Selectables/Grid");
+        if (ImGui::TreeNode("Grid"))
+        {
+            static char selected[4][4] = { { 1, 0, 0, 0 }, { 0, 1, 0, 0 }, { 0, 0, 1, 0 }, { 0, 0, 0, 1 } };
+
+            // Add in a bit of silly fun...
+            const float time = (float)ImGui::GetTime();
+            const bool winning_state = memchr(selected, 0, sizeof(selected)) == NULL; // If all cells are selected...
+            if (winning_state)
+                ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.5f + 0.5f * cosf(time * 2.0f), 0.5f + 0.5f * sinf(time * 3.0f)));
+
+            for (int y = 0; y < 4; y++)
+                for (int x = 0; x < 4; x++)
+                {
+                    if (x > 0)
+                        ImGui::SameLine();
+                    ImGui::PushID(y * 4 + x);
+                    if (ImGui::Selectable("Sailor", selected[y][x] != 0, 0, ImVec2(50, 50)))
+                    {
+                        // Toggle clicked cell + toggle neighbors
+                        selected[y][x] ^= 1;
+                        if (x > 0) { selected[y][x - 1] ^= 1; }
+                        if (x < 3) { selected[y][x + 1] ^= 1; }
+                        if (y > 0) { selected[y - 1][x] ^= 1; }
+                        if (y < 3) { selected[y + 1][x] ^= 1; }
+                    }
+                    ImGui::PopID();
+                }
+
+            if (winning_state)
+                ImGui::PopStyleVar();
+            ImGui::TreePop();
+        }
+        IMGUI_DEMO_MARKER("Widgets/Selectables/Alignment");
+        if (ImGui::TreeNode("Alignment"))
+        {
+            HelpMarker(
+                "By default, Selectables uses style.SelectableTextAlign but it can be overridden on a per-item "
+                "basis using PushStyleVar(). You'll probably want to always keep your default situation to "
+                "left-align otherwise it becomes difficult to layout multiple items on a same line");
+            static bool selected[3 * 3] = { true, false, true, false, true, false, true, false, true };
+            for (int y = 0; y < 3; y++)
+            {
+                for (int x = 0; x < 3; x++)
+                {
+                    ImVec2 alignment = ImVec2((float)x / 2.0f, (float)y / 2.0f);
+                    char name[32];
+                    sprintf(name, "(%.1f,%.1f)", alignment.x, alignment.y);
+                    if (x > 0) ImGui::SameLine();
+                    ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, alignment);
+                    ImGui::Selectable(name, &selected[3 * y + x], ImGuiSelectableFlags_None, ImVec2(80, 80));
+                    ImGui::PopStyleVar();
+                }
+            }
+            ImGui::TreePop();
+        }
         ImGui::TreePop();
     }
 }
 
+//-----------------------------------------------------------------------------
+// [SECTION] DemoWindowWidgetsSelectionAndMultiSelect()
+//-----------------------------------------------------------------------------
+// Multi-selection demos
+// Also read: https://github.com/ocornut/imgui/wiki/Multi-Select
+//-----------------------------------------------------------------------------
+
 static const char* ExampleNames[] =
 {
     "Artichoke", "Arugula", "Asparagus", "Avocado", "Bamboo Shoots", "Bean Sprouts", "Beans", "Beet", "Belgian Endive", "Bell Pepper",
@@ -3191,14 +2602,7 @@ struct ExampleDualListBox
     }
 };
 
-//-----------------------------------------------------------------------------
-// [SECTION] ShowDemoWindowMultiSelect()
-//-----------------------------------------------------------------------------
-// Multi-selection demos
-// Also read: https://github.com/ocornut/imgui/wiki/Multi-Select
-//-----------------------------------------------------------------------------
-
-static void ShowDemoWindowMultiSelect(ImGuiDemoWindowData* demo_data)
+static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_data)
 {
     IMGUI_DEMO_MARKER("Widgets/Selection State & Multi-Select");
     if (ImGui::TreeNode("Selection State & Multi-Select"))
@@ -3558,7 +2962,7 @@ static void ShowDemoWindowMultiSelect(ImGuiDemoWindowData* demo_data)
         if (ImGui::TreeNode("Multi-Select (trees)"))
         {
             HelpMarker(
-                "This is rather advanced and experimental. If you are getting started with multi-select,"
+                "This is rather advanced and experimental. If you are getting started with multi-select, "
                 "please don't start by looking at how to use it for a tree!\n\n"
                 "Future versions will try to simplify and formalize some of this.");
 
@@ -3934,10 +3338,839 @@ static void ShowDemoWindowMultiSelect(ImGuiDemoWindowData* demo_data)
 }
 
 //-----------------------------------------------------------------------------
-// [SECTION] ShowDemoWindowLayout()
+// [SECTION] DemoWindowWidgetsTabs()
 //-----------------------------------------------------------------------------
 
-static void ShowDemoWindowLayout()
+static void DemoWindowWidgetsTabs()
+{
+    IMGUI_DEMO_MARKER("Widgets/Tabs");
+    if (ImGui::TreeNode("Tabs"))
+    {
+        IMGUI_DEMO_MARKER("Widgets/Tabs/Basic");
+        if (ImGui::TreeNode("Basic"))
+        {
+            ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_None;
+            if (ImGui::BeginTabBar("MyTabBar", tab_bar_flags))
+            {
+                if (ImGui::BeginTabItem("Avocado"))
+                {
+                    ImGui::Text("This is the Avocado tab!\nblah blah blah blah blah");
+                    ImGui::EndTabItem();
+                }
+                if (ImGui::BeginTabItem("Broccoli"))
+                {
+                    ImGui::Text("This is the Broccoli tab!\nblah blah blah blah blah");
+                    ImGui::EndTabItem();
+                }
+                if (ImGui::BeginTabItem("Cucumber"))
+                {
+                    ImGui::Text("This is the Cucumber tab!\nblah blah blah blah blah");
+                    ImGui::EndTabItem();
+                }
+                ImGui::EndTabBar();
+            }
+            ImGui::Separator();
+            ImGui::TreePop();
+        }
+
+        IMGUI_DEMO_MARKER("Widgets/Tabs/Advanced & Close Button");
+        if (ImGui::TreeNode("Advanced & Close Button"))
+        {
+            // Expose a couple of the available flags. In most cases you may just call BeginTabBar() with no flags (0).
+            static ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_Reorderable;
+            ImGui::CheckboxFlags("ImGuiTabBarFlags_Reorderable", &tab_bar_flags, ImGuiTabBarFlags_Reorderable);
+            ImGui::CheckboxFlags("ImGuiTabBarFlags_AutoSelectNewTabs", &tab_bar_flags, ImGuiTabBarFlags_AutoSelectNewTabs);
+            ImGui::CheckboxFlags("ImGuiTabBarFlags_TabListPopupButton", &tab_bar_flags, ImGuiTabBarFlags_TabListPopupButton);
+            ImGui::CheckboxFlags("ImGuiTabBarFlags_NoCloseWithMiddleMouseButton", &tab_bar_flags, ImGuiTabBarFlags_NoCloseWithMiddleMouseButton);
+            ImGui::CheckboxFlags("ImGuiTabBarFlags_DrawSelectedOverline", &tab_bar_flags, ImGuiTabBarFlags_DrawSelectedOverline);
+            if ((tab_bar_flags & ImGuiTabBarFlags_FittingPolicyMask_) == 0)
+                tab_bar_flags |= ImGuiTabBarFlags_FittingPolicyDefault_;
+            if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyResizeDown", &tab_bar_flags, ImGuiTabBarFlags_FittingPolicyResizeDown))
+                tab_bar_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyResizeDown);
+            if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyScroll", &tab_bar_flags, ImGuiTabBarFlags_FittingPolicyScroll))
+                tab_bar_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyScroll);
+
+            // Tab Bar
+            ImGui::AlignTextToFramePadding();
+            ImGui::Text("Opened:");
+            const char* names[4] = { "Artichoke", "Beetroot", "Celery", "Daikon" };
+            static bool opened[4] = { true, true, true, true }; // Persistent user state
+            for (int n = 0; n < IM_ARRAYSIZE(opened); n++)
+            {
+                ImGui::SameLine();
+                ImGui::Checkbox(names[n], &opened[n]);
+            }
+
+            // Passing a bool* to BeginTabItem() is similar to passing one to Begin():
+            // the underlying bool will be set to false when the tab is closed.
+            if (ImGui::BeginTabBar("MyTabBar", tab_bar_flags))
+            {
+                for (int n = 0; n < IM_ARRAYSIZE(opened); n++)
+                    if (opened[n] && ImGui::BeginTabItem(names[n], &opened[n], ImGuiTabItemFlags_None))
+                    {
+                        ImGui::Text("This is the %s tab!", names[n]);
+                        if (n & 1)
+                            ImGui::Text("I am an odd tab.");
+                        ImGui::EndTabItem();
+                    }
+                ImGui::EndTabBar();
+            }
+            ImGui::Separator();
+            ImGui::TreePop();
+        }
+
+        IMGUI_DEMO_MARKER("Widgets/Tabs/TabItemButton & Leading-Trailing flags");
+        if (ImGui::TreeNode("TabItemButton & Leading/Trailing flags"))
+        {
+            static ImVector active_tabs;
+            static int next_tab_id = 0;
+            if (next_tab_id == 0) // Initialize with some default tabs
+                for (int i = 0; i < 3; i++)
+                    active_tabs.push_back(next_tab_id++);
+
+            // TabItemButton() and Leading/Trailing flags are distinct features which we will demo together.
+            // (It is possible to submit regular tabs with Leading/Trailing flags, or TabItemButton tabs without Leading/Trailing flags...
+            // but they tend to make more sense together)
+            static bool show_leading_button = true;
+            static bool show_trailing_button = true;
+            ImGui::Checkbox("Show Leading TabItemButton()", &show_leading_button);
+            ImGui::Checkbox("Show Trailing TabItemButton()", &show_trailing_button);
+
+            // Expose some other flags which are useful to showcase how they interact with Leading/Trailing tabs
+            static ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_FittingPolicyResizeDown;
+            ImGui::CheckboxFlags("ImGuiTabBarFlags_TabListPopupButton", &tab_bar_flags, ImGuiTabBarFlags_TabListPopupButton);
+            if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyResizeDown", &tab_bar_flags, ImGuiTabBarFlags_FittingPolicyResizeDown))
+                tab_bar_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyResizeDown);
+            if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyScroll", &tab_bar_flags, ImGuiTabBarFlags_FittingPolicyScroll))
+                tab_bar_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyScroll);
+
+            if (ImGui::BeginTabBar("MyTabBar", tab_bar_flags))
+            {
+                // Demo a Leading TabItemButton(): click the "?" button to open a menu
+                if (show_leading_button)
+                    if (ImGui::TabItemButton("?", ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_NoTooltip))
+                        ImGui::OpenPopup("MyHelpMenu");
+                if (ImGui::BeginPopup("MyHelpMenu"))
+                {
+                    ImGui::Selectable("Hello!");
+                    ImGui::EndPopup();
+                }
+
+                // Demo Trailing Tabs: click the "+" button to add a new tab.
+                // (In your app you may want to use a font icon instead of the "+")
+                // We submit it before the regular tabs, but thanks to the ImGuiTabItemFlags_Trailing flag it will always appear at the end.
+                if (show_trailing_button)
+                    if (ImGui::TabItemButton("+", ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_NoTooltip))
+                        active_tabs.push_back(next_tab_id++); // Add new tab
+
+                // Submit our regular tabs
+                for (int n = 0; n < active_tabs.Size; )
+                {
+                    bool open = true;
+                    char name[16];
+                    snprintf(name, IM_ARRAYSIZE(name), "%04d", active_tabs[n]);
+                    if (ImGui::BeginTabItem(name, &open, ImGuiTabItemFlags_None))
+                    {
+                        ImGui::Text("This is the %s tab!", name);
+                        ImGui::EndTabItem();
+                    }
+
+                    if (!open)
+                        active_tabs.erase(active_tabs.Data + n);
+                    else
+                        n++;
+                }
+
+                ImGui::EndTabBar();
+            }
+            ImGui::Separator();
+            ImGui::TreePop();
+        }
+        ImGui::TreePop();
+    }
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] DemoWindowWidgetsText()
+//-----------------------------------------------------------------------------
+
+static void DemoWindowWidgetsText()
+{
+    IMGUI_DEMO_MARKER("Widgets/Text");
+    if (ImGui::TreeNode("Text"))
+    {
+        IMGUI_DEMO_MARKER("Widgets/Text/Colored Text");
+        if (ImGui::TreeNode("Colorful Text"))
+        {
+            // Using shortcut. You can use PushStyleColor()/PopStyleColor() for more flexibility.
+            ImGui::TextColored(ImVec4(1.0f, 0.0f, 1.0f, 1.0f), "Pink");
+            ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "Yellow");
+            ImGui::TextDisabled("Disabled");
+            ImGui::SameLine(); HelpMarker("The TextDisabled color is stored in ImGuiStyle.");
+            ImGui::TreePop();
+        }
+
+        IMGUI_DEMO_MARKER("Widgets/Text/Word Wrapping");
+        if (ImGui::TreeNode("Word Wrapping"))
+        {
+            // Using shortcut. You can use PushTextWrapPos()/PopTextWrapPos() for more flexibility.
+            ImGui::TextWrapped(
+                "This text should automatically wrap on the edge of the window. The current implementation "
+                "for text wrapping follows simple rules suitable for English and possibly other languages.");
+            ImGui::Spacing();
+
+            static float wrap_width = 200.0f;
+            ImGui::SliderFloat("Wrap width", &wrap_width, -20, 600, "%.0f");
+
+            ImDrawList* draw_list = ImGui::GetWindowDrawList();
+            for (int n = 0; n < 2; n++)
+            {
+                ImGui::Text("Test paragraph %d:", n);
+                ImVec2 pos = ImGui::GetCursorScreenPos();
+                ImVec2 marker_min = ImVec2(pos.x + wrap_width, pos.y);
+                ImVec2 marker_max = ImVec2(pos.x + wrap_width + 10, pos.y + ImGui::GetTextLineHeight());
+                ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + wrap_width);
+                if (n == 0)
+                    ImGui::Text("The lazy dog is a good dog. This paragraph should fit within %.0f pixels. Testing a 1 character word. The quick brown fox jumps over the lazy dog.", wrap_width);
+                else
+                    ImGui::Text("aaaaaaaa bbbbbbbb, c cccccccc,dddddddd. d eeeeeeee   ffffffff. gggggggg!hhhhhhhh");
+
+                // Draw actual text bounding box, following by marker of our expected limit (should not overlap!)
+                draw_list->AddRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), IM_COL32(255, 255, 0, 255));
+                draw_list->AddRectFilled(marker_min, marker_max, IM_COL32(255, 0, 255, 255));
+                ImGui::PopTextWrapPos();
+            }
+
+            ImGui::TreePop();
+        }
+
+        IMGUI_DEMO_MARKER("Widgets/Text/UTF-8 Text");
+        if (ImGui::TreeNode("UTF-8 Text"))
+        {
+            // UTF-8 test with Japanese characters
+            // (Needs a suitable font? Try "Google Noto" or "Arial Unicode". See docs/FONTS.md for details.)
+            // - From C++11 you can use the u8"my text" syntax to encode literal strings as UTF-8
+            // - For earlier compiler, you may be able to encode your sources as UTF-8 (e.g. in Visual Studio, you
+            //   can save your source files as 'UTF-8 without signature').
+            // - FOR THIS DEMO FILE ONLY, BECAUSE WE WANT TO SUPPORT OLD COMPILERS, WE ARE *NOT* INCLUDING RAW UTF-8
+            //   CHARACTERS IN THIS SOURCE FILE. Instead we are encoding a few strings with hexadecimal constants.
+            //   Don't do this in your application! Please use u8"text in any language" in your application!
+            // Note that characters values are preserved even by InputText() if the font cannot be displayed,
+            // so you can safely copy & paste garbled characters into another application.
+            ImGui::TextWrapped(
+                "CJK text will only appear if the font was loaded with the appropriate CJK character ranges. "
+                "Call io.Fonts->AddFontFromFileTTF() manually to load extra character ranges. "
+                "Read docs/FONTS.md for details.");
+            ImGui::Text("Hiragana: \xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91\xe3\x81\x93 (kakikukeko)");
+            ImGui::Text("Kanjis: \xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e (nihongo)");
+            static char buf[32] = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e";
+            //static char buf[32] = u8"NIHONGO"; // <- this is how you would write it with C++11, using real kanjis
+            ImGui::InputText("UTF-8 input", buf, IM_ARRAYSIZE(buf));
+            ImGui::TreePop();
+        }
+        ImGui::TreePop();
+    }
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] DemoWindowWidgetsTextFilter()
+//-----------------------------------------------------------------------------
+
+static void DemoWindowWidgetsTextFilter()
+{
+    IMGUI_DEMO_MARKER("Widgets/Text Filter");
+    if (ImGui::TreeNode("Text Filter"))
+    {
+        // Helper class to easy setup a text filter.
+        // You may want to implement a more feature-full filtering scheme in your own application.
+        HelpMarker("Not a widget per-se, but ImGuiTextFilter is a helper to perform simple filtering on text strings.");
+        static ImGuiTextFilter filter;
+        ImGui::Text("Filter usage:\n"
+            "  \"\"         display all lines\n"
+            "  \"xxx\"      display lines containing \"xxx\"\n"
+            "  \"xxx,yyy\"  display lines containing \"xxx\" or \"yyy\"\n"
+            "  \"-xxx\"     hide lines containing \"xxx\"");
+        filter.Draw();
+        const char* lines[] = { "aaa1.c", "bbb1.c", "ccc1.c", "aaa2.cpp", "bbb2.cpp", "ccc2.cpp", "abc.h", "hello, world" };
+        for (int i = 0; i < IM_ARRAYSIZE(lines); i++)
+            if (filter.PassFilter(lines[i]))
+                ImGui::BulletText("%s", lines[i]);
+        ImGui::TreePop();
+    }
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] DemoWindowWidgetsTextInput()
+//-----------------------------------------------------------------------------
+
+static void DemoWindowWidgetsTextInput()
+{
+    // To wire InputText() with std::string or any other custom string type,
+    // see the "Text Input > Resize Callback" section of this demo, and the misc/cpp/imgui_stdlib.h file.
+    IMGUI_DEMO_MARKER("Widgets/Text Input");
+    if (ImGui::TreeNode("Text Input"))
+    {
+        IMGUI_DEMO_MARKER("Widgets/Text Input/Multi-line Text Input");
+        if (ImGui::TreeNode("Multi-line Text Input"))
+        {
+            // Note: we are using a fixed-sized buffer for simplicity here. See ImGuiInputTextFlags_CallbackResize
+            // and the code in misc/cpp/imgui_stdlib.h for how to setup InputText() for dynamically resizing strings.
+            static char text[1024 * 16] =
+                "/*\n"
+                " The Pentium F00F bug, shorthand for F0 0F C7 C8,\n"
+                " the hexadecimal encoding of one offending instruction,\n"
+                " more formally, the invalid operand with locked CMPXCHG8B\n"
+                " instruction bug, is a design flaw in the majority of\n"
+                " Intel Pentium, Pentium MMX, and Pentium OverDrive\n"
+                " processors (all in the P5 microarchitecture).\n"
+                "*/\n\n"
+                "label:\n"
+                "\tlock cmpxchg8b eax\n";
+
+            static ImGuiInputTextFlags flags = ImGuiInputTextFlags_AllowTabInput;
+            HelpMarker("You can use the ImGuiInputTextFlags_CallbackResize facility if you need to wire InputTextMultiline() to a dynamic string type. See misc/cpp/imgui_stdlib.h for an example. (This is not demonstrated in imgui_demo.cpp because we don't want to include  in here)");
+            ImGui::CheckboxFlags("ImGuiInputTextFlags_ReadOnly", &flags, ImGuiInputTextFlags_ReadOnly);
+            ImGui::CheckboxFlags("ImGuiInputTextFlags_AllowTabInput", &flags, ImGuiInputTextFlags_AllowTabInput);
+            ImGui::SameLine(); HelpMarker("When _AllowTabInput is set, passing through the widget with Tabbing doesn't automatically activate it, in order to also cycling through subsequent widgets.");
+            ImGui::CheckboxFlags("ImGuiInputTextFlags_CtrlEnterForNewLine", &flags, ImGuiInputTextFlags_CtrlEnterForNewLine);
+            ImGui::InputTextMultiline("##source", text, IM_ARRAYSIZE(text), ImVec2(-FLT_MIN, ImGui::GetTextLineHeight() * 16), flags);
+            ImGui::TreePop();
+        }
+
+        IMGUI_DEMO_MARKER("Widgets/Text Input/Filtered Text Input");
+        if (ImGui::TreeNode("Filtered Text Input"))
+        {
+            struct TextFilters
+            {
+                // Modify character input by altering 'data->Eventchar' (ImGuiInputTextFlags_CallbackCharFilter callback)
+                static int FilterCasingSwap(ImGuiInputTextCallbackData* data)
+                {
+                    if (data->EventChar >= 'a' && data->EventChar <= 'z') { data->EventChar -= 'a' - 'A'; } // Lowercase becomes uppercase
+                    else if (data->EventChar >= 'A' && data->EventChar <= 'Z') { data->EventChar += 'a' - 'A'; } // Uppercase becomes lowercase
+                    return 0;
+                }
+
+                // Return 0 (pass) if the character is 'i' or 'm' or 'g' or 'u' or 'i', otherwise return 1 (filter out)
+                static int FilterImGuiLetters(ImGuiInputTextCallbackData* data)
+                {
+                    if (data->EventChar < 256 && strchr("imgui", (char)data->EventChar))
+                        return 0;
+                    return 1;
+                }
+            };
+
+            static char buf1[32] = ""; ImGui::InputText("default", buf1, 32);
+            static char buf2[32] = ""; ImGui::InputText("decimal", buf2, 32, ImGuiInputTextFlags_CharsDecimal);
+            static char buf3[32] = ""; ImGui::InputText("hexadecimal", buf3, 32, ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase);
+            static char buf4[32] = ""; ImGui::InputText("uppercase", buf4, 32, ImGuiInputTextFlags_CharsUppercase);
+            static char buf5[32] = ""; ImGui::InputText("no blank", buf5, 32, ImGuiInputTextFlags_CharsNoBlank);
+            static char buf6[32] = ""; ImGui::InputText("casing swap", buf6, 32, ImGuiInputTextFlags_CallbackCharFilter, TextFilters::FilterCasingSwap); // Use CharFilter callback to replace characters.
+            static char buf7[32] = ""; ImGui::InputText("\"imgui\"", buf7, 32, ImGuiInputTextFlags_CallbackCharFilter, TextFilters::FilterImGuiLetters); // Use CharFilter callback to disable some characters.
+            ImGui::TreePop();
+        }
+
+        IMGUI_DEMO_MARKER("Widgets/Text Input/Password input");
+        if (ImGui::TreeNode("Password Input"))
+        {
+            static char password[64] = "password123";
+            ImGui::InputText("password", password, IM_ARRAYSIZE(password), ImGuiInputTextFlags_Password);
+            ImGui::SameLine(); HelpMarker("Display all characters as '*'.\nDisable clipboard cut and copy.\nDisable logging.\n");
+            ImGui::InputTextWithHint("password (w/ hint)", "", password, IM_ARRAYSIZE(password), ImGuiInputTextFlags_Password);
+            ImGui::InputText("password (clear)", password, IM_ARRAYSIZE(password));
+            ImGui::TreePop();
+        }
+
+        IMGUI_DEMO_MARKER("Widgets/Text Input/Completion, History, Edit Callbacks");
+        if (ImGui::TreeNode("Completion, History, Edit Callbacks"))
+        {
+            struct Funcs
+            {
+                static int MyCallback(ImGuiInputTextCallbackData* data)
+                {
+                    if (data->EventFlag == ImGuiInputTextFlags_CallbackCompletion)
+                    {
+                        data->InsertChars(data->CursorPos, "..");
+                    }
+                    else if (data->EventFlag == ImGuiInputTextFlags_CallbackHistory)
+                    {
+                        if (data->EventKey == ImGuiKey_UpArrow)
+                        {
+                            data->DeleteChars(0, data->BufTextLen);
+                            data->InsertChars(0, "Pressed Up!");
+                            data->SelectAll();
+                        }
+                        else if (data->EventKey == ImGuiKey_DownArrow)
+                        {
+                            data->DeleteChars(0, data->BufTextLen);
+                            data->InsertChars(0, "Pressed Down!");
+                            data->SelectAll();
+                        }
+                    }
+                    else if (data->EventFlag == ImGuiInputTextFlags_CallbackEdit)
+                    {
+                        // Toggle casing of first character
+                        char c = data->Buf[0];
+                        if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) data->Buf[0] ^= 32;
+                        data->BufDirty = true;
+
+                        // Increment a counter
+                        int* p_int = (int*)data->UserData;
+                        *p_int = *p_int + 1;
+                    }
+                    return 0;
+                }
+            };
+            static char buf1[64];
+            ImGui::InputText("Completion", buf1, 64, ImGuiInputTextFlags_CallbackCompletion, Funcs::MyCallback);
+            ImGui::SameLine(); HelpMarker(
+                "Here we append \"..\" each time Tab is pressed. "
+                "See 'Examples>Console' for a more meaningful demonstration of using this callback.");
+
+            static char buf2[64];
+            ImGui::InputText("History", buf2, 64, ImGuiInputTextFlags_CallbackHistory, Funcs::MyCallback);
+            ImGui::SameLine(); HelpMarker(
+                "Here we replace and select text each time Up/Down are pressed. "
+                "See 'Examples>Console' for a more meaningful demonstration of using this callback.");
+
+            static char buf3[64];
+            static int edit_count = 0;
+            ImGui::InputText("Edit", buf3, 64, ImGuiInputTextFlags_CallbackEdit, Funcs::MyCallback, (void*)&edit_count);
+            ImGui::SameLine(); HelpMarker(
+                "Here we toggle the casing of the first character on every edit + count edits.");
+            ImGui::SameLine(); ImGui::Text("(%d)", edit_count);
+
+            ImGui::TreePop();
+        }
+
+        IMGUI_DEMO_MARKER("Widgets/Text Input/Resize Callback");
+        if (ImGui::TreeNode("Resize Callback"))
+        {
+            // To wire InputText() with std::string or any other custom string type,
+            // you can use the ImGuiInputTextFlags_CallbackResize flag + create a custom ImGui::InputText() wrapper
+            // using your preferred type. See misc/cpp/imgui_stdlib.h for an implementation of this using std::string.
+            HelpMarker(
+                "Using ImGuiInputTextFlags_CallbackResize to wire your custom string type to InputText().\n\n"
+                "See misc/cpp/imgui_stdlib.h for an implementation of this for std::string.");
+            struct Funcs
+            {
+                static int MyResizeCallback(ImGuiInputTextCallbackData* data)
+                {
+                    if (data->EventFlag == ImGuiInputTextFlags_CallbackResize)
+                    {
+                        ImVector* my_str = (ImVector*)data->UserData;
+                        IM_ASSERT(my_str->begin() == data->Buf);
+                        my_str->resize(data->BufSize); // NB: On resizing calls, generally data->BufSize == data->BufTextLen + 1
+                        data->Buf = my_str->begin();
+                    }
+                    return 0;
+                }
+
+                // Note: Because ImGui:: is a namespace you would typically add your own function into the namespace.
+                // For example, you code may declare a function 'ImGui::InputText(const char* label, MyString* my_str)'
+                static bool MyInputTextMultiline(const char* label, ImVector* my_str, const ImVec2& size = ImVec2(0, 0), ImGuiInputTextFlags flags = 0)
+                {
+                    IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0);
+                    return ImGui::InputTextMultiline(label, my_str->begin(), (size_t)my_str->size(), size, flags | ImGuiInputTextFlags_CallbackResize, Funcs::MyResizeCallback, (void*)my_str);
+                }
+            };
+
+            // For this demo we are using ImVector as a string container.
+            // Note that because we need to store a terminating zero character, our size/capacity are 1 more
+            // than usually reported by a typical string class.
+            static ImVector my_str;
+            if (my_str.empty())
+                my_str.push_back(0);
+            Funcs::MyInputTextMultiline("##MyStr", &my_str, ImVec2(-FLT_MIN, ImGui::GetTextLineHeight() * 16));
+            ImGui::Text("Data: %p\nSize: %d\nCapacity: %d", (void*)my_str.begin(), my_str.size(), my_str.capacity());
+            ImGui::TreePop();
+        }
+
+        IMGUI_DEMO_MARKER("Widgets/Text Input/Eliding, Alignment");
+        if (ImGui::TreeNode("Eliding, Alignment"))
+        {
+            static char buf1[128] = "/path/to/some/folder/with/long/filename.cpp";
+            static ImGuiInputTextFlags flags = ImGuiInputTextFlags_ElideLeft;
+            ImGui::CheckboxFlags("ImGuiInputTextFlags_ElideLeft", &flags, ImGuiInputTextFlags_ElideLeft);
+            ImGui::InputText("Path", buf1, IM_ARRAYSIZE(buf1), flags);
+            ImGui::TreePop();
+        }
+
+        IMGUI_DEMO_MARKER("Widgets/Text Input/Miscellaneous");
+        if (ImGui::TreeNode("Miscellaneous"))
+        {
+            static char buf1[16];
+            static ImGuiInputTextFlags flags = ImGuiInputTextFlags_EscapeClearsAll;
+            ImGui::CheckboxFlags("ImGuiInputTextFlags_EscapeClearsAll", &flags, ImGuiInputTextFlags_EscapeClearsAll);
+            ImGui::CheckboxFlags("ImGuiInputTextFlags_ReadOnly", &flags, ImGuiInputTextFlags_ReadOnly);
+            ImGui::CheckboxFlags("ImGuiInputTextFlags_NoUndoRedo", &flags, ImGuiInputTextFlags_NoUndoRedo);
+            ImGui::InputText("Hello", buf1, IM_ARRAYSIZE(buf1), flags);
+            ImGui::TreePop();
+        }
+
+        ImGui::TreePop();
+    }
+
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] DemoWindowWidgetsTooltips()
+//-----------------------------------------------------------------------------
+
+static void DemoWindowWidgetsTooltips()
+{
+    IMGUI_DEMO_MARKER("Widgets/Tooltips");
+    if (ImGui::TreeNode("Tooltips"))
+    {
+        // Tooltips are windows following the mouse. They do not take focus away.
+        ImGui::SeparatorText("General");
+
+        // Typical use cases:
+        // - Short-form (text only):      SetItemTooltip("Hello");
+        // - Short-form (any contents):   if (BeginItemTooltip()) { Text("Hello"); EndTooltip(); }
+
+        // - Full-form (text only):       if (IsItemHovered(...)) { SetTooltip("Hello"); }
+        // - Full-form (any contents):    if (IsItemHovered(...) && BeginTooltip()) { Text("Hello"); EndTooltip(); }
+
+        HelpMarker(
+            "Tooltip are typically created by using a IsItemHovered() + SetTooltip() sequence.\n\n"
+            "We provide a helper SetItemTooltip() function to perform the two with standards flags.");
+
+        ImVec2 sz = ImVec2(-FLT_MIN, 0.0f);
+
+        ImGui::Button("Basic", sz);
+        ImGui::SetItemTooltip("I am a tooltip");
+
+        ImGui::Button("Fancy", sz);
+        if (ImGui::BeginItemTooltip())
+        {
+            ImGui::Text("I am a fancy tooltip");
+            static float arr[] = { 0.6f, 0.1f, 1.0f, 0.5f, 0.92f, 0.1f, 0.2f };
+            ImGui::PlotLines("Curve", arr, IM_ARRAYSIZE(arr));
+            ImGui::Text("Sin(time) = %f", sinf((float)ImGui::GetTime()));
+            ImGui::EndTooltip();
+        }
+
+        ImGui::SeparatorText("Always On");
+
+        // Showcase NOT relying on a IsItemHovered() to emit a tooltip.
+        // Here the tooltip is always emitted when 'always_on == true'.
+        static int always_on = 0;
+        ImGui::RadioButton("Off", &always_on, 0);
+        ImGui::SameLine();
+        ImGui::RadioButton("Always On (Simple)", &always_on, 1);
+        ImGui::SameLine();
+        ImGui::RadioButton("Always On (Advanced)", &always_on, 2);
+        if (always_on == 1)
+            ImGui::SetTooltip("I am following you around.");
+        else if (always_on == 2 && ImGui::BeginTooltip())
+        {
+            ImGui::ProgressBar(sinf((float)ImGui::GetTime()) * 0.5f + 0.5f, ImVec2(ImGui::GetFontSize() * 25, 0.0f));
+            ImGui::EndTooltip();
+        }
+
+        ImGui::SeparatorText("Custom");
+
+        HelpMarker(
+            "Passing ImGuiHoveredFlags_ForTooltip to IsItemHovered() is the preferred way to standardize "
+            "tooltip activation details across your application. You may however decide to use custom "
+            "flags for a specific tooltip instance.");
+
+        // The following examples are passed for documentation purpose but may not be useful to most users.
+        // Passing ImGuiHoveredFlags_ForTooltip to IsItemHovered() will pull ImGuiHoveredFlags flags values from
+        // 'style.HoverFlagsForTooltipMouse' or 'style.HoverFlagsForTooltipNav' depending on whether mouse or keyboard/gamepad is being used.
+        // With default settings, ImGuiHoveredFlags_ForTooltip is equivalent to ImGuiHoveredFlags_DelayShort + ImGuiHoveredFlags_Stationary.
+        ImGui::Button("Manual", sz);
+        if (ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip))
+            ImGui::SetTooltip("I am a manually emitted tooltip.");
+
+        ImGui::Button("DelayNone", sz);
+        if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNone))
+            ImGui::SetTooltip("I am a tooltip with no delay.");
+
+        ImGui::Button("DelayShort", sz);
+        if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort | ImGuiHoveredFlags_NoSharedDelay))
+            ImGui::SetTooltip("I am a tooltip with a short delay (%0.2f sec).", ImGui::GetStyle().HoverDelayShort);
+
+        ImGui::Button("DelayLong", sz);
+        if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal | ImGuiHoveredFlags_NoSharedDelay))
+            ImGui::SetTooltip("I am a tooltip with a long delay (%0.2f sec).", ImGui::GetStyle().HoverDelayNormal);
+
+        ImGui::Button("Stationary", sz);
+        if (ImGui::IsItemHovered(ImGuiHoveredFlags_Stationary))
+            ImGui::SetTooltip("I am a tooltip requiring mouse to be stationary before activating.");
+
+        // Using ImGuiHoveredFlags_ForTooltip will pull flags from 'style.HoverFlagsForTooltipMouse' or 'style.HoverFlagsForTooltipNav',
+        // which default value include the ImGuiHoveredFlags_AllowWhenDisabled flag.
+        ImGui::BeginDisabled();
+        ImGui::Button("Disabled item", sz);
+        if (ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip))
+            ImGui::SetTooltip("I am a a tooltip for a disabled item.");
+        ImGui::EndDisabled();
+
+        ImGui::TreePop();
+    }
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] DemoWindowWidgetsTreeNodes()
+//-----------------------------------------------------------------------------
+
+static void DemoWindowWidgetsTreeNodes()
+{
+    IMGUI_DEMO_MARKER("Widgets/Tree Nodes");
+    if (ImGui::TreeNode("Tree Nodes"))
+    {
+        IMGUI_DEMO_MARKER("Widgets/Tree Nodes/Basic trees");
+        if (ImGui::TreeNode("Basic trees"))
+        {
+            for (int i = 0; i < 5; i++)
+            {
+                // Use SetNextItemOpen() so set the default state of a node to be open. We could
+                // also use TreeNodeEx() with the ImGuiTreeNodeFlags_DefaultOpen flag to achieve the same thing!
+                if (i == 0)
+                    ImGui::SetNextItemOpen(true, ImGuiCond_Once);
+
+                // Here we use PushID() to generate a unique base ID, and then the "" used as TreeNode id won't conflict.
+                // An alternative to using 'PushID() + TreeNode("", ...)' to generate a unique ID is to use 'TreeNode((void*)(intptr_t)i, ...)',
+                // aka generate a dummy pointer-sized value to be hashed. The demo below uses that technique. Both are fine.
+                ImGui::PushID(i);
+                if (ImGui::TreeNode("", "Child %d", i))
+                {
+                    ImGui::Text("blah blah");
+                    ImGui::SameLine();
+                    if (ImGui::SmallButton("button")) {}
+                    ImGui::TreePop();
+                }
+                ImGui::PopID();
+            }
+            ImGui::TreePop();
+        }
+
+        IMGUI_DEMO_MARKER("Widgets/Tree Nodes/Advanced, with Selectable nodes");
+        if (ImGui::TreeNode("Advanced, with Selectable nodes"))
+        {
+            HelpMarker(
+                "This is a more typical looking tree with selectable nodes.\n"
+                "Click to select, CTRL+Click to toggle, click on arrows or double-click to open.");
+            static ImGuiTreeNodeFlags base_flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_SpanAvailWidth;
+            static bool align_label_with_current_x_position = false;
+            static bool test_drag_and_drop = false;
+            ImGui::CheckboxFlags("ImGuiTreeNodeFlags_OpenOnArrow", &base_flags, ImGuiTreeNodeFlags_OpenOnArrow);
+            ImGui::CheckboxFlags("ImGuiTreeNodeFlags_OpenOnDoubleClick", &base_flags, ImGuiTreeNodeFlags_OpenOnDoubleClick);
+            ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanAvailWidth", &base_flags, ImGuiTreeNodeFlags_SpanAvailWidth); ImGui::SameLine(); HelpMarker("Extend hit area to all available width instead of allowing more items to be laid out after the node.");
+            ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanFullWidth", &base_flags, ImGuiTreeNodeFlags_SpanFullWidth);
+            ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanLabelWidth", &base_flags, ImGuiTreeNodeFlags_SpanLabelWidth); ImGui::SameLine(); HelpMarker("Reduce hit area to the text label and a bit of margin.");
+            ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanAllColumns", &base_flags, ImGuiTreeNodeFlags_SpanAllColumns); ImGui::SameLine(); HelpMarker("For use in Tables only.");
+            ImGui::CheckboxFlags("ImGuiTreeNodeFlags_AllowOverlap", &base_flags, ImGuiTreeNodeFlags_AllowOverlap);
+            ImGui::CheckboxFlags("ImGuiTreeNodeFlags_Framed", &base_flags, ImGuiTreeNodeFlags_Framed); ImGui::SameLine(); HelpMarker("Draw frame with background (e.g. for CollapsingHeader)");
+            ImGui::CheckboxFlags("ImGuiTreeNodeFlags_NavLeftJumpsBackHere", &base_flags, ImGuiTreeNodeFlags_NavLeftJumpsBackHere);
+            ImGui::Checkbox("Align label with current X position", &align_label_with_current_x_position);
+            ImGui::Checkbox("Test tree node as drag source", &test_drag_and_drop);
+            ImGui::Text("Hello!");
+            if (align_label_with_current_x_position)
+                ImGui::Unindent(ImGui::GetTreeNodeToLabelSpacing());
+
+            // 'selection_mask' is dumb representation of what may be user-side selection state.
+            //  You may retain selection state inside or outside your objects in whatever format you see fit.
+            // 'node_clicked' is temporary storage of what node we have clicked to process selection at the end
+            /// of the loop. May be a pointer to your own node type, etc.
+            static int selection_mask = (1 << 2);
+            int node_clicked = -1;
+            for (int i = 0; i < 6; i++)
+            {
+                // Disable the default "open on single-click behavior" + set Selected flag according to our selection.
+                // To alter selection we use IsItemClicked() && !IsItemToggledOpen(), so clicking on an arrow doesn't alter selection.
+                ImGuiTreeNodeFlags node_flags = base_flags;
+                const bool is_selected = (selection_mask & (1 << i)) != 0;
+                if (is_selected)
+                    node_flags |= ImGuiTreeNodeFlags_Selected;
+                if (i < 3)
+                {
+                    // Items 0..2 are Tree Node
+                    bool node_open = ImGui::TreeNodeEx((void*)(intptr_t)i, node_flags, "Selectable Node %d", i);
+                    if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen())
+                        node_clicked = i;
+                    if (test_drag_and_drop && ImGui::BeginDragDropSource())
+                    {
+                        ImGui::SetDragDropPayload("_TREENODE", NULL, 0);
+                        ImGui::Text("This is a drag and drop source");
+                        ImGui::EndDragDropSource();
+                    }
+                    if (i == 2 && (base_flags & ImGuiTreeNodeFlags_SpanLabelWidth))
+                    {
+                        // Item 2 has an additional inline button to help demonstrate SpanLabelWidth.
+                        ImGui::SameLine();
+                        if (ImGui::SmallButton("button")) {}
+                    }
+                    if (node_open)
+                    {
+                        ImGui::BulletText("Blah blah\nBlah Blah");
+                        ImGui::SameLine();
+                        ImGui::SmallButton("Button");
+                        ImGui::TreePop();
+                    }
+                }
+                else
+                {
+                    // Items 3..5 are Tree Leaves
+                    // The only reason we use TreeNode at all is to allow selection of the leaf. Otherwise we can
+                    // use BulletText() or advance the cursor by GetTreeNodeToLabelSpacing() and call Text().
+                    node_flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; // ImGuiTreeNodeFlags_Bullet
+                    ImGui::TreeNodeEx((void*)(intptr_t)i, node_flags, "Selectable Leaf %d", i);
+                    if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen())
+                        node_clicked = i;
+                    if (test_drag_and_drop && ImGui::BeginDragDropSource())
+                    {
+                        ImGui::SetDragDropPayload("_TREENODE", NULL, 0);
+                        ImGui::Text("This is a drag and drop source");
+                        ImGui::EndDragDropSource();
+                    }
+                }
+            }
+            if (node_clicked != -1)
+            {
+                // Update selection state
+                // (process outside of tree loop to avoid visual inconsistencies during the clicking frame)
+                if (ImGui::GetIO().KeyCtrl)
+                    selection_mask ^= (1 << node_clicked);          // CTRL+click to toggle
+                else //if (!(selection_mask & (1 << node_clicked))) // Depending on selection behavior you want, may want to preserve selection when clicking on item that is part of the selection
+                    selection_mask = (1 << node_clicked);           // Click to single-select
+            }
+            if (align_label_with_current_x_position)
+                ImGui::Indent(ImGui::GetTreeNodeToLabelSpacing());
+            ImGui::TreePop();
+        }
+        ImGui::TreePop();
+    }
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] DemoWindowWidgetsVerticalSliders()
+//-----------------------------------------------------------------------------
+
+static void DemoWindowWidgetsVerticalSliders()
+{
+    IMGUI_DEMO_MARKER("Widgets/Vertical Sliders");
+    if (ImGui::TreeNode("Vertical Sliders"))
+    {
+        const float spacing = 4;
+        ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(spacing, spacing));
+
+        static int int_value = 0;
+        ImGui::VSliderInt("##int", ImVec2(18, 160), &int_value, 0, 5);
+        ImGui::SameLine();
+
+        static float values[7] = { 0.0f, 0.60f, 0.35f, 0.9f, 0.70f, 0.20f, 0.0f };
+        ImGui::PushID("set1");
+        for (int i = 0; i < 7; i++)
+        {
+            if (i > 0) ImGui::SameLine();
+            ImGui::PushID(i);
+            ImGui::PushStyleColor(ImGuiCol_FrameBg, (ImVec4)ImColor::HSV(i / 7.0f, 0.5f, 0.5f));
+            ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, (ImVec4)ImColor::HSV(i / 7.0f, 0.6f, 0.5f));
+            ImGui::PushStyleColor(ImGuiCol_FrameBgActive, (ImVec4)ImColor::HSV(i / 7.0f, 0.7f, 0.5f));
+            ImGui::PushStyleColor(ImGuiCol_SliderGrab, (ImVec4)ImColor::HSV(i / 7.0f, 0.9f, 0.9f));
+            ImGui::VSliderFloat("##v", ImVec2(18, 160), &values[i], 0.0f, 1.0f, "");
+            if (ImGui::IsItemActive() || ImGui::IsItemHovered())
+                ImGui::SetTooltip("%.3f", values[i]);
+            ImGui::PopStyleColor(4);
+            ImGui::PopID();
+        }
+        ImGui::PopID();
+
+        ImGui::SameLine();
+        ImGui::PushID("set2");
+        static float values2[4] = { 0.20f, 0.80f, 0.40f, 0.25f };
+        const int rows = 3;
+        const ImVec2 small_slider_size(18, (float)(int)((160.0f - (rows - 1) * spacing) / rows));
+        for (int nx = 0; nx < 4; nx++)
+        {
+            if (nx > 0) ImGui::SameLine();
+            ImGui::BeginGroup();
+            for (int ny = 0; ny < rows; ny++)
+            {
+                ImGui::PushID(nx * rows + ny);
+                ImGui::VSliderFloat("##v", small_slider_size, &values2[nx], 0.0f, 1.0f, "");
+                if (ImGui::IsItemActive() || ImGui::IsItemHovered())
+                    ImGui::SetTooltip("%.3f", values2[nx]);
+                ImGui::PopID();
+            }
+            ImGui::EndGroup();
+        }
+        ImGui::PopID();
+
+        ImGui::SameLine();
+        ImGui::PushID("set3");
+        for (int i = 0; i < 4; i++)
+        {
+            if (i > 0) ImGui::SameLine();
+            ImGui::PushID(i);
+            ImGui::PushStyleVar(ImGuiStyleVar_GrabMinSize, 40);
+            ImGui::VSliderFloat("##v", ImVec2(40, 160), &values[i], 0.0f, 1.0f, "%.2f\nsec");
+            ImGui::PopStyleVar();
+            ImGui::PopID();
+        }
+        ImGui::PopID();
+        ImGui::PopStyleVar();
+        ImGui::TreePop();
+    }
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] DemoWindowWidgets()
+//-----------------------------------------------------------------------------
+
+static void DemoWindowWidgets(ImGuiDemoWindowData* demo_data)
+{
+    IMGUI_DEMO_MARKER("Widgets");
+    //ImGui::SetNextItemOpen(true, ImGuiCond_Once);
+    if (!ImGui::CollapsingHeader("Widgets"))
+        return;
+
+    const bool disable_all = demo_data->DisableSections; // The Checkbox for that is inside the "Disabled" section at the bottom
+    if (disable_all)
+        ImGui::BeginDisabled();
+
+    DemoWindowWidgetsBasic();
+    DemoWindowWidgetsBullets();
+    DemoWindowWidgetsCollapsingHeaders();
+    DemoWindowWidgetsComboBoxes();
+    DemoWindowWidgetsColorAndPickers();
+    DemoWindowWidgetsDataTypes();
+
+    if (disable_all)
+        ImGui::EndDisabled();
+    DemoWindowWidgetsDisableBlocks(demo_data);
+    if (disable_all)
+        ImGui::BeginDisabled();
+
+    DemoWindowWidgetsDragAndDrop();
+    DemoWindowWidgetsDragsAndSliders();
+    DemoWindowWidgetsImages();
+    DemoWindowWidgetsListBoxes();
+    DemoWindowWidgetsMultiComponents();
+    DemoWindowWidgetsPlotting();
+    DemoWindowWidgetsProgressBars();
+    DemoWindowWidgetsQueryingStatuses();
+    DemoWindowWidgetsSelectables();
+    DemoWindowWidgetsSelectionAndMultiSelect(demo_data);
+    DemoWindowWidgetsTabs();
+    DemoWindowWidgetsText();
+    DemoWindowWidgetsTextFilter();
+    DemoWindowWidgetsTextInput();
+    DemoWindowWidgetsTooltips();
+    DemoWindowWidgetsTreeNodes();
+    DemoWindowWidgetsVerticalSliders();
+
+    if (disable_all)
+        ImGui::EndDisabled();
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] DemoWindowLayout()
+//-----------------------------------------------------------------------------
+
+static void DemoWindowLayout()
 {
     IMGUI_DEMO_MARKER("Layout");
     if (!ImGui::CollapsingHeader("Layout & Scrolling"))
@@ -4800,10 +5033,10 @@ static void ShowDemoWindowLayout()
 }
 
 //-----------------------------------------------------------------------------
-// [SECTION] ShowDemoWindowPopups()
+// [SECTION] DemoWindowPopups()
 //-----------------------------------------------------------------------------
 
-static void ShowDemoWindowPopups()
+static void DemoWindowPopups()
 {
     IMGUI_DEMO_MARKER("Popups");
     if (!ImGui::CollapsingHeader("Popups & Modal windows"))
@@ -5264,10 +5497,10 @@ static void ShowTableColumnsStatusFlags(ImGuiTableColumnFlags flags)
 }
 
 //-----------------------------------------------------------------------------
-// [SECTION] ShowDemoWindowTables()
+// [SECTION] DemoWindowTables()
 //-----------------------------------------------------------------------------
 
-static void ShowDemoWindowTables()
+static void DemoWindowTables()
 {
     //ImGui::SetNextItemOpen(true, ImGuiCond_Once);
     IMGUI_DEMO_MARKER("Tables");
@@ -7164,7 +7397,7 @@ static void ShowDemoWindowTables()
 
     ImGui::PopID();
 
-    ShowDemoWindowColumns();
+    DemoWindowColumns();
 
     if (disable_indent)
         ImGui::PopStyleVar();
@@ -7172,7 +7405,7 @@ static void ShowDemoWindowTables()
 
 // Demonstrate old/legacy Columns API!
 // [2020: Columns are under-featured and not maintained. Prefer using the more flexible and powerful BeginTable() API!]
-static void ShowDemoWindowColumns()
+static void DemoWindowColumns()
 {
     IMGUI_DEMO_MARKER("Columns (legacy API)");
     bool open = ImGui::TreeNode("Legacy Columns API");
@@ -7379,10 +7612,10 @@ static void ShowDemoWindowColumns()
 }
 
 //-----------------------------------------------------------------------------
-// [SECTION] ShowDemoWindowInputs()
+// [SECTION] DemoWindowInputs()
 //-----------------------------------------------------------------------------
 
-static void ShowDemoWindowInputs()
+static void DemoWindowInputs()
 {
     IMGUI_DEMO_MARKER("Inputs & Focus");
     if (ImGui::CollapsingHeader("Inputs & Focus"))
@@ -7571,7 +7804,7 @@ static void ShowDemoWindowInputs()
         IMGUI_DEMO_MARKER("Inputs & Focus/Mouse Cursors");
         if (ImGui::TreeNode("Mouse Cursors"))
         {
-            const char* mouse_cursors_names[] = { "Arrow", "TextInput", "ResizeAll", "ResizeNS", "ResizeEW", "ResizeNESW", "ResizeNWSE", "Hand", "NotAllowed" };
+            const char* mouse_cursors_names[] = { "Arrow", "TextInput", "ResizeAll", "ResizeNS", "ResizeEW", "ResizeNESW", "ResizeNWSE", "Hand", "Wait", "Progress", "NotAllowed" };
             IM_ASSERT(IM_ARRAYSIZE(mouse_cursors_names) == ImGuiMouseCursor_COUNT);
 
             ImGuiMouseCursor current = ImGui::GetMouseCursor();
@@ -7749,6 +7982,9 @@ void ImGui::ShowAboutWindow(bool* p_open)
 #ifdef IMGUI_DISABLE_WIN32_FUNCTIONS
         ImGui::Text("define: IMGUI_DISABLE_WIN32_FUNCTIONS");
 #endif
+#ifdef IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS
+        ImGui::Text("define: IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS");
+#endif
 #ifdef IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS
         ImGui::Text("define: IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS");
 #endif
@@ -7966,10 +8202,6 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref)
             ImGui::SliderFloat("ChildBorderSize", &style.ChildBorderSize, 0.0f, 1.0f, "%.0f");
             ImGui::SliderFloat("PopupBorderSize", &style.PopupBorderSize, 0.0f, 1.0f, "%.0f");
             ImGui::SliderFloat("FrameBorderSize", &style.FrameBorderSize, 0.0f, 1.0f, "%.0f");
-            ImGui::SliderFloat("TabBorderSize", &style.TabBorderSize, 0.0f, 1.0f, "%.0f");
-            ImGui::SliderFloat("TabBarBorderSize", &style.TabBarBorderSize, 0.0f, 2.0f, "%.0f");
-            ImGui::SliderFloat("TabBarOverlineSize", &style.TabBarOverlineSize, 0.0f, 3.0f, "%.0f");
-            ImGui::SameLine(); HelpMarker("Overline is only drawn over the selected tab when ImGuiTabBarFlags_DrawSelectedOverline is set.");
 
             ImGui::SeparatorText("Rounding");
             ImGui::SliderFloat("WindowRounding", &style.WindowRounding, 0.0f, 12.0f, "%.0f");
@@ -7978,6 +8210,14 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref)
             ImGui::SliderFloat("PopupRounding", &style.PopupRounding, 0.0f, 12.0f, "%.0f");
             ImGui::SliderFloat("ScrollbarRounding", &style.ScrollbarRounding, 0.0f, 12.0f, "%.0f");
             ImGui::SliderFloat("GrabRounding", &style.GrabRounding, 0.0f, 12.0f, "%.0f");
+
+            ImGui::SeparatorText("Tabs");
+            ImGui::SliderFloat("TabBorderSize", &style.TabBorderSize, 0.0f, 1.0f, "%.0f");
+            ImGui::SliderFloat("TabBarBorderSize", &style.TabBarBorderSize, 0.0f, 2.0f, "%.0f");
+            ImGui::SliderFloat("TabBarOverlineSize", &style.TabBarOverlineSize, 0.0f, 3.0f, "%.0f");
+            ImGui::SameLine(); HelpMarker("Overline is only drawn over the selected tab when ImGuiTabBarFlags_DrawSelectedOverline is set.");
+            ImGui::DragFloat("TabCloseButtonMinWidthSelected", &style.TabCloseButtonMinWidthSelected, 0.1f, -1.0f, 100.0f, (style.TabCloseButtonMinWidthSelected < 0.0f) ? "%.0f (Always)" : "%.0f");
+            ImGui::DragFloat("TabCloseButtonMinWidthUnselected", &style.TabCloseButtonMinWidthUnselected, 0.1f, -1.0f, 100.0f, (style.TabCloseButtonMinWidthUnselected < 0.0f) ? "%.0f (Always)" : "%.0f");
             ImGui::SliderFloat("TabRounding", &style.TabRounding, 0.0f, 12.0f, "%.0f");
 
             ImGui::SeparatorText("Tables");
@@ -7985,11 +8225,14 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref)
             ImGui::SliderAngle("TableAngledHeadersAngle", &style.TableAngledHeadersAngle, -50.0f, +50.0f);
             ImGui::SliderFloat2("TableAngledHeadersTextAlign", (float*)&style.TableAngledHeadersTextAlign, 0.0f, 1.0f, "%.2f");
 
-            ImGui::SeparatorText("Widgets");
+            ImGui::SeparatorText("Windows");
             ImGui::SliderFloat2("WindowTitleAlign", (float*)&style.WindowTitleAlign, 0.0f, 1.0f, "%.2f");
+            ImGui::SliderFloat("WindowBorderHoverPadding", &style.WindowBorderHoverPadding, 1.0f, 20.0f, "%.0f");
             int window_menu_button_position = style.WindowMenuButtonPosition + 1;
             if (ImGui::Combo("WindowMenuButtonPosition", (int*)&window_menu_button_position, "None\0Left\0Right\0"))
                 style.WindowMenuButtonPosition = (ImGuiDir)(window_menu_button_position - 1);
+
+            ImGui::SeparatorText("Widgets");
             ImGui::Combo("ColorButtonPosition", (int*)&style.ColorButtonPosition, "Left\0Right\0");
             ImGui::SliderFloat2("ButtonTextAlign", (float*)&style.ButtonTextAlign, 0.0f, 1.0f, "%.2f");
             ImGui::SameLine(); HelpMarker("Alignment applies when a button is larger than its text content.");
@@ -7999,6 +8242,7 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref)
             ImGui::SliderFloat2("SeparatorTextAlign", (float*)&style.SeparatorTextAlign, 0.0f, 1.0f, "%.2f");
             ImGui::SliderFloat2("SeparatorTextPadding", (float*)&style.SeparatorTextPadding, 0.0f, 40.0f, "%.0f");
             ImGui::SliderFloat("LogSliderDeadzone", &style.LogSliderDeadzone, 0.0f, 12.0f, "%.0f");
+            ImGui::SliderFloat("ImageBorderSize", &style.ImageBorderSize, 0.0f, 1.0f, "%.0f");
 
             ImGui::SeparatorText("Tooltips");
             for (int n = 0; n < 2; n++)
@@ -8173,7 +8417,7 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref)
                 ImGui::EndTooltip();
             }
             ImGui::SameLine();
-            HelpMarker("When drawing circle primitives with \"num_segments == 0\" tesselation will be calculated automatically.");
+            HelpMarker("When drawing circle primitives with \"num_segments == 0\" tessellation will be calculated automatically.");
 
             ImGui::DragFloat("Global Alpha", &style.Alpha, 0.005f, 0.20f, 1.0f, "%.2f"); // Not exposing zero here so user doesn't "lose" the UI (zero alpha clips all widgets). But application code could have a toggle to switch between zero and non-zero.
             ImGui::DragFloat("Disabled Alpha", &style.DisabledAlpha, 0.005f, 0.0f, 1.0f, "%.2f"); ImGui::SameLine(); HelpMarker("Additional alpha multiplier for disabled items (multiply over current value of Alpha).");
@@ -8204,12 +8448,12 @@ void ImGui::ShowUserGuide()
     ImGui::BulletText("CTRL+Tab to select a window.");
     if (io.FontAllowUserScaling)
         ImGui::BulletText("CTRL+Mouse Wheel to zoom window contents.");
-    ImGui::BulletText("While inputing text:\n");
+    ImGui::BulletText("While inputting text:\n");
     ImGui::Indent();
     ImGui::BulletText("CTRL+Left/Right to word jump.");
     ImGui::BulletText("CTRL+A or double-click to select all.");
     ImGui::BulletText("CTRL+X/C/V to use clipboard cut/copy/paste.");
-    ImGui::BulletText("CTRL+Z,CTRL+Y to undo/redo.");
+    ImGui::BulletText("CTRL+Z to undo, CTRL+Y/CTRL+SHIFT+Z to redo.");
     ImGui::BulletText("ESCAPE to revert.");
     ImGui::Unindent();
     ImGui::BulletText("With keyboard navigation enabled:");
@@ -8245,7 +8489,7 @@ static void ShowExampleAppMainMenuBar()
         if (ImGui::BeginMenu("Edit"))
         {
             if (ImGui::MenuItem("Undo", "CTRL+Z")) {}
-            if (ImGui::MenuItem("Redo", "CTRL+Y", false, false)) {}  // Disabled item
+            if (ImGui::MenuItem("Redo", "CTRL+Y", false, false)) {} // Disabled item
             ImGui::Separator();
             if (ImGui::MenuItem("Cut", "CTRL+X")) {}
             if (ImGui::MenuItem("Copy", "CTRL+C")) {}
@@ -8635,7 +8879,7 @@ struct ExampleAppConsole
                 else
                 {
                     // Multiple matches. Complete as much as we can..
-                    // So inputing "C"+Tab will complete to "CL" then display "CLEAR" and "CLASSIFY" as matches.
+                    // So inputting "C"+Tab will complete to "CL" then display "CLEAR" and "CLASSIFY" as matches.
                     int match_len = (int)(word_end - word_start);
                     for (;;)
                     {
@@ -9493,7 +9737,7 @@ static void ShowExampleAppCustomRendering(bool* p_open)
                 float th = (n == 0) ? 1.0f : thickness;
                 draw_list->AddNgon(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, col, ngon_sides, th);                 x += sz + spacing;  // N-gon
                 draw_list->AddCircle(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, col, circle_segments, th);          x += sz + spacing;  // Circle
-                draw_list->AddEllipse(ImVec2(x + sz*0.5f, y + sz*0.5f), ImVec2(sz*0.5f, sz*0.3f), col, -0.3f, circle_segments, th); x += sz + spacing;	// Ellipse
+                draw_list->AddEllipse(ImVec2(x + sz*0.5f, y + sz*0.5f), ImVec2(sz*0.5f, sz*0.3f), col, -0.3f, circle_segments, th); x += sz + spacing;  // Ellipse
                 draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 0.0f, ImDrawFlags_None, th);          x += sz + spacing;  // Square
                 draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, ImDrawFlags_None, th);      x += sz + spacing;  // Square with all rounded corners
                 draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, corners_tl_br, th);         x += sz + spacing;  // Square with two rounded corners
diff --git a/3rdparty/imgui/src/imgui_draw.cpp b/3rdparty/imgui/src/imgui_draw.cpp
index 442ee0ae39..5018be307a 100644
--- a/3rdparty/imgui/src/imgui_draw.cpp
+++ b/3rdparty/imgui/src/imgui_draw.cpp
@@ -1,4 +1,4 @@
-// dear imgui, v1.91.8
+// dear imgui, v1.91.9b
 // (drawing and font code)
 
 /*
@@ -68,6 +68,7 @@ Index of this file:
 #pragma clang diagnostic ignored "-Wunsafe-buffer-usage"            // warning: 'xxx' is an unsafe pointer used for buffer access
 #pragma clang diagnostic ignored "-Wnontrivial-memaccess"           // warning: first argument in call to 'memset' is a pointer to non-trivially copyable type
 #pragma clang diagnostic ignored "-Wcast-qual"                      // warning: cast from 'const xxxx *' to 'xxx *' drops const qualifier
+#pragma clang diagnostic ignored "-Wswitch-default"                 // warning: 'switch' missing 'default' label
 #elif defined(__GNUC__)
 #pragma GCC diagnostic ignored "-Wpragmas"                          // warning: unknown option after '#pragma GCC diagnostic' kind
 #pragma GCC diagnostic ignored "-Wunused-function"                  // warning: 'xxxx' defined but not used
@@ -143,6 +144,7 @@ namespace IMGUI_STB_NAMESPACE
 #define STBTT_fabs(x)       ImFabs(x)
 #define STBTT_ifloor(x)     ((int)ImFloor(x))
 #define STBTT_iceil(x)      ((int)ImCeil(x))
+#define STBTT_strlen(x)     ImStrlen(x)
 #define STBTT_STATIC
 #define STB_TRUETYPE_IMPLEMENTATION
 #else
@@ -374,6 +376,7 @@ void ImGui::StyleColorsLight(ImGuiStyle* dst)
 ImDrawListSharedData::ImDrawListSharedData()
 {
     memset(this, 0, sizeof(*this));
+    InitialFringeScale = 1.0f;
     for (int i = 0; i < IM_ARRAYSIZE(ArcFastVtx); i++)
     {
         const float a = ((float)i * 2 * IM_PI) / (float)IM_ARRAYSIZE(ArcFastVtx);
@@ -433,7 +436,7 @@ void ImDrawList::_ResetForNewFrame()
     _Path.resize(0);
     _Splitter.Clear();
     CmdBuffer.push_back(ImDrawCmd());
-    _FringeScale = 1.0f;
+    _FringeScale = _Data->InitialFringeScale;
 }
 
 void ImDrawList::_ClearFreeMemory()
@@ -2401,13 +2404,13 @@ ImFontConfig::ImFontConfig()
 // - ImFontAtlas::AddCustomRectRegular()
 // - ImFontAtlas::AddCustomRectFontGlyph()
 // - ImFontAtlas::CalcCustomRectUV()
-// - ImFontAtlas::GetMouseCursorTexData()
+// - ImFontAtlasGetMouseCursorTexData()
 // - ImFontAtlas::Build()
 // - ImFontAtlasBuildMultiplyCalcLookupTable()
 // - ImFontAtlasBuildMultiplyRectAlpha8()
 // - ImFontAtlasBuildWithStbTruetype()
 // - ImFontAtlasGetBuilderForStbTruetype()
-// - ImFontAtlasUpdateConfigDataPointers()
+// - ImFontAtlasUpdateSourcesPointers()
 // - ImFontAtlasBuildSetupFont()
 // - ImFontAtlasBuildPackCustomRects()
 // - ImFontAtlasBuildRender8bppRectFromString()
@@ -2465,6 +2468,8 @@ static const ImVec2 FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[ImGuiMouseCursor_COUNT][3
     { ImVec2(73,0), ImVec2(17,17), ImVec2( 8, 8) }, // ImGuiMouseCursor_ResizeNESW
     { ImVec2(55,0), ImVec2(17,17), ImVec2( 8, 8) }, // ImGuiMouseCursor_ResizeNWSE
     { ImVec2(91,0), ImVec2(17,22), ImVec2( 5, 0) }, // ImGuiMouseCursor_Hand
+    { ImVec2(0,3),  ImVec2(12,19), ImVec2(0, 0) },  // ImGuiMouseCursor_Wait       // Arrow + custom code in ImGui::RenderMouseCursor()
+    { ImVec2(0,3),  ImVec2(12,19), ImVec2(0, 0) },  // ImGuiMouseCursor_Progress   // Arrow + custom code in ImGui::RenderMouseCursor()
     { ImVec2(109,0),ImVec2(13,15), ImVec2( 6, 7) }, // ImGuiMouseCursor_NotAllowed
 };
 
@@ -2484,7 +2489,7 @@ ImFontAtlas::~ImFontAtlas()
 void    ImFontAtlas::ClearInputData()
 {
     IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!");
-    for (ImFontConfig& font_cfg : ConfigData)
+    for (ImFontConfig& font_cfg : Sources)
         if (font_cfg.FontData && font_cfg.FontDataOwnedByAtlas)
         {
             IM_FREE(font_cfg.FontData);
@@ -2493,12 +2498,12 @@ void    ImFontAtlas::ClearInputData()
 
     // When clearing this we lose access to the font name and other information used to build the font.
     for (ImFont* font : Fonts)
-        if (font->ConfigData >= ConfigData.Data && font->ConfigData < ConfigData.Data + ConfigData.Size)
+        if (font->Sources >= Sources.Data && font->Sources < Sources.Data + Sources.Size)
         {
-            font->ConfigData = NULL;
-            font->ConfigDataCount = 0;
+            font->Sources = NULL;
+            font->SourcesCount = 0;
         }
-    ConfigData.clear();
+    Sources.clear();
     CustomRects.clear();
     PackIdMouseCursors = PackIdLines = -1;
     // Important: we leave TexReady untouched
@@ -2581,8 +2586,8 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg)
     else
         IM_ASSERT(Fonts.Size > 0 && "Cannot use MergeMode for the first font"); // When using MergeMode make sure that a font has already been added before. You can use ImGui::GetIO().Fonts->AddFontDefault() to add the default imgui font.
 
-    ConfigData.push_back(*font_cfg);
-    ImFontConfig& new_font_cfg = ConfigData.back();
+    Sources.push_back(*font_cfg);
+    ImFontConfig& new_font_cfg = Sources.back();
     if (new_font_cfg.DstFont == NULL)
         new_font_cfg.DstFont = Fonts.back();
     if (!new_font_cfg.FontDataOwnedByAtlas)
@@ -2598,8 +2603,8 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg)
     // - We may support it better later and remove this rounding.
     new_font_cfg.SizePixels = ImTrunc(new_font_cfg.SizePixels);
 
-    // Pointers to ConfigData and BuilderData are otherwise dangling
-    ImFontAtlasUpdateConfigDataPointers(this);
+    // Pointers to Sources data are otherwise dangling
+    ImFontAtlasUpdateSourcesPointers(this);
 
     // Invalidate texture
     TexReady = false;
@@ -2669,7 +2674,7 @@ ImFont* ImFontAtlas::AddFontFromFileTTF(const char* filename, float size_pixels,
     {
         // Store a short copy of filename into into the font name for convenience
         const char* p;
-        for (p = filename + strlen(filename); p > filename && p[-1] != '/' && p[-1] != '\\'; p--) {}
+        for (p = filename + ImStrlen(filename); p > filename && p[-1] != '/' && p[-1] != '\\'; p--) {}
         ImFormatString(font_cfg.Name, IM_ARRAYSIZE(font_cfg.Name), "%s, %.0fpx", p, size_pixels);
     }
     return AddFontFromMemoryTTF(data, (int)data_size, size_pixels, &font_cfg, glyph_ranges);
@@ -2704,7 +2709,7 @@ ImFont* ImFontAtlas::AddFontFromMemoryCompressedTTF(const void* compressed_ttf_d
 
 ImFont* ImFontAtlas::AddFontFromMemoryCompressedBase85TTF(const char* compressed_ttf_data_base85, float size_pixels, const ImFontConfig* font_cfg, const ImWchar* glyph_ranges)
 {
-    int compressed_ttf_size = (((int)strlen(compressed_ttf_data_base85) + 4) / 5) * 4;
+    int compressed_ttf_size = (((int)ImStrlen(compressed_ttf_data_base85) + 4) / 5) * 4;
     void* compressed_ttf = IM_ALLOC((size_t)compressed_ttf_size);
     Decode85((const unsigned char*)compressed_ttf_data_base85, (unsigned char*)compressed_ttf);
     ImFont* font = AddFontFromMemoryCompressedTTF(compressed_ttf, compressed_ttf_size, size_pixels, font_cfg, glyph_ranges);
@@ -2751,24 +2756,24 @@ void ImFontAtlas::CalcCustomRectUV(const ImFontAtlasCustomRect* rect, ImVec2* ou
     *out_uv_max = ImVec2((float)(rect->X + rect->Width) * TexUvScale.x, (float)(rect->Y + rect->Height) * TexUvScale.y);
 }
 
-bool ImFontAtlas::GetMouseCursorTexData(ImGuiMouseCursor cursor_type, ImVec2* out_offset, ImVec2* out_size, ImVec2 out_uv_border[2], ImVec2 out_uv_fill[2])
+bool ImFontAtlasGetMouseCursorTexData(ImFontAtlas* atlas, ImGuiMouseCursor cursor_type, ImVec2* out_offset, ImVec2* out_size, ImVec2 out_uv_border[2], ImVec2 out_uv_fill[2])
 {
     if (cursor_type <= ImGuiMouseCursor_None || cursor_type >= ImGuiMouseCursor_COUNT)
         return false;
-    if (Flags & ImFontAtlasFlags_NoMouseCursors)
+    if (atlas->Flags & ImFontAtlasFlags_NoMouseCursors)
         return false;
 
-    IM_ASSERT(PackIdMouseCursors != -1);
-    ImFontAtlasCustomRect* r = GetCustomRectByIndex(PackIdMouseCursors);
+    IM_ASSERT(atlas->PackIdMouseCursors != -1);
+    ImFontAtlasCustomRect* r = atlas->GetCustomRectByIndex(atlas->PackIdMouseCursors);
     ImVec2 pos = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][0] + ImVec2((float)r->X, (float)r->Y);
     ImVec2 size = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][1];
     *out_size = size;
     *out_offset = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][2];
-    out_uv_border[0] = (pos) * TexUvScale;
-    out_uv_border[1] = (pos + size) * TexUvScale;
+    out_uv_border[0] = (pos) * atlas->TexUvScale;
+    out_uv_border[1] = (pos + size) * atlas->TexUvScale;
     pos.x += FONT_ATLAS_DEFAULT_TEX_DATA_W + 1;
-    out_uv_fill[0] = (pos) * TexUvScale;
-    out_uv_fill[1] = (pos + size) * TexUvScale;
+    out_uv_fill[0] = (pos) * atlas->TexUvScale;
+    out_uv_fill[1] = (pos + size) * atlas->TexUvScale;
     return true;
 }
 
@@ -2777,7 +2782,7 @@ bool    ImFontAtlas::Build()
     IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!");
 
     // Default font is none are specified
-    if (ConfigData.Size == 0)
+    if (Sources.Size == 0)
         AddFontDefault();
 
     // Select builder
@@ -2819,11 +2824,11 @@ void    ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsig
             *data = table[*data];
 }
 
-void ImFontAtlasBuildGetOversampleFactors(const ImFontConfig* cfg, int* out_oversample_h, int* out_oversample_v)
+void ImFontAtlasBuildGetOversampleFactors(const ImFontConfig* src, int* out_oversample_h, int* out_oversample_v)
 {
     // Automatically disable horizontal oversampling over size 36
-    *out_oversample_h = (cfg->OversampleH != 0) ? cfg->OversampleH : (cfg->SizePixels * cfg->RasterizerDensity > 36.0f || cfg->PixelSnapH) ? 1 : 2;
-    *out_oversample_v = (cfg->OversampleV != 0) ? cfg->OversampleV : 1;
+    *out_oversample_h = (src->OversampleH != 0) ? src->OversampleH : (src->SizePixels * src->RasterizerDensity > 36.0f || src->PixelSnapH) ? 1 : 2;
+    *out_oversample_v = (src->OversampleV != 0) ? src->OversampleV : 1;
 }
 
 #ifdef IMGUI_ENABLE_STB_TRUETYPE
@@ -2866,7 +2871,7 @@ static void UnpackBitVectorToFlatIndexList(const ImBitVector* in, ImVector*
 
 static bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas)
 {
-    IM_ASSERT(atlas->ConfigData.Size > 0);
+    IM_ASSERT(atlas->Sources.Size > 0);
 
     ImFontAtlasBuildInit(atlas);
 
@@ -2880,32 +2885,32 @@ static bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas)
     // Temporary storage for building
     ImVector src_tmp_array;
     ImVector dst_tmp_array;
-    src_tmp_array.resize(atlas->ConfigData.Size);
+    src_tmp_array.resize(atlas->Sources.Size);
     dst_tmp_array.resize(atlas->Fonts.Size);
     memset(src_tmp_array.Data, 0, (size_t)src_tmp_array.size_in_bytes());
     memset(dst_tmp_array.Data, 0, (size_t)dst_tmp_array.size_in_bytes());
 
     // 1. Initialize font loading structure, check font data validity
-    for (int src_i = 0; src_i < atlas->ConfigData.Size; src_i++)
+    for (int src_i = 0; src_i < atlas->Sources.Size; src_i++)
     {
         ImFontBuildSrcData& src_tmp = src_tmp_array[src_i];
-        ImFontConfig& cfg = atlas->ConfigData[src_i];
-        IM_ASSERT(cfg.DstFont && (!cfg.DstFont->IsLoaded() || cfg.DstFont->ContainerAtlas == atlas));
+        ImFontConfig& src = atlas->Sources[src_i];
+        IM_ASSERT(src.DstFont && (!src.DstFont->IsLoaded() || src.DstFont->ContainerAtlas == atlas));
 
-        // Find index from cfg.DstFont (we allow the user to set cfg.DstFont. Also it makes casual debugging nicer than when storing indices)
+        // Find index from src.DstFont (we allow the user to set cfg.DstFont. Also it makes casual debugging nicer than when storing indices)
         src_tmp.DstIndex = -1;
         for (int output_i = 0; output_i < atlas->Fonts.Size && src_tmp.DstIndex == -1; output_i++)
-            if (cfg.DstFont == atlas->Fonts[output_i])
+            if (src.DstFont == atlas->Fonts[output_i])
                 src_tmp.DstIndex = output_i;
         if (src_tmp.DstIndex == -1)
         {
-            IM_ASSERT(src_tmp.DstIndex != -1); // cfg.DstFont not pointing within atlas->Fonts[] array?
+            IM_ASSERT(src_tmp.DstIndex != -1); // src.DstFont not pointing within atlas->Fonts[] array?
             return false;
         }
         // Initialize helper structure for font loading and verify that the TTF/OTF data is correct
-        const int font_offset = stbtt_GetFontOffsetForIndex((unsigned char*)cfg.FontData, cfg.FontNo);
+        const int font_offset = stbtt_GetFontOffsetForIndex((unsigned char*)src.FontData, src.FontNo);
         IM_ASSERT(font_offset >= 0 && "FontData is incorrect, or FontNo cannot be found.");
-        if (!stbtt_InitFont(&src_tmp.FontInfo, (unsigned char*)cfg.FontData, font_offset))
+        if (!stbtt_InitFont(&src_tmp.FontInfo, (unsigned char*)src.FontData, font_offset))
         {
             IM_ASSERT(0 && "stbtt_InitFont(): failed to parse FontData. It is correct and complete? Check FontDataSize.");
             return false;
@@ -2913,7 +2918,7 @@ static bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas)
 
         // Measure highest codepoints
         ImFontBuildDstData& dst_tmp = dst_tmp_array[src_tmp.DstIndex];
-        src_tmp.SrcRanges = cfg.GlyphRanges ? cfg.GlyphRanges : atlas->GetGlyphRangesDefault();
+        src_tmp.SrcRanges = src.GlyphRanges ? src.GlyphRanges : atlas->GetGlyphRangesDefault();
         for (const ImWchar* src_range = src_tmp.SrcRanges; src_range[0] && src_range[1]; src_range += 2)
         {
             // Check for valid range. This may also help detect *some* dangling pointers, because a common
@@ -2992,12 +2997,12 @@ static bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas)
         buf_packedchars_out_n += src_tmp.GlyphsCount;
 
         // Automatic selection of oversampling parameters
-        ImFontConfig& cfg = atlas->ConfigData[src_i];
+        ImFontConfig& src = atlas->Sources[src_i];
         int oversample_h, oversample_v;
-        ImFontAtlasBuildGetOversampleFactors(&cfg, &oversample_h, &oversample_v);
+        ImFontAtlasBuildGetOversampleFactors(&src, &oversample_h, &oversample_v);
 
         // Convert our ranges in the format stb_truetype wants
-        src_tmp.PackRange.font_size = cfg.SizePixels * cfg.RasterizerDensity;
+        src_tmp.PackRange.font_size = src.SizePixels * src.RasterizerDensity;
         src_tmp.PackRange.first_unicode_codepoint_in_range = 0;
         src_tmp.PackRange.array_of_unicode_codepoints = src_tmp.GlyphsList.Data;
         src_tmp.PackRange.num_chars = src_tmp.GlyphsList.Size;
@@ -3006,7 +3011,7 @@ static bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas)
         src_tmp.PackRange.v_oversample = (unsigned char)oversample_v;
 
         // Gather the sizes of all rectangles we will need to pack (this loop is based on stbtt_PackFontRangesGatherRects)
-        const float scale = (cfg.SizePixels > 0.0f) ? stbtt_ScaleForPixelHeight(&src_tmp.FontInfo, cfg.SizePixels * cfg.RasterizerDensity) : stbtt_ScaleForMappingEmToPixels(&src_tmp.FontInfo, -cfg.SizePixels * cfg.RasterizerDensity);
+        const float scale = (src.SizePixels > 0.0f) ? stbtt_ScaleForPixelHeight(&src_tmp.FontInfo, src.SizePixels * src.RasterizerDensity) : stbtt_ScaleForMappingEmToPixels(&src_tmp.FontInfo, -src.SizePixels * src.RasterizerDensity);
         for (int glyph_i = 0; glyph_i < src_tmp.GlyphsList.Size; glyph_i++)
         {
             int x0, y0, x1, y1;
@@ -3066,7 +3071,7 @@ static bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas)
     // 8. Render/rasterize font characters into the texture
     for (int src_i = 0; src_i < src_tmp_array.Size; src_i++)
     {
-        ImFontConfig& cfg = atlas->ConfigData[src_i];
+        ImFontConfig& src = atlas->Sources[src_i];
         ImFontBuildSrcData& src_tmp = src_tmp_array[src_i];
         if (src_tmp.GlyphsCount == 0)
             continue;
@@ -3074,10 +3079,10 @@ static bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas)
         stbtt_PackFontRangesRenderIntoRects(&spc, &src_tmp.FontInfo, &src_tmp.PackRange, 1, src_tmp.Rects);
 
         // Apply multiply operator
-        if (cfg.RasterizerMultiply != 1.0f)
+        if (src.RasterizerMultiply != 1.0f)
         {
             unsigned char multiply_table[256];
-            ImFontAtlasBuildMultiplyCalcLookupTable(multiply_table, cfg.RasterizerMultiply);
+            ImFontAtlasBuildMultiplyCalcLookupTable(multiply_table, src.RasterizerMultiply);
             stbrp_rect* r = &src_tmp.Rects[0];
             for (int glyph_i = 0; glyph_i < src_tmp.GlyphsCount; glyph_i++, r++)
                 if (r->was_packed)
@@ -3095,22 +3100,22 @@ static bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas)
     {
         // When merging fonts with MergeMode=true:
         // - We can have multiple input fonts writing into a same destination font.
-        // - dst_font->ConfigData is != from cfg which is our source configuration.
+        // - dst_font->Sources is != from src which is our source configuration.
         ImFontBuildSrcData& src_tmp = src_tmp_array[src_i];
-        ImFontConfig& cfg = atlas->ConfigData[src_i];
-        ImFont* dst_font = cfg.DstFont;
+        ImFontConfig& src = atlas->Sources[src_i];
+        ImFont* dst_font = src.DstFont;
 
-        const float font_scale = stbtt_ScaleForPixelHeight(&src_tmp.FontInfo, cfg.SizePixels);
+        const float font_scale = stbtt_ScaleForPixelHeight(&src_tmp.FontInfo, src.SizePixels);
         int unscaled_ascent, unscaled_descent, unscaled_line_gap;
         stbtt_GetFontVMetrics(&src_tmp.FontInfo, &unscaled_ascent, &unscaled_descent, &unscaled_line_gap);
 
         const float ascent = ImCeil(unscaled_ascent * font_scale);
         const float descent = ImFloor(unscaled_descent * font_scale);
-        ImFontAtlasBuildSetupFont(atlas, dst_font, &cfg, ascent, descent);
-        const float font_off_x = cfg.GlyphOffset.x;
-        const float font_off_y = cfg.GlyphOffset.y + IM_ROUND(dst_font->Ascent);
+        ImFontAtlasBuildSetupFont(atlas, dst_font, &src, ascent, descent);
+        const float font_off_x = src.GlyphOffset.x;
+        const float font_off_y = src.GlyphOffset.y + IM_ROUND(dst_font->Ascent);
 
-        const float inv_rasterization_scale = 1.0f / cfg.RasterizerDensity;
+        const float inv_rasterization_scale = 1.0f / src.RasterizerDensity;
 
         for (int glyph_i = 0; glyph_i < src_tmp.GlyphsCount; glyph_i++)
         {
@@ -3124,7 +3129,7 @@ static bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas)
             float y0 = q.y0 * inv_rasterization_scale + font_off_y;
             float x1 = q.x1 * inv_rasterization_scale + font_off_x;
             float y1 = q.y1 * inv_rasterization_scale + font_off_y;
-            dst_font->AddGlyph(&cfg, (ImWchar)codepoint, x0, y0, x1, y1, q.s0, q.t0, q.s1, q.t1, pc.xadvance * inv_rasterization_scale);
+            dst_font->AddGlyph(&src, (ImWchar)codepoint, x0, y0, x1, y1, q.s0, q.t0, q.s1, q.t1, pc.xadvance * inv_rasterization_scale);
         }
     }
 
@@ -3144,17 +3149,17 @@ const ImFontBuilderIO* ImFontAtlasGetBuilderForStbTruetype()
 
 #endif // IMGUI_ENABLE_STB_TRUETYPE
 
-void ImFontAtlasUpdateConfigDataPointers(ImFontAtlas* atlas)
+void ImFontAtlasUpdateSourcesPointers(ImFontAtlas* atlas)
 {
-    for (ImFontConfig& font_cfg : atlas->ConfigData)
+    for (ImFontConfig& src : atlas->Sources)
     {
-        ImFont* font = font_cfg.DstFont;
-        if (!font_cfg.MergeMode)
+        ImFont* font = src.DstFont;
+        if (!src.MergeMode)
         {
-            font->ConfigData = &font_cfg;
-            font->ConfigDataCount = 0;
+            font->Sources = &src;
+            font->SourcesCount = 0;
         }
-        font->ConfigDataCount++;
+        font->SourcesCount++;
     }
 }
 
@@ -3164,7 +3169,7 @@ void ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* f
     {
         font->ClearOutputData();
         font->FontSize = font_config->SizePixels;
-        IM_ASSERT(font->ConfigData == font_config);
+        IM_ASSERT(font->Sources == font_config);
         font->ContainerAtlas = atlas;
         font->Ascent = ascent;
         font->Descent = descent;
@@ -3349,7 +3354,7 @@ void ImFontAtlasBuildFinish(ImFontAtlas* atlas)
         if (r->Font == NULL || r->GlyphID == 0)
             continue;
 
-        // Will ignore ImFontConfig settings: GlyphMinAdvanceX, GlyphMinAdvanceY, GlyphExtraSpacing, PixelSnapH
+        // Will ignore ImFontConfig settings: GlyphMinAdvanceX, GlyphMinAdvanceY, PixelSnapH
         IM_ASSERT(r->Font->ContainerAtlas == atlas);
         ImVec2 uv0, uv1;
         atlas->CalcCustomRectUV(r, &uv0, &uv1);
@@ -3685,21 +3690,8 @@ void ImFontGlyphRangesBuilder::BuildRanges(ImVector* out_ranges)
 
 ImFont::ImFont()
 {
-    FontSize = 0.0f;
-    FallbackAdvanceX = 0.0f;
-    FallbackChar = 0;
-    EllipsisChar = 0;
-    EllipsisWidth = EllipsisCharStep = 0.0f;
-    EllipsisCharCount = 0;
-    FallbackGlyph = NULL;
-    ContainerAtlas = NULL;
-    ConfigData = NULL;
-    ConfigDataCount = 0;
-    DirtyLookupTables = false;
+    memset(this, 0, sizeof(*this));
     Scale = 1.0f;
-    Ascent = Descent = 0.0f;
-    MetricsTotalSurface = 0;
-    memset(Used8kPagesMap, 0, sizeof(Used8kPagesMap));
 }
 
 ImFont::~ImFont()
@@ -3770,8 +3762,10 @@ void ImFont::BuildLookupTable()
     }
 
     // Mark special glyphs as not visible (note that AddGlyph already mark as non-visible glyphs with zero-size polygons)
-    SetGlyphVisible((ImWchar)' ', false);
-    SetGlyphVisible((ImWchar)'\t', false);
+    if (ImFontGlyph* glyph = (ImFontGlyph*)(void*)FindGlyph((ImWchar)' '))
+        glyph->Visible = false;
+    if (ImFontGlyph* glyph = (ImFontGlyph*)(void*)FindGlyph((ImWchar)'\t'))
+        glyph->Visible = false;
 
     // Setup Fallback character
     const ImWchar fallback_chars[] = { (ImWchar)IM_UNICODE_CODEPOINT_INVALID, (ImWchar)'?', (ImWchar)' ' };
@@ -3794,7 +3788,7 @@ void ImFont::BuildLookupTable()
     // Setup Ellipsis character. It is required for rendering elided text. We prefer using U+2026 (horizontal ellipsis).
     // However some old fonts may contain ellipsis at U+0085. Here we auto-detect most suitable ellipsis character.
     // FIXME: Note that 0x2026 is rarely included in our font ranges. Because of this we are more likely to use three individual dots.
-    const ImWchar ellipsis_chars[] = { ConfigData->EllipsisChar, (ImWchar)0x2026, (ImWchar)0x0085 };
+    const ImWchar ellipsis_chars[] = { Sources->EllipsisChar, (ImWchar)0x2026, (ImWchar)0x0085 };
     const ImWchar dots_chars[] = { (ImWchar)'.', (ImWchar)0xFF0E };
     if (EllipsisChar == 0)
         EllipsisChar = FindFirstExistingGlyph(this, ellipsis_chars, IM_ARRAYSIZE(ellipsis_chars));
@@ -3827,12 +3821,6 @@ bool ImFont::IsGlyphRangeUnused(unsigned int c_begin, unsigned int c_last)
     return true;
 }
 
-void ImFont::SetGlyphVisible(ImWchar c, bool visible)
-{
-    if (ImFontGlyph* glyph = (ImFontGlyph*)(void*)FindGlyph((ImWchar)c))
-        glyph->Visible = visible ? 1 : 0;
-}
-
 void ImFont::GrowIndex(int new_size)
 {
     IM_ASSERT(IndexAdvanceX.Size == IndexLookup.Size);
@@ -3844,27 +3832,27 @@ void ImFont::GrowIndex(int new_size)
 
 // x0/y0/x1/y1 are offset from the character upper-left layout position, in pixels. Therefore x0/y0 are often fairly close to zero.
 // Not to be mistaken with texture coordinates, which are held by u0/v0/u1/v1 in normalized format (0.0..1.0 on each texture axis).
-// 'cfg' is not necessarily == 'this->ConfigData' because multiple source fonts+configs can be used to build one target font.
-void ImFont::AddGlyph(const ImFontConfig* cfg, ImWchar codepoint, float x0, float y0, float x1, float y1, float u0, float v0, float u1, float v1, float advance_x)
+// 'src' is not necessarily == 'this->Sources' because multiple source fonts+configs can be used to build one target font.
+void ImFont::AddGlyph(const ImFontConfig* src, ImWchar codepoint, float x0, float y0, float x1, float y1, float u0, float v0, float u1, float v1, float advance_x)
 {
-    if (cfg != NULL)
+    if (src != NULL)
     {
         // Clamp & recenter if needed
         const float advance_x_original = advance_x;
-        advance_x = ImClamp(advance_x, cfg->GlyphMinAdvanceX, cfg->GlyphMaxAdvanceX);
+        advance_x = ImClamp(advance_x, src->GlyphMinAdvanceX, src->GlyphMaxAdvanceX);
         if (advance_x != advance_x_original)
         {
-            float char_off_x = cfg->PixelSnapH ? ImTrunc((advance_x - advance_x_original) * 0.5f) : (advance_x - advance_x_original) * 0.5f;
+            float char_off_x = src->PixelSnapH ? ImTrunc((advance_x - advance_x_original) * 0.5f) : (advance_x - advance_x_original) * 0.5f;
             x0 += char_off_x;
             x1 += char_off_x;
         }
 
         // Snap to pixel
-        if (cfg->PixelSnapH)
+        if (src->PixelSnapH)
             advance_x = IM_ROUND(advance_x);
 
-        // Bake spacing
-        advance_x += cfg->GlyphExtraSpacing.x;
+        // Bake extra spacing
+        advance_x += src->GlyphExtraAdvanceX;
     }
 
     int glyph_idx = Glyphs.Size;
@@ -3907,7 +3895,7 @@ void ImFont::AddRemapChar(ImWchar dst, ImWchar src, bool overwrite_dst)
 }
 
 // Find glyph, return fallback if missing
-const ImFontGlyph* ImFont::FindGlyph(ImWchar c)
+ImFontGlyph* ImFont::FindGlyph(ImWchar c)
 {
     if (c >= (size_t)IndexLookup.Size)
         return FallbackGlyph;
@@ -3917,7 +3905,7 @@ const ImFontGlyph* ImFont::FindGlyph(ImWchar c)
     return &Glyphs.Data[i];
 }
 
-const ImFontGlyph* ImFont::FindGlyphNoFallback(ImWchar c)
+ImFontGlyph* ImFont::FindGlyphNoFallback(ImWchar c)
 {
     if (c >= (size_t)IndexLookup.Size)
         return NULL;
@@ -4043,7 +4031,7 @@ const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const c
 ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end, const char** remaining)
 {
     if (!text_end)
-        text_end = text_begin + strlen(text_begin); // FIXME-OPT: Need to avoid this.
+        text_end = text_begin + ImStrlen(text_begin); // FIXME-OPT: Need to avoid this.
 
     const float line_height = size;
     const float scale = size / FontSize;
@@ -4143,7 +4131,7 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im
         return;
 
     if (!text_end)
-        text_end = text_begin + strlen(text_begin); // ImGui:: functions generally already provides a valid text_end, so this is merely to handle direct calls.
+        text_end = text_begin + ImStrlen(text_begin); // ImGui:: functions generally already provides a valid text_end, so this is merely to handle direct calls.
 
     const float scale = size / FontSize;
     const float line_height = FontSize * scale;
@@ -4155,7 +4143,7 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im
     if (y + line_height < clip_rect.y)
         while (y + line_height < clip_rect.y && s < text_end)
         {
-            const char* line_end = (const char*)memchr(s, '\n', text_end - s);
+            const char* line_end = (const char*)ImMemchr(s, '\n', text_end - s);
             if (word_wrap_enabled)
             {
                 // FIXME-OPT: This is not optimal as do first do a search for \n before calling CalcWordWrapPositionA().
@@ -4179,7 +4167,7 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im
         float y_end = y;
         while (y_end < clip_rect.w && s_end < text_end)
         {
-            s_end = (const char*)memchr(s_end, '\n', text_end - s_end);
+            s_end = (const char*)ImMemchr(s_end, '\n', text_end - s_end);
             s_end = s_end ? s_end + 1 : text_end;
             y_end += line_height;
         }
diff --git a/3rdparty/imgui/src/imgui_freetype.cpp b/3rdparty/imgui/src/imgui_freetype.cpp
index 029991bbde..73b9862bcf 100644
--- a/3rdparty/imgui/src/imgui_freetype.cpp
+++ b/3rdparty/imgui/src/imgui_freetype.cpp
@@ -166,7 +166,7 @@ namespace
     // NB: No ctor/dtor, explicitly call Init()/Shutdown()
     struct FreeTypeFont
     {
-        bool                    InitFont(FT_Library ft_library, const ImFontConfig& cfg, unsigned int extra_user_flags); // Initialize from an external data buffer. Doesn't copy data, and you must ensure it stays valid up to this object lifetime.
+        bool                    InitFont(FT_Library ft_library, const ImFontConfig& src, unsigned int extra_user_flags); // Initialize from an external data buffer. Doesn't copy data, and you must ensure it stays valid up to this object lifetime.
         void                    CloseFont();
         void                    SetPixelHeight(int pixel_height); // Change font pixel size. All following calls to RasterizeGlyph() will use this size
         const FT_Glyph_Metrics* LoadGlyph(uint32_t in_codepoint);
@@ -185,9 +185,9 @@ namespace
         float           InvRasterizationDensity;
     };
 
-    bool FreeTypeFont::InitFont(FT_Library ft_library, const ImFontConfig& cfg, unsigned int extra_font_builder_flags)
+    bool FreeTypeFont::InitFont(FT_Library ft_library, const ImFontConfig& src, unsigned int extra_font_builder_flags)
     {
-        FT_Error error = FT_New_Memory_Face(ft_library, (uint8_t*)cfg.FontData, (uint32_t)cfg.FontDataSize, (uint32_t)cfg.FontNo, &Face);
+        FT_Error error = FT_New_Memory_Face(ft_library, (uint8_t*)src.FontData, (uint32_t)src.FontDataSize, (uint32_t)src.FontNo, &Face);
         if (error != 0)
             return false;
         error = FT_Select_Charmap(Face, FT_ENCODING_UNICODE);
@@ -195,7 +195,7 @@ namespace
             return false;
 
         // Convert to FreeType flags (NB: Bold and Oblique are processed separately)
-        UserFlags = cfg.FontBuilderFlags | extra_font_builder_flags;
+        UserFlags = src.FontBuilderFlags | extra_font_builder_flags;
 
         LoadFlags = 0;
         if ((UserFlags & ImGuiFreeTypeBuilderFlags_Bitmap) == 0)
@@ -222,11 +222,11 @@ namespace
         if (UserFlags & ImGuiFreeTypeBuilderFlags_LoadColor)
             LoadFlags |= FT_LOAD_COLOR;
 
-        RasterizationDensity = cfg.RasterizerDensity;
+        RasterizationDensity = src.RasterizerDensity;
         InvRasterizationDensity = 1.0f / RasterizationDensity;
 
         memset(&Info, 0, sizeof(Info));
-        SetPixelHeight((uint32_t)cfg.SizePixels);
+        SetPixelHeight((uint32_t)src.SizePixels);
 
         return true;
     }
@@ -269,11 +269,11 @@ namespace
         if (glyph_index == 0)
             return nullptr;
 
-		// If this crash for you: FreeType 2.11.0 has a crash bug on some bitmap/colored fonts.
-		// - https://gitlab.freedesktop.org/freetype/freetype/-/issues/1076
-		// - https://github.com/ocornut/imgui/issues/4567
-		// - https://github.com/ocornut/imgui/issues/4566
-		// You can use FreeType 2.10, or the patched version of 2.11.0 in VcPkg, or probably any upcoming FreeType version.
+        // If this crash for you: FreeType 2.11.0 has a crash bug on some bitmap/colored fonts.
+        // - https://gitlab.freedesktop.org/freetype/freetype/-/issues/1076
+        // - https://github.com/ocornut/imgui/issues/4567
+        // - https://github.com/ocornut/imgui/issues/4566
+        // You can use FreeType 2.10, or the patched version of 2.11.0 in VcPkg, or probably any upcoming FreeType version.
         FT_Error error = FT_Load_Glyph(Face, glyph_index, LoadFlags);
         if (error)
             return nullptr;
@@ -443,7 +443,7 @@ struct ImFontBuildDstDataFT
 
 bool ImFontAtlasBuildWithFreeTypeEx(FT_Library ft_library, ImFontAtlas* atlas, unsigned int extra_flags)
 {
-    IM_ASSERT(atlas->ConfigData.Size > 0);
+    IM_ASSERT(atlas->Sources.Size > 0);
 
     ImFontAtlasBuildInit(atlas);
 
@@ -458,36 +458,36 @@ bool ImFontAtlasBuildWithFreeTypeEx(FT_Library ft_library, ImFontAtlas* atlas, u
     bool src_load_color = false;
     ImVector src_tmp_array;
     ImVector dst_tmp_array;
-    src_tmp_array.resize(atlas->ConfigData.Size);
+    src_tmp_array.resize(atlas->Sources.Size);
     dst_tmp_array.resize(atlas->Fonts.Size);
     memset((void*)src_tmp_array.Data, 0, (size_t)src_tmp_array.size_in_bytes());
     memset((void*)dst_tmp_array.Data, 0, (size_t)dst_tmp_array.size_in_bytes());
 
     // 1. Initialize font loading structure, check font data validity
-    for (int src_i = 0; src_i < atlas->ConfigData.Size; src_i++)
+    for (int src_i = 0; src_i < atlas->Sources.Size; src_i++)
     {
         ImFontBuildSrcDataFT& src_tmp = src_tmp_array[src_i];
-        ImFontConfig& cfg = atlas->ConfigData[src_i];
+        ImFontConfig& src = atlas->Sources[src_i];
         FreeTypeFont& font_face = src_tmp.Font;
-        IM_ASSERT(cfg.DstFont && (!cfg.DstFont->IsLoaded() || cfg.DstFont->ContainerAtlas == atlas));
+        IM_ASSERT(src.DstFont && (!src.DstFont->IsLoaded() || src.DstFont->ContainerAtlas == atlas));
 
-        // Find index from cfg.DstFont (we allow the user to set cfg.DstFont. Also it makes casual debugging nicer than when storing indices)
+        // Find index from src.DstFont (we allow the user to set cfg.DstFont. Also it makes casual debugging nicer than when storing indices)
         src_tmp.DstIndex = -1;
         for (int output_i = 0; output_i < atlas->Fonts.Size && src_tmp.DstIndex == -1; output_i++)
-            if (cfg.DstFont == atlas->Fonts[output_i])
+            if (src.DstFont == atlas->Fonts[output_i])
                 src_tmp.DstIndex = output_i;
-        IM_ASSERT(src_tmp.DstIndex != -1); // cfg.DstFont not pointing within atlas->Fonts[] array?
+        IM_ASSERT(src_tmp.DstIndex != -1); // src.DstFont not pointing within atlas->Fonts[] array?
         if (src_tmp.DstIndex == -1)
             return false;
 
         // Load font
-        if (!font_face.InitFont(ft_library, cfg, extra_flags))
+        if (!font_face.InitFont(ft_library, src, extra_flags))
             return false;
 
         // Measure highest codepoints
-        src_load_color |= (cfg.FontBuilderFlags & ImGuiFreeTypeBuilderFlags_LoadColor) != 0;
+        src_load_color |= (src.FontBuilderFlags & ImGuiFreeTypeBuilderFlags_LoadColor) != 0;
         ImFontBuildDstDataFT& dst_tmp = dst_tmp_array[src_tmp.DstIndex];
-        src_tmp.SrcRanges = cfg.GlyphRanges ? cfg.GlyphRanges : atlas->GetGlyphRangesDefault();
+        src_tmp.SrcRanges = src.GlyphRanges ? src.GlyphRanges : atlas->GetGlyphRangesDefault();
         for (const ImWchar* src_range = src_tmp.SrcRanges; src_range[0] && src_range[1]; src_range += 2)
         {
             // Check for valid range. This may also help detect *some* dangling pointers, because a common
@@ -577,7 +577,7 @@ bool ImFontAtlasBuildWithFreeTypeEx(FT_Library ft_library, ImFontAtlas* atlas, u
     for (int src_i = 0; src_i < src_tmp_array.Size; src_i++)
     {
         ImFontBuildSrcDataFT& src_tmp = src_tmp_array[src_i];
-        ImFontConfig& cfg = atlas->ConfigData[src_i];
+        ImFontConfig& src = atlas->Sources[src_i];
         if (src_tmp.GlyphsCount == 0)
             continue;
 
@@ -585,10 +585,10 @@ bool ImFontAtlasBuildWithFreeTypeEx(FT_Library ft_library, ImFontAtlas* atlas, u
         buf_rects_out_n += src_tmp.GlyphsCount;
 
         // Compute multiply table if requested
-        const bool multiply_enabled = (cfg.RasterizerMultiply != 1.0f);
+        const bool multiply_enabled = (src.RasterizerMultiply != 1.0f);
         unsigned char multiply_table[256];
         if (multiply_enabled)
-            ImFontAtlasBuildMultiplyCalcLookupTable(multiply_table, cfg.RasterizerMultiply);
+            ImFontAtlasBuildMultiplyCalcLookupTable(multiply_table, src.RasterizerMultiply);
 
         // Gather the sizes of all rectangles we will need to pack
         for (int glyph_i = 0; glyph_i < src_tmp.GlyphsList.Size; glyph_i++)
@@ -687,18 +687,18 @@ bool ImFontAtlasBuildWithFreeTypeEx(FT_Library ft_library, ImFontAtlas* atlas, u
 
         // When merging fonts with MergeMode=true:
         // - We can have multiple input fonts writing into a same destination font.
-        // - dst_font->ConfigData is != from cfg which is our source configuration.
-        ImFontConfig& cfg = atlas->ConfigData[src_i];
-        ImFont* dst_font = cfg.DstFont;
+        // - dst_font->Sources is != from src which is our source configuration.
+        ImFontConfig& src = atlas->Sources[src_i];
+        ImFont* dst_font = src.DstFont;
 
         const float ascent = src_tmp.Font.Info.Ascender;
         const float descent = src_tmp.Font.Info.Descender;
-        ImFontAtlasBuildSetupFont(atlas, dst_font, &cfg, ascent, descent);
+        ImFontAtlasBuildSetupFont(atlas, dst_font, &src, ascent, descent);
 
         if (src_tmp.GlyphsCount == 0)
             continue;
-        const float font_off_x = cfg.GlyphOffset.x;
-        const float font_off_y = cfg.GlyphOffset.y + IM_ROUND(dst_font->Ascent);
+        const float font_off_x = src.GlyphOffset.x;
+        const float font_off_y = src.GlyphOffset.y + IM_ROUND(dst_font->Ascent);
 
         const int padding = atlas->TexGlyphPadding;
         for (int glyph_i = 0; glyph_i < src_tmp.GlyphsCount; glyph_i++)
@@ -724,7 +724,7 @@ bool ImFontAtlasBuildWithFreeTypeEx(FT_Library ft_library, ImFontAtlas* atlas, u
             float v0 = (ty) / (float)atlas->TexHeight;
             float u1 = (tx + info.Width) / (float)atlas->TexWidth;
             float v1 = (ty + info.Height) / (float)atlas->TexHeight;
-            dst_font->AddGlyph(&cfg, (ImWchar)src_glyph.Codepoint, x0, y0, x1, y1, u0, v0, u1, v1, info.AdvanceX * src_tmp.Font.InvRasterizationDensity);
+            dst_font->AddGlyph(&src, (ImWchar)src_glyph.Codepoint, x0, y0, x1, y1, u0, v0, u1, v1, info.AdvanceX * src_tmp.Font.InvRasterizationDensity);
 
             ImFontGlyph* dst_glyph = &dst_font->Glyphs.back();
             IM_ASSERT(dst_glyph->Codepoint == src_glyph.Codepoint);
diff --git a/3rdparty/imgui/src/imgui_tables.cpp b/3rdparty/imgui/src/imgui_tables.cpp
index e7a3b8e8d1..c98aea2d1c 100644
--- a/3rdparty/imgui/src/imgui_tables.cpp
+++ b/3rdparty/imgui/src/imgui_tables.cpp
@@ -1,4 +1,4 @@
-// dear imgui, v1.91.8
+// dear imgui, v1.91b
 // (tables and columns code)
 
 /*
@@ -221,6 +221,7 @@ Index of this file:
 #pragma clang diagnostic ignored "-Wunknown-pragmas"                // warning: unknown warning group 'xxx'
 #pragma clang diagnostic ignored "-Wold-style-cast"                 // warning: use of old-style cast                            // yes, they are more terse.
 #pragma clang diagnostic ignored "-Wfloat-equal"                    // warning: comparing floating point with == or != is unsafe // storing and comparing against same constants (typically 0.0f) is ok.
+#pragma clang diagnostic ignored "-Wformat"                         // warning: format specifies type 'int' but the argument has type 'unsigned int'
 #pragma clang diagnostic ignored "-Wformat-nonliteral"              // warning: format string is not a string literal            // passing non-literal to vsnformat(). yes, user passing incorrect format strings can crash the code.
 #pragma clang diagnostic ignored "-Wsign-conversion"                // warning: implicit conversion changes signedness
 #pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant"  // warning: zero as null pointer constant                    // some standard header variations use #define NULL 0
@@ -230,6 +231,7 @@ Index of this file:
 #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion"  // warning: implicit conversion from 'xxx' to 'float' may lose precision
 #pragma clang diagnostic ignored "-Wunsafe-buffer-usage"            // warning: 'xxx' is an unsafe pointer used for buffer access
 #pragma clang diagnostic ignored "-Wnontrivial-memaccess"           // warning: first argument in call to 'memset' is a pointer to non-trivially copyable type
+#pragma clang diagnostic ignored "-Wswitch-default"                 // warning: 'switch' missing 'default' label
 #elif defined(__GNUC__)
 #pragma GCC diagnostic ignored "-Wpragmas"                          // warning: unknown option after '#pragma GCC diagnostic' kind
 #pragma GCC diagnostic ignored "-Wfloat-equal"                      // warning: comparing floating-point with '==' or '!=' is unsafe
@@ -340,6 +342,7 @@ bool    ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG
     {
         ItemSize(outer_rect);
         ItemAdd(outer_rect, id);
+        g.NextWindowData.ClearFlags();
         return false;
     }
 
@@ -415,12 +418,15 @@ bool    ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG
 
         // Reset scroll if we are reactivating it
         if ((previous_flags & (ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY)) == 0)
-            if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasScroll) == 0)
+            if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasScroll) == 0)
                 SetNextWindowScroll(ImVec2(0.0f, 0.0f));
 
         // Create scrolling region (without border and zero window padding)
-        ImGuiWindowFlags child_window_flags = (flags & ImGuiTableFlags_ScrollX) ? ImGuiWindowFlags_HorizontalScrollbar : ImGuiWindowFlags_None;
-        BeginChildEx(name, instance_id, outer_rect.GetSize(), ImGuiChildFlags_None, child_window_flags);
+        ImGuiChildFlags child_child_flags = (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasChildFlags) ? g.NextWindowData.ChildFlags : ImGuiChildFlags_None;
+        ImGuiWindowFlags child_window_flags = (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasWindowFlags) ? g.NextWindowData.WindowFlags : ImGuiWindowFlags_None;
+        if (flags & ImGuiTableFlags_ScrollX)
+            child_window_flags |= ImGuiWindowFlags_HorizontalScrollbar;
+        BeginChildEx(name, instance_id, outer_rect.GetSize(), child_child_flags, child_window_flags);
         table->InnerWindow = g.CurrentWindow;
         table->WorkRect = table->InnerWindow->WorkRect;
         table->OuterRect = table->InnerWindow->Rect();
@@ -573,6 +579,7 @@ bool    ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG
         // Initialize
         table->SettingsOffset = -1;
         table->IsSortSpecsDirty = true;
+        table->IsSettingsDirty = true; // Records itself into .ini file even when in default state (#7934)
         table->InstanceInteracted = -1;
         table->ContextPopupColumn = -1;
         table->ReorderColumn = table->ResizedColumn = table->LastResizedColumn = -1;
@@ -972,7 +979,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table)
     // [Part 4] Apply final widths based on requested widths
     const ImRect work_rect = table->WorkRect;
     const float width_spacings = (table->OuterPaddingX * 2.0f) + (table->CellSpacingX1 + table->CellSpacingX2) * (table->ColumnsEnabledCount - 1);
-    const float width_removed = (table->HasScrollbarYPrev && !table->InnerWindow->ScrollbarY) ? g.Style.ScrollbarSize : 0.0f; // To synchronize decoration width of synched tables with mismatching scrollbar state (#5920)
+    const float width_removed = (table->HasScrollbarYPrev && !table->InnerWindow->ScrollbarY) ? g.Style.ScrollbarSize : 0.0f; // To synchronize decoration width of synced tables with mismatching scrollbar state (#5920)
     const float width_avail = ImMax(1.0f, (((table->Flags & ImGuiTableFlags_ScrollX) && table->InnerWidth == 0.0f) ? table->InnerClipRect.GetWidth() : work_rect.GetWidth()) - width_removed);
     const float width_avail_for_stretched_columns = width_avail - width_spacings - sum_width_requests;
     float width_remaining_for_stretched_columns = width_avail_for_stretched_columns;
@@ -1385,7 +1392,7 @@ void    ImGui::EndTable()
 
     // Setup inner scrolling range
     // FIXME: This ideally should be done earlier, in BeginTable() SetNextWindowContentSize call, just like writing to inner_window->DC.CursorMaxPos.y,
-    // but since the later is likely to be impossible to do we'd rather update both axises together.
+    // but since the later is likely to be impossible to do we'd rather update both axes together.
     if (table->Flags & ImGuiTableFlags_ScrollX)
     {
         const float outer_padding_for_border = (table->Flags & ImGuiTableFlags_BordersOuterV) ? TABLE_BORDER_SIZE : 0.0f;
@@ -1560,6 +1567,31 @@ void    ImGui::EndTable()
     NavUpdateCurrentWindowIsScrollPushableX();
 }
 
+// Called in TableSetupColumn() when initializing and in TableLoadSettings() for defaults before applying stored settings.
+// 'init_mask' specify which fields to initialize.
+static void TableInitColumnDefaults(ImGuiTable* table, ImGuiTableColumn* column, ImGuiTableColumnFlags init_mask)
+{
+    ImGuiTableColumnFlags flags = column->Flags;
+    if (init_mask & ImGuiTableFlags_Resizable)
+    {
+        float init_width_or_weight = column->InitStretchWeightOrWidth;
+        column->WidthRequest = ((flags & ImGuiTableColumnFlags_WidthFixed) && init_width_or_weight > 0.0f) ? init_width_or_weight : -1.0f;
+        column->StretchWeight = (init_width_or_weight > 0.0f && (flags & ImGuiTableColumnFlags_WidthStretch)) ? init_width_or_weight : -1.0f;
+        if (init_width_or_weight > 0.0f) // Disable auto-fit if an explicit width/weight has been specified
+            column->AutoFitQueue = 0x00;
+    }
+    if (init_mask & ImGuiTableFlags_Reorderable)
+        column->DisplayOrder = (ImGuiTableColumnIdx)table->Columns.index_from_ptr(column);
+    if (init_mask & ImGuiTableFlags_Hideable)
+        column->IsUserEnabled = column->IsUserEnabledNextFrame = (flags & ImGuiTableColumnFlags_DefaultHide) ? 0 : 1;
+    if (init_mask & ImGuiTableFlags_Sortable)
+    {
+        // Multiple columns using _DefaultSort will be reassigned unique SortOrder values when building the sort specs.
+        column->SortOrder = (flags & ImGuiTableColumnFlags_DefaultSort) ? 0 : -1;
+        column->SortDirection = (flags & ImGuiTableColumnFlags_DefaultSort) ? ((flags & ImGuiTableColumnFlags_PreferSortDescending) ? (ImS8)ImGuiSortDirection_Descending : (ImU8)(ImGuiSortDirection_Ascending)) : (ImS8)ImGuiSortDirection_None;
+    }
+}
+
 // See "COLUMNS SIZING POLICIES" comments at the top of this file
 // If (init_width_or_weight <= 0.0f) it is ignored
 void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, float init_width_or_weight, ImGuiID user_id)
@@ -1588,7 +1620,7 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, flo
         IM_ASSERT(init_width_or_weight <= 0.0f && "Can only specify width/weight if sizing policy is set explicitly in either Table or Column.");
 
     // When passing a width automatically enforce WidthFixed policy
-    // (whereas TableSetupColumnFlags would default to WidthAuto if table is not Resizable)
+    // (whereas TableSetupColumnFlags would default to WidthAuto if table is not resizable)
     if ((flags & ImGuiTableColumnFlags_WidthMask_) == 0 && init_width_or_weight > 0.0f)
         if ((table->Flags & ImGuiTableFlags_SizingMask_) == ImGuiTableFlags_SizingFixedFit || (table->Flags & ImGuiTableFlags_SizingMask_) == ImGuiTableFlags_SizingFixedSame)
             flags |= ImGuiTableColumnFlags_WidthFixed;
@@ -1606,27 +1638,10 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, flo
     column->InitStretchWeightOrWidth = init_width_or_weight;
     if (table->IsInitializing)
     {
-        // Init width or weight
+        ImGuiTableFlags init_flags = ~table->SettingsLoadedFlags;
         if (column->WidthRequest < 0.0f && column->StretchWeight < 0.0f)
-        {
-            if ((flags & ImGuiTableColumnFlags_WidthFixed) && init_width_or_weight > 0.0f)
-                column->WidthRequest = init_width_or_weight;
-            if (flags & ImGuiTableColumnFlags_WidthStretch)
-                column->StretchWeight = (init_width_or_weight > 0.0f) ? init_width_or_weight : -1.0f;
-
-            // Disable auto-fit if an explicit width/weight has been specified
-            if (init_width_or_weight > 0.0f)
-                column->AutoFitQueue = 0x00;
-        }
-
-        // Init default visibility/sort state
-        if ((flags & ImGuiTableColumnFlags_DefaultHide) && (table->SettingsLoadedFlags & ImGuiTableFlags_Hideable) == 0)
-            column->IsUserEnabled = column->IsUserEnabledNextFrame = false;
-        if (flags & ImGuiTableColumnFlags_DefaultSort && (table->SettingsLoadedFlags & ImGuiTableFlags_Sortable) == 0)
-        {
-            column->SortOrder = 0; // Multiple columns using _DefaultSort will be reassigned unique SortOrder values when building the sort specs.
-            column->SortDirection = (column->Flags & ImGuiTableColumnFlags_PreferSortDescending) ? (ImS8)ImGuiSortDirection_Descending : (ImU8)(ImGuiSortDirection_Ascending);
-        }
+            init_flags |= ImGuiTableFlags_Resizable;
+        TableInitColumnDefaults(table, column, init_flags);
     }
 
     // Store name (append with zero-terminator in contiguous buffer)
@@ -1635,7 +1650,7 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, flo
     if (label != NULL && label[0] != 0)
     {
         column->NameOffset = (ImS16)table->ColumnsNames.size();
-        table->ColumnsNames.append(label, label + strlen(label) + 1);
+        table->ColumnsNames.append(label, label + ImStrlen(label) + 1);
     }
 }
 
@@ -2101,7 +2116,11 @@ bool ImGui::TableSetColumnIndex(int column_n)
     {
         if (table->CurrentColumn != -1)
             TableEndCell(table);
-        IM_ASSERT(column_n >= 0 && table->ColumnsCount);
+        if ((column_n >= 0 && column_n < table->ColumnsCount) == false)
+        {
+            IM_ASSERT_USER_ERROR(column_n >= 0 && column_n < table->ColumnsCount, "TableSetColumnIndex() invalid column index!");
+            return false;
+        }
         TableBeginCell(table, column_n);
     }
 
@@ -3714,6 +3733,14 @@ void ImGui::TableLoadSettings(ImGuiTable* table)
     table->SettingsLoadedFlags = settings->SaveFlags;
     table->RefScale = settings->RefScale;
 
+    // Initialize default columns settings
+    for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
+    {
+        ImGuiTableColumn* column = &table->Columns[column_n];
+        TableInitColumnDefaults(table, column, ~0);
+        column->AutoFitQueue = 0x00;
+    }
+
     // Serialize ImGuiTableSettings/ImGuiTableColumnSettings into ImGuiTable/ImGuiTableColumn
     ImGuiTableColumnSettings* column_settings = settings->GetColumnSettings();
     ImU64 display_order_mask = 0;
@@ -3730,14 +3757,12 @@ void ImGui::TableLoadSettings(ImGuiTable* table)
                 column->StretchWeight = column_settings->WidthOrWeight;
             else
                 column->WidthRequest = column_settings->WidthOrWeight;
-            column->AutoFitQueue = 0x00;
         }
         if (settings->SaveFlags & ImGuiTableFlags_Reorderable)
             column->DisplayOrder = column_settings->DisplayOrder;
-        else
-            column->DisplayOrder = (ImGuiTableColumnIdx)column_n;
         display_order_mask |= (ImU64)1 << column->DisplayOrder;
-        column->IsUserEnabled = column->IsUserEnabledNextFrame = column_settings->IsEnabled;
+        if ((settings->SaveFlags & ImGuiTableFlags_Hideable) && column_settings->IsEnabled != -1)
+            column->IsUserEnabled = column->IsUserEnabledNextFrame = column_settings->IsEnabled == 1;
         column->SortOrder = column_settings->SortOrder;
         column->SortDirection = column_settings->SortDirection;
     }
@@ -3833,8 +3858,7 @@ static void TableSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandle
         const bool save_visible = (settings->SaveFlags & ImGuiTableFlags_Hideable) != 0;
         const bool save_order   = (settings->SaveFlags & ImGuiTableFlags_Reorderable) != 0;
         const bool save_sort    = (settings->SaveFlags & ImGuiTableFlags_Sortable) != 0;
-        if (!save_size && !save_visible && !save_order && !save_sort)
-            continue;
+        // We need to save the [Table] entry even if all the bools are false, since this records a table with "default settings".
 
         buf->reserve(buf->size() + 30 + settings->ColumnsCount * 50); // ballpark reserve
         buf->appendf("[%s][0x%08X,%d]\n", handler->TypeName, settings->ID, settings->ColumnsCount);
diff --git a/3rdparty/imgui/src/imgui_widgets.cpp b/3rdparty/imgui/src/imgui_widgets.cpp
index 1e4297fcbd..c997e17b61 100644
--- a/3rdparty/imgui/src/imgui_widgets.cpp
+++ b/3rdparty/imgui/src/imgui_widgets.cpp
@@ -1,4 +1,4 @@
-// dear imgui, v1.91.8
+// dear imgui, v1.91b
 // (widgets code)
 
 /*
@@ -70,6 +70,7 @@ Index of this file:
 #pragma clang diagnostic ignored "-Wunknown-pragmas"                // warning: unknown warning group 'xxx'
 #pragma clang diagnostic ignored "-Wold-style-cast"                 // warning: use of old-style cast                            // yes, they are more terse.
 #pragma clang diagnostic ignored "-Wfloat-equal"                    // warning: comparing floating point with == or != is unsafe // storing and comparing against same constants (typically 0.0f) is ok.
+#pragma clang diagnostic ignored "-Wformat"                         // warning: format specifies type 'int' but the argument has type 'unsigned int'
 #pragma clang diagnostic ignored "-Wformat-nonliteral"              // warning: format string is not a string literal            // passing non-literal to vsnformat(). yes, user passing incorrect format strings can crash the code.
 #pragma clang diagnostic ignored "-Wsign-conversion"                // warning: implicit conversion changes signedness
 #pragma clang diagnostic ignored "-Wunused-macros"                  // warning: macro is not used                                // we define snprintf/vsnprintf on Windows so they are available, but not always used.
@@ -80,6 +81,7 @@ Index of this file:
 #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion"  // warning: implicit conversion from 'xxx' to 'float' may lose precision
 #pragma clang diagnostic ignored "-Wunsafe-buffer-usage"            // warning: 'xxx' is an unsafe pointer used for buffer access
 #pragma clang diagnostic ignored "-Wnontrivial-memaccess"           // warning: first argument in call to 'memset' is a pointer to non-trivially copyable type
+#pragma clang diagnostic ignored "-Wswitch-default"                 // warning: 'switch' missing 'default' label
 #elif defined(__GNUC__)
 #pragma GCC diagnostic ignored "-Wpragmas"                          // warning: unknown option after '#pragma GCC diagnostic' kind
 #pragma GCC diagnostic ignored "-Wfloat-equal"                      // warning: comparing floating-point with '==' or '!=' is unsafe
@@ -169,7 +171,7 @@ void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags)
     // Calculate length
     const char* text_begin = text;
     if (text_end == NULL)
-        text_end = text + strlen(text); // FIXME-OPT
+        text_end = text + ImStrlen(text); // FIXME-OPT
 
     const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
     const float wrap_pos_x = window->DC.TextWrapPos;
@@ -209,7 +211,7 @@ void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags)
                 int lines_skipped = 0;
                 while (line < text_end && lines_skipped < lines_skippable)
                 {
-                    const char* line_end = (const char*)memchr(line, '\n', text_end - line);
+                    const char* line_end = (const char*)ImMemchr(line, '\n', text_end - line);
                     if (!line_end)
                         line_end = text_end;
                     if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0)
@@ -230,7 +232,7 @@ void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags)
                 if (IsClippedEx(line_rect, 0))
                     break;
 
-                const char* line_end = (const char*)memchr(line, '\n', text_end - line);
+                const char* line_end = (const char*)ImMemchr(line, '\n', text_end - line);
                 if (!line_end)
                     line_end = text_end;
                 text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x);
@@ -245,7 +247,7 @@ void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags)
             int lines_skipped = 0;
             while (line < text_end)
             {
-                const char* line_end = (const char*)memchr(line, '\n', text_end - line);
+                const char* line_end = (const char*)ImMemchr(line, '\n', text_end - line);
                 if (!line_end)
                     line_end = text_end;
                 if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0)
@@ -913,7 +915,7 @@ ImRect ImGui::GetWindowScrollbarRect(ImGuiWindow* window, ImGuiAxis axis)
     const ImRect outer_rect = window->Rect();
     const ImRect inner_rect = window->InnerRect;
     const float scrollbar_size = window->ScrollbarSizes[axis ^ 1]; // (ScrollbarSizes.x = width of Y scrollbar; ScrollbarSizes.y = height of X scrollbar)
-    IM_ASSERT(scrollbar_size > 0.0f);
+    IM_ASSERT(scrollbar_size >= 0.0f);
     const float border_size = IM_ROUND(window->WindowBorderSize * 0.5f);
     const float border_top = (window->Flags & ImGuiWindowFlags_MenuBar) ? IM_ROUND(g.Style.FrameBorderSize * 0.5f) : 0.0f;
     if (axis == ImGuiAxis_X)
@@ -971,8 +973,8 @@ bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS6
 
     // When we are too small, start hiding and disabling the grab (this reduce visual noise on very small window and facilitate using the window resize grab)
     float alpha = 1.0f;
-    if ((axis == ImGuiAxis_Y) && bb_frame_height < g.FontSize + g.Style.FramePadding.y * 2.0f)
-        alpha = ImSaturate((bb_frame_height - g.FontSize) / (g.Style.FramePadding.y * 2.0f));
+    if ((axis == ImGuiAxis_Y) && bb_frame_height < bb_frame_width)
+        alpha = ImSaturate(bb_frame_height / ImMax(bb_frame_width * 2.0f, 1.0f));
     if (alpha <= 0.0f)
         return false;
 
@@ -989,7 +991,8 @@ bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS6
     // But we maintain a minimum size in pixel to allow for the user to still aim inside.
     IM_ASSERT(ImMax(size_contents_v, size_visible_v) > 0.0f); // Adding this assert to check if the ImMax(XXX,1.0f) is still needed. PLEASE CONTACT ME if this triggers.
     const ImS64 win_size_v = ImMax(ImMax(size_contents_v, size_visible_v), (ImS64)1);
-    const float grab_h_pixels = ImClamp(scrollbar_size_v * ((float)size_visible_v / (float)win_size_v), style.GrabMinSize, scrollbar_size_v);
+    const float grab_h_minsize = ImMin(bb.GetSize()[axis], style.GrabMinSize);
+    const float grab_h_pixels = ImClamp(scrollbar_size_v * ((float)size_visible_v / (float)win_size_v), grab_h_minsize, scrollbar_size_v);
     const float grab_h_norm = grab_h_pixels / scrollbar_size_v;
 
     // Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar().
@@ -1061,25 +1064,45 @@ bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS6
 
 // - Read about ImTextureID here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples
 // - 'uv0' and 'uv1' are texture coordinates. Read about them from the same link above.
-void ImGui::Image(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col)
+void ImGui::ImageWithBg(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col)
 {
+    ImGuiContext& g = *GImGui;
     ImGuiWindow* window = GetCurrentWindow();
     if (window->SkipItems)
         return;
 
-    const float border_size = (border_col.w > 0.0f) ? 1.0f : 0.0f;
-    const ImVec2 padding(border_size, border_size);
+    const ImVec2 padding(g.Style.ImageBorderSize, g.Style.ImageBorderSize);
     const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + image_size + padding * 2.0f);
     ItemSize(bb);
     if (!ItemAdd(bb, 0))
         return;
 
     // Render
-    if (border_size > 0.0f)
-        window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(border_col), 0.0f, ImDrawFlags_None, border_size);
+    if (g.Style.ImageBorderSize > 0.0f)
+        window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_Border), 0.0f, ImDrawFlags_None, g.Style.ImageBorderSize);
+    if (bg_col.w > 0.0f)
+        window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, GetColorU32(bg_col));
     window->DrawList->AddImage(user_texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col));
 }
 
+void ImGui::Image(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1)
+{
+    ImageWithBg(user_texture_id, image_size, uv0, uv1);
+}
+
+// 1.91.9 (February 2025) removed 'tint_col' and 'border_col' parameters, made border size not depend on color value. (#8131, #8238)
+#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
+void ImGui::Image(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col)
+{
+    ImGuiContext& g = *GImGui;
+    PushStyleVar(ImGuiStyleVar_ImageBorderSize, (border_col.w > 0.0f) ? ImMax(1.0f, g.Style.ImageBorderSize) : 0.0f); // Preserve legacy behavior where border is always visible when border_col's Alpha is >0.0f
+    PushStyleColor(ImGuiCol_Border, border_col);
+    ImageWithBg(user_texture_id, image_size, uv0, uv1, ImVec4(0, 0, 0, 0), tint_col);
+    PopStyleColor();
+    PopStyleVar();
+}
+#endif
+
 bool ImGui::ImageButtonEx(ImGuiID id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags)
 {
     ImGuiContext& g = *GImGui;
@@ -1423,7 +1446,7 @@ bool ImGui::TextLink(const char* label)
     const ImGuiID id = window->GetID(label);
     const char* label_end = FindRenderedTextEnd(label);
 
-    ImVec2 pos = window->DC.CursorPos;
+    ImVec2 pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
     ImVec2 size = CalcTextSize(label, label_end, true);
     ImRect bb(pos, pos + size);
     ItemSize(size, 0.0f);
@@ -1828,7 +1851,7 @@ bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboF
     ImGuiContext& g = *GImGui;
     ImGuiWindow* window = GetCurrentWindow();
 
-    ImGuiNextWindowDataFlags backup_next_window_data_flags = g.NextWindowData.Flags;
+    ImGuiNextWindowDataFlags backup_next_window_data_flags = g.NextWindowData.HasFlags;
     g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
     if (window->SkipItems)
         return false;
@@ -1897,7 +1920,7 @@ bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboF
     if (!popup_open)
         return false;
 
-    g.NextWindowData.Flags = backup_next_window_data_flags;
+    g.NextWindowData.HasFlags = backup_next_window_data_flags;
     return BeginComboPopup(popup_id, bb, flags);
 }
 
@@ -1912,7 +1935,7 @@ bool ImGui::BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags
 
     // Set popup size
     float w = bb.GetWidth();
-    if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint)
+    if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSizeConstraint)
     {
         g.NextWindowData.SizeConstraintRect.Min.x = ImMax(g.NextWindowData.SizeConstraintRect.Min.x, w);
     }
@@ -1926,9 +1949,9 @@ bool ImGui::BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags
         else if (flags & ImGuiComboFlags_HeightSmall)  popup_max_height_in_items = 4;
         else if (flags & ImGuiComboFlags_HeightLarge)  popup_max_height_in_items = 20;
         ImVec2 constraint_min(0.0f, 0.0f), constraint_max(FLT_MAX, FLT_MAX);
-        if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize) == 0 || g.NextWindowData.SizeVal.x <= 0.0f) // Don't apply constraints if user specified a size
+        if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSize) == 0 || g.NextWindowData.SizeVal.x <= 0.0f) // Don't apply constraints if user specified a size
             constraint_min.x = w;
-        if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize) == 0 || g.NextWindowData.SizeVal.y <= 0.0f)
+        if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSize) == 0 || g.NextWindowData.SizeVal.y <= 0.0f)
             constraint_max.y = CalcMaxPopupHeightFromItemCount(popup_max_height_in_items);
         SetNextWindowSizeConstraints(constraint_min, constraint_max);
     }
@@ -2043,7 +2066,7 @@ static const char* Items_SingleStringGetter(void* data, int idx)
     {
         if (idx == items_count)
             break;
-        p += strlen(p) + 1;
+        p += ImStrlen(p) + 1;
         items_count++;
     }
     return *p ? p : NULL;
@@ -2060,7 +2083,7 @@ bool ImGui::Combo(const char* label, int* current_item, const char* (*getter)(vo
         preview_value = getter(user_data, *current_item);
 
     // The old Combo() API exposed "popup_max_height_in_items". The new more general BeginCombo() API doesn't have/need it, but we emulate it here.
-    if (popup_max_height_in_items != -1 && !(g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint))
+    if (popup_max_height_in_items != -1 && !(g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSizeConstraint))
         SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));
 
     if (!BeginCombo(label, preview_value, ImGuiComboFlags_None))
@@ -2111,7 +2134,7 @@ bool ImGui::Combo(const char* label, int* current_item, const char* items_separa
     const char* p = items_separated_by_zeros;       // FIXME-OPT: Avoid computing this, or at least only when combo is open
     while (*p)
     {
-        p += strlen(p) + 1;
+        p += ImStrlen(p) + 1;
         items_count++;
     }
     bool value_changed = Combo(label, current_item, Items_SingleStringGetter, (void*)items_separated_by_zeros, items_count, height_in_items);
@@ -2621,7 +2644,7 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data,
     bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id);
     if (!temp_input_is_active)
     {
-        // Tabbing or CTRL-clicking on Drag turns it into an InputText
+        // Tabbing or CTRL+click on Drag turns it into an InputText
         const bool clicked = hovered && IsMouseClicked(0, ImGuiInputFlags_None, id);
         const bool double_clicked = (hovered && g.IO.MouseClickedCount[0] == 2 && TestKeyOwner(ImGuiKey_MouseLeft, id));
         const bool make_active = (clicked || double_clicked || g.NavActivateId == id);
@@ -3225,7 +3248,7 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat
     bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id);
     if (!temp_input_is_active)
     {
-        // Tabbing or CTRL-clicking on Slider turns it into an input box
+        // Tabbing or CTRL+click on Slider turns it into an input box
         const bool clicked = hovered && IsMouseClicked(0, ImGuiInputFlags_None, id);
         const bool make_active = (clicked || g.NavActivateId == id);
         if (make_active && clicked)
@@ -3878,7 +3901,7 @@ static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char**
         line_count++;
         if (s_eol == NULL)
         {
-            s = s + strlen(s);
+            s = s + ImStrlen(s);
             break;
         }
         s = s_eol + 1;
@@ -4166,7 +4189,7 @@ void ImGuiInputTextState::OnCharPressed(unsigned int c)
     // The changes we had to make to stb_textedit_key made it very much UTF-8 specific which is not too great.
     char utf8[5];
     ImTextCharToUtf8(utf8, c);
-    stb_textedit_text(this, Stb, utf8, (int)strlen(utf8));
+    stb_textedit_text(this, Stb, utf8, (int)ImStrlen(utf8));
     CursorFollow = true;
     CursorAnimReset();
 }
@@ -4217,7 +4240,7 @@ void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, cons
 
     // Grow internal buffer if needed
     const bool is_resizable = (Flags & ImGuiInputTextFlags_CallbackResize) != 0;
-    const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)strlen(new_text);
+    const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)ImStrlen(new_text);
     if (new_text_len + BufTextLen >= BufSize)
     {
         if (!is_resizable)
@@ -4251,7 +4274,7 @@ void ImGui::PushPasswordFont()
     ImGuiContext& g = *GImGui;
     ImFont* in_font = g.Font;
     ImFont* out_font = &g.InputTextPasswordFont;
-    const ImFontGlyph* glyph = in_font->FindGlyph('*');
+    ImFontGlyph* glyph = in_font->FindGlyph('*');
     out_font->FontSize = in_font->FontSize;
     out_font->Scale = in_font->Scale;
     out_font->Ascent = in_font->Ascent;
@@ -4273,7 +4296,13 @@ static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, Im
     if (c < 0x20)
     {
         bool pass = false;
-        pass |= (c == '\n') && (flags & ImGuiInputTextFlags_Multiline) != 0; // Note that an Enter KEY will emit \r and be ignored (we poll for KEY in InputText() code)
+        pass |= (c == '\n') && (flags & ImGuiInputTextFlags_Multiline) != 0;    // Note that an Enter KEY will emit \r and be ignored (we poll for KEY in InputText() code)
+        if (c == '\n' && input_source_is_clipboard && (flags & ImGuiInputTextFlags_Multiline) == 0) // In single line mode, replace \n with a space
+        {
+            c = *p_char = ' ';
+            pass = true;
+        }
+        pass |= (c == '\n') && (flags & ImGuiInputTextFlags_Multiline) != 0;
         pass |= (c == '\t') && (flags & ImGuiInputTextFlags_AllowTabInput) != 0;
         if (!pass)
             return false;
@@ -4539,7 +4568,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
     const bool init_state = (init_make_active || user_scroll_active);
     if (init_reload_from_user_buf)
     {
-        int new_len = (int)strlen(buf);
+        int new_len = (int)ImStrlen(buf);
         IM_ASSERT(new_len + 1 <= buf_size && "Is your input buffer properly zero-terminated?");
         state->WantReloadUserBuf = false;
         InputTextReconcileUndoState(state, state->TextA.Data, state->TextLen, buf, new_len);
@@ -4561,7 +4590,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
 
         // Take a copy of the initial buffer value.
         // From the moment we focused we are normally ignoring the content of 'buf' (unless we are in read-only mode)
-        const int buf_len = (int)strlen(buf);
+        const int buf_len = (int)ImStrlen(buf);
         IM_ASSERT(buf_len + 1 <= buf_size && "Is your input buffer properly zero-terminated?");
         state->TextToRevertTo.resize(buf_len + 1);    // UTF-8. we use +1 to make sure that .Data is always pointing to at least an empty string.
         memcpy(state->TextToRevertTo.Data, buf, buf_len + 1);
@@ -4646,7 +4675,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
 
         // Read-only mode always ever read from source buffer. Refresh TextLen when active.
         if (is_readonly && state != NULL)
-            state->TextLen = (int)strlen(buf);
+            state->TextLen = (int)ImStrlen(buf);
         //if (is_readonly && state != NULL)
         //    state->TextA.clear(); // Uncomment to facilitate debugging, but we otherwise prefer to keep/amortize th allocation.
     }
@@ -4669,7 +4698,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
 
     // Select the buffer to render.
     const bool buf_display_from_state = (render_cursor || render_selection || g.ActiveId == id) && !is_readonly && state;
-    const bool is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0);
+    bool is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0);
 
     // Password pushes a temporary font with only a fallback glyph
     if (is_password && !is_displaying_hint)
@@ -4803,14 +4832,14 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
         const bool is_wordmove_key_down = is_osx ? io.KeyAlt : io.KeyCtrl;                     // OS X style: Text editing cursor movement using Alt instead of Ctrl
         const bool is_startend_key_down = is_osx && io.KeyCtrl && !io.KeySuper && !io.KeyAlt;  // OS X style: Line/Text Start and End using Cmd+Arrows instead of Home/End
 
-        // Using Shortcut() with ImGuiInputFlags_RouteFocused (default policy) to allow routing operations for other code (e.g. calling window trying to use CTRL+A and CTRL+B: formet would be handled by InputText)
+        // Using Shortcut() with ImGuiInputFlags_RouteFocused (default policy) to allow routing operations for other code (e.g. calling window trying to use CTRL+A and CTRL+B: former would be handled by InputText)
         // Otherwise we could simply assume that we own the keys as we are active.
         const ImGuiInputFlags f_repeat = ImGuiInputFlags_Repeat;
         const bool is_cut   = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_X, f_repeat, id) || Shortcut(ImGuiMod_Shift | ImGuiKey_Delete, f_repeat, id)) && !is_readonly && !is_password && (!is_multiline || state->HasSelection());
         const bool is_copy  = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_C, 0,        id) || Shortcut(ImGuiMod_Ctrl  | ImGuiKey_Insert, 0,        id)) && !is_password && (!is_multiline || state->HasSelection());
         const bool is_paste = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_V, f_repeat, id) || Shortcut(ImGuiMod_Shift | ImGuiKey_Insert, f_repeat, id)) && !is_readonly;
         const bool is_undo  = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_Z, f_repeat, id)) && !is_readonly && is_undoable;
-        const bool is_redo =  (Shortcut(ImGuiMod_Ctrl | ImGuiKey_Y, f_repeat, id) || (is_osx && Shortcut(ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Z, f_repeat, id))) && !is_readonly && is_undoable;
+        const bool is_redo =  (Shortcut(ImGuiMod_Ctrl | ImGuiKey_Y, f_repeat, id) || Shortcut(ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Z, f_repeat, id)) && !is_readonly && is_undoable;
         const bool is_select_all = Shortcut(ImGuiMod_Ctrl | ImGuiKey_A, 0, id);
 
         // We allow validate/cancel with Nav source (gamepad) to makes it easier to undo an accidental NavInput press with no keyboard wired, but otherwise it isn't very useful.
@@ -4925,7 +4954,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
             if (const char* clipboard = GetClipboardText())
             {
                 // Filter pasted buffer
-                const int clipboard_len = (int)strlen(clipboard);
+                const int clipboard_len = (int)ImStrlen(clipboard);
                 ImVector clipboard_filtered;
                 clipboard_filtered.reserve(clipboard_len + 1);
                 for (const char* s = clipboard; *s != 0; )
@@ -4937,7 +4966,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
                         continue;
                     char c_utf8[5];
                     ImTextCharToUtf8(c_utf8, c);
-                    int out_len = (int)strlen(c_utf8);
+                    int out_len = (int)ImStrlen(c_utf8);
                     clipboard_filtered.resize(clipboard_filtered.Size + out_len);
                     memcpy(clipboard_filtered.Data + clipboard_filtered.Size - out_len, c_utf8, out_len);
                 }
@@ -5067,7 +5096,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
                     if (buf_dirty)
                     {
                         // Callback may update buffer and thus set buf_dirty even in read-only mode.
-                        IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text!
+                        IM_ASSERT(callback_data.BufTextLen == (int)ImStrlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text!
                         InputTextReconcileUndoState(state, state->CallbackTextBackup.Data, state->CallbackTextBackup.Size - 1, callback_data.Buf, callback_data.BufTextLen);
                         state->TextLen = callback_data.BufTextLen;  // Assume correct length and valid UTF-8 from user, saves us an extra strlen()
                         state->CursorAnimReset();
@@ -5151,10 +5180,22 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
     const int buf_display_max_length = 2 * 1024 * 1024;
     const char* buf_display = buf_display_from_state ? state->TextA.Data : buf; //-V595
     const char* buf_display_end = NULL; // We have specialized paths below for setting the length
+
+    // Display hint when contents is empty
+    // At this point we need to handle the possibility that a callback could have modified the underlying buffer (#8368)
+    const bool new_is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0);
+    if (new_is_displaying_hint != is_displaying_hint)
+    {
+        if (is_password && !is_displaying_hint)
+            PopFont();
+        is_displaying_hint = new_is_displaying_hint;
+        if (is_password && !is_displaying_hint)
+            PushPasswordFont();
+    }
     if (is_displaying_hint)
     {
         buf_display = hint;
-        buf_display_end = hint + strlen(hint);
+        buf_display_end = hint + ImStrlen(hint);
     }
 
     // Render text. We currently only render selection when the widget is active or while scrolling.
@@ -5187,7 +5228,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
             int line_count = 1;
             if (is_multiline)
             {
-                for (const char* s = text_begin; (s = (const char*)memchr(s, '\n', (size_t)(text_end - s))) != NULL; s++)
+                for (const char* s = text_begin; (s = (const char*)ImMemchr(s, '\n', (size_t)(text_end - s))) != NULL; s++)
                 {
                     if (cursor_line_no == -1 && s >= cursor_ptr) { cursor_line_no = line_count; }
                     if (selmin_line_no == -1 && s >= selmin_ptr) { selmin_line_no = line_count; }
@@ -5265,7 +5306,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
                     break;
                 if (rect_pos.y < clip_rect.y)
                 {
-                    p = (const char*)memchr((void*)p, '\n', text_selected_end - p);
+                    p = (const char*)ImMemchr((void*)p, '\n', text_selected_end - p);
                     p = p ? p + 1 : text_selected_end;
                 }
                 else
@@ -5317,7 +5358,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
         else if (!is_displaying_hint && g.ActiveId == id)
             buf_display_end = buf_display + state->TextLen;
         else if (!is_displaying_hint)
-            buf_display_end = buf_display + strlen(buf_display);
+            buf_display_end = buf_display + ImStrlen(buf_display);
 
         if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length)
         {
@@ -5462,7 +5503,7 @@ static void ColorEditRestoreHS(const float* col, float* H, float* S, float* V)
 
 // Edit colors components (each component in 0.0f..1.0f range).
 // See enum ImGuiColorEditFlags_ for available options. e.g. Only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
-// With typical options: Left-click on color square to open color picker. Right-click to open option menu. CTRL-Click over input fields to edit them and TAB to go to next item.
+// With typical options: Left-click on color square to open color picker. Right-click to open option menu. CTRL+Click over input fields to edit them and TAB to go to next item.
 bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags)
 {
     ImGuiWindow* window = GetCurrentWindow();
@@ -6166,7 +6207,7 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl
         if (g.Style.FrameBorderSize > 0.0f)
             RenderFrameBorder(bb.Min, bb.Max, rounding);
         else
-            window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); // Color button are often in need of some sort of border
+            window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); // Color buttons are often in need of some sort of border
     }
 
     // Drag and Drop Source
@@ -7088,7 +7129,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl
 
     // Text stays at the submission position. Alignment/clipping extents ignore SpanAllColumns.
     if (is_visible)
-        RenderTextClipped(pos, ImVec2(window->WorkRect.Max.x, pos.y + size.y), label, NULL, &label_size, style.SelectableTextAlign, &bb);
+        RenderTextClipped(pos, ImVec2(ImMin(pos.x + size.x, window->WorkRect.Max.x), pos.y + size.y), label, NULL, &label_size, style.SelectableTextAlign, &bb);
 
     // Automatically close popups
     if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_NoAutoClosePopups) && (g.LastItemData.ItemFlags & ImGuiItemFlags_AutoClosePopups))
@@ -7151,7 +7192,7 @@ ImGuiTypingSelectRequest* ImGui::GetTypingSelectRequest(ImGuiTypingSelectFlags f
 
     // Append to buffer
     const int buffer_max_len = IM_ARRAYSIZE(data->SearchBuffer) - 1;
-    int buffer_len = (int)strlen(data->SearchBuffer);
+    int buffer_len = (int)ImStrlen(data->SearchBuffer);
     bool select_request = false;
     for (ImWchar w : g.IO.InputQueueCharacters)
     {
@@ -8834,7 +8875,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
     if (g.MenusIdSubmittedThisFrame.contains(id))
     {
         if (menu_is_open)
-            menu_is_open = BeginPopupEx(id, window_flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
+            menu_is_open = BeginPopupMenuEx(id, label, window_flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
         else
             g.NextWindowData.ClearFlags();          // we behave like Begin() and need to consume those values
         return menu_is_open;
@@ -8865,7 +8906,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
     const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_NoSetKeyOwner | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_NoAutoClosePopups;
     if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
     {
-        // Menu inside an horizontal menu bar
+        // Menu inside a horizontal menu bar
         // Selectable extend their highlight by half ItemSpacing in each direction.
         // For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin()
         popup_pos = ImVec2(pos.x - 1.0f - IM_TRUNC(style.ItemSpacing.x * 0.5f), pos.y - style.FramePadding.y + window->MenuBarHeight);
@@ -8996,7 +9037,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
         ImGuiLastItemData last_item_in_parent = g.LastItemData;
         SetNextWindowPos(popup_pos, ImGuiCond_Always);                  // Note: misleading: the value will serve as reference for FindBestWindowPosForPopup(), not actual pos.
         PushStyleVar(ImGuiStyleVar_ChildRounding, style.PopupRounding); // First level will use _PopupRounding, subsequent will use _ChildRounding
-        menu_is_open = BeginPopupEx(id, window_flags);                  // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
+        menu_is_open = BeginPopupMenuEx(id, label, window_flags); // menu_is_open may be 'false' when the popup is completely clipped (e.g. zero size display)
         PopStyleVar();
         if (menu_is_open)
         {
@@ -10078,7 +10119,7 @@ bool    ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open,
     else
     {
         tab->NameOffset = (ImS32)tab_bar->TabsNames.size();
-        tab_bar->TabsNames.append(label, label + strlen(label) + 1);
+        tab_bar->TabsNames.append(label, label + ImStrlen(label) + 1);
     }
 
     // Update selected tab
@@ -10355,13 +10396,24 @@ void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb,
     //  'g.ActiveId==close_button_id' will be true when we are holding on the close button, in which case both hovered booleans are false
     bool close_button_pressed = false;
     bool close_button_visible = false;
-    if (close_button_id != 0)
-        if (is_contents_visible || bb.GetWidth() >= ImMax(button_sz, g.Style.TabMinWidthForCloseButton))
-            if (g.HoveredId == tab_id || g.HoveredId == close_button_id || g.ActiveId == tab_id || g.ActiveId == close_button_id)
-                close_button_visible = true;
-    bool unsaved_marker_visible = (flags & ImGuiTabItemFlags_UnsavedDocument) != 0 && (button_pos.x + button_sz <= bb.Max.x);
+    bool is_hovered = g.HoveredId == tab_id || g.HoveredId == close_button_id || g.ActiveId == tab_id || g.ActiveId == close_button_id; // Any interaction account for this too.
 
-    if (close_button_visible)
+    if (close_button_id != 0)
+    {
+        if (is_contents_visible)
+            close_button_visible = (g.Style.TabCloseButtonMinWidthSelected < 0.0f) ? true : (is_hovered && bb.GetWidth() >= ImMax(button_sz, g.Style.TabCloseButtonMinWidthSelected));
+        else
+            close_button_visible = (g.Style.TabCloseButtonMinWidthUnselected < 0.0f) ? true : (is_hovered && bb.GetWidth() >= ImMax(button_sz, g.Style.TabCloseButtonMinWidthUnselected));
+    }
+
+    // When tabs/document is unsaved, the unsaved marker takes priority over the close button.
+    const bool unsaved_marker_visible = (flags & ImGuiTabItemFlags_UnsavedDocument) != 0 && (button_pos.x + button_sz <= bb.Max.x) && (!close_button_visible || !is_hovered);
+    if (unsaved_marker_visible)
+    {
+        const ImRect bullet_bb(button_pos, button_pos + ImVec2(button_sz, button_sz));
+        RenderBullet(draw_list, bullet_bb.GetCenter(), GetColorU32(ImGuiCol_Text));
+    }
+    else if (close_button_visible)
     {
         ImGuiLastItemData last_item_backup = g.LastItemData;
         if (CloseButton(close_button_id, button_pos))
@@ -10369,14 +10421,9 @@ void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb,
         g.LastItemData = last_item_backup;
 
         // Close with middle mouse button
-        if (!(flags & ImGuiTabItemFlags_NoCloseWithMiddleMouseButton) && IsMouseClicked(2))
+        if (is_hovered && !(flags & ImGuiTabItemFlags_NoCloseWithMiddleMouseButton) && IsMouseClicked(2))
             close_button_pressed = true;
     }
-    else if (unsaved_marker_visible)
-    {
-        const ImRect bullet_bb(button_pos, button_pos + ImVec2(button_sz, button_sz));
-        RenderBullet(draw_list, bullet_bb.GetCenter(), GetColorU32(ImGuiCol_Text));
-    }
 
     // This is all rather complicated
     // (the main idea is that because the close button only appears on hover, we don't want it to alter the ellipsis position)

From 5393d724c52cbc392635dc1b905076ec061f9c21 Mon Sep 17 00:00:00 2001
From: chaoticgd <43898262+chaoticgd@users.noreply.github.com>
Date: Wed, 19 Mar 2025 01:48:52 +0000
Subject: [PATCH 017/162] Debugger: Fix crash during breakpoint deletion

---
 pcsx2-qt/Debugger/Breakpoints/BreakpointModel.cpp  | 12 +++++++++---
 pcsx2-qt/Debugger/Breakpoints/BreakpointWidget.cpp | 14 ++++++--------
 2 files changed, 15 insertions(+), 11 deletions(-)

diff --git a/pcsx2-qt/Debugger/Breakpoints/BreakpointModel.cpp b/pcsx2-qt/Debugger/Breakpoints/BreakpointModel.cpp
index e702ae80c2..7f8deb09cf 100644
--- a/pcsx2-qt/Debugger/Breakpoints/BreakpointModel.cpp
+++ b/pcsx2-qt/Debugger/Breakpoints/BreakpointModel.cpp
@@ -59,7 +59,7 @@ int BreakpointModel::columnCount(const QModelIndex&) const
 
 QVariant BreakpointModel::data(const QModelIndex& index, int role) const
 {
-	size_t row = static_cast(index.row());
+	const size_t row = static_cast(index.row());
 	if (!index.isValid() || row >= m_breakpoints.size())
 		return QVariant();
 
@@ -298,7 +298,7 @@ Qt::ItemFlags BreakpointModel::flags(const QModelIndex& index) const
 
 bool BreakpointModel::setData(const QModelIndex& index, const QVariant& value, int role)
 {
-	size_t row = static_cast(index.row());
+	const size_t row = static_cast(index.row());
 	if (!index.isValid() || row >= m_breakpoints.size())
 		return false;
 
@@ -401,9 +401,14 @@ bool BreakpointModel::setData(const QModelIndex& index, const QVariant& value, i
 
 bool BreakpointModel::removeRows(int row, int count, const QModelIndex& index)
 {
+	const size_t begin_index = static_cast(row);
+	const size_t end_index = static_cast(row + count);
+	if (end_index > m_breakpoints.size())
+		return false;
+
 	beginRemoveRows(index, row, row + count - 1);
 
-	for (int i = row; i < row + count; i++)
+	for (size_t i = begin_index; i < end_index; i++)
 	{
 		auto bp_mc = m_breakpoints.at(i);
 
@@ -420,6 +425,7 @@ bool BreakpointModel::removeRows(int row, int count, const QModelIndex& index)
 			});
 		}
 	}
+
 	const auto begin = m_breakpoints.begin() + row;
 	const auto end = begin + count;
 	m_breakpoints.erase(begin, end);
diff --git a/pcsx2-qt/Debugger/Breakpoints/BreakpointWidget.cpp b/pcsx2-qt/Debugger/Breakpoints/BreakpointWidget.cpp
index 2348db2d9b..41a97d0a90 100644
--- a/pcsx2-qt/Debugger/Breakpoints/BreakpointWidget.cpp
+++ b/pcsx2-qt/Debugger/Breakpoints/BreakpointWidget.cpp
@@ -111,16 +111,14 @@ void BreakpointWidget::contextDelete()
 	if (!selModel->hasSelection())
 		return;
 
-	QModelIndexList rows = selModel->selectedIndexes();
+	QModelIndexList indices = selModel->selectedIndexes();
 
-	std::sort(rows.begin(), rows.end(), [](const QModelIndex& a, const QModelIndex& b) {
-		return a.row() > b.row();
-	});
+	std::set rows;
+	for (QModelIndex index : indices)
+		rows.emplace(index.row());
 
-	for (const QModelIndex& index : rows)
-	{
-		m_model->removeRows(index.row(), 1);
-	}
+	for (auto row = rows.rbegin(); row != rows.rend(); row++)
+		m_model->removeRows(*row, 1);
 }
 
 void BreakpointWidget::contextNew()

From 7910506b3ca6abe9d47742a716b67ce84ad50f2a Mon Sep 17 00:00:00 2001
From: TellowKrinkle 
Date: Tue, 18 Mar 2025 02:47:20 -0500
Subject: [PATCH 018/162] GS:HW: Avoid using blend + fbfetch for AFAIL RGB_ONLY

---
 bin/resources/shaders/opengl/tfx_fs.glsl |  9 ++++++--
 bin/resources/shaders/vulkan/tfx.glsl    |  9 ++++++--
 pcsx2/GS/Renderers/HW/GSRendererHW.cpp   | 27 ++++++++++++++++--------
 pcsx2/GS/Renderers/Metal/tfx.metal       |  7 +++++-
 pcsx2/ShaderCacheVersion.h               |  2 +-
 5 files changed, 39 insertions(+), 15 deletions(-)

diff --git a/bin/resources/shaders/opengl/tfx_fs.glsl b/bin/resources/shaders/opengl/tfx_fs.glsl
index 47c71600cf..bb004e3891 100644
--- a/bin/resources/shaders/opengl/tfx_fs.glsl
+++ b/bin/resources/shaders/opengl/tfx_fs.glsl
@@ -25,7 +25,8 @@
 #define SW_AD_TO_HW (PS_BLEND_C == 1 && PS_A_MASKED)
 #define PS_PRIMID_INIT (PS_DATE == 1 || PS_DATE == 2)
 #define NEEDS_RT_EARLY (PS_TEX_IS_FB == 1 || PS_DATE >= 5)
-#define NEEDS_RT (NEEDS_RT_EARLY || (!PS_PRIMID_INIT && (PS_FBMASK || SW_BLEND_NEEDS_RT || SW_AD_TO_HW)))
+#define NEEDS_RT_FOR_AFAIL (PS_AFAIL == 3 && PS_NO_COLOR1)
+#define NEEDS_RT (NEEDS_RT_EARLY || NEEDS_RT_FOR_AFAIL || (!PS_PRIMID_INIT && (PS_FBMASK || SW_BLEND_NEEDS_RT || SW_AD_TO_HW)))
 #define NEEDS_TEX (PS_TFX != 4)
 
 layout(std140, binding = 0) uniform cb21
@@ -1114,7 +1115,7 @@ void ps_main()
 
 	ps_fbmask(C);
 
-#if PS_AFAIL == 3 // RGB_ONLY
+#if PS_AFAIL == 3 && !PS_NO_COLOR1 // RGB_ONLY
 	// Use alpha blend factor to determine whether to update A.
 	alpha_blend.a = float(atst_pass);
 #endif
@@ -1130,6 +1131,10 @@ void ps_main()
 	#else
 		SV_Target0.rgb = C.rgb / 255.0f;
 	#endif
+	#if PS_AFAIL == 3 && !PS_NO_COLOR1 // RGB_ONLY, no dual src blend
+		if (!atst_pass)
+			SV_Target0.a = sample_from_rt().a;
+	#endif
 	#if !PS_NO_COLOR1
 		SV_Target1 = alpha_blend;
 	#endif
diff --git a/bin/resources/shaders/vulkan/tfx.glsl b/bin/resources/shaders/vulkan/tfx.glsl
index 215b00bc0c..a7050b193d 100644
--- a/bin/resources/shaders/vulkan/tfx.glsl
+++ b/bin/resources/shaders/vulkan/tfx.glsl
@@ -299,8 +299,9 @@ void main()
 #define SW_BLEND (PS_BLEND_A || PS_BLEND_B || PS_BLEND_D)
 #define SW_BLEND_NEEDS_RT (SW_BLEND && (PS_BLEND_A == 1 || PS_BLEND_B == 1 || PS_BLEND_C == 1 || PS_BLEND_D == 1))
 #define SW_AD_TO_HW (PS_BLEND_C == 1 && PS_A_MASKED)
+#define AFAIL_NEEDS_RT (PS_AFAIL == 3 && PS_NO_COLOR1)
 
-#define PS_FEEDBACK_LOOP_IS_NEEDED (PS_TEX_IS_FB == 1 || PS_FBMASK || SW_BLEND_NEEDS_RT || SW_AD_TO_HW || (PS_DATE >= 5))
+#define PS_FEEDBACK_LOOP_IS_NEEDED (PS_TEX_IS_FB == 1 || AFAIL_NEEDS_RT || PS_FBMASK || SW_BLEND_NEEDS_RT || SW_AD_TO_HW || (PS_DATE >= 5))
 
 #define NEEDS_TEX (PS_TFX != 4)
 
@@ -1381,7 +1382,7 @@ void main()
 
 	ps_fbmask(C);
 
-	#if PS_AFAIL == 3 // RGB_ONLY
+	#if PS_AFAIL == 3 && !PS_NO_COLOR1 // RGB_ONLY
 		// Use alpha blend factor to determine whether to update A.
 		alpha_blend.a = float(atst_pass);
 	#endif
@@ -1400,6 +1401,10 @@ void main()
 		#if !PS_NO_COLOR1
 			o_col1 = alpha_blend;
 		#endif
+		#if PS_AFAIL == 3 && PS_NO_COLOR1 // RGB_ONLY, no dual src blend
+			if (!atst_pass)
+				o_col0.a = sample_from_rt().a;
+		#endif
 	#endif
 
 	#if PS_ZCLAMP
diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp
index 981be4bff7..465f5f8320 100644
--- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp
+++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp
@@ -6240,23 +6240,32 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
 			// Blending might be off, ensure it's enabled.
 			// We write the alpha pass/fail to SRC1_ALPHA, which is used to update A.
 			m_conf.ps.afail = AFAIL_RGB_ONLY;
-			m_conf.ps.no_color1 = false;
-			if (!m_conf.blend.enable)
+			if ((features.framebuffer_fetch && m_conf.require_one_barrier) || m_conf.require_full_barrier)
 			{
-				m_conf.blend = GSHWDrawConfig::BlendState(true, GSDevice::CONST_ONE, GSDevice::CONST_ZERO,
-					GSDevice::OP_ADD, GSDevice::SRC1_ALPHA, GSDevice::INV_SRC1_ALPHA, false, 0);
+				// We're reading the rt anyways, use it for AFAIL
+				// This ensures we don't attempt to use fbfetch + blend, which breaks Intel GPUs on Metal
+				// Setting afail to RGB_ONLY without enabling color1 will enable this mode in the shader, so nothing more to do here.
 			}
 			else
 			{
-				if (m_conf.blend_multi_pass.enable)
+				m_conf.ps.no_color1 = false;
+				if (!m_conf.blend.enable)
 				{
-					m_conf.blend_multi_pass.blend.src_factor_alpha = GSDevice::SRC1_ALPHA;
-					m_conf.blend_multi_pass.blend.dst_factor_alpha = GSDevice::INV_SRC1_ALPHA;
+					m_conf.blend = GSHWDrawConfig::BlendState(true, GSDevice::CONST_ONE, GSDevice::CONST_ZERO,
+						GSDevice::OP_ADD, GSDevice::SRC1_ALPHA, GSDevice::INV_SRC1_ALPHA, false, 0);
 				}
 				else
 				{
-					m_conf.blend.src_factor_alpha = GSDevice::SRC1_ALPHA;
-					m_conf.blend.dst_factor_alpha = GSDevice::INV_SRC1_ALPHA;
+					if (m_conf.blend_multi_pass.enable)
+					{
+						m_conf.blend_multi_pass.blend.src_factor_alpha = GSDevice::SRC1_ALPHA;
+						m_conf.blend_multi_pass.blend.dst_factor_alpha = GSDevice::INV_SRC1_ALPHA;
+					}
+					else
+					{
+						m_conf.blend.src_factor_alpha = GSDevice::SRC1_ALPHA;
+						m_conf.blend.dst_factor_alpha = GSDevice::INV_SRC1_ALPHA;
+					}
 				}
 			}
 
diff --git a/pcsx2/GS/Renderers/Metal/tfx.metal b/pcsx2/GS/Renderers/Metal/tfx.metal
index aacc856e4d..48cd9c7095 100644
--- a/pcsx2/GS/Renderers/Metal/tfx.metal
+++ b/pcsx2/GS/Renderers/Metal/tfx.metal
@@ -97,7 +97,8 @@ constant bool SW_BLEND = (PS_BLEND_A != PS_BLEND_B) || PS_BLEND_D;
 constant bool SW_AD_TO_HW = (PS_BLEND_C == 1 && PS_A_MASKED);
 constant bool NEEDS_RT_FOR_BLEND = (((PS_BLEND_A != PS_BLEND_B) && (PS_BLEND_A == 1 || PS_BLEND_B == 1 || PS_BLEND_C == 1)) || PS_BLEND_D == 1 || SW_AD_TO_HW);
 constant bool NEEDS_RT_EARLY = PS_TEX_IS_FB || PS_DATE >= 5;
-constant bool NEEDS_RT = NEEDS_RT_EARLY || (!PS_PRIM_CHECKING_INIT && (PS_FBMASK || NEEDS_RT_FOR_BLEND));
+constant bool NEEDS_RT_FOR_AFAIL = PS_AFAIL == 3 && PS_NO_COLOR1;
+constant bool NEEDS_RT = NEEDS_RT_FOR_AFAIL || NEEDS_RT_EARLY || (!PS_PRIM_CHECKING_INIT && (PS_FBMASK || NEEDS_RT_FOR_BLEND));
 
 constant bool PS_COLOR0 = !PS_NO_COLOR;
 constant bool PS_COLOR1 = !PS_NO_COLOR1;
@@ -1202,8 +1203,12 @@ struct PSMain
 			alpha_blend.a = float(atst_pass);
 
 		if (PS_COLOR0)
+		{
 			out.c0.a = PS_RTA_CORRECTION ? C.a / 128.f : C.a / 255.f;
 			out.c0.rgb = PS_HDR ? float3(C.rgb / 65535.f) : C.rgb / 255.f;
+			if (PS_AFAIL == 3 && !PS_COLOR1 && !atst_pass) // Doing RGB_ONLY without COLOR1
+				out.c0.a = current_color.a;
+		}
 		if (PS_COLOR1)
 			out.c1 = alpha_blend;
 		if (PS_ZCLAMP)
diff --git a/pcsx2/ShaderCacheVersion.h b/pcsx2/ShaderCacheVersion.h
index af07babbae..66693958f6 100644
--- a/pcsx2/ShaderCacheVersion.h
+++ b/pcsx2/ShaderCacheVersion.h
@@ -3,4 +3,4 @@
 
 /// Version number for GS and other shaders. Increment whenever any of the contents of the
 /// shaders change, to invalidate the cache.
-static constexpr u32 SHADER_CACHE_VERSION = 59;
+static constexpr u32 SHADER_CACHE_VERSION = 60;

From 6ad825d1e0b881f6b4117e88a321b98e716bf5e6 Mon Sep 17 00:00:00 2001
From: chaoticgd <43898262+chaoticgd@users.noreply.github.com>
Date: Thu, 20 Mar 2025 23:12:43 +0000
Subject: [PATCH 019/162] Debugger: Fix Visual Studio filters

---
 pcsx2-qt/pcsx2-qt.vcxproj.filters | 237 +++++++++++++++++-------------
 1 file changed, 131 insertions(+), 106 deletions(-)

diff --git a/pcsx2-qt/pcsx2-qt.vcxproj.filters b/pcsx2-qt/pcsx2-qt.vcxproj.filters
index c62fffdba7..a6488172bd 100644
--- a/pcsx2-qt/pcsx2-qt.vcxproj.filters
+++ b/pcsx2-qt/pcsx2-qt.vcxproj.filters
@@ -22,15 +22,21 @@
     
       {ddb40cc4-9996-4ade-b647-eb549063553c}
     
-    
-      {f4084ca0-d9d5-4584-b9d2-063db9f67de2}
-    
     
       {ad04f939-64a0-4039-97aa-a38b8aa46855}
     
     
       {a622b871-62ae-4b70-b9b2-6ee30ce7fa7a}
     
+    
+      {694f359a-dd3c-4e05-b000-684acb0fe0b0}
+    
+    
+      {669ad4fe-0b5a-4722-946c-9e0742cebbca}
+    
+    
+      {63790c94-0680-417d-926c-a8b74118f80f}
+    
   
   
     
@@ -302,51 +308,6 @@
     
       Debugger
     
-    
-      Debugger\Breakpoints
-    
-    
-      Debugger\Breakpoints
-    
-    
-      Debugger\Breakpoints
-    
-    
-      Debugger\Docking
-    
-    
-      Debugger\Docking
-    
-    
-      Debugger\Docking
-    
-    
-      Debugger\Docking
-    
-    
-      Debugger\Docking
-    
-    
-      Debugger\Docking
-    
-    
-      Debugger\Docking
-    
-    
-      Debugger\Docking
-    
-    
-      Debugger\Memory
-    
-    
-      Debugger\Memory
-    
-    
-      Debugger\Memory
-    
-    
-      Debugger\Memory
-    
     
       moc
     
@@ -461,6 +422,72 @@
       Debugger\SymbolTree
     
     
+    
+      Debugger\Breakpoints
+    
+    
+      Debugger\Breakpoints
+    
+    
+      Debugger\Breakpoints
+    
+    
+      Debugger\Docking
+    
+    
+      Debugger\Docking
+    
+    
+      Debugger\Docking
+    
+    
+      Debugger\Docking
+    
+    
+      Debugger\Docking
+    
+    
+      Debugger\Docking
+    
+    
+      Debugger\Memory
+    
+    
+      Debugger\Memory
+    
+    
+      Debugger\Memory
+    
+    
+      Debugger\Memory
+    
+    
+      Debugger\Docking
+    
+    
+      Debugger\Docking
+    
+    
+      moc
+    
+    
+      moc
+    
+    
+      moc
+    
+    
+      moc
+    
+    
+      moc
+    
+    
+      moc
+    
+    
+      moc
+    
   
   
     
@@ -483,27 +510,6 @@
     
       Debugger
     
-    
-      Debugger\SymbolTree
-    
-    
-      Debugger\SymbolTree
-    
-    
-      Debugger\SymbolTree
-    
-    
-      Debugger\SymbolTree
-    
-    
-      Debugger\SymbolTree
-    
-    
-      Debugger\SymbolTree
-    
-    
-      Debugger\SymbolTree
-    
   
   
     
@@ -640,6 +646,16 @@
     
       Debugger
     
+    
+    
+    
+      Settings
+    
+    
+      Settings
+    
+    
+    
     
       Debugger\Breakpoints
     
@@ -667,17 +683,20 @@
     
       Debugger\Docking
     
-    
-      Debugger\Docking
+    
+      Debugger\SymbolTree
     
-    
-      Debugger\Docking
+    
+      Debugger\SymbolTree
     
-    
-      Debugger\Memory
+    
+      Debugger\SymbolTree
     
-    
-      Debugger\Memory
+    
+      Debugger\SymbolTree
+    
+    
+      Debugger\SymbolTree
     
     
       Debugger\Memory
@@ -685,15 +704,21 @@
     
       Debugger\Memory
     
-    
-    
-    
-      Settings
+    
+      Debugger\Memory
     
-    
-      Settings
+    
+      Debugger\Memory
+    
+    
+      Debugger\Docking
+    
+    
+      Debugger\Docking
+    
+    
+      Debugger\SymbolTree
     
-    
   
   
     
@@ -815,27 +840,6 @@
     
       Debugger
     
-    
-      Debugger\Breakpoints
-    
-    
-      Debugger\Breakpoints
-    
-    
-      Debugger\Docking
-    
-    
-      Debugger\Docking
-    
-    
-      Debugger\Memory
-    
-    
-      Debugger\Memory
-    
-    
-      Debugger\Memory
-    
     
       Settings
     
@@ -885,6 +889,27 @@
     
       Settings
     
+    
+      Debugger\Breakpoints
+    
+    
+      Debugger\Breakpoints
+    
+    
+      Debugger\Memory
+    
+    
+      Debugger\Memory
+    
+    
+      Debugger\Memory
+    
+    
+      Debugger\Docking
+    
+    
+      Debugger\Docking
+    
   
   
     
@@ -908,4 +933,4 @@
       Translations
     
   
-
+
\ No newline at end of file

From aae070f8267725e0677433f9d257a9a5e183a6ca Mon Sep 17 00:00:00 2001
From: TellowKrinkle 
Date: Tue, 18 Mar 2025 22:36:55 -0500
Subject: [PATCH 020/162] GS:MTL: Avoid WC memory on Ryzen hackintoshes

---
 pcsx2/GS/Renderers/Metal/GSDeviceMTL.h  |  1 +
 pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm | 13 ++++++++++---
 2 files changed, 11 insertions(+), 3 deletions(-)

diff --git a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.h b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.h
index 24dd039f6d..27b39357a8 100644
--- a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.h
+++ b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.h
@@ -219,6 +219,7 @@ public:
 	MRCOwned> m_draw_sync_fence;
 	MRCOwned m_fn_constants;
 	MRCOwned m_hw_vertex;
+	MTLResourceOptions m_resource_options_shared_wc;
 
 	// Previously in MetalHostDisplay.
 	MRCOwned m_view;
diff --git a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm
index ac5cb818d3..758e6889f4 100644
--- a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm
+++ b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm
@@ -10,6 +10,7 @@
 #include "common/Console.h"
 #include "common/HostSys.h"
 
+#include "cpuinfo.h"
 #include "imgui.h"
 
 #ifdef __APPLE__
@@ -90,6 +91,12 @@ GSDeviceMTL::GSDeviceMTL()
 	, m_dev(nil)
 {
 	m_backref->second = this;
+	m_resource_options_shared_wc = MTLResourceStorageModeShared | MTLResourceCPUCacheModeWriteCombined;
+#ifdef _M_X86
+	// WC memory doesn't work properly on AMD hackintoshes, and ends up being horribly slow even when only writing
+	if (cpuinfo_get_core(0)->vendor == cpuinfo_vendor_amd)
+		m_resource_options_shared_wc = MTLResourceStorageModeShared;
+#endif
 }
 
 GSDeviceMTL::~GSDeviceMTL()
@@ -107,7 +114,7 @@ GSDeviceMTL::Map GSDeviceMTL::Allocate(UploadBuffer& buffer, size_t amt)
 		size_t newsize = std::max(buffer.usage.Size() * 2, 4096);
 		while (newsize < amt)
 			newsize *= 2;
-		MTLResourceOptions options = MTLResourceStorageModeShared | MTLResourceCPUCacheModeWriteCombined;
+		MTLResourceOptions options = m_resource_options_shared_wc;
 		buffer.mtlbuffer = MRCTransfer([m_dev.dev newBufferWithLength:newsize options:options]);
 		pxAssertRel(buffer.mtlbuffer, "Failed to allocate MTLBuffer (out of memory?)");
 		buffer.buffer = [buffer.mtlbuffer contents];
@@ -148,7 +155,7 @@ GSDeviceMTL::Map GSDeviceMTL::Allocate(BufferPair& buffer, size_t amt)
 		size_t newsize = std::max(buffer.usage.Size() * 2, 4096);
 		while (newsize < amt)
 			newsize *= 2;
-		MTLResourceOptions options = MTLResourceStorageModeShared | MTLResourceCPUCacheModeWriteCombined;
+		MTLResourceOptions options = m_resource_options_shared_wc;
 		buffer.cpubuffer = MRCTransfer([m_dev.dev newBufferWithLength:newsize options:options]);
 		pxAssertRel(buffer.cpubuffer, "Failed to allocate MTLBuffer (out of memory?)");
 		buffer.buffer = [buffer.cpubuffer contents];
@@ -1277,7 +1284,7 @@ void GSDeviceMTL::UpdateTexture(id texture, u32 x, u32 y, u32 width,
 	id cmdbuf = [m_queue commandBuffer];
 	id enc = [cmdbuf blitCommandEncoder];
 	size_t bytes = data_stride * height;
-	MRCOwned> buf = MRCTransfer([m_dev.dev newBufferWithLength:bytes options:MTLResourceStorageModeShared | MTLResourceCPUCacheModeWriteCombined]);
+	MRCOwned> buf = MRCTransfer([m_dev.dev newBufferWithLength:bytes options:m_resource_options_shared_wc]);
 	memcpy([buf contents], data, bytes);
 	[enc copyFromBuffer:buf
 	       sourceOffset:0

From 2ac0420903ca672d9dbe67164bd45777fce07adf Mon Sep 17 00:00:00 2001
From: PCSX2 Bot 
Date: Mon, 24 Mar 2025 16:01:32 +0000
Subject: [PATCH 021/162] [ci skip] PAD: Update to latest controller database.

---
 bin/resources/game_controller_db.txt | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/bin/resources/game_controller_db.txt b/bin/resources/game_controller_db.txt
index 779ce0b06c..6e0d952548 100644
--- a/bin/resources/game_controller_db.txt
+++ b/bin/resources/game_controller_db.txt
@@ -610,6 +610,7 @@
 03000000921200004547000000000000,Retro Bit Sega Genesis Controller Adapter,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,lefttrigger:b7,rightshoulder:b5,righttrigger:b2,start:b6,x:b3,y:b4,platform:Windows,
 03000000790000001100000000000000,Retro Controller,a:b1,b:b2,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b6,lefttrigger:b7,rightshoulder:b4,righttrigger:b5,start:b9,x:b0,y:b3,platform:Windows,
 03000000830500006020000000000000,Retro Controller,a:b0,b:b1,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b5,rightshoulder:b8,righttrigger:b9,start:b7,x:b2,y:b3,platform:Windows,
+03000000632500007805000000000000,Retro Fighters Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
 0300000003040000c197000000000000,Retrode Adapter,a:b0,b:b4,back:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b3,x:b1,y:b5,platform:Windows,
 03000000bd12000013d0000000000000,Retrolink Sega Saturn Classic Controller,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b5,lefttrigger:b6,rightshoulder:b2,righttrigger:b7,start:b8,x:b3,y:b4,platform:Windows,
 03000000bd12000015d0000000000000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Windows,
@@ -757,8 +758,8 @@
 03000000ff1100004133000000000000,USB Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a4,righty:a2,start:b9,x:b3,y:b0,platform:Windows,
 03000000632500002305000000000000,USB Vibration Joystick,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
 03000000882800000305000000000000,V5 Game Pad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,x:b2,y:b3,platform:Windows,
-03000000790000001a18000000000000,Venom,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
-03000000790000001b18000000000000,Venom Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,
+03000000790000001a18000000000000,Venom PS4 Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
+03000000790000001b18000000000000,Venom PS4 Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
 030000006f0e00000302000000000000,Victrix PS4 Pro Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,
 030000006f0e00000702000000000000,Victrix PS4 Pro Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,
 0300000034120000adbe000000000000,vJoy Device,a:b0,b:b1,back:b15,dpdown:b6,dpleft:b7,dpright:b8,dpup:b5,guide:b16,leftshoulder:b9,leftstick:b13,lefttrigger:b11,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b14,righttrigger:b12,rightx:a3,righty:a4,start:b4,x:b2,y:b3,platform:Windows,
@@ -1288,7 +1289,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
 03000000451300000010000010010000,Genius Maxfire Grandias 12,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
 190000004b4800000010000000010000,GO-Advance Controller,a:b1,b:b0,back:b10,dpdown:b7,dpleft:b8,dpright:b9,dpup:b6,leftshoulder:b4,lefttrigger:b12,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b13,start:b15,x:b2,y:b3,platform:Linux,
 190000004b4800000010000001010000,GO-Advance Controller,a:b1,b:b0,back:b12,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,leftshoulder:b4,leftstick:b13,lefttrigger:b14,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b16,righttrigger:b15,start:b17,x:b2,y:b3,platform:Linux,
-190000004b4800000011000000010000,GO-Super Controller,a:b1,b:b0,back:b12,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b16,leftshoulder:b4,leftstick:b14,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b15,righttrigger:b7,rightx:a2,righty:a3,start:b13,x:b2,y:b3,platform:Linux,
+190000004b4800000011000000010000,GO-Super Gamepad,a:b0,b:b1,back:b12,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b16,leftshoulder:b4,leftstick:b14,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b15,righttrigger:b7,rightx:a2,righty:a3,start:b13,x:b3,y:b2,platform:Linux,
 03000000f0250000c183000010010000,Goodbetterbest Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
 03000000d11800000094000011010000,Google Stadia Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,
 05000000d11800000094000000010000,Google Stadia Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,
@@ -1425,6 +1426,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
 030000005e040000d102000003020000,Microsoft Xbox One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
 030000005e040000dd02000003020000,Microsoft Xbox One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
 030000005e040000ea02000008040000,Microsoft Xbox One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+030000005e040000ea0200000f050000,Microsoft Xbox One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
 060000005e040000120b000009050000,Microsoft Xbox One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
 030000005e040000e302000003020000,Microsoft Xbox One Elite,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
 030000005e040000000b000007040000,Microsoft Xbox One Elite 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b12,paddle2:b14,paddle3:b13,paddle4:b15,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
@@ -1718,6 +1720,8 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
 03000000100800000300000010010000,USB Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,
 03000000790000000600000007010000,USB gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,platform:Linux,
 03000000790000001100000000010000,USB Gamepad,a:b2,b:b1,back:b8,dpdown:a0,dpleft:a1,dpright:a2,dpup:a4,start:b9,platform:Linux,
+03000000790000001a18000011010000,Venom PS4 Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
+03000000790000001b18000011010000,Venom PS4 Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
 030000006f0e00000302000011010000,Victrix Pro Fightstick PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,
 030000006f0e00000702000011010000,Victrix Pro Fightstick PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,
 05000000ac0500003232000001000000,VR Box Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux,

From 23c495b93926bd6d0967293b7a3d28366562bc0b Mon Sep 17 00:00:00 2001
From: PCSX2 Bot 
Date: Sun, 23 Mar 2025 00:02:21 +0000
Subject: [PATCH 022/162] [ci skip] Qt: Update Base Translation.

---
 pcsx2-qt/Translations/pcsx2-qt_en.ts | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/pcsx2-qt/Translations/pcsx2-qt_en.ts b/pcsx2-qt/Translations/pcsx2-qt_en.ts
index e297420c2b..0115b49004 100644
--- a/pcsx2-qt/Translations/pcsx2-qt_en.ts
+++ b/pcsx2-qt/Translations/pcsx2-qt_en.ts
@@ -5898,12 +5898,12 @@ Do you want to overwrite?
 
     ExpressionParser
     
-        
+        
         Invalid memory access size %d.
         
     
     
-        
+        
         Invalid memory access (unaligned).
         
     

From 0f09b8df77376fedf8647107a474383cc35d1bff Mon Sep 17 00:00:00 2001
From: KamFretoZ <14798312+kamfretoz@users.noreply.github.com>
Date: Sat, 22 Mar 2025 20:25:00 +0700
Subject: [PATCH 023/162] BPM: GPU CLUT to FSUI

---
 pcsx2/ImGui/FullscreenUI.cpp | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/pcsx2/ImGui/FullscreenUI.cpp b/pcsx2/ImGui/FullscreenUI.cpp
index 0db1579b8a..c7b9402b60 100644
--- a/pcsx2/ImGui/FullscreenUI.cpp
+++ b/pcsx2/ImGui/FullscreenUI.cpp
@@ -4091,6 +4091,12 @@ void FullscreenUI::DrawGraphicsSettingsPage(SettingsInterface* bsi, bool show_ad
 				FSUI_NSTR("Enabled (All Primitives)"),
 			};
 
+			static constexpr const char* s_gpu_clut_options[] = {
+				FSUI_NSTR("Disabled (Default)"),
+				FSUI_NSTR("Enabled (Exact Match)"),
+				FSUI_NSTR("Enabled (Check Inside Target)"),
+			};
+
 			DrawIntListSetting(bsi, FSUI_CSTR("CPU Sprite Render Size"),
 				FSUI_CSTR("Uses software renderer to draw texture decompression-like sprites."), "EmuCore/GS",
 				"UserHacks_CPUSpriteRenderBW", 0, s_cpu_sprite_render_bw_options, std::size(s_cpu_sprite_render_bw_options), true);
@@ -4100,6 +4106,9 @@ void FullscreenUI::DrawGraphicsSettingsPage(SettingsInterface* bsi, bool show_ad
 			DrawIntListSetting(bsi, FSUI_CSTR("Software CLUT Render"),
 				FSUI_CSTR("Uses software renderer to draw texture CLUT points/sprites."), "EmuCore/GS", "UserHacks_CPUCLUTRender", 0,
 				s_cpu_clut_render_options, std::size(s_cpu_clut_render_options), true);
+			DrawIntListSetting(bsi, FSUI_CSTR("GPU Target CLUT"),
+				FSUI_CSTR("Try to detect when a game is drawing its own color palette and then renders it on the GPU with special handling."), "EmuCore/GS", "UserHacks_GPUTargetCLUTMode",
+				0, s_gpu_clut_options, std::size(s_gpu_clut_options), true, 0, manual_hw_fixes);
 			DrawIntSpinBoxSetting(bsi, FSUI_CSTR("Skip Draw Start"), FSUI_CSTR("Object range to skip drawing."), "EmuCore/GS",
 				"UserHacks_SkipDraw_Start", 0, 0, 5000, 1);
 			DrawIntSpinBoxSetting(bsi, FSUI_CSTR("Skip Draw End"), FSUI_CSTR("Object range to skip drawing."), "EmuCore/GS",

From 356ab30e8996ba5f9b1442a55f7b67a184a0c195 Mon Sep 17 00:00:00 2001
From: JordanTheToaster 
Date: Tue, 25 Mar 2025 09:29:19 +0000
Subject: [PATCH 024/162] 3rdparty: Update CPUInfo to commit
 5e3d2445e6a84d9599bee2bf78edbb4d80865e1d

---
 3rdparty/cpuinfo/include/cpuinfo.h            |  11 ++
 3rdparty/cpuinfo/src/arm/api.h                |  28 +--
 3rdparty/cpuinfo/src/arm/mach/init.c          |   1 -
 3rdparty/cpuinfo/src/arm/uarch.c              |   7 +-
 .../arm/windows/init-by-logical-sys-info.c    |  13 +-
 3rdparty/cpuinfo/src/arm/windows/init.c       | 187 +++++++++---------
 .../src/arm/windows/windows-arm-init.h        |  18 --
 3rdparty/cpuinfo/src/x86/isa.c                |  11 +-
 3rdparty/cpuinfo/src/x86/linux/cpuinfo.c      |   5 +-
 9 files changed, 149 insertions(+), 132 deletions(-)

diff --git a/3rdparty/cpuinfo/include/cpuinfo.h b/3rdparty/cpuinfo/include/cpuinfo.h
index 6eb4b8c38e..5f93819e8b 100644
--- a/3rdparty/cpuinfo/include/cpuinfo.h
+++ b/3rdparty/cpuinfo/include/cpuinfo.h
@@ -522,6 +522,8 @@ enum cpuinfo_uarch {
 	cpuinfo_uarch_falkor = 0x00400103,
 	/** Qualcomm Saphira. */
 	cpuinfo_uarch_saphira = 0x00400104,
+	/** Qualcomm Oryon. */
+	cpuinfo_uarch_oryon = 0x00400105,
 
 	/** Nvidia Denver. */
 	cpuinfo_uarch_denver = 0x00500100,
@@ -821,6 +823,7 @@ struct cpuinfo_x86_isa {
 	bool avx512_4vnniw;
 	bool avx512_4fmaps;
 	bool avx10_1;
+	bool avx10_2;
 	bool amx_bf16;
 	bool amx_tile;
 	bool amx_int8;
@@ -1444,6 +1447,14 @@ static inline bool cpuinfo_has_x86_avx10_1(void) {
 #endif
 }
 
+static inline bool cpuinfo_has_x86_avx10_2(void) {
+#if CPUINFO_ARCH_X86 || CPUINFO_ARCH_X86_64
+	return cpuinfo_isa.avx10_2;
+#else
+	return false;
+#endif
+}
+
 static inline bool cpuinfo_has_x86_hle(void) {
 #if CPUINFO_ARCH_X86 || CPUINFO_ARCH_X86_64
 	return cpuinfo_isa.hle;
diff --git a/3rdparty/cpuinfo/src/arm/api.h b/3rdparty/cpuinfo/src/arm/api.h
index ac735e3e78..32a271c4ce 100644
--- a/3rdparty/cpuinfo/src/arm/api.h
+++ b/3rdparty/cpuinfo/src/arm/api.h
@@ -1,5 +1,11 @@
 #pragma once
 
+#ifdef _MSC_VER
+#define RESTRICT_STATIC /* nothing for MSVC */
+#else
+#define RESTRICT_STATIC restrict static
+#endif
+
 #include 
 #include 
 
@@ -82,11 +88,11 @@ struct cpuinfo_arm_chipset {
 
 #ifndef __cplusplus
 CPUINFO_INTERNAL void cpuinfo_arm_chipset_to_string(
-	const struct cpuinfo_arm_chipset chipset[restrict static 1],
-	char name[restrict static CPUINFO_ARM_CHIPSET_NAME_MAX]);
+	const struct cpuinfo_arm_chipset chipset[RESTRICT_STATIC 1],
+	char name[RESTRICT_STATIC CPUINFO_ARM_CHIPSET_NAME_MAX]);
 
 CPUINFO_INTERNAL void cpuinfo_arm_fixup_chipset(
-	struct cpuinfo_arm_chipset chipset[restrict static 1],
+	struct cpuinfo_arm_chipset chipset[RESTRICT_STATIC 1],
 	uint32_t cores,
 	uint32_t max_cpu_freq_max);
 
@@ -95,23 +101,23 @@ CPUINFO_INTERNAL void cpuinfo_arm_decode_vendor_uarch(
 #if CPUINFO_ARCH_ARM
 	bool has_vfpv4,
 #endif
-	enum cpuinfo_vendor vendor[restrict static 1],
-	enum cpuinfo_uarch uarch[restrict static 1]);
+	enum cpuinfo_vendor vendor[RESTRICT_STATIC 1],
+	enum cpuinfo_uarch uarch[RESTRICT_STATIC 1]);
 
 CPUINFO_INTERNAL void cpuinfo_arm_decode_cache(
 	enum cpuinfo_uarch uarch,
 	uint32_t cluster_cores,
 	uint32_t midr,
-	const struct cpuinfo_arm_chipset chipset[restrict static 1],
+	const struct cpuinfo_arm_chipset chipset[RESTRICT_STATIC 1],
 	uint32_t cluster_id,
 	uint32_t arch_version,
-	struct cpuinfo_cache l1i[restrict static 1],
-	struct cpuinfo_cache l1d[restrict static 1],
-	struct cpuinfo_cache l2[restrict static 1],
-	struct cpuinfo_cache l3[restrict static 1]);
+	struct cpuinfo_cache l1i[RESTRICT_STATIC 1],
+	struct cpuinfo_cache l1d[RESTRICT_STATIC 1],
+	struct cpuinfo_cache l2[RESTRICT_STATIC 1],
+	struct cpuinfo_cache l3[RESTRICT_STATIC 1]);
 
 CPUINFO_INTERNAL uint32_t
-cpuinfo_arm_compute_max_cache_size(const struct cpuinfo_processor processor[restrict static 1]);
+cpuinfo_arm_compute_max_cache_size(const struct cpuinfo_processor processor[RESTRICT_STATIC 1]);
 #else /* defined(__cplusplus) */
 CPUINFO_INTERNAL void cpuinfo_arm_decode_cache(
 	enum cpuinfo_uarch uarch,
diff --git a/3rdparty/cpuinfo/src/arm/mach/init.c b/3rdparty/cpuinfo/src/arm/mach/init.c
index 47b1b18bc4..c4e6521b3c 100644
--- a/3rdparty/cpuinfo/src/arm/mach/init.c
+++ b/3rdparty/cpuinfo/src/arm/mach/init.c
@@ -101,7 +101,6 @@ static enum cpuinfo_uarch decode_uarch(uint32_t cpu_family, uint32_t core_index,
 	return cpuinfo_uarch_unknown;
 }
 
-/* Small bodge until cpuinfo merges PR #246 */
 static int read_package_name_from_brand_string(char* package_name) {
 	size_t size;
 	if (sysctlbyname("machdep.cpu.brand_string", NULL, &size, NULL, 0) != 0) {
diff --git a/3rdparty/cpuinfo/src/arm/uarch.c b/3rdparty/cpuinfo/src/arm/uarch.c
index 68531e4d1d..9679f5004e 100644
--- a/3rdparty/cpuinfo/src/arm/uarch.c
+++ b/3rdparty/cpuinfo/src/arm/uarch.c
@@ -9,8 +9,8 @@ void cpuinfo_arm_decode_vendor_uarch(
 #if CPUINFO_ARCH_ARM
 	bool has_vfpv4,
 #endif /* CPUINFO_ARCH_ARM */
-	enum cpuinfo_vendor vendor[restrict static 1],
-	enum cpuinfo_uarch uarch[restrict static 1]) {
+	enum cpuinfo_vendor vendor[RESTRICT_STATIC 1],
+	enum cpuinfo_uarch uarch[RESTRICT_STATIC 1]) {
 	switch (midr_get_implementer(midr)) {
 		case 'A':
 			*vendor = cpuinfo_vendor_arm;
@@ -332,6 +332,9 @@ void cpuinfo_arm_decode_vendor_uarch(
 					*uarch = cpuinfo_uarch_cortex_a55;
 					break;
 #if CPUINFO_ARCH_ARM64
+				case 0x001:
+					*uarch = cpuinfo_uarch_oryon;
+					break;
 				case 0xC00:
 					*uarch = cpuinfo_uarch_falkor;
 					break;
diff --git a/3rdparty/cpuinfo/src/arm/windows/init-by-logical-sys-info.c b/3rdparty/cpuinfo/src/arm/windows/init-by-logical-sys-info.c
index a644b1d019..32c9b54683 100644
--- a/3rdparty/cpuinfo/src/arm/windows/init-by-logical-sys-info.c
+++ b/3rdparty/cpuinfo/src/arm/windows/init-by-logical-sys-info.c
@@ -750,11 +750,14 @@ void store_core_info_per_processor(
 	if (cores) {
 		processors[processor_global_index].core = cores + core_id;
 		cores[core_id].core_id = core_id;
-		get_core_uarch_for_efficiency(
-			chip_info->chip_name,
-			core_info->Processor.EfficiencyClass,
-			&(cores[core_id].uarch),
-			&(cores[core_id].frequency));
+
+		if (chip_info->uarchs == NULL) {
+			cpuinfo_log_error("uarch is NULL for core %d", core_id);
+			return;
+		}
+
+		cores[core_id].uarch = chip_info->uarchs[0].uarch;
+		cores[core_id].frequency = chip_info->uarchs[0].frequency;
 
 		/* We don't have cluster information, so we handle it as
 		 * fixed 1 to (cluster / cores).
diff --git a/3rdparty/cpuinfo/src/arm/windows/init.c b/3rdparty/cpuinfo/src/arm/windows/init.c
index faa30ef567..ffbe554d16 100644
--- a/3rdparty/cpuinfo/src/arm/windows/init.c
+++ b/3rdparty/cpuinfo/src/arm/windows/init.c
@@ -7,6 +7,9 @@
 #include 
 #include 
 
+#include 
+#include 
+
 #include "windows-arm-init.h"
 
 struct cpuinfo_arm_isa cpuinfo_isa;
@@ -14,62 +17,7 @@ struct cpuinfo_arm_isa cpuinfo_isa;
 static void set_cpuinfo_isa_fields(void);
 static struct woa_chip_info* get_system_info_from_registry(void);
 
-static struct woa_chip_info woa_chip_unknown = {
-	L"Unknown",
-	woa_chip_name_unknown,
-	{{cpuinfo_vendor_unknown, cpuinfo_uarch_unknown, 0}}};
-
-/* Please add new SoC/chip info here! */
-static struct woa_chip_info woa_chips[woa_chip_name_last] = {
-	/* Microsoft SQ1 Kryo 495 4 + 4 cores (3 GHz + 1.80 GHz) */
-	[woa_chip_name_microsoft_sq_1] =
-		{L"Microsoft SQ1",
-		 woa_chip_name_microsoft_sq_1,
-		 {{
-			  cpuinfo_vendor_arm,
-			  cpuinfo_uarch_cortex_a55,
-			  1800000000,
-		  },
-		  {
-			  cpuinfo_vendor_arm,
-			  cpuinfo_uarch_cortex_a76,
-			  3000000000,
-		  }}},
-	/* Microsoft SQ2 Kryo 495 4 + 4 cores (3.15 GHz + 2.42 GHz) */
-	[woa_chip_name_microsoft_sq_2] =
-		{L"Microsoft SQ2",
-		 woa_chip_name_microsoft_sq_2,
-		 {{
-			  cpuinfo_vendor_arm,
-			  cpuinfo_uarch_cortex_a55,
-			  2420000000,
-		  },
-		  {cpuinfo_vendor_arm, cpuinfo_uarch_cortex_a76, 3150000000}}},
-	/* Snapdragon (TM) 8cx Gen 3 @ 3.0 GHz */
-	[woa_chip_name_microsoft_sq_3] =
-		{L"Snapdragon (TM) 8cx Gen 3",
-		 woa_chip_name_microsoft_sq_3,
-		 {{
-			  cpuinfo_vendor_arm,
-			  cpuinfo_uarch_cortex_a78,
-			  2420000000,
-		  },
-		  {cpuinfo_vendor_arm, cpuinfo_uarch_cortex_x1, 3000000000}}},
-	/* Microsoft Windows Dev Kit 2023 */
-	[woa_chip_name_microsoft_sq_3_devkit] =
-		{L"Snapdragon Compute Platform",
-		 woa_chip_name_microsoft_sq_3_devkit,
-		 {{
-			  cpuinfo_vendor_arm,
-			  cpuinfo_uarch_cortex_a78,
-			  2420000000,
-		  },
-		  {cpuinfo_vendor_arm, cpuinfo_uarch_cortex_x1, 3000000000}}},
-	/* Ampere Altra */
-	[woa_chip_name_ampere_altra] = {
-		L"Ampere(R) Altra(R) Processor",
-		woa_chip_name_ampere_altra,
-		{{cpuinfo_vendor_arm, cpuinfo_uarch_neoverse_n1, 3000000000}}}};
+static struct woa_chip_info woa_chip_unknown = {L"Unknown", {{cpuinfo_vendor_unknown, cpuinfo_uarch_unknown, 0}}};
 
 BOOL CALLBACK cpuinfo_arm_windows_init(PINIT_ONCE init_once, PVOID parameter, PVOID* context) {
 	struct woa_chip_info* chip_info = NULL;
@@ -87,23 +35,6 @@ BOOL CALLBACK cpuinfo_arm_windows_init(PINIT_ONCE init_once, PVOID parameter, PV
 	return true;
 }
 
-bool get_core_uarch_for_efficiency(
-	enum woa_chip_name chip,
-	BYTE EfficiencyClass,
-	enum cpuinfo_uarch* uarch,
-	uint64_t* frequency) {
-	/* For currently supported WoA chips, the Efficiency class selects
-	 * the pre-defined little and big core.
-	 * Any further supported SoC's logic should be implemented here.
-	 */
-	if (uarch && frequency && chip < woa_chip_name_last && EfficiencyClass < MAX_WOA_VALID_EFFICIENCY_CLASSES) {
-		*uarch = woa_chips[chip].uarchs[EfficiencyClass].uarch;
-		*frequency = woa_chips[chip].uarchs[EfficiencyClass].frequency;
-		return true;
-	}
-	return false;
-}
-
 /* Static helper functions */
 
 static wchar_t* read_registry(LPCWSTR subkey, LPCWSTR value) {
@@ -149,40 +80,112 @@ static wchar_t* read_registry(LPCWSTR subkey, LPCWSTR value) {
 	return text_buffer;
 }
 
+static uint64_t read_registry_qword(LPCWSTR subkey, LPCWSTR value) {
+	DWORD key_type = 0;
+	DWORD data_size = sizeof(uint64_t);
+	const DWORD flags = RRF_RT_REG_QWORD; /* Only read QWORD (REG_QWORD) values */
+	uint64_t qword_value = 0;
+	LSTATUS result = RegGetValueW(HKEY_LOCAL_MACHINE, subkey, value, flags, &key_type, &qword_value, &data_size);
+	if (result != ERROR_SUCCESS || data_size != sizeof(uint64_t)) {
+		cpuinfo_log_error("Registry QWORD read error");
+		return 0;
+	}
+	return qword_value;
+}
+
+static uint64_t read_registry_dword(LPCWSTR subkey, LPCWSTR value) {
+	DWORD key_type = 0;
+	DWORD data_size = sizeof(DWORD);
+	DWORD dword_value = 0;
+	LSTATUS result =
+		RegGetValueW(HKEY_LOCAL_MACHINE, subkey, value, RRF_RT_REG_DWORD, &key_type, &dword_value, &data_size);
+	if (result != ERROR_SUCCESS || data_size != sizeof(DWORD)) {
+		cpuinfo_log_error("Registry DWORD read error");
+		return 0;
+	}
+	return (uint64_t)dword_value;
+}
+
+static wchar_t* wcsndup(const wchar_t* src, size_t n) {
+	size_t len = wcsnlen(src, n);
+	wchar_t* dup = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (len + 1) * sizeof(wchar_t));
+	if (dup) {
+		wcsncpy_s(dup, len + 1, src, len);
+		dup[len] = L'\0';
+	}
+	return dup;
+}
+
+static struct core_info_by_chip_name get_core_info_from_midr(uint32_t midr, uint64_t frequency) {
+	struct core_info_by_chip_name info;
+	enum cpuinfo_vendor vendor;
+	enum cpuinfo_uarch uarch;
+
+#if CPUINFO_ARCH_ARM
+	bool has_vfpv4 = false;
+	cpuinfo_arm_decode_vendor_uarch(midr, has_vfpv4, &vendor, &uarch);
+#else
+	cpuinfo_arm_decode_vendor_uarch(midr, &vendor, &uarch);
+#endif
+
+	info.vendor = vendor;
+	info.uarch = uarch;
+	info.frequency = frequency;
+	return info;
+}
+
 static struct woa_chip_info* get_system_info_from_registry(void) {
 	wchar_t* text_buffer = NULL;
 	LPCWSTR cpu0_subkey = L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0";
 	LPCWSTR chip_name_value = L"ProcessorNameString";
+	LPCWSTR chip_midr_value = L"CP 4000";
+	LPCWSTR chip_mhz_value = L"~MHz";
 	struct woa_chip_info* chip_info = NULL;
 
-	HANDLE heap = GetProcessHeap();
-
 	/* Read processor model name from registry and find in the hard-coded
 	 * list. */
 	text_buffer = read_registry(cpu0_subkey, chip_name_value);
 	if (text_buffer == NULL) {
-		cpuinfo_log_error("Registry read error");
+		cpuinfo_log_error("Registry read error for processor name");
 		return NULL;
 	}
-	for (uint32_t i = 0; i < (uint32_t)woa_chip_name_last; i++) {
-		size_t compare_length = wcsnlen(woa_chips[i].chip_name_string, CPUINFO_PACKAGE_NAME_MAX);
-		int compare_result = wcsncmp(text_buffer, woa_chips[i].chip_name_string, compare_length);
-		if (compare_result == 0) {
-			chip_info = woa_chips + i;
-			break;
-		}
+
+	/*
+	 *  https://developer.arm.com/documentation/100442/0100/register-descriptions/aarch32-system-registers/midr--main-id-register
+	 *	Regedit for MIDR :
+	 *HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System\CentralProcessor\0\CP 4000
+	 */
+	uint64_t midr_qword = (uint32_t)read_registry_qword(cpu0_subkey, chip_midr_value);
+	if (midr_qword == 0) {
+		cpuinfo_log_error("Registry read error for MIDR value");
+		return NULL;
 	}
+	// MIDR is only 32 bits, so we need to cast it to uint32_t
+	uint32_t midr_value = (uint32_t)midr_qword;
+
+	/* Read the frequency from the registry
+	 * The value is in MHz, so we need to convert it to Hz */
+	uint64_t frequency_mhz = read_registry_dword(cpu0_subkey, chip_mhz_value);
+	if (frequency_mhz == 0) {
+		cpuinfo_log_error("Registry read error for frequency value");
+		return NULL;
+	}
+	// Convert MHz to Hz
+	uint64_t frequency_hz = frequency_mhz * 1000000;
+
+	// Allocate chip_info before using it.
+	chip_info = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(struct woa_chip_info));
 	if (chip_info == NULL) {
-		/* No match was found, so print a warning and assign the unknown
-		 * case. */
-		cpuinfo_log_error(
-			"Unknown chip model name '%ls'.\nPlease add new Windows on Arm SoC/chip support to arm/windows/init.c!",
-			text_buffer);
-	} else {
-		cpuinfo_log_debug("detected chip model name: %s", chip_info->chip_name_string);
+		cpuinfo_log_error("Heap allocation error for chip_info");
+		return NULL;
 	}
 
-	HeapFree(heap, 0, text_buffer);
+	// set chip_info fields
+	chip_info->chip_name_string = wcsndup(text_buffer, CPUINFO_PACKAGE_NAME_MAX - 1);
+	chip_info->uarchs[0] = get_core_info_from_midr(midr_value, frequency_hz);
+
+	cpuinfo_log_debug("detected chip model name: %ls", chip_info->chip_name_string);
+
 	return chip_info;
 }
 
@@ -216,4 +219,4 @@ static void set_cpuinfo_isa_fields(void) {
 	cpuinfo_isa.pmull = crypto;
 
 	cpuinfo_isa.crc32 = IsProcessorFeaturePresent(PF_ARM_V8_CRC32_INSTRUCTIONS_AVAILABLE) != 0;
-}
+}
\ No newline at end of file
diff --git a/3rdparty/cpuinfo/src/arm/windows/windows-arm-init.h b/3rdparty/cpuinfo/src/arm/windows/windows-arm-init.h
index b054a29822..0448243d08 100644
--- a/3rdparty/cpuinfo/src/arm/windows/windows-arm-init.h
+++ b/3rdparty/cpuinfo/src/arm/windows/windows-arm-init.h
@@ -3,17 +3,6 @@
 /* Efficiency class = 0 means little core, while 1 means big core for now. */
 #define MAX_WOA_VALID_EFFICIENCY_CLASSES 2
 
-/* List of known and supported Windows on Arm SoCs/chips. */
-enum woa_chip_name {
-	woa_chip_name_microsoft_sq_1 = 0,
-	woa_chip_name_microsoft_sq_2 = 1,
-	woa_chip_name_microsoft_sq_3 = 2,
-	woa_chip_name_microsoft_sq_3_devkit = 3,
-	woa_chip_name_ampere_altra = 4,
-	woa_chip_name_unknown = 5,
-	woa_chip_name_last = woa_chip_name_unknown
-};
-
 /* Topology information hard-coded by SoC/chip name */
 struct core_info_by_chip_name {
 	enum cpuinfo_vendor vendor;
@@ -26,14 +15,7 @@ struct core_info_by_chip_name {
  */
 struct woa_chip_info {
 	wchar_t* chip_name_string;
-	enum woa_chip_name chip_name;
 	struct core_info_by_chip_name uarchs[MAX_WOA_VALID_EFFICIENCY_CLASSES];
 };
 
-bool get_core_uarch_for_efficiency(
-	enum woa_chip_name chip,
-	BYTE EfficiencyClass,
-	enum cpuinfo_uarch* uarch,
-	uint64_t* frequency);
-
 bool cpu_info_init_by_logical_sys_info(const struct woa_chip_info* chip_info, enum cpuinfo_vendor vendor);
diff --git a/3rdparty/cpuinfo/src/x86/isa.c b/3rdparty/cpuinfo/src/x86/isa.c
index 47a6afa320..222bd231dd 100644
--- a/3rdparty/cpuinfo/src/x86/isa.c
+++ b/3rdparty/cpuinfo/src/x86/isa.c
@@ -46,6 +46,8 @@ struct cpuinfo_x86_isa cpuinfo_x86_detect_isa(
 		(max_base_index >= 7) ? cpuidex(7, 0) : (struct cpuid_regs){0, 0, 0, 0};
 	const struct cpuid_regs structured_feature_info1 =
 		(max_base_index >= 7) ? cpuidex(7, 1) : (struct cpuid_regs){0, 0, 0, 0};
+	const struct cpuid_regs structured_feature_info2 =
+		(max_base_index >= 7) ? cpuidex(0x24, 0) : (struct cpuid_regs){0, 0, 0, 0};
 
 	const uint32_t processor_capacity_info_index = UINT32_C(0x80000008);
 	const struct cpuid_regs processor_capacity_info = (max_extended_index >= processor_capacity_info_index)
@@ -430,10 +432,17 @@ struct cpuinfo_x86_isa cpuinfo_x86_detect_isa(
 	isa.avx512f = avx512_regs && !!(structured_feature_info0.ebx & UINT32_C(0x00010000));
 
 	/*
-	 * AVX 10.1 instructions:
+	 * AVX 10.1 instructions: avx 10 isa supported.
+	 * - Intel: edx[bit 19] in structured feature info (ecx = 1).
 	 */
 	isa.avx10_1 = avx512_regs && !!(structured_feature_info1.edx & UINT32_C(0x00080000));
 
+	/*
+	 * AVX 10.2 instructions: avx 10 version information.
+	 * - Intel: ebx[bits 0-7] in structured features info (eax = 24 ecx = 0).
+	 */
+	isa.avx10_2 = ((structured_feature_info2.ebx & UINT32_C(0x000000FF)) >= 2) && isa.avx10_1;
+
 	/*
 	 * AVX512PF instructions:
 	 * - Intel: ebx[bit 26] in structured feature info (ecx = 0).
diff --git a/3rdparty/cpuinfo/src/x86/linux/cpuinfo.c b/3rdparty/cpuinfo/src/x86/linux/cpuinfo.c
index 7df72aba50..8f038b0702 100644
--- a/3rdparty/cpuinfo/src/x86/linux/cpuinfo.c
+++ b/3rdparty/cpuinfo/src/x86/linux/cpuinfo.c
@@ -83,8 +83,9 @@ struct proc_cpuinfo_parser_state {
 static bool parse_line(
 	const char* line_start,
 	const char* line_end,
-	struct proc_cpuinfo_parser_state state[restrict static 1],
+	void* context,
 	uint64_t line_number) {
+	struct proc_cpuinfo_parser_state* restrict state = context;
 	/* Empty line. Skip. */
 	if (line_start == line_end) {
 		return true;
@@ -215,5 +216,5 @@ bool cpuinfo_x86_linux_parse_proc_cpuinfo(
 		.processors = processors,
 	};
 	return cpuinfo_linux_parse_multiline_file(
-		"/proc/cpuinfo", BUFFER_SIZE, (cpuinfo_line_callback)parse_line, &state);
+		"/proc/cpuinfo", BUFFER_SIZE, parse_line, &state);
 }

From 1030db87d4e2f50091e746cd72aef4fac4c0420a Mon Sep 17 00:00:00 2001
From: PCSX2 Bot 
Date: Thu, 27 Mar 2025 00:02:25 +0000
Subject: [PATCH 025/162] [ci skip] Qt: Update Base Translation.

---
 pcsx2-qt/Translations/pcsx2-qt_en.ts | 4008 +++++++++++++-------------
 pcsx2/ImGui/FullscreenUI.cpp         |    4 +
 2 files changed, 2018 insertions(+), 1994 deletions(-)

diff --git a/pcsx2-qt/Translations/pcsx2-qt_en.ts b/pcsx2-qt/Translations/pcsx2-qt_en.ts
index 0115b49004..8147682d7a 100644
--- a/pcsx2-qt/Translations/pcsx2-qt_en.ts
+++ b/pcsx2-qt/Translations/pcsx2-qt_en.ts
@@ -6102,4483 +6102,4503 @@ The URL was: %1
 
     FullscreenUI
     
-        
+        
         Could not find any CD/DVD-ROM devices. Please ensure you have a drive connected and sufficient permissions to access it.
         
     
     
-        
+        
         Use Global Setting
         
     
     
-        
+        
         Automatic binding failed, no devices are available.
         
     
     
-        
+        
         Game title copied to clipboard.
         
     
     
-        
+        
         Game serial copied to clipboard.
         
     
     
-        
+        
         Game CRC copied to clipboard.
         
     
     
-        
+        
         Game type copied to clipboard.
         
     
     
-        
+        
         Game region copied to clipboard.
         
     
     
-        
+        
         Game compatibility copied to clipboard.
         
     
     
-        
+        
         Game path copied to clipboard.
         
     
     
-        
+        
         Controller settings reset to default.
         
     
     
-        
+        
         No input profiles available.
         
     
     
-        
+        
         Create New...
         
     
     
-        
+        
         Enter the name of the input profile you wish to create.
         
     
     
-        
+        
         Are you sure you want to restore the default settings? Any preferences will be lost.
         
     
     
-        
+        
         Settings reset to defaults.
         
     
     
-        
+        
         No save present in this slot.
         
     
     
-        
+        
         No save states found.
         
     
     
-        
+        
         Failed to delete save state.
         
     
     
-        
+        
         Failed to copy text to clipboard.
         
     
     
-        
+        
         This game has no achievements.
         
     
     
-        
+        
         This game has no leaderboards.
         
     
     
-        
+        
         Reset System
         
     
     
-        
+        
         Hardcore mode will not be enabled until the system is reset. Do you want to reset the system now?
         
     
     
-        
+        
         Launch a game from images scanned from your game directories.
         
     
     
-        
+        
         Launch a game by selecting a file/disc image.
         
     
     
-        
+        
         Start the console without any disc inserted.
         
     
     
-        
+        
         Start a game from a disc in your PC's DVD drive.
         
     
     
-        
+        
         No Binding
         
     
     
-        
+        
         Setting %s binding %s.
         
     
     
-        
+        
         Push a controller button or axis now.
         
     
     
-        
+        
         Timing out in %.0f seconds...
         
     
     
-        
+        
         Unknown
         
     
     
-        
+        
         OK
         
     
     
-        
+        
         Select Device
         
     
     
-        
+        
         Details
         
     
     
-        
+        
         Options
         
     
     
-        
+        
         Copies the current global settings to this game.
         
     
     
-        
+        
         Clears all settings set for this game.
         
     
     
-        
+        
         Behaviour
         
     
     
-        
+        
         Prevents the screen saver from activating and the host from sleeping while emulation is running.
         
     
     
-        
+        
         Shows the game you are currently playing as part of your profile on Discord.
         
     
     
-        
+        
         Pauses the emulator when a game is started.
         
     
     
-        
+        
         Pauses the emulator when you minimize the window or switch to another application, and unpauses when you switch back.
         
     
     
-        
+        
         Pauses the emulator when you open the quick menu, and unpauses when you close it.
         
     
     
-        
+        
         Determines whether a prompt will be displayed to confirm shutting down the emulator/game when the hotkey is pressed.
         
     
     
-        
+        
         Automatically saves the emulator state when powering down or exiting. You can then resume directly from where you left off next time.
         
     
     
-        
+        
         Game Display
         
     
     
-        
+        
         Switches between full screen and windowed when the window is double-clicked.
         
     
     
-        
+        
         Hides the mouse pointer/cursor when the emulator is in fullscreen mode.
         
     
     
-        
+        
         Determines how large the on-screen messages and monitor are.
         
     
     
-        
+        
         Shows on-screen-display messages when events occur such as save states being created/loaded, screenshots being taken, etc.
         
     
     
-        
+        
         Shows the current emulation speed of the system in the top-right corner of the display as a percentage.
         
     
     
-        
+        
         Shows the number of video frames (or v-syncs) displayed per second by the system in the top-right corner of the display.
         
     
     
-        
+        
         Shows the CPU usage based on threads in the top-right corner of the display.
         
     
     
-        
+        
         Shows the host's GPU usage in the top-right corner of the display.
         
     
     
-        
+        
         Shows statistics about GS (primitives, draw calls) in the top-right corner of the display.
         
     
     
-        
+        
         Shows indicators when fast forwarding, pausing, and other abnormal states are active.
         
     
     
-        
+        
         Shows the current configuration in the bottom-right corner of the display.
         
     
     
-        
+        
         Shows the current controller state of the system in the bottom-left corner of the display.
         
     
     
-        
+        
         Displays warnings when settings are enabled which may break games.
         
     
     
-        
+        
         Resets configuration to defaults (excluding controller settings).
         
     
     
-        
+        
         Changes the BIOS image used to start future sessions.
         
     
     
-        
+        
         Automatic
         
     
     
-        
+        
         {0}/{1}/{2}/{3}
         
     
     
-        
+        
         Default
         
     
     
-        
+        
         WARNING: Your memory card is still writing data. Shutting down now will IRREVERSIBLY DESTROY YOUR MEMORY CARD. It is strongly recommended to resume your game and let it finish writing to your memory card.
 
 Do you wish to shutdown anyways and IRREVERSIBLY DESTROY YOUR MEMORY CARD?
         
     
     
-        
+        
         Automatically switches to fullscreen mode when a game is started.
         
     
     
-        
+        
         On-Screen Display
         
     
     
-        
+        
         %d%%
         
     
     
-        
+        
         Shows the resolution of the game in the top-right corner of the display.
         
     
     
-        
+        
         BIOS Configuration
         
     
     
-        
+        
         BIOS Selection
         
     
     
-        
+        
         Options and Patches
         
     
     
-        
+        
         Skips the intro screen, and bypasses region checks.
         
     
     
-        
+        
         Speed Control
         
     
     
-        
+        
         Normal Speed
         
     
     
-        
+        
         Sets the speed when running without fast forwarding.
         
     
     
-        
+        
         Fast Forward Speed
         
     
     
-        
+        
         Sets the speed when using the fast forward hotkey.
         
     
     
-        
+        
         Slow Motion Speed
         
     
     
-        
+        
         Sets the speed when using the slow motion hotkey.
         
     
     
-        
+        
         System Settings
         
     
     
-        
+        
         EE Cycle Rate
         
     
     
-        
+        
         Underclocks or overclocks the emulated Emotion Engine CPU.
         
     
     
-        
+        
         EE Cycle Skipping
         
     
     
-        
+        
         Enable MTVU (Multi-Threaded VU1)
         
     
     
-        
+        
         Enable Instant VU1
         
     
     
-        
+        
         Enable Cheats
         
     
     
-        
+        
         Enables loading cheats from pnach files.
         
     
     
-        
+        
         Enable Host Filesystem
         
     
     
-        
+        
         Enables access to files from the host: namespace in the virtual machine.
         
     
     
-        
+        
         Enable Fast CDVD
         
     
     
-        
+        
         Fast disc access, less loading times. Not recommended.
         
     
     
-        
+        
         Frame Pacing/Latency Control
         
     
     
-        
+        
         Maximum Frame Latency
         
     
     
-        
+        
         Sets the number of frames which can be queued.
         
     
     
-        
+        
         Optimal Frame Pacing
         
     
     
-        
+        
         Synchronize EE and GS threads after each frame. Lowest input latency, but increases system requirements.
         
     
     
-        
+        
         Speeds up emulation so that the guest refresh rate matches the host.
         
     
     
-        
+        
         Renderer
         
     
     
-        
+        
         Selects the API used to render the emulated GS.
         
     
     
-        
+        
         Synchronizes frame presentation with host refresh.
         
     
     
-        
+        
         Display
         
     
     
-        
+        
         Aspect Ratio
         
     
     
-        
+        
         Selects the aspect ratio to display the game content at.
         
     
     
-        
+        
         FMV Aspect Ratio Override
         
     
     
-        
+        
         Selects the aspect ratio for display when a FMV is detected as playing.
         
     
     
-        
+        
         Deinterlacing
         
     
     
-        
+        
         Selects the algorithm used to convert the PS2's interlaced output to progressive for display.
         
     
     
-        
+        
         Screenshot Size
         
     
     
-        
+        
         Determines the resolution at which screenshots will be saved.
         
     
     
-        
+        
         Screenshot Format
         
     
     
-        
+        
         Selects the format which will be used to save screenshots.
         
     
     
-        
+        
         Screenshot Quality
         
     
     
-        
+        
         Selects the quality at which screenshots will be compressed.
         
     
     
-        
+        
         Vertical Stretch
         
     
     
-        
+        
         Increases or decreases the virtual picture size vertically.
         
     
     
-        
+        
         Crop
         
     
     
-        
+        
         Crops the image, while respecting aspect ratio.
         
     
     
-        
+        
         %dpx
         
     
     
-        
+        
         Bilinear Upscaling
         
     
     
-        
+        
         Smooths out the image when upscaling the console to the screen.
         
     
     
-        
+        
         Integer Upscaling
         
     
     
-        
+        
         Adds padding to the display area to ensure that the ratio between pixels on the host to pixels in the console is an integer number. May result in a sharper image in some 2D games.
         
     
     
-        
+        
         Screen Offsets
         
     
     
-        
+        
         Enables PCRTC Offsets which position the screen as the game requests.
         
     
     
-        
+        
         Show Overscan
         
     
     
-        
+        
         Enables the option to show the overscan area on games which draw more than the safe area of the screen.
         
     
     
-        
+        
         Anti-Blur
         
     
     
-        
+        
         Enables internal Anti-Blur hacks. Less accurate to PS2 rendering but will make a lot of games look less blurry.
         
     
     
-        
+        
         Rendering
         
     
     
-        
+        
         Internal Resolution
         
     
     
-        
+        
         Multiplies the render resolution by the specified factor (upscaling).
         
     
     
-        
+        
         Mipmapping
         
     
     
-        
+        
         Bilinear Filtering
         
     
     
-        
+        
         Selects where bilinear filtering is utilized when rendering textures.
         
     
     
-        
+        
         Trilinear Filtering
         
     
     
-        
+        
         Selects where trilinear filtering is utilized when rendering textures.
         
     
     
-        
+        
         Anisotropic Filtering
         
     
     
-        
+        
         Dithering
         
     
     
-        
+        
         Selects the type of dithering applies when the game requests it.
         
     
     
-        
+        
         Blending Accuracy
         
     
     
-        
+        
         Determines the level of accuracy when emulating blend modes not supported by the host graphics API.
         
     
     
-        
+        
         Texture Preloading
         
     
     
-        
+        
         Uploads full textures to the GPU on use, rather than only the utilized regions. Can improve performance in some games.
         
     
     
-        
+        
         Software Rendering Threads
         
     
     
-        
+        
         Number of threads to use in addition to the main GS thread for rasterization.
         
     
     
-        
+        
         Auto Flush (Software)
         
     
     
-        
+        
         Force a primitive flush when a framebuffer is also an input texture.
         
     
     
-        
+        
         Edge AA (AA1)
         
     
     
-        
+        
         Enables emulation of the GS's edge anti-aliasing (AA1).
         
     
     
-        
+        
         Enables emulation of the GS's texture mipmapping.
         
     
     
-        
+        
         The selected input profile will be used for this game.
         
     
     
-        
+        
         Shared
         
     
     
-        
+        
         Input Profile
         
     
     
-        
+        
         Appearance
         
     
     
-        
+        
         Selects the color style to be used for Big Picture Mode.
         
     
     
-        
+        
         Show a save state selector UI when switching slots instead of showing a notification bubble.
         
     
     
-        
+        
         Integration
         
     
     
-        
+        
         Shows the current PCSX2 version on the top-right corner of the display.
         
     
     
-        
+        
         Shows the currently active input recording status.
         
     
     
-        
+        
         Shows the currently active video capture status.
         
     
     
-        
+        
         Shows a visual history of frame times in the upper-left corner of the display.
         
     
     
-        
+        
         Shows the current system hardware information on the OSD.
         
     
     
-        
+        
         Pins emulation threads to CPU cores to potentially improve performance/frame time variance.
         
     
     
-        
+        
         Enable Widescreen Patches
         
     
     
-        
+        
         Enables loading widescreen patches from pnach files.
         
     
     
-        
+        
         Enable No-Interlacing Patches
         
     
     
-        
+        
         Enables loading no-interlacing patches from pnach files.
         
     
     
-        
+        
         Hardware Fixes
         
     
     
-        
+        
         Manual Hardware Fixes
         
     
     
-        
+        
         Disables automatic hardware fixes, allowing you to set fixes manually.
         
     
     
-        
+        
         CPU Sprite Render Size
         
     
     
-        
+        
         Uses software renderer to draw texture decompression-like sprites.
         
     
     
-        
+        
         CPU Sprite Render Level
         
     
     
-        
+        
         Determines filter level for CPU sprite render.
         
     
     
-        
+        
         Software CLUT Render
         
     
     
-        
+        
         Uses software renderer to draw texture CLUT points/sprites.
         
     
-    
-        
-        Skip Draw Start
-        
-    
-    
-        
-        Object range to skip drawing.
-        
-    
-    
-        
-        Skip Draw End
-        
-    
-    
-        
-        Auto Flush (Hardware)
-        
-    
-    
-        
-        CPU Framebuffer Conversion
-        
-    
-    
-        
-        Disable Depth Conversion
-        
-    
-    
-        
-        Disable Safe Features
-        
-    
     
         
-        This option disables multiple safe features.
-        
-    
-    
-        
-        This option disables game-specific render fixes.
-        
-    
-    
-        
-        Uploads GS data when rendering a new frame to reproduce some effects accurately.
-        
-    
-    
-        
-        Disable Partial Invalidation
-        
-    
-    
-        
-        Removes texture cache entries when there is any intersection, rather than only the intersected areas.
-        
-    
-    
-        
-        Allows the texture cache to reuse as an input texture the inner portion of a previous framebuffer.
-        
-    
-    
-        
-        Read Targets When Closing
-        
-    
-    
-        
-        Flushes all targets in the texture cache back to local memory when shutting down.
-        
-    
-    
-        
-        Estimate Texture Region
-        
-    
-    
-        
-        Attempts to reduce the texture size when games do not set it themselves (e.g. Snowblind games).
-        
-    
-    
-        
-        GPU Palette Conversion
-        
-    
-    
-        
-        Upscaling Fixes
-        
-    
-    
-        
-        Adjusts vertices relative to upscaling.
-        
-    
-    
-        
-        Native Scaling
-        
-    
-    
-        
-        Attempt to do rescaling at native resolution.
-        
-    
-    
-        
-        Round Sprite
-        
-    
-    
-        
-        Adjusts sprite coordinates.
-        
-    
-    
-        
-        Bilinear Upscale
-        
-    
-    
-        
-        Can smooth out textures due to be bilinear filtered when upscaling. E.g. Brave sun glare.
-        
-    
-    
-        
-        Adjusts target texture offsets.
-        
-    
-    
-        
-        Align Sprite
-        
-    
-    
-        
-        Fixes issues with upscaling (vertical lines) in some games.
-        
-    
-    
-        
-        Merge Sprite
-        
-    
-    
-        
-        Replaces multiple post-processing sprites with a larger single sprite.
-        
-    
-    
-        
-        Lowers the GS precision to avoid gaps between pixels when upscaling. Fixes the text on Wild Arms games.
-        
-    
-    
-        
-        Unscaled Palette Texture Draws
-        
-    
-    
-        
-        Can fix some broken effects which rely on pixel perfect precision.
-        
-    
-    
-        
-        Texture Replacement
-        
-    
-    
-        
-        Load Textures
-        
-    
-    
-        
-        Loads replacement textures where available and user-provided.
-        
-    
-    
-        
-        Asynchronous Texture Loading
-        
-    
-    
-        
-        Loads replacement textures on a worker thread, reducing microstutter when replacements are enabled.
-        
-    
-    
-        
-        Precache Replacements
-        
-    
-    
-        
-        Preloads all replacement textures to memory. Not necessary with asynchronous loading.
-        
-    
-    
-        
-        Replacements Directory
-        
-    
-    
-        
-        Folders
-        
-    
-    
-        
-        Texture Dumping
-        
-    
-    
-        
-        Dump Textures
-        
-    
-    
-        
-        Dump Mipmaps
-        
-    
-    
-        
-        Includes mipmaps when dumping textures.
-        
-    
-    
-        
-        Dump FMV Textures
-        
-    
-    
-        
-        Allows texture dumping when FMVs are active. You should not enable this.
-        
-    
-    
-        
-        Post-Processing
-        
-    
-    
-        
-        FXAA
-        
-    
-    
-        
-        Enables FXAA post-processing shader.
-        
-    
-    
-        
-        Contrast Adaptive Sharpening
-        
-    
-    
-        
-        Enables FidelityFX Contrast Adaptive Sharpening.
-        
-    
-    
-        
-        CAS Sharpness
-        
-    
-    
-        
-        Determines the intensity the sharpening effect in CAS post-processing.
-        
-    
-    
-        
-        Filters
-        
-    
-    
-        
-        Shade Boost
-        
-    
-    
-        
-        Enables brightness/contrast/saturation adjustment.
-        
-    
-    
-        
-        Shade Boost Brightness
-        
-    
-    
-        
-        Adjusts brightness. 50 is normal.
-        
-    
-    
-        
-        Shade Boost Contrast
-        
-    
-    
-        
-        Adjusts contrast. 50 is normal.
-        
-    
-    
-        
-        Shade Boost Saturation
-        
-    
-    
-        
-        Adjusts saturation. 50 is normal.
-        
-    
-    
-        
-        TV Shaders
-        
-    
-    
-        
-        Advanced
-        
-    
-    
-        
-        Skip Presenting Duplicate Frames
-        
-    
-    
-        
-        Extended Upscaling Multipliers
-        
-    
-    
-        
-        Displays additional, very high upscaling multipliers dependent on GPU capability.
-        
-    
-    
-        
-        Hardware Download Mode
-        
-    
-    
-        
-        Changes synchronization behavior for GS downloads.
-        
-    
-    
-        
-        Allow Exclusive Fullscreen
-        
-    
-    
-        
-        Overrides the driver's heuristics for enabling exclusive fullscreen, or direct flip/scanout.
-        
-    
-    
-        
-        Override Texture Barriers
-        
-    
-    
-        
-        Forces texture barrier functionality to the specified value.
-        
-    
-    
-        
-        GS Dump Compression
-        
-    
-    
-        
-        Sets the compression algorithm for GS dumps.
-        
-    
-    
-        
-        Disable Framebuffer Fetch
-        
-    
-    
-        
-        Prevents the usage of framebuffer fetch when supported by host GPU.
-        
-    
-    
-        
-        Disable Shader Cache
-        
-    
-    
-        
-        Prevents the loading and saving of shaders/pipelines to disk.
-        
-    
-    
-        
-        Disable Vertex Shader Expand
-        
-    
-    
-        
-        Falls back to the CPU for expanding sprites/lines.
-        
-    
-    
-        
-        Changes when SPU samples are generated relative to system emulation.
-        
-    
-    
-        
-        %d ms
-        
-    
-    
-        
-        Settings and Operations
-        
-    
-    
-        
-        Creates a new memory card file or folder.
-        
-    
-    
-        
-        Simulates a larger memory card by filtering saves only to the current game.
-        
-    
-    
-        
-        If not set, this card will be considered unplugged.
-        
-    
-    
-        
-        The selected memory card image will be used for this slot.
-        
-    
-    
-        
-        Enable/Disable the Player LED on DualSense controllers.
-        
-    
-    
-        
-        Trigger
-        
-    
-    
-        
-        Toggles the macro when the button is pressed, instead of held.
-        
-    
-    
-        
-        Savestate
-        
-    
-    
-        
-        Compression Method
-        
-    
-    
-        
-        Sets the compression algorithm for savestate.
-        
-    
-    
-        
-        Compression Level
-        
-    
-    
-        
-        Sets the compression level for savestate.
-        
-    
-    
-        
-        Version: %s
-        
-    
-    
-        
-        {:%H:%M}
-        
-    
-    
-        
-        Slot {}
-        
-    
-    
-        
-        Dark
-        
-    
-    
-        
-        Light
-        
-    
-    
-        
-        Grey Matter
-        
-    
-    
-        
-        Untouched Lagoon
-        
-    
-    
-        
-        Baby Pastel
-        
-    
-    
-        
-        Pizza Time!
-        
-    
-    
-        
-        PCSX2 Blue
-        
-    
-    
-        
-        Scarlet Devil
-        
-    
-    
-        
-        Violet Angel
-        
-    
-    
-        
-        Cobalt Sky
-        
-    
-    
-        
-        AMOLED
-        
-    
-    
-        
-        Enabled
-        
-    
-    
-        
-        1.25x Native (~450px)
-        
-    
-    
-        
-        1.5x Native (~540px)
-        
-    
-    
-        
-        1.75x Native (~630px)
-        
-    
-    
-        
-        2x Native (~720px/HD)
-        
-    
-    
-        
-        2.5x Native (~900px/HD+)
-        
-    
-    
-        
-        3x Native (~1080px/FHD)
-        
-    
-    
-        
-        3.5x Native (~1260px)
-        
-    
-    
-        
-        4x Native (~1440px/QHD)
-        
-    
-    
-        
-        5x Native (~1800px/QHD+)
-        
-    
-    
-        
-        6x Native (~2160px/4K UHD)
-        
-    
-    
-        
-        7x Native (~2520px)
-        
-    
-    
-        
-        8x Native (~2880px/5K UHD)
-        
-    
-    
-        
-        9x Native (~3240px)
-        
-    
-    
-        
-        10x Native (~3600px/6K UHD)
-        
-    
-    
-        
-        11x Native (~3960px)
-        
-    
-    
-        
-        12x Native (~4320px/8K UHD)
-        
-    
-    
-        
-        WebP
-        
-    
-    
-        
-        Align to Native
-        
-    
-    
-        
-        Align to Native - with Texture Offset
-        
-    
-    
-        
-        Aggressive
-        
-    
-    
-        
-        Deflate64
-        
-    
-    
-        
-        Zstandard
-        
-    
-    
-        
-        LZMA2
-        
-    
-    
-        
-        Low (Fast)
-        
-    
-    
-        
-        Medium (Recommended)
-        
-    
-    
-        
-        Very High (Slow, Not Recommended)
-        
-    
-    
-        
-        Change Selection
-        
-    
-    
-        
-        Select
-        
-    
-    
-        
-        Parent Directory
-        
-    
-    
-        
-        Enter Value
-        
-    
-    
-        
-        About
-        
-    
-    
-        
-        Toggle Fullscreen
-        
-    
-    
-        
-        Navigate
-        
-    
-    
-        
-        Load Global State
-        
-    
-    
-        
-        Change Page
-        
-    
-    
-        
-        Return To Game
-        
-    
-    
-        
-        Select State
-        
-    
-    
-        
-        Select Game
-        
-    
-    
-        
-        Change View
-        
-    
-    
-        
-        Launch Options
-        
-    
-    
-        
-        Create Save State Backups
-        
-    
-    
-        
-        Show PCSX2 Version
-        
-    
-    
-        
-        Show Input Recording Status
-        
-    
-    
-        
-        Show Video Capture Status
-        
-    
-    
-        
-        Show Frame Times
-        
-    
-    
-        
-        Show Hardware Info
-        
-    
-    
-        
-        Create Memory Card
-        
-    
-    
-        
-        Configuration
-        
-    
-    
-        
-        Start Game
-        
-    
-    
-        
-        Launch a game from a file, disc, or starts the console without any disc inserted.
-        
-    
-    
-        
-        Changes settings for the application.
-        
-    
-    
-        
-        Return to desktop mode, or exit the application.
-        
-    
-    
-        
-        Back
-        
-    
-    
-        
-        Return to the previous menu.
-        
-    
-    
-        
-        Exit PCSX2
-        
-    
-    
-        
-        Completely exits the application, returning you to your desktop.
-        
-    
-    
-        
-        Desktop Mode
-        
-    
-    
-        
-        Exits Big Picture mode, returning to the desktop interface.
-        
-    
-    
-        
-        Resets all configuration to defaults (including bindings).
-        
-    
-    
-        
-        Replaces these settings with a previously saved input profile.
-        
-    
-    
-        
-        Stores the current settings to an input profile.
-        
-    
-    
-        
-        Input Sources
-        
-    
-    
-        
-        The SDL input source supports most controllers.
-        
-    
-    
-        
-        Provides vibration and LED control support over Bluetooth.
-        
-    
-    
-        
-        Allow SDL to use raw access to input devices.
-        
-    
-    
-        
-        The XInput source provides support for XBox 360/XBox One/XBox Series controllers.
-        
-    
-    
-        
-        Multitap
-        
-    
-    
-        
-        Enables an additional three controller slots. Not supported in all games.
-        
-    
-    
-        
-        Attempts to map the selected port to a chosen controller.
-        
-    
-    
-        
-        Determines how much pressure is simulated when macro is active.
-        
-    
-    
-        
-        Determines the pressure required to activate the macro.
-        
-    
-    
-        
-        Toggle every %d frames
-        
-    
-    
-        
-        Clears all bindings for this USB controller.
-        
-    
-    
-        
-        Data Save Locations
-        
-    
-    
-        
-        Show Advanced Settings
-        
-    
-    
-        
-        Changing these options may cause games to become non-functional. Modify at your own risk, the PCSX2 team will not provide support for configurations with these settings changed.
-        
-    
-    
-        
-        Logging
-        
-    
-    
-        
-        System Console
-        
-    
-    
-        
-        Writes log messages to the system console (console window/standard output).
-        
-    
-    
-        
-        File Logging
-        
-    
-    
-        
-        Writes log messages to emulog.txt.
-        
-    
-    
-        
-        Verbose Logging
-        
-    
-    
-        
-        Writes dev log messages to log sinks.
-        
-    
-    
-        
-        Log Timestamps
-        
-    
-    
-        
-        Writes timestamps alongside log messages.
-        
-    
-    
-        
-        EE Console
-        
-    
-    
-        
-        Writes debug messages from the game's EE code to the console.
-        
-    
-    
-        
-        IOP Console
-        
-    
-    
-        
-        Writes debug messages from the game's IOP code to the console.
-        
-    
-    
-        
-        CDVD Verbose Reads
-        
-    
-    
-        
-        Logs disc reads from games.
-        
-    
-    
-        
-        Emotion Engine
-        
-    
-    
-        
-        Rounding Mode
-        
-    
-    
-        
-        Determines how the results of floating-point operations are rounded. Some games need specific settings.
-        
-    
-    
-        
-        Division Rounding Mode
-        
-    
-    
-        
-        Determines how the results of floating-point division is rounded. Some games need specific settings.
-        
-    
-    
-        
-        Clamping Mode
-        
-    
-    
-        
-        Determines how out-of-range floating point numbers are handled. Some games need specific settings.
-        
-    
-    
-        
-        Enable EE Recompiler
-        
-    
-    
-        
-        Performs just-in-time binary translation of 64-bit MIPS-IV machine code to native code.
-        
-    
-    
-        
-        Enable EE Cache
-        
-    
-    
-        
-        Enables simulation of the EE's cache. Slow.
-        
-    
-    
-        
-        Enable INTC Spin Detection
-        
-    
-    
-        
-        Huge speedup for some games, with almost no compatibility side effects.
-        
-    
-    
-        
-        Enable Wait Loop Detection
-        
-    
-    
-        
-        Moderate speedup for some games, with no known side effects.
-        
-    
-    
-        
-        Enable Fast Memory Access
-        
-    
-    
-        
-        Uses backpatching to avoid register flushing on every memory access.
-        
-    
-    
-        
-        Vector Units
-        
-    
-    
-        
-        VU0 Rounding Mode
-        
-    
-    
-        
-        VU0 Clamping Mode
-        
-    
-    
-        
-        VU1 Rounding Mode
-        
-    
-    
-        
-        VU1 Clamping Mode
-        
-    
-    
-        
-        Enable VU0 Recompiler (Micro Mode)
-        
-    
-    
-        
-        New Vector Unit recompiler with much improved compatibility. Recommended.
-        
-    
-    
-        
-        Enable VU1 Recompiler
-        
-    
-    
-        
-        Enable VU Flag Optimization
-        
-    
-    
-        
-        Good speedup and high compatibility, may cause graphical errors.
-        
-    
-    
-        
-        I/O Processor
-        
-    
-    
-        
-        Enable IOP Recompiler
-        
-    
-    
-        
-        Performs just-in-time binary translation of 32-bit MIPS-I machine code to native code.
-        
-    
-    
-        
-        Graphics
-        
-    
-    
-        
-        Use Debug Device
-        
-    
-    
-        
-        Settings
-        
-    
-    
-        
-        No cheats are available for this game.
-        
-    
-    
-        
-        Cheat Codes
-        
-    
-    
-        
-        No patches are available for this game.
-        
-    
-    
-        
-        Game Patches
-        
-    
-    
-        
-        Activating cheats can cause unpredictable behavior, crashing, soft-locks, or broken saved games.
-        
-    
-    
-        
-        Activating game patches can cause unpredictable behavior, crashing, soft-locks, or broken saved games.
-        
-    
-    
-        
-        Use patches at your own risk, the PCSX2 team will provide no support for users who have enabled game patches.
-        
-    
-    
-        
-        Game Fixes
-        
-    
-    
-        
-        Game fixes should not be modified unless you are aware of what each option does and the implications of doing so.
-        
-    
-    
-        
-        FPU Multiply Hack
-        
-    
-    
-        
-        For Tales of Destiny.
-        
-    
-    
-        
-        Preload TLB Hack
-        
-    
-    
-        
-        Needed for some games with complex FMV rendering.
-        
-    
-    
-        
-        Skip MPEG Hack
-        
-    
-    
-        
-        Skips videos/FMVs in games to avoid game hanging/freezes.
-        
-    
-    
-        
-        OPH Flag Hack
-        
-    
-    
-        
-        EE Timing Hack
-        
-    
-    
-        
-        Instant DMA Hack
-        
-    
-    
-        
-        Known to affect following games: Mana Khemia 1, Metal Saga, Pilot Down Behind Enemy Lines.
-        
-    
-    
-        
-        For SOCOM 2 HUD and Spy Hunter loading hang.
-        
-    
-    
-        
-        VU Add Hack
-        
-    
-    
-        
-        Full VU0 Synchronization
-        
-    
-    
-        
-        Forces tight VU0 sync on every COP2 instruction.
-        
-    
-    
-        
-        VU Overflow Hack
-        
-    
-    
-        
-        To check for possible float overflows (Superman Returns).
-        
-    
-    
-        
-        Use accurate timing for VU XGKicks (slower).
-        
-    
-    
-        
-        Load State
-        
-    
-    
-        
-        Makes the emulated Emotion Engine skip cycles. Helps a small subset of games like SOTC. Most of the time it's harmful to performance.
-        
-    
-    
-        
-        Generally a speedup on CPUs with 4 or more cores. Safe for most games, but a few are incompatible and may hang.
-        
-    
-    
-        
-        Runs VU1 instantly. Provides a modest speed improvement in most games. Safe for most games, but a few games may exhibit graphical errors.
-        
-    
-    
-        
-        Disable the support of depth buffers in the texture cache.
+        GPU Target CLUT
         
     
     
         
-        Disable Render Fixes
+        Try to detect when a game is drawing its own color palette and then renders it on the GPU with special handling.
+        
+    
+    
+        
+        Skip Draw Start
         
     
     
         
-        Preload Frame Data
+        Object range to skip drawing.
         
     
     
-        
-        Texture Inside RT
+        
+        Skip Draw End
         
     
     
-        
-        When enabled GPU converts colormap-textures, otherwise the CPU will. It is a trade-off between GPU and CPU.
+        
+        Auto Flush (Hardware)
+        
+    
+    
+        
+        CPU Framebuffer Conversion
+        
+    
+    
+        
+        Disable Depth Conversion
+        
+    
+    
+        
+        Disable Safe Features
+        
+    
+    
+        
+        This option disables multiple safe features.
+        
+    
+    
+        
+        This option disables game-specific render fixes.
+        
+    
+    
+        
+        Uploads GS data when rendering a new frame to reproduce some effects accurately.
         
     
     
         
-        Half Pixel Offset
+        Disable Partial Invalidation
+        
+    
+    
+        
+        Removes texture cache entries when there is any intersection, rather than only the intersected areas.
+        
+    
+    
+        
+        Allows the texture cache to reuse as an input texture the inner portion of a previous framebuffer.
+        
+    
+    
+        
+        Read Targets When Closing
+        
+    
+    
+        
+        Flushes all targets in the texture cache back to local memory when shutting down.
+        
+    
+    
+        
+        Estimate Texture Region
+        
+    
+    
+        
+        Attempts to reduce the texture size when games do not set it themselves (e.g. Snowblind games).
         
     
     
         
-        Texture Offset X
+        GPU Palette Conversion
         
     
     
         
-        Texture Offset Y
+        Upscaling Fixes
+        
+    
+    
+        
+        Adjusts vertices relative to upscaling.
+        
+    
+    
+        
+        Native Scaling
+        
+    
+    
+        
+        Attempt to do rescaling at native resolution.
+        
+    
+    
+        
+        Round Sprite
+        
+    
+    
+        
+        Adjusts sprite coordinates.
+        
+    
+    
+        
+        Bilinear Upscale
+        
+    
+    
+        
+        Can smooth out textures due to be bilinear filtered when upscaling. E.g. Brave sun glare.
+        
+    
+    
+        
+        Adjusts target texture offsets.
+        
+    
+    
+        
+        Align Sprite
+        
+    
+    
+        
+        Fixes issues with upscaling (vertical lines) in some games.
+        
+    
+    
+        
+        Merge Sprite
+        
+    
+    
+        
+        Replaces multiple post-processing sprites with a larger single sprite.
+        
+    
+    
+        
+        Lowers the GS precision to avoid gaps between pixels when upscaling. Fixes the text on Wild Arms games.
+        
+    
+    
+        
+        Unscaled Palette Texture Draws
+        
+    
+    
+        
+        Can fix some broken effects which rely on pixel perfect precision.
         
     
     
         
-        Dumps replaceable textures to disk. Will reduce performance.
+        Texture Replacement
+        
+    
+    
+        
+        Load Textures
+        
+    
+    
+        
+        Loads replacement textures where available and user-provided.
+        
+    
+    
+        
+        Asynchronous Texture Loading
+        
+    
+    
+        
+        Loads replacement textures on a worker thread, reducing microstutter when replacements are enabled.
+        
+    
+    
+        
+        Precache Replacements
+        
+    
+    
+        
+        Preloads all replacement textures to memory. Not necessary with asynchronous loading.
+        
+    
+    
+        
+        Replacements Directory
+        
+    
+    
+        
+        Folders
+        
+    
+    
+        
+        Texture Dumping
+        
+    
+    
+        
+        Dump Textures
+        
+    
+    
+        
+        Dump Mipmaps
+        
+    
+    
+        
+        Includes mipmaps when dumping textures.
+        
+    
+    
+        
+        Dump FMV Textures
+        
+    
+    
+        
+        Allows texture dumping when FMVs are active. You should not enable this.
+        
+    
+    
+        
+        Post-Processing
+        
+    
+    
+        
+        FXAA
+        
+    
+    
+        
+        Enables FXAA post-processing shader.
+        
+    
+    
+        
+        Contrast Adaptive Sharpening
+        
+    
+    
+        
+        Enables FidelityFX Contrast Adaptive Sharpening.
+        
+    
+    
+        
+        CAS Sharpness
         
     
     
         
-        Applies a shader which replicates the visual effects of different styles of television set.
+        Determines the intensity the sharpening effect in CAS post-processing.
+        
+    
+    
+        
+        Filters
+        
+    
+    
+        
+        Shade Boost
         
     
     
         
-        Skips displaying frames that don't change in 25/30fps games. Can improve speed, but increase input lag/make frame pacing worse.
+        Enables brightness/contrast/saturation adjustment.
         
     
     
-        
-        Enables API-level validation of graphics commands.
+        
+        Shade Boost Brightness
+        
+    
+    
+        
+        Adjusts brightness. 50 is normal.
+        
+    
+    
+        
+        Shade Boost Contrast
+        
+    
+    
+        
+        Adjusts contrast. 50 is normal.
+        
+    
+    
+        
+        Shade Boost Saturation
+        
+    
+    
+        
+        Adjusts saturation. 50 is normal.
+        
+    
+    
+        
+        TV Shaders
+        
+    
+    
+        
+        Advanced
+        
+    
+    
+        
+        Skip Presenting Duplicate Frames
+        
+    
+    
+        
+        Extended Upscaling Multipliers
+        
+    
+    
+        
+        Displays additional, very high upscaling multipliers dependent on GPU capability.
+        
+    
+    
+        
+        Hardware Download Mode
+        
+    
+    
+        
+        Changes synchronization behavior for GS downloads.
+        
+    
+    
+        
+        Allow Exclusive Fullscreen
+        
+    
+    
+        
+        Overrides the driver's heuristics for enabling exclusive fullscreen, or direct flip/scanout.
+        
+    
+    
+        
+        Override Texture Barriers
+        
+    
+    
+        
+        Forces texture barrier functionality to the specified value.
+        
+    
+    
+        
+        GS Dump Compression
+        
+    
+    
+        
+        Sets the compression algorithm for GS dumps.
+        
+    
+    
+        
+        Disable Framebuffer Fetch
+        
+    
+    
+        
+        Prevents the usage of framebuffer fetch when supported by host GPU.
+        
+    
+    
+        
+        Disable Shader Cache
+        
+    
+    
+        
+        Prevents the loading and saving of shaders/pipelines to disk.
+        
+    
+    
+        
+        Disable Vertex Shader Expand
+        
+    
+    
+        
+        Falls back to the CPU for expanding sprites/lines.
+        
+    
+    
+        
+        Changes when SPU samples are generated relative to system emulation.
+        
+    
+    
+        
+        %d ms
+        
+    
+    
+        
+        Settings and Operations
+        
+    
+    
+        
+        Creates a new memory card file or folder.
+        
+    
+    
+        
+        Simulates a larger memory card by filtering saves only to the current game.
+        
+    
+    
+        
+        If not set, this card will be considered unplugged.
+        
+    
+    
+        
+        The selected memory card image will be used for this slot.
+        
+    
+    
+        
+        Enable/Disable the Player LED on DualSense controllers.
+        
+    
+    
+        
+        Trigger
+        
+    
+    
+        
+        Toggles the macro when the button is pressed, instead of held.
+        
+    
+    
+        
+        Savestate
+        
+    
+    
+        
+        Compression Method
+        
+    
+    
+        
+        Sets the compression algorithm for savestate.
+        
+    
+    
+        
+        Compression Level
+        
+    
+    
+        
+        Sets the compression level for savestate.
+        
+    
+    
+        
+        Version: %s
+        
+    
+    
+        
+        {:%H:%M}
+        
+    
+    
+        
+        Slot {}
+        
+    
+    
+        
+        Dark
+        
+    
+    
+        
+        Light
+        
+    
+    
+        
+        Grey Matter
+        
+    
+    
+        
+        Untouched Lagoon
+        
+    
+    
+        
+        Baby Pastel
+        
+    
+    
+        
+        Pizza Time!
+        
+    
+    
+        
+        PCSX2 Blue
+        
+    
+    
+        
+        Scarlet Devil
+        
+    
+    
+        
+        Violet Angel
+        
+    
+    
+        
+        Cobalt Sky
+        
+    
+    
+        
+        AMOLED
+        
+    
+    
+        
+        Enabled
+        
+    
+    
+        
+        1.25x Native (~450px)
+        
+    
+    
+        
+        1.5x Native (~540px)
+        
+    
+    
+        
+        1.75x Native (~630px)
+        
+    
+    
+        
+        2x Native (~720px/HD)
+        
+    
+    
+        
+        2.5x Native (~900px/HD+)
+        
+    
+    
+        
+        3x Native (~1080px/FHD)
+        
+    
+    
+        
+        3.5x Native (~1260px)
+        
+    
+    
+        
+        4x Native (~1440px/QHD)
+        
+    
+    
+        
+        5x Native (~1800px/QHD+)
+        
+    
+    
+        
+        6x Native (~2160px/4K UHD)
+        
+    
+    
+        
+        7x Native (~2520px)
+        
+    
+    
+        
+        8x Native (~2880px/5K UHD)
+        
+    
+    
+        
+        9x Native (~3240px)
+        
+    
+    
+        
+        10x Native (~3600px/6K UHD)
+        
+    
+    
+        
+        11x Native (~3960px)
+        
+    
+    
+        
+        12x Native (~4320px/8K UHD)
+        
+    
+    
+        
+        WebP
+        
+    
+    
+        
+        Align to Native
+        
+    
+    
+        
+        Align to Native - with Texture Offset
+        
+    
+    
+        
+        Aggressive
+        
+    
+    
+        
+        Enabled (Exact Match)
+        
+    
+    
+        
+        Enabled (Check Inside Target)
+        
+    
+    
+        
+        Deflate64
+        
+    
+    
+        
+        Zstandard
+        
+    
+    
+        
+        LZMA2
+        
+    
+    
+        
+        Low (Fast)
+        
+    
+    
+        
+        Medium (Recommended)
+        
+    
+    
+        
+        Very High (Slow, Not Recommended)
+        
+    
+    
+        
+        Change Selection
+        
+    
+    
+        
+        Select
+        
+    
+    
+        
+        Parent Directory
+        
+    
+    
+        
+        Enter Value
+        
+    
+    
+        
+        About
+        
+    
+    
+        
+        Toggle Fullscreen
+        
+    
+    
+        
+        Navigate
+        
+    
+    
+        
+        Load Global State
+        
+    
+    
+        
+        Change Page
+        
+    
+    
+        
+        Return To Game
+        
+    
+    
+        
+        Select State
+        
+    
+    
+        
+        Select Game
+        
+    
+    
+        
+        Change View
+        
+    
+    
+        
+        Launch Options
+        
+    
+    
+        
+        Create Save State Backups
+        
+    
+    
+        
+        Show PCSX2 Version
+        
+    
+    
+        
+        Show Input Recording Status
+        
+    
+    
+        
+        Show Video Capture Status
+        
+    
+    
+        
+        Show Frame Times
+        
+    
+    
+        
+        Show Hardware Info
+        
+    
+    
+        
+        Create Memory Card
+        
+    
+    
+        
+        Configuration
+        
+    
+    
+        
+        Start Game
+        
+    
+    
+        
+        Launch a game from a file, disc, or starts the console without any disc inserted.
+        
+    
+    
+        
+        Changes settings for the application.
+        
+    
+    
+        
+        Return to desktop mode, or exit the application.
+        
+    
+    
+        
+        Back
+        
+    
+    
+        
+        Return to the previous menu.
+        
+    
+    
+        
+        Exit PCSX2
+        
+    
+    
+        
+        Completely exits the application, returning you to your desktop.
+        
+    
+    
+        
+        Desktop Mode
+        
+    
+    
+        
+        Exits Big Picture mode, returning to the desktop interface.
+        
+    
+    
+        
+        Resets all configuration to defaults (including bindings).
+        
+    
+    
+        
+        Replaces these settings with a previously saved input profile.
+        
+    
+    
+        
+        Stores the current settings to an input profile.
+        
+    
+    
+        
+        Input Sources
+        
+    
+    
+        
+        The SDL input source supports most controllers.
+        
+    
+    
+        
+        Provides vibration and LED control support over Bluetooth.
+        
+    
+    
+        
+        Allow SDL to use raw access to input devices.
+        
+    
+    
+        
+        The XInput source provides support for XBox 360/XBox One/XBox Series controllers.
+        
+    
+    
+        
+        Multitap
+        
+    
+    
+        
+        Enables an additional three controller slots. Not supported in all games.
+        
+    
+    
+        
+        Attempts to map the selected port to a chosen controller.
+        
+    
+    
+        
+        Determines how much pressure is simulated when macro is active.
+        
+    
+    
+        
+        Determines the pressure required to activate the macro.
+        
+    
+    
+        
+        Toggle every %d frames
+        
+    
+    
+        
+        Clears all bindings for this USB controller.
+        
+    
+    
+        
+        Data Save Locations
+        
+    
+    
+        
+        Show Advanced Settings
+        
+    
+    
+        
+        Changing these options may cause games to become non-functional. Modify at your own risk, the PCSX2 team will not provide support for configurations with these settings changed.
+        
+    
+    
+        
+        Logging
+        
+    
+    
+        
+        System Console
+        
+    
+    
+        
+        Writes log messages to the system console (console window/standard output).
+        
+    
+    
+        
+        File Logging
+        
+    
+    
+        
+        Writes log messages to emulog.txt.
+        
+    
+    
+        
+        Verbose Logging
+        
+    
+    
+        
+        Writes dev log messages to log sinks.
+        
+    
+    
+        
+        Log Timestamps
+        
+    
+    
+        
+        Writes timestamps alongside log messages.
+        
+    
+    
+        
+        EE Console
+        
+    
+    
+        
+        Writes debug messages from the game's EE code to the console.
+        
+    
+    
+        
+        IOP Console
+        
+    
+    
+        
+        Writes debug messages from the game's IOP code to the console.
+        
+    
+    
+        
+        CDVD Verbose Reads
+        
+    
+    
+        
+        Logs disc reads from games.
+        
+    
+    
+        
+        Emotion Engine
+        
+    
+    
+        
+        Rounding Mode
+        
+    
+    
+        
+        Determines how the results of floating-point operations are rounded. Some games need specific settings.
+        
+    
+    
+        
+        Division Rounding Mode
+        
+    
+    
+        
+        Determines how the results of floating-point division is rounded. Some games need specific settings.
+        
+    
+    
+        
+        Clamping Mode
+        
+    
+    
+        
+        Determines how out-of-range floating point numbers are handled. Some games need specific settings.
+        
+    
+    
+        
+        Enable EE Recompiler
+        
+    
+    
+        
+        Performs just-in-time binary translation of 64-bit MIPS-IV machine code to native code.
+        
+    
+    
+        
+        Enable EE Cache
+        
+    
+    
+        
+        Enables simulation of the EE's cache. Slow.
+        
+    
+    
+        
+        Enable INTC Spin Detection
+        
+    
+    
+        
+        Huge speedup for some games, with almost no compatibility side effects.
+        
+    
+    
+        
+        Enable Wait Loop Detection
+        
+    
+    
+        
+        Moderate speedup for some games, with no known side effects.
+        
+    
+    
+        
+        Enable Fast Memory Access
+        
+    
+    
+        
+        Uses backpatching to avoid register flushing on every memory access.
+        
+    
+    
+        
+        Vector Units
+        
+    
+    
+        
+        VU0 Rounding Mode
+        
+    
+    
+        
+        VU0 Clamping Mode
+        
+    
+    
+        
+        VU1 Rounding Mode
+        
+    
+    
+        
+        VU1 Clamping Mode
+        
+    
+    
+        
+        Enable VU0 Recompiler (Micro Mode)
+        
+    
+    
+        
+        New Vector Unit recompiler with much improved compatibility. Recommended.
+        
+    
+    
+        
+        Enable VU1 Recompiler
+        
+    
+    
+        
+        Enable VU Flag Optimization
+        
+    
+    
+        
+        Good speedup and high compatibility, may cause graphical errors.
+        
+    
+    
+        
+        I/O Processor
+        
+    
+    
+        
+        Enable IOP Recompiler
+        
+    
+    
+        
+        Performs just-in-time binary translation of 32-bit MIPS-I machine code to native code.
+        
+    
+    
+        
+        Graphics
+        
+    
+    
+        
+        Use Debug Device
+        
+    
+    
+        
+        Settings
         
     
     
         
-        Use Software Renderer For FMVs
+        No cheats are available for this game.
+        
+    
+    
+        
+        Cheat Codes
+        
+    
+    
+        
+        No patches are available for this game.
+        
+    
+    
+        
+        Game Patches
+        
+    
+    
+        
+        Activating cheats can cause unpredictable behavior, crashing, soft-locks, or broken saved games.
         
     
     
         
-        To avoid TLB miss on Goemon.
+        Activating game patches can cause unpredictable behavior, crashing, soft-locks, or broken saved games.
+        
+    
+    
+        
+        Use patches at your own risk, the PCSX2 team will provide no support for users who have enabled game patches.
         
     
     
         
-        General-purpose timing hack. Known to affect following games: Digital Devil Saga, SSX.
+        Game Fixes
+        
+    
+    
+        
+        Game fixes should not be modified unless you are aware of what each option does and the implications of doing so.
         
     
     
         
-        Good for cache emulation problems. Known to affect following games: Fire Pro Wrestling Z.
+        FPU Multiply Hack
         
     
     
-        
-        Known to affect following games: Bleach Blade Battlers, Growlanser II and III, Wizardry.
+        
+        For Tales of Destiny.
+        
+    
+    
+        
+        Preload TLB Hack
         
     
     
         
-        Emulate GIF FIFO
+        Needed for some games with complex FMV rendering.
         
     
     
         
-        Correct but slower. Known to affect the following games: Fifa Street 2.
+        Skip MPEG Hack
         
     
     
         
-        DMA Busy Hack
+        Skips videos/FMVs in games to avoid game hanging/freezes.
         
     
     
-        
-        Delay VIF1 Stalls
+        
+        OPH Flag Hack
         
     
     
-        
-        Emulate VIF FIFO
+        
+        EE Timing Hack
         
     
     
         
-        Simulate VIF1 FIFO read ahead. Known to affect following games: Test Drive Unlimited, Transformers.
+        Instant DMA Hack
         
     
     
-        
-        VU I Bit Hack
-        
-    
-    
-        
-        Avoids constant recompilation in some games. Known to affect the following games: Scarface The World is Yours, Crash Tag Team Racing.
-        
-    
-    
-        
-        For Tri-Ace Games: Star Ocean 3, Radiata Stories, Valkyrie Profile 2.
+        
+        Known to affect following games: Mana Khemia 1, Metal Saga, Pilot Down Behind Enemy Lines.
         
     
     
         
-        VU Sync
-        
-    
-    
-        
-        Run behind. To avoid sync problems when reading or writing VU registers.
-        
-    
-    
-        
-        VU XGKick Sync
-        
-    
-    
-        
-        Force Blit Internal FPS Detection
+        For SOCOM 2 HUD and Spy Hunter loading hang.
         
     
     
         
-        Save State
+        VU Add Hack
         
     
     
-        
-        Load Resume State
+        
+        Full VU0 Synchronization
+        
+    
+    
+        
+        Forces tight VU0 sync on every COP2 instruction.
         
     
     
         
+        VU Overflow Hack
+        
+    
+    
+        
+        To check for possible float overflows (Superman Returns).
+        
+    
+    
+        
+        Use accurate timing for VU XGKicks (slower).
+        
+    
+    
+        
+        Load State
+        
+    
+    
+        
+        Makes the emulated Emotion Engine skip cycles. Helps a small subset of games like SOTC. Most of the time it's harmful to performance.
+        
+    
+    
+        
+        Generally a speedup on CPUs with 4 or more cores. Safe for most games, but a few are incompatible and may hang.
+        
+    
+    
+        
+        Runs VU1 instantly. Provides a modest speed improvement in most games. Safe for most games, but a few games may exhibit graphical errors.
+        
+    
+    
+        
+        Disable the support of depth buffers in the texture cache.
+        
+    
+    
+        
+        Disable Render Fixes
+        
+    
+    
+        
+        Preload Frame Data
+        
+    
+    
+        
+        Texture Inside RT
+        
+    
+    
+        
+        When enabled GPU converts colormap-textures, otherwise the CPU will. It is a trade-off between GPU and CPU.
+        
+    
+    
+        
+        Half Pixel Offset
+        
+    
+    
+        
+        Texture Offset X
+        
+    
+    
+        
+        Texture Offset Y
+        
+    
+    
+        
+        Dumps replaceable textures to disk. Will reduce performance.
+        
+    
+    
+        
+        Applies a shader which replicates the visual effects of different styles of television set.
+        
+    
+    
+        
+        Skips displaying frames that don't change in 25/30fps games. Can improve speed, but increase input lag/make frame pacing worse.
+        
+    
+    
+        
+        Enables API-level validation of graphics commands.
+        
+    
+    
+        
+        Use Software Renderer For FMVs
+        
+    
+    
+        
+        To avoid TLB miss on Goemon.
+        
+    
+    
+        
+        General-purpose timing hack. Known to affect following games: Digital Devil Saga, SSX.
+        
+    
+    
+        
+        Good for cache emulation problems. Known to affect following games: Fire Pro Wrestling Z.
+        
+    
+    
+        
+        Known to affect following games: Bleach Blade Battlers, Growlanser II and III, Wizardry.
+        
+    
+    
+        
+        Emulate GIF FIFO
+        
+    
+    
+        
+        Correct but slower. Known to affect the following games: Fifa Street 2.
+        
+    
+    
+        
+        DMA Busy Hack
+        
+    
+    
+        
+        Delay VIF1 Stalls
+        
+    
+    
+        
+        Emulate VIF FIFO
+        
+    
+    
+        
+        Simulate VIF1 FIFO read ahead. Known to affect following games: Test Drive Unlimited, Transformers.
+        
+    
+    
+        
+        VU I Bit Hack
+        
+    
+    
+        
+        Avoids constant recompilation in some games. Known to affect the following games: Scarface The World is Yours, Crash Tag Team Racing.
+        
+    
+    
+        
+        For Tri-Ace Games: Star Ocean 3, Radiata Stories, Valkyrie Profile 2.
+        
+    
+    
+        
+        VU Sync
+        
+    
+    
+        
+        Run behind. To avoid sync problems when reading or writing VU registers.
+        
+    
+    
+        
+        VU XGKick Sync
+        
+    
+    
+        
+        Force Blit Internal FPS Detection
+        
+    
+    
+        
+        Save State
+        
+    
+    
+        
+        Load Resume State
+        
+    
+    
+        
         A resume save state created at %s was found.
 
 Do you want to load this save and continue?
         
     
     
-        
+        
         Region: 
         
     
     
-        
+        
         Compatibility: 
         
     
     
-        
+        
         No Game Selected
         
     
     
-        
+        
         Search Directories
         
     
     
-        
+        
         Adds a new directory to the game search list.
         
     
     
-        
+        
         Scanning Subdirectories
         
     
     
-        
+        
         Not Scanning Subdirectories
         
     
     
-        
+        
         List Settings
         
     
     
-        
+        
         Sets which view the game list will open to.
         
     
     
-        
+        
         Determines which field the game list will be sorted by.
         
     
     
-        
+        
         Reverses the game list sort order from the default (usually ascending to descending).
         
     
     
-        
+        
         Cover Settings
         
     
     
-        
+        
         Downloads covers from a user-specified URL template.
         
     
     
-        
+        
         Operations
         
     
     
-        
+        
         Selects where anisotropic filtering is utilized when rendering textures.
         
     
     
-        
+        
         Use alternative method to calculate internal FPS to avoid false readings in some games.
         
     
     
-        
+        
         Identifies any new files added to the game directories.
         
     
     
-        
+        
         Forces a full rescan of all games previously identified.
         
     
     
-        
+        
         Download Covers
         
     
     
-        
+        
         About PCSX2
         
     
     
-        
+        
         PCSX2 is a free and open-source PlayStation 2 (PS2) emulator. Its purpose is to emulate the PS2's hardware, using a combination of MIPS CPU Interpreters, Recompilers and a Virtual Machine which manages hardware states and PS2 system memory. This allows you to play PS2 games on your PC, with many additional features and benefits.
         
     
     
-        
+        
         PlayStation 2 and PS2 are registered trademarks of Sony Interactive Entertainment. This application is not affiliated in any way with Sony Interactive Entertainment.
         
     
     
-        
+        
         When enabled and logged in, PCSX2 will scan for achievements on startup.
         
     
     
-        
+        
         "Challenge" mode for achievements, including leaderboard tracking. Disables save state, cheats, and slowdown functions.
         
     
     
-        
+        
         Displays popup messages on events such as achievement unlocks and leaderboard submissions.
         
     
     
-        
+        
         Plays sound effects for events such as achievement unlocks and leaderboard submissions.
         
     
     
-        
+        
         Shows icons in the lower-right corner of the screen when a challenge/primed achievement is active.
         
     
     
-        
+        
         When enabled, PCSX2 will list achievements from unofficial sets. These achievements are not tracked by RetroAchievements.
         
     
     
-        
+        
         When enabled, PCSX2 will assume all achievements are locked and not send any unlock notifications to the server.
         
     
     
-        
+        
         Error
         
     
     
-        
+        
         Pauses the emulator when a controller with bindings is disconnected.
         
     
     
-        
+        
         Creates a backup copy of a save state if it already exists when the save is created. The backup copy has a .backup suffix
         
     
     
-        
+        
         Enable CDVD Precaching
         
     
     
-        
+        
         Loads the disc image into RAM before starting the virtual machine.
         
     
     
-        
+        
         Vertical Sync (VSync)
         
     
     
-        
+        
         Sync to Host Refresh Rate
         
     
     
-        
+        
         Use Host VSync Timing
         
     
     
-        
+        
         Disables PCSX2's internal frame timing, and uses host vsync instead.
         
     
     
-        
+        
         Disable Mailbox Presentation
         
     
     
-        
+        
         Forces the use of FIFO over Mailbox presentation, i.e. double buffering instead of triple buffering. Usually results in worse frame pacing.
         
     
     
-        
+        
         Audio Control
         
     
     
-        
+        
         Controls the volume of the audio played on the host.
         
     
     
-        
+        
         Fast Forward Volume
         
     
     
-        
+        
         Controls the volume of the audio played on the host when fast forwarding.
         
     
     
-        
+        
         Mute All Sound
         
     
     
-        
+        
         Prevents the emulator from producing any audible sound.
         
     
     
-        
+        
         Backend Settings
         
     
     
-        
+        
         Audio Backend
         
     
     
-        
+        
         The audio backend determines how frames produced by the emulator are submitted to the host.
         
     
     
-        
+        
         Expansion
         
     
     
-        
+        
         Determines how audio is expanded from stereo to surround for supported games.
         
     
     
-        
+        
         Synchronization
         
     
     
-        
+        
         Buffer Size
         
     
     
-        
+        
         Determines the amount of audio buffered before being pulled by the host API.
         
     
     
-        
+        
         Output Latency
         
     
     
-        
+        
         Determines how much latency there is between the audio being picked up by the host API, and played through speakers.
         
     
     
-        
+        
         Minimal Output Latency
         
     
     
-        
+        
         When enabled, the minimum supported output latency will be used for the host API.
         
     
     
-        
+        
         Thread Pinning
         
     
     
-        
+        
         Force Even Sprite Position
         
     
     
-        
+        
         Displays popup messages when starting, submitting, or failing a leaderboard challenge.
         
     
     
-        
+        
         When enabled, each session will behave as if no achievements have been unlocked.
         
     
     
-        
+        
         Account
         
     
     
-        
+        
         Logs out of RetroAchievements.
         
     
     
-        
+        
         Logs in to RetroAchievements.
         
     
     
-        
+        
         Current Game
         
     
     
-        
+        
         An error occurred while deleting empty game settings:
 {}
         
     
     
-        
+        
         An error occurred while saving game settings:
 {}
         
     
     
-        
+        
         {} is not a valid disc image.
         
     
     
-        
+        
         Automatic mapping completed for {}.
         
     
     
-        
+        
         Automatic mapping failed for {}.
         
     
     
-        
+        
         Game settings initialized with global settings for '{}'.
         
     
     
-        
+        
         Game settings have been cleared for '{}'.
         
     
     
-        
+        
         Uses {} as confirm when using a controller
         
     
     
-        
+        
         {} (Current)
         
     
     
-        
+        
         {} (Folder)
         
     
     
-        
+        
         Failed to load '{}'.
         
     
     
-        
+        
         Input profile '{}' loaded.
         
     
     
-        
+        
         Input profile '{}' saved.
         
     
     
-        
+        
         Failed to save input profile '{}'.
         
     
     
-        
+        
         Port {} Controller Type
         
     
     
-        
+        
         Select Macro {} Binds
         
     
     
-        
+        
         Port {} Device
         
     
     
-        
+        
         Port {} Subtype
         
     
     
-        
+        
         {} unlabelled patch codes will automatically activate.
         
     
     
-        
+        
         {} unlabelled patch codes found but not enabled.
         
     
     
-        
+        
         This Session: {}
         
     
     
-        
+        
         All Time: {}
         
     
     
-        
+        
         Save Slot {0}
         
     
     
-        
+        
         Saved {}
         
     
     
-        
+        
         {} does not exist.
         
     
     
-        
+        
         {} deleted.
         
     
     
-        
+        
         Failed to delete {}.
         
     
     
-        
+        
         File: {}
         
     
     
-        
+        
         CRC: {:08X}
         
     
     
-        
+        
         Time Played: {}
         
     
     
-        
+        
         Last Played: {}
         
     
     
-        
+        
         Size: {:.2f} MB
         
     
     
-        
+        
         Left: 
         
     
     
-        
+        
         Top: 
         
     
     
-        
+        
         Right: 
         
     
     
-        
+        
         Bottom: 
         
     
     
-        
+        
         Summary
         
     
     
-        
+        
         Interface Settings
         
     
     
-        
+        
         BIOS Settings
         
     
     
-        
+        
         Emulation Settings
         
     
     
-        
+        
         Graphics Settings
         
     
     
-        
+        
         Audio Settings
         
     
     
-        
+        
         Memory Card Settings
         
     
     
-        
+        
         Controller Settings
         
     
     
-        
+        
         Hotkey Settings
         
     
     
-        
+        
         Achievements Settings
         
     
     
-        
+        
         Folder Settings
         
     
     
-        
+        
         Advanced Settings
         
     
     
-        
+        
         Patches
         
     
     
-        
+        
         Cheats
         
     
     
-        
+        
         2% [1 FPS (NTSC) / 1 FPS (PAL)]
         
     
     
-        
+        
         10% [6 FPS (NTSC) / 5 FPS (PAL)]
         
     
     
-        
+        
         25% [15 FPS (NTSC) / 12 FPS (PAL)]
         
     
     
-        
+        
         50% [30 FPS (NTSC) / 25 FPS (PAL)]
         
     
     
-        
+        
         75% [45 FPS (NTSC) / 37 FPS (PAL)]
         
     
     
-        
+        
         90% [54 FPS (NTSC) / 45 FPS (PAL)]
         
     
     
-        
+        
         100% [60 FPS (NTSC) / 50 FPS (PAL)]
         
     
     
-        
+        
         110% [66 FPS (NTSC) / 55 FPS (PAL)]
         
     
     
-        
+        
         120% [72 FPS (NTSC) / 60 FPS (PAL)]
         
     
     
-        
+        
         150% [90 FPS (NTSC) / 75 FPS (PAL)]
         
     
     
-        
+        
         175% [105 FPS (NTSC) / 87 FPS (PAL)]
         
     
     
-        
+        
         200% [120 FPS (NTSC) / 100 FPS (PAL)]
         
     
     
-        
+        
         300% [180 FPS (NTSC) / 150 FPS (PAL)]
         
     
     
-        
+        
         400% [240 FPS (NTSC) / 200 FPS (PAL)]
         
     
     
-        
+        
         500% [300 FPS (NTSC) / 250 FPS (PAL)]
         
     
     
-        
+        
         1000% [600 FPS (NTSC) / 500 FPS (PAL)]
         
     
     
-        
+        
         50% Speed
         
     
     
-        
+        
         60% Speed
         
     
     
-        
+        
         75% Speed
         
     
     
-        
+        
         100% Speed (Default)
         
     
     
-        
+        
         130% Speed
         
     
     
-        
+        
         180% Speed
         
     
     
-        
+        
         300% Speed
         
     
     
-        
+        
         Normal (Default)
         
     
     
-        
+        
         Mild Underclock
         
     
     
-        
+        
         Moderate Underclock
         
     
     
-        
+        
         Maximum Underclock
         
     
     
-        
+        
         Disabled
         
     
     
-        
+        
         0 Frames (Hard Sync)
         
     
     
-        
+        
         1 Frame
         
     
     
-        
+        
         2 Frames
         
     
     
-        
+        
         3 Frames
         
     
     
-        
+        
         None
         
     
     
-        
+        
         Extra + Preserve Sign
         
     
     
-        
+        
         Full
         
     
     
-        
+        
         Extra
         
     
     
-        
+        
         Automatic (Default)
         
     
     
-        
+        
         Direct3D 11
         
     
     
-        
+        
         Direct3D 12
         
     
     
-        
+        
         OpenGL
         
     
     
-        
+        
         Vulkan
         
     
     
-        
+        
         Metal
         
     
     
-        
+        
         Software
         
     
     
-        
+        
         Null
         
     
     
-        
+        
         Off
         
     
     
-        
+        
         Bilinear (Smooth)
         
     
     
-        
+        
         Bilinear (Sharp)
         
     
     
-        
+        
         Weave (Top Field First, Sawtooth)
         
     
     
-        
+        
         Weave (Bottom Field First, Sawtooth)
         
     
     
-        
+        
         Bob (Top Field First)
         
     
     
-        
+        
         Bob (Bottom Field First)
         
     
     
-        
+        
         Blend (Top Field First, Half FPS)
         
     
     
-        
+        
         Blend (Bottom Field First, Half FPS)
         
     
     
-        
+        
         Adaptive (Top Field First)
         
     
     
-        
+        
         Adaptive (Bottom Field First)
         
     
     
-        
+        
         Native (PS2)
         
     
     
-        
+        
         Nearest
         
     
     
-        
+        
         Bilinear (Forced)
         
     
     
-        
+        
         Bilinear (PS2)
         
     
     
-        
+        
         Bilinear (Forced excluding sprite)
         
     
     
-        
+        
         Off (None)
         
     
     
-        
+        
         Trilinear (PS2)
         
     
     
-        
+        
         Trilinear (Forced)
         
     
     
-        
+        
         Scaled
         
     
     
-        
+        
         Unscaled (Default)
         
     
     
-        
+        
         Minimum
         
     
     
-        
+        
         Basic (Recommended)
         
     
     
-        
+        
         Medium
         
     
     
-        
+        
         High
         
     
     
-        
+        
         Full (Slow)
         
     
     
-        
+        
         Maximum (Very Slow)
         
     
     
-        
+        
         Off (Default)
         
     
     
-        
+        
         2x
         
     
     
-        
+        
         4x
         
     
     
-        
+        
         8x
         
     
     
-        
+        
         16x
         
     
     
-        
+        
         Partial
         
     
     
-        
+        
         Full (Hash Cache)
         
     
     
-        
+        
         Force Disabled
         
     
     
-        
+        
         Force Enabled
         
     
     
-        
+        
         Accurate (Recommended)
         
     
     
-        
+        
         Disable Readbacks (Synchronize GS Thread)
         
     
     
-        
+        
         Unsynchronized (Non-Deterministic)
         
     
     
-        
+        
         Disabled (Ignore Transfers)
         
     
     
-        
+        
         Screen Resolution
         
     
     
-        
+        
         Internal Resolution (Aspect Uncorrected)
         
     
     
-        
+        
         Load/Save State
         
     
     
-        
+        
         WARNING: Memory Card Busy
         
     
     
-        
+        
         Cannot show details for games which were not scanned in the game list.
         
     
     
-        
+        
         Theme
         
     
     
-        
+        
         Pause On Controller Disconnection
         
     
     
-        
+        
         Use Save State Selector
         
     
     
-        
+        
         Swap OK/Cancel in Big Picture Mode
         
     
     
-        
+        
         SDL DualSense Player LED
         
     
     
-        
+        
         Press To Toggle
         
     
     
-        
+        
         Deadzone
         
     
     
-        
+        
         Full Boot
         
     
     
-        
+        
         Achievement Notifications
         
     
     
-        
+        
         Leaderboard Notifications
         
     
     
-        
+        
         Enable In-Game Overlays
         
     
     
-        
+        
         Encore Mode
         
     
     
-        
+        
         Spectator Mode
         
     
     
-        
+        
         PNG
         
     
     
-        
+        
         -
         
     
     
-        
+        
         Convert 4-bit and 8-bit framebuffer on the CPU instead of the GPU.
         
     
     
-        
+        
         Removes the current card from the slot.
         
     
     
-        
+        
         Determines the frequency at which the macro will toggle the buttons on and off (aka auto fire).
         
     
     
-        
+        
         {} Frames
         
     
     
-        
+        
         No Deinterlacing
         
     
     
-        
+        
         Force 32bit
         
     
     
-        
+        
         JPEG
         
     
     
-        
+        
         0 (Disabled)
         
     
     
-        
+        
         1 (64 Max Width)
         
     
     
-        
+        
         2 (128 Max Width)
         
     
     
-        
+        
         3 (192 Max Width)
         
     
     
-        
+        
         4 (256 Max Width)
         
     
     
-        
+        
         5 (320 Max Width)
         
     
     
-        
+        
         6 (384 Max Width)
         
     
     
-        
+        
         7 (448 Max Width)
         
     
     
-        
+        
         8 (512 Max Width)
         
     
     
-        
+        
         9 (576 Max Width)
         
     
     
-        
+        
         10 (640 Max Width)
         
     
     
-        
+        
         Sprites Only
         
     
     
-        
+        
         Sprites/Triangles
         
     
     
-        
+        
         Blended Sprites/Triangles
         
     
     
-        
+        
         1 (Normal)
         
     
     
-        
+        
         2 (Aggressive)
         
     
     
-        
+        
         Inside Target
         
     
     
-        
+        
         Merge Targets
         
     
     
-        
+        
         Normal (Vertex)
         
     
     
-        
+        
         Special (Texture)
         
     
     
-        
+        
         Special (Texture - Aggressive)
         
     
     
-        
+        
         Half
         
     
     
-        
+        
         Force Bilinear
         
     
     
-        
+        
         Force Nearest
         
     
     
-        
+        
         Disabled (Default)
         
     
     
-        
+        
         Enabled (Sprites Only)
         
     
     
-        
+        
         Enabled (All Primitives)
         
     
     
-        
+        
         None (Default)
         
     
     
-        
+        
         Sharpen Only (Internal Resolution)
         
     
     
-        
+        
         Sharpen and Resize (Display Resolution)
         
     
     
-        
+        
         Scanline Filter
         
     
     
-        
+        
         Diagonal Filter
         
     
     
-        
+        
         Triangular Filter
         
     
     
-        
+        
         Wave Filter
         
     
     
-        
+        
         Lottes CRT
         
     
     
-        
+        
         4xRGSS
         
     
     
-        
+        
         NxAGSS
         
     
     
-        
+        
         Uncompressed
         
     
     
-        
+        
         LZMA (xz)
         
     
     
-        
+        
         Zstandard (zst)
         
     
     
-        
+        
         PS2 (8MB)
         
     
     
-        
+        
         PS2 (16MB)
         
     
     
-        
+        
         PS2 (32MB)
         
     
     
-        
+        
         PS2 (64MB)
         
     
     
-        
+        
         PS1
         
     
     
-        
+        
         Negative
         
     
     
-        
+        
         Positive
         
     
     
-        
+        
         Chop/Zero (Default)
         
     
     
-        
+        
         Game Grid
         
     
     
-        
+        
         Game List
         
     
     
-        
+        
         Game List Settings
         
     
     
-        
+        
         Type
         
     
     
-        
+        
         Serial
         
     
     
-        
+        
         Title
         
     
     
-        
+        
         File Title
         
     
     
-        
+        
         CRC
         
     
     
-        
+        
         Time Played
         
     
     
-        
+        
         Last Played
         
     
     
-        
+        
         Size
         
     
     
-        
+        
         Select Disc Image
         
     
     
-        
+        
         Select Disc Drive
         
     
     
-        
+        
         Start File
         
     
     
-        
+        
         Start BIOS
         
     
     
-        
+        
         Start Disc
         
     
     
-        
+        
         Exit
         
     
     
-        
+        
         Set Input Binding
         
     
     
-        
+        
         Region
         
     
     
-        
+        
         Compatibility Rating
         
     
     
-        
+        
         Path
         
     
     
-        
+        
         Disc Path
         
     
     
-        
+        
         Select Disc Path
         
     
     
-        
+        
         Copy Settings
         
     
     
-        
+        
         Clear Settings
         
     
     
-        
+        
         Inhibit Screensaver
         
     
     
-        
+        
         Enable Discord Presence
         
     
     
-        
+        
         Pause On Start
         
     
     
-        
+        
         Pause On Focus Loss
         
     
     
-        
+        
         Pause On Menu
         
     
     
-        
+        
         Confirm Shutdown
         
     
     
-        
+        
         Save State On Shutdown
         
     
     
-        
+        
         Start Fullscreen
         
     
     
-        
+        
         Double-Click Toggles Fullscreen
         
     
     
-        
+        
         Hide Cursor In Fullscreen
         
     
     
-        
+        
         OSD Scale
         
     
     
-        
+        
         Show Messages
         
     
     
-        
+        
         Show Speed
         
     
     
-        
+        
         Show FPS
         
     
     
-        
+        
         Show CPU Usage
         
     
     
-        
+        
         Show GPU Usage
         
     
     
-        
+        
         Show Resolution
         
     
     
-        
+        
         Show GS Statistics
         
     
     
-        
+        
         Show Status Indicators
         
     
     
-        
+        
         Show Settings
         
     
     
-        
+        
         Show Inputs
         
     
     
-        
+        
         Warn About Unsafe Settings
         
     
     
-        
+        
         Reset Settings
         
     
     
-        
+        
         Change Search Directory
         
     
     
-        
+        
         Fast Boot
         
     
     
-        
+        
         Output Volume
         
     
     
-        
+        
         Memory Card Directory
         
     
     
-        
+        
         Folder Memory Card Filter
         
     
     
-        
+        
         Create
         
     
     
-        
+        
         Cancel
         
     
     
-        
+        
         Load Profile
         
     
     
-        
+        
         Save Profile
         
     
     
-        
+        
         Enable SDL Input Source
         
     
     
-        
+        
         SDL DualShock 4 / DualSense Enhanced Mode
         
     
     
-        
+        
         SDL Raw Input
         
     
     
-        
+        
         Enable XInput Input Source
         
     
     
-        
+        
         Enable Console Port 1 Multitap
         
     
     
-        
+        
         Enable Console Port 2 Multitap
         
     
     
-        
+        
         Controller Port {}{}
         
     
     
-        
+        
         Controller Port {}
         
     
     
-        
+        
         Controller Type
         
     
     
-        
+        
         Automatic Mapping
         
     
     
-        
+        
         Controller Port {}{} Macros
         
     
     
-        
+        
         Controller Port {} Macros
         
     
     
-        
+        
         Macro Button {}
         
     
     
-        
+        
         Buttons
         
     
     
-        
+        
         Frequency
         
     
     
-        
+        
         Pressure
         
     
     
-        
+        
         Controller Port {}{} Settings
         
     
     
-        
+        
         Controller Port {} Settings
         
     
     
-        
+        
         USB Port {}
         
     
     
-        
+        
         Device Type
         
     
     
-        
+        
         Device Subtype
         
     
     
-        
+        
         {} Bindings
         
     
     
-        
+        
         Clear Bindings
         
     
     
-        
+        
         {} Settings
         
     
     
-        
+        
         Cache Directory
         
     
     
-        
+        
         Covers Directory
         
     
     
-        
+        
         Snapshots Directory
         
     
     
-        
+        
         Save States Directory
         
     
     
-        
+        
         Game Settings Directory
         
     
     
-        
+        
         Input Profile Directory
         
     
     
-        
+        
         Cheats Directory
         
     
     
-        
+        
         Patches Directory
         
     
     
-        
+        
         Texture Replacements Directory
         
     
     
-        
+        
         Video Dumping Directory
         
     
     
-        
+        
         Resume Game
         
     
     
-        
+        
         Toggle Frame Limit
         
     
     
-        
+        
         Game Properties
         
     
     
-        
+        
         Achievements
         
     
     
-        
+        
         Save Screenshot
         
     
     
-        
+        
         Switch To Software Renderer
         
     
     
-        
+        
         Switch To Hardware Renderer
         
     
     
-        
+        
         Change Disc
         
     
     
-        
+        
         Close Game
         
     
     
-        
+        
         Exit Without Saving
         
     
     
-        
+        
         Back To Pause Menu
         
     
     
-        
+        
         Exit And Save State
         
     
     
-        
+        
         Leaderboards
         
     
     
-        
+        
         Delete Save
         
     
     
-        
+        
         Close Menu
         
     
     
-        
+        
         Delete State
         
     
     
-        
+        
         Default Boot
         
     
     
-        
+        
         Reset Play Time
         
     
     
-        
+        
         Add Search Directory
         
     
     
-        
+        
         Open in File Browser
         
     
     
-        
+        
         Disable Subdirectory Scanning
         
     
     
-        
+        
         Enable Subdirectory Scanning
         
     
     
-        
+        
         Remove From List
         
     
     
-        
+        
         Default View
         
     
     
-        
+        
         Sort By
         
     
     
-        
+        
         Sort Reversed
         
     
     
-        
+        
         Scan For New Games
         
     
     
-        
+        
         Rescan All Games
         
     
     
-        
+        
         Website
         
     
     
-        
+        
         Support Forums
         
     
     
-        
+        
         GitHub Repository
         
     
     
-        
+        
         License
         
     
     
-        
+        
         Close
         
     
     
-        
+        
         RAIntegration is being used instead of the built-in achievements implementation.
         
     
     
-        
+        
         Enable Achievements
         
     
     
-        
+        
         Hardcore Mode
         
     
     
-        
+        
         Sound Effects
         
     
     
-        
+        
         Test Unofficial Achievements
         
     
     
-        
+        
         Username: {}
         
     
     
-        
+        
         Login token generated on {}
         
     
     
-        
+        
         Logout
         
     
     
-        
+        
         Not Logged In
         
     
     
-        
+        
         Login
         
     
     
-        
+        
         Game: {0} ({1})
         
     
     
-        
+        
         Rich presence inactive or unsupported.
         
     
     
-        
+        
         Game not loaded or no RetroAchievements available.
         
     
     
-        
+        
         Card Enabled
         
     
     
-        
+        
         Card Name
         
     
     
-        
+        
         Eject Card
         
     
diff --git a/pcsx2/ImGui/FullscreenUI.cpp b/pcsx2/ImGui/FullscreenUI.cpp
index c7b9402b60..f41316f4a5 100644
--- a/pcsx2/ImGui/FullscreenUI.cpp
+++ b/pcsx2/ImGui/FullscreenUI.cpp
@@ -7400,6 +7400,8 @@ TRANSLATE_NOOP("FullscreenUI", "CPU Sprite Render Level");
 TRANSLATE_NOOP("FullscreenUI", "Determines filter level for CPU sprite render.");
 TRANSLATE_NOOP("FullscreenUI", "Software CLUT Render");
 TRANSLATE_NOOP("FullscreenUI", "Uses software renderer to draw texture CLUT points/sprites.");
+TRANSLATE_NOOP("FullscreenUI", "GPU Target CLUT");
+TRANSLATE_NOOP("FullscreenUI", "Try to detect when a game is drawing its own color palette and then renders it on the GPU with special handling.");
 TRANSLATE_NOOP("FullscreenUI", "Skip Draw Start");
 TRANSLATE_NOOP("FullscreenUI", "Object range to skip drawing.");
 TRANSLATE_NOOP("FullscreenUI", "Skip Draw End");
@@ -7874,6 +7876,8 @@ TRANSLATE_NOOP("FullscreenUI", "Force Nearest");
 TRANSLATE_NOOP("FullscreenUI", "Disabled (Default)");
 TRANSLATE_NOOP("FullscreenUI", "Enabled (Sprites Only)");
 TRANSLATE_NOOP("FullscreenUI", "Enabled (All Primitives)");
+TRANSLATE_NOOP("FullscreenUI", "Enabled (Exact Match)");
+TRANSLATE_NOOP("FullscreenUI", "Enabled (Check Inside Target)");
 TRANSLATE_NOOP("FullscreenUI", "None (Default)");
 TRANSLATE_NOOP("FullscreenUI", "Sharpen Only (Internal Resolution)");
 TRANSLATE_NOOP("FullscreenUI", "Sharpen and Resize (Display Resolution)");

From 6681614f1a250a9af290078b49e43f9c17b24cf1 Mon Sep 17 00:00:00 2001
From: TellowKrinkle 
Date: Tue, 25 Mar 2025 22:16:29 -0500
Subject: [PATCH 026/162] Core:Rec: Adjust bounds check asserts to not
 erroneously trip

---
 pcsx2/x86/iR3000A.cpp        | 2 +-
 pcsx2/x86/ix86-32/iR5900.cpp | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/pcsx2/x86/iR3000A.cpp b/pcsx2/x86/iR3000A.cpp
index 0a2daffc64..9319948894 100644
--- a/pcsx2/x86/iR3000A.cpp
+++ b/pcsx2/x86/iR3000A.cpp
@@ -1736,7 +1736,7 @@ StartRecomp:
 		}
 	}
 
-	pxAssert(xGetPtr() < recPtrEnd);
+	pxAssert(xGetPtr() < SysMemory::GetIOPRecEnd());
 
 	pxAssert(xGetPtr() - recPtr < _64kb);
 	s_pCurBlockEx->x86size = xGetPtr() - recPtr;
diff --git a/pcsx2/x86/ix86-32/iR5900.cpp b/pcsx2/x86/ix86-32/iR5900.cpp
index 674d7d0e85..c70988c885 100644
--- a/pcsx2/x86/ix86-32/iR5900.cpp
+++ b/pcsx2/x86/ix86-32/iR5900.cpp
@@ -900,7 +900,7 @@ u8* recEndThunk()
 {
 	u8* block_end = x86Ptr;
 
-	pxAssert(block_end < recPtrEnd);
+	pxAssert(block_end < SysMemory::GetEERecEnd());
 	recPtr = block_end;
 	return block_end;
 }
@@ -2698,7 +2698,7 @@ StartRecomp:
 		}
 	}
 
-	pxAssert(xGetPtr() < recPtrEnd);
+	pxAssert(xGetPtr() < SysMemory::GetEERecEnd());
 
 	s_pCurBlockEx->x86size = static_cast(xGetPtr() - recPtr);
 

From 29cd068dbd5c4f1d26fa1c58c00d1c60c64c65b5 Mon Sep 17 00:00:00 2001
From: refractionpcsx2 
Date: Thu, 27 Mar 2025 20:12:12 +0000
Subject: [PATCH 027/162] GS/HW: Fix typo on Tekken 5 CRC

---
 pcsx2/GS/Renderers/HW/GSHwHack.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pcsx2/GS/Renderers/HW/GSHwHack.cpp b/pcsx2/GS/Renderers/HW/GSHwHack.cpp
index cdcca56c5f..c79562681a 100644
--- a/pcsx2/GS/Renderers/HW/GSHwHack.cpp
+++ b/pcsx2/GS/Renderers/HW/GSHwHack.cpp
@@ -212,7 +212,7 @@ bool GSHwHack::GSC_Tekken5(GSRendererHW& r, int& skip)
 {
 	if (skip == 0)
 	{
-		if (r.IsPossibleChannelShuffle() && (RTBP0 & 31))
+		if (r.IsPossibleChannelShuffle() && !(RTBP0 & 31))
 		{
 			GSVertex* v = &r.m_vertex.buff[0];
 

From 1b4ced3e559d3d6d9d007babdc897d441d6a9d9f Mon Sep 17 00:00:00 2001
From: lightningterror <18107717+lightningterror@users.noreply.github.com>
Date: Thu, 27 Mar 2025 16:44:55 +0100
Subject: [PATCH 028/162] GS/HW: Don't use coverage when doing Af or Ad blend.

---
 pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 13 ++++---------
 1 file changed, 4 insertions(+), 9 deletions(-)

diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp
index 465f5f8320..1e832f6fc4 100644
--- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp
+++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp
@@ -4259,14 +4259,7 @@ void GSRendererHW::EmulateBlending(int rt_alpha_min, int rt_alpha_max, const boo
 				m_conf.ps.blend_d = 2;
 		}
 	}
-	// When AA1 is enabled and Alpha Blending is disabled, alpha blending done with coverage instead of alpha.
-	// We use a COV value of 128 (full coverage) in triangles (except the edge geometry, which we can't do easily).
-	if (IsCoverageAlpha())
-	{
-		m_conf.ps.fixed_one_a = 1;
-		m_conf.ps.blend_c = 0;
-	}
-	else if (m_conf.ps.blend_c == 1)
+	if (m_conf.ps.blend_c == 1)
 	{
 		// When both rt alpha min and max are equal replace Ad with Af, easier to manage.
 		if (rt_alpha_min == rt_alpha_max)
@@ -6169,6 +6162,9 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
 		new_scale_rt_alpha = rt->m_rt_alpha_scale;
 	}
 
+	// AA1: Set alpha source to coverage 128 when there is no alpha blending.
+	m_conf.ps.fixed_one_a = IsCoverageAlpha();
+
 	if ((!IsOpaque() || m_context->ALPHA.IsBlack()) && rt && ((m_conf.colormask.wrgba & 0x7) || (m_texture_shuffle && !m_copy_16bit_to_target_shuffle && !m_same_group_texture_shuffle)))
 	{
 		EmulateBlending(blend_alpha_min, blend_alpha_max, DATE, DATE_PRIMID, DATE_BARRIER, rt, can_scale_rt_alpha, new_scale_rt_alpha);
@@ -6177,7 +6173,6 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
 	{
 		m_conf.blend = {}; // No blending please
 		m_conf.ps.no_color1 = true;
-		m_conf.ps.fixed_one_a = IsCoverageAlpha();
 
 		if (can_scale_rt_alpha && !new_scale_rt_alpha && m_conf.colormask.wa)
 		{

From 8a1f2a151d16a776536c1a0f37a69542d0cd53ec Mon Sep 17 00:00:00 2001
From: chaoticgd <43898262+chaoticgd@users.noreply.github.com>
Date: Sat, 22 Mar 2025 00:40:02 +0000
Subject: [PATCH 029/162] Qt: Add GammaRay build scripts

---
 .../workflows/scripts/linux/build-gammaray.sh | 48 +++++++++++++
 .../scripts/windows/build-gammaray.bat        | 68 +++++++++++++++++++
 .gitignore                                    |  2 +
 3 files changed, 118 insertions(+)
 create mode 100755 .github/workflows/scripts/linux/build-gammaray.sh
 create mode 100644 .github/workflows/scripts/windows/build-gammaray.bat

diff --git a/.github/workflows/scripts/linux/build-gammaray.sh b/.github/workflows/scripts/linux/build-gammaray.sh
new file mode 100755
index 0000000000..7bbca4d23b
--- /dev/null
+++ b/.github/workflows/scripts/linux/build-gammaray.sh
@@ -0,0 +1,48 @@
+#!/usr/bin/env bash
+
+set -e
+
+if [ "$#" -ne 2 ]; then
+	echo "Syntax: $0  "
+	exit 1
+fi
+
+DEPSDIR=$(realpath "$1")
+INSTALLDIR=$(realpath "$2")
+
+if [ ! -d "$DEPSDIR/include/QtCore" ]; then
+	echo "Error: The build-dependencies-qt.sh script must be run on the deps directory first."
+	exit 1
+fi
+
+GAMMARAY=master
+
+mkdir -p gammaray-build
+cd gammaray-build
+
+echo "Downloading..."
+curl -L -o "GammaRay-$GAMMARAY.tar.gz" https://github.com/KDAB/GammaRay/archive/$GAMMARAY.tar.gz
+
+rm -fr "GammaRay-$GAMMARAY"
+
+echo "Extracting..."
+tar xf "GammaRay-$GAMMARAY.tar.gz"
+
+cd "GammaRay-$GAMMARAY"
+mkdir build
+cd build
+
+echo "Configuring..."
+cmake -DCMAKE_PREFIX_PATH="$DEPSDIR" -G Ninja -DCMAKE_INSTALL_PREFIX="$INSTALLDIR" -DGAMMARAY_BUILD_DOCS=false ..
+
+echo "Building..."
+cmake --build . --parallel
+
+echo "Installing..."
+cmake --build . --target install
+
+cd ../..
+
+echo "Cleaning up..."
+cd ..
+rm -r gammaray-build
diff --git a/.github/workflows/scripts/windows/build-gammaray.bat b/.github/workflows/scripts/windows/build-gammaray.bat
new file mode 100644
index 0000000000..b9ca312f29
--- /dev/null
+++ b/.github/workflows/scripts/windows/build-gammaray.bat
@@ -0,0 +1,68 @@
+@echo off
+setlocal enabledelayedexpansion
+
+echo Setting environment...
+if exist "%ProgramFiles%\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" (
+  call "%ProgramFiles%\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
+) else if exist "%ProgramFiles%\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat" (
+  call "%ProgramFiles%\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat"
+) else (
+  echo Visual Studio 2022 not found.
+  goto error
+)
+
+pushd %~dp0
+
+cd ..\..\..\..
+cd deps || goto error
+set "DEPSDIR=%CD%"
+cd ..
+mkdir gammaray
+cd gammaray || goto error
+set "INSTALLDIR=%CD%"
+cd ..
+mkdir gammaray-build
+cd gammaray-build || goto error
+set "BUILDDIR=%CD%"
+
+echo DEPSDIR=%DEPSDIR%
+echo BUILDDIR=%BUILDDIR%
+echo INSTALLDIR=%INSTALLDIR%
+
+set GAMMARAY="master"
+
+echo Downloading...
+curl -L -o "GammaRay-%GAMMARAY%.tar.gz" "https://github.com/KDAB/GammaRay/archive/%GAMMARAY%.tar.gz" || goto error
+
+rmdir /s /q "GammaRay-%GAMMARAY%"
+
+echo Extracting...
+tar -xf "GammaRay-%GAMMARAY%.tar.gz" || goto error
+
+echo Configuring...
+cmake "GammaRay-%GAMMARAY%" -B build -DCMAKE_PREFIX_PATH="%DEPSDIR%" -G Ninja -DCMAKE_INSTALL_PREFIX="%INSTALLDIR%" -DGAMMARAY_BUILD_DOCS=false || goto error
+
+echo Building...
+cmake --build build --parallel || goto error
+
+echo Installing...
+cmake --build build --target install || goto errorlevel
+
+echo Copying DLLs...
+xcopy /y "%DEPSDIR%\bin\*.dll" "%INSTALLDIR%\bin\"
+xcopy /y /e /s "%DEPSDIR%\plugins" "%INSTALLDIR%\bin\"
+
+echo Cleaning up...
+cd ..
+rd /s /q gammaray-build
+
+echo Exiting with success.
+popd
+pause
+exit 0
+
+:error
+echo Failed with error #%errorlevel%.
+popd
+pause
+exit %errorlevel%
diff --git a/.gitignore b/.gitignore
index b59e873da7..9305218258 100644
--- a/.gitignore
+++ b/.gitignore
@@ -109,6 +109,8 @@ oprofile_data/
 /deps-build
 /deps
 /deps-arm64
+/gammaray-build
+/gammaray
 /ipch
 
 !/3rdparty/libjpeg/change.log

From eb52da97029daef20a75606227a2276ff2d7ae36 Mon Sep 17 00:00:00 2001
From: PCSX2 Bot 
Date: Mon, 31 Mar 2025 16:01:29 +0000
Subject: [PATCH 030/162] [ci skip] PAD: Update to latest controller database.

---
 bin/resources/game_controller_db.txt | 20 +++++++++++---------
 1 file changed, 11 insertions(+), 9 deletions(-)

diff --git a/bin/resources/game_controller_db.txt b/bin/resources/game_controller_db.txt
index 6e0d952548..1fe52ec2fe 100644
--- a/bin/resources/game_controller_db.txt
+++ b/bin/resources/game_controller_db.txt
@@ -1434,7 +1434,8 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
 050000005e040000050b000003090000,Microsoft Xbox One Elite 2,a:b0,b:b1,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a6,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
 050000005e0400008e02000030110000,Microsoft Xbox One Elite 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b13,paddle3:b12,paddle4:b14,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
 030000005e040000120b00000b050000,Microsoft Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
-060000005e040000120b000001050000,Microsoft Xbox Series X Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+060000005e040000120b000001050000,Microsoft Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+030000005e040000120b000016050000,Microsoft Xbox Series Controller,a:b0,b:b1,x:b2,y:b3,back:b6,start:b7,guide:b8,leftshoulder:b4,rightshoulder:b5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,platform:Linux,
 03000000030000000300000002000000,Miroof,a:b1,b:b0,back:b6,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Linux,
 03000000790000001c18000010010000,Mobapad Chitu HD,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
 050000004d4f435554452d3035335800,Mocute 053X,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
@@ -1454,6 +1455,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
 03000000853200000706000012010000,Nacon GC-100,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
 0300000085320000170d000011010000,Nacon Revolution 5 Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,
 0300000085320000190d000011010000,Nacon Revolution 5 Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,
+05000000853200000503000000010000,Nacon MG-X Pro,a:b0,b:b1,x:b3,y:b4,back:b10,guide:b12,start:b11,leftstick:b13,rightstick:b14,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a5,righttrigger:a4,platform:Linux,
 030000000d0f00000900000010010000,Natec Genesis P44,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
 030000004f1f00000800000011010000,NeoGeo PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,
 0300000092120000474e000000010000,NeoGeo X Arcade Stick,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b9,x:b3,y:b2,platform:Linux,
@@ -1496,7 +1498,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
 030000006f0e0000b802000001010000,PDP Afterglow Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
 030000006f0e0000b802000013020000,PDP Afterglow Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
 030000006f0e00006401000001010000,PDP Battlefield One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
-030000006f0e0000d702000006640000,PDP Black Camo Wired Xbox Series X Controller,a:b0,b:b1,back:b6,dpdown:b13,dpleft:b14,dpright:b13,dpup:b14,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+030000006f0e0000d702000006640000,PDP Black Camo Wired Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:b13,dpleft:b14,dpright:b13,dpup:b14,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
 030000006f0e00003101000000010000,PDP EA Sports Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
 030000006f0e00008501000011010000,PDP Fightpad Pro Gamecube Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
 030000006f0e0000c802000012010000,PDP Kingdom Hearts Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
@@ -1511,7 +1513,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
 030000006f0e0000ef02000007640000,PDP Xbox Series Kinetic Wired Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
 03000000c62400000053000000010000,PowerA,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
 03000000c62400003a54000001010000,PowerA 1428124-01,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
-03000000d62000000540000001010000,PowerA Advantage Xbox Series X Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+03000000d62000000540000001010000,PowerA Advantage Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
 03000000d620000011a7000011010000,PowerA Core Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
 03000000dd62000015a7000011010000,PowerA Fusion Nintendo Switch Arcade Stick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
 03000000d620000012a7000011010000,PowerA Fusion Nintendo Switch Fight Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
@@ -1527,8 +1529,8 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
 03000000c62400001a54000001010000,PowerA Xbox One Mini Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
 03000000d62000000240000001010000,PowerA Xbox One Spectra Infinity,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
 03000000d62000000f20000001010000,PowerA Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b7,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
-03000000d62000000520000050010000,PowerA Xbox Series X Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
-03000000d62000000b20000001010000,PowerA Xbox Series X Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+03000000d62000000520000050010000,PowerA Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+03000000d62000000b20000001010000,PowerA Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
 030000006d040000d2ca000011010000,Precision Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
 03000000250900000017000010010000,PS/SS/N64 Adapter,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b5,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2~,righty:a3,start:b8,platform:Linux,
 03000000ff1100004133000010010000,PS2 Controller,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Linux,
@@ -1775,10 +1777,10 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
 060000005e040000120b000007050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
 060000005e040000120b00000b050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
 060000005e040000120b00000f050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
-030000005e040000120b000011050000,Xbox Series X Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
-030000005e040000120b000014050000,Xbox Series X Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
-050000005e040000130b000017050000,Xbox Series X Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
-060000005e040000120b00000d050000,Xbox Series X Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+030000005e040000120b000011050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+030000005e040000120b000014050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+050000005e040000130b000017050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
+060000005e040000120b00000d050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
 050000005e040000200b000013050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
 050000005e040000200b000017050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
 050000005e040000220b000017050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,

From cdf7bef1505070911d67871a0d039b5392fe19e0 Mon Sep 17 00:00:00 2001
From: JordanTheToaster 
Date: Tue, 1 Apr 2025 01:06:25 +0100
Subject: [PATCH 031/162] Deps: Update SDL to 3.2.10

---
 .github/workflows/scripts/linux/build-dependencies-qt.sh      | 4 ++--
 .github/workflows/scripts/linux/flatpak/modules/20-sdl3.json  | 4 ++--
 .../workflows/scripts/macos/build-dependencies-universal.sh   | 4 ++--
 .github/workflows/scripts/macos/build-dependencies.sh         | 4 ++--
 .../workflows/scripts/windows/build-dependencies-arm64.bat    | 4 ++--
 .github/workflows/scripts/windows/build-dependencies.bat      | 4 ++--
 6 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/.github/workflows/scripts/linux/build-dependencies-qt.sh b/.github/workflows/scripts/linux/build-dependencies-qt.sh
index aabaa694e7..39b6ca88fd 100755
--- a/.github/workflows/scripts/linux/build-dependencies-qt.sh
+++ b/.github/workflows/scripts/linux/build-dependencies-qt.sh
@@ -19,7 +19,7 @@ LIBJPEGTURBO=3.1.0
 LIBPNG=1.6.45
 LIBWEBP=1.5.0
 LZ4=b8fd2d15309dd4e605070bd4486e26b6ef814e29
-SDL=SDL3-3.2.8
+SDL=SDL3-3.2.10
 QT=6.8.2
 ZSTD=1.5.7
 KDDOCKWIDGETS=2.2.1
@@ -38,7 +38,7 @@ fd6f417fe9e3a071cf1424a5152d926a34c4a3c5070745470be6cf12a404ed79  $LIBBACKTRACE.
 926485350139ffb51ef69760db35f78846c805fef3d59bfdcb2fba704663f370  libpng-$LIBPNG.tar.xz
 7d6fab70cf844bf6769077bd5d7a74893f8ffd4dfb42861745750c63c2a5c92c  libwebp-$LIBWEBP.tar.gz
 0728800155f3ed0a0c87e03addbd30ecbe374f7b080678bbca1506051d50dec3  $LZ4.tar.gz
-13388fabb361de768ecdf2b65e52bb27d1054cae6ccb6942ba926e378e00db03  $SDL.tar.gz
+f87be7b4dec66db4098e9c167b2aa34e2ca10aeb5443bdde95ae03185ed513e0  $SDL.tar.gz
 eb33e51f49a15e023950cd7825ca74a4a2b43db8354825ac24fc1b7ee09e6fa3  zstd-$ZSTD.tar.gz
 012043ce6d411e6e8a91fdc4e05e6bedcfa10fcb1347d3c33908f7fdd10dfe05  qtbase-everywhere-src-$QT.tar.xz
 d2a1bbb84707b8a0aec29227b170be00f04383fbf2361943596d09e7e443c8e1  qtimageformats-everywhere-src-$QT.tar.xz
diff --git a/.github/workflows/scripts/linux/flatpak/modules/20-sdl3.json b/.github/workflows/scripts/linux/flatpak/modules/20-sdl3.json
index 82223c39b8..626811736c 100644
--- a/.github/workflows/scripts/linux/flatpak/modules/20-sdl3.json
+++ b/.github/workflows/scripts/linux/flatpak/modules/20-sdl3.json
@@ -14,8 +14,8 @@
   "sources": [
     {
       "type": "archive",
-      "url": "https://libsdl.org/release/SDL3-3.2.8.tar.gz",
-      "sha256": "13388fabb361de768ecdf2b65e52bb27d1054cae6ccb6942ba926e378e00db03"
+      "url": "https://libsdl.org/release/SDL3-3.2.10.tar.gz",
+      "sha256": "f87be7b4dec66db4098e9c167b2aa34e2ca10aeb5443bdde95ae03185ed513e0"
     }
   ],
   "cleanup": [
diff --git a/.github/workflows/scripts/macos/build-dependencies-universal.sh b/.github/workflows/scripts/macos/build-dependencies-universal.sh
index 66adff76b7..eb402f45ca 100755
--- a/.github/workflows/scripts/macos/build-dependencies-universal.sh
+++ b/.github/workflows/scripts/macos/build-dependencies-universal.sh
@@ -40,7 +40,7 @@ fi
 
 FREETYPE=2.13.3
 HARFBUZZ=10.0.1
-SDL=SDL3-3.2.8
+SDL=SDL3-3.2.10
 ZSTD=1.5.7
 LZ4=b8fd2d15309dd4e605070bd4486e26b6ef814e29
 LIBPNG=1.6.45
@@ -77,7 +77,7 @@ CMAKE_ARCH_UNIVERSAL=-DCMAKE_OSX_ARCHITECTURES="x86_64;arm64"
 cat > SHASUMS < SHASUMS <
Date: Wed, 26 Mar 2025 01:19:19 +0000
Subject: [PATCH 032/162] Qt: Fix custom scan range settings and make sure to
 save symbol sources

---
 .../Settings/DebugAnalysisSettingsWidget.cpp  | 60 ++++++++++++-------
 .../Settings/DebugAnalysisSettingsWidget.h    |  2 +-
 2 files changed, 41 insertions(+), 21 deletions(-)

diff --git a/pcsx2-qt/Settings/DebugAnalysisSettingsWidget.cpp b/pcsx2-qt/Settings/DebugAnalysisSettingsWidget.cpp
index 7be804cf50..5d430047ab 100644
--- a/pcsx2-qt/Settings/DebugAnalysisSettingsWidget.cpp
+++ b/pcsx2-qt/Settings/DebugAnalysisSettingsWidget.cpp
@@ -19,10 +19,14 @@ DebugAnalysisSettingsWidget::DebugAnalysisSettingsWidget(QWidget* parent)
 
 	setupSymbolSourceGrid();
 
-	m_ui.importFromElf->setChecked(Host::GetBoolSettingValue("Debugger/Analysis", "ImportSymbolsFromELF", true));
-	m_ui.importSymFileFromDefaultLocation->setChecked(Host::GetBoolSettingValue("Debugger/Analysis", "ImportSymFileFromDefaultLocation", true));
-	m_ui.demangleSymbols->setChecked(Host::GetBoolSettingValue("Debugger/Analysis", "DemangleSymbols", true));
-	m_ui.demangleParameters->setChecked(Host::GetBoolSettingValue("Debugger/Analysis", "DemangleParameters", true));
+	m_ui.importFromElf->setChecked(
+		Host::GetBoolSettingValue("Debugger/Analysis", "ImportSymbolsFromELF", true));
+	m_ui.importSymFileFromDefaultLocation->setChecked(
+		Host::GetBoolSettingValue("Debugger/Analysis", "ImportSymFileFromDefaultLocation", true));
+	m_ui.demangleSymbols->setChecked(
+		Host::GetBoolSettingValue("Debugger/Analysis", "DemangleSymbols", true));
+	m_ui.demangleParameters->setChecked(
+		Host::GetBoolSettingValue("Debugger/Analysis", "DemangleParameters", true));
 
 	setupSymbolFileList();
 
@@ -36,15 +40,22 @@ DebugAnalysisSettingsWidget::DebugAnalysisSettingsWidget(QWidget* parent)
 			m_ui.functionScanMode->setCurrentIndex(i);
 	}
 
-	m_ui.customAddressRange->setChecked(Host::GetBoolSettingValue("Debugger/Analysis", "CustomFunctionScanRange", false));
-	m_ui.addressRangeStart->setText(QString::fromStdString(Host::GetStringSettingValue("Debugger/Analysis", "FunctionScanStartAddress", "")));
-	m_ui.addressRangeEnd->setText(QString::fromStdString(Host::GetStringSettingValue("Debugger/Analysis", "FunctionScanEndAddress", "")));
+	m_ui.customAddressRange->setChecked(
+		Host::GetBoolSettingValue("Debugger/Analysis", "CustomFunctionScanRange", false));
+	m_ui.addressRangeStart->setText(QString::fromStdString(
+		Host::GetStringSettingValue("Debugger/Analysis", "FunctionScanStartAddress", "")));
+	m_ui.addressRangeEnd->setText(QString::fromStdString(
+		Host::GetStringSettingValue("Debugger/Analysis", "FunctionScanEndAddress", "")));
 
-	m_ui.grayOutOverwrittenFunctions->setChecked(Host::GetBoolSettingValue("Debugger/Analysis", "GenerateFunctionHashes", true));
+	m_ui.grayOutOverwrittenFunctions->setChecked(
+		Host::GetBoolSettingValue("Debugger/Analysis", "GenerateFunctionHashes", true));
 
-	connect(m_ui.automaticallyClearSymbols, &QCheckBox::checkStateChanged, this, &DebugAnalysisSettingsWidget::updateEnabledStates);
-	connect(m_ui.demangleSymbols, &QCheckBox::checkStateChanged, this, &DebugAnalysisSettingsWidget::updateEnabledStates);
-	connect(m_ui.customAddressRange, &QCheckBox::checkStateChanged, this, &DebugAnalysisSettingsWidget::updateEnabledStates);
+	connect(m_ui.automaticallyClearSymbols, &QCheckBox::checkStateChanged,
+		this, &DebugAnalysisSettingsWidget::updateEnabledStates);
+	connect(m_ui.demangleSymbols, &QCheckBox::checkStateChanged,
+		this, &DebugAnalysisSettingsWidget::updateEnabledStates);
+	connect(m_ui.customAddressRange, &QCheckBox::checkStateChanged,
+		this, &DebugAnalysisSettingsWidget::updateEnabledStates);
 
 	updateEnabledStates();
 }
@@ -122,8 +133,16 @@ DebugAnalysisSettingsWidget::DebugAnalysisSettingsWidget(SettingsWindow* dialog,
 	{
 		SettingWidgetBinder::BindWidgetToBoolSetting(
 			sif, m_ui.customAddressRange, "Debugger/Analysis", "CustomFunctionScanRange", false);
-		connect(m_ui.addressRangeStart, &QLineEdit::textChanged, this, &DebugAnalysisSettingsWidget::functionScanRangeChanged);
-		connect(m_ui.addressRangeEnd, &QLineEdit::textChanged, this, &DebugAnalysisSettingsWidget::functionScanRangeChanged);
+
+		m_ui.addressRangeStart->setText(QString::fromStdString(
+			getStringSettingValue("Debugger/Analysis", "FunctionScanStartAddress", "")));
+		m_ui.addressRangeEnd->setText(QString::fromStdString(
+			getStringSettingValue("Debugger/Analysis", "FunctionScanEndAddress", "")));
+
+		connect(m_ui.addressRangeStart, &QLineEdit::textChanged,
+			this, &DebugAnalysisSettingsWidget::saveFunctionScanRange);
+		connect(m_ui.addressRangeEnd, &QLineEdit::textChanged,
+			this, &DebugAnalysisSettingsWidget::saveFunctionScanRange);
 
 		m_dialog->registerWidgetHelp(m_ui.customAddressRange, tr("Custom Address Range"), tr("Unchecked"),
 			tr("Whether to look for functions from the address range specified (Checked), or from the ELF segment "
@@ -302,6 +321,9 @@ void DebugAnalysisSettingsWidget::saveSymbolSources()
 
 		i++;
 	}
+
+	QtHost::SaveGameSettings(sif, true);
+	g_emu_thread->reloadGameSettings();
 }
 
 void DebugAnalysisSettingsWidget::setupSymbolFileList()
@@ -445,7 +467,7 @@ void DebugAnalysisSettingsWidget::saveSymbolFiles()
 	g_emu_thread->reloadGameSettings();
 }
 
-void DebugAnalysisSettingsWidget::functionScanRangeChanged()
+void DebugAnalysisSettingsWidget::saveFunctionScanRange()
 {
 	if (!m_dialog)
 		return;
@@ -457,13 +479,11 @@ void DebugAnalysisSettingsWidget::functionScanRangeChanged()
 	QString start_address = m_ui.addressRangeStart->text();
 	QString end_address = m_ui.addressRangeEnd->text();
 
-	bool ok;
+	sif->SetStringValue("Debugger/Analysis", "FunctionScanStartAddress", start_address.toStdString().c_str());
+	sif->SetStringValue("Debugger/Analysis", "FunctionScanEndAddress", end_address.toStdString().c_str());
 
-	if (start_address.toUInt(&ok, 16), ok)
-		sif->SetStringValue("Debugger/Analysis", "FunctionScanStartAddress", start_address.toStdString().c_str());
-
-	if (end_address.toUInt(&ok, 16), ok)
-		sif->SetStringValue("Debugger/Analysis", "FunctionScanEndAddress", end_address.toStdString().c_str());
+	QtHost::SaveGameSettings(sif, true);
+	g_emu_thread->reloadGameSettings();
 }
 
 void DebugAnalysisSettingsWidget::updateEnabledStates()
diff --git a/pcsx2-qt/Settings/DebugAnalysisSettingsWidget.h b/pcsx2-qt/Settings/DebugAnalysisSettingsWidget.h
index cce32395b3..018430d517 100644
--- a/pcsx2-qt/Settings/DebugAnalysisSettingsWidget.h
+++ b/pcsx2-qt/Settings/DebugAnalysisSettingsWidget.h
@@ -38,7 +38,7 @@ protected:
 	void removeSymbolFile();
 	void saveSymbolFiles();
 
-	void functionScanRangeChanged();
+	void saveFunctionScanRange();
 
 	void updateEnabledStates();
 

From 9222c21b4a9abadc1257ffbc9118ceaf03185510 Mon Sep 17 00:00:00 2001
From: JordanTheToaster 
Date: Wed, 2 Apr 2025 19:44:49 +0100
Subject: [PATCH 033/162] Deps: Move Mac Qt download link to archive

---
 .github/workflows/scripts/macos/build-dependencies.sh | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/.github/workflows/scripts/macos/build-dependencies.sh b/.github/workflows/scripts/macos/build-dependencies.sh
index 86e8dcf920..10bed8c4d8 100755
--- a/.github/workflows/scripts/macos/build-dependencies.sh
+++ b/.github/workflows/scripts/macos/build-dependencies.sh
@@ -88,11 +88,11 @@ curl -L \
 	-O "https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-$LIBWEBP.tar.gz" \
 	-O "https://ffmpeg.org/releases/ffmpeg-$FFMPEG.tar.xz" \
 	-O "https://github.com/KhronosGroup/MoltenVK/archive/refs/tags/v$MOLTENVK.tar.gz" \
-	-O "https://download.qt.io/official_releases/qt/${QT%.*}/$QT/submodules/qtbase-everywhere-src-$QT.tar.xz" \
-	-O "https://download.qt.io/official_releases/qt/${QT%.*}/$QT/submodules/qtimageformats-everywhere-src-$QT.tar.xz" \
-	-O "https://download.qt.io/official_releases/qt/${QT%.*}/$QT/submodules/qtsvg-everywhere-src-$QT.tar.xz" \
-	-O "https://download.qt.io/official_releases/qt/${QT%.*}/$QT/submodules/qttools-everywhere-src-$QT.tar.xz" \
-	-O "https://download.qt.io/official_releases/qt/${QT%.*}/$QT/submodules/qttranslations-everywhere-src-$QT.tar.xz" \
+	-O "https://download.qt.io/archive/qt/${QT%.*}/$QT/submodules/qtbase-everywhere-src-$QT.tar.xz" \
+	-O "https://download.qt.io/archive/qt/${QT%.*}/$QT/submodules/qtimageformats-everywhere-src-$QT.tar.xz" \
+	-O "https://download.qt.io/archive/qt/${QT%.*}/$QT/submodules/qtsvg-everywhere-src-$QT.tar.xz" \
+	-O "https://download.qt.io/archive/qt/${QT%.*}/$QT/submodules/qttools-everywhere-src-$QT.tar.xz" \
+	-O "https://download.qt.io/archive/qt/${QT%.*}/$QT/submodules/qttranslations-everywhere-src-$QT.tar.xz" \
 	-o "shaderc-$SHADERC.tar.gz" "https://github.com/google/shaderc/archive/refs/tags/v$SHADERC.tar.gz" \
 	-o "shaderc-glslang-$SHADERC_GLSLANG.tar.gz" "https://github.com/KhronosGroup/glslang/archive/$SHADERC_GLSLANG.tar.gz" \
 	-o "shaderc-spirv-headers-$SHADERC_SPIRVHEADERS.tar.gz" "https://github.com/KhronosGroup/SPIRV-Headers/archive/$SHADERC_SPIRVHEADERS.tar.gz" \

From e68ae91b59bf8a1af0b4841ec1604de5470025d8 Mon Sep 17 00:00:00 2001
From: TellowKrinkle 
Date: Fri, 28 Mar 2025 21:12:47 -0500
Subject: [PATCH 034/162] MacOS: Mark our help menu as the macOS help menu

This gives it a nice search box for searching other menus
---
 common/CocoaTools.h     | 2 ++
 common/CocoaTools.mm    | 5 +++++
 pcsx2-qt/MainWindow.cpp | 4 ++++
 3 files changed, 11 insertions(+)

diff --git a/common/CocoaTools.h b/common/CocoaTools.h
index 1166effc4c..8da50f8a2a 100644
--- a/common/CocoaTools.h
+++ b/common/CocoaTools.h
@@ -19,6 +19,8 @@ namespace CocoaTools
 	void AddThemeChangeHandler(void* ctx, void(handler)(void* ctx));
 	/// Remove a handler previously added using AddThemeChangeHandler with the given context
 	void RemoveThemeChangeHandler(void* ctx);
+	/// Mark an NSMenu as the help menu
+	void MarkHelpMenu(void* menu);
 	/// Returns the bundle path.
 	std::optional GetBundlePath();
 	/// Get the bundle path to the actual application without any translocation fun
diff --git a/common/CocoaTools.mm b/common/CocoaTools.mm
index 71b4c91184..adbe803b80 100644
--- a/common/CocoaTools.mm
+++ b/common/CocoaTools.mm
@@ -143,6 +143,11 @@ void CocoaTools::RemoveThemeChangeHandler(void* ctx)
 	[s_themeChangeHandler removeCallback:ctx];
 }
 
+void CocoaTools::MarkHelpMenu(void* menu)
+{
+	[NSApp setHelpMenu:(__bridge NSMenu*)menu];
+}
+
 // MARK: - Sound playback
 
 bool Common::PlaySoundAsync(const char* path)
diff --git a/pcsx2-qt/MainWindow.cpp b/pcsx2-qt/MainWindow.cpp
index 80c05d515e..e44a0749b7 100644
--- a/pcsx2-qt/MainWindow.cpp
+++ b/pcsx2-qt/MainWindow.cpp
@@ -151,6 +151,10 @@ void MainWindow::initialize()
 			ctx->updateTheme(); // Qt won't notice the style change without us touching the palette in some way
 		});
 	});
+	// The cocoa backing isn't initialized yet, delay this until stuff is set up with a `RunOnUIThread` call
+	QtHost::RunOnUIThread([this]{
+		CocoaTools::MarkHelpMenu(m_ui.menuHelp->toNSMenu());
+	});
 #endif
 	m_ui.setupUi(this);
 	setupAdditionalUi();

From 76f8ffeb90020203481a319d463f288ccbc5dc4f Mon Sep 17 00:00:00 2001
From: PCSX2 Bot 
Date: Thu, 3 Apr 2025 00:02:02 +0000
Subject: [PATCH 035/162] [ci skip] Qt: Update Base Translation.

---
 pcsx2-qt/Translations/pcsx2-qt_en.ts | 280 +++++++++++++--------------
 1 file changed, 140 insertions(+), 140 deletions(-)

diff --git a/pcsx2-qt/Translations/pcsx2-qt_en.ts b/pcsx2-qt/Translations/pcsx2-qt_en.ts
index 8147682d7a..6b6df44dcd 100644
--- a/pcsx2-qt/Translations/pcsx2-qt_en.ts
+++ b/pcsx2-qt/Translations/pcsx2-qt_en.ts
@@ -3872,7 +3872,7 @@ Do you want to overwrite?
     
     
         
-        
+        
         Automatically Select Symbols To Clear
         
     
@@ -3888,25 +3888,25 @@ Do you want to overwrite?
     
     
         
-        
+        
         Import From ELF
         
     
     
         
-        
+        
         Demangle Symbols
         
     
     
         
-        
+        
         Demangle Parameters
         
     
     
         
-        
+        
         Import Default .sym File
         
     
@@ -3937,7 +3937,7 @@ Do you want to overwrite?
     
     
         
-        
+        
         Scan ELF
         
     
@@ -3973,102 +3973,102 @@ Do you want to overwrite?
     
     
         
-        
+        
         Gray Out Symbols For Overwritten Functions
         
     
     
-        
-        
-        
-        
-        
-        
+        
+        
+        
+        
+        
+        
         Checked
         
     
     
-        
+        
         Automatically delete symbols that were generated by any previous analysis runs.
         
     
     
-        
+        
         Import symbol tables stored in the game's boot ELF.
         
     
     
-        
+        
         Import symbols from a .sym file with the same name as the loaded ISO file on disk if such a file exists.
         
     
     
-        
+        
         Demangle C++ symbols during the import process so that the function and global variable names shown in the debugger are more readable.
         
     
     
-        
+        
         Include parameter lists in demangled function names.
         
     
     
-        
+        
         Scan Mode
         
     
     
-        
+        
         Choose where the function scanner looks to find functions. This option can be useful if the application loads additional code at runtime.
         
     
     
-        
+        
         Custom Address Range
         
     
     
-        
+        
         Unchecked
         
     
     
-        
+        
         Whether to look for functions from the address range specified (Checked), or from the ELF segment containing the entry point (Unchecked).
         
     
     
-        
+        
         Generate hashes for all the detected functions, and gray out the symbols displayed in the debugger for functions that no longer match.
         
     
     
-        
+        
         <i>No symbol sources in database.</i>
         
     
     
-        
+        
         <i>Start this game to modify the symbol sources list.</i>
         
     
     
-        
+        
         Path
         
     
     
-        
+        
         Base Address
         
     
     
-        
+        
         Condition
         
     
     
-        
+        
         Add Symbol File
         
     
@@ -16027,13 +16027,13 @@ Right click to clear binding
         
     
     
-        
-        
+        
+        
         Change Disc
         
     
     
-        
+        
         Load State
         
     
@@ -16596,50 +16596,50 @@ Right click to clear binding
         
     
     
-        
+        
         Start Big Picture Mode
         
     
     
         
-        
+        
         Big Picture
         In Toolbar
         
     
     
-        
+        
         Show Advanced Settings
         
     
     
-        
-        
+        
+        
         Video Capture
         
     
     
-        
+        
         Internal Resolution
         
     
     
-        
+        
         %1x Scale
         
     
     
-        
+        
         Select location to save block dump:
         
     
     
-        
+        
         Do not show again
         
     
     
-        
+        
         Changing advanced settings can have unpredictable effects on games, including graphical glitches, lock-ups, and even corrupted save files. We do not recommend changing advanced settings unless you know what you are doing, and the implications of changing each setting.
 
 The PCSX2 team will not provide any support for configurations that modify these settings, you are on your own.
@@ -16648,321 +16648,321 @@ Are you sure you want to continue?
         
     
     
-        
-        
+        
+        
         Record On Boot
         
     
     
-        
+        
         Did you want to start recording on boot?
         
     
     
-        
-        
+        
+        
         %1 Files (*.%2)
         
     
     
-        
+        
         Did you want to cancel recording on boot?
         
     
     
-        
+        
         Recording will start in a moment
         
     
     
-        
+        
         WARNING: Memory Card Busy
         
     
     
-        
+        
         Confirm Shutdown
         
     
     
-        
+        
         Are you sure you want to shut down the virtual machine?
         
     
     
-        
+        
         Save State For Resume
         
     
     
-        
-        
-        
-        
-        
-        
+        
+        
+        
+        
+        
+        
         Error
         
     
     
-        
+        
         You must select a disc to change discs.
         
     
     
-        
+        
         Properties...
         
     
     
-        
+        
         Set Cover Image...
         
     
     
-        
+        
         Exclude From List
         
     
     
-        
+        
         Reset Play Time
         
     
     
-        
+        
         Check Wiki Page
         
     
     
-        
+        
         Default Boot
         
     
     
-        
+        
         Fast Boot
         
     
     
-        
+        
         Full Boot
         
     
     
-        
+        
         Boot and Debug
         
     
     
-        
+        
         Add Search Directory...
         
     
     
-        
+        
         Start File
         
     
     
-        
+        
         Start Disc
         
     
     
-        
+        
         Select Disc Image
         
     
     
-        
+        
         Updater Error
         
     
     
-        
+        
         <p>Sorry, you are trying to update a PCSX2 version which is not an official GitHub release. To prevent incompatibilities, the auto-updater is only enabled on official builds.</p><p>To obtain an official build, please download from the link below:</p><p><a href="https://pcsx2.net/downloads/">https://pcsx2.net/downloads/</a></p>
         
     
     
-        
+        
         Automatic updating is not supported on the current platform.
         
     
     
-        
+        
         Confirm File Creation
         
     
     
-        
+        
         The pnach file '%1' does not currently exist. Do you want to create it?
         
     
     
-        
+        
         Failed to create '%1'.
         
     
     
-        
+        
         Input Recording Failed
         
     
     
-        
+        
         Failed to create file: {}
         
     
     
-        
+        
         Input Recording Files (*.p2m2)
         
     
     
-        
+        
         Input Playback Failed
         
     
     
-        
+        
         Failed to open file: {}
         
     
     
-        
+        
         Paused
         
     
     
-        
+        
         Load State Failed
         
     
     
-        
+        
         Cannot load a save state without a running VM.
         
     
     
-        
+        
         The new ELF cannot be loaded without resetting the virtual machine. Do you want to reset the virtual machine now?
         
     
     
-        
+        
         Cannot change from game to GS dump without shutting down first.
         
     
     
-        
+        
         Failed to get window info from widget
         
     
     
-        
+        
         Stop Big Picture Mode
         
     
     
-        
+        
         Exit Big Picture
         In Toolbar
         
     
     
-        
+        
         Game Properties
         
     
     
-        
+        
         Game properties is unavailable for the current game.
         
     
     
-        
+        
         Could not find any CD/DVD-ROM devices. Please ensure you have a drive connected and sufficient permissions to access it.
         
     
     
-        
+        
         Select disc drive:
         
     
     
-        
+        
         This save state does not exist.
         
     
     
-        
+        
         Select Cover Image
         
     
     
-        
+        
         Cover Already Exists
         
     
     
-        
+        
         A cover image for this game already exists, do you wish to replace it?
         
     
     
-        
-        
-        
-        
+        
+        
+        
+        
         Copy Error
         
     
     
-        
+        
         Failed to remove existing cover '%1'
         
     
     
-        
+        
         Failed to copy '%1' to '%2'
         
     
     
-        
+        
         Failed to remove '%1'
         
     
     
-        
-        
+        
+        
         Confirm Reset
         
     
     
-        
+        
         All Cover Image Types (*.jpg *.jpeg *.png *.webp)
         
     
     
-        
+        
         You must select a different file to the current cover image.
         
     
     
-        
+        
         Are you sure you want to reset the play time for '%1'?
 
 This action cannot be undone.
         
     
     
-        
+        
         Load Resume State
         
     
     
-        
+        
         A resume save state was found for this game, saved at:
 
 %1.
@@ -16971,43 +16971,43 @@ Do you want to load this state, or start from a fresh boot?
         
     
     
-        
+        
         Fresh Boot
         
     
     
-        
+        
         Delete And Boot
         
     
     
-        
+        
         Failed to delete save state file '%1'.
         
     
     
-        
+        
         Load State File...
         
     
     
-        
+        
         Load From File...
         
     
     
-        
-        
+        
+        
         Select Save State File
         
     
     
-        
+        
         Save States (*.p2s)
         
     
     
-        
+        
         Delete Save States...
         
     
@@ -17022,80 +17022,80 @@ Do you want to load this state, or start from a fresh boot?
         
     
     
-        
+        
         WARNING: Your memory card is still writing data. Shutting down now <b>WILL IRREVERSIBLY DESTROY YOUR MEMORY CARD.</b> It is strongly recommended to resume your game and let it finish writing to your memory card.<br><br>Do you wish to shutdown anyways and <b>IRREVERSIBLY DESTROY YOUR MEMORY CARD?</b>
         
     
     
-        
+        
         Save States (*.p2s *.p2s.backup)
         
     
     
-        
+        
         Undo Load State
         
     
     
-        
+        
         Resume (%2)
         
     
     
-        
+        
         Load Slot %1 (%2)
         
     
     
-        
-        
+        
+        
         Delete Save States
         
     
     
-        
+        
         Are you sure you want to delete all save states for %1?
 
 The saves will not be recoverable.
         
     
     
-        
+        
         %1 save states deleted.
         
     
     
-        
+        
         Save To File...
         
     
     
-        
+        
         Empty
         
     
     
-        
+        
         Save Slot %1 (%2)
         
     
     
-        
+        
         Confirm Disc Change
         
     
     
-        
+        
         Do you want to swap discs or boot the new image (via system reset)?
         
     
     
-        
+        
         Swap Disc
         
     
     
-        
+        
         Reset
         
     

From 47657b51abcaf4fe6f9fb070d17c8499a297d3e8 Mon Sep 17 00:00:00 2001
From: chaoticgd <43898262+chaoticgd@users.noreply.github.com>
Date: Fri, 21 Mar 2025 20:37:31 +0000
Subject: [PATCH 036/162] Debugger: Extract custom menu bar as its own class

---
 pcsx2-qt/CMakeLists.txt                   |   2 +
 pcsx2-qt/Debugger/DebuggerWindow.cpp      |   2 +-
 pcsx2-qt/Debugger/Docking/DockManager.cpp | 456 ++++++++--------------
 pcsx2-qt/Debugger/Docking/DockManager.h   |  29 +-
 pcsx2-qt/Debugger/Docking/DockMenuBar.cpp | 184 +++++++++
 pcsx2-qt/Debugger/Docking/DockMenuBar.h   |  56 +++
 pcsx2-qt/pcsx2-qt.vcxproj                 |   3 +
 pcsx2-qt/pcsx2-qt.vcxproj.filters         |   9 +
 8 files changed, 436 insertions(+), 305 deletions(-)
 create mode 100644 pcsx2-qt/Debugger/Docking/DockMenuBar.cpp
 create mode 100644 pcsx2-qt/Debugger/Docking/DockMenuBar.h

diff --git a/pcsx2-qt/CMakeLists.txt b/pcsx2-qt/CMakeLists.txt
index 322f23ee62..0f0ee4b9a3 100644
--- a/pcsx2-qt/CMakeLists.txt
+++ b/pcsx2-qt/CMakeLists.txt
@@ -196,6 +196,8 @@ target_sources(pcsx2-qt PRIVATE
 	Debugger/Docking/DockLayout.h
 	Debugger/Docking/DockManager.cpp
 	Debugger/Docking/DockManager.h
+	Debugger/Docking/DockMenuBar.cpp
+	Debugger/Docking/DockMenuBar.h
 	Debugger/Docking/DockTables.cpp
 	Debugger/Docking/DockTables.h
 	Debugger/Docking/DockUtils.cpp
diff --git a/pcsx2-qt/Debugger/DebuggerWindow.cpp b/pcsx2-qt/Debugger/DebuggerWindow.cpp
index 9f4f55c683..a92c800412 100644
--- a/pcsx2-qt/Debugger/DebuggerWindow.cpp
+++ b/pcsx2-qt/Debugger/DebuggerWindow.cpp
@@ -112,7 +112,7 @@ DebuggerWindow::DebuggerWindow(QWidget* parent)
 
 	QMenuBar* menu_bar = menuBar();
 
-	setMenuWidget(m_dock_manager->createLayoutSwitcher(menu_bar));
+	setMenuWidget(m_dock_manager->createMenuBar(menu_bar));
 
 	Host::RunOnCPUThread([]() {
 		R5900SymbolImporter.OnDebuggerOpened();
diff --git a/pcsx2-qt/Debugger/Docking/DockManager.cpp b/pcsx2-qt/Debugger/Docking/DockManager.cpp
index e76b031d47..abc0b0386d 100644
--- a/pcsx2-qt/Debugger/Docking/DockManager.cpp
+++ b/pcsx2-qt/Debugger/Docking/DockManager.cpp
@@ -25,7 +25,6 @@
 #include 
 #include 
 #include 
-#include 
 
 DockManager::DockManager(QObject* parent)
 	: QObject(parent)
@@ -33,9 +32,6 @@ DockManager::DockManager(QObject* parent)
 	QTimer* autosave_timer = new QTimer(this);
 	connect(autosave_timer, &QTimer::timeout, this, &DockManager::saveCurrentLayout);
 	autosave_timer->start(60 * 1000);
-
-	m_blink_timer = new QTimer(this);
-	connect(m_blink_timer, &QTimer::timeout, this, &DockManager::layoutSwitcherUpdateBlink);
 }
 
 void DockManager::configureDockingSystem()
@@ -144,17 +140,13 @@ void DockManager::switchToLayout(DockLayout::Index layout_index, bool blink_tab)
 			layout.thaw();
 
 			int tab_index = static_cast(layout_index);
-			if (m_switcher && tab_index >= 0 && tab_index < m_plus_tab_index)
-			{
-				m_ignore_current_tab_changed = true;
-				m_switcher->setCurrentIndex(tab_index);
-				m_ignore_current_tab_changed = false;
-			}
+			if (m_menu_bar && tab_index >= 0)
+				m_menu_bar->onCurrentLayoutChanged(layout_index);
 		}
 	}
 
 	if (blink_tab)
-		layoutSwitcherStartBlink();
+		m_menu_bar->startBlink(m_current_layout);
 }
 
 bool DockManager::switchToLayoutWithCPU(BreakPointCpu cpu, bool blink_tab)
@@ -476,168 +468,195 @@ void DockManager::createWindowsMenu(QMenu* menu)
 		menu->addAction(toggle.action);
 }
 
-QWidget* DockManager::createLayoutSwitcher(QWidget* menu_bar)
+QWidget* DockManager::createMenuBar(QWidget* original_menu_bar)
 {
-	QWidget* container = new QWidget;
+	pxAssert(!m_menu_bar);
 
-	QHBoxLayout* layout = new QHBoxLayout;
-	layout->setContentsMargins(0, 2, 2, 0);
-	container->setLayout(layout);
+	m_menu_bar = new DockMenuBar(original_menu_bar);
 
-	QWidget* menu_wrapper = new QWidget;
-	menu_wrapper->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
-	layout->addWidget(menu_wrapper);
+	connect(m_menu_bar, &DockMenuBar::currentLayoutChanged, this, [this](DockLayout::Index layout_index) {
+		if (layout_index >= m_layouts.size())
+			return;
 
-	QHBoxLayout* menu_layout = new QHBoxLayout;
-	menu_layout->setContentsMargins(0, 4, 0, 4);
-	menu_wrapper->setLayout(menu_layout);
-
-	menu_layout->addWidget(menu_bar);
-
-	m_switcher = new QTabBar;
-	m_switcher->setContentsMargins(0, 0, 0, 0);
-	m_switcher->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
-	m_switcher->setContextMenuPolicy(Qt::CustomContextMenu);
-	m_switcher->setMovable(true);
-	layout->addWidget(m_switcher);
+		switchToLayout(layout_index);
+	});
+	connect(m_menu_bar, &DockMenuBar::newButtonClicked, this, &DockManager::newLayoutClicked);
+	connect(m_menu_bar, &DockMenuBar::layoutMoved, this, &DockManager::layoutSwitcherTabMoved);
+	connect(m_menu_bar, &DockMenuBar::lockButtonToggled, this, &DockManager::setLayoutLockedAndSaveSetting);
+	connect(m_menu_bar, &DockMenuBar::layoutSwitcherContextMenuRequested,
+		this, &DockManager::openLayoutSwitcherContextMenu);
 
 	updateLayoutSwitcher();
 
-	connect(m_switcher, &QTabBar::tabMoved, this, &DockManager::layoutSwitcherTabMoved);
-	connect(m_switcher, &QTabBar::customContextMenuRequested, this, &DockManager::layoutSwitcherContextMenu);
-
-	QWidget* spacer = new QWidget;
-	spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
-	layout->addWidget(spacer);
-
 	bool layout_locked = Host::GetBaseBoolSettingValue("Debugger/UserInterface", "LayoutLocked", true);
+	setLayoutLocked(layout_locked, false);
 
-	QPushButton* lock_layout_toggle = new QPushButton;
-	lock_layout_toggle->setCheckable(true);
-	lock_layout_toggle->setChecked(layout_locked);
-	lock_layout_toggle->setFlat(true);
-	connect(lock_layout_toggle, &QPushButton::toggled, this, [this, lock_layout_toggle](bool checked) {
-		setLayoutLocked(checked, lock_layout_toggle, true);
-	});
-	layout->addWidget(lock_layout_toggle);
-
-	setLayoutLocked(layout_locked, lock_layout_toggle, false);
-
-	return container;
+	return m_menu_bar;
 }
 
 void DockManager::updateLayoutSwitcher()
 {
-	if (!m_switcher)
-		return;
-
-	disconnect(m_tab_connection);
-
-	for (int i = m_switcher->count(); i > 0; i--)
-		m_switcher->removeTab(i - 1);
-
-	for (DockLayout& layout : m_layouts)
-	{
-		const char* cpu_name = DebugInterface::cpuName(layout.cpu());
-		QString tab_name = QString("%1 (%2)").arg(layout.name()).arg(cpu_name);
-		m_switcher->addTab(tab_name);
-	}
-
-	m_plus_tab_index = m_switcher->addTab("+");
-	m_current_tab_index = m_current_layout;
-
-	if (m_current_layout != DockLayout::INVALID_INDEX)
-		m_switcher->setCurrentIndex(m_current_layout);
-
-	// If we don't have any layouts, the currently selected tab will never be
-	// changed, so we respond to all clicks instead.
-	if (!m_layouts.empty())
-		m_tab_connection = connect(m_switcher, &QTabBar::currentChanged, this, &DockManager::layoutSwitcherTabChanged);
-	else
-		m_tab_connection = connect(m_switcher, &QTabBar::tabBarClicked, this, &DockManager::layoutSwitcherTabChanged);
-
-	layoutSwitcherStopBlink();
+	if (m_menu_bar)
+		m_menu_bar->updateLayoutSwitcher(m_current_layout, m_layouts);
 }
 
-void DockManager::layoutSwitcherTabChanged(int index)
+void DockManager::newLayoutClicked()
 {
-	// Prevent recursion.
-	if (m_ignore_current_tab_changed)
-		return;
+	// The plus button has just been made the current tab, so set it back to the
+	// one corresponding to the current layout again.
+	m_menu_bar->onCurrentLayoutChanged(m_current_layout);
 
-	if (index == m_plus_tab_index)
+	auto name_validator = [this](const QString& name) {
+		return !hasNameConflict(name, DockLayout::INVALID_INDEX);
+	};
+
+	bool can_clone_current_layout = m_current_layout != DockLayout::INVALID_INDEX;
+
+	QPointer dialog = new LayoutEditorDialog(
+		name_validator, can_clone_current_layout, g_debugger_window);
+
+	if (dialog->exec() == QDialog::Accepted && name_validator(dialog->name()))
 	{
-		if (m_current_tab_index >= 0 && m_current_tab_index < m_plus_tab_index)
+		DockLayout::Index new_layout = DockLayout::INVALID_INDEX;
+
+		const auto [mode, index] = dialog->initialState();
+		switch (mode)
 		{
-			m_ignore_current_tab_changed = true;
-			m_switcher->setCurrentIndex(m_current_tab_index);
-			m_ignore_current_tab_changed = false;
+			case LayoutEditorDialog::DEFAULT_LAYOUT:
+			{
+				const DockTables::DefaultDockLayout& default_layout = DockTables::DEFAULT_DOCK_LAYOUTS.at(index);
+				new_layout = createLayout(dialog->name(), dialog->cpu(), false, default_layout.name);
+				break;
+			}
+			case LayoutEditorDialog::BLANK_LAYOUT:
+			{
+				new_layout = createLayout(dialog->name(), dialog->cpu(), false);
+				break;
+			}
+			case LayoutEditorDialog::CLONE_LAYOUT:
+			{
+				if (m_current_layout == DockLayout::INVALID_INDEX)
+					break;
+
+				DockLayout::Index old_layout = m_current_layout;
+
+				// Freeze the current layout so we can copy the geometry.
+				switchToLayout(DockLayout::INVALID_INDEX);
+
+				new_layout = createLayout(dialog->name(), dialog->cpu(), false, m_layouts.at(old_layout));
+				break;
+			}
 		}
 
-		auto name_validator = [this](const QString& name) {
-			return !hasNameConflict(name, DockLayout::INVALID_INDEX);
-		};
-
-		bool can_clone_current_layout = m_current_layout != DockLayout::INVALID_INDEX;
-
-		QPointer dialog = new LayoutEditorDialog(
-			name_validator, can_clone_current_layout, g_debugger_window);
-
-		if (dialog->exec() == QDialog::Accepted && name_validator(dialog->name()))
+		if (new_layout != DockLayout::INVALID_INDEX)
 		{
-			DockLayout::Index new_layout = DockLayout::INVALID_INDEX;
-
-			const auto [mode, index] = dialog->initialState();
-			switch (mode)
-			{
-				case LayoutEditorDialog::DEFAULT_LAYOUT:
-				{
-					const DockTables::DefaultDockLayout& default_layout = DockTables::DEFAULT_DOCK_LAYOUTS.at(index);
-					new_layout = createLayout(dialog->name(), dialog->cpu(), false, default_layout.name);
-					break;
-				}
-				case LayoutEditorDialog::BLANK_LAYOUT:
-				{
-					new_layout = createLayout(dialog->name(), dialog->cpu(), false);
-					break;
-				}
-				case LayoutEditorDialog::CLONE_LAYOUT:
-				{
-					if (m_current_layout == DockLayout::INVALID_INDEX)
-						return;
-
-					DockLayout::Index old_layout = m_current_layout;
-
-					// Freeze the current layout so we can copy the geometry.
-					switchToLayout(DockLayout::INVALID_INDEX);
-
-					new_layout = createLayout(dialog->name(), dialog->cpu(), false, m_layouts.at(old_layout));
-					break;
-				}
-			}
-
 			updateLayoutSwitcher();
 			switchToLayout(new_layout);
 		}
-
-		delete dialog.get();
 	}
-	else
-	{
-		DockLayout::Index layout_index = static_cast(index);
-		if (layout_index < 0 || layout_index >= m_layouts.size())
-			return;
 
-		switchToLayout(layout_index);
-		m_current_tab_index = index;
-	}
+	delete dialog.get();
 }
 
-void DockManager::layoutSwitcherTabMoved(int from, int to)
+void DockManager::openLayoutSwitcherContextMenu(const QPoint& pos, QTabBar* layout_switcher)
 {
-	DockLayout::Index from_index = static_cast(from);
-	DockLayout::Index to_index = static_cast(to);
+	DockLayout::Index layout_index = static_cast(layout_switcher->tabAt(pos));
+	if (layout_index >= m_layouts.size())
+		return;
 
+	DockLayout& layout = m_layouts[layout_index];
+
+	QMenu* menu = new QMenu(layout_switcher);
+	menu->setAttribute(Qt::WA_DeleteOnClose);
+
+	QAction* edit_action = menu->addAction(tr("Edit Layout"));
+	connect(edit_action, &QAction::triggered, [this, layout_index]() {
+		editLayoutClicked(layout_index);
+	});
+
+	QAction* reset_action = menu->addAction(tr("Reset Layout"));
+	reset_action->setEnabled(layout.canReset());
+	reset_action->connect(reset_action, &QAction::triggered, [this, layout_index]() {
+		resetLayoutClicked(layout_index);
+	});
+
+	QAction* delete_action = menu->addAction(tr("Delete Layout"));
+	connect(delete_action, &QAction::triggered, [this, layout_index]() {
+		deleteLayoutClicked(layout_index);
+	});
+
+	menu->popup(layout_switcher->mapToGlobal(pos));
+}
+
+void DockManager::editLayoutClicked(DockLayout::Index layout_index)
+{
+	if (layout_index >= m_layouts.size())
+		return;
+
+	DockLayout& layout = m_layouts[layout_index];
+
+	auto name_validator = [this, layout_index](const QString& name) {
+		return !hasNameConflict(name, layout_index);
+	};
+
+	QPointer dialog = new LayoutEditorDialog(
+		layout.name(), layout.cpu(), name_validator, g_debugger_window);
+
+	if (dialog->exec() != QDialog::Accepted || !name_validator(dialog->name()))
+		return;
+
+	layout.setName(dialog->name());
+	layout.setCpu(dialog->cpu());
+
+	layout.save(layout_index);
+
+	delete dialog.get();
+
+	updateLayoutSwitcher();
+}
+
+void DockManager::resetLayoutClicked(DockLayout::Index layout_index)
+{
+	if (layout_index >= m_layouts.size())
+		return;
+
+	DockLayout& layout = m_layouts[layout_index];
+	if (!layout.canReset())
+		return;
+
+	QString text = tr("Are you sure you want to reset layout '%1'?").arg(layout.name());
+	if (QMessageBox::question(g_debugger_window, tr("Confirmation"), text) != QMessageBox::Yes)
+		return;
+
+	bool current_layout = layout_index == m_current_layout;
+
+	if (current_layout)
+		switchToLayout(DockLayout::INVALID_INDEX);
+
+	layout.reset();
+	layout.save(layout_index);
+
+	if (current_layout)
+		switchToLayout(layout_index);
+}
+
+void DockManager::deleteLayoutClicked(DockLayout::Index layout_index)
+{
+	if (layout_index >= m_layouts.size())
+		return;
+
+	DockLayout& layout = m_layouts[layout_index];
+
+	QString text = tr("Are you sure you want to delete layout '%1'?").arg(layout.name());
+	if (QMessageBox::question(g_debugger_window, tr("Confirmation"), text) != QMessageBox::Yes)
+		return;
+
+	deleteLayout(layout_index);
+	updateLayoutSwitcher();
+}
+
+void DockManager::layoutSwitcherTabMoved(DockLayout::Index from_index, DockLayout::Index to_index)
+{
 	if (from_index >= m_layouts.size() || to_index >= m_layouts.size())
 	{
 		// This happens when the user tries to move a layout to the right of the
@@ -660,135 +679,6 @@ void DockManager::layoutSwitcherTabMoved(int from, int to)
 		m_current_layout = from_index;
 }
 
-void DockManager::layoutSwitcherContextMenu(QPoint pos)
-{
-	DockLayout::Index layout_index = static_cast(m_switcher->tabAt(pos));
-	if (layout_index >= m_layouts.size())
-		return;
-
-	DockLayout& layout = m_layouts[layout_index];
-
-	QMenu* menu = new QMenu(m_switcher);
-	menu->setAttribute(Qt::WA_DeleteOnClose);
-
-	QAction* edit_action = menu->addAction(tr("Edit Layout"));
-	connect(edit_action, &QAction::triggered, [this, layout_index]() {
-		if (layout_index >= m_layouts.size())
-			return;
-
-		DockLayout& layout = m_layouts[layout_index];
-
-		auto name_validator = [this, layout_index](const QString& name) {
-			return !hasNameConflict(name, layout_index);
-		};
-
-		QPointer dialog = new LayoutEditorDialog(
-			layout.name(), layout.cpu(), name_validator, g_debugger_window);
-
-		if (dialog->exec() != QDialog::Accepted || !name_validator(dialog->name()))
-			return;
-
-		layout.setName(dialog->name());
-		layout.setCpu(dialog->cpu());
-
-		layout.save(layout_index);
-
-		delete dialog.get();
-
-		updateLayoutSwitcher();
-	});
-
-	QAction* reset_action = menu->addAction(tr("Reset Layout"));
-	reset_action->setEnabled(layout.canReset());
-	reset_action->connect(reset_action, &QAction::triggered, [this, layout_index]() {
-		if (layout_index >= m_layouts.size())
-			return;
-
-		DockLayout& layout = m_layouts[layout_index];
-		if (!layout.canReset())
-			return;
-
-		QString text = tr("Are you sure you want to reset layout '%1'?").arg(layout.name());
-		if (QMessageBox::question(g_debugger_window, tr("Confirmation"), text) != QMessageBox::Yes)
-			return;
-
-		bool current_layout = layout_index == m_current_layout;
-
-		if (current_layout)
-			switchToLayout(DockLayout::INVALID_INDEX);
-
-		layout.reset();
-		layout.save(layout_index);
-
-		if (current_layout)
-			switchToLayout(layout_index);
-	});
-
-	QAction* delete_action = menu->addAction(tr("Delete Layout"));
-	connect(delete_action, &QAction::triggered, [this, layout_index]() {
-		if (layout_index >= m_layouts.size())
-			return;
-
-		DockLayout& layout = m_layouts[layout_index];
-
-		QString text = tr("Are you sure you want to delete layout '%1'?").arg(layout.name());
-		if (QMessageBox::question(g_debugger_window, tr("Confirmation"), text) != QMessageBox::Yes)
-			return;
-
-		deleteLayout(layout_index);
-		updateLayoutSwitcher();
-	});
-
-	menu->popup(m_switcher->mapToGlobal(pos));
-}
-
-void DockManager::layoutSwitcherStartBlink()
-{
-	if (!m_switcher)
-		return;
-
-	layoutSwitcherStopBlink();
-
-	if (m_current_layout == DockLayout::INVALID_INDEX)
-		return;
-
-	m_blink_tab = m_current_layout;
-	m_blink_stage = 0;
-	m_blink_timer->start(500);
-
-	layoutSwitcherUpdateBlink();
-}
-
-void DockManager::layoutSwitcherUpdateBlink()
-{
-	if (!m_switcher)
-		return;
-
-	if (m_blink_tab < m_switcher->count())
-	{
-		if (m_blink_stage % 2 == 0)
-			m_switcher->setTabTextColor(m_blink_tab, Qt::red);
-		else
-			m_switcher->setTabTextColor(m_blink_tab, m_switcher->palette().text().color());
-	}
-
-	m_blink_stage++;
-
-	if (m_blink_stage > 7)
-		m_blink_timer->stop();
-}
-
-void DockManager::layoutSwitcherStopBlink()
-{
-	if (m_blink_timer->isActive())
-	{
-		if (m_blink_tab < m_switcher->count())
-			m_switcher->setTabTextColor(m_blink_tab, m_switcher->palette().text().color());
-
-		m_blink_timer->stop();
-	}
-}
-
 bool DockManager::hasNameConflict(const QString& name, DockLayout::Index layout_index)
 {
 	std::string safe_name = Path::SanitizeFileName(name.toStdString());
@@ -879,23 +769,17 @@ bool DockManager::isLayoutLocked()
 	return m_layout_locked;
 }
 
-void DockManager::setLayoutLocked(bool locked, QPushButton* lock_layout_toggle, bool write_back)
+void DockManager::setLayoutLockedAndSaveSetting(bool locked)
+{
+	setLayoutLocked(locked, true);
+}
+
+void DockManager::setLayoutLocked(bool locked, bool save_setting)
 {
 	m_layout_locked = locked;
 
-	if (lock_layout_toggle)
-	{
-		if (m_layout_locked)
-		{
-			lock_layout_toggle->setText(tr("Layout Locked"));
-			lock_layout_toggle->setIcon(QIcon::fromTheme(QString::fromUtf8("padlock-lock")));
-		}
-		else
-		{
-			lock_layout_toggle->setText(tr("Layout Unlocked"));
-			lock_layout_toggle->setIcon(QIcon::fromTheme(QString::fromUtf8("padlock-unlock")));
-		}
-	}
+	if (m_menu_bar)
+		m_menu_bar->onLockStateChanged(locked);
 
 	updateToolBarLockState();
 
@@ -909,7 +793,7 @@ void DockManager::setLayoutLocked(bool locked, QPushButton* lock_layout_toggle,
 			stack->tabBar()->setTabText(0, stack->tabBar()->tabText(0));
 	}
 
-	if (write_back)
+	if (save_setting)
 	{
 		Host::SetBaseBoolSettingValue("Debugger/UserInterface", "LayoutLocked", m_layout_locked);
 		Host::CommitBaseSettingChanges();
diff --git a/pcsx2-qt/Debugger/Docking/DockManager.h b/pcsx2-qt/Debugger/Docking/DockManager.h
index c27df75dfc..62b0880991 100644
--- a/pcsx2-qt/Debugger/Docking/DockManager.h
+++ b/pcsx2-qt/Debugger/Docking/DockManager.h
@@ -4,6 +4,7 @@
 #pragma once
 
 #include "Debugger/Docking/DockLayout.h"
+#include "Debugger/Docking/DockMenuBar.h"
 
 #include 
 #include 
@@ -68,14 +69,14 @@ public:
 	void createToolsMenu(QMenu* menu);
 	void createWindowsMenu(QMenu* menu);
 
-	QWidget* createLayoutSwitcher(QWidget* menu_bar);
+	QWidget* createMenuBar(QWidget* original_menu_bar);
 	void updateLayoutSwitcher();
-	void layoutSwitcherTabChanged(int index);
-	void layoutSwitcherTabMoved(int from, int to);
-	void layoutSwitcherContextMenu(QPoint pos);
-	void layoutSwitcherStartBlink();
-	void layoutSwitcherUpdateBlink();
-	void layoutSwitcherStopBlink();
+	void newLayoutClicked();
+	void openLayoutSwitcherContextMenu(const QPoint& pos, QTabBar* layout_switcher);
+	void editLayoutClicked(DockLayout::Index layout_index);
+	void resetLayoutClicked(DockLayout::Index layout_index);
+	void deleteLayoutClicked(DockLayout::Index layout_index);
+	void layoutSwitcherTabMoved(DockLayout::Index from_index, DockLayout::Index to_index);
 
 	bool hasNameConflict(const QString& name, DockLayout::Index layout_index);
 
@@ -91,7 +92,8 @@ public:
 	void updateStyleSheets();
 
 	bool isLayoutLocked();
-	void setLayoutLocked(bool locked, QPushButton* lock_layout_toggle, bool write_back);
+	void setLayoutLockedAndSaveSetting(bool locked);
+	void setLayoutLocked(bool locked, bool save_setting);
 	void updateToolBarLockState();
 
 	std::optional cpu();
@@ -103,16 +105,7 @@ private:
 	std::vector m_layouts;
 	DockLayout::Index m_current_layout = DockLayout::INVALID_INDEX;
 
-	QTabBar* m_switcher = nullptr;
-	int m_plus_tab_index = -1;
-	int m_current_tab_index = -1;
-	bool m_ignore_current_tab_changed = false;
-
-	QMetaObject::Connection m_tab_connection;
+	DockMenuBar* m_menu_bar = nullptr;
 
 	bool m_layout_locked = true;
-
-	QTimer* m_blink_timer = nullptr;
-	int m_blink_tab = 0;
-	int m_blink_stage = 0;
 };
diff --git a/pcsx2-qt/Debugger/Docking/DockMenuBar.cpp b/pcsx2-qt/Debugger/Docking/DockMenuBar.cpp
new file mode 100644
index 0000000000..e88e8bfd5e
--- /dev/null
+++ b/pcsx2-qt/Debugger/Docking/DockMenuBar.cpp
@@ -0,0 +1,184 @@
+// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
+// SPDX-License-Identifier: GPL-3.0+
+
+#include "DockMenuBar.h"
+
+#include 
+#include 
+#include 
+#include 
+
+DockMenuBar::DockMenuBar(QWidget* original_menu_bar, QWidget* parent)
+	: QWidget(parent)
+{
+	QHBoxLayout* layout = new QHBoxLayout;
+	layout->setContentsMargins(0, 2, 2, 0);
+	setLayout(layout);
+
+	QWidget* menu_wrapper = new QWidget;
+	menu_wrapper->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
+	layout->addWidget(menu_wrapper);
+
+	QHBoxLayout* menu_layout = new QHBoxLayout;
+	menu_layout->setContentsMargins(0, 4, 0, 4);
+	menu_wrapper->setLayout(menu_layout);
+
+	menu_layout->addWidget(original_menu_bar);
+
+	m_layout_switcher = new QTabBar;
+	m_layout_switcher->setContentsMargins(0, 0, 0, 0);
+	m_layout_switcher->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
+	m_layout_switcher->setContextMenuPolicy(Qt::CustomContextMenu);
+	m_layout_switcher->setMovable(true);
+	layout->addWidget(m_layout_switcher);
+
+	QWidget* spacer = new QWidget;
+	spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+	layout->addWidget(spacer);
+
+	connect(m_layout_switcher, &QTabBar::tabMoved, this, [this](int from, int to) {
+		DockLayout::Index from_index = static_cast(from);
+		DockLayout::Index to_index = static_cast(to);
+		emit layoutMoved(from_index, to_index);
+	});
+
+	connect(m_layout_switcher, &QTabBar::customContextMenuRequested, this, [this](const QPoint& pos) {
+		emit layoutSwitcherContextMenuRequested(pos, m_layout_switcher);
+	});
+
+	m_blink_timer = new QTimer(this);
+	connect(m_blink_timer, &QTimer::timeout, this, &DockMenuBar::updateBlink);
+
+	m_layout_locked_toggle = new QPushButton;
+	m_layout_locked_toggle->setCheckable(true);
+	m_layout_locked_toggle->setFlat(true);
+	connect(m_layout_locked_toggle, &QPushButton::clicked, this, [this](bool checked) {
+		if (m_ignore_lock_state_changed)
+			return;
+
+		emit lockButtonToggled(checked);
+	});
+	layout->addWidget(m_layout_locked_toggle);
+}
+
+void DockMenuBar::updateLayoutSwitcher(DockLayout::Index current_index, const std::vector& layouts)
+{
+	disconnect(m_tab_connection);
+
+	for (int i = m_layout_switcher->count(); i > 0; i--)
+		m_layout_switcher->removeTab(i - 1);
+
+	for (const DockLayout& layout : layouts)
+	{
+		const char* cpu_name = DebugInterface::cpuName(layout.cpu());
+		QString tab_name = QString("%1 (%2)").arg(layout.name()).arg(cpu_name);
+		m_layout_switcher->addTab(tab_name);
+	}
+
+	m_plus_tab_index = m_layout_switcher->addTab("+");
+	m_current_tab_index = current_index;
+
+	if (current_index != DockLayout::INVALID_INDEX)
+		m_layout_switcher->setCurrentIndex(current_index);
+	else
+		m_layout_switcher->setCurrentIndex(m_plus_tab_index);
+
+	// If we don't have any layouts, the currently selected tab will never be
+	// changed, so we respond to all clicks instead.
+	if (m_plus_tab_index > 0)
+		m_tab_connection = connect(m_layout_switcher, &QTabBar::currentChanged, this, &DockMenuBar::tabChanged);
+	else
+		m_tab_connection = connect(m_layout_switcher, &QTabBar::tabBarClicked, this, &DockMenuBar::tabChanged);
+
+	stopBlink();
+}
+
+void DockMenuBar::onCurrentLayoutChanged(DockLayout::Index current_index)
+{
+	m_ignore_current_tab_changed = true;
+
+	if (current_index != DockLayout::INVALID_INDEX)
+		m_layout_switcher->setCurrentIndex(current_index);
+	else
+		m_layout_switcher->setCurrentIndex(m_plus_tab_index);
+
+	m_ignore_current_tab_changed = false;
+}
+
+void DockMenuBar::onLockStateChanged(bool layout_locked)
+{
+	m_ignore_lock_state_changed = true;
+
+	m_layout_locked_toggle->setChecked(layout_locked);
+
+	if (layout_locked)
+	{
+		m_layout_locked_toggle->setText(tr("Layout Locked"));
+		m_layout_locked_toggle->setIcon(QIcon::fromTheme(QString::fromUtf8("padlock-lock")));
+	}
+	else
+	{
+		m_layout_locked_toggle->setText(tr("Layout Unlocked"));
+		m_layout_locked_toggle->setIcon(QIcon::fromTheme(QString::fromUtf8("padlock-unlock")));
+	}
+
+	m_ignore_lock_state_changed = false;
+}
+
+void DockMenuBar::startBlink(DockLayout::Index layout_index)
+{
+	stopBlink();
+
+	if (layout_index == DockLayout::INVALID_INDEX)
+		return;
+
+	m_blink_tab = static_cast(layout_index);
+	m_blink_stage = 0;
+	m_blink_timer->start(500);
+
+	updateBlink();
+}
+
+void DockMenuBar::updateBlink()
+{
+	if (m_blink_tab < m_layout_switcher->count())
+	{
+		if (m_blink_stage % 2 == 0)
+			m_layout_switcher->setTabTextColor(m_blink_tab, Qt::red);
+		else
+			m_layout_switcher->setTabTextColor(m_blink_tab, m_layout_switcher->palette().text().color());
+	}
+
+	m_blink_stage++;
+
+	if (m_blink_stage > 7)
+		m_blink_timer->stop();
+}
+
+void DockMenuBar::stopBlink()
+{
+	if (m_blink_timer->isActive())
+	{
+		if (m_blink_tab < m_layout_switcher->count())
+			m_layout_switcher->setTabTextColor(m_blink_tab, m_layout_switcher->palette().text().color());
+
+		m_blink_timer->stop();
+	}
+}
+
+void DockMenuBar::tabChanged(int index)
+{
+	// Prevent recursion.
+	if (m_ignore_current_tab_changed)
+		return;
+
+	if (index < m_plus_tab_index)
+	{
+		DockLayout::Index layout_index = static_cast(index);
+		emit currentLayoutChanged(layout_index);
+	}
+	else if (index == m_plus_tab_index)
+	{
+		emit newButtonClicked();
+	}
+}
diff --git a/pcsx2-qt/Debugger/Docking/DockMenuBar.h b/pcsx2-qt/Debugger/Docking/DockMenuBar.h
new file mode 100644
index 0000000000..6ea77e4b5f
--- /dev/null
+++ b/pcsx2-qt/Debugger/Docking/DockMenuBar.h
@@ -0,0 +1,56 @@
+// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
+// SPDX-License-Identifier: GPL-3.0+
+
+#pragma once
+
+#include "Debugger/Docking/DockLayout.h"
+
+#include 
+#include 
+#include 
+#include 
+
+class DockMenuBar : public QWidget
+{
+	Q_OBJECT
+
+public:
+	DockMenuBar(QWidget* original_menu_bar, QWidget* parent = nullptr);
+
+	void updateLayoutSwitcher(DockLayout::Index current_index, const std::vector& layouts);
+
+	// Notify the menu bar that a new layout has been selected.
+	void onCurrentLayoutChanged(DockLayout::Index current_index);
+
+	// Notify the menu bar that the layout has been locked/unlocked.
+	void onLockStateChanged(bool layout_locked);
+
+	void startBlink(DockLayout::Index layout_index);
+	void updateBlink();
+	void stopBlink();
+
+Q_SIGNALS:
+	void currentLayoutChanged(DockLayout::Index layout_index);
+	void newButtonClicked();
+	void layoutMoved(DockLayout::Index from_index, DockLayout::Index to_index);
+	void lockButtonToggled(bool locked);
+
+	void layoutSwitcherContextMenuRequested(const QPoint& pos, QTabBar* layout_switcher);
+
+private:
+	void tabChanged(int index);
+
+
+	QTabBar* m_layout_switcher;
+	QMetaObject::Connection m_tab_connection;
+	int m_plus_tab_index = -1;
+	int m_current_tab_index = -1;
+	bool m_ignore_current_tab_changed = false;
+
+	QTimer* m_blink_timer = nullptr;
+	int m_blink_tab = 0;
+	int m_blink_stage = 0;
+
+	QPushButton* m_layout_locked_toggle;
+	bool m_ignore_lock_state_changed = false;
+};
diff --git a/pcsx2-qt/pcsx2-qt.vcxproj b/pcsx2-qt/pcsx2-qt.vcxproj
index 228f5a77d4..248803b504 100644
--- a/pcsx2-qt/pcsx2-qt.vcxproj
+++ b/pcsx2-qt/pcsx2-qt.vcxproj
@@ -126,6 +126,7 @@
     
     
     
+    
     
     
     
@@ -242,6 +243,7 @@
     
     
     
+    
     
     
     
@@ -313,6 +315,7 @@
     
     
     
+    
     
     
     
diff --git a/pcsx2-qt/pcsx2-qt.vcxproj.filters b/pcsx2-qt/pcsx2-qt.vcxproj.filters
index a6488172bd..0b461826c7 100644
--- a/pcsx2-qt/pcsx2-qt.vcxproj.filters
+++ b/pcsx2-qt/pcsx2-qt.vcxproj.filters
@@ -488,6 +488,12 @@
     
       moc
     
+    
+      moc
+    
+    
+      Debugger\Docking
+    
   
   
     
@@ -719,6 +725,9 @@
     
       Debugger\SymbolTree
     
+    
+      Debugger\Docking
+    
   
   
     

From abf074eaf4d694e47e88c4cb359126bb2e0f1da6 Mon Sep 17 00:00:00 2001
From: chaoticgd <43898262+chaoticgd@users.noreply.github.com>
Date: Fri, 21 Mar 2025 20:40:54 +0000
Subject: [PATCH 037/162] Debugger: Fix some theming issues

---
 pcsx2-qt/Debugger/DebuggerWindow.cpp      |  13 +-
 pcsx2-qt/Debugger/DebuggerWindow.h        |   2 +-
 pcsx2-qt/Debugger/Docking/DockManager.cpp |  16 +-
 pcsx2-qt/Debugger/Docking/DockManager.h   |   2 +-
 pcsx2-qt/Debugger/Docking/DockMenuBar.cpp | 174 +++++++++++++++++++++-
 pcsx2-qt/Debugger/Docking/DockMenuBar.h   |  37 +++++
 pcsx2-qt/Debugger/Docking/DockViews.cpp   |   9 ++
 pcsx2-qt/MainWindow.cpp                   |   2 +-
 8 files changed, 237 insertions(+), 18 deletions(-)

diff --git a/pcsx2-qt/Debugger/DebuggerWindow.cpp b/pcsx2-qt/Debugger/DebuggerWindow.cpp
index a92c800412..22913f93bf 100644
--- a/pcsx2-qt/Debugger/DebuggerWindow.cpp
+++ b/pcsx2-qt/Debugger/DebuggerWindow.cpp
@@ -114,6 +114,8 @@ DebuggerWindow::DebuggerWindow(QWidget* parent)
 
 	setMenuWidget(m_dock_manager->createMenuBar(menu_bar));
 
+	updateTheme();
+
 	Host::RunOnCPUThread([]() {
 		R5900SymbolImporter.OnDebuggerOpened();
 	});
@@ -193,7 +195,7 @@ void DebuggerWindow::setupFonts()
 		m_font_size++;
 
 		updateFontActions();
-		updateStyleSheets();
+		updateTheme();
 		saveFontSize();
 	});
 
@@ -205,7 +207,7 @@ void DebuggerWindow::setupFonts()
 		m_font_size--;
 
 		updateFontActions();
-		updateStyleSheets();
+		updateTheme();
 		saveFontSize();
 	});
 
@@ -213,12 +215,11 @@ void DebuggerWindow::setupFonts()
 		m_font_size = DEFAULT_FONT_SIZE;
 
 		updateFontActions();
-		updateStyleSheets();
+		updateTheme();
 		saveFontSize();
 	});
 
 	updateFontActions();
-	updateStyleSheets();
 }
 
 void DebuggerWindow::updateFontActions()
@@ -239,7 +240,7 @@ int DebuggerWindow::fontSize()
 	return m_font_size;
 }
 
-void DebuggerWindow::updateStyleSheets()
+void DebuggerWindow::updateTheme()
 {
 	// TODO: Migrate away from stylesheets to improve performance.
 	if (m_font_size != DEFAULT_FONT_SIZE)
@@ -252,7 +253,7 @@ void DebuggerWindow::updateStyleSheets()
 		setStyleSheet(QString());
 	}
 
-	dockManager().updateStyleSheets();
+	dockManager().updateTheme();
 }
 
 void DebuggerWindow::saveWindowGeometry()
diff --git a/pcsx2-qt/Debugger/DebuggerWindow.h b/pcsx2-qt/Debugger/DebuggerWindow.h
index a35748b8dd..3999a9899b 100644
--- a/pcsx2-qt/Debugger/DebuggerWindow.h
+++ b/pcsx2-qt/Debugger/DebuggerWindow.h
@@ -31,7 +31,7 @@ public:
 	void updateFontActions();
 	void saveFontSize();
 	int fontSize();
-	void updateStyleSheets();
+	void updateTheme();
 
 	void saveWindowGeometry();
 	void restoreWindowGeometry();
diff --git a/pcsx2-qt/Debugger/Docking/DockManager.cpp b/pcsx2-qt/Debugger/Docking/DockManager.cpp
index abc0b0386d..1c20580c39 100644
--- a/pcsx2-qt/Debugger/Docking/DockManager.cpp
+++ b/pcsx2-qt/Debugger/Docking/DockManager.cpp
@@ -25,6 +25,8 @@
 #include 
 #include 
 #include 
+#include 
+#include 
 
 DockManager::DockManager(QObject* parent)
 	: QObject(parent)
@@ -757,11 +759,23 @@ void DockManager::switchToDebuggerWidget(DebuggerWidget* widget)
 	}
 }
 
-void DockManager::updateStyleSheets()
+void DockManager::updateTheme()
 {
+	if (m_menu_bar)
+		m_menu_bar->updateTheme();
+
 	for (DockLayout& layout : m_layouts)
 		for (const auto& [unique_name, widget] : layout.debuggerWidgets())
 			widget->updateStyleSheet();
+
+	// KDDockWidgets::QtWidgets::TabBar sets its own style to a subclass of
+	// QProxyStyle in its constructor, so we need to update that here.
+	for (KDDockWidgets::Core::Group* group : KDDockWidgets::DockRegistry::self()->groups())
+	{
+		auto tab_bar = static_cast(group->tabBar()->view());
+		if (QProxyStyle* style = qobject_cast(tab_bar->style()))
+			style->setBaseStyle(QStyleFactory::create(qApp->style()->name()));
+	}
 }
 
 bool DockManager::isLayoutLocked()
diff --git a/pcsx2-qt/Debugger/Docking/DockManager.h b/pcsx2-qt/Debugger/Docking/DockManager.h
index 62b0880991..d2a8c4ccb4 100644
--- a/pcsx2-qt/Debugger/Docking/DockManager.h
+++ b/pcsx2-qt/Debugger/Docking/DockManager.h
@@ -89,7 +89,7 @@ public:
 	void setPrimaryDebuggerWidget(DebuggerWidget* widget, bool is_primary);
 	void switchToDebuggerWidget(DebuggerWidget* widget);
 
-	void updateStyleSheets();
+	void updateTheme();
 
 	bool isLayoutLocked();
 	void setLayoutLockedAndSaveSetting(bool locked);
diff --git a/pcsx2-qt/Debugger/Docking/DockMenuBar.cpp b/pcsx2-qt/Debugger/Docking/DockMenuBar.cpp
index e88e8bfd5e..360cac7598 100644
--- a/pcsx2-qt/Debugger/Docking/DockMenuBar.cpp
+++ b/pcsx2-qt/Debugger/Docking/DockMenuBar.cpp
@@ -3,16 +3,22 @@
 
 #include "DockMenuBar.h"
 
+#include 
 #include 
 #include 
 #include 
+#include 
 #include 
 
+static const int OUTER_MENU_MARGIN = 2;
+static const int INNER_MENU_MARGIN = 4;
+
 DockMenuBar::DockMenuBar(QWidget* original_menu_bar, QWidget* parent)
 	: QWidget(parent)
+	, m_original_menu_bar(original_menu_bar)
 {
 	QHBoxLayout* layout = new QHBoxLayout;
-	layout->setContentsMargins(0, 2, 2, 0);
+	layout->setContentsMargins(0, OUTER_MENU_MARGIN, OUTER_MENU_MARGIN, 0);
 	setLayout(layout);
 
 	QWidget* menu_wrapper = new QWidget;
@@ -20,22 +26,20 @@ DockMenuBar::DockMenuBar(QWidget* original_menu_bar, QWidget* parent)
 	layout->addWidget(menu_wrapper);
 
 	QHBoxLayout* menu_layout = new QHBoxLayout;
-	menu_layout->setContentsMargins(0, 4, 0, 4);
+	menu_layout->setContentsMargins(0, INNER_MENU_MARGIN, 0, INNER_MENU_MARGIN);
 	menu_wrapper->setLayout(menu_layout);
 
 	menu_layout->addWidget(original_menu_bar);
 
 	m_layout_switcher = new QTabBar;
 	m_layout_switcher->setContentsMargins(0, 0, 0, 0);
-	m_layout_switcher->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
+	m_layout_switcher->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
 	m_layout_switcher->setContextMenuPolicy(Qt::CustomContextMenu);
+	m_layout_switcher->setDrawBase(false);
+	m_layout_switcher->setExpanding(false);
 	m_layout_switcher->setMovable(true);
 	layout->addWidget(m_layout_switcher);
 
-	QWidget* spacer = new QWidget;
-	spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
-	layout->addWidget(spacer);
-
 	connect(m_layout_switcher, &QTabBar::tabMoved, this, [this](int from, int to) {
 		DockLayout::Index from_index = static_cast(from);
 		DockLayout::Index to_index = static_cast(to);
@@ -51,7 +55,6 @@ DockMenuBar::DockMenuBar(QWidget* original_menu_bar, QWidget* parent)
 
 	m_layout_locked_toggle = new QPushButton;
 	m_layout_locked_toggle->setCheckable(true);
-	m_layout_locked_toggle->setFlat(true);
 	connect(m_layout_locked_toggle, &QPushButton::clicked, this, [this](bool checked) {
 		if (m_ignore_lock_state_changed)
 			return;
@@ -59,6 +62,19 @@ DockMenuBar::DockMenuBar(QWidget* original_menu_bar, QWidget* parent)
 		emit lockButtonToggled(checked);
 	});
 	layout->addWidget(m_layout_locked_toggle);
+
+	updateTheme();
+}
+
+void DockMenuBar::updateTheme()
+{
+	DockMenuBarStyle* style = new DockMenuBarStyle(m_layout_switcher);
+	m_original_menu_bar->setStyle(style);
+	m_layout_switcher->setStyle(style);
+	m_layout_locked_toggle->setStyle(style);
+
+	delete m_style;
+	m_style = style;
 }
 
 void DockMenuBar::updateLayoutSwitcher(DockLayout::Index current_index, const std::vector& layouts)
@@ -166,6 +182,27 @@ void DockMenuBar::stopBlink()
 	}
 }
 
+int DockMenuBar::innerHeight() const
+{
+	return m_original_menu_bar->sizeHint().height() + INNER_MENU_MARGIN * 2;
+}
+
+void DockMenuBar::paintEvent(QPaintEvent* event)
+{
+	QPainter painter(this);
+
+	// This fixes the background colour of the menu bar when using the Windows
+	// Vista style.
+	QStyleOptionMenuItem menu_option;
+	menu_option.palette = palette();
+	menu_option.state = QStyle::State_None;
+	menu_option.menuItemType = QStyleOptionMenuItem::EmptyArea;
+	menu_option.checkType = QStyleOptionMenuItem::NotCheckable;
+	menu_option.rect = rect();
+	menu_option.menuRect = rect();
+	style()->drawControl(QStyle::CE_MenuBarEmptyArea, &menu_option, &painter, this);
+}
+
 void DockMenuBar::tabChanged(int index)
 {
 	// Prevent recursion.
@@ -182,3 +219,124 @@ void DockMenuBar::tabChanged(int index)
 		emit newButtonClicked();
 	}
 }
+
+// *****************************************************************************
+
+DockMenuBarStyle::DockMenuBarStyle(QObject* parent)
+	: QProxyStyle(QStyleFactory::create(qApp->style()->name()))
+{
+	setParent(parent);
+}
+
+void DockMenuBarStyle::drawControl(
+	ControlElement element,
+	const QStyleOption* option,
+	QPainter* painter,
+	const QWidget* widget) const
+{
+	switch (element)
+	{
+		case CE_MenuBarItem:
+		{
+			const QStyleOptionMenuItem* opt = qstyleoption_cast(option);
+			if (!opt)
+				break;
+
+			QWidget* menu_wrapper = widget->parentWidget();
+			if (!menu_wrapper)
+				break;
+
+			const DockMenuBar* menu_bar = qobject_cast(menu_wrapper->parentWidget());
+			if (!menu_bar)
+				break;
+
+			if (baseStyle()->name() != "fusion")
+				break;
+
+			// This mirrors a check in QFusionStyle::drawControl. If act is
+			// false, QFusionStyle will try to draw a border along the bottom.
+			bool act = opt->state & State_Selected && opt->state & State_Sunken;
+			if (act)
+				break;
+
+			// Extend the menu item to the bottom of the menu bar to fix the
+			// position in which it draws its bottom border. We also need to
+			// extend it up by the same amount so that the text isn't moved.
+			QStyleOptionMenuItem menu_opt = *opt;
+			int difference = (menu_bar->innerHeight() - option->rect.top()) - menu_opt.rect.height();
+			menu_opt.rect.adjust(0, -difference, 0, difference);
+			QProxyStyle::drawControl(element, &menu_opt, painter, widget);
+
+			return;
+		}
+		case CE_TabBarTab:
+		{
+			QProxyStyle::drawControl(element, option, painter, widget);
+
+			// Draw a slick-looking highlight under the currently selected tab.
+			if (baseStyle()->name() == "fusion")
+			{
+				const QStyleOptionTab* tab = qstyleoption_cast(option);
+				if (tab && (tab->state & State_Selected))
+				{
+					painter->setPen(tab->palette.highlight().color());
+					painter->drawLine(tab->rect.bottomLeft(), tab->rect.bottomRight());
+				}
+			}
+
+			return;
+		}
+		case CE_MenuBarEmptyArea:
+		{
+			// Prevent it from drawing a border in the wrong position.
+			return;
+		}
+		default:
+		{
+			break;
+		}
+	}
+
+	QProxyStyle::drawControl(element, option, painter, widget);
+}
+
+QSize DockMenuBarStyle::sizeFromContents(
+	QStyle::ContentsType type, const QStyleOption* option, const QSize& contents_size, const QWidget* widget) const
+{
+	QSize size = QProxyStyle::sizeFromContents(type, option, contents_size, widget);
+
+#ifdef Q_OS_WIN32
+	// Adjust the sizes of the layout switcher tabs depending on the theme.
+	if (type == CT_TabBarTab)
+	{
+		const QStyleOptionTab* opt = qstyleoption_cast(option);
+		if (!opt)
+			return size;
+
+		const QTabBar* tab_bar = qobject_cast(widget);
+		if (!tab_bar)
+			return size;
+
+		const DockMenuBar* menu_bar = qobject_cast(tab_bar->parentWidget());
+		if (!menu_bar)
+			return size;
+
+		if (baseStyle()->name() == "fusion" || baseStyle()->name() == "windowsvista")
+		{
+			// Make sure the tab extends to the bottom of the widget.
+			size.setHeight(menu_bar->innerHeight() - opt->rect.top());
+		}
+		else if (baseStyle()->name() == "windows11")
+		{
+			// Adjust the size of the tab such that it is vertically centred.
+			size.setHeight(menu_bar->innerHeight() - opt->rect.top() * 2 - OUTER_MENU_MARGIN);
+
+			// Make the plus button square.
+			if (opt->tabIndex + 1 == tab_bar->count())
+				size.setWidth(size.height());
+		}
+	}
+#endif
+
+	return size;
+}
diff --git a/pcsx2-qt/Debugger/Docking/DockMenuBar.h b/pcsx2-qt/Debugger/Docking/DockMenuBar.h
index 6ea77e4b5f..b9197d11fc 100644
--- a/pcsx2-qt/Debugger/Docking/DockMenuBar.h
+++ b/pcsx2-qt/Debugger/Docking/DockMenuBar.h
@@ -6,10 +6,15 @@
 #include "Debugger/Docking/DockLayout.h"
 
 #include 
+#include 
 #include 
 #include 
 #include 
 
+class DockMenuBarStyle;
+
+// The widget that replaces the normal menu bar. This contains the original menu
+// bar, the layout switcher and the layout locked/unlocked toggle button.
 class DockMenuBar : public QWidget
 {
 	Q_OBJECT
@@ -19,6 +24,8 @@ public:
 
 	void updateLayoutSwitcher(DockLayout::Index current_index, const std::vector& layouts);
 
+	void updateTheme();
+
 	// Notify the menu bar that a new layout has been selected.
 	void onCurrentLayoutChanged(DockLayout::Index current_index);
 
@@ -29,6 +36,8 @@ public:
 	void updateBlink();
 	void stopBlink();
 
+	int innerHeight() const;
+
 Q_SIGNALS:
 	void currentLayoutChanged(DockLayout::Index layout_index);
 	void newButtonClicked();
@@ -37,9 +46,13 @@ Q_SIGNALS:
 
 	void layoutSwitcherContextMenuRequested(const QPoint& pos, QTabBar* layout_switcher);
 
+protected:
+	void paintEvent(QPaintEvent* event) override;
+
 private:
 	void tabChanged(int index);
 
+	QWidget* m_original_menu_bar;
 
 	QTabBar* m_layout_switcher;
 	QMetaObject::Connection m_tab_connection;
@@ -53,4 +66,28 @@ private:
 
 	QPushButton* m_layout_locked_toggle;
 	bool m_ignore_lock_state_changed = false;
+
+	DockMenuBarStyle* m_style = nullptr;
+};
+
+// Fixes some theming issues relating to the menu bar, the layout switcher and
+// the layout locked/unlocked toggle button.
+class DockMenuBarStyle : public QProxyStyle
+{
+	Q_OBJECT
+
+public:
+	DockMenuBarStyle(QObject* parent = nullptr);
+
+	void drawControl(
+		ControlElement element,
+		const QStyleOption* option,
+		QPainter* painter,
+		const QWidget* widget = nullptr) const override;
+
+	QSize sizeFromContents(
+		QStyle::ContentsType type,
+		const QStyleOption* option,
+		const QSize& contents_size,
+		const QWidget* widget = nullptr) const override;
 };
diff --git a/pcsx2-qt/Debugger/Docking/DockViews.cpp b/pcsx2-qt/Debugger/Docking/DockViews.cpp
index 4298c6218f..f5698e11ee 100644
--- a/pcsx2-qt/Debugger/Docking/DockViews.cpp
+++ b/pcsx2-qt/Debugger/Docking/DockViews.cpp
@@ -20,6 +20,7 @@
 #include 
 #include 
 #include 
+#include 
 
 KDDockWidgets::Core::View* DockViewFactory::createDockWidget(
 	const QString& unique_name,
@@ -143,6 +144,14 @@ DockTabBar::DockTabBar(KDDockWidgets::Core::TabBar* controller, QWidget* parent)
 {
 	setContextMenuPolicy(Qt::CustomContextMenu);
 	connect(this, &DockTabBar::customContextMenuRequested, this, &DockTabBar::openContextMenu);
+
+	// The constructor of KDDockWidgets::QtWidgets::TabBar makes a QProxyStyle
+	// that ends up taking ownerhsip of the style for the entire application!
+	if (QProxyStyle* proxy_style = qobject_cast(style()))
+	{
+		proxy_style->baseStyle()->setParent(qApp);
+		proxy_style->setBaseStyle(QStyleFactory::create(qApp->style()->name()));
+	}
 }
 
 void DockTabBar::openContextMenu(QPoint pos)
diff --git a/pcsx2-qt/MainWindow.cpp b/pcsx2-qt/MainWindow.cpp
index e44a0749b7..85abf85a60 100644
--- a/pcsx2-qt/MainWindow.cpp
+++ b/pcsx2-qt/MainWindow.cpp
@@ -1785,7 +1785,7 @@ void MainWindow::updateTheme()
 	reloadThemeSpecificImages();
 
 	if (g_debugger_window)
-		g_debugger_window->updateStyleSheets();
+		g_debugger_window->updateTheme();
 }
 
 void MainWindow::reloadThemeSpecificImages()

From bf656e892f6f738adfdb554bacce2afbae2556e2 Mon Sep 17 00:00:00 2001
From: PCSX2 Bot 
Date: Fri, 4 Apr 2025 00:01:56 +0000
Subject: [PATCH 038/162] [ci skip] Qt: Update Base Translation.

---
 pcsx2-qt/Translations/pcsx2-qt_en.ts | 49 +++++++++++++++-------------
 1 file changed, 26 insertions(+), 23 deletions(-)

diff --git a/pcsx2-qt/Translations/pcsx2-qt_en.ts b/pcsx2-qt/Translations/pcsx2-qt_en.ts
index 6b6df44dcd..a385509de5 100644
--- a/pcsx2-qt/Translations/pcsx2-qt_en.ts
+++ b/pcsx2-qt/Translations/pcsx2-qt_en.ts
@@ -4984,7 +4984,7 @@ Do you want to overwrite?
     
     
         
-        
+        
         Run
         
     
@@ -5110,7 +5110,7 @@ Do you want to overwrite?
         
     
     
-        
+        
         Pause
         
     
@@ -5292,53 +5292,56 @@ Do you want to overwrite?
 
     DockManager
     
-        
+        
         No Layouts
         
     
     
-        
+        
         Add Another...
         
     
     
-        
+        
         Edit Layout
         
     
     
-        
+        
         Reset Layout
         
     
     
-        
+        
         Are you sure you want to reset layout '%1'?
         
     
     
-        
-        
+        
+        
         Confirmation
         
     
     
-        
+        
         Delete Layout
         
     
     
-        
+        
         Are you sure you want to delete layout '%1'?
         
     
+
+
+    DockMenuBar
     
-        
+        
         Layout Locked
         
     
     
-        
+        
         Layout Unlocked
         
     
@@ -5346,52 +5349,52 @@ Do you want to overwrite?
 
     DockTabBar
     
-        
+        
         Rename
         
     
     
-        
+        
         Rename Window
         
     
     
-        
+        
         New name:
         
     
     
-        
+        
         Invalid Name
         
     
     
-        
+        
         The specified name is too long.
         
     
     
-        
+        
         Reset Name
         
     
     
-        
+        
         Primary
         
     
     
-        
+        
         Set Target
         
     
     
-        
+        
         Inherit From Layout
         
     
     
-        
+        
         Close
         
     

From 4fa005ade07bb16c4783fecbf10e649842a4af1e Mon Sep 17 00:00:00 2001
From: ElTioRata 
Date: Wed, 2 Apr 2025 23:53:43 -0300
Subject: [PATCH 039/162] GameDB: resident evil 4 - HPO Native w/ Texture
 Offset

Added 'Align to Native w/ Texture Offset'
---
 bin/resources/GameIndex.yaml | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/bin/resources/GameIndex.yaml b/bin/resources/GameIndex.yaml
index 74b4d71513..1175ce9c20 100644
--- a/bin/resources/GameIndex.yaml
+++ b/bin/resources/GameIndex.yaml
@@ -23409,7 +23409,7 @@ SLES-53702:
   region: "PAL-M5"
   compat: 5
   gsHWFixes:
-    halfPixelOffset: 2 # Fixes blurriness.
+    halfPixelOffset: 5 # Fixes blurriness.
 SLES-53703:
   name: "Peter Jackson's King Kong - The Official Game of the Movie"
   name-sort: "King Kong, Peter Jackson's - The Official Game of the Movie"
@@ -23643,7 +23643,7 @@ SLES-53756:
   name: "Resident Evil 4"
   region: "PAL-M5"
   gsHWFixes:
-    halfPixelOffset: 2 # Fixes blurriness.
+    halfPixelOffset: 5 # Fixes blurriness.
 SLES-53758:
   name: "Sniper Elite [Pre-Production]"
   region: "PAL-E"
@@ -31588,7 +31588,7 @@ SLKA-25410:
   gameFixes:
     - BlitInternalFPSHack # Fixes internal FPS detection.
   gsHWFixes:
-    halfPixelOffset: 2 # Fixes blurriness.
+    halfPixelOffset: 5 # Fixes blurriness.
 SLKA-25411:
   name: "Need for Speed - ProStreet"
   region: "NTSC-K"
@@ -67457,7 +67457,7 @@ SLUS-21134:
   gameFixes:
     - BlitInternalFPSHack # Fixes internal FPS detection.
   gsHWFixes:
-    halfPixelOffset: 2 # Fixes blurriness.
+    halfPixelOffset: 5 # Fixes blurriness.
 SLUS-21135:
   name: "MVP Baseball 2005"
   region: "NTSC-U"
@@ -73016,7 +73016,7 @@ SLUS-29169:
   gameFixes:
     - BlitInternalFPSHack # Fixes internal FPS detection.
   gsHWFixes:
-    halfPixelOffset: 2 # Fixes blurriness.
+    halfPixelOffset: 5 # Fixes blurriness.
 SLUS-29170:
   name: "Total Overdose - A Gunslinger's Tale in Mexico [Demo]"
   region: "NTSC-U"

From c81a37b740b31c467f4248fc94619485a457133a Mon Sep 17 00:00:00 2001
From: Berylskid 
Date: Tue, 1 Apr 2025 00:09:48 +0900
Subject: [PATCH 040/162] GameDB: HPO update for Armored Core games

---
 bin/resources/GameIndex.yaml | 46 ++++++++++++++++--------------------
 1 file changed, 20 insertions(+), 26 deletions(-)

diff --git a/bin/resources/GameIndex.yaml b/bin/resources/GameIndex.yaml
index 1175ce9c20..9f08f40ede 100644
--- a/bin/resources/GameIndex.yaml
+++ b/bin/resources/GameIndex.yaml
@@ -1654,7 +1654,7 @@ SCAJ-20076:
   name: "Armored Core - Nexus [Disc 1]"
   region: "NTSC-Unk"
   gsHWFixes:
-    halfPixelOffset: 1 # Fixes misaligned blur.
+    halfPixelOffset: 5 # Fixes misaligned blur.
     recommendedBlendingLevel: 3 # Fixes level brightness.
   memcardFilters:
     - "SCAJ-20076"
@@ -1667,7 +1667,7 @@ SCAJ-20077:
   name: "Armored Core - Nexus [Disc 2]"
   region: "NTSC-Unk"
   gsHWFixes:
-    halfPixelOffset: 1 # Fixes misaligned blur.
+    halfPixelOffset: 5 # Fixes misaligned blur.
     recommendedBlendingLevel: 3 # Fixes level brightness.
   memcardFilters:
     - "SCAJ-20076"
@@ -1826,7 +1826,7 @@ SCAJ-20105:
   name: "Armored Core - Nine Breaker"
   region: "NTSC-Unk"
   gsHWFixes:
-    halfPixelOffset: 1 # Fixes misaligned blur.
+    halfPixelOffset: 5 # Fixes misaligned blur.
     recommendedBlendingLevel: 3 # Fixes level brightness.
     cpuSpriteRenderBW: 2 # Fixes broken water on "Upper Sea" level.
     cpuSpriteRenderLevel: 2 # Needed for above.
@@ -1941,9 +1941,7 @@ SCAJ-20121:
   name: "Armored Core - Formula Front"
   region: "NTSC-Unk"
   gsHWFixes:
-    halfPixelOffset: 1 # Fixes misaligned blur.
-    cpuSpriteRenderBW: 1 # Fixes broken shadow caused by HPO 1.
-    cpuSpriteRenderLevel: 2 # Needed for above.
+    halfPixelOffset: 5 # Fixes misaligned blur.
 SCAJ-20122:
   name: "Swords of Destiny"
   region: "NTSC-Unk"
@@ -7203,7 +7201,7 @@ SCKA-20047:
   name: "Armored Core - Nine Breaker"
   region: "NTSC-K"
   gsHWFixes:
-    halfPixelOffset: 1 # Fixes misaligned blur.
+    halfPixelOffset: 5 # Fixes misaligned blur.
     recommendedBlendingLevel: 3 # Fixes level brightness.
     cpuSpriteRenderBW: 2 # Fixes broken water on "Upper Sea" level.
     cpuSpriteRenderLevel: 2 # Needed for above.
@@ -23809,7 +23807,7 @@ SLES-53819:
   name: "Armored Core - Nine Breaker"
   region: "PAL-E"
   gsHWFixes:
-    halfPixelOffset: 1 # Fixes misaligned blur.
+    halfPixelOffset: 5 # Fixes misaligned blur.
     recommendedBlendingLevel: 3 # Fixes level brightness.
     cpuSpriteRenderBW: 2 # Fixes broken water on "Upper Sea" level.
     cpuSpriteRenderLevel: 2 # Needed for above.
@@ -29509,7 +29507,7 @@ SLES-82036:
   name: "Armored Core - Nexus [Disc 1]"
   region: "PAL-M5"
   gsHWFixes:
-    halfPixelOffset: 1 # Fixes misaligned blur.
+    halfPixelOffset: 5 # Fixes misaligned blur.
     recommendedBlendingLevel: 3 # Fixes level brightness.
   memcardFilters:
     - "SLES-82036"
@@ -29518,7 +29516,7 @@ SLES-82037:
   name: "Armored Core - Nexus [Disc 2]"
   region: "PAL-M5"
   gsHWFixes:
-    halfPixelOffset: 1 # Fixes misaligned blur.
+    halfPixelOffset: 5 # Fixes misaligned blur.
     recommendedBlendingLevel: 3 # Fixes level brightness.
   memcardFilters:
     - "SLES-82036"
@@ -30598,7 +30596,7 @@ SLKA-25201:
   name: "Armored Core - Nexus [Disc 1]"
   region: "NTSC-K"
   gsHWFixes:
-    halfPixelOffset: 1 # Fixes misaligned blur.
+    halfPixelOffset: 5 # Fixes misaligned blur.
     recommendedBlendingLevel: 3 # Fixes level brightness.
   memcardFilters:
     - "SLKA-25201"
@@ -30607,7 +30605,7 @@ SLKA-25202:
   name: "Armored Core - Nexus [Disc 2]"
   region: "NTSC-K"
   gsHWFixes:
-    halfPixelOffset: 1 # Fixes misaligned blur.
+    halfPixelOffset: 5 # Fixes misaligned blur.
     recommendedBlendingLevel: 3 # Fixes level brightness.
   memcardFilters:
     - "SLKA-25201"
@@ -30925,9 +30923,7 @@ SLKA-25270:
   name: "Armored Core - Formula Front"
   region: "NTSC-K"
   gsHWFixes:
-    halfPixelOffset: 1 # Fixes misaligned blur.
-    cpuSpriteRenderBW: 1 # Fixes broken shadow caused by HPO 1.
-    cpuSpriteRenderLevel: 2 # Needed for above.
+    halfPixelOffset: 5 # Fixes misaligned blur.
 SLKA-25271:
   name: "Harry Potter and the Order of the Phoenix"
   region: "NTSC-K"
@@ -56797,7 +56793,7 @@ SLPS-25338:
   name-en: "Armored Core - Nexus [Disc 1]"
   region: "NTSC-J"
   gsHWFixes:
-    halfPixelOffset: 1 # Fixes misaligned blur.
+    halfPixelOffset: 5 # Fixes misaligned blur.
     recommendedBlendingLevel: 3 # Fixes level brightness.
   memcardFilters:
     - "SCAJ-20076"
@@ -56812,7 +56808,7 @@ SLPS-25339:
   name-en: "Armored Core - Nexus [Disc 2]"
   region: "NTSC-J"
   gsHWFixes:
-    halfPixelOffset: 1 # Fixes misaligned blur.
+    halfPixelOffset: 5 # Fixes misaligned blur.
     recommendedBlendingLevel: 3 # Fixes level brightness.
   memcardFilters:
     - "SCAJ-20076"
@@ -57265,7 +57261,7 @@ SLPS-25408:
   name-en: "Armored Core - Nine Breaker"
   region: "NTSC-J"
   gsHWFixes:
-    halfPixelOffset: 1 # Fixes misaligned blur.
+    halfPixelOffset: 5 # Fixes misaligned blur.
     recommendedBlendingLevel: 3 # Fixes level brightness.
     cpuSpriteRenderBW: 2 # Fixes broken water on "Upper Sea" level.
     cpuSpriteRenderLevel: 2 # Needed for above.
@@ -57590,9 +57586,7 @@ SLPS-25461:
   name-en: "Armored Core - Formula Front"
   region: "NTSC-J"
   gsHWFixes:
-    halfPixelOffset: 1 # Fixes misaligned blur.
-    cpuSpriteRenderBW: 1 # Fixes broken shadow caused by HPO 1.
-    cpuSpriteRenderLevel: 2 # Needed for above.
+    halfPixelOffset: 5 # Fixes misaligned blur.
 SLPS-25462:
   name: "アーマード・コア ラストレイヴン"
   name-sort: "あーまーどこあ らすとれいゔん"
@@ -60857,7 +60851,7 @@ SLPS-73202:
   name-en: "Armored Core - Nexus [Disc 1] [PlayStation2 the Best]"
   region: "NTSC-J"
   gsHWFixes:
-    halfPixelOffset: 1 # Fixes misaligned blur.
+    halfPixelOffset: 5 # Fixes misaligned blur.
     recommendedBlendingLevel: 3 # Fixes level brightness.
   memcardFilters:
     - "SCAJ-20076"
@@ -60872,7 +60866,7 @@ SLPS-73203:
   name-en: "Armored Core - Nexus [Disc 2] [PlayStation2 the Best]"
   region: "NTSC-J"
   gsHWFixes:
-    halfPixelOffset: 1 # Fixes misaligned blur.
+    halfPixelOffset: 5 # Fixes misaligned blur.
     recommendedBlendingLevel: 3 # Fixes level brightness.
   memcardFilters:
     - "SCAJ-20076"
@@ -66576,7 +66570,7 @@ SLUS-20986:
   region: "NTSC-U"
   compat: 5
   gsHWFixes:
-    halfPixelOffset: 1 # Fixes misaligned blur.
+    halfPixelOffset: 5 # Fixes misaligned blur.
     recommendedBlendingLevel: 3 # Fixes level brightness.
   memcardFilters:
     - "SLUS-20986"
@@ -67150,7 +67144,7 @@ SLUS-21079:
   name: "Armored Core - Nexus [Disc 2]"
   region: "NTSC-U"
   gsHWFixes:
-    halfPixelOffset: 1 # Fixes misaligned blur.
+    halfPixelOffset: 5 # Fixes misaligned blur.
     recommendedBlendingLevel: 3 # Fixes level brightness.
   memcardFilters:
     - "SLUS-20986"
@@ -67828,7 +67822,7 @@ SLUS-21200:
   region: "NTSC-U"
   compat: 5
   gsHWFixes:
-    halfPixelOffset: 1 # Fixes misaligned blur.
+    halfPixelOffset: 5 # Fixes misaligned blur.
     recommendedBlendingLevel: 3 # Fixes level brightness.
     cpuSpriteRenderBW: 2 # Fixes broken water on "Upper Sea" level.
     cpuSpriteRenderLevel: 2 # Needed for above.

From 3d42da3e974d44dbb0aa6273bd0e7be4033b22e4 Mon Sep 17 00:00:00 2001
From: JimScript <66685584+JimScript@users.noreply.github.com>
Date: Fri, 4 Apr 2025 06:27:57 -0700
Subject: [PATCH 041/162] GameDB: R&C Size Matters HPO Native with Texture
 Offset

---
 bin/resources/GameIndex.yaml | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/bin/resources/GameIndex.yaml b/bin/resources/GameIndex.yaml
index 9f08f40ede..2bb0fe16b1 100644
--- a/bin/resources/GameIndex.yaml
+++ b/bin/resources/GameIndex.yaml
@@ -7651,6 +7651,7 @@ SCKA-20120:
   gsHWFixes:
     recommendedBlendingLevel: 4 # Fixes vendor and other bloom.
     autoFlush: 2 # Fixes missing bloom and shadow definition.
+    halfPixelOffset: 5 # Fixes misaligned bloom. 
 SCKA-20124:
   name: "Piposarugetchu 3" # Ape Escape 3
   region: "NTSC-K"
@@ -12434,6 +12435,7 @@ SCUS-97615:
   gsHWFixes:
     recommendedBlendingLevel: 4 # Fixes vendor and other bloom.
     autoFlush: 2 # Fixes missing bloom and shadow definition.
+    halfPixelOffset: 5 # Fixes misaligned bloom. 
 SCUS-97616:
   name: "SingStar '80s"
   region: "NTSC-U"
@@ -27628,6 +27630,7 @@ SLES-55019:
   gsHWFixes:
     recommendedBlendingLevel: 4 # Fixes vendor and other bloom.
     autoFlush: 2 # Fixes missing bloom and shadow definition.
+    halfPixelOffset: 5 # Fixes misaligned bloom. 
 SLES-55020:
   name: "Die Simpsons - Das Spiel"
   region: "PAL-G"

From fcde7fdb80e19749678ba277a184cd18d2dc83e3 Mon Sep 17 00:00:00 2001
From: JimScript <66685584+JimScript@users.noreply.github.com>
Date: Fri, 4 Apr 2025 07:44:32 -0700
Subject: [PATCH 042/162] GameDB: R&C Size Matters HPO Native with Texture
 Offset

---
 bin/resources/GameIndex.yaml | 16 ++++++++++++----
 1 file changed, 12 insertions(+), 4 deletions(-)

diff --git a/bin/resources/GameIndex.yaml b/bin/resources/GameIndex.yaml
index 2bb0fe16b1..83ad779031 100644
--- a/bin/resources/GameIndex.yaml
+++ b/bin/resources/GameIndex.yaml
@@ -6512,7 +6512,12 @@ SCES-54941:
   region: "PAL-E"
 SCES-55019:
   name: "Ratchet & Clank - Size Matters"
-  region: "PAL-M5"
+  region: "PAL-M13"
+  gsHWFixes:
+    recommendedBlendingLevel: 4 # Fixes vendor and other bloom.
+    autoFlush: 2 # Fixes missing bloom and shadow definition.
+    halfPixelOffset: 5 # Fixes misaligned bloom.
+    nativeScaling: 2 # Fixes pixelated bloom.
 SCES-55038:
   name: "EyeToy Play - Hero"
   region: "PAL-M15"
@@ -7651,7 +7656,8 @@ SCKA-20120:
   gsHWFixes:
     recommendedBlendingLevel: 4 # Fixes vendor and other bloom.
     autoFlush: 2 # Fixes missing bloom and shadow definition.
-    halfPixelOffset: 5 # Fixes misaligned bloom. 
+    halfPixelOffset: 5 # Fixes misaligned bloom.
+    nativeScaling: 2 # Fixes pixelated bloom.
 SCKA-20124:
   name: "Piposarugetchu 3" # Ape Escape 3
   region: "NTSC-K"
@@ -12435,7 +12441,8 @@ SCUS-97615:
   gsHWFixes:
     recommendedBlendingLevel: 4 # Fixes vendor and other bloom.
     autoFlush: 2 # Fixes missing bloom and shadow definition.
-    halfPixelOffset: 5 # Fixes misaligned bloom. 
+    halfPixelOffset: 5 # Fixes misaligned bloom.
+    nativeScaling: 2 # Fixes pixelated bloom.
 SCUS-97616:
   name: "SingStar '80s"
   region: "NTSC-U"
@@ -27630,7 +27637,8 @@ SLES-55019:
   gsHWFixes:
     recommendedBlendingLevel: 4 # Fixes vendor and other bloom.
     autoFlush: 2 # Fixes missing bloom and shadow definition.
-    halfPixelOffset: 5 # Fixes misaligned bloom. 
+    halfPixelOffset: 5 # Fixes misaligned bloom.
+    nativeScaling: 2 # Fixes pixelated bloom.
 SLES-55020:
   name: "Die Simpsons - Das Spiel"
   region: "PAL-G"

From ee8335e5f1159223120303b6be75987bc8f3dbd0 Mon Sep 17 00:00:00 2001
From: chaoticgd <43898262+chaoticgd@users.noreply.github.com>
Date: Fri, 28 Mar 2025 21:19:37 +0000
Subject: [PATCH 043/162] Debugger: Prevent blinking animation when stepping

---
 pcsx2-qt/Debugger/DebuggerWindow.cpp | 20 +++++++++++++-------
 pcsx2/DebugTools/Breakpoints.cpp     |  9 ++++++++-
 pcsx2/DebugTools/Breakpoints.h       | 25 +++++++------------------
 3 files changed, 28 insertions(+), 26 deletions(-)

diff --git a/pcsx2-qt/Debugger/DebuggerWindow.cpp b/pcsx2-qt/Debugger/DebuggerWindow.cpp
index 22913f93bf..bba216eaf2 100644
--- a/pcsx2-qt/Debugger/DebuggerWindow.cpp
+++ b/pcsx2-qt/Debugger/DebuggerWindow.cpp
@@ -307,12 +307,18 @@ void DebuggerWindow::onVMPaused()
 	m_ui.actionStepOver->setEnabled(true);
 	m_ui.actionStepOut->setEnabled(true);
 
-	// Switch to the CPU tab that triggered the breakpoint.
-	// Also blink the tab text to indicate that a breakpoint was triggered.
 	if (CBreakPoints::GetBreakpointTriggered())
 	{
-		const BreakPointCpu triggeredCpu = CBreakPoints::GetBreakpointTriggeredCpu();
-		m_dock_manager->switchToLayoutWithCPU(triggeredCpu, true);
+		// Select a layout tab corresponding to the CPU that triggered the
+		// breakpoint and make it start blinking unless said breakpoint was
+		// generated as a result of stepping.
+		const BreakPointCpu cpu_type = CBreakPoints::GetBreakpointTriggeredCpu();
+		if (cpu_type == BREAKPOINT_EE || cpu_type == BREAKPOINT_IOP)
+		{
+			DebugInterface& cpu = DebugInterface::get(cpu_type);
+			bool blink_tab = !CBreakPoints::IsSteppingBreakPoint(cpu_type, cpu.getPC());
+			m_dock_manager->switchToLayoutWithCPU(cpu_type, blink_tab);
+		}
 
 		Host::RunOnCPUThread([] {
 			CBreakPoints::ClearTemporaryBreakPoints();
@@ -418,7 +424,7 @@ void DebuggerWindow::onStepInto()
 		bpAddr = info.branchTarget; // Syscalls are always taken
 
 	Host::RunOnCPUThread([cpu, bpAddr] {
-		CBreakPoints::AddBreakPoint(cpu->getCpuType(), bpAddr, true);
+		CBreakPoints::AddBreakPoint(cpu->getCpuType(), bpAddr, true, true, true);
 		cpu->resumeCpu();
 	});
 
@@ -468,7 +474,7 @@ void DebuggerWindow::onStepOver()
 	}
 
 	Host::RunOnCPUThread([cpu, bpAddr] {
-		CBreakPoints::AddBreakPoint(cpu->getCpuType(), bpAddr, true);
+		CBreakPoints::AddBreakPoint(cpu->getCpuType(), bpAddr, true, true, true);
 		cpu->resumeCpu();
 	});
 
@@ -509,7 +515,7 @@ void DebuggerWindow::onStepOut()
 	u32 breakpoint_pc = stack_frames.at(1).pc;
 
 	Host::RunOnCPUThread([cpu, breakpoint_pc] {
-		CBreakPoints::AddBreakPoint(cpu->getCpuType(), breakpoint_pc, true);
+		CBreakPoints::AddBreakPoint(cpu->getCpuType(), breakpoint_pc, true, true, true);
 		cpu->resumeCpu();
 	});
 
diff --git a/pcsx2/DebugTools/Breakpoints.cpp b/pcsx2/DebugTools/Breakpoints.cpp
index 0e2cc3b287..8bc670e294 100644
--- a/pcsx2/DebugTools/Breakpoints.cpp
+++ b/pcsx2/DebugTools/Breakpoints.cpp
@@ -172,7 +172,13 @@ bool CBreakPoints::IsTempBreakPoint(BreakPointCpu cpu, u32 addr)
 	return bp != INVALID_BREAKPOINT;
 }
 
-void CBreakPoints::AddBreakPoint(BreakPointCpu cpu, u32 addr, bool temp, bool enabled)
+bool CBreakPoints::IsSteppingBreakPoint(BreakPointCpu cpu, u32 addr)
+{
+	const size_t bp = FindBreakpoint(cpu, addr, true, true);
+	return bp != INVALID_BREAKPOINT && breakPoints_[bp].stepping;
+}
+
+void CBreakPoints::AddBreakPoint(BreakPointCpu cpu, u32 addr, bool temp, bool enabled, bool stepping)
 {
 	const size_t bp = FindBreakpoint(cpu, addr, true, temp);
 	if (bp == INVALID_BREAKPOINT)
@@ -180,6 +186,7 @@ void CBreakPoints::AddBreakPoint(BreakPointCpu cpu, u32 addr, bool temp, bool en
 		BreakPoint pt;
 		pt.enabled = enabled;
 		pt.temporary = temp;
+		pt.stepping = stepping;
 		pt.addr = addr;
 		pt.cpu = cpu;
 
diff --git a/pcsx2/DebugTools/Breakpoints.h b/pcsx2/DebugTools/Breakpoints.h
index 546df37847..fd100b4cc7 100644
--- a/pcsx2/DebugTools/Breakpoints.h
+++ b/pcsx2/DebugTools/Breakpoints.h
@@ -12,15 +12,10 @@
 
 struct BreakPointCond
 {
-	DebugInterface* debug;
+	DebugInterface* debug = nullptr;
 	PostfixExpression expression;
 	std::string expressionString;
 
-	BreakPointCond()
-		: debug(NULL)
-	{
-	}
-
 	u32 Evaluate()
 	{
 		u64 result;
@@ -33,17 +28,10 @@ struct BreakPointCond
 
 struct BreakPoint
 {
-	BreakPoint()
-		: addr(0)
-		, enabled(false)
-		, temporary(false)
-		, hasCond(false)
-	{
-	}
-
-	u32 addr;
-	bool enabled;
-	bool temporary;
+	u32 addr = 0;
+	bool enabled = false;
+	bool temporary = false;
+	bool stepping = false;
 
 	bool hasCond;
 	BreakPointCond cond;
@@ -120,7 +108,8 @@ public:
 	static bool IsAddressBreakPoint(BreakPointCpu cpu, u32 addr);
 	static bool IsAddressBreakPoint(BreakPointCpu cpu, u32 addr, bool* enabled);
 	static bool IsTempBreakPoint(BreakPointCpu cpu, u32 addr);
-	static void AddBreakPoint(BreakPointCpu cpu, u32 addr, bool temp = false, bool enabled = true);
+	static bool IsSteppingBreakPoint(BreakPointCpu cpu, u32 addr);
+	static void AddBreakPoint(BreakPointCpu cpu, u32 addr, bool temp = false, bool enabled = true, bool stepping = false);
 	static void RemoveBreakPoint(BreakPointCpu cpu, u32 addr);
 	static void ChangeBreakPoint(BreakPointCpu cpu, u32 addr, bool enable);
 	static void ClearAllBreakPoints();

From 4b88a290c434b4826c6486b03db1c674a5e737e8 Mon Sep 17 00:00:00 2001
From: chaoticgd <43898262+chaoticgd@users.noreply.github.com>
Date: Thu, 27 Mar 2025 21:43:26 +0000
Subject: [PATCH 044/162] Qt: Destroy settings save timer before quitting

---
 pcsx2-qt/QtHost.cpp | 19 +++++++++++++++----
 1 file changed, 15 insertions(+), 4 deletions(-)

diff --git a/pcsx2-qt/QtHost.cpp b/pcsx2-qt/QtHost.cpp
index 1aca34a89f..62392c62b6 100644
--- a/pcsx2-qt/QtHost.cpp
+++ b/pcsx2-qt/QtHost.cpp
@@ -84,7 +84,7 @@ namespace QtHost
 //////////////////////////////////////////////////////////////////////////
 // Local variable declarations
 //////////////////////////////////////////////////////////////////////////
-static std::unique_ptr s_settings_save_timer;
+static QTimer* s_settings_save_timer = nullptr;
 static std::unique_ptr s_base_settings_interface;
 static bool s_batch_mode = false;
 static bool s_nogui_mode = false;
@@ -1404,7 +1404,7 @@ void QtHost::SaveSettings()
 	if (s_settings_save_timer)
 	{
 		s_settings_save_timer->deleteLater();
-		s_settings_save_timer.release();
+		s_settings_save_timer = nullptr;
 	}
 }
 
@@ -1420,10 +1420,21 @@ void Host::CommitBaseSettingChanges()
 	if (s_settings_save_timer)
 		return;
 
-	s_settings_save_timer = std::make_unique();
-	s_settings_save_timer->connect(s_settings_save_timer.get(), &QTimer::timeout, &QtHost::SaveSettings);
+	s_settings_save_timer = new QTimer;
+	s_settings_save_timer->connect(s_settings_save_timer, &QTimer::timeout, &QtHost::SaveSettings);
 	s_settings_save_timer->setSingleShot(true);
 	s_settings_save_timer->start(SETTINGS_SAVE_DELAY);
+
+	static bool connected = false;
+	if (!connected)
+	{
+		QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, []() {
+			delete s_settings_save_timer;
+			s_settings_save_timer = nullptr;
+		});
+
+		connected = true;
+	}
 }
 
 bool QtHost::InBatchMode()

From 9885e6196218f2880533ebbfc0a2b58c9fe1a9c6 Mon Sep 17 00:00:00 2001
From: PCSX2 Bot 
Date: Sat, 5 Apr 2025 00:02:22 +0000
Subject: [PATCH 045/162] [ci skip] Qt: Update Base Translation.

---
 pcsx2-qt/Translations/pcsx2-qt_en.ts | 36 ++++++++++++++--------------
 1 file changed, 18 insertions(+), 18 deletions(-)

diff --git a/pcsx2-qt/Translations/pcsx2-qt_en.ts b/pcsx2-qt/Translations/pcsx2-qt_en.ts
index a385509de5..8e6154fd70 100644
--- a/pcsx2-qt/Translations/pcsx2-qt_en.ts
+++ b/pcsx2-qt/Translations/pcsx2-qt_en.ts
@@ -5110,7 +5110,7 @@ Do you want to overwrite?
         
     
     
-        
+        
         Pause
         
     
@@ -5471,35 +5471,35 @@ Do you want to overwrite?
         
     
     
-        
-        
-        
-        
+        
+        
+        
+        
         Error
         
     
     
-        
+        
         Failed to create HTTPDownloader.
         
     
     
-        
+        
         Downloading %1...
         
     
     
-        
+        
         Download failed with HTTP status code %1.
         
     
     
-        
+        
         Download failed: Data is empty.
         
     
     
-        
+        
         Failed to write '%1'.
         
     
@@ -18948,40 +18948,40 @@ Ejecting {3} and replacing it with {2}.
         
     
     
-        
-        
+        
+        
         Error
         
     
     
-        
+        
         An error occurred while deleting empty game settings:
 {}
         
     
     
-        
+        
         An error occurred while saving game settings:
 {}
         
     
     
-        
+        
         Controller {} connected.
         
     
     
-        
+        
         System paused because controller {} was disconnected.
         
     
     
-        
+        
         Controller {} disconnected.
         
     
     
-        
+        
         Cancel
         
     

From 494cceff731ad6eaf309220426e2204fc17a852e Mon Sep 17 00:00:00 2001
From: chaoticgd <43898262+chaoticgd@users.noreply.github.com>
Date: Fri, 4 Apr 2025 16:34:24 +0100
Subject: [PATCH 046/162] Deps: Update KDDockWidgets to 2.2.3

---
 .github/workflows/scripts/linux/build-dependencies-qt.sh      | 4 ++--
 .../scripts/linux/flatpak/modules/23-kddockwidgets.json       | 4 ++--
 .../workflows/scripts/macos/build-dependencies-universal.sh   | 4 ++--
 .github/workflows/scripts/macos/build-dependencies.sh         | 4 ++--
 .../workflows/scripts/windows/build-dependencies-arm64.bat    | 4 ++--
 .github/workflows/scripts/windows/build-dependencies.bat      | 4 ++--
 6 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/.github/workflows/scripts/linux/build-dependencies-qt.sh b/.github/workflows/scripts/linux/build-dependencies-qt.sh
index 39b6ca88fd..d551433899 100755
--- a/.github/workflows/scripts/linux/build-dependencies-qt.sh
+++ b/.github/workflows/scripts/linux/build-dependencies-qt.sh
@@ -22,7 +22,7 @@ LZ4=b8fd2d15309dd4e605070bd4486e26b6ef814e29
 SDL=SDL3-3.2.10
 QT=6.8.2
 ZSTD=1.5.7
-KDDOCKWIDGETS=2.2.1
+KDDOCKWIDGETS=2.2.3
 
 SHADERC=2024.1
 SHADERC_GLSLANG=142052fa30f9eca191aa9dcf65359fcaed09eeec
@@ -50,7 +50,7 @@ eb3b5f0c16313d34f208d90c2fa1e588a23283eed63b101edd5422be6165d528  shaderc-$SHADE
 aa27e4454ce631c5a17924ce0624eac736da19fc6f5a2ab15a6c58da7b36950f  shaderc-glslang-$SHADERC_GLSLANG.tar.gz
 5d866ce34a4b6908e262e5ebfffc0a5e11dd411640b5f24c85a80ad44c0d4697  shaderc-spirv-headers-$SHADERC_SPIRVHEADERS.tar.gz
 03ee1a2c06f3b61008478f4abe9423454e53e580b9488b47c8071547c6a9db47  shaderc-spirv-tools-$SHADERC_SPIRVTOOLS.tar.gz
-8693e06abee0c88517d8480b22647702a51a0708f3c876ed5385d9a4e356e1a5  KDDockWidgets-$KDDOCKWIDGETS.tar.gz
+b8529755b2d54205341766ae168e83177c6120660539f9afba71af6bca4b81ec  KDDockWidgets-$KDDOCKWIDGETS.tar.gz
 EOF
 
 curl -L \
diff --git a/.github/workflows/scripts/linux/flatpak/modules/23-kddockwidgets.json b/.github/workflows/scripts/linux/flatpak/modules/23-kddockwidgets.json
index f10cb70050..a37d141280 100644
--- a/.github/workflows/scripts/linux/flatpak/modules/23-kddockwidgets.json
+++ b/.github/workflows/scripts/linux/flatpak/modules/23-kddockwidgets.json
@@ -14,8 +14,8 @@
     {
       "type": "git",
       "url": "https://github.com/KDAB/KDDockWidgets.git",
-      "tag": "v2.2.1",
-      "commit": "3aaccddc00a11a643e0959a24677838993de15ac",
+      "tag": "v2.2.3",
+      "commit": "28d16d0431d7cdc9f36cb619d22621146fdfab44",
       "disable-submodules": true
     },
     {
diff --git a/.github/workflows/scripts/macos/build-dependencies-universal.sh b/.github/workflows/scripts/macos/build-dependencies-universal.sh
index eb402f45ca..8b92e2c719 100755
--- a/.github/workflows/scripts/macos/build-dependencies-universal.sh
+++ b/.github/workflows/scripts/macos/build-dependencies-universal.sh
@@ -49,7 +49,7 @@ LIBWEBP=1.5.0
 FFMPEG=6.0
 MOLTENVK=1.2.9
 QT=6.7.3
-KDDOCKWIDGETS=2.2.1
+KDDOCKWIDGETS=2.2.3
 
 SHADERC=2024.1
 SHADERC_GLSLANG=142052fa30f9eca191aa9dcf65359fcaed09eeec
@@ -94,7 +94,7 @@ eb3b5f0c16313d34f208d90c2fa1e588a23283eed63b101edd5422be6165d528  shaderc-$SHADE
 aa27e4454ce631c5a17924ce0624eac736da19fc6f5a2ab15a6c58da7b36950f  shaderc-glslang-$SHADERC_GLSLANG.tar.gz
 5d866ce34a4b6908e262e5ebfffc0a5e11dd411640b5f24c85a80ad44c0d4697  shaderc-spirv-headers-$SHADERC_SPIRVHEADERS.tar.gz
 03ee1a2c06f3b61008478f4abe9423454e53e580b9488b47c8071547c6a9db47  shaderc-spirv-tools-$SHADERC_SPIRVTOOLS.tar.gz
-8693e06abee0c88517d8480b22647702a51a0708f3c876ed5385d9a4e356e1a5  KDDockWidgets-$KDDOCKWIDGETS.tar.gz
+b8529755b2d54205341766ae168e83177c6120660539f9afba71af6bca4b81ec  KDDockWidgets-$KDDOCKWIDGETS.tar.gz
 EOF
 
 curl -C - -L \
diff --git a/.github/workflows/scripts/macos/build-dependencies.sh b/.github/workflows/scripts/macos/build-dependencies.sh
index 10bed8c4d8..dfce1c841d 100755
--- a/.github/workflows/scripts/macos/build-dependencies.sh
+++ b/.github/workflows/scripts/macos/build-dependencies.sh
@@ -31,7 +31,7 @@ LIBWEBP=1.5.0
 FFMPEG=6.0
 MOLTENVK=1.2.9
 QT=6.7.3
-KDDOCKWIDGETS=2.2.1
+KDDOCKWIDGETS=2.2.3
 
 SHADERC=2024.1
 SHADERC_GLSLANG=142052fa30f9eca191aa9dcf65359fcaed09eeec
@@ -74,7 +74,7 @@ eb3b5f0c16313d34f208d90c2fa1e588a23283eed63b101edd5422be6165d528  shaderc-$SHADE
 aa27e4454ce631c5a17924ce0624eac736da19fc6f5a2ab15a6c58da7b36950f  shaderc-glslang-$SHADERC_GLSLANG.tar.gz
 5d866ce34a4b6908e262e5ebfffc0a5e11dd411640b5f24c85a80ad44c0d4697  shaderc-spirv-headers-$SHADERC_SPIRVHEADERS.tar.gz
 03ee1a2c06f3b61008478f4abe9423454e53e580b9488b47c8071547c6a9db47  shaderc-spirv-tools-$SHADERC_SPIRVTOOLS.tar.gz
-8693e06abee0c88517d8480b22647702a51a0708f3c876ed5385d9a4e356e1a5  KDDockWidgets-$KDDOCKWIDGETS.tar.gz
+b8529755b2d54205341766ae168e83177c6120660539f9afba71af6bca4b81ec  KDDockWidgets-$KDDOCKWIDGETS.tar.gz
 EOF
 
 curl -L \
diff --git a/.github/workflows/scripts/windows/build-dependencies-arm64.bat b/.github/workflows/scripts/windows/build-dependencies-arm64.bat
index 578c1a9014..6dacbdd6a0 100644
--- a/.github/workflows/scripts/windows/build-dependencies-arm64.bat
+++ b/.github/workflows/scripts/windows/build-dependencies-arm64.bat
@@ -54,7 +54,7 @@ set WEBP=1.5.0
 set ZLIB=1.3.1
 set ZLIBSHORT=131
 set ZSTD=1.5.7
-set KDDOCKWIDGETS=2.2.1
+set KDDOCKWIDGETS=2.2.3
 
 set SHADERC=2024.1
 set SHADERC_GLSLANG=142052fa30f9eca191aa9dcf65359fcaed09eeec
@@ -75,7 +75,7 @@ call :downloadfile "qttools-everywhere-src-%QT%.zip" "https://download.qt.io/off
 call :downloadfile "qttranslations-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qttranslations-everywhere-src-%QT%.zip" 33ccac9f99a357ffd83cb2d7179a0c0ffcba85a14d23d86619d5dc9721ded42f || goto error
 call :downloadfile "zlib%ZLIBSHORT%.zip" "https://zlib.net/zlib%ZLIBSHORT%.zip" 72af66d44fcc14c22013b46b814d5d2514673dda3d115e64b690c1ad636e7b17 || goto error
 call :downloadfile "zstd-%ZSTD%.zip" "https://github.com/facebook/zstd/archive/refs/tags/v%ZSTD%.zip" 7897bc5d620580d9b7cd3539c44b59d78f3657d33663fe97a145e07b4ebd69a4 || goto error
-call :downloadfile "KDDockWidgets-%KDDOCKWIDGETS%.zip" "https://github.com/KDAB/KDDockWidgets/archive/v2.2.1.zip" 78b5e242bf47476e150175b7de934ab84069459e151beb2d5ce84fd067138aa5 || goto error
+call :downloadfile "KDDockWidgets-%KDDOCKWIDGETS%.zip" "https://github.com/KDAB/KDDockWidgets/archive/v%KDDOCKWIDGETS%.zip" 1ba8e5b48f3b4d47d2de7121529d448532200fa36d9ed21f93909f6eb03f61cb || goto error
 
 call :downloadfile "shaderc-%SHADERC%.zip" "https://github.com/google/shaderc/archive/refs/tags/v%SHADERC%.zip" 6c9f42ed6bf42750f5369b089909abfdcf0101488b4a1f41116d5159d00af8e7 || goto error
 call :downloadfile "shaderc-glslang-%SHADERC_GLSLANG%.zip" "https://github.com/KhronosGroup/glslang/archive/%SHADERC_GLSLANG%.zip" 03ad8a6fa987af4653d0cfe6bdaed41bcf617f1366a151fb1574da75950cd3e8 || goto error
diff --git a/.github/workflows/scripts/windows/build-dependencies.bat b/.github/workflows/scripts/windows/build-dependencies.bat
index cffb572304..08036f3c55 100644
--- a/.github/workflows/scripts/windows/build-dependencies.bat
+++ b/.github/workflows/scripts/windows/build-dependencies.bat
@@ -52,7 +52,7 @@ set WEBP=1.5.0
 set ZLIB=1.3.1
 set ZLIBSHORT=131
 set ZSTD=1.5.7
-set KDDOCKWIDGETS=2.2.1
+set KDDOCKWIDGETS=2.2.3
 
 set SHADERC=2024.1
 set SHADERC_GLSLANG=142052fa30f9eca191aa9dcf65359fcaed09eeec
@@ -73,7 +73,7 @@ call :downloadfile "qttools-everywhere-src-%QT%.zip" "https://download.qt.io/off
 call :downloadfile "qttranslations-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qttranslations-everywhere-src-%QT%.zip" 33ccac9f99a357ffd83cb2d7179a0c0ffcba85a14d23d86619d5dc9721ded42f || goto error
 call :downloadfile "zlib%ZLIBSHORT%.zip" "https://zlib.net/zlib%ZLIBSHORT%.zip" 72af66d44fcc14c22013b46b814d5d2514673dda3d115e64b690c1ad636e7b17 || goto error
 call :downloadfile "zstd-%ZSTD%.zip" "https://github.com/facebook/zstd/archive/refs/tags/v%ZSTD%.zip" 7897bc5d620580d9b7cd3539c44b59d78f3657d33663fe97a145e07b4ebd69a4 || goto error
-call :downloadfile "KDDockWidgets-%KDDOCKWIDGETS%.zip" "https://github.com/KDAB/KDDockWidgets/archive/v2.2.1.zip" 78b5e242bf47476e150175b7de934ab84069459e151beb2d5ce84fd067138aa5 || goto error
+call :downloadfile "KDDockWidgets-%KDDOCKWIDGETS%.zip" "https://github.com/KDAB/KDDockWidgets/archive/v%KDDOCKWIDGETS%.zip" 1ba8e5b48f3b4d47d2de7121529d448532200fa36d9ed21f93909f6eb03f61cb || goto error
 
 call :downloadfile "shaderc-%SHADERC%.zip" "https://github.com/google/shaderc/archive/refs/tags/v%SHADERC%.zip" 6c9f42ed6bf42750f5369b089909abfdcf0101488b4a1f41116d5159d00af8e7 || goto error
 call :downloadfile "shaderc-glslang-%SHADERC_GLSLANG%.zip" "https://github.com/KhronosGroup/glslang/archive/%SHADERC_GLSLANG%.zip" 03ad8a6fa987af4653d0cfe6bdaed41bcf617f1366a151fb1574da75950cd3e8 || goto error

From 50d258fae962221de8d3c00cde94510f2f793cfd Mon Sep 17 00:00:00 2001
From: JordanTheToaster 
Date: Wed, 2 Apr 2025 08:08:57 +0100
Subject: [PATCH 047/162] Deps: Update Windows and Linux to Qt 6.9.0

---
 .../scripts/linux/build-dependencies-qt.sh    | 39 ++++---------------
 .../windows/build-dependencies-arm64.bat      | 14 +++----
 .../scripts/windows/build-dependencies.bat    | 14 +++----
 3 files changed, 22 insertions(+), 45 deletions(-)

diff --git a/.github/workflows/scripts/linux/build-dependencies-qt.sh b/.github/workflows/scripts/linux/build-dependencies-qt.sh
index d551433899..261898cbf9 100755
--- a/.github/workflows/scripts/linux/build-dependencies-qt.sh
+++ b/.github/workflows/scripts/linux/build-dependencies-qt.sh
@@ -20,7 +20,7 @@ LIBPNG=1.6.45
 LIBWEBP=1.5.0
 LZ4=b8fd2d15309dd4e605070bd4486e26b6ef814e29
 SDL=SDL3-3.2.10
-QT=6.8.2
+QT=6.9.0
 ZSTD=1.5.7
 KDDOCKWIDGETS=2.2.3
 
@@ -40,12 +40,12 @@ fd6f417fe9e3a071cf1424a5152d926a34c4a3c5070745470be6cf12a404ed79  $LIBBACKTRACE.
 0728800155f3ed0a0c87e03addbd30ecbe374f7b080678bbca1506051d50dec3  $LZ4.tar.gz
 f87be7b4dec66db4098e9c167b2aa34e2ca10aeb5443bdde95ae03185ed513e0  $SDL.tar.gz
 eb33e51f49a15e023950cd7825ca74a4a2b43db8354825ac24fc1b7ee09e6fa3  zstd-$ZSTD.tar.gz
-012043ce6d411e6e8a91fdc4e05e6bedcfa10fcb1347d3c33908f7fdd10dfe05  qtbase-everywhere-src-$QT.tar.xz
-d2a1bbb84707b8a0aec29227b170be00f04383fbf2361943596d09e7e443c8e1  qtimageformats-everywhere-src-$QT.tar.xz
-aa2579f21ca66d19cbcf31d87e9067e07932635d36869c8239d4decd0a9dc1fa  qtsvg-everywhere-src-$QT.tar.xz
-326381b7d43f07913612f291abc298ae79bd95382e2233abce982cff2b53d2c0  qttools-everywhere-src-$QT.tar.xz
-d2106e8a580bfd77702c4c1840299288d344902b0e2c758ca813ea04c6d6a3d1  qttranslations-everywhere-src-$QT.tar.xz
-5e46157908295f2bf924462d8c0855b0508ba338ced9e810891fefa295dc9647  qtwayland-everywhere-src-$QT.tar.xz
+c1800c2ea835801af04a05d4a32321d79a93954ee3ae2172bbeacf13d1f0598c  qtbase-everywhere-src-$QT.tar.xz
+2047c6242a57bf97cf40079fa9f91752c137cd9ae84760faa9a2e5e8a440606f  qtimageformats-everywhere-src-$QT.tar.xz
+ec359d930c95935ea48af58b100c2f5d0d275968ec8ca1e0e76629b7159215fc  qtsvg-everywhere-src-$QT.tar.xz
+fa645589cc3f939022401a926825972a44277dead8ec8607d9f2662e6529c9a4  qttools-everywhere-src-$QT.tar.xz
+1d5581ef5fc7c7bc556f2403017983683993bbebfcdf977ef8f180f604668c3f  qttranslations-everywhere-src-$QT.tar.xz
+503416fcb04db503bd130e6a49c45e3e546f091e83406f774a0c703130c91805  qtwayland-everywhere-src-$QT.tar.xz
 eb3b5f0c16313d34f208d90c2fa1e588a23283eed63b101edd5422be6165d528  shaderc-$SHADERC.tar.gz
 aa27e4454ce631c5a17924ce0624eac736da19fc6f5a2ab15a6c58da7b36950f  shaderc-glslang-$SHADERC_GLSLANG.tar.gz
 5d866ce34a4b6908e262e5ebfffc0a5e11dd411640b5f24c85a80ad44c0d4697  shaderc-spirv-headers-$SHADERC_SPIRVHEADERS.tar.gz
@@ -193,32 +193,9 @@ echo "Installing Qt Tools..."
 rm -fr "qttools-everywhere-src-$QT"
 tar xf "qttools-everywhere-src-$QT.tar.xz"
 cd "qttools-everywhere-src-$QT"
-# Force disable clang scanning, it gets very confused.
-patch -u configure.cmake <
Date: Sun, 6 Apr 2025 13:16:10 +0100
Subject: [PATCH 048/162] GameListWidget: Bodge broken icon styling in native
 theme

Still a bodge but this will fix the styling being missing on every other line on the type column.
---
 pcsx2-qt/GameList/GameListWidget.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pcsx2-qt/GameList/GameListWidget.cpp b/pcsx2-qt/GameList/GameListWidget.cpp
index d308d48013..b2c04607b1 100644
--- a/pcsx2-qt/GameList/GameListWidget.cpp
+++ b/pcsx2-qt/GameList/GameListWidget.cpp
@@ -116,7 +116,7 @@ namespace
 			// draw default item
 			QStyleOptionViewItem opt = option;
 			initStyleOption(&opt, index);
-			opt.icon = QIcon();
+			opt.type = QStyleOption::SO_Default;
 			QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter, 0);
 
 			const QRect r = option.rect;

From b51b32c758d29b3d00d8bfe647fa88dc3c69a519 Mon Sep 17 00:00:00 2001
From: JordanTheToaster 
Date: Wed, 26 Mar 2025 18:51:28 +0000
Subject: [PATCH 049/162] UI: Move Skip Presenting Duplicate Frames to
 Emulation Tab

---
 pcsx2-qt/Settings/EmulationSettingsWidget.cpp |  6 ++++
 pcsx2-qt/Settings/EmulationSettingsWidget.ui  | 32 +++++++++++--------
 pcsx2-qt/Settings/GraphicsSettingsWidget.cpp  |  8 -----
 pcsx2-qt/Settings/GraphicsSettingsWidget.ui   | 29 +++++++----------
 4 files changed, 36 insertions(+), 39 deletions(-)

diff --git a/pcsx2-qt/Settings/EmulationSettingsWidget.cpp b/pcsx2-qt/Settings/EmulationSettingsWidget.cpp
index e289bf3f79..5450252295 100644
--- a/pcsx2-qt/Settings/EmulationSettingsWidget.cpp
+++ b/pcsx2-qt/Settings/EmulationSettingsWidget.cpp
@@ -34,6 +34,7 @@ EmulationSettingsWidget::EmulationSettingsWidget(SettingsWindow* dialog, QWidget
 	SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.vsync, "EmuCore/GS", "VsyncEnable", false);
 	SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.syncToHostRefreshRate, "EmuCore/GS", "SyncToHostRefreshRate", false);
 	SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.useVSyncForTiming, "EmuCore/GS", "UseVSyncForTiming", false);
+	SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.skipPresentingDuplicateFrames, "EmuCore/GS", "SkipDuplicateFrames", false);
 	connect(m_ui.optimalFramePacing, &QCheckBox::checkStateChanged, this, &EmulationSettingsWidget::onOptimalFramePacingChanged);
 	connect(m_ui.vsync, &QCheckBox::checkStateChanged, this, &EmulationSettingsWidget::updateUseVSyncForTimingEnabled);
 	connect(m_ui.syncToHostRefreshRate, &QCheckBox::checkStateChanged, this, &EmulationSettingsWidget::updateUseVSyncForTimingEnabled);
@@ -154,6 +155,11 @@ EmulationSettingsWidget::EmulationSettingsWidget(SettingsWindow* dialog, QWidget
 	dialog->registerWidgetHelp(m_ui.useVSyncForTiming, tr("Use Host VSync Timing"), tr("Unchecked"),
 		tr("When synchronizing with the host refresh rate, this option disable's PCSX2's internal frame timing, and uses the host instead. "
 		   "Can result in smoother frame pacing, but at the cost of increased input latency."));
+	dialog->registerWidgetHelp(m_ui.skipPresentingDuplicateFrames, tr("Skip Presenting Duplicate Frames"), tr("Checked"),
+		tr("Detects when idle frames are being presented in 25/30fps games, and skips presenting those frames. The frame is still "
+		   "rendered, it just means the GPU has more time to complete it (this is NOT frame skipping). Can smooth out frame time "
+		   "fluctuations when the CPU/GPU are near maximum utilization, but makes frame pacing more inconsistent and can increase "
+		   "input lag. Helps when using frame generation on 25/30fps games."));
 	dialog->registerWidgetHelp(m_ui.manuallySetRealTimeClock, tr("Manually Set Real-Time Clock"), tr("Unchecked"),
 		tr("Manually set a real-time clock to use for the virtual PlayStation 2 instead of using your OS' system clock."));
 	dialog->registerWidgetHelp(m_ui.rtcDateTime, tr("Real-Time Clock"), tr("Current date and time"),
diff --git a/pcsx2-qt/Settings/EmulationSettingsWidget.ui b/pcsx2-qt/Settings/EmulationSettingsWidget.ui
index e1f56b55dd..578066ac60 100644
--- a/pcsx2-qt/Settings/EmulationSettingsWidget.ui
+++ b/pcsx2-qt/Settings/EmulationSettingsWidget.ui
@@ -7,7 +7,7 @@
     0
     0
     672
-    438
+    500
    
   
   
@@ -222,13 +222,6 @@
       
       
        
-        
-         
-          
-           Use Host VSync Timing
-          
-         
-        
         
          
           
@@ -236,6 +229,13 @@
           
          
         
+        
+         
+          
+           Vertical Sync (VSync)
+          
+         
+        
         
          
           
@@ -243,10 +243,17 @@
           
          
         
-        
-         
+        
+         
           
-           Vertical Sync (VSync)
+           Use Host VSync Timing
+          
+         
+        
+        
+         
+          
+           Skip Presenting Duplicate Frames
           
          
         
@@ -282,8 +289,7 @@
        
       
       
-       
-       
+       
       
      
     
diff --git a/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp b/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp
index c0d3dc9c70..4b7dad18f3 100644
--- a/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp
+++ b/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp
@@ -244,7 +244,6 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
 	//////////////////////////////////////////////////////////////////////////
 	SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.useBlitSwapChain, "EmuCore/GS", "UseBlitSwapChain", false);
 	SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.useDebugDevice, "EmuCore/GS", "UseDebugDevice", false);
-	SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.skipPresentingDuplicateFrames, "EmuCore/GS", "SkipDuplicateFrames", false);
 	SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.disableMailboxPresentation, "EmuCore/GS", "DisableMailboxPresentation", false);
 	SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.extendedUpscales, "EmuCore/GS", "ExtendedUpscalingMultipliers", false);
 	SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.exclusiveFullscreenControl, "EmuCore/GS", "ExclusiveFullscreenControl", -1, -1);
@@ -395,7 +394,6 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
 		m_ui.extendedUpscales = nullptr;
 		m_ui.spinCPUDuringReadbacks = nullptr;
 		m_ui.spinGPUDuringReadbacks = nullptr;
-		m_ui.skipPresentingDuplicateFrames = nullptr;
 		m_ui.overrideTextureBarriers = nullptr;
 		m_ui.disableFramebufferFetch = nullptr;
 		m_ui.disableShaderCache = nullptr;
@@ -861,12 +859,6 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
 			tr("Overrides the driver's heuristics for enabling exclusive fullscreen, or direct flip/scanout.
" "Disallowing exclusive fullscreen may enable smoother task switching and overlays, but increase input latency.")); - dialog->registerWidgetHelp(m_ui.skipPresentingDuplicateFrames, tr("Skip Presenting Duplicate Frames"), tr("Unchecked"), - tr("Detects when idle frames are being presented in 25/30fps games, and skips presenting those frames. The frame is still " - "rendered, it just means the GPU has more time to complete it (this is NOT frame skipping). Can smooth out frame time " - "fluctuations when the CPU/GPU are near maximum utilization, but makes frame pacing more inconsistent and can increase " - "input lag.")); - dialog->registerWidgetHelp(m_ui.disableMailboxPresentation, tr("Disable Mailbox Presentation"), tr("Unchecked"), tr("Forces the use of FIFO over Mailbox presentation, i.e. double buffering instead of triple buffering. " "Usually results in worse frame pacing.")); diff --git a/pcsx2-qt/Settings/GraphicsSettingsWidget.ui b/pcsx2-qt/Settings/GraphicsSettingsWidget.ui index 8963a6ef46..5ffc26604d 100644 --- a/pcsx2-qt/Settings/GraphicsSettingsWidget.ui +++ b/pcsx2-qt/Settings/GraphicsSettingsWidget.ui @@ -2131,6 +2131,13 @@
+ + + + Extended Upscaling Multipliers + + + @@ -2145,31 +2152,17 @@ - - - - Skip Presenting Duplicate Frames - - - - + - Extended Upscaling Multipliers - - - - - - - Spin GPU During Readbacks + Spin CPU During Readbacks - + - Spin CPU During Readbacks + Spin GPU During Readbacks From ee00213961cf5d1a3142dd1f15e537e7357e58f9 Mon Sep 17 00:00:00 2001 From: JordanTheToaster Date: Thu, 27 Mar 2025 10:37:17 +0000 Subject: [PATCH 050/162] PerformanceMetrics: Revert increased update rate It seems our OSD does not like updating at 0.25 per tick so let's just go back for now. --- pcsx2/PerformanceMetrics.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pcsx2/PerformanceMetrics.cpp b/pcsx2/PerformanceMetrics.cpp index a3d40f6ad9..990cc6f0db 100644 --- a/pcsx2/PerformanceMetrics.cpp +++ b/pcsx2/PerformanceMetrics.cpp @@ -15,7 +15,7 @@ #include "MTVU.h" #include "VMManager.h" -static const float UPDATE_INTERVAL = 0.25f; +static const float UPDATE_INTERVAL = 0.5f; static float s_fps = 0.0f; static float s_internal_fps = 0.0f; From 24b1be1dd2915088779d62bcb8c0be665447f0c0 Mon Sep 17 00:00:00 2001 From: JordanTheToaster Date: Wed, 2 Apr 2025 15:53:32 +0100 Subject: [PATCH 051/162] OSD: Append debug device so Kam doesn't forget --- pcsx2/ImGui/ImGuiOverlays.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pcsx2/ImGui/ImGuiOverlays.cpp b/pcsx2/ImGui/ImGuiOverlays.cpp index 692e0f027c..d4ed151412 100644 --- a/pcsx2/ImGui/ImGuiOverlays.cpp +++ b/pcsx2/ImGui/ImGuiOverlays.cpp @@ -230,7 +230,7 @@ __ri void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, f // GPU text.clear(); - text.append_format("GPU: {}", g_gs_device->GetName()); + text.append_format("GPU: {}{}", g_gs_device->GetName(), GSConfig.UseDebugDevice ? " (Debug)" : ""); DRAW_LINE(fixed_font, text.c_str(), IM_COL32(255, 255, 255, 255)); } From 7de5066c87feff974880c03a21ef581c5747fde0 Mon Sep 17 00:00:00 2001 From: JordanTheToaster Date: Wed, 2 Apr 2025 16:21:48 +0100 Subject: [PATCH 052/162] VMManager: Add warning for debug device --- pcsx2/VMManager.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pcsx2/VMManager.cpp b/pcsx2/VMManager.cpp index 824ce5c050..e48cc3898b 100644 --- a/pcsx2/VMManager.cpp +++ b/pcsx2/VMManager.cpp @@ -3173,6 +3173,11 @@ void VMManager::WarnAboutUnsafeSettings() append(ICON_FA_IMAGES, TRANSLATE_SV("VMManager", "Mipmapping is disabled. This may break rendering in some games.")); } + if (EmuConfig.GS.UseDebugDevice) + { + append(ICON_FA_BUG, + TRANSLATE_SV("VMManager", "Debug device is enabled. This will massively reduce performance.")); + } static bool render_change_warn = false; if (EmuConfig.GS.Renderer != GSRendererType::Auto && EmuConfig.GS.Renderer != GSRendererType::SW && !render_change_warn) { From 4a509610fe52136a9bafb24018ef44413bc76a96 Mon Sep 17 00:00:00 2001 From: JordanTheToaster Date: Sun, 6 Apr 2025 20:20:46 +0100 Subject: [PATCH 053/162] GameListWidget: Enable mouse tracking --- pcsx2-qt/GameList/GameListWidget.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/pcsx2-qt/GameList/GameListWidget.cpp b/pcsx2-qt/GameList/GameListWidget.cpp index b2c04607b1..79d86e1549 100644 --- a/pcsx2-qt/GameList/GameListWidget.cpp +++ b/pcsx2-qt/GameList/GameListWidget.cpp @@ -181,6 +181,7 @@ void GameListWidget::initialize() m_table_view->setSelectionBehavior(QAbstractItemView::SelectRows); m_table_view->setContextMenuPolicy(Qt::CustomContextMenu); m_table_view->setAlternatingRowColors(true); + m_table_view->setMouseTracking(true); m_table_view->setShowGrid(false); m_table_view->setCurrentIndex({}); m_table_view->horizontalHeader()->setHighlightSections(false); From a9e963e84ba9923a1f38fbab5c7ed409533620ad Mon Sep 17 00:00:00 2001 From: PCSX2 Bot Date: Mon, 7 Apr 2025 00:02:18 +0000 Subject: [PATCH 054/162] [ci skip] Qt: Update Base Translation. --- pcsx2-qt/Translations/pcsx2-qt_en.ts | 971 ++++++++++++++------------- 1 file changed, 488 insertions(+), 483 deletions(-) diff --git a/pcsx2-qt/Translations/pcsx2-qt_en.ts b/pcsx2-qt/Translations/pcsx2-qt_en.ts index 8e6154fd70..290d56bfbf 100644 --- a/pcsx2-qt/Translations/pcsx2-qt_en.ts +++ b/pcsx2-qt/Translations/pcsx2-qt_en.ts @@ -5523,7 +5523,7 @@ Do you want to overwrite? - + Enable Cheats @@ -5544,25 +5544,25 @@ Do you want to overwrite? - + Enable Host Filesystem - + Enable Fast CDVD - + Enable CDVD Precaching - + Enable Thread Pinning @@ -5573,7 +5573,7 @@ Do you want to overwrite? - + Disabled @@ -5614,7 +5614,7 @@ Do you want to overwrite? - + 100% (Normal Speed) @@ -5650,250 +5650,262 @@ Do you want to overwrite? - - + + Use Host VSync Timing - - + + Sync to Host Refresh Rate - + Optimal Frame Pacing - - + + Vertical Sync (VSync) - - + + + Skip Presenting Duplicate Frames + + + + + Real-Time Clock - - + + Manually Set Real-Time Clock - + Use Global Setting [%1] - + Normal Speed - + Sets the target emulation speed. It is not guaranteed that this speed will be reached, and if not, the emulator will run as fast as it can manage. - - + + User Preference - + + Checked - + Higher values may increase internal framerate in games, but will increase CPU requirements substantially. Lower values will reduce the CPU load allowing lightweight games to run full speed on weaker CPUs. - + Makes the emulated Emotion Engine skip cycles. Helps a small subset of games like SOTC. Most of the time it's harmful to performance. SOTC = Shadow of the Colossus. A game's title, should not be translated unless an official translation exists. - - - - - - - - - - + + + + + + + + + + Unchecked - + Fast disc access, less loading times. Check HDLoader compatibility lists for known games that have issues with this. - + Automatically loads and applies cheats on game start. - + Allows games and homebrew to access files / folders directly on the host computer. - + Fast-Forward Speed The "User Preference" string will appear after the text "Recommended Value:" - + 100% - + Sets the fast-forward speed. This speed will be used when the fast-forward hotkey is pressed/toggled. - + Slow-Motion Speed The "User Preference" string will appear after the text "Recommended Value:" - + Sets the slow-motion speed. This speed will be used when the slow-motion hotkey is pressed/toggled. - + EE Cycle Rate - + EE Cycle Skip - + Sets the priority for specific threads in a specific order ignoring the system scheduler. May help CPUs with big (P) and little (E) cores (e.g. Intel 12th or newer generation CPUs from Intel or other vendors such as AMD). P-Core = Performance Core, E-Core = Efficiency Core. See if Intel has official translations for these terms. - + Enable Multithreaded VU1 (MTVU1) - + Generally a speedup on CPUs with 4 or more cores. Safe for most games, but a few are incompatible and may hang. - + Loads the disc image into RAM before starting the virtual machine. Can reduce stutter on systems with hard drives that have long wake times, but significantly increases boot times. - + Sets the VSync queue size to 0, making every frame be completed and presented by the GS before input is polled and the next frame begins. Using this setting can reduce input lag at the cost of measurably higher CPU and GPU requirements. - + Maximum Frame Latency - + 2 Frames - + Sets the maximum number of frames that can be queued up to the GS, before the CPU thread will wait for one of them to complete before continuing. Higher values can assist with smoothing out irregular frame times, but add additional input lag. - + Speeds up emulation so that the guest refresh rate matches the host. This results in the smoothest animations possible, at the cost of potentially increasing the emulation speed by less than 1%. Sync to Host Refresh Rate will not take effect if the console's refresh rate is too far from the host's refresh rate. Users with variable refresh rate displays should disable this option. - + Enable this option to match PCSX2's refresh rate with your current monitor or screen. VSync is automatically disabled when it is not possible (eg. running at non-100% speed). - + When synchronizing with the host refresh rate, this option disable's PCSX2's internal frame timing, and uses the host instead. Can result in smoother frame pacing, <strong>but at the cost of increased input latency</strong>. - + + Detects when idle frames are being presented in 25/30fps games, and skips presenting those frames. The frame is still rendered, it just means the GPU has more time to complete it (this is NOT frame skipping). Can smooth out frame time fluctuations when the CPU/GPU are near maximum utilization, but makes frame pacing more inconsistent and can increase input lag. Helps when using frame generation on 25/30fps games. + + + + Manually set a real-time clock to use for the virtual PlayStation 2 instead of using your OS' system clock. - + Current date and time - + Real-time clock (RTC) used by the virtual PlayStation 2. Date format is the same as the one used by your OS. This time is only applied upon booting the PS2; changing it while in-game will have no effect. NOTE: This assumes you have your PS2 set to the default timezone of GMT+0 and default DST of Summer Time. Some games require an RTC date/time set after their release date. - + Use Global Setting [%1%] - + %1% [%2 FPS (NTSC) / %3 FPS (PAL)] - + Unlimited Every case that uses this particular string seems to refer to speeds: Normal Speed/Fast Forward Speed/Slow Motion Speed. - + Custom Every case that uses this particular string seems to refer to speeds: Normal Speed/Fast Forward Speed/Slow Motion Speed. - - + + Custom [%1% / %2 FPS (NTSC) / %3 FPS (PAL)] - + Custom Speed - + Enter Custom Speed @@ -12080,10 +12092,10 @@ Scanning recursively takes more time, but will identify files in subdirectories. - - - - + + + + Off (Default) @@ -12091,12 +12103,12 @@ Scanning recursively takes more time, but will identify files in subdirectories. - - + + - - - + + + Automatic (Default) @@ -12157,13 +12169,13 @@ Scanning recursively takes more time, but will identify files in subdirectories. - + None - + Bilinear (Smooth) Smooth: Refers to the texture clarity. @@ -12229,19 +12241,19 @@ Percentage sign that will appear next to a number. Add a space or whatever is ne - + Screen Offsets - + Show Overscan - + Anti-Blur @@ -12252,7 +12264,7 @@ Percentage sign that will appear next to a number. Add a space or whatever is ne - + Disable Interlace Offset @@ -12262,18 +12274,18 @@ Percentage sign that will appear next to a number. Add a space or whatever is ne - + Screen Resolution - + Internal Resolution - + PNG @@ -12325,7 +12337,7 @@ Percentage sign that will appear next to a number. Add a space or whatever is ne - + Bilinear (PS2) @@ -12372,7 +12384,7 @@ Percentage sign that will appear next to a number. Add a space or whatever is ne - + Unscaled (Default) @@ -12388,7 +12400,7 @@ Percentage sign that will appear next to a number. Add a space or whatever is ne - + Basic (Recommended) @@ -12413,18 +12425,18 @@ Percentage sign that will appear next to a number. Add a space or whatever is ne - + Texture Preloading: - + Partial - - + + Full (Hash Cache) @@ -12440,31 +12452,31 @@ Percentage sign that will appear next to a number. Add a space or whatever is ne - + Disable Depth Conversion - + GPU Palette Conversion - + Manual Hardware Renderer Fixes - + Spin GPU During Readbacks - - + + Spin CPU During Readbacks @@ -12476,15 +12488,15 @@ Percentage sign that will appear next to a number. Add a space or whatever is ne - - + + Mipmapping - - + + Auto Flush @@ -12494,12 +12506,12 @@ Percentage sign that will appear next to a number. Add a space or whatever is ne - + Force Disabled - + Force Enabled @@ -12512,8 +12524,8 @@ Percentage sign that will appear next to a number. Add a space or whatever is ne - - + + 0 (Disabled) 0 (Disabled) @@ -12570,18 +12582,18 @@ Percentage sign that will appear next to a number. Add a space or whatever is ne - + Disable Safe Features - + Preload Frame Data - + Texture Inside RT @@ -12680,13 +12692,13 @@ Percentage sign that will appear next to a number. Add a space or whatever is ne - + Merge Sprite - + Align Sprite @@ -12772,31 +12784,31 @@ Percentage sign that will appear next to a number. Add a space or whatever is ne - + Disable Partial Source Invalidation - + Read Targets When Closing - + Estimate Texture Region - + Disable Render Fixes - + Unscaled Palette Texture Draws @@ -12855,25 +12867,25 @@ Percentage sign that will appear next to a number. Add a space or whatever is ne - + Dump Textures - + Dump Mipmaps - + Dump FMV Textures - + Load Textures @@ -12920,13 +12932,13 @@ Percentage sign that will appear next to a number. Add a space or whatever is ne - + Force Even Sprite Position - + Precache Textures @@ -12949,8 +12961,8 @@ Percentage sign that will appear next to a number. Add a space or whatever is ne - - + + None (Default) @@ -12971,7 +12983,7 @@ Percentage sign that will appear next to a number. Add a space or whatever is ne - + FXAA @@ -13023,7 +13035,7 @@ Percentage sign that will appear next to a number. Add a space or whatever is ne - + Shade Boost @@ -13038,7 +13050,7 @@ Percentage sign that will appear next to a number. Add a space or whatever is ne - + Saturation @@ -13059,72 +13071,72 @@ Percentage sign that will appear next to a number. Add a space or whatever is ne - + Show Indicators - + Show Resolution - + Show Inputs - + Show GPU Usage - + Show Settings - + Show FPS - - + + Disable Mailbox Presentation Mailbox Presentation: a type of graphics-rendering technique that has not been exposed to the public that often, so chances are you will need to keep the word mailbox in English. It does not have anything to do with postal mailboxes or email inboxes/outboxes. - - + + Extended Upscaling Multipliers - + Disable Shader Cache - + Disable Vertex Shader Expand - + Show Statistics - + Asynchronous Texture Loading @@ -13135,13 +13147,13 @@ Percentage sign that will appear next to a number. Add a space or whatever is ne - + Show CPU Usage - + Warn About Unsafe Settings @@ -13167,7 +13179,7 @@ Percentage sign that will appear next to a number. Add a space or whatever is ne - + Left (Default) @@ -13178,43 +13190,43 @@ Percentage sign that will appear next to a number. Add a space or whatever is ne - + Right (Default) - + Show Frame Times - + Show PCSX2 Version - + Show Hardware Info - + Show Input Recording Status - + Show Video Capture Status - + Show VPS @@ -13323,19 +13335,13 @@ Percentage sign that will appear next to a number. Add a space or whatever is ne - + Zstandard (zst) - - Skip Presenting Duplicate Frames - - - - - + Use Blit Swap Chain Blit = a data operation. You might want to write it as-is, but fully uppercased. More information: https://en.wikipedia.org/wiki/Bit_blit \nSwap chain: see Microsoft's Terminology Portal. ---------- @@ -13357,43 +13363,43 @@ Swap chain: see Microsoft's Terminology Portal. - + Allow Exclusive Fullscreen: - + Disallowed - + Allowed - + Debugging Options - + Override Texture Barriers: - + Use Debug Device - + Show Speed Percentages - + Disable Framebuffer Fetch @@ -13460,1156 +13466,1150 @@ Swap chain: see Microsoft's Terminology Portal. - - - - + + + + Use Global Setting [%1] - - - - - - - - - - - - - + + + + + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + - + - - - - - - - + + + + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + Unchecked - + Enable Widescreen Patches - + Automatically loads and applies widescreen patches on game start. Can cause issues. - + Enable No-Interlacing Patches - + Automatically loads and applies no-interlacing patches on game start. Can cause issues. - + Disables interlacing offset which may reduce blurring in some situations. - + Bilinear Filtering - + Enables bilinear post processing filter. Smooths the overall picture as it is displayed on the screen. Corrects positioning between pixels. - + Enables PCRTC Offsets which position the screen as the game requests. Useful for some games such as WipEout Fusion for its screen shake effect, but can make the picture blurry. PCRTC: Programmable CRT (Cathode Ray Tube) Controller. - + Enables the option to show the overscan area on games which draw more than the safe area of the screen. - + FMV Aspect Ratio Override - + Determines the deinterlacing method to be used on the interlaced screen of the emulated console. Automatic should be able to correctly deinterlace most games, but if you see visibly shaky graphics, try one of the available options. - + Control the accuracy level of the GS blending unit emulation.<br> The higher the setting, the more blending is emulated in the shader accurately, and the higher the speed penalty will be.<br> Do note that Direct3D's blending is reduced in capability compared to OpenGL/Vulkan. - + Software Rendering Threads - + CPU Sprite Render Size - + Software CLUT Render - + Try to detect when a game is drawing its own color palette and then renders it on the GPU with special handling. - + This option disables game-specific render fixes. - + By default, the texture cache handles partial invalidations. Unfortunately it is very costly to compute CPU wise. This hack replaces the partial invalidation with a complete deletion of the texture to reduce the CPU load. It helps with the Snowblind engine games. - + Framebuffer Conversion - + Convert 4-bit and 8-bit framebuffer on the CPU instead of the GPU. Helps Harry Potter and Stuntman games. It has a big impact on performance. - - + + Disabled - + Overrides the full-motion video (FMV) aspect ratio. If disabled, the FMV Aspect Ratio will match the same value as the general Aspect Ratio setting. - + 90% - + Selects the quality at which screenshots will be compressed. Higher values preserve more detail for JPEG and WebP, and reduce file size for PNG. - + Enables mipmapping, which some games require to render correctly. Mipmapping uses progressively lower resolution variants of textures at progressively further distances to reduce processing load and avoid visual artifacts. - + Changes what filtering algorithm is used to map textures to surfaces.<br> Nearest: Makes no attempt to blend colors.<br> Bilinear (Forced): Will blend colors together to remove harsh edges between different colored pixels even if the game told the PS2 not to.<br> Bilinear (PS2): Will apply filtering to all surfaces that a game instructs the PS2 to filter.<br> Bilinear (Forced Excluding Sprites): Will apply filtering to all surfaces, even if the game told the PS2 not to, except sprites. - + Reduces blurriness of large textures applied to small, steeply angled surfaces by sampling colors from the two nearest Mipmaps. Requires Mipmapping to be 'on'.<br> Off: Disables the feature.<br> Trilinear (PS2): Applies Trilinear filtering to all surfaces that a game instructs the PS2 to.<br> Trilinear (Forced): Applies Trilinear filtering to all surfaces, even if the game told the PS2 not to. - + Reduces banding between colors and improves the perceived color depth.<br> Off: Disables any dithering.<br> Scaled: Upscaling-aware / Highest dithering effect.<br> Unscaled: Native Dithering / Lowest dithering effect does not increase size of squares when upscaling.<br> Force 32bit: Treat all draws as if they were 32bit to avoid banding and dithering. - + Does useless work on the CPU during readbacks to prevent it from going to into powersave modes. May improve performance during readbacks but with a significant increase in power usage. - + Submits useless work to the GPU during readbacks to prevent it from going into powersave modes. May improve performance during readbacks but with a significant increase in power usage. - + Number of rendering threads: 0 for single thread, 2 or more for multithread (1 is for debugging). 2 to 4 threads is recommended, any more than that is likely to be slower instead of faster. - + Disable the support of depth buffers in the texture cache. Will likely create various glitches and is only useful for debugging. - + Allows the texture cache to reuse as an input texture the inner portion of a previous framebuffer. - + Flushes all targets in the texture cache back to local memory when shutting down. Can prevent lost visuals when saving state or switching renderers, but can also cause graphical corruption. - + Attempts to reduce the texture size when games do not set it themselves (e.g. Snowblind games). - + Fixes issues with upscaling (vertical lines) in Namco games like Ace Combat, Tekken, Soul Calibur, etc. Namco: a game publisher and development company. Leave the name as-is. Ace Combat, Tekken, Soul Calibur: game names. Leave as-is or use official translations. - + Dumps replaceable textures to disk. Will reduce performance. - + Includes mipmaps when dumping textures. - + Allows texture dumping when FMVs are active. You should not enable this. - + Loads replacement textures on a worker thread, reducing microstutter when replacements are enabled. - + Loads replacement textures where available and user-provided. - + Preloads all replacement textures to memory. Not necessary with asynchronous loading. - + Enables FidelityFX Contrast Adaptive Sharpening. - + Determines the intensity the sharpening effect in CAS post-processing. - + Adjusts brightness. 50 is normal. - + Adjusts contrast. 50 is normal. - + Adjusts saturation. 50 is normal. - + Scales the size of the onscreen OSD from 50% to 500%. - + OSD Messages Position - + OSD Statistics Position - + Shows a variety of on-screen performance data points as selected by the user. - + Shows the vsync rate of the emulator in the top-right corner of the display. - + Shows OSD icon indicators for emulation states such as Pausing, Turbo, Fast-Forward, and Slow-Motion. - + Displays various settings and the current values of those settings, useful for debugging. - + Displays a graph showing the average frametimes. - + Shows the current system hardware information on the OSD. - + Video Codec - + Selects which Video Codec to be used for Video Capture. <b>If unsure, leave it on default.<b> - + Video Format - + Selects which Video Format to be used for Video Capture. If by chance the codec does not support the format, the first format available will be used. <b>If unsure, leave it on default.<b> - + Video Bitrate - + 6000 kbps - + Sets the video bitrate to be used. Larger bitrate generally yields better video quality at the cost of larger resulting file size. - + Automatic Resolution - + When checked, the video capture resolution will follows the internal resolution of the running game.<br><br><b>Be careful when using this setting especially when you are upscaling, as higher internal resolution (above 4x) can results in very large video capture and can cause system overload.</b> - + Enable Extra Video Arguments - + Allows you to pass arguments to the selected video codec. - + Extra Video Arguments - + Audio Codec - + Selects which Audio Codec to be used for Video Capture. <b>If unsure, leave it on default.<b> - + Audio Bitrate - + Enable Extra Audio Arguments - + Allows you to pass arguments to the selected audio codec. - + Extra Audio Arguments - + Allow Exclusive Fullscreen - + Overrides the driver's heuristics for enabling exclusive fullscreen, or direct flip/scanout.<br>Disallowing exclusive fullscreen may enable smoother task switching and overlays, but increase input latency. - + 1.25x Native (~450px) - + 1.5x Native (~540px) - + 1.75x Native (~630px) - + 2x Native (~720px/HD) - + 2.5x Native (~900px/HD+) - + 3x Native (~1080px/FHD) - + 3.5x Native (~1260px) - + 4x Native (~1440px/QHD) - + 5x Native (~1800px/QHD+) - + 6x Native (~2160px/4K UHD) - + 7x Native (~2520px) - + 8x Native (~2880px/5K UHD) - + 9x Native (~3240px) - + 10x Native (~3600px/6K UHD) - + 11x Native (~3960px) - + 12x Native (~4320px/8K UHD) - + 13x Native (~4680px) - + 14x Native (~5040px) - + 15x Native (~5400px) - + 16x Native (~5760px) - + 17x Native (~6120px) - + 18x Native (~6480px/12K UHD) - + 19x Native (~6840px) - + 20x Native (~7200px) - + 21x Native (~7560px) - + 22x Native (~7920px) - + 23x Native (~8280px) - + 24x Native (~8640px/16K UHD) - + 25x Native (~9000px) - - + + %1x Native - - - - - - - - - + + + + + + + + + Checked - + Enables internal Anti-Blur hacks. Less accurate to PS2 rendering but will make a lot of games look less blurry. - + Integer Scaling - + Adds padding to the display area to ensure that the ratio between pixels on the host to pixels in the console is an integer number. May result in a sharper image in some 2D games. - + Aspect Ratio - + Auto Standard (4:3/3:2 Progressive) - + Changes the aspect ratio used to display the console's output to the screen. The default is Auto Standard (4:3/3:2 Progressive) which automatically adjusts the aspect ratio to match how a game would be shown on a typical TV of the era. - + Deinterlacing - + Screenshot Size - + Determines the resolution at which screenshots will be saved. Internal resolutions preserve more detail at the cost of file size. - + Screenshot Format - + Selects the format which will be used to save screenshots. JPEG produces smaller files, but loses detail. - + Screenshot Quality - + 50% - - + + 100% - + Vertical Stretch - + Stretches (&lt; 100%) or squashes (&gt; 100%) the vertical component of the display. - + Fullscreen Mode - - - + + + Borderless Fullscreen - + Chooses the fullscreen resolution and frequency. - + Left - - - - + + + + 0px - + Changes the number of pixels cropped from the left side of the display. - + Top - + Changes the number of pixels cropped from the top of the display. - + Right - + Changes the number of pixels cropped from the right side of the display. - + Bottom - + Changes the number of pixels cropped from the bottom of the display. - - + + Native (PS2) (Default) - + Control the resolution at which games are rendered. High resolutions can impact performance on older or lower-end GPUs.<br>Non-native resolution may cause minor graphical issues in some games.<br>FMV resolution will remain unchanged, as the video files are pre-rendered. - + Texture Filtering - + Trilinear Filtering - + Anisotropic Filtering - + Reduces texture aliasing at extreme viewing angles. - + Dithering - + Blending Accuracy - + Texture Preloading - + Uploads entire textures at once instead of small pieces, avoiding redundant uploads when possible. Improves performance in most games, but can make a small selection slower. - + When enabled GPU converts colormap-textures, otherwise the CPU will. It is a trade-off between GPU and CPU. - + Enabling this option gives you the ability to change the renderer and upscaling fixes to your games. However IF you have ENABLED this, you WILL DISABLE AUTOMATIC SETTINGS and you can re-enable automatic settings by unchecking this option. - + 2 threads - - + + Force a primitive flush when a framebuffer is also an input texture. Fixes some processing effects such as the shadows in the Jak series and radiosity in GTA:SA. - + Enables mipmapping, which some games require to render correctly. - + The maximum target memory width that will allow the CPU Sprite Renderer to activate on. - + Tries to detect when a game is drawing its own color palette and then renders it in software, instead of on the GPU. - + GPU Target CLUT - + Skipdraw Range Start - - - - + + + + 0 - - + + Completely skips drawing surfaces from the surface in the left box up to the surface specified in the box on the right. - + Skipdraw Range End - + This option disables multiple safe features. Disables accurate Unscale Point and Line rendering which can help Xenosaga games. Disables accurate GS Memory Clearing to be done on the CPU, and let the GPU handle it, which can help Kingdom Hearts games. - + Uploads GS data when rendering a new frame to reproduce some effects accurately. - + Half Pixel Offset - + Might fix some misaligned fog, bloom, or blend effect. - + Round Sprite - + Corrects the sampling of 2D sprite textures when upscaling. Fixes lines in sprites of games like Ar tonelico when upscaling. Half option is for flat sprites, Full is for all sprites. - + Texture Offsets X - - + + Offset for the ST/UV texture coordinates. Fixes some odd texture issues and might fix some post processing alignment too. ST and UV are different types of texture coordinates, like XY would be spatial coordinates. - + Texture Offsets Y - + Lowers the GS precision to avoid gaps between pixels when upscaling. Fixes the text on Wild Arms games. Wild Arms: name of a game series. Leave as-is or use an official translation. - + Bilinear Upscale - + Can smooth out textures due to be bilinear filtered when upscaling. E.g. Brave sun glare. - + Replaces post-processing multiple paving sprites by a single fat sprite. It reduces various upscaling lines. - + Force palette texture draws to render at native resolution. - + Contrast Adaptive Sharpening You might find an official translation for this on AMD's website (Spanish version linked): https://www.amd.com/es/technologies/radeon-software-fidelityfx - + Sharpness - + Enables saturation, contrast, and brightness to be adjusted. Values of brightness, saturation, and contrast are at default 50. - + Applies the FXAA anti-aliasing algorithm to improve the visual quality of games. - + Brightness + - 50 - + Contrast - + TV Shader - + Applies a shader which replicates the visual effects of different styles of television set. - + OSD Scale - + Shows on-screen-display messages when events occur such as save states being created/loaded, screenshots being taken, etc. - + Shows the internal frame rate of the game in the top-right corner of the display. - + Shows the current emulation speed of the system in the top-right corner of the display as a percentage. - + Shows the resolution of the game in the top-right corner of the display. - + Shows host's CPU utilization. - + Shows host's GPU utilization. - + Shows counters for internal graphical utilization, useful for debugging. - + Shows the current controller state of the system in the bottom-left corner of the display. - + Shows the current PCSX2 version on the top-right corner of the display. - + Shows the currently active video capture status. - + Shows the currently active input recording status. - + Displays warnings when settings are enabled which may break games. - - + + Leave It Blank - + Parameters passed to the selected video codec.<br><b>You must use '=' to separate key from value and ':' to separate two pairs from each other.</b><br>For example: "crf = 21 : preset = veryfast" - + Sets the audio bitrate to be used. - + 160 kbps - + Parameters passed to the selected audio codec.<br><b>You must use '=' to separate key from value and ':' to separate two pairs from each other.</b><br>For example: "compression_level = 4 : joint_stereo = 1" - + GS Dump Compression - + Change the compression algorithm used when creating a GS dump. - + Uses a blit presentation model instead of flipping when using the Direct3D 11 renderer. This usually results in slower performance, but may be required for some streaming applications, or to uncap framerates on some systems. Blit = a data operation. You might want to write it as-is, but fully uppercased. More information: https://en.wikipedia.org/wiki/Bit_blit - - Detects when idle frames are being presented in 25/30fps games, and skips presenting those frames. The frame is still rendered, it just means the GPU has more time to complete it (this is NOT frame skipping). Can smooth out frame time fluctuations when the CPU/GPU are near maximum utilization, but makes frame pacing more inconsistent and can increase input lag. - - - - + Displays additional, very high upscaling multipliers dependent on GPU capability. - + Enable Debug Device - + Enables API-level validation of graphics commands. - + GS Download Mode - + Accurate - + Skips synchronizing with the GS thread and host GPU for GS downloads. Can result in a large speed boost on slower systems, at the cost of many broken graphical effects. If games are broken and you have this option enabled, please disable it first. - - - - - + + + + + Default This string refers to a default codec, whether it's an audio codec or a video codec. - + Forces the use of FIFO over Mailbox presentation, i.e. double buffering instead of triple buffering. Usually results in worse frame pacing. @@ -14617,7 +14617,7 @@ Swap chain: see Microsoft's Terminology Portal. GraphicsSettingsWidget::GraphicsSettingsWidget - + Default This string refers to a default pixel format @@ -22547,17 +22547,22 @@ Error was: {} - + + Debug device is enabled. This will massively reduce performance. + + + + Renderer is not set to Automatic. This may cause performance problems and graphical issues. - + Texture filtering is not set to Bilinear (PS2). This will break rendering in some games. - + No Game Running @@ -22577,102 +22582,102 @@ Error was: {} - + EE FPU Round Mode is not set to default, this may break some games. - + EE FPU Clamp Mode is not set to default, this may break some games. - + VU0 Round Mode is not set to default, this may break some games. - + VU1 Round Mode is not set to default, this may break some games. - + VU Clamp Mode is not set to default, this may break some games. - + 128MB RAM is enabled. Compatibility with some games may be affected. - + Game Fixes are not enabled. Compatibility with some games may be affected. - + Compatibility Patches are not enabled. Compatibility with some games may be affected. - + Frame rate for NTSC is not default. This may break some games. - + Frame rate for PAL is not default. This may break some games. - + EE Recompiler is not enabled, this will significantly reduce performance. - + VU0 Recompiler is not enabled, this will significantly reduce performance. - + VU1 Recompiler is not enabled, this will significantly reduce performance. - + IOP Recompiler is not enabled, this will significantly reduce performance. - + EE Cache is enabled, this will significantly reduce performance. - + EE Wait Loop Detection is not enabled, this may reduce performance. - + INTC Spin Detection is not enabled, this may reduce performance. - + Fastmem is not enabled, this will reduce performance. - + Instant VU1 is disabled, this may reduce performance. - + mVU Flag Hack is not enabled, this may reduce performance. From 8c1c4df10d5cbc70c0711f208f131a7fd0a6ea74 Mon Sep 17 00:00:00 2001 From: PCSX2 Bot Date: Mon, 7 Apr 2025 16:01:33 +0000 Subject: [PATCH 055/162] [ci skip] PAD: Update to latest controller database. --- bin/resources/game_controller_db.txt | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/bin/resources/game_controller_db.txt b/bin/resources/game_controller_db.txt index 1fe52ec2fe..ff9763f35a 100644 --- a/bin/resources/game_controller_db.txt +++ b/bin/resources/game_controller_db.txt @@ -193,6 +193,7 @@ 030000007d0400000640000000000000,Eliminator AfterShock,a:b1,b:b2,back:b9,dpdown:+a3,dpleft:-a5,dpright:+a5,dpup:-a3,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a4,righty:a2,start:b8,x:b0,y:b3,platform:Windows, 03000000120c0000f61c000000000000,Elite,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, 03000000430b00000300000000000000,EMS Production PS2 Adapter,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows, +03000000062000001801000000000000,EMS TrioLinker Plus II,a:b0,b:b1,x:b2,y:b3,back:b9,start:b8,leftshoulder:b6,rightshoulder:b7,leftstick:b10,rightstick:b11,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b4,righttrigger:b5,platform:Windows, 03000000242f000000b7000000000000,ESM 9110,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Windows, 03000000101c0000181c000000000000,Essential,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b4,leftx:a1,lefty:a0,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows, 030000008f0e00000f31000000000000,EXEQ,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Windows, @@ -1434,8 +1435,9 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2, 050000005e040000050b000003090000,Microsoft Xbox One Elite 2,a:b0,b:b1,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a6,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, 050000005e0400008e02000030110000,Microsoft Xbox One Elite 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b13,paddle3:b12,paddle4:b14,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, 030000005e040000120b00000b050000,Microsoft Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000120b000016050000,Microsoft Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000120b000017050000,Microsoft Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, 060000005e040000120b000001050000,Microsoft Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, -030000005e040000120b000016050000,Microsoft Xbox Series Controller,a:b0,b:b1,x:b2,y:b3,back:b6,start:b7,guide:b8,leftshoulder:b4,rightshoulder:b5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,platform:Linux, 03000000030000000300000002000000,Miroof,a:b1,b:b0,back:b6,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Linux, 03000000790000001c18000010010000,Mobapad Chitu HD,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, 050000004d4f435554452d3035335800,Mocute 053X,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, @@ -1453,9 +1455,9 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2, 030000006b1400000906000014010000,Nacon Asymmetric Wireless PS4 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, 030000006b140000010c000010010000,Nacon GC 400ES,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, 03000000853200000706000012010000,Nacon GC-100,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +05000000853200000503000000010000,Nacon MG-X Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, 0300000085320000170d000011010000,Nacon Revolution 5 Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux, 0300000085320000190d000011010000,Nacon Revolution 5 Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux, -05000000853200000503000000010000,Nacon MG-X Pro,a:b0,b:b1,x:b3,y:b4,back:b10,guide:b12,start:b11,leftstick:b13,rightstick:b14,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a5,righttrigger:a4,platform:Linux, 030000000d0f00000900000010010000,Natec Genesis P44,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, 030000004f1f00000800000011010000,NeoGeo PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux, 0300000092120000474e000000010000,NeoGeo X Arcade Stick,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b9,x:b3,y:b2,platform:Linux, @@ -1528,9 +1530,9 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2, 03000000d62000000228000001010000,PowerA Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, 03000000c62400001a54000001010000,PowerA Xbox One Mini Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, 03000000d62000000240000001010000,PowerA Xbox One Spectra Infinity,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, -03000000d62000000f20000001010000,PowerA Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b7,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, 03000000d62000000520000050010000,PowerA Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, 03000000d62000000b20000001010000,PowerA Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +03000000d62000000f20000001010000,PowerA Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b7,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, 030000006d040000d2ca000011010000,Precision Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, 03000000250900000017000010010000,PS/SS/N64 Adapter,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b5,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2~,righty:a3,start:b8,platform:Linux, 03000000ff1100004133000010010000,PS2 Controller,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Linux, @@ -1765,6 +1767,8 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2, 030000005e040000120b000009050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, 030000005e040000120b00000d050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, 030000005e040000120b00000f050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000120b000011050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +030000005e040000120b000014050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, 030000005e040000120b000015050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, 030000005e040000130b000005050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, 050000005e040000130b000001050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, @@ -1774,13 +1778,11 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2, 050000005e040000130b000011050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, 050000005e040000130b000013050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, 050000005e040000130b000015050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +050000005e040000130b000017050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, 060000005e040000120b000007050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, 060000005e040000120b00000b050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, -060000005e040000120b00000f050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, -030000005e040000120b000011050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, -030000005e040000120b000014050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, -050000005e040000130b000017050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, 060000005e040000120b00000d050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +060000005e040000120b00000f050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, 050000005e040000200b000013050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, 050000005e040000200b000017050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, 050000005e040000220b000017050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, From 4f561aa9e9fcf190fc219cce4e9f364f586a6332 Mon Sep 17 00:00:00 2001 From: KamFretoZ <14798312+kamfretoz@users.noreply.github.com> Date: Mon, 7 Apr 2025 21:38:40 +0700 Subject: [PATCH 056/162] Qt/Cheats: Add tooltip to cheat descriptions --- pcsx2-qt/Settings/GameCheatSettingsWidget.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/pcsx2-qt/Settings/GameCheatSettingsWidget.cpp b/pcsx2-qt/Settings/GameCheatSettingsWidget.cpp index 37896b5efa..91115f113f 100644 --- a/pcsx2-qt/Settings/GameCheatSettingsWidget.cpp +++ b/pcsx2-qt/Settings/GameCheatSettingsWidget.cpp @@ -283,6 +283,7 @@ QList GameCheatSettingsWidget::populateTreeViewRow(const Patch:: QStandardItem* authorItem = new QStandardItem(QString::fromStdString(pi.author)); QStandardItem* descriptionItem = new QStandardItem(QString::fromStdString(pi.description)); + descriptionItem->setToolTip(QString::fromStdString(pi.description)); items.push_back(nameItem); items.push_back(authorItem); From 854d1c0a1a5ddae52e43839cd4a2c1d04886e651 Mon Sep 17 00:00:00 2001 From: Sean <58099211+recursean@users.noreply.github.com> Date: Thu, 10 Apr 2025 16:59:49 +0000 Subject: [PATCH 057/162] Debugger: Add column titles to Disassembly view. Added new column title row to the "Disassembly" view in the debugger. Title row is non-selectable (single/double/right clicking on row do nothing) and branch lines do not get drawn on the title row. Format of title row was based on similar, existing title row on the VU0f tab in the "Registers" view. --- pcsx2-qt/Debugger/DisassemblyWidget.cpp | 146 ++++++++++++++++++------ pcsx2-qt/Debugger/DisassemblyWidget.h | 2 + 2 files changed, 114 insertions(+), 34 deletions(-) diff --git a/pcsx2-qt/Debugger/DisassemblyWidget.cpp b/pcsx2-qt/Debugger/DisassemblyWidget.cpp index 6e2b1eb30d..7a640fe278 100644 --- a/pcsx2-qt/Debugger/DisassemblyWidget.cpp +++ b/pcsx2-qt/Debugger/DisassemblyWidget.cpp @@ -355,37 +355,48 @@ void DisassemblyWidget::paintEvent(QPaintEvent* event) // Get the row height m_rowHeight = fm.height() + 2; - // Find the amount of visible rows - m_visibleRows = h / m_rowHeight; + // Find the amount of visible disassembly rows. Minus 1 to not count column title row. + m_visibleRows = h / m_rowHeight - 1; m_disassemblyManager.analyze(m_visibleStart, m_disassemblyManager.getNthNextAddress(m_visibleStart, m_visibleRows) - m_visibleStart); - // Draw the rows + const u32 curPC = cpu().getPC(); // Get the PC here, because it'll change when we are drawing and make it seem like there are two PCs + + // Format and draw title line on first row + const QString titleLineString = GetDisassemblyTitleLine(); + const QColor titleLineColor = GetDisassemblyTitleLineColor(); + painter.fillRect(0, 0 * m_rowHeight, w, m_rowHeight, titleLineColor); + painter.drawText(2, 0 * m_rowHeight, w, m_rowHeight, Qt::AlignLeft, titleLineString); + + // Prepare to draw the disassembly rows bool inSelectionBlock = false; bool alternate = m_visibleStart % 8; - const u32 curPC = cpu().getPC(); // Get the PC here, because it'll change when we are drawing and make it seem like there are two PCs - + // Draw visible disassembly rows for (u32 i = 0; i <= m_visibleRows; i++) { + // Address of instruction being displayed on row const u32 rowAddress = (i * 4) + m_visibleStart; - // Row backgrounds + // Row will be drawn at row index+1 to offset past title row + const u32 rowIndex = (i + 1) * m_rowHeight; + + // Row backgrounds if (inSelectionBlock || (m_selectedAddressStart <= rowAddress && rowAddress <= m_selectedAddressEnd)) { - painter.fillRect(0, i * m_rowHeight, w, m_rowHeight, this->palette().highlight()); + painter.fillRect(0, rowIndex, w, m_rowHeight, this->palette().highlight()); inSelectionBlock = m_selectedAddressEnd != rowAddress; } else { - painter.fillRect(0, i * m_rowHeight, w, m_rowHeight, alternate ? this->palette().base() : this->palette().alternateBase()); + painter.fillRect(0, rowIndex, w, m_rowHeight, alternate ? this->palette().base() : this->palette().alternateBase()); } // Row text painter.setPen(GetAddressFunctionColor(rowAddress)); QString lineString = DisassemblyStringFromAddress(rowAddress, painter.font(), curPC, rowAddress == m_selectedAddressStart); - painter.drawText(2, i * m_rowHeight, w, m_rowHeight, Qt::AlignLeft, lineString); + painter.drawText(2, rowIndex, w, m_rowHeight, Qt::AlignLeft, lineString); // Breakpoint marker bool enabled; @@ -394,11 +405,11 @@ void DisassemblyWidget::paintEvent(QPaintEvent* event) if (enabled) { painter.setPen(Qt::green); - painter.drawText(2, i * m_rowHeight, w, m_rowHeight, Qt::AlignLeft, "\u25A0"); + painter.drawText(2, rowIndex, w, m_rowHeight, Qt::AlignLeft, "\u25A0"); } else { - painter.drawText(2, i * m_rowHeight, w, m_rowHeight, Qt::AlignLeft, "\u2612"); + painter.drawText(2, rowIndex, w, m_rowHeight, Qt::AlignLeft, "\u2612"); } } alternate = !alternate; @@ -435,9 +446,10 @@ void DisassemblyWidget::paintEvent(QPaintEvent* event) // Explaination // ((branchLine.first - m_visibleStart) -> Find the amount of bytes from the top of the view // / 4 -> Convert that into rowss in instructions + // + 1 -> Offset 1 to account for column title row // * m_rowHeight -> convert that into rows in pixels // + (m_rowHeight / 2) -> Add half a row in pixels to center the arrow - top = (((branchLine.first - m_visibleStart) / 4) * m_rowHeight) + (m_rowHeight / 2); + top = (((branchLine.first - m_visibleStart) / 4 + 1) * m_rowHeight) + (m_rowHeight / 2); } if (branchLine.second < m_visibleStart) @@ -450,7 +462,7 @@ void DisassemblyWidget::paintEvent(QPaintEvent* event) } else { - bottom = (((branchLine.second - m_visibleStart) / 4) * m_rowHeight) + (m_rowHeight / 2); + bottom = (((branchLine.second - m_visibleStart) / 4 + 1) * m_rowHeight) + (m_rowHeight / 2); } branchCount++; @@ -467,7 +479,8 @@ void DisassemblyWidget::paintEvent(QPaintEvent* event) if (top < 0) // first is not visible, but second is { painter.drawLine(x - 2, bottom, x + 2, bottom); - painter.drawLine(x + 2, bottom, x + 2, 0); + // Draw to first visible disassembly row so branch line is not drawn on title line + painter.drawLine(x + 2, bottom, x + 2, m_rowHeight); if (branchLine.type == LINE_DOWN) { @@ -515,40 +528,56 @@ void DisassemblyWidget::paintEvent(QPaintEvent* event) void DisassemblyWidget::mousePressEvent(QMouseEvent* event) { - const u32 selectedAddress = (static_cast(event->position().y()) / m_rowHeight * 4) + m_visibleStart; - if (event->buttons() & Qt::LeftButton) + // Calculate index of row that was clicked + const u32 selectedRowIndex = static_cast(event->position().y()) / m_rowHeight; + + // Only process if a row other than the column title row was clicked + if (selectedRowIndex > 0) { - if (event->modifiers() & Qt::ShiftModifier) + // Calculate address of selected row. Index minus one for title row. + const u32 selectedAddress = ((selectedRowIndex - 1) * 4) + m_visibleStart; + if (event->buttons() & Qt::LeftButton) { - if (selectedAddress < m_selectedAddressStart) + if (event->modifiers() & Qt::ShiftModifier) + { + if (selectedAddress < m_selectedAddressStart) + { + m_selectedAddressStart = selectedAddress; + } + else if (selectedAddress > m_visibleStart) + { + m_selectedAddressEnd = selectedAddress; + } + } + else { m_selectedAddressStart = selectedAddress; - } - else if (selectedAddress > m_visibleStart) - { m_selectedAddressEnd = selectedAddress; } } - else + else if (event->buttons() & Qt::RightButton) { - m_selectedAddressStart = selectedAddress; - m_selectedAddressEnd = selectedAddress; + if (m_selectedAddressStart == m_selectedAddressEnd) + { + m_selectedAddressStart = selectedAddress; + m_selectedAddressEnd = selectedAddress; + } } + this->repaint(); } - else if (event->buttons() & Qt::RightButton) - { - if (m_selectedAddressStart == m_selectedAddressEnd) - { - m_selectedAddressStart = selectedAddress; - m_selectedAddressEnd = selectedAddress; - } - } - this->repaint(); } void DisassemblyWidget::mouseDoubleClickEvent(QMouseEvent* event) { - toggleBreakpoint((static_cast(event->position().y()) / m_rowHeight * 4) + m_visibleStart); + // Calculate index of row that was double clicked + const u32 selectedRowIndex = static_cast(event->position().y()) / m_rowHeight; + + // Only process if a row other than the column title row was double clicked + if (selectedRowIndex > 0) + { + // Calculate address of selected row. Index minus one for title row. + toggleBreakpoint(((selectedRowIndex - 1) * 4) + m_visibleStart); + } } void DisassemblyWidget::wheelEvent(QWheelEvent* event) @@ -641,6 +670,10 @@ void DisassemblyWidget::openContextMenu(QPoint pos) if (!cpu().isAlive()) return; + // Dont open context menu when used on column title row + if (pos.y() / m_rowHeight == 0) + return; + QMenu* menu = new QMenu(this); menu->setAttribute(Qt::WA_DeleteOnClose); @@ -743,6 +776,51 @@ void DisassemblyWidget::openContextMenu(QPoint pos) menu->popup(this->mapToGlobal(pos)); } +QString DisassemblyWidget::GetDisassemblyTitleLine() +{ + // Disassembly column title line based on format created by DisassemblyStringFromAddress() + QString title_line_string; + + // Determine layout of disassembly row. Layout depends on user setting "Show Instruction Bytes". + const bool show_instruction_bytes = m_showInstructionBytes && cpu().isAlive(); + if (show_instruction_bytes) + { + title_line_string = QCoreApplication::translate("DisassemblyWidgetColumnTitle", " %1 %2 %3 %4"); + } + else + { + title_line_string = QCoreApplication::translate("DisassemblyWidgetColumnTitle", " %1 %2 %3"); + } + + // First 2 chars in disassembly row is always for non-returning functions (NR) + // Do not display column title for this field. + title_line_string = title_line_string.arg(" "); + + // Second column title is always address of instruction + title_line_string = title_line_string.arg(QCoreApplication::translate("DisassemblyWidgetColumnTitle", "Location")); + + // If user specified to "Show Instruction Bytes", third column is opcode + args + if (show_instruction_bytes) + { + title_line_string = title_line_string.arg(QCoreApplication::translate("DisassemblyWidgetColumnTitle", "Bytes ")); + } + + // Last column title is always disassembled instruction + title_line_string = title_line_string.arg(QCoreApplication::translate("DisassemblyWidgetColumnTitle", "Instruction")); + + return title_line_string; +} + +QColor DisassemblyWidget::GetDisassemblyTitleLineColor() +{ + // Determine color of column title line. Based on QFusionStyle. + QColor title_line_color = this->palette().button().color(); + const int title_line_color_val = qGray(title_line_color.rgb()); + title_line_color = title_line_color.lighter(100 + qMax(1, (180 - title_line_color_val) / 6)); + title_line_color.setHsv(title_line_color.hue(), title_line_color.saturation() * 0.75, title_line_color.value()); + return title_line_color.lighter(104); +} + inline QString DisassemblyWidget::DisassemblyStringFromAddress(u32 address, QFont font, u32 pc, bool selected) { DisassemblyLineInfo line; diff --git a/pcsx2-qt/Debugger/DisassemblyWidget.h b/pcsx2-qt/Debugger/DisassemblyWidget.h index 44729db6e9..a47263fc56 100644 --- a/pcsx2-qt/Debugger/DisassemblyWidget.h +++ b/pcsx2-qt/Debugger/DisassemblyWidget.h @@ -80,6 +80,8 @@ private: bool m_goToProgramCounterOnPause = true; DisassemblyManager m_disassemblyManager; + QString GetDisassemblyTitleLine(); + QColor GetDisassemblyTitleLineColor(); inline QString DisassemblyStringFromAddress(u32 address, QFont font, u32 pc, bool selected); QColor GetAddressFunctionColor(u32 address); enum class SelectionInfo From 648ff65a7689a6fb29c27963b1f0f3de2cf82716 Mon Sep 17 00:00:00 2001 From: KamFretoZ <14798312+kamfretoz@users.noreply.github.com> Date: Tue, 8 Apr 2025 21:21:12 +0700 Subject: [PATCH 058/162] Rcheevos: Add customizable sound effects --- pcsx2-qt/SettingWidgetBinder.h | 96 +++ .../Settings/AchievementSettingsWidget.cpp | 38 ++ .../Settings/AchievementSettingsWidget.ui | 600 +++++++++++------- pcsx2-qt/Settings/AdvancedSettingsWidget.ui | 4 +- pcsx2/Achievements.cpp | 17 +- pcsx2/Config.h | 10 + pcsx2/ImGui/FullscreenUI.cpp | 46 ++ pcsx2/ImGui/ImGuiManager.cpp | 2 +- pcsx2/Pcsx2Config.cpp | 18 + 9 files changed, 571 insertions(+), 260 deletions(-) diff --git a/pcsx2-qt/SettingWidgetBinder.h b/pcsx2-qt/SettingWidgetBinder.h index 4240ccb2e3..602ae9ca77 100644 --- a/pcsx2-qt/SettingWidgetBinder.h +++ b/pcsx2-qt/SettingWidgetBinder.h @@ -1261,6 +1261,102 @@ namespace SettingWidgetBinder widget->connect(widget, &QLineEdit::editingFinished, widget, std::move(value_changed)); } + static inline void BindWidgetToFileSetting(SettingsInterface* sif, QLineEdit* widget, QAbstractButton* browse_button, + QAbstractButton* open_button, QAbstractButton* reset_button, std::string section, std::string key, std::string default_value, + const char* filter, bool allow_pergame = false, bool use_relative = true) + { + using Accessor = SettingAccessor; + + std::string current_path(Host::GetBaseStringSettingValue(section.c_str(), key.c_str(), default_value.c_str())); + if (current_path.empty()) + current_path = default_value; + else if (use_relative && !Path::IsAbsolute(current_path)) + current_path = Path::Canonicalize(Path::Combine(EmuFolders::DataRoot, current_path)); + + const QString value(QString::fromStdString(current_path)); + Accessor::setStringValue(widget, value); + + if (!allow_pergame) + { + widget->setEnabled(false); + if (browse_button) + browse_button->setEnabled(false); + if (reset_button) + reset_button->setEnabled(false); + return; + } + + auto value_changed = [widget, section = std::move(section), key = std::move(key), default_value, use_relative]() { + const std::string new_value(widget->text().toStdString()); + if (!new_value.empty()) + { + if (use_relative) + { + const std::string relative_path(Path::MakeRelative(new_value, EmuFolders::DataRoot)); + Host::SetBaseStringSettingValue(section.c_str(), key.c_str(), relative_path.c_str()); + } + else + { + Host::SetBaseStringSettingValue(section.c_str(), key.c_str(), new_value.c_str()); + } + + if (!FileSystem::FileExists(new_value.c_str())) + { + QMessageBox::critical(QtUtils::GetRootWidget(widget), qApp->translate("SettingWidgetBinder", "Error"), + qApp->translate("SettingWidgetBinder", "File cannot be found.")); + } + + Host::CommitBaseSettingChanges(); + return; + } + else + { + QMessageBox::critical(QtUtils::GetRootWidget(widget), qApp->translate("SettingWidgetBinder", "Error"), + qApp->translate("SettingWidgetBinder", "File path cannot be empty.")); + } + + // reset to old value + std::string current_path(Host::GetBaseStringSettingValue(section.c_str(), key.c_str(), default_value.c_str())); + if (current_path.empty()) + current_path = default_value; + else if (use_relative && !Path::IsAbsolute(current_path)) + current_path = Path::Canonicalize(Path::Combine(EmuFolders::DataRoot, current_path)); + + widget->setText(QString::fromStdString(current_path)); + }; + + if (browse_button) + { + QObject::connect(browse_button, &QAbstractButton::clicked, browse_button, [widget, key, value_changed, filter]() { + const QString path(QDir::toNativeSeparators(QFileDialog::getOpenFileName(QtUtils::GetRootWidget(widget), + qApp->translate("SettingWidgetBinder", "Select File"), QString(), filter))); + if (path.isEmpty()) + return; + + widget->setText(path); + value_changed(); + }); + } + if (open_button) + { + QObject::connect(open_button, &QAbstractButton::clicked, open_button, [widget]() { + QString path(Accessor::getStringValue(widget)); + if (!path.isEmpty()) + QtUtils::OpenURL(QtUtils::GetRootWidget(widget), QUrl::fromLocalFile(path)); + }); + } + if (reset_button) + { + QObject::connect( + reset_button, &QAbstractButton::clicked, reset_button, [widget, default_value = std::move(default_value), value_changed]() { + widget->setText(QString::fromStdString(default_value)); + value_changed(); + }); + } + + widget->connect(widget, &QLineEdit::editingFinished, widget, std::move(value_changed)); + } + // No need to pass a section or key since this is only used once and has six keys associated with it static inline void BindWidgetToDateTimeSetting(SettingsInterface* sif, QDateTimeEdit* widget, std::string section) { diff --git a/pcsx2-qt/Settings/AchievementSettingsWidget.cpp b/pcsx2-qt/Settings/AchievementSettingsWidget.cpp index d0e83748f2..d6595d7048 100644 --- a/pcsx2-qt/Settings/AchievementSettingsWidget.cpp +++ b/pcsx2-qt/Settings/AchievementSettingsWidget.cpp @@ -16,6 +16,8 @@ #include #include +const char* AUDIO_FILE_FILTER = QT_TRANSLATE_NOOP("MainWindow", "Audio Files (*.wav)"); + AchievementSettingsWidget::AchievementSettingsWidget(SettingsWindow* dialog, QWidget* parent) : QWidget(parent) , m_dialog(dialog) @@ -29,6 +31,9 @@ AchievementSettingsWidget::AchievementSettingsWidget(SettingsWindow* dialog, QWi SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.achievementNotifications, "Achievements", "Notifications", true); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.leaderboardNotifications, "Achievements", "LeaderboardNotifications", true); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.soundEffects, "Achievements", "SoundEffects", true); + SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.notificationSound, "Achievements", "InfoSound", true); + SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.unlockSound, "Achievements", "UnlockSound", true); + SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.lbSound, "Achievements", "LBSubmitSound", true); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.overlays, "Achievements", "Overlays", true); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.encoreMode, "Achievements", "EncoreMode", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.spectatorMode, "Achievements", "SpectatorMode", false); @@ -36,11 +41,16 @@ AchievementSettingsWidget::AchievementSettingsWidget(SettingsWindow* dialog, QWi SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.achievementNotificationsDuration, "Achievements", "NotificationsDuration", Pcsx2Config::AchievementsOptions::DEFAULT_NOTIFICATION_DURATION); SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.leaderboardNotificationsDuration, "Achievements", "LeaderboardsDuration", Pcsx2Config::AchievementsOptions::DEFAULT_LEADERBOARD_DURATION); + SettingWidgetBinder::BindWidgetToFileSetting(sif, m_ui.notificationSoundPath, m_ui.notificationSoundBrowse, m_ui.notificationSoundOpen, m_ui.notificationSoundReset, "Achievements", "InfoSoundName", Path::Combine(EmuFolders::Resources, EmuConfig.Achievements.DEFAULT_INFO_SOUND_NAME), AUDIO_FILE_FILTER, true, false); + SettingWidgetBinder::BindWidgetToFileSetting(sif, m_ui.unlockSoundPath, m_ui.unlockSoundBrowse, m_ui.unlockSoundOpen, m_ui.unlockSoundReset, "Achievements", "UnlockSoundName", Path::Combine(EmuFolders::Resources, EmuConfig.Achievements.DEFAULT_UNLOCK_SOUND_NAME), AUDIO_FILE_FILTER, true, false); + SettingWidgetBinder::BindWidgetToFileSetting(sif, m_ui.lbSoundPath, m_ui.lbSoundBrowse, m_ui.lbSoundOpen, m_ui.lbSoundReset, "Achievements", "LBSubmitSoundName", Path::Combine(EmuFolders::Resources, EmuConfig.Achievements.DEFAULT_LBSUBMIT_SOUND_NAME), AUDIO_FILE_FILTER, true, false); + dialog->registerWidgetHelp(m_ui.enable, tr("Enable Achievements"), tr("Unchecked"), tr("When enabled and logged in, PCSX2 will scan for achievements on startup.")); dialog->registerWidgetHelp(m_ui.hardcoreMode, tr("Enable Hardcore Mode"), tr("Unchecked"), tr("\"Challenge\" mode for achievements, including leaderboard tracking. Disables save state, cheats, and slowdown functions.")); dialog->registerWidgetHelp(m_ui.achievementNotifications, tr("Show Achievement Notifications"), tr("Checked"), tr("Displays popup messages on events such as achievement unlocks and game completion.")); dialog->registerWidgetHelp(m_ui.leaderboardNotifications, tr("Show Leaderboard Notifications"), tr("Checked"), tr("Displays popup messages when starting, submitting, or failing a leaderboard challenge.")); dialog->registerWidgetHelp(m_ui.soundEffects, tr("Enable Sound Effects"), tr("Checked"), tr("Plays sound effects for events such as achievement unlocks and leaderboard submissions.")); + dialog->registerWidgetHelp(m_ui.soundEffectsBox, tr("Custom Sound Effect"), tr("Any"), tr("Customize the sound effect that are played whenever you received a notification, earned an achievement or submitted an entry to the leaderboard.")); dialog->registerWidgetHelp(m_ui.overlays, tr("Enable In-Game Overlays"), tr("Checked"), tr("Shows icons in the lower-right corner of the screen when a challenge/primed achievement is active.")); dialog->registerWidgetHelp(m_ui.encoreMode, tr("Enable Encore Mode"), tr("Unchecked"),tr("When enabled, each session will behave as if no achievements have been unlocked.")); dialog->registerWidgetHelp(m_ui.spectatorMode, tr("Enable Spectator Mode"), tr("Unchecked"), tr("When enabled, PCSX2 will assume all achievements are locked and not send any unlock notifications to the server.")); @@ -51,6 +61,10 @@ AchievementSettingsWidget::AchievementSettingsWidget(SettingsWindow* dialog, QWi connect(m_ui.hardcoreMode, &QCheckBox::checkStateChanged, this, &AchievementSettingsWidget::onHardcoreModeStateChanged); connect(m_ui.achievementNotifications, &QCheckBox::checkStateChanged, this, &AchievementSettingsWidget::updateEnableState); connect(m_ui.leaderboardNotifications, &QCheckBox::checkStateChanged, this, &AchievementSettingsWidget::updateEnableState); + connect(m_ui.soundEffects, &QCheckBox::checkStateChanged, this, &AchievementSettingsWidget::updateEnableState); + connect(m_ui.notificationSound, &QCheckBox::checkStateChanged, this, &AchievementSettingsWidget::updateEnableState); + connect(m_ui.unlockSound, &QCheckBox::checkStateChanged, this, &AchievementSettingsWidget::updateEnableState); + connect(m_ui.lbSound, &QCheckBox::checkStateChanged, this, &AchievementSettingsWidget::updateEnableState); connect(m_ui.achievementNotificationsDuration, &QSlider::valueChanged, this, &AchievementSettingsWidget::onAchievementsNotificationDurationSliderChanged); connect(m_ui.leaderboardNotificationsDuration, &QSlider::valueChanged, this, &AchievementSettingsWidget::onLeaderboardsNotificationDurationSliderChanged); @@ -73,6 +87,11 @@ AchievementSettingsWidget::AchievementSettingsWidget(SettingsWindow* dialog, QWi m_ui.verticalLayout->removeWidget(m_ui.loginBox); m_ui.loginBox->deleteLater(); m_ui.loginBox = nullptr; + + // sound effects + m_ui.verticalLayout->removeWidget(m_ui.soundEffectsBox); + m_ui.soundEffectsBox->deleteLater(); + m_ui.soundEffectsBox = nullptr; } updateEnableState(); @@ -87,6 +106,10 @@ void AchievementSettingsWidget::updateEnableState() const bool enabled = m_dialog->getEffectiveBoolValue("Achievements", "Enabled", false); const bool notifications = enabled && m_dialog->getEffectiveBoolValue("Achievements", "Notifications", true); const bool lb_notifications = enabled && m_dialog->getEffectiveBoolValue("Achievements", "LeaderboardNotifications", true); + const bool sound = m_dialog->getEffectiveBoolValue("Achievements", "SoundEffects", true); + const bool info = enabled && sound && m_dialog->getEffectiveBoolValue("Achievements", "InfoSound", true); + const bool unlock = enabled && sound && m_dialog->getEffectiveBoolValue("Achievements", "UnlockSound", true); + const bool lbsound = enabled && sound && m_dialog->getEffectiveBoolValue("Achievements", "LBSubmitSound", true); m_ui.hardcoreMode->setEnabled(enabled); m_ui.achievementNotifications->setEnabled(enabled); m_ui.leaderboardNotifications->setEnabled(enabled); @@ -94,6 +117,21 @@ void AchievementSettingsWidget::updateEnableState() m_ui.achievementNotificationsDurationLabel->setEnabled(notifications); m_ui.leaderboardNotificationsDuration->setEnabled(lb_notifications); m_ui.leaderboardNotificationsDurationLabel->setEnabled(lb_notifications); + m_ui.notificationSoundPath->setEnabled(info); + m_ui.notificationSoundBrowse->setEnabled(info); + m_ui.notificationSoundOpen->setEnabled(info); + m_ui.notificationSoundReset->setEnabled(info); + m_ui.notificationSound->setEnabled(enabled); + m_ui.unlockSoundPath->setEnabled(unlock); + m_ui.unlockSoundBrowse->setEnabled(unlock); + m_ui.unlockSoundOpen->setEnabled(unlock); + m_ui.unlockSoundReset->setEnabled(unlock); + m_ui.unlockSound->setEnabled(enabled); + m_ui.lbSoundPath->setEnabled(lbsound); + m_ui.lbSoundOpen->setEnabled(lbsound); + m_ui.lbSoundBrowse->setEnabled(lbsound); + m_ui.lbSoundReset->setEnabled(lbsound); + m_ui.lbSound->setEnabled(enabled); m_ui.soundEffects->setEnabled(enabled); m_ui.overlays->setEnabled(enabled); m_ui.encoreMode->setEnabled(enabled); diff --git a/pcsx2-qt/Settings/AchievementSettingsWidget.ui b/pcsx2-qt/Settings/AchievementSettingsWidget.ui index a45e6c9b5d..1163cfbd9b 100644 --- a/pcsx2-qt/Settings/AchievementSettingsWidget.ui +++ b/pcsx2-qt/Settings/AchievementSettingsWidget.ui @@ -24,256 +24,364 @@ 0 - - - Settings + + + true - - - - - Enable Spectator Mode - - - - - - - Enable Achievements - - - - - - - Test Unofficial Achievements - - - - - - - Enable Encore Mode - - - - - - - Enable Hardcore Mode - - - - - - - - - - Notifications - - - - - - - - 3 - - - 30 - - - 1 - - - 5 - - - Qt::Horizontal - - - false - - - QSlider::TicksBelow - - - 1 - - - - - - - 5 seconds - - - - - - - - - Show Achievement Notifications - - - - - - - - - 3 - - - 30 - - - 1 - - - 5 - - - Qt::Horizontal - - - false - - - QSlider::TicksBelow - - - 1 - - - - - - - 5 seconds - - - - - - - - - Show Leaderboard Notifications - - - - - - - Enable Sound Effects - - - - - - - Enable In-Game Overlays - - - - - - - - - - Account - - - - - - Username: + + + + 0 + -2 + 655 + 739 + + + + + + + Settings + + + + + + Test Unofficial Achievements + + + + + + + Enable Hardcore Mode + + + + + + + Enable Achievements + + + + + + + Enable Spectator Mode + + + + + + + Enable Encore Mode + + + + + + + + + + Notifications + + + + + + + + 3 + + + 30 + + + 1 + + + 5 + + + Qt::Orientation::Horizontal + + + false + + + QSlider::TickPosition::TicksBelow + + + 1 + + + + + + + 5 seconds + + + + + + + + + Show Achievement Notifications + + + + + + + + + 3 + + + 30 + + + 1 + + + 5 + + + Qt::Orientation::Horizontal + + + false + + + QSlider::TickPosition::TicksBelow + + + 1 + + + + + + + 5 seconds + + + + + + + + + Show Leaderboard Notifications + + + + + + + Enable Sound Effects + + + + + + + Enable In-Game Overlays + + + + + + + + + + Sound Effects + + + + + + + + + + + + Browse... + + + + + + + Reset + + + + + + + Achievement Unlock Sound + + + + + + + Browse... + + + + + + + + + + Browse... + + + + + + + Preview + + + + + + + Preview + + + + + + + Notification Sound + + + + + + + Preview + + + + + + + Reset + + + + + + + Reset + + + + + + + Leaderboard Submit Sound + + + + + + + + + + Account + + + + + + Username: Login token generated at: - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - - - - - View Profile... - - - - - - - Login... - - - - - - + + + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop + + + + + + + + + View Profile... + + + + + + + Login... + + + + + + + + + + + + + 0 + 75 + + + + Game Info + + + + + + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop + + + + + + + + + + <html><head/><body><p align="justify">PCSX2 uses RetroAchievements as an achievement database and for tracking progress. To use achievements, please sign up for an account at <a href="https://retroachievements.org/">retroachievements.org</a>.</p><p align="justify">To view the achievement list in-game, press the hotkey for <span style=" font-weight:600;">Open Pause Menu</span> and select <span style=" font-weight:600;">Achievements</span> from the menu.</p></body></html> + + + Qt::TextFormat::RichText + + + true + + + 8 + + + true + + + + + - - - - - 0 - 75 - - - - Game Info - - - - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - - - - - - <html><head/><body><p align="justify">PCSX2 uses RetroAchievements as an achievement database and for tracking progress. To use achievements, please sign up for an account at <a href="https://retroachievements.org/">retroachievements.org</a>.</p><p align="justify">To view the achievement list in-game, press the hotkey for <span style=" font-weight:600;">Open Pause Menu</span> and select <span style=" font-weight:600;">Achievements</span> from the menu.</p></body></html> - - - Qt::RichText - - - true - - - 8 - - - true - - - - - - - Qt::Vertical - - - - 20 - 40 - - - -
diff --git a/pcsx2-qt/Settings/AdvancedSettingsWidget.ui b/pcsx2-qt/Settings/AdvancedSettingsWidget.ui index 197fda8c7c..1db2da7dab 100644 --- a/pcsx2-qt/Settings/AdvancedSettingsWidget.ui +++ b/pcsx2-qt/Settings/AdvancedSettingsWidget.ui @@ -32,9 +32,9 @@ 0 - -447 + -449 790 - 1049 + 1051 diff --git a/pcsx2/Achievements.cpp b/pcsx2/Achievements.cpp index 7f621e5533..80a2ae28bc 100644 --- a/pcsx2/Achievements.cpp +++ b/pcsx2/Achievements.cpp @@ -74,10 +74,6 @@ namespace Achievements // Chrome uses 10 server calls per domain, seems reasonable. static constexpr u32 MAX_CONCURRENT_SERVER_CALLS = 10; - static constexpr const char* INFO_SOUND_NAME = "sounds/achievements/message.wav"; - static constexpr const char* UNLOCK_SOUND_NAME = "sounds/achievements/unlock.wav"; - static constexpr const char* LBSUBMIT_SOUND_NAME = "sounds/achievements/lbsubmit.wav"; - namespace { struct LoginWithPasswordParameters @@ -1050,9 +1046,8 @@ void Achievements::DisplayAchievementSummary() }); } - // Technically not going through the resource API, but since we're passing this to something else, we can't. - if (EmuConfig.Achievements.SoundEffects) - Common::PlaySoundAsync(EmuFolders::GetOverridableResourcePath(INFO_SOUND_NAME).c_str()); + if (EmuConfig.Achievements.SoundEffects && EmuConfig.Achievements.InfoSound) + Common::PlaySoundAsync(EmuConfig.Achievements.InfoSoundName.c_str()); } void Achievements::DisplayHardcoreDeferredMessage() @@ -1103,8 +1098,8 @@ void Achievements::HandleUnlockEvent(const rc_client_event_t* event) }); } - if (EmuConfig.Achievements.SoundEffects) - Common::PlaySoundAsync(EmuFolders::GetOverridableResourcePath(UNLOCK_SOUND_NAME).c_str()); + if (EmuConfig.Achievements.SoundEffects && EmuConfig.Achievements.UnlockSound) + Common::PlaySoundAsync(EmuConfig.Achievements.UnlockSoundName.c_str()); } void Achievements::HandleGameCompleteEvent(const rc_client_event_t* event) @@ -1197,8 +1192,8 @@ void Achievements::HandleLeaderboardSubmittedEvent(const rc_client_event_t* even }); } - if (EmuConfig.Achievements.SoundEffects) - Common::PlaySoundAsync(EmuFolders::GetOverridableResourcePath(LBSUBMIT_SOUND_NAME).c_str()); + if (EmuConfig.Achievements.SoundEffects && EmuConfig.Achievements.LBSubmitSound) + Common::PlaySoundAsync(EmuConfig.Achievements.LBSubmitSoundName.c_str()); } void Achievements::HandleLeaderboardScoreboardEvent(const rc_client_event_t* event) diff --git a/pcsx2/Config.h b/pcsx2/Config.h index fc1253d220..e879aada39 100644 --- a/pcsx2/Config.h +++ b/pcsx2/Config.h @@ -1209,6 +1209,9 @@ struct Pcsx2Config static constexpr u32 MAXIMUM_NOTIFICATION_DURATION = 30; static constexpr u32 DEFAULT_NOTIFICATION_DURATION = 5; static constexpr u32 DEFAULT_LEADERBOARD_DURATION = 10; + static constexpr const char* DEFAULT_INFO_SOUND_NAME = "sounds/achievements/message.wav"; + static constexpr const char* DEFAULT_UNLOCK_SOUND_NAME = "sounds/achievements/unlock.wav"; + static constexpr const char* DEFAULT_LBSUBMIT_SOUND_NAME = "sounds/achievements/lbsubmit.wav"; BITFIELD32() bool @@ -1220,12 +1223,19 @@ struct Pcsx2Config Notifications : 1, LeaderboardNotifications : 1, SoundEffects : 1, + InfoSound : 1, + UnlockSound : 1, + LBSubmitSound : 1, Overlays : 1; BITFIELD_END u32 NotificationsDuration = DEFAULT_NOTIFICATION_DURATION; u32 LeaderboardsDuration = DEFAULT_LEADERBOARD_DURATION; + std::string InfoSoundName; + std::string UnlockSoundName; + std::string LBSubmitSoundName; + AchievementsOptions(); void LoadSave(SettingsWrapper& wrap); diff --git a/pcsx2/ImGui/FullscreenUI.cpp b/pcsx2/ImGui/FullscreenUI.cpp index f41316f4a5..d676dd8a5a 100644 --- a/pcsx2/ImGui/FullscreenUI.cpp +++ b/pcsx2/ImGui/FullscreenUI.cpp @@ -272,6 +272,7 @@ namespace FullscreenUI static void SwitchToLanding(); static ImGuiFullscreen::FileSelectorFilters GetOpenFileFilters(); static ImGuiFullscreen::FileSelectorFilters GetDiscImageFilters(); + static ImGuiFullscreen::FileSelectorFilters GetAudioFileFilters(); static void DoStartPath( const std::string& path, std::optional state_index = std::nullopt, std::optional fast_boot = std::nullopt); static void DoStartFile(); @@ -1059,6 +1060,11 @@ ImGuiFullscreen::FileSelectorFilters FullscreenUI::GetDiscImageFilters() return {"*.bin", "*.iso", "*.cue", "*.mdf", "*.chd", "*.cso", "*.zso", "*.gz"}; } +ImGuiFullscreen::FileSelectorFilters FullscreenUI::GetAudioFileFilters() +{ + return {"*.wav"}; +} + void FullscreenUI::DoStartPath(const std::string& path, std::optional state_index, std::optional fast_boot) { VMBootParameters params; @@ -7129,6 +7135,46 @@ void FullscreenUI::DrawAchievementsSettingsPage(std::unique_lock& se if (!IsEditingGameSettings(bsi)) { + MenuHeading(FSUI_CSTR("Sound Effects")); + if (MenuButton(FSUI_ICONSTR(ICON_FA_MUSIC, "Notification Sound"), bsi->GetTinyStringValue("Achievements", "InfoSoundName"))) + { + auto callback = [bsi](const std::string& path) { + if (!path.empty()) + { + bsi->SetStringValue("Achievements", "InfoSoundName", path.c_str()); + SetSettingsChanged(bsi); + } + CloseFileSelector(); + }; + OpenFileSelector(FSUI_ICONSTR(ICON_FA_FOLDER_OPEN, "Select Notification Sound"), false, std::move(callback), GetAudioFileFilters()); + } + + if (MenuButton(FSUI_ICONSTR(ICON_FA_MUSIC, "Unlock Sound"), bsi->GetTinyStringValue("Achievements", "UnlockSoundName"))) + { + auto callback = [bsi](const std::string& path) { + if (!path.empty()) + { + bsi->SetStringValue("Achievements", "UnlockSoundName", path.c_str()); + SetSettingsChanged(bsi); + } + CloseFileSelector(); + }; + OpenFileSelector(FSUI_ICONSTR(ICON_FA_FOLDER_OPEN, "Select Unlock Sound"), false, std::move(callback), GetAudioFileFilters()); + } + + if (MenuButton(FSUI_ICONSTR(ICON_FA_MUSIC, "Leaderboard Submit Sound"), bsi->GetTinyStringValue("Achievements", "LBSubmitSoundName"))) + { + auto callback = [bsi](const std::string& path) { + if (!path.empty()) + { + bsi->SetStringValue("Achievements", "LBSubmitSoundName", path.c_str()); + SetSettingsChanged(bsi); + } + CloseFileSelector(); + }; + OpenFileSelector(FSUI_ICONSTR(ICON_FA_FOLDER_OPEN, "Select Leaderboard Submit Sound"), false, std::move(callback), GetAudioFileFilters()); + } + MenuHeading(FSUI_CSTR("Account")); if (bsi->ContainsValue("Achievements", "Token")) { diff --git a/pcsx2/ImGui/ImGuiManager.cpp b/pcsx2/ImGui/ImGuiManager.cpp index eaa1c7e93f..b1eaae3890 100644 --- a/pcsx2/ImGui/ImGuiManager.cpp +++ b/pcsx2/ImGui/ImGuiManager.cpp @@ -488,7 +488,7 @@ ImFont* ImGuiManager::AddFixedFont(float size) bool ImGuiManager::AddIconFonts(float size) { // clang-format off - static constexpr ImWchar range_fa[] = { 0xe06f,0xe06f,0xf002,0xf002,0xf005,0xf005,0xf007,0xf007,0xf00c,0xf00e,0xf011,0xf011,0xf013,0xf013,0xf017,0xf017,0xf019,0xf019,0xf021,0xf023,0xf025,0xf028,0xf02b,0xf02b,0xf02e,0xf02e,0xf030,0xf030,0xf03a,0xf03a,0xf03d,0xf03e,0xf04b,0xf04c,0xf04e,0xf04e,0xf050,0xf050,0xf052,0xf052,0xf05a,0xf05a,0xf05e,0xf05e,0xf063,0xf063,0xf067,0xf067,0xf06a,0xf06a,0xf06e,0xf06e,0xf071,0xf071,0xf077,0xf078,0xf07b,0xf07c,0xf084,0xf084,0xf091,0xf091,0xf0ac,0xf0ad,0xf0b0,0xf0b0,0xf0c5,0xf0c5,0xf0c7,0xf0c8,0xf0cb,0xf0cb,0xf0d0,0xf0d0,0xf0dc,0xf0dc,0xf0e2,0xf0e2,0xf0eb,0xf0eb,0xf0f3,0xf0f3,0xf0fe,0xf0fe,0xf11b,0xf11c,0xf120,0xf121,0xf129,0xf12a,0xf140,0xf140,0xf14a,0xf14a,0xf15b,0xf15b,0xf15d,0xf15d,0xf187,0xf188,0xf191,0xf192,0xf1b3,0xf1b3,0xf1de,0xf1de,0xf1e6,0xf1e6,0xf1ea,0xf1eb,0xf1f8,0xf1f8,0xf1fc,0xf1fc,0xf21e,0xf21e,0xf245,0xf245,0xf26c,0xf26c,0xf279,0xf279,0xf2bd,0xf2bd,0xf2db,0xf2db,0xf2f2,0xf2f2,0xf302,0xf302,0xf3c1,0xf3c1,0xf3fd,0xf3fd,0xf410,0xf410,0xf462,0xf462,0xf466,0xf466,0xf4e2,0xf4e2,0xf51f,0xf51f,0xf545,0xf545,0xf54c,0xf54c,0xf553,0xf553,0xf56d,0xf56d,0xf5a2,0xf5a2,0xf65d,0xf65e,0xf6a9,0xf6a9,0xf70e,0xf70e,0xf756,0xf756,0xf780,0xf780,0xf794,0xf794,0xf815,0xf815,0xf84c,0xf84c,0xf8cc,0xf8cc,0x0,0x0 }; + static constexpr ImWchar range_fa[] = { 0xe06f,0xe06f,0xf001,0xf002,0xf005,0xf005,0xf007,0xf007,0xf00c,0xf00e,0xf011,0xf011,0xf013,0xf013,0xf017,0xf017,0xf019,0xf019,0xf021,0xf023,0xf025,0xf028,0xf02b,0xf02b,0xf02e,0xf02e,0xf030,0xf030,0xf03a,0xf03a,0xf03d,0xf03e,0xf04b,0xf04c,0xf04e,0xf04e,0xf050,0xf050,0xf052,0xf052,0xf05a,0xf05a,0xf05e,0xf05e,0xf063,0xf063,0xf067,0xf067,0xf06a,0xf06a,0xf06e,0xf06e,0xf071,0xf071,0xf077,0xf078,0xf07b,0xf07c,0xf084,0xf084,0xf091,0xf091,0xf0ac,0xf0ad,0xf0b0,0xf0b0,0xf0c5,0xf0c5,0xf0c7,0xf0c8,0xf0cb,0xf0cb,0xf0d0,0xf0d0,0xf0dc,0xf0dc,0xf0e2,0xf0e2,0xf0eb,0xf0eb,0xf0f3,0xf0f3,0xf0fe,0xf0fe,0xf11b,0xf11c,0xf120,0xf121,0xf129,0xf12a,0xf140,0xf140,0xf14a,0xf14a,0xf15b,0xf15b,0xf15d,0xf15d,0xf187,0xf188,0xf191,0xf192,0xf1b3,0xf1b3,0xf1de,0xf1de,0xf1e6,0xf1e6,0xf1ea,0xf1eb,0xf1f8,0xf1f8,0xf1fc,0xf1fc,0xf21e,0xf21e,0xf245,0xf245,0xf26c,0xf26c,0xf279,0xf279,0xf2bd,0xf2bd,0xf2db,0xf2db,0xf2f2,0xf2f2,0xf302,0xf302,0xf3c1,0xf3c1,0xf3fd,0xf3fd,0xf410,0xf410,0xf462,0xf462,0xf466,0xf466,0xf4e2,0xf4e2,0xf51f,0xf51f,0xf545,0xf545,0xf54c,0xf54c,0xf553,0xf553,0xf56d,0xf56d,0xf5a2,0xf5a2,0xf65d,0xf65e,0xf6a9,0xf6a9,0xf70e,0xf70e,0xf756,0xf756,0xf780,0xf780,0xf794,0xf794,0xf815,0xf815,0xf84c,0xf84c,0xf8cc,0xf8cc,0x0,0x0 }; static constexpr ImWchar range_pf[] = { 0x2198,0x2199,0x219e,0x21a7,0x21b0,0x21b3,0x21ba,0x21c3,0x21ce,0x21ce,0x21d0,0x21d4,0x21dc,0x21dd,0x21e0,0x21e3,0x21e6,0x21e8,0x21f3,0x21f3,0x21f7,0x21f8,0x21fa,0x21fb,0x2206,0x2208,0x221a,0x221a,0x227a,0x227d,0x22bf,0x22c8,0x2349,0x2349,0x235a,0x235e,0x2360,0x2361,0x2364,0x2367,0x237a,0x237b,0x237d,0x237d,0x237f,0x237f,0x23b2,0x23b5,0x23cc,0x23cc,0x23f4,0x23f7,0x2427,0x243a,0x243d,0x243d,0x2443,0x2443,0x2460,0x246b,0x248f,0x248f,0x24f5,0x24fd,0x24ff,0x24ff,0x2605,0x2605,0x2699,0x2699,0x278a,0x278e,0xe000,0xe001,0xff21,0xff3a,0x0,0x0 }; // clang-format on diff --git a/pcsx2/Pcsx2Config.cpp b/pcsx2/Pcsx2Config.cpp index c70f1e9480..9d9a816b22 100644 --- a/pcsx2/Pcsx2Config.cpp +++ b/pcsx2/Pcsx2Config.cpp @@ -1830,6 +1830,9 @@ Pcsx2Config::AchievementsOptions::AchievementsOptions() Notifications = true; LeaderboardNotifications = true; SoundEffects = true; + InfoSound = true; + UnlockSound = true; + LBSubmitSound = true; Overlays = true; } @@ -1837,6 +1840,15 @@ void Pcsx2Config::AchievementsOptions::LoadSave(SettingsWrapper& wrap) { SettingsWrapSection("Achievements"); + if (InfoSoundName.empty()) + InfoSoundName = Path::Combine(EmuFolders::Resources, DEFAULT_INFO_SOUND_NAME); + + if (UnlockSoundName.empty()) + UnlockSoundName = Path::Combine(EmuFolders::Resources, DEFAULT_UNLOCK_SOUND_NAME); + + if (LBSubmitSoundName.empty()) + LBSubmitSoundName = Path::Combine(EmuFolders::Resources, DEFAULT_LBSUBMIT_SOUND_NAME); + SettingsWrapBitBool(Enabled); SettingsWrapBitBoolEx(HardcoreMode, "ChallengeMode"); SettingsWrapBitBool(EncoreMode); @@ -1845,9 +1857,15 @@ void Pcsx2Config::AchievementsOptions::LoadSave(SettingsWrapper& wrap) SettingsWrapBitBool(Notifications); SettingsWrapBitBool(LeaderboardNotifications); SettingsWrapBitBool(SoundEffects); + SettingsWrapBitBool(InfoSound); + SettingsWrapBitBool(UnlockSound); + SettingsWrapBitBool(LBSubmitSound); SettingsWrapBitBool(Overlays); SettingsWrapEntry(NotificationsDuration); SettingsWrapEntry(LeaderboardsDuration); + SettingsWrapEntry(InfoSoundName); + SettingsWrapEntry(UnlockSoundName); + SettingsWrapEntry(LBSubmitSoundName); if (wrap.IsLoading()) { From a755131488ab7906540450d41c6a626d1b3c3455 Mon Sep 17 00:00:00 2001 From: KamFretoZ <14798312+kamfretoz@users.noreply.github.com> Date: Wed, 9 Apr 2025 18:28:19 +0700 Subject: [PATCH 059/162] Qt: Enable Savestate Selector UI by default This should've been enabled by default but I've missed the part that actually enables it. --- pcsx2/Pcsx2Config.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/pcsx2/Pcsx2Config.cpp b/pcsx2/Pcsx2Config.cpp index 9d9a816b22..dca9dafd9b 100644 --- a/pcsx2/Pcsx2Config.cpp +++ b/pcsx2/Pcsx2Config.cpp @@ -1895,6 +1895,7 @@ Pcsx2Config::Pcsx2Config() EnableRecordingTools = true; EnableGameFixes = true; InhibitScreensaver = true; + UseSavestateSelector = true; BackupSavestate = true; WarnAboutUnsafeSettings = true; ManuallySetRealTimeClock = false; From 7c798126e3fa2a688a5b423e3799dfc91517f824 Mon Sep 17 00:00:00 2001 From: JordanTheToaster Date: Thu, 10 Apr 2025 23:39:08 +0100 Subject: [PATCH 060/162] GameDB: Fix broken FMVs in Clock Tower 3 --- bin/resources/GameIndex.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/bin/resources/GameIndex.yaml b/bin/resources/GameIndex.yaml index 83ad779031..b23510decf 100644 --- a/bin/resources/GameIndex.yaml +++ b/bin/resources/GameIndex.yaml @@ -17537,6 +17537,8 @@ SLES-51619: name: "Clock Tower 3" region: "PAL-M5" compat: 5 + gameFixes: + - SoftwareRendererFMVHack # Fixes brightness and striped lines in FMVs. SLES-51620: name: "Black and Bruised" region: "PAL-M5" @@ -30036,6 +30038,8 @@ SLKA-25051: name: "Clock Tower 3" region: "NTSC-K" compat: 5 + gameFixes: + - SoftwareRendererFMVHack # Fixes brightness and striped lines in FMVs. SLKA-25052: name: "Air Ranger 2 - Rescue Helicopter" region: "NTSC-K" @@ -40428,6 +40432,8 @@ SLPM-65221: name-en: "Clock Tower 3" region: "NTSC-J" compat: 5 + gameFixes: + - SoftwareRendererFMVHack # Fixes brightness and striped lines in FMVs. SLPM-65222: name: "遊戯王真デュエルモンスターズⅡ 継承されし記憶 [コナミ殿堂セレクション]" name-sort: "ゆうぎおうしんでゅえるもんすたーず2 けいしょうされしきおく [こなみでんどうせれくしょん]" @@ -52313,6 +52319,8 @@ SLPM-74416: name-sort: "くろっくたわー3 [PlayStation2 the Best]" name-en: "Clock Tower 3 [PlayStation2 the Best]" region: "NTSC-J" + gameFixes: + - SoftwareRendererFMVHack # Fixes brightness and striped lines in FMVs. SLPM-74420: name: "頭文字D Special Stage [PlayStation2 the Best]" name-sort: "いにしゃるD すぺしゃる すてーじ [PlayStation2 the Best]" @@ -64595,6 +64603,8 @@ SLUS-20633: name: "Clock Tower 3" region: "NTSC-U" compat: 5 + gameFixes: + - SoftwareRendererFMVHack # Fixes brightness and striped lines in FMVs. SLUS-20634: name: "Summer Heat Beach Volleyball" region: "NTSC-U" From a56ffee8f7d3e9840a5a8d691bce73e1b11b8f95 Mon Sep 17 00:00:00 2001 From: PCSX2 Bot Date: Fri, 11 Apr 2025 00:01:59 +0000 Subject: [PATCH 061/162] [ci skip] Qt: Update Base Translation. --- pcsx2-qt/Translations/pcsx2-qt_en.ts | 2218 ++++++++++++++------------ pcsx2/ImGui/FullscreenUI.cpp | 8 +- 2 files changed, 1184 insertions(+), 1042 deletions(-) diff --git a/pcsx2-qt/Translations/pcsx2-qt_en.ts b/pcsx2-qt/Translations/pcsx2-qt_en.ts index 290d56bfbf..eddb092067 100644 --- a/pcsx2-qt/Translations/pcsx2-qt_en.ts +++ b/pcsx2-qt/Translations/pcsx2-qt_en.ts @@ -167,182 +167,238 @@ Do you want to enable hardcore mode? AchievementSettingsWidget - - + + Enable Achievements - - + + Enable Hardcore Mode - - + + Test Unofficial Achievements - - + + Enable Sound Effects - + Notifications - - + + 5 seconds - + Account - - + + Login... - + View Profile... - + Settings - - + + Enable Spectator Mode - - + + Enable Encore Mode - - + + Show Achievement Notifications - - + + Show Leaderboard Notifications - - + + Enable In-Game Overlays - + + Sound Effects + + + + + + + Browse... + + + + + + + Reset + + + + + Achievement Unlock Sound + + + + + + + Preview + + + + + Notification Sound + + + + + Leaderboard Submit Sound + + + + Username: Login token generated at: - + Game Info - + <html><head/><body><p align="justify">PCSX2 uses RetroAchievements as an achievement database and for tracking progress. To use achievements, please sign up for an account at <a href="https://retroachievements.org/">retroachievements.org</a>.</p><p align="justify">To view the achievement list in-game, press the hotkey for <span style=" font-weight:600;">Open Pause Menu</span> and select <span style=" font-weight:600;">Achievements</span> from the menu.</p></body></html> - - - - - + + + + + Unchecked - + When enabled, PCSX2 will assume all achievements are locked and not send any unlock notifications to the server. - + When enabled, PCSX2 will list achievements from unofficial sets. Please note that these achievements are not tracked by RetroAchievements, so they unlock every time. - + "Challenge" mode for achievements, including leaderboard tracking. Disables save state, cheats, and slowdown functions. - - - - + + + + Checked - + Plays sound effects for events such as achievement unlocks and leaderboard submissions. - + Shows icons in the lower-right corner of the screen when a challenge/primed achievement is active. - + When enabled and logged in, PCSX2 will scan for achievements on startup. - + Displays popup messages on events such as achievement unlocks and game completion. - + Displays popup messages when starting, submitting, or failing a leaderboard challenge. - + + Custom Sound Effect + + + + + Any + + + + + Customize the sound effect that are played whenever you received a notification, earned an achievement or submitted an entry to the leaderboard. + + + + When enabled, each session will behave as if no achievements have been unlocked. - + Reset System - + Hardcore mode will not be enabled until the system is reset. Do you want to reset the system now? - - + + %n seconds @@ -350,18 +406,18 @@ Login token generated at: - + Username: %1 Login token generated on %2. - + Logout - + Not Logged In. @@ -369,33 +425,33 @@ Login token generated on %2. Achievements - + Hardcore mode will be enabled on system reset. - - + + {0} cannot be performed while hardcore mode is active. Do you want to disable hardcore mode? {0} will be cancelled if you select No. - + Hardcore mode is now enabled. - + {} (Hardcore Mode) - + {0}, {1}. - + You have unlocked {} of %n achievements Achievement popup @@ -404,7 +460,7 @@ Login token generated on %2. - + and earned {} of %n points Achievement popup @@ -413,22 +469,22 @@ Login token generated on %2. - + {} (Unofficial) - + Mastered {} - + {0}, {1} - + %n achievements Mastery popup @@ -437,7 +493,7 @@ Login token generated on %2. - + %n points Mastery popup @@ -446,264 +502,264 @@ Login token generated on %2. - + Leaderboard attempt started. - + Leaderboard attempt failed. - + Your Time: {}{} - + Your Score: {}{} - + Your Value: {}{} - + (Submitting) - + Achievements Disconnected - + An unlock request could not be completed. We will keep retrying to submit this request. - + Achievements Reconnected - + All pending unlock requests have completed. - + Hardcore mode is now disabled. - + Score: {0} pts (softcore: {1} pts) Unread messages: {2} - - + + Confirm Hardcore Mode - + Active Challenge Achievements - + (Hardcore Mode) - + You have unlocked all achievements and earned {} points! - - + + Leaderboard Download Failed - + Your Time: {0} (Best: {1}) - + Your Score: {0} (Best: {1}) - + Your Value: {0} (Best: {1}) - + {0} Leaderboard Position: {1} of {2} - + Server error in {0}: {1} - + Yes - + No - + You have unlocked {0} of {1} achievements, earning {2} of {3} possible points. - + Unknown - + Locked - + Unlocked - + Unsupported - + Unofficial - + Recently Unlocked - + Active Challenges - + Almost There - + {} points - + {} point - + XXX points - + Unlocked: {} - + This game has {} leaderboards. - + Submitting scores is disabled because hardcore mode is off. Leaderboards are read-only. - + Show Best - + Show Nearby - + Rank - + Name - + Time - + Score - + Value - + Date Submitted - + Downloading leaderboard data, please wait... - - + + Loading... - - + + This game has no achievements. - + Failed to read executable from disc. Achievements disabled. @@ -5123,43 +5179,43 @@ Do you want to overwrite? - + Copy Address - + Copy Instruction Hex - + NOP Instruction(s) - + Run to Cursor - + Follow Branch - + Add Function - + Rename Function - + Remove Function @@ -5221,66 +5277,94 @@ Do you want to overwrite? - + &Copy Instruction Text - + Copy Function Name - + Restore Instruction(s) - + Asse&mble new Instruction(s) - + &Jump to Cursor - + Toggle &Breakpoint - + &Go to Address - + Go to PC on Pause - + Restore Function - + Stub (NOP) Function - + Show &Instruction Bytes - + %1 NOT VALID ADDRESS + + DisassemblyWidgetColumnTitle + + + %1 %2 %3 %4 + + + + + %1 %2 %3 + + + + + Location + + + + + Bytes + + + + + Instruction + + + DockLayout @@ -6117,4503 +6201,4533 @@ The URL was: %1 FullscreenUI - + Could not find any CD/DVD-ROM devices. Please ensure you have a drive connected and sufficient permissions to access it. - + Use Global Setting - + Automatic binding failed, no devices are available. - + Game title copied to clipboard. - + Game serial copied to clipboard. - + Game CRC copied to clipboard. - + Game type copied to clipboard. - + Game region copied to clipboard. - + Game compatibility copied to clipboard. - + Game path copied to clipboard. - + Controller settings reset to default. - + No input profiles available. - + Create New... - + Enter the name of the input profile you wish to create. - + Are you sure you want to restore the default settings? Any preferences will be lost. - + Settings reset to defaults. - + No save present in this slot. - + No save states found. - + Failed to delete save state. - + Failed to copy text to clipboard. - + This game has no achievements. - + This game has no leaderboards. - + Reset System - + Hardcore mode will not be enabled until the system is reset. Do you want to reset the system now? - + Launch a game from images scanned from your game directories. - + Launch a game by selecting a file/disc image. - + Start the console without any disc inserted. - + Start a game from a disc in your PC's DVD drive. - + No Binding - + Setting %s binding %s. - + Push a controller button or axis now. - + Timing out in %.0f seconds... - + Unknown - + OK - + Select Device - + Details - + Options - + Copies the current global settings to this game. - + Clears all settings set for this game. - + Behaviour - + Prevents the screen saver from activating and the host from sleeping while emulation is running. - + Shows the game you are currently playing as part of your profile on Discord. - + Pauses the emulator when a game is started. - + Pauses the emulator when you minimize the window or switch to another application, and unpauses when you switch back. - + Pauses the emulator when you open the quick menu, and unpauses when you close it. - + Determines whether a prompt will be displayed to confirm shutting down the emulator/game when the hotkey is pressed. - + Automatically saves the emulator state when powering down or exiting. You can then resume directly from where you left off next time. - + Game Display - + Switches between full screen and windowed when the window is double-clicked. - + Hides the mouse pointer/cursor when the emulator is in fullscreen mode. - + Determines how large the on-screen messages and monitor are. - + Shows on-screen-display messages when events occur such as save states being created/loaded, screenshots being taken, etc. - + Shows the current emulation speed of the system in the top-right corner of the display as a percentage. - + Shows the number of video frames (or v-syncs) displayed per second by the system in the top-right corner of the display. - + Shows the CPU usage based on threads in the top-right corner of the display. - + Shows the host's GPU usage in the top-right corner of the display. - + Shows statistics about GS (primitives, draw calls) in the top-right corner of the display. - + Shows indicators when fast forwarding, pausing, and other abnormal states are active. - + Shows the current configuration in the bottom-right corner of the display. - + Shows the current controller state of the system in the bottom-left corner of the display. - + Displays warnings when settings are enabled which may break games. - + Resets configuration to defaults (excluding controller settings). - + Changes the BIOS image used to start future sessions. - + Automatic - + {0}/{1}/{2}/{3} - + Default - + WARNING: Your memory card is still writing data. Shutting down now will IRREVERSIBLY DESTROY YOUR MEMORY CARD. It is strongly recommended to resume your game and let it finish writing to your memory card. Do you wish to shutdown anyways and IRREVERSIBLY DESTROY YOUR MEMORY CARD? - + Automatically switches to fullscreen mode when a game is started. - + On-Screen Display - + %d%% - + Shows the resolution of the game in the top-right corner of the display. - + BIOS Configuration - + BIOS Selection - + Options and Patches - + Skips the intro screen, and bypasses region checks. - + Speed Control - + Normal Speed - + Sets the speed when running without fast forwarding. - + Fast Forward Speed - + Sets the speed when using the fast forward hotkey. - + Slow Motion Speed - + Sets the speed when using the slow motion hotkey. - + System Settings - + EE Cycle Rate - + Underclocks or overclocks the emulated Emotion Engine CPU. - + EE Cycle Skipping - + Enable MTVU (Multi-Threaded VU1) - + Enable Instant VU1 - + Enable Cheats - + Enables loading cheats from pnach files. - + Enable Host Filesystem - + Enables access to files from the host: namespace in the virtual machine. - + Enable Fast CDVD - + Fast disc access, less loading times. Not recommended. - + Frame Pacing/Latency Control - + Maximum Frame Latency - + Sets the number of frames which can be queued. - + Optimal Frame Pacing - + Synchronize EE and GS threads after each frame. Lowest input latency, but increases system requirements. - + Speeds up emulation so that the guest refresh rate matches the host. - + Renderer - + Selects the API used to render the emulated GS. - + Synchronizes frame presentation with host refresh. - + Display - + Aspect Ratio - + Selects the aspect ratio to display the game content at. - + FMV Aspect Ratio Override - + Selects the aspect ratio for display when a FMV is detected as playing. - + Deinterlacing - + Selects the algorithm used to convert the PS2's interlaced output to progressive for display. - + Screenshot Size - + Determines the resolution at which screenshots will be saved. - + Screenshot Format - + Selects the format which will be used to save screenshots. - + Screenshot Quality - + Selects the quality at which screenshots will be compressed. - + Vertical Stretch - + Increases or decreases the virtual picture size vertically. - + Crop - + Crops the image, while respecting aspect ratio. - + %dpx - + Bilinear Upscaling - + Smooths out the image when upscaling the console to the screen. - + Integer Upscaling - + Adds padding to the display area to ensure that the ratio between pixels on the host to pixels in the console is an integer number. May result in a sharper image in some 2D games. - + Screen Offsets - + Enables PCRTC Offsets which position the screen as the game requests. - + Show Overscan - + Enables the option to show the overscan area on games which draw more than the safe area of the screen. - + Anti-Blur - + Enables internal Anti-Blur hacks. Less accurate to PS2 rendering but will make a lot of games look less blurry. - + Rendering - + Internal Resolution - + Multiplies the render resolution by the specified factor (upscaling). - + Mipmapping - + Bilinear Filtering - + Selects where bilinear filtering is utilized when rendering textures. - + Trilinear Filtering - + Selects where trilinear filtering is utilized when rendering textures. - + Anisotropic Filtering - + Dithering - + Selects the type of dithering applies when the game requests it. - + Blending Accuracy - + Determines the level of accuracy when emulating blend modes not supported by the host graphics API. - + Texture Preloading - + Uploads full textures to the GPU on use, rather than only the utilized regions. Can improve performance in some games. - + Software Rendering Threads - + Number of threads to use in addition to the main GS thread for rasterization. - + Auto Flush (Software) - + Force a primitive flush when a framebuffer is also an input texture. - + Edge AA (AA1) - + Enables emulation of the GS's edge anti-aliasing (AA1). - + Enables emulation of the GS's texture mipmapping. - + The selected input profile will be used for this game. - + Shared - + Input Profile - + Appearance - + Selects the color style to be used for Big Picture Mode. - + Show a save state selector UI when switching slots instead of showing a notification bubble. - + Integration - + Shows the current PCSX2 version on the top-right corner of the display. - + Shows the currently active input recording status. - + Shows the currently active video capture status. - + Shows a visual history of frame times in the upper-left corner of the display. - + Shows the current system hardware information on the OSD. - + Pins emulation threads to CPU cores to potentially improve performance/frame time variance. - + Enable Widescreen Patches - + Enables loading widescreen patches from pnach files. - + Enable No-Interlacing Patches - + Enables loading no-interlacing patches from pnach files. - + Hardware Fixes - + Manual Hardware Fixes - + Disables automatic hardware fixes, allowing you to set fixes manually. - + CPU Sprite Render Size - + Uses software renderer to draw texture decompression-like sprites. - + CPU Sprite Render Level - + Determines filter level for CPU sprite render. - + Software CLUT Render - + Uses software renderer to draw texture CLUT points/sprites. - + GPU Target CLUT - + Try to detect when a game is drawing its own color palette and then renders it on the GPU with special handling. - + Skip Draw Start - + Object range to skip drawing. - + Skip Draw End - + Auto Flush (Hardware) - + CPU Framebuffer Conversion - + Disable Depth Conversion - + Disable Safe Features - + This option disables multiple safe features. - + This option disables game-specific render fixes. - + Uploads GS data when rendering a new frame to reproduce some effects accurately. - + Disable Partial Invalidation - + Removes texture cache entries when there is any intersection, rather than only the intersected areas. - + Allows the texture cache to reuse as an input texture the inner portion of a previous framebuffer. - + Read Targets When Closing - + Flushes all targets in the texture cache back to local memory when shutting down. - + Estimate Texture Region - + Attempts to reduce the texture size when games do not set it themselves (e.g. Snowblind games). - + GPU Palette Conversion - + Upscaling Fixes - + Adjusts vertices relative to upscaling. - + Native Scaling - + Attempt to do rescaling at native resolution. - + Round Sprite - + Adjusts sprite coordinates. - + Bilinear Upscale - + Can smooth out textures due to be bilinear filtered when upscaling. E.g. Brave sun glare. - + Adjusts target texture offsets. - + Align Sprite - + Fixes issues with upscaling (vertical lines) in some games. - + Merge Sprite - + Replaces multiple post-processing sprites with a larger single sprite. - + Lowers the GS precision to avoid gaps between pixels when upscaling. Fixes the text on Wild Arms games. - + Unscaled Palette Texture Draws - + Can fix some broken effects which rely on pixel perfect precision. - + Texture Replacement - + Load Textures - + Loads replacement textures where available and user-provided. - + Asynchronous Texture Loading - + Loads replacement textures on a worker thread, reducing microstutter when replacements are enabled. - + Precache Replacements - + Preloads all replacement textures to memory. Not necessary with asynchronous loading. - + Replacements Directory - + Folders - + Texture Dumping - + Dump Textures - + Dump Mipmaps - + Includes mipmaps when dumping textures. - + Dump FMV Textures - + Allows texture dumping when FMVs are active. You should not enable this. - + Post-Processing - + FXAA - + Enables FXAA post-processing shader. - + Contrast Adaptive Sharpening - + Enables FidelityFX Contrast Adaptive Sharpening. - + CAS Sharpness - + Determines the intensity the sharpening effect in CAS post-processing. - + Filters - + Shade Boost - + Enables brightness/contrast/saturation adjustment. - + Shade Boost Brightness - + Adjusts brightness. 50 is normal. - + Shade Boost Contrast - + Adjusts contrast. 50 is normal. - + Shade Boost Saturation - + Adjusts saturation. 50 is normal. - + TV Shaders - + Advanced - + Skip Presenting Duplicate Frames - + Extended Upscaling Multipliers - + Displays additional, very high upscaling multipliers dependent on GPU capability. - + Hardware Download Mode - + Changes synchronization behavior for GS downloads. - + Allow Exclusive Fullscreen - + Overrides the driver's heuristics for enabling exclusive fullscreen, or direct flip/scanout. - + Override Texture Barriers - + Forces texture barrier functionality to the specified value. - + GS Dump Compression - + Sets the compression algorithm for GS dumps. - + Disable Framebuffer Fetch - + Prevents the usage of framebuffer fetch when supported by host GPU. - + Disable Shader Cache - + Prevents the loading and saving of shaders/pipelines to disk. - + Disable Vertex Shader Expand - + Falls back to the CPU for expanding sprites/lines. - + Changes when SPU samples are generated relative to system emulation. - + %d ms - + Settings and Operations - + Creates a new memory card file or folder. - + Simulates a larger memory card by filtering saves only to the current game. - + If not set, this card will be considered unplugged. - + The selected memory card image will be used for this slot. - + Enable/Disable the Player LED on DualSense controllers. - + Trigger - + Toggles the macro when the button is pressed, instead of held. - + Savestate - + Compression Method - + Sets the compression algorithm for savestate. - + Compression Level - + Sets the compression level for savestate. - + Version: %s - + {:%H:%M} - + Slot {} - + Dark - + Light - + Grey Matter - + Untouched Lagoon - + Baby Pastel - + Pizza Time! - + PCSX2 Blue - + Scarlet Devil - + Violet Angel - + Cobalt Sky - + AMOLED - + Enabled - + 1.25x Native (~450px) - + 1.5x Native (~540px) - + 1.75x Native (~630px) - + 2x Native (~720px/HD) - + 2.5x Native (~900px/HD+) - + 3x Native (~1080px/FHD) - + 3.5x Native (~1260px) - + 4x Native (~1440px/QHD) - + 5x Native (~1800px/QHD+) - + 6x Native (~2160px/4K UHD) - + 7x Native (~2520px) - + 8x Native (~2880px/5K UHD) - + 9x Native (~3240px) - + 10x Native (~3600px/6K UHD) - + 11x Native (~3960px) - + 12x Native (~4320px/8K UHD) - + WebP - + Align to Native - + Align to Native - with Texture Offset - + Aggressive - + Enabled (Exact Match) - + Enabled (Check Inside Target) - + Deflate64 - + Zstandard - + LZMA2 - + Low (Fast) - + Medium (Recommended) - + Very High (Slow, Not Recommended) - + Change Selection - + Select - + Parent Directory - + Enter Value - + About - + Toggle Fullscreen - + Navigate - + Load Global State - + Change Page - + Return To Game - + Select State - + Select Game - + Change View - + Launch Options - + Create Save State Backups - + Show PCSX2 Version - + Show Input Recording Status - + Show Video Capture Status - + Show Frame Times - + Show Hardware Info - + Create Memory Card - + Configuration - + Start Game - + Launch a game from a file, disc, or starts the console without any disc inserted. - + Changes settings for the application. - + Return to desktop mode, or exit the application. - + Back - + Return to the previous menu. - + Exit PCSX2 - + Completely exits the application, returning you to your desktop. - + Desktop Mode - + Exits Big Picture mode, returning to the desktop interface. - + Resets all configuration to defaults (including bindings). - + Replaces these settings with a previously saved input profile. - + Stores the current settings to an input profile. - + Input Sources - + The SDL input source supports most controllers. - + Provides vibration and LED control support over Bluetooth. - + Allow SDL to use raw access to input devices. - + The XInput source provides support for XBox 360/XBox One/XBox Series controllers. - + Multitap - + Enables an additional three controller slots. Not supported in all games. - + Attempts to map the selected port to a chosen controller. - + Determines how much pressure is simulated when macro is active. - + Determines the pressure required to activate the macro. - + Toggle every %d frames - + Clears all bindings for this USB controller. - + Data Save Locations - + Show Advanced Settings - + Changing these options may cause games to become non-functional. Modify at your own risk, the PCSX2 team will not provide support for configurations with these settings changed. - + Logging - + System Console - + Writes log messages to the system console (console window/standard output). - + File Logging - + Writes log messages to emulog.txt. - + Verbose Logging - + Writes dev log messages to log sinks. - + Log Timestamps - + Writes timestamps alongside log messages. - + EE Console - + Writes debug messages from the game's EE code to the console. - + IOP Console - + Writes debug messages from the game's IOP code to the console. - + CDVD Verbose Reads - + Logs disc reads from games. - + Emotion Engine - + Rounding Mode - + Determines how the results of floating-point operations are rounded. Some games need specific settings. - + Division Rounding Mode - + Determines how the results of floating-point division is rounded. Some games need specific settings. - + Clamping Mode - + Determines how out-of-range floating point numbers are handled. Some games need specific settings. - + Enable EE Recompiler - + Performs just-in-time binary translation of 64-bit MIPS-IV machine code to native code. - + Enable EE Cache - + Enables simulation of the EE's cache. Slow. - + Enable INTC Spin Detection - + Huge speedup for some games, with almost no compatibility side effects. - + Enable Wait Loop Detection - + Moderate speedup for some games, with no known side effects. - + Enable Fast Memory Access - + Uses backpatching to avoid register flushing on every memory access. - + Vector Units - + VU0 Rounding Mode - + VU0 Clamping Mode - + VU1 Rounding Mode - + VU1 Clamping Mode - + Enable VU0 Recompiler (Micro Mode) - + New Vector Unit recompiler with much improved compatibility. Recommended. - + Enable VU1 Recompiler - + Enable VU Flag Optimization - + Good speedup and high compatibility, may cause graphical errors. - + I/O Processor - + Enable IOP Recompiler - + Performs just-in-time binary translation of 32-bit MIPS-I machine code to native code. - + Graphics - + Use Debug Device - + Settings - + No cheats are available for this game. - + Cheat Codes - + No patches are available for this game. - + Game Patches - + Activating cheats can cause unpredictable behavior, crashing, soft-locks, or broken saved games. - + Activating game patches can cause unpredictable behavior, crashing, soft-locks, or broken saved games. - + Use patches at your own risk, the PCSX2 team will provide no support for users who have enabled game patches. - + Game Fixes - + Game fixes should not be modified unless you are aware of what each option does and the implications of doing so. - + FPU Multiply Hack - + For Tales of Destiny. - + Preload TLB Hack - + Needed for some games with complex FMV rendering. - + Skip MPEG Hack - + Skips videos/FMVs in games to avoid game hanging/freezes. - + OPH Flag Hack - + EE Timing Hack - + Instant DMA Hack - + Known to affect following games: Mana Khemia 1, Metal Saga, Pilot Down Behind Enemy Lines. - + For SOCOM 2 HUD and Spy Hunter loading hang. - + VU Add Hack - + Full VU0 Synchronization - + Forces tight VU0 sync on every COP2 instruction. - + VU Overflow Hack - + To check for possible float overflows (Superman Returns). - + Use accurate timing for VU XGKicks (slower). - + Load State - + Makes the emulated Emotion Engine skip cycles. Helps a small subset of games like SOTC. Most of the time it's harmful to performance. - + Generally a speedup on CPUs with 4 or more cores. Safe for most games, but a few are incompatible and may hang. - + Runs VU1 instantly. Provides a modest speed improvement in most games. Safe for most games, but a few games may exhibit graphical errors. - + Disable the support of depth buffers in the texture cache. - + Disable Render Fixes - + Preload Frame Data - + Texture Inside RT - + When enabled GPU converts colormap-textures, otherwise the CPU will. It is a trade-off between GPU and CPU. - + Half Pixel Offset - + Texture Offset X - + Texture Offset Y - + Dumps replaceable textures to disk. Will reduce performance. - + Applies a shader which replicates the visual effects of different styles of television set. - + Skips displaying frames that don't change in 25/30fps games. Can improve speed, but increase input lag/make frame pacing worse. - + Enables API-level validation of graphics commands. - + Use Software Renderer For FMVs - + To avoid TLB miss on Goemon. - + General-purpose timing hack. Known to affect following games: Digital Devil Saga, SSX. - + Good for cache emulation problems. Known to affect following games: Fire Pro Wrestling Z. - + Known to affect following games: Bleach Blade Battlers, Growlanser II and III, Wizardry. - + Emulate GIF FIFO - + Correct but slower. Known to affect the following games: Fifa Street 2. - + DMA Busy Hack - + Delay VIF1 Stalls - + Emulate VIF FIFO - + Simulate VIF1 FIFO read ahead. Known to affect following games: Test Drive Unlimited, Transformers. - + VU I Bit Hack - + Avoids constant recompilation in some games. Known to affect the following games: Scarface The World is Yours, Crash Tag Team Racing. - + For Tri-Ace Games: Star Ocean 3, Radiata Stories, Valkyrie Profile 2. - + VU Sync - + Run behind. To avoid sync problems when reading or writing VU registers. - + VU XGKick Sync - + Force Blit Internal FPS Detection - + Save State - + Load Resume State - + A resume save state created at %s was found. Do you want to load this save and continue? - + Region: - + Compatibility: - + No Game Selected - + Search Directories - + Adds a new directory to the game search list. - + Scanning Subdirectories - + Not Scanning Subdirectories - + List Settings - + Sets which view the game list will open to. - + Determines which field the game list will be sorted by. - + Reverses the game list sort order from the default (usually ascending to descending). - + Cover Settings - + Downloads covers from a user-specified URL template. - + Operations - + Selects where anisotropic filtering is utilized when rendering textures. - + Use alternative method to calculate internal FPS to avoid false readings in some games. - + Identifies any new files added to the game directories. - + Forces a full rescan of all games previously identified. - + Download Covers - + About PCSX2 - + PCSX2 is a free and open-source PlayStation 2 (PS2) emulator. Its purpose is to emulate the PS2's hardware, using a combination of MIPS CPU Interpreters, Recompilers and a Virtual Machine which manages hardware states and PS2 system memory. This allows you to play PS2 games on your PC, with many additional features and benefits. - + PlayStation 2 and PS2 are registered trademarks of Sony Interactive Entertainment. This application is not affiliated in any way with Sony Interactive Entertainment. - + When enabled and logged in, PCSX2 will scan for achievements on startup. - + "Challenge" mode for achievements, including leaderboard tracking. Disables save state, cheats, and slowdown functions. - + Displays popup messages on events such as achievement unlocks and leaderboard submissions. - + Plays sound effects for events such as achievement unlocks and leaderboard submissions. - + Shows icons in the lower-right corner of the screen when a challenge/primed achievement is active. - + When enabled, PCSX2 will list achievements from unofficial sets. These achievements are not tracked by RetroAchievements. - + When enabled, PCSX2 will assume all achievements are locked and not send any unlock notifications to the server. - + Error - + Pauses the emulator when a controller with bindings is disconnected. - + Creates a backup copy of a save state if it already exists when the save is created. The backup copy has a .backup suffix - + Enable CDVD Precaching - + Loads the disc image into RAM before starting the virtual machine. - + Vertical Sync (VSync) - + Sync to Host Refresh Rate - + Use Host VSync Timing - + Disables PCSX2's internal frame timing, and uses host vsync instead. - + Disable Mailbox Presentation - + Forces the use of FIFO over Mailbox presentation, i.e. double buffering instead of triple buffering. Usually results in worse frame pacing. - + Audio Control - + Controls the volume of the audio played on the host. - + Fast Forward Volume - + Controls the volume of the audio played on the host when fast forwarding. - + Mute All Sound - + Prevents the emulator from producing any audible sound. - + Backend Settings - + Audio Backend - + The audio backend determines how frames produced by the emulator are submitted to the host. - + Expansion - + Determines how audio is expanded from stereo to surround for supported games. - + Synchronization - + Buffer Size - + Determines the amount of audio buffered before being pulled by the host API. - + Output Latency - + Determines how much latency there is between the audio being picked up by the host API, and played through speakers. - + Minimal Output Latency - + When enabled, the minimum supported output latency will be used for the host API. - + Thread Pinning - + Force Even Sprite Position - + Displays popup messages when starting, submitting, or failing a leaderboard challenge. - + When enabled, each session will behave as if no achievements have been unlocked. - + Account - + Logs out of RetroAchievements. - + Logs in to RetroAchievements. - + Current Game - + An error occurred while deleting empty game settings: {} - + An error occurred while saving game settings: {} - + {} is not a valid disc image. - + Automatic mapping completed for {}. - + Automatic mapping failed for {}. - + Game settings initialized with global settings for '{}'. - + Game settings have been cleared for '{}'. - + Uses {} as confirm when using a controller - + {} (Current) - + {} (Folder) - + Failed to load '{}'. - + Input profile '{}' loaded. - + Input profile '{}' saved. - + Failed to save input profile '{}'. - + Port {} Controller Type - + Select Macro {} Binds - + Port {} Device - + Port {} Subtype - + {} unlabelled patch codes will automatically activate. - + {} unlabelled patch codes found but not enabled. - + This Session: {} - + All Time: {} - + Save Slot {0} - + Saved {} - + {} does not exist. - + {} deleted. - + Failed to delete {}. - + File: {} - + CRC: {:08X} - + Time Played: {} - + Last Played: {} - + Size: {:.2f} MB - + Left: - + Top: - + Right: - + Bottom: - + Summary - + Interface Settings - + BIOS Settings - + Emulation Settings - + Graphics Settings - + Audio Settings - + Memory Card Settings - + Controller Settings - + Hotkey Settings - + Achievements Settings - + Folder Settings - + Advanced Settings - + Patches - + Cheats - + 2% [1 FPS (NTSC) / 1 FPS (PAL)] - + 10% [6 FPS (NTSC) / 5 FPS (PAL)] - + 25% [15 FPS (NTSC) / 12 FPS (PAL)] - + 50% [30 FPS (NTSC) / 25 FPS (PAL)] - + 75% [45 FPS (NTSC) / 37 FPS (PAL)] - + 90% [54 FPS (NTSC) / 45 FPS (PAL)] - + 100% [60 FPS (NTSC) / 50 FPS (PAL)] - + 110% [66 FPS (NTSC) / 55 FPS (PAL)] - + 120% [72 FPS (NTSC) / 60 FPS (PAL)] - + 150% [90 FPS (NTSC) / 75 FPS (PAL)] - + 175% [105 FPS (NTSC) / 87 FPS (PAL)] - + 200% [120 FPS (NTSC) / 100 FPS (PAL)] - + 300% [180 FPS (NTSC) / 150 FPS (PAL)] - + 400% [240 FPS (NTSC) / 200 FPS (PAL)] - + 500% [300 FPS (NTSC) / 250 FPS (PAL)] - + 1000% [600 FPS (NTSC) / 500 FPS (PAL)] - + 50% Speed - + 60% Speed - + 75% Speed - + 100% Speed (Default) - + 130% Speed - + 180% Speed - + 300% Speed - + Normal (Default) - + Mild Underclock - + Moderate Underclock - + Maximum Underclock - + Disabled - + 0 Frames (Hard Sync) - + 1 Frame - + 2 Frames - + 3 Frames - + None - + Extra + Preserve Sign - + Full - + Extra - + Automatic (Default) - + Direct3D 11 - + Direct3D 12 - + OpenGL - + Vulkan - + Metal - + Software - + Null - + Off - + Bilinear (Smooth) - + Bilinear (Sharp) - + Weave (Top Field First, Sawtooth) - + Weave (Bottom Field First, Sawtooth) - + Bob (Top Field First) - + Bob (Bottom Field First) - + Blend (Top Field First, Half FPS) - + Blend (Bottom Field First, Half FPS) - + Adaptive (Top Field First) - + Adaptive (Bottom Field First) - + Native (PS2) - + Nearest - + Bilinear (Forced) - + Bilinear (PS2) - + Bilinear (Forced excluding sprite) - + Off (None) - + Trilinear (PS2) - + Trilinear (Forced) - + Scaled - + Unscaled (Default) - + Minimum - + Basic (Recommended) - + Medium - + High - + Full (Slow) - + Maximum (Very Slow) - + Off (Default) - + 2x - + 4x - + 8x - + 16x - + Partial - + Full (Hash Cache) - + Force Disabled - + Force Enabled - + Accurate (Recommended) - + Disable Readbacks (Synchronize GS Thread) - + Unsynchronized (Non-Deterministic) - + Disabled (Ignore Transfers) - + Screen Resolution - + Internal Resolution (Aspect Uncorrected) - + Load/Save State - + WARNING: Memory Card Busy - + Cannot show details for games which were not scanned in the game list. - + Theme - + Pause On Controller Disconnection - + Use Save State Selector - + Swap OK/Cancel in Big Picture Mode - + SDL DualSense Player LED - + Press To Toggle - + Deadzone - + Full Boot - + Achievement Notifications - + Leaderboard Notifications - + Enable In-Game Overlays - + Encore Mode - + Spectator Mode - + PNG - + - - + Convert 4-bit and 8-bit framebuffer on the CPU instead of the GPU. - + Removes the current card from the slot. - + Determines the frequency at which the macro will toggle the buttons on and off (aka auto fire). - + {} Frames - + No Deinterlacing - + Force 32bit - + JPEG - + 0 (Disabled) - + 1 (64 Max Width) - + 2 (128 Max Width) - + 3 (192 Max Width) - + 4 (256 Max Width) - + 5 (320 Max Width) - + 6 (384 Max Width) - + 7 (448 Max Width) - + 8 (512 Max Width) - + 9 (576 Max Width) - + 10 (640 Max Width) - + Sprites Only - + Sprites/Triangles - + Blended Sprites/Triangles - + 1 (Normal) - + 2 (Aggressive) - + Inside Target - + Merge Targets - + Normal (Vertex) - + Special (Texture) - + Special (Texture - Aggressive) - + Half - + Force Bilinear - + Force Nearest - + Disabled (Default) - + Enabled (Sprites Only) - + Enabled (All Primitives) - + None (Default) - + Sharpen Only (Internal Resolution) - + Sharpen and Resize (Display Resolution) - + Scanline Filter - + Diagonal Filter - + Triangular Filter - + Wave Filter - + Lottes CRT - + 4xRGSS - + NxAGSS - + Uncompressed - + LZMA (xz) - + Zstandard (zst) - + PS2 (8MB) - + PS2 (16MB) - + PS2 (32MB) - + PS2 (64MB) - + PS1 - + Negative - + Positive - + Chop/Zero (Default) - + Game Grid - + Game List - + Game List Settings - + Type - + Serial - + Title - + File Title - + CRC - + Time Played - + Last Played - + Size - + Select Disc Image - + Select Disc Drive - + Start File - + Start BIOS - + Start Disc - + Exit - + Set Input Binding - + Region - + Compatibility Rating - + Path - + Disc Path - + Select Disc Path - + Copy Settings - + Clear Settings - + Inhibit Screensaver - + Enable Discord Presence - + Pause On Start - + Pause On Focus Loss - + Pause On Menu - + Confirm Shutdown - + Save State On Shutdown - + Start Fullscreen - + Double-Click Toggles Fullscreen - + Hide Cursor In Fullscreen - + OSD Scale - + Show Messages - + Show Speed - + Show FPS - + Show CPU Usage - + Show GPU Usage - + Show Resolution - + Show GS Statistics - + Show Status Indicators - + Show Settings - + Show Inputs - + Warn About Unsafe Settings - + Reset Settings - + Change Search Directory - + Fast Boot - + Output Volume - + Memory Card Directory - + Folder Memory Card Filter - + Create - + Cancel - + Load Profile - + Save Profile - + Enable SDL Input Source - + SDL DualShock 4 / DualSense Enhanced Mode - + SDL Raw Input - + Enable XInput Input Source - + Enable Console Port 1 Multitap - + Enable Console Port 2 Multitap - + Controller Port {}{} - + Controller Port {} - + Controller Type - + Automatic Mapping - + Controller Port {}{} Macros - + Controller Port {} Macros - + Macro Button {} - + Buttons - + Frequency - + Pressure - + Controller Port {}{} Settings - + Controller Port {} Settings - + USB Port {} - + Device Type - + Device Subtype - + {} Bindings - + Clear Bindings - + {} Settings - + Cache Directory - + Covers Directory - + Snapshots Directory - + Save States Directory - + Game Settings Directory - + Input Profile Directory - + Cheats Directory - + Patches Directory - + Texture Replacements Directory - + Video Dumping Directory - + Resume Game - + Toggle Frame Limit - + Game Properties - + Achievements - + Save Screenshot - + Switch To Software Renderer - + Switch To Hardware Renderer - + Change Disc - + Close Game - + Exit Without Saving - + Back To Pause Menu - + Exit And Save State - + Leaderboards - + Delete Save - + Close Menu - + Delete State - + Default Boot - + Reset Play Time - + Add Search Directory - + Open in File Browser - + Disable Subdirectory Scanning - + Enable Subdirectory Scanning - + Remove From List - + Default View - + Sort By - + Sort Reversed - + Scan For New Games - + Rescan All Games - + Website - + Support Forums - + GitHub Repository - + License - + Close - + RAIntegration is being used instead of the built-in achievements implementation. - + Enable Achievements - + Hardcore Mode - + Sound Effects - + Test Unofficial Achievements - + + Notification Sound + + + + + Select Notification Sound + + + + + Unlock Sound + + + + + Select Unlock Sound + + + + + Leaderboard Submit Sound + + + + + Select Leaderboard Submit Sound + + + + Username: {} - + Login token generated on {} - + Logout - + Not Logged In - + Login - + Game: {0} ({1}) - + Rich presence inactive or unsupported. - + Game not loaded or no RetroAchievements available. - + Card Enabled - + Card Name - + Eject Card @@ -17117,6 +17231,11 @@ The saves will not be recoverable. Downloading Files + + + Audio Files (*.wav) + + MemoryCard @@ -19222,6 +19341,8 @@ Do you want to create this directory? + + Error @@ -19235,6 +19356,21 @@ Do you want to create this directory? Select folder for %1 + + + File cannot be found. + + + + + File path cannot be empty. + + + + + Select File + + SettingsDialog diff --git a/pcsx2/ImGui/FullscreenUI.cpp b/pcsx2/ImGui/FullscreenUI.cpp index d676dd8a5a..07da93002d 100644 --- a/pcsx2/ImGui/FullscreenUI.cpp +++ b/pcsx2/ImGui/FullscreenUI.cpp @@ -7715,6 +7715,7 @@ TRANSLATE_NOOP("FullscreenUI", "Shows icons in the lower-right corner of the scr TRANSLATE_NOOP("FullscreenUI", "When enabled, each session will behave as if no achievements have been unlocked."); TRANSLATE_NOOP("FullscreenUI", "When enabled, PCSX2 will assume all achievements are locked and not send any unlock notifications to the server."); TRANSLATE_NOOP("FullscreenUI", "When enabled, PCSX2 will list achievements from unofficial sets. These achievements are not tracked by RetroAchievements."); +TRANSLATE_NOOP("FullscreenUI", "Sound Effects"); TRANSLATE_NOOP("FullscreenUI", "Account"); TRANSLATE_NOOP("FullscreenUI", "Logs out of RetroAchievements."); TRANSLATE_NOOP("FullscreenUI", "Logs in to RetroAchievements."); @@ -8127,11 +8128,16 @@ TRANSLATE_NOOP("FullscreenUI", "Enable Achievements"); TRANSLATE_NOOP("FullscreenUI", "Hardcore Mode"); TRANSLATE_NOOP("FullscreenUI", "Achievement Notifications"); TRANSLATE_NOOP("FullscreenUI", "Leaderboard Notifications"); -TRANSLATE_NOOP("FullscreenUI", "Sound Effects"); TRANSLATE_NOOP("FullscreenUI", "Enable In-Game Overlays"); TRANSLATE_NOOP("FullscreenUI", "Encore Mode"); TRANSLATE_NOOP("FullscreenUI", "Spectator Mode"); TRANSLATE_NOOP("FullscreenUI", "Test Unofficial Achievements"); +TRANSLATE_NOOP("FullscreenUI", "Notification Sound"); +TRANSLATE_NOOP("FullscreenUI", "Select Notification Sound"); +TRANSLATE_NOOP("FullscreenUI", "Unlock Sound"); +TRANSLATE_NOOP("FullscreenUI", "Select Unlock Sound"); +TRANSLATE_NOOP("FullscreenUI", "Leaderboard Submit Sound"); +TRANSLATE_NOOP("FullscreenUI", "Select Leaderboard Submit Sound"); TRANSLATE_NOOP("FullscreenUI", "Username: {}"); TRANSLATE_NOOP("FullscreenUI", "Login token generated on {}"); TRANSLATE_NOOP("FullscreenUI", "Logout"); From d2c31df106da17ac98c5cbe351a81f1484212ac0 Mon Sep 17 00:00:00 2001 From: JordanTheToaster Date: Fri, 11 Apr 2025 22:30:37 +0100 Subject: [PATCH 062/162] GS/HW: Mask 16bit colours when blending is disabled --- bin/resources/shaders/dx11/tfx.fx | 2 ++ bin/resources/shaders/opengl/tfx_fs.glsl | 2 ++ bin/resources/shaders/vulkan/tfx.glsl | 2 ++ pcsx2/GS/Renderers/Metal/tfx.metal | 37 +++++++++++++----------- pcsx2/ShaderCacheVersion.h | 2 +- 5 files changed, 27 insertions(+), 18 deletions(-) diff --git a/bin/resources/shaders/dx11/tfx.fx b/bin/resources/shaders/dx11/tfx.fx index ba3b15a96f..7ed81c3d6a 100644 --- a/bin/resources/shaders/dx11/tfx.fx +++ b/bin/resources/shaders/dx11/tfx.fx @@ -852,6 +852,8 @@ void ps_color_clamp_wrap(inout float3 C) else if (PS_COLCLIP == 1 || PS_HDR == 1) C = (float3)((int3)C & (int3)0xFF); } + else if (PS_DST_FMT == FMT_16 && PS_DITHER != 3 && PS_BLEND_MIX == 0 && PS_BLEND_HW == 0) + C = (float3)((int3)C & (int3)0xF8); } void ps_blend(inout float4 Color, inout float4 As_rgba, float2 pos_xy) diff --git a/bin/resources/shaders/opengl/tfx_fs.glsl b/bin/resources/shaders/opengl/tfx_fs.glsl index bb004e3891..cf10c58f25 100644 --- a/bin/resources/shaders/opengl/tfx_fs.glsl +++ b/bin/resources/shaders/opengl/tfx_fs.glsl @@ -775,6 +775,8 @@ void ps_color_clamp_wrap(inout vec3 C) C = vec3(ivec3(C) & ivec3(0xFF)); #endif +#elif PS_DST_FMT == FMT_16 && PS_DITHER != 3 && PS_BLEND_MIX == 0 && PS_BLEND_HW == 0 + C = vec3(ivec3(C) & ivec3(0xF8)); #endif } diff --git a/bin/resources/shaders/vulkan/tfx.glsl b/bin/resources/shaders/vulkan/tfx.glsl index a7050b193d..519af4681b 100644 --- a/bin/resources/shaders/vulkan/tfx.glsl +++ b/bin/resources/shaders/vulkan/tfx.glsl @@ -1045,6 +1045,8 @@ void ps_color_clamp_wrap(inout vec3 C) C = vec3(ivec3(C) & ivec3(0xFF)); #endif +#elif PS_DST_FMT == FMT_16 && PS_DITHER != 3 && PS_BLEND_MIX == 0 && PS_BLEND_HW == 0 + C = vec3(ivec3(C) & ivec3(0xF8)); #endif } diff --git a/pcsx2/GS/Renderers/Metal/tfx.metal b/pcsx2/GS/Renderers/Metal/tfx.metal index 48cd9c7095..447d3018a2 100644 --- a/pcsx2/GS/Renderers/Metal/tfx.metal +++ b/pcsx2/GS/Renderers/Metal/tfx.metal @@ -890,28 +890,31 @@ struct PSMain void ps_color_clamp_wrap(thread float4& C) { - // When dithering the bottom 3 bits become meaningless and cause lines in the picture so we need to limit the color depth on dithered items - if (!SW_BLEND && !(PS_DITHER > 0 && PS_DITHER < 3) && !PS_FBMASK) - return; + // When dithering the bottom 3 bits become meaningless and cause lines in the picture + // so we need to limit the color depth on dithered items + if (SW_BLEND || (PS_DITHER > 0 && PS_DITHER < 3) || PS_FBMASK) + { + if (PS_DST_FMT == FMT_16 && PS_BLEND_MIX == 0 && PS_ROUND_INV) + C.rgb += 7.f; // Need to round up, not down since the shader will invert - if (PS_DST_FMT == FMT_16 && PS_BLEND_MIX == 0 && PS_ROUND_INV) - C.rgb += 7.f; // Need to round up, not down since the shader will invert + // Correct the Color value based on the output format + if (PS_COLCLIP == 0 && PS_HDR == 0) + C.rgb = clamp(C.rgb, 0.f, 255.f); // Standard Clamp - // Correct the Color value based on the output format - if (!PS_COLCLIP && !PS_HDR) - C.rgb = clamp(C.rgb, 0.f, 255.f); // Standard Clamp + // FIXME rouding of negative float? + // compiler uses trunc but it might need floor - // FIXME rouding of negative float? - // compiler uses trunc but it might need floor - - // Warning: normally blending equation is mult(A, B) = A * B >> 7. GPU have the full accuracy - // GS: Color = 1, Alpha = 255 => output 1 - // GPU: Color = 1/255, Alpha = 255/255 * 255/128 => output 1.9921875 - if (PS_DST_FMT == FMT_16 && PS_DITHER < 3 && (PS_BLEND_MIX == 0 || PS_DITHER)) + // Warning: normally blending equation is mult(A, B) = A * B >> 7. GPU have the full accuracy + // GS: Color = 1, Alpha = 255 => output 1 + // GPU: Color = 1/255, Alpha = 255/255 * 255/128 => output 1.9921875 // In 16 bits format, only 5 bits of colors are used. It impacts shadows computation of Castlevania + if (PS_DST_FMT == FMT_16 && PS_DITHER != 3 && (PS_BLEND_MIX == 0 || PS_DITHER)) + C.rgb = float3(short3(C.rgb) & 0xF8); + else if (PS_COLCLIP == 1 || PS_HDR == 1) + C.rgb = float3(short3(C.rgb) & 0xFF); + } + else if (PS_DST_FMT == FMT_16 && PS_DITHER != 3 && PS_BLEND_MIX == 0 && PS_BLEND_HW == 0) C.rgb = float3(short3(C.rgb) & 0xF8); - else if (PS_COLCLIP || PS_HDR) - C.rgb = float3(short3(C.rgb) & 0xFF); } template diff --git a/pcsx2/ShaderCacheVersion.h b/pcsx2/ShaderCacheVersion.h index 66693958f6..e4de26746b 100644 --- a/pcsx2/ShaderCacheVersion.h +++ b/pcsx2/ShaderCacheVersion.h @@ -3,4 +3,4 @@ /// Version number for GS and other shaders. Increment whenever any of the contents of the /// shaders change, to invalidate the cache. -static constexpr u32 SHADER_CACHE_VERSION = 60; +static constexpr u32 SHADER_CACHE_VERSION = 61; From 1066e8a5e9df8f602c851160b196fe71924e7882 Mon Sep 17 00:00:00 2001 From: TheLastRar Date: Thu, 10 Apr 2025 14:16:46 +0100 Subject: [PATCH 063/162] MSBuild: Adjust how MSBuild handles Qt ui files --- common/vsprops/QtCompile.props | 38 ++++++++++++++++++++++---------- common/vsprops/QtCompile.targets | 2 +- common/vsprops/QtCompile.xml | 8 +++---- 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/common/vsprops/QtCompile.props b/common/vsprops/QtCompile.props index 68ce622232..1eb8c53295 100644 --- a/common/vsprops/QtCompile.props +++ b/common/vsprops/QtCompile.props @@ -59,23 +59,37 @@ - - - - + + + - + Condition="'@(QtUi)'!=''"> - - + + + + uic %(Filename) + + "$(QtHostBinDir)uic.exe" "%(FullPath)" -o "$(QtToolOutDir)ui_%(Filename).h" + + $(QtToolOutDir)ui_%(Filename).h + + - - + + + + + + + diff --git a/common/vsprops/QtCompile.targets b/common/vsprops/QtCompile.targets index 75ac29f304..ec70459f96 100644 --- a/common/vsprops/QtCompile.targets +++ b/common/vsprops/QtCompile.targets @@ -1,7 +1,7 @@ - QtResourceClean;QtUiClean;QtMocClean;QtTsClean;$(CleanDependsOn) + QtResourceClean;QtMocClean;QtTsClean;$(CleanDependsOn) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/bin/resources/icons/flags/Other.png b/bin/resources/icons/flags/Other.png deleted file mode 100644 index 504fd9813d2a93dd8b1c67a57fd3d71cb8425180..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9805 zcmZ`EBiNAchv&(b3UPPEP;+{hOJYS)i@T7t+*yJ0SMM<@B98S9wbHs<=sfP zuR?JWgYOC0fjR_Z>?cuiM5BJC7Lk-s4kYj>B=H0)y#J*Gm@3n{0CJGk2z+XZcv|;O zP&aUz;EaWMl^5?}=*q>*7h?+YF%ZOoE`S0VY(wG7QQ@Z?`L#!${j*otYi6RlAeIx` zp$|xn7E1LP)y>pulIMAVuF?(sa*cB)+acWwSq<5TfcV@9iiVV;xy5zRJgTnQG+tRV zP)x3fD}%6QQJZ$#)zV*Jf1mSM^FXFr=A&zg)Z-g>>pVGW)%G3=Twz5}UW-Qnwcio2+MEkQ-}?agHIG4I=41GQ2jpfvpri>JZ% zkhSNHx%Od#XRvL3j)$>zEm=Okk#=VlkI`z{u8UI&>^-W}{o?p-iKC1vAc?S|S84+OK3>y?_B0HYAvZm?Je7+s)v{pPhL^n77n@ZmA0+#4e_~>9y zZYAK+{5LypWdl8?<7UjgRc}p6L_c>=E{5^h#{I>?#4U7F+N?HVwGbyiO{b{x(2nHd z0$&%7whFJJz6BaLQjZ*e>|K^y$-hQ1WF0E3%i$p^#Pu@QV{M*>h{b@UmNf*D-5>DL zHMjCRMP5{F!jt}Nyic0350qC@l}D|o-ia2Iv>sV?H2R-Z$2 z3PD=UOX#PJ;~+>f@ob4+Yya<2447__4}+R%<-9KqmO~3zlm51iiyt6!ujryhn|q!U zZm|3n0TVG92w}T&?L$k+86rhhGTxafVIEs_XO!Fg{cTHHxAEzyVB(B4(Mm*kbs&46 z*|2}4nRuejI3kA5v6kzlqxRzyM+uh~&N@eA z*y6W#W@)`XiUmq72qJoFQK{Qi-lPlp)NJtOg=Bga;jR(Z=w=fcZyb@dD1=DUU zDT?jYYC%yxjna`qx42j4Q8WFV9qK795!VwIKNv14zD!IgA4Y5{2|nkH(o75)G`MJ2 z?fsLpyYTM7U|Ovv;xBn5uOW^jH9C}b34YEH`!9}CE%KJ{FwL(lHCE!<{#&B*smy&CAbwZBDKdG1Q+g~70f*+-K$N~2 zqp;gA^HV~decx)8FBwTTA(&7ryuv@KAzOOVqgHC7S;SxUq{oQ|-FCuOa-pwJomWrs z)$}HJ;|e|3>#W0#PyzIXPf{QLbwnS7-k1qy!p%jrHHio39{rI@Np07;Ouih6Kva`2Z3veIF)AiK_zC)g1&GS611%_m1+mofba=$3*dNRw~2mBMy z+RwwLmb{w#TLCIV#ko5=q_lx)PQ&8%pr*9Rg*g2&(}vh2-Gvj@akcAc%U@$p*G-#a zCw90SLm^~RjRu>=mEbfOk7m-2VSptnqcNCzjNyL4{SC< zO=|gLm_L3vPsh?cx*Ji<9dSbhi1A5$;WfbhF5m`^}NAi=rdIcq- zR#@kBqfP{DRzI_oZmu^Apt=0!1fsJG40J7rWULGLO83FrbU0m)rXqHDqphtKJtrg9 z`}C*oKgXMZYal)Ih{P>v#`X@H&~Hgv9{qW$zOd(wk7yH9_VMGnLW?B6(U1x?)+ZK6 z_pSHppM`gRB?3B0ZGX;*f6uN+^huiUSXq}+0f%nu*P}FGbjP?HJt>#HM}PFffO7)f z^Gg?N!)KzHa+rO(G{vN{hqgIki=eK~!0l;Z>nSt^AV=Djs+-u~P<5V1f0O41u7~3{ zYv9RFgaY>w={OarYT{oX7Kh$lS5??2nyBxTnz-b-xpR2Dw<;vW4VV1hC5ls;vl>ny z9oV~jh3mSdDCUa>WUqCjN><5T9I>-{#7LO6MVl6}?zxP3Pbr!hYes5ypZscY zToQTxX1C(<*_{tc9z*&aPQg^SeM;@{CfYXVQ1woD2vL<6fhpA)*bSabzHKndhl=@J zkXS(Nz!3V77)`y&p#Ww`(jsBvystaR7nmTLEm|RviOj0}^YU7J?iU#rT2)gW{dG9- zhUJ}(2r>hiyM)i@9WlSJ7xs5lq_e~0pfFiwNKin`2U{8m-zhw}aKoz`uk$Iiga+Yj z=qxRr$Z|IO6u#G?0Wo`>T*{W*Tvjx*Y^x7ewoXq+ST#G8_RUe(4tc|!jIps-{C>}Q z5BE<=nod4jAsapM)MudZPQw+8lv`_J>8PSvc^V<10rS&ox@)m{#qy(D&bEto{MYB>r^nMa;uYl3Xf}WVVhz8Az{+6Uw0e_0Or98`yT3 zY#l>YjY`-rsnuPYi&rqSnwhyh^uoW;qv1&WA{AFzRCybIl~@Un9d<8e+$q*7HzX4y zL>v`A-vHKRnbdFFPN>Lg%4iXdP#o*_b^Y|wqZX{et%)ifXV|%8`Y-V5@xD5s@Z*^A`by&H-$0B=ZB8Q zhVAY>1Ukge#9RAIue&kSHk$mo()jq(GyMm+u2|tpL9qUjZkB=PqJ(R%edM~xaVm|D zUn9qdST`qAJ#zh@DZ&0dmgikby82Ef->a#W(!)KG&HjXe=V!{0`ox87cCfU&Q%uJY zR}jQRax}IhfKWQu187T3KN3s8@}aTr{T$?nGhZ~V?2M3uJdz6TZn@vAg0{Mishuh$ z5*9eg5(UOBnTW<4k-YojPcoMcj@K^VcG}r~*Ocq|=gr=Yi*~sbF&7P*AfKVC6lDf% z`JxQ$G9a>jWlr&P&_Y%e4ViB&lpTBlIxSN`KQh&GERBD`fA}%Bdzw^CK>s=f!?iUa zrt*qbNZ1I>==&V3_L~p?%R_fN1U+i?rH?3?!JDB>mW@ zr2kzIJQ#zt8Wi|-qct-^OGxV?RY0h)7l9R}(V*H#j z$KPQ*wBS6P$>|F|X*jZ-5sB4}BDZ~wXK_)g-hC~8GQRvHg)6nYmBu*vo=c%GiI$EF zt?maI&Nm=?0N)E^5;|}U0X_ji4wyuGRTvaZP6J|KD3O}mtohx(b|n{bQ0z6qIrA-8 zy^L*hpi7sJYs9p6->qq~aYbqiG#!Q|RO;q?tppZ*4k?N^vW+StS&K=Xsm z$-cK7(ok#?-h9{a=TZ-NmFLdLC~X`Y@UpD?%-s$IpR?4;WwnmNukmlHWMoNPcKzuI-z@IxV=fdsv+dnzLOI zaPi>D`u1r22ZLmn(yiNu+_lk>ZwARz?95`P9C(XysuaAKcn*P{zKag7;crZUjH8pb zYZe9?eKbGc+qVRAT&652fVB00#LXn?hk^16Py)Vmq}Rd22JyIHC>5C(uysVu9K=%2 zO`18Tiz@}Sgp?s4ib{XwO?#mgw~WumpeT+JewLPl!YubelP`V11S^)zDU%H)mm?%R zSN9c$2lA=di;OaS8_Y>s$el}NSk5)G*fw0Pp=3v)+T8Boko`q>9b`=7x$i_8e(<*C*vo;E=wW&KUrb3$pH zy`EgthDGeX!XWAH1{%-_uN`pgPPHmY{4xBiuI5p?e>ewgF5~cgUnab=k0KknvyT|L zEh&|+fhk&`Y8o5;OUs{lxp14vv2W&cM4lym}zp~1eM{RW1dnF^rG}n>- zjOaE?hN@eGmVtx8BRj)4L%AQF7nuf*1x(fY*M=+@y)ihFWD5MO`63UFHZlK2XsQKBa{(76R(`fsX$P? zV)N*_Hz)}$$S{2GlB&>K3(bJEnk!Z637XP6lcCCo6;0A=IQbbcR0rei-EgCcOb)eG z#G?__{j?gf3xa!gy7Q-_kEN-dJ%PXV@*tAmQY!!^4aVV4fgpwERQ{dTYdTF%uJzv{ zg*%7*iVfEh#+QSAP-K)D&E4LPc)uMi8fd65lJjCy$>=K&CMV~BdwpGzU!5+`XFJR9 zh#e04R0P_2JkdV67XPg+ILmuWiJ!V}!-|Nrol(Nug%ufXs)#OC|&PEAq+frI}7w8Jg{PgKr z=mFqN%);iU-G3q(cX204VVP0)FLi_z>2y~49~^a0>?4@%lOIC+yu;Zt3^$4^`YNytq;K2}k8 zsr(fA^{eF>K}ulIfLX#EGw`G^A!Y@1$v}?dnE48MI?^a`s36oG|A7Q- zDO&)KAW#QF`p_?oPhywr=Q78}&>)*A(IWqhxcFvfmd)IwAdT)Wdl~J!fJQ&%RhF?b zlKc0$@crhGM@;H-kvd(=_DM;&YZJI4-QK_TX}zz|(h#(-^xWAKf+aUzwy~hd{KGKs zAwl`V-AmBQnLY3YXCUYk8O!FvAUO-7U}5A5L1ddox75W@fIlv%?3rp(MM9kW0bC#> zt`|L1=IqWOZD$jnlKU#d?wCPvhI8I;d|M9D0u| zjV5S!C~}_kb$nbIlgLwoB%Pr<$WV2W?txlhPKuwq(L^jQO^91?2p;mQ!U>lZ0w@^c zJ!0e&5i2RWGOKVh`3&@jQX1&|?S!laGxqPRyjkq{r~i1o`fF==Zo!OzyDTjU$@vMf ze!xcf3~vbX-YcFUBFVwr6`(Jm*jfAz(406ow{{55U-?`6(jOq^WD(cC7%O>CWg&M3 zt#0yffcA2h&4wNN4tSUM2Dk#qqxoZ37_fx;56f!6R;OsPxz|T0?ht_XBF)~}&NSs2 zwL|a2H$GCC)V&gB&(#uT;+Xik&JrA@PJ@mBubI@@1Wp;aIap8oB+N*;Uz_JJ>F9I+ zNPqzz5}bQgN^6+f!eTaakm5LnuU2GEfLf+C217g+Hl znZWn_pyXOTAc34Gcz2HD|AnM}@8BCn3tS#{8V3gfESAg1zyt-(A^CVr}oWKA-(%(j|VRm=hYDT2)pieI>1WRm`=iU?KH(HPC8 zP|D{$C5~mAe`KUfWF6!TR9uSj-kE@8f7)K8dd$cO(ZZpcd@R6cJXgwD05M$g^-nn8 z3t?~wjvHhkYi3e;Ejg}%FAeo+S06w|CqWYzGl7Kr_^bJlMF&^Bs{

TsU&b5pK1U{~;*HUYZjjR%f_DoeY7&q{mOs`{(rS;9yu94pbI%`Q#AFY~{HU5D5`p1u;7sce(<*mz z-EG@j@xu(OwNogek{r{`z>OCd1dCA+e~FZyo% zLnMM#b!saFu~}^~4VRZxPg5qaN z9`v6gh9P&%_~)0{v9M_(G92C#DE7^0l%@1}f@~AB>5y^`6SU(g(;zbxz>R)0Uo=Q2 z=*jXcVwxK6XgZvQo|EGd4&A^S!ZhA17Jkj7gt)Q(N~?4hHzA+dY{hhd89RW%VZPNrbbmC&|WBea11phtbw?bZN zZSRsnir7sVk?XbF(kLP=hge7Eo>uWxOBkFI5;hl@zDapq&Jnu|95WsL>Hec|CRpnU z7cuhmKLwh*!*Dkv_kvDdra?-~aQKEmS=$hPWemC3-F?+(hti^SP!4H30dYb0$%#oo ze2}Et9eCt}5}CB<;(&GBXCHkDh!gVKX4;`3Z&-6qi}F-a$N}auH#2;F4?l=d*lb+{ zolkRb9ZyTb{7mEI&c0yhmI;`6j)Zoc)C_*D&mQ`X#hJu*43y}97<>sB zk^Hyqei_r5X)NtuBKIUd2J`CLX0rsWetO5Ma-UbrY5i~{0Tl}kp)5}+@s%Ii*OUT?Xdk8?ftXCgv9OTO4nJS+pdAI1d!tGQ>G zyw5?T8jW>$){zcra?E|IwB*HcdherIT>qQ-IiBF>pwz#1*SpjyT>*jRSkj%iBmtG4 z_l<}7DkAp~@kkwS>|+-h$xVKLlWJiEL2tk`LQ%e;pLd1u<9d&Ph7P)QPW&LZvR^VV zp)$bdXve?^{j;*H{TZ`K7=g@lE5+1w|f9oke>?)jYjDs2$b8R z3B811W&!0qB~96d*#bcI=-QkdWfH-?Nyia*Au_n0Lv$9kuGd49VKE_5uz#}kL5s{3 z3Y%KfXWU(FJG4cdf_73)VMa!_h#Az4G3jIo13le4R%Kc#+@?OF_{5ikdsFXwkMQuD zkgV{Kf3MEFtiup9-1@Mrd1IM`;G*|w=b6}doBA@;9Yn`Q6L8xmx$YjEa1i3uVzqrW zaIo(Zi1O*Sdz}jMiu~U`$8zLFo%#Y0h2|qQq?>aPS((X!sxN1QeLS1 zi7U#=NSx-dXMAK&SGtrtXuoW1lpve@!{1bn1hNV?kkR2SNRnSl@}+^ne;Ag|RrWz!1E>=gMMj1nAR1H7E9{4oXOb{OI@kb z8bE@_6XDtEXZTu??|pIA%7ZIsJ6$Qr?F`X$G!RACMREf2qH1Tl9qFdt0qs)}0=0Gx zI7-D*`i-gH8giE6Yi$t=n2>SNVZs~d^LlnkLE2J3a87~s0l);jDoO(mbFyc^gkW{? z-o@$#8uk_L7Nc9i#&T@}FRJ3VMPJ$oG{Pt5ng_7y*4)zMaf-tibT|8d#lvm~AJ+Rm z;DLJgWQUImulxWRxlR!48)9*Vmg*S!$f%mFXr)_&&;>w>QmDkw|TBHm2QS&7C& zkOJR5R5IEi1C%Ny;|i*W9_8tWT~*^P?2cYl>6~%q2rTFxtQ0M+5vb;(qcCg+BGfmG zY<%hoyZ(s{PbHaEiK17xn|Qx}jVr12sR@d>xO;B2aWkm5fIg2KA3m42(!_9{k30AI zF@aEZr9nPDJJG+WR=j-w{i_odZT+y`TV&*N#kH}-FM;n-ZDBSvM2B!1v|16E&A3`jOobM?Nxue9_xgi?H_%p#9GbUkd$YyN36^B2J#RW zY>a`-U;Zgiwe=e#VUBB2)9c%Y& z+6zTD)XSGVE=+$njBRu}`cO&hrG)U;gHMZfO3EIDW_~*YtHJF;l*@_DRdG56^{|0F zyGd5R2U5-ZMwouA)b^s!fC~nU%p^ zD6fmGnUXKpQrWt{Z9-i+sc6;_@S67fFelUzmB^dup=gh?Bt6x^dJ5V;vfxX1>qC`2 z!mC_9%qW{MP_1xjVF%HfF`xQ>18*B4&y-ZdP_r!U=%_&-7mvheHfw{H?=Gt{Qr@82 zY*N>((R52}Yn%o%LjzvL_GWzO2a+BSU3@$6=4pfGR=Ry^8r+TR@i~$2J_S$?a6EE|An0)rTDrEjOvNXWH{h*$GXe3$=$wg_KU0-MW z?*0IgjKDzjljp9f-VI2EcC> zD~R`$%XtA`e;)NvPqsTr2E43gr+lQ0*Jw)rlqu>OF?ZSnyl7Wa*4 z`9MsQVSR@1@>yx4oBZ#2^ufd<`ssm;Wnjct!|0)sZCbi@rHSg0u%f=P&ug~0qm;ta z1;NIQ{(mR!xk>@3n%0;Q$A|24lYJfJ)1|aBZ2$TvO^2G!amj;ZgQvfgfj0+RYO~PR zf0?M?mjO3nyW3?BTdxAC2^1sDaO@S=76>vYN9TORAT0oG+|jh^eidwp4*=l7sRk#8=4cgv>1i0Rz75H2V@uxMC$Rrhe4%(C Wy+nXF* + \ No newline at end of file diff --git a/bin/resources/icons/flags/PAL-A.png b/bin/resources/icons/flags/PAL-A.png deleted file mode 100644 index 8052c633152d3e8b567b49bff25b5c0dbd0dda38..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9294 zcmc(Fc|278`}dhMi)Ad?*NR(dk*&x?2$f{JTkTA#DBDOF`$16(-E9+?SE>wR6H^SLhPyv$tt)4^I? zR9O@N0CAf``;P*E5Ci~N7>7j>&8&zNbi(;q*joU=%XA{|6dqk;LXKKn0;TP$-_eEi zpLWL&04nFlfi0`6tA4RhM|wwDF5$A=$TlpQYUe$YI3#1G@hVHtsRwje}o1fMUZ?%d4H9(t^3Rn)R3|%VY_9l7= zST9yUrAph=uN*JucFCAk4gl-EvNKlKtpm}V&I>`v>jKu^36w$_K znDj*=g$V5uZWrT<&;TP0DIRHta%YJY6=;muPULPwL6B63yoPe;i4-lUkk~HDwLw8F zsTz3%vb)wG2)i0#5u5C!q1a^x~p#3oYQpfS;QA*wVA z5=h0!C8!7uio$DtxL`gKS{v>iva zL_rZ!9F+0aS#h^a3grX$PsNQIJT=L^eT1zlfAt$Xlcx zL%oQCVx&8WEL4Q%`3cexX$PtyFDQ$kdWZtTn|a{iZiJLDhSeEWK;C1LrBpXilxV;k z2~rBegosP{>Im2ZCnV(lQ5ObU)~Tvc^l5+(i?u1i3ac}zfcz}EEu1D+o#zF0VIBS; z=^?jbG3}D1WTYCpzKpNNz}s-ic=sM%SQ}_pBW*|iM$ZDIfQ=TI3^d8L+ny#;UF`*_ zU{pORhv0EOI4FuR6Ewh_MqRiKkkBEiA$!myC6K;^F-*=oU056=p-oanB2Z8o`H)bE z$@xbYw#P{9ASohmQBWSKPVmO$wCTdB7zr(sEV9G{v+KR38~*}2nBikdTY}J1QG;F5 z+;MM&Griva8H}0Na@wv#jvYVx4&Pel{-PRMx_eztL67za2}KW3++kh{x}PPv#x-if z7&zgCYeAX9X~aw~Db@eK#Oq$iTckM`dVoyEyQokd3tb;PwB*r{VdS%JK2k?AlOft4 z)~+^z_fXZN0Wy09DLUerjP2XmtE~>~9T}HS!MvVet6sk%nfV7g=yo8H*dGNtqM3}t zksf7|4(i`dcu4EfzWglj@pCKA%m}rBd7vG;qBnF8>e$s%*hji@Qz1+r=AEo;di72; z@@xj!ste;BDd!cy)}XVtzJAQS-Y_MUT-D@S#>lp6zFsR#j@82Xi?5&ip@Yd}EZ?K| z&@J$~=mz>D1>titb=?aOH|o(&=;W8=dyD3**Q160+f>n`EiBAU)fMM!h~_Bi(JKFq zqbKm?-=>@%O->hev=Ysc(W4RH)K*^J;A+Lnc5BOsI0*A}8UkmQM>ca{<;b8t9t)v4 zk|{uUpAk(`JHM-R8!)#zZELpr-fWG2n&+=VaO ztM$-LJj{GX0}_as2_u16Kth^YwHP^8rj@SA6i&DfOc*tZO`p*aUOR2EHJ{4}2rb#`TV>^; zw_^i(s0@9XOUjM6u@gouYwqgQ3ouXfe!e|jALodc>!w|=yHbCH6PYMzfp-+kWaO7lt4@v`Yo|Mqo+ICZc@>3n4GAskPPANy zX0Ka?S)yL}OvVS39d5%rpO zHZ7DVqnkHhR)Aig2bcHj!2njZC#dIy0Sruf?%U$ECQ$6zvhh8J3}GbRKo0dO1V!X0 zk=ebbmDWA`cB~H&(}PFk>DQb(QDAOZ|K?4!H zUTaBEMB!wu>uWK^UU+%$>)f;8%r!RgQtjCw$VI(A}y9``WzbPSJ*tG61fQSe~Mtu5pZ&ZD#UP4eE zKG6Kztq?mSL^E1r&Yb1)PYz7!rlGoWn3-g+4QpuNKLd;Y`9E+%b!rAu1-1~L zJvziY!V)E8c0l-Y^yYwy|DEX!pnCt}{trwd`9Hb;m5CAi9oQKllK*>i2ZU}5{LTG8 zm?{0n=hHno#gj-<#sxfDKrI18bQB^Cu9Z`;S2Xy00?QZxS~}H`30haA3%YA{hO9ri z*#{*n3snoD<&_wQLq{yDyk`o#)-Vbbp*6Xma%(+O-{czfFM$_*r_tOGYUi(&;#2>9+plo zlh8Iub^Es=-pg>dlq|`2GDQ<`Z>d@0zn5NKo-Ivlz@j?ZO&5D5b@Izf^TX#QdH`&3 z*1p~R@&_G$Onz(ac?i=}qO?4=jv;i9p4&@*F*fLfHs11u5#F#QFr(8l`PHcl_0heO zo!+U7g%&I8Y70MfG^BgaFwDdIRyuA$)_h`Mq>g19edBbgc7BHdyR?!tZxYW~YQV}2 z{w;<+ShzS!oS9jf*3yaa5q>+#@;CgewZ{oyB`b9C%x1#ddtscBzSXKyq0^R*+37C) zWVk4;Lg%G$;OQj6s$z_2wTVjZZ3jA$`!JknPm$ThVI}{H6rsk=NlNe>M#3jHYDf*f z7vuZgFyqfpjKv0ptPTcB(Lz>LGm+irn7Q5@r9J^KHk{`$8ve`Z;<`2N*t3U2Ox9#v z1QISyo2SWKM-HB2Rt+yz-C^K|58F^Qc5zCKV~b{Mo1^kY0?#BhbjCBz^K|A;5fZX; z^Ox3tB42>56BgKFL$fCGjopwFZfZnn8D=?y$FPEflF`CZd?R25o+-mkaRAyyOW=m<*9+aM=R5P}C|C8rBy~Y~v zBs2C(>j_Fm`;Ur%%Nn12O{oQ#F9d(Qn0iTqOLUiaa*vvxVv z8ITj)7FWjS&Dkr>Yfp0|Inb(Rl`u5RLAT?@nnT~ezAc>I4KGG#>&+c=<6Z0kGLe<2 zh)?6;alM6UE8X{K{ULxRR}kdXZ`#g(% zx1=G-f5-^Vh}48}j1}z{L7#(~%WBiKOclrpS=hRmpGOvXX>aKj7)@@&ax|q_G*AQwEAFpcA@>o$K6+Ks zKIr6y11h!%0(!HF@#6#Zv-L@YIL2WH8wK2DTxsIdKSHx-7ax90RnsGM#3@DW&NCFN z%Ba!Dy+^*jwXGGOt{FRfr9LC1cZ8*KP2~VtET@)gmxscOhGy=@pJrU`2S})v;=5^R zVSvuM5dAO=->~_%jfxFQyu@g>^zo+{ncG(e@icJl1B^^?(vR}>b%={uf0EgDqatEx{PdFx zcJcFuR1PJ!qE0!r+bIFu$PN!EI4DPQOXT-OYM7hqhK|;sFVij`H%Qf@loGsNj9Vy~ zYO{~TM6`SpSn@ow9{q(!AJ#9&?u(9U@AtA19UNtSlugy9JRvZ4)~Eznnm-QBfPYAw zjZfX;dntb2pjtJs?*2M%=HK9VPbc!!cO@@mulR-ybdRDc<@X2s8M;G0)qMg>Nh=!EIcwodM6z!;p4F7&q{EBY;B$C++kGM6 z{K`2nJv6(%v*Nbv@>dy>MqfD#yH8%%7ecu{;YVKH^ja6AxkCabpKabl0xDq)$PoIty#fkDm?q6 zh@Dv2p5t_*_<`Bw!bXMxzP*{8Yk}vl_FOqP%K3r>6@ZhcDECo>1{X@rhzD37wKu8- zeNL5s%1datr_}cCJ2CFI9Eq41vnSYqy%E{T7hQHk&P8&x1y8gn z1VSxGi^+yn#uA3${%z+xkY@4r zQ8V_S%hSChLH*#C0QT&{zVle^Xcv_x zb!VX^o2~NzJ{ffAxrpjVvaPtUYv(L}cWvH22V@^%z&O&i^Dlf=?ITA=M32A`XyUc}=iPM4Pu3`&?!LAPPq!mSkH#BgJFX>D<4qGU#C;Z+Hp^6&Nmw?H91YDXc< zjLyCIe&`T5FnqFf8(TnNnO`n7@szi(eX4STJSG_?B}WZUv=aAr81s8KnD6QyUt*qV za{{Rsm$+|hy56S(%!)H8D27c`{M9qcchtbYHU>P`VmAXS9tc8h^biGCib)a2ouy=%1VMw6jj*>E1iF8)6a8P|0ushyCz&$-x!QD zGk^Vh9VrQR6+HQaJwa$$676pZb@7$=BB*w?XX1)6^*@vqa%d<#{w4k-Eo?QaC~V z^pq4S1x^t>+00HNB%4fJ%zNQ&a#SpSGvD40nHG}>|L~47bl1noiW+aox3@>y#3Xbn zCdJRCNbaN8m$R~AHzm~>i2vblpN`W^_*&VIphz(2422T!sFA9TlemliuTpa>dp99j zo_l^MBpp_l@#V zh$Tix3IeXd`O;qfjQx1eAWB~lqBmlgGo8!r7d+X{zL40op4ois#jVOa7SdjWj5Byo zKgyr+lE)q!f~<$!A7~e|4<{yyx~Ila_9J{5uVKbzyr&Q4Q2Z8YFh1NdIwtOzJoRp4 zqr|K1x}mABX|F>@w;(~E!7_c)BY67T;0-uRGgegaM4K&|n7ID5e*N&rbT8GW-Pdw2 z)2(V#gn?gE7r?{o2Qbv-DcCf)o- zq^tkZ1kiBbuu)wP@OL;Og#}!E%P_)S6(^MT$+zqb0B{6IST@u{3J)CF>)48;Kc0+_ z0zPKI?l|nzqjNLI01r{mhr)T+C+l^A8uqXP;4z4#ego93TzW6P1deYD1fBMS`)&@X zq<%k*IduBhk`KU^Ueuu2?f|}Rjl-Eq5rHqZWSs=eIz=GiGBZH34ljRJ}h?xvo5}p4hV|@`pk$8LXb{e$%|Vmjlk*ZS_OU8 zx9PaNZIqBW(;G0Ld}lF>>(P-)1~#W8@97QJP~0c7N*Xtw(ZD}_{UjS?{XDNRKmJl#6A|H9DMReQ{t0D6Za81VAQ2UJHSr8B9b?5Y?9w9Im+0A6u~P6KMT9x_l>^0&3T2qIN3E zKs-iG?>A2_T&8-n^xADw-xdVsUd2Ib0!=-S9cw;$9HaQJ?SR?`+6MW5BX`VE4jxi{ zmk!Il1a3C13ZrWK(goIJG<0M)Z5!(t#V*OpL;v5SC6yqNj zofj_;73XV@jP9&a6^(ZN8Pwsu15;I&PuoMAZ^;etA-!6}kTaBPJlzkjFpIi zvE(x4$n#1=l6cSKao`saYQNC0fAlrdWKYG7yO6}o!^Qal z>=$SN8Sd-i=qCw;zRILnkAn){pwp+1KHGhv7(b?;V?@=*1#i&kU&up0rjx?o{7g9c z!1yjX16W|liB|IHWmV>b|SWkxy>uFr@ny{B~~uVIM=z z86}S!#J!%%;kh~w2+#>b&T*yb7JQ(mm*A)c_VSDTEgzZVU~PlvB2xlzK4O)AuQRm1 zTzu$DZjcEMS};UkrNSCFrpmRnq~Ei{!eNG-@*AF8%cG*+3IYCJR;#_k>o;$Ce)$wo zA}xP9pO^>Xotyb(-yJk}-Db+|KhC7>nVj|<+k0u{uQHW>_l?rS;&^IYnVBEGJUY3- zRoKW1V>bArL)oC1p0Df2xeO>IuHKMJ8aLr|D4WW*vo2MAd2~;FM|VEWkMqH?du+ia zF{#p+LsO2?h2Qnj$kC@BOYn;hW;E&MnoZ3giZ%3gF0+dJ!aT?Q(&QV{lXvQi1i$uR zI=K&C2{hV$xj`jtsh}x9TXeutFqCEUX6ChTqnIwQM^2 zpjU`XKCT{^+>`fvIJL(_T=|l(xbjXWUD;sAv(7w=wM)@!1LZ%5-}L_n%f=uwIjvpC zz~6{=V&Rmm`Zus|r((gRQ^Ki-yoMhpFE=UPTYOb^cfmlYxvi|k*V)~v{w6wwVchGx z|G8?}$Lmi?+B`o%Glr`QPU-7*ta0p))yJdLey92?=(Nw}gtVB2+?Rpwg9^jh3_%R0 zN=`g@UaOIP&VJ!!Rl!JV>F2n_h06l8!E}ZQ@D(g|H=GJL(2UHzD>ZbkUOtV$rbZR`BM_O&^tNv?$F=D?111a)k&vUbD zVE*$P7MG{|E5Y7h>$J4&x5ao=in?`zt;9PgHaChz8#o+P=fC*r>OR!v<>vDJQ4XWw z78&OpJ7o9KffZ;0`Tcz0&baO6@%H_b;uqUR4npI-CojbwPA?Iv{=2kIXw&qcE_Ojo6X zd2lX#;zsj(Z~vEBkqb3^CB?YU?{5U=AB)?4ZY;BAj-pjK&TJ|j`llM7@{&2>aWBYJ z{LPBV-4zwu!>D)0-!A$Zd&?gRnL6&gi8@5O;^6u zFn+kRA2!Hx#%NNKPnP`swM~RQTz&Ryz9G|J!5XZ)^5n5gz$Un~dqh^5(*s8P;;3#; z7rGvNsq49NfA}QYlh_~Dmi?r*@@HtjPs$=)AF_#*wDqoKDZU1U^;>3kccW6-V6^hX zkgHeZ`H7fZX?f8Ng&OO1c5^f-;^fASIW4LmqUVSTa}$y(q#y z0?muC(+Bp~1Cs@dC$5;X2GHLXVB6Pz3$XNc$fTIQr|qW5V*DE!BsbtNG*2Ijlb&kY zIv(s|w)dAz^U%+mh$_~dF|@o6A#27ixCA`fDa;-j*{Ht2bKtG0R275>Q({&}wPBYD zr-Y{OTP{}we^PI`a;t=?YUd&(=di1}=fquHkS3gLqd?KfIFEK(i>s=A|J5D-P7{VL zRR!}82b*3S-mz)lP17{u{Oob$DT=hSv(1sfuqFbP9Q9TT$vYRrdXpt~_Di*efP ze6;l>RFFp7AYE80^ow0pfp?#M+u84=qZ0T}8}nLgPo);eM6TBoNF`1{mDA{-j_y_sIR#%OE_ z8Q9+W60F;}`t>2u($#)-cF8KNka~vklA^jT@J8+YBd^*56%rhtu{RYEVs$s^3;bJ4 z^hJ$|B!{?KgPN`WT}>%l3jxQz5Pux$h2d?>yT_oZj8)QkC!H(X>*gA>VpDz2;xjj4 z-OWj>;10MrV3B#uD0S=*XqSK~-N*-oDeRBq95UfS^2Vruy<4^5O$x|A^G?E)4R=2& z7<`jOgg*BX%YoZh1+(CZ$gnNRAIF(w2aH+6is>dr#9J5cZ2Q`{)igT88ghILOiCd_ zarb|Ls zya2DN-5MHHfl`xMr-iue5KTPk`MH^9xj=qPiMv9uwyJf?dZg(y>&*Da(1pPhV+K}C zpu^wc3Scck;jf<5hSjmr&LbtoHRozP`34)3BwpmwLXS|nI)RX*)7isNA%T0%#Scfi z0Hyb41Y;zA+>a8vlFTX~a4#*H+hQc1Q!jj0#wt?UE={oyvq(qjKyFTCcmU3dz>TZ$ z6lH6QQ0ff3rXT%E)3AU(8nQo}`0jG}suVKzXQe#(kU6~Krh5q#lLDiQljU1LvGZC& zL5sWBxIk|rEeo&sxk@E=`1#v<5ITvWbk2~Cakiei#pS5|o8PCBL6 lJ1cJdAHReDUlXA=uH^Q)Pe9&Wf#I67%>jq~rIzG`{{V;n_SFCY diff --git a/bin/resources/icons/flags/PAL-A.svg b/bin/resources/icons/flags/PAL-A.svg new file mode 100644 index 0000000000..989da76df5 --- /dev/null +++ b/bin/resources/icons/flags/PAL-A.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/bin/resources/icons/flags/PAL-AF.png b/bin/resources/icons/flags/PAL-AF.png deleted file mode 100644 index 106ee79a9b89bcd097aaa9040cf5c164e73b098b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7938 zcma)Bc|4Te+aF_?8CgbSi!`HU64F>&shNZ#6i=en*n3*_l(cDPLP?2MOVUW&W67Yh zBr}$h2oZ+Dv~Oceh8i>Xd)++0_kDiPyL{e1#(kf2uJgUV*LA+vIcLnNwH_-q)C|=y z7>tI?Dn~C2Mg?A#*AtZCae3(rT?|HP!&PMCu$-z=VsQIA6q1cs0j-K^%yZ%6id zZ^doe)<7RbN?22_r4i|1~z&@$LPRq`jvqvWSuH5tr#H*XC$) zylBT6kFzN~`9?XBnl>@^DYf_WIhu$}7JJ3Q`(|{=-E$U+p+i4M1YIvA!`~x{()D9} zafe;sF>S|6^l+pzM(pK-_YFeM+mItyWM$Gk&n#SVYDrnFR`cCyZwJ0Qryrgk$10B_ z2~U$;d65ktUO#)+(SIr7-mRJ4;(-s}C9;q2v+jz1wlt-rH$_BN3_mIv;%75m?o854 z{IVn9qmSFji0t#1VR^6QSJ$i9vrlthR$JR=(CBI8X%{}ReBQ0HAI!YEEOyDuyT--0 zr#HX+_NmbDOW~CKwi-(8mNhO_JAhT*hUKzPDcN#`*Xl8;OtH^4j61by<4*XSXv8EFaQG%1wgkqRVgKmL zDSg_#P#gq@pzte}xr5diQwiuW4hF`v6o!#8*1T^HKG;7z3+-m4eX;+9=u`etvm zB@?Pu`Ckf+mmn z{$$tTg{Mr_c&3M^6)CfI7sZLVT*E0Fqp7jdYdh@QmE1oLHEeX+x!{K?y`+4K4ViGG z;#F^Q3iD2qCXIAQ17bgng1!Y%9tvuz$(rUyZ8C%$>YzD%gc8~d)SJ+>>fpl|P>*sWrE8QdqUujv?C+8Y%TgyS%FdfIpvuR_V=DV)XVoq1X)HPUzBgqux`akv7Iq zuGCoBaQO!+a1PllY=$?T+hmr^-1e_=6*3{W^782+)W+HWRqKb*7-IA=5uLfhT`X(I zj-ABx{SikV9W24HXLBAVGnv4Q3baW;4oxWzC}X!_GdBDKIcCp+Z(E$=AW@%xxAd_} zTjjibaBlo{=VeTFTTeR44X5z~VK`_u(^rJs{B~eut#@c{f=IIZuuGZY}2a+|A<*;ld_&vv<~v{H#%1lr=g$oz7kB z^&=?{>~x*_yKt#Y(>aiAck0E9kX0JVHBQl3`y9~bzF4@sN6p#pQQHadu|l;8*Y~cX zNOCjJsHU_CQ^sZWsqNKqkxr8&)(i`uKIf_rq6N z_ZVwB^=Ks8tY0x6eOH9Yz`$N3Yc72jpc>TT9OlEb*dLp5QJqJ84dJ>1rF3YYg|>Ya z&cPV(&DEO;%jK-eL&FoJMM!4rR5w4p(XR9+q|& z#1sRbip%PfIw2;62ud!#knPi{O(Q9^r;jfofsJ|!#lG3*i50OAorgRZy-fN(>Uq6^ zc(e4*Wt>K^kDFRS<>^N)1CTqCkkgzCS+Espz^uX<_kf5ccZF1K_YFrYj5xG z+sUZJSvKMve*e=BxjwHy8|}XOGF;%s%5?*!DvMHthnKS3G-2&vHTVHX+Ft>;TWUOF z4iveU*GP>D+1H#*h(Y6lYJVDs3+pBx-NyhKs4nK%tPw|@Btiu|g+yp4GfgLha;oZz zdJ~D!7l4$iKi2;j%%q2;2>Tg^i;N-7tf_@=;-YIBX=;!(`eZWU0%Z3@0M`E*7_vb5 z`hfAgJLLfA)EEMNS90i~u{BrFk$de@&hbT(`xvGTN4yHpLsM=pQ%o z)IW^qn}mLNd*<***^fm-S3Z@*Ch0#@9(_$d_&c_)H|>@>nQ#+mx?6Jx4B)v7o&SYKu8dSGYjjV3o(!`rwDz_V*?L*etnK4O`-9zr`^PFI@GQk!1`qtD=wvXpCZmY zW5v1eA^yhoVjM%6MF4azc678-?VHVqgqtaZt zEr?&8wTV1hgS-5y>sXSt8}+IY@U&xLO(+msR4{Ge^N3ofl`6l2SVrew@(@=Hs-;>1 z-2;`lN?Q<(5shRGpjYW!I{1)0Y1{gY&fWiI0Su8rOEA<$=Oz+a7s#Wvs7&W^Cb^5- zhm^>K`yi1vsFuH@h`Kzc&=SuE!OA}zd=rU6&7cVNW)Ga`I_wiZU9F04?>|}PKHoFn z%X3}xLA|-rbC2JQ@r|WU6UqcCy*75WoFm_JhZ&7dWo1U>MLo}r-r>lrbX$N%a5B@( z4$eV-tP^b@oj9|(L}}-Kte-|QbJZlVfOzy#N&t3E1J0|p9MTPmm~RZ}x2VKb4_US+isLb~jao%2D2S+JNpFI{UK+<`p$ z?gNJyeGhOp(-N&i;3&I1D1BaBhikruE~eA#avKji_eaQ90EhV1LSQ4&102?YiH^Rj z_OM_{#2XPxN}IP~+30SLPAqYwZm9#B^}~HtDB8)y2^Ys6gNu*W+#hDMVP3m6`QU@& z@>1Bh>hC|r9UHrMJn|RX2*!T#^hG5Ts!Ss_V~bzn%GuWR7HRXM3S2Y43}b1_UE?l3 zIzmZ!=tk{TgMFp`zT6s^-ZO(uALTPUWrBsV3oJg0=M48WmL+-BJoMhRFNOIl5w^f& zpcaFw7^2sV#)th$9NUczeWfs*oy^=g8H9!!l#WdpJh=}W8`^P?1}XrEjcuzi?d^G6 zI^5{o+o9Oseaw|M=jC;Gq0JH5i-UuTv@Iu)yH_!H+hJXDxh2!=r@oo@aQLj3kAbuQ z!77^5v&}@8RoZ+~zRtWDO-KBjG4oK~CtJq1O|nC(gWo-D=WtHb1{(Y0tBKKp_^=na zV^&;RVxWN@)K?wOy6LZVgM+9E_vA30t8POdk3Q(~Ify>rjhb1HrY0CWB?xYUEDQ3} zse#JHJF$#C$xK%bNc0&P3P3kMOY&1fAtWX+g-J7*HXi2rJx5_6=k#skg6CRb8Xo>L z{A7^E1aOBLwst}YmTcX6*kgS;x?GUjfo5P>pp&P3PuGI6H2=eXllcd{Uf zzWNDv@4vt<4t`x;PDFA3wj~;5CzRkvb6^WY#1`1mAi(oCZP1~|Fyw3w%Lo)oR>XCp zKs|!q#0;nzdRrsK&T-pBD79;i$LCGXKt&18nMW`_?&5iHw5HrEXblzFmf@%BFD}-!5MhgE4Uy7u{r$aI!VaHBRN;*qhdW z)uy`jQDhls76@o%2zJ9vIG?xC6453;6q^D$3)@di+yKoQXqs?7E#&UWBhwPyvoyu7 zFG}uVuHhKSF9|H^Q9w(}S@OT56tF>?v3sa}f zrC+3CGqV5nNsMKFo-o+;rRw5!n@!NhDGF|D$HTxYs@F|puN%23JFcQ?Cz4y#otoIu zxXOayBab}V`jH3^?aV9Bx2TKl-&0=KVmwwTDym|3y{FXA-f4ococ)!5;(t?HSj$LW zSL~#y$f;cN6F;Pu{hs1xkz-(sybd_7loN7oOqR%oAkVSax)(brDt=Tteb+K1Bt2z( zrluL$Am!b9LQGr1Z;D5C3_7RvDZ|t}?L50_+KE;b5OMuRcm64~_9zr#_HQU9WD&*_Bgm^i&3zmZmG=-|4z>?Rgg ze|jn6o&0L)Z|j=Zwc3ShC#_!3l}F`ZW%vy$#(g~(p%dw|+k!e&UdeoYoJo|^I5 ziQYFt5o+$L2F3D#b64b1_0w%+?b%g}CDd?Cuix$6{raL6Z!vv^?1=st=aUI44+vtG zEMlK0pZ&vZ2Uz4S>!qwD^5j=ihAHj^XWvsId)2F;cN_DhKF%0+pm2d1|t z?QWjl9c`v;q-OV{?3cRE{yJU7+Rg9PT&P@~Q_kzk=Bw#-c6M~!i3#{3YcJ>U@IM$m z8$-{WOs+qB?C7;O!PsO0yPYgeFkElSk^>>f5bNt(6t=1a~A|@DqER&3pH5K7!v!!23)`FlNL?+W52pMS#FolmL_~P zR2`pJ{k#77ymPlVVNObVe1Orm3?G0nldZh59w0I#lng_aCdJK8`)ej%*MQVoX~O0$ z6P%CFCjy}CcUZpMdj#}VzN1p{!u8~Y+5TbW@zCC0(&EWi-}gsiee8it>z6k5!R^5N zX6GnlMftOdNSt&u!6+EHbA9K8dt-0qMcWXXgc9GXK0ac4GiYLDuF3>%)}IsPJw5JY zjxF2DJ|W*!enhr%>3!_zhnAY*P?6w6A$#3AGw^vyo_Wgw&ECnCupGLcv(cnVS=09- z(=3+>uVhbV%JjeW#LAD9ZHy}N0YS`bZRi?PSk9BSi%-GT_naA_%CvKT25aUSeI1;# z_WtVXNv79%s;a4fp5v8sMim|JTS7nnaM@m-#m+q`lE}+LfNRdmHJv818dGPG6oh85 z`51Is$Z9^nRThOJ%Q_jdmCN5i`1ukF`6(KH_^-^le6~7mF%Y;c57exf$W39a22XxE z7)f^CKAwRZM{CGHKzN4$meT1j0ezfo0729#tWwG#{p)VGzrxCDgw0s+q$uz7N2nH@ zNNN9v)5b}Rb@kCD-4O;%`z*B`)=?!#_G{NNq$nWvj0=9tN=l{42bzds!sg01; zlx{#>b}(p{p&meKR`;VZ(hdPby60ih9yCf@`0o%Z&rCZmHxYP^VvQP3eQGY7@e|3+AomLc!s`fOB2ppPU6YUGwT$Q zze-P9B!QH5Uj!qaNqdeE_W7gc(u4v_5SV$T!c+7I2Hz#{vy)JwQOUzz@Bk%vLY`S1 zgE?EwZiLc{Duzt?CNS=)NE2KeK*hR)k}ocHK}drr_eiVhb&Pry#m4kNz$`P<WNv+>qQdFVeMmPb z3jY|WUwWpfU`l$Hh!_U0;j)NE#idfVUwOpZOCo`;ZxB+h_`9+_l+K609cAhaI1qMGLhy5OSKNT%#w|K_UE(L!grMpfoy`~J@(@QcG#aH z!GaD(@n(tNX$eI#Lz=f-swJH)YftNuYTaGPiRkGu^heZK2+J>@y=y}V(zn0qIADty z$y8~ciByZ`@~2FnW>>(byGZ=Z7YKft$nbW+TACNnOZz`%(_24oLU~W~YEV2nMwU=4Or?1tQmv1%^6X@J)P?{g zD;Qzj5(zeTFp|xsc^)uyTAqDg9%Y_*rCYE@x~KGcFQhZ-Q~>gI=*>Px5W*@D2|Dc$ zHYf1e^evK}IS+^L8af%MvPJ`sS?p|A)(bE>HGT3VEuNm*0yQ+vSFD(}OPEuA|< z3Oi;FH6BGILz}DXUKz;nQAAn0jtsA?CTpK8!>@pcrVL*N4|N$nT>0R3q`)v3Iffi; zsgM$6?I+eyR!7z#OGGrQUy4UZ;N*Qub?z2qj=(uRwNi026ycrcvpYH%cV zqspZqBMBv_6-0)%FB!s1Av+O@ptt9TgtGbOO*qkI_>wK?fd~fkQ;FghEuit*Ol6=} zPW_;ms}M1K0j`d9QEoT-N@+C|WlJGDELU|L+IG6c51Z$QQn09-#*{@EJ@j*h3aUE~ zWz)4C(Ypz1U-Na`i#t&kF+-{qGE>qM59&C_XJ08~)8|>$E96=d%Ds?SOkW3(nX4vS z7~ho#6DyYd9CvchOwt2~M7^g-umcwNPdk+`WJBUUq(MSiry=g%2PYaY*dJ*FG(Ugj z$-P3;;Yg%a=?X!%Fuif!>d2mB2ZSM&iujOEPG5j}103}0wPXAZ6v-+9=ILuEowhA7 zy6-i~JimyPV_~0|XgT7i;(#UE`1iZe=sOhX|G7q3*^r__HlLW!E`l;WRH!^!tAP56 z6$$1@wajAWM)~Zu)oGYzaP(?Dm?`b2!OB-Fx`GhiS&;z$qg_#v`dG0g5aEHw&81pd zwMw#x5Lld@FBF*aL&ospDedi9EsMaflxi&@$%@Llt}UogRCHD==JtKwVg$a&v9sdj z-@=gcR}5bViC=_l z*kx6kq%YkS2+`_Gj|`T%^Vv-gS;(T$qTPE93GmgkyuK)EH}`T+vKqwm=d&5LC5n~%@@Sd7fYom$`p9}E+IlQP&VJsyNti~=Hp^Wqo zg<~5m$HF3PJo@>9Yn-7;H3t#`8x$3;G4JHB&McPUcl>WSqNVXIvH~{W{&&cx&$AAO z>E-Q^!J8@asNG+J5#E;VklLj6n`HRm<}Yuy*Mq?Y*H?9W|F{w=!%s?bU>C6W%p>!| zz62tiW~d0>e7I|y!m+Qs5jOb0cZpBRm0kbQH*R)t@6wMF3SWWgE>8feI_NF>rqbB% zqCR(PP6a*g1g^T9!Y_!x&fTe#-&E-khQWs+9-btVVf}hpnoaeGP6ieAO3)CWm&-uM zh3j9&$&C^~vTd%VhmB80jJ)LL-j8dO^z6e++rH+r;fJ?g3ds<@5!`_5q*4Zy|6g6g z>`W`zfA3I)uVhw*Y^EHub@SPY7w}>WURI&8IiPX8gmC}r5PXyZQXJS+Lv*udD%dI<%7Zfv=LIy0sG4@Ca0t3L%83X`hAp?DJu zoD9scZc{tv6U>*Mthl*;@ diff --git a/bin/resources/icons/flags/PAL-AF.svg b/bin/resources/icons/flags/PAL-AF.svg new file mode 100644 index 0000000000..275c136da0 --- /dev/null +++ b/bin/resources/icons/flags/PAL-AF.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/bin/resources/icons/flags/PAL-AU.png b/bin/resources/icons/flags/PAL-AU.png deleted file mode 100644 index fdf4042c25190581c0f33331698786ff94ff2726..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 608 zcmeAS@N?(olHy`uVBq!ia0y~yVA;UHz^uT;3>5L}E1n3XSkfJR9T^xl_H+M9WCijW zi-X*q7}lMWc?slj2Ka=yo@Ceh|NsB(gwjJ#a&v$RzIwVihE&{od%=*A!GMQ(!HhX4 zSFLG%^MYN-1E__1;K7lA(~H%-r|Cp)N>Lo8NC<-0>e(wmaY;Skr?Y&v$cC7}^`Rg$ NJzf1=);T3K0RY)XqRRjP diff --git a/bin/resources/icons/flags/PAL-AU.svg b/bin/resources/icons/flags/PAL-AU.svg new file mode 100644 index 0000000000..fdfa0882f2 --- /dev/null +++ b/bin/resources/icons/flags/PAL-AU.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/bin/resources/icons/flags/PAL-BE.png b/bin/resources/icons/flags/PAL-BE.png deleted file mode 100644 index 3adf04483e0e23fe3582f4b511fb132ced99cbb2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1289 zcmeAS@N?(olHy`uVBq!ia0y~yU;#3j%bA#g)Wy#So&zbCbVpxD28NCO+ \ No newline at end of file diff --git a/bin/resources/icons/flags/PAL-E.png b/bin/resources/icons/flags/PAL-E.png deleted file mode 100644 index a7e3a81f13d41f046a3337649530c9d0f0ca223e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4061 zcmaJ^XEdDc)>fhuC4?Yqv{yoOB4d;hg6Mr1ktk6T(ZV~JMDHO=M2VVFB1)9#M2}9C z(FdapMj1wVhvAHz^R2Vi`SIO9_P+MD_kBIjz1CiLi8j>NVxZ-uB_SbUc&rUFCLtj| zBq1ToCnF~^8ww6nL`QF^XR1jelYTi!V(RGJRU&wt_q&>egfZ?hNX^uL7SkR~EFyWA zODJJ4A-jVA9iIToJ<%ML`6T%76djuAL{S$3PSH?NB{B+NUhWb#*m#|VnE^2klVo;% zudeZB!K`ON{%~VqX~6C{j$56YKa7cJvO0uP7BprAQ(THJL#k-{*O?nx{621?_ zN6qSk%RAW7uBVi?_mFv|YvMk>m^`-?65H8LXJfxn15<;FrgaBn|50GFzMb~6ToHdb z6~TK5m);BGcUZ|&+nRa1#M5vglD>j`*8TB}vhA|T!0BkayDfxD;g5eug}ndOPEuue zsXakyW76Y%xLbTWvkpe+X**6-VNh+s1I(ZBm|qT#S?=G=v6Kt2gq7i3*4sX#aNRm} zW$eHPO9B_Cv*lLzh(yGtP>U5|%p3nP6Q2?5+uhCR?3`^+z;Lm@4RpfS*!+_{e)-+6 zyV0mi)#sUbxK&y4?{Ruli&wo`hWFE0jX(y;(mJU*_l0zHi!DH=C(InD6pgaD$^bW^ z%IO#P9%V?KWyGdz^?Bqt14gQ|^{X)^?Ly!3dp9;6FJAAN#Q=g~*Jhpknp4Ey$l*@u z$2_wMv0+ZGXl(jw_SCw8EWnJ(v8E5e-~#Gr)nEX$sv(=W&%(qq{(=TdG!*%Qa2Zia zMZ9dFTXU<~>ybC#8>Hu#<@W|ZrU=L%$2us((`jjgxLLDmYHP$E2n-qZtV;%2%TQl3 z&Nb5DxFl40o$mgS0V~fa_`YcnPMfl1Ur!s|?SAT1h(=pv1wWGgN6066=ENtPIeyW` zP1||s*f0}q$m{48&W`}8{sLvKa13MV z)4gakI5^}(ltgq2RT|mYcN)-Kh=>!yP13iM58oO^xGCR1lZllZx@S=-(r5_G`WaWM zqWxCsO#B_h?{y!GEvx2Uhd<}UmIQ8r?;Pt~XDY+qnXs#l+}Rlp3#XM&wuDZ$!{2P# zuBcPYZE{SMNZ>q;12r}@O{kuX+MrQ4g>Me9V)Av5V?uRDyH)N%ZCvV^S4$>SRWl(u-r2MHm71Wc8*}$GATO({7j0V0KRtXWRkDx9{BgC+Yn}4(X z{;c~CFr+7+_b2nR59UJfv0tzEr1Bbrkjk`%*wiR5g@2fzQ`?QG{*OzYpWB?~9Oi^7 zXE!Zl?nKr?mCpD4z{A4-tztPBVuEC@`;^{C=F$|M*$!KXx?Ao_G9JWPbz%Q zOL^b9+mHta-ZG*#b4A3 zVu0hf)lHLgj8N+`$Y*};L-OF_hzh>@Cj^5NdxLOuc{e(;PVve_y~ON-Agb51#G>z{iv$JsMjM1%V*M>ACStb;GaY-tPW%2 zH{#o{b467MS~xUW_8pH*)4c6*|0K|d!h1A9Y^O`C@nPiJ=lD;v&3~5!RIuy7te%js z=jNnIE<$SYkpjkiRD@=XVnhB-o##3U_JrxR*X<*&d8~|FP*TrPzwP(Qh*DTfq4ZMj z-qcsyF3096Wiu)qcN-Jxvf9Ba6$1m zv#2YxB%orR@U<*fJ7Kj!`8RbVF|}MiEiDL0d{I=U5y&?tqLis38+I?QPcfA4H+l5b z>;o#_18u!~x8|>8lxCdvNr-X5k8*NmJMhtuTbMk39v{M(lzcN11-ym(MDmj*Mcoo~82r`OIC>=sP2WJ@{JP7eht?6$ck2 zTijNW>33#mebPHUPYJWQFdiwh6gHt9C&kbUM#C78$K+{h-1sRF5+yt^dJV2U&m{*F zB9Vi=4AKZU>2W#V10!GHvvOIZtFZ?%s$wiHW+)43eH3Du&*Km17$cO0aBdHKbdQ}Pw;?L_+IE9m!j~b>NhU{4kg;&O?wQkK*AJ!Lw6Gbw3%^Zr*}M&+5syP*U2BFY-{cU zGY<95L!MA3%w;xTo`tr~xxV4;3aOubNFb;ogJh?s7LsDfiy`vmF%$>~RQlB=<$s#% z|H2E;tgJ#HvmIS1dbBskF<@*xdCAKfj3|hz2*;M8SD3QfnoYro9Af+U+xgtOf8APX z_*~ToOKrdhDK6VK)R(&i= zvsW^tp~+gbzoKJ$Ig-6Zl~Qi7NsdnWimteoLX=V{SGOkNK^BJkrp>BgL>}=MzTMUl z^g1f~5)>43np@+`+zeeX%upLnc27G#5?xVFP>^iJp3m{uN#2AE#b?kK z{YkLYvPJW&l`O-$O66gIVxGRlqvzXhO==?Z#8#VqHA-5C)eLDAkm{7bu!#8~;5oYp zslUp^bp1Qie@EmFVA{apW$<{|+^K-kS@Nw!dZQOuIzn9|r4HjY4e$mJS zn05;{Zh6f4)U;A-S0Z3-i?=aE3Ei9WW`(uZxAS255i1+!JJ2fQNWD+8)a55P?(m>{ zuGIX}5JbC5-1KgXSWKw9JiIHy>@_MGNe45hgZod9(nW=~g8j0ZAxGPCsiagvb>=%U z=AuK}&MKtlz=>1|SF;?yDkc)DAiFy2Q1d$*R7VtxxqEfRk~(=cgn60@JhtE)2)^Z4 zCB4LeN5ENp)cY3F`c2eO5$=Jk3=zG?4PvXh0VfH9a`fS(=R?(xIzV}u*}}K;20ApG zV@F*jR6~nJO}t13>>;JyK z{~U)thGiSkE+*x$r-bY+j`SPvr=FXpgn$pm!w}`(y%wulk%&rls6t2G@_Zzb5dnb9 zxmN@J*51pp%bu zYPk01Ov84E^mb%m>!8>c5l8Hr+%Y@_JyiB{(HOfzpFOE}nh;y62@G#chUxjaUJ*2N zdnuci(>t8UXOtcUcZh6Z!X@?S8m?f{F@H*7H&BE)&;4@jV^6iWPra5az!GoVGN2d) zFE`Pb#Q4>vVli1NTTOExOsAyW^^zY@1?EnN!Vz14t>M;u5>THYptlLt)ip(XPh>g- zqH(FUAv21+{lz&i^f|XVsyrWwCR=Wnf7=YlW>^Y;fQZ8{IK=&>z9`s8<>bT4<@DXs zn$LRuec#GLq+YV4``Sh3kcDQ2I5@FftXBBe-*|g#aXq$X*+N89{*uaWcIlMZ_WI?{ct5tHJ7|!O;Piw%=u^VW zaDz2jI&&#W-^`Mw?E>0e_n=uX{GR6EriV3W)ONwUA1W_v;bV3&2;ku(_aCe(EiNY0 zQ7!O3H{G`p>&1EijKO2>Um*M zvIk2EFhJ|K-fZvegJ|*#Gy-+Qvn?1VoDNFEf5QdCaNF3_$37)NW<)ch%e$`CW^;OS z40H%cEAYF=eLElxABRJjZZwV}dCU2vZPAD2`!|#zowpg=aK71_-TQYk4{gu`o>hBQ zS1uatoZiX}R?J#czbpsLIi_F8F!XbKcxP-zzV{FVB3~TD9vo&0Q>+!iX*TLdss8(t e{{PTBDlD%lTZM&iWb^P<-D6FCP`UckkbeQ9f9e+i diff --git a/bin/resources/icons/flags/PAL-E.svg b/bin/resources/icons/flags/PAL-E.svg new file mode 100644 index 0000000000..045024a339 --- /dev/null +++ b/bin/resources/icons/flags/PAL-E.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/bin/resources/icons/flags/PAL-F.png b/bin/resources/icons/flags/PAL-F.png deleted file mode 100644 index 9a8abe6bc5cb0b4db01ab99fd2f6a0c6570128bf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1338 zcmeAS@N?(olHy`uVBq!ia0y~yU4SYT= zO08NpNt|`B5a-Iinev~t+!r`BGO==rXatNhMgw6qC5+~RL7EHtS$|wV>721H@F1|f OWAJqKb6Mw<&;$V912lmE diff --git a/bin/resources/icons/flags/PAL-F.svg b/bin/resources/icons/flags/PAL-F.svg new file mode 100644 index 0000000000..359779e7f0 --- /dev/null +++ b/bin/resources/icons/flags/PAL-F.svg @@ -0,0 +1 @@ +image/svg+xml diff --git a/bin/resources/icons/flags/PAL-FI.png b/bin/resources/icons/flags/PAL-FI.png deleted file mode 100644 index 843f672871855ebcbb0e5620ddb971a8ceee86e8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2202 zcmeAS@N?(olHy`uVBq!ia0y~yUNu7(F$%r@0)8UN{ik$if? zrJ=0s(~Cc|URRv`zvN{dyZP=CA7Hj&c095Fb=HrSCs|DnC=crt56`qkZ!Q_~0@MCs zh5zf8C+0ItRSd^;jwP#3+vv@1%E8Paa&+1P1{Ixy?EgX+K8$7iooO0EdeWY6b#8a1 zz5Zvb%w3Pm \ No newline at end of file diff --git a/bin/resources/icons/flags/PAL-G.png b/bin/resources/icons/flags/PAL-G.png deleted file mode 100644 index 46b560d82e91004fc26e98b6bae75aa228a5e034..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1446 zcmeAS@N?(olHy`uVBq!ia0y~yU~gbxU}<1t28txB>^K3WSkfJR9T^xl_H+M9WCijW zi-X*q7}lMWc?slj2Ka=y-UBii82+DUSR2#B&cMKG?djqeQgQ3;MMFjg29Co9K1o7T z_grS^Y}q#JvKLdcp#&819!ubXATyW{P!5~u4VXfpa)eqWQG}s}B#0ucI+lPi0U?U8 z3cItgiy}E-Fgf-{0w*-IaRnlIu0;eF_CUlgN+@WN0x`(|oW6z>@!@#Etb=UH22dp3 zb5H_;a$rJ0Ic%aEUh hw#X$fXp!Qpg;i^R6pLxe$*-W)?&<31vd$@?2>@(xrJw)+ diff --git a/bin/resources/icons/flags/PAL-G.svg b/bin/resources/icons/flags/PAL-G.svg new file mode 100644 index 0000000000..ef390466f7 --- /dev/null +++ b/bin/resources/icons/flags/PAL-G.svg @@ -0,0 +1 @@ +image/svg+xml diff --git a/bin/resources/icons/flags/PAL-GR.png b/bin/resources/icons/flags/PAL-GR.png deleted file mode 100644 index b76b2f57eae21300163b03277b83dc84d7f056a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2451 zcmeAS@N?(olHy`uVBq!ia0y~yUh+*>_)l%4co0v+!EZz`&{N>EaktaqG<`L*By<46YlccTWp$ zoMPYg=H@>E&8kg?KWtuqzWaxt!=tr9hh>SYgBa5#B?WHAOu-4Q4O=))I5|YIq$n!r zGHnu=AjTNU;n6*0mFaW;c$|CA*ZRRxJ=rTJ3=ED8G2Et&j4~fr$jc~9{r}HnUCQKh z88hCqCkL@S>1tql=Pfuvn~7_OzN^DJ4vh!PA}Eok^XDoJu6{0p6@|MlyS+5OMG zYd5L&mwytjySX%3ie(W4SJRX&*^O+`6$>n6E!+-^+TVQgHbX*sn#hHARn{+wo{Y5- zK@RexTq=gfbE{XUZQs9M5uPKLZoBjOXNyecXb@rAm%U;f4V-R3yl8E}CFT`s2R)tm_A+I z`QajC!*M-XVFrc<1BOjak#Fvw<%#>;zyqw=$piCwSVEmAG{kV6*yMfOgrR{!;D|tb zy>G^k&L@|$GKOx2O+=gH$?D>Vi + \ No newline at end of file diff --git a/bin/resources/icons/flags/PAL-I.png b/bin/resources/icons/flags/PAL-I.png deleted file mode 100644 index ef0125311e018c798202e7ec3d3728995f327b2b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1092 zcmeAS@N?(olHy`uVBq!ia0y~yVA;UHz^uT;3>5L}E1n3XSkfJR9T^xl_H+M9WCijW zi-X*q7}lMWc?slj2Ka=yGE8cS<(>v6#dJVyF`Wnqxq$`5-jKiv nCJ&Bk91Wb&lrtc?!)m*oJNwjk2f1y5IiJDP)z4*}Q$iB}{WfPp diff --git a/bin/resources/icons/flags/PAL-I.svg b/bin/resources/icons/flags/PAL-I.svg new file mode 100644 index 0000000000..6c38017668 --- /dev/null +++ b/bin/resources/icons/flags/PAL-I.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/bin/resources/icons/flags/PAL-IN.png b/bin/resources/icons/flags/PAL-IN.png deleted file mode 100644 index 51e7059f0cfa9d94fb6be42b0860835ebb5343b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6219 zcmcgwc{G&&zki-F7!fn}>=Z4wL6Id(vV>45vW>E4%Qgz}AWI=ivQycr#>6gM*75KZepp&8P9x>sdR8~0D|$9z8}REFN}=JC{+>10Dy1h z;sqVcJ3|Yj9{vS}2&kv!yrT|+{rMvBsfZ!|l&JQn#sf^d3`-{DHRxS`+;wb=D2>-c z%lm_`|IGW&GVgSrL_-L3ggIaY9MWdd22X$|07-!HIr2I16ySz9|0m5R`~xA-1qHo zX}mbA0nivW^L*>Q#}_wOen%Kf57E(-^1;@~}{N#h3j~3UHg| zyy2;aam>*HTKz$iM!-<{f@wWIuyfKZi*cgbMGP8m0KD{TXv_wm_f3%w7L_}if0aU=es`|(JV7J``-m0S)ZuEx#w!kIC39JkmNs&*?*u(8rbRgsDZ5)l{8tkkBY{2{Wu) z^Kmh7@fjd;(|OU9bP5euSc7#cy${{lyA4b`dTt0W230fNoW6A$a;#)r%G*dlx^x0Z zU(QcYjvQ76T5kXgBFEz^R+BjR6M<^)K-l|?FnBl;2*(2p&Uyn5S2tYP3GwX>`wzSN z>4I8900&X2=%Wh@uGSGj61A1&Xy-?OVKpn4ql|@p3iVi9t!lFwH1t)@|Cu7sOHj@4 zC=0;J&kWqFv?$wBlXo&0UI(vTGTJXm#OCMP0HSjZ?oy52^&&w3q(QbH)0ph7O>;Jg zBTe>OR*ysle_a~`EOULbFWsfPD1en5S10B&S0$9(U;;jJe8^#PZa5a88E)jN1_+T> zB_T@D-fafzj1e1@KNOYIdjR^3P+X(Gf@D9T%{lHR3s*q|*{x1&XK8ta_iA{V6IKxD zAMSg9>vo@JhSeaQ>cJ73)p&lOLU5*cI7Re>Y4-kwo3B*);f{K-ulKW$N86n1YO~Vr zA1?|0s2VQs^eq2oZ=E>MaxD$_wkS0~RVeTx$V+!P*WFj?CxV(J#dqnUvEtd-Z~XyK z_Mpz>H$FK=1g+;tgXkL{mhQdVKjj$j2mx1Fs%+I>WkrKwflN!mY#+}I=0BZH%#Klb z!(LP6T;kVQxFZmNg_cj>W5d3EEe(da($fh`j)~oWngq6K;UO1x`uxviM%gDYQNQy3Wx0}C?1Y@xLIDYI zZG1XJG!r?O$wAhgVy!sw)S!1-t*b4Fq6BC#SzDd{z|&JfJ+B97{!mM_*qa{%k#SChMf8U!6a`z1wfZ*7@YP0OPrl!L#kZIb{*@W$vG(|9sm4E4Q2NXq6x8s@cfz zs;2H%i8zW7mf{+N4nkOjVHq~Bz8UEq}_h}9s^0J_W1Ur@Pr z`rWT9O=t$Px^Q|$nln*2#i@I7ykt3ct9M?(ch4Up0L zs-J5e?0KgWv;ooU2!|gAE0gy6(O%;mZZ4gEz3g#u`;?A?;$B*QTRok^S03!N+Em=> zcd|zM?A-hY;>H++jxUGQ{RowdJ2EDLhNmRNwmZG>E$l`+F5{AbOf2JsxLs6cclEb) zRD;rCvaKg!C}s=EVcg3d(+OSHL!) zNnm~(o4efUoY2e>x2Wdh!i=4lvlk_D60*Dk&72Ivs#rm4_~3ekl!hdt8cQs<)wx?% ze#;Uss*F|H12XJ9H4g_r+4GZvI2Lr;8STSgnL@NL41I3rLf3XqiK@GdVP+F z5hL{|;5cyaF&w5og7hdR;gx#2>*jgMe(EFa4PH@i7oikfcUSn)c ztkk*djN5J=Ok>%@f~wlp^#lg?6RBI16TN`%~WFr$pQwW$NjQyk70lUg2kw_Mx)5=XLc`vVcXEb%d0HFFf zB~a(Lgr<#a%v1y8^5}Q9E%bF@)rZAePd%|$XsBWwK+ra&?zCu~O;RzaVmIT(i;s>o z#+@@9OnN!B&@o+2@Ww*DubQ?z<{w|tLiX>;6?y@#dm@E9A%vjoV+&W8m>6i&RvBO4 zZ#r{l1nc5R&}rJ2Fj74Cr2V5_{GSWw6?>Z48OXV1*!0ky)Ol>w;&61Q7m^g#EK5`& z;wM+zuXb!RVPzxcl@~qGq>9x?qkj!}X%F#zIQ8B_A-Ay??+5blDU}m}2948cTYT{Y zSc~Px)(B6BgeuLNitB^>Z$=cl-jwWAd%4mxVhb$GbPu`UqzS0u6#da+u)#-m(>A4( zT(L_v;UFIbI0a+(nfq8RC_y|K!ozp#neTWw3YV1(I{NH$gi@k%UR5!ESK&o6k>fx} z5in;4zU;0jU; z*koMKG)QFUGHkRV*2w7bAyUb7GA~*-S{rHidbNh%!*QfDhQViua3x4=&uJv4Ue;3d zN#nJf0WIsVRPL7XBPDQb@@F(t$+*3JxLNU=yj0Qa!)k5RQyazkGT>WHkEM@$;r?`43G-vKLA>C6<@*LF2X9ru40Fkmj!OldE2S|0weQ4DT;B)I)5> zh6U%+?@_nSKTKyr5Tke6zOhCm3_m|gj>y#rZ_8qdve12LJE31I_O5xahZO59v7bri zyRYRpYu^$42vPu}H-9qe2r{Kbo+7avh6aa=9_;MbU+?Mx6F4d(NhBXxww*K76D0t+9?^t=1{8)D#IAzugDJ;AoR^H$BVfilg5L@V0P}Q4J>^u|??}y`_3t-E?aF z3Qh2HIh`P(c5v>tvw+;P&$b=h{Val*^-(5H2liz>t9D1==i^EN>X99~+Y!f}8+{yb z;w$3sHft6JF;q81c~Bc2uf<7t&>EuQP)_{xcAcHWv_13KIsew2aFsn5t35t*)JKA%-*Sm<~=ZP-+g{BrD{Oz%ba0Fgo8;l}xP&=U^FRSOT=b5f6#-5EAdVmf#J~5p+)>!bLaZ_2K0A za@#1Hjl)|r!;kmQqp73Kw@V6h)Fs8qzAO#3l|Fk8ezr?IanSOVY;fmDK)nfaS87C_ zAJjS&Y+?W>SB(i3;t}3w1ep~CrO9}q_ts|?H5%;}>(pEPkclh}*nH2DnxSki(JzfY z+}LY!Sr$h>J}sX-RnqkWLk&`z``l`0{7NeFrfAi0Df=Hh`O%%j>M*()g(@z4!%RSVrmSI+m2|emT4___AN?&>FSk@wTU16m~7{g zXHto(XZgHMgk>2*+aVNxhujyfT#D83xfKLi-(|?*jMyv8NlTxxIiQ~J7n2Klwu_qy zY|S7d@rjIN`}PWbPvzLQ^Q@%g%Vv~5llJdw3;2L?dJOUutPbz9{i))R;Uil)&|F9N zhfk*b))!Xg%ji4Bg?+N4;2OcJ6 zr)*uWM99;dkbp~OxvcJXodaja1uebcUU=*==oDwa*giFg^Ho z%~L1$Ni9_R_*I%g%-v1JG$@Bz2R_@MLi8IG<14qA4HT1ES6Acv<3HfIzIJtsq!_CY zmn6W{nD!!%wXQZ6QWpEicV`kzvXAHVgqh^}l}^?CVzaHf4#2 z%y=Us)jhQCx+GS&?m71Q3QKgBXbOQ7WnyXS9?Hc%XY6AYeC96ScL?G)2sP41hE<{1 zu9L=cZK}V1)Mq|>6nCqMW8igSc5_V9X-P4JrVVmw4~eDxQi$hWJw+@*b$lKlbdS4JTRJ*6a!~rP{O1p&$yu{+O;I6)7yj~$uf1@o zUw!2?Z|qk>khH3G;l!qx<5IKMYv?TxHvJ?%#I1HR0wAA&ri`Rm<_hB~itv^<#by~ibAvNm@8^OP8hCiDU-70R~k&+q!saj|)< z{j**B*_~BYIF{D`#Qu}|9OE8viEb#O|^0uWjvYDM?=xP*!#=b9Sof% zV9ofGv_xhxWTICqfY5mQgK_4bILq2=9`mQ+Z&cVyGS=e%-%*xlumn&&mw% zCdaioo7|J95#dSde#5kZSj#x)tCUNK z#y?n(d*2?Nq|n)HRnMZV?@@#n&x501qL0b*4hbk?=_f9hhLKK^-QYstA2V#|UPmLt zVoryTNytm|k&iupz57Q_062n>ASZao^C6^5`ie7Zi5FXg!j}ppC1vR+b|_XuV4^II z5DdQd*KIv%xY=D1*r-O6yN>&tiq~%mKg5eolfJYS07}30sjZRVQ=Lbnsm$K-O2?OP z>=C%kH(RDjJ(GF7NYJrli|r?-JdtJQS(y=NvG;mQybFh>Hi?^;9#5BS2=8af_)=RO zJ${J`z)+8i-&oK}{5KI5NOeiB53;}}F5 zUAot%eo&3C=w?014M2n+N1nP{Xuqv2gU4X;N>{@-F3?;Qs8B<{#VKhEenW; zmP-*$$5Q1VJV-=^mIl9i!=eeGsd_^n58Ds9V@;YZggx|G6CthlN`|gDRnDg|`N5#Y zP_;M)v7dM<(P^OC-H(FE(%+ihn;G}+Y;Xb)oGi`WzUA#%6 zHqtzg`!Qn9X2ackP%&B`R@oA7dVe)wwJ@F=>+Ll~3gBc1$DiB}7BBF9yI|=Y(Gzny zHHN+$mF6;_L1V-ijTb|`bdiL*W5-G5$D{;(N73p`Nr5$|O0vAymA1w1?KX8MGb493 zZO}art2wdNoyd30pG%W3m}tYn&mxI4b}rMMrC}+Dx#^9ktvYLy=c@J|`D|}8 zg8uNoV9Rlh>eUl#8eB+b5ku^kM?Nf=i%af^N%zo3Q!gcbdJc{T|?0Xf0{A2MXmXwXto}-2V!Sa!ZFmX zv-dE_1SNXK_I)H9UgC5%;-GvB^d6d8!MTDBAn+TuplESI6~vl-U;ZV777TJ%`&w9- z_CNvzK>&(A=<|QK_-~ENd{hKgXP=q=cl`fvP!BfADSyjpe?J%)!~XvX|5qh1M#-q? U;I4>X}|BKaY$0H~OQf%K!iX diff --git a/bin/resources/icons/flags/PAL-IN.svg b/bin/resources/icons/flags/PAL-IN.svg new file mode 100644 index 0000000000..7af1dafe43 --- /dev/null +++ b/bin/resources/icons/flags/PAL-IN.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/bin/resources/icons/flags/PAL-M.png b/bin/resources/icons/flags/PAL-M.png deleted file mode 100644 index a7e3a81f13d41f046a3337649530c9d0f0ca223e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4061 zcmaJ^XEdDc)>fhuC4?Yqv{yoOB4d;hg6Mr1ktk6T(ZV~JMDHO=M2VVFB1)9#M2}9C z(FdapMj1wVhvAHz^R2Vi`SIO9_P+MD_kBIjz1CiLi8j>NVxZ-uB_SbUc&rUFCLtj| zBq1ToCnF~^8ww6nL`QF^XR1jelYTi!V(RGJRU&wt_q&>egfZ?hNX^uL7SkR~EFyWA zODJJ4A-jVA9iIToJ<%ML`6T%76djuAL{S$3PSH?NB{B+NUhWb#*m#|VnE^2klVo;% zudeZB!K`ON{%~VqX~6C{j$56YKa7cJvO0uP7BprAQ(THJL#k-{*O?nx{621?_ zN6qSk%RAW7uBVi?_mFv|YvMk>m^`-?65H8LXJfxn15<;FrgaBn|50GFzMb~6ToHdb z6~TK5m);BGcUZ|&+nRa1#M5vglD>j`*8TB}vhA|T!0BkayDfxD;g5eug}ndOPEuue zsXakyW76Y%xLbTWvkpe+X**6-VNh+s1I(ZBm|qT#S?=G=v6Kt2gq7i3*4sX#aNRm} zW$eHPO9B_Cv*lLzh(yGtP>U5|%p3nP6Q2?5+uhCR?3`^+z;Lm@4RpfS*!+_{e)-+6 zyV0mi)#sUbxK&y4?{Ruli&wo`hWFE0jX(y;(mJU*_l0zHi!DH=C(InD6pgaD$^bW^ z%IO#P9%V?KWyGdz^?Bqt14gQ|^{X)^?Ly!3dp9;6FJAAN#Q=g~*Jhpknp4Ey$l*@u z$2_wMv0+ZGXl(jw_SCw8EWnJ(v8E5e-~#Gr)nEX$sv(=W&%(qq{(=TdG!*%Qa2Zia zMZ9dFTXU<~>ybC#8>Hu#<@W|ZrU=L%$2us((`jjgxLLDmYHP$E2n-qZtV;%2%TQl3 z&Nb5DxFl40o$mgS0V~fa_`YcnPMfl1Ur!s|?SAT1h(=pv1wWGgN6066=ENtPIeyW` zP1||s*f0}q$m{48&W`}8{sLvKa13MV z)4gakI5^}(ltgq2RT|mYcN)-Kh=>!yP13iM58oO^xGCR1lZllZx@S=-(r5_G`WaWM zqWxCsO#B_h?{y!GEvx2Uhd<}UmIQ8r?;Pt~XDY+qnXs#l+}Rlp3#XM&wuDZ$!{2P# zuBcPYZE{SMNZ>q;12r}@O{kuX+MrQ4g>Me9V)Av5V?uRDyH)N%ZCvV^S4$>SRWl(u-r2MHm71Wc8*}$GATO({7j0V0KRtXWRkDx9{BgC+Yn}4(X z{;c~CFr+7+_b2nR59UJfv0tzEr1Bbrkjk`%*wiR5g@2fzQ`?QG{*OzYpWB?~9Oi^7 zXE!Zl?nKr?mCpD4z{A4-tztPBVuEC@`;^{C=F$|M*$!KXx?Ao_G9JWPbz%Q zOL^b9+mHta-ZG*#b4A3 zVu0hf)lHLgj8N+`$Y*};L-OF_hzh>@Cj^5NdxLOuc{e(;PVve_y~ON-Agb51#G>z{iv$JsMjM1%V*M>ACStb;GaY-tPW%2 zH{#o{b467MS~xUW_8pH*)4c6*|0K|d!h1A9Y^O`C@nPiJ=lD;v&3~5!RIuy7te%js z=jNnIE<$SYkpjkiRD@=XVnhB-o##3U_JrxR*X<*&d8~|FP*TrPzwP(Qh*DTfq4ZMj z-qcsyF3096Wiu)qcN-Jxvf9Ba6$1m zv#2YxB%orR@U<*fJ7Kj!`8RbVF|}MiEiDL0d{I=U5y&?tqLis38+I?QPcfA4H+l5b z>;o#_18u!~x8|>8lxCdvNr-X5k8*NmJMhtuTbMk39v{M(lzcN11-ym(MDmj*Mcoo~82r`OIC>=sP2WJ@{JP7eht?6$ck2 zTijNW>33#mebPHUPYJWQFdiwh6gHt9C&kbUM#C78$K+{h-1sRF5+yt^dJV2U&m{*F zB9Vi=4AKZU>2W#V10!GHvvOIZtFZ?%s$wiHW+)43eH3Du&*Km17$cO0aBdHKbdQ}Pw;?L_+IE9m!j~b>NhU{4kg;&O?wQkK*AJ!Lw6Gbw3%^Zr*}M&+5syP*U2BFY-{cU zGY<95L!MA3%w;xTo`tr~xxV4;3aOubNFb;ogJh?s7LsDfiy`vmF%$>~RQlB=<$s#% z|H2E;tgJ#HvmIS1dbBskF<@*xdCAKfj3|hz2*;M8SD3QfnoYro9Af+U+xgtOf8APX z_*~ToOKrdhDK6VK)R(&i= zvsW^tp~+gbzoKJ$Ig-6Zl~Qi7NsdnWimteoLX=V{SGOkNK^BJkrp>BgL>}=MzTMUl z^g1f~5)>43np@+`+zeeX%upLnc27G#5?xVFP>^iJp3m{uN#2AE#b?kK z{YkLYvPJW&l`O-$O66gIVxGRlqvzXhO==?Z#8#VqHA-5C)eLDAkm{7bu!#8~;5oYp zslUp^bp1Qie@EmFVA{apW$<{|+^K-kS@Nw!dZQOuIzn9|r4HjY4e$mJS zn05;{Zh6f4)U;A-S0Z3-i?=aE3Ei9WW`(uZxAS255i1+!JJ2fQNWD+8)a55P?(m>{ zuGIX}5JbC5-1KgXSWKw9JiIHy>@_MGNe45hgZod9(nW=~g8j0ZAxGPCsiagvb>=%U z=AuK}&MKtlz=>1|SF;?yDkc)DAiFy2Q1d$*R7VtxxqEfRk~(=cgn60@JhtE)2)^Z4 zCB4LeN5ENp)cY3F`c2eO5$=Jk3=zG?4PvXh0VfH9a`fS(=R?(xIzV}u*}}K;20ApG zV@F*jR6~nJO}t13>>;JyK z{~U)thGiSkE+*x$r-bY+j`SPvr=FXpgn$pm!w}`(y%wulk%&rls6t2G@_Zzb5dnb9 zxmN@J*51pp%bu zYPk01Ov84E^mb%m>!8>c5l8Hr+%Y@_JyiB{(HOfzpFOE}nh;y62@G#chUxjaUJ*2N zdnuci(>t8UXOtcUcZh6Z!X@?S8m?f{F@H*7H&BE)&;4@jV^6iWPra5az!GoVGN2d) zFE`Pb#Q4>vVli1NTTOExOsAyW^^zY@1?EnN!Vz14t>M;u5>THYptlLt)ip(XPh>g- zqH(FUAv21+{lz&i^f|XVsyrWwCR=Wnf7=YlW>^Y;fQZ8{IK=&>z9`s8<>bT4<@DXs zn$LRuec#GLq+YV4``Sh3kcDQ2I5@FftXBBe-*|g#aXq$X*+N89{*uaWcIlMZ_WI?{ct5tHJ7|!O;Piw%=u^VW zaDz2jI&&#W-^`Mw?E>0e_n=uX{GR6EriV3W)ONwUA1W_v;bV3&2;ku(_aCe(EiNY0 zQ7!O3H{G`p>&1EijKO2>Um*M zvIk2EFhJ|K-fZvegJ|*#Gy-+Qvn?1VoDNFEf5QdCaNF3_$37)NW<)ch%e$`CW^;OS z40H%cEAYF=eLElxABRJjZZwV}dCU2vZPAD2`!|#zowpg=aK71_-TQYk4{gu`o>hBQ zS1uatoZiX}R?J#czbpsLIi_F8F!XbKcxP-zzV{FVB3~TD9vo&0Q>+!iX*TLdss8(t e{{PTBDlD%lTZM&iWb^P<-D6FCP`UckkbeQ9f9e+i diff --git a/bin/resources/icons/flags/PAL-M.svg b/bin/resources/icons/flags/PAL-M.svg new file mode 100644 index 0000000000..045024a339 --- /dev/null +++ b/bin/resources/icons/flags/PAL-M.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/bin/resources/icons/flags/PAL-NL.png b/bin/resources/icons/flags/PAL-NL.png deleted file mode 100644 index ffd4390e35ebda014a94a03e033dc639f0eab1ea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1811 zcmeAS@N?(olHy`uVBq!ia0y~yU|X&$>Gu6pu@7n)j^EulG4ytcHp?4>}6mm8Ss31UnTWRZvi70 z<4+}p)`l9v2~H0CI6M>;;#rmmOweZv;y5ABsLArAyFrBMldHo}S7uc9at2$11VdBA z1`+R{$y{yomFDK~cbI2oZ&PBo1Dxm?cvuwwuUq|anVS5C1IobIO##L3CQ$6& z1SNtTP$Jj{N(9lMM4$^w1Y$#8Sp~%J42Ko|uUnp&&n&gT)jbwW%!}3EO~lgCIbVbLdQeS|MxC@5I(u|YQWGh!tj;C(~2K1W@fPY z_P_yH#xXFQ_`g*!B$RB>YHJ|?Hs@1)E(_VFgobAF^KpXAVfkDCmKRi2kp(u0x-Ip) TY$qty0MhR1>gTe~DWM4f-3Sn8 diff --git a/bin/resources/icons/flags/PAL-NL.svg b/bin/resources/icons/flags/PAL-NL.svg new file mode 100644 index 0000000000..65e8be9abd --- /dev/null +++ b/bin/resources/icons/flags/PAL-NL.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/bin/resources/icons/flags/PAL-NO.png b/bin/resources/icons/flags/PAL-NO.png deleted file mode 100644 index 25269b35a05209c8007f4c979b9cf4d2dc46e48a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2786 zcmeAS@N?(olHy`uVBq!ia0y~yU3eo@n|MySr z`~L7*Ou;nQnD(Eq-hRG(Q^GX+eCDl53=CZLo-U3d6}R48+Q@r|f#Jx3k}D!Q4vjbJ zoy*#68*EsXxH=4ZWvOp28S(;?(qV=F>y|Hk&m<)CxH1B*v+cFtvdxjHc9s(|WSP zj2tWm42PMNt?CZ#H#C{Dw0y6N^C}jR4PFk6t7$-;Ubp%2-v3p6s@Hj42XmAlrJ`#xwn?XS^BPW>*J2mXz*>7>*NSj5|STtVmHIn&pX; z!#0i+tqnPX6Sx^~Dk+FFZKA5ePZjTf)YrZK?)&89t&iXTTsz<0(ZJNkB4VJrph1(7 z)d!exfH4@%#5F@G;J}myrey;wRd-`)@+~$7g|4KVrxphnhCJ+F$zk13DV$_6ndEK$07ee_s5j?*Hpj#`$~BJ>Fa+%krnPpmdKI;Vst0PKTxQ~&?~ diff --git a/bin/resources/icons/flags/PAL-NO.svg b/bin/resources/icons/flags/PAL-NO.svg new file mode 100644 index 0000000000..3d104a6113 --- /dev/null +++ b/bin/resources/icons/flags/PAL-NO.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/bin/resources/icons/flags/PAL-P.png b/bin/resources/icons/flags/PAL-P.png deleted file mode 100644 index 97698b9c2294ce695450818c6a20b344e139cb43..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2633 zcmaKuX*ARg8^-@bjLDYB&RF8D`hr3 zMGIL6m7P44VUYNbwc+*UJ2U917w zp9ISVpN2@fl}z)O{vr@4vE4&du|J!G=$@AfzPAXOxS5C@Z{ljPu#^VP^NJ?I8o^`M zaDQc|$DYoeW4bM|@!EN?DS5Cciju#2E%%+%|Y-q_>DIwNg+`H&f@ z`&4iuz$@71dH5%sYIsHffizC=v*v+=geSoQIox5XxHXKhWe>9V9pM@#g}bH9zv!NQvf_tv&- z%+YMSMdwgkh2JfnOCK2>&kBr`Pz%lLefNXMUDcZ?D$6_I9sWP$gWE5C5G8+mQr{%G zKkGUPk+z7DSLvcOj^TvlDrJpMV5eoc6ap;wBR><%nqNWF0GCw`a}c~}*o+Z+H;Jv1 z&(vc(kKvgx5r`vxGKqSHEj8r)%l{88j*Y4y$SEl+N?PdsJwD^g6p3jXWUpa|V_rsz z+wEr{cksiQ8dW}rcQZ*R@si>0J(e|E;d_4*cX!{8Mx=I0=om%8tayCC?ui=>KUYqg zdSi-)fe{9k0%jbRn2}9U8IhaM>?e=zt`RP4)1{}GUFpg+aCjJ*1*?oSoA&j8mBuV1 z$qQ4RU*B*zj95IAn5|r}g#;%(0`xQ(=Ta+cy=j$Vx-32G%hqQ!h*&MA=i>0vUB$pm z+)Zw;#|s^^*?|SWYdt^6?85jA%bNMCm zthW*itp49|j7PW>nMr9#!sP|pT;X5-b<;yZ4;kY(Yh>~uR%}x+tb9HfX?n5G;9h_v3YGxsSS)gcR7j(_w`4 zQ|+5nnb_p`^|apWgo2K#7E-Q$+TvI@f3%2H?^{6-Fs$7_!XDwwCR~=N;zEc*lgr|r`X7dnj<-Xm z)FpBHInq~D36&)489c^p{WVi`&)L4Ka3**`uLi6!rE>XvJ%i`t>+uPR_;3a zYb{<*H!<)zLC0ukn~kW`sl0&5JO}AWVdUp}Mi2N@f1$aCxSRNV!+&K@_(VF4M(rGd z%@b#@^xn{}ArU8xdi$gEzfYUj3Lmop@iYOby{%{Si8Y4=^m^vNAlAN)>nDhhOiTGebkr)5OM*HNmp zw+ANub=>3LfB8|s**Jc_ADd{BE{J3GLb-fN8VOxVbi57e1nkJ!nLfVWId&qBmipxC zi+_T%)KcaWQEt?LZURqC@2sOmnjjs8I_ir$bR{k5z0W;fi@>GsHIMpf`bdRa=_#m6 zPI2Rl+kGG2XQ}NDe;pZNh;q$%EYZ%7{>Zc7QuVY=$NJH(Y5oJlrzbrQY)M=@L5@Metdn<)ANwLw1ra6&qpQ?{?f{I`Qu)+m%&-ZCD&R; z`?H?s`K-fI%EO-f*qhf{o%1`1-~|sGY&5c8=t}jz4_?$EseE^pQ4s-L+LGc7@PXX% zjk>(W>EmhwF%ys&;Z(OT?Y|=L1+KRZOWbSjg?1g!xo9e{qT4{Br-X6O3FV9q*;o$+ zS!;!^K8&Qw;j|w$vU6iP&TGVR8Zl3;U^3x-shWJ-eAh7cio!b%Vst`4y1)OTtBTFnCcTB z4X}_@{tMI3bad8C6AHhmi5g((2K^L4*{RUkBkCkO_9Y2l5Q0^QpAZ>}|IRv{yr*%? z823pHQRW}x``{*H>|MLM)E<8KHFJQc@S8r0OJlQ!Pfn \ No newline at end of file diff --git a/bin/resources/icons/flags/PAL-PL.png b/bin/resources/icons/flags/PAL-PL.png deleted file mode 100644 index 3b6d000a703ee716e8af9a576f0974cbf187530f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 487 zcmeAS@N?(olHy`uVBq!ia0y~yV1B{Cz*NY@3=~;&xxfQRaR&H=xc>kD|6SL-J0doP zEnU3~42&~9T^vIy7~h^}6l5?EU{dg2_avx}_k9Z+3s48Oz>AM*PtOO1(*mqtyl!}c YI#t8XJGGa`FoGh%)78&qol`;+0JP@Qng9R* diff --git a/bin/resources/icons/flags/PAL-PL.svg b/bin/resources/icons/flags/PAL-PL.svg new file mode 100644 index 0000000000..8169875a70 --- /dev/null +++ b/bin/resources/icons/flags/PAL-PL.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/bin/resources/icons/flags/PAL-R.png b/bin/resources/icons/flags/PAL-R.png deleted file mode 100644 index 94c0d12f317ad1a556fb8e45f2e752e9bbcef9a5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1807 zcmeAS@N?(olHy`uVBq!ia0y~yU}}z`(ZK)5S5Q;?|pkjX-q_4GaFR6ko{%Vv+|EzCCaN>pby)t6)ed*`U?d zLJeq6B14Lp_Kp3s4Yz@OLnf$_WjX6~VYByf6QBV+&3|^!Yum@2;o!i<_)|%twV_6E zf|J8O4i80zc$Oss6ZDyaI8KN&YO*}(ZV+Mm|Yj$>Gu6V8gP+)j^Kwl9B>HW2WGQ_J*OZtl-ThL*4@h3@jT1 zG?srlF2ZB(T6I&xM^Paf6wSJzXchxTa|$S$H-V!0CMY%JfKtOYP-+;G%8KeLUetV; z0Q6GP&85jwzy#ms_%GJ~@I5w@3&SR6@h8q>da{>+{x;zG^u9{!o7sZKp`VoTdl;5! zBX^hh0E diff --git a/bin/resources/icons/flags/PAL-R.svg b/bin/resources/icons/flags/PAL-R.svg new file mode 100644 index 0000000000..0b6a26ff98 --- /dev/null +++ b/bin/resources/icons/flags/PAL-R.svg @@ -0,0 +1 @@ + diff --git a/bin/resources/icons/flags/PAL-S.png b/bin/resources/icons/flags/PAL-S.png deleted file mode 100644 index 2c07dd028a54251633db3b76ede818395a9f1dfa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14346 zcmc(_Wl&tr6E}Kx7k7ss!3pl}?jg7Z5AN==IKeFhhoB+2J1m~y!JQ4kf(H-!^8CNu z5BI~Zx>fJgshX)dJw4t1>+U&oW^&MO^6Nx62 zmh$Q@08jx+vQpZwPjfo6 ztgzyF9?olBkEcHQox{F`oa8}?uniaI0$cHHr$l<=Y#Y$)FAg`UGuHek6V4gLt)oA8 zB-l7YaCOvpLpEk}3zomJI%Al2x|AXMYHJ>r2O#avx=p{wW_K*>Y0pzS0X8X*$U5>& zSjrFkj&|qnlB^A(3Z*{#BUJgZySkM&VPiM<+2N&}Mh2T0yHT^mzngDWPao%|@p;%_ z#Xo%<4u0qSF?8cg{4HPq`m$ydt(f!f%IwV3mF?BTt@S3GKp0pQ%|+m}v`pdKD)w`K z`$u>70x}`u1?JhI^}a1_A)#?g^fQ#MdGgL&BadSFdVQm6hG&x+{(i=SV-nVkv;ox? z;Mt`tI=orA;Z#v4fT&DG$-K0JO3~DjZayLUpW&Bu3McH00xY68#ASvGZv;M8d*H&K z;TA$czuh*ci}OS4TU(>bmCcDpk zKs{!`HmP?raV{m!fzxSKb}rWEKW#^*6pEy)P2$ce!b@F6bI60fJzVt~?qP|cN_%j? zh~2K|=I;Jb>7lYCRR_QXm(RRS-f<{gaaPIYBk^t1M%o#krx zWR{wtW8}cNUO~O%r}tmmjk=yJO`#Ri)`4zykCvRy1hq(F6aP#KQgPuP2V|M2Y?nlO z1>)wDn2!PUf#rvp`!FC}P*~t{`;VWwlZActL+QI8C}%>=?77Ep#*bNC>wSO!vTf-s z5^P73 zf4Do?mO(^2JA1*Vd;{I1bUpOPY3&iV6W@EXl`eNP=;mog4!dq~ikWwD5deCwq@Vf@aCLG8Pb>Re!-2-< zwfm2mo=iqRHt?VTHcPiUYKKX{<@!oU?PoRi=ELyj1LXBRh2{1{?9yu@_r35RB6v9P zUzfa;-5IBW0~s|wNSTQ>9Bey0=Edoi!A+h5>^@FvyTZZd{6Q)|CI92UX2tavPMfQD+&)q$GxqeSIy zuOq*UI67xLp`&qBK>s$pX4ByNc;0>oD=qAq$js^aFhRZoH^F$V{odi;F0&TXNms9N zD%)`rb@=Cg^r>fF{urSR0wYW&%TLfCj@dXoE+@;?$|YC{szEI*fVWUIb)A@RG-_in zv;K^4$xWI-%={vZdHV0>wMI6X!;hOYGh5c~_)8jw&+A5F%ZQ7$5xvJGPfBg6PFM`( zM?u0%_`4%zyf5ZK3GW|thR&)KT=nK#Ui}?#rF-~V+|(P1Ps|^E5-;C-pbt_g{<%NZ zyVgT*#CD3xdF=5MEE@5WN!9@^(_>cuH*dC7#TK&+sRMrfz2DTS^|buAc&1GLT=qe) zfdU>BBK88AThYI-(@R*cHz}rviKo&1wsUUvkJte=b6dk+LvFPC_#8n7OGhW+Oj}{H zqL4o^J04+nH?O{X{JjxnNmBK znzJI^y;7b{j?$DMLAy^q7Q31JKJ#6(DR^F}vr68>Mi=|yckYG6&HjdC&eIBG89CKW z++BA_cPhP`TQX$dWF(OU!Syw^Z{U0~&GxnJq>ppc7V)Bae8)h`VMUsn>*LP%gk?1_ z{M3yb3zR#sP>dZ=gyG?!>am?fDH!gypx7x@5iI28-g&l4Iec;y!yW%~ZJ!ze1#W~w zI_>8gV06KpA+J@HckQJ-aovW(ke7OGaDZ`HBivSi2^ORb?km&q?8oHTS^l1yA^gd~ z6}X=#$Y}Rpd1Kvc^h>d`6*B?=J-CSkJXG|xphw$1#caI!<5{av@m|@yVn5+BPk9@m z$>$NpL-Iun!iNMYtuQ_QUe&^d@Y;OhnI@q~RPT1)GQEN#6e(b@E&v%jUB^@)B9VF~TRtaP>s?Bc68BrmDa>q`{Lo5gwVkg_v z;p!L*wL?Sx*>ZRA50DtTuOV&2YsAf@L&Cyq+@Q^!A{dB(H2BGfe>$-LQ3T{&s$Oqv14Z)5SE%KLUjc>H+c9 zx=81`Ax6wTu%h?zdL5eQ1}v8%j0Q8feDmftjQQao>rV`l1@^G}=9v#$6<&>oy2RN* zmKxk=`y1lN(+!Gk>&6Y_ja8AG(@_?*9 zR8^%P)W6J%b3U(<^d?k$95R4_n)ZZ$GNsYh8G}Fty;zY?{l)Z2?9afKlIiP@UsQgr z{jH7J%C<`7L!(>$#>>6UF%m#SE^|bvHQ11tws(Hpw6^~Xi3SQxq8Bo>6mIA+t6p@| zb+Y&_HyC&wazWJ-L4Z&JTapnn;3Y84S`@?Z$~9AYwWJIgtKKpzCk6luhTznAfRWdV z+ts}^m|-8xzI6`XRAXpNh7_nK(KQYfJi}9}cJ526Gn`QRf>3_cj72I(U)U5r%K-1J zsY^3#1tY7}xV#K++m1@qZGV!uUCyJLSRAAIY8Z^uu{Y#fv<;;) z=L61}B?fV034OP(Xh=~%>*Nat%e(hSF4#c|#KFE6EMK!(%NaSIkWVmxcrwjT`K-Jb zi~7gAxorVbZnAG!e6o9mMisAS^dw;ANkIP62E#umt{;_Mf}?bx8_lk#xP9^OD@|tV z?8-4kNfpRMm{b%M1=7=^c+kZe&(Kc(PIqY zBWShGli1?Ud=3|;);s${3w_gqnbf4+yo-PSPh$qxR(+OsjL>lUizK8}>Z^vpKaD(W zihgyj6QptR$9$aL>TJ=dkob3$Akp@Dy!xEh?W!oP?G8cp_H)L)ZA)XJKXGTRCF2Y zqFi)`MZTnvKpqyU0;U-3G79z_tj|5uF0ebk%^7WHhND>L>@5^LsT3}DdOdW$s@clh zeO#D8Q)8m7z0h0pLr>(pI>nsXGP#-~T@tQcBy!ma;#hqnwmH`^rY|oZu(|ZD>HDl? z-pBY_{foEr-hVJWAd_Y)=>>6ho-SjBZv)R!;Ho)t-$~zH^3F1y#ZMhq8t%vYiVDPn zQTn_II%r>~@V#w5s0;SXv9nI%X6>VbHkL-+I}U$kD8*&HC`D!UDf@=$x}lAETYK=$)}sgk zs*xlhao%ChL@Q=i?pgOx{->s4HwUTvW~|;n4*i0PlgrV-dp}Wa`qPu$bl7fdnP&6T zgn03L(Z{362TX~lKN2`ekr3Ab+U4ngEPm0DuYz9`(*M+G)(8+_tzwzc$Trd}dXbNK z<%C*|W+i9B6j|S==P^sQIIrusA9hf*61P)~EE|0xi6F*?CA9?yv2;li96Fo;9A`dS z#%7Wkxq9nJ-*kh5aIm7RNL<4MN`#M}AALO59|ZzemyjqPI~9ENHrHjvi7+MKIe*z< zntc67Zo2bkXO4V&CfcKG9(Swl213@C!WV^cU0p#|Qrjp~G)Hx@O<8$_w-d)U_<bW1oAPK^X6pQ}fp$JI6We03WACzkAq9PA>Qx z?no#GxA2Eqt2aGYVjS-@w^O8Zm30 zXYNIkyrurjZ2+UH`=&BV8v}pAVBoy<-vPN?WBMn^oq0R84Y)rL2 zQs65jblDr*Bh#B=s#7Y$xQmI8BCb$Ll_jd1ZG1+n57)wXd6nEVnWLL}FdprWtegykZ|E#G^7rv^UVl1x*^(KmAksqh!ggz)oK;l^!* z0SvgXIaBSezaure4y2W_$5vE4jAp~zY(KMw*PC_>bhEscdbqOycAldx7kU<|dBNL1yk*2q)c_IZxLMw7pj z&=l@AOVYMJA}?}f6yHw59^OX9>kMo<3rhF|rhaz1HgIEyRkiGiqse9Cx*R+5c{8D| zv2m8yCtIP+j-N1_=Dw1<{)*x1H*kKr%F_*Tq$7aQhw(}Oqvl`8pV)5VlfzuFw7{I5 zP8o8S9T=b=PB#;Z!DWgk@b8heviv%{7Ge3$l0H$ zFXFEWSP}89hAMwm%KV>zSWU(uv3)()+H~bhL{mEMu^4b>QGUnrrK%}}KNW>)bFp;0 zQY*BU6C1=hF38R2Hta^yx9afaFX{Vb78tD?Kr}+CEc3`x-U#;nXABI^sty3a^ClV! z0EQMq0V+l+00jsGgQ2Iz;9dKdAD_1>%y7_4-%~1A;mRKf6o9`1`#S%sCOEqq<9_K^ zBh$6BtnofjRV=Furl~bew$XuoemsvsiUN#ua0a~A#)7}m!qCr&xiM%LR)NuSdtxVz5mDUv=%rgWE(c?p{r& zNCH~DH4#WpynGar>^hCiW{)D(Vb5de+?Ris8KD)MOXjPth5p z!Mq}99(9Pp;-lJt&~fc$3++KO7eLFDi~!D51!z-da8&jf3ihyLqcW7nb5 z65|UUCxXy1ITR2G)~(q1XV?=MBbgo9AV3s7cWD$`eEe5-Xcm|kYoUAEJYoTxE1;Ev z^TSeP0gg{@zS^g6A?lp(v~`xb->{fX-sX$Xi)_v3)Es&n-+T!=%N{jf^crq}?Hx`8;0aJFJS6X zAV6W*md(-!4887%UCarfEX>e`9a8WrBzcY7!NC_L-dzd>bv@!B=lyh#!$ z6TR>Wh(akI^!YSO!Co4}x1HXF%I`xYgz<;K{)hht?pl@aaZj6|W~ptyNp9cBJrtK9 zPXcM-&UOL{G@u99It!YA$<)ezL1KU(7-gr(3mQdBp&@=Yg!JQARddSS`@F42UX z3`nZRqy(6a7H=PrVIUxOmLORNV1mXlnX#yWvSx&W3Dp-5We|@rH{-~fRN0J_Nq!8_?&8t-<_GAK4FM32S#{&k4h2&4|_ z#my%{g;cyu{;dc|@D6p^YQhhW40N5aBJtjhRIf*2H~}E_g8DK@^vmQ;2F&QJ14fD$ zU!RqnG_iV29g0Z_+Y?3dlvS4mT-6X|Q>p7%GnuGjHDHx_>>S;K=BV3RFfl#i|5!AF za1tPbx#+v01t*^^Z_OzA$p4cscNXzO)vIwthdD(22Ae_e0oQeh#Y3<4y(udPX;Qxb zk!XSH6C@;jH0%GKP9-oDxH%kg)F)xa?gS0=&fwXm`L2J6Seu}=vt~Hb7F6t3(cYNQ&UhLr3SIsP9SgBKb zGqm8ngs8BHD6r;TyI0l9L>BLREJ>N>}wBF{5rfQ6B)tydU0w&U<1x3A>{v=p9&^8yC(c#?sT*|&Nm9!Ee4R#Zbz%IS8!Gw zi3U-N`bl422x3F^2S`>0-IvLtn5^WLAN1p?q7ONM-w3kqgT>ppc z6e4YccHn7_%|#yn(e(^704Nbav(BM&$Ikt-jDO`oa3=ww|~n=NxJ7QjFX zt8q-s2Yu%Mbnc8GsrfLVRb(*8@XhP-kIbh*X7~tYq@k7=*o$9CKpUoGHJ)jf;R$fo z1K#Kg8|>5RapOH90~@xZxWsEkfWI-yze9XN&ihJry>SnhXvvnh3ueq}fA>zgqs-Vf zFB20VDu^fmRXV5&{pOX4)XksSAnI_gYigzhNK6nwsTKno@|UzQU5q~D$&(Vmxaqh& zT*6^?2s}vN!RB0p?Nlp_bDwF7k9A4+lWA1PJBpomd& zhM8ZXCV1R(pn>^@}@p%2U51$CO zT=xZd6!8LqETEj_zURltWi^9jDgyAiYtxrLsiL==k?R;Y(T*W|Sb@0omQ|1lmR4g}hlMkXt1hU-wWQ>`iSh|zwxg~IvI=3yh#5ys$1TZRn<@HLPnHP_CdxOLnmJNWFn{y5L(5?g zP)XJqn*Y*A)|yzMWTU6$#VM|1zP>Dj((sMH;~GA&YvEq^<%#T0{8XfuFV$Mf<@BB* zMWGk+bQZL+=T|`ane@??qfMA(4mo+yg18JHD9xQBHfd%OxwmBTJ7I?Bq?rSo$bSQz zv&(LSA;4UP%nX%8&u%&=09R4&rw13tlMcKBr!R-~AC$Be)|;lukT$ zy4NkafWB+U4c$%Tc5nI|;df+D{6?BCxUh6Z_8wyEB~|^K z$!X~`334FmCJLZa()Tn!M@+ljI&PxK`c*IK4U}-%D#ok``EVD7RGP7Z(R_dPM-qY5 z|17F^C=qhnWDi@Yb6Z(NxGcS8PRg@|yP-&8m{Pi%D9K;`;}6W5_VrYt?xw(#`qT3RlRr?@9WRcJZ&F^n8_AubkrNqKAD>;0RZBU|J?=9 zFk~u9&6e6}3Zm=UMgq^Ht*@G+K7=1NDTpcMKSz5ipMmzLI?nB-#g}T52~gbW25z9N zfbWpluXfceu6y#o*}PdQ zDCND3R8LV!9;@RUCElojK2<<)C-+0HYE^97sYTpsEmkawli33I5^*RbrC83sfUSWs z^gNGlq1)pq;wYsi-R0e0c!1>d9&+UtXDA(?ywTo&K(k(%?zqP{s8&JMFz!qVU{S02 zZ|nXIAf!6&h-D|l@|}84F{KCafMM#!XOO%$*>f2+R0yESA3aR4xW;qQ`MO%PRJ>k>BU^jR&)@gdX?0D`o;`D+FRA$ri_;&TA=huD&_) zG#kAt89ksHOA*eSX^bp6OO9-&4*E4Aop?`G1qi-R{zy!w4cjS5(t`2oz&wjXPeT_) zKDYL`@v1jg88M251YaFb+5$F*GC*wVWDt%#ph~c-NXPIMN?7bKVS#OXQ+6Z(LuU}_ zmru(@e6M@Mw^JK_PDI{>>I2TIC@%(btM$gsfg$oua03`le0fIm6^f|A`C9EdZ1y_0 zgm8QvjCl4wLJ48fpHH9i;bkd&^_F-C1t`A}qKyZ9BcFy#aiB%EYq=!I+MG~H>kVO9 zN)6x{fp{vjpe0(H32ns>CdsPZOcbY_@1sLR+;5g8fO6gW82RY&c9=os_;|l#DmEZL& z3Vxs*WhqoZh_(u76Osn)i6=s+^j8BiY{Gg&AI%l82~nq9=p`PQ!LXYbE^>(|JOCbH ztU63t1PKa0OKokSZz|m3ieBMow(E2y!Sf7%mwJJW3p^Rd>Pm*XfR;g=8jHmQu4=sm z5Xo2SV?N264p5}vxbAfJ>+sMbksm+DKxYD5hWa34B?l}u6d7RhdK?AtII%m@2l|$F zv2m-CV8~HdnQeWQT)@MtKqvvy8Mh1O00ETlqK6P%S2_Y#4{Hit$NJ?ABEVU2a3?yq z!vH8h7QNDh<$ifgyXYf(yK2Uk z7yz4>tE?tiOq$#KS0|r zpHBiu(kVHY6~(Zh?}?n1L(>Ffo-zGz>gZd9=cT4}VHv{K%bWI}Whq<%A?Rx;Wz{lu zMsy4K8hOHcl6OKE-}KX2q{eIBdxhJ6Z6|!y$uLvVsA56II1r*^)|A3iJt)`9e}MeI z!6z|Ee0%8wNM~QITcM%DoG7Gd%4aCseKAUO>%U5;T#bf>oba50i_1K$Rzdo zwvBd*AXpC!Y z$q}|49W~(o%0|+xXE|rN>Y`U2K8v6;F)m3eXm&6P)|)2+dVyv@dqs z;e45mI_YLcie~6jxUc!kKZaH~gcEn)(GLc)B!dAVCdwOvh~um&*>tI~-z;b3M7T`f z(5uF>kKTS_dO-5sV|X;Uf%vkSWnp4b4FHoa$lKq^(m2&XJVM#2NqV^R1hNUP$kte! z$94v9Wj?i}&MO<%M!Pgw4Y8Iz0!ZGWMZW*AkYBd0dmf>Qzj~y&J`mPPCrN+D!Xe@s zd8d?*@s*N)VDQ1>AolDc!SCpC{V6Y zJGDEeB{9rjF~pK@t@R8RTnR^q+}=Vg8WO#>(}0lAr_UD`~1+zLw@KzFO@#Y7wC`e zu1U~KxNrpax(hGu-!oGwFTbue7}eePerMuX95O~o36&l~U`W9GW=~6igMT|NA(1E3 zPXaw3jS-?wBj7YfLr#6Kwe*YeLmWDQ_cM(OA>k3ui&px99_%4K+4b&@D>{Ph&EOK5 z&{yj)aM^_Vzvw2E*T}czHQ?Zc*fZRrrVOPdZAp^F1H4;&EL0T$t@`J0!#B)@8_Wna zjLhD=yY^(sXig&3+{#|R6tTuO^$}H_Cq(RoB;YUhpqlvjxe|Lg{vf*q+CjqEU(JqG zXFM6YYpgItfK-=Ya&n^pBFQDZQCMZWLywL?1!_|bo6p)C{nH zXCUgE&_}zalR^#rPHhgB%+xpTDDYK4BtVpz$ky7jk}+OTE`(+Z_z77RAdX?arAp~j zd}C?-Re}n^)!AK9A5>>m)7uD^{zlnkTuD;*CjgN{<6A{+8s>*WRunT&5^F_Jtr$m4 ze-IWbEugHT4mwXnivh`^%v@NpHBQg|_~AmSlk1pSq$zt=s3D!ruK{xDYt#L2>2p_4CrGG0e+c&Pr__p4VhL5%-i#W)^B?PSk6nCp@o;L~9OH7ay%{U;Z zU_{p9(I*u$nBEp0VuYsozmTK6F$XdOqG2h{4Rncg&}?Ryt%it(FOmyFsJk3~m+kuw zLOs0rRV|%rlAmTVJl#jubVE*EcA{un$9)(yM6dC!`Akq;I>?ssS|ufVJf+#_11TOI`tT*$r(h7|cjRPe*UY>=19CI8jt5jtHr7Eq)DDsx)-v6Wc7c@R z$K-V&X$u6nS00pG(O z`OH#$N=i0H-g^+33Qw+j)$$>qPSlH!f+*)^0XkivC!@E!7&6-JBR$$qBr`$qUCi^V z?!&I^={@h812@>*un2(10@%#_2&jaV&3hSCn;6xhj0f`o1|Rs#G(R@myjPCzz<2+M zLe2r$6l`pnxO&}^a#k9BY^x{l*$Y-DVF<)pbW1)>H7cS-BsQA}?y z*PeCy)1m&WpW?y{qQC(|eY_H({kk{&w~w;EwuJL>b`B*#Xv*gR9Cm;Uk|E z!a)Lt-fTlh0!4_f`UnN7fUY+HOYs7j9Q}mI5*2 zsy+}1UhV;FvVUK-J-)@h`FRMMHfEUBJ7%=P3Rgn`9!juoJq#bCC^MZaO>83C=hv8P zhvFB>kigJzkP-P7l!YDI`CxDv(WB=>scPCq&`g3|p@6F+&^lrO;THwQz>*#n?5!E1$R(A1s4d(_=v3r5zy_gYJ2sS< zNJV@m;1IuvI%Ak;%fCY3CP>)vKXcGSXLm^)B$41W$gVaOMpoD(b_^0MgHBVrOg!%B z*=R2U1c8ox4LeUfivXfwPf4Ug%T=N76Fu|osP?w3WLw@{Oy)O^IyM#cuiaoWE(eE` zhBmKU2k~I(cg!W#eE^mRj4}+%5z$L07rQ?veU?{t&U~<5$s_XnT+)C)g5Va4*=XX| zCNE;-s)t2nPU8hjmOiA8Aw#Xv9J^r8J1*=o_mT^FKH(dp{TON}&w~_=X?sCGwY!%p za+CNSnaM2^U!eNCO0VZS)m9e;VRD;t!;}i0LAW4Turm$afdj+rU?>yIc0BGawDV}= zeK#2_+PPKonj36ZPriT!#P5)*zxidFB0{LdT?X0RJ3I-ly4hgZ$AKQfF4MR-)6O`@ zea_b!6!(dxU0FfAwC3t$7;rP>@tvX2N^9pSk!n6LJi>4tcNLUGlYESkMGA&)nXHju zwYAk&DV{*wbV87uoCAPSw!gMUdapSDQ48~tb0z}fG1g#}P47kXVOQ8t%ekr`9NB9C zd3sJ1u*2|fMU)?fRxsYTb4V&+fU4wqG@1o*V0~D=HH?&gc<04N*V)n+(oNWK{6BdM zT9M0%4DbWO{TK=;Q^#%6D9sne%$X5)Ng0h@`c|=@_|JWGJig0!^1>zuYGFz@YefH0 z0|zl-HJaqb_AJ?v@!l{a6AhvR8PFObmhYu4d@BuIO3)cly8>sTL)w-|2S9dMMT~(W z{U`U+#rxF2L}{A)lrF-{5t@Q9+|wKbxvK7wRU(F=`#@d-h%1swF#@2#nJ$*%%f_C3 z8Ux@ZfnK2go&82PUkr?PNqIJJG$SCqIBpY!L8-kL@^aiZ$^$eW1z~o6TFq>W2N*ve zjMzu|j3#XyzA$nvfiSe9(^AyU=%y#e^gUdf@wC1IyVjKGj&8w9+HGHv0$|urEWvw= z5(w$?WY{OM+#6?q0v#zTWc8MLqo3VkcnL572S@~&rN+C%iXXz%8tx2rFRAUnW~)#^ zlJ5!(HP-QeS1G*EY!JbYHL!e382?>cikuF^+jSD2jy~=|%M8VsOi?`M(q3rfo4@v2 zxwq2wS~e7oAgZO{VzreS)mmZkG`B3J(i@AVZO{e~cBr8vQTEq2G;q&>C|ii1G7aj|l@uHmlgH*GRH-c|Mk_s0 z^*_y6a2yzIzuv_1J9%e}WjgYK3XcpZ@2<`Jwv1s?AY@*fJs&Mh#RpThHko`E+%m~1DC?Wa=s5LEl(Jv+eB-4zIOUfVzcN^yvp6HB!g~WaL|4TG(sJU2 zI~1eC47is-dkX|e@Yu^F)m?eqN?6f1!%H7_>d*6Qa(!&0AttWU{Gzm%0^3lceJZoP8c@$d&gvMfFdVBa)B=;iz8qjsfR9q$NCAn;KB@y+6YY zo8)Z)Fc?@CX~8sD3d;K}NWDFuasLf!?O7fjC~H%}LS%%T4K4U%8q{Q3DYApA9u zR{QZaVx8@)jdQ1vbHhoS-EKPJn0}K7HjMl`zneM6fk8BwR`D^%)1W5j`+yB>$%9Zj zT%_buY=OkzU8S3qcORLGHd(oKdA7@Wr)D@&t{h=Q{dFxWrE5(CUxT0Y(8isW=`}^~ z_2=%2$ns%QPF*XKQJiMB)BCB0(3$thm+t0qkIt;c}s^UF2s~^JBDc6Z=+n2z@rc1~HUkh9RuC`RtvY`i3$V zm~+en>ju*21;7MB`__~;eD>l7wcI`({B7oshcI>-sU@zM+tv3=?VhQje?6n*j~l5T zQT_v40}pS`RG*OU>rl#0Xo0zT+t@av&mhjaas2+fjd(t4v;%BuGnHKs}#@y>j0c&YmP1{t)lh^3XoQD zLFMDhXBzheQDNGM&+b?#(t2O>@8@Bm7G)t01qYDR;aU-kzlNxy!H^Lh;yQZZ-sd-y zu=$yG+2%WBa%<)#oV8)FEvzsFN)3;6ddn9OYgo%#a35zo4R`ZiJ`pZ}1!%(_^ zGx6mz+o$J diff --git a/bin/resources/icons/flags/PAL-SC.png b/bin/resources/icons/flags/PAL-SC.png deleted file mode 100644 index a3fd9d1c7b24f83fa5a042688ecf42c9359ff16a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 959 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYU_8&l3>1mHyjT@T@dx;XxJt^Uy}O}knzi-2 zkm&#a|6g>p8v@m4d%8G=RNQ)dYop*{1s;b$F8v9j5sj=d8~*#`IZyulv*W;ry4Asp zuWL=ckac~Yfo=rU}R$95KwR!DvSx04n-NU4xdgj(#7FV<|>pX@HqOCmY;?+P<13AG%N_f6`H~i=-&jvBEM+{Y%}$%-5XT+l^qMIsjHH01jZ-K| e>Jw@m^8bg=d#Wzp$PzfhN)!$ diff --git a/bin/resources/icons/flags/PAL-SC.svg b/bin/resources/icons/flags/PAL-SC.svg new file mode 100644 index 0000000000..444ae0a92f --- /dev/null +++ b/bin/resources/icons/flags/PAL-SC.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/bin/resources/icons/flags/PAL-SW.png b/bin/resources/icons/flags/PAL-SW.png deleted file mode 100644 index 57844722556cce863a181d555963f35bbef771be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 581 zcmeAS@N?(olHy`uVBq!ia0y~yU \ No newline at end of file diff --git a/bin/resources/icons/flags/PAL-SWI.png b/bin/resources/icons/flags/PAL-SWI.png deleted file mode 100644 index a830932d3f2e89186a61eff932537af7595c12f3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2084 zcmc&#YgEz+7AIe5D!M*O<>3p{Tpz7z3OK&qqVhq>YBP=6yP3y>7?{U7$r&X+x%{jlfkJ@>=CzkAQ`!+jP62Oibl zYqA#v0%>E9`JDoRlpr9`o@46D3Pk62o>1W^1)mJ@7r?CmU%o|%S!fZal%|Ame=1pzHCRV$^#zz&c zS;Rr1k>v)kG4JgoFg@@YVIzbh`6d=KMlimf;m+3fCWrGSJZj1O-~!xSXkop8KyET# zE<~Q^?XwW+Tkv(w=3odZlR2$rxWk>6RFrStF51YtyqcJ?pb4APGurJNl+$R7M8Z;B zqVNc2`H+hcvR@K#R^;moYyhoxw<|#)H7?fAJ0xRbX>=%FA7=y_!NmFG#XsB^@cQP* z$`R)j+oqu#&gEtceV)OqJ2MYz9J)o^vOyFpSx-^WugdZ|$*$Pp07piseF!Dx&}gJ* z1tl#JMI^3fZgKvjN#aFW@lZaaZ4M?uE_)L2u<*X@% z&2g3S&o>}lcjCsn>K;)}wu=_zTeI1vHUo3B^O0^wTFqw}){b@L&A0|6`kwN?RVU4C zB-ab91XurG82Oji#D9NSV7B)~GRunLtY7e_pByaTc1HUgyV*bL)A-wU*I>IGAoMQ= zcEf!`Z#wWrvLAm=xco~G4RdBS*9)S{+@pl@G6SpgLAfxqJIX4AtABvo1NU*TZgE!; zJr7+o2(!>f85+;HlVDNc@BlLUB$}pWm{2K>Id=rYII2CwN*W!1At&8ZJDhq%+SVeb zNtu(y`~B-98QyK7f#j8xk&*d$);dmBKlpZ}R)!B&aS<)0l^{)v4HMKEPR3;eICA~c z2}z=fe<0p>#$9w*4NRi_b$UfE8Hd{$7xHkmnP6|I3GD`C?WB1v4 z9zor7+1P5uX|@@?EI`--IEL@m{90cXs7ZDwKek3`TdhwM4-^Yb3)*eZ?Ad+6+27M zX7}leJBt=;LiJTKs2iQ{9V0rPssSG0bTH;`*OL9e;eW)4co<(^YS@R-@i4)?Z@Vyd zsUM<=kE?u%RleUzAoAx7O~T`{>lMUHHL1yBLdL^L#_=s3P2!7l-Vpvh1Js@CUJ$;b zdnk3j7ewj*>n^)~qv7*NV1Fe#%`GiwrD*@6J>*cV+_K*xsPwy>8*bQ%oUnuy^(R9x z8ilQ_#q5oSoI^az>?+{>))khNd9}CkDWiCsnG$o>Dp7FfMk1n6Ks!t=StC>m3pj$_ z0xBli(Wo{yKah4gD`ZOS5!Sl6W=pDCc!p~y)p4n;zVvO)ed&{H@T>{@qn7O8jh2|~^R<+`NkMY6`f)tR zd63aJXxMTrjW~gS5O3q&g9XR&Lj*MptdR<(snBiMBSA$|6}oCMz!Qgux*7m;{#OjN zNw=nQH_$j~7$+oS+Cu6R)T>6C%IW{P3>*|+Ok7li8R3H5C zn2ay6c|^}!tqA9n*{vniB*+$exU2dL-M?*W>IYRQEu~@A>1Gp}7>a$isS^N*#UE9_ zWtXZ#!*x}D>g$QBy1w|F8o`SYkaG;v4R@{iAOZ+=73Kvo1K&|*?$P<$lK;oe{{vj- zUZl4mb+7I*sH^wWXo$uALoaGNQ7g$Z-U4S&^TtU4heSYRcg?6_Z&}~ h)cET6Q3RJ+J4(#hOM4rfQQ&{&V*LaCs6LU`eh14X!5#nr diff --git a/bin/resources/icons/flags/PAL-SWI.svg b/bin/resources/icons/flags/PAL-SWI.svg new file mode 100644 index 0000000000..6181b7618c --- /dev/null +++ b/bin/resources/icons/flags/PAL-SWI.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/bin/resources/icons/flags/PAL-UK.png b/bin/resources/icons/flags/PAL-UK.png deleted file mode 100644 index e6ef417a8edf7ed0c12e9badd06c30e353b85f1c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5761 zcmeI0eOOcX6~_~k009Mr0ZuElAh;F^R7Gq7u~rm|f{$wposp#LoV5&-);)ra+|amb zt3z6Ag*J+9samDHs8f&#mrM)30i6uuD>q631(F*O^1{70?3~-Kd$wnLp1pK$&$EBH z&+nY``JVGTzw^6Eczad)vMHX^Jp}^6l;x>QRtp5~g#v-7Nh|_Ogl@}Dc!<|Oz4B>+ z;CQhY`;r9K!cD7}Ef!R@J#-%y!K+q0mohOiA(*k@qWfdRWXqn9NQaX#KKtX}B|aZC z{|(lO$Ch0B*b{apSH^KE^VtaoPfonCTV)1mO%dHe3wED5-{&hyn}7lM4> z_VOrM99@|3QEJA!fxj&jA7B0AhIfA8_xU{2Tj!S>F78NDx4ty9(aw~~^~>gV=P<(w zJxM3KitNcBo*uZ<;4`~*^e26Td6@*;GFMKTm(4TIP-^C=bvh0EAhdW)ubX&-eo)zX zd|6)b)`6CUj*bJuR!;Q#<>Jr6$&k*`5_>$;wa_&pN|q1n_ftnBw1=J}f4EYdc9b~s zp~t?}oXewB@#s}Lqfolfk8@3i#g}vpEJj$DCoJ0NO|ZDiF7$!L71|3HC)h@0F41YQ z*uyp=bAgV8#p`S%GR<@VELOAG5?Gw4--pEJR)owZt!hU2|&s1m+HFMcsrHZ4uq!C>==*eK)$F%|U>NPqF7Mr;?^mNfv5gJot zz)~(4@*Q1CwqV><`dxI5sv-OfoM!p{&HJlsOx=V3>Z2;l&-pNH?-qY$3YmboMR z3u-P8Th-ABi->lFZ&S1rVLj6z060%cmLO~yvWFsEz-41*eFf!(uzghhD8Te?B8G?C z>0TaouI1qvGL477%A3l=Y_tgBUDQz`qh{|w-m&DVQHRD443K|3<@7k-^lE6)@_{YJ&*;}B$8y~`+F3_Gy3gk?2DnE4tC+hOKz~6 zQYvU+(UKZR>j-nVMJnyeYACbV54DT@KmXWf&#tFRlE2KTNz{0cFqgjnB;w{pUlo*u##7floI>4qq~}40Gu2*atc;`9%?7Z194Zc@S!#Klax{QV|<`c8&-d zT|n-Lsay=F2Qy+^3L>X1_WE7AsZqoXyV~3-g|_B04X%+-K=v@#CJVu1I6#)prQK+wIiM+C-N;zCzv50&6>Lgm#e#fO@X}FlIWQC zAm&EL2h~)zbNJwut=tRuT-}2mbc3$CAT743L1Y`$4RV^Uf8-hNBX7t&eK68Deh;wYC zb;B&s+D8Q?r>BC*2y?X`MV1qC*P>()fg86TPp!{UFt+z85Sd11?)TpXB6sb_-n#+a zd4|p2^1`zqvYo2Ph)e?!tJ-w-O9&N{JJN^JYaCOoYU`!RV6bt)jlcGUUT-0dvKenf z|LUp1=#5LE>9^=pnX_j>)6Mqr%e&y9bhd;|1vuK5iT-HoN(i||KZNUZ#Cjawn_~&^ zt{A@gITa>=0FGSMZ+w6+9ms8_dJ|DSsY* zp!P)Rmx&A>zD#f6VQ11*gu}@rJp7gYC;Z{EW!{+iSJYe{9#(rJ97eSB@Hu)54?B|l z5tfksJZ!T63}KEd!_0a;<%O`_rj`Osw-GT2-!{_g_$xTx057e(c^PC0!j^mXAM-Gq zjhXd2O2We_eu(D~h$4@}5B0%thmf>d#OT>|d=-KMUbz zB1nR87kvU@-T2yR2&a*0@akhX-phN9huLVp><_2_%v?L7cSCq7k;!Lnrk~3B7LTO(K1*iPrvT}|>>+Hp9{vZBt=nNBZYy8dr+RBZ$yt!@m zrzD$huXVaYax3g^6H;>^q4KCGSn+ds*oy{ z3(_WK`8D}9?Gfw~*NAIo6^0A{1_v^Kk@$xlKVKJbdCF~%1gd}Q+qF%r%Pemxl9>_!-c!}RTLU}FCA zHors1H*@(-D>obxp|>U1 zWz2VNDB!yC(r&e$T(2bN=IQWfg=Z|uSd_{_sayVM{q47f5z|aRCp880;YBR;rYL{b z2EPXimtCh83jHTrA7*aCmser%%Nn(0MQqbEL(1AhX>hRBm$@0E(7L-PHoG=YE;W5D z^f^H$i6Zh9P31#MBZLH6W0{-r5F$xzb8Yr6H9aEq`8C-9A&Z*A%t||icw1L7HwV%unXC^xt}hilTL z)ExOTdm?z0xzo{na4~5!b1h{lYZQ$76lE*semG-tjgdS~NZqcvBAD=L3Pv|nq9`;g zPsp{x)8xf}(2?$M5Zk@4x%^hBq7_V9tl>Np@W{$1RUEL?JhZ!rm8aCHM;!vN7gw` zk>dXE(zV^5qmg;0IH=9>;46?AJ$M1nDj_crAmeby_Q<+=?0_XpHD*D9lcs@N zD3D|5tVIDzTam+8Ajxd!1-znG#M5A?0&2|gRUGj34? zH|gvilVE+i7}UoViyH4U2b&3ub**8+oA`VsxU@WN3bzA)vuP{}(_m^`iM6dF1kQO-wN)d$Bob?YkCDLD6?rgPeg*D$L* ztnP*_M|0}1DW*q~*+H1AUoca{Gh~m$w2VZJzhUUCL4LhBQx86mGJjbsj6rDr3#wCa z{5wrs44t20K(VhZ7afjihR#oUgP-hi-mt*X`4MmUp3DrFAk>j%=-kg60%euFA>Yth ziiQkdSrKpe#ZLL7nW|G?83$pXPb#WmBiY)DnSbmOC(ew-a=z}M+Oujt3^|5^iakK~ zIJP2x6TZ0U$Lnk`@ZYqlTdp#jvFryDV;c%IE>fA{YVS0qiH%jfAys9*kA}-)<8I!t zN@ecnBP7O+ydg_v?&TvS#&k4fg9d*t;bLPnZ&;!--@%A=Vxu>2NLA(1XgDM`_VI=l zs$9N~Ua_&6H>^_S@^yqujE8x{bE;gvj&%~_cHWSs%H``gBr*P!H^@}Ed>y@Tx88sK ezx2;U$dzBN%CC)_J`4VBELfhBzNBigLi>9z;|jU} diff --git a/bin/resources/icons/flags/PAL-UK.svg b/bin/resources/icons/flags/PAL-UK.svg new file mode 100644 index 0000000000..21b97e9fcb --- /dev/null +++ b/bin/resources/icons/flags/PAL-UK.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pcsx2-qt/GameList/GameListModel.cpp b/pcsx2-qt/GameList/GameListModel.cpp index 9a705669c2..5594e73ce0 100644 --- a/pcsx2-qt/GameList/GameListModel.cpp +++ b/pcsx2-qt/GameList/GameListModel.cpp @@ -569,7 +569,7 @@ QIcon GameListModel::getIconForType(GameList::EntryType type) QIcon GameListModel::getIconForRegion(GameList::Region region) { return QIcon( - QStringLiteral("%1/icons/flags/%2.png").arg(QtHost::GetResourcesBasePath()).arg(GameList::RegionToString(region))); + QStringLiteral("%1/icons/flags/%2.svg").arg(QtHost::GetResourcesBasePath()).arg(GameList::RegionToString(region))); } void GameListModel::loadThemeSpecificImages() From d4d9b3e461fb1132076d28b66aeeaf857dec078e Mon Sep 17 00:00:00 2001 From: KamFretoZ <14798312+kamfretoz@users.noreply.github.com> Date: Mon, 14 Apr 2025 23:03:04 +0700 Subject: [PATCH 083/162] Qt: Add SVG compats star icon Qt: Center compatibility stars icon --- bin/resources/icons/star-0.png | Bin 296 -> 0 bytes bin/resources/icons/star-0.svg | 128 +++++++++++++++++++++++++++ bin/resources/icons/star-1.png | Bin 442 -> 0 bytes bin/resources/icons/star-1.svg | 128 +++++++++++++++++++++++++++ bin/resources/icons/star-2.png | Bin 534 -> 0 bytes bin/resources/icons/star-2.svg | 128 +++++++++++++++++++++++++++ bin/resources/icons/star-3.png | Bin 505 -> 0 bytes bin/resources/icons/star-3.svg | 128 +++++++++++++++++++++++++++ bin/resources/icons/star-4.png | Bin 442 -> 0 bytes bin/resources/icons/star-4.svg | 128 +++++++++++++++++++++++++++ bin/resources/icons/star-5.png | Bin 297 -> 0 bytes bin/resources/icons/star-5.svg | 128 +++++++++++++++++++++++++++ pcsx2-qt/GameList/GameListModel.cpp | 2 +- pcsx2-qt/GameList/GameListWidget.cpp | 1 + 14 files changed, 770 insertions(+), 1 deletion(-) delete mode 100644 bin/resources/icons/star-0.png create mode 100644 bin/resources/icons/star-0.svg delete mode 100644 bin/resources/icons/star-1.png create mode 100644 bin/resources/icons/star-1.svg delete mode 100644 bin/resources/icons/star-2.png create mode 100644 bin/resources/icons/star-2.svg delete mode 100644 bin/resources/icons/star-3.png create mode 100644 bin/resources/icons/star-3.svg delete mode 100644 bin/resources/icons/star-4.png create mode 100644 bin/resources/icons/star-4.svg delete mode 100644 bin/resources/icons/star-5.png create mode 100644 bin/resources/icons/star-5.svg diff --git a/bin/resources/icons/star-0.png b/bin/resources/icons/star-0.png deleted file mode 100644 index 5d82bf8f8a1376723341aed3fd3353684daddefd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 296 zcmeAS@N?(olHy`uVBq!ia0vp^2|z5t!3-pg%sG|;DYF2d5LY10!OjLGB_+jyBr>>p z^Ja2#@}*0c7B60W;>3wHYu0pkcUMPau0vu=u!j_j6OOyOR;4E`6cs+=@!;p|e= zcq7x#OV>Z@ox$vJ+x(q?S7_dpSN>AE&lHVji2s}%_DsNnZ-QaBeddBGCaE9P*Zw&d eP`GrvdM(4LC>cwhg?#Hle)M$pb6Mw<&;$TGyldY8 diff --git a/bin/resources/icons/star-0.svg b/bin/resources/icons/star-0.svg new file mode 100644 index 0000000000..65c0a785d1 --- /dev/null +++ b/bin/resources/icons/star-0.svg @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + diff --git a/bin/resources/icons/star-1.png b/bin/resources/icons/star-1.png deleted file mode 100644 index 22072f3214f399292382dd00e5ba097d675a732c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^2|z5t!3-pg%sG|;slWiA5LY10&(8}afqU?mM71-jX1{V201aP5YmD|GYHUFyo6dpT&Cy21agA7sn8f<8LpYUe}_) z(~#)p?dpEO`oVAe7lt!~RnMg<-ToQ=%scrq>#OVE4eJGxY-1yTJ2G4?Ir{sY`JvLI zRg-dhzPTCj1o&QOym#O4z}20i+Mbups^r(|t!O^DlW|v8lFIGpPx^!2=|1${o-#|f z=UoO*bn=Q?Kd*~&wSjj$4;$@Rl-T%u$&_VJx`i^ACYHGxhfJ8nd-Okl)5W>H0gmt2 z@pj%@bhe^T^6wlKZSFN4c`55IimH6ww7@=d^3JvMrkvZPa5{|7NPI`%6UpOql0VKU zEX?CdEXq5`CFElmd_AzwbHX!Ci8SG5Gv`gdl9p6@WXkP&BgVK_FIW%SsB=c{`>}_i fb;7*M>+^pxC--V}$)^Q?!i&Mv)z4*}Q$iB}4EeSl diff --git a/bin/resources/icons/star-1.svg b/bin/resources/icons/star-1.svg new file mode 100644 index 0000000000..db2d9386ba --- /dev/null +++ b/bin/resources/icons/star-1.svg @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + diff --git a/bin/resources/icons/star-2.png b/bin/resources/icons/star-2.png deleted file mode 100644 index 480b9f357691c088152bf64f0de068b1e3ec12cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 534 zcmeAS@N?(olHy`uVBq!ia0vp^2|z5t!3-pg%sG|;sgMAl5LX}#1nlf=K$3+8#Aam$ zkw6g%F=kOw5fF!gK~7EvNV+<3-Mo48&nxY7=guu(zP!1)`NMsULt7L|vW4Q~<7f6s zW@cvE+S;C6`S}@8wLnRbUob<%3$8$#u}Jc}fY4fx zC2`k(&3a;T(!43NDcQ^L-t6pEb5`3aDP2p9I4XK_<@@_}jCGvrIy4@)ACxZjTU1p0 zepSuN+DWzxrIx%rWOL-qLSvz_xWOA^r9E_i&nW9`kafLnUWe*b>{a~{fl!l zW)?VWX$$9h{59-RTeQZ<-}FY>4a1de?$WLCs!6VgN^V@6D{#A@Bsrt=1gD_%2HhLU ztHfnuUx|0REb@7oR?8u$U+!OVEH5&q?92p?t`>{m3TvkxJYV2bQo_%>Kt6AY{-cQ* zv6nsexXdssa7<$1e{1+M;{SGQyMpJ UF^<7#6DamPUHx3vIVCg!0O0)BJ^%m! diff --git a/bin/resources/icons/star-2.svg b/bin/resources/icons/star-2.svg new file mode 100644 index 0000000000..c538ca4557 --- /dev/null +++ b/bin/resources/icons/star-2.svg @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + diff --git a/bin/resources/icons/star-3.png b/bin/resources/icons/star-3.png deleted file mode 100644 index 000e3499d06206a47fc4155bb1f4a3df6191cd99..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 505 zcmeAS@N?(olHy`uVBq!ia0vp^2|z5t!3-pg%sG|;sgMAl5LX}#1Ux)E92_8qgcwKx z1TZr*xjKMM00KEVSs?l6mG;e>HxF%5=%^Jd$PhYr?%eX_%afCnzdY7#Zf<^gLw(US zX*)YxyZy2IfvWjSg8YIRe1Gg}y1Y!x^vRc_N`Df&_XagFFfh7$x;Tb-9DjS|c3!K3 z0BeB6&W;rojxK-y|JSiSoAP2~=7Hy%=a;B%p2(6ie@gr3_9a((TN1u|{G7nx)$uX) zVgHKGjg`uRk88P$?mty#djC*D}1Dt7Qd``ykd>AZNlvZ z7ds@D9=^D4l3vd4f6CX^TkQH4dW3l!Yu|PSbsqb<*4MrkynE8>a{Wg8gUzRPI#}+$ z{TVT9Tkf@FvCtGVc?S-TI^zlvA<1*TO261wnf$mFw|RwDQ^@})Yug927~Q5BIT>tW q?(>fcnHcE7b$gA2(6rj1N&59|dMrM_`y7B_&EV + + + + + + + + + + + + + diff --git a/bin/resources/icons/star-4.png b/bin/resources/icons/star-4.png deleted file mode 100644 index 2ec1145bfe048683c6253e739540ef0b22889bb9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^2|z5t!3-pg%sG|;sgMAl5LY10!44!C#6&<+5FjWh zkdq?h;sDYvBgq0J85tS>ywbjT^X8Ywn#Xo1R^$u6xS_s#o&2I{(i7Sw&Ye5AY}vBp z5f1o;Is?DaX{D6{;}zb~c_;@)x_8Rh^DWA}7%4DmSrcCzQX z!wLc}mwj@Nr+ql_;P?H{k6r8MBnq-lTgm%kWr!>H*SE{hAC0hHa%DEtx!a0I%$eU< zXSXV-R@Al^?-6x+|FM)Ix$GL-%9PxyU#utR8upzQ>sYw`+T;3{8Y1s^UrkvXdw#0K zem9{l$CTM-i#}?Ma?WczJG1)^TfB1T$=lsEhl)*RtlP(_HQ`8sE=RhZWcOOOscnQ(CNZH)YlECF6*2UngDBRvVi~q diff --git a/bin/resources/icons/star-4.svg b/bin/resources/icons/star-4.svg new file mode 100644 index 0000000000..ca0940d8f3 --- /dev/null +++ b/bin/resources/icons/star-4.svg @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + diff --git a/bin/resources/icons/star-5.png b/bin/resources/icons/star-5.png deleted file mode 100644 index 9043b2f9fd94f0d919e58bae4f4490efb2a8401d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 297 zcmeAS@N?(olHy`uVBq!ia0vp^2|z5t!3-pg%sG|;DdPa25LX}#1QKE(aZU~fR#p(# z)d8dt2>!g%{%~KtI9vGG4#nN;kPNkNiF9oQOZ*TolYv9&AmTuDp24- ez4DZO_r9=CYL@+&?5Ph53 + + + + + + + + + + + + + diff --git a/pcsx2-qt/GameList/GameListModel.cpp b/pcsx2-qt/GameList/GameListModel.cpp index 5594e73ce0..141a68e949 100644 --- a/pcsx2-qt/GameList/GameListModel.cpp +++ b/pcsx2-qt/GameList/GameListModel.cpp @@ -587,7 +587,7 @@ void GameListModel::loadCommonImages() const QString base_path(QtHost::GetResourcesBasePath()); for (u32 i = 1; i < GameList::CompatibilityRatingCount; i++) - m_compatibility_pixmaps[i].load(QStringLiteral("%1/icons/star-%2.png").arg(base_path).arg(i - 1)); + m_compatibility_pixmaps[i].load(QStringLiteral("%1/icons/star-%2.svg").arg(base_path).arg(i - 1)); m_placeholder_pixmap.load(QStringLiteral("%1/cover-placeholder.png").arg(base_path)); } diff --git a/pcsx2-qt/GameList/GameListWidget.cpp b/pcsx2-qt/GameList/GameListWidget.cpp index d909960dc6..77aafdd4d5 100644 --- a/pcsx2-qt/GameList/GameListWidget.cpp +++ b/pcsx2-qt/GameList/GameListWidget.cpp @@ -223,6 +223,7 @@ void GameListWidget::initialize() m_table_view->setVerticalScrollMode(QAbstractItemView::ScrollMode::ScrollPerPixel); m_table_view->setItemDelegateForColumn(0, new GameListIconStyleDelegate(this)); m_table_view->setItemDelegateForColumn(8, new GameListIconStyleDelegate(this)); + m_table_view->setItemDelegateForColumn(9, new GameListIconStyleDelegate(this)); loadTableViewColumnVisibilitySettings(); loadTableViewColumnSortSettings(); From 915f4cc6183627c83e3f6cc9fc341600ea7c3a16 Mon Sep 17 00:00:00 2001 From: KamFretoZ <14798312+kamfretoz@users.noreply.github.com> Date: Tue, 15 Apr 2025 16:22:03 +0700 Subject: [PATCH 084/162] Qt: Update Game type and flag icons --- bin/resources/icons/flags/Other.svg | 4 +- pcsx2-qt/Settings/BIOSSettingsWidget.cpp | 16 +- pcsx2-qt/Settings/GameSummaryWidget.cpp | 2 +- pcsx2-qt/Settings/GameSummaryWidget.ui | 367 +++++++++--------- .../icons/applications-system-24.png | Bin 859 -> 0 bytes pcsx2-qt/resources/icons/media-optical-24.png | Bin 853 -> 0 bytes .../resources/icons/media-optical-gear-24.png | Bin 963 -> 0 bytes pcsx2-qt/resources/icons/media-optical.png | Bin 2876 -> 0 bytes pcsx2-qt/resources/resources.qrc | 4 - 9 files changed, 193 insertions(+), 200 deletions(-) delete mode 100644 pcsx2-qt/resources/icons/applications-system-24.png delete mode 100644 pcsx2-qt/resources/icons/media-optical-24.png delete mode 100644 pcsx2-qt/resources/icons/media-optical-gear-24.png delete mode 100644 pcsx2-qt/resources/icons/media-optical.png diff --git a/bin/resources/icons/flags/Other.svg b/bin/resources/icons/flags/Other.svg index 7588edef9a..7d32f8d108 100644 --- a/bin/resources/icons/flags/Other.svg +++ b/bin/resources/icons/flags/Other.svg @@ -1,2 +1,2 @@ - - \ No newline at end of file + + \ No newline at end of file diff --git a/pcsx2-qt/Settings/BIOSSettingsWidget.cpp b/pcsx2-qt/Settings/BIOSSettingsWidget.cpp index 6d46c219fe..88ccc51a5f 100644 --- a/pcsx2-qt/Settings/BIOSSettingsWidget.cpp +++ b/pcsx2-qt/Settings/BIOSSettingsWidget.cpp @@ -81,31 +81,31 @@ void BIOSSettingsWidget::populateList(QTreeWidget* list, const std::string& dire switch (bios_region) { case 0: // Japan - item->setIcon(0, QIcon(QStringLiteral("%1/icons/flags/NTSC-J.png").arg(res_path))); + item->setIcon(0, QIcon(QStringLiteral("%1/icons/flags/NTSC-J.svg").arg(res_path))); break; case 1: // USA - item->setIcon(0, QIcon(QStringLiteral("%1/icons/flags/NTSC-U.png").arg(res_path))); + item->setIcon(0, QIcon(QStringLiteral("%1/icons/flags/NTSC-U.svg").arg(res_path))); break; case 2: // Europe - item->setIcon(0, QIcon(QStringLiteral("%1/icons/flags/PAL-E.png").arg(res_path))); + item->setIcon(0, QIcon(QStringLiteral("%1/icons/flags/PAL-E.svg").arg(res_path))); break; case 3: // Oceania - item->setIcon(0, QIcon(QStringLiteral("%1/icons/flags/PAL-A.png").arg(res_path))); + item->setIcon(0, QIcon(QStringLiteral("%1/icons/flags/PAL-A.svg").arg(res_path))); break; case 4: // Asia - item->setIcon(0, QIcon(QStringLiteral("%1/icons/flags/NTSC-HK.png").arg(res_path))); + item->setIcon(0, QIcon(QStringLiteral("%1/icons/flags/NTSC-HK.svg").arg(res_path))); break; case 5: // Russia - item->setIcon(0, QIcon(QStringLiteral("%1/icons/flags/PAL-R.png").arg(res_path))); + item->setIcon(0, QIcon(QStringLiteral("%1/icons/flags/PAL-R.svg").arg(res_path))); break; case 6: // China - item->setIcon(0, QIcon(QStringLiteral("%1/icons/flags/NTSC-C.png").arg(res_path))); + item->setIcon(0, QIcon(QStringLiteral("%1/icons/flags/NTSC-C.svg").arg(res_path))); break; case 7: // Mexico, flag is missing @@ -114,7 +114,7 @@ void BIOSSettingsWidget::populateList(QTreeWidget* list, const std::string& dire case 9: // Test case 10: // Free default: - item->setIcon(0, QIcon(QStringLiteral("%1/icons/flags/NTSC-J.png").arg(res_path))); + item->setIcon(0, QIcon(QStringLiteral("%1/icons/flags/NTSC-J.svg").arg(res_path))); break; } diff --git a/pcsx2-qt/Settings/GameSummaryWidget.cpp b/pcsx2-qt/Settings/GameSummaryWidget.cpp index 0f40ba1344..6f56d38669 100644 --- a/pcsx2-qt/Settings/GameSummaryWidget.cpp +++ b/pcsx2-qt/Settings/GameSummaryWidget.cpp @@ -33,7 +33,7 @@ GameSummaryWidget::GameSummaryWidget(const GameList::Entry* entry, SettingsWindo for (int i = 0; i < m_ui.region->count(); i++) { m_ui.region->setItemIcon(i, - QIcon(QStringLiteral("%1/icons/flags/%2.png").arg(base_path).arg(GameList::RegionToString(static_cast(i))))); + QIcon(QStringLiteral("%1/icons/flags/%2.svg").arg(base_path).arg(GameList::RegionToString(static_cast(i))))); } m_entry_path = entry->path; diff --git a/pcsx2-qt/Settings/GameSummaryWidget.ui b/pcsx2-qt/Settings/GameSummaryWidget.ui index f34aab2fa5..2eff05813d 100644 --- a/pcsx2-qt/Settings/GameSummaryWidget.ui +++ b/pcsx2-qt/Settings/GameSummaryWidget.ui @@ -7,12 +7,12 @@ 0 0 641 - 467 + 573 - QFormLayout::ExpandingFieldsGrow + QFormLayout::FieldGrowthPolicy::ExpandingFieldsGrow 0 @@ -27,14 +27,14 @@ 0 - + Title: - + @@ -57,7 +57,7 @@ - Sorting Title: + Sorting Title: @@ -83,7 +83,7 @@ - + Path: @@ -97,14 +97,14 @@ - + Serial: - + @@ -125,7 +125,7 @@ - + CRC: @@ -139,7 +139,7 @@ - + Type: @@ -164,8 +164,7 @@ PS2 Disc - - :/icons/media-optical-24.png:/icons/media-optical-24.png + @@ -173,8 +172,7 @@ PS1 Disc - - :/icons/media-optical-24.png:/icons/media-optical-24.png + @@ -182,182 +180,181 @@ ELF (PS2 Executable) - - :/icons/applications-system-24.png:/icons/applications-system-24.png + - + Region: - - - - - - 0 - 0 - + + + + + + 0 + 0 + + + + + NTSC-B (Brazil) - - - NTSC-B (Brazil) - - - - - NTSC-C (China) - - - - - NTSC-HK (Hong Kong) - - - - - NTSC-J (Japan) - - - - - NTSC-K (Korea) - - - - - NTSC-T (Taiwan) - - - - - NTSC-U (US) - - - - - Other - - - - - PAL-A (Australia) - - - - - PAL-AF (South Africa) - - - - - PAL-AU (Austria) - - - - - PAL-BE (Belgium) - - - - - PAL-E (Europe/Australia) - - - - - PAL-F (France) - - - - - PAL-FI (Finland) - - - - - PAL-G (Germany) - - - - - PAL-GR (Greece) - - - - - PAL-I (Italy) - - - - - PAL-IN (India) - - - - - PAL-M (Europe/Australia) - - - - - PAL-NL (Netherlands) - - - - - PAL-NO (Norway) - - - - - PAL-P (Portugal) - - - - - PAL-PL (Poland) - - - - - PAL-R (Russia) - - - - - PAL-S (Spain) - - - - - PAL-SC (Scandinavia) - - - - - PAL-SW (Sweden) - - - - - PAL-SWI (Switzerland) - - - - - PAL-UK (United Kingdom) - - - - - + + + + NTSC-C (China) + + + + + NTSC-HK (Hong Kong) + + + + + NTSC-J (Japan) + + + + + NTSC-K (Korea) + + + + + NTSC-T (Taiwan) + + + + + NTSC-U (US) + + + + + Other + + + + + PAL-A (Australia) + + + + + PAL-AF (South Africa) + + + + + PAL-AU (Austria) + + + + + PAL-BE (Belgium) + + + + + PAL-E (Europe/Australia) + + + + + PAL-F (France) + + + + + PAL-FI (Finland) + + + + + PAL-G (Germany) + + + + + PAL-GR (Greece) + + + + + PAL-I (Italy) + + + + + PAL-IN (India) + + + + + PAL-M (Europe/Australia) + + + + + PAL-NL (Netherlands) + + + + + PAL-NO (Norway) + + + + + PAL-P (Portugal) + + + + + PAL-PL (Poland) + + + + + PAL-R (Russia) + + + + + PAL-S (Spain) + + + + + PAL-SC (Scandinavia) + + + + + PAL-SW (Sweden) + + + + + PAL-SWI (Switzerland) + + + + + PAL-UK (United Kingdom) + + + + + false @@ -370,7 +367,7 @@ - + Compatibility: @@ -384,7 +381,7 @@ - + Input Profile: @@ -413,7 +410,7 @@ - + @@ -436,7 +433,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -451,7 +448,7 @@ - QAbstractItemView::NoEditTriggers + QAbstractItemView::EditTrigger::NoEditTriggers false @@ -466,7 +463,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -490,7 +487,7 @@ - Qt::Vertical + Qt::Orientation::Vertical diff --git a/pcsx2-qt/resources/icons/applications-system-24.png b/pcsx2-qt/resources/icons/applications-system-24.png deleted file mode 100644 index d1aef0da7880fe5eb313ae3c8b5a298d21df3d19..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 859 zcmV-h1EldHl(xLK)!EkQ$iCcohpo)a z;I**OUUZ*BgCryX000VfQchCf?c7J0AIAfo%b9~+1-%#fRW&F{kdG-57kuT`pyRZNUJDt zQDFdA6%e9BZBz*whX`L6=!#csT4!daRTXrkQo1aF3L;`Fdk0fuvDUnV)N_f{Ab3N~ ze!ilqh&E3*rTlBIQ_hLi6scR4@>*b(=*U!vxTHLJ^XOKpc>$5y#_yXZuO=$styk^R zVk^p$4??%(<0aAGyW95|4rwpX;NbL6XjA~5UFF3~T z-cY*>-7X%8iM4t{>1w7OBn0Hv`Ra*lFrq-+AL+Nda9JUf?zO(y9US;^^UvFjr&C8@ zUNCTEcpSft)2@qXut>Z`0D29N&FS#J*n%*--O;$N3npe^yf?+WgVu>;#Cil%ZCt3M zhzTFI?CXtlF4S<{JKk3=%aplcC$yY~q=eOJ$%UzV75~DQWx`QNoIe_zC-9!sLZIw_ zOh{tz$M_OnAtsUP8#^Lgxn`_@y#EYjDh^0FS)z3o8h=fr+Mn;?Je2 l5W@KyyzFi90e$_K`~m)LHEdXdM05ZE002ovPDHLkV1gf~q0ImQ diff --git a/pcsx2-qt/resources/icons/media-optical-24.png b/pcsx2-qt/resources/icons/media-optical-24.png deleted file mode 100644 index 6ff4f653554803f43f6873a7f9c6426881ab7c6e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 853 zcmV-b1FHOqP)Yv%FfQ)=jG<^?DDg*yS%-;y}`}CzN^K= zyr!qH_VxA{H>q6!000PdQchC<6g}|#{(rkuSXQb400O;9L_t(Y$JN#8lG88{1YmU8 zI_!}w<^7+PZXN5m$O2XTqA60fFrQoFb&?c#~hn8+7@oM&+6TcV7`ctpmyhaj=rcb69*YmJql6P}GkjQThqL(aK^ zP=NRjPKfwHXd8ma08^|6B>W<@2_Z{HN`4-R`Bf;)*P8Ri3W`GE{kXkM-XCDcz7c?_ zxx^j-%&$G1ek^bVfL_6l*gKMct2iEqGQo!_1OhM8M51R#1Jbrf0oKHYyJ!L+(o#wj zum=_Q1`z(oDa;Nu!LB#~QwUU_r z8b*bgV5{dr!3qHYvHP~0hH;T82&|qfgaHCU!ymh;e$rOfDxr%ufx&H?A`9l-zINkJ zQsSz}+XUwTJh32Ft+Tq)8UO?zl&`JL3jl&3BKH3M06?rmeF6~S4a5%cuL0s6EWsm{ zw$XR9@!NyPp*hY)JFA+88c(5r9O1re||}L9l{L zB%~Sq;%p7|a_-6{m&jY??F72LAPCsO_#%#N^osL!SM)bu zo%50@o}ZU{yekL-K!jKXU~X`~OIdM)F0M%g4S41sK-=@KNVnj)pr9G0-o}v);v6DT zM2fKpKr>olY|T^X%^srz4Xo$_;ej0H70J9lf6#g}VSsh0Q0IrCHT+Ix-Ww3(4hYWs z)d7Pik@K@*b&#+`j#)v0L|lGb*MF|fkl36kSQEq_2Wh#M00000NkvXXu0mjfx#N_% diff --git a/pcsx2-qt/resources/icons/media-optical-gear-24.png b/pcsx2-qt/resources/icons/media-optical-gear-24.png deleted file mode 100644 index b3a4b34050c9b1eb1945c0b9480626a6a985aadc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 963 zcmV;!13dhRP)TYPz`yeG^xE3z&CJ}`*VyRh z?zFSHr>CpH!PT#^&5)S4G-acusK9iDrzEJvX8-^I3v^OWQvmc8ODFuI{FzC~g~R11 z0009PNklz14#5QKxu=nNttci#U&Qw`!Jn@#dJ<(%>4q&};fc6WdLFB(SD z@Neev#TauW#y)?CM#iLAoAy;mFn_i?u^J_jqZ9 zl=6m9uYKDs8-D8-LP8e<0CVg>1c=}JZAH3T0)-e5U;WK)g?s>nP{yzG3Ii{$Dqrnl zRUiP+9P&B&%dRNY5K9RaQ6MN|+qZ3-$48;HP23~!4+20?LPgBI6cYgY5Xis>0zgDX z@a@K1z-}ras@pkyXKTIID-fERbc=Y&)ieTvKRYRNwi==cg@u?$1ls|aBf>!~!h0f= zf$DLIPjBQl07pjiTiju9A44L7osCfP0BrTo?DhIE2fMX5qMz)?&{PLt$^UB0bUx?Y z30Uj_u(bgoP~BMve0la<}0clAIC8b&^7Ssa~114XNh+~RI92-yB z;`v!~3}j1CfH6j&LS9qs1VD+}#HbVe*5w^`gq**~)b^g zpoonUR3!=z8CO6XT@MMwl`y^E%)x(j0wGg?+1D?vg4XVe06|)qRX`MEKossn;>E*{ z6NsTKCvZb%Yd};61n0|=wAXxjFMOGX1GqOk|FF-vFZ}&}Csa(hPt(wWCkAdLO~h9~ l0hpftui$ll3%zvR{}cI5THFjM#s&ZY002ovPDHLkV1grxxq|=z diff --git a/pcsx2-qt/resources/icons/media-optical.png b/pcsx2-qt/resources/icons/media-optical.png deleted file mode 100644 index e06e58f3f0f0ee5716fbeea33e2481430f71dbab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2876 zcmV-C3&Zq@P)C0000;P)t-s0002~ z{{9^vAN%|J;o;(vlbQbh{xC8%va`5|ijVX3_T%H|>+A2^+TzyM+{ww){QUjU&)C4d z$+@+>&d#{q*vHz&&gSLN=Cu+20000AbW%=J0Dv5v$(?sw(p@<%Uv>Zh3YkenK~#9! z?3jyE+b|GC8)Fea^gt%I|Nl?BPj7@o2u<3V&Tti{&{EFbyDP=XuOI#BNB^}t)vtdg zBahAv!?Gk&zYK#L$A9Dl*u#=?j)63b;LAL?ziy%Lv~&b_D1iUt3GtA)vjVSF}zbQnG3v*FPI@q_fhlcs6z$LChR5IZ40 zBfh9#BVfE)cp^T5GSP$NubZIgfYNbIlOMY0qz}DkK3@5tN5)(JD0*f-rtpMu?1qGj z5`b;LDu1n?;iR{zA0IEDh4m{QRPm4UCka3-%1DFM(n}cujL4|{8d*Ji=H#o&_ zd~#Y!oTqDTm1(-1^^FHIQYbF&%dd3fR?_q7dqgx0|+4} z5jnWge>C8cT-7g@!rcyr3k&d|oHc?-2HO1>2dxg!_o%+RkMFp8;$QNbVpX&VJI_D_g~Vt>Z<`zxw{8I>1n;d6*IH|4Jf{@-zKo+YbvaJ2GKEH zGeGSzX6b?CU-!Q_0n7Ro?_qsC2IIDUY{9;+hl6_mTL+j#P?h=eY~ah&pc^*OVF4ZF z3s{ITpn-T)U-!R004WX)!1EQph`?fluhEOFe!vV}Jpw`K!Up;_2coZX;Lx%3V%T2vVpd-9C+!B!dxaaH>U^&aEJ5)s zJLh(MY}f}d&>!U@nFU{&t78za@HT&>oMSpy1`^>>AMbwEMZgo`00~|#TsOfJUGN@% zQ;|O^jr9x~pW$l2LfC=@pm=kj&W?j0;Bz&9IU+tMSI?k*{G?0e>4^sNRPYCPKA)Xq z2e0A)X~YK^-tzy>3={7ZCCX zzUlzj_4(JnfVScQr&Q+ebh@en;6QEubEVieVeuFG?J7tlU^M$f{)zC14#fG-paQTD zlok~G0D&fX0>}Epfv^v>*Q5CludD4&*mvMJn+p6A0i3?`WBw3;zai*xsLy{S_@BJY zqTuq|#5cvWV6mAZ=%_!_g$DM+}@^+Kl{!IatKVRaJ zYzX<|ok~^kFOJTHy^L8obGjD>%LjPj=edig;-Q+qk9*|eUpBj^;$Q1{Rrw2D5+yz? z0&Zo)BY(vgBe-GEA~?=p3)j#Qr^b!=UV9U<#<~a)EzyxbtS<&&#JAn36oLF5Pd^B+ z7G$hG$v@9?3oyEH!&mFW{9XFc6(CR(%bz|{3U_~DGGewCh)ocahyPmz4Y?lq9}FWl z6o`by7a;JNTOc-p2te{-{B1?x7(Z4CNS|2un+(?p&liVzztSbN1>#l&dJ*OLbvW8NL;eza($Q&I(~J|PE_Wh6D=<8LEZqOqfcl`YyMBF!33SL`6c(oqA}7X+pfo0u za7Po&{I$|G+DDU8jI?b1#r#(Zc-L3DDoS*-BaBDPStM}qx37U%0~`Qj{;MQ)Nt$u; z814Ocp_}cXU5L2wfIc%UunNTJ8s#%6TZ=X3Uc{!sdiF1SYnVi$PC{EGrOenkFq03|&07v23~0pEKDpbK{L2VUVr<^2z* zKjvq`8e1GVGJu91{Z}o2=a>Ft0Dvt?)6a6N;w{h1GZn7<_N01p1n zSv(Nuj|+ezg#1f<7=R&v4u)I$5`fE3nJXPwkTreeFJC2nF#EyB` zLjGQVY(S2W@BcZk=YQXQ-2(9U`mhBb2GCWwFP}&A4}zltumA-(Qc&Cf!Uj;wKV@5h zhA$id|F8&s-2aAZ{$qXqK{x!DzzTd$L1Wp+`kMju`~_b$AVh%wl!CEZfAs%;{k;JV z?mNH<0jl5&^?$1W&45q<@J}i@Uw_z-Bj`y!s&M;4{g3+}k08}>-v8)9b9Vh>{-EuL z{yWFVYD57J`XA-@{s$iX^B%OskNsb-|59#%z|ZY@KK8`v^U3~K{KkM+_z*y4Aoy|p z(SI-?zhHk+hUuC9NBUa+nEop!I3E1C{!#z3`@w(3gpBj&bM?=HNBVD&p!n-S{gn>) z!GF09?JO%`+O0nxfj#i=lhEJXfmE-*q|V>KzgGfln8*diBY!hs776um}E(J}nn=@6khJpTEAy@U#&f8r

icons/AppIcon64.png - icons/applications-system-24.png icons/black/index.theme icons/black/svg/arrow-left-right-line.svg icons/black/svg/artboard-2-line.svg @@ -105,9 +104,6 @@ icons/black/svg/zoom-in-line.svg icons/black/svg/zoom-out-line.svg icons/logo.png - icons/media-optical-24.png - icons/media-optical-gear-24.png - icons/media-optical.png icons/QT.png icons/update.png icons/white/index.theme From ddd17b18d7744b233d1d9d33e4e5b9320f5ebe5d Mon Sep 17 00:00:00 2001 From: PCSX2 Bot Date: Wed, 16 Apr 2025 00:02:09 +0000 Subject: [PATCH 085/162] [ci skip] Qt: Update Base Translation. --- pcsx2-qt/Translations/pcsx2-qt_en.ts | 84 ++++++++++++++-------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/pcsx2-qt/Translations/pcsx2-qt_en.ts b/pcsx2-qt/Translations/pcsx2-qt_en.ts index d1461986fb..8fcf2ef0a3 100644 --- a/pcsx2-qt/Translations/pcsx2-qt_en.ts +++ b/pcsx2-qt/Translations/pcsx2-qt_en.ts @@ -11787,7 +11787,7 @@ Scanning recursively takes more time, but will identify files in subdirectories. - + Restore @@ -11833,237 +11833,237 @@ Scanning recursively takes more time, but will identify files in subdirectories. - + PS1 Disc - + ELF (PS2 Executable) - + Region: - + NTSC-B (Brazil) Leave the code as-is, translate the country's name. - + NTSC-C (China) Leave the code as-is, translate the country's name. - + NTSC-HK (Hong Kong) Leave the code as-is, translate the country's name. - + NTSC-J (Japan) Leave the code as-is, translate the country's name. - + NTSC-K (Korea) Leave the code as-is, translate the country's name. - + NTSC-T (Taiwan) Leave the code as-is, translate the country's name. - + NTSC-U (US) Leave the code as-is, translate the country's name. - + Other - + PAL-A (Australia) Leave the code as-is, translate the country's name. - + PAL-AF (South Africa) Leave the code as-is, translate the country's name. - + PAL-AU (Austria) Leave the code as-is, translate the country's name. - + PAL-BE (Belgium) Leave the code as-is, translate the country's name. - + PAL-E (Europe/Australia) Leave the code as-is, translate the country's name. - + PAL-F (France) Leave the code as-is, translate the country's name. - + PAL-FI (Finland) Leave the code as-is, translate the country's name. - + PAL-G (Germany) Leave the code as-is, translate the country's name. - + PAL-GR (Greece) Leave the code as-is, translate the country's name. - + PAL-I (Italy) Leave the code as-is, translate the country's name. - + PAL-IN (India) Leave the code as-is, translate the country's name. - + PAL-M (Europe/Australia) Leave the code as-is, translate the country's name. - + PAL-NL (Netherlands) Leave the code as-is, translate the country's name. - + PAL-NO (Norway) Leave the code as-is, translate the country's name. - + PAL-P (Portugal) Leave the code as-is, translate the country's name. - + PAL-PL (Poland) Leave the code as-is, translate the country's name. - + PAL-R (Russia) Leave the code as-is, translate the country's name. - + PAL-S (Spain) Leave the code as-is, translate the country's name. - + PAL-SC (Scandinavia) Leave the code as-is, translate the country's name. - + PAL-SW (Sweden) Leave the code as-is, translate the country's name. - + PAL-SWI (Switzerland) Leave the code as-is, translate the country's name. - + PAL-UK (United Kingdom) Leave the code as-is, translate the country's name. - + Compatibility: - + Input Profile: - + Shared Refers to the shared settings profile. - + Disc Path: - + Browse... - + Clear - + Verify - + Search on Redump.org... From 0b53f541d186fed12eac9d40e7c2d35e5686a1b9 Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Sat, 30 Mar 2024 11:44:10 +0000 Subject: [PATCH 086/162] GS/HW: Initial work implementing RT in RT support --- bin/resources/shaders/dx11/tfx.fx | 4 +- bin/resources/shaders/opengl/tfx_fs.glsl | 4 +- bin/resources/shaders/vulkan/tfx.glsl | 4 +- pcsx2/GS/GSState.cpp | 3 +- pcsx2/GS/GSState.h | 1 + pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 215 +++++++++++++++++++---- pcsx2/GS/Renderers/HW/GSRendererHW.h | 2 + pcsx2/GS/Renderers/HW/GSTextureCache.cpp | 127 ++++++++++--- pcsx2/GS/Renderers/HW/GSTextureCache.h | 2 +- pcsx2/GS/Renderers/Metal/tfx.metal | 4 +- 10 files changed, 295 insertions(+), 71 deletions(-) diff --git a/bin/resources/shaders/dx11/tfx.fx b/bin/resources/shaders/dx11/tfx.fx index 7ed81c3d6a..717ae2549e 100644 --- a/bin/resources/shaders/dx11/tfx.fx +++ b/bin/resources/shaders/dx11/tfx.fx @@ -767,7 +767,7 @@ float4 ps_color(PS_INPUT input) float4 T = sample_color(st, input.t.w); #endif - if (PS_SHUFFLE && !PS_SHUFFLE_SAME && !PS_READ16_SRC) + if (PS_SHUFFLE && !PS_SHUFFLE_SAME && !PS_READ16_SRC && !(PS_PROCESS_BA == SHUFFLE_READWRITE && PS_PROCESS_RG == SHUFFLE_READWRITE)) { uint4 denorm_c_before = uint4(T); if (PS_PROCESS_BA & SHUFFLE_READ) @@ -1086,7 +1086,7 @@ PS_OUTPUT ps_main(PS_INPUT input) if (PS_SHUFFLE) { - if (!PS_SHUFFLE_SAME && !PS_READ16_SRC) + if (!PS_SHUFFLE_SAME && !PS_READ16_SRC && !(PS_PROCESS_BA == SHUFFLE_READWRITE && PS_PROCESS_RG == SHUFFLE_READWRITE)) { uint4 denorm_c_after = uint4(C); if (PS_PROCESS_BA & SHUFFLE_READ) diff --git a/bin/resources/shaders/opengl/tfx_fs.glsl b/bin/resources/shaders/opengl/tfx_fs.glsl index cf10c58f25..b4857cd152 100644 --- a/bin/resources/shaders/opengl/tfx_fs.glsl +++ b/bin/resources/shaders/opengl/tfx_fs.glsl @@ -679,7 +679,7 @@ vec4 ps_color() vec4 T = sample_color(st); #endif - #if PS_SHUFFLE && !PS_READ16_SRC && !PS_SHUFFLE_SAME + #if PS_SHUFFLE && !PS_READ16_SRC && !PS_SHUFFLE_SAME && !(PS_PROCESS_BA == SHUFFLE_READWRITE && PS_PROCESS_RG == SHUFFLE_READWRITE) uvec4 denorm_c_before = uvec4(T); #if (PS_PROCESS_BA & SHUFFLE_READ) T.r = float((denorm_c_before.b << 3) & 0xF8u); @@ -1064,7 +1064,7 @@ void ps_main() #if PS_SHUFFLE - #if !PS_READ16_SRC && !PS_SHUFFLE_SAME + #if !PS_READ16_SRC && !PS_SHUFFLE_SAME && !(PS_PROCESS_BA == SHUFFLE_READWRITE && PS_PROCESS_RG == SHUFFLE_READWRITE) uvec4 denorm_c_after = uvec4(C); #if (PS_PROCESS_BA & SHUFFLE_READ) C.b = float(((denorm_c_after.r >> 3) & 0x1Fu) | ((denorm_c_after.g << 2) & 0xE0u)); diff --git a/bin/resources/shaders/vulkan/tfx.glsl b/bin/resources/shaders/vulkan/tfx.glsl index 519af4681b..afbf95386e 100644 --- a/bin/resources/shaders/vulkan/tfx.glsl +++ b/bin/resources/shaders/vulkan/tfx.glsl @@ -946,7 +946,7 @@ vec4 ps_color() vec4 T = sample_color(st); #endif - #if PS_SHUFFLE && !PS_READ16_SRC && !PS_SHUFFLE_SAME + #if PS_SHUFFLE && !PS_READ16_SRC && !PS_SHUFFLE_SAME && !(PS_PROCESS_BA == SHUFFLE_READWRITE && PS_PROCESS_RG == SHUFFLE_READWRITE) uvec4 denorm_c_before = uvec4(T); #if (PS_PROCESS_BA & SHUFFLE_READ) T.r = float((denorm_c_before.b << 3) & 0xF8u); @@ -1332,7 +1332,7 @@ void main() ps_blend(C, alpha_blend); #if PS_SHUFFLE - #if !PS_READ16_SRC && !PS_SHUFFLE_SAME + #if !PS_READ16_SRC && !PS_SHUFFLE_SAME && !(PS_PROCESS_BA == SHUFFLE_READWRITE && PS_PROCESS_RG == SHUFFLE_READWRITE) uvec4 denorm_c_after = uvec4(C); #if (PS_PROCESS_BA & SHUFFLE_READ) C.b = float(((denorm_c_after.r >> 3) & 0x1Fu) | ((denorm_c_after.g << 2) & 0xE0u)); diff --git a/pcsx2/GS/GSState.cpp b/pcsx2/GS/GSState.cpp index 92ab506dc2..4fa00fce05 100644 --- a/pcsx2/GS/GSState.cpp +++ b/pcsx2/GS/GSState.cpp @@ -1674,7 +1674,8 @@ void GSState::FlushPrim() Console.Warning("GS: Possible invalid draw, Frame PSM %x ZPSM %x", m_context->FRAME.PSM, m_context->ZBUF.PSM); } #endif - + // Update scissor, it may have been modified by a previous draw + m_env.CTXT[PRIM->CTXT].UpdateScissor(); m_vt.Update(m_vertex.buff, m_index.buff, m_vertex.tail, m_index.tail, GSUtil::GetPrimClass(PRIM->PRIM)); // Texel coordinate rounding diff --git a/pcsx2/GS/GSState.h b/pcsx2/GS/GSState.h index 75416d86d5..a9d5e6c992 100644 --- a/pcsx2/GS/GSState.h +++ b/pcsx2/GS/GSState.h @@ -224,6 +224,7 @@ public: bool m_texflush_flag = false; bool m_isPackedUV_HackFlag = false; bool m_channel_shuffle = false; + bool m_in_target_draw = false; u8 m_scanmask_used = 0; u32 m_dirty_gs_regs = 0; int m_backed_up_ctx = 0; diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index 1e832f6fc4..66cdd29106 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -339,7 +339,7 @@ void GSRendererHW::ConvertSpriteTextureShuffle(u32& process_rg, u32& process_ba, tex_pos &= 0xFF; shuffle_across = (((tex_pos + 8) >> 4) ^ ((pos + 8) >> 4)) & 0x8; - const bool full_width = !shuffle_across && ((second_vert.XYZ.X - first_vert.XYZ.X) >> 4) >= 16 && m_r.width() > 8; + const bool full_width = ((second_vert.XYZ.X - first_vert.XYZ.X) >> 4) >= 16 && m_r.width() > 8; process_ba = ((pos > 112 && pos < 136) || full_width) ? SHUFFLE_WRITE : 0; process_rg = (!process_ba || full_width) ? SHUFFLE_WRITE : 0; // "same group" means it can read blue and write alpha using C32 tricks @@ -482,7 +482,7 @@ void GSRendererHW::ConvertSpriteTextureShuffle(u32& process_rg, u32& process_ba, // Dogs will reuse the Z in a different size format for a completely unrelated draw with an FBW of 2, then go back to using it in full width const bool size_is_wrong = tex->m_target ? (static_cast(tex->m_from_target_TEX0.TBW * 64) < tex->m_from_target->m_valid.z / 2) : false; const u32 draw_page_width = std::max(static_cast(m_vt.m_max.p.x + (!(process_ba & SHUFFLE_WRITE) ? 8.9f : 0.9f)) / 64, 1); - const bool single_direction_doubled = (m_vt.m_max.p.y > rt->m_valid.w) != (m_vt.m_max.p.x > rt->m_valid.z); + const bool single_direction_doubled = (m_vt.m_max.p.y > rt->m_valid.w) != (m_vt.m_max.p.x > rt->m_valid.z) || (IsSinglePageDraw() && m_r.height() > 32); if (size_is_wrong || (rt && ((rt->m_TEX0.TBW % draw_page_width) == 0 || single_direction_doubled))) { @@ -580,6 +580,14 @@ void GSRendererHW::ConvertSpriteTextureShuffle(u32& process_rg, u32& process_ba, else v[i + 1 - reversed_U].U += 128u; } + else + { + if (((pos + 8) >> 4) & 0x8) + { + v[i + reversed_pos].XYZ.X -= 128u; + v[i + 1 - reversed_pos].XYZ.X -= 128u; + } + } if (half_bottom_vert) { @@ -697,6 +705,14 @@ void GSRendererHW::ConvertSpriteTextureShuffle(u32& process_rg, u32& process_ba, m_vt.m_max.t.x += 8.0f; } } + else + { + if (fmod(std::floor(m_vt.m_min.p.x), 64.0f) == 8.0f) + { + m_vt.m_min.p.x -= 8.0f; + m_vt.m_max.p.x -= 8.0f; + } + } if (half_right_vert) { @@ -976,7 +992,7 @@ GSVector2i GSRendererHW::GetValidSize(const GSTextureCache::Source* tex) } // If it's a channel shuffle, it'll likely be just a single page, so assume full screen. - if (m_channel_shuffle) + if (m_channel_shuffle || (tex && IsPageCopy())) { const int page_x = frame_psm.pgs.x - 1; const int page_y = frame_psm.pgs.y - 1; @@ -1113,6 +1129,25 @@ bool GSRendererHW::IsPossibleChannelShuffle() const return false; } +bool GSRendererHW::IsPageCopy() const +{ + if (!PRIM->TME) + return false; + + const GSDrawingContext& next_ctx = m_env.CTXT[m_backed_up_ctx]; + + if (next_ctx.TEX0.TBP0 != (m_cached_ctx.TEX0.TBP0 + 0x20)) + return false; + + if (next_ctx.FRAME.FBP != (m_cached_ctx.FRAME.FBP + 0x1)) + return false; + + if (!NextDrawMatchesShuffle()) + return false; + + return true; +} + bool GSRendererHW::NextDrawMatchesShuffle() const { // Make sure nothing unexpected has changed. @@ -1270,6 +1305,16 @@ GSVector4i GSRendererHW::GetDrawRectForPages(u32 bw, u32 psm, u32 num_pages) return GSVector4i::loadh(size); } +bool GSRendererHW::IsSinglePageDraw() const +{ + const GSVector2i& frame_pgs = GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].pgs; + + if (m_r.width() <= frame_pgs.x && m_r.height() <= frame_pgs.y) + return true; + + return false; +} + bool GSRendererHW::TryToResolveSinglePageFramebuffer(GIFRegFRAME& FRAME, bool only_next_draw) { const u32 start_bp = FRAME.Block(); @@ -1680,7 +1725,11 @@ void GSRendererHW::Move() const int w = m_env.TRXREG.RRW; const int h = m_env.TRXREG.RRH; - + GL_CACHE("Starting Move! 0x%x W:%d F:%s => 0x%x W:%d F:%s (DIR %d%d), sPos(%d %d) dPos(%d %d) size(%d %d) draw %d", + m_env.BITBLTBUF.SBP, m_env.BITBLTBUF.SBW, psm_str(m_env.BITBLTBUF.SPSM), + m_env.BITBLTBUF.DBP, m_env.BITBLTBUF.DBW, psm_str(m_env.BITBLTBUF.DPSM), + m_env.TRXPOS.DIRX, m_env.TRXPOS.DIRY, + sx, sy, dx, dy, w, h, s_n); if (g_texture_cache->Move(m_env.BITBLTBUF.SBP, m_env.BITBLTBUF.SBW, m_env.BITBLTBUF.SPSM, sx, sy, m_env.BITBLTBUF.DBP, m_env.BITBLTBUF.DBW, m_env.BITBLTBUF.DPSM, dx, dy, w, h)) { @@ -2661,7 +2710,7 @@ void GSRendererHW::Draw() FRAME_TEX0.PSM = m_cached_ctx.FRAME.PSM; GSTextureCache::Target* tgt = g_texture_cache->LookupTarget(FRAME_TEX0, GSVector2i(m_vt.m_max.p.x, m_vt.m_max.p.y), GetTextureScaleFactor(), GSTextureCache::RenderTarget, false, - fm); + fm, false, false, false, false, GSVector4i::zero(), true); if (tgt) shuffle_target = tgt->m_32_bits_fmt; @@ -2753,14 +2802,11 @@ void GSRendererHW::Draw() const bool can_expand = !(m_cached_ctx.ZBUF.ZMSK && output_black); // Estimate size based on the scissor rectangle and height cache. - const GSVector2i t_size = GetTargetSize(src, can_expand); + GSVector2i t_size = GetTargetSize(src, can_expand); const GSVector4i t_size_rect = GSVector4i::loadh(t_size); // Ensure draw rect is clamped to framebuffer size. Necessary for updating valid area. const GSVector4i unclamped_draw_rect = m_r; - // Don't clamp on shuffle, the height cache may troll us with the REAL height. - if (!m_texture_shuffle && m_split_texture_shuffle_pages == 0) - m_r = m_r.rintersect(t_size_rect); float target_scale = GetTextureScaleFactor(); int scale_draw = IsScalingDraw(src, m_primitive_covers_without_gaps != NoGapsType::GapsFound); @@ -2817,6 +2863,10 @@ void GSRendererHW::Draw() GSTextureCache::Target* rt = nullptr; GIFRegTEX0 FRAME_TEX0; + const GSLocalMemory::psm_t& frame_psm = GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM]; + + m_in_target_draw = false; + if (!no_rt) { // FBW is going to be wrong for channel shuffling into a new target, so take it from the source. @@ -2825,21 +2875,28 @@ void GSRendererHW::Draw() FRAME_TEX0.TBW = (m_channel_shuffle && src->m_target) ? src->m_from_target_TEX0.TBW : m_cached_ctx.FRAME.FBW; FRAME_TEX0.PSM = m_cached_ctx.FRAME.PSM; + const bool possible_shuffle = draw_sprite_tex && (((src && src->m_target && src->m_from_target && src->m_from_target->m_32_bits_fmt) && + GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].bpp == 16 && GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].bpp == 16) || + IsPossibleChannelShuffle()); + // Don't clamp on shuffle, the height cache may troll us with the REAL height. + if (!possible_shuffle && m_split_texture_shuffle_pages == 0) + m_r = m_r.rintersect(t_size_rect); + // Normally we would use 1024 here to match the clear above, but The Godfather does a 1023x1023 draw instead // (very close to 1024x1024, but apparently the GS rounds down..). So, catch that here, we don't want to // create that target, because the clear isn't black, it'll hang around and never get invalidated. const bool is_square = (t_size.y == t_size.x) && m_r.w >= 1023 && m_primitive_covers_without_gaps == NoGapsType::FullCover; const bool is_clear = is_possible_mem_clear && is_square; - const bool possible_shuffle = draw_sprite_tex && (((src && src->m_target && src->m_from_target && src->m_from_target->m_32_bits_fmt) && - GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].bpp == 16 && GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].bpp == 16) || - IsPossibleChannelShuffle()); // Preserve downscaled target when copying directly from a downscaled target, or it's a normal draw using a downscaled target. Clears that are drawing to the target can also preserve size. // Of course if this size is different (in width) or this is a shuffle happening, this will be bypassed. const bool preserve_downscale_draw = std::abs(scale_draw) == 1 || (scale_draw == 0 && ((src && src->m_from_target && src->m_from_target->m_downscaled) || is_possible_mem_clear == ClearType::ClearWithDraw)); + m_in_target_draw = false; + rt = g_texture_cache->LookupTarget(FRAME_TEX0, t_size, ((src && src->m_scale != 1) && GSConfig.UserHacks_NativeScaling == GSNativeScaling::Normal && !possible_shuffle) ? GetTextureScaleFactor() : target_scale, GSTextureCache::RenderTarget, true, - fm, false, force_preload, preserve_rt_rgb, preserve_rt_alpha, unclamped_draw_rect, possible_shuffle, is_possible_mem_clear && FRAME_TEX0.TBP0 != m_cached_ctx.ZBUF.Block(), GSConfig.UserHacks_NativeScaling != GSNativeScaling::Off && preserve_downscale_draw && is_possible_mem_clear != ClearType::NormalClear); + fm, false, force_preload, preserve_rt_rgb, preserve_rt_alpha, unclamped_draw_rect, possible_shuffle, is_possible_mem_clear && FRAME_TEX0.TBP0 != m_cached_ctx.ZBUF.Block(), + GSConfig.UserHacks_NativeScaling != GSNativeScaling::Off && preserve_downscale_draw && is_possible_mem_clear != ClearType::NormalClear, src); // Draw skipped because it was a clear and there was no target. if (!rt) @@ -2860,6 +2917,10 @@ void GSRendererHW::Draw() CleanupDraw(true); return; } + else if (IsPageCopy() && src->m_from_target && m_cached_ctx.TEX0.TBP0 >= src->m_from_target->m_TEX0.TBP0) + { + FRAME_TEX0.TBW = src->m_from_target->m_TEX0.TBW; + } rt = g_texture_cache->CreateTarget(FRAME_TEX0, t_size, GetValidSize(src), (scale_draw < 0 && is_possible_mem_clear != ClearType::NormalClear) ? src->m_from_target->GetScale() : target_scale, GSTextureCache::RenderTarget, true, fm, false, force_preload, preserve_rt_color || possible_shuffle, m_r, src); @@ -2870,7 +2931,36 @@ void GSRendererHW::Draw() return; } } + else if (rt->m_TEX0.TBP0 != FRAME_TEX0.TBP0) // Must have done rt in rt + { + GSVertex* v = &m_vertex.buff[0]; + u32 vertical_offset = (((FRAME_TEX0.TBP0 - rt->m_TEX0.TBP0) >> 5) / std::max(rt->m_TEX0.TBW, 1U)) * frame_psm.pgs.y; // I know I could just not shift it.. + const u32 horizontal_offset = (((FRAME_TEX0.TBP0 - rt->m_TEX0.TBP0) >> 5) % std::max(rt->m_TEX0.TBW, 1U)) * frame_psm.pgs.x; + + for (u32 i = 0; i < m_vertex.tail; i++) + { + v[i].XYZ.Y += vertical_offset << 4; + v[i].XYZ.X += horizontal_offset << 4; + } + + m_context->scissor.in.x += horizontal_offset; + m_context->scissor.in.z += horizontal_offset; + m_context->scissor.in.y += vertical_offset; + m_context->scissor.in.w += vertical_offset; + m_r.y += vertical_offset; + m_r.w += vertical_offset; + m_r.x += horizontal_offset; + m_r.z += horizontal_offset; + m_in_target_draw = true; + m_vt.m_min.p.x += horizontal_offset; + m_vt.m_max.p.x += horizontal_offset; + m_vt.m_min.p.y += vertical_offset; + m_vt.m_max.p.y += vertical_offset; + t_size.x = rt->m_unscaled_size.x - horizontal_offset; + t_size.y = rt->m_unscaled_size.y - vertical_offset; + } + if (src && src->m_from_target && src->m_target_direct && src->m_from_target == rt) { src->m_texture = rt->m_texture; @@ -2907,7 +2997,6 @@ void GSRendererHW::Draw() if (!ds) { - ds = g_texture_cache->CreateTarget(ZBUF_TEX0, t_size, GetValidSize(src), target_scale, GSTextureCache::DepthStencil, true, 0, false, force_preload, preserve_depth, m_r, src); if (!ds) [[unlikely]] @@ -3184,7 +3273,7 @@ void GSRendererHW::Draw() } } const bool blending_cd = PRIM->ABE && !m_context->ALPHA.IsOpaque(); - if (rt && ((!is_possible_mem_clear || blending_cd) || rt->m_TEX0.PSM != FRAME_TEX0.PSM)) + if (rt && ((!is_possible_mem_clear || blending_cd) || rt->m_TEX0.PSM != FRAME_TEX0.PSM) && !m_in_target_draw) { if (rt->m_TEX0.TBW != FRAME_TEX0.TBW && !m_cached_ctx.ZBUF.ZMSK && (m_cached_ctx.FRAME.FBMSK & 0xFF000000)) { @@ -3195,11 +3284,15 @@ void GSRendererHW::Draw() if (m_cached_ctx.FRAME.FBMSK & 0xF0000000) rt->m_valid_alpha_high = false; } - rt->m_TEX0 = FRAME_TEX0; + if (FRAME_TEX0.TBW != 1 || (m_r.width() > frame_psm.pgs.x || m_r.height() > frame_psm.pgs.y)) + rt->m_TEX0 = FRAME_TEX0; } - if (ds && (!is_possible_mem_clear || ds->m_TEX0.PSM != ZBUF_TEX0.PSM || (rt && ds->m_TEX0.TBW != rt->m_TEX0.TBW))) - ds->m_TEX0 = ZBUF_TEX0; + if (ds && (!is_possible_mem_clear || ds->m_TEX0.PSM != ZBUF_TEX0.PSM || (rt && ds->m_TEX0.TBW != rt->m_TEX0.TBW)) && !m_in_target_draw) + { + if (ZBUF_TEX0.TBW != 1 || (m_r.width() > frame_psm.pgs.x || m_r.height() > frame_psm.pgs.y)) + ds->m_TEX0 = ZBUF_TEX0; + } } else if (!m_texture_shuffle) { @@ -3207,7 +3300,7 @@ void GSRendererHW::Draw() // The FBW should also be okay, since it's coming from the source. if (rt) { - const bool update_fbw = (m_channel_shuffle && src->m_target) && (!PRIM->ABE || IsOpaque() || m_context->ALPHA.IsBlack()); + const bool update_fbw = rt->m_last_draw == s_n && (m_channel_shuffle && src->m_target) && (!PRIM->ABE || IsOpaque() || m_context->ALPHA.IsBlack()); rt->m_TEX0.TBW = update_fbw ? FRAME_TEX0.TBW : std::max(rt->m_TEX0.TBW, FRAME_TEX0.TBW); rt->m_TEX0.PSM = FRAME_TEX0.PSM; } @@ -3229,7 +3322,7 @@ void GSRendererHW::Draw() GSTextureCache::Target* old_ds = nullptr; // If the draw is dated, we're going to expand in to black, so it's just a pointless rescale which will mess up our valid rects and end blocks. - if(!(m_cached_ctx.TEST.DATE && m_cached_ctx.TEST.DATM)) + if (!(m_cached_ctx.TEST.DATE && m_cached_ctx.TEST.DATM)) { GSVector2i new_size = t_size; @@ -3277,7 +3370,7 @@ void GSRendererHW::Draw() rt->ResizeDrawn(rt->GetUnscaledRect()); } - const GSVector4i update_rect = m_r.rintersect(GSVector4i::loadh(new_size)); + const GSVector4i update_rect = m_r.rintersect(GSVector4i::loadh(GSVector2i(new_w, new_h))); // Limit to 2x the vertical height of the resolution (for double buffering) rt->UpdateValidity(update_rect, can_update_size || (m_r.w <= (resolution.y * 2) && !m_texture_shuffle)); rt->UpdateDrawn(update_rect, can_update_size || (m_r.w <= (resolution.y * 2) && !m_texture_shuffle)); @@ -3346,7 +3439,7 @@ void GSRendererHW::Draw() } } } - else + else if (!m_in_target_draw) { // RT and DS sizes need to match, even if we're not doing any resizing. const int new_w = std::max(rt ? rt->m_unscaled_size.x : 0, ds ? ds->m_unscaled_size.x : 0); @@ -4132,8 +4225,8 @@ __ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool min_uv.x -= block_offset.x * t_psm.bs.x; min_uv.y -= block_offset.y * t_psm.bs.y; - if (GSLocalMemory::IsPageAligned(src->m_TEX0.PSM, m_r) && - block_offset.eq(m_r_block_offset)) + //if (/*GSLocalMemory::IsPageAligned(src->m_TEX0.PSM, m_r) &&*/ + // block_offset.eq(m_r_block_offset)) { if (min_uv.eq(GSVector4i::cxpr(0, 0, 0, 0))) channel = ChannelFetch_RED; @@ -4181,13 +4274,36 @@ __ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool // Performance GPU note: it could be wise to reduce the size to // the rendered size of the framebuffer - GSVertex* s = &m_vertex.buff[0]; - s[0].XYZ.X = static_cast(m_context->XYOFFSET.OFX + 0); - s[1].XYZ.X = static_cast(m_context->XYOFFSET.OFX + 16384); - s[0].XYZ.Y = static_cast(m_context->XYOFFSET.OFY + 0); - s[1].XYZ.Y = static_cast(m_context->XYOFFSET.OFY + 16384); + if (!m_in_target_draw && (GSConfig.UserHacks_TextureInsideRt == GSTextureInRtMode::Disabled || NextDrawMatchesShuffle())) + { + GSVertex* s = &m_vertex.buff[0]; + s[0].XYZ.X = static_cast(m_context->XYOFFSET.OFX + 0); + s[1].XYZ.X = static_cast(m_context->XYOFFSET.OFX + 16384); + s[0].XYZ.Y = static_cast(m_context->XYOFFSET.OFY + 0); + s[1].XYZ.Y = static_cast(m_context->XYOFFSET.OFY + 16384); - m_r = GSVector4i(0, 0, 1024, 1024); + s[0].U = 0; + s[1].U = 16384; + s[0].V = 0; + s[1].V = 16384; + + m_r = GSVector4i(0, 0, 1024, 1024); + } + else + { + GSVertex* s = &m_vertex.buff[0]; + s[0].XYZ.X = static_cast(m_context->XYOFFSET.OFX + (m_r.x << 4)); + s[1].XYZ.X = static_cast(m_context->XYOFFSET.OFX + (m_r.z << 4)); + s[0].XYZ.Y = static_cast(m_context->XYOFFSET.OFY + (m_r.y << 4)); + s[1].XYZ.Y = static_cast(m_context->XYOFFSET.OFY + (m_r.w << 4)); + + s[0].U = (m_r.x << 4); + s[1].U = (m_r.z << 4); + s[0].V = (m_r.y << 4); + s[1].V = (m_r.w << 4); + m_last_channel_shuffle_fbmsk = 0xFFFFFFFF; + } + m_vertex.head = m_vertex.tail = m_vertex.next = 2; m_index.tail = 2; @@ -5415,9 +5531,13 @@ __ri void GSRendererHW::HandleTextureHazards(const GSTextureCache::Target* rt, c const GSTextureCache::Source* tex, const TextureMinMaxResult& tmm, GSTextureCache::SourceRegion& source_region, bool& target_region, GSVector2i& unscaled_size, float& scale, GSDevice::RecycledTexture& src_copy) { + + const int tex_diff = tex->m_from_target ? static_cast(m_cached_ctx.TEX0.TBP0 - tex->m_from_target->m_TEX0.TBP0) : 0; + const int frame_diff = rt ? static_cast(m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0) : 0; // Detect framebuffer read that will need special handling const GSTextureCache::Target* src_target = nullptr; - if (rt && m_conf.tex == m_conf.rt) + + if (rt && m_conf.tex == m_conf.rt && !(m_channel_shuffle && tex && tex_diff != frame_diff)) { // Can we read the framebuffer directly? (i.e. sample location matches up). if (CanUseTexIsFB(rt, tex, tmm)) @@ -5457,6 +5577,10 @@ __ri void GSRendererHW::HandleTextureHazards(const GSTextureCache::Target* rt, c GL_CACHE("Source is depth buffer, unsafe to read, taking copy."); src_target = ds; } + else if (m_channel_shuffle && tex->m_from_target && tex_diff != frame_diff) + { + src_target = tex->m_from_target; + } else if (!m_downscale_source) { // No match. @@ -5479,7 +5603,34 @@ __ri void GSRendererHW::HandleTextureHazards(const GSTextureCache::Target* rt, c { copy_range = src_bounds; copy_size = src_unscaled_size; + GSVector4i::storel(©_dst_offset, copy_range); + if (m_channel_shuffle && (tex_diff || frame_diff)) + { + + u32 page_offset = (m_cached_ctx.TEX0.TBP0 - src_target->m_TEX0.TBP0) >> 5; + u32 vertical_offset = (page_offset / src_target->m_TEX0.TBW) * GSLocalMemory::m_psm[src_target->m_TEX0.PSM].pgs.y; + u32 horizontal_offset = (page_offset % src_target->m_TEX0.TBW) * GSLocalMemory::m_psm[src_target->m_TEX0.PSM].pgs.x; + copy_range.y += vertical_offset; + copy_range.x += horizontal_offset; + copy_size.y -= vertical_offset; + copy_size.x -= horizontal_offset; + + if (m_in_target_draw) + { + copy_size.x = m_r.width(); + copy_size.y = m_r.height(); + copy_range.w = copy_range.y + copy_size.y; + copy_range.z = copy_range.x + copy_size.x; + + if (tex_diff != frame_diff) + { + GSVector4i::storel(©_dst_offset, m_r); + copy_size.x += copy_dst_offset.x; + copy_size.y += copy_dst_offset.y; + } + } + } } else { @@ -5489,7 +5640,7 @@ __ri void GSRendererHW::HandleTextureHazards(const GSTextureCache::Target* rt, c copy_size.y = std::min(tex_size.y, src_unscaled_size.y); // Use the texture min/max to get the copy range if not reinterpreted. - if (m_texture_shuffle) + if (m_texture_shuffle || m_channel_shuffle) copy_range = GSVector4i::loadh(copy_size); else copy_range = tmm.coverage; diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.h b/pcsx2/GS/Renderers/HW/GSRendererHW.h index b777f7b978..dddf4f2c2a 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.h +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.h @@ -113,12 +113,14 @@ private: void SetTCOffset(); bool NextDrawHDR() const; bool IsPossibleChannelShuffle() const; + bool IsPageCopy() const; bool NextDrawMatchesShuffle() const; bool IsSplitTextureShuffle(GSTextureCache::Target* rt); GSVector4i GetSplitTextureShuffleDrawRect() const; u32 GetEffectiveTextureShuffleFbmsk() const; static GSVector4i GetDrawRectForPages(u32 bw, u32 psm, u32 num_pages); + bool IsSinglePageDraw() const; bool TryToResolveSinglePageFramebuffer(GIFRegFRAME& FRAME, bool only_next_draw); bool IsSplitClearActive() const; diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index c1ea068166..fb3be9e08f 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -945,7 +945,7 @@ GSTextureCache::Source* GSTextureCache::LookupDepthSource(const bool is_depth, c t->ResizeTexture(t->m_unscaled_size.x, t->m_unscaled_size.y); t->m_valid = dst->m_valid; } - + CopyRGBFromDepthToColor(t, dst); } @@ -1091,6 +1091,8 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const req_rect.y = region.HasY() ? region.GetMinY() : 0; GSVector4i block_boundary_rect = req_rect; + block_boundary_rect.x = block_boundary_rect.x & ~(psm_s.bs.x - 1); + block_boundary_rect.y = block_boundary_rect.y & ~(psm_s.bs.y - 1); // Round up to the nearst block boundary for lookup to avoid problems due to bilinear and inclusive rects. block_boundary_rect.z = std::max(req_rect.x + 1, (block_boundary_rect.z + (psm_s.bs.x - 2)) & ~(psm_s.bs.x - 1)); block_boundary_rect.w = std::max(req_rect.y + 1, (block_boundary_rect.w + (psm_s.bs.y - 2)) & ~(psm_s.bs.y - 1)); @@ -1646,10 +1648,6 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const { GSVector4i new_rect = req_rect; - // Just in case the TextureMinMax trolls us as it does, when checking if inside the target. - new_rect.z -= 2; - new_rect.w -= 2; - // Let's try a trick to avoid to use wrongly a depth buffer // Unfortunately, I don't have any Arc the Lad testcase // @@ -1658,7 +1656,7 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const { for (auto t : m_dst[DepthStencil]) { - if (t->m_age <= 1 && t->m_used && t->m_dirty.empty() && GSUtil::HasSharedBits(psm, t->m_TEX0.PSM) && t->Inside(bp, bw, psm, new_rect)) + if (t->m_age <= 1 && t->m_used && t->m_dirty.empty() && GSUtil::HasSharedBits(psm, t->m_TEX0.PSM) && t->Inside(bp, bw, psm, block_boundary_rect)) { GL_INS("TC: Warning depth format read as color format. Pixels will be scrambled"); // Let's fetch a depth format texture. Rational, it will avoid the texture allocation and the @@ -1668,7 +1666,7 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const GIFRegTEX0 depth_TEX0; depth_TEX0.U32[0] = TEX0.U32[0] | (0x30u << 20u); depth_TEX0.U32[1] = TEX0.U32[1]; - src = LookupDepthSource(false, depth_TEX0, TEXA, CLAMP, req_rect, possible_shuffle, linear, frame_fbp, req_color, req_alpha); + src = LookupDepthSource(false, depth_TEX0, TEXA, CLAMP, block_boundary_rect, possible_shuffle, linear, frame_fbp, req_color, req_alpha); if (src != nullptr) { @@ -1690,7 +1688,7 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const } else { - src = LookupDepthSource(false, TEX0, TEXA, CLAMP, req_rect, possible_shuffle, linear, frame_fbp, req_color, req_alpha, true); + src = LookupDepthSource(false, TEX0, TEXA, CLAMP, block_boundary_rect, possible_shuffle, linear, frame_fbp, req_color, req_alpha, true); if (src != nullptr) { @@ -1803,7 +1801,7 @@ GSVector2i GSTextureCache::ScaleRenderTargetSize(const GSVector2i& sz, float sca } GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVector2i& size, float scale, int type, - bool used, u32 fbmask, bool is_frame, bool preload, bool preserve_rgb, bool preserve_alpha, const GSVector4i draw_rect, bool is_shuffle, bool possible_clear, bool preserve_scale) + bool used, u32 fbmask, bool is_frame, bool preload, bool preserve_rgb, bool preserve_alpha, const GSVector4i draw_rect, bool is_shuffle, bool possible_clear, bool preserve_scale, GSTextureCache::Source* src) { const GSLocalMemory::psm_t& psm_s = GSLocalMemory::m_psm[TEX0.PSM]; const u32 bp = TEX0.TBP0; @@ -1812,8 +1810,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe const GSVector4 sRect(0, 0, 1, 1); GSVector4 dRect{}; bool clear = true; - const auto& calcRescale = [&size, &scale, &new_size, &new_scaled_size, &clear, &dRect](const Target* tgt) - { + const auto& calcRescale = [&size, &scale, &new_size, &new_scaled_size, &clear, &dRect](const Target* tgt) { // TODO Possible optimization: rescale only the validity rectangle of the old target texture into the new one. clear = (size.x > tgt->m_unscaled_size.x || size.y > tgt->m_unscaled_size.y); new_size = size.max(tgt->m_unscaled_size); @@ -1826,7 +1823,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe Target* dst = nullptr; auto& list = m_dst[type]; - + const GSVector4i min_rect = draw_rect.max_u32(GSVector4i(0, 0, draw_rect.x, draw_rect.y)); // TODO: Move all frame stuff to its own routine too. if (!is_frame) { @@ -1837,6 +1834,10 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe if (bp == t->m_TEX0.TBP0) { bool can_use = true; + + if (dst && (GSState::s_n - dst->m_last_draw) < (GSState::s_n - t->m_last_draw)) + continue; + // if It's an old target and it's being completely overwritten, kill it. // Dragon Quest 8 reuses a render-target sized buffer as a single-page buffer, without clearing it. But, // it does dirty it by writing over the 64x64 region. So while we can't use this heuristic for tossing @@ -1880,7 +1881,11 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe dst = t; dst->m_32_bits_fmt |= (psm_s.bpp != 16); - break; + + if (FindOverlappingTarget(dst)) + continue; + else + break; } else { @@ -1890,6 +1895,16 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe delete t; } } + // Probably pointing to half way through the target + else if ((!dst || ((GSState::s_n - dst->m_last_draw) < (GSState::s_n - t->m_last_draw))) && GSConfig.UserHacks_TextureInsideRt >= GSTextureInRtMode::InsideTargets && (t->m_TEX0.TBW == TEX0.TBW || (TEX0.TBW == 1 && t->m_TEX0.TBW > 1)) && t->Inside(bp, TEX0.TBW, TEX0.PSM, min_rect)) + { + //DevCon.Warning("Here draw %d wanted %x PSM %x got %x PSM %x offset of %d pages width %d pages draw width %d", GSState::s_n, bp, TEX0.PSM, t->m_TEX0.TBP0, t->m_TEX0.PSM, (bp - t->m_TEX0.TBP0) >> 5, t->m_TEX0.TBW, draw_rect.width()); + dst = t; + + dst->m_32_bits_fmt |= (psm_s.bpp != 16); + //Continue just in case there's a newer target + continue; + } } } else @@ -2042,6 +2057,64 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe dst->m_alpha_min = 0; dst->m_alpha_max = 0; } + else if (!is_shuffle && std::abs(static_cast(GSLocalMemory::m_psm[dst->m_TEX0.PSM].bpp - GSLocalMemory::m_psm[TEX0.PSM].bpp)) == 16) + { + const bool scale_down = GSLocalMemory::m_psm[dst->m_TEX0.PSM].bpp > GSLocalMemory::m_psm[TEX0.PSM].bpp; + new_size = dst->m_unscaled_size; + new_scaled_size = ScaleRenderTargetSize(dst->m_unscaled_size, scale); + + dRect = (GSVector4(GSVector4i::loadh(dst->m_unscaled_size)) * GSVector4(scale)).ceil(); + + if (scale_down) + { + if ((new_size.y * 2) < 1024) + { + new_scaled_size.y *= 2; + new_size.y *= 2; + dst->m_valid.y *= 2; + dst->m_valid.w *= 2; + } + dRect.y *= 2; + dRect.w *= 2; + } + else + { + new_scaled_size.y /= 2; + new_size.y /= 2; + dRect.y /= 2; + dRect.w /= 2; + dst->m_valid.y /= 2; + dst->m_valid.w /= 2; + } + GL_INS("TC Convert to 16bit: %dx%d: %dx%d @ %f -> %dx%d @ %f", dst->m_unscaled_size.x, dst->m_unscaled_size.y, + dst->m_texture->GetWidth(), dst->m_texture->GetHeight(), dst->m_scale, new_scaled_size.x, new_scaled_size.y, + scale); + DevCon.Warning("Scale %s draw %d", scale_down ? "down" : "up", GSState::s_n); + GSTexture* tex = type == RenderTarget ? g_gs_device->CreateRenderTarget(new_scaled_size.x, new_scaled_size.y, GSTexture::Format::Color, true) : + g_gs_device->CreateDepthStencil(new_scaled_size.x, new_scaled_size.y, GSTexture::Format::DepthStencil, true); + + m_target_memory_usage += tex->GetMemUsage(); + + g_gs_device->StretchRect(dst->m_texture, sRect, tex, dRect, (type == RenderTarget) ? ShaderConvert::COPY : ShaderConvert::DEPTH_COPY, false); + + + if (src && src->m_from_target && src->m_from_target == dst) + { + src->m_texture = dst->m_texture; + src->m_target_direct = false; + src->m_shared_texture = false; + } + else + { + m_target_memory_usage -= dst->m_texture->GetMemUsage(); + g_gs_device->Recycle(dst->m_texture); + } + + dst->m_TEX0.PSM = TEX0.PSM; + dst->m_texture = tex; + dst->m_unscaled_size = new_size; + + } // If our RGB was invalidated, we need to pull it from depth. // Terminator 3 will reuse our dst_matched target with the RGB masked, then later use the full ARGB area, so we need to update the depth. @@ -3220,8 +3293,7 @@ void GSTextureCache::InvalidateVideoMem(const GSOffset& off, const GSVector4i& r // But this causes rects to be too big, especially in WRC games, I don't think there's any need to align them here. GSVector4i r = rect; - off.loopPages(rect, [this, &rect, bp, bw, psm, &found](u32 page) - { + off.loopPages(rect, [this, &rect, bp, bw, psm, &found](u32 page) { auto& list = m_src.m_map[page]; for (auto i = list.begin(); i != list.end();) { @@ -3844,7 +3916,7 @@ bool GSTextureCache::Move(u32 SBP, u32 SBW, u32 SPSM, int sx, int sy, u32 DBP, u // Make sure the copy doesn't go out of bounds (it shouldn't). if ((scaled_dx + scaled_w) > dst->m_texture->GetWidth() || (scaled_dy + scaled_h) > dst->m_texture->GetHeight()) return false; - GL_CACHE("HW Move 0x%x[BW:%u PSM:%s] to 0x%x[BW:%u PSM:%s] <%d,%d->%d,%d> -> <%d,%d->%d,%d>", SBP, SBW, + DevCon.Warning("HW Move 0x%x[BW:%u PSM:%s] to 0x%x[BW:%u PSM:%s] <%d,%d->%d,%d> -> <%d,%d->%d,%d>", SBP, SBW, psm_str(SPSM), DBP, DBW, psm_str(DPSM), sx, sy, sx + w, sy + h, dx, dy, dx + w, dy + h); const bool cover_whole_target = dst->m_type == RenderTarget && GSVector4i(dx, dy, dx + w, dy + h).rintersect(dst->m_valid).eq(dst->m_valid); @@ -4401,7 +4473,7 @@ void GSTextureCache::IncAge() AgeHashCache(); // As of 04/15/2024 this is s et to 60 (just 1 second of targets), which should be fine now as it doesn't destroy targets which haven't been covered. - // + // // For reference, here are some games sensitive to killing old targets: // Original maxage was 4 here, Xenosaga 2 needs at least 240, else it flickers on scene transitions. // ffx intro scene changes leave the old image untouched for a couple of frames and only then start using it @@ -5634,8 +5706,7 @@ std::shared_ptr GSTextureCache::LookupPaletteObject(con void GSTextureCache::Read(Target* t, const GSVector4i& r) { - if ((!t->m_dirty.empty() && !t->m_dirty.GetTotalRect(t->m_TEX0, t->m_unscaled_size).rintersect(r).rempty()) - || r.width() == 0 || r.height() == 0) + if ((!t->m_dirty.empty() && !t->m_dirty.GetTotalRect(t->m_TEX0, t->m_unscaled_size).rintersect(r).rempty()) || r.width() == 0 || r.height() == 0) return; const GIFRegTEX0& TEX0 = t->m_TEX0; @@ -5856,7 +5927,10 @@ GSTextureCache::Source::~Source() // to recycle. if (!m_shared_texture && !m_from_hash_cache && m_texture) { - g_texture_cache->m_source_memory_usage -= m_texture->GetMemUsage(); + if(m_from_target) + g_texture_cache->m_target_memory_usage -= m_texture->GetMemUsage(); + else + g_texture_cache->m_source_memory_usage -= m_texture->GetMemUsage(); g_gs_device->Recycle(m_texture); } } @@ -6587,8 +6661,7 @@ void GSTextureCache::SourceMap::Add(Source* s, const GIFRegTEX0& TEX0) m_surfaces.insert(s); // The source pointer will be stored/duplicated in all m_map[array of pages] - s->m_pages.loopPages([this, s](u32 page) - { + s->m_pages.loopPages([this, s](u32 page) { s->m_erase_it[page] = m_map[page].InsertFront(s); }); } @@ -6631,8 +6704,7 @@ void GSTextureCache::SourceMap::RemoveAt(Source* s) GL_CACHE("TC: Remove Src Texture: 0x%x TBW %u PSM %s", s->m_TEX0.TBP0, s->m_TEX0.TBW, psm_str(s->m_TEX0.PSM)); - s->m_pages.loopPages([this, s](u32 page) - { + s->m_pages.loopPages([this, s](u32 page) { m_map[page].EraseIndex(s->m_erase_it[page]); }); @@ -7045,7 +7117,7 @@ std::shared_ptr GSTextureCache::PaletteMap::LookupPalet { // Palette is unused it = map.erase(it); // Erase element from map - // The palette object should now be gone as the shared pointer to the object in the map is deleted + // The palette object should now be gone as the shared pointer to the object in the map is deleted } else { @@ -7109,10 +7181,7 @@ bool GSTextureCache::SurfaceOffsetKeyEqual::operator()(const GSTextureCache::Sur { const SurfaceOffsetKeyElem& lhs_elem = lhs.elems[i]; const SurfaceOffsetKeyElem& rhs_elem = rhs.elems[i]; - if (lhs_elem.bp != rhs_elem.bp - || lhs_elem.bw != rhs_elem.bw - || lhs_elem.psm != rhs_elem.psm - || !lhs_elem.rect.eq(rhs_elem.rect)) + if (lhs_elem.bp != rhs_elem.bp || lhs_elem.bw != rhs_elem.bw || lhs_elem.psm != rhs_elem.psm || !lhs_elem.rect.eq(rhs_elem.rect)) return false; } return true; diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.h b/pcsx2/GS/Renderers/HW/GSTextureCache.h index 3ee9f925b0..0c2f264b9e 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.h +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.h @@ -491,7 +491,7 @@ public: Target* FindTargetOverlap(Target* target, int type, int psm); Target* LookupTarget(GIFRegTEX0 TEX0, const GSVector2i& size, float scale, int type, bool used = true, u32 fbmask = 0, bool is_frame = false, bool preload = GSConfig.PreloadFrameWithGSData, bool preserve_rgb = true, bool preserve_alpha = true, - const GSVector4i draw_rc = GSVector4i::zero(), bool is_shuffle = false, bool possible_clear = false, bool preserve_scale = false); + const GSVector4i draw_rc = GSVector4i::zero(), bool is_shuffle = false, bool possible_clear = false, bool preserve_scale = false, GSTextureCache::Source* src = nullptr); Target* CreateTarget(GIFRegTEX0 TEX0, const GSVector2i& size, const GSVector2i& valid_size,float scale, int type, bool used = true, u32 fbmask = 0, bool is_frame = false, bool preload = GSConfig.PreloadFrameWithGSData, bool preserve_target = true, const GSVector4i draw_rc = GSVector4i::zero(), GSTextureCache::Source* src = nullptr); diff --git a/pcsx2/GS/Renderers/Metal/tfx.metal b/pcsx2/GS/Renderers/Metal/tfx.metal index 447d3018a2..40d282e0bb 100644 --- a/pcsx2/GS/Renderers/Metal/tfx.metal +++ b/pcsx2/GS/Renderers/Metal/tfx.metal @@ -826,7 +826,7 @@ struct PSMain else T = sample_color(st); - if (PS_SHUFFLE && !PS_SHUFFLE_SAME && !PS_READ16_SRC) + if (PS_SHUFFLE && !PS_SHUFFLE_SAME && !PS_READ16_SRC && !(PS_PROCESS_BA == SHUFFLE_READWRITE && PS_PROCESS_RG == SHUFFLE_READWRITE)) { uint4 denorm_c_before = uint4(T); if (PS_PROCESS_BA & SHUFFLE_READ) @@ -1134,7 +1134,7 @@ struct PSMain if (PS_SHUFFLE) { - if (!PS_SHUFFLE_SAME && !PS_READ16_SRC) + if (!PS_SHUFFLE_SAME && !PS_READ16_SRC && !(PS_PROCESS_BA == SHUFFLE_READWRITE && PS_PROCESS_RG == SHUFFLE_READWRITE)) { uint4 denorm_c_after = uint4(C); if (PS_PROCESS_BA & SHUFFLE_READ) From 0a42313b6f53eb45ef42f63d1c81b9e092e0e364 Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Wed, 26 Jun 2024 12:23:35 +0100 Subject: [PATCH 087/162] GS/HW: Further fixes to RT in RT - Still a ways to go... --- pcsx2/GS/GSState.h | 1 + pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 91 ++++++++++++++++-------- pcsx2/GS/Renderers/HW/GSTextureCache.cpp | 84 ++++++++++++++++------ 3 files changed, 124 insertions(+), 52 deletions(-) diff --git a/pcsx2/GS/GSState.h b/pcsx2/GS/GSState.h index a9d5e6c992..5b07b03424 100644 --- a/pcsx2/GS/GSState.h +++ b/pcsx2/GS/GSState.h @@ -225,6 +225,7 @@ public: bool m_isPackedUV_HackFlag = false; bool m_channel_shuffle = false; bool m_in_target_draw = false; + u32 m_target_offset = 0; u8 m_scanmask_used = 0; u32 m_dirty_gs_regs = 0; int m_backed_up_ctx = 0; diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index 66cdd29106..39e9c62a08 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -2180,6 +2180,8 @@ void GSRendererHW::Draw() if (num_skipped_channel_shuffle_draws > 0) GL_INS("Skipped %u channel shuffle draws", num_skipped_channel_shuffle_draws); num_skipped_channel_shuffle_draws = 0; + m_last_channel_shuffle_fbp = 0xffff; + m_last_channel_shuffle_end_block = 0xffff; #else if (m_channel_shuffle) return; @@ -2866,18 +2868,21 @@ void GSRendererHW::Draw() const GSLocalMemory::psm_t& frame_psm = GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM]; m_in_target_draw = false; + m_target_offset = 0; if (!no_rt) { - // FBW is going to be wrong for channel shuffling into a new target, so take it from the source. - FRAME_TEX0.U64 = 0; - FRAME_TEX0.TBP0 = m_cached_ctx.FRAME.Block(); - FRAME_TEX0.TBW = (m_channel_shuffle && src->m_target) ? src->m_from_target_TEX0.TBW : m_cached_ctx.FRAME.FBW; - FRAME_TEX0.PSM = m_cached_ctx.FRAME.PSM; - const bool possible_shuffle = draw_sprite_tex && (((src && src->m_target && src->m_from_target && src->m_from_target->m_32_bits_fmt) && GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].bpp == 16 && GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].bpp == 16) || IsPossibleChannelShuffle()); + + // FBW is going to be wrong for channel shuffling into a new target, so take it from the source. + FRAME_TEX0.U64 = 0; + FRAME_TEX0.TBP0 = ((m_last_channel_shuffle_end_block + 1) == m_cached_ctx.FRAME.Block() && possible_shuffle) ? m_last_channel_shuffle_fbp : m_cached_ctx.FRAME.Block(); + FRAME_TEX0.TBW = (possible_shuffle && (m_last_channel_shuffle_end_block + 1) && src->m_target) ? src->m_from_target_TEX0.TBW : m_cached_ctx.FRAME.FBW; + FRAME_TEX0.PSM = m_cached_ctx.FRAME.PSM; + + // Don't clamp on shuffle, the height cache may troll us with the REAL height. if (!possible_shuffle && m_split_texture_shuffle_pages == 0) m_r = m_r.rintersect(t_size_rect); @@ -2892,12 +2897,10 @@ void GSRendererHW::Draw() // Of course if this size is different (in width) or this is a shuffle happening, this will be bypassed. const bool preserve_downscale_draw = std::abs(scale_draw) == 1 || (scale_draw == 0 && ((src && src->m_from_target && src->m_from_target->m_downscaled) || is_possible_mem_clear == ClearType::ClearWithDraw)); - m_in_target_draw = false; - rt = g_texture_cache->LookupTarget(FRAME_TEX0, t_size, ((src && src->m_scale != 1) && GSConfig.UserHacks_NativeScaling == GSNativeScaling::Normal && !possible_shuffle) ? GetTextureScaleFactor() : target_scale, GSTextureCache::RenderTarget, true, fm, false, force_preload, preserve_rt_rgb, preserve_rt_alpha, unclamped_draw_rect, possible_shuffle, is_possible_mem_clear && FRAME_TEX0.TBP0 != m_cached_ctx.ZBUF.Block(), GSConfig.UserHacks_NativeScaling != GSNativeScaling::Off && preserve_downscale_draw && is_possible_mem_clear != ClearType::NormalClear, src); - + // Draw skipped because it was a clear and there was no target. if (!rt) { @@ -2931,12 +2934,14 @@ void GSRendererHW::Draw() return; } } - else if (rt->m_TEX0.TBP0 != FRAME_TEX0.TBP0) // Must have done rt in rt + else if (rt->m_TEX0.TBP0 != m_cached_ctx.FRAME.Block()) // Must have done rt in rt { GSVertex* v = &m_vertex.buff[0]; - u32 vertical_offset = (((FRAME_TEX0.TBP0 - rt->m_TEX0.TBP0) >> 5) / std::max(rt->m_TEX0.TBW, 1U)) * frame_psm.pgs.y; // I know I could just not shift it.. + int vertical_offset = ((std::abs(static_cast(m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0)) >> 5) / std::max(rt->m_TEX0.TBW, 1U)) * frame_psm.pgs.y; // I know I could just not shift it.. - const u32 horizontal_offset = (((FRAME_TEX0.TBP0 - rt->m_TEX0.TBP0) >> 5) % std::max(rt->m_TEX0.TBW, 1U)) * frame_psm.pgs.x; + const int horizontal_offset = (std::abs(static_cast((m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0)) >> 5) % std::max(rt->m_TEX0.TBW, 1U)) * frame_psm.pgs.x; + // Used to reduce the offset made later in channel shuffles + m_target_offset = std::abs(static_cast((m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0)) >> 5); for (u32 i = 0; i < m_vertex.tail; i++) { @@ -2959,6 +2964,16 @@ void GSRendererHW::Draw() m_vt.m_max.p.y += vertical_offset; t_size.x = rt->m_unscaled_size.x - horizontal_offset; t_size.y = rt->m_unscaled_size.y - vertical_offset; + + if (t_size.y <= 0) + { + u32 new_height = m_r.w; + + //DevCon.Warning("Resizing texture %d x %d draw %d", rt->m_unscaled_size.x, new_height, s_n); + rt->ResizeTexture(rt->m_unscaled_size.x, new_height); + rt->UpdateValidity(m_r, true); + rt->UpdateDrawn(m_r, true); + } } if (src && src->m_from_target && src->m_target_direct && src->m_from_target == rt) @@ -2981,6 +2996,8 @@ void GSRendererHW::Draw() // If it's a new target, we don't know where the end is as it's starting on a shuffle, so just do every shuffle following. m_last_channel_shuffle_end_block = (rt->m_last_draw >= s_n) ? (MAX_BLOCKS - 1) : (rt->m_end_block < rt->m_TEX0.TBP0 ? (rt->m_end_block + MAX_BLOCKS) : rt->m_end_block); } + else + m_last_channel_shuffle_end_block = 0xFFFF; } GSTextureCache::Target* ds = nullptr; @@ -2989,12 +3006,14 @@ void GSRendererHW::Draw() { ZBUF_TEX0.U64 = 0; ZBUF_TEX0.TBP0 = m_cached_ctx.ZBUF.Block(); - ZBUF_TEX0.TBW = m_channel_shuffle ? src->m_from_target_TEX0.TBW : m_cached_ctx.FRAME.FBW; + ZBUF_TEX0.TBW = m_cached_ctx.FRAME.FBW; ZBUF_TEX0.PSM = m_cached_ctx.ZBUF.PSM; ds = g_texture_cache->LookupTarget(ZBUF_TEX0, t_size, target_scale, GSTextureCache::DepthStencil, m_cached_ctx.DepthWrite(), 0, false, force_preload, preserve_depth, preserve_depth, unclamped_draw_rect, IsPossibleChannelShuffle(), is_possible_mem_clear && ZBUF_TEX0.TBP0 != m_cached_ctx.FRAME.Block()); + ZBUF_TEX0.TBW = m_channel_shuffle ? src->m_from_target_TEX0.TBW : m_cached_ctx.FRAME.FBW; + if (!ds) { ds = g_texture_cache->CreateTarget(ZBUF_TEX0, t_size, GetValidSize(src), target_scale, GSTextureCache::DepthStencil, @@ -3285,7 +3304,10 @@ void GSRendererHW::Draw() rt->m_valid_alpha_high = false; } if (FRAME_TEX0.TBW != 1 || (m_r.width() > frame_psm.pgs.x || m_r.height() > frame_psm.pgs.y)) + { rt->m_TEX0 = FRAME_TEX0; + + } } if (ds && (!is_possible_mem_clear || ds->m_TEX0.PSM != ZBUF_TEX0.PSM || (rt && ds->m_TEX0.TBW != rt->m_TEX0.TBW)) && !m_in_target_draw) @@ -3439,7 +3461,7 @@ void GSRendererHW::Draw() } } } - else if (!m_in_target_draw) + else { // RT and DS sizes need to match, even if we're not doing any resizing. const int new_w = std::max(rt ? rt->m_unscaled_size.x : 0, ds ? ds->m_unscaled_size.x : 0); @@ -4117,7 +4139,7 @@ __ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool m_conf.ps.urban_chaos_hle = 1; } } - else if (m_index.tail <= 64 && m_cached_ctx.CLAMP.WMT == 3) + else if (m_index.tail < 64 && m_cached_ctx.CLAMP.WMT == 3) { // Blood will tell. I think it is channel effect too but again // implemented in a different way. I don't want to add more CRC stuff. So @@ -4291,16 +4313,24 @@ __ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool } else { + const GSLocalMemory::psm_t frame_psm = GSLocalMemory::m_psm[m_context->FRAME.PSM]; + const u32 frame_page_offset = std::max(static_cast(((m_r.x / frame_psm.pgs.x) + (m_r.y / frame_psm.pgs.y) * src->m_TEX0.TBW) - m_target_offset), 0); + m_r = GSVector4i(m_r.x & ~(frame_psm.pgs.x - 1), m_r.y & ~(frame_psm.pgs.y - 1), (m_r.z + (frame_psm.pgs.x - 1)) & ~(frame_psm.pgs.x - 1), (m_r.w + (frame_psm.pgs.y - 1)) & ~(frame_psm.pgs.y - 1)); + m_cached_ctx.FRAME.FBP += frame_page_offset; + m_in_target_draw |= frame_page_offset > 0; GSVertex* s = &m_vertex.buff[0]; s[0].XYZ.X = static_cast(m_context->XYOFFSET.OFX + (m_r.x << 4)); s[1].XYZ.X = static_cast(m_context->XYOFFSET.OFX + (m_r.z << 4)); s[0].XYZ.Y = static_cast(m_context->XYOFFSET.OFY + (m_r.y << 4)); s[1].XYZ.Y = static_cast(m_context->XYOFFSET.OFY + (m_r.w << 4)); - s[0].U = (m_r.x << 4); - s[1].U = (m_r.z << 4); - s[0].V = (m_r.y << 4); - s[1].V = (m_r.w << 4); + const GSLocalMemory::psm_t tex_psm = GSLocalMemory::m_psm[m_context->TEX0.PSM]; + const u32 tex_page_offset = (m_vt.m_min.t.x / tex_psm.pgs.x) + (m_vt.m_min.t.y / tex_psm.pgs.y); + m_cached_ctx.TEX0.TBP0 += tex_page_offset << 5; + s[0].U = m_r.x << 4; + s[1].U = m_r.z << 4; + s[0].V = m_r.y << 4; + s[1].V = m_r.w << 4; m_last_channel_shuffle_fbmsk = 0xFFFFFFFF; } @@ -5537,7 +5567,7 @@ __ri void GSRendererHW::HandleTextureHazards(const GSTextureCache::Target* rt, c // Detect framebuffer read that will need special handling const GSTextureCache::Target* src_target = nullptr; - if (rt && m_conf.tex == m_conf.rt && !(m_channel_shuffle && tex && tex_diff != frame_diff)) + if (rt && m_conf.tex == m_conf.rt && !(m_channel_shuffle && tex && (tex_diff != frame_diff || target_region))) { // Can we read the framebuffer directly? (i.e. sample location matches up). if (CanUseTexIsFB(rt, tex, tmm)) @@ -5595,7 +5625,7 @@ __ri void GSRendererHW::HandleTextureHazards(const GSTextureCache::Target* rt, c GSVector4i copy_range; GSVector2i copy_size; GSVector2i copy_dst_offset; - + bool copied_rt = false; // Shuffles take the whole target. This should've already been halved. // We can't partially copy depth targets in DirectX, and GL/Vulkan should use the direct read above. // Restricting it also breaks Tom and Jerry... @@ -5611,11 +5641,14 @@ __ri void GSRendererHW::HandleTextureHazards(const GSTextureCache::Target* rt, c u32 page_offset = (m_cached_ctx.TEX0.TBP0 - src_target->m_TEX0.TBP0) >> 5; u32 vertical_offset = (page_offset / src_target->m_TEX0.TBW) * GSLocalMemory::m_psm[src_target->m_TEX0.PSM].pgs.y; u32 horizontal_offset = (page_offset % src_target->m_TEX0.TBW) * GSLocalMemory::m_psm[src_target->m_TEX0.PSM].pgs.x; + copy_range.y += vertical_offset; copy_range.x += horizontal_offset; copy_size.y -= vertical_offset; copy_size.x -= horizontal_offset; - + target_region = false; + source_region.bits = 0; + //copied_rt = tex->m_from_target != nullptr; if (m_in_target_draw) { copy_size.x = m_r.width(); @@ -5711,12 +5744,9 @@ __ri void GSRendererHW::HandleTextureHazards(const GSTextureCache::Target* rt, c static_cast(std::ceil(static_cast(copy_dst_offset.y) * scale))); src_copy.reset(src_target->m_texture->IsDepthStencil() ? - g_gs_device->CreateDepthStencil( - scaled_copy_size.x, scaled_copy_size.y, src_target->m_texture->GetFormat(), false) : - (m_downscale_source ? g_gs_device->CreateRenderTarget(scaled_copy_size.x, scaled_copy_size.y, src_target->m_texture->GetFormat(), true, - true) : - g_gs_device->CreateTexture( - scaled_copy_size.x, scaled_copy_size.y, 1, src_target->m_texture->GetFormat(), true))); + g_gs_device->CreateDepthStencil(scaled_copy_size.x, scaled_copy_size.y, src_target->m_texture->GetFormat(), false) : + (m_downscale_source || copied_rt) ? g_gs_device->CreateRenderTarget(scaled_copy_size.x, scaled_copy_size.y, src_target->m_texture->GetFormat(), true, true) : + g_gs_device->CreateTexture(scaled_copy_size.x, scaled_copy_size.y, 1, src_target->m_texture->GetFormat(), true)); if (!src_copy) [[unlikely]] { Console.Error("Failed to allocate %dx%d texture for hazard copy", scaled_copy_size.x, scaled_copy_size.y); @@ -5724,6 +5754,7 @@ __ri void GSRendererHW::HandleTextureHazards(const GSTextureCache::Target* rt, c m_conf.ps.tfx = 4; return; } + if (m_downscale_source) { g_perfmon.Put(GSPerfMon::TextureCopies, 1); @@ -7598,7 +7629,7 @@ void GSRendererHW::ClearGSLocalMemory(const GSOffset& off, const GSVector4i& r, bool GSRendererHW::OI_BlitFMV(GSTextureCache::Target* _rt, GSTextureCache::Source* tex, const GSVector4i& r_draw) { - if (r_draw.w > 1024 && (m_vt.m_primclass == GS_SPRITE_CLASS) && (m_vertex.next == 2) && m_process_texture && !PRIM->ABE && tex && !tex->m_target && m_cached_ctx.TEX0.TBW > 0) + /*if (r_draw.w > 1024 && (m_vt.m_primclass == GS_SPRITE_CLASS) && (m_vertex.next == 2) && m_process_texture && !PRIM->ABE && tex && !tex->m_target && m_cached_ctx.TEX0.TBW > 0) { GL_PUSH("OI_BlitFMV"); @@ -7652,7 +7683,7 @@ bool GSRendererHW::OI_BlitFMV(GSTextureCache::Target* _rt, GSTextureCache::Sourc g_texture_cache->InvalidateVideoMemSubTarget(_rt); return false; // skip current draw - } + }*/ // Nothing to see keep going return true; diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index fb3be9e08f..d8135264e4 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -18,6 +18,7 @@ #include "fmt/format.h" #include +#include #ifdef __APPLE__ #include @@ -1094,8 +1095,8 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const block_boundary_rect.x = block_boundary_rect.x & ~(psm_s.bs.x - 1); block_boundary_rect.y = block_boundary_rect.y & ~(psm_s.bs.y - 1); // Round up to the nearst block boundary for lookup to avoid problems due to bilinear and inclusive rects. - block_boundary_rect.z = std::max(req_rect.x + 1, (block_boundary_rect.z + (psm_s.bs.x - 2)) & ~(psm_s.bs.x - 1)); - block_boundary_rect.w = std::max(req_rect.y + 1, (block_boundary_rect.w + (psm_s.bs.y - 2)) & ~(psm_s.bs.y - 1)); + block_boundary_rect.z = std::max(req_rect.x + 1, (block_boundary_rect.z + (psm_s.bs.x / 2)) & ~(psm_s.bs.x - 1)); + block_boundary_rect.w = std::max(req_rect.y + 1, (block_boundary_rect.w + (psm_s.bs.y / 2)) & ~(psm_s.bs.y - 1)); // Arc the Lad finds the wrong surface here when looking for a depth stencil. // Since we're currently not caching depth stencils (check ToDo in CreateSource) we should not look for it here. @@ -1117,8 +1118,8 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const if (((bp & (BLOCKS_PER_PAGE - 1)) != (t->m_TEX0.TBP0 & (BLOCKS_PER_PAGE - 1))) && (bp & (BLOCKS_PER_PAGE - 1))) continue; + //const bool overlaps = t->Inside(bp, bw, psm, block_boundary_rect); const bool overlaps = t->Overlaps(bp, bw, psm, block_boundary_rect); - // Try to make sure the target has available what we need, be careful of self referencing frames with font in the alpha. // Also is we have already found a target which we had to offset in to by using a region or exact address, // it's probable that's more correct than being inside (Tomb Raider Legends + Project Snowblind) @@ -1525,7 +1526,7 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const rect.y -= new_rect.y & ~(page_size.y - 1); } - rect = rect.rintersect(t->m_valid); + //rect = rect.rintersect(t->m_valid); if (rect.rempty()) continue; @@ -1646,8 +1647,6 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const if (!found_t && !dst && !GSConfig.UserHacks_DisableDepthSupport) { - GSVector4i new_rect = req_rect; - // Let's try a trick to avoid to use wrongly a depth buffer // Unfortunately, I don't have any Arc the Lad testcase // @@ -1836,8 +1835,11 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe bool can_use = true; if (dst && (GSState::s_n - dst->m_last_draw) < (GSState::s_n - t->m_last_draw)) + { + DevCon.Warning("Ignoring target at %x as one at %x is newer", t->m_TEX0.TBP0, dst->m_TEX0.TBP0); continue; - + } + // if It's an old target and it's being completely overwritten, kill it. // Dragon Quest 8 reuses a render-target sized buffer as a single-page buffer, without clearing it. But, // it does dirty it by writing over the 64x64 region. So while we can't use this heuristic for tossing @@ -1882,12 +1884,12 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe dst->m_32_bits_fmt |= (psm_s.bpp != 16); - if (FindOverlappingTarget(dst)) + /*if (FindOverlappingTarget(dst)) continue; - else + else*/ break; } - else + else if(!(src && src->m_from_target == t)) { GL_INS("TC: Deleting RT BP 0x%x BW %d PSM %s due to change in target", t->m_TEX0.TBP0, t->m_TEX0.TBW, psm_str(t->m_TEX0.PSM)); InvalidateSourcesFromTarget(t); @@ -1896,14 +1898,34 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe } } // Probably pointing to half way through the target - else if ((!dst || ((GSState::s_n - dst->m_last_draw) < (GSState::s_n - t->m_last_draw))) && GSConfig.UserHacks_TextureInsideRt >= GSTextureInRtMode::InsideTargets && (t->m_TEX0.TBW == TEX0.TBW || (TEX0.TBW == 1 && t->m_TEX0.TBW > 1)) && t->Inside(bp, TEX0.TBW, TEX0.PSM, min_rect)) + else if(GSConfig.UserHacks_TextureInsideRt >= GSTextureInRtMode::InsideTargets) { - //DevCon.Warning("Here draw %d wanted %x PSM %x got %x PSM %x offset of %d pages width %d pages draw width %d", GSState::s_n, bp, TEX0.PSM, t->m_TEX0.TBP0, t->m_TEX0.PSM, (bp - t->m_TEX0.TBP0) >> 5, t->m_TEX0.TBW, draw_rect.width()); - dst = t; + const u32 widthpage_offset = (std::abs(static_cast(bp - t->m_TEX0.TBP0)) >> 5) % std::max(t->m_TEX0.TBW, 1U); + const bool is_aligned_ok = widthpage_offset == 0 || ((widthpage_offset + TEX0.TBW) <= t->m_TEX0.TBW) || min_rect.width() <= 64 || (widthpage_offset == (t->m_TEX0.TBW >> 1) && (static_cast(min_rect.width()) <= (widthpage_offset * 64))); + if ((!dst || ((GSState::s_n - dst->m_last_draw) < (GSState::s_n - t->m_last_draw))) && is_aligned_ok && (t->m_TEX0.TBW == TEX0.TBW || (TEX0.TBW == 1 && t->m_TEX0.TBW > 1)) && t->Inside(bp, TEX0.TBW, TEX0.PSM, min_rect)) + { + // If it's too old, it's probably not a real target to jump in to anymore. + if ((GSState::s_n - t->m_last_draw) > 10 && (!t->m_dirty.empty() || (!is_shuffle && + !(widthpage_offset == 0/*TEX0.TBP0 == ((((t->UnwrappedEndBlock() + 1) - t->m_TEX0.TBP0) >> 1) + t->m_TEX0.TBP0)*/ || min_rect.width() <= 64 || + (widthpage_offset == (t->m_TEX0.TBW >> 1) && min_rect.width() == widthpage_offset * 64))))) + { + GL_INS("TC: Deleting RT BP 0x%x BW %d PSM %s due to change in target", t->m_TEX0.TBP0, t->m_TEX0.TBW, psm_str(t->m_TEX0.PSM)); + InvalidateSourcesFromTarget(t); + i = list.erase(i); + delete t; + } + else + { + //DevCon.Warning("Here draw %d wanted %x PSM %x got %x PSM %x offset of %d pages width %d pages draw width %d", GSState::s_n, bp, TEX0.PSM, t->m_TEX0.TBP0, t->m_TEX0.PSM, (bp - t->m_TEX0.TBP0) >> 5, t->m_TEX0.TBW, draw_rect.width()); + dst = t; - dst->m_32_bits_fmt |= (psm_s.bpp != 16); - //Continue just in case there's a newer target - continue; + dst->m_32_bits_fmt |= (psm_s.bpp != 16); + //Continue just in case there's a newer target + if (used) + list.MoveFront(i.Index()); + break; + } + } } } } @@ -2049,6 +2071,8 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe { calcRescale(dst); GSTexture* tex = g_gs_device->CreateDepthStencil(new_scaled_size.x, new_scaled_size.y, GSTexture::Format::DepthStencil, false); + if (!tex) + return nullptr; g_gs_device->StretchRect(dst->m_texture, sRect, tex, dRect, ShaderConvert::FLOAT32_TO_FLOAT24, false); g_perfmon.Put(GSPerfMon::TextureCopies, 1); g_gs_device->Recycle(dst->m_texture); @@ -2089,10 +2113,11 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe GL_INS("TC Convert to 16bit: %dx%d: %dx%d @ %f -> %dx%d @ %f", dst->m_unscaled_size.x, dst->m_unscaled_size.y, dst->m_texture->GetWidth(), dst->m_texture->GetHeight(), dst->m_scale, new_scaled_size.x, new_scaled_size.y, scale); - DevCon.Warning("Scale %s draw %d", scale_down ? "down" : "up", GSState::s_n); + //DevCon.Warning("Scale %s draw %d", scale_down ? "down" : "up", GSState::s_n); GSTexture* tex = type == RenderTarget ? g_gs_device->CreateRenderTarget(new_scaled_size.x, new_scaled_size.y, GSTexture::Format::Color, true) : g_gs_device->CreateDepthStencil(new_scaled_size.x, new_scaled_size.y, GSTexture::Format::DepthStencil, true); - + if (!tex) + return nullptr; m_target_memory_usage += tex->GetMemUsage(); g_gs_device->StretchRect(dst->m_texture, sRect, tex, dRect, (type == RenderTarget) ? ShaderConvert::COPY : ShaderConvert::DEPTH_COPY, false); @@ -2983,7 +3008,7 @@ void GSTextureCache::ScaleTargetForDisplay(Target* t, const GIFRegTEX0& dispfb, } // Inject the new size back into the cache. - GetTargetSize(t->m_TEX0.TBP0, t->m_TEX0.TBW, t->m_TEX0.PSM, 0, static_cast(needed_height)); + GetTargetSize(t->m_TEX0.TBP0, t->m_TEX0.TBW, t->m_TEX0.PSM, new_width, static_cast(needed_height)); } float GSTextureCache::ConvertColorToDepth(u32 c, ShaderConvert convert) @@ -4452,7 +4477,10 @@ void GSTextureCache::ReplaceSourceTexture(Source* s, GSTexture* new_texture, flo if (s->m_from_hash_cache) s->m_from_hash_cache->refcount++; else if (!s->m_shared_texture) + { + DevCon.Warning("replace %d", m_source_memory_usage); m_source_memory_usage += s->m_texture->GetMemUsage(); + } } void GSTextureCache::IncAge() @@ -4588,7 +4616,7 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con return nullptr; } - m_source_memory_usage += dTex->GetMemUsage(); + m_target_memory_usage += dTex->GetMemUsage(); // copy the rt in const GSVector4i area(GSVector4i(x, y, x + w, y + h).rintersect(GSVector4i(sTex->GetSize()).zwxy())); @@ -4905,7 +4933,9 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con return nullptr; } - m_source_memory_usage += dTex->GetMemUsage(); + src->m_shared_texture = false; + src->m_target_direct = false; + m_target_memory_usage += dTex->GetMemUsage(); src->m_texture = dTex; if (use_texture) @@ -5360,7 +5390,7 @@ GSTextureCache::Source* GSTextureCache::CreateMergedSource(GIFRegTEX0 TEX0, GIFR Console.Error("Failed to allocate %dx%d merged dest texture", scaled_width, scaled_height); return nullptr; } - + DevCon.Warning("Merged %d", m_source_memory_usage); m_source_memory_usage += dtex->GetMemUsage(); // Sort rect list by the texture, we want to batch as many as possible together. @@ -6251,6 +6281,7 @@ GSTextureCache::Target::~Target() { // Targets should never be shared. pxAssert(!m_shared_texture); + if (m_texture) { g_texture_cache->m_target_memory_usage -= m_texture->GetMemUsage(); @@ -6552,7 +6583,11 @@ void GSTextureCache::Target::ResizeValidity(const GSVector4i& rect) m_valid = m_valid.rintersect(rect); m_drawn_since_read = m_drawn_since_read.rintersect(rect); m_end_block = GSLocalMemory::GetEndBlockAddress(m_TEX0.TBP0, m_TEX0.TBW, m_TEX0.PSM, m_valid); + + const u32 offset = ((UnwrappedEndBlock() + 1) - m_TEX0.TBP0) % (std::max(m_TEX0.TBW, 1U) << 5); + m_end_block += offset; } + // Else No valid size, so need to resize down. // GL_CACHE("ResizeValidity (0x%x->0x%x) from R:%d,%d Valid: %d,%d", m_TEX0.TBP0, m_end_block, rect.z, rect.w, m_valid.z, m_valid.w); @@ -6565,12 +6600,16 @@ void GSTextureCache::Target::UpdateValidity(const GSVector4i& rect, bool can_res m_valid = rect; m_end_block = GSLocalMemory::GetEndBlockAddress(m_TEX0.TBP0, m_TEX0.TBW, m_TEX0.PSM, m_valid); + const u32 offset = ((UnwrappedEndBlock() + 1) - m_TEX0.TBP0) % (std::max(m_TEX0.TBW, 1U) << 5); + m_end_block += offset; } else if (can_resize) { m_valid = m_valid.runion(rect); m_end_block = GSLocalMemory::GetEndBlockAddress(m_TEX0.TBP0, m_TEX0.TBW, m_TEX0.PSM, m_valid); + const u32 offset = ((UnwrappedEndBlock() + 1) - m_TEX0.TBP0) % (std::max(m_TEX0.TBW, 1U) << 5); + m_end_block += offset; } // GL_CACHE("UpdateValidity (0x%x->0x%x) from R:%d,%d Valid: %d,%d", m_TEX0.TBP0, m_end_block, rect.z, rect.w, m_valid.z, m_valid.w); } @@ -7034,6 +7073,7 @@ void GSTextureCache::Palette::InitializeTexture() } m_tex_palette->Update(GSVector4i(0, 0, m_pal, 1), m_clut, m_pal * sizeof(m_clut[0])); + g_texture_cache->m_source_memory_usage += m_tex_palette->GetMemUsage(); } } From 4cc60437379f8b1562da89afacfff6a0a7f2d88e Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Tue, 2 Jul 2024 15:36:45 +0100 Subject: [PATCH 088/162] GS/HW: Further RT in RT changes to improve compatibility --- pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 164 ++++++++++++----------- pcsx2/GS/Renderers/HW/GSTextureCache.cpp | 29 +++- pcsx2/GS/Renderers/HW/GSTextureCache.h | 2 +- 3 files changed, 110 insertions(+), 85 deletions(-) diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index 39e9c62a08..48914673dc 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -2870,6 +2870,75 @@ void GSRendererHW::Draw() m_in_target_draw = false; m_target_offset = 0; + GSTextureCache::Target* ds = nullptr; + GIFRegTEX0 ZBUF_TEX0; + if (!no_ds) + { + ZBUF_TEX0.U64 = 0; + ZBUF_TEX0.TBP0 = m_cached_ctx.ZBUF.Block(); + ZBUF_TEX0.TBW = m_cached_ctx.FRAME.FBW; + ZBUF_TEX0.PSM = m_cached_ctx.ZBUF.PSM; + + ds = g_texture_cache->LookupTarget(ZBUF_TEX0, t_size, target_scale, GSTextureCache::DepthStencil, + m_cached_ctx.DepthWrite(), 0, false, force_preload, preserve_depth, preserve_depth, unclamped_draw_rect, IsPossibleChannelShuffle(), is_possible_mem_clear && ZBUF_TEX0.TBP0 != m_cached_ctx.FRAME.Block(), false, + src, -1); + + ZBUF_TEX0.TBW = m_channel_shuffle ? src->m_from_target_TEX0.TBW : m_cached_ctx.FRAME.FBW; + + if (!ds) + { + ds = g_texture_cache->CreateTarget(ZBUF_TEX0, t_size, GetValidSize(src), target_scale, GSTextureCache::DepthStencil, + true, 0, false, force_preload, preserve_depth, m_r, src); + if (!ds) [[unlikely]] + { + GL_INS("ERROR: Failed to create ZBUF target, skipping."); + CleanupDraw(true); + return; + } + } + else + { + // If it failed to check depth test earlier, we can now check the top bits from the alpha to get a bit more accurate picture. + if (((zm && m_cached_ctx.TEST.ZTST > ZTST_ALWAYS) || (m_vt.m_eq.z && m_cached_ctx.TEST.ZTST == ZTST_GEQUAL)) && GSLocalMemory::m_psm[m_cached_ctx.ZBUF.PSM].trbpp == 32) + { + if (ds->m_alpha_max != 0) + { + const u32 max_z = (static_cast(ds->m_alpha_max + 1) << 24) - 1; + + switch (m_cached_ctx.TEST.ZTST) + { + case ZTST_GEQUAL: + // Every Z value will pass + if (max_z <= m_vt.m_min.p.z) + { + m_cached_ctx.TEST.ZTST = ZTST_ALWAYS; + if (zm) + { + ds = nullptr; + no_ds = true; + } + } + break; + case ZTST_GREATER: + // Every Z value will pass + if (max_z < m_vt.m_min.p.z) + { + m_cached_ctx.TEST.ZTST = ZTST_ALWAYS; + if (zm) + { + ds = nullptr; + no_ds = true; + } + } + break; + default: + break; + } + } + } + } + } + if (!no_rt) { const bool possible_shuffle = draw_sprite_tex && (((src && src->m_target && src->m_from_target && src->m_from_target->m_32_bits_fmt) && @@ -2899,7 +2968,7 @@ void GSRendererHW::Draw() rt = g_texture_cache->LookupTarget(FRAME_TEX0, t_size, ((src && src->m_scale != 1) && GSConfig.UserHacks_NativeScaling == GSNativeScaling::Normal && !possible_shuffle) ? GetTextureScaleFactor() : target_scale, GSTextureCache::RenderTarget, true, fm, false, force_preload, preserve_rt_rgb, preserve_rt_alpha, unclamped_draw_rect, possible_shuffle, is_possible_mem_clear && FRAME_TEX0.TBP0 != m_cached_ctx.ZBUF.Block(), - GSConfig.UserHacks_NativeScaling != GSNativeScaling::Off && preserve_downscale_draw && is_possible_mem_clear != ClearType::NormalClear, src); + GSConfig.UserHacks_NativeScaling != GSNativeScaling::Off && preserve_downscale_draw && is_possible_mem_clear != ClearType::NormalClear, src, no_ds ? -1 : (m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0)); // Draw skipped because it was a clear and there was no target. if (!rt) @@ -2934,7 +3003,7 @@ void GSRendererHW::Draw() return; } } - else if (rt->m_TEX0.TBP0 != m_cached_ctx.FRAME.Block()) // Must have done rt in rt + else if (rt->m_TEX0.TBP0 != m_cached_ctx.FRAME.Block()) { GSVertex* v = &m_vertex.buff[0]; int vertical_offset = ((std::abs(static_cast(m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0)) >> 5) / std::max(rt->m_TEX0.TBW, 1U)) * frame_psm.pgs.y; // I know I could just not shift it.. @@ -2962,17 +3031,24 @@ void GSRendererHW::Draw() m_vt.m_max.p.x += horizontal_offset; m_vt.m_min.p.y += vertical_offset; m_vt.m_max.p.y += vertical_offset; + t_size.x = rt->m_unscaled_size.x - horizontal_offset; t_size.y = rt->m_unscaled_size.y - vertical_offset; - if (t_size.y <= 0) + // Don't resize if the BPP don't match. + if (frame_psm.bpp == GSLocalMemory::m_psm[rt->m_TEX0.PSM].bpp) { - u32 new_height = m_r.w; - - //DevCon.Warning("Resizing texture %d x %d draw %d", rt->m_unscaled_size.x, new_height, s_n); - rt->ResizeTexture(rt->m_unscaled_size.x, new_height); - rt->UpdateValidity(m_r, true); - rt->UpdateDrawn(m_r, true); + if (t_size.y <= 0) + { + u32 new_height = m_r.w; + + if (possible_shuffle && std::abs(static_cast(GSLocalMemory::m_psm[rt->m_TEX0.PSM].bpp - GSLocalMemory::m_psm[TEX0.PSM].bpp)) == 16) + new_height /= 2; + //DevCon.Warning("Resizing texture %d x %d draw %d", rt->m_unscaled_size.x, new_height, s_n); + rt->ResizeTexture(rt->m_unscaled_size.x, new_height); + rt->UpdateValidity(m_r, true); + rt->UpdateDrawn(m_r, true); + } } } @@ -3000,74 +3076,6 @@ void GSRendererHW::Draw() m_last_channel_shuffle_end_block = 0xFFFF; } - GSTextureCache::Target* ds = nullptr; - GIFRegTEX0 ZBUF_TEX0; - if (!no_ds) - { - ZBUF_TEX0.U64 = 0; - ZBUF_TEX0.TBP0 = m_cached_ctx.ZBUF.Block(); - ZBUF_TEX0.TBW = m_cached_ctx.FRAME.FBW; - ZBUF_TEX0.PSM = m_cached_ctx.ZBUF.PSM; - - ds = g_texture_cache->LookupTarget(ZBUF_TEX0, t_size, target_scale, GSTextureCache::DepthStencil, - m_cached_ctx.DepthWrite(), 0, false, force_preload, preserve_depth, preserve_depth, unclamped_draw_rect, IsPossibleChannelShuffle(), is_possible_mem_clear && ZBUF_TEX0.TBP0 != m_cached_ctx.FRAME.Block()); - - ZBUF_TEX0.TBW = m_channel_shuffle ? src->m_from_target_TEX0.TBW : m_cached_ctx.FRAME.FBW; - - if (!ds) - { - ds = g_texture_cache->CreateTarget(ZBUF_TEX0, t_size, GetValidSize(src), target_scale, GSTextureCache::DepthStencil, - true, 0, false, force_preload, preserve_depth, m_r, src); - if (!ds) [[unlikely]] - { - GL_INS("ERROR: Failed to create ZBUF target, skipping."); - CleanupDraw(true); - return; - } - } - else - { - // If it failed to check depth test earlier, we can now check the top bits from the alpha to get a bit more accurate picture. - if (((zm && m_cached_ctx.TEST.ZTST > ZTST_ALWAYS) || (m_vt.m_eq.z && m_cached_ctx.TEST.ZTST == ZTST_GEQUAL)) && GSLocalMemory::m_psm[m_cached_ctx.ZBUF.PSM].trbpp == 32) - { - if (ds->m_alpha_max != 0) - { - const u32 max_z = (static_cast(ds->m_alpha_max + 1) << 24) - 1; - - switch (m_cached_ctx.TEST.ZTST) - { - case ZTST_GEQUAL: - // Every Z value will pass - if (max_z <= m_vt.m_min.p.z) - { - m_cached_ctx.TEST.ZTST = ZTST_ALWAYS; - if (zm) - { - ds = nullptr; - no_ds = true; - } - } - break; - case ZTST_GREATER: - // Every Z value will pass - if (max_z < m_vt.m_min.p.z) - { - m_cached_ctx.TEST.ZTST = ZTST_ALWAYS; - if (zm) - { - ds = nullptr; - no_ds = true; - } - } - break; - default: - break; - } - } - } - } - } - if (m_process_texture) { GIFRegCLAMP MIP_CLAMP = m_cached_ctx.CLAMP; @@ -4296,7 +4304,7 @@ __ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool // Performance GPU note: it could be wise to reduce the size to // the rendered size of the framebuffer - if (!m_in_target_draw && (GSConfig.UserHacks_TextureInsideRt == GSTextureInRtMode::Disabled || NextDrawMatchesShuffle())) + if (GSConfig.UserHacks_TextureInsideRt == GSTextureInRtMode::Disabled || (!m_in_target_draw && NextDrawMatchesShuffle())) { GSVertex* s = &m_vertex.buff[0]; s[0].XYZ.X = static_cast(m_context->XYOFFSET.OFX + 0); diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index d8135264e4..52d0abadf6 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -1800,7 +1800,8 @@ GSVector2i GSTextureCache::ScaleRenderTargetSize(const GSVector2i& sz, float sca } GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVector2i& size, float scale, int type, - bool used, u32 fbmask, bool is_frame, bool preload, bool preserve_rgb, bool preserve_alpha, const GSVector4i draw_rect, bool is_shuffle, bool possible_clear, bool preserve_scale, GSTextureCache::Source* src) + bool used, u32 fbmask, bool is_frame, bool preload, bool preserve_rgb, bool preserve_alpha, const GSVector4i draw_rect, + bool is_shuffle, bool possible_clear, bool preserve_scale, GSTextureCache::Source* src, int offset) { const GSLocalMemory::psm_t& psm_s = GSLocalMemory::m_psm[TEX0.PSM]; const u32 bp = TEX0.TBP0; @@ -1898,8 +1899,11 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe } } // Probably pointing to half way through the target - else if(GSConfig.UserHacks_TextureInsideRt >= GSTextureInRtMode::InsideTargets) + else if (GSConfig.UserHacks_TextureInsideRt >= GSTextureInRtMode::InsideTargets) { + if (offset != -1 && (bp - t->m_TEX0.TBP0) != offset) + continue; + const u32 widthpage_offset = (std::abs(static_cast(bp - t->m_TEX0.TBP0)) >> 5) % std::max(t->m_TEX0.TBW, 1U); const bool is_aligned_ok = widthpage_offset == 0 || ((widthpage_offset + TEX0.TBW) <= t->m_TEX0.TBW) || min_rect.width() <= 64 || (widthpage_offset == (t->m_TEX0.TBW >> 1) && (static_cast(min_rect.width()) <= (widthpage_offset * 64))); if ((!dst || ((GSState::s_n - dst->m_last_draw) < (GSState::s_n - t->m_last_draw))) && is_aligned_ok && (t->m_TEX0.TBW == TEX0.TBW || (TEX0.TBW == 1 && t->m_TEX0.TBW > 1)) && t->Inside(bp, TEX0.TBW, TEX0.PSM, min_rect)) @@ -2083,6 +2087,8 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe } else if (!is_shuffle && std::abs(static_cast(GSLocalMemory::m_psm[dst->m_TEX0.PSM].bpp - GSLocalMemory::m_psm[TEX0.PSM].bpp)) == 16) { + dst->Update(false); + const bool scale_down = GSLocalMemory::m_psm[dst->m_TEX0.PSM].bpp > GSLocalMemory::m_psm[TEX0.PSM].bpp; new_size = dst->m_unscaled_size; new_scaled_size = ScaleRenderTargetSize(dst->m_unscaled_size, scale); @@ -2340,6 +2346,8 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe dst->m_valid_alpha_high = dst_match->m_valid_alpha_high; //&& psm_s.trbpp != 24; dst->m_valid_rgb = dst_match->m_valid_rgb; dst->m_was_dst_matched = true; + dst_match->m_was_dst_matched = true; + dst_match->m_valid_rgb = false; if (GSLocalMemory::m_psm[dst->m_TEX0.PSM].bpp == 16 && GSLocalMemory::m_psm[dst_match->m_TEX0.PSM].bpp > 16) dst->m_TEX0.TBW = dst_match->m_TEX0.TBW; // Be careful of shuffles of the depth as C16, but using a buffer width of 16 (Mercenaries). @@ -3174,6 +3182,17 @@ void GSTextureCache::InvalidateContainedTargets(u32 start_bp, u32 end_bp, u32 wr continue; } + // Not covering the whole target, and a different format, so just dirty it. + if (start_bp == t->m_TEX0.TBP0 && (t->UnwrappedEndBlock() > end_bp) && write_psm != t->m_TEX0.PSM) + { + const GSLocalMemory::psm_t& target_psm = GSLocalMemory::m_psm[write_psm]; + u32 total_pages = (end_bp - t->m_TEX0.TBP0) >> 5; + GSVector4i dirty_area = GSVector4i(0, 0, t->m_valid.z, (total_pages / t->m_TEX0.TBW) * target_psm.pgs.y); + InvalidateVideoMem(g_gs_renderer->m_mem.GetOffset(t->m_TEX0.TBP0, t->m_TEX0.TBW, write_psm), dirty_area, true); + ++i; + continue; + } + InvalidateSourcesFromTarget(t); t->m_valid_alpha_low &= preserve_alpha; @@ -4588,9 +4607,9 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con } bool hack = false; - bool channel_shuffle = false; + bool channel_shuffle = dst && (TEX0.PSM == PSMT8) && (GSRendererHW::GetInstance()->TestChannelShuffle(dst)); - if (dst && (x_offset != 0 || y_offset != 0)) + if (dst && (x_offset != 0 || y_offset != 0) && (TEX0.PSM != PSMT8 || channel_shuffle)) { const float scale = dst->m_scale; const int x = static_cast(scale * x_offset); @@ -4653,8 +4672,6 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con src->m_texture = dst->m_texture; src->m_unscaled_size = dst->m_unscaled_size; src->m_shared_texture = true; - - channel_shuffle = GSRendererHW::GetInstance()->TestChannelShuffle(dst); } // Invalidate immediately on recursive draws, because if we don't here, InvalidateVideoMem() will. diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.h b/pcsx2/GS/Renderers/HW/GSTextureCache.h index 0c2f264b9e..024dfbbed6 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.h +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.h @@ -491,7 +491,7 @@ public: Target* FindTargetOverlap(Target* target, int type, int psm); Target* LookupTarget(GIFRegTEX0 TEX0, const GSVector2i& size, float scale, int type, bool used = true, u32 fbmask = 0, bool is_frame = false, bool preload = GSConfig.PreloadFrameWithGSData, bool preserve_rgb = true, bool preserve_alpha = true, - const GSVector4i draw_rc = GSVector4i::zero(), bool is_shuffle = false, bool possible_clear = false, bool preserve_scale = false, GSTextureCache::Source* src = nullptr); + const GSVector4i draw_rc = GSVector4i::zero(), bool is_shuffle = false, bool possible_clear = false, bool preserve_scale = false, GSTextureCache::Source* src = nullptr, int offset = -1); Target* CreateTarget(GIFRegTEX0 TEX0, const GSVector2i& size, const GSVector2i& valid_size,float scale, int type, bool used = true, u32 fbmask = 0, bool is_frame = false, bool preload = GSConfig.PreloadFrameWithGSData, bool preserve_target = true, const GSVector4i draw_rc = GSVector4i::zero(), GSTextureCache::Source* src = nullptr); From a547d10ab9a5fe21f77426a7152557c9e235b1e1 Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Wed, 1 Jan 2025 01:01:47 +0000 Subject: [PATCH 089/162] GS/HW: Further fixes for RT in RT changes in behaviour --- bin/resources/shaders/dx11/tfx.fx | 7 +- bin/resources/shaders/opengl/tfx_fs.glsl | 7 +- bin/resources/shaders/vulkan/tfx.glsl | 7 +- pcsx2/GS/GSState.cpp | 10 + pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 301 +++++++++++++++++++---- pcsx2/GS/Renderers/HW/GSTextureCache.cpp | 206 +++++++++++----- pcsx2/GS/Renderers/HW/GSTextureCache.h | 8 +- pcsx2/GS/Renderers/Metal/tfx.metal | 7 +- 8 files changed, 430 insertions(+), 123 deletions(-) diff --git a/bin/resources/shaders/dx11/tfx.fx b/bin/resources/shaders/dx11/tfx.fx index 717ae2549e..adae2af5fb 100644 --- a/bin/resources/shaders/dx11/tfx.fx +++ b/bin/resources/shaders/dx11/tfx.fx @@ -1127,11 +1127,8 @@ PS_OUTPUT ps_main(PS_INPUT input) { if (PS_PROCESS_BA == SHUFFLE_READWRITE && PS_PROCESS_RG == SHUFFLE_READWRITE) { - C.rb = C.br; - float g_temp = C.g; - - C.g = C.a; - C.a = g_temp; + C.b = C.r; + C.a = C.g; } else if(PS_PROCESS_BA & SHUFFLE_READ) { diff --git a/bin/resources/shaders/opengl/tfx_fs.glsl b/bin/resources/shaders/opengl/tfx_fs.glsl index b4857cd152..fc02634eff 100644 --- a/bin/resources/shaders/opengl/tfx_fs.glsl +++ b/bin/resources/shaders/opengl/tfx_fs.glsl @@ -1095,11 +1095,8 @@ void ps_main() C.ga = vec2(float((denorm_c.g >> 6) | ((denorm_c.b >> 3) << 2) | (denorm_TA.x & 0x80u))); #elif PS_SHUFFLE_ACROSS #if(PS_PROCESS_BA == SHUFFLE_READWRITE && PS_PROCESS_RG == SHUFFLE_READWRITE) - C.rb = C.br; - float g_temp = C.g; - - C.g = C.a; - C.a = g_temp; + C.b = C.r; + C.a = C.g; #elif(PS_PROCESS_BA & SHUFFLE_READ) C.rb = C.bb; C.ga = C.aa; diff --git a/bin/resources/shaders/vulkan/tfx.glsl b/bin/resources/shaders/vulkan/tfx.glsl index afbf95386e..d7639ffec5 100644 --- a/bin/resources/shaders/vulkan/tfx.glsl +++ b/bin/resources/shaders/vulkan/tfx.glsl @@ -1362,11 +1362,8 @@ void main() // Write RB part. Mask will take care of the correct destination #elif PS_SHUFFLE_ACROSS #if(PS_PROCESS_BA == SHUFFLE_READWRITE && PS_PROCESS_RG == SHUFFLE_READWRITE) - C.rb = C.br; - float g_temp = C.g; - - C.g = C.a; - C.a = g_temp; + C.b = C.r; + C.a = C.g; #elif(PS_PROCESS_BA & SHUFFLE_READ) C.rb = C.bb; C.ga = C.aa; diff --git a/pcsx2/GS/GSState.cpp b/pcsx2/GS/GSState.cpp index 4fa00fce05..f8cd974724 100644 --- a/pcsx2/GS/GSState.cpp +++ b/pcsx2/GS/GSState.cpp @@ -3095,6 +3095,16 @@ __forceinline bool GSState::IsAutoFlushDraw(u32 prim) if (!(GSUtil::GetChannelMask(m_context->TEX0.PSM) & GSUtil::GetChannelMask(m_context->FRAME.PSM, m_context->FRAME.FBMSK | ~(GSLocalMemory::m_psm[m_context->FRAME.PSM].fmsk)))) return false; + // Try to detect shuffles, because these will not autoflush, they by design clash. + if (GSLocalMemory::m_psm[m_context->FRAME.PSM].bpp == 16 && GSLocalMemory::m_psm[m_context->TEX0.PSM].bpp == 16) + { + // Pretty confident here... + GSVertex* buffer = &m_vertex.buff[0]; + const bool const_spacing = (buffer[m_index.buff[0]].U - buffer[m_index.buff[0]].XYZ.X) == (m_v.U - m_v.XYZ.X); + + if (const_spacing) + return false; + } const u32 frame_mask = GSLocalMemory::m_psm[m_context->FRAME.PSM].fmsk; const bool frame_hit = m_context->FRAME.Block() == m_context->TEX0.TBP0 && !(m_context->TEST.ATE && m_context->TEST.ATST == 0 && m_context->TEST.AFAIL == 2) && ((m_context->FRAME.FBMSK & frame_mask) != frame_mask); // There's a strange behaviour we need to test on a PS2 here, if the FRAME is a Z format, like Powerdrome something swaps over, and it seems Alpha Fail of "FB Only" writes to the Z.. it's odd. diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index 48914673dc..412ea83c3f 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -339,7 +339,7 @@ void GSRendererHW::ConvertSpriteTextureShuffle(u32& process_rg, u32& process_ba, tex_pos &= 0xFF; shuffle_across = (((tex_pos + 8) >> 4) ^ ((pos + 8) >> 4)) & 0x8; - const bool full_width = ((second_vert.XYZ.X - first_vert.XYZ.X) >> 4) >= 16 && m_r.width() > 8; + const bool full_width = !shuffle_across && (((second_vert.XYZ.X + 9) - first_vert.XYZ.X) >> 4) >= 16 && m_r.width() > 8; process_ba = ((pos > 112 && pos < 136) || full_width) ? SHUFFLE_WRITE : 0; process_rg = (!process_ba || full_width) ? SHUFFLE_WRITE : 0; // "same group" means it can read blue and write alpha using C32 tricks @@ -464,7 +464,7 @@ void GSRendererHW::ConvertSpriteTextureShuffle(u32& process_rg, u32& process_ba, GSVector4::storeh(&v[1].ST.S, st); } } - m_r = fpr; + m_r = r; m_vertex.head = m_vertex.tail = m_vertex.next = 2; m_index.tail = 2; return; @@ -1134,7 +1134,8 @@ bool GSRendererHW::IsPageCopy() const if (!PRIM->TME) return false; - const GSDrawingContext& next_ctx = m_env.CTXT[m_backed_up_ctx]; + const int get_next_ctx = (m_state_flush_reason == CONTEXTCHANGE) ? m_env.PRIM.CTXT : m_backed_up_ctx; + const GSDrawingContext& next_ctx = m_env.CTXT[get_next_ctx]; if (next_ctx.TEX0.TBP0 != (m_cached_ctx.TEX0.TBP0 + 0x20)) return false; @@ -2584,7 +2585,7 @@ void GSRendererHW::Draw() GIFRegTEX0 TEX0 = {}; GSTextureCache::Source* src = nullptr; TextureMinMaxResult tmm; - + bool possible_shuffle = false; // Disable texture mapping if the blend is black and using alpha from vertex. if (m_process_texture) { @@ -2701,7 +2702,7 @@ void GSRendererHW::Draw() GIFRegTEX0 FRAME_TEX0; bool shuffle_target = false; - if (!no_rt && GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].bpp == 16) + if (!no_rt && GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].bpp == 16 && GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].bpp >= 16) { if (m_cached_ctx.FRAME.Block() != m_cached_ctx.TEX0.TBP0) { @@ -2724,16 +2725,22 @@ void GSRendererHW::Draw() const GSVertex* v = &m_vertex.buff[0]; const int first_x = ((v[0].XYZ.X - m_context->XYOFFSET.OFX) + 8) >> 4; - const int first_u = PRIM->FST ? ((v[0].U + 8) >> 4) : static_cast(((1 << m_cached_ctx.TEX0.TW) * (v[0].ST.S / v[1].RGBAQ.Q)) + 0.5f); - const int second_u = PRIM->FST ? ((v[1].U + 8) >> 4) : static_cast(((1 << m_cached_ctx.TEX0.TW) * (v[1].ST.S / v[1].RGBAQ.Q)) + 0.5f); - const bool shuffle_coords = (first_x ^ first_u) & 8; - const int draw_width = std::abs(v[1].XYZ.X - v[0].XYZ.X) >> 4; + const int first_u = PRIM->FST ? ((v[0].U + 9) >> 4) : static_cast(((1 << m_cached_ctx.TEX0.TW) * (v[0].ST.S / v[1].RGBAQ.Q))); + const int second_u = PRIM->FST ? ((v[1].U + 9) >> 4) : static_cast(((1 << m_cached_ctx.TEX0.TW) * (v[1].ST.S / v[1].RGBAQ.Q)) + 0.6f); + // offset coordinates swap around RG/BA. (Ace Combat) + const u32 minv = m_cached_ctx.CLAMP.MINV; + const u32 minu = m_cached_ctx.CLAMP.MINU; + const bool rgba_shuffle = ((m_cached_ctx.CLAMP.WMS == m_cached_ctx.CLAMP.WMT && m_cached_ctx.CLAMP.WMS == CLAMP_REGION_REPEAT) && (minu && minv)); + const bool shuffle_coords = ((first_x ^ first_u) & 8) || rgba_shuffle; + // Round up half of second coord, it can sometimes be slightly under. + const int draw_width = std::abs(v[1].XYZ.X + 9 - v[0].XYZ.X) >> 4; const int read_width = std::abs(second_u - first_u); - shuffle_target = shuffle_coords && draw_width == 8 && draw_width == read_width; + shuffle_target = shuffle_coords && (draw_width & 7) == 0 && std::abs(draw_width - read_width) <= 1; } } - const bool possible_shuffle = !no_rt && (((shuffle_target && GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].bpp == 16) || (m_cached_ctx.FRAME.Block() == m_cached_ctx.TEX0.TBP0 && ((m_cached_ctx.TEX0.PSM & 0x6) || m_cached_ctx.FRAME.PSM != m_cached_ctx.TEX0.PSM))) || IsPossibleChannelShuffle()); + + possible_shuffle = !no_rt && (((shuffle_target /*&& GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].bpp == 16*/) /*|| (m_cached_ctx.FRAME.Block() == m_cached_ctx.TEX0.TBP0 && ((m_cached_ctx.TEX0.PSM & 0x6) || m_cached_ctx.FRAME.PSM != m_cached_ctx.TEX0.PSM))*/) || IsPossibleChannelShuffle()); const bool need_aem_color = GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].trbpp <= 24 && GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].pal == 0 && ((PRIM->ABE && m_context->ALPHA.C == 0) || IsDiscardingDstAlpha()) && m_draw_env->TEXA.AEM; const u32 color_mask = (m_vt.m_max.c > GSVector4i::zero()).mask(); const bool texture_function_color = m_cached_ctx.TEX0.TFX == TFX_DECAL || (color_mask & 0xFFF) || (m_cached_ctx.TEX0.TFX > TFX_DECAL && (color_mask & 0xF000)); @@ -2759,6 +2766,7 @@ void GSRendererHW::Draw() return; } + possible_shuffle &= src && (src->m_from_target != nullptr); // We don't know the alpha range of direct sources when we first tried to optimize the alpha test. // Moving the texture lookup before the ATST optimization complicates things a lot, so instead, // recompute it, and everything derived from it again if it changes. @@ -2885,7 +2893,7 @@ void GSRendererHW::Draw() ZBUF_TEX0.TBW = m_channel_shuffle ? src->m_from_target_TEX0.TBW : m_cached_ctx.FRAME.FBW; - if (!ds) + if (!ds && m_cached_ctx.FRAME.FBP != m_cached_ctx.ZBUF.ZBP) { ds = g_texture_cache->CreateTarget(ZBUF_TEX0, t_size, GetValidSize(src), target_scale, GSTextureCache::DepthStencil, true, 0, false, force_preload, preserve_depth, m_r, src); @@ -2941,21 +2949,34 @@ void GSRendererHW::Draw() if (!no_rt) { - const bool possible_shuffle = draw_sprite_tex && (((src && src->m_target && src->m_from_target && src->m_from_target->m_32_bits_fmt) && - GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].bpp == 16 && GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].bpp == 16) || + possible_shuffle |= draw_sprite_tex && m_primitive_covers_without_gaps != NoGapsType::FullCover && (((src && src->m_target && src->m_from_target && src->m_from_target->m_32_bits_fmt) && GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].bpp == 16) || IsPossibleChannelShuffle()); // FBW is going to be wrong for channel shuffling into a new target, so take it from the source. FRAME_TEX0.U64 = 0; FRAME_TEX0.TBP0 = ((m_last_channel_shuffle_end_block + 1) == m_cached_ctx.FRAME.Block() && possible_shuffle) ? m_last_channel_shuffle_fbp : m_cached_ctx.FRAME.Block(); - FRAME_TEX0.TBW = (possible_shuffle && (m_last_channel_shuffle_end_block + 1) && src->m_target) ? src->m_from_target_TEX0.TBW : m_cached_ctx.FRAME.FBW; + FRAME_TEX0.TBW = (possible_shuffle && IsPossibleChannelShuffle() && src && src->m_from_target) ? src->m_from_target_TEX0.TBW : m_cached_ctx.FRAME.FBW; FRAME_TEX0.PSM = m_cached_ctx.FRAME.PSM; - // Don't clamp on shuffle, the height cache may troll us with the REAL height. if (!possible_shuffle && m_split_texture_shuffle_pages == 0) m_r = m_r.rintersect(t_size_rect); + // Do the lookup with the real format on a shuffle, if possible. + if (possible_shuffle && GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].bpp == 16 && GSLocalMemory ::m_psm[m_cached_ctx.FRAME.PSM].bpp == 16) + { + // Creating a new target on a shuffle, possible temp buffer, but let's try to get the real format. + const int get_next_ctx = (m_state_flush_reason == CONTEXTCHANGE) ? m_env.PRIM.CTXT : m_backed_up_ctx; + const GSDrawingContext& next_ctx = m_env.CTXT[get_next_ctx]; + + if (next_ctx.FRAME.Block() == FRAME_TEX0.TBP0 && next_ctx.FRAME.PSM != FRAME_TEX0.PSM) + FRAME_TEX0.PSM = next_ctx.FRAME.PSM; + else if (next_ctx.TEX0.TBP0 == FRAME_TEX0.TBP0 && next_ctx.TEX0.PSM != FRAME_TEX0.PSM) + FRAME_TEX0.PSM = next_ctx.TEX0.PSM; + else + FRAME_TEX0.PSM = PSMCT32; // Guess full color if no upcoming hint, it'll fix itself later. + } + // Normally we would use 1024 here to match the clear above, but The Godfather does a 1023x1023 draw instead // (very close to 1024x1024, but apparently the GS rounds down..). So, catch that here, we don't want to // create that target, because the clear isn't black, it'll hang around and never get invalidated. @@ -2968,7 +2989,7 @@ void GSRendererHW::Draw() rt = g_texture_cache->LookupTarget(FRAME_TEX0, t_size, ((src && src->m_scale != 1) && GSConfig.UserHacks_NativeScaling == GSNativeScaling::Normal && !possible_shuffle) ? GetTextureScaleFactor() : target_scale, GSTextureCache::RenderTarget, true, fm, false, force_preload, preserve_rt_rgb, preserve_rt_alpha, unclamped_draw_rect, possible_shuffle, is_possible_mem_clear && FRAME_TEX0.TBP0 != m_cached_ctx.ZBUF.Block(), - GSConfig.UserHacks_NativeScaling != GSNativeScaling::Off && preserve_downscale_draw && is_possible_mem_clear != ClearType::NormalClear, src, no_ds ? -1 : (m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0)); + GSConfig.UserHacks_NativeScaling != GSNativeScaling::Off && preserve_downscale_draw && is_possible_mem_clear != ClearType::NormalClear, src, (no_ds || !ds) ? -1 : (m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0)); // Draw skipped because it was a clear and there was no target. if (!rt) @@ -3005,13 +3026,68 @@ void GSRendererHW::Draw() } else if (rt->m_TEX0.TBP0 != m_cached_ctx.FRAME.Block()) { - GSVertex* v = &m_vertex.buff[0]; - int vertical_offset = ((std::abs(static_cast(m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0)) >> 5) / std::max(rt->m_TEX0.TBW, 1U)) * frame_psm.pgs.y; // I know I could just not shift it.. + int vertical_offset = ((static_cast(m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0) / 32) / std::max(static_cast(rt->m_TEX0.TBW), 1)) * frame_psm.pgs.y; // I know I could just not shift it.. - const int horizontal_offset = (std::abs(static_cast((m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0)) >> 5) % std::max(rt->m_TEX0.TBW, 1U)) * frame_psm.pgs.x; + const int horizontal_offset = ((static_cast((m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0)) / 32) % static_cast(std::max(rt->m_TEX0.TBW, 1U))) * frame_psm.pgs.x; // Used to reduce the offset made later in channel shuffles m_target_offset = std::abs(static_cast((m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0)) >> 5); + if (vertical_offset < 0) + { + rt->m_TEX0.TBP0 = m_cached_ctx.FRAME.Block(); + GSVector2i new_scaled_size = rt->m_unscaled_size * rt->m_scale; + // Make sure to use the original format for the offset. + int new_offset = std::abs((vertical_offset / frame_psm.pgs.y) * GSLocalMemory::m_psm[rt->m_TEX0.PSM].pgs.y); + + new_scaled_size.y += new_offset * rt->m_scale; + GSTexture* tex = g_gs_device->CreateRenderTarget(new_scaled_size.x, new_scaled_size.y, GSTexture::Format::Color, true); + //if (!tex) + // return nullptr; + //m_target_memory_usage += tex->GetMemUsage(); + GSVector4i dRect = GSVector4i(0, new_offset * rt->m_scale, new_scaled_size.x, new_scaled_size.y); + g_gs_device->StretchRect(rt->m_texture, GSVector4(0,0,1,1), tex, GSVector4(dRect), ShaderConvert::COPY, false); + + + if (src && src->m_from_target && src->m_from_target == rt) + { + src->m_texture = rt->m_texture; + src->m_target_direct = false; + src->m_shared_texture = false; + } + else + { + //m_target_memory_usage -= dst->m_texture->GetMemUsage(); + g_gs_device->Recycle(rt->m_texture); + } + + rt->m_valid.y += new_offset; + rt->m_valid.w += new_offset; + rt->m_drawn_since_read.y += new_offset; + rt->m_drawn_since_read.w += new_offset; + rt->m_texture = tex; + rt->m_unscaled_size = new_scaled_size / rt->m_scale; + + t_size.y += std::abs(vertical_offset); + vertical_offset = 0; + } + + // Z isn't offset but RT is, so we need a temp Z to align it, hopefully nothing will ever write to the Z too, right?? + if (ds && vertical_offset && (m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0) != (m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0)) + { + + int z_vertical_offset = ((static_cast(m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0) / 32) / std::max(rt->m_TEX0.TBW, 1U)) * GSLocalMemory::m_psm[m_cached_ctx.ZBUF.PSM].pgs.y; + int z_offset = vertical_offset; + GL_CACHE("RT in RT Z copy on draw %d z_vert_offset %d z_offset %d", s_n, z_vertical_offset, z_offset); + GSVector4i dRect = GSVector4i(0, z_offset * ds->m_scale, ds->m_unscaled_size.x * ds->m_scale, std::min(z_offset + m_r.w + 1, z_offset + ds->m_unscaled_size.y) * ds->m_scale); + int new_height = std::max(static_cast(ds->m_unscaled_size.y * ds->m_scale), dRect.w); + GSTexture* tex = g_gs_device->CreateDepthStencil(ds->m_unscaled_size.x * ds->m_scale, new_height, GSTexture::Format::DepthStencil, true); + g_gs_device->StretchRect(ds->m_texture, GSVector4(0.0f, z_vertical_offset / static_cast(ds->m_unscaled_size.y), 1.0f, std::min(z_vertical_offset + m_r.w + 1, ds->m_unscaled_size.y) / static_cast(ds->m_unscaled_size.y)), tex, GSVector4(dRect), ShaderConvert::DEPTH_COPY, false); + + g_texture_cache->SetTemporaryZ(tex); + } + + GSVertex* v = &m_vertex.buff[0]; + for (u32 i = 0; i < m_vertex.tail; i++) { v[i].XYZ.Y += vertical_offset << 4; @@ -3038,7 +3114,7 @@ void GSRendererHW::Draw() // Don't resize if the BPP don't match. if (frame_psm.bpp == GSLocalMemory::m_psm[rt->m_TEX0.PSM].bpp) { - if (t_size.y <= 0) + if (m_r.w > rt->m_unscaled_size.y) { u32 new_height = m_r.w; @@ -3046,8 +3122,11 @@ void GSRendererHW::Draw() new_height /= 2; //DevCon.Warning("Resizing texture %d x %d draw %d", rt->m_unscaled_size.x, new_height, s_n); rt->ResizeTexture(rt->m_unscaled_size.x, new_height); - rt->UpdateValidity(m_r, true); - rt->UpdateDrawn(m_r, true); + + const bool frame_masked = ((m_cached_ctx.FRAME.FBMSK & frame_psm.fmsk) == frame_psm.fmsk) || (m_cached_ctx.TEST.ATE && m_cached_ctx.TEST.ATST == ATST_NEVER && !(m_cached_ctx.TEST.AFAIL & AFAIL_FB_ONLY)); + + rt->UpdateValidity(m_r, !frame_masked); + rt->UpdateDrawn(m_r, !frame_masked); } } } @@ -3076,6 +3155,75 @@ void GSRendererHW::Draw() m_last_channel_shuffle_end_block = 0xFFFF; } + // Only run if DS was new and matched the framebuffer. + if (!no_ds && !ds) + { + ZBUF_TEX0.U64 = 0; + ZBUF_TEX0.TBP0 = m_cached_ctx.ZBUF.Block(); + ZBUF_TEX0.TBW = m_cached_ctx.FRAME.FBW; + ZBUF_TEX0.PSM = m_cached_ctx.ZBUF.PSM; + + ds = g_texture_cache->LookupTarget(ZBUF_TEX0, t_size, target_scale, GSTextureCache::DepthStencil, + m_cached_ctx.DepthWrite(), 0, false, force_preload, preserve_depth, preserve_depth, unclamped_draw_rect, IsPossibleChannelShuffle(), is_possible_mem_clear && ZBUF_TEX0.TBP0 != m_cached_ctx.FRAME.Block(), false, + src, -1); + + ZBUF_TEX0.TBW = m_channel_shuffle ? src->m_from_target_TEX0.TBW : m_cached_ctx.FRAME.FBW; + + // This should never happen, but just to be safe.. + if (!ds) + { + ds = g_texture_cache->CreateTarget(ZBUF_TEX0, t_size, GetValidSize(src), target_scale, GSTextureCache::DepthStencil, + true, 0, false, force_preload, preserve_depth, m_r, src); + if (!ds) [[unlikely]] + { + GL_INS("ERROR: Failed to create ZBUF target, skipping."); + CleanupDraw(true); + return; + } + } + else + { + // If it failed to check depth test earlier, we can now check the top bits from the alpha to get a bit more accurate picture. + if (((zm && m_cached_ctx.TEST.ZTST > ZTST_ALWAYS) || (m_vt.m_eq.z && m_cached_ctx.TEST.ZTST == ZTST_GEQUAL)) && GSLocalMemory::m_psm[m_cached_ctx.ZBUF.PSM].trbpp == 32) + { + if (ds->m_alpha_max != 0) + { + const u32 max_z = (static_cast(ds->m_alpha_max + 1) << 24) - 1; + + switch (m_cached_ctx.TEST.ZTST) + { + case ZTST_GEQUAL: + // Every Z value will pass + if (max_z <= m_vt.m_min.p.z) + { + m_cached_ctx.TEST.ZTST = ZTST_ALWAYS; + if (zm) + { + ds = nullptr; + no_ds = true; + } + } + break; + case ZTST_GREATER: + // Every Z value will pass + if (max_z < m_vt.m_min.p.z) + { + m_cached_ctx.TEST.ZTST = ZTST_ALWAYS; + if (zm) + { + ds = nullptr; + no_ds = true; + } + } + break; + default: + break; + } + } + } + } + } + if (m_process_texture) { GIFRegCLAMP MIP_CLAMP = m_cached_ctx.CLAMP; @@ -3089,7 +3237,8 @@ void GSRendererHW::Draw() const int first_u = PRIM->FST ? ((v[0].U + 8) >> 4) : static_cast(((1 << m_cached_ctx.TEX0.TW) * (v[0].ST.S / v[1].RGBAQ.Q)) + 0.5f); const bool shuffle_coords = (first_x ^ first_u) & 8; const u32 draw_end = GSLocalMemory::GetEndBlockAddress(m_cached_ctx.FRAME.Block(), m_cached_ctx.FRAME.FBW, m_cached_ctx.FRAME.PSM, m_r) + 1; - const bool draw_uses_target = src->m_from_target && ((src->m_from_target_TEX0.TBP0 <= m_cached_ctx.FRAME.Block() && + const u32 draw_start = GSLocalMemory::GetStartBlockAddress(m_cached_ctx.FRAME.Block(), m_cached_ctx.FRAME.FBW, m_cached_ctx.FRAME.PSM, m_r); + const bool draw_uses_target = src->m_from_target && ((src->m_from_target_TEX0.TBP0 <= draw_start && src->m_from_target->UnwrappedEndBlock() > m_cached_ctx.FRAME.Block()) || (m_cached_ctx.FRAME.Block() < src->m_from_target_TEX0.TBP0 && draw_end > src->m_from_target_TEX0.TBP0)); @@ -3330,8 +3479,8 @@ void GSRendererHW::Draw() // The FBW should also be okay, since it's coming from the source. if (rt) { - const bool update_fbw = rt->m_last_draw == s_n && (m_channel_shuffle && src->m_target) && (!PRIM->ABE || IsOpaque() || m_context->ALPHA.IsBlack()); - rt->m_TEX0.TBW = update_fbw ? FRAME_TEX0.TBW : std::max(rt->m_TEX0.TBW, FRAME_TEX0.TBW); + const bool update_fbw = !m_in_target_draw && (m_channel_shuffle && src->m_target) && (!PRIM->ABE || IsOpaque() || m_context->ALPHA.IsBlack()); + rt->m_TEX0.TBW = update_fbw ? ((src && src->m_from_target && src->m_32_bits_fmt) ? src->m_from_target->m_TEX0.TBW : FRAME_TEX0.TBW) : std::max(rt->m_TEX0.TBW, FRAME_TEX0.TBW); rt->m_TEX0.PSM = FRAME_TEX0.PSM; } if (ds) @@ -3340,6 +3489,11 @@ void GSRendererHW::Draw() ds->m_TEX0.PSM = ZBUF_TEX0.PSM; } } + // Probably grabbed an old 16bit target (Band Hero) + /*else if (m_texture_shuffle && GSLocalMemory::m_psm[rt->m_TEX0.PSM].bpp == 16) + { + rt->m_TEX0.PSM = PSMCT32; + }*/ // Figure out which channels we're writing. if (rt) @@ -3357,7 +3511,7 @@ void GSRendererHW::Draw() GSVector2i new_size = t_size; // We need to adjust the size if it's a texture shuffle as we could end up making the RT twice the size. - if (src && m_texture_shuffle && m_split_texture_shuffle_pages == 0) + if (src && m_texture_shuffle && !m_copy_16bit_to_target_shuffle && m_split_texture_shuffle_pages == 0) { if ((new_size.x > src->m_valid_rect.z && m_vt.m_max.p.x == new_size.x) || (new_size.y > src->m_valid_rect.w && m_vt.m_max.p.y == new_size.y)) { @@ -3368,9 +3522,18 @@ void GSRendererHW::Draw() } } + if (m_in_target_draw && src && m_channel_shuffle && src->m_from_target && src->m_from_target == rt && m_cached_ctx.TEX0.TBP0 == src->m_from_target->m_TEX0.TBP0) + { + new_size.y = std::max(new_size.y, static_cast((((m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0) >> 5) / rt->m_TEX0.TBW) * frame_psm.pgs.y) * 2); + GSVector4i new_valid = rt->m_valid; + new_valid.w = new_size.y; + rt->UpdateValidity(new_valid, true); + } + // We still need to make sure the dimensions of the targets match. - const int new_w = std::max(new_size.x, std::max(rt ? rt->m_unscaled_size.x : 0, ds ? ds->m_unscaled_size.x : 0)); - const int new_h = std::max(new_size.y, std::max(rt ? rt->m_unscaled_size.y : 0, ds ? ds->m_unscaled_size.y : 0)); + // Limit new size to 2048, the GS can't address more than this so may avoid some bugs/crashes. + const int new_w = std::min(2048, std::max(new_size.x, std::max(rt ? rt->m_unscaled_size.x : 0, ds ? ds->m_unscaled_size.x : 0))); + const int new_h = std::min(2048, std::max(new_size.y, std::max(rt ? rt->m_unscaled_size.y : 0, ds ? ds->m_unscaled_size.y : 0))); if (rt) { const u32 old_end_block = rt->m_end_block; @@ -3382,6 +3545,25 @@ void GSRendererHW::Draw() if (rt->GetUnscaledWidth() != new_w || rt->GetUnscaledHeight() != new_h) GL_INS("Resize RT from %dx%d to %dx%d", rt->GetUnscaledWidth(), rt->GetUnscaledHeight(), new_w, new_h); + // May not be needed/could cause problems with garbage loaded from GS memory + if (preserve_rt_color) + { + RGBAMask mask; + mask._u32 = 0xF; + + if (new_w > rt->m_unscaled_size.x) + { + GSVector4i width_dirty_rect = GSVector4i(rt->m_unscaled_size.x, 0, new_w, new_h); + g_texture_cache->AddDirtyRectTarget(rt, width_dirty_rect, rt->m_TEX0.PSM, rt->m_TEX0.TBW, mask); + } + + if (new_h > rt->m_unscaled_size.y) + { + GSVector4i height_dirty_rect = GSVector4i(0, rt->m_unscaled_size.y, new_w, new_h); + g_texture_cache->AddDirtyRectTarget(rt, height_dirty_rect, rt->m_TEX0.PSM, rt->m_TEX0.TBW, mask); + } + } + rt->ResizeTexture(new_w, new_h); if (!m_texture_shuffle && !m_channel_shuffle) @@ -3401,9 +3583,11 @@ void GSRendererHW::Draw() } const GSVector4i update_rect = m_r.rintersect(GSVector4i::loadh(GSVector2i(new_w, new_h))); + // if frame is masked or afailing always to never write frame, wanna make sure we don't touch it. This might happen if DATE or Alpha Test is being used to write to Z. + const bool frame_masked = ((m_cached_ctx.FRAME.FBMSK & frame_psm.fmsk) == frame_psm.fmsk) || (m_cached_ctx.TEST.ATE && m_cached_ctx.TEST.ATST == ATST_NEVER && !(m_cached_ctx.TEST.AFAIL & AFAIL_FB_ONLY)); // Limit to 2x the vertical height of the resolution (for double buffering) - rt->UpdateValidity(update_rect, can_update_size || (m_r.w <= (resolution.y * 2) && !m_texture_shuffle)); - rt->UpdateDrawn(update_rect, can_update_size || (m_r.w <= (resolution.y * 2) && !m_texture_shuffle)); + rt->UpdateValidity(update_rect, !frame_masked && (can_update_size || (m_r.w <= (resolution.y * 2) && !m_texture_shuffle))); + rt->UpdateDrawn(update_rect, !frame_masked && (can_update_size || (m_r.w <= (resolution.y * 2) && !m_texture_shuffle))); // Probably changing to double buffering, so invalidate any old target that was next to it. // This resolves an issue where the PCRTC will find the old target in FMV's causing flashing. // Grandia Xtreme, Onimusha Warlord. @@ -3433,7 +3617,7 @@ void GSRendererHW::Draw() const bool new_rect = ds->m_valid.rempty(); const bool new_height = new_h > ds->GetUnscaledHeight(); const int old_height = ds->m_texture->GetHeight(); - + const GSVector4i old_rect = ds->GetUnscaledRect(); pxAssert(ds->GetScale() == target_scale); if (ds->GetUnscaledWidth() != new_w || ds->GetUnscaledHeight() != new_h) GL_INS("Resize DS from %dx%d to %dx%d", ds->GetUnscaledWidth(), ds->GetUnscaledHeight(), new_w, new_h); @@ -3446,8 +3630,12 @@ void GSRendererHW::Draw() } // Limit to 2x the vertical height of the resolution (for double buffering) - ds->UpdateValidity(m_r, can_update_size || m_r.w <= (resolution.y * 2)); - ds->UpdateDrawn(m_r, can_update_size || m_r.w <= (resolution.y * 2)); + // Dark cloud writes to 424 when the buffer is only 416 high, but masks the Z. + // Updating the valid causes the Z to overlap the framebuffer, which is obviously incorrect. + const bool z_masked = m_cached_ctx.ZBUF.ZMSK; + + ds->UpdateValidity(m_r, !z_masked && (can_update_size || m_r.w <= (resolution.y * 2))); + ds->UpdateDrawn(m_r, !z_masked && (can_update_size || m_r.w <= (resolution.y * 2))); if (!new_rect && new_height && old_end_block != ds->m_end_block) { @@ -3546,7 +3734,9 @@ void GSRendererHW::Draw() { s = GetDrawDumpPath("%05d_f%05lld_rz0_%05x_%s.bmp", s_n, frame, m_cached_ctx.ZBUF.Block(), psm_str(m_cached_ctx.ZBUF.PSM)); - if (ds->m_texture) + if (g_texture_cache->GetTemporaryZ()) + g_texture_cache->GetTemporaryZ()->Save(s); + else if (ds->m_texture) ds->m_texture->Save(s); } } @@ -3635,9 +3825,10 @@ void GSRendererHW::Draw() if ((fm & fm_mask) != fm_mask && rt) { + const bool frame_masked = ((m_cached_ctx.FRAME.FBMSK & frame_psm.fmsk) == frame_psm.fmsk) || (m_cached_ctx.TEST.ATE && m_cached_ctx.TEST.ATST == ATST_NEVER && !(m_cached_ctx.TEST.AFAIL & AFAIL_FB_ONLY)); //rt->m_valid = rt->m_valid.runion(r); // Limit to 2x the vertical height of the resolution (for double buffering) - rt->UpdateValidity(real_rect, can_update_size || (real_rect.w <= (resolution.y * 2) && !m_texture_shuffle)); + rt->UpdateValidity(real_rect, !frame_masked && (can_update_size || (real_rect.w <= (resolution.y * 2) && !m_texture_shuffle))); g_texture_cache->InvalidateVideoMem(context->offset.fb, real_rect, false); @@ -3648,15 +3839,31 @@ void GSRendererHW::Draw() if (zm != 0xffffffff && ds) { + const bool z_masked = m_cached_ctx.ZBUF.ZMSK; + //ds->m_valid = ds->m_valid.runion(r); // Limit to 2x the vertical height of the resolution (for double buffering) - ds->UpdateValidity(real_rect, can_update_size || (real_rect.w <= (resolution.y * 2) && !m_texture_shuffle)); + ds->UpdateValidity(real_rect, !z_masked && (can_update_size || (real_rect.w <= (resolution.y * 2) && !m_texture_shuffle))); g_texture_cache->InvalidateVideoMem(context->offset.zb, real_rect, false); // Remove overwritten RTs at the ZBP. g_texture_cache->InvalidateVideoMemType( GSTextureCache::RenderTarget, m_cached_ctx.ZBUF.Block(), m_cached_ctx.ZBUF.PSM, zm); + + + if (g_texture_cache->GetTemporaryZ()) + { + if (m_cached_ctx.DepthWrite()) + { + int vertical_offset = ((static_cast(m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0) / 32) / std::max(static_cast(rt->m_TEX0.TBW), 1)) * frame_psm.pgs.y; + int z_vertical_offset = ((static_cast(m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0) / 32) / std::max(rt->m_TEX0.TBW, 1U)) * GSLocalMemory::m_psm[m_cached_ctx.ZBUF.PSM].pgs.y; + int z_offset = vertical_offset; + GL_CACHE("RT in RT Z copy back draw %d z_vert_offset %d z_offset %d", s_n, z_vertical_offset, z_offset); + GSVector4i dRect = GSVector4i(0, z_vertical_offset * ds->m_scale, ds->m_unscaled_size.x * ds->m_scale, std::min(z_vertical_offset + m_r.w + 1 - vertical_offset, ds->m_unscaled_size.y) * ds->m_scale); + g_gs_device->StretchRect(g_texture_cache->GetTemporaryZ(), GSVector4(0.0f, z_offset / static_cast(g_texture_cache->GetTemporaryZ()->GetHeight()), 1.0f, std::min(real_rect.w + 1, ds->m_unscaled_size.y + z_offset) / static_cast(g_texture_cache->GetTemporaryZ()->GetHeight())), ds->m_texture, GSVector4(dRect), ShaderConvert::DEPTH_COPY, false); + } + } } if (GSConfig.ShouldDump(s_n, g_perfmon.GetFrame())) @@ -4147,7 +4354,7 @@ __ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool m_conf.ps.urban_chaos_hle = 1; } } - else if (m_index.tail < 64 && m_cached_ctx.CLAMP.WMT == 3) + else if (m_index.tail <= 64 && !IsPageCopy() && m_cached_ctx.CLAMP.WMT == 3) { // Blood will tell. I think it is channel effect too but again // implemented in a different way. I don't want to add more CRC stuff. So @@ -4304,7 +4511,7 @@ __ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool // Performance GPU note: it could be wise to reduce the size to // the rendered size of the framebuffer - if (GSConfig.UserHacks_TextureInsideRt == GSTextureInRtMode::Disabled || (!m_in_target_draw && NextDrawMatchesShuffle())) + if (GSConfig.UserHacks_TextureInsideRt == GSTextureInRtMode::Disabled || (!m_in_target_draw && IsPageCopy())) { GSVertex* s = &m_vertex.buff[0]; s[0].XYZ.X = static_cast(m_context->XYOFFSET.OFX + 0); @@ -5809,6 +6016,13 @@ bool GSRendererHW::CanUseTexIsFB(const GSTextureCache::Target* rt, const GSTextu return false; } + // the texture is offset, and the frame isn't also offset, we can't do this. + if (tex->GetRegion().HasX() || tex->GetRegion().HasY()) + { + if (m_cached_ctx.FRAME.Block() != m_cached_ctx.TEX0.TBP0) + return false; + } + // If we're a shuffle, tex-is-fb is always fine. if (m_texture_shuffle || m_channel_shuffle) { @@ -5958,6 +6172,7 @@ void GSRendererHW::CleanupDraw(bool invalidate_temp_src) if (invalidate_temp_src) g_texture_cache->InvalidateTemporarySource(); + g_texture_cache->InvalidateTemporaryZ(); // Restore Scissor. m_context->UpdateScissor(); @@ -5997,7 +6212,7 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta m_conf.cb_vs.texture_offset = {}; m_conf.ps.scanmsk = env.SCANMSK.MSK; m_conf.rt = rt ? rt->m_texture : nullptr; - m_conf.ds = ds ? ds->m_texture : nullptr; + m_conf.ds = ds ? (g_texture_cache->GetTemporaryZ() ? g_texture_cache->GetTemporaryZ() : ds->m_texture) : nullptr; // Z setup has to come before channel shuffle EmulateZbuffer(ds); @@ -6371,7 +6586,7 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta const bool full_cover = rt->m_valid.rintersect(m_r).eq(rt->m_valid) && m_primitive_covers_without_gaps == NoGapsType::FullCover && !(DATE || !always_passing_alpha || !IsDepthAlwaysPassing()); // Restrict this to only when we're overwriting the whole target. - new_scale_rt_alpha = full_cover; + new_scale_rt_alpha = full_cover || rt->m_last_draw >= s_n; } } @@ -7489,7 +7704,7 @@ bool GSRendererHW::TryGSMemClear(bool no_rt, bool preserve_rt, bool invalidate_r g_texture_cache->InvalidateContainedTargets( GSLocalMemory::GetStartBlockAddress( m_cached_ctx.FRAME.Block(), m_cached_ctx.FRAME.FBW, m_cached_ctx.FRAME.PSM, m_r), - rt_end_bp, m_cached_ctx.FRAME.PSM); + rt_end_bp, m_cached_ctx.FRAME.PSM, m_cached_ctx.FRAME.FBW); GSUploadQueue clear_queue; clear_queue.draw = s_n; @@ -7512,7 +7727,7 @@ bool GSRendererHW::TryGSMemClear(bool no_rt, bool preserve_rt, bool invalidate_r g_texture_cache->InvalidateContainedTargets( GSLocalMemory::GetStartBlockAddress( m_cached_ctx.ZBUF.Block(), m_cached_ctx.FRAME.FBW, m_cached_ctx.ZBUF.PSM, m_r), - ds_end_bp, m_cached_ctx.ZBUF.PSM); + ds_end_bp, m_cached_ctx.ZBUF.PSM, m_cached_ctx.FRAME.FBW); } } diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index 52d0abadf6..e76198408d 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -149,7 +149,8 @@ void GSTextureCache::AddDirtyRectTarget(Target* target, GSVector4i rect, u32 psm if (rect.rempty()) return; - + if (rect.w > 2048) + DevCon.Warning("BAd"); std::vector::iterator it = target->m_dirty.end(); while (it != target->m_dirty.begin()) { @@ -274,6 +275,15 @@ GSVector4i GSTextureCache::TranslateAlignedRectByPage(u32 tbp, u32 tebp, u32 tbw const int inc_horizontal_offset = (page_offset % src_pgw) * src_page_size.x; in_rect = (in_rect + GSVector4i(0, inc_vertical_offset).xyxy()).max_i32(GSVector4i(0)); in_rect = (in_rect + GSVector4i(inc_horizontal_offset, 0).xyxy()).max_i32(GSVector4i(0)); + + // Project Snowblind and Tomb Raider access the rect offset by 1 page and use a region to correct it, we need to account for that here. + if (in_rect.x >= (dst_pgw * dst_page_size.x)) + { + in_rect.z -= dst_pgw * dst_page_size.x; + in_rect.x -= dst_pgw * dst_page_size.x; + in_rect.y += dst_page_size.y; + in_rect.w += dst_page_size.y; + } page_offset = 0; single_page = (in_rect.width() / src_page_size.x) <= 1 && (in_rect.height() / src_page_size.y) <= 1; } @@ -1448,8 +1458,8 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const // Make sure the texture actually is INSIDE the RT, it's possibly not valid if it isn't. // Also check BP >= TBP, create source isn't equpped to expand it backwards and all data comes from the target. (GH3) else if (GSConfig.UserHacks_TextureInsideRt >= GSTextureInRtMode::InsideTargets && - (GSLocalMemory::m_psm[color_psm].bpp >= 16 || (possible_shuffle && GSLocalMemory::m_psm[color_psm].bpp == 8 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == 32)) && // Channel shuffles or non indexed lookups. - t->m_age <= 1 && (!found_t || t->m_last_draw > dst->m_last_draw) && CanTranslate(bp, bw, psm, block_boundary_rect, t->m_TEX0.TBP0, t->m_TEX0.PSM, t->m_TEX0.TBW)) + (GSLocalMemory::m_psm[color_psm].bpp >= 16 || (/*possible_shuffle &&*/ GSLocalMemory::m_psm[color_psm].bpp == 8 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == 32 && t->m_TEX0.TBW >= (bw * 2))) && // Channel shuffles or non indexed lookups. + t->m_age <= 1 && (!found_t || t->m_last_draw > dst->m_last_draw) /*&& CanTranslate(bp, bw, psm, block_boundary_rect, t->m_TEX0.TBP0, t->m_TEX0.PSM, t->m_TEX0.TBW)*/) { if (!t->HasValidBitsForFormat(psm, req_color, req_alpha) && !(possible_shuffle && GSLocalMemory::m_psm[psm].bpp == 16 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == 32)) @@ -1481,7 +1491,7 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const } if (bp > t->m_TEX0.TBP0) { - GSVector4i new_rect = possible_shuffle ? block_boundary_rect : rect; + GSVector4i new_rect = (GSLocalMemory::m_psm[color_psm].bpp != GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp) ? block_boundary_rect : rect; if (linear) { new_rect.z -= 1; @@ -1586,15 +1596,18 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const // Omitting that check here seemed less risky than blowing CS targets out... const GSVector2i& page_size = GSLocalMemory::m_psm[src_psm].pgs; const GSOffset offset(GSLocalMemory::m_psm[src_psm].info, bp, bw, psm); + const u32 offset_bp = offset.bn(region.GetMinX(), region.GetMinY()); if (bp < t->m_TEX0.TBP0 && region.HasX() && region.HasY() && (region.GetMinX() & (page_size.x - 1)) == 0 && (region.GetMinY() & (page_size.y - 1)) == 0 && - offset.bn(region.GetMinX(), region.GetMinY()) == t->m_TEX0.TBP0) + (offset.bn(region.GetMinX(), region.GetMinY()) == t->m_TEX0.TBP0 || + (offset_bp >= t->m_TEX0.TBP0) && ((((offset_bp - t->m_TEX0.TBP0) >> 5) % bw) + (rect.width() / page_size.x)) <= bw)) { GL_CACHE("TC: Target 0x%x detected in front of TBP 0x%x with %d,%d offset (%d pages)", t->m_TEX0.TBP0, TEX0.TBP0, region.GetMinX(), region.GetMinY(), (region.GetMinY() / page_size.y) * TEX0.TBW + (region.GetMinX() / page_size.x)); - x_offset = -region.GetMinX(); - y_offset = -region.GetMinY(); + + x_offset = ((((offset_bp - t->m_TEX0.TBP0) >> 5) % bw) * page_size.x) - region.GetMinX(); + y_offset = ((((offset_bp - t->m_TEX0.TBP0) >> 5) / bw) * page_size.y) - region.GetMinY(); dst = t; tex_merge_rt = false; found_t = true; @@ -1827,7 +1840,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe // TODO: Move all frame stuff to its own routine too. if (!is_frame) { - for (auto i = list.begin(); i != list.end(); ++i) + for (auto i = list.begin(); i != list.end();) { Target* t = *i; @@ -1838,6 +1851,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe if (dst && (GSState::s_n - dst->m_last_draw) < (GSState::s_n - t->m_last_draw)) { DevCon.Warning("Ignoring target at %x as one at %x is newer", t->m_TEX0.TBP0, dst->m_TEX0.TBP0); + i++; continue; } @@ -1896,21 +1910,26 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe InvalidateSourcesFromTarget(t); i = list.erase(i); delete t; + + continue; } } // Probably pointing to half way through the target - else if (GSConfig.UserHacks_TextureInsideRt >= GSTextureInRtMode::InsideTargets) + else if (!min_rect.rempty()&& GSConfig.UserHacks_TextureInsideRt >= GSTextureInRtMode::InsideTargets) { - if (offset != -1 && (bp - t->m_TEX0.TBP0) != offset) + // Problem: Project - Snowblind and Tomb Raider offset the RT but not the Z + /*if (offset != -1 && (bp - t->m_TEX0.TBP0) != offset) + { continue; + }*/ const u32 widthpage_offset = (std::abs(static_cast(bp - t->m_TEX0.TBP0)) >> 5) % std::max(t->m_TEX0.TBW, 1U); - const bool is_aligned_ok = widthpage_offset == 0 || ((widthpage_offset + TEX0.TBW) <= t->m_TEX0.TBW) || min_rect.width() <= 64 || (widthpage_offset == (t->m_TEX0.TBW >> 1) && (static_cast(min_rect.width()) <= (widthpage_offset * 64))); - if ((!dst || ((GSState::s_n - dst->m_last_draw) < (GSState::s_n - t->m_last_draw))) && is_aligned_ok && (t->m_TEX0.TBW == TEX0.TBW || (TEX0.TBW == 1 && t->m_TEX0.TBW > 1)) && t->Inside(bp, TEX0.TBW, TEX0.PSM, min_rect)) - { + const bool is_aligned_ok = widthpage_offset == 0 || (t->m_TEX0.TBW == TEX0.TBW && ((min_rect.z >> 6) + widthpage_offset) <= TEX0.TBW) || ((widthpage_offset + TEX0.TBW) <= t->m_TEX0.TBW) || min_rect.width() <= 64 || (widthpage_offset == (t->m_TEX0.TBW >> 1) && (static_cast(min_rect.width()) <= (widthpage_offset * 64))); + if ((!dst || ((GSState::s_n - dst->m_last_draw) < (GSState::s_n - t->m_last_draw))) && is_aligned_ok && (t->m_TEX0.TBW == TEX0.TBW || (TEX0.TBW == 1 && t->m_TEX0.TBW > 1)) && t->Overlaps(bp, TEX0.TBW, TEX0.PSM, min_rect)) + { /*TEX0.TBP0 == ((((t->UnwrappedEndBlock() + 1) - t->m_TEX0.TBP0) >> 1) + t->m_TEX0.TBP0)*/ // If it's too old, it's probably not a real target to jump in to anymore. - if ((GSState::s_n - t->m_last_draw) > 10 && (!t->m_dirty.empty() || (!is_shuffle && - !(widthpage_offset == 0/*TEX0.TBP0 == ((((t->UnwrappedEndBlock() + 1) - t->m_TEX0.TBP0) >> 1) + t->m_TEX0.TBP0)*/ || min_rect.width() <= 64 || + /*if ((GSState::s_n - t->m_last_draw) > 10 && (!t->m_dirty.empty() || (!is_shuffle && + !(widthpage_offset == 0 || min_rect.width() <= 64 || (widthpage_offset == (t->m_TEX0.TBW >> 1) && min_rect.width() == widthpage_offset * 64))))) { GL_INS("TC: Deleting RT BP 0x%x BW %d PSM %s due to change in target", t->m_TEX0.TBP0, t->m_TEX0.TBW, psm_str(t->m_TEX0.PSM)); @@ -1918,6 +1937,16 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe i = list.erase(i); delete t; } + else*/ + if (!is_shuffle && !GSUtil::HasSameSwizzleBits(t->m_TEX0.PSM, TEX0.PSM)) + { + GL_INS("TC: Deleting RT BP 0x%x BW %d PSM %s due to change in target", t->m_TEX0.TBP0, t->m_TEX0.TBW, psm_str(t->m_TEX0.PSM)); + InvalidateSourcesFromTarget(t); + i = list.erase(i); + delete t; + + continue; + } else { //DevCon.Warning("Here draw %d wanted %x PSM %x got %x PSM %x offset of %d pages width %d pages draw width %d", GSState::s_n, bp, TEX0.PSM, t->m_TEX0.TBP0, t->m_TEX0.PSM, (bp - t->m_TEX0.TBP0) >> 5, t->m_TEX0.TBW, draw_rect.width()); @@ -1931,6 +1960,8 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe } } } + + i++; } } else @@ -2085,7 +2116,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe dst->m_alpha_min = 0; dst->m_alpha_max = 0; } - else if (!is_shuffle && std::abs(static_cast(GSLocalMemory::m_psm[dst->m_TEX0.PSM].bpp - GSLocalMemory::m_psm[TEX0.PSM].bpp)) == 16) + else if (std::abs(static_cast(GSLocalMemory::m_psm[dst->m_TEX0.PSM].bpp - GSLocalMemory::m_psm[TEX0.PSM].bpp)) == 16) { dst->Update(false); @@ -2094,7 +2125,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe new_scaled_size = ScaleRenderTargetSize(dst->m_unscaled_size, scale); dRect = (GSVector4(GSVector4i::loadh(dst->m_unscaled_size)) * GSVector4(scale)).ceil(); - + if (scale_down) { if ((new_size.y * 2) < 1024) @@ -2116,34 +2147,38 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe dst->m_valid.y /= 2; dst->m_valid.w /= 2; } - GL_INS("TC Convert to 16bit: %dx%d: %dx%d @ %f -> %dx%d @ %f", dst->m_unscaled_size.x, dst->m_unscaled_size.y, - dst->m_texture->GetWidth(), dst->m_texture->GetHeight(), dst->m_scale, new_scaled_size.x, new_scaled_size.y, - scale); - //DevCon.Warning("Scale %s draw %d", scale_down ? "down" : "up", GSState::s_n); - GSTexture* tex = type == RenderTarget ? g_gs_device->CreateRenderTarget(new_scaled_size.x, new_scaled_size.y, GSTexture::Format::Color, true) : - g_gs_device->CreateDepthStencil(new_scaled_size.x, new_scaled_size.y, GSTexture::Format::DepthStencil, true); - if (!tex) - return nullptr; - m_target_memory_usage += tex->GetMemUsage(); - - g_gs_device->StretchRect(dst->m_texture, sRect, tex, dRect, (type == RenderTarget) ? ShaderConvert::COPY : ShaderConvert::DEPTH_COPY, false); - - - if (src && src->m_from_target && src->m_from_target == dst) + if (!is_shuffle) { - src->m_texture = dst->m_texture; - src->m_target_direct = false; - src->m_shared_texture = false; + GL_INS("TC Convert to 16bit: %dx%d: %dx%d @ %f -> %dx%d @ %f", dst->m_unscaled_size.x, dst->m_unscaled_size.y, + dst->m_texture->GetWidth(), dst->m_texture->GetHeight(), dst->m_scale, new_scaled_size.x, new_scaled_size.y, + scale); + //DevCon.Warning("Scale %s draw %d", scale_down ? "down" : "up", GSState::s_n); + GSTexture* tex = type == RenderTarget ? g_gs_device->CreateRenderTarget(new_scaled_size.x, new_scaled_size.y, GSTexture::Format::Color, true) : + g_gs_device->CreateDepthStencil(new_scaled_size.x, new_scaled_size.y, GSTexture::Format::DepthStencil, true); + if (!tex) + return nullptr; + m_target_memory_usage += tex->GetMemUsage(); + + g_gs_device->StretchRect(dst->m_texture, sRect, tex, dRect, (type == RenderTarget) ? ShaderConvert::COPY : ShaderConvert::DEPTH_COPY, false); + + + if (src && src->m_from_target && src->m_from_target == dst) + { + src->m_texture = dst->m_texture; + src->m_target_direct = false; + src->m_shared_texture = false; + } + else + { + m_target_memory_usage -= dst->m_texture->GetMemUsage(); + g_gs_device->Recycle(dst->m_texture); + } + + dst->m_texture = tex; + dst->m_unscaled_size = new_size; } - else - { - m_target_memory_usage -= dst->m_texture->GetMemUsage(); - g_gs_device->Recycle(dst->m_texture); - } - + // New format or doing a shuffle to a 32bit target that used to be 16bit dst->m_TEX0.PSM = TEX0.PSM; - dst->m_texture = tex; - dst->m_unscaled_size = new_size; } @@ -2347,7 +2382,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe dst->m_valid_rgb = dst_match->m_valid_rgb; dst->m_was_dst_matched = true; dst_match->m_was_dst_matched = true; - dst_match->m_valid_rgb = false; + dst_match->m_valid_rgb = preserve_rgb; if (GSLocalMemory::m_psm[dst->m_TEX0.PSM].bpp == 16 && GSLocalMemory::m_psm[dst_match->m_TEX0.PSM].bpp > 16) dst->m_TEX0.TBW = dst_match->m_TEX0.TBW; // Be careful of shuffles of the depth as C16, but using a buffer width of 16 (Mercenaries). @@ -2572,7 +2607,7 @@ bool GSTextureCache::PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, cons if (valid_draw_size && supported_fmt) { - const GSVector4i newrect = GSVector4i::loadh(size); + const GSVector4i newrect = GSVector4i::loadh(valid_size); const u32 rect_end = GSLocalMemory::GetUnwrappedEndBlockAddress(TEX0.TBP0, TEX0.TBW, TEX0.PSM, newrect); RGBAMask rgba; @@ -3167,7 +3202,7 @@ bool GSTextureCache::PrepareDownloadTexture(u32 width, u32 height, GSTexture::Fo return true; } -void GSTextureCache::InvalidateContainedTargets(u32 start_bp, u32 end_bp, u32 write_psm) +void GSTextureCache::InvalidateContainedTargets(u32 start_bp, u32 end_bp, u32 write_psm, u32 write_bw) { const bool preserve_alpha = (GSLocalMemory::m_psm[write_psm].trbpp == 24); for (int type = 0; type < 2; type++) @@ -3176,22 +3211,24 @@ void GSTextureCache::InvalidateContainedTargets(u32 start_bp, u32 end_bp, u32 wr for (auto i = list.begin(); i != list.end();) { Target* const t = *i; - if (start_bp != t->m_TEX0.TBP0 && (t->m_TEX0.TBP0 < start_bp || t->UnwrappedEndBlock() > end_bp)) + if ((start_bp > t->UnwrappedEndBlock() || end_bp < t->m_TEX0.TBP0) || (start_bp != t->m_TEX0.TBP0 && (t->m_TEX0.TBP0 < start_bp || t->UnwrappedEndBlock() > end_bp) && t->m_dirty.empty())) { ++i; continue; } + const u32 total_pages = ((end_bp + 1) - t->m_TEX0.TBP0) >> 5; // Not covering the whole target, and a different format, so just dirty it. - if (start_bp == t->m_TEX0.TBP0 && (t->UnwrappedEndBlock() > end_bp) && write_psm != t->m_TEX0.PSM) + /*if (start_bp >= t->m_TEX0.TBP0 && (t->UnwrappedEndBlock() > end_bp) && write_psm != t->m_TEX0.PSM && write_bw == t->m_TEX0.TBW) { const GSLocalMemory::psm_t& target_psm = GSLocalMemory::m_psm[write_psm]; - u32 total_pages = (end_bp - t->m_TEX0.TBP0) >> 5; - GSVector4i dirty_area = GSVector4i(0, 0, t->m_valid.z, (total_pages / t->m_TEX0.TBW) * target_psm.pgs.y); - InvalidateVideoMem(g_gs_renderer->m_mem.GetOffset(t->m_TEX0.TBP0, t->m_TEX0.TBW, write_psm), dirty_area, true); + const u32 page_offset = ((start_bp - t->m_TEX0.TBP0) >> 5); + const u32 vertical_offset = (page_offset / t->m_TEX0.TBW) * target_psm.pgs.y; + GSVector4i dirty_area = GSVector4i(page_offset % t->m_TEX0.TBW, vertical_offset, t->m_valid.z, vertical_offset + ((total_pages / t->m_TEX0.TBW) * target_psm.pgs.y)); + InvalidateVideoMem(g_gs_renderer->m_mem.GetOffset(t->m_TEX0.TBP0, t->m_TEX0.TBW, t->m_TEX0.PSM), dirty_area, true); ++i; continue; - } + }*/ InvalidateSourcesFromTarget(t); @@ -3874,6 +3911,19 @@ bool GSTextureCache::Move(u32 SBP, u32 SBW, u32 SPSM, int sx, int sy, u32 DBP, u if (alpha_only && (!dst || GSLocalMemory::m_psm[dst->m_TEX0.PSM].bpp != 32)) return false; + // This is probably copying to a new buffer but using the original one as an offset, so better to use a new texture, if we don't find one. + if (dst && DBP == SBP && dy > dst->m_unscaled_size.y) + { + u32 new_DBP = DBP + (((dy / GSLocalMemory::m_psm[dst->m_TEX0.PSM].pgs.y) * DBW) << 5); + + dst = nullptr; + + DBP = new_DBP; + dy = 0; + + dst = GetExactTarget(DBP, DBW, dpsm_s.depth ? DepthStencil : RenderTarget, DBP); + } + // Beware of the case where a game might create a larger texture by moving a bunch of chunks around. if (dst && DBP == SBP && dy > dst->m_unscaled_size.y) { @@ -3960,7 +4010,7 @@ bool GSTextureCache::Move(u32 SBP, u32 SBW, u32 SPSM, int sx, int sy, u32 DBP, u // Make sure the copy doesn't go out of bounds (it shouldn't). if ((scaled_dx + scaled_w) > dst->m_texture->GetWidth() || (scaled_dy + scaled_h) > dst->m_texture->GetHeight()) return false; - DevCon.Warning("HW Move 0x%x[BW:%u PSM:%s] to 0x%x[BW:%u PSM:%s] <%d,%d->%d,%d> -> <%d,%d->%d,%d>", SBP, SBW, + GL_CACHE("HW Move after draw %d 0x%x[BW:%u PSM:%s] to 0x%x[BW:%u PSM:%s] <%d,%d->%d,%d> -> <%d,%d->%d,%d>", GSState::s_n, SBP, SBW, psm_str(SPSM), DBP, DBW, psm_str(DPSM), sx, sy, sx + w, sy + h, dx, dy, dx + w, dy + h); const bool cover_whole_target = dst->m_type == RenderTarget && GSVector4i(dx, dy, dx + w, dy + h).rintersect(dst->m_valid).eq(dst->m_valid); @@ -4086,6 +4136,7 @@ bool GSTextureCache::Move(u32 SBP, u32 SBW, u32 SPSM, int sx, int sy, u32 DBP, u // Invalidate any sources that overlap with the target (since they're now stale). InvalidateVideoMem(g_gs_renderer->m_mem.GetOffset(DBP, DBW, DPSM), GSVector4i(dx, dy, dx + w, dy + h), false); + return true; } @@ -4272,7 +4323,7 @@ GSTextureCache::Target* GSTextureCache::GetExactTarget(u32 BP, u32 BW, int type, { Target* t = *it; - if (t->m_TEX0.TBP0 == BP && t->m_TEX0.TBW == BW && t->UnwrappedEndBlock() >= end_bp) + if ((t->m_TEX0.TBP0 == BP || (GSConfig.UserHacks_TextureInsideRt >= GSTextureInRtMode::InsideTargets && t->m_TEX0.TBP0 < BP && ((BP >> 5) % t->m_TEX0.TBW) == 0)) && t->m_TEX0.TBW == BW && t->UnwrappedEndBlock() >= end_bp) { rts.MoveFront(it.Index()); return t; @@ -4988,6 +5039,9 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con g_gs_device->ConvertToIndexedTexture(sTex, dst->m_scale, x_offset, y_offset, std::max(dst->m_TEX0.TBW, 1u) * 64, dst->m_TEX0.PSM, dTex, std::max(TEX0.TBW, 1u) * 64, TEX0.PSM); + + src->m_region.SetX((x_offset / GSLocalMemory::m_psm[dst->m_TEX0.PSM].pgs.x) * GSLocalMemory::m_psm[TEX0.PSM].pgs.x, tw); + src->m_region.SetY((y_offset / GSLocalMemory::m_psm[dst->m_TEX0.PSM].pgs.y) * GSLocalMemory::m_psm[TEX0.PSM].pgs.y, th); } else { @@ -5139,8 +5193,10 @@ GSTextureCache::Source* GSTextureCache::CreateMergedSource(GIFRegTEX0 TEX0, GIFR { // We *should* be able to use the TBW here as an indicator of size... except Destroy All Humans 2 sets // TBW to 10, and samples from 64 through 703... which means it'd be grabbing the next row at the end. - const int tex_width = std::max(64 * TEX0.TBW, region.GetMaxX()); - const int tex_height = region.HasY() ? region.GetHeight() : (1 << TEX0.TH); + // Round the size up to the next block + const GSLocalMemory::psm_t& psm_s = GSLocalMemory::m_psm[TEX0.PSM]; + const int tex_width = (std::max(64 * TEX0.TBW, region.GetMaxX()) + (psm_s.bs.x - 1)) & ~(psm_s.bs.x - 1); + const int tex_height = ((region.HasY() ? region.GetHeight() : (1 << TEX0.TH)) + (psm_s.bs.y - 1)) & ~(psm_s.bs.y - 1); const int scaled_width = static_cast(static_cast(tex_width) * scale); const int scaled_height = static_cast(static_cast(tex_height) * scale); @@ -6602,9 +6658,11 @@ void GSTextureCache::Target::ResizeValidity(const GSVector4i& rect) m_end_block = GSLocalMemory::GetEndBlockAddress(m_TEX0.TBP0, m_TEX0.TBW, m_TEX0.PSM, m_valid); const u32 offset = ((UnwrappedEndBlock() + 1) - m_TEX0.TBP0) % (std::max(m_TEX0.TBW, 1U) << 5); - m_end_block += offset; + + if (offset) + m_end_block = m_end_block + ((std::max(m_TEX0.TBW, 1U) << 5) - offset); } - + // Else No valid size, so need to resize down. // GL_CACHE("ResizeValidity (0x%x->0x%x) from R:%d,%d Valid: %d,%d", m_TEX0.TBP0, m_end_block, rect.z, rect.w, m_valid.z, m_valid.w); @@ -6612,13 +6670,18 @@ void GSTextureCache::Target::ResizeValidity(const GSVector4i& rect) void GSTextureCache::Target::UpdateValidity(const GSVector4i& rect, bool can_resize) { + if (m_TEX0.TBP0 == 0x1a00 && rect.w == 448 && can_resize) + DevCon.Warning("Here"); + if (m_valid.eq(GSVector4i::zero())) { m_valid = rect; m_end_block = GSLocalMemory::GetEndBlockAddress(m_TEX0.TBP0, m_TEX0.TBW, m_TEX0.PSM, m_valid); const u32 offset = ((UnwrappedEndBlock() + 1) - m_TEX0.TBP0) % (std::max(m_TEX0.TBW, 1U) << 5); - m_end_block += offset; + + if (offset) + m_end_block = m_end_block + ((std::max(m_TEX0.TBW, 1U) << 5) - offset); } else if (can_resize) { @@ -6626,7 +6689,9 @@ void GSTextureCache::Target::UpdateValidity(const GSVector4i& rect, bool can_res m_end_block = GSLocalMemory::GetEndBlockAddress(m_TEX0.TBP0, m_TEX0.TBW, m_TEX0.PSM, m_valid); const u32 offset = ((UnwrappedEndBlock() + 1) - m_TEX0.TBP0) % (std::max(m_TEX0.TBW, 1U) << 5); - m_end_block += offset; + + if (offset) + m_end_block = m_end_block + ((std::max(m_TEX0.TBW, 1U) << 5) - offset); } // GL_CACHE("UpdateValidity (0x%x->0x%x) from R:%d,%d Valid: %d,%d", m_TEX0.TBP0, m_end_block, rect.z, rect.w, m_valid.z, m_valid.w); } @@ -6997,6 +7062,29 @@ void GSTextureCache::InvalidateTemporarySource() m_temporary_source = nullptr; } +void GSTextureCache::SetTemporaryZ(GSTexture* temp_z) +{ + m_temporary_z = temp_z; +} + +GSTexture* GSTextureCache::GetTemporaryZ() +{ + if (!m_temporary_z) + return nullptr; + + return m_temporary_z; +} + + +void GSTextureCache::InvalidateTemporaryZ() +{ + if (!m_temporary_z) + return; + + g_gs_device->Recycle(m_temporary_z); + m_temporary_z = nullptr; +} + void GSTextureCache::InjectHashCacheTexture(const HashCacheKey& key, GSTexture* tex, const std::pair& alpha_minmax) { // When we insert we update memory usage. Old texture gets removed below. diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.h b/pcsx2/GS/Renderers/HW/GSTextureCache.h index 024dfbbed6..8dc7315f55 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.h +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.h @@ -427,6 +427,7 @@ protected: std::unordered_map m_surface_offset_cache; Source* m_temporary_source = nullptr; // invalidated after the draw + GSTexture* m_temporary_z = nullptr; // invalidated after the draw std::unique_ptr m_color_download_texture; std::unique_ptr m_uint16_download_texture; @@ -508,7 +509,7 @@ public: bool HasTargetInHeightCache(u32 bp, u32 fbw, u32 psm, u32 max_age = std::numeric_limits::max(), bool move_front = true); bool Has32BitTarget(u32 bp); - void InvalidateContainedTargets(u32 start_bp, u32 end_bp, u32 write_psm = PSMCT32); + void InvalidateContainedTargets(u32 start_bp, u32 end_bp, u32 write_psm = PSMCT32, u32 write_bw = 1); void InvalidateVideoMemType(int type, u32 bp, u32 write_psm = PSMCT32, u32 write_fbmsk = 0, bool dirty_only = false); void InvalidateVideoMemSubTarget(GSTextureCache::Target* rt); void InvalidateVideoMem(const GSOffset& off, const GSVector4i& r, bool target = true); @@ -551,6 +552,11 @@ public: /// Invalidates a temporary source, a partial copy only created from the current RT/DS for the current draw. void InvalidateTemporarySource(); + void SetTemporaryZ(GSTexture* temp_z); + GSTexture* GetTemporaryZ(); + + /// Invalidates a temporary Z, a partial copy only created from the current DS for the current draw when Z is not offset but RT is + void InvalidateTemporaryZ(); /// Injects a texture into the hash cache, by using GSTexture::Swap(), transitively applying to all sources. Ownership of tex is transferred. void InjectHashCacheTexture(const HashCacheKey& key, GSTexture* tex, const std::pair& alpha_minmax); diff --git a/pcsx2/GS/Renderers/Metal/tfx.metal b/pcsx2/GS/Renderers/Metal/tfx.metal index 40d282e0bb..2c3d5e4557 100644 --- a/pcsx2/GS/Renderers/Metal/tfx.metal +++ b/pcsx2/GS/Renderers/Metal/tfx.metal @@ -1175,11 +1175,8 @@ struct PSMain { if (PS_PROCESS_BA == SHUFFLE_READWRITE && PS_PROCESS_RG == SHUFFLE_READWRITE) { - C.rb = C.br; - float g_temp = C.g; - - C.g = C.a; - C.a = g_temp; + C.b = C.r; + C.a = C.g; } else if(PS_PROCESS_BA & SHUFFLE_READ) { From fed266f5ac0034e82ba92e65f717f90f12f236cc Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Mon, 6 Jan 2025 17:34:30 +0000 Subject: [PATCH 090/162] GS/HW: Fixes to texture is target offsets --- bin/resources/shaders/dx11/tfx.fx | 4 +- bin/resources/shaders/opengl/tfx_fs.glsl | 4 +- bin/resources/shaders/vulkan/tfx.glsl | 4 +- pcsx2/GS/GSState.cpp | 5 +- pcsx2/GS/Renderers/HW/GSHwHack.cpp | 6 +- pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 127 ++++++++++++++++------- pcsx2/GS/Renderers/HW/GSTextureCache.cpp | 58 +++++++---- pcsx2/GS/Renderers/Metal/tfx.metal | 4 +- 8 files changed, 142 insertions(+), 70 deletions(-) diff --git a/bin/resources/shaders/dx11/tfx.fx b/bin/resources/shaders/dx11/tfx.fx index adae2af5fb..33ca3634be 100644 --- a/bin/resources/shaders/dx11/tfx.fx +++ b/bin/resources/shaders/dx11/tfx.fx @@ -1127,8 +1127,8 @@ PS_OUTPUT ps_main(PS_INPUT input) { if (PS_PROCESS_BA == SHUFFLE_READWRITE && PS_PROCESS_RG == SHUFFLE_READWRITE) { - C.b = C.r; - C.a = C.g; + C.br = C.rb; + C.ag = C.ga; } else if(PS_PROCESS_BA & SHUFFLE_READ) { diff --git a/bin/resources/shaders/opengl/tfx_fs.glsl b/bin/resources/shaders/opengl/tfx_fs.glsl index fc02634eff..b19dee992b 100644 --- a/bin/resources/shaders/opengl/tfx_fs.glsl +++ b/bin/resources/shaders/opengl/tfx_fs.glsl @@ -1095,8 +1095,8 @@ void ps_main() C.ga = vec2(float((denorm_c.g >> 6) | ((denorm_c.b >> 3) << 2) | (denorm_TA.x & 0x80u))); #elif PS_SHUFFLE_ACROSS #if(PS_PROCESS_BA == SHUFFLE_READWRITE && PS_PROCESS_RG == SHUFFLE_READWRITE) - C.b = C.r; - C.a = C.g; + C.br = C.rb; + C.ag = C.ga; #elif(PS_PROCESS_BA & SHUFFLE_READ) C.rb = C.bb; C.ga = C.aa; diff --git a/bin/resources/shaders/vulkan/tfx.glsl b/bin/resources/shaders/vulkan/tfx.glsl index d7639ffec5..757313ff90 100644 --- a/bin/resources/shaders/vulkan/tfx.glsl +++ b/bin/resources/shaders/vulkan/tfx.glsl @@ -1362,8 +1362,8 @@ void main() // Write RB part. Mask will take care of the correct destination #elif PS_SHUFFLE_ACROSS #if(PS_PROCESS_BA == SHUFFLE_READWRITE && PS_PROCESS_RG == SHUFFLE_READWRITE) - C.b = C.r; - C.a = C.g; + C.br = C.rb; + C.ag = C.ga; #elif(PS_PROCESS_BA & SHUFFLE_READ) C.rb = C.bb; C.ga = C.aa; diff --git a/pcsx2/GS/GSState.cpp b/pcsx2/GS/GSState.cpp index f8cd974724..ba628a8426 100644 --- a/pcsx2/GS/GSState.cpp +++ b/pcsx2/GS/GSState.cpp @@ -467,7 +467,8 @@ void GSState::DumpVertices(const std::string& filename) file << std::setfill('0') << std::setw(3) << unsigned(v.RGBAQ.R) << DEL; file << std::setfill('0') << std::setw(3) << unsigned(v.RGBAQ.G) << DEL; file << std::setfill('0') << std::setw(3) << unsigned(v.RGBAQ.B) << DEL; - file << std::setfill('0') << std::setw(3) << unsigned(v.RGBAQ.A); + file << std::setfill('0') << std::setw(3) << unsigned(v.RGBAQ.A) << DEL; + file << "FOG: " << std::setfill('0') << std::setw(3) << unsigned(v.FOG); file << std::endl; } @@ -3100,7 +3101,7 @@ __forceinline bool GSState::IsAutoFlushDraw(u32 prim) { // Pretty confident here... GSVertex* buffer = &m_vertex.buff[0]; - const bool const_spacing = (buffer[m_index.buff[0]].U - buffer[m_index.buff[0]].XYZ.X) == (m_v.U - m_v.XYZ.X); + const bool const_spacing = std::abs(buffer[m_index.buff[0]].U - buffer[m_index.buff[0]].XYZ.X) == std::abs(m_v.U - m_v.XYZ.X) && std::abs(buffer[m_index.buff[1]].XYZ.X - buffer[m_index.buff[0]].XYZ.X) < 64; if (const_spacing) return false; diff --git a/pcsx2/GS/Renderers/HW/GSHwHack.cpp b/pcsx2/GS/Renderers/HW/GSHwHack.cpp index c79562681a..3b20ad8e2d 100644 --- a/pcsx2/GS/Renderers/HW/GSHwHack.cpp +++ b/pcsx2/GS/Renderers/HW/GSHwHack.cpp @@ -1099,7 +1099,7 @@ bool GSHwHack::OI_SonicUnleashed(GSRendererHW& r, GSTexture* rt, GSTexture* ds, // compute shadow in RG, // save result in alpha with a TS, // Restore RG channel that we previously copied to render shadows. - + // Important note: The game downsizes the target to half height, then later expands it back up to full size, that's why PCSX2 doesn't like it, we don't support that behaviour. const GIFRegTEX0& Texture = RTEX0; GIFRegTEX0 Frame = {}; @@ -1110,9 +1110,9 @@ bool GSHwHack::OI_SonicUnleashed(GSRendererHW& r, GSTexture* rt, GSTexture* ds, if ((!rt) || (!RPRIM->TME) || (GSLocalMemory::m_psm[Texture.PSM].bpp != 16) || (GSLocalMemory::m_psm[Frame.PSM].bpp != 16) || (Texture.TBP0 == Frame.TBP0) || (Frame.TBW != 16 && Texture.TBW != 16)) return true; - GL_INS("OI_SonicUnleashed replace draw by a copy"); + GL_INS("OI_SonicUnleashed replace draw by a copy draw %d", r.s_n); - GSTextureCache::Target* src = g_texture_cache->LookupTarget(Texture, GSVector2i(1, 1), r.GetTextureScaleFactor(), GSTextureCache::RenderTarget); + GSTextureCache::Target* src = g_texture_cache->LookupTarget(Texture, GSVector2i(1, 1), r.GetTextureScaleFactor(), GSTextureCache::RenderTarget, true, 0, false, false, true, true, GSVector4i::zero(), true); if (!src) return true; diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index 412ea83c3f..67f80548a9 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -339,7 +339,7 @@ void GSRendererHW::ConvertSpriteTextureShuffle(u32& process_rg, u32& process_ba, tex_pos &= 0xFF; shuffle_across = (((tex_pos + 8) >> 4) ^ ((pos + 8) >> 4)) & 0x8; - const bool full_width = !shuffle_across && (((second_vert.XYZ.X + 9) - first_vert.XYZ.X) >> 4) >= 16 && m_r.width() > 8; + const bool full_width = ((second_vert.XYZ.X - first_vert.XYZ.X) >> 4) >= 16 && m_r.width() > 8 && tex && tex->m_from_target && rt == tex->m_from_target; process_ba = ((pos > 112 && pos < 136) || full_width) ? SHUFFLE_WRITE : 0; process_rg = (!process_ba || full_width) ? SHUFFLE_WRITE : 0; // "same group" means it can read blue and write alpha using C32 tricks @@ -726,10 +726,25 @@ void GSRendererHW::ConvertSpriteTextureShuffle(u32& process_rg, u32& process_ba, m_vt.m_max.p.y = floor(m_vt.m_max.p.y + 1.9f) / 2.0f; } - m_context->scissor.in.x = m_vt.m_min.p.x; - m_context->scissor.in.z = m_vt.m_max.p.x + 0.9f; - m_context->scissor.in.y = m_vt.m_min.p.y; - m_context->scissor.in.w = m_vt.m_max.p.y + 0.9f; + if (m_context->scissor.in.x & 8) + { + m_context->scissor.in.x &= ~0xf;//m_vt.m_min.p.x; + + if (half_right_vert) + m_context->scissor.in.x /= 2; + } + if (m_context->scissor.in.z & 8) + { + m_context->scissor.in.z += 8; //m_vt.m_min.p.x; + + if (half_right_vert) + m_context->scissor.in.z /= 2; + } + if (half_bottom_vert) + { + m_context->scissor.in.y /= 2; + m_context->scissor.in.w /= 2; + } // Only do this is the source is being interpreted as 16bit if (half_bottom_uv) @@ -2704,7 +2719,26 @@ void GSRendererHW::Draw() bool shuffle_target = false; if (!no_rt && GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].bpp == 16 && GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].bpp >= 16) { - if (m_cached_ctx.FRAME.Block() != m_cached_ctx.TEX0.TBP0) + if (!shuffle_target && GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].bpp == 16) + { + const GSVertex* v = &m_vertex.buff[0]; + + const int first_x = std::abs(static_cast(((v[0].XYZ.X - m_context->XYOFFSET.OFX) + 8))) >> 4; + const int first_u = PRIM->FST ? ((v[0].U + 9) >> 4) : static_cast(((1 << m_cached_ctx.TEX0.TW) * (v[0].ST.S / v[1].RGBAQ.Q))); + const int second_u = PRIM->FST ? ((v[1].U + 9) >> 4) : static_cast(((1 << m_cached_ctx.TEX0.TW) * (v[1].ST.S / v[1].RGBAQ.Q)) + 0.6f); + // offset coordinates swap around RG/BA. (Ace Combat) + const u32 minv = m_cached_ctx.CLAMP.MINV; + const u32 minu = m_cached_ctx.CLAMP.MINU; + const bool rgba_shuffle = ((m_cached_ctx.CLAMP.WMS == m_cached_ctx.CLAMP.WMT && m_cached_ctx.CLAMP.WMS == CLAMP_REGION_REPEAT) && (minu && minv)); + const bool shuffle_coords = ((first_x ^ first_u) & 8) || rgba_shuffle; + // Round up half of second coord, it can sometimes be slightly under. + const int draw_width = std::abs(v[1].XYZ.X + 9 - v[0].XYZ.X) >> 4; + const int read_width = std::abs(second_u - first_u); + + shuffle_target = shuffle_coords && (draw_width & 7) == 0 && std::abs(draw_width - read_width) <= 1; + } + + if (m_cached_ctx.FRAME.Block() != m_cached_ctx.TEX0.TBP0 || !shuffle_target) { // FBW is going to be wrong for channel shuffling into a new target, so take it from the source. FRAME_TEX0.U64 = 0; @@ -2720,24 +2754,6 @@ void GSRendererHW::Draw() tgt = nullptr; } - if (!shuffle_target && GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].bpp == 16) - { - const GSVertex* v = &m_vertex.buff[0]; - - const int first_x = ((v[0].XYZ.X - m_context->XYOFFSET.OFX) + 8) >> 4; - const int first_u = PRIM->FST ? ((v[0].U + 9) >> 4) : static_cast(((1 << m_cached_ctx.TEX0.TW) * (v[0].ST.S / v[1].RGBAQ.Q))); - const int second_u = PRIM->FST ? ((v[1].U + 9) >> 4) : static_cast(((1 << m_cached_ctx.TEX0.TW) * (v[1].ST.S / v[1].RGBAQ.Q)) + 0.6f); - // offset coordinates swap around RG/BA. (Ace Combat) - const u32 minv = m_cached_ctx.CLAMP.MINV; - const u32 minu = m_cached_ctx.CLAMP.MINU; - const bool rgba_shuffle = ((m_cached_ctx.CLAMP.WMS == m_cached_ctx.CLAMP.WMT && m_cached_ctx.CLAMP.WMS == CLAMP_REGION_REPEAT) && (minu && minv)); - const bool shuffle_coords = ((first_x ^ first_u) & 8) || rgba_shuffle; - // Round up half of second coord, it can sometimes be slightly under. - const int draw_width = std::abs(v[1].XYZ.X + 9 - v[0].XYZ.X) >> 4; - const int read_width = std::abs(second_u - first_u); - - shuffle_target = shuffle_coords && (draw_width & 7) == 0 && std::abs(draw_width - read_width) <= 1; - } } possible_shuffle = !no_rt && (((shuffle_target /*&& GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].bpp == 16*/) /*|| (m_cached_ctx.FRAME.Block() == m_cached_ctx.TEX0.TBP0 && ((m_cached_ctx.TEX0.PSM & 0x6) || m_cached_ctx.FRAME.PSM != m_cached_ctx.TEX0.PSM))*/) || IsPossibleChannelShuffle()); @@ -2962,6 +2978,7 @@ void GSRendererHW::Draw() if (!possible_shuffle && m_split_texture_shuffle_pages == 0) m_r = m_r.rintersect(t_size_rect); + GSVector4i lookup_rect = unclamped_draw_rect; // Do the lookup with the real format on a shuffle, if possible. if (possible_shuffle && GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].bpp == 16 && GSLocalMemory ::m_psm[m_cached_ctx.FRAME.PSM].bpp == 16) { @@ -2975,6 +2992,22 @@ void GSRendererHW::Draw() FRAME_TEX0.PSM = next_ctx.TEX0.PSM; else FRAME_TEX0.PSM = PSMCT32; // Guess full color if no upcoming hint, it'll fix itself later. + + // This is just for overlap detection, it doesn't matter which direction we do this in + if (GSLocalMemory::m_psm[FRAME_TEX0.PSM].bpp == 32) + { + // Shuffling with a double width (Sonic Unleashed for example which does a wierd shuffle/not shuffle green backup/restore). + if (src && std::abs((lookup_rect.width() / 2) - src->m_from_target->m_unscaled_size.x) <= 8) + { + lookup_rect.x /= 2; + lookup_rect.z /= 2; + } + else + { + lookup_rect.y /= 2; + lookup_rect.w /= 2; + } + } } // Normally we would use 1024 here to match the clear above, but The Godfather does a 1023x1023 draw instead @@ -2988,7 +3021,7 @@ void GSRendererHW::Draw() const bool preserve_downscale_draw = std::abs(scale_draw) == 1 || (scale_draw == 0 && ((src && src->m_from_target && src->m_from_target->m_downscaled) || is_possible_mem_clear == ClearType::ClearWithDraw)); rt = g_texture_cache->LookupTarget(FRAME_TEX0, t_size, ((src && src->m_scale != 1) && GSConfig.UserHacks_NativeScaling == GSNativeScaling::Normal && !possible_shuffle) ? GetTextureScaleFactor() : target_scale, GSTextureCache::RenderTarget, true, - fm, false, force_preload, preserve_rt_rgb, preserve_rt_alpha, unclamped_draw_rect, possible_shuffle, is_possible_mem_clear && FRAME_TEX0.TBP0 != m_cached_ctx.ZBUF.Block(), + fm, false, force_preload, preserve_rt_rgb, preserve_rt_alpha, lookup_rect, possible_shuffle, is_possible_mem_clear && FRAME_TEX0.TBP0 != m_cached_ctx.ZBUF.Block(), GSConfig.UserHacks_NativeScaling != GSNativeScaling::Off && preserve_downscale_draw && is_possible_mem_clear != ClearType::NormalClear, src, (no_ds || !ds) ? -1 : (m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0)); // Draw skipped because it was a clear and there was no target. @@ -3027,7 +3060,7 @@ void GSRendererHW::Draw() else if (rt->m_TEX0.TBP0 != m_cached_ctx.FRAME.Block()) { int vertical_offset = ((static_cast(m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0) / 32) / std::max(static_cast(rt->m_TEX0.TBW), 1)) * frame_psm.pgs.y; // I know I could just not shift it.. - + int texture_offset = 0; const int horizontal_offset = ((static_cast((m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0)) / 32) % static_cast(std::max(rt->m_TEX0.TBW, 1U))) * frame_psm.pgs.x; // Used to reduce the offset made later in channel shuffles m_target_offset = std::abs(static_cast((m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0)) >> 5); @@ -3038,6 +3071,7 @@ void GSRendererHW::Draw() GSVector2i new_scaled_size = rt->m_unscaled_size * rt->m_scale; // Make sure to use the original format for the offset. int new_offset = std::abs((vertical_offset / frame_psm.pgs.y) * GSLocalMemory::m_psm[rt->m_TEX0.PSM].pgs.y); + texture_offset = new_offset; new_scaled_size.y += new_offset * rt->m_scale; GSTexture* tex = g_gs_device->CreateRenderTarget(new_scaled_size.x, new_scaled_size.y, GSTexture::Format::Color, true); @@ -3048,18 +3082,13 @@ void GSRendererHW::Draw() g_gs_device->StretchRect(rt->m_texture, GSVector4(0,0,1,1), tex, GSVector4(dRect), ShaderConvert::COPY, false); - if (src && src->m_from_target && src->m_from_target == rt) + if (src && src->m_from_target && src->m_from_target == rt && src->m_target_direct) { - src->m_texture = rt->m_texture; - src->m_target_direct = false; - src->m_shared_texture = false; - } - else - { - //m_target_memory_usage -= dst->m_texture->GetMemUsage(); - g_gs_device->Recycle(rt->m_texture); + src->m_texture = tex; } + g_gs_device->Recycle(rt->m_texture); + rt->m_valid.y += new_offset; rt->m_valid.w += new_offset; rt->m_drawn_since_read.y += new_offset; @@ -3090,8 +3119,26 @@ void GSRendererHW::Draw() for (u32 i = 0; i < m_vertex.tail; i++) { - v[i].XYZ.Y += vertical_offset << 4; v[i].XYZ.X += horizontal_offset << 4; + v[i].XYZ.Y += vertical_offset << 4; + } + + if (texture_offset && src && src->m_from_target && src->m_target_direct && src->m_from_target == rt) + { + GSVector4i src_region = src->GetRegionRect(); + + if (src_region.rempty()) + { + src_region = GSVector4i::loadh(rt->m_unscaled_size); + src_region.y += texture_offset; + } + else + { + src_region.y += texture_offset; + src_region.w += texture_offset; + } + src->m_region.SetX(src_region.x, src_region.z); + src->m_region.SetY(src_region.y, src_region.w); } m_context->scissor.in.x += horizontal_offset; @@ -3136,6 +3183,7 @@ void GSRendererHW::Draw() src->m_texture = rt->m_texture; src->m_scale = rt->GetScale(); src->m_unscaled_size = rt->m_unscaled_size; + } target_scale = rt->GetScale(); @@ -3546,7 +3594,7 @@ void GSRendererHW::Draw() GL_INS("Resize RT from %dx%d to %dx%d", rt->GetUnscaledWidth(), rt->GetUnscaledHeight(), new_w, new_h); // May not be needed/could cause problems with garbage loaded from GS memory - if (preserve_rt_color) + /*if (preserve_rt_color) { RGBAMask mask; mask._u32 = 0xF; @@ -3562,7 +3610,7 @@ void GSRendererHW::Draw() GSVector4i height_dirty_rect = GSVector4i(0, rt->m_unscaled_size.y, new_w, new_h); g_texture_cache->AddDirtyRectTarget(rt, height_dirty_rect, rt->m_TEX0.PSM, rt->m_TEX0.TBW, mask); } - } + }*/ rt->ResizeTexture(new_w, new_h); @@ -3617,10 +3665,11 @@ void GSRendererHW::Draw() const bool new_rect = ds->m_valid.rempty(); const bool new_height = new_h > ds->GetUnscaledHeight(); const int old_height = ds->m_texture->GetHeight(); - const GSVector4i old_rect = ds->GetUnscaledRect(); + pxAssert(ds->GetScale() == target_scale); if (ds->GetUnscaledWidth() != new_w || ds->GetUnscaledHeight() != new_h) GL_INS("Resize DS from %dx%d to %dx%d", ds->GetUnscaledWidth(), ds->GetUnscaledHeight(), new_w, new_h); + ds->ResizeTexture(new_w, new_h); if (!m_texture_shuffle && !m_channel_shuffle) diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index e76198408d..ff57ad7796 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -236,7 +236,7 @@ bool GSTextureCache::CanTranslate(u32 bp, u32 bw, u32 spsm, GSVector4i r, u32 db // The page width matches. // The rect width is less than the width of the destination texture and the height is less than or equal to 1 page high. // The rect width and height is equal to the page size and it covers the width of the incoming bw, so lines are sequential. - const bool page_aligned_rect = masked_rect.eq(r); + const bool page_aligned_rect = masked_rect.xyxy().eq(r.xyxy()); const bool width_match = ((bw * 64) / src_page_size.x) == ((dbw * 64) / dst_page_size.x); const bool sequential_pages = page_aligned_rect && r.x == 0 && r.z == src_pixel_width; const bool single_row = (((bw * 64) / src_page_size.x) <= ((dbw * 64) / dst_page_size.x)) && r.z <= src_pixel_width && r.w <= src_page_size.y; @@ -277,12 +277,12 @@ GSVector4i GSTextureCache::TranslateAlignedRectByPage(u32 tbp, u32 tebp, u32 tbw in_rect = (in_rect + GSVector4i(inc_horizontal_offset, 0).xyxy()).max_i32(GSVector4i(0)); // Project Snowblind and Tomb Raider access the rect offset by 1 page and use a region to correct it, we need to account for that here. - if (in_rect.x >= (dst_pgw * dst_page_size.x)) + if (in_rect.x >= (src_pgw * src_page_size.x)) { - in_rect.z -= dst_pgw * dst_page_size.x; - in_rect.x -= dst_pgw * dst_page_size.x; - in_rect.y += dst_page_size.y; - in_rect.w += dst_page_size.y; + in_rect.z -= src_pgw * src_page_size.x; + in_rect.x -= src_pgw * src_page_size.x; + in_rect.y += src_page_size.y; + in_rect.w += src_page_size.y; } page_offset = 0; single_page = (in_rect.width() / src_page_size.x) <= 1 && (in_rect.height() / src_page_size.y) <= 1; @@ -1458,13 +1458,24 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const // Make sure the texture actually is INSIDE the RT, it's possibly not valid if it isn't. // Also check BP >= TBP, create source isn't equpped to expand it backwards and all data comes from the target. (GH3) else if (GSConfig.UserHacks_TextureInsideRt >= GSTextureInRtMode::InsideTargets && - (GSLocalMemory::m_psm[color_psm].bpp >= 16 || (/*possible_shuffle &&*/ GSLocalMemory::m_psm[color_psm].bpp == 8 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == 32 && t->m_TEX0.TBW >= (bw * 2))) && // Channel shuffles or non indexed lookups. + (GSLocalMemory::m_psm[color_psm].bpp >= 16 || (/*possible_shuffle &&*/ GSLocalMemory::m_psm[color_psm].bpp == 8 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == 32)) && // Channel shuffles or non indexed lookups. t->m_age <= 1 && (!found_t || t->m_last_draw > dst->m_last_draw) /*&& CanTranslate(bp, bw, psm, block_boundary_rect, t->m_TEX0.TBP0, t->m_TEX0.PSM, t->m_TEX0.TBW)*/) { if (!t->HasValidBitsForFormat(psm, req_color, req_alpha) && !(possible_shuffle && GSLocalMemory::m_psm[psm].bpp == 16 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == 32)) continue; + if (GSLocalMemory::m_psm[color_psm].bpp == 16 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp && (t->m_TEX0.TBW != bw && (t->m_TEX0.TBW * 2) != bw)) + { + DevCon.Warning("BP %x - 16bit bad match for target bp %x bw %d src %d format %d", bp, t->m_TEX0.TBP0, t->m_TEX0.TBW, bw, t->m_TEX0.PSM); + continue; + } + else if (!possible_shuffle && (GSLocalMemory::m_psm[color_psm].bpp == 8 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == 32 && + !((t->m_TEX0.TBW == (bw / 2)) || (t->m_TEX0.TBW >= (bw / 2) && (req_rect.w < GSLocalMemory::m_psm[psm].pgs.y))))) + { + DevCon.Warning("BP %x - 8bit bad match for target bp %x bw %d src %d format %d", bp, t->m_TEX0.TBP0, t->m_TEX0.TBW, bw, t->m_TEX0.PSM); + continue; + } // PSM equality needed because CreateSource does not handle PSM conversion. // Only inclusive hit to limit false hits. GSVector4i rect = req_rect; @@ -1600,7 +1611,7 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const if (bp < t->m_TEX0.TBP0 && region.HasX() && region.HasY() && (region.GetMinX() & (page_size.x - 1)) == 0 && (region.GetMinY() & (page_size.y - 1)) == 0 && (offset.bn(region.GetMinX(), region.GetMinY()) == t->m_TEX0.TBP0 || - (offset_bp >= t->m_TEX0.TBP0) && ((((offset_bp - t->m_TEX0.TBP0) >> 5) % bw) + (rect.width() / page_size.x)) <= bw)) + ((offset_bp >= t->m_TEX0.TBP0) && ((((offset_bp - t->m_TEX0.TBP0) >> 5) % bw) + (rect.width() / page_size.x)) <= bw))) { GL_CACHE("TC: Target 0x%x detected in front of TBP 0x%x with %d,%d offset (%d pages)", t->m_TEX0.TBP0, TEX0.TBP0, region.GetMinX(), region.GetMinY(), @@ -1915,7 +1926,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe } } // Probably pointing to half way through the target - else if (!min_rect.rempty()&& GSConfig.UserHacks_TextureInsideRt >= GSTextureInRtMode::InsideTargets) + else if (!min_rect.rempty() && GSConfig.UserHacks_TextureInsideRt >= GSTextureInRtMode::InsideTargets) { // Problem: Project - Snowblind and Tomb Raider offset the RT but not the Z /*if (offset != -1 && (bp - t->m_TEX0.TBP0) != offset) @@ -2607,7 +2618,7 @@ bool GSTextureCache::PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, cons if (valid_draw_size && supported_fmt) { - const GSVector4i newrect = GSVector4i::loadh(valid_size); + const GSVector4i newrect = GSVector4i::loadh(size); const u32 rect_end = GSLocalMemory::GetUnwrappedEndBlockAddress(TEX0.TBP0, TEX0.TBW, TEX0.PSM, newrect); RGBAMask rgba; @@ -3217,7 +3228,7 @@ void GSTextureCache::InvalidateContainedTargets(u32 start_bp, u32 end_bp, u32 wr continue; } - const u32 total_pages = ((end_bp + 1) - t->m_TEX0.TBP0) >> 5; + //const u32 total_pages = ((end_bp + 1) - t->m_TEX0.TBP0) >> 5; // Not covering the whole target, and a different format, so just dirty it. /*if (start_bp >= t->m_TEX0.TBP0 && (t->UnwrappedEndBlock() > end_bp) && write_psm != t->m_TEX0.PSM && write_bw == t->m_TEX0.TBW) { @@ -4322,8 +4333,8 @@ GSTextureCache::Target* GSTextureCache::GetExactTarget(u32 BP, u32 BW, int type, for (auto it = rts.begin(); it != rts.end(); ++it) // Iterate targets from MRU to LRU. { Target* t = *it; - - if ((t->m_TEX0.TBP0 == BP || (GSConfig.UserHacks_TextureInsideRt >= GSTextureInRtMode::InsideTargets && t->m_TEX0.TBP0 < BP && ((BP >> 5) % t->m_TEX0.TBW) == 0)) && t->m_TEX0.TBW == BW && t->UnwrappedEndBlock() >= end_bp) + const u32 tgt_bw = std::max(t->m_TEX0.TBW, 1U); + if ((t->m_TEX0.TBP0 == BP || (GSConfig.UserHacks_TextureInsideRt >= GSTextureInRtMode::InsideTargets && t->m_TEX0.TBP0 < BP && ((BP >> 5) % tgt_bw) == 0)) && tgt_bw == BW && t->UnwrappedEndBlock() >= end_bp) { rts.MoveFront(it.Index()); return t; @@ -5040,8 +5051,22 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con std::max(dst->m_TEX0.TBW, 1u) * 64, dst->m_TEX0.PSM, dTex, std::max(TEX0.TBW, 1u) * 64, TEX0.PSM); - src->m_region.SetX((x_offset / GSLocalMemory::m_psm[dst->m_TEX0.PSM].pgs.x) * GSLocalMemory::m_psm[TEX0.PSM].pgs.x, tw); - src->m_region.SetY((y_offset / GSLocalMemory::m_psm[dst->m_TEX0.PSM].pgs.y) * GSLocalMemory::m_psm[TEX0.PSM].pgs.y, th); + // Adjust the region for the newly translated rect. + u32 const dst_y_height = GSLocalMemory::m_psm[dst->m_TEX0.PSM].pgs.y; + u32 const src_y_height = GSLocalMemory::m_psm[TEX0.PSM].pgs.y; + u32 const dst_page_offset = (y_offset / dst_y_height) * std::max(dst->m_TEX0.TBW, 1U); + y_offset = (dst_page_offset / (std::max(TEX0.TBW / 2U, 1U))) * src_y_height; + + u32 const src_page_width = GSLocalMemory::m_psm[TEX0.PSM].pgs.x; + x_offset = (x_offset / GSLocalMemory::m_psm[dst->m_TEX0.PSM].pgs.x) * GSLocalMemory::m_psm[TEX0.PSM].pgs.x; + if (x_offset >= static_cast(std::max(TEX0.TBW, 1U) * src_page_width)) + { + const u32 adjust = x_offset / src_page_width; + y_offset += adjust * GSLocalMemory::m_psm[TEX0.PSM].pgs.y; + x_offset -= src_page_width * adjust; + } + src->m_region.SetX(x_offset, x_offset + tw); + src->m_region.SetY(y_offset, y_offset + th); } else { @@ -6670,9 +6695,6 @@ void GSTextureCache::Target::ResizeValidity(const GSVector4i& rect) void GSTextureCache::Target::UpdateValidity(const GSVector4i& rect, bool can_resize) { - if (m_TEX0.TBP0 == 0x1a00 && rect.w == 448 && can_resize) - DevCon.Warning("Here"); - if (m_valid.eq(GSVector4i::zero())) { m_valid = rect; diff --git a/pcsx2/GS/Renderers/Metal/tfx.metal b/pcsx2/GS/Renderers/Metal/tfx.metal index 2c3d5e4557..249d658c7d 100644 --- a/pcsx2/GS/Renderers/Metal/tfx.metal +++ b/pcsx2/GS/Renderers/Metal/tfx.metal @@ -1175,8 +1175,8 @@ struct PSMain { if (PS_PROCESS_BA == SHUFFLE_READWRITE && PS_PROCESS_RG == SHUFFLE_READWRITE) { - C.b = C.r; - C.a = C.g; + C.br = C.rb; + C.ag = C.ga; } else if(PS_PROCESS_BA & SHUFFLE_READ) { From 38d792fd974960fa9cd0c868ca6563aac38145c0 Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Sat, 11 Jan 2025 01:29:08 +0000 Subject: [PATCH 091/162] GS/HW: More alterations for new RT in RT system --- pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 26 ++--- pcsx2/GS/Renderers/HW/GSTextureCache.cpp | 121 ++++++++++++++--------- 2 files changed, 90 insertions(+), 57 deletions(-) diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index 67f80548a9..2ff193c047 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -2717,20 +2717,23 @@ void GSRendererHW::Draw() GIFRegTEX0 FRAME_TEX0; bool shuffle_target = false; - if (!no_rt && GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].bpp == 16 && GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].bpp >= 16) + if (!no_rt && GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].bpp == 16 && GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].bpp >= 16 && + (m_vt.m_primclass == GS_SPRITE_CLASS || (m_vt.m_primclass == GS_TRIANGLE_CLASS && (m_index.tail % 6) == 0 && TrianglesAreQuads(true)))) { if (!shuffle_target && GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].bpp == 16) { const GSVertex* v = &m_vertex.buff[0]; - const int first_x = std::abs(static_cast(((v[0].XYZ.X - m_context->XYOFFSET.OFX) + 8))) >> 4; - const int first_u = PRIM->FST ? ((v[0].U + 9) >> 4) : static_cast(((1 << m_cached_ctx.TEX0.TW) * (v[0].ST.S / v[1].RGBAQ.Q))); - const int second_u = PRIM->FST ? ((v[1].U + 9) >> 4) : static_cast(((1 << m_cached_ctx.TEX0.TW) * (v[1].ST.S / v[1].RGBAQ.Q)) + 0.6f); + const int first_x = std::clamp((static_cast(((v[0].XYZ.X - m_context->XYOFFSET.OFX) + 8))) >> 4, 0, 2048); + const bool offset_last = PRIM->FST ? (v[1].U > v[0].U) : ((v[1].ST.S / v[1].RGBAQ.Q) > (v[0].ST.S / v[1].RGBAQ.Q)); + const int first_u = PRIM->FST ? ((v[0].U + (offset_last ? 0 : 9)) >> 4) : std::clamp(static_cast(((1 << m_cached_ctx.TEX0.TW) * (v[0].ST.S / v[1].RGBAQ.Q)) + (offset_last ? 0.0f : 0.6f)), 0, 2048); + const int second_u = PRIM->FST ? ((v[1].U + (offset_last ? 9 : 0)) >> 4) : std::clamp(static_cast(((1 << m_cached_ctx.TEX0.TW) * (v[1].ST.S / v[1].RGBAQ.Q)) + (offset_last ? 0.6f : 0.0f)), 0, 2048); // offset coordinates swap around RG/BA. (Ace Combat) const u32 minv = m_cached_ctx.CLAMP.MINV; const u32 minu = m_cached_ctx.CLAMP.MINU; const bool rgba_shuffle = ((m_cached_ctx.CLAMP.WMS == m_cached_ctx.CLAMP.WMT && m_cached_ctx.CLAMP.WMS == CLAMP_REGION_REPEAT) && (minu && minv)); - const bool shuffle_coords = ((first_x ^ first_u) & 8) || rgba_shuffle; + const bool shuffle_coords = ((first_x ^ first_u) & 0xF) == 8 || rgba_shuffle; + // Round up half of second coord, it can sometimes be slightly under. const int draw_width = std::abs(v[1].XYZ.X + 9 - v[0].XYZ.X) >> 4; const int read_width = std::abs(second_u - first_u); @@ -3161,14 +3164,13 @@ void GSRendererHW::Draw() // Don't resize if the BPP don't match. if (frame_psm.bpp == GSLocalMemory::m_psm[rt->m_TEX0.PSM].bpp) { - if (m_r.w > rt->m_unscaled_size.y) + if (m_r.w > rt->m_unscaled_size.y || m_r.z > rt->m_unscaled_size.x) { - u32 new_height = m_r.w; + u32 new_height = std::max(m_r.w, rt->m_unscaled_size.y); + u32 new_width = std::max(m_r.z, rt->m_unscaled_size.x); - if (possible_shuffle && std::abs(static_cast(GSLocalMemory::m_psm[rt->m_TEX0.PSM].bpp - GSLocalMemory::m_psm[TEX0.PSM].bpp)) == 16) - new_height /= 2; //DevCon.Warning("Resizing texture %d x %d draw %d", rt->m_unscaled_size.x, new_height, s_n); - rt->ResizeTexture(rt->m_unscaled_size.x, new_height); + rt->ResizeTexture(new_height, new_height); const bool frame_masked = ((m_cached_ctx.FRAME.FBMSK & frame_psm.fmsk) == frame_psm.fmsk) || (m_cached_ctx.TEST.ATE && m_cached_ctx.TEST.ATST == ATST_NEVER && !(m_cached_ctx.TEST.AFAIL & AFAIL_FB_ONLY)); @@ -3773,7 +3775,7 @@ void GSRendererHW::Draw() if (rt && GSConfig.SaveRT) { - s = GetDrawDumpPath("%05d_f%05lld_rt0_%05x_%s.bmp", s_n, frame, m_cached_ctx.FRAME.Block(), psm_str(m_cached_ctx.FRAME.PSM)); + s = GetDrawDumpPath("%05d_f%05lld_rt0_%05x_(%05x)_%s.bmp", s_n, frame, m_cached_ctx.FRAME.Block(), rt->m_TEX0.TBP0, psm_str(m_cached_ctx.FRAME.PSM)); if (rt->m_texture) rt->m_texture->Save(s); @@ -3781,7 +3783,7 @@ void GSRendererHW::Draw() if (ds && GSConfig.SaveDepth) { - s = GetDrawDumpPath("%05d_f%05lld_rz0_%05x_%s.bmp", s_n, frame, m_cached_ctx.ZBUF.Block(), psm_str(m_cached_ctx.ZBUF.PSM)); + s = GetDrawDumpPath("%05d_f%05lld_rz0_%05x_(%05x)_%s.bmp", s_n, frame, m_cached_ctx.ZBUF.Block(), ds->m_TEX0.TBP0, psm_str(m_cached_ctx.ZBUF.PSM)); if (g_texture_cache->GetTemporaryZ()) g_texture_cache->GetTemporaryZ()->Save(s); diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index ff57ad7796..c4d0a0a129 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -149,8 +149,7 @@ void GSTextureCache::AddDirtyRectTarget(Target* target, GSVector4i rect, u32 psm if (rect.rempty()) return; - if (rect.w > 2048) - DevCon.Warning("BAd"); + std::vector::iterator it = target->m_dirty.end(); while (it != target->m_dirty.begin()) { @@ -337,8 +336,22 @@ GSVector4i GSTextureCache::TranslateAlignedRectByPage(u32 tbp, u32 tebp, u32 tbw // Results won't be square, if it's not invalidation, it's a texture, which is problematic to translate, so let's not (FIFA 2005). if (!is_invalidation) { - DevCon.Warning("Uneven pages mess up sbp %x dbp %x spgw %d dpgw %d", sbp, tbp, src_pgw, dst_pgw); - return GSVector4i::zero(); + if (sbp != tbp) + { + // Just take the start page, as this is likely tex in rt, and that's all we care about. + const u32 start_page = (in_rect.y / src_page_size.y) + (in_rect.x / src_page_size.x); + in_rect.x = (start_page % dst_pgw) * dst_page_size.x; + in_rect.y = (start_page / dst_pgw) * dst_page_size.y; + in_rect.z = in_rect.x + dst_page_size.x; + in_rect.w = in_rect.y + dst_page_size.y; + + return in_rect; + } + else + { + DevCon.Warning("Uneven pages mess up sbp %x dbp %x spgw %d dpgw %d", sbp, tbp, src_pgw, dst_pgw); + return GSVector4i::zero(); + } } //TODO: Maybe control dirty blocks directly and add them page at a time for better granularity. @@ -1465,13 +1478,16 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const if (!t->HasValidBitsForFormat(psm, req_color, req_alpha) && !(possible_shuffle && GSLocalMemory::m_psm[psm].bpp == 16 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == 32)) continue; - if (GSLocalMemory::m_psm[color_psm].bpp == 16 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp && (t->m_TEX0.TBW != bw && (t->m_TEX0.TBW * 2) != bw)) + u32 horz_page_offset = ((bp - t->m_TEX0.TBP0) >> 5) % t->m_TEX0.TBW; + if (GSLocalMemory::m_psm[color_psm].bpp == 16 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == 32 && bw != 1 && + ((t->m_TEX0.TBW < (horz_page_offset + ((block_boundary_rect.z + GSLocalMemory::m_psm[psm].pgs.x - 1) / GSLocalMemory::m_psm[psm].pgs.x)) || + (t->m_TEX0.TBW != bw && block_boundary_rect.w > GSLocalMemory::m_psm[psm].pgs.y)))) { DevCon.Warning("BP %x - 16bit bad match for target bp %x bw %d src %d format %d", bp, t->m_TEX0.TBP0, t->m_TEX0.TBW, bw, t->m_TEX0.PSM); continue; } - else if (!possible_shuffle && (GSLocalMemory::m_psm[color_psm].bpp == 8 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == 32 && - !((t->m_TEX0.TBW == (bw / 2)) || (t->m_TEX0.TBW >= (bw / 2) && (req_rect.w < GSLocalMemory::m_psm[psm].pgs.y))))) + else if (!possible_shuffle && (GSLocalMemory::m_psm[color_psm].bpp == 8 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == 32 && bw != 1 && + !((t->m_TEX0.TBW == (bw / 2)) || (t->m_TEX0.TBW >= (bw / 2) && (block_boundary_rect.w <= GSLocalMemory::m_psm[psm].pgs.y))))) { DevCon.Warning("BP %x - 8bit bad match for target bp %x bw %d src %d format %d", bp, t->m_TEX0.TBP0, t->m_TEX0.TBW, bw, t->m_TEX0.PSM); continue; @@ -1935,21 +1951,17 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe }*/ const u32 widthpage_offset = (std::abs(static_cast(bp - t->m_TEX0.TBP0)) >> 5) % std::max(t->m_TEX0.TBW, 1U); - const bool is_aligned_ok = widthpage_offset == 0 || (t->m_TEX0.TBW == TEX0.TBW && ((min_rect.z >> 6) + widthpage_offset) <= TEX0.TBW) || ((widthpage_offset + TEX0.TBW) <= t->m_TEX0.TBW) || min_rect.width() <= 64 || (widthpage_offset == (t->m_TEX0.TBW >> 1) && (static_cast(min_rect.width()) <= (widthpage_offset * 64))); - if ((!dst || ((GSState::s_n - dst->m_last_draw) < (GSState::s_n - t->m_last_draw))) && is_aligned_ok && (t->m_TEX0.TBW == TEX0.TBW || (TEX0.TBW == 1 && t->m_TEX0.TBW > 1)) && t->Overlaps(bp, TEX0.TBW, TEX0.PSM, min_rect)) - { /*TEX0.TBP0 == ((((t->UnwrappedEndBlock() + 1) - t->m_TEX0.TBP0) >> 1) + t->m_TEX0.TBP0)*/ - // If it's too old, it's probably not a real target to jump in to anymore. - /*if ((GSState::s_n - t->m_last_draw) > 10 && (!t->m_dirty.empty() || (!is_shuffle && - !(widthpage_offset == 0 || min_rect.width() <= 64 || - (widthpage_offset == (t->m_TEX0.TBW >> 1) && min_rect.width() == widthpage_offset * 64))))) - { - GL_INS("TC: Deleting RT BP 0x%x BW %d PSM %s due to change in target", t->m_TEX0.TBP0, t->m_TEX0.TBW, psm_str(t->m_TEX0.PSM)); - InvalidateSourcesFromTarget(t); - i = list.erase(i); - delete t; - } - else*/ - if (!is_shuffle && !GSUtil::HasSameSwizzleBits(t->m_TEX0.PSM, TEX0.PSM)) + const bool is_aligned_ok = widthpage_offset == 0 || (t->m_TEX0.TBW == TEX0.TBW && + ((((min_rect.z + 63) >> 6) + widthpage_offset) <= TEX0.TBW) || + ((widthpage_offset + TEX0.TBW) <= t->m_TEX0.TBW) || + min_rect.width() <= 64 || (widthpage_offset == (t->m_TEX0.TBW >> 1) && + (static_cast(min_rect.width()) <= (widthpage_offset * 64)))); + if ((!dst || ((GSState::s_n - dst->m_last_draw) < (GSState::s_n - t->m_last_draw))) && is_aligned_ok && (t->m_TEX0.TBW == TEX0.TBW || TEX0.TBW == 1) && t->Overlaps(bp, TEX0.TBW, TEX0.PSM, min_rect)) + { + const GSLocalMemory::psm_t& s_psm = GSLocalMemory::m_psm[TEX0.PSM]; + + if (!is_shuffle && (!GSUtil::HasSameSwizzleBits(t->m_TEX0.PSM, TEX0.PSM) || + (widthpage_offset % std::max(t->m_TEX0.TBW, 1U)) != 0 && ((widthpage_offset + (min_rect.width() + (s_psm.pgs.x - 1)) / s_psm.pgs.x)) > t->m_TEX0.TBW)) { GL_INS("TC: Deleting RT BP 0x%x BW %d PSM %s due to change in target", t->m_TEX0.TBP0, t->m_TEX0.TBW, psm_str(t->m_TEX0.PSM)); InvalidateSourcesFromTarget(t); @@ -1958,7 +1970,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe continue; } - else + else if (t->m_dirty.empty()) { //DevCon.Warning("Here draw %d wanted %x PSM %x got %x PSM %x offset of %d pages width %d pages draw width %d", GSState::s_n, bp, TEX0.PSM, t->m_TEX0.TBP0, t->m_TEX0.PSM, (bp - t->m_TEX0.TBP0) >> 5, t->m_TEX0.TBW, draw_rect.width()); dst = t; @@ -2136,27 +2148,29 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe new_scaled_size = ScaleRenderTargetSize(dst->m_unscaled_size, scale); dRect = (GSVector4(GSVector4i::loadh(dst->m_unscaled_size)) * GSVector4(scale)).ceil(); - - if (scale_down) + if (!is_shuffle || GSLocalMemory::m_psm[dst->m_TEX0.PSM].bpp == 16) { - if ((new_size.y * 2) < 1024) + if (scale_down) { - new_scaled_size.y *= 2; - new_size.y *= 2; - dst->m_valid.y *= 2; - dst->m_valid.w *= 2; + if ((new_size.y * 2) < 1024) + { + new_scaled_size.y *= 2; + new_size.y *= 2; + dst->m_valid.y *= 2; + dst->m_valid.w *= 2; + } + dRect.y *= 2; + dRect.w *= 2; + } + else + { + new_scaled_size.y /= 2; + new_size.y /= 2; + dRect.y /= 2; + dRect.w /= 2; + dst->m_valid.y /= 2; + dst->m_valid.w /= 2; } - dRect.y *= 2; - dRect.w *= 2; - } - else - { - new_scaled_size.y /= 2; - new_size.y /= 2; - dRect.y /= 2; - dRect.w /= 2; - dst->m_valid.y /= 2; - dst->m_valid.w /= 2; } if (!is_shuffle) { @@ -2188,9 +2202,16 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe dst->m_texture = tex; dst->m_unscaled_size = new_size; } - // New format or doing a shuffle to a 32bit target that used to be 16bit - dst->m_TEX0.PSM = TEX0.PSM; + // New format or doing a shuffle to a 32bit target that used to be 16bit + if (!is_shuffle) + dst->m_TEX0.PSM = TEX0.PSM; + // LEGO Dome Racers does a copy to a target as 8bit in alpha only, this doesn't really work great for us, so let's make it 32bit with invalid RGB. + else if (dst->m_TEX0.PSM == PSMT8H) + { + //dst->m_TEX0.PSM = PSMCT32; + dst->m_valid_rgb = false; + } } // If our RGB was invalidated, we need to pull it from depth. @@ -2315,7 +2336,15 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe { continue; } - + // If the format is completely different, but it's the same location, it's likely just overwriting it, so get rid. + if (!is_shuffle && t->m_TEX0.TBW != TEX0.TBW && TEX0.TBW != 1 && !preserve_rgb && min_rect.w > GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y) + { + DevCon.Warning("Deleting Z draw %d", GSState::s_n); + InvalidateSourcesFromTarget(t); + i = rev_list.erase(i); + delete t; + continue; + } const GSLocalMemory::psm_t& t_psm_s = GSLocalMemory::m_psm[t->m_TEX0.PSM]; if (t_psm_s.bpp != psm_s.bpp) { @@ -2486,7 +2515,8 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe pxAssert(dst && dst->m_texture && dst->m_scale == scale); } - + if (dst && dst->m_TEX0.TBP0 == 0x3f80 && dst->m_TEX0.PSM == 0) + DevCon.Warning("It's 32bit on draw %d", GSState::s_n); return dst; } @@ -2819,6 +2849,7 @@ bool GSTextureCache::PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, cons GL_INS("RT double buffer copy from FBP 0x%x, %dx%d => %d,%d", t->m_TEX0.TBP0, copy_width, copy_height, 0, dst_offset_scaled_height); + // Clear the dirty first t->Update(); dst->Update(); From 5b93d2642bded8cbabc51c1ecc5b26d378d2b3c5 Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Sun, 12 Jan 2025 06:33:38 +0000 Subject: [PATCH 092/162] GS/HW: More changes some regressions --- pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 15 +- pcsx2/GS/Renderers/HW/GSTextureCache.cpp | 264 ++++++++++++++--------- 2 files changed, 170 insertions(+), 109 deletions(-) diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index 2ff193c047..07c2f4930c 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -2741,7 +2741,7 @@ void GSRendererHW::Draw() shuffle_target = shuffle_coords && (draw_width & 7) == 0 && std::abs(draw_width - read_width) <= 1; } - if (m_cached_ctx.FRAME.Block() != m_cached_ctx.TEX0.TBP0 || !shuffle_target) + if (!shuffle_target) { // FBW is going to be wrong for channel shuffling into a new target, so take it from the source. FRAME_TEX0.U64 = 0; @@ -2827,7 +2827,7 @@ void GSRendererHW::Draw() // Urban Reign trolls by scissoring a draw to a target at 0x0-0x117F to 378x449 which ends up the size being rounded up to 640x480 // causing the buffer to expand to around 0x1400, which makes a later framebuffer at 0x1180 to fail to be created correctly. // We can cheese this by checking if the Z is masked and the resultant colour is going to be black anyway. - const bool output_black = PRIM->ABE && ((m_context->ALPHA.A == 1 && m_context->ALPHA.B == 0 && GetAlphaMinMax().min >= 128) || m_context->ALPHA.IsBlack()) && m_draw_env->COLCLAMP.CLAMP == 1; + const bool output_black = PRIM->ABE && ((m_context->ALPHA.A == 1 || m_context->ALPHA.IsBlack()) && m_context->ALPHA.D != 1) && m_draw_env->COLCLAMP.CLAMP == 1; const bool can_expand = !(m_cached_ctx.ZBUF.ZMSK && output_black); // Estimate size based on the scissor rectangle and height cache. @@ -3064,7 +3064,7 @@ void GSRendererHW::Draw() { int vertical_offset = ((static_cast(m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0) / 32) / std::max(static_cast(rt->m_TEX0.TBW), 1)) * frame_psm.pgs.y; // I know I could just not shift it.. int texture_offset = 0; - const int horizontal_offset = ((static_cast((m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0)) / 32) % static_cast(std::max(rt->m_TEX0.TBW, 1U))) * frame_psm.pgs.x; + int horizontal_offset = ((static_cast((m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0)) / 32) % static_cast(std::max(rt->m_TEX0.TBW, 1U))) * frame_psm.pgs.x; // Used to reduce the offset made later in channel shuffles m_target_offset = std::abs(static_cast((m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0)) >> 5); @@ -3103,6 +3103,13 @@ void GSRendererHW::Draw() vertical_offset = 0; } + if (horizontal_offset < 0) + { + // Thankfully this doesn't really happen, but catwoman moves the framebuffer backwards 1 page with a channel shuffle, which is really messy and not easy to deal with. + // Hopefully the quick channel shuffle will just guess this and run with it. + rt->m_TEX0.TBP0 += horizontal_offset; + horizontal_offset = 0; + } // Z isn't offset but RT is, so we need a temp Z to align it, hopefully nothing will ever write to the Z too, right?? if (ds && vertical_offset && (m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0) != (m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0)) { @@ -3152,7 +3159,7 @@ void GSRendererHW::Draw() m_r.w += vertical_offset; m_r.x += horizontal_offset; m_r.z += horizontal_offset; - m_in_target_draw = true; + m_in_target_draw = rt->m_TEX0.TBP0 != m_cached_ctx.FRAME.Block(); m_vt.m_min.p.x += horizontal_offset; m_vt.m_max.p.x += horizontal_offset; m_vt.m_min.p.y += vertical_offset; diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index c4d0a0a129..dd42d0f10f 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -1863,6 +1863,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe Target* dst = nullptr; auto& list = m_dst[type]; + const GSVector4i min_rect = draw_rect.max_u32(GSVector4i(0, 0, draw_rect.x, draw_rect.y)); // TODO: Move all frame stuff to its own routine too. if (!is_frame) @@ -1951,12 +1952,18 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe }*/ const u32 widthpage_offset = (std::abs(static_cast(bp - t->m_TEX0.TBP0)) >> 5) % std::max(t->m_TEX0.TBW, 1U); - const bool is_aligned_ok = widthpage_offset == 0 || (t->m_TEX0.TBW == TEX0.TBW && + /*const bool is_aligned_ok = widthpage_offset == 0 || (t->m_TEX0.TBW == TEX0.TBW && ((((min_rect.z + 63) >> 6) + widthpage_offset) <= TEX0.TBW) || ((widthpage_offset + TEX0.TBW) <= t->m_TEX0.TBW) || min_rect.width() <= 64 || (widthpage_offset == (t->m_TEX0.TBW >> 1) && - (static_cast(min_rect.width()) <= (widthpage_offset * 64)))); - if ((!dst || ((GSState::s_n - dst->m_last_draw) < (GSState::s_n - t->m_last_draw))) && is_aligned_ok && (t->m_TEX0.TBW == TEX0.TBW || TEX0.TBW == 1) && t->Overlaps(bp, TEX0.TBW, TEX0.PSM, min_rect)) + (static_cast(min_rect.width()) <= (widthpage_offset * 64))));*/ + const bool is_aligned_ok = widthpage_offset == 0 || ((min_rect.width() <= static_cast((t->m_TEX0.TBW - widthpage_offset) * 64) && (t->m_TEX0.TBW == TEX0.TBW || TEX0.TBW == 1)) && bp >= t->m_TEX0.TBP0); + const bool no_target_or_newer = (!dst || ((GSState::s_n - dst->m_last_draw) < (GSState::s_n - t->m_last_draw))); + const bool width_match = (t->m_TEX0.TBW == TEX0.TBW || TEX0.TBW == 1); + // if it's a shuffle, some games tend to offset back by a page, such as Tomb Raider, for no disernable reason, but it then causes problems. + // This can also happen horizontally (Catwoman moves everything one page left with shuffles), but this is too messy to deal with right now. + const bool overlaps = t->Overlaps(bp, TEX0.TBW, TEX0.PSM, min_rect) || (is_shuffle && t->Overlaps(bp, TEX0.TBW, TEX0.PSM, min_rect + GSVector4i(0, 0, 0, 32))); + if (no_target_or_newer && is_aligned_ok && width_match && overlaps) { const GSLocalMemory::psm_t& s_psm = GSLocalMemory::m_psm[TEX0.PSM]; @@ -2204,12 +2211,12 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe } // New format or doing a shuffle to a 32bit target that used to be 16bit - if (!is_shuffle) + if (!is_shuffle || GSLocalMemory::m_psm[dst->m_TEX0.PSM].bpp < GSLocalMemory::m_psm[TEX0.PSM].bpp) dst->m_TEX0.PSM = TEX0.PSM; // LEGO Dome Racers does a copy to a target as 8bit in alpha only, this doesn't really work great for us, so let's make it 32bit with invalid RGB. else if (dst->m_TEX0.PSM == PSMT8H) { - //dst->m_TEX0.PSM = PSMCT32; + dst->m_TEX0.PSM = PSMCT32; dst->m_valid_rgb = false; } } @@ -2515,8 +2522,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe pxAssert(dst && dst->m_texture && dst->m_scale == scale); } - if (dst && dst->m_TEX0.TBP0 == 0x3f80 && dst->m_TEX0.PSM == 0) - DevCon.Warning("It's 32bit on draw %d", GSState::s_n); + return dst; } @@ -2536,7 +2542,8 @@ GSTextureCache::Target* GSTextureCache::CreateTarget(GIFRegTEX0 TEX0, const GSVe // Avoid making garbage targets (usually PCRTC). if (GSVector4i::loadh(size).rempty()) return nullptr; - + if (TEX0.TBP0 == 0x3320 || TEX0.TBP0 == 0x32a0) + DevCon.Warning("Making target %x on draw %d", TEX0.TBP0, GSState::s_n); Target* dst = Target::Create(TEX0, size.x, size.y, scale, type, true); if (!dst) [[unlikely]] return nullptr; @@ -2787,113 +2794,113 @@ bool GSTextureCache::PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, cons auto j = i; Target* t = *j; - if (dst != t && t->m_TEX0.PSM == dst->m_TEX0.PSM/* && t->m_TEX0.TBW == dst->m_TEX0.TBW*/) - if (t->Overlaps(dst->m_TEX0.TBP0, dst->m_TEX0.TBW, dst->m_TEX0.PSM, dst->m_valid)) + if (dst != t && t->m_TEX0.PSM == dst->m_TEX0.PSM && t->Overlaps(dst->m_TEX0.TBP0, dst->m_TEX0.TBW, dst->m_TEX0.PSM, dst->m_valid) && + static_cast(((t->m_TEX0.TBP0 - dst->m_TEX0.TBP0) / 32) % std::max(dst->m_TEX0.TBW, 1U)) <= std::max(0, static_cast(dst->m_TEX0.TBW - t->m_TEX0.TBW))) + { + const u32 buffer_width = std::max(1U, dst->m_TEX0.TBW); + + // If the two targets are misaligned, it's likely a relocation, so we can just kill the old target. + // Kill targets that are overlapping new targets, but ignore the copy if the old target is dirty because we favour GS memory. + if (((((t->m_TEX0.TBP0 - dst->m_TEX0.TBP0) >> 5) % buffer_width) != 0) && !t->m_dirty.empty()) { - const u32 buffer_width = std::max(1U, dst->m_TEX0.TBW); + InvalidateSourcesFromTarget(t); + i = list.erase(j); + delete t; - // If the two targets are misaligned, it's likely a relocation, so we can just kill the old target. - // Kill targets that are overlapping new targets, but ignore the copy if the old target is dirty because we favour GS memory. - if (((((t->m_TEX0.TBP0 - dst->m_TEX0.TBP0) >> 5) % buffer_width) != 0) && !t->m_dirty.empty()) + continue; + } + + // could be overwriting a double buffer, so if it's the second half of it, just reduce the size down to half. + if (((((t->UnwrappedEndBlock() + 1) - t->m_TEX0.TBP0) >> 1) + t->m_TEX0.TBP0) == dst->m_TEX0.TBP0) + { + GSVector4i new_valid = t->m_valid; + new_valid.w /= 2; + GL_INS("RT resize buffer for FBP 0x%x, %dx%d => %d,%d", t->m_TEX0.TBP0, t->m_valid.width(), t->m_valid.height(), new_valid.width(), new_valid.height()); + t->ResizeValidity(new_valid); + return hw_clear.value_or(false); + } + // The new texture is behind it but engulfs the whole thing, shrink the new target so it grows in the HW Draw resize. + else if (dst->m_TEX0.TBP0 < t->m_TEX0.TBP0 && (dst->UnwrappedEndBlock() + 1) > t->m_TEX0.TBP0) + { + const int rt_pages = ((t->UnwrappedEndBlock() + 1) - t->m_TEX0.TBP0) >> 5; + const int overlapping_pages = std::min(rt_pages, static_cast((dst->UnwrappedEndBlock() + 1) - t->m_TEX0.TBP0) >> 5); + const int overlapping_pages_height = ((overlapping_pages + (buffer_width - 1)) / buffer_width) * GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y; + + if (overlapping_pages_height == 0 || (overlapping_pages % buffer_width)) { - InvalidateSourcesFromTarget(t); - i = list.erase(j); - delete t; - + // No overlap top copy or the widths don't match. + i++; continue; } - // could be overwriting a double buffer, so if it's the second half of it, just reduce the size down to half. - if (((((t->UnwrappedEndBlock() + 1) - t->m_TEX0.TBP0) >> 1) + t->m_TEX0.TBP0) == dst->m_TEX0.TBP0) + const int dst_offset_height = ((((t->m_TEX0.TBP0 - dst->m_TEX0.TBP0) >> 5) / buffer_width) * GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y); + const int texture_height = (dst->m_TEX0.TBW == t->m_TEX0.TBW) ? (dst_offset_height + t->m_valid.w) : (dst_offset_height + overlapping_pages_height); + + if (texture_height > dst->m_unscaled_size.y && !dst->ResizeTexture(dst->m_unscaled_size.x, texture_height, true)) { - GSVector4i new_valid = t->m_valid; - new_valid.w /= 2; - GL_INS("RT resize buffer for FBP 0x%x, %dx%d => %d,%d", t->m_TEX0.TBP0, t->m_valid.width(), t->m_valid.height(), new_valid.width(), new_valid.height()); - t->ResizeValidity(new_valid); - return hw_clear.value_or(false); + // Resize failed, probably ran out of VRAM, better luck next time. Fall back to CPU. + DevCon.Warning("Failed to resize target on preload? Draw %d", GSState::s_n); + i++; + continue; } - // The new texture is behind it but engulfs the whole thing, shrink the new target so it grows in the HW Draw resize. - else if (dst->m_TEX0.TBP0 < t->m_TEX0.TBP0 && (dst->UnwrappedEndBlock() + 1) > t->m_TEX0.TBP0) + + const int dst_offset_width = (((t->m_TEX0.TBP0 - dst->m_TEX0.TBP0) >> 5) % buffer_width) * GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.x; + const int dst_offset_scaled_width = dst_offset_width * dst->m_scale; + const int dst_offset_scaled_height = dst_offset_height * dst->m_scale; + const GSVector4i dst_rect_scale = GSVector4i(t->m_valid.x, dst_offset_height, t->m_valid.z, texture_height); + + if (((!hw_clear && (preserve_target || preload)) || dst_rect_scale.rintersect(draw_rect).rempty()) && dst->GetScale() == t->GetScale()) { - const int rt_pages = ((t->UnwrappedEndBlock() + 1) - t->m_TEX0.TBP0) >> 5; - const int overlapping_pages = std::min(rt_pages, static_cast((dst->UnwrappedEndBlock() + 1) - t->m_TEX0.TBP0) >> 5); - const int overlapping_pages_height = ((overlapping_pages + (buffer_width - 1)) / buffer_width) * GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y; + int copy_width = ((t->m_texture->GetWidth()) > (dst->m_texture->GetWidth()) ? (dst->m_texture->GetWidth()) : t->m_texture->GetWidth()) - dst_offset_scaled_width; + int copy_height = (texture_height - dst_offset_height) * t->m_scale; - if (overlapping_pages_height == 0 || (overlapping_pages % buffer_width)) - { - // No overlap top copy or the widths don't match. - i++; - continue; - } - - const int dst_offset_height = ((((t->m_TEX0.TBP0 - dst->m_TEX0.TBP0) >> 5) / buffer_width) * GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y); - const int texture_height = (dst->m_TEX0.TBW == t->m_TEX0.TBW) ? (dst_offset_height + t->m_valid.w) : (dst_offset_height + overlapping_pages_height); - - if (texture_height > dst->m_unscaled_size.y && !dst->ResizeTexture(dst->m_unscaled_size.x, texture_height, true)) - { - // Resize failed, probably ran out of VRAM, better luck next time. Fall back to CPU. - DevCon.Warning("Failed to resize target on preload? Draw %d", GSState::s_n); - i++; - continue; - } - - const int dst_offset_width = (((t->m_TEX0.TBP0 - dst->m_TEX0.TBP0) >> 5) % buffer_width) * GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.x; - const int dst_offset_scaled_width = dst_offset_width * dst->m_scale; - const int dst_offset_scaled_height = dst_offset_height * dst->m_scale; - const GSVector4i dst_rect_scale = GSVector4i(t->m_valid.x, dst_offset_height, t->m_valid.z, texture_height); - - if (((!hw_clear && (preserve_target || preload)) || dst_rect_scale.rintersect(draw_rect).rempty()) && dst->GetScale() == t->GetScale()) - { - int copy_width = ((t->m_texture->GetWidth()) > (dst->m_texture->GetWidth()) ? (dst->m_texture->GetWidth()) : t->m_texture->GetWidth()) - dst_offset_scaled_width; - int copy_height = (texture_height - dst_offset_height) * t->m_scale; - - GL_INS("RT double buffer copy from FBP 0x%x, %dx%d => %d,%d", t->m_TEX0.TBP0, copy_width, copy_height, 0, dst_offset_scaled_height); + GL_INS("RT double buffer copy from FBP 0x%x, %dx%d => %d,%d", t->m_TEX0.TBP0, copy_width, copy_height, 0, dst_offset_scaled_height); - // Clear the dirty first - t->Update(); - dst->Update(); + // Clear the dirty first + t->Update(); + dst->Update(); - // Clamp it if it gets too small, shouldn't happen but stranger things have happened. - if (copy_width < 0) - { - copy_width = 0; - } - - // Invalidate has been moved to after DrawPrims(), because we might kill the current sources' backing. - if (!t->m_valid_rgb || !(t->m_valid_alpha_high || t->m_valid_alpha_low) || t->m_scale != dst->m_scale) - { - const GSVector4 src_rect = GSVector4(0, 0, copy_width, copy_height) / (GSVector4(t->m_texture->GetSize()).xyxy()); - const GSVector4 dst_rect = GSVector4(dst_offset_scaled_width, dst_offset_scaled_height, dst_offset_scaled_width + copy_width, dst_offset_scaled_height + copy_height); - g_gs_device->StretchRect(t->m_texture, src_rect, dst->m_texture, dst_rect, t->m_valid_rgb, t->m_valid_rgb, t->m_valid_rgb, t->m_valid_alpha_high || t->m_valid_alpha_low); - } - else - { - if ((copy_width + dst_offset_scaled_width) > (dst->m_unscaled_size.x * dst->m_scale) || (copy_height + dst_offset_scaled_height) > (dst->m_unscaled_size.y * dst->m_scale)) - { - copy_width = std::min(copy_width, static_cast((dst->m_unscaled_size.x * dst->m_scale) - dst_offset_scaled_width)); - copy_height = std::min(copy_height, static_cast((dst->m_unscaled_size.y * dst->m_scale) - dst_offset_scaled_height)); - } - - g_gs_device->CopyRect(t->m_texture, dst->m_texture, GSVector4i(0, 0, copy_width, copy_height), dst_offset_scaled_width, dst_offset_scaled_height); - } - } - - // src is using this target, so point it at the new copy. - if (src && src->m_target && src->m_from_target == t) + // Clamp it if it gets too small, shouldn't happen but stranger things have happened. + if (copy_width < 0) { - src->m_from_target = dst; - src->m_texture = dst->m_texture; - src->m_region.SetY(src->m_region.GetMinY() + dst_offset_height, src->m_region.GetMaxY() + dst_offset_height); - src->m_region.SetX(src->m_region.GetMinX() + dst_offset_width, src->m_region.GetMaxX() + dst_offset_width); + copy_width = 0; } - InvalidateSourcesFromTarget(t); - i = list.erase(j); - delete t; - continue; + // Invalidate has been moved to after DrawPrims(), because we might kill the current sources' backing. + if (!t->m_valid_rgb || !(t->m_valid_alpha_high || t->m_valid_alpha_low) || t->m_scale != dst->m_scale) + { + const GSVector4 src_rect = GSVector4(0, 0, copy_width, copy_height) / (GSVector4(t->m_texture->GetSize()).xyxy()); + const GSVector4 dst_rect = GSVector4(dst_offset_scaled_width, dst_offset_scaled_height, dst_offset_scaled_width + copy_width, dst_offset_scaled_height + copy_height); + g_gs_device->StretchRect(t->m_texture, src_rect, dst->m_texture, dst_rect, t->m_valid_rgb, t->m_valid_rgb, t->m_valid_rgb, t->m_valid_alpha_high || t->m_valid_alpha_low); + } + else + { + if ((copy_width + dst_offset_scaled_width) > (dst->m_unscaled_size.x * dst->m_scale) || (copy_height + dst_offset_scaled_height) > (dst->m_unscaled_size.y * dst->m_scale)) + { + copy_width = std::min(copy_width, static_cast((dst->m_unscaled_size.x * dst->m_scale) - dst_offset_scaled_width)); + copy_height = std::min(copy_height, static_cast((dst->m_unscaled_size.y * dst->m_scale) - dst_offset_scaled_height)); + } + + g_gs_device->CopyRect(t->m_texture, dst->m_texture, GSVector4i(0, 0, copy_width, copy_height), dst_offset_scaled_width, dst_offset_scaled_height); + } } + + // src is using this target, so point it at the new copy. + if (src && src->m_target && src->m_from_target == t) + { + src->m_from_target = dst; + src->m_texture = dst->m_texture; + src->m_region.SetY(src->m_region.GetMinY() + dst_offset_height, src->m_region.GetMaxY() + dst_offset_height); + src->m_region.SetX(src->m_region.GetMinX() + dst_offset_width, src->m_region.GetMaxX() + dst_offset_width); + } + + InvalidateSourcesFromTarget(t); + i = list.erase(j); + delete t; + continue; } + } i++; } } @@ -3244,7 +3251,7 @@ bool GSTextureCache::PrepareDownloadTexture(u32 width, u32 height, GSTexture::Fo return true; } -void GSTextureCache::InvalidateContainedTargets(u32 start_bp, u32 end_bp, u32 write_psm, u32 write_bw) +/*void GSTextureCache::InvalidateContainedTargets(u32 start_bp, u32 end_bp, u32 write_psm, u32 write_bw) { const bool preserve_alpha = (GSLocalMemory::m_psm[write_psm].trbpp == 24); for (int type = 0; type < 2; type++) @@ -3261,16 +3268,63 @@ void GSTextureCache::InvalidateContainedTargets(u32 start_bp, u32 end_bp, u32 wr //const u32 total_pages = ((end_bp + 1) - t->m_TEX0.TBP0) >> 5; // Not covering the whole target, and a different format, so just dirty it. - /*if (start_bp >= t->m_TEX0.TBP0 && (t->UnwrappedEndBlock() > end_bp) && write_psm != t->m_TEX0.PSM && write_bw == t->m_TEX0.TBW) + //if (start_bp >= t->m_TEX0.TBP0 && (t->UnwrappedEndBlock() > end_bp) && write_psm != t->m_TEX0.PSM && write_bw == t->m_TEX0.TBW) + //{ + // const GSLocalMemory::psm_t& target_psm = GSLocalMemory::m_psm[write_psm]; + // const u32 page_offset = ((start_bp - t->m_TEX0.TBP0) >> 5); + // const u32 vertical_offset = (page_offset / t->m_TEX0.TBW) * target_psm.pgs.y; + // GSVector4i dirty_area = GSVector4i(page_offset % t->m_TEX0.TBW, vertical_offset, t->m_valid.z, vertical_offset + ((total_pages / t->m_TEX0.TBW) * target_psm.pgs.y)); + // InvalidateVideoMem(g_gs_renderer->m_mem.GetOffset(t->m_TEX0.TBP0, t->m_TEX0.TBW, t->m_TEX0.PSM), dirty_area, true); + // ++i; + // continue; + //} + + InvalidateSourcesFromTarget(t); + + t->m_valid_alpha_low &= preserve_alpha; + t->m_valid_alpha_high &= preserve_alpha; + t->m_valid_rgb &= !(t->m_TEX0.TBP0 == start_bp); + + // Don't keep partial depth buffers around. + if ((!t->m_valid_alpha_low && !t->m_valid_alpha_high && !t->m_valid_rgb) || type == DepthStencil) + { + auto& rev_list = m_dst[1 - type]; + for (auto j = rev_list.begin(); j != rev_list.end();) + { + Target* const rev_t = *j; + if (rev_t->m_TEX0.TBP0 == t->m_TEX0.TBP0 && GSLocalMemory::m_psm[rev_t->m_TEX0.PSM].bpp == GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp) + { + rev_t->m_was_dst_matched = false; + break; + } + ++j; + } + + GL_CACHE("TC: InvalidateContainedTargets: Remove Target %s[%x, %s]", to_string(type), t->m_TEX0.TBP0, psm_str(t->m_TEX0.PSM)); + i = list.erase(i); + delete t; + continue; + } + + GL_CACHE("TC: InvalidateContainedTargets: Clear RGB valid on %s[%x, %s]", to_string(type), t->m_TEX0.TBP0, psm_str(t->m_TEX0.PSM)); + ++i; + } + } +}*/ +void GSTextureCache::InvalidateContainedTargets(u32 start_bp, u32 end_bp, u32 write_psm, u32 write_bw) +{ + const bool preserve_alpha = (GSLocalMemory::m_psm[write_psm].trbpp == 24); + for (int type = 0; type < 2; type++) + { + auto& list = m_dst[type]; + for (auto i = list.begin(); i != list.end();) + { + Target* const t = *i; + if (start_bp != t->m_TEX0.TBP0 && (t->m_TEX0.TBP0 < start_bp || t->UnwrappedEndBlock() > end_bp)) { - const GSLocalMemory::psm_t& target_psm = GSLocalMemory::m_psm[write_psm]; - const u32 page_offset = ((start_bp - t->m_TEX0.TBP0) >> 5); - const u32 vertical_offset = (page_offset / t->m_TEX0.TBW) * target_psm.pgs.y; - GSVector4i dirty_area = GSVector4i(page_offset % t->m_TEX0.TBW, vertical_offset, t->m_valid.z, vertical_offset + ((total_pages / t->m_TEX0.TBW) * target_psm.pgs.y)); - InvalidateVideoMem(g_gs_renderer->m_mem.GetOffset(t->m_TEX0.TBP0, t->m_TEX0.TBW, t->m_TEX0.PSM), dirty_area, true); ++i; continue; - }*/ + } InvalidateSourcesFromTarget(t); From 710b07a857d055413aa88ecf82883d147cbafe61 Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Tue, 14 Jan 2025 13:47:18 +0000 Subject: [PATCH 093/162] GS/HW: Fix offset Z channel shuffle hazard. Adjust Tekken 5 CRC --- bin/resources/GameIndex.yaml | 11 +++++++++++ pcsx2/GS/Renderers/HW/GSHwHack.cpp | 24 +++++++++++++++--------- pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 3 ++- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/bin/resources/GameIndex.yaml b/bin/resources/GameIndex.yaml index b502bba95f..6d4d6e361c 100644 --- a/bin/resources/GameIndex.yaml +++ b/bin/resources/GameIndex.yaml @@ -1973,6 +1973,7 @@ SCAJ-20125: clampModes: eeClampMode: 2 # Fixes camera and stops constant coin noises on Pirates Cove. gsHWFixes: + textureInsideRT: 1 # Fixes heat haze half screen problem. alignSprite: 1 halfPixelOffset: 4 # Align post. nativeScaling: 1 # Fixes depth of field effect. @@ -1983,6 +1984,7 @@ SCAJ-20126: clampModes: eeClampMode: 2 # Fixes camera and stops constant coin noises on Pirates Cove. gsHWFixes: + textureInsideRT: 1 # Fixes heat haze half screen problem. alignSprite: 1 halfPixelOffset: 4 # Align post. nativeScaling: 1 # Fixes depth of field effect. @@ -2460,6 +2462,7 @@ SCAJ-20199: clampModes: eeClampMode: 2 # Fixes camera and stops constant coin noises on Pirates Cove. gsHWFixes: + textureInsideRT: 1 # Fixes heat haze half screen problem. alignSprite: 1 halfPixelOffset: 4 # Align post. nativeScaling: 1 # Fixes depth of field effect. @@ -4162,6 +4165,7 @@ SCED-53538: clampModes: eeClampMode: 2 # Fixes camera and stops constant coin noises on Pirates Cove. gsHWFixes: + textureInsideRT: 1 # Fixes heat haze half screen problem. alignSprite: 1 halfPixelOffset: 4 # Align post. nativeScaling: 1 # Fixes depth of field effect. @@ -5787,6 +5791,7 @@ SCES-53202: clampModes: eeClampMode: 2 # Fixes camera and stops constant coin noises on Pirates Cove. gsHWFixes: + textureInsideRT: 1 # Fixes heat haze half screen problem. alignSprite: 1 halfPixelOffset: 4 # Align post. nativeScaling: 1 # Fixes depth of field effect. @@ -7238,6 +7243,7 @@ SCKA-20049: clampModes: eeClampMode: 2 # Fixes camera and stops constant coin noises on Pirates Cove. gsHWFixes: + textureInsideRT: 1 # Fixes heat haze half screen problem. alignSprite: 1 halfPixelOffset: 4 # Align post. nativeScaling: 1 # Fixes depth of field effect. @@ -7463,6 +7469,7 @@ SCKA-20081: clampModes: eeClampMode: 2 # Fixes camera and stops constant coin noises on Pirates Cove. gsHWFixes: + textureInsideRT: 1 # Fixes heat haze half screen problem. alignSprite: 1 halfPixelOffset: 4 # Align post. nativeScaling: 1 # Fixes depth of field effect. @@ -57880,6 +57887,7 @@ SLPS-25510: clampModes: eeClampMode: 2 # Fixes camera and stops constant coin noises on Pirates Cove. gsHWFixes: + textureInsideRT: 1 # Fixes heat haze half screen problem. alignSprite: 1 # Fixes vertical lines. halfPixelOffset: 4 # Align post. nativeScaling: 1 # Fixes depth of field effect. @@ -61052,6 +61060,7 @@ SLPS-73223: clampModes: eeClampMode: 2 # Fixes camera and stops constant coin noises on Pirates Cove. gsHWFixes: + textureInsideRT: 1 # Fixes heat haze half screen problem. alignSprite: 1 # Fixes vertical lines. halfPixelOffset: 4 # Align post. nativeScaling: 1 # Fixes depth of field effect. @@ -67063,6 +67072,7 @@ SLUS-21059: clampModes: eeClampMode: 2 # Fixes camera and stops constant coin noises on Pirates Cove. gsHWFixes: + textureInsideRT: 1 # Fixes heat haze half screen problem. alignSprite: 1 # Fixes vertical lines. halfPixelOffset: 4 # Align post. nativeScaling: 1 # Fixes depth of field effect. @@ -67612,6 +67622,7 @@ SLUS-21160: clampModes: eeClampMode: 2 # Fixes camera and stops constant coin noises on Pirates Cove. gsHWFixes: + textureInsideRT: 1 # Fixes heat haze half screen problem. alignSprite: 1 # Fixes vertical lines. halfPixelOffset: 4 # Align post. nativeScaling: 1 # Fixes depth of field effect. diff --git a/pcsx2/GS/Renderers/HW/GSHwHack.cpp b/pcsx2/GS/Renderers/HW/GSHwHack.cpp index 3b20ad8e2d..e21b40ee84 100644 --- a/pcsx2/GS/Renderers/HW/GSHwHack.cpp +++ b/pcsx2/GS/Renderers/HW/GSHwHack.cpp @@ -248,19 +248,25 @@ bool GSHwHack::GSC_Tekken5(GSRendererHW& r, int& skip) if (!s_nativeres && r.PRIM->PRIM == GS_SPRITE && RTME && RTEX0.TFX == 1 && RFPSM == RTPSM && RTPSM == PSMCT32 && RFBMSK == 0xFF000000 && r.m_index.tail > 2) { + GSVertex* v = &r.m_vertex.buff[0]; // Don't enable hack on native res. // Fixes ghosting/blur effect and white lines appearing in stages: Moonfit Wilderness, Acid Rain - caused by upscaling. // Game copies the framebuffer as individual page rects with slight offsets (like 1/16 of a pixel etc) which doesn't wokr well with upscaling. // This should catch all the scenarios, maybe overdoes it, but it's for 1 game and it's non-detrimental, it's better than squares all over the screen. - const GSVector4i draw_size(r.m_vt.m_min.p.x, r.m_vt.m_min.p.y, r.m_vt.m_max.p.x + 1.0f, r.m_vt.m_max.p.y + 1.0f); - const GSVector4i read_size(r.m_vt.m_min.t.x, r.m_vt.m_min.t.y, r.m_vt.m_max.t.x + 0.5f, r.m_vt.m_max.t.y + 0.5f); - r.ReplaceVerticesWithSprite(draw_size, read_size, GSVector2i(read_size.width(), read_size.height()), draw_size); - } - else if (RZTST == 1 && RTME && (RFBP == 0x02bc0 || RFBP == 0x02be0 || RFBP == 0x02d00 || RFBP == 0x03480 || RFBP == 0x034a0) && RFPSM == RTPSM && RTBP0 == 0x00000 && RTPSM == PSMCT32) - { - // The moving display effect(flames) is not emulated properly in the entire screen so let's remove the effect in the stage: Burning Temple. Related to half screen bottom issue. - // Fixes black lines in the stage: Burning Temple - caused by upscaling. Note the black lines can also be fixed with Merge Sprite hack. - skip = 2; + if (v[0].XYZ.X & 0xF) + { + const GSVector4i draw_size(r.m_vt.m_min.p.x, r.m_vt.m_min.p.y, r.m_vt.m_max.p.x + 1.0f, r.m_vt.m_max.p.y + 1.0f); + const GSVector4i read_size(r.m_vt.m_min.t.x, r.m_vt.m_min.t.y, r.m_vt.m_max.t.x + 0.5f, r.m_vt.m_max.t.y + 0.5f); + r.ReplaceVerticesWithSprite(draw_size, read_size, GSVector2i(read_size.width(), read_size.height()), draw_size); + } + else + { + // Fixes the alignment of the two halves for the heat haze on the temple stage. + for (int i = 0; i < r.m_index.tail; i+=2) + { + v[i].XYZ.Y -= 0x8; + } + } } } diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index 07c2f4930c..3fb98d1384 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -5860,7 +5860,8 @@ __ri void GSRendererHW::HandleTextureHazards(const GSTextureCache::Target* rt, c GL_CACHE("Source is render target, taking copy."); src_target = rt; } - else if (ds && m_conf.tex == m_conf.ds) + // Be careful of single page channel shuffles where depth is the source but it's not going to the same place, we can't read this directly. + else if (ds && m_conf.tex == m_conf.ds && (!m_channel_shuffle || static_cast(m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0) == static_cast(m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0))) { // GL, Vulkan (in General layout), not DirectX! const bool can_read_current_depth_buffer = g_gs_device->Features().test_and_sample_depth; From fd81b47413a4ec8df7fa92c7c9008315c1a5c084 Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Tue, 14 Jan 2025 22:51:30 +0000 Subject: [PATCH 094/162] GS/HW: Fix some back to back shuffles and inside source invalidation --- pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 27 +++++++++++++----------- pcsx2/GS/Renderers/HW/GSRendererHW.h | 1 + pcsx2/GS/Renderers/HW/GSTextureCache.cpp | 21 ++++++++++-------- pcsx2/GS/Renderers/HW/GSTextureCache.h | 2 +- 4 files changed, 29 insertions(+), 22 deletions(-) diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index 3fb98d1384..8abbd15144 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -1015,8 +1015,8 @@ GSVector2i GSRendererHW::GetValidSize(const GSTextureCache::Source* tex) // Round up the page as channel shuffles are generally done in pages at a time // Keep in mind the source might be an 8bit texture - int src_width = tex->GetUnscaledWidth(); - int src_height = tex->GetUnscaledHeight(); + int src_width = tex->m_from_target ? tex->m_from_target->m_valid.width() : tex->GetUnscaledWidth(); + int src_height = tex->m_from_target ? tex->m_from_target->m_valid.height() : tex->GetUnscaledHeight(); if (!tex->m_from_target && GSLocalMemory::m_psm[tex->m_TEX0.PSM].bpp == 8) { @@ -2158,9 +2158,7 @@ void GSRendererHW::Draw() DumpVertices(s); } -#ifdef ENABLE_OGL_DEBUG static u32 num_skipped_channel_shuffle_draws = 0; -#endif // We mess with this state as an optimization, so take a copy and use that instead. const GSDrawingContext* context = m_context; @@ -2184,24 +2182,26 @@ void GSRendererHW::Draw() // Tomb Raider: Underworld does similar, except with R, G, B in separate palettes, therefore // we need to split on those too. m_channel_shuffle = IsPossibleChannelShuffle() && m_last_channel_shuffle_fbmsk == m_context->FRAME.FBMSK && - m_last_channel_shuffle_fbp <= m_context->FRAME.Block() && m_last_channel_shuffle_end_block > m_context->FRAME.Block(); + m_last_channel_shuffle_fbp <= m_context->FRAME.Block() && m_last_channel_shuffle_end_block > m_context->FRAME.Block() && + m_last_channel_shuffle_tbp <= m_context->TEX0.TBP0; -#ifdef ENABLE_OGL_DEBUG if (m_channel_shuffle) { + m_last_channel_shuffle_fbp = m_context->FRAME.Block(); + m_last_channel_shuffle_tbp = m_context->TEX0.TBP0; + num_skipped_channel_shuffle_draws++; return; } +#ifdef ENABLE_OGL_DEBUG if (num_skipped_channel_shuffle_draws > 0) - GL_INS("Skipped %u channel shuffle draws", num_skipped_channel_shuffle_draws); + GL_CACHE("Skipped %d channel shuffle draws ending at %d", num_skipped_channel_shuffle_draws, s_n); +#endif num_skipped_channel_shuffle_draws = 0; m_last_channel_shuffle_fbp = 0xffff; + m_last_channel_shuffle_tbp = 0xffff; m_last_channel_shuffle_end_block = 0xffff; -#else - if (m_channel_shuffle) - return; -#endif } GL_PUSH("HW Draw %d (Context %u)", s_n, PRIM->CTXT); @@ -3204,6 +3204,7 @@ void GSRendererHW::Draw() if (m_channel_shuffle) { m_last_channel_shuffle_fbp = rt->m_TEX0.TBP0; + m_last_channel_shuffle_tbp = src->m_TEX0.TBP0; // If it's a new target, we don't know where the end is as it's starting on a shuffle, so just do every shuffle following. m_last_channel_shuffle_end_block = (rt->m_last_draw >= s_n) ? (MAX_BLOCKS - 1) : (rt->m_end_block < rt->m_TEX0.TBP0 ? (rt->m_end_block + MAX_BLOCKS) : rt->m_end_block); @@ -3345,6 +3346,7 @@ void GSRendererHW::Draw() if (rt) { m_last_channel_shuffle_fbp = rt->m_TEX0.TBP0; + m_last_channel_shuffle_tbp = src->m_TEX0.TBP0; // Urban Chaos goes from Z16 to C32, so let's just use the rt's original end block. if (!src->m_from_target || GSLocalMemory::m_psm[src->m_from_target_TEX0.PSM].bpp != GSLocalMemory::m_psm[rt->m_TEX0.PSM].bpp) m_last_channel_shuffle_end_block = rt->m_end_block; @@ -5835,8 +5837,9 @@ __ri void GSRendererHW::HandleTextureHazards(const GSTextureCache::Target* rt, c bool& target_region, GSVector2i& unscaled_size, float& scale, GSDevice::RecycledTexture& src_copy) { - const int tex_diff = tex->m_from_target ? static_cast(m_cached_ctx.TEX0.TBP0 - tex->m_from_target->m_TEX0.TBP0) : 0; + const int tex_diff = tex->m_from_target ? static_cast(m_cached_ctx.TEX0.TBP0 - tex->m_from_target->m_TEX0.TBP0) : static_cast(m_cached_ctx.TEX0.TBP0 - tex->m_TEX0.TBP0); const int frame_diff = rt ? static_cast(m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0) : 0; + // Detect framebuffer read that will need special handling const GSTextureCache::Target* src_target = nullptr; diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.h b/pcsx2/GS/Renderers/HW/GSRendererHW.h index dddf4f2c2a..4e5f937b03 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.h +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.h @@ -174,6 +174,7 @@ private: u32 m_last_channel_shuffle_fbmsk = 0; u32 m_last_channel_shuffle_fbp = 0; + u32 m_last_channel_shuffle_tbp = 0; u32 m_last_channel_shuffle_end_block = 0; GIFRegFRAME m_split_clear_start = {}; diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index dd42d0f10f..72ad335193 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -2542,8 +2542,7 @@ GSTextureCache::Target* GSTextureCache::CreateTarget(GIFRegTEX0 TEX0, const GSVe // Avoid making garbage targets (usually PCRTC). if (GSVector4i::loadh(size).rempty()) return nullptr; - if (TEX0.TBP0 == 0x3320 || TEX0.TBP0 == 0x32a0) - DevCon.Warning("Making target %x on draw %d", TEX0.TBP0, GSState::s_n); + Target* dst = Target::Create(TEX0, size.x, size.y, scale, type, true); if (!dst) [[unlikely]] return nullptr; @@ -3428,6 +3427,12 @@ void GSTextureCache::InvalidateVideoMem(const GSOffset& off, const GSVector4i& r const u32 bw = off.bw(); const u32 psm = off.psm(); + // Get the bounds that we're invalidating in blocks, so we can remove any targets which are completely contained. + // Unfortunately sometimes the draw rect is incorrect, and since the end block gets the rect -1, it'll underflow, + // so we need to prevent that from happening. Just make it a single block in that case, and hope for the best. + const u32 start_bp = GSLocalMemory::GetStartBlockAddress(off.bp(), off.bw(), off.psm(), rect); + const u32 end_bp = rect.rempty() ? start_bp : GSLocalMemory::GetUnwrappedEndBlockAddress(off.bp(), off.bw(), off.psm(), rect); + if (!target) { // Remove Source that have same BP as the render target (color&dss) @@ -3438,7 +3443,7 @@ void GSTextureCache::InvalidateVideoMem(const GSOffset& off, const GSVector4i& r Source* s = *i; ++i; - if (GSUtil::HasSharedBits(bp, psm, s->m_TEX0.TBP0, s->m_TEX0.PSM) || + if ((GSUtil::HasSharedBits(psm, s->m_TEX0.PSM) && (bp >= start_bp && bp < end_bp)) || (GSUtil::HasSharedBits(bp, psm, s->m_from_target_TEX0.TBP0, s->m_TEX0.PSM) && s->m_target)) { m_src.RemoveAt(s); @@ -3535,11 +3540,6 @@ void GSTextureCache::InvalidateVideoMem(const GSOffset& off, const GSVector4i& r if (!target) return; - // Get the bounds that we're invalidating in blocks, so we can remove any targets which are completely contained. - // Unfortunately sometimes the draw rect is incorrect, and since the end block gets the rect -1, it'll underflow, - // so we need to prevent that from happening. Just make it a single block in that case, and hope for the best. - const u32 start_bp = GSLocalMemory::GetStartBlockAddress(off.bp(), off.bw(), off.psm(), rect); - const u32 end_bp = rect.rempty() ? start_bp : GSLocalMemory::GetUnwrappedEndBlockAddress(off.bp(), off.bw(), off.psm(), rect); RGBAMask rgba; rgba._u32 = GSUtil::GetChannelMask(psm); @@ -4819,6 +4819,9 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con src->m_texture = dst->m_texture; src->m_unscaled_size = dst->m_unscaled_size; src->m_shared_texture = true; + + if(channel_shuffle) + m_temporary_source = src; } // Invalidate immediately on recursive draws, because if we don't here, InvalidateVideoMem() will. @@ -5074,7 +5077,7 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con } // kill source immediately if it's the RT/DS, because that'll get invalidated immediately - if (GSRendererHW::GetInstance()->IsTBPFrameOrZ(dst->m_TEX0.TBP0)) + if (GSRendererHW::GetInstance()->IsTBPFrameOrZ(dst->m_TEX0.TBP0) || channel_shuffle) { GL_CACHE("TC: Source is RT or ZBUF, invalidating after draw."); m_temporary_source = src; diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.h b/pcsx2/GS/Renderers/HW/GSTextureCache.h index 8dc7315f55..7f7d02c3aa 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.h +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.h @@ -518,7 +518,7 @@ public: /// Removes any sources which point to the specified target. void InvalidateSourcesFromTarget(const Target* t); - /// Replaces a source's texture externally. Required for some CRC hacks. + /// Removes any sources which point to the same address as a new target. void ReplaceSourceTexture(Source* s, GSTexture* new_texture, float new_scale, const GSVector2i& new_unscaled_size, HashCacheEntry* hc_entry, bool new_texture_is_shared); From 671cc753b5e6565f2aca152f8cbc5656c8b4c4c5 Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Wed, 15 Jan 2025 00:52:59 +0000 Subject: [PATCH 095/162] GS/HW: Sync depth texture information when updating dst_match --- pcsx2/GS/Renderers/HW/GSTextureCache.cpp | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index 72ad335193..919ee4091a 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -1386,6 +1386,7 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const DevCon.Warning("Failed to update dst matched texture"); } t->m_valid_rgb = true; + t->m_TEX0 = dst_match->m_TEX0; break; } } @@ -4007,19 +4008,6 @@ bool GSTextureCache::Move(u32 SBP, u32 SBW, u32 SPSM, int sx, int sy, u32 DBP, u if (alpha_only && (!dst || GSLocalMemory::m_psm[dst->m_TEX0.PSM].bpp != 32)) return false; - // This is probably copying to a new buffer but using the original one as an offset, so better to use a new texture, if we don't find one. - if (dst && DBP == SBP && dy > dst->m_unscaled_size.y) - { - u32 new_DBP = DBP + (((dy / GSLocalMemory::m_psm[dst->m_TEX0.PSM].pgs.y) * DBW) << 5); - - dst = nullptr; - - DBP = new_DBP; - dy = 0; - - dst = GetExactTarget(DBP, DBW, dpsm_s.depth ? DepthStencil : RenderTarget, DBP); - } - // Beware of the case where a game might create a larger texture by moving a bunch of chunks around. if (dst && DBP == SBP && dy > dst->m_unscaled_size.y) { @@ -4032,7 +4020,7 @@ bool GSTextureCache::Move(u32 SBP, u32 SBW, u32 SPSM, int sx, int sy, u32 DBP, u dst = GetExactTarget(DBP, DBW, dpsm_s.depth ? DepthStencil : RenderTarget, DBP); } - + // Beware of the case where a game might create a larger texture by moving a bunch of chunks around. // We use dx/dy == 0 and the TBW check as a safeguard to make sure these go through to local memory. // We can also recreate the target if it's previously been created in the height cache with a valid size. From 17ac5b5e0677e798045a9e836f46901e9a854870 Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Thu, 16 Jan 2025 01:37:46 +0000 Subject: [PATCH 096/162] GS/HW: Fixes for Tex in RT and shuffle detection --- pcsx2/GS/Renderers/HW/GSHwHack.cpp | 3 ++- pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 16 +++++++++----- pcsx2/GS/Renderers/HW/GSTextureCache.cpp | 28 +++++++----------------- 3 files changed, 20 insertions(+), 27 deletions(-) diff --git a/pcsx2/GS/Renderers/HW/GSHwHack.cpp b/pcsx2/GS/Renderers/HW/GSHwHack.cpp index e21b40ee84..244b833ffc 100644 --- a/pcsx2/GS/Renderers/HW/GSHwHack.cpp +++ b/pcsx2/GS/Renderers/HW/GSHwHack.cpp @@ -1144,7 +1144,8 @@ bool GSHwHack::OI_SonicUnleashed(GSRendererHW& r, GSTexture* rt, GSTexture* ds, const GSVector2i copy_size(std::min(rt_size.x, src_size.x), std::min(rt_size.y, src_size.y)); const GSVector4 sRect(0.0f, 0.0f, static_cast(copy_size.x) / static_cast(src_size.x), static_cast(copy_size.y) / static_cast(src_size.y)); - const GSVector4 dRect(0, 0, copy_size.x, copy_size.y); + // This is kind of a bodge because the game confuses everything since the source is really 16bit and it assumes it's really drawing 16bit on the copy back, resizing the target. + const GSVector4 dRect(0, 0, copy_size.x, copy_size.y * (src->m_32_bits_fmt ? 1 : 2)); g_gs_device->StretchRect(src->m_texture, sRect, rt, dRect, true, true, true, false); diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index 8abbd15144..e0fff29812 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -2731,14 +2731,16 @@ void GSRendererHW::Draw() // offset coordinates swap around RG/BA. (Ace Combat) const u32 minv = m_cached_ctx.CLAMP.MINV; const u32 minu = m_cached_ctx.CLAMP.MINU; - const bool rgba_shuffle = ((m_cached_ctx.CLAMP.WMS == m_cached_ctx.CLAMP.WMT && m_cached_ctx.CLAMP.WMS == CLAMP_REGION_REPEAT) && (minu && minv)); + // Make sure minu or minv are actually a mask on some bits, false positives of games setting 512 (0x1ff) are not masks used for shuffles. + const bool rgba_shuffle = ((m_cached_ctx.CLAMP.WMS == m_cached_ctx.CLAMP.WMT && m_cached_ctx.CLAMP.WMS == CLAMP_REGION_REPEAT) && (minu && minv && ((minu + 1 & minu) || (minv + 1 & minv)))); const bool shuffle_coords = ((first_x ^ first_u) & 0xF) == 8 || rgba_shuffle; // Round up half of second coord, it can sometimes be slightly under. const int draw_width = std::abs(v[1].XYZ.X + 9 - v[0].XYZ.X) >> 4; const int read_width = std::abs(second_u - first_u); - shuffle_target = shuffle_coords && (draw_width & 7) == 0 && std::abs(draw_width - read_width) <= 1; + // m_skip check is just mainly for NFS Undercover, but should hopefully pick up any other games which rewrite shuffles. + shuffle_target = shuffle_coords && (((draw_width & 7) == 0 && std::abs(draw_width - read_width) <= 1) || m_skip > 50); } if (!shuffle_target) @@ -2785,7 +2787,7 @@ void GSRendererHW::Draw() return; } - possible_shuffle &= src && (src->m_from_target != nullptr); + possible_shuffle &= src && (src->m_from_target != nullptr && (src->m_from_target->m_32_bits_fmt) || (m_skip && possible_shuffle)); // We don't know the alpha range of direct sources when we first tried to optimize the alpha test. // Moving the texture lookup before the ATST optimization complicates things a lot, so instead, // recompute it, and everything derived from it again if it changes. @@ -2827,7 +2829,7 @@ void GSRendererHW::Draw() // Urban Reign trolls by scissoring a draw to a target at 0x0-0x117F to 378x449 which ends up the size being rounded up to 640x480 // causing the buffer to expand to around 0x1400, which makes a later framebuffer at 0x1180 to fail to be created correctly. // We can cheese this by checking if the Z is masked and the resultant colour is going to be black anyway. - const bool output_black = PRIM->ABE && ((m_context->ALPHA.A == 1 || m_context->ALPHA.IsBlack()) && m_context->ALPHA.D != 1) && m_draw_env->COLCLAMP.CLAMP == 1; + const bool output_black = PRIM->ABE && ((m_context->ALPHA.A == 1 && m_context->ALPHA.D > 1) || (m_context->ALPHA.IsBlack() && m_context->ALPHA.D != 1)) && m_draw_env->COLCLAMP.CLAMP == 1; const bool can_expand = !(m_cached_ctx.ZBUF.ZMSK && output_black); // Estimate size based on the scissor rectangle and height cache. @@ -7914,7 +7916,9 @@ void GSRendererHW::ClearGSLocalMemory(const GSOffset& off, const GSVector4i& r, bool GSRendererHW::OI_BlitFMV(GSTextureCache::Target* _rt, GSTextureCache::Source* tex, const GSVector4i& r_draw) { - /*if (r_draw.w > 1024 && (m_vt.m_primclass == GS_SPRITE_CLASS) && (m_vertex.next == 2) && m_process_texture && !PRIM->ABE && tex && !tex->m_target && m_cached_ctx.TEX0.TBW > 0) + // Not required when using Tex in RT + if (r_draw.w > 1024 && (m_vt.m_primclass == GS_SPRITE_CLASS) && (m_vertex.next == 2) && m_process_texture && !PRIM->ABE && + tex && !tex->m_target && m_cached_ctx.TEX0.TBW > 0 && GSConfig.UserHacks_TextureInsideRt == GSTextureInRtMode::Disabled) { GL_PUSH("OI_BlitFMV"); @@ -7968,7 +7972,7 @@ bool GSRendererHW::OI_BlitFMV(GSTextureCache::Target* _rt, GSTextureCache::Sourc g_texture_cache->InvalidateVideoMemSubTarget(_rt); return false; // skip current draw - }*/ + } // Nothing to see keep going return true; diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index 919ee4091a..9c00b3daef 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -336,22 +336,8 @@ GSVector4i GSTextureCache::TranslateAlignedRectByPage(u32 tbp, u32 tebp, u32 tbw // Results won't be square, if it's not invalidation, it's a texture, which is problematic to translate, so let's not (FIFA 2005). if (!is_invalidation) { - if (sbp != tbp) - { - // Just take the start page, as this is likely tex in rt, and that's all we care about. - const u32 start_page = (in_rect.y / src_page_size.y) + (in_rect.x / src_page_size.x); - in_rect.x = (start_page % dst_pgw) * dst_page_size.x; - in_rect.y = (start_page / dst_pgw) * dst_page_size.y; - in_rect.z = in_rect.x + dst_page_size.x; - in_rect.w = in_rect.y + dst_page_size.y; - - return in_rect; - } - else - { - DevCon.Warning("Uneven pages mess up sbp %x dbp %x spgw %d dpgw %d", sbp, tbp, src_pgw, dst_pgw); - return GSVector4i::zero(); - } + DevCon.Warning("Uneven pages mess up sbp %x dbp %x spgw %d dpgw %d", sbp, tbp, src_pgw, dst_pgw); + return GSVector4i::zero(); } //TODO: Maybe control dirty blocks directly and add them page at a time for better granularity. @@ -1146,7 +1132,8 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const // Try to make sure the target has available what we need, be careful of self referencing frames with font in the alpha. // Also is we have already found a target which we had to offset in to by using a region or exact address, // it's probable that's more correct than being inside (Tomb Raider Legends + Project Snowblind) - if (!overlaps || (found_t && dst->m_TEX0.TBP0 >= bp && (GSState::s_n - dst->m_last_draw) < (GSState::s_n - t->m_last_draw))) + // Vakyrie Profile 2 also has some in draws which get done on a different target due to a slight offset, so we need to make sure we have the newer one. + if (!overlaps || (found_t && (GSState::s_n - dst->m_last_draw) < (GSState::s_n - t->m_last_draw))) continue; const bool width_match = (std::max(64U, bw * 64U) >> GSLocalMemory::m_psm[psm].info.pageShiftX()) == @@ -1487,8 +1474,9 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const DevCon.Warning("BP %x - 16bit bad match for target bp %x bw %d src %d format %d", bp, t->m_TEX0.TBP0, t->m_TEX0.TBW, bw, t->m_TEX0.PSM); continue; } - else if (!possible_shuffle && (GSLocalMemory::m_psm[color_psm].bpp == 8 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == 32 && bw != 1 && - !((t->m_TEX0.TBW == (bw / 2)) || (t->m_TEX0.TBW >= (bw / 2) && (block_boundary_rect.w <= GSLocalMemory::m_psm[psm].pgs.y))))) + // Keep note that 2 bw is basically 1 normal page, as bw is in 64 pixels, and 8bit pages are 128 pixels wide, aka 2 bw. + else if (!possible_shuffle && (GSLocalMemory::m_psm[color_psm].bpp == 8 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == 32 && + !((t->m_TEX0.TBW == (bw / 2)) || (((bw + 1) / 2) <= t->m_TEX0.TBW && (block_boundary_rect.w <= GSLocalMemory::m_psm[psm].pgs.y))))) { DevCon.Warning("BP %x - 8bit bad match for target bp %x bw %d src %d format %d", bp, t->m_TEX0.TBP0, t->m_TEX0.TBW, bw, t->m_TEX0.PSM); continue; @@ -1566,7 +1554,7 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const //rect = rect.rintersect(t->m_valid); - if (rect.rempty()) + if (rect.rintersect(t->m_valid).rempty()) continue; if (!t->m_dirty.empty()) From e7bd6693621b6dd2937e6e06f32fe71a4e84c355 Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Thu, 16 Jan 2025 10:42:02 +0000 Subject: [PATCH 097/162] GS/HW: Centralize new target resizing calls to fix statistics/tidy up Also add an override for GSVector4i loadl to take a GSVector2i --- pcsx2/GS/GSVector4i.h | 5 +++ pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 17 +++------ pcsx2/GS/Renderers/HW/GSTextureCache.cpp | 46 ++++++++++++------------ pcsx2/GS/Renderers/HW/GSTextureCache.h | 2 +- 4 files changed, 34 insertions(+), 36 deletions(-) diff --git a/pcsx2/GS/GSVector4i.h b/pcsx2/GS/GSVector4i.h index 53bfc2c6d5..63c9cc1f98 100644 --- a/pcsx2/GS/GSVector4i.h +++ b/pcsx2/GS/GSVector4i.h @@ -1599,6 +1599,11 @@ public: return loadh(&v); } + __forceinline static GSVector4i loadl(const GSVector2i& v) + { + return loadl(&v); + } + __forceinline static GSVector4i load(const void* pl, const void* ph) { return loadh(ph, loadl(pl)); diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index e0fff29812..5b1fea85d2 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -3073,33 +3073,24 @@ void GSRendererHW::Draw() if (vertical_offset < 0) { rt->m_TEX0.TBP0 = m_cached_ctx.FRAME.Block(); - GSVector2i new_scaled_size = rt->m_unscaled_size * rt->m_scale; + GSVector2i new_size = rt->m_unscaled_size; // Make sure to use the original format for the offset. int new_offset = std::abs((vertical_offset / frame_psm.pgs.y) * GSLocalMemory::m_psm[rt->m_TEX0.PSM].pgs.y); texture_offset = new_offset; - new_scaled_size.y += new_offset * rt->m_scale; - GSTexture* tex = g_gs_device->CreateRenderTarget(new_scaled_size.x, new_scaled_size.y, GSTexture::Format::Color, true); - //if (!tex) - // return nullptr; - //m_target_memory_usage += tex->GetMemUsage(); - GSVector4i dRect = GSVector4i(0, new_offset * rt->m_scale, new_scaled_size.x, new_scaled_size.y); - g_gs_device->StretchRect(rt->m_texture, GSVector4(0,0,1,1), tex, GSVector4(dRect), ShaderConvert::COPY, false); + new_size.y += new_offset; + rt->ResizeTexture(new_size.x, new_size.y, true, true, GSVector4i::loadh(new_size * rt->m_scale).loadl(GSVector2i(0, new_offset * rt->m_scale))); if (src && src->m_from_target && src->m_from_target == rt && src->m_target_direct) { - src->m_texture = tex; + src->m_texture = rt->m_texture; } - g_gs_device->Recycle(rt->m_texture); - rt->m_valid.y += new_offset; rt->m_valid.w += new_offset; rt->m_drawn_since_read.y += new_offset; rt->m_drawn_since_read.w += new_offset; - rt->m_texture = tex; - rt->m_unscaled_size = new_scaled_size / rt->m_scale; t_size.y += std::abs(vertical_offset); vertical_offset = 0; diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index 9c00b3daef..d87bab3e52 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -2173,30 +2173,19 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe GL_INS("TC Convert to 16bit: %dx%d: %dx%d @ %f -> %dx%d @ %f", dst->m_unscaled_size.x, dst->m_unscaled_size.y, dst->m_texture->GetWidth(), dst->m_texture->GetHeight(), dst->m_scale, new_scaled_size.x, new_scaled_size.y, scale); - //DevCon.Warning("Scale %s draw %d", scale_down ? "down" : "up", GSState::s_n); - GSTexture* tex = type == RenderTarget ? g_gs_device->CreateRenderTarget(new_scaled_size.x, new_scaled_size.y, GSTexture::Format::Color, true) : - g_gs_device->CreateDepthStencil(new_scaled_size.x, new_scaled_size.y, GSTexture::Format::DepthStencil, true); - if (!tex) - return nullptr; - m_target_memory_usage += tex->GetMemUsage(); - - g_gs_device->StretchRect(dst->m_texture, sRect, tex, dRect, (type == RenderTarget) ? ShaderConvert::COPY : ShaderConvert::DEPTH_COPY, false); - if (src && src->m_from_target && src->m_from_target == dst) { src->m_texture = dst->m_texture; src->m_target_direct = false; src->m_shared_texture = false; + + dst->ResizeTexture(new_size.x, new_size.y, true, true, GSVector4i(dRect), true); } else { - m_target_memory_usage -= dst->m_texture->GetMemUsage(); - g_gs_device->Recycle(dst->m_texture); + dst->ResizeTexture(new_size.x, new_size.y, true, true, GSVector4i(dRect)); } - - dst->m_texture = tex; - dst->m_unscaled_size = new_size; } // New format or doing a shuffle to a 32bit target that used to be 16bit @@ -6782,7 +6771,7 @@ void GSTextureCache::Target::UpdateValidity(const GSVector4i& rect, bool can_res // GL_CACHE("UpdateValidity (0x%x->0x%x) from R:%d,%d Valid: %d,%d", m_TEX0.TBP0, m_end_block, rect.z, rect.w, m_valid.z, m_valid.w); } -bool GSTextureCache::Target::ResizeTexture(int new_unscaled_width, int new_unscaled_height, bool recycle_old) +bool GSTextureCache::Target::ResizeTexture(int new_unscaled_width, int new_unscaled_height, bool recycle_old, bool require_new_rect, GSVector4i new_rect, bool keep_old) { if (m_unscaled_size.x == new_unscaled_width && m_unscaled_size.y == new_unscaled_height) return true; @@ -6806,7 +6795,7 @@ bool GSTextureCache::Target::ResizeTexture(int new_unscaled_width, int new_unsca // Only need to copy if it's been written to. if (m_texture->GetState() == GSTexture::State::Dirty) { - const GSVector4i rc = GSVector4i::loadh(size.min(new_size)); + const GSVector4i rc = require_new_rect ? new_rect : GSVector4i::loadh(size.min(new_size)); if (tex->IsDepthStencil()) { // Can't do partial copies in DirectX for depth textures, and it's probably not ideal in other @@ -6815,8 +6804,15 @@ bool GSTextureCache::Target::ResizeTexture(int new_unscaled_width, int new_unsca } else { - // Fast memcpy()-like path for color targets. - g_gs_device->CopyRect(m_texture, tex, rc, 0, 0); + if (require_new_rect) + { + g_gs_device->StretchRect(m_texture, tex, GSVector4(rc), ShaderConvert::COPY, false); + } + else + { + // Fast memcpy()-like path for color targets. + g_gs_device->CopyRect(m_texture, tex, rc, 0, 0); + } } g_perfmon.Put(GSPerfMon::TextureCopies, 1); @@ -6834,12 +6830,18 @@ bool GSTextureCache::Target::ResizeTexture(int new_unscaled_width, int new_unsca g_gs_device->InvalidateRenderTarget(tex); } - g_texture_cache->m_target_memory_usage = (g_texture_cache->m_target_memory_usage - m_texture->GetMemUsage()) + tex->GetMemUsage(); - if (recycle_old) - g_gs_device->Recycle(m_texture); + if (!keep_old) + { + g_texture_cache->m_target_memory_usage = (g_texture_cache->m_target_memory_usage - m_texture->GetMemUsage()) + tex->GetMemUsage(); + + if (recycle_old) + g_gs_device->Recycle(m_texture); + else + delete m_texture; + } else - delete m_texture; + g_texture_cache->m_target_memory_usage += tex->GetMemUsage(); m_texture = tex; m_unscaled_size = new_unscaled_size; diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.h b/pcsx2/GS/Renderers/HW/GSTextureCache.h index 7f7d02c3aa..7bb1d5b15d 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.h +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.h @@ -257,7 +257,7 @@ public: void UpdateValidChannels(u32 psm, u32 fbmsk); /// Resizes target texture, DOES NOT RESCALE. - bool ResizeTexture(int new_unscaled_width, int new_unscaled_height, bool recycle_old = true); + bool ResizeTexture(int new_unscaled_width, int new_unscaled_height, bool recycle_old = true, bool require_offset = false, GSVector4i offset = GSVector4i::zero(), bool keep_old = false); private: void UpdateTextureDebugName(); From 21569063413f943f225ebfc562668e152451227a Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Thu, 16 Jan 2025 18:16:57 +0000 Subject: [PATCH 098/162] GS/HW: Allow 1:1 quads to be optimized for textures. Fixes for shuffles --- pcsx2/GS/GSState.cpp | 37 +++++++++++++++++++++----- pcsx2/GS/Renderers/HW/GSHwHack.cpp | 2 +- pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 8 +++--- 3 files changed, 36 insertions(+), 11 deletions(-) diff --git a/pcsx2/GS/GSState.cpp b/pcsx2/GS/GSState.cpp index ba628a8426..0c3d221700 100644 --- a/pcsx2/GS/GSState.cpp +++ b/pcsx2/GS/GSState.cpp @@ -3871,7 +3871,8 @@ GSState::TextureMinMaxResult GSState::GetTextureMinMax(GIFRegTEX0 TEX0, GIFRegCL const GSVector2 grad(uv_range / pos_range); // Adjust texture range when sprites get scissor clipped. Since we linearly interpolate, this // optimization doesn't work when perspective correction is enabled. - if (m_vt.m_primclass == GS_SPRITE_CLASS && PRIM->FST == 1 && m_primitive_covers_without_gaps != NoGapsType::GapsFound) + // Allowing for quads when the gradiant is 1. It's not guaranteed (would need to check the grandient on each vector), but should be close enough. + if ((m_vt.m_primclass == GS_SPRITE_CLASS || (m_vt.m_primclass == GS_TRIANGLE_CLASS && TrianglesAreQuads(false) && grad.x == 1.0f && grad.y == 1.0f)) && m_primitive_covers_without_gaps != NoGapsType::GapsFound) { // When coordinates are fractional, GS appears to draw to the right/bottom (effectively // taking the ceiling), not to the top/left (taking the floor). @@ -3882,11 +3883,24 @@ GSState::TextureMinMaxResult GSState::GetTextureMinMax(GIFRegTEX0 TEX0, GIFRegCL const GSVertex* vert_first = &m_vertex.buff[m_index.buff[0]]; const GSVertex* vert_second = &m_vertex.buff[m_index.buff[1]]; + const GSVertex* vert_third = &m_vertex.buff[m_index.buff[2]]; GSVector4 new_st = st; + bool u_forward_check = false; + bool x_forward_check = false; + if (m_vt.m_primclass == GS_TRIANGLE_CLASS) + { + u_forward_check = PRIM->FST ? ((vert_first->U < vert_second->U) || (vert_first->U < vert_third->U)) : (((vert_first->ST.S / vert_first->RGBAQ.Q) < (vert_second->ST.S / vert_second->RGBAQ.Q)) || ((vert_first->ST.S / vert_first->RGBAQ.Q) < (vert_third->ST.S / vert_third->RGBAQ.Q))); + x_forward_check = (vert_first->XYZ.X < vert_second->XYZ.X) || (vert_first->XYZ.X < vert_third->XYZ.X); + } + else + { + u_forward_check = PRIM->FST ? (vert_first->U < vert_second->U) : ((vert_first->ST.T / vert_first->RGBAQ.Q) < (vert_second->ST.T / vert_first->RGBAQ.Q)); + x_forward_check = vert_first->XYZ.Y < vert_second->XYZ.Y; + } // Check if the UV coords are going in a different direction to the verts, if they match direction, no need to swap - const bool u_forward = vert_first->U < vert_second->U; - const bool x_forward = vert_first->XYZ.X < vert_second->XYZ.X; + const bool u_forward = u_forward_check; + const bool x_forward = x_forward_check; const bool swap_x = u_forward != x_forward; if (int_rc.left < scissored_rc.left) @@ -3909,9 +3923,20 @@ GSState::TextureMinMaxResult GSState::GetTextureMinMax(GIFRegTEX0 TEX0, GIFRegCL st.x = new_st.x; st.z = new_st.z; } - - const bool v_forward = vert_first->V < vert_second->V; - const bool y_forward = vert_first->XYZ.Y < vert_second->XYZ.Y; + bool v_forward_check = false; + bool y_forward_check = false; + if (m_vt.m_primclass == GS_TRIANGLE_CLASS) + { + v_forward_check = PRIM->FST ? ((vert_first->V < vert_second->V) || (vert_first->V < vert_third->V)) : (((vert_first->ST.T / vert_first->RGBAQ.Q) < (vert_second->ST.T / vert_second->RGBAQ.Q)) || ((vert_first->ST.T / vert_first->RGBAQ.Q) < (vert_third->ST.T / vert_third->RGBAQ.Q))); + y_forward_check = (vert_first->XYZ.Y < vert_second->XYZ.Y) || (vert_first->XYZ.Y < vert_third->XYZ.Y); + } + else + { + v_forward_check = PRIM->FST ? (vert_first->V < vert_second->V) : ((vert_first->ST.T / vert_first->RGBAQ.Q) < (vert_second->ST.T / vert_first->RGBAQ.Q)); + y_forward_check = vert_first->XYZ.Y < vert_second->XYZ.Y; + } + const bool v_forward = v_forward_check; + const bool y_forward = y_forward_check; const bool swap_y = v_forward != y_forward; if (int_rc.top < scissored_rc.top) diff --git a/pcsx2/GS/Renderers/HW/GSHwHack.cpp b/pcsx2/GS/Renderers/HW/GSHwHack.cpp index 244b833ffc..d69dd81744 100644 --- a/pcsx2/GS/Renderers/HW/GSHwHack.cpp +++ b/pcsx2/GS/Renderers/HW/GSHwHack.cpp @@ -1145,7 +1145,7 @@ bool GSHwHack::OI_SonicUnleashed(GSRendererHW& r, GSTexture* rt, GSTexture* ds, const GSVector4 sRect(0.0f, 0.0f, static_cast(copy_size.x) / static_cast(src_size.x), static_cast(copy_size.y) / static_cast(src_size.y)); // This is kind of a bodge because the game confuses everything since the source is really 16bit and it assumes it's really drawing 16bit on the copy back, resizing the target. - const GSVector4 dRect(0, 0, copy_size.x, copy_size.y * (src->m_32_bits_fmt ? 1 : 2)); + const GSVector4 dRect(0, 0, copy_size.x, copy_size.y); g_gs_device->StretchRect(src->m_texture, sRect, rt, dRect, true, true, true, false); diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index 5b1fea85d2..c081fc0d33 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -2718,7 +2718,7 @@ void GSRendererHW::Draw() GIFRegTEX0 FRAME_TEX0; bool shuffle_target = false; if (!no_rt && GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].bpp == 16 && GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].bpp >= 16 && - (m_vt.m_primclass == GS_SPRITE_CLASS || (m_vt.m_primclass == GS_TRIANGLE_CLASS && (m_index.tail % 6) == 0 && TrianglesAreQuads(true)))) + (m_vt.m_primclass == GS_SPRITE_CLASS || (m_vt.m_primclass == GS_TRIANGLE_CLASS && (m_index.tail % 6) == 0 && TrianglesAreQuads(true) && m_index.tail > 6))) { if (!shuffle_target && GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].bpp == 16) { @@ -2787,7 +2787,7 @@ void GSRendererHW::Draw() return; } - possible_shuffle &= src && (src->m_from_target != nullptr && (src->m_from_target->m_32_bits_fmt) || (m_skip && possible_shuffle)); + possible_shuffle &= src && (src->m_from_target != nullptr || (m_skip && possible_shuffle)); // We don't know the alpha range of direct sources when we first tried to optimize the alpha test. // Moving the texture lookup before the ATST optimization complicates things a lot, so instead, // recompute it, and everything derived from it again if it changes. @@ -4564,7 +4564,7 @@ __ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool // Performance GPU note: it could be wise to reduce the size to // the rendered size of the framebuffer - if (GSConfig.UserHacks_TextureInsideRt == GSTextureInRtMode::Disabled || (!m_in_target_draw && IsPageCopy())) + if (GSConfig.UserHacks_TextureInsideRt == GSTextureInRtMode::Disabled || (!m_in_target_draw && IsPageCopy()) || m_conf.ps.urban_chaos_hle || m_conf.ps.tales_of_abyss_hle) { GSVertex* s = &m_vertex.buff[0]; s[0].XYZ.X = static_cast(m_context->XYOFFSET.OFX + 0); @@ -5919,7 +5919,7 @@ __ri void GSRendererHW::HandleTextureHazards(const GSTextureCache::Target* rt, c target_region = false; source_region.bits = 0; //copied_rt = tex->m_from_target != nullptr; - if (m_in_target_draw) + if (page_offset && m_in_target_draw) { copy_size.x = m_r.width(); copy_size.y = m_r.height(); From eae359a3b8a002677654a0b9b758313441559dbb Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Thu, 16 Jan 2025 18:39:54 +0000 Subject: [PATCH 099/162] GS/HW: Don't interfere with Tales/Urban Chaos HLE shuffles --- pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index c081fc0d33..45c54fd38f 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -2187,8 +2187,12 @@ void GSRendererHW::Draw() if (m_channel_shuffle) { - m_last_channel_shuffle_fbp = m_context->FRAME.Block(); - m_last_channel_shuffle_tbp = m_context->TEX0.TBP0; + // These HLE's skip several channel shuffles in a row which change blends etc. Let's not break the flow, it gets upset. + if (!m_conf.ps.urban_chaos_hle && !m_conf.ps.tales_of_abyss_hle) + { + m_last_channel_shuffle_fbp = m_context->FRAME.Block(); + m_last_channel_shuffle_tbp = m_context->TEX0.TBP0; + } num_skipped_channel_shuffle_draws++; return; From 05bf7af85962a5a57167ee9ef841b15702b2aa77 Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Fri, 17 Jan 2025 01:32:58 +0000 Subject: [PATCH 100/162] GS/HW: Further fixes to HW renderer behaviour --- pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 45 ++++++++++++++++++++---- pcsx2/GS/Renderers/HW/GSTextureCache.cpp | 4 +-- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index 45c54fd38f..09b7d45e9d 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -728,7 +728,7 @@ void GSRendererHW::ConvertSpriteTextureShuffle(u32& process_rg, u32& process_ba, if (m_context->scissor.in.x & 8) { - m_context->scissor.in.x &= ~0xf;//m_vt.m_min.p.x; + m_context->scissor.in.x &= ~0xf; //m_vt.m_min.p.x; if (half_right_vert) m_context->scissor.in.x /= 2; @@ -758,6 +758,38 @@ void GSRendererHW::ConvertSpriteTextureShuffle(u32& process_rg, u32& process_ba, m_vt.m_min.t.x /= 2.0f; m_vt.m_max.t.x = (m_vt.m_max.t.x + 1.9f) / 2.0f; } + + // Special case used in Call of Duty - World at War where it doubles the height and halves the width, but the height is double doubled. + // Check the height of the original texture, if it's half of the draw height, then make it wide instead. + if (half_bottom_uv && tex->m_from_target && m_cached_ctx.TEX0.TBW == m_cached_ctx.FRAME.FBW && + tex->m_from_target->m_TEX0.TBW == (m_cached_ctx.TEX0.TBW * 2) && (m_cached_ctx.TEX0.TBW * 64) == floor(m_vt.m_max.t.x)) + { + m_r.z *= 2; + m_r.w /= 2; + + m_vt.m_max.t.y /= 2; + m_vt.m_max.t.x *= 2; + m_vt.m_max.p.y /= 2; + m_vt.m_max.p.x *= 2; + m_context->scissor.in.w /= 2; + m_context->scissor.in.z *= 2; + + v[1].XYZ.X = ((v[m_index.buff[m_index.tail - 1]].XYZ.X - m_context->XYOFFSET.OFX) * 2) + m_context->XYOFFSET.OFX; + v[1].XYZ.Y = ((v[m_index.buff[m_index.tail - 1]].XYZ.Y - m_context->XYOFFSET.OFY) / 2) + m_context->XYOFFSET.OFY; + + v[1].U = v[m_index.buff[m_index.tail - 1]].U * 2; + v[1].V = v[m_index.buff[m_index.tail - 1]].V / 2; + + v[1].ST.S = v[m_index.buff[m_index.tail - 1]].ST.S * 2; + v[1].ST.T = v[m_index.buff[m_index.tail - 1]].ST.T / 2; + + m_vertex.head = m_vertex.tail = m_vertex.next = 2; + m_index.tail = 2; + + m_cached_ctx.TEX0.TBW *= 2; + m_cached_ctx.FRAME.FBW *= 2; + GL_CACHE("Half width/double height shuffle detected, width changed to %d", m_cached_ctx.FRAME.FBW); + } } GSVector4 GSRendererHW::RealignTargetTextureCoordinate(const GSTextureCache::Source* tex) @@ -3057,8 +3089,9 @@ void GSRendererHW::Draw() FRAME_TEX0.TBW = src->m_from_target->m_TEX0.TBW; } - rt = g_texture_cache->CreateTarget(FRAME_TEX0, t_size, GetValidSize(src), (scale_draw < 0 && is_possible_mem_clear != ClearType::NormalClear) ? src->m_from_target->GetScale() : target_scale, GSTextureCache::RenderTarget, true, - fm, false, force_preload, preserve_rt_color || possible_shuffle, m_r, src); + rt = g_texture_cache->CreateTarget(FRAME_TEX0, t_size, GetValidSize(src), (scale_draw < 0 && is_possible_mem_clear != ClearType::NormalClear) ? src->m_from_target->GetScale() : target_scale, + GSTextureCache::RenderTarget, true, fm, false, force_preload, preserve_rt_color || possible_shuffle, lookup_rect, src); + if (!rt) [[unlikely]] { GL_INS("ERROR: Failed to create FRAME target, skipping."); @@ -4588,7 +4621,7 @@ __ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool const GSLocalMemory::psm_t frame_psm = GSLocalMemory::m_psm[m_context->FRAME.PSM]; const u32 frame_page_offset = std::max(static_cast(((m_r.x / frame_psm.pgs.x) + (m_r.y / frame_psm.pgs.y) * src->m_TEX0.TBW) - m_target_offset), 0); m_r = GSVector4i(m_r.x & ~(frame_psm.pgs.x - 1), m_r.y & ~(frame_psm.pgs.y - 1), (m_r.z + (frame_psm.pgs.x - 1)) & ~(frame_psm.pgs.x - 1), (m_r.w + (frame_psm.pgs.y - 1)) & ~(frame_psm.pgs.y - 1)); - m_cached_ctx.FRAME.FBP += frame_page_offset; + //m_cached_ctx.FRAME.FBP += frame_page_offset; m_in_target_draw |= frame_page_offset > 0; GSVertex* s = &m_vertex.buff[0]; s[0].XYZ.X = static_cast(m_context->XYOFFSET.OFX + (m_r.x << 4)); @@ -4598,7 +4631,7 @@ __ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool const GSLocalMemory::psm_t tex_psm = GSLocalMemory::m_psm[m_context->TEX0.PSM]; const u32 tex_page_offset = (m_vt.m_min.t.x / tex_psm.pgs.x) + (m_vt.m_min.t.y / tex_psm.pgs.y); - m_cached_ctx.TEX0.TBP0 += tex_page_offset << 5; + //m_cached_ctx.TEX0.TBP0 += tex_page_offset << 5; s[0].U = m_r.x << 4; s[1].U = m_r.z << 4; s[0].V = m_r.y << 4; @@ -5923,7 +5956,7 @@ __ri void GSRendererHW::HandleTextureHazards(const GSTextureCache::Target* rt, c target_region = false; source_region.bits = 0; //copied_rt = tex->m_from_target != nullptr; - if (page_offset && m_in_target_draw) + if (m_in_target_draw && (page_offset || frame_diff)) { copy_size.x = m_r.width(); copy_size.y = m_r.height(); diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index d87bab3e52..92ecfe5314 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -1948,10 +1948,10 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe (static_cast(min_rect.width()) <= (widthpage_offset * 64))));*/ const bool is_aligned_ok = widthpage_offset == 0 || ((min_rect.width() <= static_cast((t->m_TEX0.TBW - widthpage_offset) * 64) && (t->m_TEX0.TBW == TEX0.TBW || TEX0.TBW == 1)) && bp >= t->m_TEX0.TBP0); const bool no_target_or_newer = (!dst || ((GSState::s_n - dst->m_last_draw) < (GSState::s_n - t->m_last_draw))); - const bool width_match = (t->m_TEX0.TBW == TEX0.TBW || TEX0.TBW == 1); + const bool width_match = (t->m_TEX0.TBW == TEX0.TBW || (TEX0.TBW == 1 && draw_rect.w <= GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y)); // if it's a shuffle, some games tend to offset back by a page, such as Tomb Raider, for no disernable reason, but it then causes problems. // This can also happen horizontally (Catwoman moves everything one page left with shuffles), but this is too messy to deal with right now. - const bool overlaps = t->Overlaps(bp, TEX0.TBW, TEX0.PSM, min_rect) || (is_shuffle && t->Overlaps(bp, TEX0.TBW, TEX0.PSM, min_rect + GSVector4i(0, 0, 0, 32))); + const bool overlaps = t->Overlaps(bp, TEX0.TBW, TEX0.PSM, min_rect) || (is_shuffle && src && GSLocalMemory::m_psm[src->m_TEX0.PSM].bpp == 8 && t->Overlaps(bp, TEX0.TBW, TEX0.PSM, min_rect + GSVector4i(0, 0, 0, 32))); if (no_target_or_newer && is_aligned_ok && width_match && overlaps) { const GSLocalMemory::psm_t& s_psm = GSLocalMemory::m_psm[TEX0.PSM]; From c1ffd93f28315b67132073d7922b2f20000f764c Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Thu, 23 Jan 2025 15:48:35 +0000 Subject: [PATCH 101/162] GS/HW: Fix up shuffle behaviour and affected areas - Channel shuffles now check how many pages require drawing before doing the shuffle. - Split texture shuffles don't create new targets with bad valid areas. --- pcsx2/GS/GSState.cpp | 26 +++- pcsx2/GS/GSState.h | 17 +++ pcsx2/GS/Renderers/HW/GSHwHack.cpp | 4 + pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 178 +++++++++++++++++------ pcsx2/GS/Renderers/HW/GSRendererHW.h | 8 +- pcsx2/GS/Renderers/HW/GSTextureCache.cpp | 64 +++++++- pcsx2/GS/Renderers/HW/GSTextureCache.h | 2 +- 7 files changed, 244 insertions(+), 55 deletions(-) diff --git a/pcsx2/GS/GSState.cpp b/pcsx2/GS/GSState.cpp index 0c3d221700..b23000d94b 100644 --- a/pcsx2/GS/GSState.cpp +++ b/pcsx2/GS/GSState.cpp @@ -119,6 +119,10 @@ GSState::~GSState() _aligned_free(m_vertex.buff); if (m_index.buff) _aligned_free(m_index.buff); + if (m_draw_vertex.buff) + _aligned_free(m_draw_vertex.buff); + if (m_draw_index.buff) + _aligned_free(m_draw_index.buff); } std::string GSState::GetDrawDumpPath(const char* format, ...) @@ -850,7 +854,7 @@ void GSState::ApplyTEX0(GIFRegTEX0& TEX0) // Urban Chaos writes to the memory backing the CLUT in the middle of a shuffle, and // it's unclear whether the CLUT would actually get reloaded in that case. if (TEX0.CBP != m_mem.m_clut.GetCLUTCBP()) - m_channel_shuffle = false; + m_channel_shuffle_abort = true; } TEX0.CPSM &= 0xa; // 1010b @@ -2796,8 +2800,10 @@ void GSState::GrowVertexBuffer() const u32 maxcount = std::max(m_vertex.maxcount * 3 / 2, 10000); GSVertex* vertex = static_cast(_aligned_malloc(sizeof(GSVertex) * maxcount, 32)); + GSVertex* draw_vertex = static_cast(_aligned_malloc(sizeof(GSVertex) * maxcount, 32)); // Worst case index list is a list of points with vs expansion, 6 indices per point u16* index = static_cast(_aligned_malloc(sizeof(u16) * maxcount * 6, 32)); + u16* draw_index = static_cast(_aligned_malloc(sizeof(u16) * maxcount * 6, 32)); if (!vertex || !index) { @@ -2823,6 +2829,22 @@ void GSState::GrowVertexBuffer() _aligned_free(m_index.buff); } + if (m_draw_vertex.buff) + { + std::memcpy(draw_vertex, m_draw_vertex.buff, sizeof(GSVertex) * m_vertex.tail); + + _aligned_free(m_draw_vertex.buff); + } + + if (m_draw_index.buff) + { + std::memcpy(draw_index, m_draw_index.buff, sizeof(u16) * m_index.tail); + + _aligned_free(m_draw_index.buff); + } + + m_draw_vertex.buff = draw_vertex; + m_draw_index.buff = draw_index; m_vertex.buff = vertex; m_vertex.maxcount = maxcount - 3; // -3 to have some space at the end of the buffer before DrawingKick can grow it m_index.buff = index; @@ -3872,7 +3894,7 @@ GSState::TextureMinMaxResult GSState::GetTextureMinMax(GIFRegTEX0 TEX0, GIFRegCL // Adjust texture range when sprites get scissor clipped. Since we linearly interpolate, this // optimization doesn't work when perspective correction is enabled. // Allowing for quads when the gradiant is 1. It's not guaranteed (would need to check the grandient on each vector), but should be close enough. - if ((m_vt.m_primclass == GS_SPRITE_CLASS || (m_vt.m_primclass == GS_TRIANGLE_CLASS && TrianglesAreQuads(false) && grad.x == 1.0f && grad.y == 1.0f)) && m_primitive_covers_without_gaps != NoGapsType::GapsFound) + if (m_primitive_covers_without_gaps != NoGapsType::GapsFound && (m_vt.m_primclass == GS_SPRITE_CLASS || (m_vt.m_primclass == GS_TRIANGLE_CLASS && grad.x == 1.0f && grad.y == 1.0f && TrianglesAreQuads(false)))) { // When coordinates are fractional, GS appears to draw to the right/bottom (effectively // taking the ceiling), not to the top/left (taking the floor). diff --git a/pcsx2/GS/GSState.h b/pcsx2/GS/GSState.h index 5b07b03424..cf09202b40 100644 --- a/pcsx2/GS/GSState.h +++ b/pcsx2/GS/GSState.h @@ -145,6 +145,21 @@ protected: u32 tail; } m_index = {}; + struct + { + GSVertex* buff; + u32 head, tail, next, maxcount; // head: first vertex, tail: last vertex + 1, next: last indexed + 1 + u32 xy_tail; + GSVector4i xy[4]; + GSVector4i xyhead; + } m_draw_vertex = {}; + + struct + { + u16* buff; + u32 tail; + } m_draw_index = {}; + void UpdateContext(); void UpdateScissor(); @@ -225,6 +240,8 @@ public: bool m_isPackedUV_HackFlag = false; bool m_channel_shuffle = false; bool m_in_target_draw = false; + bool m_channel_shuffle_abort = false; + u32 m_target_offset = 0; u8 m_scanmask_used = 0; u32 m_dirty_gs_regs = 0; diff --git a/pcsx2/GS/Renderers/HW/GSHwHack.cpp b/pcsx2/GS/Renderers/HW/GSHwHack.cpp index d69dd81744..b5036a28f1 100644 --- a/pcsx2/GS/Renderers/HW/GSHwHack.cpp +++ b/pcsx2/GS/Renderers/HW/GSHwHack.cpp @@ -1004,6 +1004,10 @@ bool GSHwHack::OI_PointListPalette(GSRendererHW& r, GSTexture* rt, GSTexture* ds && r.m_cached_ctx.FRAME.FBMSK == 0 // No frame buffer masking. ) { + int mask = (r.m_vt.m_max.p.xyxy() == r.m_vt.m_min.p.xyxy()).mask(); + if (mask == 0xf) + return true; + const u32 FBP = r.m_cached_ctx.FRAME.Block(); const u32 FBW = r.m_cached_ctx.FRAME.FBW; GL_INS("PointListPalette - m_r = <%d, %d => %d, %d>, n_vertices = %u, FBP = 0x%x, FBW = %u", r.m_r.x, r.m_r.y, r.m_r.z, r.m_r.w, n_vertices, FBP, FBW); diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index 09b7d45e9d..229f106b44 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -1130,7 +1130,8 @@ bool GSRendererHW::NextDrawHDR() const bool GSRendererHW::IsPossibleChannelShuffle() const { if (!PRIM->TME || m_cached_ctx.TEX0.PSM != PSMT8 || // 8-bit texture draw - m_vt.m_primclass != GS_SPRITE_CLASS) // draw_sprite_tex + m_vt.m_primclass != GS_SPRITE_CLASS || // draw_sprite_tex + (m_vertex.tail <= 2 && (((m_vt.m_max.p - m_vt.m_min.p) <= GSVector4(8.0f)).mask() & 0x3) == 0x3)) // Powerdrome does a tiny shuffle on a couple of pixels, can't reliably translate this. { return false; } @@ -1144,6 +1145,7 @@ bool GSRendererHW::IsPossibleChannelShuffle() const const int draw_height = std::abs(v[1].XYZ.Y - v[0].XYZ.Y) >> 4; const bool mask_clamp = (m_cached_ctx.CLAMP.WMS | m_cached_ctx.CLAMP.WMT) & 0x2; + const bool draw_match = (draw_height == 2) || (draw_width == 8); if (draw_match || mask_clamp) @@ -1215,7 +1217,7 @@ bool GSRendererHW::NextDrawMatchesShuffle() const return true; } -bool GSRendererHW::IsSplitTextureShuffle(GSTextureCache::Target* rt) +bool GSRendererHW::IsSplitTextureShuffle(GIFRegTEX0& rt_TEX0, GSVector4i& valid_area) { // For this to work, we're peeking into the next draw, therefore we need dirty registers. if (m_dirty_gs_regs == 0) @@ -1258,7 +1260,7 @@ bool GSRendererHW::IsSplitTextureShuffle(GSTextureCache::Target* rt) const u32 pages_high = static_cast(aligned_rc.height()) / frame_psm.pgs.y; const u32 num_pages = m_context->FRAME.FBW * pages_high; // Jurassic - The Hunted will do a split shuffle with a height of 512 (256) when it's supposed to be 448, so it redoes one row of the shuffle. - const u32 rt_half = (((rt->m_valid.height() / GSLocalMemory::m_psm[rt->m_TEX0.PSM].pgs.y) / 2) * rt->m_TEX0.TBW) + (rt->m_TEX0.TBP0 >> 5); + const u32 rt_half = (((valid_area.height() / GSLocalMemory::m_psm[rt_TEX0.PSM].pgs.y) / 2) * rt_TEX0.TBW) + (rt_TEX0.TBP0 >> 5); // If this is a split texture shuffle, the next draw's FRAME/TEX0 should line up. // Re-add the offset we subtracted in Draw() to get the original FBP/TBP0.. this won't handle wrapping. Oh well. // "Potential" ones are for Jak3 which does a split shuffle on a 128x128 texture with a width of 256, writing to the lower half then offsetting 2 pages. @@ -1294,7 +1296,7 @@ bool GSRendererHW::IsSplitTextureShuffle(GSTextureCache::Target* rt) // If the game has changed the texture width to 1 we need to retanslate it to whatever the rt has so the final rect is correct. if (m_cached_ctx.FRAME.FBW == 1) - m_split_texture_shuffle_fbw = rt->m_TEX0.TBW; + m_split_texture_shuffle_fbw = rt_TEX0.TBW; else m_split_texture_shuffle_fbw = m_cached_ctx.FRAME.FBW; } @@ -1303,10 +1305,10 @@ bool GSRendererHW::IsSplitTextureShuffle(GSTextureCache::Target* rt) u32 total_pages = num_pages; // If the current draw is further than the half way point and the next draw is the half way point, then we can assume it's just overdrawing. - if (next_ctx.FRAME.FBP == rt_half && num_pages > (rt_half - (rt->m_TEX0.TBP0 >> 5))) + if (next_ctx.FRAME.FBP == rt_half && num_pages > (rt_half - (rt_TEX0.TBP0 >> 5))) { - vertical_pages = (rt->m_valid.height() / GSLocalMemory::m_psm[rt->m_TEX0.PSM].pgs.y) / 2; - total_pages = vertical_pages * rt->m_TEX0.TBW; + vertical_pages = (valid_area.height() / GSLocalMemory::m_psm[rt_TEX0.PSM].pgs.y) / 2; + total_pages = vertical_pages * rt_TEX0.TBW; } if ((m_split_texture_shuffle_pages % m_split_texture_shuffle_fbw) == 0) @@ -2213,12 +2215,14 @@ void GSRendererHW::Draw() // Fortunately, it seems to change the FBMSK along the way, so this check alone is sufficient. // Tomb Raider: Underworld does similar, except with R, G, B in separate palettes, therefore // we need to split on those too. - m_channel_shuffle = IsPossibleChannelShuffle() && m_last_channel_shuffle_fbmsk == m_context->FRAME.FBMSK && + m_channel_shuffle = !m_channel_shuffle_abort && IsPossibleChannelShuffle() && m_last_channel_shuffle_fbmsk == m_context->FRAME.FBMSK && m_last_channel_shuffle_fbp <= m_context->FRAME.Block() && m_last_channel_shuffle_end_block > m_context->FRAME.Block() && m_last_channel_shuffle_tbp <= m_context->TEX0.TBP0; if (m_channel_shuffle) { + // Tombraider does vertical strips 2 pages at a time, then puts them horizontally, it's a mess, so let it do the full screen shuffle. + m_full_screen_shuffle |= !IsPageCopy() && NextDrawMatchesShuffle(); // These HLE's skip several channel shuffles in a row which change blends etc. Let's not break the flow, it gets upset. if (!m_conf.ps.urban_chaos_hle && !m_conf.ps.tales_of_abyss_hle) { @@ -2230,16 +2234,59 @@ void GSRendererHW::Draw() return; } + if (m_channel_shuffle_width) + { + if (m_last_rt) + { + //DevCon.Warning("Skipped %d draw %d was abort %d", num_skipped_channel_shuffle_draws, s_n, (int)m_channel_shuffle_abort); + // Some games like Tomb raider abort early, we're never going to know the real height, and the system doesn't work right for partials. + // But it's good enough for games like Hitman Blood Money which only shuffle part of the screen + + if (!m_full_screen_shuffle) + { + const u32 width_pages = ((num_skipped_channel_shuffle_draws + 1) % std::max(1U, m_channel_shuffle_width) % std::max(1U, m_channel_shuffle_width)) * 64;; + m_conf.scissor.w = m_conf.scissor.y + (((num_skipped_channel_shuffle_draws + 1 + (m_channel_shuffle_width - 1)) / std::max(1U, m_channel_shuffle_width)) * 32) * m_conf.cb_ps.ScaleFactor.z; + if (width_pages) + m_conf.scissor.z = m_conf.scissor.x + (((num_skipped_channel_shuffle_draws + 1) % std::max(1U, m_channel_shuffle_width) % std::max(1U, m_channel_shuffle_width)) * 64) * m_conf.cb_ps.ScaleFactor.z; + } + g_gs_device->RenderHW(m_conf); + + if (GSConfig.DumpGSData) + { + std::string s; + + if (GSConfig.ShouldDump(s_n - 1, g_perfmon.GetFrame())) + { + if (m_last_rt && GSConfig.SaveRT) + { + const u64 frame = g_perfmon.GetFrame(); + + s = GetDrawDumpPath("%05d_f%05lld_rt1_%05x_(%05x)_%s.bmp", s_n - 1, frame, m_last_channel_shuffle_fbp, m_last_rt->m_TEX0.TBP0, psm_str(m_cached_ctx.FRAME.PSM)); + + m_last_rt->m_texture->Save(s); + } + } + } + g_texture_cache->InvalidateTemporarySource(); + CleanupDraw(false); + } + } #ifdef ENABLE_OGL_DEBUG if (num_skipped_channel_shuffle_draws > 0) GL_CACHE("Skipped %d channel shuffle draws ending at %d", num_skipped_channel_shuffle_draws, s_n); #endif num_skipped_channel_shuffle_draws = 0; + m_last_channel_shuffle_fbp = 0xffff; m_last_channel_shuffle_tbp = 0xffff; m_last_channel_shuffle_end_block = 0xffff; } + m_last_rt = nullptr; + m_channel_shuffle_width = 0; + m_full_screen_shuffle = false; + m_channel_shuffle_abort = false; + GL_PUSH("HW Draw %d (Context %u)", s_n, PRIM->CTXT); GL_INS("FLUSH REASON: %s%s", GetFlushReasonString(m_state_flush_reason), (m_state_flush_reason != GSFlushReason::CONTEXTCHANGE && m_dirty_gs_regs) ? " AND POSSIBLE CONTEXT CHANGE" : @@ -2378,14 +2425,6 @@ void GSRendererHW::Draw() const bool draw_sprite_tex = PRIM->TME && (m_vt.m_primclass == GS_SPRITE_CLASS); - // We trigger the sw prim render here super early, to avoid creating superfluous render targets. - if (CanUseSwPrimRender(no_rt, no_ds, draw_sprite_tex) && SwPrimRender(*this, true, true)) - { - GL_CACHE("Possible texture decompression, drawn with SwPrimRender() (BP %x BW %u TBP0 %x TBW %u)", - m_cached_ctx.FRAME.Block(), m_cached_ctx.FRAME.FBMSK, m_cached_ctx.TEX0.TBP0, m_cached_ctx.TEX0.TBW); - return; - } - // GS doesn't fill the right or bottom edges of sprites/triangles, and for a pixel to be shaded, the vertex // must cross the center. In other words, the range is equal to the floor of coordinates +0.5. Except for // the case where the minimum equals the maximum, because at least one pixel is filled per line. @@ -2413,6 +2452,14 @@ void GSRendererHW::Draw() return; } + // We trigger the sw prim render here super early, to avoid creating superfluous render targets. + if (CanUseSwPrimRender(no_rt, no_ds, draw_sprite_tex) && SwPrimRender(*this, true, true)) + { + GL_CACHE("Possible texture decompression, drawn with SwPrimRender() (BP %x BW %u TBP0 %x TBW %u)", + m_cached_ctx.FRAME.Block(), m_cached_ctx.FRAME.FBMSK, m_cached_ctx.TEX0.TBP0, m_cached_ctx.TEX0.TBW); + return; + } + // We want to fix up the context if we're doing a double half clear, regardless of whether we do the CPU fill. const ClearType is_possible_mem_clear = IsConstantDirectWriteMemClear(); if (!GSConfig.UserHacks_DisableSafeFeatures && is_possible_mem_clear) @@ -2946,7 +2993,7 @@ void GSRendererHW::Draw() ds = g_texture_cache->LookupTarget(ZBUF_TEX0, t_size, target_scale, GSTextureCache::DepthStencil, m_cached_ctx.DepthWrite(), 0, false, force_preload, preserve_depth, preserve_depth, unclamped_draw_rect, IsPossibleChannelShuffle(), is_possible_mem_clear && ZBUF_TEX0.TBP0 != m_cached_ctx.FRAME.Block(), false, - src, -1); + src, nullptr, -1); ZBUF_TEX0.TBW = m_channel_shuffle ? src->m_from_target_TEX0.TBW : m_cached_ctx.FRAME.FBW; @@ -3009,16 +3056,18 @@ void GSRendererHW::Draw() possible_shuffle |= draw_sprite_tex && m_primitive_covers_without_gaps != NoGapsType::FullCover && (((src && src->m_target && src->m_from_target && src->m_from_target->m_32_bits_fmt) && GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].bpp == 16) || IsPossibleChannelShuffle()); + const bool possible_horizontal_texture_shuffle = possible_shuffle && src && src->m_from_target && m_r.w <= src->m_from_target->m_valid.w && m_r.z > src->m_from_target->m_valid.z && m_cached_ctx.FRAME.FBW > src->m_from_target_TEX0.TBW; + // FBW is going to be wrong for channel shuffling into a new target, so take it from the source. FRAME_TEX0.U64 = 0; FRAME_TEX0.TBP0 = ((m_last_channel_shuffle_end_block + 1) == m_cached_ctx.FRAME.Block() && possible_shuffle) ? m_last_channel_shuffle_fbp : m_cached_ctx.FRAME.Block(); - FRAME_TEX0.TBW = (possible_shuffle && IsPossibleChannelShuffle() && src && src->m_from_target) ? src->m_from_target_TEX0.TBW : m_cached_ctx.FRAME.FBW; + FRAME_TEX0.TBW = (possible_horizontal_texture_shuffle || (possible_shuffle && src && src->m_from_target && IsPossibleChannelShuffle())) ? src->m_from_target_TEX0.TBW : m_cached_ctx.FRAME.FBW; FRAME_TEX0.PSM = m_cached_ctx.FRAME.PSM; // Don't clamp on shuffle, the height cache may troll us with the REAL height. if (!possible_shuffle && m_split_texture_shuffle_pages == 0) m_r = m_r.rintersect(t_size_rect); - + GSVector4i lookup_rect = unclamped_draw_rect; // Do the lookup with the real format on a shuffle, if possible. if (possible_shuffle && GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].bpp == 16 && GSLocalMemory ::m_psm[m_cached_ctx.FRAME.PSM].bpp == 16) @@ -3035,10 +3084,10 @@ void GSRendererHW::Draw() FRAME_TEX0.PSM = PSMCT32; // Guess full color if no upcoming hint, it'll fix itself later. // This is just for overlap detection, it doesn't matter which direction we do this in - if (GSLocalMemory::m_psm[FRAME_TEX0.PSM].bpp == 32) + if (GSLocalMemory::m_psm[FRAME_TEX0.PSM].bpp == 32 && src && src->m_from_target) { // Shuffling with a double width (Sonic Unleashed for example which does a wierd shuffle/not shuffle green backup/restore). - if (src && std::abs((lookup_rect.width() / 2) - src->m_from_target->m_unscaled_size.x) <= 8) + if (std::abs((lookup_rect.width() / 2) - src->m_from_target->m_unscaled_size.x) <= 8) { lookup_rect.x /= 2; lookup_rect.z /= 2; @@ -3063,7 +3112,7 @@ void GSRendererHW::Draw() rt = g_texture_cache->LookupTarget(FRAME_TEX0, t_size, ((src && src->m_scale != 1) && GSConfig.UserHacks_NativeScaling == GSNativeScaling::Normal && !possible_shuffle) ? GetTextureScaleFactor() : target_scale, GSTextureCache::RenderTarget, true, fm, false, force_preload, preserve_rt_rgb, preserve_rt_alpha, lookup_rect, possible_shuffle, is_possible_mem_clear && FRAME_TEX0.TBP0 != m_cached_ctx.ZBUF.Block(), - GSConfig.UserHacks_NativeScaling != GSNativeScaling::Off && preserve_downscale_draw && is_possible_mem_clear != ClearType::NormalClear, src, (no_ds || !ds) ? -1 : (m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0)); + GSConfig.UserHacks_NativeScaling != GSNativeScaling::Off && preserve_downscale_draw && is_possible_mem_clear != ClearType::NormalClear, src, ds, (no_ds || !ds) ? -1 : (m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0)); // Draw skipped because it was a clear and there was no target. if (!rt) @@ -3089,6 +3138,18 @@ void GSRendererHW::Draw() FRAME_TEX0.TBW = src->m_from_target->m_TEX0.TBW; } + if (possible_shuffle && IsSplitTextureShuffle(FRAME_TEX0, lookup_rect)) + { + DevCon.Warning("Split texture shuffle early exit"); + // If TEX0 == FBP, we're going to have a source left in the TC. + // That source will get used in the actual draw unsafely, so kick it out. + if (m_cached_ctx.FRAME.Block() == m_cached_ctx.TEX0.TBP0) + g_texture_cache->InvalidateVideoMem(context->offset.fb, m_r, false); + + CleanupDraw(true); + return; + } + rt = g_texture_cache->CreateTarget(FRAME_TEX0, t_size, GetValidSize(src), (scale_draw < 0 && is_possible_mem_clear != ClearType::NormalClear) ? src->m_from_target->GetScale() : target_scale, GSTextureCache::RenderTarget, true, fm, false, force_preload, preserve_rt_color || possible_shuffle, lookup_rect, src); @@ -3253,7 +3314,7 @@ void GSRendererHW::Draw() ds = g_texture_cache->LookupTarget(ZBUF_TEX0, t_size, target_scale, GSTextureCache::DepthStencil, m_cached_ctx.DepthWrite(), 0, false, force_preload, preserve_depth, preserve_depth, unclamped_draw_rect, IsPossibleChannelShuffle(), is_possible_mem_clear && ZBUF_TEX0.TBP0 != m_cached_ctx.FRAME.Block(), false, - src, -1); + src, nullptr, -1); ZBUF_TEX0.TBW = m_channel_shuffle ? src->m_from_target_TEX0.TBW : m_cached_ctx.FRAME.FBW; @@ -3343,17 +3404,18 @@ void GSRendererHW::Draw() (shuffle_coords || rt->m_32_bits_fmt)) && (src->m_32_bits_fmt || m_copy_16bit_to_target_shuffle) && (draw_sprite_tex || (m_vt.m_primclass == GS_TRIANGLE_CLASS && (m_index.tail % 6) == 0 && TrianglesAreQuads(true))); - }; - if (m_texture_shuffle && IsSplitTextureShuffle(rt)) - { - // If TEX0 == FBP, we're going to have a source left in the TC. - // That source will get used in the actual draw unsafely, so kick it out. - if (m_cached_ctx.FRAME.Block() == m_cached_ctx.TEX0.TBP0) - g_texture_cache->InvalidateVideoMem(context->offset.fb, m_r, false); + if (m_texture_shuffle && IsSplitTextureShuffle(rt->m_TEX0, rt->m_valid)) + { + DevCon.Warning("Split texture shuffle"); + // If TEX0 == FBP, we're going to have a source left in the TC. + // That source will get used in the actual draw unsafely, so kick it out. + if (m_cached_ctx.FRAME.Block() == m_cached_ctx.TEX0.TBP0) + g_texture_cache->InvalidateVideoMem(context->offset.fb, m_r, false); - CleanupDraw(true); - return; + CleanupDraw(true); + return; + } } if ((src->m_target || (m_cached_ctx.FRAME.Block() == m_cached_ctx.TEX0.TBP0)) && IsPossibleChannelShuffle()) @@ -3902,7 +3964,6 @@ void GSRendererHW::Draw() if (!skip_draw) DrawPrims(rt, ds, src, tmm); - // // Temporary source *must* be invalidated before normal, because otherwise it'll be double freed. g_texture_cache->InvalidateTemporarySource(); @@ -3970,7 +4031,7 @@ void GSRendererHW::Draw() std::string s; - if (rt && GSConfig.SaveRT) + if (rt && GSConfig.SaveRT && !m_last_rt) { s = GetDrawDumpPath("%05d_f%05lld_rt1_%05x_%s.bmp", s_n, frame, m_cached_ctx.FRAME.Block(), psm_str(m_cached_ctx.FRAME.PSM)); @@ -4049,7 +4110,7 @@ bool GSRendererHW::VerifyIndices() return true; } -void GSRendererHW::SetupIA(float target_scale, float sx, float sy) +void GSRendererHW::SetupIA(float target_scale, float sx, float sy, bool req_vert_backup) { GL_PUSH("IA"); @@ -4130,7 +4191,20 @@ void GSRendererHW::SetupIA(float target_scale, float sx, float sy) { m_conf.topology = GSHWDrawConfig::Topology::Triangle; m_conf.vs.expand = GSHWDrawConfig::VSExpand::Sprite; - m_conf.verts = m_vertex.buff; + + if (req_vert_backup) + { + memcpy(m_draw_vertex.buff, m_vertex.buff, sizeof(GSVertex) * m_vertex.next); + memcpy(m_draw_index.buff, m_index.buff, sizeof(u16) * m_index.tail); + + m_conf.verts = m_draw_vertex.buff; + m_conf.indices = m_draw_index.buff; + } + else + { + m_conf.verts = m_vertex.buff; + m_conf.indices = m_index.buff; + } m_conf.nverts = m_vertex.next; m_conf.nindices = m_index.tail * 3; m_conf.indices_per_prim = 6; @@ -4171,9 +4245,20 @@ void GSRendererHW::SetupIA(float target_scale, float sx, float sy) ASSUME(0); } - m_conf.verts = m_vertex.buff; + if (req_vert_backup) + { + memcpy(m_draw_vertex.buff, m_vertex.buff, sizeof(GSVertex) * m_vertex.next); + memcpy(m_draw_index.buff, m_index.buff, sizeof(u16) * m_index.tail); + + m_conf.verts = m_draw_vertex.buff; + m_conf.indices = m_draw_index.buff; + } + else + { + m_conf.verts = m_vertex.buff; + m_conf.indices = m_index.buff; + } m_conf.nverts = m_vertex.next; - m_conf.indices = m_index.buff; m_conf.nindices = m_index.tail; } @@ -4601,6 +4686,8 @@ __ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool // Performance GPU note: it could be wise to reduce the size to // the rendered size of the framebuffer + const GSLocalMemory::psm_t frame_psm = GSLocalMemory::m_psm[m_context->FRAME.PSM]; + m_full_screen_shuffle = (m_r.height() > frame_psm.pgs.y) || (m_r.width() > frame_psm.pgs.x) || GSConfig.UserHacks_TextureInsideRt == GSTextureInRtMode::Disabled; if (GSConfig.UserHacks_TextureInsideRt == GSTextureInRtMode::Disabled || (!m_in_target_draw && IsPageCopy()) || m_conf.ps.urban_chaos_hle || m_conf.ps.tales_of_abyss_hle) { GSVertex* s = &m_vertex.buff[0]; @@ -4615,10 +4702,13 @@ __ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool s[1].V = 16384; m_r = GSVector4i(0, 0, 1024, 1024); + + // We need to count the pages that get shuffled to, some games (like Hitman Blood Money dialogue blur effects) only do half the screen. + if (!m_full_screen_shuffle && !m_conf.ps.urban_chaos_hle && !m_conf.ps.tales_of_abyss_hle && src) + m_channel_shuffle_width = src->m_TEX0.TBW; } else { - const GSLocalMemory::psm_t frame_psm = GSLocalMemory::m_psm[m_context->FRAME.PSM]; const u32 frame_page_offset = std::max(static_cast(((m_r.x / frame_psm.pgs.x) + (m_r.y / frame_psm.pgs.y) * src->m_TEX0.TBW) - m_target_offset), 0); m_r = GSVector4i(m_r.x & ~(frame_psm.pgs.x - 1), m_r.y & ~(frame_psm.pgs.y - 1), (m_r.z + (frame_psm.pgs.x - 1)) & ~(frame_psm.pgs.x - 1), (m_r.w + (frame_psm.pgs.y - 1)) & ~(frame_psm.pgs.y - 1)); //m_cached_ctx.FRAME.FBP += frame_page_offset; @@ -4643,6 +4733,7 @@ __ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool m_index.tail = 2; m_primitive_covers_without_gaps = NoGapsType::FullCover; + m_channel_shuffle_abort = false; return true; } @@ -6997,7 +7088,7 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta m_conf.drawarea = m_channel_shuffle ? scissor : scissor.rintersect(ComputeBoundingBox(rtsize, rtscale)); m_conf.scissor = (DATE && !DATE_BARRIER) ? m_conf.drawarea : scissor; - SetupIA(rtscale, sx, sy); + SetupIA(rtscale, sx, sy, m_channel_shuffle_width != 0); if (ate_second_pass) { @@ -7085,7 +7176,10 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta m_conf.drawlist = (m_conf.require_full_barrier && m_vt.m_primclass == GS_SPRITE_CLASS) ? &m_drawlist : nullptr; - g_gs_device->RenderHW(m_conf); + if (!m_channel_shuffle_width) + g_gs_device->RenderHW(m_conf); + else + m_last_rt = rt; } // If the EE uploaded a new CLUT since the last draw, use that. @@ -7345,7 +7439,7 @@ bool GSRendererHW::CanUseSwPrimRender(bool no_rt, bool no_ds, bool draw_sprite_t { GSTextureCache::Target* rt = g_texture_cache->GetTargetWithSharedBits(m_cached_ctx.FRAME.Block(), m_cached_ctx.FRAME.PSM); - if (!rt) + if (!rt || (!rt->m_dirty.empty() && rt->m_dirty.GetTotalRect(rt->m_TEX0, rt->m_unscaled_size).rintersect(m_r).eq(m_r))) return true; rt = nullptr; diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.h b/pcsx2/GS/Renderers/HW/GSRendererHW.h index 4e5f937b03..0a323f9209 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.h +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.h @@ -92,7 +92,7 @@ private: void DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Target* ds, GSTextureCache::Source* tex, const TextureMinMaxResult& tmm); void ResetStates(); - void SetupIA(float target_scale, float sx, float sy); + void SetupIA(float target_scale, float sx, float sy, bool req_vert_backup); void EmulateTextureShuffleAndFbmask(GSTextureCache::Target* rt, GSTextureCache::Source* tex); bool EmulateChannelShuffle(GSTextureCache::Target* src, bool test_only); void EmulateBlending(int rt_alpha_min, int rt_alpha_max, const bool DATE, bool& DATE_PRIMID, bool& DATE_BARRIER, GSTextureCache::Target* rt, @@ -115,7 +115,7 @@ private: bool IsPossibleChannelShuffle() const; bool IsPageCopy() const; bool NextDrawMatchesShuffle() const; - bool IsSplitTextureShuffle(GSTextureCache::Target* rt); + bool IsSplitTextureShuffle(GIFRegTEX0& rt_TEX0, GSVector4i& valid_area); GSVector4i GetSplitTextureShuffleDrawRect() const; u32 GetEffectiveTextureShuffleFbmsk() const; @@ -176,6 +176,10 @@ private: u32 m_last_channel_shuffle_fbp = 0; u32 m_last_channel_shuffle_tbp = 0; u32 m_last_channel_shuffle_end_block = 0; + u32 m_channel_shuffle_width = 0; + bool m_full_screen_shuffle = false; + + GSTextureCache::Target* m_last_rt; GIFRegFRAME m_split_clear_start = {}; GIFRegZBUF m_split_clear_start_Z = {}; diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index 92ecfe5314..5fca0d9099 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -1296,7 +1296,21 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const if (!possible_shuffle && frame_fbp != t->m_TEX0.TBP0 && rect_clean && bp == t->m_TEX0.TBP0 && t && GSUtil::HasCompatibleBits(psm, t->m_TEX0.PSM) && width_match && real_fmt_match) { if (!tex_merge_rt && t->Overlaps(bp, bw, psm, req_rect)) + { + // Resize but be careful of +bilinear in req_rect, as it can screw valid areas. + if (psm_s.bpp == GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp && !block_boundary_rect.rintersect(t->m_valid).eq(block_boundary_rect)) + { + RGBAMask rgba_mask; + rgba_mask.c.a = req_alpha; + rgba_mask.c.r = rgba_mask.c.g = rgba_mask.c.b = req_color; + if (block_boundary_rect.z > t->m_valid.z) + AddDirtyRectTarget(t, GSVector4i(t->m_valid.z, t->m_valid.y, block_boundary_rect.z, std::max(block_boundary_rect.w, t->m_valid.w)), t->m_TEX0.PSM, t->m_TEX0.TBW, rgba_mask); + if (block_boundary_rect.w > t->m_valid.w) + AddDirtyRectTarget(t, GSVector4i(t->m_valid.x, t->m_valid.w, std::max(block_boundary_rect.z, t->m_valid.z), block_boundary_rect.w), t->m_TEX0.PSM, t->m_TEX0.TBW, rgba_mask); + } + // Resize including the extra pixel for bilinear. ResizeTarget(t, req_rect, bp, psm, bw); + } } } @@ -1800,6 +1814,21 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const } } + if (src->m_from_target && src->m_target_direct && src->m_region.HasEither()) + { + if (src->m_from_target->m_TEX0.TBP0 == src->m_TEX0.TBP0) + { + src->m_region.SetX(std::min(region.GetMinX(), src->m_region.GetMinX()), std::max(region.GetMaxX(), src->m_region.GetMaxX())); + src->m_region.SetY(std::min(region.GetMinY(), src->m_region.GetMinY()), std::max(region.GetMaxY(), src->m_region.GetMaxY())); + } + else if (src->m_TEX0.TBP0 > src->m_from_target->m_TEX0.TBP0) + { + GSVector4i dst_offset = TranslateAlignedRectByPage(src->m_from_target, src->m_TEX0.TBP0, src->m_TEX0.PSM, src->m_TEX0.TBW, GSVector4i(0, 0, 1, 1), false); + src->m_region.SetX(dst_offset.x + region.GetMinX(), dst_offset.x + region.GetMaxX()); + src->m_region.SetY(dst_offset.y + region.GetMinY(), dst_offset.y + region.GetMaxY()); + } + } + if (gpu_clut) AttachPaletteToSource(src, gpu_clut); else if (src->m_palette && (!src->m_palette_obj || !src->ClutMatch({clut, psm_s.pal}))) @@ -1830,7 +1859,7 @@ GSVector2i GSTextureCache::ScaleRenderTargetSize(const GSVector2i& sz, float sca GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVector2i& size, float scale, int type, bool used, u32 fbmask, bool is_frame, bool preload, bool preserve_rgb, bool preserve_alpha, const GSVector4i draw_rect, - bool is_shuffle, bool possible_clear, bool preserve_scale, GSTextureCache::Source* src, int offset) + bool is_shuffle, bool possible_clear, bool preserve_scale, GSTextureCache::Source* src, GSTextureCache::Target* ds, int offset) { const GSLocalMemory::psm_t& psm_s = GSLocalMemory::m_psm[TEX0.PSM]; const u32 bp = TEX0.TBP0; @@ -1968,6 +1997,23 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe } else if (t->m_dirty.empty()) { + + if (TEX0.TBW == t->m_TEX0.TBW && !is_shuffle && widthpage_offset == 0 && ((min_rect.w + 63)/ 64) > 1) + { + // Beyond Good and Evil does this awful thing where it puts one framebuffer at 0xf00, with the first row of pages blanked out, and the whole thing goes down to 0x2080 + // which is a problem, because it then puts the Z buffer at 0x1fc0, then offsets THAT by 1 row of pages, so it starts at, you guessed it, 2080. + // So let's check the *real* start. + u32 real_start_address = GSLocalMemory::GetStartBlockAddress(t->m_TEX0.TBP0, t->m_TEX0.TBW, t->m_TEX0.PSM, t->m_drawn_since_read); + u32 new_end_address = GSLocalMemory::GetEndBlockAddress(TEX0.TBP0, TEX0.TBW, TEX0.PSM, min_rect); + + // Not really overlapping. + if (real_start_address > new_end_address) + { + i++; + continue; + } + } + //DevCon.Warning("Here draw %d wanted %x PSM %x got %x PSM %x offset of %d pages width %d pages draw width %d", GSState::s_n, bp, TEX0.PSM, t->m_TEX0.TBP0, t->m_TEX0.PSM, (bp - t->m_TEX0.TBP0) >> 5, t->m_TEX0.TBW, draw_rect.width()); dst = t; @@ -2322,7 +2368,9 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe continue; } // If the format is completely different, but it's the same location, it's likely just overwriting it, so get rid. - if (!is_shuffle && t->m_TEX0.TBW != TEX0.TBW && TEX0.TBW != 1 && !preserve_rgb && min_rect.w > GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y) + // Make sure it's not currently in use, that could be bad. + if (!is_shuffle && (!ds || (ds != t)) && + t->m_TEX0.TBW != TEX0.TBW && TEX0.TBW != 1 && !preserve_rgb && min_rect.w > GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y) { DevCon.Warning("Deleting Z draw %d", GSState::s_n); InvalidateSourcesFromTarget(t); @@ -2637,11 +2685,12 @@ bool GSTextureCache::PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, cons RGBAMask rgba; rgba._u32 = GSUtil::GetChannelMask(TEX0.PSM); + dst->UpdateValidity(GSVector4i::loadh(valid_size)); if (!is_frame && !preload && !(src && src->m_TEX0.TBP0 == dst->m_TEX0.TBP0)) { - if ((preserve_target || !draw_rect.eq(dst->m_valid)) && GSRendererHW::GetInstance()->m_draw_transfers.size() > 0) + if ((preserve_target || !draw_rect.eq(GSVector4i::loadh(valid_size))) && GSRendererHW::GetInstance()->m_draw_transfers.size() > 0) { auto& transfers = GSRendererHW::GetInstance()->m_draw_transfers; const int last_draw = transfers.back().draw; @@ -2735,8 +2784,6 @@ bool GSTextureCache::PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, cons { const GSVector4i save_rect = preserve_target ? newrect : eerect; - if (!hw_clear) - dst->UpdateValidity(save_rect); GL_INS("Preloading the RT DATA from updated GS Memory"); AddDirtyRectTarget(dst, save_rect, TEX0.PSM, TEX0.TBW, rgba, GSLocalMemory::m_psm[TEX0.PSM].trbpp >= 16); } @@ -4878,8 +4925,9 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con src->m_valid_rect = dst->m_valid; src->m_end_block = dst->m_end_block; - // Do this first as we could be adding in alpha from an upgraded 24bit target. - dst->Update(); + // Do this first as we could be adding in alpha from an upgraded 24bit target. if the rect intersects a dirty area. + if (!dst->m_dirty.empty() && !src_range->rintersect(dst->m_dirty.GetTotalRect(dst->m_TEX0, dst->m_unscaled_size)).rempty()) + dst->Update(); src->m_valid_alpha_minmax = true; if ((src->m_TEX0.PSM & 0xf) == PSMCT24) @@ -6596,7 +6644,7 @@ void GSTextureCache::Target::Update(bool cannot_scale) { if (alpha_minmax.second > 128 || (m_TEX0.PSM & 0xf) == PSMCT24) UnscaleRTAlpha(); - else if (!cannot_scale && total_rect.eq(m_valid)) + else if (!cannot_scale && total_rect.rintersect(m_valid).eq(m_valid)) m_rt_alpha_scale = true; } diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.h b/pcsx2/GS/Renderers/HW/GSTextureCache.h index 7bb1d5b15d..9de5db136a 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.h +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.h @@ -492,7 +492,7 @@ public: Target* FindTargetOverlap(Target* target, int type, int psm); Target* LookupTarget(GIFRegTEX0 TEX0, const GSVector2i& size, float scale, int type, bool used = true, u32 fbmask = 0, bool is_frame = false, bool preload = GSConfig.PreloadFrameWithGSData, bool preserve_rgb = true, bool preserve_alpha = true, - const GSVector4i draw_rc = GSVector4i::zero(), bool is_shuffle = false, bool possible_clear = false, bool preserve_scale = false, GSTextureCache::Source* src = nullptr, int offset = -1); + const GSVector4i draw_rc = GSVector4i::zero(), bool is_shuffle = false, bool possible_clear = false, bool preserve_scale = false, GSTextureCache::Source* src = nullptr, GSTextureCache::Target* ds = nullptr, int offset = -1); Target* CreateTarget(GIFRegTEX0 TEX0, const GSVector2i& size, const GSVector2i& valid_size,float scale, int type, bool used = true, u32 fbmask = 0, bool is_frame = false, bool preload = GSConfig.PreloadFrameWithGSData, bool preserve_target = true, const GSVector4i draw_rc = GSVector4i::zero(), GSTextureCache::Source* src = nullptr); From f94d5faaf29cef5b6af4c0d015efe9ceab465059 Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Fri, 24 Jan 2025 14:55:18 +0000 Subject: [PATCH 102/162] GS/HW: Further fixes and rewrite of AlignedRectTranslate --- pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 93 +++++++-- pcsx2/GS/Renderers/HW/GSRendererHW.h | 2 +- pcsx2/GS/Renderers/HW/GSTextureCache.cpp | 245 ++++++++++++++++++----- pcsx2/GS/Renderers/HW/GSTextureCache.h | 2 +- 4 files changed, 270 insertions(+), 72 deletions(-) diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index 229f106b44..d43264eeea 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -3140,7 +3140,6 @@ void GSRendererHW::Draw() if (possible_shuffle && IsSplitTextureShuffle(FRAME_TEX0, lookup_rect)) { - DevCon.Warning("Split texture shuffle early exit"); // If TEX0 == FBP, we're going to have a source left in the TC. // That source will get used in the actual draw unsafely, so kick it out. if (m_cached_ctx.FRAME.Block() == m_cached_ctx.TEX0.TBP0) @@ -3382,7 +3381,8 @@ void GSRendererHW::Draw() { // Hypothesis: texture shuffle is used as a postprocessing effect so texture will be an old target. // Initially code also tested the RT but it gives too much false-positive - const int first_x = ((v[0].XYZ.X - m_context->XYOFFSET.OFX) + 8) >> 4; + const int horizontal_offset = ((static_cast((m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0)) / 32) % static_cast(std::max(rt->m_TEX0.TBW, 1U))) * frame_psm.pgs.x; + const int first_x = (((v[0].XYZ.X - m_context->XYOFFSET.OFX) + 8) >> 4) - horizontal_offset; const int first_u = PRIM->FST ? ((v[0].U + 8) >> 4) : static_cast(((1 << m_cached_ctx.TEX0.TW) * (v[0].ST.S / v[1].RGBAQ.Q)) + 0.5f); const bool shuffle_coords = (first_x ^ first_u) & 8; const u32 draw_end = GSLocalMemory::GetEndBlockAddress(m_cached_ctx.FRAME.Block(), m_cached_ctx.FRAME.FBW, m_cached_ctx.FRAME.PSM, m_r) + 1; @@ -3407,7 +3407,6 @@ void GSRendererHW::Draw() if (m_texture_shuffle && IsSplitTextureShuffle(rt->m_TEX0, rt->m_valid)) { - DevCon.Warning("Split texture shuffle"); // If TEX0 == FBP, we're going to have a source left in the TC. // That source will get used in the actual draw unsafely, so kick it out. if (m_cached_ctx.FRAME.Block() == m_cached_ctx.TEX0.TBP0) @@ -3561,7 +3560,9 @@ void GSRendererHW::Draw() // Deferred update of TEX0. We don't want to change it when we're doing a shuffle/clear, because it // may increase the buffer width, or change PSM, which breaks P8 conversion amongst other things. + // Some texture shuffles can be to new targets (or reused ones) so they may need their valid rects adjusting. const bool can_update_size = !is_possible_mem_clear && !m_texture_shuffle && !m_channel_shuffle; + if (!m_texture_shuffle && !m_channel_shuffle) { // Try to turn blits in to single sprites, saves upscaling problems when striped clears/blits. @@ -3660,17 +3661,51 @@ void GSRendererHW::Draw() if (!(m_cached_ctx.TEST.DATE && m_cached_ctx.TEST.DATM)) { GSVector2i new_size = t_size; - + GSVector4i update_rect = m_r; + const GIFRegTEX0& draw_TEX0 = rt ? rt->m_TEX0 : ds->m_TEX0; + const int buffer_width = std::max(draw_TEX0.TBW, 1U) * 64; // We need to adjust the size if it's a texture shuffle as we could end up making the RT twice the size. - if (src && m_texture_shuffle && !m_copy_16bit_to_target_shuffle && m_split_texture_shuffle_pages == 0) + if (src && m_texture_shuffle && !m_copy_16bit_to_target_shuffle) { if ((new_size.x > src->m_valid_rect.z && m_vt.m_max.p.x == new_size.x) || (new_size.y > src->m_valid_rect.w && m_vt.m_max.p.y == new_size.y)) { if (new_size.y <= src->m_valid_rect.w && (rt->m_TEX0.TBW != m_cached_ctx.FRAME.FBW)) + { new_size.x /= 2; + } else + { new_size.y /= 2; + } } + + if (update_rect.z > src->m_valid_rect.z && (rt->m_TEX0.TBW != m_cached_ctx.FRAME.FBW)) + { + // This is a case for Superman Shadow of Apokalypse where it is *nearly* double height and slightly wider, but the page count adds up. + if (update_rect.w > src->m_valid_rect.w) + { + update_rect = src->m_valid_rect; + } + else + { + update_rect.x /= 2; + update_rect.z /= 2; + } + } + else + { + update_rect.y /= 2; + update_rect.w /= 2; + } + } + // NFS Undercover does a draw with double width of the actual width 1280x240, which functions the same as doubling the height. + // Ignore single page/0 page stuff, that's just gonna get silly + else if (buffer_width > 64 && update_rect.z > buffer_width) + { + float multifactor = static_cast(update_rect.z) / static_cast(buffer_width); + + update_rect.w *= multifactor; + update_rect.z = buffer_width; } if (m_in_target_draw && src && m_channel_shuffle && src->m_from_target && src->m_from_target == rt && m_cached_ctx.TEX0.TBP0 == src->m_from_target->m_TEX0.TBP0) @@ -3733,12 +3768,13 @@ void GSRendererHW::Draw() rt->ResizeDrawn(rt->GetUnscaledRect()); } - const GSVector4i update_rect = m_r.rintersect(GSVector4i::loadh(GSVector2i(new_w, new_h))); + const bool rt_update = can_update_size || (m_texture_shuffle && (src && rt && src->m_from_target != rt)); + // if frame is masked or afailing always to never write frame, wanna make sure we don't touch it. This might happen if DATE or Alpha Test is being used to write to Z. const bool frame_masked = ((m_cached_ctx.FRAME.FBMSK & frame_psm.fmsk) == frame_psm.fmsk) || (m_cached_ctx.TEST.ATE && m_cached_ctx.TEST.ATST == ATST_NEVER && !(m_cached_ctx.TEST.AFAIL & AFAIL_FB_ONLY)); // Limit to 2x the vertical height of the resolution (for double buffering) - rt->UpdateValidity(update_rect, !frame_masked && (can_update_size || (m_r.w <= (resolution.y * 2) && !m_texture_shuffle))); - rt->UpdateDrawn(update_rect, !frame_masked && (can_update_size || (m_r.w <= (resolution.y * 2) && !m_texture_shuffle))); + rt->UpdateValidity(update_rect, !frame_masked && (rt_update || (m_r.w <= (resolution.y * 2) && !m_texture_shuffle))); + rt->UpdateDrawn(update_rect, !frame_masked && (rt_update || (m_r.w <= (resolution.y * 2) && !m_texture_shuffle))); // Probably changing to double buffering, so invalidate any old target that was next to it. // This resolves an issue where the PCRTC will find the old target in FMV's causing flashing. // Grandia Xtreme, Onimusha Warlord. @@ -4003,13 +4039,13 @@ void GSRendererHW::Draw() GSTextureCache::RenderTarget, m_cached_ctx.ZBUF.Block(), m_cached_ctx.ZBUF.PSM, zm); - if (g_texture_cache->GetTemporaryZ()) + if (ds && g_texture_cache->GetTemporaryZ()) { if (m_cached_ctx.DepthWrite()) { - int vertical_offset = ((static_cast(m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0) / 32) / std::max(static_cast(rt->m_TEX0.TBW), 1)) * frame_psm.pgs.y; - int z_vertical_offset = ((static_cast(m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0) / 32) / std::max(rt->m_TEX0.TBW, 1U)) * GSLocalMemory::m_psm[m_cached_ctx.ZBUF.PSM].pgs.y; - int z_offset = vertical_offset; + const int vertical_offset = ((static_cast(m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0) / 32) / std::max(static_cast(rt->m_TEX0.TBW), 1)) * frame_psm.pgs.y; + const int z_vertical_offset = ((static_cast(m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0) / 32) / std::max(rt->m_TEX0.TBW, 1U)) * GSLocalMemory::m_psm[m_cached_ctx.ZBUF.PSM].pgs.y; + const int z_offset = vertical_offset; GL_CACHE("RT in RT Z copy back draw %d z_vert_offset %d z_offset %d", s_n, z_vertical_offset, z_offset); GSVector4i dRect = GSVector4i(0, z_vertical_offset * ds->m_scale, ds->m_unscaled_size.x * ds->m_scale, std::min(z_vertical_offset + m_r.w + 1 - vertical_offset, ds->m_unscaled_size.y) * ds->m_scale); g_gs_device->StretchRect(g_texture_cache->GetTemporaryZ(), GSVector4(0.0f, z_offset / static_cast(g_texture_cache->GetTemporaryZ()->GetHeight()), 1.0f, std::min(real_rect.w + 1, ds->m_unscaled_size.y + z_offset) / static_cast(g_texture_cache->GetTemporaryZ()->GetHeight())), ds->m_texture, GSVector4(dRect), ShaderConvert::DEPTH_COPY, false); @@ -4504,7 +4540,7 @@ bool GSRendererHW::TestChannelShuffle(GSTextureCache::Target* src) return m_channel_shuffle; } -__ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool test_only) +__ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool test_only, GSTextureCache::Target* rt) { if ((src->m_texture->GetType() == GSTexture::Type::DepthStencil) && !src->m_32_bits_fmt) { @@ -4709,9 +4745,30 @@ __ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool } else { - const u32 frame_page_offset = std::max(static_cast(((m_r.x / frame_psm.pgs.x) + (m_r.y / frame_psm.pgs.y) * src->m_TEX0.TBW) - m_target_offset), 0); + const u32 frame_page_offset = std::max(static_cast(((m_r.x / frame_psm.pgs.x) + (m_r.y / frame_psm.pgs.y) * rt->m_TEX0.TBW)), 0); m_r = GSVector4i(m_r.x & ~(frame_psm.pgs.x - 1), m_r.y & ~(frame_psm.pgs.y - 1), (m_r.z + (frame_psm.pgs.x - 1)) & ~(frame_psm.pgs.x - 1), (m_r.w + (frame_psm.pgs.y - 1)) & ~(frame_psm.pgs.y - 1)); - //m_cached_ctx.FRAME.FBP += frame_page_offset; + + // Hitman suffers from this, not sure on the exact scenario at the moment, but we need the barrier. + if (PRIM->ABE && m_context->ALPHA.IsCdInBlend()) + { + if (m_prim_overlap == PRIM_OVERLAP_NO || !g_gs_device->Features().texture_barrier) + m_conf.require_one_barrier = true; + else + m_conf.require_full_barrier = true; + } + + // This is for offsetting the texture, however if the texture has a region clamp, we don't want to move it. + // A good two test games for this is Ghost in the Shell (no region clamp) and Tekken 5 (offset clamp on shadows) + if (rt && rt->m_TEX0.TBP0 == m_cached_ctx.FRAME.Block()) + { + const bool req_offset = (m_cached_ctx.CLAMP.WMS != 3 || (m_cached_ctx.CLAMP.MAXU & ~0xF) == 0) && + (m_cached_ctx.CLAMP.WMT != 3 || (m_cached_ctx.CLAMP.MAXV & ~0x3) == 0); + //DevCon.Warning("Draw %d offset %d", s_n, frame_page_offset); + // Offset the frame but clear the draw offset + if (req_offset) + m_cached_ctx.FRAME.FBP += frame_page_offset; + } + m_in_target_draw |= frame_page_offset > 0; GSVertex* s = &m_vertex.buff[0]; s[0].XYZ.X = static_cast(m_context->XYOFFSET.OFX + (m_r.x << 4)); @@ -4721,7 +4778,7 @@ __ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool const GSLocalMemory::psm_t tex_psm = GSLocalMemory::m_psm[m_context->TEX0.PSM]; const u32 tex_page_offset = (m_vt.m_min.t.x / tex_psm.pgs.x) + (m_vt.m_min.t.y / tex_psm.pgs.y); - //m_cached_ctx.TEX0.TBP0 += tex_page_offset << 5; + s[0].U = m_r.x << 4; s[1].U = m_r.z << 4; s[0].V = m_r.y << 4; @@ -5578,7 +5635,7 @@ __ri void GSRendererHW::EmulateTextureSampler(const GSTextureCache::Target* rt, // Hazard handling (i.e. reading from the current RT/DS). GSTextureCache::SourceRegion source_region = tex->GetRegion(); - bool target_region = (tex->IsFromTarget() && source_region.HasEither()); + bool target_region = tex->IsFromTarget() && source_region.HasEither(); GSVector2i unscaled_size = target_region ? tex->GetRegionSize() : tex->GetUnscaledSize(); float scale = tex->GetScale(); HandleTextureHazards(rt, ds, tex, tmm, source_region, target_region, unscaled_size, scale, src_copy); @@ -6406,7 +6463,7 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta // vertex list (it will interact with PrimitiveOverlap and accurate // blending) if (m_channel_shuffle && tex && tex->m_from_target) - EmulateChannelShuffle(tex->m_from_target, false); + EmulateChannelShuffle(tex->m_from_target, false, rt); // Upscaling hack to avoid various line/grid issues MergeSprite(tex); diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.h b/pcsx2/GS/Renderers/HW/GSRendererHW.h index 0a323f9209..aa5fb49828 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.h +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.h @@ -94,7 +94,7 @@ private: void ResetStates(); void SetupIA(float target_scale, float sx, float sy, bool req_vert_backup); void EmulateTextureShuffleAndFbmask(GSTextureCache::Target* rt, GSTextureCache::Source* tex); - bool EmulateChannelShuffle(GSTextureCache::Target* src, bool test_only); + bool EmulateChannelShuffle(GSTextureCache::Target* src, bool test_only, GSTextureCache::Target* rt = nullptr); void EmulateBlending(int rt_alpha_min, int rt_alpha_max, const bool DATE, bool& DATE_PRIMID, bool& DATE_BARRIER, GSTextureCache::Target* rt, bool can_scale_rt_alpha, bool& new_rt_alpha_scale); void CleanupDraw(bool invalidate_temp_src); diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index 5fca0d9099..85a64fb9ad 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -253,6 +253,163 @@ bool GSTextureCache::CanTranslate(u32 bp, u32 bw, u32 spsm, GSVector4i r, u32 db } +GSVector4i GSTextureCache::TranslateAlignedRectByPage(u32 tbp, u32 tebp, u32 tbw, u32 tpsm, u32 sbp, u32 spsm, u32 sbw, GSVector4i src_r, bool is_invalidation) +{ + const GSVector2i src_page_size = GSLocalMemory::m_psm[spsm].pgs; + const GSVector2i dst_page_size = GSLocalMemory::m_psm[tpsm].pgs; + const int clamped_sbw = static_cast(std::max(1U, sbw)); + const int clamped_tbw = static_cast(std::max(1U, tbw)); + const int src_bw = clamped_sbw * 64; + const int dst_bw = clamped_tbw * 64; + const GSLocalMemory::psm_t& s_psm = GSLocalMemory::m_psm[spsm]; + const GSLocalMemory::psm_t& t_psm = GSLocalMemory::m_psm[tpsm]; + const int src_pgw = std::max(1, src_bw / src_page_size.x); + const int dst_pgw = std::max(1, dst_bw / dst_page_size.x); + GSVector4i in_rect = src_r; + + if (sbp < tebp && tebp < tbp) + sbp += 0x4000; + // DST = the target we're trying to fit in to. + // SRC = the format being requested, so we want to from SRC to DST. + int page_offset = (static_cast(sbp) - static_cast(tbp)) >> 5; + int block_offset = (static_cast(sbp) - static_cast(tbp)) & 0x1F; + + if (!(s_psm.bpp == t_psm.bpp)) + { + const int src_bpp = s_psm.bpp; + + if (block_offset) + in_rect = in_rect.ralign(s_psm.bs); + else + in_rect = in_rect.ralign(s_psm.pgs); + + // Convert rect down in to pages and blocks. + const GSVector4i in_pages = GSVector4i(in_rect.x / s_psm.pgs.x, in_rect.y / s_psm.pgs.y, in_rect.z / s_psm.pgs.x, in_rect.w / s_psm.pgs.y); + in_rect -= GSVector4i(in_pages.x * s_psm.pgs.x, in_pages.y * s_psm.pgs.y, in_pages.z * s_psm.pgs.x, in_pages.w * s_psm.pgs.y); + // Handle a minimum of 1 block, they are a different shape between 16 and 32bit. 8x8 vs 16x8. + // FIXME: Block layouts are different between 32bit/8bit and other formats (8x4 instead of 4x8), so this could be a problem if the game invalidates too much. + const GSVector4i in_blocks = GSVector4i(in_rect.x / s_psm.bs.x, in_rect.y / s_psm.bs.y, (in_rect.z + (s_psm.bs.x - 1)) / s_psm.bs.x, (in_rect.w + (s_psm.bs.y - 1)) / s_psm.bs.y); + + // Project Snowblind and Tomb Raider access the rect offset by 1 page and use a region to correct it, we need to account for that here. + in_rect = GSVector4i(in_pages.x * t_psm.pgs.x, in_pages.y * t_psm.pgs.y, in_pages.z * t_psm.pgs.x, in_pages.w * t_psm.pgs.y); + in_rect += GSVector4i(in_blocks.x * t_psm.bs.x, in_blocks.y * t_psm.bs.y, in_blocks.z * t_psm.bs.x, in_blocks.w * t_psm.bs.y); + + if (in_rect.rempty()) + { + DevCon.Warning("Error translating rect"); + return GSVector4i::zero(); + } + } + + GSVector4i new_rect = GSVector4i::zero(); + + if (src_pgw != dst_pgw) + { + const int horizontal_dst_page_offset = page_offset % clamped_tbw; + const bool single_row = ((src_pgw + horizontal_dst_page_offset) <= clamped_tbw) && (in_rect.height() <= dst_page_size.y); + const bool single_page = (in_rect.width() <= t_psm.pgs.x) && (in_rect.height() <= t_psm.pgs.y); + const int vertical_offset = in_rect.y / t_psm.pgs.y; + const int horizontal_offset = in_rect.x / t_psm.pgs.x; + const int rect_offset = horizontal_offset + (vertical_offset * src_pgw); + const int rect_pages = ((in_rect.width() / t_psm.pgs.x) % src_pgw) + ((in_rect.height() / t_psm.pgs.y) * src_pgw); + page_offset += rect_offset; + in_rect -= GSVector4i(horizontal_offset * t_psm.pgs.x, vertical_offset * t_psm.pgs.y).xyxy(); + + if (sbw == 0) // Intentionally check this separately + { + // BW == 0 loops vertically on the first page. So just copy the whole page vertically. + if (in_rect.z > dst_page_size.x) + { + new_rect.x = 0; + new_rect.z = (dst_page_size.x); + } + else + { + new_rect.x = in_rect.x; + new_rect.z = in_rect.z; + } + if (in_rect.w > dst_page_size.y) + { + new_rect.y = 0; + new_rect.w = dst_page_size.y; + } + else + { + new_rect.y = in_rect.y; + new_rect.w = in_rect.w; + } + } + else if (src_pgw == 1 && (horizontal_dst_page_offset + rect_pages) <= clamped_tbw) // Intentionally check this separately + { + new_rect.x = (horizontal_dst_page_offset * t_psm.pgs.x) + in_rect.x; + new_rect.z = new_rect.x + (rect_pages * t_psm.pgs.x); + new_rect.y = (page_offset / dst_pgw) * t_psm.pgs.y; + new_rect.w = new_rect.y + t_psm.pgs.y; + } + else if (single_row || single_page) // Single page and single row should be handled the same here + { + //The offsets will move this to the right place + const GSVector2i start_page = GSVector2i(page_offset % dst_pgw, page_offset / dst_pgw); + new_rect.x = (start_page.x * t_psm.pgs.x) + in_rect.x; + new_rect.z = (start_page.x * t_psm.pgs.x) + in_rect.z; + new_rect.y = (start_page.y * t_psm.pgs.y) + in_rect.y; + new_rect.w = (start_page.y * t_psm.pgs.y) + in_rect.w; + } + else + { + + + // Fills full length, so count pages based on the width, adjust rect to fill original rect. + // Battle Assault 3 does a move with BW 7 instead of 8 and does 448x512, instead of 512x448. Same result, but confusing for us. + if ((in_rect.width() / dst_page_size.x) == src_pgw) + { + // The width is mismatched to the page. + if (!is_invalidation && GSConfig.UserHacks_TextureInsideRt < GSTextureInRtMode::MergeTargets) + { + DevCon.Warning("Uneven pages mess up sbp %x dbp %x spgw %d dpgw %d src fmt %d dst fmt %d src_rect %d, %d, %d, %d draw %d", sbp, tbp, src_pgw, dst_pgw, spsm, tpsm, in_rect.x, in_rect.y, in_rect.z, in_rect.w, GSState::s_n); + return GSVector4i::zero(); + } + + const GSVector2i start_page = GSVector2i(page_offset % dst_pgw, page_offset / dst_pgw); + int page_count = (in_rect.height() / dst_page_size.y) * src_pgw; + + // Round up to a whole row, it's better than the alternative. + // Busin 0 - Wizardry Alternative Neo moves with non even rows. + const int horizontal_offset = (page_count % dst_pgw); + if (horizontal_offset) + page_count += dst_pgw - horizontal_offset; + + int new_height = (page_count / dst_pgw) * dst_page_size.y; + new_rect.x = 0; + new_rect.z = dst_pgw * dst_page_size.x; + new_rect.y = start_page.y * dst_page_size.y; + new_rect.w = new_rect.y + new_height; + } + else + { + //TODO: Maybe control dirty blocks directly and add them page at a time for better granularity. + const GSVector2i start_page = GSVector2i((page_offset + rect_offset) % dst_pgw, page_offset / dst_pgw); + DevCon.Warning("Fudging start position"); + // Not easily translatable full pages and make sure the height is rounded upto encompass the half row. + new_rect.x = start_page.x * dst_page_size.x; + new_rect.z = new_rect.x + in_rect.z; + new_rect.y = start_page.y * dst_page_size.y; + new_rect.w = new_rect.y + in_rect.w; + } + } + } + else // Widths match + { + const int horizontal_dst_page_offset = page_offset % clamped_tbw; + const int vertical_dst_page_offset = page_offset / clamped_tbw; + GSVector4i offset_rect(horizontal_dst_page_offset * t_psm.pgs.x, vertical_dst_page_offset * t_psm.pgs.y); + new_rect = in_rect + offset_rect.xyxy(); + } + + return new_rect; +} + +/* GSVector4i GSTextureCache::TranslateAlignedRectByPage(u32 tbp, u32 tebp, u32 tbw, u32 tpsm, u32 sbp, u32 spsm, u32 sbw, GSVector4i src_r, bool is_invalidation) { const GSVector2i src_page_size = GSLocalMemory::m_psm[spsm].pgs; @@ -410,7 +567,7 @@ GSVector4i GSTextureCache::TranslateAlignedRectByPage(u32 tbp, u32 tebp, u32 tbw } return new_rect; -} +}*/ GSVector4i GSTextureCache::TranslateAlignedRectByPage(Target* t, u32 sbp, u32 spsm, u32 sbw, GSVector4i src_r, bool is_invalidation) { @@ -860,6 +1017,13 @@ GSTextureCache::Source* GSTextureCache::LookupDepthSource(const bool is_depth, c bool inside_target = false; GSVector4i target_rc(r); + GSVector4i block_boundary_rect = target_rc; + block_boundary_rect.x = block_boundary_rect.x & ~(psm_s.bs.x - 1); + block_boundary_rect.y = block_boundary_rect.y & ~(psm_s.bs.y - 1); + // Round up to the nearst block boundary for lookup to avoid problems due to bilinear and inclusive rects. + block_boundary_rect.z = std::max(target_rc.x + 1, (block_boundary_rect.z + (psm_s.bs.x / 2)) & ~(psm_s.bs.x - 1)); + block_boundary_rect.w = std::max(target_rc.y + 1, (block_boundary_rect.w + (psm_s.bs.y / 2)) & ~(psm_s.bs.y - 1)); + for (auto t : m_dst[DepthStencil]) { if (!t->m_used || (!t->m_dirty.empty() && !is_depth)) @@ -887,53 +1051,30 @@ GSTextureCache::Source* GSTextureCache::LookupDepthSource(const bool is_depth, c const GSVector2i page_size = GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs; const bool can_translate = CanTranslate(bp, TEX0.TBW, psm, r, t->m_TEX0.TBP0, t->m_TEX0.PSM, t->m_TEX0.TBW); const bool swizzle_match = psm_s.depth == GSLocalMemory::m_psm[t->m_TEX0.PSM].depth; - GSVector4i new_rect = r; - - if (linear) - { - new_rect.z -= 1; - new_rect.w -= 1; - } + GSVector4i new_rect = block_boundary_rect; if (can_translate) { if (swizzle_match) { - target_rc = TranslateAlignedRectByPage(t, bp, psm, TEX0.TBW, new_rect); + block_boundary_rect = TranslateAlignedRectByPage(t, bp, psm, TEX0.TBW, new_rect); } else { - // If it's not page aligned, grab the whole pages it covers, to be safe. - if (psm_s.bpp != GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp) - { - const GSVector2i dst_page_size = psm_s.pgs; - target_rc = GSVector4i(target_rc.x / page_size.x, target_rc.y / page_size.y, - (target_rc.z + (page_size.x - 1)) / page_size.x, - (target_rc.w + (page_size.y - 1)) / page_size.y); - target_rc = GSVector4i(target_rc.x * dst_page_size.x, target_rc.y * dst_page_size.y, - target_rc.z * dst_page_size.x, target_rc.w * dst_page_size.y); - } - else - { - target_rc.x &= ~(page_size.x - 1); - target_rc.y &= ~(page_size.y - 1); - target_rc.z = (new_rect.z + (page_size.x - 1)) & ~(page_size.x - 1); - target_rc.w = (new_rect.w + (page_size.y - 1)) & ~(page_size.y - 1); - } - target_rc = TranslateAlignedRectByPage(t, bp & ~((1 << 5) - 1), psm, TEX0.TBW, target_rc); + const GSVector2i src_page_size = psm_s.pgs; + new_rect.x &= ~(src_page_size.x - 1); + new_rect.y &= ~(src_page_size.y - 1); + new_rect.z = (new_rect.z + (src_page_size.x - 1)) & ~(src_page_size.x - 1); + new_rect.w = (new_rect.w + (src_page_size.y - 1)) & ~(src_page_size.y - 1); + block_boundary_rect = TranslateAlignedRectByPage(t, bp & ~((1 << 5) - 1), psm, TEX0.TBW, new_rect); } - if (!target_rc.rempty()) + if (!block_boundary_rect.rempty()) { dst = t; inside_target = true; } } - if (linear) - { - new_rect.z += 1; - new_rect.w += 1; - } } } @@ -1007,8 +1148,8 @@ GSTextureCache::Source* GSTextureCache::LookupDepthSource(const bool is_depth, c if (inside_target) { // Need to set it up as a region target. - src->m_region.SetX(target_rc.x, target_rc.z); - src->m_region.SetY(target_rc.y, target_rc.w); + src->m_region.SetX(block_boundary_rect.x, block_boundary_rect.z); + src->m_region.SetY(block_boundary_rect.y, block_boundary_rect.w); } if (GSRendererHW::GetInstance()->IsTBPFrameOrZ(dst->m_TEX0.TBP0)) @@ -1434,7 +1575,7 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const } else { - if (!t->HasValidBitsForFormat(psm, req_color, req_alpha) && !(possible_shuffle && GSLocalMemory::m_psm[psm].bpp == 16 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == 32)) + if (!t->HasValidBitsForFormat(psm, req_color, req_alpha, t->m_TEX0.TBW == TEX0.TBW) && !(possible_shuffle && GSLocalMemory::m_psm[psm].bpp == 16 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == 32)) continue; dst = t; @@ -1459,7 +1600,7 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const // Detect half of the render target (fix snow engine game) // Target Page (8KB) have always a width of 64 pixels // Half of the Target is TBW/2 pages * 8KB / (1 block * 256B) = 0x10 - if (!t->HasValidBitsForFormat(psm, req_color, req_alpha) && !(possible_shuffle && GSLocalMemory::m_psm[psm].bpp == 16 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == 32)) + if (!t->HasValidBitsForFormat(psm, req_color, req_alpha, t->m_TEX0.TBW == TEX0.TBW) && !(possible_shuffle && GSLocalMemory::m_psm[psm].bpp == 16 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == 32)) continue; half_right = true; @@ -1476,10 +1617,6 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const (GSLocalMemory::m_psm[color_psm].bpp >= 16 || (/*possible_shuffle &&*/ GSLocalMemory::m_psm[color_psm].bpp == 8 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == 32)) && // Channel shuffles or non indexed lookups. t->m_age <= 1 && (!found_t || t->m_last_draw > dst->m_last_draw) /*&& CanTranslate(bp, bw, psm, block_boundary_rect, t->m_TEX0.TBP0, t->m_TEX0.PSM, t->m_TEX0.TBW)*/) { - - if (!t->HasValidBitsForFormat(psm, req_color, req_alpha) && !(possible_shuffle && GSLocalMemory::m_psm[psm].bpp == 16 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == 32)) - continue; - u32 horz_page_offset = ((bp - t->m_TEX0.TBP0) >> 5) % t->m_TEX0.TBW; if (GSLocalMemory::m_psm[color_psm].bpp == 16 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == 32 && bw != 1 && ((t->m_TEX0.TBW < (horz_page_offset + ((block_boundary_rect.z + GSLocalMemory::m_psm[psm].pgs.x - 1) / GSLocalMemory::m_psm[psm].pgs.x)) || @@ -1497,7 +1634,7 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const } // PSM equality needed because CreateSource does not handle PSM conversion. // Only inclusive hit to limit false hits. - GSVector4i rect = req_rect; + GSVector4i rect = block_boundary_rect; int src_bw = bw; int src_psm = psm; @@ -1521,12 +1658,8 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const } if (bp > t->m_TEX0.TBP0) { - GSVector4i new_rect = (GSLocalMemory::m_psm[color_psm].bpp != GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp) ? block_boundary_rect : rect; - if (linear) - { - new_rect.z -= 1; - new_rect.w -= 1; - } + GSVector4i new_rect = (GSLocalMemory::m_psm[color_psm].bpp != GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp && (psm & 0x7) != PSMCT16) ? block_boundary_rect : rect; + // Check if it is possible to hit with valid offset on the given Target. // Fixes Jak eyes rendering. // Fixes Xenosaga 3 last dungeon graphic bug. @@ -1578,6 +1711,9 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const continue; } + if (!t->HasValidBitsForFormat(psm, req_color, req_alpha, t->m_TEX0.TBW == TEX0.TBW) && !(possible_shuffle && GSLocalMemory::m_psm[psm].bpp == 16 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == 32)) + continue; + x_offset = rect.x; y_offset = rect.y; dst = t; @@ -1599,6 +1735,9 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const } if (so.is_valid) { + if (!t->HasValidBitsForFormat(psm, req_color, req_alpha, t->m_TEX0.TBW == TEX0.TBW) && !(possible_shuffle && GSLocalMemory::m_psm[psm].bpp == 16 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == 32)) + continue; + dst = t; // Offset from Target to Source in Target coords. x_offset = so.b2a_offset.x; @@ -1635,7 +1774,10 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const GL_CACHE("TC: Target 0x%x detected in front of TBP 0x%x with %d,%d offset (%d pages)", t->m_TEX0.TBP0, TEX0.TBP0, region.GetMinX(), region.GetMinY(), (region.GetMinY() / page_size.y) * TEX0.TBW + (region.GetMinX() / page_size.x)); - + + if (!t->HasValidBitsForFormat(psm, req_color, req_alpha, t->m_TEX0.TBW == TEX0.TBW) && !(possible_shuffle && GSLocalMemory::m_psm[psm].bpp == 16 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == 32)) + continue; + x_offset = ((((offset_bp - t->m_TEX0.TBP0) >> 5) % bw) * page_size.x) - region.GetMinX(); y_offset = ((((offset_bp - t->m_TEX0.TBP0) >> 5) / bw) * page_size.y) - region.GetMinY(); dst = t; @@ -3809,7 +3951,7 @@ void GSTextureCache::InvalidateLocalMem(const GSOffset& off, const GSVector4i& r // Check exact match first const bool bpp_match = GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == GSLocalMemory::m_psm[psm].bpp; const u32 page_mask = ((1 << 5) - 1); - const bool exact_mem_match = (read_start & ~page_mask) == (t->m_TEX0.TBP0 & ~page_mask) && ((read_end + page_mask) & ~page_mask) == ((t->m_end_block + page_mask) & ~page_mask); + const bool exact_mem_match = (read_start & ~page_mask) == (t->m_TEX0.TBP0 & ~page_mask) && ((read_end + (page_mask - 1)) & ~page_mask) <= t->m_end_block; const bool expecting_this_tex = exact_mem_match || (bpp_match && bw == t->m_TEX0.TBW && (((read_start & ~page_mask) == t->m_TEX0.TBP0) || (bp >= t->m_TEX0.TBP0 && ((read_end + page_mask) & ~page_mask) <= ((t->m_end_block + page_mask) & ~page_mask)))); if (!expecting_this_tex) @@ -6718,7 +6860,7 @@ void GSTextureCache::Target::UpdateValidChannels(u32 psm, u32 fbmsk) m_valid_rgb |= (psm_s.trbpp >= 24 && (fbmsk & 0x00FFFFFF) != 0x00FFFFFF) || (psm_s.trbpp == 16); } -bool GSTextureCache::Target::HasValidBitsForFormat(u32 psm, bool req_color, bool req_alpha) +bool GSTextureCache::Target::HasValidBitsForFormat(u32 psm, bool req_color, bool req_alpha, bool width_match) { // Grab validities.. bool alpha_valid = false; @@ -6741,7 +6883,6 @@ bool GSTextureCache::Target::HasValidBitsForFormat(u32 psm, bool req_color, bool if (req_alpha && !alpha_valid && color_valid && (m_TEX0.PSM & 0xF) <= PSMCT24 && (psm & 0xF) == PSMCT32) { - RGBAMask mask; mask._u32 = 0x8; m_TEX0.PSM &= ~PSMCT24; diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.h b/pcsx2/GS/Renderers/HW/GSTextureCache.h index 9de5db136a..a0a434f448 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.h +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.h @@ -238,7 +238,7 @@ public: static Target* Create(GIFRegTEX0 TEX0, int w, int h, float scale, int type, bool clear); __fi bool HasValidAlpha() const { return (m_valid_alpha_low | m_valid_alpha_high); } - bool HasValidBitsForFormat(u32 psm, bool req_color, bool req_alpha); + bool HasValidBitsForFormat(u32 psm, bool req_color, bool req_alpha, bool width_match); void ResizeDrawn(const GSVector4i& rect); void UpdateDrawn(const GSVector4i& rect, bool can_resize = true); From bdd9f3040436c1f0d2f18e0163edb447f01d76fe Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Fri, 24 Jan 2025 16:06:13 +0000 Subject: [PATCH 103/162] GS: Add CRC hack for Guitar Hero 3 to handle crowds --- bin/resources/GameIndex.yaml | 6 ++++++ pcsx2/GS/Renderers/HW/GSHwHack.cpp | 17 +++++++++++++++++ pcsx2/GS/Renderers/HW/GSHwHack.h | 1 + 3 files changed, 24 insertions(+) diff --git a/bin/resources/GameIndex.yaml b/bin/resources/GameIndex.yaml index 6d4d6e361c..9c5175a6b6 100644 --- a/bin/resources/GameIndex.yaml +++ b/bin/resources/GameIndex.yaml @@ -27286,6 +27286,7 @@ SLES-54962: autoFlush: 1 # Fixes bloom intensity. halfPixelOffset: 4 # Mostly aligns post processing. nativeScaling: 1 # Fixes post processing smoothness and position. + getSkipCount: "GSC_GuitarHero" SLES-54963: name: "Tony Hawk's Proving Ground" region: "PAL-E" @@ -27333,6 +27334,7 @@ SLES-54974: autoFlush: 1 # Fixes bloom intensity. halfPixelOffset: 4 # Mostly aligns post processing. nativeScaling: 1 # Fixes post processing smoothness and position. + getSkipCount: "GSC_GuitarHero" SLES-54975: name: "George Of The Jungle" region: "PAL-E" @@ -31633,6 +31635,7 @@ SLKA-25414: autoFlush: 1 # Fixes bloom intensity. halfPixelOffset: 4 # Mostly aligns post processing. nativeScaling: 1 # Fixes post processing smoothness and position. + getSkipCount: "GSC_GuitarHero" SLKA-25417: name: "Jin Samguk Mussang 4 - Empires" region: "NTSC-K" @@ -59870,6 +59873,7 @@ SLPS-25840: autoFlush: 1 # Fixes bloom intensity. halfPixelOffset: 4 # Mostly aligns post processing. nativeScaling: 1 # Fixes post processing smoothness and position. + getSkipCount: "GSC_GuitarHero" SLPS-25841: name: "テイルズ オブ デスティニー ディレクターズカット [プレミアムBOX]" name-sort: "ているず おぶ ですてぃにー でぃれくたーずかっと [ぷれみあむBOX]" @@ -60174,6 +60178,7 @@ SLPS-25890: autoFlush: 1 # Fixes bloom intensity. halfPixelOffset: 4 # Mostly aligns post processing. nativeScaling: 1 # Fixes post processing smoothness and position. + getSkipCount: "GSC_GuitarHero" SLPS-25891: name: "乃木坂春香の秘密 こすぷれ、はじめました♥ [限定版]" name-sort: "のぎざかはるかのひみつ こすぷれ はじめました [げんていばん]" @@ -70742,6 +70747,7 @@ SLUS-21672: autoFlush: 1 # Fixes bloom intensity. halfPixelOffset: 4 # Mostly aligns post processing. nativeScaling: 1 # Fixes post processing smoothness and position. + getSkipCount: "GSC_GuitarHero" SLUS-21673: name: "College Hoops 2K8" region: "NTSC-U" diff --git a/pcsx2/GS/Renderers/HW/GSHwHack.cpp b/pcsx2/GS/Renderers/HW/GSHwHack.cpp index b5036a28f1..6b84f1377d 100644 --- a/pcsx2/GS/Renderers/HW/GSHwHack.cpp +++ b/pcsx2/GS/Renderers/HW/GSHwHack.cpp @@ -131,6 +131,22 @@ bool GSHwHack::GSC_SacredBlaze(GSRendererHW& r, int& skip) return true; } +bool GSHwHack::GSC_GuitarHero(GSRendererHW& r, int& skip) +{ + // Crowd sprite generation is a mess, better done in software. + if (skip == 0) + { + if (RTBW <= 4 && RTME && RFBW <= 4 && (r.m_context->TEX1.MMIN & 1) == 0) + { + r.ClearGSLocalMemory(r.m_context->offset.zb, r.m_r, 0); + r.SwPrimRender(r, RFBP != 0x2DC0, false); + skip = 1; + } + } + + return true; +} + bool GSHwHack::GSC_SFEX3(GSRendererHW& r, int& skip) { if (skip == 0) @@ -1531,6 +1547,7 @@ const GSHwHack::Entry GSHwHack::s_get_skip_count_function CRC_F(GSC_Manhunt2), CRC_F(GSC_MidnightClub3), CRC_F(GSC_SacredBlaze), + CRC_F(GSC_GuitarHero), CRC_F(GSC_SakuraWarsSoLongMyLove), CRC_F(GSC_Simple2000Vol114), CRC_F(GSC_SFEX3), diff --git a/pcsx2/GS/Renderers/HW/GSHwHack.h b/pcsx2/GS/Renderers/HW/GSHwHack.h index 11d36c17da..856cd46349 100644 --- a/pcsx2/GS/Renderers/HW/GSHwHack.h +++ b/pcsx2/GS/Renderers/HW/GSHwHack.h @@ -10,6 +10,7 @@ public: static bool GSC_GiTS(GSRendererHW& r, int& skip); static bool GSC_Manhunt2(GSRendererHW& r, int& skip); static bool GSC_SacredBlaze(GSRendererHW& r, int& skip); + static bool GSC_GuitarHero(GSRendererHW& r, int& skip); static bool GSC_SFEX3(GSRendererHW& r, int& skip); static bool GSC_DTGames(GSRendererHW& r, int& skip); static bool GSC_Tekken5(GSRendererHW& r, int& skip); From 6f961edcb1fc339f824f4126df62aa071ea847db Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Fri, 24 Jan 2025 17:44:47 +0000 Subject: [PATCH 104/162] GS/HW: Remove no longer required CRCs --- pcsx2/GS/Renderers/HW/GSHwHack.cpp | 89 ------------------------------ pcsx2/GS/Renderers/HW/GSHwHack.h | 3 - 2 files changed, 92 deletions(-) diff --git a/pcsx2/GS/Renderers/HW/GSHwHack.cpp b/pcsx2/GS/Renderers/HW/GSHwHack.cpp index 6b84f1377d..bb0b96ea11 100644 --- a/pcsx2/GS/Renderers/HW/GSHwHack.cpp +++ b/pcsx2/GS/Renderers/HW/GSHwHack.cpp @@ -35,52 +35,6 @@ static bool s_nativeres; // Partial level, broken on all renderers. //////////////////////////////////////////////////////////////////////////////// -bool GSHwHack::GSC_DeathByDegreesTekkenNinaWilliams(GSRendererHW& r, int& skip) -{ - // Note: Game also has issues with texture shuffle not supported on strange clamp mode. - // See https://forums.pcsx2.net/Thread-GSDX-Texture-Cache-Bug-Report-Death-By-Degrees-SLUS-20934-NTSC - if (skip == 0) - { - if (!s_nativeres && RTME && RFBP == 0 && RTBP0 == 0x34a0 && RTPSM == PSMCT32) - { - // Don't enable hack on native res if crc is below aggressive. - // Upscaling issue similar to Tekken 5. - skip = 1; // Animation pane - } -#if 0 - else if (RFBP == 0x3500 && RTPSM == PSMT8 && RFBMSK == 0xFFFF00FF) - { - // Needs to be further tested so put it on Aggressive for now, likely channel shuffle. - skip = 4; // Underwater white fog - } -#endif - } - else - { - if (!s_nativeres && RTME && (RFBP | RTBP0 | RFPSM | RTPSM) && RFBMSK == 0x00FFFFFF) - { - // Needs to be further tested so assume it's related with the upscaling hack. - skip = 1; // Animation speed - } - } - - return true; -} - -bool GSHwHack::GSC_GiTS(GSRendererHW& r, int& skip) -{ - if (skip == 0) - { - if (RTME && RFBP == 0x03000 && RFPSM == PSMCT32 && RTPSM == PSMT8) - { - // Channel effect not properly supported yet - skip = 9; - } - } - - return true; -} - // Channel effect not properly supported yet bool GSHwHack::GSC_Manhunt2(GSRendererHW& r, int& skip) { @@ -937,44 +891,6 @@ bool GSHwHack::GSC_MetalGearSolid3(GSRendererHW& r, int& skip) return true; } -bool GSHwHack::GSC_BigMuthaTruckers(GSRendererHW& r, int& skip) -{ - // Rendering pattern: - // CRTC frontbuffer at 0x0 is interlaced (half vertical resolution), - // game needs to do a depth effect (so green channel to alpha), - // but there is a vram limitation so green is pushed into the alpha channel of the CRCT buffer, - // vertical resolution is half so only half is processed at once - // We, however, don't have this limitation so we'll replace the draw with a full-screen TS. - - const GIFRegTEX0& Texture = RTEX0; - - GIFRegTEX0 Frame = {}; - Frame.TBW = RFRAME.FBW; - Frame.TBP0 = RFRAME.Block(); - const int frame_offset_pal = GSLocalMemory::GetEndBlockAddress(0xa00, 10, PSMCT32, GSVector4i(0, 0, 640, 256)) + 1; - const int frame_offset_ntsc = GSLocalMemory::GetEndBlockAddress(0xa00, 10, PSMCT32, GSVector4i(0, 0, 640, 224)) + 1; - const GSVector4i rect = GSVector4i(r.m_vt.m_min.p.x, r.m_vt.m_min.p.y, r.m_vt.m_max.p.x, r.m_vt.m_max.p.y); - - if (RPRIM->TME && Frame.TBW == 10 && Texture.TBW == 10 && Texture.PSM == PSMCT16 && ((rect.w == 512 && Frame.TBP0 == frame_offset_pal) || (Frame.TBP0 == frame_offset_ntsc && rect.w == 448))) - { - // 224 ntsc, 256 pal. - GL_INS("GSC_BigMuthaTruckers half bottom offset %d", r.m_context->XYOFFSET.OFX >> 4); - - const size_t count = r.m_vertex.next; - GSVertex* v = &r.m_vertex.buff[0]; - const u16 offset = (u16)rect.w * 16; - - for (size_t i = 0; i < count; i++) - v[i].XYZ.Y += offset; - - r.m_vt.m_min.p.y += rect.w; - r.m_vt.m_max.p.y += rect.w; - r.m_cached_ctx.FRAME.FBP = 0x50; // 0xA00 >> 5 - } - - return true; -} - bool GSHwHack::GSC_HitmanBloodMoney(GSRendererHW& r, int& skip) { // The game does a stupid thing where it backs up the last 2 pages of the framebuffer with shuffles, uploads a CT32 texture to it @@ -1565,7 +1481,6 @@ const GSHwHack::Entry GSHwHack::s_get_skip_count_function CRC_F(GSC_HitmanBloodMoney), // Channel Effect - CRC_F(GSC_GiTS), CRC_F(GSC_SteambotChronicles), // Depth Issue @@ -1574,10 +1489,6 @@ const GSHwHack::Entry GSHwHack::s_get_skip_count_function // Half Screen bottom issue CRC_F(GSC_Tekken5), - // Texture shuffle - CRC_F(GSC_DeathByDegreesTekkenNinaWilliams), // + Upscaling issues - CRC_F(GSC_BigMuthaTruckers), - // Upscaling hacks CRC_F(GSC_UltramanFightingEvolution), }; diff --git a/pcsx2/GS/Renderers/HW/GSHwHack.h b/pcsx2/GS/Renderers/HW/GSHwHack.h index 856cd46349..d30db57d8b 100644 --- a/pcsx2/GS/Renderers/HW/GSHwHack.h +++ b/pcsx2/GS/Renderers/HW/GSHwHack.h @@ -6,8 +6,6 @@ class GSHwHack { public: - static bool GSC_DeathByDegreesTekkenNinaWilliams(GSRendererHW& r, int& skip); - static bool GSC_GiTS(GSRendererHW& r, int& skip); static bool GSC_Manhunt2(GSRendererHW& r, int& skip); static bool GSC_SacredBlaze(GSRendererHW& r, int& skip); static bool GSC_GuitarHero(GSRendererHW& r, int& skip); @@ -31,7 +29,6 @@ public: static bool GSC_NFSUndercover(GSRendererHW& r, int& skip); static bool GSC_PolyphonyDigitalGames(GSRendererHW& r, int& skip); static bool GSC_MetalGearSolid3(GSRendererHW& r, int& skip); - static bool GSC_BigMuthaTruckers(GSRendererHW& r, int& skip); static bool GSC_HitmanBloodMoney(GSRendererHW& r, int& skip); static bool OI_PointListPalette(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t); From c38a0cdec9de32864ba0050b33bcf81306ccdab2 Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Fri, 24 Jan 2025 22:23:27 +0000 Subject: [PATCH 105/162] GS/HW: Don't update TBP on targets + make target src's temporary --- pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 4 ++++ pcsx2/GS/Renderers/HW/GSTextureCache.cpp | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index d43264eeea..5c5379572b 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -3614,6 +3614,7 @@ void GSRendererHW::Draw() } if (FRAME_TEX0.TBW != 1 || (m_r.width() > frame_psm.pgs.x || m_r.height() > frame_psm.pgs.y)) { + FRAME_TEX0.TBP0 = rt->m_TEX0.TBP0; rt->m_TEX0 = FRAME_TEX0; } @@ -3622,7 +3623,10 @@ void GSRendererHW::Draw() if (ds && (!is_possible_mem_clear || ds->m_TEX0.PSM != ZBUF_TEX0.PSM || (rt && ds->m_TEX0.TBW != rt->m_TEX0.TBW)) && !m_in_target_draw) { if (ZBUF_TEX0.TBW != 1 || (m_r.width() > frame_psm.pgs.x || m_r.height() > frame_psm.pgs.y)) + { + ZBUF_TEX0.TBP0 = ds->m_TEX0.TBP0; ds->m_TEX0 = ZBUF_TEX0; + } } } else if (!m_texture_shuffle) diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index 85a64fb9ad..86c50d8747 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -2073,7 +2073,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe // 2. Preserved data will be in the correct place (in most cases) // 3. Less deleting sources/targets // 4. We can basically do clears in hardware, if they aren't insane ones - if (can_use && !is_shuffle && ((preserve_alpha && preserve_rgb) || (draw_rect.w > GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y && !possible_clear)) && TEX0.TBW != t->m_TEX0.TBW && t->m_dirty.size() >= 1) + if (can_use && ((!is_shuffle && t->m_dirty.size() >= 1) || (is_shuffle && src && GSLocalMemory::m_psm[src->m_TEX0.PSM].bpp == 8 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == 16)) && ((preserve_alpha && preserve_rgb) || (draw_rect.w > GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y && !possible_clear)) && TEX0.TBW != t->m_TEX0.TBW) { can_use = false; } @@ -5321,6 +5321,8 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con sTex, sRectF, dTex, GSVector4(destX, destY, new_size.x, new_size.y), shader, false); } + m_temporary_source = src; + g_perfmon.Put(GSPerfMon::TextureCopies, 1); #ifdef PCSX2_DEVBUILD From 4e8887c80b435e24587fc00bcb236837a6c15a12 Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Sat, 25 Jan 2025 13:21:35 +0000 Subject: [PATCH 106/162] GameDB: Adjust fixes for games affected by RT in RT --- bin/resources/GameIndex.yaml | 369 ++++++++++++++++++++++++----------- 1 file changed, 256 insertions(+), 113 deletions(-) diff --git a/bin/resources/GameIndex.yaml b/bin/resources/GameIndex.yaml index 9c5175a6b6..1eaebf4503 100644 --- a/bin/resources/GameIndex.yaml +++ b/bin/resources/GameIndex.yaml @@ -174,9 +174,8 @@ PAPX-90020: recommendedBlendingLevel: 4 # Improves banding and effect emulation. autoFlush: 1 # Fixes light bloom intensity. estimateTextureRegion: 1 # Improves performance and reduces hash cache size. - halfPixelOffset: 4 # Aligns post effects. + halfPixelOffset: 2 # Aligns post effects. nativeScaling: 1 # Fixes post effects. - getSkipCount: "GSC_GiTS" PAPX-90201: name: "ファンタビジョン [体験版]" name-sort: "ふぁんたびじょん [たいけんばん]" @@ -524,7 +523,7 @@ PAPX-90524: region: "NTSC-J" gsHWFixes: roundSprite: 1 # Fixes font sizes and lines in UI. - cpuFramebufferConversion: 1 # Fixes sepia-tone flashback sequences. + textureInsideRT: 1 # Fixes sepia-tone flashback sequences. forceEvenSpritePosition: 1 # Fixes font artifacts and out-of-bound 2D textures. gpuPaletteConversion: 2 # Fixes micro-stuttering and reduces HC size. PBGP-0061: @@ -1250,26 +1249,31 @@ SCAJ-10011: region: "NTSC-Unk" gsHWFixes: alignSprite: 1 # Fixes vertical lines. + textureInsideRT: 1 # Fixes post effects. SCAJ-10012: name: "Taiko Drum Master" region: "NTSC-Unk" gsHWFixes: alignSprite: 1 # Fixes vertical lines. + textureInsideRT: 1 # Fixes post effects. SCAJ-10013: name: "Taiko no Tatsujin - Tobikkiri! Anime Special" region: "NTSC-Unk" gsHWFixes: alignSprite: 1 # Fixes vertical lines. + textureInsideRT: 1 # Fixes post effects. SCAJ-10014: name: "Taiko no Tatsujin - Wai Wai Happy! Rokudaime" region: "NTSC-Unk" gsHWFixes: alignSprite: 1 # Fixes vertical lines. + textureInsideRT: 1 # Fixes post effects. SCAJ-10015: name: "Taiko no Tatsujin - Doka! to Oomori Nanadaime" region: "NTSC-Unk" gsHWFixes: alignSprite: 1 # Fixes vertical lines. + textureInsideRT: 1 # Fixes post effects. getSkipCount: "GSC_Tekken5" # Fixes upscaling grid, same engine. SCAJ-20001: name: "Ratchet & Clank" @@ -1330,7 +1334,6 @@ SCAJ-20010: region: "NTSC-Unk" gsHWFixes: textureInsideRT: 1 # Fixes inside RT shuffling. - getSkipCount: "GSC_BigMuthaTruckers" SCAJ-20011: name: "Armored Core 3 - Silent Line" region: "NTSC-HK" @@ -1395,6 +1398,8 @@ SCAJ-20020: texturePreloading: 1 # Performs better with partial preload because it is slow on locations outside gameplay foremost. mergeSprite: 1 # Fixes misaligned white lines. PCRTCOverscan: 1 # Fixes missing HUD. + textureInsideRT: 1 # Fixes post shuffle effect. + halfPixelOffset: 2 # Fixes boxes around shuffle effect pages. SCAJ-20021: name: "Metal Slug 3" region: "NTSC-Unk" @@ -1511,6 +1516,8 @@ SCAJ-20047: region: "NTSC-Unk" gsHWFixes: texturePreloading: 0 # Performs much better with no preload. + textureInsideRT: 1 # Fixes post processing. + halfPixelOffset: 5 # Fixes alignment of shuffles and post processing. SCAJ-20048: name: "R:RACING EVOLUTION" name-sort: "Rれーしんぐえぼりゅーしょん" @@ -1560,6 +1567,8 @@ SCAJ-20060: region: "NTSC-Unk" gsHWFixes: texturePreloading: 0 # Performs much better with no preload. + textureInsideRT: 1 # Fixes post processing. + halfPixelOffset: 5 # Fixes alignment of shuffles and post processing. SCAJ-20061: name: "Seven Samurai 20XX" region: "NTSC-Unk" @@ -1631,9 +1640,9 @@ SCAJ-20072: recommendedBlendingLevel: 4 # Improves banding and effect emulation. autoFlush: 1 # Fixes light bloom intensity. estimateTextureRegion: 1 # Improves performance and reduces hash cache size. - halfPixelOffset: 4 # Aligns post effects. + halfPixelOffset: 2 # Aligns post effects. nativeScaling: 1 # Fixes post effects. - getSkipCount: "GSC_GiTS" + textureInsideRT: 1 # Fixes post shuffles. SCAJ-20073: name: "Jak and Daxter II" region: "NTSC-Unk" @@ -1899,9 +1908,8 @@ SCAJ-20116: region: "NTSC-C-J" gsHWFixes: alignSprite: 1 # Fixes FMV lines. - cpuSpriteRenderBW: 2 # Prevents Death By VRAM explosion. - cpuSpriteRenderLevel: 2 # Needed for above. - getSkipCount: "GSC_DeathByDegreesTekkenNinaWilliams" + halfPixelOffset: 5 # Fixes alignment of shuffles and post processing. + textureInsideRT: 1 # Fixes post shuffles. SCAJ-20117: name: "Fu-un Bakumatsu-den" region: "NTSC-Unk" @@ -2274,7 +2282,7 @@ SCAJ-20169: region: "NTSC-Unk" gsHWFixes: autoFlush: 1 # Fixes lighting. - halfPixelOffset: 4 # Fixes ghosting. + halfPixelOffset: 5 # Fixes ghosting. nativeScaling: 2 # Fixes post processing. recommendedBlendingLevel: 4 # Fixes missing light brightness. SCAJ-20170: @@ -2373,7 +2381,7 @@ SCAJ-20183: region: "NTSC-J" gsHWFixes: roundSprite: 1 # Fixes font sizes and lines in UI. - cpuFramebufferConversion: 1 # Fixes sepia-tone flashback sequences. + textureInsideRT: 1 # Fixes sepia-tone flashback sequences. forceEvenSpritePosition: 1 # Fixes font artifacts and out-of-bound 2D textures. gpuPaletteConversion: 2 # Fixes micro-stuttering and reduces HC size. SCAJ-20184: @@ -3571,6 +3579,7 @@ SCED-52137: autoFlush: 2 # Fixes sun luminosity. halfPixelOffset: 2 # Aligns Depth of Field. nativeScaling: 2 # Fixes Depth of Field effect. + textureInsideRT: 1 # Fixes half screen fog effect. SCED-52141: name: "WRC 3 [Demo]" region: "PAL-E" @@ -3582,6 +3591,7 @@ SCED-52141: autoFlush: 2 # Fixes sun luminosity. halfPixelOffset: 2 # Aligns Depth of Field. nativeScaling: 2 # Fixes Depth of Field effect. + textureInsideRT: 1 # Fixes half screen fog effect. SCED-52147: name: "EyeToy - Christmas Wishi Washi" region: "PAL-E" @@ -4565,8 +4575,7 @@ SCES-50000: clampModes: vuClampMode: 2 # Fixes texture rendering in the intro. gsHWFixes: - cpuFramebufferConversion: 1 - textureInsideRT: 1 + textureInsideRT: 1 # Fixes post effects halfPixelOffset: 2 # Fixes title screen and some intro post processing alignment. roundSprite: 1 # Fixes ui and hud alignment. gpuPaletteConversion: 2 # Lots of CLUTs in large textures. @@ -5289,6 +5298,7 @@ SCES-51684: autoFlush: 2 # Fixes sun luminosity. halfPixelOffset: 2 # Aligns Depth of Field. nativeScaling: 2 # Fixes Depth of Field effect. + textureInsideRT: 1 # Fixes half screen fog effect. patches: 80802EA9: content: |- @@ -5331,6 +5341,8 @@ SCES-51844: region: "PAL-M5" gsHWFixes: texturePreloading: 0 # Performs much better with no preload. + textureInsideRT: 1 # Fixes post processing. + halfPixelOffset: 5 # Fixes alignment of shuffles and post processing. SCES-51895: name: "EyeToy - Groove" region: "PAL-M11" @@ -5428,6 +5440,7 @@ SCES-52137: autoFlush: 2 # Fixes sun luminosity. halfPixelOffset: 2 # Aligns Depth of Field. nativeScaling: 2 # Fixes Depth of Field effect. + textureInsideRT: 1 # Fixes half screen fog effect. SCES-52154: name: "EyeToy - Chat" region: "PAL-M11" @@ -5635,9 +5648,8 @@ SCES-52586: compat: 5 gsHWFixes: alignSprite: 1 # Fixes FMV lines. - cpuSpriteRenderBW: 2 # Prevents Death By VRAM explosion. - cpuSpriteRenderLevel: 2 # Needed for above. - getSkipCount: "GSC_DeathByDegreesTekkenNinaWilliams" + halfPixelOffset: 5 # Fixes alignment of shuffles and post processing. + textureInsideRT: 1 # Fixes post shuffles. SCES-52596: name: "This is Football 2005" region: "PAL-Unk" @@ -5657,6 +5669,7 @@ SCES-52684: autoFlush: 2 # Fixes sun luminosity. halfPixelOffset: 2 # Aligns Depth of Field. nativeScaling: 2 # Fixes Depth of Field effect. + textureInsideRT: 1 # Fixes half screen fog effect. SCES-52748: name: "EyeToy - Play 2" region: "PAL-M12" @@ -5737,18 +5750,16 @@ SCES-53053: region: "PAL-F-I" gsHWFixes: alignSprite: 1 # Fixes FMV lines. - cpuSpriteRenderBW: 2 # Prevents Death By VRAM explosion. - cpuSpriteRenderLevel: 2 # Needed for above. - getSkipCount: "GSC_DeathByDegreesTekkenNinaWilliams" + halfPixelOffset: 5 # Fixes alignment of shuffles and post processing. + textureInsideRT: 1 # Fixes post shuffles. SCES-53054: name: "Death by Degrees" region: "PAL-E-G" compat: 5 gsHWFixes: alignSprite: 1 # Fixes FMV lines. - cpuSpriteRenderBW: 2 # Prevents Death By VRAM explosion. - cpuSpriteRenderLevel: 2 # Needed for above. - getSkipCount: "GSC_DeathByDegreesTekkenNinaWilliams" + halfPixelOffset: 5 # Fixes alignment of shuffles and post processing. + textureInsideRT: 1 # Fixes post shuffles. SCES-53055: name: "Eyetoy - Antigrav" region: "PAL-M5" @@ -6998,6 +7009,8 @@ SCKA-20015: compat: 5 gsHWFixes: texturePreloading: 0 # Performs much better with no preload. + textureInsideRT: 1 # Fixes post processing. + halfPixelOffset: 5 # Fixes alignment of shuffles and post processing. SCKA-20016: name: "SoulCalibur II" region: "NTSC-K" @@ -7071,9 +7084,9 @@ SCKA-20027: recommendedBlendingLevel: 4 # Improves banding and effect emulation. autoFlush: 1 # Fixes light bloom intensity. estimateTextureRegion: 1 # Improves performance and reduces hash cache size. - halfPixelOffset: 4 # Aligns post effects. + halfPixelOffset: 2 # Aligns post effects. nativeScaling: 1 # Fixes post effects. - getSkipCount: "GSC_GiTS" + textureInsideRT: 1 # Fixes post shuffles. SCKA-20028: name: "Ico [PlayStation2 Big Hit Series]" region: "NTSC-K" @@ -7125,6 +7138,8 @@ SCKA-20034: region: "NTSC-K" gsHWFixes: texturePreloading: 0 # Performs much better with no preload. + textureInsideRT: 1 # Fixes post processing. + halfPixelOffset: 5 # Fixes alignment of shuffles and post processing. SCKA-20035: name: "Hot Shots Golf 3 [PlayStation 2 Big Hit Series]" region: "NTSC-K" @@ -7155,9 +7170,8 @@ SCKA-20039: region: "NTSC-K" gsHWFixes: alignSprite: 1 # Fixes FMV lines. - cpuSpriteRenderBW: 2 # Prevents Death By VRAM explosion. - cpuSpriteRenderLevel: 2 # Needed for above. - getSkipCount: "GSC_DeathByDegreesTekkenNinaWilliams" + halfPixelOffset: 2 # Aligns post effects. + textureInsideRT: 1 # Fixes post shuffles. SCKA-20040: name: "Jak 3" region: "NTSC-K" @@ -8651,9 +8665,8 @@ SCPS-15064: recommendedBlendingLevel: 4 # Improves banding and effect emulation. autoFlush: 1 # Fixes light bloom intensity. estimateTextureRegion: 1 # Improves performance and reduces hash cache size. - halfPixelOffset: 4 # Aligns post effects. + halfPixelOffset: 2 # Aligns post effects. nativeScaling: 1 # Fixes post effects. - getSkipCount: "GSC_GiTS" patches: A5768F53: content: |- @@ -9145,7 +9158,7 @@ SCPS-15118: region: "NTSC-J" gsHWFixes: roundSprite: 1 # Fixes font sizes and lines in UI. - cpuFramebufferConversion: 1 # Fixes sepia-tone flashback sequences. + textureInsideRT: 1 # Fixes sepia-tone flashback sequences. forceEvenSpritePosition: 1 # Fixes font artifacts and out-of-bound 2D textures. gpuPaletteConversion: 2 # Fixes micro-stuttering and reduces HC size. SCPS-15119: @@ -9846,7 +9859,7 @@ SCPS-19333: region: "NTSC-J" gsHWFixes: roundSprite: 1 # Fixes font sizes and lines in UI. - cpuFramebufferConversion: 1 # Fixes sepia-tone flashback sequences. + textureInsideRT: 1 # Fixes sepia-tone flashback sequences. forceEvenSpritePosition: 1 # Fixes font artifacts and out-of-bound 2D textures. gpuPaletteConversion: 2 # Fixes micro-stuttering and reduces HC size. SCPS-19335: @@ -13336,6 +13349,12 @@ SLED-52736: SLED-52851: name: "TOCA Race Driver 2" region: "PAL-Unk" + gsHWFixes: + alignSprite: 1 # Fixes lighting and vertical lines, also works with normal vertex. + roundSprite: 1 # Fixes misaligned map. + halfPixelOffset: 2 # Fixes depth of field alignment. + nativeScaling: 1 # Fixes depth of field. + textureInsideRT: 1 # Fixes shadows. SLED-52852: name: "Forgotten Realms - Demon Stone [Demo]" region: "PAL-E" @@ -13422,8 +13441,13 @@ SLED-53109: cpuCLUTRender: 1 # Fixes broken headlights. minimumBlendingLevel: 3 SLED-53137: - name: "Stolen" - region: "PAL-Unk" + name: "Stolen [Demo]" + region: "PAL-M5" + gsHWFixes: + halfPixelOffset: 2 # Fixes misaligned bloom effects. + texturePreloading: 1 # Performs much better with partial preload. + textureInsideRT: 1 # Fixes post effects. + nativeScaling: 1 # Fixes lighting quality and positioning. SLED-53198: name: "Rugby 2005" region: "PAL-Unk" @@ -13581,6 +13605,7 @@ SLED-53723: autoFlush: 1 # Corrects vignette to match software. estimateTextureRegion: 1 # Improves performance and reduces hash cache size. halfPixelOffset: 4 # Aligns blur more correctly to match software. + textureInsideRT: 1 # Fixes post processing effects. SLED-53731: name: "Battlefield 2 - Modern Combat [Demo]" region: "PAL-E" @@ -13627,10 +13652,9 @@ SLED-53888: gsHWFixes: alignSprite: 1 # Fixes lighting and vertical lines, also works with normal vertex. roundSprite: 1 # Fixes misaligned map. - skipDrawStart: 1 # Removes large black box around player car till we properly emulate it. - skipDrawEnd: 1 # Removes large black box around player car till we properly emulate it. halfPixelOffset: 2 # Fixes depth of field alignment. nativeScaling: 1 # Fixes depth of field. + textureInsideRT: 1 # Fixes shadows. SLED-53937: name: "Black [Demo]" region: "PAL-M5" @@ -15882,8 +15906,6 @@ SLES-50876: gsHWFixes: autoFlush: 1 # Aligns and corrects shadows. halfPixelOffset: 2 # Fixes sun and depth line. - cpuCLUTRender: 1 # Fixes janky coloured cars. - gpuTargetCLUT: 1 # Fixes janky coloured cars. textureInsideRT: 1 # Fixes car textures. cpuSpriteRenderBW: 4 # Alleviates text and sky rendering issues. cpuSpriteRenderLevel: 2 # Needed for above. @@ -17062,7 +17084,6 @@ SLES-51355: compat: 5 gsHWFixes: textureInsideRT: 1 # Fixes inside RT shuffling. - getSkipCount: "GSC_BigMuthaTruckers" SLES-51356: name: "Road Trip Adventure" region: "PAL-M3" @@ -18547,6 +18568,9 @@ SLES-51997: compat: 5 clampModes: eeClampMode: 3 # For grey screen ingame. + gsHWFixes: + textureInsideRT: 1 # Fixes post effects on player 2. + halfPixelOffset: 4 # Fixes chromatic effect. SLES-51998: name: "Kao the Kangaroo - Round 2" region: "PAL-M5" @@ -18746,11 +18770,14 @@ SLES-52096: halfPixelOffset: 4 # Fixes post processing alignment. roundSprite: 1 # Fixes sprite misaligment. SLES-52097: - name: "SWAT - Global Strike Force" + name: "SWAT - Global Strike Team" region: "PAL-E-G" compat: 5 clampModes: eeClampMode: 3 # For grey screen ingame. + gsHWFixes: + textureInsideRT: 1 # Fixes post effects on player 2. + halfPixelOffset: 4 # Fixes chromatic effect. SLES-52100: name: "NRL Rugby League" region: "PAL-E" @@ -18758,7 +18785,7 @@ SLES-52101: name: "Wrath Unleashed" region: "PAL-M5" gsHWFixes: - textureInsideRT: 1 # Fixes flashing and some models still very broken in hardware mode. + textureInsideRT: 1 # Fixes colors. SLES-52102: name: "Hugo Bukkazoom!" region: "PAL-M12" @@ -18821,6 +18848,8 @@ SLES-52132: gsHWFixes: texturePreloading: 1 # Performs much better with partial preload. autoFlush: 1 # Fixes bloom rendering. + textureInsideRT: 1 # Fixes post processing. + halfPixelOffset: 5 # Fixes alignment of shuffles and post processing. roundModes: vu1RoundMode: 0 # Fixes missing light cones curtains and certain effects. speedHacks: @@ -18831,6 +18860,8 @@ SLES-52133: gsHWFixes: texturePreloading: 1 # Performs much better with partial preload. autoFlush: 1 # Fixes bloom rendering. + textureInsideRT: 1 # Fixes post processing. + halfPixelOffset: 5 # Fixes alignment of shuffles and post processing. roundModes: vu1RoundMode: 0 # Fixes missing light cones curtains and certain effects. speedHacks: @@ -18842,6 +18873,8 @@ SLES-52134: gsHWFixes: texturePreloading: 1 # Performs much better with partial preload. autoFlush: 1 # Fixes bloom rendering. + textureInsideRT: 1 # Fixes post processing. + halfPixelOffset: 5 # Fixes alignment of shuffles and post processing. roundModes: vu1RoundMode: 0 # Fixes missing light cones curtains and certain effects. speedHacks: @@ -18852,6 +18885,8 @@ SLES-52135: gsHWFixes: texturePreloading: 1 # Performs much better with partial preload. autoFlush: 1 # Fixes bloom rendering. + textureInsideRT: 1 # Fixes post processing. + halfPixelOffset: 5 # Fixes alignment of shuffles and post processing. roundModes: vu1RoundMode: 0 # Fixes missing light cones curtains and certain effects. speedHacks: @@ -18862,6 +18897,8 @@ SLES-52136: gsHWFixes: texturePreloading: 1 # Performs much better with partial preload. autoFlush: 1 # Fixes bloom rendering. + textureInsideRT: 1 # Fixes post processing. + halfPixelOffset: 5 # Fixes alignment of shuffles and post processing. roundModes: vu1RoundMode: 0 # Fixes missing light cones curtains and certain effects. speedHacks: @@ -18900,8 +18937,6 @@ SLES-52153: gsHWFixes: autoFlush: 1 # Aligns and corrects shadows. halfPixelOffset: 2 # Fixes sun and depth line. - cpuCLUTRender: 1 # Fixes janky coloured cars. - gpuTargetCLUT: 1 # Fixes janky coloured cars. textureInsideRT: 1 # Fixes car textures. cpuSpriteRenderBW: 4 # Alleviates text and sky rendering issues. cpuSpriteRenderLevel: 2 # Needed for above. @@ -19244,6 +19279,8 @@ SLES-52322: texturePreloading: 1 # Performs better with partial preload because it is slow on locations outside gameplay foremost. mergeSprite: 1 # Fixes misaligned white lines. PCRTCOverscan: 1 # Fixes missing HUD. + textureInsideRT: 1 # Fixes post shuffle effect. + halfPixelOffset: 2 # Fixes boxes around shuffle effect pages. SLES-52323: name: "Richard Burns Rally" region: "PAL-M5" @@ -20122,12 +20159,17 @@ SLES-52637: region: "PAL-M5" gsHWFixes: alignSprite: 1 # Fixes lighting and vertical lines, also works with normal vertex. + textureInsideRT: 1 # Fixes shadows. SLES-52638: name: "DTM Race Driver 2" region: "PAL-M5" compat: 5 gsHWFixes: alignSprite: 1 # Fixes lighting and vertical lines, also works with normal vertex. + roundSprite: 1 # Fixes misaligned map. + halfPixelOffset: 2 # Fixes depth of field alignment. + nativeScaling: 1 # Fixes depth of field. + textureInsideRT: 1 # Fixes shadows. SLES-52639: name: "V8 Supercars 2" region: "PAL-M5" @@ -20790,7 +20832,7 @@ SLES-52859: gsHWFixes: autoFlush: 1 # Fixes the post processing positioning. textureInsideRT: 1 # Fixes post processing. - halfPixelOffset: 4 # Aligns bloom effect. + halfPixelOffset: 5 # Aligns bloom effect. nativeScaling: 2 # Fixes post processing smoothness and position. SLES-52861: name: "King Arthur" @@ -20838,9 +20880,10 @@ SLES-52882: region: "PAL-M5" compat: 4 gsHWFixes: - disablePartialInvalidation: 1 # Improves performance. halfPixelOffset: 2 # Fixes misaligned bloom effects. texturePreloading: 1 # Performs much better with partial preload. + textureInsideRT: 1 # Fixes post effects. + nativeScaling: 1 # Fixes lighting quality and positioning. SLES-52884: name: "Duel Masters" region: "PAL-M5" @@ -21018,6 +21061,8 @@ SLES-52942: gsHWFixes: halfPixelOffset: 2 # Aligns post bloom. nativeScaling: 2 # Fixes light blooms. + textureInsideRT: 1 # Improves performance. + preloadFrameData: 1 # Fixes light trails. getSkipCount: "GSC_MidnightClub3" patches: EBE1972D: @@ -21282,9 +21327,9 @@ SLES-53020: recommendedBlendingLevel: 4 # Improves banding and effect emulation. autoFlush: 1 # Fixes light bloom intensity. estimateTextureRegion: 1 # Improves performance and reduces hash cache size. - halfPixelOffset: 4 # Aligns post effects. + halfPixelOffset: 2 # Aligns post effects. nativeScaling: 1 # Fixes post effects. - getSkipCount: "GSC_GiTS" + textureInsideRT: 1 # Fixes post shuffles. patches: BF6F101F: content: |- @@ -21351,7 +21396,7 @@ SLES-53028: clampModes: vuClampMode: 0 # Fixes bump mapping issues. gsHWFixes: - halfPixelOffset: 4 # Fixes post processing position. + halfPixelOffset: 5 # Fixes alignment of shuffles and post processing. autoFlush: 1 # Fixes sun intensity. textureInsideRT: 1 # Fixes post processing. getSkipCount: "GSC_HitmanBloodMoney" @@ -21361,7 +21406,7 @@ SLES-53029: clampModes: vuClampMode: 0 # Fixes bump mapping issues. gsHWFixes: - halfPixelOffset: 4 # Fixes post processing position. + halfPixelOffset: 5 # Fixes alignment of shuffles and post processing. autoFlush: 1 # Fixes sun intensity. textureInsideRT: 1 # Fixes post processing. getSkipCount: "GSC_HitmanBloodMoney" @@ -21371,7 +21416,7 @@ SLES-53030: clampModes: vuClampMode: 0 # Fixes bump mapping issues. gsHWFixes: - halfPixelOffset: 4 # Fixes post processing position. + halfPixelOffset: 5 # Fixes alignment of shuffles and post processing. autoFlush: 1 # Fixes sun intensity. textureInsideRT: 1 # Fixes post processing. getSkipCount: "GSC_HitmanBloodMoney" @@ -21381,7 +21426,7 @@ SLES-53031: clampModes: vuClampMode: 0 # Fixes bump mapping issues. gsHWFixes: - halfPixelOffset: 4 # Fixes post processing position. + halfPixelOffset: 5 # Fixes alignment of shuffles and post processing. autoFlush: 1 # Fixes sun intensity. textureInsideRT: 1 # Fixes post processing. getSkipCount: "GSC_HitmanBloodMoney" @@ -21391,7 +21436,7 @@ SLES-53032: clampModes: vuClampMode: 0 # Fixes bump mapping issues. gsHWFixes: - halfPixelOffset: 4 # Fixes post processing position. + halfPixelOffset: 5 # Fixes alignment of shuffles and post processing. autoFlush: 1 # Fixes sun intensity. textureInsideRT: 1 # Fixes post processing. getSkipCount: "GSC_HitmanBloodMoney" @@ -21557,10 +21602,9 @@ SLES-53087: gsHWFixes: alignSprite: 1 # Fixes lighting and vertical lines, also works with normal vertex. roundSprite: 1 # Fixes misaligned map. - skipDrawStart: 1 # Removes large black box around player car till we properly emulate it. - skipDrawEnd: 1 # Removes large black box around player car till we properly emulate it. halfPixelOffset: 2 # Fixes depth of field alignment. nativeScaling: 1 # Fixes depth of field. + textureInsideRT: 1 # Fixes shadows. memcardFilters: - "SLES-53087" - "SLES-52637" # Race Driver 2 save unlocks 'Class A 4WD Track Challenge' in career early. @@ -21570,10 +21614,9 @@ SLES-53088: gsHWFixes: alignSprite: 1 # Fixes lighting and vertical lines, also works with normal vertex. roundSprite: 1 # Fixes misaligned map. - skipDrawStart: 1 # Removes large black box around player car till we properly emulate it. - skipDrawEnd: 1 # Removes large black box around player car till we properly emulate it. halfPixelOffset: 2 # Fixes depth of field alignment. nativeScaling: 1 # Fixes depth of field. + textureInsideRT: 1 # Fixes shadows. memcardFilters: - "SLES-53088" - "SLES-52638" # Race Driver 2 save unlocks 'Class A 4WD Track Challenge' in career early. @@ -21583,10 +21626,9 @@ SLES-53089: gsHWFixes: alignSprite: 1 # Fixes lighting and vertical lines, also works with normal vertex. roundSprite: 1 # Fixes misaligned map. - skipDrawStart: 1 # Removes large black box around player car till we properly emulate it. - skipDrawEnd: 1 # Removes large black box around player car till we properly emulate it. halfPixelOffset: 2 # Fixes depth of field alignment. nativeScaling: 1 # Fixes depth of field. + textureInsideRT: 1 # Fixes shadows. memcardFilters: - "SLES-53089" - "SLES-52639" # Race Driver 2 save unlocks 'Class A 4WD Track Challenge' in career early. @@ -21676,7 +21718,7 @@ SLES-53124: gsHWFixes: autoFlush: 1 # Fixes the post processing positioning. textureInsideRT: 1 # Fixes post processing. - halfPixelOffset: 4 # Aligns bloom effect. + halfPixelOffset: 5 # Aligns bloom effect. nativeScaling: 2 # Fixes post processing smoothness and position. SLES-53125: name: "Enthusia Professional Racing" @@ -22373,6 +22415,7 @@ SLES-53415: halfPixelOffset: 2 # Fix text and post blur. nativeScaling: 2 # Fixes post effects. cpuSpriteRenderBW: 1 # Fixes textures. + cpuSpriteRenderLevel: 1 # Needed for above. SLES-53416: name: "Call of Duty 2 - Big Red One" region: "PAL-M3" @@ -22382,6 +22425,7 @@ SLES-53416: halfPixelOffset: 2 # Fix text and post blur. nativeScaling: 2 # Fixes post effects. cpuSpriteRenderBW: 1 # Fixes textures. + cpuSpriteRenderLevel: 1 # Needed for above. SLES-53417: name: "Call of Duty 2 - Big Red One" region: "PAL-G" @@ -22391,6 +22435,7 @@ SLES-53417: halfPixelOffset: 2 # Fix text and post blur. nativeScaling: 2 # Fixes post effects. cpuSpriteRenderBW: 1 # Fixes textures. + cpuSpriteRenderLevel: 1 # Needed for above. SLES-53418: name: "Tak - The Great JuJu Challenge" region: "PAL-A" @@ -23136,6 +23181,7 @@ SLES-53616: eeRoundMode: 0 # Fixes scene switching in intro. gsHWFixes: cpuSpriteRenderBW: 1 # Fixes textures. + cpuSpriteRenderLevel: 1 # Needed for above. preloadFrameData: 1 # Fixes numberplates and ensures above targets are valid. roundSprite: 1 # Fixes lines in some post-effects. gpuTargetCLUT: 1 # Fixes light occlusion. @@ -23150,6 +23196,7 @@ SLES-53617: eeRoundMode: 0 # Fixes scene switching in intro. gsHWFixes: cpuSpriteRenderBW: 1 # Fixes textures. + cpuSpriteRenderLevel: 1 # Needed for above. preloadFrameData: 1 # Fixes numberplates and ensures above targets are valid. roundSprite: 1 # Fixes lines in some post-effects. gpuTargetCLUT: 1 # Fixes light occlusion. @@ -23164,6 +23211,7 @@ SLES-53618: eeRoundMode: 0 # Fixes scene switching in intro. gsHWFixes: cpuSpriteRenderBW: 1 # Fixes textures. + cpuSpriteRenderLevel: 1 # Needed for above. preloadFrameData: 1 # Fixes numberplates and ensures above targets are valid. roundSprite: 1 # Fixes lines in some post-effects. gpuTargetCLUT: 1 # Fixes light occlusion. @@ -23436,6 +23484,7 @@ SLES-53703: autoFlush: 1 # Corrects vignette to match software. estimateTextureRegion: 1 # Improves performance and reduces hash cache size. halfPixelOffset: 4 # Aligns blur more correctly to match software. + textureInsideRT: 1 # Fixes post processing effects. SLES-53704: name: "Peter Jackson's King Kong - The Official Game of the Movie" name-sort: "King Kong, Peter Jackson's - The Official Game of the Movie" @@ -23445,6 +23494,7 @@ SLES-53704: autoFlush: 1 # Corrects vignette to match software. estimateTextureRegion: 1 # Improves performance and reduces hash cache size. halfPixelOffset: 4 # Aligns blur more correctly to match software. + textureInsideRT: 1 # Fixes post processing effects. SLES-53705: name: "Peter Jackson's King Kong - The Official Game of the Movie" name-sort: "King Kong, Peter Jackson's - The Official Game of the Movie" @@ -23454,6 +23504,7 @@ SLES-53705: autoFlush: 1 # Corrects vignette to match software. estimateTextureRegion: 1 # Improves performance and reduces hash cache size. halfPixelOffset: 4 # Aligns blur more correctly to match software. + textureInsideRT: 1 # Fixes post processing effects. SLES-53706: name: "The Chronicles of Narnia - The Lion, the Witch and the Wardrobe" name-sort: "Chronicles of Narnia, The - The Lion, the Witch and the Wardrobe" @@ -23507,6 +23558,8 @@ SLES-53717: gsHWFixes: halfPixelOffset: 2 # Aligns post bloom. nativeScaling: 2 # Fixes light blooms. + textureInsideRT: 1 # Improves performance. + preloadFrameData: 1 # Fixes light trails. getSkipCount: "GSC_MidnightClub3" patches: 208183AF: @@ -23538,6 +23591,7 @@ SLES-53722: halfPixelOffset: 2 # Fix text and post blur. nativeScaling: 2 # Fixes post effects. cpuSpriteRenderBW: 1 # Fixes textures. + cpuSpriteRenderLevel: 1 # Needed for above. SLES-53724: name: "World Series of Poker" region: "PAL-E" @@ -24865,7 +24919,7 @@ SLES-54185: compat: 5 gsHWFixes: autoFlush: 1 # Fixes lighting. - halfPixelOffset: 4 # Fixes ghosting. + halfPixelOffset: 5 # Fixes ghosting. nativeScaling: 2 # Fixes post processing. recommendedBlendingLevel: 4 # Fixes missing light brightness. SLES-54186: @@ -25902,12 +25956,21 @@ SLES-54510: SLES-54511: name: "UEFA Champions League 2006-2007" region: "PAL-E" + gsHWFixes: + cpuSpriteRenderBW: 2 # Fixes broken lights due to copying around P8 data. + cpuSpriteRenderLevel: 2 # Needed for above. SLES-54512: name: "UEFA Champions League 2006-2007" region: "PAL-F-G" + gsHWFixes: + cpuSpriteRenderBW: 2 # Fixes broken lights due to copying around P8 data. + cpuSpriteRenderLevel: 2 # Needed for above. SLES-54513: name: "UEFA Champions League 2006-2007" region: "PAL-I-S" + gsHWFixes: + cpuSpriteRenderBW: 2 # Fixes broken lights due to copying around P8 data. + cpuSpriteRenderLevel: 2 # Needed for above. SLES-54516: name: "Thrillville" region: "PAL-F-G" @@ -26851,8 +26914,9 @@ SLES-54819: region: "PAL-M5" compat: 5 gsHWFixes: - getSkipCount: "GSC_Manhunt2" autoFlush: 1 # Fixes missing lights and light intensity. + textureInsideRT: 1 # Fixes post lighting. + halfPixelOffset: 2 # Fixes post effects. SLES-54820: name: "Stuntman - Ignition" region: "PAL-M5" @@ -27318,7 +27382,7 @@ SLES-54972: region: "PAL-M3" gsHWFixes: roundSprite: 1 # Fixes font sizes and lines in UI. - cpuFramebufferConversion: 1 # Fixes sepia-tone flashback sequences. + textureInsideRT: 1 # Fixes sepia-tone flashback sequences. forceEvenSpritePosition: 1 # Fixes font artifacts and out-of-bound 2D textures. gpuPaletteConversion: 2 # Fixes micro-stuttering and reduces HC size. SLES-54973: @@ -27721,6 +27785,7 @@ SLES-55031: region: "PAL-F" gsHWFixes: cpuSpriteRenderBW: 1 # Fixes textures. + cpuSpriteRenderLevel: 1 # Needed for above. halfPixelOffset: 4 # Aligns post processing. autoFlush: 1 # Fixes post processing. SLES-55032: @@ -27873,6 +27938,8 @@ SLES-55109: region: "PAL-E" roundModes: vu0RoundMode: 0 # Fixes invisible wall collision in bedroom. + gsHWFixes: + textureInsideRT: 1 # Fixes half screen. SLES-55110: name: "Odin Sphere" region: "PAL-M5" @@ -28085,6 +28152,7 @@ SLES-55191: autoFlush: 1 # Fixes bloom intensity. halfPixelOffset: 4 # Mostly aligns post processing. nativeScaling: 1 # Fixes post processing smoothness and position. + getSkipCount: "GSC_GuitarHero" SLES-55192: name: "Steam Express" region: "PAL-M5" @@ -28150,6 +28218,7 @@ SLES-55200: autoFlush: 1 # Fixes bloom intensity. halfPixelOffset: 4 # Mostly aligns post processing. nativeScaling: 1 # Fixes post processing smoothness and position. + getSkipCount: "GSC_GuitarHero" SLES-55201: name: "Riding Star" region: "PAL-M4" @@ -28233,6 +28302,7 @@ SLES-55234: region: "PAL-SW" gsHWFixes: cpuSpriteRenderBW: 1 # Fixes textures. + cpuSpriteRenderLevel: 1 # Needed for above. halfPixelOffset: 4 # Aligns post processing. autoFlush: 1 # Fixes post processing. SLES-55235: @@ -28240,6 +28310,7 @@ SLES-55235: region: "PAL-I" gsHWFixes: cpuSpriteRenderBW: 1 # Fixes textures. + cpuSpriteRenderLevel: 1 # Needed for above. halfPixelOffset: 4 # Aligns post processing. autoFlush: 1 # Fixes post processing. SLES-55236: @@ -28247,6 +28318,7 @@ SLES-55236: region: "PAL-G-S" gsHWFixes: cpuSpriteRenderBW: 1 # Fixes textures. + cpuSpriteRenderLevel: 1 # Needed for above. halfPixelOffset: 4 # Aligns post processing. autoFlush: 1 # Fixes post processing. SLES-55237: @@ -30595,8 +30667,6 @@ SLKA-25196: gsHWFixes: autoFlush: 1 # Aligns and corrects shadows. halfPixelOffset: 2 # Fixes sun and depth line. - cpuCLUTRender: 1 # Fixes janky coloured cars. - gpuTargetCLUT: 1 # Fixes janky coloured cars. textureInsideRT: 1 # Fixes car textures. cpuSpriteRenderBW: 4 # Alleviates text and sky rendering issues. cpuSpriteRenderLevel: 2 # Needed for above. @@ -30721,6 +30791,8 @@ SLKA-25218: gsHWFixes: texturePreloading: 1 # Performs much better with partial preload. autoFlush: 1 # Fixes bloom rendering. + textureInsideRT: 1 # Fixes post processing. + halfPixelOffset: 5 # Fixes alignment of shuffles and post processing. roundModes: vu1RoundMode: 0 # Fixes missing light cones curtains and certain effects. speedHacks: @@ -31270,6 +31342,7 @@ SLKA-25337: autoFlush: 1 # Corrects vignette to match software. estimateTextureRegion: 1 # Improves performance and reduces hash cache size. halfPixelOffset: 4 # Aligns blur more correctly to match software. + textureInsideRT: 1 # Fixes post processing effects. SLKA-25338: name: "The Godfather" name-sort: "Godfather, The" @@ -31691,6 +31764,7 @@ SLKA-25434: autoFlush: 1 # Fixes bloom intensity. halfPixelOffset: 4 # Mostly aligns post processing. nativeScaling: 1 # Fixes post processing smoothness and position. + getSkipCount: "GSC_GuitarHero" SLKA-25435: name: "Sengoku Basara X" region: "NTSC-J-K" @@ -31699,6 +31773,7 @@ SLKA-25436: region: "NTSC-K" gsHWFixes: cpuSpriteRenderBW: 1 # Fixes textures. + cpuSpriteRenderLevel: 1 # Needed for above. SLKA-25437: name: "Street Fighter Anniversary Collection" region: "NTSC-K" @@ -32190,10 +32265,9 @@ SLPM-55046: gsHWFixes: alignSprite: 1 # Fixes lighting and vertical lines, also works with normal vertex. roundSprite: 1 # Fixes misaligned map. - skipDrawStart: 1 # Removes large black box around player car till we properly emulate it. - skipDrawEnd: 1 # Removes large black box around player car till we properly emulate it. halfPixelOffset: 2 # Fixes depth of field alignment. nativeScaling: 1 # Fixes depth of field. + textureInsideRT: 1 # Fixes shadows. memcardFilters: - "SLPM-55046" - "SLPM-66498" # Race Driver 2 save unlocks 'Class A 4WD Track Challenge' in career early. @@ -32371,6 +32445,8 @@ SLPM-55080: texturePreloading: 1 # Performs better with partial preload because it is slow on locations outside gameplay foremost. mergeSprite: 1 # Fixes misaligned white lines. PCRTCOverscan: 1 # Fixes missing HUD. + textureInsideRT: 1 # Fixes post shuffle effect. + halfPixelOffset: 2 # Fixes boxes around shuffle effect pages. SLPM-55081: name: "ドラッグオンドラグーン2 ~封印の紅 背徳の黒~" name-sort: "どらっぐおんどらぐーん2 ~ふういんのあか はいとくのくろ~" @@ -33461,8 +33537,7 @@ SLPM-60109: clampModes: vuClampMode: 2 # Fixes texture rendering in the intro. gsHWFixes: - cpuFramebufferConversion: 1 - textureInsideRT: 1 + textureInsideRT: 1 # Fixes post effects halfPixelOffset: 2 # Fixes title screen and some intro post processing alignment. roundSprite: 1 # Fixes ui and hud alignment. gpuPaletteConversion: 2 # Lots of CLUTs in large textures. @@ -33966,6 +34041,8 @@ SLPM-60217: region: "NTSC-J" gsHWFixes: texturePreloading: 0 # Performs much better with no preload. + textureInsideRT: 1 # Fixes post processing. + halfPixelOffset: 5 # Fixes alignment of shuffles and post processing. SLPM-60218: name: "GUNGRAVE O.D. [体験版]" name-sort: "がんぐれいぶ おーでぃー [たいけんばん]" @@ -34138,9 +34215,8 @@ SLPM-60257: region: "NTSC-J" gsHWFixes: alignSprite: 1 # Fixes FMV lines. - cpuSpriteRenderBW: 2 # Prevents Death By VRAM explosion. - cpuSpriteRenderLevel: 2 # Needed for above. - getSkipCount: "GSC_DeathByDegreesTekkenNinaWilliams" + halfPixelOffset: 5 # Fixes alignment of shuffles and post processing. + textureInsideRT: 1 # Fixes post shuffles. SLPM-60258: name: "THE TYPING OF THE DEAD ZOMBIE PANIC [体験版]" name-sort: "ざ たいぴんぐおぶ ざ でっど ぞんび ぱにっく [たいけんばん]" @@ -34694,8 +34770,6 @@ SLPM-61092: gsHWFixes: autoFlush: 1 # Aligns and corrects shadows. halfPixelOffset: 2 # Fixes sun and depth line. - cpuCLUTRender: 1 # Fixes janky coloured cars. - gpuTargetCLUT: 1 # Fixes janky coloured cars. textureInsideRT: 1 # Fixes car textures. cpuSpriteRenderBW: 4 # Alleviates text and sky rendering issues. cpuSpriteRenderLevel: 2 # Needed for above. @@ -36981,7 +37055,6 @@ SLPM-62378: region: "NTSC-J" gsHWFixes: textureInsideRT: 1 # Fixes inside RT shuffling. - getSkipCount: "GSC_BigMuthaTruckers" SLPM-62379: name: "カラオケレボリューション J-POPベストVol.2" name-sort: "からおけれぼりゅーしょん J-POPべすとVol.2" @@ -39606,6 +39679,9 @@ SLPM-65073: name-sort: "げんそうすいこでん3 [しょかいせいさんぶん:とくしゅしよう]" name-en: "Gensou Suikoden III [Limited Edition]" region: "NTSC-J" + gsHWFixes: + textureInsideRT: 1 # Fixes half screen. + halfPixelOffset: 4 # Fixes post effects. memcardFilters: # This looks like a mess because it includes all serials for Suikoden 3, Suikoden 2, Suikogaiden 1, and Suikogaiden 2. A lot of these probably aren't actually required but it's not really hurting anything to have them here. - "SLPM-65073" - "SLPM-65074" @@ -39625,6 +39701,9 @@ SLPM-65074: name-sort: "げんそうすいこでん3" name-en: "Gensou Suikoden III" region: "NTSC-J" + gsHWFixes: + textureInsideRT: 1 # Fixes half screen. + halfPixelOffset: 4 # Fixes post effects. memcardFilters: - "SLPM-65073" - "SLPM-65074" @@ -40544,7 +40623,6 @@ SLPM-65234: region: "NTSC-J" gsHWFixes: textureInsideRT: 1 # Fixes inside RT shuffling. - getSkipCount: "GSC_BigMuthaTruckers" SLPM-65235: name: "ニュールーマニア ポロリ青春" name-sort: "にゅーるーまにあ ぽろりせいしゅん" @@ -40740,6 +40818,8 @@ SLPM-65266: texturePreloading: 1 # Performs better with partial preload because it is slow on locations outside gameplay foremost. mergeSprite: 1 # Fixes misaligned white lines. PCRTCOverscan: 1 # Fixes missing HUD. + textureInsideRT: 1 # Fixes post shuffle effect. + halfPixelOffset: 2 # Fixes boxes around shuffle effect pages. SLPM-65267: name: "鋼鉄の咆哮2 - ウォーシップガンナー" name-sort: "くろがねのほうこう2 うぉーしっぷがんなー" @@ -40965,6 +41045,9 @@ SLPM-65305: name-sort: "げんそうすいこでん3 [KONAMI THE BEST]" name-en: "Gensou Suikoden 3" region: "NTSC-J" + gsHWFixes: + textureInsideRT: 1 # Fixes half screen. + halfPixelOffset: 4 # Fixes post effects. memcardFilters: - "SLPM-65073" - "SLPM-65074" @@ -42509,6 +42592,7 @@ SLPM-65583: autoFlush: 2 # Fixes sun luminosity. halfPixelOffset: 2 # Aligns Depth of Field. nativeScaling: 2 # Fixes Depth of Field effect. + textureInsideRT: 1 # Fixes half screen fog effect. SLPM-65584: name: "真・三國無双3 猛将伝" name-sort: "しんさんごくむそう3 もうしょうでん" @@ -43132,6 +43216,9 @@ SLPM-65694: name-sort: "げんそうすいこでん3 [こなみでんどうせれくしょん]" name-en: "Gensou Suikoden 3 [KONAMI Dendou Selection]" region: "NTSC-J" + gsHWFixes: + textureInsideRT: 1 # Fixes half screen. + halfPixelOffset: 4 # Fixes post effects. memcardFilters: - "SLPM-65073" - "SLPM-65074" @@ -43404,8 +43491,6 @@ SLPM-65741: gsHWFixes: autoFlush: 1 # Aligns and corrects shadows. halfPixelOffset: 2 # Fixes sun and depth line. - cpuCLUTRender: 1 # Fixes janky coloured cars. - gpuTargetCLUT: 1 # Fixes janky coloured cars. textureInsideRT: 1 # Fixes car textures. cpuSpriteRenderBW: 4 # Alleviates text and sky rendering issues. cpuSpriteRenderLevel: 2 # Needed for above. @@ -45542,6 +45627,7 @@ SLPM-66103: autoFlush: 2 # Fixes sun luminosity. halfPixelOffset: 2 # Aligns Depth of Field. nativeScaling: 2 # Fixes Depth of Field effect. + textureInsideRT: 1 # Fixes half screen fog effect. SLPM-66104: name: "ぷよぷよフィーバー2 [チュー!]" name-sort: "ぷよぷよふぃーばー2" @@ -46252,6 +46338,7 @@ SLPM-66211: autoFlush: 1 # Corrects vignette to match software. estimateTextureRegion: 1 # Improves performance and reduces hash cache size. halfPixelOffset: 4 # Aligns blur more correctly to match software. + textureInsideRT: 1 # Fixes post processing effects. SLPM-66212: name: "SEGA RALLY 2006" name-sort: "せが らりー 2006" @@ -46662,7 +46749,7 @@ SLPM-66271: compat: 5 gsHWFixes: autoFlush: 1 # Fixes lighting. - halfPixelOffset: 4 # Fixes ghosting. + halfPixelOffset: 5 # Fixes ghosting. nativeScaling: 2 # Fixes post processing. recommendedBlendingLevel: 4 # Fixes missing light brightness. SLPM-66272: @@ -47034,6 +47121,7 @@ SLPM-66328: halfPixelOffset: 2 # Fix text and post blur. nativeScaling: 2 # Fixes post effects. cpuSpriteRenderBW: 1 # Fixes textures. + cpuSpriteRenderLevel: 1 # Needed for above. SLPM-66329: name: "魔法先生ネギま! 課外授業 乙女のドキドキ・ビーチサイド" name-sort: "まほうせんせいねぎま! かがいじゅぎょう おとめのどきどき びーちさいど" @@ -47916,6 +48004,7 @@ SLPM-66473: eeRoundMode: 0 # Fixes scene switching in intro. gsHWFixes: cpuSpriteRenderBW: 1 # Fixes textures. + cpuSpriteRenderLevel: 1 # Needed for above. preloadFrameData: 1 # Fixes numberplates and ensures above targets are valid. roundSprite: 1 # Fixes lines in some post-effects. gpuTargetCLUT: 1 # Fixes light occlusion. @@ -48092,6 +48181,10 @@ SLPM-66498: region: "NTSC-J" gsHWFixes: alignSprite: 1 # Fixes lighting and vertical lines, also works with normal vertex. + roundSprite: 1 # Fixes misaligned map. + halfPixelOffset: 2 # Fixes depth of field alignment. + nativeScaling: 1 # Fixes depth of field. + textureInsideRT: 1 # Fixes shadows. SLPM-66499: name: "神様家族 応援願望" name-sort: "かみさまかぞく おうえんがんぼう" @@ -48878,7 +48971,7 @@ SLPM-66629: region: "NTSC-J" gsHWFixes: autoFlush: 1 # Fixes lighting. - halfPixelOffset: 4 # Fixes ghosting. + halfPixelOffset: 5 # Fixes ghosting. nativeScaling: 2 # Fixes post processing. recommendedBlendingLevel: 4 # Fixes missing light brightness. SLPM-66630: @@ -50408,10 +50501,9 @@ SLPM-66881: gsHWFixes: alignSprite: 1 # Fixes lighting and vertical lines, also works with normal vertex. roundSprite: 1 # Fixes misaligned map. - skipDrawStart: 1 # Removes large black box around player car till we properly emulate it. - skipDrawEnd: 1 # Removes large black box around player car till we properly emulate it. halfPixelOffset: 2 # Fixes depth of field alignment. nativeScaling: 1 # Fixes depth of field. + textureInsideRT: 1 # Fixes shadows. memcardFilters: - "SLPM-66881" - "SLPM-66498" # Race Driver 2 save unlocks 'Class A 4WD Track Challenge' in career early. @@ -51432,7 +51524,7 @@ SLPM-68016: region: "NTSC-J" gsHWFixes: autoFlush: 1 # Fixes lighting. - halfPixelOffset: 4 # Fixes ghosting. + halfPixelOffset: 5 # Fixes ghosting. nativeScaling: 2 # Fixes post processing. recommendedBlendingLevel: 4 # Fixes missing light brightness. SLPM-68017: @@ -51500,6 +51592,9 @@ SLPM-68505: name-sort: "げんそうすいこでん3" name-en: "Gensou Suikoden III" region: "NTSC-J" + gsHWFixes: + textureInsideRT: 1 # Fixes half screen. + halfPixelOffset: 4 # Fixes post effects. SLPM-68509: name: "頭文字D Special Stage [講談社懸賞品]" name-sort: "いにしゃるD すぺしゃる すてーじ [こうだんしゃけんしょうひん]" @@ -51928,6 +52023,7 @@ SLPM-74243: eeRoundMode: 0 # Fixes scene switching in intro. gsHWFixes: cpuSpriteRenderBW: 1 # Fixes textures. + cpuSpriteRenderLevel: 1 # Needed for above. preloadFrameData: 1 # Fixes numberplates and ensures above targets are valid. roundSprite: 1 # Fixes lines in some post-effects. gpuTargetCLUT: 1 # Fixes light occlusion. @@ -52389,8 +52485,7 @@ SLPS-20001: clampModes: vuClampMode: 2 # Fixes texture rendering in the intro. gsHWFixes: - cpuFramebufferConversion: 1 - textureInsideRT: 1 + textureInsideRT: 1 # Fixes post effects halfPixelOffset: 2 # Fixes title screen and some intro post processing alignment. roundSprite: 1 # Fixes ui and hud alignment. gpuPaletteConversion: 2 # Lots of CLUTs in large textures. @@ -54213,6 +54308,7 @@ SLPS-20382: region: "NTSC-J" gsHWFixes: alignSprite: 1 # Fixes vertical lines. + textureInsideRT: 1 # Fixes post effects. SLPS-20383: name: "太鼓の達人 あつまれ!祭りだ!!四代目" name-sort: "たいこのたつじん あつまれ!まつりだ!!よんだいめ" @@ -54220,6 +54316,7 @@ SLPS-20383: region: "NTSC-J" gsHWFixes: alignSprite: 1 # Fixes vertical lines. + textureInsideRT: 1 # Fixes post effects. SLPS-20384: name: "流行り神 警視庁怪異事件ファイル [初回限定版]" name-sort: "はやりがみ けいしちょうかいいじけんふぁいる [しょかいげんていばん]" @@ -54287,6 +54384,7 @@ SLPS-20399: region: "NTSC-J" gsHWFixes: alignSprite: 1 # Fixes vertical lines. + textureInsideRT: 1 # Fixes post effects. SLPS-20400: name: "太鼓の達人 ゴー!ゴー!五代目 [ソフト単体]" name-sort: "たいこのたつじん ごー!ごー!ごだいめ [そふとたんたい]" @@ -54294,6 +54392,7 @@ SLPS-20400: region: "NTSC-J" gsHWFixes: alignSprite: 1 # Fixes vertical lines. + textureInsideRT: 1 # Fixes post effects. SLPS-20401: name: "テクモ ヒットパレード" name-sort: "てくも ひっとぱれーど" @@ -54365,6 +54464,7 @@ SLPS-20413: region: "NTSC-J" gsHWFixes: alignSprite: 1 # Fixes vertical lines. + textureInsideRT: 1 # Fixes post effects. SLPS-20414: name: "太鼓の達人 TAIKO DRUM MASTER" name-sort: "たいこのたつじん TAIKO DRUM MASTER" @@ -54373,6 +54473,7 @@ SLPS-20414: compat: 5 gsHWFixes: alignSprite: 1 # Fixes vertical lines. + textureInsideRT: 1 # Fixes post effects. SLPS-20416: name: "陰陽大戦記 白虎演舞 [“EyeToy”カメラ同梱版]" name-sort: "おんみょうたいせんき びゃっこえんぶ [あいとーいかめらどうこんばん]" @@ -54424,6 +54525,7 @@ SLPS-20424: region: "NTSC-J" gsHWFixes: alignSprite: 1 # Fixes vertical lines. + textureInsideRT: 1 # Fixes post effects. SLPS-20425: name: "太鼓の達人 とびっきり!アニメスペシャル [ソフト単体]" name-sort: "たいこのたつじん とびっきり!あにめすぺしゃる [そふとたんたい]" @@ -54431,6 +54533,7 @@ SLPS-20425: region: "NTSC-J" gsHWFixes: alignSprite: 1 # Fixes vertical lines. + textureInsideRT: 1 # Fixes post effects. SLPS-20426: name: "マダガスカル" name-sort: "まだかすかる" @@ -54540,6 +54643,7 @@ SLPS-20450: region: "NTSC-J" gsHWFixes: alignSprite: 1 # Fixes vertical lines. + textureInsideRT: 1 # Fixes post effects. SLPS-20451: name: "太鼓の達人 わいわいハッピー!六代目" name-sort: "たいこのたつじん わいわいはっぴー!ろくだいめ" @@ -54547,6 +54651,7 @@ SLPS-20451: region: "NTSC-J" gsHWFixes: alignSprite: 1 # Fixes vertical lines. + textureInsideRT: 1 # Fixes post effects. SLPS-20452: name: "SIMPLE2000シリーズ Ultimate Vol.30 降臨!族車ゴッド~仏恥義理★愛羅武勇~" name-sort: "しんぷる2000しりーず あるてぃめっと Vol.30 こうりん!ぞくしゃごっど ぶっちぎりあいらぶゆう" @@ -54747,6 +54852,7 @@ SLPS-20485: region: "NTSC-J" gsHWFixes: alignSprite: 1 # Fixes vertical lines. + textureInsideRT: 1 # Fixes post effects. getSkipCount: "GSC_Tekken5" # Fixes upscaling grid, same engine. SLPS-20486: name: "太鼓の達人 ドカッ!と大盛り七代目 [ソフト単体]" @@ -54755,6 +54861,7 @@ SLPS-20486: region: "NTSC-J" gsHWFixes: alignSprite: 1 # Fixes vertical lines. + textureInsideRT: 1 # Fixes post effects. getSkipCount: "GSC_Tekken5" # Fixes upscaling grid, same engine. SLPS-20487: name: "パチスロキング! 科学忍者隊ガッチャマン" @@ -56546,6 +56653,8 @@ SLPS-25289: region: "NTSC-J" gsHWFixes: texturePreloading: 0 # Performs much better with no preload. + textureInsideRT: 1 # Fixes post processing. + halfPixelOffset: 5 # Fixes alignment of shuffles and post processing. SLPS-25290: name: "タイムクライシス3 [ソフト単体]" name-sort: "たいむくらいしす3 [そふとたんたい]" @@ -56553,6 +56662,8 @@ SLPS-25290: region: "NTSC-J" gsHWFixes: texturePreloading: 0 # Performs much better with no preload. + textureInsideRT: 1 # Fixes post processing. + halfPixelOffset: 5 # Fixes alignment of shuffles and post processing. SLPS-25291: name: "Baldur's Gate - DARK ALLIANCE - [PCCW Japan The BEST]" name-sort: "ばるだーずげーと だーくあらいあんす [PCCW Japan The BEST]" @@ -57275,6 +57386,8 @@ SLPS-25406: gsHWFixes: texturePreloading: 1 # Performs much better with partial preload. autoFlush: 1 # Fixes bloom rendering. + textureInsideRT: 1 # Fixes post processing. + halfPixelOffset: 5 # Fixes alignment of shuffles and post processing. roundModes: vu1RoundMode: 0 # Fixes missing light cones curtains and certain effects. speedHacks: @@ -57404,9 +57517,8 @@ SLPS-25422: region: "NTSC-J" gsHWFixes: alignSprite: 1 # Fixes FMV lines. - cpuSpriteRenderBW: 2 # Prevents Death By VRAM explosion. - cpuSpriteRenderLevel: 2 # Needed for above. - getSkipCount: "GSC_DeathByDegreesTekkenNinaWilliams" + halfPixelOffset: 5 # Fixes alignment of shuffles and post processing. + textureInsideRT: 1 # Fixes post shuffles. SLPS-25423: name: "怪盗アプリコット 完全版 [限定版]" name-sort: "かいとうあぷりこっと かんぜんばん [げんていばん]" @@ -58177,6 +58289,8 @@ SLPS-25563: gsHWFixes: texturePreloading: 1 # Performs much better with partial preload. autoFlush: 1 # Fixes bloom rendering. + textureInsideRT: 1 # Fixes post processing. + halfPixelOffset: 5 # Fixes alignment of shuffles and post processing. roundModes: vu1RoundMode: 0 # Fixes missing light cones curtains and certain effects. speedHacks: @@ -60130,6 +60244,7 @@ SLPS-25886: autoFlush: 1 # Fixes bloom intensity. halfPixelOffset: 4 # Mostly aligns post processing. nativeScaling: 1 # Fixes post processing smoothness and position. + getSkipCount: "GSC_GuitarHero" SLPS-25887: name: "スーパーロボット大戦Z" name-sort: "すーぱーろぼっとたいせんZ" @@ -60166,6 +60281,7 @@ SLPS-25889: autoFlush: 1 # Fixes bloom intensity. halfPixelOffset: 4 # Mostly aligns post processing. nativeScaling: 1 # Fixes post processing smoothness and position. + getSkipCount: "GSC_GuitarHero" SLPS-25890: name: "ギターヒーロー3 レジェンドオブロック [ソフト単体]" name-sort: "ぎたーひーろー3 れじぇんどおぶろっく [そふとたんたい]" @@ -60751,8 +60867,7 @@ SLPS-71502: name-en: "Ridge Racer V [MEGA HITS!]" region: "NTSC-J" gsHWFixes: - cpuFramebufferConversion: 1 - textureInsideRT: 1 + textureInsideRT: 1 # Fixes post effects halfPixelOffset: 2 # Fixes title screen and some intro post processing alignment. roundSprite: 1 # Fixes ui and hud alignment. gpuPaletteConversion: 2 # Lots of CLUTs in large textures. @@ -61671,8 +61786,7 @@ SLUS-20002: clampModes: vuClampMode: 2 # Fixes texture rendering in the intro. gsHWFixes: - cpuFramebufferConversion: 1 - textureInsideRT: 1 + textureInsideRT: 1 # Fixes post effects halfPixelOffset: 2 # Fixes title screen and some intro post processing alignment. roundSprite: 1 # Fixes ui and hud alignment. gpuPaletteConversion: 2 # Lots of CLUTs in large textures. @@ -62854,7 +62968,6 @@ SLUS-20291: compat: 5 gsHWFixes: textureInsideRT: 1 # Fixes inside RT shuffling. - getSkipCount: "GSC_BigMuthaTruckers" SLUS-20292: name: "Tsugunai - Atonement" region: "NTSC-U" @@ -63329,6 +63442,9 @@ SLUS-20387: name: "Suikoden III" region: "NTSC-U" compat: 5 + gsHWFixes: + textureInsideRT: 1 # Fixes half screen. + halfPixelOffset: 4 # Fixes post effects. memcardFilters: # Allows import of Suikoden II clear data. - "SLUS-20387" - "SLUS-00958" @@ -63575,6 +63691,9 @@ SLUS-20433: compat: 5 clampModes: eeClampMode: 3 # For grey screen ingame. + gsHWFixes: + textureInsideRT: 1 # Fixes post effects on player 2. + halfPixelOffset: 4 # Fixes chromatic effect. SLUS-20434: name: "Myst III - Exile" region: "NTSC-U" @@ -64397,8 +64516,6 @@ SLUS-20587: gsHWFixes: autoFlush: 1 # Aligns and corrects shadows. halfPixelOffset: 2 # Fixes sun and depth line. - cpuCLUTRender: 1 # Fixes janky coloured cars. - gpuTargetCLUT: 1 # Fixes janky coloured cars. textureInsideRT: 1 # Fixes car textures. cpuSpriteRenderBW: 4 # Alleviates text and sky rendering issues. cpuSpriteRenderLevel: 2 # Needed for above. @@ -64495,7 +64612,6 @@ SLUS-20605: region: "NTSC-U" gsHWFixes: textureInsideRT: 1 # Fixes inside RT shuffling. - getSkipCount: "GSC_BigMuthaTruckers" SLUS-20606: name: "Bounty Hunter - Seek & Destroy" region: "NTSC-U" @@ -64687,6 +64803,8 @@ SLUS-20645: compat: 5 gsHWFixes: texturePreloading: 0 # Performs much better with no preload. + textureInsideRT: 1 # Fixes post processing. + halfPixelOffset: 5 # Fixes alignment of shuffles and post processing. SLUS-20646: name: "Mark Davis Pro Bass Challenge" region: "NTSC-U" @@ -65155,6 +65273,8 @@ SLUS-20732: texturePreloading: 1 # Performs better with partial preload because it is slow on locations outside gameplay foremost. mergeSprite: 1 # Fixes misaligned white lines. PCRTCOverscan: 1 # Fixes missing HUD. + textureInsideRT: 1 # Fixes post shuffle effect. + halfPixelOffset: 2 # Fixes boxes around shuffle effect pages. SLUS-20733: name: "Castlevania - Lament of Innocence" region: "NTSC-U" @@ -65701,7 +65821,7 @@ SLUS-20840: region: "NTSC-U" compat: 5 gsHWFixes: - textureInsideRT: 1 # Fixes flashing and some models still very broken in hardware mode. + textureInsideRT: 1 # Fixes colors. SLUS-20841: name: "NFL Street" region: "NTSC-U" @@ -65955,6 +66075,8 @@ SLUS-20882: gsHWFixes: texturePreloading: 1 # Performs much better with partial preload. autoFlush: 1 # Fixes bloom rendering. + textureInsideRT: 1 # Fixes post processing. + halfPixelOffset: 5 # Fixes alignment of shuffles and post processing. roundModes: vu1RoundMode: 0 # Fixes missing light cones curtains and certain effects. speedHacks: @@ -66284,9 +66406,8 @@ SLUS-20934: compat: 5 gsHWFixes: alignSprite: 1 # Fixes FMV lines. - cpuSpriteRenderBW: 2 # Prevents Death By VRAM explosion. - cpuSpriteRenderLevel: 2 # Needed for above. - getSkipCount: "GSC_DeathByDegreesTekkenNinaWilliams" + halfPixelOffset: 5 # Fixes alignment of shuffles and post processing. + textureInsideRT: 1 # Fixes post shuffles. SLUS-20935: name: "IHRA Professional Drag Racing 2005" region: "NTSC-U" @@ -66720,9 +66841,9 @@ SLUS-21006: recommendedBlendingLevel: 4 # Improves banding and effect emulation. autoFlush: 1 # Fixes light bloom intensity. estimateTextureRegion: 1 # Improves performance and reduces hash cache size. - halfPixelOffset: 4 # Aligns post effects. + halfPixelOffset: 2 # Aligns post effects. nativeScaling: 1 # Fixes post effects. - getSkipCount: "GSC_GiTS" + textureInsideRT: 1 # Fixes post shuffles. patches: default: content: |- @@ -66887,6 +67008,8 @@ SLUS-21029: gsHWFixes: halfPixelOffset: 2 # Aligns post bloom. nativeScaling: 2 # Fixes light blooms. + textureInsideRT: 1 # Improves performance. + preloadFrameData: 1 # Fixes light trails. getSkipCount: "GSC_MidnightClub3" patches: 0DD3417A: @@ -66950,7 +67073,7 @@ SLUS-21037: autoFlush: 1 # Fixes the post processing positioning. textureInsideRT: 1 # Fixes post processing. nativeScaling: 2 # Fixes post processing smoothness and position. - halfPixelOffset: 4 # Makes bloom alignment slightly less awful. + halfPixelOffset: 5 # Makes bloom alignment slightly less awful. SLUS-21038: name: "Pinball Hall of Fame - The Gottlieb Collection" region: "NTSC-U" @@ -66961,6 +67084,10 @@ SLUS-21039: compat: 5 gsHWFixes: alignSprite: 1 # Fixes lighting and vertical lines, also works with normal vertex. + roundSprite: 1 # Fixes misaligned map. + halfPixelOffset: 2 # Fixes depth of field alignment. + nativeScaling: 1 # Fixes depth of field. + textureInsideRT: 1 # Fixes shadows. SLUS-21040: name: "The Shield" name-sort: "Shield, The" @@ -67274,9 +67401,10 @@ SLUS-21099: region: "NTSC-U" compat: 4 gsHWFixes: - disablePartialInvalidation: 1 # Improves performance. halfPixelOffset: 2 # Fixes misaligned bloom effects. texturePreloading: 1 # Performs much better with partial preload. + textureInsideRT: 1 # Fixes post effects. + nativeScaling: 1 # Fixes lighting quality and positioning. SLUS-21100: name: "NCAA March Madness 2005" region: "NTSC-U" @@ -67319,6 +67447,7 @@ SLUS-21106: eeRoundMode: 0 # Fixes scene switching in intro. gsHWFixes: cpuSpriteRenderBW: 1 # Fixes textures. + cpuSpriteRenderLevel: 1 # Needed for above. preloadFrameData: 1 # Fixes numberplates and ensures above targets are valid. roundSprite: 1 # Fixes lines in some post-effects. gpuTargetCLUT: 1 # Fixes light occlusion. @@ -67335,7 +67464,7 @@ SLUS-21108: clampModes: vuClampMode: 0 # Fixes bump mapping issues gsHWFixes: - halfPixelOffset: 4 # Fixes post processing position. + halfPixelOffset: 5 # Fixes alignment of shuffles and post processing. autoFlush: 1 # Fixes sun intensity. textureInsideRT: 1 # Fixes post processing. getSkipCount: "GSC_HitmanBloodMoney" @@ -67746,10 +67875,9 @@ SLUS-21182: gsHWFixes: alignSprite: 1 # Fixes lighting and vertical lines, also works with normal vertex. roundSprite: 1 # Fixes misaligned map. - skipDrawStart: 1 # Removes large black box around player car till we properly emulate it. - skipDrawEnd: 1 # Removes large black box around player car till we properly emulate it. halfPixelOffset: 2 # Fixes depth of field alignment. nativeScaling: 1 # Fixes depth of field. + textureInsideRT: 1 # Fixes shadows. memcardFilters: - "SLUS-21182" - "SLUS-21039" # Race Driver 2 save unlocks 'Class A 4WD Track Challenge' in career early. @@ -68046,6 +68174,7 @@ SLUS-21228: halfPixelOffset: 2 # Fix text and post blur. nativeScaling: 2 # Fixes post effects. cpuSpriteRenderBW: 1 # Fixes textures. + cpuSpriteRenderLevel: 1 # Needed for above. SLUS-21229: name: "Motocross Mania 3" region: "NTSC-U" @@ -68684,6 +68813,7 @@ SLUS-21311: autoFlush: 1 # Corrects vignette to match software. estimateTextureRegion: 1 # Improves performance and reduces hash cache size. halfPixelOffset: 4 # Aligns blur more correctly to match software. + textureInsideRT: 1 # Fixes post processing effects. SLUS-21312: name: "Wallace & Gromit - The Curse of the Were-Rabbit" region: "NTSC-U" @@ -68734,6 +68864,7 @@ SLUS-21318: gsHWFixes: roundSprite: 1 # Fixes lines in sprites. cpuSpriteRenderBW: 1 # Fixes textures. + cpuSpriteRenderLevel: 1 # Needed for above. SLUS-21319: name: "Flow - Urban Dance Uprising" region: "NTSC-U" @@ -68968,6 +69099,8 @@ SLUS-21355: gsHWFixes: halfPixelOffset: 2 # Aligns post bloom. nativeScaling: 2 # Fixes light blooms. + textureInsideRT: 1 # Improves performance. + preloadFrameData: 1 # Fixes light trails. getSkipCount: "GSC_MidnightClub3" patches: 60A42FF5: @@ -69432,7 +69565,7 @@ SLUS-21419: compat: 5 gsHWFixes: autoFlush: 1 # Fixes lighting. - halfPixelOffset: 4 # Fixes ghosting. + halfPixelOffset: 5 # Fixes ghosting. nativeScaling: 2 # Fixes post processing. recommendedBlendingLevel: 4 # Fixes missing light brightness. SLUS-21420: @@ -70191,6 +70324,9 @@ SLUS-21581: name: "UEFA Champions League 2006-2007" region: "NTSC-U" compat: 5 + gsHWFixes: + cpuSpriteRenderBW: 2 # Fixes broken lights due to copying around P8 data. + cpuSpriteRenderLevel: 2 # Needed for above. SLUS-21582: name: "MVP '07 - NCAA Baseball" region: "NTSC-U" @@ -70386,8 +70522,9 @@ SLUS-21613: region: "NTSC-U" compat: 5 gsHWFixes: - getSkipCount: "GSC_Manhunt2" autoFlush: 1 # Fixes missing lights and light intensity. + textureInsideRT: 1 # Fixes post lighting. + halfPixelOffset: 2 # Fixes Post effects. SLUS-21614: name: "Star Wars - The Force Unleashed" region: "NTSC-U" @@ -70411,7 +70548,7 @@ SLUS-21615: compat: 5 gsHWFixes: roundSprite: 1 # Fixes font sizes and lines in UI. - cpuFramebufferConversion: 1 # Fixes sepia-tone flashback sequences. + textureInsideRT: 1 # Fixes sepia-tone flashback sequences. forceEvenSpritePosition: 1 # Fixes font artifacts and out-of-bound 2D textures. gpuPaletteConversion: 2 # Fixes micro-stuttering and reduces HC size. SLUS-21616: @@ -70971,6 +71108,8 @@ SLUS-21716: compat: 5 roundModes: vu0RoundMode: 0 # Fixes invisible wall collision in bedroom. + gsHWFixes: + textureInsideRT: 1 # Fixes half screen. SLUS-21717: name: "Dora the Explorer - Dora Saves the Mermaids" region: "NTSC-U" @@ -71119,6 +71258,7 @@ SLUS-21740: autoFlush: 1 # Fixes bloom intensity. halfPixelOffset: 4 # Mostly aligns post processing. nativeScaling: 1 # Fixes post processing smoothness and position. + getSkipCount: "GSC_GuitarHero" SLUS-21741: name: "Sea Monsters - A Prehistoric Adventure" region: "NTSC-U" @@ -71208,6 +71348,7 @@ SLUS-21757: compat: 5 gsHWFixes: cpuSpriteRenderBW: 1 # Fixes textures. + cpuSpriteRenderLevel: 1 # Needed for above. halfPixelOffset: 4 # Aligns post processing. autoFlush: 1 # Fixes post processing. SLUS-21758: @@ -72699,6 +72840,8 @@ SLUS-29087: eeClampMode: 3 # Characters are visible in-game. gsHWFixes: texturePreloading: 1 # Performs better with partial preload because it is slow on locations outside gameplay foremost. + textureInsideRT: 1 # Fixes post shuffle effect. + halfPixelOffset: 2 # Fixes boxes around shuffle effect pages. SLUS-29088: name: "Champions of Norrath - Realms of EverQuest [Demo]" region: "NTSC-U" @@ -72813,9 +72956,9 @@ SLUS-29123: recommendedBlendingLevel: 4 # Improves banding and effect emulation. autoFlush: 1 # Fixes light bloom intensity. estimateTextureRegion: 1 # Improves performance and reduces hash cache size. - halfPixelOffset: 4 # Aligns post effects. + halfPixelOffset: 2 # Aligns post effects. nativeScaling: 1 # Fixes post effects. - getSkipCount: "GSC_GiTS" + textureInsideRT: 1 # Fixes post shuffles. SLUS-29124: name: "Fight Club [Demo]" region: "NTSC-U" @@ -72856,7 +72999,7 @@ SLUS-29131: gsHWFixes: autoFlush: 1 # Fixes the post processing positioning. textureInsideRT: 1 # Fixes post processing. - halfPixelOffset: 4 # Aligns bloom effect. + halfPixelOffset: 5 # Aligns bloom effect. nativeScaling: 2 # Fixes post processing smoothness and position. SLUS-29132: name: "S.L.A.I. - Steel Lancer Arena International - Phantom Crash [Public Beta Vol.1.0]" @@ -73092,10 +73235,9 @@ SLUS-29178: gsHWFixes: alignSprite: 1 # Fixes lighting and vertical lines, also works with normal vertex. roundSprite: 1 # Fixes misaligned map. - skipDrawStart: 1 # Removes large black box around player car till we properly emulate it. - skipDrawEnd: 1 # Removes large black box around player car till we properly emulate it. halfPixelOffset: 2 # Fixes depth of field alignment. nativeScaling: 1 # Fixes depth of field. + textureInsideRT: 1 # Fixes shadows. SLUS-29179: name: "Sonic Riders [Demo]" region: "NTSC-U" @@ -73143,6 +73285,7 @@ SLUS-29191: vuClampMode: 0 # Fixes bump mapping issues gsHWFixes: textureInsideRT: 1 # Fixes post processing. + halfPixelOffset: 5 # Fixes alignment of shuffles and post processing in Hitman. getSkipCount: "GSC_HitmanBloodMoney" SLUS-29192: name: "Test Drive Unlimited [Public Beta Vol.1.0]" From a8750ce8adfce287e95bc07166a9e290560b6320 Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Sat, 25 Jan 2025 20:34:06 +0000 Subject: [PATCH 107/162] GS/HW: Check all overlapping pages when clearing sources --- pcsx2/GS/Renderers/HW/GSTextureCache.cpp | 46 +++++++++++++----------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index 86c50d8747..0c4919759f 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -3602,38 +3602,42 @@ void GSTextureCache::InvalidateVideoMem(const GSOffset& off, const GSVector4i& r if (!target) { + const int pages = (end_bp + ((1<<5)-1) - start_bp) >> 5; // Remove Source that have same BP as the render target (color&dss) - // rendering will dirty the copy - auto& list = m_src.m_map[bp >> 5]; - for (auto i = list.begin(); i != list.end();) + /// rendering will dirty the copy + for (int pgs = 0; pgs < pages; pgs++) { - Source* s = *i; - ++i; - - if ((GSUtil::HasSharedBits(psm, s->m_TEX0.PSM) && (bp >= start_bp && bp < end_bp)) || - (GSUtil::HasSharedBits(bp, psm, s->m_from_target_TEX0.TBP0, s->m_TEX0.PSM) && s->m_target)) - { - m_src.RemoveAt(s); - } - } - - u32 bbp = bp + bw * 0x10; - if (bw >= 16 && bbp < 16384) - { - // Detect half of the render target (fix snow engine game) - // Target Page (8KB) have always a width of 64 pixels - // Half of the Target is TBW/2 pages * 8KB / (1 block * 256B) = 0x10 - auto& list = m_src.m_map[bbp >> 5]; + auto& list = m_src.m_map[((bp >> 5) + pgs) & 0x1ff]; for (auto i = list.begin(); i != list.end();) { Source* s = *i; ++i; - if (GSUtil::HasSharedBits(bbp, psm, s->m_TEX0.TBP0, s->m_TEX0.PSM)) + if ((GSUtil::HasSharedBits(psm, s->m_TEX0.PSM) && (end_bp > s->m_TEX0.TBP0 && start_bp < s->UnwrappedEndBlock()) && !s->m_target) || + (GSUtil::HasSharedBits(bp, psm, s->m_from_target_TEX0.TBP0, s->m_TEX0.PSM) && s->m_target)) { m_src.RemoveAt(s); } } + + u32 bbp = bp + bw * 0x10; + if (bw >= 16 && bbp < 16384) + { + // Detect half of the render target (fix snow engine game) + // Target Page (8KB) have always a width of 64 pixels + // Half of the Target is TBW/2 pages * 8KB / (1 block * 256B) = 0x10 + auto& list = m_src.m_map[bbp >> 5]; + for (auto i = list.begin(); i != list.end();) + { + Source* s = *i; + ++i; + + if (GSUtil::HasSharedBits(bbp, psm, s->m_TEX0.TBP0, s->m_TEX0.PSM)) + { + m_src.RemoveAt(s); + } + } + } } } From bd4a77992e4dc824bcf7a0dce92bd527d4ec75aa Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Sat, 25 Jan 2025 20:35:32 +0000 Subject: [PATCH 108/162] GS/HW: Predict valid sizes based on repeated draws and scissor - this should be okay/limited to certain situations like Battlefield 2. Scissor isn't 100% guaranteed to be right, but it's probably better than nothing. --- pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index 5c5379572b..b5483a5958 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -1029,6 +1029,12 @@ GSVector2i GSRendererHW::GetValidSize(const GSTextureCache::Source* tex) // e.g. Burnout 3, God of War II, etc. int height = std::min(m_context->scissor.in.w, m_r.w); + // We can check if the next draw is doing the same from the next page, and assume it's a per line clear. + // Battlefield 2 does this. + int pages = ((GSLocalMemory::GetEndBlockAddress(m_cached_ctx.FRAME.Block(), m_cached_ctx.FRAME.FBW, m_cached_ctx.FRAME.PSM, m_r) + 1) - m_cached_ctx.FRAME.Block()) >> 5; + if (m_cached_ctx.FRAME.FBW > 1 && m_r.height() <= 64 && (pages % m_cached_ctx.FRAME.FBW) == 0 && m_env.CTXT[m_backed_up_ctx].FRAME.FBP == (m_cached_ctx.FRAME.FBP + pages) && NextDrawMatchesShuffle()) + height = std::max(m_context->scissor.in.w, height); + // If the draw is less than a page high, FBW=0 is the same as FBW=1. const GSLocalMemory::psm_t& frame_psm = GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM]; int width = std::min(std::max(m_cached_ctx.FRAME.FBW, 1) * 64, m_context->scissor.in.z); From 85bd46e45779edd38d74eb3f2a3413bd46d37b85 Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Sat, 25 Jan 2025 20:36:32 +0000 Subject: [PATCH 109/162] GameDB-GS/HW: Remove Battlefield 2 CRC hacks, add Tex Inside RT instead --- bin/resources/GameIndex.yaml | 46 +++++++++++++----------------- pcsx2/GS/Renderers/HW/GSHwHack.cpp | 39 ------------------------- pcsx2/GS/Renderers/HW/GSHwHack.h | 2 -- 3 files changed, 20 insertions(+), 67 deletions(-) diff --git a/bin/resources/GameIndex.yaml b/bin/resources/GameIndex.yaml index 1eaebf4503..15c17a5ae9 100644 --- a/bin/resources/GameIndex.yaml +++ b/bin/resources/GameIndex.yaml @@ -3887,9 +3887,13 @@ SCED-52899: SCED-52932: name: "Bonus Demo 8" region: "PAL-M5" + gsHWFixes: + textureInsideRT: 1 # Fixes shadows in TOCA Race Driver 2. SCED-52933: name: "Bonus Demo 8" region: "PAL-M5" + gsHWFixes: + textureInsideRT: 1 # Fixes shadows in TOCA Race Driver 2. SCED-52935: name: "SingStar Party [Demo]" region: "PAL-E" @@ -3963,6 +3967,8 @@ SCED-52997: SCED-53018: name: "Bonus Demo 8 (Geu)" region: "PAL-G" + gsHWFixes: + textureInsideRT: 1 # Fixes shadows in TOCA Race Driver 2. SCED-53043: name: "Magazine Ufficiale PlayStation 2 Speciale Platinum Italia" region: "PAL-I" @@ -13614,8 +13620,7 @@ SLED-53731: autoFlush: 1 # Post-processing. halfPixelOffset: 2 # Offset post-processing. texturePreloading: 1 # Improves performance. - getSkipCount: "GSC_Battlefield2" # Depth clear. - beforeDraw: "OI_Battlefield2" # Framebuffer copy, fixes rendering for bottom part of screen. + textureInsideRT: 1 # Fixes per line drawing. SLED-53732: name: "Spartan - Total Warrior [Demo]" region: "PAL" @@ -23615,8 +23620,7 @@ SLES-53729: autoFlush: 1 # Post-processing. halfPixelOffset: 2 # Offset post-processing. texturePreloading: 1 # Improves performance. - getSkipCount: "GSC_Battlefield2" # Depth clear. - beforeDraw: "OI_Battlefield2" # Framebuffer copy, fixes rendering for bottom part of screen. + textureInsideRT: 1 # Fixes per line drawing. SLES-53730: name: "Battlefield 2 - Modern Combat" region: "PAL-M3" @@ -23625,8 +23629,7 @@ SLES-53730: autoFlush: 1 # Post-processing. halfPixelOffset: 2 # Offset post-processing. texturePreloading: 1 # Improves performance. - getSkipCount: "GSC_Battlefield2" # Depth clear. - beforeDraw: "OI_Battlefield2" # Framebuffer copy, fixes rendering for bottom part of screen. + textureInsideRT: 1 # Fixes per line drawing. SLES-53734: name: "50 Cent - Bulletproof" region: "PAL-E" @@ -31298,13 +31301,11 @@ SLKA-25330: name: "Battlefield 2 - Modern Combat" region: "NTSC-K" gsHWFixes: - autoFlush: 2 # Post-processing. + minimumBlendingLevel: 4 # Fixes ground texture rendering. + autoFlush: 1 # Post-processing. halfPixelOffset: 2 # Offset post-processing. - texturePreloading: 1 # Spikes all over the place otherwise. - textureInsideRT: 1 # Fixes light shinging through objects. - cpuCLUTRender: 1 # Fixes light shining through objects. - getSkipCount: "GSC_Battlefield2" # Depth clear. - beforeDraw: "OI_Battlefield2" # Framebuffer copy, fixes rendering for bottom part of screen. + texturePreloading: 1 # Improves performance. + textureInsideRT: 1 # Fixes per line drawing. SLKA-25331: name: "Marc Ecko's Getting Up - Contents Under Pressure" region: "NTSC-K" @@ -32192,8 +32193,7 @@ SLPM-55034: autoFlush: 1 # Post-processing. halfPixelOffset: 2 # Offset post-processing. texturePreloading: 1 # Improves performance. - getSkipCount: "GSC_Battlefield2" # Depth clear. - beforeDraw: "OI_Battlefield2" # Framebuffer copy, fixes rendering for bottom part of screen. + textureInsideRT: 1 # Fixes per line drawing. SLPM-55035: name: "ファイトナイト ラウンド2 [EA:SY! 1980]" name-sort: "ふぁいとないと らうんど2 [EA:SY! 1980]" @@ -46304,8 +46304,7 @@ SLPM-66206: autoFlush: 1 # Post-processing. halfPixelOffset: 2 # Offset post-processing. texturePreloading: 1 # Improves performance. - getSkipCount: "GSC_Battlefield2" # Depth clear. - beforeDraw: "OI_Battlefield2" # Framebuffer copy, fixes rendering for bottom part of screen. + textureInsideRT: 1 # Fixes per line drawing. SLPM-66207: name: "どろろ [SEGA THE BEST 2800]" name-sort: "どろろ [SEGA THE BEST 2800]" @@ -49096,8 +49095,7 @@ SLPM-66651: autoFlush: 1 # Post-processing. halfPixelOffset: 2 # Offset post-processing. texturePreloading: 1 # Improves performance. - getSkipCount: "GSC_Battlefield2" # Depth clear. - beforeDraw: "OI_Battlefield2" # Framebuffer copy, fixes rendering for bottom part of screen. + textureInsideRT: 1 # Fixes per line drawing. SLPM-66652: name: "バーンアウト リベンジ [EA BEST HITS]" name-sort: "ばーんあうと りべんじ [EA BEST HITS]" @@ -66987,8 +66985,7 @@ SLUS-21026: autoFlush: 1 # Post-processing. halfPixelOffset: 2 # Offset post-processing. texturePreloading: 1 # Improves performance. - getSkipCount: "GSC_Battlefield2" # Depth clear. - beforeDraw: "OI_Battlefield2" # Framebuffer copy, fixes rendering for bottom part of screen. + textureInsideRT: 1 # Fixes per line drawing. SLUS-21027: name: "The Lord of the Rings - The Third Age" name-sort: "Lord of the Rings, The - The Third Age" @@ -72940,8 +72937,7 @@ SLUS-29117: autoFlush: 1 # Post-processing. halfPixelOffset: 2 # Offset post-processing. texturePreloading: 1 # Improves performance. - getSkipCount: "GSC_Battlefield2" # Depth clear. - beforeDraw: "OI_Battlefield2" # Framebuffer copy, fixes rendering for bottom part of screen. + textureInsideRT: 1 # Fixes per line drawing. SLUS-29118: name: "Need for Speed - Underground 2 [Demo]" region: "NTSC-U" @@ -73093,8 +73089,7 @@ SLUS-29152: autoFlush: 1 # Post-processing. halfPixelOffset: 2 # Offset post-processing. texturePreloading: 1 # Improves performance. - getSkipCount: "GSC_Battlefield2" # Depth clear. - beforeDraw: "OI_Battlefield2" # Framebuffer copy, fixes rendering for bottom part of screen. + textureInsideRT: 1 # Fixes per line drawing. SLUS-29153: name: "Burnout Revenge [Demo]" region: "NTSC-U" @@ -73209,8 +73204,7 @@ SLUS-29172: autoFlush: 1 # Post-processing. halfPixelOffset: 2 # Offset post-processing. texturePreloading: 1 # Improves performance. - getSkipCount: "GSC_Battlefield2" # Depth clear. - beforeDraw: "OI_Battlefield2" # Framebuffer copy, fixes rendering for bottom part of screen. + textureInsideRT: 1 # Fixes per line drawing. SLUS-29173: name: "The Sims 2 [Demo]" name-sort: "Sims 2, The [Demo]" diff --git a/pcsx2/GS/Renderers/HW/GSHwHack.cpp b/pcsx2/GS/Renderers/HW/GSHwHack.cpp index bb0b96ea11..a9a9fb6654 100644 --- a/pcsx2/GS/Renderers/HW/GSHwHack.cpp +++ b/pcsx2/GS/Renderers/HW/GSHwHack.cpp @@ -1142,43 +1142,6 @@ bool GSHwHack::OI_BurnoutGames(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GS return false; } -bool GSHwHack::GSC_Battlefield2(GSRendererHW& r, int& skip) -{ - if (skip == 0) - { - if (RZBP >= RFBP && RFBP >= 0x2000 && RZBP >= 0x2700 && ((RZBP - RFBP) == 0x700)) - { - skip = 7; - - GIFRegTEX0 TEX0 = {}; - TEX0.TBP0 = RFBP; - TEX0.TBW = 8; - GSTextureCache::Target* dst = g_texture_cache->LookupTarget(TEX0, r.GetTargetSize(), r.GetTextureScaleFactor(), GSTextureCache::DepthStencil); - if (dst) - { - g_gs_device->ClearDepth(dst->m_texture, 0.0f); - } - } - } - - return true; -} - -bool GSHwHack::OI_Battlefield2(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t) -{ - if (!RPRIM->TME || RFRAME.Block() > 0xD00 || RTEX0.TBP0 > 0x1D00) - return true; - - if (rt && t && RFRAME.Block() == 0 && RTEX0.TBP0 == 0x1000) - { - const GSVector4i rc(0, 0, std::min(rt->GetWidth(), t->m_texture->GetWidth()), std::min(rt->GetHeight(), t->m_texture->GetHeight())); - g_gs_device->CopyRect(t->m_texture, rt, rc, 0, 0); - } - - g_texture_cache->InvalidateTemporarySource(); - return false; -} - bool GSHwHack::OI_HauntingGround(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t) { // Haunting Ground clears two targets by doing a direct colour write at 0x3000, covering a target at 0x3380. @@ -1474,7 +1437,6 @@ const GSHwHack::Entry GSHwHack::s_get_skip_count_function CRC_F(GSC_ZettaiZetsumeiToshi2), CRC_F(GSC_BlackAndBurnoutSky), CRC_F(GSC_BlueTongueGames), - CRC_F(GSC_Battlefield2), CRC_F(GSC_NFSUndercover), CRC_F(GSC_PolyphonyDigitalGames), CRC_F(GSC_MetalGearSolid3), @@ -1500,7 +1462,6 @@ const GSHwHack::Entry GSHwHack::s_before_draw_functions[] CRC_F(OI_SonicUnleashed), CRC_F(OI_ArTonelico2), CRC_F(OI_BurnoutGames), - CRC_F(OI_Battlefield2), CRC_F(OI_HauntingGround), }; diff --git a/pcsx2/GS/Renderers/HW/GSHwHack.h b/pcsx2/GS/Renderers/HW/GSHwHack.h index d30db57d8b..68c5cbb5fc 100644 --- a/pcsx2/GS/Renderers/HW/GSHwHack.h +++ b/pcsx2/GS/Renderers/HW/GSHwHack.h @@ -25,7 +25,6 @@ public: static bool GSC_UrbanReign(GSRendererHW& r, int& skip); static bool GSC_SteambotChronicles(GSRendererHW& r, int& skip); static bool GSC_BlueTongueGames(GSRendererHW& r, int& skip); - static bool GSC_Battlefield2(GSRendererHW& r, int& skip); static bool GSC_NFSUndercover(GSRendererHW& r, int& skip); static bool GSC_PolyphonyDigitalGames(GSRendererHW& r, int& skip); static bool GSC_MetalGearSolid3(GSRendererHW& r, int& skip); @@ -37,7 +36,6 @@ public: static bool OI_SonicUnleashed(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t); static bool OI_ArTonelico2(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t); static bool OI_BurnoutGames(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t); - static bool OI_Battlefield2(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t); static bool OI_HauntingGround(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t); static bool MV_Growlanser(GSRendererHW& r); From b568e1387a160e23d97e30deb9e807668599d9bc Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Sun, 26 Jan 2025 16:35:49 +0000 Subject: [PATCH 110/162] GS/HW: Allow offsetting in to a target if full contained. --- pcsx2/GS/Renderers/HW/GSTextureCache.cpp | 32 +++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index 0c4919759f..4cf94b8632 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -1189,7 +1189,7 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const const u32* const clut = g_gs_renderer->m_mem.m_clut; GSTexture* const gpu_clut = (psm_s.pal > 0) ? g_gs_renderer->m_mem.m_clut.GetGPUTexture() : nullptr; - const SourceRegion region = SourceRegion::Create(TEX0, CLAMP); + SourceRegion region = SourceRegion::Create(TEX0, CLAMP); // Prevent everything going to rubbish if a game somehow sends a TW/TH above 10, and region isn't being used. if ((TEX0.TW > 10 && !region.HasX()) || (TEX0.TH > 10 && !region.HasY())) @@ -1754,6 +1754,13 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const } else { + + GSVector4i inside_block_boundary_rect = r; + inside_block_boundary_rect.x = (inside_block_boundary_rect.x + (psm_s.bs.x - 1)) & ~(psm_s.bs.x - 1); + inside_block_boundary_rect.y = (inside_block_boundary_rect.y + (psm_s.bs.y - 1)) & ~(psm_s.bs.y - 1); + // Round up to the nearst block boundary for lookup to avoid problems due to bilinear and inclusive rects. + inside_block_boundary_rect.z = inside_block_boundary_rect.z & ~(psm_s.bs.x - 1); + inside_block_boundary_rect.w = inside_block_boundary_rect.w & ~(psm_s.bs.y - 1); // Some games, such as Tomb Raider: Underworld, and Destroy All Humans shift the texture pointer // back behind the framebuffer, but then offset their texture coordinates to compensate. Why they // do this, I have no idea... but it's usually only a page wide/high of an offset. Thankfully, @@ -1817,6 +1824,29 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const else continue; } + // Else read it back, might be our only choice. Ridge Racer writes to the right side of 0x1a40 for headlights, then tries to access it with the base of 0x9a0 + // naturally, it misses here. But let's make sure the formats match well enough. coordinates could be too low by half a pixel when on the edge of the screen, so need a conservitive rect. + else if (bw == t->m_TEX0.TBW && GSLocalMemory::m_psm[psm].bpp == GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp && t->Inside(bp, bw, psm, inside_block_boundary_rect)) + { + if (!t->HasValidBitsForFormat(psm, req_color, req_alpha, true)) + continue; + + GIFRegCLAMP fake_CLAMP; + fake_CLAMP.WMS = CLAMP_REGION_CLAMP; + fake_CLAMP.WMT = CLAMP_REGION_CLAMP; + fake_CLAMP.MINU = 0; + fake_CLAMP.MINV = 0; + fake_CLAMP.MAXV = std::min(static_cast(1u << TEX0.TH), 1022u); + fake_CLAMP.MAXU = std::min(static_cast(1u << TEX0.TW), 1022u); + region = SourceRegion::Create(TEX0, fake_CLAMP); + + const GSVector4i custom_offset_rect = TranslateAlignedRectByPage(t, bp, psm, bw, block_boundary_rect); + x_offset = custom_offset_rect.x; + y_offset = custom_offset_rect.y; + dst = t; + tex_merge_rt = false; + found_t = true; + } } } } From 2c5ce5763a3b0b58940d40c9136ea9aa0c07c00a Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Thu, 30 Jan 2025 18:53:09 +0000 Subject: [PATCH 111/162] GS/HW: Intercept excessively large clears --- pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index b5483a5958..8ab39e677a 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -3109,8 +3109,8 @@ void GSRendererHW::Draw() // Normally we would use 1024 here to match the clear above, but The Godfather does a 1023x1023 draw instead // (very close to 1024x1024, but apparently the GS rounds down..). So, catch that here, we don't want to // create that target, because the clear isn't black, it'll hang around and never get invalidated. - const bool is_square = (t_size.y == t_size.x) && m_r.w >= 1023 && m_primitive_covers_without_gaps == NoGapsType::FullCover; - const bool is_clear = is_possible_mem_clear && is_square; + const bool is_large_rect = (t_size.y >= t_size.x) && m_r.w >= 1023 && m_primitive_covers_without_gaps == NoGapsType::FullCover; + const bool is_clear = is_possible_mem_clear && is_large_rect; // Preserve downscaled target when copying directly from a downscaled target, or it's a normal draw using a downscaled target. Clears that are drawing to the target can also preserve size. // Of course if this size is different (in width) or this is a shuffle happening, this will be bypassed. @@ -3119,7 +3119,7 @@ void GSRendererHW::Draw() rt = g_texture_cache->LookupTarget(FRAME_TEX0, t_size, ((src && src->m_scale != 1) && GSConfig.UserHacks_NativeScaling == GSNativeScaling::Normal && !possible_shuffle) ? GetTextureScaleFactor() : target_scale, GSTextureCache::RenderTarget, true, fm, false, force_preload, preserve_rt_rgb, preserve_rt_alpha, lookup_rect, possible_shuffle, is_possible_mem_clear && FRAME_TEX0.TBP0 != m_cached_ctx.ZBUF.Block(), GSConfig.UserHacks_NativeScaling != GSNativeScaling::Off && preserve_downscale_draw && is_possible_mem_clear != ClearType::NormalClear, src, ds, (no_ds || !ds) ? -1 : (m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0)); - + // Draw skipped because it was a clear and there was no target. if (!rt) { @@ -3785,6 +3785,7 @@ void GSRendererHW::Draw() // Limit to 2x the vertical height of the resolution (for double buffering) rt->UpdateValidity(update_rect, !frame_masked && (rt_update || (m_r.w <= (resolution.y * 2) && !m_texture_shuffle))); rt->UpdateDrawn(update_rect, !frame_masked && (rt_update || (m_r.w <= (resolution.y * 2) && !m_texture_shuffle))); + // Probably changing to double buffering, so invalidate any old target that was next to it. // This resolves an issue where the PCRTC will find the old target in FMV's causing flashing. // Grandia Xtreme, Onimusha Warlord. @@ -3833,7 +3834,7 @@ void GSRendererHW::Draw() const bool z_masked = m_cached_ctx.ZBUF.ZMSK; ds->UpdateValidity(m_r, !z_masked && (can_update_size || m_r.w <= (resolution.y * 2))); - ds->UpdateDrawn(m_r, !z_masked && (can_update_size || m_r.w <= (resolution.y * 2))); + ds->UpdateDrawn(m_r, !z_masked && (can_update_size || m_r.w <= (resolution.y * 2))); if (!new_rect && new_height && old_end_block != ds->m_end_block) { From 6c7eec57784f26b39bb913622e1b7ad31f23e712 Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Thu, 30 Jan 2025 18:53:29 +0000 Subject: [PATCH 112/162] GS/HW: Don't allow Tex in RT if not contained --- pcsx2/GS/Renderers/HW/GSTextureCache.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index 4cf94b8632..d9bd32a80c 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -1714,6 +1714,9 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const if (!t->HasValidBitsForFormat(psm, req_color, req_alpha, t->m_TEX0.TBW == TEX0.TBW) && !(possible_shuffle && GSLocalMemory::m_psm[psm].bpp == 16 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == 32)) continue; + if (!t->Inside(bp, bw, psm, block_boundary_rect)) + continue; + x_offset = rect.x; y_offset = rect.y; dst = t; From 14aad730de2220d745d94dd8f6f34737dc38ea31 Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Thu, 30 Jan 2025 22:54:13 +0000 Subject: [PATCH 113/162] GS: Code cleanup at the behest of Const-Man --- pcsx2/GS/Renderers/HW/GSHwHack.cpp | 2 +- pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 38 +++++++++++------------- pcsx2/GS/Renderers/HW/GSTextureCache.cpp | 8 ++--- 3 files changed, 23 insertions(+), 25 deletions(-) diff --git a/pcsx2/GS/Renderers/HW/GSHwHack.cpp b/pcsx2/GS/Renderers/HW/GSHwHack.cpp index a9a9fb6654..75b191e3d1 100644 --- a/pcsx2/GS/Renderers/HW/GSHwHack.cpp +++ b/pcsx2/GS/Renderers/HW/GSHwHack.cpp @@ -936,7 +936,7 @@ bool GSHwHack::OI_PointListPalette(GSRendererHW& r, GSTexture* rt, GSTexture* ds && r.m_cached_ctx.FRAME.FBMSK == 0 // No frame buffer masking. ) { - int mask = (r.m_vt.m_max.p.xyxy() == r.m_vt.m_min.p.xyxy()).mask(); + const int mask = (r.m_vt.m_max.p.xyxy() == r.m_vt.m_min.p.xyxy()).mask(); if (mask == 0xf) return true; diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index 8ab39e677a..0aeb5a6013 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -1031,7 +1031,7 @@ GSVector2i GSRendererHW::GetValidSize(const GSTextureCache::Source* tex) // We can check if the next draw is doing the same from the next page, and assume it's a per line clear. // Battlefield 2 does this. - int pages = ((GSLocalMemory::GetEndBlockAddress(m_cached_ctx.FRAME.Block(), m_cached_ctx.FRAME.FBW, m_cached_ctx.FRAME.PSM, m_r) + 1) - m_cached_ctx.FRAME.Block()) >> 5; + const int pages = ((GSLocalMemory::GetEndBlockAddress(m_cached_ctx.FRAME.Block(), m_cached_ctx.FRAME.FBW, m_cached_ctx.FRAME.PSM, m_r) + 1) - m_cached_ctx.FRAME.Block()) >> 5; if (m_cached_ctx.FRAME.FBW > 1 && m_r.height() <= 64 && (pages % m_cached_ctx.FRAME.FBW) == 0 && m_env.CTXT[m_backed_up_ctx].FRAME.FBP == (m_cached_ctx.FRAME.FBP + pages) && NextDrawMatchesShuffle()) height = std::max(m_context->scissor.in.w, height); @@ -3178,7 +3178,7 @@ void GSRendererHW::Draw() rt->m_TEX0.TBP0 = m_cached_ctx.FRAME.Block(); GSVector2i new_size = rt->m_unscaled_size; // Make sure to use the original format for the offset. - int new_offset = std::abs((vertical_offset / frame_psm.pgs.y) * GSLocalMemory::m_psm[rt->m_TEX0.PSM].pgs.y); + const int new_offset = std::abs((vertical_offset / frame_psm.pgs.y) * GSLocalMemory::m_psm[rt->m_TEX0.PSM].pgs.y); texture_offset = new_offset; new_size.y += new_offset; @@ -3210,11 +3210,10 @@ void GSRendererHW::Draw() if (ds && vertical_offset && (m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0) != (m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0)) { - int z_vertical_offset = ((static_cast(m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0) / 32) / std::max(rt->m_TEX0.TBW, 1U)) * GSLocalMemory::m_psm[m_cached_ctx.ZBUF.PSM].pgs.y; - int z_offset = vertical_offset; - GL_CACHE("RT in RT Z copy on draw %d z_vert_offset %d z_offset %d", s_n, z_vertical_offset, z_offset); - GSVector4i dRect = GSVector4i(0, z_offset * ds->m_scale, ds->m_unscaled_size.x * ds->m_scale, std::min(z_offset + m_r.w + 1, z_offset + ds->m_unscaled_size.y) * ds->m_scale); - int new_height = std::max(static_cast(ds->m_unscaled_size.y * ds->m_scale), dRect.w); + const int z_vertical_offset = ((static_cast(m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0) / 32) / std::max(rt->m_TEX0.TBW, 1U)) * GSLocalMemory::m_psm[m_cached_ctx.ZBUF.PSM].pgs.y; + GL_CACHE("RT in RT Z copy on draw %d z_vert_offset %d z_offset %d", s_n, z_vertical_offset, vertical_offset); + GSVector4i dRect = GSVector4i(0, vertical_offset * ds->m_scale, ds->m_unscaled_size.x * ds->m_scale, std::min(vertical_offset + m_r.w + 1, vertical_offset + ds->m_unscaled_size.y) * ds->m_scale); + const int new_height = std::max(static_cast(ds->m_unscaled_size.y * ds->m_scale), dRect.w); GSTexture* tex = g_gs_device->CreateDepthStencil(ds->m_unscaled_size.x * ds->m_scale, new_height, GSTexture::Format::DepthStencil, true); g_gs_device->StretchRect(ds->m_texture, GSVector4(0.0f, z_vertical_offset / static_cast(ds->m_unscaled_size.y), 1.0f, std::min(z_vertical_offset + m_r.w + 1, ds->m_unscaled_size.y) / static_cast(ds->m_unscaled_size.y)), tex, GSVector4(dRect), ShaderConvert::DEPTH_COPY, false); @@ -3269,11 +3268,11 @@ void GSRendererHW::Draw() { if (m_r.w > rt->m_unscaled_size.y || m_r.z > rt->m_unscaled_size.x) { - u32 new_height = std::max(m_r.w, rt->m_unscaled_size.y); - u32 new_width = std::max(m_r.z, rt->m_unscaled_size.x); + const u32 new_height = std::max(m_r.w, rt->m_unscaled_size.y); + const u32 new_width = std::max(m_r.z, rt->m_unscaled_size.x); //DevCon.Warning("Resizing texture %d x %d draw %d", rt->m_unscaled_size.x, new_height, s_n); - rt->ResizeTexture(new_height, new_height); + rt->ResizeTexture(new_width, new_height); const bool frame_masked = ((m_cached_ctx.FRAME.FBMSK & frame_psm.fmsk) == frame_psm.fmsk) || (m_cached_ctx.TEST.ATE && m_cached_ctx.TEST.ATST == ATST_NEVER && !(m_cached_ctx.TEST.AFAIL & AFAIL_FB_ONLY)); @@ -3712,9 +3711,7 @@ void GSRendererHW::Draw() // Ignore single page/0 page stuff, that's just gonna get silly else if (buffer_width > 64 && update_rect.z > buffer_width) { - float multifactor = static_cast(update_rect.z) / static_cast(buffer_width); - - update_rect.w *= multifactor; + update_rect.w *= static_cast(update_rect.z) / static_cast(buffer_width); update_rect.z = buffer_width; } @@ -4056,10 +4053,11 @@ void GSRendererHW::Draw() { const int vertical_offset = ((static_cast(m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0) / 32) / std::max(static_cast(rt->m_TEX0.TBW), 1)) * frame_psm.pgs.y; const int z_vertical_offset = ((static_cast(m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0) / 32) / std::max(rt->m_TEX0.TBW, 1U)) * GSLocalMemory::m_psm[m_cached_ctx.ZBUF.PSM].pgs.y; - const int z_offset = vertical_offset; - GL_CACHE("RT in RT Z copy back draw %d z_vert_offset %d z_offset %d", s_n, z_vertical_offset, z_offset); - GSVector4i dRect = GSVector4i(0, z_vertical_offset * ds->m_scale, ds->m_unscaled_size.x * ds->m_scale, std::min(z_vertical_offset + m_r.w + 1 - vertical_offset, ds->m_unscaled_size.y) * ds->m_scale); - g_gs_device->StretchRect(g_texture_cache->GetTemporaryZ(), GSVector4(0.0f, z_offset / static_cast(g_texture_cache->GetTemporaryZ()->GetHeight()), 1.0f, std::min(real_rect.w + 1, ds->m_unscaled_size.y + z_offset) / static_cast(g_texture_cache->GetTemporaryZ()->GetHeight())), ds->m_texture, GSVector4(dRect), ShaderConvert::DEPTH_COPY, false); + const GSVector4i dRect = GSVector4i(0, z_vertical_offset * ds->m_scale, ds->m_unscaled_size.x * ds->m_scale, std::min(z_vertical_offset + m_r.w + 1 - vertical_offset, ds->m_unscaled_size.y) * ds->m_scale); + + GL_CACHE("RT in RT Z copy back draw %d z_vert_offset %d z_offset %d", s_n, z_vertical_offset, vertical_offset); + g_gs_device->StretchRect(g_texture_cache->GetTemporaryZ(), GSVector4(0.0f, vertical_offset / static_cast(g_texture_cache->GetTemporaryZ()->GetHeight()), 1.0f, + std::min(real_rect.w + 1, ds->m_unscaled_size.y + vertical_offset) / static_cast(g_texture_cache->GetTemporaryZ()->GetHeight())), ds->m_texture, GSVector4(dRect), ShaderConvert::DEPTH_COPY, false); } } } @@ -6104,9 +6102,9 @@ __ri void GSRendererHW::HandleTextureHazards(const GSTextureCache::Target* rt, c if (m_channel_shuffle && (tex_diff || frame_diff)) { - u32 page_offset = (m_cached_ctx.TEX0.TBP0 - src_target->m_TEX0.TBP0) >> 5; - u32 vertical_offset = (page_offset / src_target->m_TEX0.TBW) * GSLocalMemory::m_psm[src_target->m_TEX0.PSM].pgs.y; - u32 horizontal_offset = (page_offset % src_target->m_TEX0.TBW) * GSLocalMemory::m_psm[src_target->m_TEX0.PSM].pgs.x; + const u32 page_offset = (m_cached_ctx.TEX0.TBP0 - src_target->m_TEX0.TBP0) >> 5; + const u32 vertical_offset = (page_offset / src_target->m_TEX0.TBW) * GSLocalMemory::m_psm[src_target->m_TEX0.PSM].pgs.y; + const u32 horizontal_offset = (page_offset % src_target->m_TEX0.TBW) * GSLocalMemory::m_psm[src_target->m_TEX0.PSM].pgs.x; copy_range.y += vertical_offset; copy_range.x += horizontal_offset; diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index d9bd32a80c..6e05ede3bf 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -379,7 +379,7 @@ GSVector4i GSTextureCache::TranslateAlignedRectByPage(u32 tbp, u32 tebp, u32 tbw if (horizontal_offset) page_count += dst_pgw - horizontal_offset; - int new_height = (page_count / dst_pgw) * dst_page_size.y; + const int new_height = (page_count / dst_pgw) * dst_page_size.y; new_rect.x = 0; new_rect.z = dst_pgw * dst_page_size.x; new_rect.y = start_page.y * dst_page_size.y; @@ -3653,7 +3653,7 @@ void GSTextureCache::InvalidateVideoMem(const GSOffset& off, const GSVector4i& r } } - u32 bbp = bp + bw * 0x10; + const u32 bbp = bp + bw * 0x10; if (bw >= 16 && bbp < 16384) { // Detect half of the render target (fix snow engine game) @@ -4223,7 +4223,7 @@ bool GSTextureCache::Move(u32 SBP, u32 SBW, u32 SPSM, int sx, int sy, u32 DBP, u dst = GetExactTarget(DBP, DBW, dpsm_s.depth ? DepthStencil : RenderTarget, DBP); } - + // Beware of the case where a game might create a larger texture by moving a bunch of chunks around. // We use dx/dy == 0 and the TBW check as a safeguard to make sure these go through to local memory. // We can also recreate the target if it's previously been created in the height cache with a valid size. @@ -7578,7 +7578,7 @@ std::shared_ptr GSTextureCache::PaletteMap::LookupPalet { // Palette is unused it = map.erase(it); // Erase element from map - // The palette object should now be gone as the shared pointer to the object in the map is deleted + // The palette object should now be gone as the shared pointer to the object in the map is deleted } else { From 19b8755c8914a78b901b3247965807ab7fb4295f Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Tue, 4 Feb 2025 02:12:18 +0000 Subject: [PATCH 114/162] GS/HW: Support RT in RT in SW renderer fallback check --- pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 17 +++++++++++++++++ pcsx2/GS/Renderers/HW/GSTextureCache.cpp | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index 0aeb5a6013..57f14578e1 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -7460,12 +7460,29 @@ bool GSRendererHW::CanUseSwPrimRender(bool no_rt, bool no_ds, bool draw_sprite_t if (level < 2 && (IsMipMapActive() || !IsOpaque())) return false; + // Middle of a shuffle, don't SW it. + if (m_split_texture_shuffle_pages) + return false; + // Make sure this isn't something we've actually rendered to (e.g. a texture shuffle). if (PRIM->TME) { GSTextureCache::Target* src_target = g_texture_cache->GetTargetWithSharedBits(m_cached_ctx.TEX0.TBP0, m_cached_ctx.TEX0.PSM); if (src_target) { + // If we also have the destination target and it's all valid data and it needs blending, then we can't SW render it as we will nuke valid information. + if (!IsOpaque()) + { + GSTextureCache::Target* dst_target = g_texture_cache->GetTargetWithSharedBits(m_cached_ctx.FRAME.Block(), m_cached_ctx.FRAME.PSM); + + if (dst_target && dst_target->m_dirty.empty() && ((!(GSUtil::GetChannelMask(m_cached_ctx.FRAME.PSM) & 0x7)) || dst_target->m_valid_rgb) && + ((!(GSUtil::GetChannelMask(m_cached_ctx.FRAME.PSM) & 0x8)) || (dst_target->m_valid_alpha_low && dst_target->m_valid_alpha_high))) + return false; + } + + if (((GSUtil::GetChannelMask(m_cached_ctx.TEX0.PSM) & 0x7) && !src_target->m_valid_rgb) || ((GSUtil::GetChannelMask(m_cached_ctx.TEX0.PSM) & 0x8) && (!src_target->m_valid_alpha_low || !src_target->m_valid_alpha_high))) + return true; + // If the EE has written over our sample area, we're fine to do this on the CPU, despite the target. if (!src_target->m_dirty.empty()) { diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index 6e05ede3bf..b097766eaf 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -4627,7 +4627,7 @@ GSTextureCache::Target* GSTextureCache::GetTargetWithSharedBits(u32 BP, u32 PSM) { Target* t = *it; const u32 t_psm = (t->HasValidAlpha()) ? t->m_TEX0.PSM & ~0x1 : t->m_TEX0.PSM; - if (GSUtil::HasSharedBits(BP, PSM, t->m_TEX0.TBP0, t_psm)) + if (GSUtil::HasSharedBits(PSM, t_psm) && (t->m_TEX0.TBP0 == BP || (GSConfig.UserHacks_TextureInsideRt >= GSTextureInRtMode::InsideTargets && t->m_TEX0.TBP0 < BP && t->UnwrappedEndBlock() > BP))) return t; } From 73595d93f315863cd8163a82b3ad6f18c46c7fe9 Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Wed, 5 Feb 2025 01:16:16 +0000 Subject: [PATCH 115/162] GS/HW: Fix some format conversion scaling problems --- pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 14 ++++++++------ pcsx2/GS/Renderers/HW/GSTextureCache.cpp | 16 ++++++++-------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index 57f14578e1..a59fd52ee0 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -2254,6 +2254,8 @@ void GSRendererHW::Draw() m_conf.scissor.w = m_conf.scissor.y + (((num_skipped_channel_shuffle_draws + 1 + (m_channel_shuffle_width - 1)) / std::max(1U, m_channel_shuffle_width)) * 32) * m_conf.cb_ps.ScaleFactor.z; if (width_pages) m_conf.scissor.z = m_conf.scissor.x + (((num_skipped_channel_shuffle_draws + 1) % std::max(1U, m_channel_shuffle_width) % std::max(1U, m_channel_shuffle_width)) * 64) * m_conf.cb_ps.ScaleFactor.z; + + m_last_rt->UpdateValidity(GSVector4i::loadh(m_last_rt->m_unscaled_size).rintersect(GSVector4i(GSVector4(m_conf.scissor) / m_conf.cb_ps.ScaleFactor.z)), true); } g_gs_device->RenderHW(m_conf); @@ -3715,13 +3717,13 @@ void GSRendererHW::Draw() update_rect.z = buffer_width; } - if (m_in_target_draw && src && m_channel_shuffle && src->m_from_target && src->m_from_target == rt && m_cached_ctx.TEX0.TBP0 == src->m_from_target->m_TEX0.TBP0) + /*if (m_in_target_draw && src && m_channel_shuffle && src->m_from_target && src->m_from_target == rt && m_cached_ctx.TEX0.TBP0 == src->m_from_target->m_TEX0.TBP0) { - new_size.y = std::max(new_size.y, static_cast((((m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0) >> 5) / rt->m_TEX0.TBW) * frame_psm.pgs.y) * 2); + new_size.y = std::max(new_size.y, static_cast((((m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0) >> 5) / rt->m_TEX0.TBW) * frame_psm.pgs.y)); GSVector4i new_valid = rt->m_valid; - new_valid.w = new_size.y; + new_valid.w = std::max(new_valid.w, static_cast((((m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0) >> 5) / rt->m_TEX0.TBW) * frame_psm.pgs.y) + frame_psm.pgs.y); rt->UpdateValidity(new_valid, true); - } + }*/ // We still need to make sure the dimensions of the targets match. // Limit new size to 2048, the GS can't address more than this so may avoid some bugs/crashes. @@ -4056,8 +4058,8 @@ void GSRendererHW::Draw() const GSVector4i dRect = GSVector4i(0, z_vertical_offset * ds->m_scale, ds->m_unscaled_size.x * ds->m_scale, std::min(z_vertical_offset + m_r.w + 1 - vertical_offset, ds->m_unscaled_size.y) * ds->m_scale); GL_CACHE("RT in RT Z copy back draw %d z_vert_offset %d z_offset %d", s_n, z_vertical_offset, vertical_offset); - g_gs_device->StretchRect(g_texture_cache->GetTemporaryZ(), GSVector4(0.0f, vertical_offset / static_cast(g_texture_cache->GetTemporaryZ()->GetHeight()), 1.0f, - std::min(real_rect.w + 1, ds->m_unscaled_size.y + vertical_offset) / static_cast(g_texture_cache->GetTemporaryZ()->GetHeight())), ds->m_texture, GSVector4(dRect), ShaderConvert::DEPTH_COPY, false); + g_gs_device->StretchRect(g_texture_cache->GetTemporaryZ(), GSVector4(0.0f, (static_cast(vertical_offset) * ds->m_scale) / static_cast(g_texture_cache->GetTemporaryZ()->GetHeight()), 1.0f, + floor(static_cast(std::min(real_rect.w + 1, ds->m_unscaled_size.y + vertical_offset)) * ds->m_scale) / static_cast(g_texture_cache->GetTemporaryZ()->GetHeight())), ds->m_texture, GSVector4(dRect), ShaderConvert::DEPTH_COPY, false); } } } diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index b097766eaf..9f5195e2cd 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -2369,15 +2369,14 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe { if (scale_down) { - if ((new_size.y * 2) < 1024) - { - new_scaled_size.y *= 2; - new_size.y *= 2; - dst->m_valid.y *= 2; - dst->m_valid.w *= 2; - } + new_scaled_size.y *= 2; + dst->m_valid.y *= 2; + dst->m_valid.w *= 2; dRect.y *= 2; dRect.w *= 2; + + new_size.y *= 2; + new_size.y = std::max(dst->m_valid.w, new_size.y); } else { @@ -2387,6 +2386,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe dRect.w /= 2; dst->m_valid.y /= 2; dst->m_valid.w /= 2; + new_size.y = std::max(dst->m_valid.w, new_size.y); } } if (!is_shuffle) @@ -2627,7 +2627,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe dst->m_valid = dst_match->m_valid; dst->m_valid_alpha_low = dst_match->m_valid_alpha_low; //&& psm_s.trbpp != 24; dst->m_valid_alpha_high = dst_match->m_valid_alpha_high; //&& psm_s.trbpp != 24; - dst->m_valid_rgb = dst_match->m_valid_rgb; + dst->m_valid_rgb = dst_match->m_valid_rgb && (dst->m_TEX0.TBW == TEX0.TBW || min_rect.w <= GSLocalMemory::m_psm[dst_match->m_TEX0.PSM].pgs.y); dst->m_was_dst_matched = true; dst_match->m_was_dst_matched = true; dst_match->m_valid_rgb = preserve_rgb; From 3f0250957b7289478b7f4536d28fd3a93a90e02b Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Thu, 6 Feb 2025 04:28:29 +0000 Subject: [PATCH 116/162] GS/HW: Fix more regressions with RT in RT --- pcsx2/GS/Renderers/HW/GSHwHack.cpp | 20 +++- pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 113 ++++++++++++++++---- pcsx2/GS/Renderers/HW/GSRendererHW.h | 2 + pcsx2/GS/Renderers/HW/GSTextureCache.cpp | 129 +++++++++++++++++------ 4 files changed, 209 insertions(+), 55 deletions(-) diff --git a/pcsx2/GS/Renderers/HW/GSHwHack.cpp b/pcsx2/GS/Renderers/HW/GSHwHack.cpp index 75b191e3d1..8341864503 100644 --- a/pcsx2/GS/Renderers/HW/GSHwHack.cpp +++ b/pcsx2/GS/Renderers/HW/GSHwHack.cpp @@ -232,7 +232,7 @@ bool GSHwHack::GSC_Tekken5(GSRendererHW& r, int& skip) else { // Fixes the alignment of the two halves for the heat haze on the temple stage. - for (int i = 0; i < r.m_index.tail; i+=2) + for (u32 i = 0; i < r.m_index.tail; i+=2) { v[i].XYZ.Y -= 0x8; } @@ -1060,12 +1060,27 @@ bool GSHwHack::OI_SonicUnleashed(GSRendererHW& r, GSTexture* rt, GSTexture* ds, return true; const GSVector2i src_size(src->m_texture->GetSize()); + + GSTextureCache::Target* rt_again = g_texture_cache->LookupTarget(Frame, src_size, src->m_scale, GSTextureCache::RenderTarget); + if ((rt_again->m_TEX0.PSM & 0x3) == PSMCT16) + { + GSVector4i dRect = rt_again->m_valid; + + dRect = GSVector4i(GSVector4(GSVector4i::loadh(rt_again->m_unscaled_size)) * rt_again->m_scale); + dRect.y /= 2; + dRect.w /= 2; + rt_again->m_valid.y /= 2; + rt_again->m_valid.w /= 2; + rt_again->m_TEX0.PSM = PSMCT32; + rt_again->ResizeTexture(rt_again->m_unscaled_size.x, rt_again->m_unscaled_size.y / 2, true, true, dRect, false); + rt = rt_again->m_texture; + } + GSVector2i rt_size(rt->GetSize()); // This is awful, but so is the CRC hack... it's a texture shuffle split horizontally instead of vertically. if (rt_size.x < src_size.x || rt_size.y < src_size.y) { - GSTextureCache::Target* rt_again = g_texture_cache->LookupTarget(Frame, src_size, src->m_scale, GSTextureCache::RenderTarget); if (rt_again->m_unscaled_size.x < src->m_unscaled_size.x || rt_again->m_unscaled_size.y < src->m_unscaled_size.y) { GSVector2i new_size = GSVector2i(std::max(rt_again->m_unscaled_size.x, src->m_unscaled_size.x), @@ -1076,6 +1091,7 @@ bool GSHwHack::OI_SonicUnleashed(GSRendererHW& r, GSTexture* rt, GSTexture* ds, rt_again->UpdateDrawn(GSVector4i::loadh(new_size)); } } + const GSVector2i copy_size(std::min(rt_size.x, src_size.x), std::min(rt_size.y, src_size.y)); diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index a59fd52ee0..bab2a76693 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -762,7 +762,7 @@ void GSRendererHW::ConvertSpriteTextureShuffle(u32& process_rg, u32& process_ba, // Special case used in Call of Duty - World at War where it doubles the height and halves the width, but the height is double doubled. // Check the height of the original texture, if it's half of the draw height, then make it wide instead. if (half_bottom_uv && tex->m_from_target && m_cached_ctx.TEX0.TBW == m_cached_ctx.FRAME.FBW && - tex->m_from_target->m_TEX0.TBW == (m_cached_ctx.TEX0.TBW * 2) && (m_cached_ctx.TEX0.TBW * 64) == floor(m_vt.m_max.t.x)) + tex->m_from_target->m_TEX0.TBW == (m_cached_ctx.TEX0.TBW * 2) && (m_cached_ctx.TEX0.TBW * 64) == floor(m_vt.m_max.t.x) && m_vt.m_max.t.y > tex->m_from_target->m_valid.w) { m_r.z *= 2; m_r.w /= 2; @@ -1029,14 +1029,15 @@ GSVector2i GSRendererHW::GetValidSize(const GSTextureCache::Source* tex) // e.g. Burnout 3, God of War II, etc. int height = std::min(m_context->scissor.in.w, m_r.w); + const GSLocalMemory::psm_t& frame_psm = GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM]; // We can check if the next draw is doing the same from the next page, and assume it's a per line clear. // Battlefield 2 does this. const int pages = ((GSLocalMemory::GetEndBlockAddress(m_cached_ctx.FRAME.Block(), m_cached_ctx.FRAME.FBW, m_cached_ctx.FRAME.PSM, m_r) + 1) - m_cached_ctx.FRAME.Block()) >> 5; - if (m_cached_ctx.FRAME.FBW > 1 && m_r.height() <= 64 && (pages % m_cached_ctx.FRAME.FBW) == 0 && m_env.CTXT[m_backed_up_ctx].FRAME.FBP == (m_cached_ctx.FRAME.FBP + pages) && NextDrawMatchesShuffle()) + if (m_cached_ctx.FRAME.FBW > 1 && m_r.height() == frame_psm.pgs.y && (pages % m_cached_ctx.FRAME.FBW) == 0 && m_env.CTXT[m_backed_up_ctx].FRAME.FBP == (m_cached_ctx.FRAME.FBP + pages) && + !IsPossibleChannelShuffle() && NextDrawMatchesShuffle()) height = std::max(m_context->scissor.in.w, height); // If the draw is less than a page high, FBW=0 is the same as FBW=1. - const GSLocalMemory::psm_t& frame_psm = GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM]; int width = std::min(std::max(m_cached_ctx.FRAME.FBW, 1) * 64, m_context->scissor.in.z); if (m_cached_ctx.FRAME.FBW == 0 && m_r.w > frame_psm.pgs.y) { @@ -2250,13 +2251,17 @@ void GSRendererHW::Draw() if (!m_full_screen_shuffle) { - const u32 width_pages = ((num_skipped_channel_shuffle_draws + 1) % std::max(1U, m_channel_shuffle_width) % std::max(1U, m_channel_shuffle_width)) * 64;; + const u32 width_pages = ((num_skipped_channel_shuffle_draws + 1) % std::max(1U, m_channel_shuffle_width) % std::max(1U, m_channel_shuffle_width)) * 64; + ; m_conf.scissor.w = m_conf.scissor.y + (((num_skipped_channel_shuffle_draws + 1 + (m_channel_shuffle_width - 1)) / std::max(1U, m_channel_shuffle_width)) * 32) * m_conf.cb_ps.ScaleFactor.z; if (width_pages) m_conf.scissor.z = m_conf.scissor.x + (((num_skipped_channel_shuffle_draws + 1) % std::max(1U, m_channel_shuffle_width) % std::max(1U, m_channel_shuffle_width)) * 64) * m_conf.cb_ps.ScaleFactor.z; m_last_rt->UpdateValidity(GSVector4i::loadh(m_last_rt->m_unscaled_size).rintersect(GSVector4i(GSVector4(m_conf.scissor) / m_conf.cb_ps.ScaleFactor.z)), true); } + else + m_last_rt->UpdateValidity(m_channel_shuffle_src_valid); + g_gs_device->RenderHW(m_conf); if (GSConfig.DumpGSData) @@ -2692,6 +2697,7 @@ void GSRendererHW::Draw() GSTextureCache::Source* src = nullptr; TextureMinMaxResult tmm; bool possible_shuffle = false; + bool draw_uses_target = false; // Disable texture mapping if the blend is black and using alpha from vertex. if (m_process_texture) { @@ -2811,7 +2817,7 @@ void GSRendererHW::Draw() if (!no_rt && GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].bpp == 16 && GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].bpp >= 16 && (m_vt.m_primclass == GS_SPRITE_CLASS || (m_vt.m_primclass == GS_TRIANGLE_CLASS && (m_index.tail % 6) == 0 && TrianglesAreQuads(true) && m_index.tail > 6))) { - if (!shuffle_target && GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].bpp == 16) + if (GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].bpp == 16) { const GSVertex* v = &m_vertex.buff[0]; @@ -2834,7 +2840,8 @@ void GSRendererHW::Draw() shuffle_target = shuffle_coords && (((draw_width & 7) == 0 && std::abs(draw_width - read_width) <= 1) || m_skip > 50); } - if (!shuffle_target) + // It's possible it's writing to an old 32bit target, but is actually just a 16bit copy, so let's make sure it's actually using a mask. + if (!shuffle_target && m_cached_ctx.FRAME.FBMSK) { // FBW is going to be wrong for channel shuffling into a new target, so take it from the source. FRAME_TEX0.U64 = 0; @@ -2878,6 +2885,17 @@ void GSRendererHW::Draw() return; } + + + const u32 draw_end = GSLocalMemory::GetEndBlockAddress(m_cached_ctx.FRAME.Block(), m_cached_ctx.FRAME.FBW, m_cached_ctx.FRAME.PSM, m_r) + 1; + const u32 draw_start = GSLocalMemory::GetStartBlockAddress(m_cached_ctx.FRAME.Block(), m_cached_ctx.FRAME.FBW, m_cached_ctx.FRAME.PSM, m_r); + draw_uses_target = src->m_from_target && ((src->m_from_target_TEX0.TBP0 <= draw_start && + src->m_from_target->UnwrappedEndBlock() > m_cached_ctx.FRAME.Block()) || + (m_cached_ctx.FRAME.Block() < src->m_from_target_TEX0.TBP0 && draw_end > src->m_from_target_TEX0.TBP0)); + + if (possible_shuffle && GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].bpp != 16) + possible_shuffle &= draw_uses_target; + possible_shuffle &= src && (src->m_from_target != nullptr || (m_skip && possible_shuffle)); // We don't know the alpha range of direct sources when we first tried to optimize the alpha test. // Moving the texture lookup before the ATST optimization complicates things a lot, so instead, @@ -3061,7 +3079,8 @@ void GSRendererHW::Draw() if (!no_rt) { - possible_shuffle |= draw_sprite_tex && m_primitive_covers_without_gaps != NoGapsType::FullCover && (((src && src->m_target && src->m_from_target && src->m_from_target->m_32_bits_fmt) && GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].bpp == 16) || + possible_shuffle |= draw_sprite_tex && m_primitive_covers_without_gaps != NoGapsType::FullCover && (((src && src->m_target && src->m_from_target && src->m_from_target->m_32_bits_fmt) && + (GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].bpp == 16 || draw_uses_target) && GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].bpp == 16) || IsPossibleChannelShuffle()); const bool possible_horizontal_texture_shuffle = possible_shuffle && src && src->m_from_target && m_r.w <= src->m_from_target->m_valid.w && m_r.z > src->m_from_target->m_valid.z && m_cached_ctx.FRAME.FBW > src->m_from_target_TEX0.TBW; @@ -3277,7 +3296,7 @@ void GSRendererHW::Draw() rt->ResizeTexture(new_width, new_height); const bool frame_masked = ((m_cached_ctx.FRAME.FBMSK & frame_psm.fmsk) == frame_psm.fmsk) || (m_cached_ctx.TEST.ATE && m_cached_ctx.TEST.ATST == ATST_NEVER && !(m_cached_ctx.TEST.AFAIL & AFAIL_FB_ONLY)); - + rt->UpdateValidity(m_r, !frame_masked); rt->UpdateDrawn(m_r, !frame_masked); } @@ -3294,6 +3313,14 @@ void GSRendererHW::Draw() target_scale = rt->GetScale(); + if (ds && ds->m_scale != target_scale) + { + const GSVector2i unscaled_size(ds->m_unscaled_size.x, ds->m_unscaled_size.y); + ds->ResizeTexture(ds->m_unscaled_size.x * target_scale, ds->m_unscaled_size.y * target_scale, true); + // Slightly abusing the texture resize. + ds->m_scale = target_scale; + ds->m_unscaled_size = unscaled_size; + } // The target might have previously been a C32 format with valid alpha. If we're switching to C24, we need to preserve it. preserve_rt_alpha |= (GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].trbpp == 24 && rt->HasValidAlpha()); preserve_rt_color = preserve_rt_rgb || preserve_rt_alpha; @@ -3392,11 +3419,6 @@ void GSRendererHW::Draw() const int first_x = (((v[0].XYZ.X - m_context->XYOFFSET.OFX) + 8) >> 4) - horizontal_offset; const int first_u = PRIM->FST ? ((v[0].U + 8) >> 4) : static_cast(((1 << m_cached_ctx.TEX0.TW) * (v[0].ST.S / v[1].RGBAQ.Q)) + 0.5f); const bool shuffle_coords = (first_x ^ first_u) & 8; - const u32 draw_end = GSLocalMemory::GetEndBlockAddress(m_cached_ctx.FRAME.Block(), m_cached_ctx.FRAME.FBW, m_cached_ctx.FRAME.PSM, m_r) + 1; - const u32 draw_start = GSLocalMemory::GetStartBlockAddress(m_cached_ctx.FRAME.Block(), m_cached_ctx.FRAME.FBW, m_cached_ctx.FRAME.PSM, m_r); - const bool draw_uses_target = src->m_from_target && ((src->m_from_target_TEX0.TBP0 <= draw_start && - src->m_from_target->UnwrappedEndBlock() > m_cached_ctx.FRAME.Block()) || - (m_cached_ctx.FRAME.Block() < src->m_from_target_TEX0.TBP0 && draw_end > src->m_from_target_TEX0.TBP0)); // copy of a 16bit source in to this target, make sure it's opaque and not bilinear to reduce false positives. m_copy_16bit_to_target_shuffle = m_cached_ctx.TEX0.TBP0 != m_cached_ctx.FRAME.Block() && rt->m_32_bits_fmt == true && IsOpaque() @@ -3866,6 +3888,34 @@ void GSRendererHW::Draw() ds->ResizeTexture(new_w, new_h); } + // Hitman Contracts double duties the framebuffer it's messing with and swaps between 16bit and 32bit data, so if it's grabbed the 32bit target, we need to resize it. + if (!m_texture_shuffle && !m_channel_shuffle && rt && src && src->m_from_target == rt && src->m_target_direct && rt->m_texture == src->m_texture) + { + if (GSLocalMemory::m_psm[src->m_from_target_TEX0.PSM].bpp != (GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].bpp)) + { + GSVector2i new_size = src->m_from_target->m_unscaled_size; + + if (GSLocalMemory::m_psm[src->m_from_target->m_TEX0.PSM].bpp == 32) + new_size.y *= 2; + else + new_size.y /= 2; + + const GSVector4i dRect = GSVector4i(GSVector4(GSVector4i(0, 0, new_size.x, new_size.y)) * rt->m_scale); + const GSVector2i old_unscaled = rt->m_unscaled_size; + rt->ResizeTexture(new_size.x, new_size.y, false, true, dRect, true); + + if (GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].bpp >= 16) + { + GSTexture* new_tex = rt->m_texture; + rt->m_texture = src->m_texture; + rt->m_unscaled_size = old_unscaled; + src->m_target_direct = false; + src->m_shared_texture = false; + src->m_texture = new_tex; + src->m_unscaled_size = new_size; + } + } + } bool skip_draw = false; if (!GSConfig.UserHacks_DisableSafeFeatures && is_possible_mem_clear) skip_draw = TryTargetClear(rt, ds, preserve_rt_color, preserve_depth); @@ -3877,7 +3927,7 @@ void GSRendererHW::Draw() { const u32 alpha = m_cached_ctx.FRAME.FBMSK >> 24; const u32 alpha_mask = GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].fmsk >> 24; - rt->Update(m_texture_shuffle || (alpha != 0 && (alpha & alpha_mask) != alpha_mask) || (!alpha && GetAlphaMinMax().max > 128)); + rt->Update(m_texture_shuffle || (alpha != 0 && (alpha & alpha_mask) != alpha_mask) || (!alpha && (GetAlphaMinMax().max | (m_context->FBA.FBA << 7)) > 128)); } else rt->m_age = 0; @@ -4735,6 +4785,7 @@ __ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool const GSLocalMemory::psm_t frame_psm = GSLocalMemory::m_psm[m_context->FRAME.PSM]; m_full_screen_shuffle = (m_r.height() > frame_psm.pgs.y) || (m_r.width() > frame_psm.pgs.x) || GSConfig.UserHacks_TextureInsideRt == GSTextureInRtMode::Disabled; + m_channel_shuffle_src_valid = src->m_valid; if (GSConfig.UserHacks_TextureInsideRt == GSTextureInRtMode::Disabled || (!m_in_target_draw && IsPageCopy()) || m_conf.ps.urban_chaos_hle || m_conf.ps.tales_of_abyss_hle) { GSVertex* s = &m_vertex.buff[0]; @@ -4752,7 +4803,13 @@ __ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool // We need to count the pages that get shuffled to, some games (like Hitman Blood Money dialogue blur effects) only do half the screen. if (!m_full_screen_shuffle && !m_conf.ps.urban_chaos_hle && !m_conf.ps.tales_of_abyss_hle && src) + { + // We've probably gotten a fake number, so just reset it, it'll be updated again later. + if (rt->m_last_draw >= s_n) + rt->ResizeValidity(GSVector4i::zero()); + m_channel_shuffle_width = src->m_TEX0.TBW; + } } else { @@ -4787,14 +4844,23 @@ __ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool s[0].XYZ.Y = static_cast(m_context->XYOFFSET.OFY + (m_r.y << 4)); s[1].XYZ.Y = static_cast(m_context->XYOFFSET.OFY + (m_r.w << 4)); - const GSLocalMemory::psm_t tex_psm = GSLocalMemory::m_psm[m_context->TEX0.PSM]; - const u32 tex_page_offset = (m_vt.m_min.t.x / tex_psm.pgs.x) + (m_vt.m_min.t.y / tex_psm.pgs.y); - s[0].U = m_r.x << 4; s[1].U = m_r.z << 4; s[0].V = m_r.y << 4; s[1].V = m_r.w << 4; m_last_channel_shuffle_fbmsk = 0xFFFFFFFF; + + // If we're doing per page copying, then set the valid 1 frame ahead if we're continuing, as this will save the target lookup making a new target for the new row. + u32 frame_offset = m_cached_ctx.FRAME.Block() + (IsPageCopy() ? 0x20 : 0); + GSVector4i new_valid = rt->m_valid; + int offset_height = static_cast((((frame_offset - rt->m_TEX0.TBP0) >> 5) / rt->m_TEX0.TBW) * frame_psm.pgs.y) + frame_psm.pgs.y; + + // This is an annoying case where the draw is offset to draw on the right hand side of a texture (Hitman Blood Money pause screen). + if (!IsPageCopy() && NextDrawMatchesShuffle() && ((frame_offset >> 5) + 1) % rt->m_TEX0.TBW == 0) + offset_height += frame_psm.pgs.y; + + new_valid.w = std::max(new_valid.w, offset_height); + rt->UpdateValidity(new_valid, true); } m_vertex.head = m_vertex.tail = m_vertex.next = 2; @@ -6464,6 +6530,8 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta m_conf.ps.scanmsk = env.SCANMSK.MSK; m_conf.rt = rt ? rt->m_texture : nullptr; m_conf.ds = ds ? (g_texture_cache->GetTemporaryZ() ? g_texture_cache->GetTemporaryZ() : ds->m_texture) : nullptr; + + pxAssert(!ds || !rt || (ds->m_texture->GetSize().x == rt->m_texture->GetSize().x && ds->m_texture->GetSize().y == rt->m_texture->GetSize().y)); // Z setup has to come before channel shuffle EmulateZbuffer(ds); @@ -7482,7 +7550,16 @@ bool GSRendererHW::CanUseSwPrimRender(bool no_rt, bool no_ds, bool draw_sprite_t return false; } - if (((GSUtil::GetChannelMask(m_cached_ctx.TEX0.PSM) & 0x7) && !src_target->m_valid_rgb) || ((GSUtil::GetChannelMask(m_cached_ctx.TEX0.PSM) & 0x8) && (!src_target->m_valid_alpha_low || !src_target->m_valid_alpha_high))) + const bool need_aem_color = GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].trbpp <= 24 && GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].pal == 0 && ((PRIM->ABE && m_context->ALPHA.C == 0) || IsDiscardingDstAlpha()) && m_draw_env->TEXA.AEM; + const u32 color_mask = (m_vt.m_max.c > GSVector4i::zero()).mask(); + const bool texture_function_color = m_cached_ctx.TEX0.TFX == TFX_DECAL || (color_mask & 0xFFF) || (m_cached_ctx.TEX0.TFX > TFX_DECAL && (color_mask & 0xF000)); + const bool texture_function_alpha = m_cached_ctx.TEX0.TFX != TFX_MODULATE || (color_mask & 0xF000); + const u32 fm_mask = GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].fmsk; + const bool req_color = (texture_function_color && (!PRIM->ABE || (PRIM->ABE && IsUsingCsInBlend())) && (m_cached_ctx.FRAME.FBMSK & (fm_mask & 0x00FFFFFF)) != (fm_mask & 0x00FFFFFF)) || need_aem_color; + const bool alpha_used = (GSUtil::GetChannelMask(m_context->TEX0.PSM) == 0x8 || (m_context->TEX0.TCC && texture_function_alpha)) && ((PRIM->ABE && IsUsingAsInBlend()) || (m_cached_ctx.TEST.ATE && m_cached_ctx.TEST.ATST > ATST_ALWAYS) || (m_cached_ctx.FRAME.FBMSK & (fm_mask & 0xFF000000)) != (fm_mask & 0xFF000000)); + const bool req_alpha = (GSUtil::GetChannelMask(m_context->TEX0.PSM) & 0x8) && alpha_used; + + if ((req_color && !src_target->m_valid_rgb) || (req_alpha && (!src_target->m_valid_alpha_low || !src_target->m_valid_alpha_high))) return true; // If the EE has written over our sample area, we're fine to do this on the CPU, despite the target. diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.h b/pcsx2/GS/Renderers/HW/GSRendererHW.h index aa5fb49828..047f41a87f 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.h +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.h @@ -177,6 +177,7 @@ private: u32 m_last_channel_shuffle_tbp = 0; u32 m_last_channel_shuffle_end_block = 0; u32 m_channel_shuffle_width = 0; + GSVector4i m_channel_shuffle_src_valid = GSVector4i::zero(); bool m_full_screen_shuffle = false; GSTextureCache::Target* m_last_rt; @@ -206,6 +207,7 @@ public: __fi static GSRendererHW* GetInstance() { return static_cast(g_gs_renderer.get()); } __fi HWCachedCtx* GetCachedCtx() { return &m_cached_ctx; } + __fi u32 GetLastChannelShuffleFBP() { return m_last_channel_shuffle_fbp; } void Destroy() override; void UpdateRenderFixes() override; diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index 9f5195e2cd..0c875b330e 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -276,8 +276,6 @@ GSVector4i GSTextureCache::TranslateAlignedRectByPage(u32 tbp, u32 tebp, u32 tbw if (!(s_psm.bpp == t_psm.bpp)) { - const int src_bpp = s_psm.bpp; - if (block_offset) in_rect = in_rect.ralign(s_psm.bs); else @@ -406,6 +404,21 @@ GSVector4i GSTextureCache::TranslateAlignedRectByPage(u32 tbp, u32 tebp, u32 tbw new_rect = in_rect + offset_rect.xyxy(); } + if (new_rect.z > dst_bw) + { + if (new_rect.x >= dst_bw) + { + new_rect.x -= dst_bw; + new_rect.z -= dst_bw; + new_rect.y += t_psm.pgs.y; + new_rect.w += t_psm.pgs.y; + } + else + { + new_rect.z = (dst_pgw * dst_page_size.x); + new_rect.w += dst_page_size.y; + } + } return new_rect; } @@ -1048,7 +1061,6 @@ GSTextureCache::Source* GSTextureCache::LookupDepthSource(const bool is_depth, c } else if (!dst && bp >= t->m_TEX0.TBP0 && bp < t->m_end_block) { - const GSVector2i page_size = GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs; const bool can_translate = CanTranslate(bp, TEX0.TBW, psm, r, t->m_TEX0.TBP0, t->m_TEX0.PSM, t->m_TEX0.TBW); const bool swizzle_match = psm_s.depth == GSLocalMemory::m_psm[t->m_TEX0.PSM].depth; GSVector4i new_rect = block_boundary_rect; @@ -1399,7 +1411,7 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const { for (auto& dirty : t->m_dirty) { - GSVector4i dirty_rect = dirty.GetDirtyRect(t->m_TEX0, true); + GSVector4i dirty_rect = dirty.GetDirtyRect(t->m_TEX0, t->m_TEX0.PSM != dirty.psm); if (!dirty_rect.rintersect(new_rect).rempty()) { rect_clean = false; @@ -1626,8 +1638,10 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const continue; } // Keep note that 2 bw is basically 1 normal page, as bw is in 64 pixels, and 8bit pages are 128 pixels wide, aka 2 bw. - else if (!possible_shuffle && (GSLocalMemory::m_psm[color_psm].bpp == 8 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == 32 && - !((t->m_TEX0.TBW == (bw / 2)) || (((bw + 1) / 2) <= t->m_TEX0.TBW && (block_boundary_rect.w <= GSLocalMemory::m_psm[psm].pgs.y))))) + // Also check for 4HH/HL and 8H which use the alpha channel, if the page order is wrong this can cause problems as well (Jak X font). + else if (!possible_shuffle && GSLocalMemory::m_psm[psm].trbpp <= 8 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == 32 && + (!(block_boundary_rect.w <= GSLocalMemory::m_psm[psm].pgs.y && ((GSLocalMemory::m_psm[psm].bpp == 32) ? bw : ((bw + 1) / 2)) <= t->m_TEX0.TBW) && + !(((GSLocalMemory::m_psm[psm].bpp == 32) ? bw : ((bw + 1) / 2)) == t->m_TEX0.TBW))) { DevCon.Warning("BP %x - 8bit bad match for target bp %x bw %d src %d format %d", bp, t->m_TEX0.TBP0, t->m_TEX0.TBW, bw, t->m_TEX0.PSM); continue; @@ -2080,7 +2094,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe // Dragon Quest 8 reuses a render-target sized buffer as a single-page buffer, without clearing it. But, // it does dirty it by writing over the 64x64 region. So while we can't use this heuristic for tossing // targets at BW=1 because it breaks other games, we can when the *new* buffer area is completely dirty. - if (!preserve_rgb && !preserve_alpha && TEX0.TBW != t->m_TEX0.TBW) + if (((!preserve_rgb && !preserve_alpha) || (t->m_was_dst_matched && fbmask == 0xffffff)) && TEX0.TBW != t->m_TEX0.TBW) { // Old targets or shrunk targets where Y draw height goes outside the page. if (TEX0.TBW > 1 && (t->m_age >= 1 || (type == RenderTarget && draw_rect.w > GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y && TEX0.TBW < t->m_TEX0.TBW))) @@ -2160,8 +2174,19 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe { const GSLocalMemory::psm_t& s_psm = GSLocalMemory::m_psm[TEX0.PSM]; + // I know what you're thinking, and I hate the guy who wrote it too (me). Project Snowblind, Tomb Raider etc decide to offset where they're drawing using a channel shuffle, and this gets messy, so best just to kill the old target. + if (is_shuffle && src->m_TEX0.PSM == PSMT8 && GSRendererHW::GetInstance()->m_context->FRAME.FBW == 1 && t->m_last_draw != (GSState::s_n - 1) && src && src->m_from_target && src->m_from_target->m_TEX0.TBP0 == src->m_TEX0.TBP0 && widthpage_offset && src->m_from_target != t) + { + GL_INS("TC: Deleting RT BP 0x%x BW %d PSM %s offset overwrite shuffle", t->m_TEX0.TBP0, t->m_TEX0.TBW, psm_str(t->m_TEX0.PSM)); + InvalidateSourcesFromTarget(t); + i = list.erase(i); + delete t; + + continue; + } + if (!is_shuffle && (!GSUtil::HasSameSwizzleBits(t->m_TEX0.PSM, TEX0.PSM) || - (widthpage_offset % std::max(t->m_TEX0.TBW, 1U)) != 0 && ((widthpage_offset + (min_rect.width() + (s_psm.pgs.x - 1)) / s_psm.pgs.x)) > t->m_TEX0.TBW)) + ((widthpage_offset % std::max(t->m_TEX0.TBW, 1U)) != 0 && ((widthpage_offset + (min_rect.width() + (s_psm.pgs.x - 1)) / s_psm.pgs.x)) > t->m_TEX0.TBW))) { GL_INS("TC: Deleting RT BP 0x%x BW %d PSM %s due to change in target", t->m_TEX0.TBP0, t->m_TEX0.TBW, psm_str(t->m_TEX0.PSM)); InvalidateSourcesFromTarget(t); @@ -2172,7 +2197,6 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe } else if (t->m_dirty.empty()) { - if (TEX0.TBW == t->m_TEX0.TBW && !is_shuffle && widthpage_offset == 0 && ((min_rect.w + 63)/ 64) > 1) { // Beyond Good and Evil does this awful thing where it puts one framebuffer at 0xf00, with the first row of pages blanked out, and the whole thing goes down to 0x2080 @@ -2337,6 +2361,12 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe dst->m_scale = scale; dst->m_unscaled_size = new_size; dst->m_downscaled = scale == 1.0f && g_gs_renderer->GetUpscaleMultiplier() > 1.0f; + + if (src && src->m_target && src->m_from_target == dst) + { + src->m_texture = dst->m_texture; + src->m_scale = dst->m_scale; + } } else if (dst->m_scale != scale) scale = dst->m_scale; @@ -2356,15 +2386,15 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe dst->m_alpha_min = 0; dst->m_alpha_max = 0; } - else if (std::abs(static_cast(GSLocalMemory::m_psm[dst->m_TEX0.PSM].bpp - GSLocalMemory::m_psm[TEX0.PSM].bpp)) == 16) + else if ((used || type == GSTextureCache::DepthStencil) && (std::abs(static_cast(GSLocalMemory::m_psm[dst->m_TEX0.PSM].bpp - GSLocalMemory::m_psm[TEX0.PSM].bpp)) == 16)) { - dst->Update(false); + dst->Update(dst->m_alpha_max <= 128); const bool scale_down = GSLocalMemory::m_psm[dst->m_TEX0.PSM].bpp > GSLocalMemory::m_psm[TEX0.PSM].bpp; new_size = dst->m_unscaled_size; - new_scaled_size = ScaleRenderTargetSize(dst->m_unscaled_size, scale); + new_scaled_size = ScaleRenderTargetSize(dst->m_unscaled_size, dst->m_scale); - dRect = (GSVector4(GSVector4i::loadh(dst->m_unscaled_size)) * GSVector4(scale)).ceil(); + dRect = (GSVector4(GSVector4i::loadh(dst->m_unscaled_size)) * GSVector4(dst->m_scale)).ceil(); if (!is_shuffle || GSLocalMemory::m_psm[dst->m_TEX0.PSM].bpp == 16) { if (scale_down) @@ -2408,10 +2438,20 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe dst->ResizeTexture(new_size.x, new_size.y, true, true, GSVector4i(dRect)); } } - + // New format or doing a shuffle to a 32bit target that used to be 16bit - if (!is_shuffle || GSLocalMemory::m_psm[dst->m_TEX0.PSM].bpp < GSLocalMemory::m_psm[TEX0.PSM].bpp) - dst->m_TEX0.PSM = TEX0.PSM; + if ((!is_shuffle && (GSLocalMemory::m_psm[dst->m_TEX0.PSM].bpp != GSLocalMemory::m_psm[TEX0.PSM].bpp || GSLocalMemory::m_psm[dst->m_TEX0.PSM].depth != GSLocalMemory::m_psm[TEX0.PSM].depth)) || + (is_shuffle && GSLocalMemory::m_psm[dst->m_TEX0.PSM].bpp == 16)) + { + if(GSLocalMemory::m_psm[dst->m_TEX0.PSM].depth != GSLocalMemory::m_psm[TEX0.PSM].depth || dst->m_TEX0.TBW != TEX0.TBW) + dst->m_32_bits_fmt = GSLocalMemory::m_psm[TEX0.PSM].bpp != 16; + + if (!is_shuffle || (is_shuffle && GSLocalMemory::m_psm[dst->m_TEX0.PSM].bpp == 16)) + { + dst->m_TEX0.PSM = TEX0.PSM; + dst->m_TEX0.TBW = TEX0.TBW; + } + } // LEGO Dome Racers does a copy to a target as 8bit in alpha only, this doesn't really work great for us, so let's make it 32bit with invalid RGB. else if (dst->m_TEX0.PSM == PSMT8H) { @@ -2538,7 +2578,8 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe // Don't pull in targets without valid lower 24 bits, it makes no sense to convert them. // FIXME: Technically the difference in size is fine, but if the target gets reinterpreted, the hw renderer doesn't rearrange the target. // This does cause some extra uploads in some games (like Burnout), but without this, bad data gets displayed in games like Transformers. - if (bp != t->m_TEX0.TBP0 || !t->m_valid_rgb || (!is_shuffle && t->m_TEX0.TBW < TEX0.TBW && possible_clear)) + if (bp != t->m_TEX0.TBP0 || !t->m_valid_rgb || (!is_shuffle && t->m_TEX0.TBW != TEX0.TBW && + (possible_clear || ((~GSLocalMemory::m_psm[t->m_TEX0.PSM].fmsk | fbmask) == 0xffffffff)))) { continue; } @@ -2715,7 +2756,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe } } } - + if (dst) { dst->m_used |= used; @@ -2960,6 +3001,7 @@ bool GSTextureCache::PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, cons const GSVector4i save_rect = preserve_target ? newrect : eerect; GL_INS("Preloading the RT DATA from updated GS Memory"); + AddDirtyRectTarget(dst, save_rect, TEX0.PSM, TEX0.TBW, rgba, GSLocalMemory::m_psm[TEX0.PSM].trbpp >= 16); } } @@ -3010,7 +3052,7 @@ bool GSTextureCache::PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, cons } // could be overwriting a double buffer, so if it's the second half of it, just reduce the size down to half. - if (((((t->UnwrappedEndBlock() + 1) - t->m_TEX0.TBP0) >> 1) + t->m_TEX0.TBP0) == dst->m_TEX0.TBP0) + if (((((t->UnwrappedEndBlock() + 1) - t->m_TEX0.TBP0) >> 1) + t->m_TEX0.TBP0) == dst->m_TEX0.TBP0 && dst->m_TEX0.TBW == t->m_TEX0.TBW) { GSVector4i new_valid = t->m_valid; new_valid.w /= 2; @@ -3205,7 +3247,7 @@ void GSTextureCache::Target::UnscaleRTAlpha() { const GSVector2i rtsize(m_texture->GetSize()); const GSVector4i valid_rect = GSVector4i(GSVector4(m_valid) * GSVector4(m_scale)); - GL_PUSH("UnscaleRTAlpha(valid=(%dx%d %d,%d=>%d,%d))", m_valid.width(), m_valid.height(), m_valid.x, m_valid.y, m_valid.z, m_valid.w); + GL_PUSH("UnscaleRTAlpha(valid=(%dx%d %d,%d=>%d,%d))", valid_rect.width(), valid_rect.height(), valid_rect.x, valid_rect.y, valid_rect.z, valid_rect.w); if (GSTexture* temp_rt = g_gs_device->CreateRenderTarget(rtsize.x, rtsize.y, GSTexture::Format::Color, !GSVector4i::loadh(rtsize).eq(valid_rect))) { @@ -4997,14 +5039,17 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con GL_CACHE("TC: Sample offset (%d,%d) reduced region directly from target: %dx%d -> %dx%d @ %d,%d", dst->m_texture->GetWidth(), x_offset, y_offset, dst->m_texture->GetHeight(), w, h, x_offset, y_offset); - if (x_offset < 0) - src->m_region.SetX(x_offset, region.GetMaxX() + x_offset); - else - src->m_region.SetX(x_offset, x_offset + tw); - if (y_offset < 0) - src->m_region.SetY(y_offset, region.GetMaxY() + y_offset); - else - src->m_region.SetY(y_offset, y_offset + th); + if (!GSRendererHW::GetInstance()->IsTBPFrameOrZ(TEX0.TBP0) || !channel_shuffle) + { + if (x_offset < 0) + src->m_region.SetX(x_offset, region.GetMaxX() + x_offset); + else + src->m_region.SetX(x_offset, x_offset + tw); + if (y_offset < 0) + src->m_region.SetY(y_offset, region.GetMaxY() + y_offset); + else + src->m_region.SetY(y_offset, y_offset + th); + } src->m_target_direct = true; src->m_texture = dst->m_texture; @@ -5139,11 +5184,24 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con if (is_8bits) { - // Unscale 8 bits textures, quality won't be nice but format is really awful - src->m_unscaled_size.x = tw; - src->m_unscaled_size.y = th; - new_size.x = tw; - new_size.y = th; + if (dst->m_TEX0.TBP0 == TEX0.TBP0) + { + // Unscale 8 bits textures, quality won't be nice but format is really awful + src->m_unscaled_size.x = tw; + src->m_unscaled_size.y = th; + new_size.x = tw; + new_size.y = th; + } + else + { + const GSLocalMemory::psm_t& s_psm = GSLocalMemory::m_psm[TEX0.PSM]; + const GSLocalMemory::psm_t& t_psm = GSLocalMemory::m_psm[dst->m_TEX0.PSM]; + u32 dst_pages = (dst->m_unscaled_size.x / t_psm.pgs.x) * (dst->m_unscaled_size.y / t_psm.pgs.y); + src->m_unscaled_size.x = std::max(static_cast(TEX0.TBW) * (s_psm.pgs.x / 2), (s_psm.pgs.x / 2)); + src->m_unscaled_size.y = std::max(static_cast(dst_pages / std::max((TEX0.TBW / 2U), 1U)) * s_psm.pgs.y, s_psm.pgs.y); + new_size.x = src->m_unscaled_size.x; + new_size.y = src->m_unscaled_size.y; + } } // pitch conversion @@ -5347,6 +5405,9 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con } src->m_region.SetX(x_offset, x_offset + tw); src->m_region.SetY(y_offset, y_offset + th); + + if(!GSConfig.UserHacks_NativePaletteDraw) + m_temporary_source = src; } else { @@ -5358,8 +5419,6 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con sTex, sRectF, dTex, GSVector4(destX, destY, new_size.x, new_size.y), shader, false); } - m_temporary_source = src; - g_perfmon.Put(GSPerfMon::TextureCopies, 1); #ifdef PCSX2_DEVBUILD From 59dfbae9b4230234b17e1c454e5cdd7cc988b226 Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Mon, 10 Feb 2025 14:34:43 +0000 Subject: [PATCH 117/162] GS/HW: Disable per page split shuffle when RT in RT is enabled --- pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index bab2a76693..36df014e8a 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -1252,10 +1252,14 @@ bool GSRendererHW::IsSplitTextureShuffle(GIFRegTEX0& rt_TEX0, GSVector4i& valid_ const GSLocalMemory::psm_t& frame_psm = GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM]; const GSDrawingContext& next_ctx = m_env.CTXT[m_backed_up_ctx]; + + // Also don't allow pag sized shuffles when we have RT inside RT, we handle this manually. See Peter Jackson's - King Kong. + const bool in_rt_per_page = GSConfig.UserHacks_TextureInsideRt >= GSTextureInRtMode::InsideTargets && (pos_rc.width() <= frame_psm.pgs.x && pos_rc.height() <= frame_psm.pgs.y); + // Y should be page aligned. X should be too, but if it's doing a copy with a shuffle (which is kinda silly), both the // position and coordinates may be offset by +8. See Psi-Ops - The Mindgate Conspiracy. if ((aligned_rc.x & 7) != 0 || aligned_rc.x > 8 || (aligned_rc.z & 7) != 0 || - aligned_rc.y != 0 || (aligned_rc.w & (frame_psm.pgs.y - 1)) != 0) + aligned_rc.y != 0 || (aligned_rc.w & (frame_psm.pgs.y - 1)) != 0 || in_rt_per_page) { return false; } From 9f98e28b082ab52f8c4df4ae662a1ad994445f41 Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Tue, 11 Feb 2025 12:27:48 +0000 Subject: [PATCH 118/162] GS/HW: Fix Z Tex in RT regions + read back sources for SW if needed --- pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 16 ++++++++++++++-- pcsx2/GS/Renderers/HW/GSTextureCache.cpp | 4 ++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index 36df014e8a..b567d6d462 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -509,7 +509,7 @@ void GSRendererHW::ConvertSpriteTextureShuffle(u32& process_rg, u32& process_ba, // width_diff will be zero is both are BW == 1, so be careful of that. const bool same_width = width_diff > 0 || (m_cached_ctx.FRAME.FBW == 1 && width_diff == 0); // Draw is double width and the draw is twice the width of the next draws texture. - if (!same_width && max_tex_draw_width >= (m_cached_ctx.FRAME.FBW * 64)) + if ((!same_width && max_tex_draw_width >= (m_cached_ctx.FRAME.FBW * 64)) || (single_direction_doubled && (m_vt.m_max.p.x >= (rt->m_valid.z * 2)))) { half_bottom_uv = false; half_bottom_vert = false; @@ -7566,6 +7566,7 @@ bool GSRendererHW::CanUseSwPrimRender(bool no_rt, bool no_ds, bool draw_sprite_t if ((req_color && !src_target->m_valid_rgb) || (req_alpha && (!src_target->m_valid_alpha_low || !src_target->m_valid_alpha_high))) return true; + bool req_readback = false; // If the EE has written over our sample area, we're fine to do this on the CPU, despite the target. if (!src_target->m_dirty.empty()) { @@ -7591,11 +7592,22 @@ bool GSRendererHW::CanUseSwPrimRender(bool no_rt, bool no_ds, bool draw_sprite_t } } } + else + { + // If the target isn't dirty we might have valid data, so let's check their areas overlap, if so we need to read it back for SW. + GSVector4i src_rect = GSVector4i(m_vt.m_min.t.x, m_vt.m_min.t.y, m_vt.m_max.t.x, m_vt.m_max.t.x); + GSVector4i area = g_texture_cache->TranslateAlignedRectByPage(src_target, m_cached_ctx.TEX0.TBP0, m_cached_ctx.TEX0.PSM, m_cached_ctx.TEX0.TBW, src_rect, false); + req_readback = !area.rintersect(src_target->m_drawn_since_read).eq(GSVector4i::zero()); + } // Make sure it actually makes sense to use this target as a source, given the formats, and it wouldn't just sample as garbage. // We can't rely exclusively on the dirty rect check above, because sometimes the targets are from older frames and too large. if (!GSUtil::HasSameSwizzleBits(m_cached_ctx.TEX0.PSM, src_target->m_TEX0.PSM) && (!src_target->m_32_bits_fmt || GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].bpp != 16)) - return true; + { + if (req_readback) + g_texture_cache->Read(src_target, src_target->m_drawn_since_read); + return true; + } return false; } diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index 0c875b330e..cc75a6ff76 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -1160,8 +1160,8 @@ GSTextureCache::Source* GSTextureCache::LookupDepthSource(const bool is_depth, c if (inside_target) { // Need to set it up as a region target. - src->m_region.SetX(block_boundary_rect.x, block_boundary_rect.z); - src->m_region.SetY(block_boundary_rect.y, block_boundary_rect.w); + src->m_region.SetX(block_boundary_rect.x, src->m_from_target->m_valid.z); + src->m_region.SetY(block_boundary_rect.y, src->m_from_target->m_valid.w); } if (GSRendererHW::GetInstance()->IsTBPFrameOrZ(dst->m_TEX0.TBP0)) From 045bcbc7daa17743e037deadde185b398df4f682 Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Thu, 20 Feb 2025 00:14:36 +0000 Subject: [PATCH 119/162] GS/HW: More RT in RT regression fixes and adjustments Restored the Z clear CRC hack for Battlefield 2, it's probably the least invasive one and the most difficult one to emulate, it was still problematic. --- bin/resources/GameIndex.yaml | 11 ++ pcsx2/GS/GSState.cpp | 2 +- pcsx2/GS/Renderers/HW/GSHwHack.cpp | 53 ++++++- pcsx2/GS/Renderers/HW/GSHwHack.h | 1 + pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 176 ++++++++++++++--------- pcsx2/GS/Renderers/HW/GSTextureCache.cpp | 119 ++++++++++++--- 6 files changed, 267 insertions(+), 95 deletions(-) diff --git a/bin/resources/GameIndex.yaml b/bin/resources/GameIndex.yaml index 15c17a5ae9..4271b5a74d 100644 --- a/bin/resources/GameIndex.yaml +++ b/bin/resources/GameIndex.yaml @@ -13621,6 +13621,7 @@ SLED-53731: halfPixelOffset: 2 # Offset post-processing. texturePreloading: 1 # Improves performance. textureInsideRT: 1 # Fixes per line drawing. + getSkipCount: "GSC_Battlefield2" # Depth clear. SLED-53732: name: "Spartan - Total Warrior [Demo]" region: "PAL" @@ -23621,6 +23622,7 @@ SLES-53729: halfPixelOffset: 2 # Offset post-processing. texturePreloading: 1 # Improves performance. textureInsideRT: 1 # Fixes per line drawing. + getSkipCount: "GSC_Battlefield2" # Depth clear. SLES-53730: name: "Battlefield 2 - Modern Combat" region: "PAL-M3" @@ -23630,6 +23632,7 @@ SLES-53730: halfPixelOffset: 2 # Offset post-processing. texturePreloading: 1 # Improves performance. textureInsideRT: 1 # Fixes per line drawing. + getSkipCount: "GSC_Battlefield2" # Depth clear. SLES-53734: name: "50 Cent - Bulletproof" region: "PAL-E" @@ -31306,6 +31309,7 @@ SLKA-25330: halfPixelOffset: 2 # Offset post-processing. texturePreloading: 1 # Improves performance. textureInsideRT: 1 # Fixes per line drawing. + getSkipCount: "GSC_Battlefield2" # Depth clear. SLKA-25331: name: "Marc Ecko's Getting Up - Contents Under Pressure" region: "NTSC-K" @@ -32194,6 +32198,7 @@ SLPM-55034: halfPixelOffset: 2 # Offset post-processing. texturePreloading: 1 # Improves performance. textureInsideRT: 1 # Fixes per line drawing. + getSkipCount: "GSC_Battlefield2" # Depth clear. SLPM-55035: name: "ファイトナイト ラウンド2 [EA:SY! 1980]" name-sort: "ふぁいとないと らうんど2 [EA:SY! 1980]" @@ -46305,6 +46310,7 @@ SLPM-66206: halfPixelOffset: 2 # Offset post-processing. texturePreloading: 1 # Improves performance. textureInsideRT: 1 # Fixes per line drawing. + getSkipCount: "GSC_Battlefield2" # Depth clear. SLPM-66207: name: "どろろ [SEGA THE BEST 2800]" name-sort: "どろろ [SEGA THE BEST 2800]" @@ -49096,6 +49102,7 @@ SLPM-66651: halfPixelOffset: 2 # Offset post-processing. texturePreloading: 1 # Improves performance. textureInsideRT: 1 # Fixes per line drawing. + getSkipCount: "GSC_Battlefield2" # Depth clear. SLPM-66652: name: "バーンアウト リベンジ [EA BEST HITS]" name-sort: "ばーんあうと りべんじ [EA BEST HITS]" @@ -66986,6 +66993,7 @@ SLUS-21026: halfPixelOffset: 2 # Offset post-processing. texturePreloading: 1 # Improves performance. textureInsideRT: 1 # Fixes per line drawing. + getSkipCount: "GSC_Battlefield2" # Depth clear. SLUS-21027: name: "The Lord of the Rings - The Third Age" name-sort: "Lord of the Rings, The - The Third Age" @@ -72938,6 +72946,7 @@ SLUS-29117: halfPixelOffset: 2 # Offset post-processing. texturePreloading: 1 # Improves performance. textureInsideRT: 1 # Fixes per line drawing. + getSkipCount: "GSC_Battlefield2" # Depth clear. SLUS-29118: name: "Need for Speed - Underground 2 [Demo]" region: "NTSC-U" @@ -73090,6 +73099,7 @@ SLUS-29152: halfPixelOffset: 2 # Offset post-processing. texturePreloading: 1 # Improves performance. textureInsideRT: 1 # Fixes per line drawing. + getSkipCount: "GSC_Battlefield2" # Depth clear. SLUS-29153: name: "Burnout Revenge [Demo]" region: "NTSC-U" @@ -73205,6 +73215,7 @@ SLUS-29172: halfPixelOffset: 2 # Offset post-processing. texturePreloading: 1 # Improves performance. textureInsideRT: 1 # Fixes per line drawing. + getSkipCount: "GSC_Battlefield2" # Depth clear. SLUS-29173: name: "The Sims 2 [Demo]" name-sort: "Sims 2, The [Demo]" diff --git a/pcsx2/GS/GSState.cpp b/pcsx2/GS/GSState.cpp index b23000d94b..adb28d0777 100644 --- a/pcsx2/GS/GSState.cpp +++ b/pcsx2/GS/GSState.cpp @@ -4468,7 +4468,7 @@ bool GSState::GSTransferBuffer::Update(int tw, int th, int bpp, int& len) { if (len > packet_size) { -#if defined(PCSX2_DEVBUILD) || defined(_DEBUG) +#if defined(_DEBUG) Console.Warning("GS transfer buffer overflow len %d remaining %d, tex_size %d tw %d th %d bpp %d", len, remaining, tex_size, tw, th, bpp); #endif } diff --git a/pcsx2/GS/Renderers/HW/GSHwHack.cpp b/pcsx2/GS/Renderers/HW/GSHwHack.cpp index 8341864503..d7db96ba77 100644 --- a/pcsx2/GS/Renderers/HW/GSHwHack.cpp +++ b/pcsx2/GS/Renderers/HW/GSHwHack.cpp @@ -742,7 +742,7 @@ bool GSHwHack::GSC_PolyphonyDigitalGames(GSRendererHW& r, int& skip) for (u32 channel = 0; channel < 3; channel++) { - const GIFRegTEX0 TEX0 = GIFRegTEX0::Create(base + channel * page_offset, RTEX0.TBW, PSMCT32); + const GIFRegTEX0 TEX0 = GIFRegTEX0::Create(base + channel * page_offset, 10, PSMCT32); GSTextureCache::Target* dst = g_texture_cache->LookupTarget(TEX0, src->GetUnscaledSize(), src->GetScale(), GSTextureCache::RenderTarget, true, fbmsk); if (!dst) { @@ -775,6 +775,35 @@ bool GSHwHack::GSC_PolyphonyDigitalGames(GSRendererHW& r, int& skip) } } + +bool GSHwHack::GSC_Battlefield2(GSRendererHW& r, int& skip) +{ + if (skip == 0) + { + if (RZBP >= RFBP && RFBP >= 0x2000 && RZBP >= 0x2700 && ((RZBP - RFBP) == 0x700)) + { + skip = 7; + + GIFRegTEX0 TEX0 = {}; + TEX0.TBP0 = RFBP; + TEX0.TBW = 8; + GSTextureCache::Target* dst = g_texture_cache->LookupTarget(TEX0, r.GetTargetSize(), r.GetTextureScaleFactor(), GSTextureCache::DepthStencil); + + if (!dst) + dst = g_texture_cache->CreateTarget(TEX0, r.GetTargetSize(), r.GetValidSize(nullptr), r.GetTextureScaleFactor(), GSTextureCache::DepthStencil, + true, 0, false, false, false, GSVector4i(0,0,1,1), nullptr); + + if (dst) + { + float dc = r.m_vertex.buff[1].XYZ.Z; + g_gs_device->ClearDepth(dst->m_texture, dc * std::exp2(-32.0f)); + } + } + } + + return true; +} + bool GSHwHack::GSC_BlueTongueGames(GSRendererHW& r, int& skip) { GSDrawingContext* context = r.m_context; @@ -1064,16 +1093,29 @@ bool GSHwHack::OI_SonicUnleashed(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Target* rt_again = g_texture_cache->LookupTarget(Frame, src_size, src->m_scale, GSTextureCache::RenderTarget); if ((rt_again->m_TEX0.PSM & 0x3) == PSMCT16) { - GSVector4i dRect = rt_again->m_valid; + GSVector4 dRect; - dRect = GSVector4i(GSVector4(GSVector4i::loadh(rt_again->m_unscaled_size)) * rt_again->m_scale); + GSVector4 source_rect = GSVector4(static_cast(rt_again->m_valid.x) / static_cast(rt_again->m_unscaled_size.x), static_cast(rt_again->m_valid.y) / static_cast(rt_again->m_unscaled_size.y), + static_cast(rt_again->m_valid.z) / static_cast(rt_again->m_unscaled_size.x), static_cast(rt_again->m_valid.w) / static_cast(rt_again->m_unscaled_size.y)); + + dRect = GSVector4(rt_again->m_valid) * rt_again->m_scale; dRect.y /= 2; dRect.w /= 2; rt_again->m_valid.y /= 2; rt_again->m_valid.w /= 2; rt_again->m_TEX0.PSM = PSMCT32; - rt_again->ResizeTexture(rt_again->m_unscaled_size.x, rt_again->m_unscaled_size.y / 2, true, true, dRect, false); - rt = rt_again->m_texture; + GSTexture* tex = g_gs_device->CreateRenderTarget(rt_again->m_unscaled_size.x * rt_again->m_scale, rt_again->m_unscaled_size.y * rt_again->m_scale, GSTexture::Format::Color, false); + + if (!tex) + return false; + + + g_gs_device->StretchRect(rt_again->m_texture, source_rect, tex, dRect, ShaderConvert::COPY, false); + + + g_gs_device->Recycle(rt_again->m_texture); + rt_again->m_texture = tex; + rt = tex; } GSVector2i rt_size(rt->GetSize()); @@ -1457,6 +1499,7 @@ const GSHwHack::Entry GSHwHack::s_get_skip_count_function CRC_F(GSC_PolyphonyDigitalGames), CRC_F(GSC_MetalGearSolid3), CRC_F(GSC_HitmanBloodMoney), + CRC_F(GSC_Battlefield2), // Channel Effect CRC_F(GSC_SteambotChronicles), diff --git a/pcsx2/GS/Renderers/HW/GSHwHack.h b/pcsx2/GS/Renderers/HW/GSHwHack.h index 68c5cbb5fc..bd1e24bec0 100644 --- a/pcsx2/GS/Renderers/HW/GSHwHack.h +++ b/pcsx2/GS/Renderers/HW/GSHwHack.h @@ -26,6 +26,7 @@ public: static bool GSC_SteambotChronicles(GSRendererHW& r, int& skip); static bool GSC_BlueTongueGames(GSRendererHW& r, int& skip); static bool GSC_NFSUndercover(GSRendererHW& r, int& skip); + static bool GSC_Battlefield2(GSRendererHW& r, int& skip); static bool GSC_PolyphonyDigitalGames(GSRendererHW& r, int& skip); static bool GSC_MetalGearSolid3(GSRendererHW& r, int& skip); static bool GSC_HitmanBloodMoney(GSRendererHW& r, int& skip); diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index b567d6d462..9dc0988ae9 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -2252,19 +2252,29 @@ void GSRendererHW::Draw() //DevCon.Warning("Skipped %d draw %d was abort %d", num_skipped_channel_shuffle_draws, s_n, (int)m_channel_shuffle_abort); // Some games like Tomb raider abort early, we're never going to know the real height, and the system doesn't work right for partials. // But it's good enough for games like Hitman Blood Money which only shuffle part of the screen + const int width = std::max(static_cast(m_last_rt->m_TEX0.TBW) * 64, 64); + const int shuffle_height = (((num_skipped_channel_shuffle_draws + 1 + (std::max(1, (width / 64) - 1))) * 64) / width) * 32; + const int shuffle_width = std::min((num_skipped_channel_shuffle_draws + 1) * 64, static_cast(width)); + GSVector4i valid_area = GSVector4i::loadh(GSVector2i(shuffle_width, shuffle_height)); + const int offset = (((m_last_channel_shuffle_fbp + 0x20) - m_last_rt->m_TEX0.TBP0) >> 5) - (num_skipped_channel_shuffle_draws + 1); + + if (offset) + { + int vertical_offset = (offset / std::max(1U, m_channel_shuffle_width)) * 32; + valid_area.y += vertical_offset; + valid_area.w += vertical_offset; + } if (!m_full_screen_shuffle) { - const u32 width_pages = ((num_skipped_channel_shuffle_draws + 1) % std::max(1U, m_channel_shuffle_width) % std::max(1U, m_channel_shuffle_width)) * 64; - ; - m_conf.scissor.w = m_conf.scissor.y + (((num_skipped_channel_shuffle_draws + 1 + (m_channel_shuffle_width - 1)) / std::max(1U, m_channel_shuffle_width)) * 32) * m_conf.cb_ps.ScaleFactor.z; - if (width_pages) - m_conf.scissor.z = m_conf.scissor.x + (((num_skipped_channel_shuffle_draws + 1) % std::max(1U, m_channel_shuffle_width) % std::max(1U, m_channel_shuffle_width)) * 64) * m_conf.cb_ps.ScaleFactor.z; - - m_last_rt->UpdateValidity(GSVector4i::loadh(m_last_rt->m_unscaled_size).rintersect(GSVector4i(GSVector4(m_conf.scissor) / m_conf.cb_ps.ScaleFactor.z)), true); + m_conf.scissor.w = m_conf.scissor.y + shuffle_height * m_conf.cb_ps.ScaleFactor.z; + if (shuffle_width) + m_conf.scissor.z = m_conf.scissor.x + (shuffle_width * m_conf.cb_ps.ScaleFactor.z); + else + m_conf.scissor.z = std::min(m_conf.scissor.z, static_cast((m_channel_shuffle_width * 64) * m_conf.cb_ps.ScaleFactor.z)); } - else - m_last_rt->UpdateValidity(m_channel_shuffle_src_valid); + + m_last_rt->UpdateValidity(valid_area); g_gs_device->RenderHW(m_conf); @@ -2303,6 +2313,7 @@ void GSRendererHW::Draw() m_channel_shuffle_width = 0; m_full_screen_shuffle = false; m_channel_shuffle_abort = false; + m_channel_shuffle_src_valid = GSVector4i::zero(); GL_PUSH("HW Draw %d (Context %u)", s_n, PRIM->CTXT); GL_INS("FLUSH REASON: %s%s", GetFlushReasonString(m_state_flush_reason), @@ -3092,7 +3103,7 @@ void GSRendererHW::Draw() // FBW is going to be wrong for channel shuffling into a new target, so take it from the source. FRAME_TEX0.U64 = 0; FRAME_TEX0.TBP0 = ((m_last_channel_shuffle_end_block + 1) == m_cached_ctx.FRAME.Block() && possible_shuffle) ? m_last_channel_shuffle_fbp : m_cached_ctx.FRAME.Block(); - FRAME_TEX0.TBW = (possible_horizontal_texture_shuffle || (possible_shuffle && src && src->m_from_target && IsPossibleChannelShuffle())) ? src->m_from_target_TEX0.TBW : m_cached_ctx.FRAME.FBW; + FRAME_TEX0.TBW = (possible_horizontal_texture_shuffle || (possible_shuffle && src && src->m_from_target && IsPossibleChannelShuffle()&& m_cached_ctx.FRAME.FBW <= 2)) ? src->m_from_target_TEX0.TBW : m_cached_ctx.FRAME.FBW; FRAME_TEX0.PSM = m_cached_ctx.FRAME.PSM; // Don't clamp on shuffle, the height cache may troll us with the REAL height. @@ -3189,6 +3200,11 @@ void GSRendererHW::Draw() CleanupDraw(true); return; } + + if (IsPageCopy() && m_cached_ctx.FRAME.FBW == 1) + { + rt->UpdateValidity(GSVector4i::loadh(GSVector2i(GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].pgs.x, GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].pgs.y)), true); + } } else if (rt->m_TEX0.TBP0 != m_cached_ctx.FRAME.Block()) { @@ -3220,6 +3236,15 @@ void GSRendererHW::Draw() rt->m_drawn_since_read.y += new_offset; rt->m_drawn_since_read.w += new_offset; + if (rt->m_dirty.size()) + { + for (int i = 0; i < rt->m_dirty.size(); i++) + { + rt->m_dirty[i].r.y += new_offset; + rt->m_dirty[i].r.w += new_offset; + } + } + t_size.y += std::abs(vertical_offset); vertical_offset = 0; } @@ -3231,63 +3256,66 @@ void GSRendererHW::Draw() rt->m_TEX0.TBP0 += horizontal_offset; horizontal_offset = 0; } - // Z isn't offset but RT is, so we need a temp Z to align it, hopefully nothing will ever write to the Z too, right?? - if (ds && vertical_offset && (m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0) != (m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0)) + + if (vertical_offset || horizontal_offset) { + // Z isn't offset but RT is, so we need a temp Z to align it, hopefully nothing will ever write to the Z too, right?? + if (ds && vertical_offset && (m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0) != (m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0)) + { - const int z_vertical_offset = ((static_cast(m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0) / 32) / std::max(rt->m_TEX0.TBW, 1U)) * GSLocalMemory::m_psm[m_cached_ctx.ZBUF.PSM].pgs.y; - GL_CACHE("RT in RT Z copy on draw %d z_vert_offset %d z_offset %d", s_n, z_vertical_offset, vertical_offset); - GSVector4i dRect = GSVector4i(0, vertical_offset * ds->m_scale, ds->m_unscaled_size.x * ds->m_scale, std::min(vertical_offset + m_r.w + 1, vertical_offset + ds->m_unscaled_size.y) * ds->m_scale); - const int new_height = std::max(static_cast(ds->m_unscaled_size.y * ds->m_scale), dRect.w); - GSTexture* tex = g_gs_device->CreateDepthStencil(ds->m_unscaled_size.x * ds->m_scale, new_height, GSTexture::Format::DepthStencil, true); - g_gs_device->StretchRect(ds->m_texture, GSVector4(0.0f, z_vertical_offset / static_cast(ds->m_unscaled_size.y), 1.0f, std::min(z_vertical_offset + m_r.w + 1, ds->m_unscaled_size.y) / static_cast(ds->m_unscaled_size.y)), tex, GSVector4(dRect), ShaderConvert::DEPTH_COPY, false); + const int z_vertical_offset = ((static_cast(m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0) / 32) / std::max(rt->m_TEX0.TBW, 1U)) * GSLocalMemory::m_psm[m_cached_ctx.ZBUF.PSM].pgs.y; + GL_CACHE("RT in RT Z copy on draw %d z_vert_offset %d z_offset %d", s_n, z_vertical_offset, vertical_offset); + GSVector4i dRect = GSVector4i(0, vertical_offset * ds->m_scale, ds->m_unscaled_size.x * ds->m_scale, std::min(vertical_offset + m_r.w + 1, vertical_offset + ds->m_unscaled_size.y) * ds->m_scale); + const int new_height = std::max(static_cast(ds->m_unscaled_size.y * ds->m_scale), dRect.w); + GSTexture* tex = g_gs_device->CreateDepthStencil(ds->m_unscaled_size.x * ds->m_scale, new_height, GSTexture::Format::DepthStencil, true); + g_gs_device->StretchRect(ds->m_texture, GSVector4(0.0f, z_vertical_offset / static_cast(ds->m_unscaled_size.y), 1.0f, std::min(z_vertical_offset + m_r.w + 1, ds->m_unscaled_size.y) / static_cast(ds->m_unscaled_size.y)), tex, GSVector4(dRect), ShaderConvert::DEPTH_COPY, false); - g_texture_cache->SetTemporaryZ(tex); - } - - GSVertex* v = &m_vertex.buff[0]; - - for (u32 i = 0; i < m_vertex.tail; i++) - { - v[i].XYZ.X += horizontal_offset << 4; - v[i].XYZ.Y += vertical_offset << 4; - } - - if (texture_offset && src && src->m_from_target && src->m_target_direct && src->m_from_target == rt) - { - GSVector4i src_region = src->GetRegionRect(); - - if (src_region.rempty()) - { - src_region = GSVector4i::loadh(rt->m_unscaled_size); - src_region.y += texture_offset; + g_texture_cache->SetTemporaryZ(tex); } - else + + GSVertex* v = &m_vertex.buff[0]; + + for (u32 i = 0; i < m_vertex.tail; i++) { - src_region.y += texture_offset; - src_region.w += texture_offset; + v[i].XYZ.X += horizontal_offset << 4; + v[i].XYZ.Y += vertical_offset << 4; } - src->m_region.SetX(src_region.x, src_region.z); - src->m_region.SetY(src_region.y, src_region.w); + + if (texture_offset && src && src->m_from_target && src->m_target_direct && src->m_from_target == rt) + { + GSVector4i src_region = src->GetRegionRect(); + + if (src_region.rempty()) + { + src_region = GSVector4i::loadh(rt->m_unscaled_size); + src_region.y += texture_offset; + } + else + { + src_region.y += texture_offset; + src_region.w += texture_offset; + } + src->m_region.SetX(src_region.x, src_region.z); + src->m_region.SetY(src_region.y, src_region.w); + } + + m_context->scissor.in.x += horizontal_offset; + m_context->scissor.in.z += horizontal_offset; + m_context->scissor.in.y += vertical_offset; + m_context->scissor.in.w += vertical_offset; + m_r.y += vertical_offset; + m_r.w += vertical_offset; + m_r.x += horizontal_offset; + m_r.z += horizontal_offset; + m_in_target_draw = rt->m_TEX0.TBP0 != m_cached_ctx.FRAME.Block(); + m_vt.m_min.p.x += horizontal_offset; + m_vt.m_max.p.x += horizontal_offset; + m_vt.m_min.p.y += vertical_offset; + m_vt.m_max.p.y += vertical_offset; + + t_size.x = rt->m_unscaled_size.x - horizontal_offset; + t_size.y = rt->m_unscaled_size.y - vertical_offset; } - - m_context->scissor.in.x += horizontal_offset; - m_context->scissor.in.z += horizontal_offset; - m_context->scissor.in.y += vertical_offset; - m_context->scissor.in.w += vertical_offset; - m_r.y += vertical_offset; - m_r.w += vertical_offset; - m_r.x += horizontal_offset; - m_r.z += horizontal_offset; - m_in_target_draw = rt->m_TEX0.TBP0 != m_cached_ctx.FRAME.Block(); - m_vt.m_min.p.x += horizontal_offset; - m_vt.m_max.p.x += horizontal_offset; - m_vt.m_min.p.y += vertical_offset; - m_vt.m_max.p.y += vertical_offset; - - t_size.x = rt->m_unscaled_size.x - horizontal_offset; - t_size.y = rt->m_unscaled_size.y - vertical_offset; - // Don't resize if the BPP don't match. if (frame_psm.bpp == GSLocalMemory::m_psm[rt->m_TEX0.PSM].bpp) { @@ -3304,6 +3332,10 @@ void GSRendererHW::Draw() rt->UpdateValidity(m_r, !frame_masked); rt->UpdateDrawn(m_r, !frame_masked); } + else if (IsPageCopy() && m_cached_ctx.FRAME.FBW == 1) + { + rt->UpdateValidity(GSVector4i::loadh(GSVector2i(GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].pgs.x, GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].pgs.y + vertical_offset)), true); + } } } @@ -3320,6 +3352,7 @@ void GSRendererHW::Draw() if (ds && ds->m_scale != target_scale) { const GSVector2i unscaled_size(ds->m_unscaled_size.x, ds->m_unscaled_size.y); + ds->m_scale = 1; ds->ResizeTexture(ds->m_unscaled_size.x * target_scale, ds->m_unscaled_size.y * target_scale, true); // Slightly abusing the texture resize. ds->m_scale = target_scale; @@ -3668,7 +3701,7 @@ void GSRendererHW::Draw() // The FBW should also be okay, since it's coming from the source. if (rt) { - const bool update_fbw = !m_in_target_draw && (m_channel_shuffle && src->m_target) && (!PRIM->ABE || IsOpaque() || m_context->ALPHA.IsBlack()); + const bool update_fbw = (FRAME_TEX0.TBW != rt->m_TEX0.TBW || rt->m_TEX0.TBW == 1) && !m_in_target_draw && (m_channel_shuffle && src->m_target) && (!PRIM->ABE || IsOpaque() || m_context->ALPHA.IsBlack()); rt->m_TEX0.TBW = update_fbw ? ((src && src->m_from_target && src->m_32_bits_fmt) ? src->m_from_target->m_TEX0.TBW : FRAME_TEX0.TBW) : std::max(rt->m_TEX0.TBW, FRAME_TEX0.TBW); rt->m_TEX0.PSM = FRAME_TEX0.PSM; } @@ -3799,8 +3832,8 @@ void GSRendererHW::Draw() g_texture_cache->GetTargetSize(rt->m_TEX0.TBP0, rt->m_TEX0.TBW, rt->m_TEX0.PSM, 0, new_h); } - rt->ResizeValidity(rt->GetUnscaledRect()); - rt->ResizeDrawn(rt->GetUnscaledRect()); + rt->ResizeValidity(rt->m_valid.rintersect(rt->GetUnscaledRect())); + rt->ResizeDrawn(rt->m_drawn_since_read.rintersect(rt->GetUnscaledRect())); } const bool rt_update = can_update_size || (m_texture_shuffle && (src && rt && src->m_from_target != rt)); @@ -3917,6 +3950,7 @@ void GSRendererHW::Draw() src->m_shared_texture = false; src->m_texture = new_tex; src->m_unscaled_size = new_size; + src->m_TEX0.PSM = m_cached_ctx.TEX0.PSM; } } } @@ -3958,8 +3992,8 @@ void GSRendererHW::Draw() if (GSConfig.SaveTexture && src) { - s = GetDrawDumpPath("%05d_f%05lld_itex_%05x_%s_%d%d_%02x_%02x_%02x_%02x.dds", - s_n, frame, static_cast(m_cached_ctx.TEX0.TBP0), psm_str(m_cached_ctx.TEX0.PSM), + s = GetDrawDumpPath("%05d_f%05lld_itex_%s_%05x(%05x)_%s_%d%d_%02x_%02x_%02x_%02x.dds", + s_n, frame, (src->m_from_target ? "tgt" : "gs"), static_cast(m_cached_ctx.TEX0.TBP0), (src->m_from_target ? src->m_from_target->m_TEX0.TBP0 : src->m_TEX0.TBP0), psm_str(m_cached_ctx.TEX0.PSM), static_cast(m_cached_ctx.CLAMP.WMS), static_cast(m_cached_ctx.CLAMP.WMT), static_cast(m_cached_ctx.CLAMP.MINU), static_cast(m_cached_ctx.CLAMP.MAXU), static_cast(m_cached_ctx.CLAMP.MINV), static_cast(m_cached_ctx.CLAMP.MAXV)); @@ -4134,7 +4168,7 @@ void GSRendererHW::Draw() if (rt && GSConfig.SaveRT && !m_last_rt) { - s = GetDrawDumpPath("%05d_f%05lld_rt1_%05x_%s.bmp", s_n, frame, m_cached_ctx.FRAME.Block(), psm_str(m_cached_ctx.FRAME.PSM)); + s = GetDrawDumpPath("%05d_f%05lld_rt1_%05x_(%05x)_%s.bmp", s_n, frame, m_cached_ctx.FRAME.Block(), rt->m_TEX0.TBP0, psm_str(m_cached_ctx.FRAME.PSM)); rt->m_texture->Save(s); } @@ -4790,7 +4824,7 @@ __ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool const GSLocalMemory::psm_t frame_psm = GSLocalMemory::m_psm[m_context->FRAME.PSM]; m_full_screen_shuffle = (m_r.height() > frame_psm.pgs.y) || (m_r.width() > frame_psm.pgs.x) || GSConfig.UserHacks_TextureInsideRt == GSTextureInRtMode::Disabled; m_channel_shuffle_src_valid = src->m_valid; - if (GSConfig.UserHacks_TextureInsideRt == GSTextureInRtMode::Disabled || (!m_in_target_draw && IsPageCopy()) || m_conf.ps.urban_chaos_hle || m_conf.ps.tales_of_abyss_hle) + if (GSConfig.UserHacks_TextureInsideRt == GSTextureInRtMode::Disabled || ((src->m_TEX0.TBW == rt->m_TEX0.TBW) && (!m_in_target_draw && IsPageCopy())) || m_conf.ps.urban_chaos_hle || m_conf.ps.tales_of_abyss_hle) { GSVertex* s = &m_vertex.buff[0]; s[0].XYZ.X = static_cast(m_context->XYOFFSET.OFX + 0); @@ -4859,9 +4893,15 @@ __ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool GSVector4i new_valid = rt->m_valid; int offset_height = static_cast((((frame_offset - rt->m_TEX0.TBP0) >> 5) / rt->m_TEX0.TBW) * frame_psm.pgs.y) + frame_psm.pgs.y; + const int get_next_ctx = (m_state_flush_reason == CONTEXTCHANGE) ? m_env.PRIM.CTXT : m_backed_up_ctx; + const GSDrawingContext& next_ctx = m_env.CTXT[get_next_ctx]; + const u32 safe_TBW = std::max(rt->m_TEX0.TBW, 1U); // This is an annoying case where the draw is offset to draw on the right hand side of a texture (Hitman Blood Money pause screen). - if (!IsPageCopy() && NextDrawMatchesShuffle() && ((frame_offset >> 5) + 1) % rt->m_TEX0.TBW == 0) + if (m_state_flush_reason == GSFlushReason::CONTEXTCHANGE && !IsPageCopy() && NextDrawMatchesShuffle() && next_ctx.FRAME.FBP > m_cached_ctx.FRAME.FBP && (next_ctx.FRAME.FBP < (m_cached_ctx.FRAME.FBP + safe_TBW)) && + (next_ctx.FRAME.FBP - m_cached_ctx.FRAME.FBP) < safe_TBW && (next_ctx.FRAME.FBP % safe_TBW) != ((m_cached_ctx.FRAME.FBP % safe_TBW) + 1)) + { offset_height += frame_psm.pgs.y; + } new_valid.w = std::max(new_valid.w, offset_height); rt->UpdateValidity(new_valid, true); diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index cc75a6ff76..3bda1bc33e 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -309,7 +309,7 @@ GSVector4i GSTextureCache::TranslateAlignedRectByPage(u32 tbp, u32 tebp, u32 tbw const int vertical_offset = in_rect.y / t_psm.pgs.y; const int horizontal_offset = in_rect.x / t_psm.pgs.x; const int rect_offset = horizontal_offset + (vertical_offset * src_pgw); - const int rect_pages = ((in_rect.width() / t_psm.pgs.x) % src_pgw) + ((in_rect.height() / t_psm.pgs.y) * src_pgw); + const int rect_pages = std::max(((in_rect.width() / t_psm.pgs.x) % src_pgw) + ((in_rect.height() / t_psm.pgs.y) * src_pgw), 1); page_offset += rect_offset; in_rect -= GSVector4i(horizontal_offset * t_psm.pgs.x, vertical_offset * t_psm.pgs.y).xyxy(); @@ -387,7 +387,6 @@ GSVector4i GSTextureCache::TranslateAlignedRectByPage(u32 tbp, u32 tebp, u32 tbw { //TODO: Maybe control dirty blocks directly and add them page at a time for better granularity. const GSVector2i start_page = GSVector2i((page_offset + rect_offset) % dst_pgw, page_offset / dst_pgw); - DevCon.Warning("Fudging start position"); // Not easily translatable full pages and make sure the height is rounded upto encompass the half row. new_rect.x = start_page.x * dst_page_size.x; new_rect.z = new_rect.x + in_rect.z; @@ -1646,6 +1645,12 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const DevCon.Warning("BP %x - 8bit bad match for target bp %x bw %d src %d format %d", bp, t->m_TEX0.TBP0, t->m_TEX0.TBW, bw, t->m_TEX0.PSM); continue; } + else if (!possible_shuffle && GSLocalMemory::m_psm[psm].trbpp == 8 && TEX0.TBW == 1) + { + DevCon.Warning("Too small for relocation, skipping"); + continue; + } + // PSM equality needed because CreateSource does not handle PSM conversion. // Only inclusive hit to limit false hits. GSVector4i rect = block_boundary_rect; @@ -1672,6 +1677,12 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const } if (bp > t->m_TEX0.TBP0) { + if (!region.HasEither() && GSLocalMemory::m_psm[psm].bpp == 32 && (t->m_TEX0.TBW - (((bp - t->m_TEX0.TBP0) >> 5) % t->m_TEX0.TBW)) < static_cast((block_boundary_rect.width() + 63) / 64)) + { + DevCon.Warning("Bad alignmenet"); + continue; + } + GSVector4i new_rect = (GSLocalMemory::m_psm[color_psm].bpp != GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp && (psm & 0x7) != PSMCT16) ? block_boundary_rect : rect; // Check if it is possible to hit with valid offset on the given Target. @@ -2089,7 +2100,6 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe i++; continue; } - // if It's an old target and it's being completely overwritten, kill it. // Dragon Quest 8 reuses a render-target sized buffer as a single-page buffer, without clearing it. But, // it does dirty it by writing over the 64x64 region. So while we can't use this heuristic for tossing @@ -2107,7 +2117,23 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe can_use = !t->m_dirty.GetTotalRect(TEX0, size).rintersect(size_rect).eq(size_rect); } } + else if (type == RenderTarget && (fbmask == 0xffffff && !t->m_was_dst_matched && TEX0.TBW != t->m_TEX0.TBW)) + { + // When returning to being matched with the Z buffer in width, we need to make sure the RGB is up to date as it could get used later (Hitman Contracts). + auto& rev_list = m_dst[1 - type]; + Target* dst_match = nullptr; + for (auto j = rev_list.begin(); j != rev_list.end(); ++j) + { + Target* ds = *j; + if (t->m_TEX0.TBP0 != ds->m_TEX0.TBP0 || !ds->m_valid_rgb || TEX0.TBW != ds->m_TEX0.TBW) + continue; + + t->m_was_dst_matched = true; + t->m_valid_rgb = false; + break; + } + } // TODO: What might be a nicer solution than this, is to rearrange the targets to match the new layout, however this comes with some caviets: // 1. They can draw wider than the FBW // 2. The dirty+valid rects will need to also be rearranged @@ -2175,7 +2201,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe const GSLocalMemory::psm_t& s_psm = GSLocalMemory::m_psm[TEX0.PSM]; // I know what you're thinking, and I hate the guy who wrote it too (me). Project Snowblind, Tomb Raider etc decide to offset where they're drawing using a channel shuffle, and this gets messy, so best just to kill the old target. - if (is_shuffle && src->m_TEX0.PSM == PSMT8 && GSRendererHW::GetInstance()->m_context->FRAME.FBW == 1 && t->m_last_draw != (GSState::s_n - 1) && src && src->m_from_target && src->m_from_target->m_TEX0.TBP0 == src->m_TEX0.TBP0 && widthpage_offset && src->m_from_target != t) + if (is_shuffle && src->m_TEX0.PSM == PSMT8 && GSRendererHW::GetInstance()->m_context->FRAME.FBW == 1 && t->m_last_draw != (GSState::s_n - 1) && src && src->m_from_target && (src->m_from_target->m_TEX0.TBP0 == src->m_TEX0.TBP0 || (((src->m_TEX0.TBP0 - src->m_from_target->m_TEX0.TBP0) >> 5) % std::max(src->m_from_target->m_TEX0.TBW, 1U) == 0)) && widthpage_offset && src->m_from_target != t) { GL_INS("TC: Deleting RT BP 0x%x BW %d PSM %s offset overwrite shuffle", t->m_TEX0.TBP0, t->m_TEX0.TBW, psm_str(t->m_TEX0.PSM)); InvalidateSourcesFromTarget(t); @@ -2195,7 +2221,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe continue; } - else if (t->m_dirty.empty()) + else if (t->m_dirty.empty() || (t->m_TEX0.TBP0 <= bp && t->m_dirty.GetTotalRect(t->m_TEX0, t->m_unscaled_size).rintersect(GSVector4i(0, 0, 0, 0) .max_i32(TranslateAlignedRectByPage(t, TEX0.TBP0, TEX0.PSM, TEX0.TBW, min_rect))).rempty())) { if (TEX0.TBW == t->m_TEX0.TBW && !is_shuffle && widthpage_offset == 0 && ((min_rect.w + 63)/ 64) > 1) { @@ -2362,7 +2388,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe dst->m_unscaled_size = new_size; dst->m_downscaled = scale == 1.0f && g_gs_renderer->GetUpscaleMultiplier() > 1.0f; - if (src && src->m_target && src->m_from_target == dst) + if (src && src->m_target && src->m_from_target == dst && src->m_shared_texture) { src->m_texture = dst->m_texture; src->m_scale = dst->m_scale; @@ -2391,32 +2417,39 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe dst->Update(dst->m_alpha_max <= 128); const bool scale_down = GSLocalMemory::m_psm[dst->m_TEX0.PSM].bpp > GSLocalMemory::m_psm[TEX0.PSM].bpp; + bool req_copy = true; new_size = dst->m_unscaled_size; new_scaled_size = ScaleRenderTargetSize(dst->m_unscaled_size, dst->m_scale); - dRect = (GSVector4(GSVector4i::loadh(dst->m_unscaled_size)) * GSVector4(dst->m_scale)).ceil(); + dRect = (GSVector4(dst->m_valid) * GSVector4(dst->m_scale)).ceil(); + GSVector4 source_rect = GSVector4(static_cast(dst->m_valid.x) / static_cast(dst->m_unscaled_size.x), static_cast(dst->m_valid.y) / static_cast(dst->m_unscaled_size.y), + static_cast(dst->m_valid.z) / static_cast(dst->m_unscaled_size.x), static_cast(dst->m_valid.w) / static_cast(dst->m_unscaled_size.y)); if (!is_shuffle || GSLocalMemory::m_psm[dst->m_TEX0.PSM].bpp == 16) { if (scale_down) { - new_scaled_size.y *= 2; dst->m_valid.y *= 2; dst->m_valid.w *= 2; dRect.y *= 2; dRect.w *= 2; - new_size.y *= 2; - new_size.y = std::max(dst->m_valid.w, new_size.y); + if (new_size.y < dst->m_valid.w) + { + new_size.y = dst->m_valid.w; + new_scaled_size = ScaleRenderTargetSize(new_size, dst->m_scale); + // Using our resize texture only really works if we're scaling exactly. + req_copy = source_rect.w != 1.0f; + } } else { - new_scaled_size.y /= 2; - new_size.y /= 2; dRect.y /= 2; dRect.w /= 2; dst->m_valid.y /= 2; dst->m_valid.w /= 2; - new_size.y = std::max(dst->m_valid.w, new_size.y); + req_copy = true; + /*new_size.y /= 2; + new_scaled_size.y = new_size.y * dst->m_scale;*/ } } if (!is_shuffle) @@ -2431,11 +2464,49 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe src->m_target_direct = false; src->m_shared_texture = false; - dst->ResizeTexture(new_size.x, new_size.y, true, true, GSVector4i(dRect), true); + if(!req_copy) + dst->ResizeTexture(new_size.x, new_size.y, true, true, GSVector4i(dRect), true); + else + { + GSTexture* tex = type == RenderTarget ? g_gs_device->CreateRenderTarget(new_scaled_size.x, new_scaled_size.y, GSTexture::Format::Color, clear) : + g_gs_device->CreateDepthStencil(new_scaled_size.x, new_scaled_size.y, GSTexture::Format::DepthStencil, clear); + if (!tex) + return nullptr; + + g_gs_device->StretchRect(dst->m_texture, source_rect, tex, dRect, (type == RenderTarget) ? ShaderConvert::COPY : ShaderConvert::DEPTH_COPY, false); + + g_perfmon.Put(GSPerfMon::TextureCopies, 1); + m_target_memory_usage = m_target_memory_usage + tex->GetMemUsage(); + + // Don't kill the target here as it's being used for the source. + dst->m_texture = tex; + dst->m_unscaled_size = new_size; + } } else { - dst->ResizeTexture(new_size.x, new_size.y, true, true, GSVector4i(dRect)); + if (!req_copy) + dst->ResizeTexture(new_size.x, new_size.y, true, true, GSVector4i(dRect)); + else + { + GSTexture* tex = type == RenderTarget ? g_gs_device->CreateRenderTarget(new_scaled_size.x, new_scaled_size.y, GSTexture::Format::Color, clear) : + g_gs_device->CreateDepthStencil(new_scaled_size.x, new_scaled_size.y, GSTexture::Format::DepthStencil, clear); + if (!tex) + return nullptr; + + if (scale_down) + g_gs_device->StretchRect(dst->m_texture, source_rect, tex, dRect, (type == RenderTarget) ? ShaderConvert::COPY : ShaderConvert::DEPTH_COPY, false); + else + g_gs_device->StretchRect(dst->m_texture, source_rect, tex, dRect, (type == RenderTarget) ? ShaderConvert::COPY : ShaderConvert::DEPTH_COPY, false); + + g_perfmon.Put(GSPerfMon::TextureCopies, 1); + m_target_memory_usage = (m_target_memory_usage - dst->m_texture->GetMemUsage()) + tex->GetMemUsage(); + + g_gs_device->Recycle(dst->m_texture); + + dst->m_texture = tex; + dst->m_unscaled_size = new_size; + } } } @@ -2540,7 +2611,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe } else { - if (!dst->m_dirty.empty()) + if (!dst->m_dirty.empty() && bp == dst->m_TEX0.TBP0) { GL_INS("TC: Clearing dirty list for %s[%x] because we're overwriting the whole target.", to_string(type), dst->m_TEX0.TBP0); dst->m_dirty.clear(); @@ -2797,8 +2868,10 @@ GSTextureCache::Target* GSTextureCache::CreateTarget(GIFRegTEX0 TEX0, const GSVe if (!is_frame) { - // Not *strictly* correct if RGB is masked, but we won't use it as a texture if not.. - dst->m_valid_rgb = true; + // Not having this valid could make things explode, but I do enjoy watching the world burn (and this is actually more correct). + dst->m_valid_rgb =true; + + const u32 mask = GSLocalMemory::m_psm[TEX0.PSM].fmsk; // If there is an opposite target without valid RGB, we need to match them up auto& rev_list = m_dst[1 - type]; @@ -2807,15 +2880,19 @@ GSTextureCache::Target* GSTextureCache::CreateTarget(GIFRegTEX0 TEX0, const GSVe Target* const rev_t = *j; if (rev_t->m_TEX0.TBP0 == dst->m_TEX0.TBP0 && GSLocalMemory::m_psm[rev_t->m_TEX0.PSM].bpp == GSLocalMemory::m_psm[dst->m_TEX0.PSM].bpp) { - if (!rev_t->m_valid_rgb) + if (GSLocalMemory::m_psm[rev_t->m_TEX0.PSM].trbpp == 24 && ((fbmask & 0x00FFFFFF) & mask) == (mask & 0x00FFFFFF)) + dst->m_valid_rgb = false; + + if (!rev_t->m_valid_rgb && dst->m_valid_rgb) rev_t->m_was_dst_matched = true; + break; } ++j; } const int bpp = GSLocalMemory::m_psm[TEX0.PSM].trbpp; - const u32 mask = GSLocalMemory::m_psm[TEX0.PSM].fmsk; + // If the alpha is masked and preloaded, we need to say it's valid else textures might fail to use the whole texture if RGB is valid. if (((fbmask & 0xFF000000) & mask) != (mask & 0xFF000000) && bpp != 24) { @@ -7060,7 +7137,7 @@ void GSTextureCache::Target::UpdateValidity(const GSVector4i& rect, bool can_res bool GSTextureCache::Target::ResizeTexture(int new_unscaled_width, int new_unscaled_height, bool recycle_old, bool require_new_rect, GSVector4i new_rect, bool keep_old) { - if (m_unscaled_size.x == new_unscaled_width && m_unscaled_size.y == new_unscaled_height) + if (m_unscaled_size.x == new_unscaled_width && m_unscaled_size.y == new_unscaled_height && !require_new_rect) return true; const GSVector2i size = m_texture->GetSize(); From 0dbae3c46c624aa7bd827d27286dbb561be0b74e Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Thu, 20 Feb 2025 11:02:19 +0000 Subject: [PATCH 120/162] GS/HW: Reduce number of targets in pool when doing channels shuffles --- pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 41 ++++++++++++++++++-------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index 9dc0988ae9..261bc42c1e 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -6207,38 +6207,55 @@ __ri void GSRendererHW::HandleTextureHazards(const GSTextureCache::Target* rt, c // Restricting it also breaks Tom and Jerry... if (m_downscale_source || m_channel_shuffle || tex->m_texture->GetType() == GSTexture::Type::DepthStencil) { - copy_range = src_bounds; - copy_size = src_unscaled_size; + if (m_channel_shuffle) + { + // Just make it the size of the RT, since it will be making a new target every draw (most likely) it saves making 130 new targets and drown + copy_size.x = rt->m_unscaled_size.x; + copy_size.y = rt->m_unscaled_size.y; + copy_range.x = copy_range.y = 0; + copy_range.z = std::min(m_r.width(), copy_size.x); + copy_range.w = std::min(m_r.height(), copy_size.y); + } + else + { + copy_range = src_bounds; + copy_size = src_unscaled_size; + } GSVector4i::storel(©_dst_offset, copy_range); if (m_channel_shuffle && (tex_diff || frame_diff)) { const u32 page_offset = (m_cached_ctx.TEX0.TBP0 - src_target->m_TEX0.TBP0) >> 5; - const u32 vertical_offset = (page_offset / src_target->m_TEX0.TBW) * GSLocalMemory::m_psm[src_target->m_TEX0.PSM].pgs.y; const u32 horizontal_offset = (page_offset % src_target->m_TEX0.TBW) * GSLocalMemory::m_psm[src_target->m_TEX0.PSM].pgs.x; + const u32 vertical_offset = (page_offset / src_target->m_TEX0.TBW) * GSLocalMemory::m_psm[src_target->m_TEX0.PSM].pgs.y; - copy_range.y += vertical_offset; copy_range.x += horizontal_offset; - copy_size.y -= vertical_offset; - copy_size.x -= horizontal_offset; + copy_range.y += vertical_offset; + copy_range.z += horizontal_offset; + copy_range.w += vertical_offset; + + if (!m_channel_shuffle) + { + copy_size.y -= vertical_offset; + copy_size.x -= horizontal_offset; + } target_region = false; source_region.bits = 0; //copied_rt = tex->m_from_target != nullptr; if (m_in_target_draw && (page_offset || frame_diff)) { - copy_size.x = m_r.width(); - copy_size.y = m_r.height(); - copy_range.w = copy_range.y + copy_size.y; - copy_range.z = copy_range.x + copy_size.x; + copy_range.z = copy_range.x + m_r.width(); + copy_range.w = copy_range.y + m_r.height(); if (tex_diff != frame_diff) { GSVector4i::storel(©_dst_offset, m_r); - copy_size.x += copy_dst_offset.x; - copy_size.y += copy_dst_offset.y; } } + + copy_range.z = std::min(copy_range.z, copy_size.x); + copy_range.w = std::min(copy_range.w, copy_size.y); } } else From ca9c8414771c1fc6ad924d6aa54c5c40f9b4bf16 Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Thu, 20 Feb 2025 18:34:10 +0000 Subject: [PATCH 121/162] GS/HW: Attempt to reduce the load of copies for offset Z --- pcsx2/GS/GSState.h | 2 + pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 99 +++++++++++++++++++----- pcsx2/GS/Renderers/HW/GSTextureCache.cpp | 33 ++++++++ pcsx2/GS/Renderers/HW/GSTextureCache.h | 10 ++- 4 files changed, 124 insertions(+), 20 deletions(-) diff --git a/pcsx2/GS/GSState.h b/pcsx2/GS/GSState.h index cf09202b40..638b87090f 100644 --- a/pcsx2/GS/GSState.h +++ b/pcsx2/GS/GSState.h @@ -239,6 +239,8 @@ public: bool m_texflush_flag = false; bool m_isPackedUV_HackFlag = false; bool m_channel_shuffle = false; + bool m_using_temp_z = false; + bool m_temp_z_full_copy = false; bool m_in_target_draw = false; bool m_channel_shuffle_abort = false; diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index 261bc42c1e..566061f6b5 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -2565,6 +2565,7 @@ void GSRendererHW::Draw() m_texture_shuffle = false; m_copy_16bit_to_target_shuffle = false; m_same_group_texture_shuffle = false; + m_using_temp_z = false; const bool is_split_texture_shuffle = (m_split_texture_shuffle_pages > 0); if (is_split_texture_shuffle) @@ -2677,6 +2678,12 @@ void GSRendererHW::Draw() GSTextureCache::DepthStencil, ds_end_bp)) == nullptr || m_r.rintersect(tgt->m_valid).eq(tgt->m_valid)); + if (g_texture_cache->GetTemporaryZ() != nullptr && (m_cached_ctx.FRAME.Block() == g_texture_cache->GetTemporaryZInfo().ZBP || m_cached_ctx.ZBUF.Block() == g_texture_cache->GetTemporaryZInfo().ZBP)) + { + g_texture_cache->InvalidateTemporaryZ(); + } + + if (overwriting_whole_rt && overwriting_whole_ds && TryGSMemClear(no_rt, preserve_rt_color, is_zero_color_clear, rt_end_bp, no_ds, preserve_depth, is_zero_depth_clear, ds_end_bp)) @@ -3262,15 +3269,29 @@ void GSRendererHW::Draw() // Z isn't offset but RT is, so we need a temp Z to align it, hopefully nothing will ever write to the Z too, right?? if (ds && vertical_offset && (m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0) != (m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0)) { - const int z_vertical_offset = ((static_cast(m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0) / 32) / std::max(rt->m_TEX0.TBW, 1U)) * GSLocalMemory::m_psm[m_cached_ctx.ZBUF.PSM].pgs.y; - GL_CACHE("RT in RT Z copy on draw %d z_vert_offset %d z_offset %d", s_n, z_vertical_offset, vertical_offset); - GSVector4i dRect = GSVector4i(0, vertical_offset * ds->m_scale, ds->m_unscaled_size.x * ds->m_scale, std::min(vertical_offset + m_r.w + 1, vertical_offset + ds->m_unscaled_size.y) * ds->m_scale); - const int new_height = std::max(static_cast(ds->m_unscaled_size.y * ds->m_scale), dRect.w); - GSTexture* tex = g_gs_device->CreateDepthStencil(ds->m_unscaled_size.x * ds->m_scale, new_height, GSTexture::Format::DepthStencil, true); - g_gs_device->StretchRect(ds->m_texture, GSVector4(0.0f, z_vertical_offset / static_cast(ds->m_unscaled_size.y), 1.0f, std::min(z_vertical_offset + m_r.w + 1, ds->m_unscaled_size.y) / static_cast(ds->m_unscaled_size.y)), tex, GSVector4(dRect), ShaderConvert::DEPTH_COPY, false); + if (g_texture_cache->GetTemporaryZ() != nullptr) + { + GSTextureCache::TempZAddress z_address_info = g_texture_cache->GetTemporaryZInfo(); - g_texture_cache->SetTemporaryZ(tex); + if (ds->m_TEX0.TBP0 != z_address_info.ZBP || z_address_info.offset != (vertical_offset - z_vertical_offset)) + g_texture_cache->InvalidateTemporaryZ(); + } + + if (g_texture_cache->GetTemporaryZ() == nullptr) + { + m_temp_z_full_copy = false; + u32 vertical_size = std::max(rt->m_unscaled_size.y, ds->m_unscaled_size.y); + GL_CACHE("RT in RT Z copy on draw %d z_vert_offset %d z_offset %d", s_n, z_vertical_offset, vertical_offset); + GSVector4i dRect = GSVector4i(0, vertical_offset * ds->m_scale, ds->m_unscaled_size.x * ds->m_scale, (vertical_offset + ds->m_unscaled_size.y - z_vertical_offset) * ds->m_scale); + const int new_height = std::max(static_cast(vertical_size * ds->m_scale), dRect.w); + GSTexture* tex = g_gs_device->CreateDepthStencil(ds->m_unscaled_size.x * ds->m_scale, new_height, GSTexture::Format::DepthStencil, true); + g_gs_device->StretchRect(ds->m_texture, GSVector4(0.0f, z_vertical_offset / static_cast(ds->m_unscaled_size.y), 1.0f, (ds->m_unscaled_size.y - z_vertical_offset) / static_cast(ds->m_unscaled_size.y)), tex, GSVector4(dRect), ShaderConvert::DEPTH_COPY, false); + g_perfmon.Put(GSPerfMon::TextureCopies, 1); + g_texture_cache->SetTemporaryZ(tex); + g_texture_cache->SetTemporaryZInfo(ds->m_TEX0.TBP0, vertical_offset - z_vertical_offset); + } + m_using_temp_z = true; } GSVertex* v = &m_vertex.buff[0]; @@ -4020,7 +4041,7 @@ void GSRendererHW::Draw() { s = GetDrawDumpPath("%05d_f%05lld_rz0_%05x_(%05x)_%s.bmp", s_n, frame, m_cached_ctx.ZBUF.Block(), ds->m_TEX0.TBP0, psm_str(m_cached_ctx.ZBUF.PSM)); - if (g_texture_cache->GetTemporaryZ()) + if (m_using_temp_z) g_texture_cache->GetTemporaryZ()->Save(s); else if (ds->m_texture) ds->m_texture->Save(s); @@ -4120,6 +4141,13 @@ void GSRendererHW::Draw() // Remove overwritten Zs at the FBP. g_texture_cache->InvalidateVideoMemType(GSTextureCache::DepthStencil, m_cached_ctx.FRAME.Block(), m_cached_ctx.FRAME.PSM, m_texture_shuffle ? GetEffectiveTextureShuffleFbmsk() : fm); + + if (!m_using_temp_z && g_texture_cache->GetTemporaryZ() != nullptr) + { + GSTextureCache::TempZAddress temp_z_info = g_texture_cache->GetTemporaryZInfo(); + if (GSLocalMemory::GetStartBlockAddress(rt->m_TEX0.TBP0, rt->m_TEX0.TBW, rt->m_TEX0.PSM, real_rect) <= temp_z_info.ZBP && GSLocalMemory::GetEndBlockAddress(rt->m_TEX0.TBP0, rt->m_TEX0.TBW, rt->m_TEX0.PSM, real_rect) > temp_z_info.ZBP) + g_texture_cache->InvalidateTemporaryZ(); + } } if (zm != 0xffffffff && ds) @@ -4136,18 +4164,53 @@ void GSRendererHW::Draw() g_texture_cache->InvalidateVideoMemType( GSTextureCache::RenderTarget, m_cached_ctx.ZBUF.Block(), m_cached_ctx.ZBUF.PSM, zm); - - if (ds && g_texture_cache->GetTemporaryZ()) + if (m_using_temp_z) { if (m_cached_ctx.DepthWrite()) { - const int vertical_offset = ((static_cast(m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0) / 32) / std::max(static_cast(rt->m_TEX0.TBW), 1)) * frame_psm.pgs.y; - const int z_vertical_offset = ((static_cast(m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0) / 32) / std::max(rt->m_TEX0.TBW, 1U)) * GSLocalMemory::m_psm[m_cached_ctx.ZBUF.PSM].pgs.y; - const GSVector4i dRect = GSVector4i(0, z_vertical_offset * ds->m_scale, ds->m_unscaled_size.x * ds->m_scale, std::min(z_vertical_offset + m_r.w + 1 - vertical_offset, ds->m_unscaled_size.y) * ds->m_scale); + const int get_next_ctx = m_env.PRIM.CTXT; + const GSDrawingContext& next_ctx = m_env.CTXT[get_next_ctx]; + if ((m_state_flush_reason != CONTEXTCHANGE) || next_ctx.ZBUF.ZBP == m_context->ZBUF.ZBP && next_ctx.FRAME.FBP == m_context->FRAME.FBP) + { + m_temp_z_full_copy = true; + } + else + { + const int vertical_offset = ((static_cast(m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0) / 32) / std::max(static_cast(rt->m_TEX0.TBW), 1)) * frame_psm.pgs.y; + const int z_vertical_offset = ((static_cast(m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0) / 32) / std::max(rt->m_TEX0.TBW, 1U)) * GSLocalMemory::m_psm[m_cached_ctx.ZBUF.PSM].pgs.y; + + if (!m_temp_z_full_copy) + { + const GSVector4i dRect = GSVector4i(real_rect.x * ds->m_scale, (z_vertical_offset + (real_rect.y - vertical_offset)) * ds->m_scale, (real_rect.z + (1.0f / ds->m_scale)) * ds->m_scale, (z_vertical_offset + (real_rect.w + (1.0f / ds->m_scale) - vertical_offset)) * ds->m_scale); + const GSVector4 sRect = GSVector4((real_rect.x * ds->m_scale) / static_cast(g_texture_cache->GetTemporaryZ()->GetWidth()), static_cast(real_rect.y * ds->m_scale) / static_cast(g_texture_cache->GetTemporaryZ()->GetHeight()), ((real_rect.z + (1.0f / ds->m_scale)) * ds->m_scale) / static_cast(g_texture_cache->GetTemporaryZ()->GetWidth()), + static_cast((real_rect.w + (1.0f / ds->m_scale)) * ds->m_scale) / static_cast(g_texture_cache->GetTemporaryZ()->GetHeight())); + GL_CACHE("RT in RT Z copy back draw %d z_vert_offset %d z_offset %d", s_n, z_vertical_offset, vertical_offset); + g_gs_device->StretchRect(g_texture_cache->GetTemporaryZ(), sRect, ds->m_texture, GSVector4(dRect), ShaderConvert::DEPTH_COPY, false); + g_perfmon.Put(GSPerfMon::TextureCopies, 1); + } + else + { + const GSVector4i dRect = GSVector4i(0, ds->m_valid.y * ds->m_scale, ds->m_valid.z * ds->m_scale, ds->m_valid.w * ds->m_scale); + const GSVector4 sRect = GSVector4((ds->m_valid.x * ds->m_scale) / static_cast(g_texture_cache->GetTemporaryZ()->GetWidth()), static_cast((ds->m_valid.y + vertical_offset) * ds->m_scale) / static_cast(g_texture_cache->GetTemporaryZ()->GetHeight()), ((ds->m_valid.z + (1.0f / ds->m_scale)) * ds->m_scale) / static_cast(g_texture_cache->GetTemporaryZ()->GetWidth()), + static_cast(((ds->m_valid.w + vertical_offset) + (1.0f / ds->m_scale)) * ds->m_scale) / static_cast(g_texture_cache->GetTemporaryZ()->GetHeight())); + GL_CACHE("RT in RT Z copy back draw %d z_vert_offset %d z_offset %d", s_n, z_vertical_offset, vertical_offset); + g_gs_device->StretchRect(g_texture_cache->GetTemporaryZ(), sRect, ds->m_texture, GSVector4(dRect), ShaderConvert::DEPTH_COPY, false); + g_perfmon.Put(GSPerfMon::TextureCopies, 1); + } - GL_CACHE("RT in RT Z copy back draw %d z_vert_offset %d z_offset %d", s_n, z_vertical_offset, vertical_offset); - g_gs_device->StretchRect(g_texture_cache->GetTemporaryZ(), GSVector4(0.0f, (static_cast(vertical_offset) * ds->m_scale) / static_cast(g_texture_cache->GetTemporaryZ()->GetHeight()), 1.0f, - floor(static_cast(std::min(real_rect.w + 1, ds->m_unscaled_size.y + vertical_offset)) * ds->m_scale) / static_cast(g_texture_cache->GetTemporaryZ()->GetHeight())), ds->m_texture, GSVector4(dRect), ShaderConvert::DEPTH_COPY, false); + m_temp_z_full_copy = false; + } + } + } + else if (m_cached_ctx.DepthWrite() && g_texture_cache->GetTemporaryZ() != nullptr) + { + GSTextureCache::TempZAddress z_address_info = g_texture_cache->GetTemporaryZInfo(); + if (ds->m_TEX0.TBP0 == z_address_info.ZBP) + { + GL_CACHE("RT in RT Updating Z copy on draw %d z_offset %d", s_n, z_address_info.offset); + GSVector4i dRect = GSVector4i(real_rect.x * ds->m_scale, (z_address_info.offset + real_rect.y) * ds->m_scale, (real_rect.z + (1.0f / ds->m_scale)) * ds->m_scale, (z_address_info.offset + real_rect.w + (1.0f / ds->m_scale)) * ds->m_scale); + g_gs_device->StretchRect(ds->m_texture, GSVector4(real_rect.x / static_cast(ds->m_unscaled_size.x), real_rect.y / static_cast(ds->m_unscaled_size.y), (real_rect.z + (1.0f / ds->m_scale)) / static_cast(ds->m_unscaled_size.x), (real_rect.w + (1.0f / ds->m_scale)) / static_cast(ds->m_unscaled_size.y)), g_texture_cache->GetTemporaryZ(), GSVector4(dRect), ShaderConvert::DEPTH_COPY, false); + g_perfmon.Put(GSPerfMon::TextureCopies, 1); } } } @@ -6549,8 +6612,6 @@ void GSRendererHW::CleanupDraw(bool invalidate_temp_src) // Remove any RT source. if (invalidate_temp_src) g_texture_cache->InvalidateTemporarySource(); - - g_texture_cache->InvalidateTemporaryZ(); // Restore Scissor. m_context->UpdateScissor(); @@ -6590,7 +6651,7 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta m_conf.cb_vs.texture_offset = {}; m_conf.ps.scanmsk = env.SCANMSK.MSK; m_conf.rt = rt ? rt->m_texture : nullptr; - m_conf.ds = ds ? (g_texture_cache->GetTemporaryZ() ? g_texture_cache->GetTemporaryZ() : ds->m_texture) : nullptr; + m_conf.ds = ds ? (m_using_temp_z ? g_texture_cache->GetTemporaryZ() : ds->m_texture) : nullptr; pxAssert(!ds || !rt || (ds->m_texture->GetSize().x == rt->m_texture->GetSize().x && ds->m_texture->GetSize().y == rt->m_texture->GetSize().y)); diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index 3bda1bc33e..93003fb16c 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -3893,6 +3893,12 @@ void GSTextureCache::InvalidateVideoMem(const GSOffset& off, const GSVector4i& r GL_CACHE("TC: Dirty Target(%s) (0x%x) r(%d,%d,%d,%d)", to_string(type), t->m_TEX0.TBP0, r.x, r.y, r.z, r.w); + if (t->m_type == DepthStencil && GetTemporaryZ() != nullptr) + { + if (GetTemporaryZInfo().ZBP == t->m_TEX0.TBP0) + InvalidateTemporaryZ(); + } + if (GSLocalMemory::m_psm[psm].depth) DirtyRectByPage(bp, psm, bw, t, r); else @@ -7005,6 +7011,22 @@ void GSTextureCache::Target::Update(bool cannot_scale) m_alpha_range |= alpha_minmax.first != alpha_minmax.second; } g_gs_device->Recycle(t); + + if (m_type == DepthStencil && g_texture_cache->GetTemporaryZ() != nullptr) + { + if (g_texture_cache->GetTemporaryZInfo().ZBP == m_TEX0.TBP0) + { + GSTextureCache::TempZAddress z_address_info = g_texture_cache->GetTemporaryZInfo(); + if (m_TEX0.TBP0 == z_address_info.ZBP) + { + //GL_CACHE("RT in RT Updating Z copy on draw %d z_offset %d", s_n, z_address_info.offset); + GSVector4i dRect = GSVector4i(total_rect.x * m_scale, (z_address_info.offset + total_rect.y) * m_scale, (total_rect.z + (1.0f / m_scale)) * m_scale, (z_address_info.offset + total_rect.w + (1.0f / m_scale)) * m_scale); + g_gs_device->StretchRect(m_texture, GSVector4(total_rect.x / static_cast(m_unscaled_size.x), total_rect.y / static_cast(m_unscaled_size.y), (total_rect.z + (1.0f / m_scale)) / static_cast(m_unscaled_size.x), (total_rect.w + (1.0f / m_scale)) / static_cast(m_unscaled_size.y)), g_texture_cache->GetTemporaryZ(), GSVector4(dRect), ShaderConvert::DEPTH_COPY, false); + g_perfmon.Put(GSPerfMon::TextureCopies, 1); + } + } + } + m_dirty.clear(); } @@ -7514,6 +7536,17 @@ void GSTextureCache::InvalidateTemporarySource() m_temporary_source = nullptr; } +GSTextureCache::TempZAddress GSTextureCache::GetTemporaryZInfo() +{ + return m_temporary_z_info; +} + +void GSTextureCache::SetTemporaryZInfo(u32 address, u32 offset) +{ + m_temporary_z_info.ZBP = address; + m_temporary_z_info.offset = offset; +} + void GSTextureCache::SetTemporaryZ(GSTexture* temp_z) { m_temporary_z = temp_z; diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.h b/pcsx2/GS/Renderers/HW/GSTextureCache.h index a0a434f448..807c1280d4 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.h +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.h @@ -206,6 +206,12 @@ public: bool operator()(const PaletteKey& lhs, const PaletteKey& rhs) const; }; + struct TempZAddress + { + u32 ZBP; + u32 offset; + }; + class Target : public Surface { public: @@ -428,6 +434,7 @@ protected: Source* m_temporary_source = nullptr; // invalidated after the draw GSTexture* m_temporary_z = nullptr; // invalidated after the draw + TempZAddress m_temporary_z_info; std::unique_ptr m_color_download_texture; std::unique_ptr m_uint16_download_texture; @@ -554,7 +561,8 @@ public: void InvalidateTemporarySource(); void SetTemporaryZ(GSTexture* temp_z); GSTexture* GetTemporaryZ(); - + TempZAddress GetTemporaryZInfo(); + void SetTemporaryZInfo(u32 address, u32 offset); /// Invalidates a temporary Z, a partial copy only created from the current DS for the current draw when Z is not offset but RT is void InvalidateTemporaryZ(); From 13ee2abeef1c39d3433c2005f331fe792c5f3e87 Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Tue, 4 Mar 2025 12:58:23 +0000 Subject: [PATCH 122/162] GS/HW: Fix some clear behaviour --- pcsx2/GS/GSState.cpp | 2 +- pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/pcsx2/GS/GSState.cpp b/pcsx2/GS/GSState.cpp index adb28d0777..eac0daf7eb 100644 --- a/pcsx2/GS/GSState.cpp +++ b/pcsx2/GS/GSState.cpp @@ -3093,7 +3093,7 @@ void GSState::CalculatePrimitiveCoversWithoutGaps() } else if (m_vt.m_primclass == GS_TRIANGLE_CLASS) { - m_primitive_covers_without_gaps = (m_index.tail == 6 && TrianglesAreQuads()) ? m_primitive_covers_without_gaps : GapsFound; + m_primitive_covers_without_gaps = ((m_index.tail % 6) == 0 && TrianglesAreQuads()) ? m_primitive_covers_without_gaps : GapsFound; return; } else if (m_vt.m_primclass != GS_SPRITE_CLASS) diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index 566061f6b5..e50a664e46 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -3353,9 +3353,23 @@ void GSRendererHW::Draw() rt->UpdateValidity(m_r, !frame_masked); rt->UpdateDrawn(m_r, !frame_masked); } - else if (IsPageCopy() && m_cached_ctx.FRAME.FBW == 1) + else if ((IsPageCopy() || is_possible_mem_clear) && m_r.width() <= frame_psm.pgs.x && m_r.height() <= frame_psm.pgs.y) { - rt->UpdateValidity(GSVector4i::loadh(GSVector2i(GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].pgs.x, GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].pgs.y + vertical_offset)), true); + const int get_next_ctx = m_env.PRIM.CTXT; + const GSDrawingContext& next_ctx = m_env.CTXT[get_next_ctx]; + GSVector4i update_valid = GSVector4i::loadh(GSVector2i(horizontal_offset + GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].pgs.x, GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].pgs.y + vertical_offset)); + rt->UpdateValidity(update_valid, true); + if (is_possible_mem_clear) + { + if ((horizontal_offset + GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].pgs.x) >= static_cast(rt->m_TEX0.TBW * 64) && next_ctx.FRAME.Block() == (m_cached_ctx.FRAME.Block() + 0x20)) + { + update_valid.x = 0; + update_valid.z = GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].pgs.x; + update_valid.y += GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].pgs.y; + update_valid.w += GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].pgs.y; + rt->UpdateValidity(update_valid, true); + } + } } } } From a6d5598c08bb8ceabbea18719c8b239b079de982 Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Tue, 4 Mar 2025 19:10:41 +0000 Subject: [PATCH 123/162] GS/HW: More RT in RT regression fixes --- bin/resources/shaders/dx11/tfx.fx | 2 +- bin/resources/shaders/opengl/tfx_fs.glsl | 2 +- bin/resources/shaders/vulkan/tfx.glsl | 4 +- pcsx2/GS/GS.cpp | 9 ++ pcsx2/GS/GSState.cpp | 45 +++++- pcsx2/GS/GSState.h | 2 + pcsx2/GS/Renderers/Common/GSRenderer.cpp | 9 -- pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 181 +++++++++++++-------- pcsx2/GS/Renderers/HW/GSTextureCache.cpp | 192 +++++++++++++++++------ pcsx2/GS/Renderers/HW/GSTextureCache.h | 2 +- pcsx2/GS/Renderers/Metal/tfx.metal | 4 +- pcsx2/ShaderCacheVersion.h | 2 +- 12 files changed, 316 insertions(+), 138 deletions(-) diff --git a/bin/resources/shaders/dx11/tfx.fx b/bin/resources/shaders/dx11/tfx.fx index 33ca3634be..a0de0b4246 100644 --- a/bin/resources/shaders/dx11/tfx.fx +++ b/bin/resources/shaders/dx11/tfx.fx @@ -1127,7 +1127,7 @@ PS_OUTPUT ps_main(PS_INPUT input) { if (PS_PROCESS_BA == SHUFFLE_READWRITE && PS_PROCESS_RG == SHUFFLE_READWRITE) { - C.br = C.rb; + C.br = C.rb; C.ag = C.ga; } else if(PS_PROCESS_BA & SHUFFLE_READ) diff --git a/bin/resources/shaders/opengl/tfx_fs.glsl b/bin/resources/shaders/opengl/tfx_fs.glsl index b19dee992b..8dc7f852ff 100644 --- a/bin/resources/shaders/opengl/tfx_fs.glsl +++ b/bin/resources/shaders/opengl/tfx_fs.glsl @@ -1095,7 +1095,7 @@ void ps_main() C.ga = vec2(float((denorm_c.g >> 6) | ((denorm_c.b >> 3) << 2) | (denorm_TA.x & 0x80u))); #elif PS_SHUFFLE_ACROSS #if(PS_PROCESS_BA == SHUFFLE_READWRITE && PS_PROCESS_RG == SHUFFLE_READWRITE) - C.br = C.rb; + C.br = C.rb; C.ag = C.ga; #elif(PS_PROCESS_BA & SHUFFLE_READ) C.rb = C.bb; diff --git a/bin/resources/shaders/vulkan/tfx.glsl b/bin/resources/shaders/vulkan/tfx.glsl index 757313ff90..3061226288 100644 --- a/bin/resources/shaders/vulkan/tfx.glsl +++ b/bin/resources/shaders/vulkan/tfx.glsl @@ -1343,8 +1343,6 @@ void main() #endif #endif - - // Special case for 32bit input and 16bit output, shuffle used by The Godfather #if PS_SHUFFLE_SAME #if (PS_PROCESS_BA & SHUFFLE_READ) @@ -1362,7 +1360,7 @@ void main() // Write RB part. Mask will take care of the correct destination #elif PS_SHUFFLE_ACROSS #if(PS_PROCESS_BA == SHUFFLE_READWRITE && PS_PROCESS_RG == SHUFFLE_READWRITE) - C.br = C.rb; + C.br = C.rb; C.ag = C.ga; #elif(PS_PROCESS_BA & SHUFFLE_READ) C.rb = C.bb; diff --git a/pcsx2/GS/GS.cpp b/pcsx2/GS/GS.cpp index e17bad25ec..43f88e826d 100644 --- a/pcsx2/GS/GS.cpp +++ b/pcsx2/GS/GS.cpp @@ -435,6 +435,15 @@ void GSgifTransfer3(u8* mem, u32 size) void GSvsync(u32 field, bool registers_written) { + // Update this here because we need to check if the pending draw affects the current frame, so our regs need to be updated. + g_gs_renderer->PCRTCDisplays.SetVideoMode(g_gs_renderer->GetVideoMode()); + g_gs_renderer->PCRTCDisplays.EnableDisplays(g_gs_renderer->m_regs->PMODE, g_gs_renderer->m_regs->SMODE2, g_gs_renderer->isReallyInterlaced()); + g_gs_renderer->PCRTCDisplays.CheckSameSource(); + g_gs_renderer->PCRTCDisplays.SetRects(0, g_gs_renderer->m_regs->DISP[0].DISPLAY, g_gs_renderer->m_regs->DISP[0].DISPFB); + g_gs_renderer->PCRTCDisplays.SetRects(1, g_gs_renderer->m_regs->DISP[1].DISPLAY, g_gs_renderer->m_regs->DISP[1].DISPFB); + g_gs_renderer->PCRTCDisplays.CalculateDisplayOffset(g_gs_renderer->m_scanmask_used); + g_gs_renderer->PCRTCDisplays.CalculateFramebufferOffset(g_gs_renderer->m_scanmask_used); + // Do not move the flush into the VSync() method. It's here because EE transfers // get cleared in HW VSync, and may be needed for a buffered draw (FFX FMVs). g_gs_renderer->Flush(GSState::VSYNC); diff --git a/pcsx2/GS/GSState.cpp b/pcsx2/GS/GSState.cpp index eac0daf7eb..cd208b2c71 100644 --- a/pcsx2/GS/GSState.cpp +++ b/pcsx2/GS/GSState.cpp @@ -1477,6 +1477,35 @@ void GSState::Flush(GSFlushReason reason) if (m_index.tail > 0) { + // Unless Vsync really needs the pending draw, don't do it when VSync happens as it can really screw up our heuristics when looking ahead. + if (reason == VSYNC) + { + GSDrawingContext* draw_ctx = &m_prev_env.CTXT[m_prev_env.PRIM.CTXT]; + const u32 start_bp = GSLocalMemory::GetStartBlockAddress(draw_ctx->FRAME.Block(), draw_ctx->FRAME.FBW, draw_ctx->FRAME.PSM, temp_draw_rect); + const u32 end_bp = GSLocalMemory::GetEndBlockAddress(draw_ctx->FRAME.Block(), draw_ctx->FRAME.FBW, draw_ctx->FRAME.PSM, temp_draw_rect); + bool needs_flush[2] = {PCRTCDisplays.PCRTCDisplays[0].enabled, PCRTCDisplays.PCRTCDisplays[1].enabled}; + + if (PCRTCDisplays.PCRTCDisplays[1].enabled) + { + const u32 out_start_bp = GSLocalMemory::GetStartBlockAddress(PCRTCDisplays.PCRTCDisplays[1].Block(), PCRTCDisplays.PCRTCDisplays[1].FBW, PCRTCDisplays.PCRTCDisplays[1].PSM, PCRTCDisplays.PCRTCDisplays[1].framebufferRect); + const u32 out_end_bp = GSLocalMemory::GetEndBlockAddress(PCRTCDisplays.PCRTCDisplays[1].Block(), PCRTCDisplays.PCRTCDisplays[1].FBW, PCRTCDisplays.PCRTCDisplays[1].PSM, PCRTCDisplays.PCRTCDisplays[1].framebufferRect); + + if (out_start_bp > end_bp || out_end_bp < start_bp) + needs_flush[1] = false; + } + + if (PCRTCDisplays.PCRTCDisplays[0].enabled) + { + const u32 out_start_bp = GSLocalMemory::GetStartBlockAddress(PCRTCDisplays.PCRTCDisplays[0].Block(), PCRTCDisplays.PCRTCDisplays[0].FBW, PCRTCDisplays.PCRTCDisplays[0].PSM, PCRTCDisplays.PCRTCDisplays[0].framebufferRect); + const u32 out_end_bp = GSLocalMemory::GetEndBlockAddress(PCRTCDisplays.PCRTCDisplays[0].Block(), PCRTCDisplays.PCRTCDisplays[0].FBW, PCRTCDisplays.PCRTCDisplays[0].PSM, PCRTCDisplays.PCRTCDisplays[0].framebufferRect); + + if (out_start_bp > end_bp || out_end_bp < start_bp) + needs_flush[0] = false; + } + + if (!needs_flush[0] && !needs_flush[1]) + return; + } m_state_flush_reason = reason; // Used to prompt the current draw that it's modifying its own CLUT. @@ -1942,10 +1971,10 @@ void GSState::Write(const u8* mem, int len) m_draw_transfers.push_back(new_transfer); } - GL_CACHE("Write! %u ... => 0x%x W:%d F:%s (DIR %d%d), dPos(%d %d) size(%d %d)", s_transfer_n, + GL_CACHE("Write! %u ... => 0x%x W:%d F:%s (DIR %d%d), dPos(%d %d) size(%d %d) draw %d", s_transfer_n, blit.DBP, blit.DBW, psm_str(blit.DPSM), m_env.TRXPOS.DIRX, m_env.TRXPOS.DIRY, - m_env.TRXPOS.DSAX, m_env.TRXPOS.DSAY, w, h); + m_env.TRXPOS.DSAX, m_env.TRXPOS.DSAY, w, h, s_n); if (len >= m_tr.total) { @@ -3093,7 +3122,7 @@ void GSState::CalculatePrimitiveCoversWithoutGaps() } else if (m_vt.m_primclass == GS_TRIANGLE_CLASS) { - m_primitive_covers_without_gaps = ((m_index.tail % 6) == 0 && TrianglesAreQuads()) ? m_primitive_covers_without_gaps : GapsFound; + m_primitive_covers_without_gaps = ((m_index.tail == 6 || ((m_index.tail % 6) == 0 && m_primitive_covers_without_gaps == FullCover)) && TrianglesAreQuads()) ? m_primitive_covers_without_gaps : GapsFound; return; } else if (m_vt.m_primclass != GS_SPRITE_CLASS) @@ -3123,7 +3152,7 @@ __forceinline bool GSState::IsAutoFlushDraw(u32 prim) { // Pretty confident here... GSVertex* buffer = &m_vertex.buff[0]; - const bool const_spacing = std::abs(buffer[m_index.buff[0]].U - buffer[m_index.buff[0]].XYZ.X) == std::abs(m_v.U - m_v.XYZ.X) && std::abs(buffer[m_index.buff[1]].XYZ.X - buffer[m_index.buff[0]].XYZ.X) < 64; + const bool const_spacing = std::abs(buffer[m_index.buff[0]].U - buffer[m_index.buff[0]].XYZ.X) == std::abs(m_v.U - m_v.XYZ.X) && std::abs(buffer[m_index.buff[1]].XYZ.X - buffer[m_index.buff[0]].XYZ.X) <= 256; // Lequal to 16 pixels apart. if (const_spacing) return false; @@ -4728,10 +4757,16 @@ GSVector2i GSState::GSPCRTCRegs::GetFramebufferSize(int display) void GSState::GSPCRTCRegs::SetRects(int display, GSRegDISPLAY displayReg, GSRegDISPFB framebufferReg) { // Save framebuffer information first, while we're here. + PCRTCDisplays[display].prevFramebufferReg.FBP = PCRTCDisplays[display].FBP; + PCRTCDisplays[display].prevFramebufferReg.FBW = PCRTCDisplays[display].FBW; + PCRTCDisplays[display].prevFramebufferReg.PSM = PCRTCDisplays[display].PSM; + PCRTCDisplays[display].prevFramebufferReg.DBX = PCRTCDisplays[display].DBX; + PCRTCDisplays[display].prevFramebufferReg.DBY = PCRTCDisplays[display].DBY; PCRTCDisplays[display].FBP = framebufferReg.FBP; PCRTCDisplays[display].FBW = framebufferReg.FBW; PCRTCDisplays[display].PSM = framebufferReg.PSM; - PCRTCDisplays[display].prevFramebufferReg = framebufferReg; + PCRTCDisplays[display].DBX = framebufferReg.DBX; + PCRTCDisplays[display].DBY = framebufferReg.DBY; // Probably not really enabled but will cause a mess. // Q-Ball Billiards enables both circuits but doesn't set one of them up. if (PCRTCDisplays[display].FBW == 0 && displayReg.DW == 0 && displayReg.DH == 0 && displayReg.MAGH == 0) diff --git a/pcsx2/GS/GSState.h b/pcsx2/GS/GSState.h index 638b87090f..4d3c696b08 100644 --- a/pcsx2/GS/GSState.h +++ b/pcsx2/GS/GSState.h @@ -323,6 +323,8 @@ public: int FBP; int FBW; int PSM; + int DBY; + int DBX; GSRegDISPFB prevFramebufferReg; GSVector2i prevDisplayOffset; GSVector2i displayOffset; diff --git a/pcsx2/GS/Renderers/Common/GSRenderer.cpp b/pcsx2/GS/Renderers/Common/GSRenderer.cpp index fc068c13a4..4fff716634 100644 --- a/pcsx2/GS/Renderers/Common/GSRenderer.cpp +++ b/pcsx2/GS/Renderers/Common/GSRenderer.cpp @@ -87,10 +87,6 @@ bool GSRenderer::Merge(int field) int y_offset[3] = { 0, 0, 0 }; const bool feedback_merge = m_regs->EXTWRITE.WRITE == 1; - PCRTCDisplays.SetVideoMode(GetVideoMode()); - PCRTCDisplays.EnableDisplays(m_regs->PMODE, m_regs->SMODE2, isReallyInterlaced()); - PCRTCDisplays.CheckSameSource(); - if (!PCRTCDisplays.PCRTCDisplays[0].enabled && !PCRTCDisplays.PCRTCDisplays[1].enabled) { m_real_size = GSVector2i(0, 0); @@ -101,11 +97,6 @@ bool GSRenderer::Merge(int field) const bool game_deinterlacing = (m_regs->DISP[0].DISPFB.DBY != PCRTCDisplays.PCRTCDisplays[0].prevFramebufferReg.DBY) != (m_regs->DISP[1].DISPFB.DBY != PCRTCDisplays.PCRTCDisplays[1].prevFramebufferReg.DBY); - PCRTCDisplays.SetRects(0, m_regs->DISP[0].DISPLAY, m_regs->DISP[0].DISPFB); - PCRTCDisplays.SetRects(1, m_regs->DISP[1].DISPLAY, m_regs->DISP[1].DISPFB); - PCRTCDisplays.CalculateDisplayOffset(m_scanmask_used); - PCRTCDisplays.CalculateFramebufferOffset(m_scanmask_used); - // Only need to check the right/bottom on software renderer, hardware always gets the full texture then cuts a bit out later. if (PCRTCDisplays.FrameRectMatch() && !PCRTCDisplays.FrameWrap() && !feedback_merge) { diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index e50a664e46..48aea90f5e 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -587,6 +587,12 @@ void GSRendererHW::ConvertSpriteTextureShuffle(u32& process_rg, u32& process_ba, v[i + reversed_pos].XYZ.X -= 128u; v[i + 1 - reversed_pos].XYZ.X -= 128u; } + // Needed for when there's no barriers. + if (v[i + reversed_U].U & 128) + { + v[i + reversed_U].U -= 128u; + v[i + 1 - reversed_U].U -= 128u; + } } if (half_bottom_vert) @@ -649,6 +655,14 @@ void GSRendererHW::ConvertSpriteTextureShuffle(u32& process_rg, u32& process_ba, else v[i + 1 - reversed_S].ST.S += offset_8pix; } + else + { + if (static_cast(v[i + reversed_S].ST.S * tw) & 8) + { + v[i + reversed_S].ST.S -= offset_8pix; + v[i + 1 - reversed_S].ST.S -= offset_8pix; + } + } if (half_bottom_vert) { @@ -2481,7 +2495,7 @@ void GSRendererHW::Draw() } // We trigger the sw prim render here super early, to avoid creating superfluous render targets. - if (CanUseSwPrimRender(no_rt, no_ds, draw_sprite_tex) && SwPrimRender(*this, true, true)) + if (CanUseSwPrimRender(no_rt, no_ds, draw_sprite_tex && m_process_texture) && SwPrimRender(*this, true, true)) { GL_CACHE("Possible texture decompression, drawn with SwPrimRender() (BP %x BW %u TBP0 %x TBW %u)", m_cached_ctx.FRAME.Block(), m_cached_ctx.FRAME.FBMSK, m_cached_ctx.TEX0.TBP0, m_cached_ctx.TEX0.TBW); @@ -2643,27 +2657,34 @@ void GSRendererHW::Draw() // Try to fix large single-page-wide draws. bool height_invalid = m_r.w >= 1024; + const GSVector2i& pgs = GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].pgs; if (height_invalid && m_cached_ctx.FRAME.FBW <= 1 && TryToResolveSinglePageFramebuffer(m_cached_ctx.FRAME, true)) { - const GSVector2i& pgs = GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].pgs; ReplaceVerticesWithSprite( GetDrawRectForPages(m_cached_ctx.FRAME.FBW, m_cached_ctx.FRAME.PSM, (m_r.w + (pgs.y - 1)) / pgs.y), GSVector2i(1, 1)); height_invalid = false; } - const bool is_zero_color_clear = (GetConstantDirectWriteMemClearColor() == 0 && !preserve_rt_color); - const bool is_zero_depth_clear = (GetConstantDirectWriteMemClearDepth() == 0 && !preserve_depth); + // Be careful of being 1 pixel from filled. + const bool page_aligned = (m_r.w % pgs.y) == (pgs.y - 1) || (m_r.w % pgs.y) == 0; + const bool is_zero_color_clear = (GetConstantDirectWriteMemClearColor() == 0 && !preserve_rt_color && page_aligned); + const bool is_zero_depth_clear = (GetConstantDirectWriteMemClearDepth() == 0 && !preserve_depth && page_aligned); // If it's an invalid-sized draw, do the mem clear on the CPU, we don't want to create huge targets. // If clearing to zero, don't bother creating the target. Games tend to clear more than they use, wasting VRAM/bandwidth. if (is_zero_color_clear || is_zero_depth_clear || height_invalid) { - const u32 rt_end_bp = GSLocalMemory::GetUnwrappedEndBlockAddress( + u32 rt_end_bp = GSLocalMemory::GetUnwrappedEndBlockAddress( m_cached_ctx.FRAME.Block(), m_cached_ctx.FRAME.FBW, m_cached_ctx.FRAME.PSM, m_r); const u32 ds_end_bp = GSLocalMemory::GetUnwrappedEndBlockAddress( m_cached_ctx.ZBUF.Block(), m_cached_ctx.FRAME.FBW, m_cached_ctx.ZBUF.PSM, m_r); + + // This can get missed by the double half clear, but we can make sure we nuke everything inside if the Z is butted up against the FRAME. + if (!no_ds && (rt_end_bp + 1) == m_cached_ctx.ZBUF.Block() && GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].trbpp == GSLocalMemory::m_psm[m_cached_ctx.ZBUF.PSM].trbpp) + rt_end_bp = ds_end_bp; + // If this is a partial clear of a larger buffer, we can't invalidate the target, since we'll be losing data // which only existed on the GPU. Assume a BW change is a new target, though. Test case: Persona 3 shadows. GSTextureCache::Target* tgt; @@ -2893,7 +2914,10 @@ void GSRendererHW::Draw() // TODO: Be able to send an alpha of 1.0 (blended with vertex alpha maybe?) so we can avoid sending the texture, since we don't always need it. // Example games: Evolution Snowboarding, Final Fantasy Dirge of Cerberus, Red Dead Revolver, Stuntman, Tony Hawk's Underground 2, Ultimate Spider-Man. if (!req_color && !alpha_used) + { m_process_texture = false; + possible_shuffle = false; + } else { src = tex_psm.depth ? g_texture_cache->LookupDepthSource(true, TEX0, env.TEXA, MIP_CLAMP, tmm.coverage, possible_shuffle, m_vt.IsLinear(), m_cached_ctx.FRAME.Block(), req_color, req_alpha) : @@ -2971,31 +2995,36 @@ void GSRendererHW::Draw() const GSVector4i unclamped_draw_rect = m_r; float target_scale = GetTextureScaleFactor(); + bool scaled_copy = false; int scale_draw = IsScalingDraw(src, m_primitive_covers_without_gaps != NoGapsType::GapsFound); - if (target_scale > 1.0f && scale_draw > 0) + if (GSConfig.UserHacks_NativeScaling != GSNativeScaling::Off) { - // 1 == Downscale, so we need to reduce the size of the target also. - // 2 == Upscale, so likely putting it over the top of the render target. - if (scale_draw == 1) + if (target_scale > 1.0f && scale_draw > 0) { - target_scale = 1.0f; - m_downscale_source = src->m_from_target->GetScale() > 1.0f; + // 1 == Downscale, so we need to reduce the size of the target also. + // 2 == Upscale, so likely putting it over the top of the render target. + if (scale_draw == 1) + { + target_scale = 1.0f; + m_downscale_source = src->m_from_target->GetScale() > 1.0f; + } + else + m_downscale_source = GSConfig.UserHacks_NativeScaling != GSNativeScaling::Aggressive ? false : src->m_from_target->GetScale() > 1.0f; // Bad for GTA + Full Spectrum Warrior, good for Sacred Blaze + Parappa. } else - m_downscale_source = GSConfig.UserHacks_NativeScaling != GSNativeScaling::Aggressive ? false : src->m_from_target->GetScale() > 1.0f; // Bad for GTA + Full Spectrum Warrior, good for Sacred Blaze + Parappa. - } - else - { - // if it's directly copying keep the scale - Ratchet and clank hits this, stops edge garbage happening. - // Keep it to small targets of 256 or lower. - if (scale_draw == -1 && src && src->m_from_target && src->m_from_target->m_downscaled && static_cast(m_cached_ctx.FRAME.FBW * 64) <= (PCRTCDisplays.GetResolution().x >> 1) && - (GSVector4i(m_vt.m_min.p).xyxy() == GSVector4i(m_vt.m_min.t).xyxy()).alltrue() && (GSVector4i(m_vt.m_max.p).xyxy() == GSVector4i(m_vt.m_max.t).xyxy()).alltrue()) { - target_scale = src->m_from_target->GetScale(); - scale_draw = 1; - } + // if it's directly copying keep the scale - Ratchet and clank hits this, stops edge garbage happening. + // Keep it to small targets of 256 or lower. + if (scale_draw == -1 && src && src->m_from_target && src->m_from_target->m_downscaled && static_cast(m_cached_ctx.FRAME.FBW * 64) <= (PCRTCDisplays.GetResolution().x >> 1) && + (GSVector4i(m_vt.m_min.p).xyxy() == GSVector4i(m_vt.m_min.t).xyxy()).alltrue() && (GSVector4i(m_vt.m_max.p).xyxy() == GSVector4i(m_vt.m_max.t).xyxy()).alltrue()) + { + target_scale = src->m_from_target->GetScale(); + scale_draw = 1; + scaled_copy = true; + } - m_downscale_source = false; + m_downscale_source = false; + } } if (IsPossibleChannelShuffle() && src && src->m_from_target && src->m_from_target->GetScale() != target_scale) @@ -3101,7 +3130,7 @@ void GSRendererHW::Draw() if (!no_rt) { - possible_shuffle |= draw_sprite_tex && m_primitive_covers_without_gaps != NoGapsType::FullCover && (((src && src->m_target && src->m_from_target && src->m_from_target->m_32_bits_fmt) && + possible_shuffle |= draw_sprite_tex && m_process_texture && m_primitive_covers_without_gaps != NoGapsType::FullCover && (((src && src->m_target && src->m_from_target && src->m_from_target->m_32_bits_fmt) && (GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].bpp == 16 || draw_uses_target) && GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].bpp == 16) || IsPossibleChannelShuffle()); @@ -3157,7 +3186,7 @@ void GSRendererHW::Draw() // Preserve downscaled target when copying directly from a downscaled target, or it's a normal draw using a downscaled target. Clears that are drawing to the target can also preserve size. // Of course if this size is different (in width) or this is a shuffle happening, this will be bypassed. - const bool preserve_downscale_draw = std::abs(scale_draw) == 1 || (scale_draw == 0 && ((src && src->m_from_target && src->m_from_target->m_downscaled) || is_possible_mem_clear == ClearType::ClearWithDraw)); + const bool preserve_downscale_draw = (GSConfig.UserHacks_NativeScaling != GSNativeScaling::Off && (std::abs(scale_draw) == 1 || (scale_draw == 0 && src && src->m_from_target && src->m_from_target->m_downscaled))) || is_possible_mem_clear == ClearType::ClearWithDraw; rt = g_texture_cache->LookupTarget(FRAME_TEX0, t_size, ((src && src->m_scale != 1) && GSConfig.UserHacks_NativeScaling == GSNativeScaling::Normal && !possible_shuffle) ? GetTextureScaleFactor() : target_scale, GSTextureCache::RenderTarget, true, fm, false, force_preload, preserve_rt_rgb, preserve_rt_alpha, lookup_rect, possible_shuffle, is_possible_mem_clear && FRAME_TEX0.TBP0 != m_cached_ctx.ZBUF.Block(), @@ -3198,7 +3227,7 @@ void GSRendererHW::Draw() return; } - rt = g_texture_cache->CreateTarget(FRAME_TEX0, t_size, GetValidSize(src), (scale_draw < 0 && is_possible_mem_clear != ClearType::NormalClear) ? src->m_from_target->GetScale() : target_scale, + rt = g_texture_cache->CreateTarget(FRAME_TEX0, t_size, GetValidSize(src), (GSConfig.UserHacks_NativeScaling != GSNativeScaling::Off && scale_draw < 0 && is_possible_mem_clear != ClearType::NormalClear) ? src->m_from_target->GetScale() : target_scale, GSTextureCache::RenderTarget, true, fm, false, force_preload, preserve_rt_color || possible_shuffle, lookup_rect, src); if (!rt) [[unlikely]] @@ -3245,7 +3274,7 @@ void GSRendererHW::Draw() if (rt->m_dirty.size()) { - for (int i = 0; i < rt->m_dirty.size(); i++) + for (int i = 0; i < static_cast(rt->m_dirty.size()); i++) { rt->m_dirty[i].r.y += new_offset; rt->m_dirty[i].r.w += new_offset; @@ -3266,34 +3295,6 @@ void GSRendererHW::Draw() if (vertical_offset || horizontal_offset) { - // Z isn't offset but RT is, so we need a temp Z to align it, hopefully nothing will ever write to the Z too, right?? - if (ds && vertical_offset && (m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0) != (m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0)) - { - const int z_vertical_offset = ((static_cast(m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0) / 32) / std::max(rt->m_TEX0.TBW, 1U)) * GSLocalMemory::m_psm[m_cached_ctx.ZBUF.PSM].pgs.y; - if (g_texture_cache->GetTemporaryZ() != nullptr) - { - GSTextureCache::TempZAddress z_address_info = g_texture_cache->GetTemporaryZInfo(); - - if (ds->m_TEX0.TBP0 != z_address_info.ZBP || z_address_info.offset != (vertical_offset - z_vertical_offset)) - g_texture_cache->InvalidateTemporaryZ(); - } - - if (g_texture_cache->GetTemporaryZ() == nullptr) - { - m_temp_z_full_copy = false; - u32 vertical_size = std::max(rt->m_unscaled_size.y, ds->m_unscaled_size.y); - GL_CACHE("RT in RT Z copy on draw %d z_vert_offset %d z_offset %d", s_n, z_vertical_offset, vertical_offset); - GSVector4i dRect = GSVector4i(0, vertical_offset * ds->m_scale, ds->m_unscaled_size.x * ds->m_scale, (vertical_offset + ds->m_unscaled_size.y - z_vertical_offset) * ds->m_scale); - const int new_height = std::max(static_cast(vertical_size * ds->m_scale), dRect.w); - GSTexture* tex = g_gs_device->CreateDepthStencil(ds->m_unscaled_size.x * ds->m_scale, new_height, GSTexture::Format::DepthStencil, true); - g_gs_device->StretchRect(ds->m_texture, GSVector4(0.0f, z_vertical_offset / static_cast(ds->m_unscaled_size.y), 1.0f, (ds->m_unscaled_size.y - z_vertical_offset) / static_cast(ds->m_unscaled_size.y)), tex, GSVector4(dRect), ShaderConvert::DEPTH_COPY, false); - g_perfmon.Put(GSPerfMon::TextureCopies, 1); - g_texture_cache->SetTemporaryZ(tex); - g_texture_cache->SetTemporaryZInfo(ds->m_TEX0.TBP0, vertical_offset - z_vertical_offset); - } - m_using_temp_z = true; - } - GSVertex* v = &m_vertex.buff[0]; for (u32 i = 0; i < m_vertex.tail; i++) @@ -3336,6 +3337,36 @@ void GSRendererHW::Draw() t_size.x = rt->m_unscaled_size.x - horizontal_offset; t_size.y = rt->m_unscaled_size.y - vertical_offset; + + // Z isn't offset but RT is, so we need a temp Z to align it, hopefully nothing will ever write to the Z too, right?? + if (ds && vertical_offset && (m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0) != (m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0)) + { + const int z_vertical_offset = ((static_cast(m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0) / 32) / std::max(rt->m_TEX0.TBW, 1U)) * GSLocalMemory::m_psm[m_cached_ctx.ZBUF.PSM].pgs.y; + if (g_texture_cache->GetTemporaryZ() != nullptr) + { + GSTextureCache::TempZAddress z_address_info = g_texture_cache->GetTemporaryZInfo(); + + if (ds->m_TEX0.TBP0 != z_address_info.ZBP || z_address_info.offset != static_cast(vertical_offset - z_vertical_offset)) + g_texture_cache->InvalidateTemporaryZ(); + } + + if (g_texture_cache->GetTemporaryZ() == nullptr) + { + m_temp_z_full_copy = false; + u32 vertical_size = std::max(rt->m_unscaled_size.y, ds->m_unscaled_size.y); + GL_CACHE("RT in RT Z copy on draw %d z_vert_offset %d z_offset %d", s_n, z_vertical_offset, vertical_offset); + GSVector4i dRect = GSVector4i(0, vertical_offset * ds->m_scale, ds->m_unscaled_size.x * ds->m_scale, (vertical_offset + ds->m_unscaled_size.y - z_vertical_offset) * ds->m_scale); + const int new_height = std::max(static_cast(vertical_size * ds->m_scale), dRect.w); + GSTexture* tex = g_gs_device->CreateDepthStencil(ds->m_unscaled_size.x * ds->m_scale, new_height, GSTexture::Format::DepthStencil, true); + g_gs_device->StretchRect(ds->m_texture, GSVector4(0.0f, z_vertical_offset / static_cast(ds->m_unscaled_size.y), 1.0f, (ds->m_unscaled_size.y - z_vertical_offset) / static_cast(ds->m_unscaled_size.y)), tex, GSVector4(dRect), ShaderConvert::DEPTH_COPY, false); + g_perfmon.Put(GSPerfMon::TextureCopies, 1); + g_texture_cache->SetTemporaryZ(tex); + g_texture_cache->SetTemporaryZInfo(ds->m_TEX0.TBP0, vertical_offset - z_vertical_offset); + t_size.y = std::max(new_height, t_size.y); + } + m_using_temp_z = true; + + } } // Don't resize if the BPP don't match. if (frame_psm.bpp == GSLocalMemory::m_psm[rt->m_TEX0.PSM].bpp) @@ -3373,7 +3404,7 @@ void GSRendererHW::Draw() } } } - + if (src && src->m_from_target && src->m_target_direct && src->m_from_target == rt) { src->m_texture = rt->m_texture; @@ -3392,6 +3423,7 @@ void GSRendererHW::Draw() // Slightly abusing the texture resize. ds->m_scale = target_scale; ds->m_unscaled_size = unscaled_size; + ds->m_downscaled = rt->m_downscaled; } // The target might have previously been a C32 format with valid alpha. If we're switching to C24, we need to preserve it. preserve_rt_alpha |= (GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].trbpp == 24 && rt->HasValidAlpha()); @@ -3713,17 +3745,16 @@ void GSRendererHW::Draw() if (m_cached_ctx.FRAME.FBMSK & 0xF0000000) rt->m_valid_alpha_high = false; } - if (FRAME_TEX0.TBW != 1 || (m_r.width() > frame_psm.pgs.x || m_r.height() > frame_psm.pgs.y)) + if (FRAME_TEX0.TBW != 1 || (m_r.width() > frame_psm.pgs.x || m_r.height() > frame_psm.pgs.y) || (scale_draw == 1 && !scaled_copy)) { FRAME_TEX0.TBP0 = rt->m_TEX0.TBP0; rt->m_TEX0 = FRAME_TEX0; - } } if (ds && (!is_possible_mem_clear || ds->m_TEX0.PSM != ZBUF_TEX0.PSM || (rt && ds->m_TEX0.TBW != rt->m_TEX0.TBW)) && !m_in_target_draw) { - if (ZBUF_TEX0.TBW != 1 || (m_r.width() > frame_psm.pgs.x || m_r.height() > frame_psm.pgs.y)) + if (ZBUF_TEX0.TBW != 1 || (m_r.width() > frame_psm.pgs.x || m_r.height() > frame_psm.pgs.y) || (scale_draw == 1 && !scaled_copy)) { ZBUF_TEX0.TBP0 = ds->m_TEX0.TBP0; ds->m_TEX0 = ZBUF_TEX0; @@ -3821,8 +3852,10 @@ void GSRendererHW::Draw() // We still need to make sure the dimensions of the targets match. // Limit new size to 2048, the GS can't address more than this so may avoid some bugs/crashes. - const int new_w = std::min(2048, std::max(new_size.x, std::max(rt ? rt->m_unscaled_size.x : 0, ds ? ds->m_unscaled_size.x : 0))); - const int new_h = std::min(2048, std::max(new_size.y, std::max(rt ? rt->m_unscaled_size.y : 0, ds ? ds->m_unscaled_size.y : 0))); + GSVector2i ds_size = m_using_temp_z ? GSVector2i(g_texture_cache->GetTemporaryZ()->GetSize() / ds->m_scale) : (ds ? ds->m_unscaled_size : GSVector2i(0,0)); + + const int new_w = std::min(2048, std::max(new_size.x, std::max(rt ? rt->m_unscaled_size.x : 0, ds ? ds_size.x : 0))); + const int new_h = std::min(2048, std::max(new_size.y, std::max(rt ? rt->m_unscaled_size.y : 0, ds ? ds_size.y : 0))); if (rt) { const u32 old_end_block = rt->m_end_block; @@ -3915,6 +3948,23 @@ void GSRendererHW::Draw() ds->ResizeTexture(new_w, new_h); + + if (m_using_temp_z) + { + const int z_width = g_texture_cache->GetTemporaryZ()->GetWidth() / ds->m_scale; + const int z_height = g_texture_cache->GetTemporaryZ()->GetHeight() / ds->m_scale; + + if (z_width != new_w || z_height != new_h) + { + GSVector4i dRect = GSVector4i(0, 0, g_texture_cache->GetTemporaryZ()->GetWidth(), g_texture_cache->GetTemporaryZ()->GetHeight()); + + GSTexture* tex = g_gs_device->CreateDepthStencil(new_w * ds->m_scale, new_h * ds->m_scale, GSTexture::Format::DepthStencil, true); + g_gs_device->StretchRect(g_texture_cache->GetTemporaryZ(), GSVector4(0.0f, 0.0f, 1.0f, 1.0f), tex, GSVector4(dRect), ShaderConvert::DEPTH_COPY, false); + g_perfmon.Put(GSPerfMon::TextureCopies, 1); + g_texture_cache->InvalidateTemporaryZ(); + g_texture_cache->SetTemporaryZ(tex); + } + } if (!m_texture_shuffle && !m_channel_shuffle) { ds->ResizeValidity(ds->GetUnscaledRect()); @@ -4111,7 +4161,7 @@ void GSRendererHW::Draw() } // Noting to do if no texture is sampled - if (PRIM->FST && draw_sprite_tex) + if (PRIM->FST && draw_sprite_tex && m_process_texture) { if ((GSConfig.UserHacks_RoundSprite > 1) || (GSConfig.UserHacks_RoundSprite == 1 && !m_vt.IsLinear())) { @@ -4184,7 +4234,7 @@ void GSRendererHW::Draw() { const int get_next_ctx = m_env.PRIM.CTXT; const GSDrawingContext& next_ctx = m_env.CTXT[get_next_ctx]; - if ((m_state_flush_reason != CONTEXTCHANGE) || next_ctx.ZBUF.ZBP == m_context->ZBUF.ZBP && next_ctx.FRAME.FBP == m_context->FRAME.FBP) + if ((m_state_flush_reason != CONTEXTCHANGE) || (next_ctx.ZBUF.ZBP == m_context->ZBUF.ZBP && next_ctx.FRAME.FBP == m_context->FRAME.FBP)) { m_temp_z_full_copy = true; } @@ -6667,7 +6717,7 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta m_conf.rt = rt ? rt->m_texture : nullptr; m_conf.ds = ds ? (m_using_temp_z ? g_texture_cache->GetTemporaryZ() : ds->m_texture) : nullptr; - pxAssert(!ds || !rt || (ds->m_texture->GetSize().x == rt->m_texture->GetSize().x && ds->m_texture->GetSize().y == rt->m_texture->GetSize().y)); + pxAssert(!ds || !rt || (m_conf.ds->GetSize().x == m_conf.rt->GetSize().x && m_conf.ds->GetSize().y == m_conf.rt->GetSize().y)); // Z setup has to come before channel shuffle EmulateZbuffer(ds); @@ -8511,9 +8561,6 @@ bool GSRendererHW::TextureCoversWithoutGapsNotEqual() int GSRendererHW::IsScalingDraw(GSTextureCache::Source* src, bool no_gaps) { - if (GSConfig.UserHacks_NativeScaling == GSNativeScaling::Off) - return 0; - const GSVector2i draw_size = GSVector2i(m_vt.m_max.p.x - m_vt.m_min.p.x, m_vt.m_max.p.y - m_vt.m_min.p.y); const GSVector2i tex_size = GSVector2i(m_vt.m_max.t.x - m_vt.m_min.t.x, m_vt.m_max.t.y - m_vt.m_min.t.y); diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index 93003fb16c..1c38ab1730 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -1645,7 +1645,7 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const DevCon.Warning("BP %x - 8bit bad match for target bp %x bw %d src %d format %d", bp, t->m_TEX0.TBP0, t->m_TEX0.TBW, bw, t->m_TEX0.PSM); continue; } - else if (!possible_shuffle && GSLocalMemory::m_psm[psm].trbpp == 8 && TEX0.TBW == 1) + else if (!possible_shuffle && GSLocalMemory::m_psm[psm].bpp <= 8 && TEX0.TBW == 1) { DevCon.Warning("Too small for relocation, skipping"); continue; @@ -1739,7 +1739,8 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const if (!t->HasValidBitsForFormat(psm, req_color, req_alpha, t->m_TEX0.TBW == TEX0.TBW) && !(possible_shuffle && GSLocalMemory::m_psm[psm].bpp == 16 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == 32)) continue; - if (!t->Inside(bp, bw, psm, block_boundary_rect)) + // Be careful of shuffles where it can shuffle the width of the target, even though it may not have all been drawn to. + if (!possible_shuffle && !t->Inside(bp, bw, psm, block_boundary_rect)) continue; x_offset = rect.x; @@ -1924,7 +1925,7 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const } else { - if (!possible_shuffle && TEX0.PSM == PSMT8 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp != 32) + if (!possible_shuffle && TEX0.PSM == PSMT8 && (GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp != 32 || !(t->m_valid_alpha_high && t->m_valid_alpha_low && t->m_valid_rgb))) { continue; } @@ -2094,7 +2095,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe { bool can_use = true; - if (dst && (GSState::s_n - dst->m_last_draw) < (GSState::s_n - t->m_last_draw)) + if (dst && ((GSState::s_n - dst->m_last_draw) < (GSState::s_n - t->m_last_draw) && dst->m_TEX0.TBP0 <= bp)) { DevCon.Warning("Ignoring target at %x as one at %x is newer", t->m_TEX0.TBP0, dst->m_TEX0.TBP0); i++; @@ -2121,7 +2122,6 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe { // When returning to being matched with the Z buffer in width, we need to make sure the RGB is up to date as it could get used later (Hitman Contracts). auto& rev_list = m_dst[1 - type]; - Target* dst_match = nullptr; for (auto j = rev_list.begin(); j != rev_list.end(); ++j) { Target* ds = *j; @@ -2155,15 +2155,10 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe { if (used) list.MoveFront(i.Index()); - dst = t; dst->m_32_bits_fmt |= (psm_s.bpp != 16); - - /*if (FindOverlappingTarget(dst)) - continue; - else*/ - break; + break; } else if(!(src && src->m_from_target == t)) { @@ -2178,18 +2173,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe // Probably pointing to half way through the target else if (!min_rect.rempty() && GSConfig.UserHacks_TextureInsideRt >= GSTextureInRtMode::InsideTargets) { - // Problem: Project - Snowblind and Tomb Raider offset the RT but not the Z - /*if (offset != -1 && (bp - t->m_TEX0.TBP0) != offset) - { - continue; - }*/ - const u32 widthpage_offset = (std::abs(static_cast(bp - t->m_TEX0.TBP0)) >> 5) % std::max(t->m_TEX0.TBW, 1U); - /*const bool is_aligned_ok = widthpage_offset == 0 || (t->m_TEX0.TBW == TEX0.TBW && - ((((min_rect.z + 63) >> 6) + widthpage_offset) <= TEX0.TBW) || - ((widthpage_offset + TEX0.TBW) <= t->m_TEX0.TBW) || - min_rect.width() <= 64 || (widthpage_offset == (t->m_TEX0.TBW >> 1) && - (static_cast(min_rect.width()) <= (widthpage_offset * 64))));*/ const bool is_aligned_ok = widthpage_offset == 0 || ((min_rect.width() <= static_cast((t->m_TEX0.TBW - widthpage_offset) * 64) && (t->m_TEX0.TBW == TEX0.TBW || TEX0.TBW == 1)) && bp >= t->m_TEX0.TBP0); const bool no_target_or_newer = (!dst || ((GSState::s_n - dst->m_last_draw) < (GSState::s_n - t->m_last_draw))); const bool width_match = (t->m_TEX0.TBW == TEX0.TBW || (TEX0.TBW == 1 && draw_rect.w <= GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y)); @@ -2201,7 +2185,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe const GSLocalMemory::psm_t& s_psm = GSLocalMemory::m_psm[TEX0.PSM]; // I know what you're thinking, and I hate the guy who wrote it too (me). Project Snowblind, Tomb Raider etc decide to offset where they're drawing using a channel shuffle, and this gets messy, so best just to kill the old target. - if (is_shuffle && src->m_TEX0.PSM == PSMT8 && GSRendererHW::GetInstance()->m_context->FRAME.FBW == 1 && t->m_last_draw != (GSState::s_n - 1) && src && src->m_from_target && (src->m_from_target->m_TEX0.TBP0 == src->m_TEX0.TBP0 || (((src->m_TEX0.TBP0 - src->m_from_target->m_TEX0.TBP0) >> 5) % std::max(src->m_from_target->m_TEX0.TBW, 1U) == 0)) && widthpage_offset && src->m_from_target != t) + if (is_shuffle && src && src->m_TEX0.PSM == PSMT8 && GSRendererHW::GetInstance()->m_context->FRAME.FBW == 1 && t->m_last_draw != (GSState::s_n - 1) && src->m_from_target && (src->m_from_target->m_TEX0.TBP0 == src->m_TEX0.TBP0 || (((src->m_TEX0.TBP0 - src->m_from_target->m_TEX0.TBP0) >> 5) % std::max(src->m_from_target->m_TEX0.TBW, 1U) == 0)) && widthpage_offset && src->m_from_target != t) { GL_INS("TC: Deleting RT BP 0x%x BW %d PSM %s offset overwrite shuffle", t->m_TEX0.TBP0, t->m_TEX0.TBW, psm_str(t->m_TEX0.PSM)); InvalidateSourcesFromTarget(t); @@ -2246,7 +2230,10 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe //Continue just in case there's a newer target if (used) list.MoveFront(i.Index()); - break; + if (t->m_TEX0.TBP0 <= bp || GSLocalMemory::GetStartBlockAddress(TEX0.TBP0, TEX0.TBW, TEX0.PSM, min_rect) >= bp) + break; + else + continue; } } } @@ -2827,7 +2814,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe } } } - + if (dst) { dst->m_used |= used; @@ -3112,7 +3099,7 @@ bool GSTextureCache::PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, cons auto j = i; Target* t = *j; - if (dst != t && t->m_TEX0.PSM == dst->m_TEX0.PSM && t->Overlaps(dst->m_TEX0.TBP0, dst->m_TEX0.TBW, dst->m_TEX0.PSM, dst->m_valid) && + if (dst != t && t->m_TEX0.PSM == dst->m_TEX0.PSM && dst->m_TEX0.TBW == t->m_TEX0.TBW && t->Overlaps(dst->m_TEX0.TBP0, dst->m_TEX0.TBW, dst->m_TEX0.PSM, dst->m_valid) && static_cast(((t->m_TEX0.TBP0 - dst->m_TEX0.TBP0) / 32) % std::max(dst->m_TEX0.TBW, 1U)) <= std::max(0, static_cast(dst->m_TEX0.TBW - t->m_TEX0.TBW))) { const u32 buffer_width = std::max(1U, dst->m_TEX0.TBW); @@ -3223,6 +3210,91 @@ bool GSTextureCache::PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, cons } } } + else + { + for (int type = 0; type < 2; type++) + { + auto& list = m_dst[type]; + for (auto i = list.begin(); i != list.end();) + { + auto j = i; + Target* t = *j; + if (t != dst && t->Overlaps(dst->m_TEX0.TBP0, dst->m_TEX0.TBW, dst->m_TEX0.PSM, dst->m_valid) && GSUtil::HasSharedBits(dst->m_TEX0.PSM, t->m_TEX0.PSM)) + { + if (dst->m_TEX0.TBP0 > t->m_TEX0.TBP0 && (((dst->m_TEX0.TBP0 - t->m_TEX0.TBP0) >> 5) % std::max(t->m_TEX0.TBW, 1U)) == 0) + { + int height_adjust = (((dst->m_TEX0.TBP0 - t->m_TEX0.TBP0) >> 5) / std::max(t->m_TEX0.TBW, 1U)) * GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y; + + t->m_valid.w = std::min(height_adjust, t->m_valid.w); + t->ResizeValidity(t->m_valid); + } + else if (dst->m_TEX0.TBP0 < t->m_TEX0.TBP0 && (((t->m_TEX0.TBP0 - dst->m_TEX0.TBP0) >> 5) % std::max(t->m_TEX0.TBW, 1U)) == 0) + { + if (GSUtil::GetChannelMask(dst->m_TEX0.PSM) == 0x7 && (t->m_valid_alpha_high || t->m_valid_alpha_low)) + { + t->m_valid_rgb = false; + i++; + continue; + } + + int height_adjust = ((((dst->m_end_block + 1) - t->m_TEX0.TBP0) >> 5) / std::max(t->m_TEX0.TBW, 1U)) * GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y; + + if (height_adjust < t->m_unscaled_size.y) + { + t->m_TEX0.TBP0 = dst->m_end_block + 1; + t->m_valid.w -= height_adjust; + t->ResizeValidity(t->m_valid); + + GSTexture* tex = (type == RenderTarget) ? + g_gs_device->CreateRenderTarget(t->m_texture->GetWidth(), + t->m_texture->GetHeight(), GSTexture::Format::Color, true) : + g_gs_device->CreateDepthStencil(t->m_texture->GetWidth(), + t->m_texture->GetHeight(), GSTexture::Format::DepthStencil, true); + if (tex) + { + g_gs_device->CopyRect(t->m_texture, tex, GSVector4i(0, height_adjust * t->m_scale, t->m_texture->GetWidth(), t->m_texture->GetHeight()), 0, 0); + if (src && src->m_target && src->m_from_target == t) + { + src->m_from_target = t; + src->m_texture = t->m_texture; + src->m_target_direct = false; + src->m_shared_texture = false; + } + else + { + g_gs_device->Recycle(t->m_texture); + } + t->m_texture = tex; + } + } + else + { + if (src && src->m_target && src->m_from_target == t) + { + src->m_from_target = t; + src->m_texture = t->m_texture; + src->m_target_direct = false; + src->m_shared_texture = false; + + t->m_texture = nullptr; + i = list.erase(j); + delete t; + } + else + { + InvalidateSourcesFromTarget(t); + i = list.erase(j); + delete t; + } + } + } + + } + i++; + } + } + } + return hw_clear.value_or(false); } @@ -3236,6 +3308,7 @@ GSTextureCache::Target* GSTextureCache::LookupDisplayTarget(GIFRegTEX0 TEX0, con // Didn't find a target, check if the frame was uploaded. bool can_create = is_feedback; + GSVector2i new_size = size; if (!is_feedback && GSRendererHW::GetInstance()->m_draw_transfers.size() > 0) { @@ -3274,8 +3347,24 @@ GSTextureCache::Target* GSTextureCache::LookupDisplayTarget(GIFRegTEX0 TEX0, con { iter = std::vector::reverse_iterator(GSRendererHW::GetInstance()->m_draw_transfers.erase(iter.base() - 1)); } - else - ++iter; + // Double buffers, usually FMV's, if checking for the upper buffer, creating another target could mess things up. + else if (GSLocalMemory::GetStartBlockAddress(iter->blit.DBP, iter->blit.DBW, iter->blit.DPSM, iter->rect) <= TEX0.TBP0 && transfer_end >= rect_end && iter->rect.width() == size.x) + { + GSTextureCache::Target* tgt = g_texture_cache->GetExactTarget(iter->blit.DBP, iter->blit.DBW, GSTextureCache::RenderTarget, iter->blit.DBP + 1); + + if (tgt) // Make this target bigger. + { + RGBAMask mask; + mask._u32 = GSUtil::GetChannelMask(iter->blit.DPSM); + tgt->UpdateValidity(iter->rect, true); + new_size.y = iter->rect.w; + tgt->ResizeTexture(new_size.x, new_size.y); + AddDirtyRectTarget(tgt, iter->rect, iter->blit.DPSM, iter->blit.DBW, mask, false); + tgt->Update(); + + return tgt; + } + } // In theory it might not be a full rect, but it should be enough to display *something*. // It's also possible we haven't saved enough of the transfers to fill the rect if the game draws the picture in lots of small transfers. @@ -3287,7 +3376,7 @@ GSTextureCache::Target* GSTextureCache::LookupDisplayTarget(GIFRegTEX0 TEX0, con } } - return can_create ? CreateTarget(TEX0, size, size, scale, RenderTarget, true, 0, true) : nullptr; + return can_create ? CreateTarget(TEX0, new_size, new_size, scale, RenderTarget, true, 0, true) : nullptr; } void GSTextureCache::Target::ScaleRTAlpha() @@ -3638,8 +3727,27 @@ void GSTextureCache::InvalidateContainedTargets(u32 start_bp, u32 end_bp, u32 wr for (auto i = list.begin(); i != list.end();) { Target* const t = *i; + if (start_bp != t->m_TEX0.TBP0 && (t->m_TEX0.TBP0 > end_bp || t->UnwrappedEndBlock() < start_bp)) + { + ++i; + continue; + } + + // If not fully contained, just dirty the area. if (start_bp != t->m_TEX0.TBP0 && (t->m_TEX0.TBP0 < start_bp || t->UnwrappedEndBlock() > end_bp)) { + if (write_bw == t->m_TEX0.TBW && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == GSLocalMemory::m_psm[write_psm].bpp) + { + const u32 page_offset = ((end_bp - start_bp) >> 5); + const u32 end_width = write_bw * 64; + const u32 end_height = ((page_offset / std::max(write_bw, 1U)) * GSLocalMemory::m_psm[write_psm].pgs.y) + GSLocalMemory::m_psm[write_psm].pgs.y; + const GSVector4i r = GSVector4i(0, 0, end_width, end_height); + const GSVector4i invalidate_r = TranslateAlignedRectByPage(t, start_bp, write_psm, write_bw, r, false).rintersect(t->m_valid); // it is invalidation but we need a real rect. + RGBAMask mask; + mask._u32 = GSUtil::GetChannelMask(write_psm); + AddDirtyRectTarget(t, invalidate_r, t->m_TEX0.PSM, t->m_TEX0.TBW, mask, false); + } + ++i; continue; } @@ -7016,11 +7124,11 @@ void GSTextureCache::Target::Update(bool cannot_scale) { if (g_texture_cache->GetTemporaryZInfo().ZBP == m_TEX0.TBP0) { - GSTextureCache::TempZAddress z_address_info = g_texture_cache->GetTemporaryZInfo(); + const GSTextureCache::TempZAddress z_address_info = g_texture_cache->GetTemporaryZInfo(); if (m_TEX0.TBP0 == z_address_info.ZBP) { //GL_CACHE("RT in RT Updating Z copy on draw %d z_offset %d", s_n, z_address_info.offset); - GSVector4i dRect = GSVector4i(total_rect.x * m_scale, (z_address_info.offset + total_rect.y) * m_scale, (total_rect.z + (1.0f / m_scale)) * m_scale, (z_address_info.offset + total_rect.w + (1.0f / m_scale)) * m_scale); + const GSVector4i dRect = GSVector4i(total_rect.x * m_scale, (z_address_info.offset + total_rect.y) * m_scale, (total_rect.z + (1.0f / m_scale)) * m_scale, (z_address_info.offset + total_rect.w + (1.0f / m_scale)) * m_scale); g_gs_device->StretchRect(m_texture, GSVector4(total_rect.x / static_cast(m_unscaled_size.x), total_rect.y / static_cast(m_unscaled_size.y), (total_rect.z + (1.0f / m_scale)) / static_cast(m_unscaled_size.x), (total_rect.w + (1.0f / m_scale)) / static_cast(m_unscaled_size.y)), g_texture_cache->GetTemporaryZ(), GSVector4(dRect), ShaderConvert::DEPTH_COPY, false); g_perfmon.Put(GSPerfMon::TextureCopies, 1); } @@ -7120,11 +7228,6 @@ void GSTextureCache::Target::ResizeValidity(const GSVector4i& rect) m_valid = m_valid.rintersect(rect); m_drawn_since_read = m_drawn_since_read.rintersect(rect); m_end_block = GSLocalMemory::GetEndBlockAddress(m_TEX0.TBP0, m_TEX0.TBW, m_TEX0.PSM, m_valid); - - const u32 offset = ((UnwrappedEndBlock() + 1) - m_TEX0.TBP0) % (std::max(m_TEX0.TBW, 1U) << 5); - - if (offset) - m_end_block = m_end_block + ((std::max(m_TEX0.TBW, 1U) << 5) - offset); } // Else No valid size, so need to resize down. @@ -7139,32 +7242,25 @@ void GSTextureCache::Target::UpdateValidity(const GSVector4i& rect, bool can_res m_valid = rect; m_end_block = GSLocalMemory::GetEndBlockAddress(m_TEX0.TBP0, m_TEX0.TBW, m_TEX0.PSM, m_valid); - const u32 offset = ((UnwrappedEndBlock() + 1) - m_TEX0.TBP0) % (std::max(m_TEX0.TBW, 1U) << 5); - - if (offset) - m_end_block = m_end_block + ((std::max(m_TEX0.TBW, 1U) << 5) - offset); } else if (can_resize) { m_valid = m_valid.runion(rect); m_end_block = GSLocalMemory::GetEndBlockAddress(m_TEX0.TBP0, m_TEX0.TBW, m_TEX0.PSM, m_valid); - const u32 offset = ((UnwrappedEndBlock() + 1) - m_TEX0.TBP0) % (std::max(m_TEX0.TBW, 1U) << 5); - - if (offset) - m_end_block = m_end_block + ((std::max(m_TEX0.TBW, 1U) << 5) - offset); } // GL_CACHE("UpdateValidity (0x%x->0x%x) from R:%d,%d Valid: %d,%d", m_TEX0.TBP0, m_end_block, rect.z, rect.w, m_valid.z, m_valid.w); } bool GSTextureCache::Target::ResizeTexture(int new_unscaled_width, int new_unscaled_height, bool recycle_old, bool require_new_rect, GSVector4i new_rect, bool keep_old) { - if (m_unscaled_size.x == new_unscaled_width && m_unscaled_size.y == new_unscaled_height && !require_new_rect) - return true; - const GSVector2i size = m_texture->GetSize(); const GSVector2i new_unscaled_size = GSVector2i(new_unscaled_width, new_unscaled_height); const GSVector2i new_size = ScaleRenderTargetSize(new_unscaled_size, m_scale); + + if (size.x == new_size.x && size.y == new_size.y && !require_new_rect) + return true; + const bool clear = (new_size.x > size.x || new_size.y > size.y); GSTexture* tex = m_texture->IsDepthStencil() ? @@ -7219,7 +7315,7 @@ bool GSTextureCache::Target::ResizeTexture(int new_unscaled_width, int new_unsca if (!keep_old) { - g_texture_cache->m_target_memory_usage = (g_texture_cache->m_target_memory_usage - m_texture->GetMemUsage()) + tex->GetMemUsage(); + g_texture_cache->m_target_memory_usage = (g_texture_cache->m_target_memory_usage - m_texture->GetMemUsage()) + tex->GetMemUsage(); if (recycle_old) g_gs_device->Recycle(m_texture); diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.h b/pcsx2/GS/Renderers/HW/GSTextureCache.h index 807c1280d4..db27ce41ca 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.h +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.h @@ -563,7 +563,7 @@ public: GSTexture* GetTemporaryZ(); TempZAddress GetTemporaryZInfo(); void SetTemporaryZInfo(u32 address, u32 offset); - /// Invalidates a temporary Z, a partial copy only created from the current DS for the current draw when Z is not offset but RT is + /// Invalidates a temporary Z, a partial copy only created from the current DS for the current draw when Z is not offset but RT is. void InvalidateTemporaryZ(); /// Injects a texture into the hash cache, by using GSTexture::Swap(), transitively applying to all sources. Ownership of tex is transferred. diff --git a/pcsx2/GS/Renderers/Metal/tfx.metal b/pcsx2/GS/Renderers/Metal/tfx.metal index 249d658c7d..337e0f4b5c 100644 --- a/pcsx2/GS/Renderers/Metal/tfx.metal +++ b/pcsx2/GS/Renderers/Metal/tfx.metal @@ -1175,7 +1175,7 @@ struct PSMain { if (PS_PROCESS_BA == SHUFFLE_READWRITE && PS_PROCESS_RG == SHUFFLE_READWRITE) { - C.br = C.rb; + C.br = C.rb; C.ag = C.ga; } else if(PS_PROCESS_BA & SHUFFLE_READ) @@ -1190,7 +1190,7 @@ struct PSMain } } } - + ps_dither(C, alpha_blend.a); // Color clamp/wrap needs to be done after sw blending and dithering diff --git a/pcsx2/ShaderCacheVersion.h b/pcsx2/ShaderCacheVersion.h index e4de26746b..6e66cc63cb 100644 --- a/pcsx2/ShaderCacheVersion.h +++ b/pcsx2/ShaderCacheVersion.h @@ -3,4 +3,4 @@ /// Version number for GS and other shaders. Increment whenever any of the contents of the /// shaders change, to invalidate the cache. -static constexpr u32 SHADER_CACHE_VERSION = 61; +static constexpr u32 SHADER_CACHE_VERSION = 62; From c6b558cb3d8d209a5436f0c634301995780be6d6 Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Wed, 5 Mar 2025 00:18:34 +0000 Subject: [PATCH 124/162] GS/HW: Fix up source region behaviour --- pcsx2/GS/Renderers/HW/GSTextureCache.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index 1c38ab1730..763f40743c 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -2019,14 +2019,16 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const { if (src->m_from_target->m_TEX0.TBP0 == src->m_TEX0.TBP0) { - src->m_region.SetX(std::min(region.GetMinX(), src->m_region.GetMinX()), std::max(region.GetMaxX(), src->m_region.GetMaxX())); - src->m_region.SetY(std::min(region.GetMinY(), src->m_region.GetMinY()), std::max(region.GetMaxY(), src->m_region.GetMaxY())); + src->m_region.bits = 0; + src->m_region.SetX(0, region.HasX() ? region.GetMaxX() : (1 << TEX0.TW)); + src->m_region.SetY(0, region.HasY() ? region.GetMaxY() : (1 << TEX0.TH)); } else if (src->m_TEX0.TBP0 > src->m_from_target->m_TEX0.TBP0) { GSVector4i dst_offset = TranslateAlignedRectByPage(src->m_from_target, src->m_TEX0.TBP0, src->m_TEX0.PSM, src->m_TEX0.TBW, GSVector4i(0, 0, 1, 1), false); - src->m_region.SetX(dst_offset.x + region.GetMinX(), dst_offset.x + region.GetMaxX()); - src->m_region.SetY(dst_offset.y + region.GetMinY(), dst_offset.y + region.GetMaxY()); + src->m_region.bits = 0; + src->m_region.SetX(dst_offset.x, dst_offset.x + (region.HasX() ? std::min(region.GetMaxX(), (1 << TEX0.TW)) : (1 << TEX0.TW))); + src->m_region.SetY(dst_offset.y, dst_offset.y + (region.HasY() ? std::min(region.GetMaxY(), (1 << TEX0.TH)) : (1 << TEX0.TH))); } } @@ -2090,7 +2092,6 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe for (auto i = list.begin(); i != list.end();) { Target* t = *i; - if (bp == t->m_TEX0.TBP0) { bool can_use = true; From 22c9433c1c8140bb3d549ab7c6c71e677a506a2f Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Fri, 21 Mar 2025 01:06:44 +0000 Subject: [PATCH 125/162] GS/HW: Split out invalidation in case RT processing invalidates Z, causing a use after free --- pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 43 +++++++++++++++----------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index 48aea90f5e..267fae4870 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -4200,18 +4200,6 @@ void GSRendererHW::Draw() // Limit to 2x the vertical height of the resolution (for double buffering) rt->UpdateValidity(real_rect, !frame_masked && (can_update_size || (real_rect.w <= (resolution.y * 2) && !m_texture_shuffle))); - g_texture_cache->InvalidateVideoMem(context->offset.fb, real_rect, false); - - // Remove overwritten Zs at the FBP. - g_texture_cache->InvalidateVideoMemType(GSTextureCache::DepthStencil, m_cached_ctx.FRAME.Block(), - m_cached_ctx.FRAME.PSM, m_texture_shuffle ? GetEffectiveTextureShuffleFbmsk() : fm); - - if (!m_using_temp_z && g_texture_cache->GetTemporaryZ() != nullptr) - { - GSTextureCache::TempZAddress temp_z_info = g_texture_cache->GetTemporaryZInfo(); - if (GSLocalMemory::GetStartBlockAddress(rt->m_TEX0.TBP0, rt->m_TEX0.TBW, rt->m_TEX0.PSM, real_rect) <= temp_z_info.ZBP && GSLocalMemory::GetEndBlockAddress(rt->m_TEX0.TBP0, rt->m_TEX0.TBW, rt->m_TEX0.PSM, real_rect) > temp_z_info.ZBP) - g_texture_cache->InvalidateTemporaryZ(); - } } if (zm != 0xffffffff && ds) @@ -4222,12 +4210,6 @@ void GSRendererHW::Draw() // Limit to 2x the vertical height of the resolution (for double buffering) ds->UpdateValidity(real_rect, !z_masked && (can_update_size || (real_rect.w <= (resolution.y * 2) && !m_texture_shuffle))); - g_texture_cache->InvalidateVideoMem(context->offset.zb, real_rect, false); - - // Remove overwritten RTs at the ZBP. - g_texture_cache->InvalidateVideoMemType( - GSTextureCache::RenderTarget, m_cached_ctx.ZBUF.Block(), m_cached_ctx.ZBUF.PSM, zm); - if (m_using_temp_z) { if (m_cached_ctx.DepthWrite()) @@ -4313,6 +4295,31 @@ void GSRendererHW::Draw() if (ds) ds->m_last_draw = s_n; + + if ((fm & fm_mask) != fm_mask && !no_rt) + { + g_texture_cache->InvalidateVideoMem(context->offset.fb, real_rect, false); + + // Remove overwritten Zs at the FBP. + g_texture_cache->InvalidateVideoMemType(GSTextureCache::DepthStencil, m_cached_ctx.FRAME.Block(), + m_cached_ctx.FRAME.PSM, m_texture_shuffle ? GetEffectiveTextureShuffleFbmsk() : fm); + + if (rt && !m_using_temp_z && g_texture_cache->GetTemporaryZ() != nullptr) + { + GSTextureCache::TempZAddress temp_z_info = g_texture_cache->GetTemporaryZInfo(); + if (GSLocalMemory::GetStartBlockAddress(rt->m_TEX0.TBP0, rt->m_TEX0.TBW, rt->m_TEX0.PSM, real_rect) <= temp_z_info.ZBP && GSLocalMemory::GetEndBlockAddress(rt->m_TEX0.TBP0, rt->m_TEX0.TBW, rt->m_TEX0.PSM, real_rect) > temp_z_info.ZBP) + g_texture_cache->InvalidateTemporaryZ(); + } + } + + if (zm != 0xffffffff && !no_ds) + { + g_texture_cache->InvalidateVideoMem(context->offset.zb, real_rect, false); + + // Remove overwritten RTs at the ZBP. + g_texture_cache->InvalidateVideoMemType( + GSTextureCache::RenderTarget, m_cached_ctx.ZBUF.Block(), m_cached_ctx.ZBUF.PSM, zm); + } #ifdef DISABLE_HW_TEXTURE_CACHE if (rt) g_texture_cache->Read(rt, real_rect); From 27e067889d8a712b53d6def0d9431941b1d514ee Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Sat, 22 Mar 2025 14:41:53 +0000 Subject: [PATCH 126/162] GS/HW: Try to improve first barrier placement for Metal, Vulkan and OGL --- pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm | 2 +- pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp | 15 +++++++++------ pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp | 4 ++++ 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm index 758e6889f4..b8e0318378 100644 --- a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm +++ b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm @@ -2114,7 +2114,7 @@ void GSDeviceMTL::MREInitHWDraw(GSHWDrawConfig& config, const Map& verts) void GSDeviceMTL::RenderHW(GSHWDrawConfig& config) { @autoreleasepool { - if (config.tex && config.ds == config.tex) + if (config.tex && (config.ds == config.tex || config.rt == config.tex)) EndRenderPass(); // Barrier size_t vertsize = config.nverts * sizeof(*config.verts); diff --git a/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp b/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp index b485149ac9..8ae9200c95 100644 --- a/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp +++ b/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp @@ -2496,12 +2496,6 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config) config.drawarea.width(), config.drawarea.height()); CopyRect(hdr_rt ? hdr_rt : config.rt, draw_rt_clone, config.drawarea, config.drawarea.left, config.drawarea.top); } - else if (config.tex && config.tex == config.ds) - { - // Ensure all depth writes are finished before sampling - GL_INS("Texture barrier to flush depth before reading"); - glTextureBarrier(); - } IASetVertexBuffer(config.verts, config.nverts); if (config.vs.expand != GSHWDrawConfig::VSExpand::None && !GLAD_GL_ARB_shader_draw_parameters) @@ -2563,6 +2557,15 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config) SetupPipeline(psel); + const bool check_barrier = !(config.require_one_barrier && !m_features.texture_barrier); + + // Be careful of the rt already being bound and the blend using the RT without a barrier. + if (check_barrier && ((config.tex && (config.tex == config.ds || config.tex == config.rt)) || ((psel.ps.IsFeedbackLoop() || psel.ps.blend_c == 1) && GLState::rt == config.rt))) + { + // Ensure all depth writes are finished before sampling + GL_INS("Texture barrier to flush depth or rt before reading"); + glTextureBarrier(); + } // additional non-pipeline config stuff const bool point_size_enabled = config.vs.point_size; if (GLState::point_size != point_size_enabled) diff --git a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp index 086779191b..d82df886f6 100644 --- a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp +++ b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp @@ -5641,6 +5641,10 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config) PipelineSelector& pipe = m_pipeline_selector; UpdateHWPipelineSelector(config, pipe); + // If we don't have a barrier but the texture was drawn to last draw, end the pass to insert a barrier. + if (InRenderPass() && !pipe.IsRTFeedbackLoop() && (config.tex == m_current_render_target || config.tex == m_current_depth_target)) + EndRenderPass(); + // now blit the hdr texture back to the original target if (hdr_rt) { From 730207e75b3770cd4db6bffbc4076812375f923a Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Sat, 22 Mar 2025 21:35:06 +0000 Subject: [PATCH 127/162] GS/HW: Do not set a source region when using channel shuffles or tex is rt --- pcsx2/GS/Renderers/HW/GSTextureCache.cpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index 763f40743c..21fb7a9ef6 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -5506,9 +5506,15 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con src->m_shared_texture = true; src->m_32_bits_fmt = dst->m_32_bits_fmt; - // if the size doesn't match, we need to engage shader sampling. - if (new_size != dst_texture_size) + // kill source immediately if it's the RT/DS, because that'll get invalidated immediately + if (GSRendererHW::GetInstance()->IsTBPFrameOrZ(dst->m_TEX0.TBP0) || channel_shuffle) { + GL_CACHE("TC: Source is RT or ZBUF, invalidating after draw."); + m_temporary_source = src; + } + else if (new_size != dst_texture_size) + { + // if the size doesn't match, we need to engage shader sampling. GL_CACHE("TC: Sample reduced region directly from target: %dx%d -> %dx%d", dst_texture_size.x, dst_texture_size.y, new_size.x, new_size.y); @@ -5517,13 +5523,6 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con if (new_size.y != dst_texture_size.y) src->m_region.SetY(region_rect.y, region_rect.w); } - - // kill source immediately if it's the RT/DS, because that'll get invalidated immediately - if (GSRendererHW::GetInstance()->IsTBPFrameOrZ(dst->m_TEX0.TBP0) || channel_shuffle) - { - GL_CACHE("TC: Source is RT or ZBUF, invalidating after draw."); - m_temporary_source = src; - } } else { From a3051f5d58d86b391399fa49fbcad24e052ce67e Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Sat, 22 Mar 2025 23:21:11 +0000 Subject: [PATCH 128/162] GS/HW: More RT in RT regression fixes/improvements --- pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 12 ++++++------ pcsx2/GS/Renderers/HW/GSTextureCache.cpp | 15 ++++++++------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index 267fae4870..2a13a44964 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -2494,6 +2494,8 @@ void GSRendererHW::Draw() return; } + m_process_texture = PRIM->TME && !(PRIM->ABE && m_context->ALPHA.IsBlack() && !m_cached_ctx.TEX0.TCC) && !(no_rt && (!m_cached_ctx.TEST.ATE || m_cached_ctx.TEST.ATST <= ATST_ALWAYS)); + // We trigger the sw prim render here super early, to avoid creating superfluous render targets. if (CanUseSwPrimRender(no_rt, no_ds, draw_sprite_tex && m_process_texture) && SwPrimRender(*this, true, true)) { @@ -2511,8 +2513,6 @@ void GSRendererHW::Draw() DetectRedundantBufferClear(no_rt, no_ds, fm_mask); } - m_process_texture = PRIM->TME && !(PRIM->ABE && m_context->ALPHA.IsBlack() && !m_cached_ctx.TEX0.TCC) && !(no_rt && (!m_cached_ctx.TEST.ATE || m_cached_ctx.TEST.ATST <= ATST_ALWAYS)); - CalculatePrimitiveCoversWithoutGaps(); const bool not_writing_to_all = (m_primitive_covers_without_gaps != NoGapsType::FullCover || AreAnyPixelsDiscarded() || !all_depth_tests_pass); @@ -6321,7 +6321,7 @@ __ri void GSRendererHW::HandleTextureHazards(const GSTextureCache::Target* rt, c { src_target = tex->m_from_target; } - else if (!m_downscale_source) + else if (!m_downscale_source || !tex->m_from_target) { // No match. return; @@ -6335,7 +6335,7 @@ __ri void GSRendererHW::HandleTextureHazards(const GSTextureCache::Target* rt, c GSVector4i copy_range; GSVector2i copy_size; GSVector2i copy_dst_offset; - bool copied_rt = false; + const bool copied_rt = src_target && !tex->m_shared_texture; // Shuffles take the whole target. This should've already been halved. // We can't partially copy depth targets in DirectX, and GL/Vulkan should use the direct read above. // Restricting it also breaks Tom and Jerry... @@ -6347,8 +6347,8 @@ __ri void GSRendererHW::HandleTextureHazards(const GSTextureCache::Target* rt, c copy_size.x = rt->m_unscaled_size.x; copy_size.y = rt->m_unscaled_size.y; copy_range.x = copy_range.y = 0; - copy_range.z = std::min(m_r.width(), copy_size.x); - copy_range.w = std::min(m_r.height(), copy_size.y); + copy_range.z = std::min(m_r.width() + 1, copy_size.x); + copy_range.w = std::min(m_r.height() + 1, copy_size.y); } else { diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index 21fb7a9ef6..88815d4200 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -5506,13 +5506,7 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con src->m_shared_texture = true; src->m_32_bits_fmt = dst->m_32_bits_fmt; - // kill source immediately if it's the RT/DS, because that'll get invalidated immediately - if (GSRendererHW::GetInstance()->IsTBPFrameOrZ(dst->m_TEX0.TBP0) || channel_shuffle) - { - GL_CACHE("TC: Source is RT or ZBUF, invalidating after draw."); - m_temporary_source = src; - } - else if (new_size != dst_texture_size) + if (new_size != dst_texture_size) { // if the size doesn't match, we need to engage shader sampling. GL_CACHE("TC: Sample reduced region directly from target: %dx%d -> %dx%d", dst_texture_size.x, @@ -5523,6 +5517,13 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con if (new_size.y != dst_texture_size.y) src->m_region.SetY(region_rect.y, region_rect.w); } + + // kill source immediately if it's the RT/DS, because that'll get invalidated immediately + if (GSRendererHW::GetInstance()->IsTBPFrameOrZ(dst->m_TEX0.TBP0) || channel_shuffle) + { + GL_CACHE("TC: Source is RT or ZBUF, invalidating after draw."); + m_temporary_source = src; + } } else { From 474135488366773999564708906d48017a48ae98 Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Sun, 23 Mar 2025 05:18:02 +0000 Subject: [PATCH 129/162] GS/HW: Clamp native scaling texture read size to texture size Fixes Transformers light rays --- bin/resources/GameIndex.yaml | 27 +++++++++++++++++--------- pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 5 ++++- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/bin/resources/GameIndex.yaml b/bin/resources/GameIndex.yaml index 4271b5a74d..7592b9151e 100644 --- a/bin/resources/GameIndex.yaml +++ b/bin/resources/GameIndex.yaml @@ -13278,8 +13278,9 @@ SLED-52441: speedHacks: mtvu: 0 gsHWFixes: - halfPixelOffset: 2 # Fixes depth line. + halfPixelOffset: 5 # Fixes depth line. textureInsideRT: 1 # Fixes shadow and sky rendering. + nativeScaling: 2 # Fixes light rays. deinterlace: 9 # Any other method causes very bad lines on movement of the camera. SLED-52473: name: "Transformers - Optimus Prime [Demo]" @@ -13289,8 +13290,9 @@ SLED-52473: speedHacks: mtvu: 0 gsHWFixes: - halfPixelOffset: 2 # Fixes depth line. + halfPixelOffset: 5 # Fixes depth line. textureInsideRT: 1 # Fixes shadow and sky rendering. + nativeScaling: 2 # Fixes light rays. deinterlace: 9 # Any other method causes very bad lines on movement of the camera. SLED-52474: name: "Transformers - Red Alert [Demo]" @@ -13300,8 +13302,9 @@ SLED-52474: speedHacks: mtvu: 0 gsHWFixes: - halfPixelOffset: 2 # Fixes depth line. + halfPixelOffset: 5 # Fixes depth line. textureInsideRT: 1 # Fixes shadow and sky rendering. + nativeScaling: 2 # Fixes light rays. deinterlace: 9 # Any other method causes very bad lines on movement of the camera. SLED-52476: name: "UEFA Euro 2004 - Portugal [Demo]" @@ -19460,8 +19463,9 @@ SLES-52388: speedHacks: mtvu: 0 gsHWFixes: - halfPixelOffset: 2 # Fixes depth line. + halfPixelOffset: 5 # Fixes depth line. textureInsideRT: 1 # Fixes shadow and sky rendering. + nativeScaling: 2 # Fixes light rays. deinterlace: 9 # Any other method causes very bad lines on movement of the camera. SLES-52392: name: "Combat Elite - WWII Paratroopers [Preview]" @@ -22082,8 +22086,9 @@ SLES-53309: speedHacks: mtvu: 0 gsHWFixes: - halfPixelOffset: 2 # Fixes depth line. + halfPixelOffset: 5 # Fixes depth line. textureInsideRT: 1 # Fixes shadow and sky rendering. + nativeScaling: 2 # Fixes light rays. deinterlace: 9 # Any other method causes very bad lines on movement of the camera. SLES-53311: name: "Graffiti Kingdom" @@ -30604,8 +30609,9 @@ SLKA-25175: speedHacks: mtvu: 0 gsHWFixes: - halfPixelOffset: 2 # Fixes depth line. + halfPixelOffset: 5 # Fixes depth line. textureInsideRT: 1 # Fixes shadow and sky rendering. + nativeScaling: 2 # Fixes light rays. deinterlace: 9 # Any other method causes very bad lines on movement of the camera. SLKA-25176: name: "Pump It Up - Exceed" @@ -64911,8 +64917,9 @@ SLUS-20668: speedHacks: mtvu: 0 gsHWFixes: - halfPixelOffset: 2 # Fixes depth line. + halfPixelOffset: 5 # Fixes depth line. textureInsideRT: 1 # Fixes shadow and sky rendering. + nativeScaling: 2 # Fixes light rays. deinterlace: 9 # Any other method causes very bad lines on movement of the camera. SLUS-20669: name: "Resident Evil - Dead Aim" @@ -72469,8 +72476,9 @@ SLUS-28040: speedHacks: mtvu: 0 gsHWFixes: - halfPixelOffset: 2 # Fixes depth line. + halfPixelOffset: 5 # Fixes depth line. textureInsideRT: 1 # Fixes shadow and sky rendering. + nativeScaling: 2 # Fixes light rays. deinterlace: 9 # Any other method causes very bad lines on movement of the camera. SLUS-28043: name: "Test Drive - Eve of Destruction [Trade Demo]" @@ -72901,8 +72909,9 @@ SLUS-29107: speedHacks: mtvu: 0 gsHWFixes: - halfPixelOffset: 2 # Fixes depth line. + halfPixelOffset: 5 # Fixes depth line. textureInsideRT: 1 # Fixes shadow and sky rendering. + nativeScaling: 2 # Fixes light rays. deinterlace: 9 # Any other method causes very bad lines on movement of the camera. SLUS-29108: name: "Def Jam - Fight for NY [Demo]" diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index 2a13a44964..cbf6ace9bc 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -8569,7 +8569,10 @@ bool GSRendererHW::TextureCoversWithoutGapsNotEqual() int GSRendererHW::IsScalingDraw(GSTextureCache::Source* src, bool no_gaps) { const GSVector2i draw_size = GSVector2i(m_vt.m_max.p.x - m_vt.m_min.p.x, m_vt.m_max.p.y - m_vt.m_min.p.y); - const GSVector2i tex_size = GSVector2i(m_vt.m_max.t.x - m_vt.m_min.t.x, m_vt.m_max.t.y - m_vt.m_min.t.y); + GSVector2i tex_size = GSVector2i(m_vt.m_max.t.x - m_vt.m_min.t.x, m_vt.m_max.t.y - m_vt.m_min.t.y); + + tex_size.x = std::min(tex_size.x, 1 << m_cached_ctx.TEX0.TW); + tex_size.y = std::min(tex_size.y, 1 << m_cached_ctx.TEX0.TH); const bool is_target_src = src && src->m_from_target; From c6a20961b825ed68740a9412d1a5300dc700dcaa Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Fri, 4 Apr 2025 21:44:07 +0100 Subject: [PATCH 130/162] GS/HW: Improve quad detection on triangle strips --- pcsx2/GS/GSState.cpp | 93 +++++++++++++++++++++++--- pcsx2/GS/GSState.h | 4 +- pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 24 ++++--- 3 files changed, 102 insertions(+), 19 deletions(-) diff --git a/pcsx2/GS/GSState.cpp b/pcsx2/GS/GSState.cpp index cd208b2c71..e3617ab81e 100644 --- a/pcsx2/GS/GSState.cpp +++ b/pcsx2/GS/GSState.cpp @@ -1758,6 +1758,7 @@ void GSState::FlushPrim() // Skip draw if Z test is enabled, but set to fail all pixels. const bool skip_draw = (m_context->TEST.ZTE && m_context->TEST.ZTST == ZTST_NEVER); + m_quad_check_valid = false; if (!skip_draw) Draw(); @@ -2879,11 +2880,17 @@ void GSState::GrowVertexBuffer() m_index.buff = index; } -bool GSState::TrianglesAreQuads(bool shuffle_check) const +bool GSState::TrianglesAreQuads(bool shuffle_check) { // If this is a quad, there should only be two distinct values for both X and Y, which // also happen to be the minimum/maximum bounds of the primitive. + if (!shuffle_check && m_quad_check_valid) + return m_are_quads; + const GSVertex* const v = m_vertex.buff; + m_are_quads = false; + m_quad_check_valid = !shuffle_check; + for (u32 idx = 0; idx < m_index.tail; idx += 6) { const u16* const i = m_index.buff + idx; @@ -2906,15 +2913,44 @@ bool GSState::TrianglesAreQuads(bool shuffle_check) const return false; } // Degenerate triangles should've been culled already, so we can check indices. - u32 extra_verts = 0; - for (u32 j = 3; j < 6; j++) + // This doesn't really make much sense when it's a triangle strip as it will always have 1 extra vert, so check for distinct values for them. + if (PRIM->PRIM != GS_TRIANGLESTRIP) { - const u16 tri2_idx = i[j]; - if (tri2_idx != i[0] && tri2_idx != i[1] && tri2_idx != i[2]) - extra_verts++; + u32 extra_verts = 0; + for (u32 j = 3; j < 6; j++) + { + const u16 tri2_idx = i[j]; + if (tri2_idx != i[0] && tri2_idx != i[1] && tri2_idx != i[2]) + extra_verts++; + } + if (extra_verts == 1) + continue; + } + else if (m_index.tail == 6) + { + const int first_X = m_vertex.buff[m_index.buff[0]].XYZ.X; + const int first_Y = m_vertex.buff[m_index.buff[0]].XYZ.Y; + const int second_X = m_vertex.buff[m_index.buff[1]].XYZ.X; + const int second_Y = m_vertex.buff[m_index.buff[1]].XYZ.Y; + const int third_X = m_vertex.buff[m_index.buff[2]].XYZ.X; + const int third_Y = m_vertex.buff[m_index.buff[2]].XYZ.Y; + const int new_X = m_vertex.buff[m_index.buff[5]].XYZ.X; + const int new_Y = m_vertex.buff[m_index.buff[5]].XYZ.Y; + + const int middle_Y = (second_Y >= third_Y) ? (third_Y + ((second_Y - third_Y) / 2)) : (second_Y + ((third_Y - second_Y) / 2)); + const int middle_X = (second_X >= third_X) ? (third_X + ((second_X - third_X) / 2)) : (second_X + ((third_X - second_X) / 2)); + const bool first_lt_X = first_X <= middle_X; + const bool first_lt_Y = first_Y <= middle_Y; + const bool new_lt_X = new_X <= middle_X; + const bool new_lt_Y = new_Y <= middle_Y; + + // Check if verts are on the same side. Not totally accurate, but should be good enough. + if (first_lt_X == new_lt_X && new_lt_Y == first_lt_Y) + return false; + + m_prim_overlap = PRIM_OVERLAP_NO; + break; } - if (extra_verts == 1) - continue; // As a fallback, they might've used different vertices with a tri list, not strip. // Note that this won't work unless the quad is axis-aligned. @@ -2942,6 +2978,7 @@ bool GSState::TrianglesAreQuads(bool shuffle_check) const } } + m_are_quads = true; return true; } @@ -3104,6 +3141,46 @@ bool GSState::SpriteDrawWithoutGaps() return true; } + // Assume it's small sprites. NFSMW and a few other games draw 32x32 sprites in rows to fill the screen. + if (((first_dpY + 8) >> 4) == GSLocalMemory::m_psm[m_context->FRAME.PSM].pgs.y) + { + int lastXEdge = std::max(v[1].XYZ.X, v[0].XYZ.X); + int lastYEdge = std::max(v[1].XYZ.Y, v[0].XYZ.Y); + for (u32 i = 2; i < m_vertex.next; i += 2) + { + const int dpY = v[i + 1].XYZ.Y - v[i].XYZ.Y; + + if (first_dpY != dpY) + return false; + + const int newYStart = std::min(v[i + 1].XYZ.Y, v[i].XYZ.Y); + const int newXEdge = std::max(v[i + 1].XYZ.X, v[i].XYZ.X); + if (lastYEdge != newYStart) + { + if (newYStart != static_cast(m_context->XYOFFSET.OFY)) + return false; + + const int newXStart = std::min(v[i + 1].XYZ.X, v[i].XYZ.X); + + if (newXStart != lastXEdge) + return false; + } + else + { + const int dpX = v[i + 1].XYZ.X - v[i].XYZ.X; + if (first_dpX != dpX || lastXEdge != newXEdge) + return false; + } + + lastXEdge = newXEdge; + lastYEdge = std::max(v[i + 1].XYZ.Y, v[i].XYZ.Y); + } + + m_prim_overlap = PRIM_OVERLAP_NO; + + return true; + } + return false; } diff --git a/pcsx2/GS/GSState.h b/pcsx2/GS/GSState.h index 4d3c696b08..511a6c7b28 100644 --- a/pcsx2/GS/GSState.h +++ b/pcsx2/GS/GSState.h @@ -234,6 +234,8 @@ public: GSVector4i temp_draw_rect = {}; std::unique_ptr m_dump; bool m_scissor_invalid = false; + bool m_quad_check_valid = false; + bool m_are_quads = false; bool m_nativeres = false; bool m_mipmap = false; bool m_texflush_flag = false; @@ -439,7 +441,7 @@ public: void DumpVertices(const std::string& filename); - bool TrianglesAreQuads(bool shuffle_check = false) const; + bool TrianglesAreQuads(bool shuffle_check = false); PRIM_OVERLAP PrimitiveOverlap(); bool SpriteDrawWithoutGaps(); void CalculatePrimitiveCoversWithoutGaps(); diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index cbf6ace9bc..056260f732 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -1103,12 +1103,14 @@ GSVector2i GSRendererHW::GetValidSize(const GSTextureCache::Source* tex) if (tex_width_pgs == half_draw_width_pgs) { GL_CACHE("Halving width due to texture shuffle with double width, %dx%d -> %dx%d", width, height, width / 2, height); - width /= 2; + const int src_width = tex ? (tex->m_from_target ? tex->m_from_target->m_valid.width() : tex->GetUnscaledWidth()) : (width / 2); + width = std::min(width / 2, src_width); } else { GL_CACHE("Halving height due to texture shuffle, %dx%d -> %dx%d", width, height, width, height / 2); - height /= 2; + const int src_height = tex ? (tex->m_from_target ? tex->m_from_target->m_valid.height() : tex->GetUnscaledHeight()) : (height / 2); + height = std::min(height / 2, src_height); } } @@ -2294,15 +2296,13 @@ void GSRendererHW::Draw() if (GSConfig.DumpGSData) { - std::string s; - if (GSConfig.ShouldDump(s_n - 1, g_perfmon.GetFrame())) { if (m_last_rt && GSConfig.SaveRT) { const u64 frame = g_perfmon.GetFrame(); - s = GetDrawDumpPath("%05d_f%05lld_rt1_%05x_(%05x)_%s.bmp", s_n - 1, frame, m_last_channel_shuffle_fbp, m_last_rt->m_TEX0.TBP0, psm_str(m_cached_ctx.FRAME.PSM)); + std::string s = GetDrawDumpPath("%05d_f%05lld_rt1_%05x_(%05x)_%s.bmp", s_n - 1, frame, m_last_channel_shuffle_fbp, m_last_rt->m_TEX0.TBP0, psm_str(m_cached_ctx.FRAME.PSM)); m_last_rt->m_texture->Save(s); } @@ -2704,7 +2704,6 @@ void GSRendererHW::Draw() g_texture_cache->InvalidateTemporaryZ(); } - if (overwriting_whole_rt && overwriting_whole_ds && TryGSMemClear(no_rt, preserve_rt_color, is_zero_color_clear, rt_end_bp, no_ds, preserve_depth, is_zero_depth_clear, ds_end_bp)) @@ -3061,9 +3060,10 @@ void GSRendererHW::Draw() GSTextureCache::Target* ds = nullptr; GIFRegTEX0 ZBUF_TEX0; + ZBUF_TEX0.U64 = 0; + if (!no_ds) { - ZBUF_TEX0.U64 = 0; ZBUF_TEX0.TBP0 = m_cached_ctx.ZBUF.Block(); ZBUF_TEX0.TBW = m_cached_ctx.FRAME.FBW; ZBUF_TEX0.PSM = m_cached_ctx.ZBUF.PSM; @@ -3229,7 +3229,7 @@ void GSRendererHW::Draw() rt = g_texture_cache->CreateTarget(FRAME_TEX0, t_size, GetValidSize(src), (GSConfig.UserHacks_NativeScaling != GSNativeScaling::Off && scale_draw < 0 && is_possible_mem_clear != ClearType::NormalClear) ? src->m_from_target->GetScale() : target_scale, GSTextureCache::RenderTarget, true, fm, false, force_preload, preserve_rt_color || possible_shuffle, lookup_rect, src); - + if (!rt) [[unlikely]] { GL_INS("ERROR: Failed to create FRAME target, skipping."); @@ -3906,6 +3906,10 @@ void GSRendererHW::Draw() const bool rt_update = can_update_size || (m_texture_shuffle && (src && rt && src->m_from_target != rt)); + // If it's updating from a texture shuffle, limit the size to the source size. + if (rt_update && !can_update_size && src->m_from_target) + update_rect = update_rect.rintersect(src->m_from_target->m_valid); + // if frame is masked or afailing always to never write frame, wanna make sure we don't touch it. This might happen if DATE or Alpha Test is being used to write to Z. const bool frame_masked = ((m_cached_ctx.FRAME.FBMSK & frame_psm.fmsk) == frame_psm.fmsk) || (m_cached_ctx.TEST.ATE && m_cached_ctx.TEST.ATST == ATST_NEVER && !(m_cached_ctx.TEST.AFAIL & AFAIL_FB_ONLY)); // Limit to 2x the vertical height of the resolution (for double buffering) @@ -5023,7 +5027,7 @@ __ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool m_last_channel_shuffle_fbmsk = 0xFFFFFFFF; // If we're doing per page copying, then set the valid 1 frame ahead if we're continuing, as this will save the target lookup making a new target for the new row. - u32 frame_offset = m_cached_ctx.FRAME.Block() + (IsPageCopy() ? 0x20 : 0); + const u32 frame_offset = m_cached_ctx.FRAME.Block() + (IsPageCopy() ? 0x20 : 0); GSVector4i new_valid = rt->m_valid; int offset_height = static_cast((((frame_offset - rt->m_TEX0.TBP0) >> 5) / rt->m_TEX0.TBW) * frame_psm.pgs.y) + frame_psm.pgs.y; @@ -8618,7 +8622,7 @@ int GSRendererHW::IsScalingDraw(GSTextureCache::Source* src, bool no_gaps) ClearType GSRendererHW::IsConstantDirectWriteMemClear() { - const bool direct_draw = (m_vt.m_primclass == GS_SPRITE_CLASS) || (((m_index.tail % 6) == 0 && TrianglesAreQuads()) && m_vt.m_primclass == GS_TRIANGLE_CLASS); + const bool direct_draw = (m_vt.m_primclass == GS_SPRITE_CLASS) || (m_vt.m_primclass == GS_TRIANGLE_CLASS && (m_index.tail % 6) == 0 && TrianglesAreQuads()); // Constant Direct Write without texture/test/blending (aka a GS mem clear) if (direct_draw && !PRIM->TME // Direct write && !(m_draw_env->SCANMSK.MSK & 2) && !m_cached_ctx.TEST.ATE // no alpha test From c70aba2ca552ef133c36eec6b4b53552dab38d8e Mon Sep 17 00:00:00 2001 From: TheLastRar Date: Thu, 17 Apr 2025 19:43:15 +0100 Subject: [PATCH 131/162] Common: Allow shared reading of log files --- common/Console.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/Console.cpp b/common/Console.cpp index 8f8aa084bc..2415181bf4 100644 --- a/common/Console.cpp +++ b/common/Console.cpp @@ -342,7 +342,7 @@ bool Log::SetFileOutputLevel(LOGLEVEL level, std::string path) if (!s_file_handle || s_file_path != path) { s_file_handle.reset(); - s_file_handle = FileSystem::OpenManagedCFile(path.c_str(), "wb"); + s_file_handle = FileSystem::OpenManagedSharedCFile(path.c_str(), "wb", FileSystem::FileShareMode::DenyWrite); if (s_file_handle) { s_file_path = std::move(path); From 8e24ad724c120d6c0fad7c06b5eb3ee6f2514485 Mon Sep 17 00:00:00 2001 From: lightningterror <18107717+lightningterror@users.noreply.github.com> Date: Tue, 15 Apr 2025 09:57:15 +0200 Subject: [PATCH 132/162] CDVD: Adjust ram requirements when precaching. --- common/HostSys.h | 4 ++++ common/Windows/WinMisc.cpp | 8 ++++++++ pcsx2/CDVD/ThreadedFileReader.cpp | 17 +++++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/common/HostSys.h b/common/HostSys.h index b41581a7d1..f79c36fb70 100644 --- a/common/HostSys.h +++ b/common/HostSys.h @@ -181,6 +181,10 @@ private: extern u64 GetTickFrequency(); extern u64 GetCPUTicks(); extern u64 GetPhysicalMemory(); +#ifdef _WIN32 +// TODO: Someone do linux/mac. +extern u64 GetAvailablePhysicalMemory(); +#endif /// Spin for a short period of time (call while spinning waiting for a lock) /// Returns the approximate number of ns that passed extern u32 ShortSpin(); diff --git a/common/Windows/WinMisc.cpp b/common/Windows/WinMisc.cpp index 3cbdc201d0..b7bdb89d24 100644 --- a/common/Windows/WinMisc.cpp +++ b/common/Windows/WinMisc.cpp @@ -57,6 +57,14 @@ u64 GetPhysicalMemory() return status.ullTotalPhys; } +u64 GetAvailablePhysicalMemory() +{ + MEMORYSTATUSEX status; + status.dwLength = sizeof(status); + GlobalMemoryStatusEx(&status); + return status.ullAvailPhys; +} + // Calculates the Windows OS Version and processor architecture, and returns it as a // human-readable string. :) std::string GetOSVersionString() diff --git a/pcsx2/CDVD/ThreadedFileReader.cpp b/pcsx2/CDVD/ThreadedFileReader.cpp index 4a1b5828e0..550b2794a7 100644 --- a/pcsx2/CDVD/ThreadedFileReader.cpp +++ b/pcsx2/CDVD/ThreadedFileReader.cpp @@ -266,10 +266,26 @@ bool ThreadedFileReader::Precache2(ProgressCallback* progress, Error* error) bool ThreadedFileReader::CheckAvailableMemoryForPrecaching(u64 required_size, Error* error) { +#ifdef _WIN32 + // We want to check available physical memory instead of total. + const u64 memory_available = GetAvailablePhysicalMemory(); + // Reserve 2GB of available memory for headroom. + constexpr u64 memory_reserve = 2147483648; + const u64 max_precache_size = std::max(0LL, static_cast(memory_available - memory_reserve)); + + if (required_size > max_precache_size) + { + Error::SetStringFmt(error, + TRANSLATE_FS("CDVD", "Not enough free memory available for precaching, ({}GB) required."), + (required_size + memory_reserve) / _1gb); + return false; + } +#else // Don't allow precaching to use more than 50% of system memory. // Hopefully nobody's running 2-4GB potatoes anymore.... const u64 memory_size = GetPhysicalMemory(); const u64 max_precache_size = memory_size / 2; + if (required_size > max_precache_size) { Error::SetStringFmt(error, @@ -277,6 +293,7 @@ bool ThreadedFileReader::CheckAvailableMemoryForPrecaching(u64 required_size, Er required_size / _1gb, max_precache_size / _1gb); return false; } +#endif return true; } From 9a476f82830fc73f3215ad11cff89eb0c4bcded8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Apr 2025 14:14:57 +0000 Subject: [PATCH 133/162] Bump @octokit/request and @octokit/plugin-throttling Bumps [@octokit/request](https://github.com/octokit/request.js) to 9.2.2 and updates ancestor dependency [@octokit/plugin-throttling](https://github.com/octokit/plugin-throttling.js). These dependencies need to be updated together. Updates `@octokit/request` from 5.6.2 to 9.2.2 - [Release notes](https://github.com/octokit/request.js/releases) - [Commits](https://github.com/octokit/request.js/compare/v5.6.2...v9.2.2) Updates `@octokit/plugin-throttling` from 3.5.2 to 9.6.0 - [Release notes](https://github.com/octokit/plugin-throttling.js/releases) - [Commits](https://github.com/octokit/plugin-throttling.js/compare/v3.5.2...v9.6.0) --- updated-dependencies: - dependency-name: "@octokit/request" dependency-version: 9.2.2 dependency-type: indirect - dependency-name: "@octokit/plugin-throttling" dependency-version: 9.6.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- .../generate-release-notes/package-lock.json | 411 ++++++++---------- .../generate-release-notes/package.json | 2 +- 2 files changed, 176 insertions(+), 237 deletions(-) diff --git a/.github/workflows/scripts/releases/generate-release-notes/package-lock.json b/.github/workflows/scripts/releases/generate-release-notes/package-lock.json index beef6c7aa8..8d95ec831a 100644 --- a/.github/workflows/scripts/releases/generate-release-notes/package-lock.json +++ b/.github/workflows/scripts/releases/generate-release-notes/package-lock.json @@ -9,123 +9,11 @@ "license": "ISC", "dependencies": { "@octokit/plugin-retry": "^3.0.9", - "@octokit/plugin-throttling": "^3.5.2", + "@octokit/plugin-throttling": "^9.6.0", "@octokit/rest": "^21.1.1" } }, "node_modules/@octokit/auth-token": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz", - "integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==", - "peer": true, - "dependencies": { - "@octokit/types": "^6.0.3" - } - }, - "node_modules/@octokit/core": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.5.1.tgz", - "integrity": "sha512-omncwpLVxMP+GLpLPgeGJBF6IWJFjXDS5flY5VbppePYX9XehevbDykRH9PdCdvqt9TS5AOTiDide7h0qrkHjw==", - "peer": true, - "dependencies": { - "@octokit/auth-token": "^2.4.4", - "@octokit/graphql": "^4.5.8", - "@octokit/request": "^5.6.0", - "@octokit/request-error": "^2.0.5", - "@octokit/types": "^6.0.3", - "before-after-hook": "^2.2.0", - "universal-user-agent": "^6.0.0" - } - }, - "node_modules/@octokit/endpoint": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz", - "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==", - "peer": true, - "dependencies": { - "@octokit/types": "^6.0.3", - "is-plain-object": "^5.0.0", - "universal-user-agent": "^6.0.0" - } - }, - "node_modules/@octokit/graphql": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz", - "integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==", - "peer": true, - "dependencies": { - "@octokit/request": "^5.6.0", - "@octokit/types": "^6.0.3", - "universal-user-agent": "^6.0.0" - } - }, - "node_modules/@octokit/openapi-types": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-11.1.0.tgz", - "integrity": "sha512-dWZfYvCCdjZzDYA3lIAMF72Q0jld8xidqCq5Ryw09eBJXZdcM6he0vWBTvw/b5UnGYqexxOyHWgfrsTlUJL3Gw==" - }, - "node_modules/@octokit/plugin-retry": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-3.0.9.tgz", - "integrity": "sha512-r+fArdP5+TG6l1Rv/C9hVoty6tldw6cE2pRHNGmFPdyfrc696R6JjrQ3d7HdVqGwuzfyrcaLAKD7K8TX8aehUQ==", - "dependencies": { - "@octokit/types": "^6.0.3", - "bottleneck": "^2.15.3" - } - }, - "node_modules/@octokit/plugin-throttling": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-3.5.2.tgz", - "integrity": "sha512-Eu7kfJxU8vmHqWGNszWpg+GVp2tnAfax3XQV5CkYPEE69C+KvInJXW9WajgSeW+cxYe0UVdouzCtcreGNuJo7A==", - "dependencies": { - "@octokit/types": "^6.0.1", - "bottleneck": "^2.15.3" - }, - "peerDependencies": { - "@octokit/core": "^3.5.0" - } - }, - "node_modules/@octokit/request": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.2.tgz", - "integrity": "sha512-je66CvSEVf0jCpRISxkUcCa0UkxmFs6eGDRSbfJtAVwbLH5ceqF+YEyC8lj8ystKyZTy8adWr0qmkY52EfOeLA==", - "peer": true, - "dependencies": { - "@octokit/endpoint": "^6.0.1", - "@octokit/request-error": "^2.1.0", - "@octokit/types": "^6.16.1", - "is-plain-object": "^5.0.0", - "node-fetch": "^2.6.1", - "universal-user-agent": "^6.0.0" - } - }, - "node_modules/@octokit/request-error": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", - "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", - "peer": true, - "dependencies": { - "@octokit/types": "^6.0.3", - "deprecation": "^2.0.0", - "once": "^1.4.0" - } - }, - "node_modules/@octokit/rest": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-21.1.1.tgz", - "integrity": "sha512-sTQV7va0IUVZcntzy1q3QqPm/r8rWtDCqpRAmb8eXXnKkjoQEtFe3Nt5GTVsHft+R6jJoHeSiVLcgcvhtue/rg==", - "license": "MIT", - "dependencies": { - "@octokit/core": "^6.1.4", - "@octokit/plugin-paginate-rest": "^11.4.2", - "@octokit/plugin-request-log": "^5.3.1", - "@octokit/plugin-rest-endpoint-methods": "^13.3.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/rest/node_modules/@octokit/auth-token": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.2.tgz", "integrity": "sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw==", @@ -134,7 +22,7 @@ "node": ">= 18" } }, - "node_modules/@octokit/rest/node_modules/@octokit/core": { + "node_modules/@octokit/core": { "version": "6.1.4", "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.4.tgz", "integrity": "sha512-lAS9k7d6I0MPN+gb9bKDt7X8SdxknYqAMh44S5L+lNqIN2NuV8nvv3g8rPp7MuRxcOpxpUIATWprO0C34a8Qmg==", @@ -152,7 +40,22 @@ "node": ">= 18" } }, - "node_modules/@octokit/rest/node_modules/@octokit/endpoint": { + "node_modules/@octokit/core/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "license": "MIT" + }, + "node_modules/@octokit/core/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/endpoint": { "version": "10.1.3", "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.3.tgz", "integrity": "sha512-nBRBMpKPhQUxCsQQeW+rCJ/OPSMcj3g0nfHn01zGYZXuNDvvXudF/TYY6APj5THlurerpFN4a/dQAIAaM6BYhA==", @@ -165,7 +68,22 @@ "node": ">= 18" } }, - "node_modules/@octokit/rest/node_modules/@octokit/graphql": { + "node_modules/@octokit/endpoint/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "license": "MIT" + }, + "node_modules/@octokit/endpoint/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/graphql": { "version": "8.2.1", "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.2.1.tgz", "integrity": "sha512-n57hXtOoHrhwTWdvhVkdJHdhTv0JstjDbDRhJfwIRNfFqmSo1DaK/mD2syoNUoLCyqSjBpGAKOG0BuwF392slw==", @@ -179,6 +97,139 @@ "node": ">= 18" } }, + "node_modules/@octokit/graphql/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "license": "MIT" + }, + "node_modules/@octokit/graphql/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-11.1.0.tgz", + "integrity": "sha512-dWZfYvCCdjZzDYA3lIAMF72Q0jld8xidqCq5Ryw09eBJXZdcM6he0vWBTvw/b5UnGYqexxOyHWgfrsTlUJL3Gw==" + }, + "node_modules/@octokit/plugin-retry": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-3.0.9.tgz", + "integrity": "sha512-r+fArdP5+TG6l1Rv/C9hVoty6tldw6cE2pRHNGmFPdyfrc696R6JjrQ3d7HdVqGwuzfyrcaLAKD7K8TX8aehUQ==", + "dependencies": { + "@octokit/types": "^6.0.3", + "bottleneck": "^2.15.3" + } + }, + "node_modules/@octokit/plugin-throttling": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-9.6.0.tgz", + "integrity": "sha512-zn7m1N3vpJDaVzLqjCRdJ0cRzNiekHEWPi8Ww9xyPNrDt5PStHvVE0eR8wy4RSU8Eg7YO8MHyvn6sv25EGVhhg==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.7.0", + "bottleneck": "^2.15.3" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "^6.1.3" + } + }, + "node_modules/@octokit/plugin-throttling/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "license": "MIT" + }, + "node_modules/@octokit/plugin-throttling/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/request": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.2.tgz", + "integrity": "sha512-dZl0ZHx6gOQGcffgm1/Sf6JfEpmh34v3Af2Uci02vzUYz6qEN6zepoRtmybWXIGXFIK8K9ylE3b+duCWqhArtg==", + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^10.1.3", + "@octokit/request-error": "^6.1.7", + "@octokit/types": "^13.6.2", + "fast-content-type-parse": "^2.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/request-error": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.7.tgz", + "integrity": "sha512-69NIppAwaauwZv6aOzb+VVLwt+0havz9GT5YplkeJv7fG7a40qpLt/yZKyiDxAhgz0EtgNdNcb96Z0u+Zyuy2g==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.6.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/request-error/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "license": "MIT" + }, + "node_modules/@octokit/request-error/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/request/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "license": "MIT" + }, + "node_modules/@octokit/request/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@octokit/rest": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-21.1.1.tgz", + "integrity": "sha512-sTQV7va0IUVZcntzy1q3QqPm/r8rWtDCqpRAmb8eXXnKkjoQEtFe3Nt5GTVsHft+R6jJoHeSiVLcgcvhtue/rg==", + "license": "MIT", + "dependencies": { + "@octokit/core": "^6.1.4", + "@octokit/plugin-paginate-rest": "^11.4.2", + "@octokit/plugin-request-log": "^5.3.1", + "@octokit/plugin-rest-endpoint-methods": "^13.3.0" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/@octokit/rest/node_modules/@octokit/openapi-types": { "version": "23.0.1", "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-23.0.1.tgz", @@ -227,34 +278,6 @@ "@octokit/core": ">=6" } }, - "node_modules/@octokit/rest/node_modules/@octokit/request": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.2.tgz", - "integrity": "sha512-dZl0ZHx6gOQGcffgm1/Sf6JfEpmh34v3Af2Uci02vzUYz6qEN6zepoRtmybWXIGXFIK8K9ylE3b+duCWqhArtg==", - "license": "MIT", - "dependencies": { - "@octokit/endpoint": "^10.1.3", - "@octokit/request-error": "^6.1.7", - "@octokit/types": "^13.6.2", - "fast-content-type-parse": "^2.0.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/rest/node_modules/@octokit/request-error": { - "version": "6.1.7", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.7.tgz", - "integrity": "sha512-69NIppAwaauwZv6aOzb+VVLwt+0havz9GT5YplkeJv7fG7a40qpLt/yZKyiDxAhgz0EtgNdNcb96Z0u+Zyuy2g==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^13.6.2" - }, - "engines": { - "node": ">= 18" - } - }, "node_modules/@octokit/rest/node_modules/@octokit/types": { "version": "13.8.0", "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.8.0.tgz", @@ -264,18 +287,6 @@ "@octokit/openapi-types": "^23.0.1" } }, - "node_modules/@octokit/rest/node_modules/before-after-hook": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", - "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==", - "license": "Apache-2.0" - }, - "node_modules/@octokit/rest/node_modules/universal-user-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz", - "integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==", - "license": "ISC" - }, "node_modules/@octokit/types": { "version": "6.33.0", "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.33.0.tgz", @@ -285,22 +296,16 @@ } }, "node_modules/before-after-hook": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz", - "integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ==", - "peer": true + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", + "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==", + "license": "Apache-2.0" }, "node_modules/bottleneck": { "version": "2.19.5", "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==" }, - "node_modules/deprecation": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", - "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", - "peer": true - }, "node_modules/fast-content-type-parse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-2.0.1.tgz", @@ -317,77 +322,11 @@ ], "license": "MIT" }, - "node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "peer": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "peer": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", - "peer": true - }, "node_modules/universal-user-agent": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", - "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==", - "peer": true - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", - "peer": true - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "peer": true, - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "peer": true + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz", + "integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==", + "license": "ISC" } } } diff --git a/.github/workflows/scripts/releases/generate-release-notes/package.json b/.github/workflows/scripts/releases/generate-release-notes/package.json index d7c62c6620..4cd7b41ffa 100644 --- a/.github/workflows/scripts/releases/generate-release-notes/package.json +++ b/.github/workflows/scripts/releases/generate-release-notes/package.json @@ -11,7 +11,7 @@ "license": "ISC", "dependencies": { "@octokit/plugin-retry": "^3.0.9", - "@octokit/plugin-throttling": "^3.5.2", + "@octokit/plugin-throttling": "^9.6.0", "@octokit/rest": "^21.1.1" } } From f2ab4e840effef611fc471b30d1617f95796a860 Mon Sep 17 00:00:00 2001 From: KamFretoZ <14798312+kamfretoz@users.noreply.github.com> Date: Wed, 16 Apr 2025 17:32:06 +0700 Subject: [PATCH 134/162] Qt: Change Default Theme --- pcsx2-qt/Themes.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pcsx2-qt/Themes.cpp b/pcsx2-qt/Themes.cpp index 77d6170e79..ca8b4e9b85 100644 --- a/pcsx2-qt/Themes.cpp +++ b/pcsx2-qt/Themes.cpp @@ -28,7 +28,7 @@ const char* QtHost::GetDefaultThemeName() #ifdef __APPLE__ return ""; #else - return "darkfusion"; + return "darkfusionblue"; #endif } From cd48e78667596df21975b0118609b256a7986d38 Mon Sep 17 00:00:00 2001 From: PCSX2 Bot Date: Fri, 18 Apr 2025 00:01:58 +0000 Subject: [PATCH 135/162] [ci skip] Qt: Update Base Translation. --- pcsx2-qt/Translations/pcsx2-qt_en.ts | 131 ++++++++++++++------------- 1 file changed, 68 insertions(+), 63 deletions(-) diff --git a/pcsx2-qt/Translations/pcsx2-qt_en.ts b/pcsx2-qt/Translations/pcsx2-qt_en.ts index 8fcf2ef0a3..5d07434bc1 100644 --- a/pcsx2-qt/Translations/pcsx2-qt_en.ts +++ b/pcsx2-qt/Translations/pcsx2-qt_en.ts @@ -2392,7 +2392,12 @@ Leaderboard Position: {1} of {2} - + + Not enough free memory available for precaching, ({}GB) required. + + + + Required memory ({}GB) is the above the maximum allowed ({}GB). @@ -10789,77 +10794,77 @@ Do you want to load this save and continue? - + Failed to change window after update. The log may contain more information. - + Upscale multiplier set to {}x. - + Saving screenshot to '{}'. - + Saved screenshot to '{}'. - + Failed to save screenshot to '{}'. - + Host GPU device encountered an error and was recovered. This may have broken rendering. - + CAS is not available, your graphics driver does not support the required functionality. - + with no compression - + with LZMA compression - + with Zstandard compression - + Saving {0} GS dump {1} to '{2}' - + single frame - + multi-frame - + Failed to render/download screenshot. - + Saved GS dump to '{}'. @@ -14779,190 +14784,190 @@ Swap chain: see Microsoft's Terminology Portal. Hotkeys - - - - + + + - - - - - - - - - + + + + + + + + + + Graphics - + Save Screenshot - + Toggle Video Capture - + Save Single Frame GS Dump - + Save Multi Frame GS Dump - + Toggle Software Rendering - + Increase Upscale Multiplier - + Decrease Upscale Multiplier - + Toggle On-Screen Display - + Cycle Aspect Ratio - + Aspect ratio set to '{}'. - + Toggle Hardware Mipmapping - + Hardware mipmapping is now enabled. - + Hardware mipmapping is now disabled. - + Cycle Deinterlace Mode - + Automatic - + Off - + Weave (Top Field First) - + Weave (Bottom Field First) - + Bob (Top Field First) - + Bob (Bottom Field First) - + Blend (Top Field First) - + Blend (Bottom Field First) - + Adaptive (Top Field First) - + Adaptive (Bottom Field First) - + Deinterlace mode set to '{}'. - + Toggle Texture Dumping - + Texture dumping is now enabled. - + Texture dumping is now disabled. - + Toggle Texture Replacements - + Texture replacements are now enabled. - + Texture replacements are now disabled. - + Reload Texture Replacements - + Texture replacements are not enabled. - + Reloading texture replacements... From aaeff2ea0f96e64459b4c8ac70238e477c6ac44d Mon Sep 17 00:00:00 2001 From: TheLastRar Date: Sat, 12 Apr 2025 16:32:50 +0100 Subject: [PATCH 136/162] DEV9: Deduplicate some UDP sockets code --- pcsx2/CMakeLists.txt | 2 + .../DEV9/Sessions/UDP_Session/UDP_Common.cpp | 182 ++++++++++++++++++ pcsx2/DEV9/Sessions/UDP_Session/UDP_Common.h | 31 +++ .../Sessions/UDP_Session/UDP_FixedPort.cpp | 137 ++----------- .../DEV9/Sessions/UDP_Session/UDP_Session.cpp | 136 ++----------- pcsx2/pcsx2.vcxproj | 2 + pcsx2/pcsx2.vcxproj.filters | 6 + 7 files changed, 246 insertions(+), 250 deletions(-) create mode 100644 pcsx2/DEV9/Sessions/UDP_Session/UDP_Common.cpp create mode 100644 pcsx2/DEV9/Sessions/UDP_Session/UDP_Common.h diff --git a/pcsx2/CMakeLists.txt b/pcsx2/CMakeLists.txt index 93467e202f..99afcd91fe 100644 --- a/pcsx2/CMakeLists.txt +++ b/pcsx2/CMakeLists.txt @@ -309,6 +309,7 @@ set(pcsx2DEV9Sources DEV9/Sessions/TCP_Session/TCP_Session.cpp DEV9/Sessions/TCP_Session/TCP_Session_In.cpp DEV9/Sessions/TCP_Session/TCP_Session_Out.cpp + DEV9/Sessions/UDP_Session/UDP_Common.cpp DEV9/Sessions/UDP_Session/UDP_FixedPort.cpp DEV9/Sessions/UDP_Session/UDP_Session.cpp DEV9/smap.cpp @@ -354,6 +355,7 @@ set(pcsx2DEV9Headers DEV9/Sessions/BaseSession.h DEV9/Sessions/ICMP_Session/ICMP_Session.h DEV9/Sessions/TCP_Session/TCP_Session.h + DEV9/Sessions/UDP_Session/UDP_Common.h DEV9/Sessions/UDP_Session/UDP_FixedPort.h DEV9/Sessions/UDP_Session/UDP_BaseSession.h DEV9/Sessions/UDP_Session/UDP_Session.h diff --git a/pcsx2/DEV9/Sessions/UDP_Session/UDP_Common.cpp b/pcsx2/DEV9/Sessions/UDP_Session/UDP_Common.cpp new file mode 100644 index 0000000000..23bdbae55e --- /dev/null +++ b/pcsx2/DEV9/Sessions/UDP_Session/UDP_Common.cpp @@ -0,0 +1,182 @@ +// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#include + +#include "common/Console.h" + +#ifdef __POSIX__ +#define SOCKET_ERROR -1 +#define INVALID_SOCKET -1 +#include +#include +#include +#include +#endif + +#ifdef _WIN32 +#include "common/RedtapeWindows.h" +#include +#else +#include +#endif + +#include "UDP_Common.h" +#include "DEV9/PacketReader/IP/UDP/UDP_Packet.h" + +using namespace PacketReader; +using namespace PacketReader::IP; +using namespace PacketReader::IP::UDP; + +namespace Sessions::UDP_Common +{ +#ifdef _WIN32 + SOCKET CreateSocket(IP_Address adapterIP, std::optional port) + { + SOCKET client = INVALID_SOCKET; +#elif defined(__POSIX__) + int CreateSocket(IP_Address adapterIP, std::optional port) + { + int client = INVALID_SOCKET; +#endif + + int ret; + client = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (client == INVALID_SOCKET) + { + Console.Error("DEV9: UDP: Failed to open socket. Error: %d", +#ifdef _WIN32 + WSAGetLastError()); +#elif defined(__POSIX__) + errno); +#endif + return INVALID_SOCKET; + } + + constexpr int reuseAddress = true; // BOOL on Windows + ret = setsockopt(client, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&reuseAddress), sizeof(reuseAddress)); + + if (ret == SOCKET_ERROR) + Console.Error("DEV9: UDP: Failed to set SO_REUSEADDR. Error: %d", +#ifdef _WIN32 + WSAGetLastError()); +#elif defined(__POSIX__) + errno); +#endif + + if (port.has_value() || adapterIP != IP_Address{{{0, 0, 0, 0}}}) + { + sockaddr_in endpoint{}; + endpoint.sin_family = AF_INET; + endpoint.sin_addr = std::bit_cast(adapterIP); + if (port.has_value()) + endpoint.sin_port = htons(port.value()); + + ret = bind(client, reinterpret_cast(&endpoint), sizeof(endpoint)); + + if (ret == SOCKET_ERROR) + Console.Error("DEV9: UDP: Failed to bind socket. Error: %d", +#ifdef _WIN32 + WSAGetLastError()); +#elif defined(__POSIX__) + errno); +#endif + } + return client; + } + +#ifdef _WIN32 + std::tuple, bool> RecvFrom(SOCKET client, u16 port) +#elif defined(__POSIX__) + std::tuple, bool> RecvFrom(int client, u16 port) +#endif + { + int ret; + fd_set sReady; + fd_set sExcept; + + // not const Linux + timeval nowait{}; + FD_ZERO(&sReady); + FD_ZERO(&sExcept); + FD_SET(client, &sReady); + FD_SET(client, &sExcept); + ret = select(client + 1, &sReady, nullptr, &sExcept, &nowait); + + if (ret == SOCKET_ERROR) + { + Console.Error("DEV9: UDP: select failed. Error code: %d", +#ifdef _WIN32 + WSAGetLastError()); +#elif defined(__POSIX__) + errno); +#endif + return {std::nullopt, true}; + } + else if (FD_ISSET(client, &sExcept)) + { +#ifdef _WIN32 + int len = sizeof(ret); + if (getsockopt(client, SOL_SOCKET, SO_ERROR, reinterpret_cast(&ret), &len) < 0) + { + Console.Error("DEV9: UDP: Unknown UDP connection error (getsockopt error: %d)", WSAGetLastError()); + } +#elif defined(__POSIX__) + socklen_t len = sizeof(ret); + if (getsockopt(client, SOL_SOCKET, SO_ERROR, reinterpret_cast(&ret), &len) < 0) + Console.Error("DEV9: UDP: Unknown UDP connection error (getsockopt error: %d)", errno); +#endif + else + Console.Error("DEV9: UDP: Socket error: %d", ret); + + return {std::nullopt, false}; + } + else if (FD_ISSET(client, &sReady)) + { + unsigned long available = 0; + PayloadData* recived = nullptr; + std::unique_ptr buffer; + sockaddr_in endpoint{}; + + // FIONREAD returns total size of all available messages + // however, we only read one message at a time +#ifdef _WIN32 + ret = ioctlsocket(client, FIONREAD, &available); +#elif defined(__POSIX__) + ret = ioctl(client, FIONREAD, &available); +#endif + if (ret != SOCKET_ERROR) + { + buffer = std::make_unique(available); + +#ifdef _WIN32 + int fromlen = sizeof(endpoint); +#elif defined(__POSIX__) + socklen_t fromlen = sizeof(endpoint); +#endif + ret = recvfrom(client, reinterpret_cast(buffer.get()), available, 0, reinterpret_cast(&endpoint), &fromlen); + } + + if (ret == SOCKET_ERROR) + { + Console.Error("DEV9: UDP: UDP recv error: %d", +#ifdef _WIN32 + WSAGetLastError()); +#elif defined(__POSIX__) + errno); +#endif + return {std::nullopt, false}; + } + + recived = new PayloadData(ret); + memcpy(recived->data.get(), buffer.get(), ret); + + std::unique_ptr iRet = std::make_unique(recived); + iRet->destinationPort = port; + iRet->sourcePort = ntohs(endpoint.sin_port); + + return {ReceivedPayload{std::bit_cast(endpoint.sin_addr), std::move(iRet)}, true}; + } + return {std::nullopt, true}; + } +} // namespace Sessions::UDP_Common diff --git a/pcsx2/DEV9/Sessions/UDP_Session/UDP_Common.h b/pcsx2/DEV9/Sessions/UDP_Session/UDP_Common.h new file mode 100644 index 0000000000..d2233c9b47 --- /dev/null +++ b/pcsx2/DEV9/Sessions/UDP_Session/UDP_Common.h @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#pragma once +#include +#ifdef _WIN32 +#include +#elif defined(__POSIX__) +#include +#endif + +#include "common/Pcsx2Defs.h" +#include "DEV9/Sessions/BaseSession.h" + +namespace Sessions::UDP_Common +{ + // Binds the socket when provided with an IP +#ifdef _WIN32 + SOCKET CreateSocket(PacketReader::IP::IP_Address adapterIP, std::optional port); +#elif defined(__POSIX__) + int CreateSocket(PacketReader::IP::IP_Address adapterIP, std::optional port); +#endif + + // Receives from the client and packages the data into ReceivedPayload + // port is the local port to be written to the UDP header in ReceivedPayload +#ifdef _WIN32 + std::tuple, bool> RecvFrom(SOCKET client, u16 port); +#elif defined(__POSIX__) + std::tuple, bool> RecvFrom(int client, u16 port); +#endif +} // namespace Sessions::UDP_Common diff --git a/pcsx2/DEV9/Sessions/UDP_Session/UDP_FixedPort.cpp b/pcsx2/DEV9/Sessions/UDP_Session/UDP_FixedPort.cpp index 5e08805bc6..9b9ddd09ac 100644 --- a/pcsx2/DEV9/Sessions/UDP_Session/UDP_FixedPort.cpp +++ b/pcsx2/DEV9/Sessions/UDP_Session/UDP_FixedPort.cpp @@ -21,6 +21,7 @@ #include #endif +#include "UDP_Common.h" #include "UDP_FixedPort.h" #include "DEV9/PacketReader/IP/UDP/UDP_Packet.h" @@ -51,33 +52,15 @@ namespace Sessions void UDP_FixedPort::Init() { - int ret; - client = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + client = UDP_Common::CreateSocket(adapterIP, port); if (client == INVALID_SOCKET) { - Console.Error("DEV9: UDP: Failed to open socket. Error: %d", -#ifdef _WIN32 - WSAGetLastError()); -#elif defined(__POSIX__) - errno); -#endif RaiseEventConnectionClosed(); return; } - constexpr int reuseAddress = true; // BOOL on Windows - ret = setsockopt(client, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&reuseAddress), sizeof(reuseAddress)); - - if (ret == SOCKET_ERROR) - Console.Error("DEV9: UDP: Failed to set SO_REUSEADDR. Error: %d", -#ifdef _WIN32 - WSAGetLastError()); -#elif defined(__POSIX__) - errno); -#endif - constexpr int broadcastEnable = true; // BOOL on Windows - ret = setsockopt(client, SOL_SOCKET, SO_BROADCAST, reinterpret_cast(&broadcastEnable), sizeof(broadcastEnable)); + const int ret = setsockopt(client, SOL_SOCKET, SO_BROADCAST, reinterpret_cast(&broadcastEnable), sizeof(broadcastEnable)); if (ret == SOCKET_ERROR) Console.Error("DEV9: UDP: Failed to set SO_BROADCAST. Error: %d", @@ -87,25 +70,6 @@ namespace Sessions errno); #endif - sockaddr_in endpoint{}; - endpoint.sin_family = AF_INET; - endpoint.sin_addr = std::bit_cast(adapterIP); - endpoint.sin_port = htons(port); - - ret = bind(client, reinterpret_cast(&endpoint), sizeof(endpoint)); - - if (ret == SOCKET_ERROR) - { - Console.Error("DEV9: UDP: Failed to bind socket. Error: %d", -#ifdef _WIN32 - WSAGetLastError()); -#elif defined(__POSIX__) - errno); -#endif - RaiseEventConnectionClosed(); - return; - } - open.store(true); } @@ -114,102 +78,25 @@ namespace Sessions if (!open.load()) return std::nullopt; - int ret; - fd_set sReady; - fd_set sExcept; + std::optional ret; + bool success; + std::tie(ret, success) = UDP_Common::RecvFrom(client, port); - timeval nowait{}; - FD_ZERO(&sReady); - FD_ZERO(&sExcept); - FD_SET(client, &sReady); - FD_SET(client, &sExcept); - ret = select(client + 1, &sReady, nullptr, &sExcept, &nowait); - - bool hasData; - if (ret == SOCKET_ERROR) + if (!success) { - hasData = false; - Console.Error("DEV9: UDP: select failed. Error code: %d", -#ifdef _WIN32 - WSAGetLastError()); -#elif defined(__POSIX__) - errno); -#endif + RaiseEventConnectionClosed(); + return std::nullopt; } - else if (FD_ISSET(client, &sExcept)) + else if (ret.has_value()) { - hasData = false; - - int error = 0; -#ifdef _WIN32 - int len = sizeof(error); - if (getsockopt(client, SOL_SOCKET, SO_ERROR, reinterpret_cast(&error), &len) < 0) - Console.Error("DEV9: UDP: Unknown UDP connection error (getsockopt error: %d)", WSAGetLastError()); -#elif defined(__POSIX__) - socklen_t len = sizeof(error); - if (getsockopt(client, SOL_SOCKET, SO_ERROR, reinterpret_cast(&error), &len) < 0) - Console.Error("DEV9: UDP: Unknown UDP connection error (getsockopt error: %d)", errno); -#endif - else - Console.Error("DEV9: UDP: Recv error: %d", error); - } - else - hasData = FD_ISSET(client, &sReady); - - if (hasData) - { - unsigned long available = 0; - PayloadData* recived = nullptr; - std::unique_ptr buffer; - sockaddr_in endpoint{}; - - // FIONREAD returns total size of all available messages - // however, we only read one message at a time -#ifdef _WIN32 - ret = ioctlsocket(client, FIONREAD, &available); -#elif defined(__POSIX__) - ret = ioctl(client, FIONREAD, &available); -#endif - if (ret != SOCKET_ERROR) - { - buffer = std::make_unique(available); - -#ifdef _WIN32 - int fromlen = sizeof(endpoint); -#elif defined(__POSIX__) - socklen_t fromlen = sizeof(endpoint); -#endif - ret = recvfrom(client, reinterpret_cast(buffer.get()), available, 0, reinterpret_cast(&endpoint), &fromlen); - } - - if (ret == SOCKET_ERROR) - { - Console.Error("DEV9: UDP: UDP recv error: %d", -#ifdef _WIN32 - WSAGetLastError()); -#elif defined(__POSIX__) - errno); -#endif - RaiseEventConnectionClosed(); - return std::nullopt; - } - - recived = new PayloadData(ret); - memcpy(recived->data.get(), buffer.get(), ret); - - std::unique_ptr iRet = std::make_unique(recived); - iRet->destinationPort = port; - iRet->sourcePort = ntohs(endpoint.sin_port); - - IP_Address srvIP = std::bit_cast(endpoint.sin_addr); { std::lock_guard numberlock(connectionSentry); for (size_t i = 0; i < connections.size(); i++) { UDP_BaseSession* s = connections[i]; - if (s->WillRecive(srvIP)) - return ReceivedPayload{srvIP, std::move(iRet)}; + if (s->WillRecive(ret.value().sourceIP)) + return ret; } } Console.Error("DEV9: UDP: Unexpected packet, dropping"); diff --git a/pcsx2/DEV9/Sessions/UDP_Session/UDP_Session.cpp b/pcsx2/DEV9/Sessions/UDP_Session/UDP_Session.cpp index 5f246417d3..82b9534bf2 100644 --- a/pcsx2/DEV9/Sessions/UDP_Session/UDP_Session.cpp +++ b/pcsx2/DEV9/Sessions/UDP_Session/UDP_Session.cpp @@ -20,6 +20,7 @@ #endif #include "UDP_Session.h" +#include "UDP_Common.h" #include "DEV9/PacketReader/IP/UDP/UDP_Packet.h" using namespace PacketReader; @@ -75,97 +76,17 @@ namespace Sessions return std::nullopt; } - int ret; - fd_set sReady; - fd_set sExcept; + std::optional ret; + bool success; + std::tie(ret, success) = UDP_Common::RecvFrom(client, srcPort); - timeval nowait{}; - FD_ZERO(&sReady); - FD_ZERO(&sExcept); - FD_SET(client, &sReady); - FD_SET(client, &sExcept); - ret = select(client + 1, &sReady, nullptr, &sExcept, &nowait); - - bool hasData; - if (ret == SOCKET_ERROR) + if (!success) { - hasData = false; - Console.Error("DEV9: UDP: Select failed. Error code: %d", -#ifdef _WIN32 - WSAGetLastError()); -#elif defined(__POSIX__) - errno); -#endif - } - else if (FD_ISSET(client, &sExcept)) - { - hasData = false; - - int error = 0; -#ifdef _WIN32 - int len = sizeof(error); - if (getsockopt(client, SOL_SOCKET, SO_ERROR, reinterpret_cast(&error), &len) < 0) - Console.Error("DEV9: UDP: Unknown UDP connection error (getsockopt error: %d)", WSAGetLastError()); -#elif defined(__POSIX__) - socklen_t len = sizeof(error); - if (getsockopt(client, SOL_SOCKET, SO_ERROR, reinterpret_cast(&error), &len) < 0) - Console.Error("DEV9: UDP: Unknown UDP connection error (getsockopt error: %d)", errno); -#endif - else - Console.Error("DEV9: UDP: Recv error: %d", error); - } - else - hasData = FD_ISSET(client, &sReady); - - if (hasData) - { - unsigned long available = 0; - PayloadData* recived = nullptr; - std::unique_ptr buffer; - sockaddr_in endpoint{}; - - // FIONREAD returns total size of all available messages - // however, we only read one message at a time -#ifdef _WIN32 - ret = ioctlsocket(client, FIONREAD, &available); -#elif defined(__POSIX__) - ret = ioctl(client, FIONREAD, &available); -#endif - if (ret != SOCKET_ERROR) - { - buffer = std::make_unique(available); - -#ifdef _WIN32 - int fromlen = sizeof(endpoint); -#elif defined(__POSIX__) - socklen_t fromlen = sizeof(endpoint); -#endif - ret = recvfrom(client, reinterpret_cast(buffer.get()), available, 0, reinterpret_cast(&endpoint), &fromlen); - } - - if (ret == SOCKET_ERROR) - { - Console.Error("DEV9: UDP: Recv error: %d", -#ifdef _WIN32 - WSAGetLastError()); -#elif defined(__POSIX__) - errno); -#endif - RaiseEventConnectionClosed(); - return std::nullopt; - } - - recived = new PayloadData(ret); - memcpy(recived->data.get(), buffer.get(), ret); - - std::unique_ptr iRet = std::make_unique(recived); - iRet->destinationPort = srcPort; - iRet->sourcePort = destPort; - - deathClockStart.store(std::chrono::steady_clock::now()); - - return ReceivedPayload{destIP, std::move(iRet)}; + RaiseEventConnectionClosed(); + return std::nullopt; } + else if (ret.has_value()) + return ret; if (std::chrono::steady_clock::now() - deathClockStart.load() > MAX_IDLE) { @@ -211,54 +132,19 @@ namespace Sessions destPort = udp.destinationPort; srcPort = udp.sourcePort; - int ret; - client = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + client = UDP_Common::CreateSocket(adapterIP, std::nullopt); if (client == INVALID_SOCKET) { - Console.Error("DEV9: UDP: Failed to open socket. Error: %d", -#ifdef _WIN32 - WSAGetLastError()); -#elif defined(__POSIX__) - errno); -#endif RaiseEventConnectionClosed(); return false; } - constexpr int reuseAddress = true; // BOOL on Windows - ret = setsockopt(client, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&reuseAddress), sizeof(reuseAddress)); - - if (ret == SOCKET_ERROR) - Console.Error("DEV9: UDP: Failed to set SO_REUSEADDR. Error: %d", -#ifdef _WIN32 - WSAGetLastError()); -#elif defined(__POSIX__) - errno); -#endif - - if (adapterIP.integer != 0) - { - sockaddr_in endpoint{}; - endpoint.sin_family = AF_INET; - endpoint.sin_addr = std::bit_cast(adapterIP); - - ret = bind(client, reinterpret_cast(&endpoint), sizeof(endpoint)); - - if (ret == SOCKET_ERROR) - Console.Error("DEV9: UDP: Failed to bind socket. Error: %d", -#ifdef _WIN32 - WSAGetLastError()); -#elif defined(__POSIX__) - errno); -#endif - } - sockaddr_in endpoint{}; endpoint.sin_family = AF_INET; endpoint.sin_addr = std::bit_cast(destIP); endpoint.sin_port = htons(destPort); - ret = connect(client, reinterpret_cast(&endpoint), sizeof(endpoint)); + const int ret = connect(client, reinterpret_cast(&endpoint), sizeof(endpoint)); if (ret == SOCKET_ERROR) { diff --git a/pcsx2/pcsx2.vcxproj b/pcsx2/pcsx2.vcxproj index 4535dbb8a8..07ec5f367c 100644 --- a/pcsx2/pcsx2.vcxproj +++ b/pcsx2/pcsx2.vcxproj @@ -195,6 +195,7 @@ + @@ -636,6 +637,7 @@ + diff --git a/pcsx2/pcsx2.vcxproj.filters b/pcsx2/pcsx2.vcxproj.filters index e72297d4b7..367c744acf 100644 --- a/pcsx2/pcsx2.vcxproj.filters +++ b/pcsx2/pcsx2.vcxproj.filters @@ -944,6 +944,9 @@ System\Ps2\DEV9\Sessions\TCP_Session + + System\Ps2\DEV9\Sessions\UDP_Session + System\Ps2\DEV9\Sessions\UDP_Session @@ -1844,6 +1847,9 @@ System\Ps2\DEV9\Sessions\TCP_Session + + System\Ps2\DEV9\Sessions\UDP_Session + System\Ps2\DEV9\Sessions\UDP_Session From 6c49a5aa9d2acd4347ca720ec5a6517509abc83c Mon Sep 17 00:00:00 2001 From: TheLastRar Date: Sat, 12 Apr 2025 16:32:50 +0100 Subject: [PATCH 137/162] DEV9: Fix race condition in UDP sockets --- .../Sessions/UDP_Session/UDP_BaseSession.h | 1 + .../Sessions/UDP_Session/UDP_FixedPort.cpp | 43 ++++++++++++++----- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/pcsx2/DEV9/Sessions/UDP_Session/UDP_BaseSession.h b/pcsx2/DEV9/Sessions/UDP_Session/UDP_BaseSession.h index 5edccecac4..513a4f4f68 100644 --- a/pcsx2/DEV9/Sessions/UDP_Session/UDP_BaseSession.h +++ b/pcsx2/DEV9/Sessions/UDP_Session/UDP_BaseSession.h @@ -16,5 +16,6 @@ namespace Sessions } virtual bool WillRecive(PacketReader::IP::IP_Address parDestIP) = 0; + virtual void ForceClose() { RaiseEventConnectionClosed(); }; }; } // namespace Sessions diff --git a/pcsx2/DEV9/Sessions/UDP_Session/UDP_FixedPort.cpp b/pcsx2/DEV9/Sessions/UDP_Session/UDP_FixedPort.cpp index 9b9ddd09ac..c980541805 100644 --- a/pcsx2/DEV9/Sessions/UDP_Session/UDP_FixedPort.cpp +++ b/pcsx2/DEV9/Sessions/UDP_Session/UDP_FixedPort.cpp @@ -84,8 +84,28 @@ namespace Sessions if (!success) { - RaiseEventConnectionClosed(); - return std::nullopt; + // See Reset() for why we copy the vector. + std::vector connectionsCopy; + { + std::lock_guard numberlock(connectionSentry); + open.store(false); + connectionsCopy = connections; + } + + if (connectionsCopy.size() == 0) + { + // Can close immediately. + RaiseEventConnectionClosed(); + return std::nullopt; + } + else + { + // Need to wait for child connections to close. + for (size_t i = 0; i < connectionsCopy.size(); i++) + connectionsCopy[i]->ForceClose(); + + return std::nullopt; + } } else if (ret.has_value()) { @@ -112,10 +132,12 @@ namespace Sessions void UDP_FixedPort::Reset() { - // Reseting a session may cause that session to close itself, - // when that happens, the connections vector gets modified via our close handler. - // Duplicate the vector to avoid iterating over a modified collection, - // this also avoids the issue of recursive locking when our close handler takes a lock. + /* + * Reseting a session may cause that session to close itself, + * when that happens, the connections vector gets modified via our close handler. + * Duplicate the vector to avoid iterating over a modified collection, + * this also avoids the issue of recursive locking when our close handler takes a lock. + */ std::vector connectionsCopy; { std::lock_guard numberlock(connectionSentry); @@ -128,16 +150,15 @@ namespace Sessions UDP_Session* UDP_FixedPort::NewClientSession(ConnectionKey parNewKey, bool parIsBrodcast, bool parIsMulticast) { + // Lock the whole function so we can't race between the open check and creating the session + std::lock_guard numberlock(connectionSentry); if (!open.load()) return nullptr; UDP_Session* s = new UDP_Session(parNewKey, adapterIP, parIsBrodcast, parIsMulticast, client); s->AddConnectionClosedHandler([&](BaseSession* session) { HandleChildConnectionClosed(session); }); - { - std::lock_guard numberlock(connectionSentry); - connections.push_back(s); - } + connections.push_back(s); return s; } @@ -159,6 +180,8 @@ namespace Sessions UDP_FixedPort::~UDP_FixedPort() { + DevCon.WriteLn("DEV9: Socket: UDPFixedPort %d had %d child connections", port, connections.size()); + open.store(false); if (client != INVALID_SOCKET) { From 39b4905ef19584a40274a33edb46468605fe392a Mon Sep 17 00:00:00 2001 From: TheLastRar Date: Sat, 12 Apr 2025 16:32:50 +0100 Subject: [PATCH 138/162] DEV9: Fix race condition when handling closed socket connections --- pcsx2/DEV9/sockets.cpp | 22 ++++++++++++++++------ pcsx2/DEV9/sockets.h | 8 ++++++-- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/pcsx2/DEV9/sockets.cpp b/pcsx2/DEV9/sockets.cpp index 745de87d10..667944118c 100644 --- a/pcsx2/DEV9/sockets.cpp +++ b/pcsx2/DEV9/sockets.cpp @@ -203,14 +203,19 @@ bool SocketAdapter::recv(NetPacket* pkt) ScopedGuard cleanup([&]() { // Garbage collect closed connections - for (BaseSession* s : deleteQueueRecvThread) - delete s; - deleteQueueRecvThread.clear(); + if (deleteQueueRecvThread.size() != 0) + { + std::lock_guard deletelock(deleteRecvSentry); + for (BaseSession* s : deleteQueueRecvThread) + delete s; + deleteQueueRecvThread.clear(); + } }); EthernetFrame* bFrame; if (!vRecBuffer.Dequeue(&bFrame)) { + std::lock_guard deletelock(deleteSendSentry); std::vector keys = connections.GetKeys(); for (size_t i = 0; i < keys.size(); i++) { @@ -259,9 +264,13 @@ bool SocketAdapter::send(NetPacket* pkt) pxAssert(std::this_thread::get_id() == sendThreadId); ScopedGuard cleanup([&]() { // Garbage collect closed connections - for (BaseSession* s : deleteQueueSendThread) - delete s; - deleteQueueSendThread.clear(); + if (deleteQueueSendThread.size() != 0) + { + std::lock_guard deletelock(deleteSendSentry); + for (BaseSession* s : deleteQueueSendThread) + delete s; + deleteQueueSendThread.clear(); + } }); EthernetFrame frame(pkt); @@ -375,6 +384,7 @@ bool SocketAdapter::SendIP(IP_Packet* ipPkt) Key.ip = ipPkt->destinationIP; Key.protocol = ipPkt->protocol; + std::lock_guard deletelock(deleteRecvSentry); switch (ipPkt->protocol) //(Prase Payload) { case (u8)IP_Type::ICMP: diff --git a/pcsx2/DEV9/sockets.h b/pcsx2/DEV9/sockets.h index ae970e80ad..b5b9dd538b 100644 --- a/pcsx2/DEV9/sockets.h +++ b/pcsx2/DEV9/sockets.h @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-3.0+ #pragma once +#include #include #include "net.h" @@ -23,14 +24,17 @@ class SocketAdapter : public NetAdapter bool initialized = false; PacketReader::IP::IP_Address adapterIP; - //Sentrys replaced by the requirment for each session class to have thread safe destructor - ThreadSafeMap connections; ThreadSafeMap fixedUDPPorts; std::thread::id sendThreadId; std::vector deleteQueueSendThread; std::vector deleteQueueRecvThread; + //Mutex to be held when processing the delete queue. + //The Send thread will lock the RecvSentry to prevent the recv thread + //from deleting a session the send thread might be currently working on. + std::mutex deleteSendSentry; + std::mutex deleteRecvSentry; public: SocketAdapter(); From 07be6bb5ae8a5c0e7c314bcc1129c28a6936aace Mon Sep 17 00:00:00 2001 From: TheLastRar Date: Sat, 12 Apr 2025 16:32:50 +0100 Subject: [PATCH 139/162] DEV9: Ignore UDP socket ICMP errors on recv These where already ignored on send --- .../DEV9/Sessions/UDP_Session/UDP_Common.cpp | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/pcsx2/DEV9/Sessions/UDP_Session/UDP_Common.cpp b/pcsx2/DEV9/Sessions/UDP_Session/UDP_Common.cpp index 23bdbae55e..7268c33f1b 100644 --- a/pcsx2/DEV9/Sessions/UDP_Session/UDP_Common.cpp +++ b/pcsx2/DEV9/Sessions/UDP_Session/UDP_Common.cpp @@ -129,6 +129,7 @@ namespace Sessions::UDP_Common else Console.Error("DEV9: UDP: Socket error: %d", ret); + // All socket errors assumed fatal. return {std::nullopt, false}; } else if (FD_ISSET(client, &sReady)) @@ -159,13 +160,23 @@ namespace Sessions::UDP_Common if (ret == SOCKET_ERROR) { - Console.Error("DEV9: UDP: UDP recv error: %d", #ifdef _WIN32 - WSAGetLastError()); + ret = WSAGetLastError(); #elif defined(__POSIX__) - errno); + ret = errno; +#endif + Console.Error("DEV9: UDP: recvfrom error: %d", ret); + + /* + * We can receive an ICMP Port Unreacable error as a WSAECONNRESET/ECONNREFUSED error + * Ignore the error, recv will be retried next loop + */ + return {std::nullopt, +#ifdef _WIN32 + ret == WSAECONNRESET}; +#elif defined(__POSIX__) + ret == ECONNREFUSED}; #endif - return {std::nullopt, false}; } recived = new PayloadData(ret); From 01120f612007fd84a678ac7fce65b1cba85503f3 Mon Sep 17 00:00:00 2001 From: Immersion95 <47425204+Immersion95@users.noreply.github.com> Date: Tue, 15 Apr 2025 15:19:04 +0200 Subject: [PATCH 140/162] GameDB: Adds Software FMV hack to Soulcalibur 2/3 Games Fixes https://github.com/PCSX2/pcsx2/issues/2852 --- bin/resources/GameIndex.yaml | 49 +++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/bin/resources/GameIndex.yaml b/bin/resources/GameIndex.yaml index 7592b9151e..43b9adc777 100644 --- a/bin/resources/GameIndex.yaml +++ b/bin/resources/GameIndex.yaml @@ -1413,6 +1413,8 @@ SCAJ-20023: region: "NTSC-Unk" clampModes: vuClampMode: 2 # Respawn issues, Fixes SPS, avoids teleporting characters. + gameFixes: + - SoftwareRendererFMVHack # Horizontal bottom line in FMV. gsHWFixes: alignSprite: 1 # Fixes vertical lines. SCAJ-20024: @@ -2206,10 +2208,11 @@ SCAJ-20158: SCAJ-20159: name: "SoulCalibur III" region: "NTSC-C" - gameFixes: - - EETimingHack # Fixes bad colours on character select when in Progressive Scan. clampModes: vuClampMode: 2 # Respawn issues, Fixes SPS, avoids teleporting characters. + gameFixes: + - EETimingHack # Fixes bad colours on character select when in Progressive Scan. + - SoftwareRendererFMVHack # Horizontal bottom line in FMV. gsHWFixes: alignSprite: 1 # Fixes vertical lines. nativeScaling: 2 # Fixes misaligned bloom. @@ -4212,10 +4215,11 @@ SCED-53662: SCED-53674: name: "SoulCalibur III [Demo]" region: "PAL-E" - gameFixes: - - EETimingHack # Fixes bad colours on character select when in Progressive Scan. clampModes: vuClampMode: 2 # Respawn issues, Fixes SPS, avoids teleporting characters. + gameFixes: + - EETimingHack # Fixes bad colours on character select when in Progressive Scan. + - SoftwareRendererFMVHack # Horizontal bottom line in FMV. gsHWFixes: alignSprite: 1 # Fixes vertical lines. nativeScaling: 2 # Fixes misaligned bloom. @@ -5888,10 +5892,11 @@ SCES-53312: name: "SoulCalibur III" region: "PAL-M5" compat: 5 - gameFixes: - - EETimingHack # Fixes bad colours on character select when in Progressive Scan. clampModes: vuClampMode: 2 # Respawn issues, Fixes SPS, avoids teleporting characters. + gameFixes: + - EETimingHack # Fixes bad colours on character select when in Progressive Scan. + - SoftwareRendererFMVHack # Horizontal bottom line in FMV. gsHWFixes: alignSprite: 1 # Fixes vertical lines. nativeScaling: 2 # Fixes misaligned bloom. @@ -7023,6 +7028,8 @@ SCKA-20016: compat: 5 clampModes: vuClampMode: 2 # Respawn issues, Fixes SPS, avoids teleporting characters. + gameFixes: + - SoftwareRendererFMVHack # Horizontal bottom line in FMV. gsHWFixes: alignSprite: 1 # Fixes vertical lines. SCKA-20017: @@ -7324,10 +7331,11 @@ SCKA-20059: name: "SoulCalibur III" region: "NTSC-K" compat: 5 - gameFixes: - - EETimingHack # Fixes bad colours on character select when in Progressive Scan. clampModes: vuClampMode: 2 # Respawn issues, Fixes SPS, avoids teleporting characters. + gameFixes: + - EETimingHack # Fixes bad colours on character select when in Progressive Scan. + - SoftwareRendererFMVHack # Horizontal bottom line in FMV. gsHWFixes: alignSprite: 1 # Fixes vertical lines. nativeScaling: 2 # Fixes misaligned bloom. @@ -13189,6 +13197,8 @@ SLED-51901: region: "PAL-E" clampModes: vuClampMode: 2 # Respawn issues, Fixes SPS, avoids teleporting characters. + gameFixes: + - SoftwareRendererFMVHack # Horizontal bottom line in FMV. gsHWFixes: alignSprite: 1 # Fixes vertical lines. SLED-51902: @@ -17965,6 +17975,8 @@ SLES-51799: compat: 5 clampModes: vuClampMode: 2 # Respawn issues, Fixes SPS, avoids teleporting characters. + gameFixes: + - SoftwareRendererFMVHack # Horizontal bottom line in FMV. gsHWFixes: alignSprite: 1 # Fixes vertical lines. SLES-51800: @@ -35040,10 +35052,11 @@ SLPM-61133: name-sort: "そうるきゃりばー3 [たいけんばん]" name-en: "SoulCalibur III [Trial]" region: "NTSC-J" - gameFixes: - - EETimingHack # Fixes bad colours on character select when in Progressive Scan. clampModes: vuClampMode: 2 # Respawn issues, Fixes SPS, avoids teleporting characters. + gameFixes: + - EETimingHack # Fixes bad colours on character select when in Progressive Scan. + - SoftwareRendererFMVHack # Horizontal bottom line in FMV. gsHWFixes: alignSprite: 1 # Fixes vertical lines. nativeScaling: 2 # Fixes misaligned bloom. @@ -56345,6 +56358,8 @@ SLPS-25230: compat: 5 clampModes: vuClampMode: 2 # Respawn issues, Fixes SPS, avoids teleporting characters. + gameFixes: + - SoftwareRendererFMVHack # Horizontal bottom line in FMV. gsHWFixes: alignSprite: 1 # Fixes vertical lines. SLPS-25231: @@ -58389,10 +58404,11 @@ SLPS-25577: name-en: "SoulCalibur III" region: "NTSC-J" compat: 5 - gameFixes: - - EETimingHack # Fixes bad colours on character select when in Progressive Scan. clampModes: vuClampMode: 2 # Respawn issues, Fixes SPS, avoids teleporting characters. + gameFixes: + - EETimingHack # Fixes bad colours on character select when in Progressive Scan. + - SoftwareRendererFMVHack # Horizontal bottom line in FMV. gsHWFixes: alignSprite: 1 # Fixes vertical lines. nativeScaling: 2 # Fixes misaligned bloom. @@ -64797,6 +64813,8 @@ SLUS-20643: compat: 5 clampModes: vuClampMode: 2 # Respawn issues, Fixes SPS, avoids teleporting characters. + gameFixes: + - SoftwareRendererFMVHack # Horizontal bottom line in FMV. gsHWFixes: alignSprite: 1 # Fixes vertical lines. SLUS-20644: @@ -68110,10 +68128,11 @@ SLUS-21216: name: "SoulCalibur III" region: "NTSC-U" compat: 5 - gameFixes: - - EETimingHack # Fixes bad colours on character select when in Progressive Scan. clampModes: vuClampMode: 2 # Respawn issues, Fixes SPS, avoids teleporting characters. + gameFixes: + - EETimingHack # Fixes bad colours on character select when in Progressive Scan. + - SoftwareRendererFMVHack # Horizontal bottom line in FMV. gsHWFixes: alignSprite: 1 # Fixes vertical lines. nativeScaling: 2 # Fixes misaligned bloom. @@ -72749,6 +72768,8 @@ SLUS-29058: region: "NTSC-U" clampModes: vuClampMode: 2 # Respawn issues, Fixes SPS, avoids teleporting characters. + gameFixes: + - SoftwareRendererFMVHack # Horizontal bottom line in FMV. gsHWFixes: alignSprite: 1 # Fixes vertical lines. SLUS-29059: From 18a7e8b22c3ff8b693b9eac05b3f993d53688c82 Mon Sep 17 00:00:00 2001 From: TheLastRar Date: Sun, 9 Mar 2025 19:41:19 +0000 Subject: [PATCH 141/162] SDLInputSource: Enable support for the Sixaxis driver on Windows --- pcsx2/Input/SDLInputSource.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pcsx2/Input/SDLInputSource.cpp b/pcsx2/Input/SDLInputSource.cpp index eb3035b18f..8cb0a0b4ca 100644 --- a/pcsx2/Input/SDLInputSource.cpp +++ b/pcsx2/Input/SDLInputSource.cpp @@ -555,6 +555,11 @@ void SDLInputSource::SetHints() // Gets us pressure sensitive button support on Linux // Apparently doesn't work on Windows, so leave it off there SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS3, "1"); +#else + // Use the Sixaxis driver (or DsHidMini in SXS mode). + // We don't support DsHidMini's SDF mode as none of the + // PS3 hints allow accessing all the pressure sense axis. + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS3_SIXAXIS_DRIVER, "1"); #endif #ifdef __APPLE__ From 8ae84614d527b60ef6a9962ed3e767c9eed399c9 Mon Sep 17 00:00:00 2001 From: TheLastRar Date: Sun, 9 Mar 2025 20:17:29 +0000 Subject: [PATCH 142/162] SDLInputSource: Support auto mapping pressure sense buttons Also provide UI strings and icons --- pcsx2/Input/SDLInputSource.cpp | 142 ++++++++++++++++++++++++++++++--- pcsx2/Input/SDLInputSource.h | 2 + 2 files changed, 132 insertions(+), 12 deletions(-) diff --git a/pcsx2/Input/SDLInputSource.cpp b/pcsx2/Input/SDLInputSource.cpp index 8cb0a0b4ca..ee0879f6e6 100644 --- a/pcsx2/Input/SDLInputSource.cpp +++ b/pcsx2/Input/SDLInputSource.cpp @@ -60,6 +60,25 @@ static const char* const* s_sdl_trigger_names_list[] = { // Switch }; +static constexpr const char* s_sdl_ps3_sxs_pressure_names[] = { + nullptr, // JoyAxis0 + nullptr, // JoyAxis1 + nullptr, // JoyAxis2 + nullptr, // JoyAxis3 + nullptr, // JoyAxis4 + nullptr, // JoyAxis5 + "Cross (Pressure)", // JoyAxis6 + "Circle (Pressure)", // JoyAxis7 + "Square (Pressure)", // JoyAxis8 + "Triangle (Pressure)", // JoyAxis9 + "L1 (Pressure)", // JoyAxis10 + "R1 (Pressure)", // JoyAxis11 + "D-Pad Up (Pressure)", // JoyAxis12 + "D-Pad Down (Pressure)", // JoyAxis13 + "D-Pad Left (Pressure)", // JoyAxis14 + "D-Pad Right (Pressure)", // JoyAxis15 +}; + static constexpr const char* s_sdl_axis_icons[][2] = { {ICON_PF_LEFT_ANALOG_LEFT, ICON_PF_LEFT_ANALOG_RIGHT}, // SDL_GAMEPAD_AXIS_LEFTX {ICON_PF_LEFT_ANALOG_UP, ICON_PF_LEFT_ANALOG_DOWN}, // SDL_GAMEPAD_AXIS_LEFTY @@ -87,6 +106,25 @@ static const char* const* s_sdl_trigger_icons_list[] = { // Switch }; +static constexpr const char* s_sdl_ps3_pressure_icons[] = { + nullptr, // JoyAxis0 + nullptr, // JoyAxis1 + nullptr, // JoyAxis2 + nullptr, // JoyAxis3 + nullptr, // JoyAxis4 + nullptr, // JoyAxis5 + "P" ICON_PF_BUTTON_CROSS, // JoyAxis6 + "P" ICON_PF_BUTTON_CIRCLE, // JoyAxis7 + "P" ICON_PF_BUTTON_SQUARE, // JoyAxis8 + "P" ICON_PF_BUTTON_TRIANGLE, // JoyAxis9 + "P" ICON_PF_LEFT_SHOULDER_L1, // JoyAxis10 + "P" ICON_PF_RIGHT_SHOULDER_R1, // JoyAxis11 + "P" ICON_PF_DPAD_UP, // JoyAxis12 + "P" ICON_PF_DPAD_DOWN, // JoyAxis13 + "P" ICON_PF_DPAD_LEFT, // JoyAxis14 + "P" ICON_PF_DPAD_RIGHT, // JoyAxis15 +}; + static constexpr const GenericInputBinding s_sdl_generic_binding_axis_mapping[][2] = { {GenericInputBinding::LeftStickLeft, GenericInputBinding::LeftStickRight}, // SDL_GAMEPAD_AXIS_LEFTX {GenericInputBinding::LeftStickUp, GenericInputBinding::LeftStickDown}, // SDL_GAMEPAD_AXIS_LEFTY @@ -95,6 +133,24 @@ static constexpr const GenericInputBinding s_sdl_generic_binding_axis_mapping[][ {GenericInputBinding::Unknown, GenericInputBinding::L2}, // SDL_GAMEPAD_AXIS_LEFT_TRIGGER {GenericInputBinding::Unknown, GenericInputBinding::R2}, // SDL_GAMEPAD_AXIS_RIGHT_TRIGGER }; +static constexpr const GenericInputBinding s_sdl_ps3_binding_pressure_mapping[] = { + GenericInputBinding::Unknown, // JoyAxis0 + GenericInputBinding::Unknown, // JoyAxis1 + GenericInputBinding::Unknown, // JoyAxis2 + GenericInputBinding::Unknown, // JoyAxis3 + GenericInputBinding::Unknown, // JoyAxis4 + GenericInputBinding::Unknown, // JoyAxis5 + GenericInputBinding::Cross, // JoyAxis6 + GenericInputBinding::Circle, // JoyAxis7 + GenericInputBinding::Square, // JoyAxis8 + GenericInputBinding::Triangle, // JoyAxis9 + GenericInputBinding::L1, // JoyAxis10 + GenericInputBinding::R1, // JoyAxis11 + GenericInputBinding::DPadUp, // JoyAxis12 + GenericInputBinding::DPadDown, // JoyAxis13 + GenericInputBinding::DPadLeft, // JoyAxis14 + GenericInputBinding::DPadRight, // JoyAxis15 +}; static constexpr const char* s_sdl_button_setting_names[] = { "FaceSouth", // SDL_GAMEPAD_BUTTON_SOUTH @@ -265,7 +321,7 @@ static constexpr const char* s_sdl_button_ps3_icons[] = { ICON_PF_BUTTON_SQUARE, // SDL_GAMEPAD_BUTTON_WEST ICON_PF_BUTTON_TRIANGLE, // SDL_GAMEPAD_BUTTON_NORTH ICON_PF_SELECT_SHARE, // SDL_GAMEPAD_BUTTON_BACK - ICON_PF_XBOX, // SDL_GAMEPAD_BUTTON_GUIDE + ICON_PF_PLAYSTATION, // SDL_GAMEPAD_BUTTON_GUIDE ICON_PF_START, // SDL_GAMEPAD_BUTTON_START ICON_PF_LEFT_ANALOG_CLICK, // SDL_GAMEPAD_BUTTON_LEFT_STICK ICON_PF_RIGHT_ANALOG_CLICK, // SDL_GAMEPAD_BUTTON_RIGHT_STICK @@ -360,12 +416,17 @@ static constexpr const GenericInputBinding s_sdl_generic_binding_button_mapping[ GenericInputBinding::DPadDown, // SDL_GAMEPAD_BUTTON_DPAD_DOWN GenericInputBinding::DPadLeft, // SDL_GAMEPAD_BUTTON_DPAD_LEFT GenericInputBinding::DPadRight, // SDL_GAMEPAD_BUTTON_DPAD_RIGHT - GenericInputBinding::Unknown, // SDL_GAMEPAD_BUTTON_MISC1 - GenericInputBinding::Unknown, // SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1 - GenericInputBinding::Unknown, // SDL_GAMEPAD_BUTTON_LEFT_PADDLE1 - GenericInputBinding::Unknown, // SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2 - GenericInputBinding::Unknown, // SDL_GAMEPAD_BUTTON_LEFT_PADDLE2 - GenericInputBinding::Unknown, // SDL_GAMEPAD_BUTTON_TOUCHPAD +}; +static constexpr const GenericInputBinding s_sdl_ps3_binding_button_mapping[] = { + GenericInputBinding::Unknown, // SDL_GAMEPAD_BUTTON_SOUTH + GenericInputBinding::Unknown, // SDL_GAMEPAD_BUTTON_EAST + GenericInputBinding::Unknown, // SDL_GAMEPAD_BUTTON_WEST + GenericInputBinding::Unknown, // SDL_GAMEPAD_BUTTON_NORTH + GenericInputBinding::Select, // SDL_GAMEPAD_BUTTON_BACK + GenericInputBinding::System, // SDL_GAMEPAD_BUTTON_GUIDE + GenericInputBinding::Start, // SDL_GAMEPAD_BUTTON_START + GenericInputBinding::L3, // SDL_GAMEPAD_BUTTON_LEFT_STICK + GenericInputBinding::R3, // SDL_GAMEPAD_BUTTON_RIGHT_STICK }; static constexpr const char* s_sdl_hat_direction_names[] = { @@ -925,7 +986,21 @@ TinyString SDLInputSource::ConvertKeyToString(InputBindingKey key, bool display, ret.format("SDL-{} {}", static_cast(key.source_index), s_sdl_trigger_names[trigger_index]); } else - ret.format("SDL-{} {}Axis {}{}", static_cast(key.source_index), modifier, key.data - std::size(s_sdl_axis_setting_names) + 1, key.invert ? "~" : ""); + { + bool is_sixaxis = false; + if (it != m_controllers.end()) + is_sixaxis = IsControllerSixaxis(*it); + + const int joy_axis_Index = key.data - std::size(s_sdl_axis_setting_names); + + if (is_sixaxis && key.modifier == InputModifier::FullAxis && key.invert == false && + joy_axis_Index < std::size(s_sdl_ps3_sxs_pressure_names) && s_sdl_ps3_sxs_pressure_names[joy_axis_Index] != nullptr) + { + ret.format("SDL-{} {}", static_cast(key.source_index), s_sdl_ps3_sxs_pressure_names[joy_axis_Index]); + } + else + ret.format("SDL-{} {}Axis {}{}", static_cast(key.source_index), modifier, joy_axis_Index + 1, key.invert ? "~" : ""); + } } else { @@ -1035,6 +1110,13 @@ TinyString SDLInputSource::ConvertKeyToIcon(InputBindingKey key) ret.format("SDL-{} {}", static_cast(key.source_index), s_sdl_trigger_icons[trigger_index]); } } + else if (it != m_controllers.end() && IsControllerSixaxis(*it) && key.invert == false) + { + const int joy_axis_Index = key.data - std::size(s_sdl_axis_setting_names); + + if (joy_axis_Index < std::size(s_sdl_ps3_pressure_icons) && s_sdl_ps3_pressure_icons[joy_axis_Index] != nullptr) + ret.format("SDL-{} {}", static_cast(key.source_index), s_sdl_ps3_pressure_icons[joy_axis_Index]); + } } else if (key.source_subtype == InputSubclass::ControllerButton) { @@ -1513,11 +1595,34 @@ bool SDLInputSource::GetGenericBindingMapping(const std::string_view device, Inp if (positive != GenericInputBinding::Unknown) mapping->emplace_back(positive, fmt::format("SDL-{}/+{}", pid, s_sdl_axis_setting_names[i])); } - for (u32 i = 0; i < std::size(s_sdl_generic_binding_button_mapping); i++) + + if (IsControllerSixaxis(*it)) { - const GenericInputBinding binding = s_sdl_generic_binding_button_mapping[i]; - if (binding != GenericInputBinding::Unknown) - mapping->emplace_back(binding, fmt::format("SDL-{}/{}", pid, s_sdl_button_setting_names[i])); + // PS3 with pressure sensitive support + for (u32 i = 0; i < std::size(s_sdl_ps3_binding_pressure_mapping); i++) + { + const GenericInputBinding binding = s_sdl_ps3_binding_pressure_mapping[i]; + if (binding != GenericInputBinding::Unknown) + mapping->emplace_back(binding, fmt::format("SDL-{}/FullJoyAxis{}", pid, i)); + } + + // PS3 non pressure sensitive buttons + for (u32 i = 0; i < std::size(s_sdl_ps3_binding_button_mapping); i++) + { + const GenericInputBinding binding = s_sdl_ps3_binding_button_mapping[i]; + if (binding != GenericInputBinding::Unknown) + mapping->emplace_back(binding, fmt::format("SDL-{}/{}", pid, s_sdl_button_setting_names[i])); + } + } + else + { + // Standard buttons + for (u32 i = 0; i < std::size(s_sdl_generic_binding_button_mapping); i++) + { + const GenericInputBinding binding = s_sdl_generic_binding_button_mapping[i]; + if (binding != GenericInputBinding::Unknown) + mapping->emplace_back(binding, fmt::format("SDL-{}/{}", pid, s_sdl_button_setting_names[i])); + } } if (it->use_gamepad_rumble || it->haptic_left_right_effect) @@ -1629,3 +1734,16 @@ void SDLInputSource::SendRumbleUpdate(ControllerData* cd) SDL_StopHapticRumble(cd->haptic); } } + +bool SDLInputSource::IsControllerSixaxis(const ControllerData& cd) +{ + const SDL_GamepadType type = SDL_GetRealGamepadType(cd.gamepad); + + // We check the number of buttons to exclude DsHidMini's SDF mode (which has 17 buttons??) + // SDF's input layout differs from the sixaxis or linux drivers, we only support the latter layout. + // This differing layout also isn't mapped correctly in SDL, I think due to how L2/R2 are exposed. + // Also see SetHints regarding reading the pressure sense from DsHidMini's SDF mode. + return type == SDL_GAMEPAD_TYPE_PS3 && + SDL_GetNumJoystickAxes(cd.joystick) == 16 && + SDL_GetNumJoystickButtons(cd.joystick) == 11; +} diff --git a/pcsx2/Input/SDLInputSource.h b/pcsx2/Input/SDLInputSource.h index 120597899a..a2cb3b7efc 100644 --- a/pcsx2/Input/SDLInputSource.h +++ b/pcsx2/Input/SDLInputSource.h @@ -88,6 +88,8 @@ private: bool HandleJoystickHatEvent(const SDL_JoyHatEvent* ev); void SendRumbleUpdate(ControllerData* cd); + bool IsControllerSixaxis(const ControllerData& cd); + ControllerDataVector m_controllers; // ConvertKeyToString and ConvertKeyToIcon can inspect the From 559e4e75ebf5bf0de534e8f561b6e36b68d0f9c2 Mon Sep 17 00:00:00 2001 From: EXtremeExploit Date: Thu, 17 Apr 2025 23:07:40 -0300 Subject: [PATCH 143/162] GameDB: Add memcard filters for Mortal Kombat Armageddon Premium --- bin/resources/GameIndex.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bin/resources/GameIndex.yaml b/bin/resources/GameIndex.yaml index 43b9adc777..316928f7c0 100644 --- a/bin/resources/GameIndex.yaml +++ b/bin/resources/GameIndex.yaml @@ -69521,6 +69521,9 @@ SLUS-21410: name: "Mortal Kombat - Armageddon" region: "NTSC-U" compat: 5 + memcardFilters: # Saves from the non-Premium can be imported + - "SLUS-21410" + - "SLUS-21543" SLUS-21411: name: "M2 Rock" region: "NTSC-U" @@ -70163,6 +70166,9 @@ SLUS-21543: name: "Mortal Kombat - Armageddon [Premium Edition]" region: "NTSC-U" compat: 5 + memcardFilters: # Saves from the non-Premium can be imported + - "SLUS-21543" + - "SLUS-21410" SLUS-21544: name: "Fantastic 4 - Rise of Silver Surfer" region: "NTSC-U" From fcca07765bf5c0f4299554b47a561b57e75b3eaa Mon Sep 17 00:00:00 2001 From: Filoppi Date: Mon, 14 Apr 2025 23:44:06 +0300 Subject: [PATCH 144/162] GS: Clarify HW blends code a bit --- pcsx2/GS/Renderers/Common/GSDevice.cpp | 1 + pcsx2/GS/Renderers/Common/GSDevice.h | 34 ++++++++++++++++---------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/pcsx2/GS/Renderers/Common/GSDevice.cpp b/pcsx2/GS/Renderers/Common/GSDevice.cpp index 33b7a8e554..71195467e1 100644 --- a/pcsx2/GS/Renderers/Common/GSDevice.cpp +++ b/pcsx2/GS/Renderers/Common/GSDevice.cpp @@ -904,6 +904,7 @@ bool GSHWDrawConfig::BlendState::IsEffective(ColorMaskSelector colormask) const // clang-format off +// Maps PS2 blend modes to our best approximation of them with PC hardware const std::array GSDevice::m_blendMap = {{ { BLEND_NO_REC , OP_ADD , CONST_ONE , CONST_ZERO} , // 0000: (Cs - Cs)*As + Cs ==> Cs diff --git a/pcsx2/GS/Renderers/Common/GSDevice.h b/pcsx2/GS/Renderers/Common/GSDevice.h index f4cf8366b9..5055fbaa3a 100644 --- a/pcsx2/GS/Renderers/Common/GSDevice.h +++ b/pcsx2/GS/Renderers/Common/GSDevice.h @@ -257,11 +257,15 @@ enum HWBlendFlags BLEND_A_MAX = 0x8000, // Impossible blending uses coeff bigger than 1 }; -// Determines the HW blend function for DX11/OGL +// Determines the HW blend function for the video backend struct HWBlend { + typedef u8 BlendOp; /*GSDevice::BlendOp*/ + typedef u8 BlendFactor; /*GSDevice::BlendFactor*/ + u16 flags; - u8 op, src, dst; + BlendOp op; + BlendFactor src, dst; }; struct alignas(16) GSHWDrawConfig @@ -354,7 +358,7 @@ struct alignas(16) GSHWDrawConfig u32 blend_c : 2; u32 blend_d : 2; u32 fixed_one_a : 1; - u32 blend_hw : 3; + u32 blend_hw : 3; /*HWBlendType*/ u32 a_masked : 1; u32 hdr : 1; u32 rta_correction : 1; @@ -631,26 +635,30 @@ struct alignas(16) GSHWDrawConfig return true; } }; + // For hardware rendering backends struct BlendState { + typedef u8 BlendOp; /*GSDevice::BlendOp*/ + typedef u8 BlendFactor; /*GSDevice::BlendFactor*/ + union { struct { - u8 enable : 1; - u8 constant_enable : 1; - u8 op : 6; - u8 src_factor : 4; - u8 dst_factor : 4; - u8 src_factor_alpha : 4; - u8 dst_factor_alpha : 4; + bool enable : 1; + bool constant_enable : 1; + BlendOp op : 6; + BlendFactor src_factor : 4; + BlendFactor dst_factor : 4; + BlendFactor src_factor_alpha : 4; + BlendFactor dst_factor_alpha : 4; u8 constant; }; u32 key; }; constexpr BlendState(): key(0) {} - constexpr BlendState(bool enable_, u8 src_factor_, u8 dst_factor_, u8 op_, - u8 src_alpha_factor_, u8 dst_alpha_factor_, bool constant_enable_, u8 constant_) + constexpr BlendState(bool enable_, BlendFactor src_factor_, BlendFactor dst_factor_, BlendOp op_, + BlendFactor src_alpha_factor_, BlendFactor dst_alpha_factor_, bool constant_enable_, u8 constant_) : key(0) { enable = enable_; @@ -730,7 +738,7 @@ struct alignas(16) GSHWDrawConfig struct BlendMultiPass { BlendState blend; - u8 blend_hw; + u8 blend_hw; /*HWBlendType*/ u8 dither; bool enable; }; From 8be2b907b3774164dab9277b19ed9c6d4dbca702 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Tue, 15 Apr 2025 15:21:25 +0300 Subject: [PATCH 145/162] GS: Add actual HDR and HQ textures and rename the "HDR" textures to colclip (hw) given that's actually what they are (HDR was a very loose term for it) --- bin/resources/shaders/dx11/convert.fx | 4 +- bin/resources/shaders/dx11/tfx.fx | 12 +- bin/resources/shaders/opengl/convert.glsl | 8 +- bin/resources/shaders/opengl/tfx_fs.glsl | 10 +- bin/resources/shaders/vulkan/convert.glsl | 8 +- bin/resources/shaders/vulkan/tfx.glsl | 12 +- pcsx2/GS/Renderers/Common/GSDevice.cpp | 26 +++- pcsx2/GS/Renderers/Common/GSDevice.h | 22 +-- pcsx2/GS/Renderers/Common/GSTexture.cpp | 10 +- pcsx2/GS/Renderers/Common/GSTexture.h | 11 +- pcsx2/GS/Renderers/DX11/GSDevice11.cpp | 58 +++---- pcsx2/GS/Renderers/DX11/GSTexture11.cpp | 4 +- pcsx2/GS/Renderers/DX12/GSDevice12.cpp | 123 ++++++++------- pcsx2/GS/Renderers/DX12/GSDevice12.h | 4 +- pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 105 +++++++------ pcsx2/GS/Renderers/HW/GSRendererHW.h | 2 +- pcsx2/GS/Renderers/Metal/GSDeviceMTL.h | 8 +- pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm | 74 ++++----- pcsx2/GS/Renderers/Metal/GSMTLSharedHeader.h | 2 +- pcsx2/GS/Renderers/Metal/convert.metal | 4 +- pcsx2/GS/Renderers/Metal/tfx.metal | 12 +- pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp | 62 ++++---- pcsx2/GS/Renderers/OpenGL/GSTextureOGL.cpp | 4 +- pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp | 156 ++++++++++--------- pcsx2/GS/Renderers/Vulkan/GSDeviceVK.h | 10 +- pcsx2/ShaderCacheVersion.h | 2 +- 26 files changed, 390 insertions(+), 363 deletions(-) diff --git a/bin/resources/shaders/dx11/convert.fx b/bin/resources/shaders/dx11/convert.fx index ddcd70e5ae..9771e62d4c 100644 --- a/bin/resources/shaders/dx11/convert.fx +++ b/bin/resources/shaders/dx11/convert.fx @@ -170,7 +170,7 @@ PS_OUTPUT ps_rta_decorrection(PS_INPUT input) return output; } -PS_OUTPUT ps_hdr_init(PS_INPUT input) +PS_OUTPUT ps_colclip_init(PS_INPUT input) { PS_OUTPUT output; float4 value = sample_c(input.t); @@ -178,7 +178,7 @@ PS_OUTPUT ps_hdr_init(PS_INPUT input) return output; } -PS_OUTPUT ps_hdr_resolve(PS_INPUT input) +PS_OUTPUT ps_colclip_resolve(PS_INPUT input) { PS_OUTPUT output; float4 value = sample_c(input.t); diff --git a/bin/resources/shaders/dx11/tfx.fx b/bin/resources/shaders/dx11/tfx.fx index a0de0b4246..2bd548ec60 100644 --- a/bin/resources/shaders/dx11/tfx.fx +++ b/bin/resources/shaders/dx11/tfx.fx @@ -55,7 +55,7 @@ #define PS_CHANNEL_FETCH 0 #define PS_TALES_OF_ABYSS_HLE 0 #define PS_URBAN_CHAOS_HLE 0 -#define PS_HDR 0 +#define PS_COLCLIP_HW 0 #define PS_RTA_CORRECTION 0 #define PS_RTA_SRC_CORRECTION 0 #define PS_COLCLIP 0 @@ -799,7 +799,7 @@ void ps_fbmask(inout float4 C, float2 pos_xy) { if (PS_FBMASK) { - float multi = PS_HDR ? 65535.0f : 255.0f; + float multi = PS_COLCLIP_HW ? 65535.0f : 255.0f; float4 RT = trunc(RtTexture.Load(int3(pos_xy, 0)) * multi + 0.1f); C = (float4)(((uint4)C & ~FbMask) | ((uint4)RT & FbMask)); } @@ -843,13 +843,13 @@ void ps_color_clamp_wrap(inout float3 C) C += 7.0f; // Need to round up, not down since the shader will invert // Standard Clamp - if (PS_COLCLIP == 0 && PS_HDR == 0) + if (PS_COLCLIP == 0 && PS_COLCLIP_HW == 0) C = clamp(C, (float3)0.0f, (float3)255.0f); // In 16 bits format, only 5 bits of color are used. It impacts shadows computation of Castlevania if (PS_DST_FMT == FMT_16 && PS_DITHER != 3 && (PS_BLEND_MIX == 0 || PS_DITHER)) C = (float3)((int3)C & (int3)0xF8); - else if (PS_COLCLIP == 1 || PS_HDR == 1) + else if (PS_COLCLIP == 1 || PS_COLCLIP_HW == 1) C = (float3)((int3)C & (int3)0xFF); } else if (PS_DST_FMT == FMT_16 && PS_DITHER != 3 && PS_BLEND_MIX == 0 && PS_BLEND_HW == 0) @@ -898,7 +898,7 @@ void ps_blend(inout float4 Color, inout float4 As_rgba, float2 pos_xy) } float Ad = PS_RTA_CORRECTION ? trunc(RT.a * 128.0f + 0.1f) / 128.0f : trunc(RT.a * 255.0f + 0.1f) / 128.0f; - float color_multi = PS_HDR ? 65535.0f : 255.0f; + float color_multi = PS_COLCLIP_HW ? 65535.0f : 255.0f; float3 Cd = trunc(RT.rgb * color_multi + 0.1f); float3 Cs = Color.rgb; @@ -1157,7 +1157,7 @@ PS_OUTPUT ps_main(PS_INPUT input) #if !PS_NO_COLOR output.c0.a = PS_RTA_CORRECTION ? C.a / 128.0f : C.a / 255.0f; - output.c0.rgb = PS_HDR ? float3(C.rgb / 65535.0f) : C.rgb / 255.0f; + output.c0.rgb = PS_COLCLIP_HW ? float3(C.rgb / 65535.0f) : C.rgb / 255.0f; #if !PS_NO_COLOR1 output.c1 = alpha_blend; #endif diff --git a/bin/resources/shaders/opengl/convert.glsl b/bin/resources/shaders/opengl/convert.glsl index b4c0d52721..27320a82ce 100644 --- a/bin/resources/shaders/opengl/convert.glsl +++ b/bin/resources/shaders/opengl/convert.glsl @@ -348,16 +348,16 @@ void ps_rta_decorrection() } #endif -#ifdef ps_hdr_init -void ps_hdr_init() +#ifdef ps_colclip_init +void ps_colclip_init() { vec4 value = sample_c(); SV_Target0 = vec4(round(value.rgb * 255.0f) / 65535.0f, value.a); } #endif -#ifdef ps_hdr_resolve -void ps_hdr_resolve() +#ifdef ps_colclip_resolve +void ps_colclip_resolve() { vec4 value = sample_c(); SV_Target0 = vec4(vec3(uvec3(value.rgb * 65535.0f) & 255u) / 255.0f, value.a); diff --git a/bin/resources/shaders/opengl/tfx_fs.glsl b/bin/resources/shaders/opengl/tfx_fs.glsl index 8dc7f852ff..f290e8d5e8 100644 --- a/bin/resources/shaders/opengl/tfx_fs.glsl +++ b/bin/resources/shaders/opengl/tfx_fs.glsl @@ -707,7 +707,7 @@ void ps_fbmask(inout vec4 C) { // FIXME do I need special case for 16 bits #if PS_FBMASK - #if PS_HDR == 1 + #if PS_COLCLIP_HW == 1 vec4 RT = trunc(sample_from_rt() * 65535.0f); #else vec4 RT = trunc(sample_from_rt() * 255.0f + 0.1f); @@ -757,7 +757,7 @@ void ps_color_clamp_wrap(inout vec3 C) #endif // Correct the Color value based on the output format -#if PS_COLCLIP == 0 && PS_HDR == 0 +#if PS_COLCLIP == 0 && PS_COLCLIP_HW == 0 // Standard Clamp C = clamp(C, vec3(0.0f), vec3(255.0f)); #endif @@ -771,7 +771,7 @@ void ps_color_clamp_wrap(inout vec3 C) #if PS_DST_FMT == FMT_16 && PS_DITHER < 3 && (PS_BLEND_MIX == 0 || PS_DITHER) // In 16 bits format, only 5 bits of colors are used. It impacts shadows computation of Castlevania C = vec3(ivec3(C) & ivec3(0xF8)); -#elif PS_COLCLIP == 1 || PS_HDR == 1 +#elif PS_COLCLIP == 1 || PS_COLCLIP_HW == 1 C = vec3(ivec3(C) & ivec3(0xFF)); #endif @@ -828,7 +828,7 @@ float As = As_rgba.a; #endif // Let the compiler do its jobs ! - #if PS_HDR == 1 + #if PS_COLCLIP_HW == 1 vec3 Cd = trunc(RT.rgb * 65535.0f); #else vec3 Cd = trunc(RT.rgb * 255.0f + 0.1f); @@ -1125,7 +1125,7 @@ void ps_main() #else SV_Target0.a = C.a / 255.0f; #endif - #if PS_HDR == 1 + #if PS_COLCLIP_HW == 1 SV_Target0.rgb = vec3(C.rgb / 65535.0f); #else SV_Target0.rgb = C.rgb / 255.0f; diff --git a/bin/resources/shaders/vulkan/convert.glsl b/bin/resources/shaders/vulkan/convert.glsl index 7b370affc5..9a3b551dd0 100644 --- a/bin/resources/shaders/vulkan/convert.glsl +++ b/bin/resources/shaders/vulkan/convert.glsl @@ -148,16 +148,16 @@ void ps_rta_decorrection() } #endif -#ifdef ps_hdr_init -void ps_hdr_init() +#ifdef ps_colclip_init +void ps_colclip_init() { vec4 value = sample_c(v_tex); o_col0 = vec4(roundEven(value.rgb * 255.0f) / 65535.0f, value.a); } #endif -#ifdef ps_hdr_resolve -void ps_hdr_resolve() +#ifdef ps_colclip_resolve +void ps_colclip_resolve() { vec4 value = sample_c(v_tex); o_col0 = vec4(vec3(uvec3(value.rgb * 65535.5f) & 255u) / 255.0f, value.a); diff --git a/bin/resources/shaders/vulkan/tfx.glsl b/bin/resources/shaders/vulkan/tfx.glsl index 3061226288..ba21b4c0b5 100644 --- a/bin/resources/shaders/vulkan/tfx.glsl +++ b/bin/resources/shaders/vulkan/tfx.glsl @@ -281,7 +281,7 @@ void main() #define PS_CHANNEL_FETCH 0 #define PS_TALES_OF_ABYSS_HLE 0 #define PS_URBAN_CHAOS_HLE 0 -#define PS_HDR 0 +#define PS_COLCLIP_HW 0 #define PS_COLCLIP 0 #define PS_BLEND_A 0 #define PS_BLEND_B 0 @@ -974,7 +974,7 @@ void ps_fbmask(inout vec4 C) { #if PS_FBMASK - #if PS_HDR == 1 + #if PS_COLCLIP_HW == 1 vec4 RT = trunc(sample_from_rt() * 65535.0f); #else vec4 RT = trunc(sample_from_rt() * 255.0f + 0.1f); @@ -1027,7 +1027,7 @@ void ps_color_clamp_wrap(inout vec3 C) #endif // Correct the Color value based on the output format -#if PS_COLCLIP == 0 && PS_HDR == 0 +#if PS_COLCLIP == 0 && PS_COLCLIP_HW == 0 // Standard Clamp C = clamp(C, vec3(0.0f), vec3(255.0f)); #endif @@ -1041,7 +1041,7 @@ void ps_color_clamp_wrap(inout vec3 C) #if PS_DST_FMT == FMT_16 && PS_DITHER != 3 && (PS_BLEND_MIX == 0 || PS_DITHER > 0) // In 16 bits format, only 5 bits of colors are used. It impacts shadows computation of Castlevania C = vec3(ivec3(C) & ivec3(0xF8)); -#elif PS_COLCLIP == 1 || PS_HDR == 1 +#elif PS_COLCLIP == 1 || PS_COLCLIP_HW == 1 C = vec3(ivec3(C) & ivec3(0xFF)); #endif @@ -1098,7 +1098,7 @@ void ps_blend(inout vec4 Color, inout vec4 As_rgba) #endif // Let the compiler do its jobs ! - #if PS_HDR == 1 + #if PS_COLCLIP_HW == 1 vec3 Cd = trunc(RT.rgb * 65535.0f); #else vec3 Cd = trunc(RT.rgb * 255.0f + 0.1f); @@ -1390,7 +1390,7 @@ void main() #else o_col0.a = C.a / 255.0f; #endif - #if PS_HDR == 1 + #if PS_COLCLIP_HW == 1 o_col0.rgb = vec3(C.rgb / 65535.0f); #else o_col0.rgb = C.rgb / 255.0f; diff --git a/pcsx2/GS/Renderers/Common/GSDevice.cpp b/pcsx2/GS/Renderers/Common/GSDevice.cpp index 71195467e1..f717e9ca10 100644 --- a/pcsx2/GS/Renderers/Common/GSDevice.cpp +++ b/pcsx2/GS/Renderers/Common/GSDevice.cpp @@ -46,8 +46,8 @@ const char* shaderName(ShaderConvert value) case ShaderConvert::DATM_0: return "ps_datm0"; case ShaderConvert::DATM_1_RTA_CORRECTION: return "ps_datm1_rta_correction"; case ShaderConvert::DATM_0_RTA_CORRECTION: return "ps_datm0_rta_correction"; - case ShaderConvert::HDR_INIT: return "ps_hdr_init"; - case ShaderConvert::HDR_RESOLVE: return "ps_hdr_resolve"; + case ShaderConvert::COLCLIP_INIT: return "ps_colclip_init"; + case ShaderConvert::COLCLIP_RESOLVE: return "ps_colclip_resolve"; case ShaderConvert::RTA_CORRECTION: return "ps_rta_correction"; case ShaderConvert::RTA_DECORRECTION: return "ps_rta_decorrection"; case ShaderConvert::TRANSPARENCY_FILTER: return "ps_filter_transparency"; @@ -103,7 +103,9 @@ const char* shaderName(PresentShader value) enum class TextureLabel { ColorRT, - HDRRT, + ColorHQRT, + ColorHDRRT, + ColorClipRT, U16RT, U32RT, DepthStencil, @@ -127,8 +129,12 @@ static TextureLabel GetTextureLabel(GSTexture::Type type, GSTexture::Format form { case GSTexture::Format::Color: return TextureLabel::ColorRT; - case GSTexture::Format::HDRColor: - return TextureLabel::HDRRT; + case GSTexture::Format::ColorHQ: + return TextureLabel::ColorHQRT; + case GSTexture::Format::ColorHDR: + return TextureLabel::ColorHDRRT; + case GSTexture::Format::ColorClip: + return TextureLabel::ColorClipRT; case GSTexture::Format::UInt16: return TextureLabel::U16RT; case GSTexture::Format::UInt32: @@ -149,6 +155,7 @@ static TextureLabel GetTextureLabel(GSTexture::Type type, GSTexture::Format form case GSTexture::Format::BC2: case GSTexture::Format::BC3: case GSTexture::Format::BC7: + case GSTexture::Format::ColorHDR: return TextureLabel::ReplacementTexture; default: return TextureLabel::Other; @@ -163,14 +170,19 @@ static TextureLabel GetTextureLabel(GSTexture::Type type, GSTexture::Format form } } +// Debug names static const char* TextureLabelString(TextureLabel label) { switch (label) { case TextureLabel::ColorRT: return "Color RT"; - case TextureLabel::HDRRT: - return "HDR RT"; + case TextureLabel::ColorHQRT: + return "Color HQ RT"; + case TextureLabel::ColorHDRRT: + return "Color HDR RT"; + case TextureLabel::ColorClipRT: + return "Color Clip RT"; case TextureLabel::U16RT: return "U16 RT"; case TextureLabel::U32RT: diff --git a/pcsx2/GS/Renderers/Common/GSDevice.h b/pcsx2/GS/Renderers/Common/GSDevice.h index 5055fbaa3a..b61dca49be 100644 --- a/pcsx2/GS/Renderers/Common/GSDevice.h +++ b/pcsx2/GS/Renderers/Common/GSDevice.h @@ -22,8 +22,8 @@ enum class ShaderConvert DATM_0, DATM_1_RTA_CORRECTION, DATM_0_RTA_CORRECTION, - HDR_INIT, - HDR_RESOLVE, + COLCLIP_INIT, + COLCLIP_RESOLVE, RTA_CORRECTION, RTA_DECORRECTION, TRANSPARENCY_FILTER, @@ -360,10 +360,10 @@ struct alignas(16) GSHWDrawConfig u32 fixed_one_a : 1; u32 blend_hw : 3; /*HWBlendType*/ u32 a_masked : 1; - u32 hdr : 1; + u32 colclip_hw : 1; // colclip (COLCLAMP off) emulation through HQ textures u32 rta_correction : 1; u32 rta_source_correction : 1; - u32 colclip : 1; + u32 colclip : 1; // COLCLAMP off (color blend outputs wrap around 0-255) u32 blend_mix : 2; u32 round_inv : 1; // Blending will invert the value, so rounding needs to go the other way u32 pabe : 1; @@ -683,7 +683,7 @@ struct alignas(16) GSHWDrawConfig Full, ///< Full emulation (using barriers / ROV) }; - enum class HDRMode : u8 + enum class ColClipMode : u8 { NoModify = 0, ConvertOnly = 1, @@ -750,9 +750,9 @@ struct alignas(16) GSHWDrawConfig PSConstantBuffer cb_ps; // These are here as they need to be preserved between draws, and the state clear only does up to the constant buffers. - HDRMode hdr_mode; - GIFRegFRAME hdr_frame; - GSVector4i hdr_update_area; ///< Area in the framebuffer which HDR will modify; + ColClipMode colclip_mode; + GIFRegFRAME colclip_frame; + GSVector4i colclip_update_area; ///< Area in the framebuffer which colclip will modify; }; class GSDevice : public GSAlignedClass<32> @@ -873,7 +873,7 @@ protected: GSTexture* m_target_tmp = nullptr; GSTexture* m_current = nullptr; GSTexture* m_cas = nullptr; - GSTexture* m_hdr_rt = nullptr; ///< Temp HDR texture + GSTexture* m_colclip_rt = nullptr; ///< Temp hw colclip texture bool AcquireWindow(bool recreate_window); @@ -898,9 +898,9 @@ public: /// Returns a string containing current adapter in use. const std::string& GetName() const { return m_name; } - GSTexture* GetHDRTexture() const { return m_hdr_rt; } + GSTexture* GetColorClipTexture() const { return m_colclip_rt; } - void SetHDRTexture(GSTexture* tex) { m_hdr_rt = tex; } + void SetColorClipTexture(GSTexture* tex) { m_colclip_rt = tex; } /// Returns a string representing the specified API. static const char* RenderAPIToString(RenderAPI api); diff --git a/pcsx2/GS/Renderers/Common/GSTexture.cpp b/pcsx2/GS/Renderers/Common/GSTexture.cpp index e0212a7110..4de4714759 100644 --- a/pcsx2/GS/Renderers/Common/GSTexture.cpp +++ b/pcsx2/GS/Renderers/Common/GSTexture.cpp @@ -66,7 +66,9 @@ const char* GSTexture::GetFormatName(Format format) static constexpr const char* format_names[] = { "Invalid", "Color", - "HDRColor", + "ColorHQ", + "ColorHDR", + "ColorClip", "DepthStencil", "UNorm8", "UInt16", @@ -90,7 +92,9 @@ u32 GSTexture::GetCompressedBytesPerBlock(Format format) static constexpr u32 bytes_per_block[] = { 1, // Invalid 4, // Color/RGBA8 - 8, // HDRColor/RGBA16 + 4, // ColorHQ/RGB10A2 + 8, // ColorHDR/RGBA16F + 8, // ColorClip/RGBA16 4, // DepthStencil 1, // UNorm8/R8 2, // UInt16/R16UI @@ -99,7 +103,7 @@ u32 GSTexture::GetCompressedBytesPerBlock(Format format) 8, // BC1 - 16 pixels in 64 bits 16, // BC2 - 16 pixels in 128 bits 16, // BC3 - 16 pixels in 128 bits - 16, // BC4 - 16 pixels in 128 bits + 16, // BC7 - 16 pixels in 128 bits }; return bytes_per_block[static_cast(format)]; diff --git a/pcsx2/GS/Renderers/Common/GSTexture.h b/pcsx2/GS/Renderers/Common/GSTexture.h index fab5ece270..d9123dc599 100644 --- a/pcsx2/GS/Renderers/Common/GSTexture.h +++ b/pcsx2/GS/Renderers/Common/GSTexture.h @@ -22,15 +22,17 @@ public: Invalid = 0, RenderTarget = 1, DepthStencil, - Texture, - RWTexture, + Texture, // Generic texture (usually is color textures loaded by the game) + RWTexture, // UAV }; enum class Format : u8 { Invalid = 0, ///< Used for initialization - Color, ///< Standard (RGBA8) color texture - HDRColor, ///< Color texture with more bits for colclip emulation (RGBA16Unorm) + Color, ///< Standard (RGBA8) color texture (used to store most of PS2's textures) + ColorHQ, ///< High quality (RGB10A2) color texture (no proper alpha) + ColorHDR, ///< High dynamic range (RGBA16F) color texture + ColorClip, ///< Color texture with more bits for colclip (wrap) emulation, given that blending requires 9bpc (RGBA16Unorm) DepthStencil, ///< Depth stencil texture UNorm8, ///< A8UNorm texture for paletted textures and the OSD font UInt16, ///< UInt16 texture for reading back 16-bit depth @@ -40,6 +42,7 @@ public: BC2, ///< BC2, aka DXT2/3 compressed texture for replacements BC3, ///< BC3, aka DXT4/5 compressed texture for replacements BC7, ///< BC7, aka BPTC compressed texture for replacements + Last = BC7, }; enum class State : u8 diff --git a/pcsx2/GS/Renderers/DX11/GSDevice11.cpp b/pcsx2/GS/Renderers/DX11/GSDevice11.cpp index 3e6f174b25..09f1097b77 100644 --- a/pcsx2/GS/Renderers/DX11/GSDevice11.cpp +++ b/pcsx2/GS/Renderers/DX11/GSDevice11.cpp @@ -1748,7 +1748,7 @@ void GSDevice11::SetupPS(const PSSelector& sel, const GSHWDrawConfig::PSConstant sm.AddMacro("PS_DST_FMT", sel.dst_fmt); sm.AddMacro("PS_DEPTH_FMT", sel.depth_fmt); sm.AddMacro("PS_PAL_FMT", sel.pal_fmt); - sm.AddMacro("PS_HDR", sel.hdr); + sm.AddMacro("PS_COLCLIP_HW", sel.colclip_hw); sm.AddMacro("PS_RTA_CORRECTION", sel.rta_correction); sm.AddMacro("PS_RTA_SRC_CORRECTION", sel.rta_source_correction); sm.AddMacro("PS_COLCLIP", sel.colclip); @@ -2521,43 +2521,43 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config) GSVector2i rtsize = (config.rt ? config.rt : config.ds)->GetSize(); - GSTexture* hdr_rt = g_gs_device->GetHDRTexture(); + GSTexture* colclip_rt = g_gs_device->GetColorClipTexture(); - if (hdr_rt) + if (colclip_rt) { - if (config.hdr_mode == GSHWDrawConfig::HDRMode::EarlyResolve) + if (config.colclip_mode == GSHWDrawConfig::ColClipMode::EarlyResolve) { const GSVector2i size = config.rt->GetSize(); - const GSVector4 dRect(config.hdr_update_area); + const GSVector4 dRect(config.colclip_update_area); const GSVector4 sRect = dRect / GSVector4(size.x, size.y).xyxy(); - StretchRect(hdr_rt, sRect, config.rt, dRect, ShaderConvert::HDR_RESOLVE, false); + StretchRect(colclip_rt, sRect, config.rt, dRect, ShaderConvert::COLCLIP_RESOLVE, false); g_perfmon.Put(GSPerfMon::TextureCopies, 1); - Recycle(hdr_rt); + Recycle(colclip_rt); - g_gs_device->SetHDRTexture(nullptr); + g_gs_device->SetColorClipTexture(nullptr); - hdr_rt = nullptr; + colclip_rt = nullptr; } else - config.ps.hdr = 1; + config.ps.colclip_hw = 1; } - if (config.ps.hdr) + if (config.ps.colclip_hw) { - if (!hdr_rt) + if (!colclip_rt) { - config.hdr_update_area = config.drawarea; + config.colclip_update_area = config.drawarea; - const GSVector4 dRect = GSVector4((config.hdr_mode == GSHWDrawConfig::HDRMode::ConvertOnly) ? GSVector4i::loadh(rtsize) : config.drawarea); + const GSVector4 dRect = GSVector4((config.colclip_mode == GSHWDrawConfig::ColClipMode::ConvertOnly) ? GSVector4i::loadh(rtsize) : config.drawarea); const GSVector4 sRect = dRect / GSVector4(rtsize.x, rtsize.y).xyxy(); - hdr_rt = CreateRenderTarget(rtsize.x, rtsize.y, GSTexture::Format::HDRColor); - if (!hdr_rt) + colclip_rt = CreateRenderTarget(rtsize.x, rtsize.y, GSTexture::Format::ColorClip); + if (!colclip_rt) return; - g_gs_device->SetHDRTexture(hdr_rt); + g_gs_device->SetColorClipTexture(colclip_rt); // Warning: StretchRect must be called before BeginScene otherwise // vertices will be overwritten. Trust me you don't want to do that. - StretchRect(config.rt, sRect, hdr_rt, dRect, ShaderConvert::HDR_INIT, false); + StretchRect(config.rt, sRect, colclip_rt, dRect, ShaderConvert::COLCLIP_INIT, false); g_perfmon.Put(GSPerfMon::TextureCopies, 1); } } @@ -2569,7 +2569,7 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config) if (!primid_tex) return; - StretchRect(hdr_rt ? hdr_rt : config.rt, GSVector4(config.drawarea) / GSVector4(rtsize).xyxy(), + StretchRect(colclip_rt ? colclip_rt : config.rt, GSVector4(config.drawarea) / GSVector4(rtsize).xyxy(), primid_tex, GSVector4(config.drawarea), m_date.primid_init_ps[static_cast(config.datm)].get(), nullptr, false); } else if (config.destination_alpha != GSHWDrawConfig::DestinationAlphaMode::Off) @@ -2585,7 +2585,7 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config) {GSVector4(dst.z, -dst.w, 0.5f, 1.0f), GSVector2(src.z, src.w)}, }; - SetupDATE(hdr_rt ? hdr_rt : config.rt, config.ds, vertices, config.datm); + SetupDATE(colclip_rt ? colclip_rt : config.rt, config.ds, vertices, config.datm); } if (config.vs.expand != GSHWDrawConfig::VSExpand::None) @@ -2649,7 +2649,7 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config) // Do not always bind the rt when it's not needed, // only bind it when effects use it such as fbmask emulation currently // because we copy the frame buffer and it is quite slow. - CloneTexture(hdr_rt ? hdr_rt : config.rt, &rt_copy, config.drawarea); + CloneTexture(colclip_rt ? colclip_rt : config.rt, &rt_copy, config.drawarea); if (rt_copy) { if (config.require_one_barrier) @@ -2679,7 +2679,7 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config) } SetupOM(config.depth, OMBlendSelector(config.colormask, config.blend), config.blend.constant); - OMSetRenderTargets(hdr_rt ? hdr_rt : config.rt, config.ds, &config.scissor); + OMSetRenderTargets(colclip_rt ? colclip_rt : config.rt, config.ds, &config.scissor); DrawIndexedPrimitive(); if (config.blend_multi_pass.enable) @@ -2714,20 +2714,20 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config) if (primid_tex) Recycle(primid_tex); - if (hdr_rt) + if (colclip_rt) { - config.hdr_update_area = config.hdr_update_area.runion(config.drawarea); + config.colclip_update_area = config.colclip_update_area.runion(config.drawarea); - if (config.hdr_mode == GSHWDrawConfig::HDRMode::ResolveOnly || config.hdr_mode == GSHWDrawConfig::HDRMode::ConvertAndResolve) + if (config.colclip_mode == GSHWDrawConfig::ColClipMode::ResolveOnly || config.colclip_mode == GSHWDrawConfig::ColClipMode::ConvertAndResolve) { const GSVector2i size = config.rt->GetSize(); - const GSVector4 dRect(config.hdr_update_area); + const GSVector4 dRect(config.colclip_update_area); const GSVector4 sRect = dRect / GSVector4(size.x, size.y).xyxy(); - StretchRect(hdr_rt, sRect, config.rt, dRect, ShaderConvert::HDR_RESOLVE, false); + StretchRect(colclip_rt, sRect, config.rt, dRect, ShaderConvert::COLCLIP_RESOLVE, false); g_perfmon.Put(GSPerfMon::TextureCopies, 1); - Recycle(hdr_rt); + Recycle(colclip_rt); - g_gs_device->SetHDRTexture(nullptr); + g_gs_device->SetColorClipTexture(nullptr); } } } diff --git a/pcsx2/GS/Renderers/DX11/GSTexture11.cpp b/pcsx2/GS/Renderers/DX11/GSTexture11.cpp index b7ecc85b25..fa9e9b5953 100644 --- a/pcsx2/GS/Renderers/DX11/GSTexture11.cpp +++ b/pcsx2/GS/Renderers/DX11/GSTexture11.cpp @@ -29,7 +29,9 @@ DXGI_FORMAT GSTexture11::GetDXGIFormat(Format format) switch (format) { case GSTexture::Format::Color: return DXGI_FORMAT_R8G8B8A8_UNORM; - case GSTexture::Format::HDRColor: return DXGI_FORMAT_R16G16B16A16_UNORM; + case GSTexture::Format::ColorHQ: return DXGI_FORMAT_R10G10B10A2_UNORM; + case GSTexture::Format::ColorHDR: return DXGI_FORMAT_R16G16B16A16_FLOAT; + case GSTexture::Format::ColorClip: return DXGI_FORMAT_R16G16B16A16_UNORM; case GSTexture::Format::DepthStencil: return DXGI_FORMAT_R32G8X24_TYPELESS; case GSTexture::Format::UNorm8: return DXGI_FORMAT_A8_UNORM; case GSTexture::Format::UInt16: return DXGI_FORMAT_R16_UINT; diff --git a/pcsx2/GS/Renderers/DX12/GSDevice12.cpp b/pcsx2/GS/Renderers/DX12/GSDevice12.cpp index 71996a5413..c6163227d8 100644 --- a/pcsx2/GS/Renderers/DX12/GSDevice12.cpp +++ b/pcsx2/GS/Renderers/DX12/GSDevice12.cpp @@ -1265,13 +1265,17 @@ void GSDevice12::DrawIndexedPrimitive(int offset, int count) void GSDevice12::LookupNativeFormat(GSTexture::Format format, DXGI_FORMAT* d3d_format, DXGI_FORMAT* srv_format, DXGI_FORMAT* rtv_format, DXGI_FORMAT* dsv_format) const { - static constexpr std::array, static_cast(GSTexture::Format::BC7) + 1> + static constexpr std::array, static_cast(GSTexture::Format::Last) + 1> s_format_mapping = {{ {DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN}, // Invalid {DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_UNKNOWN}, // Color + {DXGI_FORMAT_R10G10B10A2_UNORM, DXGI_FORMAT_R10G10B10A2_UNORM, DXGI_FORMAT_R10G10B10A2_UNORM, + DXGI_FORMAT_UNKNOWN}, // ColorHQ + {DXGI_FORMAT_R16G16B16A16_FLOAT, DXGI_FORMAT_R16G16B16A16_FLOAT, DXGI_FORMAT_R16G16B16A16_FLOAT, + DXGI_FORMAT_UNKNOWN}, // ColorHDR {DXGI_FORMAT_R16G16B16A16_UNORM, DXGI_FORMAT_R16G16B16A16_UNORM, DXGI_FORMAT_R16G16B16A16_UNORM, - DXGI_FORMAT_UNKNOWN}, // HDRColor + DXGI_FORMAT_UNKNOWN}, // ColorClip {DXGI_FORMAT_D32_FLOAT_S8X24_UINT, DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_D32_FLOAT_S8X24_UINT}, // DepthStencil {DXGI_FORMAT_A8_UNORM, DXGI_FORMAT_A8_UNORM, DXGI_FORMAT_A8_UNORM, DXGI_FORMAT_UNKNOWN}, // UNorm8 @@ -2540,10 +2544,10 @@ bool GSDevice12::CompileConvertPipelines() j & 1u, (j >> 1) & 1u, (j >> 2) & 1u, (j >> 3) & 1u)); } } - else if (i == ShaderConvert::HDR_INIT || i == ShaderConvert::HDR_RESOLVE) + else if (i == ShaderConvert::COLCLIP_INIT || i == ShaderConvert::COLCLIP_RESOLVE) { - const bool is_setup = i == ShaderConvert::HDR_INIT; - std::array, 2>& arr = is_setup ? m_hdr_setup_pipelines : m_hdr_finish_pipelines; + const bool is_setup = i == ShaderConvert::COLCLIP_INIT; + std::array, 2>& arr = is_setup ? m_colclip_setup_pipelines : m_colclip_finish_pipelines; for (u32 ds = 0; ds < 2; ds++) { pxAssert(!arr[ds]); @@ -2554,7 +2558,7 @@ bool GSDevice12::CompileConvertPipelines() if (!arr[ds]) return false; - D3D12::SetObjectName(arr[ds].get(), TinyString::from_format("HDR {}/copy pipeline (ds={})", is_setup ? "setup" : "finish", ds)); + D3D12::SetObjectName(arr[ds].get(), TinyString::from_format("ColorClip {}/copy pipeline (ds={})", is_setup ? "setup" : "finish", ds)); } } } @@ -2780,8 +2784,8 @@ void GSDevice12::DestroyResources() m_color_copy = {}; m_present = {}; m_convert = {}; - m_hdr_setup_pipelines = {}; - m_hdr_finish_pipelines = {}; + m_colclip_setup_pipelines = {}; + m_colclip_finish_pipelines = {}; m_date_image_setup_pipelines = {}; m_fxaa_pipeline.reset(); m_shadeboost_pipeline.reset(); @@ -2897,7 +2901,7 @@ const ID3DBlob* GSDevice12::GetTFXPixelShader(const GSHWDrawConfig::PSSelector& sm.AddMacro("PS_DST_FMT", sel.dst_fmt); sm.AddMacro("PS_DEPTH_FMT", sel.depth_fmt); sm.AddMacro("PS_PAL_FMT", sel.pal_fmt); - sm.AddMacro("PS_HDR", sel.hdr); + sm.AddMacro("PS_COLCLIP_HW", sel.colclip_hw); sm.AddMacro("PS_RTA_CORRECTION", sel.rta_correction); sm.AddMacro("PS_RTA_SRC_CORRECTION", sel.rta_source_correction); sm.AddMacro("PS_COLCLIP", sel.colclip); @@ -2955,7 +2959,7 @@ GSDevice12::ComPtr GSDevice12::CreateTFXPipeline(const Pipe { const GSTexture::Format format = IsDATEModePrimIDInit(p.ps.date) ? GSTexture::Format::PrimID : - (p.ps.hdr ? GSTexture::Format::HDRColor : GSTexture::Format::Color); + (p.ps.colclip_hw ? GSTexture::Format::ColorClip : GSTexture::Format::Color); DXGI_FORMAT native_format; LookupNativeFormat(format, nullptr, nullptr, &native_format, nullptr); @@ -3817,7 +3821,7 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config) const bool stencil_DATE = (config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::Stencil || config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::StencilOne); - GSTexture12* hdr_rt = static_cast(g_gs_device->GetHDRTexture()); + GSTexture12* colclip_rt = static_cast(g_gs_device->GetColorClipTexture()); GSTexture12* draw_rt = static_cast(config.rt); GSTexture12* draw_ds = static_cast(config.ds); GSTexture12* draw_rt_clone = nullptr; @@ -3830,15 +3834,15 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config) // figure out the pipeline UpdateHWPipelineSelector(config); - // now blit the hdr texture back to the original target - if (hdr_rt) + // now blit the colclip texture back to the original target + if (colclip_rt) { - if (config.hdr_mode == GSHWDrawConfig::HDRMode::EarlyResolve) + if (config.colclip_mode == GSHWDrawConfig::ColClipMode::EarlyResolve) { - GL_PUSH("Blit HDR back to RT"); + GL_PUSH("Blit ColorClip back to RT"); EndRenderPass(); - hdr_rt->TransitionToState(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); + colclip_rt->TransitionToState(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); draw_rt = static_cast(config.rt); OMSetRenderTargets(draw_rt, draw_ds, config.scissor); @@ -3850,19 +3854,19 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config) D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_NO_ACCESS, D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_NO_ACCESS, draw_rt->GetUNormClearColor(), 0.0f, 0); - const GSVector4 sRect(GSVector4(config.hdr_update_area) / GSVector4(rtsize.x, rtsize.y).xyxy()); - SetPipeline(m_hdr_finish_pipelines[pipe.ds].get()); - SetUtilityTexture(hdr_rt, m_point_sampler_cpu); - DrawStretchRect(sRect, GSVector4(config.hdr_update_area), rtsize); + const GSVector4 sRect(GSVector4(config.colclip_update_area) / GSVector4(rtsize.x, rtsize.y).xyxy()); + SetPipeline(m_colclip_finish_pipelines[pipe.ds].get()); + SetUtilityTexture(colclip_rt, m_point_sampler_cpu); + DrawStretchRect(sRect, GSVector4(config.colclip_update_area), rtsize); g_perfmon.Put(GSPerfMon::TextureCopies, 1); - Recycle(hdr_rt); - g_gs_device->SetHDRTexture(nullptr); + Recycle(colclip_rt); + g_gs_device->SetColorClipTexture(nullptr); } else { - draw_rt = hdr_rt; - pipe.ps.hdr = 1; + draw_rt = colclip_rt; + pipe.ps.colclip_hw = 1; } } @@ -3903,7 +3907,7 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config) if (config.require_one_barrier) { // requires a copy of the RT - draw_rt_clone = static_cast(CreateTexture(rtsize.x, rtsize.y, 1, hdr_rt ? GSTexture::Format::HDRColor : GSTexture::Format::Color, true)); + draw_rt_clone = static_cast(CreateTexture(rtsize.x, rtsize.y, 1, colclip_rt ? GSTexture::Format::ColorClip : GSTexture::Format::Color, true)); if (draw_rt_clone) { EndRenderPass(); @@ -3917,19 +3921,19 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config) } } - // Switch to hdr target for colclip rendering - if (pipe.ps.hdr) + // Switch to colclip target for colclip hw rendering + if (pipe.ps.colclip_hw) { - if (!hdr_rt) + if (!colclip_rt) { - config.hdr_update_area = config.drawarea; + config.colclip_update_area = config.drawarea; EndRenderPass(); - hdr_rt = static_cast(CreateRenderTarget(rtsize.x, rtsize.y, GSTexture::Format::HDRColor, false)); - if (!hdr_rt) + colclip_rt = static_cast(CreateRenderTarget(rtsize.x, rtsize.y, GSTexture::Format::ColorClip, false)); + if (!colclip_rt) { - Console.WriteLn("D3D12: Failed to allocate HDR render target, aborting draw."); + Console.WriteLn("D3D12: Failed to allocate ColorClip render target, aborting draw."); if (date_image) Recycle(date_image); @@ -3937,17 +3941,17 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config) return; } - g_gs_device->SetHDRTexture(static_cast(hdr_rt)); + g_gs_device->SetColorClipTexture(static_cast(colclip_rt)); - // propagate clear value through if the hdr render is the first + // propagate clear value through if the colclip render is the first if (draw_rt->GetState() == GSTexture::State::Cleared) { - hdr_rt->SetState(GSTexture::State::Cleared); - hdr_rt->SetClearColor(draw_rt->GetClearColor()); + colclip_rt->SetState(GSTexture::State::Cleared); + colclip_rt->SetClearColor(draw_rt->GetClearColor()); } else if (draw_rt->GetState() == GSTexture::State::Dirty) { - GL_PUSH_("HDR Render Target Setup"); + GL_PUSH_("ColorClip Render Target Setup"); draw_rt->TransitionToState(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); } @@ -3956,7 +3960,7 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config) PSSetShaderResource(2, draw_rt, true); } - draw_rt = hdr_rt; + draw_rt = colclip_rt; } // clear texture binding when it's bound to RT or DS @@ -3966,11 +3970,10 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config) PSSetShaderResource(0, nullptr, false); } - // avoid restarting the render pass just to switch from rt+depth to rt and vice versa if (m_in_render_pass && (m_current_render_target == draw_rt || m_current_depth_target == draw_ds)) { // avoid restarting the render pass just to switch from rt+depth to rt and vice versa - // keep the depth even if doing HDR draws, because the next draw will probably re-enable depth + // keep the depth even if doing colclip hw draws, because the next draw will probably re-enable depth if (!draw_rt && m_current_render_target && config.tex != m_current_render_target && m_current_render_target->GetSize() == draw_ds->GetSize()) { @@ -3991,9 +3994,9 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config) if (!m_in_render_pass) { GSVector4 clear_color = draw_rt ? draw_rt->GetUNormClearColor() : GSVector4::zero(); - if (pipe.ps.hdr) + if (pipe.ps.colclip_hw) { - // Denormalize clear color for HDR. + // Denormalize clear color for hw colclip. clear_color *= GSVector4::cxpr(255.0f / 65535.0f, 255.0f / 65535.0f, 255.0f / 65535.0f, 1.0f); } BeginRenderPass(GetLoadOpForTexture(draw_rt), @@ -4007,13 +4010,13 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config) clear_color, draw_ds ? draw_ds->GetClearDepth() : 0.0f, 1); } - // rt -> hdr blit if enabled - if (hdr_rt && (config.hdr_mode == GSHWDrawConfig::HDRMode::ConvertOnly || config.hdr_mode == GSHWDrawConfig::HDRMode::ConvertAndResolve) && config.rt->GetState() == GSTexture::State::Dirty) + // rt -> colclip hw blit if enabled + if (colclip_rt && (config.colclip_mode == GSHWDrawConfig::ColClipMode::ConvertOnly || config.colclip_mode == GSHWDrawConfig::ColClipMode::ConvertAndResolve) && config.rt->GetState() == GSTexture::State::Dirty) { SetUtilityTexture(static_cast(config.rt), m_point_sampler_cpu); - SetPipeline(m_hdr_setup_pipelines[pipe.ds].get()); + SetPipeline(m_colclip_setup_pipelines[pipe.ds].get()); - const GSVector4 drawareaf = GSVector4((config.hdr_mode == GSHWDrawConfig::HDRMode::ConvertOnly) ? GSVector4i::loadh(rtsize) : config.drawarea); + const GSVector4 drawareaf = GSVector4((config.colclip_mode == GSHWDrawConfig::ColClipMode::ConvertOnly) ? GSVector4i::loadh(rtsize) : config.drawarea); const GSVector4 sRect(drawareaf / GSVector4(rtsize.x, rtsize.y).xyxy()); DrawStretchRect(sRect, GSVector4(drawareaf), rtsize); g_perfmon.Put(GSPerfMon::TextureCopies, 1); @@ -4021,9 +4024,9 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config) GL_POP(); } - // VB/IB upload, if we did DATE setup and it's not HDR this has already been done + // VB/IB upload, if we did DATE setup and it's not colclip hw this has already been done SetPrimitiveTopology(s_primitive_topology_mapping[static_cast(config.topology)]); - if (!date_image || hdr_rt) + if (!date_image || colclip_rt) UploadHWDrawVerticesAndIndices(config); // now we can do the actual draw @@ -4067,17 +4070,17 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config) if (date_image) Recycle(date_image); - // now blit the hdr texture back to the original target - if (hdr_rt) + // now blit the colclip texture back to the original target + if (colclip_rt) { - config.hdr_update_area = config.hdr_update_area.runion(config.drawarea); + config.colclip_update_area = config.colclip_update_area.runion(config.drawarea); - if ((config.hdr_mode == GSHWDrawConfig::HDRMode::ResolveOnly || config.hdr_mode == GSHWDrawConfig::HDRMode::ConvertAndResolve)) + if ((config.colclip_mode == GSHWDrawConfig::ColClipMode::ResolveOnly || config.colclip_mode == GSHWDrawConfig::ColClipMode::ConvertAndResolve)) { - GL_PUSH("Blit HDR back to RT"); + GL_PUSH("Blit ColorClip back to RT"); EndRenderPass(); - hdr_rt->TransitionToState(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); + colclip_rt->TransitionToState(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); draw_rt = static_cast(config.rt); OMSetRenderTargets(draw_rt, draw_ds, config.scissor); @@ -4089,14 +4092,14 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config) D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_NO_ACCESS, D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_NO_ACCESS, draw_rt->GetUNormClearColor(), 0.0f, 0); - const GSVector4 sRect(GSVector4(config.hdr_update_area) / GSVector4(rtsize.x, rtsize.y).xyxy()); - SetPipeline(m_hdr_finish_pipelines[pipe.ds].get()); - SetUtilityTexture(hdr_rt, m_point_sampler_cpu); - DrawStretchRect(sRect, GSVector4(config.hdr_update_area), rtsize); + const GSVector4 sRect(GSVector4(config.colclip_update_area) / GSVector4(rtsize.x, rtsize.y).xyxy()); + SetPipeline(m_colclip_finish_pipelines[pipe.ds].get()); + SetUtilityTexture(colclip_rt, m_point_sampler_cpu); + DrawStretchRect(sRect, GSVector4(config.colclip_update_area), rtsize); g_perfmon.Put(GSPerfMon::TextureCopies, 1); - Recycle(hdr_rt); - g_gs_device->SetHDRTexture(nullptr); + Recycle(colclip_rt); + g_gs_device->SetColorClipTexture(nullptr); } } } diff --git a/pcsx2/GS/Renderers/DX12/GSDevice12.h b/pcsx2/GS/Renderers/DX12/GSDevice12.h index 76af8bc54e..042b79d171 100644 --- a/pcsx2/GS/Renderers/DX12/GSDevice12.h +++ b/pcsx2/GS/Renderers/DX12/GSDevice12.h @@ -315,8 +315,8 @@ private: std::array, 32> m_color_copy{}; std::array, 2> m_merge{}; std::array, NUM_INTERLACE_SHADERS> m_interlace{}; - std::array, 2> m_hdr_setup_pipelines{}; // [depth] - std::array, 2> m_hdr_finish_pipelines{}; // [depth] + std::array, 2> m_colclip_setup_pipelines{}; // [depth] + std::array, 2> m_colclip_finish_pipelines{}; // [depth] std::array, 4>, 2> m_date_image_setup_pipelines{}; // [depth][datm] ComPtr m_fxaa_pipeline; ComPtr m_shadeboost_pipeline; diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index 056260f732..7401a2bb29 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -1135,7 +1135,7 @@ GSVector2i GSRendererHW::GetTargetSize(const GSTextureCache::Source* tex, const return g_texture_cache->GetTargetSize(m_cached_ctx.FRAME.Block(), m_cached_ctx.FRAME.FBW, m_cached_ctx.FRAME.PSM, valid_size.x, valid_size.y, can_expand); } -bool GSRendererHW::NextDrawHDR() const +bool GSRendererHW::NextDrawColClip() const { const int get_next_ctx = (m_state_flush_reason == CONTEXTCHANGE) ? m_env.PRIM.CTXT : m_backed_up_ctx; const GSDrawingContext& next_ctx = m_env.CTXT[get_next_ctx]; @@ -2438,31 +2438,31 @@ void GSRendererHW::Draw() // I hate that I have to do this, but some games (like Pac-Man World Rally) troll us by causing a flush with degenerate triangles, so we don't have all available information about the next draw. // So we have to check when the next draw happens if our frame has changed or if it's become recursive. - const bool has_HDR_texture = g_gs_device->GetHDRTexture() != nullptr; - if (!no_rt && has_HDR_texture && (m_conf.hdr_frame.FBP != m_cached_ctx.FRAME.FBP || m_conf.hdr_frame.Block() == m_cached_ctx.TEX0.TBP0)) + const bool has_colclip_texture = g_gs_device->GetColorClipTexture() != nullptr; + if (!no_rt && has_colclip_texture && (m_conf.colclip_frame.FBP != m_cached_ctx.FRAME.FBP || m_conf.colclip_frame.Block() == m_cached_ctx.TEX0.TBP0)) { GIFRegTEX0 FRAME; - FRAME.TBP0 = m_conf.hdr_frame.Block(); - FRAME.TBW = m_conf.hdr_frame.FBW; - FRAME.PSM = m_conf.hdr_frame.PSM; + FRAME.TBP0 = m_conf.colclip_frame.Block(); + FRAME.TBW = m_conf.colclip_frame.FBW; + FRAME.PSM = m_conf.colclip_frame.PSM; GSTextureCache::Target* old_rt = g_texture_cache->LookupTarget(FRAME, GSVector2i(1, 1), GetTextureScaleFactor(), GSTextureCache::RenderTarget, true, fm, false, false, true, true, GSVector4i(0, 0, 1, 1), true, false, false); if (old_rt) { - GL_CACHE("Pre-draw resolve of HDR! Address: %x", FRAME.TBP0); - GSTexture* hdr_texture = g_gs_device->GetHDRTexture(); - g_gs_device->StretchRect(hdr_texture, GSVector4(m_conf.hdr_update_area) / GSVector4(GSVector4i(hdr_texture->GetSize()).xyxy()), old_rt->m_texture, GSVector4(m_conf.hdr_update_area), - ShaderConvert::HDR_RESOLVE, false); + GL_CACHE("Pre-draw resolve of colclip! Address: %x", FRAME.TBP0); + GSTexture* colclip_texture = g_gs_device->GetColorClipTexture(); + g_gs_device->StretchRect(colclip_texture, GSVector4(m_conf.colclip_update_area) / GSVector4(GSVector4i(colclip_texture->GetSize()).xyxy()), old_rt->m_texture, GSVector4(m_conf.colclip_update_area), + ShaderConvert::COLCLIP_RESOLVE, false); - g_gs_device->Recycle(hdr_texture); + g_gs_device->Recycle(colclip_texture); - g_gs_device->SetHDRTexture(nullptr); + g_gs_device->SetColorClipTexture(nullptr); } else - DevCon.Warning("Error resolving HDR texture for pre-draw resolve"); + DevCon.Warning("Error resolving colclip texture for pre-draw resolve"); } const bool draw_sprite_tex = PRIM->TME && (m_vt.m_primclass == GS_SPRITE_CLASS); @@ -4267,12 +4267,12 @@ void GSRendererHW::Draw() if (GSConfig.ShouldDump(s_n, g_perfmon.GetFrame())) { - const bool writeback_HDR_texture = g_gs_device->GetHDRTexture() != nullptr; - if (writeback_HDR_texture) + const bool writeback_colclip_texture = g_gs_device->GetColorClipTexture() != nullptr; + if (writeback_colclip_texture) { - GSTexture* hdr_texture = g_gs_device->GetHDRTexture(); - g_gs_device->StretchRect(hdr_texture, GSVector4(m_conf.hdr_update_area) / GSVector4(GSVector4i(hdr_texture->GetSize()).xyxy()), rt->m_texture, GSVector4(m_conf.hdr_update_area), - ShaderConvert::HDR_RESOLVE, false); + GSTexture* colclip_texture = g_gs_device->GetColorClipTexture(); + g_gs_device->StretchRect(colclip_texture, GSVector4(m_conf.colclip_update_area) / GSVector4(GSVector4i(colclip_texture->GetSize()).xyxy()), rt->m_texture, GSVector4(m_conf.colclip_update_area), + ShaderConvert::COLCLIP_RESOLVE, false); } const u64 frame = g_perfmon.GetFrame(); @@ -5228,10 +5228,10 @@ void GSRendererHW::EmulateBlending(int rt_alpha_min, int rt_alpha_max, const boo // Per pixel alpha blending. const bool PABE = m_draw_env->PABE.PABE && GetAlphaMinMax().min < 128; - // HW blend can handle it, no need for sw or hdr colclip, Cd*Alpha or Cd*(1 - Alpha) where Alpha <= 128. + // HW blend can handle it, no need for sw or hw colclip, Cd*Alpha or Cd*(1 - Alpha) where Alpha <= 128. bool color_dest_blend2 = !PABE && ((m_conf.ps.blend_a == 1 && m_conf.ps.blend_b == 2 && m_conf.ps.blend_d == 2) || (m_conf.ps.blend_a == 2 && m_conf.ps.blend_b == 1 && m_conf.ps.blend_d == 1)) && (alpha_eq_less_one || (alpha_c1_eq_less_max_one && new_rt_alpha_scale)); - // HW blend can handle it, no need for sw or hdr colclip, Cs*Alpha + Cd*(1 - Alpha) or Cd*Alpha + Cs*(1 - Alpha) where Alpha <= 128. + // HW blend can handle it, no need for sw or hw colclip, Cs*Alpha + Cd*(1 - Alpha) or Cd*Alpha + Cs*(1 - Alpha) where Alpha <= 128. bool blend_zero_to_one_range = !PABE && ((m_conf.ps.blend_a == 0 && m_conf.ps.blend_b == 1 && m_conf.ps.blend_d == 1) || (blend_flag & BLEND_MIX3)) && (alpha_eq_less_one || (alpha_c1_eq_less_max_one && new_rt_alpha_scale)); @@ -5382,36 +5382,35 @@ void GSRendererHW::EmulateBlending(int rt_alpha_min, int rt_alpha_max, const boo // Color clip if (COLCLAMP.CLAMP == 0) { - bool has_HDR_texture = g_gs_device->GetHDRTexture() != nullptr; + bool has_colclip_texture = g_gs_device->GetColorClipTexture() != nullptr; - // Don't know any game that resizes the RT mid HDR, but gotta be careful. - if (has_HDR_texture) + // Don't know any game that resizes the RT mid colclip, but gotta be careful. + if (has_colclip_texture) { - GSTexture* hdr_texture = g_gs_device->GetHDRTexture(); + GSTexture* colclip_texture = g_gs_device->GetColorClipTexture(); - if (hdr_texture->GetSize() != rt->m_texture->GetSize()) + if (colclip_texture->GetSize() != rt->m_texture->GetSize()) { + GL_CACHE("Pre-Blend resolve of colclip due to size change! Address: %x", rt->m_TEX0.TBP0); + g_gs_device->StretchRect(colclip_texture, GSVector4(m_conf.colclip_update_area) / GSVector4(GSVector4i(colclip_texture->GetSize()).xyxy()), rt->m_texture, GSVector4(m_conf.colclip_update_area), + ShaderConvert::COLCLIP_RESOLVE, false); - GL_CACHE("Pre-Blend resolve of HDR due to size change! Address: %x", rt->m_TEX0.TBP0); - g_gs_device->StretchRect(hdr_texture, GSVector4(m_conf.hdr_update_area) / GSVector4(GSVector4i(hdr_texture->GetSize()).xyxy()), rt->m_texture, GSVector4(m_conf.hdr_update_area), - ShaderConvert::HDR_RESOLVE, false); + g_gs_device->Recycle(colclip_texture); - g_gs_device->Recycle(hdr_texture); + g_gs_device->SetColorClipTexture(nullptr); - g_gs_device->SetHDRTexture(nullptr); - - has_HDR_texture = false; + has_colclip_texture = false; } } - const bool free_colclip = !has_HDR_texture && (features.framebuffer_fetch || no_prim_overlap || blend_non_recursive); + const bool free_colclip = !has_colclip_texture && (features.framebuffer_fetch || no_prim_overlap || blend_non_recursive); GL_DBG("COLCLIP Info (Blending: %u/%u/%u/%u, OVERLAP: %d)", m_conf.ps.blend_a, m_conf.ps.blend_b, m_conf.ps.blend_c, m_conf.ps.blend_d, m_prim_overlap); if (color_dest_blend || color_dest_blend2 || blend_zero_to_one_range) { // No overflow, disable colclip. GL_INS("COLCLIP mode DISABLED"); sw_blending = false; - m_conf.hdr_mode = (has_HDR_texture && !NextDrawHDR()) ? GSHWDrawConfig::HDRMode::ResolveOnly : GSHWDrawConfig::HDRMode::NoModify; + m_conf.colclip_mode = (has_colclip_texture && !NextDrawColClip()) ? GSHWDrawConfig::ColClipMode::ResolveOnly : GSHWDrawConfig::ColClipMode::NoModify; } else if (free_colclip) { @@ -5419,35 +5418,35 @@ void GSRendererHW::EmulateBlending(int rt_alpha_min, int rt_alpha_max, const boo GL_INS("COLCLIP Free mode ENABLED"); m_conf.ps.colclip = 1; sw_blending = true; - // Disable the HDR algo + // Disable the colclip hw algo accumulation_blend = false; blend_mix = false; - m_conf.hdr_mode = (has_HDR_texture && !NextDrawHDR()) ? GSHWDrawConfig::HDRMode::ResolveOnly : GSHWDrawConfig::HDRMode::NoModify; + m_conf.colclip_mode = (has_colclip_texture && !NextDrawColClip()) ? GSHWDrawConfig::ColClipMode::ResolveOnly : GSHWDrawConfig::ColClipMode::NoModify; } else if (accumulation_blend) { // A fast algo that requires 2 passes - GL_INS("COLCLIP Fast HDR mode ENABLED"); - m_conf.ps.hdr = 1; - sw_blending = true; // Enable sw blending for the HDR algo + GL_INS("COLCLIP Fast HW mode ENABLED"); + m_conf.ps.colclip_hw = 1; + sw_blending = true; // Enable sw blending for the colclip algo - m_conf.hdr_mode = has_HDR_texture ? (NextDrawHDR() ? GSHWDrawConfig::HDRMode::NoModify : GSHWDrawConfig::HDRMode::ResolveOnly) : (NextDrawHDR() ? GSHWDrawConfig::HDRMode::ConvertOnly : GSHWDrawConfig::HDRMode::ConvertAndResolve); + m_conf.colclip_mode = has_colclip_texture ? (NextDrawColClip() ? GSHWDrawConfig::ColClipMode::NoModify : GSHWDrawConfig::ColClipMode::ResolveOnly) : (NextDrawColClip() ? GSHWDrawConfig::ColClipMode::ConvertOnly : GSHWDrawConfig::ColClipMode::ConvertAndResolve); } else if (sw_blending) { // A slow algo that could requires several passes (barely used) GL_INS("COLCLIP SW mode ENABLED"); m_conf.ps.colclip = 1; - m_conf.hdr_mode = (has_HDR_texture && !NextDrawHDR()) ? GSHWDrawConfig::HDRMode::ResolveOnly : GSHWDrawConfig::HDRMode::NoModify; + m_conf.colclip_mode = (has_colclip_texture && !NextDrawColClip()) ? GSHWDrawConfig::ColClipMode::ResolveOnly : GSHWDrawConfig::ColClipMode::NoModify; } else { - GL_INS("COLCLIP HDR mode ENABLED"); - m_conf.ps.hdr = 1; - m_conf.hdr_mode = has_HDR_texture ? (NextDrawHDR() ? GSHWDrawConfig::HDRMode::NoModify : GSHWDrawConfig::HDRMode::ResolveOnly) : (NextDrawHDR() ? GSHWDrawConfig::HDRMode::ConvertOnly : GSHWDrawConfig::HDRMode::ConvertAndResolve); + GL_INS("COLCLIP HW mode ENABLED"); + m_conf.ps.colclip_hw = 1; + m_conf.colclip_mode = has_colclip_texture ? (NextDrawColClip() ? GSHWDrawConfig::ColClipMode::NoModify : GSHWDrawConfig::ColClipMode::ResolveOnly) : (NextDrawColClip() ? GSHWDrawConfig::ColClipMode::ConvertOnly : GSHWDrawConfig::ColClipMode::ConvertAndResolve); } - m_conf.hdr_frame = m_cached_ctx.FRAME; + m_conf.colclip_frame = m_cached_ctx.FRAME; } // Per pixel alpha blending @@ -5478,13 +5477,13 @@ void GSRendererHW::EmulateBlending(int rt_alpha_min, int rt_alpha_max, const boo blend_mix = false; m_conf.ps.pabe = 1; - // HDR mode should be disabled when doing sw blend, swap with sw colclip. - if (m_conf.ps.hdr) + // hw colclip mode should be disabled when doing sw blend, swap with sw colclip. + if (m_conf.ps.colclip_hw) { - const bool has_HDR_texture = g_gs_device->GetHDRTexture() != nullptr; - m_conf.ps.hdr = 0; + const bool has_colclip_texture = g_gs_device->GetColorClipTexture() != nullptr; + m_conf.ps.colclip_hw = 0; m_conf.ps.colclip = 1; - m_conf.hdr_mode = has_HDR_texture ? GSHWDrawConfig::HDRMode::EarlyResolve : GSHWDrawConfig::HDRMode::NoModify; + m_conf.colclip_mode = has_colclip_texture ? GSHWDrawConfig::ColClipMode::EarlyResolve : GSHWDrawConfig::ColClipMode::NoModify; } } else @@ -5552,9 +5551,9 @@ void GSRendererHW::EmulateBlending(int rt_alpha_min, int rt_alpha_max, const boo if (blend.op == GSDevice::OP_REV_SUBTRACT) { pxAssert(m_conf.ps.blend_a == 2); - if (m_conf.ps.hdr) + if (m_conf.ps.colclip_hw) { - // HDR uses unorm, which is always positive + // HW colclip uses unorm, which is always positive // Have the shader do the inversion, then clip to remove the negative m_conf.blend.op = GSDevice::OP_ADD; } @@ -7405,7 +7404,7 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta m_conf.require_full_barrier = false; } // Multi-pass algorithms shouldn't be needed with full barrier and backends may not handle this correctly - pxAssert(!m_conf.require_full_barrier || !m_conf.ps.hdr); + pxAssert(!m_conf.require_full_barrier || !m_conf.ps.colclip_hw); // Swap full barrier for one barrier when there's no overlap, or a texture shuffle. if (m_conf.require_full_barrier && (m_prim_overlap == PRIM_OVERLAP_NO || m_conf.ps.shuffle)) diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.h b/pcsx2/GS/Renderers/HW/GSRendererHW.h index 047f41a87f..c0b587517f 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.h +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.h @@ -111,7 +111,7 @@ private: void EmulateATST(float& AREF, GSHWDrawConfig::PSSelector& ps, bool pass_2); void SetTCOffset(); - bool NextDrawHDR() const; + bool NextDrawColClip() const; bool IsPossibleChannelShuffle() const; bool IsPageCopy() const; bool NextDrawMatchesShuffle() const; diff --git a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.h b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.h index 27b39357a8..6e3001fa53 100644 --- a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.h +++ b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.h @@ -256,11 +256,9 @@ public: MRCOwned> m_clut_pipeline[2]; MRCOwned> m_stencil_clear_pipeline; MRCOwned> m_primid_init_pipeline[2][4]; - MRCOwned> m_hdr_init_pipeline; - MRCOwned> m_hdr_rta_init_pipeline; - MRCOwned> m_hdr_clear_pipeline; - MRCOwned> m_hdr_resolve_pipeline; - MRCOwned> m_hdr_rta_resolve_pipeline; + MRCOwned> m_colclip_init_pipeline; + MRCOwned> m_colclip_clear_pipeline; + MRCOwned> m_colclip_resolve_pipeline; MRCOwned> m_fxaa_pipeline; MRCOwned> m_shadeboost_pipeline; MRCOwned> m_imgui_pipeline; diff --git a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm index b8e0318378..43badee1f0 100644 --- a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm +++ b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm @@ -510,7 +510,9 @@ static constexpr MTLPixelFormat ConvertPixelFormat(GSTexture::Format format) case GSTexture::Format::UInt16: return MTLPixelFormatR16Uint; case GSTexture::Format::UNorm8: return MTLPixelFormatA8Unorm; case GSTexture::Format::Color: return MTLPixelFormatRGBA8Unorm; - case GSTexture::Format::HDRColor: return MTLPixelFormatRGBA16Unorm; + case GSTexture::Format::ColorHQ: return MTLPixelFormatRGB10A2Unorm; + case GSTexture::Format::ColorHDR: return MTLPixelFormatRGBA16Float; + case GSTexture::Format::ColorClip: return MTLPixelFormatRGBA16Unorm; case GSTexture::Format::DepthStencil: return MTLPixelFormatDepth32Float_Stencil8; case GSTexture::Format::Invalid: return MTLPixelFormatInvalid; case GSTexture::Format::BC1: return MTLPixelFormatBC1_RGBA; @@ -1065,14 +1067,14 @@ bool GSDeviceMTL::Create(GSVSyncMode vsync_mode, bool allow_present_throttle) auto pdesc = [[MTLRenderPipelineDescriptor new] autorelease]; // FS Triangle Pipelines pdesc.colorAttachments[0].pixelFormat = ConvertPixelFormat(GSTexture::Format::Color); - m_hdr_resolve_pipeline = MakePipeline(pdesc, fs_triangle, LoadShader(@"ps_hdr_resolve"), @"HDR Resolve"); + m_colclip_resolve_pipeline = MakePipeline(pdesc, fs_triangle, LoadShader(@"ps_colclip_resolve"), @"ColorClip Resolve"); m_fxaa_pipeline = MakePipeline(pdesc, fs_triangle, LoadShader(@"ps_fxaa"), @"fxaa"); m_shadeboost_pipeline = MakePipeline(pdesc, fs_triangle, LoadShader(@"ps_shadeboost"), @"shadeboost"); m_clut_pipeline[0] = MakePipeline(pdesc, fs_triangle, LoadShader(@"ps_convert_clut_4"), @"4-bit CLUT Update"); m_clut_pipeline[1] = MakePipeline(pdesc, fs_triangle, LoadShader(@"ps_convert_clut_8"), @"8-bit CLUT Update"); - pdesc.colorAttachments[0].pixelFormat = ConvertPixelFormat(GSTexture::Format::HDRColor); - m_hdr_init_pipeline = MakePipeline(pdesc, fs_triangle, LoadShader(@"ps_hdr_init"), @"HDR Init"); - m_hdr_clear_pipeline = MakePipeline(pdesc, fs_triangle, LoadShader(@"ps_clear"), @"HDR Clear"); + pdesc.colorAttachments[0].pixelFormat = ConvertPixelFormat(GSTexture::Format::ColorClip); + m_colclip_init_pipeline = MakePipeline(pdesc, fs_triangle, LoadShader(@"ps_colclip_init"), @"ColorClip Init"); + m_colclip_clear_pipeline = MakePipeline(pdesc, fs_triangle, LoadShader(@"ps_clear"), @"ColorClip Clear"); pdesc.colorAttachments[0].pixelFormat = MTLPixelFormatInvalid; pdesc.stencilAttachmentPixelFormat = MTLPixelFormatDepth32Float_Stencil8; m_datm_pipeline[0] = MakePipeline(pdesc, fs_triangle, LoadShader(@"ps_datm0"), @"datm0"); @@ -1116,8 +1118,8 @@ bool GSDeviceMTL::Create(GSVSyncMode vsync_mode, bool allow_present_throttle) case ShaderConvert::DATM_1_RTA_CORRECTION: case ShaderConvert::CLUT_4: case ShaderConvert::CLUT_8: - case ShaderConvert::HDR_INIT: - case ShaderConvert::HDR_RESOLVE: + case ShaderConvert::COLCLIP_INIT: + case ShaderConvert::COLCLIP_RESOLVE: continue; case ShaderConvert::FLOAT32_TO_32_BITS: pdesc.colorAttachments[0].pixelFormat = ConvertPixelFormat(GSTexture::Format::UInt32); @@ -1174,7 +1176,7 @@ bool GSDeviceMTL::Create(GSVSyncMode vsync_mode, bool allow_present_throttle) m_present_pipeline[i] = MakePipeline(pdesc, vs_convert, LoadShader(name), [NSString stringWithFormat:@"present_%s", shaderName(conv) + 3]); } - pdesc.colorAttachments[0].pixelFormat = MTLPixelFormatRGBA8Unorm; + pdesc.colorAttachments[0].pixelFormat = ConvertPixelFormat(GSTexture::Format::Color); for (size_t i = 0; i < std::size(m_convert_pipeline_copy_mask); i++) { MTLColorWriteMask mask = MTLColorWriteMaskNone; @@ -1852,7 +1854,7 @@ void GSDeviceMTL::MRESetHWPipelineState(GSHWDrawConfig::VSSelector vssel, GSHWDr setFnConstantI(m_fn_constants, pssel.blend_d, GSMTLConstantIndex_PS_BLEND_D); setFnConstantI(m_fn_constants, pssel.blend_hw, GSMTLConstantIndex_PS_BLEND_HW); setFnConstantB(m_fn_constants, pssel.a_masked, GSMTLConstantIndex_PS_A_MASKED); - setFnConstantB(m_fn_constants, pssel.hdr, GSMTLConstantIndex_PS_HDR); + setFnConstantB(m_fn_constants, pssel.colclip_hw, GSMTLConstantIndex_PS_COLCLIP_HW); setFnConstantB(m_fn_constants, pssel.rta_correction, GSMTLConstantIndex_PS_RTA_CORRECTION); setFnConstantB(m_fn_constants, pssel.rta_source_correction, GSMTLConstantIndex_PS_RTA_SRC_CORRECTION); setFnConstantB(m_fn_constants, pssel.colclip, GSMTLConstantIndex_PS_COLCLIP); @@ -2142,53 +2144,53 @@ void GSDeviceMTL::RenderHW(GSHWDrawConfig& config) GSTexture* stencil = nullptr; GSTexture* primid_tex = nullptr; GSTexture* rt = config.rt; - GSTexture* hdr_rt = g_gs_device->GetHDRTexture(); + GSTexture* colclip_rt = g_gs_device->GetColorClipTexture(); - if (hdr_rt) + if (colclip_rt) { - if (config.hdr_mode == GSHWDrawConfig::HDRMode::EarlyResolve) + if (config.colclip_mode == GSHWDrawConfig::ColClipMode::EarlyResolve) { - BeginRenderPass(@"HDR Resolve", config.rt, MTLLoadActionLoad, nullptr, MTLLoadActionDontCare); - RenderCopy(hdr_rt, m_hdr_resolve_pipeline, config.hdr_update_area); + BeginRenderPass(@"ColorClip Resolve", config.rt, MTLLoadActionLoad, nullptr, MTLLoadActionDontCare); + RenderCopy(colclip_rt, m_colclip_resolve_pipeline, config.colclip_update_area); g_perfmon.Put(GSPerfMon::TextureCopies, 1); - Recycle(hdr_rt); + Recycle(colclip_rt); - g_gs_device->SetHDRTexture(nullptr); + g_gs_device->SetColorClipTexture(nullptr); - hdr_rt = nullptr; + colclip_rt = nullptr; } else - config.ps.hdr = 1; + config.ps.colclip_hw = 1; } - if (config.ps.hdr) + if (config.ps.colclip_hw) { - if (!hdr_rt) + if (!colclip_rt) { - config.hdr_update_area = config.drawarea; + config.colclip_update_area = config.drawarea; GSVector2i size = config.rt->GetSize(); - rt = hdr_rt = CreateRenderTarget(size.x, size.y, GSTexture::Format::HDRColor, false); + rt = colclip_rt = CreateRenderTarget(size.x, size.y, GSTexture::Format::ColorClip, false); - g_gs_device->SetHDRTexture(hdr_rt); + g_gs_device->SetColorClipTexture(colclip_rt); - const GSVector4i copy_rect = (config.hdr_mode == GSHWDrawConfig::HDRMode::ConvertOnly) ? GSVector4i::loadh(size) : config.drawarea; + const GSVector4i copy_rect = (config.colclip_mode == GSHWDrawConfig::ColClipMode::ConvertOnly) ? GSVector4i::loadh(size) : config.drawarea; switch (config.rt->GetState()) { case GSTexture::State::Dirty: - BeginRenderPass(@"HDR Init", hdr_rt, MTLLoadActionDontCare, nullptr, MTLLoadActionDontCare); - RenderCopy(config.rt, m_hdr_init_pipeline, copy_rect); + BeginRenderPass(@"ColorClip Init", colclip_rt, MTLLoadActionDontCare, nullptr, MTLLoadActionDontCare); + RenderCopy(config.rt, m_colclip_init_pipeline, copy_rect); g_perfmon.Put(GSPerfMon::TextureCopies, 1); break; case GSTexture::State::Cleared: { - BeginRenderPass(@"HDR Clear", hdr_rt, MTLLoadActionDontCare, nullptr, MTLLoadActionDontCare); + BeginRenderPass(@"ColorClip Clear", colclip_rt, MTLLoadActionDontCare, nullptr, MTLLoadActionDontCare); GSVector4 color = GSVector4::rgba32(config.rt->GetClearColor()) / GSVector4::cxpr(65535, 65535, 65535, 255); [m_current_render.encoder setFragmentBytes:&color length:sizeof(color) atIndex:GSMTLBufferIndexUniforms]; - RenderCopy(nullptr, m_hdr_clear_pipeline, copy_rect); + RenderCopy(nullptr, m_colclip_clear_pipeline, copy_rect); break; } @@ -2197,7 +2199,7 @@ void GSDeviceMTL::RenderHW(GSHWDrawConfig& config) } } - rt = hdr_rt; + rt = colclip_rt; } switch (config.destination_alpha) @@ -2284,19 +2286,19 @@ void GSDeviceMTL::RenderHW(GSHWDrawConfig& config) SendHWDraw(config, mtlenc, index_buffer, index_buffer_offset); } - if (hdr_rt) + if (colclip_rt) { - config.hdr_update_area = config.hdr_update_area.runion(config.drawarea); + config.colclip_update_area = config.colclip_update_area.runion(config.drawarea); - if ((config.hdr_mode == GSHWDrawConfig::HDRMode::ResolveOnly || config.hdr_mode == GSHWDrawConfig::HDRMode::ConvertAndResolve)) + if ((config.colclip_mode == GSHWDrawConfig::ColClipMode::ResolveOnly || config.colclip_mode == GSHWDrawConfig::ColClipMode::ConvertAndResolve)) { - BeginRenderPass(@"HDR Resolve", config.rt, MTLLoadActionLoad, nullptr, MTLLoadActionDontCare); - RenderCopy(hdr_rt, m_hdr_resolve_pipeline, config.hdr_update_area); + BeginRenderPass(@"ColorClip Resolve", config.rt, MTLLoadActionLoad, nullptr, MTLLoadActionDontCare); + RenderCopy(colclip_rt, m_colclip_resolve_pipeline, config.colclip_update_area); g_perfmon.Put(GSPerfMon::TextureCopies, 1); - Recycle(hdr_rt); + Recycle(colclip_rt); - g_gs_device->SetHDRTexture(nullptr); + g_gs_device->SetColorClipTexture(nullptr); } } diff --git a/pcsx2/GS/Renderers/Metal/GSMTLSharedHeader.h b/pcsx2/GS/Renderers/Metal/GSMTLSharedHeader.h index 26ccccf393..1773fdc65a 100644 --- a/pcsx2/GS/Renderers/Metal/GSMTLSharedHeader.h +++ b/pcsx2/GS/Renderers/Metal/GSMTLSharedHeader.h @@ -192,7 +192,7 @@ enum GSMTLFnConstants GSMTLConstantIndex_PS_BLEND_D, GSMTLConstantIndex_PS_BLEND_HW, GSMTLConstantIndex_PS_A_MASKED, - GSMTLConstantIndex_PS_HDR, + GSMTLConstantIndex_PS_COLCLIP_HW, GSMTLConstantIndex_PS_RTA_CORRECTION, GSMTLConstantIndex_PS_RTA_SRC_CORRECTION, GSMTLConstantIndex_PS_COLCLIP, diff --git a/pcsx2/GS/Renderers/Metal/convert.metal b/pcsx2/GS/Renderers/Metal/convert.metal index 6c3aa84356..f2fa5d0491 100644 --- a/pcsx2/GS/Renderers/Metal/convert.metal +++ b/pcsx2/GS/Renderers/Metal/convert.metal @@ -138,13 +138,13 @@ fragment float4 ps_rta_decorrection(ConvertShaderData data [[stage_in]], Convert return float4(in.rgb, in.a * (128.25f / 255.0f)); } -fragment float4 ps_hdr_init(float4 p [[position]], DirectReadTextureIn tex) +fragment float4 ps_colclip_init(float4 p [[position]], DirectReadTextureIn tex) { float4 in = tex.read(p); return float4(round(in.rgb * 255.f) / 65535.f, in.a); } -fragment float4 ps_hdr_resolve(float4 p [[position]], DirectReadTextureIn tex) +fragment float4 ps_colclip_resolve(float4 p [[position]], DirectReadTextureIn tex) { float4 in = tex.read(p); return float4(float3(uint3(in.rgb * 65535.5f) & 255) / 255.f, in.a); diff --git a/pcsx2/GS/Renderers/Metal/tfx.metal b/pcsx2/GS/Renderers/Metal/tfx.metal index 337e0f4b5c..3e364e3558 100644 --- a/pcsx2/GS/Renderers/Metal/tfx.metal +++ b/pcsx2/GS/Renderers/Metal/tfx.metal @@ -47,7 +47,7 @@ constant uint PS_BLEND_C [[function_constant(GSMTLConstantIndex_PS_BL constant uint PS_BLEND_D [[function_constant(GSMTLConstantIndex_PS_BLEND_D)]]; constant uint PS_BLEND_HW [[function_constant(GSMTLConstantIndex_PS_BLEND_HW)]]; constant bool PS_A_MASKED [[function_constant(GSMTLConstantIndex_PS_A_MASKED)]]; -constant bool PS_HDR [[function_constant(GSMTLConstantIndex_PS_HDR)]]; +constant bool PS_COLCLIP_HW [[function_constant(GSMTLConstantIndex_PS_COLCLIP_HW)]]; constant bool PS_RTA_CORRECTION [[function_constant(GSMTLConstantIndex_PS_RTA_CORRECTION)]]; constant bool PS_RTA_SRC_CORRECTION [[function_constant(GSMTLConstantIndex_PS_RTA_SRC_CORRECTION)]]; constant bool PS_COLCLIP [[function_constant(GSMTLConstantIndex_PS_COLCLIP)]]; @@ -858,7 +858,7 @@ struct PSMain { if (PS_FBMASK) { - float multi = PS_HDR ? 65535.0 : 255.5; + float multi = PS_COLCLIP_HW ? 65535.0 : 255.5; C = float4((uint4(int4(C)) & (cb.fbmask ^ 0xff)) | (uint4(current_color * float4(multi, multi, multi, 255)) & cb.fbmask)); } } @@ -898,7 +898,7 @@ struct PSMain C.rgb += 7.f; // Need to round up, not down since the shader will invert // Correct the Color value based on the output format - if (PS_COLCLIP == 0 && PS_HDR == 0) + if (PS_COLCLIP == 0 && PS_COLCLIP_HW == 0) C.rgb = clamp(C.rgb, 0.f, 255.f); // Standard Clamp // FIXME rouding of negative float? @@ -910,7 +910,7 @@ struct PSMain // In 16 bits format, only 5 bits of colors are used. It impacts shadows computation of Castlevania if (PS_DST_FMT == FMT_16 && PS_DITHER != 3 && (PS_BLEND_MIX == 0 || PS_DITHER)) C.rgb = float3(short3(C.rgb) & 0xF8); - else if (PS_COLCLIP == 1 || PS_HDR == 1) + else if (PS_COLCLIP == 1 || PS_COLCLIP_HW == 1) C.rgb = float3(short3(C.rgb) & 0xFF); } else if (PS_DST_FMT == FMT_16 && PS_DITHER != 3 && PS_BLEND_MIX == 0 && PS_BLEND_HW == 0) @@ -963,7 +963,7 @@ struct PSMain current_color.a = float(denorm_rt.g & 0x80); } } - float multi = PS_HDR ? 65535.0 : 255.5; + float multi = PS_COLCLIP_HW ? 65535.0 : 255.5; float3 Cd = trunc(current_color.rgb * multi); float3 Cs = Color.rgb; @@ -1205,7 +1205,7 @@ struct PSMain if (PS_COLOR0) { out.c0.a = PS_RTA_CORRECTION ? C.a / 128.f : C.a / 255.f; - out.c0.rgb = PS_HDR ? float3(C.rgb / 65535.f) : C.rgb / 255.f; + out.c0.rgb = PS_COLCLIP_HW ? float3(C.rgb / 65535.f) : C.rgb / 255.f; if (PS_AFAIL == 3 && !PS_COLOR1 && !atst_pass) // Doing RGB_ONLY without COLOR1 out.c0.a = current_color.a; } diff --git a/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp b/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp index 8ae9200c95..1775f9caa6 100644 --- a/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp +++ b/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp @@ -1394,7 +1394,7 @@ std::string GSDeviceOGL::GetPSSource(const PSSelector& sel) + fmt::format("#define PS_READ16_SRC {}\n", sel.real16src) + fmt::format("#define PS_WRITE_RG {}\n", sel.write_rg) + fmt::format("#define PS_FBMASK {}\n", sel.fbmask) - + fmt::format("#define PS_HDR {}\n", sel.hdr) + + fmt::format("#define PS_COLCLIP_HW {}\n", sel.colclip_hw) + fmt::format("#define PS_RTA_CORRECTION {}\n", sel.rta_correction) + fmt::format("#define PS_RTA_SRC_CORRECTION {}\n", sel.rta_source_correction) + fmt::format("#define PS_DITHER {}\n", sel.dither) @@ -2414,43 +2414,43 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config) GSVector2i rtsize = (config.rt ? config.rt : config.ds)->GetSize(); GSTexture* primid_texture = nullptr; - GSTexture* hdr_rt = g_gs_device->GetHDRTexture(); + GSTexture* colclip_rt = g_gs_device->GetColorClipTexture(); - if (hdr_rt) + if (colclip_rt) { - if (config.hdr_mode == GSHWDrawConfig::HDRMode::EarlyResolve) + if (config.colclip_mode == GSHWDrawConfig::ColClipMode::EarlyResolve) { const GSVector2i size = config.rt->GetSize(); - const GSVector4 dRect(config.hdr_update_area); + const GSVector4 dRect(config.colclip_update_area); const GSVector4 sRect = dRect / GSVector4(size.x, size.y).xyxy(); - StretchRect(hdr_rt, sRect, config.rt, dRect, ShaderConvert::HDR_RESOLVE, false); + StretchRect(colclip_rt, sRect, config.rt, dRect, ShaderConvert::COLCLIP_RESOLVE, false); - Recycle(hdr_rt); + Recycle(colclip_rt); - g_gs_device->SetHDRTexture(nullptr); + g_gs_device->SetColorClipTexture(nullptr); - hdr_rt = nullptr; + colclip_rt = nullptr; } else { - config.ps.hdr = 1; + config.ps.colclip_hw = 1; } } - if (config.ps.hdr) + if (config.ps.colclip_hw) { - if (!hdr_rt) + if (!colclip_rt) { - config.hdr_update_area = config.drawarea; + config.colclip_update_area = config.drawarea; - hdr_rt = CreateRenderTarget(rtsize.x, rtsize.y, GSTexture::Format::HDRColor, false); - OMSetRenderTargets(hdr_rt, config.ds, nullptr); + colclip_rt = CreateRenderTarget(rtsize.x, rtsize.y, GSTexture::Format::ColorClip, false); + OMSetRenderTargets(colclip_rt, config.ds, nullptr); - g_gs_device->SetHDRTexture(hdr_rt); + g_gs_device->SetColorClipTexture(colclip_rt); - const GSVector4 dRect = GSVector4((config.hdr_mode == GSHWDrawConfig::HDRMode::ConvertOnly) ? GSVector4i::loadh(rtsize) : config.drawarea); + const GSVector4 dRect = GSVector4((config.colclip_mode == GSHWDrawConfig::ColClipMode::ConvertOnly) ? GSVector4i::loadh(rtsize) : config.drawarea); const GSVector4 sRect = dRect / GSVector4(rtsize.x, rtsize.y).xyxy(); - StretchRect(config.rt, sRect, hdr_rt, dRect, ShaderConvert::HDR_INIT, false); + StretchRect(config.rt, sRect, colclip_rt, dRect, ShaderConvert::COLCLIP_INIT, false); } } @@ -2461,7 +2461,7 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config) case GSHWDrawConfig::DestinationAlphaMode::Full: break; // No setup case GSHWDrawConfig::DestinationAlphaMode::PrimIDTracking: - primid_texture = InitPrimDateTexture(hdr_rt ? hdr_rt : config.rt, config.drawarea, config.datm); + primid_texture = InitPrimDateTexture(colclip_rt ? colclip_rt : config.rt, config.drawarea, config.datm); break; case GSHWDrawConfig::DestinationAlphaMode::StencilOne: if (m_features.texture_barrier) @@ -2481,7 +2481,7 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config) {GSVector4(dst.x, dst.w, 0.0f, 0.0f), GSVector2(src.x, src.w)}, {GSVector4(dst.z, dst.w, 0.0f, 0.0f), GSVector2(src.z, src.w)}, }; - SetupDATE(hdr_rt ? hdr_rt : config.rt, config.ds, vertices, config.datm); + SetupDATE(colclip_rt ? colclip_rt : config.rt, config.ds, vertices, config.datm); } } @@ -2490,11 +2490,11 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config) if (config.require_one_barrier && !m_features.texture_barrier) { // Requires a copy of the RT - draw_rt_clone = CreateTexture(rtsize.x, rtsize.y, 1, hdr_rt ? GSTexture::Format::HDRColor : GSTexture::Format::Color, true); + draw_rt_clone = CreateTexture(rtsize.x, rtsize.y, 1, colclip_rt ? GSTexture::Format::ColorClip : GSTexture::Format::Color, true); GL_PUSH("Copy RT to temp texture for fbmask {%d,%d %dx%d}", config.drawarea.left, config.drawarea.top, config.drawarea.width(), config.drawarea.height()); - CopyRect(hdr_rt ? hdr_rt : config.rt, draw_rt_clone, config.drawarea, config.drawarea.left, config.drawarea.top); + CopyRect(colclip_rt ? colclip_rt : config.rt, draw_rt_clone, config.drawarea, config.drawarea.left, config.drawarea.top); } IASetVertexBuffer(config.verts, config.nverts); @@ -2534,7 +2534,7 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config) if (draw_rt_clone) PSSetShaderResource(2, draw_rt_clone); else if (config.require_one_barrier || config.require_full_barrier) - PSSetShaderResource(2, hdr_rt ? hdr_rt : config.rt); + PSSetShaderResource(2, colclip_rt ? colclip_rt : config.rt); SetupSampler(config.sampler); @@ -2622,7 +2622,7 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config) } // avoid changing framebuffer just to switch from rt+depth to rt and vice versa - GSTexture* draw_rt = hdr_rt ? hdr_rt : config.rt; + GSTexture* draw_rt = colclip_rt ? colclip_rt : config.rt; GSTexture* draw_ds = config.ds; if (!draw_rt && GLState::rt && GLState::ds == draw_ds && config.tex != GLState::rt && GLState::rt->GetSize() == draw_ds->GetSize()) @@ -2700,20 +2700,20 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config) if (draw_rt_clone) Recycle(draw_rt_clone); - if (hdr_rt) + if (colclip_rt) { - config.hdr_update_area = config.hdr_update_area.runion(config.drawarea); + config.colclip_update_area = config.colclip_update_area.runion(config.drawarea); - if ((config.hdr_mode == GSHWDrawConfig::HDRMode::ResolveOnly || config.hdr_mode == GSHWDrawConfig::HDRMode::ConvertAndResolve)) + if ((config.colclip_mode == GSHWDrawConfig::ColClipMode::ResolveOnly || config.colclip_mode == GSHWDrawConfig::ColClipMode::ConvertAndResolve)) { const GSVector2i size = config.rt->GetSize(); - const GSVector4 dRect(config.hdr_update_area); + const GSVector4 dRect(config.colclip_update_area); const GSVector4 sRect = dRect / GSVector4(size.x, size.y).xyxy(); - StretchRect(hdr_rt, sRect, config.rt, dRect, ShaderConvert::HDR_RESOLVE, false); + StretchRect(colclip_rt, sRect, config.rt, dRect, ShaderConvert::COLCLIP_RESOLVE, false); - Recycle(hdr_rt); + Recycle(colclip_rt); - g_gs_device->SetHDRTexture(nullptr); + g_gs_device->SetColorClipTexture(nullptr); } } } diff --git a/pcsx2/GS/Renderers/OpenGL/GSTextureOGL.cpp b/pcsx2/GS/Renderers/OpenGL/GSTextureOGL.cpp index 238ad16ea8..9f0e9b00ee 100644 --- a/pcsx2/GS/Renderers/OpenGL/GSTextureOGL.cpp +++ b/pcsx2/GS/Renderers/OpenGL/GSTextureOGL.cpp @@ -65,6 +65,8 @@ GSTextureOGL::GSTextureOGL(Type type, int width, int height, int levels, Format // 4 channel normalized case Format::Color: + case Format::ColorHQ: + case Format::ColorHDR: gl_fmt = GL_RGBA8; m_int_format = GL_RGBA; m_int_type = GL_UNSIGNED_BYTE; @@ -72,7 +74,7 @@ GSTextureOGL::GSTextureOGL(Type type, int width, int height, int levels, Format break; // 4 channel float - case Format::HDRColor: + case Format::ColorClip: gl_fmt = GL_RGBA16; m_int_format = GL_RGBA; m_int_type = GL_UNSIGNED_SHORT; diff --git a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp index d82df886f6..0f997951ce 100644 --- a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp +++ b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp @@ -2715,10 +2715,12 @@ void GSDeviceVK::DrawIndexedPrimitive(int offset, int count) VkFormat GSDeviceVK::LookupNativeFormat(GSTexture::Format format) const { - static constexpr std::array(GSTexture::Format::BC7) + 1> s_format_mapping = {{ + static constexpr std::array(GSTexture::Format::Last) + 1> s_format_mapping = {{ VK_FORMAT_UNDEFINED, // Invalid VK_FORMAT_R8G8B8A8_UNORM, // Color - VK_FORMAT_R16G16B16A16_UNORM, // HDRColor + VK_FORMAT_A2B10G10R10_UNORM_PACK32, // ColorHQ + VK_FORMAT_R16G16B16A16_SFLOAT, // ColorHDR + VK_FORMAT_R16G16B16A16_UNORM, // ColorClip VK_FORMAT_D32_SFLOAT_S8_UINT, // DepthStencil VK_FORMAT_R8_UNORM, // UNorm8 VK_FORMAT_R16_UINT, // UInt16 @@ -3820,14 +3822,14 @@ bool GSDeviceVK::CreateRenderPasses() } while (0) const VkFormat rt_format = LookupNativeFormat(GSTexture::Format::Color); - const VkFormat hdr_rt_format = LookupNativeFormat(GSTexture::Format::HDRColor); + const VkFormat colclip_rt_format = LookupNativeFormat(GSTexture::Format::ColorClip); const VkFormat depth_format = LookupNativeFormat(GSTexture::Format::DepthStencil); for (u32 rt = 0; rt < 2; rt++) { for (u32 ds = 0; ds < 2; ds++) { - for (u32 hdr = 0; hdr < 2; hdr++) + for (u32 colclip = 0; colclip < 2; colclip++) { for (u32 stencil = 0; stencil < 2; stencil++) { @@ -3841,12 +3843,12 @@ bool GSDeviceVK::CreateRenderPasses() opb++) { const VkFormat rp_rt_format = - (rt != 0) ? ((hdr != 0) ? hdr_rt_format : rt_format) : VK_FORMAT_UNDEFINED; + (rt != 0) ? ((colclip != 0) ? colclip_rt_format : rt_format) : VK_FORMAT_UNDEFINED; const VkFormat rp_depth_format = (ds != 0) ? depth_format : VK_FORMAT_UNDEFINED; const VkAttachmentLoadOp opc = (!stencil || !m_features.stencil_buffer) ? VK_ATTACHMENT_LOAD_OP_DONT_CARE : VK_ATTACHMENT_LOAD_OP_LOAD; - GET(m_tfx_render_pass[rt][ds][hdr][stencil][fbl][dsp][opa][opb], rp_rt_format, + GET(m_tfx_render_pass[rt][ds][colclip][stencil][fbl][dsp][opa][opb], rp_rt_format, rp_depth_format, (fbl != 0), (dsp != 0), static_cast(opa), static_cast(opb), static_cast(opc)); } @@ -4023,10 +4025,10 @@ bool GSDeviceVK::CompileConvertPipelines() (j >> 1) & 1u, (j >> 2) & 1u, (j >> 3) & 1u); } } - else if (i == ShaderConvert::HDR_INIT || i == ShaderConvert::HDR_RESOLVE) + else if (i == ShaderConvert::COLCLIP_INIT || i == ShaderConvert::COLCLIP_RESOLVE) { - const bool is_setup = i == ShaderConvert::HDR_INIT; - VkPipeline(&arr)[2][2] = *(is_setup ? &m_hdr_setup_pipelines : &m_hdr_finish_pipelines); + const bool is_setup = i == ShaderConvert::COLCLIP_INIT; + VkPipeline(&arr)[2][2] = *(is_setup ? &m_colclip_setup_pipelines : &m_colclip_finish_pipelines); for (u32 ds = 0; ds < 2; ds++) { for (u32 fbl = 0; fbl < 2; fbl++) @@ -4040,7 +4042,7 @@ bool GSDeviceVK::CompileConvertPipelines() if (!arr[ds][fbl]) return false; - Vulkan::SetObjectName(m_device, arr[ds][fbl], "HDR %s/copy pipeline (ds=%u, fbl=%u)", + Vulkan::SetObjectName(m_device, arr[ds][fbl], "ColorClip %s/copy pipeline (ds=%u, fbl=%u)", is_setup ? "setup" : "finish", i, ds, fbl); } } @@ -4610,10 +4612,10 @@ void GSDeviceVK::DestroyResources() { for (u32 fbl = 0; fbl < 2; fbl++) { - if (m_hdr_setup_pipelines[ds][fbl] != VK_NULL_HANDLE) - vkDestroyPipeline(m_device, m_hdr_setup_pipelines[ds][fbl], nullptr); - if (m_hdr_finish_pipelines[ds][fbl] != VK_NULL_HANDLE) - vkDestroyPipeline(m_device, m_hdr_finish_pipelines[ds][fbl], nullptr); + if (m_colclip_setup_pipelines[ds][fbl] != VK_NULL_HANDLE) + vkDestroyPipeline(m_device, m_colclip_setup_pipelines[ds][fbl], nullptr); + if (m_colclip_finish_pipelines[ds][fbl] != VK_NULL_HANDLE) + vkDestroyPipeline(m_device, m_colclip_finish_pipelines[ds][fbl], nullptr); } } for (u32 ds = 0; ds < 2; ds++) @@ -4782,7 +4784,7 @@ VkShaderModule GSDeviceVK::GetTFXFragmentShader(const GSHWDrawConfig::PSSelector AddMacro(ss, "PS_READ16_SRC", sel.real16src); AddMacro(ss, "PS_WRITE_RG", sel.write_rg); AddMacro(ss, "PS_FBMASK", sel.fbmask); - AddMacro(ss, "PS_HDR", sel.hdr); + AddMacro(ss, "PS_COLCLIP_HW", sel.colclip_hw); AddMacro(ss, "PS_RTA_CORRECTION", sel.rta_correction); AddMacro(ss, "PS_RTA_SRC_CORRECTION", sel.rta_source_correction); AddMacro(ss, "PS_DITHER", sel.dither); @@ -4838,7 +4840,7 @@ VkPipeline GSDeviceVK::CreateTFXPipeline(const PipelineSelector& p) else { gpb.SetRenderPass( - GetTFXRenderPass(p.rt, p.ds, p.ps.hdr, p.dss.date, + GetTFXRenderPass(p.rt, p.ds, p.ps.colclip_hw, p.dss.date, p.IsRTFeedbackLoop(), p.IsTestingAndSamplingDepth(), p.rt ? VK_ATTACHMENT_LOAD_OP_LOAD : VK_ATTACHMENT_LOAD_OP_DONT_CARE, p.ds ? VK_ATTACHMENT_LOAD_OP_LOAD : VK_ATTACHMENT_LOAD_OP_DONT_CARE), @@ -5595,7 +5597,7 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config) GSTextureVK* draw_rt = static_cast(config.rt); GSTextureVK* draw_ds = static_cast(config.ds); GSTextureVK* draw_rt_clone = nullptr; - GSTextureVK* hdr_rt = static_cast(g_gs_device->GetHDRTexture()); + GSTextureVK* colclip_rt = static_cast(g_gs_device->GetColorClipTexture()); // stream buffer in first, in case we need to exec SetVSConstantBuffer(config.cb_vs); @@ -5621,11 +5623,11 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config) GSTextureVK* date_image = nullptr; if (config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::PrimIDTracking) { - // If we have a HDR in progress, we need to use the HDR texture, but we can't check this later as there's a chicken/egg problem with the pipe setup. + // If we have a colclip in progress, we need to use the colclip texture, but we can't check this later as there's a chicken/egg problem with the pipe setup. GSTexture* backup_rt = config.rt; - if(hdr_rt) - config.rt = hdr_rt; + if(colclip_rt) + config.rt = colclip_rt; date_image = SetupPrimitiveTrackingDATE(config); if (!date_image) @@ -5645,15 +5647,15 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config) if (InRenderPass() && !pipe.IsRTFeedbackLoop() && (config.tex == m_current_render_target || config.tex == m_current_depth_target)) EndRenderPass(); - // now blit the hdr texture back to the original target - if (hdr_rt) + // now blit the colclip texture back to the original target + if (colclip_rt) { - if (config.hdr_mode == GSHWDrawConfig::HDRMode::EarlyResolve) + if (config.colclip_mode == GSHWDrawConfig::ColClipMode::EarlyResolve) { - GL_PUSH("Blit HDR back to RT"); + GL_PUSH("Blit ColorClip back to RT"); EndRenderPass(); - hdr_rt->TransitionToLayout(GSTextureVK::Layout::ShaderReadOnly); + colclip_rt->TransitionToLayout(GSTextureVK::Layout::ShaderReadOnly); draw_rt = static_cast(config.rt); OMSetRenderTargets(draw_rt, draw_ds, GSVector4i::loadh(rtsize), static_cast(pipe.feedback_loop_flags)); @@ -5681,22 +5683,22 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config) draw_rt->GetRect()); } - const GSVector4 drawareaf = GSVector4(config.hdr_update_area); + const GSVector4 drawareaf = GSVector4(config.colclip_update_area); const GSVector4 sRect(drawareaf / GSVector4(rtsize).xyxy()); - SetPipeline(m_hdr_finish_pipelines[pipe.ds][pipe.IsRTFeedbackLoop()]); - SetUtilityTexture(hdr_rt, m_point_sampler); + SetPipeline(m_colclip_finish_pipelines[pipe.ds][pipe.IsRTFeedbackLoop()]); + SetUtilityTexture(colclip_rt, m_point_sampler); DrawStretchRect(sRect, drawareaf, rtsize); g_perfmon.Put(GSPerfMon::TextureCopies, 1); - Recycle(hdr_rt); - g_gs_device->SetHDRTexture(nullptr); + Recycle(colclip_rt); + g_gs_device->SetColorClipTexture(nullptr); - hdr_rt = nullptr; + colclip_rt = nullptr; } else { - pipe.ps.hdr = 1; - draw_rt = hdr_rt; + pipe.ps.colclip_hw = 1; + draw_rt = colclip_rt; } } @@ -5726,7 +5728,7 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config) if (config.require_one_barrier && !m_features.texture_barrier) { // requires a copy of the RT - draw_rt_clone = static_cast(CreateTexture(rtsize.x, rtsize.y, 1, hdr_rt ? GSTexture::Format::HDRColor : GSTexture::Format::Color, true)); + draw_rt_clone = static_cast(CreateTexture(rtsize.x, rtsize.y, 1, colclip_rt ? GSTexture::Format::ColorClip : GSTexture::Format::Color, true)); if (draw_rt_clone) { EndRenderPass(); @@ -5739,17 +5741,17 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config) } } - // Switch to hdr target for colclip rendering - if (pipe.ps.hdr) + // Switch to colclip target for colclip hw rendering + if (pipe.ps.colclip_hw) { - if (!hdr_rt) + if (!colclip_rt) { - config.hdr_update_area = config.drawarea; + config.colclip_update_area = config.drawarea; EndRenderPass(); - hdr_rt = static_cast(CreateRenderTarget(rtsize.x, rtsize.y, GSTexture::Format::HDRColor, false)); - if (!hdr_rt) + colclip_rt = static_cast(CreateRenderTarget(rtsize.x, rtsize.y, GSTexture::Format::ColorClip, false)); + if (!colclip_rt) { - Console.WriteLn("Failed to allocate HDR render target, aborting draw."); + Console.WriteLn("Failed to allocate ColorClip render target, aborting draw."); if (date_image) Recycle(date_image); @@ -5757,13 +5759,13 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config) GL_POP(); return; } - g_gs_device->SetHDRTexture(static_cast(hdr_rt)); + g_gs_device->SetColorClipTexture(static_cast(colclip_rt)); - // propagate clear value through if the hdr render is the first + // propagate clear value through if the colclip render is the first if (draw_rt->GetState() == GSTexture::State::Cleared) { - hdr_rt->SetState(GSTexture::State::Cleared); - hdr_rt->SetClearColor(draw_rt->GetClearColor()); + colclip_rt->SetState(GSTexture::State::Cleared); + colclip_rt->SetClearColor(draw_rt->GetClearColor()); // If depth is cleared, we need to commit it, because we're only going to draw to the active part of the FB. if (draw_ds && draw_ds->GetState() == GSTexture::State::Cleared && !config.drawarea.eq(GSVector4i::loadh(rtsize))) @@ -5771,7 +5773,7 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config) } else if (draw_rt->GetState() == GSTexture::State::Dirty) { - GL_PUSH_("HDR Render Target Setup"); + GL_PUSH_("ColorClip Render Target Setup"); draw_rt->TransitionToLayout(GSTextureVK::Layout::ShaderReadOnly); } @@ -5779,7 +5781,7 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config) if (config.require_one_barrier && !m_features.texture_barrier) PSSetShaderResource(2, draw_rt, true); } - draw_rt = hdr_rt; + draw_rt = colclip_rt; } // clear texture binding when it's bound to RT or DS. @@ -5790,15 +5792,15 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config) } // render pass restart optimizations - if (hdr_rt && (config.hdr_mode == GSHWDrawConfig::HDRMode::ConvertAndResolve || config.hdr_mode == GSHWDrawConfig::HDRMode::ConvertOnly)) + if (colclip_rt && (config.colclip_mode == GSHWDrawConfig::ColClipMode::ConvertAndResolve || config.colclip_mode == GSHWDrawConfig::ColClipMode::ConvertOnly)) { - // HDR requires blitting. + // colclip hw requires blitting. EndRenderPass(); } else if (InRenderPass() && (m_current_render_target == draw_rt || m_current_depth_target == draw_ds)) { // avoid restarting the render pass just to switch from rt+depth to rt and vice versa - // keep the depth even if doing HDR draws, because the next draw will probably re-enable depth + // keep the depth even if doing colclip hw draws, because the next draw will probably re-enable depth if (!draw_rt && m_current_render_target && config.tex != m_current_render_target && m_current_render_target->GetSize() == draw_ds->GetSize()) { @@ -5817,15 +5819,15 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config) } // We don't need the very first barrier if this is the first draw after switching to feedback loop, - // because the layout change in itself enforces the execution dependency. HDR needs a barrier between - // setup and the first draw to read it. TODO: Make HDR use subpasses instead. + // because the layout change in itself enforces the execution dependency. colclip hw needs a barrier between + // setup and the first draw to read it. TODO: Make colclip hw use subpasses instead. // However, it turns out *not* doing this causes GPU resets on RDNA3, specifically Windows drivers. // Despite the layout changing enforcing the execution dependency between previous draws and the first // input attachment read, it still wants the region/fragment-local barrier... const bool skip_first_barrier = - (draw_rt && draw_rt->GetLayout() != GSTextureVK::Layout::FeedbackLoop && !pipe.ps.hdr && !IsDeviceAMD()); + (draw_rt && draw_rt->GetLayout() != GSTextureVK::Layout::FeedbackLoop && !pipe.ps.colclip_hw && !IsDeviceAMD()); OMSetRenderTargets(draw_rt, draw_ds, config.scissor, static_cast(pipe.feedback_loop_flags)); if (pipe.IsRTFeedbackLoop()) @@ -5843,14 +5845,14 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config) { const VkAttachmentLoadOp rt_op = GetLoadOpForTexture(draw_rt); const VkAttachmentLoadOp ds_op = GetLoadOpForTexture(draw_ds); - const VkRenderPass rp = GetTFXRenderPass(pipe.rt, pipe.ds, pipe.ps.hdr, + const VkRenderPass rp = GetTFXRenderPass(pipe.rt, pipe.ds, pipe.ps.colclip_hw, config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::Stencil, pipe.IsRTFeedbackLoop(), pipe.IsTestingAndSamplingDepth(), rt_op, ds_op); const bool is_clearing_rt = (rt_op == VK_ATTACHMENT_LOAD_OP_CLEAR || ds_op == VK_ATTACHMENT_LOAD_OP_CLEAR); - // Only draw to the active area of the HDR target. Except when depth is cleared, we need to use the full + // Only draw to the active area of the colclip hw target. Except when depth is cleared, we need to use the full // buffer size, otherwise it'll only clear the draw part of the depth buffer. - const GSVector4i render_area = (pipe.ps.hdr && (config.hdr_mode == GSHWDrawConfig::HDRMode::ConvertAndResolve) && ds_op != VK_ATTACHMENT_LOAD_OP_CLEAR) ? config.drawarea : + const GSVector4i render_area = (pipe.ps.colclip_hw && (config.colclip_mode == GSHWDrawConfig::ColClipMode::ConvertAndResolve) && ds_op != VK_ATTACHMENT_LOAD_OP_CLEAR) ? config.drawarea : GSVector4i::loadh(rtsize); if (is_clearing_rt) @@ -5861,9 +5863,9 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config) if (draw_rt) { GSVector4 clear_color = draw_rt->GetUNormClearColor(); - if (pipe.ps.hdr) + if (pipe.ps.colclip_hw) { - // Denormalize clear color for HDR. + // Denormalize clear color for hw colclip. clear_color *= GSVector4::cxpr(255.0f / 65535.0f, 255.0f / 65535.0f, 255.0f / 65535.0f, 1.0f); } GSVector4::store(&cvs[cv_count++].color, clear_color); @@ -5888,14 +5890,14 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config) vkCmdClearAttachments(m_current_command_buffer, 1, &ca, 1, &rc); } - // rt -> hdr blit if enabled - if (hdr_rt && (config.hdr_mode == GSHWDrawConfig::HDRMode::ConvertOnly || config.hdr_mode == GSHWDrawConfig::HDRMode::ConvertAndResolve) && config.rt->GetState() == GSTexture::State::Dirty) + // rt -> colclip hw blit if enabled + if (colclip_rt && (config.colclip_mode == GSHWDrawConfig::ColClipMode::ConvertOnly || config.colclip_mode == GSHWDrawConfig::ColClipMode::ConvertAndResolve) && config.rt->GetState() == GSTexture::State::Dirty) { OMSetRenderTargets(draw_rt, draw_ds, GSVector4i::loadh(rtsize), static_cast(pipe.feedback_loop_flags)); SetUtilityTexture(static_cast(config.rt), m_point_sampler); - SetPipeline(m_hdr_setup_pipelines[pipe.ds][pipe.IsRTFeedbackLoop()]); + SetPipeline(m_colclip_setup_pipelines[pipe.ds][pipe.IsRTFeedbackLoop()]); - const GSVector4 drawareaf = GSVector4((config.hdr_mode == GSHWDrawConfig::HDRMode::ConvertOnly) ? GSVector4i::loadh(rtsize) : config.drawarea); + const GSVector4 drawareaf = GSVector4((config.colclip_mode == GSHWDrawConfig::ColClipMode::ConvertOnly) ? GSVector4i::loadh(rtsize) : config.drawarea); const GSVector4 sRect(drawareaf / GSVector4(rtsize).xyxy()); DrawStretchRect(sRect, drawareaf, rtsize); g_perfmon.Put(GSPerfMon::TextureCopies, 1); @@ -5904,8 +5906,8 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config) OMSetRenderTargets(draw_rt, draw_ds, config.scissor, static_cast(pipe.feedback_loop_flags)); } - // VB/IB upload, if we did DATE setup and it's not HDR this has already been done - if (!date_image || hdr_rt) + // VB/IB upload, if we did DATE setup and it's not colclip hw this has already been done + if (!date_image || colclip_rt) UploadHWDrawVerticesAndIndices(config); // now we can do the actual draw @@ -5955,21 +5957,21 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config) if (date_image) Recycle(date_image); - // now blit the hdr texture back to the original target - if (hdr_rt) + // now blit the colclip texture back to the original target + if (colclip_rt) { - config.hdr_update_area = config.hdr_update_area.runion(config.drawarea); + config.colclip_update_area = config.colclip_update_area.runion(config.drawarea); - if ((config.hdr_mode == GSHWDrawConfig::HDRMode::ResolveOnly || config.hdr_mode == GSHWDrawConfig::HDRMode::ConvertAndResolve)) + if ((config.colclip_mode == GSHWDrawConfig::ColClipMode::ResolveOnly || config.colclip_mode == GSHWDrawConfig::ColClipMode::ConvertAndResolve)) { - GL_PUSH("Blit HDR back to RT"); + GL_PUSH("Blit ColorClip back to RT"); EndRenderPass(); - hdr_rt->TransitionToLayout(GSTextureVK::Layout::ShaderReadOnly); + colclip_rt->TransitionToLayout(GSTextureVK::Layout::ShaderReadOnly); draw_rt = static_cast(config.rt); - OMSetRenderTargets(draw_rt, draw_ds, (config.hdr_mode == GSHWDrawConfig::HDRMode::ResolveOnly) ? GSVector4i::loadh(rtsize) : config.scissor, static_cast(pipe.feedback_loop_flags)); + OMSetRenderTargets(draw_rt, draw_ds, (config.colclip_mode == GSHWDrawConfig::ColClipMode::ResolveOnly) ? GSVector4i::loadh(rtsize) : config.scissor, static_cast(pipe.feedback_loop_flags)); // if this target was cleared and never drawn to, perform the clear as part of the resolve here. if (draw_rt->GetState() == GSTexture::State::Cleared) @@ -5994,19 +5996,19 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config) draw_rt->GetRect()); } - const GSVector4 drawareaf = GSVector4(config.hdr_update_area); + const GSVector4 drawareaf = GSVector4(config.colclip_update_area); const GSVector4 sRect(drawareaf / GSVector4(rtsize).xyxy()); - SetPipeline(m_hdr_finish_pipelines[pipe.ds][pipe.IsRTFeedbackLoop()]); - SetUtilityTexture(hdr_rt, m_point_sampler); + SetPipeline(m_colclip_finish_pipelines[pipe.ds][pipe.IsRTFeedbackLoop()]); + SetUtilityTexture(colclip_rt, m_point_sampler); DrawStretchRect(sRect, drawareaf, rtsize); g_perfmon.Put(GSPerfMon::TextureCopies, 1); - Recycle(hdr_rt); - g_gs_device->SetHDRTexture(nullptr); + Recycle(colclip_rt); + g_gs_device->SetColorClipTexture(nullptr); } } - config.hdr_mode = GSHWDrawConfig::HDRMode::NoModify; + config.colclip_mode = GSHWDrawConfig::ColClipMode::NoModify; } void GSDeviceVK::UpdateHWPipelineSelector(GSHWDrawConfig& config, PipelineSelector& pipe) diff --git a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.h b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.h index 153d075a25..975b45f167 100644 --- a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.h +++ b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.h @@ -394,8 +394,8 @@ private: std::array m_color_copy{}; std::array m_merge{}; std::array m_interlace{}; - VkPipeline m_hdr_setup_pipelines[2][2] = {}; // [depth][feedback_loop] - VkPipeline m_hdr_finish_pipelines[2][2] = {}; // [depth][feedback_loop] + VkPipeline m_colclip_setup_pipelines[2][2] = {}; // [depth][feedback_loop] + VkPipeline m_colclip_finish_pipelines[2][2] = {}; // [depth][feedback_loop] VkRenderPass m_date_image_setup_render_passes[2][2] = {}; // [depth][clear] VkPipeline m_date_image_setup_pipelines[2][4] = {}; // [depth][datm] VkPipeline m_fxaa_pipeline = {}; @@ -415,7 +415,7 @@ private: VkRenderPass m_date_setup_render_pass = VK_NULL_HANDLE; VkRenderPass m_swap_chain_render_pass = VK_NULL_HANDLE; - VkRenderPass m_tfx_render_pass[2][2][2][3][2][2][3][3] = {}; // [rt][ds][hdr][date][fbl][dsp][rt_op][ds_op] + VkRenderPass m_tfx_render_pass[2][2][2][3][2][2][3][3] = {}; // [rt][ds][colclip][date][fbl][dsp][rt_op][ds_op] VkDescriptorSetLayout m_cas_ds_layout = VK_NULL_HANDLE; VkPipelineLayout m_cas_pipeline_layout = VK_NULL_HANDLE; @@ -486,10 +486,10 @@ public: /// Returns true if Vulkan is suitable as a default for the devices in the system. static bool IsSuitableDefaultRenderer(); - __fi VkRenderPass GetTFXRenderPass(bool rt, bool ds, bool hdr, bool stencil, bool fbl, bool dsp, + __fi VkRenderPass GetTFXRenderPass(bool rt, bool ds, bool colclip, bool stencil, bool fbl, bool dsp, VkAttachmentLoadOp rt_op, VkAttachmentLoadOp ds_op) const { - return m_tfx_render_pass[rt][ds][hdr][stencil][fbl][dsp][rt_op][ds_op]; + return m_tfx_render_pass[rt][ds][colclip][stencil][fbl][dsp][rt_op][ds_op]; } __fi VkSampler GetPointSampler() const { return m_point_sampler; } __fi VkSampler GetLinearSampler() const { return m_linear_sampler; } diff --git a/pcsx2/ShaderCacheVersion.h b/pcsx2/ShaderCacheVersion.h index 6e66cc63cb..75899116da 100644 --- a/pcsx2/ShaderCacheVersion.h +++ b/pcsx2/ShaderCacheVersion.h @@ -3,4 +3,4 @@ /// Version number for GS and other shaders. Increment whenever any of the contents of the /// shaders change, to invalidate the cache. -static constexpr u32 SHADER_CACHE_VERSION = 62; +static constexpr u32 SHADER_CACHE_VERSION = 63; From 93aae2859340c5ed423e759c5a52a4a31fe34354 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Tue, 15 Apr 2025 06:53:53 +0300 Subject: [PATCH 146/162] GS: Polish spacing and comments --- pcsx2/GS/Renderers/DX11/GSDevice11.cpp | 12 ------------ .../GS/Renderers/HW/GSTextureReplacementLoaders.cpp | 8 ++++---- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/pcsx2/GS/Renderers/DX11/GSDevice11.cpp b/pcsx2/GS/Renderers/DX11/GSDevice11.cpp index 09f1097b77..1c2ecdca7d 100644 --- a/pcsx2/GS/Renderers/DX11/GSDevice11.cpp +++ b/pcsx2/GS/Renderers/DX11/GSDevice11.cpp @@ -1307,7 +1307,6 @@ void GSDevice11::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* else { ds = GSVector2i(m_window_info.surface_width, m_window_info.surface_height); - } // om @@ -1318,8 +1317,6 @@ void GSDevice11::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* OMSetBlendState(bs, 0); - - // ia const float left = dRect.x * 2 / ds.x - 1.0f; @@ -1336,7 +1333,6 @@ void GSDevice11::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* }; - IASetVertexBuffer(vertices, sizeof(vertices[0]), std::size(vertices)); IASetInputLayout(m_convert.il.get()); IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); @@ -1345,7 +1341,6 @@ void GSDevice11::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* VSSetShader(m_convert.vs.get(), nullptr); - // ps PSSetShaderResource(0, sTex); @@ -1382,8 +1377,6 @@ void GSDevice11::PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* OMSetDepthStencilState(m_convert.dss.get(), 0); OMSetBlendState(m_convert.bs[D3D11_COLOR_WRITE_ENABLE_ALL].get(), 0); - - // ia const float left = dRect.x * 2 / ds.x - 1.0f; @@ -1399,8 +1392,6 @@ void GSDevice11::PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* {GSVector4(right, bottom, 0.5f, 1.0f), GSVector2(sRect.z, sRect.w)}, }; - - IASetVertexBuffer(vertices, sizeof(vertices[0]), std::size(vertices)); IASetInputLayout(m_present.il.get()); IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); @@ -1409,7 +1400,6 @@ void GSDevice11::PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* VSSetShader(m_present.vs.get(), nullptr); - // ps PSSetShaderResource(0, sTex); @@ -2182,8 +2172,6 @@ void GSDevice11::SetupDATE(GSTexture* rt, GSTexture* ds, const GSVertexPT1* vert // DrawPrimitive(); - - // } void* GSDevice11::IAMapVertexBuffer(u32 stride, u32 count) diff --git a/pcsx2/GS/Renderers/HW/GSTextureReplacementLoaders.cpp b/pcsx2/GS/Renderers/HW/GSTextureReplacementLoaders.cpp index e94a4e67cd..212e02326f 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureReplacementLoaders.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureReplacementLoaders.cpp @@ -465,7 +465,7 @@ static bool ParseDDSHeader(std::FILE* fp, DDSLoadInfo* info) } const GSDevice::FeatureSupport features(g_gs_device->Features()); - if (header.ddspf.dwFourCC == MAKEFOURCC('D', 'X', 'T', '1') || dxt10_format == 71) + if (header.ddspf.dwFourCC == MAKEFOURCC('D', 'X', 'T', '1') || dxt10_format == 71 /*DXGI_FORMAT_BC1_UNORM*/) { info->format = GSTexture::Format::BC1; info->block_size = 4; @@ -473,7 +473,7 @@ static bool ParseDDSHeader(std::FILE* fp, DDSLoadInfo* info) if (!features.dxt_textures) return false; } - else if (header.ddspf.dwFourCC == MAKEFOURCC('D', 'X', 'T', '2') || header.ddspf.dwFourCC == MAKEFOURCC('D', 'X', 'T', '3') || dxt10_format == 74) + else if (header.ddspf.dwFourCC == MAKEFOURCC('D', 'X', 'T', '2') || header.ddspf.dwFourCC == MAKEFOURCC('D', 'X', 'T', '3') || dxt10_format == 74 /*DXGI_FORMAT_BC2_UNORM*/) { info->format = GSTexture::Format::BC2; info->block_size = 4; @@ -481,7 +481,7 @@ static bool ParseDDSHeader(std::FILE* fp, DDSLoadInfo* info) if (!features.dxt_textures) return false; } - else if (header.ddspf.dwFourCC == MAKEFOURCC('D', 'X', 'T', '4') || header.ddspf.dwFourCC == MAKEFOURCC('D', 'X', 'T', '5') || dxt10_format == 77) + else if (header.ddspf.dwFourCC == MAKEFOURCC('D', 'X', 'T', '4') || header.ddspf.dwFourCC == MAKEFOURCC('D', 'X', 'T', '5') || dxt10_format == 77 /*DXGI_FORMAT_BC3_UNORM*/) { info->format = GSTexture::Format::BC3; info->block_size = 4; @@ -489,7 +489,7 @@ static bool ParseDDSHeader(std::FILE* fp, DDSLoadInfo* info) if (!features.dxt_textures) return false; } - else if (dxt10_format == 98) + else if (dxt10_format == 98 /*DXGI_FORMAT_BC7_UNORM*/) { info->format = GSTexture::Format::BC7; info->block_size = 4; From 00f19c9777972ca79c1b455224dd981597fa7ff8 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Tue, 15 Apr 2025 06:54:52 +0300 Subject: [PATCH 147/162] GS: Fix displays that are currently unplugged (disabled) from throwing unnecessary warnings --- pcsx2/GS/Renderers/DX11/D3D.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pcsx2/GS/Renderers/DX11/D3D.cpp b/pcsx2/GS/Renderers/DX11/D3D.cpp index 88f85c4d28..7cc6b4463a 100644 --- a/pcsx2/GS/Renderers/DX11/D3D.cpp +++ b/pcsx2/GS/Renderers/DX11/D3D.cpp @@ -87,6 +87,7 @@ std::vector D3D::GetAdapterInfo(IDXGIFactory5* factory) ai.max_upscale_multiplier = GSGetMaxUpscaleMultiplier(ai.max_texture_size); wil::com_ptr_nothrow output; + // Only check the first output, which would be the primary display (if any is connected) if (SUCCEEDED(hr = adapter->EnumOutputs(0, &output))) { UINT num_modes = 0; @@ -111,7 +112,7 @@ std::vector D3D::GetAdapterInfo(IDXGIFactory5* factory) ERROR_LOG("GetDisplayModeList() failed: {:08X}", static_cast(hr)); } } - else + else if (hr != DXGI_ERROR_NOT_FOUND) { ERROR_LOG("EnumOutputs() failed: {:08X}", static_cast(hr)); } From fec3fe3b6b96036d188c24b982ae380034ff6093 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Wed, 16 Apr 2025 16:56:09 +0300 Subject: [PATCH 148/162] GS: Add DX11 shaders debug names --- pcsx2/GS/Renderers/DX11/D3D.cpp | 2 +- pcsx2/GS/Renderers/DX11/D3D11ShaderCache.cpp | 25 ++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/pcsx2/GS/Renderers/DX11/D3D.cpp b/pcsx2/GS/Renderers/DX11/D3D.cpp index 7cc6b4463a..faa4d251f4 100644 --- a/pcsx2/GS/Renderers/DX11/D3D.cpp +++ b/pcsx2/GS/Renderers/DX11/D3D.cpp @@ -516,7 +516,7 @@ wil::com_ptr_nothrow D3D::CompileShader(D3D::ShaderType type, D3D_FEAT } static constexpr UINT flags_non_debug = D3DCOMPILE_OPTIMIZATION_LEVEL3; - static constexpr UINT flags_debug = D3DCOMPILE_SKIP_OPTIMIZATION | D3DCOMPILE_DEBUG; + static constexpr UINT flags_debug = D3DCOMPILE_SKIP_OPTIMIZATION | D3DCOMPILE_DEBUG | D3DCOMPILE_DEBUG_NAME_FOR_SOURCE; wil::com_ptr_nothrow blob; wil::com_ptr_nothrow error_blob; diff --git a/pcsx2/GS/Renderers/DX11/D3D11ShaderCache.cpp b/pcsx2/GS/Renderers/DX11/D3D11ShaderCache.cpp index 2fb4481a4d..3e0b7754a1 100644 --- a/pcsx2/GS/Renderers/DX11/D3D11ShaderCache.cpp +++ b/pcsx2/GS/Renderers/DX11/D3D11ShaderCache.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-3.0+ #include "GS/Renderers/DX11/D3D11ShaderCache.h" +#include "GS/Renderers/DX11/GSDevice11.h" #include "GS/GS.h" #include "Config.h" @@ -300,6 +301,12 @@ wil::com_ptr_nothrow D3D11ShaderCache::GetVertexShader(ID3D1 return {}; } + const char* shader_name = entry_point; // Ideally we'd feed in a proper name + if (shader_name) + { + GSDevice11::SetD3DDebugObjectName(shader.get(), shader_name); + } + return shader; } @@ -320,6 +327,12 @@ bool D3D11ShaderCache::GetVertexShaderAndInputLayout(ID3D11Device* device, ID3D1 return {}; } + const char* shader_name = entry_point; // Ideally we'd feed in a proper name + if (shader_name) + { + GSDevice11::SetD3DDebugObjectName(actual_vs.get(), shader_name); + } + hr = device->CreateInputLayout(layout, layout_size, blob->GetBufferPointer(), blob->GetBufferSize(), il); if (FAILED(hr)) { @@ -348,6 +361,12 @@ wil::com_ptr_nothrow D3D11ShaderCache::GetPixelShader(ID3D11D return {}; } + const char* shader_name = entry_point; // Ideally we'd feed in a proper name + if (shader_name) + { + GSDevice11::SetD3DDebugObjectName(shader.get(), shader_name); + } + return shader; } @@ -368,6 +387,12 @@ wil::com_ptr_nothrow D3D11ShaderCache::GetComputeShader(ID3 return {}; } + const char* shader_name = entry_point; // Ideally we'd feed in a proper name + if (shader_name) + { + GSDevice11::SetD3DDebugObjectName(shader.get(), shader_name); + } + return shader; } From a170c7ccb19bf068c3e0904312a08c6593ddc2c7 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Wed, 16 Apr 2025 19:43:55 +0300 Subject: [PATCH 149/162] GS: Fix DX12 m_color_copy shaders not applying the right RTA_CORRECTION offset (see the VK implementation, which is identical except for this issue) --- pcsx2/GS/Renderers/DX12/GSDevice12.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pcsx2/GS/Renderers/DX12/GSDevice12.cpp b/pcsx2/GS/Renderers/DX12/GSDevice12.cpp index c6163227d8..d3e0963917 100644 --- a/pcsx2/GS/Renderers/DX12/GSDevice12.cpp +++ b/pcsx2/GS/Renderers/DX12/GSDevice12.cpp @@ -1428,9 +1428,10 @@ void GSDevice12::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* GL_PUSH("ColorCopy Red:%d Green:%d Blue:%d Alpha:%d", red, green, blue, alpha); const u32 index = (red ? 1 : 0) | (green ? 2 : 0) | (blue ? 4 : 0) | (alpha ? 8 : 0); + int rta_offset = (shader == ShaderConvert::RTA_CORRECTION) ? 16 : 0; const bool allow_discard = (index == 0xf); DoStretchRect(static_cast(sTex), sRect, static_cast(dTex), dRect, - m_color_copy[index].get(), false, allow_discard); + m_color_copy[index + rta_offset].get(), false, allow_discard); } void GSDevice12::PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, From 718adda74932d3e3123ed3abecd75f5e5cc18558 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Wed, 16 Apr 2025 20:16:53 +0300 Subject: [PATCH 150/162] GS: Fix DX12 setting the object names for the wrong pipelines (possibly causing random memory writes), also fix some bad naming --- pcsx2/GS/Renderers/DX12/GSDevice12.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pcsx2/GS/Renderers/DX12/GSDevice12.cpp b/pcsx2/GS/Renderers/DX12/GSDevice12.cpp index d3e0963917..749c4306da 100644 --- a/pcsx2/GS/Renderers/DX12/GSDevice12.cpp +++ b/pcsx2/GS/Renderers/DX12/GSDevice12.cpp @@ -2603,8 +2603,8 @@ bool GSDevice12::CompilePresentPipelines() return false; } - ComPtr m_convert_vs = GetUtilityVertexShader(*shader, "vs_main"); - if (!m_convert_vs) + ComPtr vs = GetUtilityVertexShader(*shader, "vs_main"); + if (!vs) return false; D3D12::GraphicsPipelineBuilder gpb; @@ -2612,7 +2612,7 @@ bool GSDevice12::CompilePresentPipelines() AddUtilityVertexAttributes(gpb); gpb.SetNoCullRasterizationState(); gpb.SetNoBlendingState(); - gpb.SetVertexShader(m_convert_vs.get()); + gpb.SetVertexShader(vs.get()); gpb.SetDepthState(false, false, D3D12_COMPARISON_FUNC_ALWAYS); gpb.SetNoStencilState(); gpb.SetRenderTarget(0, DXGI_FORMAT_R8G8B8A8_UNORM); @@ -2668,7 +2668,7 @@ bool GSDevice12::CompileInterlacePipelines() if (!m_interlace[i]) return false; - D3D12::SetObjectName(m_convert[i].get(), TinyString::from_format("Interlace pipeline {}", static_cast(i))); + D3D12::SetObjectName(m_interlace[i].get(), TinyString::from_format("Interlace pipeline {}", static_cast(i))); } return true; @@ -2705,7 +2705,7 @@ bool GSDevice12::CompileMergePipelines() if (!m_merge[i]) return false; - D3D12::SetObjectName(m_convert[i].get(), TinyString::from_format("Merge pipeline {}", i)); + D3D12::SetObjectName(m_merge[i].get(), TinyString::from_format("Merge pipeline {}", i)); } return true; From bb48110f953130f9d71e10719141dc64cbf6915a Mon Sep 17 00:00:00 2001 From: Filoppi Date: Wed, 16 Apr 2025 20:42:39 +0300 Subject: [PATCH 151/162] GS: Fix VK also naming the wrong pipelines --- pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp index 0f997951ce..c1441d16ec 100644 --- a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp +++ b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp @@ -4198,7 +4198,7 @@ bool GSDeviceVK::CompileInterlacePipelines() if (!m_interlace[i]) return false; - Vulkan::SetObjectName(m_device, m_convert[i], "Interlace pipeline %d", i); + Vulkan::SetObjectName(m_device, m_interlace[i], "Interlace pipeline %d", i); } return true; @@ -4250,7 +4250,7 @@ bool GSDeviceVK::CompileMergePipelines() if (!m_merge[i]) return false; - Vulkan::SetObjectName(m_device, m_convert[i], "Merge pipeline %d", i); + Vulkan::SetObjectName(m_device, m_merge[i], "Merge pipeline %d", i); } return true; From f485cf8ebca7d64a6636c6d1e32346dc3a33b67b Mon Sep 17 00:00:00 2001 From: Filoppi Date: Wed, 16 Apr 2025 21:51:07 +0300 Subject: [PATCH 152/162] GS: fix more badly named debug objects --- pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp index c1441d16ec..232fe1176a 100644 --- a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp +++ b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp @@ -3766,7 +3766,7 @@ bool GSDeviceVK::CreatePipelineLayouts() plb.AddDescriptorSet(m_utility_ds_layout); if ((m_utility_pipeline_layout = plb.Create(dev)) == VK_NULL_HANDLE) return false; - Vulkan::SetObjectName(dev, m_utility_ds_layout, "Convert pipeline layout"); + Vulkan::SetObjectName(dev, m_utility_pipeline_layout, "Convert pipeline layout"); ////////////////////////////////////////////////////////////////////////// // Draw/TFX Pipeline Layout @@ -4969,7 +4969,7 @@ void GSDeviceVK::InitializeState() Vulkan::SetObjectName(m_device, m_point_sampler, "Point sampler"); m_linear_sampler = GetSampler(GSHWDrawConfig::SamplerSelector::Linear()); if (m_linear_sampler) - Vulkan::SetObjectName(m_device, m_point_sampler, "Linear sampler"); + Vulkan::SetObjectName(m_device, m_linear_sampler, "Linear sampler"); m_tfx_sampler_sel = GSHWDrawConfig::SamplerSelector::Point().key; m_tfx_sampler = m_point_sampler; From 2109df04ca4ad94c191a3e8483f614bba7d40f0a Mon Sep 17 00:00:00 2001 From: Filoppi Date: Fri, 18 Apr 2025 02:25:32 +0300 Subject: [PATCH 153/162] GS: Remove duplicate shader creation for RTA correction. The same shader would have been created just above. --- pcsx2/GS/Renderers/DX12/GSDevice12.cpp | 7 ------- pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp | 8 -------- 2 files changed, 15 deletions(-) diff --git a/pcsx2/GS/Renderers/DX12/GSDevice12.cpp b/pcsx2/GS/Renderers/DX12/GSDevice12.cpp index 749c4306da..dc0cf04361 100644 --- a/pcsx2/GS/Renderers/DX12/GSDevice12.cpp +++ b/pcsx2/GS/Renderers/DX12/GSDevice12.cpp @@ -2525,13 +2525,6 @@ bool GSDevice12::CompileConvertPipelines() // compile color copy pipelines gpb.SetRenderTarget(0, DXGI_FORMAT_R8G8B8A8_UNORM); gpb.SetDepthStencilFormat(DXGI_FORMAT_UNKNOWN); - - ComPtr ps(GetUtilityPixelShader(*shader, shaderName(i))); - if (!ps) - return false; - - gpb.SetPixelShader(ps.get()); - for (u32 j = 16; j < 32; j++) { pxAssert(!m_color_copy[j]); diff --git a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp index 232fe1176a..47e1695833 100644 --- a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp +++ b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp @@ -4001,15 +4001,7 @@ bool GSDeviceVK::CompileConvertPipelines() } else if (i == ShaderConvert::RTA_CORRECTION) { - // compile color copy pipelines gpb.SetRenderPass(m_utility_color_render_pass_discard, 0); - VkShaderModule ps = GetUtilityFragmentShader(*shader, shaderName(i)); - if (ps == VK_NULL_HANDLE) - return false; - - ScopedGuard ps_guard([this, &ps]() { vkDestroyShaderModule(m_device, ps, nullptr); }); - gpb.SetFragmentShader(ps); - for (u32 j = 16; j < 32; j++) { pxAssert(!m_color_copy[j]); From a545982a280419054f83565ef5b18f4676d55c16 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Fri, 18 Apr 2025 02:54:06 +0300 Subject: [PATCH 154/162] GS: Fix crash if CAS pipeline failed to compile (it'd crash when the user enables CAS, which is still enough of a good reason to make the whole rendering backend fail to initialize) --- pcsx2/GS/Renderers/DX12/GSDevice12.cpp | 4 +++- pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pcsx2/GS/Renderers/DX12/GSDevice12.cpp b/pcsx2/GS/Renderers/DX12/GSDevice12.cpp index dc0cf04361..678df9e6a6 100644 --- a/pcsx2/GS/Renderers/DX12/GSDevice12.cpp +++ b/pcsx2/GS/Renderers/DX12/GSDevice12.cpp @@ -739,7 +739,9 @@ bool GSDevice12::Create(GSVSyncMode vsync_mode, bool allow_present_throttle) return false; } - CompileCASPipelines(); + if (!CompileCASPipelines()) + return false; + if (!CompileImGuiPipeline()) return false; diff --git a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp index 47e1695833..762f65ed5e 100644 --- a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp +++ b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp @@ -2104,7 +2104,8 @@ bool GSDeviceVK::Create(GSVSyncMode vsync_mode, bool allow_present_throttle) return false; } - CompileCASPipelines(); + if (!CompileCASPipelines()) + return false; if (!CompileImGuiPipeline()) return false; From a19bb9104152f8eb95675822d4646e9a2f0c6d57 Mon Sep 17 00:00:00 2001 From: GregoireLD <48733955+GregoireLD@users.noreply.github.com> Date: Fri, 18 Apr 2025 19:33:51 +0200 Subject: [PATCH 155/162] GameDB: Add a new Ico version (#11103) --- bin/resources/GameIndex.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/bin/resources/GameIndex.yaml b/bin/resources/GameIndex.yaml index 316928f7c0..d3b11ee3f5 100644 --- a/bin/resources/GameIndex.yaml +++ b/bin/resources/GameIndex.yaml @@ -4578,6 +4578,17 @@ SCED-55120: SCED-55432: name: "Buzz! Junior - Ace Racers [Demo]" region: "PAL-M15" +SCES-00000: + name: "Ico [Preview]" + region: "PAL-M5" + compat: 5 + clampModes: + eeClampMode: 2 # Otherwise freezes in various spots, check full intro. + vuClampMode: 1 # Otherwise camera does not focus correctly on main character. + gsHWFixes: + mipmap: 1 + halfPixelOffset: 1 # Fixes effect misalignment. + moveHandler: "MV_Ico" # Fixes depth buffer post-processing. SCES-50000: name: "Ridge Racer V" region: "PAL-M5" From 717775d9abdb5ad7d56f9510e45c86da04d47751 Mon Sep 17 00:00:00 2001 From: PCSX2 Bot Date: Sat, 19 Apr 2025 00:02:19 +0000 Subject: [PATCH 156/162] [ci skip] Qt: Update Base Translation. --- pcsx2-qt/Translations/pcsx2-qt_en.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pcsx2-qt/Translations/pcsx2-qt_en.ts b/pcsx2-qt/Translations/pcsx2-qt_en.ts index 5d07434bc1..d50ba17053 100644 --- a/pcsx2-qt/Translations/pcsx2-qt_en.ts +++ b/pcsx2-qt/Translations/pcsx2-qt_en.ts @@ -10879,24 +10879,24 @@ Do you want to load this save and continue? - + Stencil buffers and texture barriers are both unavailable, this will break some graphical effects. - + Spin GPU During Readbacks is enabled, but calibrated timestamps are unavailable. This might be really slow. - + Your system has the "OpenCL, OpenGL, and Vulkan Compatibility Pack" installed. This Vulkan driver crashes PCSX2 on some GPUs. To use the Vulkan renderer, you should remove this app package. - + The Vulkan renderer was automatically selected, but no compatible devices were found. You should update all graphics drivers in your system, including any integrated GPUs to use the Vulkan renderer. @@ -19266,12 +19266,12 @@ Ejecting {3} and replacing it with {2}. SDLInputSource - + SDL3 Migration - + As part of our upgrade to SDL3, we've had to migrate your binds Your controller did not match the Xbox layout and may need rebinding Please verify your controller settings and amend if required From 842190e15eebeb8b7c9f5f5ddb64a61df8133695 Mon Sep 17 00:00:00 2001 From: lightningterror <18107717+lightningterror@users.noreply.github.com> Date: Sat, 19 Apr 2025 10:23:33 +0200 Subject: [PATCH 157/162] CDVD: Adjust ram requirements when precaching on linux/Mac. --- common/Darwin/DarwinMisc.cpp | 21 +++++++++++++++++++++ common/HostSys.h | 3 --- common/Linux/LnxMisc.cpp | 10 ++++++++++ pcsx2/CDVD/ThreadedFileReader.cpp | 17 +---------------- 4 files changed, 32 insertions(+), 19 deletions(-) diff --git a/common/Darwin/DarwinMisc.cpp b/common/Darwin/DarwinMisc.cpp index 400c26c9b5..5f12d186f7 100644 --- a/common/Darwin/DarwinMisc.cpp +++ b/common/Darwin/DarwinMisc.cpp @@ -50,6 +50,27 @@ u64 GetPhysicalMemory() return getmem; } +u64 GetAvailablePhysicalMemory() +{ + const mach_port_t host_port = mach_host_self(); + vm_size_t page_size; + + if (host_page_size(host_port, &page_size) != KERN_SUCCESS) + return 0; + + vm_statistics64_data_t vm_stat; + mach_msg_type_number_t host_size = sizeof(vm_statistics64_data_t) / sizeof(integer_t); + + if (host_statistics64(host_port, HOST_VM_INFO, reinterpret_cast(&vm_stat), &host_size) != KERN_SUCCESS) + return 0; + + const u64 free_pages = static_cast(vm_stat.free_count); + const u64 inactive_pages = static_cast(vm_stat.inactive_count); + const u64 get_available_mem = (free_pages + inactive_pages) * page_size; + + return get_available_mem; +} + static mach_timebase_info_data_t s_timebase_info; static const u64 tickfreq = []() { if (mach_timebase_info(&s_timebase_info) != KERN_SUCCESS) diff --git a/common/HostSys.h b/common/HostSys.h index f79c36fb70..08eb31232c 100644 --- a/common/HostSys.h +++ b/common/HostSys.h @@ -181,10 +181,7 @@ private: extern u64 GetTickFrequency(); extern u64 GetCPUTicks(); extern u64 GetPhysicalMemory(); -#ifdef _WIN32 -// TODO: Someone do linux/mac. extern u64 GetAvailablePhysicalMemory(); -#endif /// Spin for a short period of time (call while spinning waiting for a lock) /// Returns the approximate number of ns that passed extern u32 ShortSpin(); diff --git a/common/Linux/LnxMisc.cpp b/common/Linux/LnxMisc.cpp index acefebe701..2ef073932b 100644 --- a/common/Linux/LnxMisc.cpp +++ b/common/Linux/LnxMisc.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -40,6 +41,15 @@ u64 GetPhysicalMemory() return pages * getpagesize(); } +u64 GetAvailablePhysicalMemory() +{ + struct sysinfo info; + if (sysinfo(&info) != 0) + return 0; + + return static_cast(info.freeram) * info.mem_unit; +} + u64 GetTickFrequency() { return 1000000000; // unix measures in nanoseconds diff --git a/pcsx2/CDVD/ThreadedFileReader.cpp b/pcsx2/CDVD/ThreadedFileReader.cpp index 550b2794a7..1e9d9273aa 100644 --- a/pcsx2/CDVD/ThreadedFileReader.cpp +++ b/pcsx2/CDVD/ThreadedFileReader.cpp @@ -266,12 +266,11 @@ bool ThreadedFileReader::Precache2(ProgressCallback* progress, Error* error) bool ThreadedFileReader::CheckAvailableMemoryForPrecaching(u64 required_size, Error* error) { -#ifdef _WIN32 // We want to check available physical memory instead of total. const u64 memory_available = GetAvailablePhysicalMemory(); // Reserve 2GB of available memory for headroom. constexpr u64 memory_reserve = 2147483648; - const u64 max_precache_size = std::max(0LL, static_cast(memory_available - memory_reserve)); + const u64 max_precache_size = std::max(s64{0}, static_cast(memory_available - memory_reserve)); if (required_size > max_precache_size) { @@ -280,20 +279,6 @@ bool ThreadedFileReader::CheckAvailableMemoryForPrecaching(u64 required_size, Er (required_size + memory_reserve) / _1gb); return false; } -#else - // Don't allow precaching to use more than 50% of system memory. - // Hopefully nobody's running 2-4GB potatoes anymore.... - const u64 memory_size = GetPhysicalMemory(); - const u64 max_precache_size = memory_size / 2; - - if (required_size > max_precache_size) - { - Error::SetStringFmt(error, - TRANSLATE_FS("CDVD", "Required memory ({}GB) is the above the maximum allowed ({}GB)."), - required_size / _1gb, max_precache_size / _1gb); - return false; - } -#endif return true; } From 3afa9ca403a9b6907957f8c049bef958563288be Mon Sep 17 00:00:00 2001 From: lightningterror <18107717+lightningterror@users.noreply.github.com> Date: Sat, 19 Apr 2025 10:28:23 +0200 Subject: [PATCH 158/162] VMManager: Add available ram info in log. --- pcsx2/VMManager.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pcsx2/VMManager.cpp b/pcsx2/VMManager.cpp index e48cc3898b..95c9e149ff 100644 --- a/pcsx2/VMManager.cpp +++ b/pcsx2/VMManager.cpp @@ -2504,8 +2504,10 @@ void VMManager::LogCPUCapabilities() Console.WriteLnFmt( " Operating System = {}\n" - " Physical RAM = {} MB", + " Available RAM = {} MB\n" + " Physical RAM = {} MB\n", GetOSVersionString(), + GetAvailablePhysicalMemory() / _1mb, GetPhysicalMemory() / _1mb); Console.WriteLnFmt(" Processor = {}", cpuinfo_get_package(0)->name); From 80ca5ea5fd41117e3b54b3aa79b6e627d0bd9a23 Mon Sep 17 00:00:00 2001 From: lightningterror <18107717+lightningterror@users.noreply.github.com> Date: Fri, 18 Apr 2025 20:16:24 +0200 Subject: [PATCH 159/162] GS/HW: Rename GSC_Tekken5 to GSC_NamcoGames. Multiple games use it. --- bin/resources/GameIndex.yaml | 36 +++++++++++++++--------------- pcsx2/GS/Renderers/HW/GSHwHack.cpp | 8 +++---- pcsx2/GS/Renderers/HW/GSHwHack.h | 2 +- 3 files changed, 22 insertions(+), 24 deletions(-) diff --git a/bin/resources/GameIndex.yaml b/bin/resources/GameIndex.yaml index d3b11ee3f5..13f27b7b7a 100644 --- a/bin/resources/GameIndex.yaml +++ b/bin/resources/GameIndex.yaml @@ -1274,7 +1274,7 @@ SCAJ-10015: gsHWFixes: alignSprite: 1 # Fixes vertical lines. textureInsideRT: 1 # Fixes post effects. - getSkipCount: "GSC_Tekken5" # Fixes upscaling grid, same engine. + getSkipCount: "GSC_NamcoGames" # Fixes upscaling grid, same engine. SCAJ-20001: name: "Ratchet & Clank" region: "NTSC-Unk" @@ -1987,7 +1987,7 @@ SCAJ-20125: alignSprite: 1 halfPixelOffset: 4 # Align post. nativeScaling: 1 # Fixes depth of field effect. - getSkipCount: "GSC_Tekken5" + getSkipCount: "GSC_NamcoGames" SCAJ-20126: name: "Tekken 5" region: "NTSC-Unk" @@ -1998,7 +1998,7 @@ SCAJ-20126: alignSprite: 1 halfPixelOffset: 4 # Align post. nativeScaling: 1 # Fixes depth of field effect. - getSkipCount: "GSC_Tekken5" + getSkipCount: "GSC_NamcoGames" SCAJ-20127: name: "EyeToy - Play 2 [with Camera]" region: "NTSC-Unk" @@ -2248,7 +2248,7 @@ SCAJ-20163: gsHWFixes: halfPixelOffset: 2 # Fixes ghosting. autoFlush: 2 # Fixes post lighting. - getSkipCount: "GSC_Tekken5" # Fixes upscaling grid, same engine. + getSkipCount: "GSC_NamcoGames" # Fixes upscaling grid, same engine. SCAJ-20164: name: "Kingdom Hearts II" region: "NTSC-Unk" @@ -2477,7 +2477,7 @@ SCAJ-20199: alignSprite: 1 halfPixelOffset: 4 # Align post. nativeScaling: 1 # Fixes depth of field effect. - getSkipCount: "GSC_Tekken5" + getSkipCount: "GSC_NamcoGames" SCAJ-25002: name: "Shinobi" region: "NTSC-Unk" @@ -4188,7 +4188,7 @@ SCED-53538: alignSprite: 1 halfPixelOffset: 4 # Align post. nativeScaling: 1 # Fixes depth of field effect. - getSkipCount: "GSC_Tekken5" + getSkipCount: "GSC_NamcoGames" SCED-53611: name: "Official PlayStation 2 Magazine - German Kids Special" region: "PAL-G" @@ -5827,7 +5827,7 @@ SCES-53202: alignSprite: 1 halfPixelOffset: 4 # Align post. nativeScaling: 1 # Fixes depth of field effect. - getSkipCount: "GSC_Tekken5" + getSkipCount: "GSC_NamcoGames" SCES-53247: name: "WRC Rally Evolved" region: "PAL-M8" @@ -7285,7 +7285,7 @@ SCKA-20049: alignSprite: 1 halfPixelOffset: 4 # Align post. nativeScaling: 1 # Fixes depth of field effect. - getSkipCount: "GSC_Tekken5" + getSkipCount: "GSC_NamcoGames" SCKA-20050: name: "Tales of Legendia" region: "NTSC-K" @@ -7512,7 +7512,7 @@ SCKA-20081: alignSprite: 1 halfPixelOffset: 4 # Align post. nativeScaling: 1 # Fixes depth of field effect. - getSkipCount: "GSC_Tekken5" + getSkipCount: "GSC_NamcoGames" SCKA-20082: name: "Ace Combat 5 - The Unsung War [PlayStation 2 Big Hit Series]" region: "NTSC-K" @@ -54888,7 +54888,7 @@ SLPS-20485: gsHWFixes: alignSprite: 1 # Fixes vertical lines. textureInsideRT: 1 # Fixes post effects. - getSkipCount: "GSC_Tekken5" # Fixes upscaling grid, same engine. + getSkipCount: "GSC_NamcoGames" # Fixes upscaling grid, same engine. SLPS-20486: name: "太鼓の達人 ドカッ!と大盛り七代目 [ソフト単体]" name-sort: "たいこのたつじん どかっ!とおおもりななだいめ [そふとたんたい]" @@ -54897,7 +54897,7 @@ SLPS-20486: gsHWFixes: alignSprite: 1 # Fixes vertical lines. textureInsideRT: 1 # Fixes post effects. - getSkipCount: "GSC_Tekken5" # Fixes upscaling grid, same engine. + getSkipCount: "GSC_NamcoGames" # Fixes upscaling grid, same engine. SLPS-20487: name: "パチスロキング! 科学忍者隊ガッチャマン" name-sort: "ぱちすろきんぐ! かがくにんじゃたいがっちゃまん" @@ -58043,7 +58043,7 @@ SLPS-25510: alignSprite: 1 # Fixes vertical lines. halfPixelOffset: 4 # Align post. nativeScaling: 1 # Fixes depth of field effect. - getSkipCount: "GSC_Tekken5" + getSkipCount: "GSC_NamcoGames" SLPS-25511: name: "羅刹 -Alternative-" name-sort: "らせつ -Alternative-" @@ -58484,7 +58484,7 @@ SLPS-25586: halfPixelOffset: 4 # Fixes post positioning. nativeScaling: 1 # Fixes post effects. autoFlush: 2 # Fixes post lighting. - getSkipCount: "GSC_Tekken5" # Fixes upscaling grid, same engine. + getSkipCount: "GSC_NamcoGames" # Fixes upscaling grid, same engine. SLPS-25587: name: "シュガシュガルーン 恋もおしゃれもピックアップ!" name-sort: "しゅがしゅがるーん こいもおしゃれもぴっくあっぷ!" @@ -61222,7 +61222,7 @@ SLPS-73223: alignSprite: 1 # Fixes vertical lines. halfPixelOffset: 4 # Align post. nativeScaling: 1 # Fixes depth of field effect. - getSkipCount: "GSC_Tekken5" + getSkipCount: "GSC_NamcoGames" SLPS-73224: name: "ゼノサーガ エピソードⅡ [善悪の彼岸] [PlayStation2 the Best] [ディスク1/2]" name-sort: "ぜのさーが えぴそーど2 ぜんあくのひがん [PlayStation2 the Best] [でぃすく1/2]" @@ -61522,7 +61522,7 @@ SLPS-73252: halfPixelOffset: 4 # Fixes post positioning. nativeScaling: 1 # Fixes post effects. autoFlush: 2 # Fixes post lighting. - getSkipCount: "GSC_Tekken5" # Fixes upscaling grid, same engine. + getSkipCount: "GSC_NamcoGames" # Fixes upscaling grid, same engine. SLPS-73253: name: "るろうに剣心-明治剣客浪漫譚- 炎上!京都輪廻 [PlayStation2 the Best]" name-sort: "るろうにけんしん めいじけんかくろまんたん えんじょう きょうとりんね [PlayStation2 the Best]" @@ -67249,7 +67249,7 @@ SLUS-21059: alignSprite: 1 # Fixes vertical lines. halfPixelOffset: 4 # Align post. nativeScaling: 1 # Fixes depth of field effect. - getSkipCount: "GSC_Tekken5" + getSkipCount: "GSC_NamcoGames" SLUS-21060: name: "WWE SmackDown! vs. RAW" region: "NTSC-U" @@ -67801,7 +67801,7 @@ SLUS-21160: alignSprite: 1 # Fixes vertical lines. halfPixelOffset: 4 # Align post. nativeScaling: 1 # Fixes depth of field effect. - getSkipCount: "GSC_Tekken5" + getSkipCount: "GSC_NamcoGames" SLUS-21161: name: "Fight Night Round 2" region: "NTSC-U" @@ -69387,7 +69387,7 @@ SLUS-21386: halfPixelOffset: 4 # Fixes post positioning. nativeScaling: 1 # Fixes post effects. autoFlush: 2 # Fixes post lighting. - getSkipCount: "GSC_Tekken5" # Fixes upscaling grid, same engine. + getSkipCount: "GSC_NamcoGames" # Fixes upscaling grid, same engine. SLUS-21387: name: "Warship Gunner 2" region: "NTSC-U" diff --git a/pcsx2/GS/Renderers/HW/GSHwHack.cpp b/pcsx2/GS/Renderers/HW/GSHwHack.cpp index d7db96ba77..8a703e5193 100644 --- a/pcsx2/GS/Renderers/HW/GSHwHack.cpp +++ b/pcsx2/GS/Renderers/HW/GSHwHack.cpp @@ -178,7 +178,7 @@ bool GSHwHack::GSC_DTGames(GSRendererHW& r, int& skip) return true; } -bool GSHwHack::GSC_Tekken5(GSRendererHW& r, int& skip) +bool GSHwHack::GSC_NamcoGames(GSRendererHW& r, int& skip) { if (skip == 0) { @@ -195,7 +195,7 @@ bool GSHwHack::GSC_Tekken5(GSRendererHW& r, int& skip) if (!rt) return false; - GL_INS("GSC_Tekken5(): HLE channel shuffle"); + GL_INS("GSC_NamcoGames(): HLE channel shuffle"); // have to set up the palette ourselves too, since GSC executes before it does r.m_mem.m_clut.Read32(RTEX0, r.m_draw_env->TEXA); @@ -1502,14 +1502,12 @@ const GSHwHack::Entry GSHwHack::s_get_skip_count_function CRC_F(GSC_Battlefield2), // Channel Effect + CRC_F(GSC_NamcoGames), CRC_F(GSC_SteambotChronicles), // Depth Issue CRC_F(GSC_BurnoutGames), - // Half Screen bottom issue - CRC_F(GSC_Tekken5), - // Upscaling hacks CRC_F(GSC_UltramanFightingEvolution), }; diff --git a/pcsx2/GS/Renderers/HW/GSHwHack.h b/pcsx2/GS/Renderers/HW/GSHwHack.h index bd1e24bec0..18dd0a2a82 100644 --- a/pcsx2/GS/Renderers/HW/GSHwHack.h +++ b/pcsx2/GS/Renderers/HW/GSHwHack.h @@ -11,7 +11,7 @@ public: static bool GSC_GuitarHero(GSRendererHW& r, int& skip); static bool GSC_SFEX3(GSRendererHW& r, int& skip); static bool GSC_DTGames(GSRendererHW& r, int& skip); - static bool GSC_Tekken5(GSRendererHW& r, int& skip); + static bool GSC_NamcoGames(GSRendererHW& r, int& skip); static bool GSC_BurnoutGames(GSRendererHW& r, int& skip); static bool GSC_BlackAndBurnoutSky(GSRendererHW& r, int& skip); static bool GSC_MidnightClub3(GSRendererHW& r, int& skip); From 17bf27c0183016dac2b8a5a5844741bdb26726b0 Mon Sep 17 00:00:00 2001 From: lightningterror <18107717+lightningterror@users.noreply.github.com> Date: Fri, 18 Apr 2025 19:43:24 +0200 Subject: [PATCH 160/162] GS/DX12: Copy/bind rt when tex is fb on slot 0. --- pcsx2/GS/Renderers/DX11/GSDevice11.cpp | 2 +- pcsx2/GS/Renderers/DX12/GSDevice12.cpp | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/pcsx2/GS/Renderers/DX11/GSDevice11.cpp b/pcsx2/GS/Renderers/DX11/GSDevice11.cpp index 1c2ecdca7d..8077bee3de 100644 --- a/pcsx2/GS/Renderers/DX11/GSDevice11.cpp +++ b/pcsx2/GS/Renderers/DX11/GSDevice11.cpp @@ -2631,7 +2631,7 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config) } GSTexture* rt_copy = nullptr; - if (config.require_one_barrier || (config.tex && config.tex == config.rt)) // Used as "bind rt" flag when texture barrier is unsupported + if (config.require_one_barrier || (config.tex && config.tex == config.rt)) // Used as "bind rt" flag when texture barrier is unsupported. { // Bind the RT.This way special effect can use it. // Do not always bind the rt when it's not needed, diff --git a/pcsx2/GS/Renderers/DX12/GSDevice12.cpp b/pcsx2/GS/Renderers/DX12/GSDevice12.cpp index 678df9e6a6..8bd107b8a1 100644 --- a/pcsx2/GS/Renderers/DX12/GSDevice12.cpp +++ b/pcsx2/GS/Renderers/DX12/GSDevice12.cpp @@ -3900,7 +3900,7 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config) } } - if (config.require_one_barrier) + if (config.require_one_barrier || (config.tex && config.tex == config.rt)) // Used as "bind rt" flag when texture barrier is unsupported. { // requires a copy of the RT draw_rt_clone = static_cast(CreateTexture(rtsize.x, rtsize.y, 1, colclip_rt ? GSTexture::Format::ColorClip : GSTexture::Format::Color, true)); @@ -3913,7 +3913,10 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config) draw_rt_clone->SetState(GSTexture::State::Invalidated); CopyRect(draw_rt, draw_rt_clone, config.drawarea, config.drawarea.left, config.drawarea.top); - PSSetShaderResource(2, draw_rt_clone, true); + if (config.require_one_barrier) + PSSetShaderResource(2, draw_rt_clone, true); + if (config.tex && config.tex == config.rt) + PSSetShaderResource(0, draw_rt_clone, true); } } From f7b9c489989db15826c9fd8a9c3a40544cf7e6ec Mon Sep 17 00:00:00 2001 From: Mrlinkwii Date: Sat, 19 Apr 2025 15:46:34 +0100 Subject: [PATCH 161/162] Deps: update redump database --- bin/resources/RedumpDatabase.yaml | 408 +++++++++++++++++++++++------- 1 file changed, 318 insertions(+), 90 deletions(-) diff --git a/bin/resources/RedumpDatabase.yaml b/bin/resources/RedumpDatabase.yaml index d0a17169e8..020eb849b1 100644 --- a/bin/resources/RedumpDatabase.yaml +++ b/bin/resources/RedumpDatabase.yaml @@ -1749,7 +1749,7 @@ - hashes: - md5: f95ea9eb021181b888229df60ba8effd size: 2159050752 - name: Shadow the Hedgehog (Europe, Australia) (En,Fr,De,Es,It) + name: Shadow the Hedgehog (Europe, Australia) (En,Ja,Fr,De,Es,It) serial: SLES-53542 version: '1.01' - hashes: @@ -8524,7 +8524,7 @@ - hashes: - md5: ce030f48dcb588682902b5a569baa24e size: 4677795840 - name: 007 - Quantum of Solace (USA) + name: 007 - Quantum of Solace (USA) (En,Fr) serial: SLUS-21813 version: '1.00' - hashes: @@ -8620,7 +8620,7 @@ - hashes: - md5: c15a1707d1c89728e0c596b97ba91b27 size: 4313153536 - name: Official PlayStation 2 Magazine Sonderausgabe 2004-3 (Germany) (En,Fr,De,Es,It) + name: Official PlayStation 2 Magazine - Special Edition 2004-03 (Germany) (En,Fr,De,Es,It) serial: SCED-52997 version: '1.00' - hashes: @@ -8854,7 +8854,7 @@ - hashes: - md5: fab56e6f617b0a64ad35221341bd167c size: 3529965568 - name: Rock Band - Metal Track Pack (USA) + name: Rock Band - Metal Track Pack (USA, Canada) serial: SLUS-21889 version: '1.00' - hashes: @@ -9753,7 +9753,7 @@ - hashes: - md5: 63d3e8e5da0305650160bc50f46df87b size: 4033478656 - name: Fight Night Round 3 (Europe) (En,Fr) + name: Fight Night Round 3 (Europe, Australia) (En,Fr) serial: SLES-53982 version: '1.00' - hashes: @@ -13331,7 +13331,7 @@ - hashes: - md5: aac303724ce688c8f242245ea0f4b9e4 size: 877953024 - name: Tokyo Bus Guide - Kyou kara Kimi mo Untenshu (Japan) + name: SuperLite 2000 Vol. 5 - Tokyo Bus Guide - Kyou kara Kimi mo Untenshu (Japan) serial: SLPM-65032 version: '1.04' - hashes: @@ -15880,7 +15880,7 @@ - hashes: - md5: 29e5cf458549ca5321dd0c12619a002b size: 3846504448 - name: Bakugan - Battle Brawlers (USA) + name: Bakugan - Battle Brawlers (USA) (En,Fr) serial: SLUS-21902 version: '1.00' - hashes: @@ -20525,7 +20525,7 @@ - hashes: - md5: 2e7fcee5a1333b72eef8a18c7a673e9f size: 8539766784 - name: Guitar Hero - Metallica (USA) + name: Guitar Hero - Metallica (USA) (En,Fr) serial: SLUS-21843 version: '1.00' - hashes: @@ -20868,7 +20868,7 @@ - hashes: - md5: 35d939b30458790cf58ddd16ef11e159 size: 697149264 - name: Online Start-Up Disc 4.0 - Broadband Only (USA) + name: Online Start-Up Disc 4.0 - Broadband Only (USA) (v1.02) serial: PBPX-95248 version: '1.02' - hashes: @@ -21320,8 +21320,7 @@ - hashes: - md5: 4f18266ee64804fc1ba858c5d5bee759 size: 3782836224 - name: Official PlayStation 2 Magazine Germany Special Edition 01-2004 (Germany) - (En,Fr,De,Es,It) + name: Official PlayStation 2 Magazine - Special Edition 2004-01 (Germany) (En,Fr,De,Es,It) serial: SCED-52119 version: '1.01' - hashes: @@ -21764,7 +21763,7 @@ - hashes: - md5: e6159c29d23cd03252e94823045b4ff8 size: 4071424000 - name: DJ Hero (USA) + name: DJ Hero (USA) (En,Fr) serial: SLUS-21909 version: '1.00' - hashes: @@ -22190,7 +22189,7 @@ - hashes: - md5: 007f0ccc933f59b65f28c72ad227843d size: 4580573184 - name: Score International Baja 1000 - World Championship Off Road Racing (Europe) + name: SCORE International Baja 1000 - World Championship Off Road Racing (Europe) serial: SLES-55295 version: '1.00' - hashes: @@ -22862,7 +22861,7 @@ - hashes: - md5: cadce0add00903396577b5d53cff1929 size: 4679565312 - name: Marvel Super Hero Squad (USA) + name: Marvel Super Hero Squad (USA) (En,Fr,Es) serial: SLUS-21910 version: '1.00' - hashes: @@ -22946,7 +22945,7 @@ - hashes: - md5: 77ae418d710142291b5d6eb5d5d904d1 size: 3007021056 - name: Buzz! The Schools Quiz (Europe) + name: Buzz! The Schools Quiz (UK) (v2.00) serial: SCES-54941 version: '2.00' - hashes: @@ -30411,7 +30410,7 @@ - hashes: - md5: 01abdbe56de95af5b5379cf7fea1bec0 size: 3683680256 - name: Ratchet & Clank 2 - Gagaga! Ginga no Commando-ssu (Japan, Asia) + name: Ratchet & Clank 2 - Gagaga! Ginga no Commando ssu (Japan, Asia) serial: SCAJ-20052 version: '1.00' - hashes: @@ -32035,7 +32034,7 @@ - hashes: - md5: 47e03c7b246239330d5e41efb5160fda size: 1328939008 - name: Nickelodeon Bob L'eponge - Silence on Tourne! (France) + name: Nickelodeon Bob L'eponge - Silence on Tourne ! (France) serial: SLES-53495 version: '1.00' - hashes: @@ -33603,19 +33602,19 @@ - hashes: - md5: ac71dddc4a60475a689fcb6f1836e607 size: 4307582976 - name: Buzz! The Big Quiz (Europe) (En,Fr,Nl) + name: Buzz! The Big Quiz (Belgium, Netherlands) (En,Fr,Nl) serial: SCES-53925 version: '1.00' - hashes: - md5: 0f457023f2a436d38a0cd53599115846 size: 4611080192 - name: Buzz! The Mega Quiz (Europe) (En,Fr,Nl) + name: Buzz! The Mega Quiz (Belgium, Netherlands) (En,Fr,Nl) serial: SCES-54505 version: '1.00' - hashes: - md5: 409302dffc2512d1ccd2227d17ff6bf2 size: 4688314368 - name: Buzz! The Pop Quiz (Europe) (En,Fr,Nl) + name: Buzz! The Pop Quiz (Belgium, Netherlands) (En,Fr,Nl) serial: SCES-55098 version: '1.00' - hashes: @@ -34577,7 +34576,7 @@ - hashes: - md5: 4703eb006c9921927145c878acd7324c size: 3536748544 - name: Buzz! The Sports Quiz (Europe) (En,Fr,Nl) + name: Buzz! The Sports Quiz (Belgium, Netherlands) (En,Fr,Nl) serial: SCES-54265 version: '1.00' - hashes: @@ -36547,7 +36546,7 @@ - hashes: - md5: fa7523e49dc9825589504430857c5c25 size: 564202464 - name: LEGO Island Xtreme Stunts (USA) + name: Island Xtreme Stunts (USA) serial: SLUS-20575 version: '1.01' - hashes: @@ -38437,7 +38436,7 @@ - hashes: - md5: 06553f511a16b8cc0396bab31cc910a6 size: 2924576768 - name: DreamWorks Madagascar - Escape 2 Africa (USA) + name: DreamWorks Madagascar - Escape 2 Africa (USA) (En,Fr) serial: SLUS-21840 version: '1.01' - hashes: @@ -39061,7 +39060,7 @@ - hashes: - md5: cc5e862a633532654176096888dfacba size: 2679865344 - name: PES 2011 - Pro Evolution Soccer (USA) + name: PES 2011 - Pro Evolution Soccer (USA) (En,Fr,Es,Pt) serial: SLUS-21942 version: '1.00' - hashes: @@ -39355,7 +39354,7 @@ - hashes: - md5: 298ad8520c82f4edbaacb3194bc46d5c size: 3530391552 - name: PES 2010 - Pro Evolution Soccer (USA) + name: PES 2010 - Pro Evolution Soccer (USA) (En,Fr,Es,Pt) serial: SLUS-21918 version: '1.02' - hashes: @@ -39421,7 +39420,7 @@ - hashes: - md5: 491a3f6a39ce290de2f6dbb9ba98575f size: 2129297408 - name: PES 2013 - Pro Evolution Soccer (USA) + name: PES 2013 - Pro Evolution Soccer (USA) (En,Fr,Es,Pt) serial: SLUS-21955 version: '1.00' - hashes: @@ -39433,7 +39432,7 @@ - hashes: - md5: b2d139590f169442a46d21ed2f8558ab size: 2437316608 - name: PES 2012 - Pro Evolution Soccer (USA) + name: PES 2012 - Pro Evolution Soccer (USA) (En,Fr,Es,Pt) serial: SLUS-21948 version: '1.00' - hashes: @@ -39511,7 +39510,7 @@ - hashes: - md5: 6821bded97877694e8cf4ccf9ca64ad4 size: 4659347456 - name: Vitamin Z - Welcome Our New Supplement Boys (Japan) (Genteiban) + name: VitaminZ - Welcome Our New Supplement Boys (Japan) (Genteiban) serial: SLPS-25922 version: '1.02' - hashes: @@ -39770,7 +39769,7 @@ - hashes: - md5: 6f2533c2ae433f1beac4f1aa3a88f4c6 size: 4588797952 - name: AND 1 Streetball (USA) (v1.03) + name: AND 1 Streetball (USA) (En,Fr,Es) (v1.03) serial: SLUS-21237 version: '1.03' - hashes: @@ -39902,7 +39901,7 @@ - hashes: - md5: 2d3dd4cd0d8c16e97a1a887b70349658 size: 4636672000 - name: Score International Baja 1000 - The Official Game (USA) + name: SCORE International Baja 1000 - The Official Game (USA) serial: SLUS-21850 version: '1.00' - hashes: @@ -40463,7 +40462,7 @@ - md5: 08a63db4f248acdc24cdaa0b9507b854 size: 2205908992 name: Umishou (Japan) - serial: SLPM-66864 + serial: FVGK-0001 version: '1.01' - hashes: - md5: d3b19450aea4a9688e876fa71830aa77 @@ -40745,7 +40744,7 @@ - hashes: - md5: 9be1477e6c0bd67bb6aff3a02601b4a6 size: 1534427136 - name: Freaky Flyers (USA) (Demo) + name: Freaky Flyers (USA) (Demo) (Rev 1) serial: SLUS-29061 version: '0.30' - hashes: @@ -41497,7 +41496,7 @@ - hashes: - md5: 6dc613680039ba59d20f8baa61e56fc5 size: 739706352 - name: Momotarou Dentetsu 11 - Black Bombee Shutsugen! no Maki (Japan) + name: Momotarou Dentetsu 11 - Black Bonby Shutsugen! no Maki (Japan) serial: SLPM-62475 version: '1.01' - hashes: @@ -44241,7 +44240,7 @@ - md5: 34d981b3883eaeb95c82fbed14dcad26 size: 3115188224 name: Harukanaru Toki no Naka de 3 (Japan) (History Box) - serial: SLPM-66005 + serial: SLPM-65849 version: '1.02' - hashes: - md5: d8ecac6d03a601d4a81f1389bfe25962 @@ -45207,13 +45206,13 @@ - hashes: - md5: 29a454ebd230549939654022af5db917 size: 4107206656 - name: Vitamin X - We are Super Supriment Boys (Japan) + name: VitaminX - We are Super Supriment Boys (Japan) serial: SLPS-25761 version: '1.01' - hashes: - md5: 19cc959d502b7eebae156ee760731454 size: 4659347456 - name: Vitamin Z - Welcome Our New Supplement Boys (Japan) + name: VitaminZ - Welcome Our New Supplement Boys (Japan) serial: SLPS-25923 version: '1.02' - hashes: @@ -46244,7 +46243,7 @@ - hashes: - md5: ecb21ce06339ab6b628e077f3c03690b size: 3962535936 - name: Suigetsu - Mayoi Gokoro (Japan) + name: Suigetsu - Mayoigokoro (Japan) serial: SLPM-65751 version: '1.01' - hashes: @@ -47270,7 +47269,7 @@ - hashes: - md5: 68634f9b74fe47e4a039ada615970ca1 size: 541969008 - name: Momotarou Dentetsu 15 - Godai Bombee Toujou! no Maki (Japan) (v2.00) + name: Momotarou Dentetsu 15 - Godai Bonby Toujou! no Maki (Japan) (v2.00) serial: SLPM-74104 version: '2.00' - hashes: @@ -49343,8 +49342,7 @@ - hashes: - md5: 72e49d4201496dea3e0851abf58098cf size: 4677992448 - name: Official PlayStation 2 Magazine Germany Special Edition 2003-01 (Germany) - (En,De) + name: Official PlayStation 2 Magazine - Special Edition 2003-01 (Germany) (En,De) serial: SCED-51512 version: '1.00' - hashes: @@ -49422,7 +49420,7 @@ - hashes: - md5: aa26f473fc9ecf2df7fad3a9acc30351 size: 3115188224 - name: Harukanaru Toki no Naka de 3 (Japan) (Triple Pack) + name: Harukanaru Toki no Naka de 3 (Japan) (Premium Box) serial: SLPM-65850 version: '1.02' - hashes: @@ -51309,12 +51307,6 @@ name: Magazine Ufficiale PlayStation 2 Demo Italia 05-2006 (Italy) (En,Fr,De,Es,It) serial: SCED-54180 version: '1.00' -- hashes: - - md5: 0b14951eb38e4f5f4730bcf48bf64898 - size: 4569923584 - name: Magazine Ufficiale PlayStation 2 Italia 05-04 (Italy) (En,Fr,De,Es,It) - serial: SCED-52443 - version: '1.00' - hashes: - md5: 771bfe48ce1a10c8a638da8bedc9acf6 size: 4175429632 @@ -51698,13 +51690,13 @@ - hashes: - md5: cf45002cd37168e4a2b3a8f96c560941 size: 3983638528 - name: Official PlayStation 2 Magazine Germany Special 2-2005 (Germany) + name: Official PlayStation 2 Magazine - Special Edition 2005-02 (Germany) serial: SCED-53662 version: '1.00' - hashes: - md5: 8191fa8441cd8c947ab80b9db6de0ce3 size: 4583129088 - name: Official PlayStation 2 Magazine Germany Special 3-2005 (Germany) + name: Official PlayStation 2 Magazine - Special Edition 2005-03 (Germany) serial: SCED-53938 version: '1.00' - hashes: @@ -51818,7 +51810,7 @@ - hashes: - md5: 8e18b5399008416a7d012f2ea1bf9353 size: 663976656 - name: Pro Evolution Soccer 3 (Europe) (Demo 1) + name: Pro Evolution Soccer 3 (UK) (Demo) serial: SLED-51992 version: '1.00' - hashes: @@ -51950,7 +51942,7 @@ - hashes: - md5: 209c4247ffb84795b23ce2cec0f090a4 size: 3793453056 - name: Official PlayStation 2 Magazine Germany Special Edition 2003-03 (Germany) + name: Official PlayStation 2 Magazine - Special Edition 2003-03 (Germany) serial: SCED-51551 version: '1.00' - hashes: @@ -52038,7 +52030,7 @@ - hashes: - md5: e8e5b428c642c78d510cbd854c17b9cc size: 1616740352 - name: Primal + The Mark of Kri + War of the Monsters (Europe) (En,Fr,De,Es,It) + name: Primal & The Mark of Kri & War of the Monsters (Europe) (En,Fr,De,Es,It) serial: SCED-51506 version: '1.00' - hashes: @@ -52350,7 +52342,7 @@ - hashes: - md5: d42eed1d14cdaf8add8295116776a447 size: 3765108736 - name: Best PS2 Games Ever 10 (Europe) + name: Official PlayStation 2 Magazine-UK Special Edition - Yearbook 2002 (Europe) serial: SCED-51389 version: '1.00' - hashes: @@ -52637,7 +52629,7 @@ - hashes: - md5: cded45ee044b5a7614644fa444a59989 size: 4334157824 - name: Official PlayStation 2 Magazine Germany Special Edition 2003-02 (Germany) + name: Official PlayStation 2 Magazine - Special Edition 2003-02 (Germany) serial: SCED-51549 version: '1.01' - hashes: @@ -53111,7 +53103,7 @@ - hashes: - md5: f309b4b48dc775dbbde635a1ef3b662b size: 4522639360 - name: Germany Special Issue 3 (Germany) + name: Official PlayStation 2 Magazine - Special Edition 3 (Germany) serial: SCED-51375 version: '1.00' - hashes: @@ -54054,7 +54046,7 @@ - hashes: - md5: ac5b76bd39b0cabd1acfe4dcb08df502 size: 1649803264 - name: Primal + The Mark of Kri (Europe) + name: Primal & The Mark of Kri (Europe) serial: SCED-51491 version: '1.00' - hashes: @@ -54085,7 +54077,7 @@ - hashes: - md5: 2475b61666f06dc2aa9ca032f15a4cde size: 4241293312 - name: Official PlayStation 2 Magazine Germany Special Edition 2007-1 (Germany) + name: Official PlayStation 2 Magazine - Special Edition 2007-01 (Germany) serial: SCED-54693 version: '1.02' - hashes: @@ -54462,7 +54454,7 @@ - hashes: - md5: 97a9156abd199f588d4f46e9af7ab5a7 size: 4107206656 - name: Vitamin X - We are Super Supriment Boys (Japan) (Genteiban) + name: VitaminX - We are Super Supriment Boys (Japan) (Genteiban) serial: SLPS-25760 version: '1.01' - hashes: @@ -54767,7 +54759,7 @@ - hashes: - md5: a4b5b1d6c13c15bc1bac6fc63622c9ee size: 2925920256 - name: Ever 17 - The Out of Infinity Premium Edition (Japan) + name: Ever 17 - The Out of Infinity - Premium Edition (Japan) serial: SLPM-65421 version: '1.01' - hashes: @@ -55910,7 +55902,7 @@ - hashes: - md5: 05b210d50d92a418ac1e37a1fff0df8a size: 243996480 - name: Giant Robo - The Animation - Chikyuu ga Seishi suru Hi (Japan) + name: Giant Robo - The Animation - Chikyuu ga Seishi Suru Hi (Japan) serial: SLPM-62526 version: '1.02' - hashes: @@ -56111,7 +56103,7 @@ - hashes: - md5: 45d62f1cf862b8e6c47d373a5c55d2f2 size: 708511776 - name: Pro Evolution Soccer 4 (Europe) (Demo) + name: Pro Evolution Soccer 4 (UK) (Demo) serial: SLED-52873 version: '1.02' - hashes: @@ -56171,7 +56163,7 @@ - hashes: - md5: 139585af1bf276d5f2e17a32def4f66d size: 4528504832 - name: Official PlayStation 2 Magazine Germany Special Edition 1 (Germany) + name: Official PlayStation 2 Magazine - Special Edition 1 (Germany) serial: SCED-50780 version: '1.10' - hashes: @@ -57668,7 +57660,7 @@ - hashes: - md5: 1ce4a9368f7c6c73dcd983ad5f86f3eb size: 718702992 - name: MaxPlay (Europe) (Unl) + name: Max Play (Europe) (Unl) version: 1.00 (European) - hashes: - md5: 8182f27cc5a0cac14da217e45861e49e @@ -57841,8 +57833,7 @@ - hashes: - md5: 4fb718d2059a1843882a70b17922b483 size: 4613996544 - name: Official PlayStation 2 Magazine Germany Special Edition 2005-01 (Germany) - (En,De) + name: Official PlayStation 2 Magazine - Special Edition 2005-01 (Germany) (En,De) serial: SCED-53298 version: '1.02' - hashes: @@ -57854,7 +57845,7 @@ - hashes: - md5: 739a2f519751c64f7291d036105c2c0d size: 1670316032 - name: Buzz! Junior - Robojam (Europe) (Beta) (2007-01-12) + name: Buzz! Junior - RoboJam (Europe) (Beta) (2007-01-12) serial: SCES-54676 version: '1.00' - hashes: @@ -58392,7 +58383,7 @@ - hashes: - md5: 6969a8f3623387131e2e1d49fff4c3f5 size: 4492853248 - name: Magical Tale - Chiicha na Mahoutsukai (Japan) + name: Magical Tale - Chitcha na Mahoutsukai (Japan) serial: SLPM-65965 version: '1.01' - hashes: @@ -59340,7 +59331,7 @@ - hashes: - md5: 8d61b97a6dd45165b012b338d42e53a8 size: 2358018048 - name: Harry Potter and the Prisoner of Azkaban (Korea) + name: Harry Potter-wa Azkaban-ui Joesu (Korea) serial: SLKA-25172 version: '1.01' - hashes: @@ -59646,7 +59637,7 @@ - hashes: - md5: 1162e6411d9e16fbefc9fd8a9e886d70 size: 4412145664 - name: Official PlayStation 2 Magazine Germany Winter 2006 (Germany) (En,De) + name: Official PlayStation 2 Magazine - Winter 2006 (Germany) (En,De) serial: SCED-54558 version: '1.00' - hashes: @@ -60504,7 +60495,7 @@ - hashes: - md5: 78f327cd6c0f2bd6f99a299e982b9a93 size: 1253867520 - name: TOCA Race Driver 3 + V8 Supercars Australia 3 (Australia) (Demo) + name: TOCA Race Driver 3 (Australia) (Demo) serial: SLED-53888 version: '1.00' - hashes: @@ -60764,7 +60755,7 @@ - hashes: - md5: 7e964b27119adc590228452d42c634b0 size: 4614062080 - name: Buzz! Hollywood Quiz (Europe) (En,Fr,Nl) + name: Buzz! Hollywood Quiz (Belgium, Netherlands) (En,Fr,Nl) serial: SCES-54854 version: '1.00' - hashes: @@ -62041,7 +62032,7 @@ - hashes: - md5: dd5ccdc3e325716d7f7ed08a9836f3f0 size: 3938189312 - name: PlayStation 2 Revista Oficial - Portugal 9 (Portugal) + name: Official PlayStation 2 Magazine Demo 32 (Portugal) serial: SCED-51657 version: '1.00' - hashes: @@ -62487,7 +62478,7 @@ - hashes: - md5: fe79ea717184a278802679ef78f6df7b size: 7774928896 - name: Zhen San Guo Wu Shuang 5 Special (Taiwan) + name: Zhen Sanguo Wushuang 5 Special (Taiwan) serial: SLAJ-35007 version: '1.00' - hashes: @@ -63024,7 +63015,7 @@ - hashes: - md5: 18e6281d238ccb19e8975d45e8c157f0 size: 3816554496 - name: Offizielle PlayStation 2 Magazin 02-2004, Das - Uncut Edition (Germany) + name: Official PlayStation 2 Magazine 02-2004 - Uncut Edition (Germany) serial: SCED-52082 version: '1.01' - hashes: @@ -63141,7 +63132,7 @@ - hashes: - md5: d38ab694e14843f16af11a15d1883023 size: 4677795840 - name: Koi suru Otome to Shugo no Tate - The Shield of AIGIS (Japan) + name: Koi Suru Otome to Shugo no Tate - The Shield of AIGIS (Japan) serial: SLPM-55098 version: '1.02' - hashes: @@ -63554,7 +63545,7 @@ - hashes: - md5: 3acdf0ae8cc67fe9e0db9f4707cb3905 size: 2638381056 - name: I Love Baseball - Pro Yakyuu o Koyonaku Ai suru Hitotachi e (Japan) + name: I Love Baseball - Pro Yakyuu o Koyonaku Ai Suru Hitotachi e (Japan) serial: SLPM-65633 version: '1.04' - hashes: @@ -63941,13 +63932,13 @@ - hashes: - md5: 7ea2da0f39ae55794fd9fd5006b417cc size: 4446388224 - name: Offizielle PlayStation 2 Magazin 09-2004, Das (Germany) + name: Official PlayStation 2 Magazine 09-2004 (Germany) serial: SCED-52089 version: '1.02' - hashes: - md5: 11b80bd02a0a874212b014c56e44a4c5 size: 4033347584 - name: Offizielle PlayStation 2 Magazin 12-2003, Das (Germany) + name: Official PlayStation Magazine 12-2003 (Germany) serial: SCED-51936 version: '1.00' - hashes: @@ -64181,7 +64172,7 @@ - hashes: - md5: 8990d1c3b88b04b1bbc7a78ee3e9df2f size: 33821760 - name: Karat PS2-you Pro Action Replay 2 Taikenban (Japan) (Unl) + name: Karat PS2-you Pro Action Replay 2 Taikenban (Japan) (Unl) (Rev 1) version: '1.7' - hashes: - md5: 4a2754811946ef9badbbd872f541a03f @@ -64254,7 +64245,7 @@ - hashes: - md5: 0e35e8fd22fe57f8727b9764685bbc29 size: 3871703040 - name: Offizielle PlayStation 2 Magazin, Das - Special Edition 2 (Germany) + name: Official PlayStation 2 Magazine - Special Edition 2 (Germany) serial: SCED-51161 version: '1.00' - hashes: @@ -64563,7 +64554,7 @@ - hashes: - md5: 477eb508b08f59386478908e481eb8a1 size: 4519690240 - name: Official PlayStation 2 Magazine - German Kids Special (Germany) (En,De) + name: Official PlayStation 2 Magazine - Kids Special (Germany) (En,De) serial: SCED-53611 version: '1.00' - hashes: @@ -64580,7 +64571,7 @@ - hashes: - md5: ac9cb481855792415314e06e5c12b291 size: 664183632 - name: Pro Evolution Soccer 3 (Europe) (Demo 2) + name: Pro Evolution Soccer 3 (Europe) (Demo) serial: SLED-51994 version: '1.00' - hashes: @@ -65185,7 +65176,7 @@ - hashes: - md5: 86af0c99c8a8566e5f9307110f339e20 size: 364475328 - name: Monopoly - Mezase!! Daifugou Jinsei!! (Japan) + name: Monopoly - Mezase!! Daifugou Jinsei!! (Japan) (v2.00) serial: SLPS-20281 version: '2.00' - hashes: @@ -66352,7 +66343,7 @@ - hashes: - md5: ff59d561b843dbbb940c631d53294d63 size: 1552318464 - name: Monsterspass (Austria, Switzerland) (En,Fr,De,Es,It,Nl,Pt) + name: Monsterspass (Austria) (En,Fr,De,Es,It,Nl,Pt) serial: SCES-54704 version: '1.00' - hashes: @@ -66436,7 +66427,7 @@ - hashes: - md5: 79fc6bc53ff748088a36d2f6778d0a0b size: 1943797760 - name: Big! Sports Quiz, The (Austria, Switzerland) + name: Big! Sports Quiz, The (Austria) serial: SCES-54526 version: '1.00' - hashes: @@ -66803,7 +66794,7 @@ - hashes: - md5: 91450691bed8af904b291c4d86c0c338 size: 1645182976 - name: Jungle Party (Austria, Switzerland) + name: Jungle Party (Austria) serial: SCES-54524 version: '1.01' - hashes: @@ -66934,7 +66925,7 @@ - hashes: - md5: 777cd430a73b95945cee679a27446eed size: 2297200640 - name: Disney-Pixar Finding Nemo (Korea) + name: Disney-Pixar Nemo-reul Chajaseo (Korea) serial: SLKA-25056 version: '1.00' - hashes: @@ -68550,7 +68541,7 @@ - hashes: - md5: 5b315096f6bfc1897e52fec9c877b81d size: 4250206208 - name: Offizielle PlayStation 2 Magazin 13-2003, Das - Uncut Edition (Germany) + name: Official PlayStation 2 Magazine 13-2003 - Uncut Edition (Germany) serial: SCED-51937 version: '1.00' - hashes: @@ -69278,7 +69269,7 @@ - hashes: - md5: 6905874bbb6167417bac324464d70d28 size: 541969008 - name: Momotarou Dentetsu 15 - Godai Bombee Toujou! no Maki (Japan) (v1.01) + name: Momotarou Dentetsu 15 - Godai Bonby Toujou! no Maki (Japan) (v1.01) serial: SLPM-62702 version: '1.01' - hashes: @@ -69326,7 +69317,7 @@ - hashes: - md5: 950783ce0723d41ff7aa46659a6eafbc size: 4492853248 - name: Magical Tale - Chiicha na Mahoutsukai (Japan) (Shokai Genteiban) + name: Magical Tale - Chitcha na Mahoutsukai (Japan) (Shokai Genteiban) serial: SLPM-65964 version: '1.01' - hashes: @@ -69341,3 +69332,240 @@ name: Quilt - Anata to Tsumugu Yume to Koi no Dress (Japan) serial: SLPM-66735 version: '1.02' +- hashes: + - md5: ad64473c57824cf607cf40a7d85cf36c + size: 2300313600 + name: Guitar Hero (USA) (Demo) + serial: SLUS-29177 + version: '1.00' +- hashes: + - md5: 667607deb941380aa4d13db387b93077 + size: 93254448 + name: Codes Exclusifs (France) (Unl) + version: '1.00' +- hashes: + - md5: 589e37362d073b8ad52670cdd5664415 + size: 1822326784 + name: DearS (Japan) (Genteiban) + serial: SLPS-25371 + version: '1.04' +- hashes: + - md5: 4afb874dc19e35051e79f7a881a584b4 + size: 1524793344 + name: Winning Post 6 - 2005-nendoban (Japan) (Premium Pack) + serial: SLPM-65893 + version: '1.01' +- hashes: + - md5: 132ce8a9e16ca7ef5a356aa111bce318 + size: 4354473984 + name: Jak 3 (Europe) (En,Fr,De,Es,It,Pt,Ko,Ru) (Beta) (2004-09-14) + serial: SCES-52460 + version: '1.00' +- hashes: + - md5: 6432d610b89d00eb1eea2ee71cf27147 + size: 3007119360 + name: Buzz! The Schools Quiz (UK) (v1.00) + serial: SCES-54941 + version: '1.00' +- hashes: + - md5: 3c47093b42a1e1a39692b220e7659ad9 + size: 4601774080 + name: Big! Pop Quiz, The (Austria) + serial: SCES-55099 + version: '1.00' +- hashes: + - md5: 859073451dbb344df7cee32703245ed5 + size: 306380800 + name: Freaky Flyers (USA) (Demo) + serial: SLUS-29051 + version: '1.00' +- hashes: + - md5: 8d987ddccb1e2ec4c068896a4182bc4c + size: 1542029312 + name: Tennis no Oujisama - Smash Hit! 2 (Japan) (Shokai SP Genteiban C-Type) + serial: SLPM-65453 + version: '1.01' +- hashes: + - md5: b0a2a871249538d80b92e7ef837f20dd + size: 761692848 + name: CD avec les Codes Action Replay Exclusivement pour le Jeu Enter the Matrix + (France) + version: 1.01 (European) +- hashes: + - md5: db0c13bd5bddb659d563b4e2c0fcd5e8 + size: 226549344 + name: Action Replay Ultimate Cheats for Use with Grand Theft Auto - Vice City (UK) + (Unl) + version: '1.30' +- hashes: + - md5: 7866eaffddb52bbf7375ab4deca20dd6 + size: 33821760 + name: Karat PS2-you Pro Action Replay 2 Taikenban (Japan) (Unl) + version: '1.7' +- hashes: + - md5: 76b15475cac6977f2510ea07d9b87043 + size: 4348444672 + name: Official PlayStation 2 Magazine - Special Edition 2006-01 (Germany) + serial: SCED-54034 + version: '1.00' +- hashes: + - md5: c9f8be1699b50935c1e1989cd76bdbe1 + size: 697118688 + name: Online Start-Up Disc 4.0 - Broadband Only (USA) (v1.01) + serial: PBPX-95248 + version: '1.01' +- hashes: + - md5: d7cfe2da9a3598cb10bc61abd2657acd + size: 3497885696 + name: Big! Movie Quiz, The (Austria) + serial: SCES-54857 + version: '1.00' +- hashes: + - md5: 875894a624cc3d7bc81311985b5eeff7 + size: 549201408 + name: Argus-ui Jeonsa (Korea) (Cheheompan) + serial: SCKA-90005 + version: '1.00' +- hashes: + - md5: 7f3b5e111ca4c77039a1641fe2785423 + size: 3271229440 + name: Mai-Otome HiME - Otome Butoushi!! (Japan) (Limited Edition) + serial: SLPS-25680 + version: '1.01' +- hashes: + - md5: 5626f8c491117a0924380c786ab7e458 + size: 3049783296 + name: Fire It Up Kids (Europe) + serial: SCED-53678 + version: '1.00' +- hashes: + - md5: 7d6503407ba1ea104753be2bfe1283e3 + size: 2560065536 + name: Nickelodeon SpongeBob SquarePants - Movin' with Friends (Europe) (En,Fr,De,Es) + (Beta) (2004-10-29) +- hashes: + - md5: baf280135d74603e097a813d52e25afa + size: 707949648 + name: Pro Evolution Soccer 4 (Europe) (Demo) + serial: SLED-52878 + version: '1.03' +- hashes: + - md5: 1fa77218061839d6ac9b39159fa46680 + size: 1249804288 + name: SingStar Rocks! (Europe) (Demo) + serial: SCED-54086 + version: '1.00' +- hashes: + - md5: 2d91ab06f6fc0f918c9389990df71a63 + size: 1378156544 + name: This Is Football 2004 (Belgium) (Demo) + serial: SCED-52321 + version: '1.01' +- hashes: + - md5: 6917a832cf75177a18c4ef4499966ba4 + size: 3448471552 + name: Official PlayStation 2 Magazine Demo 10 (Spain) + serial: SCED-50406 +- hashes: + - md5: e1615a414d9b561e70b395155edb7e39 + size: 4697686016 + name: Chaos Legion (Europe) (Demo) + serial: SLED-51808 + version: '1.00' +- hashes: + - md5: 670fabe932a7ad8b81f6356d162cdac6 + size: 4569923584 + name: Magazine Ufficiale PlayStation 2 Italia 05-04 (Italy) (En,Fr,De,Es,It) + serial: SCED-52443 + version: '1.00' +- hashes: + - md5: dd4dd82c735f0189b5cd91c0c5e4b327 + size: 364472976 + name: Monopoly - Mezase!! Daifugou Jinsei!! (Japan) (v1.04) + serial: SLPS-20281 + version: '1.04' +- hashes: + - md5: 2be7a399436665d532383e9e6d21f7fb + size: 1632665600 + name: Musashiden II - Blademaster (Japan) (Taikenban) + serial: SLPM-61117 + version: '1.00' +- hashes: + - md5: 85f8dd4c84ac2fd97f4b154280c01910 + size: 1239580672 + name: Bratz - Rock Angelz (Denmark) + serial: SLES-53578 + version: '1.00' +- hashes: + - md5: 36c27c981bfdd9f311ecc36da4ad7071 + size: 598507520 + name: Metal Gear Solid 2 - Sons of Liberty (Europe) (En,Fr,De) (Demo) + serial: SLED-50782 + version: '1.00' +- hashes: + - md5: 626b946f71116a9ec455b41c0fb8aa3a + size: 2710274048 + name: Bonus Demo 7 (15-16) (Europe) + serial: SCED-52437 + version: '1.02' +- hashes: + - md5: 2696c838c64d0f7f0c86adc5bbc1f54a + size: 4292870144 + name: Buzz! The Big Quiz (Switzerland) (Fr,De,It) + serial: SCES-54071 + version: '1.00' +- hashes: + - md5: 865cb20ccc3b938879eb3cae955ab70c + size: 4598398976 + name: Buzz! The Mega Quiz (Switzerland) (Fr,De,It) + serial: SCES-54506 + version: '1.00' +- hashes: + - md5: f543c17d10aa576cc4ea74622a7050ee + size: 750130416 + name: Code Breaker (USA) (Unl) (v4.0) + version: '4.0' +- hashes: + - md5: bae22925feb7587573b663cc83bc1eb3 + size: 2719285248 + name: Kaido Battle 2 - Chain Reaction (Korea) + serial: SLKA-25146 + version: '1.02' +- hashes: + - md5: 92c7a59eb4650bbc8a429970640b871b + size: 748700400 + name: Time Crisis II (Europe) (Demo) + serial: SCED-50473 + version: '1.01' +- hashes: + - md5: fd46dd639a28f9f8ec7475f999d8b653 + size: 2786852864 + name: Call of Duty - World at War - Final Fronts (Korea) + serial: SLKA-25449 + version: '1.00' +- hashes: + - md5: 41ccada9d75b88b20adf6db41349df52 + size: 4511367168 + name: Shijag-ui Ilbo All Stars (Korea) + serial: SLKA-25152 + version: '1.01' +- hashes: + - md5: d46987283a22024d4b8e3e36c597035f + size: 4116643840 + name: Fight Night Round 2 (Europe, Australia) (Demo) + serial: SLED-53126 + version: '1.00' +- hashes: + - md5: 22a1b12df6fae4ecd8da5bed8cd91dd6 + size: 4192600064 + name: SOCOM 3 - U.S. Navy SEALs (USA) (Beta) (2005-09-08) +- hashes: + - md5: 3a04c1e3548d8169271336a73ae67e93 + size: 566942544 + name: Taz - Wanted (Europe) (2002-01-29) + version: Beta +- hashes: + - md5: ecb58db2de607ed68a215d052381a83b + size: 245814576 + name: Taz - Wanted (Europe) (2001-05-31) + version: Beta From 9fe8235edaf38a257fbe464fec0f1d664f607346 Mon Sep 17 00:00:00 2001 From: Mrlinkwii Date: Sat, 19 Apr 2025 15:49:29 +0100 Subject: [PATCH 162/162] tool : update generate_redump_yaml.py --- tools/generate_redump_yaml.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/generate_redump_yaml.py b/tools/generate_redump_yaml.py index d4e6b38abe..10e9178bb6 100644 --- a/tools/generate_redump_yaml.py +++ b/tools/generate_redump_yaml.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python3 + import sys import xml.etree.ElementTree as ET import re