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