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_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        log::info!("[Surtr] Renderer backend: GpuRenderer (wgpu)");
43
44        // Request adapter with robust multi-stage fallback for Bumblebee/Optimus compatibility
45        log::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            log::info!("[GPU] Available adapters:");
53            for a in &adapters {
54                let info = a.get_info();
55                log::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                    log::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                log::info!(
79                    "[GPU] Forced adapter selection via WGPU_ADAPTER_NAME='{}'",
80                    filter
81                );
82            } else {
83                log::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            log::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            log::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        log::info!(
134            "[GPU] Selected adapter: {} ({:?}) on backend: {:?} -- detected as {}",
135            info.name,
136            info.device_type,
137            info.backend,
138            caps.vendor
139        );
140        log::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            log::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            log::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        log::info!(
202            "[GPU] Available present modes: {:?}",
203            surface_caps.present_modes
204        );
205        log::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            log::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            log::info!("[GPU] Selected: Mailbox (no vsync)");
221            wgpu::PresentMode::Mailbox
222        } else {
223            log::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        log::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        log::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        log::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                    log::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            log::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
380        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
381            label: Some("Surtr Main Shader"),
382            source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Owned(wgsl_src)),
383        });
384
385        #[cfg(target_arch = "wasm32")]
386        let texture_array_count: Option<std::num::NonZeroU32> = None;
387        #[cfg(not(target_arch = "wasm32"))]
388        let texture_array_count: Option<std::num::NonZeroU32> = std::num::NonZeroU32::new(32);
389
390        let texture_bind_group_layout =
391            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
392                entries: &[
393                    wgpu::BindGroupLayoutEntry {
394                        binding: 0,
395                        visibility: wgpu::ShaderStages::FRAGMENT,
396                        ty: wgpu::BindingType::Texture {
397                            multisampled: false,
398                            view_dimension: wgpu::TextureViewDimension::D2,
399                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
400                        },
401                        count: texture_array_count,
402                    },
403                    wgpu::BindGroupLayoutEntry {
404                        binding: 1,
405                        visibility: wgpu::ShaderStages::FRAGMENT,
406                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
407                        count: None,
408                    },
409                ],
410                label: Some("Niflheim Texture Bind Group Layout"),
411            });
412
413        let env_bind_group_layout =
414            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
415                entries: &[
416                    wgpu::BindGroupLayoutEntry {
417                        binding: 0,
418                        visibility: wgpu::ShaderStages::FRAGMENT,
419                        ty: wgpu::BindingType::Texture {
420                            multisampled: false,
421                            view_dimension: wgpu::TextureViewDimension::D2,
422                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
423                        },
424                        count: None,
425                    },
426                    wgpu::BindGroupLayoutEntry {
427                        binding: 1,
428                        visibility: wgpu::ShaderStages::FRAGMENT,
429                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
430                        count: None,
431                    },
432                ],
433                label: Some("Surtr Environment Bind Group Layout"),
434            });
435
436        let berserker_bind_group_layout =
437            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
438                entries: &[
439                    wgpu::BindGroupLayoutEntry {
440                        binding: 0,
441                        visibility: wgpu::ShaderStages::FRAGMENT,
442                        ty: wgpu::BindingType::Buffer {
443                            ty: wgpu::BufferBindingType::Uniform,
444                            has_dynamic_offset: false,
445                            min_binding_size: None,
446                        },
447                        count: None,
448                    },
449                    wgpu::BindGroupLayoutEntry {
450                        binding: 1,
451                        visibility: wgpu::ShaderStages::FRAGMENT | wgpu::ShaderStages::VERTEX,
452                        ty: wgpu::BindingType::Buffer {
453                            ty: wgpu::BufferBindingType::Uniform,
454                            has_dynamic_offset: false,
455                            min_binding_size: None,
456                        },
457                        count: None,
458                    },
459                ],
460                label: Some("Surtr Berserker Bind Group Layout"),
461            });
462
463        let gradient_bind_group_layout =
464            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
465                entries: &[
466                    wgpu::BindGroupLayoutEntry {
467                        binding: 0,
468                        visibility: wgpu::ShaderStages::FRAGMENT,
469                        ty: wgpu::BindingType::Texture {
470                            multisampled: false,
471                            view_dimension: wgpu::TextureViewDimension::D2,
472                            sample_type: wgpu::TextureSampleType::Float { filterable: false },
473                        },
474                        count: None,
475                    },
476                    wgpu::BindGroupLayoutEntry {
477                        binding: 1,
478                        visibility: wgpu::ShaderStages::FRAGMENT,
479                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
480                        count: None,
481                    },
482                ],
483                label: Some("Surtr Gradient Bind Group Layout"),
484            });
485
486        let pipes = compile_render_pipelines(
487            &device,
488            format,
489            pipeline_cache.as_ref(),
490            &texture_bind_group_layout,
491            &env_bind_group_layout,
492            &berserker_bind_group_layout,
493            &gradient_bind_group_layout,
494            &shader,
495            wgsl_opaque.as_str(),
496            wgsl_glass.as_str(),
497            &queue,
498        );
499
500        // Forge the Mega-Heim (4096x4096 RGBA for production batching)
501        let mega_heim_tex = device.create_texture(&wgpu::TextureDescriptor {
502            label: Some("Surtr Mega-Heim"),
503            size: wgpu::Extent3d {
504                width: 4096,
505                height: 4096,
506                depth_or_array_layers: 1,
507            },
508            mip_level_count: 1,
509            sample_count: 1,
510            dimension: wgpu::TextureDimension::D2,
511            format: wgpu::TextureFormat::Rgba8UnormSrgb,
512            usage: wgpu::TextureUsages::TEXTURE_BINDING
513                | wgpu::TextureUsages::COPY_DST
514                | wgpu::TextureUsages::COPY_SRC,
515            view_formats: &[],
516        });
517        let mega_heim_view_obj = mega_heim_tex.create_view(&wgpu::TextureViewDescriptor::default());
518        let text_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
519            address_mode_u: wgpu::AddressMode::ClampToEdge,
520            address_mode_v: wgpu::AddressMode::ClampToEdge,
521            mag_filter: wgpu::FilterMode::Linear,
522            min_filter: wgpu::FilterMode::Linear,
523            ..Default::default()
524        });
525
526        // Forge the Niflheim Dummy Texture (1x1 White)
527        let dummy_size = wgpu::Extent3d {
528            width: 1,
529            height: 1,
530            depth_or_array_layers: 1,
531        };
532        let dummy_texture = device.create_texture(&wgpu::TextureDescriptor {
533            label: Some("Niflheim Dummy Texture"),
534            size: dummy_size,
535            mip_level_count: 1,
536            sample_count: 1,
537            dimension: wgpu::TextureDimension::D2,
538            format: wgpu::TextureFormat::Rgba8UnormSrgb,
539            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
540            view_formats: &[],
541        });
542        queue.write_texture(
543            wgpu::TexelCopyTextureInfo {
544                texture: &dummy_texture,
545                mip_level: 0,
546                origin: wgpu::Origin3d::ZERO,
547                aspect: wgpu::TextureAspect::All,
548            },
549            &[255, 255, 255, 255],
550            wgpu::TexelCopyBufferLayout {
551                offset: 0,
552                bytes_per_row: Some(4),
553                rows_per_image: Some(1),
554            },
555            dummy_size,
556        );
557
558        let dummy_view = dummy_texture.create_view(&wgpu::TextureViewDescriptor::default());
559
560        // Non-filtering sampler required by the gradient bind group layout.
561        let gradient_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
562            address_mode_u: wgpu::AddressMode::ClampToEdge,
563            address_mode_v: wgpu::AddressMode::ClampToEdge,
564            address_mode_w: wgpu::AddressMode::ClampToEdge,
565            mag_filter: wgpu::FilterMode::Nearest,
566            min_filter: wgpu::FilterMode::Nearest,
567            mipmap_filter: wgpu::MipmapFilterMode::Nearest,
568            ..Default::default()
569        });
570
571        // Gradient bind group: requires non-filterable texture + non-filtering sampler.
572        // The gradient layout expects Float { filterable: false } texture.
573        let gradient_dummy_texture = device.create_texture(&wgpu::TextureDescriptor {
574            label: Some("Gradient Dummy Texture"),
575            size: wgpu::Extent3d { width: 1, height: 1, depth_or_array_layers: 1 },
576            mip_level_count: 1,
577            sample_count: 1,
578            dimension: wgpu::TextureDimension::D2,
579            format: wgpu::TextureFormat::Rgba16Float,
580            usage: wgpu::TextureUsages::TEXTURE_BINDING,
581            view_formats: &[],
582        });
583        let gradient_dummy_view = gradient_dummy_texture.create_view(&wgpu::TextureViewDescriptor::default());
584        let gradient_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
585            layout: &gradient_bind_group_layout,
586            entries: &[
587                wgpu::BindGroupEntry {
588                    binding: 0,
589                    resource: wgpu::BindingResource::TextureView(&gradient_dummy_view),
590                },
591                wgpu::BindGroupEntry {
592                    binding: 1,
593                    resource: wgpu::BindingResource::Sampler(&gradient_sampler),
594                },
595            ],
596            label: Some("Gradient Dummy Bind Group"),
597        });
598        let dummy_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
599            address_mode_u: wgpu::AddressMode::ClampToEdge,
600            address_mode_v: wgpu::AddressMode::ClampToEdge,
601            address_mode_w: wgpu::AddressMode::ClampToEdge,
602            mag_filter: wgpu::FilterMode::Linear,
603            min_filter: wgpu::FilterMode::Nearest,
604            mipmap_filter: wgpu::MipmapFilterMode::Nearest,
605            ..Default::default()
606        });
607
608        // Non-filtering sampler required by the gradient bind group layout.
609        let gradient_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
610            address_mode_u: wgpu::AddressMode::ClampToEdge,
611            address_mode_v: wgpu::AddressMode::ClampToEdge,
612            address_mode_w: wgpu::AddressMode::ClampToEdge,
613            mag_filter: wgpu::FilterMode::Nearest,
614            min_filter: wgpu::FilterMode::Nearest,
615            mipmap_filter: wgpu::MipmapFilterMode::Nearest,
616            ..Default::default()
617        });
618
619        let mut texture_views_list: Vec<wgpu::TextureView> =
620            (0..32).map(|_| dummy_view.clone()).collect();
621        texture_views_list[0] = mega_heim_view_obj.clone();
622
623        let views_refs: Vec<&wgpu::TextureView> = texture_views_list.iter().collect();
624        let mega_heim_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
625            layout: &texture_bind_group_layout,
626            entries: &[
627                wgpu::BindGroupEntry {
628                    binding: 0,
629                    resource: wgpu::BindingResource::TextureViewArray(&views_refs),
630                },
631                wgpu::BindGroupEntry {
632                    binding: 1,
633                    resource: wgpu::BindingResource::Sampler(&text_sampler),
634                },
635            ],
636            label: Some("Mega-Heim Bind Group"),
637        });
638
639        let dummy_views_refs: Vec<&wgpu::TextureView> = (0..32).map(|_| &dummy_view).collect();
640        let dummy_texture_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
641            layout: &texture_bind_group_layout,
642            entries: &[
643                wgpu::BindGroupEntry {
644                    binding: 0,
645                    resource: wgpu::BindingResource::TextureViewArray(&dummy_views_refs),
646                },
647                wgpu::BindGroupEntry {
648                    binding: 1,
649                    resource: wgpu::BindingResource::Sampler(&dummy_sampler),
650                },
651            ],
652            label: Some("Dummy Texture Bind Group"),
653        });
654
655        let dummy_env_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
656            layout: &env_bind_group_layout,
657            entries: &[
658                wgpu::BindGroupEntry {
659                    binding: 0,
660                    resource: wgpu::BindingResource::TextureView(&dummy_view),
661                },
662                wgpu::BindGroupEntry {
663                    binding: 1,
664                    resource: wgpu::BindingResource::Sampler(&dummy_sampler),
665                },
666            ],
667            label: Some("Dummy Env Bind Group"),
668        });
669        let dummy_depth_tex = device.create_texture(&wgpu::TextureDescriptor {
670            label: Some("Surtr Dummy Depth Texture"),
671            size: wgpu::Extent3d {
672                width: 1,
673                height: 1,
674                depth_or_array_layers: 1,
675            },
676            mip_level_count: 1,
677            sample_count: 1,
678            dimension: wgpu::TextureDimension::D2,
679            format: wgpu::TextureFormat::Depth32Float,
680            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
681            view_formats: &[],
682        });
683        let dummy_depth_view = dummy_depth_tex.create_view(&wgpu::TextureViewDescriptor::default());
684
685        let dummy_depth_tex_msaa = device.create_texture(&wgpu::TextureDescriptor {
686            label: Some("Surtr Dummy Depth Texture MSAA"),
687            size: wgpu::Extent3d {
688                width: 1,
689                height: 1,
690                depth_or_array_layers: 1,
691            },
692            mip_level_count: 1,
693            sample_count: 4,
694            dimension: wgpu::TextureDimension::D2,
695            format: wgpu::TextureFormat::Depth32Float,
696            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
697            view_formats: &[],
698        });
699        let dummy_depth_view_msaa =
700            dummy_depth_tex_msaa.create_view(&wgpu::TextureViewDescriptor::default());
701
702        let mut texture_registry = LruCache::new(NonZeroUsize::new(31).unwrap());
703        let mut texture_bind_groups = Vec::new();
704
705        // Index 0 is permanently reserved for the Mega-Heim atlas. Loaded images start at 1.
706        texture_registry.put("__mega_heim".to_string(), 0);
707        texture_bind_groups.push(mega_heim_bind_group.clone());
708
709        let geometry_buffers =
710            crate::types::GeometryBuffers::forge(&device, MAX_VERTICES, MAX_INDICES);
711
712        // Forge the Heart (Berserker Uniforms)
713        let current_theme = ColorTheme::default();
714        use wgpu::util::DeviceExt;
715        let theme_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
716            label: Some("Surtr Theme Buffer"),
717            contents: bytemuck::bytes_of(&current_theme),
718            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
719        });
720
721        let (width, height, scale_factor) = if let Some((ref window, _, ref config)) = surface_info
722        {
723            (config.width, config.height, window.scale_factor() as f32)
724        } else if let Some((w, h, _)) = headless_info {
725            (w, h, 1.0)
726        } else {
727            (1280, 720, 1.0)
728        };
729
730        let mut current_scene =
731            SceneUniforms::new(width as f32 / scale_factor, height as f32 / scale_factor);
732        current_scene.scale_factor = scale_factor;
733        let msaa_sample_count = QualityLevel::default().msaa_sample_count();
734        let scene_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
735            label: Some("Surtr Scene Buffer"),
736            contents: bytemuck::bytes_of(&current_scene),
737            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
738        });
739
740        let berserker_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
741            layout: &berserker_bind_group_layout,
742            entries: &[
743                wgpu::BindGroupEntry {
744                    binding: 0,
745                    resource: theme_buffer.as_entire_binding(),
746                },
747                wgpu::BindGroupEntry {
748                    binding: 1,
749                    resource: scene_buffer.as_entire_binding(),
750                },
751            ],
752            label: Some("Surtr Berserker Bind Group"),
753        });
754
755        let mut registry = crate::kvasir::registry::ResourceRegistry::new();
756        let mut surfaces = std::collections::HashMap::new();
757        let mut current_window = None;
758        let mut headless_context = None;
759
760        if let Some((window, surface, config)) = surface_info {
761            let window_id = window.id();
762            let ctx = create_surface_context(
763                &device,
764                surface,
765                config,
766                &env_bind_group_layout,
767                &texture_bind_group_layout,
768                scale_factor,
769                msaa_sample_count,
770                &mut registry,
771            );
772            surfaces.insert(window_id, ctx);
773            current_window = Some(window_id);
774        } else if let Some((w, h, f)) = headless_info {
775            headless_context = Some(create_headless_context(
776                &device,
777                w,
778                h,
779                f,
780                &env_bind_group_layout,
781                &texture_bind_group_layout,
782                &mut registry,
783                msaa_sample_count,
784            ));
785        }
786
787        let staging_belt = wgpu::util::StagingBelt::new((*device).clone(), 1024 * 1024);
788
789        let glass_output_bind_group_layout = env_bind_group_layout.clone();
790
791        Self {
792            registry,
793            ai_material_rx: None,
794            active_offscreens: Vec::new(),
795            effect_pipelines: std::collections::HashMap::new(),
796            effect_params_buffer: device.create_buffer(&wgpu::BufferDescriptor {
797                label: Some("Dummy Effect Buffer"),
798                size: 256,
799                usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
800                mapped_at_creation: false,
801            }),
802            effect_params_bind_group: device.create_bind_group(&wgpu::BindGroupDescriptor {
803                label: Some("Dummy Effect Bind Group"),
804                layout: &device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
805                    label: None,
806                    entries: &[],
807                }),
808                entries: &[],
809            }),
810            linear_sampler: device.create_sampler(&wgpu::SamplerDescriptor {
811                label: Some("Linear Sampler"),
812                address_mode_u: wgpu::AddressMode::ClampToEdge,
813                address_mode_v: wgpu::AddressMode::ClampToEdge,
814                address_mode_w: wgpu::AddressMode::ClampToEdge,
815                mag_filter: wgpu::FilterMode::Linear,
816                min_filter: wgpu::FilterMode::Linear,
817                mipmap_filter: wgpu::MipmapFilterMode::Linear,
818                ..Default::default()
819            }),
820            instance,
821            adapter,
822            device: device.clone(),
823            queue: queue.clone(),
824
825            surfaces,
826            current_window,
827            headless_context,
828            pipeline: pipes.pipeline,
829            opaque_pipeline: pipes.opaque_pipeline,
830            ui_pipeline: pipes.ui_pipeline,
831            glass_pipeline: pipes.glass_pipeline,
832            bloom_extract_pipeline: pipes.bloom_extract_pipeline,
833            copy_pipeline: pipes.copy_pipeline,
834            composite_pipeline: pipes.composite_pipeline,
835            env_bind_group_layout,
836            mega_heim_tex,
837            mega_heim_bind_group,
838            config: crate::subsystems::RendererConfig::default(),
839            text: crate::types::TextSubsystem::forge(NonZeroUsize::new(8192).unwrap()),
840            heim_packer: SkylinePacker::new(4096, 4096),
841            image_uv_registry: {
842                let mut cache = LruCache::new(NonZeroUsize::new(256).unwrap());
843                cache.put(
844                    "__mega_heim".to_string(),
845                    cvkg_core::Rect {
846                        x: 0.0,
847                        y: 0.0,
848                        width: 1.0,
849                        height: 1.0,
850                    },
851                );
852                cache
853            },
854            texture_registry,
855            texture_views: texture_views_list,
856            dummy_sampler,
857            dummy_depth_view,
858            dummy_depth_view_msaa,
859            svg: crate::types::SvgSubsystem::forge(
860                &device,
861                &queue,
862                NonZeroUsize::new(512).unwrap(),
863                NonZeroUsize::new(512).unwrap(),
864            ),
865            dummy_texture_bind_group,
866            gradient_stop_texture: dummy_texture.clone(),
867            gradient_stop_texture_view: dummy_view.clone(),
868            gradient_bind_group,
869            gradient_texture_cache: std::collections::HashMap::new(),
870            gradient_stops_hash: 0,
871            gradient_bind_group_layout,
872            dummy_env_bind_group,
873            texture_bind_group_layout,
874            texture_bind_groups,
875            shared_elements: LruCache::new(NonZeroUsize::new(1024).unwrap()),
876            geometry_buffers,
877            vertices: Vec::with_capacity(MAX_VERTICES),
878            indices: Vec::with_capacity(MAX_INDICES),
879            instance_data: Vec::with_capacity(MAX_VERTICES / 4),
880            draw_calls: Vec::new(),
881            current_texture_id: None,
882            opacity_stack: vec![1.0],
883            clip_stack: Vec::new(),
884            slice_stack: Vec::new(),
885            shadow_stack: Vec::new(),
886            theme_buffer,
887            scene_buffer,
888            berserker_bind_group,
889            berserker_bind_group_layout,
890            start_time: std::time::Instant::now(),
891            current_theme,
892            current_scene,
893            background_pipeline: pipes.background_pipeline,
894            current_z: 0.0,
895            default_background_color: [0.02, 0.02, 0.05, 1.0],
896            app_drew_background: false,
897            frame_rendered: false,
898            current_draw_order: 0,
899            telemetry: cvkg_core::TelemetryData::default(),
900            last_frame_start: std::time::Instant::now(),
901            last_redraw_start: std::time::Instant::now(),
902            frame_budget: cvkg_core::FrameBudget::default(),
903            capture_staging_buffer: None,
904            compositor_index_cursor: 0,
905            vram_buffers_bytes: 0,
906            vram_textures_bytes: 0,
907            _debug_layout: false,
908            transform_stack: Vec::new(),
909            redraw_requested: false,
910            skuld_queries,
911            skuld_buffer,
912            skuld_read_buffer,
913            skuld_period,
914            last_gpu_time_ns: 0,
915            particle_compute_pipeline: pipes.particle_compute_pipeline,
916            particle_compute_bgl: pipes.particle_compute_bgl,
917            particle_buffer: pipes.particle_buffer,
918            particle_uniform_buffer: pipes.particle_uniform_buffer,
919            particles: crate::types::ParticleSubsystem::forge(),
920            particle_render_pipeline: pipes.particle_render_pipeline,
921            particle_render_bgl: pipes.particle_render_bgl,
922            particle_render_bind_group: None,
923            particle_compute_bind_group: None,
924            vnode_stack: Vec::new(),
925            event_handlers: std::collections::HashMap::new(),
926            staging_belt,
927            staging_command_buffers: Vec::new(),
928            glass_output_bind_group_layout,
929            current_draw_material: cvkg_core::DrawMaterial::Opaque,
930            portal_regions: std::collections::VecDeque::new(),
931            cached_graph_plan: None,
932            material_compilation_hash: 0,
933            memo_cache: std::collections::HashMap::new(),
934            frame_generation: 0,
935            quality_level: QualityLevel::default(),
936            pipeline_cache,
937            bloom_enabled: true,
938            volumetric_enabled: false,
939            path_geometry_cache: lru::LruCache::new(NonZeroUsize::new(64).unwrap()),
940            color_blind_mode: crate::color_blindness::ColorBlindMode::Normal,
941            color_blind_intensity: 1.0,
942            color_blind_pipeline: pipes.color_blind_pipeline,
943            volumetric_pipeline: pipes.volumetric_pipeline,
944            volumetric_bind_group_layout: pipes.volumetric_bind_group_layout,
945            volumetric_uniform_buffer: pipes.volumetric_uniform_buffer,
946            volumetric_depth_sampler: pipes.volumetric_depth_sampler,
947            hologram_instances: Vec::new(),
948            color_blind_bind_group_layout: pipes.color_blind_bind_group_layout,
949            color_blind_uniform_buffer: pipes.color_blind_uniform_buffer,
950            sampler: pipes.sampler,
951            kawase_down_pipeline: pipes.kawase_down_pipeline,
952            kawase_up_pipeline: pipes.kawase_up_pipeline,
953            kawase_bind_group_layout: pipes.kawase_bind_group_layout,
954            kawase_uniform: pipes.kawase_uniform,
955            kawase_uniform_buffers: pipes.kawase_uniform_buffers,
956            bind_group_cache: std::sync::Mutex::new(std::collections::HashMap::new()),
957            texture_view_cache: std::sync::Mutex::new(std::collections::HashMap::new()),
958
959            // SVG Filter Engine Resources (initialized lazily on first use)
960            blur_pipeline: None,
961            blur_uniform: None,
962            blur_bind_group_layout: None,
963            blend_pipeline: None,
964            blend_bind_group_layout: None,
965            flood_pipeline: None,
966            copy_bind_group_layout: None,
967
968            // Error tracking
969            render_error_count: 0,
970            has_fatal_error: false,
971        }
972    }
973}