Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
a7d9897
fix(agc): write host-only multisample memory as zeros in a capture sn…
chitters2004 Oct 28, 2026
e6252ee
fix(agc): zero host-only multisample memory in capture deltas
chitters2004 Oct 28, 2026
97284fe
fix(agc): release a parked REWIND in frame replay with its whole memo…
Tehshortbus Oct 28, 2026
db4ab05
fix(agc): read capture deltas and replay them while queues wait on th…
chitters2004 Oct 28, 2026
4ef54b6
fix(agc): keep frame replay's pacing warning at its previous text
chitters2004 Oct 29, 2026
79d7617
Merge main (ce9394b844) into fix/agc-capture-settled-deltas
chitters2004 Oct 29, 2026
76ce637
test(agc): shut down replay fixtures before static teardown
JJenkx Oct 29, 2026
6e75492
Merge remote-tracking branch 'origin/main' into fix/agc-capture-settl…
chitters2004 Oct 31, 2026
1126a88
Merge commit 'daa8d64e456287fc97e1c4cc3920ffdb9cfa4025' into fix/agc-…
chitters2004 Oct 31, 2026
c136864
Merge remote-tracking branch 'origin/main' into port-12567
chitters2004 Nov 1, 2026
bd7f4a2
Merge remote-tracking branch 'origin/main' into capture port
chitters2004 Nov 1, 2026
1ed9922
Merge branch 'main' into fix/agc-capture-settled-deltas
chitters2004 Nov 2, 2026
12ca5b8
fix(agc): flush a chunk's pending GPU writes before frame replay keep…
chitters2004 Nov 4, 2026
d625d2b
Merge branch 'main' into fix/agc-capture-settled-deltas
chitters2004 Nov 4, 2026
30d7714
Merge remote-tracking branch 'origin/main' into fix/agc-capture-settl…
chitters2004 Nov 5, 2026
c4afb1e
Merge remote-tracking branch 'origin/main' into fix/agc-capture-settl…
chitters2004 Nov 6, 2026
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
18 changes: 16 additions & 2 deletions core/libs/prx/libSceAgcDriver/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3644,7 +3644,7 @@ if(BUILD_TESTING)

