[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:
@@ -215,7 +215,19 @@ void CommandProcessor::CaptureShipDrawForRE(
|
|||||||
uint32_t bits = register_file_->values[r];
|
uint32_t bits = register_file_->values[r];
|
||||||
th = (th ^ bits) * 1099511628211ull;
|
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) {
|
if (!cap_seen.insert(key).second) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -227,6 +239,47 @@ void CommandProcessor::CaptureShipDrawForRE(
|
|||||||
"DRAW vbase=0x{:08X} stride={} vcount={} indices={} prim={} vs=0x{:016X}\n",
|
"DRAW vbase=0x{:08X} stride={} vcount={} indices={} prim={} vs=0x{:016X}\n",
|
||||||
vbase, stride, vcount, uint32_t(init.num_indices),
|
vbase, stride, vcount, uint32_t(init.num_indices),
|
||||||
uint32_t(init.prim_type), vs->ucode_data_hash());
|
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;
|
uint32_t n = vcount < 64 ? vcount : 64;
|
||||||
cap_out << " pos:";
|
cap_out << " pos:";
|
||||||
auto be_f32 = [](const uint8_t* q) {
|
auto be_f32 = [](const uint8_t* q) {
|
||||||
|
|||||||
Reference in New Issue
Block a user