diff --git a/src/render.c b/src/render.c index 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;