1#[derive(Debug, Clone)]
36struct ShelfPacker {
37 width: u32,
38 height: u32,
39 shelf_y: u32,
40 shelf_height: u32,
41 current_x: u32,
42}
43
44impl ShelfPacker {
45 fn new(width: u32, height: u32) -> Self {
46 Self { width, height, shelf_y: 0, shelf_height: 0, current_x: 0 }
47 }
48
49 fn pack(&mut self, w: u32, h: u32) -> Option<(u32, u32)> {
50 if w > self.width || h > self.height {
52 return None;
53 }
54
55 if self.current_x + w > self.width {
57 self.shelf_y += self.shelf_height;
59 self.current_x = 0;
60 self.shelf_height = 0;
61 }
62
63 if self.shelf_y + h > self.height {
64 return None; }
66
67 let pos = (self.current_x, self.shelf_y);
68 self.current_x += w;
69 if h > self.shelf_height {
70 self.shelf_height = h;
71 }
72 Some(pos)
73 }
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79
80 #[test]
81 fn test_shelf_packer_basic() {
82 let mut packer = ShelfPacker::new(100, 100);
83
84 assert_eq!(packer.pack(10, 10), Some((0, 0)));
86 assert_eq!(packer.current_x, 10);
87 assert_eq!(packer.shelf_height, 10);
88
89 assert_eq!(packer.pack(20, 15), Some((10, 0)));
91 assert_eq!(packer.current_x, 30);
92 assert_eq!(packer.shelf_height, 15);
93 }
94
95 #[test]
96 fn test_shelf_packer_wrap() {
97 let mut packer = ShelfPacker::new(100, 100);
98 packer.pack(60, 10);
99
100 assert_eq!(packer.pack(50, 20), Some((0, 10)));
102 assert_eq!(packer.current_x, 50);
103 assert_eq!(packer.shelf_y, 10);
104 assert_eq!(packer.shelf_height, 20);
105 }
106
107 #[test]
108 fn test_shelf_packer_full() {
109 let mut packer = ShelfPacker::new(10, 10);
110 assert_eq!(packer.pack(11, 5), None);
111 assert_eq!(packer.pack(5, 11), None);
112 }
113}
114
115use cvkg_core::{Mesh, Rect, Renderer, LAYOUT_DIRTY};
116use std::sync::Arc;
117use std::sync::atomic::Ordering;
118include!(concat!(env!("OUT_DIR"), "/shader_spirv.rs"));
119
120
121#[derive(Clone, Debug)]
123pub struct SvgModel {
124 pub vertices: Vec<Vertex>,
125 pub indices: Vec<u32>,
126 pub view_box: Rect,
127}
128
129pub use accesskit::{
132 ActionHandler, ActionRequest, ActivationHandler, DeactivationHandler, Node, NodeId, Role, Tree,
133 TreeId, TreeUpdate,
134};
135pub use accesskit_winit::Adapter as ShieldWallAdapter;
136
137pub use cvkg_core::{
139 ColorTheme, SceneUniforms,
140};
141
142use lyon::tessellation::{
143 FillOptions, FillTessellator, FillVertex, FillVertexConstructor, VertexBuffers, BuffersBuilder
144};
145
146#[repr(C)]
147#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
148pub struct Vertex {
149 pub position: [f32; 3],
150 pub normal: [f32; 3],
151 pub uv: [f32; 2],
152 pub color: [f32; 4],
153 pub mode: u32,
154 pub radius: f32,
155 pub slice: [f32; 4],
156 pub logical: [f32; 2],
157 pub size: [f32; 2],
158 pub screen: [f32; 2],
159 pub clip: [f32; 4], pub translation: [f32; 2],
161 pub scale: [f32; 2],
162 pub rotation: f32,
163 pub _pad: f32,
164}
165
166
167
168#[derive(Debug, Clone)]
171struct DrawCall {
172 pub texture_id: Option<u32>,
173 pub scissor_rect: Option<Rect>,
174 pub index_start: u32,
175 pub index_count: u32,
176 pub is_glass: bool,
177 pub is_ui: bool,
178}
179
180#[derive(Debug, Clone, Copy)]
181struct ShadowState {
182 pub radius: f32,
183 pub color: [f32; 4],
184 pub _offset: [f32; 2],
185}
186
187impl Vertex {
188 const ATTRIBUTES: [wgpu::VertexAttribute; 14] = wgpu::vertex_attr_array![
189 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 ];
204
205
206 fn desc() -> wgpu::VertexBufferLayout<'static> {
207 wgpu::VertexBufferLayout {
208 array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
209 step_mode: wgpu::VertexStepMode::Vertex,
210 attributes: &Self::ATTRIBUTES,
211 }
212 }
213}
214
215pub struct SurtrRenderer {
217 instance: Arc<wgpu::Instance>,
218 adapter: Arc<wgpu::Adapter>,
219 device: Arc<wgpu::Device>,
220 queue: Arc<wgpu::Queue>,
221
222 surfaces: std::collections::HashMap<winit::window::WindowId, SurfaceContext>,
224 current_window: Option<winit::window::WindowId>,
225 pub headless_context: Option<HeadlessContext>,
226
227 text_engine: runic_text::RunicTextEngine,
229 mega_atlas_tex: wgpu::Texture,
230 #[allow(dead_code)]
231 mega_atlas_view: wgpu::TextureView,
232 _mega_atlas_sampler: wgpu::Sampler,
233 mega_atlas_bind_group: wgpu::BindGroup,
234 text_cache: std::collections::HashMap<runic_text::CacheKey, (Rect, f32, f32)>,
235 atlas_packer: ShelfPacker,
236 image_uv_registry: std::collections::HashMap<String, Rect>,
237 texture_registry: std::collections::HashMap<String, u32>,
238 svg_cache: std::collections::HashMap<String, SvgModel>,
239
240 dummy_texture_bind_group: wgpu::BindGroup,
242 dummy_env_bind_group: wgpu::BindGroup,
243 texture_bind_group_layout: wgpu::BindGroupLayout,
244 texture_bind_groups: Vec<wgpu::BindGroup>,
245 shared_elements: std::collections::HashMap<String, cvkg_core::Rect>,
246
247 vertex_buffer: wgpu::Buffer,
249 index_buffer: wgpu::Buffer,
250 vertices: Vec<Vertex>,
251 indices: Vec<u32>,
252 draw_calls: Vec<DrawCall>,
253 current_texture_id: Option<u32>,
254
255 opacity_stack: Vec<f32>,
257 clip_stack: Vec<Rect>,
258 slice_stack: Vec<(f32, f32)>,
259 shadow_stack: Vec<ShadowState>,
260
261 theme_buffer: wgpu::Buffer,
263 scene_buffer: wgpu::Buffer,
264 berserker_bind_group: wgpu::BindGroup,
265 #[allow(dead_code)]
266 berserker_bind_group_layout: wgpu::BindGroupLayout,
267 start_time: std::time::Instant,
268 current_theme: ColorTheme,
269 current_scene: SceneUniforms,
270 current_z: f32,
271
272 pipeline: wgpu::RenderPipeline,
274 background_pipeline: wgpu::RenderPipeline,
275 bloom_extract_pipeline: wgpu::RenderPipeline,
276 blur_h_pipeline: wgpu::RenderPipeline,
277 blur_v_pipeline: wgpu::RenderPipeline,
278 composite_pipeline: wgpu::RenderPipeline,
279 env_bind_group_layout: wgpu::BindGroupLayout,
280
281 pub telemetry: cvkg_core::TelemetryData,
283
284 pub frame_budget: cvkg_core::FrameBudget,
286 pub last_redraw_start: std::time::Instant,
288 pub last_frame_start: std::time::Instant,
290
291 vram_buffers_bytes: u64,
293 vram_textures_bytes: u64,
294
295 _debug_layout: bool,
297
298 transform_stack: Vec<([f32; 2], [f32; 2], f32)>,
300 pub redraw_requested: bool,
302}
303
304
305struct SurfaceContext {
306 surface: wgpu::Surface<'static>,
307 config: wgpu::SurfaceConfiguration,
308 scene_texture: wgpu::TextureView,
309 scene_bind_group: wgpu::BindGroup,
310 scene_texture_bind_group: wgpu::BindGroup,
311 depth_texture_view: wgpu::TextureView,
312 blur_texture_a: wgpu::TextureView,
313 blur_texture_b: wgpu::TextureView,
314 blur_bind_group_a: wgpu::BindGroup,
315 blur_bind_group_b: wgpu::BindGroup,
316 blur_env_bind_group_a: wgpu::BindGroup,
317 scale_factor: f32,
318 sampler: wgpu::Sampler,
319}
320
321pub struct HeadlessContext {
323 pub scene_texture: wgpu::TextureView,
324 pub scene_bind_group: wgpu::BindGroup,
325 pub scene_texture_bind_group: wgpu::BindGroup,
326 pub depth_texture_view: wgpu::TextureView,
327 pub blur_texture_a: wgpu::TextureView,
328 pub blur_texture_b: wgpu::TextureView,
329 pub blur_bind_group_a: wgpu::BindGroup,
330 pub blur_bind_group_b: wgpu::BindGroup,
331 pub blur_env_bind_group_a: wgpu::BindGroup,
332 pub scale_factor: f32,
333 pub sampler: wgpu::Sampler,
334 pub width: u32,
335 pub height: u32,
336 pub output_texture: wgpu::Texture,
337 pub output_view: wgpu::TextureView,
338}
339
340const MAX_VERTICES: usize = 100_000;
341const MAX_INDICES: usize = 150_000;
342
343impl SurtrRenderer {
344 pub async fn forge(window: Arc<winit::window::Window>) -> Self {
351 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
352 backends: wgpu::Backends::all(),
353 flags: wgpu::InstanceFlags::default(),
354 backend_options: wgpu::BackendOptions::default(),
355 display: None,
356 memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
357 });
358
359 let surface = instance
360 .create_surface(window.clone())
361 .expect("Failed to create surface");
362
363 println!("[GPU] Requesting HighPerformance adapter...");
365 let mut adapter = instance
366 .request_adapter(&wgpu::RequestAdapterOptions {
367 power_preference: wgpu::PowerPreference::HighPerformance,
368 compatible_surface: Some(&surface),
369 force_fallback_adapter: false,
370 })
371 .await
372 .ok();
373
374 if adapter.is_none() {
375 println!("[GPU] HighPerformance adapter failed (possible Bumblebee/Optimus), trying LowPower...");
376 adapter = instance
377 .request_adapter(&wgpu::RequestAdapterOptions {
378 power_preference: wgpu::PowerPreference::LowPower,
379 compatible_surface: Some(&surface),
380 force_fallback_adapter: false,
381 })
382 .await
383 .ok();
384 }
385
386 if adapter.is_none() {
387 println!("[GPU] Hardware adapters failed, trying Software fallback...");
388 adapter = instance
389 .request_adapter(&wgpu::RequestAdapterOptions {
390 power_preference: wgpu::PowerPreference::LowPower,
391 compatible_surface: Some(&surface),
392 force_fallback_adapter: true,
393 })
394 .await
395 .ok();
396 }
397
398 let adapter = adapter.expect("Failed to find a suitable GPU for Surtr");
399 println!("[GPU] Selected adapter: {:?}", adapter.get_info().name);
400
401 let (device, queue) = adapter
402 .request_device(&wgpu::DeviceDescriptor {
403 label: Some("Surtr Forge"),
404 required_features: wgpu::Features::empty(),
405 required_limits: wgpu::Limits::default(),
406 memory_hints: wgpu::MemoryHints::default(),
407 experimental_features: wgpu::ExperimentalFeatures::disabled(),
408 trace: wgpu::Trace::Off,
409 })
410 .await
411 .expect("Failed to create Surtr device");
412
413 let instance = Arc::new(instance);
414 let adapter = Arc::new(adapter);
415 let device = Arc::new(device);
416 let queue = Arc::new(queue);
417
418 let size = window.inner_size();
419 let surface_caps = surface.get_capabilities(&adapter);
420 let surface_format = surface_caps
421 .formats
422 .iter()
423 .find(|f| f.is_srgb())
424 .copied()
425 .unwrap_or(surface_caps.formats[0]);
426
427 let config = wgpu::SurfaceConfiguration {
428 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
429 format: surface_format,
430 width: size.width,
431 height: size.height,
432 present_mode: wgpu::PresentMode::Fifo,
433 alpha_mode: surface_caps.alpha_modes[0],
434 view_formats: vec![],
435 desired_maximum_frame_latency: 2,
436 };
437 surface.configure(&device, &config);
438
439 Self::forge_internal(
440 instance,
441 adapter,
442 device,
443 queue,
444 Some((window, surface, config)),
445 None
446 ).await
447 }
448
449 async fn forge_internal(
450 instance: Arc<wgpu::Instance>,
451 adapter: Arc<wgpu::Adapter>,
452 device: Arc<wgpu::Device>,
453 queue: Arc<wgpu::Queue>,
454 surface_info: Option<(Arc<winit::window::Window>, wgpu::Surface<'static>, wgpu::SurfaceConfiguration)>,
455 headless_info: Option<(u32, u32, wgpu::TextureFormat)>,
456 ) -> Self {
457 let format = if let Some((_, _, ref config)) = surface_info {
458 config.format
459 } else if let Some((_, _, f)) = headless_info {
460 f
461 } else {
462 wgpu::TextureFormat::Rgba8UnormSrgb
463 };
464
465 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
466 label: Some("Muspelheim Main Shader"),
467 source: wgpu::ShaderSource::SpirV(std::borrow::Cow::Borrowed(SPIRV)),
468 });
469
470 let texture_bind_group_layout =
472 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
473 entries: &[
474 wgpu::BindGroupLayoutEntry {
475 binding: 0,
476 visibility: wgpu::ShaderStages::FRAGMENT,
477 ty: wgpu::BindingType::Texture {
478 multisampled: false,
479 view_dimension: wgpu::TextureViewDimension::D2,
480 sample_type: wgpu::TextureSampleType::Float { filterable: true },
481 },
482 count: None,
483 },
484 wgpu::BindGroupLayoutEntry {
485 binding: 1,
486 visibility: wgpu::ShaderStages::FRAGMENT,
487 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
488 count: None,
489 },
490 ],
491 label: Some("Niflheim Texture Bind Group Layout"),
492 });
493
494 let env_bind_group_layout =
497 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
498 entries: &[
499 wgpu::BindGroupLayoutEntry {
500 binding: 0,
501 visibility: wgpu::ShaderStages::FRAGMENT,
502 ty: wgpu::BindingType::Texture {
503 multisampled: false,
504 view_dimension: wgpu::TextureViewDimension::D2,
505 sample_type: wgpu::TextureSampleType::Float { filterable: true },
506 },
507 count: None,
508 },
509 wgpu::BindGroupLayoutEntry {
510 binding: 1,
511 visibility: wgpu::ShaderStages::FRAGMENT,
512 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
513 count: None,
514 },
515 ],
516 label: Some("Surtr Environment Bind Group Layout"),
517 });
518
519 let berserker_bind_group_layout =
520 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
521 entries: &[
522 wgpu::BindGroupLayoutEntry {
523 binding: 0,
524 visibility: wgpu::ShaderStages::FRAGMENT,
525 ty: wgpu::BindingType::Buffer {
526 ty: wgpu::BufferBindingType::Uniform,
527 has_dynamic_offset: false,
528 min_binding_size: None,
529 },
530 count: None,
531 },
532 wgpu::BindGroupLayoutEntry {
533 binding: 1,
534 visibility: wgpu::ShaderStages::FRAGMENT | wgpu::ShaderStages::VERTEX,
535 ty: wgpu::BindingType::Buffer {
536 ty: wgpu::BufferBindingType::Uniform,
537 has_dynamic_offset: false,
538 min_binding_size: None,
539 },
540 count: None,
541 },
542 ],
543 label: Some("Surtr Berserker Bind Group Layout"),
544 });
545
546 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
548 label: Some("Surtr Main Pipeline Layout"),
549 bind_group_layouts: &[
550 Some(&texture_bind_group_layout),
551 Some(&env_bind_group_layout),
552 Some(&berserker_bind_group_layout),
553 ],
554 immediate_size: 0,
555 });
556
557 let post_process_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
559 label: Some("Muspelheim Post Process Layout"),
560 bind_group_layouts: &[
561 Some(&texture_bind_group_layout),
562 Some(&env_bind_group_layout),
563 Some(&berserker_bind_group_layout),
564 ],
565 immediate_size: 0,
566 });
567
568 let composite_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
570 label: Some("Muspelheim Composite Layout"),
571 bind_group_layouts: &[
572 Some(&texture_bind_group_layout),
573 Some(&env_bind_group_layout),
574 Some(&berserker_bind_group_layout),
575 ],
576 immediate_size: 0,
577 });
578
579 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
580 label: Some("Surtr Main Pipeline"),
581 layout: Some(&pipeline_layout),
582 vertex: wgpu::VertexState {
583 module: &shader,
584 entry_point: Some("vs_main"),
585 buffers: &[Vertex::desc()],
586 compilation_options: wgpu::PipelineCompilationOptions::default(),
587 },
588 fragment: Some(wgpu::FragmentState {
589 module: &shader,
590 entry_point: Some("fs_main"),
591 targets: &[Some(wgpu::ColorTargetState {
592 format: format,
593 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
594 write_mask: wgpu::ColorWrites::ALL,
595 })],
596 compilation_options: wgpu::PipelineCompilationOptions::default(),
597 }),
598 primitive: wgpu::PrimitiveState::default(),
599 depth_stencil: Some(wgpu::DepthStencilState {
600 format: wgpu::TextureFormat::Depth32Float,
601 depth_write_enabled: Some(true),
602 depth_compare: Some(wgpu::CompareFunction::LessEqual),
603 stencil: wgpu::StencilState::default(),
604 bias: wgpu::DepthBiasState::default(),
605 }),
606 multisample: wgpu::MultisampleState::default(),
607 multiview_mask: None,
608 cache: None,
609 });
610
611 let background_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
612 label: Some("Surtr Background Pipeline"),
613 layout: Some(&pipeline_layout),
614 vertex: wgpu::VertexState {
615 module: &shader,
616 entry_point: Some("vs_fullscreen"),
617 buffers: &[],
618 compilation_options: wgpu::PipelineCompilationOptions::default(),
619 },
620 fragment: Some(wgpu::FragmentState {
621 module: &shader,
622 entry_point: Some("fs_background"),
623 targets: &[Some(wgpu::ColorTargetState {
624 format: format,
625 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
626 write_mask: wgpu::ColorWrites::ALL,
627 })],
628 compilation_options: wgpu::PipelineCompilationOptions::default(),
629 }),
630 primitive: wgpu::PrimitiveState::default(),
631 depth_stencil: Some(wgpu::DepthStencilState {
632 format: wgpu::TextureFormat::Depth32Float,
633 depth_write_enabled: Some(true),
634 depth_compare: Some(wgpu::CompareFunction::Always),
635 stencil: wgpu::StencilState::default(),
636 bias: wgpu::DepthBiasState::default(),
637 }),
638 multisample: wgpu::MultisampleState::default(),
639 multiview_mask: None,
640 cache: None,
641 });
642
643 let bloom_extract_pipeline =
645 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
646 label: Some("Muspelheim Bloom Extract"),
647 layout: Some(&post_process_layout),
648 vertex: wgpu::VertexState {
649 module: &shader,
650 entry_point: Some("vs_fullscreen"),
651 buffers: &[],
652 compilation_options: wgpu::PipelineCompilationOptions::default(),
653 },
654 fragment: Some(wgpu::FragmentState {
655 module: &shader,
656 entry_point: Some("fs_bloom_extract"),
657 targets: &[Some(wgpu::ColorTargetState {
658 format: format,
659 blend: None,
660 write_mask: wgpu::ColorWrites::ALL,
661 })],
662 compilation_options: wgpu::PipelineCompilationOptions::default(),
663 }),
664 primitive: wgpu::PrimitiveState::default(),
665 depth_stencil: None,
666 multisample: wgpu::MultisampleState::default(),
667 multiview_mask: None,
668 cache: None,
669 });
670
671 let blur_h_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
673 label: Some("Muspelheim Horizontal Blur"),
674 layout: Some(&post_process_layout),
675 vertex: wgpu::VertexState {
676 module: &shader,
677 entry_point: Some("vs_fullscreen"),
678 buffers: &[],
679 compilation_options: wgpu::PipelineCompilationOptions::default(),
680 },
681 fragment: Some(wgpu::FragmentState {
682 module: &shader,
683 entry_point: Some("fs_blur_h"),
684 targets: &[Some(wgpu::ColorTargetState {
685 format: format,
686 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
687 write_mask: wgpu::ColorWrites::ALL,
688 })],
689 compilation_options: wgpu::PipelineCompilationOptions::default(),
690 }),
691 primitive: wgpu::PrimitiveState::default(),
692 depth_stencil: None,
693 multisample: wgpu::MultisampleState::default(),
694 multiview_mask: None,
695 cache: None,
696 });
697
698 let blur_v_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
699 label: Some("Muspelheim Vertical Blur"),
700 layout: Some(&post_process_layout),
701 vertex: wgpu::VertexState {
702 module: &shader,
703 entry_point: Some("vs_fullscreen"),
704 buffers: &[],
705 compilation_options: wgpu::PipelineCompilationOptions::default(),
706 },
707 fragment: Some(wgpu::FragmentState {
708 module: &shader,
709 entry_point: Some("fs_blur_v"),
710 targets: &[Some(wgpu::ColorTargetState {
711 format: format,
712 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
713 write_mask: wgpu::ColorWrites::ALL,
714 })],
715 compilation_options: wgpu::PipelineCompilationOptions::default(),
716 }),
717 primitive: wgpu::PrimitiveState::default(),
718 depth_stencil: None,
719 multisample: wgpu::MultisampleState::default(),
720 multiview_mask: None,
721 cache: None,
722 });
723
724 let composite_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
726 label: Some("Muspelheim Composite"),
727 layout: Some(&composite_layout),
728 vertex: wgpu::VertexState {
729 module: &shader,
730 entry_point: Some("vs_fullscreen"),
731 buffers: &[],
732 compilation_options: wgpu::PipelineCompilationOptions::default(),
733 },
734 fragment: Some(wgpu::FragmentState {
735 module: &shader,
736 entry_point: Some("fs_composite"),
737 targets: &[Some(wgpu::ColorTargetState {
738 format: format,
739 blend: Some(wgpu::BlendState {
741 color: wgpu::BlendComponent {
742 src_factor: wgpu::BlendFactor::One,
743 dst_factor: wgpu::BlendFactor::One,
744 operation: wgpu::BlendOperation::Add,
745 },
746 alpha: wgpu::BlendComponent {
747 src_factor: wgpu::BlendFactor::One,
748 dst_factor: wgpu::BlendFactor::One,
749 operation: wgpu::BlendOperation::Add,
750 },
751 }),
752 write_mask: wgpu::ColorWrites::ALL,
753 })],
754 compilation_options: wgpu::PipelineCompilationOptions::default(),
755 }),
756 primitive: wgpu::PrimitiveState::default(),
757 depth_stencil: None,
758 multisample: wgpu::MultisampleState::default(),
759 multiview_mask: None,
760 cache: None,
761 });
762
763 let mega_atlas_tex = device.create_texture(&wgpu::TextureDescriptor {
765 label: Some("Surtr Mega-Atlas"),
766 size: wgpu::Extent3d {
767 width: 4096,
768 height: 4096,
769 depth_or_array_layers: 1,
770 },
771 mip_level_count: 1,
772 sample_count: 1,
773 dimension: wgpu::TextureDimension::D2,
774 format: wgpu::TextureFormat::Rgba8UnormSrgb,
775 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
776 view_formats: &[],
777 });
778 let mega_atlas_view_obj = mega_atlas_tex.create_view(&wgpu::TextureViewDescriptor::default());
779 let text_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
780 address_mode_u: wgpu::AddressMode::ClampToEdge,
781 address_mode_v: wgpu::AddressMode::ClampToEdge,
782 mag_filter: wgpu::FilterMode::Linear, min_filter: wgpu::FilterMode::Linear,
784 ..Default::default()
785 });
786
787 let mega_atlas_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
788 layout: &texture_bind_group_layout,
789 entries: &[
790 wgpu::BindGroupEntry {
791 binding: 0,
792 resource: wgpu::BindingResource::TextureView(&mega_atlas_view_obj),
793 },
794 wgpu::BindGroupEntry {
795 binding: 1,
796 resource: wgpu::BindingResource::Sampler(&text_sampler),
797 },
798 ],
799 label: Some("Mega-Atlas Bind Group"),
800 });
801
802 queue.write_texture(
804 wgpu::TexelCopyTextureInfo {
805 texture: &mega_atlas_tex,
806 mip_level: 0,
807 origin: wgpu::Origin3d::ZERO,
808 aspect: wgpu::TextureAspect::All,
809 },
810 &vec![0u8; 4096 * 4096 * 4],
811 wgpu::TexelCopyBufferLayout {
812 offset: 0,
813 bytes_per_row: Some(4096 * 4),
814 rows_per_image: Some(4096),
815 },
816 wgpu::Extent3d {
817 width: 4096,
818 height: 4096,
819 depth_or_array_layers: 1,
820 },
821 );
822
823 let dummy_size = wgpu::Extent3d {
825 width: 1,
826 height: 1,
827 depth_or_array_layers: 1,
828 };
829 let dummy_texture = device.create_texture(&wgpu::TextureDescriptor {
830 label: Some("Niflheim Dummy Texture"),
831 size: dummy_size,
832 mip_level_count: 1,
833 sample_count: 1,
834 dimension: wgpu::TextureDimension::D2,
835 format: wgpu::TextureFormat::Rgba8UnormSrgb,
836 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
837 view_formats: &[],
838 });
839 queue.write_texture(
840 wgpu::TexelCopyTextureInfo {
841 texture: &dummy_texture,
842 mip_level: 0,
843 origin: wgpu::Origin3d::ZERO,
844 aspect: wgpu::TextureAspect::All,
845 },
846 &[0, 0, 0, 255],
847 wgpu::TexelCopyBufferLayout {
848 offset: 0,
849 bytes_per_row: Some(4),
850 rows_per_image: Some(1),
851 },
852 dummy_size,
853 );
854
855 let dummy_view = dummy_texture.create_view(&wgpu::TextureViewDescriptor::default());
856 let dummy_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
857 address_mode_u: wgpu::AddressMode::ClampToEdge,
858 address_mode_v: wgpu::AddressMode::ClampToEdge,
859 address_mode_w: wgpu::AddressMode::ClampToEdge,
860 mag_filter: wgpu::FilterMode::Linear,
861 min_filter: wgpu::FilterMode::Nearest,
862 mipmap_filter: wgpu::MipmapFilterMode::Nearest,
863 ..Default::default()
864 });
865
866 let dummy_texture_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
867 layout: &texture_bind_group_layout,
868 entries: &[
869 wgpu::BindGroupEntry {
870 binding: 0,
871 resource: wgpu::BindingResource::TextureView(&dummy_view),
872 },
873 wgpu::BindGroupEntry {
874 binding: 1,
875 resource: wgpu::BindingResource::Sampler(&dummy_sampler),
876 },
877 ],
878 label: Some("Dummy Texture Bind Group"),
879 });
880
881 let dummy_env_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
882 layout: &env_bind_group_layout,
883 entries: &[
884 wgpu::BindGroupEntry {
885 binding: 0,
886 resource: wgpu::BindingResource::TextureView(&dummy_view),
887 },
888 wgpu::BindGroupEntry {
889 binding: 1,
890 resource: wgpu::BindingResource::Sampler(&dummy_sampler),
891 },
892 ],
893 label: Some("Dummy Env Bind Group"),
894 });
895
896 let mut texture_registry = std::collections::HashMap::new();
897 let mut texture_bind_groups = Vec::new();
898
899 texture_registry.insert("__mega_atlas".to_string(), 0);
900 texture_bind_groups.push(mega_atlas_bind_group.clone());
901
902 let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
904 label: Some("Surtr Vertex Anvil"),
905 size: (MAX_VERTICES * std::mem::size_of::<Vertex>()) as u64,
906 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
907 mapped_at_creation: false,
908 });
909
910 let index_buffer = device.create_buffer(&wgpu::BufferDescriptor {
911 label: Some("Surtr Index Anvil"),
912 size: (MAX_INDICES * std::mem::size_of::<u32>()) as u64,
913 usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
914 mapped_at_creation: false,
915 });
916
917 let current_theme = ColorTheme::default();
919 use wgpu::util::DeviceExt;
920 let theme_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
921 label: Some("Surtr Theme Buffer"),
922 contents: bytemuck::bytes_of(¤t_theme),
923 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
924 });
925
926 let (width, height, scale_factor) = if let Some((ref window, _, ref config)) = surface_info {
927 (config.width, config.height, window.scale_factor() as f32)
928 } else if let Some((w, h, _)) = headless_info {
929 (w, h, 1.0)
930 } else {
931 (1280, 720, 1.0)
932 };
933
934 let mut current_scene = SceneUniforms::new(
935 width as f32 / scale_factor,
936 height as f32 / scale_factor,
937 );
938 current_scene.scale_factor = scale_factor;
939 let scene_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
940 label: Some("Surtr Scene Buffer"),
941 contents: bytemuck::bytes_of(¤t_scene),
942 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
943 });
944
945 let berserker_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
946 layout: &berserker_bind_group_layout,
947 entries: &[
948 wgpu::BindGroupEntry {
949 binding: 0,
950 resource: theme_buffer.as_entire_binding(),
951 },
952 wgpu::BindGroupEntry {
953 binding: 1,
954 resource: scene_buffer.as_entire_binding(),
955 },
956 ],
957 label: Some("Surtr Berserker Bind Group"),
958 });
959
960 let mut surfaces = std::collections::HashMap::new();
961 let mut current_window = None;
962 let mut headless_context = None;
963
964 if let Some((window, surface, config)) = surface_info {
965 let window_id = window.id();
966 let ctx = Self::create_surface_context(
967 &device,
968 surface,
969 config,
970 &env_bind_group_layout,
971 &texture_bind_group_layout,
972 scale_factor,
973 );
974 surfaces.insert(window_id, ctx);
975 current_window = Some(window_id);
976 } else if let Some((w, h, f)) = headless_info {
977 headless_context = Some(Self::create_headless_context(
978 &device,
979 w,
980 h,
981 f,
982 &env_bind_group_layout,
983 &texture_bind_group_layout,
984 ));
985 }
986
987 Self {
988 instance,
989 adapter,
990 device,
991 queue,
992 surfaces,
993 current_window,
994 headless_context,
995 pipeline,
996 bloom_extract_pipeline,
997 blur_h_pipeline,
998 blur_v_pipeline,
999 composite_pipeline,
1000 env_bind_group_layout,
1001 text_engine: runic_text::RunicTextEngine::default(),
1002 mega_atlas_tex,
1003 mega_atlas_view: mega_atlas_view_obj,
1004 _mega_atlas_sampler: text_sampler,
1005 mega_atlas_bind_group,
1006 text_cache: std::collections::HashMap::new(),
1007 atlas_packer: ShelfPacker::new(4096, 4096),
1008 image_uv_registry: std::collections::HashMap::new(),
1009 svg_cache: std::collections::HashMap::new(),
1010 dummy_texture_bind_group,
1011 dummy_env_bind_group,
1012 texture_bind_group_layout,
1013 texture_bind_groups,
1014 texture_registry,
1015 shared_elements: std::collections::HashMap::new(),
1016 vertex_buffer,
1017 index_buffer,
1018 vertices: Vec::with_capacity(MAX_VERTICES),
1019 indices: Vec::with_capacity(MAX_INDICES),
1020 draw_calls: Vec::new(),
1021 current_texture_id: None,
1022 opacity_stack: vec![1.0],
1023 clip_stack: Vec::new(),
1024 slice_stack: Vec::new(),
1025 shadow_stack: Vec::new(),
1026 theme_buffer,
1027 scene_buffer,
1028 berserker_bind_group,
1029 berserker_bind_group_layout,
1030 start_time: std::time::Instant::now(),
1031 current_theme,
1032 current_scene,
1033 background_pipeline,
1034 current_z: 0.0,
1035 telemetry: cvkg_core::TelemetryData::default(),
1036 last_frame_start: std::time::Instant::now(),
1037 last_redraw_start: std::time::Instant::now(),
1038 frame_budget: cvkg_core::FrameBudget::default(),
1039 vram_buffers_bytes: 0,
1040 vram_textures_bytes: 0,
1041 _debug_layout: false,
1042 transform_stack: Vec::new(),
1043 redraw_requested: false,
1044 }
1045 }
1046
1047 fn update_vram_telemetry(&mut self) {
1049 let mut buffer_bytes = 0;
1051 buffer_bytes += (MAX_VERTICES * std::mem::size_of::<Vertex>()) as u64;
1052 buffer_bytes += (MAX_INDICES * std::mem::size_of::<u32>()) as u64;
1053 buffer_bytes += std::mem::size_of::<cvkg_core::ColorTheme>() as u64;
1054 buffer_bytes += std::mem::size_of::<cvkg_core::SceneUniforms>() as u64;
1055 self.vram_buffers_bytes = buffer_bytes;
1056
1057 let mut texture_bytes = 0;
1059 texture_bytes += 4096 * 4096 * 4; texture_bytes += 4; for ctx in self.surfaces.values() {
1063 let bpp = 4;
1064 let surface_bytes = (ctx.config.width * ctx.config.height * bpp) as u64;
1065 texture_bytes += surface_bytes * 3; texture_bytes += (ctx.config.width * ctx.config.height * 4) as u64; }
1068
1069 self.vram_textures_bytes = texture_bytes;
1070
1071 self.telemetry.vram_buffers_mb = buffer_bytes as f32 / 1_048_576.0;
1072 self.telemetry.vram_textures_mb = texture_bytes as f32 / 1_048_576.0;
1073 self.telemetry.vram_pipelines_mb = 0.0;
1074 self.telemetry.vram_usage_mb = self.telemetry.vram_buffers_mb + self.telemetry.vram_textures_mb;
1075 }
1076
1077 pub fn get_telemetry(&self) -> cvkg_core::TelemetryData {
1079 self.telemetry.clone()
1080 }
1081
1082 pub fn resize(&mut self, window_id: winit::window::WindowId, width: u32, height: u32, scale_factor: f32) {
1084 if width > 0 && height > 0
1085 && let Some(ctx) = self.surfaces.get_mut(&window_id) {
1086 ctx.config.width = width;
1087 ctx.config.height = height;
1088 ctx.scale_factor = scale_factor;
1089 ctx.surface.configure(&self.device, &ctx.config);
1090
1091 let texture_desc = wgpu::TextureDescriptor {
1093 label: Some("Surtr Scene Texture"),
1094 size: wgpu::Extent3d { width, height, depth_or_array_layers: 1 },
1095 mip_level_count: 1,
1096 sample_count: 1,
1097 dimension: wgpu::TextureDimension::D2,
1098 format: ctx.config.format,
1099 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
1100 view_formats: &[],
1101 };
1102
1103 let scene_tex = self.device.create_texture(&texture_desc);
1104 ctx.scene_texture = scene_tex.create_view(&wgpu::TextureViewDescriptor::default());
1105
1106 let blur_tex_a = self.device.create_texture(&texture_desc);
1107 ctx.blur_texture_a = blur_tex_a.create_view(&wgpu::TextureViewDescriptor::default());
1108
1109 let blur_tex_b = self.device.create_texture(&texture_desc);
1110 ctx.blur_texture_b = blur_tex_b.create_view(&wgpu::TextureViewDescriptor::default());
1111
1112 ctx.scene_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1114 layout: &self.env_bind_group_layout,
1115 entries: &[
1116 wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&ctx.scene_texture) },
1117 wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&ctx.sampler) },
1118 ],
1119 label: Some("Scene Bind Group Resize"),
1120 });
1121
1122 ctx.scene_texture_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1123 layout: &self.texture_bind_group_layout,
1124 entries: &[
1125 wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&ctx.scene_texture) },
1126 wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&ctx.sampler) },
1127 ],
1128 label: Some("Scene Texture Bind Group Resize"),
1129 });
1130
1131 ctx.blur_bind_group_a = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1132 layout: &self.texture_bind_group_layout,
1133 entries: &[
1134 wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&ctx.blur_texture_a) },
1135 wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&ctx.sampler) },
1136 ],
1137 label: Some("Blur Bind Group A Resize"),
1138 });
1139
1140 ctx.blur_bind_group_b = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1141 layout: &self.texture_bind_group_layout,
1142 entries: &[
1143 wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&ctx.blur_texture_b) },
1144 wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&ctx.sampler) },
1145 ],
1146 label: Some("Blur Bind Group B Resize"),
1147 });
1148
1149 ctx.blur_env_bind_group_a = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1150 layout: &self.env_bind_group_layout,
1151 entries: &[
1152 wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&ctx.blur_texture_a) },
1153 wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&ctx.sampler) },
1154 ],
1155 label: Some("Blur Env Bind Group A Resize"),
1156 });
1157
1158 let depth_texture = self.device.create_texture(&wgpu::TextureDescriptor {
1159 label: Some("Surtr Depth Texture"),
1160 size: wgpu::Extent3d { width, height, depth_or_array_layers: 1 },
1161 mip_level_count: 1,
1162 sample_count: 1,
1163 dimension: wgpu::TextureDimension::D2,
1164 format: wgpu::TextureFormat::Depth32Float,
1165 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1166 view_formats: &[],
1167 });
1168 ctx.depth_texture_view = depth_texture.create_view(&wgpu::TextureViewDescriptor::default());
1169 }
1170 }
1171
1172 pub fn begin_frame_headless(&mut self) -> wgpu::CommandEncoder {
1174 self.current_window = None;
1175 self.vertices.clear();
1176 self.indices.clear();
1177 self.draw_calls.clear();
1178 self.shared_elements.clear();
1179 self.current_texture_id = None;
1180 self.opacity_stack = vec![1.0];
1181 self.clip_stack.clear();
1182 self.slice_stack.clear();
1183 self.current_z = 0.0;
1184
1185 self.last_frame_start = std::time::Instant::now();
1186 self.telemetry.draw_calls = 0;
1187 self.telemetry.vertices = 0;
1188
1189 let ctx = self.headless_context.as_ref().expect("Headless context not initialized");
1190 let time = self.start_time.elapsed().as_secs_f32();
1191 let logical_w = ctx.width as f32 / ctx.scale_factor;
1192 let logical_h = ctx.height as f32 / ctx.scale_factor;
1193 let dt = time - self.current_scene.time;
1194 self.current_scene.time = time;
1195 self.current_scene.delta_time = dt;
1196 self.current_scene.resolution = [logical_w, logical_h];
1197 self.current_scene.scale_factor = ctx.scale_factor;
1198 self.current_scene.proj = glam::Mat4::orthographic_lh(0.0, logical_w, logical_h, 0.0, -1000.0, 1000.0);
1199
1200 self.queue.write_buffer(
1201 &self.scene_buffer,
1202 0,
1203 bytemuck::bytes_of(&self.current_scene),
1204 );
1205
1206 self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
1207 label: Some("Surtr Headless Command Encoder"),
1208 })
1209 }
1210
1211 pub fn begin_frame(&mut self, window_id: winit::window::WindowId) -> wgpu::CommandEncoder {
1213 self.current_window = Some(window_id);
1214 self.vertices.clear();
1215 self.indices.clear();
1216 self.draw_calls.clear();
1217 self.shared_elements.clear();
1218 self.current_texture_id = None;
1219 self.opacity_stack = vec![1.0];
1220 self.clip_stack.clear();
1221 self.slice_stack.clear();
1222 self.current_z = 0.0;
1223
1224 self.last_frame_start = std::time::Instant::now();
1225 self.telemetry.draw_calls = 0;
1226 self.telemetry.vertices = 0;
1227
1228 let ctx = self.surfaces.get(&window_id).expect("Window not registered");
1229 let time = self.start_time.elapsed().as_secs_f32();
1230 let logical_w = ctx.config.width as f32 / ctx.scale_factor;
1231 let logical_h = ctx.config.height as f32 / ctx.scale_factor;
1232 let dt = time - self.current_scene.time;
1233 self.current_scene.time = time;
1234 self.current_scene.delta_time = dt;
1235 self.current_scene.resolution = [logical_w, logical_h];
1236 self.current_scene.scale_factor = ctx.scale_factor;
1237 self.current_scene.proj = glam::Mat4::orthographic_lh(0.0, logical_w, logical_h, 0.0, -1000.0, 1000.0);
1238
1239 self.queue.write_buffer(
1240 &self.scene_buffer,
1241 0,
1242 bytemuck::bytes_of(&self.current_scene),
1243 );
1244
1245 self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
1246 label: Some("Surtr Command Encoder"),
1247 })
1248 }
1249
1250
1251
1252
1253 pub fn register_window(&mut self, window: Arc<winit::window::Window>) {
1255 let size = window.inner_size();
1256 let surface = self.instance.create_surface(window.clone()).expect("Failed to create surface");
1257 let caps = surface.get_capabilities(&self.adapter);
1258 let format = caps.formats[0];
1259 let config = wgpu::SurfaceConfiguration {
1260 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1261 format,
1262 width: size.width,
1263 height: size.height,
1264 present_mode: wgpu::PresentMode::Fifo,
1265 alpha_mode: caps.alpha_modes[0],
1266 view_formats: vec![],
1267 desired_maximum_frame_latency: 2,
1268 };
1269 surface.configure(&self.device, &config);
1270
1271 let ctx = Self::create_surface_context(
1272 &self.device,
1273 surface,
1274 config,
1275 &self.env_bind_group_layout,
1276 &self.texture_bind_group_layout,
1277 window.scale_factor() as f32,
1278 );
1279
1280 self.surfaces.insert(window.id(), ctx);
1281 }
1282
1283 fn create_headless_context(
1284 device: &wgpu::Device,
1285 width: u32,
1286 height: u32,
1287 format: wgpu::TextureFormat,
1288 env_bind_group_layout: &wgpu::BindGroupLayout,
1289 texture_bind_group_layout: &wgpu::BindGroupLayout,
1290 ) -> HeadlessContext {
1291 let texture_desc = wgpu::TextureDescriptor {
1292 label: Some("Surtr Headless Scene Texture"),
1293 size: wgpu::Extent3d {
1294 width,
1295 height,
1296 depth_or_array_layers: 1,
1297 },
1298 mip_level_count: 1,
1299 sample_count: 1,
1300 dimension: wgpu::TextureDimension::D2,
1301 format,
1302 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_SRC,
1303 view_formats: &[],
1304 };
1305
1306 let scene_tex = device.create_texture(&texture_desc);
1307 let scene_texture = scene_tex.create_view(&wgpu::TextureViewDescriptor::default());
1308
1309 let blur_tex_a = device.create_texture(&texture_desc);
1310 let blur_texture_a = blur_tex_a.create_view(&wgpu::TextureViewDescriptor::default());
1311
1312 let blur_tex_b = device.create_texture(&texture_desc);
1313 let blur_texture_b = blur_tex_b.create_view(&wgpu::TextureViewDescriptor::default());
1314
1315 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1316 address_mode_u: wgpu::AddressMode::ClampToEdge,
1317 address_mode_v: wgpu::AddressMode::ClampToEdge,
1318 mag_filter: wgpu::FilterMode::Linear,
1319 min_filter: wgpu::FilterMode::Linear,
1320 ..Default::default()
1321 });
1322
1323 let scene_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1324 layout: env_bind_group_layout,
1325 entries: &[
1326 wgpu::BindGroupEntry {
1327 binding: 0,
1328 resource: wgpu::BindingResource::TextureView(&scene_texture),
1329 },
1330 wgpu::BindGroupEntry {
1331 binding: 1,
1332 resource: wgpu::BindingResource::Sampler(&sampler),
1333 },
1334 ],
1335 label: Some("Headless Scene Bind Group"),
1336 });
1337
1338 let scene_texture_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1339 layout: texture_bind_group_layout,
1340 entries: &[
1341 wgpu::BindGroupEntry {
1342 binding: 0,
1343 resource: wgpu::BindingResource::TextureView(&scene_texture),
1344 },
1345 wgpu::BindGroupEntry {
1346 binding: 1,
1347 resource: wgpu::BindingResource::Sampler(&sampler),
1348 },
1349 ],
1350 label: Some("Headless Scene Texture Bind Group"),
1351 });
1352
1353 let blur_bind_group_a = device.create_bind_group(&wgpu::BindGroupDescriptor {
1354 layout: texture_bind_group_layout,
1355 entries: &[
1356 wgpu::BindGroupEntry {
1357 binding: 0,
1358 resource: wgpu::BindingResource::TextureView(&blur_texture_a),
1359 },
1360 wgpu::BindGroupEntry {
1361 binding: 1,
1362 resource: wgpu::BindingResource::Sampler(&sampler),
1363 },
1364 ],
1365 label: Some("Headless Blur Bind Group A"),
1366 });
1367
1368 let blur_bind_group_b = device.create_bind_group(&wgpu::BindGroupDescriptor {
1369 layout: texture_bind_group_layout,
1370 entries: &[
1371 wgpu::BindGroupEntry {
1372 binding: 0,
1373 resource: wgpu::BindingResource::TextureView(&blur_texture_b),
1374 },
1375 wgpu::BindGroupEntry {
1376 binding: 1,
1377 resource: wgpu::BindingResource::Sampler(&sampler),
1378 },
1379 ],
1380 label: Some("Headless Blur Bind Group B"),
1381 });
1382
1383 let blur_env_bind_group_a = device.create_bind_group(&wgpu::BindGroupDescriptor {
1384 layout: env_bind_group_layout,
1385 entries: &[
1386 wgpu::BindGroupEntry {
1387 binding: 0,
1388 resource: wgpu::BindingResource::TextureView(&blur_texture_a),
1389 },
1390 wgpu::BindGroupEntry {
1391 binding: 1,
1392 resource: wgpu::BindingResource::Sampler(&sampler),
1393 },
1394 ],
1395 label: Some("Headless Blur Env Bind Group A"),
1396 });
1397
1398 let depth_texture = device.create_texture(&wgpu::TextureDescriptor {
1399 label: Some("Headless Depth Texture"),
1400 size: wgpu::Extent3d { width, height, depth_or_array_layers: 1 },
1401 mip_level_count: 1,
1402 sample_count: 1,
1403 dimension: wgpu::TextureDimension::D2,
1404 format: wgpu::TextureFormat::Depth32Float,
1405 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1406 view_formats: &[],
1407 });
1408 let depth_texture_view = depth_texture.create_view(&wgpu::TextureViewDescriptor::default());
1409
1410 let output_texture = device.create_texture(&wgpu::TextureDescriptor {
1411 label: Some("Headless Output Texture"),
1412 size: wgpu::Extent3d { width, height, depth_or_array_layers: 1 },
1413 mip_level_count: 1,
1414 sample_count: 1,
1415 dimension: wgpu::TextureDimension::D2,
1416 format,
1417 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::COPY_SRC,
1418 view_formats: &[],
1419 });
1420 let output_view = output_texture.create_view(&wgpu::TextureViewDescriptor::default());
1421
1422 HeadlessContext {
1423 scene_texture,
1424 scene_bind_group,
1425 scene_texture_bind_group,
1426 depth_texture_view,
1427 blur_texture_a,
1428 blur_texture_b,
1429 blur_bind_group_a,
1430 blur_bind_group_b,
1431 blur_env_bind_group_a,
1432 scale_factor: 1.0,
1433 sampler,
1434 width,
1435 height,
1436 output_texture,
1437 output_view,
1438 }
1439 }
1440
1441 fn create_surface_context(
1442 device: &wgpu::Device,
1443 surface: wgpu::Surface<'static>,
1444 config: wgpu::SurfaceConfiguration,
1445 env_bind_group_layout: &wgpu::BindGroupLayout,
1446 texture_bind_group_layout: &wgpu::BindGroupLayout,
1447 scale_factor: f32,
1448 ) -> SurfaceContext {
1449 let width = config.width;
1450 let height = config.height;
1451
1452 let texture_desc = wgpu::TextureDescriptor {
1453 label: Some("Surtr Scene Texture"),
1454 size: wgpu::Extent3d {
1455 width,
1456 height,
1457 depth_or_array_layers: 1,
1458 },
1459 mip_level_count: 1,
1460 sample_count: 1,
1461 dimension: wgpu::TextureDimension::D2,
1462 format: config.format,
1463 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
1464 view_formats: &[],
1465 };
1466
1467 let scene_tex = device.create_texture(&texture_desc);
1468 let scene_texture = scene_tex.create_view(&wgpu::TextureViewDescriptor::default());
1469
1470 let blur_tex_a = device.create_texture(&texture_desc);
1471 let blur_texture_a = blur_tex_a.create_view(&wgpu::TextureViewDescriptor::default());
1472
1473 let blur_tex_b = device.create_texture(&texture_desc);
1474 let blur_texture_b = blur_tex_b.create_view(&wgpu::TextureViewDescriptor::default());
1475
1476 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1477 address_mode_u: wgpu::AddressMode::ClampToEdge,
1478 address_mode_v: wgpu::AddressMode::ClampToEdge,
1479 mag_filter: wgpu::FilterMode::Linear,
1480 min_filter: wgpu::FilterMode::Linear,
1481 ..Default::default()
1482 });
1483
1484 let scene_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1485 layout: env_bind_group_layout,
1486 entries: &[
1487 wgpu::BindGroupEntry {
1488 binding: 0,
1489 resource: wgpu::BindingResource::TextureView(&scene_texture),
1490 },
1491 wgpu::BindGroupEntry {
1492 binding: 1,
1493 resource: wgpu::BindingResource::Sampler(&sampler),
1494 },
1495 ],
1496 label: Some("Scene Bind Group"),
1497 });
1498
1499 let scene_texture_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1500 layout: texture_bind_group_layout,
1501 entries: &[
1502 wgpu::BindGroupEntry {
1503 binding: 0,
1504 resource: wgpu::BindingResource::TextureView(&scene_texture),
1505 },
1506 wgpu::BindGroupEntry {
1507 binding: 1,
1508 resource: wgpu::BindingResource::Sampler(&sampler),
1509 },
1510 ],
1511 label: Some("Scene Texture Bind Group"),
1512 });
1513
1514 let blur_bind_group_a = device.create_bind_group(&wgpu::BindGroupDescriptor {
1515 layout: texture_bind_group_layout,
1516 entries: &[
1517 wgpu::BindGroupEntry {
1518 binding: 0,
1519 resource: wgpu::BindingResource::TextureView(&blur_texture_a),
1520 },
1521 wgpu::BindGroupEntry {
1522 binding: 1,
1523 resource: wgpu::BindingResource::Sampler(&sampler),
1524 },
1525 ],
1526 label: Some("Blur Bind Group A"),
1527 });
1528
1529 let blur_bind_group_b = device.create_bind_group(&wgpu::BindGroupDescriptor {
1530 layout: texture_bind_group_layout,
1531 entries: &[
1532 wgpu::BindGroupEntry {
1533 binding: 0,
1534 resource: wgpu::BindingResource::TextureView(&blur_texture_b),
1535 },
1536 wgpu::BindGroupEntry {
1537 binding: 1,
1538 resource: wgpu::BindingResource::Sampler(&sampler),
1539 },
1540 ],
1541 label: Some("Blur Bind Group B"),
1542 });
1543
1544 let depth_texture = device.create_texture(&wgpu::TextureDescriptor {
1545 label: Some("Surtr Depth Texture"),
1546 size: wgpu::Extent3d {
1547 width,
1548 height,
1549 depth_or_array_layers: 1,
1550 },
1551 mip_level_count: 1,
1552 sample_count: 1,
1553 dimension: wgpu::TextureDimension::D2,
1554 format: wgpu::TextureFormat::Depth32Float,
1555 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
1556 view_formats: &[],
1557 });
1558 let depth_texture_view = depth_texture.create_view(&wgpu::TextureViewDescriptor::default());
1559
1560 let blur_env_bind_group_a = device.create_bind_group(&wgpu::BindGroupDescriptor {
1561 layout: env_bind_group_layout,
1562 entries: &[
1563 wgpu::BindGroupEntry {
1564 binding: 0,
1565 resource: wgpu::BindingResource::TextureView(&blur_texture_a),
1566 },
1567 wgpu::BindGroupEntry {
1568 binding: 1,
1569 resource: wgpu::BindingResource::Sampler(&sampler),
1570 },
1571 wgpu::BindGroupEntry {
1572 binding: 2,
1573 resource: wgpu::BindingResource::TextureView(&depth_texture_view),
1574 },
1575 ],
1576 label: Some("Blur Env Bind Group A"),
1577 });
1578
1579 SurfaceContext {
1580 surface,
1581 config,
1582 scene_texture,
1583 scene_bind_group,
1584 scene_texture_bind_group,
1585 depth_texture_view,
1586 blur_texture_a,
1587 blur_texture_b,
1588 blur_bind_group_a,
1589 blur_bind_group_b,
1590 blur_env_bind_group_a,
1591 scale_factor,
1592 sampler,
1593 }
1594 }
1595
1596 pub fn reset_time(&mut self) {
1598 self.start_time = std::time::Instant::now();
1599 }
1600
1601 fn shatter_internal(
1602 &mut self,
1603 rect: Rect,
1604 pieces: u32,
1605 force: f32,
1606 color: [f32; 4],
1607 mode: u32,
1608 ) {
1609 let count = (pieces as f32).sqrt().ceil() as u32;
1611 let dw = rect.width / count as f32;
1612 let dh = rect.height / count as f32;
1613
1614 let c = self.apply_opacity(color);
1615
1616 for y in 0..count {
1617 for x in 0..count {
1618 let shard_rect = Rect {
1619 x: rect.x + x as f32 * dw,
1620 y: rect.y + y as f32 * dh,
1621 width: dw,
1622 height: dh,
1623 };
1624
1625 let uv = Rect {
1626 x: x as f32 / count as f32,
1627 y: y as f32 / count as f32,
1628 width: 1.0 / count as f32,
1629 height: 1.0 / count as f32,
1630 };
1631
1632 self.fill_rect_with_full_params(shard_rect, c, mode, None, force, uv);
1633 }
1634 }
1635 }
1636
1637 fn recursive_bolt(&mut self, from: [f32; 2], to: [f32; 2], depth: u32, color: [f32; 4]) {
1638 if depth == 0 {
1639 self.draw_lightning_segment(from, to, color);
1640 return;
1641 }
1642
1643 let mid_x = (from[0] + to[0]) * 0.5;
1644 let mid_y = (from[1] + to[1]) * 0.5;
1645
1646 let dx = to[0] - from[0];
1647 let dy = to[1] - from[1];
1648 let len = (dx * dx + dy * dy).sqrt();
1649
1650 let offset_scale = len * 0.15;
1652 let seed = (from[0] * 12.9898 + from[1] * 78.233 + (depth as f32) * 37.11)
1653 .sin()
1654 .fract();
1655 let offset_x = -dy / len * (seed - 0.5) * offset_scale;
1656 let offset_y = dx / len * (seed - 0.5) * offset_scale;
1657
1658 let mid = [mid_x + offset_x, mid_y + offset_y];
1659
1660 self.recursive_bolt(from, mid, depth - 1, color);
1661 self.recursive_bolt(mid, to, depth - 1, color);
1662
1663 if depth > 2 && seed > 0.8 {
1665 let branch_to = [
1666 mid[0] + offset_x * 2.0 + (seed * 100.0).sin() * 50.0,
1667 mid[1] + offset_y * 2.0 + (seed * 100.0).cos() * 50.0,
1668 ];
1669 self.recursive_bolt(mid, branch_to, depth - 2, color);
1670 }
1671 }
1672
1673 fn draw_lightning_segment(&mut self, from: [f32; 2], to: [f32; 2], color: [f32; 4]) {
1674 let dx = to[0] - from[0];
1675 let dy = to[1] - from[1];
1676 let len = (dx * dx + dy * dy).sqrt();
1677 if len < 0.001 {
1678 return;
1679 }
1680
1681 let glow_width = 32.0;
1682 let core_width = 4.0;
1683 let c = self.apply_opacity(color);
1684
1685 let gnx = -dy / len * glow_width * 0.5;
1687 let gny = dx / len * glow_width * 0.5;
1688 let gp1 = [from[0] + gnx, from[1] + gny];
1689 let gp2 = [to[0] + gnx, to[1] + gny];
1690 let gp3 = [to[0] - gnx, to[1] - gny];
1691 let gp4 = [from[0] - gnx, from[1] - gny];
1692 self.push_oriented_quad(
1693 [gp1, gp2, gp3, gp4],
1694 c,
1695 9,
1696 Rect {
1697 x: 0.0,
1698 y: 0.0,
1699 width: 1.0,
1700 height: 1.0,
1701 },
1702 );
1703
1704 let cnx = -dy / len * core_width * 0.5;
1706 let cny = dx / len * core_width * 0.5;
1707 let cp1 = [from[0] + cnx, from[1] + cny];
1708 let cp2 = [to[0] + cnx, to[1] + cny];
1709 let cp3 = [to[0] - cnx, to[1] - cny];
1710 let cp4 = [from[0] - cnx, from[1] - cny];
1711 self.push_oriented_quad(
1712 [cp1, cp2, cp3, cp4],
1713 [1.0, 1.0, 1.0, c[3]],
1714 0,
1715 Rect {
1716 x: 0.0,
1717 y: 0.0,
1718 width: 1.0,
1719 height: 1.0,
1720 },
1721 );
1722 }
1723
1724 fn push_oriented_quad(
1725 &mut self,
1726 points: [[f32; 2]; 4],
1727 color: [f32; 4],
1728 mode: u32,
1729 uv_rect: Rect,
1730 ) {
1731 let scissor = self.clip_stack.last().copied();
1732 let texture_id = None; if self.draw_calls.is_empty()
1735 || self.current_texture_id != texture_id
1736 || self.draw_calls.last().unwrap().scissor_rect != scissor
1737 {
1738 self.current_texture_id = texture_id;
1739 self.draw_calls.push(DrawCall {
1740 texture_id,
1741 scissor_rect: scissor,
1742 index_start: self.indices.len() as u32,
1743 index_count: 0,
1744 is_glass: mode == 7,
1745 is_ui: mode == 6,
1746 });
1747 }
1748
1749 let uvs = [
1750 [uv_rect.x, uv_rect.y],
1751 [uv_rect.x + uv_rect.width, uv_rect.y],
1752 [uv_rect.x + uv_rect.width, uv_rect.y + uv_rect.height],
1753 [uv_rect.x, uv_rect.y + uv_rect.height],
1754 ];
1755
1756 let screen = [self.current_width() as f32, self.current_height() as f32];
1757 let rect = Rect {
1758 x: points[0][0],
1759 y: points[0][1],
1760 width: 1.0,
1761 height: 1.0,
1762 };
1763
1764 for i in 0..4 {
1765 let px = points[i][0];
1766 let py = points[i][1];
1767
1768 let (translation, scale_transform, rotation) = self.get_current_transform();
1769 self.vertices.push(Vertex {
1770 position: [px, py, 0.0],
1771 normal: [0.0, 0.0, 1.0],
1772 uv: uvs[i],
1773 color,
1774 mode,
1775 radius: 0.0,
1776 slice: [0.0, 0.0, 0.0, 1.0],
1777 logical: [px - rect.x, py - rect.y],
1778 size: [rect.width, rect.height],
1779 screen,
1780 clip: [-10000.0, -10000.0, 20000.0, 20000.0],
1781 translation,
1782 scale: scale_transform,
1783 rotation,
1784 _pad: 0.0,
1785 });
1786 }
1787
1788 if let Some(call) = self.draw_calls.last_mut() {
1789 call.index_count += 6;
1790 }
1791 }
1792 fn get_texture_id(&self, name: &str) -> Option<u32> {
1793 self.texture_registry.get(name).copied()
1794 }
1795
1796 pub fn fill_rect_with_mode(
1798 &mut self,
1799 rect: Rect,
1800 color: [f32; 4],
1801 mode: u32,
1802 texture_id: Option<u32>,
1803 ) {
1804 self.fill_rect_with_full_params(
1805 rect,
1806 color,
1807 mode,
1808 texture_id,
1809 0.0,
1810 Rect {
1811 x: 0.0,
1812 y: 0.0,
1813 width: 1.0,
1814 height: 1.0,
1815 },
1816 );
1817 }
1818
1819 fn fill_rect_with_full_params(
1820 &mut self,
1821 rect: Rect,
1822 color: [f32; 4],
1823 mode: u32,
1824 texture_id: Option<u32>,
1825 radius: f32,
1826 uv_rect: Rect,
1827 ) {
1828 if let Some(shadow) = self.shadow_stack.last().copied()
1830 && shadow.color[3] > 0.001 {
1831 Renderer::draw_drop_shadow(
1832 self,
1833 rect,
1834 radius,
1835 shadow.color,
1836 shadow.radius,
1837 0.0, );
1839 }
1840
1841 let slice = self
1842 .slice_stack
1843 .last()
1844 .copied()
1845 .map(|(a, o)| [a, o, 1.0, 1.0])
1846 .unwrap_or([0.0, 0.0, 0.0, 1.0]);
1847 self.fill_rect_with_full_params_and_slice(
1848 rect, color, mode, texture_id, radius, uv_rect, slice,
1849 );
1850 }
1851
1852 fn fill_rect_with_full_params_and_slice(
1853 &mut self,
1854 rect: Rect,
1855 color: [f32; 4],
1856 mode: u32,
1857 texture_id: Option<u32>,
1858 radius: f32,
1859 uv_rect: Rect,
1860 slice: [f32; 4],
1861 ) {
1862 let scissor = self.clip_stack.last().copied();
1863
1864 let is_glass = mode == 7;
1865 let is_ui = !is_glass;
1866
1867 let last_call = self.draw_calls.last();
1869 let needs_new_call = self.draw_calls.is_empty()
1870 || self.current_texture_id != texture_id
1871 || last_call.unwrap().scissor_rect != scissor
1872 || last_call.unwrap().is_glass != is_glass
1873 || last_call.unwrap().is_ui != is_ui;
1874
1875 if needs_new_call {
1876 self.current_texture_id = texture_id;
1877 self.draw_calls.push(DrawCall {
1878 texture_id,
1879 scissor_rect: scissor,
1880 index_start: self.indices.len() as u32,
1881 index_count: 0,
1882 is_glass,
1883 is_ui,
1884 });
1885 }
1886
1887 let scale = self.current_scale_factor();
1888 let snap = |v: f32| (v * scale).round() / scale;
1889
1890 let base_idx = self.vertices.len() as u32;
1891 let x1 = snap(rect.x);
1892 let y1 = snap(rect.y);
1893 let x2 = snap(rect.x + rect.width);
1894 let y2 = snap(rect.y + rect.height);
1895 let z = self.current_z;
1896 let normal = [0.0, 0.0, 1.0];
1897 let screen = [self.current_width() as f32, self.current_height() as f32];
1898 let clip_rect = self.clip_stack.last().copied().unwrap_or(cvkg_core::Rect {
1899 x: -10000.0,
1900 y: -10000.0,
1901 width: 20000.0,
1902 height: 20000.0,
1903 });
1904 let clip = [clip_rect.x, clip_rect.y, clip_rect.width, clip_rect.height];
1905
1906 let (translation, scale_transform, rotation) = self.get_current_transform();
1907
1908 self.vertices.push(Vertex {
1909 position: [x1, y1, z],
1910 normal,
1911 uv: [uv_rect.x, uv_rect.y],
1912 color,
1913 mode,
1914 radius,
1915 slice,
1916 logical: [0.0, 0.0],
1917 size: [rect.width, rect.height],
1918 screen,
1919 clip,
1920 translation,
1921 scale: scale_transform,
1922 rotation,
1923 _pad: 0.0,
1924 });
1925 self.vertices.push(Vertex {
1926 position: [x2, y1, z],
1927 normal,
1928 uv: [uv_rect.x + uv_rect.width, uv_rect.y],
1929 color,
1930 mode,
1931 radius,
1932 slice,
1933 logical: [rect.width, 0.0],
1934 size: [rect.width, rect.height],
1935 screen,
1936 clip,
1937 translation,
1938 scale: scale_transform,
1939 rotation,
1940 _pad: 0.0,
1941 });
1942 self.vertices.push(Vertex {
1943 position: [x2, y2, z],
1944 normal,
1945 uv: [uv_rect.x + uv_rect.width, uv_rect.y + uv_rect.height],
1946 color,
1947 mode,
1948 radius,
1949 slice,
1950 logical: [rect.width, rect.height],
1951 size: [rect.width, rect.height],
1952 screen,
1953 clip,
1954 translation,
1955 scale: scale_transform,
1956 rotation,
1957 _pad: 0.0,
1958 });
1959 self.vertices.push(Vertex {
1960 position: [x1, y2, z],
1961 normal,
1962 uv: [uv_rect.x, uv_rect.y + uv_rect.height],
1963 color,
1964 mode,
1965 radius,
1966 slice,
1967 logical: [0.0, rect.height],
1968 size: [rect.width, rect.height],
1969 screen,
1970 clip,
1971 translation,
1972 scale: scale_transform,
1973 rotation,
1974 _pad: 0.0,
1975 });
1976
1977 self.indices.extend_from_slice(&[
1978 base_idx,
1979 base_idx + 1,
1980 base_idx + 2,
1981 base_idx,
1982 base_idx + 2,
1983 base_idx + 3,
1984 ]);
1985
1986 if let Some(call) = self.draw_calls.last_mut() {
1987 call.index_count += 6;
1988 }
1989 }
1990
1991 pub fn end_frame(&mut self, mut encoder: wgpu::CommandEncoder) {
1993 let (surface_texture, target_view, ctx_scene_texture, ctx_depth_texture_view, ctx_blur_env_bind_group_a, ctx_scene_texture_bind_group, ctx_blur_texture_a, ctx_blur_texture_b, _ctx_sampler, ctx_blur_bind_group_a, ctx_blur_bind_group_b) = if let Some(window_id) = self.current_window {
1994 let ctx = self.surfaces.get(&window_id).expect("Missing surface context");
1995 let frame = match ctx.surface.get_current_texture() {
1996 wgpu::CurrentSurfaceTexture::Success(t) => t,
1997 wgpu::CurrentSurfaceTexture::Suboptimal(t) => {
1998 ctx.surface.configure(&self.device, &ctx.config);
1999 t
2000 }
2001 _ => return, };
2003 let view = frame.texture.create_view(&wgpu::TextureViewDescriptor::default());
2004 (Some(frame), view, &ctx.scene_texture, &ctx.depth_texture_view, &ctx.blur_env_bind_group_a, &ctx.scene_texture_bind_group, &ctx.blur_texture_a, &ctx.blur_texture_b, &ctx.sampler, &ctx.blur_bind_group_a, &ctx.blur_bind_group_b)
2005 } else {
2006 let ctx = self.headless_context.as_ref().expect("No headless context for end_frame");
2007 (None, ctx.output_view.clone(), &ctx.scene_texture, &ctx.depth_texture_view, &ctx.blur_env_bind_group_a, &ctx.scene_texture_bind_group, &ctx.blur_texture_a, &ctx.blur_texture_b, &ctx.sampler, &ctx.blur_bind_group_a, &ctx.blur_bind_group_b)
2008 };
2009
2010 {
2012 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2013 label: Some("Surtr P1 Opaque Background"),
2014 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2015 view: ctx_scene_texture,
2016 resolve_target: None,
2017 ops: wgpu::Operations {
2018 load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 0.0 }),
2019 store: wgpu::StoreOp::Store,
2020 },
2021 depth_slice: None,
2022 })],
2023 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
2024 view: ctx_depth_texture_view,
2025 depth_ops: Some(wgpu::Operations {
2026 load: wgpu::LoadOp::Clear(1.0),
2027 store: wgpu::StoreOp::Store,
2028 }),
2029 stencil_ops: None,
2030 }),
2031 occlusion_query_set: None,
2032 timestamp_writes: None,
2033 multiview_mask: None,
2034 });
2035
2036 p.set_pipeline(&self.background_pipeline);
2038 p.set_bind_group(0, &self.dummy_texture_bind_group, &[]);
2039 p.set_bind_group(1, ctx_blur_env_bind_group_a, &[]); p.set_bind_group(2, &self.berserker_bind_group, &[]);
2041 p.draw(0..6, 0..1);
2042
2043 if !self.draw_calls.is_empty() {
2045 p.set_pipeline(&self.pipeline);
2046 p.set_vertex_buffer(0, self.vertex_buffer.slice(..));
2047 p.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
2048 p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
2049 p.set_bind_group(2, &self.berserker_bind_group, &[]);
2050
2051 for call in self.draw_calls.iter().filter(|c| !c.is_glass && !c.is_ui) {
2052 let bg = if let Some(id) = call.texture_id {
2053 if id == 0 {
2054 &self.mega_atlas_bind_group
2055 } else {
2056 self.texture_bind_groups
2057 .get(id as usize)
2058 .unwrap_or(&self.dummy_texture_bind_group)
2059 }
2060 } else {
2061 &self.dummy_texture_bind_group
2062 };
2063 p.set_bind_group(0, bg, &[]);
2064 p.draw_indexed(
2065 call.index_start..call.index_start + call.index_count,
2066 0,
2067 0..1,
2068 );
2069 self.telemetry.draw_calls += 1;
2070 self.telemetry.vertices += call.index_count;
2071 }
2072 }
2073 }
2074
2075 {
2078 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2080 label: Some("Surtr Blur Extract"),
2081 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2082 view: ctx_blur_texture_a,
2083 resolve_target: None,
2084 ops: wgpu::Operations {
2085 load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 0.0 }),
2086 store: wgpu::StoreOp::Store,
2087 },
2088 depth_slice: None,
2089 })],
2090 ..Default::default()
2091 });
2092 p.set_pipeline(&self.bloom_extract_pipeline); p.set_bind_group(0, ctx_scene_texture_bind_group, &[]);
2094 p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
2095 p.set_bind_group(2, &self.berserker_bind_group, &[]);
2096 p.draw(0..6, 0..1);
2097 }
2098
2099 let blur_iters: u32 = 4;
2100 for _i in 0..blur_iters {
2101 {
2102 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2103 label: Some("Blur H"),
2104 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2105 view: ctx_blur_texture_b,
2106 resolve_target: None,
2107 ops: wgpu::Operations {
2108 load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 0.0 }),
2109 store: wgpu::StoreOp::Store,
2110 },
2111 depth_slice: None,
2112 })],
2113 ..Default::default()
2114 });
2115 p.set_pipeline(&self.blur_h_pipeline);
2116 p.set_bind_group(0, ctx_blur_bind_group_a, &[]);
2117 p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
2118 p.set_bind_group(2, &self.berserker_bind_group, &[]);
2119 p.draw(0..6, 0..1);
2120 }
2121 {
2122 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2123 label: Some("Blur V"),
2124 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2125 view: ctx_blur_texture_a,
2126 resolve_target: None,
2127 ops: wgpu::Operations {
2128 load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 0.0 }),
2129 store: wgpu::StoreOp::Store,
2130 },
2131 depth_slice: None,
2132 })],
2133 ..Default::default()
2134 });
2135 p.set_pipeline(&self.blur_v_pipeline);
2136 p.set_bind_group(0, ctx_blur_bind_group_b, &[]);
2137 p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
2138 p.set_bind_group(2, &self.berserker_bind_group, &[]);
2139 p.draw(0..6, 0..1);
2140 }
2141 }
2142
2143 {
2145 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2146 label: Some("Surtr P3 Liquid Glass"),
2147 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2148 view: ctx_scene_texture, resolve_target: None,
2150 ops: wgpu::Operations {
2151 load: wgpu::LoadOp::Load,
2152 store: wgpu::StoreOp::Store,
2153 },
2154 depth_slice: None,
2155 })],
2156 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
2157 view: ctx_depth_texture_view,
2158 depth_ops: Some(wgpu::Operations {
2159 load: wgpu::LoadOp::Load,
2160 store: wgpu::StoreOp::Store,
2161 }),
2162 stencil_ops: None,
2163 }),
2164 occlusion_query_set: None,
2165 timestamp_writes: None,
2166 multiview_mask: None,
2167 });
2168
2169 p.set_pipeline(&self.pipeline);
2170 p.set_vertex_buffer(0, self.vertex_buffer.slice(..));
2171 p.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
2172 p.set_bind_group(1, ctx_blur_env_bind_group_a, &[]); p.set_bind_group(2, &self.berserker_bind_group, &[]);
2174
2175 for call in self.draw_calls.iter().filter(|c| c.is_glass) {
2176 let bg = if let Some(id) = call.texture_id {
2177 if id == 0 {
2178 &self.mega_atlas_bind_group
2179 } else {
2180 self.texture_bind_groups
2181 .get(id as usize)
2182 .unwrap_or(&self.dummy_texture_bind_group)
2183 }
2184 } else {
2185 &self.dummy_texture_bind_group
2186 };
2187 p.set_bind_group(0, bg, &[]);
2188 p.draw_indexed(
2189 call.index_start..call.index_start + call.index_count,
2190 0,
2191 0..1,
2192 );
2193 self.telemetry.draw_calls += 1;
2194 self.telemetry.vertices += call.index_count;
2195 }
2196 }
2197
2198 {
2200 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2201 label: Some("Surtr P4 UI Layer"),
2202 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2203 view: ctx_scene_texture,
2204 resolve_target: None,
2205 ops: wgpu::Operations {
2206 load: wgpu::LoadOp::Load,
2207 store: wgpu::StoreOp::Store,
2208 },
2209 depth_slice: None,
2210 })],
2211 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
2212 view: ctx_depth_texture_view,
2213 depth_ops: Some(wgpu::Operations {
2214 load: wgpu::LoadOp::Load,
2215 store: wgpu::StoreOp::Store,
2216 }),
2217 stencil_ops: None,
2218 }),
2219 occlusion_query_set: None,
2220 timestamp_writes: None,
2221 multiview_mask: None,
2222 });
2223
2224 p.set_pipeline(&self.pipeline);
2225 p.set_vertex_buffer(0, self.vertex_buffer.slice(..));
2226 p.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
2227 p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
2228 p.set_bind_group(2, &self.berserker_bind_group, &[]);
2229
2230 for call in self.draw_calls.iter().filter(|c| c.is_ui) {
2231 let bg = if let Some(id) = call.texture_id {
2232 if id == 0 {
2233 &self.mega_atlas_bind_group
2234 } else {
2235 self.texture_bind_groups
2236 .get(id as usize)
2237 .unwrap_or(&self.dummy_texture_bind_group)
2238 }
2239 } else {
2240 &self.dummy_texture_bind_group
2241 };
2242 p.set_bind_group(0, bg, &[]);
2243 p.draw_indexed(
2244 call.index_start..call.index_start + call.index_count,
2245 0,
2246 0..1,
2247 );
2248 self.telemetry.draw_calls += 1;
2249 self.telemetry.vertices += call.index_count;
2250 }
2251 }
2252
2253 {
2255 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2256 label: Some("Surtr Bloom Extract"),
2257 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2258 view: ctx_blur_texture_a,
2259 resolve_target: None,
2260 ops: wgpu::Operations {
2261 load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 0.0 }),
2262 store: wgpu::StoreOp::Store,
2263 },
2264 depth_slice: None,
2265 })],
2266 ..Default::default()
2267 });
2268 p.set_pipeline(&self.bloom_extract_pipeline);
2269 p.set_bind_group(0, ctx_scene_texture_bind_group, &[]);
2270 p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
2271 p.set_bind_group(2, &self.berserker_bind_group, &[]);
2272 p.draw(0..6, 0..1);
2273 }
2274
2275 for _ in 0..2 {
2277 {
2278 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2279 label: Some("Bloom Blur H"),
2280 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2281 view: ctx_blur_texture_b,
2282 resolve_target: None,
2283 ops: wgpu::Operations {
2284 load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 0.0 }),
2285 store: wgpu::StoreOp::Store,
2286 },
2287 depth_slice: None,
2288 })],
2289 ..Default::default()
2290 });
2291 p.set_pipeline(&self.blur_h_pipeline);
2292 p.set_bind_group(0, ctx_blur_bind_group_a, &[]);
2293 p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
2294 p.set_bind_group(2, &self.berserker_bind_group, &[]);
2295 p.draw(0..6, 0..1);
2296 }
2297 {
2298 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2299 label: Some("Bloom Blur V"),
2300 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2301 view: ctx_blur_texture_a,
2302 resolve_target: None,
2303 ops: wgpu::Operations {
2304 load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 0.0 }),
2305 store: wgpu::StoreOp::Store,
2306 },
2307 depth_slice: None,
2308 })],
2309 ..Default::default()
2310 });
2311 p.set_pipeline(&self.blur_v_pipeline);
2312 p.set_bind_group(0, ctx_blur_bind_group_b, &[]);
2313 p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
2314 p.set_bind_group(2, &self.berserker_bind_group, &[]);
2315 p.draw(0..6, 0..1);
2316 }
2317 }
2318
2319 {
2321 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2322 label: Some("Surtr P7 Composite"),
2323 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2324 view: &target_view,
2325 resolve_target: None,
2326 ops: wgpu::Operations {
2327 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
2328 store: wgpu::StoreOp::Store,
2329 },
2330 depth_slice: None,
2331 })],
2332 ..Default::default()
2333 });
2334 p.set_pipeline(&self.composite_pipeline);
2335 p.set_bind_group(0, ctx_scene_texture_bind_group, &[]);
2338 p.set_bind_group(1, ctx_blur_env_bind_group_a, &[]); p.set_bind_group(2, &self.berserker_bind_group, &[]);
2340 p.draw(0..6, 0..1);
2341 self.telemetry.draw_calls += 1;
2342 }
2343
2344 self.telemetry.frame_time_ms = self.last_frame_start.elapsed().as_secs_f32() * 1000.0;
2345 self.update_vram_telemetry();
2346 self.queue.submit(Some(encoder.finish()));
2347 if let Some(f) = surface_texture {
2348 f.present();
2349 }
2350 }
2351}
2352
2353impl cvkg_core::ElapsedTime for SurtrRenderer {
2354 fn delta_time(&self) -> f32 {
2355 self.current_scene.delta_time
2356 }
2357
2358 fn elapsed_time(&self) -> f32 {
2359 self.start_time.elapsed().as_secs_f32()
2360 }
2361}
2362
2363impl cvkg_core::Renderer for SurtrRenderer {
2364 fn is_over_budget(&self) -> bool {
2365 self.frame_budget.allow_degradation &&
2366 self.last_frame_start.elapsed().as_secs_f32() * 1000.0 > self.frame_budget.target_ms
2367 }
2368
2369 fn fill_rect(&mut self, rect: Rect, color: [f32; 4]) {
2371 self.fill_rect_with_mode(rect, self.apply_opacity(color), 0, None);
2372 }
2373
2374 fn fill_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4]) {
2375 self.fill_rect_with_full_params(
2376 rect,
2377 self.apply_opacity(color),
2378 3,
2379 None,
2380 radius,
2381 Rect {
2382 x: 0.0,
2383 y: 0.0,
2384 width: 1.0,
2385 height: 1.0,
2386 },
2387 );
2388 }
2389
2390 fn fill_ellipse(&mut self, rect: Rect, color: [f32; 4]) {
2391 self.fill_rect_with_full_params(
2392 rect,
2393 self.apply_opacity(color),
2394 4,
2395 None,
2396 0.0,
2397 Rect {
2398 x: 0.0,
2399 y: 0.0,
2400 width: 1.0,
2401 height: 1.0,
2402 },
2403 );
2404 }
2405
2406 fn bifrost(&mut self, rect: Rect, blur: f32, _saturation: f32, opacity: f32) {
2407 let screen_uv = Rect {
2409 x: rect.x / self.current_width() as f32,
2410 y: rect.y / self.current_height() as f32,
2411 width: rect.width / self.current_width() as f32,
2412 height: rect.height / self.current_height() as f32,
2413 };
2414 self.fill_rect_with_full_params(rect, [1.0, 1.0, 1.0, opacity], 7, None, blur, screen_uv);
2417 }
2418
2419 fn gungnir(&mut self, rect: Rect, color: [f32; 4], radius: f32, intensity: f32) {
2420 let center_x = rect.x + rect.width * 0.5;
2423 let center_y = rect.y + rect.height * 0.5;
2424 let max_dim = rect.width.max(rect.height) * 0.5 + radius;
2425
2426 for i in 0..8 {
2428 let alpha = intensity / (i as f32 + 1.0) * 0.3;
2429 let glow_color = [color[0], color[1], color[2], alpha];
2430 self.fill_rect_with_mode(
2431 Rect {
2432 x: center_x - max_dim - i as f32 * 2.0,
2433 y: center_y - max_dim - i as f32 * 2.0,
2434 width: max_dim * 2.0 + i as f32 * 4.0,
2435 height: max_dim * 2.0 + i as f32 * 4.0,
2436 },
2437 glow_color,
2438 8, None,
2440 );
2441 }
2442 }
2443
2444 fn stroke_rect(&mut self, rect: Rect, color: [f32; 4], stroke_width: f32) {
2445 let c = self.apply_opacity(color);
2446 let hw = stroke_width;
2447 self.fill_rect_with_mode(
2449 Rect {
2450 x: rect.x,
2451 y: rect.y,
2452 width: rect.width,
2453 height: hw,
2454 },
2455 c,
2456 1,
2457 None,
2458 );
2459 self.fill_rect_with_mode(
2460 Rect {
2461 x: rect.x,
2462 y: rect.y + rect.height - hw,
2463 width: rect.width,
2464 height: hw,
2465 },
2466 c,
2467 1,
2468 None,
2469 );
2470 self.fill_rect_with_mode(
2471 Rect {
2472 x: rect.x,
2473 y: rect.y,
2474 width: hw,
2475 height: rect.height,
2476 },
2477 c,
2478 1,
2479 None,
2480 );
2481 self.fill_rect_with_mode(
2482 Rect {
2483 x: rect.x + rect.width - hw,
2484 y: rect.y,
2485 width: hw,
2486 height: rect.height,
2487 },
2488 c,
2489 1,
2490 None,
2491 );
2492 }
2493
2494 fn stroke_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4], stroke_width: f32) {
2495 self.fill_rect_with_full_params(
2496 rect,
2497 self.apply_opacity(color),
2498 17,
2499 None,
2500 radius,
2501 Rect {
2502 x: stroke_width,
2503 y: 0.0,
2504 width: 0.0,
2505 height: 0.0,
2506 },
2507 );
2508 }
2509
2510 fn stroke_ellipse(&mut self, _rect: Rect, _color: [f32; 4], _stroke_width: f32) {
2511 }
2513
2514 fn draw_linear_gradient(
2515 &mut self,
2516 rect: Rect,
2517 start_color: [f32; 4],
2518 end_color: [f32; 4],
2519 angle: f32,
2520 ) {
2521 self.fill_rect_with_full_params_and_slice(
2522 rect,
2523 self.apply_opacity(start_color),
2524 15,
2525 None,
2526 0.0,
2527 Rect {
2528 x: angle,
2529 y: 0.0,
2530 width: 1.0,
2531 height: 1.0,
2532 },
2533 end_color,
2534 );
2535 }
2536
2537 fn draw_radial_gradient(&mut self, rect: Rect, inner_color: [f32; 4], outer_color: [f32; 4]) {
2538 self.fill_rect_with_full_params_and_slice(
2539 rect,
2540 self.apply_opacity(inner_color),
2541 16,
2542 None,
2543 0.0,
2544 Rect {
2545 x: 0.0,
2546 y: 0.0,
2547 width: 1.0,
2548 height: 1.0,
2549 },
2550 outer_color,
2551 );
2552 }
2553
2554 fn draw_drop_shadow(
2555 &mut self,
2556 rect: Rect,
2557 radius: f32,
2558 color: [f32; 4],
2559 blur: f32,
2560 spread: f32,
2561 ) {
2562 let margin = blur + spread;
2563 let inflated = Rect {
2564 x: rect.x - margin,
2565 y: rect.y - margin,
2566 width: rect.width + margin * 2.0,
2567 height: rect.height + margin * 2.0,
2568 };
2569 self.fill_rect_with_full_params(
2571 inflated,
2572 self.apply_opacity(color),
2573 18,
2574 None,
2575 radius,
2576 Rect {
2577 x: margin,
2578 y: blur,
2579 width: 0.0,
2580 height: 0.0,
2581 },
2582 );
2583 }
2584
2585 fn stroke_dashed_rounded_rect(
2586 &mut self,
2587 rect: Rect,
2588 radius: f32,
2589 color: [f32; 4],
2590 width: f32,
2591 dash: f32,
2592 gap: f32,
2593 ) {
2594 self.fill_rect_with_full_params(
2595 rect,
2596 self.apply_opacity(color),
2597 19,
2598 None,
2599 radius,
2600 Rect {
2601 x: width,
2602 y: dash,
2603 width: gap,
2604 height: 0.0,
2605 },
2606 );
2607 }
2608
2609 fn draw_9slice(
2610 &mut self,
2611 image_name: &str,
2612 rect: Rect,
2613 left: f32,
2614 top: f32,
2615 right: f32,
2616 bottom: f32,
2617 ) {
2618 let c = self.apply_opacity([1.0, 1.0, 1.0, 1.0]);
2619 let tid = self.get_texture_id(image_name);
2620 self.fill_rect_with_full_params(
2621 rect,
2622 c,
2623 20,
2624 tid,
2625 bottom,
2626 Rect {
2627 x: left,
2628 y: top,
2629 width: right,
2630 height: 0.0,
2631 },
2632 );
2633 }
2634
2635 fn draw_line(
2636 &mut self,
2637 x1: f32,
2638 y1: f32,
2639 x2: f32,
2640 y2: f32,
2641 color: [f32; 4],
2642 stroke_width: f32,
2643 ) {
2644 let dx = x2 - x1;
2645 let dy = y2 - y1;
2646 let len = (dx * dx + dy * dy).sqrt();
2647 if len < 0.001 {
2648 return;
2649 }
2650
2651 let c = self.apply_opacity(color);
2652 let tid = self.get_texture_id("__mega_atlas");
2653
2654 self.fill_rect_with_mode(
2655 Rect {
2656 x: (x1 + x2) / 2.0 - len / 2.0,
2657 y: (y1 + y2) / 2.0 - stroke_width / 2.0,
2658 width: len,
2659 height: stroke_width,
2660 },
2661 c,
2662 1, tid,
2664 );
2665 }
2666
2667 fn draw_image(&mut self, image_name: &str, rect: Rect) {
2668 let tid = self.get_texture_id("__mega_atlas");
2669 let uv_rect = self.image_uv_registry.get(image_name).copied().unwrap_or(Rect { x: 0.0, y: 0.0, width: 1.0, height: 1.0 });
2670 self.fill_rect_with_full_params(
2671 rect,
2672 [1.0, 1.0, 1.0, 1.0],
2673 2,
2674 tid,
2675 0.0,
2676 uv_rect,
2677 );
2678 }
2679
2680 fn draw_text(&mut self, text: &str, x: f32, y: f32, size: f32, color: [f32; 4]) {
2681 let scaled_size = size * self.current_scale_factor();
2683 let shaped = self.text_engine.shape(text, "sans-serif", scaled_size);
2684 let c = self.apply_opacity(color);
2685
2686 for glyph in shaped.glyphs {
2687 let cache_key = glyph.cache_key;
2688
2689 let (uv_rect, w, h) = if let Some(info) = self.text_cache.get(&cache_key) {
2690 *info
2691 } else {
2692 if let Some(image) = self.text_engine.rasterize(cache_key) {
2693 let gw = image.width;
2694 let gh = image.height;
2695
2696 if let Some((nx, ny)) = self.atlas_packer.pack(gw, gh) {
2697 let mut rgba_data = Vec::with_capacity((gw * gh * 4) as usize);
2698 for alpha in &image.data {
2699 rgba_data.push(255);
2700 rgba_data.push(255);
2701 rgba_data.push(255);
2702 rgba_data.push(*alpha);
2703 }
2704
2705 self.queue.write_texture(
2706 wgpu::TexelCopyTextureInfo {
2707 texture: &self.mega_atlas_tex,
2708 mip_level: 0,
2709 origin: wgpu::Origin3d { x: nx, y: ny, z: 0 },
2710 aspect: wgpu::TextureAspect::All,
2711 },
2712 &rgba_data,
2713 wgpu::TexelCopyBufferLayout {
2714 offset: 0,
2715 bytes_per_row: Some(gw * 4),
2716 rows_per_image: Some(gh),
2717 },
2718 wgpu::Extent3d {
2719 width: gw,
2720 height: gh,
2721 depth_or_array_layers: 1,
2722 },
2723 );
2724
2725 let info = (
2726 Rect {
2727 x: nx as f32 / 4096.0,
2728 y: ny as f32 / 4096.0,
2729 width: gw as f32 / 4096.0,
2730 height: gh as f32 / 4096.0,
2731 },
2732 gw as f32,
2733 gh as f32,
2734 );
2735 self.text_cache.insert(cache_key, info);
2736 info
2737 } else {
2738 (Rect::zero(), 0.0, 0.0)
2739 }
2740 } else {
2741 (Rect::zero(), 0.0, 0.0)
2742 }
2743 };
2744
2745 if w > 0.0 {
2746 let glyph_rect = Rect {
2749 x: x + glyph.x / self.current_scale_factor(),
2750 y: y + glyph.y / self.current_scale_factor(),
2751 width: w / self.current_scale_factor(),
2752 height: h / self.current_scale_factor(),
2753 };
2754 let tid = self.get_texture_id("__mega_atlas");
2755 self.fill_rect_with_full_params(glyph_rect, c, 6, tid, 0.0, uv_rect);
2756 }
2757 }
2758 }
2759
2760 fn measure_text(&mut self, text: &str, size: f32) -> (f32, f32) {
2762 let shaped = self.text_engine.shape(text, "sans-serif", size);
2763 (shaped.width, shaped.height)
2764 }
2765
2766 fn draw_texture(&mut self, texture_id: u32, rect: Rect) {
2767 self.fill_rect_with_full_params(
2768 rect,
2769 [1.0, 1.0, 1.0, 1.0],
2770 2,
2771 Some(texture_id),
2772 0.0,
2773 Rect {
2774 x: 0.0,
2775 y: 0.0,
2776 width: 1.0,
2777 height: 1.0,
2778 },
2779 );
2780 }
2781
2782 fn load_image(&mut self, name: &str, data: &[u8]) {
2784 if self.image_uv_registry.contains_key(name) {
2785 return;
2786 }
2787 let img_result = image::load_from_memory(data);
2788 let img = match img_result {
2789 Ok(img) => img.to_rgba8(),
2790 Err(e) => {
2791 eprintln!("Failed to load image {}: {}", name, e);
2792 image::RgbaImage::from_pixel(1, 1, image::Rgba([0, 0, 0, 255]))
2793 }
2794 };
2795 let (width, height) = img.dimensions();
2796
2797 if let Some((x, y)) = self.atlas_packer.pack(width, height) {
2799 let size = wgpu::Extent3d {
2800 width,
2801 height,
2802 depth_or_array_layers: 1,
2803 };
2804 self.queue.write_texture(
2805 wgpu::TexelCopyTextureInfo {
2806 texture: &self.mega_atlas_tex,
2807 mip_level: 0,
2808 origin: wgpu::Origin3d { x, y, z: 0 },
2809 aspect: wgpu::TextureAspect::All,
2810 },
2811 &img,
2812 wgpu::TexelCopyBufferLayout {
2813 offset: 0,
2814 bytes_per_row: Some(4 * width),
2815 rows_per_image: Some(height),
2816 },
2817 size,
2818 );
2819
2820 let uv_rect = Rect {
2822 x: x as f32 / 4096.0,
2823 y: y as f32 / 4096.0,
2824 width: width as f32 / 4096.0,
2825 height: height as f32 / 4096.0,
2826 };
2827 self.image_uv_registry.insert(name.to_string(), uv_rect);
2828 self.texture_registry.insert(name.to_string(), 0);
2829 } else {
2830 eprintln!("Mega-Atlas is FULL! Could not pack image: {}", name);
2831 }
2832 }
2833
2834 fn push_clip_rect(&mut self, rect: Rect) {
2835 self.clip_stack.push(rect);
2836 }
2837
2838 fn pop_clip_rect(&mut self) {
2839 self.clip_stack.pop();
2840 }
2841
2842 fn current_clip_rect(&self) -> Rect {
2843 self.clip_stack
2844 .last()
2845 .copied()
2846 .unwrap_or(Rect::new(
2847 0.0,
2848 0.0,
2849 self.current_width() as f32,
2850 self.current_height() as f32,
2851 ))
2852 }
2853
2854 fn memoize(&mut self, _id: u64, _data_hash: u64, render_fn: &dyn Fn(&mut dyn Renderer)) {
2855 render_fn(self);
2856 }
2857
2858 fn push_opacity(&mut self, opacity: f32) {
2859 let current = self.opacity_stack.last().copied().unwrap_or(1.0);
2860 self.opacity_stack.push(current * opacity);
2861 }
2862
2863 fn pop_opacity(&mut self) {
2864 self.opacity_stack.pop();
2865 }
2866
2867 fn push_shadow(&mut self, radius: f32, color: [f32; 4], offset: [f32; 2]) {
2868 self.shadow_stack.push(ShadowState {
2869 radius,
2870 color,
2871 _offset: offset,
2872 });
2873 }
2874
2875 fn pop_shadow(&mut self) {
2876 self.shadow_stack.pop();
2877 }
2878
2879 fn push_transform(&mut self, translation: [f32; 2], scale: [f32; 2], rotation: f32) {
2880 let (current_t, current_s, current_r) = self.transform_stack.last().copied().unwrap_or(([0.0, 0.0], [1.0, 1.0], 0.0));
2881
2882 let new_t = [current_t[0] + translation[0] * current_s[0], current_t[1] + translation[1] * current_s[1]];
2886 let new_s = [current_s[0] * scale[0], current_s[1] * scale[1]];
2887 let new_r = current_r + rotation;
2888
2889 self.transform_stack.push((new_t, new_s, new_r));
2890 }
2891
2892 fn pop_transform(&mut self) {
2893 self.transform_stack.pop();
2894 }
2895
2896 fn set_theme(&mut self, theme: ColorTheme) {
2897
2898 self.current_theme = theme;
2899 self.queue
2900 .write_buffer(&self.theme_buffer, 0, bytemuck::bytes_of(&theme));
2901 }
2902
2903 fn set_rage(&mut self, rage: f32) {
2904 self.current_scene.berzerker_rage = rage;
2905 }
2907
2908 fn trigger_shatter_event(&mut self, origin: [f32; 2], force: f32) {
2909 self.current_scene.shatter_origin = origin;
2910 self.current_scene.shatter_time = self.current_scene.time;
2911 self.current_scene.shatter_force = force;
2912 }
2913
2914 fn push_mjolnir_slice(&mut self, angle: f32, offset: f32) {
2917 self.slice_stack.push((angle, offset));
2918 }
2919
2920 fn pop_mjolnir_slice(&mut self) {
2922 self.slice_stack.pop();
2923 }
2924
2925 fn mjolnir_shatter(&mut self, rect: Rect, pieces: u32, force: f32, color: [f32; 4]) {
2926 self.shatter_internal(rect, pieces, force, color, 8);
2927 }
2928
2929 fn mjolnir_fluid_shatter(&mut self, rect: Rect, pieces: u32, force: f32, color: [f32; 4]) {
2930 self.shatter_internal(rect, pieces, force, color, 11);
2931 }
2932
2933 fn draw_mjolnir_bolt(&mut self, from: [f32; 2], to: [f32; 2], color: [f32; 4]) {
2934 self.recursive_bolt(from, to, 4, color);
2935 }
2936
2937 fn upload_data_texture(&mut self, id: &str, data: &[f32], width: u32, height: u32) {
2938 let size = wgpu::Extent3d {
2939 width,
2940 height,
2941 depth_or_array_layers: 1,
2942 };
2943 let texture = self.device.create_texture(&wgpu::TextureDescriptor {
2944 label: Some(id),
2945 size,
2946 mip_level_count: 1,
2947 sample_count: 1,
2948 dimension: wgpu::TextureDimension::D2,
2949 format: wgpu::TextureFormat::R32Float,
2950 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
2951 view_formats: &[],
2952 });
2953 self.queue.write_texture(
2954 wgpu::TexelCopyTextureInfo {
2955 texture: &texture,
2956 mip_level: 0,
2957 origin: wgpu::Origin3d::ZERO,
2958 aspect: wgpu::TextureAspect::All,
2959 },
2960 bytemuck::cast_slice(data),
2961 wgpu::TexelCopyBufferLayout {
2962 offset: 0,
2963 bytes_per_row: Some(4 * width),
2964 rows_per_image: Some(height),
2965 },
2966 size,
2967 );
2968 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
2969 let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
2970 address_mode_u: wgpu::AddressMode::ClampToEdge,
2971 address_mode_v: wgpu::AddressMode::ClampToEdge,
2972 mag_filter: wgpu::FilterMode::Linear,
2973 ..Default::default()
2974 });
2975 let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2976 layout: &self.texture_bind_group_layout,
2977 entries: &[
2978 wgpu::BindGroupEntry {
2979 binding: 0,
2980 resource: wgpu::BindingResource::TextureView(&view),
2981 },
2982 wgpu::BindGroupEntry {
2983 binding: 1,
2984 resource: wgpu::BindingResource::Sampler(&sampler),
2985 },
2986 ],
2987 label: Some(id),
2988 });
2989 self.texture_bind_groups.push(bind_group);
2990 let tid = (self.texture_bind_groups.len() - 1) as u32;
2991 self.texture_registry.insert(id.to_string(), tid);
2992 }
2993
2994 fn draw_heatmap(&mut self, texture_id: &str, rect: Rect, _palette: &str) {
2995 let tid = self.get_texture_id(texture_id);
2996 self.fill_rect_with_mode(rect, [1.0, 1.0, 1.0, 1.0], 12, tid);
2997 }
2998
2999 fn draw_mesh(&mut self, mesh: &Mesh, color: [f32; 4], transform: glam::Mat4) {
3000 let base_idx = self.vertices.len() as u32;
3001 let screen = [self.current_width() as f32, self.current_height() as f32];
3002
3003 for i in 0..mesh.vertices.len() {
3004 let pos = transform.transform_point3(glam::Vec3::from(mesh.vertices[i]));
3005 let norm = transform.transform_vector3(glam::Vec3::from(mesh.normals[i]));
3006
3007 let (translation, scale_transform, rotation) = self.get_current_transform();
3008 self.vertices.push(Vertex {
3009 position: pos.to_array(),
3010 normal: norm.to_array(),
3011 uv: [0.0, 0.0],
3012 color,
3013 mode: 13, radius: 0.0,
3015 slice: [0.0, 0.0, 0.0, 1.0],
3016 logical: [0.0, 0.0],
3017 size: [0.0, 0.0],
3018 screen,
3019 clip: [-10000.0, -10000.0, 20000.0, 20000.0],
3020 translation,
3021 scale: scale_transform,
3022 rotation,
3023 _pad: 0.0,
3024 });
3025 }
3026
3027 for idx in &mesh.indices {
3028 self.indices.push(base_idx + idx);
3029 }
3030
3031 if self.draw_calls.is_empty() || self.current_texture_id.is_some() {
3032 self.current_texture_id = None;
3033 self.draw_calls.push(DrawCall {
3034 texture_id: None,
3035 scissor_rect: self.clip_stack.last().copied(),
3036 index_start: (self.indices.len() as u32) - (mesh.indices.len() as u32),
3037 index_count: mesh.indices.len() as u32,
3038 is_glass: false,
3039 is_ui: false,
3040 });
3041 } else {
3042 self.draw_calls.last_mut().unwrap().index_count += mesh.indices.len() as u32;
3043 }
3044 }
3045
3046 fn register_shared_element(&mut self, id: &str, rect: Rect) {
3047 self.shared_elements.insert(id.to_string(), rect);
3048 }
3049
3050 fn set_z_index(&mut self, z: f32) {
3051 self.current_z = z;
3052 }
3053
3054 fn get_z_index(&self) -> f32 {
3055 self.current_z
3056 }
3057
3058
3059 fn request_redraw(&mut self) {
3060 self.redraw_requested = true;
3061 }
3062}
3063
3064fn usvg_to_lyon(path: &usvg::Path) -> lyon::path::Path {
3067 let mut builder = lyon::path::Path::builder();
3068 for segment in path.data().segments() {
3069 match segment {
3070 usvg::tiny_skia_path::PathSegment::MoveTo(p) => {
3071 builder.begin(lyon::math::point(p.x, p.y));
3072 }
3073 usvg::tiny_skia_path::PathSegment::LineTo(p) => {
3074 builder.line_to(lyon::math::point(p.x, p.y));
3075 }
3076 usvg::tiny_skia_path::PathSegment::QuadTo(p1, p) => {
3077 builder.quadratic_bezier_to(
3078 lyon::math::point(p1.x, p1.y),
3079 lyon::math::point(p.x, p.y),
3080 );
3081 }
3082 usvg::tiny_skia_path::PathSegment::CubicTo(p1, p2, p) => {
3083 builder.cubic_bezier_to(
3084 lyon::math::point(p1.x, p1.y),
3085 lyon::math::point(p2.x, p2.y),
3086 lyon::math::point(p.x, p.y),
3087 );
3088 }
3089 usvg::tiny_skia_path::PathSegment::Close => {
3090 builder.end(true);
3091 }
3092 }
3093 }
3094 builder.build()
3095}
3096
3097impl SurtrRenderer {
3098 fn get_current_transform(&self) -> ([f32; 2], [f32; 2], f32) {
3099 self.transform_stack
3100 .last()
3101 .cloned()
3102 .unwrap_or(([0.0, 0.0], [1.0, 1.0], 0.0))
3103 }
3104}
3105
3106struct SceneVertexConstructor {
3107 color: [f32; 4],
3108 translation: [f32; 2],
3109 scale: [f32; 2],
3110 rotation: f32,
3111}
3112
3113impl FillVertexConstructor<Vertex> for SceneVertexConstructor {
3114 fn new_vertex(&mut self, vertex: FillVertex) -> Vertex {
3115 Vertex {
3116 position: [vertex.position().x, vertex.position().y, 0.0],
3117 normal: [0.0, 0.0, 1.0],
3118 uv: [0.0, 0.0],
3119 color: self.color,
3120 mode: 0,
3121 radius: 0.0,
3122 slice: [0.0, 0.0, 0.0, 1.0],
3123 logical: [vertex.position().x, vertex.position().y],
3124 size: [1.0, 1.0],
3125 screen: [0.0, 0.0],
3126 clip: [-10000.0, -10000.0, 20000.0, 20000.0],
3127 translation: self.translation,
3128 scale: self.scale,
3129 rotation: self.rotation,
3130 _pad: 0.0,
3131 }
3132 }
3133}
3134
3135impl Drop for SurtrRenderer {
3136 fn drop(&mut self) {
3137 let _ = self.device.poll(wgpu::PollType::Wait {
3139 submission_index: None,
3140 timeout: None,
3141 });
3142 }
3143}
3144
3145impl cvkg_core::FrameRenderer<wgpu::CommandEncoder> for SurtrRenderer {
3146 fn begin_frame(&mut self) -> wgpu::CommandEncoder {
3147 cvkg_core::begin_render_phase();
3148 let id = self.current_window.expect("No target window set for frame. Call set_target_window first.");
3149 self.begin_frame(id)
3150 }
3151
3152 fn render_frame(&mut self) {
3153 if LAYOUT_DIRTY.swap(false, Ordering::AcqRel)
3156 && let Some(window_id) = self.current_window
3157 && let Some(surface_ctx) = self.surfaces.get(&window_id) {
3158 let w = surface_ctx.config.width as f32;
3159 let h = surface_ctx.config.height as f32;
3160 let border_rect = cvkg_core::Rect { x: 0.0, y: 0.0, width: w, height: h };
3161 self.stroke_rect(border_rect, [1.0, 0.0, 0.0, 1.0], 10.0);
3163 }
3164
3165 self.queue.write_buffer(&self.vertex_buffer, 0, bytemuck::cast_slice(&self.vertices));
3167 self.queue.write_buffer(&self.index_buffer, 0, bytemuck::cast_slice(&self.indices));
3168
3169 self.current_scene.time = self.start_time.elapsed().as_secs_f32();
3171 self.queue.write_buffer(&self.scene_buffer, 0, bytemuck::bytes_of(&self.current_scene));
3172 self.queue.write_buffer(&self.theme_buffer, 0, bytemuck::bytes_of(&self.current_theme));
3173 }
3174
3175 fn end_frame(&mut self, encoder: wgpu::CommandEncoder) {
3176 self.end_frame(encoder);
3177 cvkg_core::end_render_phase();
3178 }
3179}
3180
3181impl SurtrRenderer {
3182 fn apply_opacity(&self, mut color: [f32; 4]) -> [f32; 4] {
3184 if let Some(&alpha) = self.opacity_stack.last() {
3185 color[3] *= alpha;
3186 }
3187 color
3188 }
3189
3190 pub fn load_svg(&mut self, name: &str, data: &[u8]) {
3192 let opt = usvg::Options::default();
3193 let tree = usvg::Tree::from_data(data, &opt).expect("Failed to parse SVG");
3194
3195 let view_box = Rect {
3196 x: 0.0,
3197 y: 0.0,
3198 width: tree.size().width(),
3199 height: tree.size().height(),
3200 };
3201
3202 let mut vertices = Vec::new();
3203 let mut indices = Vec::new();
3204 let mut tessellator = FillTessellator::new();
3205
3206 for child in tree.root().children() {
3207 self.tessellate_node(child, &mut tessellator, &mut vertices, &mut indices);
3208 }
3209
3210 self.svg_cache.insert(name.to_string(), SvgModel {
3211 vertices,
3212 indices,
3213 view_box,
3214 });
3215 }
3216
3217 fn tessellate_node(&self, node: &usvg::Node, tessellator: &mut FillTessellator, vertices: &mut Vec<Vertex>, indices: &mut Vec<u32>) {
3218 if let usvg::Node::Group(ref group) = *node {
3219 for child in group.children() {
3220 self.tessellate_node(child, tessellator, vertices, indices);
3221 }
3222 } else if let usvg::Node::Path(ref path) = *node
3223 && let Some(fill) = path.fill() {
3224 let color = match fill.paint() {
3225 usvg::Paint::Color(c) => [
3226 c.red as f32 / 255.0,
3227 c.green as f32 / 255.0,
3228 c.blue as f32 / 255.0,
3229 fill.opacity().get(),
3230 ],
3231 _ => [1.0, 1.0, 1.0, 1.0],
3232 };
3233
3234 let lyon_path = usvg_to_lyon(path);
3235 let mut buffers: VertexBuffers<Vertex, u32> = VertexBuffers::new();
3236 let base_vertex_idx = vertices.len() as u32;
3237
3238 tessellator.tessellate_path(
3239 &lyon_path,
3240 &FillOptions::default(),
3241 &mut BuffersBuilder::new(&mut buffers, SceneVertexConstructor {
3242 color,
3243 translation: [0.0, 0.0],
3244 scale: [1.0, 1.0],
3245 rotation: 0.0,
3246 }),
3247 ).unwrap();
3248
3249 vertices.extend(buffers.vertices);
3250 for idx in buffers.indices {
3251 indices.push(base_vertex_idx + idx);
3252 }
3253 }
3254 }
3255
3256 pub fn draw_svg(&mut self, name: &str, rect: Rect, color: Option<[f32; 4]>, mode: u32) {
3258 let model = if let Some(m) = self.svg_cache.get(name) {
3259 m.clone()
3260 } else {
3261 return;
3262 };
3263
3264 let _scale_x = rect.width / model.view_box.width;
3265 let _scale_y = rect.height / model.view_box.height;
3266 let base_idx = self.vertices.len() as u32;
3267 let screen = [self.current_width() as f32, self.current_height() as f32];
3268 let clip_rect = self.clip_stack.last().copied().unwrap_or(cvkg_core::Rect {
3269 x: -10000.0,
3270 y: -10000.0,
3271 width: 20000.0,
3272 height: 20000.0,
3273 });
3274 let clip = [clip_rect.x, clip_rect.y, clip_rect.width, clip_rect.height];
3275 let scale = self.current_scale_factor();
3276 let snap = |v: f32| (v * scale).round() / scale;
3277
3278 for v in &model.vertices {
3279 let mut v = *v;
3280 let rel_x = (v.position[0] - model.view_box.x) / model.view_box.width;
3281 let rel_y = (v.position[1] - model.view_box.y) / model.view_box.height;
3282
3283 v.position[0] = snap(rect.x + rel_x * rect.width);
3284 v.position[1] = snap(rect.y + rel_y * rect.height);
3285 v.position[2] = self.current_z;
3286 v.logical = [v.position[0], v.position[1]];
3287 v.screen = screen;
3288 v.clip = clip;
3289 v.mode = mode;
3290
3291 if let Some(override_color) = color {
3292 v.color = self.apply_opacity(override_color);
3293 } else {
3294 v.color = self.apply_opacity(v.color);
3295 }
3296 self.vertices.push(v);
3297 }
3298
3299 for idx in &model.indices {
3300 self.indices.push(base_idx + *idx);
3301 }
3302
3303 let is_ui = true;
3304 let is_glass = mode == 7;
3305 let tid = self.get_texture_id("__mega_atlas");
3306
3307 let last_call = self.draw_calls.last();
3308 let needs_new_call = self.draw_calls.is_empty()
3309 || self.current_texture_id != tid
3310 || last_call.unwrap().scissor_rect != self.clip_stack.last().copied()
3311 || last_call.unwrap().is_glass != is_glass
3312 || last_call.unwrap().is_ui != is_ui;
3313
3314 if needs_new_call {
3315 self.current_texture_id = tid;
3316 self.draw_calls.push(DrawCall {
3317 texture_id: tid,
3318 scissor_rect: self.clip_stack.last().copied(),
3319 index_start: (self.indices.len() - model.indices.len()) as u32,
3320 index_count: 0,
3321 is_glass,
3322 is_ui,
3323 });
3324 }
3325
3326 if let Some(call) = self.draw_calls.last_mut() {
3327 call.index_count += model.indices.len() as u32;
3328 }
3329 }
3330
3331 pub async fn forge_headless(width: u32, height: u32) -> Self {
3333 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
3334 backends: wgpu::Backends::all(),
3335 flags: wgpu::InstanceFlags::default(),
3336 backend_options: wgpu::BackendOptions::default(),
3337 display: None,
3338 memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
3339 });
3340
3341 println!("[GPU] Requesting HighPerformance adapter...");
3343 let mut adapter = instance
3344 .request_adapter(&wgpu::RequestAdapterOptions {
3345 power_preference: wgpu::PowerPreference::HighPerformance,
3346 compatible_surface: None,
3347 force_fallback_adapter: false,
3348 })
3349 .await
3350 .ok();
3351
3352 if adapter.is_none() {
3353 println!("[GPU] HighPerformance adapter failed (possible Bumblebee/Optimus), trying LowPower...");
3354 adapter = instance
3355 .request_adapter(&wgpu::RequestAdapterOptions {
3356 power_preference: wgpu::PowerPreference::LowPower,
3357 compatible_surface: None,
3358 force_fallback_adapter: false,
3359 })
3360 .await
3361 .ok();
3362 }
3363
3364 if adapter.is_none() {
3365 println!("[GPU] Hardware adapters failed, trying Software fallback...");
3366 adapter = instance
3367 .request_adapter(&wgpu::RequestAdapterOptions {
3368 power_preference: wgpu::PowerPreference::LowPower,
3369 compatible_surface: None,
3370 force_fallback_adapter: true,
3371 })
3372 .await
3373 .ok();
3374 }
3375
3376 let adapter = adapter.expect("Failed to find a suitable GPU for Surtr");
3377 println!("[GPU] Selected adapter: {:?}", adapter.get_info().name);
3378
3379 let (device, queue) = adapter
3380 .request_device(&wgpu::DeviceDescriptor {
3381 label: Some("Surtr Headless Forge"),
3382 required_features: wgpu::Features::empty(),
3383 required_limits: wgpu::Limits::default(),
3384 memory_hints: wgpu::MemoryHints::default(),
3385 experimental_features: wgpu::ExperimentalFeatures::disabled(),
3386 trace: wgpu::Trace::Off,
3387 })
3388 .await
3389 .expect("Failed to create Surtr device");
3390
3391 let instance = Arc::new(instance);
3392 let adapter = Arc::new(adapter);
3393 let device = Arc::new(device);
3394 let queue = Arc::new(queue);
3395
3396 Self::forge_internal(
3397 instance,
3398 adapter,
3399 device,
3400 queue,
3401 None,
3402 Some((width, height, wgpu::TextureFormat::Rgba8UnormSrgb))
3403 ).await
3404 }
3405
3406 pub async fn capture_frame(&self) -> Vec<u8> {
3408 let ctx = self.headless_context.as_ref().expect("Headless context required for capture");
3409 let u32_size = std::mem::size_of::<u32>() as u32;
3410 let width = ctx.width;
3411 let height = ctx.height;
3412 let bytes_per_row = width * u32_size;
3413 let padding = (256 - (bytes_per_row % 256)) % 256;
3414 let padded_bytes_per_row = bytes_per_row + padding;
3415
3416 let output_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
3417 label: Some("Capture Buffer"),
3418 size: (padded_bytes_per_row as u64 * height as u64),
3419 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
3420 mapped_at_creation: false,
3421 });
3422
3423 let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
3424 label: Some("Capture Encoder"),
3425 });
3426
3427 encoder.copy_texture_to_buffer(
3428 wgpu::TexelCopyTextureInfo {
3429 texture: &ctx.output_texture,
3430 mip_level: 0,
3431 origin: wgpu::Origin3d::ZERO,
3432 aspect: wgpu::TextureAspect::All,
3433 },
3434 wgpu::TexelCopyBufferInfo {
3435 buffer: &output_buffer,
3436 layout: wgpu::TexelCopyBufferLayout {
3437 offset: 0,
3438 bytes_per_row: Some(padded_bytes_per_row),
3439 rows_per_image: Some(height),
3440 },
3441 },
3442 wgpu::Extent3d {
3443 width: width,
3444 height: height,
3445 depth_or_array_layers: 1,
3446 },
3447 );
3448
3449 self.queue.submit(Some(encoder.finish()));
3450
3451 let buffer_slice = output_buffer.slice(..);
3452 let (sender, receiver) = futures::channel::oneshot::channel();
3453 buffer_slice.map_async(wgpu::MapMode::Read, move |v| {
3454 let _ = sender.send(v);
3455 });
3456
3457 let _ = self.device.poll(wgpu::PollType::Wait {
3458 submission_index: None,
3459 timeout: None,
3460 });
3461
3462 if let Ok(Ok(_)) = receiver.await {
3463 let data = buffer_slice.get_mapped_range();
3464 let mut result = Vec::with_capacity((width * height * 4) as usize);
3465
3466 for y in 0..height {
3467 let start = (y * padded_bytes_per_row) as usize;
3468 let end = start + bytes_per_row as usize;
3469 result.extend_from_slice(&data[start..end]);
3470 }
3471
3472 drop(data);
3473 output_buffer.unmap();
3474 result
3475 } else {
3476 panic!("Failed to capture frame")
3477 }
3478 }
3479
3480 fn current_width(&self) -> u32 {
3481 if let Some(id) = self.current_window {
3482 self.surfaces.get(&id).unwrap().config.width
3483 } else {
3484 self.headless_context.as_ref().unwrap().width
3485 }
3486 }
3487
3488 fn current_height(&self) -> u32 {
3489 if let Some(id) = self.current_window {
3490 self.surfaces.get(&id).unwrap().config.height
3491 } else {
3492 self.headless_context.as_ref().unwrap().height
3493 }
3494 }
3495
3496 fn current_scale_factor(&self) -> f32 {
3497 if let Some(id) = self.current_window {
3498 self.surfaces.get(&id).unwrap().scale_factor
3499 } else {
3500 self.headless_context.as_ref().unwrap().scale_factor
3501 }
3502 }
3503}
3504