From be79ddf499cd896c0e75aa068d344cb013ee02e0 Mon Sep 17 00:00:00 2001 From: Nova Date: Wed, 18 Mar 2026 14:00:00 -0700 Subject: [PATCH] xrt: Add chroma key c/shaders: Multiply original alpha with chroma keyed alpha comp: Add chroma key for projection_depth layers Apply 1 suggestion(s) to 1 file(s) Co-authored-by: Jakob Bornecrantz resolve suggestions (i think) reorder application of chroma key revert: do_projection function Apply 2 suggestion(s) to 1 file(s) Co-authored-by: Jakob Bornecrantz Part-of: --- src/xrt/compositor/main/comp_compositor.c | 11 +++ .../compositor/multi/comp_multi_compositor.c | 3 + src/xrt/compositor/multi/comp_multi_private.h | 4 ++ src/xrt/compositor/multi/comp_multi_system.c | 59 ++++++++++++++++ src/xrt/compositor/render/render_interface.h | 20 ++++++ src/xrt/compositor/shaders/hsv.inc.glsl | 69 +++++++++++++++++++ src/xrt/compositor/shaders/layer.comp | 55 +++++++++++++++ src/xrt/compositor/shaders/srgb.inc.glsl | 18 +++++ src/xrt/compositor/util/comp_render_cs.c | 18 +++++ src/xrt/include/xrt/xrt_compositor.h | 40 +++++++++++ src/xrt/include/xrt/xrt_defines.h | 13 ++++ 11 files changed, 310 insertions(+) create mode 100644 src/xrt/compositor/shaders/hsv.inc.glsl diff --git a/src/xrt/compositor/main/comp_compositor.c b/src/xrt/compositor/main/comp_compositor.c index 0d35fcb42..2f18b2262 100644 --- a/src/xrt/compositor/main/comp_compositor.c +++ b/src/xrt/compositor/main/comp_compositor.c @@ -279,6 +279,17 @@ can_do_one_projection_layer_fast_path(struct comp_compositor *c) enum xrt_layer_type type = layer->data.type; + // Check if chroma key is active for projection layers + if (type == XRT_LAYER_PROJECTION) { + if (layer->data.proj.chroma_key.curve > 0.0f) { + return false; + } + } else if (type == XRT_LAYER_PROJECTION_DEPTH) { + if (layer->data.depth.chroma_key.curve > 0.0f) { + return false; + } + } + // Handled by the distortion shader. return type == XRT_LAYER_PROJECTION || // type == XRT_LAYER_PROJECTION_DEPTH; diff --git a/src/xrt/compositor/multi/comp_multi_compositor.c b/src/xrt/compositor/multi/comp_multi_compositor.c index 1fc135ce4..33675d43f 100644 --- a/src/xrt/compositor/multi/comp_multi_compositor.c +++ b/src/xrt/compositor/multi/comp_multi_compositor.c @@ -977,6 +977,9 @@ multi_compositor_create(struct multi_system_compositor *msc, struct multi_compositor *mc = U_TYPED_CALLOC(struct multi_compositor); + // Initialize state - base session is anything that's not an overlay + mc->state.is_base_session = !xsi->is_overlay; + mc->base.base.get_swapchain_create_properties = multi_compositor_get_swapchain_create_properties; mc->base.base.create_swapchain = multi_compositor_create_swapchain; mc->base.base.import_swapchain = multi_compositor_import_swapchain; diff --git a/src/xrt/compositor/multi/comp_multi_private.h b/src/xrt/compositor/multi/comp_multi_private.h index 0e777f442..6c7ec05f8 100644 --- a/src/xrt/compositor/multi/comp_multi_private.h +++ b/src/xrt/compositor/multi/comp_multi_private.h @@ -129,6 +129,7 @@ struct multi_compositor int64_t z_order; bool session_active; + bool is_base_session; } state; struct @@ -373,6 +374,9 @@ struct multi_system_compositor //! List of active clients. struct multi_compositor *clients[MULTI_MAX_CLIENTS]; + + //! Chroma key parameters in HSV space + struct xrt_layer_chroma_key_data chroma_key; }; /*! diff --git a/src/xrt/compositor/multi/comp_multi_system.c b/src/xrt/compositor/multi/comp_multi_system.c index e673afcb0..4fa96fc9e 100644 --- a/src/xrt/compositor/multi/comp_multi_system.c +++ b/src/xrt/compositor/multi/comp_multi_system.c @@ -11,6 +11,7 @@ */ #include "xrt/xrt_config_os.h" +#include "xrt/xrt_defines.h" #include "xrt/xrt_session.h" #include "os/os_time.h" @@ -305,6 +306,41 @@ transfer_layers_locked(struct multi_system_compositor *msc, int64_t display_time // Sort the stack array qsort(array, count, sizeof(struct multi_compositor *), overlay_sort_func); + // Find base compositor using multiple criteria + struct multi_compositor *base_comp = NULL; + for (size_t k = 0; k < count; k++) { + struct multi_compositor *mc = array[k]; + if (mc->state.is_base_session && // Explicit flag + mc->state.visible && // Must be visible + mc->state.session_active && // Must be active + mc->delivered.active) { // Must have valid data + base_comp = mc; + break; + } + } + + bool chroma_key_enabled = msc->chroma_key.curve > 0.f; + + if (chroma_key_enabled && base_comp != NULL) { + base_comp->delivered.data.env_blend_mode = XRT_BLEND_MODE_ALPHA_BLEND; + + for (uint32_t i = 0; i < base_comp->delivered.layer_count; i++) { + struct multi_layer_entry *layer = &base_comp->delivered.layers[i]; + + if (layer->data.type == XRT_LAYER_PROJECTION) { + layer->data.proj.chroma_key.hsv_min = msc->chroma_key.hsv_min; + layer->data.proj.chroma_key.hsv_max = msc->chroma_key.hsv_max; + layer->data.proj.chroma_key.curve = msc->chroma_key.curve; + layer->data.proj.chroma_key.despill = msc->chroma_key.despill; + } else if (layer->data.type == XRT_LAYER_PROJECTION_DEPTH) { + layer->data.depth.chroma_key.hsv_min = msc->chroma_key.hsv_min; + layer->data.depth.chroma_key.hsv_max = msc->chroma_key.hsv_max; + layer->data.depth.chroma_key.curve = msc->chroma_key.curve; + layer->data.depth.chroma_key.despill = msc->chroma_key.despill; + } + } + } + // find first (ordered by bottom to top) active client to retrieve xrt_layer_frame_data const enum xrt_blend_mode blend_mode = find_active_blend_mode(array, count); @@ -625,6 +661,26 @@ system_compositor_set_z_order(struct xrt_system_compositor *xsc, struct xrt_comp //! @todo Locking? mc->state.z_order = z_order; + os_mutex_unlock(&msc->list_and_timing_lock); + + return XRT_SUCCESS; +} + +static xrt_result_t +system_compositor_set_base_chroma_key_params(struct xrt_system_compositor *xsc, + struct xrt_colour_hsv_f32 hsv_min, + struct xrt_colour_hsv_f32 hsv_max, + float curve, + float despill) +{ + struct multi_system_compositor *msc = multi_system_compositor(xsc); + + os_mutex_lock(&msc->list_and_timing_lock); + msc->chroma_key.hsv_min = hsv_min; + msc->chroma_key.hsv_max = hsv_max; + msc->chroma_key.curve = curve; + msc->chroma_key.despill = despill; + os_mutex_unlock(&msc->list_and_timing_lock); return XRT_SUCCESS; } @@ -636,6 +692,8 @@ system_compositor_set_main_app_visibility(struct xrt_system_compositor *xsc, str struct multi_compositor *mc = multi_compositor(xc); (void)msc; + mc->state.is_base_session = visible; + union xrt_session_event xse = XRT_STRUCT_INIT; xse.type = XRT_SESSION_EVENT_OVERLAY_CHANGE; xse.overlay.visible = visible; @@ -775,6 +833,7 @@ comp_multi_create_system_compositor(struct xrt_compositor_native *xcn, msc->base.destroy = system_compositor_destroy; msc->xmcc.set_state = system_compositor_set_state; msc->xmcc.set_z_order = system_compositor_set_z_order; + msc->xmcc.set_base_chroma_key_params = system_compositor_set_base_chroma_key_params; msc->xmcc.set_main_app_visibility = system_compositor_set_main_app_visibility; msc->xmcc.notify_loss_pending = system_compositor_notify_loss_pending; msc->xmcc.notify_lost = system_compositor_notify_lost; diff --git a/src/xrt/compositor/render/render_interface.h b/src/xrt/compositor/render/render_interface.h index 8a198707f..466341c7f 100644 --- a/src/xrt/compositor/render/render_interface.h +++ b/src/xrt/compositor/render/render_interface.h @@ -1182,6 +1182,23 @@ struct render_compute_blit_push_data struct xrt_rect target_rect; }; +/*! + * Chroma key parameters for std140 layout, using HSV min/max range. + * + * @relates render_compute + */ +struct render_chroma_key_info +{ + float hsv_min_h; + float hsv_min_s; + float hsv_min_v; + float hsv_max_h; + float hsv_max_s; + float hsv_max_v; + float curve; + float despill; +}; + /*! * UBO data that is sent to the compute layer shaders. * @@ -1268,6 +1285,9 @@ struct render_compute_layer_ubo_data //! Timewarp matrices struct xrt_matrix_4x4 transforms_timewarp; + //! Chroma key parameters (per layer) + struct render_chroma_key_info chroma_key; + /*! * For quad layers */ diff --git a/src/xrt/compositor/shaders/hsv.inc.glsl b/src/xrt/compositor/shaders/hsv.inc.glsl new file mode 100644 index 000000000..573e8fbf1 --- /dev/null +++ b/src/xrt/compositor/shaders/hsv.inc.glsl @@ -0,0 +1,69 @@ +// Copyright 2021, Collabora Ltd. +// Author: Nova King +// SPDX-License-Identifier: BSL-1.0 + +// RGB to HSV conversion. Returns vec3(h, s, v) with h in [0, 1]. +vec3 rgb_to_hsv(vec3 c) +{ + float cmax = max(c.r, max(c.g, c.b)); + float cmin = min(c.r, min(c.g, c.b)); + float delta = cmax - cmin; + + float h = 0.0; + if (delta > 0.0) { + if (cmax == c.r) { + h = mod((c.g - c.b) / delta, 6.0) / 6.0; + } else if (cmax == c.g) { + h = ((c.b - c.r) / delta + 2.0) / 6.0; + } else { + h = ((c.r - c.g) / delta + 4.0) / 6.0; + } + } + + float s = (cmax > 0.0) ? (delta / cmax) : 0.0; + return vec3(h, s, cmax); +} + +// HSV to RGB conversion. Expects vec3(h, s, v) with h in [0, 1]. +vec3 hsv_to_rgb(vec3 hsv) +{ + float c = hsv.z * hsv.y; + float h6 = hsv.x * 6.0; + float x = c * (1.0 - abs(mod(h6, 2.0) - 1.0)); + float m = hsv.z - c; + + vec3 rgb; + if (h6 < 1.0) rgb = vec3(c, x, 0.0); + else if (h6 < 2.0) rgb = vec3(x, c, 0.0); + else if (h6 < 3.0) rgb = vec3(0.0, c, x); + else if (h6 < 4.0) rgb = vec3(0.0, x, c); + else if (h6 < 5.0) rgb = vec3(x, 0.0, c); + else rgb = vec3(c, 0.0, x); + + return rgb + m; +} + +// Compute how far a value is outside the [lo, hi] range, normalized. +// Returns 0 when inside, 1 when at or beyond the range width away. +float range_distance(float val, float lo, float hi) +{ + if (lo >= hi) return 1.0; // Degenerate range, no match + float below = max(lo - val, 0.0); + float above = max(val - hi, 0.0); + return max(below, above) / (hi - lo); +} + +// Same as range_distance but handles hue wrapping in [0, 1]. +float hue_distance(float h, float lo, float hi) +{ + if (lo < hi) { + // Normal range + return range_distance(h, lo, hi); + } + // Wrapped range (e.g. min=0.9, max=0.1 means red spanning 0) + // Inside if h >= lo OR h <= hi + if (h >= lo || h <= hi) return 0.0; + float dist_to_lo = min(abs(h - lo), 1.0 - abs(h - lo)); + float range = 1.0 - lo + hi; + return dist_to_lo / max(range, 0.001); +} diff --git a/src/xrt/compositor/shaders/layer.comp b/src/xrt/compositor/shaders/layer.comp index 528e0e0c9..01f921bf0 100644 --- a/src/xrt/compositor/shaders/layer.comp +++ b/src/xrt/compositor/shaders/layer.comp @@ -8,6 +8,7 @@ #extension GL_GOOGLE_include_directive : require #include "srgb.inc.glsl" +#include "hsv.inc.glsl" #include "layer_defines.inc.glsl" #define LAYER ubo.layers[layer] @@ -34,6 +35,18 @@ struct image_info uint _padding1; }; +// Must match render_chroma_key_info in C code. +struct chroma_key_info +{ + float hsv_min_h; + float hsv_min_s; + float hsv_min_v; + float hsv_max_h; + float hsv_max_s; + float hsv_max_v; + float curve; + float despill; +}; const float PI = acos(-1); @@ -75,6 +88,8 @@ struct Layer { // timewarp matrices mat4 transform; + // per-layer chroma key settings + chroma_key_info chroma_key; // for quad layers @@ -99,6 +114,45 @@ layout(set = 0, binding = 3, std140) uniform restrict Config Layer layers[RENDER_MAX_LAYERS]; } ubo; +vec4 apply_chroma_key(vec4 color, uint layer) +{ + float curve = LAYER.chroma_key.curve; + + if (curve <= 0.0) { + return color; // Chroma key disabled + } + + vec3 hsv_a = vec3(LAYER.chroma_key.hsv_min_h, LAYER.chroma_key.hsv_min_s, LAYER.chroma_key.hsv_min_v); + vec3 hsv_b = vec3(LAYER.chroma_key.hsv_max_h, LAYER.chroma_key.hsv_max_s, LAYER.chroma_key.hsv_max_v); + float despill = LAYER.chroma_key.despill; + + // Sort so S/V min/max are correct regardless of input order + // Hue is left unsorted to preserve wrapping semantics + vec3 hsv_min = vec3(hsv_a.x, min(hsv_a.y, hsv_b.y), min(hsv_a.z, hsv_b.z)); + vec3 hsv_max = vec3(hsv_b.x, max(hsv_a.y, hsv_b.y), max(hsv_a.z, hsv_b.z)); + + // Convert to sRGB then to HSV for color matching + vec3 srgb = do_color_correction ? from_linear_to_srgb(color.rgb) : color.rgb; + vec3 hsv = rgb_to_hsv(srgb); + + // Compute distance from the HSV range (0 = inside, >0 = outside) + float h_dist = hue_distance(hsv.x, hsv_min.x, hsv_max.x); + float s_dist = range_distance(hsv.y, hsv_min.y, hsv_max.y); + float v_dist = range_distance(hsv.z, hsv_min.z, hsv_max.z); + float dist = length(vec3(h_dist, s_dist, v_dist)); + + // Apply power curve: <1 = softer falloff, 1 = linear, >1 = harder cutoff + float chroma_alpha = clamp(pow(dist, curve), 0.0, 1.0); + + // Despill: reduce saturation of pixels near the key color + float despill_strength = despill * (1.0 - chroma_alpha); + hsv.y *= (1.0 - despill_strength); + vec3 rgb_despill = hsv_to_rgb(hsv); + color.rgb = do_color_correction ? from_srgb_to_linear(rgb_despill) : rgb_despill; + + // Pre-multiply RGB by the chroma alpha + return vec4(color.rgb * chroma_alpha, color.a * chroma_alpha); +} vec2 position_to_view_uv(ivec2 extent, uint ix, uint iy) { @@ -495,6 +549,7 @@ vec4 do_layers(vec2 view_uv) break; case LAYER_COMP_TYPE_PROJECTION: rgba = do_projection(view_uv, layer); + rgba = apply_chroma_key(rgba, layer); break; default: break; } diff --git a/src/xrt/compositor/shaders/srgb.inc.glsl b/src/xrt/compositor/shaders/srgb.inc.glsl index af1b90ed1..b4c52ff20 100644 --- a/src/xrt/compositor/shaders/srgb.inc.glsl +++ b/src/xrt/compositor/shaders/srgb.inc.glsl @@ -20,3 +20,21 @@ vec3 from_linear_to_srgb(vec3 linear_rgb) from_linear_to_srgb_channel(linear_rgb.b) ); } + +float from_srgb_to_linear_channel(float value) +{ + if (value <= 0.04045) { + return value / 12.92; + } else { + return pow((value + 0.055) / 1.055, 2.4); + } +} + +vec3 from_srgb_to_linear(vec3 srgb) +{ + return vec3( + from_srgb_to_linear_channel(srgb.r), + from_srgb_to_linear_channel(srgb.g), + from_srgb_to_linear_channel(srgb.b) + ); +} diff --git a/src/xrt/compositor/util/comp_render_cs.c b/src/xrt/compositor/util/comp_render_cs.c index a2b6e0d01..ff7313f6f 100644 --- a/src/xrt/compositor/util/comp_render_cs.c +++ b/src/xrt/compositor/util/comp_render_cs.c @@ -250,6 +250,19 @@ do_cs_projection_layer(const struct comp_layer *layer, ubo_data->layers[cur_layer + 0].image_info.depth_image_index = cur_image++; } + // Chroma key - per layer settings + const struct xrt_layer_chroma_key_data *ck = (layer_data->type == XRT_LAYER_PROJECTION_DEPTH) + ? &layer_data->depth.chroma_key + : &layer_data->proj.chroma_key; + ubo_data->layers[cur_layer].chroma_key.hsv_min_h = ck->hsv_min.h; + ubo_data->layers[cur_layer].chroma_key.hsv_min_s = ck->hsv_min.s; + ubo_data->layers[cur_layer].chroma_key.hsv_min_v = ck->hsv_min.v; + ubo_data->layers[cur_layer].chroma_key.hsv_max_h = ck->hsv_max.h; + ubo_data->layers[cur_layer].chroma_key.hsv_max_s = ck->hsv_max.s; + ubo_data->layers[cur_layer].chroma_key.hsv_max_v = ck->hsv_max.v; + ubo_data->layers[cur_layer].chroma_key.curve = ck->curve; + ubo_data->layers[cur_layer].chroma_key.despill = ck->despill; + set_post_transform_rect( // layer_data, // data &vd->sub.norm_rect, // src_norm_rect @@ -582,6 +595,11 @@ comp_render_cs_layer(struct render_compute *render, ubo_data->view = *target_view; ubo_data->pre_transform = *pre_transform; + // Initialize chroma key settings for all layers to zero + for (uint32_t i = 0; i < RENDER_MAX_LAYERS; i++) { + ubo_data->layers[i].chroma_key = (struct render_chroma_key_info){0}; + } + for (uint32_t c_layer_i = 0; c_layer_i < layer_count; c_layer_i++) { const struct comp_layer *layer = &layers[c_layer_i]; const struct xrt_layer_data *data = &layer->data; diff --git a/src/xrt/include/xrt/xrt_compositor.h b/src/xrt/include/xrt/xrt_compositor.h index 40c9be619..594ba4cae 100644 --- a/src/xrt/include/xrt/xrt_compositor.h +++ b/src/xrt/include/xrt/xrt_compositor.h @@ -178,6 +178,26 @@ enum xrt_blend_factor XRT_BLEND_FACTOR_MAX_ENUM_FB = 0x7FFFFFFF, }; + +/*! + * Chroma key parameters in HSV space. + * Alpha 0-1 interpolates between hsv_min and hsv_max + * with curve as a power curve defined by the curve exponent. + * + * Used for projection layers. + */ +struct xrt_layer_chroma_key_data +{ + //!< Minimum HSV bounds + struct xrt_colour_hsv_f32 hsv_min; + //! Maximum HSV bounds + struct xrt_colour_hsv_f32 hsv_max; + //! Power curve for alpha falloff (1.0 = linear) + float curve; + //! Despill strength (0.0 = none, 1.0 = full) + float despill; +}; + /*! * Advanced blend * provides explicit control over source and destination blend factors, @@ -232,6 +252,8 @@ struct xrt_layer_projection_view_data struct xrt_layer_projection_data { struct xrt_layer_projection_view_data v[XRT_MAX_VIEWS]; + + struct xrt_layer_chroma_key_data chroma_key; }; /*! @@ -269,6 +291,8 @@ struct xrt_layer_projection_depth_data struct xrt_layer_projection_view_data v[XRT_MAX_VIEWS]; struct xrt_layer_depth_data d[XRT_MAX_VIEWS]; + + struct xrt_layer_chroma_key_data chroma_key; }; /*! @@ -2403,6 +2427,22 @@ struct xrt_multi_compositor_control */ xrt_result_t (*set_z_order)(struct xrt_system_compositor *xsc, struct xrt_compositor *xc, int64_t z_order); + /*! + * Set the chroma key parameters for the base app's projection layers. + * This is used to punch holes through opaque projection layers and adjust their blend mode. + * Uses HSV min/max range for flexible color targeting. + * + * @param hsv_min Minimum HSV bounds + * @param hsv_max Maximum HSV bounds + * @param curve Power curve for alpha falloff + * @param despill Despill strength + */ + xrt_result_t (*set_base_chroma_key_params)(struct xrt_system_compositor *xsc, + struct xrt_colour_hsv_f32 hsv_min, + struct xrt_colour_hsv_f32 hsv_max, + float curve, + float despill); + /*! * Tell this client/session if the main application is visible or not. */ diff --git a/src/xrt/include/xrt/xrt_defines.h b/src/xrt/include/xrt/xrt_defines.h index 22541cba4..b227f0ca6 100644 --- a/src/xrt/include/xrt/xrt_defines.h +++ b/src/xrt/include/xrt/xrt_defines.h @@ -401,6 +401,19 @@ struct xrt_colour_rgb_f32 float b; }; +/*! + * A 3 element HSV colour with floating point channels. + * All values are in [0, 1] range, with hue wrapping at 1. + * + * @ingroup xrt_iface math + */ +struct xrt_colour_hsv_f32 +{ + float h; + float s; + float v; +}; + /*! * A 4 element colour with floating point channels. * -- 2.51.2