@All
Do not know if it worth to report anymore, due to all known "dunno if anything will be released" , but seems i found another bug in ogles2 (at least i think so): glBindTexture(GL_TEXTURE_2D, 0) leak memory for no reasson and on exit not free it as well.
That call should just unbind the currently bound 2D texture and restore texture unit 0’s binding to the GLES default object. It should not allocate anything per call. And on x86/etc that it. But seems on our ogles2 realisation this eat memory a lot with no reasson.
I.e:
- Binding real texture IDs does not leak.
- Cycling between real texture IDs does not leak.
- glActiveTexture(GL_TEXTURE0) alone does not leak.
- glBindTexture(GL_TEXTURE_2D, 0) leaks heavily.
It looks like OGLES2 internal bug is: when texture ID 0 is bound, the library creates or realizes some internal “default/no texture” Nova-side object/state each time,
but does not reuse or free it. Or it triggers a state/submit path that appends retained objects for texture 0.
There is SDL2 based test case if anyone need it:
#include <SDL.h>
#include <SDL_opengles2.h>
#include <stdio.h>
static GLuint create_texture(void)
{
static const unsigned char pixel[4] = {255U, 255U, 255U, 255U};
GLuint texture;
texture = 0U;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixel);
return texture;
}
int main(int argc, char **argv)
{
SDL_Window *window;
SDL_GLContext context;
GLuint texture;
unsigned int start_ms;
unsigned int last_ms;
unsigned long frames;
unsigned long binds;
(void)argc;
(void)argv;
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) != 0) {
return 1;
}
(void)SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
(void)SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
(void)SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
(void)SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
window = SDL_CreateWindow(
"glBindTexture 0 leak",
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED,
320,
240,
SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN);
if (window == NULL) {
SDL_Quit();
return 1;
}
context = SDL_GL_CreateContext(window);
if (context == NULL) {
SDL_DestroyWindow(window);
SDL_Quit();
return 1;
}
if (SDL_GL_MakeCurrent(window, context) != 0) {
SDL_GL_DeleteContext(context);
SDL_DestroyWindow(window);
SDL_Quit();
return 1;
}
texture = create_texture();
if (texture == 0U || glGetError() != GL_NO_ERROR) {
SDL_GL_DeleteContext(context);
SDL_DestroyWindow(window);
SDL_Quit();
return 1;
}
frames = 0UL;
binds = 0UL;
start_ms = SDL_GetTicks();
last_ms = start_ms;
for (;;) {
SDL_Event event;
unsigned int now_ms;
int i;
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT ||
(event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE)) {
goto done;
}
}
for (i = 0; i < 64; ++i) {
glBindTexture(GL_TEXTURE_2D, 0);
++binds;
}
SDL_GL_SwapWindow(window);
SDL_Delay(1);
++frames;
now_ms = SDL_GetTicks();
if (now_ms - last_ms >= 1000U) {
printf("t=%u frames=%lu binds=%lu\n", (now_ms - start_ms) / 1000U, frames, binds);
fflush(stdout);
last_ms = now_ms;
}
if (now_ms - start_ms >= 20000U) {
break;
}
}
done:
glDeleteTextures(1, &texture);
(void)SDL_GL_MakeCurrent(window, NULL);
SDL_GL_DeleteContext(context);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
It can be build for x86 too if need it (and there it will have no issues, of course).
Edited by kas1e on 2026/7/6 16:25:37
Edited by kas1e on 2026/7/6 16:31:37