1use crate::error::RenderError;
10use crate::gpu_utils;
11use crate::wgpu_conversions::ImageScalingModeWgpu;
12use par_term_config::ImageScalingMode;
13use std::collections::HashMap;
14use std::time::Instant;
15use wgpu::*;
16
17const MAX_TEXTURE_CACHE_SIZE: usize = 100;
20
21const INITIAL_GRAPHICS_INSTANCE_CAPACITY: usize = 32;
24
25#[repr(C)]
27#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
28struct SixelInstance {
29 position: [f32; 2], tex_coords: [f32; 4], size: [f32; 2], alpha: f32, _padding: f32, }
35
36#[derive(Debug, Clone, Copy)]
38pub struct PaneRenderGeometry {
39 pub window_width: f32,
40 pub window_height: f32,
41 pub pane_origin_x: f32,
42 pub pane_origin_y: f32,
43}
44
45#[allow(clippy::too_many_arguments)]
51fn compute_graphic_geometry(
52 tex_w: f32,
53 tex_h: f32,
54 crop: [u32; 4],
55 width_cells: usize,
56 height_cells: usize,
57 cell_w: f32,
58 cell_h: f32,
59 clip_px: f32,
60 has_cols: bool,
61 has_rows: bool,
62 preserve_aspect: bool,
63 is_virtual: bool,
64 window_w: f32,
65 window_h: f32,
66) -> ([f32; 4], [f32; 2]) {
67 let has_crop = crop != [0, 0, 0, 0];
68
69 let (sx, sy, sw, sh) = if has_crop && tex_w > 0.0 && tex_h > 0.0 {
71 let x = (crop[0] as f32).min(tex_w);
72 let y = (crop[1] as f32).min(tex_h);
73 let w = if crop[2] > 0 {
74 (crop[2] as f32).min(tex_w - x)
75 } else {
76 tex_w - x
77 };
78 let h = if crop[3] > 0 {
79 (crop[3] as f32).min(tex_h - y)
80 } else {
81 tex_h - y
82 };
83 (x, y, w.max(0.0), h.max(0.0))
84 } else {
85 (0.0, 0.0, tex_w, tex_h)
86 };
87
88 if sw <= 0.0 || sh <= 0.0 {
97 return ([0.0, 0.0, 0.0, 0.0], [0.0, 0.0]);
98 }
99 let aspect = sw / sh;
100 let (dest_w, dest_h) = if is_virtual || (has_cols && has_rows) {
101 (width_cells as f32 * cell_w, height_cells as f32 * cell_h)
102 } else if has_cols && !has_rows {
103 let dw = width_cells as f32 * cell_w;
104 (dw, dw / aspect)
105 } else if has_rows && !has_cols {
106 let dh = height_cells as f32 * cell_h;
107 (dh * aspect, dh)
108 } else if has_crop && sw > 0.0 && sh > 0.0 {
109 (sw, sh)
110 } else if preserve_aspect && tex_w > 0.0 && tex_h > 0.0 {
111 (tex_w, tex_h)
112 } else {
113 (width_cells as f32 * cell_w, height_cells as f32 * cell_h)
114 };
115
116 let visible_frac = if dest_h > 0.0 {
118 ((dest_h - clip_px) / dest_h).clamp(0.0, 1.0)
119 } else {
120 0.0
121 };
122 let scrolled_frac = if dest_h > 0.0 {
123 (clip_px / dest_h).clamp(0.0, 1.0)
124 } else {
125 0.0
126 };
127
128 let uv = if sw > 0.0 && sh > 0.0 && tex_w > 0.0 && tex_h > 0.0 {
130 [
131 sx / tex_w,
132 (sy + sh * scrolled_frac) / tex_h,
133 sw / tex_w,
134 (sh * visible_frac) / tex_h,
135 ]
136 } else {
137 [0.0, 0.0, 1.0, 1.0]
138 };
139
140 let size = (dest_w / window_w, dest_h * visible_frac / window_h);
141
142 (uv, size.into())
143}
144
145#[cfg(test)]
146mod geometry_tests {
147 use super::compute_graphic_geometry;
148
149 const WW: f32 = 800.0;
150 const WH: f32 = 600.0;
151 const CW: f32 = 10.0;
152 const CH: f32 = 20.0;
153
154 #[test]
157 fn no_crop_both_cells_uses_dest_fraction_for_uv() {
158 let (uv, size) = compute_graphic_geometry(
159 100.0,
160 100.0,
161 [0, 0, 0, 0],
162 10,
163 2,
164 CW,
165 CH,
166 20.0, true,
168 true, false,
170 false, WW,
172 WH,
173 );
174 let expected_uv_y = 50.0 / 100.0;
175 let expected_uv_h = 50.0 / 100.0;
176 assert!((uv[1] - expected_uv_y).abs() < 1e-5);
177 assert!((uv[3] - expected_uv_h).abs() < 1e-5);
178 assert!((size[1] - 20.0 / WH).abs() < 1e-5);
179 }
180
181 #[test]
183 fn natural_crop_without_cells_uses_crop_height_for_dest() {
184 let (uv, size) = compute_graphic_geometry(
185 100.0,
186 100.0,
187 [0, 0, 0, 25],
188 1,
189 1,
190 CW,
191 CH,
192 20.0,
193 false,
194 false,
195 false,
196 false,
197 WW,
198 WH,
199 );
200 let expected_uv_y = (0.0 + 25.0 * 0.8) / 100.0;
201 let expected_uv_h = (25.0 * 0.2) / 100.0;
202 assert!((uv[1] - expected_uv_y).abs() < 1e-5);
203 assert!((uv[3] - expected_uv_h).abs() < 1e-5);
204 assert!((size[1] - 5.0 / WH).abs() < 1e-5);
205 }
206
207 #[test]
209 fn y_offset_produces_sub_row_clip() {
210 let top_px = -1.0 * CH + 5.0;
211 let clip_px = (-top_px).max(0.0);
212 assert_eq!(clip_px, 15.0);
213
214 let (uv, size) = compute_graphic_geometry(
215 100.0,
216 100.0,
217 [0, 0, 0, 0],
218 10,
219 3,
220 CW,
221 CH,
222 clip_px,
223 true,
224 true,
225 false,
226 false,
227 WW,
228 WH,
229 );
230 assert!((uv[1] - 25.0 / 100.0).abs() < 1e-5);
231 assert!((uv[3] - 75.0 / 100.0).abs() < 1e-5);
232 assert!((size[1] - 45.0 / WH).abs() < 1e-5);
233 }
234
235 #[test]
237 fn c_only_computes_exact_height_from_aspect() {
238 let (_uv, size) = compute_graphic_geometry(
239 100.0,
240 100.0,
241 [0, 0, 0, 0],
242 5,
243 3,
244 CW,
245 CH,
246 0.0,
247 true,
248 false, false,
250 false,
251 WW,
252 WH,
253 );
254 assert!((size[0] - 50.0 / WW).abs() < 1e-5);
256 assert!((size[1] - 50.0 / WH).abs() < 1e-5);
257 }
258
259 #[test]
261 fn r_only_computes_exact_width_from_aspect() {
262 let (_uv, size) = compute_graphic_geometry(
263 100.0,
264 100.0,
265 [0, 0, 0, 0],
266 4,
267 2,
268 CW,
269 CH,
270 0.0,
271 false,
272 true, false,
274 false,
275 WW,
276 WH,
277 );
278 assert!((size[0] - 40.0 / WW).abs() < 1e-5);
279 assert!((size[1] - 40.0 / WH).abs() < 1e-5);
280 }
281
282 #[test]
284 fn c_only_wide_source_computes_proportional_height() {
285 let (_uv, size) = compute_graphic_geometry(
286 100.0,
287 50.0,
288 [0, 0, 0, 0],
289 5,
290 1,
291 CW,
292 CH,
293 0.0,
294 true,
295 false,
296 false,
297 false,
298 WW,
299 WH,
300 );
301 assert!((size[0] - 50.0 / WW).abs() < 1e-5);
302 assert!((size[1] - 25.0 / WH).abs() < 1e-5);
303 }
304
305 #[test]
309 fn zero_size_crop_at_edge_returns_zero_output() {
310 let (uv, size) = compute_graphic_geometry(
311 100.0,
312 100.0,
313 [100, 0, 0, 0],
314 5,
315 3,
316 CW,
317 CH,
318 0.0,
319 true,
320 false,
321 false,
322 false,
323 WW,
324 WH,
325 );
326 assert_eq!(uv, [0.0, 0.0, 0.0, 0.0]);
328 assert_eq!(size, [0.0, 0.0]);
329 }
330}
331
332#[derive(Debug, Clone, Copy)]
338pub struct GraphicRenderInfo {
339 pub id: u64,
341 pub screen_row: isize,
343 pub col: usize,
345 pub width_cells: usize,
347 pub height_cells: usize,
349 pub alpha: f32,
351 pub scroll_offset_rows: usize,
353 pub destination_offset_x: u32,
355 pub destination_offset_y: u32,
356 pub source_crop: [u32; 4],
358 pub has_cols: bool,
360 pub has_rows: bool,
362}
363
364struct SixelTextureInfo {
366 texture: Texture,
367 #[allow(dead_code)] view: TextureView,
369 bind_group: BindGroup,
370 width: u32,
371 height: u32,
372}
373
374struct CachedTexture {
376 texture: SixelTextureInfo,
377 last_used: Instant,
379}
380
381pub struct GraphicsRenderer {
383 pipeline: RenderPipeline,
385 bind_group_layout: BindGroupLayout,
386 sampler: Sampler,
387
388 instance_buffer: Buffer,
390 instance_capacity: usize,
391
392 texture_cache: HashMap<u64, CachedTexture>,
394
395 cell_width: f32,
397 cell_height: f32,
398 window_padding: f32,
399 content_offset_y: f32,
401 content_offset_x: f32,
403
404 preserve_aspect_ratio: bool,
406}
407
408impl GraphicsRenderer {
409 pub fn new(
411 device: &Device,
412 surface_format: TextureFormat,
413 cell_width: f32,
414 cell_height: f32,
415 window_padding: f32,
416 scaling_mode: ImageScalingMode,
417 preserve_aspect_ratio: bool,
418 ) -> Result<Self, RenderError> {
419 let bind_group_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
421 label: Some("Sixel Bind Group Layout"),
422 entries: &[
423 BindGroupLayoutEntry {
425 binding: 0,
426 visibility: ShaderStages::FRAGMENT,
427 ty: BindingType::Texture {
428 sample_type: TextureSampleType::Float { filterable: true },
429 view_dimension: TextureViewDimension::D2,
430 multisampled: false,
431 },
432 count: None,
433 },
434 BindGroupLayoutEntry {
436 binding: 1,
437 visibility: ShaderStages::FRAGMENT,
438 ty: BindingType::Sampler(SamplerBindingType::Filtering),
439 count: None,
440 },
441 ],
442 });
443
444 let sampler = gpu_utils::create_sampler_with_filter(
446 device,
447 scaling_mode.to_filter_mode(),
448 Some("Sixel Sampler"),
449 );
450
451 let pipeline = Self::create_pipeline(device, surface_format, &bind_group_layout)?;
453
454 let initial_capacity = INITIAL_GRAPHICS_INSTANCE_CAPACITY;
456 let instance_buffer = device.create_buffer(&BufferDescriptor {
457 label: Some("Sixel Instance Buffer"),
458 size: (initial_capacity * std::mem::size_of::<SixelInstance>()) as u64,
459 usage: BufferUsages::VERTEX | BufferUsages::COPY_DST,
460 mapped_at_creation: false,
461 });
462
463 Ok(Self {
464 pipeline,
465 bind_group_layout,
466 sampler,
467 instance_buffer,
468 instance_capacity: initial_capacity,
469 texture_cache: HashMap::new(),
470 cell_width,
471 cell_height,
472 window_padding,
473 content_offset_y: 0.0,
474 content_offset_x: 0.0,
475 preserve_aspect_ratio,
476 })
477 }
478
479 fn create_pipeline(
481 device: &Device,
482 format: TextureFormat,
483 bind_group_layout: &BindGroupLayout,
484 ) -> Result<RenderPipeline, RenderError> {
485 let shader = device.create_shader_module(ShaderModuleDescriptor {
486 label: Some("Sixel Shader"),
487 source: ShaderSource::Wgsl(include_str!("shaders/sixel.wgsl").into()),
488 });
489
490 let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
491 label: Some("Sixel Pipeline Layout"),
492 bind_group_layouts: &[Some(bind_group_layout)],
493 immediate_size: 0,
494 });
495
496 Ok(device.create_render_pipeline(&RenderPipelineDescriptor {
497 label: Some("Sixel Pipeline"),
498 layout: Some(&pipeline_layout),
499 vertex: VertexState {
500 module: &shader,
501 entry_point: Some("vs_main"),
502 buffers: &[Some(VertexBufferLayout {
503 array_stride: std::mem::size_of::<SixelInstance>() as u64,
504 step_mode: VertexStepMode::Instance,
505 attributes: &vertex_attr_array![
506 0 => Float32x2, 1 => Float32x4, 2 => Float32x2, 3 => Float32, ],
511 })],
512 compilation_options: Default::default(),
513 },
514 fragment: Some(FragmentState {
515 module: &shader,
516 entry_point: Some("fs_main"),
517 targets: &[Some(ColorTargetState {
518 format,
519 blend: Some(BlendState::PREMULTIPLIED_ALPHA_BLENDING),
521 write_mask: ColorWrites::ALL,
522 })],
523 compilation_options: Default::default(),
524 }),
525 primitive: PrimitiveState {
526 topology: PrimitiveTopology::TriangleStrip,
527 ..Default::default()
528 },
529 depth_stencil: None,
530 multisample: MultisampleState::default(),
531 cache: None,
532 multiview_mask: None,
533 }))
534 }
535
536 pub fn get_or_create_texture(
546 &mut self,
547 device: &Device,
548 queue: &Queue,
549 id: u64,
550 rgba_data: &[u8],
551 width: u32,
552 height: u32,
553 ) -> Result<(), RenderError> {
554 if let Some(cached) = self.texture_cache.get_mut(&id) {
557 cached.last_used = Instant::now();
559
560 const VIRTUAL_PLACEMENT_ID_FLAG: u64 = 1u64 << 63;
568 if id & VIRTUAL_PLACEMENT_ID_FLAG != 0 {
569 return Ok(());
570 }
571
572 let expected_size = (width * height * 4) as usize;
575 if rgba_data.len() != expected_size {
576 return Err(RenderError::InvalidTextureData {
577 expected: expected_size,
578 actual: rgba_data.len(),
579 });
580 }
581
582 queue.write_texture(
584 TexelCopyTextureInfo {
585 texture: &cached.texture.texture,
586 mip_level: 0,
587 origin: Origin3d::ZERO,
588 aspect: TextureAspect::All,
589 },
590 rgba_data,
591 TexelCopyBufferLayout {
592 offset: 0,
593 bytes_per_row: Some(4 * width),
594 rows_per_image: Some(height),
595 },
596 Extent3d {
597 width,
598 height,
599 depth_or_array_layers: 1,
600 },
601 );
602
603 return Ok(());
604 }
605
606 let expected_size = (width * height * 4) as usize;
608 if rgba_data.len() != expected_size {
609 return Err(RenderError::InvalidTextureData {
610 expected: expected_size,
611 actual: rgba_data.len(),
612 });
613 }
614
615 if self.texture_cache.len() >= MAX_TEXTURE_CACHE_SIZE
617 && let Some((&lru_id, _)) = self
618 .texture_cache
619 .iter()
620 .min_by_key(|(_, cached)| cached.last_used)
621 {
622 log::debug!(
623 "[GRAPHICS] Evicting LRU texture: id={}, cache_size={}",
624 lru_id,
625 self.texture_cache.len()
626 );
627 self.texture_cache.remove(&lru_id);
628 }
629
630 let texture = device.create_texture(&TextureDescriptor {
632 label: Some(&format!("Sixel Texture {}", id)),
633 size: Extent3d {
634 width,
635 height,
636 depth_or_array_layers: 1,
637 },
638 mip_level_count: 1,
639 sample_count: 1,
640 dimension: TextureDimension::D2,
641 format: TextureFormat::Rgba8Unorm,
642 usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
643 view_formats: &[],
644 });
645
646 queue.write_texture(
648 TexelCopyTextureInfo {
649 texture: &texture,
650 mip_level: 0,
651 origin: Origin3d::ZERO,
652 aspect: TextureAspect::All,
653 },
654 rgba_data,
655 TexelCopyBufferLayout {
656 offset: 0,
657 bytes_per_row: Some(4 * width),
658 rows_per_image: Some(height),
659 },
660 Extent3d {
661 width,
662 height,
663 depth_or_array_layers: 1,
664 },
665 );
666
667 let view = texture.create_view(&TextureViewDescriptor::default());
668
669 let bind_group = device.create_bind_group(&BindGroupDescriptor {
671 label: Some(&format!("Sixel Bind Group {}", id)),
672 layout: &self.bind_group_layout,
673 entries: &[
674 BindGroupEntry {
675 binding: 0,
676 resource: BindingResource::TextureView(&view),
677 },
678 BindGroupEntry {
679 binding: 1,
680 resource: BindingResource::Sampler(&self.sampler),
681 },
682 ],
683 });
684
685 self.texture_cache.insert(
687 id,
688 CachedTexture {
689 texture: SixelTextureInfo {
690 texture,
691 view,
692 bind_group,
693 width,
694 height,
695 },
696 last_used: Instant::now(),
697 },
698 );
699
700 log::debug!(
701 "[GRAPHICS] Created sixel texture: id={}, size={}x{}, cache_size={}/{}",
702 id,
703 width,
704 height,
705 self.texture_cache.len(),
706 MAX_TEXTURE_CACHE_SIZE
707 );
708
709 Ok(())
710 }
711
712 pub fn render(
722 &mut self,
723 device: &Device,
724 queue: &Queue,
725 render_pass: &mut RenderPass,
726 graphics: &[GraphicRenderInfo],
727 window_width: f32,
728 window_height: f32,
729 ) -> Result<(), RenderError> {
730 if graphics.is_empty() {
731 return Ok(());
732 }
733
734 let mut instances = Vec::with_capacity(graphics.len());
736 for g in graphics {
737 let (
738 id,
739 row,
740 col,
741 _width_cells,
742 _height_cells,
743 alpha,
744 _scroll_offset_rows,
745 dest_off_x,
746 dest_off_y,
747 crop,
748 has_cols,
749 has_rows,
750 ) = (
751 g.id,
752 g.screen_row,
753 g.col,
754 g.width_cells,
755 g.height_cells,
756 g.alpha,
757 g.scroll_offset_rows,
758 g.destination_offset_x,
759 g.destination_offset_y,
760 g.source_crop,
761 g.has_cols,
762 g.has_rows,
763 );
764 if let Some(cached) = self.texture_cache.get_mut(&id) {
766 cached.last_used = Instant::now();
767 let tex_info = &cached.texture;
768
769 let top_px = row as f32 * self.cell_height + dest_off_y as f32;
773 let clip_px = (-top_px).max(0.0);
774 let draw_y_px = top_px.max(0.0);
775 let x = (self.window_padding
776 + self.content_offset_x
777 + col as f32 * self.cell_width
778 + dest_off_x as f32)
779 / window_width;
780 let y = (self.window_padding + self.content_offset_y + draw_y_px) / window_height;
781
782 const VIRTUAL_PLACEMENT_ID_FLAG: u64 = 1u64 << 63;
783 let is_virtual_placement = id & VIRTUAL_PLACEMENT_ID_FLAG != 0;
784 let (tex_coords, size) = compute_graphic_geometry(
785 tex_info.width as f32,
786 tex_info.height as f32,
787 crop,
788 _width_cells,
789 _height_cells,
790 self.cell_width,
791 self.cell_height,
792 clip_px,
793 has_cols,
794 has_rows,
795 self.preserve_aspect_ratio,
796 is_virtual_placement,
797 window_width,
798 window_height,
799 );
800
801 instances.push(SixelInstance {
802 position: [x, y],
803 tex_coords,
804 size,
805 alpha,
806 _padding: 0.0,
807 });
808 }
809 }
810
811 if instances.is_empty() {
812 return Ok(());
813 }
814
815 log::debug!(
817 "[GRAPHICS] Rendering {} sixel graphics (from {} total graphics provided)",
818 instances.len(),
819 graphics.len()
820 );
821
822 let required_capacity = instances.len();
824 if required_capacity > self.instance_capacity {
825 let new_capacity = (required_capacity * 2).max(32);
826 self.instance_buffer = device.create_buffer(&BufferDescriptor {
827 label: Some("Sixel Instance Buffer"),
828 size: (new_capacity * std::mem::size_of::<SixelInstance>()) as u64,
829 usage: BufferUsages::VERTEX | BufferUsages::COPY_DST,
830 mapped_at_creation: false,
831 });
832 self.instance_capacity = new_capacity;
833 }
834
835 queue.write_buffer(&self.instance_buffer, 0, bytemuck::cast_slice(&instances));
837
838 render_pass.set_pipeline(&self.pipeline);
840
841 render_pass.set_vertex_buffer(0, self.instance_buffer.slice(..));
843
844 let mut instance_idx = 0u32;
846 for g in graphics {
847 if let Some(cached) = self.texture_cache.get(&g.id) {
848 render_pass.set_bind_group(0, &cached.texture.bind_group, &[]);
849 render_pass.draw(0..4, instance_idx..(instance_idx + 1));
850 instance_idx += 1;
851 }
852 }
853
854 Ok(())
855 }
856
857 pub fn render_for_pane(
873 &mut self,
874 device: &Device,
875 queue: &Queue,
876 render_pass: &mut RenderPass,
877 graphics: &[GraphicRenderInfo],
878 pane_geometry: PaneRenderGeometry,
879 ) -> Result<(), RenderError> {
880 let PaneRenderGeometry {
881 window_width,
882 window_height,
883 pane_origin_x,
884 pane_origin_y,
885 } = pane_geometry;
886 if graphics.is_empty() {
887 return Ok(());
888 }
889
890 let mut instances = Vec::with_capacity(graphics.len());
892 for g in graphics {
893 let (
894 id,
895 row,
896 col,
897 _width_cells,
898 _height_cells,
899 alpha,
900 _scroll_offset_rows,
901 dest_off_x,
902 dest_off_y,
903 crop,
904 has_cols,
905 has_rows,
906 ) = (
907 g.id,
908 g.screen_row,
909 g.col,
910 g.width_cells,
911 g.height_cells,
912 g.alpha,
913 g.scroll_offset_rows,
914 g.destination_offset_x,
915 g.destination_offset_y,
916 g.source_crop,
917 g.has_cols,
918 g.has_rows,
919 );
920 if let Some(cached) = self.texture_cache.get_mut(&id) {
922 cached.last_used = Instant::now();
923 let tex_info = &cached.texture;
924
925 let top_px = row as f32 * self.cell_height + dest_off_y as f32;
926 let clip_px = (-top_px).max(0.0);
927 let draw_y_px = top_px.max(0.0);
928 let x = (pane_origin_x + col as f32 * self.cell_width + dest_off_x as f32)
929 / window_width;
930 let y = (pane_origin_y + draw_y_px) / window_height;
931
932 const VIRTUAL_PLACEMENT_ID_FLAG: u64 = 1u64 << 63;
933 let is_virtual_placement = id & VIRTUAL_PLACEMENT_ID_FLAG != 0;
934 let (tex_coords, size) = compute_graphic_geometry(
935 tex_info.width as f32,
936 tex_info.height as f32,
937 crop,
938 _width_cells,
939 _height_cells,
940 self.cell_width,
941 self.cell_height,
942 clip_px,
943 has_cols,
944 has_rows,
945 self.preserve_aspect_ratio,
946 is_virtual_placement,
947 window_width,
948 window_height,
949 );
950
951 instances.push(SixelInstance {
952 position: [x, y],
953 tex_coords,
954 size,
955 alpha,
956 _padding: 0.0,
957 });
958 }
959 }
960
961 if instances.is_empty() {
962 return Ok(());
963 }
964
965 let required_capacity = instances.len();
967 if required_capacity > self.instance_capacity {
968 let new_capacity = (required_capacity * 2).max(32);
969 self.instance_buffer = device.create_buffer(&BufferDescriptor {
970 label: Some("Sixel Instance Buffer"),
971 size: (new_capacity * std::mem::size_of::<SixelInstance>()) as u64,
972 usage: BufferUsages::VERTEX | BufferUsages::COPY_DST,
973 mapped_at_creation: false,
974 });
975 self.instance_capacity = new_capacity;
976 }
977
978 queue.write_buffer(&self.instance_buffer, 0, bytemuck::cast_slice(&instances));
980
981 render_pass.set_pipeline(&self.pipeline);
983 render_pass.set_vertex_buffer(0, self.instance_buffer.slice(..));
984
985 let mut instance_idx = 0u32;
986 for g in graphics {
987 if let Some(cached) = self.texture_cache.get(&g.id) {
988 render_pass.set_bind_group(0, &cached.texture.bind_group, &[]);
989 render_pass.draw(0..4, instance_idx..(instance_idx + 1));
990 instance_idx += 1;
991 }
992 }
993
994 Ok(())
995 }
996
997 pub fn remove_texture(&mut self, id: u64) {
999 self.texture_cache.remove(&id);
1000 }
1001
1002 pub fn clear_cache(&mut self) {
1004 self.texture_cache.clear();
1005 }
1006
1007 pub fn cache_size(&self) -> usize {
1009 self.texture_cache.len()
1010 }
1011
1012 pub fn update_cell_dimensions(
1014 &mut self,
1015 cell_width: f32,
1016 cell_height: f32,
1017 window_padding: f32,
1018 ) {
1019 self.cell_width = cell_width;
1020 self.cell_height = cell_height;
1021 self.window_padding = window_padding;
1022 }
1023
1024 pub fn set_content_offset_y(&mut self, offset: f32) {
1026 self.content_offset_y = offset;
1027 }
1028
1029 pub fn set_content_offset_x(&mut self, offset: f32) {
1031 self.content_offset_x = offset;
1032 }
1033
1034 pub fn set_preserve_aspect_ratio(&mut self, preserve: bool) {
1036 self.preserve_aspect_ratio = preserve;
1037 }
1038
1039 pub fn update_scaling_mode(&mut self, device: &Device, scaling_mode: ImageScalingMode) {
1044 self.sampler = gpu_utils::create_sampler_with_filter(
1045 device,
1046 scaling_mode.to_filter_mode(),
1047 Some("Sixel Sampler"),
1048 );
1049 self.texture_cache.clear();
1051 }
1052}