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