if(WIN32 OR CMAKE_SYSTEM_NAME STREQUAL "Linux")
add_test_executable(agc_driver_frame_replay_tests tests/FrameReplay.cpp Execution/src/FrameCapture.cpp)
target_include_directories(agc_driver_frame_replay_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_include_directories(agc_driver_frame_replay_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR} $<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>)
target_link_libraries(agc_driver_frame_replay_tests PRIVATE libSceAgc libSceAgcDriver libkernel libc libzstd_static)
set_target_properties(agc_driver_frame_replay_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_driver_frame_replay_tests)
Expand Down Expand Up @@ -3677,10 +3677,24 @@ if(BUILD_TESTING)
set_tests_properties(agc_driver_frame_replay_concurrent PROPERTIES
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_FRAME_CAPTURE=${CMAKE_CURRENT_BINARY_DIR}/frame-replay-concurrent-capture;APS5_FRAME_CAPTURE_AT_FLIP=1;APS5_FRAME_CAPTURE_FRAMES=8"
TIMEOUT 120)
add_test(NAME agc_driver_frame_replay_rewind COMMAND agc_driver_frame_replay_tests $<TARGET_FILE:agc_frame_replay> rewind)
set_tests_properties(agc_driver_frame_replay_rewind PROPERTIES
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_FRAME_CAPTURE=${CMAKE_CURRENT_BINARY_DIR}/frame-replay-rewind-capture;APS5_FRAME_CAPTURE_AT_FLIP=1;APS5_FRAME_CAPTURE_FRAMES=1"
TIMEOUT 120)
if(WIN32)
set_tests_properties(agc_driver_frame_replay_concurrent PROPERTIES ENVIRONMENT_MODIFICATION
set_tests_properties(agc_driver_frame_replay_concurrent agc_driver_frame_replay_rewind PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
foreach(mode IN ITEMS rewind_store rewind_copy rewind_compute rewind_release rewind_fill rewind_split wait_label fence_reuse busy_queue)
add_test(NAME agc_driver_frame_replay_${mode} COMMAND agc_driver_frame_replay_tests $<TARGET_FILE:agc_frame_replay> ${mode})
set_tests_properties(agc_driver_frame_replay_${mode} PROPERTIES
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_FRAME_CAPTURE=${CMAKE_CURRENT_BINARY_DIR}/frame-replay-${mode}-capture;APS5_FRAME_CAPTURE_AT_FLIP=1;APS5_FRAME_CAPTURE_FRAMES=2"
SKIP_RETURN_CODE 77 TIMEOUT 120)
if(WIN32)
set_tests_properties(agc_driver_frame_replay_${mode} PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endforeach()
endif()
endif()

Expand Down
3 changes: 3 additions & 0 deletions core/libs/prx/libSceAgcDriver/Execution/include/Driver.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ extern "C" void AgcDriverShutdown_nid_postfix();
extern "C" void AgcDriverRequestScreenshot_nid_postfix();
extern "C" void AgcDriverSuspendPoint_nid_postfix();
extern "C" void AgcDriverSerialProgress_nid_postfix(std::uint64_t* accepted, std::uint64_t* completed);
extern "C" bool AgcDriverHoldSettled_nid_postfix(std::int64_t milliseconds);
extern "C" void AgcDriverReleaseSettled_nid_postfix();
extern "C" bool AgcDriverSettled_nid_postfix();
extern "C" std::size_t AgcDriverAwaitedWaits_nid_postfix(AgcDriver::AwaitedWait* waits, std::size_t capacity);
extern "C" void AgcDriverWaitForPipelineJournal_nid_postfix();
extern "C" std::uint64_t AgcDriverDispatchesAdoptedAhead_nid_postfix();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ class Driver {
std::vector<AwaitedWait> AwaitedWaits();
void WaitForPipelineJournal();
bool WaitIdleFor(std::chrono::milliseconds timeout);
bool WaitSettledFor(std::chrono::milliseconds timeout);
void ReleaseSettled();
bool Settled();
void RegisterVideoOutput(std::uint32_t handle, const std::shared_ptr<IVideoOutput>& output);
void UnregisterVideoOutput(std::uint32_t handle, const std::shared_ptr<IVideoOutput>& output);
void AttachWindow(const PresentationWindow& window);
Expand Down Expand Up @@ -129,6 +132,7 @@ class Driver {
void waitForFlipRoom(const Submission& submission);
void holdFlipBehindWorker(const Submission& submission);
bool awaitsTitle(std::uint64_t awaited) const;
bool parkedOnTitle() const;
void noteAwaitingTitle(std::uint32_t queue, std::uint64_t awaited);
void releaseFlipHold();
void reserveOutputs(Submission& submission);
Expand Down Expand Up @@ -387,7 +391,9 @@ class Driver {
std::atomic<std::uint64_t> shape{0};
};
std::array<AwaitedSlot, SubmissionQueueEnd - 0x20 + 1> awaitedSlots{};
static bool stillAwaited(const AwaitedSlot& slot, std::uint64_t awaited);
void noteWaitFields(std::uint32_t queue, std::span<const std::uint32_t> packet);
std::atomic<bool> captureHold{false};
std::atomic<std::uint64_t> flipSerial{0};
std::atomic<std::uint64_t> flipBatchesUnsignaled{0};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <span>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

namespace AgcDriver {
Expand Down Expand Up @@ -132,6 +133,8 @@ class FrameCaptureReader {

std::string_view FrameCallName(FrameCallKind kind);
bool AllZero(std::span<const std::byte> bytes);
using GuestRead = void (*)(std::uint64_t address, std::span<std::byte> destination);
std::uint64_t ReadAroundRanges(std::uint64_t address, std::span<std::byte> bytes, std::span<const std::pair<std::uint64_t, std::uint64_t>> skipped, GuestRead read);

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ class FrameRecorder {
void start();
void finish();
void snapshot(const Range& range);
void readGuest(std::uint64_t address, std::span<std::byte> bytes);
void writeReferenceFrames();
bool abandonIfDiskLow();
void stop(const char* reason);
Expand Down Expand Up @@ -104,6 +105,7 @@ class FrameRecorder {
std::uint64_t recordedCalls = 0;
std::uint64_t startAttempts = 0;
std::uint64_t unreadableBytes = 0;
std::uint64_t hostOnlySampleBytes = 0;
std::uint64_t allocationsGeneration = 0;
std::uint64_t lateRanges = 0;
std::vector<std::string> warnings;
Expand Down
1 change: 1 addition & 0 deletions core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ bool WaitSatisfiedUnchecked(std::span<const std::uint32_t> packet);
// Whether a WAIT_REG_MEM's compare holds for `value` (the 4 or 8 bytes a label the recorder still
// holds will store), without reading memory.
bool WaitComparesValue(std::span<const std::uint32_t> packet, std::uint64_t value);
bool WaitCompares(std::uint32_t function, std::uint64_t reference, std::uint64_t mask, std::uint64_t value);
std::size_t WaitAwaitedBytes(std::span<const std::uint32_t> packet);
std::size_t ConditionalWords(std::span<const std::uint32_t> packet);
std::uint32_t ReadCondition(std::span<const std::uint32_t> packet);
Expand Down
12 changes: 12 additions & 0 deletions core/libs/prx/libSceAgcDriver/Execution/src/Driver/EntryPoints.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,18 @@ extern "C" void AgcDriverSerialProgress_nid_postfix(std::uint64_t* accepted, std
*completed = done;
}

extern "C" bool AgcDriverHoldSettled_nid_postfix(std::int64_t milliseconds) {
return AgcDriver::DriverDetail::Driver::Get().WaitSettledFor(std::chrono::milliseconds(milliseconds));
}

extern "C" void AgcDriverReleaseSettled_nid_postfix() {
AgcDriver::DriverDetail::Driver::Get().ReleaseSettled();
}

extern "C" bool AgcDriverSettled_nid_postfix() {
return AgcDriver::DriverDetail::Driver::Get().Settled();
}

extern "C" void AgcDriverSubmitFlip_nid_postfix(const AgcDriver::FlipInfo& info) try {
auto* const recorder = AgcDriver::FrameRecorder::Active();
if (recorder == nullptr) {
Expand Down
35 changes: 35 additions & 0 deletions core/libs/prx/libSceAgcDriver/Execution/src/Driver/Lifecycle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,41 @@ bool Driver::WaitIdleFor(std::chrono::milliseconds timeout) {
return idle;
}

bool Driver::WaitSettledFor(std::chrono::milliseconds timeout) {
require(!onWorkerThread(), "worker cannot wait for itself");
std::unique_lock lock(mutex);
const auto deadline = std::chrono::steady_clock::now() + timeout;
const auto settled = [&] {
if (failure != nullptr || stopping) return true;
captureHold.store(true, std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_seq_cst);
if (completed >= accepted || parkedOnTitle()) return true;
captureHold.store(false, std::memory_order_relaxed);
return false;
};
++idleWaiters;
bool done = settled();
while (!done && std::chrono::steady_clock::now() < deadline) done = changed.wait_for(lock, std::chrono::milliseconds(1), settled);
--idleWaiters;
rethrowFailure();
checkStopping();
return done;
}

bool Driver::Settled() {
std::lock_guard lock(mutex);
return failure != nullptr || stopping || completed >= accepted || parkedOnTitle();
}

void Driver::ReleaseSettled() {
if (!captureHold.load(std::memory_order_relaxed)) return;
{
std::lock_guard lock(mutex);
captureHold.store(false, std::memory_order_relaxed);
}
changed.notify_all();
}

std::vector<AwaitedWait> Driver::AwaitedWaits() {
std::vector<AwaitedWait> result;
for (const auto& slot : awaitedSlots) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "prx/libSceAgcDriver/Execution/include/Driver/Queues/Submission.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver/Queues/SubmissionRing.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver/Diagnostics.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawPipeline.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver/Synchronization/SynchronizationStatistics.hpp"
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
#include "prx/libSceAgcDriver/Execution/include/Pm4.hpp"
Expand Down Expand Up @@ -252,6 +253,25 @@ bool Driver::awaitsTitle(std::uint64_t awaited) const {
return runningWorkers.load(std::memory_order_acquire) == 0 && orderHolders.load(std::memory_order_acquire) == 0 && !completionsPending() && !Graphics::Recorder::SnapshotWriteOverlaps(awaited, 4);
}

bool Driver::stillAwaited(const AwaitedSlot& slot, std::uint64_t awaited) {
const auto shape = slot.shape.load(std::memory_order_relaxed);
if (shape == 0) return (*reinterpret_cast<const volatile std::uint32_t*>(awaited) & Pm4::RewindValid) == 0;
const std::uint64_t value = shape >> 32u == 8 ? *reinterpret_cast<const volatile std::uint64_t*>(awaited) : *reinterpret_cast<const volatile std::uint32_t*>(awaited);
return !Pm4::WaitCompares(static_cast<std::uint32_t>(shape), slot.reference.load(std::memory_order_relaxed), slot.mask.load(std::memory_order_relaxed), value);
}

bool Driver::parkedOnTitle() const {
if (!queue0Uncommitted.empty() || DrawPipeline::Queue0().Busy() || DrawFinisher::Busy() || Graphics::Recorder::QueuedLabelsPending()) return false;
bool parked = false;
for (const auto& [queue, worker] : workers) {
const auto& slot = awaitedSlots[queue == 0 ? 0 : queue - 0x20 + 1];
const auto awaited = slot.address.load(std::memory_order_acquire);
if (awaited == 0 ? !worker.pending.empty() : !awaitsTitle(awaited) || !stillAwaited(slot, awaited)) return false;
parked = parked || awaited != 0;
}
return parked;
}

void Driver::noteAwaitingTitle(std::uint32_t queue, std::uint64_t awaited) {
if (queue != 0 || flipHolders.load(std::memory_order_acquire) == 0 || queue0AwaitsTitle.load(std::memory_order_acquire) || !awaitsTitle(awaited)) return;
std::lock_guard lock(mutex);
Expand Down Expand Up @@ -428,6 +448,7 @@ void Driver::Submit(const Packet* packet, std::uint32_t queue, bool cpuFlip) {
const auto progressDeadline = progress ? std::chrono::steady_clock::now() + std::chrono::milliseconds(2) : std::chrono::steady_clock::time_point{};
{
std::unique_lock lock(mutex);
if (captureHold.load(std::memory_order_relaxed)) changed.wait(lock, [&] { return !captureHold.load(std::memory_order_relaxed); });
rethrowFailure();
checkStopping();
require(accepted != std::numeric_limits<std::uint64_t>::max(), "submission serial overflow");
Expand Down Expand Up @@ -497,6 +518,7 @@ void Driver::SuspendPoint() {
auto* const capture = SubmissionCapture::Active();
auto captured = capture != nullptr ? SubmissionCapture::Prepare(0, SubmissionKind::Suspend, {}, {}) : CapturedSubmission{};
std::unique_lock lock(mutex);
if (captureHold.load(std::memory_order_relaxed)) changed.wait(lock, [&] { return !captureHold.load(std::memory_order_relaxed); });
rethrowFailure();
checkStopping();
require(accepted != std::numeric_limits<std::uint64_t>::max(), "submission serial overflow");
Expand Down Expand Up @@ -559,14 +581,18 @@ void Driver::noteWaitFields(std::uint32_t queue, std::span<const std::uint32_t>

void Driver::noteWaitBlocked(std::uint32_t queue, std::uint64_t awaited, bool blocked) {
auto& slot = awaitedSlots[queue == 0 ? 0 : queue - 0x20 + 1];
if (!blocked) slot.shape.store(0, std::memory_order_relaxed);
if (!blocked) {
runningWorkers.fetch_add(1, std::memory_order_seq_cst);
while (captureHold.load(std::memory_order_seq_cst)) PollSleep();
slot.shape.store(0, std::memory_order_relaxed);
}
slot.address.store(blocked ? awaited : 0, std::memory_order_release);
if (blocked) {
if (runningWorkers.fetch_sub(1, std::memory_order_acq_rel) == 1) {
idleEpoch.fetch_add(1, std::memory_order_release);
changed.notify_all();
}
} else runningWorkers.fetch_add(1, std::memory_order_acq_rel);
}
if (queue == 0) queue0Awaited.store(blocked ? awaited : 0, std::memory_order_release);
if (queue == 0 && !blocked) queue0AwaitsTitle.store(false, std::memory_order_release);
if (blocked && orderHolders.load(std::memory_order_acquire) != 0) {
Expand Down
17 changes: 17 additions & 0 deletions core/libs/prx/libSceAgcDriver/Execution/src/FrameCapture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,23 @@ struct Run {

}

std::uint64_t ReadAroundRanges(std::uint64_t address, std::span<std::byte> bytes, std::span<const std::pair<std::uint64_t, std::uint64_t>> skipped, GuestRead read) {
auto cursor = address;
const auto end = address + bytes.size();
std::uint64_t zeroed = 0;
for (const auto& [begin, size] : skipped) {
const auto skip = std::max(begin, cursor);
const auto stop = std::min(begin + size, end);
if (stop <= skip) continue;
read(cursor, bytes.subspan(static_cast<std::size_t>(cursor - address), static_cast<std::size_t>(skip - cursor)));
std::memset(bytes.data() + (skip - address), 0, static_cast<std::size_t>(stop - skip));
zeroed += stop - skip;
cursor = stop;
}
read(cursor, bytes.subspan(static_cast<std::size_t>(cursor - address)));
return zeroed;
}

bool AllZero(std::span<const std::byte> bytes) {
constexpr std::size_t Word = sizeof(std::uint64_t);
std::size_t at = 0;
Expand Down
22 changes: 16 additions & 6 deletions core/libs/prx/libSceAgcDriver/Execution/src/FrameRecorder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "prx/libSceAgcDriver/Execution/include/FrameRecorder.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp"
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
#include "prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp"
#include "prx/libSceAgcDriver/TraceRevision.hpp"
#include "prx/libc/include/GuestAllocations.hpp"
#include "prx/libc/include/GuestArena.hpp"
Expand Down Expand Up @@ -411,10 +412,14 @@ void FrameRecorder::writeDelta(std::span<const Span> spans, std::span<const std:
blocks.insert(blocks.end(), written.begin(), written.end());
}
auto& driver = DriverDetail::Driver::Get();
const struct Hold {
DriverDetail::Driver& driver;
~Hold() { driver.ReleaseSettled(); }
} hold{driver};
auto [accepted, completed] = driver.SerialProgress();
if (completed < accepted && (!added.empty() || !blocks.empty())) {
if (!driver.WaitIdleFor(std::chrono::seconds(10))) {
stop("stopped: the GPU work submitted before a memory delta did not finish within 10 s");
if (!driver.WaitSettledFor(std::chrono::seconds(10))) {
stop("stopped: the GPU work submitted before a memory delta neither finished nor waited for the title within 10 s");
return;
}
std::tie(accepted, completed) = driver.SerialProgress();
Expand All @@ -432,7 +437,7 @@ void FrameRecorder::writeDelta(std::span<const Span> spans, std::span<const std:
}
for (std::uint64_t at = 0; at < range.bytes;) {
bytes.resize(static_cast<std::size_t>(std::min<std::uint64_t>(FrameCaptureWriter::BlockBytes, range.bytes - at)));
GuestMemory::ReadCommittedUnsynced(range.address + at, bytes);
readGuest(range.address + at, bytes);
const bool zero = std::all_of(bytes.begin(), bytes.end(), [](std::byte value) { return value == std::byte{0}; });
if (!zero) {
writer->WriteDelta(group, completed, range.address + at, bytes);
Expand All @@ -456,7 +461,7 @@ void FrameRecorder::writeDelta(std::span<const Span> spans, std::span<const std:
}
for (const auto& [begin, end] : blocks) {
bytes.resize(static_cast<std::size_t>(end - begin));
GuestMemory::ReadCommittedUnsynced(begin, bytes);
readGuest(begin, bytes);
writer->WriteDelta(group, completed, begin, bytes);
deltaBytes += bytes.size();
any = true;
Expand All @@ -481,11 +486,15 @@ void FrameRecorder::snapshot(const Range& range) {
for (std::uint64_t at = 0; at < range.bytes; at += SnapshotPieceBytes) {
if (abandonIfDiskLow()) return;
bytes.resize(static_cast<std::size_t>(std::min<std::uint64_t>(SnapshotPieceBytes, range.bytes - at)));
GuestMemory::ReadCommitted(range.address + at, bytes);
readGuest(range.address + at, bytes);
snapshotBytes += writer->WriteSnapshot(range.address + at, bytes);
}
}

void FrameRecorder::readGuest(std::uint64_t address, std::span<std::byte> bytes) {
hostOnlySampleBytes += ReadAroundRanges(address, bytes, Graphics::HostOnlySampleRanges(address, bytes.size()), GuestMemory::ReadCommitted);
}

bool FrameRecorder::abandonIfDiskLow() {
std::error_code error;
const auto space = std::filesystem::space(directory, error);
Expand Down Expand Up @@ -583,7 +592,7 @@ std::string FrameRecorder::header() const {
text += ",\"arena_base\":" + std::to_string(arenaBase) + ",\"arena_bytes\":" + std::to_string(arenaBytes);
text += ",\"start_boundary\":" + std::to_string(startBoundary) + ",\"frames\":" + std::to_string(recordedFrames);
text += ",\"calls\":" + std::to_string(recordedCalls) + ",\"prologue_calls\":" + std::to_string(prologue.size());
text += ",\"ranges\":" + std::to_string(ranges.size()) + ",\"late_ranges\":" + std::to_string(lateRanges) + ",\"snapshot_bytes\":" + std::to_string(snapshotBytes) + ",\"unreadable_bytes\":" + std::to_string(unreadableBytes);
text += ",\"ranges\":" + std::to_string(ranges.size()) + ",\"late_ranges\":" + std::to_string(lateRanges) + ",\"snapshot_bytes\":" + std::to_string(snapshotBytes) + ",\"unreadable_bytes\":" + std::to_string(unreadableBytes) + ",\"host_only_sample_bytes\":" + std::to_string(hostOnlySampleBytes);
text += ",\"memory_groups\":" + std::to_string(deltaGroups) + ",\"delta_bytes\":" + std::to_string(deltaBytes);
text += ",\"capture_seconds\":" + std::to_string(std::chrono::duration<double>(std::chrono::steady_clock::now() - startedAt).count());
text += ",\"env\":{";
Expand All @@ -598,6 +607,7 @@ std::string FrameRecorder::header() const {
text += (first ? "" : ",") + jsonString(warning);
first = false;
}
if (hostOnlySampleBytes != 0) text += (first ? "" : ",") + jsonString(std::to_string(hostOnlySampleBytes) + " bytes under multisampled targets whose samples are only on the host are zero in the capture, so replay starts those targets without their earlier contents");
return text + "]}\n";
}

Expand Down
Loading
Loading