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