Skip to main content

cvkg_render_gpu/renderer/
init.rs

1use std::sync::Arc;
2use std::collections::HashMap;
3use std::num::NonZeroUsize;
4use lru::LruCache;
5use crate::types::{SurfaceContext, HeadlessContext, GpuParticle, ParticleUniforms, MAX_VERTICES, MAX_INDICES, MAX_PARTICLES};
6use crate::renderer::{GpuRenderer, QualityLevel};
7use crate::renderer::context_helpers::{load_pipeline_cache_with_integrity_check, compute_mip_levels, create_surface_context, create_headless_context};
8use crate::renderer::pipelines::compile_render_pipelines;
9use crate::heim::SkylinePacker;
10use crate::{
11    WGSL_COMMON, WGSL_SHAPES, WGSL_BIFROST, WGSL_BLOOM, WGSL_COLOR_BLIND,
12    WGSL_MATERIAL_OPAQUE, WGSL_MATERIAL_GLASS
13};
14use cvkg_core::{ColorTheme, SceneUniforms, Rect};
15
16impl GpuRenderer {
17    /// forge -- Initializes the Surtr GPU renderer from a winit window.
18    ///
19    /// This method performs the following:
20    /// 1. Negotiates a wgpu surface and adapter.
21    /// 2. Forges the Muspelheim multi-pass pipeline layouts.
22    /// 3. Initializes the Berserker state buffers and texture registries.
23    pub async fn forge(window: Arc<winit::window::Window>) -> Self {
24        let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
25            backends: wgpu::Backends::all(),
26            flags: wgpu::InstanceFlags::default(),
27            backend_options: wgpu::BackendOptions::default(),
28            display: None,
29            memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
30        });
31
32        let surface = instance
33            .create_surface(window.clone())
34            .expect("Failed to create surface");
35
36        // Request adapter with robust multi-stage fallback for Bumblebee/Optimus compatibility
37        log::info!("[GPU] Requesting HighPerformance adapter...");
38
39        let mut adapter = None;
40
41        #[cfg(not(target_arch = "wasm32"))]
42        if let Ok(filter) = std::env::var("WGPU_ADAPTER_NAME") {
43            let adapters = instance.enumerate_adapters(wgpu::Backends::all()).await;
44            log::info!("[GPU] Available adapters:");
45            for a in &adapters {
46                let info = a.get_info();
47                log::info!(
48                    "  - Name: '{}' | Driver: '{}' | Backend: {:?}",
49                    info.name,
50                    info.driver,
51                    info.backend
52                );
53            }
54
55            adapter = adapters.into_iter().find(|a| {
56                let info = a.get_info();
57                let match_found = info.name.to_lowercase().contains(&filter.to_lowercase())
58                    || info.driver.to_lowercase().contains(&filter.to_lowercase());
59                if match_found {
60                    log::info!(
61                        "[GPU] Manual selection match: {} | Driver: {}",
62                        info.name,
63                        info.driver
64                    );
65                }
66                match_found
67            });
68
69            if adapter.is_some() {
70                log::info!(
71                    "[GPU] Forced adapter selection via WGPU_ADAPTER_NAME='{}'",
72                    filter
73                );
74            } else {
75                log::warn!(
76                    "[GPU] WGPU_ADAPTER_NAME='{}' provided but no matching adapter found. Falling back...",
77                    filter
78                );
79            }
80        }
81
82        if adapter.is_none() {
83            adapter = instance
84                .request_adapter(&wgpu::RequestAdapterOptions {
85                    power_preference: wgpu::PowerPreference::HighPerformance,
86                    compatible_surface: Some(&surface),
87                    force_fallback_adapter: false,
88                })
89                .await
90                .ok();
91        }
92
93        if adapter.is_none() {
94            log::warn!(
95                "[GPU] HighPerformance adapter failed (possible Bumblebee/Optimus), trying LowPower..."
96            );
97            adapter = instance
98                .request_adapter(&wgpu::RequestAdapterOptions {
99                    power_preference: wgpu::PowerPreference::LowPower,
100                    compatible_surface: Some(&surface),
101                    force_fallback_adapter: false,
102                })
103                .await
104                .ok();
105        }
106
107        if adapter.is_none() {
108            log::warn!("[GPU] Hardware adapters failed, trying Software fallback...");
109            adapter = instance
110                .request_adapter(&wgpu::RequestAdapterOptions {
111                    power_preference: wgpu::PowerPreference::LowPower,
112                    compatible_surface: Some(&surface),
113                    force_fallback_adapter: true,
114                })
115                .await
116                .ok();
117        }
118
119        let adapter = adapter.expect("Failed to find a suitable GPU for Surtr");
120        let info = adapter.get_info();
121        // P1-26: detect GPU vendor for logging and future
122        // capability-based shader selection.
123        let caps = crate::subsystems::GpuCapabilities::detect(
124            &info.name,
125            format!("{:?}", info.backend),
126        );
127        log::info!(
128            "[GPU] Selected adapter: {} ({:?}) on backend: {:?} -- detected as {}",
129            info.name,
130            info.device_type,
131            info.backend,
132            caps.vendor
133        );
134        log::info!("[GPU] Driver info: {} - {}", info.driver, info.driver_info);
135        let supports_timestamps = adapter.features().contains(wgpu::Features::TIMESTAMP_QUERY);
136        let supports_pipeline_cache = adapter.features().contains(wgpu::Features::PIPELINE_CACHE);
137        #[cfg(not(target_arch = "wasm32"))]
138        let mut required_features =
139            wgpu::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING
140                | wgpu::Features::TEXTURE_BINDING_ARRAY;
141
142        #[cfg(target_arch = "wasm32")]
143        let mut required_features = wgpu::Features::empty(); // Fallbacks for WebGL
144        if supports_timestamps {
145            required_features |= wgpu::Features::TIMESTAMP_QUERY;
146        }
147        if supports_pipeline_cache {
148            required_features |= wgpu::Features::PIPELINE_CACHE;
149        }
150        // Enable validation layer in debug builds for better error reporting
151        #[cfg(all(debug_assertions, not(target_arch = "wasm32")))]
152        {
153            log::info!("[GPU] Validation layer enabled (debug build)");
154        }
155
156        let (device, queue) = adapter
157            .request_device(&wgpu::DeviceDescriptor {
158                label: Some("Surtr Forge"),
159                required_features,
160                required_limits: wgpu::Limits {
161                    max_bindings_per_bind_group: 256,
162                    max_binding_array_elements_per_shader_stage: 256,
163                    ..wgpu::Limits::default()
164                },
165                memory_hints: wgpu::MemoryHints::default(),
166                experimental_features: wgpu::ExperimentalFeatures::disabled(),
167                trace: wgpu::Trace::Off,
168            })
169            .await
170            .expect("Failed to create Surtr device");
171
172        let instance = Arc::new(instance);
173        let adapter = Arc::new(adapter);
174
175        device.on_uncaptured_error(Arc::new(|error| {
176            log::error!(
177                "[GPU] Uncaptured device error (Device Lost or Panic): {:?}",
178                error
179            );
180        }));
181
182        let device = Arc::new(device);
183        let queue = Arc::new(queue);
184
185        let size = window.inner_size();
186        // Ensure we have valid dimensions - Wayland may return 0 for not-yet-committed surfaces
187        let width = if size.width > 0 { size.width } else { 1280 };
188        let height = if size.height > 0 { size.height } else { 720 };
189        let surface_caps = surface.get_capabilities(&adapter);
190        // HDR/Display P3 surface format selection:
191        // WHY: Tahoe requires wide-gamut Display P3 or HDR (Rgba16Float) color spaces when available.
192        // CONTRACT: Uses select_best_surface_format to safely fall back on mobile/legacy GPUs.
193        let surface_format = Self::select_best_surface_format(&surface_caps.formats);
194
195        log::info!("[GPU] Available present modes: {:?}", surface_caps.present_modes);
196        log::info!("[GPU] Adapter: {} ({:?})", adapter.get_info().name, adapter.get_info().backend);
197        let present_mode = if surface_caps
198            .present_modes
199            .contains(&wgpu::PresentMode::Immediate)
200        {
201            log::info!("[GPU] Selected: Immediate (no vsync, uncapped)");
202            wgpu::PresentMode::Immediate
203        } else if surface_caps
204            .present_modes
205            .contains(&wgpu::PresentMode::Mailbox)
206        {
207            log::info!("[GPU] Selected: Mailbox (no vsync)");
208            wgpu::PresentMode::Mailbox
209        } else {
210            log::info!("[GPU] Selected: Fifo (V-Sync capped at compositor rate)");
211            wgpu::PresentMode::Fifo
212        };
213
214        let alpha_mode = if surface_caps
215            .alpha_modes
216            .contains(&wgpu::CompositeAlphaMode::PostMultiplied)
217        {
218            wgpu::CompositeAlphaMode::PostMultiplied
219        } else if surface_caps
220            .alpha_modes
221            .contains(&wgpu::CompositeAlphaMode::PreMultiplied)
222        {
223            wgpu::CompositeAlphaMode::PreMultiplied
224        } else {
225            surface_caps.alpha_modes[0]
226        };
227
228        log::info!(
229            "[GPU] Configuring surface: {}x{} | {:?} | {:?}",
230            width,
231            height,
232            present_mode,
233            alpha_mode
234        );
235
236        let config = wgpu::SurfaceConfiguration {
237            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
238            format: surface_format,
239            width,
240            height,
241            present_mode,
242            alpha_mode,
243            view_formats: vec![],
244            desired_maximum_frame_latency: 1,
245        };
246        surface.configure(&device, &config);
247        log::info!("[GPU] Surface configuration successful.");
248
249        let renderer = Self::forge_internal(
250            instance,
251            adapter,
252            device,
253            queue,
254            Some((window, surface, config)),
255            None,
256        )
257        .await;
258        log::info!("[GPU] Forge internal complete.");
259        renderer
260    }
261
262    /// Internal rendering pipeline constructor.
263    pub(crate) async fn forge_internal(
264        instance: Arc<wgpu::Instance>,
265        adapter: Arc<wgpu::Adapter>,
266        device: Arc<wgpu::Device>,
267        queue: Arc<wgpu::Queue>,
268        surface_info: Option<(
269            Arc<winit::window::Window>,
270            wgpu::Surface<'static>,
271            wgpu::SurfaceConfiguration,
272        )>,
273        headless_info: Option<(u32, u32, wgpu::TextureFormat)>,
274    ) -> Self {
275        let format = if let Some((_, _, ref config)) = surface_info {
276            config.format
277        } else if let Some((_, _, f)) = headless_info {
278            f
279        } else {
280            wgpu::TextureFormat::Rgba8UnormSrgb
281        };
282
283        let supports_timestamps = adapter.features().contains(wgpu::Features::TIMESTAMP_QUERY);
284        let skuld_period = queue.get_timestamp_period();
285        let (skuld_queries, skuld_buffer, skuld_read_buffer) = if supports_timestamps {
286            let q = device.create_query_set(&wgpu::QuerySetDescriptor {
287                label: Some("Skuld Timestamp Queries"),
288                count: 2,
289                ty: wgpu::QueryType::Timestamp,
290            });
291            let b = device.create_buffer(&wgpu::BufferDescriptor {
292                label: Some("Skuld Query Buffer"),
293                size: 16,
294                usage: wgpu::BufferUsages::QUERY_RESOLVE | wgpu::BufferUsages::COPY_SRC,
295                mapped_at_creation: false,
296            });
297            let rb = device.create_buffer(&wgpu::BufferDescriptor {
298                label: Some("Skuld Read Buffer"),
299                size: 16,
300                usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
301                mapped_at_creation: false,
302            });
303            (Some(q), Some(b), Some(rb))
304        } else {
305            (None, None, None)
306        };
307
308        let pipeline_cache = if device.features().contains(wgpu::Features::PIPELINE_CACHE) {
309            let cache_dir = std::env::current_exe()
310                .ok()
311                .and_then(|p| p.parent().map(|d| d.join("pipeline_cache")))
312                .unwrap_or_else(|| std::env::temp_dir().join("cvkg_pipeline_cache"));
313            let _ = std::fs::create_dir_all(&cache_dir);
314            let cache_path = cache_dir.join("cvkg_render_gpu.bin");
315            let cache_data = match load_pipeline_cache_with_integrity_check(&cache_path) {
316                Ok(data) => data,
317                Err(reason) => {
318                    log::warn!(
319                        "[GPU] pipeline cache integrity check failed: {reason}; using empty cache"
320                    );
321                    None
322                }
323            };
324            Some(unsafe {
325                device.create_pipeline_cache(&wgpu::PipelineCacheDescriptor {
326                    label: Some("CVKG Pipeline Cache"),
327                    data: cache_data.as_deref(),
328                    fallback: true,
329                })
330            })
331        } else {
332            log::debug!(
333                "[GPU] device does not expose PIPELINE_CACHE; compiling pipelines without cache"
334            );
335            None
336        };
337        let materials_generated = crate::material::generate_builtins_wgsl();
338
339        let wgsl_src = format!(
340            "{}{}{}{}{}{}",
341            WGSL_COMMON,
342            WGSL_SHAPES,
343            WGSL_BIFROST,
344            WGSL_BLOOM,
345            WGSL_COLOR_BLIND,
346            materials_generated
347        );
348        let wgsl_opaque = format!(
349            "{}{}{}{}{}{}",
350            WGSL_COMMON,
351            WGSL_MATERIAL_OPAQUE,
352            WGSL_BIFROST,
353            WGSL_BLOOM,
354            WGSL_COLOR_BLIND,
355            materials_generated
356        );
357        let wgsl_glass = format!(
358            "{}{}{}{}{}{}",
359            WGSL_COMMON,
360            WGSL_MATERIAL_GLASS,
361            WGSL_BIFROST,
362            WGSL_BLOOM,
363            WGSL_COLOR_BLIND,
364            materials_generated
365        );
366
367        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
368            label: Some("Surtr Main Shader"),
369            source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Owned(wgsl_src)),
370        });
371
372        #[cfg(target_arch = "wasm32")]
373        let texture_array_count: Option<std::num::NonZeroU32> = None;
374        #[cfg(not(target_arch = "wasm32"))]
375        let texture_array_count: Option<std::num::NonZeroU32> =
376            std::num::NonZeroU32::new(32);
377
378        let texture_bind_group_layout =
379            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
380                entries: &[
381                    wgpu::BindGroupLayoutEntry {
382                        binding: 0,
383                        visibility: wgpu::ShaderStages::FRAGMENT,
384                        ty: wgpu::BindingType::Texture {
385                            multisampled: false,
386                            view_dimension: wgpu::TextureViewDimension::D2,
387                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
388                        },
389                        count: texture_array_count,
390                    },
391                    wgpu::BindGroupLayoutEntry {
392                        binding: 1,
393                        visibility: wgpu::ShaderStages::FRAGMENT,
394                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
395                        count: None,
396                    },
397                ],
398                label: Some("Niflheim Texture Bind Group Layout"),
399            });
400
401        let env_bind_group_layout =
402            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
403                entries: &[
404                    wgpu::BindGroupLayoutEntry {
405                        binding: 0,
406                        visibility: wgpu::ShaderStages::FRAGMENT,
407                        ty: wgpu::BindingType::Texture {
408                            multisampled: false,
409                            view_dimension: wgpu::TextureViewDimension::D2,
410                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
411                        },
412                        count: None,
413                    },
414                    wgpu::BindGroupLayoutEntry {
415                        binding: 1,
416                        visibility: wgpu::ShaderStages::FRAGMENT,
417                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
418                        count: None,
419                    },
420                ],
421                label: Some("Surtr Environment Bind Group Layout"),
422            });
423
424        let berserker_bind_group_layout =
425            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
426                entries: &[
427                    wgpu::BindGroupLayoutEntry {
428                        binding: 0,
429                        visibility: wgpu::ShaderStages::FRAGMENT,
430                        ty: wgpu::BindingType::Buffer {
431                            ty: wgpu::BufferBindingType::Uniform,
432                            has_dynamic_offset: false,
433                            min_binding_size: None,
434                        },
435                        count: None,
436                    },
437                    wgpu::BindGroupLayoutEntry {
438                        binding: 1,
439                        visibility: wgpu::ShaderStages::FRAGMENT | wgpu::ShaderStages::VERTEX,
440                        ty: wgpu::BindingType::Buffer {
441                            ty: wgpu::BufferBindingType::Uniform,
442                            has_dynamic_offset: false,
443                            min_binding_size: None,
444                        },
445                        count: None,
446                    },
447                ],
448                label: Some("Surtr Berserker Bind Group Layout"),
449            });
450
451        let pipes = compile_render_pipelines(
452            &device,
453            format,
454            pipeline_cache.as_ref(),
455            &texture_bind_group_layout,
456            &env_bind_group_layout,
457            &berserker_bind_group_layout,
458            &shader,
459            wgsl_opaque.as_str(),
460            wgsl_glass.as_str(),
461            &queue,
462        );
463
464        // Forge the Mega-Heim (4096x4096 RGBA for production batching)
465        let mega_heim_tex = device.create_texture(&wgpu::TextureDescriptor {
466            label: Some("Surtr Mega-Heim"),
467            size: wgpu::Extent3d {
468                width: 4096,
469                height: 4096,
470                depth_or_array_layers: 1,
471            },
472            mip_level_count: 1,
473            sample_count: 1,
474            dimension: wgpu::TextureDimension::D2,
475            format: wgpu::TextureFormat::Rgba8UnormSrgb,
476            usage: wgpu::TextureUsages::TEXTURE_BINDING
477                | wgpu::TextureUsages::COPY_DST
478                | wgpu::TextureUsages::COPY_SRC,
479            view_formats: &[],
480        });
481        let mega_heim_view_obj = mega_heim_tex.create_view(&wgpu::TextureViewDescriptor::default());
482        let text_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
483            address_mode_u: wgpu::AddressMode::ClampToEdge,
484            address_mode_v: wgpu::AddressMode::ClampToEdge,
485            mag_filter: wgpu::FilterMode::Linear,
486            min_filter: wgpu::FilterMode::Linear,
487            ..Default::default()
488        });
489
490        // Forge the Niflheim Dummy Texture (1x1 White)
491        let dummy_size = wgpu::Extent3d {
492            width: 1,
493            height: 1,
494            depth_or_array_layers: 1,
495        };
496        let dummy_texture = device.create_texture(&wgpu::TextureDescriptor {
497            label: Some("Niflheim Dummy Texture"),
498            size: dummy_size,
499            mip_level_count: 1,
500            sample_count: 1,
501            dimension: wgpu::TextureDimension::D2,
502            format: wgpu::TextureFormat::Rgba8UnormSrgb,
503            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
504            view_formats: &[],
505        });
506        queue.write_texture(
507            wgpu::TexelCopyTextureInfo {
508                texture: &dummy_texture,
509                mip_level: 0,
510                origin: wgpu::Origin3d::ZERO,
511                aspect: wgpu::TextureAspect::All,
512            },
513            &[255, 255, 255, 255],
514            wgpu::TexelCopyBufferLayout {
515                offset: 0,
516                bytes_per_row: Some(4),
517                rows_per_image: Some(1),
518            },
519            dummy_size,
520        );
521
522        let dummy_view = dummy_texture.create_view(&wgpu::TextureViewDescriptor::default());
523        let dummy_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
524            address_mode_u: wgpu::AddressMode::ClampToEdge,
525            address_mode_v: wgpu::AddressMode::ClampToEdge,
526            address_mode_w: wgpu::AddressMode::ClampToEdge,
527            mag_filter: wgpu::FilterMode::Linear,
528            min_filter: wgpu::FilterMode::Nearest,
529            mipmap_filter: wgpu::MipmapFilterMode::Nearest,
530            ..Default::default()
531        });
532
533        let mut texture_views_list: Vec<wgpu::TextureView> =
534            (0..32).map(|_| dummy_view.clone()).collect();
535        texture_views_list[0] = mega_heim_view_obj.clone();
536
537        let views_refs: Vec<&wgpu::TextureView> = texture_views_list.iter().collect();
538        let mega_heim_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
539            layout: &texture_bind_group_layout,
540            entries: &[
541                wgpu::BindGroupEntry {
542                    binding: 0,
543                    resource: wgpu::BindingResource::TextureViewArray(&views_refs),
544                },
545                wgpu::BindGroupEntry {
546                    binding: 1,
547                    resource: wgpu::BindingResource::Sampler(&text_sampler),
548                },
549            ],
550            label: Some("Mega-Heim Bind Group"),
551        });
552
553        let dummy_views_refs: Vec<&wgpu::TextureView> = (0..32).map(|_| &dummy_view).collect();
554        let dummy_texture_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
555            layout: &texture_bind_group_layout,
556            entries: &[
557                wgpu::BindGroupEntry {
558                    binding: 0,
559                    resource: wgpu::BindingResource::TextureViewArray(&dummy_views_refs),
560                },
561                wgpu::BindGroupEntry {
562                    binding: 1,
563                    resource: wgpu::BindingResource::Sampler(&dummy_sampler),
564                },
565            ],
566            label: Some("Dummy Texture Bind Group"),
567        });
568
569        let dummy_env_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
570            layout: &env_bind_group_layout,
571            entries: &[
572                wgpu::BindGroupEntry {
573                    binding: 0,
574                    resource: wgpu::BindingResource::TextureView(&dummy_view),
575                },
576                wgpu::BindGroupEntry {
577                    binding: 1,
578                    resource: wgpu::BindingResource::Sampler(&dummy_sampler),
579                },
580            ],
581            label: Some("Dummy Env Bind Group"),
582        });
583
584        let mut texture_registry = LruCache::new(NonZeroUsize::new(31).unwrap());
585        let mut texture_bind_groups = Vec::new();
586
587        // Index 0 is permanently reserved for the Mega-Heim atlas. Loaded images start at 1.
588        texture_registry.put("__mega_heim".to_string(), 0);
589        texture_bind_groups.push(mega_heim_bind_group.clone());
590
591        let geometry_buffers =
592            crate::types::GeometryBuffers::forge(&device, MAX_VERTICES, MAX_INDICES);
593
594        // Forge the Heart (Berserker Uniforms)
595        let current_theme = ColorTheme::default();
596        use wgpu::util::DeviceExt;
597        let theme_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
598            label: Some("Surtr Theme Buffer"),
599            contents: bytemuck::bytes_of(&current_theme),
600            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
601        });
602
603        let (width, height, scale_factor) = if let Some((ref window, _, ref config)) = surface_info
604        {
605            (config.width, config.height, window.scale_factor() as f32)
606        } else if let Some((w, h, _)) = headless_info {
607            (w, h, 1.0)
608        } else {
609            (1280, 720, 1.0)
610        };
611
612        let mut current_scene =
613            SceneUniforms::new(width as f32 / scale_factor, height as f32 / scale_factor);
614        current_scene.scale_factor = scale_factor;
615        let msaa_sample_count = QualityLevel::default().msaa_sample_count();
616        let scene_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
617            label: Some("Surtr Scene Buffer"),
618            contents: bytemuck::bytes_of(&current_scene),
619            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
620        });
621
622        let berserker_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
623            layout: &berserker_bind_group_layout,
624            entries: &[
625                wgpu::BindGroupEntry {
626                    binding: 0,
627                    resource: theme_buffer.as_entire_binding(),
628                },
629                wgpu::BindGroupEntry {
630                    binding: 1,
631                    resource: scene_buffer.as_entire_binding(),
632                },
633            ],
634            label: Some("Surtr Berserker Bind Group"),
635        });
636
637        let mut registry = crate::kvasir::registry::ResourceRegistry::new();
638        let mut surfaces = std::collections::HashMap::new();
639        let mut current_window = None;
640        let mut headless_context = None;
641
642        if let Some((window, surface, config)) = surface_info {
643            let window_id = window.id();
644            let ctx = create_surface_context(
645                &device,
646                surface,
647                config,
648                &env_bind_group_layout,
649                &texture_bind_group_layout,
650                scale_factor,
651                msaa_sample_count,
652                &mut registry,
653            );
654            surfaces.insert(window_id, ctx);
655            current_window = Some(window_id);
656        } else if let Some((w, h, f)) = headless_info {
657            headless_context = Some(create_headless_context(
658                &device,
659                w,
660                h,
661                f,
662                &env_bind_group_layout,
663                &texture_bind_group_layout,
664                &mut registry,
665                msaa_sample_count,
666            ));
667        }
668
669        let staging_belt = wgpu::util::StagingBelt::new((*device).clone(), 1024 * 1024);
670
671        let glass_output_bind_group_layout = env_bind_group_layout.clone();
672
673        Self {
674            registry,
675            ai_material_rx: None,
676            active_offscreens: Vec::new(),
677            effect_pipelines: std::collections::HashMap::new(),
678            effect_params_buffer: device.create_buffer(&wgpu::BufferDescriptor {
679                label: Some("Dummy Effect Buffer"),
680                size: 256,
681                usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
682                mapped_at_creation: false,
683            }),
684            effect_params_bind_group: device.create_bind_group(&wgpu::BindGroupDescriptor {
685                label: Some("Dummy Effect Bind Group"),
686                layout: &device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
687                    label: None,
688                    entries: &[],
689                }),
690                entries: &[],
691            }),
692            linear_sampler: device.create_sampler(&wgpu::SamplerDescriptor {
693                label: Some("Linear Sampler"),
694                address_mode_u: wgpu::AddressMode::ClampToEdge,
695                address_mode_v: wgpu::AddressMode::ClampToEdge,
696                address_mode_w: wgpu::AddressMode::ClampToEdge,
697                mag_filter: wgpu::FilterMode::Linear,
698                min_filter: wgpu::FilterMode::Linear,
699                mipmap_filter: wgpu::MipmapFilterMode::Linear,
700                ..Default::default()
701            }),
702            instance,
703            adapter,
704            device: device.clone(),
705            queue: queue.clone(),
706
707            surfaces,
708            current_window,
709            headless_context,
710            pipeline: pipes.pipeline,
711            opaque_pipeline: pipes.opaque_pipeline,
712            ui_pipeline: pipes.ui_pipeline,
713            glass_pipeline: pipes.glass_pipeline,
714            bloom_extract_pipeline: pipes.bloom_extract_pipeline,
715            copy_pipeline: pipes.copy_pipeline,
716            composite_pipeline: pipes.composite_pipeline,
717            env_bind_group_layout,
718            mega_heim_tex,
719            mega_heim_bind_group,
720            config: crate::subsystems::RendererConfig::default(),
721            text: crate::types::TextSubsystem::forge(
722                NonZeroUsize::new(8192).unwrap(),
723            ),
724            heim_packer: SkylinePacker::new(4096, 4096),
725            image_uv_registry: {
726                let mut cache = LruCache::new(NonZeroUsize::new(256).unwrap());
727                cache.put("__mega_heim".to_string(), cvkg_core::Rect { x: 0.0, y: 0.0, width: 1.0, height: 1.0 });
728                cache
729            },
730            texture_registry,
731            texture_views: texture_views_list,
732            dummy_sampler,
733            svg: crate::types::SvgSubsystem::forge(
734                &device,
735                &queue,
736                NonZeroUsize::new(512).unwrap(),
737                NonZeroUsize::new(512).unwrap(),
738            ),
739            dummy_texture_bind_group,
740            dummy_env_bind_group,
741            texture_bind_group_layout,
742            texture_bind_groups,
743            shared_elements: LruCache::new(NonZeroUsize::new(1024).unwrap()),
744            geometry_buffers,
745            vertices: Vec::with_capacity(MAX_VERTICES),
746            indices: Vec::with_capacity(MAX_INDICES),
747            instance_data: Vec::with_capacity(MAX_VERTICES / 4),
748            draw_calls: Vec::new(),
749            current_texture_id: None,
750            opacity_stack: vec![1.0],
751            clip_stack: Vec::new(),
752            slice_stack: Vec::new(),
753            shadow_stack: Vec::new(),
754            theme_buffer,
755            scene_buffer,
756            berserker_bind_group,
757            berserker_bind_group_layout,
758            start_time: std::time::Instant::now(),
759            current_theme,
760            current_scene,
761            background_pipeline: pipes.background_pipeline,
762            current_z: 0.0,
763            default_background_color: [0.02, 0.02, 0.05, 1.0],
764            app_drew_background: false,
765            frame_rendered: false,
766            current_draw_order: 0,
767            telemetry: cvkg_core::TelemetryData::default(),
768            last_frame_start: std::time::Instant::now(),
769            last_redraw_start: std::time::Instant::now(),
770            frame_budget: cvkg_core::FrameBudget::default(),
771            capture_staging_buffer: None,
772            compositor_index_cursor: 0,
773            vram_buffers_bytes: 0,
774            vram_textures_bytes: 0,
775            _debug_layout: false,
776            transform_stack: Vec::new(),
777            redraw_requested: false,
778            skuld_queries,
779            skuld_buffer,
780            skuld_read_buffer,
781            skuld_period,
782            last_gpu_time_ns: 0,
783            particle_compute_pipeline: pipes.particle_compute_pipeline,
784            particle_compute_bgl: pipes.particle_compute_bgl,
785            particle_buffer: pipes.particle_buffer,
786            particle_uniform_buffer: pipes.particle_uniform_buffer,
787            particles: crate::types::ParticleSubsystem::forge(),
788            particle_render_pipeline: pipes.particle_render_pipeline,
789            particle_render_bgl: pipes.particle_render_bgl,
790            particle_render_bind_group: None,
791            particle_compute_bind_group: None,
792            vnode_stack: Vec::new(),
793            event_handlers: std::collections::HashMap::new(),
794            staging_belt,
795            staging_command_buffers: Vec::new(),
796            glass_output_bind_group_layout,
797            current_draw_material: cvkg_core::DrawMaterial::Opaque,
798            portal_regions: std::collections::VecDeque::new(),
799            cached_graph_plan: None,
800            material_compilation_hash: 0,
801            memo_cache: std::collections::HashMap::new(),
802            frame_generation: 0,
803            quality_level: QualityLevel::default(),
804            pipeline_cache,
805            bloom_enabled: true,
806            volumetric_enabled: false,
807            path_geometry_cache: lru::LruCache::new(NonZeroUsize::new(64).unwrap()),
808            color_blind_mode: crate::color_blindness::ColorBlindMode::Normal,
809            color_blind_intensity: 1.0,
810            color_blind_pipeline: pipes.color_blind_pipeline,
811            volumetric_pipeline: pipes.volumetric_pipeline,
812            volumetric_bind_group_layout: pipes.volumetric_bind_group_layout,
813            volumetric_uniform_buffer: pipes.volumetric_uniform_buffer,
814            volumetric_depth_sampler: pipes.volumetric_depth_sampler,
815            hologram_instances: Vec::new(),
816            color_blind_bind_group_layout: pipes.color_blind_bind_group_layout,
817            color_blind_uniform_buffer: pipes.color_blind_uniform_buffer,
818            sampler: pipes.sampler,
819            kawase_down_pipeline: pipes.kawase_down_pipeline,
820            kawase_up_pipeline: pipes.kawase_up_pipeline,
821            kawase_bind_group_layout: pipes.kawase_bind_group_layout,
822            kawase_uniform: pipes.kawase_uniform,
823            kawase_uniform_buffers: pipes.kawase_uniform_buffers,
824            bind_group_cache: std::sync::Mutex::new(std::collections::HashMap::new()),
825            texture_view_cache: std::sync::Mutex::new(std::collections::HashMap::new()),
826
827            // SVG Filter Engine Resources (initialized lazily on first use)
828            blur_pipeline: None,
829            blur_uniform: None,
830            blur_bind_group_layout: None,
831            blend_pipeline: None,
832            blend_bind_group_layout: None,
833            flood_pipeline: None,
834            copy_bind_group_layout: None,
835        }
836    }
837}