diff --git a/src/xenia/gpu/command_processor.cc b/src/xenia/gpu/command_processor.cc index 89863b6ee..48d15e5d9 100644 --- a/src/xenia/gpu/command_processor.cc +++ b/src/xenia/gpu/command_processor.cc @@ -127,13 +127,13 @@ const char* ReVertexFormatName(xenos::VertexFormat f) { // ── Ship-placement capture (RE) ───────────────────────────────────────────── // A hotkey (F10 in the emulator window) requests a one-shot snapshot: the next -// batch of draws is dumped to xenia_ship_capture.log with each draw's guest -// vertex-buffer address, vertex/index counts, and up to 64 WORLD-space vertex -// positions. Capital-ship parts are transformed into world space before the -// draw, so these positions are ground truth for the assembled placement — a -// tool correlates each draw to a decoded .xpr part (vertex count → affine fit). -// De-duped by buffer address, so a static ship's parts collapse to one record -// each even across the few frames the budget spans. +// batch of draws is dumped to xenia_ship_capture_NN.log (NN = press number — +// several angles can be captured in one run without overwriting) with each +// draw's guest vertex-buffer address, vertex/index counts, up to 64 LOCAL +// vertex positions, and the first 48 VS float constants (c0..c2 = the WVP rows +// that place the part). De-duped by (buffer address, c0..c2 transform) — NOT by +// address alone — so the SAME part buffer drawn several times (two engine +// nacelles, every ship of a fleet) yields one record per distinct placement. namespace { constexpr int kShipCaptureBudget = 8000; // draws to scan per request std::atomic g_ship_capture_gen{0}; @@ -141,9 +141,9 @@ std::atomic g_ship_capture_remaining{0}; } // namespace void RequestShipCaptureFrame() { - g_ship_capture_gen.fetch_add(1, std::memory_order_relaxed); + uint32_t gen = g_ship_capture_gen.fetch_add(1, std::memory_order_relaxed) + 1; g_ship_capture_remaining.store(kShipCaptureBudget, std::memory_order_relaxed); - XELOGI("[SHIP-CAP] capture armed → xenia_ship_capture.log"); + XELOGI("[SHIP-CAP] capture armed → xenia_ship_capture_{:02d}.log", gen); } void CommandProcessor::CaptureShipDrawForRE( @@ -154,19 +154,24 @@ void CommandProcessor::CaptureShipDrawForRE( } static std::mutex cap_mutex; static std::ofstream cap_out; - static std::unordered_set cap_seen; // by vertex-buffer address + static std::unordered_set cap_seen; // by (vbase, WVP transform) static uint32_t cap_gen = 0; std::lock_guard lock(cap_mutex); if (g_ship_capture_remaining.load(std::memory_order_relaxed) <= 0) { return; } - // A new request (fresh generation) reopens the file and clears the de-dup set. + // A new request (fresh generation) opens its own numbered snapshot file and + // clears the de-dup set, so each F10 press yields an independent capture. uint32_t gen = g_ship_capture_gen.load(std::memory_order_relaxed); if (gen != cap_gen || !cap_out.is_open()) { - cap_out.open("xenia_ship_capture.log", std::ios::out | std::ios::trunc); + if (cap_out.is_open()) { + cap_out.close(); + } + auto name = fmt::format("xenia_ship_capture_{:02d}.log", gen); + cap_out.open(name, std::ios::out | std::ios::trunc); cap_seen.clear(); cap_gen = gen; - XELOGI("[SHIP-CAP] writing xenia_ship_capture.log"); + XELOGI("[SHIP-CAP] writing {}", name); } g_ship_capture_remaining.fetch_sub(1, std::memory_order_relaxed); if (!cap_out.is_open()) { @@ -196,11 +201,25 @@ void CommandProcessor::CaptureShipDrawForRE( if (pos_off_bytes < 0 || stride == 0 || vbase == 0) { return; // no float-position stream (UI/effects) — skip } - // One record per distinct vertex buffer. - if (!cap_seen.insert(vbase).second) { + // One record per distinct (buffer, placement): hash the c0..c2 WVP rows so + // repeated draws of the SAME buffer at DIFFERENT transforms (multi-instance + // parts, fleet ships) each get their own record. + auto vsc0 = register_file_->Get(); + uint64_t th = 1469598103934665603ull; // FNV-1a over the 12 c0..c2 floats + for (uint32_t i = 0; i < 12; ++i) { + uint32_t idx = vsc0.base + (i / 4); + if (idx >= 256) { + break; + } + uint32_t r = XE_GPU_REG_SHADER_CONSTANT_000_X + 4 * idx + (i % 4); + uint32_t bits = register_file_->values[r]; + th = (th ^ bits) * 1099511628211ull; + } + uint64_t key = (uint64_t(vbase) << 32) ^ (th & 0xFFFFFFFFull) ^ (th >> 32); + if (!cap_seen.insert(key).second) { return; } - if (cap_seen.size() > 4096) { + if (cap_seen.size() > 8192) { return; } uint32_t vcount = buf_bytes / stride;