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