video_core: Rewrite vulkan and videoout

This commit is contained in:
GPUCode 2024-04-14 17:09:51 +03:00
parent 0a94899c86
commit c01b6f8397
89 changed files with 5378 additions and 2150 deletions

View file

@ -0,0 +1,74 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string_view>
#include "common/assert.h"
#include "common/types.h"
namespace Libraries::VideoOut {
constexpr std::size_t MaxDisplayBuffers = 16;
constexpr std::size_t MaxDisplayBufferGroups = 4;
enum class PixelFormat : u32 {
Unknown,
A8R8G8B8Srgb = 0x80000000,
A8B8G8R8Srgb = 0x80002200,
A2R10G10B10 = 0x88060000,
A2R10G10B10Srgb = 0x88000000,
A2R10G10B10Bt2020Pq = 0x88740000,
A16R16G16B16Float = 0xC1060000,
};
enum class TilingMode : s32 {
Tile = 0,
Linear = 1,
};
constexpr std::string_view GetPixelFormatString(PixelFormat format) {
switch (format) {
case PixelFormat::A8R8G8B8Srgb:
return "A8R8G8B8Srgb";
case PixelFormat::A8B8G8R8Srgb:
return "A8B8G8R8Srgb";
case PixelFormat::A2R10G10B10:
return "A2R10G10B10";
case PixelFormat::A2R10G10B10Srgb:
return "A2R10G10B10Srgb";
case PixelFormat::A2R10G10B10Bt2020Pq:
return "A2R10G10B10Bt2020Pq";
case PixelFormat::A16R16G16B16Float:
return "A16R16G16B16Float";
default:
UNREACHABLE_MSG("Unknown pixel format {}", static_cast<u32>(format));
return "";
}
}
struct BufferAttribute {
PixelFormat pixel_format;
TilingMode tiling_mode;
s32 aspect_ratio;
u32 width;
u32 height;
u32 pitch_in_pixel;
u32 option;
u32 reserved0;
u64 reserved1;
};
struct BufferAttributeGroup {
bool is_occupied;
BufferAttribute attrib;
u32 size_in_bytes;
};
struct VideoOutBuffer {
s32 group_index{-1};
uintptr_t address_left;
uintptr_t address_right;
};
} // namespace Libraries::VideoOut

View file

