From 80c7bd06bf8eddd3426e8d9816e90ab6b8b1e580 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Sun, 19 Jul 2026 14:39:09 +0100 Subject: [PATCH 01/15] perf(gl): improved state tracking --- src/gl/gl_renderer.c | 135 ++++++++++++++++++++---------------- src/gl/gl_renderer.h | 21 ++++++ src/gl_common/gl_wrappers.h | 23 +++++- 3 files changed, 119 insertions(+), 60 deletions(-) diff --git a/src/gl/gl_renderer.c b/src/gl/gl_renderer.c index 1cc5cb727..c080fab89 100644 --- a/src/gl/gl_renderer.c +++ b/src/gl/gl_renderer.c @@ -479,7 +479,14 @@ static void glInit(Renderer* renderer, DataWin* dataWin) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // Enable blending - glEnable(GL_BLEND); + glSetCap(gl, GL_BLEND, true); + + // Initialise state cache + memset(&gl->state, 0, sizeof(gl->state)); + gl->state.uniformsDirty = true; + gl->state.currentFbo = 0; + gl->state.blendEnabled = true; + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); gl->batchCount = 0; @@ -540,19 +547,27 @@ static void glShaderSettingsRefresh(Renderer* renderer) { float fogG = (float) BGR_G(gl->fogColor) / 255.0f; float fogB = (float) BGR_B(gl->fogColor) / 255.0f; - glUseProgram(gl->defaultShaderProgram->shaderId); + DefaultShaderUniforms cur; + cur.wvp = renderer->gmlMatrices[MATRIX_WORLD_VIEW_PROJECTION]; + Matrix4f_flipClipY(&cur.wvp); + cur.fogColor[0] = fogR; cur.fogColor[1] = fogG; cur.fogColor[2] = fogB; + cur.fogColor[3] = gl->fogEnable ? 1.0f : 0.0f; + cur.alphaTestRef = gl->alphaTestRef; + cur.alphaTestEnabled = gl->alphaTestEnable; - Matrix4f flippedClip[MATRICES_MAX]; - memcpy(flippedClip, renderer->gmlMatrices, sizeof(flippedClip)); - //I was making the Legacy OpenGL renderer work with the projections, then I realized I think I only need to flip the Projection(s) and not the other ones - Matrix4f_flipClipY(&flippedClip[MATRIX_PROJECTION]); - Matrix4f_flipClipY(&flippedClip[MATRIX_WORLD_VIEW_PROJECTION]); + if (!gl->state.uniformsDirty && memcmp(&gl->state.last, &cur, sizeof(cur)) == 0) + return; + + glUseProgram(gl->defaultShaderProgram->shaderId); - glUniformMatrix4fv(gl->uWorldViewProjection->location, 1, GL_FALSE, flippedClip[MATRIX_WORLD_VIEW_PROJECTION].m); - glUniform4f(gl->uFogColor->location, fogR, fogG, fogB, gl->fogEnable ? 1.0f : 0.0f); - glUniform1f(gl->uAlphaTestRef->location, gl->alphaTestRef); - glUniform1i(gl->uAlphaTestEnabled->location, gl->alphaTestEnable); + glUniformMatrix4fv(gl->uWorldViewProjection->location, 1, GL_FALSE, cur.wvp.m); + glUniform4f(gl->uFogColor->location, cur.fogColor[0], cur.fogColor[1], cur.fogColor[2], cur.fogColor[3]); + glUniform1f(gl->uAlphaTestRef->location, cur.alphaTestRef); + glUniform1i(gl->uAlphaTestEnabled->location, cur.alphaTestEnabled); glUniform1i(gl->uTexture->location, 1); + + gl->state.last = cur; + gl->state.uniformsDirty = false; } } @@ -578,6 +593,7 @@ static void glApplyProjection(Renderer* renderer, const Matrix4f* viewMatrix,con renderer->gmlMatrices[MATRIX_WORLD_VIEW] = worldView; renderer->gmlMatrices[MATRIX_WORLD_VIEW_PROJECTION] = worldViewProjection; //oh my I hope it's good enough. + gl->state.uniformsDirty = true; glShaderSettingsRefresh(renderer); } @@ -646,7 +662,7 @@ static void glBeginFrame(Renderer* renderer, int32_t gameW, int32_t gameH, int32 // Bind the application_surface int32_t appId = gl->base.runner->applicationSurfaceId; - glBindFramebuffer(GL_FRAMEBUFFER, gl->surfaces[appId]); + glBindFramebufferCached(gl, GL_FRAMEBUFFER, gl->surfaces[appId]); glViewport(0, 0, gameW, gameH); gl->base.CPortX = 0; gl->base.CPortY = 0; @@ -664,14 +680,14 @@ static void glBeginView(Renderer* renderer, MAYBE_UNUSED int32_t viewX, MAYBE_UN // FBO uses game resolution, port coordinates are in game space // OpenGL viewport Y is bottom-up, game Y is top-down - glViewport(portX, portY, portW, portH); + glViewportCached(gl, portX, portY, portW, portH); gl->base.CPortX = portX; gl->base.CPortY = portY; gl->base.CPortW = portW; gl->base.CPortH = portH; - glEnable(GL_SCISSOR_TEST); + glSetCap(gl, GL_SCISSOR_TEST, true); glScissor(portX, portY, portW, portH); int32_t viewCurrent = 0; @@ -693,7 +709,7 @@ static void glBeginView(Renderer* renderer, MAYBE_UNUSED int32_t viewX, MAYBE_UN static void glEndView(Renderer* renderer) { GLRenderer* gl = (GLRenderer*) renderer; flushBatch(gl); - glDisable(GL_SCISSOR_TEST); + glSetCap(gl, GL_SCISSOR_TEST, false); } static void glBeginGUI(Renderer* renderer, MAYBE_UNUSED int32_t guiW, MAYBE_UNUSED int32_t guiH, int32_t portX, int32_t portY, int32_t portW, MAYBE_UNUSED int32_t portH, int32_t targetSurfaceId) { @@ -703,19 +719,19 @@ static void glBeginGUI(Renderer* renderer, MAYBE_UNUSED int32_t guiW, MAYBE_UNUS gl->currentTextureId = 0; if (targetSurfaceId == RENDER_TARGET_HOST_FRAMEBUFFER) { - glBindFramebuffer(GL_FRAMEBUFFER, gl->hostFramebuffer); + glBindFramebufferCached(gl, GL_FRAMEBUFFER, gl->hostFramebuffer); glViewport(0, 0, portW, portH); glScissor(0, 0, portW, portH); } else { require(targetSurfaceId >= 0 && (uint32_t) targetSurfaceId < gl->surfaceCount); require(gl->surfaces[targetSurfaceId] != 0); - glBindFramebuffer(GL_FRAMEBUFFER, gl->surfaces[targetSurfaceId]); + glBindFramebufferCached(gl, GL_FRAMEBUFFER, gl->surfaces[targetSurfaceId]); int32_t glPortY = gl->gameH - portY - portH; glViewport(portX, glPortY, portW, portH); glScissor(portX, glPortY, portW, portH); } - glEnable(GL_SCISSOR_TEST); + glSetCap(gl, GL_SCISSOR_TEST, true); //I dunno hopefully this is at least somewhat correct... gl->base.cameraCurrent = GUI_CAMERA; GMLCamera* camera = &renderer->runner->guiCamera; @@ -785,7 +801,7 @@ static void glSetGuiProjection(Renderer* renderer, int32_t guiW, int32_t guiH, M static void glEndGUI(Renderer* renderer) { GLRenderer* gl = (GLRenderer*) renderer; flushBatch(gl); - glDisable(GL_SCISSOR_TEST); + glSetCap(gl, GL_SCISSOR_TEST, false); } static void glEndFrameInit(Renderer* renderer) { @@ -793,7 +809,7 @@ static void glEndFrameInit(Renderer* renderer) { if (hasVAO()) glBindVertexArray(0); if (renderer->runner->usingAppSurface && !renderer->runner->appSurfaceAutoDraw) { - glBindFramebuffer(GL_FRAMEBUFFER, gl->hostFramebuffer); + glBindFramebufferCached(gl, GL_FRAMEBUFFER, gl->hostFramebuffer); return; } } @@ -810,9 +826,8 @@ static void glEndFrameEnd(Renderer* renderer) { GLCommon_beginLetterboxBlit(gl->surfaces[appId], gl->hostFramebuffer); GLCommon_endLetterboxBlit(gl->surfaceWidth[appId], gl->surfaceHeight[appId], gl->gameW, gl->gameH, gl->windowW, gl->windowH, gl->hostFramebuffer); } else { - glBindFramebuffer(GL_FRAMEBUFFER, gl->hostFramebuffer); - GLboolean scissorWasEnabled = glIsEnabled(GL_SCISSOR_TEST); - if (scissorWasEnabled) glDisable(GL_SCISSOR_TEST); + glBindFramebufferCached(gl, GL_FRAMEBUFFER, gl->hostFramebuffer); + glSetCap(gl, GL_SCISSOR_TEST, !(gl->state.scissorEnabled)); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); @@ -820,7 +835,7 @@ static void glEndFrameEnd(Renderer* renderer) { glViewport(0, 0, gl->windowW, gl->windowH); renderer->vtable->setGuiProjection(renderer, gl->windowW, gl->windowH, gl->windowW, gl->windowH, false); - glDisable(GL_BLEND); + glSetCap(gl, GL_BLEND, false); int32_t sx, sy, ex, ey; GLCommon_computeLetterbox(gl->gameW, gl->gameH, gl->windowW, gl->windowH, &sx, &sy, &ex, &ey); @@ -830,8 +845,8 @@ static void glEndFrameEnd(Renderer* renderer) { renderer->vtable->drawSurface(renderer, appId, 0, 0, gl->gameW, gl->gameH, (float)sx, (float)sy, scaleX, scaleY, 0.0f, 0xFFFFFF, 1.0f); flushBatch(gl); - glEnable(GL_BLEND); - if (scissorWasEnabled) glEnable(GL_SCISSOR_TEST); + glSetCap(gl, GL_BLEND, false); + glSetCap(gl, GL_SCISSOR_TEST, !(gl->state.scissorEnabled)); } } @@ -1919,8 +1934,7 @@ static int32_t glCreateSurface(Renderer* renderer, int32_t width, int32_t height flushBatch(gl); // Save the current FBO binding so creating a surface doesn't change the active render target. - GLint prevBinding = 0; - glGetIntegerv(GL_FRAMEBUFFER_BINDING, &prevBinding); + GLint prevBinding = gl->state.currentFbo; uint32_t surfaceIndex = GLCommon_findOrAllocateSurfaceSlot(&gl->surfaces, &gl->surfaceTexture, &gl->surfaceWidth, &gl->surfaceHeight, &gl->surfaceCount); @@ -1938,14 +1952,14 @@ static int32_t glCreateSurface(Renderer* renderer, int32_t width, int32_t height glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode); - glBindFramebuffer(GL_FRAMEBUFFER, gl->surfaces[surfaceIndex]); + glBindFramebufferCached(gl, GL_FRAMEBUFFER, gl->surfaces[surfaceIndex]); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gl->surfaceTexture[surfaceIndex], 0); gl->surfaceWidth[surfaceIndex] = width; gl->surfaceHeight[surfaceIndex] = height; fprintf(stderr, "GL: Created surface %u with size (%dx%d)\n", surfaceIndex, width, height); - glBindFramebuffer(GL_FRAMEBUFFER, (GLuint) prevBinding); + glBindFramebufferCached(gl, GL_FRAMEBUFFER, (GLuint) prevBinding); return (int32_t) surfaceIndex; } @@ -1994,8 +2008,7 @@ static void glSurfaceResize(Renderer* renderer, int32_t surfaceID, int32_t width if (gl->surfaces[surfaceID] == 0) return; if (gl->surfaceWidth[surfaceID] == width && gl->surfaceHeight[surfaceID] == height) return; - GLint prevBinding = 0; - glGetIntegerv(GL_FRAMEBUFFER_BINDING, &prevBinding); + GLint prevBinding = gl->state.currentFbo; if (gl->surfaceTexture[surfaceID] != 0) glDeleteTextures(1, &gl->surfaceTexture[surfaceID]); @@ -2007,14 +2020,14 @@ static void glSurfaceResize(Renderer* renderer, int32_t surfaceID, int32_t width glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glBindFramebuffer(GL_FRAMEBUFFER, gl->surfaces[surfaceID]); + glBindFramebufferCached(gl, GL_FRAMEBUFFER, gl->surfaces[surfaceID]); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gl->surfaceTexture[surfaceID], 0); gl->surfaceWidth[surfaceID] = width; gl->surfaceHeight[surfaceID] = height; fprintf(stderr, "GL: Resized Surface %u Size (%dx%d)\n", surfaceID, width, height); - glBindFramebuffer(GL_FRAMEBUFFER, (GLuint) prevBinding); + glBindFramebufferCached(gl, GL_FRAMEBUFFER, (GLuint) prevBinding); } static bool glSurfaceExists(Renderer* renderer, int32_t surfaceId) { @@ -2044,11 +2057,11 @@ static bool glSetRenderTarget(Renderer* renderer, int32_t surfaceId, bool implic if (0 > surfaceId || (uint32_t) surfaceId >= gl->surfaceCount) return false; if (gl->surfaces[surfaceId] == 0) return false; - glBindFramebuffer(GL_FRAMEBUFFER, gl->surfaces[surfaceId]); + glBindFramebufferCached(gl, GL_FRAMEBUFFER, gl->surfaces[surfaceId]); if (surfaceId == renderer->runner->applicationSurfaceId && implicitApplicationSurface) { glViewport(gl->base.CPortX, gl->base.CPortY, gl->base.CPortW, gl->base.CPortH); - glEnable(GL_SCISSOR_TEST); + glSetCap(gl, GL_SCISSOR_TEST, true); glApplyProjection(renderer,&camera->viewMatrix,&camera->projectionMatrix); @@ -2058,8 +2071,8 @@ static bool glSetRenderTarget(Renderer* renderer, int32_t surfaceId, bool implic if (surfaceId == view->surfaceId) { //the surface belongs to the view we are rending, we use the view's camera. - glViewport(0, 0, gl->surfaceWidth[surfaceId], gl->surfaceHeight[surfaceId]); - glDisable(GL_SCISSOR_TEST); + glViewportCached(gl, 0, 0, gl->surfaceWidth[surfaceId], gl->surfaceHeight[surfaceId]); + glSetCap(gl, GL_SCISSOR_TEST, false); glApplyProjection(renderer,&camera->viewMatrix,&camera->projectionMatrix); return true; } else { @@ -2080,15 +2093,15 @@ static bool glSetRenderTarget(Renderer* renderer, int32_t surfaceId, bool implic camera->viewAngle = 0; Runner_updateCameraViewSimple(camera); - glViewport(0, 0, gl->surfaceWidth[surfaceId], gl->surfaceHeight[surfaceId]); - glDisable(GL_SCISSOR_TEST); + glViewportCached(gl, 0, 0, gl->surfaceWidth[surfaceId], gl->surfaceHeight[surfaceId]); + glSetCap(gl, GL_SCISSOR_TEST, false); glApplyProjection(renderer, &camera->viewMatrix,&camera->projectionMatrix); return true; } - glViewport(0, 0, gl->surfaceWidth[surfaceId], gl->surfaceHeight[surfaceId]); - glDisable(GL_SCISSOR_TEST); + glViewportCached(gl, 0, 0, gl->surfaceWidth[surfaceId], gl->surfaceHeight[surfaceId]); + glSetCap(gl, GL_SCISSOR_TEST, false); return true; } @@ -2103,16 +2116,15 @@ static void glSurfaceCopy(Renderer* renderer, int32_t destSurfaceID, int32_t des if (gl->isGL3) { GLCommon_surfaceBlit(gl->surfaces, gl->surfaceWidth, gl->surfaceHeight, gl->surfaceCount, destSurfaceID, destX, destY, srcSurfaceID, srcX, srcY, srcW, srcH, part); } else { - GLint prevBinding = 0; - glGetIntegerv(GL_FRAMEBUFFER_BINDING, &prevBinding); + GLuint prevBinding = gl->state.currentFbo; Matrix4f prevProj = renderer->gmlMatrices[MATRIX_WORLD_VIEW_PROJECTION]; - bool prevBlend = glIsEnabled(GL_BLEND); - GLint prevViewport[4]; - glGetIntegerv(GL_VIEWPORT, prevViewport); + bool prevBlend = gl->state.blendEnabled; + int32_t prevViewport[4]; + memcpy(prevViewport, gl->state.viewport, sizeof(prevViewport)); - glBindFramebuffer(GL_FRAMEBUFFER, gl->surfaces[destSurfaceID]); - glViewport(0, 0, gl->surfaceWidth[destSurfaceID], gl->surfaceHeight[destSurfaceID]); - glDisable(GL_BLEND); + glBindFramebufferCached(gl, GL_FRAMEBUFFER, gl->surfaces[destSurfaceID]); + glViewportCached(gl, 0, 0, gl->surfaceWidth[destSurfaceID], gl->surfaceHeight[destSurfaceID]); + glSetCap(gl, GL_BLEND, false); renderer->vtable->setGuiProjection(renderer, gl->surfaceWidth[destSurfaceID], gl->surfaceHeight[destSurfaceID], gl->surfaceWidth[destSurfaceID], gl->surfaceHeight[destSurfaceID], true); @@ -2124,11 +2136,11 @@ static void glSurfaceCopy(Renderer* renderer, int32_t destSurfaceID, int32_t des renderer->vtable->drawSurface(renderer, srcSurfaceID, sX, sY, sW, sH, (float)destX, (float)destY, 1.0f, 1.0f, 0.0f, 0xFFFFFF, 1.0f); flushBatch(gl); - glBindFramebuffer(GL_FRAMEBUFFER, (GLuint) prevBinding); + glBindFramebufferCached(gl, GL_FRAMEBUFFER, (GLuint) prevBinding); renderer->gmlMatrices[MATRIX_WORLD_VIEW_PROJECTION] = prevProj; glShaderSettingsRefresh(renderer); - if (prevBlend) glEnable(GL_BLEND); - glViewport(prevViewport[0], prevViewport[1], prevViewport[2], prevViewport[3]); + if (prevBlend) glSetCap(gl, GL_BLEND, true); + glViewportCached(gl, prevViewport[0], prevViewport[1], prevViewport[2], prevViewport[3]); } } @@ -2238,7 +2250,7 @@ static int32_t glCreateSpriteFromSurface(Renderer* renderer, int32_t surfaceID, // Flush any pending draws before reading pixels flushBatch(gl); - glBindFramebuffer(GL_FRAMEBUFFER, gl->surfaces[surfaceID]); + glBindFramebufferCached(gl, GL_FRAMEBUFFER, gl->surfaces[surfaceID]); uint8_t* pixels = (uint8_t *)safeMalloc((size_t) w * (size_t) h * 4); if (pixels == nullptr) return -1; @@ -2352,6 +2364,7 @@ static int32_t glGpuGetBlendMode(Renderer* renderer) { static void glGpuSetBlendMode(Renderer* renderer, int32_t mode) { GLRenderer* gl = (GLRenderer*) renderer; + if (gl->currentBlendMode == mode) return; flushBatch(gl); gl->currentBlendMode = mode; @@ -2382,13 +2395,14 @@ static void glGpuSetBlendModeExt(Renderer* renderer, int32_t sfactor, int32_t df } static void glGpuSetBlendEnable(Renderer* renderer, bool enable) { - flushBatch((GLRenderer*)renderer); - enable ? glEnable(GL_BLEND) : glDisable(GL_BLEND); + GLRenderer* gl = (GLRenderer*) renderer; + flushBatch(gl); + glSetCap(gl, GL_BLEND, enable); } static bool glGpuGetBlendEnable(Renderer* renderer) { - (void)renderer; - return glIsEnabled(GL_BLEND); + GLRenderer* gl = (GLRenderer*) renderer; + return gl->state.blendEnabled; } static void glGpuSetAlphaTestEnable(Renderer* renderer, bool enable) { @@ -2396,6 +2410,7 @@ static void glGpuSetAlphaTestEnable(Renderer* renderer, bool enable) { if (gl->alphaTestEnable == enable) return; flushBatch(gl); gl->alphaTestEnable = enable; + gl->state.uniformsDirty = true; glShaderSettingsRefresh(renderer); } @@ -2405,6 +2420,7 @@ static void glGpuSetAlphaTestRef(Renderer* renderer, uint8_t ref) { if (gl->alphaTestRef == refF) return; flushBatch(gl); gl->alphaTestRef = refF; + gl->state.uniformsDirty = true; glShaderSettingsRefresh(renderer); } @@ -2432,6 +2448,7 @@ static void glGpuSetFog(Renderer* renderer, bool enable, uint32_t color) { flushBatch(gl); gl->fogEnable = enable; gl->fogColor = color; + gl->state.uniformsDirty = true; glShaderSettingsRefresh(renderer); } @@ -2659,7 +2676,7 @@ static void glSetMatrix(Renderer* renderer, int32_t matrixType, Matrix4f matrix) renderer->gmlMatrices[MATRIX_WORLD_VIEW] = worldView; renderer->gmlMatrices[MATRIX_WORLD_VIEW_PROJECTION] = worldViewProjection; - + gl->state.uniformsDirty = true; glShaderSettingsRefresh(renderer); } diff --git a/src/gl/gl_renderer.h b/src/gl/gl_renderer.h index 26a5a7178..f33504e37 100644 --- a/src/gl/gl_renderer.h +++ b/src/gl/gl_renderer.h @@ -36,6 +36,25 @@ typedef struct { uint8_t r, g, b, a; } Vertex; +typedef struct { + Matrix4f wvp; + float fogColor[4]; // RGB + A (A=0 disabled, A=1 enabled) + float alphaTestRef; + bool alphaTestEnabled; +} DefaultShaderUniforms; + +typedef struct { + // Cached GL state + GLuint currentFbo; + int32_t viewport[4]; + bool blendEnabled; + bool scissorEnabled; + + // Default shader uniform tracking + bool uniformsDirty; + DefaultShaderUniforms last; +} GLState; + // Exposed in the header so platform-specific code (main.c) can access FBO fields for screenshots. typedef struct { Renderer base; // Must be first field for struct embedding @@ -98,6 +117,8 @@ typedef struct { GLShaderUniform* uAlphaTestRef; GLShaderUniform* uAlphaTestEnabled; GLShaderUniform* uTexture; + + GLState state; } GLRenderer; bool GLRenderer_ensureTextureLoaded(GLRenderer* gl, uint32_t pageId); diff --git a/src/gl_common/gl_wrappers.h b/src/gl_common/gl_wrappers.h index 01ce02abe..7723c8e67 100644 --- a/src/gl_common/gl_wrappers.h +++ b/src/gl_common/gl_wrappers.h @@ -1,6 +1,8 @@ #if !defined(_BS_GL_WRAPPERS_H_) && !defined(__EMSCRIPTEN__) && !defined(PLATFORM_PS3) && !defined(__ANDROID__) #define _BS_GL_WRAPPERS_H_ +#include "gl_renderer.h" + static inline void gl_init_wrappers(void) { if (!glBindVertexArray) glBindVertexArray = glBindVertexArrayOES; @@ -36,4 +38,23 @@ static inline void gl_init_wrappers(void) { glBlendFuncSeparate = glBlendFuncSeparateEXT; } -#endif +static inline void glBindFramebufferCached(GLRenderer* gl, GLenum target, GLuint fbo) { + if (target == GL_FRAMEBUFFER || target == GL_DRAW_FRAMEBUFFER) + gl->state.currentFbo = fbo; + glBindFramebuffer(target, fbo); +} + +static inline void glViewportCached(GLRenderer* gl, int32_t x, int32_t y, int32_t w, int32_t h) { + gl->state.viewport[0] = x; gl->state.viewport[1] = y; + gl->state.viewport[2] = w; gl->state.viewport[3] = h; + glViewport(x, y, w, h); +} + +static inline void glSetCap(GLRenderer* gl, GLenum cap, bool enable) { + if (cap == GL_SCISSOR_TEST) gl->state.scissorEnabled = enable; + else if (cap == GL_BLEND) gl->state.blendEnabled = enable; + if (enable) glEnable(cap); + else glDisable(cap); +} + +#endif \ No newline at end of file From ea4658686d4d1febb0220c826617b2ae2cc85b4a Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Sun, 19 Jul 2026 15:51:33 +0100 Subject: [PATCH 02/15] fix web build? --- src/gl/gl_renderer.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/gl/gl_renderer.c b/src/gl/gl_renderer.c index c080fab89..55bb6926a 100644 --- a/src/gl/gl_renderer.c +++ b/src/gl/gl_renderer.c @@ -73,6 +73,26 @@ static inline uint8_t floatToUnormByte(float v) { return (uint8_t)(v * 255.0f + 0.5f); } +// ===[ State Tracking Wrappers ]=== +static inline void glBindFramebufferCached(GLRenderer* gl, GLenum target, GLuint fbo) { + if (target == GL_FRAMEBUFFER || target == GL_DRAW_FRAMEBUFFER) + gl->state.currentFbo = fbo; + glBindFramebuffer(target, fbo); +} + +static inline void glViewportCached(GLRenderer* gl, int32_t x, int32_t y, int32_t w, int32_t h) { + gl->state.viewport[0] = x; gl->state.viewport[1] = y; + gl->state.viewport[2] = w; gl->state.viewport[3] = h; + glViewport(x, y, w, h); +} + +static inline void glSetCap(GLRenderer* gl, GLenum cap, bool enable) { + if (cap == GL_SCISSOR_TEST) gl->state.scissorEnabled = enable; + else if (cap == GL_BLEND) gl->state.blendEnabled = enable; + if (enable) glEnable(cap); + else glDisable(cap); +} + // ===[ Shader Compilation ]=== static GLuint compileShader(GLenum type, const char* source, bool* ok) { From 9b08a78c006854684757318abd5f52f7b9ae2607 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Sun, 19 Jul 2026 15:53:04 +0100 Subject: [PATCH 03/15] whoops --- src/gl_common/gl_wrappers.h | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/gl_common/gl_wrappers.h b/src/gl_common/gl_wrappers.h index 7723c8e67..69545ab61 100644 --- a/src/gl_common/gl_wrappers.h +++ b/src/gl_common/gl_wrappers.h @@ -38,23 +38,4 @@ static inline void gl_init_wrappers(void) { glBlendFuncSeparate = glBlendFuncSeparateEXT; } -static inline void glBindFramebufferCached(GLRenderer* gl, GLenum target, GLuint fbo) { - if (target == GL_FRAMEBUFFER || target == GL_DRAW_FRAMEBUFFER) - gl->state.currentFbo = fbo; - glBindFramebuffer(target, fbo); -} - -static inline void glViewportCached(GLRenderer* gl, int32_t x, int32_t y, int32_t w, int32_t h) { - gl->state.viewport[0] = x; gl->state.viewport[1] = y; - gl->state.viewport[2] = w; gl->state.viewport[3] = h; - glViewport(x, y, w, h); -} - -static inline void glSetCap(GLRenderer* gl, GLenum cap, bool enable) { - if (cap == GL_SCISSOR_TEST) gl->state.scissorEnabled = enable; - else if (cap == GL_BLEND) gl->state.blendEnabled = enable; - if (enable) glEnable(cap); - else glDisable(cap); -} - #endif \ No newline at end of file From 83adf45ca8e30bb5f9839e0dbc627a40612ca71f Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Sun, 19 Jul 2026 16:11:22 +0100 Subject: [PATCH 04/15] perf(legacy-gl): improved state tracking also improved readability of setRenderTarget --- src/gl/gl_renderer.c | 58 ++++++----- src/gl_legacy/gl_legacy_renderer.c | 156 ++++++++++++++++------------- src/gl_legacy/gl_legacy_renderer.h | 13 +++ 3 files changed, 127 insertions(+), 100 deletions(-) diff --git a/src/gl/gl_renderer.c b/src/gl/gl_renderer.c index 55bb6926a..59f80220e 100644 --- a/src/gl/gl_renderer.c +++ b/src/gl/gl_renderer.c @@ -2067,9 +2067,7 @@ static bool glSetRenderTarget(Renderer* renderer, int32_t surfaceId, bool implic flushBatch(gl); int32_t viewCurrent = 0; - if (renderer->runner->viewsEnabled) { - viewCurrent = renderer->runner->viewCurrent; - } + if (renderer->runner->viewsEnabled) viewCurrent = renderer->runner->viewCurrent; RuntimeView* view = &renderer->runner->views[viewCurrent]; gl->base.cameraCurrent = view->cameraId; GMLCamera* camera = Runner_getCameraById(renderer->runner, gl->base.cameraCurrent); @@ -2083,40 +2081,40 @@ static bool glSetRenderTarget(Renderer* renderer, int32_t surfaceId, bool implic glViewport(gl->base.CPortX, gl->base.CPortY, gl->base.CPortW, gl->base.CPortH); glSetCap(gl, GL_SCISSOR_TEST, true); - glApplyProjection(renderer,&camera->viewMatrix,&camera->projectionMatrix); + glApplyProjection(renderer, &camera->viewMatrix ,&camera->projectionMatrix); return true; } if (surfaceId == view->surfaceId) { - //the surface belongs to the view we are rending, we use the view's camera. - glViewportCached(gl, 0, 0, gl->surfaceWidth[surfaceId], gl->surfaceHeight[surfaceId]); - glSetCap(gl, GL_SCISSOR_TEST, false); - glApplyProjection(renderer,&camera->viewMatrix,&camera->projectionMatrix); - return true; + //the surface belongs to the view we are rending, we use the view's camera. + glViewportCached(gl, 0, 0, gl->surfaceWidth[surfaceId], gl->surfaceHeight[surfaceId]); + glSetCap(gl, GL_SCISSOR_TEST, false); + glApplyProjection(renderer, &camera->viewMatrix, &camera->projectionMatrix); + return true; } else { - //camera will use full surface. - gl->base.cameraCurrent = SURFACE_CAMERA; - GMLCamera* camera = &renderer->runner->surfaceCamera; - - camera->allocated = true; - camera->viewX = 0.0; - camera->viewY = 0.0; - camera->viewWidth = gl->surfaceWidth[surfaceId]; - camera->viewHeight = gl->surfaceHeight[surfaceId]; - camera->borderX = 0; - camera->borderY = 0; - camera->speedX = 0; - camera->speedY = 0; - camera->objectId = -1; - camera->viewAngle = 0; - Runner_updateCameraViewSimple(camera); - - glViewportCached(gl, 0, 0, gl->surfaceWidth[surfaceId], gl->surfaceHeight[surfaceId]); - glSetCap(gl, GL_SCISSOR_TEST, false); - glApplyProjection(renderer, &camera->viewMatrix,&camera->projectionMatrix); - return true; + //camera will use full surface. + gl->base.cameraCurrent = SURFACE_CAMERA; + GMLCamera* camera = &renderer->runner->surfaceCamera; + + camera->allocated = true; + camera->viewX = 0.0; + camera->viewY = 0.0; + camera->viewWidth = gl->surfaceWidth[surfaceId]; + camera->viewHeight = gl->surfaceHeight[surfaceId]; + camera->borderX = 0; + camera->borderY = 0; + camera->speedX = 0; + camera->speedY = 0; + camera->objectId = -1; + camera->viewAngle = 0; + Runner_updateCameraViewSimple(camera); + + glViewportCached(gl, 0, 0, gl->surfaceWidth[surfaceId], gl->surfaceHeight[surfaceId]); + glSetCap(gl, GL_SCISSOR_TEST, false); + glApplyProjection(renderer, &camera->viewMatrix,&camera->projectionMatrix); + return true; } diff --git a/src/gl_legacy/gl_legacy_renderer.c b/src/gl_legacy/gl_legacy_renderer.c index baf6f9211..a4b601e5f 100644 --- a/src/gl_legacy/gl_legacy_renderer.c +++ b/src/gl_legacy/gl_legacy_renderer.c @@ -17,7 +17,7 @@ extern GLint gPalettedUPaletteVLoc; if (0.0f > _v) break; \ glActiveTexture(GL_TEXTURE1); \ glBindTexture(GL_TEXTURE_2D, PS3Textures_getClutTexture()); \ - glEnable(GL_TEXTURE_2D); \ + glSetCap(gl, GL_TEXTURE_2D, true); \ glActiveTexture(GL_TEXTURE0); \ glUseProgram(gPalettedProgram); \ if (gPalettedUPaletteVLoc >= 0) glUniform1f(gPalettedUPaletteVLoc, _v); \ @@ -25,7 +25,7 @@ extern GLint gPalettedUPaletteVLoc; #define PS3_PALETTED_END() do { \ glUseProgram(0); \ glActiveTexture(GL_TEXTURE1); \ - glDisable(GL_TEXTURE_2D); \ + glSetCap(gl, GL_TEXTURE_2D, false); \ glActiveTexture(GL_TEXTURE0); \ } while (0) #else @@ -90,11 +90,34 @@ static bool hasFBO() { #endif } +// ===[ State Tracking Wrappers ]=== + +static inline void glBindFramebufferCached(GLLegacyRenderer* gl, GLenum target, GLuint fbo) { + if (target == GL_FRAMEBUFFER || target == GL_DRAW_FRAMEBUFFER) + gl->state.currentFbo = fbo; + glBindFramebuffer(target, fbo); +} + +static inline void glViewportCached(GLLegacyRenderer* gl, int32_t x, int32_t y, int32_t w, int32_t h) { + gl->state.viewport[0] = x; gl->state.viewport[1] = y; + gl->state.viewport[2] = w; gl->state.viewport[3] = h; + glViewport(x, y, w, h); +} + +static inline void glSetCap(GLLegacyRenderer* gl, GLenum cap, bool enable) { + if (cap == GL_BLEND) gl->state.blendEnabled = enable; + else if (cap == GL_SCISSOR_TEST) gl->state.scissorEnabled = enable; + else if (cap == GL_DEPTH_TEST) gl->state.depthTestEnabled = enable; + else if (cap == GL_TEXTURE_2D) gl->state.texture2DEnabled = enable; + if (enable) glEnable(cap); + else glDisable(cap); +} + // ===[ Helpers ]=== static void glApplyViewport(GLLegacyRenderer* gl, int32_t x, int32_t y, int32_t w, int32_t h) { - glViewport(x, y, w, h); - glEnable(GL_SCISSOR_TEST); + glViewportCached(gl, x, y, w, h); + glSetCap(gl, GL_SCISSOR_TEST, true); glScissor(x, y, w, h); gl->base.CPortX = x; @@ -161,8 +184,8 @@ static void glInit(Renderer* renderer, DataWin* dataWin) { } // Prepare texture slots for lazy loading (PNG decode deferred to first use) - glEnable(GL_TEXTURE_2D); - glDisable(GL_DEPTH_TEST); + glSetCap(gl, GL_TEXTURE_2D, true); + glSetCap(gl, GL_DEPTH_TEST, false); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); #ifdef PLATFORM_PS3 @@ -193,7 +216,7 @@ static void glInit(Renderer* renderer, DataWin* dataWin) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // Enable blending - glEnable(GL_BLEND); + glSetCap(gl, GL_BLEND, true); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBindTexture(GL_TEXTURE_2D, 0); @@ -245,8 +268,8 @@ static void glBeginFrame(Renderer* renderer, int32_t gameW, int32_t gameH, int32 // Bind the application_surface (sized/created by Runner_beginFrame's ensureApplicationSurface call right before this). int32_t appId = gl->base.runner->applicationSurfaceId; - glBindFramebuffer(GL_FRAMEBUFFER, gl->surfaces[appId]); - glViewport(0, 0, gameW, gameH); + glBindFramebufferCached(gl, GL_FRAMEBUFFER, gl->surfaces[appId]); + glViewportCached(gl, 0, 0, gameW, gameH); gl->base.CPortX = 0; gl->base.CPortY = 0; gl->base.CPortW = gameW; @@ -278,7 +301,8 @@ static void glBeginView(Renderer* renderer, MAYBE_UNUSED int32_t viewX, MAYBE_UN } static void glEndView(MAYBE_UNUSED Renderer* renderer) { - glDisable(GL_SCISSOR_TEST); + GLLegacyRenderer* gl = (GLLegacyRenderer*) renderer; + glSetCap(gl, GL_SCISSOR_TEST, false); } static void glBeginGUI(Renderer* renderer, int32_t guiW, int32_t guiH, int32_t portX, int32_t portY, int32_t portW, int32_t portH, int32_t targetSurfaceId) { @@ -287,14 +311,14 @@ static void glBeginGUI(Renderer* renderer, int32_t guiW, int32_t guiH, int32_t p glBindTexture(GL_TEXTURE_2D, 0); if (targetSurfaceId == RENDER_TARGET_HOST_FRAMEBUFFER) { - glBindFramebuffer(GL_FRAMEBUFFER, 0); - glViewport(0, 0, portW, portH); - glEnable(GL_SCISSOR_TEST); + glBindFramebufferCached(gl, GL_FRAMEBUFFER, 0); + glViewportCached(gl, 0, 0, portW, portH); + glSetCap(gl, GL_SCISSOR_TEST, true); glScissor(0, 0, portW, portH); } else { require(targetSurfaceId >= 0 && (uint32_t) targetSurfaceId < gl->surfaceCount); require(gl->surfaces[targetSurfaceId] != 0); - glBindFramebuffer(GL_FRAMEBUFFER, gl->surfaces[targetSurfaceId]); + glBindFramebufferCached(gl, GL_FRAMEBUFFER, gl->surfaces[targetSurfaceId]); glApplyViewport(gl, portX, portY, portW, portH); } @@ -363,13 +387,14 @@ static void glSetGuiProjection(MAYBE_UNUSED Renderer* renderer, int32_t guiW, in } static void glEndGUI(MAYBE_UNUSED Renderer* renderer) { - glDisable(GL_SCISSOR_TEST); + GLLegacyRenderer* gl = (GLLegacyRenderer*) renderer; + glSetCap(gl, GL_SCISSOR_TEST, false); } static void glEndFrameInit(Renderer* renderer) { GLLegacyRenderer* gl = (GLLegacyRenderer*) renderer; if (renderer->runner->usingAppSurface && !renderer->runner->appSurfaceAutoDraw) { - glBindFramebuffer(GL_FRAMEBUFFER, 0); + glBindFramebufferCached(gl, GL_FRAMEBUFFER, 0); return; } int32_t appId = gl->base.runner->applicationSurfaceId; @@ -1512,17 +1537,18 @@ static void glGpuSetBlendModeExt(Renderer* renderer, int32_t sfactor, int32_t df } static void glGpuSetBlendEnable(Renderer* renderer, bool enable) { - (void)renderer; - enable ? glEnable(GL_BLEND) : glDisable(GL_BLEND); + GLLegacyRenderer* gl = (GLLegacyRenderer*) renderer; + glSetCap(gl, GL_BLEND, enable); } -static bool glGpuGetBlendEnable(MAYBE_UNUSED Renderer* renderer) { - - return glIsEnabled(GL_BLEND); +static bool glGpuGetBlendEnable(Renderer* renderer) { + GLLegacyRenderer* gl = (GLLegacyRenderer*) renderer; + return gl->state.blendEnabled; } -static void glGpuSetAlphaTestEnable(MAYBE_UNUSED Renderer* renderer, bool enable) { - enable ? glEnable(GL_ALPHA_TEST) : glDisable(GL_ALPHA_TEST); +static void glGpuSetAlphaTestEnable(Renderer* renderer, bool enable) { + GLLegacyRenderer* gl = (GLLegacyRenderer*) renderer; + glSetCap(gl, GL_ALPHA_TEST, enable); } static void glGpuSetAlphaTestRef(MAYBE_UNUSED Renderer* renderer, uint8_t ref) { @@ -1552,8 +1578,7 @@ static int32_t glLegacyCreateSurface(Renderer* renderer, int32_t width, int32_t GLLegacyRenderer* gl = (GLLegacyRenderer*) renderer; // Save the current FBO binding so creating a surface doesn't change the active render target. - GLint prevBinding = 0; - glGetIntegerv(GL_FRAMEBUFFER_BINDING, &prevBinding); + GLint prevBinding = gl->state.currentFbo; uint32_t surfaceIndex = GLCommon_findOrAllocateSurfaceSlot(&gl->surfaces, &gl->surfaceTexture, &gl->surfaceWidth, &gl->surfaceHeight, &gl->surfaceCount); @@ -1569,7 +1594,7 @@ static int32_t glLegacyCreateSurface(Renderer* renderer, int32_t width, int32_t glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - glBindFramebuffer(GL_FRAMEBUFFER, gl->surfaces[surfaceIndex]); + glBindFramebufferCached(gl, GL_FRAMEBUFFER, gl->surfaces[surfaceIndex]); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gl->surfaceTexture[surfaceIndex], 0); GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); @@ -1581,7 +1606,7 @@ static int32_t glLegacyCreateSurface(Renderer* renderer, int32_t width, int32_t gl->surfaceHeight[surfaceIndex] = height; fprintf(stderr, "GL: Created surface %u with size (%dx%d)\n", surfaceIndex, width, height); - glBindFramebuffer(GL_FRAMEBUFFER, (GLuint) prevBinding); + glBindFramebufferCached(gl, GL_FRAMEBUFFER, (GLuint) prevBinding); return (int32_t) surfaceIndex; } @@ -1629,8 +1654,7 @@ static void glLegacySurfaceResize(Renderer* renderer, int32_t surfaceId, int32_t if (gl->surfaces[surfaceId] == 0) return; if (gl->surfaceWidth[surfaceId] == width && gl->surfaceHeight[surfaceId] == height) return; - GLint prevBinding = 0; - glGetIntegerv(GL_FRAMEBUFFER_BINDING, &prevBinding); + GLint prevBinding = gl->state.currentFbo; if (gl->surfaceTexture[surfaceId] != 0) glDeleteTextures(1, &gl->surfaceTexture[surfaceId]); @@ -1645,9 +1669,9 @@ static void glLegacySurfaceResize(Renderer* renderer, int32_t surfaceId, int32_t glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - glBindFramebuffer(GL_FRAMEBUFFER, gl->surfaces[surfaceId]); + glBindFramebufferCached(gl, GL_FRAMEBUFFER, gl->surfaces[surfaceId]); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gl->surfaceTexture[surfaceId], 0); - glBindFramebuffer(GL_FRAMEBUFFER, (GLuint) prevBinding); + glBindFramebufferCached(gl, GL_FRAMEBUFFER, (GLuint) prevBinding); gl->surfaceWidth[surfaceId] = width; gl->surfaceHeight[surfaceId] = height; @@ -1672,9 +1696,7 @@ static bool glLegacySetRenderTarget(Renderer* renderer, int32_t surfaceId, bool GLLegacyRenderer* gl = (GLLegacyRenderer*) renderer; int32_t viewCurrent = 0; - if (renderer->runner->viewsEnabled) { - viewCurrent = renderer->runner->viewCurrent; - } + if (renderer->runner->viewsEnabled) viewCurrent = renderer->runner->viewCurrent; RuntimeView* view = &renderer->runner->views[viewCurrent]; gl->base.cameraCurrent = view->cameraId; GMLCamera* camera = Runner_getCameraById(renderer->runner, gl->base.cameraCurrent); @@ -1682,50 +1704,44 @@ static bool glLegacySetRenderTarget(Renderer* renderer, int32_t surfaceId, bool if (0 > surfaceId || (uint32_t) surfaceId >= gl->surfaceCount) return false; if (gl->surfaces[surfaceId] == 0) return false; - glBindFramebuffer(GL_FRAMEBUFFER, gl->surfaces[surfaceId]); + glBindFramebufferCached(gl, GL_FRAMEBUFFER, gl->surfaces[surfaceId]); if (surfaceId == renderer->runner->applicationSurfaceId && implicitApplicationSurface) { - glViewport(gl->base.CPortX, gl->base.CPortY, gl->base.CPortW, gl->base.CPortH); - glEnable(GL_SCISSOR_TEST); - glApplyProjection(renderer,&camera->viewMatrix,&camera->projectionMatrix); + glViewportCached(gl, gl->base.CPortX, gl->base.CPortY, gl->base.CPortW, gl->base.CPortH); + glSetCap(gl, GL_SCISSOR_TEST, true); + glApplyProjection(renderer, &camera->viewMatrix, &camera->projectionMatrix); return true; } if (surfaceId == view->surfaceId) { - //the surface belongs to the view we are rending, we use the view's camera. - glViewport(0, 0, gl->surfaceWidth[surfaceId], gl->surfaceHeight[surfaceId]); - glDisable(GL_SCISSOR_TEST); - glApplyProjection(renderer,&camera->viewMatrix,&camera->projectionMatrix); - return true; + //the surface belongs to the view we are rending, we use the view's camera. + glViewportCached(gl, 0, 0, gl->surfaceWidth[surfaceId], gl->surfaceHeight[surfaceId]); + glSetCap(gl, GL_SCISSOR_TEST, false); + glApplyProjection(renderer, &camera->viewMatrix, &camera->projectionMatrix); + return true; } else { - //camera will use full surface. - gl->base.cameraCurrent = SURFACE_CAMERA; - GMLCamera* camera = &renderer->runner->surfaceCamera; - - camera->allocated = true; - camera->viewX = 0.0; - camera->viewY = 0.0; - camera->viewWidth = gl->surfaceWidth[surfaceId]; - camera->viewHeight = gl->surfaceHeight[surfaceId]; - camera->borderX = 0; - camera->borderY = 0; - camera->speedX = 0; - camera->speedY = 0; - camera->objectId = -1; - camera->viewAngle = 0; - Runner_updateCameraViewSimple(camera); - - glViewport(0, 0, gl->surfaceWidth[surfaceId], gl->surfaceHeight[surfaceId]); - glDisable(GL_SCISSOR_TEST); - glApplyProjection(renderer, &camera->viewMatrix,&camera->projectionMatrix); - return true; + //camera will use full surface. + gl->base.cameraCurrent = SURFACE_CAMERA; + GMLCamera* camera = &renderer->runner->surfaceCamera; + + camera->allocated = true; + camera->viewX = 0.0; + camera->viewY = 0.0; + camera->viewWidth = gl->surfaceWidth[surfaceId]; + camera->viewHeight = gl->surfaceHeight[surfaceId]; + camera->borderX = 0; + camera->borderY = 0; + camera->speedX = 0; + camera->speedY = 0; + camera->objectId = -1; + camera->viewAngle = 0; + Runner_updateCameraViewSimple(camera); + + glViewportCached(gl, 0, 0, gl->surfaceWidth[surfaceId], gl->surfaceHeight[surfaceId]); + glSetCap(gl, GL_SCISSOR_TEST, false); + glApplyProjection(renderer, &camera->viewMatrix,&camera->projectionMatrix); + return true; } - - - glViewport(0, 0, gl->surfaceWidth[surfaceId], gl->surfaceHeight[surfaceId]); - glDisable(GL_SCISSOR_TEST); - - return true; } // Resolves a surfaceID to a GL texture and its actual texture size diff --git a/src/gl_legacy/gl_legacy_renderer.h b/src/gl_legacy/gl_legacy_renderer.h index a423bf4bf..433af7423 100644 --- a/src/gl_legacy/gl_legacy_renderer.h +++ b/src/gl_legacy/gl_legacy_renderer.h @@ -11,6 +11,16 @@ #include #endif +typedef struct { + // Cached GL state + GLuint currentFbo; + int32_t viewport[4]; + bool blendEnabled; + bool scissorEnabled; + bool depthTestEnabled; + bool texture2DEnabled; +} LegacyGLState; + // ===[ GLLegacyRenderer Struct ]=== // Exposed in the header so platform-specific code (main.c) can access FBO fields for screenshots. typedef struct { @@ -53,6 +63,9 @@ typedef struct { int32_t currentDFactor; int32_t currentSFactorAlpha; int32_t currentDFactorAlpha; + + // State tracking + LegacyGLState state; } GLLegacyRenderer; bool GLLegacyRenderer_ensureTextureLoaded(GLLegacyRenderer* gl, uint32_t pageId); From 91fd728842b0db27a7f5c382e5776daaa5010feb Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Sun, 19 Jul 2026 16:22:06 +0100 Subject: [PATCH 05/15] refactor(legacy-gl): use switch-case for glSetCap --- src/gl_legacy/gl_legacy_renderer.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/gl_legacy/gl_legacy_renderer.c b/src/gl_legacy/gl_legacy_renderer.c index a4b601e5f..d99df5285 100644 --- a/src/gl_legacy/gl_legacy_renderer.c +++ b/src/gl_legacy/gl_legacy_renderer.c @@ -105,12 +105,14 @@ static inline void glViewportCached(GLLegacyRenderer* gl, int32_t x, int32_t y, } static inline void glSetCap(GLLegacyRenderer* gl, GLenum cap, bool enable) { - if (cap == GL_BLEND) gl->state.blendEnabled = enable; - else if (cap == GL_SCISSOR_TEST) gl->state.scissorEnabled = enable; - else if (cap == GL_DEPTH_TEST) gl->state.depthTestEnabled = enable; - else if (cap == GL_TEXTURE_2D) gl->state.texture2DEnabled = enable; + switch (cap) { + case GL_BLEND: gl->state.blendEnabled = enable; break; + case GL_SCISSOR_TEST: gl->state.scissorEnabled = enable; break; + case GL_DEPTH_TEST: gl->state.depthTestEnabled = enable; break; + case GL_TEXTURE_2D: gl->state.texture2DEnabled = enable; break; + } if (enable) glEnable(cap); - else glDisable(cap); + else glDisable(cap); } // ===[ Helpers ]=== From fb9d2a0db507762858fefa4c3264cb97c85a48d9 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Mon, 20 Jul 2026 13:13:43 +0100 Subject: [PATCH 06/15] perf(gl): early return if things are the same, also cache scissor state --- src/gl/gl_renderer.c | 52 ++++++++++++++++++++++-------- src/gl/gl_renderer.h | 1 + src/gl_legacy/gl_legacy_renderer.c | 45 +++++++++++++++++++++----- src/gl_legacy/gl_legacy_renderer.h | 1 + 4 files changed, 77 insertions(+), 22 deletions(-) diff --git a/src/gl/gl_renderer.c b/src/gl/gl_renderer.c index 59f80220e..5f6cfcfaf 100644 --- a/src/gl/gl_renderer.c +++ b/src/gl/gl_renderer.c @@ -74,23 +74,47 @@ static inline uint8_t floatToUnormByte(float v) { } // ===[ State Tracking Wrappers ]=== + static inline void glBindFramebufferCached(GLRenderer* gl, GLenum target, GLuint fbo) { - if (target == GL_FRAMEBUFFER || target == GL_DRAW_FRAMEBUFFER) - gl->state.currentFbo = fbo; + if (gl->state.currentFbo == fbo) return; + gl->state.currentFbo = fbo; glBindFramebuffer(target, fbo); } static inline void glViewportCached(GLRenderer* gl, int32_t x, int32_t y, int32_t w, int32_t h) { + if (gl->state.viewport[0] == x && gl->state.viewport[1] == y && + gl->state.viewport[2] == w && gl->state.viewport[3] == h) { + return; + } gl->state.viewport[0] = x; gl->state.viewport[1] = y; gl->state.viewport[2] = w; gl->state.viewport[3] = h; glViewport(x, y, w, h); } +static inline void glScissorCached(GLRenderer* gl, int32_t x, int32_t y, int32_t w, int32_t h) { + if (gl->state.scissor[0] == x && gl->state.scissor[1] == y && + gl->state.scissor[2] == w && gl->state.scissor[3] == h) { + return; + } + gl->state.scissor[0] = x; gl->state.scissor[1] = y; + gl->state.scissor[2] = w; gl->state.scissor[3] = h; + glScissor(x, y, w, h); +} + static inline void glSetCap(GLRenderer* gl, GLenum cap, bool enable) { - if (cap == GL_SCISSOR_TEST) gl->state.scissorEnabled = enable; - else if (cap == GL_BLEND) gl->state.blendEnabled = enable; - if (enable) glEnable(cap); - else glDisable(cap); + switch (cap) { + case GL_SCISSOR_TEST: + if (gl->state.scissorEnabled == enable) return; + gl->state.scissorEnabled = enable; + break; + case GL_BLEND: + if (gl->state.blendEnabled == enable) return; + gl->state.blendEnabled = enable; + break; + default: + break; + } + enable ? glEnable(cap) : glDisable(cap); } // ===[ Shader Compilation ]=== @@ -683,7 +707,7 @@ static void glBeginFrame(Renderer* renderer, int32_t gameW, int32_t gameH, int32 // Bind the application_surface int32_t appId = gl->base.runner->applicationSurfaceId; glBindFramebufferCached(gl, GL_FRAMEBUFFER, gl->surfaces[appId]); - glViewport(0, 0, gameW, gameH); + glViewportCached(gl, 0, 0, gameW, gameH); gl->base.CPortX = 0; gl->base.CPortY = 0; gl->base.CPortW = gameW; @@ -708,7 +732,7 @@ static void glBeginView(Renderer* renderer, MAYBE_UNUSED int32_t viewX, MAYBE_UN gl->base.CPortH = portH; glSetCap(gl, GL_SCISSOR_TEST, true); - glScissor(portX, portY, portW, portH); + glScissorCached(gl, portX, portY, portW, portH); int32_t viewCurrent = 0; if (renderer->runner->viewsEnabled) { @@ -740,15 +764,15 @@ static void glBeginGUI(Renderer* renderer, MAYBE_UNUSED int32_t guiW, MAYBE_UNUS if (targetSurfaceId == RENDER_TARGET_HOST_FRAMEBUFFER) { glBindFramebufferCached(gl, GL_FRAMEBUFFER, gl->hostFramebuffer); - glViewport(0, 0, portW, portH); - glScissor(0, 0, portW, portH); + glViewportCached(gl, 0, 0, portW, portH); + glScissorCached(gl, 0, 0, portW, portH); } else { require(targetSurfaceId >= 0 && (uint32_t) targetSurfaceId < gl->surfaceCount); require(gl->surfaces[targetSurfaceId] != 0); glBindFramebufferCached(gl, GL_FRAMEBUFFER, gl->surfaces[targetSurfaceId]); int32_t glPortY = gl->gameH - portY - portH; - glViewport(portX, glPortY, portW, portH); - glScissor(portX, glPortY, portW, portH); + glViewportCached(gl, portX, glPortY, portW, portH); + glScissorCached(gl, portX, glPortY, portW, portH); } glSetCap(gl, GL_SCISSOR_TEST, true); @@ -852,7 +876,7 @@ static void glEndFrameEnd(Renderer* renderer) { glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); - glViewport(0, 0, gl->windowW, gl->windowH); + glViewportCached(gl, 0, 0, gl->windowW, gl->windowH); renderer->vtable->setGuiProjection(renderer, gl->windowW, gl->windowH, gl->windowW, gl->windowH, false); glSetCap(gl, GL_BLEND, false); @@ -2078,7 +2102,7 @@ static bool glSetRenderTarget(Renderer* renderer, int32_t surfaceId, bool implic glBindFramebufferCached(gl, GL_FRAMEBUFFER, gl->surfaces[surfaceId]); if (surfaceId == renderer->runner->applicationSurfaceId && implicitApplicationSurface) { - glViewport(gl->base.CPortX, gl->base.CPortY, gl->base.CPortW, gl->base.CPortH); + glViewportCached(gl, gl->base.CPortX, gl->base.CPortY, gl->base.CPortW, gl->base.CPortH); glSetCap(gl, GL_SCISSOR_TEST, true); glApplyProjection(renderer, &camera->viewMatrix ,&camera->projectionMatrix); diff --git a/src/gl/gl_renderer.h b/src/gl/gl_renderer.h index f33504e37..e2f5574d9 100644 --- a/src/gl/gl_renderer.h +++ b/src/gl/gl_renderer.h @@ -47,6 +47,7 @@ typedef struct { // Cached GL state GLuint currentFbo; int32_t viewport[4]; + int32_t scissor[4]; bool blendEnabled; bool scissorEnabled; diff --git a/src/gl_legacy/gl_legacy_renderer.c b/src/gl_legacy/gl_legacy_renderer.c index d99df5285..69e2d4e34 100644 --- a/src/gl_legacy/gl_legacy_renderer.c +++ b/src/gl_legacy/gl_legacy_renderer.c @@ -93,23 +93,52 @@ static bool hasFBO() { // ===[ State Tracking Wrappers ]=== static inline void glBindFramebufferCached(GLLegacyRenderer* gl, GLenum target, GLuint fbo) { - if (target == GL_FRAMEBUFFER || target == GL_DRAW_FRAMEBUFFER) - gl->state.currentFbo = fbo; + if (gl->state.currentFbo == fbo) return; + gl->state.currentFbo = fbo; glBindFramebuffer(target, fbo); } static inline void glViewportCached(GLLegacyRenderer* gl, int32_t x, int32_t y, int32_t w, int32_t h) { + if (gl->state.viewport[0] == x && gl->state.viewport[1] == y && + gl->state.viewport[2] == w && gl->state.viewport[3] == h) return; + gl->state.viewport[0] = x; gl->state.viewport[1] = y; gl->state.viewport[2] = w; gl->state.viewport[3] = h; glViewport(x, y, w, h); } +static inline void glScissorCached(GLLegacyRenderer* gl, int32_t x, int32_t y, int32_t w, int32_t h) { + if (gl->state.scissor[0] == x && gl->state.scissor[1] == y && + gl->state.scissor[2] == w && gl->state.scissor[3] == h) return; + + gl->state.scissor[0] = x; gl->state.scissor[1] = y; + gl->state.scissor[2] = w; gl->state.scissor[3] = h; + glScissor(x, y, w, h); +} + + static inline void glSetCap(GLLegacyRenderer* gl, GLenum cap, bool enable) { switch (cap) { - case GL_BLEND: gl->state.blendEnabled = enable; break; - case GL_SCISSOR_TEST: gl->state.scissorEnabled = enable; break; - case GL_DEPTH_TEST: gl->state.depthTestEnabled = enable; break; - case GL_TEXTURE_2D: gl->state.texture2DEnabled = enable; break; + case GL_BLEND: { + if (gl->state.blendEnabled == enable) return; + gl->state.blendEnabled = enable; + break; + } + case GL_SCISSOR_TEST: { + if (gl->state.scissorEnabled == enable) return; + gl->state.scissorEnabled = enable; + break; + } + case GL_DEPTH_TEST: { + if (gl->state.depthTestEnabled == enable) return; + gl->state.depthTestEnabled = enable; + break; + } + case GL_TEXTURE_2D: { + if (gl->state.texture2DEnabled == enable) return; + gl->state.texture2DEnabled = enable; + break; + } } if (enable) glEnable(cap); else glDisable(cap); @@ -120,7 +149,7 @@ static inline void glSetCap(GLLegacyRenderer* gl, GLenum cap, bool enable) { static void glApplyViewport(GLLegacyRenderer* gl, int32_t x, int32_t y, int32_t w, int32_t h) { glViewportCached(gl, x, y, w, h); glSetCap(gl, GL_SCISSOR_TEST, true); - glScissor(x, y, w, h); + glScissorCached(gl, x, y, w, h); gl->base.CPortX = x; gl->base.CPortY = y; @@ -316,7 +345,7 @@ static void glBeginGUI(Renderer* renderer, int32_t guiW, int32_t guiH, int32_t p glBindFramebufferCached(gl, GL_FRAMEBUFFER, 0); glViewportCached(gl, 0, 0, portW, portH); glSetCap(gl, GL_SCISSOR_TEST, true); - glScissor(0, 0, portW, portH); + glScissorCached(gl, 0, 0, portW, portH); } else { require(targetSurfaceId >= 0 && (uint32_t) targetSurfaceId < gl->surfaceCount); require(gl->surfaces[targetSurfaceId] != 0); diff --git a/src/gl_legacy/gl_legacy_renderer.h b/src/gl_legacy/gl_legacy_renderer.h index 433af7423..2154e4b51 100644 --- a/src/gl_legacy/gl_legacy_renderer.h +++ b/src/gl_legacy/gl_legacy_renderer.h @@ -15,6 +15,7 @@ typedef struct { // Cached GL state GLuint currentFbo; int32_t viewport[4]; + int32_t scissor[4]; bool blendEnabled; bool scissorEnabled; bool depthTestEnabled; From d2c221fbe59e44600a447e808deee035f603728b Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Mon, 20 Jul 2026 13:40:59 +0100 Subject: [PATCH 07/15] perf(gl): cached letterbox blit, cache clearColor --- src/gl/gl_renderer.c | 44 ++++++++++++++++++++++++++---- src/gl/gl_renderer.h | 3 ++ src/gl_legacy/gl_legacy_renderer.c | 37 ++++++++++++++++++++++--- src/gl_legacy/gl_legacy_renderer.h | 3 ++ 4 files changed, 78 insertions(+), 9 deletions(-) diff --git a/src/gl/gl_renderer.c b/src/gl/gl_renderer.c index 5f6cfcfaf..770769d1c 100644 --- a/src/gl/gl_renderer.c +++ b/src/gl/gl_renderer.c @@ -76,7 +76,21 @@ static inline uint8_t floatToUnormByte(float v) { // ===[ State Tracking Wrappers ]=== static inline void glBindFramebufferCached(GLRenderer* gl, GLenum target, GLuint fbo) { - if (gl->state.currentFbo == fbo) return; + switch (target) { + case GL_FRAMEBUFFER: + if (gl->state.currentReadFbo == fbo && gl->state.currentDrawFbo == fbo) return; + gl->state.currentReadFbo = fbo; + gl->state.currentDrawFbo = fbo; + break; + case GL_READ_FRAMEBUFFER: + if (gl->state.currentReadFbo == fbo) return; + gl->state.currentReadFbo = fbo; + break; + case GL_DRAW_FRAMEBUFFER: + if (gl->state.currentDrawFbo == fbo) return; + gl->state.currentDrawFbo = fbo; + break; + } gl->state.currentFbo = fbo; glBindFramebuffer(target, fbo); } @@ -117,6 +131,14 @@ static inline void glSetCap(GLRenderer* gl, GLenum cap, bool enable) { enable ? glEnable(cap) : glDisable(cap); } +static inline void glClearColorCached(GLRenderer* gl, float r, float g, float b, float a) { + if (gl->state.clearColor[0] == r && gl->state.clearColor[1] == g && + gl->state.clearColor[2] == b && gl->state.clearColor[3] == a) return; + gl->state.clearColor[0] = r; gl->state.clearColor[1] = g; + gl->state.clearColor[2] = b; gl->state.clearColor[3] = a; + glClearColor(r, g, b, a); +} + // ===[ Shader Compilation ]=== static GLuint compileShader(GLenum type, const char* source, bool* ok) { @@ -867,13 +889,25 @@ static void glEndFrameEnd(Renderer* renderer) { int32_t appId = gl->base.runner->applicationSurfaceId; if (gl->isGL3) { - GLCommon_beginLetterboxBlit(gl->surfaces[appId], gl->hostFramebuffer); - GLCommon_endLetterboxBlit(gl->surfaceWidth[appId], gl->surfaceHeight[appId], gl->gameW, gl->gameH, gl->windowW, gl->windowH, gl->hostFramebuffer); + GLuint prevRead = gl->state.currentReadFbo; + GLuint prevDraw = gl->state.currentDrawFbo; + + glBindFramebufferCached(gl, GL_READ_FRAMEBUFFER, gl->surfaces[appId]); + glBindFramebufferCached(gl, GL_DRAW_FRAMEBUFFER, gl->hostFramebuffer); + + glClearColorCached(gl, 0.0f, 0.0f, 0.0f, 1.0f); + int32_t sx, sy, ex, ey; + GLCommon_computeLetterbox(gl->gameW, gl->gameH, gl->windowW, gl->windowH, &sx, &sy, &ex, &ey); + glBlitFramebuffer(0, 0, gl->surfaceWidth[appId], gl->surfaceHeight[appId], + sx, ey, ex, sy, GL_COLOR_BUFFER_BIT, GL_NEAREST); + + glBindFramebufferCached(gl, GL_READ_FRAMEBUFFER, prevRead); + glBindFramebufferCached(gl, GL_DRAW_FRAMEBUFFER, prevDraw); } else { glBindFramebufferCached(gl, GL_FRAMEBUFFER, gl->hostFramebuffer); glSetCap(gl, GL_SCISSOR_TEST, !(gl->state.scissorEnabled)); - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClearColorCached(gl, 0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glViewportCached(gl, 0, 0, gl->windowW, gl->windowH); @@ -908,7 +942,7 @@ static void glClearScreen(Renderer* renderer, uint32_t color, float alpha) { // GML draw_clear ignores the active scissor and clears the whole target. Disable scissor for the clear and restore it after. //No it doesn't? - glClearColor(r, g, b, alpha); + glClearColorCached(gl, r, g, b, alpha); glClear(GL_COLOR_BUFFER_BIT); } diff --git a/src/gl/gl_renderer.h b/src/gl/gl_renderer.h index e2f5574d9..c8525b395 100644 --- a/src/gl/gl_renderer.h +++ b/src/gl/gl_renderer.h @@ -46,8 +46,11 @@ typedef struct { typedef struct { // Cached GL state GLuint currentFbo; + GLuint currentReadFbo; + GLuint currentDrawFbo; int32_t viewport[4]; int32_t scissor[4]; + float clearColor[4]; bool blendEnabled; bool scissorEnabled; diff --git a/src/gl_legacy/gl_legacy_renderer.c b/src/gl_legacy/gl_legacy_renderer.c index 69e2d4e34..6ac9e0365 100644 --- a/src/gl_legacy/gl_legacy_renderer.c +++ b/src/gl_legacy/gl_legacy_renderer.c @@ -144,6 +144,14 @@ static inline void glSetCap(GLLegacyRenderer* gl, GLenum cap, bool enable) { else glDisable(cap); } +static inline void glClearColorCached(GLLegacyRenderer* gl, float r, float g, float b, float a) { + if (gl->state.clearColor[0] == r && gl->state.clearColor[1] == g && + gl->state.clearColor[2] == b && gl->state.clearColor[3] == a) return; + gl->state.clearColor[0] = r; gl->state.clearColor[1] = g; + gl->state.clearColor[2] = b; gl->state.clearColor[3] = a; + glClearColor(r, g, b, a); +} + // ===[ Helpers ]=== static void glApplyViewport(GLLegacyRenderer* gl, int32_t x, int32_t y, int32_t w, int32_t h) { @@ -248,6 +256,12 @@ static void glInit(Renderer* renderer, DataWin* dataWin) { // Enable blending glSetCap(gl, GL_BLEND, true); + + memset(&gl->state, 0, sizeof(gl->state)); + gl->state.currentFbo = 0; + gl->state.blendEnabled = true; + gl->state.texture2DEnabled = true; + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBindTexture(GL_TEXTURE_2D, 0); @@ -429,7 +443,8 @@ static void glEndFrameInit(Renderer* renderer) { return; } int32_t appId = gl->base.runner->applicationSurfaceId; - GLCommon_beginLetterboxBlit(gl->surfaces[appId], 0); + glBindFramebufferCached(gl, GL_READ_FRAMEBUFFER, gl->surfaces[appId]); + glBindFramebufferCached(gl, GL_DRAW_FRAMEBUFFER, 0); } static void glEndFrameEnd(Renderer* renderer) { @@ -438,8 +453,21 @@ static void glEndFrameEnd(Renderer* renderer) { return; } int32_t appId = gl->base.runner->applicationSurfaceId; - GLCommon_beginLetterboxBlit(gl->surfaces[appId], 0); - GLCommon_endLetterboxBlit(gl->surfaceWidth[appId], gl->surfaceHeight[appId], gl->gameW, gl->gameH, gl->windowW, gl->windowH, 0); + + GLuint prevRead = gl->state.currentReadFbo; + GLuint prevDraw = gl->state.currentDrawFbo; + + glBindFramebufferCached(gl, GL_READ_FRAMEBUFFER, gl->surfaces[appId]); + glBindFramebufferCached(gl, GL_DRAW_FRAMEBUFFER, 0); + + glClearColorCached(gl, 0.0f, 0.0f, 0.0f, 1.0f); + int32_t sx, sy, ex, ey; + GLCommon_computeLetterbox(gl->gameW, gl->gameH, gl->windowW, gl->windowH, &sx, &sy, &ex, &ey); + glBlitFramebuffer(0, 0, gl->surfaceWidth[appId], gl->surfaceHeight[appId], + sx, ey, ex, sy, GL_COLOR_BUFFER_BIT, GL_NEAREST); + + glBindFramebufferCached(gl, GL_READ_FRAMEBUFFER, prevRead); + glBindFramebufferCached(gl, GL_DRAW_FRAMEBUFFER, prevDraw); } static void glRendererFlush(MAYBE_UNUSED Renderer* renderer) {} @@ -451,7 +479,8 @@ static void glClearScreen(MAYBE_UNUSED Renderer* renderer, uint32_t color, float // GML draw_clear ignores the active scissor and clears the whole target. Disable scissor for the clear and restore it after. - glClearColor(r, g, b, alpha); + GLLegacyRenderer* gl = (GLLegacyRenderer*) renderer; + glClearColorCached(gl, r, g, b, alpha); glClear(GL_COLOR_BUFFER_BIT); } diff --git a/src/gl_legacy/gl_legacy_renderer.h b/src/gl_legacy/gl_legacy_renderer.h index 2154e4b51..741401aa2 100644 --- a/src/gl_legacy/gl_legacy_renderer.h +++ b/src/gl_legacy/gl_legacy_renderer.h @@ -14,8 +14,11 @@ typedef struct { // Cached GL state GLuint currentFbo; + GLuint currentReadFbo; + GLuint currentDrawFbo; int32_t viewport[4]; int32_t scissor[4]; + float clearColor[4]; bool blendEnabled; bool scissorEnabled; bool depthTestEnabled; From b29710d45c8651d3a0ccaaf9655bd401f955d5af Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Mon, 20 Jul 2026 13:59:58 +0100 Subject: [PATCH 08/15] perf(gl): cache activeTexture, GL_STREAM_DRAW, remove redundant VAO rebinding --- src/gl/gl_renderer.c | 47 +++++++++++++++++------------- src/gl/gl_renderer.h | 3 ++ src/gl_legacy/gl_legacy_renderer.c | 20 ++++++++----- src/gl_legacy/gl_legacy_renderer.h | 1 + 4 files changed, 43 insertions(+), 28 deletions(-) diff --git a/src/gl/gl_renderer.c b/src/gl/gl_renderer.c index 770769d1c..e9f79518a 100644 --- a/src/gl/gl_renderer.c +++ b/src/gl/gl_renderer.c @@ -139,6 +139,12 @@ static inline void glClearColorCached(GLRenderer* gl, float r, float g, float b, glClearColor(r, g, b, a); } +static inline void glActiveTextureCached(GLRenderer* gl, GLenum unit) { + if (gl->state.activeTexUnit == (int32_t)unit) return; + gl->state.activeTexUnit = (int32_t)unit; + glActiveTexture(unit); +} + // ===[ Shader Compilation ]=== static GLuint compileShader(GLenum type, const char* source, bool* ok) { @@ -204,10 +210,10 @@ static void flushBatch(GLRenderer* gl) { GLShaderUniform* uniform = findShaderUniformByName(shader, "gm_BaseTexture"); if (uniform != nullptr) - glActiveTexture(GL_TEXTURE0 + uniform->samplerSlot); + glActiveTextureCached(gl, GL_TEXTURE0 + uniform->samplerSlot); glBindTexture(GL_TEXTURE_2D, gl->currentTextureId); } else { - glActiveTexture(GL_TEXTURE1); + glActiveTextureCached(gl, GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, gl->currentTextureId); } @@ -217,13 +223,10 @@ static void flushBatch(GLRenderer* gl) { int32_t totalVboSize = MAX_QUADS * VERTICES_PER_QUAD * sizeof(Vertex); if (hasVAO()) { - glBindVertexArray(gl->vao); - glBindBuffer(GL_ARRAY_BUFFER, gl->vbo); - glBufferData(GL_ARRAY_BUFFER, totalVboSize, nullptr, GL_DYNAMIC_DRAW); glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount * sizeof(Vertex), gl->vertexData); } else { glBindBuffer(GL_ARRAY_BUFFER, gl->vbo); - glBufferData(GL_ARRAY_BUFFER, totalVboSize, nullptr, GL_DYNAMIC_DRAW); + glBufferData(GL_ARRAY_BUFFER, totalVboSize, nullptr, GL_STREAM_DRAW); glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount * sizeof(Vertex), gl->vertexData); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gl->ebo); @@ -493,7 +496,7 @@ static void glInit(Renderer* renderer, DataWin* dataWin) { // VBO: sized for max quads int32_t vboSize = MAX_QUADS * VERTICES_PER_QUAD * sizeof(Vertex); glBindBuffer(GL_ARRAY_BUFFER, gl->vbo); - glBufferData(GL_ARRAY_BUFFER, vboSize, nullptr, GL_DYNAMIC_DRAW); + glBufferData(GL_ARRAY_BUFFER, vboSize, nullptr, GL_STREAM_DRAW); int32_t eboSize = MAX_QUADS * INDICES_PER_QUAD * sizeof(uint16_t); uint16_t* indices = (uint16_t*)safeMalloc(eboSize); @@ -515,7 +518,6 @@ static void glInit(Renderer* renderer, DataWin* dataWin) { glEnableVertexAttribArray(1); glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, stride, (void*) offsetof(Vertex, u)); glEnableVertexAttribArray(2); - glBindVertexArray(0); } // Allocate CPU-side vertex buffer @@ -571,7 +573,10 @@ static void glGpuSetShader(Renderer* renderer, int32_t shaderIndex) { flushBatch(gl); GMLShader* gmlShader = &gl->gmlShaders[shaderIndex]; - glUseProgram(gmlShader->shaderId); + if (gl->currentProgram != gmlShader->shaderId) { + glUseProgram(gmlShader->shaderId); + gl->currentProgram = gmlShader->shaderId; + } //Gotta set those built-ins! they ain't gonna set themselves GLShaderUniform* gmMatricesUniform = findShaderUniformByName(gmlShader, "gm_Matrices[0]"); GLShaderUniform* gmFogColourUniform = findShaderUniformByName(gmlShader, "gm_FogColour"); @@ -624,7 +629,10 @@ static void glShaderSettingsRefresh(Renderer* renderer) { if (!gl->state.uniformsDirty && memcmp(&gl->state.last, &cur, sizeof(cur)) == 0) return; - glUseProgram(gl->defaultShaderProgram->shaderId); + if (gl->currentProgram != gl->defaultShaderProgram->shaderId) { + glUseProgram(gl->defaultShaderProgram->shaderId); + gl->currentProgram = gl->defaultShaderProgram->shaderId; + } glUniformMatrix4fv(gl->uWorldViewProjection->location, 1, GL_FALSE, cur.wvp.m); glUniform4f(gl->uFogColor->location, cur.fogColor[0], cur.fogColor[1], cur.fogColor[2], cur.fogColor[3]); @@ -666,7 +674,10 @@ static void glApplyProjection(Renderer* renderer, const Matrix4f* viewMatrix,con static void glGpuResetShader(Renderer* renderer) { GLRenderer* gl = (GLRenderer*) renderer; flushBatch(gl); - glUseProgram(gl->defaultShaderProgram->shaderId); + if (gl->currentProgram != gl->defaultShaderProgram->shaderId) { + glUseProgram(gl->defaultShaderProgram->shaderId); + gl->currentProgram = gl->defaultShaderProgram->shaderId; + } renderer->currentShader = -1; glShaderSettingsRefresh(renderer); } @@ -766,9 +777,7 @@ static void glBeginView(Renderer* renderer, MAYBE_UNUSED int32_t viewX, MAYBE_UN glApplyProjection(renderer,&camera->viewMatrix,&camera->projectionMatrix); glShaderSettingsRefresh(renderer); - glActiveTexture(GL_TEXTURE1); - - if (hasVAO()) glBindVertexArray(gl->vao); + glActiveTextureCached(gl, GL_TEXTURE1); } @@ -826,9 +835,7 @@ static void glBeginGUI(Renderer* renderer, MAYBE_UNUSED int32_t guiW, MAYBE_UNUS glApplyProjection(renderer,&camera->viewMatrix,&camera->projectionMatrix); - glActiveTexture(GL_TEXTURE1); - - if (hasVAO()) glBindVertexArray(gl->vao); + glActiveTextureCached(gl, GL_TEXTURE1); } static void glSetGuiProjection(Renderer* renderer, int32_t guiW, int32_t guiH, MAYBE_UNUSED int32_t portW, MAYBE_UNUSED int32_t portH, MAYBE_UNUSED bool renderingToUserSurface) { @@ -872,7 +879,6 @@ static void glEndGUI(Renderer* renderer) { static void glEndFrameInit(Renderer* renderer) { GLRenderer* gl = (GLRenderer*) renderer; - if (hasVAO()) glBindVertexArray(0); if (renderer->runner->usingAppSurface && !renderer->runner->appSurfaceAutoDraw) { glBindFramebufferCached(gl, GL_FRAMEBUFFER, gl->hostFramebuffer); @@ -2657,10 +2663,9 @@ static void glTextureSetStage(Renderer* renderer, int32_t slot, uint32_t texHand fprintf(stderr, "GL: Texture Stage Higher Than Max\n"); return; } - glActiveTexture(GL_TEXTURE0 + slot); + glActiveTextureCached(gl, GL_TEXTURE0 + slot); glBindTexture(GL_TEXTURE_2D, texID); - glActiveTexture(GL_TEXTURE1); - + glActiveTextureCached(gl, GL_TEXTURE1); } // Look up a texture's pixel size from the renderer's own tables. diff --git a/src/gl/gl_renderer.h b/src/gl/gl_renderer.h index c8525b395..cf7c589d0 100644 --- a/src/gl/gl_renderer.h +++ b/src/gl/gl_renderer.h @@ -51,6 +51,7 @@ typedef struct { int32_t viewport[4]; int32_t scissor[4]; float clearColor[4]; + int32_t activeTexUnit; bool blendEnabled; bool scissorEnabled; @@ -122,6 +123,8 @@ typedef struct { GLShaderUniform* uAlphaTestEnabled; GLShaderUniform* uTexture; + GLuint currentProgram; + GLState state; } GLRenderer; diff --git a/src/gl_legacy/gl_legacy_renderer.c b/src/gl_legacy/gl_legacy_renderer.c index 6ac9e0365..5ef039be1 100644 --- a/src/gl_legacy/gl_legacy_renderer.c +++ b/src/gl_legacy/gl_legacy_renderer.c @@ -15,18 +15,18 @@ extern GLint gPalettedUPaletteVLoc; #define PS3_PALETTED_BEGIN(tpagIndex) do { \ float _v = PS3Textures_getTpagPaletteV(tpagIndex); \ if (0.0f > _v) break; \ - glActiveTexture(GL_TEXTURE1); \ + glActiveTextureCached(gl, GL_TEXTURE1); \ glBindTexture(GL_TEXTURE_2D, PS3Textures_getClutTexture()); \ glSetCap(gl, GL_TEXTURE_2D, true); \ - glActiveTexture(GL_TEXTURE0); \ + glActiveTextureCached(gl, GL_TEXTURE0); \ glUseProgram(gPalettedProgram); \ if (gPalettedUPaletteVLoc >= 0) glUniform1f(gPalettedUPaletteVLoc, _v); \ } while (0) #define PS3_PALETTED_END() do { \ glUseProgram(0); \ - glActiveTexture(GL_TEXTURE1); \ + glActiveTextureCached(gl, GL_TEXTURE1); \ glSetCap(gl, GL_TEXTURE_2D, false); \ - glActiveTexture(GL_TEXTURE0); \ + glActiveTextureCached(gl, GL_TEXTURE0); \ } while (0) #else #include @@ -152,6 +152,12 @@ static inline void glClearColorCached(GLLegacyRenderer* gl, float r, float g, fl glClearColor(r, g, b, a); } +static inline void glActiveTextureCached(GLLegacyRenderer* gl, GLenum unit) { + if (gl->state.activeTexUnit == (int32_t)unit) return; + gl->state.activeTexUnit = (int32_t)unit; + glActiveTexture(unit); +} + // ===[ Helpers ]=== static void glApplyViewport(GLLegacyRenderer* gl, int32_t x, int32_t y, int32_t w, int32_t h) { @@ -341,7 +347,7 @@ static void glBeginView(Renderer* renderer, MAYBE_UNUSED int32_t viewX, MAYBE_UN GMLCamera* camera = Runner_getCameraById(renderer->runner, gl->base.cameraCurrent); glApplyProjection(renderer,&camera->viewMatrix,&camera->projectionMatrix); - glActiveTexture(GL_TEXTURE0); + glActiveTextureCached(gl, GL_TEXTURE0); } @@ -394,7 +400,7 @@ static void glBeginGUI(Renderer* renderer, int32_t guiW, int32_t guiH, int32_t p camera->projectionMatrix = projectionMatrix; glApplyProjection(renderer,&camera->viewMatrix,&camera->projectionMatrix); - glActiveTexture(GL_TEXTURE0); + glActiveTextureCached(gl, GL_TEXTURE0); } static void glSetGuiProjection(MAYBE_UNUSED Renderer* renderer, int32_t guiW, int32_t guiH, int32_t portW, int32_t portH, bool renderingToUserSurface) { @@ -503,7 +509,7 @@ bool GLLegacyRenderer_ensureTextureLoaded(GLLegacyRenderer* gl, uint32_t pageId) gl->textureWidths[pageId] = w; gl->textureHeights[pageId] = h; - glActiveTexture(GL_TEXTURE0); + glActiveTextureCached(gl, GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, gl->glTextures[pageId]); glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, pixels); // Nearest is mandatory for index textures, bilinear would interpolate palette indices into nonsense colors. diff --git a/src/gl_legacy/gl_legacy_renderer.h b/src/gl_legacy/gl_legacy_renderer.h index 741401aa2..fc7fc16d9 100644 --- a/src/gl_legacy/gl_legacy_renderer.h +++ b/src/gl_legacy/gl_legacy_renderer.h @@ -19,6 +19,7 @@ typedef struct { int32_t viewport[4]; int32_t scissor[4]; float clearColor[4]; + int32_t activeTexUnit; bool blendEnabled; bool scissorEnabled; bool depthTestEnabled; From 8e3fc797eee2e22151e23b93f892d00265441c0c Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Mon, 20 Jul 2026 18:08:34 +0100 Subject: [PATCH 09/15] refactor(gl): deduplicate code --- src/gl/gl_renderer.c | 190 +++++++++-------------------- src/gl/gl_renderer.h | 26 +--- src/gl_common/gl_common.c | 123 +++++++++++++++---- src/gl_common/gl_common.h | 48 +++++++- src/gl_legacy/gl_legacy_renderer.c | 166 ++++++++----------------- src/gl_legacy/gl_legacy_renderer.h | 19 +-- 6 files changed, 253 insertions(+), 319 deletions(-) diff --git a/src/gl/gl_renderer.c b/src/gl/gl_renderer.c index e9f79518a..99beaf8b1 100644 --- a/src/gl/gl_renderer.c +++ b/src/gl/gl_renderer.c @@ -1,5 +1,4 @@ #include "gl_renderer.h" -#include "matrix_math.h" #include "text_utils.h" #if defined(__EMSCRIPTEN__) || defined(__ANDROID__) @@ -16,7 +15,6 @@ #include "stb_ds.h" #include "utils.h" #include "image_decoder.h" -#include "gl_common.h" #include "gl_wrappers.h" // ===[ Constants ]=== @@ -73,78 +71,6 @@ static inline uint8_t floatToUnormByte(float v) { return (uint8_t)(v * 255.0f + 0.5f); } -// ===[ State Tracking Wrappers ]=== - -static inline void glBindFramebufferCached(GLRenderer* gl, GLenum target, GLuint fbo) { - switch (target) { - case GL_FRAMEBUFFER: - if (gl->state.currentReadFbo == fbo && gl->state.currentDrawFbo == fbo) return; - gl->state.currentReadFbo = fbo; - gl->state.currentDrawFbo = fbo; - break; - case GL_READ_FRAMEBUFFER: - if (gl->state.currentReadFbo == fbo) return; - gl->state.currentReadFbo = fbo; - break; - case GL_DRAW_FRAMEBUFFER: - if (gl->state.currentDrawFbo == fbo) return; - gl->state.currentDrawFbo = fbo; - break; - } - gl->state.currentFbo = fbo; - glBindFramebuffer(target, fbo); -} - -static inline void glViewportCached(GLRenderer* gl, int32_t x, int32_t y, int32_t w, int32_t h) { - if (gl->state.viewport[0] == x && gl->state.viewport[1] == y && - gl->state.viewport[2] == w && gl->state.viewport[3] == h) { - return; - } - gl->state.viewport[0] = x; gl->state.viewport[1] = y; - gl->state.viewport[2] = w; gl->state.viewport[3] = h; - glViewport(x, y, w, h); -} - -static inline void glScissorCached(GLRenderer* gl, int32_t x, int32_t y, int32_t w, int32_t h) { - if (gl->state.scissor[0] == x && gl->state.scissor[1] == y && - gl->state.scissor[2] == w && gl->state.scissor[3] == h) { - return; - } - gl->state.scissor[0] = x; gl->state.scissor[1] = y; - gl->state.scissor[2] = w; gl->state.scissor[3] = h; - glScissor(x, y, w, h); -} - -static inline void glSetCap(GLRenderer* gl, GLenum cap, bool enable) { - switch (cap) { - case GL_SCISSOR_TEST: - if (gl->state.scissorEnabled == enable) return; - gl->state.scissorEnabled = enable; - break; - case GL_BLEND: - if (gl->state.blendEnabled == enable) return; - gl->state.blendEnabled = enable; - break; - default: - break; - } - enable ? glEnable(cap) : glDisable(cap); -} - -static inline void glClearColorCached(GLRenderer* gl, float r, float g, float b, float a) { - if (gl->state.clearColor[0] == r && gl->state.clearColor[1] == g && - gl->state.clearColor[2] == b && gl->state.clearColor[3] == a) return; - gl->state.clearColor[0] = r; gl->state.clearColor[1] = g; - gl->state.clearColor[2] = b; gl->state.clearColor[3] = a; - glClearColor(r, g, b, a); -} - -static inline void glActiveTextureCached(GLRenderer* gl, GLenum unit) { - if (gl->state.activeTexUnit == (int32_t)unit) return; - gl->state.activeTexUnit = (int32_t)unit; - glActiveTexture(unit); -} - // ===[ Shader Compilation ]=== static GLuint compileShader(GLenum type, const char* source, bool* ok) { @@ -210,10 +136,10 @@ static void flushBatch(GLRenderer* gl) { GLShaderUniform* uniform = findShaderUniformByName(shader, "gm_BaseTexture"); if (uniform != nullptr) - glActiveTextureCached(gl, GL_TEXTURE0 + uniform->samplerSlot); + glActiveTextureCached(&gl->state, GL_TEXTURE0 + uniform->samplerSlot); glBindTexture(GL_TEXTURE_2D, gl->currentTextureId); } else { - glActiveTextureCached(gl, GL_TEXTURE1); + glActiveTextureCached(&gl->state, GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, gl->currentTextureId); } @@ -547,7 +473,7 @@ static void glInit(Renderer* renderer, DataWin* dataWin) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // Enable blending - glSetCap(gl, GL_BLEND, true); + glSetCap(&gl->state, GL_BLEND, true); // Initialise state cache memset(&gl->state, 0, sizeof(gl->state)); @@ -739,8 +665,8 @@ static void glBeginFrame(Renderer* renderer, int32_t gameW, int32_t gameH, int32 // Bind the application_surface int32_t appId = gl->base.runner->applicationSurfaceId; - glBindFramebufferCached(gl, GL_FRAMEBUFFER, gl->surfaces[appId]); - glViewportCached(gl, 0, 0, gameW, gameH); + glBindFramebufferCached(&gl->state, GL_FRAMEBUFFER, gl->surfaces[appId]); + glViewportCached(&gl->state, 0, 0, gameW, gameH); gl->base.CPortX = 0; gl->base.CPortY = 0; gl->base.CPortW = gameW; @@ -757,15 +683,15 @@ static void glBeginView(Renderer* renderer, MAYBE_UNUSED int32_t viewX, MAYBE_UN // FBO uses game resolution, port coordinates are in game space // OpenGL viewport Y is bottom-up, game Y is top-down - glViewportCached(gl, portX, portY, portW, portH); + glViewportCached(&gl->state, portX, portY, portW, portH); gl->base.CPortX = portX; gl->base.CPortY = portY; gl->base.CPortW = portW; gl->base.CPortH = portH; - glSetCap(gl, GL_SCISSOR_TEST, true); - glScissorCached(gl, portX, portY, portW, portH); + glSetCap(&gl->state, GL_SCISSOR_TEST, true); + glScissorCached(&gl->state, portX, portY, portW, portH); int32_t viewCurrent = 0; if (renderer->runner->viewsEnabled) { @@ -777,14 +703,14 @@ static void glBeginView(Renderer* renderer, MAYBE_UNUSED int32_t viewX, MAYBE_UN glApplyProjection(renderer,&camera->viewMatrix,&camera->projectionMatrix); glShaderSettingsRefresh(renderer); - glActiveTextureCached(gl, GL_TEXTURE1); + glActiveTextureCached(&gl->state, GL_TEXTURE1); } static void glEndView(Renderer* renderer) { GLRenderer* gl = (GLRenderer*) renderer; flushBatch(gl); - glSetCap(gl, GL_SCISSOR_TEST, false); + glSetCap(&gl->state, GL_SCISSOR_TEST, false); } static void glBeginGUI(Renderer* renderer, MAYBE_UNUSED int32_t guiW, MAYBE_UNUSED int32_t guiH, int32_t portX, int32_t portY, int32_t portW, MAYBE_UNUSED int32_t portH, int32_t targetSurfaceId) { @@ -794,19 +720,19 @@ static void glBeginGUI(Renderer* renderer, MAYBE_UNUSED int32_t guiW, MAYBE_UNUS gl->currentTextureId = 0; if (targetSurfaceId == RENDER_TARGET_HOST_FRAMEBUFFER) { - glBindFramebufferCached(gl, GL_FRAMEBUFFER, gl->hostFramebuffer); - glViewportCached(gl, 0, 0, portW, portH); - glScissorCached(gl, 0, 0, portW, portH); + glBindFramebufferCached(&gl->state, GL_FRAMEBUFFER, gl->hostFramebuffer); + glViewportCached(&gl->state, 0, 0, portW, portH); + glScissorCached(&gl->state, 0, 0, portW, portH); } else { require(targetSurfaceId >= 0 && (uint32_t) targetSurfaceId < gl->surfaceCount); require(gl->surfaces[targetSurfaceId] != 0); - glBindFramebufferCached(gl, GL_FRAMEBUFFER, gl->surfaces[targetSurfaceId]); + glBindFramebufferCached(&gl->state, GL_FRAMEBUFFER, gl->surfaces[targetSurfaceId]); int32_t glPortY = gl->gameH - portY - portH; - glViewportCached(gl, portX, glPortY, portW, portH); - glScissorCached(gl, portX, glPortY, portW, portH); + glViewportCached(&gl->state, portX, glPortY, portW, portH); + glScissorCached(&gl->state, portX, glPortY, portW, portH); } - glSetCap(gl, GL_SCISSOR_TEST, true); + glSetCap(&gl->state, GL_SCISSOR_TEST, true); //I dunno hopefully this is at least somewhat correct... gl->base.cameraCurrent = GUI_CAMERA; GMLCamera* camera = &renderer->runner->guiCamera; @@ -835,7 +761,7 @@ static void glBeginGUI(Renderer* renderer, MAYBE_UNUSED int32_t guiW, MAYBE_UNUS glApplyProjection(renderer,&camera->viewMatrix,&camera->projectionMatrix); - glActiveTextureCached(gl, GL_TEXTURE1); + glActiveTextureCached(&gl->state, GL_TEXTURE1); } static void glSetGuiProjection(Renderer* renderer, int32_t guiW, int32_t guiH, MAYBE_UNUSED int32_t portW, MAYBE_UNUSED int32_t portH, MAYBE_UNUSED bool renderingToUserSurface) { @@ -874,14 +800,14 @@ static void glSetGuiProjection(Renderer* renderer, int32_t guiW, int32_t guiH, M static void glEndGUI(Renderer* renderer) { GLRenderer* gl = (GLRenderer*) renderer; flushBatch(gl); - glSetCap(gl, GL_SCISSOR_TEST, false); + glSetCap(&gl->state, GL_SCISSOR_TEST, false); } static void glEndFrameInit(Renderer* renderer) { GLRenderer* gl = (GLRenderer*) renderer; if (renderer->runner->usingAppSurface && !renderer->runner->appSurfaceAutoDraw) { - glBindFramebufferCached(gl, GL_FRAMEBUFFER, gl->hostFramebuffer); + glBindFramebufferCached(&gl->state, GL_FRAMEBUFFER, gl->hostFramebuffer); return; } } @@ -898,28 +824,28 @@ static void glEndFrameEnd(Renderer* renderer) { GLuint prevRead = gl->state.currentReadFbo; GLuint prevDraw = gl->state.currentDrawFbo; - glBindFramebufferCached(gl, GL_READ_FRAMEBUFFER, gl->surfaces[appId]); - glBindFramebufferCached(gl, GL_DRAW_FRAMEBUFFER, gl->hostFramebuffer); + glBindFramebufferCached(&gl->state, GL_READ_FRAMEBUFFER, gl->surfaces[appId]); + glBindFramebufferCached(&gl->state, GL_DRAW_FRAMEBUFFER, gl->hostFramebuffer); - glClearColorCached(gl, 0.0f, 0.0f, 0.0f, 1.0f); + glClearColorCached(&gl->state, 0.0f, 0.0f, 0.0f, 1.0f); int32_t sx, sy, ex, ey; GLCommon_computeLetterbox(gl->gameW, gl->gameH, gl->windowW, gl->windowH, &sx, &sy, &ex, &ey); glBlitFramebuffer(0, 0, gl->surfaceWidth[appId], gl->surfaceHeight[appId], sx, ey, ex, sy, GL_COLOR_BUFFER_BIT, GL_NEAREST); - glBindFramebufferCached(gl, GL_READ_FRAMEBUFFER, prevRead); - glBindFramebufferCached(gl, GL_DRAW_FRAMEBUFFER, prevDraw); + glBindFramebufferCached(&gl->state, GL_READ_FRAMEBUFFER, prevRead); + glBindFramebufferCached(&gl->state, GL_DRAW_FRAMEBUFFER, prevDraw); } else { - glBindFramebufferCached(gl, GL_FRAMEBUFFER, gl->hostFramebuffer); - glSetCap(gl, GL_SCISSOR_TEST, !(gl->state.scissorEnabled)); + glBindFramebufferCached(&gl->state, GL_FRAMEBUFFER, gl->hostFramebuffer); + glSetCap(&gl->state, GL_SCISSOR_TEST, !(gl->state.scissorEnabled)); - glClearColorCached(gl, 0.0f, 0.0f, 0.0f, 1.0f); + glClearColorCached(&gl->state, 0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); - glViewportCached(gl, 0, 0, gl->windowW, gl->windowH); + glViewportCached(&gl->state, 0, 0, gl->windowW, gl->windowH); renderer->vtable->setGuiProjection(renderer, gl->windowW, gl->windowH, gl->windowW, gl->windowH, false); - glSetCap(gl, GL_BLEND, false); + glSetCap(&gl->state, GL_BLEND, false); int32_t sx, sy, ex, ey; GLCommon_computeLetterbox(gl->gameW, gl->gameH, gl->windowW, gl->windowH, &sx, &sy, &ex, &ey); @@ -929,8 +855,8 @@ static void glEndFrameEnd(Renderer* renderer) { renderer->vtable->drawSurface(renderer, appId, 0, 0, gl->gameW, gl->gameH, (float)sx, (float)sy, scaleX, scaleY, 0.0f, 0xFFFFFF, 1.0f); flushBatch(gl); - glSetCap(gl, GL_BLEND, false); - glSetCap(gl, GL_SCISSOR_TEST, !(gl->state.scissorEnabled)); + glSetCap(&gl->state, GL_BLEND, false); + glSetCap(&gl->state, GL_SCISSOR_TEST, !(gl->state.scissorEnabled)); } } @@ -948,7 +874,7 @@ static void glClearScreen(Renderer* renderer, uint32_t color, float alpha) { // GML draw_clear ignores the active scissor and clears the whole target. Disable scissor for the clear and restore it after. //No it doesn't? - glClearColorCached(gl, r, g, b, alpha); + glClearColorCached(&gl->state, r, g, b, alpha); glClear(GL_COLOR_BUFFER_BIT); } @@ -2036,14 +1962,14 @@ static int32_t glCreateSurface(Renderer* renderer, int32_t width, int32_t height glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode); - glBindFramebufferCached(gl, GL_FRAMEBUFFER, gl->surfaces[surfaceIndex]); + glBindFramebufferCached(&gl->state, GL_FRAMEBUFFER, gl->surfaces[surfaceIndex]); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gl->surfaceTexture[surfaceIndex], 0); gl->surfaceWidth[surfaceIndex] = width; gl->surfaceHeight[surfaceIndex] = height; fprintf(stderr, "GL: Created surface %u with size (%dx%d)\n", surfaceIndex, width, height); - glBindFramebufferCached(gl, GL_FRAMEBUFFER, (GLuint) prevBinding); + glBindFramebufferCached(&gl->state, GL_FRAMEBUFFER, (GLuint) prevBinding); return (int32_t) surfaceIndex; } @@ -2104,14 +2030,14 @@ static void glSurfaceResize(Renderer* renderer, int32_t surfaceID, int32_t width glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glBindFramebufferCached(gl, GL_FRAMEBUFFER, gl->surfaces[surfaceID]); + glBindFramebufferCached(&gl->state, GL_FRAMEBUFFER, gl->surfaces[surfaceID]); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gl->surfaceTexture[surfaceID], 0); gl->surfaceWidth[surfaceID] = width; gl->surfaceHeight[surfaceID] = height; fprintf(stderr, "GL: Resized Surface %u Size (%dx%d)\n", surfaceID, width, height); - glBindFramebufferCached(gl, GL_FRAMEBUFFER, (GLuint) prevBinding); + glBindFramebufferCached(&gl->state, GL_FRAMEBUFFER, (GLuint) prevBinding); } static bool glSurfaceExists(Renderer* renderer, int32_t surfaceId) { @@ -2123,7 +2049,7 @@ static bool glSurfaceExists(Renderer* renderer, int32_t surfaceId) { static bool glSurfaceGetPixels(Renderer* renderer, int32_t surfaceId, uint8_t* outRGBA) { GLRenderer* gl = (GLRenderer*) renderer; flushBatch(gl); - return GLCommon_surfaceGetPixels(gl->surfaces, gl->surfaceWidth, gl->surfaceHeight, gl->surfaceCount, surfaceId, outRGBA); + return GLCommon_surfaceGetPixels(&gl->state, gl->surfaces, gl->surfaceWidth, gl->surfaceHeight, gl->surfaceCount, surfaceId, outRGBA); } static bool glSetRenderTarget(Renderer* renderer, int32_t surfaceId, bool implicitApplicationSurface) { @@ -2139,11 +2065,11 @@ static bool glSetRenderTarget(Renderer* renderer, int32_t surfaceId, bool implic if (0 > surfaceId || (uint32_t) surfaceId >= gl->surfaceCount) return false; if (gl->surfaces[surfaceId] == 0) return false; - glBindFramebufferCached(gl, GL_FRAMEBUFFER, gl->surfaces[surfaceId]); + glBindFramebufferCached(&gl->state, GL_FRAMEBUFFER, gl->surfaces[surfaceId]); if (surfaceId == renderer->runner->applicationSurfaceId && implicitApplicationSurface) { - glViewportCached(gl, gl->base.CPortX, gl->base.CPortY, gl->base.CPortW, gl->base.CPortH); - glSetCap(gl, GL_SCISSOR_TEST, true); + glViewportCached(&gl->state, gl->base.CPortX, gl->base.CPortY, gl->base.CPortW, gl->base.CPortH); + glSetCap(&gl->state, GL_SCISSOR_TEST, true); glApplyProjection(renderer, &camera->viewMatrix ,&camera->projectionMatrix); @@ -2153,8 +2079,8 @@ static bool glSetRenderTarget(Renderer* renderer, int32_t surfaceId, bool implic if (surfaceId == view->surfaceId) { //the surface belongs to the view we are rending, we use the view's camera. - glViewportCached(gl, 0, 0, gl->surfaceWidth[surfaceId], gl->surfaceHeight[surfaceId]); - glSetCap(gl, GL_SCISSOR_TEST, false); + glViewportCached(&gl->state, 0, 0, gl->surfaceWidth[surfaceId], gl->surfaceHeight[surfaceId]); + glSetCap(&gl->state, GL_SCISSOR_TEST, false); glApplyProjection(renderer, &camera->viewMatrix, &camera->projectionMatrix); return true; } else { @@ -2175,15 +2101,15 @@ static bool glSetRenderTarget(Renderer* renderer, int32_t surfaceId, bool implic camera->viewAngle = 0; Runner_updateCameraViewSimple(camera); - glViewportCached(gl, 0, 0, gl->surfaceWidth[surfaceId], gl->surfaceHeight[surfaceId]); - glSetCap(gl, GL_SCISSOR_TEST, false); + glViewportCached(&gl->state, 0, 0, gl->surfaceWidth[surfaceId], gl->surfaceHeight[surfaceId]); + glSetCap(&gl->state, GL_SCISSOR_TEST, false); glApplyProjection(renderer, &camera->viewMatrix,&camera->projectionMatrix); return true; } - glViewportCached(gl, 0, 0, gl->surfaceWidth[surfaceId], gl->surfaceHeight[surfaceId]); - glSetCap(gl, GL_SCISSOR_TEST, false); + glViewportCached(&gl->state, 0, 0, gl->surfaceWidth[surfaceId], gl->surfaceHeight[surfaceId]); + glSetCap(&gl->state, GL_SCISSOR_TEST, false); return true; } @@ -2196,7 +2122,7 @@ static void glSurfaceCopy(Renderer* renderer, int32_t destSurfaceID, int32_t des if (0 > destSurfaceID || (uint32_t) destSurfaceID >= gl->surfaceCount || gl->surfaces[destSurfaceID] == 0) return; if (gl->isGL3) { - GLCommon_surfaceBlit(gl->surfaces, gl->surfaceWidth, gl->surfaceHeight, gl->surfaceCount, destSurfaceID, destX, destY, srcSurfaceID, srcX, srcY, srcW, srcH, part); + GLCommon_surfaceBlit(&gl->state, gl->surfaces, gl->surfaceWidth, gl->surfaceHeight, gl->surfaceCount, destSurfaceID, destX, destY, srcSurfaceID, srcX, srcY, srcW, srcH, part); } else { GLuint prevBinding = gl->state.currentFbo; Matrix4f prevProj = renderer->gmlMatrices[MATRIX_WORLD_VIEW_PROJECTION]; @@ -2204,9 +2130,9 @@ static void glSurfaceCopy(Renderer* renderer, int32_t destSurfaceID, int32_t des int32_t prevViewport[4]; memcpy(prevViewport, gl->state.viewport, sizeof(prevViewport)); - glBindFramebufferCached(gl, GL_FRAMEBUFFER, gl->surfaces[destSurfaceID]); - glViewportCached(gl, 0, 0, gl->surfaceWidth[destSurfaceID], gl->surfaceHeight[destSurfaceID]); - glSetCap(gl, GL_BLEND, false); + glBindFramebufferCached(&gl->state, GL_FRAMEBUFFER, gl->surfaces[destSurfaceID]); + glViewportCached(&gl->state, 0, 0, gl->surfaceWidth[destSurfaceID], gl->surfaceHeight[destSurfaceID]); + glSetCap(&gl->state, GL_BLEND, false); renderer->vtable->setGuiProjection(renderer, gl->surfaceWidth[destSurfaceID], gl->surfaceHeight[destSurfaceID], gl->surfaceWidth[destSurfaceID], gl->surfaceHeight[destSurfaceID], true); @@ -2218,11 +2144,11 @@ static void glSurfaceCopy(Renderer* renderer, int32_t destSurfaceID, int32_t des renderer->vtable->drawSurface(renderer, srcSurfaceID, sX, sY, sW, sH, (float)destX, (float)destY, 1.0f, 1.0f, 0.0f, 0xFFFFFF, 1.0f); flushBatch(gl); - glBindFramebufferCached(gl, GL_FRAMEBUFFER, (GLuint) prevBinding); + glBindFramebufferCached(&gl->state, GL_FRAMEBUFFER, (GLuint) prevBinding); renderer->gmlMatrices[MATRIX_WORLD_VIEW_PROJECTION] = prevProj; glShaderSettingsRefresh(renderer); - if (prevBlend) glSetCap(gl, GL_BLEND, true); - glViewportCached(gl, prevViewport[0], prevViewport[1], prevViewport[2], prevViewport[3]); + if (prevBlend) glSetCap(&gl->state, GL_BLEND, true); + glViewportCached(&gl->state, prevViewport[0], prevViewport[1], prevViewport[2], prevViewport[3]); } } @@ -2332,7 +2258,7 @@ static int32_t glCreateSpriteFromSurface(Renderer* renderer, int32_t surfaceID, // Flush any pending draws before reading pixels flushBatch(gl); - glBindFramebufferCached(gl, GL_FRAMEBUFFER, gl->surfaces[surfaceID]); + glBindFramebufferCached(&gl->state, GL_FRAMEBUFFER, gl->surfaces[surfaceID]); uint8_t* pixels = (uint8_t *)safeMalloc((size_t) w * (size_t) h * 4); if (pixels == nullptr) return -1; @@ -2479,7 +2405,7 @@ static void glGpuSetBlendModeExt(Renderer* renderer, int32_t sfactor, int32_t df static void glGpuSetBlendEnable(Renderer* renderer, bool enable) { GLRenderer* gl = (GLRenderer*) renderer; flushBatch(gl); - glSetCap(gl, GL_BLEND, enable); + glSetCap(&gl->state, GL_BLEND, enable); } static bool glGpuGetBlendEnable(Renderer* renderer) { @@ -2663,9 +2589,9 @@ static void glTextureSetStage(Renderer* renderer, int32_t slot, uint32_t texHand fprintf(stderr, "GL: Texture Stage Higher Than Max\n"); return; } - glActiveTextureCached(gl, GL_TEXTURE0 + slot); + glActiveTextureCached(&gl->state, GL_TEXTURE0 + slot); glBindTexture(GL_TEXTURE_2D, texID); - glActiveTextureCached(gl, GL_TEXTURE1); + glActiveTextureCached(&gl->state, GL_TEXTURE1); } // Look up a texture's pixel size from the renderer's own tables. diff --git a/src/gl/gl_renderer.h b/src/gl/gl_renderer.h index cf7c589d0..39a646fff 100644 --- a/src/gl/gl_renderer.h +++ b/src/gl/gl_renderer.h @@ -1,6 +1,8 @@ #ifndef _BS_GL_RENDERER_H_ #define _BS_GL_RENDERER_H_ +#include "gl_common.h" + #include "common.h" #include "renderer.h" #include "runner.h" @@ -36,30 +38,6 @@ typedef struct { uint8_t r, g, b, a; } Vertex; -typedef struct { - Matrix4f wvp; - float fogColor[4]; // RGB + A (A=0 disabled, A=1 enabled) - float alphaTestRef; - bool alphaTestEnabled; -} DefaultShaderUniforms; - -typedef struct { - // Cached GL state - GLuint currentFbo; - GLuint currentReadFbo; - GLuint currentDrawFbo; - int32_t viewport[4]; - int32_t scissor[4]; - float clearColor[4]; - int32_t activeTexUnit; - bool blendEnabled; - bool scissorEnabled; - - // Default shader uniform tracking - bool uniformsDirty; - DefaultShaderUniforms last; -} GLState; - // Exposed in the header so platform-specific code (main.c) can access FBO fields for screenshots. typedef struct { Renderer base; // Must be first field for struct embedding diff --git a/src/gl_common/gl_common.c b/src/gl_common/gl_common.c index 4819b35bd..999613a16 100644 --- a/src/gl_common/gl_common.c +++ b/src/gl_common/gl_common.c @@ -8,6 +8,84 @@ #include "utils.h" #include "renderer.h" // for bm_* constants +// ===[ State Tracking Wrappers ]=== + +void glBindFramebufferCached(GLState* state, GLenum target, GLuint fbo) { + switch (target) { + case GL_FRAMEBUFFER: + if (state->currentReadFbo == fbo && state->currentDrawFbo == fbo) return; + state->currentReadFbo = fbo; + state->currentDrawFbo = fbo; + break; + case GL_READ_FRAMEBUFFER: + if (state->currentReadFbo == fbo) return; + state->currentReadFbo = fbo; + break; + case GL_DRAW_FRAMEBUFFER: + if (state->currentDrawFbo == fbo) return; + state->currentDrawFbo = fbo; + break; + } + state->currentFbo = fbo; + glBindFramebuffer(target, fbo); +} + +void glViewportCached(GLState* state, int32_t x, int32_t y, int32_t w, int32_t h) { + if (state->viewport[0] == x && state->viewport[1] == y && + state->viewport[2] == w && state->viewport[3] == h) { + return; + } + state->viewport[0] = x; state->viewport[1] = y; + state->viewport[2] = w; state->viewport[3] = h; + glViewport(x, y, w, h); +} + +void glScissorCached(GLState* state, int32_t x, int32_t y, int32_t w, int32_t h) { + if (state->scissor[0] == x && state->scissor[1] == y && + state->scissor[2] == w && state->scissor[3] == h) { + return; + } + state->scissor[0] = x; state->scissor[1] = y; + state->scissor[2] = w; state->scissor[3] = h; + glScissor(x, y, w, h); +} + +void glSetCap(GLState* state, GLenum cap, bool enable) { + switch (cap) { + case GL_SCISSOR_TEST: + if (state->scissorEnabled == enable) return; + state->scissorEnabled = enable; + break; + case GL_BLEND: + if (state->blendEnabled == enable) return; + state->blendEnabled = enable; + break; + default: + break; + } + enable ? glEnable(cap) : glDisable(cap); +} + +void glClearColorCached(GLState* state, float r, float g, float b, float a) { + if (state->clearColor[0] == r && state->clearColor[1] == g && + state->clearColor[2] == b && state->clearColor[3] == a) return; + state->clearColor[0] = r; state->clearColor[1] = g; + state->clearColor[2] = b; state->clearColor[3] = a; + glClearColor(r, g, b, a); +} + +void glActiveTextureCached(GLState* state, GLenum unit) { + if (state->activeTexUnit == (int32_t)unit) return; + state->activeTexUnit = (int32_t)unit; + glActiveTexture(unit); +} + +void glPixelStoreiCached(GLState* state, GLenum pname, GLint param) { + if (state->currentPixelStorei == param) return; + state->currentPixelStorei = param; + glPixelStorei(pname, param); +} + // ===[ Letterbox blit ]=== void GLCommon_computeLetterbox(int32_t gameW, int32_t gameH, int32_t windowW, int32_t windowH, int32_t* outStartX, int32_t* outStartY, int32_t* outEndX, int32_t* outEndY) { @@ -27,17 +105,17 @@ void GLCommon_computeLetterbox(int32_t gameW, int32_t gameH, int32_t windowW, in *outEndY = startY + effH; } -void GLCommon_beginLetterboxBlit(GLuint fbo, GLuint hostFbo) { - glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, hostFbo); +void GLCommon_beginLetterboxBlit(GLState* state, GLuint fbo, GLuint hostFbo) { + glBindFramebufferCached(state, GL_READ_FRAMEBUFFER, fbo); + glBindFramebufferCached(state, GL_DRAW_FRAMEBUFFER, hostFbo); } -void GLCommon_endLetterboxBlit(int32_t fboWidth, int32_t fboHeight, int32_t gameW, int32_t gameH, int32_t windowW, int32_t windowH, GLuint hostFbo) { +void GLCommon_endLetterboxBlit(GLState* state, int32_t fboWidth, int32_t fboHeight, int32_t gameW, int32_t gameH, int32_t windowW, int32_t windowH, GLuint hostFbo) { int32_t sx, sy, ex, ey; glClearColor(0.0, 0.0, 0.0, 1.0); //please remove if it breaks something like borders, it was just my quick-fix for the color to not be randomly changed GLCommon_computeLetterbox(gameW, gameH, windowW, windowH, &sx, &sy, &ex, &ey); glBlitFramebuffer(0, 0, fboWidth, fboHeight, sx, ey, ex, sy, GL_COLOR_BUFFER_BIT, GL_NEAREST); - glBindFramebuffer(GL_FRAMEBUFFER, hostFbo); + glBindFramebufferCached(state, GL_DRAW_FRAMEBUFFER, hostFbo); } // ===[ Surface arrays ]=== @@ -71,7 +149,7 @@ static bool resolveSurfaceFBO(GLuint* surfaces, int32_t* surfaceWidth, int32_t* return true; } -void GLCommon_surfaceBlit(GLuint* surfaces, int32_t* surfaceWidth, int32_t* surfaceHeight, uint32_t count, int32_t dstId, int32_t dstX, int32_t dstY, int32_t srcId, int32_t srcX, int32_t srcY, int32_t srcW, int32_t srcH, bool part) { +void GLCommon_surfaceBlit(GLState* state, GLuint* surfaces, int32_t* surfaceWidth, int32_t* surfaceHeight, uint32_t count, int32_t dstId, int32_t dstX, int32_t dstY, int32_t srcId, int32_t srcX, int32_t srcY, int32_t srcW, int32_t srcH, bool part) { GLuint srcFbo, dstFbo; int32_t srcFboW, srcFboH; MAYBE_UNUSED int32_t dstFboW, dstFboH; @@ -82,16 +160,13 @@ void GLCommon_surfaceBlit(GLuint* surfaces, int32_t* surfaceWidth, int32_t* surf if (!resolveSurfaceFBO(surfaces, surfaceWidth, surfaceHeight, count, dstId, &dstFbo, &dstFboW, &dstFboH)) return; - int originalFramebufferBinding; - - // Yes, in OpenGL you need to use _BINDING to query things - glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &originalFramebufferBinding); + int originalFramebufferBinding = state->currentDrawFbo; + bool scissorWasEnabled = state->scissorEnabled; - GLboolean scissorWasEnabled = glIsEnabled(GL_SCISSOR_TEST); - if (scissorWasEnabled) glDisable(GL_SCISSOR_TEST); + glSetCap(state, GL_SCISSOR_TEST, false); - glBindFramebuffer(GL_READ_FRAMEBUFFER, srcFbo); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dstFbo); + glBindFramebufferCached(state, GL_READ_FRAMEBUFFER, srcFbo); + glBindFramebufferCached(state, GL_DRAW_FRAMEBUFFER, dstFbo); if (part) { // GL Y is bottom-up; convert GML's top-down (srcX, srcY) source rect. @@ -101,11 +176,11 @@ void GLCommon_surfaceBlit(GLuint* surfaces, int32_t* surfaceWidth, int32_t* surf glBlitFramebuffer(0, 0, srcFboW, srcFboH, dstX, dstY, dstX + srcFboW, dstY + srcFboH, GL_COLOR_BUFFER_BIT, GL_NEAREST); } - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, originalFramebufferBinding); - if (scissorWasEnabled) glEnable(GL_SCISSOR_TEST); + glBindFramebufferCached(state, GL_DRAW_FRAMEBUFFER, originalFramebufferBinding); + glSetCap(state, GL_SCISSOR_TEST, scissorWasEnabled); } -bool GLCommon_surfaceGetPixels(GLuint* surfaces, int32_t* surfaceWidth, int32_t* surfaceHeight, uint32_t count, int32_t surfaceId, uint8_t* outRGBA) { +bool GLCommon_surfaceGetPixels(GLState* state, GLuint* surfaces, int32_t* surfaceWidth, int32_t* surfaceHeight, uint32_t count, int32_t surfaceId, uint8_t* outRGBA) { if (0 > surfaceId || (uint32_t) surfaceId >= count) return false; @@ -117,13 +192,11 @@ bool GLCommon_surfaceGetPixels(GLuint* surfaces, int32_t* surfaceWidth, int32_t* if (0 >= w || 0 >= h) return false; - GLint prevFbo = 0; - glGetIntegerv(GL_FRAMEBUFFER_BINDING, &prevFbo); - GLint prevPackAlign = 4; - glGetIntegerv(GL_PACK_ALIGNMENT, &prevPackAlign); + GLint prevFbo = state->currentFbo; + GLint prevPackAlign = state->currentPixelStorei; - glBindFramebuffer(GL_FRAMEBUFFER, surfaces[surfaceId]); - glPixelStorei(GL_PACK_ALIGNMENT, 1); + glBindFramebufferCached(state, GL_FRAMEBUFFER, surfaces[surfaceId]); + glPixelStoreiCached(state, GL_PACK_ALIGNMENT, 1); uint8_t* tmp = (uint8_t *)safeMalloc((size_t) w * (size_t) h * 4); glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, tmp); @@ -135,8 +208,8 @@ bool GLCommon_surfaceGetPixels(GLuint* surfaces, int32_t* surfaceWidth, int32_t* } free(tmp); - glPixelStorei(GL_PACK_ALIGNMENT, prevPackAlign); - glBindFramebuffer(GL_FRAMEBUFFER, (GLuint) prevFbo); + glPixelStoreiCached(state, GL_PACK_ALIGNMENT, prevPackAlign); + glBindFramebufferCached(state, GL_FRAMEBUFFER, (GLuint) prevFbo); return true; } diff --git a/src/gl_common/gl_common.h b/src/gl_common/gl_common.h index 5a66b86f9..dd255419a 100644 --- a/src/gl_common/gl_common.h +++ b/src/gl_common/gl_common.h @@ -2,6 +2,7 @@ #define _BS_GL_COMMON_H_ #include "common.h" +#include "matrix_math.h" #include #if defined(__EMSCRIPTEN__) || defined(__ANDROID__) @@ -13,6 +14,45 @@ #include #endif +// ===[ GL State ]=== + +typedef struct { + Matrix4f wvp; + float fogColor[4]; // RGB + A (A=0 disabled, A=1 enabled) + float alphaTestRef; + bool alphaTestEnabled; +} DefaultShaderUniforms; + +typedef struct { + // Cached GL state + GLuint currentFbo; + GLuint currentReadFbo; + GLuint currentDrawFbo; + GLint currentPixelStorei; + + int32_t viewport[4]; + int32_t scissor[4]; + float clearColor[4]; + int32_t activeTexUnit; + bool blendEnabled; + bool scissorEnabled; + bool depthTestEnabled; + bool texture2DEnabled; + + // Default shader uniform tracking (modern-gl only) + bool uniformsDirty; + DefaultShaderUniforms last; +} GLState; + +// ===[ GL state tracking wrappers ]=== +void glBindFramebufferCached(GLState* state, GLenum target, GLuint fbo); +void glViewportCached(GLState* state, int32_t x, int32_t y, int32_t w, int32_t h); +void glScissorCached(GLState* state, int32_t x, int32_t y, int32_t w, int32_t h); +void glSetCap(GLState* state, GLenum cap, bool enabled); +void glClearColorCached(GLState* state, float r, float g, float b, float a); +void glActiveTextureCached(GLState* state, GLenum unit); +void glPixelStoreiCached(GLState* state, GLenum pname, int32_t param); + // ===[ Letterbox blit ]=== // Computes the letterboxed destination rect for a gameW x gameH frame inside a windowW x windowH window. @@ -20,8 +60,8 @@ void GLCommon_computeLetterbox(int32_t gameW, int32_t gameH, int32_t windowW, int32_t windowH, int32_t* outStartX, int32_t* outStartY, int32_t* outEndX, int32_t* outEndY); // Blits the given FBO (typically the application_surface) into hostFbo with letterboxing (hostFbo 0 == the window). -void GLCommon_beginLetterboxBlit(GLuint fbo, GLuint hostFbo); -void GLCommon_endLetterboxBlit(int32_t fboWidth, int32_t fboHeight, int32_t gameW, int32_t gameH, int32_t windowW, int32_t windowH, GLuint hostFbo); +void GLCommon_beginLetterboxBlit(GLState* state, GLuint fbo, GLuint hostFbo); +void GLCommon_endLetterboxBlit(GLState* state, int32_t fboWidth, int32_t fboHeight, int32_t gameW, int32_t gameH, int32_t windowW, int32_t windowH, GLuint hostFbo); // ===[ Surface arrays ]=== @@ -37,12 +77,12 @@ uint32_t GLCommon_findOrAllocateSurfaceSlot(GLuint** surfaces, GLuint** surfaceT // If part == false, ignores src{X,Y,W,H} and copies the whole source to a matching-size box at (dstX, dstY) on the destination. // If part == true, copies a src{W,H}-sized region starting at (srcX, srcY) on the source (top-down GML coords) to (dstX, dstY) on the destination. // Silently returns if either id is invalid. -void GLCommon_surfaceBlit(GLuint* surfaces, int32_t* surfaceWidth, int32_t* surfaceHeight, uint32_t count, int32_t dstId, int32_t dstX, int32_t dstY, int32_t srcId, int32_t srcX, int32_t srcY, int32_t srcW, int32_t srcH, bool part); +void GLCommon_surfaceBlit(GLState* state, GLuint* surfaces, int32_t* surfaceWidth, int32_t* surfaceHeight, uint32_t count, int32_t dstId, int32_t dstX, int32_t dstY, int32_t srcId, int32_t srcX, int32_t srcY, int32_t srcW, int32_t srcH, bool part); // Reads a surface's pixels into a top-down RGBA8 buffer of size width*height*4. // Returns false on invalid surfaceId. // Saves/restores GL_FRAMEBUFFER_BINDING and GL_PACK_ALIGNMENT around the read. -bool GLCommon_surfaceGetPixels(GLuint* surfaces, int32_t* surfaceWidth, int32_t* surfaceHeight, uint32_t count, int32_t surfaceId, uint8_t* outRGBA); +bool GLCommon_surfaceGetPixels(GLState* state, GLuint* surfaces, int32_t* surfaceWidth, int32_t* surfaceHeight, uint32_t count, int32_t surfaceId, uint8_t* outRGBA); // ===[ Blend mode translation ]=== diff --git a/src/gl_legacy/gl_legacy_renderer.c b/src/gl_legacy/gl_legacy_renderer.c index 5ef039be1..6e292440b 100644 --- a/src/gl_legacy/gl_legacy_renderer.c +++ b/src/gl_legacy/gl_legacy_renderer.c @@ -1,5 +1,4 @@ #include "gl_legacy_renderer.h" -#include "matrix_math.h" #include "text_utils.h" #include "gl_wrappers.h" @@ -15,18 +14,18 @@ extern GLint gPalettedUPaletteVLoc; #define PS3_PALETTED_BEGIN(tpagIndex) do { \ float _v = PS3Textures_getTpagPaletteV(tpagIndex); \ if (0.0f > _v) break; \ - glActiveTextureCached(gl, GL_TEXTURE1); \ + glActiveTextureCached(&gl->state, GL_TEXTURE1); \ glBindTexture(GL_TEXTURE_2D, PS3Textures_getClutTexture()); \ - glSetCap(gl, GL_TEXTURE_2D, true); \ - glActiveTextureCached(gl, GL_TEXTURE0); \ + glSetCap(&gl->state, GL_TEXTURE_2D, true); \ + glActiveTextureCached(&gl->state, GL_TEXTURE0); \ glUseProgram(gPalettedProgram); \ if (gPalettedUPaletteVLoc >= 0) glUniform1f(gPalettedUPaletteVLoc, _v); \ } while (0) #define PS3_PALETTED_END() do { \ glUseProgram(0); \ - glActiveTextureCached(gl, GL_TEXTURE1); \ - glSetCap(gl, GL_TEXTURE_2D, false); \ - glActiveTextureCached(gl, GL_TEXTURE0); \ + glActiveTextureCached(&gl->state, GL_TEXTURE1); \ + glSetCap(&gl->state, GL_TEXTURE_2D, false); \ + glActiveTextureCached(&gl->state, GL_TEXTURE0); \ } while (0) #else #include @@ -50,7 +49,6 @@ static inline int32_t nextPow2(int32_t v) { #include "stb_ds.h" #include "utils.h" #include "image_decoder.h" -#include "gl_common.h" #include "gl_wrappers.h" // ===[ Runtime OpenGL extension checks ]=== @@ -90,80 +88,12 @@ static bool hasFBO() { #endif } -// ===[ State Tracking Wrappers ]=== - -static inline void glBindFramebufferCached(GLLegacyRenderer* gl, GLenum target, GLuint fbo) { - if (gl->state.currentFbo == fbo) return; - gl->state.currentFbo = fbo; - glBindFramebuffer(target, fbo); -} - -static inline void glViewportCached(GLLegacyRenderer* gl, int32_t x, int32_t y, int32_t w, int32_t h) { - if (gl->state.viewport[0] == x && gl->state.viewport[1] == y && - gl->state.viewport[2] == w && gl->state.viewport[3] == h) return; - - gl->state.viewport[0] = x; gl->state.viewport[1] = y; - gl->state.viewport[2] = w; gl->state.viewport[3] = h; - glViewport(x, y, w, h); -} - -static inline void glScissorCached(GLLegacyRenderer* gl, int32_t x, int32_t y, int32_t w, int32_t h) { - if (gl->state.scissor[0] == x && gl->state.scissor[1] == y && - gl->state.scissor[2] == w && gl->state.scissor[3] == h) return; - - gl->state.scissor[0] = x; gl->state.scissor[1] = y; - gl->state.scissor[2] = w; gl->state.scissor[3] = h; - glScissor(x, y, w, h); -} - - -static inline void glSetCap(GLLegacyRenderer* gl, GLenum cap, bool enable) { - switch (cap) { - case GL_BLEND: { - if (gl->state.blendEnabled == enable) return; - gl->state.blendEnabled = enable; - break; - } - case GL_SCISSOR_TEST: { - if (gl->state.scissorEnabled == enable) return; - gl->state.scissorEnabled = enable; - break; - } - case GL_DEPTH_TEST: { - if (gl->state.depthTestEnabled == enable) return; - gl->state.depthTestEnabled = enable; - break; - } - case GL_TEXTURE_2D: { - if (gl->state.texture2DEnabled == enable) return; - gl->state.texture2DEnabled = enable; - break; - } - } - if (enable) glEnable(cap); - else glDisable(cap); -} - -static inline void glClearColorCached(GLLegacyRenderer* gl, float r, float g, float b, float a) { - if (gl->state.clearColor[0] == r && gl->state.clearColor[1] == g && - gl->state.clearColor[2] == b && gl->state.clearColor[3] == a) return; - gl->state.clearColor[0] = r; gl->state.clearColor[1] = g; - gl->state.clearColor[2] = b; gl->state.clearColor[3] = a; - glClearColor(r, g, b, a); -} - -static inline void glActiveTextureCached(GLLegacyRenderer* gl, GLenum unit) { - if (gl->state.activeTexUnit == (int32_t)unit) return; - gl->state.activeTexUnit = (int32_t)unit; - glActiveTexture(unit); -} - // ===[ Helpers ]=== static void glApplyViewport(GLLegacyRenderer* gl, int32_t x, int32_t y, int32_t w, int32_t h) { - glViewportCached(gl, x, y, w, h); - glSetCap(gl, GL_SCISSOR_TEST, true); - glScissorCached(gl, x, y, w, h); + glViewportCached(&gl->state, x, y, w, h); + glSetCap(&gl->state, GL_SCISSOR_TEST, true); + glScissorCached(&gl->state, x, y, w, h); gl->base.CPortX = x; gl->base.CPortY = y; @@ -229,8 +159,8 @@ static void glInit(Renderer* renderer, DataWin* dataWin) { } // Prepare texture slots for lazy loading (PNG decode deferred to first use) - glSetCap(gl, GL_TEXTURE_2D, true); - glSetCap(gl, GL_DEPTH_TEST, false); + glSetCap(&gl->state, GL_TEXTURE_2D, true); + glSetCap(&gl->state, GL_DEPTH_TEST, false); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); #ifdef PLATFORM_PS3 @@ -261,7 +191,7 @@ static void glInit(Renderer* renderer, DataWin* dataWin) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // Enable blending - glSetCap(gl, GL_BLEND, true); + glSetCap(&gl->state, GL_BLEND, true); memset(&gl->state, 0, sizeof(gl->state)); gl->state.currentFbo = 0; @@ -319,8 +249,8 @@ static void glBeginFrame(Renderer* renderer, int32_t gameW, int32_t gameH, int32 // Bind the application_surface (sized/created by Runner_beginFrame's ensureApplicationSurface call right before this). int32_t appId = gl->base.runner->applicationSurfaceId; - glBindFramebufferCached(gl, GL_FRAMEBUFFER, gl->surfaces[appId]); - glViewportCached(gl, 0, 0, gameW, gameH); + glBindFramebufferCached(&gl->state, GL_FRAMEBUFFER, gl->surfaces[appId]); + glViewportCached(&gl->state, 0, 0, gameW, gameH); gl->base.CPortX = 0; gl->base.CPortY = 0; gl->base.CPortW = gameW; @@ -347,13 +277,13 @@ static void glBeginView(Renderer* renderer, MAYBE_UNUSED int32_t viewX, MAYBE_UN GMLCamera* camera = Runner_getCameraById(renderer->runner, gl->base.cameraCurrent); glApplyProjection(renderer,&camera->viewMatrix,&camera->projectionMatrix); - glActiveTextureCached(gl, GL_TEXTURE0); + glActiveTextureCached(&gl->state, GL_TEXTURE0); } static void glEndView(MAYBE_UNUSED Renderer* renderer) { GLLegacyRenderer* gl = (GLLegacyRenderer*) renderer; - glSetCap(gl, GL_SCISSOR_TEST, false); + glSetCap(&gl->state, GL_SCISSOR_TEST, false); } static void glBeginGUI(Renderer* renderer, int32_t guiW, int32_t guiH, int32_t portX, int32_t portY, int32_t portW, int32_t portH, int32_t targetSurfaceId) { @@ -362,14 +292,14 @@ static void glBeginGUI(Renderer* renderer, int32_t guiW, int32_t guiH, int32_t p glBindTexture(GL_TEXTURE_2D, 0); if (targetSurfaceId == RENDER_TARGET_HOST_FRAMEBUFFER) { - glBindFramebufferCached(gl, GL_FRAMEBUFFER, 0); - glViewportCached(gl, 0, 0, portW, portH); - glSetCap(gl, GL_SCISSOR_TEST, true); - glScissorCached(gl, 0, 0, portW, portH); + glBindFramebufferCached(&gl->state, GL_FRAMEBUFFER, 0); + glViewportCached(&gl->state, 0, 0, portW, portH); + glSetCap(&gl->state, GL_SCISSOR_TEST, true); + glScissorCached(&gl->state, 0, 0, portW, portH); } else { require(targetSurfaceId >= 0 && (uint32_t) targetSurfaceId < gl->surfaceCount); require(gl->surfaces[targetSurfaceId] != 0); - glBindFramebufferCached(gl, GL_FRAMEBUFFER, gl->surfaces[targetSurfaceId]); + glBindFramebufferCached(&gl->state, GL_FRAMEBUFFER, gl->surfaces[targetSurfaceId]); glApplyViewport(gl, portX, portY, portW, portH); } @@ -400,7 +330,7 @@ static void glBeginGUI(Renderer* renderer, int32_t guiW, int32_t guiH, int32_t p camera->projectionMatrix = projectionMatrix; glApplyProjection(renderer,&camera->viewMatrix,&camera->projectionMatrix); - glActiveTextureCached(gl, GL_TEXTURE0); + glActiveTextureCached(&gl->state, GL_TEXTURE0); } static void glSetGuiProjection(MAYBE_UNUSED Renderer* renderer, int32_t guiW, int32_t guiH, int32_t portW, int32_t portH, bool renderingToUserSurface) { @@ -439,18 +369,18 @@ static void glSetGuiProjection(MAYBE_UNUSED Renderer* renderer, int32_t guiW, in static void glEndGUI(MAYBE_UNUSED Renderer* renderer) { GLLegacyRenderer* gl = (GLLegacyRenderer*) renderer; - glSetCap(gl, GL_SCISSOR_TEST, false); + glSetCap(&gl->state, GL_SCISSOR_TEST, false); } static void glEndFrameInit(Renderer* renderer) { GLLegacyRenderer* gl = (GLLegacyRenderer*) renderer; if (renderer->runner->usingAppSurface && !renderer->runner->appSurfaceAutoDraw) { - glBindFramebufferCached(gl, GL_FRAMEBUFFER, 0); + glBindFramebufferCached(&gl->state, GL_FRAMEBUFFER, 0); return; } int32_t appId = gl->base.runner->applicationSurfaceId; - glBindFramebufferCached(gl, GL_READ_FRAMEBUFFER, gl->surfaces[appId]); - glBindFramebufferCached(gl, GL_DRAW_FRAMEBUFFER, 0); + glBindFramebufferCached(&gl->state, GL_READ_FRAMEBUFFER, gl->surfaces[appId]); + glBindFramebufferCached(&gl->state, GL_DRAW_FRAMEBUFFER, 0); } static void glEndFrameEnd(Renderer* renderer) { @@ -463,17 +393,17 @@ static void glEndFrameEnd(Renderer* renderer) { GLuint prevRead = gl->state.currentReadFbo; GLuint prevDraw = gl->state.currentDrawFbo; - glBindFramebufferCached(gl, GL_READ_FRAMEBUFFER, gl->surfaces[appId]); - glBindFramebufferCached(gl, GL_DRAW_FRAMEBUFFER, 0); + glBindFramebufferCached(&gl->state, GL_READ_FRAMEBUFFER, gl->surfaces[appId]); + glBindFramebufferCached(&gl->state, GL_DRAW_FRAMEBUFFER, 0); - glClearColorCached(gl, 0.0f, 0.0f, 0.0f, 1.0f); + glClearColorCached(&gl->state, 0.0f, 0.0f, 0.0f, 1.0f); int32_t sx, sy, ex, ey; GLCommon_computeLetterbox(gl->gameW, gl->gameH, gl->windowW, gl->windowH, &sx, &sy, &ex, &ey); glBlitFramebuffer(0, 0, gl->surfaceWidth[appId], gl->surfaceHeight[appId], sx, ey, ex, sy, GL_COLOR_BUFFER_BIT, GL_NEAREST); - glBindFramebufferCached(gl, GL_READ_FRAMEBUFFER, prevRead); - glBindFramebufferCached(gl, GL_DRAW_FRAMEBUFFER, prevDraw); + glBindFramebufferCached(&gl->state, GL_READ_FRAMEBUFFER, prevRead); + glBindFramebufferCached(&gl->state, GL_DRAW_FRAMEBUFFER, prevDraw); } static void glRendererFlush(MAYBE_UNUSED Renderer* renderer) {} @@ -486,7 +416,7 @@ static void glClearScreen(MAYBE_UNUSED Renderer* renderer, uint32_t color, float // GML draw_clear ignores the active scissor and clears the whole target. Disable scissor for the clear and restore it after. GLLegacyRenderer* gl = (GLLegacyRenderer*) renderer; - glClearColorCached(gl, r, g, b, alpha); + glClearColorCached(&gl->state, r, g, b, alpha); glClear(GL_COLOR_BUFFER_BIT); } @@ -509,7 +439,7 @@ bool GLLegacyRenderer_ensureTextureLoaded(GLLegacyRenderer* gl, uint32_t pageId) gl->textureWidths[pageId] = w; gl->textureHeights[pageId] = h; - glActiveTextureCached(gl, GL_TEXTURE0); + glActiveTextureCached(&gl->state, GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, gl->glTextures[pageId]); glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, pixels); // Nearest is mandatory for index textures, bilinear would interpolate palette indices into nonsense colors. @@ -1604,7 +1534,7 @@ static void glGpuSetBlendModeExt(Renderer* renderer, int32_t sfactor, int32_t df static void glGpuSetBlendEnable(Renderer* renderer, bool enable) { GLLegacyRenderer* gl = (GLLegacyRenderer*) renderer; - glSetCap(gl, GL_BLEND, enable); + glSetCap(&gl->state, GL_BLEND, enable); } static bool glGpuGetBlendEnable(Renderer* renderer) { @@ -1614,7 +1544,7 @@ static bool glGpuGetBlendEnable(Renderer* renderer) { static void glGpuSetAlphaTestEnable(Renderer* renderer, bool enable) { GLLegacyRenderer* gl = (GLLegacyRenderer*) renderer; - glSetCap(gl, GL_ALPHA_TEST, enable); + glSetCap(&gl->state, GL_ALPHA_TEST, enable); } static void glGpuSetAlphaTestRef(MAYBE_UNUSED Renderer* renderer, uint8_t ref) { @@ -1660,7 +1590,7 @@ static int32_t glLegacyCreateSurface(Renderer* renderer, int32_t width, int32_t glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - glBindFramebufferCached(gl, GL_FRAMEBUFFER, gl->surfaces[surfaceIndex]); + glBindFramebufferCached(&gl->state, GL_FRAMEBUFFER, gl->surfaces[surfaceIndex]); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gl->surfaceTexture[surfaceIndex], 0); GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); @@ -1672,7 +1602,7 @@ static int32_t glLegacyCreateSurface(Renderer* renderer, int32_t width, int32_t gl->surfaceHeight[surfaceIndex] = height; fprintf(stderr, "GL: Created surface %u with size (%dx%d)\n", surfaceIndex, width, height); - glBindFramebufferCached(gl, GL_FRAMEBUFFER, (GLuint) prevBinding); + glBindFramebufferCached(&gl->state, GL_FRAMEBUFFER, (GLuint) prevBinding); return (int32_t) surfaceIndex; } @@ -1735,9 +1665,9 @@ static void glLegacySurfaceResize(Renderer* renderer, int32_t surfaceId, int32_t glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - glBindFramebufferCached(gl, GL_FRAMEBUFFER, gl->surfaces[surfaceId]); + glBindFramebufferCached(&gl->state, GL_FRAMEBUFFER, gl->surfaces[surfaceId]); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gl->surfaceTexture[surfaceId], 0); - glBindFramebufferCached(gl, GL_FRAMEBUFFER, (GLuint) prevBinding); + glBindFramebufferCached(&gl->state, GL_FRAMEBUFFER, (GLuint) prevBinding); gl->surfaceWidth[surfaceId] = width; gl->surfaceHeight[surfaceId] = height; @@ -1770,19 +1700,19 @@ static bool glLegacySetRenderTarget(Renderer* renderer, int32_t surfaceId, bool if (0 > surfaceId || (uint32_t) surfaceId >= gl->surfaceCount) return false; if (gl->surfaces[surfaceId] == 0) return false; - glBindFramebufferCached(gl, GL_FRAMEBUFFER, gl->surfaces[surfaceId]); + glBindFramebufferCached(&gl->state, GL_FRAMEBUFFER, gl->surfaces[surfaceId]); if (surfaceId == renderer->runner->applicationSurfaceId && implicitApplicationSurface) { - glViewportCached(gl, gl->base.CPortX, gl->base.CPortY, gl->base.CPortW, gl->base.CPortH); - glSetCap(gl, GL_SCISSOR_TEST, true); + glViewportCached(&gl->state, gl->base.CPortX, gl->base.CPortY, gl->base.CPortW, gl->base.CPortH); + glSetCap(&gl->state, GL_SCISSOR_TEST, true); glApplyProjection(renderer, &camera->viewMatrix, &camera->projectionMatrix); return true; } if (surfaceId == view->surfaceId) { //the surface belongs to the view we are rending, we use the view's camera. - glViewportCached(gl, 0, 0, gl->surfaceWidth[surfaceId], gl->surfaceHeight[surfaceId]); - glSetCap(gl, GL_SCISSOR_TEST, false); + glViewportCached(&gl->state, 0, 0, gl->surfaceWidth[surfaceId], gl->surfaceHeight[surfaceId]); + glSetCap(&gl->state, GL_SCISSOR_TEST, false); glApplyProjection(renderer, &camera->viewMatrix, &camera->projectionMatrix); return true; } else { @@ -1803,8 +1733,8 @@ static bool glLegacySetRenderTarget(Renderer* renderer, int32_t surfaceId, bool camera->viewAngle = 0; Runner_updateCameraViewSimple(camera); - glViewportCached(gl, 0, 0, gl->surfaceWidth[surfaceId], gl->surfaceHeight[surfaceId]); - glSetCap(gl, GL_SCISSOR_TEST, false); + glViewportCached(&gl->state, 0, 0, gl->surfaceWidth[surfaceId], gl->surfaceHeight[surfaceId]); + glSetCap(&gl->state, GL_SCISSOR_TEST, false); glApplyProjection(renderer, &camera->viewMatrix,&camera->projectionMatrix); return true; } @@ -1881,12 +1811,12 @@ static void glLegacyDrawSurface(Renderer* renderer, int32_t surfaceId, int32_t s static void glLegacySurfaceCopy(Renderer* renderer, int32_t destSurfaceID, int32_t destX, int32_t destY, int32_t srcSurfaceID, int32_t srcX, int32_t srcY, int32_t srcW, int32_t srcH, bool part) { GLLegacyRenderer* gl = (GLLegacyRenderer*) renderer; - GLCommon_surfaceBlit(gl->surfaces, gl->surfaceWidth, gl->surfaceHeight, gl->surfaceCount, destSurfaceID, destX, destY, srcSurfaceID, srcX, srcY, srcW, srcH, part); + GLCommon_surfaceBlit(&gl->state, gl->surfaces, gl->surfaceWidth, gl->surfaceHeight, gl->surfaceCount, destSurfaceID, destX, destY, srcSurfaceID, srcX, srcY, srcW, srcH, part); } static bool glLegacySurfaceGetPixels(Renderer* renderer, int32_t surfaceId, uint8_t* outRGBA) { GLLegacyRenderer* gl = (GLLegacyRenderer*) renderer; - return GLCommon_surfaceGetPixels(gl->surfaces, gl->surfaceWidth, gl->surfaceHeight, gl->surfaceCount, surfaceId, outRGBA); + return GLCommon_surfaceGetPixels(&gl->state, gl->surfaces, gl->surfaceWidth, gl->surfaceHeight, gl->surfaceCount, surfaceId, outRGBA); } diff --git a/src/gl_legacy/gl_legacy_renderer.h b/src/gl_legacy/gl_legacy_renderer.h index fc7fc16d9..70761dd23 100644 --- a/src/gl_legacy/gl_legacy_renderer.h +++ b/src/gl_legacy/gl_legacy_renderer.h @@ -1,6 +1,8 @@ #ifndef _BS_GL_LEGACY_RENDERER_H_ #define _BS_GL_LEGACY_RENDERER_H_ +#include "gl_common.h" + #include "common.h" #include "renderer.h" #include "runner.h" @@ -11,21 +13,6 @@ #include #endif -typedef struct { - // Cached GL state - GLuint currentFbo; - GLuint currentReadFbo; - GLuint currentDrawFbo; - int32_t viewport[4]; - int32_t scissor[4]; - float clearColor[4]; - int32_t activeTexUnit; - bool blendEnabled; - bool scissorEnabled; - bool depthTestEnabled; - bool texture2DEnabled; -} LegacyGLState; - // ===[ GLLegacyRenderer Struct ]=== // Exposed in the header so platform-specific code (main.c) can access FBO fields for screenshots. typedef struct { @@ -70,7 +57,7 @@ typedef struct { int32_t currentDFactorAlpha; // State tracking - LegacyGLState state; + GLState state; } GLLegacyRenderer; bool GLLegacyRenderer_ensureTextureLoaded(GLLegacyRenderer* gl, uint32_t pageId); From f22a2dd44c42b247cc9c0f7ce97bcb0aaa055166 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Mon, 20 Jul 2026 18:14:25 +0100 Subject: [PATCH 10/15] refactor(gl): bring back the letterbox blit funcs --- src/gl/gl_renderer.c | 16 ++-------------- src/gl_legacy/gl_legacy_renderer.c | 16 ++-------------- 2 files changed, 4 insertions(+), 28 deletions(-) diff --git a/src/gl/gl_renderer.c b/src/gl/gl_renderer.c index 99beaf8b1..caff6e760 100644 --- a/src/gl/gl_renderer.c +++ b/src/gl/gl_renderer.c @@ -821,20 +821,8 @@ static void glEndFrameEnd(Renderer* renderer) { int32_t appId = gl->base.runner->applicationSurfaceId; if (gl->isGL3) { - GLuint prevRead = gl->state.currentReadFbo; - GLuint prevDraw = gl->state.currentDrawFbo; - - glBindFramebufferCached(&gl->state, GL_READ_FRAMEBUFFER, gl->surfaces[appId]); - glBindFramebufferCached(&gl->state, GL_DRAW_FRAMEBUFFER, gl->hostFramebuffer); - - glClearColorCached(&gl->state, 0.0f, 0.0f, 0.0f, 1.0f); - int32_t sx, sy, ex, ey; - GLCommon_computeLetterbox(gl->gameW, gl->gameH, gl->windowW, gl->windowH, &sx, &sy, &ex, &ey); - glBlitFramebuffer(0, 0, gl->surfaceWidth[appId], gl->surfaceHeight[appId], - sx, ey, ex, sy, GL_COLOR_BUFFER_BIT, GL_NEAREST); - - glBindFramebufferCached(&gl->state, GL_READ_FRAMEBUFFER, prevRead); - glBindFramebufferCached(&gl->state, GL_DRAW_FRAMEBUFFER, prevDraw); + GLCommon_beginLetterboxBlit(&gl->state, gl->surfaces[appId], gl->hostFramebuffer); + GLCommon_endLetterboxBlit(&gl->state, gl->surfaceWidth[appId], gl->surfaceHeight[appId], gl->gameW, gl->gameH, gl->windowW, gl->windowH, gl->hostFramebuffer); } else { glBindFramebufferCached(&gl->state, GL_FRAMEBUFFER, gl->hostFramebuffer); glSetCap(&gl->state, GL_SCISSOR_TEST, !(gl->state.scissorEnabled)); diff --git a/src/gl_legacy/gl_legacy_renderer.c b/src/gl_legacy/gl_legacy_renderer.c index 6e292440b..a3452042d 100644 --- a/src/gl_legacy/gl_legacy_renderer.c +++ b/src/gl_legacy/gl_legacy_renderer.c @@ -390,20 +390,8 @@ static void glEndFrameEnd(Renderer* renderer) { } int32_t appId = gl->base.runner->applicationSurfaceId; - GLuint prevRead = gl->state.currentReadFbo; - GLuint prevDraw = gl->state.currentDrawFbo; - - glBindFramebufferCached(&gl->state, GL_READ_FRAMEBUFFER, gl->surfaces[appId]); - glBindFramebufferCached(&gl->state, GL_DRAW_FRAMEBUFFER, 0); - - glClearColorCached(&gl->state, 0.0f, 0.0f, 0.0f, 1.0f); - int32_t sx, sy, ex, ey; - GLCommon_computeLetterbox(gl->gameW, gl->gameH, gl->windowW, gl->windowH, &sx, &sy, &ex, &ey); - glBlitFramebuffer(0, 0, gl->surfaceWidth[appId], gl->surfaceHeight[appId], - sx, ey, ex, sy, GL_COLOR_BUFFER_BIT, GL_NEAREST); - - glBindFramebufferCached(&gl->state, GL_READ_FRAMEBUFFER, prevRead); - glBindFramebufferCached(&gl->state, GL_DRAW_FRAMEBUFFER, prevDraw); + GLCommon_beginLetterboxBlit(&gl->state, gl->surfaces[appId], 0); + GLCommon_endLetterboxBlit(&gl->state, gl->surfaceWidth[appId], gl->surfaceHeight[appId], gl->gameW, gl->gameH, gl->windowW, gl->windowH, 0); } static void glRendererFlush(MAYBE_UNUSED Renderer* renderer) {} From 5e59f2e302ab7644b06c8ee433314eb930f74a9c Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Mon, 20 Jul 2026 18:28:58 +0100 Subject: [PATCH 11/15] perf(gl): cache shader uniforms saves 5 gazillion strcmp calls every time a shader is set --- src/gl/gl_renderer.c | 40 +++++++++++++++++++++------------------- src/gl/gl_renderer.h | 6 ++++++ 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/src/gl/gl_renderer.c b/src/gl/gl_renderer.c index caff6e760..d4f943013 100644 --- a/src/gl/gl_renderer.c +++ b/src/gl/gl_renderer.c @@ -134,9 +134,8 @@ static void flushBatch(GLRenderer* gl) { if (gl->base.currentShader != -1) { GMLShader* shader = &gl->gmlShaders[gl->base.currentShader]; - GLShaderUniform* uniform = findShaderUniformByName(shader, "gm_BaseTexture"); - if (uniform != nullptr) - glActiveTextureCached(&gl->state, GL_TEXTURE0 + uniform->samplerSlot); + if (shader->gmBaseTexture != nullptr) + glActiveTextureCached(&gl->state, GL_TEXTURE0 + shader->gmBaseTexture->samplerSlot); glBindTexture(GL_TEXTURE_2D, gl->currentTextureId); } else { glActiveTextureCached(&gl->state, GL_TEXTURE1); @@ -245,6 +244,17 @@ static bool compileProgram(GMLShader* gmlShader, const char* name, const char* v gmlShader->uniforms[b].samplerSlot = samplerIndex; samplerIndex += 1; } + + if (strcmp(uniformName, "gm_BaseTexture") == 0) + gmlShader->gmBaseTexture = &gmlShader->uniforms[b]; + else if (strcmp(uniformName, "gm_Matrices[0]") == 0) + gmlShader->gmMatrices = &gmlShader->uniforms[b]; + else if (strcmp(uniformName, "gm_FogColour") == 0) + gmlShader->gmFogColour = &gmlShader->uniforms[b]; + else if (strcmp(uniformName, "gm_AlphaTestEnabled") == 0) + gmlShader->gmAlphaTestEnabled = &gmlShader->uniforms[b]; + else if (strcmp(uniformName, "gm_AlphaRefValue") == 0) + gmlShader->gmAlphaRefValue = &gmlShader->uniforms[b]; } gmlShader->shaderId = shaderId; @@ -503,14 +513,6 @@ static void glGpuSetShader(Renderer* renderer, int32_t shaderIndex) { glUseProgram(gmlShader->shaderId); gl->currentProgram = gmlShader->shaderId; } - //Gotta set those built-ins! they ain't gonna set themselves - GLShaderUniform* gmMatricesUniform = findShaderUniformByName(gmlShader, "gm_Matrices[0]"); - GLShaderUniform* gmFogColourUniform = findShaderUniformByName(gmlShader, "gm_FogColour"); - - //Lights are for another time - - GLShaderUniform* gmAlphaTestEnabledUniform = findShaderUniformByName(gmlShader, "gm_AlphaTestEnabled"); - GLShaderUniform* gmAlphaRefValue = findShaderUniformByName(gmlShader, "gm_AlphaRefValue"); Matrix4f flippedClip[MATRICES_MAX]; memcpy(flippedClip, renderer->gmlMatrices, sizeof(flippedClip)); @@ -518,17 +520,17 @@ static void glGpuSetShader(Renderer* renderer, int32_t shaderIndex) { Matrix4f_flipClipY(&flippedClip[MATRIX_PROJECTION]); Matrix4f_flipClipY(&flippedClip[MATRIX_WORLD_VIEW_PROJECTION]); - if (gmMatricesUniform != nullptr) { - glUniformMatrix4fv(gmMatricesUniform->location, 5, GL_FALSE, flippedClip[0].m); + if (gmlShader->gmMatrices != nullptr) { + glUniformMatrix4fv(gmlShader->gmMatrices->location, 5, GL_FALSE, flippedClip[0].m); } - if (gmFogColourUniform != nullptr) { - glUniform1i(gmFogColourUniform->location, gl->fogColor); + if (gmlShader->gmFogColour != nullptr) { + glUniform1i(gmlShader->gmFogColour->location, gl->fogColor); } - if (gmAlphaTestEnabledUniform != nullptr) { - glUniform1i(gmAlphaTestEnabledUniform->location, gl->alphaTestEnable); + if (gmlShader->gmAlphaTestEnabled != nullptr) { + glUniform1i(gmlShader->gmAlphaTestEnabled->location, gl->alphaTestEnable); } - if (gmAlphaRefValue != nullptr) { - glUniform1f(gmAlphaRefValue->location, gl->alphaTestRef); + if (gmlShader->gmAlphaRefValue != nullptr) { + glUniform1f(gmlShader->gmAlphaRefValue->location, gl->alphaTestRef); } renderer->currentShader = shaderIndex; diff --git a/src/gl/gl_renderer.h b/src/gl/gl_renderer.h index 39a646fff..7eee71c98 100644 --- a/src/gl/gl_renderer.h +++ b/src/gl/gl_renderer.h @@ -30,6 +30,12 @@ typedef struct { bool compiled; uint32_t uniformCount; GLShaderUniform* uniforms; + + GLShaderUniform* gmBaseTexture; + GLShaderUniform* gmMatrices; + GLShaderUniform* gmFogColour; + GLShaderUniform* gmAlphaTestEnabled; + GLShaderUniform* gmAlphaRefValue; } GMLShader; typedef struct { From 359abe5f27590d29cc6efd42e2de7caa7af4e321 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Mon, 20 Jul 2026 19:14:18 +0100 Subject: [PATCH 12/15] fix(gl): bring back VBO orphaning for VAO path --- src/gl/gl_renderer.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/gl/gl_renderer.c b/src/gl/gl_renderer.c index d4f943013..8ace54589 100644 --- a/src/gl/gl_renderer.c +++ b/src/gl/gl_renderer.c @@ -147,14 +147,13 @@ static void flushBatch(GLRenderer* gl) { int32_t indexCount = gl->batchCount * INDICES_PER_QUAD; int32_t totalVboSize = MAX_QUADS * VERTICES_PER_QUAD * sizeof(Vertex); - if (hasVAO()) { - glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount * sizeof(Vertex), gl->vertexData); - } else { - glBindBuffer(GL_ARRAY_BUFFER, gl->vbo); - glBufferData(GL_ARRAY_BUFFER, totalVboSize, nullptr, GL_STREAM_DRAW); - glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount * sizeof(Vertex), gl->vertexData); + glBindBuffer(GL_ARRAY_BUFFER, gl->vbo); + glBufferData(GL_ARRAY_BUFFER, totalVboSize, nullptr, GL_STREAM_DRAW); + glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount * sizeof(Vertex), gl->vertexData); + + if (!hasVAO()) { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gl->ebo); - + int32_t stride = sizeof(Vertex); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, stride, (void*) offsetof(Vertex, x)); glEnableVertexAttribArray(0); From 4dcfdead8f3d1a7aa13296ba835274bcfe063e40 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Mon, 20 Jul 2026 20:18:29 +0100 Subject: [PATCH 13/15] fix(gl): don't early return on GL_FRAMEBUFFER --- src/gl_common/gl_common.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/gl_common/gl_common.c b/src/gl_common/gl_common.c index 999613a16..aca82357e 100644 --- a/src/gl_common/gl_common.c +++ b/src/gl_common/gl_common.c @@ -13,7 +13,6 @@ void glBindFramebufferCached(GLState* state, GLenum target, GLuint fbo) { switch (target) { case GL_FRAMEBUFFER: - if (state->currentReadFbo == fbo && state->currentDrawFbo == fbo) return; state->currentReadFbo = fbo; state->currentDrawFbo = fbo; break; @@ -112,10 +111,10 @@ void GLCommon_beginLetterboxBlit(GLState* state, GLuint fbo, GLuint hostFbo) { void GLCommon_endLetterboxBlit(GLState* state, int32_t fboWidth, int32_t fboHeight, int32_t gameW, int32_t gameH, int32_t windowW, int32_t windowH, GLuint hostFbo) { int32_t sx, sy, ex, ey; - glClearColor(0.0, 0.0, 0.0, 1.0); //please remove if it breaks something like borders, it was just my quick-fix for the color to not be randomly changed + glClearColorCached(state, 0.0f, 0.0f, 0.0f, 1.0f); //please remove if it breaks something like borders, it was just my quick-fix for the color to not be randomly changed GLCommon_computeLetterbox(gameW, gameH, windowW, windowH, &sx, &sy, &ex, &ey); glBlitFramebuffer(0, 0, fboWidth, fboHeight, sx, ey, ex, sy, GL_COLOR_BUFFER_BIT, GL_NEAREST); - glBindFramebufferCached(state, GL_DRAW_FRAMEBUFFER, hostFbo); + glBindFramebufferCached(state, GL_FRAMEBUFFER, hostFbo); } // ===[ Surface arrays ]=== From 6b4e86cda7f243c556b3a6d4cafcbcfa3f7e5396 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Tue, 21 Jul 2026 11:10:15 +0100 Subject: [PATCH 14/15] perf(gl): only VBO orphan total vertex size --- src/gl/gl_renderer.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/gl/gl_renderer.c b/src/gl/gl_renderer.c index 8ace54589..d68176e94 100644 --- a/src/gl/gl_renderer.c +++ b/src/gl/gl_renderer.c @@ -146,10 +146,8 @@ static void flushBatch(GLRenderer* gl) { int32_t vertexCount = gl->batchCount * singleVertexCount; int32_t indexCount = gl->batchCount * INDICES_PER_QUAD; - int32_t totalVboSize = MAX_QUADS * VERTICES_PER_QUAD * sizeof(Vertex); glBindBuffer(GL_ARRAY_BUFFER, gl->vbo); - glBufferData(GL_ARRAY_BUFFER, totalVboSize, nullptr, GL_STREAM_DRAW); - glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount * sizeof(Vertex), gl->vertexData); + glBufferData(GL_ARRAY_BUFFER, vertexCount * sizeof(Vertex), gl->vertexData, GL_STREAM_DRAW); if (!hasVAO()) { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gl->ebo); From 72376b4fc6e400a19cc91d19c95ef6c323b35144 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Tue, 21 Jul 2026 11:22:02 +0100 Subject: [PATCH 15/15] refactor(gl_common): remove unnecessary cast from glActiveTextureCached --- src/gl_common/gl_common.c | 6 +++--- src/gl_common/gl_common.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gl_common/gl_common.c b/src/gl_common/gl_common.c index aca82357e..e634be069 100644 --- a/src/gl_common/gl_common.c +++ b/src/gl_common/gl_common.c @@ -73,9 +73,9 @@ void glClearColorCached(GLState* state, float r, float g, float b, float a) { glClearColor(r, g, b, a); } -void glActiveTextureCached(GLState* state, GLenum unit) { - if (state->activeTexUnit == (int32_t)unit) return; - state->activeTexUnit = (int32_t)unit; +void glActiveTextureCached(GLState* state, int32_t unit) { + if (state->activeTexUnit == unit) return; + state->activeTexUnit = unit; glActiveTexture(unit); } diff --git a/src/gl_common/gl_common.h b/src/gl_common/gl_common.h index dd255419a..681cec8d2 100644 --- a/src/gl_common/gl_common.h +++ b/src/gl_common/gl_common.h @@ -50,7 +50,7 @@ void glViewportCached(GLState* state, int32_t x, int32_t y, int32_t w, int32_t h void glScissorCached(GLState* state, int32_t x, int32_t y, int32_t w, int32_t h); void glSetCap(GLState* state, GLenum cap, bool enabled); void glClearColorCached(GLState* state, float r, float g, float b, float a); -void glActiveTextureCached(GLState* state, GLenum unit); +void glActiveTextureCached(GLState* state, int32_t unit); void glPixelStoreiCached(GLState* state, GLenum pname, int32_t param); // ===[ Letterbox blit ]===