[GPU] ship-capture: numbered per-press snapshots + capture every instance
Some checks failed
Orchestrator / Commit Message Validation (push) Has been skipped
Orchestrator / Lint (push) Failing after 2m0s
Orchestrator / Windows (x86-64) (push) Has been skipped
Orchestrator / Linux (x86-64) (push) Has been skipped
Orchestrator / Create Release (push) Has been skipped
Some checks failed
Orchestrator / Commit Message Validation (push) Has been skipped
Orchestrator / Lint (push) Failing after 2m0s
Orchestrator / Windows (x86-64) (push) Has been skipped
Orchestrator / Linux (x86-64) (push) Has been skipped
Orchestrator / Create Release (push) Has been skipped
F10 now writes xenia_ship_capture_NN.log (NN = press number) so several angles can be captured in one run without overwriting, and de-dups by (vertex-buffer address, c0..c2 WVP hash) instead of address alone -- the same part buffer drawn at different transforms (two engine nacelles, each ship of a fleet) now yields one record per distinct placement. Seen-set cap raised to 8192. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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<uint32_t> g_ship_capture_gen{0};
|
||||
@@ -141,9 +141,9 @@ std::atomic<int> 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<uint32_t> cap_seen; // by vertex-buffer address
|
||||
static std::unordered_set<uint64_t> cap_seen; // by (vbase, WVP transform)
|
||||
static uint32_t cap_gen = 0;
|
||||
std::lock_guard<std::mutex> 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<reg::SQ_VS_CONST>();
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user