par_term_render/renderer/rendering.rs
1// ARC-009: `take_screenshot` and its shader chain moved to `renderer/screenshot.rs`.
2// If this file needs cutting again, the next seams are:
3//
4// split_layout.rs — Split-pane geometry (render_split_panes_with_data)
5// separator_draw.rs — Separator-mark draw calls; QA-001 and QA-008 affect this area
6//
7// Tracking: Issue ARC-009 in AUDIT.md.
8
9// ARC-004: `render_split_panes` used to end every pane in its own `queue.submit`.
10// Panes are now built in one pass and drawn from a single command encoder — one
11// render pass per pane, one submit per pane batch — so pane submits per frame went
12// from N to 1. The exception is auto-dim without full-content mode, the one
13// configuration that renders the panes twice: once into the shader's intermediate
14// texture as a content mask, then again onto the content view. That is 2.
15//
16// That was unsafe until three single-instance GPU resources were made per-pane,
17// each of which had been correct only because a submit separated one pane's write
18// from the next's:
19//
20// 1. bg_instance_buffer / text_instance_buffer — every pane rebuilt them at
21// offset 0. Panes now suballocate: `begin_pane_batch` resets the cursors,
22// each build appends at them, and `emit_three_phase_draw_calls` takes
23// absolute ranges. The buffers stay bound at offset 0, so no vertex-buffer
24// offset alignment is involved and there is still one draw ordering.
25// 2. bg_state.pane_bg_uniform_cache — was keyed by image path while the uniform
26// carries pane position and size, so two panes sharing an image aliased even
27// before batching. Now keyed by pane index, with the path stored in the entry
28// so the bind group is rebuilt only when a pane's image changes.
29// 3. The scrollbar's thumb, track and mark uniforms (scrollbar.rs) — one set for
30// the whole renderer. Now one slot per pane. Hit testing stays
31// single-instance and follows the *focused* pane rather than whichever pane
32// was updated last.
33//
34// Capacity: `max_bg_instances` is sized for one full-window grid, which is not
35// enough once panes are simultaneously resident — each pane also needs a viewport
36// fill quad, its own separator rows and its own cursor overlay slots.
37// `begin_pane_batch` sums `pane_instance_capacity` over the frame's panes, keeps
38// the single-grid requirement as a floor, and grows the buffers when needed. The
39// emitters' bounds guards drop instances silently, so a build that reaches the cap
40// logs an error (once per allocation) instead of quietly losing content.
41//
42// Not measured: verifying the frame-rate effect needs a full workspace build, which
43// this change was not able to run. No speedup is claimed.
44
45use crate::cell_renderer::{PaneViewport, pane_instance_capacity};
46use anyhow::Result;
47
48use super::{
49 DividerRenderInfo, PaneDividerSettings, PaneRenderInfo, PaneTitleInfo, Renderer, SeparatorMark,
50 fill_visible_separator_marks,
51};
52
53// This file contains the multi-pane frame-level helper `render_split_panes`.
54
55fn should_populate_terminal_intermediate_texture(
56 full_content_mode: bool,
57 auto_dim_under_text: bool,
58 auto_dim_strength: f32,
59) -> bool {
60 full_content_mode || (auto_dim_under_text && auto_dim_strength > 0.0)
61}
62
63/// Per-batch settings for [`Renderer::prepare_and_draw_panes`].
64///
65/// The capacities are the summed `pane_instance_capacity` of the panes in the
66/// batch; the two flags depend on which target the batch is being drawn to.
67struct PaneBatchSettings {
68 bg_capacity: usize,
69 text_capacity: usize,
70 skip_background_image: bool,
71 fill_default_bg_cells: bool,
72}
73
74/// The pane content of one composited frame.
75///
76/// QA-011: this is the input [`Renderer::composite_panes`] needs, and it is all
77/// an offscreen capture can supply. [`SplitPanesRenderParams`] adds the two
78/// surface-only concerns — the egui overlay and its opacity override — which
79/// have no meaning off-surface: `egui::FullOutput` is produced once per frame by
80/// the live egui pass and consumed by value there, so a capture taken between
81/// frames has none to hand over.
82pub struct PaneCaptureParams<'a> {
83 pub panes: &'a [PaneRenderInfo<'a>],
84 pub dividers: &'a [DividerRenderInfo],
85 pub pane_titles: &'a [PaneTitleInfo],
86 pub focused_viewport: Option<&'a PaneViewport>,
87 pub divider_settings: &'a PaneDividerSettings,
88}
89
90/// Parameters for [`Renderer::render_split_panes`].
91pub struct SplitPanesRenderParams<'a> {
92 pub panes: &'a [PaneRenderInfo<'a>],
93 pub dividers: &'a [DividerRenderInfo],
94 pub pane_titles: &'a [PaneTitleInfo],
95 pub focused_viewport: Option<&'a PaneViewport>,
96 pub divider_settings: &'a PaneDividerSettings,
97 pub egui_data: Option<(egui::FullOutput, &'a egui::Context)>,
98 pub force_egui_opaque: bool,
99}
100
101impl Renderer {
102 /// Update one pane's scrollbar slot.
103 ///
104 /// Hit-test geometry is single-instance and follows the focused pane, so a
105 /// focused pane without a scrollbar has to drop it rather than leave another
106 /// pane's bounds in place.
107 fn update_pane_scrollbar(&mut self, index: usize, pane: &PaneRenderInfo<'_>) {
108 if pane.show_scrollbar {
109 let total_lines = pane.scrollback_len + pane.grid_size.1;
110 self.cell_renderer.update_scrollbar_for_pane(
111 index,
112 pane.scroll_offset,
113 pane.grid_size.1,
114 total_lines,
115 &pane.marks,
116 &pane.viewport,
117 );
118 } else if pane.viewport.focused {
119 self.cell_renderer.scrollbar.clear_hit_state();
120 }
121 }
122
123 /// Build every pane's instances and scrollbar slot, then draw the whole batch
124 /// to `target_view` from one command encoder (ARC-004).
125 fn prepare_and_draw_panes(
126 &mut self,
127 target_view: &wgpu::TextureView,
128 panes: &[PaneRenderInfo<'_>],
129 settings: PaneBatchSettings,
130 ) -> Result<()> {
131 self.cell_renderer
132 .begin_pane_batch(settings.bg_capacity, settings.text_capacity);
133
134 // `scratch` is declared outside the loop so its capacity is preserved
135 // across iterations, avoiding a per-pane heap allocation.
136 let mut scratch: Vec<SeparatorMark> = Vec::new();
137 let mut prepared = Vec::with_capacity(panes.len());
138 for (index, pane) in panes.iter().enumerate() {
139 self.update_pane_scrollbar(index, pane);
140 fill_visible_separator_marks(
141 &mut scratch,
142 &pane.marks,
143 pane.scrollback_len,
144 pane.scroll_offset,
145 pane.grid_size.1,
146 );
147 prepared.push(self.cell_renderer.prepare_pane(
148 index,
149 crate::cell_renderer::PaneRenderViewParams {
150 viewport: &pane.viewport,
151 cells: pane.cells,
152 cols: pane.grid_size.0,
153 rows: pane.grid_size.1,
154 cursor_pos: pane.cursor_pos,
155 cursor_opacity: pane.cursor_opacity,
156 show_scrollbar: pane.show_scrollbar,
157 clear_first: false, // Don't clear - the target was already cleared
158 skip_background_image: settings.skip_background_image,
159 fill_default_bg_cells: settings.fill_default_bg_cells,
160 separator_marks: &scratch,
161 pane_background: pane.background.as_ref(),
162 },
163 )?);
164 }
165
166 self.cell_renderer
167 .draw_prepared_panes(target_view, &prepared);
168 Ok(())
169 }
170
171 /// Load any per-pane background textures that aren't cached yet.
172 ///
173 /// Kept out of [`Renderer::composite_panes`] so the live path still runs it
174 /// *before* acquiring the swapchain drawable — the first frame using a pane
175 /// background reads it from disk, and holding the drawable across that read
176 /// would stall presentation.
177 fn preload_pane_backgrounds(&mut self, panes: &[PaneRenderInfo<'_>]) {
178 for pane in panes.iter() {
179 if let Some(ref bg) = pane.background
180 && let Some(ref path) = bg.image_path
181 && let Err(e) = self.cell_renderer.load_pane_background(path)
182 {
183 log::error!("Failed to load pane background '{}': {}", path, e);
184 }
185 }
186 }
187
188 /// Composite one frame of pane content onto `final_view`.
189 ///
190 /// Covers the shader chain, every pane's cells and inline graphics, dividers,
191 /// pane titles, the visual bell and the focus indicator, ending with the
192 /// cursor-shader composite onto `final_view`.
193 ///
194 /// QA-011: deliberately excludes the surface-only tail (egui overlay,
195 /// opaque-alpha stamp, `present`) and the `dirty` bookkeeping, so
196 /// [`Renderer::take_screenshot`] can reuse it — a capture must render even
197 /// when the renderer is clean, and must not consume the live path's dirty
198 /// flag. Callers must run [`Renderer::preload_pane_backgrounds`] first.
199 fn composite_panes(
200 &mut self,
201 p: PaneCaptureParams<'_>,
202 final_view: &wgpu::TextureView,
203 ) -> Result<()> {
204 let PaneCaptureParams {
205 panes,
206 dividers,
207 pane_titles,
208 focused_viewport,
209 divider_settings,
210 } = p;
211
212 let has_custom_shader = self.custom_shader_renderer.is_some();
213 // Only use cursor shader if it's enabled and not disabled for alt screen
214 let use_cursor_shader =
215 self.cursor_shader_renderer.is_some() && !self.cursor_shader_disabled_for_alt_screen;
216
217 // Instance-buffer capacity this frame's panes need, all resident at once.
218 let (batch_bg_capacity, batch_text_capacity) =
219 panes.iter().fold((0usize, 0usize), |(bg, text), pane| {
220 let (pane_bg, pane_text) =
221 pane_instance_capacity(pane.grid_size.0, pane.grid_size.1);
222 (bg + pane_bg, text + pane_text)
223 });
224
225 // When cursor shader is active, render all content to its intermediate texture.
226 // The cursor shader will then composite the result onto `final_view`.
227 let cursor_intermediate: Option<wgpu::TextureView> = if use_cursor_shader {
228 Some(
229 self.cursor_shader_renderer
230 .as_ref()
231 .ok_or_else(|| {
232 crate::error::RenderError::ShaderUnavailable(
233 "cursor_shader_renderer unavailable (GPU device loss?)".into(),
234 )
235 })?
236 .intermediate_texture_view()
237 .clone(),
238 )
239 } else {
240 None
241 };
242 // Content render target: cursor shader intermediate (if active) or the
243 // final target directly
244 let content_view = cursor_intermediate.as_ref().unwrap_or(final_view);
245
246 // Clear color for content rendering. When cursor shader will apply opacity,
247 // use non-premultiplied color so opacity isn't applied twice.
248 let opacity = self.cell_renderer.window_opacity as f64;
249 let clear_color = if self.cell_renderer.pipelines.bg_image_bind_group.is_some() {
250 wgpu::Color::TRANSPARENT
251 } else if use_cursor_shader {
252 // Cursor shader applies opacity — use full-opacity background
253 wgpu::Color {
254 r: self.cell_renderer.background_color[0] as f64,
255 g: self.cell_renderer.background_color[1] as f64,
256 b: self.cell_renderer.background_color[2] as f64,
257 a: 1.0,
258 }
259 } else {
260 wgpu::Color {
261 r: self.cell_renderer.background_color[0] as f64 * opacity,
262 g: self.cell_renderer.background_color[1] as f64 * opacity,
263 b: self.cell_renderer.background_color[2] as f64 * opacity,
264 a: opacity,
265 }
266 };
267
268 // Determine if the shader needs terminal pixels in iChannel4.
269 // Full-content mode processes terminal content directly; auto-dim uses the same
270 // texture as a content mask so it can dim only beneath text/content pixels.
271 let (full_content_mode, populate_terminal_intermediate_texture) = self
272 .custom_shader_renderer
273 .as_ref()
274 .map(|s| {
275 let full_content_mode = s.full_content_mode();
276 (
277 full_content_mode,
278 should_populate_terminal_intermediate_texture(
279 full_content_mode,
280 s.auto_dim_under_text,
281 s.auto_dim_strength,
282 ),
283 )
284 })
285 .unwrap_or((false, false));
286
287 // Render pane content to the shader's intermediate texture BEFORE running the
288 // shader when it needs terminal pixels via iChannel4.
289 // This must happen outside the `custom_shader_renderer` mutable borrow scope
290 // because rendering panes requires `&mut self`.
291 if populate_terminal_intermediate_texture {
292 let custom_shader = self.custom_shader_renderer.as_mut().ok_or_else(|| {
293 crate::error::RenderError::ShaderUnavailable(
294 "custom_shader_renderer unavailable for iChannel4 content (GPU device loss?)"
295 .into(),
296 )
297 })?;
298 custom_shader.clear_intermediate_texture(
299 self.cell_renderer.device(),
300 self.cell_renderer.queue(),
301 );
302 let intermediate_view = custom_shader.intermediate_texture_view().clone();
303
304 // Render each pane's content to the intermediate texture.
305 // Scrollbar geometry is updated per-pane so unfocused panes can also
306 // show their own scrollbar.
307 self.prepare_and_draw_panes(
308 &intermediate_view,
309 panes,
310 PaneBatchSettings {
311 bg_capacity: batch_bg_capacity,
312 text_capacity: batch_text_capacity,
313 skip_background_image: true, // Shader handles background
314 fill_default_bg_cells: false, // Shader shows through default-bg cells
315 },
316 )?;
317
318 // Render inline graphics to intermediate so shader can process them
319 for pane in panes.iter() {
320 if !pane.graphics.is_empty() || !pane.virtual_placements.is_empty() {
321 self.render_pane_sixel_graphics(
322 &intermediate_view,
323 &pane.viewport,
324 &pane.graphics,
325 pane.scroll_offset,
326 pane.scrollback_len,
327 pane.grid_size.1,
328 pane.cells,
329 pane.grid_size.0,
330 &pane.virtual_placements,
331 )?;
332 }
333 }
334 }
335
336 // If custom shader is enabled, render it to the content target
337 // (the shader's render pass will handle clearing the target)
338 if let Some(ref mut custom_shader) = self.custom_shader_renderer {
339 if !populate_terminal_intermediate_texture {
340 // Background-only mode without auto-dim: clear intermediate texture
341 // (shader doesn't need terminal content, panes will be rendered on top)
342 custom_shader.clear_intermediate_texture(
343 self.cell_renderer.device(),
344 self.cell_renderer.queue(),
345 );
346 }
347
348 // Render shader effect. When cursor shader is chained, render to cursor
349 // shader's intermediate without applying opacity (cursor shader will do it).
350 // When no cursor shader, render directly to surface with opacity applied.
351 custom_shader.render_with_clear_color(
352 self.cell_renderer.device(),
353 self.cell_renderer.queue(),
354 content_view,
355 !use_cursor_shader, // Apply opacity only when not chaining to cursor shader
356 clear_color,
357 )?;
358 } else {
359 // No custom shader - just clear the content target with background color
360 let mut encoder = self.cell_renderer.device().create_command_encoder(
361 &wgpu::CommandEncoderDescriptor {
362 label: Some("split pane clear encoder"),
363 },
364 );
365
366 {
367 let _clear_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
368 label: Some("surface clear pass"),
369 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
370 view: content_view,
371 resolve_target: None,
372 ops: wgpu::Operations {
373 load: wgpu::LoadOp::Clear(clear_color),
374 store: wgpu::StoreOp::Store,
375 },
376 depth_slice: None,
377 })],
378 depth_stencil_attachment: None,
379 timestamp_writes: None,
380 occlusion_query_set: None,
381 multiview_mask: None,
382 });
383 }
384
385 self.cell_renderer
386 .queue()
387 .submit(std::iter::once(encoder.finish()));
388 }
389
390 // Render background image (full-screen, after shader but before panes)
391 // Skip if custom shader is handling the background.
392 // Also skip if any pane has a per-pane background configured -
393 // per-pane backgrounds are rendered individually in render_pane_to_view.
394 let any_pane_has_background = panes.iter().any(|p| p.background.is_some());
395 let has_background_image = if !has_custom_shader && !any_pane_has_background {
396 self.cell_renderer
397 .render_background_only(content_view, false)?
398 } else {
399 false
400 };
401
402 // In full content mode, panes were already rendered to the shader's intermediate
403 // texture and the shader output includes the processed terminal content.
404 // Skip re-rendering panes to the content view.
405 if !full_content_mode {
406 // Render each pane's content (skip background image since we rendered it full-screen).
407 // Scrollbar geometry is updated per-pane so unfocused panes can also
408 // show their own scrollbar.
409 self.prepare_and_draw_panes(
410 content_view,
411 panes,
412 PaneBatchSettings {
413 bg_capacity: batch_bg_capacity,
414 text_capacity: batch_text_capacity,
415 skip_background_image: has_background_image || has_custom_shader,
416 // Only fill gaps in bg-image mode; shader shows through
417 fill_default_bg_cells: has_background_image,
418 },
419 )?;
420
421 // Render inline graphics (Sixel/iTerm2/Kitty) for each pane, clipped to its bounds
422 for pane in panes {
423 if !pane.graphics.is_empty() || !pane.virtual_placements.is_empty() {
424 self.render_pane_sixel_graphics(
425 content_view,
426 &pane.viewport,
427 &pane.graphics,
428 pane.scroll_offset,
429 pane.scrollback_len,
430 pane.grid_size.1,
431 pane.cells,
432 pane.grid_size.0,
433 &pane.virtual_placements,
434 )?;
435 }
436 }
437 }
438
439 // Render dividers between panes
440 if !dividers.is_empty() {
441 self.render_dividers(content_view, dividers, divider_settings)?;
442 }
443
444 // Render pane title bars (background + text)
445 if !pane_titles.is_empty() {
446 self.render_pane_titles(content_view, pane_titles)?;
447 }
448
449 // Render visual bell overlay (fullscreen flash)
450 if self.cell_renderer.visual_bell_intensity > 0.0 {
451 let uniforms: [f32; 8] = [
452 -1.0, // position.x (NDC left)
453 -1.0, // position.y (NDC bottom)
454 2.0, // size.x (full width in NDC)
455 2.0, // size.y (full height in NDC)
456 self.cell_renderer.visual_bell_color[0], // color.r
457 self.cell_renderer.visual_bell_color[1], // color.g
458 self.cell_renderer.visual_bell_color[2], // color.b
459 self.cell_renderer.visual_bell_intensity, // color.a (intensity)
460 ];
461 self.cell_renderer.queue().write_buffer(
462 &self.cell_renderer.buffers.visual_bell_uniform_buffer,
463 0,
464 bytemuck::cast_slice(&uniforms),
465 );
466
467 let mut encoder = self.cell_renderer.device().create_command_encoder(
468 &wgpu::CommandEncoderDescriptor {
469 label: Some("visual bell encoder"),
470 },
471 );
472 {
473 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
474 label: Some("visual bell pass"),
475 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
476 view: content_view,
477 resolve_target: None,
478 ops: wgpu::Operations {
479 load: wgpu::LoadOp::Load,
480 store: wgpu::StoreOp::Store,
481 },
482 depth_slice: None,
483 })],
484 depth_stencil_attachment: None,
485 timestamp_writes: None,
486 occlusion_query_set: None,
487 multiview_mask: None,
488 });
489 render_pass.set_pipeline(&self.cell_renderer.pipelines.visual_bell_pipeline);
490 render_pass.set_bind_group(
491 0,
492 &self.cell_renderer.pipelines.visual_bell_bind_group,
493 &[],
494 );
495 render_pass.draw(0..4, 0..1); // 4 vertices = triangle strip quad
496 }
497 self.cell_renderer
498 .queue()
499 .submit(std::iter::once(encoder.finish()));
500 }
501
502 // Render focus indicator around focused pane (only if multiple panes)
503 if panes.len() > 1
504 && let Some(viewport) = focused_viewport
505 {
506 self.render_focus_indicator(content_view, viewport, divider_settings)?;
507 }
508
509 // Apply cursor shader if active: composite content to the final target
510 if use_cursor_shader {
511 self.cursor_shader_renderer
512 .as_mut()
513 .ok_or_else(|| crate::error::RenderError::ShaderUnavailable(
514 "cursor_shader_renderer unavailable during final composite (GPU device loss?)".into(),
515 ))?
516 .render(
517 self.cell_renderer.device(),
518 self.cell_renderer.queue(),
519 final_view,
520 true, // Apply opacity - final render to the target
521 )?;
522 }
523
524 Ok(())
525 }
526
527 /// Render split panes with dividers and focus indicator
528 ///
529 /// This is the main entry point for rendering a split pane layout.
530 /// It handles:
531 /// 1. Clearing the surface
532 /// 2. Rendering each pane's content
533 /// 3. Rendering dividers between panes
534 /// 4. Rendering focus indicator around the focused pane
535 /// 5. Rendering egui overlay if provided
536 /// 6. Presenting the surface
537 ///
538 /// Steps 1–4 are shared with [`Renderer::take_screenshot`] via
539 /// [`Renderer::composite_panes`]; this method adds the surface acquisition,
540 /// the egui overlay, presentation and the `dirty` bookkeeping.
541 ///
542 /// # Arguments
543 /// * `panes` - List of panes to render with their viewport info
544 /// * `dividers` - List of dividers between panes with hover state
545 /// * `focused_viewport` - Viewport of the focused pane (for focus indicator)
546 /// * `divider_settings` - Settings for divider and focus indicator appearance
547 /// * `egui_data` - Optional egui overlay data
548 /// * `force_egui_opaque` - Force egui to render at full opacity
549 ///
550 /// # Returns
551 /// `true` if rendering was performed, `false` if skipped
552 pub fn render_split_panes(&mut self, params: SplitPanesRenderParams<'_>) -> Result<bool> {
553 let SplitPanesRenderParams {
554 panes,
555 dividers,
556 pane_titles,
557 focused_viewport,
558 divider_settings,
559 egui_data,
560 force_egui_opaque,
561 } = params;
562 // Check if we need to render
563 let force_render = self.needs_continuous_render();
564 if !self.dirty && !force_render && egui_data.is_none() {
565 return Ok(false);
566 }
567
568 self.preload_pane_backgrounds(panes);
569
570 // Get the surface texture
571 let surface_texture = match self.cell_renderer.surface.get_current_texture() {
572 wgpu::CurrentSurfaceTexture::Success(t)
573 | wgpu::CurrentSurfaceTexture::Suboptimal(t) => t,
574 other => return Err(crate::error::RenderError::Surface(format!("{other:?}")).into()),
575 };
576 let surface_view = surface_texture
577 .texture
578 .create_view(&wgpu::TextureViewDescriptor::default());
579
580 self.composite_panes(
581 PaneCaptureParams {
582 panes,
583 dividers,
584 pane_titles,
585 focused_viewport,
586 divider_settings,
587 },
588 &surface_view,
589 )?;
590
591 // Render egui overlay if provided
592 if let Some((egui_output, egui_ctx)) = egui_data {
593 self.render_egui(&surface_texture, egui_output, egui_ctx, force_egui_opaque)?;
594 }
595
596 // Ensure opaque surface when window_opacity == 1.0 (skipped for transparent windows)
597 self.cell_renderer.render_opaque_alpha(&surface_texture)?;
598
599 // Present the surface
600 surface_texture.present();
601
602 self.dirty = false;
603 Ok(true)
604 }
605
606 /// Composite a frame of pane content into an offscreen target (QA-011).
607 ///
608 /// Used by [`Renderer::take_screenshot`]; kept next to the live path so the
609 /// two stay in step.
610 pub(super) fn composite_panes_offscreen(
611 &mut self,
612 params: PaneCaptureParams<'_>,
613 target_view: &wgpu::TextureView,
614 ) -> Result<()> {
615 self.preload_pane_backgrounds(params.panes);
616 self.composite_panes(params, target_view)
617 }
618}
619
620#[cfg(test)]
621mod tests {
622 use super::*;
623
624 #[test]
625 fn background_shader_auto_dim_requires_terminal_intermediate_texture() {
626 assert!(should_populate_terminal_intermediate_texture(
627 false, true, 0.35
628 ));
629 }
630
631 #[test]
632 fn full_content_mode_requires_terminal_intermediate_texture() {
633 assert!(should_populate_terminal_intermediate_texture(
634 true, false, 0.0
635 ));
636 }
637
638 #[test]
639 fn background_only_shader_without_auto_dim_skips_terminal_intermediate_texture() {
640 assert!(!should_populate_terminal_intermediate_texture(
641 false, false, 0.35
642 ));
643 assert!(!should_populate_terminal_intermediate_texture(
644 false, true, 0.0
645 ));
646 }
647}