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 if LAYOUT_DIRTY.swap(false, Ordering::AcqRel)
1996 && let Some(window_id) = self.current_window
1997 && let Some(surface_ctx) = self.surfaces.get(&window_id) {
1998 let w = surface_ctx.config.width as f32;
1999 let h = surface_ctx.config.height as f32;
2000 let border_rect = Rect { x: 0.0, y: 0.0, width: w, height: h };
2001 self.stroke_rect(border_rect, [1.0, 0.0, 0.0, 1.0], 10.0);
2003 }
2004
2005 self.queue
2006 .write_buffer(&self.vertex_buffer, 0, bytemuck::cast_slice(&self.vertices));
2007 self.queue
2008 .write_buffer(&self.index_buffer, 0, bytemuck::cast_slice(&self.indices));
2009
2010 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 {
2011 let ctx = self.surfaces.get(&window_id).expect("Missing surface context");
2012 let frame = match ctx.surface.get_current_texture() {
2013 wgpu::CurrentSurfaceTexture::Success(t) => t,
2014 wgpu::CurrentSurfaceTexture::Suboptimal(t) => {
2015 ctx.surface.configure(&self.device, &ctx.config);
2016 t
2017 }
2018 _ => return, };
2020 let view = frame.texture.create_view(&wgpu::TextureViewDescriptor::default());
2021 (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)
2022 } else {
2023 let ctx = self.headless_context.as_ref().expect("No headless context for end_frame");
2024 (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)
2025 };
2026
2027 {
2029 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2030 label: Some("Surtr P1 Opaque Background"),
2031 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2032 view: ctx_scene_texture,
2033 resolve_target: None,
2034 ops: wgpu::Operations {
2035 load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 0.0 }),
2036 store: wgpu::StoreOp::Store,
2037 },
2038 depth_slice: None,
2039 })],
2040 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
2041 view: ctx_depth_texture_view,
2042 depth_ops: Some(wgpu::Operations {
2043 load: wgpu::LoadOp::Clear(1.0),
2044 store: wgpu::StoreOp::Store,
2045 }),
2046 stencil_ops: None,
2047 }),
2048 occlusion_query_set: None,
2049 timestamp_writes: None,
2050 multiview_mask: None,
2051 });
2052
2053 p.set_pipeline(&self.background_pipeline);
2055 p.set_bind_group(0, &self.dummy_texture_bind_group, &[]);
2056 p.set_bind_group(1, ctx_blur_env_bind_group_a, &[]); p.set_bind_group(2, &self.berserker_bind_group, &[]);
2058 p.draw(0..6, 0..1);
2059
2060 if !self.draw_calls.is_empty() {
2062 p.set_pipeline(&self.pipeline);
2063 p.set_vertex_buffer(0, self.vertex_buffer.slice(..));
2064 p.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
2065 p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
2066 p.set_bind_group(2, &self.berserker_bind_group, &[]);
2067
2068 for call in self.draw_calls.iter().filter(|c| !c.is_glass && !c.is_ui) {
2069 let bg = if let Some(id) = call.texture_id {
2070 if id == 0 {
2071 &self.mega_atlas_bind_group
2072 } else {
2073 self.texture_bind_groups
2074 .get(id as usize)
2075 .unwrap_or(&self.dummy_texture_bind_group)
2076 }
2077 } else {
2078 &self.dummy_texture_bind_group
2079 };
2080 p.set_bind_group(0, bg, &[]);
2081 p.draw_indexed(
2082 call.index_start..call.index_start + call.index_count,
2083 0,
2084 0..1,
2085 );
2086 self.telemetry.draw_calls += 1;
2087 self.telemetry.vertices += call.index_count;
2088 }
2089 }
2090 }
2091
2092 {
2095 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2097 label: Some("Surtr Blur Extract"),
2098 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2099 view: ctx_blur_texture_a,
2100 resolve_target: None,
2101 ops: wgpu::Operations {
2102 load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 0.0 }),
2103 store: wgpu::StoreOp::Store,
2104 },
2105 depth_slice: None,
2106 })],
2107 ..Default::default()
2108 });
2109 p.set_pipeline(&self.bloom_extract_pipeline); p.set_bind_group(0, ctx_scene_texture_bind_group, &[]);
2111 p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
2112 p.set_bind_group(2, &self.berserker_bind_group, &[]);
2113 p.draw(0..6, 0..1);
2114 }
2115
2116 let blur_iters: u32 = 4;
2117 for _i in 0..blur_iters {
2118 {
2119 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2120 label: Some("Blur H"),
2121 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2122 view: ctx_blur_texture_b,
2123 resolve_target: None,
2124 ops: wgpu::Operations {
2125 load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 0.0 }),
2126 store: wgpu::StoreOp::Store,
2127 },
2128 depth_slice: None,
2129 })],
2130 ..Default::default()
2131 });
2132 p.set_pipeline(&self.blur_h_pipeline);
2133 p.set_bind_group(0, ctx_blur_bind_group_a, &[]);
2134 p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
2135 p.set_bind_group(2, &self.berserker_bind_group, &[]);
2136 p.draw(0..6, 0..1);
2137 }
2138 {
2139 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2140 label: Some("Blur V"),
2141 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2142 view: ctx_blur_texture_a,
2143 resolve_target: None,
2144 ops: wgpu::Operations {
2145 load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 0.0 }),
2146 store: wgpu::StoreOp::Store,
2147 },
2148 depth_slice: None,
2149 })],
2150 ..Default::default()
2151 });
2152 p.set_pipeline(&self.blur_v_pipeline);
2153 p.set_bind_group(0, ctx_blur_bind_group_b, &[]);
2154 p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
2155 p.set_bind_group(2, &self.berserker_bind_group, &[]);
2156 p.draw(0..6, 0..1);
2157 }
2158 }
2159
2160 {
2162 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2163 label: Some("Surtr P3 Liquid Glass"),
2164 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2165 view: ctx_scene_texture, resolve_target: None,
2167 ops: wgpu::Operations {
2168 load: wgpu::LoadOp::Load,
2169 store: wgpu::StoreOp::Store,
2170 },
2171 depth_slice: None,
2172 })],
2173 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
2174 view: ctx_depth_texture_view,
2175 depth_ops: Some(wgpu::Operations {
2176 load: wgpu::LoadOp::Load,
2177 store: wgpu::StoreOp::Store,
2178 }),
2179 stencil_ops: None,
2180 }),
2181 occlusion_query_set: None,
2182 timestamp_writes: None,
2183 multiview_mask: None,
2184 });
2185
2186 p.set_pipeline(&self.pipeline);
2187 p.set_vertex_buffer(0, self.vertex_buffer.slice(..));
2188 p.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
2189 p.set_bind_group(1, ctx_blur_env_bind_group_a, &[]); p.set_bind_group(2, &self.berserker_bind_group, &[]);
2191
2192 for call in self.draw_calls.iter().filter(|c| c.is_glass) {
2193 let bg = if let Some(id) = call.texture_id {
2194 if id == 0 {
2195 &self.mega_atlas_bind_group
2196 } else {
2197 self.texture_bind_groups
2198 .get(id as usize)
2199 .unwrap_or(&self.dummy_texture_bind_group)
2200 }
2201 } else {
2202 &self.dummy_texture_bind_group
2203 };
2204 p.set_bind_group(0, bg, &[]);
2205 p.draw_indexed(
2206 call.index_start..call.index_start + call.index_count,
2207 0,
2208 0..1,
2209 );
2210 self.telemetry.draw_calls += 1;
2211 self.telemetry.vertices += call.index_count;
2212 }
2213 }
2214
2215 {
2217 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2218 label: Some("Surtr P4 UI Layer"),
2219 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2220 view: ctx_scene_texture,
2221 resolve_target: None,
2222 ops: wgpu::Operations {
2223 load: wgpu::LoadOp::Load,
2224 store: wgpu::StoreOp::Store,
2225 },
2226 depth_slice: None,
2227 })],
2228 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
2229 view: ctx_depth_texture_view,
2230 depth_ops: Some(wgpu::Operations {
2231 load: wgpu::LoadOp::Load,
2232 store: wgpu::StoreOp::Store,
2233 }),
2234 stencil_ops: None,
2235 }),
2236 occlusion_query_set: None,
2237 timestamp_writes: None,
2238 multiview_mask: None,
2239 });
2240
2241 p.set_pipeline(&self.pipeline);
2242 p.set_vertex_buffer(0, self.vertex_buffer.slice(..));
2243 p.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
2244 p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
2245 p.set_bind_group(2, &self.berserker_bind_group, &[]);
2246
2247 for call in self.draw_calls.iter().filter(|c| c.is_ui) {
2248 let bg = if let Some(id) = call.texture_id {
2249 if id == 0 {
2250 &self.mega_atlas_bind_group
2251 } else {
2252 self.texture_bind_groups
2253 .get(id as usize)
2254 .unwrap_or(&self.dummy_texture_bind_group)
2255 }
2256 } else {
2257 &self.dummy_texture_bind_group
2258 };
2259 p.set_bind_group(0, bg, &[]);
2260 p.draw_indexed(
2261 call.index_start..call.index_start + call.index_count,
2262 0,
2263 0..1,
2264 );
2265 self.telemetry.draw_calls += 1;
2266 self.telemetry.vertices += call.index_count;
2267 }
2268 }
2269
2270 {
2272 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2273 label: Some("Surtr Bloom Extract"),
2274 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2275 view: ctx_blur_texture_a,
2276 resolve_target: None,
2277 ops: wgpu::Operations {
2278 load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 0.0 }),
2279 store: wgpu::StoreOp::Store,
2280 },
2281 depth_slice: None,
2282 })],
2283 ..Default::default()
2284 });
2285 p.set_pipeline(&self.bloom_extract_pipeline);
2286 p.set_bind_group(0, ctx_scene_texture_bind_group, &[]);
2287 p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
2288 p.set_bind_group(2, &self.berserker_bind_group, &[]);
2289 p.draw(0..6, 0..1);
2290 }
2291
2292 for _ in 0..2 {
2294 {
2295 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2296 label: Some("Bloom Blur H"),
2297 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2298 view: ctx_blur_texture_b,
2299 resolve_target: None,
2300 ops: wgpu::Operations {
2301 load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 0.0 }),
2302 store: wgpu::StoreOp::Store,
2303 },
2304 depth_slice: None,
2305 })],
2306 ..Default::default()
2307 });
2308 p.set_pipeline(&self.blur_h_pipeline);
2309 p.set_bind_group(0, ctx_blur_bind_group_a, &[]);
2310 p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
2311 p.set_bind_group(2, &self.berserker_bind_group, &[]);
2312 p.draw(0..6, 0..1);
2313 }
2314 {
2315 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2316 label: Some("Bloom Blur V"),
2317 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2318 view: ctx_blur_texture_a,
2319 resolve_target: None,
2320 ops: wgpu::Operations {
2321 load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 0.0 }),
2322 store: wgpu::StoreOp::Store,
2323 },
2324 depth_slice: None,
2325 })],
2326 ..Default::default()
2327 });
2328 p.set_pipeline(&self.blur_v_pipeline);
2329 p.set_bind_group(0, ctx_blur_bind_group_b, &[]);
2330 p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
2331 p.set_bind_group(2, &self.berserker_bind_group, &[]);
2332 p.draw(0..6, 0..1);
2333 }
2334 }
2335
2336 {
2338 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2339 label: Some("Surtr P7 Composite"),
2340 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2341 view: &target_view,
2342 resolve_target: None,
2343 ops: wgpu::Operations {
2344 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
2345 store: wgpu::StoreOp::Store,
2346 },
2347 depth_slice: None,
2348 })],
2349 ..Default::default()
2350 });
2351 p.set_pipeline(&self.composite_pipeline);
2352 p.set_bind_group(0, ctx_scene_texture_bind_group, &[]);
2355 p.set_bind_group(1, ctx_blur_env_bind_group_a, &[]); p.set_bind_group(2, &self.berserker_bind_group, &[]);
2357 p.draw(0..6, 0..1);
2358 self.telemetry.draw_calls += 1;
2359 }
2360
2361 self.telemetry.frame_time_ms = self.last_frame_start.elapsed().as_secs_f32() * 1000.0;
2362 self.update_vram_telemetry();
2363 self.queue.submit(Some(encoder.finish()));
2364 if let Some(f) = surface_texture {
2365 f.present();
2366 }
2367 }
2368}
2369
2370impl cvkg_core::ElapsedTime for SurtrRenderer {
2371 fn delta_time(&self) -> f32 {
2372 self.current_scene.delta_time
2373 }
2374
2375 fn elapsed_time(&self) -> f32 {
2376 self.start_time.elapsed().as_secs_f32()
2377 }
2378}
2379
2380impl cvkg_core::Renderer for SurtrRenderer {
2381 fn fill_rect(&mut self, rect: Rect, color: [f32; 4]) {
2383 self.fill_rect_with_mode(rect, self.apply_opacity(color), 0, None);
2384 }
2385
2386 fn fill_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4]) {
2387 self.fill_rect_with_full_params(
2388 rect,
2389 self.apply_opacity(color),
2390 3,
2391 None,
2392 radius,
2393 Rect {
2394 x: 0.0,
2395 y: 0.0,
2396 width: 1.0,
2397 height: 1.0,
2398 },
2399 );
2400 }
2401
2402 fn fill_ellipse(&mut self, rect: Rect, color: [f32; 4]) {
2403 self.fill_rect_with_full_params(
2404 rect,
2405 self.apply_opacity(color),
2406 4,
2407 None,
2408 0.0,
2409 Rect {
2410 x: 0.0,
2411 y: 0.0,
2412 width: 1.0,
2413 height: 1.0,
2414 },
2415 );
2416 }
2417
2418 fn bifrost(&mut self, rect: Rect, blur: f32, _saturation: f32, opacity: f32) {
2419 let screen_uv = Rect {
2421 x: rect.x / self.current_width() as f32,
2422 y: rect.y / self.current_height() as f32,
2423 width: rect.width / self.current_width() as f32,
2424 height: rect.height / self.current_height() as f32,
2425 };
2426 self.fill_rect_with_full_params(rect, [1.0, 1.0, 1.0, opacity], 7, None, blur, screen_uv);
2429 }
2430
2431 fn gungnir(&mut self, rect: Rect, color: [f32; 4], radius: f32, intensity: f32) {
2432 let center_x = rect.x + rect.width * 0.5;
2435 let center_y = rect.y + rect.height * 0.5;
2436 let max_dim = rect.width.max(rect.height) * 0.5 + radius;
2437
2438 for i in 0..8 {
2440 let alpha = intensity / (i as f32 + 1.0) * 0.3;
2441 let glow_color = [color[0], color[1], color[2], alpha];
2442 self.fill_rect_with_mode(
2443 Rect {
2444 x: center_x - max_dim - i as f32 * 2.0,
2445 y: center_y - max_dim - i as f32 * 2.0,
2446 width: max_dim * 2.0 + i as f32 * 4.0,
2447 height: max_dim * 2.0 + i as f32 * 4.0,
2448 },
2449 glow_color,
2450 8, None,
2452 );
2453 }
2454 }
2455
2456 fn stroke_rect(&mut self, rect: Rect, color: [f32; 4], stroke_width: f32) {
2457 let c = self.apply_opacity(color);
2458 let hw = stroke_width;
2459 self.fill_rect_with_mode(
2461 Rect {
2462 x: rect.x,
2463 y: rect.y,
2464 width: rect.width,
2465 height: hw,
2466 },
2467 c,
2468 1,
2469 None,
2470 );
2471 self.fill_rect_with_mode(
2472 Rect {
2473 x: rect.x,
2474 y: rect.y + rect.height - hw,
2475 width: rect.width,
2476 height: hw,
2477 },
2478 c,
2479 1,
2480 None,
2481 );
2482 self.fill_rect_with_mode(
2483 Rect {
2484 x: rect.x,
2485 y: rect.y,
2486 width: hw,
2487 height: rect.height,
2488 },
2489 c,
2490 1,
2491 None,
2492 );
2493 self.fill_rect_with_mode(
2494 Rect {
2495 x: rect.x + rect.width - hw,
2496 y: rect.y,
2497 width: hw,
2498 height: rect.height,
2499 },
2500 c,
2501 1,
2502 None,
2503 );
2504 }
2505
2506 fn stroke_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4], stroke_width: f32) {
2507 self.fill_rect_with_full_params(
2508 rect,
2509 self.apply_opacity(color),
2510 17,
2511 None,
2512 radius,
2513 Rect {
2514 x: stroke_width,
2515 y: 0.0,
2516 width: 0.0,
2517 height: 0.0,
2518 },
2519 );
2520 }
2521
2522 fn stroke_ellipse(&mut self, _rect: Rect, _color: [f32; 4], _stroke_width: f32) {
2523 }
2525
2526 fn draw_linear_gradient(
2527 &mut self,
2528 rect: Rect,
2529 start_color: [f32; 4],
2530 end_color: [f32; 4],
2531 angle: f32,
2532 ) {
2533 self.fill_rect_with_full_params_and_slice(
2534 rect,
2535 self.apply_opacity(start_color),
2536 15,
2537 None,
2538 0.0,
2539 Rect {
2540 x: angle,
2541 y: 0.0,
2542 width: 1.0,
2543 height: 1.0,
2544 },
2545 end_color,
2546 );
2547 }
2548
2549 fn draw_radial_gradient(&mut self, rect: Rect, inner_color: [f32; 4], outer_color: [f32; 4]) {
2550 self.fill_rect_with_full_params_and_slice(
2551 rect,
2552 self.apply_opacity(inner_color),
2553 16,
2554 None,
2555 0.0,
2556 Rect {
2557 x: 0.0,
2558 y: 0.0,
2559 width: 1.0,
2560 height: 1.0,
2561 },
2562 outer_color,
2563 );
2564 }
2565
2566 fn draw_drop_shadow(
2567 &mut self,
2568 rect: Rect,
2569 radius: f32,
2570 color: [f32; 4],
2571 blur: f32,
2572 spread: f32,
2573 ) {
2574 let margin = blur + spread;
2575 let inflated = Rect {
2576 x: rect.x - margin,
2577 y: rect.y - margin,
2578 width: rect.width + margin * 2.0,
2579 height: rect.height + margin * 2.0,
2580 };
2581 self.fill_rect_with_full_params(
2583 inflated,
2584 self.apply_opacity(color),
2585 18,
2586 None,
2587 radius,
2588 Rect {
2589 x: margin,
2590 y: blur,
2591 width: 0.0,
2592 height: 0.0,
2593 },
2594 );
2595 }
2596
2597 fn stroke_dashed_rounded_rect(
2598 &mut self,
2599 rect: Rect,
2600 radius: f32,
2601 color: [f32; 4],
2602 width: f32,
2603 dash: f32,
2604 gap: f32,
2605 ) {
2606 self.fill_rect_with_full_params(
2607 rect,
2608 self.apply_opacity(color),
2609 19,
2610 None,
2611 radius,
2612 Rect {
2613 x: width,
2614 y: dash,
2615 width: gap,
2616 height: 0.0,
2617 },
2618 );
2619 }
2620
2621 fn draw_9slice(
2622 &mut self,
2623 image_name: &str,
2624 rect: Rect,
2625 left: f32,
2626 top: f32,
2627 right: f32,
2628 bottom: f32,
2629 ) {
2630 let c = self.apply_opacity([1.0, 1.0, 1.0, 1.0]);
2631 let tid = self.get_texture_id(image_name);
2632 self.fill_rect_with_full_params(
2633 rect,
2634 c,
2635 20,
2636 tid,
2637 bottom,
2638 Rect {
2639 x: left,
2640 y: top,
2641 width: right,
2642 height: 0.0,
2643 },
2644 );
2645 }
2646
2647 fn draw_line(
2648 &mut self,
2649 x1: f32,
2650 y1: f32,
2651 x2: f32,
2652 y2: f32,
2653 color: [f32; 4],
2654 stroke_width: f32,
2655 ) {
2656 let dx = x2 - x1;
2657 let dy = y2 - y1;
2658 let len = (dx * dx + dy * dy).sqrt();
2659 if len < 0.001 {
2660 return;
2661 }
2662
2663 let c = self.apply_opacity(color);
2664 let tid = self.get_texture_id("__mega_atlas");
2665
2666 self.fill_rect_with_mode(
2667 Rect {
2668 x: (x1 + x2) / 2.0 - len / 2.0,
2669 y: (y1 + y2) / 2.0 - stroke_width / 2.0,
2670 width: len,
2671 height: stroke_width,
2672 },
2673 c,
2674 1, tid,
2676 );
2677 }
2678
2679 fn draw_image(&mut self, image_name: &str, rect: Rect) {
2680 let tid = self.get_texture_id("__mega_atlas");
2681 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 });
2682 self.fill_rect_with_full_params(
2683 rect,
2684 [1.0, 1.0, 1.0, 1.0],
2685 2,
2686 tid,
2687 0.0,
2688 uv_rect,
2689 );
2690 }
2691
2692 fn draw_text(&mut self, text: &str, x: f32, y: f32, size: f32, color: [f32; 4]) {
2693 let scaled_size = size * self.current_scale_factor();
2695 let shaped = self.text_engine.shape(text, "sans-serif", scaled_size);
2696 let c = self.apply_opacity(color);
2697
2698 for glyph in shaped.glyphs {
2699 let cache_key = glyph.cache_key;
2700
2701 let (uv_rect, w, h) = if let Some(info) = self.text_cache.get(&cache_key) {
2702 *info
2703 } else {
2704 if let Some(image) = self.text_engine.rasterize(cache_key) {
2705 let gw = image.width;
2706 let gh = image.height;
2707
2708 if let Some((nx, ny)) = self.atlas_packer.pack(gw, gh) {
2709 let mut rgba_data = Vec::with_capacity((gw * gh * 4) as usize);
2710 for alpha in &image.data {
2711 rgba_data.push(255);
2712 rgba_data.push(255);
2713 rgba_data.push(255);
2714 rgba_data.push(*alpha);
2715 }
2716
2717 self.queue.write_texture(
2718 wgpu::TexelCopyTextureInfo {
2719 texture: &self.mega_atlas_tex,
2720 mip_level: 0,
2721 origin: wgpu::Origin3d { x: nx, y: ny, z: 0 },
2722 aspect: wgpu::TextureAspect::All,
2723 },
2724 &rgba_data,
2725 wgpu::TexelCopyBufferLayout {
2726 offset: 0,
2727 bytes_per_row: Some(gw * 4),
2728 rows_per_image: Some(gh),
2729 },
2730 wgpu::Extent3d {
2731 width: gw,
2732 height: gh,
2733 depth_or_array_layers: 1,
2734 },
2735 );
2736
2737 let info = (
2738 Rect {
2739 x: nx as f32 / 4096.0,
2740 y: ny as f32 / 4096.0,
2741 width: gw as f32 / 4096.0,
2742 height: gh as f32 / 4096.0,
2743 },
2744 gw as f32,
2745 gh as f32,
2746 );
2747 self.text_cache.insert(cache_key, info);
2748 info
2749 } else {
2750 (Rect::zero(), 0.0, 0.0)
2751 }
2752 } else {
2753 (Rect::zero(), 0.0, 0.0)
2754 }
2755 };
2756
2757 if w > 0.0 {
2758 let glyph_rect = Rect {
2761 x: x + glyph.x / self.current_scale_factor(),
2762 y: y + glyph.y / self.current_scale_factor(),
2763 width: w / self.current_scale_factor(),
2764 height: h / self.current_scale_factor(),
2765 };
2766 let tid = self.get_texture_id("__mega_atlas");
2767 self.fill_rect_with_full_params(glyph_rect, c, 6, tid, 0.0, uv_rect);
2768 }
2769 }
2770 }
2771
2772 fn measure_text(&mut self, text: &str, size: f32) -> (f32, f32) {
2774 let shaped = self.text_engine.shape(text, "sans-serif", size);
2775 (shaped.width, shaped.height)
2776 }
2777
2778 fn draw_texture(&mut self, texture_id: u32, rect: Rect) {
2779 self.fill_rect_with_full_params(
2780 rect,
2781 [1.0, 1.0, 1.0, 1.0],
2782 2,
2783 Some(texture_id),
2784 0.0,
2785 Rect {
2786 x: 0.0,
2787 y: 0.0,
2788 width: 1.0,
2789 height: 1.0,
2790 },
2791 );
2792 }
2793
2794 fn load_image(&mut self, name: &str, data: &[u8]) {
2796 if self.image_uv_registry.contains_key(name) {
2797 return;
2798 }
2799 let img_result = image::load_from_memory(data);
2800 let img = match img_result {
2801 Ok(img) => img.to_rgba8(),
2802 Err(e) => {
2803 eprintln!("Failed to load image {}: {}", name, e);
2804 image::RgbaImage::from_pixel(1, 1, image::Rgba([0, 0, 0, 255]))
2805 }
2806 };
2807 let (width, height) = img.dimensions();
2808
2809 if let Some((x, y)) = self.atlas_packer.pack(width, height) {
2811 let size = wgpu::Extent3d {
2812 width,
2813 height,
2814 depth_or_array_layers: 1,
2815 };
2816 self.queue.write_texture(
2817 wgpu::TexelCopyTextureInfo {
2818 texture: &self.mega_atlas_tex,
2819 mip_level: 0,
2820 origin: wgpu::Origin3d { x, y, z: 0 },
2821 aspect: wgpu::TextureAspect::All,
2822 },
2823 &img,
2824 wgpu::TexelCopyBufferLayout {
2825 offset: 0,
2826 bytes_per_row: Some(4 * width),
2827 rows_per_image: Some(height),
2828 },
2829 size,
2830 );
2831
2832 let uv_rect = Rect {
2834 x: x as f32 / 4096.0,
2835 y: y as f32 / 4096.0,
2836 width: width as f32 / 4096.0,
2837 height: height as f32 / 4096.0,
2838 };
2839 self.image_uv_registry.insert(name.to_string(), uv_rect);
2840 self.texture_registry.insert(name.to_string(), 0);
2841 } else {
2842 eprintln!("Mega-Atlas is FULL! Could not pack image: {}", name);
2843 }
2844 }
2845
2846 fn push_clip_rect(&mut self, rect: Rect) {
2847 self.clip_stack.push(rect);
2848 }
2849
2850 fn pop_clip_rect(&mut self) {
2851 self.clip_stack.pop();
2852 }
2853
2854 fn push_opacity(&mut self, opacity: f32) {
2855 let current = self.opacity_stack.last().copied().unwrap_or(1.0);
2856 self.opacity_stack.push(current * opacity);
2857 }
2858
2859 fn pop_opacity(&mut self) {
2860 self.opacity_stack.pop();
2861 }
2862
2863 fn push_shadow(&mut self, radius: f32, color: [f32; 4], offset: [f32; 2]) {
2864 self.shadow_stack.push(ShadowState {
2865 radius,
2866 color,
2867 _offset: offset,
2868 });
2869 }
2870
2871 fn pop_shadow(&mut self) {
2872 self.shadow_stack.pop();
2873 }
2874
2875 fn push_transform(&mut self, translation: [f32; 2], scale: [f32; 2], rotation: f32) {
2876 let (current_t, current_s, current_r) = self.transform_stack.last().copied().unwrap_or(([0.0, 0.0], [1.0, 1.0], 0.0));
2877
2878 let new_t = [current_t[0] + translation[0] * current_s[0], current_t[1] + translation[1] * current_s[1]];
2882 let new_s = [current_s[0] * scale[0], current_s[1] * scale[1]];
2883 let new_r = current_r + rotation;
2884
2885 self.transform_stack.push((new_t, new_s, new_r));
2886 }
2887
2888 fn pop_transform(&mut self) {
2889 self.transform_stack.pop();
2890 }
2891
2892 fn set_theme(&mut self, theme: ColorTheme) {
2893
2894 self.current_theme = theme;
2895 self.queue
2896 .write_buffer(&self.theme_buffer, 0, bytemuck::bytes_of(&theme));
2897 }
2898
2899 fn set_rage(&mut self, rage: f32) {
2900 self.current_scene.berzerker_rage = rage;
2901 }
2903
2904 fn trigger_shatter_event(&mut self, origin: [f32; 2], force: f32) {
2905 self.current_scene.shatter_origin = origin;
2906 self.current_scene.shatter_time = self.current_scene.time;
2907 self.current_scene.shatter_force = force;
2908 }
2909
2910 fn push_mjolnir_slice(&mut self, angle: f32, offset: f32) {
2913 self.slice_stack.push((angle, offset));
2914 }
2915
2916 fn pop_mjolnir_slice(&mut self) {
2918 self.slice_stack.pop();
2919 }
2920
2921 fn mjolnir_shatter(&mut self, rect: Rect, pieces: u32, force: f32, color: [f32; 4]) {
2922 self.shatter_internal(rect, pieces, force, color, 8);
2923 }
2924
2925 fn mjolnir_fluid_shatter(&mut self, rect: Rect, pieces: u32, force: f32, color: [f32; 4]) {
2926 self.shatter_internal(rect, pieces, force, color, 11);
2927 }
2928
2929 fn draw_mjolnir_bolt(&mut self, from: [f32; 2], to: [f32; 2], color: [f32; 4]) {
2930 self.recursive_bolt(from, to, 4, color);
2931 }
2932
2933 fn upload_data_texture(&mut self, id: &str, data: &[f32], width: u32, height: u32) {
2934 let size = wgpu::Extent3d {
2935 width,
2936 height,
2937 depth_or_array_layers: 1,
2938 };
2939 let texture = self.device.create_texture(&wgpu::TextureDescriptor {
2940 label: Some(id),
2941 size,
2942 mip_level_count: 1,
2943 sample_count: 1,
2944 dimension: wgpu::TextureDimension::D2,
2945 format: wgpu::TextureFormat::R32Float,
2946 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
2947 view_formats: &[],
2948 });
2949 self.queue.write_texture(
2950 wgpu::TexelCopyTextureInfo {
2951 texture: &texture,
2952 mip_level: 0,
2953 origin: wgpu::Origin3d::ZERO,
2954 aspect: wgpu::TextureAspect::All,
2955 },
2956 bytemuck::cast_slice(data),
2957 wgpu::TexelCopyBufferLayout {
2958 offset: 0,
2959 bytes_per_row: Some(4 * width),
2960 rows_per_image: Some(height),
2961 },
2962 size,
2963 );
2964 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
2965 let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
2966 address_mode_u: wgpu::AddressMode::ClampToEdge,
2967 address_mode_v: wgpu::AddressMode::ClampToEdge,
2968 mag_filter: wgpu::FilterMode::Linear,
2969 ..Default::default()
2970 });
2971 let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2972 layout: &self.texture_bind_group_layout,
2973 entries: &[
2974 wgpu::BindGroupEntry {
2975 binding: 0,
2976 resource: wgpu::BindingResource::TextureView(&view),
2977 },
2978 wgpu::BindGroupEntry {
2979 binding: 1,
2980 resource: wgpu::BindingResource::Sampler(&sampler),
2981 },
2982 ],
2983 label: Some(id),
2984 });
2985 self.texture_bind_groups.push(bind_group);
2986 let tid = (self.texture_bind_groups.len() - 1) as u32;
2987 self.texture_registry.insert(id.to_string(), tid);
2988 }
2989
2990 fn draw_heatmap(&mut self, texture_id: &str, rect: Rect, _palette: &str) {
2991 let tid = self.get_texture_id(texture_id);
2992 self.fill_rect_with_mode(rect, [1.0, 1.0, 1.0, 1.0], 12, tid);
2993 }
2994
2995 fn draw_mesh(&mut self, mesh: &Mesh, color: [f32; 4], transform: glam::Mat4) {
2996 let base_idx = self.vertices.len() as u32;
2997 let screen = [self.current_width() as f32, self.current_height() as f32];
2998
2999 for i in 0..mesh.vertices.len() {
3000 let pos = transform.transform_point3(glam::Vec3::from(mesh.vertices[i]));
3001 let norm = transform.transform_vector3(glam::Vec3::from(mesh.normals[i]));
3002
3003 let (translation, scale_transform, rotation) = self.get_current_transform();
3004 self.vertices.push(Vertex {
3005 position: pos.to_array(),
3006 normal: norm.to_array(),
3007 uv: [0.0, 0.0],
3008 color,
3009 mode: 13, radius: 0.0,
3011 slice: [0.0, 0.0, 0.0, 1.0],
3012 logical: [0.0, 0.0],
3013 size: [0.0, 0.0],
3014 screen,
3015 clip: [-10000.0, -10000.0, 20000.0, 20000.0],
3016 translation,
3017 scale: scale_transform,
3018 rotation,
3019 _pad: 0.0,
3020 });
3021 }
3022
3023 for idx in &mesh.indices {
3024 self.indices.push(base_idx + idx);
3025 }
3026
3027 if self.draw_calls.is_empty() || self.current_texture_id.is_some() {
3028 self.current_texture_id = None;
3029 self.draw_calls.push(DrawCall {
3030 texture_id: None,
3031 scissor_rect: self.clip_stack.last().copied(),
3032 index_start: (self.indices.len() as u32) - (mesh.indices.len() as u32),
3033 index_count: mesh.indices.len() as u32,
3034 is_glass: false,
3035 is_ui: false,
3036 });
3037 } else {
3038 self.draw_calls.last_mut().unwrap().index_count += mesh.indices.len() as u32;
3039 }
3040 }
3041
3042 fn register_shared_element(&mut self, id: &str, rect: Rect) {
3043 self.shared_elements.insert(id.to_string(), rect);
3044 }
3045
3046 fn set_z_index(&mut self, z: f32) {
3047 self.current_z = z;
3048 }
3049
3050 fn get_z_index(&self) -> f32 {
3051 self.current_z
3052 }
3053
3054
3055 fn request_redraw(&mut self) {
3056 self.redraw_requested = true;
3057 }
3058}
3059
3060fn usvg_to_lyon(path: &usvg::Path) -> lyon::path::Path {
3063 let mut builder = lyon::path::Path::builder();
3064 for segment in path.data().segments() {
3065 match segment {
3066 usvg::tiny_skia_path::PathSegment::MoveTo(p) => {
3067 builder.begin(lyon::math::point(p.x, p.y));
3068 }
3069 usvg::tiny_skia_path::PathSegment::LineTo(p) => {
3070 builder.line_to(lyon::math::point(p.x, p.y));
3071 }
3072 usvg::tiny_skia_path::PathSegment::QuadTo(p1, p) => {
3073 builder.quadratic_bezier_to(
3074 lyon::math::point(p1.x, p1.y),
3075 lyon::math::point(p.x, p.y),
3076 );
3077 }
3078 usvg::tiny_skia_path::PathSegment::CubicTo(p1, p2, p) => {
3079 builder.cubic_bezier_to(
3080 lyon::math::point(p1.x, p1.y),
3081 lyon::math::point(p2.x, p2.y),
3082 lyon::math::point(p.x, p.y),
3083 );
3084 }
3085 usvg::tiny_skia_path::PathSegment::Close => {
3086 builder.end(true);
3087 }
3088 }
3089 }
3090 builder.build()
3091}
3092
3093impl SurtrRenderer {
3094 fn get_current_transform(&self) -> ([f32; 2], [f32; 2], f32) {
3095 self.transform_stack
3096 .last()
3097 .cloned()
3098 .unwrap_or(([0.0, 0.0], [1.0, 1.0], 0.0))
3099 }
3100}
3101
3102struct SceneVertexConstructor {
3103 color: [f32; 4],
3104 translation: [f32; 2],
3105 scale: [f32; 2],
3106 rotation: f32,
3107}
3108
3109impl FillVertexConstructor<Vertex> for SceneVertexConstructor {
3110 fn new_vertex(&mut self, vertex: FillVertex) -> Vertex {
3111 Vertex {
3112 position: [vertex.position().x, vertex.position().y, 0.0],
3113 normal: [0.0, 0.0, 1.0],
3114 uv: [0.0, 0.0],
3115 color: self.color,
3116 mode: 0,
3117 radius: 0.0,
3118 slice: [0.0, 0.0, 0.0, 1.0],
3119 logical: [vertex.position().x, vertex.position().y],
3120 size: [1.0, 1.0],
3121 screen: [0.0, 0.0],
3122 clip: [-10000.0, -10000.0, 20000.0, 20000.0],
3123 translation: self.translation,
3124 scale: self.scale,
3125 rotation: self.rotation,
3126 _pad: 0.0,
3127 }
3128 }
3129}
3130
3131impl Drop for SurtrRenderer {
3132 fn drop(&mut self) {
3133 let _ = self.device.poll(wgpu::PollType::Wait {
3135 submission_index: None,
3136 timeout: None,
3137 });
3138 }
3139}
3140
3141impl cvkg_core::FrameRenderer<wgpu::CommandEncoder> for SurtrRenderer {
3142 fn begin_frame(&mut self) -> wgpu::CommandEncoder {
3143 cvkg_core::begin_render_phase();
3144 let id = self.current_window.expect("No target window set for frame. Call set_target_window first.");
3145 self.begin_frame(id)
3146 }
3147
3148 fn end_frame(&mut self, encoder: wgpu::CommandEncoder) {
3149 self.end_frame(encoder);
3150 cvkg_core::end_render_phase();
3151 }
3152}
3153
3154impl SurtrRenderer {
3155 fn apply_opacity(&self, mut color: [f32; 4]) -> [f32; 4] {
3157 if let Some(&alpha) = self.opacity_stack.last() {
3158 color[3] *= alpha;
3159 }
3160 color
3161 }
3162
3163 pub fn load_svg(&mut self, name: &str, data: &[u8]) {
3165 let opt = usvg::Options::default();
3166 let tree = usvg::Tree::from_data(data, &opt).expect("Failed to parse SVG");
3167
3168 let view_box = Rect {
3169 x: 0.0,
3170 y: 0.0,
3171 width: tree.size().width(),
3172 height: tree.size().height(),
3173 };
3174
3175 let mut vertices = Vec::new();
3176 let mut indices = Vec::new();
3177 let mut tessellator = FillTessellator::new();
3178
3179 for child in tree.root().children() {
3180 self.tessellate_node(child, &mut tessellator, &mut vertices, &mut indices);
3181 }
3182
3183 self.svg_cache.insert(name.to_string(), SvgModel {
3184 vertices,
3185 indices,
3186 view_box,
3187 });
3188 }
3189
3190 fn tessellate_node(&self, node: &usvg::Node, tessellator: &mut FillTessellator, vertices: &mut Vec<Vertex>, indices: &mut Vec<u32>) {
3191 if let usvg::Node::Group(ref group) = *node {
3192 for child in group.children() {
3193 self.tessellate_node(child, tessellator, vertices, indices);
3194 }
3195 } else if let usvg::Node::Path(ref path) = *node
3196 && let Some(fill) = path.fill() {
3197 let color = match fill.paint() {
3198 usvg::Paint::Color(c) => [
3199 c.red as f32 / 255.0,
3200 c.green as f32 / 255.0,
3201 c.blue as f32 / 255.0,
3202 fill.opacity().get(),
3203 ],
3204 _ => [1.0, 1.0, 1.0, 1.0],
3205 };
3206
3207 let lyon_path = usvg_to_lyon(path);
3208 let mut buffers: VertexBuffers<Vertex, u32> = VertexBuffers::new();
3209 let base_vertex_idx = vertices.len() as u32;
3210
3211 tessellator.tessellate_path(
3212 &lyon_path,
3213 &FillOptions::default(),
3214 &mut BuffersBuilder::new(&mut buffers, SceneVertexConstructor {
3215 color,
3216 translation: [0.0, 0.0],
3217 scale: [1.0, 1.0],
3218 rotation: 0.0,
3219 }),
3220 ).unwrap();
3221
3222 vertices.extend(buffers.vertices);
3223 for idx in buffers.indices {
3224 indices.push(base_vertex_idx + idx);
3225 }
3226 }
3227 }
3228
3229 pub fn draw_svg(&mut self, name: &str, rect: Rect, color: Option<[f32; 4]>, mode: u32) {
3231 let model = if let Some(m) = self.svg_cache.get(name) {
3232 m.clone()
3233 } else {
3234 return;
3235 };
3236
3237 let _scale_x = rect.width / model.view_box.width;
3238 let _scale_y = rect.height / model.view_box.height;
3239 let base_idx = self.vertices.len() as u32;
3240 let screen = [self.current_width() as f32, self.current_height() as f32];
3241 let clip_rect = self.clip_stack.last().copied().unwrap_or(cvkg_core::Rect {
3242 x: -10000.0,
3243 y: -10000.0,
3244 width: 20000.0,
3245 height: 20000.0,
3246 });
3247 let clip = [clip_rect.x, clip_rect.y, clip_rect.width, clip_rect.height];
3248 let scale = self.current_scale_factor();
3249 let snap = |v: f32| (v * scale).round() / scale;
3250
3251 for v in &model.vertices {
3252 let mut v = *v;
3253 let rel_x = (v.position[0] - model.view_box.x) / model.view_box.width;
3254 let rel_y = (v.position[1] - model.view_box.y) / model.view_box.height;
3255
3256 v.position[0] = snap(rect.x + rel_x * rect.width);
3257 v.position[1] = snap(rect.y + rel_y * rect.height);
3258 v.position[2] = self.current_z;
3259 v.logical = [v.position[0], v.position[1]];
3260 v.screen = screen;
3261 v.clip = clip;
3262 v.mode = mode;
3263
3264 if let Some(override_color) = color {
3265 v.color = self.apply_opacity(override_color);
3266 } else {
3267 v.color = self.apply_opacity(v.color);
3268 }
3269 self.vertices.push(v);
3270 }
3271
3272 for idx in &model.indices {
3273 self.indices.push(base_idx + *idx);
3274 }
3275
3276 let is_ui = true;
3277 let is_glass = mode == 7;
3278 let tid = self.get_texture_id("__mega_atlas");
3279
3280 let last_call = self.draw_calls.last();
3281 let needs_new_call = self.draw_calls.is_empty()
3282 || self.current_texture_id != tid
3283 || last_call.unwrap().scissor_rect != self.clip_stack.last().copied()
3284 || last_call.unwrap().is_glass != is_glass
3285 || last_call.unwrap().is_ui != is_ui;
3286
3287 if needs_new_call {
3288 self.current_texture_id = tid;
3289 self.draw_calls.push(DrawCall {
3290 texture_id: tid,
3291 scissor_rect: self.clip_stack.last().copied(),
3292 index_start: (self.indices.len() - model.indices.len()) as u32,
3293 index_count: 0,
3294 is_glass,
3295 is_ui,
3296 });
3297 }
3298
3299 if let Some(call) = self.draw_calls.last_mut() {
3300 call.index_count += model.indices.len() as u32;
3301 }
3302 }
3303
3304 pub async fn forge_headless(width: u32, height: u32) -> Self {
3306 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
3307 backends: wgpu::Backends::all(),
3308 flags: wgpu::InstanceFlags::default(),
3309 backend_options: wgpu::BackendOptions::default(),
3310 display: None,
3311 memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
3312 });
3313
3314 println!("[GPU] Requesting HighPerformance adapter...");
3316 let mut adapter = instance
3317 .request_adapter(&wgpu::RequestAdapterOptions {
3318 power_preference: wgpu::PowerPreference::HighPerformance,
3319 compatible_surface: None,
3320 force_fallback_adapter: false,
3321 })
3322 .await
3323 .ok();
3324
3325 if adapter.is_none() {
3326 println!("[GPU] HighPerformance adapter failed (possible Bumblebee/Optimus), trying LowPower...");
3327 adapter = instance
3328 .request_adapter(&wgpu::RequestAdapterOptions {
3329 power_preference: wgpu::PowerPreference::LowPower,
3330 compatible_surface: None,
3331 force_fallback_adapter: false,
3332 })
3333 .await
3334 .ok();
3335 }
3336
3337 if adapter.is_none() {
3338 println!("[GPU] Hardware adapters failed, trying Software fallback...");
3339 adapter = instance
3340 .request_adapter(&wgpu::RequestAdapterOptions {
3341 power_preference: wgpu::PowerPreference::LowPower,
3342 compatible_surface: None,
3343 force_fallback_adapter: true,
3344 })
3345 .await
3346 .ok();
3347 }
3348
3349 let adapter = adapter.expect("Failed to find a suitable GPU for Surtr");
3350 println!("[GPU] Selected adapter: {:?}", adapter.get_info().name);
3351
3352 let (device, queue) = adapter
3353 .request_device(&wgpu::DeviceDescriptor {
3354 label: Some("Surtr Headless Forge"),
3355 required_features: wgpu::Features::empty(),
3356 required_limits: wgpu::Limits::default(),
3357 memory_hints: wgpu::MemoryHints::default(),
3358 experimental_features: wgpu::ExperimentalFeatures::disabled(),
3359 trace: wgpu::Trace::Off,
3360 })
3361 .await
3362 .expect("Failed to create Surtr device");
3363
3364 let instance = Arc::new(instance);
3365 let adapter = Arc::new(adapter);
3366 let device = Arc::new(device);
3367 let queue = Arc::new(queue);
3368
3369 Self::forge_internal(
3370 instance,
3371 adapter,
3372 device,
3373 queue,
3374 None,
3375 Some((width, height, wgpu::TextureFormat::Rgba8UnormSrgb))
3376 ).await
3377 }
3378
3379 pub async fn capture_frame(&self) -> Vec<u8> {
3381 let ctx = self.headless_context.as_ref().expect("Headless context required for capture");
3382 let u32_size = std::mem::size_of::<u32>() as u32;
3383 let width = ctx.width;
3384 let height = ctx.height;
3385 let bytes_per_row = width * u32_size;
3386 let padding = (256 - (bytes_per_row % 256)) % 256;
3387 let padded_bytes_per_row = bytes_per_row + padding;
3388
3389 let output_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
3390 label: Some("Capture Buffer"),
3391 size: (padded_bytes_per_row as u64 * height as u64),
3392 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
3393 mapped_at_creation: false,
3394 });
3395
3396 let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
3397 label: Some("Capture Encoder"),
3398 });
3399
3400 encoder.copy_texture_to_buffer(
3401 wgpu::TexelCopyTextureInfo {
3402 texture: &ctx.output_texture,
3403 mip_level: 0,
3404 origin: wgpu::Origin3d::ZERO,
3405 aspect: wgpu::TextureAspect::All,
3406 },
3407 wgpu::TexelCopyBufferInfo {
3408 buffer: &output_buffer,
3409 layout: wgpu::TexelCopyBufferLayout {
3410 offset: 0,
3411 bytes_per_row: Some(padded_bytes_per_row),
3412 rows_per_image: Some(height),
3413 },
3414 },
3415 wgpu::Extent3d {
3416 width: width,
3417 height: height,
3418 depth_or_array_layers: 1,
3419 },
3420 );
3421
3422 self.queue.submit(Some(encoder.finish()));
3423
3424 let buffer_slice = output_buffer.slice(..);
3425 let (sender, receiver) = futures::channel::oneshot::channel();
3426 buffer_slice.map_async(wgpu::MapMode::Read, move |v| {
3427 let _ = sender.send(v);
3428 });
3429
3430 let _ = self.device.poll(wgpu::PollType::Wait {
3431 submission_index: None,
3432 timeout: None,
3433 });
3434
3435 if let Ok(Ok(_)) = receiver.await {
3436 let data = buffer_slice.get_mapped_range();
3437 let mut result = Vec::with_capacity((width * height * 4) as usize);
3438
3439 for y in 0..height {
3440 let start = (y * padded_bytes_per_row) as usize;
3441 let end = start + bytes_per_row as usize;
3442 result.extend_from_slice(&data[start..end]);
3443 }
3444
3445 drop(data);
3446 output_buffer.unmap();
3447 result
3448 } else {
3449 panic!("Failed to capture frame")
3450 }
3451 }
3452
3453 fn current_width(&self) -> u32 {
3454 if let Some(id) = self.current_window {
3455 self.surfaces.get(&id).unwrap().config.width
3456 } else {
3457 self.headless_context.as_ref().unwrap().width
3458 }
3459 }
3460
3461 fn current_height(&self) -> u32 {
3462 if let Some(id) = self.current_window {
3463 self.surfaces.get(&id).unwrap().config.height
3464 } else {
3465 self.headless_context.as_ref().unwrap().height
3466 }
3467 }
3468
3469 fn current_scale_factor(&self) -> f32 {
3470 if let Some(id) = self.current_window {
3471 self.surfaces.get(&id).unwrap().scale_factor
3472 } else {
3473 self.headless_context.as_ref().unwrap().scale_factor
3474 }
3475 }
3476}
3477