mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-07-20 14:22:00 +00:00
[client] egl: restore effects for PQ frames
PQ sources excluded every external filter from the post-process chain. Decode PQ/BT.2020 into FP16 linear scRGB before active effects and pass the resulting transfer state to the desktop shader. Keep the raw PQ path when no effect is active. This avoids adding a conversion pass or per-frame latency to the common case.
This commit is contained in:
@@ -60,6 +60,7 @@ build_shaders(
|
||||
shader/hdr_compose.vert
|
||||
shader/hdr_compose.frag
|
||||
shader/basic.vert
|
||||
shader/hdr_decode.frag
|
||||
shader/convert_24bit.frag
|
||||
shader/ffx_cas.frag
|
||||
shader/ffx_fsr1_easu.frag
|
||||
@@ -97,6 +98,7 @@ add_library(renderer_EGL STATIC
|
||||
ffx.c
|
||||
filter_glsl.c
|
||||
filter_24bit.c
|
||||
filter_hdr.c
|
||||
filter_ffx_cas.c
|
||||
filter_ffx_fsr1.c
|
||||
filter_downscale.c
|
||||
|
||||
@@ -497,14 +497,16 @@ bool egl_desktopRender(EGL_Desktop * desktop, unsigned int outputWidth,
|
||||
}
|
||||
|
||||
unsigned int finalSizeX, finalSizeY;
|
||||
bool outputHDRPQ = desktop->hdrPQ;
|
||||
EGL_Texture * texture = egl_postProcessGetOutput(desktop->pp,
|
||||
&finalSizeX, &finalSizeY);
|
||||
&finalSizeX, &finalSizeY, &outputHDRPQ);
|
||||
|
||||
if (unlikely(!texture))
|
||||
{
|
||||
texture = tex;
|
||||
finalSizeX = width;
|
||||
finalSizeY = height;
|
||||
texture = tex;
|
||||
finalSizeX = width;
|
||||
finalSizeY = height;
|
||||
outputHDRPQ = desktop->hdrPQ;
|
||||
}
|
||||
|
||||
if (target)
|
||||
@@ -546,7 +548,7 @@ bool egl_desktopRender(EGL_Desktop * desktop, unsigned int outputWidth,
|
||||
|
||||
/* 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) /
|
||||
const float mapHDRGain = (outputHDRPQ ? 10000.0f : 80.0f) /
|
||||
desktop->peakLuminance;
|
||||
const float mapHDRContentPeak =
|
||||
(float)desktop->maxCLL / desktop->peakLuminance;
|
||||
@@ -562,7 +564,7 @@ bool egl_desktopRender(EGL_Desktop * desktop, unsigned int outputWidth,
|
||||
desktop->mapHDRtoSDR && !desktop->nativeHDR);
|
||||
egl_uniform1f (shader->uMapHDRGain , mapHDRGain);
|
||||
egl_uniform1f (shader->uMapHDRContentPeak, mapHDRContentPeak);
|
||||
egl_uniform1i (shader->uMapHDRPQ , desktop->hdrPQ);
|
||||
egl_uniform1i (shader->uMapHDRPQ , outputHDRPQ);
|
||||
egl_uniform1i (shader->uOutputHDRLinear ,
|
||||
desktop->linearComposition);
|
||||
egl_shaderUse(shader->shader);
|
||||
|
||||
171
client/renderers/EGL/filter_hdr.c
Normal file
171
client/renderers/EGL/filter_hdr.c
Normal file
@@ -0,0 +1,171 @@
|
||||
/**
|
||||
* 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 "filter.h"
|
||||
#include "effect.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
|
||||
#include "basic.vert.h"
|
||||
#include "hdr_decode.frag.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef struct EGL_FilterHDR
|
||||
{
|
||||
EGL_Filter base;
|
||||
|
||||
int useDMA;
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
bool prepared;
|
||||
|
||||
EGL_Shader * shader;
|
||||
EGL_Effect * effect;
|
||||
EGL_EffectPass * pass;
|
||||
}
|
||||
EGL_FilterHDR;
|
||||
|
||||
static bool egl_filterHDRInit(EGL_Filter ** filter)
|
||||
{
|
||||
EGL_FilterHDR * this = calloc(1, sizeof(*this));
|
||||
if (!this)
|
||||
{
|
||||
DEBUG_ERROR("Failed to allocate memory");
|
||||
return false;
|
||||
}
|
||||
|
||||
this->useDMA = -1;
|
||||
|
||||
if (!egl_shaderInit(&this->shader))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the shader");
|
||||
goto error_this;
|
||||
}
|
||||
|
||||
if (!egl_effectInit(&this->effect))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the effect");
|
||||
goto error_shader;
|
||||
}
|
||||
|
||||
if (!egl_effectAddPass(this->effect, this->shader, &this->pass))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the effect pass");
|
||||
goto error_effect;
|
||||
}
|
||||
|
||||
egl_effectPassSetFilter(this->pass, GL_NEAREST, GL_NEAREST);
|
||||
*filter = &this->base;
|
||||
return true;
|
||||
|
||||
error_effect:
|
||||
egl_effectFree(&this->effect);
|
||||
|
||||
error_shader:
|
||||
egl_shaderFree(&this->shader);
|
||||
|
||||
error_this:
|
||||
free(this);
|
||||
return false;
|
||||
}
|
||||
|
||||
static void egl_filterHDRFree(EGL_Filter * filter)
|
||||
{
|
||||
EGL_FilterHDR * this = UPCAST(EGL_FilterHDR, filter);
|
||||
|
||||
egl_effectFree(&this->effect);
|
||||
egl_shaderFree(&this->shader);
|
||||
free(this);
|
||||
}
|
||||
|
||||
static bool egl_filterHDRSetup(EGL_Filter * filter,
|
||||
enum EGL_PixelFormat pixFmt, unsigned int width, unsigned int height,
|
||||
unsigned int desktopWidth, unsigned int desktopHeight, bool useDMA)
|
||||
{
|
||||
EGL_FilterHDR * this = UPCAST(EGL_FilterHDR, filter);
|
||||
(void)desktopWidth;
|
||||
(void)desktopHeight;
|
||||
|
||||
if (pixFmt != EGL_PF_RGBA10)
|
||||
return false;
|
||||
|
||||
if (this->useDMA != useDMA)
|
||||
{
|
||||
if (!egl_shaderCompile(this->shader,
|
||||
b_shader_basic_vert , b_shader_basic_vert_size,
|
||||
b_shader_hdr_decode_frag, b_shader_hdr_decode_frag_size,
|
||||
useDMA, NULL))
|
||||
{
|
||||
DEBUG_ERROR("Failed to compile the HDR decode shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
this->useDMA = useDMA;
|
||||
this->prepared = false;
|
||||
}
|
||||
|
||||
if (this->prepared && this->width == width && this->height == height)
|
||||
return true;
|
||||
|
||||
if (!egl_effectPassSetup(this->pass, EGL_PF_RGBA16F, width, height))
|
||||
return false;
|
||||
|
||||
this->width = width;
|
||||
this->height = height;
|
||||
this->prepared = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void egl_filterHDRGetOutputRes(EGL_Filter * filter,
|
||||
unsigned int * width, unsigned int * height, EGL_PixelFormat * pixFmt)
|
||||
{
|
||||
EGL_FilterHDR * this = UPCAST(EGL_FilterHDR, filter);
|
||||
*width = this->width;
|
||||
*height = this->height;
|
||||
*pixFmt = EGL_PF_RGBA16F;
|
||||
}
|
||||
|
||||
static bool egl_filterHDRPrepare(EGL_Filter * filter)
|
||||
{
|
||||
EGL_FilterHDR * this = UPCAST(EGL_FilterHDR, filter);
|
||||
this->prepared = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
static EGL_Texture * egl_filterHDRRun(EGL_Filter * filter,
|
||||
EGL_FilterRects * rects, EGL_Texture * texture)
|
||||
{
|
||||
EGL_FilterHDR * this = UPCAST(EGL_FilterHDR, filter);
|
||||
return egl_effectRun(this->effect, rects, texture);
|
||||
}
|
||||
|
||||
EGL_FilterOps egl_filterHDRDecodeOps =
|
||||
{
|
||||
.id = "hdrDecode",
|
||||
.name = "HDR Decode",
|
||||
.type = EGL_FILTER_TYPE_INTERNAL,
|
||||
.init = egl_filterHDRInit,
|
||||
.free = egl_filterHDRFree,
|
||||
.setup = egl_filterHDRSetup,
|
||||
.getOutputRes = egl_filterHDRGetOutputRes,
|
||||
.prepare = egl_filterHDRPrepare,
|
||||
.run = egl_filterHDRRun
|
||||
};
|
||||
@@ -21,6 +21,7 @@
|
||||
#pragma once
|
||||
|
||||
extern EGL_FilterOps egl_filter24bitOps;
|
||||
extern EGL_FilterOps egl_filterHDRDecodeOps;
|
||||
extern EGL_FilterOps egl_filterDownscaleOps;
|
||||
extern EGL_FilterOps egl_filterFFXCASOps;
|
||||
extern EGL_FilterOps egl_filterFFXFSR1Ops;
|
||||
|
||||
@@ -49,7 +49,11 @@ static const EGL_FilterOps * EGL_Filters[] =
|
||||
|
||||
struct EGL_PostProcess
|
||||
{
|
||||
Vector filters, internalFilters, activeFilters;
|
||||
Vector filters;
|
||||
Vector internalFilters;
|
||||
Vector activeFilters;
|
||||
Vector effectFilters;
|
||||
EGL_Filter * hdrDecode;
|
||||
EGL_Texture * output;
|
||||
unsigned int outputX, outputY;
|
||||
_Atomic(bool) modified;
|
||||
@@ -64,6 +68,7 @@ struct EGL_PostProcess
|
||||
bool useDMA;
|
||||
bool hdrPQ;
|
||||
unsigned int outputX, outputY;
|
||||
bool outputHDRPQ;
|
||||
bool fullFrame;
|
||||
}
|
||||
config;
|
||||
@@ -588,16 +593,29 @@ bool egl_postProcessInit(EGL_PostProcess ** pp)
|
||||
}
|
||||
|
||||
if (!vector_create(&this->activeFilters,
|
||||
sizeof(EGL_Filter *), ARRAY_LENGTH(EGL_Filters) + 1))
|
||||
sizeof(EGL_Filter *), ARRAY_LENGTH(EGL_Filters) + 2))
|
||||
{
|
||||
DEBUG_ERROR("Failed to allocate the active filter list");
|
||||
goto error_internal;
|
||||
}
|
||||
|
||||
if (!vector_create(&this->effectFilters,
|
||||
sizeof(EGL_Filter *), ARRAY_LENGTH(EGL_Filters)))
|
||||
{
|
||||
DEBUG_ERROR("Failed to allocate the effect filter list");
|
||||
goto error_active;
|
||||
}
|
||||
|
||||
if (!egl_filterInit(&egl_filterHDRDecodeOps, &this->hdrDecode))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the HDR decode filter");
|
||||
goto error_effect;
|
||||
}
|
||||
|
||||
if (!egl_desktopRectsInit(&this->rects, 1))
|
||||
{
|
||||
DEBUG_ERROR("Failed to initialize the desktop rects");
|
||||
goto error_active;
|
||||
goto error_hdr;
|
||||
}
|
||||
|
||||
loadPresetList(this);
|
||||
@@ -607,6 +625,12 @@ bool egl_postProcessInit(EGL_PostProcess ** pp)
|
||||
*pp = this;
|
||||
return true;
|
||||
|
||||
error_hdr:
|
||||
egl_filterFree(&this->hdrDecode);
|
||||
|
||||
error_effect:
|
||||
vector_destroy(&this->effectFilters);
|
||||
|
||||
error_active:
|
||||
vector_destroy(&this->activeFilters);
|
||||
|
||||
@@ -628,6 +652,8 @@ void egl_postProcessFree(EGL_PostProcess ** pp)
|
||||
|
||||
EGL_PostProcess * this = *pp;
|
||||
|
||||
egl_filterFree(&this->hdrDecode);
|
||||
vector_destroy(&this->effectFilters);
|
||||
vector_destroy(&this->activeFilters);
|
||||
|
||||
EGL_Filter ** filter;
|
||||
@@ -728,6 +754,7 @@ static bool configureChain(EGL_PostProcess * this, EGL_PixelFormat pixFmt,
|
||||
{
|
||||
this->config.valid = false;
|
||||
vector_clear(&this->activeFilters);
|
||||
vector_clear(&this->effectFilters);
|
||||
|
||||
unsigned int outputX = inputX;
|
||||
unsigned int outputY = inputY;
|
||||
@@ -736,45 +763,97 @@ static bool configureChain(EGL_PostProcess * this, EGL_PixelFormat pixFmt,
|
||||
bool fullFrame = false;
|
||||
|
||||
EGL_Filter * filter;
|
||||
const Vector * lists[] =
|
||||
vector_forEach(filter, &this->internalFilters)
|
||||
{
|
||||
&this->internalFilters,
|
||||
&this->filters,
|
||||
NULL
|
||||
};
|
||||
egl_filterSetOutputResHint(filter, targetX, targetY);
|
||||
|
||||
for(const Vector ** filters = lists; *filters; ++filters)
|
||||
vector_forEach(filter, *filters)
|
||||
if (!egl_filterSetup(filter, outputFormat, outputX, outputY,
|
||||
desktopWidth, desktopHeight, inputDMA) ||
|
||||
!egl_filterPrepare(filter))
|
||||
continue;
|
||||
|
||||
if (!vector_push(&this->activeFilters, &filter))
|
||||
{
|
||||
// These filters operate on their input signal directly. PQ must be
|
||||
// decoded to linear light before resampling or enhancement, and none of
|
||||
// the current external filters declares such support. Keep only the
|
||||
// mandatory format-conversion filters in a PQ chain.
|
||||
if (hdrPQ && filter->ops.type != EGL_FILTER_TYPE_INTERNAL)
|
||||
continue;
|
||||
|
||||
egl_filterSetOutputResHint(filter, targetX, targetY);
|
||||
|
||||
if (!egl_filterSetup(filter, outputFormat, outputX, outputY,
|
||||
desktopWidth, desktopHeight, inputDMA) ||
|
||||
!egl_filterPrepare(filter))
|
||||
continue;
|
||||
|
||||
if (!vector_push(&this->activeFilters, &filter))
|
||||
{
|
||||
vector_clear(&this->activeFilters);
|
||||
return false;
|
||||
}
|
||||
|
||||
fullFrame |= filter->ops.fullFrame;
|
||||
|
||||
egl_filterGetOutputRes(filter, &outputX, &outputY, &outputFormat);
|
||||
|
||||
/* The first active filter converts external DMA textures into a normal
|
||||
* texture for every subsequent filter. */
|
||||
inputDMA = false;
|
||||
vector_clear(&this->activeFilters);
|
||||
return false;
|
||||
}
|
||||
|
||||
fullFrame |= filter->ops.fullFrame;
|
||||
egl_filterGetOutputRes(filter, &outputX, &outputY, &outputFormat);
|
||||
inputDMA = false;
|
||||
}
|
||||
|
||||
const unsigned int baseOutputX = outputX;
|
||||
const unsigned int baseOutputY = outputY;
|
||||
const EGL_PixelFormat baseOutputFormat = outputFormat;
|
||||
const bool baseInputDMA = inputDMA;
|
||||
const bool baseFullFrame = fullFrame;
|
||||
|
||||
// Effects operate in linear scRGB. Probe them against the format produced
|
||||
// by the decoder first so an inactive chain does not compile or execute an
|
||||
// otherwise unnecessary conversion pass.
|
||||
if (hdrPQ)
|
||||
{
|
||||
outputFormat = EGL_PF_RGBA16F;
|
||||
inputDMA = false;
|
||||
}
|
||||
|
||||
vector_forEach(filter, &this->filters)
|
||||
{
|
||||
egl_filterSetOutputResHint(filter, targetX, targetY);
|
||||
|
||||
if (!egl_filterSetup(filter, outputFormat, outputX, outputY,
|
||||
desktopWidth, desktopHeight, inputDMA) ||
|
||||
!egl_filterPrepare(filter))
|
||||
continue;
|
||||
|
||||
if (!vector_push(&this->effectFilters, &filter))
|
||||
{
|
||||
vector_clear(&this->activeFilters);
|
||||
vector_clear(&this->effectFilters);
|
||||
return false;
|
||||
}
|
||||
|
||||
fullFrame |= filter->ops.fullFrame;
|
||||
egl_filterGetOutputRes(filter, &outputX, &outputY, &outputFormat);
|
||||
inputDMA = false;
|
||||
}
|
||||
|
||||
bool effectsActive = vector_size(&this->effectFilters) > 0;
|
||||
if (effectsActive && hdrPQ)
|
||||
{
|
||||
if (!egl_filterSetup(this->hdrDecode,
|
||||
baseOutputFormat, baseOutputX, baseOutputY,
|
||||
desktopWidth, desktopHeight, baseInputDMA) ||
|
||||
!egl_filterPrepare(this->hdrDecode))
|
||||
{
|
||||
DEBUG_ERROR("Failed to prepare HDR input for the EGL effect chain");
|
||||
vector_clear(&this->effectFilters);
|
||||
effectsActive = false;
|
||||
fullFrame = baseFullFrame;
|
||||
}
|
||||
else if (!vector_push(&this->activeFilters, &this->hdrDecode))
|
||||
{
|
||||
vector_clear(&this->activeFilters);
|
||||
vector_clear(&this->effectFilters);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
vector_forEach(filter, &this->effectFilters)
|
||||
if (!vector_push(&this->activeFilters, &filter))
|
||||
{
|
||||
vector_clear(&this->activeFilters);
|
||||
vector_clear(&this->effectFilters);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!effectsActive)
|
||||
{
|
||||
outputX = baseOutputX;
|
||||
outputY = baseOutputY;
|
||||
}
|
||||
|
||||
egl_desktopRectsMatrix(this->matrix,
|
||||
desktopWidth, desktopHeight, 0.0f, 0.0f, 1.0f, 1.0f, LG_ROTATE_0);
|
||||
|
||||
@@ -789,6 +868,7 @@ static bool configureChain(EGL_PostProcess * this, EGL_PixelFormat pixFmt,
|
||||
this->config.hdrPQ = hdrPQ;
|
||||
this->config.outputX = outputX;
|
||||
this->config.outputY = outputY;
|
||||
this->config.outputHDRPQ = hdrPQ && !effectsActive;
|
||||
this->config.fullFrame = fullFrame;
|
||||
this->config.valid = true;
|
||||
return true;
|
||||
@@ -871,9 +951,10 @@ bool egl_postProcessRun(EGL_PostProcess * this, EGL_Texture * tex,
|
||||
}
|
||||
|
||||
EGL_Texture * egl_postProcessGetOutput(EGL_PostProcess * this,
|
||||
unsigned int * outputX, unsigned int * outputY)
|
||||
unsigned int * outputX, unsigned int * outputY, bool * hdrPQ)
|
||||
{
|
||||
*outputX = this->outputX;
|
||||
*outputY = this->outputY;
|
||||
*hdrPQ = this->config.outputHDRPQ;
|
||||
return this->output;
|
||||
}
|
||||
|
||||
@@ -49,5 +49,6 @@ bool egl_postProcessRun(EGL_PostProcess * this, EGL_Texture * tex,
|
||||
EGL_DesktopRects * rects, int desktopWidth, int desktopHeight,
|
||||
unsigned int targetX, unsigned int targetY, bool useDMA, bool hdrPQ);
|
||||
|
||||
/* Return the current output texture and whether it remains PQ encoded. */
|
||||
EGL_Texture * egl_postProcessGetOutput(EGL_PostProcess * this,
|
||||
unsigned int * outputX, unsigned int * outputY);
|
||||
unsigned int * outputX, unsigned int * outputY, bool * hdrPQ);
|
||||
|
||||
37
client/renderers/EGL/shader/hdr_decode.frag
Normal file
37
client/renderers/EGL/shader/hdr_decode.frag
Normal file
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
#version 300 es
|
||||
#extension GL_OES_EGL_image_external_essl3 : enable
|
||||
|
||||
precision highp float;
|
||||
|
||||
#include "hdr.h"
|
||||
|
||||
in vec2 fragCoord;
|
||||
out vec4 fragColor;
|
||||
|
||||
uniform sampler2D sampler1;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 pq = texture(sampler1, fragCoord);
|
||||
fragColor = vec4(bt2020to709(pq2lin(pq.rgb, 1.0)) * 125.0, pq.a);
|
||||
}
|
||||
Reference in New Issue
Block a user