From e5c4fe68dca6e6558d1c48fbc6773bacd150b305 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Fri, 17 Jul 2026 22:38:46 +1000 Subject: [PATCH] [client] egl: cache renderer GL state 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. --- client/renderers/EGL/CMakeLists.txt | 1 + client/renderers/EGL/cursor.c | 15 +- client/renderers/EGL/damage.c | 7 +- client/renderers/EGL/desktop.c | 5 +- client/renderers/EGL/desktop_rects.c | 15 +- client/renderers/EGL/effect.c | 10 +- client/renderers/EGL/egl.c | 34 +++- client/renderers/EGL/framebuffer.c | 15 +- client/renderers/EGL/model.c | 18 +- client/renderers/EGL/shader.c | 7 +- client/renderers/EGL/state.c | 55 ++++++ client/renderers/EGL/state.h | 266 ++++++++++++++++++++++++++ client/renderers/EGL/texture.c | 11 +- client/renderers/EGL/texture.h | 2 + client/renderers/EGL/texture_buffer.c | 22 +-- client/renderers/EGL/texture_dmabuf.c | 8 +- client/renderers/EGL/texture_util.c | 16 +- 17 files changed, 439 insertions(+), 68 deletions(-) create mode 100644 client/renderers/EGL/state.c create mode 100644 client/renderers/EGL/state.h diff --git a/client/renderers/EGL/CMakeLists.txt b/client/renderers/EGL/CMakeLists.txt index 1f7502cf..794be2ab 100644 --- a/client/renderers/EGL/CMakeLists.txt +++ b/client/renderers/EGL/CMakeLists.txt @@ -73,6 +73,7 @@ make_defines( add_library(renderer_EGL STATIC egl.c egldebug.c + state.c shader.c texture_util.c texture.c diff --git a/client/renderers/EGL/cursor.c b/client/renderers/EGL/cursor.c index ee94c00a..174cbb68 100644 --- a/client/renderers/EGL/cursor.c +++ b/client/renderers/EGL/cursor.c @@ -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; } diff --git a/client/renderers/EGL/damage.c b/client/renderers/EGL/damage.c index 31c4d404..e28e8a99 100644 --- a/client/renderers/EGL/damage.c +++ b/client/renderers/EGL/damage.c @@ -26,6 +26,7 @@ #include "app.h" #include "desktop_rects.h" #include "shader.h" +#include "state.h" #include "cimgui.h" #include @@ -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; } diff --git a/client/renderers/EGL/desktop.c b/client/renderers/EGL/desktop.c index 14380790..22acd8ab 100644 --- a/client/renderers/EGL/desktop.c +++ b/client/renderers/EGL/desktop.c @@ -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; } diff --git a/client/renderers/EGL/desktop_rects.c b/client/renderers/EGL/desktop_rects.c index a1ab0dfd..2c2069b8 100644 --- a/client/renderers/EGL/desktop_rects.c +++ b/client/renderers/EGL/desktop_rects.c @@ -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); } diff --git a/client/renderers/EGL/effect.c b/client/renderers/EGL/effect.c index 0c54e3f2..9f292ff2 100644 --- a/client/renderers/EGL/effect.c +++ b/client/renderers/EGL/effect.c @@ -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); diff --git a/client/renderers/EGL/egl.c b/client/renderers/EGL/egl.c index 0b8cd2b8..6ff184ce 100644 --- a/client/renderers/EGL/egl.c +++ b/client/renderers/EGL/egl.c @@ -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); } diff --git a/client/renderers/EGL/framebuffer.c b/client/renderers/EGL/framebuffer.c index 6d429d92..9b4ec69a 100644 --- a/client/renderers/EGL/framebuffer.c +++ b/client/renderers/EGL/framebuffer.c @@ -19,6 +19,7 @@ */ #include "framebuffer.h" +#include "state.h" #include "texture.h" #include @@ -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) diff --git a/client/renderers/EGL/model.c b/client/renderers/EGL/model.c index 438ee340..a6d041cd 100644 --- a/client/renderers/EGL/model.c +++ b/client/renderers/EGL/model.c @@ -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) diff --git a/client/renderers/EGL/shader.c b/client/renderers/EGL/shader.c index b22dfc2a..6c67315f 100644 --- a/client/renderers/EGL/shader.c +++ b/client/renderers/EGL/shader.c @@ -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) { diff --git a/client/renderers/EGL/state.c b/client/renderers/EGL/state.c new file mode 100644 index 00000000..8246bcd2 --- /dev/null +++ b/client/renderers/EGL/state.c @@ -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 +#include + +_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); +} diff --git a/client/renderers/EGL/state.h b/client/renderers/EGL/state.h new file mode 100644 index 00000000..2f68e3d8 --- /dev/null +++ b/client/renderers/EGL/state.h @@ -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 + +#include +#include + +#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; +} diff --git a/client/renderers/EGL/texture.c b/client/renderers/EGL/texture.c index 8b09b491..2f902357 100644 --- a/client/renderers/EGL/texture.c +++ b/client/renderers/EGL/texture.c @@ -19,6 +19,7 @@ */ #include "texture.h" +#include "state.h" #include #include @@ -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); } diff --git a/client/renderers/EGL/texture.h b/client/renderers/EGL/texture.h index 2e9c59d9..1988785d 100644 --- a/client/renderers/EGL/texture.h +++ b/client/renderers/EGL/texture.h @@ -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, diff --git a/client/renderers/EGL/texture_buffer.c b/client/renderers/EGL/texture_buffer.c index 535163fc..b2bc54e1 100644 --- a/client/renderers/EGL/texture_buffer.c +++ b/client/renderers/EGL/texture_buffer.c @@ -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; } diff --git a/client/renderers/EGL/texture_dmabuf.c b/client/renderers/EGL/texture_dmabuf.c index 88568cce..20cb6076 100644 --- a/client/renderers/EGL/texture_dmabuf.c +++ b/client/renderers/EGL/texture_dmabuf.c @@ -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; } diff --git a/client/renderers/EGL/texture_util.c b/client/renderers/EGL/texture_util.c index bac471e3..5b148da6 100644 --- a/client/renderers/EGL/texture_util.c +++ b/client/renderers/EGL/texture_util.c @@ -19,6 +19,7 @@ */ #include "texture.h" +#include "state.h" #include "texture_util.h" #include @@ -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; }