[GPU] ship-capture: log each draw's index buffer, and key the de-dup on it

The offline XBG7 decoder can only assume where a block's index data lives; the
capture now states it. Each captured draw gains an `ib base=… count=… fmt=…
endian=… len=… delta_vb=… min=… max=… idx: …` line — the guest index base, the
draw's index count, and the min/max index value read out of guest memory, which
together say both where the index buffer sits relative to the vertex buffer and
how much of the vertex pool the draw covers.

Also mix the index range into the capture's de-dup key. The engine issues several
draws over one vertex buffer, each indexing a sub-range (a 119-vertex hull LOD
draws 21 indices first, then 225); keying on (vbase, transform) alone kept only
the first batch, which reads like a mysteriously short draw and cost an earlier
session a spurious "the capture disagrees with the descriptor" mystery.

Read-only diagnostics behind the existing F10 hotkey; no emulation behaviour
changes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NptfmpjdpNCKEez6d2xvA9
This commit is contained in:
2026-08-13 05:58:02 +00:00
parent a08526dff0
commit 31366e5cac

View File

@@ -215,7 +215,19 @@ void CommandProcessor::CaptureShipDrawForRE(
uint32_t bits = register_file_->values[r];
th = (th ^ bits) * 1099511628211ull;
}
uint64_t key = (uint64_t(vbase) << 32) ^ (th & 0xFFFFFFFFull) ^ (th >> 32);
// Mix the index range into the key as well (added 2026-08-13). The engine
// issues SEVERAL draws over one vertex buffer, each with its own index
// sub-range (the first Stage_02 capture showed a 119-vertex hull LOD drawn
// with 21 indices) — keying on (vbase, transform) alone kept only the first
// batch, which reads like a mysteriously short draw. With the range in the key
// every batch is recorded, so the block's full index extent is observable.
uint64_t ib_key = 0;
if (index_buffer_info) {
ib_key = (uint64_t(index_buffer_info->guest_base) << 20) ^
uint64_t(index_buffer_info->count);
}
uint64_t key = (uint64_t(vbase) << 32) ^ (th & 0xFFFFFFFFull) ^ (th >> 32) ^
(ib_key * 1099511628211ull);
if (!cap_seen.insert(key).second) {
return;
}
@@ -227,6 +239,47 @@ void CommandProcessor::CaptureShipDrawForRE(
"DRAW vbase=0x{:08X} stride={} vcount={} indices={} prim={} vs=0x{:016X}\n",
vbase, stride, vcount, uint32_t(init.num_indices),
uint32_t(init.prim_type), vs->ucode_data_hash());
// The guest's INDEX buffer for this draw. Our offline mesh decoder only
// *assumes* the index buffer sits immediately before the vertex buffer; this
// line is the ground truth for that assumption (ib base vs vbase), and the
// decoded min/max index says how much of the vertex pool the draw really
// covers — which is what the `indices=` field alone cannot answer.
if (index_buffer_info) {
const auto& ib = *index_buffer_info;
bool i32 = ib.format == xenos::IndexFormat::kInt32;
uint32_t icount = ib.count;
cap_out << fmt::format(
" ib base=0x{:08X} count={} fmt={} endian={} len={} delta_vb={}",
ib.guest_base, icount, i32 ? "u32" : "u16", uint32_t(ib.endianness),
ib.length, int64_t(vbase) - int64_t(ib.guest_base));
const uint8_t* ip = memory_->TranslatePhysical<const uint8_t*>(ib.guest_base);
if (ip && icount) {
uint32_t scan = icount < 65536 ? icount : 65536;
uint32_t imin = 0xFFFFFFFFu, imax = 0;
auto rd = [&](uint32_t k) -> uint32_t {
// Guest index data is big-endian in memory (the endianness field says
// how the GPU swaps it); read it that way and record the field so the
// offline side can compensate if a draw ever differs.
const uint8_t* q = ip + (i32 ? k * 4 : k * 2);
return i32 ? (uint32_t(q[0]) << 24) | (uint32_t(q[1]) << 16) |
(uint32_t(q[2]) << 8) | uint32_t(q[3])
: (uint32_t(q[0]) << 8) | uint32_t(q[1]);
};
for (uint32_t k = 0; k < scan; ++k) {
uint32_t v = rd(k);
if (v < imin) imin = v;
if (v > imax) imax = v;
}
cap_out << fmt::format(" min={} max={} idx:", imin, imax);
uint32_t nd = icount < 24 ? icount : 24;
for (uint32_t k = 0; k < nd; ++k) {
cap_out << fmt::format(" {}", rd(k));
}
}
cap_out << "\n";
} else {
cap_out << " ib auto\n";
}
uint32_t n = vcount < 64 ? vcount : 64;
cap_out << " pos:";
auto be_f32 = [](const uint8_t* q) {