GPU: readback-window diagnostics + min_length floor

This commit is contained in:
mchughalex 2026-07-15 08:25:44 +01:00
parent b8258670da
commit 065cade4d3
3 changed files with 65 additions and 1 deletions

View file

@ -36,6 +36,7 @@ REXCVAR_DECLARE(int32_t, scaled_resolve_small_texture_readback_max_length);
REXCVAR_DECLARE(std::string, readback_resolve); REXCVAR_DECLARE(std::string, readback_resolve);
REXCVAR_DECLARE(bool, readback_resolve_half_pixel_offset); REXCVAR_DECLARE(bool, readback_resolve_half_pixel_offset);
REXCVAR_DECLARE(int32_t, native_render_force_resolve_readback_max_length); REXCVAR_DECLARE(int32_t, native_render_force_resolve_readback_max_length);
REXCVAR_DECLARE(int32_t, native_render_force_resolve_readback_min_length);
REXCVAR_DECLARE(bool, readback_memexport); REXCVAR_DECLARE(bool, readback_memexport);
REXCVAR_DECLARE(bool, readback_memexport_fast); REXCVAR_DECLARE(bool, readback_memexport_fast);
REXCVAR_DECLARE(bool, occlusion_query_enable); REXCVAR_DECLARE(bool, occlusion_query_enable);

View file

@ -101,6 +101,18 @@ REXCVAR_DEFINE_INT32(native_render_force_resolve_readback_max_length, 0, "GPU",
.range(0, 16 * 1024 * 1024) .range(0, 16 * 1024 * 1024)
.lifecycle(rex::cvar::Lifecycle::kHotReload); .lifecycle(rex::cvar::Lifecycle::kHotReload);
REXCVAR_DEFINE_INT32(native_render_force_resolve_readback_min_length, 0, "GPU",
"Lower bound companion to "
"native_render_force_resolve_readback_max_length: when > 0, resolves "
"SHORTER than this skip the forced CPU copy (they still execute "
"GPU-side). Each forced copy is a synchronous GPU drain; the Skate 3 "
"photo-flow compose window uses this to skip the ~18 small per-frame "
"resolves (64 KB lightmap pages, postfx aux) that made the window a "
"stutter-fest, while still copying the display-card compose. 0 "
"disables (default).")
.range(0, 16 * 1024 * 1024)
.lifecycle(rex::cvar::Lifecycle::kHotReload);
REXCVAR_DEFINE_BOOL(readback_memexport, false, "GPU", REXCVAR_DEFINE_BOOL(readback_memexport, false, "GPU",
"Enable CPU readback of shader memexport writes for guest memory " "Enable CPU readback of shader memexport writes for guest memory "
"coherency (can reduce correctness issues, but may add GPU/CPU sync cost)") "coherency (can reduce correctness issues, but may add GPU/CPU sync cost)")

View file

@ -30,6 +30,7 @@
#include <rex/graphics/util/draw.h> #include <rex/graphics/util/draw.h>
#include <rex/graphics/xenos.h> #include <rex/graphics/xenos.h>
#include <rex/kernel/xboxkrnl/video.h> #include <rex/kernel/xboxkrnl/video.h>
#include <rex/chrono/clock.h>
#include <rex/logging.h> #include <rex/logging.h>
#include <rex/memory/utils.h> #include <rex/memory/utils.h>
#include <rex/system/kernel_state.h> #include <rex/system/kernel_state.h>
@ -2851,6 +2852,30 @@ bool D3D12CommandProcessor::IssueDraw(xenos::PrimitiveType primitive_type, uint3
// composition, CAS composites) must keep their resolves: skipping // composition, CAS composites) must keep their resolves: skipping
// lightpage composition left never-composed pages sampling garbage: // lightpage composition left never-composed pages sampling garbage:
// the light/dark checkerboard ground. // the light/dark checkerboard ground.
if (REXCVAR_GET(native_render_force_resolve_readback_max_length) > 0) {
// App-armed window diagnostics (Skate 3 photo flows): while the
// window is armed, show which resolves the suppression filter drops
// - pins passes (e.g. the photo display-card compose) whose output
// never reaches guest memory. Throttled.
static uint64_t s_drop_log_ms = 0;
static uint32_t s_drop_log_count = 0;
const uint64_t now_ms = rex::chrono::Clock::QueryHostUptimeMillis();
if (now_ms - s_drop_log_ms >= 1000) {
if (s_drop_log_count > 8) {
REXLOG_INFO("readback-window: (+{} more dropped resolves)",
s_drop_log_count - 8);
}
s_drop_log_ms = now_ms;
s_drop_log_count = 0;
}
if (++s_drop_log_count <= 8) {
REXLOG_INFO(
"readback-window: resolve DROPPED with suppressed pass "
"(surface_pitch={} copy_dest=0x{:08X})",
regs.Get<reg::RB_SURFACE_INFO>().surface_pitch,
uint32_t(regs[XE_GPU_REG_RB_COPY_DEST_BASE]));
}
}
return true; return true;
} }
return IssueCopy(); return IssueCopy();
@ -3730,9 +3755,35 @@ bool D3D12CommandProcessor::IssueCopy_ReadbackResolvePath() {
// framebuffer-sized resolves stay excluded. // framebuffer-sized resolves stay excluded.
const int32_t force_readback_max_length = const int32_t force_readback_max_length =
REXCVAR_GET(native_render_force_resolve_readback_max_length); REXCVAR_GET(native_render_force_resolve_readback_max_length);
const int32_t force_readback_min_length =
REXCVAR_GET(native_render_force_resolve_readback_min_length);
const bool force_window_cpu_copy = const bool force_window_cpu_copy =
force_readback_max_length > 0 && force_readback_max_length > 0 &&
written_length <= uint32_t(force_readback_max_length); written_length <= uint32_t(force_readback_max_length) &&
(force_readback_min_length <= 0 ||
written_length >= uint32_t(force_readback_min_length));
if (force_readback_max_length > 0) {
// App-armed window diagnostics (Skate 3 photo flows): every resolve
// that executes while the window is armed, and whether it takes the
// CPU-copy path. Throttled.
static uint64_t s_seen_log_ms = 0;
static uint32_t s_seen_log_count = 0;
const uint64_t now_ms = rex::chrono::Clock::QueryHostUptimeMillis();
if (now_ms - s_seen_log_ms >= 1000) {
if (s_seen_log_count > 8) {
REXLOG_INFO("readback-window: (+{} more resolves seen)",
s_seen_log_count - 8);
}
s_seen_log_ms = now_ms;
s_seen_log_count = 0;
}
if (++s_seen_log_count <= 8) {
REXLOG_INFO("readback-window: resolve dest=0x{:08X} len=0x{:X}{}",
written_address, written_length,
force_window_cpu_copy ? " -> CPU copy (kFull)"
: " (above window cap - GPU only)");
}
}
if (!force_scaled_resolve_cpu_copy && !force_window_cpu_copy) { if (!force_scaled_resolve_cpu_copy && !force_window_cpu_copy) {
return true; return true;
} }