mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-07-20 14:22:00 +00:00
[client] egl: improve HDR cursor rendering, fixes alpha blending
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -630,7 +630,7 @@ static bool egl_onFrameFormat(LG_Renderer * renderer, const LG_RendererFormat fo
|
||||
bool useNativeHDR = format.hdr && nativeHDR && !app_getHDRDescFailed();
|
||||
egl_desktopSetNativeHDR(this->desktop, useNativeHDR);
|
||||
|
||||
// Tell the cursor shader about HDR state so it can do SDR→PQ conversion
|
||||
// Tell the cursor shader whether to map SDR cursor colors into PQ
|
||||
egl_cursorSetHDRState(this->cursor, useNativeHDR, format.hdrPQ);
|
||||
|
||||
egl_update_scale_type(this);
|
||||
|
||||
@@ -3,6 +3,7 @@ precision highp float;
|
||||
precision highp int;
|
||||
|
||||
#include "color_blind.h"
|
||||
#include "hdr.h"
|
||||
|
||||
in vec2 uv;
|
||||
out vec4 color;
|
||||
@@ -10,6 +11,7 @@ out vec4 color;
|
||||
uniform sampler2D sampler1;
|
||||
uniform float scale;
|
||||
uniform int cbMode;
|
||||
uniform bool mapSDRtoPQ;
|
||||
|
||||
void main()
|
||||
{
|
||||
@@ -27,4 +29,18 @@ void main()
|
||||
|
||||
if (cbMode > 0)
|
||||
color = cbTransform(color, cbMode);
|
||||
|
||||
if (mapSDRtoPQ)
|
||||
{
|
||||
if (color.a > 0.0)
|
||||
{
|
||||
// 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 * (203.0 / 10000.0)) * color.a;
|
||||
}
|
||||
else
|
||||
color.rgb = vec3(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ const float ratio = 4.0; // Compressor ratio: 1 = disabled, <1 = exp
|
||||
const float compressor = 1.0 / ratio;
|
||||
|
||||
// PQ constants
|
||||
const float m1 = 2610.0 / 16384.0;
|
||||
const float m2 = 2523.0 / 32.0;
|
||||
const float m1inv = 16384.0 / 2610.0;
|
||||
const float m2inv = 32.0 / 2523.0;
|
||||
const float c1 = 3424.0 / 4096.0;
|
||||
@@ -72,6 +74,13 @@ vec3 pq2lin(vec3 pq, float gain)
|
||||
return pow(d, vec3(m1inv)) * gain;
|
||||
}
|
||||
|
||||
vec3 lin2pq(vec3 linear)
|
||||
{
|
||||
// ST.2084 uses absolute luminance normalized to 10000 cd/m².
|
||||
vec3 p = pow(linear, vec3(m1));
|
||||
return pow((c1 + c2 * p) / (1.0 + c3 * p), vec3(m2));
|
||||
}
|
||||
|
||||
vec3 srgb2lin(vec3 c)
|
||||
{
|
||||
vec3 v = c / 12.92;
|
||||
@@ -99,6 +108,15 @@ vec3 bt2020to709(vec3 bt2020)
|
||||
bt2020.r * -0.0182 + bt2020.g * -0.1006 + bt2020.b * 1.1187);
|
||||
}
|
||||
|
||||
// in linear space
|
||||
vec3 bt709to2020(vec3 bt709)
|
||||
{
|
||||
return vec3(
|
||||
bt709.r * 0.6274 + bt709.g * 0.3293 + bt709.b * 0.0433,
|
||||
bt709.r * 0.0691 + bt709.g * 0.9195 + bt709.b * 0.0114,
|
||||
bt709.r * 0.0164 + bt709.g * 0.0880 + bt709.b * 0.8956);
|
||||
}
|
||||
|
||||
vec3 mapToSDR(vec3 color, float gain, bool pq)
|
||||
{
|
||||
if (pq)
|
||||
|
||||
Reference in New Issue
Block a user