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