notashelf /
d1116e772101b82b426c7ea946b726022a9271e5
chroma
publicLightweight wallpaper daemon for Wayland
clone
ssh://git@git.notashelf.dev:33/notashelf/chroma.gitCommit d1116e772101
tarballunverified4 files changed+221-114
@@ -62,6 +62,14 @@ typedef struct { // Associated wallpaper chroma_image_t *image;++ // OpenGL resource cache+ GLuint texture_id;+ GLuint shader_program;+ GLuint vbo;+ GLuint ebo;+ bool gl_resources_initialized;+ bool texture_uploaded; } chroma_output_t; // Config mapping structure@@ -150,6 +158,7 @@ void chroma_egl_cleanup(chroma_state_t *state); int chroma_surface_create(chroma_state_t *state, chroma_output_t *output); void chroma_surface_destroy(chroma_output_t *output); int chroma_render_wallpaper(chroma_state_t *state, chroma_output_t *output);+void chroma_output_invalidate_texture(chroma_output_t *output); // Layer shell functions void chroma_layer_surface_configure(void *data,@@ -219,4 +228,4 @@ extern const struct zwlr_layer_surface_v1_listener // Global state for signal handling extern volatile sig_atomic_t chroma_should_quit; -#endif // CHROMA_H\ No newline at end of file+#endif // CHROMA_H@@ -87,6 +87,14 @@ static int assign_wallpaper_to_output(chroma_state_t *state, return CHROMA_ERROR_IMAGE; } + // Check if image changed and invalidate texture cache if neceessary+ bool image_changed = (output->image != image);+ if (image_changed && output->image) {+ chroma_output_invalidate_texture(output);+ chroma_log("DEBUG", "Image changed for output %u, invalidated texture",+ output->id);+ }+ // Assign image to output output->image = image; @@ -49,11 +49,12 @@ int chroma_image_load(chroma_image_t *image, const char *path) { (double)file_size / (1024.0 * 1024.0)); } - // Load image data using stb_image- stbi_set_flip_vertically_on_load(0); // Keep images right-side up+ // Load image data using stb_image, force RGBA format to avoid conversion+ stbi_set_flip_vertically_on_load(0); // keep images right-side up image->data =- stbi_load(path, &image->width, &image->height, &image->channels, 0);+ stbi_load(path, &image->width, &image->height, &image->channels, 4);+ image->channels = 4; // always RGBA after forced conversion if (!image->data) { chroma_log("ERROR", "Failed to load image %s: %s", path, stbi_failure_reason());@@ -68,39 +69,14 @@ int chroma_image_load(chroma_image_t *image, const char *path) { return CHROMA_ERROR_IMAGE; } - // Check supported formats- if (image->channels < 3 || image->channels > 4) {- chroma_log("ERROR",- "Unsupported image format: %d channels (need RGB or RGBA)",+ // Validate we have RGBA data (should always be true with forced conversion)+ if (image->channels != 4) {+ chroma_log("ERROR", "Failed to load image as RGBA: got %d channels", image->channels); chroma_image_free(image); return CHROMA_ERROR_IMAGE; } - // Convert RGB to RGBA if necessary for consistent handling- if (image->channels == 3) {- int pixel_count = image->width * image->height;- unsigned char *rgba_data = malloc(pixel_count * 4);- if (!rgba_data) {- chroma_log("ERROR", "Failed to allocate memory for RGBA conversion");- chroma_image_free(image);- return CHROMA_ERROR_MEMORY;- }-- // Convert RGB to RGBA- for (int i = 0; i < pixel_count; i++) {- rgba_data[i * 4 + 0] = image->data[i * 3 + 0]; // R- rgba_data[i * 4 + 1] = image->data[i * 3 + 1]; // G- rgba_data[i * 4 + 2] = image->data[i * 3 + 2]; // B- rgba_data[i * 4 + 3] = 255; // A- }-- // Replace original data- stbi_image_free(image->data);- image->data = rgba_data;- image->channels = 4;- }- image->loaded = true; chroma_log("INFO", "Loaded image: %s (%dx%d, %d channels, %.2f MB)", path,@@ -118,13 +94,8 @@ void chroma_image_free(chroma_image_t *image) { } if (image->data) {- if (image->channels == 4 && strlen(image->path) > 0) {- // If we converted from RGB to RGBA, use regular free()- free(image->data);- } else {- // If loaded directly by stb_image, use stbi_image_free()- stbi_image_free(image->data);- }+ // Always use stbi_image_free since we load directly with stbi_load+ stbi_image_free(image->data); image->data = NULL; } @@ -265,4 +236,4 @@ void chroma_image_init_stb(void) { stbi_ldr_to_hdr_scale(1.0f); chroma_log("DEBUG", "Initialized stb_image library");-}\ No newline at end of file+}@@ -1,4 +1,3 @@-#include <math.h> #include <stdio.h> #include <stdlib.h> #include <string.h>@@ -7,6 +6,7 @@ #include <GLES2/gl2.h> #include "../include/chroma.h"+#include "../include/stb_image.h" // Vertex shader for simple texture rendering static const char *vertex_shader_source =@@ -31,18 +31,17 @@ static const char *fragment_shader_source = // Vertices for a fullscreen quad static const float vertices[] = { // Position Texcoord- -1.0f, -1.0f, 0.0f, 1.0f, // Bottom left- 1.0f, -1.0f, 1.0f, 1.0f, // Bottom right- 1.0f, 1.0f, 1.0f, 0.0f, // Top right- -1.0f, 1.0f, 0.0f, 0.0f, // Top left+ -1.0f, -1.0f, 0.0f, 1.0f, // bottom left+ 1.0f, -1.0f, 1.0f, 1.0f, // bottom right+ 1.0f, 1.0f, 1.0f, 0.0f, // top right+ -1.0f, 1.0f, 0.0f, 0.0f, // top left }; static const unsigned int indices[] = {- 0, 1, 2, // First triangle- 2, 3, 0 // Second triangle+ 0, 1, 2, // first triangle+ 2, 3, 0 // second triangle }; -// Shader compilation helper static GLuint compile_shader(GLenum type, const char *source) { GLuint shader = glCreateShader(type); if (!shader) {@@ -66,7 +65,6 @@ static GLuint compile_shader(GLenum type, const char *source) { return shader; } -// Shader program creation helper static GLuint create_shader_program(void) { GLuint vertex_shader = compile_shader(GL_VERTEX_SHADER, vertex_shader_source); if (!vertex_shader) {@@ -108,6 +106,145 @@ static GLuint create_shader_program(void) { return program; } +// Initialize OpenGL resources for output+static int init_gl_resources(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",+ output->id);+ return CHROMA_ERROR_EGL;+ }++ // Create and setup VBO/EBO+ glGenBuffers(1, &output->vbo);+ glGenBuffers(1, &output->ebo);++ glBindBuffer(GL_ARRAY_BUFFER, output->vbo);+ glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);++ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, output->ebo);+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices,+ GL_STATIC_DRAW);++ output->texture_id = 0; // will be created when image is assigned+ output->gl_resources_initialized = true;++ chroma_log("DEBUG", "Initialized GL resources for output %u", output->id);+ return CHROMA_OK;+}++// Create or update texture from image data+static int update_texture_from_image(chroma_output_t *output,+ chroma_image_t *image) {+ if (!output || !image || !image->loaded) {+ return CHROMA_ERROR_INIT;+ }++ // If image data was already freed after previous GPU upload, we can't upload+ // again+ if (!image->data) {+ chroma_log("ERROR",+ "Cannot create texture: image data already freed for %s",+ image->path);+ return CHROMA_ERROR_IMAGE;+ }++ // Delete existing texture if it exists+ if (output->texture_id != 0) {+ glDeleteTextures(1, &output->texture_id);+ output->texture_id = 0;+ }++ // Create new texture+ glGenTextures(1, &output->texture_id);+ glBindTexture(GL_TEXTURE_2D, output->texture_id);++ // Set texture parameters+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);++ // 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);++ glBindTexture(GL_TEXTURE_2D, 0);++ // 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 * image->height * 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("DEBUG", "Updated texture for output %u (%dx%d)", output->id,+ image->width, image->height);+ return CHROMA_OK;+}++// Cleanup OpenGL resources for output+static void cleanup_gl_resources(chroma_output_t *output) {+ if (!output || !output->gl_resources_initialized) {+ return;+ }++ if (output->texture_id != 0) {+ glDeleteTextures(1, &output->texture_id);+ output->texture_id = 0;+ }++ if (output->shader_program != 0) {+ glDeleteProgram(output->shader_program);+ output->shader_program = 0;+ }++ if (output->vbo != 0) {+ glDeleteBuffers(1, &output->vbo);+ output->vbo = 0;+ }++ if (output->ebo != 0) {+ glDeleteBuffers(1, &output->ebo);+ output->ebo = 0;+ }++ output->gl_resources_initialized = false;+ chroma_log("DEBUG", "Cleaned up GL resources for output %u", output->id);+}+ // EGL configuration selection static int choose_egl_config(EGLDisplay display, EGLConfig *config) { EGLint attributes[] = {EGL_SURFACE_TYPE,@@ -292,6 +429,9 @@ void chroma_surface_destroy(chroma_output_t *output) { return; } + // Clean up OpenGL resources first+ cleanup_gl_resources(output);+ if (output->egl_surface != EGL_NO_SURFACE) { eglDestroySurface(eglGetCurrentDisplay(), output->egl_surface); output->egl_surface = EGL_NO_SURFACE;@@ -315,33 +455,7 @@ void chroma_surface_destroy(chroma_output_t *output) { chroma_log("DEBUG", "Destroyed surface for output %u", output->id); } -// Create texture from image data-static GLuint create_texture_from_image(chroma_image_t *image) {- if (!image || !image->loaded || !image->data) {- return 0;- }-- GLuint texture;- glGenTextures(1, &texture);- glBindTexture(GL_TEXTURE_2D, texture);-- // Set texture parameters- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);-- // Upload texture data- GLenum format = (image->channels == 4) ? GL_RGBA : GL_RGB;- glTexImage2D(GL_TEXTURE_2D, 0, format, image->width, image->height, 0, format,- GL_UNSIGNED_BYTE, image->data);-- glBindTexture(GL_TEXTURE_2D, 0);-- return texture;-}--// Render wallpaper to output+// Render wallpaper to output using cached resources int chroma_render_wallpaper(chroma_state_t *state, chroma_output_t *output) { if (!state || !output || !output->image || !output->image->loaded) { return CHROMA_ERROR_INIT;@@ -355,6 +469,17 @@ int chroma_render_wallpaper(chroma_state_t *state, chroma_output_t *output) { return CHROMA_ERROR_EGL; } + if (init_gl_resources(output) != CHROMA_OK) {+ return CHROMA_ERROR_EGL;+ }++ if (output->texture_id == 0) {+ if (update_texture_from_image(output, output->image) != CHROMA_OK) {+ chroma_log("ERROR", "Failed to update texture for output %u", output->id);+ return CHROMA_ERROR_EGL;+ }+ }+ // Set viewport glViewport(0, 0, output->width, output->height); @@ -362,42 +487,21 @@ int chroma_render_wallpaper(chroma_state_t *state, chroma_output_t *output) { glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); - // Create shader program (should be cached in real implementation)- GLuint program = create_shader_program();- if (!program) {- return CHROMA_ERROR_EGL;- }-- // Use shader program- glUseProgram(program);-- // Create and bind texture- GLuint texture = create_texture_from_image(output->image);- if (!texture) {- chroma_log("ERROR", "Failed to create texture for output %u", output->id);- glDeleteProgram(program);- return CHROMA_ERROR_EGL;- }+ // Use cached shader program+ glUseProgram(output->shader_program); + // Bind cached texture glActiveTexture(GL_TEXTURE0);- glBindTexture(GL_TEXTURE_2D, texture);- glUniform1i(glGetUniformLocation(program, "texture"), 0);-- // Create vertex buffer objects (should be cached in real implementation)- GLuint vbo, ebo;- glGenBuffers(1, &vbo);- glGenBuffers(1, &ebo);+ glBindTexture(GL_TEXTURE_2D, output->texture_id);+ glUniform1i(glGetUniformLocation(output->shader_program, "texture"), 0); - glBindBuffer(GL_ARRAY_BUFFER, vbo);- glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);-- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);- glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices,- GL_STATIC_DRAW);+ // Use cached VBO/EBO+ glBindBuffer(GL_ARRAY_BUFFER, output->vbo);+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, output->ebo); // Set vertex attributes- GLint position_attr = glGetAttribLocation(program, "position");- GLint texcoord_attr = glGetAttribLocation(program, "texcoord");+ GLint position_attr = glGetAttribLocation(output->shader_program, "position");+ GLint texcoord_attr = glGetAttribLocation(output->shader_program, "texcoord"); glEnableVertexAttribArray(position_attr); glVertexAttribPointer(position_attr, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float),@@ -410,11 +514,11 @@ int chroma_render_wallpaper(chroma_state_t *state, chroma_output_t *output) { // Draw glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); - // Cleanup- glDeleteBuffers(1, &vbo);- glDeleteBuffers(1, &ebo);- glDeleteTextures(1, &texture);- glDeleteProgram(program);+ // Unbind resources+ glBindBuffer(GL_ARRAY_BUFFER, 0);+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);+ glBindTexture(GL_TEXTURE_2D, 0);+ glUseProgram(0); // Swap buffers if (!eglSwapBuffers(state->egl_display, output->egl_surface)) {@@ -426,6 +530,21 @@ int chroma_render_wallpaper(chroma_state_t *state, chroma_output_t *output) { // Commit surface wl_surface_commit(output->surface); - chroma_log("DEBUG", "Rendered wallpaper to output %u", output->id);+ chroma_log("DEBUG", "Rendered wallpaper to output %u (cached resources)",+ output->id); return CHROMA_OK;-}\ No newline at end of file+}++// Invalidate texture cache for output+void chroma_output_invalidate_texture(chroma_output_t *output) {+ if (!output || !output->gl_resources_initialized) {+ return;+ }++ if (output->texture_id != 0) {+ glDeleteTextures(1, &output->texture_id);+ output->texture_id = 0;+ output->texture_uploaded = false; // reset upload flag+ chroma_log("DEBUG", "Invalidated texture cache for output %u", output->id);+ }+}