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