[client] egl: cache renderer GL state
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

Track context-local GL bindings to avoid redundant program, framebuffer,
buffer, texture, sampler, viewport, blend, and scissor state changes.

Invalidate the cache across context switches, ImGui rendering, and
shared object changes. Preserve CPU-backed texture upload correctness by
explicitly clearing pixel unpack buffer bindings where required.

This reduces hot-path driver overhead without adding waits or GPU
synchronization.
This commit is contained in:
Geoffrey McRae
2026-07-17 22:38:46 +10:00
parent b2c40a80af
commit e5c4fe68dc
17 changed files with 439 additions and 68 deletions

View File

@@ -73,6 +73,7 @@ make_defines(
add_library(renderer_EGL STATIC
egl.c
egldebug.c
state.c
shader.c
texture_util.c
texture.c

View File

@@ -25,6 +25,7 @@
#include "texture.h"
#include "shader.h"
#include "state.h"
#include "model.h"
#include "util.h"
@@ -386,7 +387,7 @@ 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);
glEnable(GL_BLEND);
egl_stateBlend(true);
switch(cursor->type)
{
case LG_CURSOR_MONOCHROME:
@@ -394,14 +395,14 @@ struct CursorState egl_cursorRender(EGL_Cursor * cursor,
setCursorTexUniforms(cursor, &cursor->norm, true, pos.x, pos.y,
size.w, size.h, scale);
egl_shaderUse(cursor->norm.shader);
glBlendFunc(GL_ZERO, GL_SRC_COLOR);
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);
egl_shaderUse(cursor->mono.shader);
glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
egl_stateBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
egl_modelSetTexture(cursor->model, cursor->mono.texture);
egl_modelRender(cursor->model);
break;
@@ -412,14 +413,14 @@ struct CursorState egl_cursorRender(EGL_Cursor * cursor,
setCursorTexUniforms(cursor, &cursor->norm, false, pos.x, pos.y,
size.w, size.h, scale);
egl_shaderUse(cursor->norm.shader);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
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);
egl_shaderUse(cursor->mono.shader);
glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
egl_stateBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
egl_modelSetTexture(cursor->model, cursor->mono.texture);
egl_modelRender(cursor->model);
break;
@@ -430,13 +431,13 @@ struct CursorState egl_cursorRender(EGL_Cursor * cursor,
setCursorTexUniforms(cursor, &cursor->norm, false, pos.x, pos.y,
size.w, size.h, scale);
egl_shaderUse(cursor->norm.shader);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
egl_stateBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
egl_modelSetTexture(cursor->model, cursor->norm.texture);
egl_modelRender(cursor->model);
break;
}
}
glDisable(GL_BLEND);
egl_stateBlend(false);
return state;
}

View File

@@ -26,6 +26,7 @@
#include "app.h"
#include "desktop_rects.h"
#include "shader.h"
#include "state.h"
#include "cimgui.h"
#include <stdlib.h>
@@ -140,8 +141,8 @@ bool egl_damageRender(EGL_Damage * damage, LG_RendererRotate rotate, const struc
update_matrix(damage);
}
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
egl_stateBlend(true);
egl_stateBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
egl_uniformMatrix3x2fv(damage->uTransform, 1, GL_FALSE, damage->transform);
egl_shaderUse(damage->shader);
@@ -152,6 +153,6 @@ bool egl_damageRender(EGL_Damage * damage, LG_RendererRotate rotate, const struc
egl_desktopRectsRender(damage->mesh);
glDisable(GL_BLEND);
egl_stateBlend(false);
return true;
}

View File

