From d78ba6fc597fef3ba2a932d32984b856eaf13bc0 Mon Sep 17 00:00:00 2001 From: Sergey Stepanov Date: Wed, 28 Feb 2024 23:26:21 +0300 Subject: [PATCH] Scale cursor speed to native resolution Try to scale pointer coordinate deltas from a display with one resolution in the browser to a display (frame size) of the emulator. When you stream a display which size is different from a display on which you use mouse, the mouse movement (DPI) should be recalculated accordingly. Used algorithm takes coordinates from the virtual display (cursor over the browser video element) and scales them to the ratio of virtual/real display sizes. Since the scale ratio value is a floating point number and we can't use subpixel coordinates on the server side, we need to take into account accumulated floating point errors when rounding coordinates to the destination screen (Libretro framebuffer). See an example: https://codepen.io/sergystepanov/full/MWRWEMY --- pkg/worker/coordinatorhandlers.go | 14 ++++++------ web/js/stream/stream.js | 36 ++++++++++++++++++++++++++----- 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/pkg/worker/coordinatorhandlers.go b/pkg/worker/coordinatorhandlers.go index 244765d22..27340aef9 100644 --- a/pkg/worker/coordinatorhandlers.go +++ b/pkg/worker/coordinatorhandlers.go @@ -126,12 +126,14 @@ func (c *coordinator) HandleGameStart(rq api.StartGameRequest[com.Uid], w *Worke } } - data, err := api.Wrap(api.Out{T: uint8(api.AppVideoChange), Payload: api.AppVideoInfo{ - W: m.VideoW, - H: m.VideoH, - A: app.AspectRatio(), - S: int(app.Scale()), - }}) + data, err := api.Wrap(api.Out{ + T: uint8(api.AppVideoChange), + Payload: api.AppVideoInfo{ + W: m.VideoW, + H: m.VideoH, + A: app.AspectRatio(), + S: int(app.Scale()), + }}) if err != nil { c.log.Error().Err(err).Msgf("wrap") } diff --git a/web/js/stream/stream.js b/web/js/stream/stream.js index 0a71ff9c2..af2dc0bec 100644 --- a/web/js/stream/stream.js +++ b/web/js/stream/stream.js @@ -179,7 +179,9 @@ const stream = (() => { if (fullscreen && !pointerLocked) { // event.pub(POINTER_LOCK_CHANGE, screen); - await screen.requestPointerLock(); + await screen.requestPointerLock( + // { unadjustedMovement: true,} + ); } screen.onpointerdown = fullscreen ? handlePointerDown : null; @@ -188,8 +190,32 @@ const stream = (() => { // !to flipped }) + let ex = 0, ey = 0; + const scaleCursorPos = (x, y) => { + const horizontal = screen.videoWidth > screen.videoHeight; + + const arW = horizontal ? screen.offsetHeight * state.aspect : screen.offsetHeight; + const arH = horizontal ? screen.offsetHeight : screen.offsetWidth * state.aspect; + const sw = arW / screen.videoWidth; + const sh = arH / screen.videoHeight; + + const rez = { + dx: x / sw + ex, + dy: y / sh + ey + } + + ex = rez.dx % 1; + ey = rez.dy % 1; + + rez.dx -= ex; + rez.dy -= ey; + + return rez; + } + const handlePointerMove = (e) => { - event.pub(MOUSE_MOVED, {dx: e.movementX, dy: e.movementY}); + const delta = scaleCursorPos(e.movementX, e.movementY); + event.pub(MOUSE_MOVED, delta); } event.sub(POINTER_LOCK_CHANGE, (lockedEl) => { @@ -214,9 +240,9 @@ const stream = (() => { state.screen.style['object-fit'] = a.toFixed(6) !== a2.toFixed(6) ? 'fill' : fit state.h = hh state.w = Math.floor(hh * a) - state.screen.setAttribute('width', '' + ww) - state.screen.setAttribute('height', '' + hh) - state.screen.style.aspectRatio = '' + state.aspect + state.screen.setAttribute('width', ww) + state.screen.setAttribute('height', hh) + state.screen.style.aspectRatio = state.aspect }) return {