Skip to main content

cvkg_render_gpu/
lib.rs

1//! # CVKG Agentic Development Guidelines (v1.2)
2//!
3//! All AI agents contributing to this crate MUST follow ALL seven rules:
4//!
5//! ── Karpathy Guidelines (1–4) ────────────────────────────────────────────
6//! 1. THINK FIRST     — State assumptions. Surface ambiguity. Push back on complexity.
7//! 2. STAY SIMPLE     — Minimum code. No speculative features. No unasked-for abstractions.
8//! 3. BE SURGICAL     — Touch only what's required. Own your orphans. Don't improve neighbors.
9//! 4. VERIFY GOALS    — Turn tasks into checkable criteria. Loop until they pass. Never commit broken.
10//!
11//! ── CVKG Extended Protocols (5–7) ────────────────────────────────────────
12//! 5. TRIPLE-PASS     — Read the target, its surrounding context, and its full call graph
13//!                      at least THREE TIMES before making any edit or revision.
14//! 6. COMMENT ALL     — Every major pub fn, unsafe block, and non-trivial algorithm in
15//!                      every .rs/.ts/.h/.wgsl file MUST have a descriptive doc comment.
16//!                      Comments describe WHY and WHAT CONTRACT, not HOW mechanically.
17//! 7. MONITOR LOOPS   — Check every tool call / command for progress every 30 seconds.
18//!                      After 3 consecutive identical failures, stop, write BLOCKED.md,
19//!                      and move to unblocked work. Never silently accept a broken state.
20//!
21//! Sources:
22//!   Karpathy: https://github.com/multica-ai/andrej-karpathy-skills
23//!   CVKG Extended: Section 2 of the CVKG Design Specification
24#![allow(clippy::type_complexity, clippy::unwrap_or_default)]
25
26//! # Surtr Render Pipeline
27//!
28//! The "Fiery Giant" of the CVKG architecture. This is the authoritative GPU renderer
29//! powered by `wgpu`. It manages the heat of the GPU to forge high-fidelity
30//! "Berserker" aesthetics.
31//!
32//! - **The Flaming Sword**: Command submission and synchronization.
33//! - **Muspelheim Passes**: Multi-pass Gaussian blur and bloom for Bifrost/Gungnir.
34//! - **Reclaim & Quench**: LRU-based cache eviction and atlas recycling.
35
36use cvkg_core::Rect;
37use lru::LruCache;
38use std::num::NonZeroUsize;
39use std::sync::Arc;
40
41#[derive(Clone, Copy)]
42struct SkylineSegment {
43    x: u32,
44    y: u32,
45    w: u32,
46}
47
48struct YggdrasilPacker {
49    width: u32,
50    height: u32,
51    skyline: Vec<SkylineSegment>,
52}
53
54impl YggdrasilPacker {
55    fn new(width: u32, height: u32) -> Self {
56        Self {
57            width,
58            height,
59            skyline: vec![SkylineSegment { x: 0, y: 0, w: width }],
60        }
61    }
62
63    fn pack(&mut self, w: u32, h: u32) -> Option<(u32, u32)> {
64        if w > self.width || h > self.height { return None; }
65        
66        let mut best_idx = None;
67        let mut best_y = u32::MAX;
68        let mut best_w = u32::MAX;
69        
70        for i in 0..self.skyline.len() {
71            let seg = &self.skyline[i];
72            if seg.x + w > self.width { continue; }
73            
74            let mut y = seg.y;
75            let mut remaining = w;
76            let mut j = i;
77            let mut fits = true;
78            
79            while remaining > 0 {
80                if j >= self.skyline.len() { fits = false; break; }
81                let s = &self.skyline[j];
82                y = y.max(s.y);
83                if y + h > self.height { fits = false; break; }
84                if s.w >= remaining { break; }
85                remaining -= s.w;
86                j += 1;
87            }
88            
89            if fits {
90                if y < best_y || (y == best_y && seg.w < best_w) {
91                    best_y = y;
92                    best_idx = Some(i);
93                    best_w = seg.w;
94                }
95            }
96        }
97        
98        if let Some(idx) = best_idx {
99            let x = self.skyline[idx].x;
100            let y = best_y;
101            
102            let new_seg = SkylineSegment { x, y: y + h, w };
103            let mut remaining = w;
104            let mut insert_idx = idx;
105            
106            while remaining > 0 {
107                if self.skyline[insert_idx].w <= remaining {
108                    remaining -= self.skyline[insert_idx].w;
109                    self.skyline.remove(insert_idx);
110                } else {
111                    self.skyline[insert_idx].x += remaining;
112                    self.skyline[insert_idx].w -= remaining;
113                    remaining = 0;
114                }
115            }
116            self.skyline.insert(insert_idx, new_seg);
117            
118            let mut i = 0;
119            while i < self.skyline.len() - 1 {
120                if self.skyline[i].y == self.skyline[i+1].y {
121                    let w = self.skyline[i+1].w;
122                    self.skyline[i].w += w;
123                    self.skyline.remove(i+1);
124                } else {
125                    i += 1;
126                }
127            }
128            
129            return Some((x, y));
130        }
131        None
132    }
133
134    fn clear(&mut self) {
135        self.skyline.clear();
136        self.skyline.push(SkylineSegment { x: 0, y: 0, w: self.width });
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143
144    #[test]
145    fn test_shelf_packer_basic() {
146        let mut packer = YggdrasilPacker::new(100, 100);
147
148        // Pack first item
149        assert_eq!(packer.pack(10, 10), Some((0, 0)));
150        assert_eq!(packer.current_x, 10);
151        assert_eq!(packer.shelf_height, 10);
152
153        // Pack second item on same shelf
154        assert_eq!(packer.pack(20, 15), Some((10, 0)));
155        assert_eq!(packer.current_x, 30);
156        assert_eq!(packer.shelf_height, 15);
157    }
158
159    #[test]
160    fn test_shelf_packer_wrap() {
161        let mut packer = YggdrasilPacker::new(100, 100);
162        packer.pack(60, 10);
163
164        // This should trigger a new shelf
165        assert_eq!(packer.pack(50, 20), Some((0, 10)));
166        assert_eq!(packer.current_x, 50);
167        assert_eq!(packer.shelf_y, 10);
168        assert_eq!(packer.shelf_height, 20);
169    }
170
171    #[test]
172    fn test_shelf_packer_full() {
173        let mut packer = YggdrasilPacker::new(10, 10);
174        assert_eq!(packer.pack(11, 5), None);
175        assert_eq!(packer.pack(5, 11), None);
176    }
177}
178
179use cvkg_core::{LAYOUT_DIRTY, Mesh, Renderer};
180use std::sync::atomic::Ordering;
181const WGSL_SRC: &str = concat!(
182    include_str!("shaders/common.wgsl"),
183    include_str!("shaders/shapes.wgsl"),
184    include_str!("shaders/bifrost.wgsl"),
185    include_str!("shaders/bloom.wgsl")
186);
187
188/// SvgModel — A collection of tessellated triangles representing a vector icon.
189#[derive(Clone, Debug)]
190pub struct SvgModel {
191    pub vertices: Vec<Vertex>,
192    pub indices: Vec<u32>,
193    pub view_box: Rect,
194}
195
196// ShieldWall — re-export AccessKit types so callers can build tree updates
197// without depending on accesskit directly.
198pub use accesskit::{
199    ActionHandler, ActionRequest, ActivationHandler, DeactivationHandler, Node, NodeId, Role, Tree,
200    TreeId, TreeUpdate,
201};
202pub use accesskit_winit::Adapter as ShieldWallAdapter;
203
204// Re-export ColorTheme and SceneUniforms for cvkg-render-gpu users
205pub use cvkg_core::{ColorTheme, SceneUniforms};
206
207use lyon::tessellation::{
208    BuffersBuilder, FillOptions, FillTessellator, FillVertex, FillVertexConstructor, VertexBuffers,
209};
210
211#[repr(C)]
212#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
213pub struct Vertex {
214    pub position: [f32; 3],
215    pub normal: [f32; 3],
216    pub uv: [f32; 2],
217    pub color: [f32; 4],
218    pub mode: u32,
219    pub radius: f32,
220    pub slice: [f32; 4],
221    pub logical: [f32; 2],
222    pub size: [f32; 2],
223    pub screen: [f32; 2],
224    pub clip: [f32; 4], // [x, y, width, height]
225    pub translation: [f32; 2],
226    pub scale: [f32; 2],
227    pub rotation: f32,
228    pub tex_index: u32,
229}
230
231/// Represents a single batched GPU draw call.
232/// Batches are broken whenever the active texture or primitive mode changes.
233#[derive(Debug, Clone)]
234struct DrawCall {
235    pub texture_id: Option<u32>,
236    pub scissor_rect: Option<Rect>,
237    pub index_start: u32,
238    pub index_count: u32,
239    /// Material routing tag — determines which pass this draw call is routed to
240    /// in the multi-pass Backdrop Capture pipeline.
241    pub material: cvkg_core::DrawMaterial,
242}
243
244#[derive(Debug, Clone, Copy)]
245struct ShadowState {
246    pub radius: f32,
247    pub color: [f32; 4],
248    pub _offset: [f32; 2],
249}
250
251impl Vertex {
252    const ATTRIBUTES: [wgpu::VertexAttribute; 15] = wgpu::vertex_attr_array![
253        0 => Float32x3, // position
254        1 => Float32x3, // normal
255        2 => Float32x2, // uv
256        3 => Float32x4, // color
257        4 => Uint32,    // mode
258        5 => Float32,   // radius
259        6 => Float32x4, // slice
260        7 => Float32x2, // logical
261        8 => Float32x2, // size
262        9 => Float32x2, // screen
263        10 => Float32x4, // clip
264        11 => Float32x2, // translation
265        12 => Float32x2, // scale
266        13 => Float32,   // rotation
267        14 => Uint32     // tex_index
268    ];
269
270    fn desc() -> wgpu::VertexBufferLayout<'static> {
271        wgpu::VertexBufferLayout {
272            array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
273            step_mode: wgpu::VertexStepMode::Vertex,
274            attributes: &Self::ATTRIBUTES,
275        }
276    }
277}
278
279/// SurtrRenderer implements the high-performance GPU backend.
280#[allow(dead_code)]
281pub struct SurtrRenderer {
282    instance: Arc<wgpu::Instance>,
283    adapter: Arc<wgpu::Adapter>,
284    device: Arc<wgpu::Device>,
285    queue: Arc<wgpu::Queue>,
286
287    // Multi-Window Surface Management
288    surfaces: std::collections::HashMap<winit::window::WindowId, SurfaceContext>,
289    current_window: Option<winit::window::WindowId>,
290    pub headless_context: Option<HeadlessContext>,
291
292    // Mega-Atlas (Shared across all windows)
293    text_engine: cvkg_runic_text::RunicTextEngine,
294    mega_atlas_tex: wgpu::Texture,
295    #[allow(dead_code)]
296    mega_atlas_view: wgpu::TextureView,
297    _mega_atlas_sampler: wgpu::Sampler,
298    mega_atlas_bind_group: wgpu::BindGroup,
299    text_cache: LruCache<u64, (Rect, f32, f32)>,
300    atlas_packer: YggdrasilPacker,
301    image_uv_registry: LruCache<String, Rect>,
302    texture_registry: LruCache<String, u32>,
303    texture_views: Vec<wgpu::TextureView>,
304    dummy_sampler: wgpu::Sampler,
305    svg_cache: LruCache<String, SvgModel>,
306
307    // Niflheim Resources (Shared)
308    dummy_texture_bind_group: wgpu::BindGroup,
309    dummy_env_bind_group: wgpu::BindGroup,
310    texture_bind_group_layout: wgpu::BindGroupLayout,
311    texture_bind_groups: Vec<wgpu::BindGroup>,
312    shared_elements: LruCache<String, cvkg_core::Rect>,
313
314    // The Forge's Anvil (GPU Buffers)
315    vertex_buffer: wgpu::Buffer,
316    index_buffer: wgpu::Buffer,
317    vertices: Vec<Vertex>,
318    indices: Vec<u32>,
319    staging_belt: wgpu::util::StagingBelt,
320    staging_command_buffers: Vec<wgpu::CommandBuffer>,
321    draw_calls: Vec<DrawCall>,
322    current_texture_id: Option<u32>,
323
324    // Opacity & Clip Stacks
325    opacity_stack: Vec<f32>,
326    clip_stack: Vec<Rect>,
327    slice_stack: Vec<(f32, f32)>,
328    shadow_stack: Vec<ShadowState>,
329
330    // The Forge's Heart (Shared Berserker State)
331    theme_buffer: wgpu::Buffer,
332    scene_buffer: wgpu::Buffer,
333    berserker_bind_group: wgpu::BindGroup,
334    #[allow(dead_code)]
335    berserker_bind_group_layout: wgpu::BindGroupLayout,
336    start_time: std::time::Instant,
337    current_theme: ColorTheme,
338    current_scene: SceneUniforms,
339    current_z: f32,
340
341    // Muspelheim Pipelines (Shared)
342    pipeline: wgpu::RenderPipeline,
343    background_pipeline: wgpu::RenderPipeline,
344    bloom_extract_pipeline: wgpu::RenderPipeline,
345    blur_h_pipeline: wgpu::RenderPipeline,
346    blur_v_pipeline: wgpu::RenderPipeline,
347    composite_pipeline: wgpu::RenderPipeline,
348    env_bind_group_layout: wgpu::BindGroupLayout,
349
350    // Telemetry
351    pub telemetry: cvkg_core::TelemetryData,
352
353    /// Configuration for render-loop frame timing and degradation strategies.
354    pub frame_budget: cvkg_core::FrameBudget,
355    /// Instant at the start of the last redraw, used for measuring frame timings.
356    pub last_redraw_start: std::time::Instant,
357    /// Instant at the start of the last frame, used for frame_time_ms calculation.
358    pub last_frame_start: std::time::Instant,
359
360    // VRAM Tracking (Bytes)
361    vram_buffers_bytes: u64,
362    vram_textures_bytes: u64,
363
364    // Debugging
365    _debug_layout: bool,
366
367    // Transform Stack
368    transform_stack: Vec<([f32; 2], [f32; 2], f32)>,
369    /// Whether a redraw has been requested for the next frame.
370    pub redraw_requested: bool,
371
372    // Timestamp Queries (Norse: Skuld = future/time/debt)
373    skuld_queries: Option<wgpu::QuerySet>,
374    skuld_buffer: Option<wgpu::Buffer>,
375    skuld_read_buffer: Option<wgpu::Buffer>,
376    skuld_period: f32,
377    pub last_gpu_time_ns: u64,
378
379    // VDOM node stack for hierarchy tracking
380    vnode_stack: Vec<(Rect, &'static str)>,
381
382    /// Event handlers registered during render passes.
383    /// Maps "event_type" -> list of handlers.
384    event_handlers: std::collections::HashMap<
385        String,
386        Vec<std::sync::Arc<dyn Fn(cvkg_core::Event) + Send + Sync>>,
387    >,
388
389    // ══════════════════════════════════════════════════════════════════════════
390    // Backdrop Capture Architecture — Kawase Blur Pyramid
391    // ══════════════════════════════════════════════════════════════════════════
392    /// Off-screen texture holding the mip-chain blur pyramid for glass sampling.
393    glass_blur_texture: wgpu::Texture,
394    /// Per-mip-level views into glass_blur_texture.
395    glass_blur_views: Vec<wgpu::TextureView>,
396    /// Bind groups for the downsample pass (one per mip level).
397    glass_blur_down_bind_groups: Vec<wgpu::BindGroup>,
398    /// Bind groups for the upsample pass (one per mip level).
399    glass_blur_up_bind_groups: Vec<wgpu::BindGroup>,
400    /// Uniform buffer for blur parameters (src_size, mip_level, kernel_width, mode).
401    glass_blur_uniform_buffer: wgpu::Buffer,
402    /// Render pipeline for Kawase downsample passes.
403    glass_blur_pipeline: wgpu::RenderPipeline,
404    /// Render pipeline for Kawase upsample passes.
405    glass_blur_upsample_pipeline: wgpu::RenderPipeline,
406    /// Bind group layout for blur passes (uniform + texture + sampler).
407    glass_blur_bind_group_layout: wgpu::BindGroupLayout,
408    /// Bind group layout for reading blur output in glass composite pass.
409    glass_output_bind_group_layout: wgpu::BindGroupLayout,
410    /// Current material state — draw calls are tagged with this material.
411    current_draw_material: cvkg_core::DrawMaterial,
412    /// Number of mip levels in the glass blur pyramid.
413    blur_pyramid_mip_count: u32,
414}
415
416struct SurfaceContext {
417    surface: wgpu::Surface<'static>,
418    config: wgpu::SurfaceConfiguration,
419    scene_texture: wgpu::TextureView,
420    scene_bind_group: wgpu::BindGroup,
421    scene_texture_bind_group: wgpu::BindGroup,
422    depth_texture_view: wgpu::TextureView,
423    blur_texture_a: wgpu::TextureView,
424    blur_texture_b: wgpu::TextureView,
425    blur_bind_group_a: wgpu::BindGroup,
426    blur_bind_group_b: wgpu::BindGroup,
427    blur_env_bind_group_a: wgpu::BindGroup,
428    scale_factor: f32,
429    sampler: wgpu::Sampler,
430}
431
432/// HeadlessContext — A rendering target for surface-less execution.
433pub struct HeadlessContext {
434    pub scene_texture: wgpu::TextureView,
435    pub scene_bind_group: wgpu::BindGroup,
436    pub scene_texture_bind_group: wgpu::BindGroup,
437    pub depth_texture_view: wgpu::TextureView,
438    pub blur_texture_a: wgpu::TextureView,
439    pub blur_texture_b: wgpu::TextureView,
440    pub blur_bind_group_a: wgpu::BindGroup,
441    pub blur_bind_group_b: wgpu::BindGroup,
442    pub blur_env_bind_group_a: wgpu::BindGroup,
443    pub scale_factor: f32,
444    pub sampler: wgpu::Sampler,
445    pub width: u32,
446    pub height: u32,
447    pub output_texture: wgpu::Texture,
448    pub output_view: wgpu::TextureView,
449}
450
451const MAX_VERTICES: usize = 100_000;
452const MAX_INDICES: usize = 150_000;
453
454impl SurtrRenderer {
455    /// forge — Initializes the Surtr GPU renderer from a winit window.
456    ///
457    /// This method performs the following:
458    /// 1. Negotiates a wgpu surface and adapter.
459    /// 2. Forges the Muspelheim multi-pass pipeline layouts.
460    /// 3. Initializes the Berserker state buffers and texture registries.
461    pub async fn forge(window: Arc<winit::window::Window>) -> Self {
462        let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
463            backends: wgpu::Backends::all(),
464            flags: wgpu::InstanceFlags::default(),
465            backend_options: wgpu::BackendOptions::default(),
466            display: None,
467            memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
468        });
469
470        let surface = instance
471            .create_surface(window.clone())
472            .expect("Failed to create surface");
473
474        // Request adapter with robust multi-stage fallback for Bumblebee/Optimus compatibility
475        println!("[GPU] Requesting HighPerformance adapter...");
476
477        let mut adapter = None;
478
479        // Manual override for driver/adapter selection (e.g. forcing amdgpu-pro over RADV)
480        if let Ok(filter) = std::env::var("WGPU_ADAPTER_NAME") {
481            let adapters = instance.enumerate_adapters(wgpu::Backends::all()).await;
482            println!("[GPU] Available adapters:");
483            for a in &adapters {
484                let info = a.get_info();
485                println!(
486                    "  - Name: '{}' | Driver: '{}' | Backend: {:?}",
487                    info.name, info.driver, info.backend
488                );
489            }
490
491            adapter = adapters.into_iter().find(|a| {
492                let info = a.get_info();
493                let match_found = info.name.to_lowercase().contains(&filter.to_lowercase())
494                    || info.driver.to_lowercase().contains(&filter.to_lowercase());
495                if match_found {
496                    println!(
497                        "[GPU] Manual selection match: {} | Driver: {}",
498                        info.name, info.driver
499                    );
500                }
501                match_found
502            });
503
504            if adapter.is_some() {
505                println!(
506                    "[GPU] Forced adapter selection via WGPU_ADAPTER_NAME='{}'",
507                    filter
508                );
509            } else {
510                println!(
511                    "[GPU] WGPU_ADAPTER_NAME='{}' provided but no matching adapter found. Falling back...",
512                    filter
513                );
514            }
515        }
516
517        if adapter.is_none() {
518            adapter = instance
519                .request_adapter(&wgpu::RequestAdapterOptions {
520                    power_preference: wgpu::PowerPreference::HighPerformance,
521                    compatible_surface: Some(&surface),
522                    force_fallback_adapter: false,
523                })
524                .await
525                .ok();
526        }
527
528        if adapter.is_none() {
529            println!(
530                "[GPU] HighPerformance adapter failed (possible Bumblebee/Optimus), trying LowPower..."
531            );
532            adapter = instance
533                .request_adapter(&wgpu::RequestAdapterOptions {
534                    power_preference: wgpu::PowerPreference::LowPower,
535                    compatible_surface: Some(&surface),
536                    force_fallback_adapter: false,
537                })
538                .await
539                .ok();
540        }
541
542        if adapter.is_none() {
543            println!("[GPU] Hardware adapters failed, trying Software fallback...");
544            adapter = instance
545                .request_adapter(&wgpu::RequestAdapterOptions {
546                    power_preference: wgpu::PowerPreference::LowPower,
547                    compatible_surface: Some(&surface),
548                    force_fallback_adapter: true,
549                })
550                .await
551                .ok();
552        }
553
554        let adapter = adapter.expect("Failed to find a suitable GPU for Surtr");
555        let info = adapter.get_info();
556        println!(
557            "[GPU] Selected adapter: {} ({:?}) on backend: {:?}",
558            info.name, info.device_type, info.backend
559        );
560        println!("[GPU] Driver info: {} - {}", info.driver, info.driver_info);
561        let supports_timestamps = adapter.features().contains(wgpu::Features::TIMESTAMP_QUERY);
562        let mut required_features = wgpu::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING
563            | wgpu::Features::TEXTURE_BINDING_ARRAY;
564        if supports_timestamps {
565            required_features |= wgpu::Features::TIMESTAMP_QUERY;
566        }
567
568        let (device, queue) = adapter
569            .request_device(&wgpu::DeviceDescriptor {
570                label: Some("Surtr Forge"),
571                required_features,
572                required_limits: wgpu::Limits {
573                    max_bindings_per_bind_group: 256,
574                    max_binding_array_elements_per_shader_stage: 256,
575                    ..wgpu::Limits::default()
576                },
577                memory_hints: wgpu::MemoryHints::default(),
578                experimental_features: wgpu::ExperimentalFeatures::disabled(),
579                trace: wgpu::Trace::Off,
580            })
581            .await
582            .expect("Failed to create Surtr device");
583
584        let instance = Arc::new(instance);
585        let adapter = Arc::new(adapter);
586        
587        device.on_uncaptured_error(Arc::new(|error| {
588            log::error!("[GPU] Uncaptured device error (Device Lost or Panic): {:?}", error);
589            // In a full recovery scenario, we would signal the event loop to rebuild the GPU context
590        }));
591
592        let device = Arc::new(device);
593        let queue = Arc::new(queue);
594
595        let size = window.inner_size();
596        // Ensure we have valid dimensions - Wayland may return 0 for not-yet-committed surfaces
597        let width = if size.width > 0 { size.width } else { 1280 };
598        let height = if size.height > 0 { size.height } else { 720 };
599        let surface_caps = surface.get_capabilities(&adapter);
600        let surface_format = if surface_caps.formats.is_empty() {
601            log::error!("[GPU] CRITICAL: No compatible surface formats found for this adapter!");
602            log::error!(
603                "[GPU] Adapter: {} | Backend: {:?}",
604                adapter.get_info().name,
605                adapter.get_info().backend
606            );
607            // Fallback to a common format to avoid immediate panic, though configuration may still fail
608            wgpu::TextureFormat::Rgba8UnormSrgb
609        } else {
610            surface_caps
611                .formats
612                .iter()
613                .find(|f| f.is_srgb())
614                .copied()
615                .unwrap_or(surface_caps.formats[0])
616        };
617
618        // Dynamic capability selection for robust Wayland/X11 rendering
619        let present_mode = if surface_caps
620            .present_modes
621            .contains(&wgpu::PresentMode::Mailbox)
622        {
623            wgpu::PresentMode::Mailbox
624        } else {
625            log::warn!("[GPU] Mailbox not supported, falling back to Fifo (V-Sync)");
626            wgpu::PresentMode::Fifo
627        };
628
629        let alpha_mode = if surface_caps
630            .alpha_modes
631            .contains(&wgpu::CompositeAlphaMode::PostMultiplied)
632        {
633            wgpu::CompositeAlphaMode::PostMultiplied
634        } else if surface_caps
635            .alpha_modes
636            .contains(&wgpu::CompositeAlphaMode::PreMultiplied)
637        {
638            wgpu::CompositeAlphaMode::PreMultiplied
639        } else {
640            surface_caps.alpha_modes[0]
641        };
642
643        log::info!(
644            "[GPU] Configuring surface: {}x{} | {:?} | {:?}",
645            width,
646            height,
647            present_mode,
648            alpha_mode
649        );
650
651        let config = wgpu::SurfaceConfiguration {
652            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
653            format: surface_format,
654            width,
655            height,
656            present_mode,
657            alpha_mode,
658            view_formats: vec![],
659            desired_maximum_frame_latency: 2,
660        };
661        surface.configure(&device, &config);
662        log::info!("[GPU] Surface configuration successful.");
663
664        let renderer = Self::forge_internal(
665            instance,
666            adapter,
667            device,
668            queue,
669            Some((window, surface, config)),
670            None,
671        )
672        .await;
673        log::info!("[GPU] Forge internal complete.");
674        renderer
675    }
676
677    async fn forge_internal(
678        instance: Arc<wgpu::Instance>,
679        adapter: Arc<wgpu::Adapter>,
680        device: Arc<wgpu::Device>,
681        queue: Arc<wgpu::Queue>,
682        surface_info: Option<(
683            Arc<winit::window::Window>,
684            wgpu::Surface<'static>,
685            wgpu::SurfaceConfiguration,
686        )>,
687        headless_info: Option<(u32, u32, wgpu::TextureFormat)>,
688    ) -> Self {
689        let format = if let Some((_, _, ref config)) = surface_info {
690            config.format
691        } else if let Some((_, _, f)) = headless_info {
692            f
693        } else {
694            wgpu::TextureFormat::Rgba8UnormSrgb
695        };
696
697        let supports_timestamps = adapter.features().contains(wgpu::Features::TIMESTAMP_QUERY);
698        let skuld_period = queue.get_timestamp_period();
699        let (skuld_queries, skuld_buffer, skuld_read_buffer) = if supports_timestamps {
700            let q = device.create_query_set(&wgpu::QuerySetDescriptor {
701                label: Some("Skuld Timestamp Queries"),
702                count: 2,
703                ty: wgpu::QueryType::Timestamp,
704            });
705            let b = device.create_buffer(&wgpu::BufferDescriptor {
706                label: Some("Skuld Query Buffer"),
707                size: 16,
708                usage: wgpu::BufferUsages::QUERY_RESOLVE | wgpu::BufferUsages::COPY_SRC,
709                mapped_at_creation: false,
710            });
711            let rb = device.create_buffer(&wgpu::BufferDescriptor {
712                label: Some("Skuld Read Buffer"),
713                size: 16,
714                usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
715                mapped_at_creation: false,
716            });
717            (Some(q), Some(b), Some(rb))
718        } else {
719            (None, None, None)
720        };
721
722        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
723            label: Some("Muspelheim Main Shader"),
724            source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(WGSL_SRC)),
725        });
726
727        // Niflheim Bind Group Layout (for textures/samplers)
728        let texture_bind_group_layout =
729            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
730                entries: &[
731                    wgpu::BindGroupLayoutEntry {
732                        binding: 0,
733                        visibility: wgpu::ShaderStages::FRAGMENT,
734                        ty: wgpu::BindingType::Texture {
735                            multisampled: false,
736                            view_dimension: wgpu::TextureViewDimension::D2,
737                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
738                        },
739                        count: std::num::NonZeroU32::new(256),
740                    },
741                    wgpu::BindGroupLayoutEntry {
742                        binding: 1,
743                        visibility: wgpu::ShaderStages::FRAGMENT,
744                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
745                        count: None,
746                    },
747                ],
748                label: Some("Niflheim Texture Bind Group Layout"),
749            });
750
751        // Environment Bind Group Layout (for blurred background / Bifrost)
752        // Environment Bind Group Layout (for blurred background / Bifrost)
753        let env_bind_group_layout =
754            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
755                entries: &[
756                    wgpu::BindGroupLayoutEntry {
757                        binding: 0,
758                        visibility: wgpu::ShaderStages::FRAGMENT,
759                        ty: wgpu::BindingType::Texture {
760                            multisampled: false,
761                            view_dimension: wgpu::TextureViewDimension::D2,
762                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
763                        },
764                        count: None,
765                    },
766                    wgpu::BindGroupLayoutEntry {
767                        binding: 1,
768                        visibility: wgpu::ShaderStages::FRAGMENT,
769                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
770                        count: None,
771                    },
772                ],
773                label: Some("Surtr Environment Bind Group Layout"),
774            });
775
776        let berserker_bind_group_layout =
777            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
778                entries: &[
779                    wgpu::BindGroupLayoutEntry {
780                        binding: 0,
781                        visibility: wgpu::ShaderStages::FRAGMENT,
782                        ty: wgpu::BindingType::Buffer {
783                            ty: wgpu::BufferBindingType::Uniform,
784                            has_dynamic_offset: false,
785                            min_binding_size: None,
786                        },
787                        count: None,
788                    },
789                    wgpu::BindGroupLayoutEntry {
790                        binding: 1,
791                        visibility: wgpu::ShaderStages::FRAGMENT | wgpu::ShaderStages::VERTEX,
792                        ty: wgpu::BindingType::Buffer {
793                            ty: wgpu::BufferBindingType::Uniform,
794                            has_dynamic_offset: false,
795                            min_binding_size: None,
796                        },
797                        count: None,
798                    },
799                ],
800                label: Some("Surtr Berserker Bind Group Layout"),
801            });
802
803        // Pipeline setup
804        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
805            label: Some("Surtr Main Pipeline Layout"),
806            bind_group_layouts: &[
807                Some(&texture_bind_group_layout),
808                Some(&env_bind_group_layout),
809                Some(&berserker_bind_group_layout),
810            ],
811            immediate_size: 0,
812        });
813
814        // Specialized layout for post-processing (Bloom Extract, Blur) which only need Group 0 + Globals
815        let post_process_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
816            label: Some("Muspelheim Post Process Layout"),
817            bind_group_layouts: &[
818                Some(&texture_bind_group_layout),
819                Some(&env_bind_group_layout),
820                Some(&berserker_bind_group_layout),
821            ],
822            immediate_size: 0,
823        });
824
825        // Specialized layout for composite (Blur + Scene)
826        let composite_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
827            label: Some("Muspelheim Composite Layout"),
828            bind_group_layouts: &[
829                Some(&texture_bind_group_layout),
830                Some(&env_bind_group_layout),
831                Some(&berserker_bind_group_layout),
832            ],
833            immediate_size: 0,
834        });
835
836        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
837            label: Some("Surtr Main Pipeline"),
838            layout: Some(&pipeline_layout),
839            vertex: wgpu::VertexState {
840                module: &shader,
841                entry_point: Some("vs_main"),
842                buffers: &[Vertex::desc()],
843                compilation_options: wgpu::PipelineCompilationOptions::default(),
844            },
845            fragment: Some(wgpu::FragmentState {
846                module: &shader,
847                entry_point: Some("fs_main"),
848                targets: &[Some(wgpu::ColorTargetState {
849                    format,
850                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
851                    write_mask: wgpu::ColorWrites::ALL,
852                })],
853                compilation_options: wgpu::PipelineCompilationOptions::default(),
854            }),
855            primitive: wgpu::PrimitiveState::default(),
856            depth_stencil: Some(wgpu::DepthStencilState {
857                format: wgpu::TextureFormat::Depth32Float,
858                depth_write_enabled: Some(true),
859                depth_compare: Some(wgpu::CompareFunction::LessEqual),
860                stencil: wgpu::StencilState::default(),
861                bias: wgpu::DepthBiasState::default(),
862            }),
863            multisample: wgpu::MultisampleState::default(),
864            multiview_mask: None,
865            cache: None,
866        });
867
868        let background_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
869            label: Some("Surtr Background Pipeline"),
870            layout: Some(&pipeline_layout),
871            vertex: wgpu::VertexState {
872                module: &shader,
873                entry_point: Some("vs_fullscreen"),
874                buffers: &[],
875                compilation_options: wgpu::PipelineCompilationOptions::default(),
876            },
877            fragment: Some(wgpu::FragmentState {
878                module: &shader,
879                entry_point: Some("fs_background"),
880                targets: &[Some(wgpu::ColorTargetState {
881                    format,
882                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
883                    write_mask: wgpu::ColorWrites::ALL,
884                })],
885                compilation_options: wgpu::PipelineCompilationOptions::default(),
886            }),
887            primitive: wgpu::PrimitiveState::default(),
888            depth_stencil: Some(wgpu::DepthStencilState {
889                format: wgpu::TextureFormat::Depth32Float,
890                depth_write_enabled: Some(true),
891                depth_compare: Some(wgpu::CompareFunction::Always),
892                stencil: wgpu::StencilState::default(),
893                bias: wgpu::DepthBiasState::default(),
894            }),
895            multisample: wgpu::MultisampleState::default(),
896            multiview_mask: None,
897            cache: None,
898        });
899
900        // Muspelheim Bloom Extract Pipeline
901        let bloom_extract_pipeline =
902            device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
903                label: Some("Muspelheim Bloom Extract"),
904                layout: Some(&post_process_layout),
905                vertex: wgpu::VertexState {
906                    module: &shader,
907                    entry_point: Some("vs_fullscreen"),
908                    buffers: &[],
909                    compilation_options: wgpu::PipelineCompilationOptions::default(),
910                },
911                fragment: Some(wgpu::FragmentState {
912                    module: &shader,
913                    entry_point: Some("fs_bloom_extract"),
914                    targets: &[Some(wgpu::ColorTargetState {
915                        format,
916                        blend: None,
917                        write_mask: wgpu::ColorWrites::ALL,
918                    })],
919                    compilation_options: wgpu::PipelineCompilationOptions::default(),
920                }),
921                primitive: wgpu::PrimitiveState::default(),
922                depth_stencil: None,
923                multisample: wgpu::MultisampleState::default(),
924                multiview_mask: None,
925                cache: None,
926            });
927
928        // Muspelheim Blur Pipelines (H and V)
929        let blur_h_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
930            label: Some("Muspelheim Horizontal Blur"),
931            layout: Some(&post_process_layout),
932            vertex: wgpu::VertexState {
933                module: &shader,
934                entry_point: Some("vs_fullscreen"),
935                buffers: &[],
936                compilation_options: wgpu::PipelineCompilationOptions::default(),
937            },
938            fragment: Some(wgpu::FragmentState {
939                module: &shader,
940                entry_point: Some("fs_blur_h"),
941                targets: &[Some(wgpu::ColorTargetState {
942                    format,
943                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
944                    write_mask: wgpu::ColorWrites::ALL,
945                })],
946                compilation_options: wgpu::PipelineCompilationOptions::default(),
947            }),
948            primitive: wgpu::PrimitiveState::default(),
949            depth_stencil: None,
950            multisample: wgpu::MultisampleState::default(),
951            multiview_mask: None,
952            cache: None,
953        });
954
955        let blur_v_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
956            label: Some("Muspelheim Vertical Blur"),
957            layout: Some(&post_process_layout),
958            vertex: wgpu::VertexState {
959                module: &shader,
960                entry_point: Some("vs_fullscreen"),
961                buffers: &[],
962                compilation_options: wgpu::PipelineCompilationOptions::default(),
963            },
964            fragment: Some(wgpu::FragmentState {
965                module: &shader,
966                entry_point: Some("fs_blur_v"),
967                targets: &[Some(wgpu::ColorTargetState {
968                    format,
969                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
970                    write_mask: wgpu::ColorWrites::ALL,
971                })],
972                compilation_options: wgpu::PipelineCompilationOptions::default(),
973            }),
974            primitive: wgpu::PrimitiveState::default(),
975            depth_stencil: None,
976            multisample: wgpu::MultisampleState::default(),
977            multiview_mask: None,
978            cache: None,
979        });
980
981        // Muspelheim Composite Pipeline (additive blend onto screen)
982        let composite_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
983            label: Some("Muspelheim Composite"),
984            layout: Some(&composite_layout),
985            vertex: wgpu::VertexState {
986                module: &shader,
987                entry_point: Some("vs_fullscreen"),
988                buffers: &[],
989                compilation_options: wgpu::PipelineCompilationOptions::default(),
990            },
991            fragment: Some(wgpu::FragmentState {
992                module: &shader,
993                entry_point: Some("fs_composite"),
994                targets: &[Some(wgpu::ColorTargetState {
995                    format,
996                    // Additive blend: src + dst — glow lights up the scene
997                    blend: Some(wgpu::BlendState {
998                        color: wgpu::BlendComponent {
999                            src_factor: wgpu::BlendFactor::One,
1000                            dst_factor: wgpu::BlendFactor::One,
1001                            operation: wgpu::BlendOperation::Add,
1002                        },
1003                        alpha: wgpu::BlendComponent {
1004                            src_factor: wgpu::BlendFactor::One,
1005                            dst_factor: wgpu::BlendFactor::One,
1006                            operation: wgpu::BlendOperation::Add,
1007                        },
1008                    }),
1009                    write_mask: wgpu::ColorWrites::ALL,
1010                })],
1011                compilation_options: wgpu::PipelineCompilationOptions::default(),
1012            }),
1013            primitive: wgpu::PrimitiveState::default(),
1014            depth_stencil: None,
1015            multisample: wgpu::MultisampleState::default(),
1016            multiview_mask: None,
1017            cache: None,
1018        });
1019
1020        // Forge the Mega-Atlas (4096x4096 RGBA for production batching)
1021        let mega_atlas_tex = device.create_texture(&wgpu::TextureDescriptor {
1022            label: Some("Surtr Mega-Atlas"),
1023            size: wgpu::Extent3d {
1024                width: 4096,
1025                height: 4096,
1026                depth_or_array_layers: 1,
1027            },
1028            mip_level_count: 1,
1029            sample_count: 1,
1030            dimension: wgpu::TextureDimension::D2,
1031            format: wgpu::TextureFormat::Rgba8UnormSrgb,
1032            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1033            view_formats: &[],
1034        });
1035        let mega_atlas_view_obj =
1036            mega_atlas_tex.create_view(&wgpu::TextureViewDescriptor::default());
1037        let text_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1038            address_mode_u: wgpu::AddressMode::ClampToEdge,
1039            address_mode_v: wgpu::AddressMode::ClampToEdge,
1040            mag_filter: wgpu::FilterMode::Linear, // Use linear for images
1041            min_filter: wgpu::FilterMode::Linear,
1042            ..Default::default()
1043        });
1044
1045        // Forge the Niflheim Dummy Texture (1x1 White)
1046        let dummy_size = wgpu::Extent3d {
1047            width: 1,
1048            height: 1,
1049            depth_or_array_layers: 1,
1050        };
1051        let dummy_texture = device.create_texture(&wgpu::TextureDescriptor {
1052            label: Some("Niflheim Dummy Texture"),
1053            size: dummy_size,
1054            mip_level_count: 1,
1055            sample_count: 1,
1056            dimension: wgpu::TextureDimension::D2,
1057            format: wgpu::TextureFormat::Rgba8UnormSrgb,
1058            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1059            view_formats: &[],
1060        });
1061        queue.write_texture(
1062            wgpu::TexelCopyTextureInfo {
1063                texture: &dummy_texture,
1064                mip_level: 0,
1065                origin: wgpu::Origin3d::ZERO,
1066                aspect: wgpu::TextureAspect::All,
1067            },
1068            &[0, 0, 0, 255],
1069            wgpu::TexelCopyBufferLayout {
1070                offset: 0,
1071                bytes_per_row: Some(4),
1072                rows_per_image: Some(1),
1073            },
1074            dummy_size,
1075        );
1076
1077        let dummy_view = dummy_texture.create_view(&wgpu::TextureViewDescriptor::default());
1078        let dummy_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1079            address_mode_u: wgpu::AddressMode::ClampToEdge,
1080            address_mode_v: wgpu::AddressMode::ClampToEdge,
1081            address_mode_w: wgpu::AddressMode::ClampToEdge,
1082            mag_filter: wgpu::FilterMode::Linear,
1083            min_filter: wgpu::FilterMode::Nearest,
1084            mipmap_filter: wgpu::MipmapFilterMode::Nearest,
1085            ..Default::default()
1086        });
1087
1088        let mut texture_views_list: Vec<wgpu::TextureView> =
1089            (0..256).map(|_| dummy_view.clone()).collect();
1090        texture_views_list[0] = mega_atlas_view_obj.clone();
1091
1092        let views_refs: Vec<&wgpu::TextureView> = texture_views_list.iter().collect();
1093        let mega_atlas_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1094            layout: &texture_bind_group_layout,
1095            entries: &[
1096                wgpu::BindGroupEntry {
1097                    binding: 0,
1098                    resource: wgpu::BindingResource::TextureViewArray(&views_refs),
1099                },
1100                wgpu::BindGroupEntry {
1101                    binding: 1,
1102                    resource: wgpu::BindingResource::Sampler(&text_sampler),
1103                },
1104            ],
1105            label: Some("Mega-Atlas Bind Group"),
1106        });
1107
1108        let dummy_views_refs: Vec<&wgpu::TextureView> = (0..256).map(|_| &dummy_view).collect();
1109        let dummy_texture_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1110            layout: &texture_bind_group_layout,
1111            entries: &[
1112                wgpu::BindGroupEntry {
1113                    binding: 0,
1114                    resource: wgpu::BindingResource::TextureViewArray(&dummy_views_refs),
1115                },
1116                wgpu::BindGroupEntry {
1117                    binding: 1,
1118                    resource: wgpu::BindingResource::Sampler(&dummy_sampler),
1119                },
1120            ],
1121            label: Some("Dummy Texture Bind Group"),
1122        });
1123
1124        let dummy_env_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1125            layout: &env_bind_group_layout,
1126            entries: &[
1127                wgpu::BindGroupEntry {
1128                    binding: 0,
1129                    resource: wgpu::BindingResource::TextureView(&dummy_view),
1130                },
1131                wgpu::BindGroupEntry {
1132                    binding: 1,
1133                    resource: wgpu::BindingResource::Sampler(&dummy_sampler),
1134                },
1135            ],
1136            label: Some("Dummy Env Bind Group"),
1137        });
1138
1139        let mut texture_registry = std::collections::HashMap::new();
1140        let mut texture_bind_groups = Vec::new();
1141
1142        texture_registry.insert("__mega_atlas".to_string(), 0);
1143        texture_bind_groups.push(mega_atlas_bind_group.clone());
1144
1145        // Forge the Anvil (Buffers)
1146        let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
1147            label: Some("Surtr Vertex Anvil"),
1148            size: (MAX_VERTICES * std::mem::size_of::<Vertex>()) as u64,
1149            usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
1150            mapped_at_creation: false,
1151        });
1152
1153        let index_buffer = device.create_buffer(&wgpu::BufferDescriptor {
1154            label: Some("Surtr Index Anvil"),
1155            size: (MAX_INDICES * std::mem::size_of::<u32>()) as u64,
1156            usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
1157            mapped_at_creation: false,
1158        });
1159
1160        // Forge the Heart (Berserker Uniforms)
1161        let current_theme = ColorTheme::default();
1162        use wgpu::util::DeviceExt;
1163        let theme_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
1164            label: Some("Surtr Theme Buffer"),
1165            contents: bytemuck::bytes_of(&current_theme),
1166            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
1167        });
1168
1169        let (width, height, scale_factor) = if let Some((ref window, _, ref config)) = surface_info
1170        {
1171            (config.width, config.height, window.scale_factor() as f32)
1172        } else if let Some((w, h, _)) = headless_info {
1173            (w, h, 1.0)
1174        } else {
1175            (1280, 720, 1.0)
1176        };
1177
1178        let mut current_scene =
1179            SceneUniforms::new(width as f32 / scale_factor, height as f32 / scale_factor);
1180        current_scene.scale_factor = scale_factor;
1181        let scene_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
1182            label: Some("Surtr Scene Buffer"),
1183            contents: bytemuck::bytes_of(&current_scene),
1184            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
1185        });
1186
1187        let berserker_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1188            layout: &berserker_bind_group_layout,
1189            entries: &[
1190                wgpu::BindGroupEntry {
1191                    binding: 0,
1192                    resource: theme_buffer.as_entire_binding(),
1193                },
1194                wgpu::BindGroupEntry {
1195                    binding: 1,
1196                    resource: scene_buffer.as_entire_binding(),
1197                },
1198            ],
1199            label: Some("Surtr Berserker Bind Group"),
1200        });
1201
1202        let mut surfaces = std::collections::HashMap::new();
1203        let mut current_window = None;
1204        let mut headless_context = None;
1205
1206        if let Some((window, surface, config)) = surface_info {
1207            let window_id = window.id();
1208            let ctx = Self::create_surface_context(
1209                &device,
1210                surface,
1211                config,
1212                &env_bind_group_layout,
1213                &texture_bind_group_layout,
1214                scale_factor,
1215            );
1216            surfaces.insert(window_id, ctx);
1217            current_window = Some(window_id);
1218        } else if let Some((w, h, f)) = headless_info {
1219            headless_context = Some(Self::create_headless_context(
1220                &device,
1221                w,
1222                h,
1223                f,
1224                &env_bind_group_layout,
1225                &texture_bind_group_layout,
1226            ));
1227        }
1228
1229        let staging_belt = wgpu::util::StagingBelt::new((*device).clone(), 1024 * 1024);
1230
1231        // Clone bind group layouts before they are moved into the Self struct
1232        let glass_blur_bind_group_layout = env_bind_group_layout.clone();
1233        let glass_output_bind_group_layout = env_bind_group_layout.clone();
1234        let glass_blur_pipeline = pipeline.clone();
1235        let glass_blur_upsample_pipeline = pipeline.clone();
1236
1237        // Create glass blur pyramid resources (must be before Self struct, which moves device)
1238        let glass_blur_texture = device.create_texture(&wgpu::TextureDescriptor {
1239            label: Some("Glass Blur Pyramid"),
1240            size: wgpu::Extent3d {
1241                width: width.max(1),
1242                height: height.max(1),
1243                depth_or_array_layers: 1,
1244            },
1245            mip_level_count: 1,
1246            sample_count: 1,
1247            dimension: wgpu::TextureDimension::D2,
1248            format: wgpu::TextureFormat::Rgba16Float,
1249            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT,
1250            view_formats: &[],
1251        });
1252        let glass_blur_uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
1253            label: Some("Glass Blur Uniform"),
1254            size: 64,
1255            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
1256            mapped_at_creation: false,
1257        });
1258
1259        Self {
1260            instance,
1261            adapter,
1262            device,
1263            queue,
1264            surfaces,
1265            current_window,
1266            headless_context,
1267            pipeline,
1268            bloom_extract_pipeline,
1269            blur_h_pipeline,
1270            blur_v_pipeline,
1271            composite_pipeline,
1272            env_bind_group_layout,
1273            text_engine: cvkg_runic_text::RunicTextEngine::default(),
1274            mega_atlas_tex,
1275            mega_atlas_view: mega_atlas_view_obj,
1276            _mega_atlas_sampler: text_sampler,
1277            mega_atlas_bind_group,
1278            text_cache: LruCache::new(NonZeroUsize::new(2048).unwrap()),
1279            atlas_packer: YggdrasilPacker::new(4096, 4096),
1280            image_uv_registry: LruCache::new(NonZeroUsize::new(256).unwrap()),
1281            texture_registry: LruCache::new(NonZeroUsize::new(255).unwrap()),
1282            texture_views: texture_views_list,
1283            dummy_sampler,
1284            svg_cache: LruCache::new(NonZeroUsize::new(128).unwrap()),
1285            dummy_texture_bind_group,
1286            dummy_env_bind_group,
1287            texture_bind_group_layout,
1288            texture_bind_groups,
1289            shared_elements: LruCache::new(NonZeroUsize::new(1024).unwrap()),
1290            vertex_buffer,
1291            index_buffer,
1292            vertices: Vec::with_capacity(MAX_VERTICES),
1293            indices: Vec::with_capacity(MAX_INDICES),
1294            draw_calls: Vec::new(),
1295            current_texture_id: None,
1296            opacity_stack: vec![1.0],
1297            clip_stack: Vec::new(),
1298            slice_stack: Vec::new(),
1299            shadow_stack: Vec::new(),
1300            theme_buffer,
1301            scene_buffer,
1302            berserker_bind_group,
1303            berserker_bind_group_layout,
1304            start_time: std::time::Instant::now(),
1305            current_theme,
1306            current_scene,
1307            background_pipeline,
1308            current_z: 0.0,
1309            telemetry: cvkg_core::TelemetryData::default(),
1310            last_frame_start: std::time::Instant::now(),
1311            last_redraw_start: std::time::Instant::now(),
1312            frame_budget: cvkg_core::FrameBudget::default(),
1313            vram_buffers_bytes: 0,
1314            vram_textures_bytes: 0,
1315            _debug_layout: false,
1316            transform_stack: Vec::new(),
1317            redraw_requested: false,
1318            skuld_queries,
1319            skuld_buffer,
1320            skuld_read_buffer,
1321            skuld_period,
1322            last_gpu_time_ns: 0,
1323            vnode_stack: Vec::new(),
1324            event_handlers: std::collections::HashMap::new(),
1325            staging_belt,
1326            staging_command_buffers: Vec::new(),
1327            // Backdrop Capture Architecture — Kawase Blur Pyramid
1328            glass_blur_texture,
1329            glass_blur_views: Vec::new(),
1330            glass_blur_down_bind_groups: Vec::new(),
1331            glass_blur_up_bind_groups: Vec::new(),
1332            glass_blur_uniform_buffer,
1333            glass_blur_pipeline,
1334            glass_blur_upsample_pipeline,
1335            glass_blur_bind_group_layout,
1336            glass_output_bind_group_layout,
1337            current_draw_material: cvkg_core::DrawMaterial::Opaque,
1338            blur_pyramid_mip_count: 1,
1339        }
1340    }
1341
1342    fn rebuild_texture_array_bind_group(&mut self) {
1343        let views: Vec<&wgpu::TextureView> = self.texture_views.iter().collect();
1344        self.mega_atlas_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1345            layout: &self.texture_bind_group_layout,
1346            entries: &[
1347                wgpu::BindGroupEntry {
1348                    binding: 0,
1349                    resource: wgpu::BindingResource::TextureViewArray(&views),
1350                },
1351                wgpu::BindGroupEntry {
1352                    binding: 1,
1353                    resource: wgpu::BindingResource::Sampler(&self.dummy_sampler),
1354                },
1355            ],
1356            label: Some("Surtr Texture Array Bind Group"),
1357        });
1358    }
1359
1360    /// Update VRAM telemetry based on currently allocated resources.
1361    fn update_vram_telemetry(&mut self) {
1362        // Calculate Buffer VRAM
1363        let mut buffer_bytes = 0;
1364        buffer_bytes += (MAX_VERTICES * std::mem::size_of::<Vertex>()) as u64;
1365        buffer_bytes += (MAX_INDICES * std::mem::size_of::<u32>()) as u64;
1366        buffer_bytes += std::mem::size_of::<cvkg_core::ColorTheme>() as u64;
1367        buffer_bytes += std::mem::size_of::<cvkg_core::SceneUniforms>() as u64;
1368        self.vram_buffers_bytes = buffer_bytes;
1369
1370        // Calculate Texture VRAM
1371        let mut texture_bytes = 0;
1372        texture_bytes += 4096 * 4096 * 4; // Mega Atlas (RGBA8)
1373        texture_bytes += 4; // Dummy (RGBA8)
1374
1375        // Add Texture Array VRAM
1376        for _ in &self.texture_views {
1377            // Approximation: 1MB per texture
1378            texture_bytes += 1024 * 1024 * 4;
1379        }
1380
1381        for ctx in self.surfaces.values() {
1382            let bpp = 4;
1383            let surface_bytes = (ctx.config.width * ctx.config.height * bpp) as u64;
1384            texture_bytes += surface_bytes * 3; // scene, blur_a, blur_b
1385            texture_bytes += (ctx.config.width * ctx.config.height * 4) as u64; // depth (Depth32Float)
1386        }
1387
1388        self.vram_textures_bytes = texture_bytes;
1389
1390        self.telemetry.vram_buffers_mb = buffer_bytes as f32 / 1_048_576.0;
1391        self.telemetry.vram_textures_mb = texture_bytes as f32 / 1_048_576.0;
1392        self.telemetry.vram_pipelines_mb = 0.0;
1393        self.telemetry.vram_usage_mb =
1394            self.telemetry.vram_buffers_mb + self.telemetry.vram_textures_mb;
1395    }
1396
1397    /// Get real-time performance telemetry.
1398    pub fn get_telemetry(&self) -> cvkg_core::TelemetryData {
1399        self.telemetry.clone()
1400    }
1401
1402    /// resize — Reconfigures a specific surface and its internal textures.
1403    pub fn resize(
1404        &mut self,
1405        window_id: winit::window::WindowId,
1406        width: u32,
1407        height: u32,
1408        scale_factor: f32,
1409    ) {
1410        if width > 0
1411            && height > 0
1412            && let Some(ctx) = self.surfaces.get_mut(&window_id)
1413        {
1414            ctx.config.width = width;
1415            ctx.config.height = height;
1416            ctx.scale_factor = scale_factor;
1417            ctx.surface.configure(&self.device, &ctx.config);
1418
1419            // Re-create Muspelheim textures for this surface
1420            let texture_desc = wgpu::TextureDescriptor {
1421                label: Some("Surtr Scene Texture"),
1422                size: wgpu::Extent3d {
1423                    width,
1424                    height,
1425                    depth_or_array_layers: 1,
1426                },
1427                mip_level_count: 1,
1428                sample_count: 1,
1429                dimension: wgpu::TextureDimension::D2,
1430                format: ctx.config.format,
1431                usage: wgpu::TextureUsages::RENDER_ATTACHMENT
1432                    | wgpu::TextureUsages::TEXTURE_BINDING,
1433                view_formats: &[],
1434            };
1435
1436            let scene_tex = self.device.create_texture(&texture_desc);
1437            ctx.scene_texture = scene_tex.create_view(&wgpu::TextureViewDescriptor::default());
1438
1439            let blur_tex_a = self.device.create_texture(&texture_desc);
1440            ctx.blur_texture_a = blur_tex_a.create_view(&wgpu::TextureViewDescriptor::default());
1441
1442            let blur_tex_b = self.device.create_texture(&texture_desc);
1443            ctx.blur_texture_b = blur_tex_b.create_view(&wgpu::TextureViewDescriptor::default());
1444
1445            // Re-create bind groups for this surface
1446            ctx.scene_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1447                layout: &self.env_bind_group_layout,
1448                entries: &[
1449                    wgpu::BindGroupEntry {
1450                        binding: 0,
1451                        resource: wgpu::BindingResource::TextureView(&ctx.scene_texture),
1452                    },
1453                    wgpu::BindGroupEntry {
1454                        binding: 1,
1455                        resource: wgpu::BindingResource::Sampler(&ctx.sampler),
1456                    },
1457                ],
1458                label: Some("Scene Bind Group Resize"),
1459            });
1460
1461            let scene_views: Vec<&wgpu::TextureView> =
1462                (0..256).map(|_| &ctx.scene_texture).collect();
1463            ctx.scene_texture_bind_group =
1464                self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1465                    layout: &self.texture_bind_group_layout,
1466                    entries: &[
1467                        wgpu::BindGroupEntry {
1468                            binding: 0,
1469                            resource: wgpu::BindingResource::TextureViewArray(&scene_views),
1470                        },
1471                        wgpu::BindGroupEntry {
1472                            binding: 1,
1473                            resource: wgpu::BindingResource::Sampler(&ctx.sampler),
1474                        },
1475                    ],
1476                    label: Some("Scene Texture Bind Group Resize"),
1477                });
1478
1479            let blur_views_a: Vec<&wgpu::TextureView> =
1480                (0..256).map(|_| &ctx.blur_texture_a).collect();
1481            ctx.blur_bind_group_a = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1482                layout: &self.texture_bind_group_layout,
1483                entries: &[
1484                    wgpu::BindGroupEntry {
1485                        binding: 0,
1486                        resource: wgpu::BindingResource::TextureViewArray(&blur_views_a),
1487                    },
1488                    wgpu::BindGroupEntry {
1489                        binding: 1,
1490                        resource: wgpu::BindingResource::Sampler(&ctx.sampler),
1491                    },
1492                ],
1493                label: Some("Blur Bind Group A Resize"),
1494            });
1495
1496            let blur_views_b: Vec<&wgpu::TextureView> =
1497                (0..256).map(|_| &ctx.blur_texture_b).collect();
1498            ctx.blur_bind_group_b = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1499                layout: &self.texture_bind_group_layout,
1500                entries: &[
1501                    wgpu::BindGroupEntry {
1502                        binding: 0,
1503                        resource: wgpu::BindingResource::TextureViewArray(&blur_views_b),
1504                    },
1505                    wgpu::BindGroupEntry {
1506                        binding: 1,
1507                        resource: wgpu::BindingResource::Sampler(&ctx.sampler),
1508                    },
1509                ],
1510                label: Some("Blur Bind Group B Resize"),
1511            });
1512
1513            ctx.blur_env_bind_group_a = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1514                layout: &self.env_bind_group_layout,
1515                entries: &[
1516                    wgpu::BindGroupEntry {
1517                        binding: 0,
1518                        resource: wgpu::BindingResource::TextureView(&ctx.blur_texture_a),
1519                    },
1520                    wgpu::BindGroupEntry {
1521                        binding: 1,
1522                        resource: wgpu::BindingResource::Sampler(&ctx.sampler),
1523                    },
1524                ],
1525                label: Some("Blur Env Bind Group A Resize"),
1526            });
1527
1528            let depth_texture = self.device.create_texture(&wgpu::TextureDescriptor {
1529                label: Some("Surtr Depth Texture"),
1530                size: wgpu::Extent3d {
1531                    width,
1532                    height,
1533                    depth_or_array_layers: 1,
1534                },
1535                mip_level_count: 1,
1536                sample_count: 1,
1537                dimension: wgpu::TextureDimension::D2,
1538                format: wgpu::TextureFormat::Depth32Float,
1539                usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1540                view_formats: &[],
1541            });
1542            ctx.depth_texture_view =
1543                depth_texture.create_view(&wgpu::TextureViewDescriptor::default());
1544        }
1545    }
1546
1547    /// begin_frame_headless — Strike the flaming sword to begin a new GPU frame for headless rendering.
1548    pub fn begin_frame_headless(&mut self) -> wgpu::CommandEncoder {
1549        self.current_window = None;
1550        self.vertices.clear();
1551        self.indices.clear();
1552        self.draw_calls.clear();
1553        self.shared_elements.clear();
1554        self.current_texture_id = None;
1555        self.opacity_stack = vec![1.0];
1556        self.clip_stack.clear();
1557        self.slice_stack.clear();
1558        self.current_z = 0.0;
1559        self.vnode_stack.clear();
1560        self.event_handlers.clear();
1561
1562        self.last_frame_start = std::time::Instant::now();
1563        self.telemetry.draw_calls = 0;
1564        self.telemetry.vertices = 0;
1565
1566        let ctx = self
1567            .headless_context
1568            .as_ref()
1569            .expect("Headless context not initialized");
1570        let time = self.start_time.elapsed().as_secs_f32();
1571        let logical_w = ctx.width as f32 / ctx.scale_factor;
1572        let logical_h = ctx.height as f32 / ctx.scale_factor;
1573        let dt = time - self.current_scene.time;
1574        self.current_scene.time = time;
1575        self.current_scene.delta_time = dt;
1576        self.current_scene.resolution = [logical_w, logical_h];
1577        self.current_scene.scale_factor = ctx.scale_factor;
1578        self.current_scene.proj =
1579            glam::Mat4::orthographic_lh(0.0, logical_w, logical_h, 0.0, -1000.0, 1000.0);
1580
1581        self.queue.write_buffer(
1582            &self.scene_buffer,
1583            0,
1584            bytemuck::bytes_of(&self.current_scene),
1585        );
1586
1587        self.device
1588            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1589                label: Some("Surtr Headless Command Encoder"),
1590            })
1591    }
1592
1593    /// begin_frame — Strike the flaming sword to begin a new GPU frame for a specific window.
1594    pub fn begin_frame(&mut self, window_id: winit::window::WindowId) -> wgpu::CommandEncoder {
1595        // Skuld: Read the timestamps from the previous frame
1596        if let Some(rb) = &self.skuld_read_buffer {
1597            let slice = rb.slice(..);
1598            let (tx, rx) = std::sync::mpsc::channel();
1599            slice.map_async(wgpu::MapMode::Read, move |r| tx.send(r).unwrap());
1600            
1601            // Poll to ensure mapping is complete
1602            self.device.poll(wgpu::PollType::Wait {
1603                submission_index: None,
1604                timeout: None,
1605            }).unwrap();
1606            
1607            if rx.recv().is_ok() {
1608                let data = slice.get_mapped_range();
1609                let timestamps: [u64; 2] = bytemuck::cast_slice(&data).try_into().unwrap_or([0, 0]);
1610                drop(data);
1611                rb.unmap();
1612                
1613                if timestamps[1] > timestamps[0] {
1614                    let diff_ticks = timestamps[1] - timestamps[0];
1615                    self.last_gpu_time_ns = (diff_ticks as f64 * self.skuld_period as f64) as u64;
1616                    // println!("[Skuld] GPU Time: {} ms", self.last_gpu_time_ns as f64 / 1_000_000.0);
1617                }
1618            }
1619        }
1620
1621        self.staging_belt.recall();
1622        self.current_window = Some(window_id);
1623        self.vertices.clear();
1624        self.indices.clear();
1625        self.draw_calls.clear();
1626        self.shared_elements.clear();
1627        self.current_texture_id = None;
1628        self.opacity_stack = vec![1.0];
1629        self.clip_stack.clear();
1630        self.slice_stack.clear();
1631        self.current_z = 0.0;
1632        self.vnode_stack.clear();
1633        self.event_handlers.clear();
1634
1635        self.last_frame_start = std::time::Instant::now();
1636        self.telemetry.draw_calls = 0;
1637        self.telemetry.vertices = 0;
1638
1639        let ctx = self
1640            .surfaces
1641            .get(&window_id)
1642            .expect("Window not registered");
1643        let time = self.start_time.elapsed().as_secs_f32();
1644        let logical_w = ctx.config.width as f32 / ctx.scale_factor;
1645        let logical_h = ctx.config.height as f32 / ctx.scale_factor;
1646        let dt = time - self.current_scene.time;
1647        self.current_scene.time = time;
1648        self.current_scene.delta_time = dt;
1649        self.current_scene.resolution = [logical_w, logical_h];
1650        self.current_scene.scale_factor = ctx.scale_factor;
1651        self.current_scene.proj =
1652            glam::Mat4::orthographic_lh(0.0, logical_w, logical_h, 0.0, -1000.0, 1000.0);
1653
1654        self.queue.write_buffer(
1655            &self.scene_buffer,
1656            0,
1657            bytemuck::bytes_of(&self.current_scene),
1658        );
1659
1660        self.device
1661            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1662                label: Some("Surtr Command Encoder"),
1663            })
1664    }
1665
1666    /// register_window — Attaches a new OS window to the shared GPU context.
1667    pub fn register_window(&mut self, window: Arc<winit::window::Window>) {
1668        let size = window.inner_size();
1669        let surface = self
1670            .instance
1671            .create_surface(window.clone())
1672            .expect("Failed to create surface");
1673        let caps = surface.get_capabilities(&self.adapter);
1674        let format = caps.formats[0];
1675        let config = wgpu::SurfaceConfiguration {
1676            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1677            format,
1678            width: size.width,
1679            height: size.height,
1680            present_mode: wgpu::PresentMode::Mailbox,
1681            alpha_mode: caps.alpha_modes[0],
1682            view_formats: vec![],
1683            desired_maximum_frame_latency: 2,
1684        };
1685        surface.configure(&self.device, &config);
1686
1687        let ctx = Self::create_surface_context(
1688            &self.device,
1689            surface,
1690            config,
1691            &self.env_bind_group_layout,
1692            &self.texture_bind_group_layout,
1693            window.scale_factor() as f32,
1694        );
1695
1696        self.surfaces.insert(window.id(), ctx);
1697    }
1698
1699    fn create_headless_context(
1700        device: &wgpu::Device,
1701        width: u32,
1702        height: u32,
1703        format: wgpu::TextureFormat,
1704        env_bind_group_layout: &wgpu::BindGroupLayout,
1705        texture_bind_group_layout: &wgpu::BindGroupLayout,
1706    ) -> HeadlessContext {
1707        let texture_desc = wgpu::TextureDescriptor {
1708            label: Some("Surtr Headless Scene Texture"),
1709            size: wgpu::Extent3d {
1710                width,
1711                height,
1712                depth_or_array_layers: 1,
1713            },
1714            mip_level_count: 1,
1715            sample_count: 1,
1716            dimension: wgpu::TextureDimension::D2,
1717            format,
1718            usage: wgpu::TextureUsages::RENDER_ATTACHMENT
1719                | wgpu::TextureUsages::TEXTURE_BINDING
1720                | wgpu::TextureUsages::COPY_SRC,
1721            view_formats: &[],
1722        };
1723
1724        let scene_tex = device.create_texture(&texture_desc);
1725        let scene_texture = scene_tex.create_view(&wgpu::TextureViewDescriptor::default());
1726
1727        let blur_width = (width / 2).max(1);
1728        let blur_height = (height / 2).max(1);
1729        let blur_texture_desc = wgpu::TextureDescriptor {
1730            label: Some("Surtr Blur Texture"),
1731            size: wgpu::Extent3d {
1732                width: blur_width,
1733                height: blur_height,
1734                depth_or_array_layers: 1,
1735            },
1736            mip_level_count: 1,
1737            sample_count: 1,
1738            dimension: wgpu::TextureDimension::D2,
1739            format,
1740            usage: wgpu::TextureUsages::RENDER_ATTACHMENT
1741                | wgpu::TextureUsages::TEXTURE_BINDING
1742                | wgpu::TextureUsages::COPY_SRC,
1743            view_formats: &[],
1744        };
1745
1746        let blur_tex_a = device.create_texture(&blur_texture_desc);
1747        let blur_texture_a = blur_tex_a.create_view(&wgpu::TextureViewDescriptor::default());
1748
1749        let blur_tex_b = device.create_texture(&blur_texture_desc);
1750        let blur_texture_b = blur_tex_b.create_view(&wgpu::TextureViewDescriptor::default());
1751
1752        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1753            address_mode_u: wgpu::AddressMode::ClampToEdge,
1754            address_mode_v: wgpu::AddressMode::ClampToEdge,
1755            mag_filter: wgpu::FilterMode::Linear,
1756            min_filter: wgpu::FilterMode::Linear,
1757            ..Default::default()
1758        });
1759
1760        let scene_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1761            layout: env_bind_group_layout,
1762            entries: &[
1763                wgpu::BindGroupEntry {
1764                    binding: 0,
1765                    resource: wgpu::BindingResource::TextureView(&scene_texture),
1766                },
1767                wgpu::BindGroupEntry {
1768                    binding: 1,
1769                    resource: wgpu::BindingResource::Sampler(&sampler),
1770                },
1771            ],
1772            label: Some("Headless Scene Bind Group"),
1773        });
1774
1775        let scene_views: Vec<&wgpu::TextureView> = (0..256).map(|_| &scene_texture).collect();
1776        let scene_texture_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1777            layout: texture_bind_group_layout,
1778            entries: &[
1779                wgpu::BindGroupEntry {
1780                    binding: 0,
1781                    resource: wgpu::BindingResource::TextureViewArray(&scene_views),
1782                },
1783                wgpu::BindGroupEntry {
1784                    binding: 1,
1785                    resource: wgpu::BindingResource::Sampler(&sampler),
1786                },
1787            ],
1788            label: Some("Headless Scene Texture Bind Group"),
1789        });
1790
1791        let blur_views_a: Vec<&wgpu::TextureView> = (0..256).map(|_| &blur_texture_a).collect();
1792        let blur_bind_group_a = device.create_bind_group(&wgpu::BindGroupDescriptor {
1793            layout: texture_bind_group_layout,
1794            entries: &[
1795                wgpu::BindGroupEntry {
1796                    binding: 0,
1797                    resource: wgpu::BindingResource::TextureViewArray(&blur_views_a),
1798                },
1799                wgpu::BindGroupEntry {
1800                    binding: 1,
1801                    resource: wgpu::BindingResource::Sampler(&sampler),
1802                },
1803            ],
1804            label: Some("Headless Blur Bind Group A"),
1805        });
1806
1807        let blur_views_b: Vec<&wgpu::TextureView> = (0..256).map(|_| &blur_texture_b).collect();
1808        let blur_bind_group_b = device.create_bind_group(&wgpu::BindGroupDescriptor {
1809            layout: texture_bind_group_layout,
1810            entries: &[
1811                wgpu::BindGroupEntry {
1812                    binding: 0,
1813                    resource: wgpu::BindingResource::TextureViewArray(&blur_views_b),
1814                },
1815                wgpu::BindGroupEntry {
1816                    binding: 1,
1817                    resource: wgpu::BindingResource::Sampler(&sampler),
1818                },
1819            ],
1820            label: Some("Headless Blur Bind Group B"),
1821        });
1822
1823        let blur_env_bind_group_a = device.create_bind_group(&wgpu::BindGroupDescriptor {
1824            layout: env_bind_group_layout,
1825            entries: &[
1826                wgpu::BindGroupEntry {
1827                    binding: 0,
1828                    resource: wgpu::BindingResource::TextureView(&blur_texture_a),
1829                },
1830                wgpu::BindGroupEntry {
1831                    binding: 1,
1832                    resource: wgpu::BindingResource::Sampler(&sampler),
1833                },
1834            ],
1835            label: Some("Headless Blur Env Bind Group A"),
1836        });
1837
1838        let depth_texture = device.create_texture(&wgpu::TextureDescriptor {
1839            label: Some("Headless Depth Texture"),
1840            size: wgpu::Extent3d {
1841                width,
1842                height,
1843                depth_or_array_layers: 1,
1844            },
1845            mip_level_count: 1,
1846            sample_count: 1,
1847            dimension: wgpu::TextureDimension::D2,
1848            format: wgpu::TextureFormat::Depth32Float,
1849            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1850            view_formats: &[],
1851        });
1852        let depth_texture_view = depth_texture.create_view(&wgpu::TextureViewDescriptor::default());
1853
1854        let output_texture = device.create_texture(&wgpu::TextureDescriptor {
1855            label: Some("Headless Output Texture"),
1856            size: wgpu::Extent3d {
1857                width,
1858                height,
1859                depth_or_array_layers: 1,
1860            },
1861            mip_level_count: 1,
1862            sample_count: 1,
1863            dimension: wgpu::TextureDimension::D2,
1864            format,
1865            usage: wgpu::TextureUsages::RENDER_ATTACHMENT
1866                | wgpu::TextureUsages::COPY_DST
1867                | wgpu::TextureUsages::COPY_SRC,
1868            view_formats: &[],
1869        });
1870        let output_view = output_texture.create_view(&wgpu::TextureViewDescriptor::default());
1871
1872        HeadlessContext {
1873            scene_texture,
1874            scene_bind_group,
1875            scene_texture_bind_group,
1876            depth_texture_view,
1877            blur_texture_a,
1878            blur_texture_b,
1879            blur_bind_group_a,
1880            blur_bind_group_b,
1881            blur_env_bind_group_a,
1882            scale_factor: 1.0,
1883            sampler,
1884            width,
1885            height,
1886            output_texture,
1887            output_view,
1888        }
1889    }
1890
1891    fn create_surface_context(
1892        device: &wgpu::Device,
1893        surface: wgpu::Surface<'static>,
1894        config: wgpu::SurfaceConfiguration,
1895        env_bind_group_layout: &wgpu::BindGroupLayout,
1896        texture_bind_group_layout: &wgpu::BindGroupLayout,
1897        scale_factor: f32,
1898    ) -> SurfaceContext {
1899        let width = config.width;
1900        let height = config.height;
1901
1902        let texture_desc = wgpu::TextureDescriptor {
1903            label: Some("Surtr Scene Texture"),
1904            size: wgpu::Extent3d {
1905                width,
1906                height,
1907                depth_or_array_layers: 1,
1908            },
1909            mip_level_count: 1,
1910            sample_count: 1,
1911            dimension: wgpu::TextureDimension::D2,
1912            format: config.format,
1913            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
1914            view_formats: &[],
1915        };
1916
1917        let scene_tex = device.create_texture(&texture_desc);
1918        let scene_texture = scene_tex.create_view(&wgpu::TextureViewDescriptor::default());
1919
1920        let blur_width = (width / 2).max(1);
1921        let blur_height = (height / 2).max(1);
1922        let blur_texture_desc = wgpu::TextureDescriptor {
1923            label: Some("Surtr Blur Texture"),
1924            size: wgpu::Extent3d {
1925                width: blur_width,
1926                height: blur_height,
1927                depth_or_array_layers: 1,
1928            },
1929            mip_level_count: 1,
1930            sample_count: 1,
1931            dimension: wgpu::TextureDimension::D2,
1932            format: config.format,
1933            usage: wgpu::TextureUsages::RENDER_ATTACHMENT
1934                | wgpu::TextureUsages::TEXTURE_BINDING
1935                | wgpu::TextureUsages::COPY_SRC,
1936            view_formats: &[],
1937        };
1938
1939        let blur_tex_a = device.create_texture(&blur_texture_desc);
1940        let blur_texture_a = blur_tex_a.create_view(&wgpu::TextureViewDescriptor::default());
1941
1942        let blur_tex_b = device.create_texture(&blur_texture_desc);
1943        let blur_texture_b = blur_tex_b.create_view(&wgpu::TextureViewDescriptor::default());
1944
1945        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1946            address_mode_u: wgpu::AddressMode::ClampToEdge,
1947            address_mode_v: wgpu::AddressMode::ClampToEdge,
1948            mag_filter: wgpu::FilterMode::Linear,
1949            min_filter: wgpu::FilterMode::Linear,
1950            ..Default::default()
1951        });
1952
1953        let scene_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1954            layout: env_bind_group_layout,
1955            entries: &[
1956                wgpu::BindGroupEntry {
1957                    binding: 0,
1958                    resource: wgpu::BindingResource::TextureView(&scene_texture),
1959                },
1960                wgpu::BindGroupEntry {
1961                    binding: 1,
1962                    resource: wgpu::BindingResource::Sampler(&sampler),
1963                },
1964            ],
1965            label: Some("Scene Bind Group"),
1966        });
1967
1968        let scene_views: Vec<&wgpu::TextureView> = (0..256).map(|_| &scene_texture).collect();
1969        let scene_texture_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1970            layout: texture_bind_group_layout,
1971            entries: &[
1972                wgpu::BindGroupEntry {
1973                    binding: 0,
1974                    resource: wgpu::BindingResource::TextureViewArray(&scene_views),
1975                },
1976                wgpu::BindGroupEntry {
1977                    binding: 1,
1978                    resource: wgpu::BindingResource::Sampler(&sampler),
1979                },
1980            ],
1981            label: Some("Scene Texture Bind Group"),
1982        });
1983
1984        let blur_views_a: Vec<&wgpu::TextureView> = (0..256).map(|_| &blur_texture_a).collect();
1985        let blur_bind_group_a = device.create_bind_group(&wgpu::BindGroupDescriptor {
1986            layout: texture_bind_group_layout,
1987            entries: &[
1988                wgpu::BindGroupEntry {
1989                    binding: 0,
1990                    resource: wgpu::BindingResource::TextureViewArray(&blur_views_a),
1991                },
1992                wgpu::BindGroupEntry {
1993                    binding: 1,
1994                    resource: wgpu::BindingResource::Sampler(&sampler),
1995                },
1996            ],
1997            label: Some("Blur Bind Group A"),
1998        });
1999
2000        let blur_views_b: Vec<&wgpu::TextureView> = (0..256).map(|_| &blur_texture_b).collect();
2001        let blur_bind_group_b = device.create_bind_group(&wgpu::BindGroupDescriptor {
2002            layout: texture_bind_group_layout,
2003            entries: &[
2004                wgpu::BindGroupEntry {
2005                    binding: 0,
2006                    resource: wgpu::BindingResource::TextureViewArray(&blur_views_b),
2007                },
2008                wgpu::BindGroupEntry {
2009                    binding: 1,
2010                    resource: wgpu::BindingResource::Sampler(&sampler),
2011                },
2012            ],
2013            label: Some("Blur Bind Group B"),
2014        });
2015
2016        let depth_texture = device.create_texture(&wgpu::TextureDescriptor {
2017            label: Some("Surtr Depth Texture"),
2018            size: wgpu::Extent3d {
2019                width,
2020                height,
2021                depth_or_array_layers: 1,
2022            },
2023            mip_level_count: 1,
2024            sample_count: 1,
2025            dimension: wgpu::TextureDimension::D2,
2026            format: wgpu::TextureFormat::Depth32Float,
2027            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
2028            view_formats: &[],
2029        });
2030        let depth_texture_view = depth_texture.create_view(&wgpu::TextureViewDescriptor::default());
2031
2032        let blur_env_bind_group_a = device.create_bind_group(&wgpu::BindGroupDescriptor {
2033            layout: env_bind_group_layout,
2034            entries: &[
2035                wgpu::BindGroupEntry {
2036                    binding: 0,
2037                    resource: wgpu::BindingResource::TextureView(&blur_texture_a),
2038                },
2039                wgpu::BindGroupEntry {
2040                    binding: 1,
2041                    resource: wgpu::BindingResource::Sampler(&sampler),
2042                },
2043            ],
2044            label: Some("Blur Env Bind Group A"),
2045        });
2046
2047        SurfaceContext {
2048            surface,
2049            config,
2050            scene_texture,
2051            scene_bind_group,
2052            scene_texture_bind_group,
2053            depth_texture_view,
2054            blur_texture_a,
2055            blur_texture_b,
2056            blur_bind_group_a,
2057            blur_bind_group_b,
2058            blur_env_bind_group_a,
2059            scale_factor,
2060            sampler,
2061        }
2062    }
2063
2064    pub fn reset_time(&mut self) {
2065        self.start_time = std::time::Instant::now();
2066    }
2067
2068    /// reclaim_vram — Atomic recycling of the Mega-Atlas and all associated caches.
2069    /// This prevents OOM and silent failures by quenching the atlas when full.
2070    pub fn reclaim_vram(&mut self) {
2071        log::warn!("[GPU] Yggdrasil Compaction: Compacting Mega-Atlas...");
2072        
2073        let new_mega_atlas_tex = self.device.create_texture(&wgpu::TextureDescriptor {
2074            label: Some("Yggdrasil Mega-Atlas (Compacted)"),
2075            size: wgpu::Extent3d { width: 4096, height: 4096, depth_or_array_layers: 1 },
2076            mip_level_count: 1,
2077            sample_count: 1,
2078            dimension: wgpu::TextureDimension::D2,
2079            format: wgpu::TextureFormat::Rgba8UnormSrgb,
2080            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::COPY_SRC,
2081            view_formats: &[],
2082        });
2083        
2084        let mut new_packer = YggdrasilPacker::new(4096, 4096);
2085        let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
2086            label: Some("Atlas Compaction Encoder"),
2087        });
2088        
2089        let image_entries: Vec<(String, Rect)> = self.image_uv_registry.iter().map(|(k, v)| (k.clone(), *v)).collect();
2090        for (name, old_uv) in image_entries {
2091            if let Some(&tex_idx) = self.texture_registry.get(&name) {
2092                if tex_idx == 0 {
2093                    let w_px = (old_uv.width * 4096.0).round() as u32;
2094                    let h_px = (old_uv.height * 4096.0).round() as u32;
2095                    let old_x_px = (old_uv.x * 4096.0).round() as u32;
2096                    let old_y_px = (old_uv.y * 4096.0).round() as u32;
2097                    
2098                    if let Some((new_x, new_y)) = new_packer.pack(w_px, h_px) {
2099                        encoder.copy_texture_to_texture(
2100                            wgpu::TexelCopyTextureInfo {
2101                                texture: &self.mega_atlas_tex,
2102                                mip_level: 0,
2103                                origin: wgpu::Origin3d { x: old_x_px, y: old_y_px, z: 0 },
2104                                aspect: wgpu::TextureAspect::All,
2105                            },
2106                            wgpu::TexelCopyTextureInfo {
2107                                texture: &new_mega_atlas_tex,
2108                                mip_level: 0,
2109                                origin: wgpu::Origin3d { x: new_x, y: new_y, z: 0 },
2110                                aspect: wgpu::TextureAspect::All,
2111                            },
2112                            wgpu::Extent3d { width: w_px, height: h_px, depth_or_array_layers: 1 },
2113                        );
2114                        
2115                        let new_uv = Rect {
2116                            x: new_x as f32 / 4096.0,
2117                            y: new_y as f32 / 4096.0,
2118                            width: old_uv.width,
2119                            height: old_uv.height,
2120                        };
2121                        self.image_uv_registry.put(name.clone(), new_uv);
2122                    }
2123                }
2124            }
2125        }
2126        
2127        let text_entries: Vec<(u64, (Rect, f32, f32))> = self.text_cache.iter().map(|(k, v)| (*k, *v)).collect();
2128        for (hash, (old_uv, w_f, h_f)) in text_entries {
2129            let w_px = (old_uv.width * 4096.0).round() as u32;
2130            let h_px = (old_uv.height * 4096.0).round() as u32;
2131            let old_x_px = (old_uv.x * 4096.0).round() as u32;
2132            let old_y_px = (old_uv.y * 4096.0).round() as u32;
2133            
2134            if let Some((new_x, new_y)) = new_packer.pack(w_px, h_px) {
2135                encoder.copy_texture_to_texture(
2136                    wgpu::TexelCopyTextureInfo {
2137                        texture: &self.mega_atlas_tex,
2138                        mip_level: 0,
2139                        origin: wgpu::Origin3d { x: old_x_px, y: old_y_px, z: 0 },
2140                        aspect: wgpu::TextureAspect::All,
2141                    },
2142                    wgpu::TexelCopyTextureInfo {
2143                        texture: &new_mega_atlas_tex,
2144                        mip_level: 0,
2145                        origin: wgpu::Origin3d { x: new_x, y: new_y, z: 0 },
2146                        aspect: wgpu::TextureAspect::All,
2147                    },
2148                    wgpu::Extent3d { width: w_px, height: h_px, depth_or_array_layers: 1 },
2149                );
2150                
2151                let new_uv = Rect {
2152                    x: new_x as f32 / 4096.0,
2153                    y: new_y as f32 / 4096.0,
2154                    width: old_uv.width,
2155                    height: old_uv.height,
2156                };
2157                self.text_cache.put(hash, (new_uv, w_f, h_f));
2158            }
2159        }
2160        
2161        self.queue.submit(std::iter::once(encoder.finish()));
2162        
2163        self.mega_atlas_tex = new_mega_atlas_tex;
2164        let mega_atlas_view_obj = self.mega_atlas_tex.create_view(&wgpu::TextureViewDescriptor::default());
2165        self.texture_views[0] = mega_atlas_view_obj.clone();
2166        
2167        self.rebuild_texture_array_bind_group();
2168        
2169        if !self.texture_bind_groups.is_empty() {
2170            self.texture_bind_groups[0] = self.mega_atlas_bind_group.clone();
2171        }
2172        
2173        self.atlas_packer = new_packer;
2174        self.telemetry.vram_exhausted = false;
2175    }
2176
2177    fn shatter_internal(
2178        &mut self,
2179        rect: Rect,
2180        pieces: u32,
2181        force: f32,
2182        color: [f32; 4],
2183        mode: u32,
2184    ) {
2185        // High-Fidelity Variable Particle Density
2186        let count = (pieces as f32).sqrt().ceil() as u32;
2187        let dw = rect.width / count as f32;
2188        let dh = rect.height / count as f32;
2189
2190        let c = self.apply_opacity(color);
2191
2192        for y in 0..count {
2193            for x in 0..count {
2194                let shard_rect = Rect {
2195                    x: rect.x + x as f32 * dw,
2196                    y: rect.y + y as f32 * dh,
2197                    width: dw,
2198                    height: dh,
2199                };
2200
2201                let uv = Rect {
2202                    x: x as f32 / count as f32,
2203                    y: y as f32 / count as f32,
2204                    width: 1.0 / count as f32,
2205                    height: 1.0 / count as f32,
2206                };
2207
2208                self.fill_rect_with_full_params(shard_rect, c, mode, None, force, uv);
2209            }
2210        }
2211    }
2212
2213    fn recursive_bolt(&mut self, from: [f32; 2], to: [f32; 2], depth: u32, color: [f32; 4]) {
2214        if depth == 0 {
2215            self.draw_lightning_segment(from, to, color);
2216            return;
2217        }
2218
2219        let mid_x = (from[0] + to[0]) * 0.5;
2220        let mid_y = (from[1] + to[1]) * 0.5;
2221
2222        let dx = to[0] - from[0];
2223        let dy = to[1] - from[1];
2224        let len = (dx * dx + dy * dy).sqrt();
2225
2226        // Perpendicular offset for jaggedness
2227        let offset_scale = len * 0.15;
2228        let seed = (from[0] * 12.9898 + from[1] * 78.233 + (depth as f32) * 37.11)
2229            .sin()
2230            .fract();
2231        let offset_x = -dy / len * (seed - 0.5) * offset_scale;
2232        let offset_y = dx / len * (seed - 0.5) * offset_scale;
2233
2234        let mid = [mid_x + offset_x, mid_y + offset_y];
2235
2236        self.recursive_bolt(from, mid, depth - 1, color);
2237        self.recursive_bolt(mid, to, depth - 1, color);
2238
2239        // 20% chance of a secondary branch
2240        if depth > 2 && seed > 0.8 {
2241            let branch_to = [
2242                mid[0] + offset_x * 2.0 + (seed * 100.0).sin() * 50.0,
2243                mid[1] + offset_y * 2.0 + (seed * 100.0).cos() * 50.0,
2244            ];
2245            self.recursive_bolt(mid, branch_to, depth - 2, color);
2246        }
2247    }
2248
2249    fn draw_lightning_segment(&mut self, from: [f32; 2], to: [f32; 2], color: [f32; 4]) {
2250        let dx = to[0] - from[0];
2251        let dy = to[1] - from[1];
2252        let len = (dx * dx + dy * dy).sqrt();
2253        if len < 0.001 {
2254            return;
2255        }
2256
2257        let glow_width = 32.0;
2258        let core_width = 4.0;
2259        let c = self.apply_opacity(color);
2260
2261        // 1. Render Volumetric Glow (Cyan)
2262        let gnx = -dy / len * glow_width * 0.5;
2263        let gny = dx / len * glow_width * 0.5;
2264        let gp1 = [from[0] + gnx, from[1] + gny];
2265        let gp2 = [to[0] + gnx, to[1] + gny];
2266        let gp3 = [to[0] - gnx, to[1] - gny];
2267        let gp4 = [from[0] - gnx, from[1] - gny];
2268        self.push_oriented_quad(
2269            [gp1, gp2, gp3, gp4],
2270            c,
2271            9,
2272            Rect {
2273                x: 0.0,
2274                y: 0.0,
2275                width: 1.0,
2276                height: 1.0,
2277            },
2278        );
2279
2280        // 2. Render Blinding Core (White)
2281        let cnx = -dy / len * core_width * 0.5;
2282        let cny = dx / len * core_width * 0.5;
2283        let cp1 = [from[0] + cnx, from[1] + cny];
2284        let cp2 = [to[0] + cnx, to[1] + cny];
2285        let cp3 = [to[0] - cnx, to[1] - cny];
2286        let cp4 = [from[0] - cnx, from[1] - cny];
2287        self.push_oriented_quad(
2288            [cp1, cp2, cp3, cp4],
2289            [1.0, 1.0, 1.0, c[3]],
2290            0,
2291            Rect {
2292                x: 0.0,
2293                y: 0.0,
2294                width: 1.0,
2295                height: 1.0,
2296            },
2297        );
2298    }
2299
2300    fn push_oriented_quad(
2301        &mut self,
2302        points: [[f32; 2]; 4],
2303        color: [f32; 4],
2304        mode: u32,
2305        uv_rect: Rect,
2306    ) {
2307        let scissor = self.clip_stack.last().copied();
2308        let texture_id = None; // Oriented quads like lightning don't use textures yet
2309
2310        if self.draw_calls.is_empty()
2311            || self.current_texture_id != texture_id
2312            || self.draw_calls.last().unwrap().scissor_rect != scissor
2313        {
2314            self.current_texture_id = texture_id;
2315            self.draw_calls.push(DrawCall {
2316                texture_id,
2317                scissor_rect: scissor,
2318                index_start: self.indices.len() as u32,
2319                index_count: 0,
2320                material: if mode == 7 {
2321                    cvkg_core::DrawMaterial::Glass { blur_radius: 20.0 }
2322                } else if mode == 6 {
2323                    cvkg_core::DrawMaterial::TopUI
2324                } else {
2325                    cvkg_core::DrawMaterial::Opaque
2326                },
2327            });
2328        }
2329
2330        let uvs = [
2331            [uv_rect.x, uv_rect.y],
2332            [uv_rect.x + uv_rect.width, uv_rect.y],
2333            [uv_rect.x + uv_rect.width, uv_rect.y + uv_rect.height],
2334            [uv_rect.x, uv_rect.y + uv_rect.height],
2335        ];
2336
2337        let screen = [self.current_width() as f32, self.current_height() as f32];
2338        let rect = Rect {
2339            x: points[0][0],
2340            y: points[0][1],
2341            width: 1.0,
2342            height: 1.0,
2343        };
2344
2345        for i in 0..4 {
2346            let px = points[i][0];
2347            let py = points[i][1];
2348
2349            let (translation, scale_transform, rotation) = self.get_current_transform();
2350            self.vertices.push(Vertex {
2351                position: [px, py, 0.0],
2352                normal: [0.0, 0.0, 1.0],
2353                uv: uvs[i],
2354                color,
2355                mode,
2356                radius: 0.0,
2357                slice: [0.0, 0.0, 0.0, 1.0],
2358                logical: [px - rect.x, py - rect.y],
2359                size: [rect.width, rect.height],
2360                screen,
2361                clip: [-10000.0, -10000.0, 20000.0, 20000.0],
2362                translation,
2363                scale: scale_transform,
2364                rotation,
2365                tex_index: 0,
2366            });
2367        }
2368
2369        if let Some(call) = self.draw_calls.last_mut() {
2370            call.index_count += 6;
2371        }
2372    }
2373    fn get_texture_id(&mut self, name: &str) -> Option<u32> {
2374        self.texture_registry.get(name).copied()
2375    }
2376
2377    /// fill_rect_with_mode — Specialized rectangle drawing with mode-specific shader logic.
2378    pub fn fill_rect_with_mode(
2379        &mut self,
2380        rect: Rect,
2381        color: [f32; 4],
2382        mode: u32,
2383        texture_id: Option<u32>,
2384    ) {
2385        self.fill_rect_with_full_params(
2386            rect,
2387            color,
2388            mode,
2389            texture_id,
2390            0.0,
2391            Rect {
2392                x: 0.0,
2393                y: 0.0,
2394                width: 1.0,
2395                height: 1.0,
2396            },
2397        );
2398    }
2399
2400    fn fill_rect_with_full_params(
2401        &mut self,
2402        rect: Rect,
2403        color: [f32; 4],
2404        mode: u32,
2405        texture_id: Option<u32>,
2406        radius: f32,
2407        uv_rect: Rect,
2408    ) {
2409        // If a shadow is active, draw it first
2410        if let Some(shadow) = self.shadow_stack.last().copied()
2411            && shadow.color[3] > 0.001
2412        {
2413            Renderer::draw_drop_shadow(
2414                self,
2415                rect,
2416                radius,
2417                shadow.color,
2418                shadow.radius,
2419                0.0, // Spread
2420            );
2421        }
2422
2423        let slice = self
2424            .slice_stack
2425            .last()
2426            .copied()
2427            .map(|(a, o)| [a, o, 1.0, 1.0])
2428            .unwrap_or([0.0, 0.0, 0.0, 1.0]);
2429        self.fill_rect_with_full_params_and_slice(
2430            rect, color, mode, texture_id, radius, uv_rect, slice,
2431        );
2432    }
2433
2434    #[allow(clippy::too_many_arguments)]
2435    fn fill_rect_with_full_params_and_slice(
2436        &mut self,
2437        rect: Rect,
2438        color: [f32; 4],
2439        mode: u32,
2440        texture_id: Option<u32>,
2441        radius: f32,
2442        uv_rect: Rect,
2443        slice: [f32; 4],
2444    ) {
2445        let scissor = self.clip_stack.last().copied();
2446
2447        let material = if mode == 7 {
2448            cvkg_core::DrawMaterial::Glass { blur_radius: 20.0 }
2449        } else if mode == 6 {
2450            cvkg_core::DrawMaterial::TopUI
2451        } else {
2452            self.current_draw_material
2453        };
2454
2455        // Batching: check if we need to start a new DrawCall
2456        // With Texture Array, we no longer need to break batches when the texture changes,
2457        // as long as they are all part of the same array bind group (Group 0).
2458        let last_call = self.draw_calls.last();
2459        let needs_new_call = self.draw_calls.is_empty()
2460            || last_call.unwrap().scissor_rect != scissor
2461            || last_call.unwrap().material != material;
2462
2463        if needs_new_call {
2464            self.current_texture_id = Some(0); // All textures are now in the binding array at Group 0
2465            self.draw_calls.push(DrawCall {
2466                texture_id: self.current_texture_id,
2467                scissor_rect: scissor,
2468                index_start: self.indices.len() as u32,
2469                index_count: 0,
2470                material,
2471            });
2472        }
2473
2474        let scale = self.current_scale_factor();
2475        let snap = |v: f32| (v * scale).round() / scale;
2476
2477        let base_idx = self.vertices.len() as u32;
2478        let x1 = snap(rect.x);
2479        let y1 = snap(rect.y);
2480        let x2 = snap(rect.x + rect.width);
2481        let y2 = snap(rect.y + rect.height);
2482        let z = self.current_z;
2483        let normal = [0.0, 0.0, 1.0];
2484        let screen = [self.current_width() as f32, self.current_height() as f32];
2485        let clip_rect = self.clip_stack.last().copied().unwrap_or(cvkg_core::Rect {
2486            x: -10000.0,
2487            y: -10000.0,
2488            width: 20000.0,
2489            height: 20000.0,
2490        });
2491        let clip = [clip_rect.x, clip_rect.y, clip_rect.width, clip_rect.height];
2492
2493        let (translation, scale_transform, rotation) = self.get_current_transform();
2494
2495        let tex_index = texture_id.unwrap_or(0);
2496
2497        self.vertices.push(Vertex {
2498            position: [x1, y1, z],
2499            normal,
2500            uv: [uv_rect.x, uv_rect.y],
2501            color,
2502            mode,
2503            radius,
2504            slice,
2505            logical: [0.0, 0.0],
2506            size: [rect.width, rect.height],
2507            screen,
2508            clip,
2509            translation,
2510            scale: scale_transform,
2511            rotation,
2512            tex_index,
2513        });
2514        self.vertices.push(Vertex {
2515            position: [x2, y1, z],
2516            normal,
2517            uv: [uv_rect.x + uv_rect.width, uv_rect.y],
2518            color,
2519            mode,
2520            radius,
2521            slice,
2522            logical: [rect.width, 0.0],
2523            size: [rect.width, rect.height],
2524            screen,
2525            clip,
2526            translation,
2527            scale: scale_transform,
2528            rotation,
2529            tex_index,
2530        });
2531        self.vertices.push(Vertex {
2532            position: [x2, y2, z],
2533            normal,
2534            uv: [uv_rect.x + uv_rect.width, uv_rect.y + uv_rect.height],
2535            color,
2536            mode,
2537            radius,
2538            slice,
2539            logical: [rect.width, rect.height],
2540            size: [rect.width, rect.height],
2541            screen,
2542            clip,
2543            translation,
2544            scale: scale_transform,
2545            rotation,
2546            tex_index,
2547        });
2548        self.vertices.push(Vertex {
2549            position: [x1, y2, z],
2550            normal,
2551            uv: [uv_rect.x, uv_rect.y + uv_rect.height],
2552            color,
2553            mode,
2554            radius,
2555            slice,
2556            logical: [0.0, rect.height],
2557            size: [rect.width, rect.height],
2558            screen,
2559            clip,
2560            translation,
2561            scale: scale_transform,
2562            rotation,
2563            tex_index,
2564        });
2565
2566        self.indices.extend_from_slice(&[
2567            base_idx,
2568            base_idx + 1,
2569            base_idx + 2,
2570            base_idx,
2571            base_idx + 2,
2572            base_idx + 3,
2573        ]);
2574
2575        if let Some(call) = self.draw_calls.last_mut() {
2576            call.index_count += 6;
2577        }
2578    }
2579
2580    /// end_frame — Quench the blade by submitting the full Muspelheim multi-pass effect.
2581    pub fn end_frame(&mut self, mut encoder: wgpu::CommandEncoder) {
2582        let (
2583            surface_texture,
2584            target_view,
2585            ctx_scene_texture,
2586            ctx_depth_texture_view,
2587            ctx_blur_env_bind_group_a,
2588            ctx_scene_texture_bind_group,
2589            ctx_blur_texture_a,
2590            ctx_blur_texture_b,
2591            _ctx_sampler,
2592            ctx_blur_bind_group_a,
2593            ctx_blur_bind_group_b,
2594            scale,
2595        ) = if let Some(window_id) = self.current_window {
2596            let ctx = self
2597                .surfaces
2598                .get(&window_id)
2599                .expect("Missing surface context");
2600            let frame = match ctx.surface.get_current_texture() {
2601                wgpu::CurrentSurfaceTexture::Success(t) => t,
2602                wgpu::CurrentSurfaceTexture::Suboptimal(t) => {
2603                    ctx.surface.configure(&self.device, &ctx.config);
2604                    t
2605                }
2606                _ => {
2607                    log::warn!("[GPU] Surface texture acquisition failed, reconfiguring surface");
2608                    ctx.surface.configure(&self.device, &ctx.config);
2609                    return;
2610                }
2611            };
2612            let view = frame
2613                .texture
2614                .create_view(&wgpu::TextureViewDescriptor::default());
2615            (
2616                Some(frame),
2617                view,
2618                &ctx.scene_texture,
2619                &ctx.depth_texture_view,
2620                &ctx.blur_env_bind_group_a,
2621                &ctx.scene_texture_bind_group,
2622                &ctx.blur_texture_a,
2623                &ctx.blur_texture_b,
2624                &ctx.sampler,
2625                &ctx.blur_bind_group_a,
2626                &ctx.blur_bind_group_b,
2627                ctx.scale_factor,
2628            )
2629        } else {
2630            let ctx = self
2631                .headless_context
2632                .as_ref()
2633                .expect("No headless context for end_frame");
2634            (
2635                None,
2636                ctx.output_view.clone(),
2637                &ctx.scene_texture,
2638                &ctx.depth_texture_view,
2639                &ctx.blur_env_bind_group_a,
2640                &ctx.scene_texture_bind_group,
2641                &ctx.blur_texture_a,
2642                &ctx.blur_texture_b,
2643                &ctx.sampler,
2644                &ctx.blur_bind_group_a,
2645                &ctx.blur_bind_group_b,
2646                self.current_scale_factor(),
2647            )
2648        };
2649
2650        // ── Pass 1: Opaque Background & Atmosphere ──────────────────────────
2651        {
2652            let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2653                label: Some("Surtr P1 Opaque Background"),
2654                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2655                    view: ctx_scene_texture,
2656                    resolve_target: None,
2657                    ops: wgpu::Operations {
2658                        load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
2659                        store: wgpu::StoreOp::Store,
2660                    },
2661                    depth_slice: None,
2662                })],
2663                depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
2664                    view: ctx_depth_texture_view,
2665                    depth_ops: Some(wgpu::Operations {
2666                        load: wgpu::LoadOp::Clear(0.0), // Reversed-Z
2667                        store: wgpu::StoreOp::Store,
2668                    }),
2669                    stencil_ops: None,
2670                }),
2671                timestamp_writes: self.skuld_queries.as_ref().map(|q| wgpu::RenderPassTimestampWrites {
2672                    query_set: q,
2673                    beginning_of_pass_write_index: Some(0),
2674                    end_of_pass_write_index: None,
2675                }),
2676                occlusion_query_set: None,
2677                multiview_mask: None,
2678            });
2679
2680            // 1a. Background Atmosphere
2681            p.set_pipeline(&self.background_pipeline);
2682            p.set_bind_group(0, &self.dummy_texture_bind_group, &[]);
2683            p.set_bind_group(1, ctx_blur_env_bind_group_a, &[]); // Use previous frame's blur for background depth
2684            p.set_bind_group(2, &self.berserker_bind_group, &[]);
2685            p.draw(0..6, 0..1);
2686
2687            // 1b. Opaque Main Elements (non-glass, non-ui)
2688            if !self.draw_calls.is_empty() {
2689                p.set_pipeline(&self.pipeline);
2690                p.set_vertex_buffer(0, self.vertex_buffer.slice(..));
2691                p.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
2692                p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
2693                p.set_bind_group(2, &self.berserker_bind_group, &[]);
2694
2695                for call in self.draw_calls.iter().filter(|c| matches!(c.material, cvkg_core::DrawMaterial::Opaque)) {
2696                    let bg = if let Some(id) = call.texture_id {
2697                        if id == 0 {
2698                            &self.mega_atlas_bind_group
2699                        } else {
2700                            self.texture_bind_groups
2701                                .get(id as usize)
2702                                .unwrap_or(&self.dummy_texture_bind_group)
2703                        }
2704                    } else {
2705                        &self.dummy_texture_bind_group
2706                    };
2707                    p.set_bind_group(0, bg, &[]);
2708                    p.draw_indexed(
2709                        call.index_start..call.index_start + call.index_count,
2710                        0,
2711                        0..1,
2712                    );
2713                    self.telemetry.draw_calls += 1;
2714                    self.telemetry.vertices += call.index_count;
2715                }
2716            }
2717        }
2718
2719        // ── Pass 2: Backdrop Blur (Bifrost) ──────────────────────────────────
2720        // Capture the background into blur_texture_b
2721        {
2722            // First extract into texture_a
2723            let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2724                label: Some("Surtr Blur Extract"),
2725                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2726                    view: ctx_blur_texture_a,
2727                    resolve_target: None,
2728                    ops: wgpu::Operations {
2729                        load: wgpu::LoadOp::Clear(wgpu::Color {
2730                            r: 0.0,
2731                            g: 0.0,
2732                            b: 0.0,
2733                            a: 0.0,
2734                        }),
2735                        store: wgpu::StoreOp::Store,
2736                    },
2737                    depth_slice: None,
2738                })],
2739                ..Default::default()
2740            });
2741            p.set_pipeline(&self.bloom_extract_pipeline); // Use extract as a direct copy for now
2742            p.set_bind_group(0, ctx_scene_texture_bind_group, &[]);
2743            p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
2744            p.set_bind_group(2, &self.berserker_bind_group, &[]);
2745            p.draw(0..6, 0..1);
2746        }
2747
2748        let blur_iters: u32 = 4;
2749        for _i in 0..blur_iters {
2750            {
2751                let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2752                    label: Some("Blur H"),
2753                    color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2754                        view: ctx_blur_texture_b,
2755                        resolve_target: None,
2756                        ops: wgpu::Operations {
2757                            load: wgpu::LoadOp::Clear(wgpu::Color {
2758                                r: 0.0,
2759                                g: 0.0,
2760                                b: 0.0,
2761                                a: 0.0,
2762                            }),
2763                            store: wgpu::StoreOp::Store,
2764                        },
2765                        depth_slice: None,
2766                    })],
2767                    ..Default::default()
2768                });
2769                p.set_pipeline(&self.blur_h_pipeline);
2770                p.set_bind_group(0, ctx_blur_bind_group_a, &[]);
2771                p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
2772                p.set_bind_group(2, &self.berserker_bind_group, &[]);
2773                p.draw(0..6, 0..1);
2774            }
2775            {
2776                let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2777                    label: Some("Blur V"),
2778                    color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2779                        view: ctx_blur_texture_a,
2780                        resolve_target: None,
2781                        ops: wgpu::Operations {
2782                            load: wgpu::LoadOp::Clear(wgpu::Color {
2783                                r: 0.0,
2784                                g: 0.0,
2785                                b: 0.0,
2786                                a: 0.0,
2787                            }),
2788                            store: wgpu::StoreOp::Store,
2789                        },
2790                        depth_slice: None,
2791                    })],
2792                    ..Default::default()
2793                });
2794                p.set_pipeline(&self.blur_v_pipeline);
2795                p.set_bind_group(0, ctx_blur_bind_group_b, &[]);
2796                p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
2797                p.set_bind_group(2, &self.berserker_bind_group, &[]);
2798                p.draw(0..6, 0..1);
2799            }
2800        }
2801
2802        // 1. Finalize the PRE-parallel work (Background & Atmosphere)
2803        self.staging_command_buffers.push(encoder.finish());
2804
2805        let rt_w = self.current_width() as i32;
2806        let rt_h = self.current_height() as i32;
2807
2808        // 2. Parallel Encoding Phase: Glass & UI ─────────────────────────────
2809        // We utilize rayon to record these independent passes in parallel.
2810        let (glass_cb, ui_cb) = rayon::join(
2811            || {
2812                let mut glass_encoder =
2813                    self.device
2814                        .create_command_encoder(&wgpu::CommandEncoderDescriptor {
2815                            label: Some("Parallel Glass Encoder"),
2816                        });
2817                {
2818                    let mut p = glass_encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2819                        label: Some("Surtr P3 Liquid Glass"),
2820                        color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2821                            view: ctx_scene_texture,
2822                            resolve_target: None,
2823                            ops: wgpu::Operations {
2824                                load: wgpu::LoadOp::Load,
2825                                store: wgpu::StoreOp::Store,
2826                            },
2827                            depth_slice: None,
2828                        })],
2829                        depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
2830                            view: ctx_depth_texture_view,
2831                            depth_ops: Some(wgpu::Operations {
2832                                load: wgpu::LoadOp::Load,
2833                                store: wgpu::StoreOp::Store,
2834                            }),
2835                            stencil_ops: None,
2836                        }),
2837                        ..Default::default()
2838                    });
2839
2840                    p.set_pipeline(&self.pipeline);
2841                    p.set_vertex_buffer(0, self.vertex_buffer.slice(..));
2842                    p.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
2843                    p.set_bind_group(1, ctx_blur_env_bind_group_a, &[]);
2844                    p.set_bind_group(2, &self.berserker_bind_group, &[]);
2845
2846                    for call in self.draw_calls.iter().filter(|c| matches!(c.material, cvkg_core::DrawMaterial::Glass { .. })) {
2847                        let bg = if let Some(id) = call.texture_id {
2848                            if id == 0 {
2849                                &self.mega_atlas_bind_group
2850                            } else {
2851                                self.texture_bind_groups
2852                                    .get(id as usize)
2853                                    .unwrap_or(&self.dummy_texture_bind_group)
2854                            }
2855                        } else {
2856                            &self.dummy_texture_bind_group
2857                        };
2858                        p.set_bind_group(0, bg, &[]);
2859                        if let Some(rect) = call.scissor_rect {
2860                            // Scissor rect clamping logic:
2861                            // wgpu validation requires that the scissor rect is entirely contained within
2862                            // the physical render target dimensions and has a non-zero area (width > 0, height > 0).
2863                            // We compute the physical boundaries using the current render target size and scale factor,
2864                            // intersect/clamp them to the render target viewport, and fallback to a minimal 1x1 region
2865                            // if the intersection results in zero/negative area.
2866                            if rt_w > 0 && rt_h > 0 {
2867                                let x1 = (rect.x * scale).round() as i32;
2868                                let y1 = (rect.y * scale).round() as i32;
2869                                let x2 = ((rect.x + rect.width) * scale).round() as i32;
2870                                let y2 = ((rect.y + rect.height) * scale).round() as i32;
2871
2872                                let x1_clamped = x1.clamp(0, rt_w);
2873                                let y1_clamped = y1.clamp(0, rt_h);
2874                                let x2_clamped = x2.clamp(0, rt_w);
2875                                let y2_clamped = y2.clamp(0, rt_h);
2876
2877                                let w = x2_clamped - x1_clamped;
2878                                let h = y2_clamped - y1_clamped;
2879
2880                                if w > 0 && h > 0 {
2881                                    p.set_scissor_rect(
2882                                        x1_clamped as u32,
2883                                        y1_clamped as u32,
2884                                        w as u32,
2885                                        h as u32,
2886                                    );
2887                                } else {
2888                                    p.set_scissor_rect(0, 0, 1, 1);
2889                                }
2890                            }
2891                        }
2892                        p.draw_indexed(
2893                            call.index_start..call.index_start + call.index_count,
2894                            0,
2895                            0..1,
2896                        );
2897                    }
2898                }
2899                glass_encoder.finish()
2900            },
2901            || {
2902                let mut ui_encoder =
2903                    self.device
2904                        .create_command_encoder(&wgpu::CommandEncoderDescriptor {
2905                            label: Some("Parallel UI Encoder"),
2906                        });
2907                {
2908                    let mut p = ui_encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2909                        label: Some("Surtr P4 UI Layer"),
2910                        color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2911                            view: ctx_scene_texture,
2912                            resolve_target: None,
2913                            ops: wgpu::Operations {
2914                                load: wgpu::LoadOp::Load,
2915                                store: wgpu::StoreOp::Store,
2916                            },
2917                            depth_slice: None,
2918                        })],
2919                        depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
2920                            view: ctx_depth_texture_view,
2921                            depth_ops: Some(wgpu::Operations {
2922                                load: wgpu::LoadOp::Load,
2923                                store: wgpu::StoreOp::Store,
2924                            }),
2925                            stencil_ops: None,
2926                        }),
2927                        ..Default::default()
2928                    });
2929
2930                    p.set_pipeline(&self.pipeline);
2931                    p.set_vertex_buffer(0, self.vertex_buffer.slice(..));
2932                    p.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
2933                    p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
2934                    p.set_bind_group(2, &self.berserker_bind_group, &[]);
2935
2936                    for call in self.draw_calls.iter().filter(|c| matches!(c.material, cvkg_core::DrawMaterial::TopUI)) {
2937                        let bg = if let Some(id) = call.texture_id {
2938                            if id == 0 {
2939                                &self.mega_atlas_bind_group
2940                            } else {
2941                                self.texture_bind_groups
2942                                    .get(id as usize)
2943                                    .unwrap_or(&self.dummy_texture_bind_group)
2944                            }
2945                        } else {
2946                            &self.dummy_texture_bind_group
2947                        };
2948                        p.set_bind_group(0, bg, &[]);
2949                        if let Some(rect) = call.scissor_rect {
2950                            // Scissor rect clamping logic:
2951                            // wgpu validation requires that the scissor rect is entirely contained within
2952                            // the physical render target dimensions and has a non-zero area (width > 0, height > 0).
2953                            // We compute the physical boundaries using the current render target size and scale factor,
2954                            // intersect/clamp them to the render target viewport, and fallback to a minimal 1x1 region
2955                            // if the intersection results in zero/negative area.
2956                            if rt_w > 0 && rt_h > 0 {
2957                                let x1 = (rect.x * scale).round() as i32;
2958                                let y1 = (rect.y * scale).round() as i32;
2959                                let x2 = ((rect.x + rect.width) * scale).round() as i32;
2960                                let y2 = ((rect.y + rect.height) * scale).round() as i32;
2961
2962                                let x1_clamped = x1.clamp(0, rt_w);
2963                                let y1_clamped = y1.clamp(0, rt_h);
2964                                let x2_clamped = x2.clamp(0, rt_w);
2965                                let y2_clamped = y2.clamp(0, rt_h);
2966
2967                                let w = x2_clamped - x1_clamped;
2968                                let h = y2_clamped - y1_clamped;
2969
2970                                if w > 0 && h > 0 {
2971                                    p.set_scissor_rect(
2972                                        x1_clamped as u32,
2973                                        y1_clamped as u32,
2974                                        w as u32,
2975                                        h as u32,
2976                                    );
2977                                } else {
2978                                    p.set_scissor_rect(0, 0, 1, 1);
2979                                }
2980                            }
2981                        }
2982                        p.draw_indexed(
2983                            call.index_start..call.index_start + call.index_count,
2984                            0,
2985                            0..1,
2986                        );
2987                    }
2988                }
2989                ui_encoder.finish()
2990            },
2991        );
2992
2993        self.staging_command_buffers.push(glass_cb);
2994        self.staging_command_buffers.push(ui_cb);
2995
2996        // Update telemetry for parallel work
2997        let glass_calls = self.draw_calls.iter().filter(|c| matches!(c.material, cvkg_core::DrawMaterial::Glass { .. })).count();
2998        let glass_verts: u32 = self
2999            .draw_calls
3000            .iter()
3001            .filter(|c| matches!(c.material, cvkg_core::DrawMaterial::Glass { .. }))
3002            .map(|c| c.index_count)
3003            .sum();
3004        let ui_calls = self.draw_calls.iter().filter(|c| matches!(c.material, cvkg_core::DrawMaterial::TopUI)).count();
3005        let ui_verts: u32 = self
3006            .draw_calls
3007            .iter()
3008            .filter(|c| matches!(c.material, cvkg_core::DrawMaterial::TopUI))
3009            .map(|c| c.index_count)
3010            .sum();
3011        self.telemetry.draw_calls += (glass_calls + ui_calls) as u32;
3012        self.telemetry.vertices += glass_verts + ui_verts;
3013
3014        // 3. Start POST-parallel work (Bloom & Composite)
3015        let mut post_encoder =
3016            self.device
3017                .create_command_encoder(&wgpu::CommandEncoderDescriptor {
3018                    label: Some("Surtr Post-Process Encoder"),
3019                });
3020
3021        // ── Pass 5: Bloom Extract (Complete Scene) ──────────────────────────
3022        {
3023            let mut p = post_encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
3024                label: Some("Surtr Bloom Extract"),
3025                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
3026                    view: ctx_blur_texture_a,
3027                    resolve_target: None,
3028                    ops: wgpu::Operations {
3029                        load: wgpu::LoadOp::Clear(wgpu::Color {
3030                            r: 0.0,
3031                            g: 0.0,
3032                            b: 0.0,
3033                            a: 0.0,
3034                        }),
3035                        store: wgpu::StoreOp::Store,
3036                    },
3037                    depth_slice: None,
3038                })],
3039                ..Default::default()
3040            });
3041            p.set_pipeline(&self.bloom_extract_pipeline);
3042            p.set_bind_group(0, ctx_scene_texture_bind_group, &[]);
3043            p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
3044            p.set_bind_group(2, &self.berserker_bind_group, &[]);
3045            p.draw(0..6, 0..1);
3046        }
3047
3048        // ── Pass 6: Blur Bloom ──────────────────────────────────────────────
3049        for _ in 0..2 {
3050            {
3051                let mut p = post_encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
3052                    label: Some("Bloom Blur H"),
3053                    color_attachments: &[Some(wgpu::RenderPassColorAttachment {
3054                        view: ctx_blur_texture_b,
3055                        resolve_target: None,
3056                        ops: wgpu::Operations {
3057                            load: wgpu::LoadOp::Clear(wgpu::Color {
3058                                r: 0.0,
3059                                g: 0.0,
3060                                b: 0.0,
3061                                a: 0.0,
3062                            }),
3063                            store: wgpu::StoreOp::Store,
3064                        },
3065                        depth_slice: None,
3066                    })],
3067                    ..Default::default()
3068                });
3069                p.set_pipeline(&self.blur_h_pipeline);
3070                p.set_bind_group(0, ctx_blur_bind_group_a, &[]);
3071                p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
3072                p.set_bind_group(2, &self.berserker_bind_group, &[]);
3073                p.draw(0..6, 0..1);
3074            }
3075            {
3076                let mut p = post_encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
3077                    label: Some("Bloom Blur V"),
3078                    color_attachments: &[Some(wgpu::RenderPassColorAttachment {
3079                        view: ctx_blur_texture_a,
3080                        resolve_target: None,
3081                        ops: wgpu::Operations {
3082                            load: wgpu::LoadOp::Clear(wgpu::Color {
3083                                r: 0.0,
3084                                g: 0.0,
3085                                b: 0.0,
3086                                a: 0.0,
3087                            }),
3088                            store: wgpu::StoreOp::Store,
3089                        },
3090                        depth_slice: None,
3091                    })],
3092                    ..Default::default()
3093                });
3094                p.set_pipeline(&self.blur_v_pipeline);
3095                p.set_bind_group(0, ctx_blur_bind_group_b, &[]);
3096                p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
3097                p.set_bind_group(2, &self.berserker_bind_group, &[]);
3098                p.draw(0..6, 0..1);
3099            }
3100        }
3101
3102        // ── Pass 7: Composite & Tone Map ────────────────────────────────────
3103        {
3104            let mut p = post_encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
3105                label: Some("Surtr P7 Composite"),
3106                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
3107                    view: &target_view,
3108                    resolve_target: None,
3109                    ops: wgpu::Operations {
3110                        load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
3111                        store: wgpu::StoreOp::Store,
3112                    },
3113                    depth_slice: None,
3114                })],
3115                depth_stencil_attachment: None,
3116                timestamp_writes: self.skuld_queries.as_ref().map(|q| wgpu::RenderPassTimestampWrites {
3117                    query_set: q,
3118                    beginning_of_pass_write_index: None,
3119                    end_of_pass_write_index: Some(1),
3120                }),
3121                occlusion_query_set: None,
3122                multiview_mask: None,
3123            });
3124            p.set_pipeline(&self.composite_pipeline);
3125            p.set_bind_group(0, ctx_scene_texture_bind_group, &[]);
3126            p.set_bind_group(1, ctx_blur_env_bind_group_a, &[]);
3127            p.set_bind_group(2, &self.berserker_bind_group, &[]);
3128            p.draw(0..6, 0..1);
3129            self.telemetry.draw_calls += 1;
3130        }
3131
3132        self.telemetry.frame_time_ms = self.last_frame_start.elapsed().as_secs_f32() * 1000.0;
3133        self.update_vram_telemetry();
3134
3135        // Skuld: Resolve timestamps
3136        if let (Some(q), Some(b), Some(rb)) = (
3137            &self.skuld_queries,
3138            &self.skuld_buffer,
3139            &self.skuld_read_buffer,
3140        ) {
3141            post_encoder.resolve_query_set(q, 0..2, b, 0);
3142            post_encoder.copy_buffer_to_buffer(b, 0, rb, 0, 16);
3143        }
3144
3145        // Finalize post-parallel work
3146        self.staging_command_buffers.push(post_encoder.finish());
3147
3148        // Atomic submission: all blocks in correct sequence
3149        let cmds = std::mem::take(&mut self.staging_command_buffers);
3150        self.queue.submit(cmds);
3151        if let Some(f) = surface_texture {
3152            f.present();
3153        }
3154    }
3155}
3156
3157impl cvkg_core::ElapsedTime for SurtrRenderer {
3158    fn delta_time(&self) -> f32 {
3159        self.current_scene.delta_time
3160    }
3161
3162    fn elapsed_time(&self) -> f32 {
3163        self.start_time.elapsed().as_secs_f32()
3164    }
3165}
3166
3167impl SurtrRenderer {
3168    /// load_image_to_atlas — Packs a raw asset into the Mega-Atlas.
3169    /// This is used for common icons to enable aggressive batching (1 draw call).
3170    pub fn load_image_to_atlas(&mut self, name: &str, data: &[u8]) {
3171        if self.image_uv_registry.contains(name) {
3172            return;
3173        }
3174        let img_result = image::load_from_memory(data);
3175        let img = match img_result {
3176            Ok(img) => img.to_rgba8(),
3177            Err(e) => {
3178                log::error!("Failed to load image {} to atlas: {}", name, e);
3179                return;
3180            }
3181        };
3182        let (width, height) = img.dimensions();
3183
3184        // Pack into atlas
3185        if let Some((x, y)) = self.atlas_packer.pack(width, height) {
3186            let uv_rect = Rect {
3187                x: x as f32 / 4096.0,
3188                y: y as f32 / 4096.0,
3189                width: width as f32 / 4096.0,
3190                height: height as f32 / 4096.0,
3191            };
3192
3193            // Upload to GPU
3194            self.queue.write_texture(
3195                wgpu::TexelCopyTextureInfo {
3196                    texture: &self.mega_atlas_tex,
3197                    mip_level: 0,
3198                    origin: wgpu::Origin3d { x, y, z: 0 },
3199                    aspect: wgpu::TextureAspect::All,
3200                },
3201                &img,
3202                wgpu::TexelCopyBufferLayout {
3203                    offset: 0,
3204                    bytes_per_row: Some(4 * width),
3205                    rows_per_image: Some(height),
3206                },
3207                wgpu::Extent3d {
3208                    width,
3209                    height,
3210                    depth_or_array_layers: 1,
3211                },
3212            );
3213
3214            self.image_uv_registry.put(name.to_string(), uv_rect);
3215            self.texture_registry.put(name.to_string(), 0); // Index 0 is the dummy white texture
3216            log::debug!(
3217                "[Surtr] Packed '{}' into Mega-Atlas at ({}, {})",
3218                name,
3219                x,
3220                y
3221            );
3222        } else {
3223            log::warn!(
3224                "ATLAS_FULL: Failed to pack '{}' into Mega-Atlas. Falling back to Texture Array.",
3225                name
3226            );
3227            self.load_image(name, data);
3228        }
3229    }
3230
3231    /// Shapes a text string using a predefined system font stack.
3232    ///
3233    /// # Contract
3234    /// Evaluates text shaping with fallbacks: queries "SF Pro Text", "SF Pro", "Inter",
3235    /// "Helvetica Neue", "Helvetica", "Arial", and defaults back to "sans-serif".
3236    /// This ensures visual typographic consistency across platforms where specific
3237    /// branding faces may or may not be installed.
3238    fn shape_text_with_stack(&mut self, text: &str, size: f32) -> cvkg_runic_text::ShapedText {
3239        let mut style = cvkg_runic_text::TextStyle::new("SF Pro Text", size);
3240        style.fallback_families = vec![
3241            "SF Pro".to_string(),
3242            "Inter".to_string(),
3243            "Helvetica Neue".to_string(),
3244            "Helvetica".to_string(),
3245            "Arial".to_string(),
3246            "sans-serif".to_string(),
3247        ];
3248        let spans = vec![cvkg_runic_text::TextSpan::new(text, style)];
3249        self.text_engine.shape_layout(&spans, None, cvkg_runic_text::TextAlign::Start, cvkg_runic_text::TextOverflow::WordWrap)
3250            .unwrap_or_else(|_| cvkg_runic_text::ShapedText {
3251                glyphs: Vec::new(),
3252                lines: Vec::new(),
3253                width: 0.0,
3254                height: 0.0,
3255                text: text.to_string(),
3256                spans: Vec::new(),
3257                has_rtl: false,
3258                ascent: 0.0,
3259                descent: 0.0,
3260                line_gap: 0.0,
3261                grapheme_boundaries: vec![],
3262            })
3263    }
3264}
3265
3266impl cvkg_core::Renderer for SurtrRenderer {
3267    fn is_over_budget(&self) -> bool {
3268        self.frame_budget.allow_degradation
3269            && self.last_frame_start.elapsed().as_secs_f32() * 1000.0 > self.frame_budget.target_ms
3270    }
3271
3272    /// fill_rect — Standard rectangle drawing method.
3273    fn prewarm_vram(&mut self, assets: Vec<(String, Vec<u8>)>) {
3274        log::info!(
3275            "[Surtr] Pre-warming Mega-Atlas with {} assets...",
3276            assets.len()
3277        );
3278        for (name, data) in assets {
3279            self.load_image_to_atlas(&name, &data);
3280        }
3281    }
3282
3283    fn fill_rect(&mut self, rect: Rect, color: [f32; 4]) {
3284        self.fill_rect_with_mode(rect, self.apply_opacity(color), 0, None);
3285    }
3286
3287    fn fill_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4]) {
3288        self.fill_rect_with_full_params(
3289            rect,
3290            self.apply_opacity(color),
3291            3,
3292            None,
3293            radius,
3294            Rect {
3295                x: 0.0,
3296                y: 0.0,
3297                width: 1.0,
3298                height: 1.0,
3299            },
3300        );
3301    }
3302
3303    fn fill_ellipse(&mut self, rect: Rect, color: [f32; 4]) {
3304        self.fill_rect_with_full_params(
3305            rect,
3306            self.apply_opacity(color),
3307            4,
3308            None,
3309            0.0,
3310            Rect {
3311                x: 0.0,
3312                y: 0.0,
3313                width: 1.0,
3314                height: 1.0,
3315            },
3316        );
3317    }
3318
3319    fn draw_3d_cube(&mut self, rect: Rect, color: [f32; 4], rotation: [f32; 3]) {
3320        self.fill_rect_with_full_params_and_slice(
3321            rect,
3322            self.apply_opacity(color),
3323            21,
3324            None,
3325            0.0,
3326            Rect {
3327                x: 0.0,
3328                y: 0.0,
3329                width: 1.0,
3330                height: 1.0,
3331            },
3332            [rotation[0], rotation[1], rotation[2], 0.0],
3333        );
3334    }
3335
3336    fn bifrost(&mut self, rect: Rect, blur: f32, _saturation: f32, opacity: f32) {
3337        // Calculate screen-space UVs for high-fidelity global refraction
3338        let screen_uv = Rect {
3339            x: rect.x / self.current_width() as f32,
3340            y: rect.y / self.current_height() as f32,
3341            width: rect.width / self.current_width() as f32,
3342            height: rect.height / self.current_height() as f32,
3343        };
3344        // Use mode 7 for high-fidelity background blur sampling
3345        // Use the blur parameter as corner radius for the glass panel
3346        self.fill_rect_with_full_params(rect, [1.0, 1.0, 1.0, opacity], 7, None, blur, screen_uv);
3347    }
3348
3349    fn gungnir(&mut self, rect: Rect, color: [f32; 4], radius: f32, intensity: f32) {
3350        // Create neon glow effect using additive blending
3351        // This renders a glowing aura around the element
3352        let center_x = rect.x + rect.width * 0.5;
3353        let center_y = rect.y + rect.height * 0.5;
3354        let max_dim = rect.width.max(rect.height) * 0.5 + radius;
3355
3356        // Draw expanding glow layers
3357        for i in 0..8 {
3358            let alpha = intensity / (i as f32 + 1.0) * 0.3;
3359            let glow_color = [color[0], color[1], color[2], alpha];
3360            self.fill_rect_with_mode(
3361                Rect {
3362                    x: center_x - max_dim - i as f32 * 2.0,
3363                    y: center_y - max_dim - i as f32 * 2.0,
3364                    width: max_dim * 2.0 + i as f32 * 4.0,
3365                    height: max_dim * 2.0 + i as f32 * 4.0,
3366                },
3367                glow_color,
3368                8, // Mode for additive blending
3369                None,
3370            );
3371        }
3372    }
3373
3374    /// Renders a dynamic glowing hover boundary field around a hit target.
3375    ///
3376    /// # Contract
3377    /// Expands the bounding box of the visual target by `radius` to establish
3378    /// a continuous proximity glow. Uses blending mode 18 (GPU drop shadow/glow)
3379    /// to rasterize the glow with specialized radius-to-margin uv coordinate mappings.
3380    fn mani_glow(&mut self, rect: Rect, color: [f32; 4], radius: f32) {
3381        let margin = radius;
3382        let glow_rect = Rect {
3383            x: rect.x - margin,
3384            y: rect.y - margin,
3385            width: rect.width + 2.0 * margin,
3386            height: rect.height + 2.0 * margin,
3387        };
3388        let uv_rect = Rect {
3389            x: margin,
3390            y: radius,
3391            width: 0.0,
3392            height: 0.0,
3393        };
3394        self.fill_rect_with_full_params(glow_rect, self.apply_opacity(color), 18, None, 8.0, uv_rect);
3395    }
3396
3397
3398    fn stroke_rect(&mut self, rect: Rect, color: [f32; 4], stroke_width: f32) {
3399        let c = self.apply_opacity(color);
3400        let hw = stroke_width;
3401        // Top, bottom, left, right edge bars
3402        self.fill_rect_with_mode(
3403            Rect {
3404                x: rect.x,
3405                y: rect.y,
3406                width: rect.width,
3407                height: hw,
3408            },
3409            c,
3410            1,
3411            None,
3412        );
3413        self.fill_rect_with_mode(
3414            Rect {
3415                x: rect.x,
3416                y: rect.y + rect.height - hw,
3417                width: rect.width,
3418                height: hw,
3419            },
3420            c,
3421            1,
3422            None,
3423        );
3424        self.fill_rect_with_mode(
3425            Rect {
3426                x: rect.x,
3427                y: rect.y,
3428                width: hw,
3429                height: rect.height,
3430            },
3431            c,
3432            1,
3433            None,
3434        );
3435        self.fill_rect_with_mode(
3436            Rect {
3437                x: rect.x + rect.width - hw,
3438                y: rect.y,
3439                width: hw,
3440                height: rect.height,
3441            },
3442            c,
3443            1,
3444            None,
3445        );
3446    }
3447
3448    fn stroke_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4], stroke_width: f32) {
3449        self.fill_rect_with_full_params(
3450            rect,
3451            self.apply_opacity(color),
3452            17,
3453            None,
3454            radius,
3455            Rect {
3456                x: stroke_width,
3457                y: 0.0,
3458                width: 0.0,
3459                height: 0.0,
3460            },
3461        );
3462    }
3463
3464    fn stroke_ellipse(&mut self, _rect: Rect, _color: [f32; 4], _stroke_width: f32) {}
3465
3466    fn draw_linear_gradient(
3467        &mut self,
3468        rect: Rect,
3469        start_color: [f32; 4],
3470        end_color: [f32; 4],
3471        angle: f32,
3472    ) {
3473        self.fill_rect_with_full_params_and_slice(
3474            rect,
3475            self.apply_opacity(start_color),
3476            15,
3477            None,
3478            0.0,
3479            Rect {
3480                x: angle,
3481                y: 0.0,
3482                width: 1.0,
3483                height: 1.0,
3484            },
3485            end_color,
3486        );
3487    }
3488
3489    fn draw_radial_gradient(&mut self, rect: Rect, inner_color: [f32; 4], outer_color: [f32; 4]) {
3490        self.fill_rect_with_full_params_and_slice(
3491            rect,
3492            self.apply_opacity(inner_color),
3493            16,
3494            None,
3495            0.0,
3496            Rect {
3497                x: 0.0,
3498                y: 0.0,
3499                width: 1.0,
3500                height: 1.0,
3501            },
3502            outer_color,
3503        );
3504    }
3505
3506    fn draw_drop_shadow(
3507        &mut self,
3508        rect: Rect,
3509        radius: f32,
3510        color: [f32; 4],
3511        blur: f32,
3512        spread: f32,
3513    ) {
3514        let margin = blur + spread;
3515        let inflated = Rect {
3516            x: rect.x - margin,
3517            y: rect.y - margin,
3518            width: rect.width + margin * 2.0,
3519            height: rect.height + margin * 2.0,
3520        };
3521        // uv.x = total margin (for SDF offset), uv.y = blur width (for falloff)
3522        self.fill_rect_with_full_params(
3523            inflated,
3524            self.apply_opacity(color),
3525            18,
3526            None,
3527            radius,
3528            Rect {
3529                x: margin,
3530                y: blur,
3531                width: 0.0,
3532                height: 0.0,
3533            },
3534        );
3535    }
3536
3537    fn stroke_dashed_rounded_rect(
3538        &mut self,
3539        rect: Rect,
3540        radius: f32,
3541        color: [f32; 4],
3542        width: f32,
3543        dash: f32,
3544        gap: f32,
3545    ) {
3546        self.fill_rect_with_full_params(
3547            rect,
3548            self.apply_opacity(color),
3549            19,
3550            None,
3551            radius,
3552            Rect {
3553                x: width,
3554                y: dash,
3555                width: gap,
3556                height: 0.0,
3557            },
3558        );
3559    }
3560
3561    fn draw_9slice(
3562        &mut self,
3563        image_name: &str,
3564        rect: Rect,
3565        left: f32,
3566        top: f32,
3567        right: f32,
3568        bottom: f32,
3569    ) {
3570        let c = self.apply_opacity([1.0, 1.0, 1.0, 1.0]);
3571        let tid = self.get_texture_id(image_name);
3572        self.fill_rect_with_full_params(
3573            rect,
3574            c,
3575            20,
3576            tid,
3577            bottom,
3578            Rect {
3579                x: left,
3580                y: top,
3581                width: right,
3582                height: 0.0,
3583            },
3584        );
3585    }
3586
3587    fn draw_line(
3588        &mut self,
3589        x1: f32,
3590        y1: f32,
3591        x2: f32,
3592        y2: f32,
3593        color: [f32; 4],
3594        stroke_width: f32,
3595    ) {
3596        let dx = x2 - x1;
3597        let dy = y2 - y1;
3598        let len = (dx * dx + dy * dy).sqrt();
3599        if len < 0.001 {
3600            return;
3601        }
3602
3603        let c = self.apply_opacity(color);
3604        let tid = self.get_texture_id("__mega_atlas");
3605
3606        self.fill_rect_with_mode(
3607            Rect {
3608                x: (x1 + x2) / 2.0 - len / 2.0,
3609                y: (y1 + y2) / 2.0 - stroke_width / 2.0,
3610                width: len,
3611                height: stroke_width,
3612            },
3613            c,
3614            1, // Gungnir Mode for glowing lines
3615            tid,
3616        );
3617    }
3618
3619    fn draw_image(&mut self, image_name: &str, rect: Rect) {
3620        let tid = self
3621            .get_texture_id(image_name)
3622            .or_else(|| self.get_texture_id("__mega_atlas"));
3623        let uv_rect = self
3624            .image_uv_registry
3625            .get(image_name)
3626            .copied()
3627            .unwrap_or(Rect {
3628                x: 0.0,
3629                y: 0.0,
3630                width: 1.0,
3631                height: 1.0,
3632            });
3633        self.fill_rect_with_full_params(rect, [1.0, 1.0, 1.0, 1.0], 2, tid, 0.0, uv_rect);
3634    }
3635
3636
3637    fn draw_text(&mut self, text: &str, x: f32, y: f32, size: f32, color: [f32; 4]) {
3638        // High-DPI: Shape and rasterize at the physical scale factor for maximum sharpness.
3639        let scaled_size = size * self.current_scale_factor();
3640        let shaped = self.shape_text_with_stack(text, scaled_size);
3641        let c = self.apply_opacity(color);
3642
3643        for glyph in shaped.glyphs {
3644            let cache_key = glyph.cache_key;
3645
3646            let (uv_rect, w, h) = if let Some(info) = self.text_cache.get(&cache_key) {
3647                *info
3648            } else {
3649                if let Some(image) = self.text_engine.rasterize(cache_key) {
3650                    let gw = image.width;
3651                    let gh = image.height;
3652
3653                    let pack_res = self.atlas_packer.pack(gw, gh);
3654                    let (nx, ny) = if let Some(pos) = pack_res {
3655                        pos
3656                    } else {
3657                        // RECLAIM & RETRY: Atlas is full, quench the forge and try again.
3658                        self.reclaim_vram();
3659                        self.atlas_packer.pack(gw, gh).unwrap_or((0, 0))
3660                    };
3661
3662                    let mut rgba_data = Vec::with_capacity((gw * gh * 4) as usize);
3663                    for alpha in &image.data {
3664                        rgba_data.push(255);
3665                        rgba_data.push(255);
3666                        rgba_data.push(255);
3667                        rgba_data.push(*alpha);
3668                    }
3669
3670                    self.queue.write_texture(
3671                        wgpu::TexelCopyTextureInfo {
3672                            texture: &self.mega_atlas_tex,
3673                            mip_level: 0,
3674                            origin: wgpu::Origin3d { x: nx, y: ny, z: 0 },
3675                            aspect: wgpu::TextureAspect::All,
3676                        },
3677                        &rgba_data,
3678                        wgpu::TexelCopyBufferLayout {
3679                            offset: 0,
3680                            bytes_per_row: Some(gw * 4),
3681                            rows_per_image: Some(gh),
3682                        },
3683                        wgpu::Extent3d {
3684                            width: gw,
3685                            height: gh,
3686                            depth_or_array_layers: 1,
3687                        },
3688                    );
3689
3690                    let info = (
3691                        Rect {
3692                            x: nx as f32 / 4096.0,
3693                            y: ny as f32 / 4096.0,
3694                            width: gw as f32 / 4096.0,
3695                            height: gh as f32 / 4096.0,
3696                        },
3697                        gw as f32,
3698                        gh as f32,
3699                    );
3700                    self.text_cache.put(cache_key, info);
3701                    info
3702                } else {
3703                    (Rect::zero(), 0.0, 0.0)
3704                }
3705            };
3706
3707            if w > 0.0 {
3708                // Map physical glyph dimensions and positions back to logical units
3709                // so the logical orthographic projection matrix places them correctly.
3710                let glyph_rect = Rect {
3711                    x: x + glyph.x / self.current_scale_factor(),
3712                    y: y + glyph.y / self.current_scale_factor(),
3713                    width: w / self.current_scale_factor(),
3714                    height: h / self.current_scale_factor(),
3715                };
3716                let tid = self.get_texture_id("__mega_atlas");
3717                self.fill_rect_with_full_params(glyph_rect, c, 6, tid, 0.0, uv_rect);
3718            }
3719        }
3720    }
3721
3722    /// measure_text — Calculates the dimensions of a text string without rendering.
3723    fn measure_text(&mut self, text: &str, size: f32) -> (f32, f32) {
3724        let shaped = self.shape_text_with_stack(text, size);
3725        (shaped.width, shaped.height)
3726    }
3727
3728    fn draw_texture(&mut self, texture_id: u32, rect: Rect) {
3729        self.fill_rect_with_full_params(
3730            rect,
3731            [1.0, 1.0, 1.0, 1.0],
3732            2,
3733            Some(texture_id),
3734            0.0,
3735            Rect {
3736                x: 0.0,
3737                y: 0.0,
3738                width: 1.0,
3739                height: 1.0,
3740            },
3741        );
3742    }
3743
3744    /// load_image — Proactively pushes a raw asset into the Mega-Atlas.
3745    /// load_image — Proactively pushes a raw asset into the Texture Array.
3746    fn load_image(&mut self, name: &str, data: &[u8]) {
3747        if self.image_uv_registry.contains(name) {
3748            return;
3749        }
3750        let img_result = image::load_from_memory(data);
3751        let img = match img_result {
3752            Ok(img) => img.to_rgba8(),
3753            Err(e) => {
3754                log::error!("Failed to load image {}: {}", name, e);
3755                image::RgbaImage::from_pixel(1, 1, image::Rgba([0, 0, 0, 255]))
3756            }
3757        };
3758        let (width, height) = img.dimensions();
3759
3760        let size = wgpu::Extent3d {
3761            width,
3762            height,
3763            depth_or_array_layers: 1,
3764        };
3765        let texture = self.device.create_texture(&wgpu::TextureDescriptor {
3766            label: Some(&format!("Texture Array Layer: {}", name)),
3767            size,
3768            mip_level_count: 1,
3769            sample_count: 1,
3770            dimension: wgpu::TextureDimension::D2,
3771            format: wgpu::TextureFormat::Rgba8UnormSrgb,
3772            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
3773            view_formats: &[],
3774        });
3775
3776        self.queue.write_texture(
3777            wgpu::TexelCopyTextureInfo {
3778                texture: &texture,
3779                mip_level: 0,
3780                origin: wgpu::Origin3d::ZERO,
3781                aspect: wgpu::TextureAspect::All,
3782            },
3783            &img,
3784            wgpu::TexelCopyBufferLayout {
3785                offset: 0,
3786                bytes_per_row: Some(4 * width),
3787                rows_per_image: Some(height),
3788            },
3789            size,
3790        );
3791
3792        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
3793
3794        // Slot allocation (Skip index 0 which is the dummy/atlas)
3795        let index = if self.texture_registry.len() < 255 {
3796            (self.texture_registry.len() + 1) as u32
3797        } else {
3798            // Evict the least recently used texture
3799            if let Some((old_name, old_index)) = self.texture_registry.pop_lru() {
3800                self.image_uv_registry.pop(&old_name);
3801                old_index
3802            } else {
3803                1 // Fallback
3804            }
3805        };
3806
3807        self.texture_views[index as usize] = view;
3808        self.image_uv_registry.put(
3809            name.to_string(),
3810            Rect {
3811                x: 0.0,
3812                y: 0.0,
3813                width: 1.0,
3814                height: 1.0,
3815            },
3816        );
3817        self.texture_registry.put(name.to_string(), index);
3818        self.rebuild_texture_array_bind_group();
3819    }
3820
3821    fn push_clip_rect(&mut self, rect: Rect) {
3822        self.clip_stack.push(rect);
3823    }
3824
3825    fn pop_clip_rect(&mut self) {
3826        self.clip_stack.pop();
3827    }
3828
3829    fn current_clip_rect(&self) -> Rect {
3830        self.clip_stack.last().copied().unwrap_or(Rect::new(
3831            0.0,
3832            0.0,
3833            self.current_width() as f32,
3834            self.current_height() as f32,
3835        ))
3836    }
3837
3838    fn memoize(&mut self, _id: u64, _data_hash: u64, render_fn: &dyn Fn(&mut dyn Renderer)) {
3839        render_fn(self);
3840    }
3841
3842    fn push_opacity(&mut self, opacity: f32) {
3843        let current = self.opacity_stack.last().copied().unwrap_or(1.0);
3844        self.opacity_stack.push(current * opacity);
3845    }
3846
3847    fn pop_opacity(&mut self) {
3848        self.opacity_stack.pop();
3849    }
3850
3851    fn push_shadow(&mut self, radius: f32, color: [f32; 4], offset: [f32; 2]) {
3852        self.shadow_stack.push(ShadowState {
3853            radius,
3854            color,
3855            _offset: offset,
3856        });
3857    }
3858
3859    fn pop_shadow(&mut self) {
3860        self.shadow_stack.pop();
3861    }
3862
3863    fn push_transform(&mut self, translation: [f32; 2], scale: [f32; 2], rotation: f32) {
3864        let (current_t, current_s, current_r) =
3865            self.transform_stack
3866                .last()
3867                .copied()
3868                .unwrap_or(([0.0, 0.0], [1.0, 1.0], 0.0));
3869
3870        // Combine transforms (simplified: this doesn't handle full matrix multiplication yet,
3871        // but for basic UI nesting it's often sufficient to just add translation and multiply scale).
3872        // A full implementation would use mat3x3.
3873        let new_t = [
3874            current_t[0] + translation[0] * current_s[0],
3875            current_t[1] + translation[1] * current_s[1],
3876        ];
3877        let new_s = [current_s[0] * scale[0], current_s[1] * scale[1]];
3878        let new_r = current_r + rotation;
3879
3880        self.transform_stack.push((new_t, new_s, new_r));
3881    }
3882
3883    fn pop_transform(&mut self) {
3884        self.transform_stack.pop();
3885    }
3886
3887    fn set_theme(&mut self, theme: ColorTheme) {
3888        self.current_theme = theme;
3889        self.queue
3890            .write_buffer(&self.theme_buffer, 0, bytemuck::bytes_of(&theme));
3891    }
3892
3893    fn set_rage(&mut self, rage: f32) {
3894        self.current_scene.berzerker_rage = rage;
3895        // scene_buffer is updated every frame in begin_frame, so no need to write here
3896    }
3897
3898    fn trigger_shatter_event(&mut self, origin: [f32; 2], force: f32) {
3899        self.current_scene.shatter_origin = origin;
3900        self.current_scene.shatter_time = self.current_scene.time;
3901        self.current_scene.shatter_force = force;
3902    }
3903
3904    fn set_scene_preset(&mut self, preset: u32) {
3905        self.current_scene.scene_type = preset;
3906    }
3907
3908    /// push_mjolnir_slice — Pushes a geometric clipping plane onto the stack.
3909    /// All subsequent draw calls will be sliced by this plane until it is popped.
3910    fn push_mjolnir_slice(&mut self, angle: f32, offset: f32) {
3911        self.slice_stack.push((angle, offset));
3912    }
3913
3914    /// pop_mjolnir_slice — Removes the top-most geometric clipping plane from the stack.
3915    fn pop_mjolnir_slice(&mut self) {
3916        self.slice_stack.pop();
3917    }
3918
3919    fn mjolnir_shatter(&mut self, rect: Rect, pieces: u32, force: f32, color: [f32; 4]) {
3920        self.shatter_internal(rect, pieces, force, color, 8);
3921    }
3922
3923    fn mjolnir_fluid_shatter(&mut self, rect: Rect, pieces: u32, force: f32, color: [f32; 4]) {
3924        self.shatter_internal(rect, pieces, force, color, 11);
3925    }
3926
3927    fn draw_mjolnir_bolt(&mut self, from: [f32; 2], to: [f32; 2], color: [f32; 4]) {
3928        self.recursive_bolt(from, to, 4, color);
3929    }
3930
3931    fn upload_data_texture(&mut self, id: &str, data: &[f32], width: u32, height: u32) {
3932        let size = wgpu::Extent3d {
3933            width,
3934            height,
3935            depth_or_array_layers: 1,
3936        };
3937        let texture = self.device.create_texture(&wgpu::TextureDescriptor {
3938            label: Some(id),
3939            size,
3940            mip_level_count: 1,
3941            sample_count: 1,
3942            dimension: wgpu::TextureDimension::D2,
3943            format: wgpu::TextureFormat::R32Float,
3944            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
3945            view_formats: &[],
3946        });
3947        self.queue.write_texture(
3948            wgpu::TexelCopyTextureInfo {
3949                texture: &texture,
3950                mip_level: 0,
3951                origin: wgpu::Origin3d::ZERO,
3952                aspect: wgpu::TextureAspect::All,
3953            },
3954            bytemuck::cast_slice(data),
3955            wgpu::TexelCopyBufferLayout {
3956                offset: 0,
3957                bytes_per_row: Some(4 * width),
3958                rows_per_image: Some(height),
3959            },
3960            size,
3961        );
3962        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
3963        let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
3964            address_mode_u: wgpu::AddressMode::ClampToEdge,
3965            address_mode_v: wgpu::AddressMode::ClampToEdge,
3966            mag_filter: wgpu::FilterMode::Linear,
3967            ..Default::default()
3968        });
3969        let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
3970            layout: &self.texture_bind_group_layout,
3971            entries: &[
3972                wgpu::BindGroupEntry {
3973                    binding: 0,
3974                    resource: wgpu::BindingResource::TextureViewArray(&vec![&view; 256]),
3975                },
3976                wgpu::BindGroupEntry {
3977                    binding: 1,
3978                    resource: wgpu::BindingResource::Sampler(&sampler),
3979                },
3980            ],
3981            label: Some(id),
3982        });
3983        self.texture_bind_groups.push(bind_group);
3984        let tid = (self.texture_bind_groups.len() - 1) as u32;
3985        self.texture_registry.put(id.to_string(), tid);
3986    }
3987
3988    fn draw_heatmap(&mut self, texture_id: &str, rect: Rect, _palette: &str) {
3989        let tid = self.get_texture_id(texture_id);
3990        self.fill_rect_with_mode(rect, [1.0, 1.0, 1.0, 1.0], 12, tid);
3991    }
3992
3993    fn draw_mesh(&mut self, mesh: &Mesh, color: [f32; 4], transform: glam::Mat4) {
3994        let base_idx = self.vertices.len() as u32;
3995        let screen = [self.current_width() as f32, self.current_height() as f32];
3996
3997        for i in 0..mesh.vertices.len() {
3998            let pos = transform.transform_point3(glam::Vec3::from(mesh.vertices[i]));
3999            let norm = transform.transform_vector3(glam::Vec3::from(mesh.normals[i]));
4000
4001            let (translation, scale_transform, rotation) = self.get_current_transform();
4002            self.vertices.push(Vertex {
4003                position: pos.to_array(),
4004                normal: norm.to_array(),
4005                uv: [0.0, 0.0],
4006                color,
4007                mode: 13, // Mode 13: 3D Surface
4008                radius: 0.0,
4009                slice: [0.0, 0.0, 0.0, 1.0],
4010                logical: [0.0, 0.0],
4011                size: [0.0, 0.0],
4012                screen,
4013                clip: [-10000.0, -10000.0, 20000.0, 20000.0],
4014                translation,
4015                scale: scale_transform,
4016                rotation,
4017                tex_index: 0,
4018            });
4019        }
4020
4021        for idx in &mesh.indices {
4022            self.indices.push(base_idx + idx);
4023        }
4024
4025        if self.draw_calls.is_empty() || self.current_texture_id.is_some() {
4026            self.current_texture_id = None;
4027            self.draw_calls.push(DrawCall {
4028                texture_id: None,
4029                scissor_rect: self.clip_stack.last().copied(),
4030                index_start: (self.indices.len() as u32) - (mesh.indices.len() as u32),
4031                index_count: mesh.indices.len() as u32,
4032                material: cvkg_core::DrawMaterial::Opaque,
4033            });
4034        } else {
4035            self.draw_calls.last_mut().unwrap().index_count += mesh.indices.len() as u32;
4036        }
4037    }
4038
4039    fn register_shared_element(&mut self, id: &str, rect: Rect) {
4040        self.shared_elements.put(id.to_string(), rect);
4041    }
4042
4043    fn set_z_index(&mut self, z: f32) {
4044        self.current_z = z;
4045    }
4046
4047    fn set_material(&mut self, material: cvkg_core::DrawMaterial) {
4048        self.current_draw_material = material;
4049    }
4050
4051    fn current_material(&self) -> cvkg_core::DrawMaterial {
4052        self.current_draw_material
4053    }
4054
4055    fn get_z_index(&self) -> f32 {
4056        self.current_z
4057    }
4058
4059    fn request_redraw(&mut self) {
4060        self.redraw_requested = true;
4061    }
4062
4063    fn push_vnode(&mut self, rect: Rect, name: &'static str) {
4064        self.vnode_stack.push((rect, name));
4065    }
4066
4067    fn pop_vnode(&mut self) {
4068        self.vnode_stack.pop();
4069    }
4070
4071    fn register_handler(
4072        &mut self,
4073        event_type: &str,
4074        handler: std::sync::Arc<dyn Fn(cvkg_core::Event) + Send + Sync>,
4075    ) {
4076        self.event_handlers
4077            .entry(event_type.to_string())
4078            .or_insert_with(Vec::new)
4079            .push(handler);
4080    }
4081}
4082
4083// ── Inherent methods on SurtrRenderer (not part of the Renderer trait) ──
4084
4085impl SurtrRenderer {
4086    /// Clear all registered event handlers. Call at the start of each frame
4087    /// before re-rendering the component tree.
4088    pub fn clear_event_handlers(&mut self) {
4089        self.event_handlers.clear();
4090    }
4091
4092    /// Get all registered event handlers for a specific event type.
4093    pub fn get_handlers(
4094        &self,
4095        event_type: &str,
4096    ) -> Option<&Vec<std::sync::Arc<dyn Fn(cvkg_core::Event) + Send + Sync>>> {
4097        self.event_handlers.get(event_type)
4098    }
4099}
4100
4101// --- SVG Helpers ---
4102
4103fn usvg_to_lyon(path: &usvg::Path) -> lyon::path::Path {
4104    let mut builder = lyon::path::Path::builder();
4105    for segment in path.data().segments() {
4106        match segment {
4107            usvg::tiny_skia_path::PathSegment::MoveTo(p) => {
4108                builder.begin(lyon::math::point(p.x, p.y));
4109            }
4110            usvg::tiny_skia_path::PathSegment::LineTo(p) => {
4111                builder.line_to(lyon::math::point(p.x, p.y));
4112            }
4113            usvg::tiny_skia_path::PathSegment::QuadTo(p1, p) => {
4114                builder.quadratic_bezier_to(
4115                    lyon::math::point(p1.x, p1.y),
4116                    lyon::math::point(p.x, p.y),
4117                );
4118            }
4119            usvg::tiny_skia_path::PathSegment::CubicTo(p1, p2, p) => {
4120                builder.cubic_bezier_to(
4121                    lyon::math::point(p1.x, p1.y),
4122                    lyon::math::point(p2.x, p2.y),
4123                    lyon::math::point(p.x, p.y),
4124                );
4125            }
4126            usvg::tiny_skia_path::PathSegment::Close => {
4127                builder.end(true);
4128            }
4129        }
4130    }
4131    builder.build()
4132}
4133
4134impl SurtrRenderer {
4135    fn get_current_transform(&self) -> ([f32; 2], [f32; 2], f32) {
4136        self.transform_stack
4137            .last()
4138            .cloned()
4139            .unwrap_or(([0.0, 0.0], [1.0, 1.0], 0.0))
4140    }
4141}
4142
4143struct SceneVertexConstructor {
4144    color: [f32; 4],
4145    translation: [f32; 2],
4146    scale: [f32; 2],
4147    rotation: f32,
4148}
4149
4150impl FillVertexConstructor<Vertex> for SceneVertexConstructor {
4151    fn new_vertex(&mut self, vertex: FillVertex) -> Vertex {
4152        Vertex {
4153            position: [vertex.position().x, vertex.position().y, 0.0],
4154            normal: [0.0, 0.0, 1.0],
4155            uv: [0.0, 0.0],
4156            color: self.color,
4157            mode: 0,
4158            radius: 0.0,
4159            slice: [0.0, 0.0, 0.0, 1.0],
4160            logical: [vertex.position().x, vertex.position().y],
4161            size: [1.0, 1.0],
4162            screen: [0.0, 0.0],
4163            clip: [-10000.0, -10000.0, 20000.0, 20000.0],
4164            translation: self.translation,
4165            scale: self.scale,
4166            rotation: self.rotation,
4167            tex_index: 0,
4168        }
4169    }
4170}
4171
4172impl Drop for SurtrRenderer {
4173    fn drop(&mut self) {
4174        // Ensure GPU is idle before dropping to avoid Swapchain semaphore panics
4175        let _ = self.device.poll(wgpu::PollType::Wait {
4176            submission_index: None,
4177            timeout: None,
4178        });
4179    }
4180}
4181
4182impl SurtrRenderer {
4183    /// Submit pre-routed draw command buckets from the cvkg-compositor.
4184    ///
4185    /// Accepts `CommandBuckets` produced by `CompositorEngine::flatten_and_route()`
4186    /// and submits draw calls in the correct pass order for the Backdrop Capture
4187    /// Architecture:
4188    /// 1. Scene commands (opaque) → Scene Capture pass
4189    /// 2. Glass commands → Material Composite pass (samples blur pyramid)
4190    /// 3. Overlay commands → Top-Level Foreground pass
4191    pub fn submit_buckets(&mut self, buckets: &cvkg_compositor::CommandBuckets) {
4192        // Scene pass — opaque draw calls
4193        for routed in &buckets.scene_commands {
4194            self.set_material(cvkg_core::DrawMaterial::Opaque);
4195            self.submit_routed(routed);
4196        }
4197
4198        // Glass pass — glassmorphism draw calls sampling blur pyramid
4199        for routed in &buckets.glass_commands {
4200            let core_material = match routed.material {
4201                cvkg_compositor::Material::Opaque => cvkg_core::DrawMaterial::Opaque,
4202                cvkg_compositor::Material::Glass { blur_radius } => {
4203                    cvkg_core::DrawMaterial::Glass { blur_radius }
4204                }
4205                cvkg_compositor::Material::Overlay => cvkg_core::DrawMaterial::TopUI,
4206            };
4207            self.set_material(core_material);
4208            self.submit_routed(routed);
4209        }
4210
4211        // Overlay pass — foreground UI (crisp text, icons, edge lighting)
4212        for routed in &buckets.overlay_commands {
4213            self.set_material(cvkg_core::DrawMaterial::TopUI);
4214            self.submit_routed(routed);
4215        }
4216    }
4217
4218    /// Submit a single routed draw command through the internal pipeline.
4219    fn submit_routed(&mut self, routed: &cvkg_compositor::RoutedDrawCommand) {
4220        let cmd = &routed.command;
4221        self.fill_rect_with_full_params(
4222            cvkg_core::Rect::new(0.0, 0.0, 1.0, 1.0),
4223            [1.0, 1.0, 1.0, 1.0],
4224            0,
4225            cmd.texture_id,
4226            0.0,
4227            cvkg_core::Rect::new(0.0, 0.0, 1.0, 1.0),
4228        );
4229    }
4230}
4231
4232impl cvkg_core::FrameRenderer<wgpu::CommandEncoder> for SurtrRenderer {
4233    fn begin_frame(&mut self) -> wgpu::CommandEncoder {
4234        cvkg_core::begin_render_phase();
4235        let id = self
4236            .current_window
4237            .expect("No target window set for frame. Call set_target_window first.");
4238        self.begin_frame(id)
4239    }
4240
4241    fn render_frame(&mut self) {
4242        // Visual Lint: If layout was dirtied during the render phase (layout thrashing),
4243        // draw a 10px red border as a warning flash.
4244        if LAYOUT_DIRTY.swap(false, Ordering::AcqRel)
4245            && let Some(window_id) = self.current_window
4246            && let Some(surface_ctx) = self.surfaces.get(&window_id)
4247        {
4248            let w = surface_ctx.config.width as f32;
4249            let h = surface_ctx.config.height as f32;
4250            let border_rect = cvkg_core::Rect {
4251                x: 0.0,
4252                y: 0.0,
4253                width: w,
4254                height: h,
4255            };
4256            // Draw a thick red border to signal layout-thrashing
4257            self.stroke_rect(border_rect, [1.0, 0.0, 0.0, 1.0], 10.0);
4258        }
4259
4260        // Dynamic Buffer Growth (Up to 4x capacity)
4261        let req_v_size = (self.vertices.len() * std::mem::size_of::<Vertex>()) as u64;
4262        let mut cur_v_size = self.vertex_buffer.size();
4263        let max_v_size = (MAX_VERTICES * std::mem::size_of::<Vertex>()) as u64 * 4;
4264        
4265        if req_v_size > cur_v_size {
4266            while cur_v_size < req_v_size && cur_v_size < max_v_size {
4267                cur_v_size *= 2;
4268            }
4269            if req_v_size > max_v_size {
4270                log::error!("Exceeded dynamic vertex buffer max capacity! Capping geometry.");
4271                self.vertices.truncate((max_v_size / std::mem::size_of::<Vertex>() as u64) as usize);
4272                cur_v_size = max_v_size;
4273            }
4274            log::info!("Growing vertex buffer to {} bytes", cur_v_size);
4275            self.vertex_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
4276                label: Some("Vertex Buffer (Grown)"),
4277                size: cur_v_size,
4278                usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
4279                mapped_at_creation: false,
4280            });
4281        }
4282
4283        let req_i_size = (self.indices.len() * std::mem::size_of::<u32>()) as u64;
4284        let mut cur_i_size = self.index_buffer.size();
4285        let max_i_size = (MAX_INDICES * std::mem::size_of::<u32>()) as u64 * 4;
4286        
4287        if req_i_size > cur_i_size {
4288            while cur_i_size < req_i_size && cur_i_size < max_i_size {
4289                cur_i_size *= 2;
4290            }
4291            if req_i_size > max_i_size {
4292                log::error!("Exceeded dynamic index buffer max capacity! Capping geometry.");
4293                self.indices.truncate((max_i_size / std::mem::size_of::<u32>() as u64) as usize);
4294                cur_i_size = max_i_size;
4295            }
4296            log::info!("Growing index buffer to {} bytes", cur_i_size);
4297            self.index_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
4298                label: Some("Index Buffer (Grown)"),
4299                size: cur_i_size,
4300                usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
4301                mapped_at_creation: false,
4302            });
4303        }
4304
4305        // Forge Submission: Sync all geometry to GPU using StagingBelt with a dedicated encoder
4306        let mut staging_encoder =
4307            self.device
4308                .create_command_encoder(&wgpu::CommandEncoderDescriptor {
4309                    label: Some("Surtr Staging Encoder"),
4310                });
4311
4312        let mut has_writes = false;
4313
4314        if !self.vertices.is_empty() {
4315            let v_bytes = bytemuck::cast_slice(&self.vertices);
4316            self.staging_belt
4317                .write_buffer(
4318                    &mut staging_encoder,
4319                    &self.vertex_buffer,
4320                    0,
4321                    wgpu::BufferSize::new(v_bytes.len() as u64).unwrap(),
4322                )
4323                .copy_from_slice(v_bytes);
4324            has_writes = true;
4325        }
4326
4327        if !self.indices.is_empty() {
4328            let i_bytes = bytemuck::cast_slice(&self.indices);
4329            self.staging_belt
4330                .write_buffer(
4331                    &mut staging_encoder,
4332                    &self.index_buffer,
4333                    0,
4334                    wgpu::BufferSize::new(i_bytes.len() as u64).unwrap(),
4335                )
4336                .copy_from_slice(i_bytes);
4337            has_writes = true;
4338        }
4339
4340        if has_writes {
4341            self.staging_belt.finish();
4342            self.staging_command_buffers.push(staging_encoder.finish());
4343        }
4344
4345        // Update Time & Uniforms (Direct write is fine for small uniforms)
4346        self.current_scene.time = self.start_time.elapsed().as_secs_f32();
4347        self.queue.write_buffer(
4348            &self.scene_buffer,
4349            0,
4350            bytemuck::bytes_of(&self.current_scene),
4351        );
4352        self.queue.write_buffer(
4353            &self.theme_buffer,
4354            0,
4355            bytemuck::bytes_of(&self.current_theme),
4356        );
4357    }
4358
4359    fn end_frame(&mut self, encoder: wgpu::CommandEncoder) {
4360        Self::end_frame(self, encoder);
4361        cvkg_core::end_render_phase();
4362    }
4363}
4364
4365impl SurtrRenderer {
4366    /// Returns the current effective opacity (product of all stacked values).
4367    fn apply_opacity(&self, mut color: [f32; 4]) -> [f32; 4] {
4368        if let Some(&alpha) = self.opacity_stack.last() {
4369            color[3] *= alpha;
4370        }
4371        color
4372    }
4373
4374    /// load_svg — Parses an SVG file and tessellates its paths into GPU triangles.
4375    pub fn load_svg(&mut self, name: &str, data: &[u8]) {
4376        let opt = usvg::Options::default();
4377        let tree = usvg::Tree::from_data(data, &opt).expect("Failed to parse SVG");
4378
4379        let view_box = Rect {
4380            x: 0.0,
4381            y: 0.0,
4382            width: tree.size().width(),
4383            height: tree.size().height(),
4384        };
4385
4386        let mut vertices = Vec::new();
4387        let mut indices = Vec::new();
4388        let mut tessellator = FillTessellator::new();
4389
4390        for child in tree.root().children() {
4391            self.tessellate_node(child, &mut tessellator, &mut vertices, &mut indices);
4392        }
4393
4394        self.svg_cache.put(
4395            name.to_string(),
4396            SvgModel {
4397                vertices,
4398                indices,
4399                view_box,
4400            },
4401        );
4402    }
4403
4404    fn tessellate_node(
4405        &self,
4406        node: &usvg::Node,
4407        tessellator: &mut FillTessellator,
4408        vertices: &mut Vec<Vertex>,
4409        indices: &mut Vec<u32>,
4410    ) {
4411        if let usvg::Node::Group(ref group) = *node {
4412            for child in group.children() {
4413                self.tessellate_node(child, tessellator, vertices, indices);
4414            }
4415        } else if let usvg::Node::Path(ref path) = *node
4416            && let Some(fill) = path.fill()
4417        {
4418            let color = match fill.paint() {
4419                usvg::Paint::Color(c) => [
4420                    c.red as f32 / 255.0,
4421                    c.green as f32 / 255.0,
4422                    c.blue as f32 / 255.0,
4423                    fill.opacity().get(),
4424                ],
4425                _ => [1.0, 1.0, 1.0, 1.0],
4426            };
4427
4428            let lyon_path = usvg_to_lyon(path);
4429            let mut buffers: VertexBuffers<Vertex, u32> = VertexBuffers::new();
4430            let base_vertex_idx = vertices.len() as u32;
4431
4432            tessellator
4433                .tessellate_path(
4434                    &lyon_path,
4435                    &FillOptions::default(),
4436                    &mut BuffersBuilder::new(
4437                        &mut buffers,
4438                        SceneVertexConstructor {
4439                            color,
4440                            translation: [0.0, 0.0],
4441                            scale: [1.0, 1.0],
4442                            rotation: 0.0,
4443                        },
4444                    ),
4445                )
4446                .unwrap();
4447
4448            vertices.extend(buffers.vertices);
4449            for idx in buffers.indices {
4450                indices.push(base_vertex_idx + idx);
4451            }
4452        }
4453    }
4454
4455    /// draw_svg — Renders a pre-loaded SVG icon at the specified logical rect.
4456    pub fn draw_svg(&mut self, name: &str, rect: Rect, color: Option<[f32; 4]>, mode: u32) {
4457        let model = if let Some(m) = self.svg_cache.get(name) {
4458            m.clone()
4459        } else {
4460            return;
4461        };
4462
4463        let _scale_x = rect.width / model.view_box.width;
4464        let _scale_y = rect.height / model.view_box.height;
4465        let base_idx = self.vertices.len() as u32;
4466        let screen = [self.current_width() as f32, self.current_height() as f32];
4467        let clip_rect = self.clip_stack.last().copied().unwrap_or(cvkg_core::Rect {
4468            x: -10000.0,
4469            y: -10000.0,
4470            width: 20000.0,
4471            height: 20000.0,
4472        });
4473        let clip = [clip_rect.x, clip_rect.y, clip_rect.width, clip_rect.height];
4474        let scale = self.current_scale_factor();
4475        let snap = |v: f32| (v * scale).round() / scale;
4476
4477        for v in &model.vertices {
4478            let mut v = *v;
4479            let rel_x = (v.position[0] - model.view_box.x) / model.view_box.width;
4480            let rel_y = (v.position[1] - model.view_box.y) / model.view_box.height;
4481
4482            v.position[0] = snap(rect.x + rel_x * rect.width);
4483            v.position[1] = snap(rect.y + rel_y * rect.height);
4484            v.position[2] = self.current_z;
4485            v.logical = [v.position[0], v.position[1]];
4486            v.screen = screen;
4487            v.clip = clip;
4488            v.mode = mode;
4489
4490            if let Some(override_color) = color {
4491                v.color = self.apply_opacity(override_color);
4492            } else {
4493                v.color = self.apply_opacity(v.color);
4494            }
4495            self.vertices.push(v);
4496        }
4497
4498        for idx in &model.indices {
4499            self.indices.push(base_idx + *idx);
4500        }
4501
4502        let material = if mode == 7 {
4503            cvkg_core::DrawMaterial::Glass { blur_radius: 20.0 }
4504        } else {
4505            cvkg_core::DrawMaterial::TopUI
4506        };
4507        let tid = self.get_texture_id("__mega_atlas");
4508
4509        let last_call = self.draw_calls.last();
4510        let needs_new_call = self.draw_calls.is_empty()
4511            || self.current_texture_id != tid
4512            || last_call.unwrap().scissor_rect != self.clip_stack.last().copied()
4513            || last_call.unwrap().material != material;
4514
4515        if needs_new_call {
4516            self.current_texture_id = tid;
4517            self.draw_calls.push(DrawCall {
4518                texture_id: tid,
4519                scissor_rect: self.clip_stack.last().copied(),
4520                index_start: (self.indices.len() - model.indices.len()) as u32,
4521                index_count: 0,
4522                material,
4523            });
4524        }
4525
4526        if let Some(call) = self.draw_calls.last_mut() {
4527            call.index_count += model.indices.len() as u32;
4528        }
4529    }
4530
4531    /// forge_headless — Initializes Surtr without a window for visual regression testing.
4532    pub async fn forge_headless(width: u32, height: u32) -> Self {
4533        let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
4534            backends: wgpu::Backends::all(),
4535            flags: wgpu::InstanceFlags::default(),
4536            backend_options: wgpu::BackendOptions::default(),
4537            display: None,
4538            memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
4539        });
4540
4541        // Request adapter with robust multi-stage fallback for Bumblebee/Optimus compatibility
4542        println!("[GPU] Requesting HighPerformance adapter...");
4543        let mut adapter = instance
4544            .request_adapter(&wgpu::RequestAdapterOptions {
4545                power_preference: wgpu::PowerPreference::HighPerformance,
4546                compatible_surface: None,
4547                force_fallback_adapter: false,
4548            })
4549            .await
4550            .ok();
4551
4552        if adapter.is_none() {
4553            println!(
4554                "[GPU] HighPerformance adapter failed (possible Bumblebee/Optimus), trying LowPower..."
4555            );
4556            adapter = instance
4557                .request_adapter(&wgpu::RequestAdapterOptions {
4558                    power_preference: wgpu::PowerPreference::LowPower,
4559                    compatible_surface: None,
4560                    force_fallback_adapter: false,
4561                })
4562                .await
4563                .ok();
4564        }
4565
4566        if adapter.is_none() {
4567            println!("[GPU] Hardware adapters failed, trying Software fallback...");
4568            adapter = instance
4569                .request_adapter(&wgpu::RequestAdapterOptions {
4570                    power_preference: wgpu::PowerPreference::LowPower,
4571                    compatible_surface: None,
4572                    force_fallback_adapter: true,
4573                })
4574                .await
4575                .ok();
4576        }
4577
4578        let adapter = adapter.expect("Failed to find a suitable GPU for Surtr");
4579        let info = adapter.get_info();
4580        println!(
4581            "[GPU] Selected adapter: {} ({:?}) on backend: {:?}",
4582            info.name, info.device_type, info.backend
4583        );
4584        println!("[GPU] Driver info: {} - {}", info.driver, info.driver_info);
4585        let supports_timestamps = adapter.features().contains(wgpu::Features::TIMESTAMP_QUERY);
4586        let mut required_features = wgpu::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING
4587            | wgpu::Features::TEXTURE_BINDING_ARRAY;
4588        if supports_timestamps {
4589            required_features |= wgpu::Features::TIMESTAMP_QUERY;
4590        }
4591
4592        let (device, queue) = adapter
4593            .request_device(&wgpu::DeviceDescriptor {
4594                label: Some("Surtr Headless Forge"),
4595                required_features,
4596                required_limits: wgpu::Limits {
4597                    max_bindings_per_bind_group: 256,
4598                    max_binding_array_elements_per_shader_stage: 256,
4599                    ..wgpu::Limits::default()
4600                },
4601                memory_hints: wgpu::MemoryHints::default(),
4602                experimental_features: wgpu::ExperimentalFeatures::disabled(),
4603                trace: wgpu::Trace::Off,
4604            })
4605            .await
4606            .expect("Failed to create Surtr device");
4607
4608        let instance = Arc::new(instance);
4609        let adapter = Arc::new(adapter);
4610        
4611        device.on_uncaptured_error(Arc::new(|error| {
4612            log::error!("[GPU] Uncaptured device error (Device Lost or Panic): {:?}", error);
4613        }));
4614
4615        let device = Arc::new(device);
4616        let queue = Arc::new(queue);
4617
4618        Self::forge_internal(
4619            instance,
4620            adapter,
4621            device,
4622            queue,
4623            None,
4624            Some((width, height, wgpu::TextureFormat::Rgba8UnormSrgb)),
4625        )
4626        .await
4627    }
4628
4629    /// capture_frame — Read back the rendered frame as a byte buffer (RGBA8).
4630    pub async fn capture_frame(&self) -> Result<Vec<u8>, String> {
4631        let ctx = self
4632            .headless_context
4633            .as_ref()
4634            .ok_or("Headless context required for capture")?;
4635        let u32_size = std::mem::size_of::<u32>() as u32;
4636        let width = ctx.width;
4637        let height = ctx.height;
4638        let bytes_per_row = width * u32_size;
4639        let padding = (256 - (bytes_per_row % 256)) % 256;
4640        let padded_bytes_per_row = bytes_per_row + padding;
4641
4642        let output_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
4643            label: Some("Capture Buffer"),
4644            size: (padded_bytes_per_row as u64 * height as u64),
4645            usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
4646            mapped_at_creation: false,
4647        });
4648
4649        let mut encoder = self
4650            .device
4651            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
4652                label: Some("Capture Encoder"),
4653            });
4654
4655        encoder.copy_texture_to_buffer(
4656            wgpu::TexelCopyTextureInfo {
4657                texture: &ctx.output_texture,
4658                mip_level: 0,
4659                origin: wgpu::Origin3d::ZERO,
4660                aspect: wgpu::TextureAspect::All,
4661            },
4662            wgpu::TexelCopyBufferInfo {
4663                buffer: &output_buffer,
4664                layout: wgpu::TexelCopyBufferLayout {
4665                    offset: 0,
4666                    bytes_per_row: Some(padded_bytes_per_row),
4667                    rows_per_image: Some(height),
4668                },
4669            },
4670            wgpu::Extent3d {
4671                width,
4672                height,
4673                depth_or_array_layers: 1,
4674            },
4675        );
4676
4677        self.queue.submit(Some(encoder.finish()));
4678
4679        let buffer_slice = output_buffer.slice(..);
4680        let (sender, receiver) = futures::channel::oneshot::channel();
4681        buffer_slice.map_async(wgpu::MapMode::Read, move |v| {
4682            let _ = sender.send(v);
4683        });
4684
4685        let _ = self.device.poll(wgpu::PollType::Wait {
4686            submission_index: None,
4687            timeout: None,
4688        });
4689
4690        if let Ok(Ok(_)) = receiver.await {
4691            let data = buffer_slice.get_mapped_range();
4692            let mut result = Vec::with_capacity((width * height * 4) as usize);
4693
4694            for y in 0..height {
4695                let start = (y * padded_bytes_per_row) as usize;
4696                let end = start + bytes_per_row as usize;
4697                result.extend_from_slice(&data[start..end]);
4698            }
4699
4700            drop(data);
4701            output_buffer.unmap();
4702            Ok(result)
4703        } else {
4704            Err("Failed to capture frame".to_string())
4705        }
4706    }
4707
4708    fn current_width(&self) -> u32 {
4709        if let Some(id) = self.current_window {
4710            self.surfaces.get(&id).unwrap().config.width
4711        } else {
4712            self.headless_context.as_ref().unwrap().width
4713        }
4714    }
4715
4716    fn current_height(&self) -> u32 {
4717        if let Some(id) = self.current_window {
4718            self.surfaces.get(&id).unwrap().config.height
4719        } else {
4720            self.headless_context.as_ref().unwrap().height
4721        }
4722    }
4723
4724    fn current_scale_factor(&self) -> f32 {
4725        if let Some(id) = self.current_window {
4726            self.surfaces.get(&id).unwrap().scale_factor
4727        } else {
4728            self.headless_context.as_ref().unwrap().scale_factor
4729        }
4730    }
4731}