Skip to content

Commit de060eb

Browse files
committed
[GPU] Double-buffer readback_resolve.
Introduce "fast" readback resolve that reads from previous frame's resolve buffers, quick swap between buffers each frame to avoid copies so minimal performance impact and mostly correct bahavior. Checks if previous frame had a buffer for the current resolve and falls through to the slow path, which also allows to support "screenshot" features in the games that do that without stalling on normal resolve operations. Re-enaled readback_memexport as separate feature, was previously bundled with readback_resolve (probably not intentionally) and ensures destination address is committed on readback resolve to avoid memory access related crashes. readback_resolve cvar changes from bool to string ternary with "none", "fast" and "full" options, defaulting to the new "fast" mode.
1 parent 657516b commit de060eb

File tree

8 files changed

+450
-121
lines changed

8 files changed

+450
-121
lines changed

src/xenia/app/emulator_window.cc

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ DECLARE_bool(guide_button);
5050

5151
DECLARE_bool(clear_memory_page_state);
5252

53-
DECLARE_bool(readback_resolve);
53+
DECLARE_string(readback_resolve);
5454

5555
DECLARE_bool(readback_memexport);
5656

@@ -1822,10 +1822,10 @@ EmulatorWindow::ControllerHotKey EmulatorWindow::ProcessControllerHotkey(
18221822
xe::threading::Sleep(delay);
18231823
break;
18241824
case ButtonFunctions::ReadbackResolve:
1825-
ToggleGPUSetting(GPUSetting::ReadbackResolve);
1825+
CycleReadbackResolve();
18261826

1827-
notificationTitle = "Toggle Readback Resolve";
1828-
notificationDesc = cvars::readback_resolve ? "Enabled" : "Disabled";
1827+
notificationTitle = "Readback Resolve Mode";
1828+
notificationDesc = cvars::readback_resolve;
18291829

18301830
// Extra Sleep
18311831
xe::threading::Sleep(delay);
@@ -2009,15 +2009,23 @@ void EmulatorWindow::ToggleGPUSetting(gpu::GPUSetting setting) {
20092009
SaveGPUSetting(GPUSetting::ClearMemoryPageState,
20102010
!cvars::clear_memory_page_state);
20112011
break;
2012-
case GPUSetting::ReadbackResolve:
2013-
SaveGPUSetting(GPUSetting::ReadbackResolve, !cvars::readback_resolve);
2014-
break;
20152012
case GPUSetting::ReadbackMemexport:
20162013
SaveGPUSetting(GPUSetting::ReadbackMemexport, !cvars::readback_memexport);
20172014
break;
20182015
}
20192016
}
20202017

