RE: log a CONTENT hash beside every sampled texture
A base address cannot distinguish 'the guest decoded a new frame' from 'the guest rotated to the next buffer of a triple-buffered set' -- a rotating buffer visits the same three addresses either way. Reading a clean one-base-change-per-present as one decode per present is how docs/re/guest-frame-rate-measured.md reached a conclusion it had to withdraw. h=<FNV-1a over 4096 sampled bytes>, omitted rather than faked when the address does not translate, so a missing hash cannot read as a matching one. Sampled rather than full: a 1280x720 plane is 900 KB and hashing all of it per draw would change the thing being measured.
This commit is contained in:
@@ -277,7 +277,21 @@ void CommandProcessor::CaptureUiDrawForRE(
|
||||
return;
|
||||
}
|
||||
if (frame != ui_last_frame) {
|
||||
ui_out << fmt::format("--- frame {} ---\n", frame);
|
||||
// ── RE: GUEST time on the frame boundary, not host time ─────────────────
|
||||
// The question this exists for: the animation clock's rate. A step in a
|
||||
// sprite's alpha per FRAME is not a rate — under this emulator the frame
|
||||
// rate is whatever the host can manage — and a host wall-clock duration is
|
||||
// the instrument that has already cost this corpus four withdrawn claims.
|
||||
//
|
||||
// `Clock::QueryGuestTickCount()` is the timebase the GUEST reads, at
|
||||
// `guest_tick_frequency()` (set to 50 MHz in emulator.cc) with the guest
|
||||
// time scalar applied. So a duration computed from these two numbers is the
|
||||
// duration the GAME experienced, which is the only one its own integrator
|
||||
// could have used. Printing the frequency beside the count means the reader
|
||||
// does not have to know what it was set to.
|
||||
ui_out << fmt::format("--- frame {} gtick={} gfreq={} ---\n", frame,
|
||||
Clock::QueryGuestTickCount(),
|
||||
Clock::guest_tick_frequency());
|
||||
ui_last_frame = frame;
|
||||
}
|
||||
|
||||
@@ -372,11 +386,42 @@ void CommandProcessor::CaptureUiDrawForRE(
|
||||
xenos::xe_gpu_texture_fetch_t tf =
|
||||
register_file_->GetTextureFetch(tb.fetch_constant);
|
||||
// Dimensions are stored as (actual - 1).
|
||||
ui_out << fmt::format(" tex[base=0x{:08X} {}x{} fmt={}]",
|
||||
uint32_t(tf.base_address) << 12,
|
||||
uint32_t(tf.size_2d.width) + 1,
|
||||
uint32_t(tf.size_2d.height) + 1,
|
||||
uint32_t(tf.format));
|
||||
// ── RE: a CONTENT hash of the sampled texture, not just its address ──
|
||||
// The question this answers: "did the guest DECODE a new frame, or did it
|
||||
// merely ROTATE to the next buffer of a triple-buffered set?" A base
|
||||
// address cannot tell those apart — a rotating buffer visits the same three
|
||||
// addresses whether or not anything was written into them — and reading a
|
||||
// clean 1-base-change-per-present as "one decode per present" is exactly
|
||||
// how `guest-frame-rate-measured.md` reached a conclusion it had to
|
||||
// withdraw. Identical content on consecutive presents means rotation
|
||||
// without decode; changing content means a genuine decode.
|
||||
//
|
||||
// Sampled, not full: a 1280x720 plane is 900 KB and hashing all of it per
|
||||
// draw would change the thing being measured. 4096 bytes spread across the
|
||||
// whole allocation is plenty to separate "identical" from "different" and
|
||||
// costs nothing. `h=` is omitted rather than faked when the address does
|
||||
// not translate, so a missing hash can never be read as a matching one.
|
||||
uint32_t tbase = uint32_t(tf.base_address) << 12;
|
||||
uint32_t tw = uint32_t(tf.size_2d.width) + 1;
|
||||
uint32_t th = uint32_t(tf.size_2d.height) + 1;
|
||||
ui_out << fmt::format(" tex[base=0x{:08X} {}x{} fmt={}",
|
||||
tbase, tw, th, uint32_t(tf.format));
|
||||
if (tw > 1 && th > 1) {
|
||||
const uint8_t* tp =
|
||||
memory_->TranslatePhysical<const uint8_t*>(tbase);
|
||||
if (tp) {
|
||||
// FNV-1a over a fixed stride so the sample set is deterministic and
|
||||
// does not depend on the format's true bytes-per-pixel.
|
||||
uint64_t hsh = 1469598103934665603ull;
|
||||
uint32_t span = tw * th; // >= 1 byte per texel for every format here
|
||||
uint32_t step = std::max<uint32_t>(1u, span / 4096u);
|
||||
for (uint32_t o = 0; o < span; o += step) {
|
||||
hsh = (hsh ^ tp[o]) * 1099511628211ull;
|
||||
}
|
||||
ui_out << fmt::format(" h={:016X}", hsh);
|
||||
}
|
||||
}
|
||||
ui_out << "]";
|
||||
}
|
||||
}
|
||||
// ── RE: the PIXEL SHADER's float constants, as the shader itself indexes ──
|
||||
|
||||
Reference in New Issue
Block a user