render: migrate rendering pipeline from OpenGL to GLES 2.0

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I55267367c8001ffc5ceac2c64015a7686a6a6964
This commit is contained in:
raf 2026-05-01 12:59:53 +03:00
commit f4275cb0f8
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF

View file

@ -187,7 +187,6 @@ static void calculate_texture_coords(chroma_scale_mode_t scale_mode,
// Vertex shader for simple texture rendering // Vertex shader for simple texture rendering
static const char *vertex_shader_source = static const char *vertex_shader_source =
"#version 120\n"
"attribute vec2 position;\n" "attribute vec2 position;\n"
"attribute vec2 texcoord;\n" "attribute vec2 texcoord;\n"
"varying vec2 v_texcoord;\n" "varying vec2 v_texcoord;\n"
@ -198,7 +197,7 @@ static const char *vertex_shader_source =
// Fragment shader for simple texture rendering // Fragment shader for simple texture rendering
static const char *fragment_shader_source = static const char *fragment_shader_source =
"#version 120\n" "precision mediump float;\n"
"varying vec2 v_texcoord;\n" "varying vec2 v_texcoord;\n"
"uniform sampler2D texture;\n" "uniform sampler2D texture;\n"
"void main() {\n" "void main() {\n"
@ -214,7 +213,7 @@ static const float vertices[] = {
-1.0f, 1.0f, 0.0f, 0.0f, // top left -1.0f, 1.0f, 0.0f, 0.0f, // top left
}; };
static const unsigned int indices[] = { static const unsigned short indices[] = {
0, 1, 2, // first triangle 0, 1, 2, // first triangle
2, 3, 0 // second triangle 2, 3, 0 // second triangle
}; };
@ -284,17 +283,29 @@ static GLuint create_shader_program(void) {
} }
// Initialize OpenGL resources for output // Initialize OpenGL resources for output
static int init_gl_resources(chroma_output_t *output) { static int init_gl_resources(chroma_state_t *state, chroma_output_t *output) {
if (!output || output->gl_resources_initialized) { if (!output || output->gl_resources_initialized) {
return CHROMA_OK; return CHROMA_OK;
} }
// Create shader prog // Use shared shader program from state if available
output->shader_program = create_shader_program(); if (state && state->shader_program != 0) {
if (!output->shader_program) { output->shader_program = state->shader_program;
chroma_log("ERROR", "Failed to create shader program for output %u", chroma_log("DEBUG", "Using shared shader program for output %u",
output->id); output->id);
return CHROMA_ERROR_EGL; } else {
// Create shader program
output->shader_program = create_shader_program();
if (!output->shader_program) {
chroma_log("ERROR", "Failed to create shader program for output %u",
output->id);
return CHROMA_ERROR_EGL;
}
// Store in state for sharing
if (state) {
state->shader_program = output->shader_program;
chroma_log("DEBUG", "Created shared shader program");
}
} }
// Create and setup VBO/EBO // Create and setup VBO/EBO
@ -308,6 +319,10 @@ static int init_gl_resources(chroma_output_t *output) {
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices,
GL_STATIC_DRAW); GL_STATIC_DRAW);
// Unbind buffers
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
output->texture_id = 0; // will be created when image is assigned output->texture_id = 0; // will be created when image is assigned
output->vbo_dirty = true; // VBO needs initial update output->vbo_dirty = true; // VBO needs initial update
output->gl_resources_initialized = true; output->gl_resources_initialized = true;
@ -370,9 +385,24 @@ static int update_texture_from_image(chroma_output_t *output,
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter);
// Upload texture data (always RGBA now) // Upload texture data with appropriate format based on channels
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image->width, image->height, 0, GLint internal_format;
GL_RGBA, GL_UNSIGNED_BYTE, image->data); GLenum format;
if (image->channels == 4) {
internal_format = GL_RGBA;
format = GL_RGBA;
} else if (image->channels == 3) {
internal_format = GL_RGB;
format = GL_RGB;
} else if (image->channels == 2) {
internal_format = GL_LUMINANCE_ALPHA;
format = GL_LUMINANCE_ALPHA;
} else {
internal_format = GL_LUMINANCE;
format = GL_LUMINANCE;
}
glTexImage2D(GL_TEXTURE_2D, 0, internal_format, image->width, image->height,
0, format, GL_UNSIGNED_BYTE, image->data);
// Generate mipmaps for trilinear filtering if they're needed // Generate mipmaps for trilinear filtering if they're needed
if (needs_mipmaps) { if (needs_mipmaps) {
@ -385,38 +415,24 @@ static int update_texture_from_image(chroma_output_t *output,
// Mark this output as having uploaded its texture // Mark this output as having uploaded its texture
output->texture_uploaded = true; output->texture_uploaded = true;
// Free system RAM copy only when ALL outputs using this image have uploaded // Decrement reference count and free system RAM when all outputs
// to GPU // have uploaded their textures
if (image->data) { image->ref_count--;
// Count total outputs using this image and how many have uploaded if (image->ref_count <= 0 && image->data) {
int total_using = 0; size_t freed_bytes =
int uploaded_count = 0; (size_t)image->width * (size_t)image->height * (size_t)image->channels;
stbi_image_free(image->data);
chroma_state_t *state = output->state; image->data = NULL;
for (int i = 0; i < state->output_count; i++) { chroma_log("INFO", "Freed %.2f MB of image data after GPU upload: %s",
if (state->outputs[i].active && state->outputs[i].image == image) { (double)freed_bytes / (1024.0 * 1024.0), image->path);
total_using++; chroma_log_resource_deallocation("image_data", freed_bytes,
if (state->outputs[i].texture_uploaded) { "post-gpu-upload");
uploaded_count++; chroma_log_memory_stats("post-gpu-upload");
} } else {
} chroma_log("DEBUG",
} "Keeping image data for %s (ref_count: %d, waiting for %d more "
"outputs)",
// Only free image data when ALL outputs using it have uploaded image->path, image->ref_count, image->ref_count);
if (total_using > 0 && uploaded_count >= total_using) {
size_t freed_bytes = (size_t)image->width * (size_t)image->height *
(size_t)image->channels;
stbi_image_free(image->data);
image->data = NULL;
chroma_log("INFO",
"Freed %.2f MB of image data after all %d outputs uploaded to "
"GPU: %s",
(double)freed_bytes / (1024.0 * 1024.0), total_using,
image->path);
chroma_log_resource_deallocation("image_data", freed_bytes,
"post-gpu-upload");
chroma_log_memory_stats("post-gpu-upload");
}
} }
chroma_log("DEBUG", "Updated texture for output %u (%dx%d)", output->id, chroma_log("DEBUG", "Updated texture for output %u (%dx%d)", output->id,
@ -425,7 +441,8 @@ static int update_texture_from_image(chroma_output_t *output,
} }
// Cleanup OpenGL resources for output // Cleanup OpenGL resources for output
static void cleanup_gl_resources(chroma_output_t *output) { static void cleanup_gl_resources(chroma_state_t *state,
chroma_output_t *output) {
if (!output || !output->gl_resources_initialized) { if (!output || !output->gl_resources_initialized) {
return; return;
} }
@ -436,8 +453,12 @@ static void cleanup_gl_resources(chroma_output_t *output) {
output->texture_id = 0; output->texture_id = 0;
} }
// Don't delete shared shader program here, it's managed by state
if (output->shader_program != 0) { if (output->shader_program != 0) {
glDeleteProgram(output->shader_program); // Only delete if this output owns the program (not shared)
if (state && state->shader_program != output->shader_program) {
glDeleteProgram(output->shader_program);
}
output->shader_program = 0; output->shader_program = 0;
} }
@ -466,9 +487,9 @@ static int choose_egl_config(EGLDisplay display, EGLConfig *config) {
EGL_BLUE_SIZE, EGL_BLUE_SIZE,
8, 8,
EGL_ALPHA_SIZE, EGL_ALPHA_SIZE,
8, 0,
EGL_RENDERABLE_TYPE, EGL_RENDERABLE_TYPE,
EGL_OPENGL_BIT, EGL_OPENGL_ES2_BIT,
EGL_NONE}; EGL_NONE};
EGLint num_configs; EGLint num_configs;
@ -508,9 +529,9 @@ int chroma_egl_init(chroma_state_t *state) {
chroma_log("INFO", "EGL initialized: version %d.%d", major, minor); chroma_log("INFO", "EGL initialized: version %d.%d", major, minor);
chroma_log_memory_stats("post-egl-init"); chroma_log_memory_stats("post-egl-init");
// Bind OpenGL API // Bind OpenGL ES API for better efficiency on embedded/GPU drivers
if (!eglBindAPI(EGL_OPENGL_API)) { if (!eglBindAPI(EGL_OPENGL_ES_API)) {
chroma_log("ERROR", "Failed to bind OpenGL API: 0x%04x", eglGetError()); chroma_log("ERROR", "Failed to bind OpenGL ES API: 0x%04x", eglGetError());
chroma_egl_cleanup(state); chroma_egl_cleanup(state);
return CHROMA_ERROR_EGL; return CHROMA_ERROR_EGL;
} }
@ -521,9 +542,8 @@ int chroma_egl_init(chroma_state_t *state) {
return CHROMA_ERROR_EGL; return CHROMA_ERROR_EGL;
} }
// Create EGL context // Create EGL context for GLES 2.0
EGLint context_attributes[] = {EGL_CONTEXT_MAJOR_VERSION, 2, EGLint context_attributes[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
EGL_CONTEXT_MINOR_VERSION, 1, EGL_NONE};
state->egl_context = eglCreateContext(state->egl_display, state->egl_config, state->egl_context = eglCreateContext(state->egl_display, state->egl_config,
EGL_NO_CONTEXT, context_attributes); EGL_NO_CONTEXT, context_attributes);
@ -543,6 +563,12 @@ void chroma_egl_cleanup(chroma_state_t *state) {
return; return;
} }
// Clean up shared shader program while context is still current
if (state->shader_program != 0) {
glDeleteProgram(state->shader_program);
state->shader_program = 0;
}
if (state->egl_display != EGL_NO_DISPLAY) { if (state->egl_display != EGL_NO_DISPLAY) {
eglMakeCurrent(state->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, eglMakeCurrent(state->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
EGL_NO_CONTEXT); EGL_NO_CONTEXT);
@ -644,8 +670,8 @@ void chroma_surface_destroy(chroma_output_t *output) {
return; return;
} }
// Clean up OpenGL resources first // Clean up OpenGL resources first. We use output's back-reference to state
cleanup_gl_resources(output); cleanup_gl_resources(output->state, output);
if (output->egl_surface != EGL_NO_SURFACE) { if (output->egl_surface != EGL_NO_SURFACE) {
eglDestroySurface(eglGetCurrentDisplay(), output->egl_surface); eglDestroySurface(eglGetCurrentDisplay(), output->egl_surface);
@ -667,6 +693,8 @@ void chroma_surface_destroy(chroma_output_t *output) {
output->surface = NULL; output->surface = NULL;
} }
output->configured = false;
// Log surface destruction // Log surface destruction
size_t surface_size = (size_t)output->width * (size_t)output->height * 4; size_t surface_size = (size_t)output->width * (size_t)output->height * 4;
chroma_log_resource_deallocation("egl_surface", surface_size, chroma_log_resource_deallocation("egl_surface", surface_size,
@ -689,7 +717,7 @@ int chroma_render_wallpaper(chroma_state_t *state, chroma_output_t *output) {
return CHROMA_ERROR_EGL; return CHROMA_ERROR_EGL;
} }
if (init_gl_resources(output) != CHROMA_OK) { if (init_gl_resources(state, output) != CHROMA_OK) {
return CHROMA_ERROR_EGL; return CHROMA_ERROR_EGL;
} }
@ -762,7 +790,7 @@ int chroma_render_wallpaper(chroma_state_t *state, chroma_output_t *output) {
4 * sizeof(float), (void *)(2 * sizeof(float))); 4 * sizeof(float), (void *)(2 * sizeof(float)));
// Draw // Draw
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0);
// Unbind resources // Unbind resources
glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ARRAY_BUFFER, 0);
@ -770,16 +798,13 @@ int chroma_render_wallpaper(chroma_state_t *state, chroma_output_t *output) {
glBindTexture(GL_TEXTURE_2D, 0); glBindTexture(GL_TEXTURE_2D, 0);
glUseProgram(0); glUseProgram(0);
// Swap buffers // Swap buffers; this implicitly commits the surface for EGL on Wayland
if (!eglSwapBuffers(state->egl_display, output->egl_surface)) { if (!eglSwapBuffers(state->egl_display, output->egl_surface)) {
chroma_log("ERROR", "Failed to swap buffers for output %u: 0x%04x", chroma_log("ERROR", "Failed to swap buffers for output %u: 0x%04x",
output->id, eglGetError()); output->id, eglGetError());
return CHROMA_ERROR_EGL; return CHROMA_ERROR_EGL;
} }
// Commit surface
wl_surface_commit(output->surface);
chroma_log("DEBUG", "Rendered wallpaper to output %u (cached resources)", chroma_log("DEBUG", "Rendered wallpaper to output %u (cached resources)",
output->id); output->id);
return CHROMA_OK; return CHROMA_OK;