@ -0,0 +1,236 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <pthread.h>
#include "common/assert.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/kernel/time_management.h"
#include "core/libraries/videoout/driver.h"
#include "video_core/renderer_vulkan/renderer_vulkan.h"
extern Frontend::WindowSDL* g_window;
namespace Libraries::VideoOut {
constexpr static bool Is32BppPixelFormat(PixelFormat format) {
switch (format) {
case PixelFormat::A8R8G8B8Srgb:
case PixelFormat::A8B8G8R8Srgb:
case PixelFormat::A2R10G10B10:
case PixelFormat::A2R10G10B10Srgb:
case PixelFormat::A2R10G10B10Bt2020Pq:
return true;
default:
return false;
}
}
constexpr u32 PixelFormatBpp(PixelFormat pixel_format) {
switch (pixel_format) {
case PixelFormat::A16R16G16B16Float:
return 8;
default:
return 4;
}
}
VideoOutDriver::VideoOutDriver(u32 width, u32 height) {
main_port.resolution.fullWidth = width;
main_port.resolution.fullHeight = height;
main_port.resolution.paneWidth = width;
main_port.resolution.paneHeight = height;
renderer = std::make_unique<Vulkan::RendererVulkan>(*g_window);
}
VideoOutDriver::~VideoOutDriver() = default;
int VideoOutDriver::Open(const ServiceThreadParams* params) {
std::scoped_lock lock{mutex};
if (main_port.is_open) {
return ORBIS_VIDEO_OUT_ERROR_RESOURCE_BUSY;
}
int handle = 1;
main_port.is_open = true;
return handle;
}
void VideoOutDriver::Close(s32 handle) {
std::scoped_lock lock{mutex};
main_port.is_open = false;
main_port.flip_rate = 0;
ASSERT(main_port.flip_events.empty());
}
VideoOutPort* VideoOutDriver::GetPort(int handle) {
if (handle != 1) [[unlikely]] {
return nullptr;
}
return &main_port;
}
int VideoOutDriver::RegisterBuffers(VideoOutPort* port, s32 startIndex, void* const* addresses,
s32 bufferNum, const BufferAttribute* attribute) {
const s32 group_index = port->FindFreeGroup();
if (group_index >= MaxDisplayBufferGroups) {
return ORBIS_VIDEO_OUT_ERROR_NO_EMPTY_SLOT;
}
if (startIndex + bufferNum > MaxDisplayBuffers || startIndex > MaxDisplayBuffers ||
bufferNum > MaxDisplayBuffers) {
LOG_ERROR(Lib_VideoOut,
"Attempted to register too many buffers startIndex = {}, bufferNum = {}",
startIndex, bufferNum);
return ORBIS_VIDEO_OUT_ERROR_INVALID_VALUE;
}
const s32 end_index = startIndex + bufferNum;
if (bufferNum > 0 &&
std::any_of(port->buffer_slots.begin() + startIndex, port->buffer_slots.begin() + end_index,
[](auto& buffer) { return buffer.group_index != -1; })) {
return ORBIS_VIDEO_OUT_ERROR_SLOT_OCCUPIED;
}
if (attribute->reserved0 != 0 || attribute->reserved1 != 0) {
LOG_ERROR(Lib_VideoOut, "Invalid reserved memebers");
return ORBIS_VIDEO_OUT_ERROR_INVALID_VALUE;
}
if (attribute->aspect_ratio != 0) {
LOG_ERROR(Lib_VideoOut, "Invalid aspect ratio = {}", attribute->aspect_ratio);
return ORBIS_VIDEO_OUT_ERROR_INVALID_ASPECT_RATIO;
}
if (attribute->width > attribute->pitch_in_pixel) {
LOG_ERROR(Lib_VideoOut, "Buffer width {} is larger than pitch {}", attribute->width,
attribute->pitch_in_pixel);
return ORBIS_VIDEO_OUT_ERROR_INVALID_PITCH;
}
if (attribute->tiling_mode < TilingMode::Tile || attribute->tiling_mode > TilingMode::Linear) {
LOG_ERROR(Lib_VideoOut, "Invalid tilingMode = {}",
static_cast<u32>(attribute->tiling_mode));
return ORBIS_VIDEO_OUT_ERROR_INVALID_TILING_MODE;
}
LOG_INFO(Lib_VideoOut,
"startIndex = {}, bufferNum = {}, pixelFormat = {}, aspectRatio = {}, "
"tilingMode = {}, width = {}, height = {}, pitchInPixel = {}, option = {:#x}",
startIndex, bufferNum, GetPixelFormatString(attribute->pixel_format),
attribute->aspect_ratio, static_cast<u32>(attribute->tiling_mode), attribute->width,
attribute->height, attribute->pitch_in_pixel, attribute->option);
auto& group = port->groups[group_index];
std::memcpy(&group.attrib, attribute, sizeof(BufferAttribute));
group.size_in_bytes =
attribute->height * attribute->pitch_in_pixel * PixelFormatBpp(attribute->pixel_format);
group.is_occupied = true;
for (u32 i = 0; i < bufferNum; i++) {
const uintptr_t address = reinterpret_cast<uintptr_t>(addresses[i]);
port->buffer_slots[startIndex + i] = VideoOutBuffer{
.group_index = group_index,
.address_left = address,
.address_right = 0,
};
LOG_INFO(Lib_VideoOut, "buffers[{}] = {:#x}", i + startIndex, address);
}
return group_index;
}
int VideoOutDriver::UnregisterBuffers(VideoOutPort* port, s32 attributeIndex) {
if (attributeIndex >= MaxDisplayBufferGroups || !port->groups[attributeIndex].is_occupied) {
LOG_ERROR(Lib_VideoOut, "Invalid attribute index {}", attributeIndex);
return ORBIS_VIDEO_OUT_ERROR_INVALID_VALUE;
}
auto& group = port->groups[attributeIndex];
group.is_occupied = false;
for (auto& buffer : port->buffer_slots) {
if (buffer.group_index != attributeIndex) {
continue;
}
buffer.group_index = -1;
}
return ORBIS_OK;
}
void VideoOutDriver::Flip(std::chrono::microseconds timeout) {
Request req;
{
std::unique_lock lock{mutex};
submit_cond.wait_for(lock, timeout, [&] { return !requests.empty(); });
if (requests.empty()) {
return;
}
// Retrieve the request.
req = requests.front();
requests.pop();
}
// Present the frame.
renderer->Present(req.frame);
std::scoped_lock lock{mutex};
// Update flip status.
auto& flip_status = req.port->flip_status;
flip_status.count++;
flip_status.processTime = Libraries::Kernel::sceKernelGetProcessTime();
flip_status.tsc = Libraries::Kernel::sceKernelReadTsc();
flip_status.submitTsc = Libraries::Kernel::sceKernelReadTsc();
flip_status.flipArg = req.flip_arg;
flip_status.currentBuffer = req.index;
flip_status.flipPendingNum = static_cast<int>(requests.size());
// Trigger flip events for the port.
for (auto& event : req.port->flip_events) {
if (event != nullptr) {
event->triggerEvent(SCE_VIDEO_OUT_EVENT_FLIP, Kernel::EVFILT_VIDEO_OUT,
reinterpret_cast<void*>(req.flip_arg));
}
}
}
bool VideoOutDriver::SubmitFlip(VideoOutPort* port, s32 index, s64 flip_arg) {
const auto& buffer = port->buffer_slots[index];
const auto& group = port->groups[buffer.group_index];
auto* frame = renderer->PrepareFrame(group, buffer.address_left);
std::scoped_lock lock{mutex};
if (requests.size() >= 2) {
return false;
}
requests.push({
.frame = frame,
.port = port,
.index = index,
.flip_arg = flip_arg,
.submit_tsc = Libraries::Kernel::sceKernelReadTsc(),
});
port->flip_status.flipPendingNum = static_cast<int>(requests.size());
port->flip_status.gcQueueNum = 0;
submit_cond.notify_one();
return true;
}
void VideoOutDriver::Vblank() {
std::scoped_lock lock{mutex};
auto& vblank_status = main_port.vblank_status;
vblank_status.count++;
vblank_status.processTime = Libraries::Kernel::sceKernelGetProcessTime();
vblank_status.tsc = Libraries::Kernel::sceKernelReadTsc();
}
} // namespace Libraries::VideoOut

