mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-07-20 14:22:00 +00:00
[client] egl: compose native PQ output in linear light
Decode PQ/BT.2020 desktop pixels into an FP16 linear scRGB target before compositing the color cursor, ImGui, damage overlay, and letterboxing. Encode the damaged result to PQ once at the surface boundary. Keep logical AND/XOR cursor masks in the encoded domain. This preserves premultiplied cursor edges, prevents encoded-domain blending, uses the Wayland output reference white for local overlays, and keeps guest cursor white and display calibration independent from frame SDR white. It also corrects HDR-to-SDR absolute luminance scaling, gamut matrices, black handling, and buffer-age repair for the extra pass. Suppress overlays when their conversion framebuffer is unavailable rather than allowing unconverted sRGB draws into an HDR target. Use named transfer constants for cursor shader state. Convert clipped surface rectangles explicitly before using the frame-damage merge helper, which avoids mixing its unsigned type with the EGL surface rectangle type. Only advertise native PQ when the FP16 composition target is available. SDR and native scRGB retain their direct single-pass paths.
This commit is contained in:
@@ -57,6 +57,8 @@ build_shaders(
|
||||
shader/damage.frag
|
||||
shader/hdr_overlay.vert
|
||||
shader/hdr_overlay.frag
|
||||
shader/hdr_compose.vert
|
||||
shader/hdr_compose.frag
|
||||
shader/basic.vert
|
||||
shader/convert_24bit.frag
|
||||
shader/ffx_cas.frag
|
||||
@@ -88,6 +90,7 @@ add_library(renderer_EGL STATIC
|
||||
cursor.c
|
||||
damage.c
|
||||
hdr_overlay.c
|
||||
hdr_compose.c
|
||||
framebuffer.c
|
||||
effect.c
|
||||
postprocess.c
|
||||
|
||||
@@ -38,6 +38,13 @@
|
||||
#include "cursor_rgb.frag.h"
|
||||
#include "cursor_mono.frag.h"
|
||||
|
||||
enum CursorTransfer
|
||||
{
|
||||
CURSOR_TRANSFER_SRGB = 0,
|
||||
CURSOR_TRANSFER_SCRGB = 1,
|
||||
CURSOR_TRANSFER_PQ = 2,
|
||||
};
|
||||
|
||||
struct CursorTex
|
||||
{
|
||||
struct EGL_Texture * texture;
|
||||
@@ -46,11 +53,15 @@ struct CursorTex
|
||||
EGL_Uniform * uScale;
|
||||
EGL_Uniform * uRotate;
|
||||
EGL_Uniform * uCBMode;
|
||||
EGL_Uniform * uWireTransfer;
|
||||
EGL_Uniform * uSourceTransfer;
|
||||
EGL_Uniform * uSDRWhiteLevel;
|
||||
EGL_Uniform * uMapHDRtoSDR;
|
||||
EGL_Uniform * uMapHDRGain;
|
||||
EGL_Uniform * uMapHDRContentPeak;
|
||||
EGL_Uniform * uColorTransformFlags;
|
||||
EGL_Uniform * uColorMatrix;
|
||||
EGL_Uniform * uColorScalar;
|
||||
EGL_Uniform * uLinearComposition;
|
||||
};
|
||||
|
||||
struct CursorPos
|
||||
@@ -79,12 +90,15 @@ struct EGL_Cursor
|
||||
LG_RendererRotate rotate;
|
||||
int cbMode;
|
||||
|
||||
_Atomic(struct CursorPos) pos;
|
||||
_Atomic(struct CursorPos) hs;
|
||||
_Atomic(struct CursorSize) size;
|
||||
_Atomic(float) scale;
|
||||
_Atomic(int) wireTransfer;
|
||||
_Atomic(float) sdrWhiteLevel;
|
||||
_Atomic(struct CursorPos) pos;
|
||||
_Atomic(struct CursorPos) hs;
|
||||
_Atomic(struct CursorSize) size;
|
||||
_Atomic(float) scale;
|
||||
_Atomic(enum CursorTransfer) sourceTransfer;
|
||||
_Atomic(float) sdrWhiteLevel;
|
||||
_Atomic(bool) mapHDRtoSDR;
|
||||
_Atomic(float) mapHDRGain;
|
||||
_Atomic(float) mapHDRContentPeak;
|
||||
|
||||
bool colorTransformUpdate;
|
||||
KVMFRColorTransform pendingColorTransform;
|
||||
@@ -119,16 +133,23 @@ 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->uWireTransfer = egl_shaderGetUniform(t->shader, "wireTransfer" );
|
||||
t->uSDRWhiteLevel = egl_shaderGetUniform(t->shader, "sdrWhiteLevel");
|
||||
t->uColorTransformFlags =
|
||||
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->uSourceTransfer = egl_shaderGetUniform(t->shader, "sourceTransfer");
|
||||
t->uSDRWhiteLevel = egl_shaderGetUniform(t->shader, "sdrWhiteLevel");
|
||||
t->uMapHDRtoSDR = egl_shaderGetUniform(t->shader, "mapHDRtoSDR" );
|
||||
t->uMapHDRGain = egl_shaderGetUniform(t->shader, "mapHDRGain" );
|
||||
t->uMapHDRContentPeak =
|
||||
egl_shaderGetUniform(t->shader, "mapHDRContentPeak");
|
||||
t->uColorTransformFlags =
|
||||
egl_shaderGetUniform(t->shader, "colorTransformFlags");
|
||||
t->uColorMatrix = egl_shaderGetUniform(t->shader, "colorMatrix[0]");
|
||||
t->uColorScalar = egl_shaderGetUniform(t->shader, "colorTransformScalar");
|
||||
t->uColorMatrix = egl_shaderGetUniform(t->shader, "colorMatrix[0]");
|
||||
t->uColorScalar =
|
||||
egl_shaderGetUniform(t->shader, "colorTransformScalar");
|
||||
t->uLinearComposition =
|
||||
egl_shaderGetUniform(t->shader, "linearComposition");
|
||||
egl_shaderAssocTextures(t->shader, 2);
|
||||
|
||||
return true;
|
||||
@@ -136,20 +157,37 @@ static bool cursorTexInit(struct CursorTex * t,
|
||||
|
||||
static inline void setCursorTexUniforms(EGL_Cursor * cursor,
|
||||
struct CursorTex * t, bool mono, float x, float y,
|
||||
float w, float h, float scale)
|
||||
float w, float h, float scale, bool linearComposition)
|
||||
{
|
||||
egl_uniform4f(t->uMousePos , x, y, w, mono ? h / 2 : h);
|
||||
egl_uniform1f(t->uScale , scale);
|
||||
egl_uniform1i(t->uRotate , cursor->rotate);
|
||||
egl_uniform1i(t->uCBMode , cursor->cbMode);
|
||||
egl_uniform1i(t->uWireTransfer,
|
||||
mono ? 0 : atomic_load(&cursor->wireTransfer));
|
||||
egl_uniform1f(t->uSDRWhiteLevel, atomic_load(&cursor->sdrWhiteLevel));
|
||||
egl_uniform1ui(t->uColorTransformFlags,
|
||||
const enum CursorTransfer sourceTransfer = mono ? CURSOR_TRANSFER_SRGB :
|
||||
atomic_load(&cursor->sourceTransfer);
|
||||
egl_uniform4f (t->uMousePos , x, y, w, mono ? h / 2 : h);
|
||||
egl_uniform1f (t->uScale , scale);
|
||||
egl_uniform1i (t->uRotate , cursor->rotate);
|
||||
// The colour-blind transform is defined for SDR signals. The desktop
|
||||
// disables it while passing native HDR through, so keep the cursor on the
|
||||
// same path rather than altering encoded PQ or linear scRGB independently.
|
||||
egl_uniform1i (t->uCBMode ,
|
||||
(sourceTransfer == CURSOR_TRANSFER_SRGB ||
|
||||
atomic_load(&cursor->mapHDRtoSDR)) ?
|
||||
cursor->cbMode : 0);
|
||||
egl_uniform1i (t->uSourceTransfer , sourceTransfer);
|
||||
egl_uniform1f (t->uSDRWhiteLevel ,
|
||||
atomic_load(&cursor->sdrWhiteLevel));
|
||||
egl_uniform1i (t->uMapHDRtoSDR ,
|
||||
!mono && atomic_load(&cursor->mapHDRtoSDR));
|
||||
egl_uniform1f (t->uMapHDRGain ,
|
||||
atomic_load(&cursor->mapHDRGain));
|
||||
egl_uniform1f (t->uMapHDRContentPeak ,
|
||||
atomic_load(&cursor->mapHDRContentPeak));
|
||||
egl_uniform1ui(t->uColorTransformFlags ,
|
||||
mono ? 0 : cursor->activeColorTransform.flags);
|
||||
egl_uniformSet(t->uColorMatrix, EGL_UNIFORM_TYPE_4FV, 3, GL_FALSE,
|
||||
egl_uniformSet (t->uColorMatrix , EGL_UNIFORM_TYPE_4FV, 3, GL_FALSE,
|
||||
cursor->activeColorTransform.matrix);
|
||||
egl_uniform1f(t->uColorScalar, cursor->activeColorTransform.scalar);
|
||||
egl_uniform1f (t->uColorScalar ,
|
||||
cursor->activeColorTransform.scalar);
|
||||
egl_uniform1i (t->uLinearComposition ,
|
||||
!mono && linearComposition);
|
||||
if (!mono && cursor->colorLUT)
|
||||
egl_stateBindTexture(1, GL_TEXTURE_2D, cursor->colorLUT);
|
||||
}
|
||||
@@ -195,13 +233,16 @@ 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)->wireTransfer, 0);
|
||||
atomic_init(&(*cursor)->sdrWhiteLevel,
|
||||
atomic_init(&(*cursor)->pos , pos );
|
||||
atomic_init(&(*cursor)->hs , hs );
|
||||
atomic_init(&(*cursor)->size , size );
|
||||
atomic_init(&(*cursor)->scale , 1.0f );
|
||||
atomic_init(&(*cursor)->sourceTransfer , CURSOR_TRANSFER_SRGB);
|
||||
atomic_init(&(*cursor)->sdrWhiteLevel ,
|
||||
KVMFR_SDR_WHITE_LEVEL_DEFAULT);
|
||||
atomic_init(&(*cursor)->mapHDRtoSDR , false);
|
||||
atomic_init(&(*cursor)->mapHDRGain , 1.0f );
|
||||
atomic_init(&(*cursor)->mapHDRContentPeak, 1.0f );
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -290,8 +331,12 @@ void egl_cursorSetState(EGL_Cursor * cursor, const bool visible,
|
||||
}
|
||||
|
||||
struct CursorState egl_cursorRender(EGL_Cursor * cursor,
|
||||
LG_RendererRotate rotate, int width, int height)
|
||||
LG_RendererRotate rotate, int width, int height,
|
||||
bool linearComposition, bool * logicalDeferred)
|
||||
{
|
||||
if (logicalDeferred)
|
||||
*logicalDeferred = false;
|
||||
|
||||
if (!cursor->visible)
|
||||
return (struct CursorState) { .visible = false };
|
||||
|
||||
@@ -443,20 +488,27 @@ struct CursorState egl_cursorRender(EGL_Cursor * cursor,
|
||||
state.rect.x = max(0, state.rect.x - 1);
|
||||
state.rect.y = max(0, state.rect.y - 1);
|
||||
|
||||
if (linearComposition && cursor->type != LG_CURSOR_COLOR)
|
||||
{
|
||||
if (logicalDeferred)
|
||||
*logicalDeferred = true;
|
||||
return state;
|
||||
}
|
||||
|
||||
egl_stateBlend(true);
|
||||
switch(cursor->type)
|
||||
{
|
||||
case LG_CURSOR_MONOCHROME:
|
||||
{
|
||||
setCursorTexUniforms(cursor, &cursor->norm, true, pos.x, pos.y,
|
||||
size.w, size.h, scale);
|
||||
size.w, size.h, scale, false);
|
||||
egl_shaderUse(cursor->norm.shader);
|
||||
egl_stateBlendFunc(GL_ZERO, GL_SRC_COLOR);
|
||||
egl_modelSetTexture(cursor->model, cursor->norm.texture);
|
||||
egl_modelRender(cursor->model);
|
||||
|
||||
setCursorTexUniforms(cursor, &cursor->mono, true, pos.x, pos.y,
|
||||
size.w, size.h, scale);
|
||||
size.w, size.h, scale, false);
|
||||
egl_shaderUse(cursor->mono.shader);
|
||||
egl_stateBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
|
||||
egl_modelSetTexture(cursor->model, cursor->mono.texture);
|
||||
@@ -467,14 +519,14 @@ struct CursorState egl_cursorRender(EGL_Cursor * cursor,
|
||||
case LG_CURSOR_MASKED_COLOR:
|
||||
{
|
||||
setCursorTexUniforms(cursor, &cursor->norm, false, pos.x, pos.y,
|
||||
size.w, size.h, scale);
|
||||
size.w, size.h, scale, linearComposition);
|
||||
egl_shaderUse(cursor->norm.shader);
|
||||
egl_stateBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
egl_modelSetTexture(cursor->model, cursor->norm.texture);
|
||||
egl_modelRender(cursor->model);
|
||||
|
||||
setCursorTexUniforms(cursor, &cursor->mono, false, pos.x, pos.y,
|
||||
size.w, size.h, scale);
|
||||
size.w, size.h, scale, linearComposition);
|
||||
egl_shaderUse(cursor->mono.shader);
|
||||
egl_stateBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
|
||||
egl_modelSetTexture(cursor->model, cursor->mono.texture);
|
||||
@@ -485,7 +537,7 @@ struct CursorState egl_cursorRender(EGL_Cursor * cursor,
|
||||
case LG_CURSOR_COLOR:
|
||||
{
|
||||
setCursorTexUniforms(cursor, &cursor->norm, false, pos.x, pos.y,
|
||||
size.w, size.h, scale);
|
||||
size.w, size.h, scale, linearComposition);
|
||||
egl_shaderUse(cursor->norm.shader);
|
||||
egl_stateBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
egl_modelSetTexture(cursor->model, cursor->norm.texture);
|
||||
@@ -498,11 +550,19 @@ struct CursorState egl_cursorRender(EGL_Cursor * cursor,
|
||||
return state;
|
||||
}
|
||||
|
||||
void egl_cursorSetHDRState(EGL_Cursor * cursor, bool hdrActive, bool hdrPQ,
|
||||
float sdrWhiteLevel)
|
||||
void egl_cursorSetHDRState(EGL_Cursor * cursor, bool sourceHDR,
|
||||
bool nativeHDR, bool mapHDRtoSDR, bool hdrPQ,
|
||||
float mapGain, float mapContentPeak)
|
||||
{
|
||||
egl_cursorSetSDRWhiteLevel(cursor, sdrWhiteLevel);
|
||||
atomic_store(&cursor->wireTransfer, !hdrActive ? 0 : (hdrPQ ? 2 : 1));
|
||||
// The cursor's white level is supplied independently by the pointer queue.
|
||||
// Do not replace it with the frame-level fallback during every render.
|
||||
atomic_store(&cursor->sourceTransfer,
|
||||
!sourceHDR ? CURSOR_TRANSFER_SRGB :
|
||||
(hdrPQ ? CURSOR_TRANSFER_PQ : CURSOR_TRANSFER_SCRGB));
|
||||
atomic_store(&cursor->mapHDRtoSDR,
|
||||
sourceHDR && !nativeHDR && mapHDRtoSDR);
|
||||
atomic_store(&cursor->mapHDRGain, mapGain);
|
||||
atomic_store(&cursor->mapHDRContentPeak, mapContentPeak);
|
||||
}
|
||||
|
||||
void egl_cursorSetSDRWhiteLevel(EGL_Cursor * cursor, float sdrWhiteLevel)
|
||||
|
||||
@@ -54,8 +54,10 @@ void egl_cursorSetState(EGL_Cursor * cursor, const bool visible,
|
||||
const float x, const float y, const float hx, const float hy);
|
||||
|
||||
struct CursorState egl_cursorRender(EGL_Cursor * cursor,
|
||||
LG_RendererRotate rotate, int width, int height);
|
||||
LG_RendererRotate rotate, int width, int height,
|
||||
bool linearComposition, bool * logicalDeferred);
|
||||
|
||||
void egl_cursorSetHDRState(EGL_Cursor * cursor, bool hdrActive, bool hdrPQ,
|
||||
float sdrWhiteLevel);
|
||||
void egl_cursorSetHDRState(EGL_Cursor * cursor, bool sourceHDR,
|
||||
bool nativeHDR, bool mapHDRtoSDR, bool hdrPQ,
|
||||
float mapGain, float mapContentPeak);
|
||||
void egl_cursorSetSDRWhiteLevel(EGL_Cursor * cursor, float sdrWhiteLevel);
|
||||
|
||||
@@ -51,6 +51,11 @@ struct EGL_Damage
|
||||
|
||||
// uniforms
|
||||
EGL_Uniform * uTransform;
|
||||
EGL_Uniform * uLinearOutput;
|
||||
EGL_Uniform * uReferenceWhiteLevel;
|
||||
|
||||
bool linearOutput;
|
||||
float referenceWhiteLevel;
|
||||
};
|
||||
|
||||
void egl_damageConfigUI(EGL_Damage * damage)
|
||||
@@ -90,7 +95,13 @@ bool egl_damageInit(EGL_Damage ** damage)
|
||||
return false;
|
||||
}
|
||||
|
||||
(*damage)->uTransform = egl_shaderGetUniform((*damage)->shader, "transform");
|
||||
(*damage)->uTransform =
|
||||
egl_shaderGetUniform((*damage)->shader, "transform");
|
||||
(*damage)->uLinearOutput =
|
||||
egl_shaderGetUniform((*damage)->shader, "linearOutput");
|
||||
(*damage)->uReferenceWhiteLevel =
|
||||
egl_shaderGetUniform((*damage)->shader, "referenceWhiteLevel");
|
||||
(*damage)->referenceWhiteLevel = 80.0f;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -130,6 +141,14 @@ void egl_damageResize(EGL_Damage * damage, float translateX, float translateY,
|
||||
update_matrix(damage);
|
||||
}
|
||||
|
||||
void egl_damageSetHDRState(EGL_Damage * damage, bool active,
|
||||
float referenceWhiteLevel)
|
||||
{
|
||||
damage->linearOutput = active;
|
||||
damage->referenceWhiteLevel = referenceWhiteLevel > 0.0f ?
|
||||
referenceWhiteLevel : 80.0f;
|
||||
}
|
||||
|
||||
bool egl_damageRender(EGL_Damage * damage, LG_RendererRotate rotate, const struct DesktopDamage * data)
|
||||
{
|
||||
if (!damage->show)
|
||||
@@ -145,6 +164,9 @@ bool egl_damageRender(EGL_Damage * damage, LG_RendererRotate rotate, const struc
|
||||
egl_stateBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
egl_uniformMatrix3x2fv(damage->uTransform, 1, GL_FALSE, damage->transform);
|
||||
egl_uniform1i(damage->uLinearOutput, damage->linearOutput);
|
||||
egl_uniform1f(damage->uReferenceWhiteLevel,
|
||||
damage->referenceWhiteLevel);
|
||||
egl_shaderUse(damage->shader);
|
||||
|
||||
if (data && data->count != 0)
|
||||
|
||||
@@ -40,5 +40,7 @@ void egl_damageConfigUI(EGL_Damage * damage);
|
||||
void egl_damageSetup(EGL_Damage * damage, int width, int height);
|
||||
void egl_damageResize(EGL_Damage * damage, float translateX, float translateY,
|
||||
float scaleX, float scaleY);
|
||||
void egl_damageSetHDRState(EGL_Damage * damage, bool active,
|
||||
float referenceWhiteLevel);
|
||||
bool egl_damageRender(EGL_Damage * damage, LG_RendererRotate rotate,
|
||||
const struct DesktopDamage * data);
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
#include "desktop.h"
|
||||
#include "state.h"
|
||||
#include "framebuffer.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/option.h"
|
||||
#include "common/locking.h"
|
||||
@@ -52,7 +53,9 @@ struct DesktopShader
|
||||
EGL_Uniform * uIsHDR;
|
||||
EGL_Uniform * uMapHDRtoSDR;
|
||||
EGL_Uniform * uMapHDRGain;
|
||||
EGL_Uniform * uMapHDRContentPeak;
|
||||
EGL_Uniform * uMapHDRPQ;
|
||||
EGL_Uniform * uOutputHDRLinear;
|
||||
};
|
||||
|
||||
struct EGL_Desktop
|
||||
@@ -91,6 +94,7 @@ struct EGL_Desktop
|
||||
// map HDR content to SDR
|
||||
bool mapHDRtoSDR;
|
||||
bool nativeHDR;
|
||||
bool linearComposition;
|
||||
int peakLuminance;
|
||||
int maxCLL;
|
||||
|
||||
@@ -119,15 +123,28 @@ static bool egl_initDesktopShader(
|
||||
return false;
|
||||
}
|
||||
|
||||
shader->uDesktopSize = egl_shaderGetUniform(shader->shader, "desktopSize" );
|
||||
shader->uTransform = egl_shaderGetUniform(shader->shader, "transform" );
|
||||
shader->uScaleAlgo = egl_shaderGetUniform(shader->shader, "scaleAlgo" );
|
||||
shader->uNVGain = egl_shaderGetUniform(shader->shader, "nvGain" );
|
||||
shader->uCBMode = egl_shaderGetUniform(shader->shader, "cbMode" );
|
||||
shader->uIsHDR = egl_shaderGetUniform(shader->shader, "isHDR" );
|
||||
shader->uMapHDRtoSDR = egl_shaderGetUniform(shader->shader, "mapHDRtoSDR" );
|
||||
shader->uMapHDRGain = egl_shaderGetUniform(shader->shader, "mapHDRGain" );
|
||||
shader->uMapHDRPQ = egl_shaderGetUniform(shader->shader, "mapHDRPQ" );
|
||||
shader->uDesktopSize =
|
||||
egl_shaderGetUniform(shader->shader, "desktopSize");
|
||||
shader->uTransform =
|
||||
egl_shaderGetUniform(shader->shader, "transform");
|
||||
shader->uScaleAlgo =
|
||||
egl_shaderGetUniform(shader->shader, "scaleAlgo");
|
||||
shader->uNVGain =
|
||||
egl_shaderGetUniform(shader->shader, "nvGain");
|
||||
shader->uCBMode =
|
||||
egl_shaderGetUniform(shader->shader, "cbMode");
|
||||
shader->uIsHDR =
|
||||
egl_shaderGetUniform(shader->shader, "isHDR");
|
||||
shader->uMapHDRtoSDR =
|
||||
egl_shaderGetUniform(shader->shader, "mapHDRtoSDR");
|
||||
shader->uMapHDRGain =
|
||||
egl_shaderGetUniform(shader->shader, "mapHDRGain");
|
||||
shader->uMapHDRContentPeak =
|
||||
egl_shaderGetUniform(shader->shader, "mapHDRContentPeak");
|
||||
shader->uMapHDRPQ =
|
||||
egl_shaderGetUniform(shader->shader, "mapHDRPQ");
|
||||
shader->uOutputHDRLinear =
|
||||
egl_shaderGetUniform(shader->shader, "outputHDRLinear");
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -426,7 +443,7 @@ bool egl_desktopRender(EGL_Desktop * desktop, unsigned int outputWidth,
|
||||
unsigned int outputHeight, const float x, const float y,
|
||||
const float scaleX, const float scaleY, enum EGL_DesktopScaleType scaleType,
|
||||
LG_RendererRotate rotate, const struct DamageRects * rects,
|
||||
bool * fullFrame)
|
||||
bool * fullFrame, EGL_Framebuffer * target)
|
||||
{
|
||||
EGL_Texture * tex;
|
||||
int width, height;
|
||||
@@ -490,8 +507,13 @@ bool egl_desktopRender(EGL_Desktop * desktop, unsigned int outputWidth,
|
||||
finalSizeY = height;
|
||||
}
|
||||
|
||||
egl_stateBindFramebuffer(0);
|
||||
egl_resetViewport(desktop->egl);
|
||||
if (target)
|
||||
egl_framebufferBind(target);
|
||||
else
|
||||
{
|
||||
egl_stateBindFramebuffer(0);
|
||||
egl_resetViewport(desktop->egl);
|
||||
}
|
||||
|
||||
egl_textureBind(texture);
|
||||
|
||||
@@ -522,26 +544,47 @@ bool egl_desktopRender(EGL_Desktop * desktop, unsigned int outputWidth,
|
||||
desktop->useDMA && texture == desktop->texture ?
|
||||
&desktop->dmaShader : &desktop->shader;
|
||||
|
||||
const float mapHDRGain =
|
||||
/* PQ is normalized to 10000 nits, while scRGB defines 1.0 as 80 nits.
|
||||
* Convert either encoding into values relative to the selected SDR peak. */
|
||||
const float mapHDRGain = (desktop->hdrPQ ? 10000.0f : 80.0f) /
|
||||
desktop->peakLuminance;
|
||||
const float mapHDRContentPeak =
|
||||
(float)desktop->maxCLL / desktop->peakLuminance;
|
||||
|
||||
egl_uniform1i (shader->uScaleAlgo , scaleAlgo);
|
||||
egl_uniform2f (shader->uDesktopSize, width, height);
|
||||
egl_uniformMatrix3x2fv(shader->uTransform , 1, GL_FALSE, desktop->matrix);
|
||||
egl_uniform1f (shader->uNVGain , desktop->nvGain);
|
||||
egl_uniform1i (shader->uCBMode , desktop->cbMode);
|
||||
egl_uniform1i (shader->uIsHDR , hdr);
|
||||
egl_uniform1i (shader->uMapHDRtoSDR, desktop->mapHDRtoSDR && !desktop->nativeHDR);
|
||||
egl_uniform1f (shader->uMapHDRGain , mapHDRGain);
|
||||
egl_uniform1i (shader->uMapHDRPQ , desktop->hdrPQ);
|
||||
egl_uniform1i (shader->uScaleAlgo , scaleAlgo);
|
||||
egl_uniform2f (shader->uDesktopSize , width, height);
|
||||
egl_uniformMatrix3x2fv(shader->uTransform ,
|
||||
1, GL_FALSE, desktop->matrix);
|
||||
egl_uniform1f (shader->uNVGain , desktop->nvGain);
|
||||
egl_uniform1i (shader->uCBMode , desktop->cbMode);
|
||||
egl_uniform1i (shader->uIsHDR , hdr);
|
||||
egl_uniform1i (shader->uMapHDRtoSDR ,
|
||||
desktop->mapHDRtoSDR && !desktop->nativeHDR);
|
||||
egl_uniform1f (shader->uMapHDRGain , mapHDRGain);
|
||||
egl_uniform1f (shader->uMapHDRContentPeak, mapHDRContentPeak);
|
||||
egl_uniform1i (shader->uMapHDRPQ , desktop->hdrPQ);
|
||||
egl_uniform1i (shader->uOutputHDRLinear ,
|
||||
desktop->linearComposition);
|
||||
egl_shaderUse(shader->shader);
|
||||
egl_desktopRectsRender(desktop->mesh);
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_desktopSetNativeHDR(EGL_Desktop * desktop, bool nativeHDR)
|
||||
void egl_desktopSetNativeHDR(EGL_Desktop * desktop, bool nativeHDR,
|
||||
bool linearComposition)
|
||||
{
|
||||
desktop->nativeHDR = nativeHDR;
|
||||
desktop->nativeHDR = nativeHDR;
|
||||
desktop->linearComposition = nativeHDR && linearComposition;
|
||||
}
|
||||
|
||||
void egl_desktopGetHDRMapping(EGL_Desktop * desktop, bool * enabled,
|
||||
float * gain, float * contentPeak)
|
||||
{
|
||||
*enabled = desktop->hdr && !desktop->useSpice &&
|
||||
desktop->mapHDRtoSDR && !desktop->nativeHDR;
|
||||
*gain = (desktop->hdrPQ ? 10000.0f : 80.0f) /
|
||||
desktop->peakLuminance;
|
||||
*contentPeak = (float)desktop->maxCLL / desktop->peakLuminance;
|
||||
}
|
||||
|
||||
void egl_desktopSpiceConfigure(EGL_Desktop * desktop, int width, int height)
|
||||
|
||||
@@ -25,7 +25,8 @@
|
||||
#include "egl.h"
|
||||
#include "desktop_rects.h"
|
||||
|
||||
typedef struct EGL_Desktop EGL_Desktop;
|
||||
typedef struct EGL_Desktop EGL_Desktop;
|
||||
typedef struct EGL_Framebuffer EGL_Framebuffer;
|
||||
|
||||
enum EGL_DesktopScaleType
|
||||
{
|
||||
@@ -42,7 +43,10 @@ bool egl_desktopInit(EGL * egl, EGL_Desktop ** desktop, EGLDisplay * display,
|
||||
void egl_desktopFree(EGL_Desktop ** desktop);
|
||||
|
||||
void egl_desktopConfigUI(EGL_Desktop * desktop);
|
||||
void egl_desktopSetNativeHDR(EGL_Desktop * desktop, bool nativeHDR);
|
||||
void egl_desktopSetNativeHDR(EGL_Desktop * desktop, bool nativeHDR,
|
||||
bool linearComposition);
|
||||
void egl_desktopGetHDRMapping(EGL_Desktop * desktop, bool * enabled,
|
||||
float * gain, float * contentPeak);
|
||||
bool egl_desktopSetup (EGL_Desktop * desktop, const LG_RendererFormat format);
|
||||
bool egl_desktopUpdate(EGL_Desktop * desktop, const FrameBuffer * frame, int dmaFd,
|
||||
const FrameDamageRect * damageRects, int damageRectsCount);
|
||||
@@ -51,7 +55,7 @@ bool egl_desktopRender(EGL_Desktop * desktop, unsigned int outputWidth,
|
||||
unsigned int outputHeight, const float x, const float y,
|
||||
const float scaleX, const float scaleY, enum EGL_DesktopScaleType scaleType,
|
||||
LG_RendererRotate rotate, const struct DamageRects * rects,
|
||||
bool * fullFrame);
|
||||
bool * fullFrame, EGL_Framebuffer * target);
|
||||
|
||||
void egl_desktopSpiceConfigure(EGL_Desktop * desktop, int width, int height);
|
||||
void egl_desktopSpiceDrawFill(EGL_Desktop * desktop, int x, int y, int width,
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
#include "desktop.h"
|
||||
#include "cursor.h"
|
||||
#include "hdr_overlay.h"
|
||||
#include "hdr_compose.h"
|
||||
#include "postprocess.h"
|
||||
#include "util.h"
|
||||
|
||||
@@ -83,6 +84,7 @@ struct Inst
|
||||
EGL_Cursor * cursor; // the mouse cursor
|
||||
EGL_Damage * damage; // the damage display
|
||||
EGL_HDROverlay * hdrOverlay;
|
||||
EGL_HDRCompose * hdrCompose;
|
||||
bool imgui; // if imgui was initialized
|
||||
|
||||
LG_RendererFormat format;
|
||||
@@ -132,6 +134,9 @@ struct Inst
|
||||
bool surfaceSupportsSCRGB;
|
||||
bool hdr; // true if current frame format is HDR
|
||||
bool nativeHDR; // true only while the surface HDR description is active
|
||||
bool nativeHDRPQ;
|
||||
float nativeReferenceWhiteLevel;
|
||||
bool linearHDRComposition;
|
||||
};
|
||||
|
||||
static struct Option egl_options[] =
|
||||
@@ -317,6 +322,7 @@ static void egl_deinitialize(LG_Renderer * renderer)
|
||||
egl_cursorFree (&this->cursor);
|
||||
egl_damageFree (&this->damage);
|
||||
egl_hdrOverlayFree(&this->hdrOverlay);
|
||||
egl_hdrComposeFree(&this->hdrCompose);
|
||||
|
||||
LG_LOCK_FREE(this->lock);
|
||||
LG_LOCK_FREE(this->desktopDamageLock);
|
||||
@@ -344,7 +350,8 @@ static bool egl_supports(LG_Renderer * renderer, LG_RendererSupport flag)
|
||||
return this->dmaSupport;
|
||||
|
||||
case LG_SUPPORTS_HDR_PQ:
|
||||
return this->surfaceSupportsPQ;
|
||||
return this->surfaceSupportsPQ &&
|
||||
egl_hdrComposeIsConfigured(this->hdrCompose);
|
||||
|
||||
case LG_SUPPORTS_HDR_SCRGB:
|
||||
return this->surfaceSupportsSCRGB;
|
||||
@@ -557,6 +564,9 @@ static void egl_onResize(LG_Renderer * renderer, const int width, const int heig
|
||||
|
||||
if (!egl_hdrOverlayResize(this->hdrOverlay, this->width, this->height))
|
||||
DEBUG_ERROR("Failed to resize the HDR overlay framebuffer");
|
||||
if (this->hdrCompose &&
|
||||
!egl_hdrComposeResize(this->hdrCompose, this->width, this->height))
|
||||
DEBUG_FATAL("Failed to resize the linear HDR composition framebuffer");
|
||||
|
||||
// this is needed to refresh the font atlas texture
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
@@ -619,18 +629,12 @@ static bool egl_updateHDRState(struct Inst * this, bool force)
|
||||
bool nativeHDR = false;
|
||||
app_getProp(LG_DS_NATIVE_HDR, &nativeHDR);
|
||||
const bool surfaceCompatible = this->format.hdrPQ ?
|
||||
this->surfaceSupportsPQ : this->surfaceSupportsSCRGB;
|
||||
(this->surfaceSupportsPQ &&
|
||||
egl_hdrComposeIsConfigured(this->hdrCompose)) :
|
||||
this->surfaceSupportsSCRGB;
|
||||
const bool useNativeHDR = !this->showSpice && this->format.hdr &&
|
||||
surfaceCompatible && nativeHDR && !app_getHDRDescFailed();
|
||||
|
||||
if (!force && this->nativeHDR == useNativeHDR)
|
||||
return false;
|
||||
|
||||
this->nativeHDR = useNativeHDR;
|
||||
egl_desktopSetNativeHDR(this->desktop, useNativeHDR);
|
||||
egl_cursorSetHDRState(this->cursor, useNativeHDR, this->format.hdrPQ,
|
||||
this->format.sdrWhiteLevel);
|
||||
|
||||
LG_DSHDRWhiteLevels whiteLevels =
|
||||
{
|
||||
.pq = 203,
|
||||
@@ -638,9 +642,29 @@ static bool egl_updateHDRState(struct Inst * this, bool force)
|
||||
};
|
||||
if (useNativeHDR && !app_getProp(LG_DS_HDR_WHITE_LEVELS, &whiteLevels))
|
||||
DEBUG_WARN("Display server did not provide native HDR white levels");
|
||||
const float referenceWhiteLevel = this->format.hdrPQ ?
|
||||
whiteLevels.pq : whiteLevels.scRGB;
|
||||
const bool stateChanged = this->nativeHDR != useNativeHDR ||
|
||||
(useNativeHDR &&
|
||||
(this->nativeHDRPQ != this->format.hdrPQ ||
|
||||
this->nativeReferenceWhiteLevel != referenceWhiteLevel));
|
||||
|
||||
if (!force && !stateChanged)
|
||||
return false;
|
||||
|
||||
this->nativeHDR = useNativeHDR;
|
||||
this->nativeHDRPQ = this->format.hdrPQ;
|
||||
this->nativeReferenceWhiteLevel = referenceWhiteLevel;
|
||||
this->linearHDRComposition = useNativeHDR && this->format.hdrPQ;
|
||||
egl_hdrComposeSetActive(this->hdrCompose,
|
||||
this->linearHDRComposition);
|
||||
egl_desktopSetNativeHDR(this->desktop, useNativeHDR,
|
||||
this->linearHDRComposition);
|
||||
egl_hdrOverlaySetState(this->hdrOverlay, useNativeHDR,
|
||||
this->format.hdrPQ,
|
||||
this->format.hdrPQ ? whiteLevels.pq : whiteLevels.scRGB);
|
||||
this->format.hdrPQ && !this->linearHDRComposition,
|
||||
referenceWhiteLevel);
|
||||
egl_damageSetHDRState(this->damage, useNativeHDR,
|
||||
referenceWhiteLevel);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -688,7 +712,9 @@ static bool egl_onFrameFormat(LG_Renderer * renderer, const LG_RendererFormat fo
|
||||
bool nativeHDR = false;
|
||||
app_getProp(LG_DS_NATIVE_HDR, &nativeHDR);
|
||||
const bool surfaceCompatible = format.hdrPQ ?
|
||||
this->surfaceSupportsPQ : this->surfaceSupportsSCRGB;
|
||||
(this->surfaceSupportsPQ &&
|
||||
egl_hdrComposeIsConfigured(this->hdrCompose)) :
|
||||
this->surfaceSupportsSCRGB;
|
||||
if (!surfaceCompatible)
|
||||
DEBUG_WARN("HDR frames being displayed on an 8-bit EGL surface "
|
||||
"or an EGL surface incompatible with the source encoding");
|
||||
@@ -1049,6 +1075,15 @@ static bool egl_renderStartup(LG_Renderer * renderer, bool useDMA)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->surfaceSupportsPQ &&
|
||||
!util_hasGLExt(gl_exts, "GL_EXT_color_buffer_half_float") &&
|
||||
!util_hasGLExt(gl_exts, "GL_EXT_color_buffer_float"))
|
||||
{
|
||||
DEBUG_WARN("Half-float render targets are unavailable; native PQ "
|
||||
"composition disabled");
|
||||
this->surfaceSupportsPQ = false;
|
||||
}
|
||||
|
||||
this->hasBufferAge = util_hasGLExt(client_exts, "EGL_EXT_buffer_age");
|
||||
if (!this->hasBufferAge)
|
||||
DEBUG_WARN("GL_EXT_buffer_age is not supported, will not perform as well.");
|
||||
@@ -1130,6 +1165,14 @@ static bool egl_renderStartup(LG_Renderer * renderer, bool useDMA)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->surfaceSupportsPQ &&
|
||||
!egl_hdrComposeInit(&this->hdrCompose))
|
||||
{
|
||||
DEBUG_WARN("Native PQ output disabled: failed to initialize linear "
|
||||
"HDR composition");
|
||||
this->surfaceSupportsPQ = false;
|
||||
}
|
||||
|
||||
if (!ImGui_ImplOpenGL3_Init("#version 300 es"))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize ImGui");
|
||||
@@ -1203,6 +1246,36 @@ static int egl_clipSurfaceDamage(
|
||||
return out;
|
||||
}
|
||||
|
||||
static int egl_mergeSurfaceDamage(struct Rect * damage, int count)
|
||||
{
|
||||
if (count <= 1)
|
||||
return count;
|
||||
|
||||
FrameDamageRect rects[count];
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
rects[i] = (FrameDamageRect) {
|
||||
.x = damage[i].x,
|
||||
.y = damage[i].y,
|
||||
.width = damage[i].w,
|
||||
.height = damage[i].h,
|
||||
};
|
||||
}
|
||||
|
||||
count = rectsMergeOverlapping(rects, count);
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
damage[i] = (struct Rect) {
|
||||
.x = rects[i].x,
|
||||
.y = rects[i].y,
|
||||
.w = rects[i].width,
|
||||
.h = rects[i].height,
|
||||
};
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
inline static void renderLetterBox(struct Inst * this)
|
||||
{
|
||||
bool hLB = this->destRect.x > 0;
|
||||
@@ -1248,12 +1321,20 @@ static bool egl_render(LG_Renderer * renderer, LG_RendererRotate rotate,
|
||||
{
|
||||
struct Inst * this = UPCAST(struct Inst, renderer);
|
||||
egl_stateCheckShared();
|
||||
EGLint bufferAge = egl_bufferAge(this);
|
||||
EGLint bufferAge = egl_bufferAge(this);
|
||||
const bool hdrStateChanged = egl_updateHDRState(this, false);
|
||||
bool renderAll = hdrStateChanged ||
|
||||
invalidateWindow || this->hadOverlay ||
|
||||
bufferAge <= 0 || bufferAge > MAX_BUFFER_AGE ||
|
||||
this->showSpice;
|
||||
bool mapCursorHDR;
|
||||
float mapCursorGain;
|
||||
float mapCursorContentPeak;
|
||||
egl_desktopGetHDRMapping(this->desktop, &mapCursorHDR,
|
||||
&mapCursorGain, &mapCursorContentPeak);
|
||||
egl_cursorSetHDRState(this->cursor,
|
||||
this->format.hdr && !this->showSpice, this->nativeHDR, mapCursorHDR,
|
||||
this->format.hdrPQ, mapCursorGain, mapCursorContentPeak);
|
||||
bool renderAll = hdrStateChanged ||
|
||||
invalidateWindow || this->hadOverlay ||
|
||||
bufferAge <= 0 || bufferAge > MAX_BUFFER_AGE ||
|
||||
this->showSpice;
|
||||
|
||||
bool hasOverlay = false;
|
||||
struct CursorState cursorState = { .visible = false };
|
||||
@@ -1328,7 +1409,9 @@ static bool egl_render(LG_Renderer * renderer, LG_RendererRotate rotate,
|
||||
}
|
||||
++this->overlayHistoryIdx;
|
||||
|
||||
bool fullFrame = false;
|
||||
bool fullFrame = false;
|
||||
const bool linearHDRComposition = egl_hdrComposeBegin(this->hdrCompose);
|
||||
bool deferredLogicalCursor = false;
|
||||
if (likely(this->destRect.w > 0 && this->destRect.h > 0))
|
||||
{
|
||||
if (egl_desktopRender(this->desktop,
|
||||
@@ -1336,11 +1419,12 @@ static bool egl_render(LG_Renderer * renderer, LG_RendererRotate rotate,
|
||||
this->translateX, this->translateY,
|
||||
this->scaleX , this->scaleY ,
|
||||
this->scaleType , rotate, renderAll ? NULL : accumulated,
|
||||
&fullFrame))
|
||||
&fullFrame, egl_hdrComposeGetFramebuffer(this->hdrCompose)))
|
||||
{
|
||||
cursorState = egl_cursorRender(this->cursor,
|
||||
(this->format.rotate + rotate) % LG_ROTATE_MAX,
|
||||
this->width, this->height);
|
||||
this->width, this->height,
|
||||
linearHDRComposition, &deferredLogicalCursor);
|
||||
}
|
||||
else
|
||||
hasOverlay = true;
|
||||
@@ -1364,13 +1448,18 @@ static bool egl_render(LG_Renderer * renderer, LG_RendererRotate rotate,
|
||||
if (damageIdx == -1)
|
||||
hasOverlay = true;
|
||||
|
||||
const bool hdrOverlay = egl_hdrOverlayBegin(this->hdrOverlay,
|
||||
damage, damageIdx);
|
||||
const bool hdrOverlay = egl_hdrOverlayBegin(
|
||||
this->hdrOverlay, damage, damageIdx);
|
||||
const bool drawOverlay = !this->nativeHDR || hdrOverlay;
|
||||
ImGui_ImplOpenGL3_NewFrame();
|
||||
ImGui_ImplOpenGL3_RenderDrawData(igGetDrawData());
|
||||
egl_stateInvalidate();
|
||||
if (drawOverlay)
|
||||
{
|
||||
ImGui_ImplOpenGL3_RenderDrawData(igGetDrawData());
|
||||
egl_stateInvalidate();
|
||||
}
|
||||
if (hdrOverlay)
|
||||
egl_hdrOverlayEnd(this->hdrOverlay, damage, damageIdx);
|
||||
egl_hdrOverlayEnd(this->hdrOverlay, damage, damageIdx,
|
||||
egl_hdrComposeGetFramebuffer(this->hdrCompose));
|
||||
|
||||
for (int i = 0; i < damageIdx; ++i)
|
||||
damage[i].y = this->height - damage[i].y - damage[i].h;
|
||||
@@ -1422,6 +1511,37 @@ static bool egl_render(LG_Renderer * renderer, LG_RendererRotate rotate,
|
||||
damageIdx = egl_clipSurfaceDamage(
|
||||
damage, damageIdx, this->width, this->height);
|
||||
|
||||
struct Rect * encodeDamage = damage;
|
||||
int encodeDamageCount =
|
||||
linearHDRComposition && renderAll ? 0 : damageIdx;
|
||||
if (linearHDRComposition && encodeDamageCount > 0)
|
||||
{
|
||||
const int capacity = damageIdx + accumulated->count;
|
||||
encodeDamage = alloca(capacity * sizeof(*encodeDamage));
|
||||
memcpy(encodeDamage, damage, damageIdx * sizeof(*encodeDamage));
|
||||
encodeDamageCount = damageIdx;
|
||||
|
||||
double matrix[6];
|
||||
egl_desktopToScreenMatrix(matrix,
|
||||
this->format.frameWidth, this->format.frameHeight,
|
||||
this->translateX, this->translateY, this->scaleX, this->scaleY, rotate,
|
||||
this->width, this->height);
|
||||
for (int i = 0; i < accumulated->count; ++i)
|
||||
encodeDamage[encodeDamageCount++] =
|
||||
egl_desktopToScreen(matrix, accumulated->rects + i);
|
||||
|
||||
encodeDamageCount = egl_clipSurfaceDamage(
|
||||
encodeDamage, encodeDamageCount, this->width, this->height);
|
||||
encodeDamageCount = egl_mergeSurfaceDamage(
|
||||
encodeDamage, encodeDamageCount);
|
||||
}
|
||||
|
||||
egl_hdrComposeEnd(this->hdrCompose, encodeDamage, encodeDamageCount);
|
||||
if (deferredLogicalCursor && cursorState.visible)
|
||||
egl_cursorRender(this->cursor,
|
||||
(this->format.rotate + rotate) % LG_ROTATE_MAX,
|
||||
this->width, this->height, false, NULL);
|
||||
|
||||
this->hadOverlay = hasOverlay;
|
||||
this->cursorLast = cursorState;
|
||||
|
||||
|
||||
176
client/renderers/EGL/hdr_compose.c
Normal file
176
client/renderers/EGL/hdr_compose.c
Normal file
@@ -0,0 +1,176 @@
|
||||
/**
|
||||
* 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_compose.h"
|
||||
|
||||
#include "framebuffer.h"
|
||||
#include "model.h"
|
||||
#include "shader.h"
|
||||
#include "state.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "hdr_compose.vert.h"
|
||||
#include "hdr_compose.frag.h"
|
||||
|
||||
struct EGL_HDRCompose
|
||||
{
|
||||
EGL_Framebuffer * framebuffer;
|
||||
EGL_Shader * shader;
|
||||
EGL_Model * model;
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
bool configured;
|
||||
bool active;
|
||||
};
|
||||
|
||||
bool egl_hdrComposeInit(EGL_HDRCompose ** compose)
|
||||
{
|
||||
*compose = calloc(1, sizeof(**compose));
|
||||
if (!*compose)
|
||||
return false;
|
||||
|
||||
EGL_HDRCompose * this = *compose;
|
||||
if (!egl_framebufferInit(&this->framebuffer) ||
|
||||
!egl_shaderInit(&this->shader) ||
|
||||
!egl_shaderCompile(this->shader,
|
||||
b_shader_hdr_compose_vert, b_shader_hdr_compose_vert_size,
|
||||
b_shader_hdr_compose_frag, b_shader_hdr_compose_frag_size,
|
||||
false, NULL) ||
|
||||
!egl_modelInit(&this->model))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize linear HDR composition");
|
||||
egl_hdrComposeFree(compose);
|
||||
return false;
|
||||
}
|
||||
|
||||
egl_modelSetDefault(this->model, false);
|
||||
egl_modelSetShader(this->model, this->shader);
|
||||
egl_modelSetTexture(this->model,
|
||||
egl_framebufferGetTexture(this->framebuffer));
|
||||
|
||||
/* Prove that an FP16 render target can actually be created before native
|
||||
* PQ support is advertised. The real size is installed on the first
|
||||
* resize. */
|
||||
if (!egl_framebufferSetup(this->framebuffer, EGL_PF_RGBA16F, 1, 1))
|
||||
{
|
||||
DEBUG_ERROR("Failed to create the linear HDR composition target");
|
||||
egl_hdrComposeFree(compose);
|
||||
return false;
|
||||
}
|
||||
this->width = 1;
|
||||
this->height = 1;
|
||||
this->configured = true;
|
||||
|
||||
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);
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_hdrComposeFree(EGL_HDRCompose ** compose)
|
||||
{
|
||||
if (!*compose)
|
||||
return;
|
||||
|
||||
EGL_HDRCompose * this = *compose;
|
||||
egl_modelFree(&this->model);
|
||||
egl_shaderFree(&this->shader);
|
||||
egl_framebufferFree(&this->framebuffer);
|
||||
free(this);
|
||||
*compose = NULL;
|
||||
}
|
||||
|
||||
bool egl_hdrComposeResize(EGL_HDRCompose * this,
|
||||
unsigned int width, unsigned int height)
|
||||
{
|
||||
if (!this)
|
||||
return false;
|
||||
|
||||
const bool wasActive = this->active;
|
||||
this->width = width;
|
||||
this->height = height;
|
||||
this->configured = false;
|
||||
this->active = false;
|
||||
|
||||
if (!width || !height)
|
||||
return true;
|
||||
|
||||
this->configured = egl_framebufferSetup(this->framebuffer,
|
||||
EGL_PF_RGBA16F, width, height);
|
||||
this->active = wasActive && this->configured;
|
||||
return this->configured;
|
||||
}
|
||||
|
||||
void egl_hdrComposeSetActive(EGL_HDRCompose * this, bool active)
|
||||
{
|
||||
if (!this)
|
||||
return;
|
||||
|
||||
this->active = active && this->configured;
|
||||
}
|
||||
|
||||
bool egl_hdrComposeIsConfigured(EGL_HDRCompose * this)
|
||||
{
|
||||
return this && this->configured;
|
||||
}
|
||||
|
||||
bool egl_hdrComposeBegin(EGL_HDRCompose * this)
|
||||
{
|
||||
if (!this || !this->active)
|
||||
return false;
|
||||
|
||||
egl_framebufferBind(this->framebuffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
void egl_hdrComposeEnd(EGL_HDRCompose * this,
|
||||
const struct Rect * damage, int damageCount)
|
||||
{
|
||||
if (!this || !this->active)
|
||||
return;
|
||||
|
||||
egl_stateBindFramebuffer(0);
|
||||
egl_stateViewport(0, 0, this->width, this->height);
|
||||
egl_stateBlend(false);
|
||||
|
||||
if (damageCount <= 0)
|
||||
{
|
||||
egl_stateScissor(false);
|
||||
egl_modelRender(this->model);
|
||||
return;
|
||||
}
|
||||
|
||||
egl_stateScissor(true);
|
||||
for (int i = 0; i < damageCount; ++i)
|
||||
{
|
||||
glScissor(damage[i].x, damage[i].y,
|
||||
damage[i].w, damage[i].h);
|
||||
egl_modelRender(this->model);
|
||||
}
|
||||
egl_stateScissor(false);
|
||||
}
|
||||
|
||||
EGL_Framebuffer * egl_hdrComposeGetFramebuffer(EGL_HDRCompose * this)
|
||||
{
|
||||
return this && this->active ? this->framebuffer : NULL;
|
||||
}
|
||||
39
client/renderers/EGL/hdr_compose.h
Normal file
39
client/renderers/EGL/hdr_compose.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* 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_Framebuffer EGL_Framebuffer;
|
||||
typedef struct EGL_HDRCompose EGL_HDRCompose;
|
||||
|
||||
bool egl_hdrComposeInit(EGL_HDRCompose ** compose);
|
||||
void egl_hdrComposeFree(EGL_HDRCompose ** compose);
|
||||
bool egl_hdrComposeResize(EGL_HDRCompose * compose,
|
||||
unsigned int width, unsigned int height);
|
||||
void egl_hdrComposeSetActive(EGL_HDRCompose * compose, bool active);
|
||||
bool egl_hdrComposeIsConfigured(EGL_HDRCompose * compose);
|
||||
bool egl_hdrComposeBegin(EGL_HDRCompose * compose);
|
||||
void egl_hdrComposeEnd(EGL_HDRCompose * compose,
|
||||
const struct Rect * damage, int damageCount);
|
||||
EGL_Framebuffer * egl_hdrComposeGetFramebuffer(EGL_HDRCompose * compose);
|
||||
@@ -202,10 +202,16 @@ bool egl_hdrOverlayBegin(EGL_HDROverlay * this,
|
||||
}
|
||||
|
||||
void egl_hdrOverlayEnd(EGL_HDROverlay * this,
|
||||
const struct Rect * damage, int damageCount)
|
||||
const struct Rect * damage, int damageCount,
|
||||
EGL_Framebuffer * target)
|
||||
{
|
||||
egl_stateBindFramebuffer(0);
|
||||
egl_stateViewport(0, 0, this->width, this->height);
|
||||
if (target)
|
||||
egl_framebufferBind(target);
|
||||
else
|
||||
{
|
||||
egl_stateBindFramebuffer(0);
|
||||
egl_stateViewport(0, 0, this->width, this->height);
|
||||
}
|
||||
|
||||
egl_uniform1i(this->uPQ, this->renderPQ);
|
||||
egl_uniform1f(this->uReferenceWhiteLevel,
|
||||
|
||||
@@ -24,7 +24,8 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct EGL_HDROverlay EGL_HDROverlay;
|
||||
typedef struct EGL_HDROverlay EGL_HDROverlay;
|
||||
typedef struct EGL_Framebuffer EGL_Framebuffer;
|
||||
|
||||
bool egl_hdrOverlayInit(EGL_HDROverlay ** overlay);
|
||||
void egl_hdrOverlayFree(EGL_HDROverlay ** overlay);
|
||||
@@ -42,4 +43,5 @@ void egl_hdrOverlaySetState(EGL_HDROverlay * overlay, bool active, bool pq,
|
||||
bool egl_hdrOverlayBegin(EGL_HDROverlay * overlay,
|
||||
const struct Rect * damage, int damageCount);
|
||||
void egl_hdrOverlayEnd(EGL_HDROverlay * overlay,
|
||||
const struct Rect * damage, int damageCount);
|
||||
const struct Rect * damage, int damageCount,
|
||||
EGL_Framebuffer * target);
|
||||
|
||||
@@ -2,15 +2,11 @@
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
|
||||
#include "hdr.h"
|
||||
|
||||
in vec2 uv;
|
||||
out vec4 color;
|
||||
|
||||
uniform sampler2D sampler1;
|
||||
uniform float scale;
|
||||
uniform int wireTransfer; // 0: sRGB, 1: scRGB, 2: PQ
|
||||
uniform float sdrWhiteLevel;
|
||||
|
||||
void main()
|
||||
{
|
||||
@@ -30,13 +26,7 @@ void main()
|
||||
if (tmp.rgb == vec3(0.0, 0.0, 0.0))
|
||||
discard;
|
||||
|
||||
if (wireTransfer == 2)
|
||||
{
|
||||
vec3 linear = bt709to2020(srgb2lin(tmp.rgb));
|
||||
tmp.rgb = lin2pq(linear * (sdrWhiteLevel / 10000.0));
|
||||
}
|
||||
else if (wireTransfer == 1)
|
||||
tmp.rgb = srgb2lin(tmp.rgb) * (sdrWhiteLevel / 80.0);
|
||||
|
||||
// Logical cursor masks operate on the encoded destination values. Their
|
||||
// binary AND/XOR operands must not be colour converted.
|
||||
color = tmp;
|
||||
}
|
||||
|
||||
@@ -12,11 +12,15 @@ uniform sampler2D sampler1;
|
||||
uniform sampler2D sampler2;
|
||||
uniform float scale;
|
||||
uniform int cbMode;
|
||||
uniform int wireTransfer; // 0: sRGB, 1: scRGB, 2: PQ
|
||||
uniform int sourceTransfer;
|
||||
uniform float sdrWhiteLevel;
|
||||
uniform bool mapHDRtoSDR;
|
||||
uniform float mapHDRGain;
|
||||
uniform float mapHDRContentPeak;
|
||||
uniform uint colorTransformFlags;
|
||||
uniform vec4 colorMatrix[3];
|
||||
uniform float colorTransformScalar;
|
||||
uniform bool linearComposition;
|
||||
|
||||
const uint COLOR_TRANSFORM_MATRIX = 1u;
|
||||
const uint COLOR_TRANSFORM_LUT = 2u;
|
||||
@@ -47,10 +51,10 @@ vec3 xyzToBT2020(vec3 xyz)
|
||||
|
||||
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);
|
||||
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),
|
||||
@@ -74,10 +78,9 @@ void main()
|
||||
else
|
||||
color = texture(sampler1, uv);
|
||||
|
||||
if (cbMode > 0)
|
||||
color = cbTransform(color, cbMode);
|
||||
|
||||
if (color.a > 0.0 && (wireTransfer != 0 || colorTransformFlags != 0u))
|
||||
if (color.a > 0.0 &&
|
||||
(sourceTransfer != TRANSFER_SRGB ||
|
||||
colorTransformFlags != 0u || cbMode > 0))
|
||||
{
|
||||
// Cursor pixels are premultiplied sRGB. Work on straight, linear BT.709
|
||||
// values through the XYZ calibration stage, encode for the active wire
|
||||
@@ -90,20 +93,36 @@ void main()
|
||||
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);
|
||||
value = sourceTransfer == TRANSFER_PQ ?
|
||||
xyzToBT2020(xyz) : xyzToBT709(xyz);
|
||||
}
|
||||
else if (wireTransfer == 2)
|
||||
else if (sourceTransfer == TRANSFER_PQ)
|
||||
value = bt709to2020(value);
|
||||
|
||||
if (wireTransfer == 2)
|
||||
if (sourceTransfer == TRANSFER_PQ)
|
||||
value = lin2pq(max(value, 0.0) * (sdrWhiteLevel / 10000.0));
|
||||
else if (wireTransfer == 1)
|
||||
else if (sourceTransfer == TRANSFER_SCRGB)
|
||||
value *= sdrWhiteLevel / 80.0;
|
||||
else
|
||||
value = lin2srgb(max(value, 0.0));
|
||||
|
||||
if ((colorTransformFlags & COLOR_TRANSFORM_LUT) != 0u)
|
||||
value = applyColorLUT(value);
|
||||
|
||||
if (mapHDRtoSDR)
|
||||
value = mapToSDR(value, mapHDRGain,
|
||||
mapHDRContentPeak, sourceTransfer == TRANSFER_PQ);
|
||||
// Native PQ surfaces are composed through a linear scRGB framebuffer.
|
||||
// Preserve the guest's transfer-domain LUT by decoding it only after the
|
||||
// complete guest transform has been applied.
|
||||
else if (linearComposition && sourceTransfer == TRANSFER_PQ)
|
||||
value = bt2020to709(pq2lin(value, 1.0)) * 125.0;
|
||||
|
||||
// Apply SDR-only accessibility transforms after HDR-to-SDR mapping and
|
||||
// to straight colour so translucent cursor edges remain premultiplied
|
||||
// correctly.
|
||||
if (cbMode > 0)
|
||||
value = cbTransform(vec4(value, 1.0), cbMode).rgb;
|
||||
color.rgb = value * color.a;
|
||||
}
|
||||
else if (color.a == 0.0)
|
||||
|
||||
@@ -2,9 +2,17 @@
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
|
||||
#include "hdr.h"
|
||||
|
||||
out vec4 color;
|
||||
|
||||
uniform bool linearOutput;
|
||||
uniform float referenceWhiteLevel;
|
||||
|
||||
void main()
|
||||
{
|
||||
color = vec4(1.0, 1.0, 0.0, 0.5);
|
||||
vec3 value = vec3(1.0, 1.0, 0.0);
|
||||
if (linearOutput)
|
||||
value = srgb2lin(value) * (referenceWhiteLevel / 80.0);
|
||||
color = vec4(value, 0.5);
|
||||
}
|
||||
|
||||
@@ -24,15 +24,17 @@ uniform int cbMode;
|
||||
uniform bool isHDR;
|
||||
uniform bool mapHDRtoSDR;
|
||||
uniform float mapHDRGain;
|
||||
uniform float mapHDRContentPeak;
|
||||
uniform bool mapHDRPQ;
|
||||
uniform bool outputHDRLinear;
|
||||
|
||||
vec4 samplePQLinear(vec2 coord)
|
||||
vec3 samplePQLinear(vec2 coord)
|
||||
{
|
||||
ivec2 size = textureSize(sampler1, 0);
|
||||
vec2 samplePos = coord * vec2(size) - 0.5;
|
||||
ivec2 base = ivec2(floor(samplePos));
|
||||
vec2 f = fract(samplePos);
|
||||
ivec2 limit = size - ivec2(1);
|
||||
ivec2 size = textureSize(sampler1, 0);
|
||||
vec2 samplePos = coord * vec2(size) - 0.5;
|
||||
ivec2 base = ivec2(floor(samplePos));
|
||||
vec2 f = fract(samplePos);
|
||||
ivec2 limit = size - ivec2(1);
|
||||
|
||||
vec3 c00 = pq2lin(texelFetch(sampler1,
|
||||
clamp(base, ivec2(0), limit), 0).rgb, 1.0);
|
||||
@@ -44,11 +46,12 @@ vec4 samplePQLinear(vec2 coord)
|
||||
clamp(base + ivec2(1, 1), ivec2(0), limit), 0).rgb, 1.0);
|
||||
|
||||
vec3 linear = mix(mix(c00, c10, f.x), mix(c01, c11, f.x), f.y);
|
||||
return vec4(lin2pq(max(linear, 0.0)), 1.0);
|
||||
return max(linear, 0.0);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
bool sampledPQLinear = false;
|
||||
switch (scaleAlgo)
|
||||
{
|
||||
case EGL_SCALE_NEAREST:
|
||||
@@ -60,13 +63,28 @@ void main()
|
||||
|
||||
case EGL_SCALE_LINEAR:
|
||||
{
|
||||
color = isHDR && mapHDRPQ ? samplePQLinear(uv) : texture(sampler1, uv);
|
||||
if (isHDR && mapHDRPQ)
|
||||
{
|
||||
vec3 linear = samplePQLinear(uv);
|
||||
color = vec4(
|
||||
outputHDRLinear ? linear : lin2pq(linear), 1.0);
|
||||
sampledPQLinear = outputHDRLinear;
|
||||
}
|
||||
else
|
||||
color = texture(sampler1, uv);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isHDR && mapHDRtoSDR)
|
||||
color.rgb = mapToSDR(color.rgb, mapHDRGain, mapHDRPQ);
|
||||
color.rgb = mapToSDR(color.rgb, mapHDRGain,
|
||||
mapHDRContentPeak, mapHDRPQ);
|
||||
else if (isHDR && outputHDRLinear && mapHDRPQ)
|
||||
{
|
||||
vec3 linear2020 = sampledPQLinear ?
|
||||
color.rgb : pq2lin(color.rgb, 1.0);
|
||||
color.rgb = bt2020to709(linear2020) * 125.0;
|
||||
}
|
||||
|
||||
// The legacy effects are defined for an SDR signal. Do not apply them to a
|
||||
// native HDR signal where their nonlinear operations would alter luminance
|
||||
|
||||
@@ -29,47 +29,24 @@ const float c1 = 3424.0 / 4096.0;
|
||||
const float c2 = 2413.0 / 128.0;
|
||||
const float c3 = 2392.0 / 128.0;
|
||||
|
||||
float minGain(vec3 pixel) { return min(pixel.r, min(pixel.g, pixel.b)); }
|
||||
float maxGain(vec3 pixel) { return max(pixel.r, max(pixel.g, pixel.b)); }
|
||||
float midGain(vec3 pixel)
|
||||
{
|
||||
return pixel.r < pixel.g ?
|
||||
(pixel.r < pixel.b ?
|
||||
min(pixel.g, pixel.b) : // min = r
|
||||
min(pixel.r, pixel.g)) : // min = b
|
||||
(pixel.g < pixel.b ?
|
||||
min(pixel.r, pixel.b) : // min = g
|
||||
min(pixel.r, pixel.g)); // min = b
|
||||
}
|
||||
const int TRANSFER_SRGB = 0;
|
||||
const int TRANSFER_SCRGB = 1;
|
||||
const int TRANSFER_PQ = 2;
|
||||
|
||||
vec3 compress(vec3 pixel)
|
||||
{
|
||||
float maxGain = maxGain(pixel);
|
||||
return pixel * (maxGain < knee ? maxGain :
|
||||
knee + max(maxGain - knee, 0.0) * compressor) / maxGain;
|
||||
}
|
||||
|
||||
vec3 fixClip(vec3 pixel)
|
||||
{
|
||||
// keep the (mid - min) / (max - min) ratio
|
||||
float preMin = minGain(pixel);
|
||||
float preMid = midGain(pixel);
|
||||
float preMax = maxGain(pixel);
|
||||
vec3 clip = clamp(pixel, 0.0, 1.0);
|
||||
float postMin = minGain(clip);
|
||||
float postMid = midGain(clip);
|
||||
float postMax = maxGain(clip);
|
||||
float ratio = (preMid - preMin) / (preMax - preMin);
|
||||
float newMid = ratio * (postMax - postMin) + postMin;
|
||||
return vec3(clip.r != postMid ? clip.r : newMid,
|
||||
clip.g != postMid ? clip.g : newMid,
|
||||
clip.b != postMid ? clip.b : newMid);
|
||||
pixel = max(pixel, 0.0);
|
||||
float peak = max(pixel.r, max(pixel.g, pixel.b));
|
||||
if (peak <= 0.0)
|
||||
return vec3(0.0);
|
||||
return pixel * (peak < knee ? peak :
|
||||
knee + (peak - knee) * compressor) / peak;
|
||||
}
|
||||
|
||||
// Returns luminance in nits
|
||||
vec3 pq2lin(vec3 pq, float gain)
|
||||
{
|
||||
vec3 p = pow(pq, vec3(m2inv));
|
||||
vec3 p = pow(max(pq, 0.0), vec3(m2inv));
|
||||
vec3 d = max(p - c1, vec3(0.0)) / (c2 - c3 * p);
|
||||
return pow(d, vec3(m1inv)) * gain;
|
||||
}
|
||||
@@ -77,25 +54,25 @@ vec3 pq2lin(vec3 pq, float gain)
|
||||
vec3 lin2pq(vec3 linear)
|
||||
{
|
||||
// ST.2084 uses absolute luminance normalized to 10000 cd/m².
|
||||
vec3 p = pow(linear, vec3(m1));
|
||||
vec3 p = pow(max(linear, 0.0), vec3(m1));
|
||||
return pow((c1 + c2 * p) / (1.0 + c3 * p), vec3(m2));
|
||||
}
|
||||
|
||||
vec3 srgb2lin(vec3 c)
|
||||
{
|
||||
vec3 v = c / 12.92;
|
||||
vec3 v2 = pow((c + vec3(0.055)) / 1.055, vec3(2.4));
|
||||
vec3 v = c / 12.92;
|
||||
vec3 v2 = pow((c + vec3(0.055)) / 1.055, vec3(2.4));
|
||||
vec3 threshold = vec3(0.04045);
|
||||
vec3 result = mix(v, v2, greaterThanEqual(c, threshold));
|
||||
vec3 result = mix(v, v2, greaterThanEqual(c, threshold));
|
||||
return result;
|
||||
}
|
||||
|
||||
vec3 lin2srgb(vec3 c)
|
||||
{
|
||||
vec3 v = c * 12.92;
|
||||
vec3 v2 = pow(c, vec3(1.0/2.4)) * 1.055 - 0.055;
|
||||
vec3 v = c * 12.92;
|
||||
vec3 v2 = pow(max(c, 0.0), vec3(1.0/2.4)) * 1.055 - 0.055;
|
||||
vec3 threshold = vec3(0.0031308);
|
||||
vec3 result = mix(v, v2, greaterThanEqual(c, threshold));
|
||||
vec3 result = mix(v, v2, greaterThanEqual(c, threshold));
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -103,29 +80,42 @@ vec3 lin2srgb(vec3 c)
|
||||
vec3 bt2020to709(vec3 bt2020)
|
||||
{
|
||||
return vec3(
|
||||
bt2020.r * 1.6605 + bt2020.g * -0.5876 + bt2020.b * -0.0728,
|
||||
bt2020.r * -0.1246 + bt2020.g * 1.1329 + bt2020.b * -0.0083,
|
||||
bt2020.r * -0.0182 + bt2020.g * -0.1006 + bt2020.b * 1.1187);
|
||||
bt2020.r * 1.6604910 + bt2020.g * -0.5876411 + bt2020.b * -0.0728499,
|
||||
bt2020.r * -0.1245505 + bt2020.g * 1.1328999 + bt2020.b * -0.0083494,
|
||||
bt2020.r * -0.0181508 + bt2020.g * -0.1005789 + bt2020.b * 1.1187297);
|
||||
}
|
||||
|
||||
// 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);
|
||||
bt709.r * 0.6274039 + bt709.g * 0.3292830 + bt709.b * 0.0433131,
|
||||
bt709.r * 0.0690973 + bt709.g * 0.9195404 + bt709.b * 0.0113623,
|
||||
bt709.r * 0.0163914 + bt709.g * 0.0880133 + bt709.b * 0.8955953);
|
||||
}
|
||||
|
||||
vec3 mapToSDR(vec3 color, float gain, bool pq)
|
||||
vec3 mapToSDR(vec3 color, float gain, float contentPeak, bool pq)
|
||||
{
|
||||
if (pq)
|
||||
{
|
||||
// HDR10: PQ-encoded BT.2020. Linearise then convert the gamut to BT.709.
|
||||
// HDR10: PQ-encoded BT.2020. Decode the absolute 10000-nit PQ range into
|
||||
// values relative to the configured SDR display peak. MaxCLL constrains
|
||||
// content luminance without changing that absolute unit conversion.
|
||||
color = pq2lin(color.rgb, gain);
|
||||
float luminance2020 =
|
||||
dot(color, vec3(0.2627002, 0.6779981, 0.0593017));
|
||||
if (contentPeak > 0.0 && luminance2020 > contentPeak)
|
||||
color *= contentPeak / luminance2020;
|
||||
color = bt2020to709(color);
|
||||
}
|
||||
// else: scRGB is already linear BT.709 (1.0 == SDR white), so no EOTF or
|
||||
// gamut conversion is required - only highlight compression below.
|
||||
return lin2srgb(compress(color));
|
||||
else
|
||||
{
|
||||
// scRGB is linear BT.709 with 1.0 fixed at 80 nits. Use the same
|
||||
// absolute-luminance mapping as PQ before compressing the highlights.
|
||||
color *= gain;
|
||||
float luminance709 = dot(color, vec3(0.2126390, 0.7151687, 0.0721923));
|
||||
if (contentPeak > 0.0 && luminance709 > contentPeak)
|
||||
color *= contentPeak / luminance709;
|
||||
}
|
||||
return lin2srgb(clamp(compress(color), 0.0, 1.0));
|
||||
}
|
||||
|
||||
18
client/renderers/EGL/shader/hdr_compose.frag
Normal file
18
client/renderers/EGL/shader/hdr_compose.frag
Normal file
@@ -0,0 +1,18 @@
|
||||
#version 300 es
|
||||
precision highp float;
|
||||
|
||||
#include "hdr.h"
|
||||
|
||||
in vec2 fragCoord;
|
||||
out vec4 color;
|
||||
|
||||
uniform sampler2D sampler1;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec3 scRGB = texture(sampler1, fragCoord).rgb;
|
||||
// Preserve negative BT.709 components used to represent colours outside
|
||||
// the BT.709 gamut. Clamp only after rotating back into BT.2020.
|
||||
vec3 linear2020 = bt709to2020(scRGB * (80.0 / 10000.0));
|
||||
color = vec4(lin2pq(max(linear2020, 0.0)), 1.0);
|
||||
}
|
||||
13
client/renderers/EGL/shader/hdr_compose.vert
Normal file
13
client/renderers/EGL/shader/hdr_compose.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