Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/libs/prx/libSceMouse/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ if(BUILD_TESTING)
${CMAKE_CURRENT_SOURCE_DIR}/../libSceVideoOut/src/MouseInput.cpp
)
target_include_directories(mouse_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_SDL2_INCLUDE_DIR})
target_link_libraries(mouse_tests PRIVATE libSceMouse libkernel libc)
target_link_libraries(mouse_tests PRIVATE libSceMouse libkernel libc SDL2-static)
configure_windows_unwind(mouse_tests)
add_test(NAME mouse_input COMMAND mouse_tests)
if(WIN32)
Expand Down
5 changes: 5 additions & 0 deletions core/libs/prx/libSceMouse/Export.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,9 @@ int APS5_VABI sceMouseResetAbsolutePosition() {
NotImplemented_nid_no_patch(__func__);
return 0;
}

bool MouseIsOpen_nid_postfix() {
return Mouse::IsOpen();
}

}
2 changes: 2 additions & 0 deletions core/libs/prx/libSceMouse/include/MouseState.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ struct MouseInputEvent {
};

namespace Mouse {
bool IsOpen();
int Initialize();
int Open(int userId, std::int32_t type, std::int32_t index, const MouseOpenParam* param);
int Close(std::int32_t handle);
Expand All @@ -24,5 +25,6 @@ void Publish(const MouseInputEvent& event);
}

extern "C" void MousePublishInput_nid_postfix(const MouseInputEvent& event);
extern "C" bool MouseIsOpen_nid_postfix();

