1use crate::heim::SkylinePacker;
2use crate::renderer::context_helpers::{
3 compute_mip_levels, create_headless_context, create_surface_context,
4 load_pipeline_cache_with_integrity_check,
5};
6use crate::renderer::pipelines::compile_render_pipelines;
7use crate::renderer::{GpuRenderer, QualityLevel};
8use crate::types::{
9 GpuParticle, HeadlessContext, MAX_INDICES, MAX_PARTICLES, MAX_VERTICES, ParticleUniforms,
10 SurfaceContext,
11};
12use crate::{
13 WGSL_BIFROST, WGSL_BLOOM, WGSL_COLOR_BLIND, WGSL_COMMON, WGSL_MATERIAL_GLASS,
14 WGSL_MATERIAL_OPAQUE, WGSL_MATERIAL_PBR, WGSL_MATERIAL_SHADOW, WGSL_SHAPES,
15};
16use cvkg_core::{ColorTheme, Rect, SceneUniforms};
17use lru::LruCache;
18use std::collections::HashMap;
19use std::num::NonZeroUsize;
20use std::sync::Arc;
21
22impl GpuRenderer {
23 pub async fn forge(window: Arc<winit::window::Window>) -> Self {
30 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
31 backends: wgpu::Backends::all(),
32 flags: wgpu::InstanceFlags::default(),
33 backend_options: wgpu::BackendOptions::default(),
34 display: None,
35 memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
36 });
37
38 let surface = instance
39 .create_surface(window.clone())
40 .expect("Failed to create surface");
41
42 tracing::info!("[Surtr] Renderer backend: GpuRenderer (wgpu)");
43
44 tracing::info!("[GPU] Requesting HighPerformance adapter...");
46
47 let mut adapter = None;
48
49 #[cfg(not(target_arch = "wasm32"))]
50 if let Ok(filter) = std::env::var("WGPU_ADAPTER_NAME") {
51 let adapters = instance.enumerate_adapters(wgpu::Backends::all()).await;
52 tracing::info!("[GPU] Available adapters:");
53 for a in &adapters {
54 let info = a.get_info();
55 tracing::info!(
56 " - Name: '{}' | Driver: '{}' | Backend: {:?}",
57 info.name,
58 info.driver,
59 info.backend
60 );
61 }
62
63 adapter = adapters.into_iter().find(|a| {
64 let info = a.get_info();
65 let match_found = info.name.to_lowercase().contains(&filter.to_lowercase())
66 || info.driver.to_lowercase().contains(&filter.to_lowercase());
67 if match_found {
68 tracing::info!(
69 "[GPU] Manual selection match: {} | Driver: {}",
70 info.name,
71 info.driver
72 );
73 }
74 match_found
75 });
76
77 if adapter.is_some() {
78 tracing::info!(
79 "[GPU] Forced adapter selection via WGPU_ADAPTER_NAME='{}'",
80 filter
81 );
82 } else {
83 tracing::warn!(
84 "[GPU] WGPU_ADAPTER_NAME='{}' provided but no matching adapter found. Falling back...",
85 filter
86 );
87 }
88 }
89
90 if adapter.is_none() {
91 adapter = instance
92 .request_adapter(&wgpu::RequestAdapterOptions {
93 power_preference: wgpu::PowerPreference::HighPerformance,
94 compatible_surface: Some(&surface),
95 force_fallback_adapter: false,
96 })
97 .await
98 .ok();
99 }
100
101 if adapter.is_none() {
102 tracing::warn!(
103 "[GPU] HighPerformance adapter failed (possible Bumblebee/Optimus), trying LowPower..."
104 );
105 adapter = instance
106 .request_adapter(&wgpu::RequestAdapterOptions {
107 power_preference: wgpu::PowerPreference::LowPower,
108 compatible_surface: Some(&surface),
109 force_fallback_adapter: false,
110 })
111 .await
112 .ok();
113 }
114
115 if adapter.is_none() {
116 tracing::warn!("[GPU] Hardware adapters failed, trying Software fallback...");
117 adapter = instance
118 .request_adapter(&wgpu::RequestAdapterOptions {
119 power_preference: wgpu::PowerPreference::LowPower,
120 compatible_surface: Some(&surface),
121 force_fallback_adapter: true,
122 })
123 .await
124 .ok();
125 }
126
127 let adapter = adapter.expect("Failed to find a suitable GPU for Surtr");
128 let info = adapter.get_info();
129 let caps =
132 crate::subsystems::GpuCapabilities::detect(&info.name, format!("{:?}", info.backend));
133 tracing::info!(
134 "[GPU] Selected adapter: {} ({:?}) on backend: {:?} -- detected as {}",
135 info.name,
136 info.device_type,
137 info.backend,
138 caps.vendor
139 );
140 tracing::info!("[GPU] Driver info: {} - {}", info.driver, info.driver_info);
141 let supports_timestamps = adapter.features().contains(wgpu::Features::TIMESTAMP_QUERY);
142 let supports_pipeline_cache = adapter.features().contains(wgpu::Features::PIPELINE_CACHE);
143 #[cfg(not(target_arch = "wasm32"))]
144 let mut required_features =
145 wgpu::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING
146 | wgpu::Features::TEXTURE_BINDING_ARRAY;
147
148 #[cfg(target_arch = "wasm32")]
149 let mut required_features = wgpu::Features::empty(); if supports_timestamps {
151 required_features |= wgpu::Features::TIMESTAMP_QUERY;
152 }
153 if supports_pipeline_cache {
154 required_features |= wgpu::Features::PIPELINE_CACHE;
155 }
156 #[cfg(all(debug_assertions, not(target_arch = "wasm32")))]
158 {
159 tracing::info!("[GPU] Validation layer enabled (debug build)");
160 }
161
162 let (device, queue) = adapter
163 .request_device(&wgpu::DeviceDescriptor {
164 label: Some("Surtr Forge"),
165 required_features,
166 required_limits: wgpu::Limits {
167 max_bindings_per_bind_group: 256,
168 max_binding_array_elements_per_shader_stage: 256,
169 ..wgpu::Limits::default()
170 },
171 memory_hints: wgpu::MemoryHints::default(),
172 experimental_features: wgpu::ExperimentalFeatures::disabled(),
173 trace: wgpu::Trace::Off,
174 })
175 .await
176 .expect("Failed to create Surtr device");
177
178 let instance = Arc::new(instance);
179 let adapter = Arc::new(adapter);
180
181 device.on_uncaptured_error(Arc::new(|error| {
182 tracing::error!(
183 "[GPU] Uncaptured device error (Device Lost or Panic): {:?}",
184 error
185 );
186 }));
187
188 let device = Arc::new(device);
189 let queue = Arc::new(queue);
190
191 let size = window.inner_size();
192 let width = if size.width > 0 { size.width } else { 1280 };
194 let height = if size.height > 0 { size.height } else { 720 };
195 let surface_caps = surface.get_capabilities(&adapter);
196 let surface_format = Self::select_best_surface_format(&surface_caps.formats);
200
201 tracing::info!(
202 "[GPU] Available present modes: {:?}",
203 surface_caps.present_modes
204 );
205 tracing::info!(
206 "[GPU] Adapter: {} ({:?})",
207 adapter.get_info().name,
208 adapter.get_info().backend
209 );
210 let present_mode = if surface_caps
211 .present_modes
212 .contains(&wgpu::PresentMode::Immediate)
213 {
214 tracing::info!("[GPU] Selected: Immediate (no vsync, uncapped)");
215 wgpu::PresentMode::Immediate
216 } else if surface_caps
217 .present_modes
218 .contains(&wgpu::PresentMode::Mailbox)
219 {
220 tracing::info!("[GPU] Selected: Mailbox (no vsync)");
221 wgpu::PresentMode::Mailbox
222 } else {
223 tracing::info!("[GPU] Selected: Fifo (V-Sync capped at compositor rate)");
224 wgpu::PresentMode::Fifo
225 };
226
227 let alpha_mode = if surface_caps
228 .alpha_modes
229 .contains(&wgpu::CompositeAlphaMode::PostMultiplied)
230 {
231 wgpu::CompositeAlphaMode::PostMultiplied
232 } else if surface_caps
233 .alpha_modes
234 .contains(&wgpu::CompositeAlphaMode::PreMultiplied)
235 {
236 wgpu::CompositeAlphaMode::PreMultiplied
237 } else {
238 surface_caps.alpha_modes[0]
239 };
240
241 tracing::info!(
242 "[GPU] Configuring surface: {}x{} | {:?} | {:?}",
243 width,
244 height,
245 present_mode,
246 alpha_mode
247 );
248
249 let config = wgpu::SurfaceConfiguration {
250 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
251 format: surface_format,
252 width,
253 height,
254 present_mode,
255 alpha_mode,
256 view_formats: vec![],
257 desired_maximum_frame_latency: 1,
258 };
259 surface.configure(&device, &config);
260 tracing::info!("[GPU] Surface configuration successful.");
261
262 let renderer = Self::forge_internal(
263 instance,
264 adapter,
265 device,
266 queue,
267 Some((window, surface, config)),
268 None,
269 )
270 .await;
271 tracing::info!("[GPU] Forge internal complete.");
272 renderer
273 }
274
275 pub(crate) async fn forge_internal(
277 instance: Arc<wgpu::Instance>,
278 adapter: Arc<wgpu::Adapter>,
279 device: Arc<wgpu::Device>,
280 queue: Arc<wgpu::Queue>,
281 surface_info: Option<(
282 Arc<winit::window::Window>,
283 wgpu::Surface<'static>,
284 wgpu::SurfaceConfiguration,
285 )>,
286 headless_info: Option<(u32, u32, wgpu::TextureFormat)>,
287 ) -> Self {
288 let format = if let Some((_, _, ref config)) = surface_info {
289 config.format
290 } else if let Some((_, _, f)) = headless_info {
291 f
292 } else {
293 wgpu::TextureFormat::Rgba8UnormSrgb
294 };
295
296 let supports_timestamps = adapter.features().contains(wgpu::Features::TIMESTAMP_QUERY);
297 let skuld_period = queue.get_timestamp_period();
298 let (skuld_queries, skuld_buffer, skuld_read_buffer) = if supports_timestamps {
299 let q = device.create_query_set(&wgpu::QuerySetDescriptor {
300 label: Some("Skuld Timestamp Queries"),
301 count: 2,
302 ty: wgpu::QueryType::Timestamp,
303 });
304 let b = device.create_buffer(&wgpu::BufferDescriptor {
305 label: Some("Skuld Query Buffer"),
306 size: 16,
307 usage: wgpu::BufferUsages::QUERY_RESOLVE | wgpu::BufferUsages::COPY_SRC,
308 mapped_at_creation: false,
309 });
310 let rb = device.create_buffer(&wgpu::BufferDescriptor {
311 label: Some("Skuld Read Buffer"),
312 size: 16,
313 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
314 mapped_at_creation: false,
315 });
316 (Some(q), Some(b), Some(rb))
317 } else {
318 (None, None, None)
319 };
320
321 let pipeline_cache = if device.features().contains(wgpu::Features::PIPELINE_CACHE) {
322 let cache_dir = std::env::current_exe()
323 .ok()
324 .and_then(|p| p.parent().map(|d| d.join("pipeline_cache")))
325 .unwrap_or_else(|| std::env::temp_dir().join("cvkg_pipeline_cache"));
326 let _ = std::fs::create_dir_all(&cache_dir);
327 let cache_path = cache_dir.join("cvkg_render_gpu.bin");
328 let cache_data = match load_pipeline_cache_with_integrity_check(&cache_path) {
329 Ok(data) => data,
330 Err(reason) => {
331 tracing::warn!(
332 "[GPU] pipeline cache integrity check failed: {reason}; using empty cache"
333 );
334 None
335 }
336 };
337 Some(unsafe {
341 device.create_pipeline_cache(&wgpu::PipelineCacheDescriptor {
342 label: Some("CVKG Pipeline Cache"),
343 data: cache_data.as_deref(),
344 fallback: true,
345 })
346 })
347 } else {
348 tracing::debug!(
349 "[GPU] device does not expose PIPELINE_CACHE; compiling pipelines without cache"
350 );
351 None
352 };
353 let materials_generated = crate::material::generate_builtins_wgsl();
354
355 let wgsl_src = format!(
356 "{}{}{}{}{}{}",
357 WGSL_COMMON,
358 WGSL_SHAPES,
359 WGSL_BIFROST,
360 WGSL_BLOOM,
361 WGSL_COLOR_BLIND,
362 materials_generated
363 );
364 let wgsl_opaque = format!(
365 "{}{}{}{}{}{}",
366 WGSL_COMMON,
367 WGSL_MATERIAL_OPAQUE,
368 WGSL_BIFROST,
369 WGSL_BLOOM,
370 WGSL_COLOR_BLIND,
371 materials_generated
372 );
373 let wgsl_glass = format!(
374 "{}{}{}{}{}{}",
375 WGSL_COMMON,
376 WGSL_MATERIAL_GLASS,
377 WGSL_BIFROST,
378 WGSL_BLOOM,
379 WGSL_COLOR_BLIND,
380 materials_generated
381 );
382 let wgsl_pbr = format!(
383 "{}{}{}{}{}{}",
384 WGSL_COMMON,
385 WGSL_MATERIAL_PBR,
386 WGSL_BIFROST,
387 WGSL_BLOOM,
388 WGSL_COLOR_BLIND,
389 materials_generated
390 );
391 let wgsl_shadow = format!(
392 "{}{}{}",
393 WGSL_COMMON, WGSL_MATERIAL_SHADOW, materials_generated
394 );
395
396 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
397 label: Some("Surtr Main Shader"),
398 source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Owned(wgsl_src)),
399 });
400
401 #[cfg(target_arch = "wasm32")]
402 let texture_array_count: Option<std::num::NonZeroU32> = None;
403 #[cfg(not(target_arch = "wasm32"))]
404 let texture_array_count: Option<std::num::NonZeroU32> = std::num::NonZeroU32::new(32);
405
406 let texture_bind_group_layout =
407 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
408 entries: &[
409 wgpu::BindGroupLayoutEntry {
410 binding: 0,
411 visibility: wgpu::ShaderStages::FRAGMENT,
412 ty: wgpu::BindingType::Texture {
413 multisampled: false,
414 view_dimension: wgpu::TextureViewDimension::D2,
415 sample_type: wgpu::TextureSampleType::Float { filterable: true },
416 },
417 count: texture_array_count,
418 },
419 wgpu::BindGroupLayoutEntry {
420 binding: 1,
421 visibility: wgpu::ShaderStages::FRAGMENT,
422 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
423 count: None,
424 },
425 ],
426 label: Some("Niflheim Texture Bind Group Layout"),
427 });
428
429 let env_bind_group_layout =
430 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
431 entries: &[
432 wgpu::BindGroupLayoutEntry {
433 binding: 0,
434 visibility: wgpu::ShaderStages::FRAGMENT,
435 ty: wgpu::BindingType::Texture {
436 multisampled: false,
437 view_dimension: wgpu::TextureViewDimension::D2,
438 sample_type: wgpu::TextureSampleType::Float { filterable: true },
439 },
440 count: None,
441 },
442 wgpu::BindGroupLayoutEntry {
443 binding: 1,
444 visibility: wgpu::ShaderStages::FRAGMENT,
445 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
446 count: None,
447 },
448 ],
449 label: Some("Surtr Environment Bind Group Layout"),
450 });
451
452 let berserker_bind_group_layout =
453 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
454 entries: &[
455 wgpu::BindGroupLayoutEntry {
456 binding: 0,
457 visibility: wgpu::ShaderStages::FRAGMENT,
458 ty: wgpu::BindingType::Buffer {
459 ty: wgpu::BufferBindingType::Uniform,
460 has_dynamic_offset: false,
461 min_binding_size: None,
462 },
463 count: None,
464 },
465 wgpu::BindGroupLayoutEntry {
466 binding: 1,
467 visibility: wgpu::ShaderStages::FRAGMENT | wgpu::ShaderStages::VERTEX,
468 ty: wgpu::BindingType::Buffer {
469 ty: wgpu::BufferBindingType::Uniform,
470 has_dynamic_offset: false,
471 min_binding_size: None,
472 },
473 count: None,
474 },
475 wgpu::BindGroupLayoutEntry {
476 binding: 2,
477 visibility: wgpu::ShaderStages::FRAGMENT | wgpu::ShaderStages::VERTEX,
478 ty: wgpu::BindingType::Buffer {
479 ty: wgpu::BufferBindingType::Uniform,
480 has_dynamic_offset: false,
481 min_binding_size: None,
482 },
483 count: None,
484 },
485 ],
486 label: Some("Surtr Berserker Bind Group Layout"),
487 });
488
489 let gradient_bind_group_layout =
490 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
491 entries: &[
492 wgpu::BindGroupLayoutEntry {
493 binding: 0,
494 visibility: wgpu::ShaderStages::FRAGMENT,
495 ty: wgpu::BindingType::Texture {
496 multisampled: false,
497 view_dimension: wgpu::TextureViewDimension::D2,
498 sample_type: wgpu::TextureSampleType::Float { filterable: false },
499 },
500 count: None,
501 },
502 wgpu::BindGroupLayoutEntry {
503 binding: 1,
504 visibility: wgpu::ShaderStages::FRAGMENT,
505 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
506 count: None,
507 },
508 ],
509 label: Some("Surtr Gradient Bind Group Layout"),
510 });
511
512 let pbr_material_bind_group_layout =
513 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
514 entries: &[
515 wgpu::BindGroupLayoutEntry {
517 binding: 0,
518 visibility: wgpu::ShaderStages::FRAGMENT,
519 ty: wgpu::BindingType::Texture {
520 multisampled: false,
521 view_dimension: wgpu::TextureViewDimension::D2,
522 sample_type: wgpu::TextureSampleType::Depth,
523 },
524 count: None,
525 },
526 wgpu::BindGroupLayoutEntry {
528 binding: 1,
529 visibility: wgpu::ShaderStages::FRAGMENT,
530 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison),
531 count: None,
532 },
533 wgpu::BindGroupLayoutEntry {
535 binding: 8,
536 visibility: wgpu::ShaderStages::FRAGMENT,
537 ty: wgpu::BindingType::Texture {
538 multisampled: false,
539 view_dimension: wgpu::TextureViewDimension::D2,
540 sample_type: wgpu::TextureSampleType::Float { filterable: true },
541 },
542 count: None,
543 },
544 wgpu::BindGroupLayoutEntry {
546 binding: 9,
547 visibility: wgpu::ShaderStages::FRAGMENT,
548 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
549 count: None,
550 },
551 wgpu::BindGroupLayoutEntry {
553 binding: 6,
554 visibility: wgpu::ShaderStages::FRAGMENT,
555 ty: wgpu::BindingType::Texture {
556 multisampled: false,
557 view_dimension: wgpu::TextureViewDimension::D2,
558 sample_type: wgpu::TextureSampleType::Float { filterable: true },
559 },
560 count: None,
561 },
562 wgpu::BindGroupLayoutEntry {
564 binding: 7,
565 visibility: wgpu::ShaderStages::FRAGMENT,
566 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
567 count: None,
568 },
569 ],
570 label: Some("Surtr PBR Material Bind Group Layout"),
571 });
572
573 let pipes = compile_render_pipelines(
574 &device,
575 format,
576 pipeline_cache.as_ref(),
577 &texture_bind_group_layout,
578 &env_bind_group_layout,
579 &berserker_bind_group_layout,
580 &gradient_bind_group_layout,
581 &pbr_material_bind_group_layout,
582 &shader,
583 wgsl_opaque.as_str(),
584 wgsl_glass.as_str(),
585 wgsl_pbr.as_str(),
586 wgsl_shadow.as_str(),
587 &queue,
588 );
589
590 let mega_heim_tex = device.create_texture(&wgpu::TextureDescriptor {
592 label: Some("Surtr Mega-Heim"),
593 size: wgpu::Extent3d {
594 width: 4096,
595 height: 4096,
596 depth_or_array_layers: 1,
597 },
598 mip_level_count: 1,
599 sample_count: 1,
600 dimension: wgpu::TextureDimension::D2,
601 format: wgpu::TextureFormat::Rgba8UnormSrgb,
602 usage: wgpu::TextureUsages::TEXTURE_BINDING
603 | wgpu::TextureUsages::COPY_DST
604 | wgpu::TextureUsages::COPY_SRC,
605 view_formats: &[],
606 });
607 let mega_heim_view_obj = mega_heim_tex.create_view(&wgpu::TextureViewDescriptor::default());
608 let text_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
609 address_mode_u: wgpu::AddressMode::ClampToEdge,
610 address_mode_v: wgpu::AddressMode::ClampToEdge,
611 mag_filter: wgpu::FilterMode::Linear,
612 min_filter: wgpu::FilterMode::Linear,
613 ..Default::default()
614 });
615
616 let dummy_size = wgpu::Extent3d {
618 width: 1,
619 height: 1,
620 depth_or_array_layers: 1,
621 };
622 let dummy_texture = device.create_texture(&wgpu::TextureDescriptor {
623 label: Some("Niflheim Dummy Texture"),
624 size: dummy_size,
625 mip_level_count: 1,
626 sample_count: 1,
627 dimension: wgpu::TextureDimension::D2,
628 format: wgpu::TextureFormat::Rgba8UnormSrgb,
629 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
630 view_formats: &[],
631 });
632 queue.write_texture(
633 wgpu::TexelCopyTextureInfo {
634 texture: &dummy_texture,
635 mip_level: 0,
636 origin: wgpu::Origin3d::ZERO,
637 aspect: wgpu::TextureAspect::All,
638 },
639 &[255, 255, 255, 255],
640 wgpu::TexelCopyBufferLayout {
641 offset: 0,
642 bytes_per_row: Some(4),
643 rows_per_image: Some(1),
644 },
645 dummy_size,
646 );
647
648 let dummy_view = dummy_texture.create_view(&wgpu::TextureViewDescriptor::default());
649
650 let gradient_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
652 address_mode_u: wgpu::AddressMode::ClampToEdge,
653 address_mode_v: wgpu::AddressMode::ClampToEdge,
654 address_mode_w: wgpu::AddressMode::ClampToEdge,
655 mag_filter: wgpu::FilterMode::Nearest,
656 min_filter: wgpu::FilterMode::Nearest,
657 mipmap_filter: wgpu::MipmapFilterMode::Nearest,
658 ..Default::default()
659 });
660
661 let gradient_dummy_texture = device.create_texture(&wgpu::TextureDescriptor {
664 label: Some("Gradient Dummy Texture"),
665 size: wgpu::Extent3d {
666 width: 1,
667 height: 1,
668 depth_or_array_layers: 1,
669 },
670 mip_level_count: 1,
671 sample_count: 1,
672 dimension: wgpu::TextureDimension::D2,
673 format: wgpu::TextureFormat::Rgba16Float,
674 usage: wgpu::TextureUsages::TEXTURE_BINDING,
675 view_formats: &[],
676 });
677 let gradient_dummy_view =
678 gradient_dummy_texture.create_view(&wgpu::TextureViewDescriptor::default());
679 let gradient_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
680 layout: &gradient_bind_group_layout,
681 entries: &[
682 wgpu::BindGroupEntry {
683 binding: 0,
684 resource: wgpu::BindingResource::TextureView(&gradient_dummy_view),
685 },
686 wgpu::BindGroupEntry {
687 binding: 1,
688 resource: wgpu::BindingResource::Sampler(&gradient_sampler),
689 },
690 ],
691 label: Some("Gradient Dummy Bind Group"),
692 });
693 let dummy_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
694 address_mode_u: wgpu::AddressMode::ClampToEdge,
695 address_mode_v: wgpu::AddressMode::ClampToEdge,
696 address_mode_w: wgpu::AddressMode::ClampToEdge,
697 mag_filter: wgpu::FilterMode::Linear,
698 min_filter: wgpu::FilterMode::Nearest,
699 mipmap_filter: wgpu::MipmapFilterMode::Nearest,
700 ..Default::default()
701 });
702
703 let gradient_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
705 address_mode_u: wgpu::AddressMode::ClampToEdge,
706 address_mode_v: wgpu::AddressMode::ClampToEdge,
707 address_mode_w: wgpu::AddressMode::ClampToEdge,
708 mag_filter: wgpu::FilterMode::Nearest,
709 min_filter: wgpu::FilterMode::Nearest,
710 mipmap_filter: wgpu::MipmapFilterMode::Nearest,
711 ..Default::default()
712 });
713
714 let mut texture_views_list: Vec<wgpu::TextureView> =
715 (0..32).map(|_| dummy_view.clone()).collect();
716 texture_views_list[0] = mega_heim_view_obj.clone();
717
718 let views_refs: Vec<&wgpu::TextureView> = texture_views_list.iter().collect();
719 let mega_heim_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
720 layout: &texture_bind_group_layout,
721 entries: &[
722 wgpu::BindGroupEntry {
723 binding: 0,
724 resource: wgpu::BindingResource::TextureViewArray(&views_refs),
725 },
726 wgpu::BindGroupEntry {
727 binding: 1,
728 resource: wgpu::BindingResource::Sampler(&text_sampler),
729 },
730 ],
731 label: Some("Mega-Heim Bind Group"),
732 });
733
734 let dummy_views_refs: Vec<&wgpu::TextureView> = (0..32).map(|_| &dummy_view).collect();
735 let dummy_texture_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
736 layout: &texture_bind_group_layout,
737 entries: &[
738 wgpu::BindGroupEntry {
739 binding: 0,
740 resource: wgpu::BindingResource::TextureViewArray(&dummy_views_refs),
741 },
742 wgpu::BindGroupEntry {
743 binding: 1,
744 resource: wgpu::BindingResource::Sampler(&dummy_sampler),
745 },
746 ],
747 label: Some("Dummy Texture Bind Group"),
748 });
749
750 let dummy_env_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
751 layout: &env_bind_group_layout,
752 entries: &[
753 wgpu::BindGroupEntry {
754 binding: 0,
755 resource: wgpu::BindingResource::TextureView(&dummy_view),
756 },
757 wgpu::BindGroupEntry {
758 binding: 1,
759 resource: wgpu::BindingResource::Sampler(&dummy_sampler),
760 },
761 ],
762 label: Some("Dummy Env Bind Group"),
763 });
764 let dummy_depth_tex = device.create_texture(&wgpu::TextureDescriptor {
765 label: Some("Surtr Dummy Depth Texture"),
766 size: wgpu::Extent3d {
767 width: 1,
768 height: 1,
769 depth_or_array_layers: 1,
770 },
771 mip_level_count: 1,
772 sample_count: 1,
773 dimension: wgpu::TextureDimension::D2,
774 format: wgpu::TextureFormat::Depth32Float,
775 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
776 view_formats: &[],
777 });
778 let dummy_depth_view = dummy_depth_tex.create_view(&wgpu::TextureViewDescriptor::default());
779
780 let dummy_depth_tex_msaa = device.create_texture(&wgpu::TextureDescriptor {
781 label: Some("Surtr Dummy Depth Texture MSAA"),
782 size: wgpu::Extent3d {
783 width: 1,
784 height: 1,
785 depth_or_array_layers: 1,
786 },
787 mip_level_count: 1,
788 sample_count: 4,
789 dimension: wgpu::TextureDimension::D2,
790 format: wgpu::TextureFormat::Depth32Float,
791 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
792 view_formats: &[],
793 });
794 let dummy_depth_view_msaa =
795 dummy_depth_tex_msaa.create_view(&wgpu::TextureViewDescriptor::default());
796
797 let shadow_map_size = 1024;
798 let shadow_map_texture = device.create_texture(&wgpu::TextureDescriptor {
799 label: Some("Surtr CSM Shadow Map Texture"),
800 size: wgpu::Extent3d {
801 width: shadow_map_size * 2, height: shadow_map_size * 2,
803 depth_or_array_layers: 1, },
805 mip_level_count: 1,
806 sample_count: 1,
807 dimension: wgpu::TextureDimension::D2,
808 format: wgpu::TextureFormat::Depth32Float,
809 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
810 view_formats: &[],
811 });
812
813 let shadow_map_view = shadow_map_texture.create_view(&wgpu::TextureViewDescriptor {
814 label: Some("Surtr CSM Shadow Map View"),
815 dimension: Some(wgpu::TextureViewDimension::D2),
816 ..wgpu::TextureViewDescriptor::default()
817 });
818
819 let shadow_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
820 label: Some("Surtr CSM Shadow Sampler"),
821 address_mode_u: wgpu::AddressMode::ClampToEdge,
822 address_mode_v: wgpu::AddressMode::ClampToEdge,
823 address_mode_w: wgpu::AddressMode::ClampToEdge,
824 mag_filter: wgpu::FilterMode::Linear,
825 min_filter: wgpu::FilterMode::Linear,
826 compare: Some(wgpu::CompareFunction::LessEqual),
827 ..wgpu::SamplerDescriptor::default()
828 });
829
830 let mut texture_registry = LruCache::new(NonZeroUsize::new(31).unwrap());
831 let mut texture_bind_groups = Vec::new();
832
833 texture_registry.put("__mega_heim".to_string(), 0);
835 texture_bind_groups.push(mega_heim_bind_group.clone());
836
837 let geometry_buffers =
838 crate::types::GeometryBuffers::forge(&device, MAX_VERTICES, MAX_INDICES);
839
840 let instance_buffer_3d = device.create_buffer(&wgpu::BufferDescriptor {
841 label: Some("Surtr 3D Instance Buffer"),
842 size: (MAX_VERTICES / 4 * std::mem::size_of::<crate::vertex::InstanceData3D>()) as u64,
843 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
844 mapped_at_creation: false,
845 });
846
847 let current_theme = ColorTheme::default();
849 use wgpu::util::DeviceExt;
850 let theme_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
851 label: Some("Surtr Theme Buffer"),
852 contents: bytemuck::bytes_of(¤t_theme),
853 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
854 });
855
856 let (width, height, scale_factor) = if let Some((ref window, _, ref config)) = surface_info
857 {
858 (config.width, config.height, window.scale_factor() as f32)
859 } else if let Some((w, h, _)) = headless_info {
860 (w, h, 1.0)
861 } else {
862 (1280, 720, 1.0)
863 };
864
865 let mut current_scene =
866 SceneUniforms::new(width as f32 / scale_factor, height as f32 / scale_factor);
867 current_scene.scale_factor = scale_factor;
868 let msaa_sample_count = QualityLevel::default().msaa_sample_count();
869 let scene_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
870 label: Some("Surtr Scene Buffer"),
871 contents: bytemuck::bytes_of(¤t_scene),
872 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
873 });
874
875 let csm_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
876 label: Some("Surtr CSM Buffer"),
877 contents: bytemuck::bytes_of(&cvkg_core::render_tier::CsmUniforms::default()),
878 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
879 });
880
881 let berserker_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
882 layout: &berserker_bind_group_layout,
883 entries: &[
884 wgpu::BindGroupEntry {
885 binding: 0,
886 resource: theme_buffer.as_entire_binding(),
887 },
888 wgpu::BindGroupEntry {
889 binding: 1,
890 resource: scene_buffer.as_entire_binding(),
891 },
892 wgpu::BindGroupEntry {
893 binding: 2,
894 resource: csm_buffer.as_entire_binding(),
895 },
896 ],
897 label: Some("Surtr Berserker Bind Group"),
898 });
899
900 let mut registry = crate::kvasir::registry::ResourceRegistry::new();
901 let mut surfaces = std::collections::HashMap::new();
902 let mut current_window = None;
903 let mut headless_context = None;
904
905 if let Some((window, surface, config)) = surface_info {
906 let window_id = window.id();
907 let ctx = create_surface_context(
908 &device,
909 surface,
910 config,
911 &env_bind_group_layout,
912 &texture_bind_group_layout,
913 scale_factor,
914 msaa_sample_count,
915 &mut registry,
916 );
917 surfaces.insert(window_id, ctx);
918 current_window = Some(window_id);
919 } else if let Some((w, h, f)) = headless_info {
920 headless_context = Some(create_headless_context(
921 &device,
922 w,
923 h,
924 f,
925 &env_bind_group_layout,
926 &texture_bind_group_layout,
927 &mut registry,
928 msaa_sample_count,
929 ));
930 }
931
932 let staging_belt = wgpu::util::StagingBelt::new((*device).clone(), 1024 * 1024);
933
934 let glass_output_bind_group_layout = env_bind_group_layout.clone();
935
936 let skinning_joint_matrices = device.create_buffer(&wgpu::BufferDescriptor {
940 label: Some("Skinning Joint Matrices Buffer"),
941 size: (256 * std::mem::size_of::<glam::Mat4>()) as u64,
942 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
943 mapped_at_creation: false,
944 });
945
946 let skinning_morph_positions = device.create_buffer(&wgpu::BufferDescriptor {
947 label: Some("Skinning Morph Positions Buffer"),
948 size: (65536 * 2 * 16) as u64,
949 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
950 mapped_at_creation: false,
951 });
952
953 let skinning_morph_weights = device.create_buffer(&wgpu::BufferDescriptor {
954 label: Some("Skinning Morph Weights Buffer"),
955 size: 16,
956 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
957 mapped_at_creation: false,
958 });
959
960 Self {
961 skinning_compute_pipeline: pipes.skinning_compute_pipeline.clone(),
962 skinning_bgl0: pipes.skinning_bgl0.clone(),
963 skinning_bgl1: pipes.skinning_bgl1.clone(),
964 skinning_bgl2: pipes.skinning_bgl2.clone(),
965 skinning_buffer_pairs: Vec::new(),
966 skinning_joint_matrices,
967 skinning_morph_positions,
968 skinning_morph_weights,
969 registry,
970 ai_material_rx: None,
971 active_offscreens: Vec::new(),
972 effect_pipelines: std::collections::HashMap::new(),
973 effect_params_buffer: device.create_buffer(&wgpu::BufferDescriptor {
974 label: Some("Dummy Effect Buffer"),
975 size: 256,
976 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
977 mapped_at_creation: false,
978 }),
979 effect_params_bind_group: device.create_bind_group(&wgpu::BindGroupDescriptor {
980 label: Some("Dummy Effect Bind Group"),
981 layout: &device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
982 label: None,
983 entries: &[],
984 }),
985 entries: &[],
986 }),
987 linear_sampler: device.create_sampler(&wgpu::SamplerDescriptor {
988 label: Some("Linear Sampler"),
989 address_mode_u: wgpu::AddressMode::ClampToEdge,
990 address_mode_v: wgpu::AddressMode::ClampToEdge,
991 address_mode_w: wgpu::AddressMode::ClampToEdge,
992 mag_filter: wgpu::FilterMode::Linear,
993 min_filter: wgpu::FilterMode::Linear,
994 mipmap_filter: wgpu::MipmapFilterMode::Linear,
995 ..Default::default()
996 }),
997 instance,
998 adapter,
999 device: device.clone(),
1000 queue: queue.clone(),
1001
1002 surfaces,
1003 current_window,
1004 headless_context,
1005 pipeline: pipes.pipeline,
1006 opaque_pipeline: pipes.opaque_pipeline,
1007 ui_pipeline: pipes.ui_pipeline,
1008 glass_pipeline: pipes.glass_pipeline,
1009 pbr_pipeline: pipes.pbr_pipeline,
1010 transparent_pipeline: pipes.transparent_pipeline,
1011 shadow_pipeline: pipes.shadow_pipeline,
1012 bloom_extract_pipeline: pipes.bloom_extract_pipeline,
1013 copy_pipeline: pipes.copy_pipeline,
1014 composite_pipeline: pipes.composite_pipeline,
1015 env_bind_group_layout,
1016 mega_heim_tex,
1017 mega_heim_bind_group,
1018 config: crate::subsystems::RendererConfig::default(),
1019 text: crate::types::TextSubsystem::forge(NonZeroUsize::new(8192).unwrap()),
1020 heim_packer: SkylinePacker::new(4096, 4096),
1021 image_uv_registry: {
1022 let mut cache = LruCache::new(NonZeroUsize::new(256).unwrap());
1023 cache.put(
1024 "__mega_heim".to_string(),
1025 cvkg_core::Rect {
1026 x: 0.0,
1027 y: 0.0,
1028 width: 1.0,
1029 height: 1.0,
1030 },
1031 );
1032 cache
1033 },
1034 texture_registry,
1035 texture_views: texture_views_list,
1036 dummy_sampler,
1037 dummy_view: dummy_view.clone(),
1038 dummy_depth_view,
1039 dummy_depth_view_msaa,
1040 svg: crate::types::SvgSubsystem::forge(
1041 &device,
1042 &queue,
1043 NonZeroUsize::new(512).unwrap(),
1044 NonZeroUsize::new(512).unwrap(),
1045 ),
1046 dummy_texture_bind_group,
1047 gradient_stop_texture: dummy_texture.clone(),
1048 gradient_stop_texture_view: dummy_view.clone(),
1049 gradient_bind_group,
1050 gradient_texture_cache: std::collections::HashMap::new(),
1051 gradient_stops_hash: 0,
1052 gradient_bind_group_layout,
1053 dummy_env_bind_group,
1054 texture_bind_group_layout,
1055 texture_bind_groups,
1056 shared_elements: LruCache::new(NonZeroUsize::new(1024).unwrap()),
1057 geometry_buffers,
1058 vertices: Vec::with_capacity(MAX_VERTICES),
1059 indices: Vec::with_capacity(MAX_INDICES),
1060 instance_data: Vec::with_capacity(MAX_VERTICES / 4),
1061 instance_data_3d: Vec::with_capacity(MAX_VERTICES / 4),
1062 instance_buffer_3d: Some(instance_buffer_3d),
1063 draw_calls: Vec::new(),
1064 current_texture_id: None,
1065 current_panel_id: None,
1066 panel_stack: Vec::new(),
1067 panel_vdoms: HashMap::new(),
1068 world_space_panels: Vec::new(),
1069 opacity_stack: vec![1.0],
1070 clip_stack: Vec::new(),
1071 slice_stack: Vec::new(),
1072 shadow_stack: Vec::new(),
1073 theme_buffer,
1074 scene_buffer,
1075 berserker_bind_group,
1076 berserker_bind_group_layout,
1077 start_time: std::time::Instant::now(),
1078 current_theme,
1079 current_scene,
1080 background_pipeline: pipes.background_pipeline,
1081 current_z: 0.0,
1082 default_background_color: [0.02, 0.02, 0.05, 1.0],
1083 app_drew_background: false,
1084 frame_rendered: false,
1085 current_draw_order: 0,
1086 telemetry: cvkg_core::TelemetryData::default(),
1087 last_frame_start: std::time::Instant::now(),
1088 last_redraw_start: std::time::Instant::now(),
1089 frame_budget: cvkg_core::FrameBudget::default(),
1090 capture_staging_buffer: None,
1091 compositor_index_cursor: 0,
1092 vram_buffers_bytes: 0,
1093 vram_textures_bytes: 0,
1094 _debug_layout: false,
1095 transform_stack: Vec::new(),
1096 transform_stack_3d: Vec::new(),
1097 redraw_requested: false,
1098 skuld_queries,
1099 skuld_buffer,
1100 skuld_read_buffer,
1101 skuld_period,
1102 last_gpu_time_ns: 0,
1103 particle_compute_pipeline: pipes.particle_compute_pipeline,
1104 particle_compute_bgl: pipes.particle_compute_bgl,
1105 particle_buffer: pipes.particle_buffer,
1106 particle_uniform_buffer: pipes.particle_uniform_buffer,
1107 particles: crate::types::ParticleSubsystem::forge(),
1108 particle_render_pipeline: pipes.particle_render_pipeline,
1109 particle_render_bgl: pipes.particle_render_bgl,
1110 particle_render_bind_group: None,
1111 particle_compute_bind_group: None,
1112 vnode_stack: Vec::new(),
1113 event_handlers: std::collections::HashMap::new(),
1114 staging_belt,
1115 staging_command_buffers: Vec::new(),
1116 glass_output_bind_group_layout,
1117 current_draw_material: cvkg_core::DrawMaterial::Opaque,
1118 portal_regions: std::collections::VecDeque::new(),
1119 cached_graph_plan: None,
1120 material_compilation_hash: 0,
1121 memo_cache: std::collections::HashMap::new(),
1122 frame_generation: 0,
1123 quality_level: QualityLevel::default(),
1124 pipeline_cache,
1125 bloom_enabled: true,
1126 volumetric_enabled: false,
1127 path_geometry_cache: lru::LruCache::new(NonZeroUsize::new(64).unwrap()),
1128 color_blind_mode: crate::color_blindness::ColorBlindMode::Normal,
1129 color_blind_intensity: 1.0,
1130 color_blind_pipeline: pipes.color_blind_pipeline,
1131 volumetric_pipeline: pipes.volumetric_pipeline,
1132 volumetric_bind_group_layout: pipes.volumetric_bind_group_layout,
1133 volumetric_uniform_buffer: pipes.volumetric_uniform_buffer,
1134 csm_buffer,
1135 pbr_material_bind_group_layout,
1136 volumetric_depth_sampler: pipes.volumetric_depth_sampler,
1137 hologram_instances: Vec::new(),
1138 color_blind_bind_group_layout: pipes.color_blind_bind_group_layout,
1139 color_blind_uniform_buffer: pipes.color_blind_uniform_buffer,
1140 sampler: pipes.sampler,
1141 kawase_down_pipeline: pipes.kawase_down_pipeline,
1142 kawase_up_pipeline: pipes.kawase_up_pipeline,
1143 kawase_bind_group_layout: pipes.kawase_bind_group_layout,
1144 kawase_uniform: pipes.kawase_uniform,
1145 kawase_uniform_buffers: pipes.kawase_uniform_buffers,
1146 bind_group_cache: std::sync::Mutex::new(std::collections::HashMap::new()),
1147 texture_view_cache: std::sync::Mutex::new(std::collections::HashMap::new()),
1148
1149 blur_pipeline: None,
1151 blur_uniform: None,
1152 blur_bind_group_layout: None,
1153 blend_pipeline: None,
1154 blend_bind_group_layout: None,
1155 flood_pipeline: None,
1156 copy_bind_group_layout: None,
1157
1158 render_error_count: 0,
1160 has_fatal_error: false,
1161
1162 shadow_map_texture: Some(shadow_map_texture),
1164 shadow_map_view: Some(shadow_map_view),
1165 shadow_sampler: Some(shadow_sampler),
1166 shadow_light_vp: glam::Mat4::IDENTITY,
1167 shadow_map_size: 1024,
1168 shadow_bias: 0.005,
1169
1170 pending_directional_light: None,
1172 pending_mesh_instances_3d: Vec::new(),
1173 pending_transparent_instances_3d: Vec::new(),
1174 pending_scene_radius: 100.0,
1175
1176 theme_stack: Vec::new(),
1177 portal_theme_stack: Vec::new(),
1178 }
1179 }
1180}