1use crate::error::RenderError;
2use crate::gpu_utils;
3use crate::wgpu_conversions::ImageScalingModeWgpu;
4use par_term_config::ImageScalingMode;
5use std::collections::HashMap;
6use std::time::Instant;
7use wgpu::*;
8
9mod layout;
10mod upload;
11
12pub use layout::PaneRenderGeometry;
13use layout::compute_graphic_geometry;
14use upload::{CachedTexture, texture_bytes};
15
16const INITIAL_GRAPHICS_INSTANCE_CAPACITY: usize = 32;
19
20#[repr(C)]
22#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
23struct SixelInstance {
24 position: [f32; 2], tex_coords: [f32; 4], size: [f32; 2], alpha: f32, _padding: f32, }
30
31#[derive(Debug, Clone, Copy)]
37pub struct GraphicRenderInfo {
38 pub id: u64,
40 pub screen_row: isize,
42 pub col: usize,
44 pub width_cells: usize,
46 pub height_cells: usize,
48 pub alpha: f32,
50 pub scroll_offset_rows: usize,
52 pub destination_offset_x: u32,
54 pub destination_offset_y: u32,
55 pub source_crop: [u32; 4],
57 pub has_cols: bool,
59 pub has_rows: bool,
61}
62
63pub struct GraphicsRenderer {
65 pipeline: RenderPipeline,
67 bind_group_layout: BindGroupLayout,
68 sampler: Sampler,
69
70 instance_buffer: Buffer,
72 instance_capacity: usize,
73
74 texture_cache: HashMap<u64, CachedTexture>,
76 texture_cache_bytes: u64,
79
80 cell_width: f32,
82 cell_height: f32,
83 window_padding: f32,
84 content_offset_y: f32,
86 content_offset_x: f32,
88
89 preserve_aspect_ratio: bool,
91}
92
93impl GraphicsRenderer {
94 pub fn new(
96 device: &Device,
97 surface_format: TextureFormat,
98 cell_width: f32,
99 cell_height: f32,
100 window_padding: f32,
101 scaling_mode: ImageScalingMode,
102 preserve_aspect_ratio: bool,
103 ) -> Result<Self, RenderError> {
104 let bind_group_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
106 label: Some("Sixel Bind Group Layout"),
107 entries: &[
108 BindGroupLayoutEntry {
110 binding: 0,
111 visibility: ShaderStages::FRAGMENT,
112 ty: BindingType::Texture {
113 sample_type: TextureSampleType::Float { filterable: true },
114 view_dimension: TextureViewDimension::D2,
115 multisampled: false,
116 },
117 count: None,
118 },
119 BindGroupLayoutEntry {
121 binding: 1,
122 visibility: ShaderStages::FRAGMENT,
123 ty: BindingType::Sampler(SamplerBindingType::Filtering),
124 count: None,
125 },
126 ],
127 });
128
129 let sampler = gpu_utils::create_sampler_with_filter(
131 device,
132 scaling_mode.to_filter_mode(),
133 Some("Sixel Sampler"),
134 );
135
136 let pipeline = Self::create_pipeline(device, surface_format, &bind_group_layout)?;
138
139 let initial_capacity = INITIAL_GRAPHICS_INSTANCE_CAPACITY;
141 let instance_buffer = device.create_buffer(&BufferDescriptor {
142 label: Some("Sixel Instance Buffer"),
143 size: (initial_capacity * std::mem::size_of::<SixelInstance>()) as u64,
144 usage: BufferUsages::VERTEX | BufferUsages::COPY_DST,
145 mapped_at_creation: false,
146 });
147
148 Ok(Self {
149 pipeline,
150 bind_group_layout,
151 sampler,
152 instance_buffer,
153 instance_capacity: initial_capacity,
154 texture_cache: HashMap::new(),
155 texture_cache_bytes: 0,
156 cell_width,
157 cell_height,
158 window_padding,
159 content_offset_y: 0.0,
160 content_offset_x: 0.0,
161 preserve_aspect_ratio,
162 })
163 }
164
165 fn create_pipeline(
167 device: &Device,
168 format: TextureFormat,
169 bind_group_layout: &BindGroupLayout,
170 ) -> Result<RenderPipeline, RenderError> {
171 let shader = device.create_shader_module(ShaderModuleDescriptor {
172 label: Some("Sixel Shader"),
173 source: ShaderSource::Wgsl(include_str!("shaders/sixel.wgsl").into()),
174 });
175
176 let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
177 label: Some("Sixel Pipeline Layout"),
178 bind_group_layouts: &[Some(bind_group_layout)],
179 immediate_size: 0,
180 });
181
182 Ok(device.create_render_pipeline(&RenderPipelineDescriptor {
183 label: Some("Sixel Pipeline"),
184 layout: Some(&pipeline_layout),
185 vertex: VertexState {
186 module: &shader,
187 entry_point: Some("vs_main"),
188 buffers: &[Some(VertexBufferLayout {
189 array_stride: std::mem::size_of::<SixelInstance>() as u64,
190 step_mode: VertexStepMode::Instance,
191 attributes: &vertex_attr_array![
192 0 => Float32x2, 1 => Float32x4, 2 => Float32x2, 3 => Float32, ],
197 })],
198 compilation_options: Default::default(),
199 },
200 fragment: Some(FragmentState {
201 module: &shader,
202 entry_point: Some("fs_main"),
203 targets: &[Some(ColorTargetState {
204 format,
205 blend: Some(BlendState::PREMULTIPLIED_ALPHA_BLENDING),
207 write_mask: ColorWrites::ALL,
208 })],
209 compilation_options: Default::default(),
210 }),
211 primitive: PrimitiveState {
212 topology: PrimitiveTopology::TriangleStrip,
213 ..Default::default()
214 },
215 depth_stencil: None,
216 multisample: MultisampleState::default(),
217 cache: None,
218 multiview_mask: None,
219 }))
220 }
221
222 pub fn render(
232 &mut self,
233 device: &Device,
234 queue: &Queue,
235 render_pass: &mut RenderPass,
236 graphics: &[GraphicRenderInfo],
237 window_width: f32,
238 window_height: f32,
239 ) -> Result<(), RenderError> {
240 if graphics.is_empty() {
241 return Ok(());
242 }
243
244 let mut instances = Vec::with_capacity(graphics.len());
246 for g in graphics {
247 let (
248 id,
249 row,
250 col,
251 _width_cells,
252 _height_cells,
253 alpha,
254 _scroll_offset_rows,
255 dest_off_x,
256 dest_off_y,
257 crop,
258 has_cols,
259 has_rows,
260 ) = (
261 g.id,
262 g.screen_row,
263 g.col,
264 g.width_cells,
265 g.height_cells,
266 g.alpha,
267 g.scroll_offset_rows,
268 g.destination_offset_x,
269 g.destination_offset_y,
270 g.source_crop,
271 g.has_cols,
272 g.has_rows,
273 );
274 if let Some(cached) = self.texture_cache.get_mut(&id) {
276 cached.last_used = Instant::now();
277 let tex_info = &cached.texture;
278
279 let top_px = row as f32 * self.cell_height + dest_off_y as f32;
283 let clip_px = (-top_px).max(0.0);
284 let draw_y_px = top_px.max(0.0);
285 let x = (self.window_padding
286 + self.content_offset_x
287 + col as f32 * self.cell_width
288 + dest_off_x as f32)
289 / window_width;
290 let y = (self.window_padding + self.content_offset_y + draw_y_px) / window_height;
291
292 const VIRTUAL_PLACEMENT_ID_FLAG: u64 = 1u64 << 63;
293 let is_virtual_placement = id & VIRTUAL_PLACEMENT_ID_FLAG != 0;
294 let (tex_coords, size) = compute_graphic_geometry(
295 tex_info.width as f32,
296 tex_info.height as f32,
297 crop,
298 _width_cells,
299 _height_cells,
300 self.cell_width,
301 self.cell_height,
302 clip_px,
303 has_cols,
304 has_rows,
305 self.preserve_aspect_ratio,
306 is_virtual_placement,
307 window_width,
308 window_height,
309 );
310
311 instances.push(SixelInstance {
312 position: [x, y],
313 tex_coords,
314 size,
315 alpha,
316 _padding: 0.0,
317 });
318 }
319 }
320
321 if instances.is_empty() {
322 return Ok(());
323 }
324
325 log::debug!(
327 "[GRAPHICS] Rendering {} sixel graphics (from {} total graphics provided)",
328 instances.len(),
329 graphics.len()
330 );
331
332 let required_capacity = instances.len();
334 if required_capacity > self.instance_capacity {
335 let new_capacity = (required_capacity * 2).max(32);
336 self.instance_buffer = device.create_buffer(&BufferDescriptor {
337 label: Some("Sixel Instance Buffer"),
338 size: (new_capacity * std::mem::size_of::<SixelInstance>()) as u64,
339 usage: BufferUsages::VERTEX | BufferUsages::COPY_DST,
340 mapped_at_creation: false,
341 });
342 self.instance_capacity = new_capacity;
343 }
344
345 queue.write_buffer(&self.instance_buffer, 0, bytemuck::cast_slice(&instances));
347
348 render_pass.set_pipeline(&self.pipeline);
350
351 render_pass.set_vertex_buffer(0, self.instance_buffer.slice(..));
353
354 let mut instance_idx = 0u32;
356 for g in graphics {
357 if let Some(cached) = self.texture_cache.get(&g.id) {
358 render_pass.set_bind_group(0, &cached.texture.bind_group, &[]);
359 render_pass.draw(0..4, instance_idx..(instance_idx + 1));
360 instance_idx += 1;
361 }
362 }
363
364 Ok(())
365 }
366
367 pub fn render_for_pane(
383 &mut self,
384 device: &Device,
385 queue: &Queue,
386 render_pass: &mut RenderPass,
387 graphics: &[GraphicRenderInfo],
388 pane_geometry: PaneRenderGeometry,
389 ) -> Result<(), RenderError> {
390 let PaneRenderGeometry {
391 window_width,
392 window_height,
393 pane_origin_x,
394 pane_origin_y,
395 } = pane_geometry;
396 if graphics.is_empty() {
397 return Ok(());
398 }
399
400 let mut instances = Vec::with_capacity(graphics.len());
402 for g in graphics {
403 let (
404 id,
405 row,
406 col,
407 _width_cells,
408 _height_cells,
409 alpha,
410 _scroll_offset_rows,
411 dest_off_x,
412 dest_off_y,
413 crop,
414 has_cols,
415 has_rows,
416 ) = (
417 g.id,
418 g.screen_row,
419 g.col,
420 g.width_cells,
421 g.height_cells,
422 g.alpha,
423 g.scroll_offset_rows,
424 g.destination_offset_x,
425 g.destination_offset_y,
426 g.source_crop,
427 g.has_cols,
428 g.has_rows,
429 );
430 if let Some(cached) = self.texture_cache.get_mut(&id) {
432 cached.last_used = Instant::now();
433 let tex_info = &cached.texture;
434
435 let top_px = row as f32 * self.cell_height + dest_off_y as f32;
436 let clip_px = (-top_px).max(0.0);
437 let draw_y_px = top_px.max(0.0);
438 let x = (pane_origin_x + col as f32 * self.cell_width + dest_off_x as f32)
439 / window_width;
440 let y = (pane_origin_y + draw_y_px) / window_height;
441
442 const VIRTUAL_PLACEMENT_ID_FLAG: u64 = 1u64 << 63;
443 let is_virtual_placement = id & VIRTUAL_PLACEMENT_ID_FLAG != 0;
444 let (tex_coords, size) = compute_graphic_geometry(
445 tex_info.width as f32,
446 tex_info.height as f32,
447 crop,
448 _width_cells,
449 _height_cells,
450 self.cell_width,
451 self.cell_height,
452 clip_px,
453 has_cols,
454 has_rows,
455 self.preserve_aspect_ratio,
456 is_virtual_placement,
457 window_width,
458 window_height,
459 );
460
461 instances.push(SixelInstance {
462 position: [x, y],
463 tex_coords,
464 size,
465 alpha,
466 _padding: 0.0,
467 });
468 }
469 }
470
471 if instances.is_empty() {
472 return Ok(());
473 }
474
475 let required_capacity = instances.len();
477 if required_capacity > self.instance_capacity {
478 let new_capacity = (required_capacity * 2).max(32);
479 self.instance_buffer = device.create_buffer(&BufferDescriptor {
480 label: Some("Sixel Instance Buffer"),
481 size: (new_capacity * std::mem::size_of::<SixelInstance>()) as u64,
482 usage: BufferUsages::VERTEX | BufferUsages::COPY_DST,
483 mapped_at_creation: false,
484 });
485 self.instance_capacity = new_capacity;
486 }
487
488 queue.write_buffer(&self.instance_buffer, 0, bytemuck::cast_slice(&instances));
490
491 render_pass.set_pipeline(&self.pipeline);
493 render_pass.set_vertex_buffer(0, self.instance_buffer.slice(..));
494
495 let mut instance_idx = 0u32;
496 for g in graphics {
497 if let Some(cached) = self.texture_cache.get(&g.id) {
498 render_pass.set_bind_group(0, &cached.texture.bind_group, &[]);
499 render_pass.draw(0..4, instance_idx..(instance_idx + 1));
500 instance_idx += 1;
501 }
502 }
503
504 Ok(())
505 }
506
507 pub fn remove_texture(&mut self, id: u64) {
509 if let Some(removed) = self.texture_cache.remove(&id) {
510 self.texture_cache_bytes -= texture_bytes(&removed.texture);
511 }
512 }
513
514 pub fn clear_cache(&mut self) {
516 self.texture_cache.clear();
517 self.texture_cache_bytes = 0;
518 }
519
520 pub fn cache_size(&self) -> usize {
522 self.texture_cache.len()
523 }
524
525 pub fn update_cell_dimensions(
527 &mut self,
528 cell_width: f32,
529 cell_height: f32,
530 window_padding: f32,
531 ) {
532 self.cell_width = cell_width;
533 self.cell_height = cell_height;
534 self.window_padding = window_padding;
535 }
536
537 pub fn set_content_offset_y(&mut self, offset: f32) {
539 self.content_offset_y = offset;
540 }
541
542 pub fn set_content_offset_x(&mut self, offset: f32) {
544 self.content_offset_x = offset;
545 }
546
547 pub fn set_preserve_aspect_ratio(&mut self, preserve: bool) {
549 self.preserve_aspect_ratio = preserve;
550 }
551
552 pub fn update_scaling_mode(&mut self, device: &Device, scaling_mode: ImageScalingMode) {
557 self.sampler = gpu_utils::create_sampler_with_filter(
558 device,
559 scaling_mode.to_filter_mode(),
560 Some("Sixel Sampler"),
561 );
562 self.texture_cache.clear();
564 self.texture_cache_bytes = 0;
565 }
566}