#endif
5 changes: 5 additions & 0 deletions core/libs/prx/libSceMouse/src/mouse_impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ void enqueue(const MouseData& data) {

namespace Mouse {

bool IsOpen() {
std::lock_guard lock(mouseMutex);
return opened;
}

int Initialize() {
std::lock_guard lock(mouseMutex);
initialized = true;
Expand Down
14 changes: 14 additions & 0 deletions core/libs/prx/libSceVideoOut/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,20 @@ configure_windows_unwind(libSceVideoOut)
foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static)
string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}")
endforeach()
add_test_executable(video_out_mouse_tests EXCLUDE_FROM_ALL tests/MouseInput.cpp)
target_include_directories(video_out_mouse_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_SDL2_INCLUDE_DIR})
target_link_libraries(video_out_mouse_tests PRIVATE libSceVideoOut libSceMouse libc SDL2-static)
set_target_properties(video_out_mouse_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(video_out_mouse_tests)
if(BUILD_TESTING)
set_target_properties(video_out_mouse_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
add_test(NAME video_out_mouse COMMAND video_out_mouse_tests)
if(WIN32)
set_tests_properties(video_out_mouse PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceVideoOut>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceMouse>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()

add_test_executable(video_out_flip_tests EXCLUDE_FROM_ALL tests/Flip.cpp)
target_include_directories(video_out_flip_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_SDL2_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/core/shader/recompiler ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR})
target_link_libraries(video_out_flip_tests PRIVATE libSceVideoOut libSceAgcDriver libkernel libc)
Expand Down
11 changes: 11 additions & 0 deletions core/libs/prx/libSceVideoOut/include/MouseInput.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,24 @@
#define CORE_LIBS_PRX_LIBSCEVIDEOOUT_MOUSEINPUT_HPP

#include "SDL_events.h"
#include <cstdint>

class MouseInput {
public:
void HandleEvent(const SDL_Event& event, unsigned windowId);
void SetPresentation(std::uint32_t sourceWidth, std::uint32_t sourceHeight, std::uint32_t presentedWidth, std::uint32_t presentedHeight);
bool CaptureWanted(bool allowed) const;
void UpdateCapture(bool allowed);

private:
std::int32_t scaled(std::int32_t delta, double scale, double& remainder);

bool focused = true;
bool captured = false;
double scaleX = 1.0;
double scaleY = 1.0;
double remainderX = 0.0;
double remainderY = 0.0;
};

#endif
1 change: 1 addition & 0 deletions core/libs/prx/libSceVideoOut/include/PadInput.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class PadInput {
~PadInput();
void HandleEvent(const SDL_Event& event, DisplayWindow& window);
void Update();
bool MouseModeEnabled() const { return mouseEnabled; }

private:
explicit PadInput(Pad::InputConfiguration configuration);
Expand Down
50 changes: 48 additions & 2 deletions core/libs/prx/libSceVideoOut/src/MouseInput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@
#include "prx/libSceMouse/include/MouseState.hpp"

#include "SDL_mouse.h"
#include "SDL_video.h"
#include <cmath>
#include <cstdio>

void MouseInput::HandleEvent(const SDL_Event& event, unsigned windowId) {
MouseInputEvent input{};
switch (event.type) {
case SDL_MOUSEMOTION:
if (event.motion.windowID != windowId || !focused) return;
input.x = event.motion.xrel;
input.y = event.motion.yrel;
input.x = scaled(event.motion.xrel, scaleX, remainderX);
input.y = scaled(event.motion.yrel, scaleY, remainderY);
break;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
Expand Down Expand Up @@ -53,3 +56,46 @@ void MouseInput::HandleEvent(const SDL_Event& event, unsigned windowId) {
}
MousePublishInput_nid_postfix(input);
}

void MouseInput::SetPresentation(std::uint32_t sourceWidth, std::uint32_t sourceHeight, std::uint32_t presentedWidth, std::uint32_t presentedHeight) {
if (sourceWidth == 0 || sourceHeight == 0 || presentedWidth == 0 || presentedHeight == 0) return;
const double x = static_cast<double>(sourceWidth) / static_cast<double>(presentedWidth);
const double y = static_cast<double>(sourceHeight) / static_cast<double>(presentedHeight);
if (x != scaleX || y != scaleY) {
std::fprintf(stderr, "[mouse] video driver %s: guest %ux%u presented at %ux%u, delta scale %.4f x %.4f\n", SDL_GetCurrentVideoDriver() ? SDL_GetCurrentVideoDriver() : "?", sourceWidth, sourceHeight, presentedWidth, presentedHeight, x, y);
scaleX = x;
scaleY = y;
remainderX = 0.0;
remainderY = 0.0;
}
}

std::int32_t MouseInput::scaled(std::int32_t delta, double scale, double& remainder) {
const double exact = static_cast<double>(delta) * scale + remainder;
const double whole = std::trunc(exact);
remainder = exact - whole;
return static_cast<std::int32_t>(whole);
}

bool MouseInput::CaptureWanted(bool allowed) const {
return allowed && focused && MouseIsOpen_nid_postfix();
}

void MouseInput::UpdateCapture(bool allowed) {
const bool wanted = CaptureWanted(allowed);
if (wanted == captured && (SDL_GetRelativeMouseMode() == SDL_TRUE) == wanted) return;
if (SDL_SetRelativeMouseMode(wanted ? SDL_TRUE : SDL_FALSE) != 0) {
static bool warned = false;
if (!warned) {
warned = true;
std::fprintf(stderr, "[mouse] relative mouse mode unavailable: %s\n", SDL_GetError());
}
return;
}
int ignoredX = 0;
int ignoredY = 0;
SDL_GetRelativeMouseState(&ignoredX, &ignoredY);
remainderX = 0.0;
remainderY = 0.0;
captured = wanted;
}
9 changes: 9 additions & 0 deletions core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "SDL_vulkan.h"
#include "prx/libSceVideoOut/include/PadInput.hpp"
#include "prx/libSceVideoOut/include/MouseInput.hpp"
#include "prx/libSceAgcDriver/Execution/include/AspectFit.hpp"
#include "prx/libSceVideoOut/include/KeyboardInput.hpp"
#include "prx/libScePad/include/PadState.hpp"
#include "prx/libkernel/Equeue/Equeue.hpp"
Expand Down Expand Up @@ -619,11 +620,19 @@ void VideoOutDriver::presentLoop(std::stop_token token, std::promise<void>& star
}
padInput.Update();
updateDisplayRefreshRate();
mouseInput.UpdateCapture(!padInput.MouseModeEnabled());
if (current) {
require(current->timing != nullptr, "missing presentation timing");
const auto dequeued = AgcDriver::FrameTiming::Clock::now();
current->timing->Add(current->timing->Get("VideoOut", "queue"), dequeued - current->queuedAt);
processFlip(*current);
int windowWidth = 0;
int windowHeight = 0;
if (window.Handle() != nullptr) SDL_GetWindowSize(window.Handle(), &windowWidth, &windowHeight);
if (windowWidth > 0 && windowHeight > 0) {
const auto presented = AgcDriver::ComputeContainSize_nid_postfix(current->width, current->height, static_cast<std::uint32_t>(windowWidth), static_cast<std::uint32_t>(windowHeight), true);
mouseInput.SetPresentation(current->width, current->height, presented.width, presented.height);
}
const auto finished = AgcDriver::FrameTiming::Clock::now();
AgcDriver::FrameTiming::Clock::duration interval{};
{
Expand Down
120 changes: 120 additions & 0 deletions core/libs/prx/libSceVideoOut/tests/MouseInput.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#include "prx/libSceVideoOut/include/MouseInput.hpp"
#include "prx/libSceMouse/include/mouse_structs.h"
#include "prx/libc/include/general/VabiMacros.hpp"
#include "SDL.h"
#include "SDL_events.h"
#include "SDL_mouse.h"
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#undef main

extern "C" {
int APS5_VABI sceMouseInit();
int APS5_VABI sceMouseOpen(int userId, std::int32_t type, std::int32_t index, const void* param);
int APS5_VABI sceMouseRead(std::int32_t handle, MouseData* data, std::int32_t num);
int APS5_VABI sceMouseClose(std::int32_t handle);
}

namespace {

void Require(bool condition, const char* reason) {
if (!condition) {
std::fprintf(stderr, "mouse input: %s\n", reason);
std::abort();
}
}

SDL_Event Motion(unsigned windowId, int xrel, int yrel) {
SDL_Event event{};
event.type = SDL_MOUSEMOTION;
event.motion.windowID = windowId;
event.motion.xrel = xrel;
event.motion.yrel = yrel;
return event;
}

SDL_Event Window(unsigned windowId, std::uint8_t kind) {
SDL_Event event{};
event.type = SDL_WINDOWEVENT;
event.window.windowID = windowId;
event.window.event = kind;
return event;
}
void CaptureDecision(MouseInput& input, std::int32_t handle) {
Require(input.CaptureWanted(true), "a focused window with an open mouse does not want the capture");
Require(!input.CaptureWanted(false), "the pad's mouse-to-stick toggle does not release the capture");
input.HandleEvent(Window(3, SDL_WINDOWEVENT_FOCUS_LOST), 3);
Require(!input.CaptureWanted(true), "losing focus does not release the capture");
input.HandleEvent(Window(3, SDL_WINDOWEVENT_FOCUS_GAINED), 3);
Require(input.CaptureWanted(true), "regaining focus does not restore the capture");
input.HandleEvent(Window(4, SDL_WINDOWEVENT_FOCUS_LOST), 3);
Require(input.CaptureWanted(true), "another window's focus loss released the capture");
Require(sceMouseClose(handle) == 0, "close failed");
Require(!input.CaptureWanted(true), "a closed mouse keeps the capture");
Require(sceMouseOpen(255, 0, 0, nullptr) >= 0, "reopen failed");
Require(input.CaptureWanted(true), "a reopened mouse does not want the capture");
input.HandleEvent(Window(3, SDL_WINDOWEVENT_CLOSE), 3);
Require(!input.CaptureWanted(true), "a closed window keeps the capture");
input.HandleEvent(Window(3, SDL_WINDOWEVENT_FOCUS_GAINED), 3);
}
void CaptureApplied(MouseInput& input) {
SDL_SetHint("SDL_VIDEODRIVER", "dummy");
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
std::fprintf(stderr, "mouse input: SDL video unavailable (%s): capture application not checked\n", SDL_GetError());
return;
}
input.UpdateCapture(true);
const bool applied = SDL_GetRelativeMouseMode() == SDL_TRUE;
input.UpdateCapture(false);
Require(SDL_GetRelativeMouseMode() == SDL_FALSE, "the capture was not released");
std::printf("mouse input: SDL %s relative mode under the %s driver\n", applied ? "applied" : "refused", SDL_GetCurrentVideoDriver());
SDL_Quit();
}
void Sum(std::int32_t handle, std::int32_t& x, std::int32_t& y) {
MouseData data[MOUSE_MAX_DATA_NUM]{};
const int count = sceMouseRead(handle, data, MOUSE_MAX_DATA_NUM);
Require(count >= 0, "read failed");
x = 0;
y = 0;
for (int index = 0; index < count; ++index) {
x += data[index].x_axis;
y += data[index].y_axis;
}
}

}

int main() {
Require(sceMouseInit() == 0, "init failed");
const std::int32_t handle = sceMouseOpen(255, 0, 0, nullptr);
Require(handle >= 0, "open failed");
MouseInput input;
std::int32_t x = 0;
std::int32_t y = 0;

input.HandleEvent(Motion(3, 10, -7), 3);
input.HandleEvent(Motion(4, 100, 100), 3);
Sum(handle, x, y);
Require(x == 10 && y == -7, "unscaled deltas are not passed through or another window's motion was not ignored");

input.SetPresentation(1920, 1080, 1843, 1037);
for (int step = 0; step < 5; ++step) input.HandleEvent(Motion(3, 10, -7), 3);
Sum(handle, x, y);
Require(x == 52 && y == -36, "deltas are not scaled by the presented image with sub-pixel carry");

input.SetPresentation(1920, 1080, 960, 540);
input.HandleEvent(Motion(3, 1, 1), 3);
input.HandleEvent(Motion(3, 1, 1), 3);
Sum(handle, x, y);
Require(x == 4 && y == 4, "a half-size presentation does not double the deltas");

input.SetPresentation(0, 0, 0, 0);
input.HandleEvent(Motion(3, 3, 3), 3);
Sum(handle, x, y);
Require(x == 6 && y == 6, "an empty presentation changed the scale");
CaptureDecision(input, handle);
CaptureApplied(input);
std::puts("mouse input tests passed");
return 0;
}
1 change: 1 addition & 0 deletions docs/dev/debt/libSceVideoOut.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@
### Functional

- [Overlay output bus](../../../core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp) (libSceVideoOut) - flips on a port opened with `SCE_VIDEO_OUT_BUS_TYPE_OVERLAY` complete and signal their events like main-bus flips, but their buffers are not shown: the overlay is not composited over the main output. Presenting them as whole frames made the window alternate between the two buses
- [Mouse capture and delta scaling](../../../core/libs/prx/libSceVideoOut/src/MouseInput.cpp) (libSceVideoOut) - the console's mouse reports relative motion only, so a host cursor can never coincide with the title's cursor; while a title has a mouse open and the window is focused the host cursor is hidden and captured (SDL relative mode, released on focus loss or while the pad's mouse-to-stick toggle is on). Motion deltas are scaled from the presented image's size in window units to the title's display resolution with sub-pixel carry; the console's deltas are counts, so a title that applies its own sensitivity moves its cursor that much faster than the host pointer would have. The scale takes SDL's relative motion in logical window units, the units of `SDL_GetWindowSize`; under Windows DPI scaling SDL may report it in physical pixels, which is unverified (no Windows run yet), and a drawable larger than the window does not change the scale.
2 changes: 2 additions & 0 deletions docs/user/INPUT_MAPPING.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ Supported input sources are:
- `MOUSE:Left`, `MOUSE:Middle`, `MOUSE:Right`, `MOUSE:X1`, or `MOUSE:X2`.
- `WHEEL:Up` or `WHEEL:Down` for pad buttons.

While a title has a mouse open (`sceMouseOpen`) and the window is focused, AnyPS5 hides and captures the host cursor, so the title's own cursor is the only one; the capture ends when the window loses focus or when the mouse-to-stick toggle (`ToggleMouse`) is on. Mouse movement reaches the title scaled from the window's presented image to the title's display resolution, so the title's cursor moves as far as the host cursor would.

Game-controller bindings are enabled automatically and are not affected by keyboard or mouse overrides in this file.

Supported actions are `Cross`, `Circle`, `Triangle`, `Square`, `L1`, `R1`, `L2`, `R2`, `L3`, `R3`, `Options`, `Up`, `Right`, `Down`, `Left`, `LeftStickLeft`, `LeftStickRight`, `LeftStickUp`, `LeftStickDown`, `RightStickLeft`, `RightStickRight`, `RightStickUp`, `RightStickDown`, `TouchLeft`, `TouchRight`, `ToggleMouse`, and `ToggleFullscreen`.
Expand Down
Loading