View file

@ -0,0 +1,82 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <condition_variable>
#include <mutex>
#include <queue>
#include "core/libraries/videoout/video_out.h"
namespace Vulkan {
struct Frame;
class RendererVulkan;
} // namespace Vulkan
namespace Libraries::VideoOut {
struct VideoOutPort {
bool is_open = false;
SceVideoOutResolutionStatus resolution;
std::array<VideoOutBuffer, MaxDisplayBuffers> buffer_slots;
std::array<BufferAttributeGroup, MaxDisplayBufferGroups> groups;
FlipStatus flip_status;
SceVideoOutVblankStatus vblank_status;
std::vector<Kernel::SceKernelEqueue> flip_events;
int flip_rate = 0;
s32 FindFreeGroup() const {
s32 index = 0;
while (index < groups.size() && groups[index].is_occupied) {
index++;
}
return index;
}
};
struct ServiceThreadParams {
u32 unknown;
bool set_priority;
u32 priority;
bool set_affinity;
u64 affinity;
};
class VideoOutDriver {
public:
explicit VideoOutDriver(u32 width, u32 height);
~VideoOutDriver();
int Open(const ServiceThreadParams* params);
void Close(s32 handle);
VideoOutPort* GetPort(s32 handle);
int RegisterBuffers(VideoOutPort* port, s32 startIndex, void* const* addresses, s32 bufferNum,
const BufferAttribute* attribute);
int UnregisterBuffers(VideoOutPort* port, s32 attributeIndex);
void Flip(std::chrono::microseconds timeout);
bool SubmitFlip(VideoOutPort* port, s32 index, s64 flip_arg);
void Vblank();
private:
struct Request {
Vulkan::Frame* frame;
VideoOutPort* port;
s32 index;
s64 flip_arg;
u64 submit_tsc;
};
std::mutex mutex;
VideoOutPort main_port{};
std::condition_variable_any submit_cond;
std::condition_variable done_cond;
std::queue<Request> requests;
std::unique_ptr<Vulkan::RendererVulkan> renderer;
bool is_neo{};
};
} // namespace Libraries::VideoOut

