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