Compare commits

..

30 Commits

Author SHA1 Message Date
Stenzek d26a2a79f1
GPU: CLUT should not be saved on CPU thread memory state 2024-12-22 15:25:24 +10:00
Stenzek c88448ed93
PageFaultHandler: Warning fix 2024-12-22 15:25:24 +10:00
Stenzek 2f460c1b79
CPU/CodeCache: Fastmem RAM faults are always writes 2024-12-22 15:25:24 +10:00
Stenzek f8750ebd1b
System: Fix pre-frame sleep getting stuck 2024-12-22 15:25:24 +10:00
Stenzek f54420f2ef
GPU: Implement PGXP for lines 2024-12-22 15:25:24 +10:00
Stenzek efd3cdd27d
GPUBackend: Remove duplicate num_vertices 2024-12-22 13:22:08 +10:00
Stenzek a919fa71aa
GameDB: PGXP-CPU for Wipeout games
Improves sprite jitter.
2024-12-21 23:08:09 +10:00
Stenzek 080d30bf0c
GPU: Slightly adjust PAL active range
Gets the PAR closer to the expected value of 59/94 with a divider of 4.
2024-12-21 23:05:30 +10:00
Stenzek ad980a7004
GPU: Fix 480i resolution display in OSD 2024-12-21 22:20:41 +10:00
Stenzek 0aa89ec7b0
System: Fix some GPU settings not updating 2024-12-21 18:29:56 +10:00
Stenzek 9b97bd5924
Packaging: Add scripts to include libc/libstdc++ in AppImage 2024-12-21 18:29:56 +10:00
Stenzek e1bd5690ac
Misc: Slightly reduce include pollution from settings.h 2024-12-21 15:28:27 +10:00
Stenzek 01dfc9ae51
GameDB: PGXP-CPU for Muppet Monster Adventure 2024-12-21 14:54:37 +10:00
Stenzek 62285dcf33
Settings: Split into GPU and main settings
That way the GPU thread copy doesn't need fields it never touches.
2024-12-21 14:53:15 +10:00
Stenzek 6b754e6759
FullscreenUI: Fix CPU/GPU thread race on achievements toggle 2024-12-21 14:40:24 +10:00
Stenzek 26db661a05
GPU: Remove global indirection 2024-12-21 14:35:12 +10:00
Stenzek c4b0430d5e
System: Remove unused SetExpansionROM() 2024-12-19 23:32:15 +10:00
Stenzek 7826c258b0
System: Frame step after runahead
Makes it behave as expected.
2024-12-19 23:32:15 +10:00
Stenzek 2d659fc3eb
GPU: Move backend work off CPU thread 2024-12-19 23:32:15 +10:00
Stenzek 831c982f3b
System: Rewrite memory save state handling
Makes it more friendly to GPU thread.
2024-12-19 22:42:28 +10:00
Stenzek 6993e6c31f
GPU/SW: Fix double draws of polyline vertices 2024-12-19 22:42:02 +10:00
Stenzek 10e2079ee4
CPU/Recompiler: Don't use far code for mtc0 cache check
Redundant for a few instructions.
2024-12-19 18:44:08 +10:00
Stenzek fe1fa765f7
CPU/Recompiler: Don't back up value to stack in mtc0
Fixes misaligned stack that could crash in log messages.
2024-12-19 18:44:08 +10:00
Stenzek 568667753d
CPU/CodeCache: Avoid log calls in faults outside of JIT code
Could be in other functions that are unsafe to call log functions from.
2024-12-19 18:44:08 +10:00
Stenzek 7116a80435
Common: Add RESTRICT macro
Gotta coerce the compiler into generating better code.
2024-12-19 16:50:03 +10:00
Stenzek 31c1cfa650
Achievements: Remove unused NeedsIdleUpdate() 2024-12-19 12:46:59 +10:00
Stenzek 2f5bdc9651
GameDB: Crash Team Racing supports JogCon 2024-12-19 12:43:02 +10:00
Stenzek 7442ec2f19
JogCon: Implement command 0x44 2024-12-19 12:42:01 +10:00
Daniel Nylander fa2442deaf
Updating Swedish translation (#3345)
* Updated Swedish translation
2024-12-19 12:06:06 +10:00
Anderson Cardoso 8df2a2c446
Atualização Português do Brasil (#3344)
Atualizado para última versão
2024-12-17 14:28:15 +10:00
87 changed files with 9966 additions and 7447 deletions

View File

@ -21454,6 +21454,7 @@ SCES-02105:
- AnalogController - AnalogController
- DigitalController - DigitalController
- NeGcon - NeGcon
- JogCon
traits: traits:
- DisablePGXPPreserveProjFP - DisablePGXPPreserveProjFP
libcrypt: true libcrypt: true
@ -21485,6 +21486,7 @@ SCUS-94426:
- AnalogController - AnalogController
- DigitalController - DigitalController
- NeGcon - NeGcon
- JogCon
traits: traits:
- DisablePGXPPreserveProjFP - DisablePGXPPreserveProjFP
metadata: metadata:
@ -106685,6 +106687,8 @@ SCES-02403:
controllers: controllers:
- AnalogController - AnalogController
- DigitalController - DigitalController
traits:
- ForcePGXPCPUMode # Fixes gaps in geometry.
metadata: metadata:
publisher: "Midway" publisher: "Midway"
developer: "Magenta Software Ltd. / Traveller's Tales" developer: "Magenta Software Ltd. / Traveller's Tales"
@ -106704,6 +106708,8 @@ SCES-03090:
controllers: controllers:
- AnalogController - AnalogController
- DigitalController - DigitalController
traits:
- ForcePGXPCPUMode # Fixes gaps in geometry.
metadata: metadata:
publisher: "Midway" publisher: "Midway"
developer: "Magenta Software Ltd. / Traveller's Tales" developer: "Magenta Software Ltd. / Traveller's Tales"
@ -106723,6 +106729,8 @@ SCES-03091:
controllers: controllers:
- AnalogController - AnalogController
- DigitalController - DigitalController
traits:
- ForcePGXPCPUMode # Fixes gaps in geometry.
metadata: metadata:
publisher: "Midway" publisher: "Midway"
developer: "Magenta Software Ltd. / Traveller's Tales" developer: "Magenta Software Ltd. / Traveller's Tales"
@ -106742,6 +106750,8 @@ SCES-03092:
controllers: controllers:
- AnalogController - AnalogController
- DigitalController - DigitalController
traits:
- ForcePGXPCPUMode # Fixes gaps in geometry.
metadata: metadata:
publisher: "Midway" publisher: "Midway"
developer: "Magenta Software Ltd. / Traveller's Tales" developer: "Magenta Software Ltd. / Traveller's Tales"
@ -106761,6 +106771,8 @@ SCES-03093:
controllers: controllers:
- AnalogController - AnalogController
- DigitalController - DigitalController
traits:
- ForcePGXPCPUMode # Fixes gaps in geometry.
metadata: metadata:
publisher: "Midway" publisher: "Midway"
developer: "Magenta Software Ltd. / Traveller's Tales" developer: "Magenta Software Ltd. / Traveller's Tales"
@ -106780,6 +106792,8 @@ SLUS-01238:
controllers: controllers:
- AnalogController - AnalogController
- DigitalController - DigitalController
traits:
- ForcePGXPCPUMode # Fixes gaps in geometry.
metadata: metadata:
publisher: "Midway" publisher: "Midway"
developer: "Magenta Software Ltd. / Traveller's Tales" developer: "Magenta Software Ltd. / Traveller's Tales"
@ -186469,6 +186483,8 @@ SCES-00010:
controllers: controllers:
- DigitalController - DigitalController
- NeGcon - NeGcon
traits:
- ForcePGXPCPUMode # Improves sprite jitter.
metadata: metadata:
publisher: "Psygnosis" publisher: "Psygnosis"
developer: "Psygnosis" developer: "Psygnosis"
@ -186491,6 +186507,8 @@ SIPS-60003:
controllers: controllers:
- DigitalController - DigitalController
- NeGcon - NeGcon
traits:
- ForcePGXPCPUMode # Improves sprite jitter.
metadata: metadata:
publisher: "Psygnosis" publisher: "Psygnosis"
developer: "Psygnosis" developer: "Psygnosis"
@ -186513,6 +186531,8 @@ SCUS-94301:
controllers: controllers:
- DigitalController - DigitalController
- NeGcon - NeGcon
traits:
- ForcePGXPCPUMode # Improves sprite jitter.
metadata: metadata:
publisher: "Psygnosis" publisher: "Psygnosis"
developer: "Psygnosis" developer: "Psygnosis"
@ -186534,6 +186554,8 @@ SLES-00327:
controllers: controllers:
- DigitalController - DigitalController
- NeGcon - NeGcon
traits:
- ForcePGXPCPUMode # Improves sprite jitter.
metadata: metadata:
publisher: "Psygnosis" publisher: "Psygnosis"
developer: "Psygnosis" developer: "Psygnosis"
@ -186550,6 +186572,8 @@ SLES-00327:
linkCable: true linkCable: true
SLED-00498: SLED-00498:
name: "WipEout 2097 (Europe) (Demo)" name: "WipEout 2097 (Europe) (Demo)"
traits:
- ForcePGXPCPUMode # Improves sprite jitter.
metadata: metadata:
genre: "** DEMO **" genre: "** DEMO **"
SCPS-10098: SCPS-10098:
@ -186562,6 +186586,8 @@ SCPS-10098:
- AnalogController - AnalogController
- DigitalController - DigitalController
- NeGcon - NeGcon
traits:
- ForcePGXPCPUMode # Improves sprite jitter.
metadata: metadata:
publisher: "Sony" publisher: "Sony"
developer: "Psygnosis Leeds Studio / The Designers Republic" developer: "Psygnosis Leeds Studio / The Designers Republic"
@ -186586,6 +186612,8 @@ SLUS-00865:
- AnalogController - AnalogController
- DigitalController - DigitalController
- NeGcon - NeGcon
traits:
- ForcePGXPCPUMode # Improves sprite jitter.
metadata: metadata:
publisher: "Sony" publisher: "Sony"
developer: "Psygnosis Leeds Studio / The Designers Republic" developer: "Psygnosis Leeds Studio / The Designers Republic"
@ -186602,6 +186630,8 @@ SLUS-00865:
linkCable: false linkCable: false
SLUS-90063: SLUS-90063:
name: "WipEout 3 (USA) (Demo)" name: "WipEout 3 (USA) (Demo)"
traits:
- ForcePGXPCPUMode # Improves sprite jitter.
metadata: metadata:
genre: "** DEMO **" genre: "** DEMO **"
SCES-02845: SCES-02845:
@ -186613,6 +186643,8 @@ SCES-02845:
- AnalogController - AnalogController
- DigitalController - DigitalController
- NeGcon - NeGcon
traits:
- ForcePGXPCPUMode # Improves sprite jitter.
metadata: metadata:
publisher: "Sony" publisher: "Sony"
developer: "Psygnosis Leeds Studio / The Designers Republic" developer: "Psygnosis Leeds Studio / The Designers Republic"
@ -186642,6 +186674,8 @@ SCPS-45078:
codes: codes:
- SCPS-45078 - SCPS-45078
- SIPS-60010 - SIPS-60010
traits:
- ForcePGXPCPUMode # Improves sprite jitter.
metadata: metadata:
publisher: "Psygnosis" publisher: "Psygnosis"
developer: "Psygnosis" developer: "Psygnosis"
@ -186664,6 +186698,8 @@ SCUS-94351:
controllers: controllers:
- DigitalController - DigitalController
- NeGcon - NeGcon
traits:
- ForcePGXPCPUMode # Improves sprite jitter.
metadata: metadata:
publisher: "Psygnosis" publisher: "Psygnosis"
developer: "Psygnosis" developer: "Psygnosis"

View File

@ -0,0 +1,29 @@
#!/usr/bin/env bash
REQUIRED_GLIBC_VERSION="__REQ_GLIBC_VERSION__"
this_dir="$(readlink -f "$(dirname "$0")")"
APPBIN="${this_dir}/usr/bin/__APPNAME__"
RUNTIME_DIR="${this_dir}/libc-runtime"
LOADER_BIN="${this_dir}/usr/bin/ld-linux"
GLIBC_VERSION=$(ldd --version | head -1 | sed -e 's/.* \([0-9.]\)/\1/')
echo "Detected glibc version ${GLIBC_VERSION}."
if [[ -z "${GLIBC_VERSION}" || ! "${GLIBC_VERSION}" < "${REQUIRED_GLIBC_VERSION}" ]]; then
echo "Using system libc/libstdc++."
exec "${APPBIN}" "$@"
fi
echo "Using bundled libc/libstdc++ from ${RUNTIME_DIR}."
if [ -z "$LD_LIBRARY_PATH" ]; then
export LD_LIBRARY_PATH="${RUNTIME_DIR}"
else
export LD_LIBRARY_PATH="${RUNTIME_DIR}:${LD_LIBRARY_PATH}"
fi
exec "${LOADER_BIN}" "${APPBIN}" "$@"

View File

@ -0,0 +1,81 @@
#!/usr/bin/env bash
set -e
function retry_command {
# Package servers tend to be unreliable at times..
# Retry a bunch of times.
local RETRIES=10
for i in $(seq 1 "$RETRIES"); do
"$@" && break
if [ "$i" == "$RETRIES" ]; then
echo "Command \"$@\" failed after ${RETRIES} retries."
exit 1
fi
done
}
this_dir="$(readlink -f "$(dirname "$0")")"
if [ "$#" -ne 5 ]; then
echo "Syntax: $0 <path to AppDir> <.deb arch> <triple> <ubuntu mirror> <binary to run>"
echo "e.g. $0 DuckStation.AppDir amd64 x86_64-linux-gnu https://archive.ubuntu.com/ubuntu/ duckstation-qt"
exit 1
fi
APPDIR=$1
DEBARCH=$2
TRIPLE=$3
MIRROR=$4
APPNAME=$5
LIBC_PACKAGE_URL="${MIRROR}/pool/main/g/glibc/libc6_2.35-0ubuntu3.8_${DEBARCH}.deb"
LIBGCCS_PACKAGE_URL="${MIRROR}/pool/main/g/gcc-12/libgcc-s1_12.3.0-1ubuntu1~22.04_${DEBARCH}.deb"
LIBSTDCXX_PACKAGE_URL="${MIRROR}/pool/main/g/gcc-12/libstdc++6_12.3.0-1ubuntu1~22.04_${DEBARCH}.deb"
GLIBC_VERSION=2.35
mkdir "temp"
cd "temp"
retry_command wget -O "libc.deb" "${LIBC_PACKAGE_URL}"
retry_command wget -O "libgccs.deb" "${LIBGCCS_PACKAGE_URL}"
retry_command wget -O "libstdc++.deb" "${LIBSTDCXX_PACKAGE_URL}"
dpkg -x "libc.deb" .
dpkg -x "libgccs.deb" .
dpkg -x "libstdc++.deb" .
# Copy everything into AppDir
RUNTIME="${APPDIR}/libc-runtime"
mkdir -p "${RUNTIME}"
# libc.so.6 and friends
cd "lib/${TRIPLE}"
cp -v * "${RUNTIME}"
cd ../../
# libstdc++
cd "usr/lib/${TRIPLE}"
cp -v * "${RUNTIME}" || true
cd ../../..
# done with temps now
cd ..
rm -fr temp
# Not risking mixing resolvers...
cd "${RUNTIME}"
rm -vf libnss_*
# Move ld-linux.so.2 into the binary directory so we can preserve arg0's directory
mv -v "ld-linux-"*.so.2 "${APPDIR}/usr/bin/ld-linux"
# Set up the replacement apprun script
cd "${APPDIR}"
rm -f AppRun.wrapped
cp "${this_dir}/inject-libc-apprun.sh" AppRun.wrapped
sed -i -e "s/__APPNAME__/${APPNAME}/" AppRun.wrapped
sed -i -e "s/__REQ_GLIBC_VERSION__/${GLIBC_VERSION}/" AppRun.wrapped
echo Done.

View File

@ -3131,6 +3131,11 @@ public:
ALWAYS_INLINE GSVector2 zw() const { return GSVector2(vget_high_s32(v4s)); } ALWAYS_INLINE GSVector2 zw() const { return GSVector2(vget_high_s32(v4s)); }
ALWAYS_INLINE static GSVector4 xyxy(const GSVector2& l, const GSVector2& h)
{
return GSVector4(vcombine_f32(l.v2s, h.v2s));
}
#define VECTOR4_SHUFFLE_4(xs, xn, ys, yn, zs, zn, ws, wn) \ #define VECTOR4_SHUFFLE_4(xs, xn, ys, yn, zs, zn, ws, wn) \
ALWAYS_INLINE GSVector4 xs##ys##zs##ws() const \ ALWAYS_INLINE GSVector4 xs##ys##zs##ws() const \
{ \ { \

View File

@ -2316,6 +2316,11 @@ public:
ALWAYS_INLINE GSVector2 zw() const { return GSVector2(z, w); } ALWAYS_INLINE GSVector2 zw() const { return GSVector2(z, w); }
ALWAYS_INLINE static GSVector4 xyxy(const GSVector2& l, const GSVector2& h)
{
return GSVector4(l.x, l.y, h.x, h.y);
}
#define VECTOR4_SHUFFLE_4(xs, xn, ys, yn, zs, zn, ws, wn) \ #define VECTOR4_SHUFFLE_4(xs, xn, ys, yn, zs, zn, ws, wn) \
ALWAYS_INLINE GSVector4 xs##ys##zs##ws() const { return GSVector4(F32[xn], F32[yn], F32[zn], F32[wn]); } ALWAYS_INLINE GSVector4 xs##ys##zs##ws() const { return GSVector4(F32[xn], F32[yn], F32[zn], F32[wn]); }

View File

@ -14,9 +14,9 @@
#define CPU_ARCH_SIMD 1 #define CPU_ARCH_SIMD 1
#define CPU_ARCH_SSE 1 #define CPU_ARCH_SSE 1
#include <emmintrin.h> #include <emmintrin.h>
#include <tmmintrin.h>
#include <smmintrin.h>
#include <immintrin.h> #include <immintrin.h>
#include <smmintrin.h>
#include <tmmintrin.h>
#if defined(__AVX2__) #if defined(__AVX2__)
#define CPU_ARCH_AVX 1 #define CPU_ARCH_AVX 1
@ -96,3 +96,40 @@ ALWAYS_INLINE_RELEASE static void MemsetPtrs(T* ptr, T value, u32 count)
for (u32 i = 0; i < remaining_count; i++) for (u32 i = 0; i < remaining_count; i++)
*(dest++) = value; *(dest++) = value;
} }
ALWAYS_INLINE static void MultiPause()
{
#if defined(CPU_ARCH_X86) || defined(CPU_ARCH_X64)
_mm_pause();
_mm_pause();
_mm_pause();
_mm_pause();
_mm_pause();
_mm_pause();
_mm_pause();
_mm_pause();
#elif defined(CPU_ARCH_ARM64) && defined(_MSC_VER)
__isb(_ARM64_BARRIER_SY);
__isb(_ARM64_BARRIER_SY);
__isb(_ARM64_BARRIER_SY);
__isb(_ARM64_BARRIER_SY);
__isb(_ARM64_BARRIER_SY);
__isb(_ARM64_BARRIER_SY);
__isb(_ARM64_BARRIER_SY);
__isb(_ARM64_BARRIER_SY);
#elif defined(CPU_ARCH_ARM64) || defined(CPU_ARCH_ARM32)
__asm__ __volatile__("isb");
__asm__ __volatile__("isb");
__asm__ __volatile__("isb");
__asm__ __volatile__("isb");
__asm__ __volatile__("isb");
__asm__ __volatile__("isb");
__asm__ __volatile__("isb");
__asm__ __volatile__("isb");
#elif defined(CPU_ARCH_RISCV64)
// Probably wrong... pause is optional :/
asm volatile("fence" ::: "memory");
#else
#pragma warning("Missing implementation")
#endif
}

View File

@ -27,6 +27,7 @@
X(GPU) \ X(GPU) \
X(GPUDevice) \ X(GPUDevice) \
X(GPUDump) \ X(GPUDump) \
X(GPUThread) \
X(GPU_SW) \ X(GPU_SW) \
X(GPU_HW) \ X(GPU_HW) \
X(GameDatabase) \ X(GameDatabase) \

View File

@ -93,6 +93,13 @@ char (&__countof_ArraySizeHelper(T (&array)[N]))[N];
} while (0) } while (0)
#endif #endif
// __restrict, potentially enables optimization by hinting the compiler that the object is unique.
#ifdef _MSC_VER
#define RESTRICT __restrict
#else
#define RESTRICT __restrict__
#endif
// disable warnings that show up at warning level 4 // disable warnings that show up at warning level 4
// TODO: Move to build system instead // TODO: Move to build system instead
#ifdef _MSC_VER #ifdef _MSC_VER

View File

@ -61,10 +61,11 @@ add_library(core
gpu_shadergen.h gpu_shadergen.h
gpu_sw.cpp gpu_sw.cpp
gpu_sw.h gpu_sw.h
gpu_sw_backend.cpp
gpu_sw_backend.h
gpu_sw_rasterizer.cpp gpu_sw_rasterizer.cpp
gpu_sw_rasterizer.h gpu_sw_rasterizer.h
gpu_thread.cpp
gpu_thread.h
gpu_thread_commands.h
gpu_types.h gpu_types.h
guncon.cpp guncon.cpp
guncon.h guncon.h
@ -73,8 +74,6 @@ add_library(core
gte_types.h gte_types.h
host.cpp host.cpp
host.h host.h
host_interface_progress_callback.cpp
host_interface_progress_callback.h
hotkeys.cpp hotkeys.cpp
input_types.h input_types.h
imgui_overlays.cpp imgui_overlays.cpp

View File

@ -9,7 +9,9 @@
#include "bus.h" #include "bus.h"
#include "cpu_core.h" #include "cpu_core.h"
#include "fullscreen_ui.h" #include "fullscreen_ui.h"
#include "gpu_thread.h"
#include "host.h" #include "host.h"
#include "imgui_overlays.h"
#include "system.h" #include "system.h"
#include "scmversion/scmversion.h" #include "scmversion/scmversion.h"
@ -480,7 +482,9 @@ void Achievements::UpdateGlyphRanges()
std::sort(sorted_codepoints.begin(), sorted_codepoints.end()); std::sort(sorted_codepoints.begin(), sorted_codepoints.end());
// Compact codepoints to ranges. // Compact codepoints to ranges.
ImGuiManager::SetEmojiFontRange(ImGuiManager::CompactFontRange(sorted_codepoints)); GPUThread::RunOnThread([sorted_codepoints = std::move(sorted_codepoints)]() {
ImGuiManager::SetEmojiFontRange(ImGuiManager::CompactFontRange(sorted_codepoints));
});
} }
bool Achievements::IsActive() bool Achievements::IsActive()
@ -828,15 +832,6 @@ void Achievements::IdleUpdate()
rc_client_idle(s_state.client); rc_client_idle(s_state.client);
} }
bool Achievements::NeedsIdleUpdate()
{
if (!IsActive())
return false;
const auto lock = GetLock();
return (s_state.http_downloader && s_state.http_downloader->HasAnyRequests());
}
void Achievements::FrameUpdate() void Achievements::FrameUpdate()
{ {
if (!IsActive()) if (!IsActive())
@ -1182,7 +1177,7 @@ void Achievements::ClientLoadGameCallback(int result, const char* error_message,
// ensure fullscreen UI is ready for notifications // ensure fullscreen UI is ready for notifications
if (display_summary) if (display_summary)
FullscreenUI::Initialize(); GPUThread::RunOnThread(&FullscreenUI::Initialize);
char url_buf[URL_BUFFER_SIZE]; char url_buf[URL_BUFFER_SIZE];
if (int err = rc_client_game_get_image_url(info, url_buf, std::size(url_buf)); err == RC_OK) if (int err = rc_client_game_get_image_url(info, url_buf, std::size(url_buf)); err == RC_OK)
@ -1238,7 +1233,7 @@ void Achievements::ClearGameHash()
void Achievements::DisplayAchievementSummary() void Achievements::DisplayAchievementSummary()
{ {
if (g_settings.achievements_notifications && FullscreenUI::Initialize()) if (g_settings.achievements_notifications)
{ {
std::string title; std::string title;
if (IsHardcoreModeActive()) if (IsHardcoreModeActive())
@ -1263,8 +1258,14 @@ void Achievements::DisplayAchievementSummary()
summary = TRANSLATE_STR("Achievements", "This game has no achievements."); summary = TRANSLATE_STR("Achievements", "This game has no achievements.");
} }
ImGuiFullscreen::AddNotification("achievement_summary", ACHIEVEMENT_SUMMARY_NOTIFICATION_TIME, std::move(title), GPUThread::RunOnThread(
std::move(summary), s_state.game_icon); [title = std::move(title), summary = std::move(summary), icon = s_state.game_icon]() mutable {
if (!FullscreenUI::Initialize())
return;
ImGuiFullscreen::AddNotification("achievement_summary", ACHIEVEMENT_SUMMARY_NOTIFICATION_TIME, std::move(title),
std::move(summary), std::move(icon));
});
} }
// Technically not going through the resource API, but since we're passing this to something else, we can't. // Technically not going through the resource API, but since we're passing this to something else, we can't.
@ -1274,12 +1275,16 @@ void Achievements::DisplayAchievementSummary()
void Achievements::DisplayHardcoreDeferredMessage() void Achievements::DisplayHardcoreDeferredMessage()
{ {
if (g_settings.achievements_hardcore_mode && !s_state.hardcore_mode && System::IsValid() && if (g_settings.achievements_hardcore_mode && !s_state.hardcore_mode && System::IsValid())
FullscreenUI::Initialize())
{ {
ImGuiFullscreen::ShowToast(std::string(), GPUThread::RunOnThread([]() {
TRANSLATE_STR("Achievements", "Hardcore mode will be enabled on system reset."), if (!FullscreenUI::Initialize())
Host::OSD_WARNING_DURATION); return;
ImGuiFullscreen::ShowToast(std::string(),
TRANSLATE_STR("Achievements", "Hardcore mode will be enabled on system reset."),
Host::OSD_WARNING_DURATION);
});
} }
} }
@ -1301,7 +1306,7 @@ void Achievements::HandleUnlockEvent(const rc_client_event_t* event)
INFO_LOG("Achievement {} ({}) for game {} unlocked", cheevo->title, cheevo->id, s_state.game_id); INFO_LOG("Achievement {} ({}) for game {} unlocked", cheevo->title, cheevo->id, s_state.game_id);
UpdateGameSummary(); UpdateGameSummary();
if (g_settings.achievements_notifications && FullscreenUI::Initialize()) if (g_settings.achievements_notifications)
{ {
std::string title; std::string title;
if (cheevo->category == RC_CLIENT_ACHIEVEMENT_CATEGORY_UNOFFICIAL) if (cheevo->category == RC_CLIENT_ACHIEVEMENT_CATEGORY_UNOFFICIAL)
@ -1311,9 +1316,15 @@ void Achievements::HandleUnlockEvent(const rc_client_event_t* event)
std::string badge_path = GetAchievementBadgePath(cheevo, cheevo->state); std::string badge_path = GetAchievementBadgePath(cheevo, cheevo->state);
ImGuiFullscreen::AddNotification(fmt::format("achievement_unlock_{}", cheevo->id), GPUThread::RunOnThread([id = cheevo->id, duration = g_settings.achievements_notification_duration,
static_cast<float>(g_settings.achievements_notification_duration), title = std::move(title), description = std::string(cheevo->description),
std::move(title), cheevo->description, std::move(badge_path)); badge_path = std::move(badge_path)]() mutable {
if (!FullscreenUI::Initialize())
return;
ImGuiFullscreen::AddNotification(fmt::format("achievement_unlock_{}", id), static_cast<float>(duration),
std::move(title), std::move(description), std::move(badge_path));
});
} }
if (g_settings.achievements_sound_effects) if (g_settings.achievements_sound_effects)
@ -1325,7 +1336,7 @@ void Achievements::HandleGameCompleteEvent(const rc_client_event_t* event)
INFO_LOG("Game {} complete", s_state.game_id); INFO_LOG("Game {} complete", s_state.game_id);
UpdateGameSummary(); UpdateGameSummary();
if (g_settings.achievements_notifications && FullscreenUI::Initialize()) if (g_settings.achievements_notifications)
{ {
std::string title = fmt::format(TRANSLATE_FS("Achievements", "Mastered {}"), s_state.game_title); std::string title = fmt::format(TRANSLATE_FS("Achievements", "Mastered {}"), s_state.game_title);
std::string message = fmt::format( std::string message = fmt::format(
@ -1334,8 +1345,14 @@ void Achievements::HandleGameCompleteEvent(const rc_client_event_t* event)
s_state.game_summary.num_unlocked_achievements), s_state.game_summary.num_unlocked_achievements),
TRANSLATE_PLURAL_STR("Achievements", "%n points", "Achievement points", s_state.game_summary.points_unlocked)); TRANSLATE_PLURAL_STR("Achievements", "%n points", "Achievement points", s_state.game_summary.points_unlocked));
ImGuiFullscreen::AddNotification("achievement_mastery", GAME_COMPLETE_NOTIFICATION_TIME, std::move(title), GPUThread::RunOnThread(
std::move(message), s_state.game_icon); [title = std::move(title), message = std::move(message), icon = s_state.game_icon]() mutable {
if (!FullscreenUI::Initialize())
return;
ImGuiFullscreen::AddNotification("achievement_mastery", GAME_COMPLETE_NOTIFICATION_TIME, std::move(title),
std::move(message), std::move(icon));
});
} }
} }
@ -1343,14 +1360,19 @@ void Achievements::HandleLeaderboardStartedEvent(const rc_client_event_t* event)
{ {
DEV_LOG("Leaderboard {} ({}) started", event->leaderboard->id, event->leaderboard->title); DEV_LOG("Leaderboard {} ({}) started", event->leaderboard->id, event->leaderboard->title);
if (g_settings.achievements_leaderboard_notifications && FullscreenUI::Initialize()) if (g_settings.achievements_leaderboard_notifications)
{ {
std::string title = event->leaderboard->title; std::string title = event->leaderboard->title;
std::string message = TRANSLATE_STR("Achievements", "Leaderboard attempt started."); std::string message = TRANSLATE_STR("Achievements", "Leaderboard attempt started.");
ImGuiFullscreen::AddNotification(fmt::format("leaderboard_{}", event->leaderboard->id), GPUThread::RunOnThread([id = event->leaderboard->id, title = std::move(title), message = std::move(message),
LEADERBOARD_STARTED_NOTIFICATION_TIME, std::move(title), std::move(message), icon = s_state.game_icon]() mutable {
s_state.game_icon); if (!FullscreenUI::Initialize())
return;
ImGuiFullscreen::AddNotification(fmt::format("leaderboard_{}", id), LEADERBOARD_STARTED_NOTIFICATION_TIME,
std::move(title), std::move(message), std::move(icon));
});
} }
} }
@ -1358,14 +1380,19 @@ void Achievements::HandleLeaderboardFailedEvent(const rc_client_event_t* event)
{ {
DEV_LOG("Leaderboard {} ({}) failed", event->leaderboard->id, event->leaderboard->title); DEV_LOG("Leaderboard {} ({}) failed", event->leaderboard->id, event->leaderboard->title);
if (g_settings.achievements_leaderboard_notifications && FullscreenUI::Initialize()) if (g_settings.achievements_leaderboard_notifications)
{ {
std::string title = event->leaderboard->title; std::string title = event->leaderboard->title;
std::string message = TRANSLATE_STR("Achievements", "Leaderboard attempt failed."); std::string message = TRANSLATE_STR("Achievements", "Leaderboard attempt failed.");
ImGuiFullscreen::AddNotification(fmt::format("leaderboard_{}", event->leaderboard->id), GPUThread::RunOnThread([id = event->leaderboard->id, title = std::move(title), message = std::move(message),
LEADERBOARD_FAILED_NOTIFICATION_TIME, std::move(title), std::move(message), icon = s_state.game_icon]() mutable {
s_state.game_icon); if (!FullscreenUI::Initialize())
return;
ImGuiFullscreen::AddNotification(fmt::format("leaderboard_{}", id), LEADERBOARD_FAILED_NOTIFICATION_TIME,
std::move(title), std::move(message), std::move(icon));
});
} }
} }
@ -1373,7 +1400,7 @@ void Achievements::HandleLeaderboardSubmittedEvent(const rc_client_event_t* even
{ {
DEV_LOG("Leaderboard {} ({}) submitted", event->leaderboard->id, event->leaderboard->title); DEV_LOG("Leaderboard {} ({}) submitted", event->leaderboard->id, event->leaderboard->title);
if (g_settings.achievements_leaderboard_notifications && FullscreenUI::Initialize()) if (g_settings.achievements_leaderboard_notifications)
{ {
static const char* value_strings[NUM_RC_CLIENT_LEADERBOARD_FORMATS] = { static const char* value_strings[NUM_RC_CLIENT_LEADERBOARD_FORMATS] = {
TRANSLATE_NOOP("Achievements", "Your Time: {}{}"), TRANSLATE_NOOP("Achievements", "Your Time: {}{}"),
@ -1389,9 +1416,14 @@ void Achievements::HandleLeaderboardSubmittedEvent(const rc_client_event_t* even
event->leaderboard->tracker_value ? event->leaderboard->tracker_value : "Unknown", event->leaderboard->tracker_value ? event->leaderboard->tracker_value : "Unknown",
g_settings.achievements_spectator_mode ? std::string_view() : TRANSLATE_SV("Achievements", " (Submitting)")); g_settings.achievements_spectator_mode ? std::string_view() : TRANSLATE_SV("Achievements", " (Submitting)"));
ImGuiFullscreen::AddNotification(fmt::format("leaderboard_{}", event->leaderboard->id), GPUThread::RunOnThread([id = event->leaderboard->id, title = std::move(title), message = std::move(message),
static_cast<float>(g_settings.achievements_leaderboard_duration), std::move(title), icon = s_state.game_icon]() mutable {
std::move(message), s_state.game_icon); if (!FullscreenUI::Initialize())
return;
ImGuiFullscreen::AddNotification(fmt::format("leaderboard_{}", id),
static_cast<float>(g_settings.achievements_leaderboard_duration),
std::move(title), std::move(message), std::move(icon));
});
} }
if (g_settings.achievements_sound_effects) if (g_settings.achievements_sound_effects)
@ -1403,7 +1435,7 @@ void Achievements::HandleLeaderboardScoreboardEvent(const rc_client_event_t* eve
DEV_LOG("Leaderboard {} scoreboard rank {} of {}", event->leaderboard_scoreboard->leaderboard_id, DEV_LOG("Leaderboard {} scoreboard rank {} of {}", event->leaderboard_scoreboard->leaderboard_id,
event->leaderboard_scoreboard->new_rank, event->leaderboard_scoreboard->num_entries); event->leaderboard_scoreboard->new_rank, event->leaderboard_scoreboard->num_entries);
if (g_settings.achievements_leaderboard_notifications && FullscreenUI::Initialize()) if (g_settings.achievements_leaderboard_notifications)
{ {
static const char* value_strings[NUM_RC_CLIENT_LEADERBOARD_FORMATS] = { static const char* value_strings[NUM_RC_CLIENT_LEADERBOARD_FORMATS] = {
TRANSLATE_NOOP("Achievements", "Your Time: {} (Best: {})"), TRANSLATE_NOOP("Achievements", "Your Time: {} (Best: {})"),
@ -1420,9 +1452,15 @@ void Achievements::HandleLeaderboardScoreboardEvent(const rc_client_event_t* eve
event->leaderboard_scoreboard->submitted_score, event->leaderboard_scoreboard->best_score), event->leaderboard_scoreboard->submitted_score, event->leaderboard_scoreboard->best_score),
event->leaderboard_scoreboard->new_rank, event->leaderboard_scoreboard->num_entries); event->leaderboard_scoreboard->new_rank, event->leaderboard_scoreboard->num_entries);
ImGuiFullscreen::AddNotification(fmt::format("leaderboard_{}", event->leaderboard->id), GPUThread::RunOnThread([id = event->leaderboard->id, title = std::move(title), message = std::move(message),
static_cast<float>(g_settings.achievements_leaderboard_duration), std::move(title), icon = s_state.game_icon]() mutable {
std::move(message), s_state.game_icon); if (!FullscreenUI::Initialize())
return;
ImGuiFullscreen::AddNotification(fmt::format("leaderboard_{}", id),
static_cast<float>(g_settings.achievements_leaderboard_duration),
std::move(title), std::move(message), std::move(icon));
});
} }
} }
@ -1552,26 +1590,30 @@ void Achievements::HandleServerDisconnectedEvent(const rc_client_event_t* event)
{ {
WARNING_LOG("Server disconnected."); WARNING_LOG("Server disconnected.");
if (FullscreenUI::Initialize()) GPUThread::RunOnThread([]() {
{ if (!FullscreenUI::Initialize())
return;
ImGuiFullscreen::ShowToast( ImGuiFullscreen::ShowToast(
TRANSLATE_STR("Achievements", "Achievements Disconnected"), TRANSLATE_STR("Achievements", "Achievements Disconnected"),
TRANSLATE_STR("Achievements", TRANSLATE_STR("Achievements",
"An unlock request could not be completed. We will keep retrying to submit this request."), "An unlock request could not be completed. We will keep retrying to submit this request."),
Host::OSD_ERROR_DURATION); Host::OSD_ERROR_DURATION);
} });
} }
void Achievements::HandleServerReconnectedEvent(const rc_client_event_t* event) void Achievements::HandleServerReconnectedEvent(const rc_client_event_t* event)
{ {
WARNING_LOG("Server reconnected."); WARNING_LOG("Server reconnected.");
if (FullscreenUI::Initialize()) GPUThread::RunOnThread([]() {
{ if (!FullscreenUI::Initialize())
return;
ImGuiFullscreen::ShowToast(TRANSLATE_STR("Achievements", "Achievements Reconnected"), ImGuiFullscreen::ShowToast(TRANSLATE_STR("Achievements", "Achievements Reconnected"),
TRANSLATE_STR("Achievements", "All pending unlock requests have completed."), TRANSLATE_STR("Achievements", "All pending unlock requests have completed."),
Host::OSD_INFO_DURATION); Host::OSD_INFO_DURATION);
} });
} }
void Achievements::ResetClient() void Achievements::ResetClient()
@ -1649,12 +1691,17 @@ void Achievements::SetHardcoreMode(bool enabled, bool force_display_message)
// new mode // new mode
s_state.hardcore_mode = enabled; s_state.hardcore_mode = enabled;
if (System::IsValid() && (HasActiveGame() || force_display_message) && FullscreenUI::Initialize()) if (System::IsValid() && (HasActiveGame() || force_display_message))
{ {
ImGuiFullscreen::ShowToast(std::string(), GPUThread::RunOnThread([enabled]() {
enabled ? TRANSLATE_STR("Achievements", "Hardcore mode is now enabled.") : if (!FullscreenUI::Initialize())
TRANSLATE_STR("Achievements", "Hardcore mode is now disabled."), return;
Host::OSD_INFO_DURATION);
ImGuiFullscreen::ShowToast(std::string(),
enabled ? TRANSLATE_STR("Achievements", "Hardcore mode is now enabled.") :
TRANSLATE_STR("Achievements", "Hardcore mode is now disabled."),
Host::OSD_INFO_DURATION);
});
} }
rc_client_set_hardcore_enabled(s_state.client, enabled); rc_client_set_hardcore_enabled(s_state.client, enabled);
@ -1696,8 +1743,15 @@ bool Achievements::DoState(StateWrapper& sw)
// before deserializing, otherwise that state's going to get lost. // before deserializing, otherwise that state's going to get lost.
if (!IsUsingRAIntegration() && s_state.load_game_request) if (!IsUsingRAIntegration() && s_state.load_game_request)
{ {
Host::DisplayLoadingScreen("Downloading achievements data..."); // Messy because GPU-thread, but at least it looks pretty.
GPUThread::RunOnThread([]() {
FullscreenUI::OpenLoadingScreen(ImGuiManager::LOGO_IMAGE_NAME,
TRANSLATE_SV("Achievements", "Downloading achievements data..."));
});
s_state.http_downloader->WaitForAllRequests(); s_state.http_downloader->WaitForAllRequests();
GPUThread::RunOnThread([]() { FullscreenUI::CloseLoadingScreen(); });
} }
u32 data_size = 0; u32 data_size = 0;
@ -1966,7 +2020,7 @@ void Achievements::ShowLoginNotification()
if (!user) if (!user)
return; return;
if (g_settings.achievements_notifications && FullscreenUI::Initialize()) if (g_settings.achievements_notifications)
{ {
std::string badge_path = GetLoggedInUserBadgePath(); std::string badge_path = GetLoggedInUserBadgePath();
std::string title = user->display_name; std::string title = user->display_name;
@ -1975,8 +2029,14 @@ void Achievements::ShowLoginNotification()
std::string summary = fmt::format(TRANSLATE_FS("Achievements", "Score: {} ({} softcore)\nUnread messages: {}"), std::string summary = fmt::format(TRANSLATE_FS("Achievements", "Score: {} ({} softcore)\nUnread messages: {}"),
user->score, user->score_softcore, user->num_unread_messages); user->score, user->score_softcore, user->num_unread_messages);
ImGuiFullscreen::AddNotification("achievements_login", LOGIN_NOTIFICATION_TIME, std::move(title), GPUThread::RunOnThread(
std::move(summary), std::move(badge_path)); [title = std::move(title), summary = std::move(summary), badge_path = std::move(badge_path)]() mutable {
if (!FullscreenUI::Initialize())
return;
ImGuiFullscreen::AddNotification("achievements_login", LOGIN_NOTIFICATION_TIME, std::move(title),
std::move(summary), std::move(badge_path));
});
} }
} }
@ -2075,6 +2135,15 @@ bool Achievements::ConfirmHardcoreModeDisable(const char* trigger)
void Achievements::ConfirmHardcoreModeDisableAsync(const char* trigger, std::function<void(bool)> callback) void Achievements::ConfirmHardcoreModeDisableAsync(const char* trigger, std::function<void(bool)> callback)
{ {
auto real_callback = [callback = std::move(callback)](bool res) mutable {
// don't run the callback in the middle of rendering the UI
Host::RunOnCPUThread([callback = std::move(callback), res]() {
if (res)
DisableHardcoreMode();
callback(res);
});
};
#ifndef __ANDROID__ #ifndef __ANDROID__
#ifdef ENABLE_RAINTEGRATION #ifdef ENABLE_RAINTEGRATION
if (IsUsingRAIntegration()) if (IsUsingRAIntegration())
@ -2085,45 +2154,38 @@ void Achievements::ConfirmHardcoreModeDisableAsync(const char* trigger, std::fun
} }
#endif #endif
if (!FullscreenUI::Initialize()) GPUThread::RunOnThread([trigger = std::string(trigger), real_callback = std::move(real_callback)]() mutable {
{ if (!FullscreenUI::Initialize())
Host::AddOSDMessage(fmt::format(TRANSLATE_FS("Achievements", "Cannot {} while hardcode mode is active."), trigger), {
Host::OSD_WARNING_DURATION); Host::AddOSDMessage(
callback(false); fmt::format(TRANSLATE_FS("Achievements", "Cannot {} while hardcode mode is active."), trigger),
return; Host::OSD_WARNING_DURATION);
} real_callback(false);
return;
}
auto real_callback = [callback = std::move(callback)](bool res) mutable { ImGuiFullscreen::OpenConfirmMessageDialog(
// don't run the callback in the middle of rendering the UI TRANSLATE_STR("Achievements", "Confirm Hardcore Mode"),
Host::RunOnCPUThread([callback = std::move(callback), res]() { fmt::format(TRANSLATE_FS("Achievements",
if (res) "{0} cannot be performed while hardcore mode is active. Do you "
DisableHardcoreMode(); "want to disable hardcore mode? {0} will be cancelled if you select No."),
callback(res); trigger),
}); std::move(real_callback), fmt::format(ICON_FA_CHECK " {}", TRANSLATE_SV("Achievements", "Yes")),
}; fmt::format(ICON_FA_TIMES " {}", TRANSLATE_SV("Achievements", "No")));
});
ImGuiFullscreen::OpenConfirmMessageDialog(
TRANSLATE_STR("Achievements", "Confirm Hardcore Mode"),
fmt::format(TRANSLATE_FS("Achievements", "{0} cannot be performed while hardcore mode is active. Do you "
"want to disable hardcore mode? {0} will be cancelled if you select No."),
trigger),
std::move(real_callback), fmt::format(ICON_FA_CHECK " {}", TRANSLATE_SV("Achievements", "Yes")),
fmt::format(ICON_FA_TIMES " {}", TRANSLATE_SV("Achievements", "No")));
#else #else
Host::AddOSDMessage(fmt::format(TRANSLATE_FS("Achievements", "Cannot {} while hardcode mode is active."), trigger), Host::ConfirmMessageAsync(
Host::OSD_WARNING_DURATION); TRANSLATE_STR("Achievements", "Confirm Hardcore Mode"),
callback(false); fmt::format(TRANSLATE_FS("Achievements", "{0} cannot be performed while hardcore mode is active. Do you want to "
"disable hardcore mode? {0} will be cancelled if you select No."),
trigger),
std::move(real_callback));
#endif #endif
} }
void Achievements::ClearUIState() void Achievements::ClearUIState()
{ {
#ifndef __ANDROID__
if (FullscreenUI::IsAchievementsWindowOpen() || FullscreenUI::IsLeaderboardsWindowOpen())
FullscreenUI::ReturnToPreviousWindow();
CloseLeaderboard(); CloseLeaderboard();
#endif
s_state.achievement_badge_paths = {}; s_state.achievement_badge_paths = {};
@ -2403,11 +2465,15 @@ void Achievements::DrawAchievementsWindow()
using ImGuiFullscreen::LayoutScale; using ImGuiFullscreen::LayoutScale;
using ImGuiFullscreen::UIStyle; using ImGuiFullscreen::UIStyle;
if (!s_state.achievement_list)
return;
auto lock = Achievements::GetLock(); auto lock = Achievements::GetLock();
// achievements can get turned off via the main UI
if (!s_state.achievement_list)
{
FullscreenUI::ReturnToPreviousWindow();
return;
}
static constexpr float alpha = 0.8f; static constexpr float alpha = 0.8f;
static constexpr float heading_alpha = 0.95f; static constexpr float heading_alpha = 0.95f;
static constexpr float heading_height_unscaled = 110.0f; static constexpr float heading_height_unscaled = 110.0f;
@ -2779,7 +2845,12 @@ void Achievements::DrawLeaderboardsWindow()
static constexpr float heading_height_unscaled = 110.0f; static constexpr float heading_height_unscaled = 110.0f;
static constexpr float tab_height_unscaled = 50.0f; static constexpr float tab_height_unscaled = 50.0f;
auto lock = Achievements::GetLock(); const auto lock = Achievements::GetLock();
if (!s_state.leaderboard_list)
{
FullscreenUI::ReturnToPreviousWindow();
return;
}
const bool is_leaderboard_open = (s_state.open_leaderboard != nullptr); const bool is_leaderboard_open = (s_state.open_leaderboard != nullptr);
bool close_leaderboard_on_exit = false; bool close_leaderboard_on_exit = false;
@ -3370,7 +3441,6 @@ void Achievements::CloseLeaderboard()
} }
s_state.open_leaderboard = nullptr; s_state.open_leaderboard = nullptr;
ImGuiFullscreen::QueueResetFocus(ImGuiFullscreen::FocusResetType::Other);
} }
#if defined(_WIN32) #if defined(_WIN32)

View File

@ -58,9 +58,6 @@ void FrameUpdate();
/// Called when the system is paused, because FrameUpdate() won't be getting called. /// Called when the system is paused, because FrameUpdate() won't be getting called.
void IdleUpdate(); void IdleUpdate();
/// Returns true if idle updates are necessary (e.g. outstanding requests).
bool NeedsIdleUpdate();
/// Saves/loads state. /// Saves/loads state.
bool DoState(StateWrapper& sw); bool DoState(StateWrapper& sw);

View File

@ -10,6 +10,7 @@
#include "common/bitutils.h" #include "common/bitutils.h"
#include "common/log.h" #include "common/log.h"
#include "common/settings_interface.h"
#include "common/string_util.h" #include "common/string_util.h"
#include "IconsFontAwesome5.h" #include "IconsFontAwesome5.h"
@ -340,14 +341,14 @@ std::unique_ptr<AnalogJoystick> AnalogJoystick::Create(u32 index)
static const Controller::ControllerBindingInfo s_binding_info[] = { static const Controller::ControllerBindingInfo s_binding_info[] = {
#define BUTTON(name, display_name, icon_name, button, genb) \ #define BUTTON(name, display_name, icon_name, button, genb) \
{ \ {name, display_name, icon_name, static_cast<u32>(button), InputBindingInfo::Type::Button, genb}
name, display_name, icon_name, static_cast<u32>(button), InputBindingInfo::Type::Button, genb \
}
#define AXIS(name, display_name, icon_name, halfaxis, genb) \ #define AXIS(name, display_name, icon_name, halfaxis, genb) \
{ \ {name, \
name, display_name, icon_name, static_cast<u32>(AnalogJoystick::Button::Count) + static_cast<u32>(halfaxis), \ display_name, \
InputBindingInfo::Type::HalfAxis, genb \ icon_name, \
} static_cast<u32>(AnalogJoystick::Button::Count) + static_cast<u32>(halfaxis), \
InputBindingInfo::Type::HalfAxis, \
genb}
// clang-format off // clang-format off
BUTTON("Up", TRANSLATE_NOOP("AnalogJoystick", "D-Pad Up"), ICON_PF_DPAD_UP, AnalogJoystick::Button::Up, GenericInputBinding::DPadUp), BUTTON("Up", TRANSLATE_NOOP("AnalogJoystick", "D-Pad Up"), ICON_PF_DPAD_UP, AnalogJoystick::Button::Up, GenericInputBinding::DPadUp),

View File

@ -1875,7 +1875,7 @@ template<MemoryAccessSize size>
u32 Bus::HWHandlers::GPURead(PhysicalMemoryAddress address) u32 Bus::HWHandlers::GPURead(PhysicalMemoryAddress address)
{ {
const u32 offset = address & GPU_MASK; const u32 offset = address & GPU_MASK;
u32 value = g_gpu->ReadRegister(FIXUP_WORD_OFFSET(size, offset)); u32 value = g_gpu.ReadRegister(FIXUP_WORD_OFFSET(size, offset));
value = FIXUP_WORD_READ_VALUE(size, offset, value); value = FIXUP_WORD_READ_VALUE(size, offset, value);
BUS_CYCLES(2); BUS_CYCLES(2);
return value; return value;
@ -1885,7 +1885,7 @@ template<MemoryAccessSize size>
void Bus::HWHandlers::GPUWrite(PhysicalMemoryAddress address, u32 value) void Bus::HWHandlers::GPUWrite(PhysicalMemoryAddress address, u32 value)
{ {
const u32 offset = address & GPU_MASK; const u32 offset = address & GPU_MASK;
g_gpu->WriteRegister(FIXUP_WORD_OFFSET(size, offset), FIXUP_WORD_WRITE_VALUE(size, offset, value)); g_gpu.WriteRegister(FIXUP_WORD_OFFSET(size, offset), FIXUP_WORD_WRITE_VALUE(size, offset, value));
} }
template<MemoryAccessSize size> template<MemoryAccessSize size>

View File

@ -5,8 +5,8 @@
#include "cdrom_async_reader.h" #include "cdrom_async_reader.h"
#include "cdrom_subq_replacement.h" #include "cdrom_subq_replacement.h"
#include "dma.h" #include "dma.h"
#include "fullscreen_ui.h"
#include "host.h" #include "host.h"
#include "host_interface_progress_callback.h"
#include "interrupt_controller.h" #include "interrupt_controller.h"
#include "settings.h" #include "settings.h"
#include "spu.h" #include "spu.h"
@ -999,7 +999,7 @@ bool CDROM::PrecacheMedia()
return false; return false;
} }
HostInterfaceProgressCallback callback; LoadingScreenProgressCallback callback;
if (!s_reader.Precache(&callback)) if (!s_reader.Precache(&callback))
{ {
Host::AddOSDMessage(TRANSLATE_STR("OSDMessage", "Precaching CD image failed, it may be unreliable."), Host::AddOSDMessage(TRANSLATE_STR("OSDMessage", "Precaching CD image failed, it may be unreliable."),

View File

@ -17,6 +17,7 @@
#include "common/file_system.h" #include "common/file_system.h"
#include "common/log.h" #include "common/log.h"
#include "common/path.h" #include "common/path.h"
#include "common/settings_interface.h"
#include "common/small_string.h" #include "common/small_string.h"
#include "common/string_util.h" #include "common/string_util.h"
#include "common/zip_helpers.h" #include "common/zip_helpers.h"

View File

@ -40,14 +40,13 @@
<ClCompile Include="gpu_hw_texture_cache.cpp" /> <ClCompile Include="gpu_hw_texture_cache.cpp" />
<ClCompile Include="gpu_shadergen.cpp" /> <ClCompile Include="gpu_shadergen.cpp" />
<ClCompile Include="gpu_sw.cpp" /> <ClCompile Include="gpu_sw.cpp" />
<ClCompile Include="gpu_sw_backend.cpp" />
<ClCompile Include="gpu_sw_rasterizer.cpp" /> <ClCompile Include="gpu_sw_rasterizer.cpp" />
<ClCompile Include="gpu_thread.cpp" />
<ClCompile Include="gte.cpp" /> <ClCompile Include="gte.cpp" />
<ClCompile Include="dma.cpp" /> <ClCompile Include="dma.cpp" />
<ClCompile Include="gpu.cpp" /> <ClCompile Include="gpu.cpp" />
<ClCompile Include="gpu_hw.cpp" /> <ClCompile Include="gpu_hw.cpp" />
<ClCompile Include="host.cpp" /> <ClCompile Include="host.cpp" />
<ClCompile Include="host_interface_progress_callback.cpp" />
<ClCompile Include="hotkeys.cpp" /> <ClCompile Include="hotkeys.cpp" />
<ClCompile Include="imgui_overlays.cpp" /> <ClCompile Include="imgui_overlays.cpp" />
<ClCompile Include="interrupt_controller.cpp" /> <ClCompile Include="interrupt_controller.cpp" />
@ -119,8 +118,9 @@
<ClInclude Include="gpu_hw_texture_cache.h" /> <ClInclude Include="gpu_hw_texture_cache.h" />
<ClInclude Include="gpu_shadergen.h" /> <ClInclude Include="gpu_shadergen.h" />
<ClInclude Include="gpu_sw.h" /> <ClInclude Include="gpu_sw.h" />
<ClInclude Include="gpu_sw_backend.h" />
<ClInclude Include="gpu_sw_rasterizer.h" /> <ClInclude Include="gpu_sw_rasterizer.h" />
<ClInclude Include="gpu_thread.h" />
<ClInclude Include="gpu_thread_commands.h" />
<ClInclude Include="gpu_types.h" /> <ClInclude Include="gpu_types.h" />
<ClInclude Include="gte.h" /> <ClInclude Include="gte.h" />
<ClInclude Include="cpu_types.h" /> <ClInclude Include="cpu_types.h" />
@ -129,7 +129,6 @@
<ClInclude Include="gpu_hw.h" /> <ClInclude Include="gpu_hw.h" />
<ClInclude Include="gte_types.h" /> <ClInclude Include="gte_types.h" />
<ClInclude Include="host.h" /> <ClInclude Include="host.h" />
<ClInclude Include="host_interface_progress_callback.h" />
<ClInclude Include="imgui_overlays.h" /> <ClInclude Include="imgui_overlays.h" />
<ClInclude Include="input_types.h" /> <ClInclude Include="input_types.h" />
<ClInclude Include="interrupt_controller.h" /> <ClInclude Include="interrupt_controller.h" />

View File

@ -33,13 +33,11 @@
<ClCompile Include="guncon.cpp" /> <ClCompile Include="guncon.cpp" />
<ClCompile Include="playstation_mouse.cpp" /> <ClCompile Include="playstation_mouse.cpp" />
<ClCompile Include="negcon.cpp" /> <ClCompile Include="negcon.cpp" />
<ClCompile Include="host_interface_progress_callback.cpp" />
<ClCompile Include="cpu_pgxp.cpp" /> <ClCompile Include="cpu_pgxp.cpp" />
<ClCompile Include="cheats.cpp" /> <ClCompile Include="cheats.cpp" />
<ClCompile Include="memory_card_image.cpp" /> <ClCompile Include="memory_card_image.cpp" />
<ClCompile Include="analog_joystick.cpp" /> <ClCompile Include="analog_joystick.cpp" />
<ClCompile Include="gpu_backend.cpp" /> <ClCompile Include="gpu_backend.cpp" />
<ClCompile Include="gpu_sw_backend.cpp" />
<ClCompile Include="multitap.cpp" /> <ClCompile Include="multitap.cpp" />
<ClCompile Include="host.cpp" /> <ClCompile Include="host.cpp" />
<ClCompile Include="game_database.cpp" /> <ClCompile Include="game_database.cpp" />
@ -67,6 +65,7 @@
<ClCompile Include="performance_counters.cpp" /> <ClCompile Include="performance_counters.cpp" />
<ClCompile Include="jogcon.cpp" /> <ClCompile Include="jogcon.cpp" />
<ClCompile Include="pio.cpp" /> <ClCompile Include="pio.cpp" />
<ClCompile Include="gpu_thread.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="types.h" /> <ClInclude Include="types.h" />
@ -102,7 +101,6 @@
<ClInclude Include="guncon.h" /> <ClInclude Include="guncon.h" />
<ClInclude Include="playstation_mouse.h" /> <ClInclude Include="playstation_mouse.h" />
<ClInclude Include="negcon.h" /> <ClInclude Include="negcon.h" />
<ClInclude Include="host_interface_progress_callback.h" />
<ClInclude Include="gte_types.h" /> <ClInclude Include="gte_types.h" />
<ClInclude Include="cpu_pgxp.h" /> <ClInclude Include="cpu_pgxp.h" />
<ClInclude Include="cpu_core_private.h" /> <ClInclude Include="cpu_core_private.h" />
@ -111,7 +109,6 @@
<ClInclude Include="analog_joystick.h" /> <ClInclude Include="analog_joystick.h" />
<ClInclude Include="gpu_types.h" /> <ClInclude Include="gpu_types.h" />
<ClInclude Include="gpu_backend.h" /> <ClInclude Include="gpu_backend.h" />
<ClInclude Include="gpu_sw_backend.h" />
<ClInclude Include="multitap.h" /> <ClInclude Include="multitap.h" />
<ClInclude Include="host.h" /> <ClInclude Include="host.h" />
<ClInclude Include="achievements.h" /> <ClInclude Include="achievements.h" />
@ -143,6 +140,8 @@
<ClInclude Include="system_private.h" /> <ClInclude Include="system_private.h" />
<ClInclude Include="jogcon.h" /> <ClInclude Include="jogcon.h" />
<ClInclude Include="pio.h" /> <ClInclude Include="pio.h" />
<ClInclude Include="gpu_thread.h" />
<ClInclude Include="gpu_thread_commands.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="gpu_sw_rasterizer.inl" /> <None Include="gpu_sw_rasterizer.inl" />

View File

@ -1692,9 +1692,10 @@ PageFaultHandler::HandlerResult CPU::CodeCache::HandleFastmemException(void* exc
// if we're writing to ram, let it go through a few times, and use manual block protection to sort it out // if we're writing to ram, let it go through a few times, and use manual block protection to sort it out
// TODO: path for manual protection to return back to read-only pages // TODO: path for manual protection to return back to read-only pages
if (is_write && !g_state.cop0_regs.sr.Isc && GetSegmentForAddress(guest_address) != CPU::Segment::KSEG2 && if (!g_state.cop0_regs.sr.Isc && GetSegmentForAddress(guest_address) != CPU::Segment::KSEG2 &&
AddressInRAM(guest_address)) AddressInRAM(guest_address))
{ {
DebugAssert(is_write);
DEV_LOG("Ignoring fault due to RAM write @ 0x{:08X}", guest_address); DEV_LOG("Ignoring fault due to RAM write @ 0x{:08X}", guest_address);
InvalidateBlocksWithPageIndex(Bus::GetRAMCodePageIndex(guest_address)); InvalidateBlocksWithPageIndex(Bus::GetRAMCodePageIndex(guest_address));
return PageFaultHandler::HandlerResult::ContinueExecution; return PageFaultHandler::HandlerResult::ContinueExecution;
@ -1707,15 +1708,12 @@ PageFaultHandler::HandlerResult CPU::CodeCache::HandleFastmemException(void* exc
guest_address = std::numeric_limits<PhysicalMemoryAddress>::max(); guest_address = std::numeric_limits<PhysicalMemoryAddress>::max();
} }
DEV_LOG("Page fault handler invoked at PC={} Address={} {}, fastmem offset {:08X}", exception_pc, fault_address,
is_write ? "(write)" : "(read)", guest_address);
auto iter = s_fastmem_backpatch_info.find(exception_pc); auto iter = s_fastmem_backpatch_info.find(exception_pc);
if (iter == s_fastmem_backpatch_info.end()) if (iter == s_fastmem_backpatch_info.end())
{
ERROR_LOG("No backpatch info found for {}", exception_pc);
return PageFaultHandler::HandlerResult::ExecuteNextHandler; return PageFaultHandler::HandlerResult::ExecuteNextHandler;
}
DEV_LOG("Page fault handler invoked at PC={} Address={} {}, fastmem offset {:08X}", exception_pc, fault_address,
is_write ? "(write)" : "(read)", guest_address);
LoadstoreBackpatchInfo& info = iter->second; LoadstoreBackpatchInfo& info = iter->second;
DEV_LOG("Backpatching {} at {}[{}] (pc {:08X} addr {:08X}): Bitmask {:08X} Addr {} Data {} Size {} Signed {:02X}", DEV_LOG("Backpatching {} at {}[{}] (pc {:08X} addr {:08X}): Bitmask {:08X} Addr {} Data {} Size {} Signed {:02X}",

View File

@ -31,10 +31,6 @@ LOG_CHANNEL(Recompiler);
#define PTR(x) vixl::aarch32::MemOperand(RSTATE, (((u8*)(x)) - ((u8*)&g_state))) #define PTR(x) vixl::aarch32::MemOperand(RSTATE, (((u8*)(x)) - ((u8*)&g_state)))
#define RMEMBASE vixl::aarch32::r3 #define RMEMBASE vixl::aarch32::r3
static constexpr u32 FUNCTION_CALLEE_SAVED_SPACE_RESERVE = 80; // 8 registers
static constexpr u32 FUNCTION_CALLER_SAVED_SPACE_RESERVE = 144; // 18 registers -> 224 bytes
static constexpr u32 FUNCTION_STACK_SIZE = FUNCTION_CALLEE_SAVED_SPACE_RESERVE + FUNCTION_CALLER_SAVED_SPACE_RESERVE;
#define RRET vixl::aarch32::r0 #define RRET vixl::aarch32::r0
#define RRETHI vixl::aarch32::r1 #define RRETHI vixl::aarch32::r1
#define RARG1 vixl::aarch32::r0 #define RARG1 vixl::aarch32::r0
@ -266,9 +262,6 @@ u32 CPU::CodeCache::EmitASMFunctions(void* code, u32 code_size)
g_enter_recompiler = armAsm->GetCursorAddress<decltype(g_enter_recompiler)>(); g_enter_recompiler = armAsm->GetCursorAddress<decltype(g_enter_recompiler)>();
{ {
// reserve some space for saving caller-saved registers
armAsm->sub(sp, sp, FUNCTION_STACK_SIZE);
// Need the CPU state for basically everything :-) // Need the CPU state for basically everything :-)
armMoveAddressToReg(armAsm, RSTATE, &g_state); armMoveAddressToReg(armAsm, RSTATE, &g_state);
} }
@ -2272,16 +2265,19 @@ void CPU::ARM32Recompiler::Compile_mtc0(CompileFlags cf)
// We could just inline the whole thing.. // We could just inline the whole thing..
Flush(FLUSH_FOR_C_CALL); Flush(FLUSH_FOR_C_CALL);
SwitchToFarCodeIfBitSet(changed_bits, 16); Label caches_unchanged;
armAsm->push(RegisterList(RARG1)); armAsm->tst(changed_bits, 1u << 16);
armAsm->b(eq, &caches_unchanged);
EmitCall(reinterpret_cast<const void*>(&CPU::UpdateMemoryPointers)); EmitCall(reinterpret_cast<const void*>(&CPU::UpdateMemoryPointers));
armAsm->pop(RegisterList(RARG1)); armAsm->ldr(RARG1, PTR(ptr)); // reload value for interrupt test below
armAsm->bind(&caches_unchanged);
// might need to reload fastmem base too
if (CodeCache::IsUsingFastmem() && m_block->HasFlag(CodeCache::BlockFlags::ContainsLoadStoreInstructions) && if (CodeCache::IsUsingFastmem() && m_block->HasFlag(CodeCache::BlockFlags::ContainsLoadStoreInstructions) &&
IsHostRegAllocated(RMEMBASE.GetCode())) IsHostRegAllocated(RMEMBASE.GetCode()))
{ {
FreeHostReg(RMEMBASE.GetCode()); FreeHostReg(RMEMBASE.GetCode());
} }
SwitchToNearCode(true);
TestInterrupts(RARG1); TestInterrupts(RARG1);
} }

View File

@ -28,10 +28,6 @@ LOG_CHANNEL(Recompiler);
#define PTR(x) vixl::aarch64::MemOperand(RSTATE, (((u8*)(x)) - ((u8*)&g_state))) #define PTR(x) vixl::aarch64::MemOperand(RSTATE, (((u8*)(x)) - ((u8*)&g_state)))
static constexpr u64 FUNCTION_CALLEE_SAVED_SPACE_RESERVE = 80; // 8 registers
static constexpr u64 FUNCTION_CALLER_SAVED_SPACE_RESERVE = 144; // 18 registers -> 224 bytes
static constexpr u64 FUNCTION_STACK_SIZE = FUNCTION_CALLEE_SAVED_SPACE_RESERVE + FUNCTION_CALLER_SAVED_SPACE_RESERVE;
#define RWRET vixl::aarch64::w0 #define RWRET vixl::aarch64::w0
#define RXRET vixl::aarch64::x0 #define RXRET vixl::aarch64::x0
#define RWARG1 vixl::aarch64::w0 #define RWARG1 vixl::aarch64::w0
@ -448,9 +444,6 @@ u32 CPU::CodeCache::EmitASMFunctions(void* code, u32 code_size)
g_enter_recompiler = armAsm->GetCursorAddress<decltype(g_enter_recompiler)>(); g_enter_recompiler = armAsm->GetCursorAddress<decltype(g_enter_recompiler)>();
{ {
// reserve some space for saving caller-saved registers
armAsm->sub(sp, sp, FUNCTION_STACK_SIZE);
// Need the CPU state for basically everything :-) // Need the CPU state for basically everything :-)
armMoveAddressToReg(armAsm, RSTATE, &g_state); armMoveAddressToReg(armAsm, RSTATE, &g_state);
@ -2435,14 +2428,13 @@ void CPU::ARM64Recompiler::Compile_mtc0(CompileFlags cf)
// We could just inline the whole thing.. // We could just inline the whole thing..
Flush(FLUSH_FOR_C_CALL); Flush(FLUSH_FOR_C_CALL);
SwitchToFarCodeIfBitSet(changed_bits, 16); Label caches_unchanged;
armAsm->sub(sp, sp, 16); armAsm->tbz(changed_bits, 16, &caches_unchanged);
armAsm->str(RWARG1, MemOperand(sp));
EmitCall(reinterpret_cast<const void*>(&CPU::UpdateMemoryPointers)); EmitCall(reinterpret_cast<const void*>(&CPU::UpdateMemoryPointers));
armAsm->ldr(RWARG1, MemOperand(sp)); armAsm->ldr(RWARG1, PTR(ptr)); // reload value for interrupt test below
armAsm->add(sp, sp, 16); if (CodeCache::IsUsingFastmem())
armAsm->ldr(RMEMBASE, PTR(&g_state.fastmem_base)); armAsm->ldr(RMEMBASE, PTR(&g_state.fastmem_base));
SwitchToNearCode(true); armAsm->bind(&caches_unchanged);
TestInterrupts(RWARG1); TestInterrupts(RWARG1);
} }

View File

@ -2283,16 +2283,15 @@ void CPU::RISCV64Recompiler::Compile_mtc0(CompileFlags cf)
// We could just inline the whole thing.. // We could just inline the whole thing..
Flush(FLUSH_FOR_C_CALL); Flush(FLUSH_FOR_C_CALL);
Label caches_unchanged;
rvAsm->SRLIW(RSCRATCH, changed_bits, 16); rvAsm->SRLIW(RSCRATCH, changed_bits, 16);
rvAsm->ANDI(RSCRATCH, RSCRATCH, 1); rvAsm->ANDI(RSCRATCH, RSCRATCH, 1);
SwitchToFarCode(true, &Assembler::BEQ, RSCRATCH, zero); rvAsm->BEQ(RSCRATCH, zero, &caches_unchanged);
rvAsm->ADDI(sp, sp, -16);
rvAsm->SW(RARG1, 0, sp);
EmitCall(reinterpret_cast<const void*>(&CPU::UpdateMemoryPointers)); EmitCall(reinterpret_cast<const void*>(&CPU::UpdateMemoryPointers));
rvAsm->LW(RARG1, 0, sp); rvAsm->LW(new_value, PTR(ptr));
rvAsm->ADDI(sp, sp, 16); if (CodeCache::IsUsingFastmem())
rvAsm->LD(RMEMBASE, PTR(&g_state.fastmem_base)); rvAsm->LD(RMEMBASE, PTR(&g_state.fastmem_base));
SwitchToNearCode(true); rvAsm->Bind(&caches_unchanged);
TestInterrupts(RARG1); TestInterrupts(RARG1);
} }

View File

@ -12,6 +12,7 @@
#include "common/align.h" #include "common/align.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/log.h" #include "common/log.h"
#include "common/small_string.h"
#include "common/string_util.h" #include "common/string_util.h"
#include <limits> #include <limits>
@ -2241,15 +2242,15 @@ void CPU::X64Recompiler::Compile_mtc0(CompileFlags cf)
// We could just inline the whole thing.. // We could just inline the whole thing..
Flush(FLUSH_FOR_C_CALL); Flush(FLUSH_FOR_C_CALL);
Label caches_unchanged;
cg->test(changed_bits, 1u << 16); cg->test(changed_bits, 1u << 16);
SwitchToFarCode(true, &CodeGenerator::jnz); cg->jz(caches_unchanged);
cg->mov(cg->dword[cg->rsp], RWARG2);
cg->sub(cg->rsp, STACK_SHADOW_SIZE + 8);
cg->call(&CPU::UpdateMemoryPointers); cg->call(&CPU::UpdateMemoryPointers);
cg->add(cg->rsp, STACK_SHADOW_SIZE + 8); cg->mov(RWARG2, cg->dword[PTR(ptr)]); // reload value for interrupt test below
cg->mov(RWARG2, cg->dword[cg->rsp]); if (CodeCache::IsUsingFastmem())
cg->mov(RMEMBASE, cg->qword[PTR(&g_state.fastmem_base)]); cg->mov(RMEMBASE, cg->qword[PTR(&g_state.fastmem_base)]);
SwitchToNearCode(true);
cg->L(caches_unchanged);
TestInterrupts(RWARG2); TestInterrupts(RWARG2);
} }
@ -2475,7 +2476,7 @@ u32 CPU::Recompiler::CompileLoadStoreThunk(void* thunk_code, u32 thunk_space, vo
num_gprs++; num_gprs++;
} }
const u32 stack_size = (((num_gprs + 1) & ~1u) * GPR_SIZE) + STACK_SHADOW_SIZE; const u32 stack_size = (((num_gprs + 1) & ~1u) * GPR_SIZE);
if (stack_size > 0) if (stack_size > 0)
{ {

View File

@ -802,9 +802,9 @@ TickCount DMA::TransferMemoryToDevice(u32 address, u32 increment, u32 word_count
{ {
case Channel::GPU: case Channel::GPU:
{ {
if (g_gpu->BeginDMAWrite()) [[likely]] if (g_gpu.BeginDMAWrite()) [[likely]]
{ {
if (GPUDump::Recorder* dump = g_gpu->GetGPUDump()) [[unlikely]] if (GPUDump::Recorder* dump = g_gpu.GetGPUDump()) [[unlikely]]
{ {
// No wraparound? // No wraparound?
dump->BeginGP0Packet(word_count); dump->BeginGP0Packet(word_count);
@ -831,10 +831,10 @@ TickCount DMA::TransferMemoryToDevice(u32 address, u32 increment, u32 word_count
{ {
u32 value; u32 value;
std::memcpy(&value, &ram_pointer[address], sizeof(u32)); std::memcpy(&value, &ram_pointer[address], sizeof(u32));
g_gpu->DMAWrite(address, value); g_gpu.DMAWrite(address, value);
address = (address + increment) & mask; address = (address + increment) & mask;
} }
g_gpu->EndDMAWrite(); g_gpu.EndDMAWrite();
} }
} }
break; break;
@ -900,7 +900,7 @@ TickCount DMA::TransferDeviceToMemory(u32 address, u32 increment, u32 word_count
switch (channel) switch (channel)
{ {
case Channel::GPU: case Channel::GPU:
g_gpu->DMARead(dest_pointer, word_count); g_gpu.DMARead(dest_pointer, word_count);
break; break;
case Channel::CDROM: case Channel::CDROM:

View File

@ -8,7 +8,9 @@
#include "controller.h" #include "controller.h"
#include "game_list.h" #include "game_list.h"
#include "gpu.h" #include "gpu.h"
#include "gpu_thread.h"
#include "host.h" #include "host.h"
#include "imgui_overlays.h"
#include "settings.h" #include "settings.h"
#include "system.h" #include "system.h"
#include "system_private.h" #include "system_private.h"
@ -203,6 +205,7 @@ struct PostProcessingStageInfo
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
// Main // Main
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
static void UpdateRunIdleState();
static void PauseForMenuOpen(bool set_pause_menu_open); static void PauseForMenuOpen(bool set_pause_menu_open);
static bool AreAnyDialogsOpen(); static bool AreAnyDialogsOpen();
static void ClosePauseMenu(); static void ClosePauseMenu();
@ -602,12 +605,12 @@ bool FullscreenUI::Initialize()
s_state.about_window_open = false; s_state.about_window_open = false;
s_state.hotkey_list_cache = InputManager::GetHotkeyList(); s_state.hotkey_list_cache = InputManager::GetHotkeyList();
if (!System::IsValid()) Host::RunOnCPUThread([]() { Host::OnFullscreenUIStartedOrStopped(true); });
if (!GPUThread::HasGPUBackend() && !GPUThread::IsGPUBackendRequested())
SwitchToLanding(); SwitchToLanding();
if (!System::IsRunning()) UpdateRunIdleState();
Host::OnIdleStateChanged();
ForceKeyNavEnabled(); ForceKeyNavEnabled();
return true; return true;
} }
@ -629,64 +632,90 @@ bool FullscreenUI::AreAnyDialogsOpen()
ImGuiFullscreen::IsFileSelectorOpen()); ImGuiFullscreen::IsFileSelectorOpen());
} }
void FullscreenUI::CheckForConfigChanges(const Settings& old_settings) void FullscreenUI::CheckForConfigChanges(const GPUSettings& old_settings)
{ {
if (!IsInitialized()) // NOTE: Called on GPU thread.
return; }
// If achievements got disabled, we might have the menu open... void FullscreenUI::UpdateRunIdleState()
// That means we're going to be reading achievement state. {
if (old_settings.achievements_enabled && !g_settings.achievements_enabled) const bool new_run_idle = HasActiveWindow();
{ GPUThread::SetRunIdleReason(GPUThread::RunIdleReason::FullscreenUIActive, new_run_idle);
if (s_state.current_main_window == MainWindowType::Achievements ||
s_state.current_main_window == MainWindowType::Leaderboards)
ReturnToPreviousWindow();
}
} }
void FullscreenUI::OnSystemStarted() void FullscreenUI::OnSystemStarted()
{ {
// NOTE: Called on CPU thread.
if (!IsInitialized()) if (!IsInitialized())
return; return;
s_state.current_main_window = MainWindowType::None; GPUThread::RunOnThread([]() {
QueueResetFocus(FocusResetType::ViewChanged); if (!IsInitialized())
} return;
void FullscreenUI::OnSystemPaused() s_state.current_main_window = MainWindowType::None;
{ QueueResetFocus(FocusResetType::ViewChanged);
// noop UpdateRunIdleState();
});
} }
void FullscreenUI::OnSystemResumed() void FullscreenUI::OnSystemResumed()
{ {
// get rid of pause menu if we unpaused another way // NOTE: Called on CPU thread.
if (s_state.current_main_window == MainWindowType::PauseMenu) if (!IsInitialized())
ClosePauseMenu(); return;
GPUThread::RunOnThread([]() {
if (!IsInitialized())
return;
// get rid of pause menu if we unpaused another way
if (s_state.current_main_window == MainWindowType::PauseMenu)
ClosePauseMenu();
UpdateRunIdleState();
});
} }
void FullscreenUI::OnSystemDestroyed() void FullscreenUI::OnSystemDestroyed()
{ {
// NOTE: Called on CPU thread.
if (!IsInitialized()) if (!IsInitialized())
return; return;
s_state.pause_menu_was_open = false; GPUThread::RunOnThread([]() {
s_state.was_paused_on_quick_menu_open = false; if (!IsInitialized())
s_state.current_pause_submenu = PauseSubMenu::None; return;
SwitchToLanding();
s_state.pause_menu_was_open = false;
s_state.was_paused_on_quick_menu_open = false;
s_state.current_pause_submenu = PauseSubMenu::None;
SwitchToLanding();
UpdateRunIdleState();
});
} }
void FullscreenUI::OnRunningGameChanged() void FullscreenUI::OnRunningGameChanged()
{ {
// NOTE: Called on CPU thread.
if (!IsInitialized()) if (!IsInitialized())
return; return;
const std::string& path = System::GetDiscPath(); const std::string& path = System::GetDiscPath();
const std::string& serial = System::GetGameSerial(); const std::string& serial = System::GetGameSerial();
std::string subtitle;
if (!serial.empty()) if (!serial.empty())
s_state.current_game_subtitle = fmt::format("{0} - {1}", serial, Path::GetFileName(path)); subtitle = fmt::format("{0} - {1}", serial, Path::GetFileName(path));
else else
s_state.current_game_subtitle = {}; subtitle = {};
GPUThread::RunOnThread([subtitle = std::move(subtitle)]() mutable {
if (!IsInitialized())
return;
s_state.current_game_subtitle = std::move(subtitle);
});
} }
void FullscreenUI::PauseForMenuOpen(bool set_pause_menu_open) void FullscreenUI::PauseForMenuOpen(bool set_pause_menu_open)
@ -703,15 +732,18 @@ void FullscreenUI::OpenPauseMenu()
if (!System::IsValid()) if (!System::IsValid())
return; return;
if (!Initialize() || s_state.current_main_window != MainWindowType::None) GPUThread::RunOnThread([]() {
return; if (!Initialize() || s_state.current_main_window != MainWindowType::None)
return;
PauseForMenuOpen(true); PauseForMenuOpen(true);
s_state.current_main_window = MainWindowType::PauseMenu; s_state.current_main_window = MainWindowType::PauseMenu;
s_state.current_pause_submenu = PauseSubMenu::None; s_state.current_pause_submenu = PauseSubMenu::None;
QueueResetFocus(FocusResetType::ViewChanged); QueueResetFocus(FocusResetType::ViewChanged);
ForceKeyNavEnabled(); ForceKeyNavEnabled();
FixStateIfPaused(); UpdateRunIdleState();
FixStateIfPaused();
});
} }
void FullscreenUI::OpenCheatsMenu() void FullscreenUI::OpenCheatsMenu()
@ -725,41 +757,39 @@ void FullscreenUI::OpenCheatsMenu()
s_state.settings_page = SettingsPage::Cheats; s_state.settings_page = SettingsPage::Cheats;
PauseForMenuOpen(true); PauseForMenuOpen(true);
ForceKeyNavEnabled(); ForceKeyNavEnabled();
UpdateRunIdleState();
FixStateIfPaused(); FixStateIfPaused();
} }
void FullscreenUI::FixStateIfPaused() void FullscreenUI::FixStateIfPaused()
{ {
if (!System::IsValid() || System::IsRunning()) if (!GPUThread::HasGPUBackend() || System::IsRunning())
return; return;
// When we're paused, we won't have trickled the key up event for escape yet. Do it now. // When we're paused, we won't have trickled the key up event for escape yet. Do it now.
ImGui::UpdateInputEvents(false); ImGui::UpdateInputEvents(false);
Host::OnIdleStateChanged();
Host::RunOnCPUThread([]() {
if (System::IsValid())
{
// Why twice? To clear the "wants keyboard input" flag.
System::InvalidateDisplay();
System::InvalidateDisplay();
}
});
} }
void FullscreenUI::ClosePauseMenu() void FullscreenUI::ClosePauseMenu()
{ {
if (!IsInitialized() || !System::IsValid()) if (!System::IsValid())
return; return;
if (System::GetState() == System::State::Paused && !s_state.was_paused_on_quick_menu_open) const bool paused = System::IsPaused();
Host::RunOnCPUThread([]() { System::PauseSystem(false); }); GPUThread::RunOnThread([paused]() {
if (!IsInitialized())
return;
s_state.current_main_window = MainWindowType::None; if (paused && !s_state.was_paused_on_quick_menu_open)
s_state.current_pause_submenu = PauseSubMenu::None; Host::RunOnCPUThread([]() { System::PauseSystem(false); });
s_state.pause_menu_was_open = false;
QueueResetFocus(FocusResetType::ViewChanged); s_state.current_main_window = MainWindowType::None;
FixStateIfPaused(); s_state.current_pause_submenu = PauseSubMenu::None;
s_state.pause_menu_was_open = false;
QueueResetFocus(FocusResetType::ViewChanged);
UpdateRunIdleState();
FixStateIfPaused();
});
} }
void FullscreenUI::OpenPauseSubMenu(PauseSubMenu submenu) void FullscreenUI::OpenPauseSubMenu(PauseSubMenu submenu)
@ -790,14 +820,21 @@ void FullscreenUI::Shutdown()
s_state.current_game_subtitle = {}; s_state.current_game_subtitle = {};
DestroyResources(); DestroyResources();
ImGuiFullscreen::Shutdown(); ImGuiFullscreen::Shutdown();
if (s_state.initialized)
Host::RunOnCPUThread([]() { Host::OnFullscreenUIStartedOrStopped(false); });
s_state.initialized = false; s_state.initialized = false;
s_state.tried_to_initialize = false; s_state.tried_to_initialize = false;
UpdateRunIdleState();
} }
void FullscreenUI::Render() void FullscreenUI::Render()
{ {
if (!s_state.initialized) if (!s_state.initialized)
{
ImGuiFullscreen::RenderLoadingScreen();
return; return;
}
ImGuiFullscreen::UploadAsyncTextures(); ImGuiFullscreen::UploadAsyncTextures();
@ -856,6 +893,8 @@ void FullscreenUI::Render()
ImGuiFullscreen::EndLayout(); ImGuiFullscreen::EndLayout();
ImGuiFullscreen::RenderLoadingScreen();
if (s_state.settings_changed.load(std::memory_order_relaxed)) if (s_state.settings_changed.load(std::memory_order_relaxed))
{ {
Host::CommitBaseSettingChanges(); Host::CommitBaseSettingChanges();
@ -889,7 +928,7 @@ void FullscreenUI::Render()
} }
} }
if (System::IsValid()) if (GPUThread::HasGPUBackend())
Host::RunOnCPUThread([]() { System::ReloadGameSettings(false); }); Host::RunOnCPUThread([]() { System::ReloadGameSettings(false); });
} }
s_state.game_settings_changed.store(false, std::memory_order_release); s_state.game_settings_changed.store(false, std::memory_order_release);
@ -908,7 +947,7 @@ void FullscreenUI::InvalidateCoverCache()
void FullscreenUI::ReturnToPreviousWindow() void FullscreenUI::ReturnToPreviousWindow()
{ {
if (System::IsValid() && s_state.pause_menu_was_open) if (GPUThread::HasGPUBackend() && s_state.pause_menu_was_open)
{ {
s_state.current_main_window = MainWindowType::PauseMenu; s_state.current_main_window = MainWindowType::PauseMenu;
QueueResetFocus(FocusResetType::ViewChanged); QueueResetFocus(FocusResetType::ViewChanged);
@ -922,7 +961,8 @@ void FullscreenUI::ReturnToPreviousWindow()
void FullscreenUI::ReturnToMainWindow() void FullscreenUI::ReturnToMainWindow()
{ {
ClosePauseMenu(); ClosePauseMenu();
s_state.current_main_window = System::IsValid() ? MainWindowType::None : MainWindowType::Landing; s_state.current_main_window = GPUThread::HasGPUBackend() ? MainWindowType::None : MainWindowType::Landing;
UpdateRunIdleState();
FixStateIfPaused(); FixStateIfPaused();
} }
@ -958,9 +998,14 @@ ImGuiFullscreen::FileSelectorFilters FullscreenUI::GetDiscImageFilters()
void FullscreenUI::DoStartPath(std::string path, std::string state, std::optional<bool> fast_boot) void FullscreenUI::DoStartPath(std::string path, std::string state, std::optional<bool> fast_boot)
{ {
if (System::IsValid()) if (GPUThread::HasGPUBackend())
return; return;
// Switch to nothing, we'll get called back via OnSystemDestroyed() if startup fails.
s_state.current_main_window = MainWindowType::None;
QueueResetFocus(FocusResetType::ViewChanged);
UpdateRunIdleState();
SystemBootParameters params; SystemBootParameters params;
params.filename = std::move(path); params.filename = std::move(path);
params.save_state = std::move(state); params.save_state = std::move(state);
@ -1155,108 +1200,116 @@ void FullscreenUI::DoChangeDiscFromFile()
void FullscreenUI::DoChangeDisc() void FullscreenUI::DoChangeDisc()
{ {
ImGuiFullscreen::ChoiceDialogOptions options; Host::RunOnCPUThread([]() {
ImGuiFullscreen::ChoiceDialogOptions options;
if (System::HasMediaSubImages()) if (System::HasMediaSubImages())
{
const u32 current_index = System::GetMediaSubImageIndex();
const u32 count = System::GetMediaSubImageCount();
options.reserve(count + 1);
options.emplace_back(FSUI_STR("From File..."), false);
for (u32 i = 0; i < count; i++)
options.emplace_back(System::GetMediaSubImageTitle(i), i == current_index);
auto callback = [](s32 index, const std::string& title, bool checked) {
if (index == 0)
{
CloseChoiceDialog();
DoChangeDiscFromFile();
return;
}
else if (index > 0)
{
System::SwitchMediaSubImage(static_cast<u32>(index - 1));
}
CloseChoiceDialog();
ReturnToPreviousWindow();
};
OpenChoiceDialog(FSUI_ICONSTR(ICON_FA_COMPACT_DISC, "Select Disc Image"), true, std::move(options),
std::move(callback));
return;
}
if (const GameDatabase::Entry* entry = System::GetGameDatabaseEntry(); entry && !entry->disc_set_serials.empty())
{
const auto lock = GameList::GetLock();
auto matches = GameList::GetMatchingEntriesForSerial(entry->disc_set_serials);
if (matches.size() > 1)
{ {
options.reserve(matches.size() + 1); const u32 current_index = System::GetMediaSubImageIndex();
const u32 count = System::GetMediaSubImageCount();
options.reserve(count + 1);
options.emplace_back(FSUI_STR("From File..."), false); options.emplace_back(FSUI_STR("From File..."), false);
std::vector<std::string> paths; for (u32 i = 0; i < count; i++)
paths.reserve(matches.size()); options.emplace_back(System::GetMediaSubImageTitle(i), i == current_index);
const std::string& current_path = System::GetDiscPath(); GPUThread::RunOnThread([options = std::move(options)]() mutable {
for (auto& [title, glentry] : matches) auto callback = [](s32 index, const std::string& title, bool checked) {
{ if (index == 0)
options.emplace_back(std::move(title), current_path == glentry->path); {
paths.push_back(glentry->path); CloseChoiceDialog();
} DoChangeDiscFromFile();
return;
}
else if (index > 0)
{
System::SwitchMediaSubImage(static_cast<u32>(index - 1));
}
auto callback = [paths = std::move(paths)](s32 index, const std::string& title, bool checked) {
if (index == 0)
{
CloseChoiceDialog(); CloseChoiceDialog();
DoChangeDiscFromFile(); ReturnToPreviousWindow();
return; };
}
else if (index > 0)
{
System::InsertMedia(paths[index - 1].c_str());
}
CloseChoiceDialog(); OpenChoiceDialog(FSUI_ICONSTR(ICON_FA_COMPACT_DISC, "Select Disc Image"), true, std::move(options),
ReturnToMainWindow(); std::move(callback));
}; });
OpenChoiceDialog(FSUI_ICONSTR(ICON_FA_COMPACT_DISC, "Select Disc Image"), true, std::move(options),
std::move(callback));
return; return;
} }
}
DoChangeDiscFromFile(); if (const GameDatabase::Entry* entry = System::GetGameDatabaseEntry(); entry && !entry->disc_set_serials.empty())
{
const auto lock = GameList::GetLock();
auto matches = GameList::GetMatchingEntriesForSerial(entry->disc_set_serials);
if (matches.size() > 1)
{
options.reserve(matches.size() + 1);
options.emplace_back(FSUI_STR("From File..."), false);
std::vector<std::string> paths;
paths.reserve(matches.size());
const std::string& current_path = System::GetDiscPath();
for (auto& [title, glentry] : matches)
{
options.emplace_back(std::move(title), current_path == glentry->path);
paths.push_back(glentry->path);
}
GPUThread::RunOnThread([options = std::move(options), paths = std::move(paths)]() mutable {
auto callback = [paths = std::move(paths)](s32 index, const std::string& title, bool checked) {
if (index == 0)
{
CloseChoiceDialog();
DoChangeDiscFromFile();
return;
}
else if (index > 0)
{
System::InsertMedia(paths[index - 1].c_str());
}
CloseChoiceDialog();
ReturnToMainWindow();
};
OpenChoiceDialog(FSUI_ICONSTR(ICON_FA_COMPACT_DISC, "Select Disc Image"), true, std::move(options),
std::move(callback));
});
return;
}
}
GPUThread::RunOnThread([]() { DoChangeDiscFromFile(); });
});
} }
void FullscreenUI::DoToggleAnalogMode() void FullscreenUI::DoToggleAnalogMode()
{ {
// hacky way to toggle analog mode // hacky way to toggle analog mode
for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++) Host::RunOnCPUThread([]() {
{ for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++)
Controller* ctrl = System::GetController(i);
if (!ctrl)
continue;
const Controller::ControllerInfo* cinfo = Controller::GetControllerInfo(ctrl->GetType());
if (!cinfo)
continue;
for (const Controller::ControllerBindingInfo& bi : cinfo->bindings)
{ {
if (std::strcmp(bi.name, "Analog") == 0) Controller* ctrl = System::GetController(i);
if (!ctrl)
continue;
const Controller::ControllerInfo* cinfo = Controller::GetControllerInfo(ctrl->GetType());
if (!cinfo)
continue;
for (const Controller::ControllerBindingInfo& bi : cinfo->bindings)
{ {
ctrl->SetBindState(bi.bind_index, 1.0f); if (std::strcmp(bi.name, "Analog") == 0)
ctrl->SetBindState(bi.bind_index, 0.0f); {
break; ctrl->SetBindState(bi.bind_index, 1.0f);
ctrl->SetBindState(bi.bind_index, 0.0f);
break;
}
} }
} }
} });
} }
void FullscreenUI::DoRequestExit() void FullscreenUI::DoRequestExit()
@ -2857,7 +2910,7 @@ void FullscreenUI::DrawSettingsWindow()
(LayoutScale(LAYOUT_MENU_BUTTON_Y_PADDING) * 2.0f) + LayoutScale(2.0f)); (LayoutScale(LAYOUT_MENU_BUTTON_Y_PADDING) * 2.0f) + LayoutScale(2.0f));
const float bg_alpha = const float bg_alpha =
System::IsValid() ? (s_state.settings_page == SettingsPage::PostProcessing ? 0.50f : 0.90f) : 1.0f; GPUThread::HasGPUBackend() ? (s_state.settings_page == SettingsPage::PostProcessing ? 0.50f : 0.90f) : 1.0f;
if (BeginFullscreenWindow(ImVec2(0.0f, 0.0f), heading_size, "settings_category", if (BeginFullscreenWindow(ImVec2(0.0f, 0.0f), heading_size, "settings_category",
ImVec4(UIStyle.PrimaryColor.x, UIStyle.PrimaryColor.y, UIStyle.PrimaryColor.z, bg_alpha))) ImVec4(UIStyle.PrimaryColor.x, UIStyle.PrimaryColor.y, UIStyle.PrimaryColor.z, bg_alpha)))
@ -3802,12 +3855,9 @@ void FullscreenUI::DrawControllerSettingsPage()
&Settings::GetMultitapModeName, &Settings::GetMultitapModeDisplayName, MultitapMode::Count); &Settings::GetMultitapModeName, &Settings::GetMultitapModeDisplayName, MultitapMode::Count);
// load mtap settings // load mtap settings
MultitapMode mtap_mode = g_settings.multitap_mode; const MultitapMode mtap_mode =
if (IsEditingGameSettings(bsi)) Settings::ParseMultitapModeName(bsi->GetTinyStringValue("ControllerPorts", "MultitapMode", "").c_str())
{ .value_or(Settings::DEFAULT_MULTITAP_MODE);
mtap_mode = Settings::ParseMultitapModeName(bsi->GetTinyStringValue("ControllerPorts", "MultitapMode", "").c_str())
.value_or(g_settings.multitap_mode);
}
const std::array<bool, 2> mtap_enabled = { const std::array<bool, 2> mtap_enabled = {
{(mtap_mode == MultitapMode::Port1Only || mtap_mode == MultitapMode::BothPorts), {(mtap_mode == MultitapMode::Port1Only || mtap_mode == MultitapMode::BothPorts),
(mtap_mode == MultitapMode::Port2Only || mtap_mode == MultitapMode::BothPorts)}}; (mtap_mode == MultitapMode::Port2Only || mtap_mode == MultitapMode::BothPorts)}};
@ -4674,7 +4724,7 @@ void FullscreenUI::DrawPostProcessingSettingsPage()
FSUI_CSTR("Reloads the shaders from disk, applying any changes."), FSUI_CSTR("Reloads the shaders from disk, applying any changes."),
bsi->GetBoolValue("PostProcessing", "Enabled", false))) bsi->GetBoolValue("PostProcessing", "Enabled", false)))
{ {
if (System::IsValid() && PostProcessing::ReloadShaders()) if (GPUThread::HasGPUBackend() && PostProcessing::ReloadShaders())
ShowToast(std::string(), FSUI_STR("Post-processing shaders reloaded.")); ShowToast(std::string(), FSUI_STR("Post-processing shaders reloaded."));
} }
@ -5110,7 +5160,7 @@ void FullscreenUI::DrawAchievementsSettingsPage()
"cheats, and slowdown functions."), "cheats, and slowdown functions."),
"Cheevos", "ChallengeMode", false, enabled)) "Cheevos", "ChallengeMode", false, enabled))
{ {
if (System::IsValid() && bsi->GetBoolValue("Cheevos", "ChallengeMode", false)) if (GPUThread::HasGPUBackend() && bsi->GetBoolValue("Cheevos", "ChallengeMode", false))
ShowToast(std::string(), FSUI_STR("Hardcore mode will be enabled on next game restart.")); ShowToast(std::string(), FSUI_STR("Hardcore mode will be enabled on next game restart."));
} }
DrawToggleSetting( DrawToggleSetting(
@ -5272,28 +5322,30 @@ void FullscreenUI::DrawAchievementsLoginWindow()
0, 0, 0); 0, 0, 0);
Host::RunOnCPUThread([username = std::string(username), password = std::string(password)]() { Host::RunOnCPUThread([username = std::string(username), password = std::string(password)]() {
ImGuiFullscreen::CloseBackgroundProgressDialog(LOGIN_PROGRESS_NAME);
Error error; Error error;
if (Achievements::Login(username.c_str(), password.c_str(), &error)) const bool result = Achievements::Login(username.c_str(), password.c_str(), &error);
{ GPUThread::RunOnThread([result, error = std::move(error)]() {
// TODO-GPU-THREAD: Synchronize access to s_achievements_login_window_open. ImGuiFullscreen::CloseBackgroundProgressDialog(LOGIN_PROGRESS_NAME);
actually_close_popup();
return;
}
// keep popup open on failure if (result)
// because of the whole popup stack thing, we need to hide the dialog while this popup is visible {
s_state.achievements_login_window_open = false; actually_close_popup();
ImGuiFullscreen::OpenInfoMessageDialog( return;
FSUI_STR("Login Error"), }
fmt::format(FSUI_FSTR("Login Failed.\nError: {}\nPlease check your username and password, and try again."),
error.GetDescription()), // keep popup open on failure
[]() { // because of the whole popup stack thing, we need to hide the dialog while this popup is visible
s_state.achievements_login_window_open = true; s_state.achievements_login_window_open = false;
QueueResetFocus(FocusResetType::PopupOpened); ImGuiFullscreen::OpenInfoMessageDialog(
}, FSUI_STR("Login Error"),
FSUI_ICONSTR(ICON_FA_TIMES, "Close")); fmt::format(FSUI_FSTR("Login Failed.\nError: {}\nPlease check your username and password, and try again."),
error.GetDescription()),
[]() {
s_state.achievements_login_window_open = true;
QueueResetFocus(FocusResetType::PopupOpened);
},
FSUI_ICONSTR(ICON_FA_TIMES, "Close"));
});
}); });
} }
@ -5804,7 +5856,7 @@ void FullscreenUI::DrawPauseMenu()
case PauseSubMenu::None: case PauseSubMenu::None:
{ {
// NOTE: Menu close must come first, because otherwise VM destruction options will race. // NOTE: Menu close must come first, because otherwise VM destruction options will race.
const bool has_game = System::IsValid() && !System::GetGameSerial().empty(); const bool has_game = GPUThread::HasGPUBackend() && !System::GetGameSerial().empty();
if (DefaultActiveButton(FSUI_ICONSTR(ICON_FA_PLAY, "Resume Game"), false) || WantsToCloseMenu()) if (DefaultActiveButton(FSUI_ICONSTR(ICON_FA_PLAY, "Resume Game"), false) || WantsToCloseMenu())
ClosePauseMenu(); ClosePauseMenu();
@ -6667,7 +6719,7 @@ void FullscreenUI::DrawGameListWindow()
ImVec2(io.DisplaySize.x, LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY) + ImVec2(io.DisplaySize.x, LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY) +
(LayoutScale(LAYOUT_MENU_BUTTON_Y_PADDING) * 2.0f) + LayoutScale(2.0f)); (LayoutScale(LAYOUT_MENU_BUTTON_Y_PADDING) * 2.0f) + LayoutScale(2.0f));
const float bg_alpha = System::IsValid() ? 0.90f : 1.0f; const float bg_alpha = GPUThread::HasGPUBackend() ? 0.90f : 1.0f;
if (BeginFullscreenWindow(ImVec2(0.0f, 0.0f), heading_size, "gamelist_view", if (BeginFullscreenWindow(ImVec2(0.0f, 0.0f), heading_size, "gamelist_view",
MulAlpha(UIStyle.PrimaryColor, bg_alpha))) MulAlpha(UIStyle.PrimaryColor, bg_alpha)))
@ -7217,7 +7269,7 @@ void FullscreenUI::DrawGameListSettingsWindow()
ImVec2(io.DisplaySize.x, LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY) + ImVec2(io.DisplaySize.x, LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY) +
(LayoutScale(LAYOUT_MENU_BUTTON_Y_PADDING) * 2.0f) + LayoutScale(2.0f)); (LayoutScale(LAYOUT_MENU_BUTTON_Y_PADDING) * 2.0f) + LayoutScale(2.0f));
const float bg_alpha = System::IsValid() ? 0.90f : 1.0f; const float bg_alpha = GPUThread::HasGPUBackend() ? 0.90f : 1.0f;
if (BeginFullscreenWindow(ImVec2(0.0f, 0.0f), heading_size, "gamelist_view", if (BeginFullscreenWindow(ImVec2(0.0f, 0.0f), heading_size, "gamelist_view",
MulAlpha(UIStyle.PrimaryColor, bg_alpha))) MulAlpha(UIStyle.PrimaryColor, bg_alpha)))
@ -7533,74 +7585,223 @@ void FullscreenUI::DrawAboutWindow()
void FullscreenUI::OpenAchievementsWindow() void FullscreenUI::OpenAchievementsWindow()
{ {
if (!System::IsValid())
return;
if (!Achievements::IsActive()) if (!Achievements::IsActive())
{ {
Host::AddKeyedOSDMessage("achievements_disabled", FSUI_STR("Achievements are not enabled."), Host::AddKeyedOSDMessage("achievements_disabled", FSUI_STR("Achievements are not enabled."),
Host::OSD_INFO_DURATION); Host::OSD_INFO_DURATION);
return; return;
} }
else if (!Achievements::HasAchievements())
if (!System::IsValid() || !Initialize())
return;
if (!Achievements::HasAchievements() || !Achievements::PrepareAchievementsWindow())
{ {
ShowToast(std::string(), FSUI_STR("This game has no achievements.")); ShowToast(std::string(), FSUI_STR("This game has no achievements."));
return; return;
} }
if (s_state.current_main_window != MainWindowType::PauseMenu) GPUThread::RunOnThread([]() {
{ if (!Initialize() || !Achievements::PrepareAchievementsWindow())
PauseForMenuOpen(false); return;
ForceKeyNavEnabled();
}
s_state.current_main_window = MainWindowType::Achievements; if (s_state.current_main_window != MainWindowType::PauseMenu)
QueueResetFocus(FocusResetType::ViewChanged); {
FixStateIfPaused(); PauseForMenuOpen(false);
} ForceKeyNavEnabled();
}
bool FullscreenUI::IsAchievementsWindowOpen() s_state.current_main_window = MainWindowType::Achievements;
{ QueueResetFocus(FocusResetType::ViewChanged);
return (s_state.current_main_window == MainWindowType::Achievements); UpdateRunIdleState();
FixStateIfPaused();
});
} }
void FullscreenUI::OpenLeaderboardsWindow() void FullscreenUI::OpenLeaderboardsWindow()
{ {
if (!System::IsValid())
return;
if (!Achievements::IsActive()) if (!Achievements::IsActive())
{ {
Host::AddKeyedOSDMessage("achievements_disabled", FSUI_STR("Leaderboards are not enabled."), Host::AddKeyedOSDMessage("achievements_disabled", FSUI_STR("Leaderboards are not enabled."),
Host::OSD_INFO_DURATION); Host::OSD_INFO_DURATION);
return; return;
} }
else if (!Achievements::HasLeaderboards())
if (!System::IsValid() || !Initialize())
return;
if (!Achievements::HasLeaderboards() || !Achievements::PrepareLeaderboardsWindow())
{ {
ShowToast(std::string(), FSUI_STR("This game has no leaderboards.")); ShowToast(std::string(), FSUI_STR("This game has no leaderboards."));
return; return;
} }
if (s_state.current_main_window != MainWindowType::PauseMenu) GPUThread::RunOnThread([]() {
{ if (!Initialize() || !Achievements::PrepareLeaderboardsWindow())
PauseForMenuOpen(false); return;
ForceKeyNavEnabled();
}
s_state.current_main_window = MainWindowType::Leaderboards; if (s_state.current_main_window != MainWindowType::PauseMenu)
QueueResetFocus(FocusResetType::ViewChanged); {
FixStateIfPaused(); PauseForMenuOpen(false);
} ForceKeyNavEnabled();
}
bool FullscreenUI::IsLeaderboardsWindowOpen() s_state.current_main_window = MainWindowType::Leaderboards;
{ QueueResetFocus(FocusResetType::ViewChanged);
return (s_state.current_main_window == MainWindowType::Leaderboards); UpdateRunIdleState();
FixStateIfPaused();
});
} }
#endif // __ANDROID__ #endif // __ANDROID__
LoadingScreenProgressCallback::LoadingScreenProgressCallback()
: ProgressCallback(), m_open_time(Timer::GetCurrentValue()), m_on_gpu_thread(GPUThread::IsOnThread())
{
}
LoadingScreenProgressCallback::~LoadingScreenProgressCallback()
{
// Did we activate?
if (m_last_progress_percent < 0)
return;
if (!m_on_gpu_thread)
{
GPUThread::RunOnThread([]() {
ImGuiFullscreen::CloseLoadingScreen();
Assert(GPUThread::GetRunIdleReason(GPUThread::RunIdleReason::LoadingScreenActive));
GPUThread::SetRunIdleReason(GPUThread::RunIdleReason::LoadingScreenActive, false);
});
}
else
{
// since this was pushing frames, we need to restore the context
GPUThread::Internal::RestoreContextAfterPresent();
}
}
void LoadingScreenProgressCallback::PushState()
{
ProgressCallback::PushState();
}
void LoadingScreenProgressCallback::PopState()
{
ProgressCallback::PopState();
Redraw(true);
}
void LoadingScreenProgressCallback::SetCancellable(bool cancellable)
{
ProgressCallback::SetCancellable(cancellable);
Redraw(true);
}
void LoadingScreenProgressCallback::SetTitle(const std::string_view title)
{
// todo?
}
void LoadingScreenProgressCallback::SetStatusText(const std::string_view text)
{
ProgressCallback::SetStatusText(text);
Redraw(true);
}
void LoadingScreenProgressCallback::SetProgressRange(u32 range)
{
u32 last_range = m_progress_range;
ProgressCallback::SetProgressRange(range);
if (m_progress_range != last_range)
Redraw(false);
}
void LoadingScreenProgressCallback::SetProgressValue(u32 value)
{
u32 lastValue = m_progress_value;
ProgressCallback::SetProgressValue(value);
if (m_progress_value != lastValue)
Redraw(false);
}
void LoadingScreenProgressCallback::Redraw(bool force)
{
if (m_last_progress_percent < 0 &&
Timer::ConvertValueToSeconds(Timer::GetCurrentValue() - m_open_time) < m_open_delay)
{
return;
}
const int percent =
static_cast<int>((static_cast<float>(m_progress_value) / static_cast<float>(m_progress_range)) * 100.0f);
DebugAssert(percent >= 0);
if (percent == m_last_progress_percent && !force)
return;
// activation?
if (m_last_progress_percent < 0 && !m_on_gpu_thread)
{
GPUThread::RunOnThread([]() {
Assert(!GPUThread::GetRunIdleReason(GPUThread::RunIdleReason::LoadingScreenActive));
GPUThread::SetRunIdleReason(GPUThread::RunIdleReason::LoadingScreenActive, true);
});
}
m_last_progress_percent = percent;
if (m_on_gpu_thread)
{
ImGuiFullscreen::RenderLoadingScreen(ImGuiManager::LOGO_IMAGE_NAME, m_status_text, 0,
static_cast<s32>(m_progress_range), static_cast<s32>(m_progress_value));
}
else
{
GPUThread::RunOnThread([status_text = SmallString(std::string_view(m_status_text)),
range = static_cast<s32>(m_progress_range), value = static_cast<s32>(m_progress_value)]() {
ImGuiFullscreen::OpenOrUpdateLoadingScreen(ImGuiManager::LOGO_IMAGE_NAME, status_text, 0, range, value);
});
}
}
void LoadingScreenProgressCallback::ModalError(const std::string_view message)
{
ERROR_LOG(message);
Host::ReportErrorAsync("Error", message);
}
bool LoadingScreenProgressCallback::ModalConfirmation(const std::string_view message)
{
INFO_LOG(message);
return Host::ConfirmMessage("Confirm", message);
}
void FullscreenUI::OpenLoadingScreen(std::string_view image, std::string_view message, s32 progress_min /*= -1*/,
s32 progress_max /*= -1*/, s32 progress_value /*= -1*/)
{
Assert(GPUThread::IsOnThread());
Assert(!GPUThread::GetRunIdleReason(GPUThread::RunIdleReason::LoadingScreenActive));
GPUThread::SetRunIdleReason(GPUThread::RunIdleReason::LoadingScreenActive, true);
ImGuiFullscreen::OpenOrUpdateLoadingScreen(image, message, progress_min, progress_max, progress_value);
}
void FullscreenUI::UpdateLoadingScreen(std::string_view image, std::string_view message, s32 progress_min /*= -1*/,
s32 progress_max /*= -1*/, s32 progress_value /*= -1*/)
{
Assert(GPUThread::IsOnThread());
Assert(GPUThread::GetRunIdleReason(GPUThread::RunIdleReason::LoadingScreenActive));
ImGuiFullscreen::OpenOrUpdateLoadingScreen(image, message, progress_min, progress_max, progress_value);
}
void FullscreenUI::CloseLoadingScreen()
{
Assert(GPUThread::IsOnThread());
Assert(GPUThread::GetRunIdleReason(GPUThread::RunIdleReason::LoadingScreenActive));
ImGuiFullscreen::CloseLoadingScreen();
GPUThread::SetRunIdleReason(GPUThread::RunIdleReason::LoadingScreenActive, false);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Translation String Area // Translation String Area
// To avoid having to type T_RANSLATE("FullscreenUI", ...) everywhere, we use the shorter macros at the top // To avoid having to type T_RANSLATE("FullscreenUI", ...) everywhere, we use the shorter macros at the top

View File

@ -13,15 +13,14 @@
class SmallStringBase; class SmallStringBase;
struct Settings; struct GPUSettings;
namespace FullscreenUI { namespace FullscreenUI {
bool Initialize(); bool Initialize();
bool IsInitialized(); bool IsInitialized();
bool HasActiveWindow(); bool HasActiveWindow();
void CheckForConfigChanges(const Settings& old_settings); void CheckForConfigChanges(const GPUSettings& old_settings);
void OnSystemStarted(); void OnSystemStarted();
void OnSystemPaused();
void OnSystemResumed(); void OnSystemResumed();
void OnSystemDestroyed(); void OnSystemDestroyed();
void OnRunningGameChanged(); void OnRunningGameChanged();
@ -30,9 +29,7 @@ void OnRunningGameChanged();
void OpenPauseMenu(); void OpenPauseMenu();
void OpenCheatsMenu(); void OpenCheatsMenu();
void OpenAchievementsWindow(); void OpenAchievementsWindow();
bool IsAchievementsWindowOpen();
void OpenLeaderboardsWindow(); void OpenLeaderboardsWindow();
bool IsLeaderboardsWindowOpen();
void ReturnToMainWindow(); void ReturnToMainWindow();
void ReturnToPreviousWindow(); void ReturnToPreviousWindow();
void SetStandardSelectionFooterText(bool back_instead_of_cancel); void SetStandardSelectionFooterText(bool back_instead_of_cancel);
@ -43,13 +40,51 @@ void Render();
void InvalidateCoverCache(); void InvalidateCoverCache();
void TimeToPrintableString(SmallStringBase* str, time_t t); void TimeToPrintableString(SmallStringBase* str, time_t t);
void OpenLoadingScreen(std::string_view image, std::string_view message, s32 progress_min = -1, s32 progress_max = -1,
s32 progress_value = -1);
void UpdateLoadingScreen(std::string_view image, std::string_view message, s32 progress_min = -1, s32 progress_max = -1,
s32 progress_value = -1);
void CloseLoadingScreen();
} // namespace FullscreenUI } // namespace FullscreenUI
class LoadingScreenProgressCallback final : public ProgressCallback
{
public:
LoadingScreenProgressCallback();
~LoadingScreenProgressCallback() override;
ALWAYS_INLINE void SetOpenDelay(float delay) { m_open_delay = delay; }
void PushState() override;
void PopState() override;
void SetCancellable(bool cancellable) override;
void SetTitle(const std::string_view title) override;
void SetStatusText(const std::string_view text) override;
void SetProgressRange(u32 range) override;
void SetProgressValue(u32 value) override;
void ModalError(const std::string_view message) override;
bool ModalConfirmation(const std::string_view message) override;
private:
void Redraw(bool force);
u64 m_open_time = 0;
float m_open_delay = 1.0f;
s32 m_last_progress_percent = -1;
bool m_on_gpu_thread = false;
};
// Host UI triggers from Big Picture mode. // Host UI triggers from Big Picture mode.
namespace Host { namespace Host {
#ifndef __ANDROID__ #ifndef __ANDROID__
/// Called whenever fullscreen UI starts/stops.
void OnFullscreenUIStartedOrStopped(bool started);
/// Requests shut down and exit of the hosting application. This may not actually exit, /// Requests shut down and exit of the hosting application. This may not actually exit,
/// if the user cancels the shutdown confirmation. /// if the user cancels the shutdown confirmation.
void RequestExitApplication(bool allow_confirm); void RequestExitApplication(bool allow_confirm);

File diff suppressed because it is too large Load Diff

View File

@ -39,13 +39,18 @@ enum class PacketType : u8;
class Recorder; class Recorder;
class Player; class Player;
} // namespace GPUDump } // namespace GPUDump
class GPUBackend;
struct Settings; struct Settings;
namespace Threading { namespace System {
class Thread; struct MemorySaveState;
} }
class GPU struct GPUBackendCommand;
struct GPUBackendDrawCommand;
class GPU final
{ {
public: public:
enum class BlitterState : u8 enum class BlitterState : u8
@ -62,7 +67,6 @@ public:
DOT_TIMER_INDEX = 0, DOT_TIMER_INDEX = 0,
HBLANK_TIMER_INDEX = 1, HBLANK_TIMER_INDEX = 1,
MAX_RESOLUTION_SCALE = 32, MAX_RESOLUTION_SCALE = 32,
DEINTERLACE_BUFFER_COUNT = 4,
DRAWING_AREA_COORD_MASK = 1023, DRAWING_AREA_COORD_MASK = 1023,
}; };
@ -80,33 +84,24 @@ public:
NTSC_HORIZONTAL_ACTIVE_END = 3288, NTSC_HORIZONTAL_ACTIVE_END = 3288,
NTSC_VERTICAL_ACTIVE_START = 16, NTSC_VERTICAL_ACTIVE_START = 16,
NTSC_VERTICAL_ACTIVE_END = 256, NTSC_VERTICAL_ACTIVE_END = 256,
PAL_HORIZONTAL_ACTIVE_START = 487, PAL_HORIZONTAL_ACTIVE_START = 488,
PAL_HORIZONTAL_ACTIVE_END = 3282, PAL_HORIZONTAL_ACTIVE_END = 3300,
PAL_VERTICAL_ACTIVE_START = 20, PAL_VERTICAL_ACTIVE_START = 20,
PAL_VERTICAL_ACTIVE_END = 308, PAL_VERTICAL_ACTIVE_END = 308,
}; };
// Base class constructor. // Base class constructor.
GPU(); GPU();
virtual ~GPU(); ~GPU();
virtual const Threading::Thread* GetSWThread() const = 0; void Initialize();
virtual bool IsHardwareRenderer() const = 0; void Shutdown();
void Reset(bool clear_vram);
virtual bool Initialize(Error* error); bool DoState(StateWrapper& sw, bool update_display);
virtual void Reset(bool clear_vram); void DoMemoryState(StateWrapper& sw, System::MemorySaveState& mss, bool update_display);
virtual bool DoState(StateWrapper& sw, GPUTexture** save_to_texture, bool update_display);
// Graphics API state reset/restore - call when drawing the UI etc.
// TODO: replace with "invalidate cached state"
virtual void RestoreDeviceContext();
// Render statistics debug window. // Render statistics debug window.
void DrawDebugStateWindow(float scale); void DrawDebugStateWindow(float scale);
void GetStatsString(SmallStringBase& str);
void GetMemoryStatsString(SmallStringBase& str);
void ResetStatistics();
void UpdateStatistics(u32 frame_count);
void CPUClockChanged(); void CPUClockChanged();
@ -170,31 +165,26 @@ public:
void SynchronizeCRTC(); void SynchronizeCRTC();
/// Recompile shaders/recreate framebuffers when needed. /// Recompile shaders/recreate framebuffers when needed.
virtual void UpdateSettings(const Settings& old_settings); void UpdateSettings(const Settings& old_settings);
/// Returns the current resolution scale.
virtual u32 GetResolutionScale() const;
/// Updates the resolution scale when it's set to automatic.
virtual void UpdateResolutionScale();
/// Returns the full display resolution of the GPU, including padding. /// Returns the full display resolution of the GPU, including padding.
std::tuple<u32, u32> GetFullDisplayResolution() const; std::tuple<u32, u32> GetFullDisplayResolution() const;
/// Computes clamped drawing area.
static GSVector4i GetClampedDrawingArea(const GPUDrawingArea& drawing_area);
float ComputeHorizontalFrequency() const; float ComputeHorizontalFrequency() const;
float ComputeVerticalFrequency() const; float ComputeVerticalFrequency() const;
float ComputeDisplayAspectRatio() const; float ComputeDisplayAspectRatio() const;
float ComputeSourceAspectRatio() const; float ComputeSourceAspectRatio() const;
float ComputePixelAspectRatio() const;
/// Computes aspect ratio correction, i.e. the scale to apply to the source aspect ratio to preserve /// Computes aspect ratio correction, i.e. the scale to apply to the source aspect ratio to preserve
/// the original pixel aspect ratio regardless of how much cropping has been applied. /// the original pixel aspect ratio regardless of how much cropping has been applied.
float ComputeAspectRatioCorrection() const; float ComputeAspectRatioCorrection() const;
/// Applies the pixel aspect ratio to a given size, preserving the larger dimension. /// Applies the pixel aspect ratio to a given size, preserving the larger dimension.
void ApplyPixelAspectRatioToSize(float* width, float* height) const; static void ApplyPixelAspectRatioToSize(float par, float* width, float* height);
static std::unique_ptr<GPU> CreateHardwareRenderer();
static std::unique_ptr<GPU> CreateSoftwareRenderer();
// Converts window coordinates into horizontal ticks and scanlines. Returns false if out of range. Used for lightguns. // Converts window coordinates into horizontal ticks and scanlines. Returns false if out of range. Used for lightguns.
void ConvertScreenCoordinatesToDisplayCoordinates(float window_x, float window_y, float* display_x, void ConvertScreenCoordinatesToDisplayCoordinates(float window_x, float window_y, float* display_x,
@ -226,38 +216,17 @@ public:
// Dumps raw VRAM to a file. // Dumps raw VRAM to a file.
bool DumpVRAMToFile(const char* filename); bool DumpVRAMToFile(const char* filename);
// Ensures all buffered vertices are drawn. // Queues the current frame for presentation. Should only be used with runahead.
virtual void FlushRender() = 0; void QueuePresentCurrentFrame();
/// Helper function for computing the draw rectangle in a larger window. /// Helper function for computing the draw rectangle in a larger window.
void CalculateDrawRect(s32 window_width, s32 window_height, bool apply_rotation, bool apply_aspect_ratio, static void CalculateDrawRect(u32 window_width, u32 window_height, u32 crtc_display_width, u32 crtc_display_height,
GSVector4i* display_rect, GSVector4i* draw_rect) const; s32 display_origin_left, s32 display_origin_top, u32 display_vram_width,
u32 display_vram_height, DisplayRotation rotation, DisplayAlignment alignment,
float pixel_aspect_ratio, bool stretch_vertically, bool integer_scale,
GSVector4i* display_rect, GSVector4i* draw_rect);
/// Helper function for computing screenshot bounds. private:
void CalculateScreenshotSize(DisplayScreenshotMode mode, u32* width, u32* height, GSVector4i* display_rect,
GSVector4i* draw_rect) const;
/// Helper function to save current display texture to PNG.
bool WriteDisplayTextureToFile(std::string path);
/// Renders the display, optionally with postprocessing to the specified image.
bool RenderScreenshotToBuffer(u32 width, u32 height, const GSVector4i display_rect, const GSVector4i draw_rect,
bool postfx, Image* out_image);
/// Helper function to save screenshot to PNG.
bool RenderScreenshotToFile(std::string path, DisplayScreenshotMode mode, u8 quality, bool compress_on_thread,
bool show_osd_message);
/// Draws the current display texture, with any post-processing.
GPUDevice::PresentResult PresentDisplay();
/// Sends the current frame to media capture.
bool SendDisplayToMediaCapture(MediaCapture* cap);
/// Reads the CLUT from the specified coordinates, accounting for wrap-around.
static void ReadCLUT(u16* dest, GPUTexturePaletteReg reg, bool clut_is_8bit);
protected:
TickCount CRTCTicksToSystemTicks(TickCount crtc_ticks, TickCount fractional_ticks) const; TickCount CRTCTicksToSystemTicks(TickCount crtc_ticks, TickCount fractional_ticks) const;
TickCount SystemTicksToCRTCTicks(TickCount sysclk_ticks, TickCount* fractional_ticks) const; TickCount SystemTicksToCRTCTicks(TickCount sysclk_ticks, TickCount* fractional_ticks) const;
@ -268,16 +237,6 @@ protected:
} }
ALWAYS_INLINE static constexpr TickCount SystemTicksToGPUTicks(TickCount sysclk_ticks) { return sysclk_ticks << 1; } ALWAYS_INLINE static constexpr TickCount SystemTicksToGPUTicks(TickCount sysclk_ticks) { return sysclk_ticks << 1; }
static constexpr std::tuple<u8, u8> UnpackTexcoord(u16 texcoord)
{
return std::make_tuple(static_cast<u8>(texcoord), static_cast<u8>(texcoord >> 8));
}
static constexpr std::tuple<u8, u8, u8> UnpackColorRGB24(u32 rgb24)
{
return std::make_tuple(static_cast<u8>(rgb24), static_cast<u8>(rgb24 >> 8), static_cast<u8>(rgb24 >> 16));
}
static bool DumpVRAMToFile(const char* filename, u32 width, u32 height, u32 stride, const void* buffer, static bool DumpVRAMToFile(const char* filename, u32 width, u32 height, u32 stride, const void* buffer,
bool remove_alpha); bool remove_alpha);
@ -297,10 +256,10 @@ protected:
void UpdateGPUIdle(); void UpdateGPUIdle();
/// Returns 0 if the currently-displayed field is on odd lines (1,3,5,...) or 1 if even (2,4,6,...). /// Returns 0 if the currently-displayed field is on odd lines (1,3,5,...) or 1 if even (2,4,6,...).
ALWAYS_INLINE u32 GetInterlacedDisplayField() const { return ZeroExtend32(m_crtc_state.interlaced_field); } ALWAYS_INLINE u8 GetInterlacedDisplayField() const { return m_crtc_state.interlaced_field; }
/// Returns 0 if the currently-displayed field is on an even line in VRAM, otherwise 1. /// Returns 0 if the currently-displayed field is on an even line in VRAM, otherwise 1.
ALWAYS_INLINE u32 GetActiveLineLSB() const { return ZeroExtend32(m_crtc_state.active_line_lsb); } ALWAYS_INLINE u8 GetActiveLineLSB() const { return m_crtc_state.active_line_lsb; }
/// Updates drawing area that's suitablef or clamping. /// Updates drawing area that's suitablef or clamping.
void SetClampedDrawingArea(); void SetClampedDrawingArea();
@ -320,7 +279,7 @@ protected:
/// Returns the number of vertices in the buffered poly-line. /// Returns the number of vertices in the buffered poly-line.
ALWAYS_INLINE u32 GetPolyLineVertexCount() const ALWAYS_INLINE u32 GetPolyLineVertexCount() const
{ {
return (static_cast<u32>(m_blit_buffer.size()) + BoolToUInt32(m_render_command.shading_enable)) >> return (static_cast<u32>(m_polyline_buffer.size()) + BoolToUInt32(m_render_command.shading_enable)) >>
BoolToUInt8(m_render_command.shading_enable); BoolToUInt8(m_render_command.shading_enable);
} }
@ -335,16 +294,13 @@ protected:
void InvalidateCLUT(); void InvalidateCLUT();
bool IsCLUTValid() const; bool IsCLUTValid() const;
// Rendering in the backend void ReadVRAM(u16 x, u16 y, u16 width, u16 height);
virtual void ReadVRAM(u32 x, u32 y, u32 width, u32 height) = 0; void UpdateVRAM(u16 x, u16 y, u16 width, u16 height, const void* data, bool set_mask, bool check_mask);
virtual void FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color) = 0; void UpdateDisplay(bool submit_frame);
virtual void UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data, bool set_mask, bool check_mask) = 0;
virtual void CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height) = 0; void PrepareForDraw();
virtual void DispatchRenderCommand() = 0; void FinishPolyline();
virtual void UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit) = 0; void FillDrawCommand(GPUBackendDrawCommand* RESTRICT cmd, GPURenderCommand rc) const;
virtual void UpdateDisplay() = 0;
virtual void DrawRendererStats();
virtual void OnBufferSwapped();
ALWAYS_INLINE_RELEASE void AddDrawTriangleTicks(GSVector2i v1, GSVector2i v2, GSVector2i v3, bool shaded, ALWAYS_INLINE_RELEASE void AddDrawTriangleTicks(GSVector2i v1, GSVector2i v2, GSVector2i v3, bool shaded,
bool textured, bool semitransparent) bool textured, bool semitransparent)
@ -441,14 +397,10 @@ protected:
u32 texture_window_value; u32 texture_window_value;
// decoded values // decoded values
// TODO: Make this a command
GPUTextureWindow texture_window; GPUTextureWindow texture_window;
bool texture_x_flip; bool texture_x_flip;
bool texture_y_flip; bool texture_y_flip;
bool texture_page_changed;
ALWAYS_INLINE bool IsTexturePageChanged() const { return texture_page_changed; }
ALWAYS_INLINE void SetTexturePageChanged() { texture_page_changed = true; }
ALWAYS_INLINE void ClearTexturePageChangedFlag() { texture_page_changed = false; }
} m_draw_mode = {}; } m_draw_mode = {};
GPUDrawingArea m_drawing_area = {}; GPUDrawingArea m_drawing_area = {};
@ -568,79 +520,21 @@ protected:
u16 row; u16 row;
} m_vram_transfer = {}; } m_vram_transfer = {};
std::unique_ptr<GPUDump::Recorder> m_gpu_dump;
HeapFIFOQueue<u64, MAX_FIFO_SIZE> m_fifo; HeapFIFOQueue<u64, MAX_FIFO_SIZE> m_fifo;
std::vector<u32> m_blit_buffer; TickCount m_max_run_ahead = 128;
u32 m_fifo_size = 128;
u32 m_blit_remaining_words; u32 m_blit_remaining_words;
GPURenderCommand m_render_command{}; GPURenderCommand m_render_command{};
std::vector<u32> m_blit_buffer;
std::unique_ptr<GPUDump::Recorder> m_gpu_dump; std::vector<u64> m_polyline_buffer;
ALWAYS_INLINE u32 FifoPop() { return Truncate32(m_fifo.Pop()); } ALWAYS_INLINE u32 FifoPop() { return Truncate32(m_fifo.Pop()); }
ALWAYS_INLINE u32 FifoPeek() { return Truncate32(m_fifo.Peek()); } ALWAYS_INLINE u32 FifoPeek() { return Truncate32(m_fifo.Peek()); }
ALWAYS_INLINE u32 FifoPeek(u32 i) { return Truncate32(m_fifo.Peek(i)); } ALWAYS_INLINE u32 FifoPeek(u32 i) { return Truncate32(m_fifo.Peek(i)); }
TickCount m_max_run_ahead = 128;
u32 m_fifo_size = 128;
void ClearDisplayTexture();
void SetDisplayTexture(GPUTexture* texture, GPUTexture* depth_texture, s32 view_x, s32 view_y, s32 view_width,
s32 view_height);
GPUDevice::PresentResult RenderDisplay(GPUTexture* target, const GSVector4i display_rect, const GSVector4i draw_rect,
bool postfx);
bool Deinterlace(u32 field, u32 line_skip);
bool DeinterlaceExtractField(u32 dst_bufidx, GPUTexture* src, u32 x, u32 y, u32 width, u32 height, u32 line_skip);
bool DeinterlaceSetTargetSize(u32 width, u32 height, bool preserve);
void DestroyDeinterlaceTextures();
bool ApplyChromaSmoothing();
u32 m_current_deinterlace_buffer = 0;
std::unique_ptr<GPUPipeline> m_deinterlace_pipeline;
std::unique_ptr<GPUPipeline> m_deinterlace_extract_pipeline;
std::array<std::unique_ptr<GPUTexture>, DEINTERLACE_BUFFER_COUNT> m_deinterlace_buffers;
std::unique_ptr<GPUTexture> m_deinterlace_texture;
std::unique_ptr<GPUPipeline> m_chroma_smoothing_pipeline;
std::unique_ptr<GPUTexture> m_chroma_smoothing_texture;
std::unique_ptr<GPUPipeline> m_display_pipeline;
GPUTexture* m_display_texture = nullptr;
GPUTexture* m_display_depth_buffer = nullptr;
s32 m_display_texture_view_x = 0;
s32 m_display_texture_view_y = 0;
s32 m_display_texture_view_width = 0;
s32 m_display_texture_view_height = 0;
struct Counters
{
u32 num_reads;
u32 num_writes;
u32 num_copies;
u32 num_vertices;
u32 num_primitives;
// u32 num_read_texture_updates;
// u32 num_ubo_updates;
};
struct Stats : Counters
{
size_t host_buffer_streamed;
u32 host_num_draws;
u32 host_num_barriers;
u32 host_num_render_passes;
u32 host_num_copies;
u32 host_num_downloads;
u32 host_num_uploads;
};
Counters m_counters = {};
Stats m_stats = {};
private: private:
bool CompileDisplayPipelines(bool display, bool deinterlace, bool chroma_smoothing, Error* error);
using GP0CommandHandler = bool (GPU::*)(); using GP0CommandHandler = bool (GPU::*)();
using GP0CommandHandlerTable = std::array<GP0CommandHandler, 256>; using GP0CommandHandlerTable = std::array<GP0CommandHandler, 256>;
static GP0CommandHandlerTable GenerateGP0CommandHandlerTable(); static GP0CommandHandlerTable GenerateGP0CommandHandlerTable();
@ -668,6 +562,6 @@ private:
static const GP0CommandHandlerTable s_GP0_command_handler_table; static const GP0CommandHandlerTable s_GP0_command_handler_table;
}; };
extern std::unique_ptr<GPU> g_gpu; extern GPU g_gpu;
extern u16 g_vram[VRAM_SIZE / sizeof(u16)]; extern u16 g_vram[VRAM_SIZE / sizeof(u16)];
extern u16 g_gpu_clut[GPU_CLUT_SIZE]; extern u16 g_gpu_clut[GPU_CLUT_SIZE];

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,9 @@
#pragma once #pragma once
#include "gpu_types.h" #include "gpu_thread_commands.h"
#include "util/gpu_device.h"
#include "common/heap_array.h" #include "common/heap_array.h"
#include "common/threading.h" #include "common/threading.h"
@ -12,85 +14,195 @@
#include <condition_variable> #include <condition_variable>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <tuple>
#ifdef _MSC_VER class Error;
#pragma warning(push) class SmallStringBase;
#pragma warning(disable : 4324) // warning C4324: 'GPUBackend': structure was padded due to alignment specifier
#endif class GPUFramebuffer;
class GPUPipeline;
struct GPUSettings;
class StateWrapper;
namespace System {
struct MemorySaveState;
}
// DESIGN NOTE: Only static methods should be called on the CPU thread.
// You specifically don't have a global pointer available for this reason.
class GPUBackend class GPUBackend
{ {
public:
static GPUThreadCommand* NewClearVRAMCommand();
static GPUThreadCommand* NewClearDisplayCommand();
static GPUBackendUpdateDisplayCommand* NewUpdateDisplayCommand();
static GPUBackendSubmitFrameCommand* NewSubmitFrameCommand();
static GPUThreadCommand* NewClearCacheCommand();
static GPUThreadCommand* NewBufferSwappedCommand();
static GPUBackendReadVRAMCommand* NewReadVRAMCommand();
static GPUBackendFillVRAMCommand* NewFillVRAMCommand();
static GPUBackendUpdateVRAMCommand* NewUpdateVRAMCommand(u32 num_words);
static GPUBackendCopyVRAMCommand* NewCopyVRAMCommand();
static GPUBackendSetDrawingAreaCommand* NewSetDrawingAreaCommand();
static GPUBackendUpdateCLUTCommand* NewUpdateCLUTCommand();
static GPUBackendDrawPolygonCommand* NewDrawPolygonCommand(u32 num_vertices);
static GPUBackendDrawPrecisePolygonCommand* NewDrawPrecisePolygonCommand(u32 num_vertices);
static GPUBackendDrawRectangleCommand* NewDrawRectangleCommand();
static GPUBackendDrawLineCommand* NewDrawLineCommand(u32 num_vertices);
static GPUBackendDrawPreciseLineCommand* NewDrawPreciseLineCommand(u32 num_vertices);
static void PushCommand(GPUThreadCommand* cmd);
static void PushCommandAndWakeThread(GPUThreadCommand* cmd);
static void PushCommandAndSync(GPUThreadCommand* cmd, bool spin);
static void SyncGPUThread(bool spin);
static bool IsUsingHardwareBackend();
static std::unique_ptr<GPUBackend> CreateHardwareBackend();
static std::unique_ptr<GPUBackend> CreateSoftwareBackend();
static bool RenderScreenshotToBuffer(u32 width, u32 height, bool postfx, Image* out_image);
static void RenderScreenshotToFile(const std::string_view path, DisplayScreenshotMode mode, u8 quality,
bool compress_on_thread, bool show_osd_message);
static bool BeginQueueFrame();
static void WaitForOneQueuedFrame();
static u32 GetQueuedFrameCount();
static bool AllocateMemorySaveStates(std::span<System::MemorySaveState> states, Error* error);
public: public:
GPUBackend(); GPUBackend();
virtual ~GPUBackend(); virtual ~GPUBackend();
ALWAYS_INLINE const Threading::Thread* GetThread() const { return m_use_gpu_thread ? &m_gpu_thread : nullptr; } virtual bool Initialize(bool upload_vram, Error* error);
ALWAYS_INLINE bool IsUsingThread() const { return m_use_gpu_thread; }
virtual bool Initialize(bool use_thread); virtual void UpdateSettings(const GPUSettings& old_settings);
virtual void Reset();
virtual void Shutdown();
void SetThreadEnabled(bool use_thread); /// Returns the current resolution scale.
virtual u32 GetResolutionScale() const = 0;
GPUBackendFillVRAMCommand* NewFillVRAMCommand(); /// Updates the resolution scale when it's set to automatic.
GPUBackendUpdateVRAMCommand* NewUpdateVRAMCommand(u32 num_words); virtual void UpdateResolutionScale() = 0;
GPUBackendCopyVRAMCommand* NewCopyVRAMCommand();
GPUBackendSetDrawingAreaCommand* NewSetDrawingAreaCommand();
GPUBackendUpdateCLUTCommand* NewUpdateCLUTCommand();
GPUBackendDrawPolygonCommand* NewDrawPolygonCommand(u32 num_vertices);
GPUBackendDrawRectangleCommand* NewDrawRectangleCommand();
GPUBackendDrawLineCommand* NewDrawLineCommand(u32 num_vertices);
void PushCommand(GPUBackendCommand* cmd); // Graphics API state reset/restore - call when drawing the UI etc.
void Sync(bool allow_sleep); // TODO: replace with "invalidate cached state"
virtual void RestoreDeviceContext() = 0;
/// Processes all pending GPU commands. /// Main command handler for GPU thread.
void RunGPULoop(); void HandleCommand(const GPUThreadCommand* cmd);
/// Draws the current display texture, with any post-processing.
GPUDevice::PresentResult PresentDisplay();
/// Helper function to save current display texture to PNG. Used for regtest.
bool WriteDisplayTextureToFile(std::string filename);
/// Helper function for computing screenshot bounds.
void CalculateScreenshotSize(DisplayScreenshotMode mode, u32* width, u32* height, GSVector4i* display_rect,
GSVector4i* draw_rect) const;
void GetStatsString(SmallStringBase& str) const;
void GetMemoryStatsString(SmallStringBase& str) const;
void ResetStatistics();
void UpdateStatistics(u32 frame_count);
protected: protected:
void* AllocateCommand(GPUBackendCommandType command, u32 size);
u32 GetPendingCommandSize() const;
void WakeGPUThread();
void StartGPUThread();
void StopGPUThread();
virtual void FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color, GPUBackendCommandParameters params) = 0;
virtual void UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data,
GPUBackendCommandParameters params) = 0;
virtual void CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height,
GPUBackendCommandParameters params) = 0;
virtual void DrawPolygon(const GPUBackendDrawPolygonCommand* cmd) = 0;
virtual void DrawRectangle(const GPUBackendDrawRectangleCommand* cmd) = 0;
virtual void DrawLine(const GPUBackendDrawLineCommand* cmd) = 0;
virtual void FlushRender() = 0;
virtual void DrawingAreaChanged(const GPUDrawingArea& new_drawing_area, const GSVector4i clamped_drawing_area) = 0;
virtual void UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit) = 0;
void HandleCommand(const GPUBackendCommand* cmd);
Threading::KernelSemaphore m_sync_semaphore;
std::atomic_bool m_gpu_thread_sleeping{false};
std::atomic_bool m_gpu_loop_done{false};
Threading::Thread m_gpu_thread;
bool m_use_gpu_thread = false;
std::mutex m_sync_mutex;
std::condition_variable m_sync_cpu_thread_cv;
std::condition_variable m_wake_gpu_thread_cv;
bool m_sync_done = false;
enum : u32 enum : u32
{ {
COMMAND_QUEUE_SIZE = 4 * 1024 * 1024, DEINTERLACE_BUFFER_COUNT = 4,
THRESHOLD_TO_WAKE_GPU = 256
}; };
FixedHeapArray<u8, COMMAND_QUEUE_SIZE> m_command_fifo_data; virtual void ReadVRAM(u32 x, u32 y, u32 width, u32 height) = 0;
alignas(HOST_CACHE_LINE_SIZE) std::atomic<u32> m_command_fifo_read_ptr{0}; virtual void FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color, bool interlaced_rendering,
alignas(HOST_CACHE_LINE_SIZE) std::atomic<u32> m_command_fifo_write_ptr{0}; u8 interlaced_display_field) = 0;
virtual void UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data, bool set_mask, bool check_mask) = 0;
virtual void CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height, bool set_mask,
bool check_mask) = 0;
virtual void DrawPolygon(const GPUBackendDrawPolygonCommand* cmd) = 0;
virtual void DrawPrecisePolygon(const GPUBackendDrawPrecisePolygonCommand* cmd) = 0;
virtual void DrawSprite(const GPUBackendDrawRectangleCommand* cmd) = 0;
virtual void DrawLine(const GPUBackendDrawLineCommand* cmd) = 0;
virtual void DrawPreciseLine(const GPUBackendDrawPreciseLineCommand* cmd) = 0;
virtual void DrawingAreaChanged() = 0;
virtual void UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit) = 0;
virtual void ClearCache() = 0;
virtual void OnBufferSwapped() = 0;
virtual void ClearVRAM() = 0;
virtual void UpdateDisplay(const GPUBackendUpdateDisplayCommand* cmd) = 0;
virtual void LoadState(const GPUBackendLoadStateCommand* cmd) = 0;
virtual bool AllocateMemorySaveState(System::MemorySaveState& mss, Error* error) = 0;
virtual void DoMemoryState(StateWrapper& sw, System::MemorySaveState& mss) = 0;
/// Ensures all pending draws are flushed to the host GPU.
virtual void FlushRender() = 0;
/// Helper function for computing the draw rectangle in a larger window.
void CalculateDrawRect(s32 window_width, s32 window_height, bool apply_rotation, bool apply_aspect_ratio,
GSVector4i* display_rect, GSVector4i* draw_rect) const;
/// Renders the display, optionally with postprocessing to the specified image.
bool RenderScreenshotToBuffer(u32 width, u32 height, const GSVector4i display_rect, const GSVector4i draw_rect,
bool postfx, Image* out_image);
bool CompileDisplayPipelines(bool display, bool deinterlace, bool chroma_smoothing, Error* error);
void HandleUpdateDisplayCommand(const GPUBackendUpdateDisplayCommand* cmd);
void HandleSubmitFrameCommand(const GPUBackendFramePresentationParameters* cmd);
void ClearDisplay();
void ClearDisplayTexture();
void SetDisplayTexture(GPUTexture* texture, GPUTexture* depth_buffer, s32 view_x, s32 view_y, s32 view_width,
s32 view_height);
GPUDevice::PresentResult RenderDisplay(GPUTexture* target, const GSVector4i display_rect, const GSVector4i draw_rect,
bool postfx);
/// Sends the current frame to media capture.
void SendDisplayToMediaCapture(MediaCapture* cap);
bool Deinterlace(u32 field, u32 line_skip);
bool DeinterlaceExtractField(u32 dst_bufidx, GPUTexture* src, u32 x, u32 y, u32 width, u32 height, u32 line_skip);
bool DeinterlaceSetTargetSize(u32 width, u32 height, bool preserve);
void DestroyDeinterlaceTextures();
bool ApplyChromaSmoothing();
s32 m_display_width = 0;
s32 m_display_height = 0;
s32 m_display_origin_left = 0;
s32 m_display_origin_top = 0;
s32 m_display_vram_width = 0;
s32 m_display_vram_height = 0;
float m_display_pixel_aspect_ratio = 1.0f;
u32 m_current_deinterlace_buffer = 0;
std::unique_ptr<GPUPipeline> m_deinterlace_pipeline;
std::unique_ptr<GPUPipeline> m_deinterlace_extract_pipeline;
std::array<std::unique_ptr<GPUTexture>, DEINTERLACE_BUFFER_COUNT> m_deinterlace_buffers;
std::unique_ptr<GPUTexture> m_deinterlace_texture;
std::unique_ptr<GPUPipeline> m_chroma_smoothing_pipeline;
std::unique_ptr<GPUTexture> m_chroma_smoothing_texture;
std::unique_ptr<GPUPipeline> m_display_pipeline;
GPUTexture* m_display_texture = nullptr;
GPUTexture* m_display_depth_buffer = nullptr;
s32 m_display_texture_view_x = 0;
s32 m_display_texture_view_y = 0;
s32 m_display_texture_view_width = 0;
s32 m_display_texture_view_height = 0;
}; };
#ifdef _MSC_VER namespace Host {
#pragma warning(pop)
#endif /// Called at the end of the frame, before presentation.
void FrameDoneOnGPUThread(GPUBackend* gpu_backend, u32 frame_number);
} // namespace Host

View File

@ -1,13 +1,17 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com> // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0 // SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "cpu_pgxp.h"
#include "gpu.h" #include "gpu.h"
#include "gpu_backend.h"
#include "gpu_dump.h" #include "gpu_dump.h"
#include "gpu_hw_texture_cache.h" #include "gpu_hw_texture_cache.h"
#include "gpu_thread_commands.h"
#include "interrupt_controller.h" #include "interrupt_controller.h"
#include "system.h" #include "system.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/gsvector_formatter.h"
#include "common/log.h" #include "common/log.h"
#include "common/string_util.h" #include "common/string_util.h"
@ -69,7 +73,7 @@ void GPU::TryExecuteCommands()
{ {
const u32 words_per_vertex = m_render_command.shading_enable ? 2 : 1; const u32 words_per_vertex = m_render_command.shading_enable ? 2 : 1;
u32 terminator_index = u32 terminator_index =
m_render_command.shading_enable ? ((static_cast<u32>(m_blit_buffer.size()) & 1u) ^ 1u) : 0u; m_render_command.shading_enable ? ((static_cast<u32>(m_polyline_buffer.size()) & 1u) ^ 1u) : 0u;
for (; terminator_index < m_fifo.GetSize(); terminator_index += words_per_vertex) for (; terminator_index < m_fifo.GetSize(); terminator_index += words_per_vertex)
{ {
// polyline must have at least two vertices, and the terminator is (word & 0xf000f000) == 0x50005000. // polyline must have at least two vertices, and the terminator is (word & 0xf000f000) == 0x50005000.
@ -82,9 +86,9 @@ void GPU::TryExecuteCommands()
const u32 words_to_copy = std::min(terminator_index, m_fifo.GetSize()); const u32 words_to_copy = std::min(terminator_index, m_fifo.GetSize());
if (words_to_copy > 0) if (words_to_copy > 0)
{ {
m_blit_buffer.reserve(m_blit_buffer.size() + words_to_copy); m_polyline_buffer.reserve(m_polyline_buffer.size() + words_to_copy);
for (u32 i = 0; i < words_to_copy; i++) for (u32 i = 0; i < words_to_copy; i++)
m_blit_buffer.push_back(FifoPop()); m_polyline_buffer.push_back(m_fifo.Pop());
} }
DEBUG_LOG("Added {} words to polyline", words_to_copy); DEBUG_LOG("Added {} words to polyline", words_to_copy);
@ -93,8 +97,8 @@ void GPU::TryExecuteCommands()
// drop terminator // drop terminator
m_fifo.RemoveOne(); m_fifo.RemoveOne();
DEBUG_LOG("Drawing poly-line with {} vertices", GetPolyLineVertexCount()); DEBUG_LOG("Drawing poly-line with {} vertices", GetPolyLineVertexCount());
DispatchRenderCommand(); FinishPolyline();
m_blit_buffer.clear(); m_polyline_buffer.clear();
EndCommand(); EndCommand();
continue; continue;
} }
@ -200,8 +204,8 @@ bool GPU::HandleNOPCommand()
bool GPU::HandleClearCacheCommand() bool GPU::HandleClearCacheCommand()
{ {
DEBUG_LOG("GP0 clear cache"); DEBUG_LOG("GP0 clear cache");
m_draw_mode.SetTexturePageChanged();
InvalidateCLUT(); InvalidateCLUT();
GPUBackend::PushCommand(GPUBackend::NewClearCacheCommand());
m_fifo.RemoveOne(); m_fifo.RemoveOne();
AddCommandTicks(1); AddCommandTicks(1);
EndCommand(); EndCommand();
@ -248,8 +252,6 @@ bool GPU::HandleSetDrawingAreaTopLeftCommand()
DEBUG_LOG("Set drawing area top-left: ({}, {})", left, top); DEBUG_LOG("Set drawing area top-left: ({}, {})", left, top);
if (m_drawing_area.left != left || m_drawing_area.top != top) if (m_drawing_area.left != left || m_drawing_area.top != top)
{ {
FlushRender();
m_drawing_area.left = left; m_drawing_area.left = left;
m_drawing_area.top = top; m_drawing_area.top = top;
m_drawing_area_changed = true; m_drawing_area_changed = true;
@ -270,8 +272,6 @@ bool GPU::HandleSetDrawingAreaBottomRightCommand()
DEBUG_LOG("Set drawing area bottom-right: ({}, {})", right, bottom); DEBUG_LOG("Set drawing area bottom-right: ({}, {})", right, bottom);
if (m_drawing_area.right != right || m_drawing_area.bottom != bottom) if (m_drawing_area.right != right || m_drawing_area.bottom != bottom)
{ {
FlushRender();
m_drawing_area.right = right; m_drawing_area.right = right;
m_drawing_area.bottom = bottom; m_drawing_area.bottom = bottom;
m_drawing_area_changed = true; m_drawing_area_changed = true;
@ -291,8 +291,6 @@ bool GPU::HandleSetDrawingOffsetCommand()
DEBUG_LOG("Set drawing offset ({}, {})", x, y); DEBUG_LOG("Set drawing offset ({}, {})", x, y);
if (m_drawing_offset.x != x || m_drawing_offset.y != y) if (m_drawing_offset.x != x || m_drawing_offset.y != y)
{ {
FlushRender();
m_drawing_offset.x = x; m_drawing_offset.x = x;
m_drawing_offset.y = y; m_drawing_offset.y = y;
} }
@ -308,11 +306,7 @@ bool GPU::HandleSetMaskBitCommand()
constexpr u32 gpustat_mask = (1 << 11) | (1 << 12); constexpr u32 gpustat_mask = (1 << 11) | (1 << 12);
const u32 gpustat_bits = (param & 0x03) << 11; const u32 gpustat_bits = (param & 0x03) << 11;
if ((m_GPUSTAT.bits & gpustat_mask) != gpustat_bits) m_GPUSTAT.bits = (m_GPUSTAT.bits & ~gpustat_mask) | gpustat_bits;
{
FlushRender();
m_GPUSTAT.bits = (m_GPUSTAT.bits & ~gpustat_mask) | gpustat_bits;
}
DEBUG_LOG("Set mask bit {} {}", BoolToUInt32(m_GPUSTAT.set_mask_while_drawing), DEBUG_LOG("Set mask bit {} {}", BoolToUInt32(m_GPUSTAT.set_mask_while_drawing),
BoolToUInt32(m_GPUSTAT.check_mask_before_draw)); BoolToUInt32(m_GPUSTAT.check_mask_before_draw));
@ -321,6 +315,35 @@ bool GPU::HandleSetMaskBitCommand()
return true; return true;
} }
void GPU::PrepareForDraw()
{
if (m_drawing_area_changed)
{
m_drawing_area_changed = false;
GPUBackendSetDrawingAreaCommand* cmd = GPUBackend::NewSetDrawingAreaCommand();
cmd->new_area = m_drawing_area;
GPUBackend::PushCommand(cmd);
}
}
void GPU::FillDrawCommand(GPUBackendDrawCommand* RESTRICT cmd, GPURenderCommand rc) const
{
cmd->interlaced_rendering = IsInterlacedRenderingEnabled();
cmd->active_line_lsb = ConvertToBoolUnchecked(m_crtc_state.active_line_lsb);
cmd->check_mask_before_draw = m_GPUSTAT.check_mask_before_draw;
cmd->set_mask_while_drawing = m_GPUSTAT.set_mask_while_drawing;
cmd->texture_enable = rc.IsTexturingEnabled();
cmd->raw_texture_enable = rc.raw_texture_enable;
cmd->transparency_enable = rc.transparency_enable;
cmd->shading_enable = rc.shading_enable;
cmd->quad_polygon = rc.quad_polygon;
cmd->dither_enable = rc.IsDitheringEnabled() && m_draw_mode.mode_reg.dither_enable;
cmd->draw_mode.bits = m_draw_mode.mode_reg.bits;
cmd->palette.bits = m_draw_mode.palette_reg.bits;
cmd->window = m_draw_mode.texture_window;
}
bool GPU::HandleRenderPolygonCommand() bool GPU::HandleRenderPolygonCommand()
{ {
const GPURenderCommand rc{FifoPeek(0)}; const GPURenderCommand rc{FifoPeek(0)};
@ -346,6 +369,7 @@ bool GPU::HandleRenderPolygonCommand()
words_per_vertex, setup_ticks); words_per_vertex, setup_ticks);
// set draw state up // set draw state up
// TODO: Get rid of SetTexturePalette() and just fill it as needed
if (rc.texture_enable) if (rc.texture_enable)
{ {
const u16 texpage_attribute = Truncate16((rc.shading_enable ? FifoPeek(5) : FifoPeek(4)) >> 16); const u16 texpage_attribute = Truncate16((rc.shading_enable ? FifoPeek(5) : FifoPeek(4)) >> 16);
@ -355,12 +379,233 @@ bool GPU::HandleRenderPolygonCommand()
UpdateCLUTIfNeeded(m_draw_mode.mode_reg.texture_mode, m_draw_mode.palette_reg); UpdateCLUTIfNeeded(m_draw_mode.mode_reg.texture_mode, m_draw_mode.palette_reg);
} }
m_counters.num_vertices += num_vertices;
m_counters.num_primitives++;
m_render_command.bits = rc.bits; m_render_command.bits = rc.bits;
m_fifo.RemoveOne(); m_fifo.RemoveOne();
DispatchRenderCommand(); PrepareForDraw();
if (g_settings.gpu_pgxp_enable)
{
GPUBackendDrawPrecisePolygonCommand* RESTRICT cmd = GPUBackend::NewDrawPrecisePolygonCommand(num_vertices);
FillDrawCommand(cmd, rc);
cmd->num_vertices = Truncate16(num_vertices);
const u32 first_color = rc.color_for_first_vertex;
const bool shaded = rc.shading_enable;
const bool textured = rc.texture_enable;
bool valid_w = g_settings.gpu_pgxp_texture_correction;
for (u32 i = 0; i < num_vertices; i++)
{
GPUBackendDrawPrecisePolygonCommand::Vertex* RESTRICT vert = &cmd->vertices[i];
vert->color = (shaded && i > 0) ? (FifoPop() & UINT32_C(0x00FFFFFF)) : first_color;
const u64 maddr_and_pos = m_fifo.Pop();
const GPUVertexPosition vp{Truncate32(maddr_and_pos)};
vert->native_x = m_drawing_offset.x + vp.x;
vert->native_y = m_drawing_offset.y + vp.y;
vert->texcoord = textured ? Truncate16(FifoPop()) : 0;
valid_w &= CPU::PGXP::GetPreciseVertex(Truncate32(maddr_and_pos >> 32), vp.bits, vert->native_x, vert->native_y,
m_drawing_offset.x, m_drawing_offset.y, &vert->x, &vert->y, &vert->w);
}
cmd->valid_w = valid_w;
if (!valid_w)
{
if (g_settings.gpu_pgxp_disable_2d)
{
// NOTE: This reads uninitialized data, but it's okay, it doesn't get used.
for (u32 i = 0; i < num_vertices; i++)
{
GPUBackendDrawPrecisePolygonCommand::Vertex& v = cmd->vertices[i];
GSVector2::store<false>(&v.x, GSVector2(GSVector2i::load<false>(&v.native_x)));
v.w = 1.0f;
}
}
else
{
for (u32 i = 0; i < num_vertices; i++)
cmd->vertices[i].w = 1.0f;
}
}
// Cull polygons which are too large.
const GSVector2 v0f = GSVector2::load<false>(&cmd->vertices[0].x);
const GSVector2 v1f = GSVector2::load<false>(&cmd->vertices[1].x);
const GSVector2 v2f = GSVector2::load<false>(&cmd->vertices[2].x);
const GSVector2 min_pos_12 = v1f.min(v2f);
const GSVector2 max_pos_12 = v1f.max(v2f);
const GSVector4i draw_rect_012 = GSVector4i(GSVector4(min_pos_12.min(v0f)).upld(GSVector4(max_pos_12.max(v0f))))
.add32(GSVector4i::cxpr(0, 0, 1, 1));
const bool first_tri_culled =
(draw_rect_012.width() > MAX_PRIMITIVE_WIDTH || draw_rect_012.height() > MAX_PRIMITIVE_HEIGHT ||
!draw_rect_012.rintersects(m_clamped_drawing_area));
if (first_tri_culled)
{
// TODO: GPU events... somehow.
DEBUG_LOG("Culling off-screen/too-large polygon: {},{} {},{} {},{}", cmd->vertices[0].native_x,
cmd->vertices[0].native_y, cmd->vertices[1].native_x, cmd->vertices[1].native_y,
cmd->vertices[2].native_x, cmd->vertices[2].native_y);
if (!rc.quad_polygon)
{
EndCommand();
return true;
}
}
else
{
AddDrawTriangleTicks(GSVector2i::load<false>(&cmd->vertices[0].native_x),
GSVector2i::load<false>(&cmd->vertices[1].native_x),
GSVector2i::load<false>(&cmd->vertices[2].native_x), rc.shading_enable, rc.texture_enable,
rc.transparency_enable);
}
// quads
if (rc.quad_polygon)
{
const GSVector2 v3f = GSVector2::load<false>(&cmd->vertices[3].x);
const GSVector4i draw_rect_123 = GSVector4i(GSVector4(min_pos_12.min(v3f)).upld(GSVector4(max_pos_12.max(v3f))))
.add32(GSVector4i::cxpr(0, 0, 1, 1));
// Cull polygons which are too large.
const bool second_tri_culled =
(draw_rect_123.width() > MAX_PRIMITIVE_WIDTH || draw_rect_123.height() > MAX_PRIMITIVE_HEIGHT ||
!draw_rect_123.rintersects(m_clamped_drawing_area));
if (second_tri_culled)
{
DEBUG_LOG("Culling off-screen/too-large polygon (quad second half): {},{} {},{} {},{}",
cmd->vertices[2].native_x, cmd->vertices[2].native_y, cmd->vertices[1].native_x,
cmd->vertices[1].native_y, cmd->vertices[0].native_x, cmd->vertices[0].native_y);
if (first_tri_culled)
{
EndCommand();
return true;
}
// Remove second part of quad.
// NOTE: Culling this way results in subtle differences with UV clamping, since the fourth vertex is no
// longer considered in the range. This is mainly apparent when the UV gradient is zero. Seems like it
// generally looks better this way, so I'm keeping it.
cmd->size = GPUThreadCommand::AlignCommandSize(sizeof(GPUBackendDrawPrecisePolygonCommand) +
3 * sizeof(GPUBackendDrawPrecisePolygonCommand::Vertex));
cmd->num_vertices = 3;
}
else
{
AddDrawTriangleTicks(GSVector2i::load<false>(&cmd->vertices[2].native_x),
GSVector2i::load<false>(&cmd->vertices[1].native_x),
GSVector2i::load<false>(&cmd->vertices[3].native_x), rc.shading_enable, rc.texture_enable,
rc.transparency_enable);
// If first part was culled, move the second part to the first.
if (first_tri_culled)
{
std::memcpy(&cmd->vertices[0], &cmd->vertices[2], sizeof(GPUBackendDrawPrecisePolygonCommand::Vertex));
std::memcpy(&cmd->vertices[2], &cmd->vertices[3], sizeof(GPUBackendDrawPrecisePolygonCommand::Vertex));
cmd->size = GPUThreadCommand::AlignCommandSize(sizeof(GPUBackendDrawPrecisePolygonCommand) +
3 * sizeof(GPUBackendDrawPrecisePolygonCommand::Vertex));
cmd->num_vertices = 3;
}
}
}
GPUBackend::PushCommand(cmd);
}
else
{
GPUBackendDrawPolygonCommand* RESTRICT cmd = GPUBackend::NewDrawPolygonCommand(num_vertices);
FillDrawCommand(cmd, rc);
cmd->num_vertices = Truncate16(num_vertices);
const u32 first_color = rc.color_for_first_vertex;
const bool shaded = rc.shading_enable;
const bool textured = rc.texture_enable;
for (u32 i = 0; i < num_vertices; i++)
{
GPUBackendDrawPolygonCommand::Vertex* RESTRICT vert = &cmd->vertices[i];
vert->color = (shaded && i > 0) ? (FifoPop() & UINT32_C(0x00FFFFFF)) : first_color;
const u64 maddr_and_pos = m_fifo.Pop();
const GPUVertexPosition vp{Truncate32(maddr_and_pos)};
vert->x = m_drawing_offset.x + vp.x;
vert->y = m_drawing_offset.y + vp.y;
vert->texcoord = textured ? Truncate16(FifoPop()) : 0;
}
// Cull polygons which are too large.
const GSVector2i v0 = GSVector2i::load<false>(&cmd->vertices[0].x);
const GSVector2i v1 = GSVector2i::load<false>(&cmd->vertices[1].x);
const GSVector2i v2 = GSVector2i::load<false>(&cmd->vertices[2].x);
const GSVector2i min_pos_12 = v1.min_s32(v2);
const GSVector2i max_pos_12 = v1.max_s32(v2);
const GSVector4i draw_rect_012 =
GSVector4i::xyxy(min_pos_12.min_s32(v0), max_pos_12.max_s32(v0)).add32(GSVector4i::cxpr(0, 0, 1, 1));
const bool first_tri_culled =
(draw_rect_012.width() > MAX_PRIMITIVE_WIDTH || draw_rect_012.height() > MAX_PRIMITIVE_HEIGHT ||
!draw_rect_012.rintersects(m_clamped_drawing_area));
if (first_tri_culled)
{
DEBUG_LOG("Culling off-screen/too-large polygon: {},{} {},{} {},{}", cmd->vertices[0].x, cmd->vertices[0].y,
cmd->vertices[1].x, cmd->vertices[1].y, cmd->vertices[2].x, cmd->vertices[2].y);
if (!rc.quad_polygon)
{
EndCommand();
return true;
}
}
else
{
AddDrawTriangleTicks(v0, v1, v2, rc.shading_enable, rc.texture_enable, rc.transparency_enable);
}
// quads
if (rc.quad_polygon)
{
const GSVector2i v3 = GSVector2i::load<false>(&cmd->vertices[3].x);
const GSVector4i draw_rect_123 = GSVector4i(min_pos_12.min_s32(v3))
.upl64(GSVector4i(max_pos_12.max_s32(v3)))
.add32(GSVector4i::cxpr(0, 0, 1, 1));
// Cull polygons which are too large.
const bool second_tri_culled =
(draw_rect_123.width() > MAX_PRIMITIVE_WIDTH || draw_rect_123.height() > MAX_PRIMITIVE_HEIGHT ||
!draw_rect_123.rintersects(m_clamped_drawing_area));
if (second_tri_culled)
{
DEBUG_LOG("Culling too-large polygon (quad second half): {},{} {},{} {},{}", cmd->vertices[2].x,
cmd->vertices[2].y, cmd->vertices[1].x, cmd->vertices[1].y, cmd->vertices[0].x, cmd->vertices[0].y);
if (first_tri_culled)
{
EndCommand();
return true;
}
// Remove second part of quad.
cmd->size = GPUThreadCommand::AlignCommandSize(sizeof(GPUBackendDrawPolygonCommand) +
3 * sizeof(GPUBackendDrawPolygonCommand::Vertex));
cmd->num_vertices = 3;
}
else
{
AddDrawTriangleTicks(v2, v1, v3, rc.shading_enable, rc.texture_enable, rc.transparency_enable);
// If first part was culled, move the second part to the first.
if (first_tri_culled)
{
std::memcpy(&cmd->vertices[0], &cmd->vertices[2], sizeof(GPUBackendDrawPolygonCommand::Vertex));
std::memcpy(&cmd->vertices[2], &cmd->vertices[3], sizeof(GPUBackendDrawPolygonCommand::Vertex));
cmd->size = GPUThreadCommand::AlignCommandSize(sizeof(GPUBackendDrawPolygonCommand) +
3 * sizeof(GPUBackendDrawPolygonCommand::Vertex));
cmd->num_vertices = 3;
}
}
}
GPUBackend::PushCommand(cmd);
}
EndCommand(); EndCommand();
return true; return true;
} }
@ -389,12 +634,65 @@ bool GPU::HandleRenderRectangleCommand()
rc.transparency_enable ? "semi-transparent" : "opaque", rc.texture_enable ? "textured" : "non-textured", rc.transparency_enable ? "semi-transparent" : "opaque", rc.texture_enable ? "textured" : "non-textured",
rc.shading_enable ? "shaded" : "monochrome", total_words, setup_ticks); rc.shading_enable ? "shaded" : "monochrome", total_words, setup_ticks);
m_counters.num_vertices++;
m_counters.num_primitives++;
m_render_command.bits = rc.bits; m_render_command.bits = rc.bits;
m_fifo.RemoveOne(); m_fifo.RemoveOne();
DispatchRenderCommand(); PrepareForDraw();
GPUBackendDrawRectangleCommand* cmd = GPUBackend::NewDrawRectangleCommand();
FillDrawCommand(cmd, rc);
cmd->color = rc.color_for_first_vertex;
const GPUVertexPosition vp{FifoPop()};
cmd->x = TruncateGPUVertexPosition(m_drawing_offset.x + vp.x);
cmd->y = TruncateGPUVertexPosition(m_drawing_offset.y + vp.y);
if (rc.texture_enable)
{
const u32 texcoord_and_palette = FifoPop();
cmd->palette.bits = Truncate16(texcoord_and_palette >> 16);
cmd->texcoord = Truncate16(texcoord_and_palette);
}
else
{
cmd->palette.bits = 0;
cmd->texcoord = 0;
}
switch (rc.rectangle_size)
{
case GPUDrawRectangleSize::R1x1:
cmd->width = 1;
cmd->height = 1;
break;
case GPUDrawRectangleSize::R8x8:
cmd->width = 8;
cmd->height = 8;
break;
case GPUDrawRectangleSize::R16x16:
cmd->width = 16;
cmd->height = 16;
break;
default:
{
const u32 width_and_height = FifoPop();
cmd->width = static_cast<u16>(width_and_height & VRAM_WIDTH_MASK);
cmd->height = static_cast<u16>((width_and_height >> 16) & VRAM_HEIGHT_MASK);
}
break;
}
const GSVector4i rect = GSVector4i(cmd->x, cmd->y, cmd->x + cmd->width, cmd->y + cmd->height);
const GSVector4i clamped_rect = m_clamped_drawing_area.rintersect(rect);
if (clamped_rect.rempty()) [[unlikely]]
{
DEBUG_LOG("Culling off-screen rectangle {}", rect);
EndCommand();
return true;
}
AddDrawRectangleTicks(clamped_rect, rc.texture_enable, rc.transparency_enable);
GPUBackend::PushCommand(cmd);
EndCommand(); EndCommand();
return true; return true;
} }
@ -411,12 +709,103 @@ bool GPU::HandleRenderLineCommand()
TRACE_LOG("Render {} {} line ({} total words)", rc.transparency_enable ? "semi-transparent" : "opaque", TRACE_LOG("Render {} {} line ({} total words)", rc.transparency_enable ? "semi-transparent" : "opaque",
rc.shading_enable ? "shaded" : "monochrome", total_words); rc.shading_enable ? "shaded" : "monochrome", total_words);
m_counters.num_vertices += 2;
m_counters.num_primitives++;
m_render_command.bits = rc.bits; m_render_command.bits = rc.bits;
m_fifo.RemoveOne(); m_fifo.RemoveOne();
DispatchRenderCommand(); PrepareForDraw();
if (g_settings.gpu_pgxp_enable)
{
GPUBackendDrawPreciseLineCommand* RESTRICT cmd = GPUBackend::NewDrawPreciseLineCommand(2);
FillDrawCommand(cmd, rc);
cmd->palette.bits = 0;
bool valid_w = g_settings.gpu_pgxp_texture_correction;
for (u32 i = 0; i < 2; i++)
{
const u32 color = ((i != 0 && rc.shading_enable) ? FifoPop() : rc.bits) & UINT32_C(0x00FFFFFF);
const u64 maddr_and_pos = m_fifo.Pop();
const GPUVertexPosition vp{Truncate32(maddr_and_pos)};
GPUBackendDrawPreciseLineCommand::Vertex* RESTRICT vert = &cmd->vertices[i];
vert->native_x = m_drawing_offset.x + vp.x;
vert->native_y = m_drawing_offset.y + vp.y;
vert->color = color;
valid_w &= CPU::PGXP::GetPreciseVertex(Truncate32(maddr_and_pos >> 32), vp.bits, vert->native_x, vert->native_y,
m_drawing_offset.x, m_drawing_offset.y, &vert->x, &vert->y, &vert->w);
}
if (!(cmd->valid_w = valid_w))
{
for (u32 i = 0; i < 2; i++)
cmd->vertices[i].w = 1.0f;
}
const GSVector2 v0f = GSVector2::load<false>(&cmd->vertices[0].x);
const GSVector2 v1f = GSVector2::load<false>(&cmd->vertices[1].x);
const GSVector4i rect =
GSVector4i(GSVector4(v0f.min(v1f)).upld(GSVector4(v0f.max(v1f)))).add32(GSVector4i::cxpr(0, 0, 1, 1));
const GSVector4i clamped_rect = rect.rintersect(m_clamped_drawing_area);
if (rect.width() > MAX_PRIMITIVE_WIDTH || rect.height() > MAX_PRIMITIVE_HEIGHT || clamped_rect.rempty())
{
DEBUG_LOG("Culling too-large/off-screen line: {},{} - {},{}", cmd->vertices[0].y, cmd->vertices[0].y,
cmd->vertices[1].x, cmd->vertices[1].y);
EndCommand();
return true;
}
AddDrawLineTicks(clamped_rect, rc.shading_enable);
GPUBackend::PushCommand(cmd);
}
else
{
GPUBackendDrawLineCommand* RESTRICT cmd = GPUBackend::NewDrawLineCommand(2);
FillDrawCommand(cmd, rc);
cmd->palette.bits = 0;
if (rc.shading_enable)
{
cmd->vertices[0].color = rc.color_for_first_vertex;
const GPUVertexPosition start_pos{FifoPop()};
cmd->vertices[0].x = m_drawing_offset.x + start_pos.x;
cmd->vertices[0].y = m_drawing_offset.y + start_pos.y;
cmd->vertices[1].color = FifoPop() & UINT32_C(0x00FFFFFF);
const GPUVertexPosition end_pos{FifoPop()};
cmd->vertices[1].x = m_drawing_offset.x + end_pos.x;
cmd->vertices[1].y = m_drawing_offset.y + end_pos.y;
}
else
{
cmd->vertices[0].color = rc.color_for_first_vertex;
cmd->vertices[1].color = rc.color_for_first_vertex;
const GPUVertexPosition start_pos{FifoPop()};
cmd->vertices[0].x = m_drawing_offset.x + start_pos.x;
cmd->vertices[0].y = m_drawing_offset.y + start_pos.y;
const GPUVertexPosition end_pos{FifoPop()};
cmd->vertices[1].x = m_drawing_offset.x + end_pos.x;
cmd->vertices[1].y = m_drawing_offset.y + end_pos.y;
}
const GSVector2i v0 = GSVector2i::load<false>(&cmd->vertices[0].x);
const GSVector2i v1 = GSVector2i::load<false>(&cmd->vertices[1].x);
const GSVector4i rect = GSVector4i::xyxy(v0.min_s32(v1), v0.max_s32(v1)).add32(GSVector4i::cxpr(0, 0, 1, 1));
const GSVector4i clamped_rect = rect.rintersect(m_clamped_drawing_area);
if (rect.width() > MAX_PRIMITIVE_WIDTH || rect.height() > MAX_PRIMITIVE_HEIGHT || clamped_rect.rempty())
{
DEBUG_LOG("Culling too-large/off-screen line: {},{} - {},{}", cmd->vertices[0].y, cmd->vertices[0].y,
cmd->vertices[1].x, cmd->vertices[1].y);
EndCommand();
return true;
}
AddDrawLineTicks(clamped_rect, rc.shading_enable);
GPUBackend::PushCommand(cmd);
}
EndCommand(); EndCommand();
return true; return true;
} }
@ -443,9 +832,9 @@ bool GPU::HandleRenderPolyLineCommand()
const u32 words_to_pop = min_words - 1; const u32 words_to_pop = min_words - 1;
// m_blit_buffer.resize(words_to_pop); // m_blit_buffer.resize(words_to_pop);
// FifoPopRange(m_blit_buffer.data(), words_to_pop); // FifoPopRange(m_blit_buffer.data(), words_to_pop);
m_blit_buffer.reserve(words_to_pop); m_polyline_buffer.reserve(words_to_pop);
for (u32 i = 0; i < words_to_pop; i++) for (u32 i = 0; i < words_to_pop; i++)
m_blit_buffer.push_back(Truncate32(FifoPop())); m_polyline_buffer.push_back(m_fifo.Pop());
// polyline goes via a different path through the blit buffer // polyline goes via a different path through the blit buffer
m_blitter_state = BlitterState::DrawingPolyLine; m_blitter_state = BlitterState::DrawingPolyLine;
@ -453,6 +842,127 @@ bool GPU::HandleRenderPolyLineCommand()
return true; return true;
} }
void GPU::FinishPolyline()
{
PrepareForDraw();
const u32 num_vertices = GetPolyLineVertexCount();
DebugAssert(num_vertices >= 2);
if (g_settings.gpu_pgxp_enable)
{
GPUBackendDrawPreciseLineCommand* RESTRICT cmd = GPUBackend::NewDrawPreciseLineCommand((num_vertices - 1) * 2);
FillDrawCommand(cmd, m_render_command);
cmd->palette.bits = 0;
u32 buffer_pos = 0;
u32 out_vertex_count = 0;
const bool shaded = m_render_command.shading_enable;
bool valid_w = g_settings.gpu_pgxp_texture_correction;
GPUBackendDrawPreciseLineCommand::Vertex start, end;
const auto read_vertex = [this, &buffer_pos, &valid_w](GPUBackendDrawPreciseLineCommand::Vertex& RESTRICT dest,
u32 color) {
const u64 maddr_and_pos = m_polyline_buffer[buffer_pos++];
const GPUVertexPosition vp{Truncate32(maddr_and_pos)};
dest.native_x = m_drawing_offset.x + vp.x;
dest.native_y = m_drawing_offset.y + vp.y;
dest.color = color;
valid_w &= CPU::PGXP::GetPreciseVertex(Truncate32(maddr_and_pos >> 32), vp.bits, dest.native_x, dest.native_y,
m_drawing_offset.x, m_drawing_offset.y, &dest.x, &dest.y, &dest.w);
};
read_vertex(start, m_render_command.color_for_first_vertex);
for (u32 i = 1; i < num_vertices; i++)
{
const u32 color =
(shaded ? Truncate32(m_polyline_buffer[buffer_pos++]) : m_render_command.bits) & UINT32_C(0x00FFFFFF);
read_vertex(end, color);
const GSVector2 start_pos = GSVector2::load<false>(&start.x);
const GSVector2 end_pos = GSVector2::load<false>(&end.x);
const GSVector4i rect =
GSVector4i(GSVector4::xyxy(start_pos.min(end_pos), start_pos.max(end_pos))).add32(GSVector4i::cxpr(0, 0, 1, 1));
const GSVector4i clamped_rect = rect.rintersect(m_clamped_drawing_area);
if (rect.width() > MAX_PRIMITIVE_WIDTH || rect.height() > MAX_PRIMITIVE_HEIGHT || clamped_rect.rempty())
{
DEBUG_LOG("Culling too-large/off-screen line: {},{} - {},{}", start_pos.x, start_pos.y, end_pos.x, end_pos.y);
}
else
{
AddDrawLineTicks(clamped_rect, m_render_command.shading_enable);
cmd->vertices[out_vertex_count++] = start;
cmd->vertices[out_vertex_count++] = end;
}
start = end;
}
if (out_vertex_count > 0)
{
DebugAssert(out_vertex_count <= cmd->num_vertices);
cmd->num_vertices = Truncate16(out_vertex_count);
GPUBackend::PushCommand(cmd);
}
}
else
{
GPUBackendDrawLineCommand* RESTRICT cmd = GPUBackend::NewDrawLineCommand((num_vertices - 1) * 2);
FillDrawCommand(cmd, m_render_command);
cmd->palette.bits = 0;
u32 buffer_pos = 0;
const GPUVertexPosition start_vp{Truncate32(m_polyline_buffer[buffer_pos++])};
const GSVector2i draw_offset = GSVector2i::load<false>(&m_drawing_offset.x);
GSVector2i start_pos = GSVector2i(start_vp.x, start_vp.y).add32(draw_offset);
u32 start_color = m_render_command.color_for_first_vertex;
const bool shaded = m_render_command.shading_enable;
u32 out_vertex_count = 0;
for (u32 i = 1; i < num_vertices; i++)
{
const u32 end_color = shaded ? (Truncate32(m_polyline_buffer[buffer_pos++] & UINT32_C(0x00FFFFFF))) :
m_render_command.color_for_first_vertex;
const GPUVertexPosition vp{Truncate32(m_polyline_buffer[buffer_pos++])};
const GSVector2i end_pos = GSVector2i(vp.x, vp.y).add32(draw_offset);
const GSVector4i rect =
GSVector4i::xyxy(start_pos.min_s32(end_pos), start_pos.max_s32(end_pos)).add32(GSVector4i::cxpr(0, 0, 1, 1));
const GSVector4i clamped_rect = rect.rintersect(m_clamped_drawing_area);
if (rect.width() > MAX_PRIMITIVE_WIDTH || rect.height() > MAX_PRIMITIVE_HEIGHT || clamped_rect.rempty())
{
DEBUG_LOG("Culling too-large/off-screen line: {},{} - {},{}", start_pos.x, start_pos.y, end_pos.x, end_pos.y);
}
else
{
AddDrawLineTicks(clamped_rect, m_render_command.shading_enable);
GPUBackendDrawLineCommand::Vertex* out_vertex = &cmd->vertices[out_vertex_count];
out_vertex_count += 2;
GSVector2i::store<false>(&out_vertex[0].x, start_pos);
out_vertex[0].color = start_color;
GSVector2i::store<false>(&out_vertex[1].x, end_pos);
out_vertex[1].color = end_color;
}
start_pos = end_pos;
start_color = end_color;
}
if (out_vertex_count > 0)
{
DebugAssert(out_vertex_count <= cmd->num_vertices);
cmd->num_vertices = Truncate16(out_vertex_count);
GPUBackend::PushCommand(cmd);
}
}
}
bool GPU::HandleFillRectangleCommand() bool GPU::HandleFillRectangleCommand()
{ {
CHECK_COMMAND_SIZE(3); CHECK_COMMAND_SIZE(3);
@ -460,8 +970,6 @@ bool GPU::HandleFillRectangleCommand()
if (IsInterlacedRenderingEnabled() && IsCRTCScanlinePending()) if (IsInterlacedRenderingEnabled() && IsCRTCScanlinePending())
SynchronizeCRTC(); SynchronizeCRTC();
FlushRender();
const u32 color = FifoPop() & 0x00FFFFFF; const u32 color = FifoPop() & 0x00FFFFFF;
const u32 dst_x = FifoPeek() & 0x3F0; const u32 dst_x = FifoPeek() & 0x3F0;
const u32 dst_y = (FifoPop() >> 16) & VRAM_HEIGHT_MASK; const u32 dst_y = (FifoPop() >> 16) & VRAM_HEIGHT_MASK;
@ -471,9 +979,18 @@ bool GPU::HandleFillRectangleCommand()
DEBUG_LOG("Fill VRAM rectangle offset=({},{}), size=({},{})", dst_x, dst_y, width, height); DEBUG_LOG("Fill VRAM rectangle offset=({},{}), size=({},{})", dst_x, dst_y, width, height);
if (width > 0 && height > 0) if (width > 0 && height > 0)
FillVRAM(dst_x, dst_y, width, height, color); {
GPUBackendFillVRAMCommand* cmd = GPUBackend::NewFillVRAMCommand();
cmd->x = static_cast<u16>(dst_x);
cmd->y = static_cast<u16>(dst_y);
cmd->width = static_cast<u16>(width);
cmd->height = static_cast<u16>(height);
cmd->color = color;
cmd->interlaced_rendering = IsInterlacedRenderingEnabled();
cmd->active_line_lsb = m_crtc_state.active_line_lsb;
GPUBackend::PushCommand(cmd);
}
m_counters.num_writes++;
AddCommandTicks(46 + ((width / 8) + 9) * height); AddCommandTicks(46 + ((width / 8) + 9) * height);
EndCommand(); EndCommand();
return true; return true;
@ -523,11 +1040,9 @@ void GPU::FinishVRAMWrite()
if (IsInterlacedRenderingEnabled() && IsCRTCScanlinePending()) if (IsInterlacedRenderingEnabled() && IsCRTCScanlinePending())
SynchronizeCRTC(); SynchronizeCRTC();
FlushRender();
if (m_blit_remaining_words == 0) if (m_blit_remaining_words == 0)
{ {
if (g_settings.debugging.dump_cpu_to_vram_copies) if (g_settings.gpu_dump_cpu_to_vram_copies)
{ {
DumpVRAMToFile(TinyString::from_format("cpu_to_vram_copy_{}.png", s_cpu_to_vram_dump_id++), m_vram_transfer.width, DumpVRAMToFile(TinyString::from_format("cpu_to_vram_copy_{}.png", s_cpu_to_vram_dump_id++), m_vram_transfer.width,
m_vram_transfer.height, sizeof(u16) * m_vram_transfer.width, m_blit_buffer.data(), true); m_vram_transfer.height, sizeof(u16) * m_vram_transfer.width, m_blit_buffer.data(), true);
@ -557,18 +1072,18 @@ void GPU::FinishVRAMWrite()
const u8* blit_ptr = reinterpret_cast<const u8*>(m_blit_buffer.data()); const u8* blit_ptr = reinterpret_cast<const u8*>(m_blit_buffer.data());
if (transferred_full_rows > 0) if (transferred_full_rows > 0)
{ {
UpdateVRAM(m_vram_transfer.x, m_vram_transfer.y, m_vram_transfer.width, transferred_full_rows, blit_ptr, UpdateVRAM(m_vram_transfer.x, m_vram_transfer.y, m_vram_transfer.width, static_cast<u16>(transferred_full_rows),
m_GPUSTAT.set_mask_while_drawing, m_GPUSTAT.check_mask_before_draw); blit_ptr, m_GPUSTAT.set_mask_while_drawing, m_GPUSTAT.check_mask_before_draw);
blit_ptr += (ZeroExtend32(m_vram_transfer.width) * transferred_full_rows) * sizeof(u16); blit_ptr += (ZeroExtend32(m_vram_transfer.width) * transferred_full_rows) * sizeof(u16);
} }
if (transferred_width_last_row > 0) if (transferred_width_last_row > 0)
{ {
UpdateVRAM(m_vram_transfer.x, m_vram_transfer.y + transferred_full_rows, transferred_width_last_row, 1, blit_ptr, UpdateVRAM(m_vram_transfer.x, static_cast<u16>(m_vram_transfer.y + transferred_full_rows),
m_GPUSTAT.set_mask_while_drawing, m_GPUSTAT.check_mask_before_draw); static_cast<u16>(transferred_width_last_row), 1, blit_ptr, m_GPUSTAT.set_mask_while_drawing,
m_GPUSTAT.check_mask_before_draw);
} }
} }
m_counters.num_writes++;
m_blit_buffer.clear(); m_blit_buffer.clear();
m_vram_transfer = {}; m_vram_transfer = {};
m_blitter_state = BlitterState::Idle; m_blitter_state = BlitterState::Idle;
@ -588,13 +1103,10 @@ bool GPU::HandleCopyRectangleVRAMToCPUCommand()
m_vram_transfer.width, m_vram_transfer.height); m_vram_transfer.width, m_vram_transfer.height);
DebugAssert(m_vram_transfer.col == 0 && m_vram_transfer.row == 0); DebugAssert(m_vram_transfer.col == 0 && m_vram_transfer.row == 0);
// all rendering should be done first...
FlushRender();
// ensure VRAM shadow is up to date // ensure VRAM shadow is up to date
ReadVRAM(m_vram_transfer.x, m_vram_transfer.y, m_vram_transfer.width, m_vram_transfer.height); ReadVRAM(m_vram_transfer.x, m_vram_transfer.y, m_vram_transfer.width, m_vram_transfer.height);
if (g_settings.debugging.dump_vram_to_cpu_copies) if (g_settings.gpu_dump_vram_to_cpu_copies)
{ {
DumpVRAMToFile(TinyString::from_format("vram_to_cpu_copy_{}.png", s_vram_to_cpu_dump_id++), m_vram_transfer.width, DumpVRAMToFile(TinyString::from_format("vram_to_cpu_copy_{}.png", s_vram_to_cpu_dump_id++), m_vram_transfer.width,
m_vram_transfer.height, sizeof(u16) * VRAM_WIDTH, m_vram_transfer.height, sizeof(u16) * VRAM_WIDTH,
@ -602,7 +1114,6 @@ bool GPU::HandleCopyRectangleVRAMToCPUCommand()
} }
// switch to pixel-by-pixel read state // switch to pixel-by-pixel read state
m_counters.num_reads++;
m_blitter_state = BlitterState::ReadingVRAM; m_blitter_state = BlitterState::ReadingVRAM;
m_command_total_words = 0; m_command_total_words = 0;
@ -633,10 +1144,16 @@ bool GPU::HandleCopyRectangleVRAMToVRAMCommand()
width == 0 || height == 0 || (src_x == dst_x && src_y == dst_y && !m_GPUSTAT.set_mask_while_drawing); width == 0 || height == 0 || (src_x == dst_x && src_y == dst_y && !m_GPUSTAT.set_mask_while_drawing);
if (!skip_copy) if (!skip_copy)
{ {
m_counters.num_copies++; GPUBackendCopyVRAMCommand* cmd = GPUBackend::NewCopyVRAMCommand();
cmd->src_x = static_cast<u16>(src_x);
FlushRender(); cmd->src_y = static_cast<u16>(src_y);
CopyVRAM(src_x, src_y, dst_x, dst_y, width, height); cmd->dst_x = static_cast<u16>(dst_x);
cmd->dst_y = static_cast<u16>(dst_y);
cmd->width = static_cast<u16>(width);
cmd->height = static_cast<u16>(height);
cmd->check_mask_before_draw = m_GPUSTAT.check_mask_before_draw;
cmd->set_mask_while_drawing = m_GPUSTAT.set_mask_while_drawing;
GPUBackend::PushCommand(cmd);
} }
AddCommandTicks(width * height * 2); AddCommandTicks(width * height * 2);

View File

@ -76,7 +76,7 @@ std::unique_ptr<GPUDump::Recorder> GPUDump::Recorder::Create(std::string path, s
ret = std::unique_ptr<Recorder>(new Recorder(std::move(fp), num_frames, std::move(path))); ret = std::unique_ptr<Recorder>(new Recorder(std::move(fp), num_frames, std::move(path)));
ret->WriteHeaders(serial); ret->WriteHeaders(serial);
g_gpu->WriteCurrentVideoModeToDump(ret.get()); g_gpu.WriteCurrentVideoModeToDump(ret.get());
ret->WriteCurrentVRAM(); ret->WriteCurrentVRAM();
// Write start of stream. // Write start of stream.
@ -285,7 +285,7 @@ void GPUDump::Recorder::WriteHeaders(std::string_view serial)
// Write textual video mode. // Write textual video mode.
BeginPacket(PacketType::TextualVideoFormat); BeginPacket(PacketType::TextualVideoFormat);
WriteString(g_gpu->IsInPALMode() ? "PAL" : "NTSC"); WriteString(g_gpu.IsInPALMode() ? "PAL" : "NTSC");
EndPacket(); EndPacket();
// Write DuckStation version. // Write DuckStation version.
@ -520,7 +520,7 @@ void GPUDump::Player::ProcessPacket(const PacketRef& pkt)
if (pkt.type <= PacketType::VSyncEvent) if (pkt.type <= PacketType::VSyncEvent)
{ {
// gp0/gp1/vsync => direct to gpu // gp0/gp1/vsync => direct to gpu
g_gpu->ProcessGPUDumpPacket(pkt.type, pkt.data); g_gpu.ProcessGPUDumpPacket(pkt.type, pkt.data);
return; return;
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,7 @@
#pragma once #pragma once
#include "gpu.h" #include "gpu_backend.h"
#include "gpu_hw_texture_cache.h" #include "gpu_hw_texture_cache.h"
#include "util/gpu_device.h" #include "util/gpu_device.h"
@ -21,7 +21,9 @@ class GPU_SW_Backend;
struct GPUBackendCommand; struct GPUBackendCommand;
struct GPUBackendDrawCommand; struct GPUBackendDrawCommand;
class GPU_HW final : public GPU // TODO: Move to cpp
// TODO: Rename to GPUHWBackend, preserved to avoid conflicts.
class GPU_HW final : public GPUBackend
{ {
public: public:
enum class BatchRenderMode : u8 enum class BatchRenderMode : u8
@ -63,21 +65,42 @@ public:
GPU_HW(); GPU_HW();
~GPU_HW() override; ~GPU_HW() override;
const Threading::Thread* GetSWThread() const override; bool Initialize(bool upload_vram, Error* error) override;
bool IsHardwareRenderer() const override;
bool Initialize(Error* error) override; u32 GetResolutionScale() const override;
void Reset(bool clear_vram) override;
bool DoState(StateWrapper& sw, GPUTexture** host_texture, bool update_display) override;
void RestoreDeviceContext() override; void RestoreDeviceContext() override;
void UpdateSettings(const Settings& old_settings) override; protected:
void UpdateSettings(const GPUSettings& old_settings) override;
u32 GetResolutionScale() const override;
void UpdateResolutionScale() override; void UpdateResolutionScale() override;
void UpdateDisplay() override; void FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color, bool interlaced_rendering, u8 active_line_lsb) override;
void ReadVRAM(u32 x, u32 y, u32 width, u32 height) override;
void UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data, bool set_mask, bool check_mask) override;
void CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height, bool set_mask,
bool check_mask) override;
void ClearCache() override;
void UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit) override;
void OnBufferSwapped() override;
void DrawPolygon(const GPUBackendDrawPolygonCommand* cmd) override;
void DrawPrecisePolygon(const GPUBackendDrawPrecisePolygonCommand* cmd) override;
void DrawSprite(const GPUBackendDrawRectangleCommand* cmd) override;
void DrawLine(const GPUBackendDrawLineCommand* cmd) override;
void DrawPreciseLine(const GPUBackendDrawPreciseLineCommand* cmd) override;
void FlushRender() override;
void DrawingAreaChanged() override;
void ClearVRAM() override;
void LoadState(const GPUBackendLoadStateCommand* cmd) override;
bool AllocateMemorySaveState(System::MemorySaveState& mss, Error* error) override;
void DoMemoryState(StateWrapper& sw, System::MemorySaveState& mss) override;
void UpdateDisplay(const GPUBackendUpdateDisplayCommand* cmd) override;
private: private:
enum : u32 enum : u32
@ -86,6 +109,7 @@ private:
MAX_VERTICES_FOR_RECTANGLE = 6 * (((MAX_PRIMITIVE_WIDTH + (TEXTURE_PAGE_WIDTH - 1)) / TEXTURE_PAGE_WIDTH) + 1u) * MAX_VERTICES_FOR_RECTANGLE = 6 * (((MAX_PRIMITIVE_WIDTH + (TEXTURE_PAGE_WIDTH - 1)) / TEXTURE_PAGE_WIDTH) + 1u) *
(((MAX_PRIMITIVE_HEIGHT + (TEXTURE_PAGE_HEIGHT - 1)) / TEXTURE_PAGE_HEIGHT) + 1u), (((MAX_PRIMITIVE_HEIGHT + (TEXTURE_PAGE_HEIGHT - 1)) / TEXTURE_PAGE_HEIGHT) + 1u),
NUM_TEXTURE_MODES = static_cast<u32>(BatchTextureMode::MaxCount), NUM_TEXTURE_MODES = static_cast<u32>(BatchTextureMode::MaxCount),
INVALID_DRAW_MODE_BITS = 0xFFFFFFFFu,
}; };
enum : u8 enum : u8
{ {
@ -164,8 +188,6 @@ private:
bool CompileResolutionDependentPipelines(Error* error); bool CompileResolutionDependentPipelines(Error* error);
bool CompileDownsamplePipelines(Error* error); bool CompileDownsamplePipelines(Error* error);
void LoadVertices();
void PrintSettingsToLog(); void PrintSettingsToLog();
void CheckSettings(); void CheckSettings();
@ -184,8 +206,10 @@ private:
u32 CalculateResolutionScale() const; u32 CalculateResolutionScale() const;
GPUDownsampleMode GetDownsampleMode(u32 resolution_scale) const; GPUDownsampleMode GetDownsampleMode(u32 resolution_scale) const;
bool ShouldDrawWithSoftwareRenderer() const;
bool IsUsingMultisampling() const; bool IsUsingMultisampling() const;
bool IsUsingDownsampling() const; bool IsUsingDownsampling(const GPUBackendUpdateDisplayCommand* cmd) const;
void SetFullVRAMDirtyRectangle(); void SetFullVRAMDirtyRectangle();
void ClearVRAMDirtyRectangle(); void ClearVRAMDirtyRectangle();
@ -195,12 +219,15 @@ private:
void AddUnclampedDrawnRectangle(const GSVector4i rect); void AddUnclampedDrawnRectangle(const GSVector4i rect);
void SetTexPageChangedOnOverlap(const GSVector4i update_rect); void SetTexPageChangedOnOverlap(const GSVector4i update_rect);
void CheckForTexPageOverlap(GSVector4i uv_rect); void CheckForTexPageOverlap(const GPUBackendDrawCommand* cmd, GSVector4i uv_rect);
bool ShouldCheckForTexPageOverlap() const; bool ShouldCheckForTexPageOverlap() const;
bool IsFlushed() const; bool IsFlushed() const;
void EnsureVertexBufferSpace(u32 required_vertices, u32 required_indices); void EnsureVertexBufferSpace(u32 required_vertices, u32 required_indices);
void EnsureVertexBufferSpaceForCurrentCommand(); void EnsureVertexBufferSpaceForCommand(const GPUBackendDrawCommand* cmd);
void PrepareDraw(const GPUBackendDrawCommand* cmd);
void FinishPolygonDraw(const GPUBackendDrawCommand* cmd, std::array<BatchVertex, 4>& vertices, u32 num_vertices,
bool is_precise, bool is_3d);
void ResetBatchVertexDepth(); void ResetBatchVertexDepth();
/// Returns the value to be written to the depth buffer for the current operation for mask bit emulation. /// Returns the value to be written to the depth buffer for the current operation for mask bit emulation.
@ -212,20 +239,6 @@ private:
/// Returns true if the draw is going to use shader blending/framebuffer fetch. /// Returns true if the draw is going to use shader blending/framebuffer fetch.
bool NeedsShaderBlending(GPUTransparencyMode transparency, BatchTextureMode texture, bool check_mask) const; bool NeedsShaderBlending(GPUTransparencyMode transparency, BatchTextureMode texture, bool check_mask) const;
void FillBackendCommandParameters(GPUBackendCommand* cmd) const;
void FillDrawCommand(GPUBackendDrawCommand* cmd, GPURenderCommand rc) const;
void UpdateSoftwareRenderer(bool copy_vram_from_hw);
void FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color) override;
void ReadVRAM(u32 x, u32 y, u32 width, u32 height) override;
void UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data, bool set_mask, bool check_mask) override;
void CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height) override;
void DispatchRenderCommand() override;
void UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit) override;
void FlushRender() override;
void DrawRendererStats() override;
void OnBufferSwapped() override;
void UpdateVRAMOnGPU(u32 x, u32 y, u32 width, u32 height, const void* data, u32 data_pitch, bool set_mask, void UpdateVRAMOnGPU(u32 x, u32 y, u32 width, u32 height, const void* data, u32 data_pitch, bool set_mask,
bool check_mask, const GSVector4i bounds); bool check_mask, const GSVector4i bounds);
bool BlitVRAMReplacementTexture(GPUTexture* tex, u32 dst_x, u32 dst_y, u32 width, u32 height); bool BlitVRAMReplacementTexture(GPUTexture* tex, u32 dst_x, u32 dst_y, u32 width, u32 height);
@ -234,17 +247,17 @@ private:
void DrawLine(const GSVector4 bounds, u32 col0, u32 col1, float depth); void DrawLine(const GSVector4 bounds, u32 col0, u32 col1, float depth);
/// Handles quads with flipped texture coordinate directions. /// Handles quads with flipped texture coordinate directions.
void HandleFlippedQuadTextureCoordinates(BatchVertex* vertices); void HandleFlippedQuadTextureCoordinates(const GPUBackendDrawCommand* cmd, BatchVertex* vertices);
bool IsPossibleSpritePolygon(const BatchVertex* vertices) const; bool IsPossibleSpritePolygon(const BatchVertex* vertices) const;
bool ExpandLineTriangles(BatchVertex* vertices); bool ExpandLineTriangles(BatchVertex* vertices);
/// Computes polygon U/V boundaries, and for overlap with the current texture page. /// Computes polygon U/V boundaries, and for overlap with the current texture page.
void ComputePolygonUVLimits(BatchVertex* vertices, u32 num_vertices); void ComputePolygonUVLimits(const GPUBackendDrawCommand* cmd, BatchVertex* vertices, u32 num_vertices);
/// Sets the depth test flag for PGXP depth buffering. /// Sets the depth test flag for PGXP depth buffering.
void SetBatchDepthBuffer(bool enabled); void SetBatchDepthBuffer(const GPUBackendDrawCommand* cmd, bool enabled);
void CheckForDepthClear(const BatchVertex* vertices, u32 num_vertices); void CheckForDepthClear(const GPUBackendDrawCommand* cmd, const BatchVertex* vertices, u32 num_vertices);
void SetBatchSpriteMode(bool enabled); void SetBatchSpriteMode(const GPUBackendDrawCommand* cmd, bool enabled);
void UpdateDownsamplingLevels(); void UpdateDownsamplingLevels();
@ -262,8 +275,6 @@ private:
std::unique_ptr<GPUTextureBuffer> m_vram_upload_buffer; std::unique_ptr<GPUTextureBuffer> m_vram_upload_buffer;
std::unique_ptr<GPUTexture> m_vram_write_texture; std::unique_ptr<GPUTexture> m_vram_write_texture;
std::unique_ptr<GPU_SW_Backend> m_sw_renderer;
BatchVertex* m_batch_vertex_ptr = nullptr; BatchVertex* m_batch_vertex_ptr = nullptr;
u16* m_batch_index_ptr = nullptr; u16* m_batch_index_ptr = nullptr;
u32 m_batch_base_vertex = 0; u32 m_batch_base_vertex = 0;
@ -305,18 +316,32 @@ private:
u8 m_texpage_dirty = 0; u8 m_texpage_dirty = 0;
bool m_batch_ubo_dirty = true; bool m_batch_ubo_dirty = true;
bool m_drawing_area_changed = true;
BatchConfig m_batch; BatchConfig m_batch;
// Changed state // Changed state
BatchUBOData m_batch_ubo_data = {}; BatchUBOData m_batch_ubo_data = {};
// Bounding box of VRAM area that the GPU has drawn into. // Bounding box of VRAM area that the GPU has drawn into.
GSVector4i m_clamped_drawing_area = {};
GSVector4i m_vram_dirty_draw_rect = INVALID_RECT; GSVector4i m_vram_dirty_draw_rect = INVALID_RECT;
GSVector4i m_vram_dirty_write_rect = INVALID_RECT; // TODO: Don't use in TC mode, should be kept at zero. GSVector4i m_vram_dirty_write_rect = INVALID_RECT; // TODO: Don't use in TC mode, should be kept at zero.
GSVector4i m_current_uv_rect = INVALID_RECT; GSVector4i m_current_uv_rect = INVALID_RECT;
GSVector4i m_current_draw_rect = INVALID_RECT; GSVector4i m_current_draw_rect = INVALID_RECT;
alignas(8) s32 m_current_texture_page_offset[2] = {}; alignas(8) s32 m_current_texture_page_offset[2] = {};
union
{
struct
{
// NOTE: Only the texture-related bits should be used here, the others are not validated.
GPUDrawModeReg mode_reg;
GPUTexturePaletteReg palette_reg;
};
u32 bits = INVALID_DRAW_MODE_BITS;
} m_draw_mode = {};
std::unique_ptr<GPUPipeline> m_wireframe_pipeline; std::unique_ptr<GPUPipeline> m_wireframe_pipeline;
// [wrapped][interlaced] // [wrapped][interlaced]

View File

@ -5,11 +5,14 @@
#include "gpu_hw.h" #include "gpu_hw.h"
#include "gpu_hw_shadergen.h" #include "gpu_hw_shadergen.h"
#include "gpu_sw_rasterizer.h" #include "gpu_sw_rasterizer.h"
#include "gpu_thread.h"
#include "host.h" #include "host.h"
#include "imgui_overlays.h"
#include "settings.h" #include "settings.h"
#include "system.h" #include "system.h"
#include "util/gpu_device.h" #include "util/gpu_device.h"
#include "util/imgui_fullscreen.h"
#include "util/imgui_manager.h" #include "util/imgui_manager.h"
#include "util/state_wrapper.h" #include "util/state_wrapper.h"
@ -50,6 +53,9 @@ static constexpr const GSVector4i& INVALID_RECT = GPU_HW::INVALID_RECT;
static constexpr const GPUTexture::Format REPLACEMENT_TEXTURE_FORMAT = GPUTexture::Format::RGBA8; static constexpr const GPUTexture::Format REPLACEMENT_TEXTURE_FORMAT = GPUTexture::Format::RGBA8;
static constexpr const char LOCAL_CONFIG_FILENAME[] = "config.yaml"; static constexpr const char LOCAL_CONFIG_FILENAME[] = "config.yaml";
static constexpr u32 STATE_PALETTE_RECORD_SIZE =
sizeof(GSVector4i) + sizeof(SourceKey) + sizeof(PaletteRecordFlags) + sizeof(HashType) + sizeof(u16) * MAX_CLUT_SIZE;
// Has to be public because it's referenced in Source. // Has to be public because it's referenced in Source.
struct HashCacheEntry struct HashCacheEntry
{ {
@ -518,6 +524,7 @@ struct GPUTextureCacheState
GPUTexture::Format hash_cache_texture_format = GPUTexture::Format::Unknown; GPUTexture::Format hash_cache_texture_format = GPUTexture::Format::Unknown;
HashCache hash_cache; HashCache hash_cache;
GPU_HW* hw_backend = nullptr; // TODO:FIXME: remove me
/// List of candidates for purging when the hash cache gets too large. /// List of candidates for purging when the hash cache gets too large.
std::vector<std::pair<HashCache::iterator, s32>> hash_cache_purge_list; std::vector<std::pair<HashCache::iterator, s32>> hash_cache_purge_list;
@ -529,7 +536,6 @@ struct GPUTextureCacheState
std::unique_ptr<GPUPipeline> replacement_draw_pipeline; // copies alpha as-is std::unique_ptr<GPUPipeline> replacement_draw_pipeline; // copies alpha as-is
std::unique_ptr<GPUPipeline> replacement_semitransparent_draw_pipeline; // inverts alpha (i.e. semitransparent) std::unique_ptr<GPUPipeline> replacement_semitransparent_draw_pipeline; // inverts alpha (i.e. semitransparent)
std::string game_id;
VRAMReplacementMap vram_replacements; VRAMReplacementMap vram_replacements;
// TODO: Combine these into one map? // TODO: Combine these into one map?
@ -555,26 +561,28 @@ ALIGN_TO_CACHE_LINE GPUTextureCacheState s_state;
bool GPUTextureCache::ShouldTrackVRAMWrites() bool GPUTextureCache::ShouldTrackVRAMWrites()
{ {
if (!g_settings.gpu_texture_cache) if (!g_gpu_settings.gpu_texture_cache)
return false; return false;
#ifdef ALWAYS_TRACK_VRAM_WRITES #ifdef ALWAYS_TRACK_VRAM_WRITES
return true; return true;
#else #else
return (IsDumpingVRAMWriteTextures() || return (IsDumpingVRAMWriteTextures() ||
(g_settings.texture_replacements.enable_texture_replacements && HasVRAMWriteTextureReplacements())); (g_gpu_settings.texture_replacements.enable_texture_replacements && HasVRAMWriteTextureReplacements()));
#endif #endif
} }
bool GPUTextureCache::IsDumpingVRAMWriteTextures() bool GPUTextureCache::IsDumpingVRAMWriteTextures()
{ {
return (g_settings.texture_replacements.dump_textures && !s_state.config.dump_texture_pages); return (g_gpu_settings.texture_replacements.dump_textures && !s_state.config.dump_texture_pages);
} }
bool GPUTextureCache::Initialize() bool GPUTextureCache::Initialize(GPU_HW* backend)
{ {
s_state.hw_backend = backend;
SetHashCacheTextureFormat(); SetHashCacheTextureFormat();
LoadLocalConfiguration(false, false); ReloadTextureReplacements(false);
UpdateVRAMTrackingState(); UpdateVRAMTrackingState();
if (!CompilePipelines()) if (!CompilePipelines())
return false; return false;
@ -582,13 +590,13 @@ bool GPUTextureCache::Initialize()
return true; return true;
} }
void GPUTextureCache::UpdateSettings(bool use_texture_cache, const Settings& old_settings) void GPUTextureCache::UpdateSettings(bool use_texture_cache, const GPUSettings& old_settings)
{ {
if (use_texture_cache) if (use_texture_cache)
{ {
UpdateVRAMTrackingState(); UpdateVRAMTrackingState();
if (g_settings.texture_replacements.enable_texture_replacements != if (g_gpu_settings.texture_replacements.enable_texture_replacements !=
old_settings.texture_replacements.enable_texture_replacements) old_settings.texture_replacements.enable_texture_replacements)
{ {
Invalidate(); Invalidate();
@ -602,9 +610,9 @@ void GPUTextureCache::UpdateSettings(bool use_texture_cache, const Settings& old
// Reload textures if configuration changes. // Reload textures if configuration changes.
const bool old_replacement_scale_linear_filter = s_state.config.replacement_scale_linear_filter; const bool old_replacement_scale_linear_filter = s_state.config.replacement_scale_linear_filter;
if (LoadLocalConfiguration(false, false) || if (LoadLocalConfiguration(false, false) ||
g_settings.texture_replacements.enable_texture_replacements != g_gpu_settings.texture_replacements.enable_texture_replacements !=
old_settings.texture_replacements.enable_texture_replacements || old_settings.texture_replacements.enable_texture_replacements ||
g_settings.texture_replacements.enable_vram_write_replacements != g_gpu_settings.texture_replacements.enable_vram_write_replacements !=
old_settings.texture_replacements.enable_vram_write_replacements) old_settings.texture_replacements.enable_vram_write_replacements)
{ {
if (use_texture_cache) if (use_texture_cache)
@ -620,6 +628,37 @@ void GPUTextureCache::UpdateSettings(bool use_texture_cache, const Settings& old
} }
} }
bool GPUTextureCache::GetStateSize(StateWrapper& sw, u32* size)
{
if (sw.GetVersion() < 73)
{
*size = 0;
return true;
}
const size_t start = sw.GetPosition();
if (!sw.DoMarker("GPUTextureCache")) [[unlikely]]
return false;
u32 num_vram_writes = 0;
sw.Do(&num_vram_writes);
for (u32 i = 0; i < num_vram_writes; i++)
{
sw.SkipBytes(sizeof(GSVector4i) * 2 + sizeof(HashType));
u32 num_palette_records = 0;
sw.Do(&num_palette_records);
sw.SkipBytes(num_palette_records * STATE_PALETTE_RECORD_SIZE);
}
if (sw.HasError()) [[unlikely]]
return false;
*size = static_cast<u32>(sw.GetPosition() - start);
return true;
}
bool GPUTextureCache::DoState(StateWrapper& sw, bool skip) bool GPUTextureCache::DoState(StateWrapper& sw, bool skip)
{ {
if (sw.GetVersion() < 73) if (sw.GetVersion() < 73)
@ -668,7 +707,7 @@ bool GPUTextureCache::DoState(StateWrapper& sw, bool skip)
sw.Do(&num_palette_records); sw.Do(&num_palette_records);
// Skip palette records if we're not dumping now. // Skip palette records if we're not dumping now.
if (g_settings.texture_replacements.dump_textures) if (g_gpu_settings.texture_replacements.dump_textures)
{ {
vrw->palette_records.reserve(num_palette_records); vrw->palette_records.reserve(num_palette_records);
for (u32 j = 0; j < num_palette_records; j++) for (u32 j = 0; j < num_palette_records; j++)
@ -760,6 +799,7 @@ void GPUTextureCache::Shutdown()
s_state.hash_cache_purge_list = {}; s_state.hash_cache_purge_list = {};
s_state.temp_vram_write_list = {}; s_state.temp_vram_write_list = {};
s_state.track_vram_writes = false; s_state.track_vram_writes = false;
s_state.hw_backend = nullptr;
for (auto it = s_state.gpu_replacement_image_cache.begin(); it != s_state.gpu_replacement_image_cache.end();) for (auto it = s_state.gpu_replacement_image_cache.begin(); it != s_state.gpu_replacement_image_cache.end();)
{ {
@ -773,7 +813,6 @@ void GPUTextureCache::Shutdown()
s_state.vram_write_texture_replacements.clear(); s_state.vram_write_texture_replacements.clear();
s_state.texture_page_texture_replacements.clear(); s_state.texture_page_texture_replacements.clear();
s_state.dumped_textures.clear(); s_state.dumped_textures.clear();
s_state.game_id = {};
} }
void GPUTextureCache::SetHashCacheTextureFormat() void GPUTextureCache::SetHashCacheTextureFormat()
@ -791,7 +830,7 @@ void GPUTextureCache::SetHashCacheTextureFormat()
bool GPUTextureCache::CompilePipelines() bool GPUTextureCache::CompilePipelines()
{ {
if (!g_settings.texture_replacements.enable_texture_replacements) if (!g_gpu_settings.texture_replacements.enable_texture_replacements)
return true; return true;
GPUPipeline::GraphicsConfig plconfig = {}; GPUPipeline::GraphicsConfig plconfig = {};
@ -1390,7 +1429,7 @@ const GPUTextureCache::Source* GPUTextureCache::ReturnSource(Source* source, con
source->from_hash_cache->last_used_frame = System::GetFrameNumber(); source->from_hash_cache->last_used_frame = System::GetFrameNumber();
// TODO: Cache var. // TODO: Cache var.
if (g_settings.texture_replacements.dump_textures) if (g_gpu_settings.texture_replacements.dump_textures)
{ {
source->active_uv_rect = source->active_uv_rect.runion(uv_rect); source->active_uv_rect = source->active_uv_rect.runion(uv_rect);
source->palette_record_flags |= flags; source->palette_record_flags |= flags;
@ -1548,7 +1587,7 @@ void GPUTextureCache::DestroySource(Source* src, bool remove_from_hash_cache)
{ {
GL_INS_FMT("Invalidate source {}", SourceToString(src)); GL_INS_FMT("Invalidate source {}", SourceToString(src));
if (g_settings.texture_replacements.dump_textures && !src->active_uv_rect.eq(INVALID_RECT)) if (g_gpu_settings.texture_replacements.dump_textures && !src->active_uv_rect.eq(INVALID_RECT))
{ {
if (!s_state.config.dump_texture_pages) if (!s_state.config.dump_texture_pages)
{ {
@ -1950,7 +1989,7 @@ void GPUTextureCache::RemoveVRAMWrite(VRAMWrite* entry)
void GPUTextureCache::DumpTexturesFromVRAMWrite(VRAMWrite* entry) void GPUTextureCache::DumpTexturesFromVRAMWrite(VRAMWrite* entry)
{ {
if (g_settings.texture_replacements.dump_textures && !s_state.config.dump_texture_pages) if (g_gpu_settings.texture_replacements.dump_textures && !s_state.config.dump_texture_pages)
{ {
for (const VRAMWrite::PaletteRecord& prec : entry->palette_records) for (const VRAMWrite::PaletteRecord& prec : entry->palette_records)
{ {
@ -2200,7 +2239,7 @@ GPUTextureCache::HashCacheEntry* GPUTextureCache::LookupHashCache(SourceKey key,
DecodeTexture(key.page, key.palette, key.mode, entry.texture.get()); DecodeTexture(key.page, key.palette, key.mode, entry.texture.get());
if (g_settings.texture_replacements.enable_texture_replacements) if (g_gpu_settings.texture_replacements.enable_texture_replacements)
ApplyTextureReplacements(key, tex_hash, pal_hash, &entry); ApplyTextureReplacements(key, tex_hash, pal_hash, &entry);
s_state.hash_cache_memory_usage += entry.texture->GetVRAMUsage(); s_state.hash_cache_memory_usage += entry.texture->GetVRAMUsage();
@ -2603,12 +2642,8 @@ size_t GPUTextureCache::DumpedTextureKeyHash::operator()(const DumpedTextureKey&
return hash; return hash;
} }
void GPUTextureCache::SetGameID(std::string game_id) void GPUTextureCache::GameSerialChanged()
{ {
if (s_state.game_id == game_id)
return;
s_state.game_id = game_id;
ReloadTextureReplacements(false); ReloadTextureReplacements(false);
} }
@ -2625,7 +2660,8 @@ GPUTexture* GPUTextureCache::GetVRAMReplacement(u32 width, u32 height, const voi
bool GPUTextureCache::ShouldDumpVRAMWrite(u32 width, u32 height) bool GPUTextureCache::ShouldDumpVRAMWrite(u32 width, u32 height)
{ {
return (g_settings.texture_replacements.dump_vram_writes && width >= s_state.config.vram_write_dump_width_threshold && return (g_gpu_settings.texture_replacements.dump_vram_writes &&
width >= s_state.config.vram_write_dump_width_threshold &&
height >= s_state.config.vram_write_dump_height_threshold); height >= s_state.config.vram_write_dump_height_threshold);
} }
@ -2716,7 +2752,7 @@ void GPUTextureCache::DumpTexture(TextureReplacementType type, u32 offset_x, u32
}; };
// skip if dumped already // skip if dumped already
if (!g_settings.texture_replacements.dump_replaced_textures) if (!g_gpu_settings.texture_replacements.dump_replaced_textures)
{ {
const TextureReplacementMap& map = (type == TextureReplacementType::TextureFromPage) ? const TextureReplacementMap& map = (type == TextureReplacementType::TextureFromPage) ?
s_state.texture_page_texture_replacements : s_state.texture_page_texture_replacements :
@ -2942,7 +2978,7 @@ bool GPUTextureCache::HasValidReplacementExtension(const std::string_view path)
void GPUTextureCache::FindTextureReplacements(bool load_vram_write_replacements, bool load_texture_replacements) void GPUTextureCache::FindTextureReplacements(bool load_vram_write_replacements, bool load_texture_replacements)
{ {
if (s_state.game_id.empty()) if (GPUThread::GetGameSerial().empty())
return; return;
FileSystem::FindResultsArray files; FileSystem::FindResultsArray files;
@ -3015,23 +3051,23 @@ void GPUTextureCache::FindTextureReplacements(bool load_vram_write_replacements,
} }
} }
if (g_settings.texture_replacements.enable_texture_replacements) if (g_gpu_settings.texture_replacements.enable_texture_replacements)
{ {
INFO_LOG("Found {} replacement upload textures for '{}'", s_state.vram_write_texture_replacements.size(), INFO_LOG("Found {} replacement upload textures for '{}'", s_state.vram_write_texture_replacements.size(),
s_state.game_id); GPUThread::GetGameSerial());
INFO_LOG("Found {} replacement page textures for '{}'", s_state.texture_page_texture_replacements.size(), INFO_LOG("Found {} replacement page textures for '{}'", s_state.texture_page_texture_replacements.size(),
s_state.game_id); GPUThread::GetGameSerial());
} }
if (g_settings.texture_replacements.enable_vram_write_replacements) if (g_gpu_settings.texture_replacements.enable_vram_write_replacements)
INFO_LOG("Found {} replacement VRAM for '{}'", s_state.vram_replacements.size(), s_state.game_id); INFO_LOG("Found {} replacement VRAM for '{}'", s_state.vram_replacements.size(), GPUThread::GetGameSerial());
} }
void GPUTextureCache::LoadTextureReplacementAliases(const ryml::ConstNodeRef& root, void GPUTextureCache::LoadTextureReplacementAliases(const ryml::ConstNodeRef& root,
bool load_vram_write_replacement_aliases, bool load_vram_write_replacement_aliases,
bool load_texture_replacement_aliases) bool load_texture_replacement_aliases)
{ {
if (s_state.game_id.empty()) if (GPUThread::GetGameSerial().empty())
return; return;
const std::string source_dir = GetTextureReplacementDirectory(); const std::string source_dir = GetTextureReplacementDirectory();
@ -3107,17 +3143,19 @@ void GPUTextureCache::LoadTextureReplacementAliases(const ryml::ConstNodeRef& ro
} }
} }
if (g_settings.texture_replacements.enable_texture_replacements) if (g_gpu_settings.texture_replacements.enable_texture_replacements)
{ {
INFO_LOG("Found {} replacement upload textures after applying aliases for '{}'", INFO_LOG("Found {} replacement upload textures after applying aliases for '{}'",
s_state.vram_write_texture_replacements.size(), s_state.game_id); s_state.vram_write_texture_replacements.size(), GPUThread::GetGameSerial());
INFO_LOG("Found {} replacement page textures after applying aliases for '{}'", INFO_LOG("Found {} replacement page textures after applying aliases for '{}'",
s_state.texture_page_texture_replacements.size(), s_state.game_id); s_state.texture_page_texture_replacements.size(), GPUThread::GetGameSerial());
} }
if (g_settings.texture_replacements.enable_vram_write_replacements) if (g_gpu_settings.texture_replacements.enable_vram_write_replacements)
{
INFO_LOG("Found {} replacement VRAM after applying aliases for '{}'", s_state.vram_replacements.size(), INFO_LOG("Found {} replacement VRAM after applying aliases for '{}'", s_state.vram_replacements.size(),
s_state.game_id); GPUThread::GetGameSerial());
}
} }
const GPUTextureCache::TextureReplacementImage* GPUTextureCache::GetTextureReplacementImage(const std::string& path) const GPUTextureCache::TextureReplacementImage* GPUTextureCache::GetTextureReplacementImage(const std::string& path)
@ -3241,8 +3279,8 @@ void GPUTextureCache::PreloadReplacementTextures()
#define UPDATE_PROGRESS() \ #define UPDATE_PROGRESS() \
if (last_update_time.GetTimeSeconds() >= UPDATE_INTERVAL) \ if (last_update_time.GetTimeSeconds() >= UPDATE_INTERVAL) \
{ \ { \
Host::DisplayLoadingScreen("Preloading replacement textures...", 0, static_cast<int>(total_textures), \ ImGuiFullscreen::RenderLoadingScreen(ImGuiManager::LOGO_IMAGE_NAME, "Preloading replacement textures...", 0, \
static_cast<int>(num_textures_loaded)); \ static_cast<int>(total_textures), static_cast<int>(num_textures_loaded)); \
last_update_time.Reset(); \ last_update_time.Reset(); \
} }
@ -3269,10 +3307,10 @@ void GPUTextureCache::PreloadReplacementTextures()
bool GPUTextureCache::EnsureGameDirectoryExists() bool GPUTextureCache::EnsureGameDirectoryExists()
{ {
if (s_state.game_id.empty()) if (GPUThread::GetGameSerial().empty())
return false; return false;
const std::string game_directory = Path::Combine(EmuFolders::Textures, s_state.game_id); const std::string game_directory = Path::Combine(EmuFolders::Textures, GPUThread::GetGameSerial());
if (FileSystem::DirectoryExists(game_directory.c_str())) if (FileSystem::DirectoryExists(game_directory.c_str()))
return true; return true;
@ -3309,12 +3347,13 @@ bool GPUTextureCache::EnsureGameDirectoryExists()
std::string GPUTextureCache::GetTextureReplacementDirectory() std::string GPUTextureCache::GetTextureReplacementDirectory()
{ {
std::string dir = Path::Combine( std::string dir =
EmuFolders::Textures, SmallString::from_format("{}" FS_OSPATH_SEPARATOR_STR "replacements", s_state.game_id)); Path::Combine(EmuFolders::Textures,
SmallString::from_format("{}" FS_OSPATH_SEPARATOR_STR "replacements", GPUThread::GetGameSerial()));
if (!FileSystem::DirectoryExists(dir.c_str())) if (!FileSystem::DirectoryExists(dir.c_str()))
{ {
// Check for the old directory structure without a replacements subdirectory. // Check for the old directory structure without a replacements subdirectory.
std::string altdir = Path::Combine(EmuFolders::Textures, s_state.game_id); std::string altdir = Path::Combine(EmuFolders::Textures, GPUThread::GetGameSerial());
if (FileSystem::DirectoryExists(altdir.c_str())) if (FileSystem::DirectoryExists(altdir.c_str()))
WARNING_LOG("Using deprecated texture replacement directory {}", altdir); WARNING_LOG("Using deprecated texture replacement directory {}", altdir);
dir = std::move(altdir); dir = std::move(altdir);
@ -3326,7 +3365,7 @@ std::string GPUTextureCache::GetTextureReplacementDirectory()
std::string GPUTextureCache::GetTextureDumpDirectory() std::string GPUTextureCache::GetTextureDumpDirectory()
{ {
return Path::Combine(EmuFolders::Textures, return Path::Combine(EmuFolders::Textures,
SmallString::from_format("{}" FS_OSPATH_SEPARATOR_STR "dumps", s_state.game_id)); SmallString::from_format("{}" FS_OSPATH_SEPARATOR_STR "dumps", GPUThread::GetGameSerial()));
} }
GPUTextureCache::VRAMReplacementName GPUTextureCache::GetVRAMWriteHash(u32 width, u32 height, const void* pixels) GPUTextureCache::VRAMReplacementName GPUTextureCache::GetVRAMWriteHash(u32 width, u32 height, const void* pixels)
@ -3354,14 +3393,15 @@ bool GPUTextureCache::LoadLocalConfiguration(bool load_vram_write_replacement_al
const Settings::TextureReplacementSettings::Configuration old_config = s_state.config; const Settings::TextureReplacementSettings::Configuration old_config = s_state.config;
// load settings from ini // load settings from ini
s_state.config = g_settings.texture_replacements.config; s_state.config = g_gpu_settings.texture_replacements.config;
if (s_state.game_id.empty()) const std::string& game_serial = GPUThread::GetGameSerial();
if (game_serial.empty())
return (s_state.config != old_config); return (s_state.config != old_config);
const std::optional<std::string> ini_data = FileSystem::ReadFileToString( const std::optional<std::string> ini_data = FileSystem::ReadFileToString(
Path::Combine(EmuFolders::Textures, Path::Combine(EmuFolders::Textures,
SmallString::from_format("{}" FS_OSPATH_SEPARATOR_STR "{}", s_state.game_id, LOCAL_CONFIG_FILENAME)) SmallString::from_format("{}" FS_OSPATH_SEPARATOR_STR "{}", game_serial, LOCAL_CONFIG_FILENAME))
.c_str()); .c_str());
if (!ini_data.has_value() || ini_data->empty()) if (!ini_data.has_value() || ini_data->empty())
return (s_state.config != old_config); return (s_state.config != old_config);
@ -3430,20 +3470,19 @@ void GPUTextureCache::ReloadTextureReplacements(bool show_info)
s_state.vram_write_texture_replacements.clear(); s_state.vram_write_texture_replacements.clear();
s_state.texture_page_texture_replacements.clear(); s_state.texture_page_texture_replacements.clear();
const bool load_vram_write_replacements = (g_settings.texture_replacements.enable_vram_write_replacements); const bool load_vram_write_replacements = (g_gpu_settings.texture_replacements.enable_vram_write_replacements);
const bool load_texture_replacements = const bool load_texture_replacements =
(g_settings.gpu_texture_cache && g_settings.texture_replacements.enable_texture_replacements); (g_gpu_settings.gpu_texture_cache && g_gpu_settings.texture_replacements.enable_texture_replacements);
if (load_vram_write_replacements || load_texture_replacements) if (load_vram_write_replacements || load_texture_replacements)
FindTextureReplacements(load_vram_write_replacements, load_texture_replacements); FindTextureReplacements(load_vram_write_replacements, load_texture_replacements);
LoadLocalConfiguration(load_vram_write_replacements, load_texture_replacements); LoadLocalConfiguration(load_vram_write_replacements, load_texture_replacements);
if (g_settings.texture_replacements.preload_textures) if (g_gpu_settings.texture_replacements.preload_textures)
PreloadReplacementTextures(); PreloadReplacementTextures();
PurgeUnreferencedTexturesFromCache(); PurgeUnreferencedTexturesFromCache();
DebugAssert(g_gpu);
UpdateVRAMTrackingState(); UpdateVRAMTrackingState();
InvalidateSources(); InvalidateSources();
@ -3596,5 +3635,5 @@ void GPUTextureCache::ApplyTextureReplacements(SourceKey key, HashType tex_hash,
g_gpu_device->RecycleTexture(std::move(entry->texture)); g_gpu_device->RecycleTexture(std::move(entry->texture));
entry->texture = std::move(replacement_tex); entry->texture = std::move(replacement_tex);
g_gpu->RestoreDeviceContext(); s_state.hw_backend->RestoreDeviceContext();
} }

View File

@ -9,7 +9,8 @@ class Image;
class GPUTexture; class GPUTexture;
class StateWrapper; class StateWrapper;
struct Settings; struct GPUSettings;
class GPU_HW;
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
// Texture Cache // Texture Cache
@ -102,9 +103,12 @@ struct Source
TListNode<Source> hash_cache_ref; TListNode<Source> hash_cache_ref;
}; };
bool Initialize(); bool Initialize(GPU_HW* backend);
void UpdateSettings(bool use_texture_cache, const Settings& old_settings); void UpdateSettings(bool use_texture_cache, const GPUSettings& old_settings);
bool GetStateSize(StateWrapper& sw, u32* size);
bool DoState(StateWrapper& sw, bool skip); bool DoState(StateWrapper& sw, bool skip);
void Shutdown(); void Shutdown();
void Invalidate(); void Invalidate();
@ -124,7 +128,7 @@ bool AreSourcePagesDrawn(SourceKey key, const GSVector4i rect);
void Compact(); void Compact();
void SetGameID(std::string game_id); void GameSerialChanged();
void ReloadTextureReplacements(bool show_info); void ReloadTextureReplacements(bool show_info);
// VRAM Write Replacements // VRAM Write Replacements

View File

@ -2,16 +2,17 @@
// SPDX-License-Identifier: CC-BY-NC-ND-4.0 // SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "gpu_sw.h" #include "gpu_sw.h"
#include "gpu_hw_texture_cache.h" #include "gpu.h"
#include "gpu_sw_rasterizer.h"
#include "settings.h" #include "settings.h"
#include "system.h" #include "system_private.h"
#include "util/gpu_device.h" #include "util/gpu_device.h"
#include "util/state_wrapper.h"
#include "common/align.h" #include "common/align.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/gsvector.h" #include "common/intrin.h"
#include "common/gsvector_formatter.h"
#include "common/log.h" #include "common/log.h"
#include <algorithm> #include <algorithm>
@ -20,25 +21,16 @@ LOG_CHANNEL(GPU);
GPU_SW::GPU_SW() = default; GPU_SW::GPU_SW() = default;
GPU_SW::~GPU_SW() GPU_SW::~GPU_SW() = default;
u32 GPU_SW::GetResolutionScale() const
{ {
g_gpu_device->RecycleTexture(std::move(m_upload_texture)); return 1u;
m_backend.Shutdown();
} }
const Threading::Thread* GPU_SW::GetSWThread() const bool GPU_SW::Initialize(bool upload_vram, Error* error)
{ {
return m_backend.GetThread(); if (!GPUBackend::Initialize(upload_vram, error))
}
bool GPU_SW::IsHardwareRenderer() const
{
return false;
}
bool GPU_SW::Initialize(Error* error)
{
if (!GPU::Initialize(error) || !m_backend.Initialize(g_settings.gpu_use_thread))
return false; return false;
static constexpr const std::array formats_for_16bit = {GPUTexture::Format::RGB5A1, GPUTexture::Format::A1BGR5, static constexpr const std::array formats_for_16bit = {GPUTexture::Format::RGB5A1, GPUTexture::Format::A1BGR5,
@ -55,34 +47,153 @@ bool GPU_SW::Initialize(Error* error)
// RGBA8 will always be supported, hence we'll find one. // RGBA8 will always be supported, hence we'll find one.
INFO_LOG("Using {} format for 16-bit display", GPUTexture::GetFormatName(m_16bit_display_format)); INFO_LOG("Using {} format for 16-bit display", GPUTexture::GetFormatName(m_16bit_display_format));
Assert(m_16bit_display_format != GPUTexture::Format::Unknown); Assert(m_16bit_display_format != GPUTexture::Format::Unknown);
// if we're using "new" vram, clear it out here
if (!upload_vram)
std::memset(g_vram, 0, sizeof(g_vram));
return true; return true;
} }
bool GPU_SW::DoState(StateWrapper& sw, GPUTexture** host_texture, bool update_display) void GPU_SW::ClearVRAM()
{ {
// need to ensure the worker thread is done std::memset(g_vram, 0, sizeof(g_vram));
m_backend.Sync(true); std::memset(g_gpu_clut, 0, sizeof(g_gpu_clut));
// ignore the host texture for software mode, since we want to save vram here
if (!GPU::DoState(sw, nullptr, update_display))
return false;
// need to still call the TC, to toss any data in the state
return GPUTextureCache::DoState(sw, true);
} }
void GPU_SW::Reset(bool clear_vram) void GPU_SW::UpdateResolutionScale()
{ {
GPU::Reset(clear_vram);
m_backend.Reset();
} }
void GPU_SW::UpdateSettings(const Settings& old_settings) void GPU_SW::LoadState(const GPUBackendLoadStateCommand* cmd)
{
std::memcpy(g_vram, cmd->vram_data, sizeof(g_vram));
std::memcpy(g_gpu_clut, cmd->clut_data, sizeof(g_gpu_clut));
}
bool GPU_SW::AllocateMemorySaveState(System::MemorySaveState& mss, Error* error)
{
mss.gpu_state_data.resize(sizeof(g_vram) + sizeof(g_gpu_clut));
return true;
}
void GPU_SW::DoMemoryState(StateWrapper& sw, System::MemorySaveState& mss)
{
sw.DoBytes(g_vram, sizeof(g_vram));
sw.DoBytes(g_gpu_clut, sizeof(g_gpu_clut));
DebugAssert(!sw.HasError());
}
void GPU_SW::ReadVRAM(u32 x, u32 y, u32 width, u32 height)
{
}
void GPU_SW::FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color, bool interlaced_rendering, u8 active_line_lsb)
{
GPU_SW_Rasterizer::FillVRAM(x, y, width, height, color, interlaced_rendering, active_line_lsb);
}
void GPU_SW::UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data, bool set_mask, bool check_mask)
{
GPU_SW_Rasterizer::WriteVRAM(x, y, width, height, data, set_mask, check_mask);
}
void GPU_SW::CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height, bool set_mask, bool check_mask)
{
GPU_SW_Rasterizer::CopyVRAM(src_x, src_y, dst_x, dst_y, width, height, set_mask, check_mask);
}
void GPU_SW::DrawPolygon(const GPUBackendDrawPolygonCommand* cmd)
{
const GPU_SW_Rasterizer::DrawTriangleFunction DrawFunction = GPU_SW_Rasterizer::GetDrawTriangleFunction(
cmd->shading_enable, cmd->texture_enable, cmd->raw_texture_enable, cmd->transparency_enable);
DrawFunction(cmd, &cmd->vertices[0], &cmd->vertices[1], &cmd->vertices[2]);
if (cmd->num_vertices > 3)
DrawFunction(cmd, &cmd->vertices[2], &cmd->vertices[1], &cmd->vertices[3]);
}
void GPU_SW::DrawPrecisePolygon(const GPUBackendDrawPrecisePolygonCommand* cmd)
{
const GPU_SW_Rasterizer::DrawTriangleFunction DrawFunction = GPU_SW_Rasterizer::GetDrawTriangleFunction(
cmd->shading_enable, cmd->texture_enable, cmd->raw_texture_enable, cmd->transparency_enable);
// Need to cut out the irrelevant bits.
// TODO: In _theory_ we could use the fixed-point parts here.
GPUBackendDrawPolygonCommand::Vertex vertices[4];
for (u32 i = 0; i < cmd->num_vertices; i++)
{
const GPUBackendDrawPrecisePolygonCommand::Vertex& src = cmd->vertices[i];
vertices[i] = GPUBackendDrawPolygonCommand::Vertex{
.x = src.native_x, .y = src.native_y, .color = src.color, .texcoord = src.texcoord};
}
DrawFunction(cmd, &vertices[0], &vertices[1], &vertices[2]);
if (cmd->num_vertices > 3)
DrawFunction(cmd, &vertices[2], &vertices[1], &vertices[3]);
}
void GPU_SW::DrawSprite(const GPUBackendDrawRectangleCommand* cmd)
{
const GPU_SW_Rasterizer::DrawRectangleFunction DrawFunction =
GPU_SW_Rasterizer::GetDrawRectangleFunction(cmd->texture_enable, cmd->raw_texture_enable, cmd->transparency_enable);
DrawFunction(cmd);
}
void GPU_SW::DrawLine(const GPUBackendDrawLineCommand* cmd)
{
const GPU_SW_Rasterizer::DrawLineFunction DrawFunction =
GPU_SW_Rasterizer::GetDrawLineFunction(cmd->shading_enable, cmd->transparency_enable);
for (u16 i = 0; i < cmd->num_vertices; i += 2)
DrawFunction(cmd, &cmd->vertices[i], &cmd->vertices[i + 1]);
}
void GPU_SW::DrawPreciseLine(const GPUBackendDrawPreciseLineCommand* cmd)
{
const GPU_SW_Rasterizer::DrawLineFunction DrawFunction =
GPU_SW_Rasterizer::GetDrawLineFunction(cmd->shading_enable, cmd->transparency_enable);
// Need to cut out the irrelevant bits.
// TODO: In _theory_ we could use the fixed-point parts here.
for (u32 i = 0; i < cmd->num_vertices; i += 2)
{
const GPUBackendDrawPreciseLineCommand::Vertex& RESTRICT start = cmd->vertices[i];
const GPUBackendDrawPreciseLineCommand::Vertex& RESTRICT end = cmd->vertices[i + 1];
const GPUBackendDrawLineCommand::Vertex vertices[2] = {
{.x = start.native_x, .y = start.native_y, .color = start.color},
{.x = end.native_x, .y = end.native_y, .color = end.color},
};
DrawFunction(cmd, &vertices[0], &vertices[1]);
}
}
void GPU_SW::DrawingAreaChanged()
{
// GPU_SW_Rasterizer::g_drawing_area set by base class.
}
void GPU_SW::ClearCache()
{
}
void GPU_SW::UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit)
{
GPU_SW_Rasterizer::UpdateCLUT(reg, clut_is_8bit);
}
void GPU_SW::OnBufferSwapped()
{
}
void GPU_SW::FlushRender()
{
}
void GPU_SW::RestoreDeviceContext()
{ {
GPU::UpdateSettings(old_settings);
if (g_settings.gpu_use_thread != old_settings.gpu_use_thread)
m_backend.SetThreadEnabled(g_settings.gpu_use_thread);
} }
GPUTexture* GPU_SW::GetDisplayTexture(u32 width, u32 height, GPUTexture::Format format) GPUTexture* GPU_SW::GetDisplayTexture(u32 width, u32 height, GPUTexture::Format format)
@ -263,32 +374,28 @@ bool GPU_SW::CopyOut(u32 src_x, u32 src_y, u32 skip_x, u32 width, u32 height, u3
} }
} }
void GPU_SW::UpdateDisplay() void GPU_SW::UpdateDisplay(const GPUBackendUpdateDisplayCommand* cmd)
{ {
// fill display texture if (!g_settings.gpu_show_vram)
m_backend.Sync(true);
if (!g_settings.debugging.show_vram)
{ {
if (IsDisplayDisabled()) if (cmd->display_disabled)
{ {
ClearDisplayTexture(); ClearDisplayTexture();
return; return;
} }
const bool is_24bit = m_GPUSTAT.display_area_color_depth_24; const bool is_24bit = cmd->display_24bit;
const bool interlaced = IsInterlacedDisplayEnabled(); const bool interlaced = cmd->interlaced_display_enabled;
const u32 field = GetInterlacedDisplayField(); const u32 field = BoolToUInt32(cmd->interlaced_display_field);
const u32 vram_offset_x = is_24bit ? m_crtc_state.regs.X : m_crtc_state.display_vram_left; const u32 vram_offset_x = is_24bit ? cmd->X : cmd->display_vram_left;
const u32 vram_offset_y = const u32 vram_offset_y = cmd->display_vram_top + ((interlaced && cmd->interlaced_display_interleaved) ? field : 0);
m_crtc_state.display_vram_top + ((interlaced && m_GPUSTAT.vertical_resolution) ? field : 0); const u32 skip_x = is_24bit ? (cmd->display_vram_left - cmd->X) : 0;
const u32 skip_x = is_24bit ? (m_crtc_state.display_vram_left - m_crtc_state.regs.X) : 0; const u32 read_width = cmd->display_vram_width;
const u32 read_width = m_crtc_state.display_vram_width; const u32 read_height = interlaced ? (cmd->display_vram_height / 2) : cmd->display_vram_height;
const u32 read_height = interlaced ? (m_crtc_state.display_vram_height / 2) : m_crtc_state.display_vram_height;
if (IsInterlacedDisplayEnabled()) if (cmd->interlaced_display_enabled)
{ {
const u32 line_skip = m_GPUSTAT.vertical_resolution; const u32 line_skip = cmd->interlaced_display_interleaved;
if (CopyOut(vram_offset_x, vram_offset_y, skip_x, read_width, read_height, line_skip, is_24bit)) if (CopyOut(vram_offset_x, vram_offset_y, skip_x, read_width, read_height, line_skip, is_24bit))
{ {
SetDisplayTexture(m_upload_texture.get(), nullptr, 0, 0, read_width, read_height); SetDisplayTexture(m_upload_texture.get(), nullptr, 0, 0, read_width, read_height);
@ -320,347 +427,7 @@ void GPU_SW::UpdateDisplay()
} }
} }
void GPU_SW::FillBackendCommandParameters(GPUBackendCommand* cmd) const std::unique_ptr<GPUBackend> GPUBackend::CreateSoftwareBackend()
{
cmd->params.bits = 0;
cmd->params.check_mask_before_draw = m_GPUSTAT.check_mask_before_draw;
cmd->params.set_mask_while_drawing = m_GPUSTAT.set_mask_while_drawing;
cmd->params.active_line_lsb = m_crtc_state.active_line_lsb;
cmd->params.interlaced_rendering = IsInterlacedRenderingEnabled();
}
void GPU_SW::FillDrawCommand(GPUBackendDrawCommand* cmd, GPURenderCommand rc) const
{
FillBackendCommandParameters(cmd);
cmd->rc.bits = rc.bits;
cmd->draw_mode.bits = m_draw_mode.mode_reg.bits;
cmd->draw_mode.dither_enable = rc.IsDitheringEnabled() && cmd->draw_mode.dither_enable;
cmd->palette.bits = m_draw_mode.palette_reg.bits;
cmd->window = m_draw_mode.texture_window;
}
void GPU_SW::DispatchRenderCommand()
{
if (m_drawing_area_changed)
{
GPUBackendSetDrawingAreaCommand* cmd = m_backend.NewSetDrawingAreaCommand();
cmd->new_area = m_drawing_area;
GSVector4i::store<false>(cmd->new_clamped_area, m_clamped_drawing_area);
m_backend.PushCommand(cmd);
m_drawing_area_changed = false;
}
const GPURenderCommand rc{m_render_command.bits};
switch (rc.primitive)
{
case GPUPrimitive::Polygon:
{
const u32 num_vertices = rc.quad_polygon ? 4 : 3;
GPUBackendDrawPolygonCommand* cmd = m_backend.NewDrawPolygonCommand(num_vertices);
FillDrawCommand(cmd, rc);
std::array<GSVector2i, 4> positions;
const u32 first_color = rc.color_for_first_vertex;
const bool shaded = rc.shading_enable;
const bool textured = rc.texture_enable;
for (u32 i = 0; i < num_vertices; i++)
{
GPUBackendDrawPolygonCommand::Vertex* vert = &cmd->vertices[i];
vert->color = (shaded && i > 0) ? (FifoPop() & UINT32_C(0x00FFFFFF)) : first_color;
const u64 maddr_and_pos = m_fifo.Pop();
const GPUVertexPosition vp{Truncate32(maddr_and_pos)};
vert->x = m_drawing_offset.x + vp.x;
vert->y = m_drawing_offset.y + vp.y;
vert->texcoord = textured ? Truncate16(FifoPop()) : 0;
positions[i] = GSVector2i::load<false>(&vert->x);
}
// Cull polygons which are too large.
const GSVector2i min_pos_12 = positions[1].min_s32(positions[2]);
const GSVector2i max_pos_12 = positions[1].max_s32(positions[2]);
const GSVector4i draw_rect_012 = GSVector4i(min_pos_12.min_s32(positions[0]))
.upl64(GSVector4i(max_pos_12.max_s32(positions[0])))
.add32(GSVector4i::cxpr(0, 0, 1, 1));
const bool first_tri_culled =
(draw_rect_012.width() > MAX_PRIMITIVE_WIDTH || draw_rect_012.height() > MAX_PRIMITIVE_HEIGHT ||
!m_clamped_drawing_area.rintersects(draw_rect_012));
if (first_tri_culled)
{
DEBUG_LOG("Culling off-screen/too-large polygon: {},{} {},{} {},{}", cmd->vertices[0].x, cmd->vertices[0].y,
cmd->vertices[1].x, cmd->vertices[1].y, cmd->vertices[2].x, cmd->vertices[2].y);
if (!rc.quad_polygon)
return;
}
else
{
AddDrawTriangleTicks(positions[0], positions[1], positions[2], rc.shading_enable, rc.texture_enable,
rc.transparency_enable);
}
// quads
if (rc.quad_polygon)
{
const GSVector4i draw_rect_123 = GSVector4i(min_pos_12.min_s32(positions[3]))
.upl64(GSVector4i(max_pos_12.max_s32(positions[3])))
.add32(GSVector4i::cxpr(0, 0, 1, 1));
// Cull polygons which are too large.
const bool second_tri_culled =
(draw_rect_123.width() > MAX_PRIMITIVE_WIDTH || draw_rect_123.height() > MAX_PRIMITIVE_HEIGHT ||
!m_clamped_drawing_area.rintersects(draw_rect_123));
if (second_tri_culled)
{
DEBUG_LOG("Culling too-large polygon (quad second half): {},{} {},{} {},{}", cmd->vertices[2].x,
cmd->vertices[2].y, cmd->vertices[1].x, cmd->vertices[1].y, cmd->vertices[0].x, cmd->vertices[0].y);
if (first_tri_culled)
return;
}
else
{
AddDrawTriangleTicks(positions[2], positions[1], positions[3], rc.shading_enable, rc.texture_enable,
rc.transparency_enable);
}
}
m_backend.PushCommand(cmd);
}
break;
case GPUPrimitive::Rectangle:
{
GPUBackendDrawRectangleCommand* cmd = m_backend.NewDrawRectangleCommand();
FillDrawCommand(cmd, rc);
cmd->color = rc.color_for_first_vertex;
const GPUVertexPosition vp{FifoPop()};
cmd->x = TruncateGPUVertexPosition(m_drawing_offset.x + vp.x);
cmd->y = TruncateGPUVertexPosition(m_drawing_offset.y + vp.y);
if (rc.texture_enable)
{
const u32 texcoord_and_palette = FifoPop();
cmd->palette.bits = Truncate16(texcoord_and_palette >> 16);
cmd->texcoord = Truncate16(texcoord_and_palette);
}
else
{
cmd->palette.bits = 0;
cmd->texcoord = 0;
}
switch (rc.rectangle_size)
{
case GPUDrawRectangleSize::R1x1:
cmd->width = 1;
cmd->height = 1;
break;
case GPUDrawRectangleSize::R8x8:
cmd->width = 8;
cmd->height = 8;
break;
case GPUDrawRectangleSize::R16x16:
cmd->width = 16;
cmd->height = 16;
break;
default:
{
const u32 width_and_height = FifoPop();
cmd->width = static_cast<u16>(width_and_height & VRAM_WIDTH_MASK);
cmd->height = static_cast<u16>((width_and_height >> 16) & VRAM_HEIGHT_MASK);
}
break;
}
const GSVector4i rect = GSVector4i(cmd->x, cmd->y, cmd->x + cmd->width, cmd->y + cmd->height);
const GSVector4i clamped_rect = m_clamped_drawing_area.rintersect(rect);
if (clamped_rect.rempty()) [[unlikely]]
{
DEBUG_LOG("Culling off-screen rectangle {}", rect);
return;
}
AddDrawRectangleTicks(clamped_rect, rc.texture_enable, rc.transparency_enable);
m_backend.PushCommand(cmd);
}
break;
case GPUPrimitive::Line:
{
if (!rc.polyline)
{
GPUBackendDrawLineCommand* cmd = m_backend.NewDrawLineCommand(2);
FillDrawCommand(cmd, rc);
cmd->palette.bits = 0;
if (rc.shading_enable)
{
cmd->vertices[0].color = rc.color_for_first_vertex;
const GPUVertexPosition start_pos{FifoPop()};
cmd->vertices[0].x = m_drawing_offset.x + start_pos.x;
cmd->vertices[0].y = m_drawing_offset.y + start_pos.y;
cmd->vertices[1].color = FifoPop() & UINT32_C(0x00FFFFFF);
const GPUVertexPosition end_pos{FifoPop()};
cmd->vertices[1].x = m_drawing_offset.x + end_pos.x;
cmd->vertices[1].y = m_drawing_offset.y + end_pos.y;
}
else
{
cmd->vertices[0].color = rc.color_for_first_vertex;
cmd->vertices[1].color = rc.color_for_first_vertex;
const GPUVertexPosition start_pos{FifoPop()};
cmd->vertices[0].x = m_drawing_offset.x + start_pos.x;
cmd->vertices[0].y = m_drawing_offset.y + start_pos.y;
const GPUVertexPosition end_pos{FifoPop()};
cmd->vertices[1].x = m_drawing_offset.x + end_pos.x;
cmd->vertices[1].y = m_drawing_offset.y + end_pos.y;
}
const GSVector4i v0 = GSVector4i::loadl<false>(&cmd->vertices[0].x);
const GSVector4i v1 = GSVector4i::loadl<false>(&cmd->vertices[1].x);
const GSVector4i rect = v0.min_s32(v1).xyxy(v0.max_s32(v1)).add32(GSVector4i::cxpr(0, 0, 1, 1));
const GSVector4i clamped_rect = rect.rintersect(m_clamped_drawing_area);
if (rect.width() > MAX_PRIMITIVE_WIDTH || rect.height() > MAX_PRIMITIVE_HEIGHT || clamped_rect.rempty())
{
DEBUG_LOG("Culling too-large/off-screen line: {},{} - {},{}", cmd->vertices[0].y, cmd->vertices[0].y,
cmd->vertices[1].x, cmd->vertices[1].y);
return;
}
AddDrawLineTicks(clamped_rect, rc.shading_enable);
m_backend.PushCommand(cmd);
}
else
{
const u32 num_vertices = GetPolyLineVertexCount();
GPUBackendDrawLineCommand* cmd = m_backend.NewDrawLineCommand((num_vertices - 1) * 2);
FillDrawCommand(cmd, m_render_command);
u32 buffer_pos = 0;
const GPUVertexPosition start_vp{m_blit_buffer[buffer_pos++]};
const GSVector2i draw_offset = GSVector2i::load<false>(&m_drawing_offset.x);
GSVector2i start_pos = GSVector2i(start_vp.x, start_vp.y).add32(draw_offset);
u32 start_color = m_render_command.color_for_first_vertex;
const bool shaded = m_render_command.shading_enable;
u32 out_vertex_count = 0;
for (u32 i = 1; i < num_vertices; i++)
{
const u32 end_color =
shaded ? (m_blit_buffer[buffer_pos++] & UINT32_C(0x00FFFFFF)) : m_render_command.color_for_first_vertex;
const GPUVertexPosition vp{m_blit_buffer[buffer_pos++]};
const GSVector2i end_pos = GSVector2i(vp.x, vp.y).add32(draw_offset);
const GSVector4i rect = GSVector4i::xyxy(start_pos.min_s32(end_pos), start_pos.max_s32(end_pos))
.add32(GSVector4i::cxpr(0, 0, 1, 1));
const GSVector4i clamped_rect = rect.rintersect(m_clamped_drawing_area);
if (rect.width() > MAX_PRIMITIVE_WIDTH || rect.height() > MAX_PRIMITIVE_HEIGHT || clamped_rect.rempty())
{
DEBUG_LOG("Culling too-large/off-screen line: {},{} - {},{}", cmd->vertices[i - 1].x,
cmd->vertices[i - 1].y, cmd->vertices[i].x, cmd->vertices[i].y);
}
else
{
AddDrawLineTicks(clamped_rect, rc.shading_enable);
GPUBackendDrawLineCommand::Vertex* out_vertex = &cmd->vertices[out_vertex_count];
out_vertex_count += 2;
GSVector2i::store<false>(&out_vertex[0].x, start_pos);
out_vertex[0].color = start_color;
GSVector2i::store<false>(&out_vertex[1].x, end_pos);
out_vertex[1].color = end_color;
}
start_pos = end_pos;
start_color = end_color;
}
if (out_vertex_count > 0)
{
DebugAssert(out_vertex_count <= cmd->num_vertices);
cmd->num_vertices = Truncate16(out_vertex_count);
m_backend.PushCommand(cmd);
}
}
}
break;
default:
UnreachableCode();
break;
}
}
void GPU_SW::ReadVRAM(u32 x, u32 y, u32 width, u32 height)
{
m_backend.Sync(false);
}
void GPU_SW::FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color)
{
GPUBackendFillVRAMCommand* cmd = m_backend.NewFillVRAMCommand();
FillBackendCommandParameters(cmd);
cmd->x = static_cast<u16>(x);
cmd->y = static_cast<u16>(y);
cmd->width = static_cast<u16>(width);
cmd->height = static_cast<u16>(height);
cmd->color = color;
m_backend.PushCommand(cmd);
}
void GPU_SW::UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data, bool set_mask, bool check_mask)
{
const u32 num_words = width * height;
GPUBackendUpdateVRAMCommand* cmd = m_backend.NewUpdateVRAMCommand(num_words);
FillBackendCommandParameters(cmd);
cmd->params.set_mask_while_drawing = set_mask;
cmd->params.check_mask_before_draw = check_mask;
cmd->x = static_cast<u16>(x);
cmd->y = static_cast<u16>(y);
cmd->width = static_cast<u16>(width);
cmd->height = static_cast<u16>(height);
std::memcpy(cmd->data, data, sizeof(u16) * num_words);
m_backend.PushCommand(cmd);
}
void GPU_SW::CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height)
{
GPUBackendCopyVRAMCommand* cmd = m_backend.NewCopyVRAMCommand();
FillBackendCommandParameters(cmd);
cmd->src_x = static_cast<u16>(src_x);
cmd->src_y = static_cast<u16>(src_y);
cmd->dst_x = static_cast<u16>(dst_x);
cmd->dst_y = static_cast<u16>(dst_y);
cmd->width = static_cast<u16>(width);
cmd->height = static_cast<u16>(height);
m_backend.PushCommand(cmd);
}
void GPU_SW::FlushRender()
{
}
void GPU_SW::UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit)
{
GPUBackendUpdateCLUTCommand* cmd = m_backend.NewUpdateCLUTCommand();
FillBackendCommandParameters(cmd);
cmd->reg.bits = reg.bits;
cmd->clut_is_8bit = clut_is_8bit;
m_backend.PushCommand(cmd);
}
std::unique_ptr<GPU> GPU::CreateSoftwareRenderer()
{ {
return std::make_unique<GPU_SW>(); return std::make_unique<GPU_SW>();
} }

View File

@ -4,7 +4,7 @@
#pragma once #pragma once
#include "gpu.h" #include "gpu.h"
#include "gpu_sw_backend.h" #include "gpu_backend.h"
#include "util/gpu_device.h" #include "util/gpu_device.h"
@ -12,35 +12,52 @@
#include <memory> #include <memory>
namespace Threading { // TODO: Move to cpp
class Thread; // TODO: Rename to GPUSWBackend, preserved to avoid conflicts.
} class GPU_SW final : public GPUBackend
class GPUTexture;
class GPU_SW final : public GPU
{ {
public: public:
GPU_SW(); GPU_SW();
~GPU_SW() override; ~GPU_SW() override;
ALWAYS_INLINE const GPU_SW_Backend& GetBackend() const { return m_backend; } bool Initialize(bool upload_vram, Error* error) override;
const Threading::Thread* GetSWThread() const override; void RestoreDeviceContext() override;
bool IsHardwareRenderer() const override;
bool Initialize(Error* error) override; u32 GetResolutionScale() const override;
bool DoState(StateWrapper& sw, GPUTexture** host_texture, bool update_display) override;
void Reset(bool clear_vram) override;
void UpdateSettings(const Settings& old_settings) override;
protected: protected:
void ReadVRAM(u32 x, u32 y, u32 width, u32 height) override; void ReadVRAM(u32 x, u32 y, u32 width, u32 height) override;
void FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color) override; void FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color, bool interlaced_rendering, u8 active_line_lsb) override;
void UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data, bool set_mask, bool check_mask) override; void UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data, bool set_mask, bool check_mask) override;
void CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height) override; void CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height, bool set_mask,
void FlushRender() override; bool check_mask) override;
void DrawPolygon(const GPUBackendDrawPolygonCommand* cmd) override;
void DrawPrecisePolygon(const GPUBackendDrawPrecisePolygonCommand* cmd) override;
void DrawLine(const GPUBackendDrawLineCommand* cmd) override;
void DrawPreciseLine(const GPUBackendDrawPreciseLineCommand* cmd) override;
void DrawSprite(const GPUBackendDrawRectangleCommand* cmd) override;
void DrawingAreaChanged() override;
void ClearCache() override;
void UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit) override; void UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit) override;
void OnBufferSwapped() override;
void UpdateDisplay(const GPUBackendUpdateDisplayCommand* cmd) override;
void ClearVRAM() override;
void FlushRender() override;
void UpdateResolutionScale() override;
void LoadState(const GPUBackendLoadStateCommand* cmd) override;
bool AllocateMemorySaveState(System::MemorySaveState& mss, Error* error) override;
void DoMemoryState(StateWrapper& sw, System::MemorySaveState& mss) override;
private:
static constexpr GPUTexture::Format FORMAT_FOR_24BIT = GPUTexture::Format::RGBA8; // RGBA8 always supported.
template<GPUTexture::Format display_format> template<GPUTexture::Format display_format>
bool CopyOut15Bit(u32 src_x, u32 src_y, u32 width, u32 height, u32 line_skip); bool CopyOut15Bit(u32 src_x, u32 src_y, u32 width, u32 height, u32 line_skip);
@ -49,21 +66,9 @@ protected:
bool CopyOut(u32 src_x, u32 src_y, u32 skip_x, u32 width, u32 height, u32 line_skip, bool is_24bit); bool CopyOut(u32 src_x, u32 src_y, u32 skip_x, u32 width, u32 height, u32 line_skip, bool is_24bit);
void UpdateDisplay() override;
void DispatchRenderCommand() override;
void FillBackendCommandParameters(GPUBackendCommand* cmd) const;
void FillDrawCommand(GPUBackendDrawCommand* cmd, GPURenderCommand rc) const;
private:
static constexpr GPUTexture::Format FORMAT_FOR_24BIT = GPUTexture::Format::RGBA8; // RGBA8 always supported.
GPUTexture* GetDisplayTexture(u32 width, u32 height, GPUTexture::Format format); GPUTexture* GetDisplayTexture(u32 width, u32 height, GPUTexture::Format format);
FixedHeapArray<u8, GPU_MAX_DISPLAY_WIDTH * GPU_MAX_DISPLAY_HEIGHT * sizeof(u32)> m_upload_buffer; FixedHeapArray<u8, GPU_MAX_DISPLAY_WIDTH * GPU_MAX_DISPLAY_HEIGHT * sizeof(u32)> m_upload_buffer;
GPUTexture::Format m_16bit_display_format = GPUTexture::Format::Unknown; GPUTexture::Format m_16bit_display_format = GPUTexture::Format::Unknown;
std::unique_ptr<GPUTexture> m_upload_texture; std::unique_ptr<GPUTexture> m_upload_texture;
GPU_SW_Backend m_backend;
}; };

View File

@ -1,88 +0,0 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "gpu_sw_backend.h"
#include "gpu.h"
#include "gpu_sw_rasterizer.h"
#include "system.h"
#include "util/gpu_device.h"
#include <algorithm>
GPU_SW_Backend::GPU_SW_Backend() = default;
GPU_SW_Backend::~GPU_SW_Backend() = default;
bool GPU_SW_Backend::Initialize(bool use_thread)
{
return GPUBackend::Initialize(use_thread);
}
void GPU_SW_Backend::Reset()
{
GPUBackend::Reset();
}
void GPU_SW_Backend::DrawPolygon(const GPUBackendDrawPolygonCommand* cmd)
{
const GPURenderCommand rc{cmd->rc.bits};
const GPU_SW_Rasterizer::DrawTriangleFunction DrawFunction = GPU_SW_Rasterizer::GetDrawTriangleFunction(
rc.shading_enable, rc.texture_enable, rc.raw_texture_enable, rc.transparency_enable);
DrawFunction(cmd, &cmd->vertices[0], &cmd->vertices[1], &cmd->vertices[2]);
if (rc.quad_polygon)
DrawFunction(cmd, &cmd->vertices[2], &cmd->vertices[1], &cmd->vertices[3]);
}
void GPU_SW_Backend::DrawRectangle(const GPUBackendDrawRectangleCommand* cmd)
{
const GPURenderCommand rc{cmd->rc.bits};
const GPU_SW_Rasterizer::DrawRectangleFunction DrawFunction =
GPU_SW_Rasterizer::GetDrawRectangleFunction(rc.texture_enable, rc.raw_texture_enable, rc.transparency_enable);
DrawFunction(cmd);
}
void GPU_SW_Backend::DrawLine(const GPUBackendDrawLineCommand* cmd)
{
const GPU_SW_Rasterizer::DrawLineFunction DrawFunction =
GPU_SW_Rasterizer::GetDrawLineFunction(cmd->rc.shading_enable, cmd->rc.transparency_enable);
for (u16 i = 1; i < cmd->num_vertices; i++)
DrawFunction(cmd, &cmd->vertices[i - 1], &cmd->vertices[i]);
}
void GPU_SW_Backend::FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color, GPUBackendCommandParameters params)
{
GPU_SW_Rasterizer::FillVRAM(x, y, width, height, color, params.interlaced_rendering, params.active_line_lsb);
}
void GPU_SW_Backend::UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data,
GPUBackendCommandParameters params)
{
GPU_SW_Rasterizer::WriteVRAM(x, y, width, height, data, params.set_mask_while_drawing, params.check_mask_before_draw);
}
void GPU_SW_Backend::CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height,
GPUBackendCommandParameters params)
{
GPU_SW_Rasterizer::CopyVRAM(src_x, src_y, dst_x, dst_y, width, height, params.set_mask_while_drawing,
params.check_mask_before_draw);
}
void GPU_SW_Backend::UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit)
{
GPU::ReadCLUT(g_gpu_clut, reg, clut_is_8bit);
}
void GPU_SW_Backend::DrawingAreaChanged(const GPUDrawingArea& new_drawing_area, const GSVector4i clamped_drawing_area)
{
GPU_SW_Rasterizer::g_drawing_area = new_drawing_area;
}
void GPU_SW_Backend::FlushRender()
{
}

View File

@ -1,32 +0,0 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once
#include "gpu.h"
#include "gpu_backend.h"
#include <array>
class GPU_SW_Backend final : public GPUBackend
{
public:
GPU_SW_Backend();
~GPU_SW_Backend() override;
bool Initialize(bool use_thread) override;
void Reset() override;
protected:
void FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color, GPUBackendCommandParameters params) override;
void UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data, GPUBackendCommandParameters params) override;
void CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height,
GPUBackendCommandParameters params) override;
void DrawPolygon(const GPUBackendDrawPolygonCommand* cmd) override;
void DrawLine(const GPUBackendDrawLineCommand* cmd) override;
void DrawRectangle(const GPUBackendDrawRectangleCommand* cmd) override;
void DrawingAreaChanged(const GPUDrawingArea& new_drawing_area, const GSVector4i clamped_drawing_area) override;
void UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit) override;
void FlushRender() override;
};

View File

@ -38,6 +38,31 @@ CopyVRAMFunction CopyVRAM = nullptr;
GPUDrawingArea g_drawing_area = {}; GPUDrawingArea g_drawing_area = {};
} // namespace GPU_SW_Rasterizer } // namespace GPU_SW_Rasterizer
void GPU_SW_Rasterizer::UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit)
{
const u16* const src_row = &g_vram[reg.GetYBase() * VRAM_WIDTH];
const u32 start_x = reg.GetXBase();
if (!clut_is_8bit)
{
// Wraparound can't happen in 4-bit mode.
std::memcpy(g_gpu_clut, &src_row[start_x], sizeof(u16) * 16);
}
else
{
if ((start_x + 256) > VRAM_WIDTH) [[unlikely]]
{
const u32 end = VRAM_WIDTH - start_x;
const u32 start = 256 - end;
std::memcpy(g_gpu_clut, &src_row[start_x], sizeof(u16) * end);
std::memcpy(g_gpu_clut + end, src_row, sizeof(u16) * start);
}
else
{
std::memcpy(g_gpu_clut, &src_row[start_x], sizeof(u16) * 256);
}
}
}
// Default scalar implementation definitions. // Default scalar implementation definitions.
namespace GPU_SW_Rasterizer::Scalar { namespace GPU_SW_Rasterizer::Scalar {
namespace { namespace {

View File

@ -4,6 +4,7 @@
#pragma once #pragma once
#include "gpu.h" #include "gpu.h"
#include "gpu_thread_commands.h"
#include "gpu_types.h" #include "gpu_types.h"
#include "common/intrin.h" #include "common/intrin.h"
@ -18,18 +19,20 @@ static constexpr u32 DITHER_LUT_SIZE = 512;
using DitherLUT = std::array<std::array<std::array<u8, DITHER_LUT_SIZE>, DITHER_MATRIX_SIZE>, DITHER_MATRIX_SIZE>; using DitherLUT = std::array<std::array<std::array<u8, DITHER_LUT_SIZE>, DITHER_MATRIX_SIZE>, DITHER_MATRIX_SIZE>;
extern const DitherLUT g_dither_lut; extern const DitherLUT g_dither_lut;
// TODO: Pack in struct
extern GPUDrawingArea g_drawing_area; extern GPUDrawingArea g_drawing_area;
extern void UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit);
using DrawRectangleFunction = void (*)(const GPUBackendDrawRectangleCommand* cmd); using DrawRectangleFunction = void (*)(const GPUBackendDrawRectangleCommand* cmd);
typedef const DrawRectangleFunction DrawRectangleFunctionTable[2][2][2]; typedef const DrawRectangleFunction DrawRectangleFunctionTable[2][2][2];
using DrawTriangleFunction = void (*)(const GPUBackendDrawPolygonCommand* cmd, using DrawTriangleFunction = void (*)(const GPUBackendDrawCommand* cmd, const GPUBackendDrawPolygonCommand::Vertex* v0,
const GPUBackendDrawPolygonCommand::Vertex* v0,
const GPUBackendDrawPolygonCommand::Vertex* v1, const GPUBackendDrawPolygonCommand::Vertex* v1,
const GPUBackendDrawPolygonCommand::Vertex* v2); const GPUBackendDrawPolygonCommand::Vertex* v2);
typedef const DrawTriangleFunction DrawTriangleFunctionTable[2][2][2][2]; typedef const DrawTriangleFunction DrawTriangleFunctionTable[2][2][2][2];
using DrawLineFunction = void (*)(const GPUBackendDrawLineCommand* cmd, const GPUBackendDrawLineCommand::Vertex* p0, using DrawLineFunction = void (*)(const GPUBackendDrawCommand* cmd, const GPUBackendDrawLineCommand::Vertex* p0,
const GPUBackendDrawLineCommand::Vertex* p1); const GPUBackendDrawLineCommand::Vertex* p1);
typedef const DrawLineFunction DrawLineFunctionTable[2][2]; typedef const DrawLineFunction DrawLineFunctionTable[2][2];

View File

@ -129,7 +129,7 @@ template<bool texture_enable, bool raw_texture_enable, bool transparency_enable>
} }
else else
{ {
const bool dithering_enable = cmd->draw_mode.dither_enable; const bool dithering_enable = cmd->dither_enable;
const u32 dither_y = (dithering_enable) ? (y & 3u) : 2u; const u32 dither_y = (dithering_enable) ? (y & 3u) : 2u;
const u32 dither_x = (dithering_enable) ? (x & 3u) : 3u; const u32 dither_x = (dithering_enable) ? (x & 3u) : 3u;
@ -143,7 +143,7 @@ template<bool texture_enable, bool raw_texture_enable, bool transparency_enable>
} }
else else
{ {
const bool dithering_enable = cmd->draw_mode.dither_enable; const bool dithering_enable = cmd->dither_enable;
const u32 dither_y = (dithering_enable) ? (y & 3u) : 2u; const u32 dither_y = (dithering_enable) ? (y & 3u) : 2u;
const u32 dither_x = (dithering_enable) ? (x & 3u) : 3u; const u32 dither_x = (dithering_enable) ? (x & 3u) : 3u;
@ -215,12 +215,12 @@ template<bool texture_enable, bool raw_texture_enable, bool transparency_enable>
} }
} }
const u16 mask_and = cmd->params.GetMaskAND(); const u16 mask_and = cmd->GetMaskAND();
if ((bg_color & mask_and) != 0) if ((bg_color & mask_and) != 0)
return; return;
DebugAssert(static_cast<u32>(x) < VRAM_WIDTH && static_cast<u32>(y) < VRAM_HEIGHT); DebugAssert(static_cast<u32>(x) < VRAM_WIDTH && static_cast<u32>(y) < VRAM_HEIGHT);
SetPixel(static_cast<u32>(x), static_cast<u32>(y), color | cmd->params.GetMaskOR()); SetPixel(static_cast<u32>(x), static_cast<u32>(y), color | cmd->GetMaskOR());
} }
#ifndef USE_VECTOR #ifndef USE_VECTOR
@ -237,7 +237,8 @@ static void DrawRectangle(const GPUBackendDrawRectangleCommand* cmd)
{ {
const s32 y = origin_y + static_cast<s32>(offset_y); const s32 y = origin_y + static_cast<s32>(offset_y);
if (y < static_cast<s32>(g_drawing_area.top) || y > static_cast<s32>(g_drawing_area.bottom) || if (y < static_cast<s32>(g_drawing_area.top) || y > static_cast<s32>(g_drawing_area.bottom) ||
(cmd->params.interlaced_rendering && cmd->params.active_line_lsb == (Truncate8(static_cast<u32>(y)) & 1u))) (cmd->interlaced_rendering &&
cmd->active_line_lsb == ConvertToBoolUnchecked(Truncate8(static_cast<u32>(y)) & 1u)))
{ {
continue; continue;
} }
@ -488,8 +489,8 @@ struct PixelVectors
clip_left = GSVectorNi(g_drawing_area.left); clip_left = GSVectorNi(g_drawing_area.left);
clip_right = GSVectorNi(g_drawing_area.right); clip_right = GSVectorNi(g_drawing_area.right);
mask_and = GSVectorNi(cmd->params.GetMaskAND()); mask_and = GSVectorNi(cmd->GetMaskAND());
mask_or = GSVectorNi(cmd->params.GetMaskOR()); mask_or = GSVectorNi(cmd->GetMaskOR());
if constexpr (texture_enable) if constexpr (texture_enable)
{ {
@ -505,10 +506,11 @@ struct PixelVectors
} // namespace } // namespace
template<bool texture_enable, bool raw_texture_enable, bool transparency_enable> template<bool texture_enable, bool raw_texture_enable, bool transparency_enable>
ALWAYS_INLINE_RELEASE static void ALWAYS_INLINE_RELEASE static void ShadePixel(const PixelVectors<texture_enable>& RESTRICT pv,
ShadePixel(const PixelVectors<texture_enable>& pv, GPUTextureMode texture_mode, GPUTransparencyMode transparency_mode, GPUTextureMode texture_mode, GPUTransparencyMode transparency_mode,
u32 start_x, u32 y, GSVectorNi vertex_color_rg, GSVectorNi vertex_color_ba, GSVectorNi texcoord_x, u32 start_x, u32 y, GSVectorNi vertex_color_rg, GSVectorNi vertex_color_ba,
GSVectorNi texcoord_y, GSVectorNi preserve_mask, GSVectorNi dither) GSVectorNi texcoord_x, GSVectorNi texcoord_y, GSVectorNi preserve_mask,
GSVectorNi dither)
{ {
static constexpr GSVectorNi coord_mask_x = GSVectorNi::cxpr(VRAM_WIDTH_MASK); static constexpr GSVectorNi coord_mask_x = GSVectorNi::cxpr(VRAM_WIDTH_MASK);
static constexpr GSVectorNi coord_mask_y = GSVectorNi::cxpr(VRAM_HEIGHT_MASK); static constexpr GSVectorNi coord_mask_y = GSVectorNi::cxpr(VRAM_HEIGHT_MASK);
@ -692,7 +694,7 @@ ShadePixel(const PixelVectors<texture_enable>& pv, GPUTextureMode texture_mode,
} }
template<bool texture_enable, bool raw_texture_enable, bool transparency_enable> template<bool texture_enable, bool raw_texture_enable, bool transparency_enable>
static void DrawRectangle(const GPUBackendDrawRectangleCommand* cmd) static void DrawRectangle(const GPUBackendDrawRectangleCommand* RESTRICT cmd)
{ {
const s32 origin_x = cmd->x; const s32 origin_x = cmd->x;
const s32 origin_y = cmd->y; const s32 origin_y = cmd->y;
@ -717,7 +719,8 @@ static void DrawRectangle(const GPUBackendDrawRectangleCommand* cmd)
{ {
const s32 y = origin_y + static_cast<s32>(offset_y); const s32 y = origin_y + static_cast<s32>(offset_y);
if (y >= static_cast<s32>(g_drawing_area.top) && y <= static_cast<s32>(g_drawing_area.bottom) && if (y >= static_cast<s32>(g_drawing_area.top) && y <= static_cast<s32>(g_drawing_area.bottom) &&
(!cmd->params.interlaced_rendering || cmd->params.active_line_lsb != (Truncate8(static_cast<u32>(y)) & 1u))) (!cmd->interlaced_rendering ||
cmd->active_line_lsb != ConvertToBoolUnchecked(Truncate8(static_cast<u32>(y)) & 1u)))
{ {
const s32 draw_y = (y & VRAM_HEIGHT_MASK); const s32 draw_y = (y & VRAM_HEIGHT_MASK);
@ -763,8 +766,8 @@ static void DrawRectangle(const GPUBackendDrawRectangleCommand* cmd)
// TODO: Vectorize line draw. // TODO: Vectorize line draw.
template<bool shading_enable, bool transparency_enable> template<bool shading_enable, bool transparency_enable>
static void DrawLine(const GPUBackendDrawLineCommand* cmd, const GPUBackendDrawLineCommand::Vertex* p0, static void DrawLine(const GPUBackendDrawCommand* RESTRICT cmd, const GPUBackendDrawLineCommand::Vertex* RESTRICT p0,
const GPUBackendDrawLineCommand::Vertex* p1) const GPUBackendDrawLineCommand::Vertex* RESTRICT p1)
{ {
static constexpr u32 XY_SHIFT = 32; static constexpr u32 XY_SHIFT = 32;
static constexpr u32 RGB_SHIFT = 12; static constexpr u32 RGB_SHIFT = 12;
@ -817,7 +820,8 @@ static void DrawLine(const GPUBackendDrawLineCommand* cmd, const GPUBackendDrawL
const s32 x = unfp_xy(curx); const s32 x = unfp_xy(curx);
const s32 y = unfp_xy(cury); const s32 y = unfp_xy(cury);
if ((!cmd->params.interlaced_rendering || cmd->params.active_line_lsb != (Truncate8(static_cast<u32>(y)) & 1u)) && if ((!cmd->interlaced_rendering ||
cmd->active_line_lsb != ConvertToBoolUnchecked(Truncate8(static_cast<u32>(y)) & 1u)) &&
x >= static_cast<s32>(g_drawing_area.left) && x <= static_cast<s32>(g_drawing_area.right) && x >= static_cast<s32>(g_drawing_area.left) && x <= static_cast<s32>(g_drawing_area.right) &&
y >= static_cast<s32>(g_drawing_area.top) && y <= static_cast<s32>(g_drawing_area.bottom)) y >= static_cast<s32>(g_drawing_area.top) && y <= static_cast<s32>(g_drawing_area.bottom))
{ {
@ -968,8 +972,8 @@ struct TrianglePart
#ifndef USE_VECTOR #ifndef USE_VECTOR
template<bool shading_enable, bool texture_enable, bool raw_texture_enable, bool transparency_enable> template<bool shading_enable, bool texture_enable, bool raw_texture_enable, bool transparency_enable>
static void DrawSpan(const GPUBackendDrawPolygonCommand* cmd, s32 y, s32 x_start, s32 x_bound, UVStepper uv, static void DrawSpan(const GPUBackendDrawCommand* RESTRICT cmd, s32 y, s32 x_start, s32 x_bound, UVStepper uv,
const UVSteps& uvstep, RGBStepper rgb, const RGBSteps& rgbstep) const UVSteps& RESTRICT uvstep, RGBStepper rgb, const RGBSteps& RESTRICT rgbstep)
{ {
s32 width = x_bound - x_start; s32 width = x_bound - x_start;
s32 current_x = TruncateGPUVertexPosition(x_start); s32 current_x = TruncateGPUVertexPosition(x_start);
@ -1008,9 +1012,10 @@ static void DrawSpan(const GPUBackendDrawPolygonCommand* cmd, s32 y, s32 x_start
} }
template<bool shading_enable, bool texture_enable, bool raw_texture_enable, bool transparency_enable> template<bool shading_enable, bool texture_enable, bool raw_texture_enable, bool transparency_enable>
ALWAYS_INLINE_RELEASE static void DrawTrianglePart(const GPUBackendDrawPolygonCommand* cmd, const TrianglePart& tp, ALWAYS_INLINE_RELEASE static void DrawTrianglePart(const GPUBackendDrawCommand* RESTRICT cmd,
const UVStepper& uv, const UVSteps& uvstep, const RGBStepper& rgb, const TrianglePart& RESTRICT tp, const UVStepper& RESTRICT uv,
const RGBSteps& rgbstep) const UVSteps& RESTRICT uvstep, const RGBStepper& RESTRICT rgb,
const RGBSteps& RESTRICT rgbstep)
{ {
static constexpr auto unfp_xy = [](s64 xfp) -> s32 { return static_cast<s32>(static_cast<u64>(xfp) >> 32); }; static constexpr auto unfp_xy = [](s64 xfp) -> s32 { return static_cast<s32>(static_cast<u64>(xfp) >> 32); };
@ -1051,7 +1056,8 @@ ALWAYS_INLINE_RELEASE static void DrawTrianglePart(const GPUBackendDrawPolygonCo
lrgb.StepY<true>(rgbstep); lrgb.StepY<true>(rgbstep);
if (y > static_cast<s32>(g_drawing_area.bottom) || if (y > static_cast<s32>(g_drawing_area.bottom) ||
(cmd->params.interlaced_rendering && cmd->params.active_line_lsb == (static_cast<u32>(current_y) & 1u))) (cmd->interlaced_rendering &&
cmd->active_line_lsb == ConvertToBoolUnchecked(static_cast<u32>(current_y) & 1u)))
{ {
continue; continue;
} }
@ -1082,7 +1088,8 @@ ALWAYS_INLINE_RELEASE static void DrawTrianglePart(const GPUBackendDrawPolygonCo
break; break;
} }
if (y >= static_cast<s32>(g_drawing_area.top) && if (y >= static_cast<s32>(g_drawing_area.top) &&
(!cmd->params.interlaced_rendering || cmd->params.active_line_lsb != (static_cast<u32>(current_y) & 1u))) (!cmd->interlaced_rendering ||
cmd->active_line_lsb != ConvertToBoolUnchecked(static_cast<u32>(current_y) & 1u)))
{ {
DrawSpan<shading_enable, texture_enable, raw_texture_enable, transparency_enable>( DrawSpan<shading_enable, texture_enable, raw_texture_enable, transparency_enable>(
cmd, y & VRAM_HEIGHT_MASK, unfp_xy(left_x), unfp_xy(right_x), luv, uvstep, lrgb, rgbstep); cmd, y & VRAM_HEIGHT_MASK, unfp_xy(left_x), unfp_xy(right_x), luv, uvstep, lrgb, rgbstep);
@ -1145,9 +1152,10 @@ struct TriangleVectors : PixelVectors<texture_enable>
} // namespace } // namespace
template<bool shading_enable, bool texture_enable, bool raw_texture_enable, bool transparency_enable> template<bool shading_enable, bool texture_enable, bool raw_texture_enable, bool transparency_enable>
ALWAYS_INLINE_RELEASE static void DrawSpan(const GPUBackendDrawPolygonCommand* cmd, s32 y, s32 x_start, s32 x_bound, ALWAYS_INLINE_RELEASE static void DrawSpan(const GPUBackendDrawCommand* RESTRICT cmd, s32 y, s32 x_start, s32 x_bound,
UVStepper uv, const UVSteps& uvstep, RGBStepper rgb, const RGBSteps& rgbstep, UVStepper uv, const UVSteps& RESTRICT uvstep, RGBStepper rgb,
const TriangleVectors<shading_enable, texture_enable>& tv) const RGBSteps& RESTRICT rgbstep,
const TriangleVectors<shading_enable, texture_enable>& RESTRICT tv)
{ {
s32 width = x_bound - x_start; s32 width = x_bound - x_start;
s32 current_x = TruncateGPUVertexPosition(x_start); s32 current_x = TruncateGPUVertexPosition(x_start);
@ -1195,7 +1203,7 @@ ALWAYS_INLINE_RELEASE static void DrawSpan(const GPUBackendDrawPolygonCommand* c
dv = GSVectorNi::zero(); dv = GSVectorNi::zero();
} }
const GSVectorNi dither = cmd->draw_mode.dither_enable ? const GSVectorNi dither = cmd->dither_enable ?
GSVectorNi::broadcast128<false>( GSVectorNi::broadcast128<false>(
&VECTOR_DITHER_MATRIX[static_cast<u32>(y) & 3][(static_cast<u32>(current_x) & 3) * 2]) : &VECTOR_DITHER_MATRIX[static_cast<u32>(y) & 3][(static_cast<u32>(current_x) & 3) * 2]) :
GSVectorNi::zero(); GSVectorNi::zero();
@ -1250,9 +1258,10 @@ ALWAYS_INLINE_RELEASE static void DrawSpan(const GPUBackendDrawPolygonCommand* c
} }
template<bool shading_enable, bool texture_enable, bool raw_texture_enable, bool transparency_enable> template<bool shading_enable, bool texture_enable, bool raw_texture_enable, bool transparency_enable>
ALWAYS_INLINE_RELEASE static void DrawTrianglePart(const GPUBackendDrawPolygonCommand* cmd, const TrianglePart& tp, ALWAYS_INLINE_RELEASE static void DrawTrianglePart(const GPUBackendDrawCommand* RESTRICT cmd,
const UVStepper& uv, const UVSteps& uvstep, const RGBStepper& rgb, const TrianglePart& RESTRICT tp, const UVStepper& RESTRICT uv,
const RGBSteps& rgbstep) const UVSteps& RESTRICT uvstep, const RGBStepper& RESTRICT rgb,
const RGBSteps& RESTRICT rgbstep)
{ {
static constexpr auto unfp_xy = [](s64 xfp) -> s32 { return static_cast<s32>(static_cast<u64>(xfp) >> 32); }; static constexpr auto unfp_xy = [](s64 xfp) -> s32 { return static_cast<s32>(static_cast<u64>(xfp) >> 32); };
@ -1295,7 +1304,8 @@ ALWAYS_INLINE_RELEASE static void DrawTrianglePart(const GPUBackendDrawPolygonCo
lrgb.StepY<true>(rgbstep); lrgb.StepY<true>(rgbstep);
if (y > static_cast<s32>(g_drawing_area.bottom) || if (y > static_cast<s32>(g_drawing_area.bottom) ||
(cmd->params.interlaced_rendering && cmd->params.active_line_lsb == (static_cast<u32>(current_y) & 1u))) (cmd->interlaced_rendering &&
cmd->active_line_lsb == ConvertToBoolUnchecked(static_cast<u32>(current_y) & 1u)))
{ {
continue; continue;
} }
@ -1328,7 +1338,8 @@ ALWAYS_INLINE_RELEASE static void DrawTrianglePart(const GPUBackendDrawPolygonCo
break; break;
} }
if (y >= static_cast<s32>(g_drawing_area.top) && if (y >= static_cast<s32>(g_drawing_area.top) &&
(!cmd->params.interlaced_rendering || cmd->params.active_line_lsb != (static_cast<u32>(current_y) & 1u))) (!cmd->interlaced_rendering ||
cmd->active_line_lsb != ConvertToBoolUnchecked(static_cast<u32>(current_y) & 1u)))
{ {
DrawSpan<shading_enable, texture_enable, raw_texture_enable, transparency_enable>( DrawSpan<shading_enable, texture_enable, raw_texture_enable, transparency_enable>(
cmd, y & VRAM_HEIGHT_MASK, unfp_xy(left_x), unfp_xy(right_x), luv, uvstep, lrgb, rgbstep, tv); cmd, y & VRAM_HEIGHT_MASK, unfp_xy(left_x), unfp_xy(right_x), luv, uvstep, lrgb, rgbstep, tv);
@ -1349,13 +1360,15 @@ ALWAYS_INLINE_RELEASE static void DrawTrianglePart(const GPUBackendDrawPolygonCo
#endif // USE_VECTOR #endif // USE_VECTOR
template<bool shading_enable, bool texture_enable, bool raw_texture_enable, bool transparency_enable> template<bool shading_enable, bool texture_enable, bool raw_texture_enable, bool transparency_enable>
static void DrawTriangle(const GPUBackendDrawPolygonCommand* cmd, const GPUBackendDrawPolygonCommand::Vertex* v0, static void DrawTriangle(const GPUBackendDrawCommand* RESTRICT cmd,
const GPUBackendDrawPolygonCommand::Vertex* v1, const GPUBackendDrawPolygonCommand::Vertex* v2) const GPUBackendDrawPolygonCommand::Vertex* RESTRICT v0,
const GPUBackendDrawPolygonCommand::Vertex* RESTRICT v1,
const GPUBackendDrawPolygonCommand::Vertex* RESTRICT v2)
{ {
#ifdef CHECK_VECTOR #ifdef CHECK_VECTOR
const GPUBackendDrawPolygonCommand::Vertex* orig_v0 = v0; const GPUBackendDrawPolygonCommand::Vertex* RESTRICT orig_v0 = v0;
const GPUBackendDrawPolygonCommand::Vertex* orig_v1 = v1; const GPUBackendDrawPolygonCommand::Vertex* RESTRICT orig_v1 = v1;
const GPUBackendDrawPolygonCommand::Vertex* orig_v2 = v2; const GPUBackendDrawPolygonCommand::Vertex* RESTRICT orig_v2 = v2;
#endif #endif
// Sort vertices so that v0 is the top vertex, v1 is the bottom vertex, and v2 is the side vertex. // Sort vertices so that v0 is the top vertex, v1 is the bottom vertex, and v2 is the side vertex.
@ -1410,8 +1423,8 @@ static void DrawTriangle(const GPUBackendDrawPolygonCommand* cmd, const GPUBacke
const u32 ofi = BoolToUInt32(!right_facing); const u32 ofi = BoolToUInt32(!right_facing);
TrianglePart triparts[2]; TrianglePart triparts[2];
TrianglePart& tpo = triparts[vo]; TrianglePart& RESTRICT tpo = triparts[vo];
TrianglePart& tpp = triparts[vo ^ 1]; TrianglePart& RESTRICT tpp = triparts[vo ^ 1];
tpo.start_y = vertices[0 ^ vo]->y; tpo.start_y = vertices[0 ^ vo]->y;
tpo.end_y = vertices[1 ^ vo]->y; tpo.end_y = vertices[1 ^ vo]->y;
tpp.start_y = vertices[1 ^ vp]->y; tpp.start_y = vertices[1 ^ vp]->y;
@ -1462,7 +1475,7 @@ static void DrawTriangle(const GPUBackendDrawPolygonCommand* cmd, const GPUBacke
// Undo the start of the vertex, so that when we add the offset for each line, it starts at the beginning value. // Undo the start of the vertex, so that when we add the offset for each line, it starts at the beginning value.
UVStepper uv; UVStepper uv;
RGBStepper rgb; RGBStepper rgb;
const GPUBackendDrawPolygonCommand::Vertex* top_left_vertex = vertices[tl]; const GPUBackendDrawPolygonCommand::Vertex* RESTRICT top_left_vertex = vertices[tl];
if constexpr (texture_enable) if constexpr (texture_enable)
{ {
uv.Init(top_left_vertex->u, top_left_vertex->v); uv.Init(top_left_vertex->u, top_left_vertex->v);
@ -1535,7 +1548,7 @@ static void FillVRAMImpl(u32 x, u32 y, u32 width, u32 height, u32 color, bool in
{ {
const u32 row = (y + yoffs) % VRAM_HEIGHT; const u32 row = (y + yoffs) % VRAM_HEIGHT;
u16* row_ptr = &g_vram[row * VRAM_WIDTH + x]; u16* RESTRICT row_ptr = &g_vram[row * VRAM_WIDTH + x];
u32 xoffs = 0; u32 xoffs = 0;
for (; xoffs < aligned_width; xoffs += vector_width, row_ptr += vector_width) for (; xoffs < aligned_width; xoffs += vector_width, row_ptr += vector_width)
GSVector4i::store<false>(row_ptr, fill); GSVector4i::store<false>(row_ptr, fill);
@ -1556,7 +1569,7 @@ static void FillVRAMImpl(u32 x, u32 y, u32 width, u32 height, u32 color, bool in
if ((row & u32(1)) == active_field) if ((row & u32(1)) == active_field)
continue; continue;
u16* row_ptr = &g_vram[row * VRAM_WIDTH + x]; u16* RESTRICT row_ptr = &g_vram[row * VRAM_WIDTH + x];
u32 xoffs = 0; u32 xoffs = 0;
for (; xoffs < aligned_width; xoffs += vector_width, row_ptr += vector_width) for (; xoffs < aligned_width; xoffs += vector_width, row_ptr += vector_width)
GSVector4i::store<false>(row_ptr, fill); GSVector4i::store<false>(row_ptr, fill);
@ -1572,7 +1585,7 @@ static void FillVRAMImpl(u32 x, u32 y, u32 width, u32 height, u32 color, bool in
if ((row & u32(1)) == active_field) if ((row & u32(1)) == active_field)
continue; continue;
u16* row_ptr = &g_vram[row * VRAM_WIDTH]; u16* RESTRICT row_ptr = &g_vram[row * VRAM_WIDTH];
for (u32 xoffs = 0; xoffs < width; xoffs++) for (u32 xoffs = 0; xoffs < width; xoffs++)
{ {
const u32 col = (x + xoffs) % VRAM_WIDTH; const u32 col = (x + xoffs) % VRAM_WIDTH;
@ -1586,7 +1599,7 @@ static void FillVRAMImpl(u32 x, u32 y, u32 width, u32 height, u32 color, bool in
for (u32 yoffs = 0; yoffs < height; yoffs++) for (u32 yoffs = 0; yoffs < height; yoffs++)
{ {
const u32 row = (y + yoffs) % VRAM_HEIGHT; const u32 row = (y + yoffs) % VRAM_HEIGHT;
u16* row_ptr = &g_vram[row * VRAM_WIDTH]; u16* RESTRICT row_ptr = &g_vram[row * VRAM_WIDTH];
for (u32 xoffs = 0; xoffs < width; xoffs++) for (u32 xoffs = 0; xoffs < width; xoffs++)
{ {
const u32 col = (x + xoffs) % VRAM_WIDTH; const u32 col = (x + xoffs) % VRAM_WIDTH;
@ -1615,7 +1628,7 @@ static void FillVRAMImpl(u32 x, u32 y, u32 width, u32 height, u32 color, bool in
if ((row & u32(1)) == active_field) if ((row & u32(1)) == active_field)
continue; continue;
u16* row_ptr = &g_vram[row * VRAM_WIDTH]; u16* RESTRICT row_ptr = &g_vram[row * VRAM_WIDTH];
for (u32 xoffs = 0; xoffs < width; xoffs++) for (u32 xoffs = 0; xoffs < width; xoffs++)
{ {
const u32 col = (x + xoffs) % VRAM_WIDTH; const u32 col = (x + xoffs) % VRAM_WIDTH;
@ -1628,7 +1641,7 @@ static void FillVRAMImpl(u32 x, u32 y, u32 width, u32 height, u32 color, bool in
for (u32 yoffs = 0; yoffs < height; yoffs++) for (u32 yoffs = 0; yoffs < height; yoffs++)
{ {
const u32 row = (y + yoffs) % VRAM_HEIGHT; const u32 row = (y + yoffs) % VRAM_HEIGHT;
u16* row_ptr = &g_vram[row * VRAM_WIDTH]; u16* RESTRICT row_ptr = &g_vram[row * VRAM_WIDTH];
for (u32 xoffs = 0; xoffs < width; xoffs++) for (u32 xoffs = 0; xoffs < width; xoffs++)
{ {
const u32 col = (x + xoffs) % VRAM_WIDTH; const u32 col = (x + xoffs) % VRAM_WIDTH;
@ -1639,12 +1652,13 @@ static void FillVRAMImpl(u32 x, u32 y, u32 width, u32 height, u32 color, bool in
#endif #endif
} }
static void WriteVRAMImpl(u32 x, u32 y, u32 width, u32 height, const void* data, bool set_mask, bool check_mask) static void WriteVRAMImpl(u32 x, u32 y, u32 width, u32 height, const void* RESTRICT data, bool set_mask,
bool check_mask)
{ {
// Fast path when the copy is not oversized. // Fast path when the copy is not oversized.
if ((x + width) <= VRAM_WIDTH && (y + height) <= VRAM_HEIGHT && !set_mask && !check_mask) if ((x + width) <= VRAM_WIDTH && (y + height) <= VRAM_HEIGHT && !set_mask && !check_mask)
{ {
const u16* src_ptr = static_cast<const u16*>(data); const u16* RESTRICT src_ptr = static_cast<const u16*>(data);
u16* dst_ptr = &g_vram[y * VRAM_WIDTH + x]; u16* dst_ptr = &g_vram[y * VRAM_WIDTH + x];
for (u32 yoffs = 0; yoffs < height; yoffs++) for (u32 yoffs = 0; yoffs < height; yoffs++)
{ {
@ -1657,7 +1671,7 @@ static void WriteVRAMImpl(u32 x, u32 y, u32 width, u32 height, const void* data,
{ {
// Slow path when we need to handle wrap-around. // Slow path when we need to handle wrap-around.
// During transfer/render operations, if ((dst_pixel & mask_and) == 0) { pixel = src_pixel | mask_or } // During transfer/render operations, if ((dst_pixel & mask_and) == 0) { pixel = src_pixel | mask_or }
const u16* src_ptr = static_cast<const u16*>(data); const u16* RESTRICT src_ptr = static_cast<const u16*>(data);
const u16 mask_and = check_mask ? 0x8000u : 0x0000u; const u16 mask_and = check_mask ? 0x8000u : 0x0000u;
const u16 mask_or = set_mask ? 0x8000u : 0x0000u; const u16 mask_or = set_mask ? 0x8000u : 0x0000u;
@ -1706,7 +1720,7 @@ static void WriteVRAMImpl(u32 x, u32 y, u32 width, u32 height, const void* data,
for (; col < width;) for (; col < width;)
{ {
// TODO: Handle unaligned reads... // TODO: Handle unaligned reads...
u16* pixel_ptr = &dst_row_ptr[(x + col++) % VRAM_WIDTH]; u16* RESTRICT pixel_ptr = &dst_row_ptr[(x + col++) % VRAM_WIDTH];
if (((*pixel_ptr) & mask_and) == 0) if (((*pixel_ptr) & mask_and) == 0)
*pixel_ptr = *(src_ptr++) | mask_or; *pixel_ptr = *(src_ptr++) | mask_or;
} }

1360
src/core/gpu_thread.cpp Normal file

File diff suppressed because it is too large Load Diff

106
src/core/gpu_thread.h Normal file
View File

@ -0,0 +1,106 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once
#include "common/types.h"
#include <functional>
#include <optional>
class Error;
struct WindowInfo;
namespace Threading {
class ThreadHandle;
}
enum class RenderAPI : u8;
enum class GPUVSyncMode : u8;
enum class GPURenderer : u8;
enum class GPUBackendCommandType : u8;
class GPUBackend;
struct GPUThreadCommand;
struct GPUBackendUpdateDisplayCommand;
namespace GPUThread {
using AsyncCallType = std::function<void()>;
using AsyncBackendCallType = std::function<void(GPUBackend*)>;
enum class RunIdleReason : u8
{
NoGPUBackend = (1 << 0),
SystemPaused = (1 << 1),
FullscreenUIActive = (1 << 2),
LoadingScreenActive = (1 << 3),
};
/// Starts Big Picture UI.
bool StartFullscreenUI(bool fullscreen, Error* error);
bool IsFullscreenUIRequested();
void StopFullscreenUI();
/// Backend control.
std::optional<GPURenderer> GetRequestedRenderer();
bool CreateGPUBackend(std::string serial, GPURenderer renderer, bool upload_vram, bool fullscreen,
bool force_recreate_device, Error* error);
void DestroyGPUBackend();
bool HasGPUBackend();
bool IsGPUBackendRequested();
void SetGameSerial(std::string serial);
/// Re-presents the current frame. Call when things like window resizes happen to re-display
/// the current frame with the correct proportions. Should only be called from the CPU thread.
void PresentCurrentFrame();
/// Handles fullscreen transitions and such.
void UpdateDisplayWindow(bool fullscreen);
/// Called when the window is resized.
void ResizeDisplayWindow(s32 width, s32 height, float scale);
/// Access to main window size from CPU thread.
const WindowInfo& GetRenderWindowInfo();
void UpdateSettings(bool gpu_settings_changed, bool device_settings_changed);
bool IsOnThread();
bool IsUsingThread();
void RunOnThread(AsyncCallType func);
void RunOnBackend(AsyncBackendCallType func, bool sync, bool spin_or_wake);
void SetVSync(GPUVSyncMode mode, bool allow_present_throttle);
// Should only be called on the GPU thread.
bool GetRunIdleReason(RunIdleReason reason);
void SetRunIdleReason(RunIdleReason reason, bool enabled);
bool IsRunningIdle();
bool IsSystemPaused();
const std::string& GetGameSerial();
GPUThreadCommand* AllocateCommand(GPUBackendCommandType command, u32 size);
void PushCommand(GPUThreadCommand* cmd);
void PushCommandAndWakeThread(GPUThreadCommand* cmd);
void PushCommandAndSync(GPUThreadCommand* cmd, bool spin);
void SyncGPUThread(bool spin);
// NOTE: Only called by GPUBackend
namespace Internal {
const Threading::ThreadHandle& GetThreadHandle();
void ProcessStartup();
void SetThreadEnabled(bool enabled);
void DoRunIdle();
void RequestShutdown();
void GPUThreadEntryPoint();
void PresentFrame(bool allow_skip_present, u64 present_time);
void RestoreContextAfterPresent();
} // namespace Internal
} // namespace GPUThread
namespace Host {
/// Called when the pause state changes, or fullscreen UI opens.
void OnGPUThreadRunIdleChanged(bool is_active);
} // namespace Host

View File

@ -0,0 +1,335 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once
#include "gpu_types.h"
#include "common/align.h"
#include <functional>
#include <string>
#include <vector>
class Error;
enum class GPUVSyncMode : u8;
class MediaCapture;
class StateWrapper;
class GPUBackend;
namespace System {
struct MemorySaveState;
}
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4200) // warning C4200: nonstandard extension used: zero-sized array in struct/union
#endif
enum class GPUBackendCommandType : u8
{
Wraparound,
AsyncCall,
AsyncBackendCall,
Reconfigure,
Shutdown,
ClearVRAM,
ClearDisplay,
UpdateDisplay,
SubmitFrame,
BufferSwapped,
LoadState,
LoadMemoryState,
SaveMemoryState,
ReadVRAM,
FillVRAM,
UpdateVRAM,
CopyVRAM,
SetDrawingArea,
UpdateCLUT,
ClearCache,
DrawPolygon,
DrawPrecisePolygon,
DrawRectangle,
DrawLine,
DrawPreciseLine,
};
struct GPUThreadCommand
{
u32 size;
GPUBackendCommandType type;
static constexpr u32 AlignCommandSize(u32 size)
{
// Ensure size is a multiple of 8 (minimum data size) so we don't end up with an unaligned command.
// NOTE: If we ever end up putting vectors in the command packets, this should be raised.
constexpr u32 COMMAND_QUEUE_ALLOCATION_ALIGNMENT = 8;
return Common::AlignUpPow2(size, COMMAND_QUEUE_ALLOCATION_ALIGNMENT);
}
};
struct GPUThreadReconfigureCommand : public GPUThreadCommand
{
Error* error_ptr;
bool* out_result;
std::string game_serial;
std::optional<GPURenderer> renderer;
std::optional<bool> fullscreen;
std::optional<bool> start_fullscreen_ui;
GPUVSyncMode vsync_mode;
bool allow_present_throttle;
bool force_recreate_device;
bool upload_vram;
};
struct GPUThreadAsyncCallCommand : public GPUThreadCommand
{
GPUThreadAsyncCallCommand(std::function<void()> func_) : func(std::move(func_)) {}
std::function<void()> func;
};
struct GPUThreadAsyncBackendCallCommand : public GPUThreadCommand
{
GPUThreadAsyncBackendCallCommand(std::function<void(GPUBackend*)> func_) : func(std::move(func_)) {}
std::function<void(GPUBackend*)> func;
};
struct GPUBackendLoadStateCommand : public GPUThreadCommand
{
u16 vram_data[VRAM_WIDTH * VRAM_HEIGHT];
u16 clut_data[GPU_CLUT_SIZE];
u32 texture_cache_state_version;
u32 texture_cache_state_size;
u8 texture_cache_state[0]; // texture_cache_state_size
};
struct GPUBackendDoMemoryStateCommand : public GPUThreadCommand
{
System::MemorySaveState* memory_save_state;
};
struct GPUBackendFramePresentationParameters
{
u32 frame_number;
u32 internal_frame_number;
u64 present_time;
MediaCapture* media_capture;
union
{
u8 bits;
BitField<u16, bool, 0, 1> allow_present_skip;
BitField<u16, bool, 1, 1> present_frame;
BitField<u16, bool, 2, 1> update_performance_counters;
};
};
struct GPUBackendUpdateDisplayCommand : public GPUThreadCommand
{
u16 display_width;
u16 display_height;
u16 display_origin_left;
u16 display_origin_top;
u16 display_vram_left;
u16 display_vram_top;
u16 display_vram_width;
u16 display_vram_height;
float display_pixel_aspect_ratio;
u16 X; // TODO: Can we get rid of this?
bool interlaced_display_enabled : 1;
bool interlaced_display_field : 1;
bool interlaced_display_interleaved : 1;
bool display_24bit : 1;
bool display_disabled : 1;
bool submit_frame : 1;
bool : 2;
GPUBackendFramePresentationParameters frame;
};
// Only used for runahead.
struct GPUBackendSubmitFrameCommand : public GPUThreadCommand
{
GPUBackendFramePresentationParameters frame;
};
struct GPUBackendReadVRAMCommand : public GPUThreadCommand
{
u16 x;
u16 y;
u16 width;
u16 height;
};
struct GPUBackendFillVRAMCommand : public GPUThreadCommand
{
u16 x;
u16 y;
u16 width;
u16 height;
u32 color;
bool interlaced_rendering;
u8 active_line_lsb;
};
struct GPUBackendUpdateVRAMCommand : public GPUThreadCommand
{
u16 x;
u16 y;
u16 width;
u16 height;
bool set_mask_while_drawing;
bool check_mask_before_draw;
u16 data[0];
};
struct GPUBackendCopyVRAMCommand : public GPUThreadCommand
{
u16 src_x;
u16 src_y;
u16 dst_x;
u16 dst_y;
u16 width;
u16 height;
bool set_mask_while_drawing;
bool check_mask_before_draw;
};
struct GPUBackendSetDrawingAreaCommand : public GPUThreadCommand
{
GPUDrawingArea new_area;
};
struct GPUBackendUpdateCLUTCommand : public GPUThreadCommand
{
GPUTexturePaletteReg reg;
bool clut_is_8bit;
};
struct GPUBackendDrawCommand : public GPUThreadCommand
{
bool interlaced_rendering : 1;
/// Returns 0 if the currently-displayed field is on an even line in VRAM, otherwise 1.
bool active_line_lsb : 1;
bool set_mask_while_drawing : 1;
bool check_mask_before_draw : 1;
bool texture_enable : 1;
bool raw_texture_enable : 1;
bool transparency_enable : 1;
bool shading_enable : 1;
bool quad_polygon : 1;
bool dither_enable : 1;
bool valid_w : 1; // only used for precise polygons
// During transfer/render operations, if ((dst_pixel & mask_and) == 0) { pixel = src_pixel | mask_or }
ALWAYS_INLINE u16 GetMaskAND() const { return check_mask_before_draw ? 0x8000 : 0x0000; }
ALWAYS_INLINE u16 GetMaskOR() const { return set_mask_while_drawing ? 0x8000 : 0x0000; }
u16 num_vertices;
GPUDrawModeReg draw_mode;
GPUTexturePaletteReg palette;
GPUTextureWindow window;
};
struct GPUBackendDrawPolygonCommand : public GPUBackendDrawCommand
{
struct Vertex
{
s32 x, y;
union
{
struct
{
u8 r, g, b, a;
};
u32 color;
};
union
{
struct
{
u8 u, v;
};
u16 texcoord;
};
};
Vertex vertices[0];
};
struct GPUBackendDrawPrecisePolygonCommand : public GPUBackendDrawCommand
{
GPUBackendDrawCommand params;
struct Vertex
{
float x, y, w;
s32 native_x, native_y;
u32 color;
u16 texcoord;
};
Vertex vertices[0];
};
struct GPUBackendDrawRectangleCommand : public GPUBackendDrawCommand
{
u16 width, height;
u16 texcoord;
s32 x, y;
u32 color;
};
struct GPUBackendDrawLineCommand : public GPUBackendDrawCommand
{
struct Vertex
{
s32 x, y;
union
{
struct
{
u8 r, g, b, a;
};
u32 color;
};
ALWAYS_INLINE void Set(s32 x_, s32 y_, u32 color_)
{
x = x_;
y = y_;
color = color_;
}
};
Vertex vertices[0];
};
struct GPUBackendDrawPreciseLineCommand : public GPUBackendDrawCommand
{
struct Vertex
{
float x, y, w;
s32 native_x, native_y;
u32 color;
};
Vertex vertices[0];
};
#ifdef _MSC_VER
#pragma warning(pop)
#endif

View File

@ -12,7 +12,6 @@
#include "common/gsvector.h" #include "common/gsvector.h"
#include <array> #include <array>
#include <string>
enum : u32 enum : u32
{ {
@ -405,12 +404,17 @@ union GPUTexturePaletteReg
ALWAYS_INLINE constexpr u32 GetYBase() const { return static_cast<u32>(y); } ALWAYS_INLINE constexpr u32 GetYBase() const { return static_cast<u32>(y); }
}; };
struct GPUTextureWindow union GPUTextureWindow
{ {
u8 and_x; struct
u8 and_y; {
u8 or_x; u8 and_x;
u8 or_y; u8 and_y;
u8 or_x;
u8 or_y;
};
u32 bits;
ALWAYS_INLINE bool operator==(const GPUTextureWindow& rhs) const ALWAYS_INLINE bool operator==(const GPUTextureWindow& rhs) const
{ {
@ -541,182 +545,3 @@ static constexpr s32 DITHER_MATRIX[DITHER_MATRIX_SIZE][DITHER_MATRIX_SIZE] = {{-
{+2, -2, +3, -1}, // row 1 {+2, -2, +3, -1}, // row 1
{-3, +1, -4, +0}, // row 2 {-3, +1, -4, +0}, // row 2
{+3, -1, +2, -2}}; // row 3 {+3, -1, +2, -2}}; // row 3
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4200) // warning C4200: nonstandard extension used: zero-sized array in struct/union
#endif
enum class GPUBackendCommandType : u8
{
Wraparound,
Sync,
FillVRAM,
UpdateVRAM,
CopyVRAM,
SetDrawingArea,
UpdateCLUT,
DrawPolygon,
DrawRectangle,
DrawLine,
};
union GPUBackendCommandParameters
{
u8 bits;
BitField<u8, bool, 0, 1> interlaced_rendering;
/// Returns 0 if the currently-displayed field is on an even line in VRAM, otherwise 1.
BitField<u8, u8, 1, 1> active_line_lsb;
BitField<u8, bool, 2, 1> set_mask_while_drawing;
BitField<u8, bool, 3, 1> check_mask_before_draw;
// During transfer/render operations, if ((dst_pixel & mask_and) == 0) { pixel = src_pixel | mask_or }
u16 GetMaskAND() const
{
// return check_mask_before_draw ? 0x8000 : 0x0000;
return Truncate16((bits << 12) & 0x8000);
}
u16 GetMaskOR() const
{
// return set_mask_while_drawing ? 0x8000 : 0x0000;
return Truncate16((bits << 13) & 0x8000);
}
};
struct GPUBackendCommand
{
u32 size;
GPUBackendCommandType type;
GPUBackendCommandParameters params;
};
struct GPUBackendSyncCommand : public GPUBackendCommand
{
bool allow_sleep;
};
struct GPUBackendFillVRAMCommand : public GPUBackendCommand
{
u16 x;
u16 y;
u16 width;
u16 height;
u32 color;
};
struct GPUBackendUpdateVRAMCommand : public GPUBackendCommand
{
u16 x;
u16 y;
u16 width;
u16 height;
u16 data[0];
};
struct GPUBackendCopyVRAMCommand : public GPUBackendCommand
{
u16 src_x;
u16 src_y;
u16 dst_x;
u16 dst_y;
u16 width;
u16 height;
};
struct GPUBackendSetDrawingAreaCommand : public GPUBackendCommand
{
GPUDrawingArea new_area;
s32 new_clamped_area[4];
};
struct GPUBackendUpdateCLUTCommand : public GPUBackendCommand
{
GPUTexturePaletteReg reg;
bool clut_is_8bit;
};
struct GPUBackendDrawCommand : public GPUBackendCommand
{
GPUDrawModeReg draw_mode;
GPURenderCommand rc;
GPUTexturePaletteReg palette;
GPUTextureWindow window;
};
struct GPUBackendDrawPolygonCommand : public GPUBackendDrawCommand
{
u16 num_vertices;
struct Vertex
{
s32 x, y;
union
{
struct
{
u8 r, g, b, a;
};
u32 color;
};
union
{
struct
{
u8 u, v;
};
u16 texcoord;
};
ALWAYS_INLINE void Set(s32 x_, s32 y_, u32 color_, u16 texcoord_)
{
x = x_;
y = y_;
color = color_;
texcoord = texcoord_;
}
};
Vertex vertices[0];
};
struct GPUBackendDrawRectangleCommand : public GPUBackendDrawCommand
{
s32 x, y;
u16 width, height;
u16 texcoord;
u32 color;
};
struct GPUBackendDrawLineCommand : public GPUBackendDrawCommand
{
u16 num_vertices;
struct Vertex
{
s32 x, y;
union
{
struct
{
u8 r, g, b, a;
};
u32 color;
};
ALWAYS_INLINE void Set(s32 x_, s32 y_, u32 color_)
{
x = x_;
y = y_;
color = color_;
}
};
Vertex vertices[0];
};
#ifdef _MSC_VER
#pragma warning(pop)
#endif

View File

@ -207,12 +207,12 @@ void GunCon::UpdatePosition()
float display_x, display_y; float display_x, display_y;
const auto& [window_x, window_y] = (m_has_relative_binds) ? GetAbsolutePositionFromRelativeAxes() : const auto& [window_x, window_y] = (m_has_relative_binds) ? GetAbsolutePositionFromRelativeAxes() :
InputManager::GetPointerAbsolutePosition(m_cursor_index); InputManager::GetPointerAbsolutePosition(m_cursor_index);
g_gpu->ConvertScreenCoordinatesToDisplayCoordinates(window_x, window_y, &display_x, &display_y); g_gpu.ConvertScreenCoordinatesToDisplayCoordinates(window_x, window_y, &display_x, &display_y);
// are we within the active display area? // are we within the active display area?
u32 tick, line; u32 tick, line;
if (display_x < 0 || display_y < 0 || if (display_x < 0 || display_y < 0 ||
!g_gpu->ConvertDisplayCoordinatesToBeamTicksAndLines(display_x, display_y, m_x_scale, &tick, &line) || !g_gpu.ConvertDisplayCoordinatesToBeamTicksAndLines(display_x, display_y, m_x_scale, &tick, &line) ||
m_shoot_offscreen) m_shoot_offscreen)
{ {
DEBUG_LOG("Lightgun out of range for window coordinates {:.0f},{:.0f}", window_x, window_y); DEBUG_LOG("Lightgun out of range for window coordinates {:.0f},{:.0f}", window_x, window_y);
@ -222,7 +222,7 @@ void GunCon::UpdatePosition()
} }
// 8MHz units for X = 44100*768*11/7 = 53222400 / 8000000 = 6.6528 // 8MHz units for X = 44100*768*11/7 = 53222400 / 8000000 = 6.6528
const double divider = static_cast<double>(g_gpu->GetCRTCFrequency()) / 8000000.0; const double divider = static_cast<double>(g_gpu.GetCRTCFrequency()) / 8000000.0;
m_position_x = static_cast<u16>(static_cast<float>(tick) / static_cast<float>(divider)); m_position_x = static_cast<u16>(static_cast<float>(tick) / static_cast<float>(divider));
m_position_y = static_cast<u16>(line); m_position_y = static_cast<u16>(line);
DEBUG_LOG("Lightgun window coordinates {:.0f},{:.0f} -> tick {} line {} 8mhz ticks {}", display_x, display_y, tick, DEBUG_LOG("Lightgun window coordinates {:.0f},{:.0f} -> tick {} line {} 8mhz ticks {}", display_x, display_y, tick,

View File

@ -2,19 +2,13 @@
// SPDX-License-Identifier: CC-BY-NC-ND-4.0 // SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "host.h" #include "host.h"
#include "fullscreen_ui.h"
#include "gpu.h" #include "gpu.h"
#include "imgui_overlays.h"
#include "shader_cache_version.h"
#include "system.h" #include "system.h"
#include "system_private.h" #include "system_private.h"
#include "scmversion/scmversion.h" #include "scmversion/scmversion.h"
#include "util/compress_helpers.h" #include "util/compress_helpers.h"
#include "util/gpu_device.h"
#include "util/imgui_manager.h"
#include "util/input_manager.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/error.h" #include "common/error.h"
@ -342,181 +336,3 @@ std::string Host::GetHTTPUserAgent()
{ {
return fmt::format("DuckStation for {} ({}) {}", TARGET_OS_STR, CPU_ARCH_STR, g_scm_tag_str); return fmt::format("DuckStation for {} ({}) {}", TARGET_OS_STR, CPU_ARCH_STR, g_scm_tag_str);
} }
bool Host::CreateGPUDevice(RenderAPI api, bool fullscreen, Error* error)
{
DebugAssert(!g_gpu_device);
INFO_LOG("Trying to create a {} GPU device...", GPUDevice::RenderAPIToString(api));
g_gpu_device = GPUDevice::CreateDeviceForAPI(api);
std::optional<GPUDevice::ExclusiveFullscreenMode> fullscreen_mode;
if (fullscreen && g_gpu_device && g_gpu_device->SupportsExclusiveFullscreen())
{
fullscreen_mode =
GPUDevice::ExclusiveFullscreenMode::Parse(Host::GetTinyStringSettingValue("GPU", "FullscreenMode", ""));
}
std::optional<bool> exclusive_fullscreen_control;
if (g_settings.display_exclusive_fullscreen_control != DisplayExclusiveFullscreenControl::Automatic)
{
exclusive_fullscreen_control =
(g_settings.display_exclusive_fullscreen_control == DisplayExclusiveFullscreenControl::Allowed);
}
u32 disabled_features = 0;
if (g_settings.gpu_disable_dual_source_blend)
disabled_features |= GPUDevice::FEATURE_MASK_DUAL_SOURCE_BLEND;
if (g_settings.gpu_disable_framebuffer_fetch)
disabled_features |= GPUDevice::FEATURE_MASK_FRAMEBUFFER_FETCH;
if (g_settings.gpu_disable_texture_buffers)
disabled_features |= GPUDevice::FEATURE_MASK_TEXTURE_BUFFERS;
if (g_settings.gpu_disable_memory_import)
disabled_features |= GPUDevice::FEATURE_MASK_MEMORY_IMPORT;
if (g_settings.gpu_disable_raster_order_views)
disabled_features |= GPUDevice::FEATURE_MASK_RASTER_ORDER_VIEWS;
if (g_settings.gpu_disable_compute_shaders)
disabled_features |= GPUDevice::FEATURE_MASK_COMPUTE_SHADERS;
if (g_settings.gpu_disable_compressed_textures)
disabled_features |= GPUDevice::FEATURE_MASK_COMPRESSED_TEXTURES;
// Don't dump shaders on debug builds for Android, users will complain about storage...
#if !defined(__ANDROID__) || defined(_DEBUG)
const std::string_view shader_dump_directory(EmuFolders::DataRoot);
#else
const std::string_view shader_dump_directory;
#endif
Error create_error;
std::optional<WindowInfo> wi;
if (!g_gpu_device ||
!(wi = Host::AcquireRenderWindow(api, fullscreen, fullscreen_mode.has_value(), &create_error)).has_value() ||
!g_gpu_device->Create(
g_settings.gpu_adapter, static_cast<GPUDevice::FeatureMask>(disabled_features), shader_dump_directory,
g_settings.gpu_disable_shader_cache ? std::string_view() : std::string_view(EmuFolders::Cache),
SHADER_CACHE_VERSION, g_settings.gpu_use_debug_device, wi.value(), System::GetEffectiveVSyncMode(),
System::ShouldAllowPresentThrottle(), fullscreen_mode.has_value() ? &fullscreen_mode.value() : nullptr,
exclusive_fullscreen_control, &create_error))
{
ERROR_LOG("Failed to create GPU device: {}", create_error.GetDescription());
if (g_gpu_device)
g_gpu_device->Destroy();
g_gpu_device.reset();
if (wi.has_value())
Host::ReleaseRenderWindow();
Error::SetStringFmt(
error,
TRANSLATE_FS("System", "Failed to create render device:\n\n{0}\n\nThis may be due to your GPU not supporting the "
"chosen renderer ({1}), or because your graphics drivers need to be updated."),
create_error.GetDescription(), GPUDevice::RenderAPIToString(api));
return false;
}
if (!ImGuiManager::Initialize(g_settings.display_osd_scale / 100.0f, g_settings.display_osd_margin, &create_error))
{
ERROR_LOG("Failed to initialize ImGuiManager: {}", create_error.GetDescription());
Error::SetStringFmt(error, "Failed to initialize ImGuiManager: {}", create_error.GetDescription());
g_gpu_device->Destroy();
g_gpu_device.reset();
Host::ReleaseRenderWindow();
return false;
}
InputManager::SetDisplayWindowSize(ImGuiManager::GetWindowWidth(), ImGuiManager::GetWindowHeight());
return true;
}
void Host::UpdateDisplayWindow(bool fullscreen)
{
if (!g_gpu_device)
return;
const GPUVSyncMode vsync_mode = System::GetEffectiveVSyncMode();
const bool allow_present_throttle = System::ShouldAllowPresentThrottle();
std::optional<GPUDevice::ExclusiveFullscreenMode> fullscreen_mode;
if (fullscreen && g_gpu_device->SupportsExclusiveFullscreen())
{
fullscreen_mode =
GPUDevice::ExclusiveFullscreenMode::Parse(Host::GetTinyStringSettingValue("GPU", "FullscreenMode", ""));
}
std::optional<bool> exclusive_fullscreen_control;
if (g_settings.display_exclusive_fullscreen_control != DisplayExclusiveFullscreenControl::Automatic)
{
exclusive_fullscreen_control =
(g_settings.display_exclusive_fullscreen_control == DisplayExclusiveFullscreenControl::Allowed);
}
g_gpu_device->DestroyMainSwapChain();
Error error;
std::optional<WindowInfo> wi =
Host::AcquireRenderWindow(g_gpu_device->GetRenderAPI(), fullscreen, fullscreen_mode.has_value(), &error);
if (!wi.has_value())
{
Host::ReportFatalError("Failed to get render window after update", error.GetDescription());
return;
}
// if surfaceless, just leave it
if (wi->IsSurfaceless())
{
DEV_LOG("Switching to surfaceless device");
if (!g_gpu_device->SwitchToSurfacelessRendering(&error))
ERROR_LOG("Failed to switch to surfaceless, rendering commands may fail: {}", error.GetDescription());
return;
}
if (!g_gpu_device->RecreateMainSwapChain(wi.value(), vsync_mode, allow_present_throttle,
fullscreen_mode.has_value() ? &fullscreen_mode.value() : nullptr,
exclusive_fullscreen_control, &error))
{
Host::ReportFatalError("Failed to change window after update", error.GetDescription());
return;
}
const float f_width = static_cast<float>(g_gpu_device->GetMainSwapChain()->GetWidth());
const float f_height = static_cast<float>(g_gpu_device->GetMainSwapChain()->GetHeight());
ImGuiManager::WindowResized(f_width, f_height);
InputManager::SetDisplayWindowSize(f_width, f_height);
System::DisplayWindowResized();
}
void Host::ResizeDisplayWindow(s32 width, s32 height, float scale)
{
if (!g_gpu_device || !g_gpu_device->HasMainSwapChain())
return;
DEV_LOG("Display window resized to {}x{}", width, height);
Error error;
if (!g_gpu_device->GetMainSwapChain()->ResizeBuffers(width, height, scale, &error))
{
ERROR_LOG("Failed to resize main swap chain: {}", error.GetDescription());
UpdateDisplayWindow(Host::IsFullscreen());
return;
}
const float f_width = static_cast<float>(g_gpu_device->GetMainSwapChain()->GetWidth());
const float f_height = static_cast<float>(g_gpu_device->GetMainSwapChain()->GetHeight());
ImGuiManager::WindowResized(f_width, f_height);
InputManager::SetDisplayWindowSize(f_width, f_height);
System::DisplayWindowResized();
}
void Host::ReleaseGPUDevice()
{
if (!g_gpu_device)
return;
ImGuiManager::DestroyAllDebugWindows();
ImGuiManager::DestroyOverlayTextures();
FullscreenUI::Shutdown();
ImGuiManager::Shutdown();
INFO_LOG("Destroying {} GPU device...", GPUDevice::RenderAPIToString(g_gpu_device->GetRenderAPI()));
g_gpu_device->Destroy();
g_gpu_device.reset();
Host::ReleaseRenderWindow();
}

View File

@ -75,10 +75,6 @@ std::span<const std::pair<const char*, const char*>> GetAvailableLanguageList();
/// Refreshes the UI when the language is changed. /// Refreshes the UI when the language is changed.
bool ChangeLanguage(const char* new_language); bool ChangeLanguage(const char* new_language);
/// Displays a loading screen with the logo, rendered with ImGui. Use when executing possibly-time-consuming tasks
/// such as compiling shaders when starting up.
void DisplayLoadingScreen(const char* message, int progress_min = -1, int progress_max = -1, int progress_value = -1);
/// Safely executes a function on the VM thread. /// Safely executes a function on the VM thread.
void RunOnCPUThread(std::function<void()> function, bool block = false); void RunOnCPUThread(std::function<void()> function, bool block = false);
@ -96,21 +92,6 @@ bool IsFullscreen();
/// Alters fullscreen state of hosting application. /// Alters fullscreen state of hosting application.
void SetFullscreen(bool enabled); void SetFullscreen(bool enabled);
/// Attempts to create the rendering device backend.
bool CreateGPUDevice(RenderAPI api, bool fullscreen, Error* error);
/// Handles fullscreen transitions and such.
void UpdateDisplayWindow(bool fullscreen);
/// Called when the window is resized.
void ResizeDisplayWindow(s32 width, s32 height, float scale);
/// Destroys any active rendering device.
void ReleaseGPUDevice();
/// Called at the end of the frame, before presentation.
void FrameDone();
namespace Internal { namespace Internal {
/// Returns true if the host should use portable mode. /// Returns true if the host should use portable mode.

View File

@ -1,88 +0,0 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "host_interface_progress_callback.h"
#include "host.h"
#include "common/log.h"
LOG_CHANNEL(Host);
HostInterfaceProgressCallback::HostInterfaceProgressCallback() : ProgressCallback()
{
}
void HostInterfaceProgressCallback::PushState()
{
ProgressCallback::PushState();
}
void HostInterfaceProgressCallback::PopState()
{
ProgressCallback::PopState();
Redraw(true);
}
void HostInterfaceProgressCallback::SetCancellable(bool cancellable)
{
ProgressCallback::SetCancellable(cancellable);
Redraw(true);
}
void HostInterfaceProgressCallback::SetTitle(const std::string_view title)
{
// todo?
}
void HostInterfaceProgressCallback::SetStatusText(const std::string_view text)
{
ProgressCallback::SetStatusText(text);
Redraw(true);
}
void HostInterfaceProgressCallback::SetProgressRange(u32 range)
{
u32 last_range = m_progress_range;
ProgressCallback::SetProgressRange(range);
if (m_progress_range != last_range)
Redraw(false);
}
void HostInterfaceProgressCallback::SetProgressValue(u32 value)
{
u32 lastValue = m_progress_value;
ProgressCallback::SetProgressValue(value);
if (m_progress_value != lastValue)
Redraw(false);
}
void HostInterfaceProgressCallback::Redraw(bool force)
{
if (m_last_progress_percent < 0 && m_open_time.GetTimeSeconds() < m_open_delay)
return;
const int percent =
static_cast<int>((static_cast<float>(m_progress_value) / static_cast<float>(m_progress_range)) * 100.0f);
if (percent == m_last_progress_percent && !force)
return;
m_last_progress_percent = percent;
Host::DisplayLoadingScreen(m_status_text.c_str(), 0, static_cast<int>(m_progress_range),
static_cast<int>(m_progress_value));
}
void HostInterfaceProgressCallback::ModalError(const std::string_view message)
{
ERROR_LOG(message);
Host::ReportErrorAsync("Error", message);
}
bool HostInterfaceProgressCallback::ModalConfirmation(const std::string_view message)
{
INFO_LOG(message);
return Host::ConfirmMessage("Confirm", message);
}

View File

@ -1,34 +0,0 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once
#include "common/progress_callback.h"
#include "common/timer.h"
class HostInterfaceProgressCallback final : public ProgressCallback
{
public:
HostInterfaceProgressCallback();
ALWAYS_INLINE void SetOpenDelay(float delay) { m_open_delay = delay; }
void PushState() override;
void PopState() override;
void SetCancellable(bool cancellable) override;
void SetTitle(const std::string_view title) override;
void SetStatusText(const std::string_view text) override;
void SetProgressRange(u32 range) override;
void SetProgressValue(u32 value) override;
void ModalError(const std::string_view message) override;
bool ModalConfirmation(const std::string_view message) override;
private:
void Redraw(bool force);
Timer m_open_time;
float m_open_delay = 1.0f;
int m_last_progress_percent = -1;
};

View File

@ -8,6 +8,7 @@
#include "fullscreen_ui.h" #include "fullscreen_ui.h"
#include "gpu.h" #include "gpu.h"
#include "gpu_hw_texture_cache.h" #include "gpu_hw_texture_cache.h"
#include "gpu_thread.h"
#include "host.h" #include "host.h"
#include "imgui_overlays.h" #include "imgui_overlays.h"
#include "settings.h" #include "settings.h"
@ -58,9 +59,8 @@ static void HotkeyModifyResolutionScale(s32 increment)
if (System::IsValid()) if (System::IsValid())
{ {
g_gpu->RestoreDeviceContext(); System::ClearMemorySaveStates(true);
g_gpu->UpdateSettings(old_settings); GPUThread::UpdateSettings(true, false);
System::ClearMemorySaveStates();
} }
} }
@ -375,11 +375,11 @@ DEFINE_HOTKEY("TogglePGXP", TRANSLATE_NOOP("Hotkeys", "Graphics"), TRANSLATE_NOO
[](s32 pressed) { [](s32 pressed) {
if (!pressed && System::IsValid()) if (!pressed && System::IsValid())
{ {
Settings old_settings = g_settings; System::ClearMemorySaveStates(true);
g_settings.gpu_pgxp_enable = !g_settings.gpu_pgxp_enable; g_settings.gpu_pgxp_enable = !g_settings.gpu_pgxp_enable;
g_gpu->RestoreDeviceContext(); GPUThread::UpdateSettings(true, false);
g_gpu->UpdateSettings(old_settings);
System::ClearMemorySaveStates();
Host::AddKeyedOSDMessage("TogglePGXP", Host::AddKeyedOSDMessage("TogglePGXP",
g_settings.gpu_pgxp_enable ? g_settings.gpu_pgxp_enable ?
TRANSLATE_STR("OSDMessage", "PGXP is now enabled.") : TRANSLATE_STR("OSDMessage", "PGXP is now enabled.") :
@ -426,13 +426,18 @@ DEFINE_HOTKEY("ToggleInternalPostProcessing", TRANSLATE_NOOP("Hotkeys", "Graphic
DEFINE_HOTKEY("ReloadPostProcessingShaders", TRANSLATE_NOOP("Hotkeys", "Graphics"), DEFINE_HOTKEY("ReloadPostProcessingShaders", TRANSLATE_NOOP("Hotkeys", "Graphics"),
TRANSLATE_NOOP("Hotkeys", "Reload Post Processing Shaders"), [](s32 pressed) { TRANSLATE_NOOP("Hotkeys", "Reload Post Processing Shaders"), [](s32 pressed) {
if (!pressed && System::IsValid()) if (!pressed && System::IsValid())
PostProcessing::ReloadShaders(); {
GPUThread::RunOnThread([]() {
if (GPUThread::HasGPUBackend())
PostProcessing::ReloadShaders();
});
}
}) })
DEFINE_HOTKEY("ReloadTextureReplacements", TRANSLATE_NOOP("Hotkeys", "Graphics"), DEFINE_HOTKEY("ReloadTextureReplacements", TRANSLATE_NOOP("Hotkeys", "Graphics"),
TRANSLATE_NOOP("Hotkeys", "Reload Texture Replacements"), [](s32 pressed) { TRANSLATE_NOOP("Hotkeys", "Reload Texture Replacements"), [](s32 pressed) {
if (!pressed && System::IsValid()) if (!pressed && System::IsValid())
GPUTextureCache::ReloadTextureReplacements(true); GPUThread::RunOnThread([]() { GPUTextureCache::ReloadTextureReplacements(true); });
}) })
DEFINE_HOTKEY("ToggleWidescreen", TRANSLATE_NOOP("Hotkeys", "Graphics"), TRANSLATE_NOOP("Hotkeys", "Toggle Widescreen"), DEFINE_HOTKEY("ToggleWidescreen", TRANSLATE_NOOP("Hotkeys", "Graphics"), TRANSLATE_NOOP("Hotkeys", "Toggle Widescreen"),
@ -448,12 +453,11 @@ DEFINE_HOTKEY("TogglePGXPDepth", TRANSLATE_NOOP("Hotkeys", "Graphics"),
if (!g_settings.gpu_pgxp_enable) if (!g_settings.gpu_pgxp_enable)
return; return;
const Settings old_settings = g_settings; System::ClearMemorySaveStates(true);
g_settings.gpu_pgxp_depth_buffer = !g_settings.gpu_pgxp_depth_buffer;
g_settings.gpu_pgxp_depth_buffer = !g_settings.gpu_pgxp_depth_buffer;
GPUThread::UpdateSettings(true, false);
g_gpu->RestoreDeviceContext();
g_gpu->UpdateSettings(old_settings);
System::ClearMemorySaveStates();
Host::AddKeyedOSDMessage("TogglePGXPDepth", Host::AddKeyedOSDMessage("TogglePGXPDepth",
g_settings.gpu_pgxp_depth_buffer ? g_settings.gpu_pgxp_depth_buffer ?
TRANSLATE_STR("OSDMessage", "PGXP Depth Buffer is now enabled.") : TRANSLATE_STR("OSDMessage", "PGXP Depth Buffer is now enabled.") :
@ -469,12 +473,11 @@ DEFINE_HOTKEY("TogglePGXPCPU", TRANSLATE_NOOP("Hotkeys", "Graphics"), TRANSLATE_
if (!g_settings.gpu_pgxp_enable) if (!g_settings.gpu_pgxp_enable)
return; return;
const Settings old_settings = g_settings; System::ClearMemorySaveStates(true);
// GPU thread is unchanged
g_settings.gpu_pgxp_cpu = !g_settings.gpu_pgxp_cpu; g_settings.gpu_pgxp_cpu = !g_settings.gpu_pgxp_cpu;
g_gpu->RestoreDeviceContext();
g_gpu->UpdateSettings(old_settings);
System::ClearMemorySaveStates();
Host::AddKeyedOSDMessage("TogglePGXPCPU", Host::AddKeyedOSDMessage("TogglePGXPCPU",
g_settings.gpu_pgxp_cpu ? g_settings.gpu_pgxp_cpu ?
TRANSLATE_STR("OSDMessage", "PGXP CPU mode is now enabled.") : TRANSLATE_STR("OSDMessage", "PGXP CPU mode is now enabled.") :
@ -584,29 +587,31 @@ DEFINE_HOTKEY("AudioVolumeDown", TRANSLATE_NOOP("Hotkeys", "Audio"), TRANSLATE_N
DEFINE_HOTKEY("LoadSelectedSaveState", TRANSLATE_NOOP("Hotkeys", "Save States"), DEFINE_HOTKEY("LoadSelectedSaveState", TRANSLATE_NOOP("Hotkeys", "Save States"),
TRANSLATE_NOOP("Hotkeys", "Load From Selected Slot"), [](s32 pressed) { TRANSLATE_NOOP("Hotkeys", "Load From Selected Slot"), [](s32 pressed) {
if (!pressed) if (!pressed)
Host::RunOnCPUThread(SaveStateSelectorUI::LoadCurrentSlot); GPUThread::RunOnThread(SaveStateSelectorUI::LoadCurrentSlot);
}) })
DEFINE_HOTKEY("SaveSelectedSaveState", TRANSLATE_NOOP("Hotkeys", "Save States"), DEFINE_HOTKEY("SaveSelectedSaveState", TRANSLATE_NOOP("Hotkeys", "Save States"),
TRANSLATE_NOOP("Hotkeys", "Save To Selected Slot"), [](s32 pressed) { TRANSLATE_NOOP("Hotkeys", "Save To Selected Slot"), [](s32 pressed) {
if (!pressed) if (!pressed)
Host::RunOnCPUThread(SaveStateSelectorUI::SaveCurrentSlot); GPUThread::RunOnThread(SaveStateSelectorUI::SaveCurrentSlot);
}) })
DEFINE_HOTKEY("SelectPreviousSaveStateSlot", TRANSLATE_NOOP("Hotkeys", "Save States"), DEFINE_HOTKEY("SelectPreviousSaveStateSlot", TRANSLATE_NOOP("Hotkeys", "Save States"),
TRANSLATE_NOOP("Hotkeys", "Select Previous Save Slot"), [](s32 pressed) { TRANSLATE_NOOP("Hotkeys", "Select Previous Save Slot"), [](s32 pressed) {
if (!pressed) if (!pressed)
Host::RunOnCPUThread([]() { SaveStateSelectorUI::SelectPreviousSlot(true); }); GPUThread::RunOnThread([]() { SaveStateSelectorUI::SelectPreviousSlot(true); });
}) })
DEFINE_HOTKEY("SelectNextSaveStateSlot", TRANSLATE_NOOP("Hotkeys", "Save States"), DEFINE_HOTKEY("SelectNextSaveStateSlot", TRANSLATE_NOOP("Hotkeys", "Save States"),
TRANSLATE_NOOP("Hotkeys", "Select Next Save Slot"), [](s32 pressed) { TRANSLATE_NOOP("Hotkeys", "Select Next Save Slot"), [](s32 pressed) {
if (!pressed) if (!pressed)
Host::RunOnCPUThread([]() { SaveStateSelectorUI::SelectNextSlot(true); }); GPUThread::RunOnThread([]() { SaveStateSelectorUI::SelectNextSlot(true); });
}) })
DEFINE_HOTKEY("SaveStateAndSelectNextSlot", TRANSLATE_NOOP("Hotkeys", "Save States"), DEFINE_HOTKEY("SaveStateAndSelectNextSlot", TRANSLATE_NOOP("Hotkeys", "Save States"),
TRANSLATE_NOOP("Hotkeys", "Save State and Select Next Slot"), [](s32 pressed) { TRANSLATE_NOOP("Hotkeys", "Save State and Select Next Slot"), [](s32 pressed) {
if (!pressed && System::IsValid()) if (!pressed && System::IsValid())
{ {
SaveStateSelectorUI::SaveCurrentSlot(); GPUThread::RunOnThread([]() {
SaveStateSelectorUI::SelectNextSlot(false); SaveStateSelectorUI::SaveCurrentSlot();
SaveStateSelectorUI::SelectNextSlot(false);
});
} }
}) })

View File

@ -9,6 +9,8 @@
#include "dma.h" #include "dma.h"
#include "fullscreen_ui.h" #include "fullscreen_ui.h"
#include "gpu.h" #include "gpu.h"
#include "gpu_backend.h"
#include "gpu_thread.h"
#include "host.h" #include "host.h"
#include "mdec.h" #include "mdec.h"
#include "performance_counters.h" #include "performance_counters.h"
@ -70,10 +72,10 @@ struct DebugWindowInfo
} // namespace } // namespace
static void FormatProcessorStat(SmallStringBase& text, double usage, double time); static void FormatProcessorStat(SmallStringBase& text, double usage, double time);
static void DrawPerformanceOverlay(float& position_y, float scale, float margin, float spacing); static void DrawPerformanceOverlay(const GPUBackend* gpu, float& position_y, float scale, float margin, float spacing);
static void DrawMediaCaptureOverlay(float& position_y, float scale, float margin, float spacing); static void DrawMediaCaptureOverlay(float& position_y, float scale, float margin, float spacing);
static void DrawFrameTimeOverlay(float& position_y, float scale, float margin, float spacing); static void DrawFrameTimeOverlay(float& position_y, float scale, float margin, float spacing);
static void DrawEnhancementsOverlay(); static void DrawEnhancementsOverlay(const GPUBackend* gpu);
static void DrawInputsOverlay(); static void DrawInputsOverlay();
#ifndef __ANDROID__ #ifndef __ANDROID__
@ -83,7 +85,7 @@ static constexpr const char* DEBUG_WINDOW_CONFIG_SECTION = "DebugWindows";
static constexpr const std::array<DebugWindowInfo, NUM_DEBUG_WINDOWS> s_debug_window_info = {{ static constexpr const std::array<DebugWindowInfo, NUM_DEBUG_WINDOWS> s_debug_window_info = {{
{"SPU", "SPU State", ":icons/applications-system.png", &SPU::DrawDebugStateWindow, 800, 915}, {"SPU", "SPU State", ":icons/applications-system.png", &SPU::DrawDebugStateWindow, 800, 915},
{"CDROM", "CD-ROM State", ":icons/applications-system.png", &CDROM::DrawDebugWindow, 800, 540}, {"CDROM", "CD-ROM State", ":icons/applications-system.png", &CDROM::DrawDebugWindow, 800, 540},
{"GPU", "GPU State", ":icons/applications-system.png", [](float sc) { g_gpu->DrawDebugStateWindow(sc); }, 450, 550}, {"GPU", "GPU State", ":icons/applications-system.png", [](float sc) { g_gpu.DrawDebugStateWindow(sc); }, 450, 550},
{"DMA", "DMA State", ":icons/applications-system.png", &DMA::DrawDebugStateWindow, 860, 180}, {"DMA", "DMA State", ":icons/applications-system.png", &DMA::DrawDebugStateWindow, 860, 180},
{"MDEC", "MDEC State", ":icons/applications-system.png", &MDEC::DrawDebugStateWindow, 300, 350}, {"MDEC", "MDEC State", ":icons/applications-system.png", &MDEC::DrawDebugStateWindow, 300, 350},
{"Timers", "Timers State", ":icons/applications-system.png", &Timers::DrawDebugStateWindow, 800, 95}, {"Timers", "Timers State", ":icons/applications-system.png", &Timers::DrawDebugStateWindow, 800, 95},
@ -119,93 +121,22 @@ static std::tuple<float, float> GetMinMax(std::span<const float> values)
return std::tie(min, max); return std::tie(min, max);
} }
void Host::DisplayLoadingScreen(const char* message, int progress_min /*= -1*/, int progress_max /*= -1*/, bool ImGuiManager::AreAnyDebugWindowsEnabled(const SettingsInterface& si)
int progress_value /*= -1*/)
{ {
if (!g_gpu_device || !g_gpu_device->HasMainSwapChain()) #ifndef __ANDROID__
const bool block_all = Achievements::IsHardcoreModeActive();
if (block_all)
return false;
for (size_t i = 0; i < NUM_DEBUG_WINDOWS; i++)
{ {
INFO_LOG("{}: {}/{}", message, progress_value, progress_max); const DebugWindowInfo& info = s_debug_window_info[i];
return; if (si.GetBoolValue(DEBUG_WINDOW_CONFIG_SECTION, info.name, false))
return true;
} }
#endif
const auto& io = ImGui::GetIO(); return false;
const float scale = ImGuiManager::GetGlobalScale();
const float width = (400.0f * scale);
const bool has_progress = (progress_min < progress_max);
// eat the last imgui frame, it might've been partially rendered by the caller.
ImGui::EndFrame();
ImGui::NewFrame();
const float logo_width = 260.0f * scale;
const float logo_height = 260.0f * scale;
ImGui::SetNextWindowSize(ImVec2(logo_width, logo_height), ImGuiCond_Always);
ImGui::SetNextWindowPos(ImVec2(io.DisplaySize.x * 0.5f, (io.DisplaySize.y * 0.5f) - (50.0f * scale)),
ImGuiCond_Always, ImVec2(0.5f, 0.5f));
if (ImGui::Begin("LoadingScreenLogo", nullptr,
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoNav |
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing |
ImGuiWindowFlags_NoBackground))
{
GPUTexture* tex = ImGuiFullscreen::GetCachedTexture("images/duck.png");
if (tex)
ImGui::Image(tex, ImVec2(logo_width, logo_height));
}
ImGui::End();
const float padding_and_rounding = 18.0f * scale;
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, padding_and_rounding);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(padding_and_rounding, padding_and_rounding));
ImGui::SetNextWindowSize(ImVec2(width, (has_progress ? 90.0f : 55.0f) * scale), ImGuiCond_Always);
ImGui::SetNextWindowPos(ImVec2(io.DisplaySize.x * 0.5f, (io.DisplaySize.y * 0.5f) + (100.0f * scale)),
ImGuiCond_Always, ImVec2(0.5f, 0.0f));
if (ImGui::Begin("LoadingScreen", nullptr,
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoNav |
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing))
{
if (has_progress)
{
ImGui::TextUnformatted(message);
TinyString buf;
buf.format("{}/{}", progress_value, progress_max);
const ImVec2 prog_size = ImGui::CalcTextSize(buf.c_str(), buf.end_ptr());
ImGui::SameLine();
ImGui::SetCursorPosX(width - padding_and_rounding - prog_size.x);
ImGui::TextUnformatted(buf.c_str(), buf.end_ptr());
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 5.0f);
ImGui::ProgressBar(static_cast<float>(progress_value) / static_cast<float>(progress_max - progress_min),
ImVec2(-1.0f, 0.0f), "");
INFO_LOG("{}: {}", message, buf);
}
else
{
const ImVec2 text_size(ImGui::CalcTextSize(message));
ImGui::SetCursorPosX((width - text_size.x) / 2.0f);
ImGui::TextUnformatted(message);
INFO_LOG(message);
}
}
ImGui::End();
ImGui::PopStyleVar(2);
ImGui::EndFrame();
// TODO: Glass effect or something.
GPUSwapChain* swap_chain = g_gpu_device->GetMainSwapChain();
if (g_gpu_device->BeginPresent(swap_chain) == GPUDevice::PresentResult::OK)
{
g_gpu_device->RenderImGui(swap_chain);
g_gpu_device->EndPresent(swap_chain, false);
}
ImGui::NewFrame();
} }
bool ImGuiManager::UpdateDebugWindowConfig() bool ImGuiManager::UpdateDebugWindowConfig()
@ -284,26 +215,28 @@ void ImGuiManager::DestroyAllDebugWindows()
#endif #endif
} }
void ImGuiManager::RenderTextOverlays() void ImGuiManager::RenderTextOverlays(const GPUBackend* gpu)
{ {
const System::State state = System::GetState(); // Don't draw anything with loading screen open, it'll be nonsensical.
if (state != System::State::Shutdown) if (ImGuiFullscreen::IsLoadingScreenOpen())
{ return;
const float scale = ImGuiManager::GetGlobalScale();
const float f_margin = ImGuiManager::GetScreenMargin() * scale;
const float margin = ImCeil(ImGuiManager::GetScreenMargin() * scale);
const float spacing = ImCeil(5.0f * scale);
float position_y = ImFloor(f_margin);
DrawPerformanceOverlay(position_y, scale, margin, spacing);
DrawFrameTimeOverlay(position_y, scale, margin, spacing);
DrawMediaCaptureOverlay(position_y, scale, margin, spacing);
if (g_settings.display_show_enhancements && state != System::State::Paused) const bool paused = GPUThread::IsSystemPaused();
DrawEnhancementsOverlay();
if (g_settings.display_show_inputs && state != System::State::Paused) const float scale = ImGuiManager::GetGlobalScale();
DrawInputsOverlay(); const float f_margin = ImGuiManager::GetScreenMargin() * scale;
} const float margin = ImCeil(ImGuiManager::GetScreenMargin() * scale);
const float spacing = ImCeil(5.0f * scale);
float position_y = ImFloor(f_margin);
DrawPerformanceOverlay(gpu, position_y, scale, margin, spacing);
DrawFrameTimeOverlay(position_y, scale, margin, spacing);
DrawMediaCaptureOverlay(position_y, scale, margin, spacing);
if (g_gpu_settings.display_show_enhancements && !paused)
DrawEnhancementsOverlay(gpu);
if (g_gpu_settings.display_show_inputs && !paused)
DrawInputsOverlay();
} }
void ImGuiManager::FormatProcessorStat(SmallStringBase& text, double usage, double time) void ImGuiManager::FormatProcessorStat(SmallStringBase& text, double usage, double time)
@ -317,12 +250,13 @@ void ImGuiManager::FormatProcessorStat(SmallStringBase& text, double usage, doub
text.append_format("{:.1f}% ({:.2f}ms)", usage, time); text.append_format("{:.1f}% ({:.2f}ms)", usage, time);
} }
void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, float margin, float spacing) void ImGuiManager::DrawPerformanceOverlay(const GPUBackend* gpu, float& position_y, float scale, float margin,
float spacing)
{ {
if (!(g_settings.display_show_fps || g_settings.display_show_speed || g_settings.display_show_gpu_stats || if (!(g_gpu_settings.display_show_fps || g_gpu_settings.display_show_speed || g_gpu_settings.display_show_gpu_stats ||
g_settings.display_show_resolution || g_settings.display_show_cpu_usage || g_gpu_settings.display_show_resolution || g_gpu_settings.display_show_cpu_usage ||
(g_settings.display_show_status_indicators && (g_gpu_settings.display_show_status_indicators &&
(System::IsPaused() || System::IsFastForwardEnabled() || System::IsTurboEnabled())))) (GPUThread::IsSystemPaused() || System::IsFastForwardEnabled() || System::IsTurboEnabled()))))
{ {
return; return;
} }
@ -352,9 +286,9 @@ void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, float
if (state == System::State::Running) if (state == System::State::Running)
{ {
const float speed = PerformanceCounters::GetEmulationSpeed(); const float speed = PerformanceCounters::GetEmulationSpeed();
if (g_settings.display_show_fps) if (g_gpu_settings.display_show_fps)
text.append_format("G: {:.2f} | V: {:.2f}", PerformanceCounters::GetFPS(), PerformanceCounters::GetVPS()); text.append_format("G: {:.2f} | V: {:.2f}", PerformanceCounters::GetFPS(), PerformanceCounters::GetVPS());
if (g_settings.display_show_speed) if (g_gpu_settings.display_show_speed)
{ {
text.append_format("{}{}%", text.empty() ? "" : " | ", static_cast<u32>(std::round(speed))); text.append_format("{}{}%", text.empty() ? "" : " | ", static_cast<u32>(std::round(speed)));
@ -377,33 +311,33 @@ void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, float
DRAW_LINE(fixed_font, text, color); DRAW_LINE(fixed_font, text, color);
} }
if (g_settings.display_show_gpu_stats) if (g_gpu_settings.display_show_gpu_stats)
{ {
g_gpu->GetStatsString(text); gpu->GetStatsString(text);
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255)); DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
g_gpu->GetMemoryStatsString(text); gpu->GetMemoryStatsString(text);
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255)); DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
} }
if (g_settings.display_show_resolution) if (g_gpu_settings.display_show_resolution)
{ {
const u32 resolution_scale = g_gpu->GetResolutionScale(); const u32 resolution_scale = gpu->GetResolutionScale();
const auto [display_width, display_height] = g_gpu->GetFullDisplayResolution(); // wrong const auto [display_width, display_height] = g_gpu.GetFullDisplayResolution(); // NOTE: Racey read.
const bool interlaced = g_gpu->IsInterlacedDisplayEnabled(); const bool interlaced = g_gpu.IsInterlacedDisplayEnabled();
const bool pal = g_gpu->IsInPALMode(); const bool pal = g_gpu.IsInPALMode();
text.format("{}x{} {} {} [{}x]", display_width * resolution_scale, display_height * resolution_scale, text.format("{}x{} {} {} [{}x]", display_width * resolution_scale, display_height * resolution_scale,
pal ? "PAL" : "NTSC", interlaced ? "Interlaced" : "Progressive", resolution_scale); pal ? "PAL" : "NTSC", interlaced ? "Interlaced" : "Progressive", resolution_scale);
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255)); DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
} }
if (g_settings.display_show_latency_stats) if (g_gpu_settings.display_show_latency_stats)
{ {
System::FormatLatencyStats(text); System::FormatLatencyStats(text);
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255)); DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
} }
if (g_settings.display_show_cpu_usage) if (g_gpu_settings.display_show_cpu_usage)
{ {
text.format("{:.2f}ms | {:.2f}ms | {:.2f}ms", PerformanceCounters::GetMinimumFrameTime(), text.format("{:.2f}ms | {:.2f}ms | {:.2f}ms", PerformanceCounters::GetMinimumFrameTime(),
PerformanceCounters::GetAverageFrameTime(), PerformanceCounters::GetMaximumFrameTime()); PerformanceCounters::GetAverageFrameTime(), PerformanceCounters::GetMaximumFrameTime());
@ -454,11 +388,11 @@ void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, float
PerformanceCounters::GetCPUThreadAverageTime()); PerformanceCounters::GetCPUThreadAverageTime());
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255)); DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
if (g_gpu->GetSWThread()) if (g_gpu_settings.gpu_use_thread)
{ {
text.assign("SW: "); text.assign("RNDR: ");
FormatProcessorStat(text, PerformanceCounters::GetSWThreadUsage(), FormatProcessorStat(text, PerformanceCounters::GetGPUThreadUsage(),
PerformanceCounters::GetSWThreadAverageTime()); PerformanceCounters::GetGPUThreadAverageTime());
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255)); DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
} }
@ -472,14 +406,14 @@ void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, float
#endif #endif
} }
if (g_settings.display_show_gpu_usage && g_gpu_device->IsGPUTimingEnabled()) if (g_gpu_settings.display_show_gpu_usage && g_gpu_device->IsGPUTimingEnabled())
{ {
text.assign("GPU: "); text.assign("GPU: ");
FormatProcessorStat(text, PerformanceCounters::GetGPUUsage(), PerformanceCounters::GetGPUAverageTime()); FormatProcessorStat(text, PerformanceCounters::GetGPUUsage(), PerformanceCounters::GetGPUAverageTime());
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255)); DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
} }
if (g_settings.display_show_status_indicators) if (g_gpu_settings.display_show_status_indicators)
{ {
const bool rewinding = System::IsRewinding(); const bool rewinding = System::IsRewinding();
if (rewinding || System::IsFastForwardEnabled() || System::IsTurboEnabled()) if (rewinding || System::IsFastForwardEnabled() || System::IsTurboEnabled())
@ -489,7 +423,7 @@ void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, float
} }
} }
} }
else if (g_settings.display_show_status_indicators && state == System::State::Paused && else if (g_gpu_settings.display_show_status_indicators && state == System::State::Paused &&
!FullscreenUI::HasActiveWindow()) !FullscreenUI::HasActiveWindow())
{ {
text.assign(ICON_EMOJI_PAUSE); text.assign(ICON_EMOJI_PAUSE);
@ -499,12 +433,12 @@ void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, float
#undef DRAW_LINE #undef DRAW_LINE
} }
void ImGuiManager::DrawEnhancementsOverlay() void ImGuiManager::DrawEnhancementsOverlay(const GPUBackend* gpu)
{ {
LargeString text; LargeString text;
text.append_format("{} {}-{}", Settings::GetConsoleRegionName(System::GetRegion()), text.append_format("{} {}-{}", Settings::GetConsoleRegionName(System::GetRegion()),
GPUDevice::RenderAPIToString(g_gpu_device->GetRenderAPI()), GPUDevice::RenderAPIToString(g_gpu_device->GetRenderAPI()),
g_gpu->IsHardwareRenderer() ? "HW" : "SW"); GPUBackend::IsUsingHardwareBackend() ? "HW" : "SW");
if (g_settings.rewind_enable) if (g_settings.rewind_enable)
text.append_format(" RW={}/{}", g_settings.rewind_save_frequency, g_settings.rewind_save_slots); text.append_format(" RW={}/{}", g_settings.rewind_save_frequency, g_settings.rewind_save_slots);
@ -519,29 +453,29 @@ void ImGuiManager::DrawEnhancementsOverlay()
text.append_format(" CDR={}x", g_settings.cdrom_read_speedup); text.append_format(" CDR={}x", g_settings.cdrom_read_speedup);
if (g_settings.cdrom_seek_speedup != 1) if (g_settings.cdrom_seek_speedup != 1)
text.append_format(" CDS={}x", g_settings.cdrom_seek_speedup); text.append_format(" CDS={}x", g_settings.cdrom_seek_speedup);
if (g_settings.gpu_resolution_scale != 1) if (g_gpu_settings.gpu_resolution_scale != 1)
text.append_format(" IR={}x", g_settings.gpu_resolution_scale); text.append_format(" IR={}x", g_gpu_settings.gpu_resolution_scale);
if (g_settings.gpu_multisamples != 1) if (g_gpu_settings.gpu_multisamples != 1)
{ {
text.append_format(" {}x{}", g_settings.gpu_multisamples, g_settings.gpu_per_sample_shading ? "SSAA" : "MSAA"); text.append_format(" {}x{}", g_gpu_settings.gpu_multisamples, g_gpu_settings.gpu_per_sample_shading ? "SSAA" : "MSAA");
} }
if (g_settings.gpu_true_color) if (g_gpu_settings.gpu_true_color)
text.append(" TrueCol"); text.append(" TrueCol");
text.append_format(" DI={}", Settings::GetDisplayDeinterlacingModeName(g_settings.display_deinterlacing_mode)); text.append_format(" DI={}", Settings::GetDisplayDeinterlacingModeName(g_gpu_settings.display_deinterlacing_mode));
if (g_settings.gpu_force_video_timing == ForceVideoTimingMode::NTSC && System::GetRegion() == ConsoleRegion::PAL) if (g_settings.gpu_force_video_timing == ForceVideoTimingMode::NTSC && System::GetRegion() == ConsoleRegion::PAL)
text.append(" PAL60"); text.append(" PAL60");
if (g_settings.gpu_force_video_timing == ForceVideoTimingMode::PAL && System::GetRegion() != ConsoleRegion::PAL) if (g_settings.gpu_force_video_timing == ForceVideoTimingMode::PAL && System::GetRegion() != ConsoleRegion::PAL)
text.append(" NTSC50"); text.append(" NTSC50");
if (g_settings.gpu_texture_filter != GPUTextureFilter::Nearest) if (g_gpu_settings.gpu_texture_filter != GPUTextureFilter::Nearest)
{ {
if (g_settings.gpu_sprite_texture_filter != g_settings.gpu_texture_filter) if (g_gpu_settings.gpu_sprite_texture_filter != g_gpu_settings.gpu_texture_filter)
{ {
text.append_format(" {}/{}", Settings::GetTextureFilterName(g_settings.gpu_texture_filter), text.append_format(" {}/{}", Settings::GetTextureFilterName(g_gpu_settings.gpu_texture_filter),
Settings::GetTextureFilterName(g_settings.gpu_sprite_texture_filter)); Settings::GetTextureFilterName(g_gpu_settings.gpu_sprite_texture_filter));
} }
else else
{ {
text.append_format(" {}", Settings::GetTextureFilterName(g_settings.gpu_texture_filter)); text.append_format(" {}", Settings::GetTextureFilterName(g_gpu_settings.gpu_texture_filter));
} }
} }
if (g_settings.gpu_widescreen_hack && g_settings.display_aspect_ratio != DisplayAspectRatio::Auto && if (g_settings.gpu_widescreen_hack && g_settings.display_aspect_ratio != DisplayAspectRatio::Auto &&
@ -549,8 +483,8 @@ void ImGuiManager::DrawEnhancementsOverlay()
{ {
text.append(" WSHack"); text.append(" WSHack");
} }
if (g_settings.gpu_line_detect_mode != GPULineDetectMode::Disabled) if (g_gpu_settings.gpu_line_detect_mode != GPULineDetectMode::Disabled)
text.append_format(" LD={}", Settings::GetLineDetectModeName(g_settings.gpu_line_detect_mode)); text.append_format(" LD={}", Settings::GetLineDetectModeName(g_gpu_settings.gpu_line_detect_mode));
if (g_settings.gpu_pgxp_enable) if (g_settings.gpu_pgxp_enable)
{ {
text.append(" PGXP"); text.append(" PGXP");
@ -626,7 +560,7 @@ void ImGuiManager::DrawMediaCaptureOverlay(float& position_y, float scale, float
void ImGuiManager::DrawFrameTimeOverlay(float& position_y, float scale, float margin, float spacing) void ImGuiManager::DrawFrameTimeOverlay(float& position_y, float scale, float margin, float spacing)
{ {
if (!g_settings.display_show_frame_times || System::IsPaused()) if (!g_settings.display_show_frame_times || GPUThread::IsSystemPaused())
return; return;
const float shadow_offset = std::ceil(1.0f * scale); const float shadow_offset = std::ceil(1.0f * scale);
@ -864,8 +798,6 @@ bool SaveStateSelectorUI::IsOpen()
void SaveStateSelectorUI::Open(float open_time /* = DEFAULT_OPEN_TIME */) void SaveStateSelectorUI::Open(float open_time /* = DEFAULT_OPEN_TIME */)
{ {
const std::string& serial = System::GetGameSerial();
s_state.open_time = 0.0f; s_state.open_time = 0.0f;
s_state.close_time = open_time; s_state.close_time = open_time;
@ -876,7 +808,7 @@ void SaveStateSelectorUI::Open(float open_time /* = DEFAULT_OPEN_TIME */)
s_state.placeholder_texture = ImGuiFullscreen::LoadTexture("no-save.png"); s_state.placeholder_texture = ImGuiFullscreen::LoadTexture("no-save.png");
s_state.is_open = true; s_state.is_open = true;
RefreshList(serial); RefreshList();
RefreshHotkeyLegend(); RefreshHotkeyLegend();
} }
@ -889,7 +821,7 @@ void SaveStateSelectorUI::Close()
s_state.next_legend = {}; s_state.next_legend = {};
} }
void SaveStateSelectorUI::RefreshList(const std::string& serial) void SaveStateSelectorUI::RefreshList()
{ {
for (ListEntry& entry : s_state.slots) for (ListEntry& entry : s_state.slots)
{ {
@ -898,9 +830,7 @@ void SaveStateSelectorUI::RefreshList(const std::string& serial)
} }
s_state.slots.clear(); s_state.slots.clear();
if (System::IsShutdown()) const std::string& serial = GPUThread::GetGameSerial();
return;
if (!serial.empty()) if (!serial.empty())
{ {
for (s32 i = 1; i <= System::PER_GAME_SAVE_STATE_SLOTS; i++) for (s32 i = 1; i <= System::PER_GAME_SAVE_STATE_SLOTS; i++)
@ -956,6 +886,7 @@ void SaveStateSelectorUI::Clear()
void SaveStateSelectorUI::ClearList() void SaveStateSelectorUI::ClearList()
{ {
DebugAssert(GPUThread::IsOnThread());
for (ListEntry& li : s_state.slots) for (ListEntry& li : s_state.slots)
{ {
if (li.preview_texture) if (li.preview_texture)
@ -1001,7 +932,7 @@ void SaveStateSelectorUI::SelectNextSlot(bool open_selector)
s_state.current_slot++; s_state.current_slot++;
if (s_state.current_slot >= total_slots) if (s_state.current_slot >= total_slots)
{ {
if (!System::GetGameSerial().empty()) if (!GPUThread::GetGameSerial().empty())
s_state.current_slot_global ^= true; s_state.current_slot_global ^= true;
s_state.current_slot -= total_slots; s_state.current_slot -= total_slots;
} }
@ -1024,7 +955,7 @@ void SaveStateSelectorUI::SelectPreviousSlot(bool open_selector)
s_state.current_slot--; s_state.current_slot--;
if (s_state.current_slot < 0) if (s_state.current_slot < 0)
{ {
if (!System::GetGameSerial().empty()) if (!GPUThread::GetGameSerial().empty())
s_state.current_slot_global ^= true; s_state.current_slot_global ^= true;
s_state.current_slot += s_state.current_slot +=
s_state.current_slot_global ? System::GLOBAL_SAVE_STATE_SLOTS : System::PER_GAME_SAVE_STATE_SLOTS; s_state.current_slot_global ? System::GLOBAL_SAVE_STATE_SLOTS : System::PER_GAME_SAVE_STATE_SLOTS;
@ -1245,7 +1176,7 @@ std::string SaveStateSelectorUI::GetCurrentSlotPath()
std::string filename; std::string filename;
if (!s_state.current_slot_global) if (!s_state.current_slot_global)
{ {
if (const std::string& serial = System::GetGameSerial(); !serial.empty()) if (const std::string& serial = GPUThread::GetGameSerial(); !serial.empty())
filename = System::GetGameSaveStateFileName(serial, s_state.current_slot + 1); filename = System::GetGameSaveStateFileName(serial, s_state.current_slot + 1);
} }
else else
@ -1258,18 +1189,22 @@ std::string SaveStateSelectorUI::GetCurrentSlotPath()
void SaveStateSelectorUI::LoadCurrentSlot() void SaveStateSelectorUI::LoadCurrentSlot()
{ {
DebugAssert(GPUThread::IsOnThread());
if (std::string path = GetCurrentSlotPath(); !path.empty()) if (std::string path = GetCurrentSlotPath(); !path.empty())
{ {
if (FileSystem::FileExists(path.c_str())) if (FileSystem::FileExists(path.c_str()))
{ {
Error error; Host::RunOnCPUThread([path = std::move(path)]() {
if (!System::LoadState(path.c_str(), &error, true)) Error error;
{ if (!System::LoadState(path.c_str(), &error, true))
Host::AddKeyedOSDMessage("LoadState", {
fmt::format(TRANSLATE_FS("OSDMessage", "Failed to load state from slot {0}:\n{1}"), Host::AddKeyedOSDMessage("LoadState",
GetCurrentSlot(), error.GetDescription()), fmt::format(TRANSLATE_FS("OSDMessage", "Failed to load state from slot {0}:\n{1}"),
Host::OSD_ERROR_DURATION); GetCurrentSlot(), error.GetDescription()),
} Host::OSD_ERROR_DURATION);
}
});
} }
else else
{ {
@ -1289,14 +1224,16 @@ void SaveStateSelectorUI::SaveCurrentSlot()
{ {
if (std::string path = GetCurrentSlotPath(); !path.empty()) if (std::string path = GetCurrentSlotPath(); !path.empty())
{ {
Error error; Host::RunOnCPUThread([path = std::move(path)]() {
if (!System::SaveState(std::move(path), &error, g_settings.create_save_state_backups, false)) Error error;
{ if (!System::SaveState(std::move(path), &error, g_settings.create_save_state_backups, false))
Host::AddIconOSDMessage("SaveState", ICON_EMOJI_WARNING, {
fmt::format(TRANSLATE_FS("OSDMessage", "Failed to save state to slot {0}:\n{1}"), Host::AddIconOSDMessage("SaveState", ICON_EMOJI_WARNING,
GetCurrentSlot(), error.GetDescription()), fmt::format(TRANSLATE_FS("OSDMessage", "Failed to save state to slot {0}:\n{1}"),
Host::OSD_ERROR_DURATION); GetCurrentSlot(), error.GetDescription()),
} Host::OSD_ERROR_DURATION);
}
});
} }
Close(); Close();
@ -1323,7 +1260,7 @@ void SaveStateSelectorUI::ShowSlotOSDMessage()
void ImGuiManager::RenderOverlayWindows() void ImGuiManager::RenderOverlayWindows()
{ {
const System::State state = System::GetState(); const System::State state = System::GetState();
if (state != System::State::Shutdown) if (state == System::State::Paused || state == System::State::Running)
{ {
if (SaveStateSelectorUI::s_state.is_open) if (SaveStateSelectorUI::s_state.is_open)
SaveStateSelectorUI::Draw(); SaveStateSelectorUI::Draw();

View File

@ -7,14 +7,23 @@
#include <string> #include <string>
class SettingsInterface;
class GPUBackend;
namespace ImGuiManager { namespace ImGuiManager {
void RenderTextOverlays();
static constexpr const char* LOGO_IMAGE_NAME = "images/duck.png";
void RenderTextOverlays(const GPUBackend* gpu);
bool AreAnyDebugWindowsEnabled(const SettingsInterface& si);
void RenderDebugWindows(); void RenderDebugWindows();
bool UpdateDebugWindowConfig(); bool UpdateDebugWindowConfig();
void DestroyAllDebugWindows(); void DestroyAllDebugWindows();
void RenderOverlayWindows(); void RenderOverlayWindows();
void DestroyOverlayTextures(); void DestroyOverlayTextures();
} // namespace ImGuiManager } // namespace ImGuiManager
namespace SaveStateSelectorUI { namespace SaveStateSelectorUI {
@ -23,7 +32,7 @@ static constexpr float DEFAULT_OPEN_TIME = 7.5f;
bool IsOpen(); bool IsOpen();
void Open(float open_time = DEFAULT_OPEN_TIME); void Open(float open_time = DEFAULT_OPEN_TIME);
void RefreshList(const std::string& serial); void RefreshList();
void Clear(); void Clear();
void ClearList(); void ClearList();
void Close(); void Close();

View File

@ -376,6 +376,14 @@ bool JogCon::Transfer(const u8 data_in, u8* data_out)
m_tx_buffer = {GetIDByte(), m_status_byte, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; m_tx_buffer = {GetIDByte(), m_status_byte, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
Poll(); Poll();
} }
else if (m_configuration_mode && data_in == 0x44)
{
Assert(m_command_step == 0);
m_response_length = (GetResponseNumHalfwords() + 1) * 2;
m_command = Command::SetAnalogMode;
m_tx_buffer = {GetIDByte(), m_status_byte, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
ResetMotorConfig();
}
else if (m_configuration_mode && data_in == 0x45) else if (m_configuration_mode && data_in == 0x45)
{ {
m_response_length = (GetResponseNumHalfwords() + 1) * 2; m_response_length = (GetResponseNumHalfwords() + 1) * 2;
@ -429,6 +437,25 @@ bool JogCon::Transfer(const u8 data_in, u8* data_out)
} }
break; break;
case Command::SetAnalogMode:
{
if (m_command_step == 2)
{
DEV_LOG("analog mode val 0x{:02x}", data_in);
if (data_in == 0x00 || data_in == 0x01)
SetJogConMode(data_in == 0x01, true);
}
else if (m_command_step == 3)
{
DEV_LOG("analog mode lock 0x{:02x}", data_in);
if (data_in == 0x02 || data_in == 0x03)
WARNING_LOG("Unimplemented analog mode lock {}", (data_in == 0x03));
}
}
break;
case Command::SetMode: case Command::SetMode:
{ {
m_configuration_mode = (m_rx_buffer[2] == 1 && m_jogcon_mode); m_configuration_mode = (m_rx_buffer[2] == 1 && m_jogcon_mode);

View File

@ -71,6 +71,7 @@ private:
ReadPad, ReadPad,
SetMode, SetMode,
GetAnalogMode, GetAnalogMode,
SetAnalogMode,
GetSetRumble, GetSetRumble,
Command46, Command46,
Command47, Command47,

View File

@ -215,12 +215,12 @@ void Justifier::UpdatePosition()
float display_x, display_y; float display_x, display_y;
const auto [window_x, window_y] = (m_has_relative_binds) ? GetAbsolutePositionFromRelativeAxes() : const auto [window_x, window_y] = (m_has_relative_binds) ? GetAbsolutePositionFromRelativeAxes() :
InputManager::GetPointerAbsolutePosition(m_cursor_index); InputManager::GetPointerAbsolutePosition(m_cursor_index);
g_gpu->ConvertScreenCoordinatesToDisplayCoordinates(window_x, window_y, &display_x, &display_y); g_gpu.ConvertScreenCoordinatesToDisplayCoordinates(window_x, window_y, &display_x, &display_y);
// are we within the active display area? // are we within the active display area?
u32 tick, line; u32 tick, line;
if (display_x < 0 || display_y < 0 || if (display_x < 0 || display_y < 0 ||
!g_gpu->ConvertDisplayCoordinatesToBeamTicksAndLines(display_x, display_y, m_x_scale, &tick, &line) || !g_gpu.ConvertDisplayCoordinatesToBeamTicksAndLines(display_x, display_y, m_x_scale, &tick, &line) ||
m_shoot_offscreen) m_shoot_offscreen)
{ {
DEV_LOG("Lightgun out of range for window coordinates {:.0f},{:.0f}", window_x, window_y); DEV_LOG("Lightgun out of range for window coordinates {:.0f},{:.0f}", window_x, window_y);
@ -234,11 +234,11 @@ void Justifier::UpdatePosition()
m_irq_tick = static_cast<u16>(static_cast<TickCount>(tick) + m_irq_tick = static_cast<u16>(static_cast<TickCount>(tick) +
System::ScaleTicksToOverclock(static_cast<TickCount>(m_tick_offset))); System::ScaleTicksToOverclock(static_cast<TickCount>(m_tick_offset)));
m_irq_first_line = static_cast<u16>(std::clamp<s32>(static_cast<s32>(line) + m_first_line_offset, m_irq_first_line = static_cast<u16>(std::clamp<s32>(static_cast<s32>(line) + m_first_line_offset,
static_cast<s32>(g_gpu->GetCRTCActiveStartLine()), static_cast<s32>(g_gpu.GetCRTCActiveStartLine()),
static_cast<s32>(g_gpu->GetCRTCActiveEndLine()))); static_cast<s32>(g_gpu.GetCRTCActiveEndLine())));
m_irq_last_line = static_cast<u16>(std::clamp<s32>(static_cast<s32>(line) + m_last_line_offset, m_irq_last_line = static_cast<u16>(std::clamp<s32>(static_cast<s32>(line) + m_last_line_offset,
static_cast<s32>(g_gpu->GetCRTCActiveStartLine()), static_cast<s32>(g_gpu.GetCRTCActiveStartLine()),
static_cast<s32>(g_gpu->GetCRTCActiveEndLine()))); static_cast<s32>(g_gpu.GetCRTCActiveEndLine())));
DEV_LOG("Lightgun window coordinates {},{} -> dpy {},{} -> tick {} line {} [{}-{}]", window_x, window_y, display_x, DEV_LOG("Lightgun window coordinates {},{} -> dpy {},{} -> tick {} line {} [{}-{}]", window_x, window_y, display_x,
display_y, tick, line, m_irq_first_line, m_irq_last_line); display_y, tick, line, m_irq_first_line, m_irq_last_line);
@ -255,7 +255,7 @@ void Justifier::UpdateIRQEvent()
return; return;
u32 current_tick, current_line; u32 current_tick, current_line;
g_gpu->GetBeamPosition(&current_tick, &current_line); g_gpu.GetBeamPosition(&current_tick, &current_line);
u32 target_line; u32 target_line;
if (current_line < m_irq_first_line || current_line >= m_irq_last_line) if (current_line < m_irq_first_line || current_line >= m_irq_last_line)
@ -263,7 +263,7 @@ void Justifier::UpdateIRQEvent()
else else
target_line = current_line + 1; target_line = current_line + 1;
const TickCount ticks_until_pos = g_gpu->GetSystemTicksUntilTicksAndLine(m_irq_tick, target_line); const TickCount ticks_until_pos = g_gpu.GetSystemTicksUntilTicksAndLine(m_irq_tick, target_line);
DEBUG_LOG("Triggering IRQ in {} ticks @ tick {} line {}", ticks_until_pos, m_irq_tick, target_line); DEBUG_LOG("Triggering IRQ in {} ticks @ tick {} line {}", ticks_until_pos, m_irq_tick, target_line);
m_irq_event.Schedule(ticks_until_pos); m_irq_event.Schedule(ticks_until_pos);
} }

View File

@ -10,6 +10,7 @@
#include "common/assert.h" #include "common/assert.h"
#include "common/bitutils.h" #include "common/bitutils.h"
#include "common/log.h" #include "common/log.h"
#include "common/settings_interface.h"
#include "IconsPromptFont.h" #include "IconsPromptFont.h"
@ -261,14 +262,14 @@ std::unique_ptr<NeGcon> NeGcon::Create(u32 index)
static const Controller::ControllerBindingInfo s_binding_info[] = { static const Controller::ControllerBindingInfo s_binding_info[] = {
#define BUTTON(name, display_name, icon_name, button, genb) \ #define BUTTON(name, display_name, icon_name, button, genb) \
{ \ {name, display_name, icon_name, static_cast<u32>(button), InputBindingInfo::Type::Button, genb}
name, display_name, icon_name, static_cast<u32>(button), InputBindingInfo::Type::Button, genb \
}
#define AXIS(name, display_name, icon_name, halfaxis, genb) \ #define AXIS(name, display_name, icon_name, halfaxis, genb) \
{ \ {name, \
name, display_name, icon_name, static_cast<u32>(NeGcon::Button::Count) + static_cast<u32>(halfaxis), \ display_name, \
InputBindingInfo::Type::HalfAxis, genb \ icon_name, \
} static_cast<u32>(NeGcon::Button::Count) + static_cast<u32>(halfaxis), \
InputBindingInfo::Type::HalfAxis, \
genb}
// clang-format off // clang-format off
BUTTON("Up", TRANSLATE_NOOP("NeGcon", "D-Pad Up"), ICON_PF_DPAD_UP, NeGcon::Button::Up, GenericInputBinding::DPadUp), BUTTON("Up", TRANSLATE_NOOP("NeGcon", "D-Pad Up"), ICON_PF_DPAD_UP, NeGcon::Button::Up, GenericInputBinding::DPadUp),

View File

@ -3,6 +3,8 @@
#include "performance_counters.h" #include "performance_counters.h"
#include "gpu.h" #include "gpu.h"
#include "gpu_backend.h"
#include "gpu_thread.h"
#include "system.h" #include "system.h"
#include "system_private.h" #include "system_private.h"
@ -45,9 +47,9 @@ struct State
float cpu_thread_usage; float cpu_thread_usage;
float cpu_thread_time; float cpu_thread_time;
u64 last_sw_time; u64 last_gpu_thread_time;
float sw_thread_usage; float gpu_thread_usage;
float sw_thread_time; float gpu_thread_time;
float average_gpu_time; float average_gpu_time;
float accumulated_gpu_time; float accumulated_gpu_time;
@ -105,14 +107,14 @@ float PerformanceCounters::GetCPUThreadAverageTime()
return s_state.cpu_thread_time; return s_state.cpu_thread_time;
} }
float PerformanceCounters::GetSWThreadUsage() float PerformanceCounters::GetGPUThreadUsage()
{ {
return s_state.sw_thread_usage; return s_state.gpu_thread_usage;
} }
float PerformanceCounters::GetSWThreadAverageTime() float PerformanceCounters::GetGPUThreadAverageTime()
{ {
return s_state.sw_thread_time; return s_state.gpu_thread_time;
} }
float PerformanceCounters::GetGPUUsage() float PerformanceCounters::GetGPUUsage()
@ -150,17 +152,16 @@ void PerformanceCounters::Reset()
s_state.last_frame_number = System::GetFrameNumber(); s_state.last_frame_number = System::GetFrameNumber();
s_state.last_internal_frame_number = System::GetInternalFrameNumber(); s_state.last_internal_frame_number = System::GetInternalFrameNumber();
s_state.last_cpu_time = System::GetCPUThreadHandle().GetCPUTime(); s_state.last_cpu_time = System::GetCPUThreadHandle().GetCPUTime();
if (const Threading::Thread* sw_thread = g_gpu->GetSWThread(); sw_thread) s_state.last_gpu_thread_time = GPUThread::Internal::GetThreadHandle().GetCPUTime();
s_state.last_sw_time = sw_thread->GetCPUTime();
else
s_state.last_sw_time = 0;
s_state.average_frame_time_accumulator = 0.0f; s_state.average_frame_time_accumulator = 0.0f;
s_state.minimum_frame_time_accumulator = 0.0f; s_state.minimum_frame_time_accumulator = 0.0f;
s_state.maximum_frame_time_accumulator = 0.0f; s_state.maximum_frame_time_accumulator = 0.0f;
std::atomic_thread_fence(std::memory_order_release);
} }
void PerformanceCounters::Update(u32 frame_number, u32 internal_frame_number) void PerformanceCounters::Update(GPUBackend* gpu, u32 frame_number, u32 internal_frame_number)
{ {
const Timer::Value now_ticks = Timer::GetCurrentValue(); const Timer::Value now_ticks = Timer::GetCurrentValue();
@ -177,7 +178,7 @@ void PerformanceCounters::Update(u32 frame_number, u32 internal_frame_number)
// update fps counter // update fps counter
const Timer::Value ticks_diff = now_ticks - s_state.last_update_time; const Timer::Value ticks_diff = now_ticks - s_state.last_update_time;
const float time = static_cast<float>(Timer::ConvertValueToSeconds(ticks_diff)); const float time = static_cast<float>(Timer::ConvertValueToSeconds(ticks_diff));
if (time < PERFORMANCE_COUNTER_UPDATE_INTERVAL) if (time < PERFORMANCE_COUNTER_UPDATE_INTERVAL || s_state.last_frame_number == frame_number)
return; return;
s_state.last_update_time = now_ticks; s_state.last_update_time = now_ticks;
@ -202,18 +203,17 @@ void PerformanceCounters::Update(u32 frame_number, u32 internal_frame_number)
s_state.fps = static_cast<float>(internal_frames_run) / time; s_state.fps = static_cast<float>(internal_frames_run) / time;
s_state.speed = (s_state.vps / System::GetVideoFrameRate()) * 100.0f; s_state.speed = (s_state.vps / System::GetVideoFrameRate()) * 100.0f;
const Threading::Thread* sw_thread = g_gpu->GetSWThread();
const u64 cpu_time = System::GetCPUThreadHandle().GetCPUTime(); const u64 cpu_time = System::GetCPUThreadHandle().GetCPUTime();
const u64 sw_time = sw_thread ? sw_thread->GetCPUTime() : 0; const u64 gpu_thread_time = GPUThread::Internal::GetThreadHandle().GetCPUTime();
const u64 cpu_delta = cpu_time - s_state.last_cpu_time; const u64 cpu_delta = cpu_time - s_state.last_cpu_time;
const u64 sw_delta = sw_time - s_state.last_sw_time; const u64 gpu_thread_delta = gpu_thread_time - s_state.last_gpu_thread_time;
s_state.last_cpu_time = cpu_time; s_state.last_cpu_time = cpu_time;
s_state.last_sw_time = sw_time; s_state.last_gpu_thread_time = gpu_thread_time;
s_state.cpu_thread_usage = static_cast<float>(static_cast<double>(cpu_delta) * pct_divider); s_state.cpu_thread_usage = static_cast<float>(static_cast<double>(cpu_delta) * pct_divider);
s_state.cpu_thread_time = static_cast<float>(static_cast<double>(cpu_delta) * time_divider); s_state.cpu_thread_time = static_cast<float>(static_cast<double>(cpu_delta) * time_divider);
s_state.sw_thread_usage = static_cast<float>(static_cast<double>(sw_delta) * pct_divider); s_state.gpu_thread_usage = static_cast<float>(static_cast<double>(gpu_thread_delta) * pct_divider);
s_state.sw_thread_time = static_cast<float>(static_cast<double>(sw_delta) * time_divider); s_state.gpu_thread_time = static_cast<float>(static_cast<double>(gpu_thread_delta) * time_divider);
if (MediaCapture* cap = System::GetMediaCapture()) if (MediaCapture* cap = System::GetMediaCapture())
cap->UpdateCaptureThreadUsage(pct_divider, time_divider); cap->UpdateCaptureThreadUsage(pct_divider, time_divider);
@ -228,13 +228,13 @@ void PerformanceCounters::Update(u32 frame_number, u32 internal_frame_number)
s_state.presents_since_last_update = 0; s_state.presents_since_last_update = 0;
if (g_settings.display_show_gpu_stats) if (g_settings.display_show_gpu_stats)
g_gpu->UpdateStatistics(frames_run); gpu->UpdateStatistics(frames_run);
VERBOSE_LOG("FPS: {:.2f} VPS: {:.2f} CPU: {:.2f} GPU: {:.2f} Avg: {:.2f}ms Min: {:.2f}ms Max: {:.2f}ms", s_state.fps, VERBOSE_LOG("FPS: {:.2f} VPS: {:.2f} CPU: {:.2f} RNDR: {:.2f} GPU: {:.2f} Avg: {:.2f}ms Min: {:.2f}ms Max: {:.2f}ms",
s_state.vps, s_state.cpu_thread_usage, s_state.gpu_usage, s_state.average_frame_time, s_state.fps, s_state.vps, s_state.cpu_thread_usage, s_state.gpu_thread_usage, s_state.gpu_usage,
s_state.minimum_frame_time, s_state.maximum_frame_time); s_state.average_frame_time, s_state.minimum_frame_time, s_state.maximum_frame_time);
Host::OnPerformanceCountersUpdated(); Host::OnPerformanceCountersUpdated(gpu);
} }
void PerformanceCounters::AccumulateGPUTime() void PerformanceCounters::AccumulateGPUTime()

View File

@ -5,6 +5,8 @@
#include "common/types.h" #include "common/types.h"
class GPUBackend;
namespace PerformanceCounters namespace PerformanceCounters
{ {
static constexpr u32 NUM_FRAME_TIME_SAMPLES = 150; static constexpr u32 NUM_FRAME_TIME_SAMPLES = 150;
@ -18,8 +20,8 @@ float GetMinimumFrameTime();
float GetMaximumFrameTime(); float GetMaximumFrameTime();
float GetCPUThreadUsage(); float GetCPUThreadUsage();
float GetCPUThreadAverageTime(); float GetCPUThreadAverageTime();
float GetSWThreadUsage(); float GetGPUThreadUsage();
float GetSWThreadAverageTime(); float GetGPUThreadAverageTime();
float GetGPUUsage(); float GetGPUUsage();
float GetGPUAverageTime(); float GetGPUAverageTime();
const FrameTimeHistory& GetFrameTimeHistory(); const FrameTimeHistory& GetFrameTimeHistory();
@ -27,7 +29,7 @@ u32 GetFrameTimeHistoryPos();
void Clear(); void Clear();
void Reset(); void Reset();
void Update(u32 frame_number, u32 internal_frame_number); void Update(GPUBackend* gpu, u32 frame_number, u32 internal_frame_number);
void AccumulateGPUTime(); void AccumulateGPUTime();
} // namespace Host } // namespace Host

View File

@ -10,6 +10,7 @@
#include "common/assert.h" #include "common/assert.h"
#include "common/log.h" #include "common/log.h"
#include "common/settings_interface.h"
#include "IconsPromptFont.h" #include "IconsPromptFont.h"

View File

@ -5,6 +5,7 @@
#include "achievements.h" #include "achievements.h"
#include "controller.h" #include "controller.h"
#include "host.h" #include "host.h"
#include "imgui_overlays.h"
#include "system.h" #include "system.h"
#include "util/gpu_device.h" #include "util/gpu_device.h"
@ -28,7 +29,8 @@
LOG_CHANNEL(Settings); LOG_CHANNEL(Settings);
Settings g_settings; ALIGN_TO_CACHE_LINE Settings g_settings;
ALIGN_TO_CACHE_LINE GPUSettings g_gpu_settings;
const char* SettingInfo::StringDefaultValue() const const char* SettingInfo::StringDefaultValue() const
{ {
@ -86,6 +88,8 @@ float SettingInfo::FloatStepValue() const
return step_value ? StringUtil::FromChars<float>(step_value).value_or(fallback_value) : fallback_value; return step_value ? StringUtil::FromChars<float>(step_value).value_or(fallback_value) : fallback_value;
} }
GPUSettings::GPUSettings() = default;
#ifdef DYNAMIC_HOST_PAGE_SIZE #ifdef DYNAMIC_HOST_PAGE_SIZE
// See note in settings.h - 16K ends up faster with LUT because of nearby code/data. // See note in settings.h - 16K ends up faster with LUT because of nearby code/data.
const CPUFastmemMode Settings::DEFAULT_CPU_FASTMEM_MODE = const CPUFastmemMode Settings::DEFAULT_CPU_FASTMEM_MODE =
@ -206,6 +210,7 @@ void Settings::Load(const SettingsInterface& si, const SettingsInterface& contro
gpu_disable_compressed_textures = si.GetBoolValue("GPU", "DisableCompressedTextures", false); gpu_disable_compressed_textures = si.GetBoolValue("GPU", "DisableCompressedTextures", false);
gpu_per_sample_shading = si.GetBoolValue("GPU", "PerSampleShading", false); gpu_per_sample_shading = si.GetBoolValue("GPU", "PerSampleShading", false);
gpu_use_thread = si.GetBoolValue("GPU", "UseThread", true); gpu_use_thread = si.GetBoolValue("GPU", "UseThread", true);
gpu_max_queued_frames = static_cast<u8>(si.GetUIntValue("GPU", "MaxQueuedFrames", DEFAULT_GPU_MAX_QUEUED_FRAMES));
gpu_use_software_renderer_for_readbacks = si.GetBoolValue("GPU", "UseSoftwareRendererForReadbacks", false); gpu_use_software_renderer_for_readbacks = si.GetBoolValue("GPU", "UseSoftwareRendererForReadbacks", false);
gpu_true_color = si.GetBoolValue("GPU", "TrueColor", true); gpu_true_color = si.GetBoolValue("GPU", "TrueColor", true);
gpu_scaled_dithering = si.GetBoolValue("GPU", "ScaledDithering", true); gpu_scaled_dithering = si.GetBoolValue("GPU", "ScaledDithering", true);
@ -250,6 +255,9 @@ void Settings::Load(const SettingsInterface& si, const SettingsInterface& contro
gpu_pgxp_depth_buffer = si.GetBoolValue("GPU", "PGXPDepthBuffer", false); gpu_pgxp_depth_buffer = si.GetBoolValue("GPU", "PGXPDepthBuffer", false);
gpu_pgxp_disable_2d = si.GetBoolValue("GPU", "PGXPDisableOn2DPolygons", false); gpu_pgxp_disable_2d = si.GetBoolValue("GPU", "PGXPDisableOn2DPolygons", false);
SetPGXPDepthClearThreshold(si.GetFloatValue("GPU", "PGXPDepthClearThreshold", DEFAULT_GPU_PGXP_DEPTH_THRESHOLD)); SetPGXPDepthClearThreshold(si.GetFloatValue("GPU", "PGXPDepthClearThreshold", DEFAULT_GPU_PGXP_DEPTH_THRESHOLD));
gpu_show_vram = si.GetBoolValue("Debug", "ShowVRAM");
gpu_dump_cpu_to_vram_copies = si.GetBoolValue("Debug", "DumpCPUToVRAMCopies");
gpu_dump_vram_to_cpu_copies = si.GetBoolValue("Debug", "DumpVRAMToCPUCopies");
gpu_dump_fast_replay_mode = si.GetBoolValue("GPU", "DumpFastReplayMode", false); gpu_dump_fast_replay_mode = si.GetBoolValue("GPU", "DumpFastReplayMode", false);
display_deinterlacing_mode = display_deinterlacing_mode =
@ -421,13 +429,9 @@ void Settings::Load(const SettingsInterface& si, const SettingsInterface& contro
achievements_leaderboard_duration = achievements_leaderboard_duration =
si.GetIntValue("Cheevos", "LeaderboardsDuration", DEFAULT_LEADERBOARD_NOTIFICATION_TIME); si.GetIntValue("Cheevos", "LeaderboardsDuration", DEFAULT_LEADERBOARD_NOTIFICATION_TIME);
debugging.show_vram = si.GetBoolValue("Debug", "ShowVRAM");
debugging.dump_cpu_to_vram_copies = si.GetBoolValue("Debug", "DumpCPUToVRAMCopies");
debugging.dump_vram_to_cpu_copies = si.GetBoolValue("Debug", "DumpVRAMToCPUCopies");
#ifndef __ANDROID__ #ifndef __ANDROID__
debugging.enable_gdb_server = si.GetBoolValue("Debug", "EnableGDBServer"); enable_gdb_server = si.GetBoolValue("Debug", "EnableGDBServer");
debugging.gdb_server_port = static_cast<u16>(si.GetUIntValue("Debug", "GDBServerPort", DEFAULT_GDB_SERVER_PORT)); gdb_server_port = static_cast<u16>(si.GetUIntValue("Debug", "GDBServerPort", DEFAULT_GDB_SERVER_PORT));
#endif #endif
texture_replacements.enable_texture_replacements = texture_replacements.enable_texture_replacements =
@ -554,6 +558,7 @@ void Settings::Save(SettingsInterface& si, bool ignore_base) const
} }
si.SetBoolValue("GPU", "PerSampleShading", gpu_per_sample_shading); si.SetBoolValue("GPU", "PerSampleShading", gpu_per_sample_shading);
si.SetUIntValue("GPU", "MaxQueuedFrames", gpu_max_queued_frames);
si.SetBoolValue("GPU", "UseThread", gpu_use_thread); si.SetBoolValue("GPU", "UseThread", gpu_use_thread);
si.SetBoolValue("GPU", "UseSoftwareRendererForReadbacks", gpu_use_software_renderer_for_readbacks); si.SetBoolValue("GPU", "UseSoftwareRendererForReadbacks", gpu_use_software_renderer_for_readbacks);
si.SetBoolValue("GPU", "TrueColor", gpu_true_color); si.SetBoolValue("GPU", "TrueColor", gpu_true_color);
@ -581,6 +586,9 @@ void Settings::Save(SettingsInterface& si, bool ignore_base) const
si.SetBoolValue("GPU", "PGXPDepthBuffer", gpu_pgxp_depth_buffer); si.SetBoolValue("GPU", "PGXPDepthBuffer", gpu_pgxp_depth_buffer);
si.SetBoolValue("GPU", "PGXPDisableOn2DPolygons", gpu_pgxp_disable_2d); si.SetBoolValue("GPU", "PGXPDisableOn2DPolygons", gpu_pgxp_disable_2d);
si.SetFloatValue("GPU", "PGXPDepthClearThreshold", GetPGXPDepthClearThreshold()); si.SetFloatValue("GPU", "PGXPDepthClearThreshold", GetPGXPDepthClearThreshold());
si.SetBoolValue("Debug", "ShowVRAM", gpu_show_vram);
si.SetBoolValue("Debug", "DumpCPUToVRAMCopies", gpu_dump_cpu_to_vram_copies);
si.SetBoolValue("Debug", "DumpVRAMToCPUCopies", gpu_dump_vram_to_cpu_copies);
si.SetBoolValue("GPU", "DumpFastReplayMode", gpu_dump_fast_replay_mode); si.SetBoolValue("GPU", "DumpFastReplayMode", gpu_dump_fast_replay_mode);
si.SetStringValue("GPU", "DeinterlacingMode", GetDisplayDeinterlacingModeName(display_deinterlacing_mode)); si.SetStringValue("GPU", "DeinterlacingMode", GetDisplayDeinterlacingModeName(display_deinterlacing_mode));
@ -697,17 +705,10 @@ void Settings::Save(SettingsInterface& si, bool ignore_base) const
si.SetIntValue("Cheevos", "NotificationsDuration", achievements_notification_duration); si.SetIntValue("Cheevos", "NotificationsDuration", achievements_notification_duration);
si.SetIntValue("Cheevos", "LeaderboardsDuration", achievements_leaderboard_duration); si.SetIntValue("Cheevos", "LeaderboardsDuration", achievements_leaderboard_duration);
if (!ignore_base)
{
si.SetBoolValue("Debug", "ShowVRAM", debugging.show_vram);
si.SetBoolValue("Debug", "DumpCPUToVRAMCopies", debugging.dump_cpu_to_vram_copies);
si.SetBoolValue("Debug", "DumpVRAMToCPUCopies", debugging.dump_vram_to_cpu_copies);
#ifndef __ANDROID__ #ifndef __ANDROID__
si.SetBoolValue("Debug", "EnableGDBServer", debugging.enable_gdb_server); si.SetBoolValue("Debug", "EnableGDBServer", enable_gdb_server);
si.SetUIntValue("Debug", "GDBServerPort", debugging.gdb_server_port); si.SetUIntValue("Debug", "GDBServerPort", gdb_server_port);
#endif #endif
}
si.SetBoolValue("TextureReplacements", "EnableTextureReplacements", texture_replacements.enable_texture_replacements); si.SetBoolValue("TextureReplacements", "EnableTextureReplacements", texture_replacements.enable_texture_replacements);
si.SetBoolValue("TextureReplacements", "EnableVRAMWriteReplacements", si.SetBoolValue("TextureReplacements", "EnableVRAMWriteReplacements",
@ -950,7 +951,7 @@ std::string Settings::TextureReplacementSettings::Configuration::ExportToYAML(bo
comment_str, replacement_scale_linear_filter); // ReplacementScaleLinearFilter comment_str, replacement_scale_linear_filter); // ReplacementScaleLinearFilter
} }
void Settings::FixIncompatibleSettings(bool display_osd_messages) void Settings::FixIncompatibleSettings(const SettingsInterface& si, bool display_osd_messages)
{ {
if (g_settings.disable_all_enhancements) if (g_settings.disable_all_enhancements)
{ {
@ -1022,6 +1023,13 @@ void Settings::FixIncompatibleSettings(bool display_osd_messages)
(g_settings.gpu_renderer != GPURenderer::Software && g_settings.gpu_texture_cache); (g_settings.gpu_renderer != GPURenderer::Software && g_settings.gpu_texture_cache);
g_settings.texture_replacements.enable_vram_write_replacements &= (g_settings.gpu_renderer != GPURenderer::Software); g_settings.texture_replacements.enable_vram_write_replacements &= (g_settings.gpu_renderer != GPURenderer::Software);
// GPU thread should be disabled if any debug windows are active, since they will be racing to read CPU thread state.
if (g_settings.gpu_use_thread && g_settings.gpu_max_queued_frames > 0 && ImGuiManager::AreAnyDebugWindowsEnabled(si))
{
WARNING_LOG("Setting maximum queued frames to 0 because one or more debug windows are enabled.");
g_settings.gpu_max_queued_frames = 0;
}
#ifndef ENABLE_MMAP_FASTMEM #ifndef ENABLE_MMAP_FASTMEM
if (g_settings.cpu_fastmem_mode == CPUFastmemMode::MMap) if (g_settings.cpu_fastmem_mode == CPUFastmemMode::MMap)
{ {
@ -1072,15 +1080,30 @@ void Settings::FixIncompatibleSettings(bool display_osd_messages)
} }
#ifndef __ANDROID__ #ifndef __ANDROID__
g_settings.debugging.enable_gdb_server = false; g_settings.enable_gdb_server = false;
#endif #endif
g_settings.debugging.show_vram = false; g_settings.gpu_show_vram = false;
g_settings.debugging.dump_cpu_to_vram_copies = false; g_settings.gpu_dump_cpu_to_vram_copies = false;
g_settings.debugging.dump_vram_to_cpu_copies = false; g_settings.gpu_dump_vram_to_cpu_copies = false;
} }
} }
bool Settings::AreGPUDeviceSettingsChanged(const Settings& old_settings) const
{
return (gpu_use_debug_device != old_settings.gpu_use_debug_device ||
gpu_disable_shader_cache != old_settings.gpu_disable_shader_cache ||
gpu_disable_dual_source_blend != old_settings.gpu_disable_dual_source_blend ||
gpu_disable_framebuffer_fetch != old_settings.gpu_disable_framebuffer_fetch ||
gpu_disable_texture_buffers != old_settings.gpu_disable_texture_buffers ||
gpu_disable_texture_copy_to_self != old_settings.gpu_disable_texture_copy_to_self ||
gpu_disable_memory_import != old_settings.gpu_disable_memory_import ||
gpu_disable_raster_order_views != old_settings.gpu_disable_raster_order_views ||
gpu_disable_compute_shaders != old_settings.gpu_disable_compute_shaders ||
gpu_disable_compressed_textures != old_settings.gpu_disable_compressed_textures ||
display_exclusive_fullscreen_control != old_settings.display_exclusive_fullscreen_control);
}
void Settings::SetDefaultLogConfig(SettingsInterface& si) void Settings::SetDefaultLogConfig(SettingsInterface& si)
{ {
si.SetStringValue("Logging", "LogLevel", GetLogLevelName(DEFAULT_LOG_LEVEL)); si.SetStringValue("Logging", "LogLevel", GetLogLevelName(DEFAULT_LOG_LEVEL));
@ -1757,7 +1780,7 @@ const char* Settings::GetDisplayAspectRatioDisplayName(DisplayAspectRatio ar)
"DisplayAspectRatio"); "DisplayAspectRatio");
} }
float Settings::GetDisplayAspectRatioValue() const float GPUSettings::GetDisplayAspectRatioValue() const
{ {
return s_display_aspect_ratio_values[static_cast<size_t>(display_aspect_ratio)]; return s_display_aspect_ratio_values[static_cast<size_t>(display_aspect_ratio)];
} }

View File

@ -8,8 +8,6 @@
#include "util/audio_stream.h" #include "util/audio_stream.h"
#include "common/log.h" #include "common/log.h"
#include "common/settings_interface.h"
#include "common/small_string.h"
#include <array> #include <array>
#include <optional> #include <optional>
@ -18,6 +16,8 @@
#include <string_view> #include <string_view>
#include <vector> #include <vector>
class SettingsInterface;
enum class RenderAPI : u8; enum class RenderAPI : u8;
enum class MediaCaptureBackend : u8; enum class MediaCaptureBackend : u8;
@ -57,47 +57,17 @@ struct SettingInfo
float FloatStepValue() const; float FloatStepValue() const;
}; };
struct Settings struct GPUSettings
{ {
Settings(); GPUSettings();
ConsoleRegion region = DEFAULT_CONSOLE_REGION; std::string gpu_adapter;
CPUExecutionMode cpu_execution_mode = DEFAULT_CPU_EXECUTION_MODE;
CPUFastmemMode cpu_fastmem_mode = DEFAULT_CPU_FASTMEM_MODE;
bool cpu_overclock_enable : 1 = false;
bool cpu_overclock_active : 1 = false;
bool cpu_recompiler_memory_exceptions : 1 = false;
bool cpu_recompiler_block_linking : 1 = true;
bool cpu_recompiler_icache : 1 = false;
u32 cpu_overclock_numerator = 1;
u32 cpu_overclock_denominator = 1;
float emulation_speed = 1.0f;
float fast_forward_speed = 0.0f;
float turbo_speed = 0.0f;
bool sync_to_host_refresh_rate : 1 = false;
bool inhibit_screensaver : 1 = true;
bool pause_on_focus_loss : 1 = false;
bool pause_on_controller_disconnection : 1 = false;
bool save_state_on_exit : 1 = true;
bool create_save_state_backups : 1 = DEFAULT_SAVE_STATE_BACKUPS;
bool confim_power_off : 1 = true;
bool load_devices_from_save_states : 1 = false;
bool apply_compatibility_settings : 1 = true;
bool apply_game_settings : 1 = true;
bool disable_all_enhancements : 1 = false;
bool enable_discord_presence : 1 = false;
bool rewind_enable : 1 = false;
float rewind_save_frequency = 10.0f;
u16 rewind_save_slots = 10;
u8 runahead_frames = 0;
GPURenderer gpu_renderer = DEFAULT_GPU_RENDERER; GPURenderer gpu_renderer = DEFAULT_GPU_RENDERER;
std::string gpu_adapter;
u8 gpu_resolution_scale = 1; u8 gpu_resolution_scale = 1;
u8 gpu_multisamples = 1; u8 gpu_multisamples = 1;
u8 gpu_max_queued_frames = DEFAULT_GPU_MAX_QUEUED_FRAMES;
bool gpu_use_thread : 1 = true; bool gpu_use_thread : 1 = true;
bool gpu_use_software_renderer_for_readbacks : 1 = false; bool gpu_use_software_renderer_for_readbacks : 1 = false;
bool gpu_use_debug_device : 1 = false; bool gpu_use_debug_device : 1 = false;
@ -117,6 +87,11 @@ struct Settings
bool gpu_accurate_blending : 1 = false; bool gpu_accurate_blending : 1 = false;
bool gpu_widescreen_hack : 1 = false; bool gpu_widescreen_hack : 1 = false;
bool gpu_texture_cache : 1 = false; bool gpu_texture_cache : 1 = false;
bool gpu_show_vram : 1 = false;
bool gpu_dump_cpu_to_vram_copies : 1 = false;
bool gpu_dump_vram_to_cpu_copies : 1 = false;
bool gpu_dump_fast_replay_mode : 1 = false;
bool gpu_pgxp_enable : 1 = false; bool gpu_pgxp_enable : 1 = false;
bool gpu_pgxp_culling : 1 = true; bool gpu_pgxp_culling : 1 = true;
bool gpu_pgxp_texture_correction : 1 = true; bool gpu_pgxp_texture_correction : 1 = true;
@ -126,6 +101,7 @@ struct Settings
bool gpu_pgxp_preserve_proj_fp : 1 = false; bool gpu_pgxp_preserve_proj_fp : 1 = false;
bool gpu_pgxp_depth_buffer : 1 = false; bool gpu_pgxp_depth_buffer : 1 = false;
bool gpu_pgxp_disable_2d : 1 = false; bool gpu_pgxp_disable_2d : 1 = false;
ForceVideoTimingMode gpu_force_video_timing = DEFAULT_FORCE_VIDEO_TIMING_MODE; ForceVideoTimingMode gpu_force_video_timing = DEFAULT_FORCE_VIDEO_TIMING_MODE;
GPUTextureFilter gpu_texture_filter = DEFAULT_GPU_TEXTURE_FILTER; GPUTextureFilter gpu_texture_filter = DEFAULT_GPU_TEXTURE_FILTER;
GPUTextureFilter gpu_sprite_texture_filter = DEFAULT_GPU_TEXTURE_FILTER; GPUTextureFilter gpu_sprite_texture_filter = DEFAULT_GPU_TEXTURE_FILTER;
@ -175,62 +151,6 @@ struct Settings
float gpu_pgxp_tolerance = -1.0f; float gpu_pgxp_tolerance = -1.0f;
float gpu_pgxp_depth_clear_threshold = DEFAULT_GPU_PGXP_DEPTH_THRESHOLD / GPU_PGXP_DEPTH_THRESHOLD_SCALE; float gpu_pgxp_depth_clear_threshold = DEFAULT_GPU_PGXP_DEPTH_THRESHOLD / GPU_PGXP_DEPTH_THRESHOLD_SCALE;
SaveStateCompressionMode save_state_compression = DEFAULT_SAVE_STATE_COMPRESSION_MODE;
u8 cdrom_readahead_sectors = DEFAULT_CDROM_READAHEAD_SECTORS;
CDROMMechaconVersion cdrom_mechacon_version = DEFAULT_CDROM_MECHACON_VERSION;
bool cdrom_region_check : 1 = false;
bool cdrom_subq_skew : 1 = false;
bool cdrom_load_image_to_ram : 1 = false;
bool cdrom_load_image_patches : 1 = false;
bool cdrom_mute_cd_audio : 1 = false;
u32 cdrom_read_speedup = 1;
u32 cdrom_seek_speedup = 1;
std::string audio_driver;
std::string audio_output_device;
u32 audio_output_volume = 100;
u32 audio_fast_forward_volume = 100;
AudioStreamParameters audio_stream_parameters;
AudioBackend audio_backend = AudioStream::DEFAULT_BACKEND;
bool audio_output_muted : 1 = false;
bool use_old_mdec_routines : 1 = false;
bool pcdrv_enable : 1 = false;
bool export_shared_memory : 1 = false;
// timing hacks section
TickCount dma_max_slice_ticks = DEFAULT_DMA_MAX_SLICE_TICKS;
TickCount dma_halt_ticks = DEFAULT_DMA_HALT_TICKS;
u32 gpu_fifo_size = DEFAULT_GPU_FIFO_SIZE;
TickCount gpu_max_run_ahead = DEFAULT_GPU_MAX_RUN_AHEAD;
// achievements
bool achievements_enabled : 1 = false;
bool achievements_hardcore_mode : 1 = false;
bool achievements_notifications : 1 = true;
bool achievements_leaderboard_notifications : 1 = true;
bool achievements_sound_effects : 1 = true;
bool achievements_overlays : 1 = true;
bool achievements_encore_mode : 1 = false;
bool achievements_spectator_mode : 1 = false;
bool achievements_unofficial_test_mode : 1 = false;
bool achievements_use_raintegration : 1 = false;
s32 achievements_notification_duration = DEFAULT_ACHIEVEMENT_NOTIFICATION_TIME;
s32 achievements_leaderboard_duration = DEFAULT_LEADERBOARD_NOTIFICATION_TIME;
struct DebugSettings
{
#ifndef __ANDROID__
u16 gdb_server_port = DEFAULT_GDB_SERVER_PORT;
bool enable_gdb_server : 1 = false;
#endif
bool show_vram : 1 = false;
bool dump_cpu_to_vram_copies : 1 = false;
bool dump_vram_to_cpu_copies : 1 = false;
} debugging;
// texture replacements // texture replacements
struct TextureReplacementSettings struct TextureReplacementSettings
{ {
@ -284,11 +204,143 @@ struct Settings
bool operator!=(const TextureReplacementSettings& rhs) const; bool operator!=(const TextureReplacementSettings& rhs) const;
} texture_replacements; } texture_replacements;
float GetDisplayAspectRatioValue() const;
ALWAYS_INLINE bool IsUsingSoftwareRenderer() const { return (gpu_renderer == GPURenderer::Software); }
ALWAYS_INLINE bool IsUsingAccurateBlending() const { return (gpu_accurate_blending && !gpu_true_color); }
ALWAYS_INLINE bool UsingPGXPCPUMode() const { return gpu_pgxp_enable && gpu_pgxp_cpu; }
ALWAYS_INLINE bool UsingPGXPDepthBuffer() const { return gpu_pgxp_enable && gpu_pgxp_depth_buffer; }
ALWAYS_INLINE float GetPGXPDepthClearThreshold() const
{
return gpu_pgxp_depth_clear_threshold * GPU_PGXP_DEPTH_THRESHOLD_SCALE;
}
ALWAYS_INLINE void SetPGXPDepthClearThreshold(float value)
{
gpu_pgxp_depth_clear_threshold = value / GPU_PGXP_DEPTH_THRESHOLD_SCALE;
}
static constexpr GPURenderer DEFAULT_GPU_RENDERER = GPURenderer::Automatic;
static constexpr GPUTextureFilter DEFAULT_GPU_TEXTURE_FILTER = GPUTextureFilter::Nearest;
static constexpr GPULineDetectMode DEFAULT_GPU_LINE_DETECT_MODE = GPULineDetectMode::Disabled;
static constexpr GPUDownsampleMode DEFAULT_GPU_DOWNSAMPLE_MODE = GPUDownsampleMode::Disabled;
static constexpr GPUWireframeMode DEFAULT_GPU_WIREFRAME_MODE = GPUWireframeMode::Disabled;
static constexpr GPUDumpCompressionMode DEFAULT_GPU_DUMP_COMPRESSION_MODE = GPUDumpCompressionMode::ZstDefault;
static constexpr float DEFAULT_GPU_PGXP_DEPTH_THRESHOLD = 300.0f;
static constexpr float GPU_PGXP_DEPTH_THRESHOLD_SCALE = 4096.0f;
static constexpr DisplayDeinterlacingMode DEFAULT_DISPLAY_DEINTERLACING_MODE = DisplayDeinterlacingMode::Progressive;
static constexpr DisplayCropMode DEFAULT_DISPLAY_CROP_MODE = DisplayCropMode::Overscan;
static constexpr DisplayAspectRatio DEFAULT_DISPLAY_ASPECT_RATIO = DisplayAspectRatio::Auto;
static constexpr DisplayAlignment DEFAULT_DISPLAY_ALIGNMENT = DisplayAlignment::Center;
static constexpr DisplayRotation DEFAULT_DISPLAY_ROTATION = DisplayRotation::Normal;
static constexpr DisplayScalingMode DEFAULT_DISPLAY_SCALING = DisplayScalingMode::BilinearSmooth;
static constexpr ForceVideoTimingMode DEFAULT_FORCE_VIDEO_TIMING_MODE = ForceVideoTimingMode::Disabled;
static constexpr DisplayExclusiveFullscreenControl DEFAULT_DISPLAY_EXCLUSIVE_FULLSCREEN_CONTROL =
DisplayExclusiveFullscreenControl::Automatic;
static constexpr DisplayScreenshotMode DEFAULT_DISPLAY_SCREENSHOT_MODE = DisplayScreenshotMode::ScreenResolution;
static constexpr DisplayScreenshotFormat DEFAULT_DISPLAY_SCREENSHOT_FORMAT = DisplayScreenshotFormat::PNG;
static constexpr u8 DEFAULT_DISPLAY_SCREENSHOT_QUALITY = 85;
static constexpr float DEFAULT_DISPLAY_PRE_FRAME_SLEEP_BUFFER = 2.0f;
static constexpr float DEFAULT_OSD_SCALE = 100.0f;
#ifndef __ANDROID__
static constexpr u8 DEFAULT_GPU_MAX_QUEUED_FRAMES = 2;
#else
static constexpr u8 DEFAULT_GPU_MAX_QUEUED_FRAMES = 3;
#endif
};
struct Settings : public GPUSettings
{
Settings();
ConsoleRegion region = DEFAULT_CONSOLE_REGION;
CPUExecutionMode cpu_execution_mode = DEFAULT_CPU_EXECUTION_MODE;
CPUFastmemMode cpu_fastmem_mode = DEFAULT_CPU_FASTMEM_MODE;
bool cpu_overclock_enable : 1 = false;
bool cpu_overclock_active : 1 = false;
bool cpu_recompiler_memory_exceptions : 1 = false;
bool cpu_recompiler_block_linking : 1 = true;
bool cpu_recompiler_icache : 1 = false;
u32 cpu_overclock_numerator = 1;
u32 cpu_overclock_denominator = 1;
float emulation_speed = 1.0f;
float fast_forward_speed = 0.0f;
float turbo_speed = 0.0f;
bool sync_to_host_refresh_rate : 1 = false;
bool inhibit_screensaver : 1 = true;
bool pause_on_focus_loss : 1 = false;
bool pause_on_controller_disconnection : 1 = false;
bool save_state_on_exit : 1 = true;
bool create_save_state_backups : 1 = DEFAULT_SAVE_STATE_BACKUPS;
bool confim_power_off : 1 = true;
bool load_devices_from_save_states : 1 = false;
bool apply_compatibility_settings : 1 = true;
bool apply_game_settings : 1 = true;
bool disable_all_enhancements : 1 = false;
bool enable_discord_presence : 1 = false;
bool rewind_enable : 1 = false;
float rewind_save_frequency = 10.0f;
u16 rewind_save_slots = 10;
u8 runahead_frames = 0;
SaveStateCompressionMode save_state_compression = DEFAULT_SAVE_STATE_COMPRESSION_MODE;
u8 cdrom_readahead_sectors = DEFAULT_CDROM_READAHEAD_SECTORS;
CDROMMechaconVersion cdrom_mechacon_version = DEFAULT_CDROM_MECHACON_VERSION;
bool cdrom_region_check : 1 = false;
bool cdrom_subq_skew : 1 = false;
bool cdrom_load_image_to_ram : 1 = false;
bool cdrom_load_image_patches : 1 = false;
bool cdrom_mute_cd_audio : 1 = false;
u32 cdrom_read_speedup = 1;
u32 cdrom_seek_speedup = 1;
std::string audio_driver;
std::string audio_output_device;
u32 audio_output_volume = 100;
u32 audio_fast_forward_volume = 100;
AudioStreamParameters audio_stream_parameters;
AudioBackend audio_backend = AudioStream::DEFAULT_BACKEND;
bool audio_output_muted : 1 = false;
bool use_old_mdec_routines : 1 = false;
bool pcdrv_enable : 1 = false;
bool export_shared_memory : 1 = false;
// timing hacks section
TickCount dma_max_slice_ticks = DEFAULT_DMA_MAX_SLICE_TICKS;
TickCount dma_halt_ticks = DEFAULT_DMA_HALT_TICKS;
u32 gpu_fifo_size = DEFAULT_GPU_FIFO_SIZE;
TickCount gpu_max_run_ahead = DEFAULT_GPU_MAX_RUN_AHEAD;
// achievements
bool achievements_enabled : 1 = false;
bool achievements_hardcore_mode : 1 = false;
bool achievements_notifications : 1 = true;
bool achievements_leaderboard_notifications : 1 = true;
bool achievements_sound_effects : 1 = true;
bool achievements_overlays : 1 = true;
bool achievements_encore_mode : 1 = false;
bool achievements_spectator_mode : 1 = false;
bool achievements_unofficial_test_mode : 1 = false;
bool achievements_use_raintegration : 1 = false;
s32 achievements_notification_duration = DEFAULT_ACHIEVEMENT_NOTIFICATION_TIME;
s32 achievements_leaderboard_duration = DEFAULT_LEADERBOARD_NOTIFICATION_TIME;
#ifndef __ANDROID__
u16 gdb_server_port = DEFAULT_GDB_SERVER_PORT;
bool enable_gdb_server : 1 = false;
#endif
bool bios_tty_logging : 1 = false; bool bios_tty_logging : 1 = false;
bool bios_patch_fast_boot : 1 = DEFAULT_FAST_BOOT_VALUE; bool bios_patch_fast_boot : 1 = DEFAULT_FAST_BOOT_VALUE;
bool bios_fast_forward_boot : 1 = false; bool bios_fast_forward_boot : 1 = false;
bool enable_8mb_ram : 1 = false; bool enable_8mb_ram : 1 = false;
bool gpu_dump_fast_replay_mode : 1 = false;
std::array<ControllerType, NUM_CONTROLLER_AND_CARD_PORTS> controller_types{}; std::array<ControllerType, NUM_CONTROLLER_AND_CARD_PORTS> controller_types{};
std::array<MemoryCardType, NUM_CONTROLLER_AND_CARD_PORTS> memory_card_types{}; std::array<MemoryCardType, NUM_CONTROLLER_AND_CARD_PORTS> memory_card_types{};
@ -305,33 +357,14 @@ struct Settings
std::string pcdrv_root; std::string pcdrv_root;
bool pcdrv_enable_writes = false; bool pcdrv_enable_writes = false;
ALWAYS_INLINE bool IsUsingSoftwareRenderer() const { return (gpu_renderer == GPURenderer::Software); }
ALWAYS_INLINE bool IsUsingAccurateBlending() const { return (gpu_accurate_blending && !gpu_true_color); }
ALWAYS_INLINE bool IsRunaheadEnabled() const { return (runahead_frames > 0); } ALWAYS_INLINE bool IsRunaheadEnabled() const { return (runahead_frames > 0); }
ALWAYS_INLINE PGXPMode GetPGXPMode()
{
return gpu_pgxp_enable ? (gpu_pgxp_cpu ? PGXPMode::CPU : PGXPMode::Memory) : PGXPMode::Disabled;
}
ALWAYS_INLINE bool UsingPGXPDepthBuffer() const { return gpu_pgxp_enable && gpu_pgxp_depth_buffer; }
ALWAYS_INLINE bool UsingPGXPCPUMode() const { return gpu_pgxp_enable && gpu_pgxp_cpu; }
ALWAYS_INLINE float GetPGXPDepthClearThreshold() const
{
return gpu_pgxp_depth_clear_threshold * GPU_PGXP_DEPTH_THRESHOLD_SCALE;
}
ALWAYS_INLINE void SetPGXPDepthClearThreshold(float value)
{
gpu_pgxp_depth_clear_threshold = value / GPU_PGXP_DEPTH_THRESHOLD_SCALE;
}
ALWAYS_INLINE s32 GetAudioOutputVolume(bool fast_forwarding) const ALWAYS_INLINE s32 GetAudioOutputVolume(bool fast_forwarding) const
{ {
return audio_output_muted ? 0 : (fast_forwarding ? audio_fast_forward_volume : audio_output_volume); return audio_output_muted ? 0 : (fast_forwarding ? audio_fast_forward_volume : audio_output_volume);
} }
float GetDisplayAspectRatioValue() const;
ALWAYS_INLINE bool IsPort1MultitapEnabled() const ALWAYS_INLINE bool IsPort1MultitapEnabled() const
{ {
return (multitap_mode == MultitapMode::Port1Only || multitap_mode == MultitapMode::BothPorts); return (multitap_mode == MultitapMode::Port1Only || multitap_mode == MultitapMode::BothPorts);
@ -378,7 +411,9 @@ struct Settings
void Save(SettingsInterface& si, bool ignore_base) const; void Save(SettingsInterface& si, bool ignore_base) const;
static void Clear(SettingsInterface& si); static void Clear(SettingsInterface& si);
void FixIncompatibleSettings(bool display_osd_messages); void FixIncompatibleSettings(const SettingsInterface& si, bool display_osd_messages);
bool AreGPUDeviceSettingsChanged(const Settings& old_settings) const;
/// Initializes configuration. /// Initializes configuration.
static void SetDefaultLogConfig(SettingsInterface& si); static void SetDefaultLogConfig(SettingsInterface& si);
@ -495,15 +530,7 @@ struct Settings
static const char* GetPIODeviceTypeModeName(PIODeviceType type); static const char* GetPIODeviceTypeModeName(PIODeviceType type);
static const char* GetPIODeviceTypeModeDisplayName(PIODeviceType type); static const char* GetPIODeviceTypeModeDisplayName(PIODeviceType type);
static constexpr GPURenderer DEFAULT_GPU_RENDERER = GPURenderer::Automatic;
static constexpr GPUTextureFilter DEFAULT_GPU_TEXTURE_FILTER = GPUTextureFilter::Nearest;
static constexpr GPULineDetectMode DEFAULT_GPU_LINE_DETECT_MODE = GPULineDetectMode::Disabled;
static constexpr GPUDownsampleMode DEFAULT_GPU_DOWNSAMPLE_MODE = GPUDownsampleMode::Disabled;
static constexpr GPUWireframeMode DEFAULT_GPU_WIREFRAME_MODE = GPUWireframeMode::Disabled;
static constexpr GPUDumpCompressionMode DEFAULT_GPU_DUMP_COMPRESSION_MODE = GPUDumpCompressionMode::ZstDefault;
static constexpr ConsoleRegion DEFAULT_CONSOLE_REGION = ConsoleRegion::Auto; static constexpr ConsoleRegion DEFAULT_CONSOLE_REGION = ConsoleRegion::Auto;
static constexpr float DEFAULT_GPU_PGXP_DEPTH_THRESHOLD = 300.0f;
static constexpr float GPU_PGXP_DEPTH_THRESHOLD_SCALE = 4096.0f;
// Prefer recompiler when supported. // Prefer recompiler when supported.
#ifdef ENABLE_RECOMPILER #ifdef ENABLE_RECOMPILER
@ -521,21 +548,6 @@ struct Settings
static constexpr CPUFastmemMode DEFAULT_CPU_FASTMEM_MODE = CPUFastmemMode::LUT; static constexpr CPUFastmemMode DEFAULT_CPU_FASTMEM_MODE = CPUFastmemMode::LUT;
#endif #endif
static constexpr DisplayDeinterlacingMode DEFAULT_DISPLAY_DEINTERLACING_MODE = DisplayDeinterlacingMode::Progressive;
static constexpr DisplayCropMode DEFAULT_DISPLAY_CROP_MODE = DisplayCropMode::Overscan;
static constexpr DisplayAspectRatio DEFAULT_DISPLAY_ASPECT_RATIO = DisplayAspectRatio::Auto;
static constexpr DisplayAlignment DEFAULT_DISPLAY_ALIGNMENT = DisplayAlignment::Center;
static constexpr DisplayRotation DEFAULT_DISPLAY_ROTATION = DisplayRotation::Normal;
static constexpr DisplayScalingMode DEFAULT_DISPLAY_SCALING = DisplayScalingMode::BilinearSmooth;
static constexpr ForceVideoTimingMode DEFAULT_FORCE_VIDEO_TIMING_MODE = ForceVideoTimingMode::Disabled;
static constexpr DisplayExclusiveFullscreenControl DEFAULT_DISPLAY_EXCLUSIVE_FULLSCREEN_CONTROL =
DisplayExclusiveFullscreenControl::Automatic;
static constexpr DisplayScreenshotMode DEFAULT_DISPLAY_SCREENSHOT_MODE = DisplayScreenshotMode::ScreenResolution;
static constexpr DisplayScreenshotFormat DEFAULT_DISPLAY_SCREENSHOT_FORMAT = DisplayScreenshotFormat::PNG;
static constexpr u8 DEFAULT_DISPLAY_SCREENSHOT_QUALITY = 85;
static constexpr float DEFAULT_DISPLAY_PRE_FRAME_SLEEP_BUFFER = 2.0f;
static constexpr float DEFAULT_OSD_SCALE = 100.0f;
static constexpr u8 DEFAULT_CDROM_READAHEAD_SECTORS = 8; static constexpr u8 DEFAULT_CDROM_READAHEAD_SECTORS = 8;
static constexpr CDROMMechaconVersion DEFAULT_CDROM_MECHACON_VERSION = CDROMMechaconVersion::VC1A; static constexpr CDROMMechaconVersion DEFAULT_CDROM_MECHACON_VERSION = CDROMMechaconVersion::VC1A;
@ -571,7 +583,8 @@ struct Settings
#endif #endif
}; };
extern Settings g_settings; ALIGN_TO_CACHE_LINE extern Settings g_settings; // CPU thread copy.
ALIGN_TO_CACHE_LINE extern GPUSettings g_gpu_settings; // GPU thread copy.
namespace EmuFolders { namespace EmuFolders {
extern std::string AppRoot; extern std::string AppRoot;

File diff suppressed because it is too large Load Diff

View File

@ -165,7 +165,6 @@ std::unique_ptr<INISettingsInterface> GetGameSettingsInterface(const GameDatabas
std::string GetInputProfilePath(std::string_view name); std::string GetInputProfilePath(std::string_view name);
State GetState(); State GetState();
void SetState(State new_state);
bool IsRunning(); bool IsRunning();
bool IsPaused(); bool IsPaused();
bool IsShutdown(); bool IsShutdown();
@ -384,7 +383,7 @@ s32 GetAudioOutputVolume();
void UpdateVolume(); void UpdateVolume();
/// Saves a screenshot to the specified file. If no file name is provided, one will be generated automatically. /// Saves a screenshot to the specified file. If no file name is provided, one will be generated automatically.
bool SaveScreenshot(const char* path = nullptr, DisplayScreenshotMode mode = g_settings.display_screenshot_mode, void SaveScreenshot(const char* path = nullptr, DisplayScreenshotMode mode = g_settings.display_screenshot_mode,
DisplayScreenshotFormat format = g_settings.display_screenshot_format, DisplayScreenshotFormat format = g_settings.display_screenshot_format,
u8 quality = g_settings.display_screenshot_quality, bool compress_on_thread = true); u8 quality = g_settings.display_screenshot_quality, bool compress_on_thread = true);
@ -400,7 +399,6 @@ MediaCapture* GetMediaCapture();
/// Media capture (video and/or audio). If no path is provided, one will be generated automatically. /// Media capture (video and/or audio). If no path is provided, one will be generated automatically.
bool StartMediaCapture(std::string path = {}); bool StartMediaCapture(std::string path = {});
bool StartMediaCapture(std::string path, bool capture_video, bool capture_audio);
void StopMediaCapture(); void StopMediaCapture();
/// Toggle Widescreen Hack and Aspect Ratio /// Toggle Widescreen Hack and Aspect Ratio
@ -413,15 +411,11 @@ void ToggleSoftwareRendering();
/// If the scale is set to 0, the internal resolution will be used, otherwise it is treated as a multiplier to 1x. /// If the scale is set to 0, the internal resolution will be used, otherwise it is treated as a multiplier to 1x.
void RequestDisplaySize(float scale = 0.0f); void RequestDisplaySize(float scale = 0.0f);
/// Renders the display.
bool PresentDisplay(bool explicit_present, u64 present_time);
void InvalidateDisplay();
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
// Memory Save States (Rewind and Runahead) // Memory Save States (Rewind and Runahead)
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
void CalculateRewindMemoryUsage(u32 num_saves, u32 resolution_scale, u64* ram_usage, u64* vram_usage); void CalculateRewindMemoryUsage(u32 num_saves, u32 resolution_scale, u64* ram_usage, u64* vram_usage);
void ClearMemorySaveStates(); void ClearMemorySaveStates(bool reallocate_resources);
void SetRunaheadReplayFlag(); void SetRunaheadReplayFlag();
/// Shared socket multiplexer, used by PINE/GDB/etc. /// Shared socket multiplexer, used by PINE/GDB/etc.

View File

@ -7,24 +7,32 @@
#include <functional> #include <functional>
class GPUBackend;
struct GPUBackendFramePresentationParameters;
namespace System { namespace System {
/// Memory save states - only for internal use. /// Memory save states - only for internal use.
struct MemorySaveState struct MemorySaveState
{ {
std::unique_ptr<GPUTexture> vram_texture;
DynamicHeapArray<u8> state_data; DynamicHeapArray<u8> state_data;
size_t state_size; size_t state_size;
std::unique_ptr<GPUTexture> vram_texture;
DynamicHeapArray<u8> gpu_state_data;
size_t gpu_state_size;
}; };
bool SaveMemoryState(MemorySaveState* mss); MemorySaveState& AllocateMemoryState();
bool LoadMemoryState(const MemorySaveState& mss); MemorySaveState& GetFirstMemoryState();
MemorySaveState& PopMemoryState();
/// Returns the maximum size of a save state, considering the current configuration. bool AllocateMemoryStates(size_t state_count);
size_t GetMaxSaveStateSize(); void FreeMemoryStateTextures();
void FreeMemoryStateStorage();
bool DoState(StateWrapper& sw, GPUTexture** host_texture, bool update_display, bool is_memory_state); void LoadMemoryState(MemorySaveState& mss, bool update_display);
void SaveMemoryState(MemorySaveState& mss);
bool IsRunaheadActive();
void IncrementFrameNumber(); void IncrementFrameNumber();
void IncrementInternalFrameNumber(); void IncrementInternalFrameNumber();
void FrameDone(); void FrameDone();
@ -33,6 +41,10 @@ void FrameDone();
GPUVSyncMode GetEffectiveVSyncMode(); GPUVSyncMode GetEffectiveVSyncMode();
bool ShouldAllowPresentThrottle(); bool ShouldAllowPresentThrottle();
/// Retrieves timing information for frame presentation on the GPU thread.
/// Returns false if this frame should not be presented or the command buffer flushed.
bool GetFramePresentationParameters(GPUBackendFramePresentationParameters* frame);
/// Call when host display size changes. /// Call when host display size changes.
void DisplayWindowResized(); void DisplayWindowResized();
@ -63,6 +75,7 @@ void IdlePollUpdate();
/// Task threads, asynchronous work which will block system shutdown. /// Task threads, asynchronous work which will block system shutdown.
void QueueTaskOnThread(std::function<void()> task); void QueueTaskOnThread(std::function<void()> task);
void RemoveSelfFromTaskThreads(); void RemoveSelfFromTaskThreads();
void JoinTaskThreads();
} // namespace System } // namespace System
@ -89,11 +102,8 @@ void OnSystemPaused();
/// Called when the VM is resumed after being paused. /// Called when the VM is resumed after being paused.
void OnSystemResumed(); void OnSystemResumed();
/// Called when the pause state changes, or fullscreen UI opens.
void OnIdleStateChanged();
/// Called when performance metrics are updated, approximately once a second. /// Called when performance metrics are updated, approximately once a second.
void OnPerformanceCountersUpdated(); void OnPerformanceCountersUpdated(const GPUBackend* gpu_backend);
/// Provided by the host; called when the running executable changes. /// Provided by the host; called when the running executable changes.
void OnGameChanged(const std::string& disc_path, const std::string& game_serial, const std::string& game_name); void OnGameChanged(const std::string& disc_path, const std::string& game_serial, const std::string& game_name);

View File

@ -315,8 +315,8 @@ u32 Timers::ReadRegister(u32 offset)
if (timer_index < 2 && cs.external_counting_enabled) if (timer_index < 2 && cs.external_counting_enabled)
{ {
// timers 0/1 depend on the GPU // timers 0/1 depend on the GPU
if (timer_index == 0 || g_gpu->IsCRTCScanlinePending()) if (timer_index == 0 || g_gpu.IsCRTCScanlinePending())
g_gpu->SynchronizeCRTC(); g_gpu.SynchronizeCRTC();
} }
s_state.sysclk_event.InvokeEarly(); s_state.sysclk_event.InvokeEarly();
@ -329,8 +329,8 @@ u32 Timers::ReadRegister(u32 offset)
if (timer_index < 2 && cs.external_counting_enabled) if (timer_index < 2 && cs.external_counting_enabled)
{ {
// timers 0/1 depend on the GPU // timers 0/1 depend on the GPU
if (timer_index == 0 || g_gpu->IsCRTCScanlinePending()) if (timer_index == 0 || g_gpu.IsCRTCScanlinePending())
g_gpu->SynchronizeCRTC(); g_gpu.SynchronizeCRTC();
} }
s_state.sysclk_event.InvokeEarly(); s_state.sysclk_event.InvokeEarly();
@ -365,8 +365,8 @@ void Timers::WriteRegister(u32 offset, u32 value)
if (timer_index < 2 && cs.external_counting_enabled) if (timer_index < 2 && cs.external_counting_enabled)
{ {
// timers 0/1 depend on the GPU // timers 0/1 depend on the GPU
if (timer_index == 0 || g_gpu->IsCRTCScanlinePending()) if (timer_index == 0 || g_gpu.IsCRTCScanlinePending())
g_gpu->SynchronizeCRTC(); g_gpu.SynchronizeCRTC();
} }
s_state.sysclk_event.InvokeEarly(); s_state.sysclk_event.InvokeEarly();

View File

@ -10,6 +10,7 @@
#include "common/assert.h" #include "common/assert.h"
#include "common/log.h" #include "common/log.h"
#include "common/small_string.h"
#include "common/thirdparty/SmallVector.h" #include "common/thirdparty/SmallVector.h"
LOG_CHANNEL(TimingEvents); LOG_CHANNEL(TimingEvents);

View File

@ -241,14 +241,14 @@ void DebuggerWindow::onBreakpointListItemChanged(QTreeWidgetItem* item, int colu
void DebuggerWindow::onStepIntoActionTriggered() void DebuggerWindow::onStepIntoActionTriggered()
{ {
Assert(System::IsPaused()); Assert(QtHost::IsSystemPaused());
saveCurrentState(); saveCurrentState();
g_emu_thread->singleStepCPU(); g_emu_thread->singleStepCPU();
} }
void DebuggerWindow::onStepOverActionTriggered() void DebuggerWindow::onStepOverActionTriggered()
{ {
Assert(System::IsPaused()); Assert(QtHost::IsSystemPaused());
if (!CPU::AddStepOverBreakpoint()) if (!CPU::AddStepOverBreakpoint())
{ {
onStepIntoActionTriggered(); onStepIntoActionTriggered();
@ -262,7 +262,7 @@ void DebuggerWindow::onStepOverActionTriggered()
void DebuggerWindow::onStepOutActionTriggered() void DebuggerWindow::onStepOutActionTriggered()
{ {
Assert(System::IsPaused()); Assert(QtHost::IsSystemPaused());
if (!CPU::AddStepOutBreakpoint()) if (!CPU::AddStepOutBreakpoint())
{ {
QMessageBox::critical(this, tr("Debugger"), tr("Failed to add step-out breakpoint, are you in a valid function?")); QMessageBox::critical(this, tr("Debugger"), tr("Failed to add step-out breakpoint, are you in a valid function?"));

View File

@ -293,6 +293,9 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
// Debugging Tab // Debugging Tab
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.gpuThread, "GPU", "UseThread", true); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.gpuThread, "GPU", "UseThread", true);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.maxQueuedFrames, "GPU", "MaxQueuedFrames",
Settings::DEFAULT_GPU_MAX_QUEUED_FRAMES);
connect(m_ui.gpuThread, &QCheckBox::checkStateChanged, this, &GraphicsSettingsWidget::onGPUThreadChanged);
SettingWidgetBinder::BindWidgetToEnumSetting( SettingWidgetBinder::BindWidgetToEnumSetting(
sif, m_ui.gpuDumpCompressionMode, "GPU", "DumpCompressionMode", &Settings::ParseGPUDumpCompressionMode, sif, m_ui.gpuDumpCompressionMode, "GPU", "DumpCompressionMode", &Settings::ParseGPUDumpCompressionMode,
@ -325,6 +328,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
onMediaCaptureVideoEnabledChanged(); onMediaCaptureVideoEnabledChanged();
onEnableTextureCacheChanged(); onEnableTextureCacheChanged();
onEnableAnyTextureReplacementsChanged(); onEnableAnyTextureReplacementsChanged();
onGPUThreadChanged();
onShowDebugSettingsChanged(QtHost::ShouldShowDebugOptions()); onShowDebugSettingsChanged(QtHost::ShouldShowDebugOptions());
// Rendering Tab // Rendering Tab
@ -610,8 +614,8 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
tr("Draws a wireframe outline of the triangles rendered by the console's GPU, either as a " tr("Draws a wireframe outline of the triangles rendered by the console's GPU, either as a "
"replacement or an overlay.")); "replacement or an overlay."));
dialog->registerWidgetHelp(m_ui.gpuThread, tr("Threaded Rendering"), tr("Checked"), dialog->registerWidgetHelp(m_ui.gpuThread, tr("Threaded Rendering"), tr("Checked"),
tr("Uses a second thread for drawing graphics. Currently only available for the software " tr("Uses a second thread for drawing graphics. Provides a significant speed improvement "
"renderer, but can provide a significant speed improvement, and is safe to use.")); "particularly with the software renderer, and is safe to use."));
dialog->registerWidgetHelp( dialog->registerWidgetHelp(
m_ui.useDebugDevice, tr("Use Debug Device"), tr("Unchecked"), m_ui.useDebugDevice, tr("Use Debug Device"), tr("Unchecked"),
@ -819,8 +823,6 @@ void GraphicsSettingsWidget::updateRendererDependentOptions()
m_ui.blitSwapChain->setEnabled(render_api == RenderAPI::D3D11); m_ui.blitSwapChain->setEnabled(render_api == RenderAPI::D3D11);
#endif #endif
m_ui.gpuThread->setEnabled(!is_hardware);
m_ui.exclusiveFullscreenLabel->setEnabled(render_api == RenderAPI::D3D11 || render_api == RenderAPI::D3D12 || m_ui.exclusiveFullscreenLabel->setEnabled(render_api == RenderAPI::D3D11 || render_api == RenderAPI::D3D12 ||
render_api == RenderAPI::Vulkan); render_api == RenderAPI::Vulkan);
m_ui.exclusiveFullscreenControl->setEnabled(render_api == RenderAPI::Vulkan); m_ui.exclusiveFullscreenControl->setEnabled(render_api == RenderAPI::Vulkan);
@ -1181,6 +1183,13 @@ void GraphicsSettingsWidget::onEnableAnyTextureReplacementsChanged()
m_ui.preloadTextureReplacements->setEnabled(any_replacements_enabled); m_ui.preloadTextureReplacements->setEnabled(any_replacements_enabled);
} }
void GraphicsSettingsWidget::onGPUThreadChanged()
{
const bool enabled = m_dialog->getEffectiveBoolValue("GPU", "UseThread", true);
m_ui.maxQueuedFrames->setEnabled(enabled);
m_ui.maxQueuedFramesLabel->setEnabled(enabled);
}
void GraphicsSettingsWidget::onTextureReplacementOptionsClicked() void GraphicsSettingsWidget::onTextureReplacementOptionsClicked()
{ {
QDialog dlg(QtUtils::GetRootWidget(this)); QDialog dlg(QtUtils::GetRootWidget(this));

View File

@ -44,6 +44,8 @@ private Q_SLOTS:
void onEnableAnyTextureReplacementsChanged(); void onEnableAnyTextureReplacementsChanged();
void onTextureReplacementOptionsClicked(); void onTextureReplacementOptionsClicked();
void onGPUThreadChanged();
private: private:
static constexpr int TAB_INDEX_RENDERING = 0; static constexpr int TAB_INDEX_RENDERING = 0;
static constexpr int TAB_INDEX_ADVANCED = 1; static constexpr int TAB_INDEX_ADVANCED = 1;

View File

@ -1286,11 +1286,29 @@
<item row="1" column="0" colspan="2"> <item row="1" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_4"> <layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="0"> <item row="0" column="0">
<widget class="QCheckBox" name="gpuThread"> <layout class="QHBoxLayout" name="horizontalLayout_7" stretch="1,0,0">
<property name="text"> <item>
<string>Threaded Rendering</string> <widget class="QCheckBox" name="gpuThread">
</property> <property name="text">
</widget> <string>Threaded Rendering</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="maxQueuedFramesLabel">
<property name="text">
<string>Max Queued Frames:</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="maxQueuedFrames">
<property name="maximum">
<number>10</number>
</property>
</widget>
</item>
</layout>
</item> </item>
</layout> </layout>
</item> </item>

View File

@ -85,6 +85,7 @@ static bool s_use_central_widget = false;
// UI thread VM validity. // UI thread VM validity.
static bool s_system_valid = false; static bool s_system_valid = false;
static bool s_system_paused = false; static bool s_system_paused = false;
static bool s_fullscreen_ui_started = false;
static std::atomic_uint32_t s_system_locked{false}; static std::atomic_uint32_t s_system_locked{false};
static QString s_current_game_title; static QString s_current_game_title;
static QString s_current_game_serial; static QString s_current_game_serial;
@ -764,7 +765,7 @@ void MainWindow::recreate()
{ {
g_emu_thread->setSurfaceless(false); g_emu_thread->setSurfaceless(false);
g_main_window->updateEmulationActions(false, System::IsValid(), Achievements::IsHardcoreModeActive()); g_main_window->updateEmulationActions(false, System::IsValid(), Achievements::IsHardcoreModeActive());
g_main_window->onFullscreenUIStateChange(g_emu_thread->isRunningFullscreenUI()); g_main_window->onFullscreenUIStartedOrStopped(s_fullscreen_ui_started);
} }
if (controller_settings_window_pos.has_value()) if (controller_settings_window_pos.has_value())
@ -1258,8 +1259,9 @@ void MainWindow::onStartFullscreenUITriggered()
g_emu_thread->startFullscreenUI(); g_emu_thread->startFullscreenUI();
} }
void MainWindow::onFullscreenUIStateChange(bool running) void MainWindow::onFullscreenUIStartedOrStopped(bool running)
{ {
s_fullscreen_ui_started = running;
m_ui.actionStartFullscreenUI->setText(running ? tr("Stop Big Picture Mode") : tr("Start Big Picture Mode")); m_ui.actionStartFullscreenUI->setText(running ? tr("Stop Big Picture Mode") : tr("Start Big Picture Mode"));
m_ui.actionStartFullscreenUI2->setText(running ? tr("Exit Big Picture") : tr("Big Picture")); m_ui.actionStartFullscreenUI2->setText(running ? tr("Exit Big Picture") : tr("Big Picture"));
} }
@ -2046,7 +2048,7 @@ void MainWindow::connectSignals()
connect(g_emu_thread, &EmuThread::mediaCaptureStarted, this, &MainWindow::onMediaCaptureStarted); connect(g_emu_thread, &EmuThread::mediaCaptureStarted, this, &MainWindow::onMediaCaptureStarted);
connect(g_emu_thread, &EmuThread::mediaCaptureStopped, this, &MainWindow::onMediaCaptureStopped); connect(g_emu_thread, &EmuThread::mediaCaptureStopped, this, &MainWindow::onMediaCaptureStopped);
connect(g_emu_thread, &EmuThread::mouseModeRequested, this, &MainWindow::onMouseModeRequested); connect(g_emu_thread, &EmuThread::mouseModeRequested, this, &MainWindow::onMouseModeRequested);
connect(g_emu_thread, &EmuThread::fullscreenUIStateChange, this, &MainWindow::onFullscreenUIStateChange); connect(g_emu_thread, &EmuThread::fullscreenUIStartedOrStopped, this, &MainWindow::onFullscreenUIStartedOrStopped);
connect(g_emu_thread, &EmuThread::achievementsLoginRequested, this, &MainWindow::onAchievementsLoginRequested); connect(g_emu_thread, &EmuThread::achievementsLoginRequested, this, &MainWindow::onAchievementsLoginRequested);
connect(g_emu_thread, &EmuThread::achievementsChallengeModeChanged, this, connect(g_emu_thread, &EmuThread::achievementsChallengeModeChanged, this,
&MainWindow::onAchievementsChallengeModeChanged); &MainWindow::onAchievementsChallengeModeChanged);
@ -2503,7 +2505,7 @@ bool MainWindow::requestShutdown(bool allow_confirm /* = true */, bool allow_sav
// reshow the main window during display updates, because otherwise fullscreen transitions and renderer switches // reshow the main window during display updates, because otherwise fullscreen transitions and renderer switches
// would briefly show and then hide the main window. So instead, we do it on shutdown, here. Except if we're in // would briefly show and then hide the main window. So instead, we do it on shutdown, here. Except if we're in
// batch mode, when we're going to exit anyway. // batch mode, when we're going to exit anyway.
if (!isRenderingToMain() && isHidden() && !QtHost::InBatchMode() && !g_emu_thread->isRunningFullscreenUI()) if (!isRenderingToMain() && isHidden() && !QtHost::InBatchMode() && !s_fullscreen_ui_started)
updateWindowState(true); updateWindowState(true);
// Now we can actually shut down the VM. // Now we can actually shut down the VM.

View File

@ -168,7 +168,7 @@ private Q_SLOTS:
void onCheatsActionTriggered(); void onCheatsActionTriggered();
void onCheatsMenuAboutToShow(); void onCheatsMenuAboutToShow();
void onStartFullscreenUITriggered(); void onStartFullscreenUITriggered();
void onFullscreenUIStateChange(bool running); void onFullscreenUIStartedOrStopped(bool running);
void onRemoveDiscActionTriggered(); void onRemoveDiscActionTriggered();
void onScanForNewGamesTriggered(); void onScanForNewGamesTriggered();
void onViewToolbarActionToggled(bool checked); void onViewToolbarActionToggled(bool checked);

View File

@ -19,7 +19,9 @@
#include "core/game_list.h" #include "core/game_list.h"
#include "core/gdb_server.h" #include "core/gdb_server.h"
#include "core/gpu.h" #include "core/gpu.h"
#include "core/gpu_backend.h"
#include "core/gpu_hw_texture_cache.h" #include "core/gpu_hw_texture_cache.h"
#include "core/gpu_thread.h"
#include "core/host.h" #include "core/host.h"
#include "core/imgui_overlays.h" #include "core/imgui_overlays.h"
#include "core/memory_card.h" #include "core/memory_card.h"
@ -223,7 +225,6 @@ bool QtHost::SaveGameSettings(SettingsInterface* sif, bool delete_if_empty)
INISettingsInterface* ini = static_cast<INISettingsInterface*>(sif); INISettingsInterface* ini = static_cast<INISettingsInterface*>(sif);
Error error; Error error;
// if there's no keys, just toss the whole thing out // if there's no keys, just toss the whole thing out
if (delete_if_empty && ini->IsEmpty()) if (delete_if_empty && ini->IsEmpty())
{ {
@ -576,13 +577,8 @@ void Host::LoadSettings(const SettingsInterface& si, std::unique_lock<std::mutex
void EmuThread::checkForSettingsChanges(const Settings& old_settings) void EmuThread::checkForSettingsChanges(const Settings& old_settings)
{ {
if (g_main_window) if (g_main_window)
{
QMetaObject::invokeMethod(g_main_window, &MainWindow::checkForSettingChanges, Qt::QueuedConnection); QMetaObject::invokeMethod(g_main_window, &MainWindow::checkForSettingChanges, Qt::QueuedConnection);
if (System::IsValid())
updatePerformanceCounters();
}
// don't mess with fullscreen while locked // don't mess with fullscreen while locked
if (!QtHost::IsSystemLocked()) if (!QtHost::IsSystemLocked())
{ {
@ -591,7 +587,7 @@ void EmuThread::checkForSettingsChanges(const Settings& old_settings)
{ {
m_is_rendering_to_main = render_to_main; m_is_rendering_to_main = render_to_main;
if (g_gpu_device) if (g_gpu_device)
Host::UpdateDisplayWindow(m_is_fullscreen); GPUThread::UpdateDisplayWindow(m_is_fullscreen);
} }
} }
} }
@ -730,33 +726,24 @@ void EmuThread::startFullscreenUI()
return; return;
} }
if (System::IsValid()) if (System::IsValid() || m_is_fullscreen_ui_started)
return; return;
// we want settings loaded so we choose the correct renderer // we want settings loaded so we choose the correct renderer
// this also sorts out input sources. // this also sorts out input sources.
System::LoadSettings(false); System::LoadSettings(false);
m_is_rendering_to_main = shouldRenderToMain(); m_is_rendering_to_main = shouldRenderToMain();
m_run_fullscreen_ui = true;
// borrow the game start fullscreen flag // borrow the game start fullscreen flag
const bool start_fullscreen = const bool start_fullscreen =
(s_start_fullscreen_ui_fullscreen || Host::GetBaseBoolSettingValue("Main", "StartFullscreen", false)); (s_start_fullscreen_ui_fullscreen || Host::GetBaseBoolSettingValue("Main", "StartFullscreen", false));
Error error; Error error;
if (!Host::CreateGPUDevice(Settings::GetRenderAPIForRenderer(g_settings.gpu_renderer), start_fullscreen, &error) || if (!GPUThread::StartFullscreenUI(start_fullscreen, &error))
!FullscreenUI::Initialize())
{ {
Host::ReportErrorAsync("Error", error.GetDescription()); Host::ReportErrorAsync("Error", error.GetDescription());
m_run_fullscreen_ui = false;
return; return;
} }
emit fullscreenUIStateChange(true);
// poll more frequently so we don't lose events
stopBackgroundControllerPollTimer();
startBackgroundControllerPollTimer();
} }
void EmuThread::stopFullscreenUI() void EmuThread::stopFullscreenUI()
@ -771,18 +758,8 @@ void EmuThread::stopFullscreenUI()
return; return;
} }
setFullscreen(false, true); if (m_is_fullscreen_ui_started)
GPUThread::StopFullscreenUI();
if (m_run_fullscreen_ui)
{
m_run_fullscreen_ui = false;
emit fullscreenUIStateChange(false);
}
if (!g_gpu_device)
return;
Host::ReleaseGPUDevice();
} }
void EmuThread::bootSystem(std::shared_ptr<SystemBootParameters> params) void EmuThread::bootSystem(std::shared_ptr<SystemBootParameters> params)
@ -889,7 +866,7 @@ void EmuThread::onDisplayWindowMouseWheelEvent(const QPoint& delta_angle)
void EmuThread::onDisplayWindowResized(int width, int height, float scale) void EmuThread::onDisplayWindowResized(int width, int height, float scale)
{ {
Host::ResizeDisplayWindow(width, height, scale); GPUThread::ResizeDisplayWindow(width, height, scale);
} }
void EmuThread::redrawDisplayWindow() void EmuThread::redrawDisplayWindow()
@ -900,10 +877,10 @@ void EmuThread::redrawDisplayWindow()
return; return;
} }
if (!g_gpu_device || System::IsShutdown()) if (System::IsShutdown())
return; return;
System::InvalidateDisplay(); GPUThread::PresentCurrentFrame();
} }
void EmuThread::toggleFullscreen() void EmuThread::toggleFullscreen()
@ -931,7 +908,7 @@ void EmuThread::setFullscreen(bool fullscreen, bool allow_render_to_main)
m_is_fullscreen = fullscreen; m_is_fullscreen = fullscreen;
m_is_rendering_to_main = allow_render_to_main && shouldRenderToMain(); m_is_rendering_to_main = allow_render_to_main && shouldRenderToMain();
Host::UpdateDisplayWindow(fullscreen); GPUThread::UpdateDisplayWindow(fullscreen);
} }
bool Host::IsFullscreen() bool Host::IsFullscreen()
@ -960,7 +937,7 @@ void EmuThread::setSurfaceless(bool surfaceless)
return; return;
m_is_surfaceless = surfaceless; m_is_surfaceless = surfaceless;
Host::UpdateDisplayWindow(false); GPUThread::UpdateDisplayWindow(false);
} }
void EmuThread::requestDisplaySize(float scale) void EmuThread::requestDisplaySize(float scale)
@ -1017,6 +994,7 @@ void Host::OnSystemStarting()
void Host::OnSystemStarted() void Host::OnSystemStarted()
{ {
g_emu_thread->stopBackgroundControllerPollTimer(); g_emu_thread->stopBackgroundControllerPollTimer();
g_emu_thread->wakeThread();
emit g_emu_thread->systemStarted(); emit g_emu_thread->systemStarted();
} }
@ -1034,6 +1012,7 @@ void Host::OnSystemResumed()
g_emu_thread->setSurfaceless(false); g_emu_thread->setSurfaceless(false);
emit g_emu_thread->systemResumed(); emit g_emu_thread->systemResumed();
g_emu_thread->wakeThread();
g_emu_thread->stopBackgroundControllerPollTimer(); g_emu_thread->stopBackgroundControllerPollTimer();
} }
@ -1045,9 +1024,14 @@ void Host::OnSystemDestroyed()
emit g_emu_thread->systemDestroyed(); emit g_emu_thread->systemDestroyed();
} }
void Host::OnIdleStateChanged() void Host::OnFullscreenUIStartedOrStopped(bool started)
{ {
g_emu_thread->wakeThread(); g_emu_thread->setFullscreenUIStarted(started);
}
void Host::OnGPUThreadRunIdleChanged(bool is_active)
{
g_emu_thread->setGPUThreadRunIdle(is_active);
} }
void EmuThread::reloadInputSources() void EmuThread::reloadInputSources()
@ -1291,7 +1275,12 @@ void EmuThread::reloadPostProcessingShaders()
} }
if (System::IsValid()) if (System::IsValid())
PostProcessing::ReloadShaders(); {
GPUThread::RunOnThread([]() {
if (GPUThread::HasGPUBackend())
PostProcessing::ReloadShaders();
});
}
} }
void EmuThread::updatePostProcessingSettings() void EmuThread::updatePostProcessingSettings()
@ -1303,7 +1292,12 @@ void EmuThread::updatePostProcessingSettings()
} }
if (System::IsValid()) if (System::IsValid())
PostProcessing::UpdateSettings(); {
GPUThread::RunOnThread([]() {
if (GPUThread::HasGPUBackend())
PostProcessing::UpdateSettings();
});
}
} }
void EmuThread::clearInputBindStateFromSource(InputBindingKey key) void EmuThread::clearInputBindStateFromSource(InputBindingKey key)
@ -1326,7 +1320,7 @@ void EmuThread::reloadTextureReplacements()
} }
if (System::IsValid()) if (System::IsValid())
GPUTextureCache::ReloadTextureReplacements(true); GPUThread::RunOnThread([]() { GPUTextureCache::ReloadTextureReplacements(true); });
} }
void EmuThread::captureGPUFrameDump() void EmuThread::captureGPUFrameDump()
@ -1679,7 +1673,8 @@ void Host::DestroyAuxiliaryRenderWindow(AuxiliaryRenderWindowHandle handle, s32*
*height = size.height(); *height = size.height();
// eat all pending events, to make sure we're not going to write input events back to a dead pointer // eat all pending events, to make sure we're not going to write input events back to a dead pointer
g_emu_thread->getEventLoop()->processEvents(QEventLoop::AllEvents); if (g_emu_thread->isCurrentThread())
g_emu_thread->getEventLoop()->processEvents(QEventLoop::AllEvents);
} }
void EmuThread::queueAuxiliaryRenderWindowInputEvent(Host::AuxiliaryRenderWindowUserData userdata, void EmuThread::queueAuxiliaryRenderWindowInputEvent(Host::AuxiliaryRenderWindowUserData userdata,
@ -1699,10 +1694,12 @@ void EmuThread::processAuxiliaryRenderWindowInputEvent(void* userdata, quint32 e
quint32 param3) quint32 param3)
{ {
DebugAssert(isCurrentThread()); DebugAssert(isCurrentThread());
ImGuiManager::ProcessAuxiliaryRenderWindowInputEvent(userdata, static_cast<Host::AuxiliaryRenderWindowEvent>(event), GPUThread::RunOnThread([userdata, event, param1, param2, param3]() {
Host::AuxiliaryRenderWindowEventParam{.uint_param = param1}, ImGuiManager::ProcessAuxiliaryRenderWindowInputEvent(userdata, static_cast<Host::AuxiliaryRenderWindowEvent>(event),
Host::AuxiliaryRenderWindowEventParam{.uint_param = param2}, Host::AuxiliaryRenderWindowEventParam{.uint_param = param1},
Host::AuxiliaryRenderWindowEventParam{.uint_param = param3}); Host::AuxiliaryRenderWindowEventParam{.uint_param = param2},
Host::AuxiliaryRenderWindowEventParam{.uint_param = param3});
});
} }
void EmuThread::doBackgroundControllerPoll() void EmuThread::doBackgroundControllerPoll()
@ -1731,7 +1728,7 @@ void EmuThread::startBackgroundControllerPollTimer()
return; return;
u32 poll_interval = BACKGROUND_CONTROLLER_POLLING_INTERVAL; u32 poll_interval = BACKGROUND_CONTROLLER_POLLING_INTERVAL;
if (FullscreenUI::IsInitialized()) if (m_gpu_thread_run_idle)
poll_interval = FULLSCREEN_UI_CONTROLLER_POLLING_INTERVAL; poll_interval = FULLSCREEN_UI_CONTROLLER_POLLING_INTERVAL;
if (GDBServer::HasAnyClients()) if (GDBServer::HasAnyClients())
poll_interval = GDB_SERVER_POLLING_INTERVAL; poll_interval = GDB_SERVER_POLLING_INTERVAL;
@ -1747,6 +1744,37 @@ void EmuThread::stopBackgroundControllerPollTimer()
m_background_controller_polling_timer->stop(); m_background_controller_polling_timer->stop();
} }
void EmuThread::setGPUThreadRunIdle(bool active)
{
if (!isCurrentThread())
{
QMetaObject::invokeMethod(this, "setGPUThreadRunIdle", Qt::QueuedConnection, Q_ARG(bool, active));
return;
}
m_gpu_thread_run_idle = active;
// break out of the event loop if we're not executing a system
if (active && !g_settings.gpu_use_thread && !System::IsRunning())
m_event_loop->quit();
// adjust the timer speed to pick up controller input faster
if (!m_background_controller_polling_timer->isActive())
return;
g_emu_thread->stopBackgroundControllerPollTimer();
g_emu_thread->startBackgroundControllerPollTimer();
}
void EmuThread::setFullscreenUIStarted(bool started)
{
if (m_is_fullscreen_ui_started == started)
return;
m_is_fullscreen_ui_started = started;
emit fullscreenUIStartedOrStopped(started);
}
void EmuThread::start() void EmuThread::start()
{ {
AssertMsg(!g_emu_thread, "Emu thread does not exist"); AssertMsg(!g_emu_thread, "Emu thread does not exist");
@ -1776,8 +1804,6 @@ void EmuThread::stopInThread()
void EmuThread::run() void EmuThread::run()
{ {
Threading::SetNameOfCurrentThread("CPU Thread");
m_event_loop = new QEventLoop(); m_event_loop = new QEventLoop();
m_started_semaphore.release(); m_started_semaphore.release();
@ -1796,6 +1822,9 @@ void EmuThread::run()
createBackgroundControllerPollTimer(); createBackgroundControllerPollTimer();
startBackgroundControllerPollTimer(); startBackgroundControllerPollTimer();
// kick off GPU thread
Threading::Thread gpu_thread(&EmuThread::gpuThreadEntryPoint);
// main loop // main loop
while (!m_shutdown_flag) while (!m_shutdown_flag)
{ {
@ -1803,24 +1832,17 @@ void EmuThread::run()
{ {
System::Execute(); System::Execute();
} }
else if (!GPUThread::IsUsingThread() && GPUThread::IsRunningIdle())
{
g_emu_thread->getEventLoop()->processEvents(QEventLoop::AllEvents);
// have to double-check the condition after processing events, because the events could shut us down
if (!GPUThread::IsUsingThread() && GPUThread::IsRunningIdle())
GPUThread::Internal::DoRunIdle();
}
else else
{ {
// we want to keep rendering the UI when paused and fullscreen UI is enabled m_event_loop->exec();
if (!FullscreenUI::HasActiveWindow() && !System::IsRunning())
{
// wait until we have a system before running
m_event_loop->exec();
continue;
}
m_event_loop->processEvents(QEventLoop::AllEvents);
System::IdlePollUpdate();
if (g_gpu_device && g_gpu_device->HasMainSwapChain())
{
System::PresentDisplay(false, 0);
if (!g_gpu_device->GetMainSwapChain()->IsVSyncModeBlocking())
g_gpu_device->GetMainSwapChain()->ThrottlePresentation();
}
} }
} }
@ -1828,13 +1850,25 @@ void EmuThread::run()
System::ShutdownSystem(false); System::ShutdownSystem(false);
destroyBackgroundControllerPollTimer(); destroyBackgroundControllerPollTimer();
// tell GPU thread to exit
GPUThread::Internal::RequestShutdown();
gpu_thread.Join();
// and tidy up everything left
System::CPUThreadShutdown(); System::CPUThreadShutdown();
// move back to UI thread // move back to UI thread
moveToThread(m_ui_thread); moveToThread(m_ui_thread);
} }
void Host::FrameDone() void EmuThread::gpuThreadEntryPoint()
{
Threading::SetNameOfCurrentThread("GPU Thread");
GPUThread::Internal::GPUThreadEntryPoint();
}
void Host::FrameDoneOnGPUThread(GPUBackend* gpu_backend, u32 frame_number)
{ {
} }
@ -1949,7 +1983,7 @@ void Host::OnInputDeviceConnected(std::string_view identifier, std::string_view
{ {
emit g_emu_thread->onInputDeviceConnected(std::string(identifier), std::string(device_name)); emit g_emu_thread->onInputDeviceConnected(std::string(identifier), std::string(device_name));
if (System::IsValid() || g_emu_thread->isRunningFullscreenUI()) if (System::IsValid() || GPUThread::IsFullscreenUIRequested())
{ {
Host::AddIconOSDMessage(fmt::format("ControllerConnected{}", identifier), ICON_FA_GAMEPAD, Host::AddIconOSDMessage(fmt::format("ControllerConnected{}", identifier), ICON_FA_GAMEPAD,
fmt::format(TRANSLATE_FS("QtHost", "Controller {} connected."), identifier), fmt::format(TRANSLATE_FS("QtHost", "Controller {} connected."), identifier),
@ -1975,7 +2009,7 @@ void Host::OnInputDeviceDisconnected(InputBindingKey key, std::string_view ident
Host::AddIconOSDMessage(fmt::format("ControllerConnected{}", identifier), ICON_FA_GAMEPAD, std::move(message), Host::AddIconOSDMessage(fmt::format("ControllerConnected{}", identifier), ICON_FA_GAMEPAD, std::move(message),
Host::OSD_WARNING_DURATION); Host::OSD_WARNING_DURATION);
} }
else if (System::IsValid() || g_emu_thread->isRunningFullscreenUI()) else if (System::IsValid() || GPUThread::IsFullscreenUIRequested())
{ {
Host::AddIconOSDMessage(fmt::format("ControllerConnected{}", identifier), ICON_FA_GAMEPAD, Host::AddIconOSDMessage(fmt::format("ControllerConnected{}", identifier), ICON_FA_GAMEPAD,
fmt::format(TRANSLATE_FS("QtHost", "Controller {} disconnected."), identifier), fmt::format(TRANSLATE_FS("QtHost", "Controller {} disconnected."), identifier),
@ -2037,17 +2071,17 @@ void Host::ReleaseRenderWindow()
g_emu_thread->releaseRenderWindow(); g_emu_thread->releaseRenderWindow();
} }
void EmuThread::updatePerformanceCounters() void EmuThread::updatePerformanceCounters(const GPUBackend* gpu_backend)
{ {
const RenderAPI render_api = g_gpu_device ? g_gpu_device->GetRenderAPI() : RenderAPI::None; const RenderAPI render_api = g_gpu_device->GetRenderAPI();
const bool hardware_renderer = g_gpu && g_gpu->IsHardwareRenderer(); const bool hardware_renderer = GPUBackend::IsUsingHardwareBackend();
u32 render_width = 0; u32 render_width = 0;
u32 render_height = 0; u32 render_height = 0;
if (g_gpu) if (gpu_backend)
{ {
const u32 render_scale = g_gpu->GetResolutionScale(); const u32 render_scale = gpu_backend->GetResolutionScale();
std::tie(render_width, render_height) = g_gpu->GetFullDisplayResolution(); std::tie(render_width, render_height) = g_gpu.GetFullDisplayResolution();
render_width *= render_scale; render_width *= render_scale;
render_height *= render_scale; render_height *= render_scale;
} }
@ -2110,9 +2144,9 @@ void EmuThread::resetPerformanceCounters()
Q_ARG(const QString&, blank)); Q_ARG(const QString&, blank));
} }
void Host::OnPerformanceCountersUpdated() void Host::OnPerformanceCountersUpdated(const GPUBackend* gpu_backend)
{ {
g_emu_thread->updatePerformanceCounters(); g_emu_thread->updatePerformanceCounters(gpu_backend);
} }
void Host::OnGameChanged(const std::string& disc_path, const std::string& game_serial, const std::string& game_name) void Host::OnGameChanged(const std::string& disc_path, const std::string& game_serial, const std::string& game_name)
@ -2209,8 +2243,8 @@ std::optional<WindowInfo> Host::GetTopLevelWindowInfo()
EmuThread::SystemLock EmuThread::pauseAndLockSystem() EmuThread::SystemLock EmuThread::pauseAndLockSystem()
{ {
const bool was_fullscreen = System::IsValid() && isFullscreen(); const bool was_fullscreen = QtHost::IsSystemValid() && isFullscreen();
const bool was_paused = System::IsPaused(); const bool was_paused = QtHost::IsSystemPaused();
// We use surfaceless rather than switching out of fullscreen, because // We use surfaceless rather than switching out of fullscreen, because
// we're paused, so we're not going to be rendering anyway. // we're paused, so we're not going to be rendering anyway.

View File

@ -44,6 +44,8 @@ class INISettingsInterface;
enum class RenderAPI : u8; enum class RenderAPI : u8;
class GPUDevice; class GPUDevice;
class GPUBackend;
class MainWindow; class MainWindow;
class DisplayWidget; class DisplayWidget;
@ -93,7 +95,6 @@ public:
ALWAYS_INLINE bool isFullscreen() const { return m_is_fullscreen; } ALWAYS_INLINE bool isFullscreen() const { return m_is_fullscreen; }
ALWAYS_INLINE bool isRenderingToMain() const { return m_is_rendering_to_main; } ALWAYS_INLINE bool isRenderingToMain() const { return m_is_rendering_to_main; }
ALWAYS_INLINE bool isSurfaceless() const { return m_is_surfaceless; } ALWAYS_INLINE bool isSurfaceless() const { return m_is_surfaceless; }
ALWAYS_INLINE bool isRunningFullscreenUI() const { return m_run_fullscreen_ui; }
std::optional<WindowInfo> acquireRenderWindow(RenderAPI render_api, bool fullscreen, bool exclusive_fullscreen, std::optional<WindowInfo> acquireRenderWindow(RenderAPI render_api, bool fullscreen, bool exclusive_fullscreen,
Error* error); Error* error);
@ -102,6 +103,7 @@ public:
void startBackgroundControllerPollTimer(); void startBackgroundControllerPollTimer();
void stopBackgroundControllerPollTimer(); void stopBackgroundControllerPollTimer();
void setFullscreenUIStarted(bool started);
void wakeThread(); void wakeThread();
bool shouldRenderToMain() const; bool shouldRenderToMain() const;
@ -109,7 +111,7 @@ public:
void bootOrLoadState(std::string path); void bootOrLoadState(std::string path);
void updatePerformanceCounters(); void updatePerformanceCounters(const GPUBackend* gpu_backend);
void resetPerformanceCounters(); void resetPerformanceCounters();
/// Locks the system by pausing it, while a popup dialog is displayed. /// Locks the system by pausing it, while a popup dialog is displayed.
@ -147,7 +149,7 @@ Q_SIGNALS:
void runningGameChanged(const QString& filename, const QString& game_serial, const QString& game_title); void runningGameChanged(const QString& filename, const QString& game_serial, const QString& game_title);
void inputProfileLoaded(); void inputProfileLoaded();
void mouseModeRequested(bool relative, bool hide_cursor); void mouseModeRequested(bool relative, bool hide_cursor);
void fullscreenUIStateChange(bool running); void fullscreenUIStartedOrStopped(bool running);
void achievementsLoginRequested(Achievements::LoginRequestReason reason); void achievementsLoginRequested(Achievements::LoginRequestReason reason);
void achievementsRefreshed(quint32 id, const QString& game_info_string); void achievementsRefreshed(quint32 id, const QString& game_info_string);
void achievementsChallengeModeChanged(bool enabled); void achievementsChallengeModeChanged(bool enabled);
@ -210,6 +212,7 @@ public Q_SLOTS:
void clearInputBindStateFromSource(InputBindingKey key); void clearInputBindStateFromSource(InputBindingKey key);
void reloadTextureReplacements(); void reloadTextureReplacements();
void captureGPUFrameDump(); void captureGPUFrameDump();
void setGPUThreadRunIdle(bool active);
private Q_SLOTS: private Q_SLOTS:
void stopInThread(); void stopInThread();
@ -227,23 +230,23 @@ protected:
void run() override; void run() override;
private: private:
using InputButtonHandler = std::function<void(bool)>;
using InputAxisHandler = std::function<void(float)>;
void createBackgroundControllerPollTimer(); void createBackgroundControllerPollTimer();
void destroyBackgroundControllerPollTimer(); void destroyBackgroundControllerPollTimer();
void confirmActionIfMemoryCardBusy(const QString& action, bool cancel_resume_on_accept, void confirmActionIfMemoryCardBusy(const QString& action, bool cancel_resume_on_accept,
std::function<void(bool)> callback) const; std::function<void(bool)> callback) const;
static void gpuThreadEntryPoint();
QThread* m_ui_thread; QThread* m_ui_thread;
QSemaphore m_started_semaphore; QSemaphore m_started_semaphore;
QEventLoop* m_event_loop = nullptr; QEventLoop* m_event_loop = nullptr;
QTimer* m_background_controller_polling_timer = nullptr; QTimer* m_background_controller_polling_timer = nullptr;
bool m_shutdown_flag = false; bool m_shutdown_flag = false;
bool m_run_fullscreen_ui = false;
bool m_is_rendering_to_main = false; bool m_is_rendering_to_main = false;
bool m_is_fullscreen = false; bool m_is_fullscreen = false;
bool m_is_fullscreen_ui_started = false;
bool m_gpu_thread_run_idle = false;
bool m_is_surfaceless = false; bool m_is_surfaceless = false;
bool m_save_state_on_shutdown = false; bool m_save_state_on_shutdown = false;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,8 @@
#include "core/controller.h" #include "core/controller.h"
#include "core/fullscreen_ui.h" #include "core/fullscreen_ui.h"
#include "core/game_list.h" #include "core/game_list.h"
#include "core/gpu.h" #include "core/gpu_backend.h"
#include "core/gpu_thread.h"
#include "core/host.h" #include "core/host.h"
#include "core/system.h" #include "core/system.h"
#include "core/system_private.h" #include "core/system_private.h"
@ -46,9 +47,11 @@ static void HookSignals();
static bool SetFolders(); static bool SetFolders();
static bool SetNewDataRoot(const std::string& filename); static bool SetNewDataRoot(const std::string& filename);
static std::string GetFrameDumpFilename(u32 frame); static std::string GetFrameDumpFilename(u32 frame);
static void GPUThreadEntryPoint();
} // namespace RegTestHost } // namespace RegTestHost
static std::unique_ptr<MemorySettingsInterface> s_base_settings_interface; static std::unique_ptr<MemorySettingsInterface> s_base_settings_interface;
static Threading::Thread s_gpu_thread;
static u32 s_frames_to_run = 60 * 60; static u32 s_frames_to_run = 60 * 60;
static u32 s_frames_remaining = 0; static u32 s_frames_remaining = 0;
@ -281,12 +284,17 @@ void Host::OnSystemResumed()
// //
} }
void Host::OnIdleStateChanged() void Host::OnGPUThreadRunIdleChanged(bool is_active)
{ {
// //
} }
void Host::OnPerformanceCountersUpdated() void Host::OnFullscreenUIStartedOrStopped(bool started)
{
//
}
void Host::OnPerformanceCountersUpdated(const GPUBackend* gpu_backend)
{ {
// //
} }
@ -375,14 +383,10 @@ void Host::DestroyAuxiliaryRenderWindow(AuxiliaryRenderWindowHandle handle, s32*
{ {
} }
void Host::FrameDone() void Host::FrameDoneOnGPUThread(GPUBackend* gpu_backend, u32 frame_number)
{ {
const u32 frame = System::GetFrameNumber(); if (s_frame_dump_interval > 0 && (s_frame_dump_interval == 1 || (frame_number % s_frame_dump_interval) == 0))
if (s_frame_dump_interval > 0 && (s_frame_dump_interval == 1 || (frame % s_frame_dump_interval) == 0)) gpu_backend->WriteDisplayTextureToFile(RegTestHost::GetFrameDumpFilename(frame_number));
{
std::string dump_filename(RegTestHost::GetFrameDumpFilename(frame));
g_gpu->WriteDisplayTextureToFile(std::move(dump_filename));
}
} }
void Host::OpenURL(std::string_view url) void Host::OpenURL(std::string_view url)
@ -508,6 +512,12 @@ void RegTestHost::HookSignals()
std::signal(SIGTERM, SignalHandler); std::signal(SIGTERM, SignalHandler);
} }
void RegTestHost::GPUThreadEntryPoint()
{
Threading::SetNameOfCurrentThread("CPU Thread");
GPUThread::Internal::GPUThreadEntryPoint();
}
void RegTestHost::InitializeEarlyConsole() void RegTestHost::InitializeEarlyConsole()
{ {
const bool was_console_enabled = Log::IsConsoleOutputEnabled(); const bool was_console_enabled = Log::IsConsoleOutputEnabled();
@ -773,6 +783,7 @@ int main(int argc, char* argv[])
} }
RegTestHost::HookSignals(); RegTestHost::HookSignals();
s_gpu_thread.Start(&RegTestHost::GPUThreadEntryPoint);
Error error; Error error;
int result = -1; int result = -1;
@ -813,6 +824,12 @@ int main(int argc, char* argv[])
result = 0; result = 0;
cleanup: cleanup:
if (s_gpu_thread.Joinable())
{
GPUThread::Internal::RequestShutdown();
s_gpu_thread.Join();
}
System::CPUThreadShutdown(); System::CPUThreadShutdown();
System::ProcessShutdown(); System::ProcessShutdown();
return result; return result;

View File

@ -385,6 +385,17 @@ const char* GPUDevice::ShaderLanguageToString(GPUShaderLanguage language)
} }
} }
const char* GPUDevice::VSyncModeToString(GPUVSyncMode mode)
{
static constexpr std::array<const char*, static_cast<size_t>(GPUVSyncMode::Count)> vsync_modes = {{
"Disabled",
"FIFO",
"Mailbox",
}};
return vsync_modes[static_cast<size_t>(mode)];
}
bool GPUDevice::IsSameRenderAPI(RenderAPI lhs, RenderAPI rhs) bool GPUDevice::IsSameRenderAPI(RenderAPI lhs, RenderAPI rhs)
{ {
return (lhs == rhs || ((lhs == RenderAPI::OpenGL || lhs == RenderAPI::OpenGLES) && return (lhs == rhs || ((lhs == RenderAPI::OpenGL || lhs == RenderAPI::OpenGLES) &&

View File

@ -657,6 +657,9 @@ public:
/// Returns a string representing the specified language. /// Returns a string representing the specified language.
static const char* ShaderLanguageToString(GPUShaderLanguage language); static const char* ShaderLanguageToString(GPUShaderLanguage language);
/// Returns a string representing the specified vsync mode.
static const char* VSyncModeToString(GPUVSyncMode mode);
/// Returns a new device for the specified API. /// Returns a new device for the specified API.
static std::unique_ptr<GPUDevice> CreateDeviceForAPI(RenderAPI api); static std::unique_ptr<GPUDevice> CreateDeviceForAPI(RenderAPI api);

View File

@ -52,6 +52,8 @@ static void DrawChoiceDialog();
static void DrawInputDialog(); static void DrawInputDialog();
static void DrawMessageDialog(); static void DrawMessageDialog();
static void DrawBackgroundProgressDialogs(ImVec2& position, float spacing); static void DrawBackgroundProgressDialogs(ImVec2& position, float spacing);
static void DrawLoadingScreen(std::string_view image, std::string_view message, s32 progress_min, s32 progress_max,
s32 progress_value, bool is_persistent);
static void DrawNotifications(ImVec2& position, float spacing); static void DrawNotifications(ImVec2& position, float spacing);
static void DrawToast(); static void DrawToast();
static bool MenuButtonFrame(const char* str_id, bool enabled, float height, bool* visible, bool* hovered, ImRect* bb, static bool MenuButtonFrame(const char* str_id, bool enabled, float height, bool* visible, bool* hovered, ImRect* bb,
@ -171,6 +173,13 @@ struct ALIGN_TO_CACHE_LINE UIState
std::vector<BackgroundProgressDialogData> background_progress_dialogs; std::vector<BackgroundProgressDialogData> background_progress_dialogs;
std::mutex background_progress_lock; std::mutex background_progress_lock;
std::string loading_screen_image;
std::string loading_screen_message;
s32 loading_screen_min = 0;
s32 loading_screen_max = 0;
s32 loading_screen_value = 0;
bool loading_screen_open = false;
}; };
} // namespace } // namespace
@ -2916,6 +2925,145 @@ void ImGuiFullscreen::DrawBackgroundProgressDialogs(ImVec2& position, float spac
ImGui::PopStyleColor(2); ImGui::PopStyleColor(2);
} }
void ImGuiFullscreen::RenderLoadingScreen(std::string_view image, std::string_view message, s32 progress_min /*= -1*/,
s32 progress_max /*= -1*/, s32 progress_value /*= -1*/)
{
if (progress_min < progress_max)
INFO_LOG("{}: {}/{}", message, progress_value, progress_max);
if (!g_gpu_device || !g_gpu_device->HasMainSwapChain())
return;
// eat the last imgui frame, it might've been partially rendered by the caller.
ImGui::EndFrame();
ImGui::NewFrame();
DrawLoadingScreen(image, message, progress_min, progress_max, progress_value, false);
ImGui::EndFrame();
GPUSwapChain* swap_chain = g_gpu_device->GetMainSwapChain();
if (g_gpu_device->BeginPresent(swap_chain) == GPUDevice::PresentResult::OK)
{
g_gpu_device->RenderImGui(swap_chain);
g_gpu_device->EndPresent(swap_chain, false);
}
ImGui::NewFrame();
}
void ImGuiFullscreen::OpenOrUpdateLoadingScreen(std::string_view image, std::string_view message,
s32 progress_min /*= -1*/, s32 progress_max /*= -1*/,
s32 progress_value /*= -1*/)
{
if (s_state.loading_screen_image != image)
s_state.loading_screen_image = image;
if (s_state.loading_screen_message != message)
s_state.loading_screen_message = message;
s_state.loading_screen_min = progress_min;
s_state.loading_screen_max = progress_max;
s_state.loading_screen_value = progress_value;
s_state.loading_screen_open = true;
}
bool ImGuiFullscreen::IsLoadingScreenOpen()
{
return s_state.loading_screen_open;
}
void ImGuiFullscreen::RenderLoadingScreen()
{
if (!s_state.loading_screen_open)
return;
DrawLoadingScreen(s_state.loading_screen_image, s_state.loading_screen_message, s_state.loading_screen_min,
s_state.loading_screen_max, s_state.loading_screen_value, true);
}
void ImGuiFullscreen::CloseLoadingScreen()
{
s_state.loading_screen_image = {};
s_state.loading_screen_message = {};
s_state.loading_screen_min = 0;
s_state.loading_screen_max = 0;
s_state.loading_screen_value = 0;
s_state.loading_screen_open = false;
}
void ImGuiFullscreen::DrawLoadingScreen(std::string_view image, std::string_view message, s32 progress_min,
s32 progress_max, s32 progress_value, bool is_persistent)
{
const auto& io = ImGui::GetIO();
const float scale = ImGuiManager::GetGlobalScale();
const float width = (400.0f * scale);
const bool has_progress = (progress_min < progress_max);
const float logo_width = 260.0f * scale;
const float logo_height = 260.0f * scale;
ImGui::SetNextWindowSize(ImVec2(logo_width, logo_height), ImGuiCond_Always);
ImGui::SetNextWindowPos(ImVec2(io.DisplaySize.x * 0.5f, (io.DisplaySize.y * 0.5f) - (50.0f * scale)),
ImGuiCond_Always, ImVec2(0.5f, 0.5f));
if (ImGui::Begin("LoadingScreenLogo", nullptr,
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoNav |
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing |
ImGuiWindowFlags_NoBackground))
{
GPUTexture* tex = GetCachedTexture(image);
if (tex)
ImGui::Image(tex, ImVec2(logo_width, logo_height));
}
ImGui::End();
const float padding_and_rounding = 18.0f * scale;
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, padding_and_rounding);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(padding_and_rounding, padding_and_rounding));
ImGui::SetNextWindowSize(ImVec2(width, ((has_progress || is_persistent) ? 90.0f : 55.0f) * scale), ImGuiCond_Always);
ImGui::SetNextWindowPos(ImVec2(io.DisplaySize.x * 0.5f, (io.DisplaySize.y * 0.5f) + (100.0f * scale)),
ImGuiCond_Always, ImVec2(0.5f, 0.0f));
if (ImGui::Begin("LoadingScreen", nullptr,
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoNav |
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing))
{
if (has_progress || is_persistent)
{
if (!message.empty())
ImGui::TextUnformatted(message.data(), message.data() + message.size());
if (has_progress)
{
TinyString buf;
buf.format("{}/{}", progress_value, progress_max);
const ImVec2 prog_size = ImGui::CalcTextSize(buf.c_str(), buf.end_ptr());
ImGui::SameLine();
ImGui::SetCursorPosX(width - padding_and_rounding - prog_size.x);
ImGui::TextUnformatted(buf.c_str(), buf.end_ptr());
}
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 5.0f);
ImGui::ProgressBar(has_progress ?
(static_cast<float>(progress_value) / static_cast<float>(progress_max - progress_min)) :
static_cast<float>(-ImGui::GetTime()),
ImVec2(-1.0f, 0.0f), "");
}
else
{
if (!message.empty())
{
const ImVec2 text_size(ImGui::CalcTextSize(message.data(), message.data() + message.size()));
ImGui::SetCursorPosX((width - text_size.x) / 2.0f);
ImGui::TextUnformatted(message.data(), message.data() + message.size());
}
}
}
ImGui::End();
ImGui::PopStyleVar(2);
}
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
// Notifications // Notifications
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////

View File

@ -327,6 +327,18 @@ void UpdateBackgroundProgressDialog(const char* str_id, std::string message, s32
void CloseBackgroundProgressDialog(const char* str_id); void CloseBackgroundProgressDialog(const char* str_id);
bool IsBackgroundProgressDialogOpen(const char* str_id); bool IsBackgroundProgressDialogOpen(const char* str_id);
/// Displays a loading screen with the logo, rendered with ImGui. Use when executing possibly-time-consuming tasks
/// such as compiling shaders when starting up.
void RenderLoadingScreen(std::string_view image, std::string_view message, s32 progress_min = -1, s32 progress_max = -1,
s32 progress_value = -1);
void OpenOrUpdateLoadingScreen(std::string_view image, std::string_view message, s32 progress_min = -1,
s32 progress_max = -1, s32 progress_value = -1);
bool IsLoadingScreenOpen();
void CloseLoadingScreen();
/// Renders a previously-configured loading screen.
void RenderLoadingScreen();
void AddNotification(std::string key, float duration, std::string title, std::string text, std::string image_path); void AddNotification(std::string key, float duration, std::string title, std::string text, std::string image_path);
void ClearNotifications(); void ClearNotifications();

View File

@ -30,12 +30,6 @@
#include <mach/task.h> #include <mach/task.h>
#endif #endif
namespace PageFaultHandler {
static std::recursive_mutex s_exception_handler_mutex;
static bool s_in_exception_handler = false;
static bool s_installed = false;
} // namespace PageFaultHandler
#if defined(CPU_ARCH_ARM64) #if defined(CPU_ARCH_ARM64)
[[maybe_unused]] static bool IsStoreInstruction(const void* ptr) [[maybe_unused]] static bool IsStoreInstruction(const void* ptr)
{ {
@ -85,7 +79,11 @@ static bool s_installed = false;
namespace PageFaultHandler { namespace PageFaultHandler {
static LONG ExceptionHandler(PEXCEPTION_POINTERS exi); static LONG ExceptionHandler(PEXCEPTION_POINTERS exi);
}
static std::recursive_mutex s_exception_handler_mutex;
static bool s_in_exception_handler = false;
static bool s_installed = false;
} // namespace PageFaultHandler
LONG PageFaultHandler::ExceptionHandler(PEXCEPTION_POINTERS exi) LONG PageFaultHandler::ExceptionHandler(PEXCEPTION_POINTERS exi)
{ {
@ -140,6 +138,10 @@ bool PageFaultHandler::Install(Error* error)
namespace PageFaultHandler { namespace PageFaultHandler {
static void SignalHandler(int sig, siginfo_t* info, void* ctx); static void SignalHandler(int sig, siginfo_t* info, void* ctx);
static std::recursive_mutex s_exception_handler_mutex;
static bool s_in_exception_handler = false;
static bool s_installed = false;
} // namespace PageFaultHandler } // namespace PageFaultHandler
void PageFaultHandler::SignalHandler(int sig, siginfo_t* info, void* ctx) void PageFaultHandler::SignalHandler(int sig, siginfo_t* info, void* ctx)
@ -210,7 +212,7 @@ void PageFaultHandler::SignalHandler(int sig, siginfo_t* info, void* ctx)
result = HandlePageFault(exception_pc, exception_address, is_write); result = HandlePageFault(exception_pc, exception_address, is_write);
s_in_exception_handler = false; s_in_exception_handler = false;
} }
s_exception_handler_mutex.unlock(); s_exception_handler_mutex.unlock();
// Resumes execution right where we left off (re-executes instruction that caused the SIGSEGV). // Resumes execution right where we left off (re-executes instruction that caused the SIGSEGV).

View File

@ -12,7 +12,7 @@
// TODO: Remove me // TODO: Remove me
#include "core/host.h" #include "core/host.h"
#include "core/host_interface_progress_callback.h" #include "core/fullscreen_ui.h"
#include "core/settings.h" #include "core/settings.h"
#include "IconsFontAwesome5.h" #include "IconsFontAwesome5.h"
@ -406,7 +406,7 @@ void PostProcessing::Chain::LoadStages()
return; return;
Error error; Error error;
HostInterfaceProgressCallback progress; LoadingScreenProgressCallback progress;
progress.SetProgressRange(stage_count); progress.SetProgressRange(stage_count);
for (u32 i = 0; i < stage_count; i++) for (u32 i = 0; i < stage_count; i++)
@ -476,7 +476,7 @@ void PostProcessing::Chain::UpdateSettings(std::unique_lock<std::mutex>& setting
m_stages.resize(stage_count); m_stages.resize(stage_count);
HostInterfaceProgressCallback progress; LoadingScreenProgressCallback progress;
progress.SetProgressRange(stage_count); progress.SetProgressRange(stage_count);
const GPUTexture::Format prev_format = m_target_format; const GPUTexture::Format prev_format = m_target_format;

View File

@ -7,6 +7,7 @@
#include "shadergen.h" #include "shadergen.h"
// TODO: Remove me // TODO: Remove me
#include "core/gpu_thread.h"
#include "core/host.h" #include "core/host.h"
#include "core/settings.h" #include "core/settings.h"
@ -72,7 +73,7 @@ static std::tuple<std::unique_ptr<reshadefx::codegen>, GPUShaderLanguage> Create
} }
// Should have a GPU device and be on the GPU thread. // Should have a GPU device and be on the GPU thread.
Assert(g_gpu_device); Assert(GPUThread::IsOnThread() && g_gpu_device);
const bool debug_info = g_gpu_device->IsDebugDevice(); const bool debug_info = g_gpu_device->IsDebugDevice();
const RenderAPI rapi = g_gpu_device->GetRenderAPI(); const RenderAPI rapi = g_gpu_device->GetRenderAPI();

View File

@ -34,6 +34,8 @@ public:
ALWAYS_INLINE bool IsReading() const { return (m_mode == Mode::Read); } ALWAYS_INLINE bool IsReading() const { return (m_mode == Mode::Read); }
ALWAYS_INLINE bool IsWriting() const { return (m_mode == Mode::Write); } ALWAYS_INLINE bool IsWriting() const { return (m_mode == Mode::Write); }
ALWAYS_INLINE u32 GetVersion() const { return m_version; } ALWAYS_INLINE u32 GetVersion() const { return m_version; }
ALWAYS_INLINE const u8* GetData() const { return m_data; }
ALWAYS_INLINE size_t GetDataSize() const { return m_size; }
ALWAYS_INLINE size_t GetPosition() const { return m_pos; } ALWAYS_INLINE size_t GetPosition() const { return m_pos; }
ALWAYS_INLINE void SetPosition(size_t pos) { m_pos = pos; } ALWAYS_INLINE void SetPosition(size_t pos) { m_pos = pos; }