brewery
notashelf /
5ba4407367ee95b5861d396a7e175731fd6b47bc

chroma

public

Lightweight wallpaper daemon for Wayland

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

Commit 5ba4407367ee

tarball

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

unverified6 files changed+323-739
render: replace EGL/OpenGL pipeline with `wl_shm` shared memory

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I15dbcca9d12b5c24ed8c82ecc375f4046a6a6964
diff --git a/Makefile b/Makefileindex c1bffd4..4b5af90 100644--- a/Makefile+++ b/Makefile@@ -34,7 +34,7 @@ DEBUG_CFLAGS += -D_GNU_SOURCE -DCHROMA_VERSION=\"$(VERSION)-debug\" DEBUG_CFLAGS += -fsanitize=address -fsanitize=undefined -fno-omit-frame-pointer  # Libraries using pkg-config-PKG_DEPS = wayland-client wayland-egl egl glesv2 wayland-protocols+PKG_DEPS = wayland-client wayland-protocols CFLAGS += $(shell pkg-config --cflags $(PKG_DEPS)) LDFLAGS += $(shell pkg-config --libs $(PKG_DEPS)) @@ -117,7 +117,7 @@ static: $(TARGET) # Check if required dependencies are available check-deps: 	@echo "Checking dependencies..."-	@pkg-config --exists $(PKG_DEPS) || (echo "Missing dependencies. Install: wayland-protocols libwayland-dev libegl1-mesa-dev libgl1-mesa-dev wayland-scanner" && exit 1)+	@pkg-config --exists $(PKG_DEPS) || (echo "Missing dependencies. Install: wayland-protocols libwayland-dev wayland-scanner" && exit 1) 	@which wayland-scanner >/dev/null || (echo "wayland-scanner not found. Please install wayland-scanner." && exit 1) 	@echo "All dependencies found." diff --git a/include/chroma.h b/include/chroma.hindex f8e2623..393c62b 100644--- a/include/chroma.h+++ b/include/chroma.h@@ -3,14 +3,11 @@  #include "wlr-layer-shell-unstable-v1.h" #include "xdg-shell.h"-#include <EGL/egl.h>-#include <GLES2/gl2.h> #include <signal.h> #include <stdbool.h> #include <stdint.h> #include <sys/types.h> #include <wayland-client.h>-#include <wayland-egl.h>  #include "chroma_version.h" @@ -73,6 +70,7 @@ typedef struct {   int channels;   char path[MAX_PATH_LEN];   bool loaded;+  bool data_from_stbi; // true if data was allocated by stbi_load   int ref_count; // Number of outputs using this image } chroma_image_t; @@ -94,10 +92,14 @@ typedef struct {   // Rendering context   struct wl_surface *surface;   struct zwlr_layer_surface_v1 *layer_surface;-  struct wl_egl_window *egl_window;-  EGLSurface egl_surface;   uint32_t configure_serial; +  // Shared memory buffer+  struct wl_buffer *shm_buffer;+  void *shm_data;+  size_t shm_size;+  int shm_stride;+   // Associated wallpaper   chroma_image_t *image; @@ -108,15 +110,6 @@ typedef struct {   float anchor_x; // custom X offset (0-100, 50=center)   float anchor_y; // custom Y offset (0-100, 50=center)   bool config_loaded;--  // OpenGL resource cache-  GLuint texture_id;-  GLuint shader_program;-  GLuint vbo;-  GLuint ebo;-  bool gl_resources_initialized;-  bool texture_uploaded;-  bool vbo_dirty; // track VBO needs update   bool configured; // track if initial configure received } chroma_output_t; @@ -159,14 +152,7 @@ typedef struct chroma_state {   struct wl_registry *registry;   struct wl_compositor *compositor;   struct zwlr_layer_shell_v1 *layer_shell;--  // EGL context-  EGLDisplay egl_display;-  EGLContext egl_context;-  EGLConfig egl_config;-  -  // Shared OpenGL resources-  GLuint shader_program;+  struct wl_shm *shm;    // Outputs   chroma_output_t outputs[MAX_OUTPUTS];@@ -221,13 +207,12 @@ void chroma_output_description(void *data, struct wl_output *output,                                const char *description); void chroma_output_done(void *data, struct wl_output *output); -// EGL and rendering-int chroma_egl_init(chroma_state_t *state);-void chroma_egl_cleanup(chroma_state_t *state);+// Rendering and surface management int chroma_surface_create(chroma_state_t *state, chroma_output_t *output); void chroma_surface_destroy(chroma_output_t *output);+int chroma_buffer_create(chroma_state_t *state, chroma_output_t *output);+void chroma_buffer_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,diff --git a/src/core.c b/src/core.cindex 9d499ba..6974ec4 100644--- a/src/core.c+++ b/src/core.c@@ -24,8 +24,6 @@ int chroma_init(chroma_state_t *state) {   // Set initial state   state->running = false;   state->initialized = false;-  state->egl_display = EGL_NO_DISPLAY;-  state->egl_context = EGL_NO_CONTEXT;    // Initialize stb_image   chroma_image_init_stb();@@ -53,9 +51,6 @@ void chroma_cleanup(chroma_state_t *state) {   // Clean up all images   chroma_images_cleanup(state); -  // Clean up EGL-  chroma_egl_cleanup(state);-   // Clean up Wayland   chroma_wayland_disconnect(state); @@ -104,24 +99,16 @@ static int assign_wallpaper_to_output(chroma_state_t *state,     return CHROMA_ERROR_IMAGE;   } -  // Check if image changed and invalidate texture cache if neceessary+  // Check if image changed   bool image_changed = (output->image != image);   if (image_changed && output->image) {     chroma_image_release(output->image);-    chroma_output_invalidate_texture(output);-    output->vbo_dirty = true; // VBO needs update for new image-    chroma_log("DEBUG", "Image changed for output %u, invalidated texture",-               output->id);+    chroma_log("DEBUG", "Image changed for output %u", output->id);   }    // Assign image to output   output->image = image; -  // Mark VBO as dirty if image changed-  if (image_changed) {-    output->vbo_dirty = true;-  }-   // Store old configuration values for comparison   chroma_scale_mode_t old_scale_mode = output->scale_mode;   chroma_filter_quality_t old_filter_quality = output->filter_quality;@@ -144,16 +131,14 @@ static int assign_wallpaper_to_output(chroma_state_t *state,                output->anchor, (double)output->anchor_x,                (double)output->anchor_y); -    // Check if configuration changed and invalidate texture if needed+    // Check if configuration changed     if (had_config &&         (old_scale_mode != output->scale_mode ||          old_filter_quality != output->filter_quality ||          old_anchor != output->anchor || old_anchor_x != output->anchor_x ||          old_anchor_y != output->anchor_y)) {-      chroma_output_invalidate_texture(output);-      output->vbo_dirty = true; // VBO needs update for new scale mode       chroma_log("DEBUG",-                 "Configuration changed for output %u, invalidated texture",+                 "Configuration changed for output %u",                  output->id);     }   } else {diff --git a/src/main.c b/src/main.cindex 9f1fe92..f2edbf8 100644--- a/src/main.c+++ b/src/main.c@@ -178,16 +178,6 @@ int main(int argc, char *argv[]) {   }   chroma_log_memory_stats("post-wayland-connect"); -  // Initialize EGL-  ret = chroma_egl_init(&state);-  if (ret != CHROMA_OK) {-    chroma_log("ERROR", "Failed to initialize EGL: %s",-               chroma_error_string(ret));-    chroma_cleanup(&state);-    return 1;-  }-  chroma_log_memory_stats("post-egl-init");-   chroma_log("INFO", "Chroma daemon initialized successfully");   chroma_log_memory_stats("pre-main-loop"); diff --git a/src/render.c b/src/render.cindex 732a1ae..626d693 100644--- a/src/render.c+++ b/src/render.c@@ -1,588 +1,309 @@+#include <errno.h>+#include <fcntl.h>+#include <math.h>+#include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h>--#include <EGL/egl.h>-#include <GLES2/gl2.h>+#include <sys/mman.h>+#include <sys/stat.h>+#include <unistd.h>  #include "../include/chroma.h" #include "../include/vendor/stb_image.h" -// Convert filter quality enum to OpenGL parameters-static void get_gl_filter_params(chroma_filter_quality_t quality,-                                 GLint *min_filter, GLint *mag_filter,-                                 bool *needs_mipmaps) {-  switch (quality) {-  case CHROMA_FILTER_NEAREST:-    *min_filter = GL_NEAREST;-    *mag_filter = GL_NEAREST;-    *needs_mipmaps = false;-    break;-  case CHROMA_FILTER_LINEAR:-    *min_filter = GL_LINEAR;-    *mag_filter = GL_LINEAR;-    *needs_mipmaps = false;-    break;-  case CHROMA_FILTER_BILINEAR:-    *min_filter = GL_LINEAR;-    *mag_filter = GL_LINEAR;-    *needs_mipmaps = false;-    break;-  case CHROMA_FILTER_TRILINEAR:-    *min_filter = GL_LINEAR_MIPMAP_LINEAR;-    *mag_filter = GL_LINEAR;-    *needs_mipmaps = true;-    break;-  default:-    *min_filter = GL_LINEAR;-    *mag_filter = GL_LINEAR;-    *needs_mipmaps = false;-    break;-  }-}--// Calculate texture coordinates based on scaling mode and anchor position-static void calculate_texture_coords(chroma_scale_mode_t scale_mode,-                                     float anchor_x, float anchor_y,-                                     int image_width, int image_height,-                                     int output_width, int output_height,-                                     float tex_coords[8]) {-  // Default texture coordinates (full texture)-  float u1 = 0.0f, v1 = 0.0f; // top-left-  float u2 = 1.0f, v2 = 1.0f; // bottom-right--  switch (scale_mode) {-  case CHROMA_SCALE_STRETCH:-    // Use full texture, stretch to fit-    u1 = 0.0f;-    v1 = 0.0f;-    u2 = 1.0f;-    v2 = 1.0f;-    break;--  case CHROMA_SCALE_CENTER:-    // Center image at original size-    // Calculate how much of the texture to show-    {-      float image_aspect = (float)image_width / (float)image_height;-      float output_aspect = (float)output_width / (float)output_height;--      if (image_aspect > output_aspect) {-        // Image is wider - fit width, show center portion vertically-        float visible_height = (float)image_width / output_aspect;-        float v_offset = ((float)image_height - visible_height) /-                         (2.0f * (float)image_height);-        u1 = 0.0f;-        v1 = v_offset;-        u2 = 1.0f;-        v2 = 1.0f - v_offset;-      } else {-        // Image is taller - fit height, show center portion horizontally-        float visible_width = (float)image_height * output_aspect;-        float u_offset =-            ((float)image_width - visible_width) / (2.0f * (float)image_width);-        u1 = u_offset;-        v1 = 0.0f;-        u2 = 1.0f - u_offset;-        v2 = 1.0f;-      }-    }-    break;--  case CHROMA_SCALE_FIT:-    // Fit image within output, maintaining aspect ratio-    {-      float image_aspect = (float)image_width / (float)image_height;-      float output_aspect = (float)output_width / (float)output_height;--      if (image_aspect > output_aspect) {-        // Image is wider - fit width, add borders top/bottom-        float scaled_height = (float)output_width / image_aspect;-        float v_border = ((float)output_height - scaled_height) /-                         (2.0f * (float)output_height);-        u1 = 0.0f;-        v1 = v_border;-        u2 = 1.0f;-        v2 = 1.0f - v_border;-      } else {-        // Image is taller - fit height, add borders left/right-        float scaled_width = (float)output_height * image_aspect;-        float u_border =-            ((float)output_width - scaled_width) / (2.0f * (float)output_width);-        u1 = u_border;-        v1 = 0.0f;-        u2 = 1.0f - u_border;-        v2 = 1.0f;-      }-    }-    break;--  case CHROMA_SCALE_FILL:-  default:-    // Fill entire output, crop if necessary-    {-      float image_aspect = (float)image_width / (float)image_height;-      float output_aspect = (float)output_width / (float)output_height;--      if (image_aspect > output_aspect) {-        // Image is wider - crop left/right-        float crop_width = (float)image_height * output_aspect;-        float u_crop =-            ((float)image_width - crop_width) / (2.0f * (float)image_width);-        u1 = u_crop;-        v1 = 0.0f;-        u2 = 1.0f - u_crop;-        v2 = 1.0f;-      } else {-        // Image is taller - crop top/bottom-        float crop_height = (float)image_width / output_aspect;-        float v_crop =-            ((float)image_height - crop_height) / (2.0f * (float)image_height);-        u1 = 0.0f;-        v1 = v_crop;-        u2 = 1.0f;-        v2 = 1.0f - v_crop;-      }-    }-    break;-  }--  // Apply anchor-based offset using anchor_x and anchor_y (0-100, 50=center)-  // anchor_x: 0=left edge, 50=center, 100=right edge-  // anchor_y: 0=top edge, 50=center, 100=bottom edge-  // u_shift: negative moves view left (shows more right side of image)-  // v_shift: negative moves view up (shows more top of image)-  float u_shift = (anchor_x - 50.0f) / 50.0f; // -1 to 1-  float v_shift = (50.0f - anchor_y) / 50.0f; // -1 to 1 (inverted for top-down)--  // Calculate the shift amount based on crop/border space available-  float u_crop = 1.0f - (u2 - u1);-  float v_crop = 1.0f - (v2 - v1);-  u1 += u_shift * u_crop;-  u2 += u_shift * u_crop;-  v1 += v_shift * v_crop;-  v2 += v_shift * v_crop;--  // Clamp to valid range [0, 1]-  if (u1 < 0.0f)-    u1 = 0.0f;-  if (u2 > 1.0f)-    u2 = 1.0f;-  if (v1 < 0.0f)-    v1 = 0.0f;-  if (v2 > 1.0f)-    v2 = 1.0f;--  // Set texture coordinates for quad (bottom-left, bottom-right, top-right,-  // top-left)-  tex_coords[0] = u1;-  tex_coords[1] = v2; // bottom-left-  tex_coords[2] = u2;-  tex_coords[3] = v2; // bottom-right-  tex_coords[4] = u2;-  tex_coords[5] = v1; // top-right-  tex_coords[6] = u1;-  tex_coords[7] = v1; // top-left-}--// Vertex shader for simple texture rendering-static const char *vertex_shader_source =-    "attribute vec2 position;\n"-    "attribute vec2 texcoord;\n"-    "varying vec2 v_texcoord;\n"-    "void main() {\n"-    "    gl_Position = vec4(position, 0.0, 1.0);\n"-    "    v_texcoord = texcoord;\n"-    "}\n";--// Fragment shader for simple texture rendering-static const char *fragment_shader_source =-    "precision mediump float;\n"-    "varying vec2 v_texcoord;\n"-    "uniform sampler2D texture;\n"-    "void main() {\n"-    "    gl_FragColor = texture2D(texture, v_texcoord);\n"-    "}\n";--// 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-};--static const unsigned short indices[] = {-    0, 1, 2, // first triangle-    2, 3, 0  // second triangle-};--static GLuint compile_shader(GLenum type, const char *source) {-  GLuint shader = glCreateShader(type);-  if (!shader) {-    chroma_log("ERROR", "Failed to create shader");-    return 0;-  }--  glShaderSource(shader, 1, &source, NULL);-  glCompileShader(shader);--  GLint status;-  glGetShaderiv(shader, GL_COMPILE_STATUS, &status);-  if (status != GL_TRUE) {-    char log[512];-    glGetShaderInfoLog(shader, sizeof(log), NULL, log);-    chroma_log("ERROR", "Shader compilation failed: %s", log);-    glDeleteShader(shader);-    return 0;-  }--  return shader;-}--static GLuint create_shader_program(void) {-  GLuint vertex_shader = compile_shader(GL_VERTEX_SHADER, vertex_shader_source);-  if (!vertex_shader) {-    return 0;-  }--  GLuint fragment_shader =-      compile_shader(GL_FRAGMENT_SHADER, fragment_shader_source);-  if (!fragment_shader) {-    glDeleteShader(vertex_shader);-    return 0;-  }--  GLuint program = glCreateProgram();-  if (!program) {-    chroma_log("ERROR", "Failed to create shader program");-    glDeleteShader(vertex_shader);-    glDeleteShader(fragment_shader);-    return 0;+// Destroy shared memory buffer for an output+void chroma_buffer_destroy(chroma_output_t *output) {+  if (!output) {+    return;   } -  glAttachShader(program, vertex_shader);-  glAttachShader(program, fragment_shader);-  glLinkProgram(program);--  GLint status;-  glGetProgramiv(program, GL_LINK_STATUS, &status);-  if (status != GL_TRUE) {-    char log[512];-    glGetProgramInfoLog(program, sizeof(log), NULL, log);-    chroma_log("ERROR", "Shader program linking failed: %s", log);-    glDeleteProgram(program);-    program = 0;+  if (output->shm_buffer) {+    wl_buffer_destroy(output->shm_buffer);+    output->shm_buffer = NULL;   } -  glDeleteShader(vertex_shader);-  glDeleteShader(fragment_shader);--  return program;-}--// Initialize OpenGL resources for output-static int init_gl_resources(chroma_state_t *state, chroma_output_t *output) {-  if (!output || output->gl_resources_initialized) {-    return CHROMA_OK;-  }--  // 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);-  } 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");+  if (output->shm_data) {+    if (munmap(output->shm_data, output->shm_size) < 0) {+      chroma_log("WARN", "munmap failed: %s", strerror(errno));     }+    output->shm_data = NULL;+    output->shm_size = 0;+    output->shm_stride = 0;   }--  // 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_DYNAMIC_DRAW);--  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, output->ebo);-  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;--  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,-                                     chroma_filter_quality_t filter_quality) {-  if (!output || !image || !image->loaded) {+// Create shared memory buffer for an output+int chroma_buffer_create(chroma_state_t *state, chroma_output_t *output) {+  if (!state || !output || !state->shm) {     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;+  int width = output->width;+  int height = output->height;+  if (width <= 0 || height <= 0) {+    return CHROMA_ERROR_INIT;   } -  // Delete existing texture if it exists-  if (output->texture_id != 0) {-    // Estimate texture size for logging-    // FIXME: Unfortunately this only works if we have previous image info.-    // Could this b made more accurate?-    if (output->image && output->image->loaded) {-      size_t texture_size = (size_t)output->image->width *-                            (size_t)output->image->height *-                            (size_t)output->image->channels;-      chroma_log_resource_deallocation("gpu_texture", texture_size,-                                       "texture replacement");+  int stride = width * 4;+  size_t size = (size_t)stride * height;++  int fd = memfd_create("chroma-shm", MFD_CLOEXEC | MFD_ALLOW_SEALING);+  if (fd < 0) {+    // Fallback to shm_open if memfd_create is unavailable+    char name[64];+    snprintf(name, sizeof(name), "/chroma-shm-%d-%d", getpid(), rand());+    fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);+    if (fd >= 0) {+      shm_unlink(name);     }-    glDeleteTextures(1, &output->texture_id);-    output->texture_id = 0;   }--  // Create new texture-  glGenTextures(1, &output->texture_id);-  glBindTexture(GL_TEXTURE_2D, output->texture_id);--  // Log GPU texture allocation-  size_t texture_size =-      (size_t)image->width * (size_t)image->height * (size_t)image->channels;-  chroma_log_resource_allocation("gpu_texture", texture_size, image->path);--  // 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);--  // Use configured filter quality-  GLint min_filter, mag_filter;-  bool needs_mipmaps;-  get_gl_filter_params(filter_quality, &min_filter, &mag_filter,-                       &needs_mipmaps);-  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter);-  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter);--  // 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;+  if (fd < 0) {+    chroma_log("ERROR", "Failed to create shared memory fd: %s",+               strerror(errno));+    return CHROMA_ERROR_MEMORY;   }-  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) {-    glGenerateMipmap(GL_TEXTURE_2D);-    chroma_log("DEBUG", "Generated mipmaps for trilinear filtering");+  if (ftruncate(fd, (off_t)size) < 0) {+    chroma_log("ERROR", "ftruncate failed: %s", strerror(errno));+    close(fd);+    return CHROMA_ERROR_MEMORY;   } -  glBindTexture(GL_TEXTURE_2D, 0);--  // Mark this output as having uploaded its texture-  output->texture_uploaded = true;--  // 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);+  void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);+  if (data == MAP_FAILED) {+    chroma_log("ERROR", "mmap failed: %s", strerror(errno));+    close(fd);+    return CHROMA_ERROR_MEMORY;   } -  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_state_t *state,-                                 chroma_output_t *output) {-  if (!output || !output->gl_resources_initialized) {-    return;-  }+  // Fill with black+  memset(data, 0, size); -  if (output->texture_id != 0) {-    chroma_log_resource_deallocation("gpu_texture", 0, "cleanup");-    glDeleteTextures(1, &output->texture_id);-    output->texture_id = 0;+  struct wl_shm_pool *pool =+      wl_shm_create_pool(state->shm, fd, (int32_t)size);+  if (!pool) {+    chroma_log("ERROR", "wl_shm_create_pool failed");+    munmap(data, size);+    close(fd);+    return CHROMA_ERROR_WAYLAND;   } -  // Don't delete shared shader program here, it's managed by state-  if (output->shader_program != 0) {-    // 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;-  }+  struct wl_buffer *buffer = wl_shm_pool_create_buffer(+      pool, 0, width, height, stride, WL_SHM_FORMAT_ARGB8888);+  wl_shm_pool_destroy(pool);+  close(fd); -  if (output->vbo != 0) {-    glDeleteBuffers(1, &output->vbo);-    output->vbo = 0;+  if (!buffer) {+    chroma_log("ERROR", "wl_shm_pool_create_buffer failed");+    munmap(data, size);+    return CHROMA_ERROR_WAYLAND;   } -  if (output->ebo != 0) {-    glDeleteBuffers(1, &output->ebo);-    output->ebo = 0;-  }+  output->shm_buffer = buffer;+  output->shm_data = data;+  output->shm_size = size;+  output->shm_stride = stride; -  output->gl_resources_initialized = false;-  chroma_log("DEBUG", "Cleaned up GL resources for output %u", output->id);+  return CHROMA_OK; } -// EGL configuration selection-static int choose_egl_config(EGLDisplay display, EGLConfig *config) {-  EGLint attributes[] = {EGL_SURFACE_TYPE,-                         EGL_WINDOW_BIT,-                         EGL_RED_SIZE,-                         8,-                         EGL_GREEN_SIZE,-                         8,-                         EGL_BLUE_SIZE,-                         8,-                         EGL_ALPHA_SIZE,-                         0,-                         EGL_RENDERABLE_TYPE,-                         EGL_OPENGL_ES2_BIT,-                         EGL_NONE};--  EGLint num_configs;-  if (!eglChooseConfig(display, attributes, config, 1, &num_configs)) {-    chroma_log("ERROR", "Failed to choose EGL config: 0x%04x", eglGetError());-    return -1;+// Sample a pixel from image data (grayscale, grayscale+alpha, RGB, or RGBA)+static inline uint32_t sample_pixel(const chroma_image_t *image, int x,+                                    int y) {+  if (!image || !image->data || x < 0 || y < 0 || x >= image->width ||+      y >= image->height) {+    return 0xFF000000; // black with full alpha   } -  if (num_configs == 0) {-    chroma_log("ERROR", "No suitable EGL configs found");-    return -1;+  int idx = (y * image->width + x) * image->channels;+  unsigned char r, g, b, a;++  switch (image->channels) {+  case 1:+    r = g = b = image->data[idx];+    a = 0xFF;+    break;+  case 2:+    r = g = b = image->data[idx];+    a = image->data[idx + 1];+    break;+  case 3:+    r = image->data[idx];+    g = image->data[idx + 1];+    b = image->data[idx + 2];+    a = 0xFF;+    break;+  default: // 4 or more+    r = image->data[idx];+    g = image->data[idx + 1];+    b = image->data[idx + 2];+    a = image->data[idx + 3];+    break;   } -  return 0;+  return ((uint32_t)a << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) |+         (uint32_t)b; } -// EGL initialization-int chroma_egl_init(chroma_state_t *state) {-  if (!state || !state->display) {-    return CHROMA_ERROR_INIT;-  }--  // Get EGL display-  state->egl_display = eglGetDisplay((EGLNativeDisplayType)state->display);-  if (state->egl_display == EGL_NO_DISPLAY) {-    chroma_log("ERROR", "Failed to get EGL display: 0x%04x", eglGetError());-    return CHROMA_ERROR_EGL;+// Draw scaled image into the shared memory buffer+static void draw_scaled_image(chroma_output_t *output,+                              chroma_image_t *image) {+  if (!output || !image || !output->shm_data) {+    return;   } -  // Initialize EGL-  EGLint major, minor;-  if (!eglInitialize(state->egl_display, &major, &minor)) {-    chroma_log("ERROR", "Failed to initialize EGL: 0x%04x", eglGetError());-    return CHROMA_ERROR_EGL;-  }+  int out_w = output->width;+  int out_h = output->height;+  int img_w = image->width;+  int img_h = image->height;+  uint32_t *dst = (uint32_t *)output->shm_data; -  chroma_log("INFO", "EGL initialized: version %d.%d", major, minor);-  chroma_log_memory_stats("post-egl-init");+  // Pre-fill with black+  memset(output->shm_data, 0, output->shm_size); -  // 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;-  }+  float anchor_x = output->anchor_x;+  float anchor_y = output->anchor_y; -  // Choose EGL config-  if (choose_egl_config(state->egl_display, &state->egl_config) != 0) {-    chroma_egl_cleanup(state);-    return CHROMA_ERROR_EGL;+  switch (output->scale_mode) {+  case CHROMA_SCALE_STRETCH: {+    for (int y = 0; y < out_h; y++) {+      for (int x = 0; x < out_w; x++) {+        int sx = (int)((float)x * img_w / out_w);+        int sy = (int)((float)y * img_h / out_h);+        sx = (sx < 0) ? 0 : (sx >= img_w) ? img_w - 1 : sx;+        sy = (sy < 0) ? 0 : (sy >= img_h) ? img_h - 1 : sy;+        dst[y * out_w + x] = sample_pixel(image, sx, sy);+      }+    }+    break;   } -  // Create EGL context for GLES 2.0-  EGLint context_attributes[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};+  case CHROMA_SCALE_FILL: {+    float img_aspect = (float)img_w / (float)img_h;+    float out_aspect = (float)out_w / (float)out_h; -  state->egl_context = eglCreateContext(state->egl_display, state->egl_config,-                                        EGL_NO_CONTEXT, context_attributes);-  if (state->egl_context == EGL_NO_CONTEXT) {-    chroma_log("ERROR", "Failed to create EGL context: 0x%04x", eglGetError());-    chroma_egl_cleanup(state);-    return CHROMA_ERROR_EGL;-  }+    float crop_w, crop_h, crop_x, crop_y; -  chroma_log("INFO", "EGL context created successfully");-  return CHROMA_OK;-}+    if (img_aspect > out_aspect) {+      // Image is wider - crop left/right+      crop_h = (float)img_h;+      crop_w = crop_h * out_aspect;+    } else {+      // Image is taller - crop top/bottom+      crop_w = (float)img_w;+      crop_h = crop_w / out_aspect;+    } -// EGL cleanup-void chroma_egl_cleanup(chroma_state_t *state) {-  if (!state) {-    return;+    crop_x = (anchor_x / 100.0f) * ((float)img_w - crop_w);+    crop_y = (anchor_y / 100.0f) * ((float)img_h - crop_h);++    // Clamp crop region+    if (crop_x < 0)+      crop_x = 0;+    if (crop_y < 0)+      crop_y = 0;+    if (crop_x > (float)img_w - crop_w)+      crop_x = (float)img_w - crop_w;+    if (crop_y > (float)img_h - crop_h)+      crop_y = (float)img_h - crop_h;++    for (int y = 0; y < out_h; y++) {+      for (int x = 0; x < out_w; x++) {+        float sx = crop_x + (float)x * crop_w / (float)out_w;+        float sy = crop_y + (float)y * crop_h / (float)out_h;+        int ix = (int)sx;+        int iy = (int)sy;+        dst[y * out_w + x] = sample_pixel(image, ix, iy);+      }+    }+    break;   } -  // Clean up shared shader program while context is still current-  if (state->shader_program != 0) {-    glDeleteProgram(state->shader_program);-    state->shader_program = 0;+  case CHROMA_SCALE_FIT: {+    float scale_x = (float)out_w / (float)img_w;+    float scale_y = (float)out_h / (float)img_h;+    float scale = (scale_x < scale_y) ? scale_x : scale_y;++    float scaled_w = (float)img_w * scale;+    float scaled_h = (float)img_h * scale;++    float offset_x = (anchor_x / 100.0f) * ((float)out_w - scaled_w);+    float offset_y = (anchor_y / 100.0f) * ((float)out_h - scaled_h);++    int start_x = (int)offset_x;+    int start_y = (int)offset_y;+    int end_x = (int)(offset_x + scaled_w + 0.5f);+    int end_y = (int)(offset_y + scaled_h + 0.5f);++    if (start_x < 0)+      start_x = 0;+    if (start_y < 0)+      start_y = 0;+    if (end_x > out_w)+      end_x = out_w;+    if (end_y > out_h)+      end_y = out_h;++    for (int y = start_y; y < end_y; y++) {+      for (int x = start_x; x < end_x; x++) {+        float sx = ((float)x - offset_x) / scale;+        float sy = ((float)y - offset_y) / scale;+        int ix = (int)sx;+        int iy = (int)sy;+        ix = (ix < 0) ? 0 : (ix >= img_w) ? img_w - 1 : ix;+        iy = (iy < 0) ? 0 : (iy >= img_h) ? img_h - 1 : iy;+        dst[y * out_w + x] = sample_pixel(image, ix, iy);+      }+    }+    break;   } -  if (state->egl_display != EGL_NO_DISPLAY) {-    eglMakeCurrent(state->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,-                   EGL_NO_CONTEXT);--    if (state->egl_context != EGL_NO_CONTEXT) {-      eglDestroyContext(state->egl_display, state->egl_context);-      state->egl_context = EGL_NO_CONTEXT;+  case CHROMA_SCALE_CENTER:+  default: {+    if (img_w <= out_w && img_h <= out_h) {+      // Image fits entirely - position with anchor+      float offset_x =+          (anchor_x / 100.0f) * ((float)out_w - (float)img_w);+      float offset_y =+          (anchor_y / 100.0f) * ((float)out_h - (float)img_h);++      for (int y = 0; y < img_h; y++) {+        for (int x = 0; x < img_w; x++) {+          int dx = (int)(offset_x + x);+          int dy = (int)(offset_y + y);+          if (dx >= 0 && dx < out_w && dy >= 0 && dy < out_h) {+            dst[dy * out_w + dx] = sample_pixel(image, x, y);+          }+        }+      }+    } else {+      // Image is larger than output - clip with anchor+      float src_offset_x =+          (anchor_x / 100.0f) * ((float)img_w - (float)out_w);+      float src_offset_y =+          (anchor_y / 100.0f) * ((float)img_h - (float)out_h);++      if (src_offset_x < 0)+        src_offset_x = 0;+      if (src_offset_y < 0)+        src_offset_y = 0;+      if (src_offset_x > (float)img_w - (float)out_w)+        src_offset_x = (float)img_w - (float)out_w;+      if (src_offset_y > (float)img_h - (float)out_h)+        src_offset_y = (float)img_h - (float)out_h;++      for (int y = 0; y < out_h; y++) {+        for (int x = 0; x < out_w; x++) {+          int sx = (int)(src_offset_x + x);+          int sy = (int)(src_offset_y + y);+          dst[y * out_w + x] = sample_pixel(image, sx, sy);+        }+      }     }--    eglTerminate(state->egl_display);-    state->egl_display = EGL_NO_DISPLAY;+    break;+  }   }--  chroma_log("INFO", "EGL cleaned up"); }  // Create surface for output@@ -612,16 +333,19 @@ int chroma_surface_create(chroma_state_t *state, chroma_output_t *output) {   }    // Configure layer surface-  zwlr_layer_surface_v1_set_size(output->layer_surface, (uint32_t)output->width,+  zwlr_layer_surface_v1_set_size(output->layer_surface,+                                 (uint32_t)output->width,                                  (uint32_t)output->height);-  zwlr_layer_surface_v1_set_anchor(output->layer_surface,-                                   ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP |-                                       ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT |-                                       ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM |-                                       ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT);+  zwlr_layer_surface_v1_set_anchor(+      output->layer_surface,+      ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP |+          ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT |+          ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM |+          ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT);   zwlr_layer_surface_v1_set_exclusive_zone(output->layer_surface, -1);   zwlr_layer_surface_v1_set_keyboard_interactivity(-      output->layer_surface, ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE);+      output->layer_surface,+      ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE);    // Add layer surface listener   zwlr_layer_surface_v1_add_listener(@@ -633,34 +357,9 @@ int chroma_surface_create(chroma_state_t *state, chroma_output_t *output) {   // Wait for configure event   wl_display_roundtrip(state->display); -  // Create EGL window-  output->egl_window =-      wl_egl_window_create(output->surface, output->width, output->height);-  if (!output->egl_window) {-    chroma_log("ERROR", "Failed to create EGL window for output %u",-               output->id);-    chroma_surface_destroy(output);-    return CHROMA_ERROR_EGL;-  }--  // Create EGL surface-  output->egl_surface =-      eglCreateWindowSurface(state->egl_display, state->egl_config,-                             (EGLNativeWindowType)output->egl_window, NULL);-  if (output->egl_surface == EGL_NO_SURFACE) {-    chroma_log("ERROR", "Failed to create EGL surface for output %u: 0x%04x",-               output->id, eglGetError());-    chroma_surface_destroy(output);-    return CHROMA_ERROR_EGL;-  }-   chroma_log("INFO", "Created surface for output %u (%dx%d)", output->id,              output->width, output->height); -  // Log surface creation resource allocation-  size_t surface_size = (size_t)output->width * (size_t)output->height * 4;-  chroma_log_resource_allocation("egl_surface", surface_size, "output surface");-   return CHROMA_OK; } @@ -670,18 +369,7 @@ void chroma_surface_destroy(chroma_output_t *output) {     return;   } -  // 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);-    output->egl_surface = EGL_NO_SURFACE;-  }--  if (output->egl_window) {-    wl_egl_window_destroy(output->egl_window);-    output->egl_window = NULL;-  }+  chroma_buffer_destroy(output);    if (output->layer_surface) {     zwlr_layer_surface_v1_destroy(output->layer_surface);@@ -695,134 +383,51 @@ void chroma_surface_destroy(chroma_output_t *output) {    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,-                                   "output surface cleanup");-   chroma_log("DEBUG", "Destroyed surface for output %u", output->id); } -// Render wallpaper to output using cached resources+// Render wallpaper to output int chroma_render_wallpaper(chroma_state_t *state, chroma_output_t *output) {   if (!state || !output || !output->image || !output->image->loaded) {     return CHROMA_ERROR_INIT;   } -  // Make context current-  if (!eglMakeCurrent(state->egl_display, output->egl_surface,-                      output->egl_surface, state->egl_context)) {-    chroma_log("ERROR", "Failed to make EGL context current: 0x%04x",-               eglGetError());-    return CHROMA_ERROR_EGL;-  }--  if (init_gl_resources(state, output) != CHROMA_OK) {-    return CHROMA_ERROR_EGL;+  if (!output->configured) {+    chroma_log("WARN", "Output %u not yet configured, skipping render",+               output->id);+    return CHROMA_ERROR_INIT;   } -  if (output->texture_id == 0) {-    if (update_texture_from_image(output, output->image,-                                  output->filter_quality) != CHROMA_OK) {-      chroma_log("ERROR", "Failed to update texture for output %u", output->id);-      return CHROMA_ERROR_EGL;-    }-  }+  // Clean up old buffer+  chroma_buffer_destroy(output); -  // Set viewport-  glViewport(0, 0, output->width, output->height);--  // Clear screen-  glClearColor(0.0f, 0.0f, 0.0f, 1.0f);-  glClear(GL_COLOR_BUFFER_BIT);--  // Use cached shader program-  glUseProgram(output->shader_program);--  // Bind cached texture-  glActiveTexture(GL_TEXTURE0);-  glBindTexture(GL_TEXTURE_2D, output->texture_id);-  glUniform1i(glGetUniformLocation(output->shader_program, "texture"), 0);--  // Update VBO only if needed. E.g, image changed, scale mode changed, or first-  // render-  if (output->vbo_dirty) {-    // Calculate texture coordinates based on scaling mode, anchor, and anchor-    // coords-    float tex_coords[8];-    calculate_texture_coords(output->scale_mode, output->anchor_x,-                             output->anchor_y, output->image->width,-                             output->image->height, output->width,-                             output->height, tex_coords);--    // Create dynamic vertex data with calculated texture coordinates-    float dynamic_vertices[] = {-        // Position  Texcoord-        -1.0f, -1.0f, tex_coords[0], tex_coords[1], // bottom-left-        1.0f,  -1.0f, tex_coords[2], tex_coords[3], // bottom-right-        1.0f,  1.0f,  tex_coords[4], tex_coords[5], // top-right-        -1.0f, 1.0f,  tex_coords[6], tex_coords[7]  // top-left-    };--    // Update VBO with dynamic texture coordinates-    glBindBuffer(GL_ARRAY_BUFFER, output->vbo);-    glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(dynamic_vertices),-                    dynamic_vertices);-    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, output->ebo);--    output->vbo_dirty = false; // mark VBO as up to date-  } else {-    // Just bind the existing buffers-    glBindBuffer(GL_ARRAY_BUFFER, output->vbo);-    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, output->ebo);+  // Create new shared memory buffer+  int ret = chroma_buffer_create(state, output);+  if (ret != CHROMA_OK) {+    chroma_log("ERROR", "Failed to create shm buffer for output %u",+               output->id);+    return ret;   } -  // Set vertex attributes-  GLint position_attr = glGetAttribLocation(output->shader_program, "position");-  GLint texcoord_attr = glGetAttribLocation(output->shader_program, "texcoord");--  glEnableVertexAttribArray((GLuint)position_attr);-  glVertexAttribPointer((GLuint)position_attr, 2, GL_FLOAT, GL_FALSE,-                        4 * sizeof(float), (void *)0);+  // Draw scaled image into buffer+  draw_scaled_image(output, output->image); -  glEnableVertexAttribArray((GLuint)texcoord_attr);-  glVertexAttribPointer((GLuint)texcoord_attr, 2, GL_FLOAT, GL_FALSE,-                        4 * sizeof(float), (void *)(2 * sizeof(float)));--  // Draw-  glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0);+  // Attach buffer to surface+  wl_surface_attach(output->surface, output->shm_buffer, 0, 0);+  wl_surface_damage_buffer(output->surface, 0, 0, output->width,+                           output->height);+  wl_surface_commit(output->surface);+  wl_display_flush(state->display); -  // Unbind resources-  glBindBuffer(GL_ARRAY_BUFFER, 0);-  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);-  glBindTexture(GL_TEXTURE_2D, 0);-  glUseProgram(0);+  chroma_log("INFO", "Rendered wallpaper to output %u (%dx%d)", output->id,+             output->width, output->height); -  // 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;+  // Release image reference and free CPU data if no longer needed+  if (output->image) {+    chroma_image_t *image = output->image;+    chroma_image_release(image);+    output->image = NULL;   } -  chroma_log("DEBUG", "Rendered wallpaper to output %u (cached resources)",-             output->id);   return CHROMA_OK; }--// 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);-  }--  // Mark VBO as dirty since texture coordinates may need recalculation-  output->vbo_dirty = true;-}diff --git a/src/wayland.c b/src/wayland.cindex c5fa252..88cdf32 100644--- a/src/wayland.c+++ b/src/wayland.c@@ -37,6 +37,13 @@ static void registry_global(void *data, struct wl_registry *registry,           "TRACE",           "wlr-layer-shell protocol available, wallpaper support enabled");     }+  } else if (strcmp(interface, wl_shm_interface.name) == 0) {+    state->shm = wl_registry_bind(registry, id, &wl_shm_interface, 1);+    if (!state->shm) {+      chroma_log("ERROR", "Failed to bind shm");+    } else {+      chroma_log("INFO", "Bound shm (version %u)", version);+    }   } else if (strcmp(interface, wl_output_interface.name) == 0) {     struct wl_output *output = wl_registry_bind(         registry, id, &wl_output_interface, version < 4 ? version : 4);@@ -91,7 +98,7 @@ static void layer_surface_configure(void *data,   chroma_log("TRACE", "Sent configure acknowledgment for output %u serial %u",              output->id, serial); -  // Mark as configured - actual commit happens in render via eglSwapBuffers+  // Mark as configured - actual commit happens in render via wl_surface_commit   output->configured = true;    chroma_log("DEBUG", "Acknowledged layer surface configure for output %u",@@ -284,6 +291,13 @@ int chroma_wayland_connect(chroma_state_t *state) {     return CHROMA_ERROR_WAYLAND;   } +  // Check if we got shm+  if (!state->shm) {+    chroma_log("ERROR", "No shm available");+    chroma_wayland_disconnect(state);+    return CHROMA_ERROR_WAYLAND;+  }+   chroma_log("INFO", "Connected to Wayland display, found %d outputs",              state->output_count);   return CHROMA_OK;@@ -316,6 +330,11 @@ void chroma_wayland_disconnect(chroma_state_t *state) {     state->layer_shell = NULL;   } +  if (state->shm) {+    wl_shm_destroy(state->shm);+    state->shm = NULL;+  }+   if (state->compositor) {     wl_compositor_destroy(state->compositor);     state->compositor = NULL;