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 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 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 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(); 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 #[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 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 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 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 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 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 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 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(¤t_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(¤t_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 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}