feat(sceVideoOut): implement sdl2 window and present thread
This commit is contained in:
parent
49e200c88a
commit
d225df7449
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -4,8 +4,12 @@
|
|||
#include <array>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <list>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#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<EventRegistration> flipEvents;
|
||||
std::vector<EventRegistration> vblankEvents;
|
||||
std::vector<EventRegistration> preVblankEvents;
|
||||
std::vector<EventRegistration> outputModeEvents;
|
||||
|
||||
uint32_t width = 1920;
|
||||
uint32_t height = 1080;
|
||||
uint64_t generation = 0;
|
||||
|
|
@ -85,16 +100,49 @@ struct VideoOutConfig {
|
|||
std::array<BufferAttributeGroup, VIDEO_OUT_BUFFER_ATTRIBUTE_NUM_MAX> 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<FlipRequest> flipQueue;
|
||||
|
||||
SDL_Window* window = nullptr;
|
||||
SDL_Surface* windowSurface = nullptr;
|
||||
|
||||
std::jthread presentThread;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,30 +1,30 @@
|
|||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
|
||||
#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<uint64_t>(buffers[i].data);
|
||||
buf.metadataAddress = reinterpret_cast<uint64_t>(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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,22 @@
|
|||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
|
||||
#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<EventRegistration>* 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<uintptr_t>(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) {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,39 @@
|
|||
#include <chrono>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "SDL.h"
|
||||
#include "prx/libkernel/Equeue/Equeue.hpp"
|
||||
#include "prx/libkernel/Time/include/Time.hpp"
|
||||
#include "prx/libSceVideoOut/include/VideoOutDriver.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
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<EventRegistration>* 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<uintptr_t>(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<void*>(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<void*>(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<void*>(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<int>(w), static_cast<int>(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<std::chrono::microseconds>(period - elapsed).count();
|
||||
if (sleepUs > 0) {
|
||||
sceKernelUsleep_nid_postfix(static_cast<KernelUseconds>(sleepUs));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue