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