@@ -19,6 +19,7 @@
*/
#include "desktop.h"
#include "state.h"
#include "common/debug.h"
#include "common/option.h"
#include "common/locking.h"
@@ -476,10 +477,9 @@ bool egl_desktopRender(EGL_Desktop * desktop, unsigned int outputWidth,
finalSizeY = height;
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
egl_stateBindFramebuffer(0);
egl_resetViewport(desktop->egl);
glActiveTexture(GL_TEXTURE0);
egl_textureBind(texture);
if (finalSizeX > width || finalSizeY > height)
@@ -523,7 +523,6 @@ bool egl_desktopRender(EGL_Desktop * desktop, unsigned int outputWidth,
egl_uniform1i (shader->uMapHDRPQ , desktop->hdrPQ);
egl_shaderUse(shader->shader);
egl_desktopRectsRender(desktop->mesh);
glBindTexture(GL_TEXTURE_2D, 0);
return true;
}

View File

@@ -19,6 +19,7 @@
*/
#include "desktop_rects.h"
#include "state.h"
#include "common/debug.h"
#include "common/KVMFR.h"
#include "common/locking.h"
@@ -54,14 +55,13 @@ bool egl_desktopRectsInit(EGL_DesktopRects ** rects_, int maxCount)
memset(rects, 0, sizeof(*rects));
glGenVertexArrays(1, &rects->vao);
glBindVertexArray(rects->vao);
egl_stateBindVertexArray(rects->vao);
glGenBuffers(2, rects->buffers);
glBindBuffer(GL_ARRAY_BUFFER, rects->buffers[0]);
egl_stateBindBuffer(GL_ARRAY_BUFFER, rects->buffers[0]);
glBufferData(GL_ARRAY_BUFFER, maxCount * 8 * sizeof(GLfloat), NULL, GL_STREAM_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, NULL);
glBindBuffer(GL_ARRAY_BUFFER, 0);
GLushort indices[maxCount * 6];
for (int i = 0; i < maxCount; ++i)
@@ -77,8 +77,6 @@ bool egl_desktopRectsInit(EGL_DesktopRects ** rects_, int maxCount)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, rects->buffers[1]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof indices, indices, GL_STATIC_DRAW);
glBindVertexArray(0);
rects->count = 0;
rects->maxCount = maxCount;
return true;
@@ -92,6 +90,7 @@ void egl_desktopRectsFree(EGL_DesktopRects ** rects_)
glDeleteVertexArrays(1, &rects->vao);
glDeleteBuffers(2, rects->buffers);
egl_stateInvalidateShared();
free(rects->lastVertices);
free(rects);
*rects_ = NULL;
@@ -161,9 +160,8 @@ void egl_desktopRectsUpdate(EGL_DesktopRects * rects, const struct DamageRects *
rects->lastVerticesCount = count;
memcpy(rects->lastVertices, vertices, sizeof(GLfloat) * count);
glBindBuffer(GL_ARRAY_BUFFER, rects->buffers[0]);
egl_stateBindBuffer(GL_ARRAY_BUFFER, rects->buffers[0]);
glBufferSubData(GL_ARRAY_BUFFER, 0, rects->count * 8 * sizeof(GLfloat), vertices);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
static void desktopToGLSpace(double matrix[6], int width, int height, double translateX,
@@ -299,7 +297,6 @@ void egl_desktopRectsRender(EGL_DesktopRects * rects)
if (unlikely(!rects->count))
return;
glBindVertexArray(rects->vao);
egl_stateBindVertexArray(rects->vao);
glDrawElements(GL_TRIANGLES, 6 * rects->count, GL_UNSIGNED_SHORT, NULL);
glBindVertexArray(0);
}

View File

@@ -22,6 +22,7 @@
#include "filter.h"
#include "framebuffer.h"
#include "state.h"
#include "texture.h"
#include "common/debug.h"
@@ -74,16 +75,21 @@ void egl_effectFree(EGL_Effect ** effect)
if (!*effect)
return;
bool deletedSampler = false;
EGL_EffectPass * pass = (*effect)->passes;
while(pass)
{
EGL_EffectPass * next = pass->next;
glDeleteSamplers(1, &pass->sampler);
deletedSampler = true;
egl_framebufferFree(&pass->framebuffer);
free(pass);
pass = next;
}
if (deletedSampler)
egl_stateInvalidateShared();
free(*effect);
*effect = NULL;
}
@@ -215,9 +221,7 @@ EGL_Texture * egl_effectRun(EGL_Effect * effect, EGL_FilterRects * rects,
egl_framebufferBind(pass->framebuffer);
glActiveTexture(GL_TEXTURE0);
egl_textureBind(texture);
glBindSampler(0, pass->sampler);
egl_textureBindWithSampler(texture, pass->sampler);
egl_uniformMatrix3x2fv(pass->uTransform, 1, GL_FALSE, rects->matrix);
egl_uniform2f(pass->uDesktopSize, rects->width, rects->height);

View File

@@ -43,6 +43,7 @@
#include "egl_dynprocs.h"
#include "model.h"
#include "shader.h"
#include "state.h"
#include "damage.h"
#include "desktop.h"
#include "cursor.h"
@@ -298,7 +299,10 @@ static void egl_deinitialize(LG_Renderer * renderer)
struct Inst * this = UPCAST(struct Inst, renderer);
if (this->imgui)
{
ImGui_ImplOpenGL3_Shutdown();
egl_stateInvalidate();
}
ringbuffer_free(&this->importTimings);
@@ -483,13 +487,14 @@ static void egl_update_scale_type(struct Inst * this)
void egl_resetViewport(EGL * this)
{
glViewport(0, 0, this->width, this->height);
egl_stateViewport(0, 0, this->width, this->height);
}
static void egl_onResize(LG_Renderer * renderer, const int width, const int height, const double scale,
const LG_RendererRect destRect, LG_RendererRotate rotate)
{
struct Inst * this = UPCAST(struct Inst, renderer);
egl_stateCheckShared();
this->width = width * scale;
this->height = height * scale;
@@ -501,7 +506,7 @@ static void egl_onResize(LG_Renderer * renderer, const int width, const int heig
this->destRect.w = destRect.w * scale;
this->destRect.h = destRect.h * scale;
glViewport(0, 0, this->width, this->height);
egl_stateViewport(0, 0, this->width, this->height);
if (destRect.valid)
{
@@ -540,6 +545,7 @@ static void egl_onResize(LG_Renderer * renderer, const int width, const int heig
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplOpenGL3_Init("#version 300 es");
ImGui_ImplOpenGL3_NewFrame();
egl_stateInvalidate();
egl_damageResize(this->damage, this->translateX, this->translateY, this->scaleX, this->scaleY);
egl_desktopResize(this->desktop, this->width, this->height);
@@ -602,8 +608,12 @@ static bool egl_onFrameFormat(LG_Renderer * renderer, const LG_RendererFormat fo
DEBUG_ERROR("Failed to make the frame context current");
return false;
}
egl_stateInvalidate();
}
egl_stateCheckShared();
if (likely(this->scalePointer))
{
float scale = max(1.0f, (float)format.screenWidth / this->width);
@@ -649,6 +659,7 @@ static bool egl_onFrame(LG_Renderer * renderer, const FrameBuffer * frame, int d
const FrameDamageRect * damageRects, int damageRectsCount)
{
struct Inst * this = UPCAST(struct Inst, renderer);
egl_stateCheckShared();
uint64_t start = nanotime();
if (unlikely(!egl_desktopUpdate(
@@ -922,6 +933,7 @@ static bool egl_renderStartup(LG_Renderer * renderer, bool useDMA)
}
eglMakeCurrent(this->display, this->surface, this->surface, this->context);
egl_stateInvalidate();
const char * gl_exts = (const char *)glGetString(GL_EXTENSIONS);
if (!gl_exts)
{
@@ -1038,6 +1050,7 @@ static bool egl_renderStartup(LG_Renderer * renderer, bool useDMA)
DEBUG_ERROR("Failed to initialize ImGui");
return false;
}
egl_stateInvalidate();
app_overlayConfigRegister("EGL", egl_configUI, this);
@@ -1067,7 +1080,7 @@ inline static void renderLetterBox(struct Inst * this)
if (hLB || vLB)
{
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glEnable(GL_SCISSOR_TEST);
egl_stateScissor(true);
if (hLB)
{
@@ -1094,7 +1107,7 @@ inline static void renderLetterBox(struct Inst * this)
glClear(GL_COLOR_BUFFER_BIT);
}
glDisable(GL_SCISSOR_TEST);
egl_stateScissor(false);
}
}
@@ -1103,6 +1116,7 @@ static bool egl_render(LG_Renderer * renderer, LG_RendererRotate rotate,
void (*preSwap)(void * udata), void * udata)
{
struct Inst * this = UPCAST(struct Inst, renderer);
egl_stateCheckShared();
EGLint bufferAge = egl_bufferAge(this);
bool renderAll = invalidateWindow || this->hadOverlay ||
bufferAge <= 0 || bufferAge > MAX_BUFFER_AGE ||
@@ -1213,6 +1227,7 @@ static bool egl_render(LG_Renderer * renderer, LG_RendererRotate rotate,
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplOpenGL3_RenderDrawData(igGetDrawData());
egl_stateInvalidate();
for (int i = 0; i < damageIdx; ++i)
damage[i].y = this->height - damage[i].y - damage[i].h;
@@ -1268,9 +1283,12 @@ static bool egl_render(LG_Renderer * renderer, LG_RendererRotate rotate,
static void * egl_createTexture(LG_Renderer * renderer,
int width, int height, uint8_t * data)
{
egl_stateCheckShared();
GLuint tex;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
egl_stateBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
egl_stateBindTexture(0, GL_TEXTURE_2D, tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
@@ -1289,8 +1307,6 @@ static void * egl_createTexture(LG_Renderer * renderer,
GL_UNSIGNED_BYTE,
data);
glBindTexture(GL_TEXTURE_2D, 0);
return (void*)(intptr_t)tex;
}
@@ -1298,11 +1314,13 @@ static void egl_freeTexture(LG_Renderer * renderer, void * texture)
{
GLuint tex = (GLuint)(intptr_t)texture;
glDeleteTextures(1, &tex);
egl_stateInvalidateShared();
}
static void egl_spiceConfigure(LG_Renderer * renderer, int width, int height)
{
struct Inst * this = UPCAST(struct Inst, renderer);
egl_stateCheckShared();
this->spiceWidth = width;
this->spiceHeight = height;
egl_desktopSpiceConfigure(this->desktop, width, height);
@@ -1312,6 +1330,7 @@ static void egl_spiceDrawFill(LG_Renderer * renderer, int x, int y, int width,
int height, uint32_t color)
{
struct Inst * this = UPCAST(struct Inst, renderer);
egl_stateCheckShared();
egl_desktopSpiceDrawFill(this->desktop, x, y, width, height, color);
}
@@ -1319,6 +1338,7 @@ static void egl_spiceDrawBitmap(LG_Renderer * renderer, int x, int y, int width,
int height, int stride, uint8_t * data, bool topDown)
{
struct Inst * this = UPCAST(struct Inst, renderer);
egl_stateCheckShared();
egl_desktopSpiceDrawBitmap(this->desktop, x, y, width, height, stride,
data, topDown);
}

View File

@@ -19,6 +19,7 @@
*/
#include "framebuffer.h"
#include "state.h"
#include "texture.h"
#include <stdlib.h>
@@ -62,6 +63,7 @@ void egl_framebufferFree(EGL_Framebuffer ** fb)
egl_textureFree(&this->tex);
glDeleteFramebuffers(1, &this->fbo);
egl_stateInvalidate();
free(this);
*fb = NULL;
}
@@ -78,11 +80,11 @@ bool egl_framebufferSetup(EGL_Framebuffer * this, enum EGL_PixelFormat pixFmt,
GLuint tex;
egl_textureGet(this->tex, &tex, NULL, NULL, NULL);
glBindTexture(GL_TEXTURE_2D, tex);
egl_stateBindTexture(0, GL_TEXTURE_2D, tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glBindFramebuffer(GL_FRAMEBUFFER, this->fbo);
egl_stateBindFramebuffer(this->fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D, tex, 0);
glDrawBuffers(1, &(GLenum){GL_COLOR_ATTACHMENT0});
@@ -90,19 +92,20 @@ bool egl_framebufferSetup(EGL_Framebuffer * this, enum EGL_PixelFormat pixFmt,
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE)
{
glBindFramebuffer(GL_FRAMEBUFFER, 0);
egl_stateBindFramebuffer(0);
DEBUG_ERROR("Failed to setup the framebuffer: 0x%x", status);
return false;
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
egl_stateBindFramebuffer(0);
return true;
}
void egl_framebufferBind(EGL_Framebuffer * this)
{
glBindFramebuffer(GL_FRAMEBUFFER, this->fbo);
glViewport(0, 0, this->tex->format.width, this->tex->format.height);
egl_stateBindFramebuffer(this->fbo);
egl_stateViewport(0, 0,
this->tex->format.width, this->tex->format.height);
}
EGL_Texture * egl_framebufferGetTexture(EGL_Framebuffer * this)

View File

@@ -20,6 +20,7 @@
#include "model.h"
#include "shader.h"
#include "state.h"
#include "texture.h"
#include "common/debug.h"
@@ -88,6 +89,8 @@ void egl_modelFree(EGL_Model ** model)
if ((*model)->vao)
glDeleteVertexArrays(1, &(*model)->vao);
egl_stateInvalidateShared();
free(*model);
*model = NULL;
}
@@ -169,16 +172,19 @@ void egl_modelRender(EGL_Model * model)
if (model->rebuild)
{
if (model->buffer)
{
glDeleteBuffers(1, &model->buffer);
egl_stateInvalidateShared();
}
if (!model->vao)
glGenVertexArrays(1, &model->vao);
glBindVertexArray(model->vao);
egl_stateBindVertexArray(model->vao);
/* create a buffer large enough */
glGenBuffers(1, &model->buffer);
glBindBuffer(GL_ARRAY_BUFFER, model->buffer);
egl_stateBindBuffer(GL_ARRAY_BUFFER, model->buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * (model->vertexCount * 5), NULL, GL_STATIC_DRAW);
GLintptr offset = 0;
@@ -206,12 +212,10 @@ void egl_modelRender(EGL_Model * model)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, (void*)(sizeof(GLfloat) * model->vertexCount * 3));
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
model->rebuild = false;
}
glBindVertexArray(model->vao);
egl_stateBindVertexArray(model->vao);
if (model->shader)
egl_shaderUse(model->shader);
@@ -230,10 +234,6 @@ void egl_modelRender(EGL_Model * model)
}
ll_unlock(model->verticies);
/* unbind and cleanup */
glBindTexture(GL_TEXTURE_2D, 0);
glBindVertexArray(0);
glUseProgram(0);
}
void egl_modelSetShader(EGL_Model * model, EGL_Shader * shader)

View File

@@ -19,6 +19,7 @@
*/
#include "shader.h"
#include "state.h"
#include "common/debug.h"
#include "common/stringutils.h"
#include "util.h"
@@ -78,7 +79,10 @@ void egl_shaderFree(EGL_Shader ** shader)
return;
if (this->hasShader)
{
glDeleteProgram(this->shader);
egl_stateInvalidateShared();
}
EGL_Uniform * uniform = this->uniforms;
while (uniform)
@@ -161,6 +165,7 @@ static bool shaderCompile(EGL_Shader * this, const char * vertex_code,
if (this->hasShader)
{
glDeleteProgram(this->shader);
egl_stateInvalidateShared();
this->hasShader = false;
}
@@ -714,7 +719,7 @@ void egl_shaderUse(EGL_Shader * this)
return;
}
glUseProgram(this->shader);
egl_stateUseProgram(this->shader);
while(this->dirtyUniforms)
{

View File

@@ -0,0 +1,55 @@
/**
* 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 "state.h"
#include <stdatomic.h>
#include <string.h>
_Thread_local EGL_StateCache g_eglState;
static _Thread_local unsigned int localGeneration;
static atomic_uint sharedGeneration = 1;
static void stateInvalidate(unsigned int generation)
{
memset(&g_eglState, 0, sizeof(g_eglState));
localGeneration = generation;
}
void egl_stateInvalidate(void)
{
stateInvalidate(atomic_load_explicit(
&sharedGeneration, memory_order_relaxed));
}
void egl_stateInvalidateShared(void)
{
const unsigned int generation = atomic_fetch_add_explicit(
&sharedGeneration, 1, memory_order_release) + 1;
stateInvalidate(generation);
}
void egl_stateCheckShared(void)
{
const unsigned int generation = atomic_load_explicit(
&sharedGeneration, memory_order_acquire);
if (localGeneration != generation)
stateInvalidate(generation);
}

View File

@@ -0,0 +1,266 @@
/**
* 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 <stdbool.h>
#include <GLES3/gl3.h>
#include <GLES2/gl2ext.h>
#define EGL_STATE_TEXTURE_UNITS 16
/*
* GL bindings are context-local and the renderer keeps each context on its
* own thread, so the cache is thread-local too. Code that changes tracked GL
* state without these helpers must invalidate the cache afterwards.
*/
typedef struct EGL_StateBinding
{
GLuint value;
bool valid;
}
EGL_StateBinding;
typedef struct EGL_StateTextureUnit
{
EGL_StateBinding texture2D;
EGL_StateBinding textureExternal;
EGL_StateBinding sampler;
}
EGL_StateTextureUnit;
typedef struct EGL_StateCache
{
EGL_StateBinding program;
EGL_StateBinding framebuffer;
EGL_StateBinding vertexArray;
EGL_StateBinding arrayBuffer;
EGL_StateBinding pixelUnpackBuffer;
EGL_StateBinding activeTexture;
EGL_StateTextureUnit textureUnit[EGL_STATE_TEXTURE_UNITS];
bool viewportValid;
GLint viewportX;
GLint viewportY;
GLsizei viewportWidth;
GLsizei viewportHeight;
bool blendValid;
bool blend;
bool blendFuncValid;
GLenum blendSrc;
GLenum blendDst;
bool scissorValid;
bool scissor;
}
EGL_StateCache;
extern _Thread_local EGL_StateCache g_eglState;
/* Invalidates only the current context/thread cache. */
void egl_stateInvalidate(void);
/* Invalidates all context caches at their next synchronization point. */
void egl_stateInvalidateShared(void);
void egl_stateCheckShared(void);
static inline void egl_stateUseProgram(GLuint program)
{
if (g_eglState.program.valid && g_eglState.program.value == program)
return;
glUseProgram(program);
g_eglState.program.value = program;
g_eglState.program.valid = true;
}
static inline void egl_stateBindFramebuffer(GLuint framebuffer)
{
if (g_eglState.framebuffer.valid &&
g_eglState.framebuffer.value == framebuffer)
return;
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
g_eglState.framebuffer.value = framebuffer;
g_eglState.framebuffer.valid = true;
}
static inline void egl_stateBindVertexArray(GLuint vertexArray)
{
if (g_eglState.vertexArray.valid &&
g_eglState.vertexArray.value == vertexArray)
return;
glBindVertexArray(vertexArray);
g_eglState.vertexArray.value = vertexArray;
g_eglState.vertexArray.valid = true;
}
static inline void egl_stateBindBuffer(GLenum target, GLuint buffer)
{
EGL_StateBinding * binding;
switch(target)
{
case GL_ARRAY_BUFFER:
binding = &g_eglState.arrayBuffer;
break;
case GL_PIXEL_UNPACK_BUFFER:
binding = &g_eglState.pixelUnpackBuffer;
break;
/* GL_ELEMENT_ARRAY_BUFFER belongs to the currently bound VAO. */
default:
glBindBuffer(target, buffer);
return;
}
if (binding->valid && binding->value == buffer)
return;
glBindBuffer(target, buffer);
binding->value = buffer;
binding->valid = true;
}
static inline bool egl_stateActiveTextureUnit(GLuint unit)
{
const GLenum texture = GL_TEXTURE0 + unit;
if (!g_eglState.activeTexture.valid ||
g_eglState.activeTexture.value != texture)
{
glActiveTexture(texture);
g_eglState.activeTexture.value = texture;
g_eglState.activeTexture.valid = true;
}
return unit < EGL_STATE_TEXTURE_UNITS;
}
static inline void egl_stateBindTexture(GLuint unit, GLenum target,
GLuint texture)
{
if (!egl_stateActiveTextureUnit(unit))
{
glBindTexture(target, texture);
return;
}
EGL_StateBinding * binding;
switch(target)
{
case GL_TEXTURE_2D:
binding = &g_eglState.textureUnit[unit].texture2D;
break;
case GL_TEXTURE_EXTERNAL_OES:
binding = &g_eglState.textureUnit[unit].textureExternal;
break;
default:
glBindTexture(target, texture);
return;
}
if (binding->valid && binding->value == texture)
return;
glBindTexture(target, texture);
binding->value = texture;
binding->valid = true;
}
static inline void egl_stateBindSampler(GLuint unit, GLuint sampler)
{
if (unit >= EGL_STATE_TEXTURE_UNITS)
{
glBindSampler(unit, sampler);
return;
}
EGL_StateBinding * binding = &g_eglState.textureUnit[unit].sampler;
if (binding->valid && binding->value == sampler)
return;
glBindSampler(unit, sampler);
binding->value = sampler;
binding->valid = true;
}
static inline void egl_stateViewport(GLint x, GLint y, GLsizei width,
GLsizei height)
{
if (g_eglState.viewportValid &&
g_eglState.viewportX == x &&
g_eglState.viewportY == y &&
g_eglState.viewportWidth == width &&
g_eglState.viewportHeight == height)
return;
glViewport(x, y, width, height);
g_eglState.viewportX = x;
g_eglState.viewportY = y;
g_eglState.viewportWidth = width;
g_eglState.viewportHeight = height;
g_eglState.viewportValid = true;
}
static inline void egl_stateBlend(bool enabled)
{
if (g_eglState.blendValid && g_eglState.blend == enabled)
return;
if (enabled)
glEnable(GL_BLEND);
else
glDisable(GL_BLEND);
g_eglState.blend = enabled;
g_eglState.blendValid = true;
}
static inline void egl_stateBlendFunc(GLenum src, GLenum dst)
{
if (g_eglState.blendFuncValid &&
g_eglState.blendSrc == src && g_eglState.blendDst == dst)
return;
glBlendFunc(src, dst);
g_eglState.blendSrc = src;
g_eglState.blendDst = dst;
g_eglState.blendFuncValid = true;
}
static inline void egl_stateScissor(bool enabled)
{
if (g_eglState.scissorValid && g_eglState.scissor == enabled)
return;
if (enabled)
glEnable(GL_SCISSOR_TEST);
else
glDisable(GL_SCISSOR_TEST);
g_eglState.scissor = enabled;
g_eglState.scissorValid = true;
}

View File

@@ -19,6 +19,7 @@
*/
#include "texture.h"
#include "state.h"
#include <stdbool.h>
#include <string.h>
@@ -88,6 +89,7 @@ void egl_textureFree(EGL_Texture ** tex)
return;
glDeleteSamplers(1, &this->sampler);
egl_stateInvalidateShared();
this->ops.free(this);
*tex = NULL;
@@ -207,7 +209,12 @@ enum EGL_TexStatus egl_textureProcess(EGL_Texture * this)
enum EGL_TexStatus egl_textureBind(EGL_Texture * this)
{
glActiveTexture(GL_TEXTURE0);
glBindSampler(0, this->sampler);
return egl_textureBindWithSampler(this, this->sampler);
}
enum EGL_TexStatus egl_textureBindWithSampler(EGL_Texture * this,
GLuint sampler)
{
egl_stateBindSampler(0, sampler);
return this->ops.bind(this);
}

View File

@@ -145,6 +145,8 @@ static inline EGL_TexStatus egl_textureGet(EGL_Texture * texture, GLuint * tex,
}
enum EGL_TexStatus egl_textureBind(EGL_Texture * texture);
enum EGL_TexStatus egl_textureBindWithSampler(EGL_Texture * texture,
GLuint sampler);
typedef void * PostProcessHandle;
PostProcessHandle egl_textureAddFilter(EGL_Texture * texture,

View File

@@ -19,6 +19,7 @@
*/
#include "texture_buffer.h"
#include "state.h"
#include "egldebug.h"
@@ -35,7 +36,10 @@ static void egl_texBuffer_cleanup(TextureBuffer * this)
egl_texUtilFreeBuffers(this->buf, this->texCount);
if (this->tex[0])
{
glDeleteTextures(this->texCount, this->tex);
egl_stateInvalidateShared();
}
for (int i = 0; i < EGL_TEX_BUFFER_MAX; ++i)
{
@@ -89,9 +93,10 @@ bool egl_texBufferSetup(EGL_Texture * texture, const EGL_TexSetup * setup)
egl_texBuffer_cleanup(this);
glGenTextures(this->texCount, this->tex);
egl_stateBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
for(int i = 0; i < this->texCount; ++i)
{
glBindTexture(GL_TEXTURE_2D, this->tex[i]);
egl_stateBindTexture(0, GL_TEXTURE_2D, this->tex[i]);
glTexImage2D(GL_TEXTURE_2D,
0,
texture->format.intFormat,
@@ -103,7 +108,6 @@ bool egl_texBufferSetup(EGL_Texture * texture, const EGL_TexSetup * setup)
NULL);
}
glBindTexture(GL_TEXTURE_2D, 0);
this->bufIndex = 0;
this->rIndex = -1;
@@ -118,7 +122,8 @@ static bool egl_texBufferUpdate(EGL_Texture * texture, const EGL_TexUpdate * upd
TextureBuffer * this = UPCAST(TextureBuffer, texture);
DEBUG_ASSERT(update->type == EGL_TEXTYPE_BUFFER);
glBindTexture(GL_TEXTURE_2D, this->tex[0]);
egl_stateBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
egl_stateBindTexture(0, GL_TEXTURE_2D, this->tex[0]);
glPixelStorei(GL_UNPACK_ROW_LENGTH, update->stride);
glTexSubImage2D(GL_TEXTURE_2D,
0,
@@ -129,8 +134,6 @@ static bool egl_texBufferUpdate(EGL_Texture * texture, const EGL_TexUpdate * upd
texture->format.format,
texture->format.dataType,
update->buffer);
glBindTexture(GL_TEXTURE_2D, 0);
return true;
}
@@ -292,8 +295,8 @@ EGL_TexStatus egl_texBufferStreamProcess(EGL_Texture * texture)
if (!keepLocked)
LG_UNLOCK(this->copyLock);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer->pbo);
glBindTexture(GL_TEXTURE_2D, tex);
egl_stateBindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer->pbo);
egl_stateBindTexture(0, GL_TEXTURE_2D, tex);
glPixelStorei(GL_UNPACK_ROW_LENGTH, texture->format.stride);
glTexSubImage2D(GL_TEXTURE_2D,
@@ -303,9 +306,6 @@ EGL_TexStatus egl_texBufferStreamProcess(EGL_Texture * texture)
texture->format.format,
texture->format.dataType,
(const void *)0);
glBindTexture(GL_TEXTURE_2D, 0);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
this->sync[index] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
if (unlikely(!this->sync[index]))
{
@@ -348,7 +348,7 @@ EGL_TexStatus egl_texBufferBind(EGL_Texture * texture)
if ((status = texture->ops.get(texture, &tex, NULL)) != EGL_TEX_STATUS_OK)
return status;
glBindTexture(GL_TEXTURE_2D, tex);
egl_stateBindTexture(0, GL_TEXTURE_2D, tex);
return EGL_TEX_STATUS_OK;
}

View File

@@ -20,6 +20,7 @@
#include "texture.h"
#include "texture_buffer.h"
#include "state.h"
#include "util.h"
#include "common/array.h"
@@ -82,7 +83,10 @@ static void egl_texDMABUFCleanup(EGL_Texture * texture)
egl_texUtilFreeBuffers(parent->buf, parent->texCount);
if (parent->tex[0])
{
glDeleteTextures(parent->texCount, parent->tex);
egl_stateInvalidateShared();
}
}
// dmabuf functions
@@ -245,7 +249,7 @@ static bool egl_texDMABUFUpdate(EGL_Texture * texture,
fdImage->texIndex = slot;
INTERLOCKED_SECTION(parent->copyLock,
{
glBindTexture(GL_TEXTURE_EXTERNAL_OES, parent->tex[slot]);
egl_stateBindTexture(0, GL_TEXTURE_EXTERNAL_OES, parent->tex[slot]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
@@ -332,7 +336,7 @@ static EGL_TexStatus egl_texDMABUFBind(EGL_Texture * texture)
if ((status = egl_texDMABUFGet(texture, &tex, NULL)) != EGL_TEX_STATUS_OK)
return status;
glBindTexture(GL_TEXTURE_EXTERNAL_OES, tex);
egl_stateBindTexture(0, GL_TEXTURE_EXTERNAL_OES, tex);
return EGL_TEX_STATUS_OK;
}

View File

@@ -19,6 +19,7 @@
*/
#include "texture.h"
#include "state.h"
#include "texture_util.h"
#include <EGL/egl.h>
@@ -112,7 +113,7 @@ bool egl_texUtilGenBuffers(const EGL_TexFormat * fmt, EGL_TexBuffer * buffers,
buffer->size = fmt->dataSize;
glGenBuffers(1, &buffer->pbo);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer->pbo);
egl_stateBindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer->pbo);
g_egl_dynProcs.glBufferStorageEXT(
GL_PIXEL_UNPACK_BUFFER,
fmt->dataSize,
@@ -131,6 +132,7 @@ bool egl_texUtilGenBuffers(const EGL_TexFormat * fmt, EGL_TexBuffer * buffers,
void egl_texUtilFreeBuffers(EGL_TexBuffer * buffers, int count)
{
bool deleted = false;
for(int i = 0; i < count; ++i)
{
EGL_TexBuffer *buffer = &buffers[i];
@@ -140,13 +142,17 @@ void egl_texUtilFreeBuffers(EGL_TexBuffer * buffers, int count)
egl_texUtilUnmapBuffer(buffer);
glDeleteBuffers(1, &buffer->pbo);
deleted = true;
buffer->pbo = 0;
}
if (deleted)
egl_stateInvalidateShared();
}
bool egl_texUtilMapBuffer(EGL_TexBuffer * buffer)
{
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer->pbo);
egl_stateBindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer->pbo);
buffer->map = glMapBufferRange(
GL_PIXEL_UNPACK_BUFFER,
0,
@@ -160,7 +166,7 @@ bool egl_texUtilMapBuffer(EGL_TexBuffer * buffer)
if (!buffer->map)
DEBUG_GL_ERROR("glMapBufferRange failed of %lu bytes", buffer->size);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
egl_stateBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
return buffer->map;
}
@@ -169,8 +175,8 @@ void egl_texUtilUnmapBuffer(EGL_TexBuffer * buffer)
if (!buffer->map)
return;
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer->pbo);
egl_stateBindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer->pbo);
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
egl_stateBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
buffer->map = NULL;
}