1use crate::renderer::SurtrRenderer;
3use crate::types::*;
4use crate::vertex::*;
5use cvkg_core::LAYOUT_DIRTY;
6use cvkg_core::{ColorTheme, Mesh, Rect, Renderer};
7use lyon::math::point;
8use lyon::tessellation::{BuffersBuilder, StrokeOptions, StrokeTessellator, VertexBuffers};
9use std::sync::atomic::Ordering;
10
11impl cvkg_core::ElapsedTime for SurtrRenderer {
12 fn delta_time(&self) -> f32 {
13 self.current_scene.delta_time
14 }
15
16 fn elapsed_time(&self) -> f32 {
17 self.start_time.elapsed().as_secs_f32()
18 }
19}
20
21impl cvkg_core::Renderer for SurtrRenderer {
22 fn is_over_budget(&self) -> bool {
23 self.frame_budget.allow_degradation
24 && self.last_frame_start.elapsed().as_secs_f32() * 1000.0 > self.frame_budget.target_ms
25 }
26
27 fn prewarm_vram(&mut self, assets: Vec<(String, Vec<u8>)>) {
29 log::info!(
30 "[Surtr] Pre-warming Mega-Heim with {} assets...",
31 assets.len()
32 );
33 for (name, data) in assets {
34 self.load_image_to_heim(&name, &data);
35 }
36 }
37
38 fn fill_rect(&mut self, rect: Rect, color: [f32; 4]) {
39 self.fill_rect_with_mode(rect, self.apply_opacity(color), 0, None);
40 }
41
42 fn fill_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4]) {
43 self.fill_rect_with_full_params(
44 rect,
45 self.apply_opacity(color),
46 3,
47 None,
48 radius,
49 Rect {
50 x: 0.0,
51 y: 0.0,
52 width: 1.0,
53 height: 1.0,
54 },
55 );
56 }
57
58 fn fill_glass_rect(&mut self, rect: Rect, radius: f32, blur_radius: f32) {
64 let blur_strength = (blur_radius / 100.0).clamp(0.0, 4.0);
67
68 if self.current_z != 0.0 {
71 self.portal_regions.push_back(rect);
72 }
73
74 self.fill_rect_with_full_params(
75 rect,
76 [1.0, 1.0, 1.0, 0.4], 7, None,
79 radius,
80 Rect {
81 x: 0.0,
82 y: 0.0,
83 width: 1.0,
84 height: 1.0,
85 },
86 );
87 }
88
89 fn fill_ellipse(&mut self, rect: Rect, color: [f32; 4]) {
90 self.fill_rect_with_full_params(
91 rect,
92 self.apply_opacity(color),
93 4,
94 None,
95 0.0,
96 Rect {
97 x: 0.0,
98 y: 0.0,
99 width: 1.0,
100 height: 1.0,
101 },
102 );
103 }
104
105 fn draw_3d_cube(&mut self, rect: Rect, color: [f32; 4], rotation: [f32; 3]) {
106 self.fill_rect_with_full_params_and_slice(
107 rect,
108 self.apply_opacity(color),
109 21,
110 None,
111 0.0,
112 Rect {
113 x: 0.0,
114 y: 0.0,
115 width: 1.0,
116 height: 1.0,
117 },
118 [rotation[0], rotation[1], rotation[2], 0.0],
119 [0.0, 0.0],
120 );
121 }
122
123 fn bifrost(&mut self, rect: Rect, blur: f32, _saturation: f32, opacity: f32) {
124 let logical_w = self.current_width() as f32 / self.current_scale_factor();
126 let logical_h = self.current_height() as f32 / self.current_scale_factor();
127 let screen_uv = Rect {
128 x: rect.x / logical_w,
129 y: rect.y / logical_h,
130 width: rect.width / logical_w,
131 height: rect.height / logical_h,
132 };
133 self.fill_rect_with_full_params(rect, [1.0, 1.0, 1.0, opacity], 7, None, blur, screen_uv);
136 }
137
138 fn gungnir(&mut self, rect: Rect, color: [f32; 4], radius: f32, intensity: f32) {
139 let center_x = rect.x + rect.width * 0.5;
142 let center_y = rect.y + rect.height * 0.5;
143 let max_dim = rect.width.max(rect.height) * 0.5 + radius;
144
145 for i in 0..8 {
147 let alpha = intensity / (i as f32 + 1.0) * 0.3;
148 let glow_color = [color[0], color[1], color[2], alpha];
149 self.fill_rect_with_mode(
150 Rect {
151 x: center_x - max_dim - i as f32 * 2.0,
152 y: center_y - max_dim - i as f32 * 2.0,
153 width: max_dim * 2.0 + i as f32 * 4.0,
154 height: max_dim * 2.0 + i as f32 * 4.0,
155 },
156 glow_color,
157 8, None,
159 );
160 }
161 }
162
163 fn mani_glow(&mut self, rect: Rect, color: [f32; 4], radius: f32) {
170 let margin = radius;
171 let glow_rect = Rect {
172 x: rect.x - margin,
173 y: rect.y - margin,
174 width: rect.width + 2.0 * margin,
175 height: rect.height + 2.0 * margin,
176 };
177 let uv_rect = Rect {
178 x: margin,
179 y: radius,
180 width: 0.0,
181 height: 0.0,
182 };
183 self.fill_rect_with_full_params(
184 glow_rect,
185 self.apply_opacity(color),
186 18,
187 None,
188 8.0,
189 uv_rect,
190 );
191 }
192
193 fn stroke_rect(&mut self, rect: Rect, color: [f32; 4], stroke_width: f32) {
194 let c = self.apply_opacity(color);
195 let hw = stroke_width;
196 self.fill_rect_with_mode(
198 Rect {
199 x: rect.x,
200 y: rect.y,
201 width: rect.width,
202 height: hw,
203 },
204 c,
205 1,
206 None,
207 );
208 self.fill_rect_with_mode(
209 Rect {
210 x: rect.x,
211 y: rect.y + rect.height - hw,
212 width: rect.width,
213 height: hw,
214 },
215 c,
216 1,
217 None,
218 );
219 self.fill_rect_with_mode(
220 Rect {
221 x: rect.x,
222 y: rect.y,
223 width: hw,
224 height: rect.height,
225 },
226 c,
227 1,
228 None,
229 );
230 self.fill_rect_with_mode(
231 Rect {
232 x: rect.x + rect.width - hw,
233 y: rect.y,
234 width: hw,
235 height: rect.height,
236 },
237 c,
238 1,
239 None,
240 );
241 }
242
243 fn stroke_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4], stroke_width: f32) {
244 self.fill_rect_with_full_params(
245 rect,
246 self.apply_opacity(color),
247 17,
248 None,
249 radius,
250 Rect {
251 x: stroke_width,
252 y: 0.0,
253 width: 0.0,
254 height: 0.0,
255 },
256 );
257 }
258
259 fn stroke_ellipse(&mut self, rect: Rect, color: [f32; 4], stroke_width: f32) {
260 let cx = rect.x + rect.width / 2.0;
262 let cy = rect.y + rect.height / 2.0;
263 let rx = rect.width / 2.0;
264 let ry = rect.height / 2.0;
265
266 let mut builder = lyon::path::Path::builder();
268 if rx > 0.0 && ry > 0.0 {
269 let segments = 64;
271 for i in 0..segments {
272 let angle = 2.0 * std::f32::consts::PI * (i as f32) / (segments as f32);
273 let x = cx + rx * angle.cos();
274 let y = cy + ry * angle.sin();
275 if i == 0 {
276 builder.begin(lyon::math::point(x, y));
277 } else {
278 builder.line_to(lyon::math::point(x, y));
279 }
280 }
281 builder.close();
282 }
283 let path = builder.build();
284 self.stroke_path(&path, color, stroke_width);
285 }
286
287 fn draw_linear_gradient(
288 &mut self,
289 rect: Rect,
290 start_color: [f32; 4],
291 end_color: [f32; 4],
292 angle: f32,
293 ) {
294 self.fill_rect_with_full_params_and_slice(
295 rect,
296 self.apply_opacity(start_color),
297 15,
298 None,
299 0.0,
300 Rect {
301 x: angle,
302 y: 0.0,
303 width: 1.0,
304 height: 1.0,
305 },
306 end_color,
307 [0.0, 0.0],
308 );
309 }
310
311 fn draw_radial_gradient(&mut self, rect: Rect, inner_color: [f32; 4], outer_color: [f32; 4]) {
312 self.fill_rect_with_full_params_and_slice(
313 rect,
314 self.apply_opacity(inner_color),
315 16,
316 None,
317 0.0,
318 Rect {
319 x: 0.0,
320 y: 0.0,
321 width: 1.0,
322 height: 1.0,
323 },
324 outer_color,
325 [0.0, 0.0],
326 );
327 }
328
329 fn draw_drop_shadow(
330 &mut self,
331 rect: Rect,
332 radius: f32,
333 color: [f32; 4],
334 blur: f32,
335 spread: f32,
336 ) {
337 let margin = blur + spread;
338 let inflated = Rect {
339 x: rect.x - margin,
340 y: rect.y - margin,
341 width: rect.width + margin * 2.0,
342 height: rect.height + margin * 2.0,
343 };
344 self.fill_rect_with_full_params(
346 inflated,
347 self.apply_opacity(color),
348 18,
349 None,
350 radius,
351 Rect {
352 x: margin,
353 y: blur,
354 width: 0.0,
355 height: 0.0,
356 },
357 );
358 }
359
360 fn stroke_dashed_rounded_rect(
361 &mut self,
362 rect: Rect,
363 radius: f32,
364 color: [f32; 4],
365 width: f32,
366 dash: f32,
367 gap: f32,
368 ) {
369 self.fill_rect_with_full_params(
370 rect,
371 self.apply_opacity(color),
372 19,
373 None,
374 radius,
375 Rect {
376 x: width,
377 y: dash,
378 width: gap,
379 height: 0.0,
380 },
381 );
382 }
383
384 fn draw_9slice(
385 &mut self,
386 image_name: &str,
387 rect: Rect,
388 left: f32,
389 top: f32,
390 right: f32,
391 bottom: f32,
392 ) {
393 let c = self.apply_opacity([1.0, 1.0, 1.0, 1.0]);
394 let tid = self.get_texture_id(image_name);
395 self.fill_rect_with_full_params(
396 rect,
397 c,
398 20,
399 tid,
400 bottom,
401 Rect {
402 x: left,
403 y: top,
404 width: right,
405 height: 0.0,
406 },
407 );
408 }
409
410 fn draw_line(
411 &mut self,
412 x1: f32,
413 y1: f32,
414 x2: f32,
415 y2: f32,
416 color: [f32; 4],
417 stroke_width: f32,
418 ) {
419 let dx = x2 - x1;
420 let dy = y2 - y1;
421 let len = (dx * dx + dy * dy).sqrt();
422 if len < 0.001 {
423 return;
424 }
425
426 let mut builder = lyon::path::Path::builder();
429 builder.begin(point(x1, y1));
430 builder.line_to(point(x2, y2));
431 builder.close();
432 let path = builder.build();
433
434 self.stroke_path(&path, color, stroke_width);
435 }
436
437 fn draw_image(&mut self, image_name: &str, rect: Rect) {
438 if !self.image_uv_registry.contains(image_name) {
440 log::warn!("[Surtr] draw_image: '{}' not loaded, skipping", image_name);
441 return;
442 }
443 let tid = self
444 .get_texture_id(image_name)
445 .or_else(|| self.get_texture_id("__mega_heim"));
446 let uv_rect = self
447 .image_uv_registry
448 .get(image_name)
449 .copied()
450 .unwrap_or(Rect {
451 x: 0.0,
452 y: 0.0,
453 width: 1.0,
454 height: 1.0,
455 });
456 self.fill_rect_with_full_params(rect, [1.0, 1.0, 1.0, 1.0], 2, tid, 0.0, uv_rect);
457 }
458
459 fn draw_text(&mut self, text: &str, x: f32, y: f32, size: f32, color: [f32; 4]) {
460 let scaled_size = size * self.current_scale_factor();
462 let shaped = self.shape_text_with_stack(text, scaled_size);
463 let c = self.apply_opacity(color);
464
465 for glyph in shaped.glyphs {
466 let cache_key = glyph.cache_key;
467
468 let (uv_rect, w, h, x_off, y_off) = if let Some(info) = self.text_cache.get(&cache_key)
469 {
470 *info
471 } else {
472 if let Some(image) = self.text_engine.rasterize(cache_key) {
473 let gw = image.width;
474 let gh = image.height;
475 let x_offset = image.x_offset;
476 let y_offset = image.y_offset;
477
478 let pack_res = self.heim_packer.pack(gw, gh);
479 let (nx, ny) = if let Some(pos) = pack_res {
480 pos
481 } else {
482 self.reclaim_vram();
484 match self.heim_packer.pack(gw, gh) {
485 Some(pos) => pos,
486 None => {
487 log::error!(
488 "Glyph heim critically full after reclaim: cannot pack {}x{} glyph for '{}', skipping",
489 gw,
490 gh,
491 text
492 );
493 continue; }
495 }
496 };
497
498 let rgba_data = if image.data.len() == (gw * gh * 4) as usize {
502 image.data
503 } else {
504 let mut data = Vec::with_capacity((gw * gh * 4) as usize);
505 for alpha in &image.data {
506 data.push(255);
507 data.push(255);
508 data.push(255);
509 data.push(*alpha);
510 }
511 data
512 };
513
514 self.queue.write_texture(
515 wgpu::TexelCopyTextureInfo {
516 texture: &self.mega_heim_tex,
517 mip_level: 0,
518 origin: wgpu::Origin3d { x: nx, y: ny, z: 0 },
519 aspect: wgpu::TextureAspect::All,
520 },
521 &rgba_data,
522 wgpu::TexelCopyBufferLayout {
523 offset: 0,
524 bytes_per_row: Some(gw * 4),
525 rows_per_image: Some(gh),
526 },
527 wgpu::Extent3d {
528 width: gw,
529 height: gh,
530 depth_or_array_layers: 1,
531 },
532 );
533
534 let info = (
535 Rect {
536 x: nx as f32 / 4096.0,
537 y: ny as f32 / 4096.0,
538 width: gw as f32 / 4096.0,
539 height: gh as f32 / 4096.0,
540 },
541 gw as f32,
542 gh as f32,
543 x_offset,
544 y_offset,
545 );
546 self.text_cache.put(cache_key, info);
547 info
548 } else {
549 (Rect::zero(), 0.0, 0.0, 0.0, 0.0)
550 }
551 };
552
553 if w > 0.0 {
554 let baseline_y = y + shaped.ascent / self.current_scale_factor();
558 let glyph_rect = Rect {
559 x: x + (glyph.x + x_off) / self.current_scale_factor(),
560 y: baseline_y + (glyph.y - y_off) / self.current_scale_factor(),
561 width: w / self.current_scale_factor(),
562 height: h / self.current_scale_factor(),
563 };
564 let tid = self.get_texture_id("__mega_heim");
565 self.fill_rect_with_full_params(glyph_rect, c, 6, tid, 0.0, uv_rect);
566 }
567 }
568 }
569
570 fn measure_text(&mut self, text: &str, size: f32) -> (f32, f32) {
572 let sf = self.current_scale_factor();
573 let shaped = self.shape_text_with_stack(text, size * sf);
574 (shaped.width / sf, shaped.height / sf)
576 }
577
578 fn shape_rich_text(
579 &mut self,
580 spans: &[cvkg_runic_text::TextSpan],
581 max_width: Option<f32>,
582 align: cvkg_runic_text::TextAlign,
583 overflow: cvkg_runic_text::TextOverflow,
584 ) -> Option<cvkg_runic_text::ShapedText> {
585 let sf = self.current_scale_factor();
586 let mut scaled_spans = spans.to_vec();
587 for span in &mut scaled_spans {
588 span.style.font_size *= sf;
589 if span.style.fallback_families.is_empty() {
590 span.style.fallback_families = vec![
591 "SF Pro".to_string(),
592 "Inter".to_string(),
593 "Helvetica Neue".to_string(),
594 "Helvetica".to_string(),
595 "Arial".to_string(),
596 "sans-serif".to_string(),
597 ];
598 }
599 }
600 let scaled_max_width = max_width.map(|w| w * sf);
601 self.text_engine
602 .shape_layout(&scaled_spans, scaled_max_width, align, overflow)
603 .ok()
604 }
605
606 fn draw_shaped_text(&mut self, shaped: &cvkg_runic_text::ShapedText, x: f32, y: f32) {
607 for glyph in &shaped.glyphs {
608 let byte_idx = shaped
609 .grapheme_boundaries
610 .get(glyph.cluster as usize)
611 .copied()
612 .unwrap_or(0);
613 let mut span_color = [1.0, 1.0, 1.0, 1.0];
614 for span in &shaped.spans {
615 if byte_idx >= span.byte_offset && byte_idx < span.byte_offset + span.text.len() {
616 span_color = [
617 span.style.color[0] as f32 / 255.0,
618 span.style.color[1] as f32 / 255.0,
619 span.style.color[2] as f32 / 255.0,
620 span.style.color[3] as f32 / 255.0,
621 ];
622 break;
623 }
624 }
625 let c = self.apply_opacity(span_color);
626
627 let cache_key = glyph.cache_key;
628 let (uv_rect, w, h, x_off, y_off) = if let Some(info) = self.text_cache.get(&cache_key)
629 {
630 *info
631 } else {
632 if let Some(image) = self.text_engine.rasterize(cache_key) {
633 let gw = image.width;
634 let gh = image.height;
635 let x_offset = image.x_offset;
636 let y_offset = image.y_offset;
637
638 let pack_res = self.heim_packer.pack(gw, gh);
639 let (nx, ny) = if let Some(pos) = pack_res {
640 pos
641 } else {
642 self.reclaim_vram();
643 match self.heim_packer.pack(gw, gh) {
644 Some(pos) => pos,
645 None => {
646 log::error!(
647 "Glyph heim critically full after reclaim: cannot pack {}x{} glyph, skipping",
648 gw,
649 gh
650 );
651 continue; }
653 }
654 };
655
656 let rgba_data = if image.data.len() == (gw * gh * 4) as usize {
660 image.data
661 } else {
662 let mut data = Vec::with_capacity((gw * gh * 4) as usize);
663 for alpha in &image.data {
664 data.push(255);
665 data.push(255);
666 data.push(255);
667 data.push(*alpha);
668 }
669 data
670 };
671
672 self.queue.write_texture(
673 wgpu::TexelCopyTextureInfo {
674 texture: &self.mega_heim_tex,
675 mip_level: 0,
676 origin: wgpu::Origin3d { x: nx, y: ny, z: 0 },
677 aspect: wgpu::TextureAspect::All,
678 },
679 &rgba_data,
680 wgpu::TexelCopyBufferLayout {
681 offset: 0,
682 bytes_per_row: Some(gw * 4),
683 rows_per_image: Some(gh),
684 },
685 wgpu::Extent3d {
686 width: gw,
687 height: gh,
688 depth_or_array_layers: 1,
689 },
690 );
691
692 let info = (
693 Rect {
694 x: nx as f32 / 4096.0,
695 y: ny as f32 / 4096.0,
696 width: gw as f32 / 4096.0,
697 height: gh as f32 / 4096.0,
698 },
699 gw as f32,
700 gh as f32,
701 x_offset,
702 y_offset,
703 );
704 self.text_cache.put(cache_key, info);
705 info
706 } else {
707 (Rect::zero(), 0.0, 0.0, 0.0, 0.0)
708 }
709 };
710
711 if w > 0.0 {
712 let sf = self.current_scale_factor();
713 let baseline_y = y + shaped.ascent / sf;
717 let glyph_rect = Rect {
718 x: x + (glyph.x + x_off) / sf,
719 y: baseline_y + (glyph.y - y_off) / sf,
720 width: w / sf,
721 height: h / sf,
722 };
723 let tid = self.get_texture_id("__mega_heim");
724 let slice = self
725 .slice_stack
726 .last()
727 .copied()
728 .map(|(a, o)| [a, o, 1.0, 1.0])
729 .unwrap_or([0.0, 0.0, 0.0, 1.0]);
730 self.fill_rect_with_full_params_and_slice(
731 glyph_rect,
732 c,
733 6,
734 tid,
735 0.0,
736 uv_rect,
737 slice,
738 [glyph.glyph_index as f32, glyph.time_offset],
739 );
740 }
741 }
742 }
743
744 fn draw_texture(&mut self, texture_id: u32, rect: Rect) {
745 self.fill_rect_with_full_params_and_slice(
746 rect,
747 [1.0, 1.0, 1.0, 1.0],
748 2,
749 Some(texture_id),
750 0.0,
751 Rect {
752 x: 0.0,
753 y: 0.0,
754 width: 1.0,
755 height: 1.0,
756 },
757 [0.0, 0.0, 0.0, 1.0],
758 [0.0, 0.0],
759 );
760 }
761
762 fn load_image(&mut self, name: &str, data: &[u8]) {
765 if self.image_uv_registry.contains(name) {
766 return;
767 }
768 let img_result = image::load_from_memory(data);
769 let img = match img_result {
770 Ok(img) => img.to_rgba8(),
771 Err(e) => {
772 log::error!("Failed to load image {}: {}", name, e);
773 image::RgbaImage::from_pixel(1, 1, image::Rgba([255, 255, 255, 255]))
774 }
775 };
776 let (width, height) = img.dimensions();
777
778 let size = wgpu::Extent3d {
779 width,
780 height,
781 depth_or_array_layers: 1,
782 };
783 let texture = self.device.create_texture(&wgpu::TextureDescriptor {
784 label: Some(&format!("Texture Array Layer: {}", name)),
785 size,
786 mip_level_count: 1,
787 sample_count: 1,
788 dimension: wgpu::TextureDimension::D2,
789 format: wgpu::TextureFormat::Rgba8UnormSrgb,
790 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
791 view_formats: &[],
792 });
793
794 self.queue.write_texture(
795 wgpu::TexelCopyTextureInfo {
796 texture: &texture,
797 mip_level: 0,
798 origin: wgpu::Origin3d::ZERO,
799 aspect: wgpu::TextureAspect::All,
800 },
801 &img,
802 wgpu::TexelCopyBufferLayout {
803 offset: 0,
804 bytes_per_row: Some(4 * width),
805 rows_per_image: Some(height),
806 },
807 size,
808 );
809
810 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
811
812 let index = if self.texture_registry.len() < 255 {
814 (self.texture_registry.len() + 1) as u32
815 } else {
816 if let Some((old_name, old_index)) = self.texture_registry.pop_lru() {
818 self.image_uv_registry.pop(&old_name);
819 old_index
820 } else {
821 1 }
823 };
824
825 self.texture_views[index as usize] = view;
826 self.image_uv_registry.put(
827 name.to_string(),
828 Rect {
829 x: 0.0,
830 y: 0.0,
831 width: 1.0,
832 height: 1.0,
833 },
834 );
835 self.texture_registry.put(name.to_string(), index);
836 self.rebuild_texture_array_bind_group();
837 }
838
839 fn push_clip_rect(&mut self, rect: Rect) {
840 self.clip_stack.push(rect);
841 }
842
843 fn pop_clip_rect(&mut self) {
844 self.clip_stack.pop();
845 }
846
847 fn current_clip_rect(&self) -> Rect {
848 self.clip_stack.last().copied().unwrap_or(Rect::new(
849 0.0,
850 0.0,
851 self.current_width() as f32,
852 self.current_height() as f32,
853 ))
854 }
855
856 fn memoize(&mut self, id: u64, data_hash: u64, render_fn: &dyn Fn(&mut dyn Renderer)) {
857 let should_skip = self.memo_cache.get(&id) == Some(&data_hash);
860
861 if !should_skip {
862 self.memo_cache.insert(id, data_hash);
864 render_fn(self);
865 }
866 }
868
869 fn push_opacity(&mut self, opacity: f32) {
870 let current = self.opacity_stack.last().copied().unwrap_or(1.0);
871 self.opacity_stack.push(current * opacity);
872 }
873
874 fn pop_opacity(&mut self) {
875 self.opacity_stack.pop();
876 }
877
878 fn push_shadow(&mut self, radius: f32, color: [f32; 4], offset: [f32; 2]) {
879 self.shadow_stack.push(ShadowState {
880 radius,
881 color,
882 _offset: offset,
883 });
884 }
885
886 fn pop_shadow(&mut self) {
887 self.shadow_stack.pop();
888 }
889
890 fn push_transform(&mut self, translation: [f32; 2], scale: [f32; 2], rotation: f32) {
891 let c = rotation.cos();
892 let sn = rotation.sin();
893 let affine = glam::Mat3::from_cols(
894 glam::Vec3::new(c * scale[0], sn * scale[0], 0.0),
895 glam::Vec3::new(-sn * scale[1], c * scale[1], 0.0),
896 glam::Vec3::new(translation[0], translation[1], 1.0),
897 );
898
899 let parent = self
900 .transform_stack
901 .last()
902 .copied()
903 .unwrap_or(glam::Mat3::IDENTITY);
904 self.transform_stack.push(parent * affine);
905 }
906
907 fn push_affine(&mut self, transform: [f32; 6]) {
908 let affine = glam::Mat3::from_cols(
909 glam::Vec3::new(transform[0], transform[1], 0.0),
910 glam::Vec3::new(transform[2], transform[3], 0.0),
911 glam::Vec3::new(transform[4], transform[5], 1.0),
912 );
913 let parent = self
914 .transform_stack
915 .last()
916 .copied()
917 .unwrap_or(glam::Mat3::IDENTITY);
918 self.transform_stack.push(parent * affine);
919 }
920
921 fn pop_transform(&mut self) {
922 self.transform_stack.pop();
923 }
924
925 fn set_theme(&mut self, theme: ColorTheme) {
926 self.current_theme = theme;
927 self.queue
928 .write_buffer(&self.theme_buffer, 0, bytemuck::bytes_of(&theme));
929 }
930
931 fn set_rage(&mut self, rage: f32) {
932 self.current_scene.berzerker_rage = rage;
933 }
935
936 fn trigger_shatter_event(&mut self, origin: [f32; 2], force: f32) {
937 self.current_scene.shatter_origin = origin;
938 self.current_scene.shatter_time = self.current_scene.time;
939 self.current_scene.shatter_force = force;
940 }
941
942 fn set_scene_preset(&mut self, preset: u32) {
943 self.current_scene.scene_type = preset;
944 }
945
946 fn push_mjolnir_slice(&mut self, angle: f32, offset: f32) {
949 self.slice_stack.push((angle, offset));
950 }
951
952 fn pop_mjolnir_slice(&mut self) {
954 self.slice_stack.pop();
955 }
956
957 fn mjolnir_shatter(&mut self, rect: Rect, pieces: u32, force: f32, color: [f32; 4]) {
958 self.shatter_internal(rect, pieces, force, color, 8);
959 }
960
961 fn mjolnir_fluid_shatter(&mut self, rect: Rect, pieces: u32, force: f32, color: [f32; 4]) {
962 self.shatter_internal(rect, pieces, force, color, 11);
963 }
964
965 fn draw_mjolnir_bolt(&mut self, from: [f32; 2], to: [f32; 2], color: [f32; 4]) {
966 self.recursive_bolt(from, to, 4, color);
967 }
968
969 fn dispatch_particles(
970 &mut self,
971 origin: [f32; 2],
972 count: u32,
973 effect_type: &str,
974 _color: [f32; 4],
975 ) {
976 log::info!(
977 "[Surtr] Dispatching {} {} particles at {:?}",
978 count,
979 effect_type,
980 origin
981 );
982 }
984
985 fn draw_hologram(&mut self, rect: Rect, hologram_id: &str, time: f32) {
986 log::info!(
987 "[Surtr] Drawing hologram {} at {:?} (t={})",
988 hologram_id,
989 rect,
990 time
991 );
992 self.stroke_rect(rect, [0.0, 1.0, 1.0, 0.5], 2.0);
995 }
996
997 fn upload_data_texture(&mut self, id: &str, data: &[f32], width: u32, height: u32) {
998 let size = wgpu::Extent3d {
999 width,
1000 height,
1001 depth_or_array_layers: 1,
1002 };
1003 let texture = self.device.create_texture(&wgpu::TextureDescriptor {
1004 label: Some(id),
1005 size,
1006 mip_level_count: 1,
1007 sample_count: 1,
1008 dimension: wgpu::TextureDimension::D2,
1009 format: wgpu::TextureFormat::R32Float,
1010 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1011 view_formats: &[],
1012 });
1013 self.queue.write_texture(
1014 wgpu::TexelCopyTextureInfo {
1015 texture: &texture,
1016 mip_level: 0,
1017 origin: wgpu::Origin3d::ZERO,
1018 aspect: wgpu::TextureAspect::All,
1019 },
1020 bytemuck::cast_slice(data),
1021 wgpu::TexelCopyBufferLayout {
1022 offset: 0,
1023 bytes_per_row: Some(4 * width),
1024 rows_per_image: Some(height),
1025 },
1026 size,
1027 );
1028 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
1029 let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
1030 address_mode_u: wgpu::AddressMode::ClampToEdge,
1031 address_mode_v: wgpu::AddressMode::ClampToEdge,
1032 mag_filter: wgpu::FilterMode::Linear,
1033 ..Default::default()
1034 });
1035 let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1036 layout: &self.texture_bind_group_layout,
1037 entries: &[
1038 wgpu::BindGroupEntry {
1039 binding: 0,
1040 resource: wgpu::BindingResource::TextureViewArray(&vec![&view; 256]),
1041 },
1042 wgpu::BindGroupEntry {
1043 binding: 1,
1044 resource: wgpu::BindingResource::Sampler(&sampler),
1045 },
1046 ],
1047 label: Some(id),
1048 });
1049 self.texture_bind_groups.push(bind_group);
1050 let tid = (self.texture_bind_groups.len() - 1) as u32;
1051 self.texture_registry.put(id.to_string(), tid);
1052 }
1053
1054 fn draw_heatmap(&mut self, texture_id: &str, rect: Rect, _palette: &str) {
1055 let tid = self.get_texture_id(texture_id);
1056 self.fill_rect_with_mode(rect, [1.0, 1.0, 1.0, 1.0], 12, tid);
1057 }
1058
1059 fn draw_mesh(&mut self, mesh: &Mesh, color: [f32; 4], transform: glam::Mat4) {
1060 let base_idx = self.vertices.len() as u32;
1061 let screen = [self.current_width() as f32, self.current_height() as f32];
1062
1063 for i in 0..mesh.vertices.len() {
1064 let pos = transform.transform_point3(glam::Vec3::from(mesh.vertices[i]));
1065 let norm = transform.transform_vector3(glam::Vec3::from(mesh.normals[i]));
1066
1067 self.vertices.push(Vertex {
1068 position: pos.to_array(),
1069 normal: norm.to_array(),
1070 uv: [0.0, 0.0],
1071 color,
1072 material_id: 13, radius: 0.0,
1074 slice: [0.0, 0.0, 0.0, 1.0],
1075 logical: [0.0, 0.0],
1076 size: [0.0, 0.0],
1077 clip: [-f32::INFINITY, -f32::INFINITY, f32::INFINITY, f32::INFINITY],
1078 tex_index: 0,
1079 });
1080 }
1081
1082 for idx in &mesh.indices {
1083 self.indices.push(base_idx + idx);
1084 }
1085
1086 let (translation, scale_transform, rotation, _, _) = self.current_transform();
1087
1088 if self.draw_calls.is_empty() || self.current_texture_id.is_some() {
1089 self.current_texture_id = None;
1090
1091 self.instance_data.push(InstanceData {
1092 translation,
1093 scale: scale_transform,
1094 rotation,
1095 blur_radius: 0.0,
1096 ior_override: 0.0,
1097 });
1098 self.draw_calls.push(DrawCall {
1099 target_id: None,
1100 texture_id: None,
1101 scissor_rect: self.clip_stack.last().copied(),
1102 index_start: (self.indices.len() as u32) - (mesh.indices.len() as u32),
1103 index_count: mesh.indices.len() as u32,
1104 material: cvkg_core::DrawMaterial::Opaque,
1105 instance_start: (self.instance_data.len() - 1) as u32,
1106 });
1107 } else {
1108 self.draw_calls.last_mut().unwrap().index_count += mesh.indices.len() as u32;
1109 }
1110 }
1111
1112 fn draw_mesh_3d(
1113 &mut self,
1114 mesh: &Mesh,
1115 material: &cvkg_core::Material3D,
1116 transform: &cvkg_core::Transform3D,
1117 ) {
1118 let base_idx = self.vertices.len() as u32;
1119 let screen = [self.current_width() as f32, self.current_height() as f32];
1120 let model_matrix = transform.to_matrix();
1121
1122 for i in 0..mesh.vertices.len() {
1123 let pos = model_matrix.transform_point3(glam::Vec3::from(mesh.vertices[i]));
1124 let norm = model_matrix.transform_vector3(glam::Vec3::from(mesh.normals[i]));
1125
1126 self.vertices.push(Vertex {
1127 position: [pos.x, pos.y, pos.z],
1128 normal: [norm.x, norm.y, norm.z],
1129 uv: [0.0, 0.0],
1130 color: material.base_color,
1131 material_id: 13, radius: 0.0,
1133 slice: [material.metallic, material.roughness, material.opacity, 1.0],
1134 logical: [0.0, 0.0],
1135 size: [0.0, 0.0],
1136 clip: [-f32::INFINITY, -f32::INFINITY, f32::INFINITY, f32::INFINITY],
1137 tex_index: 0,
1138 });
1139 }
1140
1141 for idx in &mesh.indices {
1142 self.indices.push(base_idx + idx);
1143 }
1144
1145 self.instance_data.push(InstanceData {
1146 translation: [0.0, 0.0],
1147 scale: [1.0, 1.0],
1148 rotation: 0.0,
1149 blur_radius: 0.0,
1150 ior_override: 0.0,
1151 });
1152
1153 self.draw_calls.push(DrawCall {
1154 target_id: None,
1155 texture_id: None,
1156 scissor_rect: self.clip_stack.last().copied(),
1157 index_start: (self.indices.len() as u32) - (mesh.indices.len() as u32),
1158 index_count: mesh.indices.len() as u32,
1159 material: cvkg_core::DrawMaterial::Opaque,
1160 instance_start: (self.instance_data.len() - 1) as u32,
1161 });
1162 }
1163
1164 fn set_camera_3d(&mut self, camera: &cvkg_core::Camera3D) {
1165 self.current_scene.proj = camera.projection_matrix();
1166 self.current_scene.view = camera.view_matrix();
1167 }
1168
1169 fn push_transform_3d(&mut self, transform: &cvkg_core::Transform3D) {
1170 let (translation, rotation_quat, scale_glam) =
1173 transform.to_matrix().to_scale_rotation_translation();
1174 let translation = [translation.x, translation.y];
1175 let scale = [scale_glam.x, scale_glam.y];
1176 let rotation = if rotation_quat.length_squared() > 0.0 {
1177 let (axis, angle) = rotation_quat.to_axis_angle();
1178 angle * axis.z.signum() } else {
1180 0.0
1181 };
1182 self.push_transform(translation, scale, rotation);
1183 }
1184
1185 fn pop_transform_3d(&mut self) {
1186 self.pop_transform();
1188 }
1189
1190 fn render_scene_node_3d(
1191 &mut self,
1192 position: [f32; 3],
1193 rotation: [f32; 4],
1194 scale: [f32; 3],
1195 color: [f32; 4],
1196 meshes: &[Mesh],
1197 ) {
1198 let transform = cvkg_core::Transform3D {
1199 position: glam::Vec3::from(position),
1200 rotation: glam::Quat::from_xyzw(rotation[0], rotation[1], rotation[2], rotation[3]),
1201 scale: glam::Vec3::from(scale),
1202 };
1203 if meshes.is_empty() {
1205 let h = 0.5f32;
1207 let cube = Mesh {
1208 vertices: vec![
1209 [-h, -h, -h],
1210 [h, -h, -h],
1211 [h, h, -h],
1212 [-h, h, -h],
1213 [-h, -h, h],
1214 [h, -h, h],
1215 [h, h, h],
1216 [-h, h, h],
1217 ],
1218 normals: vec![
1219 [0.0, 0.0, -1.0],
1220 [0.0, 0.0, -1.0],
1221 [0.0, 0.0, -1.0],
1222 [0.0, 0.0, -1.0],
1223 [0.0, 0.0, 1.0],
1224 [0.0, 0.0, 1.0],
1225 [0.0, 0.0, 1.0],
1226 [0.0, 0.0, 1.0],
1227 [0.0, -1.0, 0.0],
1228 [0.0, -1.0, 0.0],
1229 [0.0, -1.0, 0.0],
1230 [0.0, -1.0, 0.0],
1231 [1.0, 0.0, 0.0],
1232 [1.0, 0.0, 0.0],
1233 [1.0, 0.0, 0.0],
1234 [1.0, 0.0, 0.0],
1235 [0.0, 1.0, 0.0],
1236 [0.0, 1.0, 0.0],
1237 [0.0, 1.0, 0.0],
1238 [0.0, 1.0, 0.0],
1239 [-1.0, 0.0, 0.0],
1240 [-1.0, 0.0, 0.0],
1241 [-1.0, 0.0, 0.0],
1242 [-1.0, 0.0, 0.0],
1243 ],
1244 indices: vec![
1245 0, 1, 2, 0, 2, 3, 5, 4, 7, 5, 7, 6, 4, 0, 3, 4, 3, 7, 1, 5, 6, 1, 6, 2, 3, 2, 6, 3, 6, 7, 4, 5, 1, 4, 1, 0, ],
1252 };
1253 let material = cvkg_core::Material3D::unlit(color);
1254 self.draw_mesh_3d(&cube, &material, &transform);
1255 } else {
1256 let material = cvkg_core::Material3D::unlit(color);
1257 self.draw_mesh_3d(&meshes[0], &material, &transform);
1258 }
1259 }
1260
1261 fn register_shared_element(&mut self, id: &str, rect: Rect) {
1262 self.shared_elements.put(id.to_string(), rect);
1263 }
1264
1265 fn set_z_index(&mut self, z: f32) {
1266 self.current_z = z;
1267 }
1268
1269 fn set_material(&mut self, material: cvkg_core::DrawMaterial) {
1270 self.current_draw_material = material;
1271 }
1272
1273 fn current_material(&self) -> cvkg_core::DrawMaterial {
1274 self.current_draw_material
1275 }
1276
1277 fn get_z_index(&self) -> f32 {
1278 self.current_z
1279 }
1280
1281 fn request_redraw(&mut self) {
1282 self.redraw_requested = true;
1283 }
1284
1285 fn enter_portal(&mut self, z_index: i32) {
1297 self.current_z = z_index as f32;
1301 }
1302
1303 fn exit_portal(&mut self) {
1307 self.current_z = 0.0;
1308 }
1309
1310 fn push_vnode(&mut self, rect: Rect, name: &'static str) {
1311 self.vnode_stack.push((rect, name));
1312 }
1313
1314 fn pop_vnode(&mut self) {
1315 self.vnode_stack.pop();
1316 }
1317
1318 fn register_handler(
1319 &mut self,
1320 event_type: &str,
1321 handler: std::sync::Arc<dyn Fn(cvkg_core::Event) + Send + Sync>,
1322 ) {
1323 self.event_handlers
1324 .entry(event_type.to_string())
1325 .or_insert_with(Vec::new)
1326 .push(handler);
1327 }
1328
1329 fn serialize_svg(&mut self, name: &str) -> Result<String, String> {
1330 let tree = self
1331 .svg_trees
1332 .get(name)
1333 .ok_or_else(|| format!("SVG '{}' not found", name))?;
1334 let config = cvkg_svg_serialize::SerializerConfig::default();
1335 let mut serializer = cvkg_svg_serialize::SvgSerializer::with_config(config);
1336 serializer
1337 .serialize(tree)
1338 .map_err(|e| format!("SVG serialization failed: {}", e))
1339 }
1340
1341 fn apply_svg_filter(
1342 &mut self,
1343 name: &str,
1344 filter_id: &str,
1345 _region: Rect,
1346 ) -> Result<String, String> {
1347 let tree = self
1348 .svg_trees
1349 .get(name)
1350 .ok_or_else(|| format!("SVG '{}' not found", name))?;
1351 let _filter = Self::find_filter(tree, filter_id)
1352 .ok_or_else(|| format!("Filter '{}' not found in SVG '{}'", filter_id, name))?;
1353 let config = cvkg_svg_serialize::SerializerConfig::default();
1354 let mut serializer = cvkg_svg_serialize::SvgSerializer::with_config(config);
1355 serializer
1356 .serialize(tree)
1357 .map_err(|e| format!("SVG filter serialization failed: {}", e))
1358 }
1359}
1360
1361impl SurtrRenderer {
1364 pub fn clear_event_handlers(&mut self) {
1367 self.event_handlers.clear();
1368 }
1369
1370 pub fn get_handlers(
1372 &self,
1373 event_type: &str,
1374 ) -> Option<&Vec<std::sync::Arc<dyn Fn(cvkg_core::Event) + Send + Sync>>> {
1375 self.event_handlers.get(event_type)
1376 }
1377
1378 pub(crate) fn current_transform(&self) -> ([f32; 2], [f32; 2], f32, f32, f32) {
1382 let m = self
1385 .transform_stack
1386 .last()
1387 .copied()
1388 .unwrap_or(glam::Mat3::IDENTITY);
1389 let t = [m.z_axis.x, m.z_axis.y];
1390 let a = m.x_axis.x;
1392 let b = m.x_axis.y;
1393 let c = m.y_axis.x;
1394 let d = m.y_axis.y;
1395 let sx = (a * a + b * b).sqrt();
1396 let sy = (c * c + d * d).sqrt();
1397 let rotation = b.atan2(a);
1398 let skew_x = (a * c + b * d) / (sx * sy); (t, [sx, sy], rotation, skew_x, 0.0)
1401 }
1402
1403 pub fn stroke_path(&mut self, path: &lyon::path::Path, color: [f32; 4], stroke_width: f32) {
1404 let c = self.apply_opacity(color);
1405 let mut tessellator = StrokeTessellator::new();
1406 let mut buffers: VertexBuffers<Vertex, u32> = VertexBuffers::new();
1407 let base_vertex_idx = self.vertices.len() as u32;
1408 let base_index_idx = self.indices.len() as u32;
1409
1410 let (translation, scale, rotation, _, _) = self.current_transform();
1411 let clip_rect = self.clip_stack.last().copied().unwrap_or(cvkg_core::Rect {
1412 x: -10000.0,
1413 y: -10000.0,
1414 width: 20000.0,
1415 height: 20000.0,
1416 });
1417 let clip = [clip_rect.x, clip_rect.y, clip_rect.width, clip_rect.height];
1418
1419 let result = tessellator.tessellate_path(
1420 path,
1421 &StrokeOptions::default().with_line_width(stroke_width),
1422 &mut BuffersBuilder::new(
1423 &mut buffers,
1424 CustomStrokeVertexConstructor { color: c, clip },
1425 ),
1426 );
1427 if let Err(e) = result {
1428 log::warn!("Failed to tessellate stroke path: {:?}", e);
1429 return;
1430 }
1431
1432 self.vertices.extend(buffers.vertices);
1433 for idx in &buffers.indices {
1434 self.indices.push(base_vertex_idx + *idx);
1435 }
1436
1437 let material = self.current_material();
1438 let tid = self.get_texture_id("__mega_heim");
1439
1440 let last_call = self.draw_calls.last();
1441 let needs_new_call = self.draw_calls.is_empty()
1442 || self.current_texture_id != tid
1443 || last_call.unwrap().scissor_rect != self.clip_stack.last().copied()
1444 || last_call.unwrap().material != material;
1445
1446 if needs_new_call {
1447 self.current_texture_id = tid;
1448
1449 self.instance_data.push(InstanceData {
1450 translation,
1451 scale,
1452 rotation,
1453 blur_radius: 0.0,
1454 ior_override: 0.0,
1455 });
1456 self.draw_calls.push(DrawCall {
1457 target_id: None,
1458 texture_id: tid,
1459 scissor_rect: self.clip_stack.last().copied(),
1460 index_start: base_index_idx,
1461 index_count: buffers.indices.len() as u32,
1462 material,
1463 instance_start: (self.instance_data.len() - 1) as u32,
1464 });
1465 } else if let Some(call) = self.draw_calls.last_mut() {
1466 call.index_count += buffers.indices.len() as u32;
1467 }
1468 }
1469}
1470
1471impl cvkg_core::FrameRenderer<wgpu::CommandEncoder> for SurtrRenderer {
1472 fn begin_frame(&mut self) -> wgpu::CommandEncoder {
1473 cvkg_core::begin_render_phase();
1474 let id = self
1475 .current_window
1476 .expect("No target window set for frame. Call set_target_window first.");
1477 self.begin_frame(id)
1478 }
1479
1480 fn render_frame(&mut self) {
1481 if LAYOUT_DIRTY.swap(false, Ordering::AcqRel)
1484 && let Some(window_id) = self.current_window
1485 && let Some(surface_ctx) = self.surfaces.get(&window_id)
1486 {
1487 let w = surface_ctx.config.width as f32;
1488 let h = surface_ctx.config.height as f32;
1489 let border_rect = cvkg_core::Rect {
1490 x: 0.0,
1491 y: 0.0,
1492 width: w,
1493 height: h,
1494 };
1495 self.stroke_rect(border_rect, [1.0, 0.0, 0.0, 1.0], 10.0);
1497 }
1498
1499 let req_v_size = (self.vertices.len() * std::mem::size_of::<Vertex>()) as u64;
1501 let mut cur_v_size = self.vertex_buffer.size();
1502 let max_v_size = (MAX_VERTICES * std::mem::size_of::<Vertex>()) as u64 * 4;
1503
1504 if req_v_size > cur_v_size {
1505 while cur_v_size < req_v_size && cur_v_size < max_v_size {
1506 cur_v_size *= 2;
1507 }
1508 if req_v_size > max_v_size {
1509 log::error!("Exceeded dynamic vertex buffer max capacity! Capping geometry.");
1510 self.vertices
1511 .truncate((max_v_size / std::mem::size_of::<Vertex>() as u64) as usize);
1512 cur_v_size = max_v_size;
1513 }
1514 log::info!("Growing vertex buffer to {} bytes", cur_v_size);
1515 self.vertex_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
1516 label: Some("Vertex Buffer (Grown)"),
1517 size: cur_v_size,
1518 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
1519 mapped_at_creation: false,
1520 });
1521 }
1522
1523 let req_i_size = (self.indices.len() * std::mem::size_of::<u32>()) as u64;
1524 let mut cur_i_size = self.index_buffer.size();
1525 let max_i_size = (MAX_INDICES * std::mem::size_of::<u32>()) as u64 * 4;
1526
1527 if req_i_size > cur_i_size {
1528 while cur_i_size < req_i_size && cur_i_size < max_i_size {
1529 cur_i_size *= 2;
1530 }
1531 if req_i_size > max_i_size {
1532 log::error!("Exceeded dynamic index buffer max capacity! Capping geometry.");
1533 self.indices
1534 .truncate((max_i_size / std::mem::size_of::<u32>() as u64) as usize);
1535 cur_i_size = max_i_size;
1536 }
1537 log::info!("Growing index buffer to {} bytes", cur_i_size);
1538 self.index_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
1539 label: Some("Index Buffer (Grown)"),
1540 size: cur_i_size,
1541 usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
1542 mapped_at_creation: false,
1543 });
1544 }
1545
1546 let mut staging_encoder =
1548 self.device
1549 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1550 label: Some("Surtr Staging Encoder"),
1551 });
1552
1553 let mut has_writes = false;
1554
1555 if !self.vertices.is_empty() {
1556 let v_bytes = bytemuck::cast_slice(&self.vertices);
1557 self.staging_belt
1558 .write_buffer(
1559 &mut staging_encoder,
1560 &self.vertex_buffer,
1561 0,
1562 wgpu::BufferSize::new(v_bytes.len() as u64).unwrap(),
1563 )
1564 .copy_from_slice(v_bytes);
1565 has_writes = true;
1566 }
1567
1568 if !self.indices.is_empty() {
1569 let i_bytes = bytemuck::cast_slice(&self.indices);
1570 self.staging_belt
1571 .write_buffer(
1572 &mut staging_encoder,
1573 &self.index_buffer,
1574 0,
1575 wgpu::BufferSize::new(i_bytes.len() as u64).unwrap(),
1576 )
1577 .copy_from_slice(i_bytes);
1578 has_writes = true;
1579 }
1580
1581 if !self.instance_data.is_empty() {
1582 let inst_bytes = bytemuck::cast_slice(&self.instance_data);
1583 self.staging_belt
1584 .write_buffer(
1585 &mut staging_encoder,
1586 &self.instance_buffer,
1587 0,
1588 wgpu::BufferSize::new(inst_bytes.len() as u64).unwrap(),
1589 )
1590 .copy_from_slice(inst_bytes);
1591 has_writes = true;
1592 }
1593
1594 if has_writes {
1595 self.staging_belt.finish();
1596 self.staging_command_buffers.push(staging_encoder.finish());
1597 }
1598
1599 self.current_scene.time = self.start_time.elapsed().as_secs_f32();
1601 self.queue.write_buffer(
1602 &self.scene_buffer,
1603 0,
1604 bytemuck::bytes_of(&self.current_scene),
1605 );
1606 self.queue.write_buffer(
1607 &self.theme_buffer,
1608 0,
1609 bytemuck::bytes_of(&self.current_theme),
1610 );
1611
1612 self.telemetry.draw_calls = self.draw_calls.len() as u32;
1614 self.telemetry.vertices = self.vertices.len() as u32;
1615 }
1616
1617 fn end_frame(&mut self, encoder: wgpu::CommandEncoder) {
1618 SurtrRenderer::end_frame(self, encoder);
1620 cvkg_core::end_render_phase();
1621 }
1622}