/** * Looking Glass * Copyright © 2017-2026 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "hdr_overlay.h" #include "framebuffer.h" #include "model.h" #include "shader.h" #include "state.h" #include "texture.h" #include "common/debug.h" #include "common/rects.h" #include "common/util.h" #include #include // these headers are auto generated by cmake #include "hdr_overlay.vert.h" #include "hdr_overlay.frag.h" struct EGL_HDROverlay { EGL_Framebuffer * framebuffer; EGL_Shader * shader; EGL_Model * model; EGL_Uniform * uPQ; EGL_Uniform * uReferenceWhiteLevel; unsigned int width; unsigned int height; bool configured; _Atomic(bool) active; _Atomic(bool) pq; _Atomic(float) referenceWhiteLevel; bool renderPQ; float renderReferenceWhiteLevel; }; bool egl_hdrOverlayInit(EGL_HDROverlay ** overlay) { *overlay = calloc(1, sizeof(**overlay)); if (!*overlay) { DEBUG_ERROR("Failed to allocate the HDR overlay"); return false; } EGL_HDROverlay * this = *overlay; if (!egl_framebufferInit(&this->framebuffer) || !egl_shaderInit(&this->shader) || !egl_shaderCompile(this->shader, b_shader_hdr_overlay_vert, b_shader_hdr_overlay_vert_size, b_shader_hdr_overlay_frag, b_shader_hdr_overlay_frag_size, false, NULL) || !egl_modelInit(&this->model)) { DEBUG_ERROR("Failed to initialize the HDR overlay"); egl_hdrOverlayFree(overlay); return false; } this->uPQ = egl_shaderGetUniform(this->shader, "pq"); this->uReferenceWhiteLevel = egl_shaderGetUniform(this->shader, "referenceWhiteLevel"); egl_modelSetDefault(this->model, false); egl_modelSetShader(this->model, this->shader); egl_modelSetTexture(this->model, egl_framebufferGetTexture(this->framebuffer)); /* The conversion pass is a 1:1 copy; filtering can sample stale pixels just * outside a partial-damage region. */ EGL_Texture * texture = egl_framebufferGetTexture(this->framebuffer); glSamplerParameteri(texture->sampler, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glSamplerParameteri(texture->sampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST); atomic_init(&this->active, false); atomic_init(&this->pq, false); atomic_init(&this->referenceWhiteLevel, 80.0f); return true; } void egl_hdrOverlayFree(EGL_HDROverlay ** overlay) { if (!*overlay) return; EGL_HDROverlay * this = *overlay; egl_modelFree(&this->model); egl_shaderFree(&this->shader); egl_framebufferFree(&this->framebuffer); free(this); *overlay = NULL; } bool egl_hdrOverlayResize(EGL_HDROverlay * this, unsigned int width, unsigned int height) { this->width = width; this->height = height; this->configured = false; if (!width || !height) return true; this->configured = egl_framebufferSetup(this->framebuffer, EGL_PF_RGBA, width, height); return this->configured; } void egl_hdrOverlaySetState(EGL_HDROverlay * this, bool active, bool pq, float referenceWhiteLevel) { atomic_store(&this->referenceWhiteLevel, referenceWhiteLevel > 0.0f ? referenceWhiteLevel : 80.0f); atomic_store(&this->pq, pq); atomic_store(&this->active, active); } static int convertDamage(EGL_HDROverlay * this, const struct Rect * damage, int damageCount, FrameDamageRect * output) { int count = 0; for (int i = 0; i < damageCount; ++i) { const int x1 = clamp(damage[i].x, 0, (int)this->width); const int y1 = clamp(damage[i].y, 0, (int)this->height); const int x2 = clamp(damage[i].x + damage[i].w, 0, (int)this->width); const int y2 = clamp(damage[i].y + damage[i].h, 0, (int)this->height); if (x2 <= x1 || y2 <= y1) continue; output[count++] = (FrameDamageRect) { .x = x1, .y = this->height - y2, .width = x2 - x1, .height = y2 - y1, }; } return rectsMergeOverlapping(output, count); } bool egl_hdrOverlayBegin(EGL_HDROverlay * this, const struct Rect * damage, int damageCount) { if (!this->configured || !atomic_load(&this->active)) return false; this->renderPQ = atomic_load(&this->pq); this->renderReferenceWhiteLevel = atomic_load(&this->referenceWhiteLevel); egl_framebufferBind(this->framebuffer); egl_stateBlend(false); glClearColor(0.0f, 0.0f, 0.0f, 0.0f); if (damageCount < 0) { egl_stateScissor(false); glClear(GL_COLOR_BUFFER_BIT); } else { FrameDamageRect rects[damageCount]; const int count = convertDamage(this, damage, damageCount, rects); egl_stateScissor(true); for (int i = 0; i < count; ++i) { glScissor(rects[i].x, rects[i].y, rects[i].width, rects[i].height); glClear(GL_COLOR_BUFFER_BIT); } egl_stateScissor(false); } return true; } void egl_hdrOverlayEnd(EGL_HDROverlay * this, const struct Rect * damage, int damageCount, EGL_Framebuffer * target) { 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, this->renderReferenceWhiteLevel); egl_stateBlend(true); egl_stateBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); if (damageCount < 0) { egl_stateScissor(false); egl_modelRender(this->model); } else { FrameDamageRect rects[damageCount]; const int count = convertDamage(this, damage, damageCount, rects); egl_stateScissor(true); for (int i = 0; i < count; ++i) { glScissor(rects[i].x, rects[i].y, rects[i].width, rects[i].height); egl_modelRender(this->model); } egl_stateScissor(false); } egl_stateBlend(false); }