From d225df74492018fb705c1de558ee81f3b20a9958 Mon Sep 17 00:00:00 2001 From: boykopovar Date: Sat, 12 Sep 2026 17:32:24 +0300 Subject: [PATCH] feat(sceVideoOut): implement sdl2 window and present thread --- CMakeLists.txt | 4 +- core/libs/prx/libSceVideoOut/CMakeLists.txt | 7 +- .../libSceVideoOut/include/VideoOutDriver.hpp | 54 ++++- core/libs/prx/libSceVideoOut/src/Buffer.cpp | 62 ++--- core/libs/prx/libSceVideoOut/src/Event.cpp | 46 +++- core/libs/prx/libSceVideoOut/src/Output.cpp | 22 +- .../prx/libSceVideoOut/src/VideoOutDriver.cpp | 212 +++++++++++++++++- 7 files changed, 346 insertions(+), 61 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d405bbd42..9ce247048 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,9 +9,9 @@ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static -static-libgcc -st set(SDL_SHARED OFF CACHE BOOL "" FORCE) set(SDL_STATIC ON CACHE BOOL "" FORCE) set(SDL_TEST OFF CACHE BOOL "" FORCE) -set(SDL_VIDEO OFF CACHE BOOL "" FORCE) +set(SDL_VIDEO ON CACHE BOOL "" FORCE) set(SDL_RENDER OFF CACHE BOOL "" FORCE) -set(SDL_X11 OFF CACHE BOOL "" FORCE) +set(SDL_X11 ON CACHE BOOL "" FORCE) set(SDL_WAYLAND OFF CACHE BOOL "" FORCE) set(SDL_OPENGL OFF CACHE BOOL "" FORCE) set(SDL_OPENGLES OFF CACHE BOOL "" FORCE) diff --git a/core/libs/prx/libSceVideoOut/CMakeLists.txt b/core/libs/prx/libSceVideoOut/CMakeLists.txt index 2d0a58942..f0b60433a 100644 --- a/core/libs/prx/libSceVideoOut/CMakeLists.txt +++ b/core/libs/prx/libSceVideoOut/CMakeLists.txt @@ -5,5 +5,8 @@ add_library(libSceVideoOut SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/src/VideoOutDriver.cpp ) -target_include_directories(libSceVideoOut PRIVATE ${LIBS_INCLUDE_DIR}) -target_link_libraries(libSceVideoOut PRIVATE libc) +target_include_directories(libSceVideoOut PRIVATE + ${LIBS_INCLUDE_DIR} + ${CMAKE_SOURCE_DIR}/3rdparty/SDL2/include +) +target_link_libraries(libSceVideoOut PRIVATE libc libkernel SDL2-static) diff --git a/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp b/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp index 8d08a3d36..4128f82b3 100644 --- a/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp +++ b/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp @@ -4,8 +4,12 @@ #include #include #include +#include #include +#include +#include +#include "SDL.h" #include "SceTypes.hpp" static constexpr int VIDEO_OUT_ERROR_INVALID_VALUE = -2144796671; @@ -29,6 +33,7 @@ static constexpr int VIDEO_OUT_BUS_TYPE_SUB = 2; 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; @@ -64,10 +69,20 @@ struct BufferAttributeGroup { bool occupied = false; }; +struct EventRegistration { + KernelEqueue eq = 0; + uint64_t generation = 0; +}; + struct VideoOutConfig { std::mutex mutex; std::condition_variable_any vblankCond; + std::vector flipEvents; + std::vector vblankEvents; + std::vector preVblankEvents; + std::vector outputModeEvents; + uint32_t width = 1920; uint32_t height = 1080; uint64_t generation = 0; @@ -85,16 +100,49 @@ struct VideoOutConfig { std::array groups{}; }; -struct VideoOutDriver { - std::mutex mutex; - VideoOutConfig contexts[VIDEO_OUT_NUM_MAX]; +struct FlipRequest { + VideoOutConfig* cfg = nullptr; + uint64_t generation = 0; + int index = 0; + int flipMode = 0; + int64_t flipArg = 0; +}; +class VideoOutDriver { +public: static VideoOutDriver& Get(); + VideoOutDriver(); + ~VideoOutDriver(); + + VideoOutDriver(const VideoOutDriver&) = delete; + VideoOutDriver& operator=(const VideoOutDriver&) = delete; + int Open(int busType); bool Close(int handle); VideoOutConfig* GetConfig(int handle); bool IsOpen(int handle); + + void SubmitFlip(VideoOutConfig* cfg, int index, int flipMode, int64_t flipArg); + +private: + void presentLoop(std::stop_token token); + void vblankBegin(); + void vblankEnd(); + void processFlip(const FlipRequest& req); + void triggerEvents(VideoOutConfig& cfg, int eventKind, void* triggerData); + + std::mutex mutex; + VideoOutConfig contexts[VIDEO_OUT_NUM_MAX]; + + std::mutex flipMutex; + std::condition_variable flipCond; + std::list flipQueue; + + SDL_Window* window = nullptr; + SDL_Surface* windowSurface = nullptr; + + std::jthread presentThread; }; #endif diff --git a/core/libs/prx/libSceVideoOut/src/Buffer.cpp b/core/libs/prx/libSceVideoOut/src/Buffer.cpp index b7f8d845d..c4ed8d994 100644 --- a/core/libs/prx/libSceVideoOut/src/Buffer.cpp +++ b/core/libs/prx/libSceVideoOut/src/Buffer.cpp @@ -1,30 +1,30 @@ #include #include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" -#include "prx/libSceVideoOut/include/Buffer.hpp" #include "prx/libSceVideoOut/include/VideoOutDriver.hpp" extern "C" { -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 sceVideoOutSetBufferAttribute2(VideoOutBufferAttribute2* attribute, uint64_t pixelFormat, uint32_t tilingMode, uint32_t width, uint32_t height, uint64_t option, uint32_t dccControl, uint64_t dccCbRegisterClearColor) { if (attribute == nullptr) { - return; + throw std::runtime_error("sceVideoOutSetBufferAttribute2: null attribute"); } std::memset(attribute, 0, sizeof(VideoOutBufferAttribute2)); - attribute->tiling_mode = tiling_mode; + attribute->tiling_mode = tilingMode; attribute->aspect_ratio = 0; attribute->width = width; attribute->height = height; attribute->pitch_in_pixel = 0; attribute->option = option; - attribute->pixel_format = pixel_format; - attribute->dcc_cb_register_clear_color = dcc_cb_register_clear_color; - attribute->dcc_control = dcc_control; + attribute->pixel_format = pixelFormat; + attribute->dcc_cb_register_clear_color = dccCbRegisterClearColor; + attribute->dcc_control = dccControl; } -int APS5_VABI sceVideoOutRegisterBuffers2(int handle, int set_index, int buffer_index_start, const VideoOutBuffers* buffers, int buffer_num, const VideoOutBufferAttribute2* attribute, int category, void* option) { +int APS5_VABI sceVideoOutRegisterBuffers2(int handle, int setIndex, int bufferIndexStart, const VideoOutBuffers* buffers, int bufferNum, const VideoOutBufferAttribute2* attribute, int category, void* option) { auto* cfg = VideoOutDriver::Get().GetConfig(handle); if (cfg == nullptr) { return VIDEO_OUT_ERROR_INVALID_HANDLE; @@ -38,7 +38,7 @@ int APS5_VABI sceVideoOutRegisterBuffers2(int handle, int set_index, int buffer_ if (option != nullptr) { return VIDEO_OUT_ERROR_INVALID_OPTION; } - if (set_index < 0 || set_index >= VIDEO_OUT_BUFFER_ATTRIBUTE_NUM_MAX || buffer_index_start < 0 || buffer_index_start >= VIDEO_OUT_BUFFER_NUM_MAX || buffer_num < 1 || buffer_num > VIDEO_OUT_BUFFER_NUM_MAX || buffer_index_start + buffer_num > VIDEO_OUT_BUFFER_NUM_MAX) { + if (setIndex < 0 || setIndex >= VIDEO_OUT_BUFFER_ATTRIBUTE_NUM_MAX || bufferIndexStart < 0 || bufferIndexStart >= VIDEO_OUT_BUFFER_NUM_MAX || bufferNum < 1 || bufferNum > VIDEO_OUT_BUFFER_NUM_MAX || bufferIndexStart + bufferNum > VIDEO_OUT_BUFFER_NUM_MAX) { return VIDEO_OUT_ERROR_INVALID_VALUE; } if (category != VIDEO_OUT_BUFFER_ATTRIBUTE_CATEGORY_UNCOMPRESSED && category != VIDEO_OUT_BUFFER_ATTRIBUTE_CATEGORY_COMPRESSED) { @@ -48,11 +48,11 @@ int APS5_VABI sceVideoOutRegisterBuffers2(int handle, int set_index, int buffer_ if (cfg->closing) { return VIDEO_OUT_ERROR_INVALID_HANDLE; } - if (cfg->groups[set_index].occupied) { + if (cfg->groups[setIndex].occupied) { return VIDEO_OUT_ERROR_INVALID_INDEX; } - for (int i = 0; i < buffer_num; i++) { - if (cfg->buffers[buffer_index_start + i].Occupied()) { + for (int i = 0; i < bufferNum; i++) { + if (cfg->buffers[bufferIndexStart + i].Occupied()) { return VIDEO_OUT_ERROR_SLOT_OCCUPIED; } } @@ -60,18 +60,20 @@ int APS5_VABI sceVideoOutRegisterBuffers2(int handle, int set_index, int buffer_ group.attribute = *attribute; group.category = category; group.occupied = true; - cfg->groups[set_index] = group; - for (int i = 0; i < buffer_num; i++) { + cfg->groups[setIndex] = group; + cfg->width = attribute->width; + cfg->height = attribute->height; + for (int i = 0; i < bufferNum; i++) { VideoOutBuffer buf{}; - buf.groupIndex = set_index; + buf.groupIndex = setIndex; buf.dataAddress = reinterpret_cast(buffers[i].data); buf.metadataAddress = reinterpret_cast(buffers[i].metadata); - cfg->buffers[buffer_index_start + i] = buf; + cfg->buffers[bufferIndexStart + i] = buf; } return 0; } -int APS5_VABI sceVideoOutSubmitChangeBufferAttribute2(int handle, int set_index, const VideoOutBufferAttribute2* attribute, void* option) { +int APS5_VABI sceVideoOutSubmitChangeBufferAttribute2(int handle, int setIndex, const VideoOutBufferAttribute2* attribute, void* option) { auto* cfg = VideoOutDriver::Get().GetConfig(handle); if (cfg == nullptr) { return VIDEO_OUT_ERROR_INVALID_HANDLE; @@ -79,51 +81,50 @@ int APS5_VABI sceVideoOutSubmitChangeBufferAttribute2(int handle, int set_index, if (attribute == nullptr) { return VIDEO_OUT_ERROR_INVALID_OPTION; } - if (set_index < 0 || set_index >= VIDEO_OUT_BUFFER_ATTRIBUTE_NUM_MAX) { + if (setIndex < 0 || setIndex >= VIDEO_OUT_BUFFER_ATTRIBUTE_NUM_MAX) { return VIDEO_OUT_ERROR_INVALID_INDEX; } if (option != nullptr) { return VIDEO_OUT_ERROR_INVALID_OPTION; } std::unique_lock lock(cfg->mutex); - if (cfg->closing || !cfg->groups[set_index].occupied) { + if (cfg->closing || !cfg->groups[setIndex].occupied) { return VIDEO_OUT_ERROR_INVALID_INDEX; } - cfg->groups[set_index].attribute = *attribute; + cfg->groups[setIndex].attribute = *attribute; return 0; } -int APS5_VABI sceVideoOutUnregisterBuffers(int handle, int set_index) { +int APS5_VABI sceVideoOutUnregisterBuffers(int handle, int setIndex) { auto* cfg = VideoOutDriver::Get().GetConfig(handle); if (cfg == nullptr) { return VIDEO_OUT_ERROR_INVALID_HANDLE; } - if (set_index < 0 || set_index >= VIDEO_OUT_BUFFER_ATTRIBUTE_NUM_MAX) { + if (setIndex < 0 || setIndex >= VIDEO_OUT_BUFFER_ATTRIBUTE_NUM_MAX) { return VIDEO_OUT_ERROR_INVALID_INDEX; } std::unique_lock lock(cfg->mutex); if (cfg->closing) { return VIDEO_OUT_ERROR_INVALID_HANDLE; } - if (!cfg->groups[set_index].occupied) { + if (!cfg->groups[setIndex].occupied) { return VIDEO_OUT_ERROR_INVALID_INDEX; } - cfg->groups[set_index] = BufferAttributeGroup{}; + cfg->groups[setIndex] = BufferAttributeGroup{}; for (auto& buf : cfg->buffers) { - if (buf.groupIndex == set_index) { + if (buf.groupIndex == setIndex) { buf = VideoOutBuffer{}; } } return 0; } -int APS5_VABI sceVideoOutSubmitFlip(int handle, int index, int flip_mode, int64_t flip_arg) { - (void)flip_arg; +int APS5_VABI sceVideoOutSubmitFlip(int handle, int index, int flipMode, int64_t flipArg) { auto* cfg = VideoOutDriver::Get().GetConfig(handle); if (cfg == nullptr) { return VIDEO_OUT_ERROR_INVALID_HANDLE; } - if (flip_mode < VIDEO_OUT_FLIP_MODE_VSYNC || flip_mode > VIDEO_OUT_FLIP_MODE_VSYNC_MULTI) { + if (flipMode < VIDEO_OUT_FLIP_MODE_VSYNC || flipMode > VIDEO_OUT_FLIP_MODE_VSYNC_MULTI) { return VIDEO_OUT_ERROR_INVALID_VALUE; } if (index < VIDEO_OUT_BUFFER_INDEX_BLACK || index >= VIDEO_OUT_BUFFER_NUM_MAX) { @@ -137,9 +138,8 @@ int APS5_VABI sceVideoOutSubmitFlip(int handle, int index, int flip_mode, int64_ if (!special && !cfg->buffers[index].Occupied()) { return VIDEO_OUT_ERROR_INVALID_INDEX; } - cfg->flipStatus.currentBuffer = index; - cfg->flipStatus.flipArg = flip_arg; - cfg->flipStatus.count++; + lock.unlock(); + VideoOutDriver::Get().SubmitFlip(cfg, index, flipMode, flipArg); return 0; } diff --git a/core/libs/prx/libSceVideoOut/src/Event.cpp b/core/libs/prx/libSceVideoOut/src/Event.cpp index 1789483c1..f35e73f4d 100644 --- a/core/libs/prx/libSceVideoOut/src/Event.cpp +++ b/core/libs/prx/libSceVideoOut/src/Event.cpp @@ -1,12 +1,22 @@ +#include +#include #include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include "prx/libkernel/Equeue/Equeue.hpp" -#include "prx/libSceVideoOut/include/Event.hpp" #include "prx/libSceVideoOut/include/VideoOutDriver.hpp" -static int RegisterVideoOutEvent(int handle, KernelEqueue eq, int16_t eventKind, void* udata) { +static std::vector* getEventList(VideoOutConfig& cfg, int16_t eventKind) { + if (eventKind == VIDEO_OUT_EVENT_FLIP) return &cfg.flipEvents; + if (eventKind == VIDEO_OUT_EVENT_VBLANK) return &cfg.vblankEvents; + if (eventKind == VIDEO_OUT_EVENT_PRE_VBLANK_START) return &cfg.preVblankEvents; + if (eventKind == VIDEO_OUT_EVENT_SET_MODE) return &cfg.outputModeEvents; + throw std::runtime_error("getEventList: unknown event kind"); +} + +static int registerVideoOutEvent(int handle, KernelEqueue eq, int16_t eventKind, void* udata) { auto* cfg = VideoOutDriver::Get().GetConfig(handle); if (cfg == nullptr) { return VIDEO_OUT_ERROR_INVALID_HANDLE; @@ -57,16 +67,30 @@ static int RegisterVideoOutEvent(int handle, KernelEqueue eq, int16_t eventKind, e->event.data = 0; }; const int result = EqueueAddEvent_nid_postfix(eq, event); - return result == EQUEUE_ERROR_EBADF ? VIDEO_OUT_ERROR_INVALID_EVENT_QUEUE : result; + if (result == EQUEUE_ERROR_EBADF) { + return VIDEO_OUT_ERROR_INVALID_EVENT_QUEUE; + } + std::unique_lock lock(cfg->mutex); + EventRegistration reg; + reg.eq = eq; + reg.generation = cfg->generation; + getEventList(*cfg, eventKind)->push_back(reg); + return result; } -static int DeleteVideoOutEvent(int handle, KernelEqueue eq, int16_t eventKind) { +static int deleteVideoOutEvent(int handle, KernelEqueue eq, int16_t eventKind) { if (!VideoOutDriver::Get().IsOpen(handle)) { return VIDEO_OUT_ERROR_INVALID_HANDLE; } if (eq == 0 || !EqueuePin_nid_postfix(eq)) { return VIDEO_OUT_ERROR_INVALID_EVENT_QUEUE; } + auto* cfg = VideoOutDriver::Get().GetConfig(handle); + if (cfg != nullptr) { + std::unique_lock lock(cfg->mutex); + auto* events = getEventList(*cfg, eventKind); + events->erase(std::remove_if(events->begin(), events->end(), [eq](const EventRegistration& r) { return r.eq == eq; }), events->end()); + } const int result = EqueueDeleteEvent_nid_postfix(eq, static_cast(eventKind), EVFILT_VIDEO_OUT); if (result == EQUEUE_ERROR_EBADF) { return VIDEO_OUT_ERROR_INVALID_EVENT_QUEUE; @@ -77,31 +101,31 @@ static int DeleteVideoOutEvent(int handle, KernelEqueue eq, int16_t eventKind) { extern "C" { int APS5_VABI sceVideoOutAddFlipEvent(KernelEqueue eq, int handle, void* udata) { - return RegisterVideoOutEvent(handle, eq, VIDEO_OUT_EVENT_FLIP, udata); + return registerVideoOutEvent(handle, eq, VIDEO_OUT_EVENT_FLIP, udata); } int APS5_VABI sceVideoOutAddVblankEvent(KernelEqueue eq, int handle, void* udata) { - return RegisterVideoOutEvent(handle, eq, VIDEO_OUT_EVENT_VBLANK, udata); + return registerVideoOutEvent(handle, eq, VIDEO_OUT_EVENT_VBLANK, udata); } int APS5_VABI sceVideoOutAddPreVblankStartEvent(KernelEqueue eq, int handle, void* udata) { - return RegisterVideoOutEvent(handle, eq, VIDEO_OUT_EVENT_PRE_VBLANK_START, udata); + return registerVideoOutEvent(handle, eq, VIDEO_OUT_EVENT_PRE_VBLANK_START, udata); } int APS5_VABI sceVideoOutAddOutputModeEvent(KernelEqueue eq, int handle, void* udata) { - return RegisterVideoOutEvent(handle, eq, VIDEO_OUT_EVENT_SET_MODE, udata); + return registerVideoOutEvent(handle, eq, VIDEO_OUT_EVENT_SET_MODE, udata); } int APS5_VABI sceVideoOutDeleteFlipEvent(KernelEqueue eq, int handle) { - return DeleteVideoOutEvent(handle, eq, VIDEO_OUT_EVENT_FLIP); + return deleteVideoOutEvent(handle, eq, VIDEO_OUT_EVENT_FLIP); } int APS5_VABI sceVideoOutDeleteVblankEvent(KernelEqueue eq, int handle) { - return DeleteVideoOutEvent(handle, eq, VIDEO_OUT_EVENT_VBLANK); + return deleteVideoOutEvent(handle, eq, VIDEO_OUT_EVENT_VBLANK); } int APS5_VABI sceVideoOutDeletePreVblankStartEvent(KernelEqueue eq, int handle) { - return DeleteVideoOutEvent(handle, eq, VIDEO_OUT_EVENT_PRE_VBLANK_START); + return deleteVideoOutEvent(handle, eq, VIDEO_OUT_EVENT_PRE_VBLANK_START); } int APS5_VABI sceVideoOutGetEventId(const KernelEvent* ev) { diff --git a/core/libs/prx/libSceVideoOut/src/Output.cpp b/core/libs/prx/libSceVideoOut/src/Output.cpp index b3ad34597..a62b4bb26 100644 --- a/core/libs/prx/libSceVideoOut/src/Output.cpp +++ b/core/libs/prx/libSceVideoOut/src/Output.cpp @@ -1,12 +1,12 @@ #include #include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" -#include "prx/libSceVideoOut/include/Output.hpp" #include "prx/libSceVideoOut/include/VideoOutDriver.hpp" -static int ValidateOutputConfig(int handle, uint64_t mode, const VideoOutOutputOptions* options, void* reservedPtr, uint64_t reserved) { +static int validateOutputConfig(int handle, uint64_t mode, const VideoOutOutputOptions* options, void* reservedPtr, uint64_t reserved) { if (!VideoOutDriver::Get().IsOpen(handle)) { return VIDEO_OUT_ERROR_INVALID_HANDLE; } @@ -28,18 +28,18 @@ static int ValidateOutputConfig(int handle, uint64_t mode, const VideoOutOutputO extern "C" { -int APS5_VABI sceVideoOutOpen(int user_id, int bus_type, int index, const void* param) { +int APS5_VABI sceVideoOutOpen(int userId, int busType, int index, const void* param) { (void)param; - if (user_id != 255 && user_id != 0) { + if (userId != 255 && userId != 0) { return VIDEO_OUT_ERROR_INVALID_VALUE; } - if (bus_type != VIDEO_OUT_BUS_TYPE_MAIN && bus_type != VIDEO_OUT_BUS_TYPE_OVERLAY && bus_type != VIDEO_OUT_BUS_TYPE_SUB) { + if (busType != VIDEO_OUT_BUS_TYPE_MAIN && busType != VIDEO_OUT_BUS_TYPE_OVERLAY && busType != VIDEO_OUT_BUS_TYPE_SUB) { return VIDEO_OUT_ERROR_INVALID_VALUE; } if (index != 0) { return VIDEO_OUT_ERROR_INVALID_VALUE; } - const int handle = VideoOutDriver::Get().Open(bus_type); + const int handle = VideoOutDriver::Get().Open(busType); if (handle < 0) { return VIDEO_OUT_ERROR_RESOURCE_BUSY; } @@ -100,7 +100,7 @@ int APS5_VABI sceVideoOutGetOutputStatus(int handle, VideoOutOutputStatus* statu std::unique_lock lock(cfg->mutex); status->resolution = (cfg->width >= 3840 || cfg->height >= 2160) ? 2u : 1u; status->dynamicRange = 1; - status->refreshRate = VIDEO_OUT_REFRESH_RATE_59_94HZ; + status->refreshRate = (cfg->outputMode == VIDEO_OUT_OUTPUT_MODE_119_88HZ) ? VIDEO_OUT_REFRESH_RATE_119_88HZ : VIDEO_OUT_REFRESH_RATE_59_94HZ; status->flags = 0; status->reserved[0] = 0; status->reserved[1] = 0; @@ -138,16 +138,16 @@ int APS5_VABI sceVideoOutInitializeOutputOptions(VideoOutOutputOptions* options) return 0; } -int APS5_VABI sceVideoOutIsOutputSupported(int handle, uint64_t mode, const VideoOutOutputOptions* options, void* reserved_ptr, uint64_t reserved) { - const int result = ValidateOutputConfig(handle, mode, options, reserved_ptr, reserved); +int APS5_VABI sceVideoOutIsOutputSupported(int handle, uint64_t mode, const VideoOutOutputOptions* options, void* reservedPtr, uint64_t reserved) { + const int result = validateOutputConfig(handle, mode, options, reservedPtr, reserved); if (result != 0) { return result; } return (mode == VIDEO_OUT_OUTPUT_MODE_119_88HZ) ? 0 : 1; } -int APS5_VABI sceVideoOutConfigureOutput(int handle, uint64_t mode, const VideoOutOutputOptions* options, void* reserved_ptr, uint64_t reserved) { - const int supported = sceVideoOutIsOutputSupported(handle, mode, options, reserved_ptr, reserved); +int APS5_VABI sceVideoOutConfigureOutput(int handle, uint64_t mode, const VideoOutOutputOptions* options, void* reservedPtr, uint64_t reserved) { + const int supported = sceVideoOutIsOutputSupported(handle, mode, options, reservedPtr, reserved); if (supported < 0) { return supported; } diff --git a/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp b/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp index f3bb955d0..ce6121aa7 100644 --- a/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp +++ b/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp @@ -1,12 +1,39 @@ +#include +#include + +#include "SDL.h" +#include "prx/libkernel/Equeue/Equeue.hpp" +#include "prx/libkernel/Time/include/Time.hpp" #include "prx/libSceVideoOut/include/VideoOutDriver.hpp" -#include +static constexpr uint32_t VBLANK_FREQ_DEFAULT = 60; +static constexpr uint32_t VBLANK_FREQ_119 = 120; VideoOutDriver& VideoOutDriver::Get() { static VideoOutDriver instance; return instance; } +VideoOutDriver::VideoOutDriver() { + if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0) { + throw std::runtime_error(std::string("SDL_InitSubSystem(VIDEO) failed: ") + SDL_GetError()); + } + presentThread = std::jthread([this](std::stop_token token) { presentLoop(token); }); +} + +VideoOutDriver::~VideoOutDriver() { + if (presentThread.joinable()) { + presentThread.request_stop(); + flipCond.notify_all(); + presentThread.join(); + } + if (window != nullptr) { + SDL_DestroyWindow(window); + window = nullptr; + } + SDL_QuitSubSystem(SDL_INIT_VIDEO); +} + int VideoOutDriver::Open(int busType) { std::unique_lock lock(mutex); const int handle = busType + 1; @@ -34,6 +61,10 @@ int VideoOutDriver::Open(int busType) { for (auto& grp : cfg.groups) { grp = BufferAttributeGroup{}; } + cfg.flipEvents.clear(); + cfg.vblankEvents.clear(); + cfg.preVblankEvents.clear(); + cfg.outputModeEvents.clear(); if (++cfg.generation == 0) { throw std::runtime_error("VideoOut port generation wrapped"); } @@ -62,6 +93,10 @@ bool VideoOutDriver::Close(int handle) { grp = BufferAttributeGroup{}; } cfg.flipRate = 0; + cfg.flipEvents.clear(); + cfg.vblankEvents.clear(); + cfg.preVblankEvents.clear(); + cfg.outputModeEvents.clear(); cfg.vblankCond.notify_all(); return true; } @@ -78,3 +113,178 @@ bool VideoOutDriver::IsOpen(int handle) { std::unique_lock lock(mutex); return handle > 0 && handle < VIDEO_OUT_NUM_MAX && contexts[handle].opened; } + +void VideoOutDriver::SubmitFlip(VideoOutConfig* cfg, int index, int flipMode, int64_t flipArg) { + std::unique_lock lock(flipMutex); + if (flipQueue.size() >= VIDEO_OUT_FLIP_QUEUE_CAPACITY) { + throw std::runtime_error("VideoOut flip queue full"); + } + FlipRequest req; + req.cfg = cfg; + req.generation = cfg->generation; + req.index = index; + req.flipMode = flipMode; + req.flipArg = flipArg; + { + std::unique_lock cfgLock(cfg->mutex); + cfg->flipStatus.flipPendingNum++; + } + flipQueue.push_back(req); + flipCond.notify_one(); +} + +void VideoOutDriver::triggerEvents(VideoOutConfig& cfg, int eventKind, void* triggerData) { + std::vector* events = nullptr; + if (eventKind == VIDEO_OUT_EVENT_FLIP) { + events = &cfg.flipEvents; + } else if (eventKind == VIDEO_OUT_EVENT_VBLANK) { + events = &cfg.vblankEvents; + } else if (eventKind == VIDEO_OUT_EVENT_PRE_VBLANK_START) { + events = &cfg.preVblankEvents; + } else if (eventKind == VIDEO_OUT_EVENT_SET_MODE) { + events = &cfg.outputModeEvents; + } else { + throw std::runtime_error("triggerEvents: unknown event kind"); + } + for (auto& reg : *events) { + if (reg.generation != cfg.generation) { + continue; + } + EqueueTriggerEvent_nid_postfix(reg.eq, static_cast(eventKind), EVFILT_VIDEO_OUT, triggerData); + } +} + +void VideoOutDriver::vblankBegin() { + std::unique_lock lock(mutex); + for (int i = 1; i < VIDEO_OUT_NUM_MAX; i++) { + auto& cfg = contexts[i]; + if (!cfg.opened) { + continue; + } + std::unique_lock cfgLock(cfg.mutex); + cfg.preVblankStatus.count++; + cfg.preVblankStatus.processTime = sceKernelGetProcessTime(); + cfg.preVblankStatus.processTimeCounter = sceKernelGetProcessTimeCounter(); + triggerEvents(cfg, VIDEO_OUT_EVENT_PRE_VBLANK_START, reinterpret_cast(cfg.preVblankStatus.count)); + } +} + +void VideoOutDriver::vblankEnd() { + std::unique_lock lock(mutex); + for (int i = 1; i < VIDEO_OUT_NUM_MAX; i++) { + auto& cfg = contexts[i]; + if (!cfg.opened) { + continue; + } + std::unique_lock cfgLock(cfg.mutex); + cfg.vblankStatus.count++; + cfg.vblankStatus.processTime = sceKernelGetProcessTime(); + cfg.vblankStatus.processTimeCounter = sceKernelGetProcessTimeCounter(); + triggerEvents(cfg, VIDEO_OUT_EVENT_VBLANK, reinterpret_cast(cfg.vblankStatus.count)); + cfg.vblankCond.notify_all(); + } +} + +void VideoOutDriver::processFlip(const FlipRequest& req) { + VideoOutConfig* cfg = req.cfg; + { + std::unique_lock cfgLock(cfg->mutex); + if (!cfg->opened || cfg->closing || cfg->generation != req.generation) { + cfg->flipStatus.flipPendingNum--; + return; + } + } + const bool isBlank = req.index == VIDEO_OUT_BUFFER_INDEX_BLANK; + const bool isBlack = req.index == VIDEO_OUT_BUFFER_INDEX_BLACK; + if (isBlank || isBlack) { + if (window != nullptr) { + SDL_FillRect(windowSurface, nullptr, isBlack ? SDL_MapRGB(windowSurface->format, 0, 0, 0) : 0); + SDL_UpdateWindowSurface(window); + } + } else { + throw std::runtime_error("VideoOut Vulkan rendering not implemented"); + } + std::unique_lock cfgLock(cfg->mutex); + if (!cfg->opened || cfg->closing || cfg->generation != req.generation) { + cfg->flipStatus.flipPendingNum--; + return; + } + cfg->flipStatus.count++; + cfg->flipStatus.processTime = sceKernelGetProcessTime(); + cfg->flipStatus.processTimeCounter = sceKernelGetProcessTimeCounter(); + cfg->flipStatus.flipArg = req.flipArg; + cfg->flipStatus.currentBuffer = req.index; + cfg->flipStatus.flipPendingNum--; + triggerEvents(*cfg, VIDEO_OUT_EVENT_FLIP, reinterpret_cast(req.flipArg)); +} + +void VideoOutDriver::presentLoop(std::stop_token token) { + while (!token.stop_requested()) { + uint64_t outputMode = VIDEO_OUT_OUTPUT_MODE_DEFAULT; + { + std::unique_lock lock(mutex); + for (int i = 1; i < VIDEO_OUT_NUM_MAX; i++) { + if (contexts[i].opened) { + outputMode = contexts[i].outputMode; + break; + } + } + } + const uint32_t freq = (outputMode == VIDEO_OUT_OUTPUT_MODE_119_88HZ) ? VBLANK_FREQ_119 : VBLANK_FREQ_DEFAULT; + const auto period = std::chrono::nanoseconds(1'000'000'000ULL / freq); + const auto frameStart = std::chrono::steady_clock::now(); + + vblankBegin(); + + FlipRequest req{}; + bool hasFlip = false; + { + std::unique_lock lock(flipMutex); + if (!flipQueue.empty()) { + req = flipQueue.front(); + flipQueue.pop_front(); + hasFlip = true; + } + } + if (hasFlip) { + if (window == nullptr) { + uint32_t w = 1920; + uint32_t h = 1080; + { + std::unique_lock lock(mutex); + if (req.cfg != nullptr) { + std::unique_lock cfgLock(req.cfg->mutex); + w = req.cfg->width; + h = req.cfg->height; + } + } + window = SDL_CreateWindow("PS5", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, static_cast(w), static_cast(h), SDL_WINDOW_SHOWN); + if (window == nullptr) { + throw std::runtime_error(std::string("SDL_CreateWindow failed: ") + SDL_GetError()); + } + windowSurface = SDL_GetWindowSurface(window); + if (windowSurface == nullptr) { + throw std::runtime_error(std::string("SDL_GetWindowSurface failed: ") + SDL_GetError()); + } + } + processFlip(req); + } + + vblankEnd(); + + SDL_Event sdlEvent; + while (SDL_PollEvent(&sdlEvent)) { + if (sdlEvent.type == SDL_QUIT) { + return; + } + } + + const auto elapsed = std::chrono::steady_clock::now() - frameStart; + if (elapsed < period) { + const auto sleepUs = std::chrono::duration_cast(period - elapsed).count(); + if (sleepUs > 0) { + sceKernelUsleep_nid_postfix(static_cast(sleepUs)); + } + } + } +}