diff --git a/core/libs/prx/libScePad/Export.cpp b/core/libs/prx/libScePad/Export.cpp index bc9464c19..0d20fed00 100644 --- a/core/libs/prx/libScePad/Export.cpp +++ b/core/libs/prx/libScePad/Export.cpp @@ -139,13 +139,10 @@ int APS5_VABI scePadOpen_nid_postfix(int userId, int type, int index, const void return PAD_HANDLE; } -int APS5_VABI scePadReadState(int handle, PadData* data); - int APS5_VABI scePadRead_nid_postfix(int handle, PadData* data, int num) { + if (handle != PAD_HANDLE) return PAD_ERROR_INVALID_HANDLE; if (data == nullptr || num <= 0) APS5_INVALID_ARG_EX; - const int result = scePadReadState(handle, data); - if (result != 0) return result; - return 1; + return Pad::Read(data, num); } int APS5_VABI scePadReadState(int handle, PadData* data) { diff --git a/core/libs/prx/libScePad/include/PadState.hpp b/core/libs/prx/libScePad/include/PadState.hpp index c23b02c4d..1d2205fa6 100644 --- a/core/libs/prx/libScePad/include/PadState.hpp +++ b/core/libs/prx/libScePad/include/PadState.hpp @@ -51,6 +51,8 @@ struct PadOutputState { namespace Pad { void Initialize(); PadData ReadState(); +// Drains up to `num` queued states, oldest first; with none queued, writes the current state. Returns the count written. +int Read(PadData* data, int num); void SetVibration(std::uint8_t large, std::uint8_t small); void SetVibrationMode(int mode); void SetLightBar(bool valid, std::uint8_t r, std::uint8_t g, std::uint8_t b); diff --git a/core/libs/prx/libScePad/src/PadState.cpp b/core/libs/prx/libScePad/src/PadState.cpp index ea3e212d4..3da6c4f21 100644 --- a/core/libs/prx/libScePad/src/PadState.cpp +++ b/core/libs/prx/libScePad/src/PadState.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -12,6 +13,14 @@ namespace { PadInputState state; PadOutputState output; std::uint64_t timestamp = 0; + + struct QueuedInput { + PadInputState input; + std::uint64_t timestamp = 0; + }; + // States published since the last scePadRead; the oldest is dropped once 64 are waiting. + constexpr std::size_t kQueueCapacity = 64; + std::deque queue; std::exception_ptr failure; bool initialized = false; @@ -93,15 +102,14 @@ void Pad::Initialize() { if (!initialized) { timestamp = sceKernelGetProcessTime(); lastFuseTime = timestamp; + queue.clear(); } initialized = true; } -PadData Pad::ReadState() { - std::lock_guard lock(stateMutex); - if (failure) std::rethrow_exception(failure); - if (!initialized) throw std::runtime_error("Pad: read before initialization"); - const std::uint64_t now = sceKernelGetProcessTime(); +namespace { +// Converts one input sample; motion fusion integrates up to `now` and touch ids advance per call. +PadData BuildData(const PadInputState& state, std::uint64_t changeTime, std::uint64_t now) { PadData data{}; data.buttons = state.buttons; data.left_stick_x = state.sticks[0]; @@ -123,7 +131,7 @@ PadData Pad::ReadState() { orientation = Quat{}; biasIntegral[0] = biasIntegral[1] = biasIntegral[2] = 0.0f; } - lastFuseTime = now; + lastFuseTime = std::max(lastFuseTime, now); data.orientation_x = orientation.x; data.orientation_y = orientation.y; data.orientation_z = orientation.z; @@ -138,7 +146,7 @@ PadData Pad::ReadState() { data.connected = true; data.connected_count = 1; - data.timestamp = timestamp; + data.timestamp = changeTime; std::uint8_t touchNum = 0; for (int i = 0; i < 2; ++i) { @@ -163,6 +171,32 @@ PadData Pad::ReadState() { data.touch_data_touch_num = touchNum; return data; } +} + +PadData Pad::ReadState() { + std::lock_guard lock(stateMutex); + if (failure) std::rethrow_exception(failure); + if (!initialized) throw std::runtime_error("Pad: read before initialization"); + return BuildData(state, timestamp, sceKernelGetProcessTime()); +} + +int Pad::Read(PadData* data, int num) { + if (data == nullptr || num <= 0) throw std::invalid_argument("Pad: invalid read buffer"); + std::lock_guard lock(stateMutex); + if (failure) std::rethrow_exception(failure); + if (!initialized) throw std::runtime_error("Pad: read before initialization"); + if (queue.empty()) { + data[0] = BuildData(state, timestamp, sceKernelGetProcessTime()); + return 1; + } + int count = 0; + while (count < num && !queue.empty()) { + const QueuedInput& entry = queue.front(); + data[count++] = BuildData(entry.input, entry.timestamp, entry.timestamp); + queue.pop_front(); + } + return count; +} void Pad::SetVibration(std::uint8_t large, std::uint8_t small) { std::lock_guard lock(stateMutex); @@ -280,6 +314,8 @@ extern "C" void PadPublishInput_nid_postfix(const PadInputState& input) { state.touch == input.touch && state.deviceKind == input.deviceKind) return; state = input; timestamp = sceKernelGetProcessTime(); + if (queue.size() == kQueueCapacity) queue.pop_front(); + queue.push_back({input, timestamp}); } extern "C" void PadReportInputFailure_nid_postfix(std::exception_ptr error) { diff --git a/core/libs/tests/GuestPad.cpp b/core/libs/tests/GuestPad.cpp index 866c44ba7..68c23fd6e 100644 --- a/core/libs/tests/GuestPad.cpp +++ b/core/libs/tests/GuestPad.cpp @@ -59,6 +59,22 @@ static void CheckTouchContact() { Pad::ReadState(); } +static void CheckReadQueue(int handle) { + PadData drained[64]{}; + scePadRead_nid_postfix(handle, drained, 64); + PadInputState pressed; + pressed.buttons = static_cast(Pad::PadButton::Cross); + PadPublishInput_nid_postfix(pressed); + PadPublishInput_nid_postfix(PadInputState{}); + PadData data[4]{}; + Require(scePadRead_nid_postfix(handle, data, 4) == 2); + Require((data[0].buttons & pressed.buttons) != 0); + Require((data[1].buttons & pressed.buttons) == 0); + Require(data[0].timestamp <= data[1].timestamp); + Require(scePadRead_nid_postfix(handle, data, 4) == 1); + Require(data[0].buttons == 0); +} + static void CheckReadStateHandle(int handle) { PadData data{}; Require(scePadReadState(0, &data) == PAD_ERROR_INVALID_HANDLE); @@ -81,6 +97,7 @@ int main() { Require(scePadGetHandle(user, 2, 0) == handle); CheckTiltCorrection(handle); CheckTouchContact(); + CheckReadQueue(handle); CheckReadStateHandle(handle); Require(scePadGetHandle(0xff, 16, 0) == handle); Require(scePadGetHandle(user, 16, 0) == noHandle); diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 7154cc035..57ce5607d 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -233,4 +233,3 @@ Throughout the project, every function at every stage either **does exactly what - [sceFontOpenFontSet](../../core/libs/prx/libSceFont/src/SystemFont.cpp) (libSceFont) opens only the primary file of a system font set. Characters the console takes from the set's other files (Hangul, Thai and Arabic in Japanese and Chinese sets, Arabic in European sets) are reported as unsupported glyphs. The console file names are those of the PS4 firmware; the names of the PS5-only JG2 sets (`0x1A......`) are unknown, so they use the SSTJpPro files. When a console file is missing, an openly licensed substitute (Noto Sans, Noto Sans Mono, Noto Sans Thai, Noto Sans CJK) is loaded instead. It is not the system font: its metrics differ, so text width, line height and wrapping differ from the console. System font faces are loaded by FreeType in host memory, not in the font memory the title passes to the library: FreeType needs about 1 MiB per CJK face, which the title's font memory is not sized for. - [Font writing lines](../../core/libs/prx/libSceFont/src/WritingLine.cpp) (libSceFont) - `sceFontCreateWritingLine`, `sceFontWritingLineWritesOrder`, `sceFontWritingLineRefersRenderStep`, `sceFontWritingLineGetRenderMetrics` and `sceFontWritingLineClear` follow [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/main/src/libs/libFont.cpp): runs are placed left to right, each at the line's advance so far, with no spacing or adjustment. Only horizontal and left-to-right lines, writing attribute 0 and non-null run metrics are modelled; other values, the metrics of an empty line and unknown or null handles throw, since no reference gives their result or error code. `sceFontWritingLineGetOrderingSpace` still throws (KytyPS5's values are placeholders). Lines are allocated on the host, not in the font memory - [sceImeKeyboardGetResourceId](../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) reports that no USB keyboard is connected (`0x80bc0004`, as [shadPS4](https://github.com/shadps4-emu/shadPS4) does) because IME keyboard events are not delivered to the handler, although [libSceKeyboard](../../core/libs/prx/libSceKeyboard/src/keyboard_impl.cpp) exposes the host keyboard -- [scePadRead](../../core/libs/prx/libScePad/Export.cpp) (libScePad) keeps no queue of past states: each call writes the current state to the first entry and returns 1 whatever `num` is, so input that changes and changes back between two calls is not reported