[client] egl: improve HDR cursor rendering, fixes alpha blending

This commit is contained in:
Geoffrey McRae
2026-07-17 16:48:57 +10:00
parent 04441bfd85
commit d41c9fdc5d
4 changed files with 53 additions and 86 deletions

View File

@@ -31,7 +31,6 @@
#include <stdatomic.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
// these headers are auto generated by cmake
#include "cursor.vert.h"
@@ -46,6 +45,7 @@ struct CursorTex
GLuint uScale;
GLuint uRotate;
GLuint uCBMode;
GLint uMapSDRtoPQ;
};
struct CursorPos
@@ -74,72 +74,17 @@ struct EGL_Cursor
LG_RendererRotate rotate;
int cbMode;
// HDR state for native compositor HDR with PQ
bool hdrActive;
bool hdrPQ;
_Atomic(struct CursorPos) pos;
_Atomic(struct CursorPos) hs;
_Atomic(struct CursorSize) size;
_Atomic(float) scale;
_Atomic(bool) mapSDRtoPQ;
struct CursorTex norm;
struct CursorTex mono;
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,
const char * vertex_code , size_t vertex_size,
const char * fragment_code, size_t fragment_size)
@@ -163,10 +108,11 @@ static bool cursorTexInit(struct CursorTex * t,
return false;
}
t->uMousePos = egl_shaderGetUniform(t->shader, "mouse" );
t->uScale = egl_shaderGetUniform(t->shader, "scale" );
t->uRotate = egl_shaderGetUniform(t->shader, "rotate" );
t->uCBMode = egl_shaderGetUniform(t->shader, "cbMode" );
t->uMousePos = egl_shaderGetUniform(t->shader, "mouse" );
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");
return true;
}
@@ -175,10 +121,11 @@ static inline void setCursorTexUniforms(EGL_Cursor * cursor,
struct CursorTex * t, bool mono, float x, float y,
float w, float h, float scale)
{
glUniform4f(t->uMousePos, x, y, w, mono ? h / 2 : h);
glUniform1f(t->uScale , scale);
glUniform1i(t->uRotate , cursor->rotate);
glUniform1i(t->uCBMode , cursor->cbMode);
glUniform4f(t->uMousePos , x, y, w, mono ? h / 2 : h);
glUniform1f(t->uScale , scale);
glUniform1i(t->uRotate , cursor->rotate);
glUniform1i(t->uCBMode , cursor->cbMode);
glUniform1i(t->uMapSDRtoPQ, !mono && atomic_load(&cursor->mapSDRtoPQ));
}
static void cursorTexFree(struct CursorTex * t)
@@ -222,10 +169,11 @@ bool egl_cursorInit(EGL_Cursor ** cursor)
struct CursorPos pos = { .x = 0, .y = 0 };
struct CursorPos hs = { .x = 0, .y = 0 };
struct CursorSize size = { .w = 0, .h = 0 };
atomic_init(&(*cursor)->pos , pos );
atomic_init(&(*cursor)->hs , hs );
atomic_init(&(*cursor)->size , size);
atomic_init(&(*cursor)->scale, 1.0f);
atomic_init(&(*cursor)->pos , pos );
atomic_init(&(*cursor)->hs , hs );
atomic_init(&(*cursor)->size , size );
atomic_init(&(*cursor)->scale , 1.0f );
atomic_init(&(*cursor)->mapSDRtoPQ, false);
return true;
}
@@ -313,8 +261,6 @@ struct CursorState egl_cursorRender(EGL_Cursor * cursor,
cursor->update = false;
uint8_t * data = cursor->data;
bool needsPQ = cursor->hdrActive && cursor->hdrPQ;
switch(cursor->type)
{
case LG_CURSOR_MASKED_COLOR:
@@ -342,13 +288,6 @@ struct CursorState egl_cursorRender(EGL_Cursor * cursor,
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,
cursor->width, cursor->height, cursor->width, cursor->stride);
egl_textureUpdate(cursor->norm.texture, data, true);
@@ -498,11 +437,5 @@ struct CursorState egl_cursorRender(EGL_Cursor * cursor,
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;
}
atomic_store(&cursor->mapSDRtoPQ, hdrActive && hdrPQ);
}