image: implement ref-counted image release
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: Idb30c621744eb9aa151fcaca012d93cc6a6a6964
This commit is contained in:
parent
26eda26620
commit
edae535674
4 changed files with 108 additions and 40 deletions
17
include/chroma.h
vendored
17
include/chroma.h
vendored
|
|
@ -4,7 +4,7 @@
|
|||
#include "wlr-layer-shell-unstable-v1.h"
|
||||
#include "xdg-shell.h"
|
||||
#include <EGL/egl.h>
|
||||
#include <GL/gl.h>
|
||||
#include <GLES2/gl2.h>
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
#define MAX_OUTPUTS 16
|
||||
#define MAX_PATH_LEN 4096
|
||||
#define CONFIG_FILE_NAME "chroma.conf"
|
||||
#define CONFIG_FILE_NAME "chroma.toml"
|
||||
|
||||
// Log levels
|
||||
#define CHROMA_LOG_ERROR 0
|
||||
|
|
@ -73,6 +73,7 @@ typedef struct {
|
|||
int channels;
|
||||
char path[MAX_PATH_LEN];
|
||||
bool loaded;
|
||||
int ref_count; // Number of outputs using this image
|
||||
} chroma_image_t;
|
||||
|
||||
// Wayland output information
|
||||
|
|
@ -116,6 +117,7 @@ typedef struct {
|
|||
bool gl_resources_initialized;
|
||||
bool texture_uploaded;
|
||||
bool vbo_dirty; // track VBO needs update
|
||||
bool configured; // track if initial configure received
|
||||
} chroma_output_t;
|
||||
|
||||
// Config mapping structure
|
||||
|
|
@ -162,6 +164,9 @@ typedef struct chroma_state {
|
|||
EGLDisplay egl_display;
|
||||
EGLContext egl_context;
|
||||
EGLConfig egl_config;
|
||||
|
||||
// Shared OpenGL resources
|
||||
GLuint shader_program;
|
||||
|
||||
// Outputs
|
||||
chroma_output_t outputs[MAX_OUTPUTS];
|
||||
|
|
@ -235,12 +240,15 @@ void chroma_layer_surface_closed(void *data,
|
|||
// Image loading
|
||||
void chroma_image_init_stb(void);
|
||||
int chroma_image_load(chroma_image_t *image, const char *path,
|
||||
const chroma_config_t *config);
|
||||
const chroma_config_t *config, int output_width,
|
||||
int output_height);
|
||||
void chroma_image_free(chroma_image_t *image);
|
||||
void chroma_image_release(chroma_image_t *image);
|
||||
chroma_image_t *chroma_image_find_by_path(chroma_state_t *state,
|
||||
const char *path);
|
||||
chroma_image_t *chroma_image_get_or_load(chroma_state_t *state,
|
||||
const char *path);
|
||||
const char *path, int output_width,
|
||||
int output_height);
|
||||
int chroma_image_validate(const char *path);
|
||||
int chroma_image_get_info(const char *path, int *width, int *height,
|
||||
int *channels);
|
||||
|
|
@ -248,6 +256,7 @@ void chroma_images_cleanup(chroma_state_t *state);
|
|||
|
||||
// Configuration
|
||||
int chroma_config_load(chroma_config_t *config, const char *config_file);
|
||||
int chroma_config_load_toml(chroma_config_t *config, const char *config_file);
|
||||
void chroma_config_free(chroma_config_t *config);
|
||||
const char *chroma_config_get_image_for_output(chroma_config_t *config,
|
||||
const char *output_name,
|
||||
|
|
|
|||
19
src/core.c
19
src/core.c
|
|
@ -85,8 +85,19 @@ static int assign_wallpaper_to_output(chroma_state_t *state,
|
|||
return CHROMA_ERROR_CONFIG;
|
||||
}
|
||||
|
||||
// Load or get cached image
|
||||
chroma_image_t *image = chroma_image_get_or_load(state, image_path);
|
||||
// Check if image path is empty (no default configured)
|
||||
if (strlen(image_path) == 0) {
|
||||
chroma_log("WARN",
|
||||
"No wallpaper image configured for output %u (%s). "
|
||||
"Set default_image in config or provide -c config.toml",
|
||||
output->id, output->name ? output->name : "unknown");
|
||||
return CHROMA_ERROR_CONFIG;
|
||||
}
|
||||
|
||||
// Load or get cached image with output dimensions for intelligent
|
||||
// downsampling
|
||||
chroma_image_t *image = chroma_image_get_or_load(
|
||||
state, image_path, output->width, output->height);
|
||||
if (!image) {
|
||||
chroma_log("ERROR", "Failed to load image for output %u: %s", output->id,
|
||||
image_path);
|
||||
|
|
@ -96,6 +107,7 @@ static int assign_wallpaper_to_output(chroma_state_t *state,
|
|||
// Check if image changed and invalidate texture cache if neceessary
|
||||
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",
|
||||
|
|
@ -161,7 +173,8 @@ static int assign_wallpaper_to_output(chroma_state_t *state,
|
|||
// Render wallpaper
|
||||
int ret = chroma_render_wallpaper(state, output);
|
||||
if (ret != CHROMA_OK) {
|
||||
chroma_log("ERROR", "Failed to render wallpaper for output %u", output->id);
|
||||
chroma_log("ERROR", "Failed to render wallpaper for output %u: %s", output->id,
|
||||
chroma_error_string(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
|||
101
src/image.c
101
src/image.c
|
|
@ -58,8 +58,9 @@ static void calculate_optimal_size(int original_width, int original_height,
|
|||
// being*. Must be revisited in the future to see how it stands as the program
|
||||
// evolves.
|
||||
static int downsample_image(unsigned char *src_data, int src_width,
|
||||
int src_height, unsigned char *dst_data,
|
||||
int dst_width, int dst_height) {
|
||||
int src_height, int src_channels,
|
||||
unsigned char *dst_data, int dst_width,
|
||||
int dst_height, int dst_channels) {
|
||||
if (!src_data || !dst_data) {
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -77,14 +78,18 @@ static int downsample_image(unsigned char *src_data, int src_width,
|
|||
src_x = (src_x >= src_width) ? src_width - 1 : src_x;
|
||||
src_y = (src_y >= src_height) ? src_height - 1 : src_y;
|
||||
|
||||
// Copy pixel (RGBA)
|
||||
int src_idx = (src_y * src_width + src_x) * 4;
|
||||
int dst_idx = (y * dst_width + x) * 4;
|
||||
// Copy pixel data
|
||||
int src_idx = (src_y * src_width + src_x) * src_channels;
|
||||
int dst_idx = (y * dst_width + x) * dst_channels;
|
||||
|
||||
dst_data[dst_idx + 0] = src_data[src_idx + 0]; // R
|
||||
dst_data[dst_idx + 1] = src_data[src_idx + 1]; // G
|
||||
dst_data[dst_idx + 2] = src_data[src_idx + 2]; // B
|
||||
dst_data[dst_idx + 3] = src_data[src_idx + 3]; // A
|
||||
if (dst_channels == 4 && src_channels == 4) {
|
||||
dst_data[dst_idx + 3] = src_data[src_idx + 3]; // A
|
||||
} else if (dst_channels == 4) {
|
||||
dst_data[dst_idx + 3] = 255; // Full alpha for RGB source
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -92,8 +97,11 @@ static int downsample_image(unsigned char *src_data, int src_width,
|
|||
}
|
||||
|
||||
// Load image from file with configurable downsampling
|
||||
// output_width/output_height: actual output dimensions for intelligent
|
||||
// downsampling
|
||||
int chroma_image_load(chroma_image_t *image, const char *path,
|
||||
const chroma_config_t *config) {
|
||||
const chroma_config_t *config, int output_width,
|
||||
int output_height) {
|
||||
if (!image || !path) {
|
||||
chroma_log("ERROR", "Invalid parameters for image loading");
|
||||
return CHROMA_ERROR_INIT;
|
||||
|
|
@ -117,12 +125,24 @@ int chroma_image_load(chroma_image_t *image, const char *path,
|
|||
(double)file_size / (1024.0 * 1024.0));
|
||||
}
|
||||
|
||||
// Load image data using stb_image, force RGBA format to avoid conversion
|
||||
// Load image data using stb_image
|
||||
// First, check actual channels to decide if we need alpha
|
||||
stbi_set_flip_vertically_on_load(0); // keep images right-side up
|
||||
|
||||
image->data =
|
||||
stbi_load(path, &image->width, &image->height, &image->channels, 4);
|
||||
image->channels = 4; // always RGBA after forced conversion
|
||||
int actual_channels = 0;
|
||||
if (!stbi_info(path, &image->width, &image->height, &actual_channels)) {
|
||||
chroma_log("ERROR", "Failed to get image info for %s: %s", path,
|
||||
stbi_failure_reason());
|
||||
return CHROMA_ERROR_IMAGE;
|
||||
}
|
||||
|
||||
// Load with actual channels or force RGBA if image has alpha
|
||||
// For wallpapers, we typically don't need alpha unless the image has it
|
||||
int desired_channels = (actual_channels == 4 || actual_channels == 2) ? 4 : 3;
|
||||
|
||||
image->data = stbi_load(path, &image->width, &image->height, &image->channels,
|
||||
desired_channels);
|
||||
image->channels = desired_channels;
|
||||
if (!image->data) {
|
||||
chroma_log("ERROR", "Failed to load image %s: %s", path,
|
||||
stbi_failure_reason());
|
||||
|
|
@ -137,13 +157,8 @@ int chroma_image_load(chroma_image_t *image, const char *path,
|
|||
return CHROMA_ERROR_IMAGE;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
chroma_log("DEBUG", "Loaded image %s with %d channels (original had %d)",
|
||||
path, image->channels, actual_channels);
|
||||
|
||||
// Store original dimensions before potential downsampling
|
||||
int original_width = image->width;
|
||||
|
|
@ -155,9 +170,14 @@ int chroma_image_load(chroma_image_t *image, const char *path,
|
|||
int optimal_height = original_height;
|
||||
|
||||
if (config && config->enable_downsampling) {
|
||||
calculate_optimal_size(original_width, original_height,
|
||||
config->max_output_width, config->max_output_height,
|
||||
&optimal_width, &optimal_height);
|
||||
// Use output dimensions if provided, otherwise fall back to config defaults
|
||||
int max_width =
|
||||
(output_width > 0) ? output_width : config->max_output_width;
|
||||
int max_height =
|
||||
(output_height > 0) ? output_height : config->max_output_height;
|
||||
|
||||
calculate_optimal_size(original_width, original_height, max_width,
|
||||
max_height, &optimal_width, &optimal_height);
|
||||
|
||||
// Apply minimum scale factor constraint
|
||||
float scale_x = (float)optimal_width / (float)original_width;
|
||||
|
|
@ -178,7 +198,7 @@ int chroma_image_load(chroma_image_t *image, const char *path,
|
|||
(optimal_width < original_width || optimal_height < original_height);
|
||||
}
|
||||
|
||||
// Downsamp if needed and enabled
|
||||
// Downsample if needed and enabled
|
||||
if (should_downsample) {
|
||||
double reduction_ratio = (double)(optimal_width * optimal_height) /
|
||||
(double)(original_width * original_height) * 100.0;
|
||||
|
|
@ -187,7 +207,8 @@ int chroma_image_load(chroma_image_t *image, const char *path,
|
|||
original_width, original_height, optimal_width, optimal_height,
|
||||
reduction_ratio);
|
||||
|
||||
size_t optimal_size = (size_t)optimal_width * (size_t)optimal_height * 4;
|
||||
size_t optimal_size =
|
||||
(size_t)optimal_width * (size_t)optimal_height * image->channels;
|
||||
unsigned char *downsampled_data = malloc(optimal_size);
|
||||
if (!downsampled_data) {
|
||||
chroma_log("ERROR", "Failed to allocate memory for downsampled image");
|
||||
|
|
@ -196,8 +217,8 @@ int chroma_image_load(chroma_image_t *image, const char *path,
|
|||
}
|
||||
|
||||
if (downsample_image(image->data, original_width, original_height,
|
||||
downsampled_data, optimal_width,
|
||||
optimal_height) != 0) {
|
||||
image->channels, downsampled_data, optimal_width,
|
||||
optimal_height, image->channels) != 0) {
|
||||
chroma_log("ERROR", "Failed to downsample image");
|
||||
free(downsampled_data);
|
||||
chroma_image_free(image);
|
||||
|
|
@ -218,6 +239,7 @@ int chroma_image_load(chroma_image_t *image, const char *path,
|
|||
}
|
||||
|
||||
image->loaded = true;
|
||||
image->ref_count = 1; // Initial reference from the first output
|
||||
|
||||
// Calculate and log memory allocation
|
||||
size_t image_size =
|
||||
|
|
@ -258,6 +280,7 @@ void chroma_image_free(chroma_image_t *image) {
|
|||
image->height = 0;
|
||||
image->channels = 0;
|
||||
image->loaded = false;
|
||||
image->ref_count = 0;
|
||||
|
||||
if (strlen(image->path) > 0) {
|
||||
chroma_log("DEBUG", "Freed image: %s", image->path);
|
||||
|
|
@ -266,6 +289,18 @@ void chroma_image_free(chroma_image_t *image) {
|
|||
memset(image->path, 0, sizeof(image->path));
|
||||
}
|
||||
|
||||
// Release a reference to an image; free when ref_count reaches zero
|
||||
void chroma_image_release(chroma_image_t *image) {
|
||||
if (!image) {
|
||||
return;
|
||||
}
|
||||
|
||||
image->ref_count--;
|
||||
if (image->ref_count <= 0) {
|
||||
chroma_image_free(image);
|
||||
}
|
||||
}
|
||||
|
||||
// Find image by path in state
|
||||
chroma_image_t *chroma_image_find_by_path(chroma_state_t *state,
|
||||
const char *path) {
|
||||
|
|
@ -283,8 +318,11 @@ chroma_image_t *chroma_image_find_by_path(chroma_state_t *state,
|
|||
}
|
||||
|
||||
// Load image if not already loaded
|
||||
// output_width/output_height: actual output dimensions for intelligent
|
||||
// downsampling
|
||||
chroma_image_t *chroma_image_get_or_load(chroma_state_t *state,
|
||||
const char *path) {
|
||||
const char *path, int output_width,
|
||||
int output_height) {
|
||||
if (!state || !path) {
|
||||
return NULL;
|
||||
}
|
||||
|
|
@ -292,7 +330,9 @@ chroma_image_t *chroma_image_get_or_load(chroma_state_t *state,
|
|||
// Check if already loaded
|
||||
chroma_image_t *existing = chroma_image_find_by_path(state, path);
|
||||
if (existing && existing->loaded) {
|
||||
chroma_log("DEBUG", "Using cached image: %s", path);
|
||||
chroma_log("DEBUG", "Using cached image: %s (ref_count: %d)", path,
|
||||
existing->ref_count);
|
||||
existing->ref_count++;
|
||||
return existing;
|
||||
}
|
||||
|
||||
|
|
@ -307,8 +347,9 @@ chroma_image_t *chroma_image_get_or_load(chroma_state_t *state,
|
|||
state->image_count++;
|
||||
}
|
||||
|
||||
// Load the image with configuration
|
||||
if (chroma_image_load(image, path, &state->config) != CHROMA_OK) {
|
||||
// Load the image with configuration and output dimensions
|
||||
if (chroma_image_load(image, path, &state->config, output_width,
|
||||
output_height) != CHROMA_OK) {
|
||||
// If this was a new slot, decrement count
|
||||
if (!existing) {
|
||||
state->image_count--;
|
||||
|
|
@ -376,7 +417,7 @@ void chroma_images_cleanup(chroma_state_t *state) {
|
|||
chroma_log("DEBUG", "Cleaning up %d images", state->image_count);
|
||||
|
||||
for (int i = 0; i < state->image_count; i++) {
|
||||
chroma_image_free(&state->images[i]);
|
||||
chroma_image_release(&state->images[i]);
|
||||
}
|
||||
|
||||
state->image_count = 0;
|
||||
|
|
|
|||
|
|
@ -91,9 +91,8 @@ static void layer_surface_configure(void *data,
|
|||
chroma_log("TRACE", "Sent configure acknowledgment for output %u serial %u",
|
||||
output->id, serial);
|
||||
|
||||
// Commit the surface to apply the acknowledgment
|
||||
wl_surface_commit(output->surface);
|
||||
chroma_log("TRACE", "Surface committed for output %u", output->id);
|
||||
// Mark as configured - actual commit happens in render via eglSwapBuffers
|
||||
output->configured = true;
|
||||
|
||||
chroma_log("DEBUG", "Acknowledged layer surface configure for output %u",
|
||||
output->id);
|
||||
|
|
@ -382,6 +381,12 @@ void chroma_output_remove(chroma_state_t *state, uint32_t id) {
|
|||
chroma_log("INFO", "Removing output %u (%s)", id,
|
||||
output->name ? output->name : "unknown");
|
||||
|
||||
// Release image reference if the output holds one
|
||||
if (output->image) {
|
||||
chroma_image_release(output->image);
|
||||
output->image = NULL;
|
||||
}
|
||||
|
||||
// Clean up surface if it exists
|
||||
if (output->surface) {
|
||||
chroma_surface_destroy(output);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue