End mouse motion batches on mouse button events

This commit is contained in:
Cameron Gutman committed 2026-10-22 21:26:18 -05:00
1 parent f1cca2dd6b
commit 421bd74e01
1 file changed
+84 -63
+84 -63
View File
@@ -53,6 +53,9 @@ typedef struct _PACKET_HOLDER {
uint32_t enetPacketFlags; uint32_t enetPacketFlags;
uint8_t channelId; uint8_t channelId;
int relMouseDeltaX;
int relMouseDeltaY;
// The union must be the last member since we abuse the NV_UNICODE_PACKET // The union must be the last member since we abuse the NV_UNICODE_PACKET
// text field to store variable length data which gets split before being // text field to store variable length data which gets split before being
// sent to the host. // sent to the host.
@@ -79,19 +82,12 @@ typedef struct _PACKET_HOLDER {
static PLT_MUTEX batchedInputMutex; static PLT_MUTEX batchedInputMutex;
static PPACKET_HOLDER currentQueuedControllerPacket[MAX_GAMEPADS]; static PPACKET_HOLDER currentQueuedControllerPacket[MAX_GAMEPADS];
static PPACKET_HOLDER currentQueuedRelativeMousePacket;
static PPACKET_HOLDER currentQueuedAbsoluteMousePacket;
static struct { static struct {
float x, y, z; float x, y, z;
bool dirty; // Update ready to send (queued packet holder in packetQueue) bool dirty; // Update ready to send (queued packet holder in packetQueue)
} currentGamepadSensorState[MAX_GAMEPADS][MAX_MOTION_EVENTS]; } currentGamepadSensorState[MAX_GAMEPADS][MAX_MOTION_EVENTS];
static struct {
int deltaX, deltaY;
bool dirty; // Update ready to send (queued packet holder in packetQueue)
} currentRelativeMouseState;
static struct {
int x, y;
int width, height;
bool dirty; // Update ready to send (queued packet holder in packetQueue)
} currentAbsoluteMouseState;
// Initializes the input stream // Initializes the input stream
int initializeInputStream(void) { int initializeInputStream(void) {
@@ -121,9 +117,9 @@ int initializeInputStream(void) {
absCurrentPosX = absCurrentPosY = 0.5f; absCurrentPosX = absCurrentPosY = 0.5f;
memset(currentQueuedControllerPacket, 0, sizeof(currentQueuedControllerPacket)); memset(currentQueuedControllerPacket, 0, sizeof(currentQueuedControllerPacket));
currentQueuedRelativeMousePacket = NULL;
currentQueuedAbsoluteMousePacket = NULL;
memset(currentGamepadSensorState, 0, sizeof(currentGamepadSensorState)); memset(currentGamepadSensorState, 0, sizeof(currentGamepadSensorState));
memset(&currentRelativeMouseState, 0, sizeof(currentRelativeMouseState));
memset(&currentAbsoluteMouseState, 0, sizeof(currentAbsoluteMouseState));
PltCreateMutex(&batchedInputMutex); PltCreateMutex(&batchedInputMutex);
return 0; return 0;
@@ -158,6 +154,8 @@ void destroyInputStream(void) {
} }
memset(currentQueuedControllerPacket, 0, sizeof(currentQueuedControllerPacket)); memset(currentQueuedControllerPacket, 0, sizeof(currentQueuedControllerPacket));
currentQueuedRelativeMousePacket = NULL;
currentQueuedAbsoluteMousePacket = NULL;
PltDeleteMutex(&batchedInputMutex); PltDeleteMutex(&batchedInputMutex);
} }
@@ -409,13 +407,12 @@ static void inputSendThreadProc(void* context) {
PltLockMutex(&batchedInputMutex); PltLockMutex(&batchedInputMutex);
int deltaX = currentRelativeMouseState.deltaX; if (holder == currentQueuedRelativeMousePacket) {
int deltaY = currentRelativeMouseState.deltaY; currentQueuedRelativeMousePacket = NULL;
currentRelativeMouseState.deltaX = 0; }
currentRelativeMouseState.deltaY = 0;
// The state change is no longer pending int deltaX = holder->relMouseDeltaX;
currentRelativeMouseState.dirty = false; int deltaY = holder->relMouseDeltaY;
PltUnlockMutex(&batchedInputMutex); PltUnlockMutex(&batchedInputMutex);
@@ -480,20 +477,9 @@ static void inputSendThreadProc(void* context) {
PltLockMutex(&batchedInputMutex); PltLockMutex(&batchedInputMutex);
// Populate the packet with the latest state if (holder == currentQueuedAbsoluteMousePacket) {
holder->packet.mouseMoveAbs.x = BE16(currentAbsoluteMouseState.x); currentQueuedAbsoluteMousePacket = NULL;
holder->packet.mouseMoveAbs.y = BE16(currentAbsoluteMouseState.y); }
// There appears to be a rounding error in GFE's scaling calculation which prevents
// the cursor from reaching the far edge of the screen when streaming at smaller
// resolutions with a higher desktop resolution (like streaming 720p with a desktop
// resolution of 1080p, or streaming 720p/1080p with a desktop resolution of 4K).
// Subtracting one from the reference dimensions seems to work around this issue.
holder->packet.mouseMoveAbs.width = BE16(currentAbsoluteMouseState.width - 1);
holder->packet.mouseMoveAbs.height = BE16(currentAbsoluteMouseState.height - 1);
// The state change is no longer pending
currentAbsoluteMouseState.dirty = false;
PltUnlockMutex(&batchedInputMutex); PltUnlockMutex(&batchedInputMutex);
@@ -760,6 +746,7 @@ int stopInputStream(void) {
int LiSendMouseMoveEvent(short deltaX, short deltaY) { int LiSendMouseMoveEvent(short deltaX, short deltaY) {
PPACKET_HOLDER holder; PPACKET_HOLDER holder;
int err; int err;
bool enqueueHolder = false;
if (!initialized) { if (!initialized) {
return -2; return -2;
@@ -771,20 +758,15 @@ int LiSendMouseMoveEvent(short deltaX, short deltaY) {
PltLockMutex(&batchedInputMutex); PltLockMutex(&batchedInputMutex);
// Combine the previous deltas with the new one // Switching to relative motion ends any pending absolute mouse batch
currentRelativeMouseState.deltaX += deltaX; currentQueuedAbsoluteMousePacket = NULL;
currentRelativeMouseState.deltaY += deltaY;
// Queue a packet holder if this is the only pending relative mouse event holder = currentQueuedRelativeMousePacket;
if (!currentRelativeMouseState.dirty) { if (!holder) {
// Set the dirty flag to claim ownership of inserting the packet holder
// and unlock to allow other threads to enqueue or process input.
currentRelativeMouseState.dirty = true;
PltUnlockMutex(&batchedInputMutex); PltUnlockMutex(&batchedInputMutex);
holder = allocatePacketHolder(0); holder = allocatePacketHolder(0);
if (holder == NULL) { if (holder == NULL) {
currentRelativeMouseState.dirty = false;
return -1; return -1;
} }
@@ -802,21 +784,37 @@ int LiSendMouseMoveEvent(short deltaX, short deltaY) {
holder->packet.mouseMoveRel.header.magic = LE32(MOUSE_MOVE_REL_MAGIC); holder->packet.mouseMoveRel.header.magic = LE32(MOUSE_MOVE_REL_MAGIC);
} }
// Remaining fields are set in the input thread based on the latest currentRelativeMouseState values holder->relMouseDeltaX = 0;
holder->relMouseDeltaY = 0;
enqueueHolder = true;
PltLockMutex(&batchedInputMutex);
currentQueuedRelativeMousePacket = holder;
}
// Combine previous deltas with the new one
holder->relMouseDeltaX += deltaX;
holder->relMouseDeltaY += deltaY;
PltUnlockMutex(&batchedInputMutex);
if (enqueueHolder) {
err = LbqOfferQueueItem(&packetQueue, holder, &holder->entry); err = LbqOfferQueueItem(&packetQueue, holder, &holder->entry);
if (err != LBQ_SUCCESS) { if (err != LBQ_SUCCESS) {
LC_ASSERT(err == LBQ_BOUND_EXCEEDED); LC_ASSERT(err == LBQ_BOUND_EXCEEDED);
Limelog("Input queue reached maximum size limit\n"); Limelog("Input queue reached maximum size limit\n");
freePacketHolder(holder);
// We weren't able to insert the entry, so let the next call try again PltLockMutex(&batchedInputMutex);
currentRelativeMouseState.dirty = false; if (currentQueuedRelativeMousePacket == holder) {
currentQueuedRelativeMousePacket = NULL;
}
PltUnlockMutex(&batchedInputMutex);
freePacketHolder(holder);
} }
} }
else { else {
// There's already a packet holder queued to send this event
PltUnlockMutex(&batchedInputMutex);
err = 0; err = 0;
} }
@@ -827,6 +825,7 @@ int LiSendMouseMoveEvent(short deltaX, short deltaY) {
int LiSendMousePositionEvent(short x, short y, short referenceWidth, short referenceHeight) { int LiSendMousePositionEvent(short x, short y, short referenceWidth, short referenceHeight) {
PPACKET_HOLDER holder; PPACKET_HOLDER holder;
int err; int err;
bool enqueueHolder = false;
if (!initialized) { if (!initialized) {
return -2; return -2;
@@ -834,22 +833,15 @@ int LiSendMousePositionEvent(short x, short y, short referenceWidth, short refer
PltLockMutex(&batchedInputMutex); PltLockMutex(&batchedInputMutex);
// Overwrite the previous mouse location with the new one // Switching to absolute position ends any pending relative mouse batch
currentAbsoluteMouseState.x = x; currentQueuedRelativeMousePacket = NULL;
currentAbsoluteMouseState.y = y;
currentAbsoluteMouseState.width = referenceWidth;
currentAbsoluteMouseState.height = referenceHeight;
// Queue a packet holder if this is the only pending absolute mouse event holder = currentQueuedAbsoluteMousePacket;
if (!currentAbsoluteMouseState.dirty) { if (!holder) {
// Set the dirty flag to claim ownership of inserting the packet holder
// and unlock to allow other threads to enqueue or process input.
currentAbsoluteMouseState.dirty = true;
PltUnlockMutex(&batchedInputMutex); PltUnlockMutex(&batchedInputMutex);
holder = allocatePacketHolder(0); holder = allocatePacketHolder(0);
if (holder == NULL) { if (holder == NULL) {
currentAbsoluteMouseState.dirty = false;
return -1; return -1;
} }
@@ -862,21 +854,42 @@ int LiSendMousePositionEvent(short x, short y, short referenceWidth, short refer
holder->packet.mouseMoveAbs.header.magic = LE32(MOUSE_MOVE_ABS_MAGIC); holder->packet.mouseMoveAbs.header.magic = LE32(MOUSE_MOVE_ABS_MAGIC);
holder->packet.mouseMoveAbs.unused = 0; holder->packet.mouseMoveAbs.unused = 0;
// Remaining fields are set in the input thread based on the latest currentAbsoluteMouseState values enqueueHolder = true;
PltLockMutex(&batchedInputMutex);
currentQueuedAbsoluteMousePacket = holder;
}
// Overwrite the previous mouse location with the new one
holder->packet.mouseMoveAbs.x = BE16(x);
holder->packet.mouseMoveAbs.y = BE16(y);
// There appears to be a rounding error in GFE's scaling calculation which prevents
// the cursor from reaching the far edge of the screen when streaming at smaller
// resolutions with a higher desktop resolution (like streaming 720p with a desktop
// resolution of 1080p, or streaming 720p/1080p with a desktop resolution of 4K).
// Subtracting one from the reference dimensions seems to work around this issue.
holder->packet.mouseMoveAbs.width = BE16(referenceWidth - 1);
holder->packet.mouseMoveAbs.height = BE16(referenceHeight - 1);
PltUnlockMutex(&batchedInputMutex);
if (enqueueHolder) {
err = LbqOfferQueueItem(&packetQueue, holder, &holder->entry); err = LbqOfferQueueItem(&packetQueue, holder, &holder->entry);
if (err != LBQ_SUCCESS) { if (err != LBQ_SUCCESS) {
LC_ASSERT(err == LBQ_BOUND_EXCEEDED); LC_ASSERT(err == LBQ_BOUND_EXCEEDED);
Limelog("Input queue reached maximum size limit\n"); Limelog("Input queue reached maximum size limit\n");
freePacketHolder(holder);
// We weren't able to insert the entry, so let the next call try again PltLockMutex(&batchedInputMutex);
currentAbsoluteMouseState.dirty = false; if (currentQueuedAbsoluteMousePacket == holder) {
currentQueuedAbsoluteMousePacket = NULL;
}
PltUnlockMutex(&batchedInputMutex);
freePacketHolder(holder);
} }
} }
else { else {
// There's already a packet holder queued to send this event
PltUnlockMutex(&batchedInputMutex);
err = 0; err = 0;
} }
@@ -925,6 +938,14 @@ int LiSendMouseButtonEvent(char action, int button) {
holder->packet.mouseButton.header.magic = LE32(holder->packet.mouseButton.header.magic); holder->packet.mouseButton.header.magic = LE32(holder->packet.mouseButton.header.magic);
holder->packet.mouseButton.button = (uint8_t)button; holder->packet.mouseButton.button = (uint8_t)button;
// End any current mouse motion batches so that mouse movements that occurred
// before this button event are sent before it, and mouse movements that occur
// after this button event are sent after it.
PltLockMutex(&batchedInputMutex);
currentQueuedRelativeMousePacket = NULL;
currentQueuedAbsoluteMousePacket = NULL;
PltUnlockMutex(&batchedInputMutex);
err = LbqOfferQueueItem(&packetQueue, holder, &holder->entry); err = LbqOfferQueueItem(&packetQueue, holder, &holder->entry);
if (err != LBQ_SUCCESS) { if (err != LBQ_SUCCESS) {
LC_ASSERT(err == LBQ_BOUND_EXCEEDED); LC_ASSERT(err == LBQ_BOUND_EXCEEDED);