[client/idd] network: add GPU video transport

This commit is contained in:
Geoffrey McRae
2026-10-01 03:51:33 +10:00
parent d24d11c3b7
commit 53c25dd5a9
19 changed files with 6528 additions and 73 deletions

View File

@@ -15,6 +15,9 @@ set(TRANSPORT_NETWORK_SOURCES
known_hosts.c
network.c
recovery.c
video.c
video/decoder_egl.c
video/pyrowave.c
)
if(WIN32)
@@ -40,6 +43,7 @@ target_include_directories(transport_Network PRIVATE
target_link_libraries(transport_Network PRIVATE
lg_common
lg_egl_provider
msquic::msquic
)

View File

@@ -1071,8 +1071,7 @@ static void networkConnectionSetSessionFeatures(NetworkConnection * this)
if (services & (UINT16_C(1) << LG_NET_SERVICE_CONTROL))
this->session.features |=
LG_TRANSPORT_FEATURE_SET_CURSOR_POS |
LG_TRANSPORT_FEATURE_WINDOW_SIZE |
LG_TRANSPORT_FEATURE_FRAME_SCHEDULE;
LG_TRANSPORT_FEATURE_WINDOW_SIZE;
}
static bool networkConnectionHandleAuthChallenge(NetworkConnection * this,

View File

@@ -27,6 +27,7 @@
#include "input.h"
#include "known_hosts.h"
#include "recovery.h"
#include "video.h"
#include "app.h"
#include "interface/renderer.h"
@@ -56,6 +57,9 @@
(LG_NET_CLIPBOARD_CHUNK_HEADER_WIRE_SIZE + NETWORK_TRANSFER_CHUNK)
#define NETWORK_FILE_MAX_PAYLOAD \
(LG_NET_FILE_CHUNK_HEADER_WIRE_SIZE + NETWORK_TRANSFER_CHUNK)
#define NETWORK_VIDEO_MAX_PAYLOAD \
(LG_NET_VIDEO_FRAGMENT_HEADER_WIRE_SIZE + \
LG_NET_VIDEO_MAX_FRAGMENT_LENGTH)
static const LGNetCapability NETWORK_CAPABILITIES[] =
{
@@ -94,6 +98,19 @@ static const LGNetCapability NETWORK_CAPABILITIES[] =
.maxBidirectionalStreams = 0,
.maxUnidirectionalStreams = 0,
},
{
.service = LG_NET_SERVICE_VIDEO,
.versionMin =
LG_NET_VIDEO_BLOCK_FRAGMENTATION_INTRODUCED_SERVICE_VERSION,
.versionMax = LG_NET_VIDEO_VERSION_CURRENT,
.flags = LG_NET_CAPABILITY_RELIABLE |
LG_NET_CAPABILITY_DATAGRAM | LG_NET_CAPABILITY_BULK,
.features = 0,
.maxPayloadLength = NETWORK_VIDEO_MAX_PAYLOAD,
.maxDatagramPayload = NETWORK_MAX_DATAGRAM,
.maxBidirectionalStreams = 0,
.maxUnidirectionalStreams = 1,
},
{
.service = LG_NET_SERVICE_AUDIO,
.versionMin =
@@ -160,8 +177,8 @@ struct LG_Transport
NetworkCursor * cursor;
NetworkInput * input;
NetworkRecovery * recovery;
NetworkVideo * video;
NetworkKnownHosts knownHosts;
LGEvent * frameEvent;
LGEvent * promptIdle;
LG_Lock callbackLock;
@@ -172,7 +189,6 @@ struct LG_Transport
LG_VideoStatusFn videoStatusFn;
void * videoStatusOpaque;
bool rendererAttached;
bool promptCallbackActive;
struct NetworkTrustPrompt prompt;
};
@@ -340,6 +356,17 @@ static void networkNotifyVideoStatus(LG_Transport * this)
LG_UNLOCK(this->callbackLock);
}
static void networkVideoStatus(void * opaque,
const LG_VideoComponentStatus * status)
{
LG_Transport * this = opaque;
LG_LOCK(this->stateLock);
this->videoStatus.frame = *status;
LG_UNLOCK(this->stateLock);
networkNotifyVideoStatus(this);
}
static uint64_t networkNextEpoch(uint64_t epoch)
{
++epoch;
@@ -375,18 +402,17 @@ static void networkConnectionState(void * opaque, bool connected,
networkClipboardConnectionState(this->clipboard, connected);
networkControlConnectionState(this->control, connected);
networkRecoveryConnectionState(this->recovery, connected);
networkVideoConnectionState(this->video, connected);
const bool cursorAvailable =
networkCursorConnectionState(this->cursor, connected);
LG_LOCK(this->stateLock);
networkSetUnavailable(&this->videoStatus.frame, status);
if (cursorAvailable)
networkSetAvailable(&this->videoStatus.pointer);
else
networkSetUnavailable(&this->videoStatus.pointer, status);
LG_UNLOCK(this->stateLock);
lgSignalEvent(this->frameEvent);
networkNotifyVideoStatus(this);
}
@@ -416,6 +442,10 @@ static void networkPacket(void * opaque, const LGNetPacketView * packet,
(datagram ? networkAudioDatagram(this->audio, packet) :
networkAudioPacket(this->audio, packet));
break;
case LG_NET_SERVICE_VIDEO:
valid = datagram ? networkVideoDatagram(this->video, packet) :
networkVideoPacket(this->video, packet);
break;
case LG_NET_SERVICE_CLIPBOARD:
case LG_NET_SERVICE_FILE:
valid = delivery != NETWORK_PACKET_DELIVERY_DATAGRAM &&
@@ -451,7 +481,9 @@ static void networkSendReady(void * opaque, LGNetService service,
if ((service == LG_NET_SERVICE_CLIPBOARD ||
service == LG_NET_SERVICE_FILE) && !datagram)
networkClipboardSendReady(this->clipboard, service, delivery);
lgSignalEvent(this->frameEvent);
if (service == LG_NET_SERVICE_VIDEO &&
delivery != NETWORK_PACKET_DELIVERY_BULK)
networkVideoSendReady(this->video, datagram);
}
static bool networkCreate(LG_Transport ** result)
@@ -474,10 +506,8 @@ static bool networkCreate(LG_Transport ** result)
if (!networkKnownHostsInit(&this->knownHosts))
goto fail;
this->frameEvent = lgCreateEvent(true, 0);
this->promptIdle = lgCreateEvent(false, 0);
if (!this->frameEvent || !this->promptIdle ||
!lgSignalEvent(this->promptIdle))
if (!this->promptIdle || !lgSignalEvent(this->promptIdle))
goto fail;
const char * address = option_get_string("network", "address");
@@ -516,13 +546,16 @@ static bool networkCreate(LG_Transport ** result)
!networkControlCreate(&this->control, this->connection) ||
!networkCursorCreate(&this->cursor, this->connection) ||
!networkInputCreate(&this->input, this->connection) ||
!networkRecoveryCreate(&this->recovery, this->connection))
!networkRecoveryCreate(&this->recovery, this->connection) ||
!networkVideoCreate(&this->video, this->connection))
goto fail;
networkVideoSetStatusListener(this->video, networkVideoStatus, this);
*result = this;
return true;
fail:
networkVideoDestroy(&this->video);
networkRecoveryDestroy(&this->recovery);
networkInputDestroy(&this->input);
networkCursorDestroy(&this->cursor);
@@ -530,8 +563,6 @@ fail:
networkClipboardDestroy(&this->clipboard);
networkAudioDestroy(&this->audio);
networkConnectionDestroy(&this->connection);
if (this->frameEvent)
lgFreeEvent(this->frameEvent);
if (this->promptIdle)
lgFreeEvent(this->promptIdle);
networkKnownHostsFree(&this->knownHosts);
@@ -560,13 +591,13 @@ static void networkDestroy(LG_Transport ** transport)
LG_UNLOCK(this->callbackLock);
networkRecoveryDestroy(&this->recovery);
networkVideoDestroy(&this->video);
networkInputDestroy(&this->input);
networkCursorDestroy(&this->cursor);
networkControlDestroy(&this->control);
networkClipboardDestroy(&this->clipboard);
networkAudioDestroy(&this->audio);
networkConnectionDestroy(&this->connection);
lgFreeEvent(this->frameEvent);
lgFreeEvent(this->promptIdle);
networkKnownHostsFree(&this->knownHosts);
LG_LOCK_FREE(this->promptLock);
@@ -597,9 +628,9 @@ static void networkDisconnect(LG_Transport * this)
networkClipboardConnectionState(this->clipboard, false);
networkControlConnectionState(this->control, false);
networkRecoveryConnectionState(this->recovery, false);
networkVideoConnectionState(this->video, false);
networkCursorConnectionState(this->cursor, false);
networkConnectionDisconnect(this->connection);
lgSignalEvent(this->frameEvent);
}
static bool networkSessionValid(LG_Transport * this)
@@ -633,67 +664,40 @@ static bool networkSupportsDMA(LG_Transport * this)
static bool networkAttachRenderer(LG_Transport * this,
const LG_RendererInterop * interop)
{
const size_t requiredSize = offsetof(LG_RendererInterop,
egl.sharedTextureImport) + sizeof(interop->egl.sharedTextureImport);
if (!interop ||
interop->version < LG_RENDERER_INTEROP_EGL_TEXTURE_VERSION ||
interop->size < requiredSize ||
interop->type != LG_RENDERER_INTEROP_EGL ||
!interop->egl.display || !interop->egl.config ||
!interop->egl.shareContext || !interop->egl.sharedTextureImport)
{
DEBUG_ERROR("The network transport requires EGL shared textures");
return false;
}
this->rendererAttached = true;
return true;
return networkVideoAttachRenderer(this->video, interop);
}
static void networkDetachRenderer(LG_Transport * this)
{
this->rendererAttached = false;
networkVideoDetachRenderer(this->video);
}
static LG_TransportStatus networkNextFrame(LG_Transport * this, bool useDMA,
LG_TransportFrame * frame)
{
(void)useDMA;
(void)frame;
if (!this->rendererAttached)
return LG_TRANSPORT_ERROR;
if (!networkSessionValid(this))
return LG_TRANSPORT_DISCONNECTED;
lgWaitEvent(this->frameEvent, 100);
return networkSessionValid(this) ?
LG_TRANSPORT_UNAVAILABLE : LG_TRANSPORT_DISCONNECTED;
return networkVideoNextFrame(this->video, useDMA, frame);
}
static void networkGetFrameTiming(LG_Transport * this,
const LG_TransportFrame * frame, LG_TransportFrameTiming * timing)
{
(void)this;
(void)frame;
memset(timing, 0, sizeof(*timing));
networkVideoGetFrameTiming(this->video, frame, timing);
}
static void networkReleaseFrame(LG_Transport * this,
LG_TransportFrame * frame)
{
(void)this;
(void)frame;
networkVideoReleaseFrame(this->video, frame);
}
static void networkCancelFrameWait(LG_Transport * this)
{
lgSignalEvent(this->frameEvent);
networkVideoCancelFrameWait(this->video);
}
static void networkStopFrame(LG_Transport * this)
{
lgSignalEvent(this->frameEvent);
networkVideoStopFrame(this->video);
}
static LG_TransportStatus networkNextPointer(LG_Transport * this,

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,65 @@
/**
* Looking Glass
* Copyright © 2017-2026 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#pragma once
#include "connection.h"
#include "interface/transport.h"
#include <LGProtocol/NetworkProtocol.h>
#include <stdbool.h>
#include <stdint.h>
typedef struct NetworkVideo NetworkVideo;
typedef void (*NetworkVideoStatusFn)(void * opaque,
const LG_VideoComponentStatus * status);
bool networkVideoCreate(NetworkVideo ** video,
NetworkConnection * connection);
void networkVideoDestroy(NetworkVideo ** video);
void networkVideoConnectionState(NetworkVideo * video, bool connected);
void networkVideoProcess(NetworkVideo * video);
void networkVideoSendReady(NetworkVideo * video, bool datagram);
bool networkVideoPacket(NetworkVideo * video,
const LGNetPacketView * packet);
bool networkVideoDatagram(NetworkVideo * video,
const LGNetPacketView * packet);
void networkVideoSetStatusListener(NetworkVideo * video,
NetworkVideoStatusFn callback, void * opaque);
LG_VideoComponentStatus networkVideoGetStatus(NetworkVideo * video);
bool networkVideoAttachRenderer(NetworkVideo * video,
const LG_RendererInterop * interop);
void networkVideoDetachRenderer(NetworkVideo * video);
LG_TransportStatus networkVideoNextFrame(NetworkVideo * video, bool useDMA,
LG_TransportFrame * frame);
void networkVideoGetFrameTiming(NetworkVideo * video,
const LG_TransportFrame * frame, LG_TransportFrameTiming * timing);
void networkVideoReleaseFrame(NetworkVideo * video,
LG_TransportFrame * frame);
void networkVideoCancelFrameWait(NetworkVideo * video);
void networkVideoStopFrame(NetworkVideo * video);

View File

@@ -0,0 +1,67 @@
/**
* Looking Glass
* Copyright © 2017-2026 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#pragma once
#include "interface/external_frame.h"
#include "interface/renderer.h"
#include "interface/transport.h"
#include <LGProtocol/NetworkServices.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
typedef struct NetworkVideoDecoder NetworkVideoDecoder;
typedef enum NetworkVideoDecodeResult
{
NETWORK_VIDEO_DECODE_OK,
NETWORK_VIDEO_DECODE_UNSUPPORTED,
NETWORK_VIDEO_DECODE_CORRUPT,
NETWORK_VIDEO_DECODE_ERROR,
}
NetworkVideoDecodeResult;
typedef struct NetworkVideoDecodedFrame
{
LG_ExternalFrame * externalFrame;
LG_TransportFrameFormat format;
LG_TransportFrameTiming timing;
uint64_t decodeTimeNs;
}
NetworkVideoDecodedFrame;
bool networkVideoDecoderCreate(NetworkVideoDecoder ** decoder);
void networkVideoDecoderDestroy(NetworkVideoDecoder ** decoder);
bool networkVideoDecoderAttach(NetworkVideoDecoder * decoder,
const LG_RendererInterop * interop);
void networkVideoDecoderDetach(NetworkVideoDecoder * decoder);
void networkVideoDecoderReset(NetworkVideoDecoder * decoder);
NetworkVideoDecodeResult networkVideoDecoderConfigure(
NetworkVideoDecoder * decoder, const LGNetVideoStreamConfig * config);
NetworkVideoDecodeResult networkVideoDecoderDecode(
NetworkVideoDecoder * decoder, const LGNetVideoStreamConfig * config,
const void * data, size_t size, NetworkVideoDecodedFrame * frame);
void networkVideoDecoderRelease(NetworkVideoDecodedFrame * frame);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,324 @@
/**
* Looking Glass
* Copyright © 2017-2026 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* The packet layout and block ordering validated here are adapted from the
* PyroWave bitstream specification and reference parser at commit
* 89f7e47d4abbf650c91fae766728af866c5e32a0.
*
* Copyright (c) 2025 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include "pyrowave.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#define NETWORK_PYROWAVE_HEADER_SIZE 8U
#define NETWORK_PYROWAVE_DECOMPOSITION_LEVELS 5U
#define NETWORK_PYROWAVE_ALIGNMENT \
(1U << NETWORK_PYROWAVE_DECOMPOSITION_LEVELS)
#define NETWORK_PYROWAVE_MINIMUM_SIZE \
(4U << NETWORK_PYROWAVE_DECOMPOSITION_LEVELS)
#define NETWORK_PYROWAVE_SEQUENCE_MASK 7U
static uint32_t networkPyroWaveReadU32(const uint8_t * data)
{
return (uint32_t)data[0] |
(uint32_t)data[1] << 8 |
(uint32_t)data[2] << 16 |
(uint32_t)data[3] << 24;
}
static uint16_t networkPyroWaveReadU16(const uint8_t * data)
{
return (uint16_t)((uint16_t)data[0] | (uint16_t)data[1] << 8);
}
static unsigned networkPyroWaveBitCount(uint32_t value)
{
unsigned count = 0;
while (value)
{
count += value & 1U;
value >>= 1;
}
return count;
}
static bool networkPyroWavePacketValid(
const uint8_t * packet, size_t packetSize, uint16_t ballot)
{
const unsigned groups = networkPyroWaveBitCount(ballot);
const size_t controlBase = NETWORK_PYROWAVE_HEADER_SIZE;
const size_t scaleBase = controlBase + groups * sizeof(uint16_t);
const size_t planeBase = scaleBase + groups;
if (planeBase > packetSize)
return false;
size_t planeOffset = planeBase;
unsigned signs = 0;
for (unsigned group = 0; group < 16U; ++group)
{
if (!(ballot & (UINT32_C(1) << group)))
continue;
const unsigned compact = networkPyroWaveBitCount(
ballot & ((UINT32_C(1) << group) - 1U));
const uint16_t control = networkPyroWaveReadU16(
packet + controlBase + compact * sizeof(uint16_t));
const uint8_t qBits = packet[scaleBase + compact] & 15U;
for (unsigned subblock = 0; subblock < 8U; ++subblock)
{
const unsigned planes = qBits +
((control >> (2U * subblock)) & 3U);
if (planes > packetSize - planeOffset)
return false;
uint8_t nonzero = 0;
for (unsigned plane = 0; plane < planes; ++plane)
nonzero |= packet[planeOffset++];
signs += networkPyroWaveBitCount(nonzero);
}
}
const size_t signBytes = (signs + 7U) / 8U;
if (signBytes > packetSize - planeOffset)
return false;
/* payload_words rounds the packet to a u32 boundary. No other trailing
* payload is defined by version 1. */
return packetSize - planeOffset - signBytes < sizeof(uint32_t);
}
static uint32_t networkPyroWaveAlign(uint32_t value, uint32_t alignment)
{
return (value + alignment - 1U) & ~(alignment - 1U);
}
static bool networkPyroWaveBlockCapacity(
const LGNetVideoStreamConfig * config, uint32_t * capacity)
{
if (!config || !capacity)
return false;
uint32_t width = networkPyroWaveAlign(
config->width, NETWORK_PYROWAVE_ALIGNMENT);
uint32_t height = networkPyroWaveAlign(
config->height, NETWORK_PYROWAVE_ALIGNMENT);
if (width < NETWORK_PYROWAVE_MINIMUM_SIZE)
width = NETWORK_PYROWAVE_MINIMUM_SIZE;
if (height < NETWORK_PYROWAVE_MINIMUM_SIZE)
height = NETWORK_PYROWAVE_MINIMUM_SIZE;
uint64_t blocks = 0;
for (int level = NETWORK_PYROWAVE_DECOMPOSITION_LEVELS - 1;
level >= 0; --level)
{
const uint32_t levelWidth = width >> (level + 1);
const uint32_t levelHeight = height >> (level + 1);
const uint32_t blocksWide = (levelWidth + 31U) / 32U;
const uint32_t blocksHigh = (levelHeight + 31U) / 32U;
const uint32_t bands =
level == NETWORK_PYROWAVE_DECOMPOSITION_LEVELS - 1 ? 4U : 3U;
uint32_t components = 3U;
if (!level && config->chromaSubsampling == LG_NET_VIDEO_CHROMA_420)
components = 1U;
blocks += (uint64_t)blocksWide * blocksHigh * bands * components;
}
if (!blocks || blocks > LG_NET_VIDEO_MAX_BLOCKS)
return false;
*capacity = (uint32_t)blocks;
return true;
}
bool networkPyroWaveProfileValid(const LGNetVideoStreamConfig * config)
{
if (!config || !lgNetVideoStreamConfigValid(config) ||
config->codec != LG_NET_VIDEO_CODEC_PYROWAVE ||
config->codecVersion < LG_NET_VIDEO_PYROWAVE_CODEC_VERSION_MIN ||
config->codecVersion > LG_NET_VIDEO_PYROWAVE_CODEC_VERSION_MAX)
return false;
if (config->flags & LG_NET_VIDEO_STREAM_RESERVED_ALPHA)
return false;
const bool hdr =
(config->flags & LG_NET_VIDEO_STREAM_HDR) != 0;
if (hdr ?
(config->colorPrimaries != LG_NET_COLOR_PRIMARIES_BT2020 ||
config->colorTransfer != LG_NET_COLOR_TRANSFER_PQ ||
config->colorMatrix != LG_NET_COLOR_MATRIX_BT2020) :
(config->colorPrimaries != LG_NET_COLOR_PRIMARIES_BT709 ||
config->colorTransfer != LG_NET_COLOR_TRANSFER_BT709 ||
config->colorMatrix != LG_NET_COLOR_MATRIX_BT709))
return false;
uint32_t capacity;
return networkPyroWaveBlockCapacity(config, &capacity);
}
static bool networkPyroWaveSequenceProfile(
const LGNetVideoStreamConfig * config, uint32_t word)
{
const uint32_t chroma = (word >> 26) & 1U;
const uint32_t primaries = (word >> 27) & 1U;
const uint32_t transfer = (word >> 28) & 1U;
const uint32_t transform = (word >> 29) & 1U;
const uint32_t range = (word >> 30) & 1U;
const uint32_t wireChroma =
config->chromaSubsampling == LG_NET_VIDEO_CHROMA_444 ? 1U : 0U;
const uint32_t wirePrimaries =
config->colorPrimaries == LG_NET_COLOR_PRIMARIES_BT2020 ? 1U : 0U;
const uint32_t wireTransfer =
config->colorTransfer == LG_NET_COLOR_TRANSFER_PQ ? 1U : 0U;
const uint32_t wireTransform =
config->colorMatrix == LG_NET_COLOR_MATRIX_BT2020 ? 1U : 0U;
const uint32_t wireRange =
config->colorRange == LG_NET_COLOR_RANGE_LIMITED ? 1U : 0U;
return chroma == wireChroma &&
primaries == wirePrimaries &&
transfer == wireTransfer &&
transform == wireTransform &&
range == wireRange;
}
NetworkPyroWaveResult networkPyroWaveValidate(
const LGNetVideoStreamConfig * config, const void * data, size_t size,
uint32_t expectedBlocks, uint8_t * sequence)
{
if (!networkPyroWaveProfileValid(config))
return NETWORK_PYROWAVE_UNSUPPORTED;
if (!data || size < NETWORK_PYROWAVE_HEADER_SIZE || !expectedBlocks)
return NETWORK_PYROWAVE_INVALID;
uint32_t capacity;
if (!networkPyroWaveBlockCapacity(config, &capacity) ||
expectedBlocks > capacity)
return NETWORK_PYROWAVE_INVALID;
const size_t seenSize = ((size_t)capacity + 7U) / 8U;
uint8_t * seen = calloc(1, seenSize);
if (!seen)
return NETWORK_PYROWAVE_NO_MEMORY;
const uint8_t * bytes = data;
size_t remaining = size;
uint32_t uniqueBlocks = 0;
uint32_t declaredBlocks = 0;
uint8_t frameSequence = 0;
bool haveSequence = false;
bool haveHeader = false;
bool valid = true;
while (valid && remaining >= NETWORK_PYROWAVE_HEADER_SIZE)
{
const uint32_t first = networkPyroWaveReadU32(bytes);
const uint32_t second = networkPyroWaveReadU32(bytes + 4U);
const uint8_t packetSequence =
(uint8_t)((first >> 28) & NETWORK_PYROWAVE_SEQUENCE_MASK);
const bool extended = (first >> 31) != 0;
if (haveSequence && packetSequence != frameSequence)
{
valid = false;
break;
}
haveSequence = true;
frameSequence = packetSequence;
if (extended)
{
const uint32_t code = (second >> 24) & 3U;
const uint32_t width = (first & UINT32_C(0x00003FFF)) + 1U;
const uint32_t height = ((first >> 14) & UINT32_C(0x00003FFF)) + 1U;
const uint32_t blocks = second & UINT32_C(0x00FFFFFF);
if (code || width != config->width || height != config->height ||
!blocks || blocks > capacity ||
!networkPyroWaveSequenceProfile(config, second) ||
(haveHeader && blocks != declaredBlocks))
{
valid = false;
break;
}
declaredBlocks = blocks;
haveHeader = true;
bytes += NETWORK_PYROWAVE_HEADER_SIZE;
remaining -= NETWORK_PYROWAVE_HEADER_SIZE;
continue;
}
const uint32_t ballot = first & UINT32_C(0x0000FFFF);
const uint32_t payloadWords = (first >> 16) & UINT32_C(0x00000FFF);
const uint32_t blockIndex = second >> 8;
const size_t packetSize = (size_t)payloadWords * sizeof(uint32_t);
if (!ballot || payloadWords < 2U || packetSize > remaining ||
blockIndex >= capacity ||
!networkPyroWavePacketValid(bytes, packetSize, (uint16_t)ballot))
{
valid = false;
break;
}
const uint8_t mask = (uint8_t)(1U << (blockIndex & 7U));
if (!(seen[blockIndex >> 3] & mask))
{
seen[blockIndex >> 3] |= mask;
++uniqueBlocks;
}
bytes += packetSize;
remaining -= packetSize;
}
valid = valid && !remaining && haveHeader && haveSequence &&
declaredBlocks == uniqueBlocks && expectedBlocks == uniqueBlocks;
free(seen);
if (!valid)
return NETWORK_PYROWAVE_INVALID;
if (sequence)
*sequence = frameSequence;
return NETWORK_PYROWAVE_OK;
}