2018+
void EmulatorWindow::CycleReadbackResolve() {
2019+
const std::string& current = cvars::readback_resolve;
2020+
if (current == "fast") {
2021+
gpu::SetReadbackResolveMode("full");
2022+
} else if (current == "full") {
2023+
gpu::SetReadbackResolveMode("none");
2024+
} else {
2025+
gpu::SetReadbackResolveMode("fast");
2026+
}
2027+
}
2028+
20212029
void EmulatorWindow::DisplayHotKeysConfig() {
20222030
std::string msg = "";
20232031
std::string msg_passthru = "";
@@ -2056,8 +2064,7 @@ void EmulatorWindow::DisplayHotKeysConfig() {
20562064
msg.insert(0, msg_passthru);
20572065
msg += "\n";
20582066

2059-
msg += "Readback Resolve: " +
2060-
xe::string_util::BoolToString(cvars::readback_resolve);
2067+
msg += "Readback Resolve: " + cvars::readback_resolve;
20612068
msg += "\n";
20622069

20632070
msg += "Clear Memory Page State: " +

src/xenia/app/emulator_window.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,7 @@ class EmulatorWindow {
280280
bool vibrate = true);
281281
void GamepadHotKeys();
282282
void ToggleGPUSetting(gpu::GPUSetting setting);
283+
void CycleReadbackResolve();
283284
void DisplayHotKeysConfig();
284285

285286
static std::string CanonicalizeFileExtension(

src/xenia/gpu/command_processor.cc

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,12 @@ DEFINE_bool(clear_memory_page_state, false,
4848
"for 'Team Ninja' Games to fix missing character models)",
4949
"GPU");
5050

51-
DEFINE_bool(
52-
readback_resolve, false,
53-
"Read render-to-texture results on the CPU. This may be "
54-
"needed in some games, for instance, for screenshots in saved games, but "
55-
"causes mid-frame synchronization, so it has a huge performance impact.",
51+
DEFINE_string(
52+
readback_resolve, "fast",
53+
"Controls CPU readback of render-to-texture resolve results.\n"
54+
" fast: Read from previous frame (1 frame delay, no GPU stall - default)\n"
55+
" full: Wait for GPU to finish (accurate but slow, GPU-CPU sync stall)\n"
56+
" none: Disable readback completely (some games render better without it)",
5657
"GPU");
5758

5859
DEFINE_bool(
@@ -73,9 +74,6 @@ void SaveGPUSetting(GPUSetting setting, uint64_t value) {
7374
case GPUSetting::ClearMemoryPageState:
7475
OVERRIDE_bool(clear_memory_page_state, static_cast<bool>(value));
7576
break;
76-
case GPUSetting::ReadbackResolve:
77-
OVERRIDE_bool(readback_resolve, static_cast<bool>(value));
78-
break;
7977
case GPUSetting::ReadbackMemexport:
8078
OVERRIDE_bool(readback_memexport, static_cast<bool>(value));
8179
break;
@@ -86,14 +84,28 @@ bool GetGPUSetting(GPUSetting setting) {
8684
switch (setting) {
8785
case GPUSetting::ClearMemoryPageState:
8886
return cvars::clear_memory_page_state;
89-
case GPUSetting::ReadbackResolve:
90-
return cvars::readback_resolve;
9187
case GPUSetting::ReadbackMemexport:
9288
return cvars::readback_memexport;
9389
}
9490
return false;
9591
}
9692

93+
ReadbackResolveMode GetReadbackResolveMode() {
94+
const std::string& mode = cvars::readback_resolve;
95+
if (mode == "full") {
96+
return ReadbackResolveMode::kFull;
97+
} else if (mode == "none") {
98+
return ReadbackResolveMode::kDisabled;
99+
} else {
100+
// Default to "fast" for any unrecognized value
101+
return ReadbackResolveMode::kFast;
102+
}
103+
}
104+
105+
void SetReadbackResolveMode(const std::string& mode) {
106+
OVERRIDE_string(readback_resolve, mode);
107+
}
108+
97109
using namespace xe::gpu::xenos;
98110

99111
CommandProcessor::CommandProcessor(GraphicsSystem* graphics_system,

src/xenia/gpu/command_processor.h

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,18 @@ class ByteStream;
3333

3434
namespace gpu {
3535

36-
enum class GPUSetting {
37-
ClearMemoryPageState,
38-
ReadbackResolve,
39-
ReadbackMemexport
36+
enum class GPUSetting { ClearMemoryPageState, ReadbackMemexport };
37+
38+
enum class ReadbackResolveMode {
39+
kDisabled, // No readback (none)
40+
kFast, // Delayed sync, 1 frame behind (fast)
41+
kFull // Immediate sync with GPU stall (full)
4042
};
4143

4244
void SaveGPUSetting(GPUSetting setting, uint64_t value);
4345
bool GetGPUSetting(GPUSetting setting);
46+
ReadbackResolveMode GetReadbackResolveMode();
47+
void SetReadbackResolveMode(const std::string& mode);
4448

4549
class GraphicsSystem;
4650
class Shader;
@@ -162,6 +166,27 @@ class CommandProcessor {
162166

163167
static constexpr uint32_t kReadbackBufferSizeIncrement = 16 * 1024 * 1024;
164168

169+
// Eviction policy constants for readback buffer cache
170+
static constexpr size_t kMaxReadbackBuffers = 64;
171+
static constexpr uint64_t kReadbackBufferEvictionAgeFrames = 60;
172+
173+
// Progressive alignment for readback buffers to avoid wasting memory
174+
static inline uint32_t AlignReadbackBufferSize(uint32_t size) {
175+
if (size < 1 * 1024 * 1024) {
176+
return xe::align(size, 256u * 1024u); // 256KB for < 1MB
177+
} else if (size < 4 * 1024 * 1024) {
178+
return xe::align(size, 1u * 1024u * 1024u); // 1MB for < 4MB
179+
} else {
180+
return xe::align(size, kReadbackBufferSizeIncrement); // 16MB for >= 4MB
181+
}
182+
}
183+
184+
// Generate a cache key for a specific resolve operation
185+
static inline uint64_t MakeReadbackResolveKey(uint32_t address,
186+
uint32_t length) {
187+
return (uint64_t(address) << 32) | uint64_t(length);
188+
}
189+
165190
void WorkerThreadMain();
166191
virtual bool SetupContext() = 0;
167192
virtual void ShutdownContext() = 0;

0 commit comments

Comments
 (0)