From d90d14e0210d87d24618aba1e870eabafbe0c262 Mon Sep 17 00:00:00 2001 From: Sylpheed RE agent Date: Tue, 1 Sep 2026 16:13:43 +0000 Subject: [PATCH] [RE] Log render-target state, resolves and PS constants on every UI draw The UI draw log recorded blend state, textures and geometry, which is enough to say WHICH sprite a draw is and how it composites, and not enough to answer whether a screen has more than one PASS. A second draw into an off-screen target, or a resolve of the EDRAM into a texture that a later full-screen quad samples, both look like just another quad in the old format. Three additions, all read straight out of the register file: mode= RB_MODECONTROL.edram_mode. kCopy (6) is how this GPU issues a RESOLVE, and it arrives through the same DRAW_INDX packet as a sprite -- so without this field a resolve was indistinguishable from a draw. rt0=/pitch RB_COLOR_INFO's EDRAM tile and format, RB_SURFACE_INFO's pitch and MSAA. A second target shows up as a different tile; a half-resolution post-process shows up as a pitch that is not the screen's. RESOLVE on a kCopy draw, RB_COPY_CONTROL and RB_COPY_DEST_BASE. That destination reappearing as a later tex[base=...] is what 'resolve-and-resample' means stated in addresses, rather than inferred from the picture. ps_c[...] the pixel shader's float constants, taken off its OWN float_bitmap -- the same one the backend uploads from -- so this is the shader's declared dependency set rather than a fixed window that could miss the one that matters. Pixel constants live at SHADER_CONSTANT_256_X and the c# printed is the index the shader's disassembly uses. Without the constants, an alpha ramp driven by a shader constant and one driven by per-vertex colour are the same picture. --- src/xenia/gpu/command_processor.cc | 82 ++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/src/xenia/gpu/command_processor.cc b/src/xenia/gpu/command_processor.cc index 20e75c6ae..25ada855a 100644 --- a/src/xenia/gpu/command_processor.cc +++ b/src/xenia/gpu/command_processor.cc @@ -18,6 +18,7 @@ #include "xenia/base/cvar.h" #include "xenia/base/logging.h" #include "xenia/base/profiling.h" +#include "xenia/base/math.h" #include "xenia/gpu/gpu_flags.h" #include "xenia/gpu/graphics_system.h" #include "xenia/gpu/packet_disassembler.h" @@ -323,6 +324,49 @@ void CommandProcessor::CaptureUiDrawForRE( uint32_t(bc.alpha_comb_fcn), uint32_t(bc.alpha_destblend), cc.value, mask); } + // ── RE: the RENDER TARGET this draw goes to, and whether it is a RESOLVE ── + // The question this answers: "is there a post-process pass at all?" is a + // question about render targets and passes, and the log above cannot see one. + // Two screens' worth of splash draws were read off a stream that recorded only + // blend state and geometry, so a second pass into an off-screen target — or a + // resolve of the EDRAM into a texture that a later full-screen quad samples — + // would have been invisible: it looks like just another quad. + // + // mode= RB_MODECONTROL.edram_mode. 0 = colour+depth (a normal draw), + // 4 = kCopy, which on this GPU is how a RESOLVE is issued — it + // arrives through the same DRAW_INDX packet as everything else, + // so without this field a resolve is indistinguishable from a + // sprite. + // rt0= RB_COLOR_INFO: the EDRAM tile the draw writes to, its format, + // and the exponent bias. A second pass into a DIFFERENT edram + // base is the signature of an off-screen target. + // pitch/msaa RB_SURFACE_INFO. A post-process at reduced resolution shows up + // here as a pitch that is not the screen's. + // RESOLVE … on a kCopy draw, where the EDRAM is being copied TO. That + // destination address reappearing as a `tex[base=…]` on a later + // draw is what "resolve-and-resample" means, stated in addresses + // rather than inferred from the picture. + { + auto mc = register_file_->Get(); + auto si = register_file_->Get(); + auto ci = register_file_->Get(); + ui_out << fmt::format( + " mode={} pitch={} msaa={} rt0=[tile={} fmt={} exp={}]", + uint32_t(mc.edram_mode), uint32_t(si.surface_pitch), + uint32_t(si.msaa_samples), + uint32_t(ci.color_base) | (uint32_t(ci.color_base_bit_11) << 11), + uint32_t(ci.color_format), int32_t(ci.color_exp_bias)); + if (mc.edram_mode == xenos::EdramMode::kCopy) { + auto cpc = register_file_->Get(); + ui_out << fmt::format( + " RESOLVE copy=0x{:08X}[src={} samp={} cmd={} cclr={} dclr={}]" + " dest=0x{:08X}", + cpc.value, uint32_t(cpc.copy_src_select), + uint32_t(cpc.copy_sample_select), uint32_t(cpc.copy_command), + uint32_t(cpc.color_clear_enable), uint32_t(cpc.depth_clear_enable), + register_file_->values[XE_GPU_REG_RB_COPY_DEST_BASE]); + } + } if (ps && ps->is_ucode_analyzed()) { for (const auto& tb : ps->texture_bindings()) { xenos::xe_gpu_texture_fetch_t tf = @@ -335,6 +379,44 @@ void CommandProcessor::CaptureUiDrawForRE( uint32_t(tf.format)); } } + // ── RE: the PIXEL SHADER's float constants, as the shader itself indexes ── + // "Where do a pass's parameters come from?" has four candidate answers — + // immediates in the command stream, the constant banks, a table in a pak, or + // a ramp computed in guest code. This log could not previously distinguish + // any of them, because it recorded no constants at all: an alpha ramp driven + // by a PS constant and one driven by per-vertex colour look identical once + // you are reading pixels. + // + // Only the constants the shader ACTUALLY READS are printed, off its own + // `float_bitmap` — the same bitmap the backend uses to upload them — so this + // is the shader's declared dependency set rather than a fixed window that + // might miss the one that matters or bury it in 200 unused vectors. Pixel + // constants live in the second half of the file (SHADER_CONSTANT_256_X), and + // the c# printed is the index the shader's own disassembly uses. + if (ps && ps->is_ucode_analyzed()) { + const auto& crm = ps->constant_register_map(); + ui_out << fmt::format("\n ps_c[n={}{}]:", crm.float_count, + crm.float_dynamic_addressing ? " DYNAMIC" : ""); + uint32_t printed = 0; + for (uint32_t w = 0; w < 4 && printed < 24; ++w) { + uint64_t bits = crm.float_bitmap[w]; + uint32_t b; + while (printed < 24 && xe::bit_scan_forward(bits, &b)) { + bits = xe::clear_lowest_bit(bits); + uint32_t idx = (w << 6) + b; + uint32_t r = XE_GPU_REG_SHADER_CONSTANT_256_X + (idx << 2); + ui_out << fmt::format(" c{}=({:.5f},{:.5f},{:.5f},{:.5f})", idx, + register_file_->Get(r), + register_file_->Get(r + 1), + register_file_->Get(r + 2), + register_file_->Get(r + 3)); + ++printed; + } + } + if (crm.float_count > printed) { + ui_out << fmt::format(" …+{} more", crm.float_count - printed); + } + } // The quad's geometry is the only thing that says WHICH element a draw is: // these sprites all share one shader and sample big texture pages, so the // vertex data is the identity. Dump attribute 0 of binding 0 for the first