View File

@@ -0,0 +1,42 @@
/**
* Looking Glass
* Copyright © 2017-2026 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#pragma once
#include <LGProtocol/NetworkServices.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
typedef enum NetworkPyroWaveResult
{
NETWORK_PYROWAVE_OK,
NETWORK_PYROWAVE_INVALID,
NETWORK_PYROWAVE_UNSUPPORTED,
NETWORK_PYROWAVE_NO_MEMORY,
}
NetworkPyroWaveResult;
bool networkPyroWaveProfileValid(const LGNetVideoStreamConfig * config);
NetworkPyroWaveResult networkPyroWaveValidate(
const LGNetVideoStreamConfig * config, const void * data, size_t size,
uint32_t expectedBlocks, uint8_t * sequence);

View File

@@ -0,0 +1,332 @@
/**
* Looking Glass
* Copyright © 2017-2026 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* The coefficient and inverse-wavelet shaders are OpenGL ES adaptations of
* PyroWave's Vulkan shaders at commit
* 89f7e47d4abbf650c91fae766728af866c5e32a0.
*
* Copyright (c) 2025 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#pragma once
static const char NETWORK_VIDEO_DEQUANT_SHADER_0[] =
"#version 310 es\n"
"precision highp float;\n"
"precision highp int;\n"
"layout(local_size_x=128) in;\n"
"layout(r32f,binding=0) writeonly uniform highp image2DArray outputImage;\n"
"layout(std430,binding=0) readonly buffer OffsetBuffer { uint offsets[]; };\n"
"layout(std430,binding=1) readonly buffer PayloadBuffer { uint payload[]; };\n"
"uniform ivec2 resolution;\n"
"uniform int outputLayer;\n"
"uniform int blockOffset;\n"
"uniform int blockStride;\n"
"shared uint signOffset;\n"
"shared uint planeOffset[16];\n"
"shared uint significantPrefix[128];\n"
"uint readU8(uint byteOffset) {\n"
" return (payload[byteOffset >> 2u] >> (8u * (byteOffset & 3u))) & 255u;\n"
"}\n"
"uint readU16(uint byteOffset) {\n"
" return readU8(byteOffset) | (readU8(byteOffset + 1u) << 8u);\n"
"}\n"
"ivec2 unswizzle8x8(uint index) {\n"
" uint y = bitfieldExtract(index, 0, 1);\n"
" uint x = bitfieldExtract(index, 1, 2);\n"
" y |= bitfieldExtract(index, 3, 2) << 1;\n"
" x |= bitfieldExtract(index, 5, 1) << 2;\n"
" return ivec2(x, y);\n"
"}\n"
"float quantScale(uint code) { return float(code) / 8.0 + 0.25; }\n"
"float blockScale(uint code) {\n"
" int exponent = 4 - int(code >> 3u);\n"
" return (float(8u + (code & 7u)) / 8.0) * exp2(float(exponent));\n"
"}\n"
"void storeValues(ivec2 coord, mat2x4 values) {\n"
" for (int y=0; y<2; ++y) for (int x=0; x<4; ++x) {\n"
" ivec2 target = coord + ivec2(x,y);\n"
" if (all(lessThan(target,resolution)))\n"
" imageStore(outputImage,ivec3(target,outputLayer),vec4(values[y][x]));\n"
" }\n"
"}\n"
"void main() {\n"
" uint localIndex = gl_LocalInvocationIndex;\n"
" int blockIndex = blockOffset + int(gl_WorkGroupID.y) * blockStride +\n"
" int(gl_WorkGroupID.x);\n"
" uint sourceWord = offsets[blockIndex];\n"
" uint subblock = bitfieldExtract(localIndex,0,3);\n"
" uint blockX = bitfieldExtract(localIndex,3,2);\n"
" uint blockY = bitfieldExtract(localIndex,5,2);\n"
" uint group = blockY * 4u + blockX;\n"
" ivec2 coord = ivec2(gl_WorkGroupID.xy) * 32 +\n"
" 8 * ivec2(blockX,blockY) + unswizzle8x8(subblock << 3u);\n"
" if (sourceWord == 0xffffffffu) {\n"
" storeValues(coord,mat2x4(vec4(0.0),vec4(0.0)));\n"
" return;\n"
" }\n"
" uint sourceByte = sourceWord * 4u;\n"
" uint ballot = payload[sourceWord] & 65535u;\n"
" if (localIndex == 0u) {\n"
" uint cursor = sourceByte + 8u + 3u * uint(bitCount(ballot));\n"
" for (uint index=0u; index<16u; ++index) {\n"
" planeOffset[index] = cursor;\n"
" if (bitfieldExtract(ballot,int(index),1) != 0u) {\n"
" uint compact = uint(bitCount(bitfieldExtract(ballot,0,int(index))));\n"
" uint control = readU16(sourceByte + 8u + 2u * compact);\n"
" uint qbits = readU8(sourceByte + 8u +\n"
" 2u * uint(bitCount(ballot)) + compact) & 15u;\n"
" uint low = control & 0x5555u;\n"
" uint high = control & 0xaaaau;\n"
" high |= high >> 1u;\n"
" cursor += uint(bitCount(low) + bitCount(high)) + 8u * qbits;\n"
" }\n"
" }\n"
" signOffset = cursor * 8u;\n"
" }\n"
" barrier();\n";
static const char NETWORK_VIDEO_DEQUANT_SHADER_1[] =
" mat2x4 values = mat2x4(vec4(0.0),vec4(0.0));\n"
" uint significant = 0u;\n"
" if (bitfieldExtract(ballot,int(group),1) != 0u) {\n"
" uint compact = uint(bitCount(bitfieldExtract(ballot,0,int(group))));\n"
" uint control = readU16(sourceByte + 8u + 2u * compact);\n"
" uint scaleCode = readU8(sourceByte + 8u +\n"
" 2u * uint(bitCount(ballot)) + compact);\n"
" uint planes = (scaleCode & 15u) +\n"
" bitfieldExtract(control,int(2u * subblock),2);\n"
" int magnitude[8];\n"
" for (int i=0; i<8; ++i) magnitude[i]=0;\n"
" uint cursor = planeOffset[group];\n"
" for (int plane=int(planes)-1; plane>=0; --plane) {\n"
" uint bits = readU8(cursor++);\n"
" for (int i=0; i<8; ++i)\n"
" magnitude[i] = (magnitude[i] << 1) | int((bits >> uint(i)) & 1u);\n"
" }\n"
" float scale = blockScale(payload[sourceWord+1u] & 255u) *\n"
" quantScale(scaleCode >> 4u);\n"
" for (int x=0; x<4; ++x) for (int y=0; y<2; ++y) {\n"
" int index = x * 2 + y;\n"
" float value = float(magnitude[index]);\n"
" if (value != 0.0) { value += 0.5; ++significant; }\n"
" values[y][x] = value * scale;\n"
" }\n"
" }\n"
" significantPrefix[localIndex] = significant;\n"
" barrier();\n"
" if (localIndex == 0u) {\n"
" uint prefix = 0u;\n"
" for (uint index=0u; index<128u; ++index) {\n"
" uint count = significantPrefix[index];\n"
" significantPrefix[index] = prefix;\n"
" prefix += count;\n"
" }\n"
" }\n"
" barrier();\n"
" uint bitCursor = signOffset + significantPrefix[localIndex];\n"
" uint word = payload[bitCursor >> 5u];\n"
" uint upper = payload[(bitCursor >> 5u) + 1u];\n"
" uint shift = bitCursor & 31u;\n"
" if (shift != 0u) word = (word >> shift) | (upper << (32u-shift));\n"
" uint signIndex = 0u;\n"
" for (int x=0; x<4; ++x) for (int y=0; y<2; ++y) {\n"
" if (values[y][x] != 0.0) {\n"
" if (((word >> signIndex) & 1u) != 0u) values[y][x] = -values[y][x];\n"
" ++signIndex;\n"
" }\n"
" }\n"
" storeValues(coord,values);\n"
"}\n";
static const char NETWORK_VIDEO_IDWT_SHADER_0[] =
"#version 310 es\n"
"precision highp float;\n"
"precision highp int;\n"
"layout(local_size_x=64) in;\n"
"layout(binding=0) uniform highp sampler2DArray coefficients;\n"
"layout(r32f,binding=1) writeonly uniform highp image2D outputImage;\n"
"uniform ivec2 resolution;\n"
"uniform vec2 invResolution;\n"
"uniform int inputLevel;\n"
"uniform int baseLayer;\n"
"uniform bool dcShift;\n"
"const int APRON=4, HALF_APRON=2, BLOCK=32, HALF_BLOCK=16;\n"
"const float ALPHA=-1.586134342059924;\n"
"const float BETA=-0.052980118572961;\n"
"const float GAMMA=0.882911075530934;\n"
"const float DELTA=0.443506852043971;\n"
"const float K=1.230174104914001;\n"
"const float INV_K=1.0/1.230174104914001;\n"
"shared vec2 sharedBlock[20][41];\n"
"ivec2 unswizzle8x8(uint index) {\n"
" uint y=bitfieldExtract(index,0,1), x=bitfieldExtract(index,1,2);\n"
" y|=bitfieldExtract(index,3,2)<<1; x|=bitfieldExtract(index,5,1)<<2;\n"
" return ivec2(x,y);\n"
"}\n"
"bvec2 and2(bvec2 a,bvec2 b) { return bvec2(a.x&&b.x,a.y&&b.y); }\n"
"vec2 mirrorUV(ivec2 coord,bool evenX,bool evenY) {\n"
" coord-=ivec2(and2(bvec2(evenX,evenY),lessThan(coord,ivec2(0))));\n"
" coord+=1;\n"
" coord+=ivec2(and2(bvec2(!evenX,!evenY),greaterThanEqual(coord,resolution)));\n"
" return (vec2(coord)*invResolution).yx;\n"
"}\n"
"vec4 gatherBand(ivec2 coord,bool evenX,bool evenY,int band) {\n"
" return textureGather(coefficients,vec3(mirrorUV(coord,evenX,evenY),\n"
" float(baseLayer+band)),0);\n"
"}\n"
"void store4(ivec2 coord,vec4 a,vec4 b,vec4 c,vec4 d) {\n"
" sharedBlock[coord.y][2*coord.x+0]=vec2(a.x,c.x);\n"
" sharedBlock[coord.y][2*coord.x+1]=vec2(b.x,d.x);\n"
" sharedBlock[coord.y][2*coord.x+2]=vec2(a.y,c.y);\n"
" sharedBlock[coord.y][2*coord.x+3]=vec2(b.y,d.y);\n"
" sharedBlock[coord.y+1][2*coord.x+0]=vec2(a.z,c.z);\n"
" sharedBlock[coord.y+1][2*coord.x+1]=vec2(b.z,d.z);\n"
" sharedBlock[coord.y+1][2*coord.x+2]=vec2(a.w,c.w);\n"
" sharedBlock[coord.y+1][2*coord.x+3]=vec2(b.w,d.w);\n"
"}\n"
"void loadApron(uint index) {\n"
" ivec2 base=ivec2(gl_WorkGroupID.xy)*HALF_BLOCK-HALF_APRON;\n"
" ivec2 local=2*unswizzle8x8(index); ivec2 coord=base+local;\n"
" store4(local,gatherBand(coord,true,true,0).wxzy,\n"
" gatherBand(coord,false,true,2).wxzy,\n"
" gatherBand(coord,true,false,1).wxzy,\n"
" gatherBand(coord,false,false,3).wxzy);\n"
" ivec2 horizontal=ivec2(HALF_BLOCK+2*int(index%2u),2*int(index/2u));\n"
" if (horizontal.y<HALF_BLOCK+2*HALF_APRON) { coord=base+horizontal;\n"
" store4(horizontal,gatherBand(coord,true,true,0).wxzy,\n"
" gatherBand(coord,false,true,2).wxzy,\n"
" gatherBand(coord,true,false,1).wxzy,\n"
" gatherBand(coord,false,false,3).wxzy); }\n"
" ivec2 vertical=horizontal.yx;\n"
" if (vertical.x<HALF_BLOCK) { coord=base+vertical;\n"
" store4(vertical,gatherBand(coord,true,true,0).wxzy,\n"
" gatherBand(coord,false,true,2).wxzy,\n"
" gatherBand(coord,true,false,1).wxzy,\n"
" gatherBand(coord,false,false,3).wxzy); }\n"
" barrier();\n"
"}\n";
static const char NETWORK_VIDEO_IDWT_SHADER_1[] =
"void inverse8x2(uint index) {\n"
" vec2 values[16]; ivec2 local=ivec2(8*int(index%4u),int(index/4u));\n"
" for(int i=0;i<16;i+=2){values[i]=sharedBlock[local.y][local.x+i]*K;\n"
" values[i+1]=sharedBlock[local.y][local.x+i+1]*INV_K;}\n"
" for(int i=2;i<15;i+=2)values[i]-=DELTA*(values[i-1]+values[i+1]);\n"
" for(int i=3;i<14;i+=2)values[i]-=GAMMA*(values[i-1]+values[i+1]);\n"
" for(int i=4;i<13;i+=2)values[i]-=BETA*(values[i-1]+values[i+1]);\n"
" for(int i=5;i<12;i+=2)values[i]-=ALPHA*(values[i-1]+values[i+1]);\n"
" barrier();\n"
" for(int i=2;i<6;++i){vec2 a=values[2*i],b=values[2*i+1];\n"
" int y=(local.x>>1)+(i-2); sharedBlock[y][2*local.y]=vec2(a.x,b.x);\n"
" sharedBlock[y][2*local.y+1]=vec2(a.y,b.y);}\n"
"}\n"
"void inverse4x2(uint index,bool activeLane,int yOffset) {\n"
" vec2 values[12]; ivec2 local=ivec2(4*int(index%8u),int(index/8u)+yOffset);\n"
" if(activeLane){for(int i=0;i<12;i+=2){values[i]=sharedBlock[local.y][local.x+i]*K;\n"
" values[i+1]=sharedBlock[local.y][local.x+i+1]*INV_K;}\n"
" for(int i=2;i<11;i+=2)values[i]-=DELTA*(values[i-1]+values[i+1]);\n"
" for(int i=3;i<10;i+=2)values[i]-=GAMMA*(values[i-1]+values[i+1]);\n"
" for(int i=4;i<9;i+=2)values[i]-=BETA*(values[i-1]+values[i+1]);\n"
" for(int i=5;i<8;i+=2)values[i]-=ALPHA*(values[i-1]+values[i+1]);}\n"
" barrier();\n"
" if(activeLane)for(int i=2;i<4;++i){vec2 a=values[2*i],b=values[2*i+1];\n"
" int y=(local.x>>1)+(i-2); sharedBlock[y][2*local.y]=vec2(a.x,b.x);\n"
" sharedBlock[y][2*local.y+1]=vec2(a.y,b.y);}\n"
"}\n"
"void main(){uint index=gl_LocalInvocationIndex;loadApron(index);\n"
" inverse8x2(index);inverse4x2(index,index<32u,HALF_BLOCK);barrier();\n"
" inverse8x2(index);barrier();ivec2 local=unswizzle8x8(index);\n"
" ivec2 outputSize=imageSize(outputImage);\n"
" for(int y=local.y;y<HALF_BLOCK;y+=8)for(int x=local.x;x<BLOCK;x+=8){\n"
" vec2 value=sharedBlock[y][x];if(dcShift)value+=vec2(0.5);\n"
" ivec2 a=ivec2(2*y,x)+BLOCK*ivec2(gl_WorkGroupID.yx);\n"
" ivec2 b=a+ivec2(1,0);if(all(lessThan(a,outputSize)))\n"
" imageStore(outputImage,a,vec4(value.x));\n"
" if(all(lessThan(b,outputSize)))imageStore(outputImage,b,vec4(value.y));}\n"
"}\n";
static const char NETWORK_VIDEO_YUV_SHADER[] =
"#version 310 es\n"
"precision highp float;\n"
"precision highp int;\n"
"layout(local_size_x=8,local_size_y=8) in;\n"
"layout(binding=0) uniform highp sampler2D lumaPlane;\n"
"layout(binding=1) uniform highp sampler2D cbPlane;\n"
"layout(binding=2) uniform highp sampler2D crPlane;\n"
"layout(rgba16f,binding=0) writeonly uniform highp image2D outputImage;\n"
"uniform ivec2 outputSize;\n"
"uniform bool bt2020;\n"
"uniform bool chromaLeft;\n"
"uniform float lumaOffset;\n"
"uniform float lumaScale;\n"
"uniform float chromaMidpoint;\n"
"uniform float chromaScale;\n"
"void main(){ivec2 pixel=ivec2(gl_GlobalInvocationID.xy);\n"
" if(any(greaterThanEqual(pixel,outputSize)))return;\n"
" vec2 uv=(vec2(pixel)+0.5)/vec2(outputSize);\n"
" vec2 offset=chromaLeft?vec2(0.5/float(outputSize.x),0.0):vec2(0.0);\n"
" float y=clamp((texture(lumaPlane,uv).x-lumaOffset)*lumaScale,0.0,1.0);\n"
" float cb=clamp((texture(cbPlane,uv+offset).x-chromaMidpoint)*\n"
" chromaScale,-0.5,0.5);\n"
" float cr=clamp((texture(crPlane,uv+offset).x-chromaMidpoint)*\n"
" chromaScale,-0.5,0.5);\n"
" mat3 m=bt2020?mat3(vec3(1.0),vec3(0.0,-0.16455313,1.8814),\n"
" vec3(1.4746,-0.57135313,0.0)):\n"
" mat3(vec3(1.0),vec3(0.0,-0.13397432/0.7152,1.8556),\n"
" vec3(1.5748,-0.33480248/0.7152,0.0));\n"
" vec3 rgb=clamp(m*vec3(y,cb,cr),vec3(0.0),vec3(1.0));\n"
" imageStore(outputImage,pixel,vec4(rgb,1.0));}\n";
static const char * const NETWORK_VIDEO_DEQUANT_SOURCES[] =
{
NETWORK_VIDEO_DEQUANT_SHADER_0,
NETWORK_VIDEO_DEQUANT_SHADER_1,
};
static const char * const NETWORK_VIDEO_IDWT_SOURCES[] =
{
NETWORK_VIDEO_IDWT_SHADER_0,
NETWORK_VIDEO_IDWT_SHADER_1,
};
static const char * const NETWORK_VIDEO_YUV_SOURCES[] =
{
NETWORK_VIDEO_YUV_SHADER,
};

View File

@@ -93,6 +93,7 @@
<ClCompile Include="transport\network\CNetworkServer.cpp" />
<ClCompile Include="transport\network\CNetworkSession.cpp" />
<ClCompile Include="transport\network\CNetworkTransport.cpp" />
<ClCompile Include="transport\network\CNetworkVideo.cpp" />
<ClCompile Include="transport\network\NetworkAuthCrypto.cpp" />
<ClCompile Include="transport\network\NetworkSettings.cpp" />
<ClCompile Include="config\CSettings.cpp" />
@@ -180,6 +181,7 @@
<ClInclude Include="transport\network\CNetworkServer.h" />
<ClInclude Include="transport\network\CNetworkSession.h" />
<ClInclude Include="transport\network\CNetworkTransport.h" />
<ClInclude Include="transport\network\CNetworkVideo.h" />
<ClInclude Include="transport\network\NetworkAuthCrypto.h" />
<ClInclude Include="transport\network\NetworkSettings.h" />
<ClInclude Include="config\CSettings.h" />

View File

@@ -301,6 +301,9 @@
<ClInclude Include="transport\network\CNetworkTransport.h">
<Filter>Transport\Network</Filter>
</ClInclude>
<ClInclude Include="transport\network\CNetworkVideo.h">
<Filter>Transport\Network</Filter>
</ClInclude>
<ClInclude Include="transport\network\NetworkAuthCrypto.h">
<Filter>Transport\Network</Filter>
</ClInclude>
@@ -498,6 +501,9 @@
<ClCompile Include="transport\network\CNetworkTransport.cpp">
<Filter>Transport\Network</Filter>
</ClCompile>
<ClCompile Include="transport\network\CNetworkVideo.cpp">
<Filter>Transport\Network</Filter>
</ClCompile>
<ClCompile Include="transport\network\NetworkAuthCrypto.cpp">
<Filter>Transport\Network</Filter>
</ClCompile>

View File

@@ -439,10 +439,12 @@ struct CNetworkServer::Impl
};
Impl(CNetworkTransport& transport, CNetworkAudio& audio,
CNetworkClipboard& clipboard, const NetworkSettings& settings) :
CNetworkClipboard& clipboard, CNetworkVideo& video,
const NetworkSettings& settings) :
transport(transport),
audio(audio),
clipboard(clipboard),
video(video),
settings(settings)
{
}
@@ -463,6 +465,7 @@ struct CNetworkServer::Impl
CNetworkTransport& transport;
CNetworkAudio& audio;
CNetworkClipboard& clipboard;
CNetworkVideo& video;
NetworkSettings settings;
const QUIC_API_TABLE * api = nullptr;
HQUIC registration = nullptr;
@@ -536,6 +539,15 @@ struct CNetworkServer::Impl
};
}
static NetworkVideoPeer VideoPeer(const Connection& connection)
{
return
{
connection.client,
connection.generation,
};
}
bool AudioPeerInfo(const NetworkAudioPeer& peer,
NetworkAudioPeerInfo& info)
{
@@ -679,6 +691,87 @@ struct CNetworkServer::Impl
return NetworkClipboardSendResult::FAILED;
}
bool VideoPeerInfo(const NetworkVideoPeer& peer,
NetworkVideoPeerInfo& info)
{
CSRWSharedLock guard(lock);
for (Connection * connection : connections)
{
if (connection->client != peer.client ||
connection->generation != peer.generation)
continue;
CSRWSharedLock connectionGuard(connection->lock);
const uint16_t serviceMask = UINT16_C(1) << LG_NET_SERVICE_VIDEO;
if (connection->closing ||
connection->handshake != HandshakeState::READY ||
!(connection->selectedServices & serviceMask))
return false;
const LGNetCapability& capability =
connection->selectedCapabilities[LG_NET_SERVICE_VIDEO];
info.serviceVersion = capability.versionMin;
info.capabilityFlags = capability.flags;
info.maxReliablePayload = capability.maxPayloadLength;
info.maxDatagramPayload = capability.maxDatagramPayload;
info.maxBulkPayload =
capability.flags & LG_NET_CAPABILITY_BULK ?
capability.maxPayloadLength : 0;
return true;
}
return false;
}
NetworkVideoSendResult SendVideo(
const NetworkVideoPeer& peer, NetworkVideoDelivery delivery,
uint16_t messageType, LGNetEnvelopeFlags flags,
uint64_t componentEpoch, uint64_t requestID,
const void * payload, uint32_t payloadLength)
{
CSRWSharedLock guard(lock);
for (Connection * connection : connections)
{
if (connection->client != peer.client ||
connection->generation != peer.generation)
continue;
SendResult result = SendResult::FAILED;
switch (delivery)
{
case NetworkVideoDelivery::RELIABLE:
result = TrySendPacket(*connection, LG_NET_SERVICE_VIDEO,
messageType, flags, componentEpoch, requestID,
payload, payloadLength);
break;
case NetworkVideoDelivery::DATAGRAM:
result = TrySendDatagram(*connection, LG_NET_SERVICE_VIDEO,
messageType, flags, componentEpoch, requestID,
payload, payloadLength, true);
break;
case NetworkVideoDelivery::BULK:
result = TrySendBulk(*connection, LG_NET_SERVICE_VIDEO,
messageType, flags, componentEpoch, requestID,
payload, payloadLength);
break;
}
switch (result)
{
case SendResult::SENT:
return NetworkVideoSendResult::ACCEPTED;
case SendResult::BLOCKED:
return NetworkVideoSendResult::BLOCKED;
case SendResult::FAILED:
return NetworkVideoSendResult::FAILED;
}
}
return NetworkVideoSendResult::FAILED;
}
void Shutdown(Connection& connection, QUIC_UINT62 error)
{
HQUIC handle = nullptr;
@@ -1048,6 +1141,31 @@ struct CNetworkServer::Impl
ClipboardPeer(connection), LG_NET_SERVICE_FILE, bulk);
}
void NotifyVideoSendReady(Connection& connection, Delivery delivery)
{
NetworkVideoDelivery videoDelivery = NetworkVideoDelivery::RELIABLE;
switch (delivery)
{
case Delivery::RELIABLE:
videoDelivery = NetworkVideoDelivery::RELIABLE;
break;
case Delivery::DATAGRAM:
videoDelivery = NetworkVideoDelivery::DATAGRAM;
break;
case Delivery::BULK:
videoDelivery = NetworkVideoDelivery::BULK;
break;
default:
return;
}
if (!ConsumeSendReady(connection, LG_NET_SERVICE_VIDEO, delivery))
return;
video.SendReady(VideoPeer(connection), videoDelivery);
}
SendResult TrySendPacket(Connection& connection, LGNetService service,
uint16_t messageType, LGNetEnvelopeFlags flags,
uint64_t componentEpoch, uint64_t requestID, const void * payload,
@@ -1894,6 +2012,7 @@ retry:
bool activated = false;
bool audioSelected = false;
bool clipboardSelected = false;
bool videoSelected = false;
{
CSRWExclusiveLock guard(connection.lock);
activated = !connection.closing &&
@@ -1909,15 +2028,17 @@ retry:
connection.clientNonce, sizeof(connection.clientNonce));
NetworkAuthCrypto::SecureZero(
connection.serverNonce, sizeof(connection.serverNonce));
connection.handshake = HandshakeState::READY;
audioSelected = (connection.selectedServices &
connection.handshake = HandshakeState::READY;
audioSelected = (connection.selectedServices &
(UINT16_C(1) << LG_NET_SERVICE_AUDIO)) != 0;
const uint16_t clipboardServices =
(UINT16_C(1) << LG_NET_SERVICE_CLIPBOARD) |
(UINT16_C(1) << LG_NET_SERVICE_FILE);
clipboardSelected =
clipboardSelected =
(connection.selectedServices & clipboardServices) ==
clipboardServices;
videoSelected = (connection.selectedServices &
(UINT16_C(1) << LG_NET_SERVICE_VIDEO)) != 0;
}
}
if (!activated)
@@ -1930,6 +2051,8 @@ retry:
if (clipboardSelected &&
!clipboard.PeerConnected(ClipboardPeer(connection)))
return false;
if (videoSelected && !video.PeerConnected(VideoPeer(connection)))
return false;
DEBUG_INFO("Network client %u completed the control handshake",
connection.client);
return true;
@@ -3421,6 +3544,9 @@ retry:
const bool audioSubscribe =
packet.envelope.service == LG_NET_SERVICE_AUDIO &&
packet.envelope.messageType == LG_NET_AUDIO_MESSAGE_SUBSCRIBE;
const bool videoSubscribe =
packet.envelope.service == LG_NET_SERVICE_VIDEO &&
packet.envelope.messageType == LG_NET_VIDEO_MESSAGE_SUBSCRIBE;
const bool audioResponse =
packet.envelope.service == LG_NET_SERVICE_AUDIO &&
packet.envelope.messageType == LG_NET_AUDIO_MESSAGE_STATE_ACK;
@@ -3429,7 +3555,7 @@ retry:
packet.envelope.service == LG_NET_SERVICE_FILE;
LGNetEnvelopeFlags expectedFlags =
LG_NET_ENVELOPE_RELIABLE | LG_NET_ENVELOPE_FINAL;
if (audioSubscribe)
if (audioSubscribe || videoSubscribe)
expectedFlags |= LG_NET_ENVELOPE_ACK_REQUIRED;
if (cursorStatus || audioResponse)
expectedFlags |= LG_NET_ENVELOPE_RESPONSE;
@@ -3544,6 +3670,11 @@ retry:
return audio.HandlePacket(AudioPeer(connection), packet, false) !=
NetworkAudioPacketResult::INVALID;
case LG_NET_SERVICE_VIDEO:
return video.HandlePacket(VideoPeer(connection), packet,
NetworkVideoDelivery::RELIABLE) !=
NetworkVideoPacketResult::INVALID;
case LG_NET_SERVICE_CLIPBOARD:
case LG_NET_SERVICE_FILE:
return clipboard.HandlePacket(
@@ -3621,6 +3752,10 @@ retry:
if (packet.envelope.service == LG_NET_SERVICE_AUDIO)
return audio.HandlePacket(AudioPeer(connection), packet, true) !=
NetworkAudioPacketResult::INVALID;
if (packet.envelope.service == LG_NET_SERVICE_VIDEO)
return video.HandlePacket(VideoPeer(connection), packet,
NetworkVideoDelivery::DATAGRAM) !=
NetworkVideoPacketResult::INVALID;
return false;
}
@@ -3994,6 +4129,7 @@ retry:
static_cast<unsigned>(service));
session = std::move(connection.session);
}
video.PeerDisconnected(VideoPeer(connection));
clipboard.PeerDisconnected(ClipboardPeer(connection));
audio.PeerDisconnected(AudioPeer(connection));
if (session)
@@ -4156,6 +4292,7 @@ retry:
ReadyServicesLocked(connection, Delivery::DATAGRAM);
}
owner.NotifyAudioSendReady(connection, true);
owner.NotifyVideoSendReady(connection, Delivery::DATAGRAM);
return QUIC_STATUS_SUCCESS;
}
@@ -4170,6 +4307,7 @@ retry:
ReleaseSendLocked(connection, *send);
}
owner.NotifyAudioSendReady(connection, true);
owner.NotifyVideoSendReady(connection, Delivery::DATAGRAM);
}
return QUIC_STATUS_SUCCESS;
@@ -4215,6 +4353,7 @@ retry:
ReleaseSendLocked(connection, *send);
}
owner.NotifyAudioSendReady(connection, false);
owner.NotifyVideoSendReady(connection, Delivery::RELIABLE);
owner.NotifyClipboardSendReady(connection, Delivery::RELIABLE);
return QUIC_STATUS_SUCCESS;
}
@@ -4352,7 +4491,10 @@ retry:
stream.streamID = event->START_COMPLETE.ID;
stream.bound = true;
if (stream.openingReturned)
{
ready = ReadyServicesLocked(connection, Delivery::BULK);
connection.bulkSend.writableServices |= ready;
}
}
}
if (QUIC_FAILED(status))
@@ -4367,7 +4509,10 @@ retry:
send.release();
if (ready)
{
owner.NotifyVideoSendReady(connection, Delivery::BULK);
owner.NotifyClipboardSendReady(connection, Delivery::BULK);
}
return QUIC_STATUS_SUCCESS;
}
@@ -4385,6 +4530,7 @@ retry:
CSRWExclusiveLock guard(connection.lock);
ReleaseSendLocked(connection, *send);
}
owner.NotifyVideoSendReady(connection, Delivery::BULK);
owner.NotifyClipboardSendReady(connection, Delivery::BULK);
return QUIC_STATUS_SUCCESS;
}
@@ -4438,6 +4584,7 @@ retry:
{
const uint64_t now = GetTickCount64();
audio.Process(now);
video.Process(now);
clipboard.Process(now);
CSRWSharedLock guard(lock);
if (stopping || !api)
@@ -4666,8 +4813,8 @@ retry:
CNetworkServer::CNetworkServer(CNetworkTransport& transport,
CNetworkAudio& audio, CNetworkClipboard& clipboard,
const NetworkSettings& settings) :
m_impl(new Impl(transport, audio, clipboard, settings))
CNetworkVideo& video, const NetworkSettings& settings) :
m_impl(new Impl(transport, audio, clipboard, video, settings))
{
}
@@ -4725,3 +4872,21 @@ NetworkClipboardSendResult CNetworkServer::SendClipboard(
componentEpoch, requestID, payload, payloadLength) :
NetworkClipboardSendResult::FAILED;
}
bool CNetworkServer::VideoPeerInfo(
const NetworkVideoPeer& peer, NetworkVideoPeerInfo& info)
{
return m_impl && m_impl->VideoPeerInfo(peer, info);
}
NetworkVideoSendResult CNetworkServer::SendVideo(
const NetworkVideoPeer& peer, NetworkVideoDelivery delivery,
uint16_t messageType, LGNetEnvelopeFlags flags,
uint64_t componentEpoch, uint64_t requestID,
const void * payload, uint32_t payloadLength)
{
return m_impl ?
m_impl->SendVideo(peer, delivery, messageType, flags,
componentEpoch, requestID, payload, payloadLength) :
NetworkVideoSendResult::FAILED;
}

View File

@@ -22,6 +22,7 @@
#include "transport/network/CNetworkAudio.h"
#include "transport/network/CNetworkClipboard.h"
#include "transport/network/CNetworkVideo.h"
#include <memory>
@@ -30,7 +31,8 @@ struct NetworkSettings;
class CNetworkServer final :
public INetworkAudioSender,
public INetworkClipboardSender
public INetworkClipboardSender,
public INetworkVideoSender
{
private:
struct Impl;
@@ -38,7 +40,8 @@ private:
public:
CNetworkServer(CNetworkTransport& transport, CNetworkAudio& audio,
CNetworkClipboard& clipboard, const NetworkSettings& settings);
CNetworkClipboard& clipboard, CNetworkVideo& video,
const NetworkSettings& settings);
~CNetworkServer();
CNetworkServer(const CNetworkServer&) = delete;
@@ -63,4 +66,12 @@ public:
uint16_t messageType, LGNetEnvelopeFlags flags,
uint64_t componentEpoch, uint64_t requestID,
const void * payload, uint32_t payloadLength) override;
bool VideoPeerInfo(const NetworkVideoPeer& peer,
NetworkVideoPeerInfo& info) override;
NetworkVideoSendResult SendVideo(
const NetworkVideoPeer& peer, NetworkVideoDelivery delivery,
uint16_t messageType, LGNetEnvelopeFlags flags,
uint64_t componentEpoch, uint64_t requestID,
const void * payload, uint32_t payloadLength) override;
};

View File

@@ -77,7 +77,8 @@ bool CNetworkTransport::Initialize()
try
{
m_server.reset(
new CNetworkServer(*this, m_audio, m_clipboard, m_settings));
new CNetworkServer(
*this, m_audio, m_clipboard, m_video, m_settings));
}
catch (...)
{
@@ -97,11 +98,25 @@ bool CNetworkTransport::Initialize()
m_server.reset();
return false;
}
if (!m_video.AttachSender(*m_server))
{
DEBUG_ERROR("Failed to attach the network video sender");
m_clipboard.DetachSender();
m_audio.DetachSender();
m_server.reset();
return false;
}
if ((m_config.services & TRANSPORT_SERVICE_FRAME) && !m_video.Start())
{
DEBUG_ERROR("Failed to start the network video endpoint");
m_video.DetachSender();
m_clipboard.DetachSender();
m_audio.DetachSender();
m_server.reset();
return false;
}
m_state = State::INITIALIZED;
if (m_config.services & TRANSPORT_SERVICE_FRAME)
DEBUG_WARN("Network video is unavailable until the GPU codec and "
"texture sink are attached");
return true;
}
@@ -171,6 +186,7 @@ void CNetworkTransport::Stop()
if (server)
server->Stop();
m_video.DetachSender();
m_clipboard.DetachSender();
m_audio.DetachSender();
@@ -183,6 +199,7 @@ void CNetworkTransport::Stop()
session->Close();
m_input.Stop();
m_video.Stop();
m_audio.Stop();
m_clipboard.Stop();
m_control.SetControlEvents(nullptr, {});
@@ -208,33 +225,42 @@ void CNetworkTransport::RecoveryStatus(const SourceKey& source,
const FrameProfile * CNetworkTransport::Profiles(unsigned& count) const
{
count = 0;
return nullptr;
if (!(m_config.services & TRANSPORT_SERVICE_FRAME))
{
count = 0;
return nullptr;
}
return m_video.Profiles(count);
}
CfgResult CNetworkTransport::Probe(const FrameCfg& cfg) const
{
(void)cfg;
return CfgResult::REJECTED;
return (m_config.services & TRANSPORT_SERVICE_FRAME) ?
m_video.Probe(cfg) : CfgResult::REJECTED;
}
CfgResult CNetworkTransport::Prepare(const FrameCfg& cfg)
{
(void)cfg;
return CfgResult::REJECTED;
return (m_config.services & TRANSPORT_SERVICE_FRAME) ?
m_video.Prepare(cfg) : CfgResult::REJECTED;
}
void CNetworkTransport::Commit()
{
if (m_config.services & TRANSPORT_SERVICE_FRAME)
m_video.Commit();
}
void CNetworkTransport::Abort()
{
if (m_config.services & TRANSPORT_SERVICE_FRAME)
m_video.Abort();
}
std::shared_ptr<const FrameCaps> CNetworkTransport::GetFrameCaps() const
{
return std::shared_ptr<const FrameCaps>();
return (m_config.services & TRANSPORT_SERVICE_FRAME) ?
m_video.GetFrameCaps() : std::shared_ptr<const FrameCaps>();
}
DirectFrameBufferMemory CNetworkTransport::GetDirectMemory() const
@@ -264,6 +290,11 @@ IInputSource * CNetworkTransport::Input()
return (m_config.services & TRANSPORT_SERVICE_INPUT) ? &m_input : nullptr;
}
ITexSink * CNetworkTransport::TexSink()
{
return (m_config.services & TRANSPORT_SERVICE_FRAME) ? &m_video : nullptr;
}
bool CNetworkTransport::ServiceEnabled(LGNetService service) const
{
switch (service)

View File

@@ -25,6 +25,7 @@
#include "transport/network/CNetworkAudio.h"
#include "transport/network/CNetworkClipboard.h"
#include "transport/network/CNetworkSession.h"
#include "transport/network/CNetworkVideo.h"
#include "transport/network/NetworkSettings.h"
#include <memory>
@@ -32,9 +33,8 @@
class CNetworkServer;
// This provider owns transport-independent lifecycle and session state. It
// deliberately advertises no frame capabilities and no texture sink until a
// GPU codec backend is attached; it must not be mistaken for working media.
// This provider owns the transport-independent lifecycle, media endpoints,
// and authenticated session state used by the QUIC server.
class CNetworkTransport final : public ITransport
{
private:
@@ -53,6 +53,7 @@ private:
NetworkSettings m_settings;
CNetworkAudio m_audio;
CNetworkClipboard m_clipboard;
CNetworkVideo m_video;
std::unique_ptr<CNetworkServer> m_server;
mutable CSRWLock m_lifecycleLock;
@@ -97,6 +98,7 @@ public:
IClipboardSource * Clipboard() override;
IControlSink * Control() override;
IInputSource * Input() override;
ITexSink * TexSink() override;
const NetworkSettings& Settings() const { return m_settings; }
bool ServiceEnabled(LGNetService service) const;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,118 @@
/**
* Looking Glass
* Copyright © 2017-2026 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#pragma once
#include "transport/FrameCaps.h"
#include "transport/ITexSink.h"
#include <LGProtocol/NetworkProtocol.h>
#include <LGProtocol/NetworkServices.h>
#include <memory>
#include <stdint.h>
struct NetworkVideoPeer
{
uint32_t client = 0;
uint32_t generation = 0;
};
struct NetworkVideoPeerInfo
{
uint16_t serviceVersion = 0;
LGNetCapabilityFlags capabilityFlags = 0;
uint32_t maxReliablePayload = 0;
uint32_t maxDatagramPayload = 0;
uint32_t maxBulkPayload = 0;
};
enum class NetworkVideoDelivery
{
RELIABLE,
DATAGRAM,
BULK,
};
enum class NetworkVideoSendResult
{
ACCEPTED,
BLOCKED,
FAILED,
};
enum class NetworkVideoPacketResult
{
HANDLED,
IGNORED,
INVALID,
};
class INetworkVideoSender
{
public:
virtual ~INetworkVideoSender() = default;
virtual bool VideoPeerInfo(
const NetworkVideoPeer& peer, NetworkVideoPeerInfo& info) = 0;
virtual NetworkVideoSendResult SendVideo(
const NetworkVideoPeer& peer, NetworkVideoDelivery delivery,
uint16_t messageType, LGNetEnvelopeFlags flags,
uint64_t componentEpoch, uint64_t requestID,
const void * payload, uint32_t payloadLength) = 0;
};
class CNetworkVideo final : public ITexSink
{
private:
struct Impl;
std::unique_ptr<Impl> m_impl;
public:
CNetworkVideo();
~CNetworkVideo() override;
CNetworkVideo(const CNetworkVideo&) = delete;
CNetworkVideo& operator=(const CNetworkVideo&) = delete;
bool AttachSender(INetworkVideoSender& sender);
void DetachSender();
bool Start();
void Stop();
void Process(uint64_t nowMs);
bool PeerConnected(const NetworkVideoPeer& peer);
void PeerDisconnected(const NetworkVideoPeer& peer);
NetworkVideoPacketResult HandlePacket(const NetworkVideoPeer& peer,
const LGNetPacketView& packet, NetworkVideoDelivery delivery);
void SendReady(
const NetworkVideoPeer& peer, NetworkVideoDelivery delivery);
const FrameProfile * Profiles(unsigned& count) const;
std::shared_ptr<const FrameCaps> GetFrameCaps() const;
CfgResult Probe(const FrameCfg& cfg) const;
CfgResult Prepare(const FrameCfg& cfg);
void Commit();
void Abort();
PushResult Push(FrameIn frame, TexLease lease) noexcept override;
PushResult Push(FrameIn frame, D11Lease lease) noexcept override;
};