diff --git a/.gitmodules b/.gitmodules index 3a9d8f42..60fb5fbb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/CMakeLists.txt b/CMakeLists.txt index 3685b7f8..74e0b32f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/cmake/FindFFmpeg.cmake b/cmake/FindFFmpeg.cmake new file mode 100644 index 00000000..9c45844b --- /dev/null +++ b/cmake/FindFFmpeg.cmake @@ -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() diff --git a/cmake/FindRenderDoc.cmake b/cmake/FindRenderDoc.cmake new file mode 100644 index 00000000..e4cf8a6d --- /dev/null +++ b/cmake/FindRenderDoc.cmake @@ -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) diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 9ebdd878..0b19034d 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -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) diff --git a/externals/ffmpeg-core b/externals/ffmpeg-core new file mode 160000 index 00000000..e30b7d7f --- /dev/null +++ b/externals/ffmpeg-core @@ -0,0 +1 @@ +Subproject commit e30b7d7fe228bfb3f6e41ce1040b44a15eb7d5e0 diff --git a/src/audio_core/sdl_audio.cpp b/src/audio_core/sdl_audio.cpp index 141d338e..f544c52f 100644 --- a/src/audio_core/sdl_audio.cpp +++ b/src/audio_core/sdl_audio.cpp @@ -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 #include #include -#include "common/assert.h" -#include "core/libraries/error_codes.h" -#include "sdl_audio.h" + +#include // 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; diff --git a/src/audio_core/sdl_audio.h b/src/audio_core/sdl_audio.h index d20c4455..7844bd61 100644 --- a/src/audio_core/sdl_audio.h +++ b/src/audio_core/sdl_audio.h @@ -3,7 +3,7 @@ #pragma once -#include +#include #include #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 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 }; diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp index a514652d..2c4a20de 100644 --- a/src/common/logging/filter.cpp +++ b/src/common/logging/filter.cpp @@ -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) \ diff --git a/src/common/version.h b/src/common/version.h index 92fd18fb..80de187b 100644 --- a/src/common/version.h +++ b/src/common/version.h @@ -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 diff --git a/src/core/file_format/pkg.cpp b/src/core/file_format/pkg.cpp index 6d5fb0d4..336d8101 100644 --- a/src/core/file_format/pkg.cpp +++ b/src/core/file_format/pkg.cpp @@ -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; diff --git a/src/core/file_format/pkg.h b/src/core/file_format/pkg.h index 3fef6c1c..b6b09a19 100644 --- a/src/core/file_format/pkg.h +++ b/src/core/file_format/pkg.h @@ -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); diff --git a/src/core/file_format/trp.cpp b/src/core/file_format/trp.cpp index b4d4c95e..f122709e 100644 --- a/src/core/file_format/trp.cpp +++ b/src/core/file_format/trp.cpp @@ -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& 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)) { diff --git a/src/core/file_format/trp.h b/src/core/file_format/trp.h index 6d1f13bd..56f49002 100644 --- a/src/core/file_format/trp.h +++ b/src/core/file_format/trp.h @@ -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; diff --git a/src/core/file_sys/fs.cpp b/src/core/file_sys/fs.cpp index a6d5c3ea..40d8212b 100644 --- a/src/core/file_sys/fs.cpp +++ b/src/core/file_sys/fs.cpp @@ -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, "/"); diff --git a/src/core/file_sys/fs.h b/src/core/file_sys/fs.h index d636f8bf..b0fb6324 100644 --- a/src/core/file_sys/fs.h +++ b/src/core/file_sys/fs.h @@ -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( diff --git a/src/core/libraries/audio/audioout.cpp b/src/core/libraries/audio/audioout.cpp index eac3845f..cb676afc 100644 --- a/src/core/libraries/audio/audioout.cpp +++ b/src/core/libraries/audio/audioout.cpp @@ -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(); - LOG_INFO(Lib_AudioOut, "called"); return ORBIS_OK; } diff --git a/src/core/libraries/avplayer/avplayer.cpp b/src/core/libraries/avplayer/avplayer.cpp index dd9f42b2..bd1f6b50 100644 --- a/src/core/libraries/avplayer/avplayer.cpp +++ b/src/core/libraries/avplayer/avplayer.cpp @@ -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; } diff --git a/src/core/libraries/avplayer/avplayer.h b/src/core/libraries/avplayer/avplayer.h index 39a619ee..360f06b6 100644 --- a/src/core/libraries/avplayer/avplayer.h +++ b/src/core/libraries/avplayer/avplayer.h @@ -5,39 +5,288 @@ #include "common/types.h" +#include // va_list +#include // 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 \ No newline at end of file + +} // namespace Libraries::AvPlayer diff --git a/src/core/libraries/avplayer/avplayer_common.cpp b/src/core/libraries/avplayer/avplayer_common.cpp new file mode 100644 index 00000000..306603e2 --- /dev/null +++ b/src/core/libraries/avplayer/avplayer_common.cpp @@ -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 // std::equal +#include // std::tolower +#include // std::string_view + +namespace Libraries::AvPlayer { + +using namespace Kernel; + +static bool ichar_equals(char a, char b) { + return std::tolower(static_cast(a)) == + std::tolower(static_cast(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 diff --git a/src/core/libraries/avplayer/avplayer_common.h b/src/core/libraries/avplayer/avplayer_common.h new file mode 100644 index 00000000..a53696ec --- /dev/null +++ b/src/core/libraries/avplayer/avplayer_common.h @@ -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 +#include +#include + +#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 AvPlayerQueue { +public: + size_t Size() { + return m_queue.size(); + } + + void Push(T&& value) { + std::lock_guard guard(m_mutex); + m_queue.emplace(std::forward(value)); + } + + std::optional 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 m_queue{}; +}; + +SceAvPlayerSourceType GetSourceType(std::string_view path); + +} // namespace Libraries::AvPlayer diff --git a/src/core/libraries/avplayer/avplayer_data_streamer.h b/src/core/libraries/avplayer/avplayer_data_streamer.h new file mode 100644 index 00000000..04097bb4 --- /dev/null +++ b/src/core/libraries/avplayer/avplayer_data_streamer.h @@ -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 diff --git a/src/core/libraries/avplayer/avplayer_file_streamer.cpp b/src/core/libraries/avplayer/avplayer_file_streamer.cpp new file mode 100644 index 00000000..dc1386a4 --- /dev/null +++ b/src/core/libraries/avplayer/avplayer_file_streamer.cpp @@ -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 + +extern "C" { +#include +#include +} + +#include // 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(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(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(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 diff --git a/src/core/libraries/avplayer/avplayer_file_streamer.h b/src/core/libraries/avplayer/avplayer_file_streamer.h new file mode 100644 index 00000000..658ce8c1 --- /dev/null +++ b/src/core/libraries/avplayer/avplayer_file_streamer.h @@ -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 +#include + +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 diff --git a/src/core/libraries/avplayer/avplayer_impl.cpp b/src/core/libraries/avplayer/avplayer_impl.cpp new file mode 100644 index 00000000..cdfff827 --- /dev/null +++ b/src/core/libraries/avplayer/avplayer_impl.cpp @@ -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(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(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(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(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(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(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(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(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(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 diff --git a/src/core/libraries/avplayer/avplayer_impl.h b/src/core/libraries/avplayer/avplayer_impl.h new file mode 100644 index 00000000..09989d39 --- /dev/null +++ b/src/core/libraries/avplayer/avplayer_impl.h @@ -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 + +extern "C" { +#include +#include +} + +#include +#include + +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 m_state{}; +}; + +} // namespace Libraries::AvPlayer diff --git a/src/core/libraries/avplayer/avplayer_source.cpp b/src/core/libraries/avplayer/avplayer_source.cpp new file mode 100644 index 00000000..776d389f --- /dev/null +++ b/src/core/libraries/avplayer/avplayer_source.cpp @@ -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 + +extern "C" { +#include +#include +#include +#include +#include +} + +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(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::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 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(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(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(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(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(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 diff --git a/src/core/libraries/avplayer/avplayer_source.h b/src/core/libraries/avplayer/avplayer_source.h new file mode 100644 index 00000000..7144e7ee --- /dev/null +++ b/src/core/libraries/avplayer/avplayer_source.h @@ -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 +#include +#include +#include +#include +#include + +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( + 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 + void Wait(Pred pred) { + std::unique_lock lock(m_mutex); + m_cv.wait(lock, std::move(pred)); + } + + template + 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 + bool WaitFor(std::chrono::duration 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 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; + using AVFramePtr = std::unique_ptr; + using AVCodecContextPtr = std::unique_ptr; + using SWRContextPtr = std::unique_ptr; + using SWSContextPtr = std::unique_ptr; + using AVFormatContextPtr = std::unique_ptr; + + 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 m_up_data_streamer; + + AvPlayerQueue m_audio_buffers; + AvPlayerQueue m_video_buffers; + + AvPlayerQueue m_audio_packets; + AvPlayerQueue m_video_packets; + + AvPlayerQueue m_audio_frames; + AvPlayerQueue m_video_frames; + + std::optional m_current_video_frame; + std::optional m_current_audio_frame; + + std::optional m_video_stream_index{}; + std::optional 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 diff --git a/src/core/libraries/avplayer/avplayer_state.cpp b/src/core/libraries/avplayer/avplayer_state.cpp new file mode 100644 index 00000000..884cd940 --- /dev/null +++ b/src/core/libraries/avplayer/avplayer_state.cpp @@ -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 + +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(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(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(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(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(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(*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 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 diff --git a/src/core/libraries/avplayer/avplayer_state.h b/src/core/libraries/avplayer/avplayer_state.h new file mode 100644 index 00000000..ff80b6ce --- /dev/null +++ b/src/core/libraries/avplayer/avplayer_state.h @@ -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 +#include +#include + +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 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 m_up_source; + + SceAvPlayerInitData m_init_data{}; + SceAvPlayerEventReplacement m_event_replacement{}; + bool m_auto_start{}; + u8 m_default_language[4]{}; + + std::atomic m_current_state; + std::atomic 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 m_event_queue{}; +}; + +} // namespace Libraries::AvPlayer diff --git a/src/core/libraries/error_codes.h b/src/core/libraries/error_codes.h index 5eabaaf6..74aeef67 100644 --- a/src/core/libraries/error_codes.h +++ b/src/core/libraries/error_codes.h @@ -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; diff --git a/src/core/libraries/kernel/event_queues.cpp b/src/core/libraries/kernel/event_queues.cpp index bb3d8ba7..540c20c4 100644 --- a/src/core/libraries/kernel/event_queues.cpp +++ b/src/core/libraries/kernel/event_queues.cpp @@ -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 diff --git a/src/core/libraries/kernel/event_queues.h b/src/core/libraries/kernel/event_queues.h index 0f9c42a9..d400ff18 100644 --- a/src/core/libraries/kernel/event_queues.h +++ b/src/core/libraries/kernel/event_queues.h @@ -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 diff --git a/src/core/libraries/kernel/file_system.cpp b/src/core/libraries/kernel/file_system.cpp index f8386347..990b11d6 100644 --- a/src/core/libraries/kernel/file_system.cpp +++ b/src/core/libraries/kernel/file_system.cpp @@ -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); diff --git a/src/core/libraries/kernel/libkernel.cpp b/src/core/libraries/kernel/libkernel.cpp index 460fc427..d56f4dc4 100644 --- a/src/core/libraries/kernel/libkernel.cpp +++ b/src/core/libraries/kernel/libkernel.cpp @@ -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); diff --git a/src/core/libraries/kernel/libkernel.h b/src/core/libraries/kernel/libkernel.h index 5b22dea4..5b7f1e72 100644 --- a/src/core/libraries/kernel/libkernel.h +++ b/src/core/libraries/kernel/libkernel.h @@ -14,6 +14,7 @@ namespace Libraries::Kernel { void ErrSceToPosix(int result); int ErrnoToSceKernelError(int e); +void SetPosixErrno(int e); struct OrbisTimesec { time_t t; diff --git a/src/core/libraries/kernel/thread_management.cpp b/src/core/libraries/kernel/thread_management.cpp index 6319b7c2..68953269 100644 --- a/src/core/libraries/kernel/thread_management.cpp +++ b/src/core/libraries/kernel/thread_management.cpp @@ -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) { diff --git a/src/core/libraries/network/net.cpp b/src/core/libraries/network/net.cpp index 958f9264..2c03dde3 100644 --- a/src/core/libraries/network/net.cpp +++ b/src/core/libraries/network/net.cpp @@ -10,7 +10,7 @@ #include #endif -#include +#include "common/assert.h" #include "common/logging/log.h" #include "core/libraries/error_codes.h" #include "core/libraries/libs.h" diff --git a/src/core/libraries/pad/pad.cpp b/src/core/libraries/pad/pad.cpp index c9e332d2..305b20bd 100644 --- a/src/core/libraries/pad/pad.cpp +++ b/src/core/libraries/pad/pad.cpp @@ -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::Instance(); controller->SetLightBarRGB(pParam->r, pParam->g, pParam->b); return ORBIS_OK; diff --git a/src/core/libraries/pad/pad.h b/src/core/libraries/pad/pad.h index 4f854290..3e9c14a1 100644 --- a/src/core/libraries/pad/pad.h +++ b/src/core/libraries/pad/pad.h @@ -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); diff --git a/src/core/libraries/save_data/savedata.cpp b/src/core/libraries/save_data/savedata.cpp index 64237994..20496d76 100644 --- a/src/core/libraries/save_data/savedata.cpp +++ b/src/core/libraries/save_data/savedata.cpp @@ -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) / diff --git a/src/core/libraries/videoout/video_out.cpp b/src/core/libraries/videoout/video_out.cpp index acfcbad4..d13062cd 100644 --- a/src/core/libraries/videoout/video_out.cpp +++ b/src/core/libraries/videoout/video_out.cpp @@ -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); diff --git a/src/core/libraries/videoout/video_out.h b/src/core/libraries/videoout/video_out.h index b4423efd..63cd8fed 100644 --- a/src/core/libraries/videoout/video_out.h +++ b/src/core/libraries/videoout/video_out.h @@ -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); diff --git a/src/qt_gui/about_dialog.cpp b/src/qt_gui/about_dialog.cpp new file mode 100644 index 00000000..a932d65a --- /dev/null +++ b/src/qt_gui/about_dialog.cpp @@ -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; +} diff --git a/src/qt_gui/about_dialog.h b/src/qt_gui/about_dialog.h new file mode 100644 index 00000000..8c802221 --- /dev/null +++ b/src/qt_gui/about_dialog.h @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +namespace Ui { +class AboutDialog; +} + +class AboutDialog : public QDialog { + Q_OBJECT + +public: + explicit AboutDialog(QWidget* parent = nullptr); + ~AboutDialog(); + +private: + Ui::AboutDialog* ui; +}; \ No newline at end of file diff --git a/src/qt_gui/about_dialog.ui b/src/qt_gui/about_dialog.ui new file mode 100644 index 00000000..2b60476b --- /dev/null +++ b/src/qt_gui/about_dialog.ui @@ -0,0 +1,110 @@ + + + + AboutDialog + + + + 0 + 0 + 780 + 320 + + + + About shadPS4 + + + + :/images/shadps4.ico:/images/shadps4.ico + + + + + 10 + 30 + 271 + 261 + + + + QFrame::Shape::NoFrame + + + + + + :/images/shadps4.ico + + + true + + + true + + + + + + 310 + 40 + 171 + 41 + + + + + 24 + true + + + + shadPS4 + + + + + + 310 + 90 + 451 + 101 + + + + + 14 + + + + shadPS4 is an experimental open-source emulator for the PlayStation 4. + + + true + + + + + + 310 + 180 + 451 + 101 + + + + + 14 + + + + This software should not be used to play games you have not legally obtained. + + + true + + + + + diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index aec2e7a5..f862c064 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -9,6 +9,7 @@ #include #include +#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(); diff --git a/src/qt_gui/main_window.h b/src/qt_gui/main_window.h index 35fd0bf6..3aa4453e 100644 --- a/src/qt_gui/main_window.h +++ b/src/qt_gui/main_window.h @@ -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; diff --git a/src/qt_gui/main_window_ui.h b/src/qt_gui/main_window_ui.h index 06e5cf7f..b7132c64 100644 --- a/src/qt_gui/main_window_ui.h +++ b/src/qt_gui/main_window_ui.h @@ -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)); diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index fd2df0fc..ca47f331 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -39,13 +39,28 @@ SettingsDialog::SettingsDialog(std::span 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 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, diff --git a/src/qt_gui/settings_dialog.ui b/src/qt_gui/settings_dialog.ui index 148799c5..11ba38d0 100644 --- a/src/qt_gui/settings_dialog.ui +++ b/src/qt_gui/settings_dialog.ui @@ -1,7 +1,6 @@ - SettingsDialog @@ -12,8 +11,8 @@ 0 0 - 1024 - 768 + 854 + 480 @@ -22,6 +21,12 @@ 0 + + + 11 + false + + Settings @@ -46,8 +51,8 @@ 0 0 - 1006 - 720 + 832 + 418 @@ -59,288 +64,390 @@ 0 - + - Emulator + General - + - + - + - + - Console Language + System - + - + + + 6 + + + 0 + - - Japanese - - - - - English (United States) - - - - - French (France) - - - - - Spanish (Spain) - - - - - German - - - - - Italian - - - - - Dutch - - - - - Portuguese (Portugal) - - - - - Russian - - - - - Korean - - - - - Traditional Chinese - - - - - Simplified Chinese - - - - - Finnish - - - - - Swedish - - - - - Danish - - - - - Norwegian - - - - - Polish - - - - - Portuguese (Brazil) - - - - - English (United Kingdom) - - - - - Turkish - - - - - Spanish (Latin America) - - - - - Arabic - - - - - French (Canada) - - - - - Czech - - - - - Hungarian - - - - - Greek - - - - - Romanian - - - - - Thai - - - - - Vietnamese - - - - - Indonesian - + + + + + Username + + + + + + + + + + + + + + + Console Language + + + + + + + Japanese + + + + + English (United States) + + + + + French (France) + + + + + Spanish (Spain) + + + + + German + + + + + Italian + + + + + Dutch + + + + + Portuguese (Portugal) + + + + + Russian + + + + + Korean + + + + + Traditional Chinese + + + + + Simplified Chinese + + + + + Finnish + + + + + Swedish + + + + + Danish + + + + + Norwegian + + + + + Polish + + + + + Portuguese (Brazil) + + + + + English (United Kingdom) + + + + + Turkish + + + + + Spanish (Latin America) + + + + + Arabic + + + + + French (Canada) + + + + + Czech + + + + + Hungarian + + + + + Greek + + + + + Romanian + + + + + Thai + + + + + Vietnamese + + + + + Indonesian + + + + + + + + + Qt::Orientation::Vertical + + + QSizePolicy::Policy::MinimumExpanding + + + + 0 + 0 + + + + + + + + - - - 6 + + + Emulator - - 0 - - - - - - - Username - - - - - - - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - + + + + + Enable Fullscreen + + + + + + + Show Splash + + + + + + + Is PS4 Pro + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - Qt::Orientation::Vertical - - - QSizePolicy::Policy::MinimumExpanding - - - - 0 - 0 - - - - - - - - - - 12 - - - 12 - + + + + + Logger + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Log Type + + + + + + + async + + + + + sync + + + + + + + + + + + + + + 6 + + + 0 + + + + + + + Log Filter + + + + + + + + + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + - - - Qt::Orientation::Vertical - - - QSizePolicy::Policy::MinimumExpanding - - - - 0 - 0 - - - + + + + 0 + + + 0 + + + 0 + + + 0 + + + @@ -348,7 +455,7 @@ GPU - + @@ -627,208 +734,6 @@ - - - - Qt::Orientation::Vertical - - - QSizePolicy::Policy::MinimumExpanding - - - - 0 - 0 - - - - - - - - - General - - - - - - - - - - Emulator Settings - - - - - - Enable Fullscreen - - - - - - - Show Splash - - - - - - - Is PS4 Pro - - - - - - - Qt::Orientation::Vertical - - - QSizePolicy::Policy::MinimumExpanding - - - - 0 - 0 - - - - - - - - - - - - - - - Logger Settings - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Log Type - - - - - - - async - - - - - sync - - - - - - - - - - - - - - 6 - - - 0 - - - - - - - Log Filter - - - - - - - - - - - - - - - - - - - - - - - Additional Settings - - - - - - Qt::Orientation::Vertical - - - QSizePolicy::Policy::MinimumExpanding - - - - 0 - 0 - - - - - - - - - - - - - - - Qt::Orientation::Vertical - - - QSizePolicy::Policy::MinimumExpanding - - - - 0 - 0 - - - - diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp index bbf259fe..f933ed3c 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp @@ -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 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: diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index 4b732ecd..d61e108f 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -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; diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h index 81237a9a..768b591f 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h @@ -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; diff --git a/src/shader_recompiler/frontend/control_flow_graph.cpp b/src/shader_recompiler/frontend/control_flow_graph.cpp index 2925c05d..4f3ab86e 100644 --- a/src/shader_recompiler/frontend/control_flow_graph.cpp +++ b/src/shader_recompiler/frontend/control_flow_graph.cpp @@ -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_pool_, std::span 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_pool_, std::span 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()) { diff --git a/src/shader_recompiler/frontend/control_flow_graph.h b/src/shader_recompiler/frontend/control_flow_graph.h index ebe614ee..d98d4b05 100644 --- a/src/shader_recompiler/frontend/control_flow_graph.h +++ b/src/shader_recompiler/frontend/control_flow_graph.h @@ -3,11 +3,13 @@ #pragma once +#include #include #include #include #include +#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_pool; std::span inst_list; diff --git a/src/shader_recompiler/frontend/translate/scalar_alu.cpp b/src/shader_recompiler/frontend/translate/scalar_alu.cpp index 795b148d..812d93ba 100644 --- a/src/shader_recompiler/frontend/translate/scalar_alu.cpp +++ b/src/shader_recompiler/frontend/translate/scalar_alu.cpp @@ -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: diff --git a/src/shader_recompiler/frontend/translate/translate.cpp b/src/shader_recompiler/frontend/translate/translate.cpp index d48e4def..4070560a 100644 --- a/src/shader_recompiler/frontend/translate/translate.cpp +++ b/src/shader_recompiler/frontend/translate/translate.cpp @@ -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"); diff --git a/src/shader_recompiler/frontend/translate/vector_memory.cpp b/src/shader_recompiler/frontend/translate/vector_memory.cpp index 63f6c3b4..01a549f4 100644 --- a/src/shader_recompiler/frontend/translate/vector_memory.cpp +++ b/src/shader_recompiler/frontend/translate/vector_memory.cpp @@ -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: diff --git a/src/shader_recompiler/ir/attribute.h b/src/shader_recompiler/ir/attribute.h index 3f95ff7a..2c67411f 100644 --- a/src/shader_recompiler/ir/attribute.h +++ b/src/shader_recompiler/ir/attribute.h @@ -105,7 +105,7 @@ struct fmt::formatter { 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)); } }; diff --git a/src/shader_recompiler/ir/condition.h b/src/shader_recompiler/ir/condition.h index 4b60be67..da986c48 100644 --- a/src/shader_recompiler/ir/condition.h +++ b/src/shader_recompiler/ir/condition.h @@ -44,7 +44,7 @@ constexpr std::string_view NameOf(Condition condition) { template <> struct fmt::formatter : formatter { - auto format(const Shader::IR::Condition& cond, format_context& ctx) const { + auto format(const Shader::IR::Condition cond, format_context& ctx) const { return formatter::format(NameOf(cond), ctx); } }; diff --git a/src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp b/src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp index 80591492..eef73a65 100644 --- a/src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp +++ b/src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp @@ -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; } diff --git a/src/shader_recompiler/runtime_info.h b/src/shader_recompiler/runtime_info.h index 9b592e12..b1eb6aea 100644 --- a/src/shader_recompiler/runtime_info.h +++ b/src/shader_recompiler/runtime_info.h @@ -180,6 +180,7 @@ struct Info { SamplerResourceList samplers; std::array workgroup_size{}; + std::array tgid_enable; u32 num_user_data; u32 num_input_vgprs; @@ -226,7 +227,7 @@ struct fmt::formatter { 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(stage)]); } diff --git a/src/video_core/amdgpu/liverpool.h b/src/video_core/amdgpu/liverpool.h index 706da8ec..92a24795 100644 --- a/src/video_core/amdgpu/liverpool.h +++ b/src/video_core/amdgpu/liverpool.h @@ -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 Code() const { const u32* code = Address(); BinaryInfo bininfo; @@ -933,7 +938,7 @@ struct Liverpool { std::array viewport_scissors; std::array 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); diff --git a/src/video_core/buffer_cache/buffer_cache.cpp b/src/video_core/buffer_cache/buffer_cache.cpp index 2246807a..02d6b2ce 100644 --- a/src/video_core/buffer_cache/buffer_cache.cpp +++ b/src/video_core/buffer_cache/buffer_cache.cpp @@ -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_, diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index 33ea3f86..2bcc4f0e 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -3,7 +3,6 @@ #pragma once -#include #include #include #include diff --git a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp index 4fc32ab2..e86d0652 100644 --- a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp +++ b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp @@ -329,6 +329,7 @@ std::span GetAllFormats() { vk::Format::eR16G16Sint, vk::Format::eR16G16Snorm, vk::Format::eR16Sfloat, + vk::Format::eR16Uint, vk::Format::eR16Unorm, vk::Format::eR32G32B32A32Sfloat, vk::Format::eR32G32B32A32Sint, diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 0c516dba..6bfe471c 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -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, diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h index fc507091..bc8e9913 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h @@ -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; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 0a94ce6d..c11705e7 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -93,6 +93,8 @@ Shader::Info MakeShaderInfo(Shader::Stage stage, std::span 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 PipelineCache::CreateComputePipeline() { Shader::Info info = MakeShaderInfo(Shader::Stage::Compute, cs_pgm.user_data, liverpool->regs); info.pgm_base = cs_pgm.Address(); + info.pgm_hash = compute_key; auto program = Shader::TranslateProgram(inst_pool, block_pool, code, std::move(info), profile);