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;
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
77 cell_width: f32,
79 cell_height: f32,
80 window_padding: f32,
81 content_offset_y: f32,
83 content_offset_x: f32,
85
86 preserve_aspect_ratio: bool,
88}
89
90impl GraphicsRenderer {
91 pub fn new(
93 device: &Device,
94 surface_format: TextureFormat,
95 cell_width: f32,
96 cell_height: f32,
97 window_padding: f32,
98 scaling_mode: ImageScalingMode,
99 preserve_aspect_ratio: bool,
100 ) -> Result<Self, RenderError> {
101 let bind_group_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
103 label: Some("Sixel Bind Group Layout"),
104 entries: &[
105 BindGroupLayoutEntry {
107 binding: 0,
108 visibility: ShaderStages::FRAGMENT,
109 ty: BindingType::Texture {
110 sample_type: TextureSampleType::Float { filterable: true },
111 view_dimension: TextureViewDimension::D2,
112 multisampled: false,
113 },
114 count: None,
115 },
116 BindGroupLayoutEntry {
118 binding: 1,
119 visibility: ShaderStages::FRAGMENT,
120 ty: BindingType::Sampler(SamplerBindingType::Filtering),
121 count: None,
122 },
123 ],
124 });
125
126 let sampler = gpu_utils::create_sampler_with_filter(
128 device,
129 scaling_mode.to_filter_mode(),
130 Some("Sixel Sampler"),
131 );
132
133 let pipeline = Self::create_pipeline(device, surface_format, &bind_group_layout)?;
135
136 let initial_capacity = INITIAL_GRAPHICS_INSTANCE_CAPACITY;
138 let instance_buffer = device.create_buffer(&BufferDescriptor {
139 label: Some("Sixel Instance Buffer"),
140 size: (initial_capacity * std::mem::size_of::<SixelInstance>()) as u64,
141 usage: BufferUsages::VERTEX | BufferUsages::COPY_DST,
142 mapped_at_creation: false,
143 });
144
145 Ok(Self {
146 pipeline,
147 bind_group_layout,
148 sampler,
149 instance_buffer,
150 instance_capacity: initial_capacity,
151 texture_cache: HashMap::new(),
152 cell_width,
153 cell_height,
154 window_padding,
155 content_offset_y: 0.0,
156 content_offset_x: 0.0,
157 preserve_aspect_ratio,
158 })
159 }
160
161 fn create_pipeline(
163 device: &Device,
164 format: TextureFormat,
165 bind_group_layout: &BindGroupLayout,
166 ) -> Result<RenderPipeline, RenderError> {
167 let shader = device.create_shader_module(ShaderModuleDescriptor {
168 label: Some("Sixel Shader"),
169 source: ShaderSource::Wgsl(include_str!("shaders/sixel.wgsl").into()),
170 });
171
172 let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
173 label: Some("Sixel Pipeline Layout"),
174 bind_group_layouts: &[Some(bind_group_layout)],
175 immediate_size: 0,
176 });
177
178 Ok(device.create_render_pipeline(&RenderPipelineDescriptor {
179 label: Some("Sixel Pipeline"),
180 layout: Some(&pipeline_layout),
181 vertex: VertexState {
182 module: &shader,
183 entry_point: Some("vs_main"),
184 buffers: &[Some(VertexBufferLayout {
185 array_stride: std::mem::size_of::<SixelInstance>() as u64,
186 step_mode: VertexStepMode::Instance,
187 attributes: &vertex_attr_array![
188 0 => Float32x2, 1 => Float32x4, 2 => Float32x2, 3 => Float32, ],
193 })],
194 compilation_options: Default::default(),
195 },
196 fragment: Some(FragmentState {
197 module: &shader,
198 entry_point: Some("fs_main"),
199 targets: &[Some(ColorTargetState {
200 format,
201 blend: Some(BlendState::PREMULTIPLIED_ALPHA_BLENDING),
203 write_mask: ColorWrites::ALL,
204 })],
205 compilation_options: Default::default(),
206 }),
207 primitive: PrimitiveState {
208 topology: PrimitiveTopology::TriangleStrip,
209 ..Default::default()
210 },
211 depth_stencil: None,
212 multisample: MultisampleState::default(),
213 cache: None,
214 multiview_mask: None,
215 }))
216 }
217
218 pub fn render(
228 &mut self,
229 device: &Device,
230 queue: &Queue,
231 render_pass: &mut RenderPass,
232 graphics: &[GraphicRenderInfo],
233 window_width: f32,
234 window_height: f32,
235 ) -> Result<(), RenderError> {
236 if graphics.is_empty() {
237 return Ok(());
238 }
239
240 let mut instances = Vec::with_capacity(graphics.len());
242 for g in graphics {
243 let (
244 id,
245 row,
246 col,
247 _width_cells,
248 _height_cells,
249 alpha,
250 _scroll_offset_rows,
251 dest_off_x,
252 dest_off_y,
253 crop,
254 has_cols,
255 has_rows,
256 ) = (
257 g.id,
258 g.screen_row,
259 g.col,
260 g.width_cells,
261 g.height_cells,
262 g.alpha,
263 g.scroll_offset_rows,
264 g.destination_offset_x,
265 g.destination_offset_y,
266 g.source_crop,
267 g.has_cols,
268 g.has_rows,
269 );
270 if let Some(cached) = self.texture_cache.get_mut(&id) {
272 cached.last_used = Instant::now();
273 let tex_info = &cached.texture;
274
275 let top_px = row as f32 * self.cell_height + dest_off_y as f32;
279 let clip_px = (-top_px).max(0.0);
280 let draw_y_px = top_px.max(0.0);
281 let x = (self.window_padding
282 + self.content_offset_x
283 + col as f32 * self.cell_width
284 + dest_off_x as f32)
285 / window_width;
286 let y = (self.window_padding + self.content_offset_y + draw_y_px) / window_height;
287
288 const VIRTUAL_PLACEMENT_ID_FLAG: u64 = 1u64 << 63;
289 let is_virtual_placement = id & VIRTUAL_PLACEMENT_ID_FLAG != 0;
290 let (tex_coords, size) = compute_graphic_geometry(
291 tex_info.width as f32,
292 tex_info.height as f32,
293 crop,
294 _width_cells,
295 _height_cells,
296 self.cell_width,
297 self.cell_height,
298 clip_px,
299 has_cols,
300 has_rows,
301 self.preserve_aspect_ratio,
302 is_virtual_placement,
303 window_width,
304 window_height,
305 );
306
307 instances.push(SixelInstance {
308 position: [x, y],
309 tex_coords,
310 size,
311 alpha,
312 _padding: 0.0,
313 });
314 }
315 }
316
317 if instances.is_empty() {
318 return Ok(());
319 }
320
321 log::debug!(
323 "[GRAPHICS] Rendering {} sixel graphics (from {} total graphics provided)",
324 instances.len(),
325 graphics.len()
326 );
327
328 let required_capacity = instances.len();
330 if required_capacity > self.instance_capacity {
331 let new_capacity = (required_capacity * 2).max(32);
332 self.instance_buffer = device.create_buffer(&BufferDescriptor {
333 label: Some("Sixel Instance Buffer"),
334 size: (new_capacity * std::mem::size_of::<SixelInstance>()) as u64,
335 usage: BufferUsages::VERTEX | BufferUsages::COPY_DST,
336 mapped_at_creation: false,
337 });
338 self.instance_capacity = new_capacity;
339 }
340
341 queue.write_buffer(&self.instance_buffer, 0, bytemuck::cast_slice(&instances));
343
344 render_pass.set_pipeline(&self.pipeline);
346
347 render_pass.set_vertex_buffer(0, self.instance_buffer.slice(..));
349
350 let mut instance_idx = 0u32;
352 for g in graphics {
353 if let Some(cached) = self.texture_cache.get(&g.id) {
354 render_pass.set_bind_group(0, &cached.texture.bind_group, &[]);
355 render_pass.draw(0..4, instance_idx..(instance_idx + 1));
356 instance_idx += 1;
357 }
358 }
359
360 Ok(())
361 }
362
363 pub fn render_for_pane(
379 &mut self,
380 device: &Device,
381 queue: &Queue,
382 render_pass: &mut RenderPass,
383 graphics: &[GraphicRenderInfo],
384 pane_geometry: PaneRenderGeometry,
385 ) -> Result<(), RenderError> {
386 let PaneRenderGeometry {
387 window_width,
388 window_height,
389 pane_origin_x,
390 pane_origin_y,
391 } = pane_geometry;
392 if graphics.is_empty() {
393 return Ok(());
394 }
395
396 let mut instances = Vec::with_capacity(graphics.len());
398 for g in graphics {
399 let (
400 id,
401 row,
402 col,
403 _width_cells,
404 _height_cells,
405 alpha,
406 _scroll_offset_rows,
407 dest_off_x,
408 dest_off_y,
409 crop,
410 has_cols,
411 has_rows,
412 ) = (
413 g.id,
414 g.screen_row,
415 g.col,
416 g.width_cells,
417 g.height_cells,
418 g.alpha,
419 g.scroll_offset_rows,
420 g.destination_offset_x,
421 g.destination_offset_y,
422 g.source_crop,
423 g.has_cols,
424 g.has_rows,
425 );
426 if let Some(cached) = self.texture_cache.get_mut(&id) {
428 cached.last_used = Instant::now();
429 let tex_info = &cached.texture;
430
431 let top_px = row as f32 * self.cell_height + dest_off_y as f32;
432 let clip_px = (-top_px).max(0.0);
433 let draw_y_px = top_px.max(0.0);
434 let x = (pane_origin_x + col as f32 * self.cell_width + dest_off_x as f32)
435 / window_width;
436 let y = (pane_origin_y + draw_y_px) / window_height;
437
438 const VIRTUAL_PLACEMENT_ID_FLAG: u64 = 1u64 << 63;
439 let is_virtual_placement = id & VIRTUAL_PLACEMENT_ID_FLAG != 0;
440 let (tex_coords, size) = compute_graphic_geometry(
441 tex_info.width as f32,
442 tex_info.height as f32,
443 crop,
444 _width_cells,
445 _height_cells,
446 self.cell_width,
447 self.cell_height,
448 clip_px,
449 has_cols,
450 has_rows,
451 self.preserve_aspect_ratio,
452 is_virtual_placement,
453 window_width,
454 window_height,
455 );
456
457 instances.push(SixelInstance {
458 position: [x, y],
459 tex_coords,
460 size,
461 alpha,
462 _padding: 0.0,
463 });
464 }
465 }
466
467 if instances.is_empty() {
468 return Ok(());
469 }
470
471 let required_capacity = instances.len();
473 if required_capacity > self.instance_capacity {
474 let new_capacity = (required_capacity * 2).max(32);
475 self.instance_buffer = device.create_buffer(&BufferDescriptor {
476 label: Some("Sixel Instance Buffer"),
477 size: (new_capacity * std::mem::size_of::<SixelInstance>()) as u64,
478 usage: BufferUsages::VERTEX | BufferUsages::COPY_DST,
479 mapped_at_creation: false,
480 });
481 self.instance_capacity = new_capacity;
482 }
483
484 queue.write_buffer(&self.instance_buffer, 0, bytemuck::cast_slice(&instances));
486
487 render_pass.set_pipeline(&self.pipeline);
489 render_pass.set_vertex_buffer(0, self.instance_buffer.slice(..));
490
491 let mut instance_idx = 0u32;
492 for g in graphics {
493 if let Some(cached) = self.texture_cache.get(&g.id) {
494 render_pass.set_bind_group(0, &cached.texture.bind_group, &[]);
495 render_pass.draw(0..4, instance_idx..(instance_idx + 1));
496 instance_idx += 1;
497 }
498 }
499
500 Ok(())
501 }
502
503 pub fn remove_texture(&mut self, id: u64) {
505 self.texture_cache.remove(&id);
506 }
507
508 pub fn clear_cache(&mut self) {
510 self.texture_cache.clear();
511 }
512
513 pub fn cache_size(&self) -> usize {
515 self.texture_cache.len()
516 }
517
518 pub fn update_cell_dimensions(
520 &mut self,
521 cell_width: f32,
522 cell_height: f32,
523 window_padding: f32,
524 ) {
525 self.cell_width = cell_width;
526 self.cell_height = cell_height;
527 self.window_padding = window_padding;
528 }
529
530 pub fn set_content_offset_y(&mut self, offset: f32) {
532 self.content_offset_y = offset;
533 }
534
535 pub fn set_content_offset_x(&mut self, offset: f32) {
537 self.content_offset_x = offset;
538 }
539
540 pub fn set_preserve_aspect_ratio(&mut self, preserve: bool) {
542 self.preserve_aspect_ratio = preserve;
543 }
544
545 pub fn update_scaling_mode(&mut self, device: &Device, scaling_mode: ImageScalingMode) {
550 self.sampler = gpu_utils::create_sampler_with_filter(
551 device,
552 scaling_mode.to_filter_mode(),
553 Some("Sixel Sampler"),
554 );
555 self.texture_cache.clear();
557 }
558}