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