Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions graphics/frontend/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ if(BUILD_TESTING)
PRIVATE
DolRuntime::replay_core
)
target_compile_definitions(replay_digest_tests
PRIVATE
DOLGX_REPLAY_TEST_PATH="$<TARGET_FILE:dolgx_replay>"
)
add_dependencies(replay_digest_tests dolgx_replay)
add_test(NAME replay_digest_tests COMMAND replay_digest_tests)

add_executable(dff2dolt_tests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,11 @@
// emitted before the first FRAME_BEGIN, using
// the exact command sequence + register
// exclusion lists of FifoPlayer::LoadRegisters
// header TMEM snapshot -> one TMEM_SNAPSHOT record before the preamble
// No PRESENT_STATS records are written (Aurora ground truth does not exist
// for a Dolphin recording); replay closes frames at FRAME_BEGIN/EOF instead.
//
// Not restored (v1 gaps, both logged in stats): TMEM snapshot (Dolphin
// memcpys it outside the FIFO; not expressible as raw commands — S9 scenes
// carry a near-empty snapshot) and FifoPlayer::ClearEfb's synthetic clear.
// Not restored: FifoPlayer::ClearEfb's synthetic clear.

#include <cstddef>
#include <cstdint>
Expand All @@ -44,7 +43,8 @@ struct ConvertStats {
std::uint32_t preamble_cp_regs = 0;
std::uint32_t preamble_xf_words = 0; // XF memory words written (4096)
std::uint32_t preamble_xf_regs = 0;
std::uint32_t tmem_nonzero_bytes = 0; // snapshot content we did NOT restore
std::uint32_t tmem_snapshot_bytes = 0;
std::uint32_t tmem_nonzero_bytes = 0;
char game_id[9] = {};
};

Expand Down
27 changes: 20 additions & 7 deletions graphics/frontend/include/dolruntime/aurora_recomp/replay.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,28 @@ struct ReplayResult {
std::vector<FrameDigest> frames;
};

using ReplayDrawObserver = void (*)(std::uint32_t frame_index,
std::uint32_t frame_draw,
const ConsumedDraw &draw,
unsigned long long cumulative_draw,
void *user);
using ReplayMemUpdateObserver = void (*)(std::uint32_t guest_address,
std::uint32_t size, void *user);

// Replays every record of a freshly opened (or rewound) reader. The optional
// observer taps every frontend decode event (histograms).
ReplayResult replay_trace(
trace::TraceReader& reader,
RetailGxFrontend::TraceEventObserver event_observer = nullptr,
void* event_observer_user = nullptr);
// observers tap frontend decode events, completed draws, and validated MEM1
// updates in trace order.
ReplayResult
replay_trace(trace::TraceReader &reader,
RetailGxFrontend::TraceEventObserver event_observer = nullptr,
void *event_observer_user = nullptr,
ReplayDrawObserver draw_observer = nullptr,
void *draw_observer_user = nullptr,
ReplayMemUpdateObserver mem_update_observer = nullptr,
void *mem_update_observer_user = nullptr);

// "frame N draws D zdraws Z verts V topo T store S elems E fnv X state H"
std::string format_digest_line(const FrameDigest& f);
std::string format_digest_line(const FrameDigest &f);

struct StatsCompareResult {
bool ok = false;
Expand All @@ -69,6 +82,6 @@ struct StatsCompareResult {
// 4*draws+4 (Aurora pads each unmerged draw to 4 bytes). Transient rule: the
// run fails only when more than 2 consecutive frames mismatch. Topology/
// storage extents carry Aurora merge/cache caveats and are never gated.
StatsCompareResult compare_against_stats(const ReplayResult& result);
StatsCompareResult compare_against_stats(const ReplayResult &result);

} // namespace dolruntime::aurora_recomp::replay
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ extern "C" {

namespace dolruntime::aurora_recomp {

// Replay-only physical aperture used to let the existing guest resolver carry
// Dolphin's initial TMEM snapshot into CI texture packets. It is outside GC
// MEM1 and remains an in-memory address identity; no .dolt guest memory record
// uses this range.
constexpr std::uint32_t kTmemSnapshotAddressBase = 0x1F000000u;
constexpr std::uint32_t kTmemSnapshotMaxBytes = 0x00100000u;

struct DrawTransformSnapshot {
std::uint32_t transform_flags = 0;
std::uint32_t current_pn_matrix = 0;
Expand Down Expand Up @@ -50,6 +57,11 @@ class RetailGxFrontend {

void reset(const DolGuestAddressResolver* resolver = nullptr);

// Installs the byte extent of a TMEM_SNAPSHOT record. The bytes remain owned
// by the replay context/resolver; this seeds snapshot-backed TLUT mappings as
// their BP texture state arrives.
bool restore_tmem_snapshot(std::uint32_t byte_size);

bool set_vertex_layout(std::uint8_t vtx_fmt, std::uint32_t vertex_size);
bool set_indexed_attr(std::uint8_t vtx_fmt, std::uint8_t attr,
std::uint32_t vertex_offset, std::uint8_t index_size,
Expand Down Expand Up @@ -111,6 +123,7 @@ class RetailGxFrontend {
bool record_fifo_bytes, std::uint32_t depth,
std::size_t* consumed);
bool handle_bp(std::uint32_t raw);
bool seed_snapshot_tlut(std::uint8_t slot);
bool maybe_resolve_texture(std::uint8_t slot);
bool handle_copy_trigger(std::uint32_t value);
bool handle_draw(std::uint8_t command,
Expand All @@ -135,6 +148,7 @@ class RetailGxFrontend {

bool packet_drain_enabled_ = false;
std::uint64_t zero_vertex_draws_ = 0;
std::uint32_t tmem_snapshot_size_ = 0;
TraceEventObserver event_observer_ = nullptr;
void* event_observer_user_ = nullptr;
std::uint32_t emitted_trace_count_ = 0;
Expand Down
4 changes: 4 additions & 0 deletions graphics/frontend/include/dolruntime/aurora_recomp/trace.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ enum class RecordKind : std::uint8_t {
SetArray = 4, // u8 attr | u32 guest_addr | u16 stride — HLE bridge record
MemUpdate = 5, // u32 guest_addr | u32 byte_size | bytes (resolved guest memory)
PresentStats = 6, // u32 frame_index | 9 x u32 AuroraStats fields
TmemSnapshot = 7, // u32 byte_size | raw Dolphin TMEM bytes (initial state)
};

struct TraceHeader {
Expand Down Expand Up @@ -74,6 +75,7 @@ class TraceWriter {
std::uint16_t stride);
void mem_update(std::uint32_t guest_addr, const void* bytes,
std::uint32_t byte_size);
void tmem_snapshot(const void* bytes, std::uint32_t byte_size);
void present_stats(const PresentStats& stats);

// Flushes and closes; returns false if any write failed at any point.
Expand Down Expand Up @@ -135,6 +137,8 @@ bool decode_set_array(const RecordView& r, std::uint8_t& attr,
std::uint32_t& guest_addr, std::uint16_t& stride);
bool decode_mem_update(const RecordView& r, std::uint32_t& guest_addr,
std::span<const std::uint8_t>& bytes);
bool decode_tmem_snapshot(const RecordView& r,
std::span<const std::uint8_t>& bytes);
bool decode_present_stats(const RecordView& r, PresentStats& out);

} // namespace dolruntime::aurora_recomp::trace
14 changes: 13 additions & 1 deletion graphics/frontend/src/dff2dolt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ class DffFile {
kXfRegsSize, xf_regs_, error, "XF register snapshot"))
return false;

// TMEM snapshot (v4+): only counted, never restored (see header comment).
// TMEM snapshot (v4+): Dolphin restores this outside the FIFO before
// replaying the register snapshot and frame stream.
tmem_nonzero_ = 0;
if (header_.version >= 4u && header_.tex_mem_size != 0u) {
if (!range_ok(header_.tex_mem_offset, header_.tex_mem_size))
Expand Down Expand Up @@ -188,6 +189,11 @@ class DffFile {
const std::uint32_t* xf_mem() const { return xf_mem_; }
const std::uint32_t* xf_regs() const { return xf_regs_; }
std::uint32_t tmem_nonzero() const { return tmem_nonzero_; }
std::span<const std::uint8_t> tmem() const {
if (header_.version < 4u || header_.tex_mem_size == 0u)
return {};
return {bytes_ + header_.tex_mem_offset, header_.tex_mem_size};
}
const std::uint8_t* file_bytes() const { return bytes_; }

private:
Expand Down Expand Up @@ -356,6 +362,8 @@ bool convert(const std::uint8_t* dff_bytes, std::size_t dff_size,
*stats = ConvertStats{};
stats->dff_version = dff.header().version;
stats->frames = dff.header().frame_count;
stats->tmem_snapshot_bytes =
static_cast<std::uint32_t>(dff.tmem().size());
stats->tmem_nonzero_bytes = dff.tmem_nonzero();
std::memcpy(stats->game_id, dff.header().game_id, 8u);
}
Expand All @@ -371,6 +379,10 @@ bool convert(const std::uint8_t* dff_bytes, std::size_t dff_size,
return false;
}

const std::span<const std::uint8_t> tmem = dff.tmem();
if (!tmem.empty())
writer.tmem_snapshot(tmem.data(), static_cast<std::uint32_t>(tmem.size()));

const std::vector<std::uint8_t> preamble = build_state_preamble(dff, stats);
write_gx_run(writer, preamble.data(), preamble.size(), stats);

Expand Down
Loading