1use cvkg_core::{ColorTheme, Mesh, Rect, SceneUniforms};
35use std::sync::Arc;
36
37pub use accesskit::{
40 ActionHandler, ActionRequest, ActivationHandler, DeactivationHandler, Node, NodeId, Role, Tree,
41 TreeId, TreeUpdate,
42};
43pub use accesskit_winit::Adapter as ShieldWallAdapter;
44
45#[repr(C)]
46#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
47pub struct Vertex {
48 pub position: [f32; 3],
49 pub normal: [f32; 3],
50 pub uv: [f32; 2],
51 pub color: [f32; 4],
52 pub mode: u32,
53 pub radius: f32,
54 pub slice: [f32; 4],
55 pub logical: [f32; 2],
56 pub size: [f32; 2],
57 pub screen: [f32; 2],
58 pub clip: [f32; 4], }
60
61#[derive(Debug, Clone)]
64struct DrawCall {
65 pub texture_id: Option<u32>,
66 pub scissor_rect: Option<Rect>,
67 pub index_start: u32,
68 pub index_count: u32,
69 pub is_glass: bool,
70 pub is_ui: bool,
71}
72
73impl Vertex {
74 const ATTRIBUTES: [wgpu::VertexAttribute; 11] = wgpu::vertex_attr_array![
75 0 => Float32x3, 1 => Float32x3, 2 => Float32x2, 3 => Float32x4, 4 => Uint32, 5 => Float32, 6 => Float32x4, 7 => Float32x2, 8 => Float32x2, 9 => Float32x2, 10 => Float32x4 ];
87
88 fn desc() -> wgpu::VertexBufferLayout<'static> {
89 wgpu::VertexBufferLayout {
90 array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
91 step_mode: wgpu::VertexStepMode::Vertex,
92 attributes: &Self::ATTRIBUTES,
93 }
94 }
95}
96
97pub struct SurtrRenderer {
99 device: Arc<wgpu::Device>,
100 queue: Arc<wgpu::Queue>,
101 surface: wgpu::Surface<'static>,
102 config: wgpu::SurfaceConfiguration,
103
104 #[allow(dead_code)]
106 font_system: cosmic_text::FontSystem,
107 #[allow(dead_code)]
108 swash_cache: cosmic_text::SwashCache,
109 text_atlas_tex: wgpu::Texture,
110 #[allow(dead_code)]
111 text_atlas_view: wgpu::TextureView,
112 #[allow(dead_code)]
113 text_sampler: wgpu::Sampler,
114 text_cache: std::collections::HashMap<cosmic_text::CacheKey, (Rect, f32, f32)>,
115 text_atlas_pos: (u32, u32),
116
117 dummy_texture_bind_group: wgpu::BindGroup,
119 dummy_env_bind_group: wgpu::BindGroup,
120 texture_bind_group_layout: wgpu::BindGroupLayout,
121 texture_bind_groups: Vec<wgpu::BindGroup>,
122 texture_registry: std::collections::HashMap<String, u32>,
123 shared_elements: std::collections::HashMap<String, cvkg_core::Rect>,
124
125 vertex_buffer: wgpu::Buffer,
127 index_buffer: wgpu::Buffer,
128 vertices: Vec<Vertex>,
129 indices: Vec<u32>,
130 draw_calls: Vec<DrawCall>,
131 current_texture_id: Option<u32>,
132
133 opacity_stack: Vec<f32>,
135 clip_stack: Vec<Rect>,
137 slice_stack: Vec<(f32, f32)>,
139
140 theme_buffer: wgpu::Buffer,
142 scene_buffer: wgpu::Buffer,
143 berserker_bind_group: wgpu::BindGroup,
144 #[allow(dead_code)]
145 berserker_bind_group_layout: wgpu::BindGroupLayout,
146 start_time: std::time::Instant,
147 current_theme: ColorTheme,
148 current_scene: SceneUniforms,
149
150 pipeline: wgpu::RenderPipeline,
152 background_pipeline: wgpu::RenderPipeline,
153 bloom_extract_pipeline: wgpu::RenderPipeline,
154 blur_h_pipeline: wgpu::RenderPipeline,
155 blur_v_pipeline: wgpu::RenderPipeline,
156 composite_pipeline: wgpu::RenderPipeline,
157
158 blur_texture_a: wgpu::TextureView,
160 blur_texture_b: wgpu::TextureView,
161 blur_bind_group_a: wgpu::BindGroup,
162 blur_bind_group_b: wgpu::BindGroup,
163 blur_env_bind_group_a: wgpu::BindGroup,
164 scene_texture: wgpu::TextureView,
165 scene_bind_group: wgpu::BindGroup,
166 scene_texture_bind_group: wgpu::BindGroup,
167 env_bind_group_layout: wgpu::BindGroupLayout,
168 scale_factor: f32,
169}
170
171const MAX_VERTICES: usize = 100_000;
172const MAX_INDICES: usize = 150_000;
173
174impl SurtrRenderer {
175 pub async fn forge(window: Arc<winit::window::Window>) -> Self {
182 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
183 backends: wgpu::Backends::all(),
184 flags: wgpu::InstanceFlags::default(),
185 backend_options: wgpu::BackendOptions::default(),
186 display: None,
187 memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
188 });
189
190 let surface = instance
191 .create_surface(window.clone())
192 .expect("Failed to create surface");
193
194 let adapter = instance
195 .request_adapter(&wgpu::RequestAdapterOptions {
196 power_preference: wgpu::PowerPreference::HighPerformance,
197 compatible_surface: Some(&surface),
198 force_fallback_adapter: false,
199 })
200 .await
201 .expect("Failed to find a suitable GPU for Surtr");
202
203 let (device, queue) = adapter
204 .request_device(&wgpu::DeviceDescriptor {
205 label: Some("Surtr Forge"),
206 required_features: wgpu::Features::empty(),
207 required_limits: wgpu::Limits::default(),
208 memory_hints: wgpu::MemoryHints::default(),
209 experimental_features: wgpu::ExperimentalFeatures::disabled(),
210 trace: wgpu::Trace::Off,
211 })
212 .await
213 .expect("Failed to create Surtr device");
214
215 let device = Arc::new(device);
216 let queue = Arc::new(queue);
217
218 let size = window.inner_size();
219 let surface_caps = surface.get_capabilities(&adapter);
220 let surface_format = surface_caps
221 .formats
222 .iter()
223 .find(|f| f.is_srgb())
224 .copied()
225 .unwrap_or(surface_caps.formats[0]);
226
227 let config = wgpu::SurfaceConfiguration {
228 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
229 format: surface_format,
230 width: size.width,
231 height: size.height,
232 present_mode: wgpu::PresentMode::Fifo,
233 alpha_mode: surface_caps.alpha_modes[0],
234 view_formats: vec![],
235 desired_maximum_frame_latency: 2,
236 };
237 surface.configure(&device, &config);
238
239 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
241 label: Some("Muspelheim Main Shader"),
242 source: wgpu::ShaderSource::Wgsl(include_str!("shaders.wgsl").into()),
243 });
244
245 let texture_bind_group_layout =
247 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
248 entries: &[
249 wgpu::BindGroupLayoutEntry {
250 binding: 0,
251 visibility: wgpu::ShaderStages::FRAGMENT,
252 ty: wgpu::BindingType::Texture {
253 multisampled: false,
254 view_dimension: wgpu::TextureViewDimension::D2,
255 sample_type: wgpu::TextureSampleType::Float { filterable: true },
256 },
257 count: None,
258 },
259 wgpu::BindGroupLayoutEntry {
260 binding: 1,
261 visibility: wgpu::ShaderStages::FRAGMENT,
262 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
263 count: None,
264 },
265 ],
266 label: Some("Niflheim Texture Bind Group Layout"),
267 });
268
269 let env_bind_group_layout =
272 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
273 entries: &[
274 wgpu::BindGroupLayoutEntry {
275 binding: 0,
276 visibility: wgpu::ShaderStages::FRAGMENT,
277 ty: wgpu::BindingType::Texture {
278 multisampled: false,
279 view_dimension: wgpu::TextureViewDimension::D2,
280 sample_type: wgpu::TextureSampleType::Float { filterable: true },
281 },
282 count: None,
283 },
284 wgpu::BindGroupLayoutEntry {
285 binding: 1,
286 visibility: wgpu::ShaderStages::FRAGMENT,
287 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
288 count: None,
289 },
290 ],
291 label: Some("Surtr Environment Bind Group Layout"),
292 });
293
294 let berserker_bind_group_layout =
295 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
296 entries: &[
297 wgpu::BindGroupLayoutEntry {
298 binding: 0,
299 visibility: wgpu::ShaderStages::FRAGMENT,
300 ty: wgpu::BindingType::Buffer {
301 ty: wgpu::BufferBindingType::Uniform,
302 has_dynamic_offset: false,
303 min_binding_size: None,
304 },
305 count: None,
306 },
307 wgpu::BindGroupLayoutEntry {
308 binding: 1,
309 visibility: wgpu::ShaderStages::FRAGMENT | wgpu::ShaderStages::VERTEX,
310 ty: wgpu::BindingType::Buffer {
311 ty: wgpu::BufferBindingType::Uniform,
312 has_dynamic_offset: false,
313 min_binding_size: None,
314 },
315 count: None,
316 },
317 ],
318 label: Some("Surtr Berserker Bind Group Layout"),
319 });
320
321 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
323 label: Some("Surtr Main Pipeline Layout"),
324 bind_group_layouts: &[
325 Some(&texture_bind_group_layout),
326 Some(&env_bind_group_layout),
327 Some(&berserker_bind_group_layout),
328 ],
329 immediate_size: 0,
330 });
331
332 let post_process_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
334 label: Some("Muspelheim Post Process Layout"),
335 bind_group_layouts: &[
336 Some(&texture_bind_group_layout),
337 Some(&texture_bind_group_layout),
338 Some(&berserker_bind_group_layout),
339 ],
340 immediate_size: 0,
341 });
342
343 let composite_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
345 label: Some("Muspelheim Composite Layout"),
346 bind_group_layouts: &[
347 Some(&texture_bind_group_layout),
348 Some(&texture_bind_group_layout),
349 Some(&berserker_bind_group_layout),
350 ],
351 immediate_size: 0,
352 });
353
354 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
355 label: Some("Surtr Main Pipeline"),
356 layout: Some(&pipeline_layout),
357 vertex: wgpu::VertexState {
358 module: &shader,
359 entry_point: Some("vs_main"),
360 buffers: &[Vertex::desc()],
361 compilation_options: wgpu::PipelineCompilationOptions::default(),
362 },
363 fragment: Some(wgpu::FragmentState {
364 module: &shader,
365 entry_point: Some("fs_main"),
366 targets: &[Some(wgpu::ColorTargetState {
367 format: config.format,
368 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
369 write_mask: wgpu::ColorWrites::ALL,
370 })],
371 compilation_options: wgpu::PipelineCompilationOptions::default(),
372 }),
373 primitive: wgpu::PrimitiveState::default(),
374 depth_stencil: None,
375 multisample: wgpu::MultisampleState::default(),
376 multiview_mask: None,
377 cache: None,
378 });
379
380 let background_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
381 label: Some("Surtr Background Pipeline"),
382 layout: Some(&pipeline_layout),
383 vertex: wgpu::VertexState {
384 module: &shader,
385 entry_point: Some("vs_fullscreen"),
386 buffers: &[],
387 compilation_options: wgpu::PipelineCompilationOptions::default(),
388 },
389 fragment: Some(wgpu::FragmentState {
390 module: &shader,
391 entry_point: Some("fs_background"),
392 targets: &[Some(wgpu::ColorTargetState {
393 format: config.format,
394 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
395 write_mask: wgpu::ColorWrites::ALL,
396 })],
397 compilation_options: wgpu::PipelineCompilationOptions::default(),
398 }),
399 primitive: wgpu::PrimitiveState::default(),
400 depth_stencil: None,
401 multisample: wgpu::MultisampleState::default(),
402 multiview_mask: None,
403 cache: None,
404 });
405
406 let bloom_extract_pipeline =
408 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
409 label: Some("Muspelheim Bloom Extract"),
410 layout: Some(&post_process_layout),
411 vertex: wgpu::VertexState {
412 module: &shader,
413 entry_point: Some("vs_fullscreen"),
414 buffers: &[],
415 compilation_options: wgpu::PipelineCompilationOptions::default(),
416 },
417 fragment: Some(wgpu::FragmentState {
418 module: &shader,
419 entry_point: Some("fs_bloom_extract"),
420 targets: &[Some(wgpu::ColorTargetState {
421 format: config.format,
422 blend: None,
423 write_mask: wgpu::ColorWrites::ALL,
424 })],
425 compilation_options: wgpu::PipelineCompilationOptions::default(),
426 }),
427 primitive: wgpu::PrimitiveState::default(),
428 depth_stencil: None,
429 multisample: wgpu::MultisampleState::default(),
430 multiview_mask: None,
431 cache: None,
432 });
433
434 let blur_h_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
436 label: Some("Muspelheim Horizontal Blur"),
437 layout: Some(&post_process_layout),
438 vertex: wgpu::VertexState {
439 module: &shader,
440 entry_point: Some("vs_fullscreen"),
441 buffers: &[],
442 compilation_options: wgpu::PipelineCompilationOptions::default(),
443 },
444 fragment: Some(wgpu::FragmentState {
445 module: &shader,
446 entry_point: Some("fs_blur_h"),
447 targets: &[Some(wgpu::ColorTargetState {
448 format: config.format,
449 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
450 write_mask: wgpu::ColorWrites::ALL,
451 })],
452 compilation_options: wgpu::PipelineCompilationOptions::default(),
453 }),
454 primitive: wgpu::PrimitiveState::default(),
455 depth_stencil: None,
456 multisample: wgpu::MultisampleState::default(),
457 multiview_mask: None,
458 cache: None,
459 });
460
461 let blur_v_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
462 label: Some("Muspelheim Vertical Blur"),
463 layout: Some(&post_process_layout),
464 vertex: wgpu::VertexState {
465 module: &shader,
466 entry_point: Some("vs_fullscreen"),
467 buffers: &[],
468 compilation_options: wgpu::PipelineCompilationOptions::default(),
469 },
470 fragment: Some(wgpu::FragmentState {
471 module: &shader,
472 entry_point: Some("fs_blur_v"),
473 targets: &[Some(wgpu::ColorTargetState {
474 format: config.format,
475 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
476 write_mask: wgpu::ColorWrites::ALL,
477 })],
478 compilation_options: wgpu::PipelineCompilationOptions::default(),
479 }),
480 primitive: wgpu::PrimitiveState::default(),
481 depth_stencil: None,
482 multisample: wgpu::MultisampleState::default(),
483 multiview_mask: None,
484 cache: None,
485 });
486
487 let composite_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
489 label: Some("Muspelheim Composite"),
490 layout: Some(&composite_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_composite"),
500 targets: &[Some(wgpu::ColorTargetState {
501 format: config.format,
502 blend: Some(wgpu::BlendState {
504 color: wgpu::BlendComponent {
505 src_factor: wgpu::BlendFactor::One,
506 dst_factor: wgpu::BlendFactor::One,
507 operation: wgpu::BlendOperation::Add,
508 },
509 alpha: wgpu::BlendComponent {
510 src_factor: wgpu::BlendFactor::One,
511 dst_factor: wgpu::BlendFactor::One,
512 operation: wgpu::BlendOperation::Add,
513 },
514 }),
515 write_mask: wgpu::ColorWrites::ALL,
516 })],
517 compilation_options: wgpu::PipelineCompilationOptions::default(),
518 }),
519 primitive: wgpu::PrimitiveState::default(),
520 depth_stencil: None,
521 multisample: wgpu::MultisampleState::default(),
522 multiview_mask: None,
523 cache: None,
524 });
525
526 let blur_tex_desc = wgpu::TextureDescriptor {
528 label: Some("Muspelheim Intermediate"),
529 size: wgpu::Extent3d {
530 width: config.width,
531 height: config.height,
532 depth_or_array_layers: 1,
533 },
534 mip_level_count: 1,
535 sample_count: 1,
536 dimension: wgpu::TextureDimension::D2,
537 format: config.format,
538 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
539 view_formats: &[],
540 };
541 let blur_texture_a_obj = device.create_texture(&blur_tex_desc);
542 let blur_texture_b_obj = device.create_texture(&blur_tex_desc);
543 let blur_texture_a =
544 blur_texture_a_obj.create_view(&wgpu::TextureViewDescriptor::default());
545 let blur_texture_b =
546 blur_texture_b_obj.create_view(&wgpu::TextureViewDescriptor::default());
547
548 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
549 address_mode_u: wgpu::AddressMode::ClampToEdge,
550 address_mode_v: wgpu::AddressMode::ClampToEdge,
551 mag_filter: wgpu::FilterMode::Linear,
552 ..Default::default()
553 });
554
555 let blur_bind_group_a = device.create_bind_group(&wgpu::BindGroupDescriptor {
556 layout: &texture_bind_group_layout,
557 entries: &[
558 wgpu::BindGroupEntry {
559 binding: 0,
560 resource: wgpu::BindingResource::TextureView(&blur_texture_a),
561 },
562 wgpu::BindGroupEntry {
563 binding: 1,
564 resource: wgpu::BindingResource::Sampler(&sampler),
565 },
566 ],
567 label: Some("Blur Bind Group A"),
568 });
569
570 let blur_bind_group_b = device.create_bind_group(&wgpu::BindGroupDescriptor {
571 layout: &texture_bind_group_layout,
572 entries: &[
573 wgpu::BindGroupEntry {
574 binding: 0,
575 resource: wgpu::BindingResource::TextureView(&blur_texture_b),
576 },
577 wgpu::BindGroupEntry {
578 binding: 1,
579 resource: wgpu::BindingResource::Sampler(&sampler),
580 },
581 ],
582 label: Some("Blur Bind Group B"),
583 });
584
585 let blur_env_bind_group_a = device.create_bind_group(&wgpu::BindGroupDescriptor {
586 layout: &env_bind_group_layout,
587 entries: &[
588 wgpu::BindGroupEntry {
589 binding: 0,
590 resource: wgpu::BindingResource::TextureView(&blur_texture_a),
591 },
592 wgpu::BindGroupEntry {
593 binding: 1,
594 resource: wgpu::BindingResource::Sampler(&sampler),
595 },
596 ],
597 label: Some("Blur Env Bind Group A"),
598 });
599
600 let scene_texture_obj = device.create_texture(&blur_tex_desc);
602 let scene_texture = scene_texture_obj.create_view(&wgpu::TextureViewDescriptor::default());
603 let scene_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
604 layout: &env_bind_group_layout,
605 entries: &[
606 wgpu::BindGroupEntry {
607 binding: 0,
608 resource: wgpu::BindingResource::TextureView(&scene_texture),
609 },
610 wgpu::BindGroupEntry {
611 binding: 1,
612 resource: wgpu::BindingResource::Sampler(&sampler),
613 },
614 ],
615 label: Some("Scene Capture Bind Group"),
616 });
617
618 let text_atlas_tex = device.create_texture(&wgpu::TextureDescriptor {
620 label: Some("Surtr Text Atlas"),
621 size: wgpu::Extent3d {
622 width: 1024,
623 height: 1024,
624 depth_or_array_layers: 1,
625 },
626 mip_level_count: 1,
627 sample_count: 1,
628 dimension: wgpu::TextureDimension::D2,
629 format: wgpu::TextureFormat::R8Unorm,
630 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
631 view_formats: &[],
632 });
633 let text_atlas = text_atlas_tex.create_view(&wgpu::TextureViewDescriptor::default());
634 let text_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
635 address_mode_u: wgpu::AddressMode::ClampToEdge,
636 address_mode_v: wgpu::AddressMode::ClampToEdge,
637 mag_filter: wgpu::FilterMode::Nearest,
638 min_filter: wgpu::FilterMode::Nearest,
639 ..Default::default()
640 });
641
642 queue.write_texture(
644 wgpu::TexelCopyTextureInfo {
645 texture: &text_atlas_tex,
646 mip_level: 0,
647 origin: wgpu::Origin3d::ZERO,
648 aspect: wgpu::TextureAspect::All,
649 },
650 &vec![0u8; 1024 * 1024],
651 wgpu::TexelCopyBufferLayout {
652 offset: 0,
653 bytes_per_row: Some(1024),
654 rows_per_image: Some(1024),
655 },
656 wgpu::Extent3d {
657 width: 1024,
658 height: 1024,
659 depth_or_array_layers: 1,
660 },
661 );
662
663 let dummy_size = wgpu::Extent3d {
665 width: 1,
666 height: 1,
667 depth_or_array_layers: 1,
668 };
669 let dummy_texture = device.create_texture(&wgpu::TextureDescriptor {
670 label: Some("Niflheim Dummy Texture"),
671 size: dummy_size,
672 mip_level_count: 1,
673 sample_count: 1,
674 dimension: wgpu::TextureDimension::D2,
675 format: wgpu::TextureFormat::Rgba8UnormSrgb,
676 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
677 view_formats: &[],
678 });
679 queue.write_texture(
680 wgpu::TexelCopyTextureInfo {
681 texture: &dummy_texture,
682 mip_level: 0,
683 origin: wgpu::Origin3d::ZERO,
684 aspect: wgpu::TextureAspect::All,
685 },
686 &[255, 255, 255, 255],
687 wgpu::TexelCopyBufferLayout {
688 offset: 0,
689 bytes_per_row: Some(4),
690 rows_per_image: Some(1),
691 },
692 dummy_size,
693 );
694
695 let dummy_view = dummy_texture.create_view(&wgpu::TextureViewDescriptor::default());
696 let dummy_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
697 address_mode_u: wgpu::AddressMode::ClampToEdge,
698 address_mode_v: wgpu::AddressMode::ClampToEdge,
699 address_mode_w: wgpu::AddressMode::ClampToEdge,
700 mag_filter: wgpu::FilterMode::Linear,
701 min_filter: wgpu::FilterMode::Nearest,
702 mipmap_filter: wgpu::MipmapFilterMode::Nearest,
703 ..Default::default()
704 });
705
706 let dummy_texture_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
707 layout: &texture_bind_group_layout,
708 entries: &[
709 wgpu::BindGroupEntry {
710 binding: 0,
711 resource: wgpu::BindingResource::TextureView(&dummy_view),
712 },
713 wgpu::BindGroupEntry {
714 binding: 1,
715 resource: wgpu::BindingResource::Sampler(&dummy_sampler),
716 },
717 ],
718 label: Some("Dummy Texture Bind Group"),
719 });
720
721 let dummy_env_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
722 layout: &env_bind_group_layout,
723 entries: &[
724 wgpu::BindGroupEntry {
725 binding: 0,
726 resource: wgpu::BindingResource::TextureView(&dummy_view),
727 },
728 wgpu::BindGroupEntry {
729 binding: 1,
730 resource: wgpu::BindingResource::Sampler(&dummy_sampler),
731 },
732 ],
733 label: Some("Dummy Env Bind Group"),
734 });
735
736 let mut texture_registry = std::collections::HashMap::new();
737 let mut texture_bind_groups = Vec::new();
738
739 let text_atlas_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
740 layout: &texture_bind_group_layout,
741 entries: &[
742 wgpu::BindGroupEntry {
743 binding: 0,
744 resource: wgpu::BindingResource::TextureView(&text_atlas),
745 },
746 wgpu::BindGroupEntry {
747 binding: 1,
748 resource: wgpu::BindingResource::Sampler(&text_sampler),
749 },
750 ],
751 label: Some("Text Atlas Bind Group"),
752 });
753 texture_registry.insert("__text_atlas".to_string(), texture_bind_groups.len() as u32);
754 texture_bind_groups.push(text_atlas_bg);
755
756 let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
758 label: Some("Surtr Vertex Anvil"),
759 size: (MAX_VERTICES * std::mem::size_of::<Vertex>()) as u64,
760 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
761 mapped_at_creation: false,
762 });
763
764 let index_buffer = device.create_buffer(&wgpu::BufferDescriptor {
765 label: Some("Surtr Index Anvil"),
766 size: (MAX_INDICES * std::mem::size_of::<u32>()) as u64,
767 usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
768 mapped_at_creation: false,
769 });
770
771 let current_theme = ColorTheme::default();
777 use wgpu::util::DeviceExt;
778 let theme_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
779 label: Some("Surtr Theme Buffer"),
780 contents: bytemuck::bytes_of(¤t_theme),
781 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
782 });
783
784 let scale_factor = window.scale_factor() as f32;
785 let current_scene = SceneUniforms::new(
786 size.width as f32 / scale_factor,
787 size.height as f32 / scale_factor,
788 );
789 let scene_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
790 label: Some("Surtr Scene Buffer"),
791 contents: bytemuck::bytes_of(¤t_scene),
792 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
793 });
794
795 let berserker_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
796 layout: &berserker_bind_group_layout,
797 entries: &[
798 wgpu::BindGroupEntry {
799 binding: 0,
800 resource: theme_buffer.as_entire_binding(),
801 },
802 wgpu::BindGroupEntry {
803 binding: 1,
804 resource: scene_buffer.as_entire_binding(),
805 },
806 ],
807 label: Some("Surtr Berserker Bind Group"),
808 });
809
810 let scene_texture_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
811 layout: &texture_bind_group_layout,
812 entries: &[
813 wgpu::BindGroupEntry {
814 binding: 0,
815 resource: wgpu::BindingResource::TextureView(&scene_texture),
816 },
817 wgpu::BindGroupEntry {
818 binding: 1,
819 resource: wgpu::BindingResource::Sampler(&sampler),
820 },
821 ],
822 label: Some("Scene Texture Bind Group (Group 0)"),
823 });
824
825 Self {
826 device,
827 queue,
828 surface,
829 config,
830 pipeline,
831 bloom_extract_pipeline,
832 blur_h_pipeline,
833 blur_v_pipeline,
834 composite_pipeline,
835 blur_texture_a,
836 blur_texture_b,
837 blur_bind_group_a,
838 blur_bind_group_b,
839 blur_env_bind_group_a,
840 scene_texture,
841 scene_bind_group,
842 scene_texture_bind_group,
843 env_bind_group_layout,
844 font_system: cosmic_text::FontSystem::new(),
845 swash_cache: cosmic_text::SwashCache::new(),
846 text_atlas_tex,
847 text_atlas_view: text_atlas,
848 text_sampler,
849 text_cache: std::collections::HashMap::new(),
850 text_atlas_pos: (0, 0),
851 dummy_texture_bind_group,
852 dummy_env_bind_group,
853 texture_bind_group_layout,
854 texture_bind_groups,
855 texture_registry,
856 shared_elements: std::collections::HashMap::new(),
857 vertex_buffer,
858 index_buffer,
859 vertices: Vec::with_capacity(MAX_VERTICES),
860 indices: Vec::with_capacity(MAX_INDICES),
861 draw_calls: Vec::new(),
862 current_texture_id: None,
863 opacity_stack: vec![1.0],
864 clip_stack: Vec::new(),
865 slice_stack: Vec::new(),
866 theme_buffer,
867 scene_buffer,
868 berserker_bind_group,
869 berserker_bind_group_layout,
870 start_time: std::time::Instant::now(),
871 current_theme,
872 current_scene,
873 background_pipeline,
874 scale_factor,
875 }
876 }
877
878 pub fn resize(&mut self, width: u32, height: u32, scale_factor: f32) {
884 if width > 0 && height > 0 {
885 self.config.width = width;
886 self.config.height = height;
887 self.scale_factor = scale_factor;
888 self.surface.configure(&self.device, &self.config);
889
890 let texture_desc = wgpu::TextureDescriptor {
892 label: Some("Surtr Scene Texture"),
893 size: wgpu::Extent3d {
894 width,
895 height,
896 depth_or_array_layers: 1,
897 },
898 mip_level_count: 1,
899 sample_count: 1,
900 dimension: wgpu::TextureDimension::D2,
901 format: self.config.format,
902 usage: wgpu::TextureUsages::RENDER_ATTACHMENT
903 | wgpu::TextureUsages::TEXTURE_BINDING,
904 view_formats: &[],
905 };
906
907 let scene_tex = self.device.create_texture(&texture_desc);
908 self.scene_texture = scene_tex.create_view(&wgpu::TextureViewDescriptor::default());
909
910 let blur_tex_a = self.device.create_texture(&texture_desc);
911 self.blur_texture_a = blur_tex_a.create_view(&wgpu::TextureViewDescriptor::default());
912
913 let blur_tex_b = self.device.create_texture(&texture_desc);
914 self.blur_texture_b = blur_tex_b.create_view(&wgpu::TextureViewDescriptor::default());
915
916 let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
918 address_mode_u: wgpu::AddressMode::ClampToEdge,
919 address_mode_v: wgpu::AddressMode::ClampToEdge,
920 mag_filter: wgpu::FilterMode::Linear,
921 min_filter: wgpu::FilterMode::Linear,
922 ..Default::default()
923 });
924
925 self.scene_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
926 layout: &self.env_bind_group_layout,
927 entries: &[
928 wgpu::BindGroupEntry {
929 binding: 0,
930 resource: wgpu::BindingResource::TextureView(&self.scene_texture),
931 },
932 wgpu::BindGroupEntry {
933 binding: 1,
934 resource: wgpu::BindingResource::Sampler(&sampler),
935 },
936 ],
937 label: Some("Surtr Scene Bind Group Resize"),
938 });
939
940 self.blur_bind_group_a = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
941 layout: &self.texture_bind_group_layout,
942 entries: &[
943 wgpu::BindGroupEntry {
944 binding: 0,
945 resource: wgpu::BindingResource::TextureView(&self.blur_texture_a),
946 },
947 wgpu::BindGroupEntry {
948 binding: 1,
949 resource: wgpu::BindingResource::Sampler(&sampler),
950 },
951 ],
952 label: Some("Surtr Blur Bind Group A Resize"),
953 });
954
955 self.blur_bind_group_b = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
956 layout: &self.texture_bind_group_layout,
957 entries: &[
958 wgpu::BindGroupEntry {
959 binding: 0,
960 resource: wgpu::BindingResource::TextureView(&self.blur_texture_b),
961 },
962 wgpu::BindGroupEntry {
963 binding: 1,
964 resource: wgpu::BindingResource::Sampler(&sampler),
965 },
966 ],
967 label: Some("Surtr Blur Bind Group B Resize"),
968 });
969
970 self.scene_texture_bind_group =
971 self.device.create_bind_group(&wgpu::BindGroupDescriptor {
972 layout: &self.texture_bind_group_layout,
973 entries: &[
974 wgpu::BindGroupEntry {
975 binding: 0,
976 resource: wgpu::BindingResource::TextureView(&self.scene_texture),
977 },
978 wgpu::BindGroupEntry {
979 binding: 1,
980 resource: wgpu::BindingResource::Sampler(&sampler),
981 },
982 ],
983 label: Some("Scene Texture Bind Group Resize"),
984 });
985
986 self.blur_env_bind_group_a =
987 self.device.create_bind_group(&wgpu::BindGroupDescriptor {
988 layout: &self.env_bind_group_layout,
989 entries: &[
990 wgpu::BindGroupEntry {
991 binding: 0,
992 resource: wgpu::BindingResource::TextureView(&self.blur_texture_a),
993 },
994 wgpu::BindGroupEntry {
995 binding: 1,
996 resource: wgpu::BindingResource::Sampler(&sampler),
997 },
998 ],
999 label: Some("Blur Env Bind Group A Resize"),
1000 });
1001
1002 self.current_scene.resolution = [width as f32, height as f32];
1003 }
1004 }
1005
1006 pub fn begin_frame(&mut self) -> wgpu::CommandEncoder {
1008 self.vertices.clear();
1009 self.indices.clear();
1010 self.draw_calls.clear();
1011 self.shared_elements.clear(); self.current_texture_id = None;
1013
1014 let time = self.start_time.elapsed().as_secs_f32();
1015 let logical_w = self.config.width as f32 / self.scale_factor;
1016 let logical_h = self.config.height as f32 / self.scale_factor;
1017 let dt = time - self.current_scene.time;
1018 self.current_scene.time = time;
1019 self.current_scene.delta_time = dt;
1020 self.current_scene.resolution = [logical_w, logical_h];
1021 self.current_scene.proj =
1022 glam::Mat4::orthographic_lh(0.0, logical_w, logical_h, 0.0, -100.0, 100.0);
1023
1024 self.queue.write_buffer(
1025 &self.scene_buffer,
1026 0,
1027 bytemuck::bytes_of(&self.current_scene),
1028 );
1029
1030 self.device
1031 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1032 label: Some("Surtr's Flaming Sword"),
1033 })
1034 }
1035
1036 pub fn reset_time(&mut self) {
1038 self.start_time = std::time::Instant::now();
1039 }
1040
1041 fn shatter_internal(
1042 &mut self,
1043 rect: Rect,
1044 pieces: u32,
1045 force: f32,
1046 color: [f32; 4],
1047 mode: u32,
1048 ) {
1049 let count = (pieces as f32).sqrt().ceil() as u32;
1051 let dw = rect.width / count as f32;
1052 let dh = rect.height / count as f32;
1053
1054 let c = self.apply_opacity(color);
1055
1056 for y in 0..count {
1057 for x in 0..count {
1058 let shard_rect = Rect {
1059 x: rect.x + x as f32 * dw,
1060 y: rect.y + y as f32 * dh,
1061 width: dw,
1062 height: dh,
1063 };
1064
1065 let uv = Rect {
1066 x: x as f32 / count as f32,
1067 y: y as f32 / count as f32,
1068 width: 1.0 / count as f32,
1069 height: 1.0 / count as f32,
1070 };
1071
1072 self.fill_rect_with_full_params(shard_rect, c, mode, None, force, uv);
1073 }
1074 }
1075 }
1076
1077 fn recursive_bolt(&mut self, from: [f32; 2], to: [f32; 2], depth: u32, color: [f32; 4]) {
1078 if depth == 0 {
1079 self.draw_lightning_segment(from, to, color);
1080 return;
1081 }
1082
1083 let mid_x = (from[0] + to[0]) * 0.5;
1084 let mid_y = (from[1] + to[1]) * 0.5;
1085
1086 let dx = to[0] - from[0];
1087 let dy = to[1] - from[1];
1088 let len = (dx * dx + dy * dy).sqrt();
1089
1090 let offset_scale = len * 0.15;
1092 let seed = (from[0] * 12.9898 + from[1] * 78.233 + (depth as f32) * 37.11)
1093 .sin()
1094 .fract();
1095 let offset_x = -dy / len * (seed - 0.5) * offset_scale;
1096 let offset_y = dx / len * (seed - 0.5) * offset_scale;
1097
1098 let mid = [mid_x + offset_x, mid_y + offset_y];
1099
1100 self.recursive_bolt(from, mid, depth - 1, color);
1101 self.recursive_bolt(mid, to, depth - 1, color);
1102
1103 if depth > 2 && seed > 0.8 {
1105 let branch_to = [
1106 mid[0] + offset_x * 2.0 + (seed * 100.0).sin() * 50.0,
1107 mid[1] + offset_y * 2.0 + (seed * 100.0).cos() * 50.0,
1108 ];
1109 self.recursive_bolt(mid, branch_to, depth - 2, color);
1110 }
1111 }
1112
1113 fn draw_lightning_segment(&mut self, from: [f32; 2], to: [f32; 2], color: [f32; 4]) {
1114 let dx = to[0] - from[0];
1115 let dy = to[1] - from[1];
1116 let len = (dx * dx + dy * dy).sqrt();
1117 if len < 0.001 {
1118 return;
1119 }
1120
1121 let glow_width = 32.0;
1122 let core_width = 4.0;
1123 let c = self.apply_opacity(color);
1124
1125 let gnx = -dy / len * glow_width * 0.5;
1127 let gny = dx / len * glow_width * 0.5;
1128 let gp1 = [from[0] + gnx, from[1] + gny];
1129 let gp2 = [to[0] + gnx, to[1] + gny];
1130 let gp3 = [to[0] - gnx, to[1] - gny];
1131 let gp4 = [from[0] - gnx, from[1] - gny];
1132 self.push_oriented_quad(
1133 [gp1, gp2, gp3, gp4],
1134 c,
1135 9,
1136 Rect {
1137 x: 0.0,
1138 y: 0.0,
1139 width: 1.0,
1140 height: 1.0,
1141 },
1142 );
1143
1144 let cnx = -dy / len * core_width * 0.5;
1146 let cny = dx / len * core_width * 0.5;
1147 let cp1 = [from[0] + cnx, from[1] + cny];
1148 let cp2 = [to[0] + cnx, to[1] + cny];
1149 let cp3 = [to[0] - cnx, to[1] - cny];
1150 let cp4 = [from[0] - cnx, from[1] - cny];
1151 self.push_oriented_quad(
1152 [cp1, cp2, cp3, cp4],
1153 [1.0, 1.0, 1.0, c[3]],
1154 0,
1155 Rect {
1156 x: 0.0,
1157 y: 0.0,
1158 width: 1.0,
1159 height: 1.0,
1160 },
1161 );
1162 }
1163
1164 fn push_oriented_quad(
1165 &mut self,
1166 points: [[f32; 2]; 4],
1167 color: [f32; 4],
1168 mode: u32,
1169 uv_rect: Rect,
1170 ) {
1171 let scissor = self.clip_stack.last().copied();
1172 let texture_id = None; if self.draw_calls.is_empty()
1175 || self.current_texture_id != texture_id
1176 || self.draw_calls.last().unwrap().scissor_rect != scissor
1177 {
1178 self.current_texture_id = texture_id;
1179 self.draw_calls.push(DrawCall {
1180 texture_id,
1181 scissor_rect: scissor,
1182 index_start: self.indices.len() as u32,
1183 index_count: 0,
1184 is_glass: mode == 7,
1185 is_ui: mode == 6,
1186 });
1187 }
1188
1189 let uvs = [
1190 [uv_rect.x, uv_rect.y],
1191 [uv_rect.x + uv_rect.width, uv_rect.y],
1192 [uv_rect.x + uv_rect.width, uv_rect.y + uv_rect.height],
1193 [uv_rect.x, uv_rect.y + uv_rect.height],
1194 ];
1195
1196 let screen = [self.config.width as f32, self.config.height as f32];
1197 let rect = Rect {
1198 x: points[0][0],
1199 y: points[0][1],
1200 width: 1.0,
1201 height: 1.0,
1202 };
1203
1204 for i in 0..4 {
1205 let px = points[i][0];
1206 let py = points[i][1];
1207
1208 self.vertices.push(Vertex {
1209 position: [px, py, 0.0],
1210 normal: [0.0, 0.0, 1.0],
1211 uv: uvs[i],
1212 color,
1213 mode,
1214 radius: 0.0,
1215 slice: [0.0, 0.0, 0.0, 1.0],
1216 logical: [px - rect.x, py - rect.y],
1217 size: [rect.width, rect.height],
1218 screen,
1219 clip: [-10000.0, -10000.0, 20000.0, 20000.0],
1220 });
1221 }
1222
1223 if let Some(call) = self.draw_calls.last_mut() {
1224 call.index_count += 6;
1225 }
1226 }
1227 fn get_texture_id(&self, name: &str) -> Option<u32> {
1228 self.texture_registry.get(name).copied()
1229 }
1230
1231 pub fn fill_rect_with_mode(
1233 &mut self,
1234 rect: Rect,
1235 color: [f32; 4],
1236 mode: u32,
1237 texture_id: Option<u32>,
1238 ) {
1239 self.fill_rect_with_full_params(
1240 rect,
1241 color,
1242 mode,
1243 texture_id,
1244 0.0,
1245 Rect {
1246 x: 0.0,
1247 y: 0.0,
1248 width: 1.0,
1249 height: 1.0,
1250 },
1251 );
1252 }
1253
1254 fn fill_rect_with_full_params(
1255 &mut self,
1256 rect: Rect,
1257 color: [f32; 4],
1258 mode: u32,
1259 texture_id: Option<u32>,
1260 radius: f32,
1261 uv_rect: Rect,
1262 ) {
1263 let slice = self
1264 .slice_stack
1265 .last()
1266 .copied()
1267 .map(|(a, o)| [a, o, 1.0, 1.0])
1268 .unwrap_or([0.0, 0.0, 0.0, 1.0]);
1269 self.fill_rect_with_full_params_and_slice(
1270 rect, color, mode, texture_id, radius, uv_rect, slice,
1271 );
1272 }
1273
1274 fn fill_rect_with_full_params_and_slice(
1275 &mut self,
1276 rect: Rect,
1277 color: [f32; 4],
1278 mode: u32,
1279 texture_id: Option<u32>,
1280 radius: f32,
1281 uv_rect: Rect,
1282 slice: [f32; 4],
1283 ) {
1284 let scissor = self.clip_stack.last().copied();
1285
1286 let is_glass = mode == 7;
1287 let is_ui = mode == 6;
1288
1289 let last_call = self.draw_calls.last();
1291 let needs_new_call = self.draw_calls.is_empty()
1292 || self.current_texture_id != texture_id
1293 || last_call.unwrap().scissor_rect != scissor
1294 || last_call.unwrap().is_glass != is_glass
1295 || last_call.unwrap().is_ui != is_ui;
1296
1297 if needs_new_call {
1298 self.current_texture_id = texture_id;
1299 self.draw_calls.push(DrawCall {
1300 texture_id,
1301 scissor_rect: scissor,
1302 index_start: self.indices.len() as u32,
1303 index_count: 0,
1304 is_glass,
1305 is_ui,
1306 });
1307 }
1308
1309 let base_idx = self.vertices.len() as u32;
1310 let x1 = rect.x;
1311 let y1 = rect.y;
1312 let x2 = rect.x + rect.width;
1313 let y2 = rect.y + rect.height;
1314 let z = 0.0;
1315 let normal = [0.0, 0.0, 1.0];
1316 let screen = [self.config.width as f32, self.config.height as f32];
1317 let clip_rect = self.clip_stack.last().copied().unwrap_or(cvkg_core::Rect {
1318 x: -10000.0,
1319 y: -10000.0,
1320 width: 20000.0,
1321 height: 20000.0,
1322 });
1323 let clip = [clip_rect.x, clip_rect.y, clip_rect.width, clip_rect.height];
1324
1325 self.vertices.push(Vertex {
1326 position: [x1, y1, z],
1327 normal,
1328 uv: [uv_rect.x, uv_rect.y],
1329 color,
1330 mode,
1331 radius,
1332 slice,
1333 logical: [0.0, 0.0],
1334 size: [rect.width, rect.height],
1335 screen,
1336 clip,
1337 });
1338 self.vertices.push(Vertex {
1339 position: [x2, y1, z],
1340 normal,
1341 uv: [uv_rect.x + uv_rect.width, uv_rect.y],
1342 color,
1343 mode,
1344 radius,
1345 slice,
1346 logical: [rect.width, 0.0],
1347 size: [rect.width, rect.height],
1348 screen,
1349 clip,
1350 });
1351 self.vertices.push(Vertex {
1352 position: [x2, y2, z],
1353 normal,
1354 uv: [uv_rect.x + uv_rect.width, uv_rect.y + uv_rect.height],
1355 color,
1356 mode,
1357 radius,
1358 slice,
1359 logical: [rect.width, rect.height],
1360 size: [rect.width, rect.height],
1361 screen,
1362 clip,
1363 });
1364 self.vertices.push(Vertex {
1365 position: [x1, y2, z],
1366 normal,
1367 uv: [uv_rect.x, uv_rect.y + uv_rect.height],
1368 color,
1369 mode,
1370 radius,
1371 slice,
1372 logical: [0.0, rect.height],
1373 size: [rect.width, rect.height],
1374 screen,
1375 clip,
1376 });
1377
1378 self.indices.extend_from_slice(&[
1379 base_idx,
1380 base_idx + 1,
1381 base_idx + 2,
1382 base_idx,
1383 base_idx + 2,
1384 base_idx + 3,
1385 ]);
1386
1387 if let Some(call) = self.draw_calls.last_mut() {
1388 call.index_count += 6;
1389 }
1390 }
1391
1392 pub fn end_frame(&mut self, mut encoder: wgpu::CommandEncoder) {
1394 self.queue
1395 .write_buffer(&self.vertex_buffer, 0, bytemuck::cast_slice(&self.vertices));
1396 self.queue
1397 .write_buffer(&self.index_buffer, 0, bytemuck::cast_slice(&self.indices));
1398
1399 let frame = match self.surface.get_current_texture() {
1400 wgpu::CurrentSurfaceTexture::Success(t) => t,
1401 wgpu::CurrentSurfaceTexture::Suboptimal(t) => {
1402 self.surface.configure(&self.device, &self.config);
1403 t
1404 }
1405 wgpu::CurrentSurfaceTexture::Timeout
1406 | wgpu::CurrentSurfaceTexture::Outdated
1407 | wgpu::CurrentSurfaceTexture::Lost
1408 | wgpu::CurrentSurfaceTexture::Occluded
1409 | wgpu::CurrentSurfaceTexture::Validation => {
1410 self.surface.configure(&self.device, &self.config);
1411 return;
1412 }
1413 };
1414 let screen = frame
1415 .texture
1416 .create_view(&wgpu::TextureViewDescriptor::default());
1417
1418 {
1420 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1421 label: Some("Surtr P1 Opaque Background"),
1422 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1423 view: &self.scene_texture,
1424 resolve_target: None,
1425 ops: wgpu::Operations {
1426 load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
1427 store: wgpu::StoreOp::Store,
1428 },
1429 depth_slice: None,
1430 })],
1431 depth_stencil_attachment: None,
1432 occlusion_query_set: None,
1433 timestamp_writes: None,
1434 multiview_mask: None,
1435 });
1436
1437 p.set_pipeline(&self.background_pipeline);
1439 p.set_bind_group(0, &self.dummy_texture_bind_group, &[]);
1440 p.set_bind_group(1, &self.blur_env_bind_group_a, &[]); p.set_bind_group(2, &self.berserker_bind_group, &[]);
1442 p.draw(0..6, 0..1);
1443
1444 if !self.draw_calls.is_empty() {
1446 p.set_pipeline(&self.pipeline);
1447 p.set_vertex_buffer(0, self.vertex_buffer.slice(..));
1448 p.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
1449 p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
1450 p.set_bind_group(2, &self.berserker_bind_group, &[]);
1451
1452 for call in self.draw_calls.iter().filter(|c| !c.is_glass && !c.is_ui) {
1453 let bg = if let Some(id) = call.texture_id {
1454 self.texture_bind_groups
1455 .get(id as usize)
1456 .unwrap_or(&self.dummy_texture_bind_group)
1457 } else {
1458 &self.dummy_texture_bind_group
1459 };
1460 p.set_bind_group(0, bg, &[]);
1461 p.draw_indexed(
1462 call.index_start..call.index_start + call.index_count,
1463 0,
1464 0..1,
1465 );
1466 }
1467 }
1468 }
1469
1470 {
1473 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1475 label: Some("Surtr Blur Extract"),
1476 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1477 view: &self.blur_texture_a,
1478 resolve_target: None,
1479 ops: wgpu::Operations {
1480 load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
1481 store: wgpu::StoreOp::Store,
1482 },
1483 depth_slice: None,
1484 })],
1485 ..Default::default()
1486 });
1487 p.set_pipeline(&self.bloom_extract_pipeline); p.set_bind_group(0, &self.scene_texture_bind_group, &[]);
1489 p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
1490 p.set_bind_group(2, &self.berserker_bind_group, &[]);
1491 p.draw(0..6, 0..1);
1492 }
1493
1494 let blur_iters: u32 = 4;
1495 for _i in 0..blur_iters {
1496 {
1497 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1498 label: Some("Blur H"),
1499 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1500 view: &self.blur_texture_b,
1501 resolve_target: None,
1502 ops: wgpu::Operations {
1503 load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
1504 store: wgpu::StoreOp::Store,
1505 },
1506 depth_slice: None,
1507 })],
1508 ..Default::default()
1509 });
1510 p.set_pipeline(&self.blur_h_pipeline);
1511 p.set_bind_group(0, &self.blur_bind_group_a, &[]);
1512 p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
1513 p.set_bind_group(2, &self.berserker_bind_group, &[]);
1514 p.draw(0..6, 0..1);
1515 }
1516 {
1517 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1518 label: Some("Blur V"),
1519 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1520 view: &self.blur_texture_a,
1521 resolve_target: None,
1522 ops: wgpu::Operations {
1523 load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
1524 store: wgpu::StoreOp::Store,
1525 },
1526 depth_slice: None,
1527 })],
1528 ..Default::default()
1529 });
1530 p.set_pipeline(&self.blur_v_pipeline);
1531 p.set_bind_group(0, &self.blur_bind_group_b, &[]);
1532 p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
1533 p.set_bind_group(2, &self.berserker_bind_group, &[]);
1534 p.draw(0..6, 0..1);
1535 }
1536 }
1537
1538 {
1540 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1541 label: Some("Surtr P3 Liquid Glass"),
1542 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1543 view: &self.scene_texture, resolve_target: None,
1545 ops: wgpu::Operations {
1546 load: wgpu::LoadOp::Load,
1547 store: wgpu::StoreOp::Store,
1548 },
1549 depth_slice: None,
1550 })],
1551 ..Default::default()
1552 });
1553
1554 p.set_pipeline(&self.pipeline);
1555 p.set_vertex_buffer(0, self.vertex_buffer.slice(..));
1556 p.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
1557 p.set_bind_group(1, &self.blur_env_bind_group_a, &[]); p.set_bind_group(2, &self.berserker_bind_group, &[]);
1559
1560 for call in self.draw_calls.iter().filter(|c| c.is_glass) {
1561 let bg = if let Some(id) = call.texture_id {
1562 self.texture_bind_groups
1563 .get(id as usize)
1564 .unwrap_or(&self.dummy_texture_bind_group)
1565 } else {
1566 &self.dummy_texture_bind_group
1567 };
1568 p.set_bind_group(0, bg, &[]);
1569 p.draw_indexed(
1570 call.index_start..call.index_start + call.index_count,
1571 0,
1572 0..1,
1573 );
1574 }
1575 }
1576
1577 {
1579 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1580 label: Some("Surtr P4 UI Layer"),
1581 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1582 view: &self.scene_texture,
1583 resolve_target: None,
1584 ops: wgpu::Operations {
1585 load: wgpu::LoadOp::Load,
1586 store: wgpu::StoreOp::Store,
1587 },
1588 depth_slice: None,
1589 })],
1590 ..Default::default()
1591 });
1592
1593 p.set_pipeline(&self.pipeline);
1594 p.set_vertex_buffer(0, self.vertex_buffer.slice(..));
1595 p.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
1596 p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
1597 p.set_bind_group(2, &self.berserker_bind_group, &[]);
1598
1599 for call in self.draw_calls.iter().filter(|c| c.is_ui) {
1600 let bg = if let Some(id) = call.texture_id {
1601 self.texture_bind_groups
1602 .get(id as usize)
1603 .unwrap_or(&self.dummy_texture_bind_group)
1604 } else {
1605 &self.dummy_texture_bind_group
1606 };
1607 p.set_bind_group(0, bg, &[]);
1608 p.draw_indexed(
1609 call.index_start..call.index_start + call.index_count,
1610 0,
1611 0..1,
1612 );
1613 }
1614 }
1615
1616 {
1618 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1619 label: Some("Surtr Bloom Extract"),
1620 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1621 view: &self.blur_texture_a,
1622 resolve_target: None,
1623 ops: wgpu::Operations {
1624 load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
1625 store: wgpu::StoreOp::Store,
1626 },
1627 depth_slice: None,
1628 })],
1629 ..Default::default()
1630 });
1631 p.set_pipeline(&self.bloom_extract_pipeline);
1632 p.set_bind_group(0, &self.scene_texture_bind_group, &[]);
1633 p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
1634 p.set_bind_group(2, &self.berserker_bind_group, &[]);
1635 p.draw(0..6, 0..1);
1636 }
1637
1638 for _ in 0..2 {
1640 {
1641 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1642 label: Some("Bloom Blur H"),
1643 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1644 view: &self.blur_texture_b,
1645 resolve_target: None,
1646 ops: wgpu::Operations {
1647 load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
1648 store: wgpu::StoreOp::Store,
1649 },
1650 depth_slice: None,
1651 })],
1652 ..Default::default()
1653 });
1654 p.set_pipeline(&self.blur_h_pipeline);
1655 p.set_bind_group(0, &self.blur_bind_group_a, &[]);
1656 p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
1657 p.set_bind_group(2, &self.berserker_bind_group, &[]);
1658 p.draw(0..6, 0..1);
1659 }
1660 {
1661 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1662 label: Some("Bloom Blur V"),
1663 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1664 view: &self.blur_texture_a,
1665 resolve_target: None,
1666 ops: wgpu::Operations {
1667 load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
1668 store: wgpu::StoreOp::Store,
1669 },
1670 depth_slice: None,
1671 })],
1672 ..Default::default()
1673 });
1674 p.set_pipeline(&self.blur_v_pipeline);
1675 p.set_bind_group(0, &self.blur_bind_group_b, &[]);
1676 p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
1677 p.set_bind_group(2, &self.berserker_bind_group, &[]);
1678 p.draw(0..6, 0..1);
1679 }
1680 }
1681
1682 {
1684 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1685 label: Some("Surtr P7 Final Composite"),
1686 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1687 view: &screen,
1688 resolve_target: None,
1689 ops: wgpu::Operations {
1690 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1691 store: wgpu::StoreOp::Store,
1692 },
1693 depth_slice: None,
1694 })],
1695 ..Default::default()
1696 });
1697 p.set_pipeline(&self.composite_pipeline);
1698 p.set_bind_group(0, &self.scene_texture_bind_group, &[]); p.set_bind_group(1, &self.blur_bind_group_a, &[]); p.set_bind_group(2, &self.berserker_bind_group, &[]);
1701 p.draw(0..6, 0..1);
1702 }
1703
1704 self.queue.submit(Some(encoder.finish()));
1705 frame.present();
1706 }
1707}
1708
1709impl cvkg_core::Renderer for SurtrRenderer {
1710 fn fill_rect(&mut self, rect: Rect, color: [f32; 4]) {
1712 self.fill_rect_with_mode(rect, self.apply_opacity(color), 0, None);
1713 }
1714
1715 fn fill_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4]) {
1716 self.fill_rect_with_full_params(
1717 rect,
1718 self.apply_opacity(color),
1719 3,
1720 None,
1721 radius,
1722 Rect {
1723 x: 0.0,
1724 y: 0.0,
1725 width: 1.0,
1726 height: 1.0,
1727 },
1728 );
1729 }
1730
1731 fn fill_ellipse(&mut self, rect: Rect, color: [f32; 4]) {
1732 self.fill_rect_with_full_params(
1733 rect,
1734 self.apply_opacity(color),
1735 4,
1736 None,
1737 0.0,
1738 Rect {
1739 x: 0.0,
1740 y: 0.0,
1741 width: 1.0,
1742 height: 1.0,
1743 },
1744 );
1745 }
1746
1747 fn bifrost(&mut self, rect: Rect, blur: f32, _saturation: f32, opacity: f32) {
1748 let screen_uv = Rect {
1750 x: rect.x / self.config.width as f32,
1751 y: rect.y / self.config.height as f32,
1752 width: rect.width / self.config.width as f32,
1753 height: rect.height / self.config.height as f32,
1754 };
1755 self.fill_rect_with_full_params(rect, [1.0, 1.0, 1.0, opacity], 7, None, blur, screen_uv);
1758 }
1759
1760 fn stroke_rect(&mut self, rect: Rect, color: [f32; 4], stroke_width: f32) {
1761 let c = self.apply_opacity(color);
1762 let hw = stroke_width;
1763 self.fill_rect_with_mode(
1765 Rect {
1766 x: rect.x,
1767 y: rect.y,
1768 width: rect.width,
1769 height: hw,
1770 },
1771 c,
1772 1,
1773 None,
1774 );
1775 self.fill_rect_with_mode(
1776 Rect {
1777 x: rect.x,
1778 y: rect.y + rect.height - hw,
1779 width: rect.width,
1780 height: hw,
1781 },
1782 c,
1783 1,
1784 None,
1785 );
1786 self.fill_rect_with_mode(
1787 Rect {
1788 x: rect.x,
1789 y: rect.y,
1790 width: hw,
1791 height: rect.height,
1792 },
1793 c,
1794 1,
1795 None,
1796 );
1797 self.fill_rect_with_mode(
1798 Rect {
1799 x: rect.x + rect.width - hw,
1800 y: rect.y,
1801 width: hw,
1802 height: rect.height,
1803 },
1804 c,
1805 1,
1806 None,
1807 );
1808 }
1809
1810 fn stroke_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4], stroke_width: f32) {
1811 self.fill_rect_with_full_params(
1812 rect,
1813 self.apply_opacity(color),
1814 17,
1815 None,
1816 radius,
1817 Rect {
1818 x: stroke_width,
1819 y: 0.0,
1820 width: 0.0,
1821 height: 0.0,
1822 },
1823 );
1824 }
1825
1826 fn stroke_ellipse(&mut self, _rect: Rect, _color: [f32; 4], _stroke_width: f32) {
1827 }
1829
1830 fn draw_linear_gradient(
1831 &mut self,
1832 rect: Rect,
1833 start_color: [f32; 4],
1834 end_color: [f32; 4],
1835 angle: f32,
1836 ) {
1837 self.fill_rect_with_full_params_and_slice(
1838 rect,
1839 self.apply_opacity(start_color),
1840 15,
1841 None,
1842 0.0,
1843 Rect {
1844 x: angle,
1845 y: 0.0,
1846 width: 1.0,
1847 height: 1.0,
1848 },
1849 end_color,
1850 );
1851 }
1852
1853 fn draw_radial_gradient(&mut self, rect: Rect, inner_color: [f32; 4], outer_color: [f32; 4]) {
1854 self.fill_rect_with_full_params_and_slice(
1855 rect,
1856 self.apply_opacity(inner_color),
1857 16,
1858 None,
1859 0.0,
1860 Rect {
1861 x: 0.0,
1862 y: 0.0,
1863 width: 1.0,
1864 height: 1.0,
1865 },
1866 outer_color,
1867 );
1868 }
1869
1870 fn draw_drop_shadow(
1871 &mut self,
1872 rect: Rect,
1873 radius: f32,
1874 color: [f32; 4],
1875 blur: f32,
1876 spread: f32,
1877 ) {
1878 let margin = blur + spread;
1879 let inflated = Rect {
1880 x: rect.x - margin,
1881 y: rect.y - margin,
1882 width: rect.width + margin * 2.0,
1883 height: rect.height + margin * 2.0,
1884 };
1885 self.fill_rect_with_full_params(
1887 inflated,
1888 self.apply_opacity(color),
1889 18,
1890 None,
1891 radius,
1892 Rect {
1893 x: margin,
1894 y: blur,
1895 width: 0.0,
1896 height: 0.0,
1897 },
1898 );
1899 }
1900
1901 fn stroke_dashed_rounded_rect(
1902 &mut self,
1903 rect: Rect,
1904 radius: f32,
1905 color: [f32; 4],
1906 width: f32,
1907 dash: f32,
1908 gap: f32,
1909 ) {
1910 self.fill_rect_with_full_params(
1911 rect,
1912 self.apply_opacity(color),
1913 19,
1914 None,
1915 radius,
1916 Rect {
1917 x: width,
1918 y: dash,
1919 width: gap,
1920 height: 0.0,
1921 },
1922 );
1923 }
1924
1925 fn draw_9slice(
1926 &mut self,
1927 image_name: &str,
1928 rect: Rect,
1929 left: f32,
1930 top: f32,
1931 right: f32,
1932 bottom: f32,
1933 ) {
1934 let c = self.apply_opacity([1.0, 1.0, 1.0, 1.0]);
1935 let tid = self.get_texture_id(image_name);
1936 self.fill_rect_with_full_params(
1937 rect,
1938 c,
1939 20,
1940 tid,
1941 bottom,
1942 Rect {
1943 x: left,
1944 y: top,
1945 width: right,
1946 height: 0.0,
1947 },
1948 );
1949 }
1950
1951 fn draw_line(
1952 &mut self,
1953 x1: f32,
1954 y1: f32,
1955 x2: f32,
1956 y2: f32,
1957 color: [f32; 4],
1958 stroke_width: f32,
1959 ) {
1960 let dx = x2 - x1;
1961 let dy = y2 - y1;
1962 let len = (dx * dx + dy * dy).sqrt();
1963 if len < 0.001 {
1964 return;
1965 }
1966
1967 let _angle = dy.atan2(dx).to_degrees();
1968 let c = self.apply_opacity(color);
1969
1970 self.fill_rect_with_mode(
1975 Rect {
1976 x: (x1 + x2) / 2.0 - len / 2.0,
1977 y: (y1 + y2) / 2.0 - stroke_width / 2.0,
1978 width: len,
1979 height: stroke_width,
1980 },
1981 c,
1982 1, None,
1984 );
1985 }
1986
1987 fn draw_text(&mut self, text: &str, x: f32, y: f32, size: f32, color: [f32; 4]) {
1988 let mut buffer =
1990 cosmic_text::Buffer::new(&mut self.font_system, cosmic_text::Metrics::new(size, size));
1991 buffer.set_text(
1993 &mut self.font_system,
1994 text,
1995 &cosmic_text::Attrs::new(),
1996 cosmic_text::Shaping::Basic,
1997 );
1998 buffer.shape_until_scroll(&mut self.font_system, false);
1999
2000 let c = self.apply_opacity(color);
2001
2002 let mut glyph_idx = 0;
2003 for run in buffer.layout_runs() {
2004 for glyph in run.glyphs {
2005 let tracking = (2.8 - 0.22 * (size - 16.0)).max(0.6);
2007 let x_offset = x + (glyph_idx as f32 * tracking);
2008
2009 let physical_glyph = glyph.physical((x_offset, y), 1.0);
2010 let cache_key = physical_glyph.cache_key;
2011
2012 let (uv_rect, w, h) = if let Some(info) = self.text_cache.get(&cache_key) {
2014 *info
2015 } else {
2016 if let Some(image) =
2018 self.swash_cache.get_image(&mut self.font_system, cache_key)
2019 {
2020 let (gx, _gy) = self.text_atlas_pos;
2021 let gw = image.placement.width;
2022 let gh = image.placement.height;
2023
2024 if gx + gw > 1024 {
2026 self.text_atlas_pos.0 = 0;
2027 self.text_atlas_pos.1 += 64; }
2029 let (nx, ny) = self.text_atlas_pos;
2030
2031 self.queue.write_texture(
2032 wgpu::TexelCopyTextureInfo {
2033 texture: &self.text_atlas_tex,
2034 mip_level: 0,
2035 origin: wgpu::Origin3d { x: nx, y: ny, z: 0 },
2036 aspect: wgpu::TextureAspect::All,
2037 },
2038 &image.data,
2039 wgpu::TexelCopyBufferLayout {
2040 offset: 0,
2041 bytes_per_row: Some(gw),
2042 rows_per_image: Some(gh),
2043 },
2044 wgpu::Extent3d {
2045 width: gw,
2046 height: gh,
2047 depth_or_array_layers: 1,
2048 },
2049 );
2050
2051 let info = (
2052 Rect {
2053 x: nx as f32 / 1024.0,
2054 y: ny as f32 / 1024.0,
2055 width: gw as f32 / 1024.0,
2056 height: gh as f32 / 1024.0,
2057 },
2058 gw as f32,
2059 gh as f32,
2060 );
2061 self.text_cache.insert(cache_key, info);
2062 self.text_atlas_pos.0 += gw + 2;
2063 info
2064 } else {
2065 (Rect::zero(), 0.0, 0.0)
2066 }
2067 };
2068
2069 if w > 0.0 {
2070 let glyph_rect = Rect {
2071 x: physical_glyph.x as f32,
2072 y: physical_glyph.y as f32,
2073 width: w,
2074 height: h,
2075 };
2076 let tid = self.get_texture_id("__text_atlas");
2077 self.fill_rect_with_full_params(glyph_rect, c, 6, tid, 0.0, uv_rect); }
2079 glyph_idx += 1;
2080 }
2081 }
2082 }
2083
2084 fn measure_text(&mut self, text: &str, size: f32) -> (f32, f32) {
2087 let mut buffer =
2088 cosmic_text::Buffer::new(&mut self.font_system, cosmic_text::Metrics::new(size, size));
2089 buffer.set_text(
2090 &mut self.font_system,
2091 text,
2092 &cosmic_text::Attrs::new(),
2093 cosmic_text::Shaping::Advanced,
2094 );
2095 buffer.shape_until_scroll(&mut self.font_system, false);
2096
2097 let mut width = 0.0f32;
2098 let mut height = 0.0f32;
2099
2100 for run in buffer.layout_runs() {
2101 width = width.max(run.line_w);
2102 height += size;
2103 }
2104
2105 (width, height)
2106 }
2107
2108 fn draw_texture(&mut self, texture_id: u32, rect: Rect) {
2109 self.fill_rect_with_full_params(
2110 rect,
2111 [1.0, 1.0, 1.0, 1.0],
2112 2,
2113 Some(texture_id),
2114 0.0,
2115 Rect {
2116 x: 0.0,
2117 y: 0.0,
2118 width: 1.0,
2119 height: 1.0,
2120 },
2121 );
2122 }
2123
2124 fn draw_image(&mut self, image_name: &str, rect: Rect) {
2125 let tid = self.get_texture_id(image_name);
2126 self.fill_rect_with_full_params(
2127 rect,
2128 [1.0, 1.0, 1.0, 1.0],
2129 2,
2130 tid,
2131 0.0,
2132 Rect {
2133 x: 0.0,
2134 y: 0.0,
2135 width: 1.0,
2136 height: 1.0,
2137 },
2138 );
2139 }
2140
2141 fn load_image(&mut self, name: &str, data: &[u8]) {
2144 if self.texture_registry.contains_key(name) {
2145 return;
2146 }
2147 let img = image::load_from_memory(data)
2148 .expect("Failed to load image")
2149 .to_rgba8();
2150 let (width, height) = img.dimensions();
2151 let size = wgpu::Extent3d {
2152 width,
2153 height,
2154 depth_or_array_layers: 1,
2155 };
2156 let texture = self.device.create_texture(&wgpu::TextureDescriptor {
2157 label: Some(name),
2158 size,
2159 mip_level_count: 1,
2160 sample_count: 1,
2161 dimension: wgpu::TextureDimension::D2,
2162 format: wgpu::TextureFormat::Rgba8UnormSrgb,
2163 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
2164 view_formats: &[],
2165 });
2166 self.queue.write_texture(
2167 wgpu::TexelCopyTextureInfo {
2168 texture: &texture,
2169 mip_level: 0,
2170 origin: wgpu::Origin3d::ZERO,
2171 aspect: wgpu::TextureAspect::All,
2172 },
2173 &img,
2174 wgpu::TexelCopyBufferLayout {
2175 offset: 0,
2176 bytes_per_row: Some(4 * width),
2177 rows_per_image: Some(height),
2178 },
2179 size,
2180 );
2181 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
2182 let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
2183 address_mode_u: wgpu::AddressMode::ClampToEdge,
2184 address_mode_v: wgpu::AddressMode::ClampToEdge,
2185 mag_filter: wgpu::FilterMode::Linear,
2186 ..Default::default()
2187 });
2188 let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2189 layout: &self.texture_bind_group_layout,
2190 entries: &[
2191 wgpu::BindGroupEntry {
2192 binding: 0,
2193 resource: wgpu::BindingResource::TextureView(&view),
2194 },
2195 wgpu::BindGroupEntry {
2196 binding: 1,
2197 resource: wgpu::BindingResource::Sampler(&sampler),
2198 },
2199 ],
2200 label: Some(name),
2201 });
2202 self.texture_bind_groups.push(bind_group);
2203 let id = (self.texture_bind_groups.len() - 1) as u32;
2204 self.texture_registry.insert(name.to_string(), id);
2205 }
2206
2207 fn push_clip_rect(&mut self, rect: Rect) {
2208 self.clip_stack.push(rect);
2209 }
2210
2211 fn pop_clip_rect(&mut self) {
2212 self.clip_stack.pop();
2213 }
2214
2215 fn push_opacity(&mut self, opacity: f32) {
2216 let current = self.opacity_stack.last().copied().unwrap_or(1.0);
2217 self.opacity_stack.push(current * opacity);
2218 }
2219
2220 fn pop_opacity(&mut self) {
2221 self.opacity_stack.pop();
2222 }
2223
2224 fn set_theme(&mut self, theme: ColorTheme) {
2225 self.current_theme = theme;
2226 self.queue
2227 .write_buffer(&self.theme_buffer, 0, bytemuck::bytes_of(&theme));
2228 }
2229
2230 fn set_rage(&mut self, rage: f32) {
2231 self.current_scene.berzerker_rage = rage;
2232 }
2234
2235 fn trigger_shatter_event(&mut self, origin: [f32; 2], force: f32) {
2236 self.current_scene.shatter_origin = origin;
2237 self.current_scene.shatter_time = self.current_scene.time;
2238 self.current_scene.shatter_force = force;
2239 }
2240
2241 fn push_mjolnir_slice(&mut self, angle: f32, offset: f32) {
2244 self.slice_stack.push((angle, offset));
2245 }
2246
2247 fn pop_mjolnir_slice(&mut self) {
2249 self.slice_stack.pop();
2250 }
2251
2252 fn mjolnir_shatter(&mut self, rect: Rect, pieces: u32, force: f32, color: [f32; 4]) {
2253 self.shatter_internal(rect, pieces, force, color, 8);
2254 }
2255
2256 fn mjolnir_fluid_shatter(&mut self, rect: Rect, pieces: u32, force: f32, color: [f32; 4]) {
2257 self.shatter_internal(rect, pieces, force, color, 11);
2258 }
2259
2260 fn draw_mjolnir_bolt(&mut self, from: [f32; 2], to: [f32; 2], color: [f32; 4]) {
2261 self.recursive_bolt(from, to, 4, color);
2262 }
2263
2264 fn upload_data_texture(&mut self, id: &str, data: &[f32], width: u32, height: u32) {
2265 let size = wgpu::Extent3d {
2266 width,
2267 height,
2268 depth_or_array_layers: 1,
2269 };
2270 let texture = self.device.create_texture(&wgpu::TextureDescriptor {
2271 label: Some(id),
2272 size,
2273 mip_level_count: 1,
2274 sample_count: 1,
2275 dimension: wgpu::TextureDimension::D2,
2276 format: wgpu::TextureFormat::R32Float,
2277 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
2278 view_formats: &[],
2279 });
2280 self.queue.write_texture(
2281 wgpu::TexelCopyTextureInfo {
2282 texture: &texture,
2283 mip_level: 0,
2284 origin: wgpu::Origin3d::ZERO,
2285 aspect: wgpu::TextureAspect::All,
2286 },
2287 bytemuck::cast_slice(data),
2288 wgpu::TexelCopyBufferLayout {
2289 offset: 0,
2290 bytes_per_row: Some(4 * width),
2291 rows_per_image: Some(height),
2292 },
2293 size,
2294 );
2295 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
2296 let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
2297 address_mode_u: wgpu::AddressMode::ClampToEdge,
2298 address_mode_v: wgpu::AddressMode::ClampToEdge,
2299 mag_filter: wgpu::FilterMode::Linear,
2300 ..Default::default()
2301 });
2302 let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2303 layout: &self.texture_bind_group_layout,
2304 entries: &[
2305 wgpu::BindGroupEntry {
2306 binding: 0,
2307 resource: wgpu::BindingResource::TextureView(&view),
2308 },
2309 wgpu::BindGroupEntry {
2310 binding: 1,
2311 resource: wgpu::BindingResource::Sampler(&sampler),
2312 },
2313 ],
2314 label: Some(id),
2315 });
2316 self.texture_bind_groups.push(bind_group);
2317 let tid = (self.texture_bind_groups.len() - 1) as u32;
2318 self.texture_registry.insert(id.to_string(), tid);
2319 }
2320
2321 fn draw_heatmap(&mut self, texture_id: &str, rect: Rect, _palette: &str) {
2322 let tid = self.get_texture_id(texture_id);
2323 self.fill_rect_with_mode(rect, [1.0, 1.0, 1.0, 1.0], 12, tid);
2324 }
2325
2326 fn draw_mesh(&mut self, mesh: &Mesh, color: [f32; 4], transform: glam::Mat4) {
2327 let base_idx = self.vertices.len() as u32;
2328 let screen = [self.config.width as f32, self.config.height as f32];
2329
2330 for i in 0..mesh.vertices.len() {
2331 let pos = transform.transform_point3(glam::Vec3::from(mesh.vertices[i]));
2332 let norm = transform.transform_vector3(glam::Vec3::from(mesh.normals[i]));
2333
2334 self.vertices.push(Vertex {
2335 position: pos.to_array(),
2336 normal: norm.to_array(),
2337 uv: [0.0, 0.0],
2338 color,
2339 mode: 13, radius: 0.0,
2341 slice: [0.0, 0.0, 0.0, 1.0],
2342 logical: [0.0, 0.0],
2343 size: [0.0, 0.0],
2344 screen,
2345 clip: [-10000.0, -10000.0, 20000.0, 20000.0],
2346 });
2347 }
2348
2349 for idx in &mesh.indices {
2350 self.indices.push(base_idx + idx);
2351 }
2352
2353 if self.draw_calls.is_empty() || self.current_texture_id.is_some() {
2354 self.current_texture_id = None;
2355 self.draw_calls.push(DrawCall {
2356 texture_id: None,
2357 scissor_rect: self.clip_stack.last().copied(),
2358 index_start: (self.indices.len() as u32) - (mesh.indices.len() as u32),
2359 index_count: mesh.indices.len() as u32,
2360 is_glass: false,
2361 is_ui: false,
2362 });
2363 } else {
2364 self.draw_calls.last_mut().unwrap().index_count += mesh.indices.len() as u32;
2365 }
2366 }
2367
2368 fn register_shared_element(&mut self, id: &str, rect: Rect) {
2369 self.shared_elements.insert(id.to_string(), rect);
2370 }
2371}
2372
2373impl Drop for SurtrRenderer {
2374 fn drop(&mut self) {
2375 let _ = self.device.poll(wgpu::PollType::Wait { submission_index: None, timeout: None });
2377 }
2378}
2379
2380impl cvkg_core::FrameRenderer<wgpu::CommandEncoder> for SurtrRenderer {
2381 fn begin_frame(&mut self) -> wgpu::CommandEncoder {
2382 self.begin_frame()
2383 }
2384
2385 fn end_frame(&mut self, encoder: wgpu::CommandEncoder) {
2386 self.end_frame(encoder)
2387 }
2388}
2389
2390impl SurtrRenderer {
2391 fn apply_opacity(&self, mut color: [f32; 4]) -> [f32; 4] {
2393 if let Some(&alpha) = self.opacity_stack.last() {
2394 color[3] *= alpha;
2395 }
2396 color
2397 }
2398}