This commit is contained in:
commit
fefd4b3bfd
|
@ -61,3 +61,6 @@
|
|||
[submodule "externals/date"]
|
||||
path = externals/date
|
||||
url = https://github.com/HowardHinnant/date.git
|
||||
[submodule "externals/ffmpeg-core"]
|
||||
path = externals/ffmpeg-core
|
||||
url = https://github.com/shadps4-emu/ext-ffmpeg-core
|
||||
|
|
|
@ -67,9 +67,11 @@ configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/common/scm_rev.cpp.in" "${CMAKE_
|
|||
|
||||
find_package(Boost 1.84.0 CONFIG)
|
||||
find_package(cryptopp 8.9.0 MODULE)
|
||||
find_package(FFmpeg 5.1.2 MODULE)
|
||||
find_package(fmt 10.2.1 CONFIG)
|
||||
find_package(glslang 14.2.0 CONFIG)
|
||||
find_package(magic_enum 0.9.6 CONFIG)
|
||||
find_package(RenderDoc 1.6.0 MODULE)
|
||||
find_package(SDL3 3.1.2 CONFIG)
|
||||
find_package(toml11 3.8.1 CONFIG)
|
||||
find_package(tsl-robin-map 1.3.0 CONFIG)
|
||||
|
@ -79,7 +81,6 @@ find_package(xbyak 7.07 CONFIG)
|
|||
find_package(xxHash 0.8.2 MODULE)
|
||||
find_package(zlib-ng 2.2.0 MODULE)
|
||||
find_package(Zydis 4.1.0 CONFIG)
|
||||
find_package(RenderDoc MODULE)
|
||||
|
||||
if (APPLE)
|
||||
find_package(date 3.0.1 CONFIG)
|
||||
|
@ -96,6 +97,15 @@ if(HAVE_SEM_TIMEDWAIT OR WIN32)
|
|||
add_compile_options(-DHAVE_SEM_TIMEDWAIT)
|
||||
endif()
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
|
||||
# libc++ requires -fexperimental-library to enable std::jthread and std::stop_token support.
|
||||
include(CheckCXXSymbolExists)
|
||||
check_cxx_symbol_exists(_LIBCPP_VERSION version LIBCPP)
|
||||
if(LIBCPP)
|
||||
add_compile_options(-fexperimental-library)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_subdirectory(externals)
|
||||
include_directories(src)
|
||||
|
||||
|
@ -184,6 +194,16 @@ set(SYSTEM_LIBS src/core/libraries/system/commondialog.cpp
|
|||
src/core/libraries/disc_map/disc_map.cpp
|
||||
src/core/libraries/disc_map/disc_map.h
|
||||
src/core/libraries/disc_map/disc_map_codes.h
|
||||
src/core/libraries/avplayer/avplayer_common.cpp
|
||||
src/core/libraries/avplayer/avplayer_common.h
|
||||
src/core/libraries/avplayer/avplayer_file_streamer.cpp
|
||||
src/core/libraries/avplayer/avplayer_file_streamer.h
|
||||
src/core/libraries/avplayer/avplayer_impl.cpp
|
||||
src/core/libraries/avplayer/avplayer_impl.h
|
||||
src/core/libraries/avplayer/avplayer_source.cpp
|
||||
src/core/libraries/avplayer/avplayer_source.h
|
||||
src/core/libraries/avplayer/avplayer_state.cpp
|
||||
src/core/libraries/avplayer/avplayer_state.h
|
||||
src/core/libraries/avplayer/avplayer.cpp
|
||||
src/core/libraries/avplayer/avplayer.h
|
||||
)
|
||||
|
@ -527,7 +547,10 @@ set(EMULATOR src/emulator.cpp
|
|||
if(ENABLE_QT_GUI)
|
||||
qt_add_resources(RESOURCE_FILES src/shadps4.qrc)
|
||||
|
||||
set(QT_GUI src/qt_gui/main_window_ui.h
|
||||
set(QT_GUI src/qt_gui/about_dialog.cpp
|
||||
src/qt_gui/about_dialog.h
|
||||
src/qt_gui/about_dialog.ui
|
||||
src/qt_gui/main_window_ui.h
|
||||
src/qt_gui/main_window.cpp
|
||||
src/qt_gui/main_window.h
|
||||
src/qt_gui/gui_context_menus.h
|
||||
|
@ -588,7 +611,7 @@ endif()
|
|||
|
||||
create_target_directory_groups(shadps4)
|
||||
|
||||
target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API)
|
||||
target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg)
|
||||
target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::SPIRV glslang::glslang SDL3::SDL3)
|
||||
|
||||
if (APPLE)
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
# SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
find_package(PkgConfig QUIET)
|
||||
pkg_check_modules(FFMPEG QUIET IMPORTED_TARGET libavcodec libavfilter libavformat libavutil libswresample libswscale)
|
||||
|
||||
find_file(FFMPEG_VERSION_FILE libavutil/ffversion.h HINTS "${FFMPEG_libavutil_INCLUDEDIR}")
|
||||
if (FFMPEG_VERSION_FILE)
|
||||
file(STRINGS "${FFMPEG_VERSION_FILE}" FFMPEG_VERSION_LINE REGEX "FFMPEG_VERSION")
|
||||
string(REGEX MATCH "[0-9.]+" FFMPEG_VERSION "${FFMPEG_VERSION_LINE}")
|
||||
unset(FFMPEG_VERSION_LINE)
|
||||
unset(FFMPEG_VERSION_FILE)
|
||||
endif()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(FFmpeg
|
||||
REQUIRED_VARS FFMPEG_LINK_LIBRARIES
|
||||
VERSION_VAR FFMPEG_VERSION
|
||||
)
|
||||
|
||||
if (FFmpeg_FOUND AND NOT TARGET FFmpeg::ffmpeg)
|
||||
add_library(FFmpeg::ffmpeg ALIAS PkgConfig::FFMPEG)
|
||||
endif()
|
|
@ -0,0 +1,25 @@
|
|||
# SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
find_path(RENDERDOC_INCLUDE_DIR renderdoc_app.h)
|
||||
|
||||
if (RENDERDOC_INCLUDE_DIR AND EXISTS "${RENDERDOC_INCLUDE_DIR}/renderdoc_app.h")
|
||||
file(STRINGS "${RENDERDOC_INCLUDE_DIR}/renderdoc_app.h" RENDERDOC_VERSION_LINE REGEX "typedef struct RENDERDOC_API")
|
||||
string(REGEX REPLACE ".*typedef struct RENDERDOC_API_([0-9]+)_([0-9]+)_([0-9]+).*" "\\1.\\2.\\3" RENDERDOC_VERSION "${RENDERDOC_VERSION_LINE}")
|
||||
unset(RENDERDOC_VERSION_LINE)
|
||||
endif()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(RenderDoc
|
||||
REQUIRED_VARS RENDERDOC_INCLUDE_DIR
|
||||
VERSION_VAR RENDERDOC_VERSION
|
||||
)
|
||||
|
||||
if (RenderDoc_FOUND AND NOT TARGET RenderDoc::API)
|
||||
add_library(RenderDoc::API INTERFACE IMPORTED)
|
||||
set_target_properties(RenderDoc::API PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${RENDERDOC_INCLUDE_DIR}"
|
||||
)
|
||||
endif()
|
||||
|
||||
mark_as_advanced(RENDERDOC_INCLUDE_DIR)
|
|
@ -47,6 +47,12 @@ else()
|
|||
endif()
|
||||
endif()
|
||||
|
||||
if (NOT TARGET FFmpeg::ffmpeg)
|
||||
set(ARCHITECTURE "x86_64")
|
||||
add_subdirectory(ffmpeg-core)
|
||||
add_library(FFmpeg::ffmpeg ALIAS ffmpeg)
|
||||
endif()
|
||||
|
||||
# Zlib-Ng
|
||||
if (NOT TARGET zlib-ng::zlib)
|
||||
set(ZLIB_ENABLE_TESTS OFF)
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Subproject commit e30b7d7fe228bfb3f6e41ce1040b44a15eb7d5e0
|
|
@ -1,19 +1,23 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "sdl_audio.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
|
||||
#include <SDL3/SDL_audio.h>
|
||||
#include <SDL3/SDL_init.h>
|
||||
#include <SDL3/SDL_timer.h>
|
||||
#include "common/assert.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "sdl_audio.h"
|
||||
|
||||
#include <mutex> // std::unique_lock
|
||||
|
||||
namespace Audio {
|
||||
|
||||
int SDLAudio::AudioOutOpen(int type, u32 samples_num, u32 freq,
|
||||
Libraries::AudioOut::OrbisAudioOutParamFormat format) {
|
||||
using Libraries::AudioOut::OrbisAudioOutParamFormat;
|
||||
std::scoped_lock lock{m_mutex};
|
||||
std::unique_lock lock{m_mutex};
|
||||
for (int id = 0; id < portsOut.size(); id++) {
|
||||
auto& port = portsOut[id];
|
||||
if (!port.isOpen) {
|
||||
|
@ -88,7 +92,7 @@ int SDLAudio::AudioOutOpen(int type, u32 samples_num, u32 freq,
|
|||
}
|
||||
|
||||
s32 SDLAudio::AudioOutOutput(s32 handle, const void* ptr) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
std::shared_lock lock{m_mutex};
|
||||
auto& port = portsOut[handle - 1];
|
||||
if (!port.isOpen) {
|
||||
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
|
||||
|
@ -100,7 +104,7 @@ s32 SDLAudio::AudioOutOutput(s32 handle, const void* ptr) {
|
|||
int result = SDL_PutAudioStreamData(port.stream, ptr,
|
||||
port.samples_num * port.sample_size * port.channels_num);
|
||||
// TODO find a correct value 8192 is estimated
|
||||
while (SDL_GetAudioStreamAvailable(port.stream) > 8192) {
|
||||
while (SDL_GetAudioStreamAvailable(port.stream) > 65536) {
|
||||
SDL_Delay(0);
|
||||
}
|
||||
|
||||
|
@ -109,7 +113,7 @@ s32 SDLAudio::AudioOutOutput(s32 handle, const void* ptr) {
|
|||
|
||||
bool SDLAudio::AudioOutSetVolume(s32 handle, s32 bitflag, s32* volume) {
|
||||
using Libraries::AudioOut::OrbisAudioOutParamFormat;
|
||||
std::scoped_lock lock{m_mutex};
|
||||
std::shared_lock lock{m_mutex};
|
||||
auto& port = portsOut[handle - 1];
|
||||
if (!port.isOpen) {
|
||||
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
|
||||
|
@ -147,7 +151,7 @@ bool SDLAudio::AudioOutSetVolume(s32 handle, s32 bitflag, s32* volume) {
|
|||
}
|
||||
|
||||
bool SDLAudio::AudioOutGetStatus(s32 handle, int* type, int* channels_num) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
std::shared_lock lock{m_mutex};
|
||||
auto& port = portsOut[handle - 1];
|
||||
*type = port.type;
|
||||
*channels_num = port.channels_num;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include <shared_mutex>
|
||||
#include <SDL3/SDL_audio.h>
|
||||
#include "core/libraries/audio/audioout.h"
|
||||
|
||||
|
@ -32,7 +32,7 @@ private:
|
|||
int volume[8] = {};
|
||||
SDL_AudioStream* stream = nullptr;
|
||||
};
|
||||
std::mutex m_mutex;
|
||||
std::shared_mutex m_mutex;
|
||||
std::array<PortOut, 22> portsOut; // main up to 8 ports , BGM 1 port , voice up to 4 ports ,
|
||||
// personal up to 4 ports , padspk up to 5 ports , aux 1 port
|
||||
};
|
||||
|
|
|
@ -106,12 +106,12 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
|
|||
SUB(Lib, DiscMap) \
|
||||
SUB(Lib, Png) \
|
||||
SUB(Lib, PlayGo) \
|
||||
SUB(Lib, Random) \
|
||||
SUB(Lib, Usbd) \
|
||||
SUB(Lib, Ajm) \
|
||||
SUB(Lib, ErrorDialog) \
|
||||
SUB(Lib, ImeDialog) \
|
||||
SUB(Lib, AvPlayer) \
|
||||
SUB(Lib, Random) \
|
||||
CLS(Frontend) \
|
||||
CLS(Render) \
|
||||
SUB(Render, Vulkan) \
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
namespace Common {
|
||||
|
||||
constexpr char VERSION[] = "0.1.1 WIP";
|
||||
constexpr char VERSION[] = "0.2.1 WIP";
|
||||
constexpr bool isRelease = false;
|
||||
|
||||
} // namespace Common
|
||||
|
|
|
@ -350,7 +350,7 @@ bool PKG::Extract(const std::filesystem::path& filepath, const std::filesystem::
|
|||
return true;
|
||||
}
|
||||
|
||||
void PKG::ExtractFiles(const int& index) {
|
||||
void PKG::ExtractFiles(const int index) {
|
||||
int inode_number = fsTable[index].inode;
|
||||
int inode_type = fsTable[index].type;
|
||||
std::string inode_name = fsTable[index].name;
|
||||
|
|
|
@ -104,7 +104,7 @@ public:
|
|||
~PKG();
|
||||
|
||||
bool Open(const std::filesystem::path& filepath);
|
||||
void ExtractFiles(const int& index);
|
||||
void ExtractFiles(const int index);
|
||||
bool Extract(const std::filesystem::path& filepath, const std::filesystem::path& extract,
|
||||
std::string& failreason);
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
TRP::TRP() = default;
|
||||
TRP::~TRP() = default;
|
||||
|
||||
void TRP::GetNPcommID(std::filesystem::path trophyPath, int index) {
|
||||
void TRP::GetNPcommID(const std::filesystem::path& trophyPath, int index) {
|
||||
std::filesystem::path trpPath = trophyPath / "sce_sys/npbind.dat";
|
||||
Common::FS::IOFile npbindFile(trpPath, Common::FS::FileAccessMode::Read);
|
||||
if (!npbindFile.IsOpen()) {
|
||||
|
@ -27,7 +27,7 @@ static void removePadding(std::vector<u8>& vec) {
|
|||
}
|
||||
}
|
||||
|
||||
bool TRP::Extract(std::filesystem::path trophyPath) {
|
||||
bool TRP::Extract(const std::filesystem::path& trophyPath) {
|
||||
std::string title = trophyPath.filename().string();
|
||||
std::filesystem::path gameSysDir = trophyPath / "sce_sys/trophy/";
|
||||
if (!std::filesystem::exists(gameSysDir)) {
|
||||
|
|
|
@ -33,8 +33,8 @@ class TRP {
|
|||
public:
|
||||
TRP();
|
||||
~TRP();
|
||||
bool Extract(std::filesystem::path trophyPath);
|
||||
void GetNPcommID(std::filesystem::path trophyPath, int index);
|
||||
bool Extract(const std::filesystem::path& trophyPath);
|
||||
void GetNPcommID(const std::filesystem::path& trophyPath, int index);
|
||||
|
||||
private:
|
||||
Crypto crypto;
|
||||
|
|
|
@ -25,9 +25,9 @@ void MntPoints::UnmountAll() {
|
|||
m_mnt_pairs.clear();
|
||||
}
|
||||
|
||||
std::filesystem::path MntPoints::GetHostPath(const std::string& guest_directory) {
|
||||
std::filesystem::path MntPoints::GetHostPath(std::string_view guest_directory) {
|
||||
// Evil games like Turok2 pass double slashes e.g /app0//game.kpf
|
||||
auto corrected_path = guest_directory;
|
||||
std::string corrected_path(guest_directory);
|
||||
size_t pos = corrected_path.find("//");
|
||||
while (pos != std::string::npos) {
|
||||
corrected_path.replace(pos, 2, "/");
|
||||
|
|
|
@ -31,7 +31,7 @@ public:
|
|||
void Unmount(const std::filesystem::path& host_folder, const std::string& guest_folder);
|
||||
void UnmountAll();
|
||||
|
||||
std::filesystem::path GetHostPath(const std::string& guest_directory);
|
||||
std::filesystem::path GetHostPath(std::string_view guest_directory);
|
||||
|
||||
const MntPair* GetMount(const std::string& guest_path) {
|
||||
const auto it = std::ranges::find_if(
|
||||
|
|
|
@ -175,7 +175,6 @@ int PS4_SYSV_ABI sceAudioOutGetLastOutputTime() {
|
|||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAudioOutGetPortState(s32 handle, OrbisAudioOutPortState* state) {
|
||||
|
||||
int type = 0;
|
||||
int channels_num = 0;
|
||||
|
||||
|
@ -235,11 +234,11 @@ int PS4_SYSV_ABI sceAudioOutGetSystemState() {
|
|||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAudioOutInit() {
|
||||
LOG_TRACE(Lib_AudioOut, "called");
|
||||
if (audio != nullptr) {
|
||||
return ORBIS_AUDIO_OUT_ERROR_ALREADY_INIT;
|
||||
}
|
||||
audio = std::make_unique<Audio::SDLAudio>();
|
||||
LOG_INFO(Lib_AudioOut, "called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,21 +1,34 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Generated By moduleGenerator
|
||||
#include "avplayer.h"
|
||||
|
||||
#include "avplayer_impl.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/kernel/thread_management.h"
|
||||
#include "core/libraries/libs.h"
|
||||
|
||||
namespace Libraries::AvPlayer {
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerAddSource() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
using namespace Kernel;
|
||||
|
||||
s32 PS4_SYSV_ABI sceAvPlayerAddSource(SceAvPlayerHandle handle, const char* filename) {
|
||||
LOG_TRACE(Lib_AvPlayer, "filename = {}", filename);
|
||||
if (handle == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
const auto res = handle->AddSource(filename);
|
||||
LOG_TRACE(Lib_AvPlayer, "returning {}", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerAddSourceEx() {
|
||||
s32 PS4_SYSV_ABI sceAvPlayerAddSourceEx(SceAvPlayerHandle handle, SceAvPlayerUriType uriType,
|
||||
SceAvPlayerSourceDetails* sourceDetails) {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
if (handle == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
|
@ -24,122 +37,244 @@ int PS4_SYSV_ABI sceAvPlayerChangeStream() {
|
|||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerClose() {
|
||||
s32 PS4_SYSV_ABI sceAvPlayerClose(SceAvPlayerHandle handle) {
|
||||
LOG_TRACE(Lib_AvPlayer, "called");
|
||||
if (handle == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
delete handle;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
u64 PS4_SYSV_ABI sceAvPlayerCurrentTime(SceAvPlayerHandle handle) {
|
||||
LOG_TRACE(Lib_AvPlayer, "called");
|
||||
if (handle == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
const auto res = handle->CurrentTime();
|
||||
LOG_TRACE(Lib_AvPlayer, "returning {}", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAvPlayerDisableStream(SceAvPlayerHandle handle, u32 stream_id) {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
if (handle == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAvPlayerEnableStream(SceAvPlayerHandle handle, u32 stream_id) {
|
||||
LOG_TRACE(Lib_AvPlayer, "stream_id = {}", stream_id);
|
||||
if (handle == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
const auto res = handle->EnableStream(stream_id);
|
||||
LOG_TRACE(Lib_AvPlayer, "returning {}", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
bool PS4_SYSV_ABI sceAvPlayerGetAudioData(SceAvPlayerHandle handle, SceAvPlayerFrameInfo* p_info) {
|
||||
LOG_TRACE(Lib_AvPlayer, "called");
|
||||
if (handle == nullptr || p_info == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
const auto res = handle->GetAudioData(*p_info);
|
||||
LOG_TRACE(Lib_AvPlayer, "returning {}", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAvPlayerGetStreamInfo(SceAvPlayerHandle handle, u32 stream_id,
|
||||
SceAvPlayerStreamInfo* p_info) {
|
||||
LOG_TRACE(Lib_AvPlayer, "stream_id = {}", stream_id);
|
||||
if (handle == nullptr || p_info == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
const auto res = handle->GetStreamInfo(stream_id, *p_info);
|
||||
LOG_TRACE(Lib_AvPlayer, "returning {}", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
bool PS4_SYSV_ABI sceAvPlayerGetVideoData(SceAvPlayerHandle handle,
|
||||
SceAvPlayerFrameInfo* video_info) {
|
||||
LOG_TRACE(Lib_AvPlayer, "called");
|
||||
if (handle == nullptr || video_info == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
const auto res = handle->GetVideoData(*video_info);
|
||||
LOG_TRACE(Lib_AvPlayer, "returning {}", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
bool PS4_SYSV_ABI sceAvPlayerGetVideoDataEx(SceAvPlayerHandle handle,
|
||||
SceAvPlayerFrameInfoEx* video_info) {
|
||||
LOG_TRACE(Lib_AvPlayer, "called");
|
||||
if (handle == nullptr || video_info == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
const auto res = handle->GetVideoData(*video_info);
|
||||
LOG_TRACE(Lib_AvPlayer, "returning {}", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
SceAvPlayerHandle PS4_SYSV_ABI sceAvPlayerInit(SceAvPlayerInitData* data) {
|
||||
LOG_TRACE(Lib_AvPlayer, "called");
|
||||
if (data == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (data->memory_replacement.allocate == nullptr ||
|
||||
data->memory_replacement.allocate_texture == nullptr ||
|
||||
data->memory_replacement.deallocate == nullptr ||
|
||||
data->memory_replacement.deallocate_texture == nullptr) {
|
||||
LOG_ERROR(Lib_AvPlayer, "All allocators are required for AVPlayer Initialisation.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return new AvPlayer(*data);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAvPlayerInitEx(const SceAvPlayerInitDataEx* p_data,
|
||||
SceAvPlayerHandle* p_player) {
|
||||
LOG_TRACE(Lib_AvPlayer, "called");
|
||||
if (p_data == nullptr || p_player == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
|
||||
if (p_data->memory_replacement.allocate == nullptr ||
|
||||
p_data->memory_replacement.allocate_texture == nullptr ||
|
||||
p_data->memory_replacement.deallocate == nullptr ||
|
||||
p_data->memory_replacement.deallocate_texture == nullptr) {
|
||||
LOG_ERROR(Lib_AvPlayer, "All allocators are required for AVPlayer Initialisation.");
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
|
||||
SceAvPlayerInitData data = {};
|
||||
data.memory_replacement = p_data->memory_replacement;
|
||||
data.file_replacement = p_data->file_replacement;
|
||||
data.event_replacement = p_data->event_replacement;
|
||||
data.default_language = p_data->default_language;
|
||||
data.num_output_video_framebuffers = p_data->num_output_video_framebuffers;
|
||||
data.auto_start = p_data->auto_start;
|
||||
|
||||
*p_player = new AvPlayer(data);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
bool PS4_SYSV_ABI sceAvPlayerIsActive(SceAvPlayerHandle handle) {
|
||||
LOG_TRACE(Lib_AvPlayer, "called");
|
||||
if (handle == nullptr) {
|
||||
LOG_TRACE(Lib_AvPlayer, "returning ORBIS_AVPLAYER_ERROR_INVALID_PARAMS");
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
const auto res = handle->IsActive();
|
||||
LOG_TRACE(Lib_AvPlayer, "returning {}", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAvPlayerJumpToTime(SceAvPlayerHandle handle, uint64_t time) {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called, time (msec) = {}", time);
|
||||
if (handle == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAvPlayerPause(SceAvPlayerHandle handle) {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
if (handle == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAvPlayerPostInit(SceAvPlayerHandle handle, SceAvPlayerPostInitData* data) {
|
||||
LOG_TRACE(Lib_AvPlayer, "called");
|
||||
if (handle == nullptr || data == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
const auto res = handle->PostInit(*data);
|
||||
LOG_TRACE(Lib_AvPlayer, "returning {}", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAvPlayerPrintf(const char* format, ...) {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerCurrentTime() {
|
||||
s32 PS4_SYSV_ABI sceAvPlayerResume(SceAvPlayerHandle handle) {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
if (handle == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAvPlayerSetAvSyncMode(SceAvPlayerHandle handle,
|
||||
SceAvPlayerAvSyncMode sync_mode) {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
if (handle == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAvPlayerSetLogCallback(SceAvPlayerLogCallback log_cb, void* user_data) {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerDisableStream() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
s32 PS4_SYSV_ABI sceAvPlayerSetLooping(SceAvPlayerHandle handle, bool loop_flag) {
|
||||
LOG_TRACE(Lib_AvPlayer, "called, looping = {}", loop_flag);
|
||||
if (handle == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
if (!handle->SetLooping(loop_flag)) {
|
||||
return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerEnableStream() {
|
||||
s32 PS4_SYSV_ABI sceAvPlayerSetTrickSpeed(SceAvPlayerHandle handle, s32 trick_speed) {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
if (handle == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerGetAudioData() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
s32 PS4_SYSV_ABI sceAvPlayerStart(SceAvPlayerHandle handle) {
|
||||
LOG_TRACE(Lib_AvPlayer, "called");
|
||||
if (handle == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
const auto res = handle->Start();
|
||||
LOG_TRACE(Lib_AvPlayer, "returning {}", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerGetStreamInfo() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
s32 PS4_SYSV_ABI sceAvPlayerStop(SceAvPlayerHandle handle) {
|
||||
LOG_TRACE(Lib_AvPlayer, "called");
|
||||
if (handle == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
const auto res = handle->Stop();
|
||||
LOG_TRACE(Lib_AvPlayer, "returning {}", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerGetVideoData() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
s32 PS4_SYSV_ABI sceAvPlayerStreamCount(SceAvPlayerHandle handle) {
|
||||
LOG_TRACE(Lib_AvPlayer, "called");
|
||||
if (handle == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
const auto res = handle->GetStreamCount();
|
||||
LOG_TRACE(Lib_AvPlayer, "returning {}", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerGetVideoDataEx() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerInit() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerInitEx() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerIsActive() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerJumpToTime() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerPause() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerPostInit() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerPrintf() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerResume() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerSetAvSyncMode() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerSetLogCallback() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerSetLooping() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerSetTrickSpeed() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerStart() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerStop() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerStreamCount() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerVprintf() {
|
||||
s32 PS4_SYSV_ABI sceAvPlayerVprintf(const char* format, va_list args) {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
|
|
@ -5,39 +5,288 @@
|
|||
|
||||
#include "common/types.h"
|
||||
|
||||
#include <stdarg.h> // va_list
|
||||
#include <stddef.h> // size_t
|
||||
|
||||
namespace Core::Loader {
|
||||
class SymbolsResolver;
|
||||
}
|
||||
|
||||
namespace Libraries::AvPlayer {
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerAddSource();
|
||||
int PS4_SYSV_ABI sceAvPlayerAddSourceEx();
|
||||
int PS4_SYSV_ABI sceAvPlayerChangeStream();
|
||||
int PS4_SYSV_ABI sceAvPlayerClose();
|
||||
int PS4_SYSV_ABI sceAvPlayerCurrentTime();
|
||||
int PS4_SYSV_ABI sceAvPlayerDisableStream();
|
||||
int PS4_SYSV_ABI sceAvPlayerEnableStream();
|
||||
int PS4_SYSV_ABI sceAvPlayerGetAudioData();
|
||||
int PS4_SYSV_ABI sceAvPlayerGetStreamInfo();
|
||||
int PS4_SYSV_ABI sceAvPlayerGetVideoData();
|
||||
int PS4_SYSV_ABI sceAvPlayerGetVideoDataEx();
|
||||
int PS4_SYSV_ABI sceAvPlayerInit();
|
||||
int PS4_SYSV_ABI sceAvPlayerInitEx();
|
||||
int PS4_SYSV_ABI sceAvPlayerIsActive();
|
||||
int PS4_SYSV_ABI sceAvPlayerJumpToTime();
|
||||
int PS4_SYSV_ABI sceAvPlayerPause();
|
||||
int PS4_SYSV_ABI sceAvPlayerPostInit();
|
||||
int PS4_SYSV_ABI sceAvPlayerPrintf();
|
||||
int PS4_SYSV_ABI sceAvPlayerResume();
|
||||
int PS4_SYSV_ABI sceAvPlayerSetAvSyncMode();
|
||||
int PS4_SYSV_ABI sceAvPlayerSetLogCallback();
|
||||
int PS4_SYSV_ABI sceAvPlayerSetLooping();
|
||||
int PS4_SYSV_ABI sceAvPlayerSetTrickSpeed();
|
||||
int PS4_SYSV_ABI sceAvPlayerStart();
|
||||
int PS4_SYSV_ABI sceAvPlayerStop();
|
||||
int PS4_SYSV_ABI sceAvPlayerStreamCount();
|
||||
int PS4_SYSV_ABI sceAvPlayerVprintf();
|
||||
class AvPlayer;
|
||||
|
||||
using SceAvPlayerHandle = AvPlayer*;
|
||||
|
||||
enum SceAvPlayerUriType { SCE_AVPLAYER_URI_TYPE_SOURCE = 0 };
|
||||
|
||||
struct SceAvPlayerUri {
|
||||
const char* name;
|
||||
u32 length;
|
||||
};
|
||||
|
||||
enum SceAvPlayerSourceType {
|
||||
SCE_AVPLAYER_SOURCE_TYPE_UNKNOWN = 0,
|
||||
SCE_AVPLAYER_SOURCE_TYPE_FILE_MP4 = 1,
|
||||
SCE_AVPLAYER_SOURCE_TYPE_HLS = 8
|
||||
};
|
||||
|
||||
struct SceAvPlayerSourceDetails {
|
||||
SceAvPlayerUri uri;
|
||||
u8 reserved1[64];
|
||||
SceAvPlayerSourceType source_type;
|
||||
u8 reserved2[44];
|
||||
};
|
||||
|
||||
struct SceAvPlayerAudio {
|
||||
u16 channel_count;
|
||||
u8 reserved1[2];
|
||||
u32 sample_rate;
|
||||
u32 size;
|
||||
u8 language_code[4];
|
||||
};
|
||||
|
||||
struct SceAvPlayerVideo {
|
||||
u32 width;
|
||||
u32 height;
|
||||
f32 aspect_ratio;
|
||||
u8 language_code[4];
|
||||
};
|
||||
|
||||
struct SceAvPlayerTextPosition {
|
||||
u16 top;
|
||||
u16 left;
|
||||
u16 bottom;
|
||||
u16 right;
|
||||
};
|
||||
|
||||
struct SceAvPlayerTimedText {
|
||||
u8 language_code[4];
|
||||
u16 text_size;
|
||||
u16 font_size;
|
||||
SceAvPlayerTextPosition position;
|
||||
};
|
||||
|
||||
union SceAvPlayerStreamDetails {
|
||||
u8 reserved[16];
|
||||
SceAvPlayerAudio audio;
|
||||
SceAvPlayerVideo video;
|
||||
SceAvPlayerTimedText subs;
|
||||
};
|
||||
|
||||
struct SceAvPlayerFrameInfo {
|
||||
u8* pData;
|
||||
u8 reserved[4];
|
||||
u64 timestamp;
|
||||
SceAvPlayerStreamDetails details;
|
||||
};
|
||||
|
||||
struct SceAvPlayerStreamInfo {
|
||||
u32 type;
|
||||
u8 reserved[4];
|
||||
SceAvPlayerStreamDetails details;
|
||||
u64 duration;
|
||||
u64 start_time;
|
||||
};
|
||||
|
||||
struct SceAvPlayerAudioEx {
|
||||
u16 channel_count;
|
||||
u8 reserved[2];
|
||||
u32 sample_rate;
|
||||
u32 size;
|
||||
u8 language_code[4];
|
||||
u8 reserved1[64];
|
||||
};
|
||||
|
||||
struct SceAvPlayerVideoEx {
|
||||
u32 width;
|
||||
u32 height;
|
||||
f32 aspect_ratio;
|
||||
u8 language_code[4];
|
||||
u32 framerate;
|
||||
u32 crop_left_offset;
|
||||
u32 crop_right_offset;
|
||||
u32 crop_top_offset;
|
||||
u32 crop_bottom_offset;
|
||||
u32 pitch;
|
||||
u8 luma_bit_depth;
|
||||
u8 chroma_bit_depth;
|
||||
bool video_full_range_flag;
|
||||
u8 reserved1[37];
|
||||
};
|
||||
|
||||
struct SceAvPlayerTimedTextEx {
|
||||
u8 language_code[4];
|
||||
u8 reserved[12];
|
||||
u8 reserved1[64];
|
||||
};
|
||||
|
||||
union SceAvPlayerStreamDetailsEx {
|
||||
SceAvPlayerAudioEx audio;
|
||||
SceAvPlayerVideoEx video;
|
||||
SceAvPlayerTimedTextEx subs;
|
||||
u8 reserved1[80];
|
||||
};
|
||||
|
||||
struct SceAvPlayerFrameInfoEx {
|
||||
void* pData;
|
||||
u8 reserved[4];
|
||||
u64 timestamp;
|
||||
SceAvPlayerStreamDetailsEx details;
|
||||
};
|
||||
|
||||
typedef void* PS4_SYSV_ABI (*SceAvPlayerAllocate)(void* p, u32 align, u32 size);
|
||||
typedef void PS4_SYSV_ABI (*SceAvPlayerDeallocate)(void* p, void* mem);
|
||||
typedef void* PS4_SYSV_ABI (*SceAvPlayerAllocateTexture)(void* p, u32 align, u32 size);
|
||||
typedef void PS4_SYSV_ABI (*SceAvPlayerDeallocateTexture)(void* p, void* mem);
|
||||
|
||||
struct SceAvPlayerMemAllocator {
|
||||
void* object_ptr;
|
||||
SceAvPlayerAllocate allocate;
|
||||
SceAvPlayerDeallocate deallocate;
|
||||
SceAvPlayerAllocateTexture allocate_texture;
|
||||
SceAvPlayerDeallocateTexture deallocate_texture;
|
||||
};
|
||||
|
||||
typedef s32 PS4_SYSV_ABI (*SceAvPlayerOpenFile)(void* p, const char* name);
|
||||
typedef s32 PS4_SYSV_ABI (*SceAvPlayerCloseFile)(void* p);
|
||||
typedef s32 PS4_SYSV_ABI (*SceAvPlayerReadOffsetFile)(void* p, u8* buf, u64 pos, u32 len);
|
||||
typedef u64 PS4_SYSV_ABI (*SceAvPlayerSizeFile)(void* p);
|
||||
|
||||
struct SceAvPlayerFileReplacement {
|
||||
void* object_ptr;
|
||||
SceAvPlayerOpenFile open;
|
||||
SceAvPlayerCloseFile close;
|
||||
SceAvPlayerReadOffsetFile readOffset;
|
||||
SceAvPlayerSizeFile size;
|
||||
};
|
||||
|
||||
typedef void PS4_SYSV_ABI (*SceAvPlayerEventCallback)(void* p, s32 event, s32 src_id, void* data);
|
||||
|
||||
struct SceAvPlayerEventReplacement {
|
||||
void* object_ptr;
|
||||
SceAvPlayerEventCallback event_callback;
|
||||
};
|
||||
|
||||
enum SceAvPlayerDebuglevels {
|
||||
SCE_AVPLAYER_DBG_NONE,
|
||||
SCE_AVPLAYER_DBG_INFO,
|
||||
SCE_AVPLAYER_DBG_WARNINGS,
|
||||
SCE_AVPLAYER_DBG_ALL
|
||||
};
|
||||
|
||||
struct SceAvPlayerInitData {
|
||||
SceAvPlayerMemAllocator memory_replacement;
|
||||
SceAvPlayerFileReplacement file_replacement;
|
||||
SceAvPlayerEventReplacement event_replacement;
|
||||
SceAvPlayerDebuglevels debug_level;
|
||||
u32 base_priority;
|
||||
s32 num_output_video_framebuffers;
|
||||
bool auto_start;
|
||||
u8 reserved[3];
|
||||
const char* default_language;
|
||||
};
|
||||
|
||||
struct SceAvPlayerInitDataEx {
|
||||
size_t this_size;
|
||||
SceAvPlayerMemAllocator memory_replacement;
|
||||
SceAvPlayerFileReplacement file_replacement;
|
||||
SceAvPlayerEventReplacement event_replacement;
|
||||
const char* default_language;
|
||||
SceAvPlayerDebuglevels debug_level;
|
||||
u32 audio_decoder_priority;
|
||||
u32 audio_decoder_affinity;
|
||||
u32 video_decoder_priority;
|
||||
u32 video_decoder_affinity;
|
||||
u32 demuxer_priority;
|
||||
u32 demuxer_affinity;
|
||||
u32 controller_priority;
|
||||
u32 controller_affinity;
|
||||
u32 http_streaming_priority;
|
||||
u32 http_streaming_affinity;
|
||||
u32 file_streaming_priority;
|
||||
u32 file_streaming_affinity;
|
||||
s32 num_output_video_framebuffers;
|
||||
bool auto_start;
|
||||
u8 reserved[3];
|
||||
};
|
||||
|
||||
enum SceAvPlayerStreamType {
|
||||
SCE_AVPLAYER_VIDEO,
|
||||
SCE_AVPLAYER_AUDIO,
|
||||
SCE_AVPLAYER_TIMEDTEXT,
|
||||
SCE_AVPLAYER_UNKNOWN
|
||||
};
|
||||
|
||||
enum SceAvPlayerVideoDecoderType {
|
||||
SCE_AVPLAYER_VIDEO_DECODER_TYPE_DEFAULT = 0,
|
||||
SCE_AVPLAYER_VIDEO_DECODER_TYPE_RESERVED1,
|
||||
SCE_AVPLAYER_VIDEO_DECODER_TYPE_SOFTWARE,
|
||||
SCE_AVPLAYER_VIDEO_DECODER_TYPE_SOFTWARE2
|
||||
};
|
||||
|
||||
enum SceAvPlayerAudioDecoderType {
|
||||
SCE_AVPLAYER_AUDIO_DECODER_TYPE_DEFAULT = 0,
|
||||
SCE_AVPLAYER_AUDIO_DECODER_TYPE_RESERVED1,
|
||||
SCE_AVPLAYER_AUDIO_DECODER_TYPE_RESERVED2
|
||||
};
|
||||
|
||||
struct SceAvPlayerDecoderInit {
|
||||
union {
|
||||
SceAvPlayerVideoDecoderType video_type;
|
||||
SceAvPlayerAudioDecoderType audio_type;
|
||||
u8 reserved[4];
|
||||
} decoderType;
|
||||
union {
|
||||
struct {
|
||||
s32 cpu_affinity_mask;
|
||||
s32 cpu_thread_priority;
|
||||
u8 decode_pipeline_depth;
|
||||
u8 compute_pipe_id;
|
||||
u8 compute_queue_id;
|
||||
u8 enable_interlaced;
|
||||
u8 reserved[16];
|
||||
} avcSw2;
|
||||
struct {
|
||||
u8 audio_channel_order;
|
||||
u8 reserved[27];
|
||||
} aac;
|
||||
u8 reserved[28];
|
||||
} decoderParams;
|
||||
};
|
||||
|
||||
struct SceAvPlayerHTTPCtx {
|
||||
u32 http_context_id;
|
||||
u32 ssl_context_id;
|
||||
};
|
||||
|
||||
struct SceAvPlayerPostInitData {
|
||||
u32 demux_video_buffer_size;
|
||||
SceAvPlayerDecoderInit video_decoder_init;
|
||||
SceAvPlayerDecoderInit audio_decoder_init;
|
||||
SceAvPlayerHTTPCtx http_context;
|
||||
u8 reserved[56];
|
||||
};
|
||||
|
||||
enum SceAvPlayerAvSyncMode {
|
||||
SCE_AVPLAYER_AV_SYNC_MODE_DEFAULT = 0,
|
||||
SCE_AVPLAYER_AV_SYNC_MODE_NONE
|
||||
};
|
||||
|
||||
typedef int PS4_SYSV_ABI (*SceAvPlayerLogCallback)(void* p, const char* format, va_list args);
|
||||
|
||||
enum SceAvPlayerEvents {
|
||||
SCE_AVPLAYER_STATE_STOP = 0x01,
|
||||
SCE_AVPLAYER_STATE_READY = 0x02,
|
||||
SCE_AVPLAYER_STATE_PLAY = 0x03,
|
||||
SCE_AVPLAYER_STATE_PAUSE = 0x04,
|
||||
SCE_AVPLAYER_STATE_BUFFERING = 0x05,
|
||||
SCE_AVPLAYER_TIMED_TEXT_DELIVERY = 0x10,
|
||||
SCE_AVPLAYER_WARNING_ID = 0x20,
|
||||
SCE_AVPLAYER_ENCRYPTION = 0x30,
|
||||
SCE_AVPLAYER_DRM_ERROR = 0x40
|
||||
};
|
||||
|
||||
void RegisterlibSceAvPlayer(Core::Loader::SymbolsResolver* sym);
|
||||
} // namespace Libraries::AvPlayer
|
||||
|
||||
} // namespace Libraries::AvPlayer
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "avplayer.h"
|
||||
#include "avplayer_common.h"
|
||||
|
||||
#include <algorithm> // std::equal
|
||||
#include <cctype> // std::tolower
|
||||
#include <string_view> // std::string_view
|
||||
|
||||
namespace Libraries::AvPlayer {
|
||||
|
||||
using namespace Kernel;
|
||||
|
||||
static bool ichar_equals(char a, char b) {
|
||||
return std::tolower(static_cast<unsigned char>(a)) ==
|
||||
std::tolower(static_cast<unsigned char>(b));
|
||||
}
|
||||
|
||||
static bool iequals(std::string_view l, std::string_view r) {
|
||||
return std::ranges::equal(l, r, ichar_equals);
|
||||
}
|
||||
|
||||
SceAvPlayerSourceType GetSourceType(std::string_view path) {
|
||||
if (path.empty()) {
|
||||
return SCE_AVPLAYER_SOURCE_TYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
std::string_view name = path;
|
||||
if (path.find("://") != std::string_view::npos) {
|
||||
// This path is a URI. Strip HTTP parameters from it.
|
||||
// schema://server.domain/path/file.ext/and/beyond?param=value#paragraph ->
|
||||
// -> schema://server.domain/path/to/file.ext/and/beyond
|
||||
name = path.substr(0, path.find_first_of("?#"));
|
||||
if (name.empty()) {
|
||||
return SCE_AVPLAYER_SOURCE_TYPE_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
// schema://server.domain/path/to/file.ext/and/beyond -> .ext/and/beyond
|
||||
auto ext = name.substr(name.rfind('.'));
|
||||
if (ext.empty()) {
|
||||
return SCE_AVPLAYER_SOURCE_TYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
// .ext/and/beyond -> .ext
|
||||
ext = ext.substr(0, ext.find('/'));
|
||||
|
||||
if (iequals(ext, ".mp4") || iequals(ext, ".m4v") || iequals(ext, ".m3d") ||
|
||||
iequals(ext, ".m4a") || iequals(ext, ".mov")) {
|
||||
return SCE_AVPLAYER_SOURCE_TYPE_FILE_MP4;
|
||||
}
|
||||
|
||||
if (iequals(ext, ".m3u8")) {
|
||||
return SCE_AVPLAYER_SOURCE_TYPE_HLS;
|
||||
}
|
||||
|
||||
return SCE_AVPLAYER_SOURCE_TYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
} // namespace Libraries::AvPlayer
|
|
@ -0,0 +1,91 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "avplayer.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/kernel/thread_management.h"
|
||||
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <queue>
|
||||
|
||||
#define AVPLAYER_IS_ERROR(x) ((x) < 0)
|
||||
|
||||
namespace Libraries::AvPlayer {
|
||||
|
||||
enum class AvState {
|
||||
Initial,
|
||||
AddingSource,
|
||||
Ready,
|
||||
Play,
|
||||
Stop,
|
||||
EndOfFile,
|
||||
Pause,
|
||||
C0x08,
|
||||
Jump,
|
||||
TrickMode,
|
||||
C0x0B,
|
||||
Buffering,
|
||||
Starting,
|
||||
Error,
|
||||
};
|
||||
|
||||
enum class AvEventType {
|
||||
ChangeFlowState = 21,
|
||||
WarningId = 22,
|
||||
RevertState = 30,
|
||||
AddSource = 40,
|
||||
Error = 255,
|
||||
};
|
||||
|
||||
union AvPlayerEventData {
|
||||
u32 num_frames; // 20
|
||||
AvState state; // AvEventType::ChangeFlowState
|
||||
s32 error; // AvEventType::WarningId
|
||||
u32 attempt; // AvEventType::AddSource
|
||||
};
|
||||
|
||||
struct AvPlayerEvent {
|
||||
AvEventType event;
|
||||
AvPlayerEventData payload;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class AvPlayerQueue {
|
||||
public:
|
||||
size_t Size() {
|
||||
return m_queue.size();
|
||||
}
|
||||
|
||||
void Push(T&& value) {
|
||||
std::lock_guard guard(m_mutex);
|
||||
m_queue.emplace(std::forward<T>(value));
|
||||
}
|
||||
|
||||
std::optional<T> Pop() {
|
||||
if (Size() == 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
std::lock_guard guard(m_mutex);
|
||||
auto result = std::move(m_queue.front());
|
||||
m_queue.pop();
|
||||
return result;
|
||||
}
|
||||
|
||||
void Clear() {
|
||||
std::lock_guard guard(m_mutex);
|
||||
m_queue = {};
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex m_mutex{};
|
||||
std::queue<T> m_queue{};
|
||||
};
|
||||
|
||||
SceAvPlayerSourceType GetSourceType(std::string_view path);
|
||||
|
||||
} // namespace Libraries::AvPlayer
|
|
@ -0,0 +1,20 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "avplayer.h"
|
||||
|
||||
#include "common/types.h"
|
||||
|
||||
struct AVIOContext;
|
||||
|
||||
namespace Libraries::AvPlayer {
|
||||
|
||||
class IDataStreamer {
|
||||
public:
|
||||
virtual ~IDataStreamer() = default;
|
||||
virtual AVIOContext* GetContext() = 0;
|
||||
};
|
||||
|
||||
} // namespace Libraries::AvPlayer
|
|
@ -0,0 +1,86 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "avplayer_file_streamer.h"
|
||||
|
||||
#include "avplayer_common.h"
|
||||
|
||||
#include <magic_enum.hpp>
|
||||
|
||||
extern "C" {
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavformat/avio.h>
|
||||
}
|
||||
|
||||
#include <algorithm> // std::max, std::min
|
||||
|
||||
#define AVPLAYER_AVIO_BUFFER_SIZE 4096
|
||||
|
||||
namespace Libraries::AvPlayer {
|
||||
|
||||
AvPlayerFileStreamer::AvPlayerFileStreamer(const SceAvPlayerFileReplacement& file_replacement,
|
||||
std::string_view path)
|
||||
: m_file_replacement(file_replacement) {
|
||||
const auto ptr = m_file_replacement.object_ptr;
|
||||
m_fd = m_file_replacement.open(ptr, path.data());
|
||||
ASSERT(m_fd >= 0);
|
||||
m_file_size = m_file_replacement.size(ptr);
|
||||
// avio_buffer is deallocated in `avio_context_free`
|
||||
const auto avio_buffer = reinterpret_cast<u8*>(av_malloc(AVPLAYER_AVIO_BUFFER_SIZE));
|
||||
m_avio_context =
|
||||
avio_alloc_context(avio_buffer, AVPLAYER_AVIO_BUFFER_SIZE, 0, this,
|
||||
&AvPlayerFileStreamer::ReadPacket, nullptr, &AvPlayerFileStreamer::Seek);
|
||||
}
|
||||
|
||||
AvPlayerFileStreamer::~AvPlayerFileStreamer() {
|
||||
if (m_avio_context != nullptr) {
|
||||
avio_context_free(&m_avio_context);
|
||||
}
|
||||
if (m_file_replacement.close != nullptr && m_fd >= 0) {
|
||||
const auto close = m_file_replacement.close;
|
||||
const auto ptr = m_file_replacement.object_ptr;
|
||||
close(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
s32 AvPlayerFileStreamer::ReadPacket(void* opaque, u8* buffer, s32 size) {
|
||||
const auto self = reinterpret_cast<AvPlayerFileStreamer*>(opaque);
|
||||
if (self->m_position >= self->m_file_size) {
|
||||
return AVERROR_EOF;
|
||||
}
|
||||
if (self->m_position + size > self->m_file_size) {
|
||||
size = self->m_file_size - self->m_position;
|
||||
}
|
||||
const auto read_offset = self->m_file_replacement.readOffset;
|
||||
const auto ptr = self->m_file_replacement.object_ptr;
|
||||
const auto bytes_read = read_offset(ptr, buffer, self->m_position, size);
|
||||
if (bytes_read == 0 && size != 0) {
|
||||
return AVERROR_EOF;
|
||||
}
|
||||
self->m_position += bytes_read;
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
s64 AvPlayerFileStreamer::Seek(void* opaque, s64 offset, int whence) {
|
||||
const auto self = reinterpret_cast<AvPlayerFileStreamer*>(opaque);
|
||||
if (whence & AVSEEK_SIZE) {
|
||||
return self->m_file_size;
|
||||
}
|
||||
|
||||
if (whence == SEEK_CUR) {
|
||||
self->m_position =
|
||||
std::min(u64(std::max(s64(0), s64(self->m_position) + offset)), self->m_file_size);
|
||||
return self->m_position;
|
||||
} else if (whence == SEEK_SET) {
|
||||
self->m_position = std::min(u64(std::max(s64(0), offset)), self->m_file_size);
|
||||
return self->m_position;
|
||||
} else if (whence == SEEK_END) {
|
||||
self->m_position =
|
||||
std::min(u64(std::max(s64(0), s64(self->m_file_size) + offset)), self->m_file_size);
|
||||
return self->m_position;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
} // namespace Libraries::AvPlayer
|
|
@ -0,0 +1,37 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "avplayer.h"
|
||||
#include "avplayer_data_streamer.h"
|
||||
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
struct AVIOContext;
|
||||
|
||||
namespace Libraries::AvPlayer {
|
||||
|
||||
class AvPlayerFileStreamer : public IDataStreamer {
|
||||
public:
|
||||
AvPlayerFileStreamer(const SceAvPlayerFileReplacement& file_replacement, std::string_view path);
|
||||
~AvPlayerFileStreamer();
|
||||
|
||||
AVIOContext* GetContext() override {
|
||||
return m_avio_context;
|
||||
}
|
||||
|
||||
private:
|
||||
static s32 ReadPacket(void* opaque, u8* buffer, s32 size);
|
||||
static s64 Seek(void* opaque, s64 buffer, int whence);
|
||||
|
||||
SceAvPlayerFileReplacement m_file_replacement;
|
||||
|
||||
int m_fd = -1;
|
||||
u64 m_position{};
|
||||
u64 m_file_size{};
|
||||
AVIOContext* m_avio_context{};
|
||||
};
|
||||
|
||||
} // namespace Libraries::AvPlayer
|
|
@ -0,0 +1,200 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "avplayer_common.h"
|
||||
#include "avplayer_file_streamer.h"
|
||||
#include "avplayer_impl.h"
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/kernel/libkernel.h"
|
||||
|
||||
using namespace Libraries::Kernel;
|
||||
|
||||
namespace Libraries::AvPlayer {
|
||||
|
||||
void* PS4_SYSV_ABI AvPlayer::Allocate(void* handle, u32 alignment, u32 size) {
|
||||
const auto* const self = reinterpret_cast<AvPlayer*>(handle);
|
||||
const auto allocate = self->m_init_data_original.memory_replacement.allocate;
|
||||
const auto ptr = self->m_init_data_original.memory_replacement.object_ptr;
|
||||
return allocate(ptr, alignment, size);
|
||||
}
|
||||
|
||||
void PS4_SYSV_ABI AvPlayer::Deallocate(void* handle, void* memory) {
|
||||
const auto* const self = reinterpret_cast<AvPlayer*>(handle);
|
||||
const auto deallocate = self->m_init_data_original.memory_replacement.deallocate;
|
||||
const auto ptr = self->m_init_data_original.memory_replacement.object_ptr;
|
||||
return deallocate(ptr, memory);
|
||||
}
|
||||
|
||||
void* PS4_SYSV_ABI AvPlayer::AllocateTexture(void* handle, u32 alignment, u32 size) {
|
||||
const auto* const self = reinterpret_cast<AvPlayer*>(handle);
|
||||
const auto allocate = self->m_init_data_original.memory_replacement.allocate_texture;
|
||||
const auto ptr = self->m_init_data_original.memory_replacement.object_ptr;
|
||||
return allocate(ptr, alignment, size);
|
||||
}
|
||||
|
||||
void PS4_SYSV_ABI AvPlayer::DeallocateTexture(void* handle, void* memory) {
|
||||
const auto* const self = reinterpret_cast<AvPlayer*>(handle);
|
||||
const auto deallocate = self->m_init_data_original.memory_replacement.deallocate_texture;
|
||||
const auto ptr = self->m_init_data_original.memory_replacement.object_ptr;
|
||||
return deallocate(ptr, memory);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI AvPlayer::OpenFile(void* handle, const char* filename) {
|
||||
auto const self = reinterpret_cast<AvPlayer*>(handle);
|
||||
std::lock_guard guard(self->m_file_io_mutex);
|
||||
|
||||
const auto open = self->m_init_data_original.file_replacement.open;
|
||||
const auto ptr = self->m_init_data_original.file_replacement.object_ptr;
|
||||
return open(ptr, filename);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI AvPlayer::CloseFile(void* handle) {
|
||||
auto const self = reinterpret_cast<AvPlayer*>(handle);
|
||||
std::lock_guard guard(self->m_file_io_mutex);
|
||||
|
||||
const auto close = self->m_init_data_original.file_replacement.close;
|
||||
const auto ptr = self->m_init_data_original.file_replacement.object_ptr;
|
||||
return close(ptr);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI AvPlayer::ReadOffsetFile(void* handle, u8* buffer, u64 position, u32 length) {
|
||||
auto const self = reinterpret_cast<AvPlayer*>(handle);
|
||||
std::lock_guard guard(self->m_file_io_mutex);
|
||||
|
||||
const auto read_offset = self->m_init_data_original.file_replacement.readOffset;
|
||||
const auto ptr = self->m_init_data_original.file_replacement.object_ptr;
|
||||
return read_offset(ptr, buffer, position, length);
|
||||
}
|
||||
|
||||
u64 PS4_SYSV_ABI AvPlayer::SizeFile(void* handle) {
|
||||
auto const self = reinterpret_cast<AvPlayer*>(handle);
|
||||
std::lock_guard guard(self->m_file_io_mutex);
|
||||
|
||||
const auto size = self->m_init_data_original.file_replacement.size;
|
||||
const auto ptr = self->m_init_data_original.file_replacement.object_ptr;
|
||||
return size(ptr);
|
||||
}
|
||||
|
||||
SceAvPlayerInitData AvPlayer::StubInitData(const SceAvPlayerInitData& data) {
|
||||
SceAvPlayerInitData result = data;
|
||||
result.memory_replacement.object_ptr = this;
|
||||
result.memory_replacement.allocate = &AvPlayer::Allocate;
|
||||
result.memory_replacement.deallocate = &AvPlayer::Deallocate;
|
||||
result.memory_replacement.allocate_texture = &AvPlayer::AllocateTexture;
|
||||
result.memory_replacement.deallocate_texture = &AvPlayer::DeallocateTexture;
|
||||
if (data.file_replacement.open == nullptr || data.file_replacement.close == nullptr ||
|
||||
data.file_replacement.readOffset == nullptr || data.file_replacement.size == nullptr) {
|
||||
result.file_replacement = {};
|
||||
} else {
|
||||
result.file_replacement.object_ptr = this;
|
||||
result.file_replacement.open = &AvPlayer::OpenFile;
|
||||
result.file_replacement.close = &AvPlayer::CloseFile;
|
||||
result.file_replacement.readOffset = &AvPlayer::ReadOffsetFile;
|
||||
result.file_replacement.size = &AvPlayer::SizeFile;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
AvPlayer::AvPlayer(const SceAvPlayerInitData& data)
|
||||
: m_init_data(StubInitData(data)), m_init_data_original(data),
|
||||
m_state(std::make_unique<AvPlayerState>(m_init_data)) {}
|
||||
|
||||
s32 AvPlayer::PostInit(const SceAvPlayerPostInitData& data) {
|
||||
m_post_init_data = data;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 AvPlayer::AddSource(std::string_view path) {
|
||||
if (path.empty()) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
if (AVPLAYER_IS_ERROR(m_state->AddSource(path, GetSourceType(path)))) {
|
||||
return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 AvPlayer::GetStreamCount() {
|
||||
if (m_state == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED;
|
||||
}
|
||||
const auto res = m_state->GetStreamCount();
|
||||
if (AVPLAYER_IS_ERROR(res)) {
|
||||
return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
s32 AvPlayer::GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info) {
|
||||
if (AVPLAYER_IS_ERROR(m_state->GetStreamInfo(stream_index, info))) {
|
||||
return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 AvPlayer::EnableStream(u32 stream_index) {
|
||||
if (m_state == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED;
|
||||
}
|
||||
if (!m_state->EnableStream(stream_index)) {
|
||||
return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 AvPlayer::Start() {
|
||||
return m_state->Start();
|
||||
}
|
||||
|
||||
bool AvPlayer::GetVideoData(SceAvPlayerFrameInfo& video_info) {
|
||||
if (m_state == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return m_state->GetVideoData(video_info);
|
||||
}
|
||||
|
||||
bool AvPlayer::GetVideoData(SceAvPlayerFrameInfoEx& video_info) {
|
||||
if (m_state == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return m_state->GetVideoData(video_info);
|
||||
}
|
||||
|
||||
bool AvPlayer::GetAudioData(SceAvPlayerFrameInfo& audio_info) {
|
||||
if (m_state == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return m_state->GetAudioData(audio_info);
|
||||
}
|
||||
|
||||
bool AvPlayer::IsActive() {
|
||||
if (m_state == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return m_state->IsActive();
|
||||
}
|
||||
|
||||
u64 AvPlayer::CurrentTime() {
|
||||
if (m_state == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
return m_state->CurrentTime();
|
||||
}
|
||||
|
||||
s32 AvPlayer::Stop() {
|
||||
if (m_state == nullptr || !m_state->Stop()) {
|
||||
return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
bool AvPlayer::SetLooping(bool is_looping) {
|
||||
if (m_state == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return m_state->SetLooping(is_looping);
|
||||
}
|
||||
|
||||
} // namespace Libraries::AvPlayer
|
|
@ -0,0 +1,68 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "avplayer.h"
|
||||
#include "avplayer_data_streamer.h"
|
||||
#include "avplayer_state.h"
|
||||
|
||||
#include "core/libraries/kernel/thread_management.h"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
}
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Libraries::AvPlayer {
|
||||
|
||||
class AvPlayer {
|
||||
public:
|
||||
AvPlayer(const SceAvPlayerInitData& data);
|
||||
|
||||
s32 PostInit(const SceAvPlayerPostInitData& data);
|
||||
s32 AddSource(std::string_view filename);
|
||||
s32 GetStreamCount();
|
||||
s32 GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info);
|
||||
s32 EnableStream(u32 stream_index);
|
||||
s32 Start();
|
||||
bool GetAudioData(SceAvPlayerFrameInfo& audio_info);
|
||||
bool GetVideoData(SceAvPlayerFrameInfo& video_info);
|
||||
bool GetVideoData(SceAvPlayerFrameInfoEx& video_info);
|
||||
bool IsActive();
|
||||
u64 CurrentTime();
|
||||
s32 Stop();
|
||||
bool SetLooping(bool is_looping);
|
||||
|
||||
private:
|
||||
using ScePthreadMutex = Kernel::ScePthreadMutex;
|
||||
|
||||
// Memory Replacement
|
||||
static void* PS4_SYSV_ABI Allocate(void* handle, u32 alignment, u32 size);
|
||||
static void PS4_SYSV_ABI Deallocate(void* handle, void* memory);
|
||||
static void* PS4_SYSV_ABI AllocateTexture(void* handle, u32 alignment, u32 size);
|
||||
static void PS4_SYSV_ABI DeallocateTexture(void* handle, void* memory);
|
||||
|
||||
// File Replacement
|
||||
static int PS4_SYSV_ABI OpenFile(void* handle, const char* filename);
|
||||
static int PS4_SYSV_ABI CloseFile(void* handle);
|
||||
static int PS4_SYSV_ABI ReadOffsetFile(void* handle, u8* buffer, u64 position, u32 length);
|
||||
static u64 PS4_SYSV_ABI SizeFile(void* handle);
|
||||
|
||||
SceAvPlayerInitData StubInitData(const SceAvPlayerInitData& data);
|
||||
|
||||
SceAvPlayerInitData m_init_data{};
|
||||
SceAvPlayerInitData m_init_data_original{};
|
||||
SceAvPlayerPostInitData m_post_init_data{};
|
||||
std::mutex m_file_io_mutex{};
|
||||
|
||||
std::atomic_bool m_has_source{};
|
||||
std::unique_ptr<AvPlayerState> m_state{};
|
||||
};
|
||||
|
||||
} // namespace Libraries::AvPlayer
|
|
@ -0,0 +1,730 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "avplayer_source.h"
|
||||
|
||||
#include "avplayer_file_streamer.h"
|
||||
|
||||
#include "common/singleton.h"
|
||||
#include "core/file_sys/fs.h"
|
||||
#include "core/libraries/kernel/time_management.h"
|
||||
|
||||
#include <magic_enum.hpp>
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavformat/avio.h>
|
||||
#include <libswresample/swresample.h>
|
||||
#include <libswscale/swscale.h>
|
||||
}
|
||||
|
||||
namespace Libraries::AvPlayer {
|
||||
|
||||
using namespace Kernel;
|
||||
|
||||
AvPlayerSource::AvPlayerSource(AvPlayerStateCallback& state, std::string_view path,
|
||||
const SceAvPlayerInitData& init_data,
|
||||
SceAvPlayerSourceType source_type)
|
||||
: m_state(state), m_memory_replacement(init_data.memory_replacement),
|
||||
m_num_output_video_framebuffers(
|
||||
std::min(std::max(2, init_data.num_output_video_framebuffers), 16)) {
|
||||
AVFormatContext* context = avformat_alloc_context();
|
||||
if (init_data.file_replacement.open != nullptr) {
|
||||
m_up_data_streamer =
|
||||
std::make_unique<AvPlayerFileStreamer>(init_data.file_replacement, path);
|
||||
context->pb = m_up_data_streamer->GetContext();
|
||||
ASSERT(!AVPLAYER_IS_ERROR(avformat_open_input(&context, nullptr, nullptr, nullptr)));
|
||||
} else {
|
||||
const auto mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
const auto filepath = mnt->GetHostPath(path);
|
||||
ASSERT(!AVPLAYER_IS_ERROR(
|
||||
avformat_open_input(&context, filepath.string().c_str(), nullptr, nullptr)));
|
||||
}
|
||||
m_avformat_context = AVFormatContextPtr(context, &ReleaseAVFormatContext);
|
||||
}
|
||||
|
||||
AvPlayerSource::~AvPlayerSource() {
|
||||
Stop();
|
||||
}
|
||||
|
||||
bool AvPlayerSource::FindStreamInfo() {
|
||||
if (m_avformat_context == nullptr) {
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not find stream info. NULL context.");
|
||||
return false;
|
||||
}
|
||||
if (m_avformat_context->nb_streams > 0) {
|
||||
return true;
|
||||
}
|
||||
return avformat_find_stream_info(m_avformat_context.get(), nullptr) == 0;
|
||||
}
|
||||
|
||||
s32 AvPlayerSource::GetStreamCount() {
|
||||
if (m_avformat_context == nullptr) {
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not get stream count. NULL context.");
|
||||
return -1;
|
||||
}
|
||||
LOG_INFO(Lib_AvPlayer, "Stream Count: {}", m_avformat_context->nb_streams);
|
||||
return m_avformat_context->nb_streams;
|
||||
}
|
||||
|
||||
static s32 CodecTypeToStreamType(AVMediaType codec_type) {
|
||||
switch (codec_type) {
|
||||
case AVMediaType::AVMEDIA_TYPE_VIDEO:
|
||||
return SCE_AVPLAYER_VIDEO;
|
||||
case AVMediaType::AVMEDIA_TYPE_AUDIO:
|
||||
return SCE_AVPLAYER_AUDIO;
|
||||
case AVMediaType::AVMEDIA_TYPE_SUBTITLE:
|
||||
return SCE_AVPLAYER_TIMEDTEXT;
|
||||
default:
|
||||
LOG_ERROR(Lib_AvPlayer, "Unexpected AVMediaType {}", magic_enum::enum_name(codec_type));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static f32 AVRationalToF32(const AVRational& rational) {
|
||||
return f32(rational.num) / rational.den;
|
||||
}
|
||||
|
||||
s32 AvPlayerSource::GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info) {
|
||||
info = {};
|
||||
if (m_avformat_context == nullptr || stream_index >= m_avformat_context->nb_streams) {
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not get stream {} info.", stream_index);
|
||||
return -1;
|
||||
}
|
||||
const auto p_stream = m_avformat_context->streams[stream_index];
|
||||
if (p_stream == nullptr || p_stream->codecpar == nullptr) {
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not get stream {} info. NULL stream.", stream_index);
|
||||
return -1;
|
||||
}
|
||||
info.type = CodecTypeToStreamType(p_stream->codecpar->codec_type);
|
||||
info.start_time = p_stream->start_time;
|
||||
info.duration = p_stream->duration;
|
||||
const auto p_lang_node = av_dict_get(p_stream->metadata, "language", nullptr, 0);
|
||||
if (p_lang_node != nullptr) {
|
||||
LOG_INFO(Lib_AvPlayer, "Stream {} language = {}", stream_index, p_lang_node->value);
|
||||
} else {
|
||||
LOG_WARNING(Lib_AvPlayer, "Stream {} language is unknown", stream_index);
|
||||
}
|
||||
switch (info.type) {
|
||||
case SCE_AVPLAYER_VIDEO:
|
||||
LOG_INFO(Lib_AvPlayer, "Stream {} is a video stream.", stream_index);
|
||||
info.details.video.aspect_ratio =
|
||||
f32(p_stream->codecpar->width) / p_stream->codecpar->height;
|
||||
info.details.video.width = p_stream->codecpar->width;
|
||||
info.details.video.height = p_stream->codecpar->height;
|
||||
if (p_lang_node != nullptr) {
|
||||
std::memcpy(info.details.video.language_code, p_lang_node->value,
|
||||
std::min(strlen(p_lang_node->value), size_t(3)));
|
||||
}
|
||||
break;
|
||||
case SCE_AVPLAYER_AUDIO:
|
||||
LOG_INFO(Lib_AvPlayer, "Stream {} is an audio stream.", stream_index);
|
||||
info.details.audio.channel_count = p_stream->codecpar->ch_layout.nb_channels;
|
||||
info.details.audio.sample_rate = p_stream->codecpar->sample_rate;
|
||||
info.details.audio.size = 0; // sceAvPlayerGetStreamInfo() is expected to set this to 0
|
||||
if (p_lang_node != nullptr) {
|
||||
std::memcpy(info.details.audio.language_code, p_lang_node->value,
|
||||
std::min(strlen(p_lang_node->value), size_t(3)));
|
||||
}
|
||||
break;
|
||||
case SCE_AVPLAYER_TIMEDTEXT:
|
||||
LOG_WARNING(Lib_AvPlayer, "Stream {} is a timedtext stream.", stream_index);
|
||||
info.details.subs.font_size = 12;
|
||||
info.details.subs.text_size = 12;
|
||||
if (p_lang_node != nullptr) {
|
||||
std::memcpy(info.details.subs.language_code, p_lang_node->value,
|
||||
std::min(strlen(p_lang_node->value), size_t(3)));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(Lib_AvPlayer, "Stream {} type is unknown: {}.", stream_index, info.type);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool AvPlayerSource::EnableStream(u32 stream_index) {
|
||||
if (m_avformat_context == nullptr || stream_index >= m_avformat_context->nb_streams) {
|
||||
return false;
|
||||
}
|
||||
const auto stream = m_avformat_context->streams[stream_index];
|
||||
const auto decoder = avcodec_find_decoder(stream->codecpar->codec_id);
|
||||
if (decoder == nullptr) {
|
||||
return false;
|
||||
}
|
||||
switch (stream->codecpar->codec_type) {
|
||||
case AVMediaType::AVMEDIA_TYPE_VIDEO: {
|
||||
m_video_stream_index = stream_index;
|
||||
m_video_codec_context =
|
||||
AVCodecContextPtr(avcodec_alloc_context3(decoder), &ReleaseAVCodecContext);
|
||||
if (avcodec_parameters_to_context(m_video_codec_context.get(), stream->codecpar) < 0) {
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not copy stream {} avcodec parameters to context.",
|
||||
stream_index);
|
||||
return false;
|
||||
}
|
||||
if (avcodec_open2(m_video_codec_context.get(), decoder, nullptr) < 0) {
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not open avcodec for video stream {}.", stream_index);
|
||||
return false;
|
||||
}
|
||||
const auto width = m_video_codec_context->width;
|
||||
const auto size = (width * m_video_codec_context->height * 3) / 2;
|
||||
for (u64 index = 0; index < m_num_output_video_framebuffers; ++index) {
|
||||
m_video_buffers.Push(FrameBuffer(m_memory_replacement, 0x100, size));
|
||||
}
|
||||
LOG_INFO(Lib_AvPlayer, "Video stream {} enabled", stream_index);
|
||||
break;
|
||||
}
|
||||
case AVMediaType::AVMEDIA_TYPE_AUDIO: {
|
||||
m_audio_stream_index = stream_index;
|
||||
m_audio_codec_context =
|
||||
AVCodecContextPtr(avcodec_alloc_context3(decoder), &ReleaseAVCodecContext);
|
||||
if (avcodec_parameters_to_context(m_audio_codec_context.get(), stream->codecpar) < 0) {
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not copy stream {} avcodec parameters to context.",
|
||||
stream_index);
|
||||
return false;
|
||||
}
|
||||
if (avcodec_open2(m_audio_codec_context.get(), decoder, nullptr) < 0) {
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not open avcodec for audio stream {}.", stream_index);
|
||||
return false;
|
||||
}
|
||||
const auto num_channels = m_audio_codec_context->ch_layout.nb_channels;
|
||||
const auto align = num_channels * sizeof(u16);
|
||||
const auto size = num_channels * sizeof(u16) * 1024;
|
||||
for (u64 index = 0; index < 4; ++index) {
|
||||
m_audio_buffers.Push(FrameBuffer(m_memory_replacement, 0x100, size));
|
||||
}
|
||||
LOG_INFO(Lib_AvPlayer, "Audio stream {} enabled", stream_index);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG_WARNING(Lib_AvPlayer, "Unknown stream type {} for stream {}",
|
||||
magic_enum::enum_name(stream->codecpar->codec_type), stream_index);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void AvPlayerSource::SetLooping(bool is_looping) {
|
||||
m_is_looping = is_looping;
|
||||
}
|
||||
|
||||
std::optional<bool> AvPlayerSource::HasFrames(u32 num_frames) {
|
||||
return m_video_packets.Size() > num_frames || m_is_eof;
|
||||
}
|
||||
|
||||
s32 AvPlayerSource::Start() {
|
||||
std::unique_lock lock(m_state_mutex);
|
||||
|
||||
if (m_audio_codec_context == nullptr && m_video_codec_context == nullptr) {
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not start playback. NULL context.");
|
||||
return -1;
|
||||
}
|
||||
m_demuxer_thread = std::jthread([this](std::stop_token stop) { this->DemuxerThread(stop); });
|
||||
m_video_decoder_thread =
|
||||
std::jthread([this](std::stop_token stop) { this->VideoDecoderThread(stop); });
|
||||
m_audio_decoder_thread =
|
||||
std::jthread([this](std::stop_token stop) { this->AudioDecoderThread(stop); });
|
||||
m_start_time = std::chrono::high_resolution_clock::now();
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool AvPlayerSource::Stop() {
|
||||
std::unique_lock lock(m_state_mutex);
|
||||
|
||||
if (!HasRunningThreads()) {
|
||||
LOG_WARNING(Lib_AvPlayer, "Could not stop playback: already stopped.");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_video_decoder_thread.request_stop();
|
||||
m_audio_decoder_thread.request_stop();
|
||||
m_demuxer_thread.request_stop();
|
||||
if (m_demuxer_thread.joinable()) {
|
||||
m_demuxer_thread.join();
|
||||
}
|
||||
if (m_video_decoder_thread.joinable()) {
|
||||
m_video_decoder_thread.join();
|
||||
}
|
||||
if (m_audio_decoder_thread.joinable()) {
|
||||
m_audio_decoder_thread.join();
|
||||
}
|
||||
if (m_current_audio_frame.has_value()) {
|
||||
m_audio_buffers.Push(std::move(m_current_audio_frame.value()));
|
||||
m_current_audio_frame.reset();
|
||||
}
|
||||
if (m_current_video_frame.has_value()) {
|
||||
m_video_buffers.Push(std::move(m_current_video_frame.value()));
|
||||
m_current_video_frame.reset();
|
||||
}
|
||||
m_stop_cv.Notify();
|
||||
|
||||
m_audio_packets.Clear();
|
||||
m_video_packets.Clear();
|
||||
m_audio_frames.Clear();
|
||||
m_video_frames.Clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AvPlayerSource::GetVideoData(SceAvPlayerFrameInfo& video_info) {
|
||||
if (!IsActive()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SceAvPlayerFrameInfoEx info{};
|
||||
if (!GetVideoData(info)) {
|
||||
return false;
|
||||
}
|
||||
video_info = {};
|
||||
video_info.timestamp = u64(info.timestamp);
|
||||
video_info.pData = reinterpret_cast<u8*>(info.pData);
|
||||
video_info.details.video.aspect_ratio = info.details.video.aspect_ratio;
|
||||
video_info.details.video.width = info.details.video.width;
|
||||
video_info.details.video.height = info.details.video.height;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void CopyNV12Data(u8* dst, const AVFrame& src) {
|
||||
std::memcpy(dst, src.data[0], src.width * src.height);
|
||||
std::memcpy(dst + src.width * src.height, src.data[1], (src.width * src.height) / 2);
|
||||
}
|
||||
|
||||
bool AvPlayerSource::GetVideoData(SceAvPlayerFrameInfoEx& video_info) {
|
||||
if (!IsActive()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_video_frames_cv.Wait([this] { return m_video_frames.Size() != 0 || m_is_eof; });
|
||||
|
||||
auto frame = m_video_frames.Pop();
|
||||
if (!frame.has_value()) {
|
||||
LOG_WARNING(Lib_AvPlayer, "Could get video frame. EOF reached.");
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
using namespace std::chrono;
|
||||
auto elapsed_time =
|
||||
duration_cast<milliseconds>(high_resolution_clock::now() - m_start_time).count();
|
||||
if (elapsed_time < frame->info.timestamp) {
|
||||
if (m_stop_cv.WaitFor(milliseconds(frame->info.timestamp - elapsed_time),
|
||||
[&] { return elapsed_time >= frame->info.timestamp; })) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// return the buffer to the queue
|
||||
if (m_current_video_frame.has_value()) {
|
||||
m_video_buffers.Push(std::move(m_current_video_frame.value()));
|
||||
m_video_buffers_cv.Notify();
|
||||
}
|
||||
m_current_video_frame = std::move(frame->buffer);
|
||||
video_info = frame->info;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AvPlayerSource::GetAudioData(SceAvPlayerFrameInfo& audio_info) {
|
||||
if (!IsActive()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_audio_frames_cv.Wait([this] { return m_audio_frames.Size() != 0 || m_is_eof; });
|
||||
|
||||
auto frame = m_audio_frames.Pop();
|
||||
if (!frame.has_value()) {
|
||||
LOG_WARNING(Lib_AvPlayer, "Could get audio frame. EOF reached.");
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
using namespace std::chrono;
|
||||
auto elapsed_time =
|
||||
duration_cast<milliseconds>(high_resolution_clock::now() - m_start_time).count();
|
||||
if (elapsed_time < frame->info.timestamp) {
|
||||
if (m_stop_cv.WaitFor(milliseconds(frame->info.timestamp - elapsed_time),
|
||||
[&] { return elapsed_time >= frame->info.timestamp; })) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// return the buffer to the queue
|
||||
if (m_current_audio_frame.has_value()) {
|
||||
m_audio_buffers.Push(std::move(m_current_audio_frame.value()));
|
||||
m_audio_buffers_cv.Notify();
|
||||
}
|
||||
m_current_audio_frame = std::move(frame->buffer);
|
||||
|
||||
audio_info = {};
|
||||
audio_info.timestamp = frame->info.timestamp;
|
||||
audio_info.pData = reinterpret_cast<u8*>(frame->info.pData);
|
||||
audio_info.details.audio.size = frame->info.details.audio.size;
|
||||
audio_info.details.audio.channel_count = frame->info.details.audio.channel_count;
|
||||
return true;
|
||||
}
|
||||
|
||||
u64 AvPlayerSource::CurrentTime() {
|
||||
using namespace std::chrono;
|
||||
return duration_cast<milliseconds>(high_resolution_clock::now() - m_start_time).count();
|
||||
}
|
||||
|
||||
bool AvPlayerSource::IsActive() {
|
||||
return !m_is_eof || m_audio_packets.Size() != 0 || m_video_packets.Size() != 0 ||
|
||||
m_video_frames.Size() != 0 || m_audio_frames.Size() != 0;
|
||||
}
|
||||
|
||||
void AvPlayerSource::ReleaseAVPacket(AVPacket* packet) {
|
||||
if (packet != nullptr) {
|
||||
av_packet_free(&packet);
|
||||
}
|
||||
}
|
||||
|
||||
void AvPlayerSource::ReleaseAVFrame(AVFrame* frame) {
|
||||
if (frame != nullptr) {
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
}
|
||||
|
||||
void AvPlayerSource::ReleaseAVCodecContext(AVCodecContext* context) {
|
||||
if (context != nullptr) {
|
||||
avcodec_free_context(&context);
|
||||
}
|
||||
}
|
||||
|
||||
void AvPlayerSource::ReleaseSWRContext(SwrContext* context) {
|
||||
if (context != nullptr) {
|
||||
swr_free(&context);
|
||||
}
|
||||
}
|
||||
|
||||
void AvPlayerSource::ReleaseSWSContext(SwsContext* context) {
|
||||
if (context != nullptr) {
|
||||
sws_freeContext(context);
|
||||
}
|
||||
}
|
||||
|
||||
void AvPlayerSource::ReleaseAVFormatContext(AVFormatContext* context) {
|
||||
if (context != nullptr) {
|
||||
avformat_close_input(&context);
|
||||
}
|
||||
}
|
||||
|
||||
void AvPlayerSource::DemuxerThread(std::stop_token stop) {
|
||||
using namespace std::chrono;
|
||||
if (!m_audio_stream_index.has_value() && !m_video_stream_index.has_value()) {
|
||||
LOG_WARNING(Lib_AvPlayer, "Could not start DEMUXER thread. No streams enabled.");
|
||||
return;
|
||||
}
|
||||
LOG_INFO(Lib_AvPlayer, "Demuxer Thread started");
|
||||
|
||||
while (!stop.stop_requested()) {
|
||||
if (m_video_packets.Size() > 30 && m_audio_packets.Size() > 8) {
|
||||
std::this_thread::sleep_for(milliseconds(5));
|
||||
continue;
|
||||
}
|
||||
AVPacketPtr up_packet(av_packet_alloc(), &ReleaseAVPacket);
|
||||
const auto res = av_read_frame(m_avformat_context.get(), up_packet.get());
|
||||
if (res < 0) {
|
||||
if (res == AVERROR_EOF) {
|
||||
if (m_is_looping) {
|
||||
LOG_INFO(Lib_AvPlayer, "EOF reached in demuxer. Looping the source...");
|
||||
avio_seek(m_avformat_context->pb, 0, SEEK_SET);
|
||||
if (m_video_stream_index.has_value()) {
|
||||
const auto index = m_video_stream_index.value();
|
||||
const auto stream = m_avformat_context->streams[index];
|
||||
avformat_seek_file(m_avformat_context.get(), index, 0, 0, stream->duration,
|
||||
0);
|
||||
}
|
||||
if (m_audio_stream_index.has_value()) {
|
||||
const auto index = m_audio_stream_index.value();
|
||||
const auto stream = m_avformat_context->streams[index];
|
||||
avformat_seek_file(m_avformat_context.get(), index, 0, 0, stream->duration,
|
||||
0);
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
LOG_INFO(Lib_AvPlayer, "EOF reached in demuxer. Exiting.");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not read AV frame: error = {}", res);
|
||||
m_state.OnError();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (up_packet->stream_index == m_video_stream_index) {
|
||||
m_video_packets.Push(std::move(up_packet));
|
||||
m_video_packets_cv.Notify();
|
||||
} else if (up_packet->stream_index == m_audio_stream_index) {
|
||||
m_audio_packets.Push(std::move(up_packet));
|
||||
m_audio_packets_cv.Notify();
|
||||
}
|
||||
}
|
||||
|
||||
m_is_eof = true;
|
||||
|
||||
m_video_packets_cv.Notify();
|
||||
m_audio_packets_cv.Notify();
|
||||
m_video_frames_cv.Notify();
|
||||
m_audio_frames_cv.Notify();
|
||||
|
||||
if (m_video_decoder_thread.joinable()) {
|
||||
m_video_decoder_thread.join();
|
||||
}
|
||||
if (m_audio_decoder_thread.joinable()) {
|
||||
m_audio_decoder_thread.join();
|
||||
}
|
||||
m_state.OnEOF();
|
||||
|
||||
LOG_INFO(Lib_AvPlayer, "Demuxer Thread exited normaly");
|
||||
}
|
||||
|
||||
AvPlayerSource::AVFramePtr AvPlayerSource::ConvertVideoFrame(const AVFrame& frame) {
|
||||
auto nv12_frame = AVFramePtr{av_frame_alloc(), &ReleaseAVFrame};
|
||||
nv12_frame->pts = frame.pts;
|
||||
nv12_frame->pkt_dts = frame.pkt_dts < 0 ? 0 : frame.pkt_dts;
|
||||
nv12_frame->format = AV_PIX_FMT_NV12;
|
||||
nv12_frame->width = frame.width;
|
||||
nv12_frame->height = frame.height;
|
||||
nv12_frame->sample_aspect_ratio = frame.sample_aspect_ratio;
|
||||
|
||||
av_frame_get_buffer(nv12_frame.get(), 0);
|
||||
|
||||
if (m_sws_context == nullptr) {
|
||||
m_sws_context =
|
||||
SWSContextPtr(sws_getContext(frame.width, frame.height, AVPixelFormat(frame.format),
|
||||
frame.width, frame.height, AV_PIX_FMT_NV12,
|
||||
SWS_FAST_BILINEAR, nullptr, nullptr, nullptr),
|
||||
&ReleaseSWSContext);
|
||||
}
|
||||
const auto res = sws_scale(m_sws_context.get(), frame.data, frame.linesize, 0, frame.height,
|
||||
nv12_frame->data, nv12_frame->linesize);
|
||||
if (res < 0) {
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not convert to NV12: {}", av_err2str(res));
|
||||
return AVFramePtr{nullptr, &ReleaseAVFrame};
|
||||
}
|
||||
return nv12_frame;
|
||||
}
|
||||
|
||||
Frame AvPlayerSource::PrepareVideoFrame(FrameBuffer buffer, const AVFrame& frame) {
|
||||
ASSERT(frame.format == AV_PIX_FMT_NV12);
|
||||
|
||||
auto p_buffer = buffer.GetBuffer();
|
||||
CopyNV12Data(p_buffer, frame);
|
||||
|
||||
const auto pkt_dts = u64(frame.pkt_dts) * 1000;
|
||||
const auto stream = m_avformat_context->streams[m_video_stream_index.value()];
|
||||
const auto time_base = stream->time_base;
|
||||
const auto den = time_base.den;
|
||||
const auto num = time_base.num;
|
||||
const auto timestamp = (num != 0 && den > 1) ? (pkt_dts * num) / den : pkt_dts;
|
||||
|
||||
return Frame{
|
||||
.buffer = std::move(buffer),
|
||||
.info =
|
||||
{
|
||||
.pData = p_buffer,
|
||||
.timestamp = timestamp,
|
||||
.details =
|
||||
{
|
||||
.video =
|
||||
{
|
||||
.width = u32(frame.width),
|
||||
.height = u32(frame.height),
|
||||
.aspect_ratio = AVRationalToF32(frame.sample_aspect_ratio),
|
||||
.pitch = u32(frame.linesize[0]),
|
||||
.luma_bit_depth = 8,
|
||||
.chroma_bit_depth = 8,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
void AvPlayerSource::VideoDecoderThread(std::stop_token stop) {
|
||||
using namespace std::chrono;
|
||||
LOG_INFO(Lib_AvPlayer, "Video Decoder Thread started");
|
||||
while ((!m_is_eof || m_video_packets.Size() != 0) && !stop.stop_requested()) {
|
||||
if (!m_video_packets_cv.Wait(stop,
|
||||
[this] { return m_video_packets.Size() != 0 || m_is_eof; })) {
|
||||
continue;
|
||||
}
|
||||
const auto packet = m_video_packets.Pop();
|
||||
if (!packet.has_value()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto res = avcodec_send_packet(m_video_codec_context.get(), packet->get());
|
||||
if (res < 0 && res != AVERROR(EAGAIN)) {
|
||||
m_state.OnError();
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not send packet to the video codec. Error = {}",
|
||||
av_err2str(res));
|
||||
return;
|
||||
}
|
||||
while (res >= 0) {
|
||||
if (!m_video_buffers_cv.Wait(stop, [this] { return m_video_buffers.Size() != 0; })) {
|
||||
break;
|
||||
}
|
||||
if (m_video_buffers.Size() == 0) {
|
||||
continue;
|
||||
}
|
||||
auto up_frame = AVFramePtr(av_frame_alloc(), &ReleaseAVFrame);
|
||||
res = avcodec_receive_frame(m_video_codec_context.get(), up_frame.get());
|
||||
if (res < 0) {
|
||||
if (res == AVERROR_EOF) {
|
||||
LOG_INFO(Lib_AvPlayer, "EOF reached in video decoder");
|
||||
return;
|
||||
} else if (res != AVERROR(EAGAIN)) {
|
||||
LOG_ERROR(Lib_AvPlayer,
|
||||
"Could not receive frame from the video codec. Error = {}",
|
||||
av_err2str(res));
|
||||
m_state.OnError();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
auto buffer = m_video_buffers.Pop();
|
||||
if (!buffer.has_value()) {
|
||||
// Video buffers queue was cleared. This means that player was stopped.
|
||||
break;
|
||||
}
|
||||
if (up_frame->format != AV_PIX_FMT_NV12) {
|
||||
const auto nv12_frame = ConvertVideoFrame(*up_frame);
|
||||
m_video_frames.Push(PrepareVideoFrame(std::move(buffer.value()), *nv12_frame));
|
||||
} else {
|
||||
m_video_frames.Push(PrepareVideoFrame(std::move(buffer.value()), *up_frame));
|
||||
}
|
||||
m_video_frames_cv.Notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LOG_INFO(Lib_AvPlayer, "Video Decoder Thread exited normaly");
|
||||
}
|
||||
|
||||
AvPlayerSource::AVFramePtr AvPlayerSource::ConvertAudioFrame(const AVFrame& frame) {
|
||||
auto pcm16_frame = AVFramePtr{av_frame_alloc(), &ReleaseAVFrame};
|
||||
pcm16_frame->pts = frame.pts;
|
||||
pcm16_frame->pkt_dts = frame.pkt_dts < 0 ? 0 : frame.pkt_dts;
|
||||
pcm16_frame->format = AV_SAMPLE_FMT_S16;
|
||||
pcm16_frame->ch_layout = frame.ch_layout;
|
||||
pcm16_frame->sample_rate = frame.sample_rate;
|
||||
|
||||
if (m_swr_context == nullptr) {
|
||||
SwrContext* swr_context = nullptr;
|
||||
AVChannelLayout in_ch_layout = frame.ch_layout;
|
||||
AVChannelLayout out_ch_layout = frame.ch_layout;
|
||||
swr_alloc_set_opts2(&swr_context, &out_ch_layout, AV_SAMPLE_FMT_S16, frame.sample_rate,
|
||||
&in_ch_layout, AVSampleFormat(frame.format), frame.sample_rate, 0,
|
||||
nullptr);
|
||||
m_swr_context = SWRContextPtr(swr_context, &ReleaseSWRContext);
|
||||
swr_init(m_swr_context.get());
|
||||
}
|
||||
const auto res = swr_convert_frame(m_swr_context.get(), pcm16_frame.get(), &frame);
|
||||
if (res < 0) {
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not convert to NV12: {}", av_err2str(res));
|
||||
return AVFramePtr{nullptr, &ReleaseAVFrame};
|
||||
}
|
||||
return pcm16_frame;
|
||||
}
|
||||
|
||||
Frame AvPlayerSource::PrepareAudioFrame(FrameBuffer buffer, const AVFrame& frame) {
|
||||
ASSERT(frame.format == AV_SAMPLE_FMT_S16);
|
||||
ASSERT(frame.nb_samples <= 1024);
|
||||
|
||||
auto p_buffer = buffer.GetBuffer();
|
||||
const auto size = frame.ch_layout.nb_channels * frame.nb_samples * sizeof(u16);
|
||||
std::memcpy(p_buffer, frame.data[0], size);
|
||||
|
||||
const auto pkt_dts = u64(frame.pkt_dts) * 1000;
|
||||
const auto stream = m_avformat_context->streams[m_audio_stream_index.value()];
|
||||
const auto time_base = stream->time_base;
|
||||
const auto den = time_base.den;
|
||||
const auto num = time_base.num;
|
||||
const auto timestamp = (num != 0 && den > 1) ? (pkt_dts * num) / den : pkt_dts;
|
||||
|
||||
return Frame{
|
||||
.buffer = std::move(buffer),
|
||||
.info =
|
||||
{
|
||||
.pData = p_buffer,
|
||||
.timestamp = timestamp,
|
||||
.details =
|
||||
{
|
||||
.audio =
|
||||
{
|
||||
.channel_count = u16(frame.ch_layout.nb_channels),
|
||||
.size = u32(size),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
void AvPlayerSource::AudioDecoderThread(std::stop_token stop) {
|
||||
using namespace std::chrono;
|
||||
LOG_INFO(Lib_AvPlayer, "Audio Decoder Thread started");
|
||||
while ((!m_is_eof || m_audio_packets.Size() != 0) && !stop.stop_requested()) {
|
||||
if (!m_audio_packets_cv.Wait(stop,
|
||||
[this] { return m_audio_packets.Size() != 0 || m_is_eof; })) {
|
||||
continue;
|
||||
}
|
||||
const auto packet = m_audio_packets.Pop();
|
||||
if (!packet.has_value()) {
|
||||
continue;
|
||||
}
|
||||
auto res = avcodec_send_packet(m_audio_codec_context.get(), packet->get());
|
||||
if (res < 0 && res != AVERROR(EAGAIN)) {
|
||||
m_state.OnError();
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not send packet to the audio codec. Error = {}",
|
||||
av_err2str(res));
|
||||
return;
|
||||
}
|
||||
while (res >= 0) {
|
||||
if (!m_audio_buffers_cv.Wait(stop, [this] { return m_audio_buffers.Size() != 0; })) {
|
||||
break;
|
||||
}
|
||||
if (m_audio_buffers.Size() == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto up_frame = AVFramePtr(av_frame_alloc(), &ReleaseAVFrame);
|
||||
res = avcodec_receive_frame(m_audio_codec_context.get(), up_frame.get());
|
||||
if (res < 0) {
|
||||
if (res == AVERROR_EOF) {
|
||||
LOG_INFO(Lib_AvPlayer, "EOF reached in audio decoder");
|
||||
return;
|
||||
} else if (res != AVERROR(EAGAIN)) {
|
||||
m_state.OnError();
|
||||
LOG_ERROR(Lib_AvPlayer,
|
||||
"Could not receive frame from the audio codec. Error = {}",
|
||||
av_err2str(res));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
auto buffer = m_audio_buffers.Pop();
|
||||
if (!buffer.has_value()) {
|
||||
// Audio buffers queue was cleared. This means that player was stopped.
|
||||
break;
|
||||
}
|
||||
if (up_frame->format != AV_SAMPLE_FMT_S16) {
|
||||
const auto pcm16_frame = ConvertAudioFrame(*up_frame);
|
||||
m_audio_frames.Push(PrepareAudioFrame(std::move(buffer.value()), *pcm16_frame));
|
||||
} else {
|
||||
m_audio_frames.Push(PrepareAudioFrame(std::move(buffer.value()), *up_frame));
|
||||
}
|
||||
m_audio_frames_cv.Notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LOG_INFO(Lib_AvPlayer, "Audio Decoder Thread exited normaly");
|
||||
}
|
||||
|
||||
bool AvPlayerSource::HasRunningThreads() const {
|
||||
return m_demuxer_thread.joinable() || m_video_decoder_thread.joinable() ||
|
||||
m_audio_decoder_thread.joinable();
|
||||
}
|
||||
|
||||
} // namespace Libraries::AvPlayer
|
|
@ -0,0 +1,219 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "avplayer.h"
|
||||
#include "avplayer_common.h"
|
||||
#include "avplayer_data_streamer.h"
|
||||
|
||||
#include "common/polyfill_thread.h"
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/kernel/thread_management.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
struct AVCodecContext;
|
||||
struct AVFormatContext;
|
||||
struct AVFrame;
|
||||
struct AVIOContext;
|
||||
struct AVPacket;
|
||||
struct SwrContext;
|
||||
struct SwsContext;
|
||||
|
||||
namespace Libraries::AvPlayer {
|
||||
|
||||
class AvPlayerStateCallback {
|
||||
public:
|
||||
virtual ~AvPlayerStateCallback() = default;
|
||||
|
||||
virtual void OnWarning(u32 id) = 0;
|
||||
virtual void OnError() = 0;
|
||||
virtual void OnEOF() = 0;
|
||||
};
|
||||
|
||||
class FrameBuffer {
|
||||
public:
|
||||
FrameBuffer(const SceAvPlayerMemAllocator& memory_replacement, u32 align, u32 size) noexcept
|
||||
: m_memory_replacement(memory_replacement),
|
||||
m_data(Allocate(memory_replacement, align, size)) {
|
||||
ASSERT_MSG(m_data, "Could not allocated frame buffer.");
|
||||
}
|
||||
|
||||
~FrameBuffer() {
|
||||
if (m_data != nullptr) {
|
||||
Deallocate(m_memory_replacement, m_data);
|
||||
m_data = {};
|
||||
}
|
||||
}
|
||||
|
||||
FrameBuffer(const FrameBuffer&) noexcept = delete;
|
||||
FrameBuffer& operator=(const FrameBuffer&) noexcept = delete;
|
||||
|
||||
FrameBuffer(FrameBuffer&& r) noexcept
|
||||
: m_memory_replacement(r.m_memory_replacement), m_data(r.m_data) {
|
||||
r.m_data = nullptr;
|
||||
};
|
||||
|
||||
FrameBuffer& operator=(FrameBuffer&& r) noexcept {
|
||||
std::swap(m_data, r.m_data);
|
||||
return *this;
|
||||
}
|
||||
|
||||
u8* GetBuffer() const noexcept {
|
||||
return m_data;
|
||||
}
|
||||
|
||||
private:
|
||||
static u8* Allocate(const SceAvPlayerMemAllocator& memory_replacement, u32 align, u32 size) {
|
||||
return reinterpret_cast<u8*>(
|
||||
memory_replacement.allocate(memory_replacement.object_ptr, align, size));
|
||||
}
|
||||
|
||||
static void Deallocate(const SceAvPlayerMemAllocator& memory_replacement, void* ptr) {
|
||||
memory_replacement.deallocate(memory_replacement.object_ptr, ptr);
|
||||
}
|
||||
|
||||
const SceAvPlayerMemAllocator& m_memory_replacement;
|
||||
u8* m_data = nullptr;
|
||||
};
|
||||
|
||||
struct Frame {
|
||||
FrameBuffer buffer;
|
||||
SceAvPlayerFrameInfoEx info;
|
||||
};
|
||||
|
||||
class EventCV {
|
||||
public:
|
||||
template <class Pred>
|
||||
void Wait(Pred pred) {
|
||||
std::unique_lock lock(m_mutex);
|
||||
m_cv.wait(lock, std::move(pred));
|
||||
}
|
||||
|
||||
template <class Pred>
|
||||
bool Wait(std::stop_token stop, Pred pred) {
|
||||
std::unique_lock lock(m_mutex);
|
||||
return m_cv.wait(lock, std::move(stop), std::move(pred));
|
||||
}
|
||||
|
||||
template <class Pred, class Rep, class Period>
|
||||
bool WaitFor(std::chrono::duration<Rep, Period> timeout, Pred pred) {
|
||||
std::unique_lock lock(m_mutex);
|
||||
return m_cv.wait_for(lock, timeout, std::move(pred));
|
||||
}
|
||||
|
||||
void Notify() {
|
||||
std::unique_lock lock(m_mutex);
|
||||
m_cv.notify_all();
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex m_mutex{};
|
||||
std::condition_variable_any m_cv{};
|
||||
};
|
||||
|
||||
class AvPlayerSource {
|
||||
public:
|
||||
AvPlayerSource(AvPlayerStateCallback& state, std::string_view path,
|
||||
const SceAvPlayerInitData& init_data, SceAvPlayerSourceType source_type);
|
||||
~AvPlayerSource();
|
||||
|
||||
bool FindStreamInfo();
|
||||
s32 GetStreamCount();
|
||||
s32 GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info);
|
||||
bool EnableStream(u32 stream_index);
|
||||
void SetLooping(bool is_looping);
|
||||
std::optional<bool> HasFrames(u32 num_frames);
|
||||
s32 Start();
|
||||
bool Stop();
|
||||
bool GetAudioData(SceAvPlayerFrameInfo& audio_info);
|
||||
bool GetVideoData(SceAvPlayerFrameInfo& video_info);
|
||||
bool GetVideoData(SceAvPlayerFrameInfoEx& video_info);
|
||||
u64 CurrentTime();
|
||||
bool IsActive();
|
||||
|
||||
private:
|
||||
using ScePthread = Kernel::ScePthread;
|
||||
|
||||
static void ReleaseAVPacket(AVPacket* packet);
|
||||
static void ReleaseAVFrame(AVFrame* frame);
|
||||
static void ReleaseAVCodecContext(AVCodecContext* context);
|
||||
static void ReleaseSWRContext(SwrContext* context);
|
||||
static void ReleaseSWSContext(SwsContext* context);
|
||||
static void ReleaseAVFormatContext(AVFormatContext* context);
|
||||
|
||||
using AVPacketPtr = std::unique_ptr<AVPacket, decltype(&ReleaseAVPacket)>;
|
||||
using AVFramePtr = std::unique_ptr<AVFrame, decltype(&ReleaseAVFrame)>;
|
||||
using AVCodecContextPtr = std::unique_ptr<AVCodecContext, decltype(&ReleaseAVCodecContext)>;
|
||||
using SWRContextPtr = std::unique_ptr<SwrContext, decltype(&ReleaseSWRContext)>;
|
||||
using SWSContextPtr = std::unique_ptr<SwsContext, decltype(&ReleaseSWSContext)>;
|
||||
using AVFormatContextPtr = std::unique_ptr<AVFormatContext, decltype(&ReleaseAVFormatContext)>;
|
||||
|
||||
void DemuxerThread(std::stop_token stop);
|
||||
void VideoDecoderThread(std::stop_token stop);
|
||||
void AudioDecoderThread(std::stop_token stop);
|
||||
|
||||
bool HasRunningThreads() const;
|
||||
|
||||
AVFramePtr ConvertAudioFrame(const AVFrame& frame);
|
||||
AVFramePtr ConvertVideoFrame(const AVFrame& frame);
|
||||
|
||||
Frame PrepareAudioFrame(FrameBuffer buffer, const AVFrame& frame);
|
||||
Frame PrepareVideoFrame(FrameBuffer buffer, const AVFrame& frame);
|
||||
|
||||
AvPlayerStateCallback& m_state;
|
||||
|
||||
SceAvPlayerMemAllocator m_memory_replacement{};
|
||||
u32 m_num_output_video_framebuffers{};
|
||||
|
||||
std::atomic_bool m_is_looping = false;
|
||||
std::atomic_bool m_is_eof = false;
|
||||
|
||||
std::unique_ptr<IDataStreamer> m_up_data_streamer;
|
||||
|
||||
AvPlayerQueue<FrameBuffer> m_audio_buffers;
|
||||
AvPlayerQueue<FrameBuffer> m_video_buffers;
|
||||
|
||||
AvPlayerQueue<AVPacketPtr> m_audio_packets;
|
||||
AvPlayerQueue<AVPacketPtr> m_video_packets;
|
||||
|
||||
AvPlayerQueue<Frame> m_audio_frames;
|
||||
AvPlayerQueue<Frame> m_video_frames;
|
||||
|
||||
std::optional<FrameBuffer> m_current_video_frame;
|
||||
std::optional<FrameBuffer> m_current_audio_frame;
|
||||
|
||||
std::optional<s32> m_video_stream_index{};
|
||||
std::optional<s32> m_audio_stream_index{};
|
||||
|
||||
EventCV m_audio_packets_cv{};
|
||||
EventCV m_audio_frames_cv{};
|
||||
EventCV m_audio_buffers_cv{};
|
||||
|
||||
EventCV m_video_packets_cv{};
|
||||
EventCV m_video_frames_cv{};
|
||||
EventCV m_video_buffers_cv{};
|
||||
|
||||
EventCV m_stop_cv{};
|
||||
|
||||
std::mutex m_state_mutex{};
|
||||
std::jthread m_demuxer_thread{};
|
||||
std::jthread m_video_decoder_thread{};
|
||||
std::jthread m_audio_decoder_thread{};
|
||||
|
||||
AVFormatContextPtr m_avformat_context{nullptr, &ReleaseAVFormatContext};
|
||||
AVCodecContextPtr m_video_codec_context{nullptr, &ReleaseAVCodecContext};
|
||||
AVCodecContextPtr m_audio_codec_context{nullptr, &ReleaseAVCodecContext};
|
||||
SWRContextPtr m_swr_context{nullptr, &ReleaseSWRContext};
|
||||
SWSContextPtr m_sws_context{nullptr, &ReleaseSWSContext};
|
||||
|
||||
std::chrono::high_resolution_clock::time_point m_start_time{};
|
||||
};
|
||||
|
||||
} // namespace Libraries::AvPlayer
|
|
@ -0,0 +1,493 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "avplayer_file_streamer.h"
|
||||
#include "avplayer_source.h"
|
||||
#include "avplayer_state.h"
|
||||
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/kernel/time_management.h"
|
||||
|
||||
#include <magic_enum.hpp>
|
||||
|
||||
namespace Libraries::AvPlayer {
|
||||
|
||||
using namespace Kernel;
|
||||
|
||||
void PS4_SYSV_ABI AvPlayerState::AutoPlayEventCallback(void* opaque, s32 event_id, s32 source_id,
|
||||
void* event_data) {
|
||||
auto const self = reinterpret_cast<AvPlayerState*>(opaque);
|
||||
|
||||
if (event_id == SCE_AVPLAYER_STATE_READY) {
|
||||
s32 video_stream_index = -1;
|
||||
s32 audio_stream_index = -1;
|
||||
s32 timedtext_stream_index = -1;
|
||||
const s32 stream_count = self->GetStreamCount();
|
||||
if (AVPLAYER_IS_ERROR(stream_count)) {
|
||||
return;
|
||||
}
|
||||
if (stream_count == 0) {
|
||||
self->Stop();
|
||||
return;
|
||||
}
|
||||
for (u32 stream_index = 0; stream_index < stream_count; ++stream_index) {
|
||||
SceAvPlayerStreamInfo info{};
|
||||
self->GetStreamInfo(stream_index, info);
|
||||
|
||||
const std::string_view default_language(
|
||||
reinterpret_cast<char*>(self->m_default_language));
|
||||
switch (info.type) {
|
||||
case SCE_AVPLAYER_VIDEO:
|
||||
if (video_stream_index == -1) {
|
||||
video_stream_index = stream_index;
|
||||
}
|
||||
if (!default_language.empty() &&
|
||||
default_language == reinterpret_cast<char*>(info.details.video.language_code)) {
|
||||
video_stream_index = stream_index;
|
||||
}
|
||||
break;
|
||||
case SCE_AVPLAYER_AUDIO:
|
||||
if (audio_stream_index == -1) {
|
||||
audio_stream_index = stream_index;
|
||||
}
|
||||
if (!default_language.empty() &&
|
||||
default_language == reinterpret_cast<char*>(info.details.video.language_code)) {
|
||||
audio_stream_index = stream_index;
|
||||
}
|
||||
break;
|
||||
case SCE_AVPLAYER_TIMEDTEXT:
|
||||
if (default_language.empty()) {
|
||||
timedtext_stream_index = stream_index;
|
||||
break;
|
||||
}
|
||||
if (default_language == reinterpret_cast<char*>(info.details.video.language_code)) {
|
||||
timedtext_stream_index = stream_index;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (video_stream_index != -1) {
|
||||
self->EnableStream(video_stream_index);
|
||||
}
|
||||
if (audio_stream_index != -1) {
|
||||
self->EnableStream(audio_stream_index);
|
||||
}
|
||||
if (timedtext_stream_index != -1) {
|
||||
self->EnableStream(timedtext_stream_index);
|
||||
}
|
||||
self->Start();
|
||||
return;
|
||||
}
|
||||
|
||||
// Pass other events to the game
|
||||
const auto callback = self->m_event_replacement.event_callback;
|
||||
const auto ptr = self->m_event_replacement.object_ptr;
|
||||
if (callback != nullptr) {
|
||||
callback(ptr, event_id, 0, event_data);
|
||||
}
|
||||
}
|
||||
|
||||
// Called inside GAME thread
|
||||
AvPlayerState::AvPlayerState(const SceAvPlayerInitData& init_data)
|
||||
: m_init_data(init_data), m_event_replacement(init_data.event_replacement) {
|
||||
if (m_event_replacement.event_callback == nullptr || init_data.auto_start) {
|
||||
m_auto_start = true;
|
||||
m_init_data.event_replacement.event_callback = &AvPlayerState::AutoPlayEventCallback;
|
||||
m_init_data.event_replacement.object_ptr = this;
|
||||
}
|
||||
if (init_data.default_language != nullptr) {
|
||||
std::memcpy(m_default_language, init_data.default_language, sizeof(m_default_language));
|
||||
}
|
||||
SetState(AvState::Initial);
|
||||
StartControllerThread();
|
||||
}
|
||||
|
||||
AvPlayerState::~AvPlayerState() {
|
||||
{
|
||||
std::unique_lock lock(m_source_mutex);
|
||||
m_up_source.reset();
|
||||
}
|
||||
if (m_controller_thread.joinable()) {
|
||||
m_controller_thread.request_stop();
|
||||
m_controller_thread.join();
|
||||
}
|
||||
m_event_queue.Clear();
|
||||
}
|
||||
|
||||
// Called inside GAME thread
|
||||
s32 AvPlayerState::AddSource(std::string_view path, SceAvPlayerSourceType source_type) {
|
||||
if (path.empty()) {
|
||||
LOG_ERROR(Lib_AvPlayer, "File path is empty.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_lock lock(m_source_mutex);
|
||||
if (m_up_source != nullptr) {
|
||||
LOG_ERROR(Lib_AvPlayer, "Only one source is supported.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
m_up_source = std::make_unique<AvPlayerSource>(*this, path, m_init_data, source_type);
|
||||
}
|
||||
AddSourceEvent();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Called inside GAME thread
|
||||
s32 AvPlayerState::GetStreamCount() {
|
||||
std::shared_lock lock(m_source_mutex);
|
||||
if (m_up_source == nullptr) {
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not get stream count. No source.");
|
||||
return -1;
|
||||
}
|
||||
return m_up_source->GetStreamCount();
|
||||
}
|
||||
|
||||
// Called inside GAME thread
|
||||
s32 AvPlayerState::GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info) {
|
||||
std::shared_lock lock(m_source_mutex);
|
||||
if (m_up_source == nullptr) {
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not get stream {} info. No source.", stream_index);
|
||||
return -1;
|
||||
}
|
||||
return m_up_source->GetStreamInfo(stream_index, info);
|
||||
}
|
||||
|
||||
// Called inside GAME thread
|
||||
s32 AvPlayerState::Start() {
|
||||
std::shared_lock lock(m_source_mutex);
|
||||
if (m_up_source == nullptr || m_up_source->Start() < 0) {
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not start playback.");
|
||||
return -1;
|
||||
}
|
||||
SetState(AvState::Play);
|
||||
OnPlaybackStateChanged(AvState::Play);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AvPlayerState::AvControllerThread(std::stop_token stop) {
|
||||
using std::chrono::milliseconds;
|
||||
|
||||
while (!stop.stop_requested()) {
|
||||
if (m_event_queue.Size() != 0) {
|
||||
ProcessEvent();
|
||||
continue;
|
||||
}
|
||||
std::this_thread::sleep_for(milliseconds(5));
|
||||
UpdateBufferingState();
|
||||
}
|
||||
}
|
||||
|
||||
// Called inside GAME thread
|
||||
void AvPlayerState::AddSourceEvent() {
|
||||
SetState(AvState::AddingSource);
|
||||
m_event_queue.Push(AvPlayerEvent{
|
||||
.event = AvEventType::AddSource,
|
||||
});
|
||||
}
|
||||
|
||||
void AvPlayerState::WarningEvent(s32 id) {
|
||||
m_event_queue.Push(AvPlayerEvent{
|
||||
.event = AvEventType::WarningId,
|
||||
.payload =
|
||||
{
|
||||
.error = id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Called inside GAME thread
|
||||
void AvPlayerState::StartControllerThread() {
|
||||
m_controller_thread =
|
||||
std::jthread([this](std::stop_token stop) { this->AvControllerThread(stop); });
|
||||
}
|
||||
|
||||
// Called inside GAME thread
|
||||
bool AvPlayerState::EnableStream(u32 stream_index) {
|
||||
std::shared_lock lock(m_source_mutex);
|
||||
if (m_up_source == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return m_up_source->EnableStream(stream_index);
|
||||
}
|
||||
|
||||
// Called inside GAME thread
|
||||
bool AvPlayerState::Stop() {
|
||||
std::shared_lock lock(m_source_mutex);
|
||||
if (m_up_source == nullptr || m_current_state == AvState::Stop) {
|
||||
return false;
|
||||
}
|
||||
if (!SetState(AvState::Stop)) {
|
||||
return false;
|
||||
}
|
||||
OnPlaybackStateChanged(AvState::Stop);
|
||||
return m_up_source->Stop();
|
||||
}
|
||||
|
||||
bool AvPlayerState::GetVideoData(SceAvPlayerFrameInfo& video_info) {
|
||||
std::shared_lock lock(m_source_mutex);
|
||||
if (m_up_source == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return m_up_source->GetVideoData(video_info);
|
||||
}
|
||||
|
||||
bool AvPlayerState::GetVideoData(SceAvPlayerFrameInfoEx& video_info) {
|
||||
std::shared_lock lock(m_source_mutex);
|
||||
if (m_up_source == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return m_up_source->GetVideoData(video_info);
|
||||
}
|
||||
|
||||
bool AvPlayerState::GetAudioData(SceAvPlayerFrameInfo& audio_info) {
|
||||
std::shared_lock lock(m_source_mutex);
|
||||
if (m_up_source == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return m_up_source->GetAudioData(audio_info);
|
||||
}
|
||||
|
||||
bool AvPlayerState::IsActive() {
|
||||
std::shared_lock lock(m_source_mutex);
|
||||
if (m_up_source == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return m_current_state != AvState::Stop && m_current_state != AvState::Error &&
|
||||
m_current_state != AvState::EndOfFile && m_up_source->IsActive();
|
||||
}
|
||||
|
||||
u64 AvPlayerState::CurrentTime() {
|
||||
std::shared_lock lock(m_source_mutex);
|
||||
if (m_up_source == nullptr) {
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not get current time. No source.");
|
||||
return 0;
|
||||
}
|
||||
return m_up_source->CurrentTime();
|
||||
}
|
||||
|
||||
bool AvPlayerState::SetLooping(bool is_looping) {
|
||||
std::shared_lock lock(m_source_mutex);
|
||||
if (m_up_source == nullptr) {
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not set loop flag. No source.");
|
||||
return false;
|
||||
}
|
||||
m_up_source->SetLooping(is_looping);
|
||||
return true;
|
||||
}
|
||||
|
||||
// May be called from different threads
|
||||
void AvPlayerState::OnWarning(u32 id) {
|
||||
// Forward to CONTROLLER thread
|
||||
WarningEvent(id);
|
||||
}
|
||||
|
||||
void AvPlayerState::OnError() {
|
||||
SetState(AvState::Error);
|
||||
OnPlaybackStateChanged(AvState::Error);
|
||||
}
|
||||
|
||||
void AvPlayerState::OnEOF() {
|
||||
SetState(AvState::EndOfFile);
|
||||
}
|
||||
|
||||
// Called inside CONTROLLER thread
|
||||
void AvPlayerState::OnPlaybackStateChanged(AvState state) {
|
||||
switch (state) {
|
||||
case AvState::Ready: {
|
||||
EmitEvent(SCE_AVPLAYER_STATE_READY);
|
||||
break;
|
||||
}
|
||||
case AvState::Play: {
|
||||
EmitEvent(SCE_AVPLAYER_STATE_PLAY);
|
||||
break;
|
||||
}
|
||||
case AvState::Stop: {
|
||||
EmitEvent(SCE_AVPLAYER_STATE_STOP);
|
||||
break;
|
||||
}
|
||||
case AvState::Pause: {
|
||||
EmitEvent(SCE_AVPLAYER_STATE_PAUSE);
|
||||
break;
|
||||
}
|
||||
case AvState::Buffering: {
|
||||
EmitEvent(SCE_AVPLAYER_STATE_BUFFERING);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Called inside CONTROLLER and GAME threads
|
||||
bool AvPlayerState::SetState(AvState state) {
|
||||
std::lock_guard guard(m_state_machine_mutex);
|
||||
|
||||
if (!IsStateTransitionValid(state)) {
|
||||
LOG_ERROR(Lib_AvPlayer, "Invalid state transition: {} -> {}",
|
||||
magic_enum::enum_name(m_current_state.load()), magic_enum::enum_name(state));
|
||||
return false;
|
||||
}
|
||||
m_previous_state.store(m_current_state);
|
||||
m_current_state.store(state);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Called inside CONTROLLER thread
|
||||
std::optional<bool> AvPlayerState::OnBufferingCheckEvent(u32 num_frames) {
|
||||
std::shared_lock lock(m_source_mutex);
|
||||
if (!m_up_source) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return m_up_source->HasFrames(num_frames);
|
||||
}
|
||||
|
||||
// Called inside CONTROLLER thread
|
||||
void AvPlayerState::EmitEvent(SceAvPlayerEvents event_id, void* event_data) {
|
||||
LOG_INFO(Lib_AvPlayer, "Sending event to the game: id = {}", magic_enum::enum_name(event_id));
|
||||
const auto callback = m_init_data.event_replacement.event_callback;
|
||||
if (callback) {
|
||||
const auto ptr = m_init_data.event_replacement.object_ptr;
|
||||
callback(ptr, event_id, 0, event_data);
|
||||
}
|
||||
}
|
||||
|
||||
// Called inside CONTROLLER thread
|
||||
void AvPlayerState::ProcessEvent() {
|
||||
if (m_current_state == AvState::Jump) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard guard(m_event_handler_mutex);
|
||||
|
||||
auto event = m_event_queue.Pop();
|
||||
if (!event.has_value()) {
|
||||
return;
|
||||
}
|
||||
switch (event->event) {
|
||||
case AvEventType::WarningId: {
|
||||
OnWarning(event->payload.error);
|
||||
break;
|
||||
}
|
||||
case AvEventType::RevertState: {
|
||||
SetState(m_previous_state.load());
|
||||
break;
|
||||
}
|
||||
case AvEventType::AddSource: {
|
||||
std::shared_lock lock(m_source_mutex);
|
||||
if (m_up_source->FindStreamInfo()) {
|
||||
SetState(AvState::Ready);
|
||||
OnPlaybackStateChanged(AvState::Ready);
|
||||
} else {
|
||||
OnWarning(ORBIS_AVPLAYER_ERROR_NOT_SUPPORTED);
|
||||
SetState(AvState::Error);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AvEventType::Error: {
|
||||
OnWarning(event->payload.error);
|
||||
SetState(AvState::Error);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Called inside CONTROLLER thread
|
||||
void AvPlayerState::UpdateBufferingState() {
|
||||
if (m_current_state == AvState::Buffering) {
|
||||
const auto has_frames = OnBufferingCheckEvent(10);
|
||||
if (!has_frames.has_value()) {
|
||||
return;
|
||||
}
|
||||
if (has_frames.value()) {
|
||||
const auto state =
|
||||
m_previous_state >= AvState::C0x0B ? m_previous_state.load() : AvState::Play;
|
||||
SetState(state);
|
||||
OnPlaybackStateChanged(state);
|
||||
}
|
||||
} else if (m_current_state == AvState::Play) {
|
||||
const auto has_frames = OnBufferingCheckEvent(0);
|
||||
if (!has_frames.has_value()) {
|
||||
return;
|
||||
}
|
||||
if (!has_frames.value()) {
|
||||
SetState(AvState::Buffering);
|
||||
OnPlaybackStateChanged(AvState::Buffering);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AvPlayerState::IsStateTransitionValid(AvState state) {
|
||||
switch (state) {
|
||||
case AvState::Play: {
|
||||
switch (m_current_state.load()) {
|
||||
case AvState::Stop:
|
||||
case AvState::EndOfFile:
|
||||
// case AvState::C0x08:
|
||||
case AvState::Error:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
case AvState::Pause: {
|
||||
switch (m_current_state.load()) {
|
||||
case AvState::Stop:
|
||||
case AvState::EndOfFile:
|
||||
// case AvState::C0x08:
|
||||
case AvState::Starting:
|
||||
case AvState::Error:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
case AvState::Jump: {
|
||||
switch (m_current_state.load()) {
|
||||
case AvState::Stop:
|
||||
case AvState::EndOfFile:
|
||||
// case AvState::C0x08:
|
||||
case AvState::TrickMode:
|
||||
case AvState::Starting:
|
||||
case AvState::Error:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
case AvState::TrickMode: {
|
||||
switch (m_current_state.load()) {
|
||||
case AvState::Stop:
|
||||
case AvState::EndOfFile:
|
||||
// case AvState::C0x08:
|
||||
case AvState::Jump:
|
||||
case AvState::Starting:
|
||||
case AvState::Error:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
case AvState::Buffering: {
|
||||
switch (m_current_state.load()) {
|
||||
case AvState::Stop:
|
||||
case AvState::EndOfFile:
|
||||
case AvState::Pause:
|
||||
// case AvState::C0x08:
|
||||
case AvState::Starting:
|
||||
case AvState::Error:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Libraries::AvPlayer
|
|
@ -0,0 +1,88 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "avplayer.h"
|
||||
#include "avplayer_data_streamer.h"
|
||||
#include "avplayer_source.h"
|
||||
|
||||
#include "common/polyfill_thread.h"
|
||||
#include "core/libraries/kernel/thread_management.h"
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <shared_mutex>
|
||||
|
||||
namespace Libraries::AvPlayer {
|
||||
|
||||
class Stream;
|
||||
class AvDecoder;
|
||||
|
||||
class AvPlayerState : public AvPlayerStateCallback {
|
||||
public:
|
||||
AvPlayerState(const SceAvPlayerInitData& init_data);
|
||||
~AvPlayerState();
|
||||
|
||||
s32 AddSource(std::string_view filename, SceAvPlayerSourceType source_type);
|
||||
s32 GetStreamCount();
|
||||
s32 GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info);
|
||||
bool EnableStream(u32 stream_index);
|
||||
s32 Start();
|
||||
bool Stop();
|
||||
bool GetAudioData(SceAvPlayerFrameInfo& audio_info);
|
||||
bool GetVideoData(SceAvPlayerFrameInfo& video_info);
|
||||
bool GetVideoData(SceAvPlayerFrameInfoEx& video_info);
|
||||
bool IsActive();
|
||||
u64 CurrentTime();
|
||||
bool SetLooping(bool is_looping);
|
||||
|
||||
private:
|
||||
using ScePthreadMutex = Kernel::ScePthreadMutex;
|
||||
using ScePthread = Kernel::ScePthread;
|
||||
|
||||
// Event Replacement
|
||||
static void PS4_SYSV_ABI AutoPlayEventCallback(void* handle, s32 event_id, s32 source_id,
|
||||
void* event_data);
|
||||
|
||||
void OnWarning(u32 id) override;
|
||||
void OnError() override;
|
||||
void OnEOF() override;
|
||||
|
||||
void OnPlaybackStateChanged(AvState state);
|
||||
std::optional<bool> OnBufferingCheckEvent(u32 num_frames);
|
||||
|
||||
void EmitEvent(SceAvPlayerEvents event_id, void* event_data = nullptr);
|
||||
bool SetState(AvState state);
|
||||
|
||||
void AvControllerThread(std::stop_token stop);
|
||||
|
||||
void AddSourceEvent();
|
||||
void WarningEvent(s32 id);
|
||||
|
||||
void StartControllerThread();
|
||||
void ProcessEvent();
|
||||
void UpdateBufferingState();
|
||||
bool IsStateTransitionValid(AvState state);
|
||||
|
||||
std::unique_ptr<AvPlayerSource> m_up_source;
|
||||
|
||||
SceAvPlayerInitData m_init_data{};
|
||||
SceAvPlayerEventReplacement m_event_replacement{};
|
||||
bool m_auto_start{};
|
||||
u8 m_default_language[4]{};
|
||||
|
||||
std::atomic<AvState> m_current_state;
|
||||
std::atomic<AvState> m_previous_state;
|
||||
u32 m_thread_priority;
|
||||
u32 m_thread_affinity;
|
||||
std::atomic_uint32_t m_some_event_result{};
|
||||
|
||||
std::shared_mutex m_source_mutex{};
|
||||
std::mutex m_state_machine_mutex{};
|
||||
std::mutex m_event_handler_mutex{};
|
||||
std::jthread m_controller_thread{};
|
||||
AvPlayerQueue<AvPlayerEvent> m_event_queue{};
|
||||
};
|
||||
|
||||
} // namespace Libraries::AvPlayer
|
|
@ -457,5 +457,18 @@ constexpr int ORBIS_NP_TROPHY_ERROR_HANDLE_EXCEEDS_MAX = 0x80551624;
|
|||
constexpr int ORBIS_NP_TROPHY_ERROR_CONTEXT_ALREADY_EXISTS = 0x80551613;
|
||||
constexpr int ORBIS_NP_TROPHY_ERROR_CONTEXT_EXCEEDS_MAX = 0x80551622;
|
||||
|
||||
// AvPlayer library
|
||||
constexpr int ORBIS_AVPLAYER_ERROR_INVALID_PARAMS = 0x806A0001;
|
||||
constexpr int ORBIS_AVPLAYER_ERROR_OPERATION_FAILED = 0x806A0002;
|
||||
constexpr int ORBIS_AVPLAYER_ERROR_NO_MEMORY = 0x806A0003;
|
||||
constexpr int ORBIS_AVPLAYER_ERROR_NOT_SUPPORTED = 0x806A0004;
|
||||
constexpr int ORBIS_AVPLAYER_ERROR_WAR_FILE_NONINTERLEAVED = 0x806A00A0;
|
||||
constexpr int ORBIS_AVPLAYER_ERROR_WAR_LOOPING_BACK = 0x806A00A1;
|
||||
constexpr int ORBIS_AVPLAYER_ERROR_WAR_JUMP_COMPLETE = 0x806A00A3;
|
||||
constexpr int ORBIS_AVPLAYER_ERROR_INFO_MARLIN_ENCRY = 0x806A00B0;
|
||||
constexpr int ORBIS_AVPLAYER_ERROR_INFO_PLAYREADY_ENCRY = 0x806A00B4;
|
||||
constexpr int ORBIS_AVPLAYER_ERROR_INFO_AES_ENCRY = 0x806A00B5;
|
||||
constexpr int ORBIS_AVPLAYER_ERROR_INFO_OTHER_ENCRY = 0x806A00BF;
|
||||
|
||||
// AppContent library
|
||||
constexpr int ORBIS_APP_CONTENT_ERROR_PARAMETER = 0x80D90002;
|
||||
|
|
|
@ -208,4 +208,7 @@ int PS4_SYSV_ABI sceKernelDeleteUserEvent(SceKernelEqueue eq, int id) {
|
|||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s16 PS4_SYSV_ABI sceKernelGetEventFilter(const SceKernelEvent* ev) {
|
||||
return ev->filter;
|
||||
}
|
||||
} // namespace Libraries::Kernel
|
||||
|
|
|
@ -21,5 +21,6 @@ int PS4_SYSV_ABI sceKernelDeleteUserEvent(SceKernelEqueue eq, int id);
|
|||
int PS4_SYSV_ABI sceKernelAddUserEvent(SceKernelEqueue eq, int id);
|
||||
int PS4_SYSV_ABI sceKernelAddUserEventEdge(SceKernelEqueue eq, int id);
|
||||
s32 PS4_SYSV_ABI sceKernelAddHRTimerEvent(SceKernelEqueue eq, int id, timespec* ts, void* udata);
|
||||
s16 PS4_SYSV_ABI sceKernelGetEventFilter(const SceKernelEvent* ev);
|
||||
|
||||
} // namespace Libraries::Kernel
|
||||
|
|
|
@ -112,6 +112,15 @@ int PS4_SYSV_ABI posix_open(const char* path, int flags, /* SceKernelMode*/ u16
|
|||
return result;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI open(const char* filename, const char* mode) {
|
||||
LOG_INFO(Kernel_Fs, "open redirect to sceKernelOpen");
|
||||
int result = sceKernelOpen(filename, ORBIS_KERNEL_O_RDWR, 0);
|
||||
if (result < 0) {
|
||||
return -1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceKernelClose(int d) {
|
||||
if (d < 3) { // d probably hold an error code
|
||||
return ORBIS_KERNEL_ERROR_EPERM;
|
||||
|
@ -498,6 +507,7 @@ void fileSystemSymbolsRegister(Core::Loader::SymbolsResolver* sym) {
|
|||
std::srand(std::time(nullptr));
|
||||
LIB_FUNCTION("1G3lF1Gg1k8", "libkernel", 1, "libkernel", 1, 1, sceKernelOpen);
|
||||
LIB_FUNCTION("wuCroIGjt2g", "libScePosix", 1, "libkernel", 1, 1, posix_open);
|
||||
LIB_FUNCTION("wuCroIGjt2g", "libkernel", 1, "libkernel", 1, 1, open);
|
||||
LIB_FUNCTION("UK2Tl2DWUns", "libkernel", 1, "libkernel", 1, 1, sceKernelClose);
|
||||
LIB_FUNCTION("bY-PO6JhzhQ", "libkernel", 1, "libkernel", 1, 1, posix_close);
|
||||
LIB_FUNCTION("bY-PO6JhzhQ", "libScePosix", 1, "libkernel", 1, 1, posix_close);
|
||||
|
|
|
@ -125,6 +125,37 @@ int ErrnoToSceKernelError(int e) {
|
|||
return res > SCE_KERNEL_ERROR_ESTOP ? SCE_KERNEL_ERROR_UNKNOWN : res;
|
||||
}
|
||||
|
||||
void SetPosixErrno(int e) {
|
||||
// Some error numbers are different between supported OSes or the PS4
|
||||
switch (e) {
|
||||
case EPERM:
|
||||
g_posix_errno = POSIX_EPERM;
|
||||
break;
|
||||
case EAGAIN:
|
||||
g_posix_errno = POSIX_EAGAIN;
|
||||
break;
|
||||
case ENOMEM:
|
||||
g_posix_errno = POSIX_ENOMEM;
|
||||
break;
|
||||
case EINVAL:
|
||||
g_posix_errno = POSIX_EINVAL;
|
||||
break;
|
||||
case ENOSPC:
|
||||
g_posix_errno = POSIX_ENOSPC;
|
||||
break;
|
||||
case ERANGE:
|
||||
g_posix_errno = POSIX_ERANGE;
|
||||
break;
|
||||
case EDEADLK:
|
||||
g_posix_errno = POSIX_EDEADLK;
|
||||
break;
|
||||
case ETIMEDOUT:
|
||||
g_posix_errno = POSIX_ETIMEDOUT;
|
||||
break;
|
||||
default:
|
||||
g_posix_errno = e;
|
||||
}
|
||||
}
|
||||
int PS4_SYSV_ABI sceKernelMmap(void* addr, u64 len, int prot, int flags, int fd, size_t offset,
|
||||
void** res) {
|
||||
LOG_INFO(Kernel_Vmm, "called addr = {}, len = {}, prot = {}, flags = {}, fd = {}, offset = {}",
|
||||
|
@ -425,6 +456,7 @@ void LibKernel_Register(Core::Loader::SymbolsResolver* sym) {
|
|||
LIB_FUNCTION("mJ7aghmgvfc", "libkernel", 1, "libkernel", 1, 1, sceKernelGetEventId);
|
||||
LIB_FUNCTION("9bfdLIyuwCY", "libkernel", 1, "libkernel", 1, 1, sceKernelMTypeProtect);
|
||||
LIB_FUNCTION("vSMAm3cxYTY", "libkernel", 1, "libkernel", 1, 1, sceKernelMProtect);
|
||||
LIB_FUNCTION("23CPPI1tyBY", "libkernel", 1, "libkernel", 1, 1, sceKernelGetEventFilter);
|
||||
|
||||
// misc
|
||||
LIB_FUNCTION("WslcK1FQcGI", "libkernel", 1, "libkernel", 1, 1, sceKernelIsNeoMode);
|
||||
|
|
|
@ -14,6 +14,7 @@ namespace Libraries::Kernel {
|
|||
|
||||
void ErrSceToPosix(int result);
|
||||
int ErrnoToSceKernelError(int e);
|
||||
void SetPosixErrno(int e);
|
||||
|
||||
struct OrbisTimesec {
|
||||
time_t t;
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "common/singleton.h"
|
||||
#include "common/thread.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/kernel/libkernel.h"
|
||||
#include "core/libraries/kernel/thread_management.h"
|
||||
#include "core/libraries/kernel/threads/threads.h"
|
||||
#include "core/libraries/libs.h"
|
||||
|
@ -1374,15 +1375,27 @@ int PS4_SYSV_ABI posix_pthread_detach(ScePthread thread) {
|
|||
}
|
||||
|
||||
int PS4_SYSV_ABI posix_sem_init(sem_t* sem, int pshared, unsigned int value) {
|
||||
return sem_init(sem, pshared, value);
|
||||
int result = sem_init(sem, pshared, value);
|
||||
if (result == -1) {
|
||||
SetPosixErrno(errno);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI posix_sem_wait(sem_t* sem) {
|
||||
return sem_wait(sem);
|
||||
int result = sem_wait(sem);
|
||||
if (result == -1) {
|
||||
SetPosixErrno(errno);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI posix_sem_trywait(sem_t* sem) {
|
||||
return sem_trywait(sem);
|
||||
int result = sem_trywait(sem);
|
||||
if (result == -1) {
|
||||
SetPosixErrno(errno);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifndef HAVE_SEM_TIMEDWAIT
|
||||
|
@ -1416,19 +1429,35 @@ int sem_timedwait(sem_t* sem, const struct timespec* abstime) {
|
|||
#endif
|
||||
|
||||
int PS4_SYSV_ABI posix_sem_timedwait(sem_t* sem, const timespec* t) {
|
||||
return sem_timedwait(sem, t);
|
||||
int result = sem_timedwait(sem, t);
|
||||
if (result == -1) {
|
||||
SetPosixErrno(errno);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI posix_sem_post(sem_t* sem) {
|
||||
return sem_post(sem);
|
||||
int result = sem_post(sem);
|
||||
if (result == -1) {
|
||||
SetPosixErrno(errno);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI posix_sem_destroy(sem_t* sem) {
|
||||
return sem_destroy(sem);
|
||||
int result = sem_destroy(sem);
|
||||
if (result == -1) {
|
||||
SetPosixErrno(errno);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI posix_sem_getvalue(sem_t* sem, int* sval) {
|
||||
return sem_getvalue(sem, sval);
|
||||
int result = sem_getvalue(sem, sval);
|
||||
if (result == -1) {
|
||||
SetPosixErrno(errno);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI posix_pthread_attr_getstacksize(const pthread_attr_t* attr, size_t* size) {
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
#include <arpa/inet.h>
|
||||
#endif
|
||||
|
||||
#include <common/assert.h>
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/libs.h"
|
||||
|
|
|
@ -105,7 +105,7 @@ int PS4_SYSV_ABI scePadGetControllerInformation(s32 handle, OrbisPadControllerIn
|
|||
pInfo->stickInfo.deadZoneRight = 2;
|
||||
pInfo->connectionType = ORBIS_PAD_PORT_TYPE_STANDARD;
|
||||
pInfo->connectedCount = 1;
|
||||
pInfo->connected = 1;
|
||||
pInfo->connected = true;
|
||||
pInfo->deviceClass = ORBIS_PAD_DEVICE_CLASS_STANDARD;
|
||||
return SCE_OK;
|
||||
}
|
||||
|
@ -125,9 +125,16 @@ int PS4_SYSV_ABI scePadGetDeviceInfo() {
|
|||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI scePadGetExtControllerInformation() {
|
||||
LOG_ERROR(Lib_Pad, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
int PS4_SYSV_ABI scePadGetExtControllerInformation(s32 handle,
|
||||
OrbisPadExtendedControllerInformation* pInfo) {
|
||||
LOG_INFO(Lib_Pad, "called handle = {}", handle);
|
||||
|
||||
pInfo->padType1 = 0;
|
||||
pInfo->padType2 = 0;
|
||||
pInfo->capability = 0;
|
||||
|
||||
auto res = scePadGetControllerInformation(handle, &pInfo->base);
|
||||
return res;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI scePadGetExtensionUnitInfo() {
|
||||
|
@ -237,7 +244,7 @@ int PS4_SYSV_ABI scePadOpen(s32 userId, s32 type, s32 index, const OrbisPadOpenP
|
|||
|
||||
int PS4_SYSV_ABI scePadOpenExt() {
|
||||
LOG_ERROR(Lib_Pad, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
return 1; // dummy
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI scePadOpenExt2() {
|
||||
|
@ -422,6 +429,12 @@ int PS4_SYSV_ABI scePadSetLightBar(s32 handle, const OrbisPadLightBarParam* pPar
|
|||
if (pParam != nullptr) {
|
||||
LOG_INFO(Lib_Pad, "scePadSetLightBar called handle = {} rgb = {} {} {}", handle, pParam->r,
|
||||
pParam->g, pParam->b);
|
||||
|
||||
if (pParam->r < 0xD && pParam->g < 0xD && pParam->b < 0xD) {
|
||||
LOG_INFO(Lib_Pad, "Invalid lightbar setting");
|
||||
return ORBIS_PAD_ERROR_INVALID_LIGHTBAR_SETTING;
|
||||
}
|
||||
|
||||
auto* controller = Common::Singleton<Input::GameController>::Instance();
|
||||
controller->SetLightBarRGB(pParam->r, pParam->g, pParam->b);
|
||||
return ORBIS_OK;
|
||||
|
|
|
@ -212,6 +212,19 @@ struct OrbisPadControllerInformation {
|
|||
u8 reserve[8];
|
||||
};
|
||||
|
||||
struct OrbisPadExtendedControllerInformation {
|
||||
OrbisPadControllerInformation base;
|
||||
u16 padType1;
|
||||
u16 padType2;
|
||||
u8 capability;
|
||||
|
||||
union {
|
||||
u8 quantityOfSelectorSwitch;
|
||||
int maxPhysicalWheelAngle;
|
||||
u8 data[8];
|
||||
};
|
||||
};
|
||||
|
||||
struct OrbisPadOpenParam {
|
||||
u8 reserve[8];
|
||||
};
|
||||
|
@ -248,7 +261,8 @@ int PS4_SYSV_ABI scePadGetControllerInformation(s32 handle, OrbisPadControllerIn
|
|||
int PS4_SYSV_ABI scePadGetDataInternal();
|
||||
int PS4_SYSV_ABI scePadGetDeviceId();
|
||||
int PS4_SYSV_ABI scePadGetDeviceInfo();
|
||||
int PS4_SYSV_ABI scePadGetExtControllerInformation();
|
||||
int PS4_SYSV_ABI scePadGetExtControllerInformation(s32 handle,
|
||||
OrbisPadExtendedControllerInformation* pInfo);
|
||||
int PS4_SYSV_ABI scePadGetExtensionUnitInfo();
|
||||
int PS4_SYSV_ABI scePadGetFeatureReport();
|
||||
int PS4_SYSV_ABI scePadGetHandle(s32 userId, s32 type, s32 index);
|
||||
|
|
|
@ -792,8 +792,8 @@ int PS4_SYSV_ABI sceSaveDataTransferringMount() {
|
|||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceSaveDataUmount(const OrbisSaveDataMountPoint* mountPoint) {
|
||||
LOG_INFO(Lib_SaveData, "mountPoint = {}", std::string(mountPoint->data));
|
||||
if (std::string(mountPoint->data).empty()) {
|
||||
LOG_INFO(Lib_SaveData, "mountPoint = {}", mountPoint->data);
|
||||
if (std::string_view(mountPoint->data).empty()) {
|
||||
return ORBIS_SAVE_DATA_ERROR_NOT_MOUNTED;
|
||||
}
|
||||
const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) /
|
||||
|
|
|
@ -151,6 +151,28 @@ s32 PS4_SYSV_ABI sceVideoOutSubmitFlip(s32 handle, s32 bufferIndex, s32 flipMode
|
|||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceVideoOutGetEventId(const Kernel::SceKernelEvent* ev) {
|
||||
if (ev == nullptr) {
|
||||
return SCE_VIDEO_OUT_ERROR_INVALID_ADDRESS;
|
||||
}
|
||||
if (ev->filter != Kernel::SceKernelEvent::Filter::VideoOut) {
|
||||
return ORBIS_VIDEO_OUT_ERROR_INVALID_EVENT_QUEUE;
|
||||
}
|
||||
return ev->ident;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceVideoOutGetEventData(const Kernel::SceKernelEvent* ev, int64_t* data) {
|
||||
if (ev == nullptr || data == nullptr) {
|
||||
return SCE_VIDEO_OUT_ERROR_INVALID_ADDRESS;
|
||||
}
|
||||
if (ev->filter != Kernel::SceKernelEvent::Filter::VideoOut) {
|
||||
return ORBIS_VIDEO_OUT_ERROR_INVALID_EVENT_QUEUE;
|
||||
}
|
||||
|
||||
*data = ev->data;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceVideoOutGetFlipStatus(s32 handle, FlipStatus* status) {
|
||||
if (!status) {
|
||||
LOG_ERROR(Lib_VideoOut, "Flip status is null");
|
||||
|
@ -200,7 +222,6 @@ s32 PS4_SYSV_ABI sceVideoOutGetResolutionStatus(s32 handle, SceVideoOutResolutio
|
|||
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);
|
||||
|
||||
if (index != 0) {
|
||||
|
@ -303,6 +324,9 @@ void RegisterLib(Core::Loader::SymbolsResolver* sym) {
|
|||
LIB_FUNCTION("kGVLc3htQE8", "libSceVideoOut", 1, "libSceVideoOut", 0, 0,
|
||||
sceVideoOutGetDeviceCapabilityInfo);
|
||||
LIB_FUNCTION("j6RaAUlaLv0", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutWaitVblank);
|
||||
LIB_FUNCTION("U2JJtSqNKZI", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutGetEventId);
|
||||
LIB_FUNCTION("rWUTcKdkUzQ", "libSceVideoOut", 1, "libSceVideoOut", 0, 0,
|
||||
sceVideoOutGetEventData);
|
||||
|
||||
// openOrbis appears to have libSceVideoOut_v1 module libSceVideoOut_v1.1
|
||||
LIB_FUNCTION("Up36PTk687E", "libSceVideoOut", 1, "libSceVideoOut", 1, 1, sceVideoOutOpen);
|
||||
|
|
|
@ -104,6 +104,8 @@ s32 PS4_SYSV_ABI sceVideoOutGetResolutionStatus(s32 handle, SceVideoOutResolutio
|
|||
s32 PS4_SYSV_ABI sceVideoOutOpen(SceUserServiceUserId userId, s32 busType, s32 index,
|
||||
const void* param);
|
||||
s32 PS4_SYSV_ABI sceVideoOutClose(s32 handle);
|
||||
int PS4_SYSV_ABI sceVideoOutGetEventId(const Kernel::SceKernelEvent* ev);
|
||||
int PS4_SYSV_ABI sceVideoOutGetEventData(const Kernel::SceKernelEvent* ev, int64_t* data);
|
||||
|
||||
// Internal system functions
|
||||
void sceVideoOutGetBufferLabelAddress(s32 handle, uintptr_t* label_addr);
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "about_dialog.h"
|
||||
#include "ui_about_dialog.h"
|
||||
|
||||
AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent), ui(new Ui::AboutDialog) {
|
||||
ui->setupUi(this);
|
||||
}
|
||||
|
||||
AboutDialog::~AboutDialog() {
|
||||
delete ui;
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
namespace Ui {
|
||||
class AboutDialog;
|
||||
}
|
||||
|
||||
class AboutDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit AboutDialog(QWidget* parent = nullptr);
|
||||
~AboutDialog();
|
||||
|
||||
private:
|
||||
Ui::AboutDialog* ui;
|
||||
};
|
|
@ -0,0 +1,110 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
SPDX-License-Identifier: GPL-2.0-or-later -->
|
||||
<ui version="4.0">
|
||||
<class>AboutDialog</class>
|
||||
<widget class="QDialog" name="AboutDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>780</width>
|
||||
<height>320</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>About shadPS4</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset>
|
||||
<normaloff>:/images/shadps4.ico</normaloff>:/images/shadps4.ico</iconset>
|
||||
</property>
|
||||
<widget class="QLabel" name="shad_logo">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>30</y>
|
||||
<width>271</width>
|
||||
<height>261</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::Shape::NoFrame</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="pixmap">
|
||||
<pixmap>:/images/shadps4.ico</pixmap>
|
||||
</property>
|
||||
<property name="scaledContents">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="shad_title">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>310</x>
|
||||
<y>40</y>
|
||||
<width>171</width>
|
||||
<height>41</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>24</pointsize>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>shadPS4</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="shad_text">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>310</x>
|
||||
<y>90</y>
|
||||
<width>451</width>
|
||||
<height>101</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>14</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>shadPS4 is an experimental open-source emulator for the PlayStation 4.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="shad_text_2">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>310</x>
|
||||
<y>180</y>
|
||||
<width>451</width>
|
||||
<height>101</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>14</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>This software should not be used to play games you have not legally obtained.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -9,6 +9,7 @@
|
|||
#include <QStatusBar>
|
||||
#include <QtConcurrent>
|
||||
|
||||
#include "about_dialog.h"
|
||||
#include "common/io_file.h"
|
||||
#include "common/version.h"
|
||||
#include "core/file_format/pkg.h"
|
||||
|
@ -206,6 +207,11 @@ void MainWindow::CreateConnects() {
|
|||
settingsDialog->exec();
|
||||
});
|
||||
|
||||
connect(ui->aboutAct, &QAction::triggered, this, [this]() {
|
||||
auto aboutDialog = new AboutDialog(this);
|
||||
aboutDialog->exec();
|
||||
});
|
||||
|
||||
connect(ui->setIconSizeTinyAct, &QAction::triggered, this, [this]() {
|
||||
if (isTableList) {
|
||||
m_game_list_frame->icon_size =
|
||||
|
@ -328,6 +334,7 @@ void MainWindow::CreateConnects() {
|
|||
|
||||
// Package install.
|
||||
connect(ui->bootInstallPkgAct, &QAction::triggered, this, &MainWindow::InstallPkg);
|
||||
connect(ui->bootGameAct, &QAction::triggered, this, &MainWindow::BootGame);
|
||||
connect(ui->gameInstallPathAct, &QAction::triggered, this, &MainWindow::InstallDirectory);
|
||||
|
||||
// elf viewer
|
||||
|
@ -484,6 +491,27 @@ void MainWindow::InstallPkg() {
|
|||
}
|
||||
}
|
||||
|
||||
void MainWindow::BootGame() {
|
||||
QFileDialog dialog;
|
||||
dialog.setFileMode(QFileDialog::ExistingFile);
|
||||
dialog.setNameFilter(tr("ELF files (*.bin *.elf *.oelf)"));
|
||||
if (dialog.exec()) {
|
||||
QStringList fileNames = dialog.selectedFiles();
|
||||
int nFiles = fileNames.size();
|
||||
|
||||
if (nFiles > 1) {
|
||||
QMessageBox::critical(nullptr, "Game Boot", QString("Only one file can be selected!"));
|
||||
} else {
|
||||
std::filesystem::path path(fileNames[0].toStdString());
|
||||
#ifdef _WIN64
|
||||
path = std::filesystem::path(fileNames[0].toStdWString());
|
||||
#endif
|
||||
Core::Emulator emulator;
|
||||
emulator.Run(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int nPkg) {
|
||||
if (Loader::DetectFileType(file) == Loader::FileTypes::Pkg) {
|
||||
pkg = PKG();
|
||||
|
|
|
@ -61,6 +61,7 @@ private:
|
|||
void SetLastIconSizeBullet();
|
||||
void SetUiIcons(bool isWhite);
|
||||
void InstallPkg();
|
||||
void BootGame();
|
||||
void AddRecentFiles(QString filePath);
|
||||
QIcon RecolorIcon(const QIcon& icon, bool isWhite);
|
||||
bool isIconBlack = false;
|
||||
|
|
|
@ -30,6 +30,7 @@ QT_BEGIN_NAMESPACE
|
|||
class Ui_MainWindow {
|
||||
public:
|
||||
QAction* bootInstallPkgAct;
|
||||
QAction* bootGameAct;
|
||||
QAction* addElfFolderAct;
|
||||
QAction* exitAct;
|
||||
QAction* showGameListAct;
|
||||
|
@ -44,6 +45,7 @@ public:
|
|||
QAction* gameInstallPathAct;
|
||||
QAction* dumpGameListAct;
|
||||
QAction* pkgViewerAct;
|
||||
QAction* aboutAct;
|
||||
QAction* setThemeDark;
|
||||
QAction* setThemeLight;
|
||||
QAction* setThemeGreen;
|
||||
|
@ -69,6 +71,7 @@ public:
|
|||
QMenu* menuSettings;
|
||||
QMenu* menuUtils;
|
||||
QMenu* menuThemes;
|
||||
QMenu* menuAbout;
|
||||
QToolBar* toolBar;
|
||||
|
||||
void setupUi(QMainWindow* MainWindow) {
|
||||
|
@ -92,6 +95,8 @@ public:
|
|||
bootInstallPkgAct = new QAction(MainWindow);
|
||||
bootInstallPkgAct->setObjectName("bootInstallPkgAct");
|
||||
bootInstallPkgAct->setIcon(QIcon(":images/file_icon.png"));
|
||||
bootGameAct = new QAction(MainWindow);
|
||||
bootGameAct->setObjectName("bootGameAct");
|
||||
addElfFolderAct = new QAction(MainWindow);
|
||||
addElfFolderAct->setObjectName("addElfFolderAct");
|
||||
exitAct = new QAction(MainWindow);
|
||||
|
@ -136,6 +141,8 @@ public:
|
|||
pkgViewerAct->setObjectName("pkgViewer");
|
||||
pkgViewerAct->setObjectName("pkgViewer");
|
||||
pkgViewerAct->setIcon(QIcon(":images/file_icon.png"));
|
||||
aboutAct = new QAction(MainWindow);
|
||||
aboutAct->setObjectName("aboutAct");
|
||||
setThemeDark = new QAction(MainWindow);
|
||||
setThemeDark->setObjectName("setThemeDark");
|
||||
setThemeDark->setCheckable(true);
|
||||
|
@ -242,6 +249,8 @@ public:
|
|||
menuThemes = new QMenu(menuView);
|
||||
menuThemes->setObjectName("menuThemes");
|
||||
menuThemes->setIcon(QIcon(":images/themes_icon.png"));
|
||||
menuAbout = new QMenu(menuBar);
|
||||
menuAbout->setObjectName("menuAbout");
|
||||
MainWindow->setMenuBar(menuBar);
|
||||
toolBar = new QToolBar(MainWindow);
|
||||
toolBar->setObjectName("toolBar");
|
||||
|
@ -250,7 +259,9 @@ public:
|
|||
menuBar->addAction(menuFile->menuAction());
|
||||
menuBar->addAction(menuView->menuAction());
|
||||
menuBar->addAction(menuSettings->menuAction());
|
||||
menuBar->addAction(menuAbout->menuAction());
|
||||
menuFile->addAction(bootInstallPkgAct);
|
||||
menuFile->addAction(bootGameAct);
|
||||
menuFile->addAction(addElfFolderAct);
|
||||
menuFile->addSeparator();
|
||||
menuFile->addAction(menuRecent->menuAction());
|
||||
|
@ -278,6 +289,7 @@ public:
|
|||
menuSettings->addAction(menuUtils->menuAction());
|
||||
menuUtils->addAction(dumpGameListAct);
|
||||
menuUtils->addAction(pkgViewerAct);
|
||||
menuAbout->addAction(aboutAct);
|
||||
|
||||
retranslateUi(MainWindow);
|
||||
|
||||
|
@ -290,6 +302,8 @@ public:
|
|||
QCoreApplication::translate("MainWindow", "Open/Add Elf Folder", nullptr));
|
||||
bootInstallPkgAct->setText(
|
||||
QCoreApplication::translate("MainWindow", "Install Packages (PKG)", nullptr));
|
||||
bootGameAct->setText(QCoreApplication::translate("MainWindow", "Boot Game", nullptr));
|
||||
aboutAct->setText(QCoreApplication::translate("MainWindow", "About", nullptr));
|
||||
#if QT_CONFIG(tooltip)
|
||||
bootInstallPkgAct->setToolTip(QCoreApplication::translate(
|
||||
"MainWindow", "Install application from a .pkg file", nullptr));
|
||||
|
@ -332,6 +346,7 @@ public:
|
|||
menuSettings->setTitle(QCoreApplication::translate("MainWindow", "Settings", nullptr));
|
||||
menuUtils->setTitle(QCoreApplication::translate("MainWindow", "Utils", nullptr));
|
||||
menuThemes->setTitle(QCoreApplication::translate("MainWindow", "Themes", nullptr));
|
||||
menuAbout->setTitle(QCoreApplication::translate("MainWindow", "About", nullptr));
|
||||
setThemeDark->setText(QCoreApplication::translate("MainWindow", "Dark", nullptr));
|
||||
setThemeLight->setText(QCoreApplication::translate("MainWindow", "Light", nullptr));
|
||||
setThemeGreen->setText(QCoreApplication::translate("MainWindow", "Green", nullptr));
|
||||
|
|
|
@ -39,13 +39,28 @@ SettingsDialog::SettingsDialog(std::span<const QString> physical_devices, QWidge
|
|||
ui->buttonBox->button(QDialogButtonBox::StandardButton::Close)->setFocus();
|
||||
});
|
||||
|
||||
// EMULATOR TAB
|
||||
// GENERAL TAB
|
||||
{
|
||||
connect(ui->userNameLineEdit, &QLineEdit::textChanged, this,
|
||||
[](const QString& text) { Config::setUserName(text.toStdString()); });
|
||||
|
||||
connect(ui->consoleLanguageComboBox, &QComboBox::currentIndexChanged, this,
|
||||
[](int index) { Config::setLanguage(index); });
|
||||
|
||||
connect(ui->userNameLineEdit, &QLineEdit::textChanged, this,
|
||||
[](const QString& text) { Config::setUserName(text.toStdString()); });
|
||||
connect(ui->fullscreenCheckBox, &QCheckBox::stateChanged, this,
|
||||
[](int val) { Config::setFullscreenMode(val); });
|
||||
|
||||
connect(ui->showSplashCheckBox, &QCheckBox::stateChanged, this,
|
||||
[](int val) { Config::setShowSplash(val); });
|
||||
|
||||
connect(ui->ps4proCheckBox, &QCheckBox::stateChanged, this,
|
||||
[](int val) { Config::setNeoMode(val); });
|
||||
|
||||
connect(ui->logTypeComboBox, &QComboBox::currentTextChanged, this,
|
||||
[](const QString& text) { Config::setLogType(text.toStdString()); });
|
||||
|
||||
connect(ui->logFilterLineEdit, &QLineEdit::textChanged, this,
|
||||
[](const QString& text) { Config::setLogFilter(text.toStdString()); });
|
||||
}
|
||||
|
||||
// GPU TAB
|
||||
|
@ -74,24 +89,6 @@ SettingsDialog::SettingsDialog(std::span<const QString> physical_devices, QWidge
|
|||
[](int val) { Config::setDumpPM4(val); });
|
||||
}
|
||||
|
||||
// GENERAL TAB
|
||||
{
|
||||
connect(ui->fullscreenCheckBox, &QCheckBox::stateChanged, this,
|
||||
[](int val) { Config::setFullscreenMode(val); });
|
||||
|
||||
connect(ui->showSplashCheckBox, &QCheckBox::stateChanged, this,
|
||||
[](int val) { Config::setShowSplash(val); });
|
||||
|
||||
connect(ui->ps4proCheckBox, &QCheckBox::stateChanged, this,
|
||||
[](int val) { Config::setNeoMode(val); });
|
||||
|
||||
connect(ui->logTypeComboBox, &QComboBox::currentTextChanged, this,
|
||||
[](const QString& text) { Config::setLogType(text.toStdString()); });
|
||||
|
||||
connect(ui->logFilterLineEdit, &QLineEdit::textChanged, this,
|
||||
[](const QString& text) { Config::setLogFilter(text.toStdString()); });
|
||||
}
|
||||
|
||||
// DEBUG TAB
|
||||
{
|
||||
connect(ui->debugDump, &QCheckBox::stateChanged, this,
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
SPDX-License-Identifier: GPL-2.0-or-later -->
|
||||
|
||||
<ui version="4.0">
|
||||
<class>SettingsDialog</class>
|
||||
<widget class="QDialog" name="SettingsDialog">
|
||||
|
@ -12,8 +11,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1024</width>
|
||||
<height>768</height>
|
||||
<width>854</width>
|
||||
<height>480</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
|
@ -22,6 +21,12 @@
|
|||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>11</pointsize>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Settings</string>
|
||||
</property>
|
||||
|
@ -46,8 +51,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1006</width>
|
||||
<height>720</height>
|
||||
<width>832</width>
|
||||
<height>418</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
|
@ -59,288 +64,390 @@
|
|||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="emulatorTab">
|
||||
<widget class="QWidget" name="generalTab">
|
||||
<attribute name="title">
|
||||
<string>Emulator</string>
|
||||
<string>General</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="emulatorTabVLayout" stretch="0,0">
|
||||
<layout class="QVBoxLayout" name="generalTabVLayout" stretch="0,0">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="emulatorTabHLayout" stretch="1,1,1">
|
||||
<layout class="QHBoxLayout" name="generalTabHLayout" stretch="1,1,1">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="emulatorTabLayoutLeft">
|
||||
<layout class="QVBoxLayout" name="systemTabLayoutLeft">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="consoleLanguageGroupBox">
|
||||
<widget class="QGroupBox" name="emuSettings">
|
||||
<property name="title">
|
||||
<string>Console Language</string>
|
||||
<string>System</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="settingsLayout">
|
||||
<layout class="QVBoxLayout" name="emuSettingsLayout">
|
||||
<item>
|
||||
<widget class="QComboBox" name="consoleLanguageComboBox">
|
||||
<layout class="QVBoxLayout" name="vLayoutUserName">
|
||||
<property name="spacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Japanese</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>English (United States)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>French (France)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Spanish (Spain)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>German</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Italian</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Dutch</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Portuguese (Portugal)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Russian</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Korean</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Traditional Chinese</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Simplified Chinese</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Finnish</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Swedish</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Danish</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Norwegian</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Polish</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Portuguese (Brazil)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>English (United Kingdom)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Turkish</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Spanish (Latin America)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Arabic</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>French (Canada)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Czech</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Hungarian</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Greek</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Romanian</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Thai</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Vietnamese</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Indonesian</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="hLayoutUserName">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="userName">
|
||||
<property name="title">
|
||||
<string>Username</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="userNameLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="userNameLineEdit"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="consoleLanguageGroupBox">
|
||||
<property name="title">
|
||||
<string>Console Language</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="settingsLayout">
|
||||
<item>
|
||||
<widget class="QComboBox" name="consoleLanguageComboBox">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Japanese</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>English (United States)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>French (France)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Spanish (Spain)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>German</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Italian</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Dutch</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Portuguese (Portugal)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Russian</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Korean</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Traditional Chinese</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Simplified Chinese</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Finnish</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Swedish</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Danish</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Norwegian</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Polish</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Portuguese (Brazil)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>English (United Kingdom)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Turkish</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Spanish (Latin America)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Arabic</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>French (Canada)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Czech</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Hungarian</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Greek</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Romanian</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Thai</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Vietnamese</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Indonesian</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="emulatorTabSpacerLeft">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Policy::MinimumExpanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="emulatorTabLayoutMiddle">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="vLayoutUserName">
|
||||
<property name="spacing">
|
||||
<number>6</number>
|
||||
<widget class="QGroupBox" name="emulatorSettingsGroupBox">
|
||||
<property name="title">
|
||||
<string>Emulator</string>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="hLayoutUserName">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="userName">
|
||||
<property name="title">
|
||||
<string>Username</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="userNameLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="userNameLineEdit"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="widgetSettingsTop" native="true">
|
||||
<layout class="QHBoxLayout" name="widgetGpuTopLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="additionalSettingsVLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="fullscreenCheckBox">
|
||||
<property name="text">
|
||||
<string>Enable Fullscreen</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="showSplashCheckBox">
|
||||
<property name="text">
|
||||
<string>Show Splash</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="ps4proCheckBox">
|
||||
<property name="text">
|
||||
<string>Is PS4 Pro</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="emulatorSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="widgetSettingsBottom" native="true">
|
||||
<layout class="QHBoxLayout" name="widgetGpuBottomLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="emulator_tab_layout_right_spacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Policy::MinimumExpanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="emulatorTabLayoutMiddle_2"/>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="emulatorTabLayoutRight">
|
||||
<property name="rightMargin">
|
||||
<number>12</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>12</number>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="loggerTabLayoutRight">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="loggerGroupBox">
|
||||
<property name="title">
|
||||
<string>Logger</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="loggerLayout">
|
||||
<item>
|
||||
<widget class="QWidget" name="LogTypeWidget" native="true">
|
||||
<layout class="QVBoxLayout" name="LogTypeLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="logTypeGroupBox">
|
||||
<property name="title">
|
||||
<string>Log Type</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="logTypeBoxLayout">
|
||||
<item>
|
||||
<widget class="QComboBox" name="logTypeComboBox">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>async</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>sync</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="vLayoutLogFilter">
|
||||
<property name="spacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="hLayoutLogFilter">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="logFilter">
|
||||
<property name="title">
|
||||
<string>Log Filter</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="logFilterLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="logFilterLineEdit"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="logSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="emulatorTabSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Policy::MinimumExpanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
<widget class="QWidget" name="widgetSettingsBottom" native="true">
|
||||
<layout class="QHBoxLayout" name="widgetGpuBottomLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
|
@ -348,7 +455,7 @@
|
|||
<attribute name="title">
|
||||
<string>GPU</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="gpuTabVLayout" stretch="0,0">
|
||||
<layout class="QVBoxLayout" name="gpuTabVLayout" stretch="0">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="gpuTabHLayout" stretch="1,1,1">
|
||||
<item>
|
||||
|
@ -627,208 +734,6 @@
|
|||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="gpuTabSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Policy::MinimumExpanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="generalTab">
|
||||
<attribute name="title">
|
||||
<string>General</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="generalTabVLayout" stretch="0,1">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="generalTabHLayout" stretch="1,1,1">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="generalTabLayoutLeft">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="emuSettings">
|
||||
<property name="title">
|
||||
<string>Emulator Settings</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="emuSettingsLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="fullscreenCheckBox">
|
||||
<property name="text">
|
||||
<string>Enable Fullscreen</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="showSplashCheckBox">
|
||||
<property name="text">
|
||||
<string>Show Splash</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="ps4proCheckBox">
|
||||
<property name="text">
|
||||
<string>Is PS4 Pro</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="emulatorTabSpacerLeft">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Policy::MinimumExpanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="emulatorTabLayoutMiddle">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="loggerGroupBox">
|
||||
<property name="title">
|
||||
<string>Logger Settings</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="loggerLayout">
|
||||
<item>
|
||||
<widget class="QWidget" name="LogTypeWidget" native="true">
|
||||
<layout class="QVBoxLayout" name="LogTypeLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="logTypeGroupBox">
|
||||
<property name="title">
|
||||
<string>Log Type</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="logTypeBoxLayout">
|
||||
<item>
|
||||
<widget class="QComboBox" name="logTypeComboBox">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>async</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>sync</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="vLayoutLogFilter">
|
||||
<property name="spacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="hLayoutLogFilter">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="logFilter">
|
||||
<property name="title">
|
||||
<string>Log Filter</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="logFilterLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="logFilterLineEdit"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="generalTabLayoutRight">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="additionalSettings">
|
||||
<property name="title">
|
||||
<string>Additional Settings</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="additionalSettingsVLayout">
|
||||
<item>
|
||||
<spacer name="emulatorTabSpacerRight">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Policy::MinimumExpanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="emulatorTabSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Policy::MinimumExpanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="debugTab">
|
||||
|
|
|
@ -323,7 +323,7 @@ static Id ComponentOffset(EmitContext& ctx, Id address, u32 stride, u32 bit_offs
|
|||
|
||||
static Id GetBufferFormatValue(EmitContext& ctx, u32 handle, Id address, u32 comp) {
|
||||
auto& buffer = ctx.buffers[handle];
|
||||
const auto format = buffer.buffer.GetDataFmt();
|
||||
const auto format = buffer.dfmt;
|
||||
switch (format) {
|
||||
case AmdGpu::DataFormat::FormatInvalid:
|
||||
return ctx.f32_zero_value;
|
||||
|
@ -348,7 +348,7 @@ static Id GetBufferFormatValue(EmitContext& ctx, u32 handle, Id address, u32 com
|
|||
|
||||
// uint index = address / 4;
|
||||
Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(2u));
|
||||
const u32 stride = buffer.buffer.GetStride();
|
||||
const u32 stride = buffer.stride;
|
||||
if (stride > 4) {
|
||||
const u32 index_offset = u32(AmdGpu::ComponentOffset(format, comp) / 32);
|
||||
if (index_offset > 0) {
|
||||
|
@ -360,7 +360,7 @@ static Id GetBufferFormatValue(EmitContext& ctx, u32 handle, Id address, u32 com
|
|||
|
||||
const u32 bit_offset = AmdGpu::ComponentOffset(format, comp) % 32;
|
||||
const u32 bit_width = AmdGpu::ComponentBits(format, comp);
|
||||
const auto num_format = buffer.buffer.GetNumberFmt();
|
||||
const auto num_format = buffer.nfmt;
|
||||
if (num_format == AmdGpu::NumberFormat::Float) {
|
||||
if (bit_width == 32) {
|
||||
return ctx.OpLoad(ctx.F32[1], ptr);
|
||||
|
@ -486,8 +486,8 @@ static Id ConvertF32ToFormat(EmitContext& ctx, Id value, AmdGpu::NumberFormat fo
|
|||
template <u32 N>
|
||||
static void EmitStoreBufferFormatF32xN(EmitContext& ctx, u32 handle, Id address, Id value) {
|
||||
auto& buffer = ctx.buffers[handle];
|
||||
const auto format = buffer.buffer.GetDataFmt();
|
||||
const auto num_format = buffer.buffer.GetNumberFmt();
|
||||
const auto format = buffer.dfmt;
|
||||
const auto num_format = buffer.nfmt;
|
||||
|
||||
switch (format) {
|
||||
case AmdGpu::DataFormat::FormatInvalid:
|
||||
|
|
|
@ -363,7 +363,9 @@ void EmitContext::DefineBuffers() {
|
|||
.binding = binding++,
|
||||
.data_types = data_types,
|
||||
.pointer_type = pointer_type,
|
||||
.buffer = buffer.GetVsharp(info),
|
||||
.dfmt = buffer.dfmt,
|
||||
.nfmt = buffer.nfmt,
|
||||
.stride = buffer.GetVsharp(info).GetStride(),
|
||||
});
|
||||
interfaces.push_back(id);
|
||||
i++;
|
||||
|
@ -395,6 +397,10 @@ spv::ImageFormat GetFormat(const AmdGpu::Image& image) {
|
|||
image.GetNumberFmt() == AmdGpu::NumberFormat::Float) {
|
||||
return spv::ImageFormat::R16f;
|
||||
}
|
||||
if (image.GetDataFmt() == AmdGpu::DataFormat::Format16 &&
|
||||
image.GetNumberFmt() == AmdGpu::NumberFormat::Uint) {
|
||||
return spv::ImageFormat::R16ui;
|
||||
}
|
||||
if (image.GetDataFmt() == AmdGpu::DataFormat::Format16_16 &&
|
||||
image.GetNumberFmt() == AmdGpu::NumberFormat::Float) {
|
||||
return spv::ImageFormat::Rg16f;
|
||||
|
|
|
@ -207,7 +207,9 @@ public:
|
|||
u32 binding;
|
||||
const VectorIds* data_types;
|
||||
Id pointer_type;
|
||||
AmdGpu::Buffer buffer;
|
||||
AmdGpu::DataFormat dfmt;
|
||||
AmdGpu::NumberFormat nfmt;
|
||||
u32 stride;
|
||||
};
|
||||
|
||||
u32& binding;
|
||||
|
|
|
@ -35,15 +35,22 @@ static IR::Condition MakeCondition(Opcode opcode) {
|
|||
return IR::Condition::Execz;
|
||||
case Opcode::S_CBRANCH_EXECNZ:
|
||||
return IR::Condition::Execnz;
|
||||
case Opcode::S_AND_SAVEEXEC_B64:
|
||||
case Opcode::S_ANDN2_B64:
|
||||
return IR::Condition::Execnz;
|
||||
default:
|
||||
return IR::Condition::True;
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr size_t LabelReserveSize = 32;
|
||||
|
||||
CFG::CFG(Common::ObjectPool<Block>& block_pool_, std::span<const GcnInst> inst_list_)
|
||||
: block_pool{block_pool_}, inst_list{inst_list_} {
|
||||
index_to_pc.resize(inst_list.size() + 1);
|
||||
labels.reserve(LabelReserveSize);
|
||||
EmitLabels();
|
||||
EmitDivergenceLabels();
|
||||
EmitBlocks();
|
||||
LinkBlocks();
|
||||
}
|
||||
|
@ -51,14 +58,7 @@ CFG::CFG(Common::ObjectPool<Block>& block_pool_, std::span<const GcnInst> inst_l
|
|||
void CFG::EmitLabels() {
|
||||
// Always set a label at entry point.
|
||||
u32 pc = 0;
|
||||
labels.push_back(pc);
|
||||
|
||||
const auto add_label = [this](u32 address) {
|
||||
const auto it = std::ranges::find(labels, address);
|
||||
if (it == labels.end()) {
|
||||
labels.push_back(address);
|
||||
}
|
||||
};
|
||||
AddLabel(pc);
|
||||
|
||||
// Iterate instruction list and add labels to branch targets.
|
||||
for (u32 i = 0; i < inst_list.size(); i++) {
|
||||
|
@ -66,15 +66,15 @@ void CFG::EmitLabels() {
|
|||
const GcnInst inst = inst_list[i];
|
||||
if (inst.IsUnconditionalBranch()) {
|
||||
const u32 target = inst.BranchTarget(pc);
|
||||
add_label(target);
|
||||
AddLabel(target);
|
||||
} else if (inst.IsConditionalBranch()) {
|
||||
const u32 true_label = inst.BranchTarget(pc);
|
||||
const u32 false_label = pc + inst.length;
|
||||
add_label(true_label);
|
||||
add_label(false_label);
|
||||
AddLabel(true_label);
|
||||
AddLabel(false_label);
|
||||
} else if (inst.opcode == Opcode::S_ENDPGM) {
|
||||
const u32 next_label = pc + inst.length;
|
||||
add_label(next_label);
|
||||
AddLabel(next_label);
|
||||
}
|
||||
pc += inst.length;
|
||||
}
|
||||
|
@ -84,16 +84,70 @@ void CFG::EmitLabels() {
|
|||
std::ranges::sort(labels);
|
||||
}
|
||||
|
||||
void CFG::EmitBlocks() {
|
||||
const auto get_index = [this](Label label) -> size_t {
|
||||
if (label == 0) {
|
||||
return 0ULL;
|
||||
}
|
||||
const auto it_index = std::ranges::lower_bound(index_to_pc, label);
|
||||
ASSERT(it_index != index_to_pc.end() || label > index_to_pc.back());
|
||||
return std::distance(index_to_pc.begin(), it_index);
|
||||
void CFG::EmitDivergenceLabels() {
|
||||
const auto is_open_scope = [](const GcnInst& inst) {
|
||||
// An open scope instruction is an instruction that modifies EXEC
|
||||
// but also saves the previous value to restore later. This indicates
|
||||
// we are entering a scope.
|
||||
return inst.opcode == Opcode::S_AND_SAVEEXEC_B64 ||
|
||||
// While this instruction does not save EXEC it is often used paired
|
||||
// with SAVEEXEC to mask the threads that didn't pass the condition
|
||||
// of initial branch.
|
||||
inst.opcode == Opcode::S_ANDN2_B64;
|
||||
};
|
||||
const auto is_close_scope = [](const GcnInst& inst) {
|
||||
// Closing an EXEC scope can be either a branch instruction
|
||||
// (typical case when S_AND_SAVEEXEC_B64 is right before a branch)
|
||||
// or by a move instruction to EXEC that restores the backup.
|
||||
return (inst.opcode == Opcode::S_MOV_B64 && inst.dst[0].field == OperandField::ExecLo) ||
|
||||
// Sometimes compiler might insert instructions between the SAVEEXEC and the branch.
|
||||
// Those instructions need to be wrapped in the condition as well so allow branch
|
||||
// as end scope instruction.
|
||||
inst.opcode == Opcode::S_CBRANCH_EXECZ || inst.opcode == Opcode::S_ANDN2_B64;
|
||||
};
|
||||
|
||||
// Since we will be adding new labels, avoid iterating those as well.
|
||||
const size_t end_size = labels.size();
|
||||
for (u32 l = 0; l < end_size; l++) {
|
||||
const Label start = labels[l];
|
||||
// Stop if we reached end of existing labels.
|
||||
if (l == end_size - 1) {
|
||||
break;
|
||||
}
|
||||
const Label end = labels[l + 1];
|
||||
const size_t end_index = GetIndex(end);
|
||||
|
||||
s32 curr_begin = -1;
|
||||
for (size_t index = GetIndex(start); index < end_index; index++) {
|
||||
const auto& inst = inst_list[index];
|
||||
if (is_close_scope(inst) && curr_begin != -1) {
|
||||
// If there are no instructions inside scope don't do anything.
|
||||
if (index - curr_begin == 1) {
|
||||
curr_begin = -1;
|
||||
continue;
|
||||
}
|
||||
// Add a label to the instruction right after the open scope call.
|
||||
// It is the start of a new basic block.
|
||||
const auto& save_inst = inst_list[curr_begin];
|
||||
const Label label = index_to_pc[curr_begin] + save_inst.length;
|
||||
AddLabel(label);
|
||||
// Add a label to the close scope instruction as well.
|
||||
AddLabel(index_to_pc[index]);
|
||||
// Reset scope begin.
|
||||
curr_begin = -1;
|
||||
}
|
||||
// Mark a potential start of an exec scope.
|
||||
if (is_open_scope(inst)) {
|
||||
curr_begin = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort labels to make sure block insertion is correct.
|
||||
std::ranges::sort(labels);
|
||||
}
|
||||
|
||||
void CFG::EmitBlocks() {
|
||||
for (auto it = labels.begin(); it != labels.end(); it++) {
|
||||
const Label start = *it;
|
||||
const auto next_it = std::next(it);
|
||||
|
@ -102,8 +156,10 @@ void CFG::EmitBlocks() {
|
|||
// Last label is special.
|
||||
return;
|
||||
}
|
||||
// The end label is the start instruction of next block.
|
||||
// The end instruction of this block is the previous one.
|
||||
const Label end = *next_it;
|
||||
const size_t end_index = get_index(end) - 1;
|
||||
const size_t end_index = GetIndex(end) - 1;
|
||||
const auto& end_inst = inst_list[end_index];
|
||||
|
||||
// Insert block between the labels using the last instruction
|
||||
|
@ -111,7 +167,7 @@ void CFG::EmitBlocks() {
|
|||
Block* block = block_pool.Create();
|
||||
block->begin = start;
|
||||
block->end = end;
|
||||
block->begin_index = get_index(start);
|
||||
block->begin_index = GetIndex(start);
|
||||
block->end_index = end_index;
|
||||
block->end_inst = end_inst;
|
||||
block->cond = MakeCondition(end_inst.opcode);
|
||||
|
@ -126,8 +182,26 @@ void CFG::LinkBlocks() {
|
|||
return &*it;
|
||||
};
|
||||
|
||||
for (auto& block : blocks) {
|
||||
for (auto it = blocks.begin(); it != blocks.end(); it++) {
|
||||
auto& block = *it;
|
||||
const auto end_inst{block.end_inst};
|
||||
// Handle divergence block inserted here.
|
||||
if (end_inst.opcode == Opcode::S_AND_SAVEEXEC_B64 ||
|
||||
end_inst.opcode == Opcode::S_ANDN2_B64) {
|
||||
// Blocks are stored ordered by address in the set
|
||||
auto next_it = std::next(it);
|
||||
auto* target_block = &(*next_it);
|
||||
++target_block->num_predecessors;
|
||||
block.branch_true = target_block;
|
||||
|
||||
auto merge_it = std::next(next_it);
|
||||
auto* merge_block = &(*merge_it);
|
||||
++merge_block->num_predecessors;
|
||||
block.branch_false = merge_block;
|
||||
block.end_class = EndClass::Branch;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the block doesn't end with a branch we simply
|
||||
// need to link with the next block.
|
||||
if (!end_inst.IsTerminateInstruction()) {
|
||||
|
|
|
@ -3,11 +3,13 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <boost/container/small_vector.hpp>
|
||||
#include <boost/intrusive/set.hpp>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/object_pool.h"
|
||||
#include "common/types.h"
|
||||
#include "shader_recompiler/frontend/instruction.h"
|
||||
|
@ -55,9 +57,26 @@ public:
|
|||
|
||||
private:
|
||||
void EmitLabels();
|
||||
void EmitDivergenceLabels();
|
||||
void EmitBlocks();
|
||||
void LinkBlocks();
|
||||
|
||||
void AddLabel(Label address) {
|
||||
const auto it = std::ranges::find(labels, address);
|
||||
if (it == labels.end()) {
|
||||
labels.push_back(address);
|
||||
}
|
||||
};
|
||||
|
||||
size_t GetIndex(Label label) {
|
||||
if (label == 0) {
|
||||
return 0ULL;
|
||||
}
|
||||
const auto it_index = std::ranges::lower_bound(index_to_pc, label);
|
||||
ASSERT(it_index != index_to_pc.end() || label > index_to_pc.back());
|
||||
return std::distance(index_to_pc.begin(), it_index);
|
||||
};
|
||||
|
||||
public:
|
||||
Common::ObjectPool<Block>& block_pool;
|
||||
std::span<const GcnInst> inst_list;
|
||||
|
|
|
@ -29,6 +29,8 @@ void Translator::EmitScalarAlu(const GcnInst& inst) {
|
|||
return S_CMP(ConditionOp::LG, true, inst);
|
||||
case Opcode::S_CMP_GT_I32:
|
||||
return S_CMP(ConditionOp::GT, true, inst);
|
||||
case Opcode::S_CMP_LE_I32:
|
||||
return S_CMP(ConditionOp::LE, true, inst);
|
||||
case Opcode::S_CMP_GE_I32:
|
||||
return S_CMP(ConditionOp::GE, true, inst);
|
||||
case Opcode::S_CMP_EQ_I32:
|
||||
|
|
|
@ -64,9 +64,15 @@ void Translator::EmitPrologue() {
|
|||
ir.SetVectorReg(dst_vreg++, ir.GetAttributeU32(IR::Attribute::LocalInvocationId, 1));
|
||||
ir.SetVectorReg(dst_vreg++, ir.GetAttributeU32(IR::Attribute::LocalInvocationId, 2));
|
||||
|
||||
ir.SetScalarReg(dst_sreg++, ir.GetAttributeU32(IR::Attribute::WorkgroupId, 0));
|
||||
ir.SetScalarReg(dst_sreg++, ir.GetAttributeU32(IR::Attribute::WorkgroupId, 1));
|
||||
ir.SetScalarReg(dst_sreg++, ir.GetAttributeU32(IR::Attribute::WorkgroupId, 2));
|
||||
if (info.tgid_enable[0]) {
|
||||
ir.SetScalarReg(dst_sreg++, ir.GetAttributeU32(IR::Attribute::WorkgroupId, 0));
|
||||
}
|
||||
if (info.tgid_enable[1]) {
|
||||
ir.SetScalarReg(dst_sreg++, ir.GetAttributeU32(IR::Attribute::WorkgroupId, 1));
|
||||
}
|
||||
if (info.tgid_enable[2]) {
|
||||
ir.SetScalarReg(dst_sreg++, ir.GetAttributeU32(IR::Attribute::WorkgroupId, 2));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw NotImplementedException("Unknown shader stage");
|
||||
|
|
|
@ -91,6 +91,11 @@ void Translator::EmitVectorMemory(const GcnInst& inst) {
|
|||
case Opcode::BUFFER_STORE_FORMAT_XYZW:
|
||||
return BUFFER_STORE_FORMAT(4, false, true, inst);
|
||||
|
||||
case Opcode::TBUFFER_STORE_FORMAT_X:
|
||||
return BUFFER_STORE_FORMAT(1, true, true, inst);
|
||||
case Opcode::TBUFFER_STORE_FORMAT_XYZ:
|
||||
return BUFFER_STORE_FORMAT(3, true, true, inst);
|
||||
|
||||
case Opcode::BUFFER_STORE_DWORD:
|
||||
return BUFFER_STORE_FORMAT(1, false, false, inst);
|
||||
case Opcode::BUFFER_STORE_DWORDX2:
|
||||
|
|
|
@ -105,7 +105,7 @@ struct fmt::formatter<Shader::IR::Attribute> {
|
|||
constexpr auto parse(format_parse_context& ctx) {
|
||||
return ctx.begin();
|
||||
}
|
||||
auto format(const Shader::IR::Attribute& attribute, format_context& ctx) const {
|
||||
auto format(const Shader::IR::Attribute attribute, format_context& ctx) const {
|
||||
return fmt::format_to(ctx.out(), "{}", Shader::IR::NameOf(attribute));
|
||||
}
|
||||
};
|
||||
|
|
|
@ -44,7 +44,7 @@ constexpr std::string_view NameOf(Condition condition) {
|
|||
|
||||
template <>
|
||||
struct fmt::formatter<Shader::IR::Condition> : formatter<std::string_view> {
|
||||
auto format(const Shader::IR::Condition& cond, format_context& ctx) const {
|
||||
auto format(const Shader::IR::Condition cond, format_context& ctx) const {
|
||||
return formatter<string_view>::format(NameOf(cond), ctx);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -129,19 +129,19 @@ IR::Opcode UndefOpcode(IR::VectorReg) noexcept {
|
|||
return IR::Opcode::UndefU32;
|
||||
}
|
||||
|
||||
IR::Opcode UndefOpcode(const VccLoTag&) noexcept {
|
||||
IR::Opcode UndefOpcode(const VccLoTag) noexcept {
|
||||
return IR::Opcode::UndefU32;
|
||||
}
|
||||
|
||||
IR::Opcode UndefOpcode(const SccLoTag&) noexcept {
|
||||
IR::Opcode UndefOpcode(const SccLoTag) noexcept {
|
||||
return IR::Opcode::UndefU32;
|
||||
}
|
||||
|
||||
IR::Opcode UndefOpcode(const VccHiTag&) noexcept {
|
||||
IR::Opcode UndefOpcode(const VccHiTag) noexcept {
|
||||
return IR::Opcode::UndefU32;
|
||||
}
|
||||
|
||||
IR::Opcode UndefOpcode(const FlagTag&) noexcept {
|
||||
IR::Opcode UndefOpcode(const FlagTag) noexcept {
|
||||
return IR::Opcode::UndefU1;
|
||||
}
|
||||
|
||||
|
|
|
@ -180,6 +180,7 @@ struct Info {
|
|||
SamplerResourceList samplers;
|
||||
|
||||
std::array<u32, 3> workgroup_size{};
|
||||
std::array<bool, 3> tgid_enable;
|
||||
|
||||
u32 num_user_data;
|
||||
u32 num_input_vgprs;
|
||||
|
@ -226,7 +227,7 @@ struct fmt::formatter<Shader::Stage> {
|
|||
constexpr auto parse(format_parse_context& ctx) {
|
||||
return ctx.begin();
|
||||
}
|
||||
auto format(const Shader::Stage& stage, format_context& ctx) const {
|
||||
auto format(const Shader::Stage stage, format_context& ctx) const {
|
||||
constexpr static std::array names = {"fs", "vs", "gs", "es", "hs", "ls", "cs"};
|
||||
return fmt::format_to(ctx.out(), "{}", names[static_cast<size_t>(stage)]);
|
||||
}
|
||||
|
|
|
@ -130,6 +130,7 @@ struct Liverpool {
|
|||
BitField<0, 6, u64> num_vgprs;
|
||||
BitField<6, 4, u64> num_sgprs;
|
||||
BitField<33, 5, u64> num_user_regs;
|
||||
BitField<39, 3, u64> tgid_enable;
|
||||
BitField<47, 9, u64> lds_dwords;
|
||||
} settings;
|
||||
INSERT_PADDING_WORDS(1);
|
||||
|
@ -148,6 +149,10 @@ struct Liverpool {
|
|||
return settings.lds_dwords.Value() * 128 * 4;
|
||||
}
|
||||
|
||||
bool IsTgidEnabled(u32 i) const noexcept {
|
||||
return (settings.tgid_enable.Value() >> i) & 1;
|
||||
}
|
||||
|
||||
std::span<const u32> Code() const {
|
||||
const u32* code = Address<u32*>();
|
||||
BinaryInfo bininfo;
|
||||
|
@ -933,7 +938,7 @@ struct Liverpool {
|
|||
std::array<ViewportScissor, NumViewports> viewport_scissors;
|
||||
std::array<ViewportDepth, NumViewports> viewport_depths;
|
||||
INSERT_PADDING_WORDS(0xA103 - 0xA0D4);
|
||||
u32 primitive_reset_index;
|
||||
u32 primitive_restart_index;
|
||||
INSERT_PADDING_WORDS(1);
|
||||
BlendConstants blend_constants;
|
||||
INSERT_PADDING_WORDS(0xA10B - 0xA105 - 4);
|
||||
|
@ -973,7 +978,9 @@ struct Liverpool {
|
|||
IndexBufferType index_buffer_type;
|
||||
INSERT_PADDING_WORDS(0xA2A1 - 0xA29E - 2);
|
||||
u32 enable_primitive_id;
|
||||
INSERT_PADDING_WORDS(0xA2A8 - 0xA2A1 - 1);
|
||||
INSERT_PADDING_WORDS(3);
|
||||
u32 enable_primitive_restart;
|
||||
INSERT_PADDING_WORDS(0xA2A8 - 0xA2A5 - 1);
|
||||
u32 vgt_instance_step_rate_0;
|
||||
u32 vgt_instance_step_rate_1;
|
||||
INSERT_PADDING_WORDS(0xA2D5 - 0xA2A9 - 1);
|
||||
|
@ -1160,7 +1167,7 @@ static_assert(GFX6_3D_REG_INDEX(depth_buffer.depth_slice) == 0xA017);
|
|||
static_assert(GFX6_3D_REG_INDEX(color_target_mask) == 0xA08E);
|
||||
static_assert(GFX6_3D_REG_INDEX(color_shader_mask) == 0xA08F);
|
||||
static_assert(GFX6_3D_REG_INDEX(viewport_scissors) == 0xA094);
|
||||
static_assert(GFX6_3D_REG_INDEX(primitive_reset_index) == 0xA103);
|
||||
static_assert(GFX6_3D_REG_INDEX(primitive_restart_index) == 0xA103);
|
||||
static_assert(GFX6_3D_REG_INDEX(stencil_control) == 0xA10B);
|
||||
static_assert(GFX6_3D_REG_INDEX(viewports) == 0xA10F);
|
||||
static_assert(GFX6_3D_REG_INDEX(clip_user_data) == 0xA16F);
|
||||
|
@ -1181,6 +1188,7 @@ static_assert(GFX6_3D_REG_INDEX(vs_output_control) == 0xA207);
|
|||
static_assert(GFX6_3D_REG_INDEX(index_size) == 0xA29D);
|
||||
static_assert(GFX6_3D_REG_INDEX(index_buffer_type) == 0xA29F);
|
||||
static_assert(GFX6_3D_REG_INDEX(enable_primitive_id) == 0xA2A1);
|
||||
static_assert(GFX6_3D_REG_INDEX(enable_primitive_restart) == 0xA2A5);
|
||||
static_assert(GFX6_3D_REG_INDEX(vgt_instance_step_rate_0) == 0xA2A8);
|
||||
static_assert(GFX6_3D_REG_INDEX(vgt_instance_step_rate_1) == 0xA2A9);
|
||||
static_assert(GFX6_3D_REG_INDEX(stage_enable) == 0xA2D5);
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
namespace VideoCore {
|
||||
|
||||
static constexpr size_t StagingBufferSize = 256_MB;
|
||||
static constexpr size_t StagingBufferSize = 512_MB;
|
||||
static constexpr size_t UboStreamBufferSize = 64_MB;
|
||||
|
||||
BufferCache::BufferCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_,
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <mutex>
|
||||
#include <boost/container/small_vector.hpp>
|
||||
#include <boost/icl/interval_map.hpp>
|
||||
|
|
|
@ -329,6 +329,7 @@ std::span<const vk::Format> GetAllFormats() {
|
|||
vk::Format::eR16G16Sint,
|
||||
vk::Format::eR16G16Snorm,
|
||||
vk::Format::eR16Sfloat,
|
||||
vk::Format::eR16Uint,
|
||||
vk::Format::eR16Unorm,
|
||||
vk::Format::eR32G32B32A32Sfloat,
|
||||
vk::Format::eR32G32B32A32Sint,
|
||||
|
|
|
@ -86,10 +86,10 @@ GraphicsPipeline::GraphicsPipeline(const Instance& instance_, Scheduler& schedul
|
|||
|
||||
const vk::PipelineInputAssemblyStateCreateInfo input_assembly = {
|
||||
.topology = LiverpoolToVK::PrimitiveType(key.prim_type),
|
||||
.primitiveRestartEnable = key.prim_restart_index != 0,
|
||||
.primitiveRestartEnable = key.enable_primitive_restart != 0,
|
||||
};
|
||||
ASSERT_MSG(key.prim_restart_index == 0 || key.prim_restart_index == 0xFFFF,
|
||||
"Primitive restart index other than 0xFFFF is not supported");
|
||||
ASSERT_MSG(!key.enable_primitive_restart || key.primitive_restart_index == 0xFFFF,
|
||||
"Primitive restart index other than 0xFFFF is not supported yet");
|
||||
|
||||
const vk::PipelineRasterizationStateCreateInfo raster_state = {
|
||||
.depthClampEnable = false,
|
||||
|
|
|
@ -39,7 +39,8 @@ struct GraphicsPipelineKey {
|
|||
Liverpool::StencilRefMask stencil_ref_front;
|
||||
Liverpool::StencilRefMask stencil_ref_back;
|
||||
Liverpool::PrimitiveType prim_type;
|
||||
u32 prim_restart_index;
|
||||
u32 enable_primitive_restart;
|
||||
u32 primitive_restart_index;
|
||||
Liverpool::PolygonMode polygon_mode;
|
||||
Liverpool::CullMode cull_mode;
|
||||
Liverpool::FrontFace front_face;
|
||||
|
|
|
@ -93,6 +93,8 @@ Shader::Info MakeShaderInfo(Shader::Stage stage, std::span<const u32, 16> user_d
|
|||
info.num_user_data = cs_pgm.settings.num_user_regs;
|
||||
info.workgroup_size = {cs_pgm.num_thread_x.full, cs_pgm.num_thread_y.full,
|
||||
cs_pgm.num_thread_z.full};
|
||||
info.tgid_enable = {cs_pgm.IsTgidEnabled(0), cs_pgm.IsTgidEnabled(1),
|
||||
cs_pgm.IsTgidEnabled(2)};
|
||||
info.shared_memory_size = cs_pgm.SharedMemSize();
|
||||
break;
|
||||
}
|
||||
|
@ -165,7 +167,8 @@ void PipelineCache::RefreshGraphicsKey() {
|
|||
key.stencil_ref_front = regs.stencil_ref_front;
|
||||
key.stencil_ref_back = regs.stencil_ref_back;
|
||||
key.prim_type = regs.primitive_type;
|
||||
key.prim_restart_index = regs.primitive_reset_index;
|
||||
key.enable_primitive_restart = regs.enable_primitive_restart & 1;
|
||||
key.primitive_restart_index = regs.primitive_restart_index;
|
||||
key.polygon_mode = regs.polygon_control.PolyMode();
|
||||
key.cull_mode = regs.polygon_control.CullingMode();
|
||||
key.clip_space = regs.clipper_control.clip_space;
|
||||
|
@ -323,6 +326,7 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline() {
|
|||
Shader::Info info =
|
||||
MakeShaderInfo(Shader::Stage::Compute, cs_pgm.user_data, liverpool->regs);
|
||||
info.pgm_base = cs_pgm.Address<uintptr_t>();
|
||||
info.pgm_hash = compute_key;
|
||||
auto program =
|
||||
Shader::TranslateProgram(inst_pool, block_pool, code, std::move(info), profile);
|
||||
|
||||
|
|
Loading…
Reference in New Issue