notashelf /
fa88f6162bfa9e6e6ad661c6d8402e47fceb015e
chroma
publicLightweight wallpaper daemon for Wayland
clone
ssh://git@git.notashelf.dev:33/notashelf/chroma.gitCommit fa88f6162bfa
tarballunverified1 files changed+87-62
@@ -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;