1#![allow(clippy::type_complexity, clippy::unwrap_or_default)]
25
26use cvkg_core::Rect;
37use lru::LruCache;
38use std::num::NonZeroUsize;
39use std::sync::Arc;
40
41#[derive(Clone, Copy)]
42struct SkylineSegment {
43 x: u32,
44 y: u32,
45 w: u32,
46}
47
48struct YggdrasilPacker {
49 width: u32,
50 height: u32,
51 skyline: Vec<SkylineSegment>,
52}
53
54impl YggdrasilPacker {
55 fn new(width: u32, height: u32) -> Self {
56 Self {
57 width,
58 height,
59 skyline: vec![SkylineSegment {
60 x: 0,
61 y: 0,
62 w: width,
63 }],
64 }
65 }
66
67 fn pack(&mut self, w: u32, h: u32) -> Option<(u32, u32)> {
68 if w > self.width || h > self.height {
69 return None;
70 }
71
72 let mut best_idx = None;
73 let mut best_y = u32::MAX;
74 let mut best_w = u32::MAX;
75
76 for i in 0..self.skyline.len() {
77 let seg = &self.skyline[i];
78 if seg.x + w > self.width {
79 continue;
80 }
81
82 let mut y = seg.y;
83 let mut remaining = w;
84 let mut j = i;
85 let mut fits = true;
86
87 while remaining > 0 {
88 if j >= self.skyline.len() {
89 fits = false;
90 break;
91 }
92 let s = &self.skyline[j];
93 y = y.max(s.y);
94 if y + h > self.height {
95 fits = false;
96 break;
97 }
98 if s.w >= remaining {
99 break;
100 }
101 remaining -= s.w;
102 j += 1;
103 }
104
105 if fits && (y < best_y || (y == best_y && seg.w < best_w)) {
106 best_y = y;
107 best_idx = Some(i);
108 best_w = seg.w;
109 }
110 }
111
112 if let Some(idx) = best_idx {
113 let x = self.skyline[idx].x;
114 let y = best_y;
115
116 let new_seg = SkylineSegment { x, y: y + h, w };
117 let mut remaining = w;
118 let insert_idx = idx;
119
120 while remaining > 0 {
121 if self.skyline[insert_idx].w <= remaining {
122 remaining -= self.skyline[insert_idx].w;
123 self.skyline.remove(insert_idx);
124 } else {
125 self.skyline[insert_idx].x += remaining;
126 self.skyline[insert_idx].w -= remaining;
127 remaining = 0;
128 }
129 }
130 self.skyline.insert(insert_idx, new_seg);
131
132 let mut i = 0;
133 while i < self.skyline.len() - 1 {
134 if self.skyline[i].y == self.skyline[i + 1].y {
135 let w = self.skyline[i + 1].w;
136 self.skyline[i].w += w;
137 self.skyline.remove(i + 1);
138 } else {
139 i += 1;
140 }
141 }
142
143 return Some((x, y));
144 }
145 None
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152
153 #[test]
154 fn test_shelf_packer_basic() {
155 let mut packer = YggdrasilPacker::new(100, 100);
156
157 assert_eq!(packer.pack(10, 10), Some((0, 0)));
159
160 assert_eq!(packer.pack(20, 15), Some((10, 0)));
162 }
163
164 #[test]
165 fn test_shelf_packer_wrap() {
166 let mut packer = YggdrasilPacker::new(100, 100);
167 packer.pack(60, 10);
168
169 assert_eq!(packer.pack(50, 20), Some((0, 10)));
171 }
172
173 #[test]
174 fn test_parse_svg_animations() {
175 let svg = r##"
176 <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
177 <g id="spinner">
178 <animateTransform attributeName="transform" type="rotate" from="0" to="360" dur="2s" />
179 </g>
180 <circle id="pulse">
181 <animate attributeName="opacity" from="0.5" to="1.0" dur="0.5s" />
182 </circle>
183 <!-- Edge cases: xlink:href, ms suffix, values list -->
184 <rect>
185 <animate xlink:href="#myRect" attributeName="x" values="10; 20; 30" dur="500ms" />
186 </rect>
187 </svg>
188 "##;
189 let anims = parse_svg_animations(svg.as_bytes());
190 assert_eq!(anims.len(), 3);
191
192 assert_eq!(anims[0].target_id, "spinner");
193 assert_eq!(anims[0].attribute_name, "transform");
194 assert_eq!(anims[0].duration, 2.0);
195 assert_eq!(anims[0].from_val, 0.0);
196 assert_eq!(anims[0].to_val, 360.0);
197
198 assert_eq!(anims[1].target_id, "pulse");
199 assert_eq!(anims[1].attribute_name, "opacity");
200 assert_eq!(anims[1].duration, 0.5);
201 assert_eq!(anims[1].from_val, 0.5);
202 assert_eq!(anims[1].to_val, 1.0);
203
204 assert_eq!(anims[2].target_id, "myRect");
205 assert_eq!(anims[2].attribute_name, "x");
206 assert_eq!(anims[2].duration, 0.5); assert_eq!(anims[2].from_val, 10.0);
208 assert_eq!(anims[2].to_val, 30.0);
209 }
210
211 #[test]
212 fn test_shelf_packer_full() {
213 let mut packer = YggdrasilPacker::new(10, 10);
214 assert_eq!(packer.pack(11, 5), None);
215 assert_eq!(packer.pack(5, 11), None);
216 }
217}
218
219use cvkg_core::{LAYOUT_DIRTY, Mesh, Renderer};
220use std::sync::atomic::Ordering;
221const WGSL_SRC: &str = concat!(
222 include_str!("shaders/common.wgsl"),
223 include_str!("shaders/shapes.wgsl"),
224 include_str!("shaders/bifrost.wgsl"),
225 include_str!("shaders/bloom.wgsl")
226);
227
228#[derive(Clone, Debug)]
230pub struct SvgModel {
231 pub vertices: Vec<Vertex>,
232 pub indices: Vec<u32>,
233 pub view_box: Rect,
234 pub animations: Vec<SvgAnimation>,
235}
236
237#[derive(Clone, Debug)]
238pub struct SvgAnimation {
239 pub target_id: String,
240 pub attribute_name: String,
241 pub from_val: f32,
242 pub to_val: f32,
243 pub duration: f32,
244 pub vertex_range: std::ops::Range<usize>,
245}
246
247pub use accesskit::{
250 ActionHandler, ActionRequest, ActivationHandler, DeactivationHandler, Node, NodeId, Role, Tree,
251 TreeId, TreeUpdate,
252};
253pub use accesskit_winit::Adapter as ShieldWallAdapter;
254
255pub use cvkg_core::{ColorTheme, SceneUniforms};
257
258use lyon::tessellation::{
259 BuffersBuilder, FillOptions, FillTessellator, FillVertex, FillVertexConstructor, StrokeOptions,
260 StrokeTessellator, StrokeVertex, StrokeVertexConstructor, VertexBuffers,
261};
262
263#[repr(C)]
264#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
265pub struct Vertex {
266 pub position: [f32; 3],
267 pub normal: [f32; 3],
268 pub uv: [f32; 2],
269 pub color: [f32; 4],
270 pub mode: u32,
271 pub radius: f32,
272 pub slice: [f32; 4],
273 pub logical: [f32; 2],
274 pub size: [f32; 2],
275 pub screen: [f32; 2],
276 pub clip: [f32; 4], pub translation: [f32; 2],
278 pub scale: [f32; 2],
279 pub rotation: f32,
280 pub tex_index: u32,
281}
282
283#[derive(Debug, Clone)]
286struct DrawCall {
287 pub texture_id: Option<u32>,
288 pub scissor_rect: Option<Rect>,
289 pub index_start: u32,
290 pub index_count: u32,
291 pub material: cvkg_core::DrawMaterial,
294}
295
296#[derive(Debug, Clone, Copy)]
297struct ShadowState {
298 pub radius: f32,
299 pub color: [f32; 4],
300 pub _offset: [f32; 2],
301}
302
303impl Vertex {
304 const ATTRIBUTES: [wgpu::VertexAttribute; 15] = wgpu::vertex_attr_array![
305 0 => Float32x3, 1 => Float32x3, 2 => Float32x2, 3 => Float32x4, 4 => Uint32, 5 => Float32, 6 => Float32x4, 7 => Float32x2, 8 => Float32x2, 9 => Float32x2, 10 => Float32x4, 11 => Float32x2, 12 => Float32x2, 13 => Float32, 14 => Uint32 ];
321
322 fn desc() -> wgpu::VertexBufferLayout<'static> {
323 wgpu::VertexBufferLayout {
324 array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
325 step_mode: wgpu::VertexStepMode::Vertex,
326 attributes: &Self::ATTRIBUTES,
327 }
328 }
329}
330
331#[allow(dead_code)]
333pub struct SurtrRenderer {
334 instance: Arc<wgpu::Instance>,
335 adapter: Arc<wgpu::Adapter>,
336 device: Arc<wgpu::Device>,
337 queue: Arc<wgpu::Queue>,
338
339 surfaces: std::collections::HashMap<winit::window::WindowId, SurfaceContext>,
341 current_window: Option<winit::window::WindowId>,
342 pub headless_context: Option<HeadlessContext>,
343
344 text_engine: cvkg_runic_text::RunicTextEngine,
346 mega_atlas_tex: wgpu::Texture,
347 #[allow(dead_code)]
348 mega_atlas_view: wgpu::TextureView,
349 _mega_atlas_sampler: wgpu::Sampler,
350 mega_atlas_bind_group: wgpu::BindGroup,
351 text_cache: LruCache<u64, (Rect, f32, f32)>,
352 atlas_packer: YggdrasilPacker,
353 image_uv_registry: LruCache<String, Rect>,
354 texture_registry: LruCache<String, u32>,
355 texture_views: Vec<wgpu::TextureView>,
356 dummy_sampler: wgpu::Sampler,
357 svg_cache: LruCache<String, SvgModel>,
358 svg_trees: LruCache<String, usvg::Tree>,
360 filter_device: Option<Arc<wgpu::Device>>,
362 filter_queue: Option<Arc<wgpu::Queue>>,
364 filter_sampler: wgpu::Sampler,
366 filter_engine: Option<cvkg_svg_filters::FilterEngine>,
368 filter_batches: Vec<cvkg_svg_filters::FilterNode>,
370
371 dummy_texture_bind_group: wgpu::BindGroup,
373 dummy_env_bind_group: wgpu::BindGroup,
374 texture_bind_group_layout: wgpu::BindGroupLayout,
375 texture_bind_groups: Vec<wgpu::BindGroup>,
376 shared_elements: LruCache<String, cvkg_core::Rect>,
377
378 vertex_buffer: wgpu::Buffer,
380 index_buffer: wgpu::Buffer,
381 vertices: Vec<Vertex>,
382 indices: Vec<u32>,
383 staging_belt: wgpu::util::StagingBelt,
384 staging_command_buffers: Vec<wgpu::CommandBuffer>,
385 draw_calls: Vec<DrawCall>,
386 current_texture_id: Option<u32>,
387
388 opacity_stack: Vec<f32>,
390 clip_stack: Vec<Rect>,
391 slice_stack: Vec<(f32, f32)>,
392 shadow_stack: Vec<ShadowState>,
393
394 theme_buffer: wgpu::Buffer,
396 scene_buffer: wgpu::Buffer,
397 berserker_bind_group: wgpu::BindGroup,
398 #[allow(dead_code)]
399 berserker_bind_group_layout: wgpu::BindGroupLayout,
400 start_time: std::time::Instant,
401 current_theme: ColorTheme,
402 current_scene: SceneUniforms,
403 current_z: f32,
404
405 pipeline: wgpu::RenderPipeline,
407 background_pipeline: wgpu::RenderPipeline,
408 bloom_extract_pipeline: wgpu::RenderPipeline,
409 blur_h_pipeline: wgpu::RenderPipeline,
410 blur_v_pipeline: wgpu::RenderPipeline,
411 composite_pipeline: wgpu::RenderPipeline,
412 env_bind_group_layout: wgpu::BindGroupLayout,
413
414 pub telemetry: cvkg_core::TelemetryData,
416
417 pub frame_budget: cvkg_core::FrameBudget,
419 pub last_redraw_start: std::time::Instant,
421 pub last_frame_start: std::time::Instant,
423
424 vram_buffers_bytes: u64,
426 vram_textures_bytes: u64,
427
428 _debug_layout: bool,
430
431 transform_stack: Vec<glam::Mat3>,
433 pub redraw_requested: bool,
435
436 skuld_queries: Option<wgpu::QuerySet>,
438 skuld_buffer: Option<wgpu::Buffer>,
439 skuld_read_buffer: Option<wgpu::Buffer>,
440 skuld_period: f32,
441 pub last_gpu_time_ns: u64,
442
443 vnode_stack: Vec<(Rect, &'static str)>,
445
446 event_handlers: std::collections::HashMap<
449 String,
450 Vec<std::sync::Arc<dyn Fn(cvkg_core::Event) + Send + Sync>>,
451 >,
452
453 glass_blur_texture: wgpu::Texture,
458 glass_blur_views: Vec<wgpu::TextureView>,
460 glass_blur_down_bind_groups: Vec<wgpu::BindGroup>,
462 glass_blur_up_bind_groups: Vec<wgpu::BindGroup>,
464 glass_blur_uniform_buffer: wgpu::Buffer,
466 glass_blur_pipeline: wgpu::RenderPipeline,
468 glass_blur_upsample_pipeline: wgpu::RenderPipeline,
470 glass_blur_bind_group_layout: wgpu::BindGroupLayout,
472 glass_output_bind_group_layout: wgpu::BindGroupLayout,
474 current_draw_material: cvkg_core::DrawMaterial,
476 blur_pyramid_mip_count: u32,
478}
479
480struct SurfaceContext {
481 surface: wgpu::Surface<'static>,
482 config: wgpu::SurfaceConfiguration,
483 scene_texture: wgpu::TextureView,
484 scene_bind_group: wgpu::BindGroup,
485 scene_texture_bind_group: wgpu::BindGroup,
486 depth_texture_view: wgpu::TextureView,
487 blur_texture_a: wgpu::TextureView,
488 blur_texture_b: wgpu::TextureView,
489 blur_bind_group_a: wgpu::BindGroup,
490 blur_bind_group_b: wgpu::BindGroup,
491 blur_env_bind_group_a: wgpu::BindGroup,
492 scale_factor: f32,
493 sampler: wgpu::Sampler,
494}
495
496pub struct HeadlessContext {
498 pub scene_texture: wgpu::TextureView,
499 pub scene_bind_group: wgpu::BindGroup,
500 pub scene_texture_bind_group: wgpu::BindGroup,
501 pub depth_texture_view: wgpu::TextureView,
502 pub blur_texture_a: wgpu::TextureView,
503 pub blur_texture_b: wgpu::TextureView,
504 pub blur_bind_group_a: wgpu::BindGroup,
505 pub blur_bind_group_b: wgpu::BindGroup,
506 pub blur_env_bind_group_a: wgpu::BindGroup,
507 pub scale_factor: f32,
508 pub sampler: wgpu::Sampler,
509 pub width: u32,
510 pub height: u32,
511 pub output_texture: wgpu::Texture,
512 pub output_view: wgpu::TextureView,
513}
514
515const MAX_VERTICES: usize = 100_000;
516const MAX_INDICES: usize = 150_000;
517
518impl SurtrRenderer {
519 pub async fn forge(window: Arc<winit::window::Window>) -> Self {
526 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
527 backends: wgpu::Backends::all(),
528 flags: wgpu::InstanceFlags::default(),
529 backend_options: wgpu::BackendOptions::default(),
530 display: None,
531 memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
532 });
533
534 let surface = instance
535 .create_surface(window.clone())
536 .expect("Failed to create surface");
537
538 println!("[GPU] Requesting HighPerformance adapter...");
540
541 let mut adapter = None;
542
543 if let Ok(filter) = std::env::var("WGPU_ADAPTER_NAME") {
545 let adapters = instance.enumerate_adapters(wgpu::Backends::all()).await;
546 println!("[GPU] Available adapters:");
547 for a in &adapters {
548 let info = a.get_info();
549 println!(
550 " - Name: '{}' | Driver: '{}' | Backend: {:?}",
551 info.name, info.driver, info.backend
552 );
553 }
554
555 adapter = adapters.into_iter().find(|a| {
556 let info = a.get_info();
557 let match_found = info.name.to_lowercase().contains(&filter.to_lowercase())
558 || info.driver.to_lowercase().contains(&filter.to_lowercase());
559 if match_found {
560 println!(
561 "[GPU] Manual selection match: {} | Driver: {}",
562 info.name, info.driver
563 );
564 }
565 match_found
566 });
567
568 if adapter.is_some() {
569 println!(
570 "[GPU] Forced adapter selection via WGPU_ADAPTER_NAME='{}'",
571 filter
572 );
573 } else {
574 println!(
575 "[GPU] WGPU_ADAPTER_NAME='{}' provided but no matching adapter found. Falling back...",
576 filter
577 );
578 }
579 }
580
581 if adapter.is_none() {
582 adapter = instance
583 .request_adapter(&wgpu::RequestAdapterOptions {
584 power_preference: wgpu::PowerPreference::HighPerformance,
585 compatible_surface: Some(&surface),
586 force_fallback_adapter: false,
587 })
588 .await
589 .ok();
590 }
591
592 if adapter.is_none() {
593 println!(
594 "[GPU] HighPerformance adapter failed (possible Bumblebee/Optimus), trying LowPower..."
595 );
596 adapter = instance
597 .request_adapter(&wgpu::RequestAdapterOptions {
598 power_preference: wgpu::PowerPreference::LowPower,
599 compatible_surface: Some(&surface),
600 force_fallback_adapter: false,
601 })
602 .await
603 .ok();
604 }
605
606 if adapter.is_none() {
607 println!("[GPU] Hardware adapters failed, trying Software fallback...");
608 adapter = instance
609 .request_adapter(&wgpu::RequestAdapterOptions {
610 power_preference: wgpu::PowerPreference::LowPower,
611 compatible_surface: Some(&surface),
612 force_fallback_adapter: true,
613 })
614 .await
615 .ok();
616 }
617
618 let adapter = adapter.expect("Failed to find a suitable GPU for Surtr");
619 let info = adapter.get_info();
620 println!(
621 "[GPU] Selected adapter: {} ({:?}) on backend: {:?}",
622 info.name, info.device_type, info.backend
623 );
624 println!("[GPU] Driver info: {} - {}", info.driver, info.driver_info);
625 let supports_timestamps = adapter.features().contains(wgpu::Features::TIMESTAMP_QUERY);
626 let mut required_features =
627 wgpu::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING
628 | wgpu::Features::TEXTURE_BINDING_ARRAY;
629 if supports_timestamps {
630 required_features |= wgpu::Features::TIMESTAMP_QUERY;
631 }
632
633 let (device, queue) = adapter
634 .request_device(&wgpu::DeviceDescriptor {
635 label: Some("Surtr Forge"),
636 required_features,
637 required_limits: wgpu::Limits {
638 max_bindings_per_bind_group: 256,
639 max_binding_array_elements_per_shader_stage: 256,
640 ..wgpu::Limits::default()
641 },
642 memory_hints: wgpu::MemoryHints::default(),
643 experimental_features: wgpu::ExperimentalFeatures::disabled(),
644 trace: wgpu::Trace::Off,
645 })
646 .await
647 .expect("Failed to create Surtr device");
648
649 let instance = Arc::new(instance);
650 let adapter = Arc::new(adapter);
651
652 device.on_uncaptured_error(Arc::new(|error| {
653 log::error!(
654 "[GPU] Uncaptured device error (Device Lost or Panic): {:?}",
655 error
656 );
657 }));
659
660 let device = Arc::new(device);
661 let queue = Arc::new(queue);
662
663 let size = window.inner_size();
664 let width = if size.width > 0 { size.width } else { 1280 };
666 let height = if size.height > 0 { size.height } else { 720 };
667 let surface_caps = surface.get_capabilities(&adapter);
668 let surface_format = if surface_caps.formats.is_empty() {
669 log::error!("[GPU] CRITICAL: No compatible surface formats found for this adapter!");
670 log::error!(
671 "[GPU] Adapter: {} | Backend: {:?}",
672 adapter.get_info().name,
673 adapter.get_info().backend
674 );
675 wgpu::TextureFormat::Rgba8UnormSrgb
677 } else {
678 surface_caps
679 .formats
680 .iter()
681 .find(|f| f.is_srgb())
682 .copied()
683 .unwrap_or(surface_caps.formats[0])
684 };
685
686 let present_mode = if surface_caps
688 .present_modes
689 .contains(&wgpu::PresentMode::Mailbox)
690 {
691 wgpu::PresentMode::Mailbox
692 } else {
693 log::warn!("[GPU] Mailbox not supported, falling back to Fifo (V-Sync)");
694 wgpu::PresentMode::Fifo
695 };
696
697 let alpha_mode = if surface_caps
698 .alpha_modes
699 .contains(&wgpu::CompositeAlphaMode::PostMultiplied)
700 {
701 wgpu::CompositeAlphaMode::PostMultiplied
702 } else if surface_caps
703 .alpha_modes
704 .contains(&wgpu::CompositeAlphaMode::PreMultiplied)
705 {
706 wgpu::CompositeAlphaMode::PreMultiplied
707 } else {
708 surface_caps.alpha_modes[0]
709 };
710
711 log::info!(
712 "[GPU] Configuring surface: {}x{} | {:?} | {:?}",
713 width,
714 height,
715 present_mode,
716 alpha_mode
717 );
718
719 let config = wgpu::SurfaceConfiguration {
720 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
721 format: surface_format,
722 width,
723 height,
724 present_mode,
725 alpha_mode,
726 view_formats: vec![],
727 desired_maximum_frame_latency: 2,
728 };
729 surface.configure(&device, &config);
730 log::info!("[GPU] Surface configuration successful.");
731
732 let renderer = Self::forge_internal(
733 instance,
734 adapter,
735 device,
736 queue,
737 Some((window, surface, config)),
738 None,
739 )
740 .await;
741 log::info!("[GPU] Forge internal complete.");
742 renderer
743 }
744
745 async fn forge_internal(
746 instance: Arc<wgpu::Instance>,
747 adapter: Arc<wgpu::Adapter>,
748 device: Arc<wgpu::Device>,
749 queue: Arc<wgpu::Queue>,
750 surface_info: Option<(
751 Arc<winit::window::Window>,
752 wgpu::Surface<'static>,
753 wgpu::SurfaceConfiguration,
754 )>,
755 headless_info: Option<(u32, u32, wgpu::TextureFormat)>,
756 ) -> Self {
757 let format = if let Some((_, _, ref config)) = surface_info {
758 config.format
759 } else if let Some((_, _, f)) = headless_info {
760 f
761 } else {
762 wgpu::TextureFormat::Rgba8UnormSrgb
763 };
764
765 let supports_timestamps = adapter.features().contains(wgpu::Features::TIMESTAMP_QUERY);
766 let skuld_period = queue.get_timestamp_period();
767 let (skuld_queries, skuld_buffer, skuld_read_buffer) = if supports_timestamps {
768 let q = device.create_query_set(&wgpu::QuerySetDescriptor {
769 label: Some("Skuld Timestamp Queries"),
770 count: 2,
771 ty: wgpu::QueryType::Timestamp,
772 });
773 let b = device.create_buffer(&wgpu::BufferDescriptor {
774 label: Some("Skuld Query Buffer"),
775 size: 16,
776 usage: wgpu::BufferUsages::QUERY_RESOLVE | wgpu::BufferUsages::COPY_SRC,
777 mapped_at_creation: false,
778 });
779 let rb = device.create_buffer(&wgpu::BufferDescriptor {
780 label: Some("Skuld Read Buffer"),
781 size: 16,
782 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
783 mapped_at_creation: false,
784 });
785 (Some(q), Some(b), Some(rb))
786 } else {
787 (None, None, None)
788 };
789
790 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
791 label: Some("Muspelheim Main Shader"),
792 source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(WGSL_SRC)),
793 });
794
795 let texture_bind_group_layout =
797 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
798 entries: &[
799 wgpu::BindGroupLayoutEntry {
800 binding: 0,
801 visibility: wgpu::ShaderStages::FRAGMENT,
802 ty: wgpu::BindingType::Texture {
803 multisampled: false,
804 view_dimension: wgpu::TextureViewDimension::D2,
805 sample_type: wgpu::TextureSampleType::Float { filterable: true },
806 },
807 count: std::num::NonZeroU32::new(256),
808 },
809 wgpu::BindGroupLayoutEntry {
810 binding: 1,
811 visibility: wgpu::ShaderStages::FRAGMENT,
812 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
813 count: None,
814 },
815 ],
816 label: Some("Niflheim Texture Bind Group Layout"),
817 });
818
819 let env_bind_group_layout =
822 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
823 entries: &[
824 wgpu::BindGroupLayoutEntry {
825 binding: 0,
826 visibility: wgpu::ShaderStages::FRAGMENT,
827 ty: wgpu::BindingType::Texture {
828 multisampled: false,
829 view_dimension: wgpu::TextureViewDimension::D2,
830 sample_type: wgpu::TextureSampleType::Float { filterable: true },
831 },
832 count: None,
833 },
834 wgpu::BindGroupLayoutEntry {
835 binding: 1,
836 visibility: wgpu::ShaderStages::FRAGMENT,
837 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
838 count: None,
839 },
840 ],
841 label: Some("Surtr Environment Bind Group Layout"),
842 });
843
844 let berserker_bind_group_layout =
845 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
846 entries: &[
847 wgpu::BindGroupLayoutEntry {
848 binding: 0,
849 visibility: wgpu::ShaderStages::FRAGMENT,
850 ty: wgpu::BindingType::Buffer {
851 ty: wgpu::BufferBindingType::Uniform,
852 has_dynamic_offset: false,
853 min_binding_size: None,
854 },
855 count: None,
856 },
857 wgpu::BindGroupLayoutEntry {
858 binding: 1,
859 visibility: wgpu::ShaderStages::FRAGMENT | wgpu::ShaderStages::VERTEX,
860 ty: wgpu::BindingType::Buffer {
861 ty: wgpu::BufferBindingType::Uniform,
862 has_dynamic_offset: false,
863 min_binding_size: None,
864 },
865 count: None,
866 },
867 ],
868 label: Some("Surtr Berserker Bind Group Layout"),
869 });
870
871 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
873 label: Some("Surtr Main Pipeline Layout"),
874 bind_group_layouts: &[
875 Some(&texture_bind_group_layout),
876 Some(&env_bind_group_layout),
877 Some(&berserker_bind_group_layout),
878 ],
879 immediate_size: 0,
880 });
881
882 let post_process_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
884 label: Some("Muspelheim Post Process Layout"),
885 bind_group_layouts: &[
886 Some(&texture_bind_group_layout),
887 Some(&env_bind_group_layout),
888 Some(&berserker_bind_group_layout),
889 ],
890 immediate_size: 0,
891 });
892
893 let composite_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
895 label: Some("Muspelheim Composite Layout"),
896 bind_group_layouts: &[
897 Some(&texture_bind_group_layout),
898 Some(&env_bind_group_layout),
899 Some(&berserker_bind_group_layout),
900 ],
901 immediate_size: 0,
902 });
903
904 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
905 label: Some("Surtr Main Pipeline"),
906 layout: Some(&pipeline_layout),
907 vertex: wgpu::VertexState {
908 module: &shader,
909 entry_point: Some("vs_main"),
910 buffers: &[Vertex::desc()],
911 compilation_options: wgpu::PipelineCompilationOptions::default(),
912 },
913 fragment: Some(wgpu::FragmentState {
914 module: &shader,
915 entry_point: Some("fs_main"),
916 targets: &[Some(wgpu::ColorTargetState {
917 format,
918 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
919 write_mask: wgpu::ColorWrites::ALL,
920 })],
921 compilation_options: wgpu::PipelineCompilationOptions::default(),
922 }),
923 primitive: wgpu::PrimitiveState::default(),
924 depth_stencil: Some(wgpu::DepthStencilState {
925 format: wgpu::TextureFormat::Depth32Float,
926 depth_write_enabled: Some(true),
927 depth_compare: Some(wgpu::CompareFunction::LessEqual),
928 stencil: wgpu::StencilState::default(),
929 bias: wgpu::DepthBiasState::default(),
930 }),
931 multisample: wgpu::MultisampleState::default(),
932 multiview_mask: None,
933 cache: None,
934 });
935
936 let background_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
937 label: Some("Surtr Background Pipeline"),
938 layout: Some(&pipeline_layout),
939 vertex: wgpu::VertexState {
940 module: &shader,
941 entry_point: Some("vs_fullscreen"),
942 buffers: &[],
943 compilation_options: wgpu::PipelineCompilationOptions::default(),
944 },
945 fragment: Some(wgpu::FragmentState {
946 module: &shader,
947 entry_point: Some("fs_background"),
948 targets: &[Some(wgpu::ColorTargetState {
949 format,
950 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
951 write_mask: wgpu::ColorWrites::ALL,
952 })],
953 compilation_options: wgpu::PipelineCompilationOptions::default(),
954 }),
955 primitive: wgpu::PrimitiveState::default(),
956 depth_stencil: Some(wgpu::DepthStencilState {
957 format: wgpu::TextureFormat::Depth32Float,
958 depth_write_enabled: Some(true),
959 depth_compare: Some(wgpu::CompareFunction::Always),
960 stencil: wgpu::StencilState::default(),
961 bias: wgpu::DepthBiasState::default(),
962 }),
963 multisample: wgpu::MultisampleState::default(),
964 multiview_mask: None,
965 cache: None,
966 });
967
968 let bloom_extract_pipeline =
970 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
971 label: Some("Muspelheim Bloom Extract"),
972 layout: Some(&post_process_layout),
973 vertex: wgpu::VertexState {
974 module: &shader,
975 entry_point: Some("vs_fullscreen"),
976 buffers: &[],
977 compilation_options: wgpu::PipelineCompilationOptions::default(),
978 },
979 fragment: Some(wgpu::FragmentState {
980 module: &shader,
981 entry_point: Some("fs_bloom_extract"),
982 targets: &[Some(wgpu::ColorTargetState {
983 format,
984 blend: None,
985 write_mask: wgpu::ColorWrites::ALL,
986 })],
987 compilation_options: wgpu::PipelineCompilationOptions::default(),
988 }),
989 primitive: wgpu::PrimitiveState::default(),
990 depth_stencil: None,
991 multisample: wgpu::MultisampleState::default(),
992 multiview_mask: None,
993 cache: None,
994 });
995
996 let blur_h_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
998 label: Some("Muspelheim Horizontal Blur"),
999 layout: Some(&post_process_layout),
1000 vertex: wgpu::VertexState {
1001 module: &shader,
1002 entry_point: Some("vs_fullscreen"),
1003 buffers: &[],
1004 compilation_options: wgpu::PipelineCompilationOptions::default(),
1005 },
1006 fragment: Some(wgpu::FragmentState {
1007 module: &shader,
1008 entry_point: Some("fs_blur_h"),
1009 targets: &[Some(wgpu::ColorTargetState {
1010 format,
1011 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
1012 write_mask: wgpu::ColorWrites::ALL,
1013 })],
1014 compilation_options: wgpu::PipelineCompilationOptions::default(),
1015 }),
1016 primitive: wgpu::PrimitiveState::default(),
1017 depth_stencil: None,
1018 multisample: wgpu::MultisampleState::default(),
1019 multiview_mask: None,
1020 cache: None,
1021 });
1022
1023 let blur_v_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1024 label: Some("Muspelheim Vertical Blur"),
1025 layout: Some(&post_process_layout),
1026 vertex: wgpu::VertexState {
1027 module: &shader,
1028 entry_point: Some("vs_fullscreen"),
1029 buffers: &[],
1030 compilation_options: wgpu::PipelineCompilationOptions::default(),
1031 },
1032 fragment: Some(wgpu::FragmentState {
1033 module: &shader,
1034 entry_point: Some("fs_blur_v"),
1035 targets: &[Some(wgpu::ColorTargetState {
1036 format,
1037 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
1038 write_mask: wgpu::ColorWrites::ALL,
1039 })],
1040 compilation_options: wgpu::PipelineCompilationOptions::default(),
1041 }),
1042 primitive: wgpu::PrimitiveState::default(),
1043 depth_stencil: None,
1044 multisample: wgpu::MultisampleState::default(),
1045 multiview_mask: None,
1046 cache: None,
1047 });
1048
1049 let composite_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1051 label: Some("Muspelheim Composite"),
1052 layout: Some(&composite_layout),
1053 vertex: wgpu::VertexState {
1054 module: &shader,
1055 entry_point: Some("vs_fullscreen"),
1056 buffers: &[],
1057 compilation_options: wgpu::PipelineCompilationOptions::default(),
1058 },
1059 fragment: Some(wgpu::FragmentState {
1060 module: &shader,
1061 entry_point: Some("fs_composite"),
1062 targets: &[Some(wgpu::ColorTargetState {
1063 format,
1064 blend: Some(wgpu::BlendState {
1066 color: wgpu::BlendComponent {
1067 src_factor: wgpu::BlendFactor::One,
1068 dst_factor: wgpu::BlendFactor::One,
1069 operation: wgpu::BlendOperation::Add,
1070 },
1071 alpha: wgpu::BlendComponent {
1072 src_factor: wgpu::BlendFactor::One,
1073 dst_factor: wgpu::BlendFactor::One,
1074 operation: wgpu::BlendOperation::Add,
1075 },
1076 }),
1077 write_mask: wgpu::ColorWrites::ALL,
1078 })],
1079 compilation_options: wgpu::PipelineCompilationOptions::default(),
1080 }),
1081 primitive: wgpu::PrimitiveState::default(),
1082 depth_stencil: None,
1083 multisample: wgpu::MultisampleState::default(),
1084 multiview_mask: None,
1085 cache: None,
1086 });
1087
1088 let mega_atlas_tex = device.create_texture(&wgpu::TextureDescriptor {
1090 label: Some("Surtr Mega-Atlas"),
1091 size: wgpu::Extent3d {
1092 width: 4096,
1093 height: 4096,
1094 depth_or_array_layers: 1,
1095 },
1096 mip_level_count: 1,
1097 sample_count: 1,
1098 dimension: wgpu::TextureDimension::D2,
1099 format: wgpu::TextureFormat::Rgba8UnormSrgb,
1100 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1101 view_formats: &[],
1102 });
1103 let mega_atlas_view_obj =
1104 mega_atlas_tex.create_view(&wgpu::TextureViewDescriptor::default());
1105 let text_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1106 address_mode_u: wgpu::AddressMode::ClampToEdge,
1107 address_mode_v: wgpu::AddressMode::ClampToEdge,
1108 mag_filter: wgpu::FilterMode::Linear, min_filter: wgpu::FilterMode::Linear,
1110 ..Default::default()
1111 });
1112
1113 let dummy_size = wgpu::Extent3d {
1115 width: 1,
1116 height: 1,
1117 depth_or_array_layers: 1,
1118 };
1119 let dummy_texture = device.create_texture(&wgpu::TextureDescriptor {
1120 label: Some("Niflheim Dummy Texture"),
1121 size: dummy_size,
1122 mip_level_count: 1,
1123 sample_count: 1,
1124 dimension: wgpu::TextureDimension::D2,
1125 format: wgpu::TextureFormat::Rgba8UnormSrgb,
1126 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1127 view_formats: &[],
1128 });
1129 queue.write_texture(
1130 wgpu::TexelCopyTextureInfo {
1131 texture: &dummy_texture,
1132 mip_level: 0,
1133 origin: wgpu::Origin3d::ZERO,
1134 aspect: wgpu::TextureAspect::All,
1135 },
1136 &[0, 0, 0, 255],
1137 wgpu::TexelCopyBufferLayout {
1138 offset: 0,
1139 bytes_per_row: Some(4),
1140 rows_per_image: Some(1),
1141 },
1142 dummy_size,
1143 );
1144
1145 let dummy_view = dummy_texture.create_view(&wgpu::TextureViewDescriptor::default());
1146 let dummy_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1147 address_mode_u: wgpu::AddressMode::ClampToEdge,
1148 address_mode_v: wgpu::AddressMode::ClampToEdge,
1149 address_mode_w: wgpu::AddressMode::ClampToEdge,
1150 mag_filter: wgpu::FilterMode::Linear,
1151 min_filter: wgpu::FilterMode::Nearest,
1152 mipmap_filter: wgpu::MipmapFilterMode::Nearest,
1153 ..Default::default()
1154 });
1155
1156 let mut texture_views_list: Vec<wgpu::TextureView> =
1157 (0..256).map(|_| dummy_view.clone()).collect();
1158 texture_views_list[0] = mega_atlas_view_obj.clone();
1159
1160 let views_refs: Vec<&wgpu::TextureView> = texture_views_list.iter().collect();
1161 let mega_atlas_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1162 layout: &texture_bind_group_layout,
1163 entries: &[
1164 wgpu::BindGroupEntry {
1165 binding: 0,
1166 resource: wgpu::BindingResource::TextureViewArray(&views_refs),
1167 },
1168 wgpu::BindGroupEntry {
1169 binding: 1,
1170 resource: wgpu::BindingResource::Sampler(&text_sampler),
1171 },
1172 ],
1173 label: Some("Mega-Atlas Bind Group"),
1174 });
1175
1176 let dummy_views_refs: Vec<&wgpu::TextureView> = (0..256).map(|_| &dummy_view).collect();
1177 let dummy_texture_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1178 layout: &texture_bind_group_layout,
1179 entries: &[
1180 wgpu::BindGroupEntry {
1181 binding: 0,
1182 resource: wgpu::BindingResource::TextureViewArray(&dummy_views_refs),
1183 },
1184 wgpu::BindGroupEntry {
1185 binding: 1,
1186 resource: wgpu::BindingResource::Sampler(&dummy_sampler),
1187 },
1188 ],
1189 label: Some("Dummy Texture Bind Group"),
1190 });
1191
1192 let dummy_env_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1193 layout: &env_bind_group_layout,
1194 entries: &[
1195 wgpu::BindGroupEntry {
1196 binding: 0,
1197 resource: wgpu::BindingResource::TextureView(&dummy_view),
1198 },
1199 wgpu::BindGroupEntry {
1200 binding: 1,
1201 resource: wgpu::BindingResource::Sampler(&dummy_sampler),
1202 },
1203 ],
1204 label: Some("Dummy Env Bind Group"),
1205 });
1206
1207 let mut texture_registry = std::collections::HashMap::new();
1208 let mut texture_bind_groups = Vec::new();
1209
1210 texture_registry.insert("__mega_atlas".to_string(), 0);
1211 texture_bind_groups.push(mega_atlas_bind_group.clone());
1212
1213 let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
1215 label: Some("Surtr Vertex Anvil"),
1216 size: (MAX_VERTICES * std::mem::size_of::<Vertex>()) as u64,
1217 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
1218 mapped_at_creation: false,
1219 });
1220
1221 let index_buffer = device.create_buffer(&wgpu::BufferDescriptor {
1222 label: Some("Surtr Index Anvil"),
1223 size: (MAX_INDICES * std::mem::size_of::<u32>()) as u64,
1224 usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
1225 mapped_at_creation: false,
1226 });
1227
1228 let current_theme = ColorTheme::default();
1230 use wgpu::util::DeviceExt;
1231 let theme_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
1232 label: Some("Surtr Theme Buffer"),
1233 contents: bytemuck::bytes_of(¤t_theme),
1234 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
1235 });
1236
1237 let (width, height, scale_factor) = if let Some((ref window, _, ref config)) = surface_info
1238 {
1239 (config.width, config.height, window.scale_factor() as f32)
1240 } else if let Some((w, h, _)) = headless_info {
1241 (w, h, 1.0)
1242 } else {
1243 (1280, 720, 1.0)
1244 };
1245
1246 let mut current_scene =
1247 SceneUniforms::new(width as f32 / scale_factor, height as f32 / scale_factor);
1248 current_scene.scale_factor = scale_factor;
1249 let scene_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
1250 label: Some("Surtr Scene Buffer"),
1251 contents: bytemuck::bytes_of(¤t_scene),
1252 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
1253 });
1254
1255 let berserker_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1256 layout: &berserker_bind_group_layout,
1257 entries: &[
1258 wgpu::BindGroupEntry {
1259 binding: 0,
1260 resource: theme_buffer.as_entire_binding(),
1261 },
1262 wgpu::BindGroupEntry {
1263 binding: 1,
1264 resource: scene_buffer.as_entire_binding(),
1265 },
1266 ],
1267 label: Some("Surtr Berserker Bind Group"),
1268 });
1269
1270 let mut surfaces = std::collections::HashMap::new();
1271 let mut current_window = None;
1272 let mut headless_context = None;
1273
1274 if let Some((window, surface, config)) = surface_info {
1275 let window_id = window.id();
1276 let ctx = Self::create_surface_context(
1277 &device,
1278 surface,
1279 config,
1280 &env_bind_group_layout,
1281 &texture_bind_group_layout,
1282 scale_factor,
1283 );
1284 surfaces.insert(window_id, ctx);
1285 current_window = Some(window_id);
1286 } else if let Some((w, h, f)) = headless_info {
1287 headless_context = Some(Self::create_headless_context(
1288 &device,
1289 w,
1290 h,
1291 f,
1292 &env_bind_group_layout,
1293 &texture_bind_group_layout,
1294 ));
1295 }
1296
1297 let staging_belt = wgpu::util::StagingBelt::new((*device).clone(), 1024 * 1024);
1298
1299 let glass_blur_bind_group_layout = env_bind_group_layout.clone();
1301 let glass_output_bind_group_layout = env_bind_group_layout.clone();
1302 let glass_blur_pipeline = pipeline.clone();
1303 let glass_blur_upsample_pipeline = pipeline.clone();
1304
1305 let glass_blur_texture = device.create_texture(&wgpu::TextureDescriptor {
1307 label: Some("Glass Blur Pyramid"),
1308 size: wgpu::Extent3d {
1309 width: width.max(1),
1310 height: height.max(1),
1311 depth_or_array_layers: 1,
1312 },
1313 mip_level_count: 1,
1314 sample_count: 1,
1315 dimension: wgpu::TextureDimension::D2,
1316 format: wgpu::TextureFormat::Rgba16Float,
1317 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT,
1318 view_formats: &[],
1319 });
1320 let glass_blur_uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
1321 label: Some("Glass Blur Uniform"),
1322 size: 64,
1323 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
1324 mapped_at_creation: false,
1325 });
1326
1327 Self {
1328 instance,
1329 adapter,
1330 device: device.clone(),
1331 queue: queue.clone(),
1332 surfaces,
1333 current_window,
1334 headless_context,
1335 pipeline,
1336 bloom_extract_pipeline,
1337 blur_h_pipeline,
1338 blur_v_pipeline,
1339 composite_pipeline,
1340 env_bind_group_layout,
1341 text_engine: cvkg_runic_text::RunicTextEngine::default(),
1342 mega_atlas_tex,
1343 mega_atlas_view: mega_atlas_view_obj,
1344 _mega_atlas_sampler: text_sampler,
1345 mega_atlas_bind_group,
1346 text_cache: LruCache::new(NonZeroUsize::new(2048).unwrap()),
1347 atlas_packer: YggdrasilPacker::new(4096, 4096),
1348 image_uv_registry: LruCache::new(NonZeroUsize::new(256).unwrap()),
1349 texture_registry: LruCache::new(NonZeroUsize::new(255).unwrap()),
1350 texture_views: texture_views_list,
1351 dummy_sampler,
1352 svg_cache: LruCache::new(NonZeroUsize::new(128).unwrap()),
1353 svg_trees: LruCache::new(NonZeroUsize::new(128).unwrap()),
1354 filter_device: Some(device.clone()),
1355 filter_queue: Some(queue.clone()),
1356 filter_sampler: device.create_sampler(&wgpu::SamplerDescriptor {
1357 label: Some("SVG Filter Sampler"),
1358 address_mode_u: wgpu::AddressMode::ClampToEdge,
1359 address_mode_v: wgpu::AddressMode::ClampToEdge,
1360 address_mode_w: wgpu::AddressMode::ClampToEdge,
1361 mag_filter: wgpu::FilterMode::Linear,
1362 min_filter: wgpu::FilterMode::Linear,
1363 mipmap_filter: wgpu::MipmapFilterMode::Linear,
1364 ..Default::default()
1365 }),
1366 filter_engine: None,
1367 filter_batches: Vec::new(),
1368 dummy_texture_bind_group,
1369 dummy_env_bind_group,
1370 texture_bind_group_layout,
1371 texture_bind_groups,
1372 shared_elements: LruCache::new(NonZeroUsize::new(1024).unwrap()),
1373 vertex_buffer,
1374 index_buffer,
1375 vertices: Vec::with_capacity(MAX_VERTICES),
1376 indices: Vec::with_capacity(MAX_INDICES),
1377 draw_calls: Vec::new(),
1378 current_texture_id: None,
1379 opacity_stack: vec![1.0],
1380 clip_stack: Vec::new(),
1381 slice_stack: Vec::new(),
1382 shadow_stack: Vec::new(),
1383 theme_buffer,
1384 scene_buffer,
1385 berserker_bind_group,
1386 berserker_bind_group_layout,
1387 start_time: std::time::Instant::now(),
1388 current_theme,
1389 current_scene,
1390 background_pipeline,
1391 current_z: 0.0,
1392 telemetry: cvkg_core::TelemetryData::default(),
1393 last_frame_start: std::time::Instant::now(),
1394 last_redraw_start: std::time::Instant::now(),
1395 frame_budget: cvkg_core::FrameBudget::default(),
1396 vram_buffers_bytes: 0,
1397 vram_textures_bytes: 0,
1398 _debug_layout: false,
1399 transform_stack: Vec::new(),
1400 redraw_requested: false,
1401 skuld_queries,
1402 skuld_buffer,
1403 skuld_read_buffer,
1404 skuld_period,
1405 last_gpu_time_ns: 0,
1406 vnode_stack: Vec::new(),
1407 event_handlers: std::collections::HashMap::new(),
1408 staging_belt,
1409 staging_command_buffers: Vec::new(),
1410 glass_blur_texture,
1412 glass_blur_views: Vec::new(),
1413 glass_blur_down_bind_groups: Vec::new(),
1414 glass_blur_up_bind_groups: Vec::new(),
1415 glass_blur_uniform_buffer,
1416 glass_blur_pipeline,
1417 glass_blur_upsample_pipeline,
1418 glass_blur_bind_group_layout,
1419 glass_output_bind_group_layout,
1420 current_draw_material: cvkg_core::DrawMaterial::Opaque,
1421 blur_pyramid_mip_count: 1,
1422 }
1423 }
1424
1425 fn rebuild_texture_array_bind_group(&mut self) {
1426 let views: Vec<&wgpu::TextureView> = self.texture_views.iter().collect();
1427 self.mega_atlas_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1428 layout: &self.texture_bind_group_layout,
1429 entries: &[
1430 wgpu::BindGroupEntry {
1431 binding: 0,
1432 resource: wgpu::BindingResource::TextureViewArray(&views),
1433 },
1434 wgpu::BindGroupEntry {
1435 binding: 1,
1436 resource: wgpu::BindingResource::Sampler(&self.dummy_sampler),
1437 },
1438 ],
1439 label: Some("Surtr Texture Array Bind Group"),
1440 });
1441 }
1442
1443 fn update_vram_telemetry(&mut self) {
1445 let mut buffer_bytes = 0;
1447 buffer_bytes += (MAX_VERTICES * std::mem::size_of::<Vertex>()) as u64;
1448 buffer_bytes += (MAX_INDICES * std::mem::size_of::<u32>()) as u64;
1449 buffer_bytes += std::mem::size_of::<cvkg_core::ColorTheme>() as u64;
1450 buffer_bytes += std::mem::size_of::<cvkg_core::SceneUniforms>() as u64;
1451 self.vram_buffers_bytes = buffer_bytes;
1452
1453 let mut texture_bytes = 0;
1455 texture_bytes += 4096 * 4096 * 4; texture_bytes += 4; for _ in &self.texture_views {
1460 texture_bytes += 1024 * 1024 * 4;
1462 }
1463
1464 for ctx in self.surfaces.values() {
1465 let bpp = 4;
1466 let surface_bytes = (ctx.config.width * ctx.config.height * bpp) as u64;
1467 texture_bytes += surface_bytes * 3; texture_bytes += (ctx.config.width * ctx.config.height * 4) as u64; }
1470
1471 self.vram_textures_bytes = texture_bytes;
1472
1473 self.telemetry.vram_buffers_mb = buffer_bytes as f32 / 1_048_576.0;
1474 self.telemetry.vram_textures_mb = texture_bytes as f32 / 1_048_576.0;
1475 self.telemetry.vram_pipelines_mb = 0.0;
1476 self.telemetry.vram_usage_mb =
1477 self.telemetry.vram_buffers_mb + self.telemetry.vram_textures_mb;
1478 }
1479
1480 pub fn get_telemetry(&self) -> cvkg_core::TelemetryData {
1482 self.telemetry.clone()
1483 }
1484
1485 pub fn resize(
1487 &mut self,
1488 window_id: winit::window::WindowId,
1489 width: u32,
1490 height: u32,
1491 scale_factor: f32,
1492 ) {
1493 if width > 0
1494 && height > 0
1495 && let Some(ctx) = self.surfaces.get_mut(&window_id)
1496 {
1497 ctx.config.width = width;
1498 ctx.config.height = height;
1499 ctx.scale_factor = scale_factor;
1500 ctx.surface.configure(&self.device, &ctx.config);
1501
1502 let texture_desc = wgpu::TextureDescriptor {
1504 label: Some("Surtr Scene Texture"),
1505 size: wgpu::Extent3d {
1506 width,
1507 height,
1508 depth_or_array_layers: 1,
1509 },
1510 mip_level_count: 1,
1511 sample_count: 1,
1512 dimension: wgpu::TextureDimension::D2,
1513 format: ctx.config.format,
1514 usage: wgpu::TextureUsages::RENDER_ATTACHMENT
1515 | wgpu::TextureUsages::TEXTURE_BINDING,
1516 view_formats: &[],
1517 };
1518
1519 let scene_tex = self.device.create_texture(&texture_desc);
1520 ctx.scene_texture = scene_tex.create_view(&wgpu::TextureViewDescriptor::default());
1521
1522 let blur_tex_a = self.device.create_texture(&texture_desc);
1523 ctx.blur_texture_a = blur_tex_a.create_view(&wgpu::TextureViewDescriptor::default());
1524
1525 let blur_tex_b = self.device.create_texture(&texture_desc);
1526 ctx.blur_texture_b = blur_tex_b.create_view(&wgpu::TextureViewDescriptor::default());
1527
1528 ctx.scene_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1530 layout: &self.env_bind_group_layout,
1531 entries: &[
1532 wgpu::BindGroupEntry {
1533 binding: 0,
1534 resource: wgpu::BindingResource::TextureView(&ctx.scene_texture),
1535 },
1536 wgpu::BindGroupEntry {
1537 binding: 1,
1538 resource: wgpu::BindingResource::Sampler(&ctx.sampler),
1539 },
1540 ],
1541 label: Some("Scene Bind Group Resize"),
1542 });
1543
1544 let scene_views: Vec<&wgpu::TextureView> =
1545 (0..256).map(|_| &ctx.scene_texture).collect();
1546 ctx.scene_texture_bind_group =
1547 self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1548 layout: &self.texture_bind_group_layout,
1549 entries: &[
1550 wgpu::BindGroupEntry {
1551 binding: 0,
1552 resource: wgpu::BindingResource::TextureViewArray(&scene_views),
1553 },
1554 wgpu::BindGroupEntry {
1555 binding: 1,
1556 resource: wgpu::BindingResource::Sampler(&ctx.sampler),
1557 },
1558 ],
1559 label: Some("Scene Texture Bind Group Resize"),
1560 });
1561
1562 let blur_views_a: Vec<&wgpu::TextureView> =
1563 (0..256).map(|_| &ctx.blur_texture_a).collect();
1564 ctx.blur_bind_group_a = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1565 layout: &self.texture_bind_group_layout,
1566 entries: &[
1567 wgpu::BindGroupEntry {
1568 binding: 0,
1569 resource: wgpu::BindingResource::TextureViewArray(&blur_views_a),
1570 },
1571 wgpu::BindGroupEntry {
1572 binding: 1,
1573 resource: wgpu::BindingResource::Sampler(&ctx.sampler),
1574 },
1575 ],
1576 label: Some("Blur Bind Group A Resize"),
1577 });
1578
1579 let blur_views_b: Vec<&wgpu::TextureView> =
1580 (0..256).map(|_| &ctx.blur_texture_b).collect();
1581 ctx.blur_bind_group_b = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1582 layout: &self.texture_bind_group_layout,
1583 entries: &[
1584 wgpu::BindGroupEntry {
1585 binding: 0,
1586 resource: wgpu::BindingResource::TextureViewArray(&blur_views_b),
1587 },
1588 wgpu::BindGroupEntry {
1589 binding: 1,
1590 resource: wgpu::BindingResource::Sampler(&ctx.sampler),
1591 },
1592 ],
1593 label: Some("Blur Bind Group B Resize"),
1594 });
1595
1596 ctx.blur_env_bind_group_a = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1597 layout: &self.env_bind_group_layout,
1598 entries: &[
1599 wgpu::BindGroupEntry {
1600 binding: 0,
1601 resource: wgpu::BindingResource::TextureView(&ctx.blur_texture_a),
1602 },
1603 wgpu::BindGroupEntry {
1604 binding: 1,
1605 resource: wgpu::BindingResource::Sampler(&ctx.sampler),
1606 },
1607 ],
1608 label: Some("Blur Env Bind Group A Resize"),
1609 });
1610
1611 let depth_texture = self.device.create_texture(&wgpu::TextureDescriptor {
1612 label: Some("Surtr Depth Texture"),
1613 size: wgpu::Extent3d {
1614 width,
1615 height,
1616 depth_or_array_layers: 1,
1617 },
1618 mip_level_count: 1,
1619 sample_count: 1,
1620 dimension: wgpu::TextureDimension::D2,
1621 format: wgpu::TextureFormat::Depth32Float,
1622 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1623 view_formats: &[],
1624 });
1625 ctx.depth_texture_view =
1626 depth_texture.create_view(&wgpu::TextureViewDescriptor::default());
1627 }
1628 }
1629
1630 pub fn begin_frame_headless(&mut self) -> wgpu::CommandEncoder {
1632 self.current_window = None;
1633 self.vertices.clear();
1634 self.indices.clear();
1635 self.draw_calls.clear();
1636 self.filter_batches.clear();
1637 self.shared_elements.clear();
1638 self.current_texture_id = None;
1639 self.opacity_stack = vec![1.0];
1640 self.clip_stack.clear();
1641 self.slice_stack.clear();
1642 self.transform_stack.clear();
1643 self.current_z = 0.0;
1644 self.vnode_stack.clear();
1645 self.event_handlers.clear();
1646
1647 self.last_frame_start = std::time::Instant::now();
1648 self.telemetry.draw_calls = 0;
1649 self.telemetry.vertices = 0;
1650
1651 let ctx = self
1652 .headless_context
1653 .as_ref()
1654 .expect("Headless context not initialized");
1655 let time = self.start_time.elapsed().as_secs_f32();
1656 let logical_w = ctx.width as f32 / ctx.scale_factor;
1657 let logical_h = ctx.height as f32 / ctx.scale_factor;
1658 let dt = time - self.current_scene.time;
1659 self.current_scene.time = time;
1660 self.current_scene.delta_time = dt;
1661 self.current_scene.resolution = [logical_w, logical_h];
1662 self.current_scene.scale_factor = ctx.scale_factor;
1663 self.current_scene.proj =
1664 glam::Mat4::orthographic_lh(0.0, logical_w, logical_h, 0.0, -1000.0, 1000.0);
1665
1666 self.queue.write_buffer(
1667 &self.scene_buffer,
1668 0,
1669 bytemuck::bytes_of(&self.current_scene),
1670 );
1671
1672 self.device
1673 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1674 label: Some("Surtr Headless Command Encoder"),
1675 })
1676 }
1677
1678 pub fn begin_frame(&mut self, window_id: winit::window::WindowId) -> wgpu::CommandEncoder {
1680 if let Some(rb) = &self.skuld_read_buffer {
1682 let slice = rb.slice(..);
1683 let (tx, rx) = std::sync::mpsc::channel();
1684 slice.map_async(wgpu::MapMode::Read, move |r| tx.send(r).unwrap());
1685
1686 self.device
1688 .poll(wgpu::PollType::Wait {
1689 submission_index: None,
1690 timeout: None,
1691 })
1692 .unwrap();
1693
1694 if rx.recv().is_ok() {
1695 let data = slice.get_mapped_range();
1696 let timestamps: [u64; 2] = bytemuck::cast_slice(&data).try_into().unwrap_or([0, 0]);
1697 drop(data);
1698 rb.unmap();
1699
1700 if timestamps[1] > timestamps[0] {
1701 let diff_ticks = timestamps[1] - timestamps[0];
1702 self.last_gpu_time_ns = (diff_ticks as f64 * self.skuld_period as f64) as u64;
1703 }
1705 }
1706 }
1707
1708 self.staging_belt.recall();
1709 self.current_window = Some(window_id);
1710 self.vertices.clear();
1711 self.indices.clear();
1712 self.draw_calls.clear();
1713 self.shared_elements.clear();
1714 self.current_texture_id = None;
1715 self.opacity_stack = vec![1.0];
1716 self.clip_stack.clear();
1717 self.slice_stack.clear();
1718 self.transform_stack.clear();
1719 self.current_z = 0.0;
1720 self.vnode_stack.clear();
1721 self.event_handlers.clear();
1722
1723 self.last_frame_start = std::time::Instant::now();
1724 self.telemetry.draw_calls = 0;
1725 self.telemetry.vertices = 0;
1726
1727 let ctx = self
1728 .surfaces
1729 .get(&window_id)
1730 .expect("Window not registered");
1731 let time = self.start_time.elapsed().as_secs_f32();
1732 let logical_w = ctx.config.width as f32 / ctx.scale_factor;
1733 let logical_h = ctx.config.height as f32 / ctx.scale_factor;
1734 let dt = time - self.current_scene.time;
1735 self.current_scene.time = time;
1736 self.current_scene.delta_time = dt;
1737 self.current_scene.resolution = [logical_w, logical_h];
1738 self.current_scene.scale_factor = ctx.scale_factor;
1739 self.current_scene.proj =
1740 glam::Mat4::orthographic_lh(0.0, logical_w, logical_h, 0.0, -1000.0, 1000.0);
1741
1742 self.queue.write_buffer(
1743 &self.scene_buffer,
1744 0,
1745 bytemuck::bytes_of(&self.current_scene),
1746 );
1747
1748 self.device
1749 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1750 label: Some("Surtr Command Encoder"),
1751 })
1752 }
1753
1754 pub fn register_window(&mut self, window: Arc<winit::window::Window>) {
1756 let size = window.inner_size();
1757 let surface = self
1758 .instance
1759 .create_surface(window.clone())
1760 .expect("Failed to create surface");
1761 let caps = surface.get_capabilities(&self.adapter);
1762 let format = caps.formats[0];
1763 let config = wgpu::SurfaceConfiguration {
1764 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1765 format,
1766 width: size.width,
1767 height: size.height,
1768 present_mode: wgpu::PresentMode::Mailbox,
1769 alpha_mode: caps.alpha_modes[0],
1770 view_formats: vec![],
1771 desired_maximum_frame_latency: 2,
1772 };
1773 surface.configure(&self.device, &config);
1774
1775 let ctx = Self::create_surface_context(
1776 &self.device,
1777 surface,
1778 config,
1779 &self.env_bind_group_layout,
1780 &self.texture_bind_group_layout,
1781 window.scale_factor() as f32,
1782 );
1783
1784 self.surfaces.insert(window.id(), ctx);
1785 }
1786
1787 fn create_headless_context(
1788 device: &wgpu::Device,
1789 width: u32,
1790 height: u32,
1791 format: wgpu::TextureFormat,
1792 env_bind_group_layout: &wgpu::BindGroupLayout,
1793 texture_bind_group_layout: &wgpu::BindGroupLayout,
1794 ) -> HeadlessContext {
1795 let texture_desc = wgpu::TextureDescriptor {
1796 label: Some("Surtr Headless Scene Texture"),
1797 size: wgpu::Extent3d {
1798 width,
1799 height,
1800 depth_or_array_layers: 1,
1801 },
1802 mip_level_count: 1,
1803 sample_count: 1,
1804 dimension: wgpu::TextureDimension::D2,
1805 format,
1806 usage: wgpu::TextureUsages::RENDER_ATTACHMENT
1807 | wgpu::TextureUsages::TEXTURE_BINDING
1808 | wgpu::TextureUsages::COPY_SRC,
1809 view_formats: &[],
1810 };
1811
1812 let scene_tex = device.create_texture(&texture_desc);
1813 let scene_texture = scene_tex.create_view(&wgpu::TextureViewDescriptor::default());
1814
1815 let blur_width = (width / 2).max(1);
1816 let blur_height = (height / 2).max(1);
1817 let blur_texture_desc = wgpu::TextureDescriptor {
1818 label: Some("Surtr Blur Texture"),
1819 size: wgpu::Extent3d {
1820 width: blur_width,
1821 height: blur_height,
1822 depth_or_array_layers: 1,
1823 },
1824 mip_level_count: 1,
1825 sample_count: 1,
1826 dimension: wgpu::TextureDimension::D2,
1827 format,
1828 usage: wgpu::TextureUsages::RENDER_ATTACHMENT
1829 | wgpu::TextureUsages::TEXTURE_BINDING
1830 | wgpu::TextureUsages::COPY_SRC,
1831 view_formats: &[],
1832 };
1833
1834 let blur_tex_a = device.create_texture(&blur_texture_desc);
1835 let blur_texture_a = blur_tex_a.create_view(&wgpu::TextureViewDescriptor::default());
1836
1837 let blur_tex_b = device.create_texture(&blur_texture_desc);
1838 let blur_texture_b = blur_tex_b.create_view(&wgpu::TextureViewDescriptor::default());
1839
1840 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1841 address_mode_u: wgpu::AddressMode::ClampToEdge,
1842 address_mode_v: wgpu::AddressMode::ClampToEdge,
1843 mag_filter: wgpu::FilterMode::Linear,
1844 min_filter: wgpu::FilterMode::Linear,
1845 ..Default::default()
1846 });
1847
1848 let scene_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1849 layout: env_bind_group_layout,
1850 entries: &[
1851 wgpu::BindGroupEntry {
1852 binding: 0,
1853 resource: wgpu::BindingResource::TextureView(&scene_texture),
1854 },
1855 wgpu::BindGroupEntry {
1856 binding: 1,
1857 resource: wgpu::BindingResource::Sampler(&sampler),
1858 },
1859 ],
1860 label: Some("Headless Scene Bind Group"),
1861 });
1862
1863 let scene_views: Vec<&wgpu::TextureView> = (0..256).map(|_| &scene_texture).collect();
1864 let scene_texture_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1865 layout: texture_bind_group_layout,
1866 entries: &[
1867 wgpu::BindGroupEntry {
1868 binding: 0,
1869 resource: wgpu::BindingResource::TextureViewArray(&scene_views),
1870 },
1871 wgpu::BindGroupEntry {
1872 binding: 1,
1873 resource: wgpu::BindingResource::Sampler(&sampler),
1874 },
1875 ],
1876 label: Some("Headless Scene Texture Bind Group"),
1877 });
1878
1879 let blur_views_a: Vec<&wgpu::TextureView> = (0..256).map(|_| &blur_texture_a).collect();
1880 let blur_bind_group_a = device.create_bind_group(&wgpu::BindGroupDescriptor {
1881 layout: texture_bind_group_layout,
1882 entries: &[
1883 wgpu::BindGroupEntry {
1884 binding: 0,
1885 resource: wgpu::BindingResource::TextureViewArray(&blur_views_a),
1886 },
1887 wgpu::BindGroupEntry {
1888 binding: 1,
1889 resource: wgpu::BindingResource::Sampler(&sampler),
1890 },
1891 ],
1892 label: Some("Headless Blur Bind Group A"),
1893 });
1894
1895 let blur_views_b: Vec<&wgpu::TextureView> = (0..256).map(|_| &blur_texture_b).collect();
1896 let blur_bind_group_b = device.create_bind_group(&wgpu::BindGroupDescriptor {
1897 layout: texture_bind_group_layout,
1898 entries: &[
1899 wgpu::BindGroupEntry {
1900 binding: 0,
1901 resource: wgpu::BindingResource::TextureViewArray(&blur_views_b),
1902 },
1903 wgpu::BindGroupEntry {
1904 binding: 1,
1905 resource: wgpu::BindingResource::Sampler(&sampler),
1906 },
1907 ],
1908 label: Some("Headless Blur Bind Group B"),
1909 });
1910
1911 let blur_env_bind_group_a = device.create_bind_group(&wgpu::BindGroupDescriptor {
1912 layout: env_bind_group_layout,
1913 entries: &[
1914 wgpu::BindGroupEntry {
1915 binding: 0,
1916 resource: wgpu::BindingResource::TextureView(&blur_texture_a),
1917 },
1918 wgpu::BindGroupEntry {
1919 binding: 1,
1920 resource: wgpu::BindingResource::Sampler(&sampler),
1921 },
1922 ],
1923 label: Some("Headless Blur Env Bind Group A"),
1924 });
1925
1926 let depth_texture = device.create_texture(&wgpu::TextureDescriptor {
1927 label: Some("Headless Depth Texture"),
1928 size: wgpu::Extent3d {
1929 width,
1930 height,
1931 depth_or_array_layers: 1,
1932 },
1933 mip_level_count: 1,
1934 sample_count: 1,
1935 dimension: wgpu::TextureDimension::D2,
1936 format: wgpu::TextureFormat::Depth32Float,
1937 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1938 view_formats: &[],
1939 });
1940 let depth_texture_view = depth_texture.create_view(&wgpu::TextureViewDescriptor::default());
1941
1942 let output_texture = device.create_texture(&wgpu::TextureDescriptor {
1943 label: Some("Headless Output Texture"),
1944 size: wgpu::Extent3d {
1945 width,
1946 height,
1947 depth_or_array_layers: 1,
1948 },
1949 mip_level_count: 1,
1950 sample_count: 1,
1951 dimension: wgpu::TextureDimension::D2,
1952 format,
1953 usage: wgpu::TextureUsages::RENDER_ATTACHMENT
1954 | wgpu::TextureUsages::COPY_DST
1955 | wgpu::TextureUsages::COPY_SRC,
1956 view_formats: &[],
1957 });
1958 let output_view = output_texture.create_view(&wgpu::TextureViewDescriptor::default());
1959
1960 HeadlessContext {
1961 scene_texture,
1962 scene_bind_group,
1963 scene_texture_bind_group,
1964 depth_texture_view,
1965 blur_texture_a,
1966 blur_texture_b,
1967 blur_bind_group_a,
1968 blur_bind_group_b,
1969 blur_env_bind_group_a,
1970 scale_factor: 1.0,
1971 sampler,
1972 width,
1973 height,
1974 output_texture,
1975 output_view,
1976 }
1977 }
1978
1979 fn create_surface_context(
1980 device: &wgpu::Device,
1981 surface: wgpu::Surface<'static>,
1982 config: wgpu::SurfaceConfiguration,
1983 env_bind_group_layout: &wgpu::BindGroupLayout,
1984 texture_bind_group_layout: &wgpu::BindGroupLayout,
1985 scale_factor: f32,
1986 ) -> SurfaceContext {
1987 let width = config.width;
1988 let height = config.height;
1989
1990 let texture_desc = wgpu::TextureDescriptor {
1991 label: Some("Surtr Scene Texture"),
1992 size: wgpu::Extent3d {
1993 width,
1994 height,
1995 depth_or_array_layers: 1,
1996 },
1997 mip_level_count: 1,
1998 sample_count: 1,
1999 dimension: wgpu::TextureDimension::D2,
2000 format: config.format,
2001 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
2002 view_formats: &[],
2003 };
2004
2005 let scene_tex = device.create_texture(&texture_desc);
2006 let scene_texture = scene_tex.create_view(&wgpu::TextureViewDescriptor::default());
2007
2008 let blur_width = (width / 2).max(1);
2009 let blur_height = (height / 2).max(1);
2010 let blur_texture_desc = wgpu::TextureDescriptor {
2011 label: Some("Surtr Blur Texture"),
2012 size: wgpu::Extent3d {
2013 width: blur_width,
2014 height: blur_height,
2015 depth_or_array_layers: 1,
2016 },
2017 mip_level_count: 1,
2018 sample_count: 1,
2019 dimension: wgpu::TextureDimension::D2,
2020 format: config.format,
2021 usage: wgpu::TextureUsages::RENDER_ATTACHMENT
2022 | wgpu::TextureUsages::TEXTURE_BINDING
2023 | wgpu::TextureUsages::COPY_SRC,
2024 view_formats: &[],
2025 };
2026
2027 let blur_tex_a = device.create_texture(&blur_texture_desc);
2028 let blur_texture_a = blur_tex_a.create_view(&wgpu::TextureViewDescriptor::default());
2029
2030 let blur_tex_b = device.create_texture(&blur_texture_desc);
2031 let blur_texture_b = blur_tex_b.create_view(&wgpu::TextureViewDescriptor::default());
2032
2033 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
2034 address_mode_u: wgpu::AddressMode::ClampToEdge,
2035 address_mode_v: wgpu::AddressMode::ClampToEdge,
2036 mag_filter: wgpu::FilterMode::Linear,
2037 min_filter: wgpu::FilterMode::Linear,
2038 ..Default::default()
2039 });
2040
2041 let scene_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
2042 layout: env_bind_group_layout,
2043 entries: &[
2044 wgpu::BindGroupEntry {
2045 binding: 0,
2046 resource: wgpu::BindingResource::TextureView(&scene_texture),
2047 },
2048 wgpu::BindGroupEntry {
2049 binding: 1,
2050 resource: wgpu::BindingResource::Sampler(&sampler),
2051 },
2052 ],
2053 label: Some("Scene Bind Group"),
2054 });
2055
2056 let scene_views: Vec<&wgpu::TextureView> = (0..256).map(|_| &scene_texture).collect();
2057 let scene_texture_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
2058 layout: texture_bind_group_layout,
2059 entries: &[
2060 wgpu::BindGroupEntry {
2061 binding: 0,
2062 resource: wgpu::BindingResource::TextureViewArray(&scene_views),
2063 },
2064 wgpu::BindGroupEntry {
2065 binding: 1,
2066 resource: wgpu::BindingResource::Sampler(&sampler),
2067 },
2068 ],
2069 label: Some("Scene Texture Bind Group"),
2070 });
2071
2072 let blur_views_a: Vec<&wgpu::TextureView> = (0..256).map(|_| &blur_texture_a).collect();
2073 let blur_bind_group_a = device.create_bind_group(&wgpu::BindGroupDescriptor {
2074 layout: texture_bind_group_layout,
2075 entries: &[
2076 wgpu::BindGroupEntry {
2077 binding: 0,
2078 resource: wgpu::BindingResource::TextureViewArray(&blur_views_a),
2079 },
2080 wgpu::BindGroupEntry {
2081 binding: 1,
2082 resource: wgpu::BindingResource::Sampler(&sampler),
2083 },
2084 ],
2085 label: Some("Blur Bind Group A"),
2086 });
2087
2088 let blur_views_b: Vec<&wgpu::TextureView> = (0..256).map(|_| &blur_texture_b).collect();
2089 let blur_bind_group_b = device.create_bind_group(&wgpu::BindGroupDescriptor {
2090 layout: texture_bind_group_layout,
2091 entries: &[
2092 wgpu::BindGroupEntry {
2093 binding: 0,
2094 resource: wgpu::BindingResource::TextureViewArray(&blur_views_b),
2095 },
2096 wgpu::BindGroupEntry {
2097 binding: 1,
2098 resource: wgpu::BindingResource::Sampler(&sampler),
2099 },
2100 ],
2101 label: Some("Blur Bind Group B"),
2102 });
2103
2104 let depth_texture = device.create_texture(&wgpu::TextureDescriptor {
2105 label: Some("Surtr Depth Texture"),
2106 size: wgpu::Extent3d {
2107 width,
2108 height,
2109 depth_or_array_layers: 1,
2110 },
2111 mip_level_count: 1,
2112 sample_count: 1,
2113 dimension: wgpu::TextureDimension::D2,
2114 format: wgpu::TextureFormat::Depth32Float,
2115 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
2116 view_formats: &[],
2117 });
2118 let depth_texture_view = depth_texture.create_view(&wgpu::TextureViewDescriptor::default());
2119
2120 let blur_env_bind_group_a = device.create_bind_group(&wgpu::BindGroupDescriptor {
2121 layout: env_bind_group_layout,
2122 entries: &[
2123 wgpu::BindGroupEntry {
2124 binding: 0,
2125 resource: wgpu::BindingResource::TextureView(&blur_texture_a),
2126 },
2127 wgpu::BindGroupEntry {
2128 binding: 1,
2129 resource: wgpu::BindingResource::Sampler(&sampler),
2130 },
2131 ],
2132 label: Some("Blur Env Bind Group A"),
2133 });
2134
2135 SurfaceContext {
2136 surface,
2137 config,
2138 scene_texture,
2139 scene_bind_group,
2140 scene_texture_bind_group,
2141 depth_texture_view,
2142 blur_texture_a,
2143 blur_texture_b,
2144 blur_bind_group_a,
2145 blur_bind_group_b,
2146 blur_env_bind_group_a,
2147 scale_factor,
2148 sampler,
2149 }
2150 }
2151
2152 pub fn reset_time(&mut self) {
2153 self.start_time = std::time::Instant::now();
2154 }
2155
2156 pub fn reclaim_vram(&mut self) {
2159 log::warn!("[GPU] Yggdrasil Compaction: Compacting Mega-Atlas...");
2160
2161 let new_mega_atlas_tex = self.device.create_texture(&wgpu::TextureDescriptor {
2162 label: Some("Yggdrasil Mega-Atlas (Compacted)"),
2163 size: wgpu::Extent3d {
2164 width: 4096,
2165 height: 4096,
2166 depth_or_array_layers: 1,
2167 },
2168 mip_level_count: 1,
2169 sample_count: 1,
2170 dimension: wgpu::TextureDimension::D2,
2171 format: wgpu::TextureFormat::Rgba8UnormSrgb,
2172 usage: wgpu::TextureUsages::TEXTURE_BINDING
2173 | wgpu::TextureUsages::COPY_DST
2174 | wgpu::TextureUsages::COPY_SRC,
2175 view_formats: &[],
2176 });
2177
2178 let mut new_packer = YggdrasilPacker::new(4096, 4096);
2179 let mut encoder = self
2180 .device
2181 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
2182 label: Some("Atlas Compaction Encoder"),
2183 });
2184
2185 let image_entries: Vec<(String, Rect)> = self
2186 .image_uv_registry
2187 .iter()
2188 .map(|(k, v)| (k.clone(), *v))
2189 .collect();
2190 for (name, old_uv) in image_entries {
2191 if let Some(&tex_idx) = self.texture_registry.get(&name)
2192 && tex_idx == 0
2193 {
2194 let w_px = (old_uv.width * 4096.0).round() as u32;
2195 let h_px = (old_uv.height * 4096.0).round() as u32;
2196 let old_x_px = (old_uv.x * 4096.0).round() as u32;
2197 let old_y_px = (old_uv.y * 4096.0).round() as u32;
2198
2199 if let Some((new_x, new_y)) = new_packer.pack(w_px, h_px) {
2200 encoder.copy_texture_to_texture(
2201 wgpu::TexelCopyTextureInfo {
2202 texture: &self.mega_atlas_tex,
2203 mip_level: 0,
2204 origin: wgpu::Origin3d {
2205 x: old_x_px,
2206 y: old_y_px,
2207 z: 0,
2208 },
2209 aspect: wgpu::TextureAspect::All,
2210 },
2211 wgpu::TexelCopyTextureInfo {
2212 texture: &new_mega_atlas_tex,
2213 mip_level: 0,
2214 origin: wgpu::Origin3d {
2215 x: new_x,
2216 y: new_y,
2217 z: 0,
2218 },
2219 aspect: wgpu::TextureAspect::All,
2220 },
2221 wgpu::Extent3d {
2222 width: w_px,
2223 height: h_px,
2224 depth_or_array_layers: 1,
2225 },
2226 );
2227
2228 let new_uv = Rect {
2229 x: new_x as f32 / 4096.0,
2230 y: new_y as f32 / 4096.0,
2231 width: old_uv.width,
2232 height: old_uv.height,
2233 };
2234 self.image_uv_registry.put(name.clone(), new_uv);
2235 }
2236 }
2237 }
2238
2239 let text_entries: Vec<(u64, (Rect, f32, f32))> =
2240 self.text_cache.iter().map(|(k, v)| (*k, *v)).collect();
2241 for (hash, (old_uv, w_f, h_f)) in text_entries {
2242 let w_px = (old_uv.width * 4096.0).round() as u32;
2243 let h_px = (old_uv.height * 4096.0).round() as u32;
2244 let old_x_px = (old_uv.x * 4096.0).round() as u32;
2245 let old_y_px = (old_uv.y * 4096.0).round() as u32;
2246
2247 if let Some((new_x, new_y)) = new_packer.pack(w_px, h_px) {
2248 encoder.copy_texture_to_texture(
2249 wgpu::TexelCopyTextureInfo {
2250 texture: &self.mega_atlas_tex,
2251 mip_level: 0,
2252 origin: wgpu::Origin3d {
2253 x: old_x_px,
2254 y: old_y_px,
2255 z: 0,
2256 },
2257 aspect: wgpu::TextureAspect::All,
2258 },
2259 wgpu::TexelCopyTextureInfo {
2260 texture: &new_mega_atlas_tex,
2261 mip_level: 0,
2262 origin: wgpu::Origin3d {
2263 x: new_x,
2264 y: new_y,
2265 z: 0,
2266 },
2267 aspect: wgpu::TextureAspect::All,
2268 },
2269 wgpu::Extent3d {
2270 width: w_px,
2271 height: h_px,
2272 depth_or_array_layers: 1,
2273 },
2274 );
2275
2276 let new_uv = Rect {
2277 x: new_x as f32 / 4096.0,
2278 y: new_y as f32 / 4096.0,
2279 width: old_uv.width,
2280 height: old_uv.height,
2281 };
2282 self.text_cache.put(hash, (new_uv, w_f, h_f));
2283 }
2284 }
2285
2286 self.queue.submit(std::iter::once(encoder.finish()));
2287
2288 self.mega_atlas_tex = new_mega_atlas_tex;
2289 let mega_atlas_view_obj = self
2290 .mega_atlas_tex
2291 .create_view(&wgpu::TextureViewDescriptor::default());
2292 self.texture_views[0] = mega_atlas_view_obj.clone();
2293
2294 self.rebuild_texture_array_bind_group();
2295
2296 if !self.texture_bind_groups.is_empty() {
2297 self.texture_bind_groups[0] = self.mega_atlas_bind_group.clone();
2298 }
2299
2300 self.atlas_packer = new_packer;
2301 self.telemetry.vram_exhausted = false;
2302 }
2303
2304 fn shatter_internal(
2305 &mut self,
2306 rect: Rect,
2307 pieces: u32,
2308 force: f32,
2309 color: [f32; 4],
2310 mode: u32,
2311 ) {
2312 let count = (pieces as f32).sqrt().ceil() as u32;
2314 let dw = rect.width / count as f32;
2315 let dh = rect.height / count as f32;
2316
2317 let c = self.apply_opacity(color);
2318
2319 for y in 0..count {
2320 for x in 0..count {
2321 let shard_rect = Rect {
2322 x: rect.x + x as f32 * dw,
2323 y: rect.y + y as f32 * dh,
2324 width: dw,
2325 height: dh,
2326 };
2327
2328 let uv = Rect {
2329 x: x as f32 / count as f32,
2330 y: y as f32 / count as f32,
2331 width: 1.0 / count as f32,
2332 height: 1.0 / count as f32,
2333 };
2334
2335 self.fill_rect_with_full_params(shard_rect, c, mode, None, force, uv);
2336 }
2337 }
2338 }
2339
2340 fn recursive_bolt(&mut self, from: [f32; 2], to: [f32; 2], depth: u32, color: [f32; 4]) {
2341 if depth == 0 {
2342 self.draw_lightning_segment(from, to, color);
2343 return;
2344 }
2345
2346 let mid_x = (from[0] + to[0]) * 0.5;
2347 let mid_y = (from[1] + to[1]) * 0.5;
2348
2349 let dx = to[0] - from[0];
2350 let dy = to[1] - from[1];
2351 let len = (dx * dx + dy * dy).sqrt();
2352
2353 let offset_scale = len * 0.15;
2355 let seed = (from[0] * 12.9898 + from[1] * 78.233 + (depth as f32) * 37.11)
2356 .sin()
2357 .fract();
2358 let offset_x = -dy / len * (seed - 0.5) * offset_scale;
2359 let offset_y = dx / len * (seed - 0.5) * offset_scale;
2360
2361 let mid = [mid_x + offset_x, mid_y + offset_y];
2362
2363 self.recursive_bolt(from, mid, depth - 1, color);
2364 self.recursive_bolt(mid, to, depth - 1, color);
2365
2366 if depth > 2 && seed > 0.8 {
2368 let branch_to = [
2369 mid[0] + offset_x * 2.0 + (seed * 100.0).sin() * 50.0,
2370 mid[1] + offset_y * 2.0 + (seed * 100.0).cos() * 50.0,
2371 ];
2372 self.recursive_bolt(mid, branch_to, depth - 2, color);
2373 }
2374 }
2375
2376 fn draw_lightning_segment(&mut self, from: [f32; 2], to: [f32; 2], color: [f32; 4]) {
2377 let dx = to[0] - from[0];
2378 let dy = to[1] - from[1];
2379 let len = (dx * dx + dy * dy).sqrt();
2380 if len < 0.001 {
2381 return;
2382 }
2383
2384 let glow_width = 32.0;
2385 let core_width = 4.0;
2386 let c = self.apply_opacity(color);
2387
2388 let gnx = -dy / len * glow_width * 0.5;
2390 let gny = dx / len * glow_width * 0.5;
2391 let gp1 = [from[0] + gnx, from[1] + gny];
2392 let gp2 = [to[0] + gnx, to[1] + gny];
2393 let gp3 = [to[0] - gnx, to[1] - gny];
2394 let gp4 = [from[0] - gnx, from[1] - gny];
2395 self.push_oriented_quad(
2396 [gp1, gp2, gp3, gp4],
2397 c,
2398 9,
2399 Rect {
2400 x: 0.0,
2401 y: 0.0,
2402 width: 1.0,
2403 height: 1.0,
2404 },
2405 );
2406
2407 let cnx = -dy / len * core_width * 0.5;
2409 let cny = dx / len * core_width * 0.5;
2410 let cp1 = [from[0] + cnx, from[1] + cny];
2411 let cp2 = [to[0] + cnx, to[1] + cny];
2412 let cp3 = [to[0] - cnx, to[1] - cny];
2413 let cp4 = [from[0] - cnx, from[1] - cny];
2414 self.push_oriented_quad(
2415 [cp1, cp2, cp3, cp4],
2416 [1.0, 1.0, 1.0, c[3]],
2417 0,
2418 Rect {
2419 x: 0.0,
2420 y: 0.0,
2421 width: 1.0,
2422 height: 1.0,
2423 },
2424 );
2425 }
2426
2427 fn push_oriented_quad(
2428 &mut self,
2429 points: [[f32; 2]; 4],
2430 color: [f32; 4],
2431 mode: u32,
2432 uv_rect: Rect,
2433 ) {
2434 let scissor = self.clip_stack.last().copied();
2435 let texture_id = None; if self.draw_calls.is_empty()
2438 || self.current_texture_id != texture_id
2439 || self.draw_calls.last().unwrap().scissor_rect != scissor
2440 {
2441 self.current_texture_id = texture_id;
2442 self.draw_calls.push(DrawCall {
2443 texture_id,
2444 scissor_rect: scissor,
2445 index_start: self.indices.len() as u32,
2446 index_count: 0,
2447 material: if mode == 7 {
2448 cvkg_core::DrawMaterial::Glass { blur_radius: 20.0 }
2449 } else if mode == 6 {
2450 cvkg_core::DrawMaterial::TopUI
2451 } else {
2452 cvkg_core::DrawMaterial::Opaque
2453 },
2454 });
2455 }
2456
2457 let uvs = [
2458 [uv_rect.x, uv_rect.y],
2459 [uv_rect.x + uv_rect.width, uv_rect.y],
2460 [uv_rect.x + uv_rect.width, uv_rect.y + uv_rect.height],
2461 [uv_rect.x, uv_rect.y + uv_rect.height],
2462 ];
2463
2464 let screen = [self.current_width() as f32, self.current_height() as f32];
2465 let rect = Rect {
2466 x: points[0][0],
2467 y: points[0][1],
2468 width: 1.0,
2469 height: 1.0,
2470 };
2471
2472 for i in 0..4 {
2473 let px = points[i][0];
2474 let py = points[i][1];
2475
2476 let (translation, scale_transform, rotation, _, _) = self.current_transform();
2477 self.vertices.push(Vertex {
2478 position: [px, py, 0.0],
2479 normal: [0.0, 0.0, 1.0],
2480 uv: uvs[i],
2481 color,
2482 mode,
2483 radius: 0.0,
2484 slice: [0.0, 0.0, 0.0, 1.0],
2485 logical: [px - rect.x, py - rect.y],
2486 size: [rect.width, rect.height],
2487 screen,
2488 clip: [-10000.0, -10000.0, 20000.0, 20000.0],
2489 translation,
2490 scale: scale_transform,
2491 rotation,
2492 tex_index: 0,
2493 });
2494 }
2495
2496 if let Some(call) = self.draw_calls.last_mut() {
2497 call.index_count += 6;
2498 }
2499 }
2500 fn get_texture_id(&mut self, name: &str) -> Option<u32> {
2501 self.texture_registry.get(name).copied()
2502 }
2503
2504 pub fn fill_rect_with_mode(
2506 &mut self,
2507 rect: Rect,
2508 color: [f32; 4],
2509 mode: u32,
2510 texture_id: Option<u32>,
2511 ) {
2512 self.fill_rect_with_full_params(
2513 rect,
2514 color,
2515 mode,
2516 texture_id,
2517 0.0,
2518 Rect {
2519 x: 0.0,
2520 y: 0.0,
2521 width: 1.0,
2522 height: 1.0,
2523 },
2524 );
2525 }
2526
2527 fn fill_rect_with_full_params(
2528 &mut self,
2529 rect: Rect,
2530 color: [f32; 4],
2531 mode: u32,
2532 texture_id: Option<u32>,
2533 radius: f32,
2534 uv_rect: Rect,
2535 ) {
2536 if let Some(shadow) = self.shadow_stack.last().copied()
2538 && shadow.color[3] > 0.001
2539 {
2540 Renderer::draw_drop_shadow(
2541 self,
2542 rect,
2543 radius,
2544 shadow.color,
2545 shadow.radius,
2546 0.0, );
2548 }
2549
2550 let slice = self
2551 .slice_stack
2552 .last()
2553 .copied()
2554 .map(|(a, o)| [a, o, 1.0, 1.0])
2555 .unwrap_or([0.0, 0.0, 0.0, 1.0]);
2556 self.fill_rect_with_full_params_and_slice(
2557 rect, color, mode, texture_id, radius, uv_rect, slice,
2558 );
2559 }
2560
2561 #[allow(clippy::too_many_arguments)]
2562 fn fill_rect_with_full_params_and_slice(
2563 &mut self,
2564 rect: Rect,
2565 color: [f32; 4],
2566 mode: u32,
2567 texture_id: Option<u32>,
2568 radius: f32,
2569 uv_rect: Rect,
2570 slice: [f32; 4],
2571 ) {
2572 let scissor = self.clip_stack.last().copied();
2573
2574 let material = if mode == 7 {
2575 cvkg_core::DrawMaterial::Glass { blur_radius: 20.0 }
2576 } else if mode == 6 {
2577 cvkg_core::DrawMaterial::TopUI
2578 } else {
2579 self.current_draw_material
2580 };
2581
2582 let last_call = self.draw_calls.last();
2586 let needs_new_call = self.draw_calls.is_empty()
2587 || last_call.unwrap().scissor_rect != scissor
2588 || last_call.unwrap().material != material;
2589
2590 if needs_new_call {
2591 self.current_texture_id = Some(0); self.draw_calls.push(DrawCall {
2593 texture_id: self.current_texture_id,
2594 scissor_rect: scissor,
2595 index_start: self.indices.len() as u32,
2596 index_count: 0,
2597 material,
2598 });
2599 }
2600
2601 let scale = self.current_scale_factor();
2602 let snap = |v: f32| (v * scale).round() / scale;
2603
2604 let base_idx = self.vertices.len() as u32;
2605 let x1 = snap(rect.x);
2606 let y1 = snap(rect.y);
2607 let x2 = snap(rect.x + rect.width);
2608 let y2 = snap(rect.y + rect.height);
2609 let z = self.current_z;
2610 let normal = [0.0, 0.0, 1.0];
2611 let screen = [self.current_width() as f32, self.current_height() as f32];
2612 let clip_rect = self.clip_stack.last().copied().unwrap_or(cvkg_core::Rect {
2613 x: -10000.0,
2614 y: -10000.0,
2615 width: 20000.0,
2616 height: 20000.0,
2617 });
2618 let clip = [clip_rect.x, clip_rect.y, clip_rect.width, clip_rect.height];
2619
2620 let (translation, scale_transform, rotation, _, _) = self.current_transform();
2621
2622 let tex_index = texture_id.unwrap_or(0);
2623
2624 self.vertices.push(Vertex {
2625 position: [x1, y1, z],
2626 normal,
2627 uv: [uv_rect.x, uv_rect.y],
2628 color,
2629 mode,
2630 radius,
2631 slice,
2632 logical: [0.0, 0.0],
2633 size: [rect.width, rect.height],
2634 screen,
2635 clip,
2636 translation,
2637 scale: scale_transform,
2638 rotation,
2639 tex_index,
2640 });
2641 self.vertices.push(Vertex {
2642 position: [x2, y1, z],
2643 normal,
2644 uv: [uv_rect.x + uv_rect.width, uv_rect.y],
2645 color,
2646 mode,
2647 radius,
2648 slice,
2649 logical: [rect.width, 0.0],
2650 size: [rect.width, rect.height],
2651 screen,
2652 clip,
2653 translation,
2654 scale: scale_transform,
2655 rotation,
2656 tex_index,
2657 });
2658 self.vertices.push(Vertex {
2659 position: [x2, y2, z],
2660 normal,
2661 uv: [uv_rect.x + uv_rect.width, uv_rect.y + uv_rect.height],
2662 color,
2663 mode,
2664 radius,
2665 slice,
2666 logical: [rect.width, rect.height],
2667 size: [rect.width, rect.height],
2668 screen,
2669 clip,
2670 translation,
2671 scale: scale_transform,
2672 rotation,
2673 tex_index,
2674 });
2675 self.vertices.push(Vertex {
2676 position: [x1, y2, z],
2677 normal,
2678 uv: [uv_rect.x, uv_rect.y + uv_rect.height],
2679 color,
2680 mode,
2681 radius,
2682 slice,
2683 logical: [0.0, rect.height],
2684 size: [rect.width, rect.height],
2685 screen,
2686 clip,
2687 translation,
2688 scale: scale_transform,
2689 rotation,
2690 tex_index,
2691 });
2692
2693 self.indices.extend_from_slice(&[
2694 base_idx,
2695 base_idx + 1,
2696 base_idx + 2,
2697 base_idx,
2698 base_idx + 2,
2699 base_idx + 3,
2700 ]);
2701
2702 if let Some(call) = self.draw_calls.last_mut() {
2703 call.index_count += 6;
2704 }
2705 }
2706
2707 pub fn end_frame(&mut self, mut encoder: wgpu::CommandEncoder) {
2709 let (
2710 surface_texture,
2711 target_view,
2712 ctx_scene_texture,
2713 ctx_depth_texture_view,
2714 ctx_blur_env_bind_group_a,
2715 ctx_scene_texture_bind_group,
2716 ctx_blur_texture_a,
2717 ctx_blur_texture_b,
2718 _ctx_sampler,
2719 ctx_blur_bind_group_a,
2720 ctx_blur_bind_group_b,
2721 scale,
2722 ) = if let Some(window_id) = self.current_window {
2723 let ctx = self
2724 .surfaces
2725 .get(&window_id)
2726 .expect("Missing surface context");
2727 let frame = match ctx.surface.get_current_texture() {
2728 wgpu::CurrentSurfaceTexture::Success(t) => t,
2729 wgpu::CurrentSurfaceTexture::Suboptimal(t) => {
2730 ctx.surface.configure(&self.device, &ctx.config);
2731 t
2732 }
2733 _ => {
2734 log::warn!("[GPU] Surface texture acquisition failed, reconfiguring surface");
2735 ctx.surface.configure(&self.device, &ctx.config);
2736 return;
2737 }
2738 };
2739 let view = frame
2740 .texture
2741 .create_view(&wgpu::TextureViewDescriptor::default());
2742 (
2743 Some(frame),
2744 view,
2745 &ctx.scene_texture,
2746 &ctx.depth_texture_view,
2747 &ctx.blur_env_bind_group_a,
2748 &ctx.scene_texture_bind_group,
2749 &ctx.blur_texture_a,
2750 &ctx.blur_texture_b,
2751 &ctx.sampler,
2752 &ctx.blur_bind_group_a,
2753 &ctx.blur_bind_group_b,
2754 ctx.scale_factor,
2755 )
2756 } else {
2757 let ctx = self
2758 .headless_context
2759 .as_ref()
2760 .expect("No headless context for end_frame");
2761 (
2762 None,
2763 ctx.output_view.clone(),
2764 &ctx.scene_texture,
2765 &ctx.depth_texture_view,
2766 &ctx.blur_env_bind_group_a,
2767 &ctx.scene_texture_bind_group,
2768 &ctx.blur_texture_a,
2769 &ctx.blur_texture_b,
2770 &ctx.sampler,
2771 &ctx.blur_bind_group_a,
2772 &ctx.blur_bind_group_b,
2773 self.current_scale_factor(),
2774 )
2775 };
2776
2777 {
2779 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2780 label: Some("Surtr P1 Opaque Background"),
2781 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2782 view: ctx_scene_texture,
2783 resolve_target: None,
2784 ops: wgpu::Operations {
2785 load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
2786 store: wgpu::StoreOp::Store,
2787 },
2788 depth_slice: None,
2789 })],
2790 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
2791 view: ctx_depth_texture_view,
2792 depth_ops: Some(wgpu::Operations {
2793 load: wgpu::LoadOp::Clear(0.0), store: wgpu::StoreOp::Store,
2795 }),
2796 stencil_ops: None,
2797 }),
2798 timestamp_writes: self.skuld_queries.as_ref().map(|q| {
2799 wgpu::RenderPassTimestampWrites {
2800 query_set: q,
2801 beginning_of_pass_write_index: Some(0),
2802 end_of_pass_write_index: None,
2803 }
2804 }),
2805 occlusion_query_set: None,
2806 multiview_mask: None,
2807 });
2808
2809 p.set_pipeline(&self.background_pipeline);
2811 p.set_bind_group(0, &self.dummy_texture_bind_group, &[]);
2812 p.set_bind_group(1, ctx_blur_env_bind_group_a, &[]); p.set_bind_group(2, &self.berserker_bind_group, &[]);
2814 p.draw(0..6, 0..1);
2815
2816 if !self.draw_calls.is_empty() {
2818 p.set_pipeline(&self.pipeline);
2819 p.set_vertex_buffer(0, self.vertex_buffer.slice(..));
2820 p.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
2821 p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
2822 p.set_bind_group(2, &self.berserker_bind_group, &[]);
2823
2824 for call in self
2825 .draw_calls
2826 .iter()
2827 .filter(|c| matches!(c.material, cvkg_core::DrawMaterial::Opaque))
2828 {
2829 let bg = if let Some(id) = call.texture_id {
2830 if id == 0 {
2831 &self.mega_atlas_bind_group
2832 } else {
2833 self.texture_bind_groups
2834 .get(id as usize)
2835 .unwrap_or(&self.dummy_texture_bind_group)
2836 }
2837 } else {
2838 &self.dummy_texture_bind_group
2839 };
2840 p.set_bind_group(0, bg, &[]);
2841 p.draw_indexed(
2842 call.index_start..call.index_start + call.index_count,
2843 0,
2844 0..1,
2845 );
2846 self.telemetry.draw_calls += 1;
2847 self.telemetry.vertices += call.index_count;
2848 }
2849 }
2850 }
2851
2852 {
2855 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2857 label: Some("Surtr Blur Extract"),
2858 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2859 view: ctx_blur_texture_a,
2860 resolve_target: None,
2861 ops: wgpu::Operations {
2862 load: wgpu::LoadOp::Clear(wgpu::Color {
2863 r: 0.0,
2864 g: 0.0,
2865 b: 0.0,
2866 a: 0.0,
2867 }),
2868 store: wgpu::StoreOp::Store,
2869 },
2870 depth_slice: None,
2871 })],
2872 ..Default::default()
2873 });
2874 p.set_pipeline(&self.bloom_extract_pipeline); p.set_bind_group(0, ctx_scene_texture_bind_group, &[]);
2876 p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
2877 p.set_bind_group(2, &self.berserker_bind_group, &[]);
2878 p.draw(0..6, 0..1);
2879 }
2880
2881 let blur_iters: u32 = 4;
2882 for _i in 0..blur_iters {
2883 {
2884 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2885 label: Some("Blur H"),
2886 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2887 view: ctx_blur_texture_b,
2888 resolve_target: None,
2889 ops: wgpu::Operations {
2890 load: wgpu::LoadOp::Clear(wgpu::Color {
2891 r: 0.0,
2892 g: 0.0,
2893 b: 0.0,
2894 a: 0.0,
2895 }),
2896 store: wgpu::StoreOp::Store,
2897 },
2898 depth_slice: None,
2899 })],
2900 ..Default::default()
2901 });
2902 p.set_pipeline(&self.blur_h_pipeline);
2903 p.set_bind_group(0, ctx_blur_bind_group_a, &[]);
2904 p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
2905 p.set_bind_group(2, &self.berserker_bind_group, &[]);
2906 p.draw(0..6, 0..1);
2907 }
2908 {
2909 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2910 label: Some("Blur V"),
2911 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2912 view: ctx_blur_texture_a,
2913 resolve_target: None,
2914 ops: wgpu::Operations {
2915 load: wgpu::LoadOp::Clear(wgpu::Color {
2916 r: 0.0,
2917 g: 0.0,
2918 b: 0.0,
2919 a: 0.0,
2920 }),
2921 store: wgpu::StoreOp::Store,
2922 },
2923 depth_slice: None,
2924 })],
2925 ..Default::default()
2926 });
2927 p.set_pipeline(&self.blur_v_pipeline);
2928 p.set_bind_group(0, ctx_blur_bind_group_b, &[]);
2929 p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
2930 p.set_bind_group(2, &self.berserker_bind_group, &[]);
2931 p.draw(0..6, 0..1);
2932 }
2933 }
2934
2935 self.staging_command_buffers.push(encoder.finish());
2937
2938 let rt_w = self.current_width() as i32;
2939 let rt_h = self.current_height() as i32;
2940
2941 let (glass_cb, ui_cb) = rayon::join(
2944 || {
2945 let mut glass_encoder =
2946 self.device
2947 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
2948 label: Some("Parallel Glass Encoder"),
2949 });
2950 {
2951 let mut p = glass_encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2952 label: Some("Surtr P3 Liquid Glass"),
2953 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2954 view: ctx_scene_texture,
2955 resolve_target: None,
2956 ops: wgpu::Operations {
2957 load: wgpu::LoadOp::Load,
2958 store: wgpu::StoreOp::Store,
2959 },
2960 depth_slice: None,
2961 })],
2962 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
2963 view: ctx_depth_texture_view,
2964 depth_ops: Some(wgpu::Operations {
2965 load: wgpu::LoadOp::Load,
2966 store: wgpu::StoreOp::Store,
2967 }),
2968 stencil_ops: None,
2969 }),
2970 ..Default::default()
2971 });
2972
2973 p.set_pipeline(&self.pipeline);
2974 p.set_vertex_buffer(0, self.vertex_buffer.slice(..));
2975 p.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
2976 p.set_bind_group(1, ctx_blur_env_bind_group_a, &[]);
2977 p.set_bind_group(2, &self.berserker_bind_group, &[]);
2978
2979 for call in self
2980 .draw_calls
2981 .iter()
2982 .filter(|c| matches!(c.material, cvkg_core::DrawMaterial::Glass { .. }))
2983 {
2984 let bg = if let Some(id) = call.texture_id {
2985 if id == 0 {
2986 &self.mega_atlas_bind_group
2987 } else {
2988 self.texture_bind_groups
2989 .get(id as usize)
2990 .unwrap_or(&self.dummy_texture_bind_group)
2991 }
2992 } else {
2993 &self.dummy_texture_bind_group
2994 };
2995 p.set_bind_group(0, bg, &[]);
2996 if let Some(rect) = call.scissor_rect {
2997 if rt_w > 0 && rt_h > 0 {
3004 let x1 = (rect.x * scale).round() as i32;
3005 let y1 = (rect.y * scale).round() as i32;
3006 let x2 = ((rect.x + rect.width) * scale).round() as i32;
3007 let y2 = ((rect.y + rect.height) * scale).round() as i32;
3008
3009 let x1_clamped = x1.clamp(0, rt_w);
3010 let y1_clamped = y1.clamp(0, rt_h);
3011 let x2_clamped = x2.clamp(0, rt_w);
3012 let y2_clamped = y2.clamp(0, rt_h);
3013
3014 let w = x2_clamped - x1_clamped;
3015 let h = y2_clamped - y1_clamped;
3016
3017 if w > 0 && h > 0 {
3018 p.set_scissor_rect(
3019 x1_clamped as u32,
3020 y1_clamped as u32,
3021 w as u32,
3022 h as u32,
3023 );
3024 } else {
3025 p.set_scissor_rect(0, 0, 1, 1);
3026 }
3027 }
3028 }
3029 p.draw_indexed(
3030 call.index_start..call.index_start + call.index_count,
3031 0,
3032 0..1,
3033 );
3034 }
3035 }
3036 glass_encoder.finish()
3037 },
3038 || {
3039 let mut ui_encoder =
3040 self.device
3041 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
3042 label: Some("Parallel UI Encoder"),
3043 });
3044 {
3045 let mut p = ui_encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
3046 label: Some("Surtr P4 UI Layer"),
3047 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
3048 view: ctx_scene_texture,
3049 resolve_target: None,
3050 ops: wgpu::Operations {
3051 load: wgpu::LoadOp::Load,
3052 store: wgpu::StoreOp::Store,
3053 },
3054 depth_slice: None,
3055 })],
3056 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
3057 view: ctx_depth_texture_view,
3058 depth_ops: Some(wgpu::Operations {
3059 load: wgpu::LoadOp::Load,
3060 store: wgpu::StoreOp::Store,
3061 }),
3062 stencil_ops: None,
3063 }),
3064 ..Default::default()
3065 });
3066
3067 p.set_pipeline(&self.pipeline);
3068 p.set_vertex_buffer(0, self.vertex_buffer.slice(..));
3069 p.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
3070 p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
3071 p.set_bind_group(2, &self.berserker_bind_group, &[]);
3072
3073 for call in self
3074 .draw_calls
3075 .iter()
3076 .filter(|c| matches!(c.material, cvkg_core::DrawMaterial::TopUI))
3077 {
3078 let bg = if let Some(id) = call.texture_id {
3079 if id == 0 {
3080 &self.mega_atlas_bind_group
3081 } else {
3082 self.texture_bind_groups
3083 .get(id as usize)
3084 .unwrap_or(&self.dummy_texture_bind_group)
3085 }
3086 } else {
3087 &self.dummy_texture_bind_group
3088 };
3089 p.set_bind_group(0, bg, &[]);
3090 if let Some(rect) = call.scissor_rect {
3091 if rt_w > 0 && rt_h > 0 {
3098 let x1 = (rect.x * scale).round() as i32;
3099 let y1 = (rect.y * scale).round() as i32;
3100 let x2 = ((rect.x + rect.width) * scale).round() as i32;
3101 let y2 = ((rect.y + rect.height) * scale).round() as i32;
3102
3103 let x1_clamped = x1.clamp(0, rt_w);
3104 let y1_clamped = y1.clamp(0, rt_h);
3105 let x2_clamped = x2.clamp(0, rt_w);
3106 let y2_clamped = y2.clamp(0, rt_h);
3107
3108 let w = x2_clamped - x1_clamped;
3109 let h = y2_clamped - y1_clamped;
3110
3111 if w > 0 && h > 0 {
3112 p.set_scissor_rect(
3113 x1_clamped as u32,
3114 y1_clamped as u32,
3115 w as u32,
3116 h as u32,
3117 );
3118 } else {
3119 p.set_scissor_rect(0, 0, 1, 1);
3120 }
3121 }
3122 }
3123 p.draw_indexed(
3124 call.index_start..call.index_start + call.index_count,
3125 0,
3126 0..1,
3127 );
3128 }
3129 }
3130 ui_encoder.finish()
3131 },
3132 );
3133
3134 self.staging_command_buffers.push(glass_cb);
3135 self.staging_command_buffers.push(ui_cb);
3136
3137 let glass_calls = self
3139 .draw_calls
3140 .iter()
3141 .filter(|c| matches!(c.material, cvkg_core::DrawMaterial::Glass { .. }))
3142 .count();
3143 let glass_verts: u32 = self
3144 .draw_calls
3145 .iter()
3146 .filter(|c| matches!(c.material, cvkg_core::DrawMaterial::Glass { .. }))
3147 .map(|c| c.index_count)
3148 .sum();
3149 let ui_calls = self
3150 .draw_calls
3151 .iter()
3152 .filter(|c| matches!(c.material, cvkg_core::DrawMaterial::TopUI))
3153 .count();
3154 let ui_verts: u32 = self
3155 .draw_calls
3156 .iter()
3157 .filter(|c| matches!(c.material, cvkg_core::DrawMaterial::TopUI))
3158 .map(|c| c.index_count)
3159 .sum();
3160 self.telemetry.draw_calls += (glass_calls + ui_calls) as u32;
3161 self.telemetry.vertices += glass_verts + ui_verts;
3162
3163 let mut post_encoder =
3165 self.device
3166 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
3167 label: Some("Surtr Post-Process Encoder"),
3168 });
3169
3170 {
3172 let mut p = post_encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
3173 label: Some("Surtr Bloom Extract"),
3174 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
3175 view: ctx_blur_texture_a,
3176 resolve_target: None,
3177 ops: wgpu::Operations {
3178 load: wgpu::LoadOp::Clear(wgpu::Color {
3179 r: 0.0,
3180 g: 0.0,
3181 b: 0.0,
3182 a: 0.0,
3183 }),
3184 store: wgpu::StoreOp::Store,
3185 },
3186 depth_slice: None,
3187 })],
3188 ..Default::default()
3189 });
3190 p.set_pipeline(&self.bloom_extract_pipeline);
3191 p.set_bind_group(0, ctx_scene_texture_bind_group, &[]);
3192 p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
3193 p.set_bind_group(2, &self.berserker_bind_group, &[]);
3194 p.draw(0..6, 0..1);
3195 }
3196
3197 for _ in 0..2 {
3199 {
3200 let mut p = post_encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
3201 label: Some("Bloom Blur H"),
3202 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
3203 view: ctx_blur_texture_b,
3204 resolve_target: None,
3205 ops: wgpu::Operations {
3206 load: wgpu::LoadOp::Clear(wgpu::Color {
3207 r: 0.0,
3208 g: 0.0,
3209 b: 0.0,
3210 a: 0.0,
3211 }),
3212 store: wgpu::StoreOp::Store,
3213 },
3214 depth_slice: None,
3215 })],
3216 ..Default::default()
3217 });
3218 p.set_pipeline(&self.blur_h_pipeline);
3219 p.set_bind_group(0, ctx_blur_bind_group_a, &[]);
3220 p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
3221 p.set_bind_group(2, &self.berserker_bind_group, &[]);
3222 p.draw(0..6, 0..1);
3223 }
3224 {
3225 let mut p = post_encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
3226 label: Some("Bloom Blur V"),
3227 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
3228 view: ctx_blur_texture_a,
3229 resolve_target: None,
3230 ops: wgpu::Operations {
3231 load: wgpu::LoadOp::Clear(wgpu::Color {
3232 r: 0.0,
3233 g: 0.0,
3234 b: 0.0,
3235 a: 0.0,
3236 }),
3237 store: wgpu::StoreOp::Store,
3238 },
3239 depth_slice: None,
3240 })],
3241 ..Default::default()
3242 });
3243 p.set_pipeline(&self.blur_v_pipeline);
3244 p.set_bind_group(0, ctx_blur_bind_group_b, &[]);
3245 p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
3246 p.set_bind_group(2, &self.berserker_bind_group, &[]);
3247 p.draw(0..6, 0..1);
3248 }
3249 }
3250
3251 {
3253 let mut p = post_encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
3254 label: Some("Surtr P7 Composite"),
3255 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
3256 view: &target_view,
3257 resolve_target: None,
3258 ops: wgpu::Operations {
3259 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
3260 store: wgpu::StoreOp::Store,
3261 },
3262 depth_slice: None,
3263 })],
3264 depth_stencil_attachment: None,
3265 timestamp_writes: self.skuld_queries.as_ref().map(|q| {
3266 wgpu::RenderPassTimestampWrites {
3267 query_set: q,
3268 beginning_of_pass_write_index: None,
3269 end_of_pass_write_index: Some(1),
3270 }
3271 }),
3272 occlusion_query_set: None,
3273 multiview_mask: None,
3274 });
3275 p.set_pipeline(&self.composite_pipeline);
3276 p.set_bind_group(0, ctx_scene_texture_bind_group, &[]);
3277 p.set_bind_group(1, ctx_blur_env_bind_group_a, &[]);
3278 p.set_bind_group(2, &self.berserker_bind_group, &[]);
3279 p.draw(0..6, 0..1);
3280 self.telemetry.draw_calls += 1;
3281 }
3282
3283 self.telemetry.frame_time_ms = self.last_frame_start.elapsed().as_secs_f32() * 1000.0;
3284 self.update_vram_telemetry();
3285
3286 if let (Some(q), Some(b), Some(rb)) = (
3288 &self.skuld_queries,
3289 &self.skuld_buffer,
3290 &self.skuld_read_buffer,
3291 ) {
3292 post_encoder.resolve_query_set(q, 0..2, b, 0);
3293 post_encoder.copy_buffer_to_buffer(b, 0, rb, 0, 16);
3294 }
3295
3296 self.staging_command_buffers.push(post_encoder.finish());
3298
3299 let cmds = std::mem::take(&mut self.staging_command_buffers);
3301 self.queue.submit(cmds);
3302 if let Some(f) = surface_texture {
3303 f.present();
3304 }
3305 }
3306}
3307
3308impl cvkg_core::ElapsedTime for SurtrRenderer {
3309 fn delta_time(&self) -> f32 {
3310 self.current_scene.delta_time
3311 }
3312
3313 fn elapsed_time(&self) -> f32 {
3314 self.start_time.elapsed().as_secs_f32()
3315 }
3316}
3317
3318impl SurtrRenderer {
3319 pub fn load_image_to_atlas(&mut self, name: &str, data: &[u8]) {
3322 if self.image_uv_registry.contains(name) {
3323 return;
3324 }
3325 let img_result = image::load_from_memory(data);
3326 let img = match img_result {
3327 Ok(img) => img.to_rgba8(),
3328 Err(e) => {
3329 log::error!("Failed to load image {} to atlas: {}", name, e);
3330 return;
3331 }
3332 };
3333 let (width, height) = img.dimensions();
3334
3335 if let Some((x, y)) = self.atlas_packer.pack(width, height) {
3337 let uv_rect = Rect {
3338 x: x as f32 / 4096.0,
3339 y: y as f32 / 4096.0,
3340 width: width as f32 / 4096.0,
3341 height: height as f32 / 4096.0,
3342 };
3343
3344 self.queue.write_texture(
3346 wgpu::TexelCopyTextureInfo {
3347 texture: &self.mega_atlas_tex,
3348 mip_level: 0,
3349 origin: wgpu::Origin3d { x, y, z: 0 },
3350 aspect: wgpu::TextureAspect::All,
3351 },
3352 &img,
3353 wgpu::TexelCopyBufferLayout {
3354 offset: 0,
3355 bytes_per_row: Some(4 * width),
3356 rows_per_image: Some(height),
3357 },
3358 wgpu::Extent3d {
3359 width,
3360 height,
3361 depth_or_array_layers: 1,
3362 },
3363 );
3364
3365 self.image_uv_registry.put(name.to_string(), uv_rect);
3366 self.texture_registry.put(name.to_string(), 0); log::debug!(
3368 "[Surtr] Packed '{}' into Mega-Atlas at ({}, {})",
3369 name,
3370 x,
3371 y
3372 );
3373 } else {
3374 log::warn!(
3375 "ATLAS_FULL: Failed to pack '{}' into Mega-Atlas. Falling back to Texture Array.",
3376 name
3377 );
3378 self.load_image(name, data);
3379 }
3380 }
3381
3382 fn shape_text_with_stack(&mut self, text: &str, size: f32) -> cvkg_runic_text::ShapedText {
3390 let mut style = cvkg_runic_text::TextStyle::new("SF Pro Text", size);
3391 style.fallback_families = vec![
3392 "SF Pro".to_string(),
3393 "Inter".to_string(),
3394 "Helvetica Neue".to_string(),
3395 "Helvetica".to_string(),
3396 "Arial".to_string(),
3397 "sans-serif".to_string(),
3398 ];
3399 let spans = vec![cvkg_runic_text::TextSpan::new(text, style)];
3400 self.text_engine
3401 .shape_layout(
3402 &spans,
3403 None,
3404 cvkg_runic_text::TextAlign::Start,
3405 cvkg_runic_text::TextOverflow::WordWrap,
3406 )
3407 .unwrap_or_else(|_| cvkg_runic_text::ShapedText {
3408 glyphs: Vec::new(),
3409 lines: Vec::new(),
3410 width: 0.0,
3411 height: 0.0,
3412 text: text.to_string(),
3413 spans: Vec::new(),
3414 has_rtl: false,
3415 ascent: 0.0,
3416 descent: 0.0,
3417 line_gap: 0.0,
3418 grapheme_boundaries: vec![],
3419 })
3420 }
3421}
3422
3423impl cvkg_core::Renderer for SurtrRenderer {
3424 fn is_over_budget(&self) -> bool {
3425 self.frame_budget.allow_degradation
3426 && self.last_frame_start.elapsed().as_secs_f32() * 1000.0 > self.frame_budget.target_ms
3427 }
3428
3429 fn prewarm_vram(&mut self, assets: Vec<(String, Vec<u8>)>) {
3431 log::info!(
3432 "[Surtr] Pre-warming Mega-Atlas with {} assets...",
3433 assets.len()
3434 );
3435 for (name, data) in assets {
3436 self.load_image_to_atlas(&name, &data);
3437 }
3438 }
3439
3440 fn fill_rect(&mut self, rect: Rect, color: [f32; 4]) {
3441 self.fill_rect_with_mode(rect, self.apply_opacity(color), 0, None);
3442 }
3443
3444 fn fill_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4]) {
3445 self.fill_rect_with_full_params(
3446 rect,
3447 self.apply_opacity(color),
3448 3,
3449 None,
3450 radius,
3451 Rect {
3452 x: 0.0,
3453 y: 0.0,
3454 width: 1.0,
3455 height: 1.0,
3456 },
3457 );
3458 }
3459
3460 fn fill_ellipse(&mut self, rect: Rect, color: [f32; 4]) {
3461 self.fill_rect_with_full_params(
3462 rect,
3463 self.apply_opacity(color),
3464 4,
3465 None,
3466 0.0,
3467 Rect {
3468 x: 0.0,
3469 y: 0.0,
3470 width: 1.0,
3471 height: 1.0,
3472 },
3473 );
3474 }
3475
3476 fn draw_3d_cube(&mut self, rect: Rect, color: [f32; 4], rotation: [f32; 3]) {
3477 self.fill_rect_with_full_params_and_slice(
3478 rect,
3479 self.apply_opacity(color),
3480 21,
3481 None,
3482 0.0,
3483 Rect {
3484 x: 0.0,
3485 y: 0.0,
3486 width: 1.0,
3487 height: 1.0,
3488 },
3489 [rotation[0], rotation[1], rotation[2], 0.0],
3490 );
3491 }
3492
3493 fn bifrost(&mut self, rect: Rect, blur: f32, _saturation: f32, opacity: f32) {
3494 let screen_uv = Rect {
3496 x: rect.x / self.current_width() as f32,
3497 y: rect.y / self.current_height() as f32,
3498 width: rect.width / self.current_width() as f32,
3499 height: rect.height / self.current_height() as f32,
3500 };
3501 self.fill_rect_with_full_params(rect, [1.0, 1.0, 1.0, opacity], 7, None, blur, screen_uv);
3504 }
3505
3506 fn gungnir(&mut self, rect: Rect, color: [f32; 4], radius: f32, intensity: f32) {
3507 let center_x = rect.x + rect.width * 0.5;
3510 let center_y = rect.y + rect.height * 0.5;
3511 let max_dim = rect.width.max(rect.height) * 0.5 + radius;
3512
3513 for i in 0..8 {
3515 let alpha = intensity / (i as f32 + 1.0) * 0.3;
3516 let glow_color = [color[0], color[1], color[2], alpha];
3517 self.fill_rect_with_mode(
3518 Rect {
3519 x: center_x - max_dim - i as f32 * 2.0,
3520 y: center_y - max_dim - i as f32 * 2.0,
3521 width: max_dim * 2.0 + i as f32 * 4.0,
3522 height: max_dim * 2.0 + i as f32 * 4.0,
3523 },
3524 glow_color,
3525 8, None,
3527 );
3528 }
3529 }
3530
3531 fn mani_glow(&mut self, rect: Rect, color: [f32; 4], radius: f32) {
3538 let margin = radius;
3539 let glow_rect = Rect {
3540 x: rect.x - margin,
3541 y: rect.y - margin,
3542 width: rect.width + 2.0 * margin,
3543 height: rect.height + 2.0 * margin,
3544 };
3545 let uv_rect = Rect {
3546 x: margin,
3547 y: radius,
3548 width: 0.0,
3549 height: 0.0,
3550 };
3551 self.fill_rect_with_full_params(
3552 glow_rect,
3553 self.apply_opacity(color),
3554 18,
3555 None,
3556 8.0,
3557 uv_rect,
3558 );
3559 }
3560
3561 fn stroke_rect(&mut self, rect: Rect, color: [f32; 4], stroke_width: f32) {
3562 let c = self.apply_opacity(color);
3563 let hw = stroke_width;
3564 self.fill_rect_with_mode(
3566 Rect {
3567 x: rect.x,
3568 y: rect.y,
3569 width: rect.width,
3570 height: hw,
3571 },
3572 c,
3573 1,
3574 None,
3575 );
3576 self.fill_rect_with_mode(
3577 Rect {
3578 x: rect.x,
3579 y: rect.y + rect.height - hw,
3580 width: rect.width,
3581 height: hw,
3582 },
3583 c,
3584 1,
3585 None,
3586 );
3587 self.fill_rect_with_mode(
3588 Rect {
3589 x: rect.x,
3590 y: rect.y,
3591 width: hw,
3592 height: rect.height,
3593 },
3594 c,
3595 1,
3596 None,
3597 );
3598 self.fill_rect_with_mode(
3599 Rect {
3600 x: rect.x + rect.width - hw,
3601 y: rect.y,
3602 width: hw,
3603 height: rect.height,
3604 },
3605 c,
3606 1,
3607 None,
3608 );
3609 }
3610
3611 fn stroke_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4], stroke_width: f32) {
3612 self.fill_rect_with_full_params(
3613 rect,
3614 self.apply_opacity(color),
3615 17,
3616 None,
3617 radius,
3618 Rect {
3619 x: stroke_width,
3620 y: 0.0,
3621 width: 0.0,
3622 height: 0.0,
3623 },
3624 );
3625 }
3626
3627 fn stroke_ellipse(&mut self, rect: Rect, color: [f32; 4], stroke_width: f32) {
3628 let cx = rect.x + rect.width / 2.0;
3630 let cy = rect.y + rect.height / 2.0;
3631 let rx = rect.width / 2.0;
3632 let ry = rect.height / 2.0;
3633
3634 let mut builder = lyon::path::Path::builder();
3636 if rx > 0.0 && ry > 0.0 {
3637 let segments = 64;
3639 for i in 0..segments {
3640 let angle = 2.0 * std::f32::consts::PI * (i as f32) / (segments as f32);
3641 let x = cx + rx * angle.cos();
3642 let y = cy + ry * angle.sin();
3643 if i == 0 {
3644 builder.begin(lyon::math::point(x, y));
3645 } else {
3646 builder.line_to(lyon::math::point(x, y));
3647 }
3648 }
3649 builder.close();
3650 }
3651 let path = builder.build();
3652 self.stroke_path(&path, color, stroke_width);
3653 }
3654
3655 fn draw_linear_gradient(
3656 &mut self,
3657 rect: Rect,
3658 start_color: [f32; 4],
3659 end_color: [f32; 4],
3660 angle: f32,
3661 ) {
3662 self.fill_rect_with_full_params_and_slice(
3663 rect,
3664 self.apply_opacity(start_color),
3665 15,
3666 None,
3667 0.0,
3668 Rect {
3669 x: angle,
3670 y: 0.0,
3671 width: 1.0,
3672 height: 1.0,
3673 },
3674 end_color,
3675 );
3676 }
3677
3678 fn draw_radial_gradient(&mut self, rect: Rect, inner_color: [f32; 4], outer_color: [f32; 4]) {
3679 self.fill_rect_with_full_params_and_slice(
3680 rect,
3681 self.apply_opacity(inner_color),
3682 16,
3683 None,
3684 0.0,
3685 Rect {
3686 x: 0.0,
3687 y: 0.0,
3688 width: 1.0,
3689 height: 1.0,
3690 },
3691 outer_color,
3692 );
3693 }
3694
3695 fn draw_drop_shadow(
3696 &mut self,
3697 rect: Rect,
3698 radius: f32,
3699 color: [f32; 4],
3700 blur: f32,
3701 spread: f32,
3702 ) {
3703 let margin = blur + spread;
3704 let inflated = Rect {
3705 x: rect.x - margin,
3706 y: rect.y - margin,
3707 width: rect.width + margin * 2.0,
3708 height: rect.height + margin * 2.0,
3709 };
3710 self.fill_rect_with_full_params(
3712 inflated,
3713 self.apply_opacity(color),
3714 18,
3715 None,
3716 radius,
3717 Rect {
3718 x: margin,
3719 y: blur,
3720 width: 0.0,
3721 height: 0.0,
3722 },
3723 );
3724 }
3725
3726 fn stroke_dashed_rounded_rect(
3727 &mut self,
3728 rect: Rect,
3729 radius: f32,
3730 color: [f32; 4],
3731 width: f32,
3732 dash: f32,
3733 gap: f32,
3734 ) {
3735 self.fill_rect_with_full_params(
3736 rect,
3737 self.apply_opacity(color),
3738 19,
3739 None,
3740 radius,
3741 Rect {
3742 x: width,
3743 y: dash,
3744 width: gap,
3745 height: 0.0,
3746 },
3747 );
3748 }
3749
3750 fn draw_9slice(
3751 &mut self,
3752 image_name: &str,
3753 rect: Rect,
3754 left: f32,
3755 top: f32,
3756 right: f32,
3757 bottom: f32,
3758 ) {
3759 let c = self.apply_opacity([1.0, 1.0, 1.0, 1.0]);
3760 let tid = self.get_texture_id(image_name);
3761 self.fill_rect_with_full_params(
3762 rect,
3763 c,
3764 20,
3765 tid,
3766 bottom,
3767 Rect {
3768 x: left,
3769 y: top,
3770 width: right,
3771 height: 0.0,
3772 },
3773 );
3774 }
3775
3776 fn draw_line(
3777 &mut self,
3778 x1: f32,
3779 y1: f32,
3780 x2: f32,
3781 y2: f32,
3782 color: [f32; 4],
3783 stroke_width: f32,
3784 ) {
3785 let dx = x2 - x1;
3786 let dy = y2 - y1;
3787 let len = (dx * dx + dy * dy).sqrt();
3788 if len < 0.001 {
3789 return;
3790 }
3791
3792 let c = self.apply_opacity(color);
3793 let tid = self.get_texture_id("__mega_atlas");
3794
3795 self.fill_rect_with_mode(
3796 Rect {
3797 x: (x1 + x2) / 2.0 - len / 2.0,
3798 y: (y1 + y2) / 2.0 - stroke_width / 2.0,
3799 width: len,
3800 height: stroke_width,
3801 },
3802 c,
3803 1, tid,
3805 );
3806 }
3807
3808 fn draw_image(&mut self, image_name: &str, rect: Rect) {
3809 let tid = self
3810 .get_texture_id(image_name)
3811 .or_else(|| self.get_texture_id("__mega_atlas"));
3812 let uv_rect = self
3813 .image_uv_registry
3814 .get(image_name)
3815 .copied()
3816 .unwrap_or(Rect {
3817 x: 0.0,
3818 y: 0.0,
3819 width: 1.0,
3820 height: 1.0,
3821 });
3822 self.fill_rect_with_full_params(rect, [1.0, 1.0, 1.0, 1.0], 2, tid, 0.0, uv_rect);
3823 }
3824
3825 fn draw_text(&mut self, text: &str, x: f32, y: f32, size: f32, color: [f32; 4]) {
3826 let scaled_size = size * self.current_scale_factor();
3828 let shaped = self.shape_text_with_stack(text, scaled_size);
3829 let c = self.apply_opacity(color);
3830
3831 for glyph in shaped.glyphs {
3832 let cache_key = glyph.cache_key;
3833
3834 let (uv_rect, w, h) = if let Some(info) = self.text_cache.get(&cache_key) {
3835 *info
3836 } else {
3837 if let Some(image) = self.text_engine.rasterize(cache_key) {
3838 let gw = image.width;
3839 let gh = image.height;
3840
3841 let pack_res = self.atlas_packer.pack(gw, gh);
3842 let (nx, ny) = if let Some(pos) = pack_res {
3843 pos
3844 } else {
3845 self.reclaim_vram();
3847 self.atlas_packer.pack(gw, gh).unwrap_or((0, 0))
3848 };
3849
3850 let mut rgba_data = Vec::with_capacity((gw * gh * 4) as usize);
3851 for alpha in &image.data {
3852 rgba_data.push(255);
3853 rgba_data.push(255);
3854 rgba_data.push(255);
3855 rgba_data.push(*alpha);
3856 }
3857
3858 self.queue.write_texture(
3859 wgpu::TexelCopyTextureInfo {
3860 texture: &self.mega_atlas_tex,
3861 mip_level: 0,
3862 origin: wgpu::Origin3d { x: nx, y: ny, z: 0 },
3863 aspect: wgpu::TextureAspect::All,
3864 },
3865 &rgba_data,
3866 wgpu::TexelCopyBufferLayout {
3867 offset: 0,
3868 bytes_per_row: Some(gw * 4),
3869 rows_per_image: Some(gh),
3870 },
3871 wgpu::Extent3d {
3872 width: gw,
3873 height: gh,
3874 depth_or_array_layers: 1,
3875 },
3876 );
3877
3878 let info = (
3879 Rect {
3880 x: nx as f32 / 4096.0,
3881 y: ny as f32 / 4096.0,
3882 width: gw as f32 / 4096.0,
3883 height: gh as f32 / 4096.0,
3884 },
3885 gw as f32,
3886 gh as f32,
3887 );
3888 self.text_cache.put(cache_key, info);
3889 info
3890 } else {
3891 (Rect::zero(), 0.0, 0.0)
3892 }
3893 };
3894
3895 if w > 0.0 {
3896 let glyph_rect = Rect {
3899 x: x + glyph.x / self.current_scale_factor(),
3900 y: y + glyph.y / self.current_scale_factor(),
3901 width: w / self.current_scale_factor(),
3902 height: h / self.current_scale_factor(),
3903 };
3904 let tid = self.get_texture_id("__mega_atlas");
3905 self.fill_rect_with_full_params(glyph_rect, c, 6, tid, 0.0, uv_rect);
3906 }
3907 }
3908 }
3909
3910 fn measure_text(&mut self, text: &str, size: f32) -> (f32, f32) {
3912 let shaped = self.shape_text_with_stack(text, size);
3913 (shaped.width, shaped.height)
3914 }
3915
3916 fn shape_rich_text(
3917 &mut self,
3918 spans: &[cvkg_runic_text::TextSpan],
3919 max_width: Option<f32>,
3920 align: cvkg_runic_text::TextAlign,
3921 overflow: cvkg_runic_text::TextOverflow,
3922 ) -> Option<cvkg_runic_text::ShapedText> {
3923 let sf = self.current_scale_factor();
3924 let mut scaled_spans = spans.to_vec();
3925 for span in &mut scaled_spans {
3926 span.style.font_size *= sf;
3927 if span.style.fallback_families.is_empty() {
3928 span.style.fallback_families = vec![
3929 "SF Pro".to_string(),
3930 "Inter".to_string(),
3931 "Helvetica Neue".to_string(),
3932 "Helvetica".to_string(),
3933 "Arial".to_string(),
3934 "sans-serif".to_string(),
3935 ];
3936 }
3937 }
3938 let scaled_max_width = max_width.map(|w| w * sf);
3939 self.text_engine
3940 .shape_layout(&scaled_spans, scaled_max_width, align, overflow)
3941 .ok()
3942 }
3943
3944 fn draw_shaped_text(&mut self, shaped: &cvkg_runic_text::ShapedText, x: f32, y: f32) {
3945 for glyph in &shaped.glyphs {
3946 let byte_idx = shaped
3947 .grapheme_boundaries
3948 .get(glyph.cluster as usize)
3949 .copied()
3950 .unwrap_or(0);
3951 let mut span_color = [1.0, 1.0, 1.0, 1.0];
3952 for span in &shaped.spans {
3953 if byte_idx >= span.byte_offset && byte_idx < span.byte_offset + span.text.len() {
3954 span_color = [
3955 span.style.color[0] as f32 / 255.0,
3956 span.style.color[1] as f32 / 255.0,
3957 span.style.color[2] as f32 / 255.0,
3958 span.style.color[3] as f32 / 255.0,
3959 ];
3960 break;
3961 }
3962 }
3963 let c = self.apply_opacity(span_color);
3964
3965 let cache_key = glyph.cache_key;
3966 let (uv_rect, w, h) = if let Some(info) = self.text_cache.get(&cache_key) {
3967 *info
3968 } else {
3969 if let Some(image) = self.text_engine.rasterize(cache_key) {
3970 let gw = image.width;
3971 let gh = image.height;
3972
3973 let pack_res = self.atlas_packer.pack(gw, gh);
3974 let (nx, ny) = if let Some(pos) = pack_res {
3975 pos
3976 } else {
3977 self.reclaim_vram();
3978 self.atlas_packer.pack(gw, gh).unwrap_or((0, 0))
3979 };
3980
3981 let mut rgba_data = Vec::with_capacity((gw * gh * 4) as usize);
3982 for alpha in &image.data {
3983 rgba_data.push(255);
3984 rgba_data.push(255);
3985 rgba_data.push(255);
3986 rgba_data.push(*alpha);
3987 }
3988
3989 self.queue.write_texture(
3990 wgpu::TexelCopyTextureInfo {
3991 texture: &self.mega_atlas_tex,
3992 mip_level: 0,
3993 origin: wgpu::Origin3d { x: nx, y: ny, z: 0 },
3994 aspect: wgpu::TextureAspect::All,
3995 },
3996 &rgba_data,
3997 wgpu::TexelCopyBufferLayout {
3998 offset: 0,
3999 bytes_per_row: Some(gw * 4),
4000 rows_per_image: Some(gh),
4001 },
4002 wgpu::Extent3d {
4003 width: gw,
4004 height: gh,
4005 depth_or_array_layers: 1,
4006 },
4007 );
4008
4009 let info = (
4010 Rect {
4011 x: nx as f32 / 4096.0,
4012 y: ny as f32 / 4096.0,
4013 width: gw as f32 / 4096.0,
4014 height: gh as f32 / 4096.0,
4015 },
4016 gw as f32,
4017 gh as f32,
4018 );
4019 self.text_cache.put(cache_key, info);
4020 info
4021 } else {
4022 (Rect::zero(), 0.0, 0.0)
4023 }
4024 };
4025
4026 if w > 0.0 {
4027 let sf = self.current_scale_factor();
4028 let glyph_rect = Rect {
4029 x: x + glyph.x / sf,
4030 y: y + glyph.y / sf,
4031 width: w / sf,
4032 height: h / sf,
4033 };
4034 let tid = self.get_texture_id("__mega_atlas");
4035 self.fill_rect_with_full_params(glyph_rect, c, 6, tid, 0.0, uv_rect);
4036 }
4037 }
4038 }
4039
4040 fn draw_texture(&mut self, texture_id: u32, rect: Rect) {
4041 self.fill_rect_with_full_params(
4042 rect,
4043 [1.0, 1.0, 1.0, 1.0],
4044 2,
4045 Some(texture_id),
4046 0.0,
4047 Rect {
4048 x: 0.0,
4049 y: 0.0,
4050 width: 1.0,
4051 height: 1.0,
4052 },
4053 );
4054 }
4055
4056 fn load_image(&mut self, name: &str, data: &[u8]) {
4059 if self.image_uv_registry.contains(name) {
4060 return;
4061 }
4062 let img_result = image::load_from_memory(data);
4063 let img = match img_result {
4064 Ok(img) => img.to_rgba8(),
4065 Err(e) => {
4066 log::error!("Failed to load image {}: {}", name, e);
4067 image::RgbaImage::from_pixel(1, 1, image::Rgba([0, 0, 0, 255]))
4068 }
4069 };
4070 let (width, height) = img.dimensions();
4071
4072 let size = wgpu::Extent3d {
4073 width,
4074 height,
4075 depth_or_array_layers: 1,
4076 };
4077 let texture = self.device.create_texture(&wgpu::TextureDescriptor {
4078 label: Some(&format!("Texture Array Layer: {}", name)),
4079 size,
4080 mip_level_count: 1,
4081 sample_count: 1,
4082 dimension: wgpu::TextureDimension::D2,
4083 format: wgpu::TextureFormat::Rgba8UnormSrgb,
4084 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
4085 view_formats: &[],
4086 });
4087
4088 self.queue.write_texture(
4089 wgpu::TexelCopyTextureInfo {
4090 texture: &texture,
4091 mip_level: 0,
4092 origin: wgpu::Origin3d::ZERO,
4093 aspect: wgpu::TextureAspect::All,
4094 },
4095 &img,
4096 wgpu::TexelCopyBufferLayout {
4097 offset: 0,
4098 bytes_per_row: Some(4 * width),
4099 rows_per_image: Some(height),
4100 },
4101 size,
4102 );
4103
4104 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
4105
4106 let index = if self.texture_registry.len() < 255 {
4108 (self.texture_registry.len() + 1) as u32
4109 } else {
4110 if let Some((old_name, old_index)) = self.texture_registry.pop_lru() {
4112 self.image_uv_registry.pop(&old_name);
4113 old_index
4114 } else {
4115 1 }
4117 };
4118
4119 self.texture_views[index as usize] = view;
4120 self.image_uv_registry.put(
4121 name.to_string(),
4122 Rect {
4123 x: 0.0,
4124 y: 0.0,
4125 width: 1.0,
4126 height: 1.0,
4127 },
4128 );
4129 self.texture_registry.put(name.to_string(), index);
4130 self.rebuild_texture_array_bind_group();
4131 }
4132
4133 fn push_clip_rect(&mut self, rect: Rect) {
4134 self.clip_stack.push(rect);
4135 }
4136
4137 fn pop_clip_rect(&mut self) {
4138 self.clip_stack.pop();
4139 }
4140
4141 fn current_clip_rect(&self) -> Rect {
4142 self.clip_stack.last().copied().unwrap_or(Rect::new(
4143 0.0,
4144 0.0,
4145 self.current_width() as f32,
4146 self.current_height() as f32,
4147 ))
4148 }
4149
4150 fn memoize(&mut self, _id: u64, _data_hash: u64, render_fn: &dyn Fn(&mut dyn Renderer)) {
4151 render_fn(self);
4152 }
4153
4154 fn push_opacity(&mut self, opacity: f32) {
4155 let current = self.opacity_stack.last().copied().unwrap_or(1.0);
4156 self.opacity_stack.push(current * opacity);
4157 }
4158
4159 fn pop_opacity(&mut self) {
4160 self.opacity_stack.pop();
4161 }
4162
4163 fn push_shadow(&mut self, radius: f32, color: [f32; 4], offset: [f32; 2]) {
4164 self.shadow_stack.push(ShadowState {
4165 radius,
4166 color,
4167 _offset: offset,
4168 });
4169 }
4170
4171 fn pop_shadow(&mut self) {
4172 self.shadow_stack.pop();
4173 }
4174
4175 fn push_transform(&mut self, translation: [f32; 2], scale: [f32; 2], rotation: f32) {
4176 let c = rotation.cos();
4177 let sn = rotation.sin();
4178 let affine = glam::Mat3::from_cols(
4179 glam::Vec3::new(c * scale[0], sn * scale[0], 0.0),
4180 glam::Vec3::new(-sn * scale[1], c * scale[1], 0.0),
4181 glam::Vec3::new(translation[0], translation[1], 1.0),
4182 );
4183
4184 let parent = self
4185 .transform_stack
4186 .last()
4187 .copied()
4188 .unwrap_or(glam::Mat3::IDENTITY);
4189 self.transform_stack.push(parent * affine);
4190 }
4191
4192 fn push_affine(&mut self, transform: [f32; 6]) {
4193 let affine = glam::Mat3::from_cols(
4194 glam::Vec3::new(transform[0], transform[1], 0.0),
4195 glam::Vec3::new(transform[2], transform[3], 0.0),
4196 glam::Vec3::new(transform[4], transform[5], 1.0),
4197 );
4198 let parent = self
4199 .transform_stack
4200 .last()
4201 .copied()
4202 .unwrap_or(glam::Mat3::IDENTITY);
4203 self.transform_stack.push(parent * affine);
4204 }
4205
4206 fn pop_transform(&mut self) {
4207 self.transform_stack.pop();
4208 }
4209
4210 fn set_theme(&mut self, theme: ColorTheme) {
4211 self.current_theme = theme;
4212 self.queue
4213 .write_buffer(&self.theme_buffer, 0, bytemuck::bytes_of(&theme));
4214 }
4215
4216 fn set_rage(&mut self, rage: f32) {
4217 self.current_scene.berzerker_rage = rage;
4218 }
4220
4221 fn trigger_shatter_event(&mut self, origin: [f32; 2], force: f32) {
4222 self.current_scene.shatter_origin = origin;
4223 self.current_scene.shatter_time = self.current_scene.time;
4224 self.current_scene.shatter_force = force;
4225 }
4226
4227 fn set_scene_preset(&mut self, preset: u32) {
4228 self.current_scene.scene_type = preset;
4229 }
4230
4231 fn push_mjolnir_slice(&mut self, angle: f32, offset: f32) {
4234 self.slice_stack.push((angle, offset));
4235 }
4236
4237 fn pop_mjolnir_slice(&mut self) {
4239 self.slice_stack.pop();
4240 }
4241
4242 fn mjolnir_shatter(&mut self, rect: Rect, pieces: u32, force: f32, color: [f32; 4]) {
4243 self.shatter_internal(rect, pieces, force, color, 8);
4244 }
4245
4246 fn mjolnir_fluid_shatter(&mut self, rect: Rect, pieces: u32, force: f32, color: [f32; 4]) {
4247 self.shatter_internal(rect, pieces, force, color, 11);
4248 }
4249
4250 fn draw_mjolnir_bolt(&mut self, from: [f32; 2], to: [f32; 2], color: [f32; 4]) {
4251 self.recursive_bolt(from, to, 4, color);
4252 }
4253
4254 fn upload_data_texture(&mut self, id: &str, data: &[f32], width: u32, height: u32) {
4255 let size = wgpu::Extent3d {
4256 width,
4257 height,
4258 depth_or_array_layers: 1,
4259 };
4260 let texture = self.device.create_texture(&wgpu::TextureDescriptor {
4261 label: Some(id),
4262 size,
4263 mip_level_count: 1,
4264 sample_count: 1,
4265 dimension: wgpu::TextureDimension::D2,
4266 format: wgpu::TextureFormat::R32Float,
4267 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
4268 view_formats: &[],
4269 });
4270 self.queue.write_texture(
4271 wgpu::TexelCopyTextureInfo {
4272 texture: &texture,
4273 mip_level: 0,
4274 origin: wgpu::Origin3d::ZERO,
4275 aspect: wgpu::TextureAspect::All,
4276 },
4277 bytemuck::cast_slice(data),
4278 wgpu::TexelCopyBufferLayout {
4279 offset: 0,
4280 bytes_per_row: Some(4 * width),
4281 rows_per_image: Some(height),
4282 },
4283 size,
4284 );
4285 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
4286 let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
4287 address_mode_u: wgpu::AddressMode::ClampToEdge,
4288 address_mode_v: wgpu::AddressMode::ClampToEdge,
4289 mag_filter: wgpu::FilterMode::Linear,
4290 ..Default::default()
4291 });
4292 let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
4293 layout: &self.texture_bind_group_layout,
4294 entries: &[
4295 wgpu::BindGroupEntry {
4296 binding: 0,
4297 resource: wgpu::BindingResource::TextureViewArray(&vec![&view; 256]),
4298 },
4299 wgpu::BindGroupEntry {
4300 binding: 1,
4301 resource: wgpu::BindingResource::Sampler(&sampler),
4302 },
4303 ],
4304 label: Some(id),
4305 });
4306 self.texture_bind_groups.push(bind_group);
4307 let tid = (self.texture_bind_groups.len() - 1) as u32;
4308 self.texture_registry.put(id.to_string(), tid);
4309 }
4310
4311 fn draw_heatmap(&mut self, texture_id: &str, rect: Rect, _palette: &str) {
4312 let tid = self.get_texture_id(texture_id);
4313 self.fill_rect_with_mode(rect, [1.0, 1.0, 1.0, 1.0], 12, tid);
4314 }
4315
4316 fn draw_mesh(&mut self, mesh: &Mesh, color: [f32; 4], transform: glam::Mat4) {
4317 let base_idx = self.vertices.len() as u32;
4318 let screen = [self.current_width() as f32, self.current_height() as f32];
4319
4320 for i in 0..mesh.vertices.len() {
4321 let pos = transform.transform_point3(glam::Vec3::from(mesh.vertices[i]));
4322 let norm = transform.transform_vector3(glam::Vec3::from(mesh.normals[i]));
4323
4324 let (translation, scale_transform, rotation, _, _) = self.current_transform();
4325 self.vertices.push(Vertex {
4326 position: pos.to_array(),
4327 normal: norm.to_array(),
4328 uv: [0.0, 0.0],
4329 color,
4330 mode: 13, radius: 0.0,
4332 slice: [0.0, 0.0, 0.0, 1.0],
4333 logical: [0.0, 0.0],
4334 size: [0.0, 0.0],
4335 screen,
4336 clip: [-10000.0, -10000.0, 20000.0, 20000.0],
4337 translation,
4338 scale: scale_transform,
4339 rotation,
4340 tex_index: 0,
4341 });
4342 }
4343
4344 for idx in &mesh.indices {
4345 self.indices.push(base_idx + idx);
4346 }
4347
4348 if self.draw_calls.is_empty() || self.current_texture_id.is_some() {
4349 self.current_texture_id = None;
4350 self.draw_calls.push(DrawCall {
4351 texture_id: None,
4352 scissor_rect: self.clip_stack.last().copied(),
4353 index_start: (self.indices.len() as u32) - (mesh.indices.len() as u32),
4354 index_count: mesh.indices.len() as u32,
4355 material: cvkg_core::DrawMaterial::Opaque,
4356 });
4357 } else {
4358 self.draw_calls.last_mut().unwrap().index_count += mesh.indices.len() as u32;
4359 }
4360 }
4361
4362 fn register_shared_element(&mut self, id: &str, rect: Rect) {
4363 self.shared_elements.put(id.to_string(), rect);
4364 }
4365
4366 fn set_z_index(&mut self, z: f32) {
4367 self.current_z = z;
4368 }
4369
4370 fn set_material(&mut self, material: cvkg_core::DrawMaterial) {
4371 self.current_draw_material = material;
4372 }
4373
4374 fn current_material(&self) -> cvkg_core::DrawMaterial {
4375 self.current_draw_material
4376 }
4377
4378 fn get_z_index(&self) -> f32 {
4379 self.current_z
4380 }
4381
4382 fn request_redraw(&mut self) {
4383 self.redraw_requested = true;
4384 }
4385
4386 fn push_vnode(&mut self, rect: Rect, name: &'static str) {
4387 self.vnode_stack.push((rect, name));
4388 }
4389
4390 fn pop_vnode(&mut self) {
4391 self.vnode_stack.pop();
4392 }
4393
4394 fn register_handler(
4395 &mut self,
4396 event_type: &str,
4397 handler: std::sync::Arc<dyn Fn(cvkg_core::Event) + Send + Sync>,
4398 ) {
4399 self.event_handlers
4400 .entry(event_type.to_string())
4401 .or_insert_with(Vec::new)
4402 .push(handler);
4403 }
4404
4405 fn serialize_svg(&mut self, name: &str) -> Result<String, String> {
4406 let tree = self
4407 .svg_trees
4408 .get(name)
4409 .ok_or_else(|| format!("SVG '{}' not found", name))?;
4410 let config = cvkg_svg_serialize::SerializerConfig::default();
4411 let mut serializer = cvkg_svg_serialize::SvgSerializer::with_config(config);
4412 serializer
4413 .serialize(tree)
4414 .map_err(|e| format!("SVG serialization failed: {}", e))
4415 }
4416
4417 fn apply_svg_filter(
4418 &mut self,
4419 name: &str,
4420 filter_id: &str,
4421 _region: Rect,
4422 ) -> Result<String, String> {
4423 let tree = self
4424 .svg_trees
4425 .get(name)
4426 .ok_or_else(|| format!("SVG '{}' not found", name))?;
4427 let _filter = Self::find_filter(tree, filter_id)
4428 .ok_or_else(|| format!("Filter '{}' not found in SVG '{}'", filter_id, name))?;
4429 let config = cvkg_svg_serialize::SerializerConfig::default();
4430 let mut serializer = cvkg_svg_serialize::SvgSerializer::with_config(config);
4431 serializer
4432 .serialize(tree)
4433 .map_err(|e| format!("SVG filter serialization failed: {}", e))
4434 }
4435}
4436
4437impl SurtrRenderer {
4440 pub fn clear_event_handlers(&mut self) {
4443 self.event_handlers.clear();
4444 }
4445
4446 pub fn get_handlers(
4448 &self,
4449 event_type: &str,
4450 ) -> Option<&Vec<std::sync::Arc<dyn Fn(cvkg_core::Event) + Send + Sync>>> {
4451 self.event_handlers.get(event_type)
4452 }
4453
4454 pub(crate) fn current_transform(&self) -> ([f32; 2], [f32; 2], f32, f32, f32) {
4458 let m = self
4460 .transform_stack
4461 .last()
4462 .copied()
4463 .unwrap_or(glam::Mat3::IDENTITY);
4464 let t = [m.z_axis.x, m.z_axis.y];
4465 let a = m.x_axis.x;
4467 let b = m.x_axis.y;
4468 let c = m.y_axis.x;
4469 let d = m.y_axis.y;
4470 let sx = (a * a + b * b).sqrt();
4471 let sy = (c * c + d * d).sqrt();
4472 let rotation = b.atan2(a);
4473 let skew_x = (a * c + b * d) / (sx * sy); (t, [sx, sy], rotation, skew_x, 0.0)
4476 }
4477
4478 pub fn stroke_path(&mut self, path: &lyon::path::Path, color: [f32; 4], stroke_width: f32) {
4479 let c = self.apply_opacity(color);
4480 let mut tessellator = StrokeTessellator::new();
4481 let mut buffers: VertexBuffers<Vertex, u32> = VertexBuffers::new();
4482 let base_vertex_idx = self.vertices.len() as u32;
4483
4484 let (translation, scale, rotation, _, _) = self.current_transform();
4485 let screen = [self.current_width() as f32, self.current_height() as f32];
4486 let clip_rect = self.clip_stack.last().copied().unwrap_or(cvkg_core::Rect {
4487 x: -10000.0,
4488 y: -10000.0,
4489 width: 20000.0,
4490 height: 20000.0,
4491 });
4492 let clip = [clip_rect.x, clip_rect.y, clip_rect.width, clip_rect.height];
4493
4494 tessellator
4495 .tessellate_path(
4496 path,
4497 &StrokeOptions::default().with_line_width(stroke_width),
4498 &mut BuffersBuilder::new(
4499 &mut buffers,
4500 CustomStrokeVertexConstructor {
4501 color: c,
4502 translation,
4503 scale,
4504 rotation,
4505 screen,
4506 clip,
4507 },
4508 ),
4509 )
4510 .unwrap();
4511
4512 self.vertices.extend(buffers.vertices);
4513 for idx in &buffers.indices {
4514 self.indices.push(base_vertex_idx + *idx);
4515 }
4516
4517 let material = self.current_material();
4518 let tid = self.get_texture_id("__mega_atlas");
4519
4520 let last_call = self.draw_calls.last();
4521 let needs_new_call = self.draw_calls.is_empty()
4522 || self.current_texture_id != tid
4523 || last_call.unwrap().scissor_rect != self.clip_stack.last().copied()
4524 || last_call.unwrap().material != material;
4525
4526 if needs_new_call {
4527 self.current_texture_id = tid;
4528 self.draw_calls.push(DrawCall {
4529 texture_id: tid,
4530 scissor_rect: self.clip_stack.last().copied(),
4531 index_start: base_vertex_idx,
4532 index_count: buffers.indices.len() as u32,
4533 material,
4534 });
4535 } else if let Some(call) = self.draw_calls.last_mut() {
4536 call.index_count += buffers.indices.len() as u32;
4537 }
4538 }
4539}
4540
4541pub fn parse_svg_animations(data: &[u8]) -> Vec<SvgAnimation> {
4542 let mut parsed_animations = Vec::new();
4543 if let Ok(xml_doc) = roxmltree::Document::parse(std::str::from_utf8(data).unwrap_or("")) {
4544 for node in xml_doc.descendants() {
4545 if node.tag_name().name() == "animateTransform" || node.tag_name().name() == "animate" {
4546 let target_id = node
4547 .attribute("href")
4548 .or_else(|| node.attribute(("http://www.w3.org/1999/xlink", "href")))
4549 .or_else(|| node.attribute("xlink:href"))
4550 .or_else(|| node.parent_element().and_then(|p| p.attribute("id")))
4551 .unwrap_or("")
4552 .trim_start_matches('#')
4553 .to_string();
4554
4555 if !target_id.is_empty() {
4556 let dur_str = node.attribute("dur").unwrap_or("1s");
4557 let duration = if dur_str.ends_with("ms") {
4558 dur_str
4559 .trim_end_matches("ms")
4560 .parse::<f32>()
4561 .unwrap_or(1000.0)
4562 / 1000.0
4563 } else {
4564 dur_str.trim_end_matches('s').parse::<f32>().unwrap_or(1.0)
4565 };
4566
4567 let (from_val, to_val) = if let Some(values) = node.attribute("values") {
4568 let parts: Vec<&str> = values.split(';').collect();
4569 if parts.len() >= 2 {
4570 let f = parts[0].trim().parse::<f32>().unwrap_or(0.0);
4571 let t = parts[parts.len() - 1].trim().parse::<f32>().unwrap_or(0.0);
4572 (f, t)
4573 } else {
4574 (0.0, 360.0) }
4576 } else {
4577 let f = node
4578 .attribute("from")
4579 .unwrap_or("0")
4580 .parse::<f32>()
4581 .unwrap_or(0.0);
4582 let t = node
4583 .attribute("to")
4584 .unwrap_or("360")
4585 .parse::<f32>()
4586 .unwrap_or(360.0);
4587 (f, t)
4588 };
4589
4590 let attr = node
4591 .attribute("attributeName")
4592 .unwrap_or("transform")
4593 .to_string();
4594
4595 parsed_animations.push(SvgAnimation {
4596 target_id,
4597 attribute_name: attr,
4598 from_val,
4599 to_val,
4600 duration,
4601 vertex_range: 0..0, });
4603 }
4604 }
4605 }
4606 }
4607 parsed_animations
4608}
4609
4610fn usvg_to_lyon(path: &usvg::Path) -> lyon::path::Path {
4613 let mut builder = lyon::path::Path::builder();
4614 for segment in path.data().segments() {
4615 match segment {
4616 usvg::tiny_skia_path::PathSegment::MoveTo(p) => {
4617 builder.begin(lyon::math::point(p.x, p.y));
4618 }
4619 usvg::tiny_skia_path::PathSegment::LineTo(p) => {
4620 builder.line_to(lyon::math::point(p.x, p.y));
4621 }
4622 usvg::tiny_skia_path::PathSegment::QuadTo(p1, p) => {
4623 builder.quadratic_bezier_to(
4624 lyon::math::point(p1.x, p1.y),
4625 lyon::math::point(p.x, p.y),
4626 );
4627 }
4628 usvg::tiny_skia_path::PathSegment::CubicTo(p1, p2, p) => {
4629 builder.cubic_bezier_to(
4630 lyon::math::point(p1.x, p1.y),
4631 lyon::math::point(p2.x, p2.y),
4632 lyon::math::point(p.x, p.y),
4633 );
4634 }
4635 usvg::tiny_skia_path::PathSegment::Close => {
4636 builder.end(true);
4637 }
4638 }
4639 }
4640 builder.build()
4641}
4642
4643struct SceneVertexConstructor {
4644 color: [f32; 4],
4645 translation: [f32; 2],
4646 scale: [f32; 2],
4647 rotation: f32,
4648}
4649
4650struct CustomStrokeVertexConstructor {
4652 color: [f32; 4],
4653 translation: [f32; 2],
4654 scale: [f32; 2],
4655 rotation: f32,
4656 screen: [f32; 2],
4657 clip: [f32; 4],
4658}
4659
4660impl StrokeVertexConstructor<Vertex> for CustomStrokeVertexConstructor {
4661 fn new_vertex(&mut self, vertex: StrokeVertex) -> Vertex {
4662 let pos = vertex.position();
4663 Vertex {
4664 position: [pos.x, pos.y, 0.0],
4665 normal: [0.0, 0.0, 1.0],
4666 uv: [0.0, 0.0],
4667 color: self.color,
4668 mode: 0,
4669 radius: 0.0,
4670 slice: [0.0, 0.0, 0.0, 1.0],
4671 logical: [pos.x, pos.y],
4672 size: [1.0, 1.0],
4673 screen: self.screen,
4674 clip: self.clip,
4675 translation: self.translation,
4676 scale: self.scale,
4677 rotation: self.rotation,
4678 tex_index: 0,
4679 }
4680 }
4681}
4682
4683impl FillVertexConstructor<Vertex> for SceneVertexConstructor {
4684 fn new_vertex(&mut self, vertex: FillVertex) -> Vertex {
4685 Vertex {
4686 position: [vertex.position().x, vertex.position().y, 0.0],
4687 normal: [0.0, 0.0, 1.0],
4688 uv: [0.0, 0.0],
4689 color: self.color,
4690 mode: 0,
4691 radius: 0.0,
4692 slice: [0.0, 0.0, 0.0, 1.0],
4693 logical: [vertex.position().x, vertex.position().y],
4694 size: [1.0, 1.0],
4695 screen: [0.0, 0.0],
4696 clip: [-10000.0, -10000.0, 20000.0, 20000.0],
4697 translation: self.translation,
4698 scale: self.scale,
4699 rotation: self.rotation,
4700 tex_index: 0,
4701 }
4702 }
4703}
4704
4705impl Drop for SurtrRenderer {
4706 fn drop(&mut self) {
4707 let _ = self.device.poll(wgpu::PollType::Wait {
4709 submission_index: None,
4710 timeout: None,
4711 });
4712 }
4713}
4714
4715impl SurtrRenderer {
4716 pub fn submit_buckets(&mut self, buckets: &cvkg_compositor::CommandBuckets) {
4725 for routed in &buckets.scene_commands {
4727 self.set_material(cvkg_core::DrawMaterial::Opaque);
4728 self.submit_routed(routed);
4729 }
4730
4731 for routed in &buckets.glass_commands {
4733 let core_material = match routed.material {
4734 cvkg_compositor::Material::Opaque => cvkg_core::DrawMaterial::Opaque,
4735 cvkg_compositor::Material::Glass {
4736 blur_radius,
4737 depth_index: _,
4738 } => cvkg_core::DrawMaterial::Glass { blur_radius },
4739 cvkg_compositor::Material::Overlay => cvkg_core::DrawMaterial::TopUI,
4740 _ => cvkg_core::DrawMaterial::Opaque,
4741 };
4742 self.set_material(core_material);
4743 self.submit_routed(routed);
4744 }
4745
4746 for routed in &buckets.overlay_commands {
4748 self.set_material(cvkg_core::DrawMaterial::TopUI);
4749 self.submit_routed(routed);
4750 }
4751 }
4752
4753 fn submit_routed(&mut self, routed: &cvkg_compositor::RoutedDrawCommand) {
4755 let cmd = &routed.command;
4756 self.fill_rect_with_full_params(
4757 cvkg_core::Rect::new(0.0, 0.0, 1.0, 1.0),
4758 [1.0, 1.0, 1.0, 1.0],
4759 0,
4760 cmd.texture_id,
4761 0.0,
4762 cvkg_core::Rect::new(0.0, 0.0, 1.0, 1.0),
4763 );
4764 }
4765}
4766
4767impl cvkg_core::FrameRenderer<wgpu::CommandEncoder> for SurtrRenderer {
4768 fn begin_frame(&mut self) -> wgpu::CommandEncoder {
4769 cvkg_core::begin_render_phase();
4770 let id = self
4771 .current_window
4772 .expect("No target window set for frame. Call set_target_window first.");
4773 self.begin_frame(id)
4774 }
4775
4776 fn render_frame(&mut self) {
4777 if LAYOUT_DIRTY.swap(false, Ordering::AcqRel)
4780 && let Some(window_id) = self.current_window
4781 && let Some(surface_ctx) = self.surfaces.get(&window_id)
4782 {
4783 let w = surface_ctx.config.width as f32;
4784 let h = surface_ctx.config.height as f32;
4785 let border_rect = cvkg_core::Rect {
4786 x: 0.0,
4787 y: 0.0,
4788 width: w,
4789 height: h,
4790 };
4791 self.stroke_rect(border_rect, [1.0, 0.0, 0.0, 1.0], 10.0);
4793 }
4794
4795 let req_v_size = (self.vertices.len() * std::mem::size_of::<Vertex>()) as u64;
4797 let mut cur_v_size = self.vertex_buffer.size();
4798 let max_v_size = (MAX_VERTICES * std::mem::size_of::<Vertex>()) as u64 * 4;
4799
4800 if req_v_size > cur_v_size {
4801 while cur_v_size < req_v_size && cur_v_size < max_v_size {
4802 cur_v_size *= 2;
4803 }
4804 if req_v_size > max_v_size {
4805 log::error!("Exceeded dynamic vertex buffer max capacity! Capping geometry.");
4806 self.vertices
4807 .truncate((max_v_size / std::mem::size_of::<Vertex>() as u64) as usize);
4808 cur_v_size = max_v_size;
4809 }
4810 log::info!("Growing vertex buffer to {} bytes", cur_v_size);
4811 self.vertex_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
4812 label: Some("Vertex Buffer (Grown)"),
4813 size: cur_v_size,
4814 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
4815 mapped_at_creation: false,
4816 });
4817 }
4818
4819 let req_i_size = (self.indices.len() * std::mem::size_of::<u32>()) as u64;
4820 let mut cur_i_size = self.index_buffer.size();
4821 let max_i_size = (MAX_INDICES * std::mem::size_of::<u32>()) as u64 * 4;
4822
4823 if req_i_size > cur_i_size {
4824 while cur_i_size < req_i_size && cur_i_size < max_i_size {
4825 cur_i_size *= 2;
4826 }
4827 if req_i_size > max_i_size {
4828 log::error!("Exceeded dynamic index buffer max capacity! Capping geometry.");
4829 self.indices
4830 .truncate((max_i_size / std::mem::size_of::<u32>() as u64) as usize);
4831 cur_i_size = max_i_size;
4832 }
4833 log::info!("Growing index buffer to {} bytes", cur_i_size);
4834 self.index_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
4835 label: Some("Index Buffer (Grown)"),
4836 size: cur_i_size,
4837 usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
4838 mapped_at_creation: false,
4839 });
4840 }
4841
4842 let mut staging_encoder =
4844 self.device
4845 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
4846 label: Some("Surtr Staging Encoder"),
4847 });
4848
4849 let mut has_writes = false;
4850
4851 if !self.vertices.is_empty() {
4852 let v_bytes = bytemuck::cast_slice(&self.vertices);
4853 self.staging_belt
4854 .write_buffer(
4855 &mut staging_encoder,
4856 &self.vertex_buffer,
4857 0,
4858 wgpu::BufferSize::new(v_bytes.len() as u64).unwrap(),
4859 )
4860 .copy_from_slice(v_bytes);
4861 has_writes = true;
4862 }
4863
4864 if !self.indices.is_empty() {
4865 let i_bytes = bytemuck::cast_slice(&self.indices);
4866 self.staging_belt
4867 .write_buffer(
4868 &mut staging_encoder,
4869 &self.index_buffer,
4870 0,
4871 wgpu::BufferSize::new(i_bytes.len() as u64).unwrap(),
4872 )
4873 .copy_from_slice(i_bytes);
4874 has_writes = true;
4875 }
4876
4877 if has_writes {
4878 self.staging_belt.finish();
4879 self.staging_command_buffers.push(staging_encoder.finish());
4880 }
4881
4882 self.current_scene.time = self.start_time.elapsed().as_secs_f32();
4884 self.queue.write_buffer(
4885 &self.scene_buffer,
4886 0,
4887 bytemuck::bytes_of(&self.current_scene),
4888 );
4889 self.queue.write_buffer(
4890 &self.theme_buffer,
4891 0,
4892 bytemuck::bytes_of(&self.current_theme),
4893 );
4894 }
4895
4896 fn end_frame(&mut self, encoder: wgpu::CommandEncoder) {
4897 Self::end_frame(self, encoder);
4898 cvkg_core::end_render_phase();
4899 }
4900}
4901
4902impl SurtrRenderer {
4903 fn apply_opacity(&self, mut color: [f32; 4]) -> [f32; 4] {
4905 if let Some(&alpha) = self.opacity_stack.last() {
4906 color[3] *= alpha;
4907 }
4908 color
4909 }
4910
4911 pub fn load_svg(&mut self, name: &str, data: &[u8]) {
4913 let opt = usvg::Options::default();
4914 let tree = usvg::Tree::from_data(data, &opt).expect("Failed to parse SVG");
4915
4916 let view_box = Rect {
4917 x: 0.0,
4918 y: 0.0,
4919 width: tree.size().width(),
4920 height: tree.size().height(),
4921 };
4922
4923 let parsed_animations = parse_svg_animations(data);
4924
4925 let mut vertices = Vec::new();
4926 let mut indices = Vec::new();
4927 let mut tessellator = FillTessellator::new();
4928 let mut finalized_animations = Vec::new();
4929
4930 for child in tree.root().children() {
4931 self.tessellate_node(
4932 child,
4933 &mut tessellator,
4934 &mut vertices,
4935 &mut indices,
4936 &parsed_animations,
4937 &mut finalized_animations,
4938 );
4939 }
4940
4941 self.svg_cache.put(
4942 name.to_string(),
4943 SvgModel {
4944 vertices,
4945 indices,
4946 view_box,
4947 animations: finalized_animations,
4948 },
4949 );
4950 self.svg_trees.put(name.to_string(), tree);
4951 }
4952
4953 fn tessellate_node(
4954 &self,
4955 node: &usvg::Node,
4956 tessellator: &mut FillTessellator,
4957 vertices: &mut Vec<Vertex>,
4958 indices: &mut Vec<u32>,
4959 parsed_animations: &[SvgAnimation],
4960 finalized_animations: &mut Vec<SvgAnimation>,
4961 ) {
4962 let start_idx = vertices.len();
4963 let node_id = match node {
4964 usvg::Node::Group(g) => g.id().to_string(),
4965 usvg::Node::Path(p) => p.id().to_string(),
4966 _ => String::new(),
4967 };
4968
4969 if let usvg::Node::Group(ref group) = *node {
4970 for child in group.children() {
4971 self.tessellate_node(
4972 child,
4973 tessellator,
4974 vertices,
4975 indices,
4976 parsed_animations,
4977 finalized_animations,
4978 );
4979 }
4980 } else if let usvg::Node::Path(ref path) = *node
4981 && let Some(fill) = path.fill()
4982 {
4983 let color = match fill.paint() {
4984 usvg::Paint::Color(c) => [
4985 c.red as f32 / 255.0,
4986 c.green as f32 / 255.0,
4987 c.blue as f32 / 255.0,
4988 fill.opacity().get(),
4989 ],
4990 _ => [1.0, 1.0, 1.0, 1.0],
4991 };
4992
4993 let lyon_path = usvg_to_lyon(path);
4994 let mut buffers: VertexBuffers<Vertex, u32> = VertexBuffers::new();
4995 let base_vertex_idx = vertices.len() as u32;
4996
4997 tessellator
4998 .tessellate_path(
4999 &lyon_path,
5000 &FillOptions::default(),
5001 &mut BuffersBuilder::new(
5002 &mut buffers,
5003 SceneVertexConstructor {
5004 color,
5005 translation: [0.0, 0.0],
5006 scale: [1.0, 1.0],
5007 rotation: 0.0,
5008 },
5009 ),
5010 )
5011 .unwrap();
5012
5013 vertices.extend(buffers.vertices);
5014 for idx in buffers.indices {
5015 indices.push(base_vertex_idx + idx);
5016 }
5017 }
5018
5019 let end_idx = vertices.len();
5020 if !node_id.is_empty() && start_idx < end_idx {
5021 for anim in parsed_animations {
5022 if anim.target_id == node_id {
5023 let mut final_anim = anim.clone();
5024 final_anim.vertex_range = start_idx..end_idx;
5025 finalized_animations.push(final_anim);
5026 }
5027 }
5028 }
5029 }
5030
5031 pub fn draw_svg(&mut self, name: &str, rect: Rect, color: Option<[f32; 4]>, mode: u32) {
5033 let model = if let Some(m) = self.svg_cache.get(name) {
5034 m.clone()
5035 } else {
5036 return;
5037 };
5038
5039 let _scale_x = rect.width / model.view_box.width;
5040 let _scale_y = rect.height / model.view_box.height;
5041 let base_idx = self.vertices.len() as u32;
5042 let screen = [self.current_width() as f32, self.current_height() as f32];
5043 let clip_rect = self.clip_stack.last().copied().unwrap_or(cvkg_core::Rect {
5044 x: -10000.0,
5045 y: -10000.0,
5046 width: 20000.0,
5047 height: 20000.0,
5048 });
5049 let clip = [clip_rect.x, clip_rect.y, clip_rect.width, clip_rect.height];
5050 let scale = self.current_scale_factor();
5051 let snap = |v: f32| (v * scale).round() / scale;
5052
5053 let mut local_vertices = model.vertices.clone();
5054 for anim in &model.animations {
5055 let t = (self.current_scene.time % anim.duration) / anim.duration;
5056 let val = anim.from_val + (anim.to_val - anim.from_val) * t;
5057
5058 if anim.attribute_name == "transform" {
5059 let mut min_x = f32::MAX;
5061 let mut min_y = f32::MAX;
5062 let mut max_x = f32::MIN;
5063 let mut max_y = f32::MIN;
5064 for i in anim.vertex_range.clone() {
5065 let p = local_vertices[i].position;
5066 if p[0] < min_x {
5067 min_x = p[0];
5068 }
5069 if p[1] < min_y {
5070 min_y = p[1];
5071 }
5072 if p[0] > max_x {
5073 max_x = p[0];
5074 }
5075 if p[1] > max_y {
5076 max_y = p[1];
5077 }
5078 }
5079 let cx = (min_x + max_x) * 0.5;
5080 let cy = (min_y + max_y) * 0.5;
5081
5082 let c = val.to_radians().cos();
5083 let s = val.to_radians().sin();
5084
5085 for i in anim.vertex_range.clone() {
5086 let p = local_vertices[i].position;
5087 let dx = p[0] - cx;
5088 let dy = p[1] - cy;
5089 local_vertices[i].position[0] = cx + dx * c - dy * s;
5090 local_vertices[i].position[1] = cy + dx * s + dy * c;
5091 }
5092 } else if anim.attribute_name == "opacity" {
5093 for i in anim.vertex_range.clone() {
5094 local_vertices[i].color[3] = val;
5095 }
5096 }
5097 }
5098
5099 for mut v in local_vertices {
5100 let rel_x = (v.position[0] - model.view_box.x) / model.view_box.width;
5101 let rel_y = (v.position[1] - model.view_box.y) / model.view_box.height;
5102
5103 v.position[0] = snap(rect.x + rel_x * rect.width);
5104 v.position[1] = snap(rect.y + rel_y * rect.height);
5105 v.position[2] = self.current_z;
5106 v.logical = [v.position[0], v.position[1]];
5107 v.screen = screen;
5108 v.clip = clip;
5109 v.mode = mode;
5110
5111 if let Some(override_color) = color {
5112 let mut c = override_color;
5113 c[3] *= v.color[3]; v.color = self.apply_opacity(c);
5115 } else {
5116 v.color = self.apply_opacity(v.color);
5117 }
5118 self.vertices.push(v);
5119 }
5120
5121 for idx in &model.indices {
5122 self.indices.push(base_idx + *idx);
5123 }
5124
5125 let material = if mode == 7 {
5126 cvkg_core::DrawMaterial::Glass { blur_radius: 20.0 }
5127 } else {
5128 cvkg_core::DrawMaterial::TopUI
5129 };
5130 let tid = self.get_texture_id("__mega_atlas");
5131
5132 let last_call = self.draw_calls.last();
5133 let needs_new_call = self.draw_calls.is_empty()
5134 || self.current_texture_id != tid
5135 || last_call.unwrap().scissor_rect != self.clip_stack.last().copied()
5136 || last_call.unwrap().material != material;
5137
5138 if needs_new_call {
5139 self.current_texture_id = tid;
5140 self.draw_calls.push(DrawCall {
5141 texture_id: tid,
5142 scissor_rect: self.clip_stack.last().copied(),
5143 index_start: (self.indices.len() - model.indices.len()) as u32,
5144 index_count: 0,
5145 material,
5146 });
5147 }
5148
5149 if let Some(call) = self.draw_calls.last_mut() {
5150 call.index_count += model.indices.len() as u32;
5151 }
5152 }
5153
5154 pub async fn forge_headless(width: u32, height: u32) -> Self {
5156 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
5157 backends: wgpu::Backends::all(),
5158 flags: wgpu::InstanceFlags::default(),
5159 backend_options: wgpu::BackendOptions::default(),
5160 display: None,
5161 memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
5162 });
5163
5164 println!("[GPU] Requesting HighPerformance adapter...");
5166 let mut adapter = instance
5167 .request_adapter(&wgpu::RequestAdapterOptions {
5168 power_preference: wgpu::PowerPreference::HighPerformance,
5169 compatible_surface: None,
5170 force_fallback_adapter: false,
5171 })
5172 .await
5173 .ok();
5174
5175 if adapter.is_none() {
5176 println!(
5177 "[GPU] HighPerformance adapter failed (possible Bumblebee/Optimus), trying LowPower..."
5178 );
5179 adapter = instance
5180 .request_adapter(&wgpu::RequestAdapterOptions {
5181 power_preference: wgpu::PowerPreference::LowPower,
5182 compatible_surface: None,
5183 force_fallback_adapter: false,
5184 })
5185 .await
5186 .ok();
5187 }
5188
5189 if adapter.is_none() {
5190 println!("[GPU] Hardware adapters failed, trying Software fallback...");
5191 adapter = instance
5192 .request_adapter(&wgpu::RequestAdapterOptions {
5193 power_preference: wgpu::PowerPreference::LowPower,
5194 compatible_surface: None,
5195 force_fallback_adapter: true,
5196 })
5197 .await
5198 .ok();
5199 }
5200
5201 let adapter = adapter.expect("Failed to find a suitable GPU for Surtr");
5202 let info = adapter.get_info();
5203 println!(
5204 "[GPU] Selected adapter: {} ({:?}) on backend: {:?}",
5205 info.name, info.device_type, info.backend
5206 );
5207 println!("[GPU] Driver info: {} - {}", info.driver, info.driver_info);
5208 let required_features = adapter.features()
5209 & (wgpu::Features::TIMESTAMP_QUERY
5210 | wgpu::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING
5211 | wgpu::Features::TEXTURE_BINDING_ARRAY);
5212
5213 let (device, queue) = adapter
5214 .request_device(&wgpu::DeviceDescriptor {
5215 label: Some("Surtr Headless Forge"),
5216 required_features,
5217 required_limits: wgpu::Limits {
5218 max_bindings_per_bind_group: adapter
5219 .limits()
5220 .max_bindings_per_bind_group
5221 .min(256),
5222 max_binding_array_elements_per_shader_stage: adapter
5223 .limits()
5224 .max_binding_array_elements_per_shader_stage
5225 .min(256),
5226 ..wgpu::Limits::default()
5227 },
5228 memory_hints: wgpu::MemoryHints::default(),
5229 experimental_features: wgpu::ExperimentalFeatures::disabled(),
5230 trace: wgpu::Trace::Off,
5231 })
5232 .await
5233 .expect("Failed to create Surtr device");
5234
5235 let instance = Arc::new(instance);
5236 let adapter = Arc::new(adapter);
5237
5238 device.on_uncaptured_error(Arc::new(|error| {
5239 log::error!(
5240 "[GPU] Uncaptured device error (Device Lost or Panic): {:?}",
5241 error
5242 );
5243 }));
5244
5245 let device = Arc::new(device);
5246 let queue = Arc::new(queue);
5247
5248 Self::forge_internal(
5249 instance,
5250 adapter,
5251 device,
5252 queue,
5253 None,
5254 Some((width, height, wgpu::TextureFormat::Rgba8UnormSrgb)),
5255 )
5256 .await
5257 }
5258
5259 pub async fn capture_frame(&self) -> Result<Vec<u8>, String> {
5261 let ctx = self
5262 .headless_context
5263 .as_ref()
5264 .ok_or("Headless context required for capture")?;
5265 let u32_size = std::mem::size_of::<u32>() as u32;
5266 let width = ctx.width;
5267 let height = ctx.height;
5268 let bytes_per_row = width * u32_size;
5269 let padding = (256 - (bytes_per_row % 256)) % 256;
5270 let padded_bytes_per_row = bytes_per_row + padding;
5271
5272 let output_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
5273 label: Some("Capture Buffer"),
5274 size: (padded_bytes_per_row as u64 * height as u64),
5275 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
5276 mapped_at_creation: false,
5277 });
5278
5279 let mut encoder = self
5280 .device
5281 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
5282 label: Some("Capture Encoder"),
5283 });
5284
5285 encoder.copy_texture_to_buffer(
5286 wgpu::TexelCopyTextureInfo {
5287 texture: &ctx.output_texture,
5288 mip_level: 0,
5289 origin: wgpu::Origin3d::ZERO,
5290 aspect: wgpu::TextureAspect::All,
5291 },
5292 wgpu::TexelCopyBufferInfo {
5293 buffer: &output_buffer,
5294 layout: wgpu::TexelCopyBufferLayout {
5295 offset: 0,
5296 bytes_per_row: Some(padded_bytes_per_row),
5297 rows_per_image: Some(height),
5298 },
5299 },
5300 wgpu::Extent3d {
5301 width,
5302 height,
5303 depth_or_array_layers: 1,
5304 },
5305 );
5306
5307 self.queue.submit(Some(encoder.finish()));
5308
5309 let buffer_slice = output_buffer.slice(..);
5310 let (sender, receiver) = futures::channel::oneshot::channel();
5311 buffer_slice.map_async(wgpu::MapMode::Read, move |v| {
5312 let _ = sender.send(v);
5313 });
5314
5315 let _ = self.device.poll(wgpu::PollType::Wait {
5316 submission_index: None,
5317 timeout: None,
5318 });
5319
5320 if let Ok(Ok(_)) = receiver.await {
5321 let data = buffer_slice.get_mapped_range();
5322 let mut result = Vec::with_capacity((width * height * 4) as usize);
5323
5324 for y in 0..height {
5325 let start = (y * padded_bytes_per_row) as usize;
5326 let end = start + bytes_per_row as usize;
5327 result.extend_from_slice(&data[start..end]);
5328 }
5329
5330 drop(data);
5331 output_buffer.unmap();
5332 Ok(result)
5333 } else {
5334 Err("Failed to capture frame".to_string())
5335 }
5336 }
5337
5338 fn current_width(&self) -> u32 {
5339 if let Some(id) = self.current_window {
5340 self.surfaces.get(&id).unwrap().config.width
5341 } else {
5342 self.headless_context.as_ref().unwrap().width
5343 }
5344 }
5345
5346 fn current_height(&self) -> u32 {
5347 if let Some(id) = self.current_window {
5348 self.surfaces.get(&id).unwrap().config.height
5349 } else {
5350 self.headless_context.as_ref().unwrap().height
5351 }
5352 }
5353
5354 fn current_scale_factor(&self) -> f32 {
5355 if let Some(id) = self.current_window {
5356 self.surfaces.get(&id).unwrap().scale_factor
5357 } else {
5358 self.headless_context.as_ref().unwrap().scale_factor
5359 }
5360 }
5361
5362 fn find_filter<'a>(tree: &'a usvg::Tree, filter_id: &str) -> Option<&'a usvg::filter::Filter> {
5364 tree.filters()
5365 .iter()
5366 .find(|f| f.id() == filter_id)
5367 .map(|arc| arc.as_ref())
5368 }
5369}