mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-09-02 04:03:56 +00:00
Some checks failed
build / client (Debug, map[cc:clang cxx:clang++], libdecor) (push) Has been cancelled
build / client (Debug, map[cc:clang cxx:clang++], xdg-shell) (push) Has been cancelled
build / client (Debug, map[cc:gcc cxx:g++], libdecor) (push) Has been cancelled
build / client (Debug, map[cc:gcc cxx:g++], xdg-shell) (push) Has been cancelled
build / client (Release, map[cc:clang cxx:clang++], libdecor) (push) Has been cancelled
build / client (Release, map[cc:clang cxx:clang++], xdg-shell) (push) Has been cancelled
build / client (Release, map[cc:gcc cxx:g++], libdecor) (push) Has been cancelled
build / client (Release, map[cc:gcc cxx:g++], xdg-shell) (push) Has been cancelled
build / module (push) Has been cancelled
build / host-linux (push) Has been cancelled
build / host-windows-cross (push) Has been cancelled
build / host-windows-native (push) Has been cancelled
build / idd (push) Has been cancelled
build / obs (clang) (push) Has been cancelled
build / obs (gcc) (push) Has been cancelled
build / docs (push) Has been cancelled
Add LGProtocol as a pinned submodule and consume its KVMFR protocol definitions throughout the client, host, IDD, OBS, and profiler. Keep Looking Glass framebuffer helpers local while removing duplicated protocol headers and migrating users to KVMFR-scoped types.
768 lines
22 KiB
C
768 lines
22 KiB
C
/**
|
|
* 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 "desktop.h"
|
|
#include "state.h"
|
|
#include "framebuffer.h"
|
|
#include "common/debug.h"
|
|
#include "common/option.h"
|
|
#include "common/locking.h"
|
|
#include "common/time.h"
|
|
|
|
#include "app.h"
|
|
#include "texture.h"
|
|
#include "texture_buffer.h"
|
|
#include "shader.h"
|
|
#include "desktop_rects.h"
|
|
#include "cimgui.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
// these headers are auto generated by cmake
|
|
#include "desktop.vert.h"
|
|
#include "desktop_rgb.frag.h"
|
|
#include "desktop_rgb.def.h"
|
|
|
|
#include "postprocess.h"
|
|
#include "filters.h"
|
|
|
|
enum
|
|
{
|
|
HDR_PEAK_LUMINANCE_MIN = 80,
|
|
HDR_PEAK_LUMINANCE_DEFAULT = 10000,
|
|
HDR_PEAK_LUMINANCE_MAX = 10000
|
|
};
|
|
|
|
struct DesktopShader
|
|
{
|
|
EGL_Shader * shader;
|
|
EGL_Uniform * uTransform;
|
|
EGL_Uniform * uDesktopSize;
|
|
EGL_Uniform * uScaleAlgo;
|
|
EGL_Uniform * uNVGain;
|
|
EGL_Uniform * uCBMode;
|
|
EGL_Uniform * uIsHDR;
|
|
EGL_Uniform * uMapHDRtoSDR;
|
|
EGL_Uniform * uMapHDRGain;
|
|
EGL_Uniform * uMapHDRContentPeak;
|
|
EGL_Uniform * uMapHDRPQ;
|
|
EGL_Uniform * uOutputHDRLinear;
|
|
};
|
|
|
|
struct EGL_Desktop
|
|
{
|
|
EGL * egl;
|
|
EGLDisplay * display;
|
|
|
|
EGL_Texture * texture;
|
|
struct DesktopShader shader;
|
|
EGL_DesktopRects * mesh;
|
|
GLfloat matrix[6];
|
|
|
|
// internals
|
|
int width, height;
|
|
bool hdr;
|
|
bool hdrPQ;
|
|
LG_RendererRotate rotate;
|
|
|
|
bool useSwSurface;
|
|
int swSurfaceWidth, swSurfaceHeight;
|
|
EGL_Texture * swSurfaceTexture;
|
|
|
|
// scale algorithm
|
|
int scaleAlgo;
|
|
|
|
// night vision
|
|
int nvMax;
|
|
int nvGain;
|
|
|
|
// colorblind mode
|
|
int cbMode;
|
|
|
|
bool useDMA;
|
|
LG_RendererFormat format;
|
|
|
|
// map HDR content to SDR
|
|
bool mapHDRtoSDR;
|
|
bool nativeHDR;
|
|
bool linearComposition;
|
|
int peakLuminance;
|
|
int maxCLL;
|
|
|
|
EGL_PostProcess * pp;
|
|
_Atomic(bool) processFrame;
|
|
};
|
|
|
|
// forwards
|
|
void toggleNV(int key, void * opaque);
|
|
|
|
static bool egl_initDesktopShader(
|
|
struct DesktopShader * shader,
|
|
const char * vertex_code , size_t vertex_size,
|
|
const char * fragment_code, size_t fragment_size,
|
|
bool useDMA
|
|
)
|
|
{
|
|
if (!egl_shaderInit(&shader->shader))
|
|
return false;
|
|
|
|
if (!egl_shaderCompile(shader->shader,
|
|
vertex_code , vertex_size,
|
|
fragment_code, fragment_size,
|
|
useDMA, NULL))
|
|
{
|
|
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->uMapHDRContentPeak =
|
|
egl_shaderGetUniform(shader->shader, "mapHDRContentPeak");
|
|
shader->uMapHDRPQ =
|
|
egl_shaderGetUniform(shader->shader, "mapHDRPQ");
|
|
shader->uOutputHDRLinear =
|
|
egl_shaderGetUniform(shader->shader, "outputHDRLinear");
|
|
|
|
return true;
|
|
}
|
|
|
|
bool egl_desktopInit(EGL * egl, EGL_Desktop ** desktop_, EGLDisplay * display,
|
|
bool useDMA, int maxRects)
|
|
{
|
|
EGL_Desktop * desktop = calloc(1, sizeof(EGL_Desktop));
|
|
if (!desktop)
|
|
{
|
|
DEBUG_ERROR("Failed to malloc EGL_Desktop");
|
|
return false;
|
|
}
|
|
*desktop_ = desktop;
|
|
|
|
desktop->egl = egl;
|
|
desktop->display = display;
|
|
|
|
if (!egl_textureInit(&desktop->texture, display,
|
|
useDMA ? EGL_TEXTYPE_DMABUF : EGL_TEXTYPE_FRAMEBUFFER))
|
|
{
|
|
DEBUG_ERROR("Failed to initialize the desktop texture");
|
|
return false;
|
|
}
|
|
|
|
if (!egl_desktopRectsInit(&desktop->mesh, maxRects))
|
|
{
|
|
DEBUG_ERROR("Failed to initialize the desktop mesh");
|
|
return false;
|
|
}
|
|
|
|
if (!egl_initDesktopShader(
|
|
&desktop->shader,
|
|
b_shader_desktop_vert , b_shader_desktop_vert_size,
|
|
b_shader_desktop_rgb_frag, b_shader_desktop_rgb_frag_size,
|
|
false))
|
|
{
|
|
DEBUG_ERROR("Failed to initialize the desktop shader");
|
|
return false;
|
|
}
|
|
|
|
app_registerKeybind(KEY_N, toggleNV, desktop,
|
|
"Toggle night vision mode");
|
|
|
|
desktop->nvMax = option_get_int("egl", "nvGainMax");
|
|
desktop->nvGain = option_get_int("egl", "nvGain" );
|
|
desktop->cbMode = option_get_int("egl", "cbMode" );
|
|
desktop->scaleAlgo = option_get_int("egl", "scale" );
|
|
desktop->useDMA = useDMA;
|
|
|
|
desktop->mapHDRtoSDR = option_get_bool("egl", "mapHDRtoSDR" );
|
|
desktop->peakLuminance = option_get_int ("egl", "peakLuminance");
|
|
desktop->maxCLL = option_get_int ("egl", "maxCLL" );
|
|
|
|
if (!egl_postProcessInit(&desktop->pp))
|
|
{
|
|
DEBUG_ERROR("Failed to initialize the post process manager");
|
|
return false;
|
|
}
|
|
|
|
// this MUST be first
|
|
egl_postProcessAdd(desktop->pp, &egl_filter24bitOps);
|
|
|
|
egl_postProcessAdd(desktop->pp, &egl_filterDownscaleOps);
|
|
egl_postProcessAdd(desktop->pp, &egl_filterFFXCASOps );
|
|
egl_postProcessAdd(desktop->pp, &egl_filterFFXFSR1Ops );
|
|
egl_postProcessAdd(desktop->pp, &egl_filterGLSLOps );
|
|
return true;
|
|
}
|
|
|
|
void toggleNV(int key, void * opaque)
|
|
{
|
|
EGL_Desktop * desktop = (EGL_Desktop *)opaque;
|
|
if (desktop->nvGain++ == desktop->nvMax)
|
|
desktop->nvGain = 0;
|
|
|
|
if (desktop->nvGain == 0) app_alert(LG_ALERT_INFO, "NV Disabled");
|
|
else if (desktop->nvGain == 1) app_alert(LG_ALERT_INFO, "NV Enabled");
|
|
else app_alert(LG_ALERT_INFO, "NV Gain + %d", desktop->nvGain - 1);
|
|
|
|
app_invalidateWindow(true);
|
|
}
|
|
|
|
bool egl_desktopScaleValidate(struct Option * opt, const char ** error)
|
|
{
|
|
if (opt->value.x_int >= 0 && opt->value.x_int < EGL_SCALE_MAX)
|
|
return true;
|
|
|
|
*error = "Invalid scale algorithm number";
|
|
return false;
|
|
}
|
|
|
|
void egl_desktopFree(EGL_Desktop ** desktop)
|
|
{
|
|
if (!*desktop)
|
|
return;
|
|
|
|
egl_textureFree (&(*desktop)->texture );
|
|
egl_textureFree (&(*desktop)->swSurfaceTexture);
|
|
egl_shaderFree (&(*desktop)->shader .shader);
|
|
egl_desktopRectsFree(&(*desktop)->mesh );
|
|
egl_postProcessFree(&(*desktop)->pp);
|
|
|
|
free(*desktop);
|
|
*desktop = NULL;
|
|
}
|
|
|
|
static const char * algorithmNames[EGL_SCALE_MAX] = {
|
|
[EGL_SCALE_AUTO] = "Automatic (downscale: linear, upscale: nearest)",
|
|
[EGL_SCALE_NEAREST] = "Nearest",
|
|
[EGL_SCALE_LINEAR] = "Linear",
|
|
};
|
|
|
|
void egl_desktopConfigUI(EGL_Desktop * desktop)
|
|
{
|
|
igText("Scale algorithm:");
|
|
igPushItemWidth(igGetWindowWidth() - igGetStyle()->WindowPadding.x * 2);
|
|
if (igBeginCombo("##scale", algorithmNames[desktop->scaleAlgo], 0))
|
|
{
|
|
for (int i = 0; i < EGL_SCALE_MAX; ++i)
|
|
{
|
|
bool selected = i == desktop->scaleAlgo;
|
|
if (igSelectable_Bool(algorithmNames[i], selected, 0,
|
|
(ImVec2) { 0.0f, 0.0f }))
|
|
desktop->scaleAlgo = i;
|
|
if (selected)
|
|
igSetItemDefaultFocus();
|
|
}
|
|
igEndCombo();
|
|
}
|
|
igPopItemWidth();
|
|
|
|
igText("Night vision mode:");
|
|
igSameLine(0.0f, -1.0f);
|
|
igPushItemWidth(igGetWindowWidth() - igGetCursorPosX() - igGetStyle()->WindowPadding.x);
|
|
|
|
const char * format;
|
|
switch (desktop->nvGain)
|
|
{
|
|
case 0: format = "off"; break;
|
|
case 1: format = "on"; break;
|
|
default: format = "gain: %d";
|
|
}
|
|
igSliderInt("##nvgain", &desktop->nvGain, 0, desktop->nvMax, format, 0);
|
|
igPopItemWidth();
|
|
|
|
bool mapHDRtoSDR = desktop->mapHDRtoSDR;
|
|
int peakLuminance = desktop->peakLuminance;
|
|
int maxCLL = desktop->maxCLL;
|
|
|
|
igSeparator();
|
|
igCheckbox("Map HDR content to SDR", &mapHDRtoSDR);
|
|
igSliderInt("Peak Luminance", &peakLuminance, 1, 10000,
|
|
"%d nits",
|
|
ImGuiInputTextFlags_CharsDecimal);
|
|
igSliderInt("Max content light level", &maxCLL, 1, 10000,
|
|
"%d nits", ImGuiInputTextFlags_CharsDecimal);
|
|
|
|
if (mapHDRtoSDR != desktop->mapHDRtoSDR ||
|
|
peakLuminance != desktop->peakLuminance ||
|
|
maxCLL != desktop->maxCLL)
|
|
{
|
|
desktop->mapHDRtoSDR = mapHDRtoSDR;
|
|
desktop->peakLuminance = max(1, peakLuminance);
|
|
desktop->maxCLL = max(1, maxCLL);
|
|
app_invalidateWindow(true);
|
|
}
|
|
}
|
|
|
|
bool egl_desktopSetup(EGL_Desktop * desktop, const LG_RendererFormat format)
|
|
{
|
|
memcpy(&desktop->format, &format, sizeof(LG_RendererFormat));
|
|
|
|
enum EGL_PixelFormat pixFmt;
|
|
switch(format.type)
|
|
{
|
|
case FRAME_TYPE_BGRA:
|
|
pixFmt = EGL_PF_BGRA;
|
|
break;
|
|
|
|
case FRAME_TYPE_RGBA:
|
|
pixFmt = EGL_PF_RGBA;
|
|
break;
|
|
|
|
case FRAME_TYPE_RGBA10:
|
|
pixFmt = EGL_PF_RGBA10;
|
|
break;
|
|
|
|
case FRAME_TYPE_RGBA16F:
|
|
pixFmt = EGL_PF_RGBA16F;
|
|
break;
|
|
|
|
case FRAME_TYPE_BGR_32:
|
|
pixFmt = EGL_PF_BGR_32;
|
|
break;
|
|
|
|
case FRAME_TYPE_RGB_24:
|
|
pixFmt = EGL_PF_RGB_24;
|
|
break;
|
|
|
|
default:
|
|
DEBUG_ERROR("Unsupported frame format");
|
|
return false;
|
|
}
|
|
|
|
desktop->width = format.frameWidth;
|
|
desktop->height = format.frameHeight;
|
|
desktop->hdr = format.hdr;
|
|
desktop->hdrPQ = format.hdrPQ;
|
|
|
|
if (!egl_textureSetup(
|
|
desktop->texture,
|
|
pixFmt,
|
|
desktop->format.dataWidth,
|
|
desktop->format.dataHeight,
|
|
desktop->format.stride,
|
|
desktop->format.pitch
|
|
))
|
|
{
|
|
DEBUG_ERROR("Failed to setup the desktop texture");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool egl_desktopUpdate(EGL_Desktop * desktop,
|
|
const KVMFRFrameBuffer * frame,
|
|
LG_RendererFrameToken frameToken, int dmaFd,
|
|
const KVMFRFrameDamageRect * damageRects, int damageRectsCount,
|
|
uint64_t * waitTimeNs, LG_FrameReleaseFn releaseFn,
|
|
void * releaseOpaque, uint64_t releaseHandle)
|
|
{
|
|
/*
|
|
* Both the DMA and staged upload paths need a complete frame. Do this
|
|
* before selecting either path so a producer that is still filling an
|
|
* indirect frame cannot be mistaken for a broken DMA import. Retain the
|
|
* last image and release a direct frame lease when there is one.
|
|
*/
|
|
if (unlikely(!framebuffer_wait_timed(
|
|
frame, desktop->texture->format.dataSize, waitTimeNs)))
|
|
{
|
|
if (releaseFn)
|
|
releaseFn(releaseOpaque, releaseHandle);
|
|
return true;
|
|
}
|
|
|
|
if (likely(desktop->useDMA && dmaFd >= 0))
|
|
{
|
|
if (likely(egl_textureUpdateFromDMA(
|
|
desktop->texture, frame, frameToken, dmaFd, waitTimeNs,
|
|
releaseFn, releaseOpaque, releaseHandle)))
|
|
{
|
|
atomic_store(&desktop->processFrame, true);
|
|
return true;
|
|
}
|
|
|
|
DEBUG_WARN("DMA update failed, disabling DMABUF imports");
|
|
|
|
const char * vendor = (const char *)glGetString(GL_VENDOR);
|
|
if (strstr(vendor, "NVIDIA"))
|
|
{
|
|
DEBUG_WARN("NVIDIA's DMABUF support is incomplete, please direct your complaints to NVIDIA");
|
|
DEBUG_WARN("This is not a bug in Looking Glass");
|
|
}
|
|
|
|
desktop->useDMA = false;
|
|
|
|
const char * gl_exts = (const char *)glGetString(GL_EXTENSIONS);
|
|
if (!util_hasGLExt(gl_exts, "GL_EXT_buffer_storage"))
|
|
{
|
|
DEBUG_ERROR("GL_EXT_buffer_storage is needed to use EGL backend");
|
|
return false;
|
|
}
|
|
|
|
egl_textureFree(&desktop->texture);
|
|
if (!egl_textureInit(&desktop->texture, desktop->display,
|
|
EGL_TEXTYPE_FRAMEBUFFER))
|
|
{
|
|
DEBUG_ERROR("Failed to initialize the desktop texture");
|
|
return false;
|
|
}
|
|
|
|
if (!egl_desktopSetup(desktop, desktop->format))
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Streamed textures trigger post-processing when the render context submits
|
|
* the upload. Signalling here can race with that submission and process the
|
|
* previous texture contents instead.
|
|
*/
|
|
if (likely(egl_textureUpdateFromFrame(desktop->texture, frame, frameToken,
|
|
damageRects, damageRectsCount, waitTimeNs)))
|
|
{
|
|
if (releaseFn)
|
|
releaseFn(releaseOpaque, releaseHandle);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void egl_desktopRestart(EGL_Desktop * desktop)
|
|
{
|
|
egl_textureReset(desktop->texture);
|
|
atomic_store(&desktop->processFrame, false);
|
|
}
|
|
|
|
void egl_desktopPoll(EGL_Desktop * desktop)
|
|
{
|
|
egl_texturePoll(desktop->texture);
|
|
}
|
|
|
|
void egl_desktopResize(EGL_Desktop * desktop, int width, int height)
|
|
{
|
|
atomic_store(&desktop->processFrame, true);
|
|
}
|
|
|
|
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,
|
|
LG_RendererFrameToken damageFrameToken,
|
|
LG_RendererFrameToken frameTokenLimit, bool * fullFrame,
|
|
LG_RendererFrameToken * consumedFrameToken,
|
|
LG_RendererFrameToken * renderedFrameToken,
|
|
uint64_t * effectsTime, EGL_Framebuffer * target)
|
|
{
|
|
EGL_Texture * tex;
|
|
int width, height;
|
|
|
|
/* This also retires completed snapshots while the software surface is
|
|
* shown. */
|
|
egl_texturePoll(desktop->texture);
|
|
|
|
if (unlikely(desktop->useSwSurface))
|
|
{
|
|
tex = desktop->swSurfaceTexture;
|
|
width = desktop->swSurfaceWidth;
|
|
height = desktop->swSurfaceHeight;
|
|
}
|
|
else
|
|
{
|
|
tex = desktop->texture;
|
|
width = desktop->width;
|
|
height = desktop->height;
|
|
}
|
|
|
|
if (unlikely(outputWidth == 0 || outputHeight == 0))
|
|
DEBUG_FATAL("outputWidth || outputHeight == 0");
|
|
|
|
LG_RendererFrameToken frameToken;
|
|
const enum EGL_TexStatus status =
|
|
egl_textureProcess(tex, frameTokenLimit, &frameToken);
|
|
const bool textureUpdated = status == EGL_TEX_STATUS_UPDATED;
|
|
|
|
*consumedFrameToken = frameToken;
|
|
*renderedFrameToken = LG_RENDERER_FRAME_TOKEN_NONE;
|
|
*effectsTime = 0;
|
|
if (unlikely(status != EGL_TEX_STATUS_OK && !textureUpdated))
|
|
{
|
|
if (status != EGL_TEX_STATUS_NOTREADY)
|
|
DEBUG_ERROR("Failed to process the desktop texture");
|
|
}
|
|
|
|
*fullFrame = false;
|
|
if (frameToken != LG_RENDERER_FRAME_TOKEN_NONE &&
|
|
frameToken != damageFrameToken)
|
|
{
|
|
rects = NULL;
|
|
*fullFrame = true;
|
|
}
|
|
|
|
int scaleAlgo = EGL_SCALE_NEAREST;
|
|
|
|
egl_desktopRectsMatrix(desktop->matrix,
|
|
width, height, x, y, scaleX, scaleY, rotate);
|
|
egl_desktopRectsUpdate(desktop->mesh,
|
|
rects ? rects->rects : NULL, rects ? rects->count : -1,
|
|
width, height);
|
|
|
|
const bool hdr = desktop->hdr && !desktop->useSwSurface;
|
|
uint32_t hdrPeak = 0;
|
|
if (hdr)
|
|
{
|
|
hdrPeak = max(desktop->format.hdrMaxDisplayLuminance,
|
|
desktop->format.hdrMaxContentLightLevel);
|
|
if (!hdrPeak)
|
|
hdrPeak = HDR_PEAK_LUMINANCE_DEFAULT;
|
|
else
|
|
hdrPeak = clamp(hdrPeak,
|
|
HDR_PEAK_LUMINANCE_MIN, HDR_PEAK_LUMINANCE_MAX);
|
|
}
|
|
|
|
bool processFrame = textureUpdated;
|
|
processFrame |= atomic_exchange(&desktop->processFrame, false);
|
|
processFrame |= egl_postProcessConfigModified(desktop->pp);
|
|
if (processFrame)
|
|
{
|
|
const uint64_t effectsStart = nanotime();
|
|
const bool postProcessed = egl_postProcessRun(
|
|
desktop->pp, tex, desktop->mesh,
|
|
width, height, outputWidth, outputHeight, false,
|
|
hdr && desktop->hdrPQ, (float)hdrPeak);
|
|
*effectsTime = nanotime() - effectsStart;
|
|
|
|
if (postProcessed && egl_postProcessNeedsFullFrame(desktop->pp))
|
|
{
|
|
/* The filter output may have changed everywhere, but this only applies
|
|
* to the render that actually evaluated the filter. */
|
|
egl_desktopRectsUpdate(desktop->mesh, NULL, -1, width, height);
|
|
*fullFrame = true;
|
|
}
|
|
}
|
|
|
|
unsigned int finalSizeX, finalSizeY;
|
|
bool outputHDRPQ = desktop->hdrPQ;
|
|
EGL_Texture * texture = egl_postProcessGetOutput(desktop->pp,
|
|
&finalSizeX, &finalSizeY, &outputHDRPQ);
|
|
|
|
if (unlikely(!texture))
|
|
{
|
|
texture = tex;
|
|
finalSizeX = width;
|
|
finalSizeY = height;
|
|
outputHDRPQ = desktop->hdrPQ;
|
|
}
|
|
|
|
if (target)
|
|
egl_framebufferBind(target);
|
|
else
|
|
{
|
|
egl_stateBindFramebuffer(0);
|
|
egl_resetViewport(desktop->egl);
|
|
}
|
|
|
|
const EGL_TexStatus bindStatus = egl_textureBind(texture);
|
|
if (unlikely(bindStatus != EGL_TEX_STATUS_OK))
|
|
{
|
|
if (bindStatus != EGL_TEX_STATUS_NOTREADY)
|
|
DEBUG_ERROR("Failed to bind the desktop texture");
|
|
if (!desktop->useSwSurface && desktop->useDMA && processFrame)
|
|
egl_textureMarkUsed(desktop->texture);
|
|
return false;
|
|
}
|
|
*renderedFrameToken = tex->frameToken;
|
|
|
|
if (finalSizeX > width || finalSizeY > height)
|
|
scaleType = EGL_DESKTOP_DOWNSCALE;
|
|
|
|
switch (desktop->scaleAlgo)
|
|
{
|
|
case EGL_SCALE_AUTO:
|
|
switch (scaleType)
|
|
{
|
|
case EGL_DESKTOP_UPSCALE:
|
|
scaleAlgo = EGL_SCALE_NEAREST;
|
|
break;
|
|
|
|
case EGL_DESKTOP_NOSCALE:
|
|
case EGL_DESKTOP_DOWNSCALE:
|
|
scaleAlgo = EGL_SCALE_LINEAR;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
scaleAlgo = desktop->scaleAlgo;
|
|
}
|
|
|
|
const struct DesktopShader * shader = &desktop->shader;
|
|
|
|
/* 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 = (outputHDRPQ ? 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_uniform1f (shader->uMapHDRContentPeak, mapHDRContentPeak);
|
|
egl_uniform1i (shader->uMapHDRPQ , outputHDRPQ);
|
|
egl_uniform1i (shader->uOutputHDRLinear ,
|
|
desktop->linearComposition);
|
|
egl_shaderUse(shader->shader);
|
|
egl_desktopRectsRender(desktop->mesh);
|
|
if (!desktop->useSwSurface && desktop->useDMA &&
|
|
(processFrame || texture == tex))
|
|
egl_textureMarkUsed(desktop->texture);
|
|
return true;
|
|
}
|
|
|
|
void egl_desktopSetNativeHDR(EGL_Desktop * desktop, bool nativeHDR,
|
|
bool linearComposition)
|
|
{
|
|
desktop->nativeHDR = nativeHDR;
|
|
desktop->linearComposition = nativeHDR && linearComposition;
|
|
}
|
|
|
|
void egl_desktopGetHDRMapping(EGL_Desktop * desktop, bool * enabled,
|
|
float * gain, float * contentPeak)
|
|
{
|
|
*enabled = desktop->hdr && !desktop->useSwSurface &&
|
|
desktop->mapHDRtoSDR && !desktop->nativeHDR;
|
|
*gain = (desktop->hdrPQ ? 10000.0f : 80.0f) /
|
|
desktop->peakLuminance;
|
|
*contentPeak = (float)desktop->maxCLL / desktop->peakLuminance;
|
|
}
|
|
|
|
void egl_desktopSwSurfaceConfigure(EGL_Desktop * desktop,
|
|
int width, int height)
|
|
{
|
|
if (!desktop->swSurfaceTexture)
|
|
if (!egl_textureInit(&desktop->swSurfaceTexture, desktop->display,
|
|
EGL_TEXTYPE_BUFFER_MAP))
|
|
{
|
|
DEBUG_ERROR("Failed to initialize the software surface texture");
|
|
return;
|
|
}
|
|
|
|
if (!egl_textureSetup(
|
|
desktop->swSurfaceTexture,
|
|
EGL_PF_BGRA,
|
|
width,
|
|
height,
|
|
width,
|
|
width * 4
|
|
))
|
|
{
|
|
DEBUG_ERROR("Failed to setup the software surface texture");
|
|
return;
|
|
}
|
|
|
|
desktop->swSurfaceWidth = width;
|
|
desktop->swSurfaceHeight = height;
|
|
}
|
|
|
|
void egl_desktopSwSurfaceDrawFill(EGL_Desktop * desktop,
|
|
int x, int y, int width, int height, uint32_t color)
|
|
{
|
|
if (!desktop->swSurfaceTexture || width <= 0 || height <= 0)
|
|
return;
|
|
|
|
const int64_t right = (int64_t)x + width;
|
|
const int64_t bottom = (int64_t)y + height;
|
|
if (x >= desktop->swSurfaceWidth ||
|
|
y >= desktop->swSurfaceHeight ||
|
|
right <= 0 || bottom <= 0)
|
|
return;
|
|
|
|
x = x < 0 ? 0 : x;
|
|
y = y < 0 ? 0 : y;
|
|
width = (right > desktop->swSurfaceWidth ?
|
|
desktop->swSurfaceWidth : (int)right ) - x;
|
|
height = (bottom > desktop->swSurfaceHeight ?
|
|
desktop->swSurfaceHeight : (int)bottom) - y;
|
|
|
|
if (egl_texBufferStreamFill(
|
|
desktop->swSurfaceTexture, x, y, width, height, color))
|
|
atomic_store(&desktop->processFrame, true);
|
|
}
|
|
|
|
void egl_desktopSwSurfaceDrawBitmap(EGL_Desktop * desktop,
|
|
int x, int y, int width, int height, int stride, uint8_t * data,
|
|
bool topDown)
|
|
{
|
|
if (!desktop->swSurfaceTexture || !data ||
|
|
width <= 0 || height <= 0 || stride <= 0 || width > stride / 4)
|
|
return;
|
|
|
|
const int originalHeight = height;
|
|
const int64_t right = (int64_t)x + width;
|
|
const int64_t bottom = (int64_t)y + height;
|
|
if (x >= desktop->swSurfaceWidth ||
|
|
y >= desktop->swSurfaceHeight ||
|
|
right <= 0 || bottom <= 0)
|
|
return;
|
|
|
|
const int sourceX = x < 0 ? -x : 0;
|
|
const int sourceY = y < 0 ? -y : 0;
|
|
x = x < 0 ? 0 : x;
|
|
y = y < 0 ? 0 : y;
|
|
width = (right > desktop->swSurfaceWidth ?
|
|
desktop->swSurfaceWidth : (int)right ) - x;
|
|
height = (bottom > desktop->swSurfaceHeight ?
|
|
desktop->swSurfaceHeight : (int)bottom) - y;
|
|
|
|
const int sourceRow = topDown ?
|
|
sourceY : originalHeight - sourceY - height;
|
|
data += (size_t)sourceRow * stride + (size_t)sourceX * 4;
|
|
|
|
egl_textureUpdateRect(desktop->swSurfaceTexture,
|
|
x, y, width, height, stride / 4, stride, data, topDown);
|
|
atomic_store(&desktop->processFrame, true);
|
|
}
|
|
|
|
void egl_desktopSwSurfaceShow(EGL_Desktop * desktop, bool show)
|
|
{
|
|
desktop->useSwSurface = show;
|
|
atomic_store(&desktop->processFrame, true);
|
|
}
|