From 26ca90893ae428651e61705d6f16fd988a207276 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Sun, 19 Jul 2026 03:11:42 +1000 Subject: [PATCH] [idd/client] apply the Windows display color transform Honor the IddCx 3x4 XYZ matrix and post-transfer LUT in the IDD conversion pipeline. Carry transform changes to the client so EGL applies the same calibration to hardware cursors, and invalidate the full frame whenever calibration changes. --- client/include/interface/renderer.h | 5 + client/renderers/EGL/cursor.c | 74 ++++- client/renderers/EGL/cursor.h | 3 + client/renderers/EGL/egl.c | 40 ++- client/renderers/EGL/shader/cursor_rgb.frag | 82 ++++- client/src/main.c | 20 +- common/include/common/KVMFR.h | 31 +- idd/LGIdd/CIndirectDeviceContext.cpp | 82 ++++- idd/LGIdd/CIndirectDeviceContext.h | 15 +- idd/LGIdd/CPostProcessor.cpp | 16 +- idd/LGIdd/CPostProcessor.h | 10 + idd/LGIdd/CSwapChainProcessor.cpp | 1 + idd/LGIdd/Device.cpp | 49 ++- idd/LGIdd/LGIdd.vcxproj | 4 +- idd/LGIdd/LGIdd.vcxproj.filters | 6 + idd/LGIdd/effect/CColorTransformEffect.cpp | 328 ++++++++++++++++++++ idd/LGIdd/effect/CColorTransformEffect.h | 48 +++ 17 files changed, 766 insertions(+), 48 deletions(-) create mode 100644 idd/LGIdd/effect/CColorTransformEffect.cpp create mode 100644 idd/LGIdd/effect/CColorTransformEffect.h diff --git a/client/include/interface/renderer.h b/client/include/interface/renderer.h index c6667c23..de6fe316 100644 --- a/client/include/interface/renderer.h +++ b/client/include/interface/renderer.h @@ -158,6 +158,11 @@ typedef struct LG_RendererOps bool (*onMouseShape)(LG_Renderer * renderer, const LG_RendererCursor cursor, const int width, const int height, const int pitch, const uint8_t * data); + /* optional display calibration update for hardware cursor composition + * Context: cursorThread */ + void (*onMouseColorTransform)(LG_Renderer * renderer, + const KVMFRColorTransform * transform); + /* called when the mouse has moved or changed visibillity * Context: cursorThread */ bool (*onMouseEvent)(LG_Renderer * renderer, const bool visible, int x, int y, diff --git a/client/renderers/EGL/cursor.c b/client/renderers/EGL/cursor.c index b81d1cd2..2d123fa3 100644 --- a/client/renderers/EGL/cursor.c +++ b/client/renderers/EGL/cursor.c @@ -46,8 +46,11 @@ struct CursorTex EGL_Uniform * uScale; EGL_Uniform * uRotate; EGL_Uniform * uCBMode; - EGL_Uniform * uMapSDRtoPQ; + EGL_Uniform * uWireTransfer; EGL_Uniform * uSDRWhiteLevel; + EGL_Uniform * uColorTransformFlags; + EGL_Uniform * uColorMatrix; + EGL_Uniform * uColorScalar; }; struct CursorPos @@ -80,9 +83,14 @@ struct EGL_Cursor _Atomic(struct CursorPos) hs; _Atomic(struct CursorSize) size; _Atomic(float) scale; - _Atomic(bool) mapSDRtoPQ; + _Atomic(int) wireTransfer; _Atomic(float) sdrWhiteLevel; + bool colorTransformUpdate; + KVMFRColorTransform pendingColorTransform; + KVMFRColorTransform activeColorTransform; + GLuint colorLUT; + struct CursorTex norm; struct CursorTex mono; struct EGL_Model * model; @@ -115,8 +123,13 @@ static bool cursorTexInit(struct CursorTex * t, t->uScale = egl_shaderGetUniform(t->shader, "scale" ); t->uRotate = egl_shaderGetUniform(t->shader, "rotate" ); t->uCBMode = egl_shaderGetUniform(t->shader, "cbMode" ); - t->uMapSDRtoPQ = egl_shaderGetUniform(t->shader, "mapSDRtoPQ" ); + t->uWireTransfer = egl_shaderGetUniform(t->shader, "wireTransfer" ); t->uSDRWhiteLevel = egl_shaderGetUniform(t->shader, "sdrWhiteLevel"); + t->uColorTransformFlags = + egl_shaderGetUniform(t->shader, "colorTransformFlags"); + t->uColorMatrix = egl_shaderGetUniform(t->shader, "colorMatrix[0]"); + t->uColorScalar = egl_shaderGetUniform(t->shader, "colorTransformScalar"); + egl_shaderAssocTextures(t->shader, 2); return true; } @@ -129,8 +142,16 @@ static inline void setCursorTexUniforms(EGL_Cursor * cursor, egl_uniform1f(t->uScale , scale); egl_uniform1i(t->uRotate , cursor->rotate); egl_uniform1i(t->uCBMode , cursor->cbMode); - egl_uniform1i(t->uMapSDRtoPQ , !mono && atomic_load(&cursor->mapSDRtoPQ)); + egl_uniform1i(t->uWireTransfer, + mono ? 0 : atomic_load(&cursor->wireTransfer)); egl_uniform1f(t->uSDRWhiteLevel, atomic_load(&cursor->sdrWhiteLevel)); + egl_uniform1ui(t->uColorTransformFlags, + mono ? 0 : cursor->activeColorTransform.flags); + egl_uniformSet(t->uColorMatrix, EGL_UNIFORM_TYPE_4FV, 3, GL_FALSE, + cursor->activeColorTransform.matrix); + egl_uniform1f(t->uColorScalar, cursor->activeColorTransform.scalar); + if (!mono && cursor->colorLUT) + egl_stateBindTexture(1, GL_TEXTURE_2D, cursor->colorLUT); } static void cursorTexFree(struct CursorTex * t) @@ -178,7 +199,7 @@ bool egl_cursorInit(EGL_Cursor ** cursor) atomic_init(&(*cursor)->hs , hs ); atomic_init(&(*cursor)->size , size ); atomic_init(&(*cursor)->scale , 1.0f ); - atomic_init(&(*cursor)->mapSDRtoPQ, false); + atomic_init(&(*cursor)->wireTransfer, 0); atomic_init(&(*cursor)->sdrWhiteLevel, KVMFR_SDR_WHITE_LEVEL_DEFAULT); @@ -196,6 +217,8 @@ void egl_cursorFree(EGL_Cursor ** cursor) cursorTexFree(&(*cursor)->norm); cursorTexFree(&(*cursor)->mono); + if ((*cursor)->colorLUT) + glDeleteTextures(1, &(*cursor)->colorLUT); egl_modelFree(&(*cursor)->model); free(*cursor); @@ -222,6 +245,7 @@ bool egl_cursorSetShape(EGL_Cursor * cursor, const LG_RendererCursor type, if (!cursor->data) { DEBUG_ERROR("Failed to malloc buffer for cursor shape"); + LG_UNLOCK(cursor->lock); return false; } @@ -235,6 +259,15 @@ bool egl_cursorSetShape(EGL_Cursor * cursor, const LG_RendererCursor type, return true; } +void egl_cursorSetColorTransform(EGL_Cursor * cursor, + const KVMFRColorTransform * transform) +{ + LG_LOCK(cursor->lock); + cursor->pendingColorTransform = *transform; + cursor->colorTransformUpdate = true; + LG_UNLOCK(cursor->lock); +} + void egl_cursorSetSize(EGL_Cursor * cursor, const float w, const float h) { struct CursorSize size = { .w = w, .h = h }; @@ -262,14 +295,36 @@ struct CursorState egl_cursorRender(EGL_Cursor * cursor, if (!cursor->visible) return (struct CursorState) { .visible = false }; - if (cursor->update) + if (cursor->update || cursor->colorTransformUpdate) { LG_LOCK(cursor->lock); + const bool updateShape = cursor->update; cursor->update = false; - uint8_t * data = cursor->data; - switch(cursor->type) + if (cursor->colorTransformUpdate) { + cursor->activeColorTransform = cursor->pendingColorTransform; + cursor->colorTransformUpdate = false; + + if (cursor->activeColorTransform.flags & KVMFR_COLOR_TRANSFORM_LUT) + { + if (!cursor->colorLUT) + glGenTextures(1, &cursor->colorLUT); + egl_stateBindTexture(1, GL_TEXTURE_2D, cursor->colorLUT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, 4096, 1, 0, + GL_RGBA, GL_FLOAT, cursor->activeColorTransform.lut); + } + } + + if (updateShape) + { + uint8_t * data = cursor->data; + switch(cursor->type) + { case LG_CURSOR_MASKED_COLOR: { uint32_t xor[cursor->height][cursor->width]; @@ -330,6 +385,7 @@ struct CursorState egl_cursorRender(EGL_Cursor * cursor, egl_textureUpdate(cursor->mono.texture, (uint8_t *)xor, true); break; } + } } LG_UNLOCK(cursor->lock); } @@ -447,5 +503,5 @@ void egl_cursorSetHDRState(EGL_Cursor * cursor, bool hdrActive, bool hdrPQ, { atomic_store(&cursor->sdrWhiteLevel, sdrWhiteLevel > 0.0f ? sdrWhiteLevel : KVMFR_SDR_WHITE_LEVEL_DEFAULT); - atomic_store(&cursor->mapSDRtoPQ, hdrActive && hdrPQ); + atomic_store(&cursor->wireTransfer, !hdrActive ? 0 : (hdrPQ ? 2 : 1)); } diff --git a/client/renderers/EGL/cursor.h b/client/renderers/EGL/cursor.h index 0fd85b41..88f48c13 100644 --- a/client/renderers/EGL/cursor.h +++ b/client/renderers/EGL/cursor.h @@ -43,6 +43,9 @@ bool egl_cursorSetShape( const int stride, const uint8_t * data); +void egl_cursorSetColorTransform(EGL_Cursor * cursor, + const KVMFRColorTransform * transform); + void egl_cursorSetSize(EGL_Cursor * cursor, const float x, const float y); void egl_cursorSetScale(EGL_Cursor * cursor, const float scale); diff --git a/client/renderers/EGL/egl.c b/client/renderers/EGL/egl.c index 47a0d049..c93439d0 100644 --- a/client/renderers/EGL/egl.c +++ b/client/renderers/EGL/egl.c @@ -577,6 +577,13 @@ static bool egl_onMouseShape(LG_Renderer * renderer, const LG_RendererCursor cur return true; } +static void egl_onMouseColorTransform(LG_Renderer * renderer, + const KVMFRColorTransform * transform) +{ + struct Inst * this = UPCAST(struct Inst, renderer); + egl_cursorSetColorTransform(this->cursor, transform); +} + static bool egl_onMouseEvent(LG_Renderer * renderer, const bool visible, int x, int y, const int hx, const int hy) { @@ -1438,22 +1445,23 @@ static void egl_spiceShow(LG_Renderer * renderer, bool show) struct LG_RendererOps LGR_EGL = { - .getName = egl_getName, - .setup = egl_setup, - .create = egl_create, - .initialize = egl_initialize, - .deinitialize = egl_deinitialize, - .supports = egl_supports, - .onRestart = egl_onRestart, - .onResize = egl_onResize, - .onMouseShape = egl_onMouseShape, - .onMouseEvent = egl_onMouseEvent, - .onFrameFormat = egl_onFrameFormat, - .onFrame = egl_onFrame, - .renderStartup = egl_renderStartup, - .render = egl_render, - .createTexture = egl_createTexture, - .freeTexture = egl_freeTexture, + .getName = egl_getName, + .setup = egl_setup, + .create = egl_create, + .initialize = egl_initialize, + .deinitialize = egl_deinitialize, + .supports = egl_supports, + .onRestart = egl_onRestart, + .onResize = egl_onResize, + .onMouseShape = egl_onMouseShape, + .onMouseColorTransform = egl_onMouseColorTransform, + .onMouseEvent = egl_onMouseEvent, + .onFrameFormat = egl_onFrameFormat, + .onFrame = egl_onFrame, + .renderStartup = egl_renderStartup, + .render = egl_render, + .createTexture = egl_createTexture, + .freeTexture = egl_freeTexture, .spiceConfigure = egl_spiceConfigure, .spiceDrawFill = egl_spiceDrawFill, diff --git a/client/renderers/EGL/shader/cursor_rgb.frag b/client/renderers/EGL/shader/cursor_rgb.frag index 9be30404..e6496d43 100644 --- a/client/renderers/EGL/shader/cursor_rgb.frag +++ b/client/renderers/EGL/shader/cursor_rgb.frag @@ -9,10 +9,56 @@ in vec2 uv; out vec4 color; uniform sampler2D sampler1; +uniform sampler2D sampler2; uniform float scale; uniform int cbMode; -uniform bool mapSDRtoPQ; +uniform int wireTransfer; // 0: sRGB, 1: scRGB, 2: PQ uniform float sdrWhiteLevel; +uniform uint colorTransformFlags; +uniform vec4 colorMatrix[3]; +uniform float colorTransformScalar; + +const uint COLOR_TRANSFORM_MATRIX = 1u; +const uint COLOR_TRANSFORM_LUT = 2u; + +vec3 bt709ToXYZ(vec3 rgb) +{ + return vec3( + dot(rgb, vec3(0.4123908, 0.3575843, 0.1804808)), + dot(rgb, vec3(0.2126390, 0.7151687, 0.0721923)), + dot(rgb, vec3(0.0193308, 0.1191948, 0.9505322))); +} + +vec3 xyzToBT709(vec3 xyz) +{ + return vec3( + dot(xyz, vec3( 3.2409699, -1.5373832, -0.4986108)), + dot(xyz, vec3(-0.9692436, 1.8759675, 0.0415551)), + dot(xyz, vec3( 0.0556301, -0.2039770, 1.0569715))); +} + +vec3 xyzToBT2020(vec3 xyz) +{ + return vec3( + dot(xyz, vec3( 1.7166512, -0.3556708, -0.2533663)), + dot(xyz, vec3(-0.6666844, 1.6164812, 0.0157685)), + dot(xyz, vec3( 0.0176399, -0.0427706, 0.9421031))); +} + +vec3 applyColorLUT(vec3 value) +{ + vec3 pos = clamp(value, 0.0, 1.0) * 4095.0; + ivec3 lo = ivec3(floor(pos)); + ivec3 hi = min(lo + ivec3(1), ivec3(4095)); + vec3 f = fract(pos); + return vec3( + mix(texelFetch(sampler2, ivec2(lo.r, 0), 0).r, + texelFetch(sampler2, ivec2(hi.r, 0), 0).r, f.r), + mix(texelFetch(sampler2, ivec2(lo.g, 0), 0).g, + texelFetch(sampler2, ivec2(hi.g, 0), 0).g, f.g), + mix(texelFetch(sampler2, ivec2(lo.b, 0), 0).b, + texelFetch(sampler2, ivec2(hi.b, 0), 0).b, f.b)); +} void main() { @@ -31,17 +77,35 @@ void main() if (cbMode > 0) color = cbTransform(color, cbMode); - if (mapSDRtoPQ) + if (color.a > 0.0 && (wireTransfer != 0 || colorTransformFlags != 0u)) { - if (color.a > 0.0) + // Cursor pixels are premultiplied sRGB. Work on straight, linear BT.709 + // values through the XYZ calibration stage, encode for the active wire + // format, apply its 1D LUT, then restore premultiplication. + vec3 value = srgb2lin(clamp(color.rgb / color.a, 0.0, 1.0)); + if ((colorTransformFlags & COLOR_TRANSFORM_MATRIX) != 0u) { - // Cursor pixels are premultiplied. Convert the straight color to PQ, - // then premultiply again so the blend operation remains valid. - vec3 srgb = clamp(color.rgb / color.a, 0.0, 1.0); - vec3 linear = bt709to2020(srgb2lin(srgb)); - color.rgb = lin2pq(linear * (sdrWhiteLevel / 10000.0)) * color.a; + vec3 xyz = bt709ToXYZ(value); + xyz = vec3( + dot(vec4(xyz, 1.0), colorMatrix[0]), + dot(vec4(xyz, 1.0), colorMatrix[1]), + dot(vec4(xyz, 1.0), colorMatrix[2])) * colorTransformScalar; + value = wireTransfer == 2 ? xyzToBT2020(xyz) : xyzToBT709(xyz); } + else if (wireTransfer == 2) + value = bt709to2020(value); + + if (wireTransfer == 2) + value = lin2pq(max(value, 0.0) * (sdrWhiteLevel / 10000.0)); + else if (wireTransfer == 1) + value *= sdrWhiteLevel / 80.0; else - color.rgb = vec3(0.0); + value = lin2srgb(max(value, 0.0)); + + if ((colorTransformFlags & COLOR_TRANSFORM_LUT) != 0u) + value = applyColorLUT(value); + color.rgb = value * color.a; } + else if (color.a == 0.0) + color.rgb = vec3(0.0); } diff --git a/client/src/main.c b/client/src/main.c index 2294607d..42c310d1 100644 --- a/client/src/main.c +++ b/client/src/main.c @@ -431,8 +431,11 @@ int main_cursorThread(void * unused) } KVMFRCursor * tmp = (KVMFRCursor *)msg.mem; - const int neededSize = sizeof(*tmp) + - (msg.udata & CURSOR_FLAG_SHAPE ? tmp->height * tmp->pitch : 0); + const int shapeSize = msg.udata & CURSOR_FLAG_SHAPE ? + tmp->height * tmp->pitch : 0; + const int neededSize = sizeof(*tmp) + shapeSize + + (msg.udata & CURSOR_FLAG_COLOR_TRANSFORM ? + sizeof(KVMFRColorTransform) : 0); if (cursor && neededSize > cursorSize) { @@ -488,6 +491,15 @@ int main_cursorThread(void * unused) } } + if ((msg.udata & CURSOR_FLAG_COLOR_TRANSFORM) && + g_state.lgr->ops.onMouseColorTransform) + { + const KVMFRColorTransform * transform = + (const KVMFRColorTransform *)((const uint8_t *)(cursor + 1) + + shapeSize); + g_state.lgr->ops.onMouseColorTransform(g_state.lgr, transform); + } + if (msg.udata & CURSOR_FLAG_POSITION) { bool valid = g_cursor.guest.valid; @@ -517,7 +529,9 @@ int main_cursorThread(void * unused) g_cursor.guest.hy ); - if (g_params.mouseRedraw && g_cursor.guest.visible && !g_state.stopVideo) + if ((g_params.mouseRedraw || + (msg.udata & CURSOR_FLAG_COLOR_TRANSFORM)) && + g_cursor.guest.visible && !g_state.stopVideo) lgSignalEvent(g_state.frameEvent); } diff --git a/common/include/common/KVMFR.h b/common/include/common/KVMFR.h index a3bfabb5..82b1edd5 100644 --- a/common/include/common/KVMFR.h +++ b/common/include/common/KVMFR.h @@ -28,7 +28,7 @@ #include "types.h" #define KVMFR_MAGIC "KVMFR---" -#define KVMFR_VERSION 21 +#define KVMFR_VERSION 22 // Fallback used by producers that cannot report the source display's SDR // white level. IDD frames override this with IDDCX_METADATA2::SdrWhiteLevel. @@ -51,9 +51,10 @@ enum { - CURSOR_FLAG_POSITION = 0x1, - CURSOR_FLAG_VISIBLE = 0x2, - CURSOR_FLAG_SHAPE = 0x4 + CURSOR_FLAG_POSITION = 0x1, + CURSOR_FLAG_VISIBLE = 0x2, + CURSOR_FLAG_SHAPE = 0x4, + CURSOR_FLAG_COLOR_TRANSFORM = 0x8 }; typedef uint32_t KVMFRCursorFlags; @@ -137,6 +138,28 @@ typedef struct KVMFRCursor } KVMFRCursor; +enum +{ + KVMFR_COLOR_TRANSFORM_MATRIX = 0x1, + KVMFR_COLOR_TRANSFORM_LUT = 0x2, +}; + +typedef uint32_t KVMFRColorTransformFlags; + +// Optional payload appended to KVMFRCursor when +// CURSOR_FLAG_COLOR_TRANSFORM is present. The matrix is an XYZ-to-XYZ +// adjustment; the LUT is applied after encoding to the active wire transfer +// function. Four LUT components keep the payload directly uploadable as an +// RGBA32F texture, with alpha reserved and set to 1.0. +typedef struct KVMFRColorTransform +{ + KVMFRColorTransformFlags flags; + float matrix[3][4]; + float scalar; + float lut[4096][4]; +} +KVMFRColorTransform; + enum { FRAME_FLAG_BLOCK_SCREENSAVER = 0x1 , diff --git a/idd/LGIdd/CIndirectDeviceContext.cpp b/idd/LGIdd/CIndirectDeviceContext.cpp index 82577158..a632403e 100644 --- a/idd/LGIdd/CIndirectDeviceContext.cpp +++ b/idd/LGIdd/CIndirectDeviceContext.cpp @@ -868,6 +868,20 @@ bool CIndirectDeviceContext::SetupLGMP(size_t alignSize) memset(lgmpHostMemPtr(m_pointerShapeMemory[i]), 0, MAX_POINTER_SIZE); } + for (int i = 0; i < COLOR_TRANSFORM_BUFFERS; ++i) + { + if ((status = lgmpHostMemAlloc(m_lgmp, + sizeof(KVMFRCursor) + sizeof(KVMFRColorTransform), + &m_pointerTransformMemory[i])) != LGMP_OK) + { + DEBUG_ERROR("lgmpHostMemAlloc Failed (Pointer Transform): %s", + lgmpStatusString(status)); + return false; + } + memset(lgmpHostMemPtr(m_pointerTransformMemory[i]), 0, + sizeof(KVMFRCursor) + sizeof(KVMFRColorTransform)); + } + m_maxFrameSize = lgmpHostMemAvail(m_lgmp); m_maxFrameSize = (m_maxFrameSize -(m_alignSize - 1)) & ~(m_alignSize - 1); m_maxFrameSize /= LGMP_Q_FRAME_LEN; @@ -951,6 +965,9 @@ void CIndirectDeviceContext::DeInitLGMP() lgmpHostMemFree(&m_pointerMemory[i]); for (int i = 0; i < POINTER_SHAPE_BUFFERS; ++i) lgmpHostMemFree(&m_pointerShapeMemory[i]); + + for (int i = 0; i < COLOR_TRANSFORM_BUFFERS; ++i) + lgmpHostMemFree(&m_pointerTransformMemory[i]); lgmpHostFree(&m_lgmp); } @@ -1017,7 +1034,10 @@ void CIndirectDeviceContext::LGMPTimer() } if (lgmpHostQueueNewSubs(m_pointerQueue)) + { ResendCursor(); + SendColorTransform(); + } } bool CIndirectDeviceContext::FrameBufferAvailable() const @@ -1272,6 +1292,24 @@ void CIndirectDeviceContext::SendCursor(const IDARG_OUT_QUERY_HWCURSOR& info, co } } +void CIndirectDeviceContext::SetColorTransform( + std::shared_ptr transform) +{ + AcquireSRWLockExclusive(&m_colorTransformLock); + m_colorTransform = std::move(transform); + ReleaseSRWLockExclusive(&m_colorTransformLock); + SendColorTransform(); +} + +std::shared_ptr +CIndirectDeviceContext::GetColorTransform() const +{ + AcquireSRWLockShared(&m_colorTransformLock); + auto transform = m_colorTransform; + ReleaseSRWLockShared(&m_colorTransformLock); + return transform; +} + #ifdef HAS_IDDCX_110 void CIndirectDeviceContext::SetHDRActive(const struct IDDCX_HDR10_METADATA * hdrMeta) { @@ -1325,7 +1363,49 @@ bool CIndirectDeviceContext::GetHDRMetadata(D12FrameFormat & format) const return true; } -void CIndirectDeviceContext::ResendCursor() const +void CIndirectDeviceContext::SendColorTransform() +{ + if (!m_pointerQueue || !m_pointerTransformMemory[0]) + return; + + PLGMPMemory mem = m_pointerTransformMemory[m_pointerTransformIndex]; + if (++m_pointerTransformIndex == COLOR_TRANSFORM_BUFFERS) + m_pointerTransformIndex = 0; + + KVMFRCursor * cursor = (KVMFRCursor *)lgmpHostMemPtr(mem); + KVMFRColorTransform * output = + (KVMFRColorTransform *)(cursor + 1); + const auto transform = GetColorTransform(); + + output->flags = 0; + if (transform) + { + if (transform->matrixEnabled) + output->flags |= KVMFR_COLOR_TRANSFORM_MATRIX; + if (transform->lutEnabled) + output->flags |= KVMFR_COLOR_TRANSFORM_LUT; + memcpy(output->matrix, transform->matrix, sizeof(output->matrix)); + output->scalar = transform->scalar; + memcpy(output->lut, transform->lut, sizeof(output->lut)); + } + + LGMP_STATUS status; + while ((status = lgmpHostQueuePost(m_pointerQueue, + CURSOR_FLAG_COLOR_TRANSFORM, mem)) != LGMP_OK) + { + if (status == LGMP_ERR_QUEUE_FULL) + { + Sleep(1); + continue; + } + + DEBUG_ERROR("lgmpHostQueuePost Failed (Pointer Transform): %s", + lgmpStatusString(status)); + break; + } +} + +void CIndirectDeviceContext::ResendCursor() { PLGMPMemory mem = m_pointerShape; if (!mem) diff --git a/idd/LGIdd/CIndirectDeviceContext.h b/idd/LGIdd/CIndirectDeviceContext.h index 0dab53b6..563d5459 100644 --- a/idd/LGIdd/CIndirectDeviceContext.h +++ b/idd/LGIdd/CIndirectDeviceContext.h @@ -45,6 +45,7 @@ extern "C" { #endif #define MAX_POINTER_SIZE (sizeof(KVMFRCursor) + (512 * 512 * 4)) +#define COLOR_TRANSFORM_BUFFERS 3 #define POINTER_SHAPE_BUFFERS 3 //FIXME: this should not really be done here, this is a hack @@ -90,9 +91,11 @@ private: PLGMPHostQueue m_pointerQueue = nullptr; PLGMPMemory m_pointerMemory [LGMP_Q_POINTER_LEN ] = {}; PLGMPMemory m_pointerShapeMemory[POINTER_SHAPE_BUFFERS] = {}; + PLGMPMemory m_pointerTransformMemory[COLOR_TRANSFORM_BUFFERS] = {}; PLGMPMemory m_pointerShape = nullptr; int m_pointerMemoryIndex = 0; int m_pointerShapeIndex = 0; + int m_pointerTransformIndex = 0; bool m_cursorVisible = false; int m_cursorX = 0, m_cursorY = 0; @@ -137,6 +140,12 @@ private: bool m_lastHDRActive = false; bool m_lastHDRMetadata = false; + // Windows display calibration transform. The callback publishes immutable + // snapshots so the swap-chain thread never observes a partially updated + // matrix or LUT. + mutable SRWLOCK m_colorTransformLock = SRWLOCK_INIT; + std::shared_ptr m_colorTransform; + void QueryIddCxCapabilities(); bool CanUseIddCx110DDIs() const { return m_canProcessFP16; } @@ -145,7 +154,8 @@ private: void DeInitLGMP(); void LGMPTimer(); - void ResendCursor() const; + void ResendCursor(); + void SendColorTransform(); void InitializeEdid(); // Guards m_displayModes and m_edid. The mode list is rebuilt on the LGMP @@ -220,6 +230,9 @@ public: // Tracks HDR state from EvtIddCxMonitorSetDefaultHdrMetadata void SetHDRActive(const struct IDDCX_HDR10_METADATA * hdrMeta); + void SetColorTransform(std::shared_ptr transform); + std::shared_ptr GetColorTransform() const; + // Returns true if the display is currently in HDR mode bool IsHDRActive() const { diff --git a/idd/LGIdd/CPostProcessor.cpp b/idd/LGIdd/CPostProcessor.cpp index 2343541a..11fd2ae9 100644 --- a/idd/LGIdd/CPostProcessor.cpp +++ b/idd/LGIdd/CPostProcessor.cpp @@ -13,6 +13,7 @@ #include "CD3D12Device.h" #include "CDebug.h" +#include "effect/CColorTransformEffect.h" #include "effect/CDownsampleEffect.h" #include "effect/CHDR16to10Effect.h" #include "effect/CRGB24Effect.h" @@ -38,6 +39,15 @@ bool CPostProcessor::Init(std::shared_ptr dx12Device) m_device = dx12Device->GetDevice(); m_effects.clear(); + std::unique_ptr colorTransform(new CColorTransformEffect()); + if (colorTransform->Init(m_device)) + { + DEBUG_INFO("Created post-processing effect: %s", colorTransform->GetName()); + m_effects.push_back(std::move(colorTransform)); + } + else + return false; + std::unique_ptr downsample(new CDownsampleEffect()); if (downsample->Init(m_device)) { @@ -86,7 +96,8 @@ bool CPostProcessor::Configure(const D12FrameFormat& srcFormat, bool * formatCha srcFormat.width == m_srcFormat.width && srcFormat.height == m_srcFormat.height && srcFormat.hdr == m_srcFormat.hdr && - srcFormat.hdrPQ == m_srcFormat.hdrPQ) + srcFormat.hdrPQ == m_srcFormat.hdrPQ && + srcFormat.colorTransform == m_srcFormat.colorTransform) { // Static HDR metadata may change independently of the resource format. // Propagate it without recreating textures or post-processing state. @@ -132,7 +143,8 @@ bool CPostProcessor::Configure(const D12FrameFormat& srcFormat, bool * formatCha oldDst.height != m_dstFormat.height || oldDst.hdr != m_dstFormat.hdr || oldDst.hdrPQ != m_dstFormat.hdrPQ || - oldDst.sdrWhiteLevel != m_dstFormat.sdrWhiteLevel; + oldDst.sdrWhiteLevel != m_dstFormat.sdrWhiteLevel || + oldDst.colorTransform != m_dstFormat.colorTransform; return true; } diff --git a/idd/LGIdd/CPostProcessor.h b/idd/LGIdd/CPostProcessor.h index 60d4cdc7..a50568be 100644 --- a/idd/LGIdd/CPostProcessor.h +++ b/idd/LGIdd/CPostProcessor.h @@ -34,6 +34,15 @@ enum class PostProcessStatus FAILED }; +struct D12ColorTransform +{ + bool matrixEnabled = false; + float matrix[3][4] = {}; + float scalar = 1.0f; + bool lutEnabled = false; + float lut[4096][4] = {}; +}; + struct D12FrameFormat { D3D12_RESOURCE_DESC desc = {}; @@ -44,6 +53,7 @@ struct D12FrameFormat bool hdrPQ = false; bool hdrMetadata = false; uint32_t sdrWhiteLevel = KVMFR_SDR_WHITE_LEVEL_DEFAULT; + std::shared_ptr colorTransform; // HDR static metadata (SMPTE ST 2086) // Display color primaries in 0.00002 units (xy coordinates) diff --git a/idd/LGIdd/CSwapChainProcessor.cpp b/idd/LGIdd/CSwapChainProcessor.cpp index 1729f5e6..dfb853c4 100644 --- a/idd/LGIdd/CSwapChainProcessor.cpp +++ b/idd/LGIdd/CSwapChainProcessor.cpp @@ -538,6 +538,7 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr acquiredBuffer srcFormat.height = srcDesc.Height; srcFormat.format = GetFrameType(srcDesc.Format); srcFormat.sdrWhiteLevel = sdrWhiteLevel; + srcFormat.colorTransform = m_devContext->GetColorTransform(); switch (colorSpace) { diff --git a/idd/LGIdd/Device.cpp b/idd/LGIdd/Device.cpp index 8f0f242f..765cc874 100644 --- a/idd/LGIdd/Device.cpp +++ b/idd/LGIdd/Device.cpp @@ -28,6 +28,8 @@ #include #include #include +#include +#include #include "CDebug.h" #include "CIndirectDeviceContext.h" @@ -192,8 +194,51 @@ NTSTATUS LGIddMonitorSetDefaultHdrMetadata(IDDCX_MONITOR monitor, NTSTATUS LGIddMonitorSetGammaRamp(IDDCX_MONITOR monitor, const IDARG_IN_SET_GAMMARAMP* inArgs) { - UNREFERENCED_PARAMETER(monitor); - UNREFERENCED_PARAMETER(inArgs); + auto* wrapper = WdfObjectGet_CIndirectMonitorContextWrapper(monitor); + auto* ctx = wrapper->context->GetDeviceContext(); + + if (inArgs->Type == IDDCX_GAMMARAMP_TYPE_DEFAULT) + { + ctx->SetColorTransform(nullptr); + DEBUG_INFO("Display color transform reset to default"); + return STATUS_SUCCESS; + } + + if (inArgs->Type != IDDCX_GAMMARAMP_TYPE_3x4_COLORSPACE_TRANSFORM) + { + DEBUG_WARN("Unsupported gamma ramp type %u", inArgs->Type); + return STATUS_NOT_SUPPORTED; + } + + if (!inArgs->pGammaRampData || + inArgs->GammaRampSizeInBytes < + sizeof(IDDCX_GAMMARAMP_3X4_COLORSPACE_TRANSFORM)) + { + DEBUG_ERROR("Invalid 3x4 color transform buffer (%u bytes)", + inArgs->GammaRampSizeInBytes); + return STATUS_INVALID_PARAMETER; + } + + const auto* input = static_cast< + const IDDCX_GAMMARAMP_3X4_COLORSPACE_TRANSFORM*>( + inArgs->pGammaRampData); + auto transform = std::make_shared(); + transform->matrixEnabled = !!input->MatrixEnabled; + transform->scalar = input->ScalarMultiplier; + transform->lutEnabled = !!input->LutEnabled; + memcpy(transform->matrix, input->ColorMatrix3x4, + sizeof(transform->matrix)); + for (unsigned i = 0; i < 4096; ++i) + { + transform->lut[i][0] = input->LookupTable1D[i].Red; + transform->lut[i][1] = input->LookupTable1D[i].Green; + transform->lut[i][2] = input->LookupTable1D[i].Blue; + transform->lut[i][3] = 1.0f; + } + + ctx->SetColorTransform(std::move(transform)); + DEBUG_INFO("Display color transform updated (matrix:%d lut:%d)", + input->MatrixEnabled, input->LutEnabled); return STATUS_SUCCESS; } diff --git a/idd/LGIdd/LGIdd.vcxproj b/idd/LGIdd/LGIdd.vcxproj index 1f18b16f..9f17e34e 100644 --- a/idd/LGIdd/LGIdd.vcxproj +++ b/idd/LGIdd/LGIdd.vcxproj @@ -37,6 +37,7 @@ + @@ -61,6 +62,7 @@ + @@ -274,4 +276,4 @@ $(BuildDependsOn); - \ No newline at end of file + diff --git a/idd/LGIdd/LGIdd.vcxproj.filters b/idd/LGIdd/LGIdd.vcxproj.filters index a8450d9c..1bcf7aca 100644 --- a/idd/LGIdd/LGIdd.vcxproj.filters +++ b/idd/LGIdd/LGIdd.vcxproj.filters @@ -94,6 +94,9 @@ Header Files + + Header Files + Header Files\effect @@ -163,6 +166,9 @@ Source Files + + Source Files + Source Files\effect diff --git a/idd/LGIdd/effect/CColorTransformEffect.cpp b/idd/LGIdd/effect/CColorTransformEffect.cpp new file mode 100644 index 00000000..682d26ac --- /dev/null +++ b/idd/LGIdd/effect/CColorTransformEffect.cpp @@ -0,0 +1,328 @@ +/** + * 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. + */ + +#include "CColorTransformEffect.h" + +#include "CDebug.h" + +#include + +using namespace PostProcessUtil; + +namespace +{ + enum TransferFunction : UINT + { + TRANSFER_LINEAR, + TRANSFER_SRGB, + TRANSFER_PQ, + }; + + bool CreateUploadBuffer(const ComPtr& device, size_t size, + ComPtr& resource) + { + D3D12_HEAP_PROPERTIES heapProps = {}; + heapProps.Type = D3D12_HEAP_TYPE_UPLOAD; + + D3D12_RESOURCE_DESC desc = {}; + desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + desc.Width = size; + desc.Height = 1; + desc.DepthOrArraySize = 1; + desc.MipLevels = 1; + desc.SampleDesc.Count = 1; + desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + + const HRESULT hr = device->CreateCommittedResource(&heapProps, + D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_GENERIC_READ, + nullptr, IID_PPV_ARGS(&resource)); + return SUCCEEDED(hr); + } + + bool Upload(const ComPtr& resource, + const void * data, size_t size) + { + void * dst = nullptr; + const D3D12_RANGE readRange = { 0, 0 }; + if (FAILED(resource->Map(0, &readRange, &dst))) + return false; + std::memcpy(dst, data, size); + resource->Unmap(0, nullptr); + return true; + } +} + +bool CColorTransformEffect::Init(const ComPtr& device) +{ + D3D12_DESCRIPTOR_RANGE ranges[4] = {}; + ranges[0].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_CBV; + ranges[0].NumDescriptors = 1; + ranges[0].BaseShaderRegister = 0; + ranges[0].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND; + ranges[1].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV; + ranges[1].NumDescriptors = 1; + ranges[1].BaseShaderRegister = 0; + ranges[1].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND; + ranges[2].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV; + ranges[2].NumDescriptors = 1; + ranges[2].BaseShaderRegister = 1; + ranges[2].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND; + ranges[3].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_UAV; + ranges[3].NumDescriptors = 1; + ranges[3].BaseShaderRegister = 0; + ranges[3].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND; + + const char * shader = + "cbuffer Constants : register(b0)\n" + "{\n" + " float4 ColorMatrix[3];\n" + " float Scalar;\n" + " uint MatrixEnabled;\n" + " uint LutEnabled;\n" + " uint InputTransfer;\n" + " uint OutputTransfer;\n" + "};\n" + "Texture2D src : register(t0);\n" + "Buffer lut : register(t1);\n" + "RWTexture2D dst : register(u0);\n" + "static const float PQ_m1 = 0.1593017578125;\n" + "static const float PQ_m2 = 78.84375;\n" + "static const float PQ_c1 = 0.8359375;\n" + "static const float PQ_c2 = 18.8515625;\n" + "static const float PQ_c3 = 18.6875;\n" + "float3 decode(float3 value)\n" + "{\n" + " if (InputTransfer == 1)\n" + " return lerp(value / 12.92, pow((value + 0.055) / 1.055, 2.4),\n" + " step(0.04045, value));\n" + " if (InputTransfer == 2)\n" + " {\n" + " float3 p = pow(max(value, 0.0), 1.0 / PQ_m2);\n" + " return pow(max(p - PQ_c1, 0.0) / max(PQ_c2 - PQ_c3 * p, 1e-6),\n" + " 1.0 / PQ_m1);\n" + " }\n" + " return value;\n" + "}\n" + "float3 encode(float3 value)\n" + "{\n" + " if (OutputTransfer == 1)\n" + " return lerp(value * 12.92, 1.055 * pow(max(value, 0.0), 1.0 / 2.4) - 0.055,\n" + " step(0.0031308, value));\n" + " if (OutputTransfer == 2)\n" + " {\n" + " float3 p = pow(max(value, 0.0), PQ_m1);\n" + " return pow((PQ_c1 + PQ_c2 * p) / (1.0 + PQ_c3 * p), PQ_m2);\n" + " }\n" + " return value;\n" + "}\n" + "float3 rgbToXYZ(float3 rgb)\n" + "{\n" + " if (InputTransfer == 2)\n" + " return float3(\n" + " dot(rgb, float3(0.6369580, 0.1446169, 0.1688810)),\n" + " dot(rgb, float3(0.2627002, 0.6779981, 0.0593017)),\n" + " dot(rgb, float3(0.0000000, 0.0280727, 1.0609851)));\n" + " return float3(\n" + " dot(rgb, float3(0.4123908, 0.3575843, 0.1804808)),\n" + " dot(rgb, float3(0.2126390, 0.7151687, 0.0721923)),\n" + " dot(rgb, float3(0.0193308, 0.1191948, 0.9505322)));\n" + "}\n" + "float3 xyzToRGB(float3 xyz)\n" + "{\n" + " if (OutputTransfer == 2)\n" + " return float3(\n" + " dot(xyz, float3( 1.7166512, -0.3556708, -0.2533663)),\n" + " dot(xyz, float3(-0.6666844, 1.6164812, 0.0157685)),\n" + " dot(xyz, float3( 0.0176399, -0.0427706, 0.9421031)));\n" + " return float3(\n" + " dot(xyz, float3( 3.2409699, -1.5373832, -0.4986108)),\n" + " dot(xyz, float3(-0.9692436, 1.8759675, 0.0415551)),\n" + " dot(xyz, float3( 0.0556301, -0.2039770, 1.0569715)));\n" + "}\n" + "float3 applyLut(float3 value)\n" + "{\n" + " float3 pos = saturate(value) * 4095.0;\n" + " uint3 lo = (uint3)floor(pos);\n" + " uint3 hi = min(lo + 1, 4095);\n" + " float3 f = frac(pos);\n" + " return float3(\n" + " lerp(lut[lo.r].r, lut[hi.r].r, f.r),\n" + " lerp(lut[lo.g].g, lut[hi.g].g, f.g),\n" + " lerp(lut[lo.b].b, lut[hi.b].b, f.b));\n" + "}\n" + "[numthreads(" POST_PROCESS_THREADS_STR ", " POST_PROCESS_THREADS_STR ", 1)]\n" + "void main(uint3 dt : SV_DispatchThreadID)\n" + "{\n" + " float4 pixel = src[dt.xy];\n" + " float3 value = decode(pixel.rgb);\n" + " if (MatrixEnabled != 0 || InputTransfer != OutputTransfer)\n" + " {\n" + " float3 xyz = rgbToXYZ(value);\n" + " if (MatrixEnabled != 0)\n" + " xyz = float3(dot(float4(xyz, 1.0), ColorMatrix[0]),\n" + " dot(float4(xyz, 1.0), ColorMatrix[1]),\n" + " dot(float4(xyz, 1.0), ColorMatrix[2])) * Scalar;\n" + " value = xyzToRGB(xyz);\n" + " }\n" + " if (InputTransfer == 0 && OutputTransfer == 2)\n" + " value *= 80.0 / 10000.0;\n" + " value = encode(value);\n" + " if (LutEnabled != 0)\n" + " value = applyLut(value);\n" + " dst[dt.xy] = float4(value, pixel.a);\n" + "}\n"; + + if (!InitCompute(device, ranges, ARRAYSIZE(ranges), nullptr, 0, shader)) + return false; + + const size_t constSize = AlignTo(sizeof(m_consts), + (size_t)D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT); + if (!CreateUploadBuffer(device, constSize, m_constBuffer) || + !CreateUploadBuffer(device, sizeof(float) * 4096 * 4, m_lutBuffer)) + { + DEBUG_ERROR("Failed to create color transform buffers"); + return false; + } + + return true; +} + +PostProcessStatus CColorTransformEffect::SetFormat( + const ComPtr& device, + const D12FrameFormat& src, D12FrameFormat& dst) +{ + if (!src.colorTransform || + (!src.colorTransform->matrixEnabled && !src.colorTransform->lutEnabled)) + return PostProcessStatus::BYPASS_EFFECT; + + DXGI_FORMAT dstFormat; + FrameType frameType; + switch (src.desc.Format) + { + case DXGI_FORMAT_B8G8R8A8_UNORM: + dstFormat = DXGI_FORMAT_R8G8B8A8_UNORM; + frameType = FRAME_TYPE_RGBA; + break; + case DXGI_FORMAT_R8G8B8A8_UNORM: + case DXGI_FORMAT_R10G10B10A2_UNORM: + dstFormat = src.desc.Format; + frameType = src.format; + break; + case DXGI_FORMAT_R16G16B16A16_FLOAT: + // The client wire format is HDR10. Perform the XYZ adjustment before + // the BT.2020 rotation, and its LUT after PQ encoding, in one pass. + dstFormat = DXGI_FORMAT_R10G10B10A2_UNORM; + frameType = FRAME_TYPE_RGBA10; + break; + default: + DEBUG_ERROR("Unsupported color transform source format %u", src.desc.Format); + return PostProcessStatus::FAILED; + } + + D3D12_RESOURCE_DESC desc = src.desc; + desc.Format = dstFormat; + desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; + if (!m_dst || m_dst->GetDesc().Width != desc.Width || + m_dst->GetDesc().Height != desc.Height || + m_dst->GetDesc().Format != desc.Format) + { + if (!CreateDefaultTexture(device, desc, m_dst)) + return PostProcessStatus::FAILED; + } + + std::memcpy(m_consts.matrix, src.colorTransform->matrix, + sizeof(m_consts.matrix)); + m_consts.scalar = src.colorTransform->scalar; + m_consts.matrixEnabled = src.colorTransform->matrixEnabled; + m_consts.lutEnabled = src.colorTransform->lutEnabled; + m_consts.inputTransfer = src.hdrPQ ? TRANSFER_PQ : + (src.hdr ? TRANSFER_LINEAR : TRANSFER_SRGB); + m_consts.outputTransfer = src.hdr ? TRANSFER_PQ : TRANSFER_SRGB; + + std::memcpy(m_lut, src.colorTransform->lut, sizeof(m_lut)); + m_uploadPending = true; + + m_srcFormat = src.desc.Format; + m_dstFormat = dstFormat; + m_threadsX = ((unsigned)desc.Width + (Threads - 1)) / Threads; + m_threadsY = ((unsigned)desc.Height + (Threads - 1)) / Threads; + + dst.desc = desc; + dst.format = frameType; + if (src.hdr) + dst.hdrPQ = true; + return PostProcessStatus::SUCCESS; +} + +ComPtr CColorTransformEffect::Run( + const ComPtr& device, + const ComPtr& commandList, + const ComPtr& src, RECT dirtyRects[], + unsigned * nbDirtyRects) +{ + UNREFERENCED_PARAMETER(dirtyRects); + UNREFERENCED_PARAMETER(nbDirtyRects); + + // GetComputeQueue waits for the previous submission before Run is called, + // so this is the first point where the shared upload buffers are guaranteed + // not to be in use by the GPU. + if (m_uploadPending) + { + if (!Upload(m_constBuffer, &m_consts, sizeof(m_consts)) || + !Upload(m_lutBuffer, m_lut, sizeof(m_lut))) + DEBUG_ERROR("Failed to upload display color transform"); + else + m_uploadPending = false; + } + + TransitionDst(commandList, D3D12_RESOURCE_STATE_COPY_SOURCE, + D3D12_RESOURCE_STATE_UNORDERED_ACCESS); + + D3D12_CPU_DESCRIPTOR_HANDLE handle = + m_descHeap->GetCPUDescriptorHandleForHeapStart(); + const UINT inc = device->GetDescriptorHandleIncrementSize( + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + + D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc = {}; + cbvDesc.BufferLocation = m_constBuffer->GetGPUVirtualAddress(); + cbvDesc.SizeInBytes = (UINT)AlignTo(sizeof(m_consts), + (size_t)D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT); + device->CreateConstantBufferView(&cbvDesc, handle); + handle.ptr += inc; + + D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {}; + srvDesc.Format = m_srcFormat; + srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; + srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + srvDesc.Texture2D.MipLevels = 1; + device->CreateShaderResourceView(src.Get(), &srvDesc, handle); + handle.ptr += inc; + + D3D12_SHADER_RESOURCE_VIEW_DESC lutDesc = {}; + lutDesc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT; + lutDesc.ViewDimension = D3D12_SRV_DIMENSION_BUFFER; + lutDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + lutDesc.Buffer.NumElements = 4096; + device->CreateShaderResourceView(m_lutBuffer.Get(), &lutDesc, handle); + handle.ptr += inc; + + D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc = {}; + uavDesc.Format = m_dstFormat; + uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D; + device->CreateUnorderedAccessView(m_dst.Get(), nullptr, &uavDesc, handle); + + Bind(commandList); + commandList->Dispatch(m_threadsX, m_threadsY, 1); + + TransitionDst(commandList, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, + D3D12_RESOURCE_STATE_COPY_SOURCE); + return m_dst; +} diff --git a/idd/LGIdd/effect/CColorTransformEffect.h b/idd/LGIdd/effect/CColorTransformEffect.h new file mode 100644 index 00000000..d6cbb2d1 --- /dev/null +++ b/idd/LGIdd/effect/CColorTransformEffect.h @@ -0,0 +1,48 @@ +/** + * 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. + */ + +#pragma once + +#include "CComputeEffect.h" + +class CColorTransformEffect : public CComputeEffect +{ +private: + struct Consts + { + float matrix[3][4]; + float scalar; + UINT matrixEnabled; + UINT lutEnabled; + UINT inputTransfer; + UINT outputTransfer; + } m_consts = {}; + float m_lut[4096][4] = {}; + bool m_uploadPending = false; + + ComPtr m_constBuffer; + ComPtr m_lutBuffer; + DXGI_FORMAT m_srcFormat = DXGI_FORMAT_UNKNOWN; + DXGI_FORMAT m_dstFormat = DXGI_FORMAT_UNKNOWN; + +public: + const char * GetName() const override { return "ColorTransform"; } + + bool Init(const ComPtr& device); + + PostProcessStatus SetFormat(const ComPtr& device, + const D12FrameFormat& src, D12FrameFormat& dst) override; + + ComPtr Run(const ComPtr& device, + const ComPtr& commandList, + const ComPtr& src, RECT dirtyRects[], + unsigned * nbDirtyRects) override; +};