[GPU] Change readback_resolve values and default

Rename values to [none, some, fast, full], "fast" is still the default
but is what was previously known as "slow". Also fixing the controller
hotkey handling to update the cached value as well as persist the value
in per-game config.
This commit is contained in:
Herman S.
2025-12-02 12:00:22 +09:00
parent e3a1f920f1
commit 00bf9daa8b
6 changed files with 81 additions and 24 deletions
+22 -9
View File
@@ -2213,16 +2213,29 @@ void EmulatorWindow::ToggleGPUSetting(gpu::GPUSetting setting) {
}
void EmulatorWindow::CycleReadbackResolve() {
const std::string& current = cvars::readback_resolve;
if (current == "fast") {
gpu::SetReadbackResolveMode("slow");
} else if (current == "slow") {
gpu::SetReadbackResolveMode("full");
} else if (current == "full") {
gpu::SetReadbackResolveMode("none");
} else {
gpu::SetReadbackResolveMode("fast");
auto* graphics_system = emulator_->graphics_system();
if (!graphics_system) return;
auto* command_processor = graphics_system->command_processor();
if (!command_processor) return;
gpu::ReadbackResolveMode current =
command_processor->GetReadbackResolveMode();
gpu::ReadbackResolveMode next;
switch (current) {
case gpu::ReadbackResolveMode::kDisabled:
next = gpu::ReadbackResolveMode::kSome;
break;
case gpu::ReadbackResolveMode::kSome:
next = gpu::ReadbackResolveMode::kFast;
break;
case gpu::ReadbackResolveMode::kFast:
next = gpu::ReadbackResolveMode::kFull;
break;
default:
next = gpu::ReadbackResolveMode::kDisabled;
break;
}
command_processor->SetReadbackResolveMode(next);
}
void EmulatorWindow::DisplayHotKeysConfig() {
+47 -5
View File
@@ -14,6 +14,7 @@
#include "xenia/base/cvar.h"
#include "xenia/base/logging.h"
#include "xenia/base/profiling.h"
#include "xenia/config.h"
#include "xenia/gpu/gpu_flags.h"
#include "xenia/gpu/graphics_system.h"
#include "xenia/gpu/packet_disassembler.h"
@@ -52,8 +53,8 @@ DEFINE_bool(clear_memory_page_state, true,
DEFINE_string(
readback_resolve, "fast",
"Controls CPU readback of render-to-texture resolve results.\n"
" fast: Read from previous frame, skip copy on cache hit (default)\n"
" slow: Read from previous frame, copy every frame\n"
" fast: Read from previous frame, copy every frame (default)\n"
" some: Read from previous frame, skip copy on cache hit\n"
" full: Wait for GPU to finish (accurate but slow, GPU-CPU sync stall)\n"
" none: Disable readback completely (some games render better without it)",
"GPU");
@@ -104,8 +105,8 @@ static ReadbackResolveMode ParseReadbackResolveMode() {
const std::string& mode = cvars::readback_resolve;
if (mode == "full") {
return ReadbackResolveMode::kFull;
} else if (mode == "slow") {
return ReadbackResolveMode::kSlow;
} else if (mode == "some") {
return ReadbackResolveMode::kSome;
} else if (mode == "none") {
return ReadbackResolveMode::kDisabled;
} else {
@@ -114,7 +115,7 @@ static ReadbackResolveMode ParseReadbackResolveMode() {
}
}
void SetReadbackResolveMode(const std::string& mode) {
static void SetReadbackResolveCvar(const std::string& mode) {
OVERRIDE_string(readback_resolve, mode);
}
@@ -278,6 +279,47 @@ void CommandProcessor::InvalidateGpuMemory() {}
void CommandProcessor::ClearReadbackBuffers() {}
void CommandProcessor::SetReadbackResolveMode(ReadbackResolveMode mode) {
if (cached_readback_resolve_mode_ == mode) {
return;
}
// Update cached value
cached_readback_resolve_mode_ = mode;
// Update cvar string for UI display
const char* mode_str = "fast";
switch (mode) {
case ReadbackResolveMode::kDisabled:
mode_str = "none";
break;
case ReadbackResolveMode::kSome:
mode_str = "some";
break;
case ReadbackResolveMode::kFull:
mode_str = "full";
break;
default:
break;
}
SetReadbackResolveCvar(mode_str);
// Save to per-game config if a title is loaded
uint32_t title_id = kernel_state_ ? kernel_state_->title_id() : 0;
if (title_id != 0) {
toml::table config_table = config::LoadGameConfig(title_id);
if (!config_table.contains("GPU")) {
config_table.insert("GPU", toml::table{});
}
auto* gpu_table = config_table["GPU"].as_table();
if (gpu_table) {
gpu_table->insert_or_assign("readback_resolve", mode_str);
}
config::SaveGameConfig(title_id, config_table);
}
}
void CommandProcessor::SetDesiredSwapPostEffect(
SwapPostEffect swap_post_effect) {
if (swap_post_effect_desired_ == swap_post_effect) {
+5 -3
View File
@@ -37,14 +37,13 @@ enum class GPUSetting { ClearMemoryPageState, ReadbackMemexport };
enum class ReadbackResolveMode {
kDisabled, // No readback (none)
kFast, // Delayed sync, skip copy on cache hit (fast)
kSlow, // Delayed sync, copy every frame (slow)
kSome, // Delayed sync, skip copy on cache hit (some)
kFast, // Delayed sync, copy every frame (fast)
kFull // Immediate sync with GPU stall (full)
};
void SaveGPUSetting(GPUSetting setting, uint64_t value);
bool GetGPUSetting(GPUSetting setting);
void SetReadbackResolveMode(const std::string& mode);
// Occlusion query pool size for both D3D12 and Vulkan backends.
// Queries complete synchronously with GPU stalls.
@@ -117,6 +116,9 @@ class CommandProcessor {
return cached_readback_resolve_mode_;
}
// Set readback resolve mode (updates both cvar and cached value)
void SetReadbackResolveMode(ReadbackResolveMode mode);
// "Desired" is for the external thread managing the post-processing effect.
SwapPostEffect GetDesiredSwapPostEffect() const {
return swap_post_effect_desired_;
@@ -3256,7 +3256,7 @@ bool D3D12CommandProcessor::IssueCopy_ReadbackResolvePath() {
ReadbackResolveMode readback_mode = GetReadbackResolveMode();
bool use_delayed_sync = (readback_mode == ReadbackResolveMode::kFast ||
readback_mode == ReadbackResolveMode::kSlow);
readback_mode == ReadbackResolveMode::kSome);
uint32_t read_index = write_index;
if (use_delayed_sync) {
@@ -3286,11 +3286,11 @@ bool D3D12CommandProcessor::IssueCopy_ReadbackResolvePath() {
}
// Copy to guest memory
// "fast" mode: only copy on cache miss (saves CPU)
// "slow" mode: always copy (1 frame behind, no GPU stall)
// "some" mode: only copy on cache miss (saves CPU)
// "fast" mode: always copy (1 frame behind, no GPU stall)
// "full" mode: always copy (GPU sync already done above)
bool should_copy =
(readback_mode == ReadbackResolveMode::kFast) ? is_cache_miss : true;
(readback_mode == ReadbackResolveMode::kSome) ? is_cache_miss : true;
if (should_copy && read_source != nullptr &&
written_length <= rb.sizes[read_index] &&
rb.mapped_data[read_index] != nullptr) {
@@ -3274,7 +3274,7 @@ bool VulkanCommandProcessor::IssueCopy() {
shared_memory_buffer, rb.buffers[write_index], 1, &copy_region);
bool use_delayed_sync = (readback_mode == ReadbackResolveMode::kFast ||
readback_mode == ReadbackResolveMode::kSlow);
readback_mode == ReadbackResolveMode::kSome);
uint32_t read_index = write_index;
if (use_delayed_sync) {
@@ -3306,7 +3306,7 @@ bool VulkanCommandProcessor::IssueCopy() {
}
bool should_copy =
(readback_mode == ReadbackResolveMode::kFast) ? is_cache_miss : true;
(readback_mode == ReadbackResolveMode::kSome) ? is_cache_miss : true;
if (should_copy && rb.buffers[read_index] != VK_NULL_HANDLE &&
written_length <= rb.sizes[read_index] &&
rb.mapped_data[read_index] != nullptr) {
+1 -1
View File
@@ -78,7 +78,7 @@ GetKnownEnumOptions() {
#endif
{"d3d12_readback_resolve",
{"kCopy", "kComputeLuminance", "kComputeRGBA16"}},
{"readback_resolve", {"fast", "slow", "full", "none"}},
{"readback_resolve", {"fast", "some", "full", "none"}},
{"render_target_path", {"performance", "accuracy"}},
{"postprocess_antialiasing", {"off", "fxaa", "fxaa_extreme"}},
{"postprocess_scaling_and_sharpening",