View file

@ -0,0 +1,236 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "common/config.h"
#include "common/logging/log.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
#include "core/libraries/system/userservice.h"
#include "core/libraries/videoout/driver.h"
#include "core/libraries/videoout/video_out.h"
#include "core/loader/symbols_resolver.h"
namespace Libraries::VideoOut {
static std::unique_ptr<VideoOutDriver> driver;
void PS4_SYSV_ABI sceVideoOutSetBufferAttribute(BufferAttribute* attribute, PixelFormat pixelFormat,
u32 tilingMode, u32 aspectRatio, u32 width,
u32 height, u32 pitchInPixel) {
LOG_INFO(Lib_VideoOut,
"pixelFormat = {}, tilingMode = {}, aspectRatio = {}, width = {}, height = {}, "
"pitchInPixel = {}",
GetPixelFormatString(pixelFormat), tilingMode, aspectRatio, width, height,
pitchInPixel);
std::memset(attribute, 0, sizeof(BufferAttribute));
attribute->pixel_format = static_cast<PixelFormat>(pixelFormat);
attribute->tiling_mode = static_cast<TilingMode>(tilingMode);
attribute->aspect_ratio = aspectRatio;
attribute->width = width;
attribute->height = height;
attribute->pitch_in_pixel = pitchInPixel;
attribute->option = SCE_VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_NONE;
}
s32 PS4_SYSV_ABI sceVideoOutAddFlipEvent(Kernel::SceKernelEqueue eq, s32 handle, void* udata) {
LOG_INFO(Lib_VideoOut, "handle = {}", handle);
auto* port = driver->GetPort(handle);
if (port == nullptr) {
return ORBIS_VIDEO_OUT_ERROR_INVALID_HANDLE;
}
if (eq == nullptr) {
return ORBIS_VIDEO_OUT_ERROR_INVALID_EVENT_QUEUE;
}
Kernel::EqueueEvent event{};
event.isTriggered = false;
event.event.ident = SCE_VIDEO_OUT_EVENT_FLIP;
event.event.filter = Kernel::EVFILT_VIDEO_OUT;
event.event.udata = udata;
event.event.fflags = 0;
event.event.data = 0;
event.filter.data = port;
port->flip_events.push_back(eq);
return eq->addEvent(event);
}
s32 PS4_SYSV_ABI sceVideoOutRegisterBuffers(s32 handle, s32 startIndex, void* const* addresses,
s32 bufferNum, const BufferAttribute* attribute) {
if (!addresses || !attribute) {
LOG_ERROR(Lib_VideoOut, "Addresses are null");
return ORBIS_VIDEO_OUT_ERROR_INVALID_ADDRESS;
}
auto* port = driver->GetPort(handle);
if (!port || !port->is_open) {
LOG_ERROR(Lib_VideoOut, "Invalid handle = {}", handle);
return ORBIS_VIDEO_OUT_ERROR_INVALID_HANDLE;
}
return driver->RegisterBuffers(port, startIndex, addresses, bufferNum, attribute);
}
s32 PS4_SYSV_ABI sceVideoOutSetFlipRate(s32 handle, s32 rate) {
LOG_INFO(Lib_VideoOut, "called");
driver->GetPort(handle)->flip_rate = rate;
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVideoOutIsFlipPending(s32 handle) {
LOG_INFO(Lib_VideoOut, "called");
s32 pending = driver->GetPort(handle)->flip_status.flipPendingNum;
return pending;
}
s32 PS4_SYSV_ABI sceVideoOutSubmitFlip(s32 handle, s32 bufferIndex, s32 flipMode, s64 flipArg) {
auto* port = driver->GetPort(handle);
if (!port) {
LOG_ERROR(Lib_VideoOut, "Invalid handle = {}", handle);
return ORBIS_VIDEO_OUT_ERROR_INVALID_HANDLE;
}
if (flipMode != 1) {
LOG_WARNING(Lib_VideoOut, "flipmode = {}", flipMode);
}
ASSERT_MSG(bufferIndex != -1, "Blank output not supported");
if (bufferIndex < -1 || bufferIndex > 15) {
LOG_ERROR(Lib_VideoOut, "Invalid bufferIndex = {}", bufferIndex);
return ORBIS_VIDEO_OUT_ERROR_INVALID_INDEX;
}
if (port->buffer_slots[bufferIndex].group_index < 0) {
LOG_ERROR(Lib_VideoOut, "Slot in bufferIndex = {} is not registered", bufferIndex);
return ORBIS_VIDEO_OUT_ERROR_INVALID_INDEX;
}
LOG_INFO(Lib_VideoOut, "bufferIndex = {}, flipMode = {}, flipArg = {}", bufferIndex, flipMode,
flipArg);
if (!driver->SubmitFlip(port, bufferIndex, flipArg)) {
LOG_ERROR(Lib_VideoOut, "Flip queue is full");
return ORBIS_VIDEO_OUT_ERROR_FLIP_QUEUE_FULL;
}
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVideoOutGetFlipStatus(s32 handle, FlipStatus* status) {
if (!status) {
LOG_ERROR(Lib_VideoOut, "Flip status is null");
return ORBIS_VIDEO_OUT_ERROR_INVALID_ADDRESS;
}
auto* port = driver->GetPort(handle);
if (!port) {
LOG_ERROR(Lib_VideoOut, "Invalid port handle = {}", handle);
return ORBIS_VIDEO_OUT_ERROR_INVALID_HANDLE;
}
*status = port->flip_status;
LOG_INFO(Lib_VideoOut,
"count = {}, processTime = {}, tsc = {}, submitTsc = {}, flipArg = {}, gcQueueNum = "
"{}, flipPendingNum = {}, currentBuffer = {}",
status->count, status->processTime, status->tsc, status->submitTsc, status->flipArg,
status->gcQueueNum, status->flipPendingNum, status->currentBuffer);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVideoOutGetResolutionStatus(s32 handle, SceVideoOutResolutionStatus* status) {
LOG_INFO(Lib_VideoOut, "called");
*status = driver->GetPort(handle)->resolution;
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVideoOutOpen(SceUserServiceUserId userId, s32 busType, s32 index,
const void* param) {
LOG_INFO(Lib_VideoOut, "called");
ASSERT(userId == UserService::ORBIS_USER_SERVICE_USER_ID_SYSTEM || userId == 0);
ASSERT(busType == SCE_VIDEO_OUT_BUS_TYPE_MAIN);
ASSERT(param == nullptr);
if (index != 0) {
LOG_ERROR(Lib_VideoOut, "Index != 0");
return ORBIS_VIDEO_OUT_ERROR_INVALID_VALUE;
}
auto* params = reinterpret_cast<const ServiceThreadParams*>(param);
int handle = driver->Open(params);
if (handle < 0) {
LOG_ERROR(Lib_VideoOut, "All available handles are open");
return ORBIS_VIDEO_OUT_ERROR_RESOURCE_BUSY;
}
return handle;
}
s32 PS4_SYSV_ABI sceVideoOutClose(s32 handle) {
driver->Close(handle);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVideoOutUnregisterBuffers(s32 handle, s32 attributeIndex) {
auto* port = driver->GetPort(handle);
if (!port || !port->is_open) {
return ORBIS_VIDEO_OUT_ERROR_INVALID_HANDLE;
}
return driver->UnregisterBuffers(port, attributeIndex);
}
void Flip(std::chrono::microseconds micros) {
return driver->Flip(micros);
}
void Vblank() {
return driver->Vblank();
}
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
driver = std::make_unique<VideoOutDriver>(Config::getScreenWidth(), Config::getScreenHeight());
LIB_FUNCTION("SbU3dwp80lQ", "libSceVideoOut", 1, "libSceVideoOut", 0, 0,
sceVideoOutGetFlipStatus);
LIB_FUNCTION("U46NwOiJpys", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutSubmitFlip);
LIB_FUNCTION("w3BY+tAEiQY", "libSceVideoOut", 1, "libSceVideoOut", 0, 0,
sceVideoOutRegisterBuffers);
LIB_FUNCTION("HXzjK9yI30k", "libSceVideoOut", 1, "libSceVideoOut", 0, 0,
sceVideoOutAddFlipEvent);
LIB_FUNCTION("CBiu4mCE1DA", "libSceVideoOut", 1, "libSceVideoOut", 0, 0,
sceVideoOutSetFlipRate);
LIB_FUNCTION("i6-sR91Wt-4", "libSceVideoOut", 1, "libSceVideoOut", 0, 0,
sceVideoOutSetBufferAttribute);
LIB_FUNCTION("6kPnj51T62Y", "libSceVideoOut", 1, "libSceVideoOut", 0, 0,
sceVideoOutGetResolutionStatus);
LIB_FUNCTION("Up36PTk687E", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutOpen);
LIB_FUNCTION("zgXifHT9ErY", "libSceVideoOut", 1, "libSceVideoOut", 0, 0,
sceVideoOutIsFlipPending);
LIB_FUNCTION("N5KDtkIjjJ4", "libSceVideoOut", 1, "libSceVideoOut", 0, 0,
sceVideoOutUnregisterBuffers);
LIB_FUNCTION("uquVH4-Du78", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutClose);
// openOrbis appears to have libSceVideoOut_v1 module libSceVideoOut_v1.1
LIB_FUNCTION("Up36PTk687E", "libSceVideoOut", 1, "libSceVideoOut", 1, 1, sceVideoOutOpen);
LIB_FUNCTION("CBiu4mCE1DA", "libSceVideoOut", 1, "libSceVideoOut", 1, 1,
sceVideoOutSetFlipRate);
LIB_FUNCTION("HXzjK9yI30k", "libSceVideoOut", 1, "libSceVideoOut", 1, 1,
sceVideoOutAddFlipEvent);
LIB_FUNCTION("i6-sR91Wt-4", "libSceVideoOut", 1, "libSceVideoOut", 1, 1,
sceVideoOutSetBufferAttribute);
LIB_FUNCTION("w3BY+tAEiQY", "libSceVideoOut", 1, "libSceVideoOut", 1, 1,
sceVideoOutRegisterBuffers);
LIB_FUNCTION("U46NwOiJpys", "libSceVideoOut", 1, "libSceVideoOut", 1, 1, sceVideoOutSubmitFlip);
LIB_FUNCTION("SbU3dwp80lQ", "libSceVideoOut", 1, "libSceVideoOut", 1, 1,
sceVideoOutGetFlipStatus);
}
} // namespace Libraries::VideoOut

View file

@ -0,0 +1,107 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/libraries/kernel/event_queues.h"
#include "core/libraries/videoout/buffer.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::VideoOut {
using SceUserServiceUserId = s32; // TODO move it to proper place
// SceVideoOutBusType
constexpr int SCE_VIDEO_OUT_BUS_TYPE_MAIN = 0; // Main output
constexpr int SCE_VIDEO_OUT_BUS_TYPE_AUX_SOCIAL_SCREEN = 5; // Aux output for social
constexpr int SCE_VIDEO_OUT_BUS_TYPE_AUX_GAME_LIVE_STREAMING = 6; // Aux output for screaming
// SceVideoOutRefreshRate
constexpr int SCE_VIDEO_OUT_REFRESH_RATE_UNKNOWN = 0;
constexpr int SCE_VIDEO_OUT_REFRESH_RATE_23_98HZ = 1;
constexpr int SCE_VIDEO_OUT_REFRESH_RATE_50HZ = 2;
constexpr int SCE_VIDEO_OUT_REFRESH_RATE_59_94HZ = 3;
constexpr int SCE_VIDEO_OUT_REFRESH_RATE_119_88HZ = 13;
constexpr int SCE_VIDEO_OUT_REFRESH_RATE_89_91HZ = 35;
constexpr s64 SCE_VIDEO_OUT_REFRESH_RATE_ANY = 0xFFFFFFFFFFFFFFFFUL;
constexpr int SCE_VIDEO_OUT_PIXEL_FORMAT_A8R8G8B8_SRGB = 0x80000000;
constexpr int SCE_VIDEO_OUT_PIXEL_FORMAT_A8B8G8R8_SRGB = 0x80002200;
constexpr int SCE_VIDEO_OUT_PIXEL_FORMAT_A2R10G10B10 = 0x88060000;
constexpr int SCE_VIDEO_OUT_PIXEL_FORMAT_A2R10G10B10_SRGB = 0x88000000;
constexpr int SCE_VIDEO_OUT_PIXEL_FORMAT_A2R10G10B10_BT2020_PQ = 0x88740000;
constexpr int SCE_VIDEO_OUT_PIXEL_FORMAT_A16R16G16B16_FLOAT = 0xC1060000;
constexpr int SCE_VIDEO_OUT_PIXEL_FORMAT_YCBCR420_BT709 = 0x08322200;
constexpr int SCE_VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_NONE = 0;
constexpr int SCE_VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_VR = 7;
constexpr int SCE_VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_STRICT_COLORIMETRY = 8;
enum SceVideoOutEventId : s16 {
SCE_VIDEO_OUT_EVENT_FLIP = 0,
SCE_VIDEO_OUT_EVENT_VBLANK = 1,
SCE_VIDEO_OUT_EVENT_PRE_VBLANK_START = 2
};
enum AspectRatioMode : s32 {
SCE_VIDEO_OUT_ASPECT_RATIO_16_9 = 0,
};
struct FlipStatus {
u64 count = 0;
u64 processTime = 0;
u64 tsc = 0;
s64 flipArg = -1;
u64 submitTsc = 0;
u64 reserved0 = 0;
s32 gcQueueNum = 0;
s32 flipPendingNum = 0;
s32 currentBuffer = -1;
u32 reserved1 = 0;
};
struct SceVideoOutResolutionStatus {
s32 fullWidth = 1280;
s32 fullHeight = 720;
s32 paneWidth = 1280;
s32 paneHeight = 720;
u64 refreshRate = SCE_VIDEO_OUT_REFRESH_RATE_59_94HZ;
float screenSizeInInch = 50;
u16 flags = 0;
u16 reserved0 = 0;
u32 reserved1[3] = {0};
};
struct SceVideoOutVblankStatus {
u64 count = 0;
u64 processTime = 0;
u64 tsc = 0;
u64 reserved[1] = {0};
u8 flags = 0;
u8 pad1[7] = {};
};
void PS4_SYSV_ABI sceVideoOutSetBufferAttribute(BufferAttribute* attribute, PixelFormat pixelFormat,
u32 tilingMode, u32 aspectRatio, u32 width,
u32 height, u32 pitchInPixel);
s32 PS4_SYSV_ABI sceVideoOutAddFlipEvent(Kernel::SceKernelEqueue eq, s32 handle, void* udata);
s32 PS4_SYSV_ABI sceVideoOutRegisterBuffers(s32 handle, s32 startIndex, void* const* addresses,
s32 bufferNum, const BufferAttribute* attribute);
s32 PS4_SYSV_ABI sceVideoOutSetFlipRate(s32 handle, s32 rate);
s32 PS4_SYSV_ABI sceVideoOutIsFlipPending(s32 handle);
s32 PS4_SYSV_ABI sceVideoOutSubmitFlip(s32 handle, s32 bufferIndex, s32 flipMode, s64 flipArg);
s32 PS4_SYSV_ABI sceVideoOutGetFlipStatus(s32 handle, FlipStatus* status);
s32 PS4_SYSV_ABI sceVideoOutGetResolutionStatus(s32 handle, SceVideoOutResolutionStatus* status);
s32 PS4_SYSV_ABI sceVideoOutOpen(SceUserServiceUserId userId, s32 busType, s32 index,
const void* param);
s32 PS4_SYSV_ABI sceVideoOutClose(s32 handle);
void Flip(std::chrono::microseconds micros);
void Vblank();
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::VideoOut