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