Files
AnyPS5/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp
T
Get-fix 901b92c794 feat(libSceVideoOut): register PS4 display buffers
Add the PS4 functions sceVideoOutSetBufferAttribute and
sceVideoOutRegisterBuffers on top of the existing registration. The PS4
call uses the first free attribute set and returns its index. It
accepts linear A8R8G8B8_SRGB and A8B8G8R8_SRGB buffers, which map to the
PS5 B8G8R8A8 and R8G8B8A8 formats. It throws for tiled buffers, whose
PS4 layout differs from the PS5 one. Linear PS4 buffers may be 4 KiB
aligned: the OpenOrbis graphics sample places its second 1920x1080
buffer 8,294,400 bytes after the first. PS5 buffers keep the 64 KiB
rule. A handle with PS4 buffers writes the PS4 flip status (64 bytes)
and vblank status (32 bytes). Writing the 128-byte PS5 flip status into
the sample's stack structure overwrote its saved frame pointer.
2026-10-14 20:52:00 +03:00

260 lines
9.3 KiB
C++

#ifndef CORE_LIBS_PRX_LIBSCEVIDEOOUT_INCLUDE_VIDEOOUTDRIVER_HPP
#define CORE_LIBS_PRX_LIBSCEVIDEOOUT_INCLUDE_VIDEOOUTDRIVER_HPP
#include <array>
#include <chrono>
#include <condition_variable>
#include <cstddef>
#include <cstdint>
#include <list>
#include <mutex>
#include <thread>
#include <vector>
#include <atomic>
#include <exception>
#include <future>
#include <memory>
#include <stdexcept>
#include "prx/libSceAgcDriver/Execution/include/VideoOutput.hpp"
#include "prx/libSceAgcDriver/Execution/include/DisplayBuffer.hpp"
#include "prx/libSceVideoOut/include/DisplayWindow.hpp"
#include "prx/libSceVideoOut/include/BufferReuseTracker.hpp"
#include "prx/libSceVideoOut/include/HostHdrProbe.hpp"
#include "prx/libc/include/Shutdown.hpp"
#include "prx/libkernel/Time/include/TimedWait.hpp"
#include "SDL.h"
#include "SceTypes.hpp"
static constexpr int VIDEO_OUT_ERROR_INVALID_VALUE = -2144796671;
static constexpr int VIDEO_OUT_ERROR_INVALID_ADDRESS = -2144796670;
static constexpr int VIDEO_OUT_ERROR_INVALID_HANDLE = -2144796661;
static constexpr int VIDEO_OUT_ERROR_INVALID_EVENT_QUEUE = -2144796660;
static constexpr int VIDEO_OUT_ERROR_INVALID_INDEX = -2144796662;
static constexpr int VIDEO_OUT_ERROR_INVALID_OPTION = -2144796646;
static constexpr int VIDEO_OUT_ERROR_INVALID_CATEGORY = -2144796643;
static constexpr int VIDEO_OUT_ERROR_SLOT_OCCUPIED = -2144796656;
static constexpr int VIDEO_OUT_ERROR_RESOURCE_BUSY = -2144796663;
static constexpr int VIDEO_OUT_ERROR_FLIP_QUEUE_FULL = -2144796654;
static constexpr int VIDEO_OUT_ERROR_UNSUPPORTED_OUTPUT_MODE = -2144796650;
static constexpr int VIDEO_OUT_ERROR_UNAVAILABLE_OUTPUT_MODE = -2144796647;
static constexpr int VIDEO_OUT_ERROR_INVALID_EVENT = -2144796659;
static constexpr int VIDEO_OUT_ERROR_UNKNOWN_OUTPUT_MODE = -2144796642;
static constexpr int VIDEO_OUT_BUS_TYPE_MAIN = 0;
static constexpr int VIDEO_OUT_BUS_TYPE_OVERLAY = 1;
static constexpr int VIDEO_OUT_BUS_TYPE_SUB = 2;
static constexpr uint32_t VIDEO_OUT_DEFAULT_WIDTH = 1920;
static constexpr uint32_t VIDEO_OUT_DEFAULT_HEIGHT = 1080;
static constexpr std::uint32_t VIDEO_OUT_OPEN_PARAM_FIRST_WORD = 16;
static constexpr std::int32_t VIDEO_OUT_SERVICE_THREAD_PRIORITY_HIGHEST = 256;
static constexpr std::int32_t VIDEO_OUT_SERVICE_THREAD_PRIORITY_LOWEST = 767;
static constexpr std::uint64_t VIDEO_OUT_SERVICE_THREAD_AFFINITY_ALL = 0x1FFF;
struct VideoOutOpenParam {
std::uint32_t firstWord;
std::uint32_t setPriority;
std::int32_t priority;
std::uint32_t setAffinity;
std::uint64_t affinity;
};
static_assert(offsetof(VideoOutOpenParam, affinity) == 16);
static constexpr int VIDEO_OUT_BUFFER_NUM_MAX = 16;
static constexpr int VIDEO_OUT_BUFFER_ATTRIBUTE_NUM_MAX = 4;
static constexpr int VIDEO_OUT_NUM_MAX = 4;
static constexpr std::size_t VIDEO_OUT_FLIP_QUEUE_CAPACITY = 16;
static constexpr int VIDEO_OUT_BUFFER_ATTRIBUTE_CATEGORY_UNCOMPRESSED = 0;
static constexpr int VIDEO_OUT_BUFFER_ATTRIBUTE_CATEGORY_COMPRESSED = 1;
static constexpr std::uint64_t VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_NONE = 0;
static constexpr std::uint64_t VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_STRICT_COLORIMETRY = 8;
static constexpr std::uint32_t VIDEO_OUT_DCC_CONTROL_BLOCK_LAYOUT = 0x10026c;
static constexpr int VIDEO_OUT_EVENT_FLIP = 0;
static constexpr int VIDEO_OUT_EVENT_VBLANK = 1;
static constexpr int VIDEO_OUT_EVENT_PRE_VBLANK_START = 2;
static constexpr int VIDEO_OUT_EVENT_SET_MODE = 8;
static constexpr int VIDEO_OUT_EVENT_VRR_STATUS = 16;
static constexpr int VIDEO_OUT_FLIP_MODE_VSYNC = 1;
static constexpr int VIDEO_OUT_FLIP_MODE_VSYNC_MULTI = 4;
static constexpr int VIDEO_OUT_BUFFER_INDEX_BLACK = -2;
static constexpr int VIDEO_OUT_BUFFER_INDEX_BLANK = -1;
static constexpr uint64_t VIDEO_OUT_OUTPUT_MODE_DEFAULT = 0x0000000000000001ULL;
static constexpr uint64_t VIDEO_OUT_OUTPUT_MODE_119_88HZ = 0x000000000000000FULL;
static constexpr std::uint32_t VIDEO_OUT_DYNAMIC_RANGE_SDR = 1;
static constexpr std::uint32_t VIDEO_OUT_DYNAMIC_RANGE_HDR = 2;
static constexpr std::uint64_t VIDEO_OUT_OUTPUT_STATUS_FLAG_HDR = 1;
static constexpr uint64_t VIDEO_OUT_REFRESH_RATE_59_94HZ = 3;
static constexpr uint64_t VIDEO_OUT_REFRESH_RATE_119_88HZ = 13;
struct VideoOutBuffer {
int groupIndex = -1;
uint64_t dataAddress = 0;
uint64_t metadataAddress = 0;
bool Occupied() const { return groupIndex >= 0; }
};
struct BufferAttributeGroup {
VideoOutBufferAttribute2 attribute{};
int category = VIDEO_OUT_BUFFER_ATTRIBUTE_CATEGORY_UNCOMPRESSED;
bool occupied = false;
bool ps4 = false;
};
AgcDriver::DisplayBuffer DescribeVideoOutBuffer(const VideoOutBuffer& buffer, const BufferAttributeGroup& group);
struct EventRegistration {
KernelEqueue eq = 0;
uint64_t generation = 0;
};
struct VideoOutConfig {
std::mutex mutex;
std::condition_variable_any vblankCond;
std::vector<EventRegistration> flipEvents;
std::vector<EventRegistration> vblankEvents;
std::vector<EventRegistration> preVblankEvents;
std::vector<EventRegistration> outputModeEvents;
std::vector<EventRegistration> vrrStatusEvents;
uint32_t width = VIDEO_OUT_DEFAULT_WIDTH;
uint32_t height = VIDEO_OUT_DEFAULT_HEIGHT;
uint64_t generation = 0;
int busType = VIDEO_OUT_BUS_TYPE_MAIN;
bool opened = false;
bool closing = false;
bool ps4 = false;
std::exception_ptr failure;
std::stop_token shutdownToken = LibcShutdownToken_nid_postfix();
int flipRate = 0;
uint64_t lastFlipVblank = 0;
std::chrono::steady_clock::time_point lastTimingFlip{};
uint64_t outputMode = VIDEO_OUT_OUTPUT_MODE_DEFAULT;
float gamma = 1.0f;
VideoOutFlipStatus flipStatus{};
VideoOutVblankStatus vblankStatus{};
VideoOutVblankStatus preVblankStatus{};
std::array<VideoOutBuffer, VIDEO_OUT_BUFFER_NUM_MAX> buffers{};
alignas(std::atomic_ref<std::uint64_t>::required_alignment) std::array<std::uint64_t, VIDEO_OUT_BUFFER_NUM_MAX> bufferLabels{};
std::array<uint32_t, VIDEO_OUT_BUFFER_NUM_MAX> bufferPending{};
std::array<BufferReuseTracker, VIDEO_OUT_BUFFER_NUM_MAX> bufferReuse;
std::array<BufferAttributeGroup, VIDEO_OUT_BUFFER_ATTRIBUTE_NUM_MAX> groups{};
bool Closed() const { return !opened || closing; }
void CheckAlive() const {
if (failure) std::rethrow_exception(failure);
if (shutdownToken.stop_requested()) throw ProcessShutdown{};
}
void Check() const {
CheckAlive();
if (Closed()) throw std::runtime_error("VideoOut: port is closed");
}
};
struct FlipQueue;
struct FlipRequest final : AgcDriver::IFlipRequest, std::enable_shared_from_this<FlipRequest> {
std::uint64_t reuseTicket = 0;
std::shared_ptr<VideoOutConfig> cfg;
std::shared_ptr<FlipQueue> queue;
uint64_t generation = 0;
std::uint32_t outputHandle = 0;
int index = 0;
int flipMode = 0;
int flipRate = 0;
int64_t flipArg = 0;
uint32_t width = 0;
uint32_t height = 0;
VideoOutBuffer buffer;
BufferAttributeGroup group;
bool groupCaptured = false;
bool reserved = false;
bool unregistered = false;
bool ready = false;
bool gpuComplete = false;
bool terminal = false;
std::shared_ptr<AgcDriver::FrameTiming> timing;
std::chrono::steady_clock::time_point queuedAt;
~FlipRequest() override;
void GpuReady(const std::shared_ptr<AgcDriver::FrameTiming>& frameTiming) override;
void Fail(std::exception_ptr error) noexcept override;
void Cancel() noexcept;
void ReleaseLocked() noexcept;
};
struct FlipQueue {
std::mutex mutex;
TimedWait::Condition changed;
std::list<std::shared_ptr<FlipRequest>> requests;
std::atomic<std::size_t> reservations = 0;
std::exception_ptr failure;
bool stopping = false;
};
class VideoOutDriver {
public:
static VideoOutDriver& Get();
VideoOutDriver();
~VideoOutDriver();
void Shutdown();
VideoOutDriver(const VideoOutDriver&) = delete;
VideoOutDriver& operator=(const VideoOutDriver&) = delete;
int Open(int busType);
bool Close(int handle);
std::shared_ptr<VideoOutConfig> GetConfig(int handle);
bool IsOpen(int handle);
bool HasConfig(int handle);
bool SupportsHighFrameRate();
int GetDisplayRefreshRate();
// 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);
void presentLoop(std::stop_token token, std::promise<void>& started);
void vblankLoop(std::stop_token token);
void vblankEnd(std::uint64_t tick);
void updateDisplayRefreshRate();
void processFlip(FlipRequest& req);
void triggerEvents(VideoOutConfig& cfg, int eventKind, void* triggerData);
void pollHostHdr();
std::mutex mutex;
std::mutex shutdownMutex;
bool stopped = false;
std::array<std::shared_ptr<VideoOutConfig>, VIDEO_OUT_NUM_MAX> contexts;
std::array<std::shared_ptr<AgcDriver::IVideoOutput>, VIDEO_OUT_NUM_MAX> outputs;
std::shared_ptr<FlipQueue> flipQueue = std::make_shared<FlipQueue>();
DisplayWindow window;
HostHdrProbe hdrProbe;
std::uint64_t hdrGeneration = 0;
std::atomic<int> displayRefreshRate{0};
std::jthread presentThread;
std::jthread vblankThread;
};
#endif