Files
AnyPS5/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp
T
LexroidandClaude Opus 5.5 5cd25dedbd fix(videoout): accept a buffer attribute change while a flip is pending
sceVideoOutSubmitChangeBufferAttribute2 threw when a buffer of the group
had a flip pending, and Marvel's Wolverine makes that call with flips in
the queue. A flip now takes its buffer group when it is reserved, in
submission order, so flips submitted before the change keep the
attributes their buffers were rendered with and later flips use the
new ones.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 21:06:04 +03:00

244 lines
8.5 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/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 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;
};
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;
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{};
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);
// 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);
private:
bool close(int handle);
void presentLoop(std::stop_token token, std::promise<void>& started);
void vblankLoop(std::stop_token token);
void vblankEnd();
void processFlip(FlipRequest& req);
void triggerEvents(VideoOutConfig& cfg, int eventKind, void* triggerData);
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;
std::jthread presentThread;
std::jthread vblankThread;
};
#endif