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