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