Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
22 changes: 19 additions & 3 deletions core/libs/prx/libSceAgcDriver/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3731,11 +3731,11 @@ if(BUILD_TESTING)
endif()

add_test_executable(agc_driver_frame_replay_state_tests tests/FrameReplayState.cpp Execution/src/FrameCapture.cpp)
target_include_directories(agc_driver_frame_replay_state_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_include_directories(agc_driver_frame_replay_state_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR})
target_link_libraries(agc_driver_frame_replay_state_tests PRIVATE libSceAgcDriver libkernel libc libzstd_static)
set_target_properties(agc_driver_frame_replay_state_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_driver_frame_replay_state_tests)
set(agcFrameReplayModes order tokens blocking)
set(agcFrameReplayModes order tokens blocking reservations reservation_failure)
if(WIN32 OR CMAKE_SYSTEM_NAME STREQUAL "Linux")
list(APPEND agcFrameReplayModes zero shader shader_host)
endif()
Expand Down Expand Up @@ -3772,12 +3772,28 @@ if(BUILD_TESTING)
add_dependencies(agc_driver_frame_replay_tests agc_frame_replay)
add_test(NAME agc_driver_frame_replay COMMAND agc_driver_frame_replay_tests $<TARGET_FILE:agc_frame_replay>)
set_tests_properties(agc_driver_frame_replay PROPERTIES
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_FRAME_CAPTURE=${CMAKE_CURRENT_BINARY_DIR}/frame-replay-capture;APS5_FRAME_CAPTURE_AT_FLIP=1;APS5_FRAME_CAPTURE_FRAMES=2"
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_FRAME_CAPTURE=${CMAKE_CURRENT_BINARY_DIR}/frame replay capture;APS5_FRAME_CAPTURE_AT_FLIP=1;APS5_FRAME_CAPTURE_FRAMES=2"
TIMEOUT 120)
if(WIN32)
set_tests_properties(agc_driver_frame_replay PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test(NAME agc_driver_frame_replay_cold_pipelines COMMAND agc_driver_frame_replay_tests $<TARGET_FILE:agc_frame_replay> pipelines)
set_tests_properties(agc_driver_frame_replay_cold_pipelines PROPERTIES
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=0;APS5_HEADLESS_PIPELINE_CACHE=1;ANYPS5_SHADER_CACHE_DIR=${CMAKE_CURRENT_BINARY_DIR}/frame replay pipeline cache;APS5_FRAME_CAPTURE=${CMAKE_CURRENT_BINARY_DIR}/frame replay cold capture;APS5_FRAME_CAPTURE_AT_FLIP=1;APS5_FRAME_CAPTURE_FRAMES=2"
TIMEOUT 30)
if(WIN32)
set_tests_properties(agc_driver_frame_replay_cold_pipelines PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test(NAME agc_driver_frame_replay_reservation_error COMMAND agc_driver_frame_replay_tests $<TARGET_FILE:agc_frame_replay> reservation_error)
set_tests_properties(agc_driver_frame_replay_reservation_error PROPERTIES
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_FRAME_CAPTURE=${CMAKE_CURRENT_BINARY_DIR}/frame replay reservation error"
TIMEOUT 30)
if(WIN32)
set_tests_properties(agc_driver_frame_replay_reservation_error PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test(NAME agc_driver_frame_replay_concurrent COMMAND agc_driver_frame_replay_tests $<TARGET_FILE:agc_frame_replay> concurrent)
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"
Expand Down
1 change: 1 addition & 0 deletions core/libs/prx/libSceAgcDriver/Execution/include/Driver.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ 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" 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();
extern "C" std::uint64_t AgcDriverDispatchesRejectedAhead_nid_postfix();
extern "C" void AgcDriverSetAheadAdoptHook_nid_postfix(void (*hook)());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ class Driver {
std::pair<std::uint64_t, std::uint64_t> SerialProgress();
std::uint64_t FlipSubmissions() const;
std::vector<AwaitedWait> AwaitedWaits();
void WaitForPipelineJournal();
bool WaitIdleFor(std::chrono::milliseconds timeout);
void RegisterVideoOutput(std::uint32_t handle, const std::shared_ptr<IVideoOutput>& output);
void UnregisterVideoOutput(std::uint32_t handle, const std::shared_ptr<IVideoOutput>& output);
Expand Down Expand Up @@ -449,7 +450,7 @@ class Driver {
std::map<std::uint32_t, QueueState> queues;
std::map<std::uint32_t, std::shared_ptr<IVideoOutput>> outputs;
struct EopFlip {
std::uint32_t handle;
FlipInfo info;
std::shared_ptr<IFlipRequest> request;
};
std::map<std::uint32_t, EopFlip> eopFlips;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ struct FlipInfo {
std::int64_t argument;
};

class EopFlipAlreadyReserved final : public std::runtime_error {
public:
EopFlipAlreadyReserved() : std::runtime_error("EOP flip token is already reserved for this flip") {}
};

class IFlipRequest {
public:
virtual ~IFlipRequest() = default;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,10 @@ extern "C" std::size_t AgcDriverAwaitedWaits_nid_postfix(AgcDriver::AwaitedWait*
return count;
}

extern "C" void AgcDriverWaitForPipelineJournal_nid_postfix() {
AgcDriver::DriverDetail::Driver::Get().WaitForPipelineJournal();
}

extern "C" void AgcDriverSerialProgress_nid_postfix(std::uint64_t* accepted, std::uint64_t* completed) {
const auto [submitted, done] = AgcDriver::DriverDetail::Driver::Get().SerialProgress();
*accepted = submitted;
Expand Down
10 changes: 10 additions & 0 deletions core/libs/prx/libSceAgcDriver/Execution/src/Driver/Lifecycle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,16 @@ std::vector<AwaitedWait> Driver::AwaitedWaits() {
return result;
}

void Driver::WaitForPipelineJournal() {
std::shared_ptr<VulkanDevice> localDevice = device.Load();
if (localDevice == nullptr) {
std::lock_guard gpuLock(GuestMemory::GpuMutex());
if (device == nullptr) device = std::make_shared<VulkanDevice>();
localDevice = device;
}
localDevice->WaitForPipelineJournalReplay();
}

std::pair<std::uint64_t, std::uint64_t> Driver::SerialProgress() {
std::lock_guard lock(mutex);
return {accepted, completed};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,17 @@ std::uint32_t Driver::ReserveEopFlipAt(const FlipInfo& info, std::uint32_t token
std::uint32_t Driver::reserveEopFlip(const FlipInfo& info, std::uint32_t token) {
rethrowFailure();
checkStopping();
require(token >= EopFlipTokenBase && token <= EopFlipTokenLimit && !eopFlips.contains(token), "EOP flip token is not free");
require(token >= EopFlipTokenBase && token <= EopFlipTokenLimit, "EOP flip token is out of range");
const auto output = outputs.find(info.handle);
require(output != outputs.end(), "EOP flip references an unregistered video output");
if (const auto found = eopFlips.find(token); found != eopFlips.end()) {
const auto& reserved = found->second.info;
require(reserved.handle == info.handle && reserved.index == info.index && reserved.mode == info.mode && reserved.argument == info.argument, "EOP flip token is reserved for a different flip");
throw EopFlipAlreadyReserved{};
}
auto request = output->second->TryReserve(info);
if (!request) return 0;
eopFlips.emplace(token, EopFlip{info.handle, std::move(request)});
eopFlips.emplace(token, EopFlip{info, std::move(request)});
nextEopToken = std::max(nextEopToken, token + 1);
return token;
}
Expand All @@ -53,7 +58,7 @@ void Driver::UnregisterVideoOutput(std::uint32_t handle, const std::shared_ptr<I
std::lock_guard lock(mutex);
const auto it = outputs.find(handle);
require(it != outputs.end() && it->second == output, "video output registration mismatch");
std::erase_if(eopFlips, [handle](const auto& entry) { return entry.second.handle == handle; });
std::erase_if(eopFlips, [handle](const auto& entry) { return entry.second.info.handle == handle; });
outputs.erase(it);
}

Expand Down
3 changes: 2 additions & 1 deletion core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2189,7 +2189,8 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) :
Graphics::PrepareImportWatch(graphicsContext());
state->bufferPool = std::make_shared<Graphics::BufferPool>(graphicsContext());
state->emptyBuffer = std::make_unique<Graphics::Buffer>(graphicsContext(), Graphics::EmptyBufferBytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
state->pipelineCache = std::make_unique<Graphics::PipelineCache>(graphicsContext(), state->properties, window != nullptr || compileOnly);
static const bool headlessPipelineCache = std::getenv("APS5_HEADLESS_PIPELINE_CACHE") != nullptr;
state->pipelineCache = std::make_unique<Graphics::PipelineCache>(graphicsContext(), state->properties, window != nullptr || compileOnly || headlessPipelineCache);
state->detiler = std::make_unique<Graphics::TextureDetiler>(graphicsContext());
state->textureCache = std::make_unique<Graphics::TextureCache>(graphicsContext());
state->colorTransfer = std::make_unique<Graphics::GpuColorTransfer>(graphicsContext());
Expand Down
91 changes: 77 additions & 14 deletions core/libs/prx/libSceAgcDriver/tests/FrameReplay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ bool equal(const std::byte* bytes, const std::vector<std::byte>& expected) {
return std::memcmp(bytes, expected.data(), expected.size()) == 0;
}

void testCaptureAndReplay(const std::filesystem::path& tool) {
void testCaptureAndReplay(const std::filesystem::path& tool, bool pipelines) {
const char* directory = std::getenv("APS5_FRAME_CAPTURE");
check(directory != nullptr && *directory != '\0', "APS5_FRAME_CAPTURE is not set");
const std::filesystem::path capture(directory);
Expand Down Expand Up @@ -186,17 +186,40 @@ void testCaptureAndReplay(const std::filesystem::path& tool) {
check(recorded(late.base), "the block committed during the capture is not in its ranges");
check(reader.Header().find("\"late_ranges\":1") != std::string::npos, "the capture header does not count one range committed during the capture: " + reader.Header());

const auto firstDump = capture / "first.bin";
const auto secondDump = capture / "second.bin";
auto command = "\"" + tool.string() + "\" \"" + capture.string() + "\" --strict --dump " + std::to_string(at(layout.firstTarget)) + ":" + std::to_string(DataBytes) + ":\"" + firstDump.string() +
"\" --dump " + std::to_string(at(late.firstTarget)) + ":" + std::to_string(DataBytes) + ":\"" + secondDump.string() + "\"";
const auto firstDump = capture / "first dump.bin";
const auto secondDump = capture / "second dump.bin";
const auto replay = [&](const std::string& options, const std::filesystem::path& output) {
std::filesystem::remove(firstDump);
std::filesystem::remove(secondDump);
auto command = "\"" + tool.string() + "\" \"" + capture.string() + "\" --strict " + options + " --dump " + std::to_string(at(layout.firstTarget)) + ":" + std::to_string(DataBytes) + ":\"" +
firstDump.string() + "\" --dump " + std::to_string(at(late.firstTarget)) + ":" + std::to_string(DataBytes) + ":\"" + secondDump.string() + "\" > \"" + output.string() + "\" 2> \"" + output.string() + ".log\"";
#ifdef _WIN32
command = "\"" + command + "\"";
command = "\"" + command + "\"";
#endif
const auto code = std::system(command.c_str());
check(code == 0, "agc_frame_replay exited with " + std::to_string(code));
check(readFile(firstDump) == first, "the replay did not reproduce the first copy");
check(readFile(secondDump) == second, "the replay did not reproduce the second copy");
const auto code = std::system(command.c_str());
check(code == 0, "agc_frame_replay " + options + " exited with " + std::to_string(code));
check(readFile(firstDump) == first, "the replay " + options + " did not reproduce the first copy");
check(readFile(secondDump) == second, "the replay " + options + " did not reproduce the second copy");
const auto json = readFile(output);
return std::string(reinterpret_cast<const char*>(json.data()), json.size());
};
replay("", capture / "replay output.json");
if (pipelines) {
check(std::getenv("APS5_HEADLESS_PIPELINE_CACHE") != nullptr, "the pipeline cache switch was not inherited");
const auto warm = capture / "warm output.json";
replay("", warm);
const auto warmLog = readFile(warm.string() + ".log");
check(std::string(reinterpret_cast<const char*>(warmLog.data()), warmLog.size()).find("[pipeline-cache] loaded ") != std::string::npos, "the warm replay did not load the seeded pipeline cache");
const auto cold = capture / "cold output.json";
replay("--cold-pipelines", cold);
const auto coldLog = readFile(cold.string() + ".log");
check(std::string(reinterpret_cast<const char*>(coldLog.data()), coldLog.size()).find("[pipeline-cache] loaded ") == std::string::npos, "--cold-pipelines loaded a cache with APS5_HEADLESS_PIPELINE_CACHE inherited");
return;
}
const auto repeated = replay("--repeat 3", capture / "repeat output.json");
std::size_t passes = 0;
for (auto found = repeated.find("\"frames\":2,\"p50\""); found != std::string::npos; found = repeated.find("\"frames\":2,\"p50\"", found + 1)) ++passes;
check(passes == 3, "the repeated replay did not replay both frames in each of three passes: " + repeated);
}

void testConcurrentCopies(const std::filesystem::path& tool) {
Expand Down Expand Up @@ -261,16 +284,56 @@ void testConcurrentCopies(const std::filesystem::path& tool) {
}
}

void testReservationError(const std::filesystem::path& tool) {
const char* directory = std::getenv("APS5_FRAME_CAPTURE");
check(directory != nullptr && *directory != '\0', "APS5_FRAME_CAPTURE is not set");
const std::filesystem::path capture(directory);
std::uintptr_t base = 0;
std::size_t size = 0;
GuestArena::GuestArenaRange_nid_postfix(&base, &size);
AgcDriver::FrameCaptureWriter writer(capture);
AgcDriver::FrameCall output;
output.kind = FrameCallKind::RegisterVideoOutput;
output.values = {Handle};
writer.WriteCall(output);
AgcDriver::FrameCall reservation;
reservation.kind = FrameCallKind::ReserveEopFlip;
reservation.values = {Handle, static_cast<std::uint64_t>(-1), 1, 0, AgcDriver::EopFlipTokenBase};
writer.WriteCall(reservation);
AgcDriver::FrameCall start;
start.kind = FrameCallKind::FrameStart;
writer.WriteCall(start);
output.kind = FrameCallKind::UnregisterVideoOutput;
writer.WriteCall(output);
output.kind = FrameCallKind::RegisterVideoOutput;
writer.WriteCall(output);
reservation.values[3] = 1;
writer.WriteCall(reservation);
writer.Finish("{\"arena_base\":" + std::to_string(base) + "}");
const auto result = capture / "reservation error.json";
const auto log = capture / "reservation error.log";
auto command = "\"" + tool.string() + "\" \"" + capture.string() + "\" --repeat 2 --strict > \"" + result.string() + "\" 2> \"" + log.string() + "\"";
#ifdef _WIN32
command = "\"" + command + "\"";
#endif
check(std::system(command.c_str()) != 0, "the replay accepted an incompatible reservation on the second pass");
const auto errors = readFile(log);
check(std::string(reinterpret_cast<const char*>(errors.data()), errors.size()).find("EOP flip token is reserved for a different flip") != std::string::npos, "the repeated replay did not report the incompatible reservation");
const auto report = readFile(result);
check(std::string(reinterpret_cast<const char*>(report.data()), report.size()).find("\"p50\"") == std::string::npos, "a failed repeated reservation produced a successful replay report");
}

}

int main(int argc, char** argv) {
if (argc != 2 && !(argc == 3 && std::string(argv[2]) == "concurrent")) {
std::fprintf(stderr, "usage: agc_driver_frame_replay_tests <agc_frame_replay> [concurrent]\n");
if (argc != 2 && !(argc == 3 && (std::string(argv[2]) == "concurrent" || std::string(argv[2]) == "pipelines" || std::string(argv[2]) == "reservation_error"))) {
std::fprintf(stderr, "usage: agc_driver_frame_replay_tests <agc_frame_replay> [concurrent|pipelines|reservation_error]\n");
return 2;
}
try {
if (argc == 3) testConcurrentCopies(argv[1]);
else testCaptureAndReplay(argv[1]);
if (argc == 3 && std::string(argv[2]) == "concurrent") testConcurrentCopies(argv[1]);
else if (argc == 3 && std::string(argv[2]) == "reservation_error") testReservationError(argv[1]);
else testCaptureAndReplay(argv[1], argc == 3);
} catch (const std::exception& error) {
std::fprintf(stderr, "FAILED: %s\n", error.what());
return 1;
Expand Down
Loading
Loading