Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class Driver {
void ReportFailure(std::exception_ptr error);
void Submit(const Packet* packet, std::uint32_t queue);
void SuspendPoint();
std::uint32_t ReserveEopFlip(const FlipInfo& info);
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 @@ -324,6 +325,12 @@ 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;
std::shared_ptr<IFlipRequest> request;
};
std::map<std::uint32_t, EopFlip> eopFlips;
std::uint32_t nextEopToken = EopFlipTokenBase;
DevicePointer device;

std::mutex labelStoresMutex;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ namespace AgcDriver {

class FrameTiming;

inline constexpr std::uint32_t EopFlipTokenBase = 0x04000000u;
inline constexpr std::uint32_t EopFlipTokenLimit = 0x07ffffffu;

inline constexpr std::uint32_t FlipPacketHeader = 0xc004105cu;
inline constexpr std::uint32_t FlipPacketWords = 6;
// AnyPS5's internal encoding for the driver-generated rendering wait.
Expand Down Expand Up @@ -40,6 +43,7 @@ class IVideoOutput {
public:
virtual ~IVideoOutput() = default;
virtual std::shared_ptr<IFlipRequest> Reserve(const FlipInfo& info) = 0;
virtual std::shared_ptr<IFlipRequest> TryReserve(const FlipInfo& info) { return Reserve(info); }
virtual void WaitForFlipRoom() {}
virtual std::shared_ptr<IRenderingWait> CaptureRenderingWait(std::uint32_t index) {
throw std::runtime_error("VideoOut: rendering waits are unsupported by this output");
Expand All @@ -49,6 +53,8 @@ class IVideoOutput {

}

extern "C" std::uint32_t AgcDriverReserveEopFlip_nid_postfix(const AgcDriver::FlipInfo& info);

extern "C" void AgcDriverRegisterVideoOutput_nid_postfix(std::uint32_t handle, const std::shared_ptr<AgcDriver::IVideoOutput>& output);
extern "C" void AgcDriverUnregisterVideoOutput_nid_postfix(std::uint32_t handle, const std::shared_ptr<AgcDriver::IVideoOutput>& output);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ extern "C" void AgcDriverSuspendPoint_nid_postfix() try {
LibcAwaitExit_nid_postfix();
}

extern "C" std::uint32_t AgcDriverReserveEopFlip_nid_postfix(const AgcDriver::FlipInfo& info) {
return AgcDriver::DriverDetail::Driver::Get().ReserveEopFlip(info);
}

extern "C" void AgcDriverRegisterVideoOutput_nid_postfix(std::uint32_t handle, const std::shared_ptr<AgcDriver::IVideoOutput>& output) {
AgcDriver::RegisterVideoOutput(handle, output);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ void Driver::stop() {
{
std::lock_guard lock(mutex);
stopping = true;
eopFlips.clear();
for (auto& [queue, worker] : workers) {
worker.pending.clear();
worker.unfinishedWrites.clear();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,15 @@ void Driver::execute(const Submission& submission) {
recent.Record(cursor);
if (header == RenderingWaitPacketHeader) {
timed(&WorkerProfile::waitMs, [&] { submission.renderingWaits.at(cursor)->Wait(); });
} else if (header == FlipPacketHeader) {
} else if (submission.flips.contains(cursor)) {
if (opcode == 0x49) {
if (!wroteOnGpu) {
Pm4::Execute(packet, queue);
if (const auto label = Pm4::DecodeLabelWrite(packet)) noteLabelStore(label->address, label->Bytes(), ++eventSerial, submission.queue);
}
if (endOfPipeInterrupt && !interruptDeferred) AgcDriverDeliverEopInterrupt(submission.queue);
}
if (deviceUse.owns_lock()) deviceUse.unlock();
CheckFailure();
std::uint64_t batchesAtFlip = 0, unsignaledAtFlip = 0;
bool flipInterruptDeferred = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,27 @@
#include "prx/libSceAgcDriver/Execution/include/Driver/Queues/WorkerAffinity.hpp"
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp"
#include <algorithm>
#include "prx/libSceAgcDriver/Execution/include/TracyZone.hpp"
#include <cstdlib>

namespace AgcDriver::DriverDetail {

std::uint32_t Driver::ReserveEopFlip(const FlipInfo& info) {
std::lock_guard lock(mutex);
rethrowFailure();
checkStopping();
require(nextEopToken <= EopFlipTokenLimit, "EOP flip token space exhausted");
const auto output = outputs.find(info.handle);
require(output != outputs.end(), "EOP flip references an unregistered video output");
auto request = output->second->TryReserve(info);
if (!request) return 0;
const auto token = nextEopToken;
eopFlips.emplace(token, EopFlip{info.handle, std::move(request)});
++nextEopToken;
return token;
}

void Driver::RegisterVideoOutput(std::uint32_t handle, const std::shared_ptr<IVideoOutput>& output) {
require(output != nullptr, "null video output");
std::lock_guard lock(mutex);
Expand All @@ -21,6 +37,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; });
outputs.erase(it);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ void writeCaptured(Driver& driver, SubmissionCapture& capture, const CapturedSub
}
}

bool EopRelease(std::span<const std::uint32_t> words) {
return words.size() >= 8 && (words[0] >> 30u) == 3u && Pm4::PacketWords(words[0]) == 8u && ((words[0] >> 8u) & 0xffu) == 0x49u && ((words[2] >> 24u) & 7u) == 2u;
}

}

void Driver::copyCommands(Submission& submission, const std::uint32_t* guest, std::size_t words) {
Expand Down Expand Up @@ -166,10 +170,19 @@ void Driver::noteAwaitingTitle(std::uint32_t queue, std::uint64_t awaited) {

void Driver::holdFlipBehindWorker(const Submission& submission) {
if (submission.queue != 0 || onWorkerThread()) return;
bool candidate = false;
for (std::size_t cursor = 0; cursor < submission.commands.size() && !candidate; cursor += Pm4::PacketWords(submission.commands[cursor])) {
const auto words = std::span(submission.commands).subspan(cursor);
candidate = words[0] == FlipPacketHeader || EopRelease(words);
}
if (!candidate) return;
std::unique_lock lock(mutex);
bool flips = false;
for (std::size_t cursor = 0; cursor < submission.commands.size() && !flips; cursor += Pm4::PacketWords(submission.commands[cursor])) flips = submission.commands[cursor] == FlipPacketHeader;
for (std::size_t cursor = 0; cursor < submission.commands.size() && !flips; cursor += Pm4::PacketWords(submission.commands[cursor])) {
const auto words = std::span(submission.commands).subspan(cursor);
flips = words[0] == FlipPacketHeader || (EopRelease(words) && eopFlips.contains(words[7]));
}
if (!flips) return;
std::unique_lock lock(mutex);
flipHolders.fetch_add(1, std::memory_order_acq_rel);
changed.wait(lock, [&] { return failure != nullptr || stopping.load(std::memory_order_acquire) || shutdownToken.stop_requested() || flipsAhead.load(std::memory_order_acquire) == 0 || queue0AwaitsTitle.load(std::memory_order_acquire); });
flipHolders.fetch_sub(1, std::memory_order_acq_rel);
Expand All @@ -182,6 +195,7 @@ void Driver::releaseFlipHold() {
}

void Driver::reserveOutputs(Submission& submission) {
std::set<std::uint32_t> eopTokens;
for (std::size_t cursor = 0; cursor < submission.commands.size();) {
const auto* words = submission.commands.data() + cursor;
if (words[0] == RenderingWaitPacketHeader) {
Expand All @@ -191,6 +205,15 @@ void Driver::reserveOutputs(Submission& submission) {
require(wait != nullptr, "video output returned a null rendering wait");
submission.renderingWaits.emplace(cursor, std::move(wait));
}
if (EopRelease(std::span(submission.commands).subspan(cursor))) {
const auto token = words[7];
const auto flip = eopFlips.find(token);
if (flip != eopFlips.end()) {
require(submission.queue == 0, "EOP flips on async queues are not implemented");
require(eopTokens.insert(token).second, "EOP flip token repeated in a submission");
submission.flips.emplace(cursor, flip->second.request);
}
}
noteHeldAtSubmit(submission, cursor);
if (words[0] == FlipPacketHeader) {
const auto output = outputs.find(words[1]);
Expand All @@ -202,6 +225,7 @@ void Driver::reserveOutputs(Submission& submission) {
}
cursor += Pm4::PacketWords(words[0]);
}
for (const auto token : eopTokens) eopFlips.erase(token);
}

void Driver::executeRewindTail(const Submission& stalled) {
Expand Down
11 changes: 11 additions & 0 deletions core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,11 @@ void Validate(std::span<const std::uint32_t> packet, std::uint32_t queue) {
require(packet.size() >= 5, "WRITE_DATA has no data");
require((packet[1] & ~(0x40110f00u | WriteDataCachePolicy)) == 0, "WRITE_DATA engine or reserved fields are not implemented");
const auto destination = (packet[1] >> 8u) & 0xfu;
if (destination == 0) {
const auto count = (packet[1] & 0x10000u) != 0 ? 1u : packet.size() - 4;
require(packet[3] == 0 && packet[2] >= 0xc340u && packet[2] <= 0xc347u && count <= 0xc348u - packet[2], "WRITE_DATA register destination outside the thread-trace user-data registers is not implemented");
break;
}
require(destination == 1 || destination == 2 || destination == 3 || destination == 5, "WRITE_DATA register destination is not implemented");
require((packet[2] & 3u) == 0, "misaligned WRITE_DATA destination");
break;
Expand Down Expand Up @@ -578,6 +583,7 @@ std::optional<LabelWrite> DecodeLabelWrite(std::span<const std::uint32_t> packet
return label;
}
if (opcode == 0x37 && packet.size() > 4) {
if (((packet[1] >> 8u) & 0xfu) == 0) return std::nullopt;
const auto destination = address(packet[2], packet[3]);
if (destination == 0) return std::nullopt;
// WR_ONE_ADDR stores every value at the same address: the last one remains.
Expand Down Expand Up @@ -1044,6 +1050,11 @@ void Execute(std::span<const std::uint32_t> packet, QueueState& queue) {
return;
}
case 0x37: {
if (((packet[1] >> 8u) & 0xfu) == 0) {
const bool repeat = (packet[1] & 0x10000u) != 0;
for (std::size_t i = 4; i < packet.size(); ++i) writeRegister(queue, 0x79, packet[2] - 0xc000u + (repeat ? 0u : static_cast<std::uint32_t>(i - 4)), packet[i]);
return;
}
const auto destination = address(packet[2], packet[3]);
if ((packet[1] & 0x10000u) != 0) {
for (const auto& value : packet.subspan(4)) GuestMemory::Write(destination, std::as_bytes(std::span(&value, 1)), 4);
Expand Down
16 changes: 16 additions & 0 deletions core/libs/prx/libSceAgcDriver/tests/Pm4.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,21 @@ void testCopies() {
#endif
}

void testTraceRegisterWrites() {
AgcDriver::QueueState state;
const auto repeated = makePacket(0x37, {0x06010000u, 0xc343u, 0u, 0x1234u, 0x5678u});
check(!AgcDriver::Pm4::DecodeLabelWrite(repeated), "trace register payload was decoded as a guest memory label");
execute(state, repeated);
check(state.userConfig.at(0x343u) == 0x5678u, "one-address trace write did not retain the last register value");
execute(state, makePacket(0x37, {0x06000000u, 0xc340u, 0u, 1u, 2u, 3u}));
check(state.userConfig.at(0x340u) == 1u && state.userConfig.at(0x341u) == 2u && state.userConfig.at(0x342u) == 3u, "sequential trace write did not advance the register address");
execute(state, makePacket(0x37, {0x06010000u, 0xc347u, 0u, 4u, 5u}));
check(state.userConfig.at(0x347u) == 5u, "one-address trace write crossed the register range");
for (const auto& invalid : {makePacket(0x37, {0x06000000u, 0xc347u, 0u, 1u, 2u}), makePacket(0x37, {0x06010000u, 0xc348u, 0u, 1u}), makePacket(0x37, {0x06010000u, 0xc343u, 1u, 1u})}) {
expectFailure([&] { execute(state, invalid); }, "register destination outside");
}
}

void testMemoryCopyDecode() {
using AgcDriver::Pm4::DecodeMemoryCopy;
constexpr std::uint64_t source = 0x1120000000ull, destination = 0x403d7b400ull;
Expand Down Expand Up @@ -1241,6 +1256,7 @@ int main(int argc, char** argv) {
testIndirectDraw();
testIndexAttributesIndirect();
testMemory();
testTraceRegisterWrites();
testCopies();
testMemoryCopyDecode();
testMemorySynchronization();
Expand Down
8 changes: 8 additions & 0 deletions core/libs/prx/libSceVideoOut/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ if(BUILD_TESTING)
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endforeach()
foreach(flipMode IN ITEMS eop eop_main eop_aftergpu)
add_test(NAME video_out_flip_${flipMode} COMMAND video_out_flip_tests ${flipMode})
set_tests_properties(video_out_flip_${flipMode} PROPERTIES TIMEOUT 30 SKIP_RETURN_CODE 77 RESOURCE_LOCK videoout_app0 ENVIRONMENT "SDL_VIDEODRIVER=${ANYPS5_VIDEO_OUT_TEST_DRIVER}")
if(WIN32)
set_tests_properties(video_out_flip_${flipMode} PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endforeach()
endif()

if(BUILD_TESTING AND NOT WIN32)
Expand Down
1 change: 1 addition & 0 deletions core/libs/prx/libSceVideoOut/include/Buffer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ int APS5_VABI sceVideoOutRegisterBuffers2(int handle, int set_index, int buffer_
void APS5_VABI sceVideoOutSetBufferAttribute2(VideoOutBufferAttribute2* attribute, uint64_t pixel_format, uint32_t tiling_mode, uint32_t width, uint32_t height, uint64_t option, uint32_t dcc_control, uint64_t dcc_cb_register_clear_color);
void APS5_VABI sceVideoOutAddBufferAttributeOption(VideoOutBufferAttribute2* attribute, uint64_t option);
int APS5_VABI sceVideoOutSubmitChangeBufferAttribute2(int handle, int set_index, const VideoOutBufferAttribute2* attribute, void* option);
int APS5_VABI sceVideoOutSubmitEopFlip(int handle, int index, int flip_mode, int64_t flip_arg, std::uint32_t* token);
int APS5_VABI sceVideoOutSubmitFlip(int handle, int index, int flip_mode, int64_t flip_arg);
int APS5_VABI sceVideoOutUnregisterBuffers(int handle, int set_index);

Expand Down
1 change: 1 addition & 0 deletions core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ class VideoOutDriver {

// 0, or VIDEO_OUT_ERROR_FLIP_QUEUE_FULL when the title has VIDEO_OUT_FLIP_QUEUE_CAPACITY flips pending.
int SubmitFlip(int handle, int index, int flipMode, int64_t flipArg);
int SubmitEopFlip(int handle, int index, int flipMode, int64_t flipArg, std::uint32_t* token);

private:
bool close(int handle);
Expand Down
7 changes: 7 additions & 0 deletions core/libs/prx/libSceVideoOut/src/Buffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,13 @@ int APS5_VABI sceVideoOutUnregisterBuffers(int handle, int setIndex) try {
LibcAwaitExit_nid_postfix();
}

int APS5_VABI sceVideoOutSubmitEopFlip(int handle, int index, int flipMode, int64_t flipArg, std::uint32_t* token) try {
if (token == nullptr) return VIDEO_OUT_ERROR_INVALID_ADDRESS;
return VideoOutDriver::Get().SubmitEopFlip(handle, index, flipMode, flipArg, token);
} catch (const ProcessShutdown&) {
LibcAwaitExit_nid_postfix();
}

int APS5_VABI sceVideoOutSubmitFlip(int handle, int index, int flipMode, int64_t flipArg) try {
auto cfg = VideoOutDriver::Get().GetConfig(handle);
if (cfg == nullptr) {
Expand Down
30 changes: 28 additions & 2 deletions core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include <bit>
#include <chrono>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <exception>
#include <thread>
Expand Down Expand Up @@ -132,7 +133,10 @@ class VideoOutput final : public AgcDriver::IVideoOutput {
require(room, "flip queue stayed full for 60 s: the presenter is not completing flips");
}

std::shared_ptr<AgcDriver::IFlipRequest> Reserve(const AgcDriver::FlipInfo& info) override {
std::shared_ptr<AgcDriver::IFlipRequest> Reserve(const AgcDriver::FlipInfo& info) override { return Reserve(info, false); }
std::shared_ptr<AgcDriver::IFlipRequest> TryReserve(const AgcDriver::FlipInfo& info) override { return Reserve(info, true); }

std::shared_ptr<AgcDriver::IFlipRequest> Reserve(const AgcDriver::FlipInfo& info, bool limited) {
// All flip modes are presented at the next vsync.
require(info.mode >= VIDEO_OUT_FLIP_MODE_VSYNC && info.mode <= 6, "unsupported flip mode");
require(info.index >= VIDEO_OUT_BUFFER_INDEX_BLACK && info.index < VIDEO_OUT_BUFFER_NUM_MAX, "invalid flip index");
Expand All @@ -146,11 +150,15 @@ class VideoOutput final : public AgcDriver::IVideoOutput {
std::lock_guard queueLock(queue->mutex);
if (queue->failure) std::rethrow_exception(queue->failure);
if (queue->stopping || cfg->shutdownToken.stop_requested()) throw ProcessShutdown{};
if (limited && queue->reservations.load() >= VIDEO_OUT_FLIP_QUEUE_CAPACITY) return {};
std::lock_guard lock(cfg->mutex);
checkConfig(*cfg);
request->generation = cfg->generation;
request->flipRate = cfg->flipRate;
if (info.index >= 0) request->reuseTicket = cfg->bufferReuse[info.index].Reserve();
if (info.index >= 0) {
if (limited) require(cfg->buffers[info.index].Occupied(), "EOP flip buffer is not registered");
request->reuseTicket = cfg->bufferReuse[info.index].Reserve();
}
++queue->reservations;
++cfg->flipStatus.flipPendingNum;
if (info.index >= 0) ++cfg->bufferPending[info.index];
Expand Down Expand Up @@ -437,6 +445,24 @@ int VideoOutDriver::SubmitFlip(int handle, int index, int flipMode, int64_t flip
return 0;
}

int VideoOutDriver::SubmitEopFlip(int handle, int index, int flipMode, int64_t flipArg, std::uint32_t* token) {
std::lock_guard lock(mutex);
if (handle <= 0 || handle >= VIDEO_OUT_NUM_MAX || !outputs[handle]) return VIDEO_OUT_ERROR_INVALID_HANDLE;
if (flipMode < VIDEO_OUT_FLIP_MODE_VSYNC || flipMode > 6) return VIDEO_OUT_ERROR_INVALID_VALUE;
if (index < VIDEO_OUT_BUFFER_INDEX_BLACK || index >= VIDEO_OUT_BUFFER_NUM_MAX) return VIDEO_OUT_ERROR_INVALID_INDEX;
const auto cfg = contexts[handle];
{
std::lock_guard cfgLock(cfg->mutex);
checkConfig(*cfg);
if (index >= 0 && !cfg->buffers[index].Occupied()) return VIDEO_OUT_ERROR_INVALID_INDEX;
}
const AgcDriver::FlipInfo info{static_cast<std::uint32_t>(handle), index, static_cast<std::uint32_t>(flipMode), flipArg};
const auto reserved = AgcDriverReserveEopFlip_nid_postfix(info);
if (reserved == 0) return VIDEO_OUT_ERROR_FLIP_QUEUE_FULL;
std::memcpy(token, &reserved, sizeof(reserved));
return 0;
}

void VideoOutDriver::triggerEvents(VideoOutConfig& cfg, int eventKind, void* triggerData) {
std::vector<EventRegistration>* events = nullptr;
if (eventKind == VIDEO_OUT_EVENT_FLIP) events = &cfg.flipEvents;
Expand Down
Loading
Loading