mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-07-20 22:32:01 +00:00
[client] egl: implement SDR to HDR mapping for cursors
This commit is contained in:
@@ -31,6 +31,7 @@
|
|||||||
#include <stdatomic.h>
|
#include <stdatomic.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
// these headers are auto generated by cmake
|
// these headers are auto generated by cmake
|
||||||
#include "cursor.vert.h"
|
#include "cursor.vert.h"
|
||||||
@@ -73,6 +74,10 @@ struct EGL_Cursor
|
|||||||
LG_RendererRotate rotate;
|
LG_RendererRotate rotate;
|
||||||
int cbMode;
|
int cbMode;
|
||||||
|
|
||||||
|
// HDR state for native compositor HDR with PQ
|
||||||
|
bool hdrActive;
|
||||||
|
bool hdrPQ;
|
||||||
|
|
||||||
_Atomic(struct CursorPos) pos;
|
_Atomic(struct CursorPos) pos;
|
||||||
_Atomic(struct CursorPos) hs;
|
_Atomic(struct CursorPos) hs;
|
||||||
_Atomic(struct CursorSize) size;
|
_Atomic(struct CursorSize) size;
|
||||||
@@ -83,6 +88,58 @@ struct EGL_Cursor
|
|||||||
struct EGL_Model * model;
|
struct EGL_Model * model;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// PQ constants (ST.2084)
|
||||||
|
#define PQ_M1 (2610.0f / 16384.0f)
|
||||||
|
#define PQ_M2 (2523.0f / 32.0f)
|
||||||
|
#define PQ_C1 (3424.0f / 4096.0f)
|
||||||
|
#define PQ_C2 (2413.0f / 128.0f)
|
||||||
|
#define PQ_C3 (2392.0f / 128.0f)
|
||||||
|
#define SDR_WHITE_NITS 203.0f
|
||||||
|
|
||||||
|
static float srgb2lin_f(float c)
|
||||||
|
{
|
||||||
|
c /= 255.0f;
|
||||||
|
if (c <= 0.04045f)
|
||||||
|
return c / 12.92f;
|
||||||
|
return powf((c + 0.055f) / 1.055f, 2.4f);
|
||||||
|
}
|
||||||
|
|
||||||
|
static float lin2pq_f(float nits)
|
||||||
|
{
|
||||||
|
float y = powf(nits / 10000.0f, PQ_M1);
|
||||||
|
float p = (PQ_C1 + PQ_C2 * y) / (1.0f + PQ_C3 * y);
|
||||||
|
return powf(p, PQ_M2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void convertCursorDataToPQ(uint8_t * data, int width, int height,
|
||||||
|
int stride)
|
||||||
|
{
|
||||||
|
for (int y = 0; y < height; ++y)
|
||||||
|
{
|
||||||
|
uint8_t * row = data + (size_t)stride * y;
|
||||||
|
for (int x = 0; x < width; ++x)
|
||||||
|
{
|
||||||
|
uint8_t * px = row + (size_t)x * 4;
|
||||||
|
// BGRA order: px[0]=B, px[1]=G, px[2]=R, px[3]=A
|
||||||
|
float b_lin = srgb2lin_f(px[0]);
|
||||||
|
float g_lin = srgb2lin_f(px[1]);
|
||||||
|
float r_lin = srgb2lin_f(px[2]);
|
||||||
|
|
||||||
|
float b_nits = b_lin * SDR_WHITE_NITS;
|
||||||
|
float g_nits = g_lin * SDR_WHITE_NITS;
|
||||||
|
float r_nits = r_lin * SDR_WHITE_NITS;
|
||||||
|
|
||||||
|
float b_pq = lin2pq_f(b_nits);
|
||||||
|
float g_pq = lin2pq_f(g_nits);
|
||||||
|
float r_pq = lin2pq_f(r_nits);
|
||||||
|
|
||||||
|
px[0] = (uint8_t)(b_pq * 255.0f + 0.5f);
|
||||||
|
px[1] = (uint8_t)(g_pq * 255.0f + 0.5f);
|
||||||
|
px[2] = (uint8_t)(r_pq * 255.0f + 0.5f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static bool cursorTexInit(struct CursorTex * t,
|
static bool cursorTexInit(struct CursorTex * t,
|
||||||
const char * vertex_code , size_t vertex_size,
|
const char * vertex_code , size_t vertex_size,
|
||||||
const char * fragment_code, size_t fragment_size)
|
const char * fragment_code, size_t fragment_size)
|
||||||
@@ -256,6 +313,7 @@ struct CursorState egl_cursorRender(EGL_Cursor * cursor,
|
|||||||
cursor->update = false;
|
cursor->update = false;
|
||||||
|
|
||||||
uint8_t * data = cursor->data;
|
uint8_t * data = cursor->data;
|
||||||
|
bool needsPQ = cursor->hdrActive && cursor->hdrPQ;
|
||||||
|
|
||||||
switch(cursor->type)
|
switch(cursor->type)
|
||||||
{
|
{
|
||||||
@@ -284,6 +342,13 @@ struct CursorState egl_cursorRender(EGL_Cursor * cursor,
|
|||||||
|
|
||||||
case LG_CURSOR_COLOR:
|
case LG_CURSOR_COLOR:
|
||||||
{
|
{
|
||||||
|
// Convert color data from sRGB to PQ when compositor HDR PQ is active.
|
||||||
|
// This is done CPU-side because the shader also handles mask textures
|
||||||
|
// where PQ conversion would break the GL_ZERO/GL_SRC_COLOR blend math.
|
||||||
|
if (needsPQ)
|
||||||
|
convertCursorDataToPQ(data, cursor->width, cursor->height,
|
||||||
|
cursor->stride);
|
||||||
|
|
||||||
egl_textureSetup(cursor->norm.texture, EGL_PF_BGRA,
|
egl_textureSetup(cursor->norm.texture, EGL_PF_BGRA,
|
||||||
cursor->width, cursor->height, cursor->width, cursor->stride);
|
cursor->width, cursor->height, cursor->width, cursor->stride);
|
||||||
egl_textureUpdate(cursor->norm.texture, data, true);
|
egl_textureUpdate(cursor->norm.texture, data, true);
|
||||||
@@ -310,6 +375,7 @@ struct CursorState egl_cursorRender(EGL_Cursor * cursor,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Monochrome cursors use AND/XOR mask textures - never convert to PQ
|
||||||
egl_textureSetup(cursor->norm.texture, EGL_PF_BGRA,
|
egl_textureSetup(cursor->norm.texture, EGL_PF_BGRA,
|
||||||
cursor->width, cursor->height, cursor->width, sizeof(and[0]));
|
cursor->width, cursor->height, cursor->width, sizeof(and[0]));
|
||||||
egl_textureSetup(cursor->mono.texture, EGL_PF_BGRA,
|
egl_textureSetup(cursor->mono.texture, EGL_PF_BGRA,
|
||||||
@@ -429,3 +495,14 @@ struct CursorState egl_cursorRender(EGL_Cursor * cursor,
|
|||||||
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void egl_cursorSetHDRState(EGL_Cursor * cursor, bool hdrActive, bool hdrPQ)
|
||||||
|
{
|
||||||
|
if (cursor->hdrActive != hdrActive || cursor->hdrPQ != hdrPQ)
|
||||||
|
{
|
||||||
|
cursor->hdrActive = hdrActive;
|
||||||
|
cursor->hdrPQ = hdrPQ;
|
||||||
|
// Force a re-upload of the cursor texture with the new color space
|
||||||
|
cursor->update = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -52,3 +52,5 @@ void egl_cursorSetState(EGL_Cursor * cursor, const bool visible,
|
|||||||
|
|
||||||
struct CursorState egl_cursorRender(EGL_Cursor * cursor,
|
struct CursorState egl_cursorRender(EGL_Cursor * cursor,
|
||||||
LG_RendererRotate rotate, int width, int height);
|
LG_RendererRotate rotate, int width, int height);
|
||||||
|
|
||||||
|
void egl_cursorSetHDRState(EGL_Cursor * cursor, bool hdrActive, bool hdrPQ);
|
||||||
|
|||||||
@@ -627,8 +627,11 @@ static bool egl_onFrameFormat(LG_Renderer * renderer, const LG_RendererFormat fo
|
|||||||
// (e.g., compositor missing required primaries), keep tone-mapping on.
|
// (e.g., compositor missing required primaries), keep tone-mapping on.
|
||||||
bool nativeHDR = false;
|
bool nativeHDR = false;
|
||||||
app_getProp(LG_DS_NATIVE_HDR, &nativeHDR);
|
app_getProp(LG_DS_NATIVE_HDR, &nativeHDR);
|
||||||
egl_desktopSetNativeHDR(this->desktop,
|
bool useNativeHDR = format.hdr && nativeHDR && !app_getHDRDescFailed();
|
||||||
format.hdr && nativeHDR && !app_getHDRDescFailed());
|
egl_desktopSetNativeHDR(this->desktop, useNativeHDR);
|
||||||
|
|
||||||
|
// Tell the cursor shader about HDR state so it can do SDR→PQ conversion
|
||||||
|
egl_cursorSetHDRState(this->cursor, useNativeHDR, format.hdrPQ);
|
||||||
|
|
||||||
egl_update_scale_type(this);
|
egl_update_scale_type(this);
|
||||||
egl_damageSetup(this->damage, format.frameWidth, format.frameHeight);
|
egl_damageSetup(this->damage, format.frameWidth, format.frameHeight);
|
||||||
|
|||||||
Reference in New Issue
Block a user