diff --git a/CMakeLists.txt b/CMakeLists.txt index 1345e50e5..914e25e40 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -227,6 +227,8 @@ find_package(fmt 10.2.0 CONFIG) find_package(glslang 15 CONFIG) find_package(half 1.12.0 MODULE) find_package(magic_enum 0.9.7 CONFIG) +find_package(miniz 3.1 CONFIG) +find_package(nlohmann_json 3.12 CONFIG) find_package(PNG 1.6 MODULE) find_package(OpenAL CONFIG) find_package(RenderDoc 1.6.0 MODULE) @@ -1119,7 +1121,7 @@ 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 FFmpeg::ffmpeg Dear_ImGui gcn half::half ZLIB::ZLIB PNG::PNG) target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator LibAtrac9 sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::glslang SDL3::SDL3 SDL3_mixer::SDL3_mixer pugixml::pugixml) -target_link_libraries(shadps4 PRIVATE stb::headers libusb::usb lfreist-hwinfo::hwinfo nlohmann_json::nlohmann_json miniz fdk-aac CLI11::CLI11 OpenAL::OpenAL Cpp_Httplib) +target_link_libraries(shadps4 PRIVATE stb::headers libusb::usb lfreist-hwinfo::hwinfo nlohmann_json::nlohmann_json miniz::miniz fdk-aac CLI11::CLI11 OpenAL::OpenAL Cpp_Httplib) target_compile_definitions(shadps4 PRIVATE IMGUI_USER_CONFIG="imgui/imgui_config.h") target_compile_definitions(Dear_ImGui PRIVATE IMGUI_USER_CONFIG="${PROJECT_SOURCE_DIR}/src/imgui/imgui_config.h") diff --git a/documents/Debugging/Debugging.md b/documents/Debugging/Debugging.md index 8bb4b8fbd..013ca15fb 100644 --- a/documents/Debugging/Debugging.md +++ b/documents/Debugging/Debugging.md @@ -73,6 +73,8 @@ You can configure the emulator by editing the `config.toml` file found in the `u - Examples: - If the log is being spammed with messages coming from Lib.Pad, you can use `Lib.Pad:Critical` to only log critical-level messages. - If you'd like to mute everything, but still want to receive messages from Vulkan rendering: `*:Critical Render.Vulkan:Info` + - `isIdenticalLogGrouped`: Group same logs in one line with a counter (`true`/`false`) + - By default, the emulator will not rewrite the same line, and instead add a counter. - `Fullscreen`: Display the game in a full screen borderless window. diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 7f6e6ec4e..41a0f71c7 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -259,16 +259,19 @@ if (WIN32) add_subdirectory(ext-wepoll) endif() -if (NOT TARGET fdk-aac) add_subdirectory(aacdec) -endif() #nlohmann json +if (NOT TARGET nlohmann_json::nlohmann_json) set(JSON_BuildTests OFF CACHE INTERNAL "") add_subdirectory(json) +endif() # miniz +if (NOT TARGET miniz::miniz) add_subdirectory(miniz) +add_library(miniz::miniz ALIAS miniz) +endif() # cli11 if (NOT TARGET CLI11::CLI11) diff --git a/src/common/config.cpp b/src/common/config.cpp index a5eea0a64..fb1181d62 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -141,6 +141,7 @@ static ConfigEntry isTrophyPopupDisabled(false); static ConfigEntry trophyNotificationDuration(6.0); static ConfigEntry logFilter(""); static ConfigEntry logType("sync"); +static ConfigEntry isIdenticalLogGrouped(true); static ConfigEntry userName("shadPS4"); static ConfigEntry isShowSplash(false); static ConfigEntry isSideTrophy("right"); @@ -395,6 +396,10 @@ string getLogType() { return logType.get(); } +bool groupIdenticalLogs() { + return isIdenticalLogGrouped.get(); +} + string getUserName() { return userName.get(); } @@ -694,6 +699,10 @@ void setLogType(const string& type, bool is_game_specific) { logType.set(type, is_game_specific); } +void setIdenticalLogGrouped(bool enable, bool is_game_specific) { + isIdenticalLogGrouped.set(enable, is_game_specific); +} + void setLogFilter(const string& type, bool is_game_specific) { logFilter.set(type, is_game_specific); } @@ -893,6 +902,7 @@ void load(const std::filesystem::path& path, bool is_game_specific) { enableDiscordRPC = toml::find_or(general, "enableDiscordRPC", enableDiscordRPC); logFilter.setFromToml(general, "logFilter", is_game_specific); logType.setFromToml(general, "logType", is_game_specific); + isIdenticalLogGrouped.setFromToml(general, "isIdenticalLogGrouped", is_game_specific); userName.setFromToml(general, "userName", is_game_specific); isShowSplash.setFromToml(general, "showSplash", is_game_specific); isSideTrophy.setFromToml(general, "sideTrophy", is_game_specific); @@ -1081,6 +1091,7 @@ void save(const std::filesystem::path& path, bool is_game_specific) { is_game_specific); logFilter.setTomlValue(data, "General", "logFilter", is_game_specific); logType.setTomlValue(data, "General", "logType", is_game_specific); + isIdenticalLogGrouped.setTomlValue(data, "General", "isIdenticalLogGrouped", is_game_specific); userName.setTomlValue(data, "General", "userName", is_game_specific); isShowSplash.setTomlValue(data, "General", "showSplash", is_game_specific); isSideTrophy.setTomlValue(data, "General", "sideTrophy", is_game_specific); @@ -1224,6 +1235,7 @@ void setDefaultValues(bool is_game_specific) { trophyNotificationDuration.set(6.0, is_game_specific); logFilter.set("", is_game_specific); logType.set("sync", is_game_specific); + isIdenticalLogGrouped.set("isIdenticalLogGrouped", is_game_specific); userName.set("shadPS4", is_game_specific); isShowSplash.set(false, is_game_specific); isSideTrophy.set("right", is_game_specific); diff --git a/src/common/config.h b/src/common/config.h index a9e0f7010..eb2b91f52 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -101,6 +101,8 @@ void setPipelineCacheEnabled(bool enable, bool is_game_specific = false); void setPipelineCacheArchived(bool enable, bool is_game_specific = false); std::string getLogType(); void setLogType(const std::string& type, bool is_game_specific = false); +bool groupIdenticalLogs(); +void setGroupIdenticalLogs(bool enable, bool is_game_specific = false); std::string getLogFilter(); void setLogFilter(const std::string& type, bool is_game_specific = false); double getTrophyNotificationDuration(); diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index 168350b96..930b1ac30 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -70,7 +70,7 @@ public: ~FileBackend() = default; void Write(const Entry& entry) { - if (!enabled) { + if (!enabled && entry.log_level != Level::Critical) { return; } @@ -209,41 +209,62 @@ public: } } - std::unique_lock entry_loc(_mutex); - - if (_last_entry.message == message) { - ++_last_entry.counter; - return; - } - - if (_last_entry.counter >= 2) { - _last_entry.message += " x" + std::to_string(_last_entry.counter); - } - - if (_last_entry.counter >= 1) { - if (Config::getLogType() == "async") { - message_queue.EmplaceWait(_last_entry); - } else { - ForEachBackend([this](auto& backend) { backend.Write(this->_last_entry); }); - std::fflush(stdout); - } - } - using std::chrono::duration_cast; using std::chrono::microseconds; using std::chrono::steady_clock; - this->_last_entry = { - .timestamp = duration_cast(steady_clock::now() - time_origin), - .log_class = log_class, - .log_level = log_level, - .filename = filename, - .line_num = line_num, - .function = function, - .message = message, - .thread = Common::GetCurrentThreadName(), - .counter = 1, - }; + if (Config::groupIdenticalLogs()) { + std::unique_lock entry_loc(_mutex); + + if (_last_entry.message == message) { + ++_last_entry.counter; + return; + } + + if (_last_entry.counter >= 2) { + _last_entry.message += " x" + std::to_string(_last_entry.counter); + } + + if (_last_entry.counter >= 1) { + if (Config::getLogType() == "async") { + message_queue.EmplaceWait(_last_entry); + } else { + ForEachBackend([this](auto& backend) { backend.Write(this->_last_entry); }); + std::fflush(stdout); + } + } + + this->_last_entry = { + .timestamp = duration_cast(steady_clock::now() - time_origin), + .log_class = log_class, + .log_level = log_level, + .filename = filename, + .line_num = line_num, + .function = function, + .message = message, + .thread = Common::GetCurrentThreadName(), + .counter = 1, + }; + } else { + const Entry entry = { + .timestamp = duration_cast(steady_clock::now() - time_origin), + .log_class = log_class, + .log_level = log_level, + .filename = filename, + .line_num = line_num, + .function = function, + .message = message, + .thread = Common::GetCurrentThreadName(), + .counter = 1, + }; + + if (Config::getLogType() == "async") { + message_queue.EmplaceWait(entry); + } else { + ForEachBackend([&entry](auto& backend) { backend.Write(entry); }); + std::fflush(stdout); + } + } } private: @@ -275,21 +296,23 @@ private: } void StopBackendThread() { - // log last message - if (_last_entry.counter >= 2) { - _last_entry.message += " x" + std::to_string(_last_entry.counter); - } - - if (_last_entry.counter >= 1) { - if (Config::getLogType() == "async") { - message_queue.EmplaceWait(_last_entry); - } else { - ForEachBackend([this](auto& backend) { backend.Write(this->_last_entry); }); - std::fflush(stdout); + if (Config::groupIdenticalLogs()) { + // log last message + if (_last_entry.counter >= 2) { + _last_entry.message += " x" + std::to_string(_last_entry.counter); } - } - this->_last_entry = {}; + if (_last_entry.counter >= 1) { + if (Config::getLogType() == "async") { + message_queue.EmplaceWait(_last_entry); + } else { + ForEachBackend([this](auto& backend) { backend.Write(this->_last_entry); }); + std::fflush(stdout); + } + } + + this->_last_entry = {}; + } backend_thread.request_stop(); if (backend_thread.joinable()) { diff --git a/src/core/debug_state.cpp b/src/core/debug_state.cpp index f898117ec..3cc6010b5 100644 --- a/src/core/debug_state.cpp +++ b/src/core/debug_state.cpp @@ -6,6 +6,7 @@ #include "common/assert.h" #include "common/native_clock.h" #include "common/singleton.h" +#include "core/signals.h" #include "debug_state.h" #include "devtools/widget/common.h" #include "libraries/kernel/time.h" @@ -33,7 +34,7 @@ static void PauseThread(ThreadID id) { SuspendThread(handle); CloseHandle(handle); #else - pthread_kill(id, SIGUSR1); + pthread_kill(id, SIGSLEEP); #endif } @@ -43,7 +44,7 @@ static void ResumeThread(ThreadID id) { ResumeThread(handle); CloseHandle(handle); #else - pthread_kill(id, SIGUSR1); + pthread_kill(id, SIGSLEEP); #endif } diff --git a/src/core/file_sys/fs.cpp b/src/core/file_sys/fs.cpp index cba95fe37..96a04ee5e 100644 --- a/src/core/file_sys/fs.cpp +++ b/src/core/file_sys/fs.cpp @@ -55,7 +55,7 @@ std::filesystem::path MntPoints::GetHostPath(std::string_view path, bool* is_rea if (path.length() > 255) return ""; - const MntPair* mount = GetMount(corrected_path); + const std::optional mount = GetMount(corrected_path); if (!mount) { return ""; } diff --git a/src/core/file_sys/fs.h b/src/core/file_sys/fs.h index 599d9e823..0522c3d8a 100644 --- a/src/core/file_sys/fs.h +++ b/src/core/file_sys/fs.h @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -58,14 +59,17 @@ public: return it == m_mnt_pairs.end() ? nullptr : &*it; } - const MntPair* GetMount(const std::string& guest_path) { + const std::optional GetMount(const std::string& guest_path) { std::scoped_lock lock{m_mutex}; const auto it = std::ranges::find_if(m_mnt_pairs, [&](const auto& mount) { // When doing starts-with check, add a trailing slash to make sure we don't match // against only part of the mount path. return guest_path == mount.mount || guest_path.starts_with(mount.mount + "/"); }); - return it == m_mnt_pairs.end() ? nullptr : &*it; + if (it == m_mnt_pairs.end()) { + return std::nullopt; + } + return *it; } private: @@ -81,7 +85,8 @@ enum class FileType { Device, Socket, Epoll, - Resolver + Resolver, + Equeue }; struct File { diff --git a/src/core/ipc/ipc.cpp b/src/core/ipc/ipc.cpp index ea7cd38b4..aab3e7de5 100644 --- a/src/core/ipc/ipc.cpp +++ b/src/core/ipc/ipc.cpp @@ -174,12 +174,18 @@ void IPC::InputLoop() { } else if (cmd == "USB_LOAD_FIGURE") { const auto ref = Libraries::Usbd::usb_backend->GetImplRef(); if (ref) { - ref->LoadFigure(next_str(), next_u64(), next_u64()); + std::string file_name = next_str(); + const u8 pad = next_u64(); + const u8 slot = next_u64(); + ref->LoadFigure(file_name, pad, slot); } } else if (cmd == "USB_REMOVE_FIGURE") { const auto ref = Libraries::Usbd::usb_backend->GetImplRef(); if (ref) { - ref->RemoveFigure(next_u64(), next_u64(), next_u64() != 0); + const u8 pad = next_u64(); + const u8 slot = next_u64(); + bool full_remove = next_u64() != 0; + ref->RemoveFigure(pad, slot, full_remove); } } else if (cmd == "USB_MOVE_FIGURE") { const auto ref = Libraries::Usbd::usb_backend->GetImplRef(); diff --git a/src/core/libraries/gnmdriver/gnmdriver.cpp b/src/core/libraries/gnmdriver/gnmdriver.cpp index 1181f6299..326dc2418 100644 --- a/src/core/libraries/gnmdriver/gnmdriver.cpp +++ b/src/core/libraries/gnmdriver/gnmdriver.cpp @@ -123,21 +123,23 @@ static inline bool IsValidEventType(Platform::InterruptId id) { static_cast(id) == static_cast(Platform::InterruptId::GfxEop); } -s32 PS4_SYSV_ABI sceGnmAddEqEvent(SceKernelEqueue eq, u64 id, void* udata) { +s32 PS4_SYSV_ABI sceGnmAddEqEvent(OrbisKernelEqueue eq, u64 id, void* udata) { LOG_TRACE(Lib_GnmDriver, "called"); - if (!eq) { + auto equeue = GetEqueue(eq); + if (!equeue) { return ORBIS_KERNEL_ERROR_EBADF; } EqueueEvent kernel_event{}; kernel_event.event.ident = id; - kernel_event.event.filter = SceKernelEvent::Filter::GraphicsCore; - kernel_event.event.flags = SceKernelEvent::Flags::Add; + kernel_event.event.filter = OrbisKernelEvent::Filter::GraphicsCore; + kernel_event.event.flags = OrbisKernelEvent::Flags::Add; kernel_event.event.fflags = 0; kernel_event.event.data = id; kernel_event.event.udata = udata; - eq->AddEvent(kernel_event); + + equeue->AddEvent(kernel_event); Platform::IrqC::Instance()->Register( static_cast(id), @@ -149,10 +151,11 @@ s32 PS4_SYSV_ABI sceGnmAddEqEvent(SceKernelEqueue eq, u64 id, void* udata) { return; // Event data is expected to be an event type as per sceGnmGetEqEventType. - eq->TriggerEvent(static_cast(id), SceKernelEvent::Filter::GraphicsCore, - reinterpret_cast(id)); + equeue->TriggerEvent(static_cast(id), + OrbisKernelEvent::Filter::GraphicsCore, + reinterpret_cast(id)); }, - eq); + equeue); return ORBIS_OK; } @@ -267,16 +270,17 @@ int PS4_SYSV_ABI sceGnmDebugHardwareStatus() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceGnmDeleteEqEvent(SceKernelEqueue eq, u64 id) { +s32 PS4_SYSV_ABI sceGnmDeleteEqEvent(OrbisKernelEqueue eq, u64 id) { LOG_TRACE(Lib_GnmDriver, "called"); - if (!eq) { + auto equeue = GetEqueue(eq); + if (!equeue) { return ORBIS_KERNEL_ERROR_EBADF; } - eq->RemoveEvent(id, SceKernelEvent::Filter::GraphicsCore); + equeue->RemoveEvent(id, OrbisKernelEvent::Filter::GraphicsCore); - Platform::IrqC::Instance()->Unregister(static_cast(id), eq); + Platform::IrqC::Instance()->Unregister(static_cast(id), equeue); return ORBIS_OK; } @@ -895,7 +899,7 @@ int PS4_SYSV_ABI sceGnmGetDebugTimestamp() { return ORBIS_OK; } -int PS4_SYSV_ABI sceGnmGetEqEventType(const SceKernelEvent* ev) { +int PS4_SYSV_ABI sceGnmGetEqEventType(const OrbisKernelEvent* ev) { LOG_TRACE(Lib_GnmDriver, "called"); return sceKernelGetEventData(ev); } @@ -2052,7 +2056,7 @@ int PS4_SYSV_ABI sceGnmSqttWaitForEvent() { } static inline s32 PatchFlipRequest(u32* cmdbuf, u32 size, u32 vo_handle, u32 buf_idx, u32 flip_mode, - u32 flip_arg, void* unk) { + s64 flip_arg, void* unk) { // check for `prepareFlip` packet cmdbuf += size - 64; ASSERT_MSG(cmdbuf[0] == 0xc03e1000, "Can't find `prepareFlip` packet"); @@ -2138,7 +2142,7 @@ static inline s32 PatchFlipRequest(u32* cmdbuf, u32 size, u32 vo_handle, u32 buf s32 PS4_SYSV_ABI sceGnmSubmitAndFlipCommandBuffers(u32 count, u32* dcb_gpu_addrs[], u32* dcb_sizes_in_bytes, u32* ccb_gpu_addrs[], u32* ccb_sizes_in_bytes, u32 vo_handle, - u32 buf_idx, u32 flip_mode, u32 flip_arg) { + u32 buf_idx, u32 flip_mode, s64 flip_arg) { return sceGnmSubmitAndFlipCommandBuffersForWorkload( count, count, dcb_gpu_addrs, dcb_sizes_in_bytes, ccb_gpu_addrs, ccb_sizes_in_bytes, vo_handle, buf_idx, flip_mode, flip_arg); @@ -2146,7 +2150,7 @@ s32 PS4_SYSV_ABI sceGnmSubmitAndFlipCommandBuffers(u32 count, u32* dcb_gpu_addrs s32 PS4_SYSV_ABI sceGnmSubmitAndFlipCommandBuffersForWorkload( u32 workload, u32 count, u32* dcb_gpu_addrs[], u32* dcb_sizes_in_bytes, u32* ccb_gpu_addrs[], - u32* ccb_sizes_in_bytes, u32 vo_handle, u32 buf_idx, u32 flip_mode, u32 flip_arg) { + u32* ccb_sizes_in_bytes, u32 vo_handle, u32 buf_idx, u32 flip_mode, s64 flip_arg) { LOG_DEBUG(Lib_GnmDriver, "called [buf = {}]", buf_idx); auto* cmdbuf = dcb_gpu_addrs[count - 1]; diff --git a/src/core/libraries/gnmdriver/gnmdriver.h b/src/core/libraries/gnmdriver/gnmdriver.h index 4001f4661..9f5fde628 100644 --- a/src/core/libraries/gnmdriver/gnmdriver.h +++ b/src/core/libraries/gnmdriver/gnmdriver.h @@ -14,7 +14,7 @@ namespace Libraries::GnmDriver { using namespace Kernel; -s32 PS4_SYSV_ABI sceGnmAddEqEvent(SceKernelEqueue eq, u64 id, void* udata); +s32 PS4_SYSV_ABI sceGnmAddEqEvent(OrbisKernelEqueue eq, u64 id, void* udata); int PS4_SYSV_ABI sceGnmAreSubmitsAllowed(); int PS4_SYSV_ABI sceGnmBeginWorkload(u32 workload_stream, u64* workload); s32 PS4_SYSV_ABI sceGnmComputeWaitOnAddress(u32* cmdbuf, u32 size, uintptr_t addr, u32 mask, @@ -31,7 +31,7 @@ int PS4_SYSV_ABI sceGnmDebuggerSetAddressWatch(); int PS4_SYSV_ABI sceGnmDebuggerWriteGds(); int PS4_SYSV_ABI sceGnmDebuggerWriteSqIndirectRegister(); int PS4_SYSV_ABI sceGnmDebugHardwareStatus(); -s32 PS4_SYSV_ABI sceGnmDeleteEqEvent(SceKernelEqueue eq, u64 id); +s32 PS4_SYSV_ABI sceGnmDeleteEqEvent(OrbisKernelEqueue eq, u64 id); int PS4_SYSV_ABI sceGnmDestroyWorkloadStream(); void PS4_SYSV_ABI sceGnmDingDong(u32 gnm_vqid, u32 next_offs_dw); void PS4_SYSV_ABI sceGnmDingDongForWorkload(u32 gnm_vqid, u32 next_offs_dw, u64 workload_id); @@ -87,7 +87,7 @@ int PS4_SYSV_ABI sceGnmGetCoredumpMode(); int PS4_SYSV_ABI sceGnmGetCoredumpProtectionFaultTimestamp(); int PS4_SYSV_ABI sceGnmGetDbgGcHandle(); int PS4_SYSV_ABI sceGnmGetDebugTimestamp(); -int PS4_SYSV_ABI sceGnmGetEqEventType(const SceKernelEvent* ev); +int PS4_SYSV_ABI sceGnmGetEqEventType(const OrbisKernelEvent* ev); int PS4_SYSV_ABI sceGnmGetEqTimeStamp(); int PS4_SYSV_ABI sceGnmGetGpuBlockStatus(); u32 PS4_SYSV_ABI sceGnmGetGpuCoreClockFrequency(); @@ -211,10 +211,10 @@ int PS4_SYSV_ABI sceGnmSqttWaitForEvent(); s32 PS4_SYSV_ABI sceGnmSubmitAndFlipCommandBuffers(u32 count, u32* dcb_gpu_addrs[], u32* dcb_sizes_in_bytes, u32* ccb_gpu_addrs[], u32* ccb_sizes_in_bytes, u32 vo_handle, - u32 buf_idx, u32 flip_mode, u32 flip_arg); + u32 buf_idx, u32 flip_mode, s64 flip_arg); int PS4_SYSV_ABI sceGnmSubmitAndFlipCommandBuffersForWorkload( u32 workload, u32 count, u32* dcb_gpu_addrs[], u32* dcb_sizes_in_bytes, u32* ccb_gpu_addrs[], - u32* ccb_sizes_in_bytes, u32 vo_handle, u32 buf_idx, u32 flip_mode, u32 flip_arg); + u32* ccb_sizes_in_bytes, u32 vo_handle, u32 buf_idx, u32 flip_mode, s64 flip_arg); s32 PS4_SYSV_ABI sceGnmSubmitCommandBuffers(u32 count, const u32* dcb_gpu_addrs[], u32* dcb_sizes_in_bytes, const u32* ccb_gpu_addrs[], u32* ccb_sizes_in_bytes); diff --git a/src/core/libraries/kernel/equeue.cpp b/src/core/libraries/kernel/equeue.cpp index 72e38b265..68a6dae24 100644 --- a/src/core/libraries/kernel/equeue.cpp +++ b/src/core/libraries/kernel/equeue.cpp @@ -2,12 +2,18 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include "common/assert.h" #include "common/debug.h" #include "common/logging/log.h" +#include "common/singleton.h" +#include "core/file_sys/fs.h" #include "core/libraries/kernel/equeue.h" +#include "core/libraries/kernel/kernel.h" #include "core/libraries/kernel/orbis_error.h" +#include "core/libraries/kernel/posix_error.h" +#include "core/libraries/kernel/time.h" #include "core/libraries/libs.h" namespace Libraries::Kernel { @@ -15,43 +21,104 @@ namespace Libraries::Kernel { extern boost::asio::io_context io_context; extern void KernelSignalRequest(); -static constexpr auto HrTimerSpinlockThresholdUs = 1200u; +static std::unordered_map kqueues; +static constexpr auto HrTimerSpinlockThresholdNs = 1200000u; + +EqueueInternal* GetEqueue(OrbisKernelEqueue eq) { + if (!kqueues.contains(eq)) { + return nullptr; + } + return kqueues[eq]; +} + +static void HrTimerCallback(OrbisKernelEqueue eq, const OrbisKernelEvent& kevent) { + if (kqueues.contains(eq)) { + kqueues[eq]->TriggerEvent(kevent.ident, OrbisKernelEvent::Filter::HrTimer, kevent.udata); + } +} + +static void TimerCallback(OrbisKernelEqueue eq, const OrbisKernelEvent& kevent) { + if (kqueues.contains(eq) && kqueues[eq]->EventExists(kevent.ident, kevent.filter)) { + kqueues[eq]->TriggerEvent(kevent.ident, OrbisKernelEvent::Filter::Timer, kevent.udata); + if (!(kevent.flags & OrbisKernelEvent::Flags::OneShot)) { + // Reschedule the event for its next period. + kqueues[eq]->ScheduleEvent(kevent.ident, kevent.filter, TimerCallback); + } + } +} // Events are uniquely identified by id and filter. - bool EqueueInternal::AddEvent(EqueueEvent& event) { - std::scoped_lock lock{m_mutex}; + { + std::scoped_lock lock{m_mutex}; - event.time_added = std::chrono::steady_clock::now(); - if (event.event.filter == SceKernelEvent::Filter::Timer || - event.event.filter == SceKernelEvent::Filter::HrTimer) { - // HrTimer events are offset by the threshold of time at the end that we spinlock for - // greater accuracy. - const auto offset = - event.event.filter == SceKernelEvent::Filter::HrTimer ? HrTimerSpinlockThresholdUs : 0u; - event.timer_interval = std::chrono::microseconds(event.event.data - offset); + // Calculate timer interval + event.time_added = std::chrono::steady_clock::now(); + if (event.event.filter == OrbisKernelEvent::Filter::Timer) { + // Set timer interval, this is stored in milliseconds for timers. + event.timer_interval = std::chrono::milliseconds(event.event.data); + } else if (event.event.filter == OrbisKernelEvent::Filter::HrTimer) { + // Retrieve inputted time, this is stored in the bintime format. + OrbisKernelBintime* time = reinterpret_cast(event.event.data); + + // Convert the bintime format to a timespec. + OrbisKernelTimespec ts; + ts.tv_sec = time->sec; + ts.tv_nsec = (1000000000 * (time->frac >> 32)) >> 32; + + // Then use the timespec to set the timer interval. + event.timer_interval = std::chrono::nanoseconds(ts.tv_nsec + ts.tv_sec * 1000000000); + } + + // First, check if there's already an event with the same id and filter. + u64 id = event.event.ident; + OrbisKernelEvent::Filter filter = event.event.filter; + const auto& find_it = std::ranges::find_if(m_events, [id, filter](auto& ev) { + return ev.event.ident == id && ev.event.filter == filter; + }); + // If there is a duplicate event, we need to update that instead. + if (find_it != m_events.cend()) { + // Specifically, update user data and timer_interval. + // Trigger status and event data should remain intact. + auto& old_event = *find_it; + old_event.timer_interval = event.timer_interval; + old_event.event.udata = event.event.udata; + return true; + } + + // Clear input data from event. + event.event.data = 0; + + // Remove add flag from event + event.event.flags &= ~OrbisKernelEvent::Flags::Add; + + // Clear flag is appended to most event types internally. + if (event.event.filter != OrbisKernelEvent::Filter::User) { + event.event.flags |= OrbisKernelEvent::Flags::Clear; + } + + const auto& it = std::ranges::find(m_events, event); + if (it != m_events.cend()) { + *it = std::move(event); + } else { + m_events.emplace_back(std::move(event)); + } } - // Remove add flag from event - event.event.flags &= ~SceKernelEvent::Flags::Add; - - // Clear flag is appended to most event types internally. - if (event.event.filter != SceKernelEvent::Filter::User) { - event.event.flags |= SceKernelEvent::Flags::Clear; - } - - const auto& it = std::ranges::find(m_events, event); - if (it != m_events.cend()) { - *it = std::move(event); - } else { - m_events.emplace_back(std::move(event)); + // Schedule callbacks for timer events + if (event.event.filter == OrbisKernelEvent::Timer) { + return this->ScheduleEvent(event.event.ident, OrbisKernelEvent::Filter::Timer, + TimerCallback); + } else if (event.event.filter == OrbisKernelEvent::HrTimer) { + return this->ScheduleEvent(event.event.ident, OrbisKernelEvent::Filter::HrTimer, + HrTimerCallback); } return true; } bool EqueueInternal::ScheduleEvent(u64 id, s16 filter, - void (*callback)(SceKernelEqueue, const SceKernelEvent&)) { + void (*callback)(OrbisKernelEqueue, const OrbisKernelEvent&)) { std::scoped_lock lock{m_mutex}; const auto& it = std::ranges::find_if(m_events, [id, filter](auto& ev) { @@ -62,8 +129,8 @@ bool EqueueInternal::ScheduleEvent(u64 id, s16 filter, } const auto& event = *it; - ASSERT(event.event.filter == SceKernelEvent::Filter::Timer || - event.event.filter == SceKernelEvent::Filter::HrTimer); + ASSERT(event.event.filter == OrbisKernelEvent::Filter::Timer || + event.event.filter == OrbisKernelEvent::Filter::HrTimer); if (!it->timer) { it->timer = std::make_unique(io_context, event.timer_interval); @@ -84,7 +151,7 @@ bool EqueueInternal::ScheduleEvent(u64 id, s16 filter, } return; } - callback(this, event_data); + callback(this->m_handle, event_data); }); KernelSignalRequest(); @@ -105,7 +172,7 @@ bool EqueueInternal::RemoveEvent(u64 id, s16 filter) { return has_found; } -int EqueueInternal::WaitForEvents(SceKernelEvent* ev, int num, const SceKernelUseconds* timo) { +int EqueueInternal::WaitForEvents(OrbisKernelEvent* ev, int num, const OrbisKernelUseconds* timo) { if (timo != nullptr && *timo == 0) { // Effectively acts as a poll; only events that have already // arrived at the time of this function call can be received @@ -135,15 +202,6 @@ int EqueueInternal::WaitForEvents(SceKernelEvent* ev, int num, const SceKernelUs m_cond.wait_for(lock, std::chrono::microseconds(micros), predicate); } - if (HasSmallTimer()) { - if (count > 0) { - const auto time_waited = std::chrono::duration_cast( - std::chrono::steady_clock::now() - m_events[0].time_added) - .count(); - count = WaitForSmallTimer(ev, num, std::max(0l, long(micros - time_waited))); - } - } - return count; } @@ -153,10 +211,13 @@ bool EqueueInternal::TriggerEvent(u64 ident, s16 filter, void* trigger_data) { std::scoped_lock lock{m_mutex}; for (auto& event : m_events) { if (event.event.ident == ident && event.event.filter == filter) { - if (filter == SceKernelEvent::Filter::VideoOut) { + if (filter == OrbisKernelEvent::Filter::VideoOut) { event.TriggerDisplay(trigger_data); - } else if (filter == SceKernelEvent::Filter::User) { + } else if (filter == OrbisKernelEvent::Filter::User) { event.TriggerUser(trigger_data); + } else if (filter == OrbisKernelEvent::Filter::Timer || + filter == OrbisKernelEvent::Filter::HrTimer) { + event.TriggerTimer(); } else { event.Trigger(trigger_data); } @@ -168,15 +229,15 @@ bool EqueueInternal::TriggerEvent(u64 ident, s16 filter, void* trigger_data) { return has_found; } -int EqueueInternal::GetTriggeredEvents(SceKernelEvent* ev, int num) { +int EqueueInternal::GetTriggeredEvents(OrbisKernelEvent* ev, int num) { int count = 0; for (auto it = m_events.begin(); it != m_events.end();) { if (it->IsTriggered()) { ev[count++] = it->event; - if (it->event.flags & SceKernelEvent::Flags::Clear) { + if (it->event.flags & OrbisKernelEvent::Flags::Clear) { it->Clear(); } - if (it->event.flags & SceKernelEvent::Flags::OneShot) { + if (it->event.flags & OrbisKernelEvent::Flags::OneShot) { it = m_events.erase(it); } else { ++it; @@ -194,10 +255,17 @@ int EqueueInternal::GetTriggeredEvents(SceKernelEvent* ev, int num) { } bool EqueueInternal::AddSmallTimer(EqueueEvent& ev) { + // Retrieve inputted time, this is stored in the bintime format + OrbisKernelBintime* time = reinterpret_cast(ev.event.data); + OrbisKernelTimespec ts; + ts.tv_sec = time->sec; + ts.tv_nsec = ((1000000000 * (time->frac >> 32)) >> 32); + + // Create the small timer SmallTimer st; st.event = ev.event; st.added = std::chrono::steady_clock::now(); - st.interval = std::chrono::microseconds{ev.event.data}; + st.interval = std::chrono::nanoseconds(ts.tv_nsec + ts.tv_sec * 1000000000); { std::scoped_lock lock{m_mutex}; m_small_timers[st.event.ident] = std::move(st); @@ -205,7 +273,7 @@ bool EqueueInternal::AddSmallTimer(EqueueEvent& ev) { return true; } -int EqueueInternal::WaitForSmallTimer(SceKernelEvent* ev, int num, u32 micros) { +int EqueueInternal::WaitForSmallTimer(OrbisKernelEvent* ev, int num, u32 micros) { ASSERT(num >= 1); auto curr_clock = std::chrono::steady_clock::now(); @@ -246,18 +314,119 @@ bool EqueueInternal::EventExists(u64 id, s16 filter) { return it != m_events.cend(); } -int PS4_SYSV_ABI sceKernelCreateEqueue(SceKernelEqueue* eq, const char* name) { +s32 PS4_SYSV_ABI posix_kqueue() { + // Reserve a file handle for the kqueue + auto* handles = Common::Singleton::Instance(); + s32 kqueue_handle = handles->CreateHandle(); + auto* kqueue_file = handles->GetFile(kqueue_handle); + kqueue_file->type = Core::FileSys::FileType::Equeue; + + // Plenty of equeue logic uses names to identify queues. + // Create a unique name for the queue we create. + char name[32]; + memset(name, 0, sizeof(name)); + snprintf(name, sizeof(name), "kqueue%i", kqueue_handle); + + // Create the queue + kqueues[kqueue_handle] = new EqueueInternal(kqueue_handle, name); + LOG_INFO(Kernel_Event, "kqueue created with name {}", name); + + // Return handle. + return kqueue_handle; +} + +// Helper method to detect supported filters. +// We don't want to allow adding events we don't handle properly. +bool SupportedEqueueFilter(OrbisKernelEvent::Filter filter) { + return filter == OrbisKernelEvent::Filter::GraphicsCore || + filter == OrbisKernelEvent::Filter::HrTimer || + filter == OrbisKernelEvent::Filter::Timer || filter == OrbisKernelEvent::Filter::User || + filter == OrbisKernelEvent::Filter::VideoOut; +} + +s32 PS4_SYSV_ABI posix_kevent(s32 handle, OrbisKernelEvent* changelist, u64 nchanges, + OrbisKernelEvent* eventlist, u64 nevents, + OrbisKernelTimespec* timeout) { + LOG_INFO(Kernel_Event, "called, eq = {}, nchanges = {}, nevents = {}", handle, nchanges, + nevents); + + // Get the equeue + if (!kqueues.contains(handle)) { + *__Error() = POSIX_EBADF; + return ORBIS_FAIL; + } + auto equeue = kqueues[handle]; + + // First step is to apply all changes in changelist. + for (u64 i = 0; i < nchanges; i++) { + auto event = changelist[i]; + if (!SupportedEqueueFilter(event.filter)) { + LOG_ERROR(Kernel_Event, "Unsupported event filter {}", + magic_enum::enum_name(event.filter)); + continue; + } + + // Check the event flags to determine the appropriate action + if (event.flags & OrbisKernelEvent::Flags::Add) { + // The caller is requesting to add an event. + EqueueEvent internal_event{}; + internal_event.event = event; + if (!equeue->AddEvent(internal_event)) { + // Failed to add event, return error. + *__Error() = POSIX_ENOMEM; + return ORBIS_FAIL; + } + } + + if (event.flags & OrbisKernelEvent::Flags::Delete) { + // The caller is requesting to remove an event. + if (!equeue->RemoveEvent(event.ident, event.filter)) { + // Failed to remove event, return error. + *__Error() = POSIX_ENOENT; + return ORBIS_FAIL; + } + } + + if (event.filter == OrbisKernelEvent::Filter::User && event.fflags == 0x1000000) { + // For user events, this fflags value indicates we need to trigger the event. + if (!equeue->TriggerEvent(event.ident, OrbisKernelEvent::Filter::User, event.udata)) { + *__Error() = POSIX_ENOENT; + return ORBIS_FAIL; + } + } else if (event.fflags != 0) { + // The title is using filter-specific flags. Right now, these are unhandled. + LOG_ERROR(Kernel_Event, "Unhandled fflags {:#x} for event filter {}", event.fflags, + magic_enum::enum_name(event.filter)); + continue; + } + } + + // Now we need to wait on the event list. + s32 count = 0; + if (nevents > 0) { + if (timeout != nullptr) { + OrbisKernelUseconds micros = (timeout->tv_sec * 1000000) + (timeout->tv_nsec / 1000); + count = equeue->WaitForEvents(eventlist, nevents, µs); + } else { + count = equeue->WaitForEvents(eventlist, nevents, nullptr); + } + } + return count; +} + +int PS4_SYSV_ABI sceKernelCreateEqueue(OrbisKernelEqueue* eq, const char* name) { if (eq == nullptr) { LOG_ERROR(Kernel_Event, "Event queue is null!"); return ORBIS_KERNEL_ERROR_EINVAL; } + if (name == nullptr) { LOG_ERROR(Kernel_Event, "Event queue name is null!"); return ORBIS_KERNEL_ERROR_EINVAL; } // Maximum is 32 including null terminator - static constexpr size_t MaxEventQueueNameSize = 32; + static constexpr u64 MaxEventQueueNameSize = 32; if (std::strlen(name) > MaxEventQueueNameSize) { LOG_ERROR(Kernel_Event, "Event queue name exceeds 32 bytes!"); return ORBIS_KERNEL_ERROR_ENAMETOOLONG; @@ -265,29 +434,42 @@ int PS4_SYSV_ABI sceKernelCreateEqueue(SceKernelEqueue* eq, const char* name) { LOG_INFO(Kernel_Event, "name = {}", name); - *eq = new EqueueInternal(name); + // Reserve a file handle for the kqueue + auto* handles = Common::Singleton::Instance(); + OrbisKernelEqueue kqueue_handle = handles->CreateHandle(); + auto* kqueue_file = handles->GetFile(kqueue_handle); + kqueue_file->type = Core::FileSys::FileType::Equeue; + + // Create the equeue + kqueues[kqueue_handle] = new EqueueInternal(kqueue_handle, name); + *eq = kqueue_handle; + return ORBIS_OK; } -int PS4_SYSV_ABI sceKernelDeleteEqueue(SceKernelEqueue eq) { - if (eq == nullptr) { +int PS4_SYSV_ABI sceKernelDeleteEqueue(OrbisKernelEqueue eq) { + if (!kqueues.contains(eq)) { return ORBIS_KERNEL_ERROR_EBADF; } - delete eq; + auto* handles = Common::Singleton::Instance(); + handles->DeleteHandle(eq); + kqueues.erase(eq); return ORBIS_OK; } -int PS4_SYSV_ABI sceKernelWaitEqueue(SceKernelEqueue eq, SceKernelEvent* ev, int num, int* out, - SceKernelUseconds* timo) { +int PS4_SYSV_ABI sceKernelWaitEqueue(OrbisKernelEqueue eq, OrbisKernelEvent* ev, int num, int* out, + OrbisKernelUseconds* timo) { HLE_TRACE; - TRACE_HINT(eq->GetName()); - LOG_TRACE(Kernel_Event, "equeue = {} num = {}", eq->GetName(), num); - - if (eq == nullptr) { + if (!kqueues.contains(eq)) { return ORBIS_KERNEL_ERROR_EBADF; } + auto& equeue = kqueues[eq]; + + TRACE_HINT(equeue->GetName()); + LOG_TRACE(Kernel_Event, "equeue = {} num = {}", equeue->GetName(), num); + if (ev == nullptr) { return ORBIS_KERNEL_ERROR_EFAULT; } @@ -297,7 +479,7 @@ int PS4_SYSV_ABI sceKernelWaitEqueue(SceKernelEqueue eq, SceKernelEvent* ev, int return ORBIS_KERNEL_ERROR_EINVAL; } - *out = eq->WaitForEvents(ev, num, timo); + *out = equeue->WaitForEvents(ev, num, timo); if (*out == 0) { return ORBIS_KERNEL_ERROR_ETIMEDOUT; @@ -306,31 +488,22 @@ int PS4_SYSV_ABI sceKernelWaitEqueue(SceKernelEqueue eq, SceKernelEvent* ev, int return ORBIS_OK; } -static void HrTimerCallback(SceKernelEqueue eq, const SceKernelEvent& kevent) { - static EqueueEvent event; - event.event = kevent; - event.event.data = HrTimerSpinlockThresholdUs; - eq->AddSmallTimer(event); - eq->TriggerEvent(kevent.ident, SceKernelEvent::Filter::HrTimer, kevent.udata); -} - -s32 PS4_SYSV_ABI sceKernelAddHRTimerEvent(SceKernelEqueue eq, int id, timespec* ts, void* udata) { - if (eq == nullptr) { +s32 PS4_SYSV_ABI sceKernelAddHRTimerEvent(OrbisKernelEqueue eq, int id, OrbisKernelTimespec* ts, + void* udata) { + if (!kqueues.contains(eq)) { return ORBIS_KERNEL_ERROR_EBADF; } - if (ts->tv_sec > 100 || ts->tv_nsec < 100'000) { - return ORBIS_KERNEL_ERROR_EINVAL; - } - ASSERT(ts->tv_nsec > 1000); // assume 1us resolution - const auto total_us = ts->tv_sec * 1000'000 + ts->tv_nsec / 1000; + const auto total_ns = ts->tv_sec * 1000000000 + ts->tv_nsec; EqueueEvent event{}; event.event.ident = id; - event.event.filter = SceKernelEvent::Filter::HrTimer; - event.event.flags = SceKernelEvent::Flags::Add | SceKernelEvent::Flags::OneShot; + event.event.filter = OrbisKernelEvent::Filter::HrTimer; + event.event.flags = OrbisKernelEvent::Flags::Add | OrbisKernelEvent::Flags::OneShot; event.event.fflags = 0; - event.event.data = total_us; + // Data is stored as the address of a OrbisKernelBintime struct. + OrbisKernelBintime time{ts->tv_sec, ts->tv_nsec * 0x44b82fa09}; + event.event.data = reinterpret_cast(&time); event.event.udata = udata; // HR timers cannot be implemented within the existing event queue architecture due to the @@ -340,158 +513,137 @@ s32 PS4_SYSV_ABI sceKernelAddHRTimerEvent(SceKernelEqueue eq, int id, timespec* // `HrTimerSpinlockThresholdUs`) and fall back to boost asio timers if the time to tick is // large. Even for large delays, we truncate a small portion to complete the wait // using the spinlock, prioritizing precision. - - if (eq->EventExists(event.event.ident, event.event.filter)) { - eq->RemoveEvent(id, SceKernelEvent::Filter::HrTimer); + auto& equeue = kqueues[eq]; + if (total_ns < HrTimerSpinlockThresholdNs) { + return equeue->AddSmallTimer(event) ? ORBIS_OK : ORBIS_KERNEL_ERROR_ENOMEM; } - if (total_us < HrTimerSpinlockThresholdUs) { - return eq->AddSmallTimer(event) ? ORBIS_OK : ORBIS_KERNEL_ERROR_ENOMEM; - } - - if (!eq->AddEvent(event) || - !eq->ScheduleEvent(id, SceKernelEvent::Filter::HrTimer, HrTimerCallback)) { + if (!equeue->AddEvent(event)) { return ORBIS_KERNEL_ERROR_ENOMEM; } return ORBIS_OK; } -int PS4_SYSV_ABI sceKernelDeleteHRTimerEvent(SceKernelEqueue eq, int id) { - if (eq == nullptr) { +int PS4_SYSV_ABI sceKernelDeleteHRTimerEvent(OrbisKernelEqueue eq, int id) { + if (!kqueues.contains(eq)) { return ORBIS_KERNEL_ERROR_EBADF; } - if (eq->HasSmallTimer()) { - return eq->RemoveSmallTimer(id) ? ORBIS_OK : ORBIS_KERNEL_ERROR_ENOENT; + auto& equeue = kqueues[eq]; + if (equeue->HasSmallTimer()) { + return equeue->RemoveSmallTimer(id) ? ORBIS_OK : ORBIS_KERNEL_ERROR_ENOENT; } else { - return eq->RemoveEvent(id, SceKernelEvent::Filter::HrTimer) ? ORBIS_OK - : ORBIS_KERNEL_ERROR_ENOENT; + return equeue->RemoveEvent(id, OrbisKernelEvent::Filter::HrTimer) + ? ORBIS_OK + : ORBIS_KERNEL_ERROR_ENOENT; } } -static void TimerCallback(SceKernelEqueue eq, const SceKernelEvent& kevent) { - if (eq->EventExists(kevent.ident, kevent.filter)) { - eq->TriggerEvent(kevent.ident, SceKernelEvent::Filter::Timer, kevent.udata); - - if (!(kevent.flags & SceKernelEvent::Flags::OneShot)) { - // Reschedule the event for its next period. - eq->ScheduleEvent(kevent.ident, kevent.filter, TimerCallback); - } - } -} - -int PS4_SYSV_ABI sceKernelAddTimerEvent(SceKernelEqueue eq, int id, SceKernelUseconds usec, +int PS4_SYSV_ABI sceKernelAddTimerEvent(OrbisKernelEqueue eq, int id, OrbisKernelUseconds usec, void* udata) { - if (eq == nullptr) { + if (!kqueues.contains(eq)) { return ORBIS_KERNEL_ERROR_EBADF; } EqueueEvent event{}; event.event.ident = static_cast(id); - event.event.filter = SceKernelEvent::Filter::Timer; - event.event.flags = SceKernelEvent::Flags::Add; + event.event.filter = OrbisKernelEvent::Filter::Timer; + event.event.flags = OrbisKernelEvent::Flags::Add; event.event.fflags = 0; - event.event.data = usec; + event.event.data = usec / 1000; event.event.udata = udata; - if (eq->EventExists(event.event.ident, event.event.filter)) { - eq->RemoveEvent(id, SceKernelEvent::Filter::Timer); - LOG_DEBUG(Kernel_Event, - "Timer event already exists, removing it: queue name={}, queue id={}", - eq->GetName(), event.event.ident); - } - - LOG_DEBUG(Kernel_Event, "Added timing event: queue name={}, queue id={}, usec={}, pointer={:x}", - eq->GetName(), event.event.ident, usec, reinterpret_cast(udata)); - - if (!eq->AddEvent(event) || - !eq->ScheduleEvent(id, SceKernelEvent::Filter::Timer, TimerCallback)) { + auto& equeue = kqueues[eq]; + if (!equeue->AddEvent(event)) { return ORBIS_KERNEL_ERROR_ENOMEM; } return ORBIS_OK; } -int PS4_SYSV_ABI sceKernelDeleteTimerEvent(SceKernelEqueue eq, int id) { - if (eq == nullptr) { +int PS4_SYSV_ABI sceKernelDeleteTimerEvent(OrbisKernelEqueue eq, int id) { + if (!kqueues.contains(eq)) { return ORBIS_KERNEL_ERROR_EBADF; } - return eq->RemoveEvent(id, SceKernelEvent::Filter::Timer) ? ORBIS_OK - : ORBIS_KERNEL_ERROR_ENOENT; + return kqueues[eq]->RemoveEvent(id, OrbisKernelEvent::Filter::Timer) + ? ORBIS_OK + : ORBIS_KERNEL_ERROR_ENOENT; } -int PS4_SYSV_ABI sceKernelAddUserEvent(SceKernelEqueue eq, int id) { - if (eq == nullptr) { +int PS4_SYSV_ABI sceKernelAddUserEvent(OrbisKernelEqueue eq, int id) { + if (!kqueues.contains(eq)) { return ORBIS_KERNEL_ERROR_EBADF; } EqueueEvent event{}; event.event.ident = id; - event.event.filter = SceKernelEvent::Filter::User; + event.event.filter = OrbisKernelEvent::Filter::User; event.event.udata = 0; - event.event.flags = SceKernelEvent::Flags::Add; + event.event.flags = OrbisKernelEvent::Flags::Add; event.event.fflags = 0; event.event.data = 0; - return eq->AddEvent(event) ? ORBIS_OK : ORBIS_KERNEL_ERROR_ENOMEM; + return kqueues[eq]->AddEvent(event) ? ORBIS_OK : ORBIS_KERNEL_ERROR_ENOMEM; } -int PS4_SYSV_ABI sceKernelAddUserEventEdge(SceKernelEqueue eq, int id) { - if (eq == nullptr) { +int PS4_SYSV_ABI sceKernelAddUserEventEdge(OrbisKernelEqueue eq, int id) { + if (!kqueues.contains(eq)) { return ORBIS_KERNEL_ERROR_EBADF; } EqueueEvent event{}; event.event.ident = id; - event.event.filter = SceKernelEvent::Filter::User; + event.event.filter = OrbisKernelEvent::Filter::User; event.event.udata = 0; - event.event.flags = SceKernelEvent::Flags::Add | SceKernelEvent::Flags::Clear; + event.event.flags = OrbisKernelEvent::Flags::Add | OrbisKernelEvent::Flags::Clear; event.event.fflags = 0; event.event.data = 0; - return eq->AddEvent(event) ? ORBIS_OK : ORBIS_KERNEL_ERROR_ENOMEM; + return kqueues[eq]->AddEvent(event) ? ORBIS_OK : ORBIS_KERNEL_ERROR_ENOMEM; } -void* PS4_SYSV_ABI sceKernelGetEventUserData(const SceKernelEvent* ev) { +void* PS4_SYSV_ABI sceKernelGetEventUserData(const OrbisKernelEvent* ev) { ASSERT(ev); return ev->udata; } -u64 PS4_SYSV_ABI sceKernelGetEventId(const SceKernelEvent* ev) { +u64 PS4_SYSV_ABI sceKernelGetEventId(const OrbisKernelEvent* ev) { return ev->ident; } -int PS4_SYSV_ABI sceKernelTriggerUserEvent(SceKernelEqueue eq, int id, void* udata) { - if (eq == nullptr) { +int PS4_SYSV_ABI sceKernelTriggerUserEvent(OrbisKernelEqueue eq, int id, void* udata) { + if (!kqueues.contains(eq)) { return ORBIS_KERNEL_ERROR_EBADF; } - if (!eq->TriggerEvent(id, SceKernelEvent::Filter::User, udata)) { + if (!kqueues[eq]->TriggerEvent(id, OrbisKernelEvent::Filter::User, udata)) { return ORBIS_KERNEL_ERROR_ENOENT; } return ORBIS_OK; } -int PS4_SYSV_ABI sceKernelDeleteUserEvent(SceKernelEqueue eq, int id) { - if (eq == nullptr) { +int PS4_SYSV_ABI sceKernelDeleteUserEvent(OrbisKernelEqueue eq, int id) { + if (!kqueues.contains(eq)) { return ORBIS_KERNEL_ERROR_EBADF; } - if (!eq->RemoveEvent(id, SceKernelEvent::Filter::User)) { + if (!kqueues[eq]->RemoveEvent(id, OrbisKernelEvent::Filter::User)) { return ORBIS_KERNEL_ERROR_ENOENT; } return ORBIS_OK; } -int PS4_SYSV_ABI sceKernelGetEventFilter(const SceKernelEvent* ev) { +int PS4_SYSV_ABI sceKernelGetEventFilter(const OrbisKernelEvent* ev) { return ev->filter; } -u64 PS4_SYSV_ABI sceKernelGetEventData(const SceKernelEvent* ev) { +u64 PS4_SYSV_ABI sceKernelGetEventData(const OrbisKernelEvent* ev) { return ev->data; } void RegisterEventQueue(Core::Loader::SymbolsResolver* sym) { + LIB_FUNCTION("nh2IFMgKTv8", "libScePosix", 1, "libkernel", posix_kqueue); + LIB_FUNCTION("RW-GEfpnsqg", "libScePosix", 1, "libkernel", posix_kevent); LIB_FUNCTION("D0OdFMjp46I", "libkernel", 1, "libkernel", sceKernelCreateEqueue); LIB_FUNCTION("jpFjmgAC5AE", "libkernel", 1, "libkernel", sceKernelDeleteEqueue); LIB_FUNCTION("fzyMKs9kim0", "libkernel", 1, "libkernel", sceKernelWaitEqueue); diff --git a/src/core/libraries/kernel/equeue.h b/src/core/libraries/kernel/equeue.h index 332aa15c2..9b3c73c19 100644 --- a/src/core/libraries/kernel/equeue.h +++ b/src/core/libraries/kernel/equeue.h @@ -22,10 +22,15 @@ namespace Libraries::Kernel { class EqueueInternal; struct EqueueEvent; -using SceKernelUseconds = u32; -using SceKernelEqueue = EqueueInternal*; +struct OrbisKernelBintime { + s64 sec; + s64 frac; +}; -struct SceKernelEvent { +using OrbisKernelUseconds = u32; +using OrbisKernelEqueue = s64; + +struct OrbisKernelEvent { enum Filter : s16 { None = 0, Read = -1, @@ -78,10 +83,10 @@ struct OrbisVideoOutEventData { }; struct EqueueEvent { - SceKernelEvent event; + OrbisKernelEvent event; void* data = nullptr; std::chrono::steady_clock::time_point time_added; - std::chrono::microseconds timer_interval; + std::chrono::nanoseconds timer_interval; std::unique_ptr timer; void Clear() { @@ -92,20 +97,23 @@ struct EqueueEvent { void Trigger(void* data) { is_triggered = true; - event.fflags++; event.data = reinterpret_cast(data); } void TriggerUser(void* data) { is_triggered = true; - event.fflags++; event.udata = data; } + void TriggerTimer() { + is_triggered = true; + event.data++; + } + void TriggerDisplay(void* data) { is_triggered = true; if (data != nullptr) { - auto event_data = static_cast(event.data); + auto event_data = std::bit_cast(event.data); auto event_hint_raw = reinterpret_cast(data); auto event_hint = static_cast(event_hint_raw); if (event_hint.event_id == event.ident && event.ident != 0xfe) { @@ -134,13 +142,14 @@ private: class EqueueInternal { struct SmallTimer { - SceKernelEvent event; + OrbisKernelEvent event; std::chrono::steady_clock::time_point added; - std::chrono::microseconds interval; + std::chrono::nanoseconds interval; }; public: - explicit EqueueInternal(std::string_view name) : m_name(name) {} + explicit EqueueInternal(OrbisKernelEqueue handle, std::string_view name) + : m_handle(handle), m_name(name) {} std::string_view GetName() const { return m_name; @@ -148,11 +157,11 @@ public: bool AddEvent(EqueueEvent& event); bool ScheduleEvent(u64 id, s16 filter, - void (*callback)(SceKernelEqueue, const SceKernelEvent&)); + void (*callback)(OrbisKernelEqueue, const OrbisKernelEvent&)); bool RemoveEvent(u64 id, s16 filter); - int WaitForEvents(SceKernelEvent* ev, int num, const SceKernelUseconds* timo); + int WaitForEvents(OrbisKernelEvent* ev, int num, const OrbisKernelUseconds* timo); bool TriggerEvent(u64 ident, s16 filter, void* trigger_data); - int GetTriggeredEvents(SceKernelEvent* ev, int num); + int GetTriggeredEvents(OrbisKernelEvent* ev, int num); bool AddSmallTimer(EqueueEvent& event); bool HasSmallTimer() { @@ -167,11 +176,12 @@ public: return false; } - int WaitForSmallTimer(SceKernelEvent* ev, int num, u32 micros); + int WaitForSmallTimer(OrbisKernelEvent* ev, int num, u32 micros); bool EventExists(u64 id, s16 filter); private: + OrbisKernelEqueue m_handle; std::string m_name; std::mutex m_mutex; std::vector m_events; @@ -179,7 +189,8 @@ private: std::unordered_map m_small_timers; }; -u64 PS4_SYSV_ABI sceKernelGetEventData(const SceKernelEvent* ev); +EqueueInternal* GetEqueue(OrbisKernelEqueue eq); +u64 PS4_SYSV_ABI sceKernelGetEventData(const OrbisKernelEvent* ev); void RegisterEventQueue(Core::Loader::SymbolsResolver* sym); diff --git a/src/core/libraries/kernel/file_system.cpp b/src/core/libraries/kernel/file_system.cpp index bc4e2def6..184343801 100644 --- a/src/core/libraries/kernel/file_system.cpp +++ b/src/core/libraries/kernel/file_system.cpp @@ -792,7 +792,8 @@ s32 PS4_SYSV_ABI fstat(s32 fd, OrbisKernelStat* sb) { return file->socket->fstat(sb); } case Core::FileSys::FileType::Epoll: - case Core::FileSys::FileType::Resolver: { + case Core::FileSys::FileType::Resolver: + case Core::FileSys::FileType::Equeue: { LOG_ERROR(Kernel_Fs, "(STUBBED) file type {}", magic_enum::enum_name(file->type.load())); break; } diff --git a/src/core/libraries/kernel/process.cpp b/src/core/libraries/kernel/process.cpp index 31cc9a81b..a79da62ee 100644 --- a/src/core/libraries/kernel/process.cpp +++ b/src/core/libraries/kernel/process.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "common/config.h" @@ -275,6 +275,10 @@ s32 PS4_SYSV_ABI sceKernelGetModuleList2(s32* handles, u64 num_array, u64* out_c return ORBIS_OK; } +u32 PS4_SYSV_ABI posix_getuid() { + return 1; +} + s32 PS4_SYSV_ABI exit(s32 status) { UNREACHABLE_MSG("Exiting with status code {}", status); return 0; @@ -299,6 +303,7 @@ void RegisterProcess(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("HZO7xOos4xc", "libkernel", 1, "libkernel", sceKernelGetModuleInfoInternal); LIB_FUNCTION("IuxnUuXk6Bg", "libkernel", 1, "libkernel", sceKernelGetModuleList); LIB_FUNCTION("ZzzC3ZGVAkc", "libkernel", 1, "libkernel", sceKernelGetModuleList2); + LIB_FUNCTION("kg4x8Prhfxw", "libkernel", 1, "libkernel", posix_getuid); LIB_FUNCTION("6Z83sYWFlA8", "libkernel", 1, "libkernel", exit); } diff --git a/src/core/libraries/kernel/threads/exception.cpp b/src/core/libraries/kernel/threads/exception.cpp index 95ced79c0..0b27f2bd8 100644 --- a/src/core/libraries/kernel/threads/exception.cpp +++ b/src/core/libraries/kernel/threads/exception.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "common/assert.h" @@ -6,6 +6,7 @@ #include "core/libraries/kernel/threads/exception.h" #include "core/libraries/kernel/threads/pthread.h" #include "core/libraries/libs.h" +#include "core/signals.h" #ifdef _WIN64 #include "common/ntapi.h" @@ -15,11 +16,156 @@ namespace Libraries::Kernel { -static std::array Handlers{}; +#ifdef _WIN32 + +// Windows doesn't have native versions of these, and we don't need to use them either. +static s32 NativeToOrbisSignal(s32 s) { + return s; +} + +static s32 OrbisToNativeSignal(s32 s) { + return s; +} + +#else + +static s32 NativeToOrbisSignal(s32 s) { + switch (s) { + case SIGHUP: + return POSIX_SIGHUP; + case SIGINT: + return POSIX_SIGINT; + case SIGQUIT: + return POSIX_SIGQUIT; + case SIGILL: + return POSIX_SIGILL; + case SIGTRAP: + return POSIX_SIGTRAP; + case SIGABRT: + return POSIX_SIGABRT; + case SIGFPE: + return POSIX_SIGFPE; + case SIGKILL: + return POSIX_SIGKILL; + case SIGBUS: + return POSIX_SIGBUS; + case SIGSEGV: + return POSIX_SIGSEGV; + case SIGSYS: + return POSIX_SIGSYS; + case SIGPIPE: + return POSIX_SIGPIPE; + case SIGALRM: + return POSIX_SIGALRM; + case SIGTERM: + return POSIX_SIGTERM; + case SIGURG: + return POSIX_SIGURG; + case SIGSTOP: + return POSIX_SIGSTOP; + case SIGTSTP: + return POSIX_SIGTSTP; + case SIGCONT: + return POSIX_SIGCONT; + case SIGCHLD: + return POSIX_SIGCHLD; + case SIGTTIN: + return POSIX_SIGTTIN; + case SIGTTOU: + return POSIX_SIGTTOU; + case SIGIO: + return POSIX_SIGIO; + case SIGXCPU: + return POSIX_SIGXCPU; + case SIGXFSZ: + return POSIX_SIGXFSZ; + case SIGVTALRM: + return POSIX_SIGVTALRM; + case SIGPROF: + return POSIX_SIGPROF; + case SIGWINCH: + return POSIX_SIGWINCH; + case SIGUSR1: + return POSIX_SIGUSR1; + case SIGUSR2: + return POSIX_SIGUSR2; + default: + UNREACHABLE_MSG("Unknown signal {}", s); + } +} + +static s32 OrbisToNativeSignal(s32 s) { + switch (s) { + case POSIX_SIGHUP: + return SIGHUP; + case POSIX_SIGINT: + return SIGINT; + case POSIX_SIGQUIT: + return SIGQUIT; + case POSIX_SIGILL: + return SIGILL; + case POSIX_SIGTRAP: + return SIGTRAP; + case POSIX_SIGABRT: + return SIGABRT; + case POSIX_SIGFPE: + return SIGFPE; + case POSIX_SIGKILL: + return SIGKILL; + case POSIX_SIGBUS: + return SIGBUS; + case POSIX_SIGSEGV: + return SIGSEGV; + case POSIX_SIGSYS: + return SIGSYS; + case POSIX_SIGPIPE: + return SIGPIPE; + case POSIX_SIGALRM: + return SIGALRM; + case POSIX_SIGTERM: + return SIGTERM; + case POSIX_SIGURG: + return SIGURG; + case POSIX_SIGSTOP: + return SIGSTOP; + case POSIX_SIGTSTP: + return SIGTSTP; + case POSIX_SIGCONT: + return SIGCONT; + case POSIX_SIGCHLD: + return SIGCHLD; + case POSIX_SIGTTIN: + return SIGTTIN; + case POSIX_SIGTTOU: + return SIGTTOU; + case POSIX_SIGIO: + return SIGIO; + case POSIX_SIGXCPU: + return SIGXCPU; + case POSIX_SIGXFSZ: + return SIGXFSZ; + case POSIX_SIGVTALRM: + return SIGVTALRM; + case POSIX_SIGPROF: + return SIGPROF; + case POSIX_SIGWINCH: + return SIGWINCH; + case POSIX_SIGUSR1: + return SIGUSR1; + case POSIX_SIGUSR2: + return SIGUSR2; + default: + UNREACHABLE_MSG("Unknown signal {}", s); + } +} + +#endif + +std::array Handlers{}; #ifndef _WIN64 -void SigactionHandler(int signum, siginfo_t* inf, ucontext_t* raw_context) { - const auto handler = Handlers[POSIX_SIGUSR1]; +void SigactionHandler(int native_signum, siginfo_t* inf, ucontext_t* raw_context) { + const auto handler = Handlers[native_signum]; if (handler) { auto ctx = Ucontext{}; #ifdef __APPLE__ @@ -42,6 +188,8 @@ void SigactionHandler(int signum, siginfo_t* inf, ucontext_t* raw_context) { ctx.uc_mcontext.mc_rsp = regs.__rsp; ctx.uc_mcontext.mc_fs = regs.__fs; ctx.uc_mcontext.mc_gs = regs.__gs; + ctx.uc_mcontext.mc_rip = regs.__rip; + ctx.uc_mcontext.mc_addr = reinterpret_cast(inf->si_addr); #else const auto& regs = raw_context->uc_mcontext.gregs; ctx.uc_mcontext.mc_r8 = regs[REG_R8]; @@ -62,15 +210,18 @@ void SigactionHandler(int signum, siginfo_t* inf, ucontext_t* raw_context) { ctx.uc_mcontext.mc_rsp = regs[REG_RSP]; ctx.uc_mcontext.mc_fs = (regs[REG_CSGSFS] >> 32) & 0xFFFF; ctx.uc_mcontext.mc_gs = (regs[REG_CSGSFS] >> 16) & 0xFFFF; + ctx.uc_mcontext.mc_rip = (regs[REG_RIP]); + ctx.uc_mcontext.mc_addr = reinterpret_cast(inf->si_addr); #endif - handler(POSIX_SIGUSR1, &ctx); + handler(NativeToOrbisSignal(native_signum), &ctx); } } #else void ExceptionHandler(void* arg1, void* arg2, void* arg3, PCONTEXT context) { const char* thrName = (char*)arg1; + int native_signum = reinterpret_cast(arg2); LOG_INFO(Lib_Kernel, "Exception raised successfully on thread '{}'", thrName); - const auto handler = Handlers[POSIX_SIGUSR1]; + const auto handler = Handlers[native_signum]; if (handler) { auto ctx = Ucontext{}; ctx.uc_mcontext.mc_r8 = context->R8; @@ -91,49 +242,71 @@ void ExceptionHandler(void* arg1, void* arg2, void* arg3, PCONTEXT context) { ctx.uc_mcontext.mc_rsp = context->Rsp; ctx.uc_mcontext.mc_fs = context->SegFs; ctx.uc_mcontext.mc_gs = context->SegGs; - handler(POSIX_SIGUSR1, &ctx); + handler(NativeToOrbisSignal(native_signum), &ctx); } } #endif int PS4_SYSV_ABI sceKernelInstallExceptionHandler(s32 signum, SceKernelExceptionHandler handler) { - if (signum != POSIX_SIGUSR1) { - LOG_ERROR(Lib_Kernel, "Installing non-supported exception handler for signal {}", signum); - return 0; + if (signum > POSIX_SIGUSR2) { + return ORBIS_KERNEL_ERROR_EINVAL; } - ASSERT_MSG(!Handlers[POSIX_SIGUSR1], "Invalid parameters"); - Handlers[POSIX_SIGUSR1] = handler; + LOG_INFO(Lib_Kernel, "Installing signal handler for {}", signum); + int const native_signum = OrbisToNativeSignal(signum); +#ifdef __APPLE__ + ASSERT_MSG(native_signum != SIGVTALRM, "SIGVTALRM is HLE-reserved on macOS!"); +#endif + ASSERT_MSG(!Handlers[native_signum], "Invalid parameters"); + Handlers[native_signum] = handler; #ifndef _WIN64 + if (native_signum == SIGSEGV || native_signum == SIGBUS || native_signum == SIGILL) { + return ORBIS_OK; // These are handled in Core::SignalHandler + } struct sigaction act = {}; act.sa_flags = SA_SIGINFO | SA_RESTART; act.sa_sigaction = reinterpret_cast(SigactionHandler); - sigaction(SIGUSR2, &act, nullptr); + sigemptyset(&act.sa_mask); + sigaction(native_signum, &act, nullptr); #endif - return 0; + return ORBIS_OK; } int PS4_SYSV_ABI sceKernelRemoveExceptionHandler(s32 signum) { - if (signum != POSIX_SIGUSR1) { - LOG_ERROR(Lib_Kernel, "Installing non-supported exception handler for signal {}", signum); - return 0; + if (signum > POSIX_SIGUSR2) { + return ORBIS_KERNEL_ERROR_EINVAL; } - ASSERT_MSG(Handlers[POSIX_SIGUSR1], "Invalid parameters"); - Handlers[POSIX_SIGUSR1] = nullptr; + int const native_signum = OrbisToNativeSignal(signum); + ASSERT_MSG(Handlers[native_signum], "Invalid parameters"); + Handlers[native_signum] = nullptr; #ifndef _WIN64 - struct sigaction act = {}; - act.sa_flags = SA_SIGINFO | SA_RESTART; - act.sa_sigaction = nullptr; - sigaction(SIGUSR2, &act, nullptr); + if (native_signum == SIGSEGV || native_signum == SIGBUS || native_signum == SIGILL) { + struct sigaction action{}; + action.sa_sigaction = Core::SignalHandler; + action.sa_flags = SA_SIGINFO | SA_ONSTACK; + sigemptyset(&action.sa_mask); + + ASSERT_MSG(sigaction(native_signum, &action, nullptr) == 0, + "Failed to reinstate original signal handler for signal {}", native_signum); + } else { + struct sigaction act = {}; + act.sa_flags = SA_SIGINFO | SA_RESTART; + act.sa_sigaction = nullptr; + sigemptyset(&act.sa_mask); + sigaction(native_signum, &act, nullptr); + } #endif - return 0; + return ORBIS_OK; } int PS4_SYSV_ABI sceKernelRaiseException(PthreadT thread, int signum) { + if (signum != POSIX_SIGUSR1) { + return ORBIS_KERNEL_ERROR_EINVAL; + } LOG_WARNING(Lib_Kernel, "Raising exception on thread '{}'", thread->name); - ASSERT_MSG(signum == POSIX_SIGUSR1, "Attempting to raise non user defined signal!"); + int const native_signum = OrbisToNativeSignal(signum); #ifndef _WIN64 const auto pthr = reinterpret_cast(thread->native_thr.GetHandle()); - const auto ret = pthread_kill(pthr, SIGUSR2); + const auto ret = pthread_kill(pthr, native_signum); if (ret != 0) { LOG_ERROR(Kernel, "Failed to send exception signal to thread '{}': {}", thread->name, strerror(ret)); @@ -143,10 +316,11 @@ int PS4_SYSV_ABI sceKernelRaiseException(PthreadT thread, int signum) { option.UserApcFlags = QueueUserApcFlagsSpecialUserApc; u64 res = NtQueueApcThreadEx(reinterpret_cast(thread->native_thr.GetHandle()), option, - ExceptionHandler, (void*)thread->name.c_str(), nullptr, nullptr); + ExceptionHandler, (void*)thread->name.c_str(), + (void*)native_signum, nullptr); ASSERT(res == 0); #endif - return 0; + return ORBIS_OK; } s32 PS4_SYSV_ABI sceKernelDebugRaiseException(s32 error, s64 unk) { @@ -154,7 +328,7 @@ s32 PS4_SYSV_ABI sceKernelDebugRaiseException(s32 error, s64 unk) { return ORBIS_KERNEL_ERROR_EINVAL; } UNREACHABLE_MSG("error {:#x}", error); - return 0; + return ORBIS_OK; } s32 PS4_SYSV_ABI sceKernelDebugRaiseExceptionOnReleaseMode(s32 error, s64 unk) { @@ -162,7 +336,7 @@ s32 PS4_SYSV_ABI sceKernelDebugRaiseExceptionOnReleaseMode(s32 error, s64 unk) { return ORBIS_KERNEL_ERROR_EINVAL; } UNREACHABLE_MSG("error {:#x}", error); - return 0; + return ORBIS_OK; } void RegisterException(Core::Loader::SymbolsResolver* sym) { diff --git a/src/core/libraries/kernel/threads/exception.h b/src/core/libraries/kernel/threads/exception.h index 6a851e9d8..42c92ab2e 100644 --- a/src/core/libraries/kernel/threads/exception.h +++ b/src/core/libraries/kernel/threads/exception.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -13,8 +13,39 @@ namespace Libraries::Kernel { using SceKernelExceptionHandler = PS4_SYSV_ABI void (*)(int, void*); -constexpr int POSIX_SIGSEGV = 11; -constexpr int POSIX_SIGUSR1 = 30; +constexpr s32 POSIX_SIGHUP = 1; +constexpr s32 POSIX_SIGINT = 2; +constexpr s32 POSIX_SIGQUIT = 3; +constexpr s32 POSIX_SIGILL = 4; +constexpr s32 POSIX_SIGTRAP = 5; +constexpr s32 POSIX_SIGABRT = 6; +constexpr s32 POSIX_SIGEMT = 7; +constexpr s32 POSIX_SIGFPE = 8; +constexpr s32 POSIX_SIGKILL = 9; +constexpr s32 POSIX_SIGBUS = 10; +constexpr s32 POSIX_SIGSEGV = 11; +constexpr s32 POSIX_SIGSYS = 12; +constexpr s32 POSIX_SIGPIPE = 13; +constexpr s32 POSIX_SIGALRM = 14; +constexpr s32 POSIX_SIGTERM = 15; +constexpr s32 POSIX_SIGURG = 16; +constexpr s32 POSIX_SIGSTOP = 17; +constexpr s32 POSIX_SIGTSTP = 18; +constexpr s32 POSIX_SIGCONT = 19; +constexpr s32 POSIX_SIGCHLD = 20; +constexpr s32 POSIX_SIGTTIN = 21; +constexpr s32 POSIX_SIGTTOU = 22; +constexpr s32 POSIX_SIGIO = 23; +constexpr s32 POSIX_SIGXCPU = 24; +constexpr s32 POSIX_SIGXFSZ = 25; +constexpr s32 POSIX_SIGVTALRM = 26; +constexpr s32 POSIX_SIGPROF = 27; +constexpr s32 POSIX_SIGWINCH = 28; +constexpr s32 POSIX_SIGINFO = 29; +constexpr s32 POSIX_SIGUSR1 = 30; +constexpr s32 POSIX_SIGUSR2 = 31; +constexpr s32 POSIX_SIGTHR = 32; +constexpr s32 POSIX_SIGLIBRT = 33; struct Mcontext { u64 mc_onstack; diff --git a/src/core/libraries/kernel/threads/mutex.cpp b/src/core/libraries/kernel/threads/mutex.cpp index 31e8b900b..51d2d3bcd 100644 --- a/src/core/libraries/kernel/threads/mutex.cpp +++ b/src/core/libraries/kernel/threads/mutex.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include @@ -39,7 +39,7 @@ static constexpr PthreadMutexAttr PthreadMutexattrAdaptiveDefault = { using CallocFun = void* (*)(size_t, size_t); -static int MutexInit(PthreadMutexT* mutex, const PthreadMutexAttr* mutex_attr, const char* name) { +static s32 MutexInit(PthreadMutexT* mutex, const PthreadMutexAttr* mutex_attr, const char* name) { const PthreadMutexAttr* attr; if (mutex_attr == nullptr) { attr = &PthreadMutexattrDefault; @@ -60,7 +60,7 @@ static int MutexInit(PthreadMutexT* mutex, const PthreadMutexAttr* mutex_attr, c if (name) { pmutex->name = name; } else { - static int MutexId = 0; + static s32 MutexId = 0; pmutex->name = fmt::format("Mutex{}", MutexId++); } @@ -79,7 +79,7 @@ static int MutexInit(PthreadMutexT* mutex, const PthreadMutexAttr* mutex_attr, c return 0; } -static int InitStatic(Pthread* thread, PthreadMutexT* mutex) { +static s32 InitStatic(Pthread* thread, PthreadMutexT* mutex) { std::scoped_lock lk{MutxStaticLock}; if (*mutex == THR_MUTEX_INITIALIZER) { @@ -90,17 +90,17 @@ static int InitStatic(Pthread* thread, PthreadMutexT* mutex) { return 0; } -int PS4_SYSV_ABI posix_pthread_mutex_init(PthreadMutexT* mutex, +s32 PS4_SYSV_ABI posix_pthread_mutex_init(PthreadMutexT* mutex, const PthreadMutexAttrT* mutex_attr) { return MutexInit(mutex, mutex_attr ? *mutex_attr : nullptr, nullptr); } -int PS4_SYSV_ABI scePthreadMutexInit(PthreadMutexT* mutex, const PthreadMutexAttrT* mutex_attr, +s32 PS4_SYSV_ABI scePthreadMutexInit(PthreadMutexT* mutex, const PthreadMutexAttrT* mutex_attr, const char* name) { return MutexInit(mutex, mutex_attr ? *mutex_attr : nullptr, name); } -int PS4_SYSV_ABI posix_pthread_mutex_destroy(PthreadMutexT* mutex) { +s32 PS4_SYSV_ABI posix_pthread_mutex_destroy(PthreadMutexT* mutex) { PthreadMutexT m = *mutex; if (m < THR_MUTEX_DESTROYED) { return 0; @@ -116,7 +116,7 @@ int PS4_SYSV_ABI posix_pthread_mutex_destroy(PthreadMutexT* mutex) { return 0; } -int PthreadMutex::SelfTryLock() { +s32 PthreadMutex::SelfTryLock() { switch (Type()) { case PthreadMutexType::ErrorCheck: case PthreadMutexType::Normal: @@ -135,7 +135,7 @@ int PthreadMutex::SelfTryLock() { } } -int PthreadMutex::SelfLock(const OrbisKernelTimespec* abstime, u64 usec) { +s32 PthreadMutex::SelfLock(const OrbisKernelTimespec* abstime, u64 usec) { const auto DoSleep = [&] { if (abstime == THR_RELTIME) { std::this_thread::sleep_for(std::chrono::microseconds(usec)); @@ -185,7 +185,7 @@ int PthreadMutex::SelfLock(const OrbisKernelTimespec* abstime, u64 usec) { } } -int PthreadMutex::Lock(const OrbisKernelTimespec* abstime, u64 usec) { +s32 PthreadMutex::Lock(const OrbisKernelTimespec* abstime, u64 usec) { Pthread* curthread = g_curthread; if (m_owner == curthread) { return SelfLock(abstime, usec); @@ -198,7 +198,7 @@ int PthreadMutex::Lock(const OrbisKernelTimespec* abstime, u64 usec) { * faster than entering the kernel */ if (m_protocol == PthreadMutexProt::None) [[likely]] { - int count = m_spinloops; + s32 count = m_spinloops; while (count--) { if (m_lock.try_lock()) { m_owner = curthread; @@ -217,7 +217,7 @@ int PthreadMutex::Lock(const OrbisKernelTimespec* abstime, u64 usec) { } } - int ret = 0; + s32 ret = 0; if (abstime == nullptr) { m_lock.lock(); } else if (abstime != THR_RELTIME && (abstime->tv_nsec < 0 || abstime->tv_nsec >= 1000000000)) @@ -236,40 +236,40 @@ int PthreadMutex::Lock(const OrbisKernelTimespec* abstime, u64 usec) { return ret; } -int PthreadMutex::TryLock() { +s32 PthreadMutex::TryLock() { Pthread* curthread = g_curthread; if (m_owner == curthread) { return SelfTryLock(); } - const int ret = m_lock.try_lock() ? 0 : POSIX_EBUSY; + const s32 ret = m_lock.try_lock() ? 0 : POSIX_EBUSY; if (ret == 0) { m_owner = curthread; } return ret; } -int PS4_SYSV_ABI posix_pthread_mutex_trylock(PthreadMutexT* mutex) { +s32 PS4_SYSV_ABI posix_pthread_mutex_trylock(PthreadMutexT* mutex) { CHECK_AND_INIT_MUTEX return (*mutex)->TryLock(); } -int PS4_SYSV_ABI posix_pthread_mutex_lock(PthreadMutexT* mutex) { +s32 PS4_SYSV_ABI posix_pthread_mutex_lock(PthreadMutexT* mutex) { CHECK_AND_INIT_MUTEX return (*mutex)->Lock(nullptr); } -int PS4_SYSV_ABI posix_pthread_mutex_timedlock(PthreadMutexT* mutex, +s32 PS4_SYSV_ABI posix_pthread_mutex_timedlock(PthreadMutexT* mutex, const OrbisKernelTimespec* abstime) { CHECK_AND_INIT_MUTEX return (*mutex)->Lock(abstime); } -int PS4_SYSV_ABI posix_pthread_mutex_reltimedlock_np(PthreadMutexT* mutex, u64 usec) { +s32 PS4_SYSV_ABI posix_pthread_mutex_reltimedlock_np(PthreadMutexT* mutex, u64 usec) { CHECK_AND_INIT_MUTEX return (*mutex)->Lock(THR_RELTIME, usec); } -int PthreadMutex::Unlock() { +s32 PthreadMutex::Unlock() { Pthread* curthread = g_curthread; /* * Check if the running thread is not the owner of the mutex. @@ -294,7 +294,7 @@ int PthreadMutex::Unlock() { return 0; } -int PS4_SYSV_ABI posix_pthread_mutex_unlock(PthreadMutexT* mutex) { +s32 PS4_SYSV_ABI posix_pthread_mutex_unlock(PthreadMutexT* mutex) { PthreadMutex* mp = *mutex; if (mp <= THR_MUTEX_DESTROYED) [[unlikely]] { if (mp == THR_MUTEX_DESTROYED) { @@ -305,29 +305,29 @@ int PS4_SYSV_ABI posix_pthread_mutex_unlock(PthreadMutexT* mutex) { return mp->Unlock(); } -int PS4_SYSV_ABI posix_pthread_mutex_getspinloops_np(PthreadMutexT* mutex, int* count) { +s32 PS4_SYSV_ABI posix_pthread_mutex_getspinloops_np(PthreadMutexT* mutex, int* count) { CHECK_AND_INIT_MUTEX *count = (*mutex)->m_spinloops; return 0; } -int PS4_SYSV_ABI posix_pthread_mutex_setspinloops_np(PthreadMutexT* mutex, int count) { +s32 PS4_SYSV_ABI posix_pthread_mutex_setspinloops_np(PthreadMutexT* mutex, s32 count) { CHECK_AND_INIT_MUTEX(*mutex)->m_spinloops = count; return 0; } -int PS4_SYSV_ABI posix_pthread_mutex_getyieldloops_np(PthreadMutexT* mutex, int* count) { +s32 PS4_SYSV_ABI posix_pthread_mutex_getyieldloops_np(PthreadMutexT* mutex, int* count) { CHECK_AND_INIT_MUTEX *count = (*mutex)->m_yieldloops; return 0; } -int PS4_SYSV_ABI posix_pthread_mutex_setyieldloops_np(PthreadMutexT* mutex, int count) { +s32 PS4_SYSV_ABI posix_pthread_mutex_setyieldloops_np(PthreadMutexT* mutex, s32 count) { CHECK_AND_INIT_MUTEX(*mutex)->m_yieldloops = count; return 0; } -int PS4_SYSV_ABI posix_pthread_mutex_isowned_np(PthreadMutexT* mutex) { +s32 PS4_SYSV_ABI posix_pthread_mutex_isowned_np(PthreadMutexT* mutex) { PthreadMutex* m = *mutex; if (m <= THR_MUTEX_DESTROYED) { return 0; @@ -335,7 +335,7 @@ int PS4_SYSV_ABI posix_pthread_mutex_isowned_np(PthreadMutexT* mutex) { return m->m_owner == g_curthread; } -int PthreadMutex::IsOwned(Pthread* curthread) const { +s32 PthreadMutex::IsOwned(Pthread* curthread) const { if (this <= THR_MUTEX_DESTROYED) [[unlikely]] { if (this == THR_MUTEX_DESTROYED) { return POSIX_EINVAL; @@ -348,7 +348,7 @@ int PthreadMutex::IsOwned(Pthread* curthread) const { return 0; } -int PS4_SYSV_ABI posix_pthread_mutexattr_init(PthreadMutexAttrT* attr) { +s32 PS4_SYSV_ABI posix_pthread_mutexattr_init(PthreadMutexAttrT* attr) { auto pattr = new (std::nothrow) PthreadMutexAttr{}; if (pattr == nullptr) { return POSIX_ENOMEM; @@ -358,7 +358,7 @@ int PS4_SYSV_ABI posix_pthread_mutexattr_init(PthreadMutexAttrT* attr) { return 0; } -int PS4_SYSV_ABI posix_pthread_mutexattr_setkind_np(PthreadMutexAttrT* attr, +s32 PS4_SYSV_ABI posix_pthread_mutexattr_setkind_np(PthreadMutexAttrT* attr, PthreadMutexType kind) { if (attr == nullptr || *attr == nullptr) { *__Error() = POSIX_EINVAL; @@ -368,7 +368,7 @@ int PS4_SYSV_ABI posix_pthread_mutexattr_setkind_np(PthreadMutexAttrT* attr, return 0; } -int PS4_SYSV_ABI posix_pthread_mutexattr_getkind_np(PthreadMutexAttrT attr) { +s32 PS4_SYSV_ABI posix_pthread_mutexattr_getkind_np(PthreadMutexAttrT attr) { if (attr == nullptr) { *__Error() = POSIX_EINVAL; return -1; @@ -376,7 +376,7 @@ int PS4_SYSV_ABI posix_pthread_mutexattr_getkind_np(PthreadMutexAttrT attr) { return static_cast(attr->m_type); } -int PS4_SYSV_ABI posix_pthread_mutexattr_settype(PthreadMutexAttrT* attr, PthreadMutexType type) { +s32 PS4_SYSV_ABI posix_pthread_mutexattr_settype(PthreadMutexAttrT* attr, PthreadMutexType type) { if (attr == nullptr || *attr == nullptr || type < PthreadMutexType::ErrorCheck || type >= PthreadMutexType::Max) { return POSIX_EINVAL; @@ -385,7 +385,7 @@ int PS4_SYSV_ABI posix_pthread_mutexattr_settype(PthreadMutexAttrT* attr, Pthrea return 0; } -int PS4_SYSV_ABI posix_pthread_mutexattr_gettype(PthreadMutexAttrT* attr, PthreadMutexType* type) { +s32 PS4_SYSV_ABI posix_pthread_mutexattr_gettype(PthreadMutexAttrT* attr, PthreadMutexType* type) { if (attr == nullptr || *attr == nullptr || (*attr)->m_type >= PthreadMutexType::Max) { return POSIX_EINVAL; } @@ -393,7 +393,7 @@ int PS4_SYSV_ABI posix_pthread_mutexattr_gettype(PthreadMutexAttrT* attr, Pthrea return 0; } -int PS4_SYSV_ABI posix_pthread_mutexattr_destroy(PthreadMutexAttrT* attr) { +s32 PS4_SYSV_ABI posix_pthread_mutexattr_destroy(PthreadMutexAttrT* attr) { if (attr == nullptr || *attr == nullptr) { return POSIX_EINVAL; } @@ -402,7 +402,7 @@ int PS4_SYSV_ABI posix_pthread_mutexattr_destroy(PthreadMutexAttrT* attr) { return 0; } -int PS4_SYSV_ABI posix_pthread_mutexattr_getprotocol(PthreadMutexAttrT* mattr, +s32 PS4_SYSV_ABI posix_pthread_mutexattr_getprotocol(PthreadMutexAttrT* mattr, PthreadMutexProt* protocol) { if (mattr == nullptr || *mattr == nullptr) { return POSIX_EINVAL; @@ -411,7 +411,7 @@ int PS4_SYSV_ABI posix_pthread_mutexattr_getprotocol(PthreadMutexAttrT* mattr, return 0; } -int PS4_SYSV_ABI posix_pthread_mutexattr_setprotocol(PthreadMutexAttrT* mattr, +s32 PS4_SYSV_ABI posix_pthread_mutexattr_setprotocol(PthreadMutexAttrT* mattr, PthreadMutexProt protocol) { if (mattr == nullptr || *mattr == nullptr || (protocol < PthreadMutexProt::None) || (protocol > PthreadMutexProt::Protect)) { @@ -422,6 +422,15 @@ int PS4_SYSV_ABI posix_pthread_mutexattr_setprotocol(PthreadMutexAttrT* mattr, return 0; } +s32 PS4_SYSV_ABI posix_pthread_mutexattr_setpshared(PthreadMutexAttrT* attr, s32 pshared) { + constexpr s32 POSIX_PTHREAD_PROCESS_PRIVATE = 0; + constexpr s32 POSIX_PTHREAD_PROCESS_SHARED = 1; + if (!attr || !*attr || pshared != POSIX_PTHREAD_PROCESS_PRIVATE) { + return POSIX_EINVAL; + } + return ORBIS_OK; +} + void RegisterMutex(Core::Loader::SymbolsResolver* sym) { // Posix LIB_FUNCTION("ttHNfU+qDBU", "libScePosix", 1, "libkernel", posix_pthread_mutex_init); @@ -434,6 +443,7 @@ void RegisterMutex(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("5txKfcMUAok", "libScePosix", 1, "libkernel", posix_pthread_mutexattr_setprotocol); LIB_FUNCTION("HF7lK46xzjY", "libScePosix", 1, "libkernel", posix_pthread_mutexattr_destroy); LIB_FUNCTION("K-jXhbt2gn4", "libScePosix", 1, "libkernel", posix_pthread_mutex_trylock); + LIB_FUNCTION("EXv3ztGqtDM", "libScePosix", 1, "libkernel", posix_pthread_mutexattr_setpshared); // Posix-Kernel LIB_FUNCTION("ttHNfU+qDBU", "libkernel", 1, "libkernel", posix_pthread_mutex_init); @@ -444,6 +454,7 @@ void RegisterMutex(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("mDmgMOGVUqg", "libkernel", 1, "libkernel", posix_pthread_mutexattr_settype); LIB_FUNCTION("HF7lK46xzjY", "libkernel", 1, "libkernel", posix_pthread_mutexattr_destroy); LIB_FUNCTION("K-jXhbt2gn4", "libkernel", 1, "libkernel", posix_pthread_mutex_trylock); + LIB_FUNCTION("EXv3ztGqtDM", "libkernel", 1, "libkernel", posix_pthread_mutexattr_setpshared); // Orbis LIB_FUNCTION("cmo1RIYva9o", "libkernel", 1, "libkernel", ORBIS(scePthreadMutexInit)); diff --git a/src/core/libraries/kernel/threads/pthread_attr.cpp b/src/core/libraries/kernel/threads/pthread_attr.cpp index aa5781f4f..2f3f26049 100644 --- a/src/core/libraries/kernel/threads/pthread_attr.cpp +++ b/src/core/libraries/kernel/threads/pthread_attr.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "core/libraries/kernel/kernel.h" @@ -291,7 +291,7 @@ void RegisterThreadAttr(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("2Q0z6rnBrTE", "libScePosix", 1, "libkernel", posix_pthread_attr_setstacksize); LIB_FUNCTION("Ucsu-OK+els", "libScePosix", 1, "libkernel", posix_pthread_attr_get_np); LIB_FUNCTION("RtLRV-pBTTY", "libScePosix", 1, "libkernel", posix_pthread_attr_getschedpolicy); - LIB_FUNCTION("JarMIy8kKEY", "libkernel", 1, "libkernel", posix_pthread_attr_setschedpolicy); + LIB_FUNCTION("JarMIy8kKEY", "libScePosix", 1, "libkernel", posix_pthread_attr_setschedpolicy); LIB_FUNCTION("E+tyo3lp5Lw", "libScePosix", 1, "libkernel", posix_pthread_attr_setdetachstate); LIB_FUNCTION("zHchY8ft5pk", "libScePosix", 1, "libkernel", posix_pthread_attr_destroy); LIB_FUNCTION("euKRgm0Vn2M", "libScePosix", 1, "libkernel", posix_pthread_attr_setschedparam); diff --git a/src/core/libraries/kernel/time.cpp b/src/core/libraries/kernel/time.cpp index 51f86e2c7..3e1648b98 100644 --- a/src/core/libraries/kernel/time.cpp +++ b/src/core/libraries/kernel/time.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include @@ -548,6 +548,8 @@ void RegisterTime(Core::Loader::SymbolsResolver* sym) { initial_ptc = clock->GetUptime(); // POSIX + LIB_FUNCTION("NhpspxdjEKU", "libkernel", 1, "libkernel", posix_nanosleep); + LIB_FUNCTION("NhpspxdjEKU", "libScePosix", 1, "libkernel", posix_nanosleep); LIB_FUNCTION("yS8U2TGCe1A", "libkernel", 1, "libkernel", posix_nanosleep); LIB_FUNCTION("yS8U2TGCe1A", "libScePosix", 1, "libkernel", posix_nanosleep); LIB_FUNCTION("QcteRwbsnV0", "libkernel", 1, "libkernel", posix_usleep); diff --git a/src/core/libraries/save_data/save_instance.cpp b/src/core/libraries/save_data/save_instance.cpp index bc6bbfd72..baeec5d2c 100644 --- a/src/core/libraries/save_data/save_instance.cpp +++ b/src/core/libraries/save_data/save_instance.cpp @@ -105,7 +105,7 @@ SaveInstance::SaveInstance(int slot_num, Libraries::UserService::OrbisUserServic mount_point = "/savedata" + std::to_string(slot_num); this->exists = fs::exists(param_sfo_path); - this->mounted = g_mnt->GetMount(mount_point) != nullptr; + this->mounted = g_mnt->GetMount(mount_point) != std::nullopt; } SaveInstance::~SaveInstance() { diff --git a/src/core/libraries/videoout/driver.cpp b/src/core/libraries/videoout/driver.cpp index 4951c4f1a..bebbf9602 100644 --- a/src/core/libraries/videoout/driver.cpp +++ b/src/core/libraries/videoout/driver.cpp @@ -193,7 +193,7 @@ void VideoOutDriver::Flip(const Request& req) { if (event != nullptr) { event->TriggerEvent( static_cast(OrbisVideoOutInternalEventId::Flip), - Kernel::SceKernelEvent::Filter::VideoOut, + Kernel::OrbisKernelEvent::Filter::VideoOut, reinterpret_cast(static_cast(OrbisVideoOutInternalEventId::Flip) | (req.flip_arg << 16))); } @@ -224,7 +224,7 @@ bool VideoOutDriver::SubmitFlip(VideoOutPort* port, s32 index, s64 flip_arg, bool is_eop /*= false*/) { { std::unique_lock lock{port->port_mutex}; - if (index != -1 && port->flip_status.flip_pending_num >= port->NumRegisteredBuffers()) { + if (index != -1 && port->flip_status.flip_pending_num > 16) { LOG_ERROR(Lib_VideoOut, "Flip queue is full"); return false; } @@ -252,6 +252,7 @@ void VideoOutDriver::SubmitFlipInternal(VideoOutPort* port, s32 index, s64 flip_ frame = presenter->PrepareBlankFrame(false); } else { const auto& buffer = port->buffer_slots[index]; + ASSERT_MSG(buffer.group_index >= 0, "Trying to flip an unregistered buffer!"); const auto& group = port->groups[buffer.group_index]; frame = presenter->PrepareFrame(group, buffer.address_left); } @@ -314,20 +315,25 @@ void VideoOutDriver::PresentThread(std::stop_token token) { { // Needs lock here as can be concurrently read by `sceVideoOutGetVblankStatus` std::scoped_lock lock{main_port.vo_mutex}; + + // Trigger flip events for the port + for (auto& event : main_port.vblank_events) { + if (event != nullptr) { + event->TriggerEvent(static_cast(OrbisVideoOutInternalEventId::Vblank), + Kernel::OrbisKernelEvent::Filter::VideoOut, + reinterpret_cast( + static_cast(OrbisVideoOutInternalEventId::Vblank) | + (vblank_status.count << 16))); + } + } + + // Update vblank status vblank_status.count++; vblank_status.process_time = Libraries::Kernel::sceKernelGetProcessTime(); vblank_status.tsc = Libraries::Kernel::sceKernelReadTsc(); main_port.vblank_cv.notify_all(); } - // Trigger flip events for the port. - for (auto& event : main_port.vblank_events) { - if (event != nullptr) { - event->TriggerEvent(static_cast(OrbisVideoOutInternalEventId::Vblank), - Kernel::SceKernelEvent::Filter::VideoOut, nullptr); - } - } - timer.End(); } } diff --git a/src/core/libraries/videoout/driver.h b/src/core/libraries/videoout/driver.h index e0eace791..96bd58500 100644 --- a/src/core/libraries/videoout/driver.h +++ b/src/core/libraries/videoout/driver.h @@ -25,8 +25,8 @@ struct VideoOutPort { std::array groups; FlipStatus flip_status; SceVideoOutVblankStatus vblank_status; - std::vector flip_events; - std::vector vblank_events; + std::vector flip_events; + std::vector vblank_events; std::mutex vo_mutex; std::mutex port_mutex; std::condition_variable vo_cv; diff --git a/src/core/libraries/videoout/video_out.cpp b/src/core/libraries/videoout/video_out.cpp index e9176afdc..1b8a6b59d 100644 --- a/src/core/libraries/videoout/video_out.cpp +++ b/src/core/libraries/videoout/video_out.cpp @@ -38,7 +38,7 @@ void PS4_SYSV_ABI sceVideoOutSetBufferAttribute(BufferAttribute* attribute, Pixe attribute->option = SCE_VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_NONE; } -s32 PS4_SYSV_ABI sceVideoOutAddFlipEvent(Kernel::SceKernelEqueue eq, s32 handle, void* udata) { +s32 PS4_SYSV_ABI sceVideoOutAddFlipEvent(Kernel::OrbisKernelEqueue eq, s32 handle, void* udata) { LOG_INFO(Lib_VideoOut, "handle = {}", handle); auto* port = driver->GetPort(handle); @@ -46,39 +46,41 @@ s32 PS4_SYSV_ABI sceVideoOutAddFlipEvent(Kernel::SceKernelEqueue eq, s32 handle, return ORBIS_VIDEO_OUT_ERROR_INVALID_HANDLE; } - if (eq == nullptr) { + auto equeue = Kernel::GetEqueue(eq); + if (equeue == nullptr) { return ORBIS_VIDEO_OUT_ERROR_INVALID_EVENT_QUEUE; } Kernel::EqueueEvent event{}; event.event.ident = static_cast(OrbisVideoOutInternalEventId::Flip); - event.event.filter = Kernel::SceKernelEvent::Filter::VideoOut; - event.event.flags = Kernel::SceKernelEvent::Flags::Add; + event.event.filter = Kernel::OrbisKernelEvent::Filter::VideoOut; + event.event.flags = Kernel::OrbisKernelEvent::Flags::Add; event.event.udata = udata; event.event.fflags = 0; event.event.data = 0; event.data = port; - eq->AddEvent(event); + equeue->AddEvent(event); - port->flip_events.push_back(eq); + port->flip_events.push_back(equeue); return ORBIS_OK; } -s32 PS4_SYSV_ABI sceVideoOutDeleteFlipEvent(Kernel::SceKernelEqueue eq, s32 handle) { +s32 PS4_SYSV_ABI sceVideoOutDeleteFlipEvent(Kernel::OrbisKernelEqueue eq, s32 handle) { auto* port = driver->GetPort(handle); if (port == nullptr) { return ORBIS_VIDEO_OUT_ERROR_INVALID_HANDLE; } - if (eq == nullptr) { + auto equeue = Kernel::GetEqueue(eq); + if (equeue == nullptr) { return ORBIS_VIDEO_OUT_ERROR_INVALID_EVENT_QUEUE; } - eq->RemoveEvent(handle, Kernel::SceKernelEvent::Filter::VideoOut); - port->flip_events.erase(find(port->flip_events.begin(), port->flip_events.end(), eq)); + equeue->RemoveEvent(handle, Kernel::OrbisKernelEvent::Filter::VideoOut); + port->flip_events.erase(find(port->flip_events.begin(), port->flip_events.end(), equeue)); return ORBIS_OK; } -s32 PS4_SYSV_ABI sceVideoOutAddVblankEvent(Kernel::SceKernelEqueue eq, s32 handle, void* udata) { +s32 PS4_SYSV_ABI sceVideoOutAddVblankEvent(Kernel::OrbisKernelEqueue eq, s32 handle, void* udata) { LOG_INFO(Lib_VideoOut, "handle = {}", handle); auto* port = driver->GetPort(handle); @@ -86,35 +88,37 @@ s32 PS4_SYSV_ABI sceVideoOutAddVblankEvent(Kernel::SceKernelEqueue eq, s32 handl return ORBIS_VIDEO_OUT_ERROR_INVALID_HANDLE; } - if (eq == nullptr) { + auto equeue = Kernel::GetEqueue(eq); + if (equeue == nullptr) { return ORBIS_VIDEO_OUT_ERROR_INVALID_EVENT_QUEUE; } Kernel::EqueueEvent event{}; event.event.ident = static_cast(OrbisVideoOutInternalEventId::Vblank); - event.event.filter = Kernel::SceKernelEvent::Filter::VideoOut; - event.event.flags = Kernel::SceKernelEvent::Flags::Add; + event.event.filter = Kernel::OrbisKernelEvent::Filter::VideoOut; + event.event.flags = Kernel::OrbisKernelEvent::Flags::Add; event.event.udata = udata; event.event.fflags = 0; event.event.data = 0; event.data = port; - eq->AddEvent(event); + equeue->AddEvent(event); - port->vblank_events.push_back(eq); + port->vblank_events.push_back(equeue); return ORBIS_OK; } -s32 PS4_SYSV_ABI sceVideoOutDeleteVblankEvent(Kernel::SceKernelEqueue eq, s32 handle) { +s32 PS4_SYSV_ABI sceVideoOutDeleteVblankEvent(Kernel::OrbisKernelEqueue eq, s32 handle) { auto* port = driver->GetPort(handle); if (port == nullptr) { return ORBIS_VIDEO_OUT_ERROR_INVALID_HANDLE; } - if (eq == nullptr) { + auto equeue = Kernel::GetEqueue(eq); + if (equeue == nullptr) { return ORBIS_VIDEO_OUT_ERROR_INVALID_EVENT_QUEUE; } - eq->RemoveEvent(handle, Kernel::SceKernelEvent::Filter::VideoOut); - port->vblank_events.erase(find(port->vblank_events.begin(), port->vblank_events.end(), eq)); + equeue->RemoveEvent(handle, Kernel::OrbisKernelEvent::Filter::VideoOut); + port->vblank_events.erase(find(port->vblank_events.begin(), port->vblank_events.end(), equeue)); return ORBIS_OK; } @@ -180,11 +184,11 @@ s32 PS4_SYSV_ABI sceVideoOutSubmitFlip(s32 handle, s32 bufferIndex, s32 flipMode return ORBIS_OK; } -s32 PS4_SYSV_ABI sceVideoOutGetEventId(const Kernel::SceKernelEvent* ev) { +s32 PS4_SYSV_ABI sceVideoOutGetEventId(const Kernel::OrbisKernelEvent* ev) { if (ev == nullptr) { return ORBIS_VIDEO_OUT_ERROR_INVALID_ADDRESS; } - if (ev->filter != Kernel::SceKernelEvent::Filter::VideoOut) { + if (ev->filter != Kernel::OrbisKernelEvent::Filter::VideoOut) { return ORBIS_VIDEO_OUT_ERROR_INVALID_EVENT; } @@ -208,16 +212,16 @@ s32 PS4_SYSV_ABI sceVideoOutGetEventId(const Kernel::SceKernelEvent* ev) { } } -s32 PS4_SYSV_ABI sceVideoOutGetEventData(const Kernel::SceKernelEvent* ev, s64* data) { +s32 PS4_SYSV_ABI sceVideoOutGetEventData(const Kernel::OrbisKernelEvent* ev, s64* data) { if (ev == nullptr || data == nullptr) { return ORBIS_VIDEO_OUT_ERROR_INVALID_ADDRESS; } - if (ev->filter != Kernel::SceKernelEvent::Filter::VideoOut) { + if (ev->filter != Kernel::OrbisKernelEvent::Filter::VideoOut) { return ORBIS_VIDEO_OUT_ERROR_INVALID_EVENT; } auto event_data = ev->data >> 0x10; - if (ev->ident != static_cast(OrbisVideoOutInternalEventId::Flip) || ev->data == 0) { + if (ev->ident != static_cast(OrbisVideoOutInternalEventId::Flip) || ev->data >= 0) { *data = event_data; } else { *data = event_data | 0xffff000000000000; @@ -225,11 +229,11 @@ s32 PS4_SYSV_ABI sceVideoOutGetEventData(const Kernel::SceKernelEvent* ev, s64* return ORBIS_OK; } -s32 PS4_SYSV_ABI sceVideoOutGetEventCount(const Kernel::SceKernelEvent* ev) { +s32 PS4_SYSV_ABI sceVideoOutGetEventCount(const Kernel::OrbisKernelEvent* ev) { if (ev == nullptr) { return ORBIS_VIDEO_OUT_ERROR_INVALID_ADDRESS; } - if (ev->filter != Kernel::SceKernelEvent::Filter::VideoOut) { + if (ev->filter != Kernel::OrbisKernelEvent::Filter::VideoOut) { return ORBIS_VIDEO_OUT_ERROR_INVALID_EVENT; } @@ -338,7 +342,7 @@ s32 PS4_SYSV_ABI sceVideoOutGetBufferLabelAddress(s32 handle, uintptr_t* label_a return 16; } -s32 sceVideoOutSubmitEopFlip(s32 handle, u32 buf_id, u32 mode, u32 arg, void** unk) { +s32 sceVideoOutSubmitEopFlip(s32 handle, u32 buf_id, u32 mode, s64 flip_arg, void** unk) { auto* port = driver->GetPort(handle); if (!port) { return ORBIS_VIDEO_OUT_ERROR_INVALID_HANDLE; @@ -348,7 +352,7 @@ s32 sceVideoOutSubmitEopFlip(s32 handle, u32 buf_id, u32 mode, u32 arg, void** u Platform::InterruptId::GfxFlip, [=](Platform::InterruptId irq) { ASSERT_MSG(irq == Platform::InterruptId::GfxFlip, "An unexpected IRQ occured"); ASSERT_MSG(port->buffer_labels[buf_id] == 1, "Out of order flip IRQ"); - const auto result = driver->SubmitFlip(port, buf_id, arg, true); + const auto result = driver->SubmitFlip(port, buf_id, flip_arg, true); ASSERT_MSG(result, "EOP flip submission failed"); }); diff --git a/src/core/libraries/videoout/video_out.h b/src/core/libraries/videoout/video_out.h index ba2732ff7..09b79e85d 100644 --- a/src/core/libraries/videoout/video_out.h +++ b/src/core/libraries/videoout/video_out.h @@ -119,8 +119,8 @@ struct OrbisVideoOutEventData { void PS4_SYSV_ABI sceVideoOutSetBufferAttribute(BufferAttribute* attribute, PixelFormat pixelFormat, u32 tilingMode, u32 aspectRatio, u32 width, u32 height, u32 pitchInPixel); -s32 PS4_SYSV_ABI sceVideoOutAddFlipEvent(Kernel::SceKernelEqueue eq, s32 handle, void* udata); -s32 PS4_SYSV_ABI sceVideoOutAddVblankEvent(Kernel::SceKernelEqueue eq, s32 handle, void* udata); +s32 PS4_SYSV_ABI sceVideoOutAddFlipEvent(Kernel::OrbisKernelEqueue eq, s32 handle, void* udata); +s32 PS4_SYSV_ABI sceVideoOutAddVblankEvent(Kernel::OrbisKernelEqueue eq, s32 handle, void* udata); s32 PS4_SYSV_ABI sceVideoOutRegisterBuffers(s32 handle, s32 startIndex, void* const* addresses, s32 bufferNum, const BufferAttribute* attribute); s32 PS4_SYSV_ABI sceVideoOutGetBufferLabelAddress(s32 handle, uintptr_t* label_addr); @@ -133,13 +133,13 @@ s32 PS4_SYSV_ABI sceVideoOutGetResolutionStatus(s32 handle, SceVideoOutResolutio s32 PS4_SYSV_ABI sceVideoOutOpen(Libraries::UserService::OrbisUserServiceUserId userId, s32 busType, s32 index, const void* param); s32 PS4_SYSV_ABI sceVideoOutClose(s32 handle); -s32 PS4_SYSV_ABI sceVideoOutGetEventId(const Kernel::SceKernelEvent* ev); -s32 PS4_SYSV_ABI sceVideoOutGetEventData(const Kernel::SceKernelEvent* ev, s64* data); +s32 PS4_SYSV_ABI sceVideoOutGetEventId(const Kernel::OrbisKernelEvent* ev); +s32 PS4_SYSV_ABI sceVideoOutGetEventData(const Kernel::OrbisKernelEvent* ev, s64* data); s32 PS4_SYSV_ABI sceVideoOutColorSettingsSetGamma(SceVideoOutColorSettings* settings, float gamma); s32 PS4_SYSV_ABI sceVideoOutAdjustColor(s32 handle, const SceVideoOutColorSettings* settings); // Internal system functions -s32 sceVideoOutSubmitEopFlip(s32 handle, u32 buf_id, u32 mode, u32 arg, void** unk); +s32 sceVideoOutSubmitEopFlip(s32 handle, u32 buf_id, u32 mode, s64 flip_arg, void** unk); void RegisterLib(Core::Loader::SymbolsResolver* sym); diff --git a/src/core/signals.cpp b/src/core/signals.cpp index 8df4edea8..70b431d39 100644 --- a/src/core/signals.cpp +++ b/src/core/signals.cpp @@ -5,6 +5,7 @@ #include "common/assert.h" #include "common/decoder.h" #include "common/signal_context.h" +#include "core/libraries/kernel/threads/exception.h" #include "core/signals.h" #ifdef _WIN32 @@ -17,6 +18,13 @@ #endif #endif +#ifndef _WIN32 +namespace Libraries::Kernel { +void SigactionHandler(int native_signum, siginfo_t* inf, ucontext_t* raw_context); +extern std::array Handlers; +} // namespace Libraries::Kernel +#endif + namespace Core { #if defined(_WIN32) @@ -66,7 +74,7 @@ static std::string DisassembleInstruction(void* code_address) { return buffer; } -static void SignalHandler(int sig, siginfo_t* info, void* raw_context) { +void SignalHandler(int sig, siginfo_t* info, void* raw_context) { const auto* signals = Signals::Instance(); auto* code_address = Common::GetRip(raw_context); @@ -76,6 +84,13 @@ static void SignalHandler(int sig, siginfo_t* info, void* raw_context) { case SIGBUS: { const bool is_write = Common::IsWriteError(raw_context); if (!signals->DispatchAccessViolation(raw_context, info->si_addr)) { + // If the guest has installed a custom signal handler, and the access violation didn't + // come from HLE memory tracking, pass the signal on + if (Libraries::Kernel::Handlers[sig]) { + Libraries::Kernel::SigactionHandler(sig, info, + reinterpret_cast(raw_context)); + return; + } UNREACHABLE_MSG("Unhandled access violation at code address {}: {} address {}", fmt::ptr(code_address), is_write ? "Write to" : "Read from", fmt::ptr(info->si_addr)); @@ -84,17 +99,23 @@ static void SignalHandler(int sig, siginfo_t* info, void* raw_context) { } case SIGILL: if (!signals->DispatchIllegalInstruction(raw_context)) { + if (Libraries::Kernel::Handlers[sig]) { + Libraries::Kernel::SigactionHandler(sig, info, + reinterpret_cast(raw_context)); + return; + } UNREACHABLE_MSG("Unhandled illegal instruction at code address {}: {}", fmt::ptr(code_address), DisassembleInstruction(code_address)); } break; - case SIGUSR1: { // Sleep thread until signal is received - sigset_t sigset; - sigemptyset(&sigset); - sigaddset(&sigset, SIGUSR1); - sigwait(&sigset, &sig); - } break; default: + if (sig == SIGSLEEP) { + // Sleep thread until signal is received again + sigset_t sigset; + sigemptyset(&sigset); + sigaddset(&sigset, SIGSLEEP); + sigwait(&sigset, &sig); + } break; } } @@ -116,7 +137,7 @@ SignalDispatch::SignalDispatch() { "Failed to register access violation signal handler."); ASSERT_MSG(sigaction(SIGILL, &action, nullptr) == 0, "Failed to register illegal instruction signal handler."); - ASSERT_MSG(sigaction(SIGUSR1, &action, nullptr) == 0, + ASSERT_MSG(sigaction(SIGSLEEP, &action, nullptr) == 0, "Failed to register sleep signal handler."); #endif } diff --git a/src/core/signals.h b/src/core/signals.h index 0409b73ae..90801debb 100644 --- a/src/core/signals.h +++ b/src/core/signals.h @@ -1,17 +1,29 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include +#include #include "common/singleton.h" #include "common/types.h" +#ifdef _WIN32 +#define SIGSLEEP -1 +#elif defined(__APPLE__) +#define SIGSLEEP SIGVTALRM +#else +#define SIGSLEEP SIGRTMAX +#endif namespace Core { using AccessViolationHandler = bool (*)(void* context, void* fault_address); using IllegalInstructionHandler = bool (*)(void* context); +#ifndef _WIN32 +void SignalHandler(int sig, siginfo_t* info, void* raw_context); +#endif + /// Receives OS signals and dispatches to the appropriate handlers. class SignalDispatch { public: diff --git a/src/core/tls.h b/src/core/tls.h index 04f694489..27de518ea 100644 --- a/src/core/tls.h +++ b/src/core/tls.h @@ -60,9 +60,9 @@ void ClearStack() { template ReturnType ExecuteGuest(PS4_SYSV_ABI ReturnType (*func)(FuncArgs...), CallArgs&&... args) { EnsureThreadInitialized(); - // clear stack to avoid trash from EnsureThreadInitialized (skip on fiber stacks) + // clear stack to avoid trash from EnsureThreadInitialized auto* tcb = GetTcbBase(); - if (tcb == nullptr || tcb->tcb_fiber == nullptr) { + if (tcb != nullptr && tcb->tcb_fiber == nullptr) { ClearStack<12_KB>(); } return func(std::forward(args)...); diff --git a/src/emulator.cpp b/src/emulator.cpp index 87ce82326..27def3565 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -239,6 +239,7 @@ void Emulator::Run(std::filesystem::path file, std::vector args, LOG_INFO(Config, "Game-specific config exists: {}", has_game_config); LOG_INFO(Config, "General LogType: {}", Config::getLogType()); + LOG_INFO(Config, "General isIdenticalLogGrouped: {}", Config::groupIdenticalLogs()); LOG_INFO(Config, "General isNeo: {}", Config::isNeoModeConsole()); LOG_INFO(Config, "General isDevKit: {}", Config::isDevKitConsole()); LOG_INFO(Config, "General isConnectedToNetwork: {}", Config::getIsConnectedToNetwork()); @@ -340,11 +341,8 @@ void Emulator::Run(std::filesystem::path file, std::vector args, g_window = window.get(); - const auto& mount_data_dir = Common::FS::GetUserPath(Common::FS::PathType::GameDataDir) / id; - if (!std::filesystem::exists(mount_data_dir)) { - std::filesystem::create_directory(mount_data_dir); - } - mnt->Mount(mount_data_dir, "/data"); // should just exist, manually create with game serial + const auto& mount_data_dir = Common::FS::GetUserPath(Common::FS::PathType::GameDataDir); + mnt->Mount(mount_data_dir, "/data"); // Mounting temp folders const auto& mount_temp_dir = Common::FS::GetUserPath(Common::FS::PathType::TempDataDir) / id; diff --git a/src/video_core/texture_cache/image.cpp b/src/video_core/texture_cache/image.cpp index cce0cd281..418641bc3 100644 --- a/src/video_core/texture_cache/image.cpp +++ b/src/video_core/texture_cache/image.cpp @@ -462,50 +462,134 @@ static std::pair SanitizeCopyLayers(const ImageInfo& src_info, const I void Image::CopyImage(Image& src_image) { const auto& src_info = src_image.info; const u32 num_mips = std::min(src_info.resources.levels, info.resources.levels); - ASSERT(src_info.resources.layers == info.resources.layers || num_mips == 1); + + // Check format compatibility + if (src_info.pixel_format != info.pixel_format) { + LOG_DEBUG(Render_Vulkan, + "Copy between different formats: src={}, dst={}. Color may be incorrect.", + vk::to_string(src_info.pixel_format), vk::to_string(info.pixel_format)); + } const u32 width = src_info.size.width; const u32 height = src_info.size.height; - const u32 depth = + const u32 base_depth = info.type == AmdGpu::ImageType::Color3D ? info.size.depth : src_info.size.depth; + auto [test_src_layers, test_dst_layers] = SanitizeCopyLayers(src_info, info, base_depth); + + ASSERT(test_src_layers == test_dst_layers || num_mips == 1 || + (ConvertImageType(src_info.type) != ConvertImageType(info.type) && + (test_src_layers == 1 || test_dst_layers == 1))); + SetBackingSamples(info.num_samples, false); src_image.SetBackingSamples(src_info.num_samples); boost::container::small_vector image_copies; + + const bool src_is_2d = ConvertImageType(src_info.type) == vk::ImageType::e2D; + const bool src_is_3d = ConvertImageType(src_info.type) == vk::ImageType::e3D; + const bool dst_is_2d = ConvertImageType(info.type) == vk::ImageType::e2D; + const bool dst_is_3d = ConvertImageType(info.type) == vk::ImageType::e3D; + + const bool is_2d_to_3d = src_is_2d && dst_is_3d; + const bool is_3d_to_2d = src_is_3d && dst_is_2d; + const bool is_same_type = !is_2d_to_3d && !is_3d_to_2d; + + // Determine aspect mask - exclude stencil + vk::ImageAspectFlags aspect = vk::ImageAspectFlagBits::eColor; + + // For depth/stencil images, only copy the depth aspect (skip stencil) + if (src_image.aspect_mask & vk::ImageAspectFlagBits::eDepth) { + aspect = vk::ImageAspectFlagBits::eDepth; + } + for (u32 mip = 0; mip < num_mips; ++mip) { const auto mip_w = std::max(width >> mip, 1u); const auto mip_h = std::max(height >> mip, 1u); - const auto mip_d = std::max(depth >> mip, 1u); - const auto [src_layers, dst_layers] = SanitizeCopyLayers(src_info, info, mip_d); + const auto mip_d = std::max(base_depth >> mip, 1u); - image_copies.emplace_back(vk::ImageCopy{ - .srcSubresource{ - .aspectMask = src_image.aspect_mask & ~vk::ImageAspectFlagBits::eStencil, - .mipLevel = mip, - .baseArrayLayer = 0, - .layerCount = src_layers, - }, - .dstSubresource{ - .aspectMask = aspect_mask & ~vk::ImageAspectFlagBits::eStencil, - .mipLevel = mip, - .baseArrayLayer = 0, - .layerCount = dst_layers, - }, - .extent = {mip_w, mip_h, mip_d}, - }); + auto [src_layers, dst_layers] = SanitizeCopyLayers(src_info, info, mip_d); + + if (is_same_type) { + u32 copy_layers = std::min(src_layers, dst_layers); + + if (src_is_3d) + src_layers = 1; + if (dst_is_3d) + dst_layers = 1; + + vk::ImageCopy copy_region = { + .srcSubresource{ + .aspectMask = aspect, + .mipLevel = mip, + .baseArrayLayer = 0, + .layerCount = copy_layers, + }, + .dstSubresource{ + .aspectMask = aspect, + .mipLevel = mip, + .baseArrayLayer = 0, + .layerCount = copy_layers, + }, + .extent = vk::Extent3D(mip_w, mip_h, mip_d), + }; + image_copies.push_back(copy_region); + } else if (is_2d_to_3d) { + vk::ImageCopy copy_region = { + .srcSubresource{ + .aspectMask = aspect, + .mipLevel = mip, + .baseArrayLayer = 0, + .layerCount = src_layers, + }, + .dstSubresource{ + .aspectMask = aspect, + .mipLevel = mip, + .baseArrayLayer = 0, + .layerCount = 1, + }, + .extent = vk::Extent3D(mip_w, mip_h, src_layers), + }; + image_copies.push_back(copy_region); + } else if (is_3d_to_2d) { + vk::ImageCopy copy_region = { + .srcSubresource{ + .aspectMask = aspect, + .mipLevel = mip, + .baseArrayLayer = 0, + .layerCount = 1, + }, + .dstSubresource{ + .aspectMask = aspect, + .mipLevel = mip, + .baseArrayLayer = 0, + .layerCount = dst_layers, + }, + .extent = vk::Extent3D(mip_w, mip_h, dst_layers), + }; + image_copies.push_back(copy_region); + } } scheduler->EndRendering(); + + // Remove the pipeline stage flags - they don't belong here src_image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {}); + Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {}); auto cmdbuf = scheduler->CommandBuffer(); - cmdbuf.copyImage(src_image.GetImage(), src_image.backing->state.layout, GetImage(), - backing->state.layout, image_copies); - Transit(vk::ImageLayout::eGeneral, - vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {}); + if (!image_copies.empty()) { + cmdbuf.copyImage(src_image.GetImage(), vk::ImageLayout::eTransferSrcOptimal, GetImage(), + vk::ImageLayout::eTransferDstOptimal, image_copies); + } + + // Remove pipeline stage flags here too + src_image.Transit(vk::ImageLayout::eShaderReadOnlyOptimal, vk::AccessFlagBits2::eShaderRead, + {}); + + Transit(vk::ImageLayout::eShaderReadOnlyOptimal, vk::AccessFlagBits2::eShaderRead, {}); } void Image::CopyImageWithBuffer(Image& src_image, vk::Buffer buffer, u64 offset) { diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index 17c7e67b3..a8d846cfc 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -297,6 +297,14 @@ std::tuple TextureCache::ResolveOverlap(const ImageInfo& imag if (image_info.guest_address == cache_image.info.guest_address) { const u32 lhs_block_size = image_info.num_bits * image_info.num_samples; const u32 rhs_block_size = cache_image.info.num_bits * cache_image.info.num_samples; + + if (image_info.pitch != cache_image.info.pitch) { + if (safe_to_delete) { + FreeImage(cache_image_id); + } + return {merged_image_id, -1, -1}; + } + if (image_info.BlockDim() != cache_image.info.BlockDim() || lhs_block_size != rhs_block_size) { // Very likely this kind of overlap is caused by allocation from a pool. @@ -346,6 +354,111 @@ std::tuple TextureCache::ResolveOverlap(const ImageInfo& imag return {merged_image_id, -1, -1}; } + // Enhanced debug logging for unreachable case + // Calculate expected size based on format and dimensions + u64 expected_size = + (static_cast(image_info.size.width) * static_cast(image_info.size.height) * + static_cast(image_info.size.depth) * static_cast(image_info.num_bits) / 8); + LOG_ERROR(Render_Vulkan, + "Unresolvable image overlap with equal memory address:\n" + "=== OLD IMAGE (cached) ===\n" + " Address: {:#x}\n" + " Size: {:#x} bytes\n" + " Format: {}\n" + " Type: {}\n" + " Width: {}\n" + " Height: {}\n" + " Depth: {}\n" + " Pitch: {}\n" + " Mip levels: {}\n" + " Array layers: {}\n" + " Samples: {}\n" + " Tile mode: {:#x}\n" + " Block size: {} bits\n" + " Is block-comp: {}\n" + " Guest size: {:#x}\n" + " Last accessed: tick {}\n" + " Safe to delete: {}\n" + "\n" + "=== NEW IMAGE (requested) ===\n" + " Address: {:#x}\n" + " Size: {:#x} bytes\n" + " Format: {}\n" + " Type: {}\n" + " Width: {}\n" + " Height: {}\n" + " Depth: {}\n" + " Pitch: {}\n" + " Mip levels: {}\n" + " Array layers: {}\n" + " Samples: {}\n" + " Tile mode: {:#x}\n" + " Block size: {} bits\n" + " Is block-comp: {}\n" + " Guest size: {:#x}\n" + "\n" + "=== COMPARISON ===\n" + " Same format: {}\n" + " Same type: {}\n" + " Same tile mode: {}\n" + " Same block size: {}\n" + " Same BlockDim: {}\n" + " Same pitch: {}\n" + " Old resources <= new: {} (old: {}, new: {})\n" + " Old size <= new size: {}\n" + " Expected size (calc): {} bytes\n" + " Size ratio (new/expected): {:.2f}x\n" + " Size ratio (new/old): {:.2f}x\n" + " Old vs expected diff: {} bytes ({:+.2f}%)\n" + " New vs expected diff: {} bytes ({:+.2f}%)\n" + " Merged image ID: {}\n" + " Binding type: {}\n" + " Current tick: {}\n" + " Age (ticks since last access): {}", + + // Old image details + cache_image.info.guest_address, cache_image.info.guest_size, + vk::to_string(cache_image.info.pixel_format), + static_cast(cache_image.info.type), cache_image.info.size.width, + cache_image.info.size.height, cache_image.info.size.depth, cache_image.info.pitch, + cache_image.info.resources.levels, cache_image.info.resources.layers, + cache_image.info.num_samples, static_cast(cache_image.info.tile_mode), + cache_image.info.num_bits, cache_image.info.props.is_block, + cache_image.info.guest_size, cache_image.tick_accessed_last, safe_to_delete, + + // New image details + image_info.guest_address, image_info.guest_size, + vk::to_string(image_info.pixel_format), static_cast(image_info.type), + image_info.size.width, image_info.size.height, image_info.size.depth, + image_info.pitch, image_info.resources.levels, image_info.resources.layers, + image_info.num_samples, static_cast(image_info.tile_mode), + image_info.num_bits, image_info.props.is_block, image_info.guest_size, + + // Comparison + (image_info.pixel_format == cache_image.info.pixel_format), + (image_info.type == cache_image.info.type), + (image_info.tile_mode == cache_image.info.tile_mode), + (image_info.num_bits == cache_image.info.num_bits), + (image_info.BlockDim() == cache_image.info.BlockDim()), + (image_info.pitch == cache_image.info.pitch), + (cache_image.info.resources <= image_info.resources), + cache_image.info.resources.levels, image_info.resources.levels, + (cache_image.info.guest_size <= image_info.guest_size), expected_size, + + // Size ratios + static_cast(image_info.guest_size) / expected_size, + static_cast(image_info.guest_size) / cache_image.info.guest_size, + + // Difference between actual and expected sizes with percentages + static_cast(cache_image.info.guest_size) - static_cast(expected_size), + (static_cast(cache_image.info.guest_size) / expected_size - 1.0) * 100.0, + + static_cast(image_info.guest_size) - static_cast(expected_size), + (static_cast(image_info.guest_size) / expected_size - 1.0) * 100.0, + + merged_image_id.index, static_cast(binding), scheduler.CurrentTick(), + scheduler.CurrentTick() - cache_image.tick_accessed_last); + UNREACHABLE_MSG("Encountered unresolvable image overlap with equal memory address."); }