Skip to main content

cvkg_render_gpu/renderer/
init.rs

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    /// forge -- Initializes the Surtr GPU renderer from a winit window.
24    ///
25    /// This method performs the following:
26    /// 1. Negotiates a wgpu surface and adapter.
27    /// 2. Forges the Muspelheim multi-pass pipeline layouts.
28    /// 3. Initializes the Berserker state buffers and texture registries.
29    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        // Request adapter with robust multi-stage fallback for Bumblebee/Optimus compatibility
45        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        // P1-26: detect GPU vendor for logging and future
130        // capability-based shader selection.
131        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(); // Fallbacks for WebGL
150        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        // Enable validation layer in debug builds for better error reporting
157        #[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        // Ensure we have valid dimensions - Wayland may return 0 for not-yet-committed surfaces
193        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        // HDR/Display P3 surface format selection:
197        // WHY: Tahoe requires wide-gamut Display P3 or HDR (Rgba16Float) color spaces when available.
198        // CONTRACT: Uses select_best_surface_format to safely fall back on mobile/legacy GPUs.
199        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    /// Internal rendering pipeline constructor.
276    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 {
338                device.create_pipeline_cache(&wgpu::PipelineCacheDescriptor {
339                    label: Some("CVKG Pipeline Cache"),
340                    data: cache_data.as_deref(),
341                    fallback: true,
342                })
343            })
344        } else {
345            tracing::debug!(
346                "[GPU] device does not expose PIPELINE_CACHE; compiling pipelines without cache"
347            );
348            None
349        };
350        let materials_generated = crate::material::generate_builtins_wgsl();
351
352        let wgsl_src = format!(
353            "{}{}{}{}{}{}",
354            WGSL_COMMON,
355            WGSL_SHAPES,
356            WGSL_BIFROST,
357            WGSL_BLOOM,
358            WGSL_COLOR_BLIND,
359            materials_generated
360        );
361        let wgsl_opaque = format!(
362            "{}{}{}{}{}{}",
363            WGSL_COMMON,
364            WGSL_MATERIAL_OPAQUE,
365            WGSL_BIFROST,
366            WGSL_BLOOM,
367            WGSL_COLOR_BLIND,
368            materials_generated
369        );
370        let wgsl_glass = format!(
371            "{}{}{}{}{}{}",
372            WGSL_COMMON,
373            WGSL_MATERIAL_GLASS,
374            WGSL_BIFROST,
375            WGSL_BLOOM,
376            WGSL_COLOR_BLIND,
377            materials_generated
378        );
379        let wgsl_pbr = format!(
380            "{}{}{}{}{}{}",
381            WGSL_COMMON,
382            WGSL_MATERIAL_PBR,
383            WGSL_BIFROST,
384            WGSL_BLOOM,
385            WGSL_COLOR_BLIND,
386            materials_generated
387        );
388        let wgsl_shadow = format!(
389            "{}{}{}",
390            WGSL_COMMON, WGSL_MATERIAL_SHADOW, materials_generated
391        );
392
393        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
394            label: Some("Surtr Main Shader"),
395            source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Owned(wgsl_src)),
396        });
397
398        #[cfg(target_arch = "wasm32")]
399        let texture_array_count: Option<std::num::NonZeroU32> = None;
400        #[cfg(not(target_arch = "wasm32"))]
401        let texture_array_count: Option<std::num::NonZeroU32> = std::num::NonZeroU32::new(32);
402
403        let texture_bind_group_layout =
404            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
405                entries: &[
406                    wgpu::BindGroupLayoutEntry {
407                        binding: 0,
408                        visibility: wgpu::ShaderStages::FRAGMENT,
409                        ty: wgpu::BindingType::Texture {
410                            multisampled: false,
411                            view_dimension: wgpu::TextureViewDimension::D2,
412                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
413                        },
414                        count: texture_array_count,
415                    },
416                    wgpu::BindGroupLayoutEntry {
417                        binding: 1,
418                        visibility: wgpu::ShaderStages::FRAGMENT,
419                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
420                        count: None,
421                    },
422                ],
423                label: Some("Niflheim Texture Bind Group Layout"),
424            });
425
426        let env_bind_group_layout =
427            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
428                entries: &[
429                    wgpu::BindGroupLayoutEntry {
430                        binding: 0,
431                        visibility: wgpu::ShaderStages::FRAGMENT,
432                        ty: wgpu::BindingType::Texture {
433                            multisampled: false,
434                            view_dimension: wgpu::TextureViewDimension::D2,
435                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
436                        },
437                        count: None,
438                    },
439                    wgpu::BindGroupLayoutEntry {
440                        binding: 1,
441                        visibility: wgpu::ShaderStages::FRAGMENT,
442                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
443                        count: None,
444                    },
445                ],
446                label: Some("Surtr Environment Bind Group Layout"),
447            });
448
449        let berserker_bind_group_layout =
450            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
451                entries: &[
452                    wgpu::BindGroupLayoutEntry {
453                        binding: 0,
454                        visibility: wgpu::ShaderStages::FRAGMENT,
455                        ty: wgpu::BindingType::Buffer {
456                            ty: wgpu::BufferBindingType::Uniform,
457                            has_dynamic_offset: false,
458                            min_binding_size: None,
459                        },
460                        count: None,
461                    },
462                    wgpu::BindGroupLayoutEntry {
463                        binding: 1,
464                        visibility: wgpu::ShaderStages::FRAGMENT | wgpu::ShaderStages::VERTEX,
465                        ty: wgpu::BindingType::Buffer {
466                            ty: wgpu::BufferBindingType::Uniform,
467                            has_dynamic_offset: false,
468                            min_binding_size: None,
469                        },
470                        count: None,
471                    },
472                    wgpu::BindGroupLayoutEntry {
473                        binding: 2,
474                        visibility: wgpu::ShaderStages::FRAGMENT | wgpu::ShaderStages::VERTEX,
475                        ty: wgpu::BindingType::Buffer {
476                            ty: wgpu::BufferBindingType::Uniform,
477                            has_dynamic_offset: false,
478                            min_binding_size: None,
479                        },
480                        count: None,
481                    },
482                ],
483                label: Some("Surtr Berserker Bind Group Layout"),
484            });
485
486        let gradient_bind_group_layout =
487            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
488                entries: &[
489                    wgpu::BindGroupLayoutEntry {
490                        binding: 0,
491                        visibility: wgpu::ShaderStages::FRAGMENT,
492                        ty: wgpu::BindingType::Texture {
493                            multisampled: false,
494                            view_dimension: wgpu::TextureViewDimension::D2,
495                            sample_type: wgpu::TextureSampleType::Float { filterable: false },
496                        },
497                        count: None,
498                    },
499                    wgpu::BindGroupLayoutEntry {
500                        binding: 1,
501                        visibility: wgpu::ShaderStages::FRAGMENT,
502                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
503                        count: None,
504                    },
505                ],
506                label: Some("Surtr Gradient Bind Group Layout"),
507            });
508
509        let pbr_material_bind_group_layout =
510            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
511                entries: &[
512                    // Binding 0: Shadow Map Texture Array (depth array)
513                    wgpu::BindGroupLayoutEntry {
514                        binding: 0,
515                        visibility: wgpu::ShaderStages::FRAGMENT,
516                        ty: wgpu::BindingType::Texture {
517                            multisampled: false,
518                            view_dimension: wgpu::TextureViewDimension::D2Array,
519                            sample_type: wgpu::TextureSampleType::Depth,
520                        },
521                        count: None,
522                    },
523                    // Binding 1: Shadow Sampler (comparison)
524                    wgpu::BindGroupLayoutEntry {
525                        binding: 1,
526                        visibility: wgpu::ShaderStages::FRAGMENT,
527                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison),
528                        count: None,
529                    },
530                    // Binding 8: IBL Texture (standard 2D)
531                    wgpu::BindGroupLayoutEntry {
532                        binding: 8,
533                        visibility: wgpu::ShaderStages::FRAGMENT,
534                        ty: wgpu::BindingType::Texture {
535                            multisampled: false,
536                            view_dimension: wgpu::TextureViewDimension::D2,
537                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
538                        },
539                        count: None,
540                    },
541                    // Binding 9: IBL Sampler (filtering)
542                    wgpu::BindGroupLayoutEntry {
543                        binding: 9,
544                        visibility: wgpu::ShaderStages::FRAGMENT,
545                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
546                        count: None,
547                    },
548                    // Binding 6: Normal Map Texture (standard 2D)
549                    wgpu::BindGroupLayoutEntry {
550                        binding: 6,
551                        visibility: wgpu::ShaderStages::FRAGMENT,
552                        ty: wgpu::BindingType::Texture {
553                            multisampled: false,
554                            view_dimension: wgpu::TextureViewDimension::D2,
555                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
556                        },
557                        count: None,
558                    },
559                    // Binding 7: Normal Map Sampler (filtering)
560                    wgpu::BindGroupLayoutEntry {
561                        binding: 7,
562                        visibility: wgpu::ShaderStages::FRAGMENT,
563                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
564                        count: None,
565                    },
566                ],
567                label: Some("Surtr PBR Material Bind Group Layout"),
568            });
569
570        let pipes = compile_render_pipelines(
571            &device,
572            format,
573            pipeline_cache.as_ref(),
574            &texture_bind_group_layout,
575            &env_bind_group_layout,
576            &berserker_bind_group_layout,
577            &gradient_bind_group_layout,
578            &pbr_material_bind_group_layout,
579            &shader,
580            wgsl_opaque.as_str(),
581            wgsl_glass.as_str(),
582            wgsl_pbr.as_str(),
583            wgsl_shadow.as_str(),
584            &queue,
585        );
586
587        // Forge the Mega-Heim (4096x4096 RGBA for production batching)
588        let mega_heim_tex = device.create_texture(&wgpu::TextureDescriptor {
589            label: Some("Surtr Mega-Heim"),
590            size: wgpu::Extent3d {
591                width: 4096,
592                height: 4096,
593                depth_or_array_layers: 1,
594            },
595            mip_level_count: 1,
596            sample_count: 1,
597            dimension: wgpu::TextureDimension::D2,
598            format: wgpu::TextureFormat::Rgba8UnormSrgb,
599            usage: wgpu::TextureUsages::TEXTURE_BINDING
600                | wgpu::TextureUsages::COPY_DST
601                | wgpu::TextureUsages::COPY_SRC,
602            view_formats: &[],
603        });
604        let mega_heim_view_obj = mega_heim_tex.create_view(&wgpu::TextureViewDescriptor::default());
605        let text_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
606            address_mode_u: wgpu::AddressMode::ClampToEdge,
607            address_mode_v: wgpu::AddressMode::ClampToEdge,
608            mag_filter: wgpu::FilterMode::Linear,
609            min_filter: wgpu::FilterMode::Linear,
610            ..Default::default()
611        });
612
613        // Forge the Niflheim Dummy Texture (1x1 White)
614        let dummy_size = wgpu::Extent3d {
615            width: 1,
616            height: 1,
617            depth_or_array_layers: 1,
618        };
619        let dummy_texture = device.create_texture(&wgpu::TextureDescriptor {
620            label: Some("Niflheim Dummy Texture"),
621            size: dummy_size,
622            mip_level_count: 1,
623            sample_count: 1,
624            dimension: wgpu::TextureDimension::D2,
625            format: wgpu::TextureFormat::Rgba8UnormSrgb,
626            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
627            view_formats: &[],
628        });
629        queue.write_texture(
630            wgpu::TexelCopyTextureInfo {
631                texture: &dummy_texture,
632                mip_level: 0,
633                origin: wgpu::Origin3d::ZERO,
634                aspect: wgpu::TextureAspect::All,
635            },
636            &[255, 255, 255, 255],
637            wgpu::TexelCopyBufferLayout {
638                offset: 0,
639                bytes_per_row: Some(4),
640                rows_per_image: Some(1),
641            },
642            dummy_size,
643        );
644
645        let dummy_view = dummy_texture.create_view(&wgpu::TextureViewDescriptor::default());
646
647        // Non-filtering sampler required by the gradient bind group layout.
648        let gradient_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
649            address_mode_u: wgpu::AddressMode::ClampToEdge,
650            address_mode_v: wgpu::AddressMode::ClampToEdge,
651            address_mode_w: wgpu::AddressMode::ClampToEdge,
652            mag_filter: wgpu::FilterMode::Nearest,
653            min_filter: wgpu::FilterMode::Nearest,
654            mipmap_filter: wgpu::MipmapFilterMode::Nearest,
655            ..Default::default()
656        });
657
658        // Gradient bind group: requires non-filterable texture + non-filtering sampler.
659        // The gradient layout expects Float { filterable: false } texture.
660        let gradient_dummy_texture = device.create_texture(&wgpu::TextureDescriptor {
661            label: Some("Gradient Dummy Texture"),
662            size: wgpu::Extent3d {
663                width: 1,
664                height: 1,
665                depth_or_array_layers: 1,
666            },
667            mip_level_count: 1,
668            sample_count: 1,
669            dimension: wgpu::TextureDimension::D2,
670            format: wgpu::TextureFormat::Rgba16Float,
671            usage: wgpu::TextureUsages::TEXTURE_BINDING,
672            view_formats: &[],
673        });
674        let gradient_dummy_view =
675            gradient_dummy_texture.create_view(&wgpu::TextureViewDescriptor::default());
676        let gradient_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
677            layout: &gradient_bind_group_layout,
678            entries: &[
679                wgpu::BindGroupEntry {
680                    binding: 0,
681                    resource: wgpu::BindingResource::TextureView(&gradient_dummy_view),
682                },
683                wgpu::BindGroupEntry {
684                    binding: 1,
685                    resource: wgpu::BindingResource::Sampler(&gradient_sampler),
686                },
687            ],
688            label: Some("Gradient Dummy Bind Group"),
689        });
690        let dummy_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
691            address_mode_u: wgpu::AddressMode::ClampToEdge,
692            address_mode_v: wgpu::AddressMode::ClampToEdge,
693            address_mode_w: wgpu::AddressMode::ClampToEdge,
694            mag_filter: wgpu::FilterMode::Linear,
695            min_filter: wgpu::FilterMode::Nearest,
696            mipmap_filter: wgpu::MipmapFilterMode::Nearest,
697            ..Default::default()
698        });
699
700        // Non-filtering sampler required by the gradient bind group layout.
701        let gradient_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
702            address_mode_u: wgpu::AddressMode::ClampToEdge,
703            address_mode_v: wgpu::AddressMode::ClampToEdge,
704            address_mode_w: wgpu::AddressMode::ClampToEdge,
705            mag_filter: wgpu::FilterMode::Nearest,
706            min_filter: wgpu::FilterMode::Nearest,
707            mipmap_filter: wgpu::MipmapFilterMode::Nearest,
708            ..Default::default()
709        });
710
711        let mut texture_views_list: Vec<wgpu::TextureView> =
712            (0..32).map(|_| dummy_view.clone()).collect();
713        texture_views_list[0] = mega_heim_view_obj.clone();
714
715        let views_refs: Vec<&wgpu::TextureView> = texture_views_list.iter().collect();
716        let mega_heim_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
717            layout: &texture_bind_group_layout,
718            entries: &[
719                wgpu::BindGroupEntry {
720                    binding: 0,
721                    resource: wgpu::BindingResource::TextureViewArray(&views_refs),
722                },
723                wgpu::BindGroupEntry {
724                    binding: 1,
725                    resource: wgpu::BindingResource::Sampler(&text_sampler),
726                },
727            ],
728            label: Some("Mega-Heim Bind Group"),
729        });
730
731        let dummy_views_refs: Vec<&wgpu::TextureView> = (0..32).map(|_| &dummy_view).collect();
732        let dummy_texture_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
733            layout: &texture_bind_group_layout,
734            entries: &[
735                wgpu::BindGroupEntry {
736                    binding: 0,
737                    resource: wgpu::BindingResource::TextureViewArray(&dummy_views_refs),
738                },
739                wgpu::BindGroupEntry {
740                    binding: 1,
741                    resource: wgpu::BindingResource::Sampler(&dummy_sampler),
742                },
743            ],
744            label: Some("Dummy Texture Bind Group"),
745        });
746
747        let dummy_env_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
748            layout: &env_bind_group_layout,
749            entries: &[
750                wgpu::BindGroupEntry {
751                    binding: 0,
752                    resource: wgpu::BindingResource::TextureView(&dummy_view),
753                },
754                wgpu::BindGroupEntry {
755                    binding: 1,
756                    resource: wgpu::BindingResource::Sampler(&dummy_sampler),
757                },
758            ],
759            label: Some("Dummy Env Bind Group"),
760        });
761        let dummy_depth_tex = device.create_texture(&wgpu::TextureDescriptor {
762            label: Some("Surtr Dummy Depth Texture"),
763            size: wgpu::Extent3d {
764                width: 1,
765                height: 1,
766                depth_or_array_layers: 1,
767            },
768            mip_level_count: 1,
769            sample_count: 1,
770            dimension: wgpu::TextureDimension::D2,
771            format: wgpu::TextureFormat::Depth32Float,
772            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
773            view_formats: &[],
774        });
775        let dummy_depth_view = dummy_depth_tex.create_view(&wgpu::TextureViewDescriptor::default());
776
777        let dummy_depth_tex_msaa = device.create_texture(&wgpu::TextureDescriptor {
778            label: Some("Surtr Dummy Depth Texture MSAA"),
779            size: wgpu::Extent3d {
780                width: 1,
781                height: 1,
782                depth_or_array_layers: 1,
783            },
784            mip_level_count: 1,
785            sample_count: 4,
786            dimension: wgpu::TextureDimension::D2,
787            format: wgpu::TextureFormat::Depth32Float,
788            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
789            view_formats: &[],
790        });
791        let dummy_depth_view_msaa =
792            dummy_depth_tex_msaa.create_view(&wgpu::TextureViewDescriptor::default());
793
794        let shadow_map_size = 1024;
795        let shadow_map_texture = device.create_texture(&wgpu::TextureDescriptor {
796            label: Some("Surtr CSM Shadow Map Texture"),
797            size: wgpu::Extent3d {
798                width: shadow_map_size,
799                height: shadow_map_size,
800                depth_or_array_layers: 4, // 4 cascades
801            },
802            mip_level_count: 1,
803            sample_count: 1,
804            dimension: wgpu::TextureDimension::D2,
805            format: wgpu::TextureFormat::Depth32Float,
806            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
807            view_formats: &[],
808        });
809
810        let shadow_map_view = shadow_map_texture.create_view(&wgpu::TextureViewDescriptor {
811            label: Some("Surtr CSM Shadow Map View"),
812            dimension: Some(wgpu::TextureViewDimension::D2Array),
813            ..wgpu::TextureViewDescriptor::default()
814        });
815
816        let shadow_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
817            label: Some("Surtr CSM Shadow Sampler"),
818            address_mode_u: wgpu::AddressMode::ClampToEdge,
819            address_mode_v: wgpu::AddressMode::ClampToEdge,
820            address_mode_w: wgpu::AddressMode::ClampToEdge,
821            mag_filter: wgpu::FilterMode::Linear,
822            min_filter: wgpu::FilterMode::Linear,
823            compare: Some(wgpu::CompareFunction::LessEqual),
824            ..wgpu::SamplerDescriptor::default()
825        });
826
827        let mut texture_registry = LruCache::new(NonZeroUsize::new(31).unwrap());
828        let mut texture_bind_groups = Vec::new();
829
830        // Index 0 is permanently reserved for the Mega-Heim atlas. Loaded images start at 1.
831        texture_registry.put("__mega_heim".to_string(), 0);
832        texture_bind_groups.push(mega_heim_bind_group.clone());
833
834        let geometry_buffers =
835            crate::types::GeometryBuffers::forge(&device, MAX_VERTICES, MAX_INDICES);
836
837        let instance_buffer_3d = device.create_buffer(&wgpu::BufferDescriptor {
838            label: Some("Surtr 3D Instance Buffer"),
839            size: (MAX_VERTICES / 4 * std::mem::size_of::<crate::vertex::InstanceData3D>()) as u64,
840            usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
841            mapped_at_creation: false,
842        });
843
844        // Forge the Heart (Berserker Uniforms)
845        let current_theme = ColorTheme::default();
846        use wgpu::util::DeviceExt;
847        let theme_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
848            label: Some("Surtr Theme Buffer"),
849            contents: bytemuck::bytes_of(&current_theme),
850            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
851        });
852
853        let (width, height, scale_factor) = if let Some((ref window, _, ref config)) = surface_info
854        {
855            (config.width, config.height, window.scale_factor() as f32)
856        } else if let Some((w, h, _)) = headless_info {
857            (w, h, 1.0)
858        } else {
859            (1280, 720, 1.0)
860        };
861
862        let mut current_scene =
863            SceneUniforms::new(width as f32 / scale_factor, height as f32 / scale_factor);
864        current_scene.scale_factor = scale_factor;
865        let msaa_sample_count = QualityLevel::default().msaa_sample_count();
866        let scene_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
867            label: Some("Surtr Scene Buffer"),
868            contents: bytemuck::bytes_of(&current_scene),
869            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
870        });
871
872        let csm_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
873            label: Some("Surtr CSM Buffer"),
874            contents: bytemuck::bytes_of(&cvkg_core::render_tier::CsmUniforms::default()),
875            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
876        });
877
878        let berserker_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
879            layout: &berserker_bind_group_layout,
880            entries: &[
881                wgpu::BindGroupEntry {
882                    binding: 0,
883                    resource: theme_buffer.as_entire_binding(),
884                },
885                wgpu::BindGroupEntry {
886                    binding: 1,
887                    resource: scene_buffer.as_entire_binding(),
888                },
889                wgpu::BindGroupEntry {
890                    binding: 2,
891                    resource: csm_buffer.as_entire_binding(),
892                },
893            ],
894            label: Some("Surtr Berserker Bind Group"),
895        });
896
897        let mut registry = crate::kvasir::registry::ResourceRegistry::new();
898        let mut surfaces = std::collections::HashMap::new();
899        let mut current_window = None;
900        let mut headless_context = None;
901
902        if let Some((window, surface, config)) = surface_info {
903            let window_id = window.id();
904            let ctx = create_surface_context(
905                &device,
906                surface,
907                config,
908                &env_bind_group_layout,
909                &texture_bind_group_layout,
910                scale_factor,
911                msaa_sample_count,
912                &mut registry,
913            );
914            surfaces.insert(window_id, ctx);
915            current_window = Some(window_id);
916        } else if let Some((w, h, f)) = headless_info {
917            headless_context = Some(create_headless_context(
918                &device,
919                w,
920                h,
921                f,
922                &env_bind_group_layout,
923                &texture_bind_group_layout,
924                &mut registry,
925                msaa_sample_count,
926            ));
927        }
928
929        let staging_belt = wgpu::util::StagingBelt::new((*device).clone(), 1024 * 1024);
930
931        let glass_output_bind_group_layout = env_bind_group_layout.clone();
932
933        Self {
934            registry,
935            ai_material_rx: None,
936            active_offscreens: Vec::new(),
937            effect_pipelines: std::collections::HashMap::new(),
938            effect_params_buffer: device.create_buffer(&wgpu::BufferDescriptor {
939                label: Some("Dummy Effect Buffer"),
940                size: 256,
941                usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
942                mapped_at_creation: false,
943            }),
944            effect_params_bind_group: device.create_bind_group(&wgpu::BindGroupDescriptor {
945                label: Some("Dummy Effect Bind Group"),
946                layout: &device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
947                    label: None,
948                    entries: &[],
949                }),
950                entries: &[],
951            }),
952            linear_sampler: device.create_sampler(&wgpu::SamplerDescriptor {
953                label: Some("Linear Sampler"),
954                address_mode_u: wgpu::AddressMode::ClampToEdge,
955                address_mode_v: wgpu::AddressMode::ClampToEdge,
956                address_mode_w: wgpu::AddressMode::ClampToEdge,
957                mag_filter: wgpu::FilterMode::Linear,
958                min_filter: wgpu::FilterMode::Linear,
959                mipmap_filter: wgpu::MipmapFilterMode::Linear,
960                ..Default::default()
961            }),
962            instance,
963            adapter,
964            device: device.clone(),
965            queue: queue.clone(),
966
967            surfaces,
968            current_window,
969            headless_context,
970            pipeline: pipes.pipeline,
971            opaque_pipeline: pipes.opaque_pipeline,
972            ui_pipeline: pipes.ui_pipeline,
973            glass_pipeline: pipes.glass_pipeline,
974            pbr_pipeline: pipes.pbr_pipeline,
975            transparent_pipeline: pipes.transparent_pipeline,
976            shadow_pipeline: pipes.shadow_pipeline,
977            bloom_extract_pipeline: pipes.bloom_extract_pipeline,
978            copy_pipeline: pipes.copy_pipeline,
979            composite_pipeline: pipes.composite_pipeline,
980            env_bind_group_layout,
981            mega_heim_tex,
982            mega_heim_bind_group,
983            config: crate::subsystems::RendererConfig::default(),
984            text: crate::types::TextSubsystem::forge(NonZeroUsize::new(8192).unwrap()),
985            heim_packer: SkylinePacker::new(4096, 4096),
986            image_uv_registry: {
987                let mut cache = LruCache::new(NonZeroUsize::new(256).unwrap());
988                cache.put(
989                    "__mega_heim".to_string(),
990                    cvkg_core::Rect {
991                        x: 0.0,
992                        y: 0.0,
993                        width: 1.0,
994                        height: 1.0,
995                    },
996                );
997                cache
998            },
999            texture_registry,
1000            texture_views: texture_views_list,
1001            dummy_sampler,
1002            dummy_view: dummy_view.clone(),
1003            dummy_depth_view,
1004            dummy_depth_view_msaa,
1005            svg: crate::types::SvgSubsystem::forge(
1006                &device,
1007                &queue,
1008                NonZeroUsize::new(512).unwrap(),
1009                NonZeroUsize::new(512).unwrap(),
1010            ),
1011            dummy_texture_bind_group,
1012            gradient_stop_texture: dummy_texture.clone(),
1013            gradient_stop_texture_view: dummy_view.clone(),
1014            gradient_bind_group,
1015            gradient_texture_cache: std::collections::HashMap::new(),
1016            gradient_stops_hash: 0,
1017            gradient_bind_group_layout,
1018            dummy_env_bind_group,
1019            texture_bind_group_layout,
1020            texture_bind_groups,
1021            shared_elements: LruCache::new(NonZeroUsize::new(1024).unwrap()),
1022            geometry_buffers,
1023            vertices: Vec::with_capacity(MAX_VERTICES),
1024            indices: Vec::with_capacity(MAX_INDICES),
1025            instance_data: Vec::with_capacity(MAX_VERTICES / 4),
1026            instance_data_3d: Vec::with_capacity(MAX_VERTICES / 4),
1027            instance_buffer_3d: Some(instance_buffer_3d),
1028            draw_calls: Vec::new(),
1029            current_texture_id: None,
1030            current_panel_id: None,
1031            panel_stack: Vec::new(),
1032            panel_vdoms: HashMap::new(),
1033            world_space_panels: Vec::new(),
1034            opacity_stack: vec![1.0],
1035            clip_stack: Vec::new(),
1036            slice_stack: Vec::new(),
1037            shadow_stack: Vec::new(),
1038            theme_buffer,
1039            scene_buffer,
1040            berserker_bind_group,
1041            berserker_bind_group_layout,
1042            start_time: std::time::Instant::now(),
1043            current_theme,
1044            current_scene,
1045            background_pipeline: pipes.background_pipeline,
1046            current_z: 0.0,
1047            default_background_color: [0.02, 0.02, 0.05, 1.0],
1048            app_drew_background: false,
1049            frame_rendered: false,
1050            current_draw_order: 0,
1051            telemetry: cvkg_core::TelemetryData::default(),
1052            last_frame_start: std::time::Instant::now(),
1053            last_redraw_start: std::time::Instant::now(),
1054            frame_budget: cvkg_core::FrameBudget::default(),
1055            capture_staging_buffer: None,
1056            compositor_index_cursor: 0,
1057            vram_buffers_bytes: 0,
1058            vram_textures_bytes: 0,
1059            _debug_layout: false,
1060            transform_stack: Vec::new(),
1061            transform_stack_3d: Vec::new(),
1062            redraw_requested: false,
1063            skuld_queries,
1064            skuld_buffer,
1065            skuld_read_buffer,
1066            skuld_period,
1067            last_gpu_time_ns: 0,
1068            particle_compute_pipeline: pipes.particle_compute_pipeline,
1069            particle_compute_bgl: pipes.particle_compute_bgl,
1070            particle_buffer: pipes.particle_buffer,
1071            particle_uniform_buffer: pipes.particle_uniform_buffer,
1072            particles: crate::types::ParticleSubsystem::forge(),
1073            particle_render_pipeline: pipes.particle_render_pipeline,
1074            particle_render_bgl: pipes.particle_render_bgl,
1075            particle_render_bind_group: None,
1076            particle_compute_bind_group: None,
1077            vnode_stack: Vec::new(),
1078            event_handlers: std::collections::HashMap::new(),
1079            staging_belt,
1080            staging_command_buffers: Vec::new(),
1081            glass_output_bind_group_layout,
1082            current_draw_material: cvkg_core::DrawMaterial::Opaque,
1083            portal_regions: std::collections::VecDeque::new(),
1084            cached_graph_plan: None,
1085            material_compilation_hash: 0,
1086            memo_cache: std::collections::HashMap::new(),
1087            frame_generation: 0,
1088            quality_level: QualityLevel::default(),
1089            pipeline_cache,
1090            bloom_enabled: true,
1091            volumetric_enabled: false,
1092            path_geometry_cache: lru::LruCache::new(NonZeroUsize::new(64).unwrap()),
1093            color_blind_mode: crate::color_blindness::ColorBlindMode::Normal,
1094            color_blind_intensity: 1.0,
1095            color_blind_pipeline: pipes.color_blind_pipeline,
1096            volumetric_pipeline: pipes.volumetric_pipeline,
1097            volumetric_bind_group_layout: pipes.volumetric_bind_group_layout,
1098            volumetric_uniform_buffer: pipes.volumetric_uniform_buffer,
1099            csm_buffer,
1100            pbr_material_bind_group_layout,
1101            volumetric_depth_sampler: pipes.volumetric_depth_sampler,
1102            hologram_instances: Vec::new(),
1103            color_blind_bind_group_layout: pipes.color_blind_bind_group_layout,
1104            color_blind_uniform_buffer: pipes.color_blind_uniform_buffer,
1105            sampler: pipes.sampler,
1106            kawase_down_pipeline: pipes.kawase_down_pipeline,
1107            kawase_up_pipeline: pipes.kawase_up_pipeline,
1108            kawase_bind_group_layout: pipes.kawase_bind_group_layout,
1109            kawase_uniform: pipes.kawase_uniform,
1110            kawase_uniform_buffers: pipes.kawase_uniform_buffers,
1111            bind_group_cache: std::sync::Mutex::new(std::collections::HashMap::new()),
1112            texture_view_cache: std::sync::Mutex::new(std::collections::HashMap::new()),
1113
1114            // SVG Filter Engine Resources (initialized lazily on first use)
1115            blur_pipeline: None,
1116            blur_uniform: None,
1117            blur_bind_group_layout: None,
1118            blend_pipeline: None,
1119            blend_bind_group_layout: None,
1120            flood_pipeline: None,
1121            copy_bind_group_layout: None,
1122
1123            // Error tracking
1124            render_error_count: 0,
1125            has_fatal_error: false,
1126
1127            // Shadow map resources
1128            shadow_map_texture: Some(shadow_map_texture),
1129            shadow_map_view: Some(shadow_map_view),
1130            shadow_sampler: Some(shadow_sampler),
1131            shadow_light_vp: glam::Mat4::IDENTITY,
1132            shadow_map_size: 1024,
1133            shadow_bias: 0.005,
1134
1135            // 3D mesh staging
1136            pending_directional_light: None,
1137            pending_mesh_instances_3d: Vec::new(),
1138            pending_transparent_instances_3d: Vec::new(),
1139            pending_scene_radius: 100.0,
1140
1141            theme_stack: Vec::new(),
1142            portal_theme_stack: Vec::new(),
1143        }
1144    }
1145}