Compare commits
12 commits
d16a861e52
...
55012e16f9
| Author | SHA1 | Date | |
|---|---|---|---|
|
55012e16f9 |
|||
|
d402e6e300 |
|||
|
7ccb21af79 |
|||
|
5a1332080d |
|||
|
d145d88b7e |
|||
|
e5931e3910 |
|||
|
3d4974a128 |
|||
|
46ea940242 |
|||
|
ec628eb1af |
|||
|
5bfae35738 |
|||
|
746e83d3da |
|||
|
c9d32e14ab |
14 changed files with 2154 additions and 91 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -8,6 +8,7 @@ src/xdg-shell.c
|
||||||
src/wlr-layer-shell-unstable-v1.c
|
src/wlr-layer-shell-unstable-v1.c
|
||||||
|
|
||||||
# Ignore test stuff that I create to... test stuff.
|
# Ignore test stuff that I create to... test stuff.
|
||||||
test_*
|
test_memory
|
||||||
|
test_config
|
||||||
*.jpg
|
*.jpg
|
||||||
*.conf
|
*.conf
|
||||||
|
|
|
||||||
2
Makefile
2
Makefile
|
|
@ -116,7 +116,9 @@ version-header:
|
||||||
@echo "#ifndef CHROMA_VERSION_H" > $(INCDIR)/chroma_version.h
|
@echo "#ifndef CHROMA_VERSION_H" > $(INCDIR)/chroma_version.h
|
||||||
@echo "#define CHROMA_VERSION_H" >> $(INCDIR)/chroma_version.h
|
@echo "#define CHROMA_VERSION_H" >> $(INCDIR)/chroma_version.h
|
||||||
@echo "" >> $(INCDIR)/chroma_version.h
|
@echo "" >> $(INCDIR)/chroma_version.h
|
||||||
|
@echo "#ifndef CHROMA_VERSION" >> $(INCDIR)/chroma_version.h
|
||||||
@echo "#define CHROMA_VERSION \"$(VERSION)\"" >> $(INCDIR)/chroma_version.h
|
@echo "#define CHROMA_VERSION \"$(VERSION)\"" >> $(INCDIR)/chroma_version.h
|
||||||
|
@echo "#endif" >> $(INCDIR)/chroma_version.h
|
||||||
@echo "" >> $(INCDIR)/chroma_version.h
|
@echo "" >> $(INCDIR)/chroma_version.h
|
||||||
@echo "#endif // CHROMA_VERSION_H" >> $(INCDIR)/chroma_version.h
|
@echo "#endif // CHROMA_VERSION_H" >> $(INCDIR)/chroma_version.h
|
||||||
|
|
||||||
|
|
|
||||||
37
README.md
37
README.md
|
|
@ -1,24 +1,39 @@
|
||||||
# Chroma
|
# Chroma
|
||||||
|
|
||||||
A simple, lightweight and efficientt wallpaper daeemon for Wayland compositors
|
Super-fast, lightweight and efficient wallpaper daemon for Wayland compositors
|
||||||
with multi-monitor & automatic hotplugging support. Born from my woes with
|
with multi-monitor & hotplugging support. Born from my woes with Hyprpaper,
|
||||||
Hyprpaper, swaybg and most ironically SWWW, which turned out to be NOT a
|
swaybg and most ironically _SWWW_ (now called AWWW for some awful reason), which
|
||||||
solution to my Wayland wallpaper woes.
|
turned out to be NOT a solution to my Wayland wallpaper woes. Smaller, faster,
|
||||||
|
cleaner and somehow still more feature-rich...
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
I did not expect to be writing something too feature-rich, but I still ended up
|
Chroma combines simplicity with powerful performance optimizations and
|
||||||
with something relatively convoluted. Chroma (mostly) reliably supports:
|
comprehensive monitor management. Here's what makes Chroma stand out:
|
||||||
|
|
||||||
- **Multi-monitors**: Automatically detects and manages wallpapers for all
|
### Core Functionality
|
||||||
connected displays
|
|
||||||
|
- **Memory-efficient**: Smart caching and resource management
|
||||||
|
- **Optimized rendering**: VBO dirty flagging and reduced GPU overhead
|
||||||
|
- **Multi-monitor support**: Automatically detects and manages wallpapers for
|
||||||
|
all connected displays
|
||||||
|
- **Configurable scaling modes**: Fill, fit, stretch, and center options per
|
||||||
|
monitor
|
||||||
- **Hotplugging**: Dynamically handles monitor connection/disconnection events
|
- **Hotplugging**: Dynamically handles monitor connection/disconnection events
|
||||||
- **Per-output configuration**: Set different wallpapers for each monitor
|
- **Per-output configuration**: Set different wallpapers and settings for each
|
||||||
|
monitor
|
||||||
|
- **Global and per-output settings**: Override defaults for specific monitors
|
||||||
|
- **Performance tuning**: Adjustable downsampling limits and quality
|
||||||
|
thresholds
|
||||||
- **Multiple image formats**: Supports JPEG, PNG, BMP, TGA, PSD, GIF, HDR, PIC,
|
- **Multiple image formats**: Supports JPEG, PNG, BMP, TGA, PSD, GIF, HDR, PIC,
|
||||||
PPM, PGM
|
PPM, PGM
|
||||||
- **EGL/OpenGL rendering**: Hardware-accelerated wallpaper rendering
|
- **EGL/OpenGL rendering**: Hardware-accelerated wallpaper rendering
|
||||||
- **Configuration file support**: Easy setup with INI-style config files
|
- **Configuration file support**: Easy setup with INI-style config files
|
||||||
- **Signal handling**: Graceful shutdown and configuration reload (SIGHUP)
|
- **Signal handling**: Graceful shutdown and configuration reload (SIGHUP)
|
||||||
|
- **Intelligent downsampling**: Automatically reduces large image resolution to
|
||||||
|
save memory (up to 94% memory savings for 8K images)
|
||||||
|
- **Advanced filtering**: Nearest, linear, bilinear, and trilinear filtering
|
||||||
|
options
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
|
@ -198,7 +213,7 @@ make analyze # requires cppcheck
|
||||||
|
|
||||||
- C11 standard
|
- C11 standard
|
||||||
- 2-space indentation
|
- 2-space indentation
|
||||||
- No tabs
|
- No tabs (except for the Makefile, obviously)
|
||||||
- Function names: `chroma_function_name`
|
- Function names: `chroma_function_name`
|
||||||
- Constants: `CHROMA_CONSTANT_NAME`
|
- Constants: `CHROMA_CONSTANT_NAME`
|
||||||
|
|
||||||
|
|
@ -209,3 +224,5 @@ make analyze # requires cppcheck
|
||||||
This project is made available under Mozilla Public License (MPL) version 2.0.
|
This project is made available under Mozilla Public License (MPL) version 2.0.
|
||||||
See [LICENSE](LICENSE) for more details on the exact conditions. An online copy
|
See [LICENSE](LICENSE) for more details on the exact conditions. An online copy
|
||||||
is provided [here](https://www.mozilla.org/en-US/MPL/2.0/).
|
is provided [here](https://www.mozilla.org/en-US/MPL/2.0/).
|
||||||
|
|
||||||
|
<!--markdownlint-enable MD059 -->
|
||||||
|
|
|
||||||
|
|
@ -27,15 +27,59 @@ default_image = ~/.config/chroma/default.jpg
|
||||||
# Usually set via command line option --daemon, but can be set here too
|
# Usually set via command line option --daemon, but can be set here too
|
||||||
daemon_mode = false
|
daemon_mode = false
|
||||||
|
|
||||||
|
# Global scaling mode for wallpapers (used as default for all outputs)
|
||||||
|
# Options: fill, fit, stretch, center
|
||||||
|
# fill - Fill entire output, crop if necessary (default)
|
||||||
|
# fit - Fit image within output, add borders if needed
|
||||||
|
# stretch - Stretch to fill output, may distort aspect ratio
|
||||||
|
# center - Center image at original size
|
||||||
|
scale_mode = fill
|
||||||
|
|
||||||
|
# Global image filtering quality (used as default for all outputs)
|
||||||
|
# Options: nearest, linear, bilinear, trilinear
|
||||||
|
# nearest - Nearest neighbor filtering (pixelated, fast)
|
||||||
|
# linear - Linear filtering (smooth, default)
|
||||||
|
# bilinear - Bilinear filtering (smoother)
|
||||||
|
# trilinear - Trilinear filtering (smoothest)
|
||||||
|
filter_quality = linear
|
||||||
|
|
||||||
|
# Image downsampling settings for performance optimization
|
||||||
|
# ===================================================
|
||||||
|
# Enable automatic downsampling of large images to save memory and improve performance
|
||||||
|
# Set to false to keep original resolution for all images (uses more memory!)
|
||||||
|
enable_downsampling = true
|
||||||
|
|
||||||
|
# Maximum expected output resolution
|
||||||
|
# Images larger than these dimensions may be automatically downsampled
|
||||||
|
# Adjust based on your actual monitor setup
|
||||||
|
max_output_width = 3840 # 4K width (change to 2560 for 1440p, 1920 for 1080p)
|
||||||
|
max_output_height = 2160 # 4K height (change to 1440 for 1440p, 1080 for 1080p)
|
||||||
|
|
||||||
|
# Minimum scale factor
|
||||||
|
# Prevents images from being scaled below this percentage of original size
|
||||||
|
# Useful to preserve detail on very high-resolution images
|
||||||
|
# Range: 0.1 to 1.0 (10% to 100%)
|
||||||
|
min_scale_factor = 0.25 # Don't scale below 25% of original size
|
||||||
|
|
||||||
# Output-specific wallpaper mappings
|
# Output-specific wallpaper mappings
|
||||||
# ==================================
|
# ==================================
|
||||||
# Format: output.OUTPUT_NAME = /path/to/image.ext
|
# Basic format: output.OUTPUT_NAME = /path/to/image.ext
|
||||||
|
#
|
||||||
|
# Extended format with per-output settings:
|
||||||
|
# output.OUTPUT_NAME = /path/to/image.ext
|
||||||
|
# output.OUTPUT_NAME.scale = fill|fit|stretch|center
|
||||||
|
# output.OUTPUT_NAME.filter = nearest|linear|bilinear|trilinear
|
||||||
#
|
#
|
||||||
# To find your output names, run one of these commands:
|
# To find your output names, run one of these commands:
|
||||||
# - wlr-randr (for wlroots-based compositors like Sway, Hyprland)
|
#
|
||||||
|
# Compositor Agnostic:
|
||||||
|
# - wlr-randr (for wlroots-based compositors)
|
||||||
# - wayland-info | grep wl_output
|
# - wayland-info | grep wl_output
|
||||||
# - kanshi list-outputs
|
# - kanshi list-outputs
|
||||||
#
|
#
|
||||||
|
# Compositor Specific
|
||||||
|
# - hyprctl monitors -j | jq .[].name (Hyprland specific)
|
||||||
|
#
|
||||||
# Common output name patterns:
|
# Common output name patterns:
|
||||||
# - DP-1, DP-2, DP-3, etc. (DisplayPort)
|
# - DP-1, DP-2, DP-3, etc. (DisplayPort)
|
||||||
# - HDMI-A-1, HDMI-A-2, etc. (HDMI)
|
# - HDMI-A-1, HDMI-A-2, etc. (HDMI)
|
||||||
|
|
@ -43,6 +87,14 @@ daemon_mode = false
|
||||||
# - DVI-D-1, DVI-I-1 (DVI)
|
# - DVI-D-1, DVI-I-1 (DVI)
|
||||||
# - VGA-1 (VGA, legacy)
|
# - VGA-1 (VGA, legacy)
|
||||||
#
|
#
|
||||||
# Example:
|
# Examples:
|
||||||
# output.HDMI-A-1 = ~/Pictures/wallpaper.jpg
|
# output.HDMI-A-1 = ~/Pictures/wallpaper.jpg
|
||||||
|
# output.DP-1 = ~/Pictures/monitor1.png
|
||||||
|
# output.DP-1.scale = fit
|
||||||
|
# output.DP-1.filter = bilinear
|
||||||
|
# output.DP-2 = ~/Pictures/monitor2.jpg
|
||||||
|
# output.DP-2.scale = stretch
|
||||||
|
# output.eDP-1 = ~/Pictures/laptop-wallpaper.jpg
|
||||||
|
# output.eDP-1.scale = center
|
||||||
|
# output.eDP-1.filter = trilinear
|
||||||
|
|
||||||
|
|
|
||||||
6
flake.lock
generated
6
flake.lock
generated
|
|
@ -2,11 +2,11 @@
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1759155593,
|
"lastModified": 1768665130,
|
||||||
"narHash": "sha256-potIJyEY7ExgfiMzr44/KBODFednyzRUAE2vs4aThHs=",
|
"narHash": "sha256-N9eZwqdrubgKRDICaJ92Q5UMghBR1nbHnokSgZ21EJI=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "13ca7febc86978bdb67c0ae94f568b189ae84eef",
|
"rev": "c444f4a39d8efbf5e07d678ffde3e661735e1d7e",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
|
||||||
16
flake.nix
16
flake.nix
|
|
@ -1,8 +1,11 @@
|
||||||
{
|
{
|
||||||
description = "Wayland Wallpaper Daemon";
|
description = "Super-fast, lightweight and efficient wallpaper daemon for Wayland compositors";
|
||||||
|
|
||||||
inputs.nixpkgs.url = "github:NixOS/nixpkgs?ref?nixos-unstable";
|
inputs.nixpkgs.url = "github:NixOS/nixpkgs?ref?nixos-unstable";
|
||||||
outputs = {nixpkgs, ...}: let
|
outputs = {
|
||||||
|
nixpkgs,
|
||||||
|
self,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
systems = ["x86_64-linux" "aarch64-linux"];
|
systems = ["x86_64-linux" "aarch64-linux"];
|
||||||
forAllSystems = f:
|
forAllSystems = f:
|
||||||
builtins.listToAttrs (map (system: {
|
builtins.listToAttrs (map (system: {
|
||||||
|
|
@ -18,5 +21,12 @@
|
||||||
in {
|
in {
|
||||||
default = pkgs.callPackage ./shell.nix {};
|
default = pkgs.callPackage ./shell.nix {};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
packages = forAllSystems (system: let
|
||||||
|
pkgs = pkgsFor system;
|
||||||
|
in {
|
||||||
|
chroma = pkgs.callPackage ./nix/package.nix {};
|
||||||
|
default = self.packages.${system}.chroma;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
12
include/chroma.h
vendored
12
include/chroma.h
vendored
|
|
@ -99,6 +99,7 @@ typedef struct {
|
||||||
GLuint ebo;
|
GLuint ebo;
|
||||||
bool gl_resources_initialized;
|
bool gl_resources_initialized;
|
||||||
bool texture_uploaded;
|
bool texture_uploaded;
|
||||||
|
bool vbo_dirty; // track VBO needs update
|
||||||
} chroma_output_t;
|
} chroma_output_t;
|
||||||
|
|
||||||
// Config mapping structure
|
// Config mapping structure
|
||||||
|
|
@ -119,6 +120,12 @@ typedef struct {
|
||||||
// Global scaling and filtering settings (used as defaults)
|
// Global scaling and filtering settings (used as defaults)
|
||||||
chroma_scale_mode_t default_scale_mode;
|
chroma_scale_mode_t default_scale_mode;
|
||||||
chroma_filter_quality_t default_filter_quality;
|
chroma_filter_quality_t default_filter_quality;
|
||||||
|
|
||||||
|
// Image downsampling settings
|
||||||
|
bool enable_downsampling; // enable automatic downsampling
|
||||||
|
int max_output_width; // maximum expected output width
|
||||||
|
int max_output_height; // maximum expected output height
|
||||||
|
float min_scale_factor; // minimum scale factor (don't scale below this)
|
||||||
} chroma_config_t;
|
} chroma_config_t;
|
||||||
|
|
||||||
// Main application state
|
// Main application state
|
||||||
|
|
@ -205,7 +212,8 @@ void chroma_layer_surface_closed(void *data,
|
||||||
|
|
||||||
// Image loading
|
// Image loading
|
||||||
void chroma_image_init_stb(void);
|
void chroma_image_init_stb(void);
|
||||||
int chroma_image_load(chroma_image_t *image, const char *path);
|
int chroma_image_load(chroma_image_t *image, const char *path,
|
||||||
|
const chroma_config_t *config);
|
||||||
void chroma_image_free(chroma_image_t *image);
|
void chroma_image_free(chroma_image_t *image);
|
||||||
chroma_image_t *chroma_image_find_by_path(chroma_state_t *state,
|
chroma_image_t *chroma_image_find_by_path(chroma_state_t *state,
|
||||||
const char *path);
|
const char *path);
|
||||||
|
|
@ -224,7 +232,7 @@ const char *chroma_config_get_image_for_output(chroma_config_t *config,
|
||||||
int chroma_config_get_mapping_for_output(
|
int chroma_config_get_mapping_for_output(
|
||||||
chroma_config_t *config, const char *output_name,
|
chroma_config_t *config, const char *output_name,
|
||||||
chroma_scale_mode_t *scale_mode, chroma_filter_quality_t *filter_quality);
|
chroma_scale_mode_t *scale_mode, chroma_filter_quality_t *filter_quality);
|
||||||
int chroma_config_create_sample(const char *config_file);
|
|
||||||
void chroma_config_print(const chroma_config_t *config);
|
void chroma_config_print(const chroma_config_t *config);
|
||||||
|
|
||||||
// Main loop and events
|
// Main loop and events
|
||||||
|
|
|
||||||
1724
include/stb_image_write.h
Normal file
1724
include/stb_image_write.h
Normal file
File diff suppressed because it is too large
Load diff
65
nix/package.nix
Normal file
65
nix/package.nix
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
{
|
||||||
|
lib,
|
||||||
|
stdenv,
|
||||||
|
wayland,
|
||||||
|
wayland-protocols,
|
||||||
|
wayland-scanner,
|
||||||
|
libxkbcommon,
|
||||||
|
libGL,
|
||||||
|
mesa,
|
||||||
|
glibc,
|
||||||
|
pkg-config,
|
||||||
|
}: let
|
||||||
|
fs = lib.fileset;
|
||||||
|
s = ../.;
|
||||||
|
in
|
||||||
|
stdenv.mkDerivation {
|
||||||
|
pname = "chroma";
|
||||||
|
version = "1.0.0";
|
||||||
|
|
||||||
|
src = fs.toSource {
|
||||||
|
root = s;
|
||||||
|
fileset = fs.unions [
|
||||||
|
(s + /include)
|
||||||
|
(s + /protocols)
|
||||||
|
(s + /src)
|
||||||
|
|
||||||
|
(s + /Makefile)
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
buildInputs = [
|
||||||
|
# Wayland libraries
|
||||||
|
wayland.dev
|
||||||
|
wayland-protocols
|
||||||
|
wayland-scanner
|
||||||
|
libxkbcommon
|
||||||
|
|
||||||
|
# EGL/OpenGL libraries
|
||||||
|
libGL
|
||||||
|
mesa
|
||||||
|
|
||||||
|
# System libraries
|
||||||
|
glibc.dev
|
||||||
|
];
|
||||||
|
|
||||||
|
nativeBuildInputs = [
|
||||||
|
pkg-config
|
||||||
|
];
|
||||||
|
|
||||||
|
makeFlags = [
|
||||||
|
"PREFIX=$(out)"
|
||||||
|
"SYSTEMD_DIR=$(out)/lib/systemd/system"
|
||||||
|
];
|
||||||
|
|
||||||
|
postInstall = ''
|
||||||
|
install -Dm755 ${../chroma.conf.sample} $out/share/chroma.conf.sample
|
||||||
|
'';
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
description = "Super-fast, lightweight and efficient wallpaper daemon for Wayland compositors";
|
||||||
|
license = lib.licenses.mpl20;
|
||||||
|
mainProgram = "chroma";
|
||||||
|
maintainers = with lib.maitainers; [NotAShelf];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -25,6 +25,9 @@ pkgs.mkShell {
|
||||||
|
|
||||||
# System libraries
|
# System libraries
|
||||||
glibc.dev
|
glibc.dev
|
||||||
|
|
||||||
|
# For Tests
|
||||||
|
(python313.withPackages (ps: with ps; [matplotlib numpy]))
|
||||||
];
|
];
|
||||||
|
|
||||||
nativeBuildInputs = with pkgs; [
|
nativeBuildInputs = with pkgs; [
|
||||||
|
|
@ -44,8 +47,5 @@ pkgs.mkShell {
|
||||||
'';
|
'';
|
||||||
|
|
||||||
# Environment variables for the build system
|
# Environment variables for the build system
|
||||||
env = {
|
env.WAYLAND_DEBUG = 1;
|
||||||
CHROMA_VERSION = "1.0.0";
|
|
||||||
WAYLAND_DEBUG = 1;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
73
src/config.c
73
src/config.c
|
|
@ -184,6 +184,12 @@ static void init_default_config(chroma_config_t *config) {
|
||||||
config->default_scale_mode = CHROMA_SCALE_FILL;
|
config->default_scale_mode = CHROMA_SCALE_FILL;
|
||||||
config->default_filter_quality = CHROMA_FILTER_LINEAR;
|
config->default_filter_quality = CHROMA_FILTER_LINEAR;
|
||||||
|
|
||||||
|
// Set default downsampling settings
|
||||||
|
config->enable_downsampling = true; // enable by default, performance etc.
|
||||||
|
config->max_output_width = 3840; // 4K width
|
||||||
|
config->max_output_height = 2160; // 4K height
|
||||||
|
config->min_scale_factor = 0.25f; // don't scale below 25%
|
||||||
|
|
||||||
// Set default image path (can be overridden)
|
// Set default image path (can be overridden)
|
||||||
const char *home = getenv("HOME");
|
const char *home = getenv("HOME");
|
||||||
if (home) {
|
if (home) {
|
||||||
|
|
@ -276,6 +282,34 @@ static int parse_config_line(chroma_config_t *config, char *line,
|
||||||
"Filter quality configuration: key='%s', value='%s', parsed=%s",
|
"Filter quality configuration: key='%s', value='%s', parsed=%s",
|
||||||
key, value,
|
key, value,
|
||||||
filter_quality_to_string(config->default_filter_quality));
|
filter_quality_to_string(config->default_filter_quality));
|
||||||
|
} else if (strcasecmp(key, "enable_downsampling") == 0) {
|
||||||
|
config->enable_downsampling = parse_bool(value);
|
||||||
|
chroma_log("DEBUG", "Set downsampling: %s",
|
||||||
|
config->enable_downsampling ? "enabled" : "disabled");
|
||||||
|
} else if (strcasecmp(key, "max_output_width") == 0) {
|
||||||
|
int width = atoi(value);
|
||||||
|
if (width > 0 && width <= 16384) { // Reasonable limits
|
||||||
|
config->max_output_width = width;
|
||||||
|
chroma_log("DEBUG", "Set max output width: %d", width);
|
||||||
|
} else {
|
||||||
|
chroma_log("WARN", "Invalid max_output_width: %s (using 3840)", value);
|
||||||
|
}
|
||||||
|
} else if (strcasecmp(key, "max_output_height") == 0) {
|
||||||
|
int height = atoi(value);
|
||||||
|
if (height > 0 && height <= 16384) { // Reasonable limits
|
||||||
|
config->max_output_height = height;
|
||||||
|
chroma_log("DEBUG", "Set max output height: %d", height);
|
||||||
|
} else {
|
||||||
|
chroma_log("WARN", "Invalid max_output_height: %s (using 2160)", value);
|
||||||
|
}
|
||||||
|
} else if (strcasecmp(key, "min_scale_factor") == 0) {
|
||||||
|
float factor = atof(value);
|
||||||
|
if (factor > 0.0f && factor <= 1.0f) { // Valid range
|
||||||
|
config->min_scale_factor = factor;
|
||||||
|
chroma_log("DEBUG", "Set minimum scale factor: %.2f", factor);
|
||||||
|
} else {
|
||||||
|
chroma_log("WARN", "Invalid min_scale_factor: %s (using 0.25)", value);
|
||||||
|
}
|
||||||
} else if (strncasecmp(key, "output.", 7) == 0) {
|
} else if (strncasecmp(key, "output.", 7) == 0) {
|
||||||
// Output-specific mapping: e.g., output.DP-1=/path/to/image.jpg
|
// Output-specific mapping: e.g., output.DP-1=/path/to/image.jpg
|
||||||
const char *output_name = key + 7;
|
const char *output_name = key + 7;
|
||||||
|
|
@ -515,39 +549,7 @@ int chroma_config_get_mapping_for_output(
|
||||||
return CHROMA_OK;
|
return CHROMA_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a sample configuration file
|
|
||||||
int chroma_config_create_sample(const char *config_file) {
|
|
||||||
if (!config_file) {
|
|
||||||
return CHROMA_ERROR_INIT;
|
|
||||||
}
|
|
||||||
|
|
||||||
FILE *file = fopen(config_file, "w");
|
|
||||||
if (!file) {
|
|
||||||
chroma_log("ERROR", "Failed to create sample config file %s: %s",
|
|
||||||
config_file, strerror(errno));
|
|
||||||
return CHROMA_ERROR_CONFIG;
|
|
||||||
}
|
|
||||||
|
|
||||||
fprintf(file, "# Chroma Wallpaper Daemon Configuration\n");
|
|
||||||
fprintf(file, "# Lines starting with # are comments\n\n");
|
|
||||||
|
|
||||||
fprintf(file, "# Default wallpaper for outputs without specific mapping\n");
|
|
||||||
fprintf(file, "default_image = ~/.config/chroma/default.jpg\n\n");
|
|
||||||
|
|
||||||
fprintf(file, "# Output-specific wallpapers\n");
|
|
||||||
fprintf(file, "# Format: output.OUTPUT_NAME = /path/to/image.jpg\n");
|
|
||||||
fprintf(file, "# You can find output names using: wlr-randr\n");
|
|
||||||
fprintf(file, "\n");
|
|
||||||
fprintf(file, "# Examples:\n");
|
|
||||||
fprintf(file, "# output.DP-1 = ~/.config/chroma/monitor1.jpg\n");
|
|
||||||
fprintf(file, "# output.DP-2 = ~/.config/chroma/monitor2.png\n");
|
|
||||||
fprintf(file, "# output.HDMI-A-1 = ~/.config/chroma/hdmi.jpg\n");
|
|
||||||
|
|
||||||
fclose(file);
|
|
||||||
|
|
||||||
chroma_log("INFO", "Created sample configuration file: %s", config_file);
|
|
||||||
return CHROMA_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print current configuration for debugging
|
// Print current configuration for debugging
|
||||||
void chroma_config_print(const chroma_config_t *config) {
|
void chroma_config_print(const chroma_config_t *config) {
|
||||||
|
|
@ -562,6 +564,13 @@ void chroma_config_print(const chroma_config_t *config) {
|
||||||
scale_mode_to_string(config->default_scale_mode));
|
scale_mode_to_string(config->default_scale_mode));
|
||||||
chroma_log("INFO", "Default filter quality: %s",
|
chroma_log("INFO", "Default filter quality: %s",
|
||||||
filter_quality_to_string(config->default_filter_quality));
|
filter_quality_to_string(config->default_filter_quality));
|
||||||
|
chroma_log("INFO", "Downsampling: %s",
|
||||||
|
config->enable_downsampling ? "enabled" : "disabled");
|
||||||
|
if (config->enable_downsampling) {
|
||||||
|
chroma_log("INFO", "Max output size: %dx%d", config->max_output_width,
|
||||||
|
config->max_output_height);
|
||||||
|
chroma_log("INFO", "Min scale factor: %.2f", config->min_scale_factor);
|
||||||
|
}
|
||||||
chroma_log("INFO", "Output mappings: %d", config->mapping_count);
|
chroma_log("INFO", "Output mappings: %d", config->mapping_count);
|
||||||
|
|
||||||
for (int i = 0; i < config->mapping_count; i++) {
|
for (int i = 0; i < config->mapping_count; i++) {
|
||||||
|
|
|
||||||
11
src/core.c
11
src/core.c
|
|
@ -96,6 +96,7 @@ static int assign_wallpaper_to_output(chroma_state_t *state,
|
||||||
bool image_changed = (output->image != image);
|
bool image_changed = (output->image != image);
|
||||||
if (image_changed && output->image) {
|
if (image_changed && output->image) {
|
||||||
chroma_output_invalidate_texture(output);
|
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",
|
chroma_log("DEBUG", "Image changed for output %u, invalidated texture",
|
||||||
output->id);
|
output->id);
|
||||||
}
|
}
|
||||||
|
|
@ -103,6 +104,11 @@ static int assign_wallpaper_to_output(chroma_state_t *state,
|
||||||
// Assign image to output
|
// Assign image to output
|
||||||
output->image = image;
|
output->image = image;
|
||||||
|
|
||||||
|
// Mark VBO as dirty if image changed
|
||||||
|
if (image_changed) {
|
||||||
|
output->vbo_dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Store old configuration values for comparison
|
// Store old configuration values for comparison
|
||||||
chroma_scale_mode_t old_scale_mode = output->scale_mode;
|
chroma_scale_mode_t old_scale_mode = output->scale_mode;
|
||||||
chroma_filter_quality_t old_filter_quality = output->filter_quality;
|
chroma_filter_quality_t old_filter_quality = output->filter_quality;
|
||||||
|
|
@ -120,6 +126,7 @@ static int assign_wallpaper_to_output(chroma_state_t *state,
|
||||||
if (had_config && (old_scale_mode != output->scale_mode ||
|
if (had_config && (old_scale_mode != output->scale_mode ||
|
||||||
old_filter_quality != output->filter_quality)) {
|
old_filter_quality != output->filter_quality)) {
|
||||||
chroma_output_invalidate_texture(output);
|
chroma_output_invalidate_texture(output);
|
||||||
|
output->vbo_dirty = true; // VBO needs update for new scale mode
|
||||||
chroma_log("DEBUG",
|
chroma_log("DEBUG",
|
||||||
"Configuration changed for output %u, invalidated texture",
|
"Configuration changed for output %u, invalidated texture",
|
||||||
output->id);
|
output->id);
|
||||||
|
|
@ -273,14 +280,14 @@ int chroma_run(chroma_state_t *state) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use select() to wait for events with timeout
|
// Use `select()` to wait for events with longer timeout to reduce CPU usage
|
||||||
fd_set readfds;
|
fd_set readfds;
|
||||||
struct timeval timeout;
|
struct timeval timeout;
|
||||||
|
|
||||||
FD_ZERO(&readfds);
|
FD_ZERO(&readfds);
|
||||||
FD_SET(fd, &readfds);
|
FD_SET(fd, &readfds);
|
||||||
|
|
||||||
timeout.tv_sec = 1; // 1s timeout
|
timeout.tv_sec = 10;
|
||||||
timeout.tv_usec = 0;
|
timeout.tv_usec = 0;
|
||||||
|
|
||||||
int select_result = select(fd + 1, &readfds, NULL, NULL, &timeout);
|
int select_result = select(fd + 1, &readfds, NULL, NULL, &timeout);
|
||||||
|
|
|
||||||
154
src/image.c
154
src/image.c
|
|
@ -24,8 +24,76 @@ static long get_file_size(const char *path) {
|
||||||
return st.st_size;
|
return st.st_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load image from file
|
// Calculate optimal image size based on output dimensions
|
||||||
int chroma_image_load(chroma_image_t *image, const char *path) {
|
static void calculate_optimal_size(int original_width, int original_height,
|
||||||
|
int max_output_width, int max_output_height,
|
||||||
|
int *optimal_width, int *optimal_height) {
|
||||||
|
// If image is smaller than outputs, keep original size
|
||||||
|
if (original_width <= max_output_width &&
|
||||||
|
original_height <= max_output_height) {
|
||||||
|
*optimal_width = original_width;
|
||||||
|
*optimal_height = original_height;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate scale factor to fit within max output dimensions
|
||||||
|
float scale_x = (float)max_output_width / original_width;
|
||||||
|
float scale_y = (float)max_output_height / original_height;
|
||||||
|
float scale = (scale_x < scale_y) ? scale_x : scale_y;
|
||||||
|
|
||||||
|
// Apply scale factor with minimum size to avoid too small images
|
||||||
|
scale = (scale > 1.0f) ? 1.0f : scale;
|
||||||
|
scale = (scale < 0.25f) ? 0.25f : scale; // XXX: don't scale below 25%
|
||||||
|
|
||||||
|
*optimal_width = (int)(original_width * scale);
|
||||||
|
*optimal_height = (int)(original_height * scale);
|
||||||
|
|
||||||
|
// Ensure even dimensions for better GPU alignment
|
||||||
|
*optimal_width = (*optimal_width / 2) * 2;
|
||||||
|
*optimal_height = (*optimal_height / 2) * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: this is a very simple way of implementing box filter downsampling for
|
||||||
|
// memory efficiency Could be better, but this is good enough *for the time
|
||||||
|
// 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) {
|
||||||
|
if (!src_data || !dst_data) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
float x_ratio = (float)src_width / dst_width;
|
||||||
|
float y_ratio = (float)src_height / dst_height;
|
||||||
|
|
||||||
|
for (int y = 0; y < dst_height; y++) {
|
||||||
|
for (int x = 0; x < dst_width; x++) {
|
||||||
|
// Calculate corresponding source pixel
|
||||||
|
int src_x = (int)(x * x_ratio);
|
||||||
|
int src_y = (int)(y * y_ratio);
|
||||||
|
|
||||||
|
// Ensure we're within bounds
|
||||||
|
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;
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load image from file with configurable downsampling
|
||||||
|
int chroma_image_load(chroma_image_t *image, const char *path,
|
||||||
|
const chroma_config_t *config) {
|
||||||
if (!image || !path) {
|
if (!image || !path) {
|
||||||
chroma_log("ERROR", "Invalid parameters for image loading");
|
chroma_log("ERROR", "Invalid parameters for image loading");
|
||||||
return CHROMA_ERROR_INIT;
|
return CHROMA_ERROR_INIT;
|
||||||
|
|
@ -77,15 +145,87 @@ int chroma_image_load(chroma_image_t *image, const char *path) {
|
||||||
return CHROMA_ERROR_IMAGE;
|
return CHROMA_ERROR_IMAGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Store original dimensions before potential downsampling
|
||||||
|
int original_width = image->width;
|
||||||
|
int original_height = image->height;
|
||||||
|
|
||||||
|
// Apply intelligent downsampling if enabled
|
||||||
|
bool should_downsample = false;
|
||||||
|
int optimal_width = original_width;
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Apply minimum scale factor constraint
|
||||||
|
float scale_x = (float)optimal_width / original_width;
|
||||||
|
float scale_y = (float)optimal_height / original_height;
|
||||||
|
float scale = (scale_x < scale_y) ? scale_x : scale_y;
|
||||||
|
|
||||||
|
if (scale < config->min_scale_factor) {
|
||||||
|
scale = config->min_scale_factor;
|
||||||
|
optimal_width = (int)(original_width * scale);
|
||||||
|
optimal_height = (int)(original_height * scale);
|
||||||
|
|
||||||
|
// Ensure even dimensions
|
||||||
|
optimal_width = (optimal_width / 2) * 2;
|
||||||
|
optimal_height = (optimal_height / 2) * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
should_downsample =
|
||||||
|
(optimal_width < original_width || optimal_height < original_height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Downsamp if needed and enabled
|
||||||
|
if (should_downsample) {
|
||||||
|
chroma_log("INFO",
|
||||||
|
"Downsampling image from %dx%d to %dx%d (%.1f%% of original)",
|
||||||
|
original_width, original_height, optimal_width, optimal_height,
|
||||||
|
(float)(optimal_width * optimal_height) /
|
||||||
|
(original_width * original_height) * 100.0f);
|
||||||
|
|
||||||
|
size_t optimal_size = (size_t)optimal_width * optimal_height * 4;
|
||||||
|
unsigned char *downsampled_data = malloc(optimal_size);
|
||||||
|
if (!downsampled_data) {
|
||||||
|
chroma_log("ERROR", "Failed to allocate memory for downsampled image");
|
||||||
|
chroma_image_free(image);
|
||||||
|
return CHROMA_ERROR_MEMORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (downsample_image(image->data, original_width, original_height,
|
||||||
|
downsampled_data, optimal_width,
|
||||||
|
optimal_height) != 0) {
|
||||||
|
chroma_log("ERROR", "Failed to downsample image");
|
||||||
|
free(downsampled_data);
|
||||||
|
chroma_image_free(image);
|
||||||
|
return CHROMA_ERROR_IMAGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
stbi_image_free(image->data);
|
||||||
|
image->data = downsampled_data;
|
||||||
|
image->width = optimal_width;
|
||||||
|
image->height = optimal_height;
|
||||||
|
|
||||||
|
chroma_log("DEBUG", "Successfully downsampled image to %dx%d",
|
||||||
|
optimal_width, optimal_height);
|
||||||
|
} else if (config && !config->enable_downsampling) {
|
||||||
|
chroma_log("DEBUG",
|
||||||
|
"Downsampling disabled, keeping original resolution %dx%d",
|
||||||
|
original_width, original_height);
|
||||||
|
}
|
||||||
|
|
||||||
image->loaded = true;
|
image->loaded = true;
|
||||||
|
|
||||||
// Calculate and log memory allocation
|
// Calculate and log memory allocation
|
||||||
size_t image_size = (size_t)image->width * image->height * image->channels;
|
size_t image_size = (size_t)image->width * image->height * image->channels;
|
||||||
chroma_log_resource_allocation("image_data", image_size, path);
|
chroma_log_resource_allocation("image_data", image_size, path);
|
||||||
|
|
||||||
chroma_log("INFO", "Loaded image: %s (%dx%d, %d channels, %.2f MB)", path,
|
chroma_log("INFO", "Loaded image: %s (%dx%d, %d channels, %.2f MB)%s", path,
|
||||||
image->width, image->height, image->channels,
|
image->width, image->height, image->channels,
|
||||||
(double)image_size / (1024.0 * 1024.0));
|
(double)image_size / (1024.0 * 1024.0),
|
||||||
|
should_downsample ? " (downsampled)" : "");
|
||||||
|
|
||||||
return CHROMA_OK;
|
return CHROMA_OK;
|
||||||
}
|
}
|
||||||
|
|
@ -159,8 +299,8 @@ chroma_image_t *chroma_image_get_or_load(chroma_state_t *state,
|
||||||
state->image_count++;
|
state->image_count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the image
|
// Load the image with configuration
|
||||||
if (chroma_image_load(image, path) != CHROMA_OK) {
|
if (chroma_image_load(image, path, &state->config) != CHROMA_OK) {
|
||||||
// If this was a new slot, decrement count
|
// If this was a new slot, decrement count
|
||||||
if (!existing) {
|
if (!existing) {
|
||||||
state->image_count--;
|
state->image_count--;
|
||||||
|
|
@ -241,7 +381,7 @@ void chroma_image_init_stb(void) {
|
||||||
// Set stb_image options
|
// Set stb_image options
|
||||||
stbi_set_flip_vertically_on_load(0);
|
stbi_set_flip_vertically_on_load(0);
|
||||||
|
|
||||||
// These could be made configurable
|
// FIXME: these could be made configurable
|
||||||
stbi_ldr_to_hdr_gamma(2.2f);
|
stbi_ldr_to_hdr_gamma(2.2f);
|
||||||
stbi_ldr_to_hdr_scale(1.0f);
|
stbi_ldr_to_hdr_scale(1.0f);
|
||||||
|
|
||||||
|
|
|
||||||
72
src/render.c
72
src/render.c
|
|
@ -10,27 +10,33 @@
|
||||||
|
|
||||||
// Convert filter quality enum to OpenGL parameters
|
// Convert filter quality enum to OpenGL parameters
|
||||||
static void get_gl_filter_params(chroma_filter_quality_t quality,
|
static void get_gl_filter_params(chroma_filter_quality_t quality,
|
||||||
GLint *min_filter, GLint *mag_filter) {
|
GLint *min_filter, GLint *mag_filter,
|
||||||
|
bool *needs_mipmaps) {
|
||||||
switch (quality) {
|
switch (quality) {
|
||||||
case CHROMA_FILTER_NEAREST:
|
case CHROMA_FILTER_NEAREST:
|
||||||
*min_filter = GL_NEAREST;
|
*min_filter = GL_NEAREST;
|
||||||
*mag_filter = GL_NEAREST;
|
*mag_filter = GL_NEAREST;
|
||||||
|
*needs_mipmaps = false;
|
||||||
break;
|
break;
|
||||||
case CHROMA_FILTER_LINEAR:
|
case CHROMA_FILTER_LINEAR:
|
||||||
*min_filter = GL_LINEAR;
|
*min_filter = GL_LINEAR;
|
||||||
*mag_filter = GL_LINEAR;
|
*mag_filter = GL_LINEAR;
|
||||||
|
*needs_mipmaps = false;
|
||||||
break;
|
break;
|
||||||
case CHROMA_FILTER_BILINEAR:
|
case CHROMA_FILTER_BILINEAR:
|
||||||
*min_filter = GL_LINEAR_MIPMAP_LINEAR;
|
*min_filter = GL_LINEAR;
|
||||||
*mag_filter = GL_LINEAR;
|
*mag_filter = GL_LINEAR;
|
||||||
|
*needs_mipmaps = false;
|
||||||
break;
|
break;
|
||||||
case CHROMA_FILTER_TRILINEAR:
|
case CHROMA_FILTER_TRILINEAR:
|
||||||
*min_filter = GL_LINEAR_MIPMAP_LINEAR;
|
*min_filter = GL_LINEAR_MIPMAP_LINEAR;
|
||||||
*mag_filter = GL_LINEAR;
|
*mag_filter = GL_LINEAR;
|
||||||
|
*needs_mipmaps = true;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
*min_filter = GL_LINEAR;
|
*min_filter = GL_LINEAR;
|
||||||
*mag_filter = GL_LINEAR;
|
*mag_filter = GL_LINEAR;
|
||||||
|
*needs_mipmaps = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -271,7 +277,8 @@ static int init_gl_resources(chroma_output_t *output) {
|
||||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices,
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices,
|
||||||
GL_STATIC_DRAW);
|
GL_STATIC_DRAW);
|
||||||
|
|
||||||
output->texture_id = 0; // will be created when image is assigned
|
output->texture_id = 0; // will be created when image is assigned
|
||||||
|
output->vbo_dirty = true; // VBO needs initial update
|
||||||
output->gl_resources_initialized = true;
|
output->gl_resources_initialized = true;
|
||||||
|
|
||||||
chroma_log("DEBUG", "Initialized GL resources for output %u", output->id);
|
chroma_log("DEBUG", "Initialized GL resources for output %u", output->id);
|
||||||
|
|
@ -324,7 +331,9 @@ static int update_texture_from_image(chroma_output_t *output,
|
||||||
|
|
||||||
// Use configured filter quality
|
// Use configured filter quality
|
||||||
GLint min_filter, mag_filter;
|
GLint min_filter, mag_filter;
|
||||||
get_gl_filter_params(filter_quality, &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_MIN_FILTER, min_filter);
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter);
|
||||||
|
|
||||||
|
|
@ -332,6 +341,12 @@ static int update_texture_from_image(chroma_output_t *output,
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image->width, image->height, 0,
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image->width, image->height, 0,
|
||||||
GL_RGBA, GL_UNSIGNED_BYTE, image->data);
|
GL_RGBA, 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");
|
||||||
|
}
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
|
||||||
// Mark this output as having uploaded its texture
|
// Mark this output as having uploaded its texture
|
||||||
|
|
@ -670,26 +685,36 @@ int chroma_render_wallpaper(chroma_state_t *state, chroma_output_t *output) {
|
||||||
glBindTexture(GL_TEXTURE_2D, output->texture_id);
|
glBindTexture(GL_TEXTURE_2D, output->texture_id);
|
||||||
glUniform1i(glGetUniformLocation(output->shader_program, "texture"), 0);
|
glUniform1i(glGetUniformLocation(output->shader_program, "texture"), 0);
|
||||||
|
|
||||||
// Calculate texture coordinates based on scaling mode
|
// Update VBO only if needed. E.g, image changed, scale mode changed, or first
|
||||||
float tex_coords[8];
|
// render
|
||||||
calculate_texture_coords(output->scale_mode, output->image->width,
|
if (output->vbo_dirty) {
|
||||||
output->image->height, output->width, output->height,
|
// Calculate texture coordinates based on scaling mode
|
||||||
tex_coords);
|
float tex_coords[8];
|
||||||
|
calculate_texture_coords(output->scale_mode, output->image->width,
|
||||||
|
output->image->height, output->width,
|
||||||
|
output->height, tex_coords);
|
||||||
|
|
||||||
// Create dynamic vertex data with calculated texture coordinates
|
// Create dynamic vertex data with calculated texture coordinates
|
||||||
float dynamic_vertices[] = {
|
float dynamic_vertices[] = {
|
||||||
// Position Texcoord
|
// Position Texcoord
|
||||||
-1.0f, -1.0f, tex_coords[0], tex_coords[1], // bottom-left
|
-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[2], tex_coords[3], // bottom-right
|
||||||
1.0f, 1.0f, tex_coords[4], tex_coords[5], // top-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
|
-1.0f, 1.0f, tex_coords[6], tex_coords[7] // top-left
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update VBO with dynamic texture coordinates
|
// Update VBO with dynamic texture coordinates
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, output->vbo);
|
glBindBuffer(GL_ARRAY_BUFFER, output->vbo);
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(dynamic_vertices),
|
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(dynamic_vertices),
|
||||||
dynamic_vertices);
|
dynamic_vertices);
|
||||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, output->ebo);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
// Set vertex attributes
|
// Set vertex attributes
|
||||||
GLint position_attr = glGetAttribLocation(output->shader_program, "position");
|
GLint position_attr = glGetAttribLocation(output->shader_program, "position");
|
||||||
|
|
@ -739,4 +764,7 @@ void chroma_output_invalidate_texture(chroma_output_t *output) {
|
||||||
output->texture_uploaded = false; // reset upload flag
|
output->texture_uploaded = false; // reset upload flag
|
||||||
chroma_log("DEBUG", "Invalidated texture cache for output %u", output->id);
|
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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue