fix(agc-driver): queue frame dumps at the dump scale
A frame presented from guest memory with APS5_DUMP_FRAMES is read back at the size of the display buffer, and that whole copy went into the dump writer's queue with the scale beside it (31.6 MiB for a 3840x2160 buffer). The queue holds 64 MiB, so the third frame the writer had not finished ended the run with "frame dump queue exceeded 64 MiB". A frame blitted from the resident image is already reduced on the GPU and queued at the reduced size. The readback is now reduced by the dump scale when the slot retires and queued at that size, the same pixels the writer kept before. With APS5_DUMP_SCALE=1 nothing is reduced and the limit is reached as before. video_out_frame_dump_queue presents four 3840x2160 frames from guest memory while the writer is held on the first file and compares every dump with the decoded display buffer.
This commit is contained in:
parent
b42ef24e07
commit
abc1cfa57d
|
|
@ -591,13 +591,19 @@ struct VulkanDevice::State {
|
|||
|
||||
void WriteFrameDump(const PresentSlot& slot) {
|
||||
require(slot.dumpBuffer != nullptr, "frame dump was not recorded");
|
||||
std::vector<std::byte> full(slot.dumpBuffer->Bytes().begin(), slot.dumpBuffer->Bytes().end());
|
||||
const auto full = slot.dumpBuffer->Bytes();
|
||||
const auto index = slot.dumpIndex;
|
||||
const auto width = slot.dumpWidth;
|
||||
const auto height = slot.dumpHeight;
|
||||
const auto scale = slot.dumpScale;
|
||||
require(scale != 0 && full.size() == static_cast<std::size_t>(slot.dumpWidth) * slot.dumpHeight * 4, "invalid frame dump pixels");
|
||||
const auto width = (slot.dumpWidth + scale - 1) / scale;
|
||||
const auto height = (slot.dumpHeight + scale - 1) / scale;
|
||||
std::vector<std::byte> pixels(static_cast<std::size_t>(width) * height * 4);
|
||||
for (std::uint32_t y = 0; y < height; ++y) {
|
||||
for (std::uint32_t x = 0; x < width; ++x) std::memcpy(pixels.data() + (static_cast<std::size_t>(y) * width + x) * 4, full.data() + (static_cast<std::size_t>(y) * scale * slot.dumpWidth + static_cast<std::size_t>(x) * scale) * 4, 4);
|
||||
}
|
||||
static const auto start = std::chrono::steady_clock::now();
|
||||
std::fprintf(stderr, "[gpu] frame_%03d.bmp: %ux%u display read back on the GPU at %.1f s\n", index, width, height, std::chrono::duration<double>(std::chrono::steady_clock::now() - start).count());
|
||||
dumpWriter.Enqueue(index, width, height, slot.dumpScale, std::move(full));
|
||||
std::fprintf(stderr, "[gpu] frame_%03d.bmp: %ux%u display read back on the GPU at %.1f s\n", index, slot.dumpWidth, slot.dumpHeight, std::chrono::duration<double>(std::chrono::steady_clock::now() - start).count());
|
||||
dumpWriter.Enqueue(index, width, height, 1, std::move(pixels));
|
||||
}
|
||||
|
||||
// Blocks on the fence, or polls it every 50 us with APS5_PRESENT_POLL_FENCE=1 (the difference
|
||||
|
|
|
|||
|
|
@ -51,6 +51,16 @@ if(BUILD_TESTING)
|
|||
endforeach()
|
||||
endif()
|
||||
|
||||
if(BUILD_TESTING AND NOT WIN32)
|
||||
add_test_executable(video_out_frame_dump_queue_tests tests/FrameDumpQueue.cpp)
|
||||
target_include_directories(video_out_frame_dump_queue_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_SDL2_INCLUDE_DIR} ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR})
|
||||
target_link_libraries(video_out_frame_dump_queue_tests PRIVATE libSceVideoOut libSceAgcDriver libkernel libc)
|
||||
set_target_properties(video_out_frame_dump_queue_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
add_test(NAME video_out_frame_dump_queue COMMAND video_out_frame_dump_queue_tests)
|
||||
set_tests_properties(video_out_frame_dump_queue PROPERTIES TIMEOUT 30 SKIP_RETURN_CODE 77 RESOURCE_LOCK videoout_app0
|
||||
ENVIRONMENT "SDL_VIDEODRIVER=${ANYPS5_VIDEO_OUT_TEST_DRIVER};APS5_DUMP_FRAMES=4;APS5_DUMP_SCALE=4")
|
||||
endif()
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_test_executable(pad_sdl_tests tests/PadSdl.cpp)
|
||||
target_link_libraries(pad_sdl_tests PRIVATE SDL2-static)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,160 @@
|
|||
#include "prx/libSceVideoOut/include/VideoOutDriver.hpp"
|
||||
#include "prx/libSceVideoOut/include/Buffer.hpp"
|
||||
#include "prx/libSceVideoOut/include/Output.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/DisplayBuffer.hpp"
|
||||
#include "prx/libc/include/Shutdown.hpp"
|
||||
#include "tests/VideoOutTestEnvironment.hpp"
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
#include <mutex>
|
||||
#include <span>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#undef main
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::uint32_t Width = 3840;
|
||||
constexpr std::uint32_t Height = 2160;
|
||||
constexpr std::uint32_t Scale = 4;
|
||||
constexpr std::uint32_t DumpWidth = Width / Scale;
|
||||
constexpr std::uint32_t DumpHeight = Height / Scale;
|
||||
constexpr std::size_t DumpBytes = 54 + static_cast<std::size_t>(DumpWidth) * DumpHeight * 4;
|
||||
constexpr int Frames = 4;
|
||||
|
||||
void check(bool condition, const char* reason) {
|
||||
if (!condition) throw std::runtime_error(reason);
|
||||
}
|
||||
|
||||
std::string frameName(int index) {
|
||||
char name[32];
|
||||
std::snprintf(name, sizeof(name), "frame_%03d.bmp", index);
|
||||
return name;
|
||||
}
|
||||
|
||||
class HeldDump final {
|
||||
int descriptor;
|
||||
|
||||
public:
|
||||
explicit HeldDump(int index) {
|
||||
const auto name = frameName(index);
|
||||
check(mkfifo(name.c_str(), 0600) == 0, "a frame dump pipe could not be created");
|
||||
descriptor = open(name.c_str(), O_RDWR);
|
||||
check(descriptor >= 0, "a frame dump pipe could not be opened");
|
||||
}
|
||||
|
||||
~HeldDump() { close(descriptor); }
|
||||
|
||||
HeldDump(const HeldDump&) = delete;
|
||||
HeldDump& operator=(const HeldDump&) = delete;
|
||||
|
||||
std::vector<char> Release() const {
|
||||
std::vector<char> dump(DumpBytes);
|
||||
for (std::size_t done = 0; done < dump.size();) {
|
||||
pollfd waiting{descriptor, POLLIN, 0};
|
||||
check(poll(&waiting, 1, 15000) == 1, "a queued frame dump was not written");
|
||||
const auto count = read(descriptor, dump.data() + done, dump.size() - done);
|
||||
check(count > 0, "a frame dump pipe could not be read");
|
||||
done += static_cast<std::size_t>(count);
|
||||
}
|
||||
return dump;
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<char> readDump(int index) {
|
||||
std::ifstream file(frameName(index), std::ios::binary);
|
||||
check(file.is_open(), "a frame dump is missing");
|
||||
return {std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()};
|
||||
}
|
||||
|
||||
void checkDump(std::span<const char> dump, std::span<const std::byte> decoded) {
|
||||
check(dump.size() == DumpBytes, "a frame dump has the wrong size");
|
||||
std::uint32_t extent[2];
|
||||
std::memcpy(extent, dump.data() + 18, sizeof(extent));
|
||||
check(extent[0] == DumpWidth && extent[1] == static_cast<std::uint32_t>(-static_cast<std::int32_t>(DumpHeight)), "a frame dump has the wrong extent");
|
||||
for (std::uint32_t y = 0; y < DumpHeight; ++y) {
|
||||
for (std::uint32_t x = 0; x < DumpWidth; ++x) {
|
||||
const auto dumped = dump.data() + 54 + (static_cast<std::size_t>(y) * DumpWidth + x) * 4;
|
||||
const auto source = decoded.data() + (static_cast<std::size_t>(y) * Scale * Width + static_cast<std::size_t>(x) * Scale) * 4;
|
||||
check(std::memcmp(dumped, source, 4) == 0, "a frame dump pixel differs from the display buffer");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void testDumpQueue() {
|
||||
const int handle = sceVideoOutOpen(255, 0, 0, nullptr);
|
||||
auto cfg = VideoOutDriver::Get().GetConfig(handle);
|
||||
std::vector<std::byte> allocation(static_cast<std::size_t>((Width + 127) / 128) * ((Height + 127) / 128) * 65536 + 65535);
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(allocation.data());
|
||||
const std::span<std::byte> storage{allocation.data() + ((65536u - (address & 65535u)) & 65535u), allocation.size() - 65535u};
|
||||
const AgcDriver::DisplayBuffer display{reinterpret_cast<std::uint64_t>(storage.data()), 0x8000000000000000ull, Width, Height};
|
||||
check(AgcDriver::DisplayBufferSize(display) == storage.size(), "incorrect padded display footprint");
|
||||
for (std::uint32_t y = 0; y < Height; ++y) {
|
||||
for (std::uint32_t x = 0; x < Width; ++x) {
|
||||
const auto offset = AgcDriver::DisplayBufferOffset(display, x, y);
|
||||
storage[offset] = static_cast<std::byte>(x & 255u);
|
||||
storage[offset + 1] = static_cast<std::byte>(y & 255u);
|
||||
storage[offset + 2] = static_cast<std::byte>((x >> 8u) | ((y >> 8u) << 4u));
|
||||
storage[offset + 3] = std::byte{255};
|
||||
}
|
||||
}
|
||||
const auto decoded = AgcDriver::ReadDisplayBuffer(display);
|
||||
VideoOutBuffers buffer{storage.data(), nullptr, {nullptr, nullptr}};
|
||||
VideoOutBufferAttribute2 attribute{};
|
||||
sceVideoOutSetBufferAttribute2(&attribute, display.pixelFormat, 0, Width, Height, 0, 0, 0);
|
||||
check(sceVideoOutRegisterBuffers2(handle, 0, 0, &buffer, 1, &attribute, 0, nullptr) == 0, "buffer registration failed");
|
||||
const HeldDump first(0);
|
||||
const HeldDump last(Frames - 1);
|
||||
std::exception_ptr failure;
|
||||
try {
|
||||
for (int frame = 0; frame < Frames; ++frame) {
|
||||
check(sceVideoOutSubmitFlip(handle, 0, 1, frame) == 0, "the flip was not accepted");
|
||||
std::unique_lock lock(cfg->mutex);
|
||||
check(cfg->vblankCond.wait_for(lock, std::chrono::seconds(15), [&] { return (cfg->failure && cfg->flipStatus.flipPendingNum == 0) || cfg->flipStatus.count == static_cast<std::uint64_t>(frame) + 1; }), "the flip did not complete");
|
||||
if (cfg->failure) std::rethrow_exception(cfg->failure);
|
||||
}
|
||||
} catch (...) {
|
||||
failure = std::current_exception();
|
||||
}
|
||||
const auto firstDump = first.Release();
|
||||
if (failure) std::rethrow_exception(failure);
|
||||
checkDump(firstDump, decoded);
|
||||
const auto lastDump = last.Release();
|
||||
checkDump(lastDump, decoded);
|
||||
for (int frame = 1; frame < Frames - 1; ++frame) checkDump(readDump(frame), decoded);
|
||||
sceVideoOutUnregisterBuffers(handle, 0);
|
||||
sceVideoOutClose(handle);
|
||||
LibcRunShutdown_nid_postfix();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
VideoOutTestEnvironment environment;
|
||||
try {
|
||||
testDumpQueue();
|
||||
std::puts("VideoOut frame dump queue test passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::fprintf(stderr, "%s\n", error.what());
|
||||
try { LibcRunShutdown_nid_postfix(); }
|
||||
catch (const std::exception& shutdown) { std::fprintf(stderr, "shutdown: %s\n", shutdown.what()); }
|
||||
if (std::string(error.what()).find("Vulkan support") != std::string::npos && !std::getenv("ANYPS5_REQUIRE_DISPLAY")) {
|
||||
std::printf("skipped, no display or Vulkan device: %s\n", error.what());
|
||||
return 77;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue