mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-07-21 14:52:04 +00:00
egl: correct ImGui overlay luminance in HDR
Render ImGui to an intermediate SDR framebuffer and map it into the native PQ or scRGB output using the Wayland surface reference white. Preserve premultiplied alpha during conversion and limit composition to damaged overlay regions.
This commit is contained in:
@@ -55,6 +55,8 @@ build_shaders(
|
||||
shader/cursor_mono.frag
|
||||
shader/damage.vert
|
||||
shader/damage.frag
|
||||
shader/hdr_overlay.vert
|
||||
shader/hdr_overlay.frag
|
||||
shader/basic.vert
|
||||
shader/convert_24bit.frag
|
||||
shader/ffx_cas.frag
|
||||
@@ -85,6 +87,7 @@ add_library(renderer_EGL STATIC
|
||||
desktop_rects.c
|
||||
cursor.c
|
||||
damage.c
|
||||
hdr_overlay.c
|
||||
framebuffer.c
|
||||
effect.c
|
||||
postprocess.c
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
#include "damage.h"
|
||||
#include "desktop.h"
|
||||
#include "cursor.h"
|
||||
#include "hdr_overlay.h"
|
||||
#include "postprocess.h"
|
||||
#include "util.h"
|
||||
|
||||
@@ -78,6 +79,7 @@ struct Inst
|
||||
EGL_Desktop * desktop; // the desktop
|
||||
EGL_Cursor * cursor; // the mouse cursor
|
||||
EGL_Damage * damage; // the damage display
|
||||
EGL_HDROverlay * hdrOverlay;
|
||||
bool imgui; // if imgui was initialized
|
||||
|
||||
LG_RendererFormat format;
|
||||
@@ -309,6 +311,7 @@ static void egl_deinitialize(LG_Renderer * renderer)
|
||||
egl_desktopFree(&this->desktop);
|
||||
egl_cursorFree (&this->cursor);
|
||||
egl_damageFree (&this->damage);
|
||||
egl_hdrOverlayFree(&this->hdrOverlay);
|
||||
|
||||
LG_LOCK_FREE(this->lock);
|
||||
LG_LOCK_FREE(this->desktopDamageLock);
|
||||
@@ -541,6 +544,9 @@ static void egl_onResize(LG_Renderer * renderer, const int width, const int heig
|
||||
this->desktopDamage[this->desktopDamageIdx].count = -1;
|
||||
});
|
||||
|
||||
if (!egl_hdrOverlayResize(this->hdrOverlay, this->width, this->height))
|
||||
DEBUG_ERROR("Failed to resize the HDR overlay framebuffer");
|
||||
|
||||
// this is needed to refresh the font atlas texture
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
ImGui_ImplOpenGL3_Init("#version 300 es");
|
||||
@@ -644,6 +650,16 @@ static bool egl_onFrameFormat(LG_Renderer * renderer, const LG_RendererFormat fo
|
||||
egl_cursorSetHDRState(this->cursor, useNativeHDR, format.hdrPQ,
|
||||
format.sdrWhiteLevel);
|
||||
|
||||
LG_DSHDRWhiteLevels whiteLevels =
|
||||
{
|
||||
.pq = 203,
|
||||
.scRGB = 80,
|
||||
};
|
||||
if (useNativeHDR && !app_getProp(LG_DS_HDR_WHITE_LEVELS, &whiteLevels))
|
||||
DEBUG_WARN("Display server did not provide native HDR white levels");
|
||||
egl_hdrOverlaySetState(this->hdrOverlay, useNativeHDR, format.hdrPQ,
|
||||
format.hdrPQ ? whiteLevels.pq : whiteLevels.scRGB);
|
||||
|
||||
egl_update_scale_type(this);
|
||||
egl_damageSetup(this->damage, format.frameWidth, format.frameHeight);
|
||||
|
||||
@@ -1045,6 +1061,12 @@ static bool egl_renderStartup(LG_Renderer * renderer, bool useDMA)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!egl_hdrOverlayInit(&this->hdrOverlay))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the HDR overlay");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ImGui_ImplOpenGL3_Init("#version 300 es"))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize ImGui");
|
||||
@@ -1225,9 +1247,13 @@ static bool egl_render(LG_Renderer * renderer, LG_RendererRotate rotate,
|
||||
if (damageIdx == -1)
|
||||
hasOverlay = true;
|
||||
|
||||
const bool hdrOverlay = egl_hdrOverlayBegin(this->hdrOverlay,
|
||||
damage, damageIdx);
|
||||
ImGui_ImplOpenGL3_NewFrame();
|
||||
ImGui_ImplOpenGL3_RenderDrawData(igGetDrawData());
|
||||
egl_stateInvalidate();
|
||||
if (hdrOverlay)
|
||||
egl_hdrOverlayEnd(this->hdrOverlay, damage, damageIdx);
|
||||
|
||||
for (int i = 0; i < damageIdx; ++i)
|
||||
damage[i].y = this->height - damage[i].y - damage[i].h;
|
||||
|
||||
238
client/renderers/EGL/hdr_overlay.c
Normal file
238
client/renderers/EGL/hdr_overlay.c
Normal file
@@ -0,0 +1,238 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "hdr_overlay.h"
|
||||
|
||||
#include "framebuffer.h"
|
||||
#include "model.h"
|
||||
#include "shader.h"
|
||||
#include "state.h"
|
||||
#include "texture.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/rects.h"
|
||||
#include "common/util.h"
|
||||
|
||||
#include <stdatomic.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// these headers are auto generated by cmake
|
||||
#include "hdr_overlay.vert.h"
|
||||
#include "hdr_overlay.frag.h"
|
||||
|
||||
struct EGL_HDROverlay
|
||||
{
|
||||
EGL_Framebuffer * framebuffer;
|
||||
EGL_Shader * shader;
|
||||
EGL_Model * model;
|
||||
|
||||
EGL_Uniform * uPQ;
|
||||
EGL_Uniform * uReferenceWhiteLevel;
|
||||
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
bool configured;
|
||||
|
||||
_Atomic(bool) active;
|
||||
_Atomic(bool) pq;
|
||||
_Atomic(float) referenceWhiteLevel;
|
||||
|
||||
bool renderPQ;
|
||||
float renderReferenceWhiteLevel;
|
||||
};
|
||||
|
||||
bool egl_hdrOverlayInit(EGL_HDROverlay ** overlay)
|
||||
{
|
||||
*overlay = calloc(1, sizeof(**overlay));
|
||||
if (!*overlay)
|
||||
{
|
||||
DEBUG_ERROR("Failed to allocate the HDR overlay");
|
||||
return false;
|
||||
}
|
||||
|
||||
EGL_HDROverlay * this = *overlay;
|
||||
if (!egl_framebufferInit(&this->framebuffer) ||
|
||||
!egl_shaderInit(&this->shader) ||
|
||||
!egl_shaderCompile(this->shader,
|
||||
b_shader_hdr_overlay_vert, b_shader_hdr_overlay_vert_size,
|
||||
b_shader_hdr_overlay_frag, b_shader_hdr_overlay_frag_size,
|
||||
false, NULL) ||
|
||||
!egl_modelInit(&this->model))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the HDR overlay");
|
||||
egl_hdrOverlayFree(overlay);
|
||||
return false;
|
||||
}
|
||||
|
||||
this->uPQ = egl_shaderGetUniform(this->shader, "pq");
|
||||
this->uReferenceWhiteLevel =
|
||||
egl_shaderGetUniform(this->shader, "referenceWhiteLevel");
|
||||
|
||||
egl_modelSetDefault(this->model, false);
|
||||
egl_modelSetShader(this->model, this->shader);
|
||||
egl_modelSetTexture(this->model,
|
||||
egl_framebufferGetTexture(this->framebuffer));
|
||||
|
||||
/* The conversion pass is a 1:1 copy; filtering can sample stale pixels just
|
||||
* outside a partial-damage region. */
|
||||
EGL_Texture * texture = egl_framebufferGetTexture(this->framebuffer);
|
||||
glSamplerParameteri(texture->sampler, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glSamplerParameteri(texture->sampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
|
||||
atomic_init(&this->active, false);
|
||||
atomic_init(&this->pq, false);
|
||||
atomic_init(&this->referenceWhiteLevel, 80.0f);
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_hdrOverlayFree(EGL_HDROverlay ** overlay)
|
||||
{
|
||||
if (!*overlay)
|
||||
return;
|
||||
|
||||
EGL_HDROverlay * this = *overlay;
|
||||
egl_modelFree(&this->model);
|
||||
egl_shaderFree(&this->shader);
|
||||
egl_framebufferFree(&this->framebuffer);
|
||||
free(this);
|
||||
*overlay = NULL;
|
||||
}
|
||||
|
||||
bool egl_hdrOverlayResize(EGL_HDROverlay * this, unsigned int width,
|
||||
unsigned int height)
|
||||
{
|
||||
this->width = width;
|
||||
this->height = height;
|
||||
this->configured = false;
|
||||
|
||||
if (!width || !height)
|
||||
return true;
|
||||
|
||||
this->configured = egl_framebufferSetup(this->framebuffer, EGL_PF_RGBA,
|
||||
width, height);
|
||||
return this->configured;
|
||||
}
|
||||
|
||||
void egl_hdrOverlaySetState(EGL_HDROverlay * this, bool active, bool pq,
|
||||
float referenceWhiteLevel)
|
||||
{
|
||||
atomic_store(&this->referenceWhiteLevel,
|
||||
referenceWhiteLevel > 0.0f ? referenceWhiteLevel : 80.0f);
|
||||
atomic_store(&this->pq, pq);
|
||||
atomic_store(&this->active, active);
|
||||
}
|
||||
|
||||
static int convertDamage(EGL_HDROverlay * this, const struct Rect * damage,
|
||||
int damageCount, FrameDamageRect * output)
|
||||
{
|
||||
int count = 0;
|
||||
for (int i = 0; i < damageCount; ++i)
|
||||
{
|
||||
const int x1 = clamp(damage[i].x, 0, (int)this->width);
|
||||
const int y1 = clamp(damage[i].y, 0, (int)this->height);
|
||||
const int x2 = clamp(damage[i].x + damage[i].w, 0, (int)this->width);
|
||||
const int y2 = clamp(damage[i].y + damage[i].h, 0, (int)this->height);
|
||||
if (x2 <= x1 || y2 <= y1)
|
||||
continue;
|
||||
|
||||
output[count++] = (FrameDamageRect)
|
||||
{
|
||||
.x = x1,
|
||||
.y = this->height - y2,
|
||||
.width = x2 - x1,
|
||||
.height = y2 - y1,
|
||||
};
|
||||
}
|
||||
|
||||
return rectsMergeOverlapping(output, count);
|
||||
}
|
||||
|
||||
bool egl_hdrOverlayBegin(EGL_HDROverlay * this,
|
||||
const struct Rect * damage, int damageCount)
|
||||
{
|
||||
if (!this->configured || !atomic_load(&this->active))
|
||||
return false;
|
||||
|
||||
this->renderPQ = atomic_load(&this->pq);
|
||||
this->renderReferenceWhiteLevel =
|
||||
atomic_load(&this->referenceWhiteLevel);
|
||||
|
||||
egl_framebufferBind(this->framebuffer);
|
||||
egl_stateBlend(false);
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
|
||||
if (damageCount < 0)
|
||||
{
|
||||
egl_stateScissor(false);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
else
|
||||
{
|
||||
FrameDamageRect rects[damageCount];
|
||||
const int count = convertDamage(this, damage, damageCount, rects);
|
||||
|
||||
egl_stateScissor(true);
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
glScissor(rects[i].x, rects[i].y,
|
||||
rects[i].width, rects[i].height);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
egl_stateScissor(false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_hdrOverlayEnd(EGL_HDROverlay * this,
|
||||
const struct Rect * damage, int damageCount)
|
||||
{
|
||||
egl_stateBindFramebuffer(0);
|
||||
egl_stateViewport(0, 0, this->width, this->height);
|
||||
|
||||
egl_uniform1i(this->uPQ, this->renderPQ);
|
||||
egl_uniform1f(this->uReferenceWhiteLevel,
|
||||
this->renderReferenceWhiteLevel);
|
||||
|
||||
egl_stateBlend(true);
|
||||
egl_stateBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
if (damageCount < 0)
|
||||
{
|
||||
egl_stateScissor(false);
|
||||
egl_modelRender(this->model);
|
||||
}
|
||||
else
|
||||
{
|
||||
FrameDamageRect rects[damageCount];
|
||||
const int count = convertDamage(this, damage, damageCount, rects);
|
||||
|
||||
egl_stateScissor(true);
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
glScissor(rects[i].x, rects[i].y,
|
||||
rects[i].width, rects[i].height);
|
||||
egl_modelRender(this->model);
|
||||
}
|
||||
egl_stateScissor(false);
|
||||
}
|
||||
|
||||
egl_stateBlend(false);
|
||||
}
|
||||
45
client/renderers/EGL/hdr_overlay.h
Normal file
45
client/renderers/EGL/hdr_overlay.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/types.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct EGL_HDROverlay EGL_HDROverlay;
|
||||
|
||||
bool egl_hdrOverlayInit(EGL_HDROverlay ** overlay);
|
||||
void egl_hdrOverlayFree(EGL_HDROverlay ** overlay);
|
||||
|
||||
bool egl_hdrOverlayResize(EGL_HDROverlay * overlay, unsigned int width,
|
||||
unsigned int height);
|
||||
void egl_hdrOverlaySetState(EGL_HDROverlay * overlay, bool active, bool pq,
|
||||
float referenceWhiteLevel);
|
||||
|
||||
/*
|
||||
* Begin returns true when ImGui should be rendered into the HDR overlay
|
||||
* framebuffer. The caller must then call egl_hdrOverlayEnd after ImGui has
|
||||
* finished rendering and the EGL state cache has been invalidated.
|
||||
*/
|
||||
bool egl_hdrOverlayBegin(EGL_HDROverlay * overlay,
|
||||
const struct Rect * damage, int damageCount);
|
||||
void egl_hdrOverlayEnd(EGL_HDROverlay * overlay,
|
||||
const struct Rect * damage, int damageCount);
|
||||
35
client/renderers/EGL/shader/hdr_overlay.frag
Normal file
35
client/renderers/EGL/shader/hdr_overlay.frag
Normal file
@@ -0,0 +1,35 @@
|
||||
#version 300 es
|
||||
precision highp float;
|
||||
|
||||
#include "hdr.h"
|
||||
|
||||
in vec2 fragCoord;
|
||||
out vec4 color;
|
||||
|
||||
uniform sampler2D sampler1;
|
||||
uniform bool pq;
|
||||
uniform float referenceWhiteLevel;
|
||||
|
||||
void main()
|
||||
{
|
||||
color = texture(sampler1, fragCoord);
|
||||
if (color.a <= 0.0)
|
||||
{
|
||||
color.rgb = vec3(0.0);
|
||||
return;
|
||||
}
|
||||
|
||||
// ImGui's framebuffer contains premultiplied sRGB. Convert the straight
|
||||
// color, then premultiply again for composition onto the HDR framebuffer.
|
||||
vec3 linear = srgb2lin(clamp(color.rgb / color.a, 0.0, 1.0));
|
||||
if (pq)
|
||||
{
|
||||
linear = bt709to2020(linear);
|
||||
color.rgb = lin2pq(linear * (referenceWhiteLevel / 10000.0)) * color.a;
|
||||
}
|
||||
else
|
||||
{
|
||||
// scRGB is linear sRGB and defines 1.0 as 80 nits.
|
||||
color.rgb = linear * (referenceWhiteLevel / 80.0) * color.a;
|
||||
}
|
||||
}
|
||||
13
client/renderers/EGL/shader/hdr_overlay.vert
Normal file
13
client/renderers/EGL/shader/hdr_overlay.vert
Normal file
@@ -0,0 +1,13 @@
|
||||
#version 300 es
|
||||
precision highp float;
|
||||
|
||||
layout(location = 0) in vec3 vertex;
|
||||
layout(location = 1) in vec2 coord;
|
||||
|
||||
out vec2 fragCoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = vec4(vertex, 1.0);
|
||||
fragCoord = coord;
|
||||
}
|
||||
Reference in New Issue
Block a user