Pack NV_UNICODE_PACKET densely for Sunshine

This reduces overhead up to 32x for large text blocks and avoids
flooding the ENet connection with packets all at once.
This commit is contained in:
Cameron Gutman committed 2026-10-19 22:51:41 -05:00
1 parent 4a95eb35b2
commit 04053843c9
1 file changed
+83 -38
+83 -38
View File
@@ -319,6 +319,36 @@ static void floatToNetfloat(float in, netfloat out) {
} }
} }
// Returns the length in bytes of the UTF-8 code point at the given offset in the text,
// or 0 if the byte at that offset does not start a valid UTF-8 code point.
static uint32_t getUtf8CodePointLength(const char* utf8Text, uint32_t offset) {
uint8_t firstByte = (uint8_t)utf8Text[offset];
uint32_t codePointLength;
if ((firstByte & 0x80) == 0x00) {
// 1 byte code point
codePointLength = 1;
}
else if ((firstByte & 0xE0) == 0xC0) {
// 2 byte code point
codePointLength = 2;
}
else if ((firstByte & 0xF0) == 0xE0) {
// 3 byte code point
codePointLength = 3;
}
else if ((firstByte & 0xF8) == 0xF0) {
// 4 byte code point
codePointLength = 4;
}
else {
Limelog("Invalid unicode code point starting byte: %02x\n", firstByte);
return 0;
}
return codePointLength;
}
// Input thread proc // Input thread proc
static void inputSendThreadProc(void* context) { static void inputSendThreadProc(void* context) {
SOCK_RET err; SOCK_RET err;
@@ -549,56 +579,71 @@ static void inputSendThreadProc(void* context) {
uint32_t totalLength = PAYLOAD_SIZE(holder) - sizeof(uint32_t); uint32_t totalLength = PAYLOAD_SIZE(holder) - sizeof(uint32_t);
uint32_t i = 0; uint32_t i = 0;
// HACK: This is a workaround for the fact that GFE doesn't appear to synchronize keyboard if (!IS_SUNSHINE()) {
// and UTF-8 text events with each other. We need to make sure any previous keyboard events // HACK: This is a workaround for the fact that GFE doesn't appear to synchronize keyboard
// have been processed prior to sending these UTF-8 events to avoid interference between // and UTF-8 text events with each other. We need to make sure any previous keyboard events
// the two (especially with modifier keys). // have been processed prior to sending these UTF-8 events to avoid interference between
flushInputOnControlStream(); // the two (especially with modifier keys).
while (!PltIsThreadInterrupted(&inputSendThread) && isControlDataInTransit()) { flushInputOnControlStream();
PltSleepMs(10); while (!PltIsThreadInterrupted(&inputSendThread) && isControlDataInTransit()) {
PltSleepMs(10);
}
// Finally, sleep an additional 50 ms to allow the events to be processed by Windows
PltSleepMs(50);
} }
// Finally, sleep an additional 50 ms to allow the events to be processed by Windows
PltSleepMs(50);
// We send each Unicode code point individually. This way we can always ensure they will
// never straddle a packet boundary (which will cause a parsing error on the host).
while (i < totalLength && !PltIsThreadInterrupted(&inputSendThread)) { while (i < totalLength && !PltIsThreadInterrupted(&inputSendThread)) {
uint32_t codePointLength; uint32_t chunkLength = 0;
uint8_t firstByte = (uint8_t)holder->packet.unicode.text[i]; uint32_t remaining = totalLength - i;
if ((firstByte & 0x80) == 0x00) {
// 1 byte code point if (IS_SUNSHINE()) {
codePointLength = 1; // Sunshine accepts multiple code points per packet, so we pack as many as
} // fit within the NV_UNICODE_PACKET to send fewer packets overall. Code points
else if ((firstByte & 0xE0) == 0xC0) { // are never split across packet boundaries.
// 2 byte code point while (chunkLength < remaining) {
codePointLength = 2; uint32_t codePointLength = getUtf8CodePointLength(holder->packet.unicode.text, i + chunkLength);
} if (codePointLength == 0) {
else if ((firstByte & 0xF0) == 0xE0) { break;
// 3 byte code point }
codePointLength = 3; if (chunkLength + codePointLength > UTF8_TEXT_EVENT_MAX_COUNT) {
} break;
else if ((firstByte & 0xF8) == 0xF0) { }
// 4 byte code point chunkLength += codePointLength;
codePointLength = 4; }
// No code point fit, so stop sending
if (chunkLength == 0) {
break;
}
} }
else { else {
Limelog("Invalid unicode code point starting byte: %02x\n", firstByte); // We send each Unicode code point individually. This way we can always ensure they will
break; // never straddle a packet boundary (which will cause a parsing error on the host).
uint32_t codePointLength = getUtf8CodePointLength(holder->packet.unicode.text, i);
if (codePointLength == 0) {
break;
}
chunkLength = codePointLength;
} }
// Use the original packet as a template and fixup to send one code point at a time // Clamp the chunk if the final code point is truncated
splitPacket = *holder; if (chunkLength > remaining) {
splitPacket.packet.unicode.header.size = BE32(sizeof(uint32_t) + codePointLength); chunkLength = remaining;
memcpy(splitPacket.packet.unicode.text, &holder->packet.unicode.text[i], codePointLength); }
// Encrypt and send the split packet // Use the original packet as a template and fixup to send the chunk
if (!sendInputPacket(&splitPacket, i + 1 < totalLength)) { splitPacket = *holder;
splitPacket.packet.unicode.header.size = BE32(sizeof(uint32_t) + chunkLength);
memcpy(splitPacket.packet.unicode.text, &holder->packet.unicode.text[i], chunkLength);
// Encrypt and send the chunk
if (!sendInputPacket(&splitPacket, i + chunkLength < totalLength)) {
freePacketHolder(holder); freePacketHolder(holder);
return; return;
} }
i += codePointLength; i += chunkLength;
} }
freePacketHolder(holder); freePacketHolder(holder);