diff --git a/src/ControlStream.c b/src/ControlStream.c index 1647c67..09fbb57 100644 --- a/src/ControlStream.c +++ b/src/ControlStream.c @@ -49,6 +49,7 @@ static int lastSeenFrame; static bool stopping; static bool disconnectPending; static bool encryptedControlStream; +static bool hdrEnabled; static int intervalGoodFrameCount; static int intervalTotalFrameCount; @@ -76,6 +77,7 @@ static PPLT_CRYPTO_CONTEXT decryptionCtx; #define IDX_INPUT_DATA 5 #define IDX_RUMBLE_DATA 6 #define IDX_TERMINATION 7 +#define IDX_HDR_INFO 8 #define CONTROL_STREAM_TIMEOUT_SEC 10 #define CONTROL_STREAM_LINGER_TIMEOUT_SEC 2 @@ -89,6 +91,7 @@ static const short packetTypesGen3[] = { -1, // Input data (unused) -1, // Rumble data (unused) -1, // Termination (unused) + -1, // HDR mode (unused) }; static const short packetTypesGen4[] = { 0x0606, // Request IDR frame @@ -99,6 +102,7 @@ static const short packetTypesGen4[] = { -1, // Input data (unused) -1, // Rumble data (unused) -1, // Termination (unused) + -1, // HDR mode (unused) }; static const short packetTypesGen5[] = { 0x0305, // Start A @@ -109,6 +113,7 @@ static const short packetTypesGen5[] = { 0x0207, // Input data -1, // Rumble data (unused) -1, // Termination (unused) + -1, // HDR mode (unknown) }; static const short packetTypesGen7[] = { 0x0305, // Start A @@ -119,6 +124,7 @@ static const short packetTypesGen7[] = { 0x0206, // Input data 0x010b, // Rumble data 0x0100, // Termination + 0x010e, // HDR mode }; static const short packetTypesGen7Enc[] = { 0x0302, // Request IDR frame @@ -129,6 +135,7 @@ static const short packetTypesGen7Enc[] = { 0x0206, // Input data 0x010b, // Rumble data 0x0109, // Termination (extended) + 0x010e, // HDR mode }; static const char requestIdrFrameGen3[] = { 0, 0 }; @@ -267,6 +274,7 @@ int initializeControlStream(void) { usePeriodicPing = APP_VERSION_AT_LEAST(7, 1, 415); encryptionCtx = PltCreateCryptoContext(); decryptionCtx = PltCreateCryptoContext(); + hdrEnabled = false; return 0; } @@ -799,6 +807,19 @@ static void controlReceiveThreadFunc(void* context) { ListenerCallbacks.rumble(controllerNumber, lowFreqRumble, highFreqRumble); } + else if (ctlHdr->type == packetTypes[IDX_HDR_INFO]) { + BYTE_BUFFER bb; + uint8_t enableByte; + + BbInitializeWrappedBuffer(&bb, (char*)ctlHdr, sizeof(*ctlHdr), packetLength - sizeof(*ctlHdr), BYTE_ORDER_LITTLE); + + // FIXME: There are 7 additional bytes that appear to always be all zeros. What do they mean? + // Is there some way that GFE tells us the HDR mastering metadata (NV_HDR_COLOR_DATA) set by the game? + BbGet8(&bb, &enableByte); + + hdrEnabled = (enableByte != 0); + ListenerCallbacks.setHdrMode(hdrEnabled); + } else if (ctlHdr->type == packetTypes[IDX_TERMINATION]) { BYTE_BUFFER bb; @@ -1396,3 +1417,7 @@ int startControlStream(void) { return 0; } + +bool LiGetCurrentHostDisplayHdrMode(void) { + return hdrEnabled; +} diff --git a/src/FakeCallbacks.c b/src/FakeCallbacks.c index 41bcea3..d8cada4 100644 --- a/src/FakeCallbacks.c +++ b/src/FakeCallbacks.c @@ -36,6 +36,7 @@ static void fakeClConnectionTerminated(int errorCode) {} static void fakeClLogMessage(const char* format, ...) {} static void fakeClRumble(unsigned short controllerNumber, unsigned short lowFreqMotor, unsigned short highFreqMotor) {} static void fakeClConnectionStatusUpdate(int connectionStatus) {} +static void fakeClSetHdrMode(bool enabled) {} static CONNECTION_LISTENER_CALLBACKS fakeClCallbacks = { .stageStarting = fakeClStageStarting, @@ -45,7 +46,8 @@ static CONNECTION_LISTENER_CALLBACKS fakeClCallbacks = { .connectionTerminated = fakeClConnectionTerminated, .logMessage = fakeClLogMessage, .rumble = fakeClRumble, - .connectionStatusUpdate = fakeClConnectionStatusUpdate + .connectionStatusUpdate = fakeClConnectionStatusUpdate, + .setHdrMode = fakeClSetHdrMode, }; void fixupMissingCallbacks(PDECODER_RENDERER_CALLBACKS* drCallbacks, PAUDIO_RENDERER_CALLBACKS* arCallbacks, @@ -121,5 +123,8 @@ void fixupMissingCallbacks(PDECODER_RENDERER_CALLBACKS* drCallbacks, PAUDIO_REND if ((*clCallbacks)->connectionStatusUpdate == NULL) { (*clCallbacks)->connectionStatusUpdate = fakeClConnectionStatusUpdate; } + if ((*clCallbacks)->setHdrMode == NULL) { + (*clCallbacks)->setHdrMode = fakeClSetHdrMode; + } } } diff --git a/src/Limelight.h b/src/Limelight.h index f24454e..aa26dfe 100644 --- a/src/Limelight.h +++ b/src/Limelight.h @@ -421,6 +421,12 @@ typedef void(*ConnListenerRumble)(unsigned short controllerNumber, unsigned shor #define CONN_STATUS_POOR 1 typedef void(*ConnListenerConnectionStatusUpdate)(int connectionStatus); +// This callback is invoked to notify the client of a change in HDR mode on +// the host. The client will probably want to update the local display mode +// to match the state of HDR on the host. This callback may be invoked even +// if enableHdr is false in the stream configuration. +typedef void(*ConnListenerSetHdrMode)(bool hdrEnabled); + typedef struct _CONNECTION_LISTENER_CALLBACKS { ConnListenerStageStarting stageStarting; ConnListenerStageComplete stageComplete; @@ -430,6 +436,7 @@ typedef struct _CONNECTION_LISTENER_CALLBACKS { ConnListenerLogMessage logMessage; ConnListenerRumble rumble; ConnListenerConnectionStatusUpdate connectionStatusUpdate; + ConnListenerSetHdrMode setHdrMode; } CONNECTION_LISTENER_CALLBACKS, *PCONNECTION_LISTENER_CALLBACKS; // Use this function to zero the connection callbacks when allocated on the stack or heap @@ -654,6 +661,10 @@ bool LiPeekNextVideoFrame(PDECODE_UNIT* decodeUnit); void LiWakeWaitForVideoFrame(void); void LiCompleteVideoFrame(VIDEO_FRAME_HANDLE handle, int drStatus); +// This function returns the last reported HDR mode from the host PC. +// See ConnListenerSetHdrMode() for more details. +bool LiGetCurrentHostDisplayHdrMode(void); + #ifdef __cplusplus } #endif