1use std::collections::HashMap;
2
3use anchor_kit_core::{
4 primitives::rectangle::Rectangle,
5 render::RenderList,
6 style::{FontFamily, FontStyle, FontWeight},
7};
8use glyphon::{
9 Attrs, Cache, FontSystem, Metrics, Shaping, SwashCache, TextArea, TextAtlas, TextBounds,
10 TextRenderer, Viewport,
11};
12use image::GenericImageView;
13use uuid::Uuid;
14use wgpu::include_wgsl;
15
16pub struct ScreenInfo {
17 pub size_px: [u32; 2], pub scale_factor: f32, }
20
21#[repr(C)]
22#[derive(Copy, Clone, Debug, bytemuck::NoUninit)] struct Vertex {
24 position: [f32; 2], local_uv: [f32; 2], background_color: [f32; 4], border_radius: [f32; 4], border_width: f32,
29 border_color: [f32; 4], scale: [f32; 2], }
32
33impl Vertex {
34 const ATTRIBS: [wgpu::VertexAttribute; 7] = wgpu::vertex_attr_array![
35 0 => Float32x2, 1 => Float32x2, 2 => Float32x4, 3 => Float32x4, 4 => Float32, 5 => Float32x4, 6 => Float32x2, ];
43
44 fn capacity_to_bytes(capacity: usize) -> wgpu::BufferAddress {
45 (capacity * std::mem::size_of::<Self>()) as wgpu::BufferAddress
46 }
47
48 fn desc() -> wgpu::VertexBufferLayout<'static> {
49 wgpu::VertexBufferLayout {
50 array_stride: std::mem::size_of::<Self>() as wgpu::BufferAddress,
51 step_mode: wgpu::VertexStepMode::Vertex,
52 attributes: &Self::ATTRIBS,
53 }
54 }
55}
56
57fn get_vertex_buffer(device: &wgpu::Device, capacity_bytes: wgpu::BufferAddress) -> wgpu::Buffer {
58 device.create_buffer(&wgpu::BufferDescriptor {
59 label: Some("anchor-kit vertex buffer"),
60 size: capacity_bytes,
61 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
62 mapped_at_creation: false,
63 })
64}
65
66fn get_index_buffer(device: &wgpu::Device, capacity_bytes: wgpu::BufferAddress) -> wgpu::Buffer {
67 device.create_buffer(&wgpu::BufferDescriptor {
68 label: Some("anchor-kit index buffer"),
69 size: capacity_bytes,
70 usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
71 mapped_at_creation: false,
72 })
73}
74
75fn get_vertices_and_indices_for_rectangle(
82 rect: &Rectangle,
83 screen_info: &ScreenInfo,
84 vertex_offset: u32,
85) -> ([Vertex; 4], [u32; 6]) {
86 let [x, y] = rect.position;
87 let [w, h] = rect.size;
88 let scale_axis = w.min(h) as f32;
89 let scale = if scale_axis <= 0.0 {
90 [1.0, 1.0]
91 } else {
92 [(w as f32) / scale_axis, (h as f32) / scale_axis]
93 };
94 let [screen_w, screen_h] = screen_info.size_px;
95
96 let x0 = x as f32 / screen_w as f32;
98 let x1 = (x + w) as f32 / screen_w as f32;
99 let y0 = y as f32 / screen_h as f32;
100 let y1 = (y + h) as f32 / screen_h as f32;
101
102 let background_color = rect.style.background_color.to_rgba_f32();
103 let border_color = rect.style.border_color.to_rgba_f32();
104
105 let mut local_radius = rect.style.border_radius;
107 for r in local_radius.iter_mut() {
108 *r = (*r / w.min(h) as f32).min(0.5) }
110 let local_border_width = rect.style.border_width / w.min(h) as f32;
111
112 let v0 = Vertex {
114 position: [x0, y0],
115 local_uv: [0.0, 0.0],
116 background_color,
117 border_radius: local_radius,
118 border_width: local_border_width,
119 border_color,
120 scale,
121 };
122 let v1 = Vertex {
123 position: [x1, y0],
124 local_uv: [1.0, 0.0],
125 background_color,
126 border_radius: local_radius,
127 border_width: local_border_width,
128 border_color,
129 scale,
130 };
131 let v2 = Vertex {
132 position: [x1, y1],
133 local_uv: [1.0, 1.0],
134 background_color,
135 border_radius: local_radius,
136 border_width: local_border_width,
137 border_color,
138 scale,
139 };
140 let v3 = Vertex {
141 position: [x0, y1],
142 local_uv: [0.0, 1.0],
143 background_color,
144 border_radius: local_radius,
145 border_width: local_border_width,
146 border_color,
147 scale,
148 };
149
150 let vertices = [v0, v1, v2, v3];
151
152 let indices = [
154 vertex_offset,
155 vertex_offset + 2,
156 vertex_offset + 1,
157 vertex_offset,
158 vertex_offset + 3,
159 vertex_offset + 2,
160 ];
161
162 (vertices, indices)
163}
164
165struct GlyphonRenderer {
166 font_system: FontSystem,
167 swash_cache: SwashCache,
168 viewport: Viewport,
169 atlas: TextAtlas,
170 text_renderer: TextRenderer,
171}
172
173impl GlyphonRenderer {
174 pub fn new(
175 device: &wgpu::Device,
176 queue: &wgpu::Queue,
177 texture_format: wgpu::TextureFormat,
178 ) -> Self {
179 let font_system = FontSystem::new();
181 let swash_cache = SwashCache::new();
182 let glyphon_cache = Cache::new(device);
183 let viewport = Viewport::new(device, &glyphon_cache);
184 let mut atlas = TextAtlas::new(device, queue, &glyphon_cache, texture_format);
185 let text_renderer =
186 TextRenderer::new(&mut atlas, device, wgpu::MultisampleState::default(), None);
187
188 GlyphonRenderer {
189 font_system,
190 swash_cache,
191 viewport,
192 atlas,
193 text_renderer,
194 }
195 }
196
197 pub fn render_text(
198 &mut self,
199 device: &wgpu::Device,
200 queue: &wgpu::Queue,
201 render_pass: &mut wgpu::RenderPass<'_>,
202 screen_info: &ScreenInfo,
203 render_list: &RenderList,
204 ) {
205 if render_list.text.is_empty() {
207 return;
208 }
209
210 let [screen_w, screen_h] = screen_info.size_px;
211
212 self.viewport.update(
213 queue,
214 glyphon::Resolution {
215 width: screen_w,
216 height: screen_h,
217 },
218 );
219
220 let physical_width = screen_w as f32 * screen_info.scale_factor;
221 let physical_height = screen_h as f32 * screen_info.scale_factor;
222
223 let mut text_areas: Vec<TextArea> = Vec::with_capacity(render_list.text.len());
225
226 let mut text_buffers: Vec<glyphon::Buffer> = Vec::with_capacity(render_list.text.len());
228
229 for text_item in &render_list.text {
230 let text_style = &text_item.text_style;
231
232 let mut text_buffer = glyphon::Buffer::new(
234 &mut self.font_system,
235 Metrics::new(text_style.font_size, text_style.line_height),
236 );
237
238 text_buffer.set_size(
239 &mut self.font_system,
240 Some(physical_width),
241 Some(physical_height),
242 );
243
244 let text_color = glyphon::Color::rgba(
245 text_item.text_style.text_color.r,
246 text_item.text_style.text_color.g,
247 text_item.text_style.text_color.b,
248 text_item.text_style.text_color.a,
249 );
250
251 let text_attrs = Attrs::new()
252 .family(Self::anchor_kit_font_family_to_glyphon(
253 &text_style.font_family,
254 ))
255 .style(Self::anchor_kit_font_style_to_glyphon(
256 &text_style.font_style,
257 ))
258 .weight(Self::anchor_kit_font_weight_to_glyphon(
259 &text_style.font_weight,
260 ))
261 .color(text_color);
262
263 text_buffer.set_text(
264 &mut self.font_system,
265 &text_item.text,
266 &text_attrs,
267 Shaping::Advanced,
268 );
269
270 text_buffer.shape_until_scroll(&mut self.font_system, false);
272
273 text_buffers.push(text_buffer);
274 }
275
276 for (i, text_buffer) in text_buffers.iter().enumerate() {
277 let text_item = &render_list.text[i];
278
279 let [x, y] = text_item.position;
280 let [w, h] = text_item.size;
281 let text_bounds = TextBounds {
282 left: x as i32,
283 top: y as i32,
284 right: (x + w) as i32,
285 bottom: (y + h) as i32,
286 };
287
288 let text_color = glyphon::Color::rgba(
289 text_item.text_style.text_color.r,
290 text_item.text_style.text_color.g,
291 text_item.text_style.text_color.b,
292 text_item.text_style.text_color.a,
293 );
294
295 text_areas.push(TextArea {
296 buffer: &text_buffer,
297 left: x as f32,
298 top: y as f32,
299 scale: 1.0, bounds: text_bounds,
301 default_color: text_color,
302 custom_glyphs: &[],
303 });
304 }
305
306 if let Err(err) = self.text_renderer.prepare(
307 device,
308 queue,
309 &mut self.font_system,
310 &mut self.atlas,
311 &self.viewport,
312 text_areas,
313 &mut self.swash_cache,
314 ) {
315 println!("error with glyphon text prepare: {:?}", err);
317 return;
318 }
319
320 if let Err(err) = self
321 .text_renderer
322 .render(&mut self.atlas, &self.viewport, render_pass)
323 {
324 println!("error with glyphon text render: {:?}", err);
326 return;
327 }
328
329 self.atlas.trim();
330 }
331
332 fn anchor_kit_font_family_to_glyphon(font_family: &FontFamily) -> glyphon::Family<'_> {
333 match font_family {
334 FontFamily::Name(name) => glyphon::Family::Name(name),
335 FontFamily::Serif => glyphon::Family::Serif,
336 FontFamily::SansSerif => glyphon::Family::SansSerif,
337 FontFamily::Cursive => glyphon::Family::Cursive,
338 FontFamily::Fantasy => glyphon::Family::Fantasy,
339 FontFamily::Monospace => glyphon::Family::Monospace,
340 }
341 }
342
343 fn anchor_kit_font_weight_to_glyphon(font_weight: &FontWeight) -> glyphon::Weight {
344 match font_weight {
345 FontWeight::Thin => glyphon::Weight::THIN,
346 FontWeight::ExtraLight => glyphon::Weight::EXTRA_LIGHT,
347 FontWeight::Light => glyphon::Weight::LIGHT,
348 FontWeight::Normal => glyphon::Weight::NORMAL,
349 FontWeight::Medium => glyphon::Weight::MEDIUM,
350 FontWeight::SemiBold => glyphon::Weight::SEMIBOLD,
351 FontWeight::Bold => glyphon::Weight::BOLD,
352 FontWeight::ExtraBold => glyphon::Weight::EXTRA_BOLD,
353 FontWeight::Black => glyphon::Weight::BLACK,
354 }
355 }
356
357 fn anchor_kit_font_style_to_glyphon(font_style: &FontStyle) -> glyphon::Style {
358 match font_style {
359 FontStyle::Normal => glyphon::Style::Normal,
360 FontStyle::Italic => glyphon::Style::Italic,
361 FontStyle::Oblique => glyphon::Style::Oblique,
362 }
363 }
364}
365
366pub struct Renderer {
367 main_pipeline: wgpu::RenderPipeline,
368 image_pipeline: wgpu::RenderPipeline, vertex_buffer: wgpu::Buffer,
370 vertex_buffer_capacity: usize,
371 index_buffer: wgpu::Buffer,
372 index_buffer_capacity: usize,
373 glyphon_renderer: GlyphonRenderer,
374 bind_groups: HashMap<Uuid, wgpu::BindGroup>, texture_bind_group_layout: wgpu::BindGroupLayout, }
377
378impl Renderer {
379 pub fn new(
380 device: &wgpu::Device,
381 queue: &wgpu::Queue,
382 texture_format: wgpu::TextureFormat,
383 ) -> Self {
384 let shader = device.create_shader_module(include_wgsl!("shader.wgsl"));
386
387 let initial_vertex_buffer_capacity = 1024; let vertex_buffer = get_vertex_buffer(
389 device,
390 Vertex::capacity_to_bytes(initial_vertex_buffer_capacity),
391 );
392
393 let initial_index_buffer_capacity = 2048;
395 let index_buffer = get_index_buffer(
396 device,
397 (initial_index_buffer_capacity * std::mem::size_of::<u32>()) as wgpu::BufferAddress, );
399
400 let main_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
401 label: Some("anchor-kit main layout"),
402 bind_group_layouts: &[],
403 push_constant_ranges: &[],
404 });
405
406 let main_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
407 label: Some("anchor-kit render pipeline"),
408 layout: Some(&main_pipeline_layout),
409 vertex: wgpu::VertexState {
410 module: &shader,
411 entry_point: Some("vs_main"),
412 buffers: &[Vertex::desc()], compilation_options: wgpu::PipelineCompilationOptions::default(),
414 },
415 fragment: Some(wgpu::FragmentState {
416 module: &shader,
417 entry_point: Some("fs_main"),
418 targets: &[Some(wgpu::ColorTargetState {
419 format: texture_format,
420 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
421 write_mask: wgpu::ColorWrites::ALL,
422 })],
423 compilation_options: wgpu::PipelineCompilationOptions::default(),
424 }),
425 primitive: wgpu::PrimitiveState {
426 topology: wgpu::PrimitiveTopology::TriangleList,
427 strip_index_format: None,
428 front_face: wgpu::FrontFace::Ccw,
429 cull_mode: Some(wgpu::Face::Back),
430 polygon_mode: wgpu::PolygonMode::Fill,
431 unclipped_depth: false,
432 conservative: false,
433 },
434 depth_stencil: None,
435 multisample: wgpu::MultisampleState {
436 count: 1,
437 mask: !0,
438 alpha_to_coverage_enabled: false,
439 },
440 multiview: None,
441 cache: None,
442 });
443
444 let texture_bind_group_layout =
445 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
446 entries: &[
447 wgpu::BindGroupLayoutEntry {
448 binding: 0,
449 visibility: wgpu::ShaderStages::FRAGMENT,
450 ty: wgpu::BindingType::Texture {
451 multisampled: false,
452 view_dimension: wgpu::TextureViewDimension::D2,
453 sample_type: wgpu::TextureSampleType::Float { filterable: true },
454 },
455 count: None,
456 },
457 wgpu::BindGroupLayoutEntry {
458 binding: 1,
459 visibility: wgpu::ShaderStages::FRAGMENT,
460 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
461 count: None,
462 },
463 ],
464 label: Some("anchor-kit bindgroup layout"),
465 });
466
467 let image_pipeline_layout =
468 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
469 label: Some("anchor-kit image pipeline layout"),
470 bind_group_layouts: &[&texture_bind_group_layout], push_constant_ranges: &[],
472 });
473
474 let image_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
476 label: Some("anchor-kit render pipeline"),
477 layout: Some(&image_pipeline_layout),
478 vertex: wgpu::VertexState {
479 module: &shader,
480 entry_point: Some("vs_main"),
481 buffers: &[Vertex::desc()], compilation_options: wgpu::PipelineCompilationOptions::default(),
483 },
484 fragment: Some(wgpu::FragmentState {
485 module: &shader,
486 entry_point: Some("fs_image"), targets: &[Some(wgpu::ColorTargetState {
488 format: texture_format,
489 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
490 write_mask: wgpu::ColorWrites::ALL,
491 })],
492 compilation_options: wgpu::PipelineCompilationOptions::default(),
493 }),
494 primitive: wgpu::PrimitiveState {
495 topology: wgpu::PrimitiveTopology::TriangleList,
496 strip_index_format: None,
497 front_face: wgpu::FrontFace::Ccw,
498 cull_mode: Some(wgpu::Face::Back),
499 polygon_mode: wgpu::PolygonMode::Fill,
500 unclipped_depth: false,
501 conservative: false,
502 },
503 depth_stencil: None,
504 multisample: wgpu::MultisampleState {
505 count: 1,
506 mask: !0,
507 alpha_to_coverage_enabled: false,
508 },
509 multiview: None,
510 cache: None,
511 });
512
513 Renderer {
514 main_pipeline,
515 image_pipeline,
516 vertex_buffer,
517 vertex_buffer_capacity: initial_vertex_buffer_capacity as usize,
518 index_buffer,
519 index_buffer_capacity: initial_index_buffer_capacity as usize,
520 glyphon_renderer: GlyphonRenderer::new(device, queue, texture_format),
521 bind_groups: HashMap::new(),
522 texture_bind_group_layout,
523 }
524 }
525
526 pub fn render(
527 &mut self,
528 device: &wgpu::Device,
529 queue: &wgpu::Queue,
530 render_pass: &mut wgpu::RenderPass<'_>,
531 screen_info: &ScreenInfo,
532 render_list: &RenderList,
533 ) {
534 let mut vertices: Vec<Vertex> = vec![];
536 let mut indices: Vec<u32> = vec![];
537
538 let main_pipeline_index_offset = indices.len();
540 for rect in &render_list.rectangles {
541 let (new_vertices, new_indices) =
543 get_vertices_and_indices_for_rectangle(rect, screen_info, vertices.len() as u32);
544 vertices.extend_from_slice(&new_vertices);
545 indices.extend_from_slice(&new_indices);
546 }
547 let main_pipeline_index_count = indices.len();
548
549 struct ImageDraw {
551 texture_id: Uuid,
552 index_offset: usize, index_count: usize,
554 }
555 let mut image_draws: Vec<ImageDraw> = vec![];
556
557 for image in &render_list.images {
558 let image_index_offset = indices.len();
559
560 let (new_vertices, new_indices) = get_vertices_and_indices_for_rectangle(
561 &image.rectangle,
562 screen_info,
563 vertices.len() as u32,
564 );
565 vertices.extend_from_slice(&new_vertices);
566 indices.extend_from_slice(&new_indices);
567
568 image_draws.push(ImageDraw {
569 texture_id: image.texture_id,
570 index_offset: image_index_offset,
571 index_count: new_indices.len(),
572 })
573 }
574
575 self.resize_vertex_buffer_if_required(device, vertices.len());
577 self.resize_index_buffer_if_required(device, indices.len());
578
579 queue.write_buffer(&self.vertex_buffer, 0, bytemuck::cast_slice(&vertices));
581 queue.write_buffer(&self.index_buffer, 0, bytemuck::cast_slice(&indices));
582
583 render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
584 render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
585
586 render_pass.set_pipeline(&self.main_pipeline);
588 render_pass.draw_indexed(
589 main_pipeline_index_offset as u32..main_pipeline_index_count as u32, 0,
591 0..1,
592 );
593
594 render_pass.set_pipeline(&self.image_pipeline);
596 for image_draw in image_draws.iter() {
597 if let Some(bind_group) = self.bind_groups.get(&image_draw.texture_id) {
598 render_pass.set_bind_group(0, bind_group, &[]);
599 render_pass.draw_indexed(
600 image_draw.index_offset as u32
601 ..(image_draw.index_offset + image_draw.index_count) as u32, 0,
603 0..1,
604 );
605 }
606 }
607
608 self.glyphon_renderer
610 .render_text(device, queue, render_pass, screen_info, render_list);
611 }
612
613 pub fn get_image_id_from_bytes(
615 &mut self,
616 device: &wgpu::Device,
617 queue: &wgpu::Queue,
618 diffuse_bytes: &[u8],
619 ) -> Uuid {
620 let diffuse_image = image::load_from_memory(diffuse_bytes).unwrap();
621 let diffuse_rgba = diffuse_image.to_rgba8();
622 let dimensions = diffuse_image.dimensions();
623
624 let texture_size = wgpu::Extent3d {
626 width: dimensions.0,
627 height: dimensions.1,
628 depth_or_array_layers: 1, };
630
631 let diffuse_texture = device.create_texture(&wgpu::TextureDescriptor {
632 size: texture_size,
633 mip_level_count: 1,
634 sample_count: 1,
635 dimension: wgpu::TextureDimension::D2,
636 format: wgpu::TextureFormat::Rgba8UnormSrgb,
637 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
638 label: Some("anchor-kit diffuse texture"),
639 view_formats: &[],
640 });
641
642 queue.write_texture(
643 wgpu::TexelCopyTextureInfo {
644 texture: &diffuse_texture,
645 mip_level: 0,
646 origin: wgpu::Origin3d::ZERO,
647 aspect: wgpu::TextureAspect::All,
648 },
649 &diffuse_rgba,
650 wgpu::TexelCopyBufferLayout {
651 offset: 0,
652 bytes_per_row: Some(4 * dimensions.0),
653 rows_per_image: Some(dimensions.1),
654 },
655 texture_size,
656 );
657
658 let diffuse_texture_view =
659 diffuse_texture.create_view(&wgpu::TextureViewDescriptor::default());
660
661 let diffuse_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
662 address_mode_u: wgpu::AddressMode::ClampToEdge,
663 address_mode_v: wgpu::AddressMode::ClampToEdge,
664 address_mode_w: wgpu::AddressMode::ClampToEdge,
665 mag_filter: wgpu::FilterMode::Linear,
666 min_filter: wgpu::FilterMode::Linear,
667 mipmap_filter: wgpu::FilterMode::Linear,
668 ..Default::default()
669 });
670
671 let diffuse_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
672 layout: &self.texture_bind_group_layout, entries: &[
674 wgpu::BindGroupEntry {
675 binding: 0,
676 resource: wgpu::BindingResource::TextureView(&diffuse_texture_view),
677 },
678 wgpu::BindGroupEntry {
679 binding: 1,
680 resource: wgpu::BindingResource::Sampler(&diffuse_sampler),
681 },
682 ],
683 label: Some("anchor-kit diffuse bind group"),
684 });
685
686 let id = Uuid::new_v4();
688 self.bind_groups.insert(id, diffuse_bind_group);
689 id }
691
692 fn resize_vertex_buffer_if_required(
693 &mut self,
694 device: &wgpu::Device,
695 num_requested_vertices: usize,
696 ) {
697 if num_requested_vertices <= self.vertex_buffer_capacity {
698 return;
699 }
700 let new_size = num_requested_vertices.next_power_of_two(); self.vertex_buffer = get_vertex_buffer(device, Vertex::capacity_to_bytes(new_size));
702 self.vertex_buffer_capacity = new_size;
703 }
704
705 fn resize_index_buffer_if_required(
706 &mut self,
707 device: &wgpu::Device,
708 num_requested_indices: usize,
709 ) {
710 if num_requested_indices <= self.index_buffer_capacity {
711 return;
712 }
713 let new_size = num_requested_indices.next_power_of_two();
714 self.index_buffer = get_index_buffer(
715 device,
716 (new_size * std::mem::size_of::<u32>()) as wgpu::BufferAddress, );
718 self.index_buffer_capacity = new_size;
719 }
720}