brewery
notashelf /
fa88f6162bfa9e6e6ad661c6d8402e47fceb015e

chroma

public

Lightweight wallpaper daemon for Wayland

Star0tarball
clone
ssh://git@git.notashelf.dev:33/notashelf/chroma.git

Commit fa88f6162bfa

tarball

NotAShelf <raf@notashelf.dev> · 2026-05-17 21:23 UTC

unverified1 files changed+87-62
render: migrate rendering pipeline from OpenGL to GLES 2.0

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I55267367c8001ffc5ceac2c64015a7686a6a6964
diff --git a/src/render.c b/src/render.cindex 2ade4c4..732a1ae 100644--- a/src/render.c+++ b/src/render.c@@ -187,7 +187,6 @@ static void calculate_texture_coords(chroma_scale_mode_t scale_mode,  // Vertex shader for simple texture rendering static const char *vertex_shader_source =-    "#version 120\n"     "attribute vec2 position;\n"     "attribute vec2 texcoord;\n"     "varying vec2 v_texcoord;\n"@@ -198,7 +197,7 @@ static const char *vertex_shader_source =  // Fragment shader for simple texture rendering static const char *fragment_shader_source =-    "#version 120\n"+    "precision mediump float;\n"     "varying vec2 v_texcoord;\n"     "uniform sampler2D texture;\n"     "void main() {\n"@@ -214,7 +213,7 @@ static const float vertices[] = {     -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     2, 3, 0  // second triangle };@@ -284,17 +283,29 @@ static GLuint create_shader_program(void) { }  // 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) {     return CHROMA_OK;   } -  // Create shader prog-  output->shader_program = create_shader_program();-  if (!output->shader_program) {-    chroma_log("ERROR", "Failed to create shader program for output %u",+  // Use shared shader program from state if available+  if (state && state->shader_program != 0) {+    output->shader_program = state->shader_program;+    chroma_log("DEBUG", "Using shared shader program for output %u",                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@@ -308,6 +319,10 @@ static int init_gl_resources(chroma_output_t *output) {   glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices,                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->vbo_dirty = true; // VBO needs initial update   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_MAG_FILTER, mag_filter); -  // Upload texture data (always RGBA now)-  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image->width, image->height, 0,-               GL_RGBA, GL_UNSIGNED_BYTE, image->data);+  // Upload texture data with appropriate format based on channels+  GLint internal_format;+  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   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   output->texture_uploaded = true; -  // Free system RAM copy only when ALL outputs using this image have uploaded-  // to GPU-  if (image->data) {-    // Count total outputs using this image and how many have uploaded-    int total_using = 0;-    int uploaded_count = 0;--    chroma_state_t *state = output->state;-    for (int i = 0; i < state->output_count; i++) {-      if (state->outputs[i].active && state->outputs[i].image == image) {-        total_using++;-        if (state->outputs[i].texture_uploaded) {-          uploaded_count++;-        }-      }-    }--    // Only free image data when ALL outputs using it have uploaded-    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");-    }+  // Decrement reference count and free system RAM when all outputs+  // have uploaded their textures+  image->ref_count--;+  if (image->ref_count <= 0 && image->data) {+    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 GPU upload: %s",+               (double)freed_bytes / (1024.0 * 1024.0), image->path);+    chroma_log_resource_deallocation("image_data", freed_bytes,+                                     "post-gpu-upload");+    chroma_log_memory_stats("post-gpu-upload");+  } else {+    chroma_log("DEBUG",+               "Keeping image data for %s (ref_count: %d, waiting for %d more "+               "outputs)",+               image->path, image->ref_count, image->ref_count);   }    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-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) {     return;   }@@ -436,8 +453,12 @@ static void cleanup_gl_resources(chroma_output_t *output) {     output->texture_id = 0;   } +  // Don't delete shared shader program here, it's managed by state   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;   } @@ -466,9 +487,9 @@ static int choose_egl_config(EGLDisplay display, EGLConfig *config) {                          EGL_BLUE_SIZE,                          8,                          EGL_ALPHA_SIZE,-                         8,+                         0,                          EGL_RENDERABLE_TYPE,-                         EGL_OPENGL_BIT,+                         EGL_OPENGL_ES2_BIT,                          EGL_NONE};    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_memory_stats("post-egl-init"); -  // Bind OpenGL API-  if (!eglBindAPI(EGL_OPENGL_API)) {-    chroma_log("ERROR", "Failed to bind OpenGL API: 0x%04x", eglGetError());+  // Bind OpenGL ES API for better efficiency on embedded/GPU drivers+  if (!eglBindAPI(EGL_OPENGL_ES_API)) {+    chroma_log("ERROR", "Failed to bind OpenGL ES API: 0x%04x", eglGetError());     chroma_egl_cleanup(state);     return CHROMA_ERROR_EGL;   }@@ -521,9 +542,8 @@ int chroma_egl_init(chroma_state_t *state) {     return CHROMA_ERROR_EGL;   } -  // Create EGL context-  EGLint context_attributes[] = {EGL_CONTEXT_MAJOR_VERSION, 2,-                                 EGL_CONTEXT_MINOR_VERSION, 1, EGL_NONE};+  // Create EGL context for GLES 2.0+  EGLint context_attributes[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};    state->egl_context = eglCreateContext(state->egl_display, state->egl_config,                                         EGL_NO_CONTEXT, context_attributes);@@ -543,6 +563,12 @@ void chroma_egl_cleanup(chroma_state_t *state) {     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) {     eglMakeCurrent(state->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,                    EGL_NO_CONTEXT);@@ -644,8 +670,8 @@ void chroma_surface_destroy(chroma_output_t *output) {     return;   } -  // Clean up OpenGL resources first-  cleanup_gl_resources(output);+  // Clean up OpenGL resources first. We use output's back-reference to state+  cleanup_gl_resources(output->state, output);    if (output->egl_surface != EGL_NO_SURFACE) {     eglDestroySurface(eglGetCurrentDisplay(), output->egl_surface);@@ -667,6 +693,8 @@ void chroma_surface_destroy(chroma_output_t *output) {     output->surface = NULL;   } +  output->configured = false;+   // Log surface destruction   size_t surface_size = (size_t)output->width * (size_t)output->height * 4;   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;   } -  if (init_gl_resources(output) != CHROMA_OK) {+  if (init_gl_resources(state, output) != CHROMA_OK) {     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)));    // Draw-  glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);+  glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0);    // Unbind resources   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);   glUseProgram(0); -  // Swap buffers+  // Swap buffers; this implicitly commits the surface for EGL on Wayland   if (!eglSwapBuffers(state->egl_display, output->egl_surface)) {     chroma_log("ERROR", "Failed to swap buffers for output %u: 0x%04x",                output->id, eglGetError());     return CHROMA_ERROR_EGL;   } -  // Commit surface-  wl_surface_commit(output->surface);-   chroma_log("DEBUG", "Rendered wallpaper to output %u (cached resources)",              output->id);   return CHROMA_OK;