Skip to main content

par_term_render/renderer/shaders/
background.rs

1//! Background (custom) shader initialisation and runtime management.
2//!
3//! Covers the `CustomShaderRenderer` lifecycle: creation at startup via
4//! [`init_custom_shader`] and runtime enable/disable/reload operations
5//! exposed as `impl Renderer` methods.
6
7use super::super::Renderer;
8use super::{CustomShaderEnableParams, CustomShaderInitParams};
9use crate::cell_renderer::CellRenderer;
10use crate::custom_shader_renderer::CustomShaderRenderer;
11
12/// Initialize the custom shader renderer if configured.
13///
14/// Returns `(renderer, shader_path)` where both are `Some` if initialization succeeded.
15pub(super) fn init_custom_shader(
16    cell_renderer: &CellRenderer,
17    params: CustomShaderInitParams<'_>,
18) -> (Option<CustomShaderRenderer>, Option<String>) {
19    let CustomShaderInitParams {
20        size_width,
21        size_height,
22        window_padding,
23        path: custom_shader_path,
24        enabled: custom_shader_enabled,
25        animation: custom_shader_animation,
26        animation_speed: custom_shader_animation_speed,
27        window_opacity,
28        full_content: custom_shader_full_content,
29        brightness: custom_shader_brightness,
30        channel_paths: custom_shader_channel_paths,
31        cubemap_path: custom_shader_cubemap_path,
32        custom_uniforms,
33        use_background_as_channel0,
34        background_channel0_blend_mode,
35        auto_dim_under_text,
36        auto_dim_strength,
37    } = params;
38    log::info!(
39        "[shader-init] init_custom_shader: enabled={}, path={:?}",
40        custom_shader_enabled,
41        custom_shader_path
42    );
43    if !custom_shader_enabled {
44        log::info!("[shader-init] Skipping: custom_shader_enabled=false");
45        return (None, None);
46    }
47
48    let Some(shader_path) = custom_shader_path else {
49        log::info!("[shader-init] Skipping: custom_shader_path is None");
50        return (None, None);
51    };
52
53    let path = par_term_config::Config::shader_path(shader_path);
54    match CustomShaderRenderer::new(
55        cell_renderer.device(),
56        cell_renderer.queue(),
57        crate::custom_shader_renderer::CustomShaderRendererConfig {
58            surface_format: cell_renderer.surface_format(),
59            shader_path: &path,
60            width: size_width,
61            height: size_height,
62            animation_enabled: custom_shader_animation,
63            animation_speed: custom_shader_animation_speed,
64            window_opacity,
65            full_content_mode: custom_shader_full_content,
66            channel_paths: custom_shader_channel_paths,
67            cubemap_path: custom_shader_cubemap_path,
68            custom_uniforms,
69            background_channel0_blend_mode,
70        },
71    ) {
72        Ok(mut renderer) => {
73            renderer.update_cell_dimensions(
74                cell_renderer.cell_width(),
75                cell_renderer.cell_height(),
76                window_padding,
77            );
78            renderer.set_scale_factor(cell_renderer.scale_factor);
79            renderer.set_brightness(custom_shader_brightness);
80            renderer.set_auto_dim_under_text(auto_dim_under_text, auto_dim_strength);
81
82            // Apply use_background_as_channel0 setting
83            if use_background_as_channel0 {
84                // Sync background texture and set flag
85                let bg_texture = cell_renderer.get_background_as_channel_texture();
86                renderer.set_background_texture(cell_renderer.device(), bg_texture);
87                renderer.update_use_background_as_channel0(
88                    cell_renderer.device(),
89                    use_background_as_channel0,
90                );
91            }
92
93            log::info!(
94                "[SHADER] Custom shader renderer initialized from: {} (use_bg_as_ch0={})",
95                path.display(),
96                use_background_as_channel0
97            );
98            (Some(renderer), Some(shader_path.to_string()))
99        }
100        Err(e) => {
101            log::info!(
102                "[SHADER] ERROR: Failed to load custom shader '{}': {}",
103                path.display(),
104                e
105            );
106            (None, None)
107        }
108    }
109}
110
111// ============================================================================
112// Background shader impl Renderer methods
113// ============================================================================
114
115impl Renderer {
116    /// Enable or disable animation for the custom shader at runtime
117    pub fn set_custom_shader_animation(&mut self, enabled: bool) {
118        if let Some(ref mut custom_shader) = self.custom_shader_renderer {
119            custom_shader.set_animation_enabled(enabled);
120            self.dirty = true;
121        }
122    }
123
124    /// Update custom shader uniform values keyed by control name.
125    pub fn set_custom_shader_uniform_values(
126        &mut self,
127        values: std::collections::BTreeMap<String, par_term_config::ShaderUniformValue>,
128    ) {
129        if let Some(ref mut custom_shader) = self.custom_shader_renderer {
130            custom_shader.set_custom_uniform_values(values);
131            self.dirty = true;
132        }
133    }
134
135    /// Reload the custom shader from source code.
136    ///
137    /// Compiles the new shader source and replaces the current pipeline.
138    /// If compilation fails, returns an error and the old shader remains active.
139    pub fn reload_shader_from_source(
140        &mut self,
141        source: &str,
142    ) -> Result<(), crate::error::RenderError> {
143        if let Some(ref mut custom_shader) = self.custom_shader_renderer {
144            custom_shader
145                .reload_from_source(self.cell_renderer.device(), source, "editor")
146                .map_err(|e| crate::error::RenderError::NoActiveShader(format!("{:#}", e)))?;
147            self.dirty = true;
148            Ok(())
149        } else {
150            Err(crate::error::RenderError::NoActiveShader(
151                "No custom shader is currently loaded. Enable a custom shader first.".to_string(),
152            ))
153        }
154    }
155
156    /// Enable/disable custom shader at runtime.
157    ///
158    /// When enabling, tries to (re)load the shader from the given path; when disabling,
159    /// drops the renderer instance.
160    pub fn set_custom_shader_enabled(
161        &mut self,
162        params: CustomShaderEnableParams<'_>,
163    ) -> Result<(), String> {
164        let CustomShaderEnableParams {
165            enabled,
166            shader_path,
167            window_opacity,
168            animation_enabled,
169            animation_speed,
170            full_content,
171            brightness,
172            channel_paths,
173            cubemap_path,
174            custom_uniforms,
175            background_channel0_blend_mode,
176            auto_dim_under_text,
177            auto_dim_strength,
178        } = params;
179        match (enabled, shader_path) {
180            (true, Some(path)) => {
181                // Check if the shader path has changed
182                let path_changed = self.custom_shader_path.as_deref() != Some(path);
183
184                // If we already have a shader renderer and path hasn't changed, just update flags and textures
185                if let Some(renderer) = &mut self.custom_shader_renderer
186                    && !path_changed
187                {
188                    renderer.set_animation_enabled(animation_enabled);
189                    renderer.set_animation_speed(animation_speed);
190                    renderer.set_opacity(window_opacity);
191                    renderer.set_full_content_mode(full_content);
192                    renderer.set_brightness(brightness);
193                    renderer.set_auto_dim_under_text(auto_dim_under_text, auto_dim_strength);
194                    renderer.set_background_channel0_blend_mode(background_channel0_blend_mode);
195                    renderer.set_custom_uniform_values(custom_uniforms.clone());
196
197                    // Update channel textures (they may have changed even if shader path didn't)
198                    for (i, path) in channel_paths.iter().enumerate() {
199                        if let Err(e) = renderer.update_channel_texture(
200                            self.cell_renderer.device(),
201                            self.cell_renderer.queue(),
202                            (i + 1) as u8, // channel indices are 1-4
203                            path.as_deref(),
204                        ) {
205                            log::warn!("Failed to update channel {} texture: {}", i, e);
206                        }
207                    }
208
209                    // Update cubemap if provided
210                    if let Some(cubemap) = cubemap_path
211                        && let Err(e) = renderer.update_cubemap(
212                            self.cell_renderer.device(),
213                            self.cell_renderer.queue(),
214                            Some(cubemap),
215                        )
216                    {
217                        log::warn!("Failed to update cubemap: {}", e);
218                    }
219
220                    return Ok(());
221                }
222
223                let shader_path_full = par_term_config::Config::shader_path(path);
224                match CustomShaderRenderer::new(
225                    self.cell_renderer.device(),
226                    self.cell_renderer.queue(),
227                    crate::custom_shader_renderer::CustomShaderRendererConfig {
228                        surface_format: self.cell_renderer.surface_format(),
229                        shader_path: &shader_path_full,
230                        width: self.size.width,
231                        height: self.size.height,
232                        animation_enabled,
233                        animation_speed,
234                        window_opacity,
235                        full_content_mode: full_content,
236                        channel_paths,
237                        cubemap_path,
238                        custom_uniforms,
239                        background_channel0_blend_mode,
240                    },
241                ) {
242                    Ok(mut renderer) => {
243                        // Sync cell dimensions for cursor position calculation
244                        renderer.update_cell_dimensions(
245                            self.cell_renderer.cell_width(),
246                            self.cell_renderer.cell_height(),
247                            self.cell_renderer.window_padding(),
248                        );
249                        // Sync DPI scale factor for cursor sizing
250                        renderer.set_scale_factor(self.cell_renderer.scale_factor);
251                        // Apply brightness setting
252                        renderer.set_brightness(brightness);
253                        renderer.set_auto_dim_under_text(auto_dim_under_text, auto_dim_strength);
254                        // Sync keep_text_opaque from cell renderer
255                        renderer.set_keep_text_opaque(self.cell_renderer.keep_text_opaque());
256                        // Pass background color but don't activate solid color mode
257                        // Custom shaders handle their own background
258                        renderer.set_background_color(
259                            self.cell_renderer.solid_background_color(),
260                            false,
261                        );
262                        log::info!(
263                            "[SHADER] Custom shader enabled at runtime: {}",
264                            shader_path_full.display()
265                        );
266                        self.custom_shader_renderer = Some(renderer);
267                        self.custom_shader_path = Some(path.to_string());
268
269                        // When background shader is enabled, cursor shader should not have its own background
270                        self.sync_cursor_shader_background_state();
271
272                        self.dirty = true;
273                        Ok(())
274                    }
275                    Err(e) => {
276                        let error_msg = format!(
277                            "Failed to load shader '{}': {}",
278                            shader_path_full.display(),
279                            e
280                        );
281                        log::info!("[SHADER] ERROR: {}", error_msg);
282                        Err(error_msg)
283                    }
284                }
285            }
286            _ => {
287                if self.custom_shader_renderer.is_some() {
288                    log::info!("[SHADER] Custom shader disabled at runtime");
289                }
290                self.custom_shader_renderer = None;
291                self.custom_shader_path = None;
292
293                // When background shader is disabled, cursor shader should get its own background back
294                self.sync_cursor_shader_background_state();
295
296                self.dirty = true;
297                Ok(())
298            }
299        }
300    }
301
302    /// Set whether to use the background image as iChannel0 for the custom shader.
303    pub fn set_use_background_as_channel0(&mut self, use_background: bool) {
304        if let Some(ref mut custom_shader) = self.custom_shader_renderer {
305            custom_shader
306                .update_use_background_as_channel0(self.cell_renderer.device(), use_background);
307            self.dirty = true;
308        }
309    }
310
311    /// Update the background texture for use as iChannel0 in shaders.
312    ///
313    /// Call this whenever the background image changes to sync the shader's
314    /// channel0 texture. Only has effect if use_background_as_channel0 is enabled.
315    pub fn sync_background_texture_to_shader(&mut self) {
316        if let Some(ref mut custom_shader) = self.custom_shader_renderer {
317            let bg_texture = self.cell_renderer.get_background_as_channel_texture();
318            custom_shader.set_background_texture(self.cell_renderer.device(), bg_texture);
319            self.dirty = true;
320        }
321    }
322
323    /// Update both the use_background_as_channel0 flag and sync the texture.
324    pub fn update_background_as_channel0(&mut self, use_background: bool) {
325        if let Some(ref mut custom_shader) = self.custom_shader_renderer {
326            // Always sync the background texture first - it may have changed
327            let bg_texture = self.cell_renderer.get_background_as_channel_texture();
328            custom_shader.set_background_texture(self.cell_renderer.device(), bg_texture);
329
330            // Then update the flag - this will recreate bind group if flag actually changed
331            custom_shader
332                .update_use_background_as_channel0(self.cell_renderer.device(), use_background);
333
334            self.dirty = true;
335        }
336    }
337
338    /// Update background as channel0 with solid color support.
339    ///
340    /// Handles the case where background_mode is Color and we need to
341    /// create a solid color texture to pass as iChannel0 instead of an image.
342    pub fn update_background_as_channel0_with_mode(
343        &mut self,
344        use_background: bool,
345        background_mode: par_term_config::BackgroundMode,
346        color: [u8; 3],
347    ) {
348        if let Some(ref mut custom_shader) = self.custom_shader_renderer {
349            let bg_texture = match background_mode {
350                par_term_config::BackgroundMode::Default => {
351                    log::info!("update_background_as_channel0_with_mode: Default mode, no texture");
352                    None
353                }
354                par_term_config::BackgroundMode::Color => {
355                    log::info!(
356                        "update_background_as_channel0_with_mode: Color mode, creating solid color texture RGB({},{},{})",
357                        color[0],
358                        color[1],
359                        color[2]
360                    );
361                    Some(self.cell_renderer.get_solid_color_as_channel_texture(color))
362                }
363                par_term_config::BackgroundMode::Image => {
364                    let tex = self.cell_renderer.get_background_as_channel_texture();
365                    log::info!(
366                        "update_background_as_channel0_with_mode: Image mode, texture={}",
367                        if tex.is_some() { "Some" } else { "None" }
368                    );
369                    tex
370                }
371            };
372
373            let has_texture = bg_texture.is_some();
374            custom_shader.set_background_texture(self.cell_renderer.device(), bg_texture);
375            custom_shader
376                .update_use_background_as_channel0(self.cell_renderer.device(), use_background);
377
378            log::info!(
379                "update_background_as_channel0_with_mode: use_background={}, has_texture={}",
380                use_background,
381                has_texture
382            );
383
384            self.dirty = true;
385        }
386    }
387}