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