1use cvkg_core::{Rect, ColorTheme, SceneUniforms, Mesh};
35use std::sync::Arc;
36
37pub use accesskit::{
40 ActionHandler, ActionRequest, ActivationHandler, DeactivationHandler,
41 Node, NodeId, Role, Tree, TreeId, TreeUpdate,
42};
43pub use accesskit_winit::Adapter as ShieldWallAdapter;
44
45
46#[repr(C)]
47#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
48pub struct Vertex {
49 pub position: [f32; 3],
50 pub normal: [f32; 3],
51 pub uv: [f32; 2],
52 pub color: [f32; 4],
53 pub mode: u32,
54 pub radius: f32,
55 pub slice: [f32; 4],
56 pub logical: [f32; 2],
57 pub size: [f32; 2],
58 pub screen: [f32; 2],
59 pub clip: [f32; 4], }
61
62#[derive(Debug, Clone)]
65struct DrawCall {
66 pub texture_id: Option<u32>,
67 pub scissor_rect: Option<Rect>,
68 pub index_start: u32,
69 pub index_count: u32,
70 pub is_glass: bool,
71 pub is_ui: bool,
72}
73
74impl Vertex {
75 const ATTRIBUTES: [wgpu::VertexAttribute; 11] = wgpu::vertex_attr_array![
76 0 => Float32x3, 1 => Float32x3, 2 => Float32x2, 3 => Float32x4, 4 => Uint32, 5 => Float32, 6 => Float32x4, 7 => Float32x2, 8 => Float32x2, 9 => Float32x2, 10 => Float32x4 ];
88
89 fn desc() -> wgpu::VertexBufferLayout<'static> {
90 wgpu::VertexBufferLayout {
91 array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
92 step_mode: wgpu::VertexStepMode::Vertex,
93 attributes: &Self::ATTRIBUTES,
94 }
95 }
96}
97
98pub struct SurtrRenderer {
100 device: Arc<wgpu::Device>,
101 queue: Arc<wgpu::Queue>,
102 surface: wgpu::Surface<'static>,
103 config: wgpu::SurfaceConfiguration,
104
105
106 #[allow(dead_code)]
108 font_system: cosmic_text::FontSystem,
109 #[allow(dead_code)]
110 swash_cache: cosmic_text::SwashCache,
111 text_atlas_tex: wgpu::Texture,
112 #[allow(dead_code)]
113 text_atlas_view: wgpu::TextureView,
114 #[allow(dead_code)]
115 text_sampler: wgpu::Sampler,
116 text_cache: std::collections::HashMap<cosmic_text::CacheKey, (Rect, f32, f32)>,
117 text_atlas_pos: (u32, u32),
118
119 dummy_bind_group: wgpu::BindGroup,
121 texture_bind_group_layout: wgpu::BindGroupLayout,
122 texture_bind_groups: Vec<wgpu::BindGroup>,
123 texture_registry: std::collections::HashMap<String, u32>,
124 shared_elements: std::collections::HashMap<String, cvkg_core::Rect>,
125
126 vertex_buffer: wgpu::Buffer,
128 index_buffer: wgpu::Buffer,
129 vertices: Vec<Vertex>,
130 indices: Vec<u32>,
131 draw_calls: Vec<DrawCall>,
132 current_texture_id: Option<u32>,
133
134 opacity_stack: Vec<f32>,
136 clip_stack: Vec<Rect>,
138 slice_stack: Vec<(f32, f32)>,
140
141 theme_buffer: wgpu::Buffer,
143 scene_buffer: wgpu::Buffer,
144 berserker_bind_group: wgpu::BindGroup,
145 #[allow(dead_code)]
146 berserker_bind_group_layout: wgpu::BindGroupLayout,
147 start_time: std::time::Instant,
148 current_theme: ColorTheme,
149 current_scene: SceneUniforms,
150
151 pipeline: wgpu::RenderPipeline,
153 background_pipeline: wgpu::RenderPipeline,
154 bloom_extract_pipeline: wgpu::RenderPipeline,
155 blur_h_pipeline: wgpu::RenderPipeline,
156 blur_v_pipeline: wgpu::RenderPipeline,
157 composite_pipeline: wgpu::RenderPipeline,
158
159 blur_texture_a: wgpu::TextureView,
161 blur_texture_b: wgpu::TextureView,
162 blur_bind_group_a: wgpu::BindGroup,
163 blur_bind_group_b: wgpu::BindGroup,
164 scene_texture: wgpu::TextureView,
165 scene_bind_group: wgpu::BindGroup,
166 scene_texture_bind_group: wgpu::BindGroup,
167}
168
169const MAX_VERTICES: usize = 100_000;
170const MAX_INDICES: usize = 150_000;
171
172impl SurtrRenderer {
173 pub async fn forge(window: Arc<winit::window::Window>) -> Self {
175 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
176 backends: wgpu::Backends::all(),
177 flags: wgpu::InstanceFlags::default(),
178 backend_options: wgpu::BackendOptions::default(),
179 display: None,
180 memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
181 });
182
183 let surface = instance.create_surface(window.clone()).expect("Failed to create surface");
184
185 let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions {
186 power_preference: wgpu::PowerPreference::HighPerformance,
187 compatible_surface: Some(&surface),
188 force_fallback_adapter: false,
189 }).await.expect("Failed to find a suitable GPU for Surtr");
190
191 let (device, queue) = adapter.request_device(
192 &wgpu::DeviceDescriptor {
193 label: Some("Surtr Forge"),
194 required_features: wgpu::Features::empty(),
195 required_limits: wgpu::Limits::default(),
196 memory_hints: wgpu::MemoryHints::default(),
197 experimental_features: wgpu::ExperimentalFeatures::disabled(),
198 trace: wgpu::Trace::Off,
199 },
200 ).await.expect("Failed to create Surtr device");
201
202 let device = Arc::new(device);
203 let queue = Arc::new(queue);
204
205 let size = window.inner_size();
206 let surface_caps = surface.get_capabilities(&adapter);
207 let surface_format = surface_caps.formats.iter()
208 .find(|f| f.is_srgb())
209 .copied()
210 .unwrap_or(surface_caps.formats[0]);
211
212 let config = wgpu::SurfaceConfiguration {
213 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
214 format: surface_format,
215 width: size.width,
216 height: size.height,
217 present_mode: wgpu::PresentMode::Fifo,
218 alpha_mode: surface_caps.alpha_modes[0],
219 view_formats: vec![],
220 desired_maximum_frame_latency: 2,
221 };
222 surface.configure(&device, &config);
223
224 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
226 label: Some("Muspelheim Main Shader"),
227 source: wgpu::ShaderSource::Wgsl(include_str!("shaders.wgsl").into()),
228 });
229
230 let texture_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
232 entries: &[
233 wgpu::BindGroupLayoutEntry {
234 binding: 0,
235 visibility: wgpu::ShaderStages::FRAGMENT,
236 ty: wgpu::BindingType::Texture {
237 multisampled: false,
238 view_dimension: wgpu::TextureViewDimension::D2,
239 sample_type: wgpu::TextureSampleType::Float { filterable: true },
240 },
241 count: None,
242 },
243 wgpu::BindGroupLayoutEntry {
244 binding: 1,
245 visibility: wgpu::ShaderStages::FRAGMENT,
246 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
247 count: None,
248 },
249 ],
250 label: Some("Niflheim Texture Bind Group Layout"),
251 });
252
253 let env_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
256 entries: &[
257 wgpu::BindGroupLayoutEntry {
258 binding: 0,
259 visibility: wgpu::ShaderStages::FRAGMENT,
260 ty: wgpu::BindingType::Texture {
261 multisampled: false,
262 view_dimension: wgpu::TextureViewDimension::D2,
263 sample_type: wgpu::TextureSampleType::Float { filterable: true },
264 },
265 count: None,
266 },
267 wgpu::BindGroupLayoutEntry {
268 binding: 1,
269 visibility: wgpu::ShaderStages::FRAGMENT,
270 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
271 count: None,
272 },
273 ],
274 label: Some("Surtr Environment Bind Group Layout"),
275 });
276
277 let berserker_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
278 entries: &[
279 wgpu::BindGroupLayoutEntry {
280 binding: 0,
281 visibility: wgpu::ShaderStages::FRAGMENT,
282 ty: wgpu::BindingType::Buffer {
283 ty: wgpu::BufferBindingType::Uniform,
284 has_dynamic_offset: false,
285 min_binding_size: None,
286 },
287 count: None,
288 },
289 wgpu::BindGroupLayoutEntry {
290 binding: 1,
291 visibility: wgpu::ShaderStages::FRAGMENT | wgpu::ShaderStages::VERTEX,
292 ty: wgpu::BindingType::Buffer {
293 ty: wgpu::BufferBindingType::Uniform,
294 has_dynamic_offset: false,
295 min_binding_size: None,
296 },
297 count: None,
298 },
299 ],
300 label: Some("Surtr Berserker Bind Group Layout"),
301 });
302
303 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
305 label: Some("Surtr Main Pipeline Layout"),
306 bind_group_layouts: &[Some(&texture_bind_group_layout), Some(&env_bind_group_layout), Some(&berserker_bind_group_layout)],
307 immediate_size: 0,
308 });
309
310 let post_process_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
312 label: Some("Muspelheim Post Process Layout"),
313 bind_group_layouts: &[
314 Some(&texture_bind_group_layout),
315 Some(&texture_bind_group_layout),
316 Some(&berserker_bind_group_layout),
317 ],
318 immediate_size: 0,
319 });
320
321 let composite_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
323 label: Some("Muspelheim Composite Layout"),
324 bind_group_layouts: &[
325 Some(&texture_bind_group_layout),
326 Some(&texture_bind_group_layout),
327 Some(&berserker_bind_group_layout),
328 ],
329 immediate_size: 0,
330 });
331
332 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
333 label: Some("Surtr Main Pipeline"),
334 layout: Some(&pipeline_layout),
335 vertex: wgpu::VertexState {
336 module: &shader,
337 entry_point: Some("vs_main"),
338 buffers: &[Vertex::desc()],
339 compilation_options: wgpu::PipelineCompilationOptions::default(),
340 },
341 fragment: Some(wgpu::FragmentState {
342 module: &shader,
343 entry_point: Some("fs_main"),
344 targets: &[Some(wgpu::ColorTargetState {
345 format: config.format,
346 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
347 write_mask: wgpu::ColorWrites::ALL,
348 })],
349 compilation_options: wgpu::PipelineCompilationOptions::default(),
350 }),
351 primitive: wgpu::PrimitiveState::default(),
352 depth_stencil: None,
353 multisample: wgpu::MultisampleState::default(),
354 multiview_mask: None,
355 cache: None,
356 });
357
358 let background_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
359 label: Some("Surtr Background Pipeline"),
360 layout: Some(&pipeline_layout),
361 vertex: wgpu::VertexState {
362 module: &shader,
363 entry_point: Some("vs_fullscreen"),
364 buffers: &[],
365 compilation_options: wgpu::PipelineCompilationOptions::default(),
366 },
367 fragment: Some(wgpu::FragmentState {
368 module: &shader,
369 entry_point: Some("fs_background"),
370 targets: &[Some(wgpu::ColorTargetState {
371 format: config.format,
372 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
373 write_mask: wgpu::ColorWrites::ALL,
374 })],
375 compilation_options: wgpu::PipelineCompilationOptions::default(),
376 }),
377 primitive: wgpu::PrimitiveState::default(),
378 depth_stencil: None,
379 multisample: wgpu::MultisampleState::default(),
380 multiview_mask: None,
381 cache: None,
382 });
383
384 let bloom_extract_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
386 label: Some("Muspelheim Bloom Extract"),
387 layout: Some(&post_process_layout),
388 vertex: wgpu::VertexState {
389 module: &shader,
390 entry_point: Some("vs_fullscreen"),
391 buffers: &[],
392 compilation_options: wgpu::PipelineCompilationOptions::default(),
393 },
394 fragment: Some(wgpu::FragmentState {
395 module: &shader,
396 entry_point: Some("fs_bloom_extract"),
397 targets: &[Some(wgpu::ColorTargetState {
398 format: config.format,
399 blend: None,
400 write_mask: wgpu::ColorWrites::ALL,
401 })],
402 compilation_options: wgpu::PipelineCompilationOptions::default(),
403 }),
404 primitive: wgpu::PrimitiveState::default(),
405 depth_stencil: None,
406 multisample: wgpu::MultisampleState::default(),
407 multiview_mask: None,
408 cache: None,
409 });
410
411 let blur_h_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
413 label: Some("Muspelheim Horizontal Blur"),
414 layout: Some(&post_process_layout),
415 vertex: wgpu::VertexState {
416 module: &shader,
417 entry_point: Some("vs_fullscreen"),
418 buffers: &[],
419 compilation_options: wgpu::PipelineCompilationOptions::default(),
420 },
421 fragment: Some(wgpu::FragmentState {
422 module: &shader,
423 entry_point: Some("fs_blur_h"),
424 targets: &[Some(wgpu::ColorTargetState {
425 format: config.format,
426 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
427 write_mask: wgpu::ColorWrites::ALL,
428 })],
429 compilation_options: wgpu::PipelineCompilationOptions::default(),
430 }),
431 primitive: wgpu::PrimitiveState::default(),
432 depth_stencil: None,
433 multisample: wgpu::MultisampleState::default(),
434 multiview_mask: None,
435 cache: None,
436 });
437
438 let blur_v_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
439 label: Some("Muspelheim Vertical Blur"),
440 layout: Some(&post_process_layout),
441 vertex: wgpu::VertexState {
442 module: &shader,
443 entry_point: Some("vs_fullscreen"),
444 buffers: &[],
445 compilation_options: wgpu::PipelineCompilationOptions::default(),
446 },
447 fragment: Some(wgpu::FragmentState {
448 module: &shader,
449 entry_point: Some("fs_blur_v"),
450 targets: &[Some(wgpu::ColorTargetState {
451 format: config.format,
452 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
453 write_mask: wgpu::ColorWrites::ALL,
454 })],
455 compilation_options: wgpu::PipelineCompilationOptions::default(),
456 }),
457 primitive: wgpu::PrimitiveState::default(),
458 depth_stencil: None,
459 multisample: wgpu::MultisampleState::default(),
460 multiview_mask: None,
461 cache: None,
462 });
463
464 let composite_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
466 label: Some("Muspelheim Composite"),
467 layout: Some(&composite_layout),
468 vertex: wgpu::VertexState {
469 module: &shader,
470 entry_point: Some("vs_fullscreen"),
471 buffers: &[],
472 compilation_options: wgpu::PipelineCompilationOptions::default(),
473 },
474 fragment: Some(wgpu::FragmentState {
475 module: &shader,
476 entry_point: Some("fs_composite"),
477 targets: &[Some(wgpu::ColorTargetState {
478 format: config.format,
479 blend: Some(wgpu::BlendState {
481 color: wgpu::BlendComponent {
482 src_factor: wgpu::BlendFactor::One,
483 dst_factor: wgpu::BlendFactor::One,
484 operation: wgpu::BlendOperation::Add,
485 },
486 alpha: wgpu::BlendComponent {
487 src_factor: wgpu::BlendFactor::One,
488 dst_factor: wgpu::BlendFactor::One,
489 operation: wgpu::BlendOperation::Add,
490 },
491 }),
492 write_mask: wgpu::ColorWrites::ALL,
493 })],
494 compilation_options: wgpu::PipelineCompilationOptions::default(),
495 }),
496 primitive: wgpu::PrimitiveState::default(),
497 depth_stencil: None,
498 multisample: wgpu::MultisampleState::default(),
499 multiview_mask: None,
500 cache: None,
501 });
502
503 let blur_tex_desc = wgpu::TextureDescriptor {
505 label: Some("Muspelheim Intermediate"),
506 size: wgpu::Extent3d {
507 width: config.width,
508 height: config.height,
509 depth_or_array_layers: 1,
510 },
511 mip_level_count: 1,
512 sample_count: 1,
513 dimension: wgpu::TextureDimension::D2,
514 format: config.format,
515 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
516 view_formats: &[],
517 };
518 let blur_texture_a_obj = device.create_texture(&blur_tex_desc);
519 let blur_texture_b_obj = device.create_texture(&blur_tex_desc);
520 let blur_texture_a = blur_texture_a_obj.create_view(&wgpu::TextureViewDescriptor::default());
521 let blur_texture_b = blur_texture_b_obj.create_view(&wgpu::TextureViewDescriptor::default());
522
523 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
524 address_mode_u: wgpu::AddressMode::ClampToEdge,
525 address_mode_v: wgpu::AddressMode::ClampToEdge,
526 mag_filter: wgpu::FilterMode::Linear,
527 ..Default::default()
528 });
529
530 let blur_bind_group_a = device.create_bind_group(&wgpu::BindGroupDescriptor {
531 layout: &texture_bind_group_layout,
532 entries: &[
533 wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&blur_texture_a) },
534 wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&sampler) },
535 ],
536 label: Some("Blur Bind Group A"),
537 });
538
539 let blur_bind_group_b = device.create_bind_group(&wgpu::BindGroupDescriptor {
540 layout: &texture_bind_group_layout,
541 entries: &[
542 wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&blur_texture_b) },
543 wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&sampler) },
544 ],
545 label: Some("Blur Bind Group B"),
546 });
547
548 let scene_texture_obj = device.create_texture(&blur_tex_desc);
550 let scene_texture = scene_texture_obj.create_view(&wgpu::TextureViewDescriptor::default());
551 let scene_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
552 layout: &env_bind_group_layout,
553 entries: &[
554 wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&scene_texture) },
555 wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&sampler) },
556 ],
557 label: Some("Scene Capture Bind Group"),
558 });
559
560 let text_atlas_tex = device.create_texture(&wgpu::TextureDescriptor {
562 label: Some("Surtr Text Atlas"),
563 size: wgpu::Extent3d { width: 1024, height: 1024, depth_or_array_layers: 1 },
564 mip_level_count: 1,
565 sample_count: 1,
566 dimension: wgpu::TextureDimension::D2,
567 format: wgpu::TextureFormat::R8Unorm,
568 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
569 view_formats: &[],
570 });
571 let text_atlas = text_atlas_tex.create_view(&wgpu::TextureViewDescriptor::default());
572 let text_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
573 address_mode_u: wgpu::AddressMode::ClampToEdge,
574 address_mode_v: wgpu::AddressMode::ClampToEdge,
575 mag_filter: wgpu::FilterMode::Nearest,
576 min_filter: wgpu::FilterMode::Nearest,
577 ..Default::default()
578 });
579
580 queue.write_texture(
582 wgpu::TexelCopyTextureInfo {
583 texture: &text_atlas_tex,
584 mip_level: 0,
585 origin: wgpu::Origin3d::ZERO,
586 aspect: wgpu::TextureAspect::All,
587 },
588 &vec![0u8; 1024 * 1024],
589 wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(1024), rows_per_image: Some(1024) },
590 wgpu::Extent3d { width: 1024, height: 1024, depth_or_array_layers: 1 },
591 );
592
593 let dummy_size = wgpu::Extent3d {
595 width: 1,
596 height: 1,
597 depth_or_array_layers: 1,
598 };
599 let dummy_texture = device.create_texture(&wgpu::TextureDescriptor {
600 label: Some("Niflheim Dummy Texture"),
601 size: dummy_size,
602 mip_level_count: 1,
603 sample_count: 1,
604 dimension: wgpu::TextureDimension::D2,
605 format: wgpu::TextureFormat::Rgba8UnormSrgb,
606 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
607 view_formats: &[],
608 });
609 queue.write_texture(
610 wgpu::TexelCopyTextureInfo {
611 texture: &dummy_texture,
612 mip_level: 0,
613 origin: wgpu::Origin3d::ZERO,
614 aspect: wgpu::TextureAspect::All,
615 },
616 &[255, 255, 255, 255],
617 wgpu::TexelCopyBufferLayout {
618 offset: 0,
619 bytes_per_row: Some(4),
620 rows_per_image: Some(1),
621 },
622 dummy_size,
623 );
624
625 let dummy_view = dummy_texture.create_view(&wgpu::TextureViewDescriptor::default());
626 let dummy_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
627 address_mode_u: wgpu::AddressMode::ClampToEdge,
628 address_mode_v: wgpu::AddressMode::ClampToEdge,
629 address_mode_w: wgpu::AddressMode::ClampToEdge,
630 mag_filter: wgpu::FilterMode::Linear,
631 min_filter: wgpu::FilterMode::Nearest,
632 mipmap_filter: wgpu::MipmapFilterMode::Nearest,
633 ..Default::default()
634 });
635
636 let dummy_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
637 layout: &texture_bind_group_layout,
638 entries: &[
639 wgpu::BindGroupEntry {
640 binding: 0,
641 resource: wgpu::BindingResource::TextureView(&dummy_view),
642 },
643 wgpu::BindGroupEntry {
644 binding: 1,
645 resource: wgpu::BindingResource::Sampler(&dummy_sampler),
646 },
647 ],
648 label: Some("Niflheim Dummy Bind Group"),
649 });
650
651 let mut texture_registry = std::collections::HashMap::new();
652 let mut texture_bind_groups = Vec::new();
653
654 let text_atlas_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
655 layout: &texture_bind_group_layout,
656 entries: &[
657 wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&text_atlas) },
658 wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&text_sampler) },
659 ],
660 label: Some("Text Atlas Bind Group"),
661 });
662 texture_registry.insert("__text_atlas".to_string(), texture_bind_groups.len() as u32);
663 texture_bind_groups.push(text_atlas_bg);
664
665 let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
667 label: Some("Surtr Vertex Anvil"),
668 size: (MAX_VERTICES * std::mem::size_of::<Vertex>()) as u64,
669 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
670 mapped_at_creation: false,
671 });
672
673 let index_buffer = device.create_buffer(&wgpu::BufferDescriptor {
674 label: Some("Surtr Index Anvil"),
675 size: (MAX_INDICES * std::mem::size_of::<u16>()) as u64,
676 usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
677 mapped_at_creation: false,
678 });
679
680
681 let current_theme = ColorTheme::default();
687 use wgpu::util::DeviceExt;
688 let theme_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
689 label: Some("Surtr Theme Buffer"),
690 contents: bytemuck::bytes_of(¤t_theme),
691 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
692 });
693
694 let current_scene = SceneUniforms::new(config.width as f32, config.height as f32);
695 let scene_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
696 label: Some("Surtr Scene Buffer"),
697 contents: bytemuck::bytes_of(¤t_scene),
698 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
699 });
700
701 let berserker_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
702 layout: &berserker_bind_group_layout,
703 entries: &[
704 wgpu::BindGroupEntry {
705 binding: 0,
706 resource: theme_buffer.as_entire_binding(),
707 },
708 wgpu::BindGroupEntry {
709 binding: 1,
710 resource: scene_buffer.as_entire_binding(),
711 },
712 ],
713 label: Some("Surtr Berserker Bind Group"),
714 });
715
716 let scene_texture_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
717 layout: &texture_bind_group_layout,
718 entries: &[
719 wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&scene_texture) },
720 wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&sampler) },
721 ],
722 label: Some("Scene Texture Bind Group (Group 0)"),
723 });
724
725 Self {
726 device,
727 queue,
728 surface,
729 config,
730 pipeline,
731 bloom_extract_pipeline,
732 blur_h_pipeline,
733 blur_v_pipeline,
734 composite_pipeline,
735 blur_texture_a,
736 blur_texture_b,
737 blur_bind_group_a,
738 blur_bind_group_b,
739 scene_texture,
740 scene_bind_group,
741 scene_texture_bind_group,
742 font_system: cosmic_text::FontSystem::new(),
743 swash_cache: cosmic_text::SwashCache::new(),
744 text_atlas_tex,
745 text_atlas_view: text_atlas,
746 text_sampler,
747 text_cache: std::collections::HashMap::new(),
748 text_atlas_pos: (0, 0),
749 dummy_bind_group,
750 texture_bind_group_layout,
751 texture_bind_groups,
752 texture_registry,
753 shared_elements: std::collections::HashMap::new(),
754 vertex_buffer,
755 index_buffer,
756 vertices: Vec::with_capacity(MAX_VERTICES),
757 indices: Vec::with_capacity(MAX_INDICES),
758 draw_calls: Vec::new(),
759 current_texture_id: None,
760 opacity_stack: vec![1.0],
761 clip_stack: Vec::new(),
762 slice_stack: Vec::new(),
763 theme_buffer,
764 scene_buffer,
765 berserker_bind_group,
766 berserker_bind_group_layout,
767 start_time: std::time::Instant::now(),
768 current_theme,
769 current_scene,
770 background_pipeline,
771 }
772 }
773
774 pub fn resize(&mut self, width: u32, height: u32) {
775 if width > 0 && height > 0 {
776 self.config.width = width;
777 self.config.height = height;
778 self.surface.configure(&self.device, &self.config);
779
780 let texture_desc = wgpu::TextureDescriptor {
782 label: Some("Surtr Scene Texture"),
783 size: wgpu::Extent3d { width, height, depth_or_array_layers: 1 },
784 mip_level_count: 1,
785 sample_count: 1,
786 dimension: wgpu::TextureDimension::D2,
787 format: self.config.format,
788 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
789 view_formats: &[],
790 };
791
792 let scene_tex = self.device.create_texture(&texture_desc);
793 self.scene_texture = scene_tex.create_view(&wgpu::TextureViewDescriptor::default());
794
795 let blur_tex_a = self.device.create_texture(&texture_desc);
796 self.blur_texture_a = blur_tex_a.create_view(&wgpu::TextureViewDescriptor::default());
797
798 let blur_tex_b = self.device.create_texture(&texture_desc);
799 self.blur_texture_b = blur_tex_b.create_view(&wgpu::TextureViewDescriptor::default());
800
801 let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
803 address_mode_u: wgpu::AddressMode::ClampToEdge,
804 address_mode_v: wgpu::AddressMode::ClampToEdge,
805 mag_filter: wgpu::FilterMode::Linear,
806 min_filter: wgpu::FilterMode::Linear,
807 ..Default::default()
808 });
809
810 let env_layout = self.device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
813 entries: &[
814 wgpu::BindGroupLayoutEntry {
815 binding: 0,
816 visibility: wgpu::ShaderStages::FRAGMENT,
817 ty: wgpu::BindingType::Texture {
818 sample_type: wgpu::TextureSampleType::Float { filterable: true },
819 view_dimension: wgpu::TextureViewDimension::D2,
820 multisampled: false,
821 },
822 count: None,
823 },
824 wgpu::BindGroupLayoutEntry {
825 binding: 1,
826 visibility: wgpu::ShaderStages::FRAGMENT,
827 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
828 count: None,
829 },
830 ],
831 label: Some("Surtr Env Layout Resize"),
832 });
833
834 self.scene_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
835 layout: &env_layout,
836 entries: &[
837 wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&self.scene_texture) },
838 wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&sampler) },
839 ],
840 label: Some("Surtr Scene Bind Group Resize"),
841 });
842
843 self.blur_bind_group_a = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
844 layout: &self.texture_bind_group_layout,
845 entries: &[
846 wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&self.blur_texture_a) },
847 wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&sampler) },
848 ],
849 label: Some("Surtr Blur Bind Group A Resize"),
850 });
851
852 self.blur_bind_group_b = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
853 layout: &self.texture_bind_group_layout,
854 entries: &[
855 wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&self.blur_texture_b) },
856 wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&sampler) },
857 ],
858 label: Some("Surtr Blur Bind Group B Resize"),
859 });
860
861 self.scene_texture_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
862 layout: &self.texture_bind_group_layout,
863 entries: &[
864 wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&self.scene_texture) },
865 wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&sampler) },
866 ],
867 label: Some("Scene Texture Bind Group Resize"),
868 });
869
870 self.current_scene.resolution = [width as f32, height as f32];
871 }
872 }
873
874 pub fn begin_frame(&mut self) -> wgpu::CommandEncoder {
876 self.vertices.clear();
877 self.indices.clear();
878 self.draw_calls.clear();
879 self.shared_elements.clear(); self.current_texture_id = None;
881
882 let time = self.start_time.elapsed().as_secs_f32();
883 let (width, height) = (self.config.width as f32, self.config.height as f32);
884 let dt = time - self.current_scene.time;
885 self.current_scene.time = time;
886 self.current_scene.delta_time = dt;
887 self.current_scene.resolution = [width, height];
888 self.current_scene.proj = glam::Mat4::orthographic_lh(0.0, width, height, 0.0, -100.0, 100.0);
889
890 self.queue.write_buffer(&self.scene_buffer, 0, bytemuck::bytes_of(&self.current_scene));
891
892 self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
893 label: Some("Surtr's Flaming Sword"),
894 })
895 }
896
897 pub fn reset_time(&mut self) {
899 self.start_time = std::time::Instant::now();
900 }
901
902 fn shatter_internal(&mut self, rect: Rect, pieces: u32, force: f32, color: [f32; 4], mode: u32) {
903 let count = (pieces as f32).sqrt().ceil() as u32;
905 let dw = rect.width / count as f32;
906 let dh = rect.height / count as f32;
907
908 let c = self.apply_opacity(color);
909
910 for y in 0..count {
911 for x in 0..count {
912 let shard_rect = Rect {
913 x: rect.x + x as f32 * dw,
914 y: rect.y + y as f32 * dh,
915 width: dw,
916 height: dh,
917 };
918
919 let uv = Rect {
920 x: x as f32 / count as f32,
921 y: y as f32 / count as f32,
922 width: 1.0 / count as f32,
923 height: 1.0 / count as f32,
924 };
925
926 self.fill_rect_with_full_params(
927 shard_rect,
928 c,
929 mode,
930 None,
931 force,
932 uv
933 );
934 }
935 }
936 }
937
938 fn recursive_bolt(&mut self, from: [f32; 2], to: [f32; 2], depth: u32, color: [f32; 4]) {
939 if depth == 0 {
940 self.draw_lightning_segment(from, to, color);
941 return;
942 }
943
944 let mid_x = (from[0] + to[0]) * 0.5;
945 let mid_y = (from[1] + to[1]) * 0.5;
946
947 let dx = to[0] - from[0];
948 let dy = to[1] - from[1];
949 let len = (dx * dx + dy * dy).sqrt();
950
951 let offset_scale = len * 0.15;
953 let seed = (from[0] * 12.9898 + from[1] * 78.233 + (depth as f32) * 37.11).sin().fract();
954 let offset_x = -dy / len * (seed - 0.5) * offset_scale;
955 let offset_y = dx / len * (seed - 0.5) * offset_scale;
956
957 let mid = [mid_x + offset_x, mid_y + offset_y];
958
959 self.recursive_bolt(from, mid, depth - 1, color);
960 self.recursive_bolt(mid, to, depth - 1, color);
961
962 if depth > 2 && seed > 0.8 {
964 let branch_to = [
965 mid[0] + offset_x * 2.0 + (seed * 100.0).sin() * 50.0,
966 mid[1] + offset_y * 2.0 + (seed * 100.0).cos() * 50.0
967 ];
968 self.recursive_bolt(mid, branch_to, depth - 2, color);
969 }
970 }
971
972 fn draw_lightning_segment(&mut self, from: [f32; 2], to: [f32; 2], color: [f32; 4]) {
973 let dx = to[0] - from[0];
974 let dy = to[1] - from[1];
975 let len = (dx * dx + dy * dy).sqrt();
976 if len < 0.001 { return; }
977
978 let glow_width = 32.0;
979 let core_width = 4.0;
980 let c = self.apply_opacity(color);
981
982 let gnx = -dy / len * glow_width * 0.5;
984 let gny = dx / len * glow_width * 0.5;
985 let gp1 = [from[0] + gnx, from[1] + gny];
986 let gp2 = [to[0] + gnx, to[1] + gny];
987 let gp3 = [to[0] - gnx, to[1] - gny];
988 let gp4 = [from[0] - gnx, from[1] - gny];
989 self.push_oriented_quad([gp1, gp2, gp3, gp4], c, 9, Rect { x: 0.0, y: 0.0, width: 1.0, height: 1.0 });
990
991 let cnx = -dy / len * core_width * 0.5;
993 let cny = dx / len * core_width * 0.5;
994 let cp1 = [from[0] + cnx, from[1] + cny];
995 let cp2 = [to[0] + cnx, to[1] + cny];
996 let cp3 = [to[0] - cnx, to[1] - cny];
997 let cp4 = [from[0] - cnx, from[1] - cny];
998 self.push_oriented_quad([cp1, cp2, cp3, cp4], [1.0, 1.0, 1.0, c[3]], 0, Rect { x: 0.0, y: 0.0, width: 1.0, height: 1.0 });
999 }
1000
1001 fn push_oriented_quad(&mut self, points: [[f32; 2]; 4], color: [f32; 4], mode: u32, uv_rect: Rect) {
1002 let scissor = self.clip_stack.last().copied();
1003 let texture_id = None; if self.draw_calls.is_empty() || self.current_texture_id != texture_id || self.draw_calls.last().unwrap().scissor_rect != scissor {
1006 self.current_texture_id = texture_id;
1007 self.draw_calls.push(DrawCall {
1008 texture_id,
1009 scissor_rect: scissor,
1010 index_start: self.indices.len() as u32,
1011 index_count: 0,
1012 is_glass: mode == 7,
1013 is_ui: mode == 6,
1014 });
1015 }
1016
1017 let uvs = [
1018 [uv_rect.x, uv_rect.y],
1019 [uv_rect.x + uv_rect.width, uv_rect.y],
1020 [uv_rect.x + uv_rect.width, uv_rect.y + uv_rect.height],
1021 [uv_rect.x, uv_rect.y + uv_rect.height],
1022 ];
1023
1024 let screen = [self.config.width as f32, self.config.height as f32];
1025 let rect = Rect { x: points[0][0], y: points[0][1], width: 1.0, height: 1.0 };
1026
1027 for i in 0..4 {
1028 let px = points[i][0];
1029 let py = points[i][1];
1030
1031 self.vertices.push(Vertex {
1032 position: [px, py, 0.0],
1033 normal: [0.0, 0.0, 1.0],
1034 uv: uvs[i],
1035 color,
1036 mode,
1037 radius: 0.0,
1038 slice: [0.0, 0.0, 0.0, 1.0],
1039 logical: [px - rect.x, py - rect.y],
1040 size: [rect.width, rect.height],
1041 screen,
1042 clip: [-10000.0, -10000.0, 20000.0, 20000.0],
1043 });
1044 }
1045
1046 if let Some(call) = self.draw_calls.last_mut() {
1047 call.index_count += 6;
1048 }
1049 }
1050 fn get_texture_id(&self, name: &str) -> Option<u32> {
1051 self.texture_registry.get(name).copied()
1052 }
1053
1054 fn fill_rect_with_mode(&mut self, rect: Rect, color: [f32; 4], mode: u32, texture_id: Option<u32>) {
1055 self.fill_rect_with_full_params(rect, color, mode, texture_id, 0.0, Rect { x: 0.0, y: 0.0, width: 1.0, height: 1.0 });
1056 }
1057
1058 fn fill_rect_with_full_params(&mut self, rect: Rect, color: [f32; 4], mode: u32, texture_id: Option<u32>, radius: f32, uv_rect: Rect) {
1059 let slice = self.slice_stack.last().copied().map(|(a, o)| [a, o, 1.0, 1.0]).unwrap_or([0.0, 0.0, 0.0, 1.0]);
1060 self.fill_rect_with_full_params_and_slice(rect, color, mode, texture_id, radius, uv_rect, slice);
1061 }
1062
1063 fn fill_rect_with_full_params_and_slice(&mut self, rect: Rect, color: [f32; 4], mode: u32, texture_id: Option<u32>, radius: f32, uv_rect: Rect, slice: [f32; 4]) {
1064 let scissor = self.clip_stack.last().copied();
1065
1066 let is_glass = mode == 7;
1067 let is_ui = mode == 6;
1068
1069 let last_call = self.draw_calls.last();
1071 let needs_new_call = self.draw_calls.is_empty()
1072 || self.current_texture_id != texture_id
1073 || last_call.unwrap().scissor_rect != scissor
1074 || last_call.unwrap().is_glass != is_glass
1075 || last_call.unwrap().is_ui != is_ui;
1076
1077 if needs_new_call {
1078 self.current_texture_id = texture_id;
1079 self.draw_calls.push(DrawCall {
1080 texture_id,
1081 scissor_rect: scissor,
1082 index_start: self.indices.len() as u32,
1083 index_count: 0,
1084 is_glass,
1085 is_ui,
1086 });
1087 }
1088
1089 let base_idx = self.vertices.len() as u32;
1090 let x1 = rect.x;
1091 let y1 = rect.y;
1092 let x2 = rect.x + rect.width;
1093 let y2 = rect.y + rect.height;
1094 let z = 0.0;
1095 let normal = [0.0, 0.0, 1.0];
1096 let screen = [self.config.width as f32, self.config.height as f32];
1097 let clip_rect = self.clip_stack.last().copied().unwrap_or(cvkg_core::Rect { x: -10000.0, y: -10000.0, width: 20000.0, height: 20000.0 });
1098 let clip = [clip_rect.x, clip_rect.y, clip_rect.width, clip_rect.height];
1099
1100 self.vertices.push(Vertex {
1101 position: [x1, y1, z], normal, uv: [uv_rect.x, uv_rect.y], color, mode, radius,
1102 slice, logical: [0.0, 0.0], size: [rect.width, rect.height], screen, clip
1103 });
1104 self.vertices.push(Vertex {
1105 position: [x2, y1, z], normal, uv: [uv_rect.x + uv_rect.width, uv_rect.y], color, mode, radius,
1106 slice, logical: [rect.width, 0.0], size: [rect.width, rect.height], screen, clip
1107 });
1108 self.vertices.push(Vertex {
1109 position: [x2, y2, z], normal, uv: [uv_rect.x + uv_rect.width, uv_rect.y + uv_rect.height], color, mode, radius,
1110 slice, logical: [rect.width, rect.height], size: [rect.width, rect.height], screen, clip
1111 });
1112 self.vertices.push(Vertex {
1113 position: [x1, y2, z], normal, uv: [uv_rect.x, uv_rect.y + uv_rect.height], color, mode, radius,
1114 slice, logical: [0.0, rect.height], size: [rect.width, rect.height], screen, clip
1115 });
1116
1117 self.indices.extend_from_slice(&[
1118 base_idx, base_idx + 1, base_idx + 2,
1119 base_idx, base_idx + 2, base_idx + 3,
1120 ]);
1121
1122 if let Some(call) = self.draw_calls.last_mut() {
1123 call.index_count += 6;
1124 }
1125 }
1126
1127 pub fn end_frame(&mut self, mut encoder: wgpu::CommandEncoder) {
1129 self.queue.write_buffer(&self.vertex_buffer, 0, bytemuck::cast_slice(&self.vertices));
1130 self.queue.write_buffer(&self.index_buffer, 0, bytemuck::cast_slice(&self.indices));
1131
1132 let frame = match self.surface.get_current_texture() {
1133 wgpu::CurrentSurfaceTexture::Success(t) => t,
1134 wgpu::CurrentSurfaceTexture::Suboptimal(t) => t,
1135 _ => {
1136 self.surface.configure(&self.device, &self.config);
1137 return;
1138 }
1139 };
1140 let screen = frame.texture.create_view(&wgpu::TextureViewDescriptor::default());
1141
1142 {
1144 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1145 label: Some("Surtr P1 Opaque Background"),
1146 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1147 view: &self.scene_texture,
1148 resolve_target: None,
1149 ops: wgpu::Operations {
1150 load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
1151 store: wgpu::StoreOp::Store,
1152 },
1153 depth_slice: None,
1154 })],
1155 depth_stencil_attachment: None,
1156 occlusion_query_set: None,
1157 timestamp_writes: None,
1158 multiview_mask: None,
1159 });
1160
1161 p.set_pipeline(&self.background_pipeline);
1163 p.set_bind_group(0, &self.dummy_bind_group, &[]);
1164 p.set_bind_group(1, &self.blur_bind_group_a, &[]); p.set_bind_group(2, &self.berserker_bind_group, &[]);
1166 p.draw(0..6, 0..1);
1167
1168 if !self.draw_calls.is_empty() {
1170 p.set_pipeline(&self.pipeline);
1171 p.set_vertex_buffer(0, self.vertex_buffer.slice(..));
1172 p.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
1173 p.set_bind_group(1, &self.dummy_bind_group, &[]);
1174 p.set_bind_group(2, &self.berserker_bind_group, &[]);
1175
1176 for call in self.draw_calls.iter().filter(|c| !c.is_glass && !c.is_ui) {
1177 let bg = if let Some(id) = call.texture_id {
1178 self.texture_bind_groups.get(id as usize).unwrap_or(&self.dummy_bind_group)
1179 } else {
1180 &self.dummy_bind_group
1181 };
1182 p.set_bind_group(0, bg, &[]);
1183 p.draw_indexed(call.index_start..call.index_start + call.index_count, 0, 0..1);
1184 }
1185 }
1186 }
1187
1188 {
1191 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1193 label: Some("Surtr Blur Extract"),
1194 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1195 view: &self.blur_texture_a,
1196 resolve_target: None,
1197 ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), store: wgpu::StoreOp::Store },
1198 depth_slice: None,
1199 })],
1200 ..Default::default()
1201 });
1202 p.set_pipeline(&self.bloom_extract_pipeline); p.set_bind_group(0, &self.scene_texture_bind_group, &[]);
1204 p.set_bind_group(1, &self.dummy_bind_group, &[]);
1205 p.set_bind_group(2, &self.berserker_bind_group, &[]);
1206 p.draw(0..6, 0..1);
1207 }
1208
1209 let blur_iters: u32 = 4;
1210 for _i in 0..blur_iters {
1211 {
1212 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1213 label: Some("Blur H"),
1214 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1215 view: &self.blur_texture_b,
1216 resolve_target: None,
1217 ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), store: wgpu::StoreOp::Store },
1218 depth_slice: None,
1219 })],
1220 ..Default::default()
1221 });
1222 p.set_pipeline(&self.blur_h_pipeline);
1223 p.set_bind_group(0, &self.blur_bind_group_a, &[]);
1224 p.set_bind_group(1, &self.dummy_bind_group, &[]);
1225 p.set_bind_group(2, &self.berserker_bind_group, &[]);
1226 p.draw(0..6, 0..1);
1227 }
1228 {
1229 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1230 label: Some("Blur V"),
1231 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1232 view: &self.blur_texture_a,
1233 resolve_target: None,
1234 ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), store: wgpu::StoreOp::Store },
1235 depth_slice: None,
1236 })],
1237 ..Default::default()
1238 });
1239 p.set_pipeline(&self.blur_v_pipeline);
1240 p.set_bind_group(0, &self.blur_bind_group_b, &[]);
1241 p.set_bind_group(1, &self.dummy_bind_group, &[]);
1242 p.set_bind_group(2, &self.berserker_bind_group, &[]);
1243 p.draw(0..6, 0..1);
1244 }
1245 }
1246
1247 {
1249 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1250 label: Some("Surtr P3 Liquid Glass"),
1251 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1252 view: &self.scene_texture, resolve_target: None,
1254 ops: wgpu::Operations {
1255 load: wgpu::LoadOp::Load,
1256 store: wgpu::StoreOp::Store,
1257 },
1258 depth_slice: None,
1259 })],
1260 ..Default::default()
1261 });
1262
1263 p.set_pipeline(&self.pipeline);
1264 p.set_vertex_buffer(0, self.vertex_buffer.slice(..));
1265 p.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
1266 p.set_bind_group(1, &self.blur_bind_group_a, &[]); p.set_bind_group(2, &self.berserker_bind_group, &[]);
1268
1269 for call in self.draw_calls.iter().filter(|c| c.is_glass) {
1270 let bg = if let Some(id) = call.texture_id {
1271 self.texture_bind_groups.get(id as usize).unwrap_or(&self.dummy_bind_group)
1272 } else {
1273 &self.dummy_bind_group
1274 };
1275 p.set_bind_group(0, bg, &[]);
1276 p.draw_indexed(call.index_start..call.index_start + call.index_count, 0, 0..1);
1277 }
1278 }
1279
1280 {
1282 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1283 label: Some("Surtr P4 UI Layer"),
1284 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1285 view: &self.scene_texture,
1286 resolve_target: None,
1287 ops: wgpu::Operations {
1288 load: wgpu::LoadOp::Load,
1289 store: wgpu::StoreOp::Store,
1290 },
1291 depth_slice: None,
1292 })],
1293 ..Default::default()
1294 });
1295
1296 p.set_pipeline(&self.pipeline);
1297 p.set_vertex_buffer(0, self.vertex_buffer.slice(..));
1298 p.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
1299 p.set_bind_group(1, &self.dummy_bind_group, &[]);
1300 p.set_bind_group(2, &self.berserker_bind_group, &[]);
1301
1302 for call in self.draw_calls.iter().filter(|c| c.is_ui) {
1303 let bg = if let Some(id) = call.texture_id {
1304 self.texture_bind_groups.get(id as usize).unwrap_or(&self.dummy_bind_group)
1305 } else {
1306 &self.dummy_bind_group
1307 };
1308 p.set_bind_group(0, bg, &[]);
1309 p.draw_indexed(call.index_start..call.index_start + call.index_count, 0, 0..1);
1310 }
1311 }
1312
1313 {
1315 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1316 label: Some("Surtr Bloom Extract"),
1317 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1318 view: &self.blur_texture_a,
1319 resolve_target: None,
1320 ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), store: wgpu::StoreOp::Store },
1321 depth_slice: None,
1322 })],
1323 ..Default::default()
1324 });
1325 p.set_pipeline(&self.bloom_extract_pipeline);
1326 p.set_bind_group(0, &self.scene_texture_bind_group, &[]);
1327 p.set_bind_group(1, &self.dummy_bind_group, &[]);
1328 p.set_bind_group(2, &self.berserker_bind_group, &[]);
1329 p.draw(0..6, 0..1);
1330 }
1331
1332 for _ in 0..2 {
1334 {
1335 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1336 label: Some("Bloom Blur H"),
1337 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1338 view: &self.blur_texture_b,
1339 resolve_target: None,
1340 ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), store: wgpu::StoreOp::Store },
1341 depth_slice: None,
1342 })],
1343 ..Default::default()
1344 });
1345 p.set_pipeline(&self.blur_h_pipeline);
1346 p.set_bind_group(0, &self.blur_bind_group_a, &[]);
1347 p.set_bind_group(1, &self.dummy_bind_group, &[]);
1348 p.set_bind_group(2, &self.berserker_bind_group, &[]);
1349 p.draw(0..6, 0..1);
1350 }
1351 {
1352 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1353 label: Some("Bloom Blur V"),
1354 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1355 view: &self.blur_texture_a,
1356 resolve_target: None,
1357 ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), store: wgpu::StoreOp::Store },
1358 depth_slice: None,
1359 })],
1360 ..Default::default()
1361 });
1362 p.set_pipeline(&self.blur_v_pipeline);
1363 p.set_bind_group(0, &self.blur_bind_group_b, &[]);
1364 p.set_bind_group(1, &self.dummy_bind_group, &[]);
1365 p.set_bind_group(2, &self.berserker_bind_group, &[]);
1366 p.draw(0..6, 0..1);
1367 }
1368 }
1369
1370 {
1372 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1373 label: Some("Surtr P7 Final Composite"),
1374 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1375 view: &screen,
1376 resolve_target: None,
1377 ops: wgpu::Operations {
1378 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1379 store: wgpu::StoreOp::Store,
1380 },
1381 depth_slice: None,
1382 })],
1383 ..Default::default()
1384 });
1385 p.set_pipeline(&self.composite_pipeline);
1386 p.set_bind_group(0, &self.scene_bind_group, &[]); p.set_bind_group(1, &self.blur_bind_group_a, &[]); p.set_bind_group(2, &self.berserker_bind_group, &[]);
1389 p.draw(0..6, 0..1);
1390 }
1391
1392 self.queue.submit(Some(encoder.finish()));
1393 frame.present();
1394 }
1395}
1396
1397impl cvkg_core::Renderer for SurtrRenderer {
1398 fn fill_rect(&mut self, rect: Rect, color: [f32; 4]) {
1399 self.fill_rect_with_mode(rect, self.apply_opacity(color), 0, None);
1400 }
1401
1402 fn fill_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4]) {
1403 self.fill_rect_with_full_params(rect, self.apply_opacity(color), 3, None, radius, Rect { x: 0.0, y: 0.0, width: 1.0, height: 1.0 });
1404 }
1405
1406 fn fill_ellipse(&mut self, rect: Rect, color: [f32; 4]) {
1407 self.fill_rect_with_full_params(rect, self.apply_opacity(color), 4, None, 0.0, Rect { x: 0.0, y: 0.0, width: 1.0, height: 1.0 });
1408 }
1409
1410 fn bifrost(&mut self, rect: Rect, blur: f32, _saturation: f32, opacity: f32) {
1411 let screen_uv = Rect {
1413 x: rect.x / self.config.width as f32,
1414 y: rect.y / self.config.height as f32,
1415 width: rect.width / self.config.width as f32,
1416 height: rect.height / self.config.height as f32,
1417 };
1418 self.fill_rect_with_full_params(rect, [1.0, 1.0, 1.0, opacity], 7, None, blur, screen_uv);
1421 }
1422
1423 fn stroke_rect(&mut self, rect: Rect, color: [f32; 4], stroke_width: f32) {
1424 let c = self.apply_opacity(color);
1425 let hw = stroke_width;
1426 self.fill_rect_with_mode(Rect { x: rect.x, y: rect.y, width: rect.width, height: hw }, c, 1, None);
1428 self.fill_rect_with_mode(Rect { x: rect.x, y: rect.y + rect.height - hw, width: rect.width, height: hw }, c, 1, None);
1429 self.fill_rect_with_mode(Rect { x: rect.x, y: rect.y, width: hw, height: rect.height }, c, 1, None);
1430 self.fill_rect_with_mode(Rect { x: rect.x + rect.width - hw, y: rect.y, width: hw, height: rect.height }, c, 1, None);
1431 }
1432
1433 fn stroke_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4], stroke_width: f32) {
1434 self.fill_rect_with_full_params(rect, self.apply_opacity(color), 17, None, radius, Rect { x: stroke_width, y: 0.0, width: 0.0, height: 0.0 });
1435 }
1436
1437 fn stroke_ellipse(&mut self, _rect: Rect, _color: [f32; 4], _stroke_width: f32) {
1438 }
1440
1441 fn draw_linear_gradient(&mut self, rect: Rect, start_color: [f32; 4], end_color: [f32; 4], angle: f32) {
1442 self.fill_rect_with_full_params_and_slice(rect, self.apply_opacity(start_color), 15, None, 0.0, Rect { x: angle, y: 0.0, width: 1.0, height: 1.0 }, end_color);
1443 }
1444
1445 fn draw_radial_gradient(&mut self, rect: Rect, inner_color: [f32; 4], outer_color: [f32; 4]) {
1446 self.fill_rect_with_full_params_and_slice(rect, self.apply_opacity(inner_color), 16, None, 0.0, Rect { x: 0.0, y: 0.0, width: 1.0, height: 1.0 }, outer_color);
1447 }
1448
1449 fn draw_drop_shadow(&mut self, rect: Rect, radius: f32, color: [f32; 4], blur: f32, spread: f32) {
1450 let margin = blur + spread;
1451 let inflated = Rect {
1452 x: rect.x - margin,
1453 y: rect.y - margin,
1454 width: rect.width + margin * 2.0,
1455 height: rect.height + margin * 2.0,
1456 };
1457 self.fill_rect_with_full_params(inflated, self.apply_opacity(color), 18, None, radius, Rect { x: margin, y: blur, width: 0.0, height: 0.0 });
1459 }
1460
1461 fn stroke_dashed_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4], width: f32, dash: f32, gap: f32) {
1462 self.fill_rect_with_full_params(rect, self.apply_opacity(color), 19, None, radius, Rect { x: width, y: dash, width: gap, height: 0.0 });
1463 }
1464
1465 fn draw_9slice(&mut self, image_name: &str, rect: Rect, left: f32, top: f32, right: f32, bottom: f32) {
1466 let c = self.apply_opacity([1.0, 1.0, 1.0, 1.0]);
1467 let tid = self.get_texture_id(image_name);
1468 self.fill_rect_with_full_params(rect, c, 20, tid, bottom, Rect { x: left, y: top, width: right, height: 0.0 });
1469 }
1470
1471 fn draw_line(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, color: [f32; 4], stroke_width: f32) {
1472 let dx = x2 - x1;
1473 let dy = y2 - y1;
1474 let len = (dx * dx + dy * dy).sqrt();
1475 if len < 0.001 { return; }
1476
1477 let _angle = dy.atan2(dx).to_degrees();
1478 let c = self.apply_opacity(color);
1479
1480 self.fill_rect_with_mode(
1485 Rect { x: (x1 + x2) / 2.0 - len / 2.0, y: (y1 + y2) / 2.0 - stroke_width / 2.0, width: len, height: stroke_width },
1486 c,
1487 1, None
1489 );
1490 }
1491
1492 fn draw_text(&mut self, text: &str, x: f32, y: f32, size: f32, color: [f32; 4]) {
1493 let mut buffer = cosmic_text::Buffer::new(&mut self.font_system, cosmic_text::Metrics::new(size, size));
1495 buffer.set_text(&mut self.font_system, text, &cosmic_text::Attrs::new(), cosmic_text::Shaping::Basic);
1497 buffer.shape_until_scroll(&mut self.font_system, false);
1498
1499 let c = self.apply_opacity(color);
1500
1501 let mut glyph_idx = 0;
1502 for run in buffer.layout_runs() {
1503 for glyph in run.glyphs {
1504 let tracking = (2.8 - 0.22 * (size - 16.0)).max(0.6);
1506 let x_offset = x + (glyph_idx as f32 * tracking);
1507
1508 let physical_glyph = glyph.physical((x_offset, y), 1.0);
1509 let cache_key = physical_glyph.cache_key;
1510
1511 let (uv_rect, w, h) = if let Some(info) = self.text_cache.get(&cache_key) {
1513 *info
1514 } else {
1515 if let Some(image) = self.swash_cache.get_image(&mut self.font_system, cache_key) {
1517 let (gx, _gy) = self.text_atlas_pos;
1518 let gw = image.placement.width;
1519 let gh = image.placement.height;
1520
1521 if gx + gw > 1024 {
1523 self.text_atlas_pos.0 = 0;
1524 self.text_atlas_pos.1 += 64; }
1526 let (nx, ny) = self.text_atlas_pos;
1527
1528 self.queue.write_texture(
1529 wgpu::TexelCopyTextureInfo {
1530 texture: &self.text_atlas_tex,
1531 mip_level: 0,
1532 origin: wgpu::Origin3d { x: nx, y: ny, z: 0 },
1533 aspect: wgpu::TextureAspect::All,
1534 },
1535 &image.data,
1536 wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(gw), rows_per_image: Some(gh) },
1537 wgpu::Extent3d { width: gw, height: gh, depth_or_array_layers: 1 },
1538 );
1539
1540 let info = (Rect { x: nx as f32 / 1024.0, y: ny as f32 / 1024.0, width: gw as f32 / 1024.0, height: gh as f32 / 1024.0 }, gw as f32, gh as f32);
1541 self.text_cache.insert(cache_key, info);
1542 self.text_atlas_pos.0 += gw + 2;
1543 info
1544 } else {
1545 (Rect::zero(), 0.0, 0.0)
1546 }
1547 };
1548
1549 if w > 0.0 {
1550 let glyph_rect = Rect {
1551 x: physical_glyph.x as f32,
1552 y: physical_glyph.y as f32,
1553 width: w,
1554 height: h,
1555 };
1556 let tid = self.get_texture_id("__text_atlas");
1557 self.fill_rect_with_full_params(glyph_rect, c, 6, tid, 0.0, uv_rect); }
1559 glyph_idx += 1;
1560 }
1561 }
1562 }
1563
1564 fn measure_text(&mut self, text: &str, size: f32) -> (f32, f32) {
1565 let mut buffer = cosmic_text::Buffer::new(&mut self.font_system, cosmic_text::Metrics::new(size, size));
1566 buffer.set_text(&mut self.font_system, text, &cosmic_text::Attrs::new(), cosmic_text::Shaping::Advanced);
1567 buffer.shape_until_scroll(&mut self.font_system, false);
1568
1569 let mut width = 0.0f32;
1570 let mut height = 0.0f32;
1571
1572 for run in buffer.layout_runs() {
1573 width = width.max(run.line_w);
1574 height += size;
1575 }
1576
1577 (width, height)
1578 }
1579
1580 fn draw_texture(&mut self, texture_id: u32, rect: Rect) {
1581 self.fill_rect_with_full_params(rect, [1.0, 1.0, 1.0, 1.0], 2, Some(texture_id), 0.0, Rect { x: 0.0, y: 0.0, width: 1.0, height: 1.0 });
1582 }
1583
1584 fn draw_image(&mut self, image_name: &str, rect: Rect) {
1585 let tid = self.get_texture_id(image_name);
1586 self.fill_rect_with_full_params(rect, [1.0, 1.0, 1.0, 1.0], 2, tid, 0.0, Rect { x: 0.0, y: 0.0, width: 1.0, height: 1.0 });
1587 }
1588
1589 fn load_image(&mut self, name: &str, data: &[u8]) {
1590 let img = image::load_from_memory(data).expect("Failed to load image").to_rgba8();
1591 let (width, height) = img.dimensions();
1592 let size = wgpu::Extent3d { width, height, depth_or_array_layers: 1 };
1593 let texture = self.device.create_texture(&wgpu::TextureDescriptor {
1594 label: Some(name),
1595 size,
1596 mip_level_count: 1,
1597 sample_count: 1,
1598 dimension: wgpu::TextureDimension::D2,
1599 format: wgpu::TextureFormat::Rgba8UnormSrgb,
1600 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1601 view_formats: &[],
1602 });
1603 self.queue.write_texture(
1604 wgpu::TexelCopyTextureInfo {
1605 texture: &texture,
1606 mip_level: 0,
1607 origin: wgpu::Origin3d::ZERO,
1608 aspect: wgpu::TextureAspect::All,
1609 },
1610 &img,
1611 wgpu::TexelCopyBufferLayout {
1612 offset: 0,
1613 bytes_per_row: Some(4 * width),
1614 rows_per_image: Some(height),
1615 },
1616 size,
1617 );
1618 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
1619 let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
1620 address_mode_u: wgpu::AddressMode::ClampToEdge,
1621 address_mode_v: wgpu::AddressMode::ClampToEdge,
1622 mag_filter: wgpu::FilterMode::Linear,
1623 ..Default::default()
1624 });
1625 let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1626 layout: &self.texture_bind_group_layout,
1627 entries: &[
1628 wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&view) },
1629 wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&sampler) },
1630 ],
1631 label: Some(name),
1632 });
1633 self.texture_bind_groups.push(bind_group);
1634 let id = (self.texture_bind_groups.len() - 1) as u32;
1635 self.texture_registry.insert(name.to_string(), id);
1636 }
1637
1638 fn push_clip_rect(&mut self, rect: Rect) {
1639 self.clip_stack.push(rect);
1640 }
1641
1642 fn pop_clip_rect(&mut self) {
1643 self.clip_stack.pop();
1644 }
1645
1646 fn push_opacity(&mut self, opacity: f32) {
1647 let current = self.opacity_stack.last().copied().unwrap_or(1.0);
1648 self.opacity_stack.push(current * opacity);
1649 }
1650
1651 fn pop_opacity(&mut self) {
1652 self.opacity_stack.pop();
1653 }
1654
1655 fn set_theme(&mut self, theme: ColorTheme) {
1656 self.current_theme = theme;
1657 self.queue.write_buffer(&self.theme_buffer, 0, bytemuck::bytes_of(&theme));
1658 }
1659
1660 fn set_rage(&mut self, rage: f32) {
1661 self.current_scene.berzerker_rage = rage;
1662 }
1664
1665 fn trigger_shatter_event(&mut self, origin: [f32; 2], force: f32) {
1666 self.current_scene.shatter_origin = origin;
1667 self.current_scene.shatter_time = self.current_scene.time;
1668 self.current_scene.shatter_force = force;
1669 }
1670
1671
1672
1673 fn push_mjolnir_slice(&mut self, angle: f32, offset: f32) {
1674 self.slice_stack.push((angle, offset));
1675 }
1676
1677 fn pop_mjolnir_slice(&mut self) {
1678 self.slice_stack.pop();
1679 }
1680
1681 fn mjolnir_shatter(&mut self, rect: Rect, pieces: u32, force: f32, color: [f32; 4]) {
1682 self.shatter_internal(rect, pieces, force, color, 8);
1683 }
1684
1685 fn mjolnir_fluid_shatter(&mut self, rect: Rect, pieces: u32, force: f32, color: [f32; 4]) {
1686 self.shatter_internal(rect, pieces, force, color, 11);
1687 }
1688
1689 fn draw_mjolnir_bolt(&mut self, from: [f32; 2], to: [f32; 2], color: [f32; 4]) {
1690 self.recursive_bolt(from, to, 4, color);
1691 }
1692
1693 fn upload_data_texture(&mut self, id: &str, data: &[f32], width: u32, height: u32) {
1694 let size = wgpu::Extent3d { width, height, depth_or_array_layers: 1 };
1695 let texture = self.device.create_texture(&wgpu::TextureDescriptor {
1696 label: Some(id),
1697 size,
1698 mip_level_count: 1,
1699 sample_count: 1,
1700 dimension: wgpu::TextureDimension::D2,
1701 format: wgpu::TextureFormat::R32Float,
1702 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1703 view_formats: &[],
1704 });
1705 self.queue.write_texture(
1706 wgpu::TexelCopyTextureInfo {
1707 texture: &texture,
1708 mip_level: 0,
1709 origin: wgpu::Origin3d::ZERO,
1710 aspect: wgpu::TextureAspect::All,
1711 },
1712 bytemuck::cast_slice(data),
1713 wgpu::TexelCopyBufferLayout {
1714 offset: 0,
1715 bytes_per_row: Some(4 * width),
1716 rows_per_image: Some(height),
1717 },
1718 size,
1719 );
1720 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
1721 let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
1722 address_mode_u: wgpu::AddressMode::ClampToEdge,
1723 address_mode_v: wgpu::AddressMode::ClampToEdge,
1724 mag_filter: wgpu::FilterMode::Linear,
1725 ..Default::default()
1726 });
1727 let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1728 layout: &self.texture_bind_group_layout,
1729 entries: &[
1730 wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&view) },
1731 wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&sampler) },
1732 ],
1733 label: Some(id),
1734 });
1735 self.texture_bind_groups.push(bind_group);
1736 let tid = (self.texture_bind_groups.len() - 1) as u32;
1737 self.texture_registry.insert(id.to_string(), tid);
1738 }
1739
1740 fn draw_heatmap(&mut self, texture_id: &str, rect: Rect, _palette: &str) {
1741 let tid = self.get_texture_id(texture_id);
1742 self.fill_rect_with_mode(rect, [1.0, 1.0, 1.0, 1.0], 12, tid);
1743 }
1744
1745 fn draw_mesh(&mut self, mesh: &Mesh, color: [f32; 4], transform: glam::Mat4) {
1746 let base_idx = self.vertices.len() as u32;
1747 let screen = [self.config.width as f32, self.config.height as f32];
1748
1749 for i in 0..mesh.vertices.len() {
1750 let pos = transform.transform_point3(glam::Vec3::from(mesh.vertices[i]));
1751 let norm = transform.transform_vector3(glam::Vec3::from(mesh.normals[i]));
1752
1753 self.vertices.push(Vertex {
1754 position: pos.to_array(),
1755 normal: norm.to_array(),
1756 uv: [0.0, 0.0],
1757 color,
1758 mode: 13, radius: 0.0,
1760 slice: [0.0, 0.0, 0.0, 1.0],
1761 logical: [0.0, 0.0],
1762 size: [0.0, 0.0],
1763 screen,
1764 clip: [-10000.0, -10000.0, 20000.0, 20000.0],
1765 });
1766 }
1767
1768 for idx in &mesh.indices {
1769 self.indices.push(base_idx + idx);
1770 }
1771
1772 if self.draw_calls.is_empty() || self.current_texture_id.is_some() {
1773 self.current_texture_id = None;
1774 self.draw_calls.push(DrawCall {
1775 texture_id: None,
1776 scissor_rect: self.clip_stack.last().copied(),
1777 index_start: (self.indices.len() as u32) - (mesh.indices.len() as u32),
1778 index_count: mesh.indices.len() as u32,
1779 is_glass: false,
1780 is_ui: false,
1781 });
1782 } else {
1783 self.draw_calls.last_mut().unwrap().index_count += mesh.indices.len() as u32;
1784 }
1785 }
1786
1787 fn register_shared_element(&mut self, id: &str, rect: Rect) {
1788 self.shared_elements.insert(id.to_string(), rect);
1789 }
1790}
1791
1792
1793impl Drop for SurtrRenderer {
1794 fn drop(&mut self) {
1795 let _ = self.device.poll(wgpu::PollType::wait_indefinitely());
1797 }
1798}
1799
1800impl cvkg_core::FrameRenderer<wgpu::CommandEncoder> for SurtrRenderer {
1801 fn begin_frame(&mut self) -> wgpu::CommandEncoder {
1802 self.begin_frame()
1803 }
1804
1805 fn end_frame(&mut self, encoder: wgpu::CommandEncoder) {
1806 self.end_frame(encoder)
1807 }
1808}
1809
1810impl SurtrRenderer {
1811 fn apply_opacity(&self, mut color: [f32; 4]) -> [f32; 4] {
1813 if let Some(&alpha) = self.opacity_stack.last() {
1814 color[3] *= alpha;
1815 }
1816 color
1817 }
1818}