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
25//! # Surtr Render Pipeline
26//!
27//! The "Fiery Giant" of the CVKG architecture. This is the authoritative GPU renderer
28//! powered by `wgpu`. It manages the heat of the GPU to forge high-fidelity
29//! "Berserker" aesthetics.
30//!
31//! - **The Flaming Sword**: Command submission and synchronization.
32//! - **Muspelheim Passes**: Multi-pass Gaussian blur and bloom for Bifrost/Gungnir.
33
34/// ShelfPacker — A simple shelf-based atlas packer for the Mega-Atlas.
35#[derive(Debug, Clone)]
36struct ShelfPacker {
37    width: u32,
38    height: u32,
39    shelf_y: u32,
40    shelf_height: u32,
41    current_x: u32,
42}
43
44impl ShelfPacker {
45    fn new(width: u32, height: u32) -> Self {
46        Self { width, height, shelf_y: 0, shelf_height: 0, current_x: 0 }
47    }
48
49    fn pack(&mut self, w: u32, h: u32) -> Option<(u32, u32)> {
50        // Bounds check for the item itself
51        if w > self.width || h > self.height {
52            return None;
53        }
54
55        // Shelf packing algorithm: simple and fast for real-time UI.
56        if self.current_x + w > self.width {
57            // New shelf
58            self.shelf_y += self.shelf_height;
59            self.current_x = 0;
60            self.shelf_height = 0;
61        }
62
63        if self.shelf_y + h > self.height {
64            return None; // Out of space
65        }
66
67        let pos = (self.current_x, self.shelf_y);
68        self.current_x += w;
69        if h > self.shelf_height {
70            self.shelf_height = h;
71        }
72        Some(pos)
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn test_shelf_packer_basic() {
82        let mut packer = ShelfPacker::new(100, 100);
83        
84        // Pack first item
85        assert_eq!(packer.pack(10, 10), Some((0, 0)));
86        assert_eq!(packer.current_x, 10);
87        assert_eq!(packer.shelf_height, 10);
88        
89        // Pack second item on same shelf
90        assert_eq!(packer.pack(20, 15), Some((10, 0)));
91        assert_eq!(packer.current_x, 30);
92        assert_eq!(packer.shelf_height, 15);
93    }
94
95    #[test]
96    fn test_shelf_packer_wrap() {
97        let mut packer = ShelfPacker::new(100, 100);
98        packer.pack(60, 10);
99        
100        // This should trigger a new shelf
101        assert_eq!(packer.pack(50, 20), Some((0, 10)));
102        assert_eq!(packer.current_x, 50);
103        assert_eq!(packer.shelf_y, 10);
104        assert_eq!(packer.shelf_height, 20);
105    }
106
107    #[test]
108    fn test_shelf_packer_full() {
109        let mut packer = ShelfPacker::new(10, 10);
110        assert_eq!(packer.pack(11, 5), None);
111        assert_eq!(packer.pack(5, 11), None);
112    }
113}
114
115use cvkg_core::{Mesh, Rect, Renderer, LAYOUT_DIRTY};
116use std::sync::Arc;
117use std::sync::atomic::Ordering;
118include!(concat!(env!("OUT_DIR"), "/shader_spirv.rs"));
119
120
121/// SvgModel — A collection of tessellated triangles representing a vector icon.
122#[derive(Clone, Debug)]
123pub struct SvgModel {
124    pub vertices: Vec<Vertex>,
125    pub indices: Vec<u32>,
126    pub view_box: Rect,
127}
128
129// ShieldWall — re-export AccessKit types so callers can build tree updates
130// without depending on accesskit directly.
131pub use accesskit::{
132    ActionHandler, ActionRequest, ActivationHandler, DeactivationHandler, Node, NodeId, Role, Tree,
133    TreeId, TreeUpdate,
134};
135pub use accesskit_winit::Adapter as ShieldWallAdapter;
136
137// Re-export ColorTheme and SceneUniforms for cvkg-render-gpu users
138pub use cvkg_core::{
139    ColorTheme, SceneUniforms,
140};
141
142use lyon::tessellation::{
143    FillOptions, FillTessellator, FillVertex, FillVertexConstructor, VertexBuffers, BuffersBuilder
144};
145
146#[repr(C)]
147#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
148pub struct Vertex {
149    pub position: [f32; 3],
150    pub normal: [f32; 3],
151    pub uv: [f32; 2],
152    pub color: [f32; 4],
153    pub mode: u32,
154    pub radius: f32,
155    pub slice: [f32; 4],
156    pub logical: [f32; 2],
157    pub size: [f32; 2],
158    pub screen: [f32; 2],
159    pub clip: [f32; 4], // [x, y, width, height]
160    pub translation: [f32; 2],
161    pub scale: [f32; 2],
162    pub rotation: f32,
163    pub _pad: f32,
164}
165
166
167
168/// Represents a single batched GPU draw call.
169/// Batches are broken whenever the active texture or primitive mode changes.
170#[derive(Debug, Clone)]
171struct DrawCall {
172    pub texture_id: Option<u32>,
173    pub scissor_rect: Option<Rect>,
174    pub index_start: u32,
175    pub index_count: u32,
176    pub is_glass: bool,
177    pub is_ui: bool,
178}
179
180#[derive(Debug, Clone, Copy)]
181struct ShadowState {
182    pub radius: f32,
183    pub color: [f32; 4],
184    pub _offset: [f32; 2],
185}
186
187impl Vertex {
188    const ATTRIBUTES: [wgpu::VertexAttribute; 14] = wgpu::vertex_attr_array![
189        0 => Float32x3, // position
190        1 => Float32x3, // normal
191        2 => Float32x2, // uv
192        3 => Float32x4, // color
193        4 => Uint32,    // mode
194        5 => Float32,   // radius
195        6 => Float32x4, // slice
196        7 => Float32x2, // logical
197        8 => Float32x2, // size
198        9 => Float32x2, // screen
199        10 => Float32x4, // clip
200        11 => Float32x2, // translation
201        12 => Float32x2, // scale
202        13 => Float32    // rotation
203    ];
204
205
206    fn desc() -> wgpu::VertexBufferLayout<'static> {
207        wgpu::VertexBufferLayout {
208            array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
209            step_mode: wgpu::VertexStepMode::Vertex,
210            attributes: &Self::ATTRIBUTES,
211        }
212    }
213}
214
215/// SurtrRenderer implements the high-performance GPU backend.
216pub struct SurtrRenderer {
217    instance: Arc<wgpu::Instance>,
218    adapter: Arc<wgpu::Adapter>,
219    device: Arc<wgpu::Device>,
220    queue: Arc<wgpu::Queue>,
221    
222    // Multi-Window Surface Management
223    surfaces: std::collections::HashMap<winit::window::WindowId, SurfaceContext>,
224    current_window: Option<winit::window::WindowId>,
225    pub headless_context: Option<HeadlessContext>,
226
227    // Mega-Atlas (Shared across all windows)
228    text_engine: runic_text::RunicTextEngine,
229    mega_atlas_tex: wgpu::Texture,
230    #[allow(dead_code)]
231    mega_atlas_view: wgpu::TextureView,
232    _mega_atlas_sampler: wgpu::Sampler,
233    mega_atlas_bind_group: wgpu::BindGroup,
234    text_cache: std::collections::HashMap<runic_text::CacheKey, (Rect, f32, f32)>,
235    atlas_packer: ShelfPacker,
236    image_uv_registry: std::collections::HashMap<String, Rect>,
237    texture_registry: std::collections::HashMap<String, u32>,
238    svg_cache: std::collections::HashMap<String, SvgModel>,
239
240    // Niflheim Resources (Shared)
241    dummy_texture_bind_group: wgpu::BindGroup,
242    dummy_env_bind_group: wgpu::BindGroup,
243    texture_bind_group_layout: wgpu::BindGroupLayout,
244    texture_bind_groups: Vec<wgpu::BindGroup>,
245    shared_elements: std::collections::HashMap<String, cvkg_core::Rect>,
246
247    // The Forge's Anvil (GPU Buffers)
248    vertex_buffer: wgpu::Buffer,
249    index_buffer: wgpu::Buffer,
250    vertices: Vec<Vertex>,
251    indices: Vec<u32>,
252    draw_calls: Vec<DrawCall>,
253    current_texture_id: Option<u32>,
254
255    // Opacity & Clip Stacks
256    opacity_stack: Vec<f32>,
257    clip_stack: Vec<Rect>,
258    slice_stack: Vec<(f32, f32)>,
259    shadow_stack: Vec<ShadowState>,
260
261    // The Forge's Heart (Shared Berserker State)
262    theme_buffer: wgpu::Buffer,
263    scene_buffer: wgpu::Buffer,
264    berserker_bind_group: wgpu::BindGroup,
265    #[allow(dead_code)]
266    berserker_bind_group_layout: wgpu::BindGroupLayout,
267    start_time: std::time::Instant,
268    current_theme: ColorTheme,
269    current_scene: SceneUniforms,
270    current_z: f32,
271
272    // Muspelheim Pipelines (Shared)
273    pipeline: wgpu::RenderPipeline,
274    background_pipeline: wgpu::RenderPipeline,
275    bloom_extract_pipeline: wgpu::RenderPipeline,
276    blur_h_pipeline: wgpu::RenderPipeline,
277    blur_v_pipeline: wgpu::RenderPipeline,
278    composite_pipeline: wgpu::RenderPipeline,
279    env_bind_group_layout: wgpu::BindGroupLayout,
280    
281    // Telemetry
282    pub telemetry: cvkg_core::TelemetryData,
283
284    /// Configuration for render-loop frame timing and degradation strategies.
285    pub frame_budget: cvkg_core::FrameBudget,
286    /// Instant at the start of the last redraw, used for measuring frame timings.
287    pub last_redraw_start: std::time::Instant,
288    /// Instant at the start of the last frame, used for frame_time_ms calculation.
289    pub last_frame_start: std::time::Instant,
290
291    // VRAM Tracking (Bytes)
292    vram_buffers_bytes: u64,
293    vram_textures_bytes: u64,
294
295    // Debugging
296    _debug_layout: bool,
297
298    // Transform Stack
299    transform_stack: Vec<([f32; 2], [f32; 2], f32)>,
300    /// Whether a redraw has been requested for the next frame.
301    pub redraw_requested: bool,
302}
303
304
305struct SurfaceContext {
306    surface: wgpu::Surface<'static>,
307    config: wgpu::SurfaceConfiguration,
308    scene_texture: wgpu::TextureView,
309    scene_bind_group: wgpu::BindGroup,
310    scene_texture_bind_group: wgpu::BindGroup,
311    depth_texture_view: wgpu::TextureView,
312    blur_texture_a: wgpu::TextureView,
313    blur_texture_b: wgpu::TextureView,
314    blur_bind_group_a: wgpu::BindGroup,
315    blur_bind_group_b: wgpu::BindGroup,
316    blur_env_bind_group_a: wgpu::BindGroup,
317    scale_factor: f32,
318    sampler: wgpu::Sampler,
319}
320
321/// HeadlessContext — A rendering target for surface-less execution.
322pub struct HeadlessContext {
323    pub scene_texture: wgpu::TextureView,
324    pub scene_bind_group: wgpu::BindGroup,
325    pub scene_texture_bind_group: wgpu::BindGroup,
326    pub depth_texture_view: wgpu::TextureView,
327    pub blur_texture_a: wgpu::TextureView,
328    pub blur_texture_b: wgpu::TextureView,
329    pub blur_bind_group_a: wgpu::BindGroup,
330    pub blur_bind_group_b: wgpu::BindGroup,
331    pub blur_env_bind_group_a: wgpu::BindGroup,
332    pub scale_factor: f32,
333    pub sampler: wgpu::Sampler,
334    pub width: u32,
335    pub height: u32,
336    pub output_texture: wgpu::Texture,
337    pub output_view: wgpu::TextureView,
338}
339
340const MAX_VERTICES: usize = 100_000;
341const MAX_INDICES: usize = 150_000;
342
343impl SurtrRenderer {
344    /// forge — Initializes the Surtr GPU renderer from a winit window.
345    ///
346    /// This method performs the following:
347    /// 1. Negotiates a wgpu surface and adapter.
348    /// 2. Forges the Muspelheim multi-pass pipeline layouts.
349    /// 3. Initializes the Berserker state buffers and texture registries.
350    pub async fn forge(window: Arc<winit::window::Window>) -> Self {
351        let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
352            backends: wgpu::Backends::all(),
353            flags: wgpu::InstanceFlags::default(),
354            backend_options: wgpu::BackendOptions::default(),
355            display: None,
356            memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
357        });
358
359        let surface = instance
360            .create_surface(window.clone())
361            .expect("Failed to create surface");
362
363        let adapter = instance
364            .request_adapter(&wgpu::RequestAdapterOptions {
365                power_preference: wgpu::PowerPreference::HighPerformance,
366                compatible_surface: Some(&surface),
367                force_fallback_adapter: false,
368            })
369            .await
370            .expect("Failed to find a suitable GPU for Surtr");
371
372        let (device, queue) = adapter
373            .request_device(&wgpu::DeviceDescriptor {
374                label: Some("Surtr Forge"),
375                required_features: wgpu::Features::empty(),
376                required_limits: wgpu::Limits::default(),
377                memory_hints: wgpu::MemoryHints::default(),
378                experimental_features: wgpu::ExperimentalFeatures::disabled(),
379                trace: wgpu::Trace::Off,
380            })
381            .await
382            .expect("Failed to create Surtr device");
383
384        let instance = Arc::new(instance);
385        let adapter = Arc::new(adapter);
386        let device = Arc::new(device);
387        let queue = Arc::new(queue);
388
389        let size = window.inner_size();
390        let surface_caps = surface.get_capabilities(&adapter);
391        let surface_format = surface_caps
392            .formats
393            .iter()
394            .find(|f| f.is_srgb())
395            .copied()
396            .unwrap_or(surface_caps.formats[0]);
397
398        let config = wgpu::SurfaceConfiguration {
399            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
400            format: surface_format,
401            width: size.width,
402            height: size.height,
403            present_mode: wgpu::PresentMode::Fifo,
404            alpha_mode: surface_caps.alpha_modes[0],
405            view_formats: vec![],
406            desired_maximum_frame_latency: 2,
407        };
408        surface.configure(&device, &config);
409        
410        Self::forge_internal(
411            instance,
412            adapter,
413            device,
414            queue,
415            Some((window, surface, config)),
416            None
417        ).await
418    }
419
420    async fn forge_internal(
421        instance: Arc<wgpu::Instance>,
422        adapter: Arc<wgpu::Adapter>,
423        device: Arc<wgpu::Device>,
424        queue: Arc<wgpu::Queue>,
425        surface_info: Option<(Arc<winit::window::Window>, wgpu::Surface<'static>, wgpu::SurfaceConfiguration)>,
426        headless_info: Option<(u32, u32, wgpu::TextureFormat)>,
427    ) -> Self {
428        let format = if let Some((_, _, ref config)) = surface_info {
429            config.format
430        } else if let Some((_, _, f)) = headless_info {
431            f
432        } else {
433            wgpu::TextureFormat::Rgba8UnormSrgb
434        };
435
436        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
437            label: Some("Muspelheim Main Shader"),
438            source: wgpu::ShaderSource::SpirV(std::borrow::Cow::Borrowed(SPIRV)),
439        });
440
441        // Niflheim Bind Group Layout (for textures/samplers)
442        let texture_bind_group_layout =
443            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
444                entries: &[
445                    wgpu::BindGroupLayoutEntry {
446                        binding: 0,
447                        visibility: wgpu::ShaderStages::FRAGMENT,
448                        ty: wgpu::BindingType::Texture {
449                            multisampled: false,
450                            view_dimension: wgpu::TextureViewDimension::D2,
451                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
452                        },
453                        count: None,
454                    },
455                    wgpu::BindGroupLayoutEntry {
456                        binding: 1,
457                        visibility: wgpu::ShaderStages::FRAGMENT,
458                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
459                        count: None,
460                    },
461                ],
462                label: Some("Niflheim Texture Bind Group Layout"),
463            });
464
465        // Environment Bind Group Layout (for blurred background / Bifrost)
466        // Environment Bind Group Layout (for blurred background / Bifrost)
467        let env_bind_group_layout =
468            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
469                entries: &[
470                    wgpu::BindGroupLayoutEntry {
471                        binding: 0,
472                        visibility: wgpu::ShaderStages::FRAGMENT,
473                        ty: wgpu::BindingType::Texture {
474                            multisampled: false,
475                            view_dimension: wgpu::TextureViewDimension::D2,
476                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
477                        },
478                        count: None,
479                    },
480                    wgpu::BindGroupLayoutEntry {
481                        binding: 1,
482                        visibility: wgpu::ShaderStages::FRAGMENT,
483                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
484                        count: None,
485                    },
486                ],
487                label: Some("Surtr Environment Bind Group Layout"),
488            });
489
490        let berserker_bind_group_layout =
491            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
492                entries: &[
493                    wgpu::BindGroupLayoutEntry {
494                        binding: 0,
495                        visibility: wgpu::ShaderStages::FRAGMENT,
496                        ty: wgpu::BindingType::Buffer {
497                            ty: wgpu::BufferBindingType::Uniform,
498                            has_dynamic_offset: false,
499                            min_binding_size: None,
500                        },
501                        count: None,
502                    },
503                    wgpu::BindGroupLayoutEntry {
504                        binding: 1,
505                        visibility: wgpu::ShaderStages::FRAGMENT | wgpu::ShaderStages::VERTEX,
506                        ty: wgpu::BindingType::Buffer {
507                            ty: wgpu::BufferBindingType::Uniform,
508                            has_dynamic_offset: false,
509                            min_binding_size: None,
510                        },
511                        count: None,
512                    },
513                ],
514                label: Some("Surtr Berserker Bind Group Layout"),
515            });
516
517        // Pipeline setup
518        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
519            label: Some("Surtr Main Pipeline Layout"),
520            bind_group_layouts: &[
521                Some(&texture_bind_group_layout),
522                Some(&env_bind_group_layout),
523                Some(&berserker_bind_group_layout),
524            ],
525            immediate_size: 0,
526        });
527
528        // Specialized layout for post-processing (Bloom Extract, Blur) which only need Group 0 + Globals
529        let post_process_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
530            label: Some("Muspelheim Post Process Layout"),
531            bind_group_layouts: &[
532                Some(&texture_bind_group_layout),
533                Some(&env_bind_group_layout),
534                Some(&berserker_bind_group_layout),
535            ],
536            immediate_size: 0,
537        });
538
539        // Specialized layout for composite (Blur + Scene)
540        let composite_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
541            label: Some("Muspelheim Composite Layout"),
542            bind_group_layouts: &[
543                Some(&texture_bind_group_layout),
544                Some(&env_bind_group_layout),
545                Some(&berserker_bind_group_layout),
546            ],
547            immediate_size: 0,
548        });
549
550        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
551            label: Some("Surtr Main Pipeline"),
552            layout: Some(&pipeline_layout),
553            vertex: wgpu::VertexState {
554                module: &shader,
555                entry_point: Some("vs_main"),
556                buffers: &[Vertex::desc()],
557                compilation_options: wgpu::PipelineCompilationOptions::default(),
558            },
559            fragment: Some(wgpu::FragmentState {
560                module: &shader,
561                entry_point: Some("fs_main"),
562                targets: &[Some(wgpu::ColorTargetState {
563                    format: format,
564                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
565                    write_mask: wgpu::ColorWrites::ALL,
566                })],
567                compilation_options: wgpu::PipelineCompilationOptions::default(),
568            }),
569            primitive: wgpu::PrimitiveState::default(),
570            depth_stencil: Some(wgpu::DepthStencilState {
571                format: wgpu::TextureFormat::Depth32Float,
572                depth_write_enabled: Some(true),
573                depth_compare: Some(wgpu::CompareFunction::LessEqual),
574                stencil: wgpu::StencilState::default(),
575                bias: wgpu::DepthBiasState::default(),
576            }),
577            multisample: wgpu::MultisampleState::default(),
578            multiview_mask: None,
579            cache: None,
580        });
581
582        let background_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
583            label: Some("Surtr Background Pipeline"),
584            layout: Some(&pipeline_layout),
585            vertex: wgpu::VertexState {
586                module: &shader,
587                entry_point: Some("vs_fullscreen"),
588                buffers: &[],
589                compilation_options: wgpu::PipelineCompilationOptions::default(),
590            },
591            fragment: Some(wgpu::FragmentState {
592                module: &shader,
593                entry_point: Some("fs_background"),
594                targets: &[Some(wgpu::ColorTargetState {
595                    format: format,
596                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
597                    write_mask: wgpu::ColorWrites::ALL,
598                })],
599                compilation_options: wgpu::PipelineCompilationOptions::default(),
600            }),
601            primitive: wgpu::PrimitiveState::default(),
602            depth_stencil: Some(wgpu::DepthStencilState {
603                format: wgpu::TextureFormat::Depth32Float,
604                depth_write_enabled: Some(true),
605                depth_compare: Some(wgpu::CompareFunction::Always),
606                stencil: wgpu::StencilState::default(),
607                bias: wgpu::DepthBiasState::default(),
608            }),
609            multisample: wgpu::MultisampleState::default(),
610            multiview_mask: None,
611            cache: None,
612        });
613
614        // Muspelheim Bloom Extract Pipeline
615        let bloom_extract_pipeline =
616            device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
617                label: Some("Muspelheim Bloom Extract"),
618                layout: Some(&post_process_layout),
619                vertex: wgpu::VertexState {
620                    module: &shader,
621                    entry_point: Some("vs_fullscreen"),
622                    buffers: &[],
623                    compilation_options: wgpu::PipelineCompilationOptions::default(),
624                },
625                fragment: Some(wgpu::FragmentState {
626                    module: &shader,
627                    entry_point: Some("fs_bloom_extract"),
628                    targets: &[Some(wgpu::ColorTargetState {
629                        format: format,
630                        blend: None,
631                        write_mask: wgpu::ColorWrites::ALL,
632                    })],
633                    compilation_options: wgpu::PipelineCompilationOptions::default(),
634                }),
635                primitive: wgpu::PrimitiveState::default(),
636                depth_stencil: None,
637                multisample: wgpu::MultisampleState::default(),
638                multiview_mask: None,
639                cache: None,
640            });
641
642        // Muspelheim Blur Pipelines (H and V)
643        let blur_h_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
644            label: Some("Muspelheim Horizontal Blur"),
645            layout: Some(&post_process_layout),
646            vertex: wgpu::VertexState {
647                module: &shader,
648                entry_point: Some("vs_fullscreen"),
649                buffers: &[],
650                compilation_options: wgpu::PipelineCompilationOptions::default(),
651            },
652            fragment: Some(wgpu::FragmentState {
653                module: &shader,
654                entry_point: Some("fs_blur_h"),
655                targets: &[Some(wgpu::ColorTargetState {
656                    format: format,
657                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
658                    write_mask: wgpu::ColorWrites::ALL,
659                })],
660                compilation_options: wgpu::PipelineCompilationOptions::default(),
661            }),
662            primitive: wgpu::PrimitiveState::default(),
663            depth_stencil: None,
664            multisample: wgpu::MultisampleState::default(),
665            multiview_mask: None,
666            cache: None,
667        });
668
669        let blur_v_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
670            label: Some("Muspelheim Vertical Blur"),
671            layout: Some(&post_process_layout),
672            vertex: wgpu::VertexState {
673                module: &shader,
674                entry_point: Some("vs_fullscreen"),
675                buffers: &[],
676                compilation_options: wgpu::PipelineCompilationOptions::default(),
677            },
678            fragment: Some(wgpu::FragmentState {
679                module: &shader,
680                entry_point: Some("fs_blur_v"),
681                targets: &[Some(wgpu::ColorTargetState {
682                    format: format,
683                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
684                    write_mask: wgpu::ColorWrites::ALL,
685                })],
686                compilation_options: wgpu::PipelineCompilationOptions::default(),
687            }),
688            primitive: wgpu::PrimitiveState::default(),
689            depth_stencil: None,
690            multisample: wgpu::MultisampleState::default(),
691            multiview_mask: None,
692            cache: None,
693        });
694
695        // Muspelheim Composite Pipeline (additive blend onto screen)
696        let composite_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
697            label: Some("Muspelheim Composite"),
698            layout: Some(&composite_layout),
699            vertex: wgpu::VertexState {
700                module: &shader,
701                entry_point: Some("vs_fullscreen"),
702                buffers: &[],
703                compilation_options: wgpu::PipelineCompilationOptions::default(),
704            },
705            fragment: Some(wgpu::FragmentState {
706                module: &shader,
707                entry_point: Some("fs_composite"),
708                targets: &[Some(wgpu::ColorTargetState {
709                    format: format,
710                    // Additive blend: src + dst — glow lights up the scene
711                    blend: Some(wgpu::BlendState {
712                        color: wgpu::BlendComponent {
713                            src_factor: wgpu::BlendFactor::One,
714                            dst_factor: wgpu::BlendFactor::One,
715                            operation: wgpu::BlendOperation::Add,
716                        },
717                        alpha: wgpu::BlendComponent {
718                            src_factor: wgpu::BlendFactor::One,
719                            dst_factor: wgpu::BlendFactor::One,
720                            operation: wgpu::BlendOperation::Add,
721                        },
722                    }),
723                    write_mask: wgpu::ColorWrites::ALL,
724                })],
725                compilation_options: wgpu::PipelineCompilationOptions::default(),
726            }),
727            primitive: wgpu::PrimitiveState::default(),
728            depth_stencil: None,
729            multisample: wgpu::MultisampleState::default(),
730            multiview_mask: None,
731            cache: None,
732        });
733
734        // Forge the Mega-Atlas (4096x4096 RGBA for production batching)
735        let mega_atlas_tex = device.create_texture(&wgpu::TextureDescriptor {
736            label: Some("Surtr Mega-Atlas"),
737            size: wgpu::Extent3d {
738                width: 4096,
739                height: 4096,
740                depth_or_array_layers: 1,
741            },
742            mip_level_count: 1,
743            sample_count: 1,
744            dimension: wgpu::TextureDimension::D2,
745            format: wgpu::TextureFormat::Rgba8UnormSrgb,
746            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
747            view_formats: &[],
748        });
749        let mega_atlas_view_obj = mega_atlas_tex.create_view(&wgpu::TextureViewDescriptor::default());
750        let text_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
751            address_mode_u: wgpu::AddressMode::ClampToEdge,
752            address_mode_v: wgpu::AddressMode::ClampToEdge,
753            mag_filter: wgpu::FilterMode::Linear, // Use linear for images
754            min_filter: wgpu::FilterMode::Linear,
755            ..Default::default()
756        });
757
758        let mega_atlas_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
759            layout: &texture_bind_group_layout,
760            entries: &[
761                wgpu::BindGroupEntry {
762                    binding: 0,
763                    resource: wgpu::BindingResource::TextureView(&mega_atlas_view_obj),
764                },
765                wgpu::BindGroupEntry {
766                    binding: 1,
767                    resource: wgpu::BindingResource::Sampler(&text_sampler),
768                },
769            ],
770            label: Some("Mega-Atlas Bind Group"),
771        });
772
773        // Clear the mega atlas to transparency initially
774        queue.write_texture(
775            wgpu::TexelCopyTextureInfo {
776                texture: &mega_atlas_tex,
777                mip_level: 0,
778                origin: wgpu::Origin3d::ZERO,
779                aspect: wgpu::TextureAspect::All,
780            },
781            &vec![0u8; 4096 * 4096 * 4],
782            wgpu::TexelCopyBufferLayout {
783                offset: 0,
784                bytes_per_row: Some(4096 * 4),
785                rows_per_image: Some(4096),
786            },
787            wgpu::Extent3d {
788                width: 4096,
789                height: 4096,
790                depth_or_array_layers: 1,
791            },
792        );
793
794        // Forge the Niflheim Dummy Texture (1x1 White)
795        let dummy_size = wgpu::Extent3d {
796            width: 1,
797            height: 1,
798            depth_or_array_layers: 1,
799        };
800        let dummy_texture = device.create_texture(&wgpu::TextureDescriptor {
801            label: Some("Niflheim Dummy Texture"),
802            size: dummy_size,
803            mip_level_count: 1,
804            sample_count: 1,
805            dimension: wgpu::TextureDimension::D2,
806            format: wgpu::TextureFormat::Rgba8UnormSrgb,
807            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
808            view_formats: &[],
809        });
810        queue.write_texture(
811            wgpu::TexelCopyTextureInfo {
812                texture: &dummy_texture,
813                mip_level: 0,
814                origin: wgpu::Origin3d::ZERO,
815                aspect: wgpu::TextureAspect::All,
816            },
817            &[0, 0, 0, 255],
818            wgpu::TexelCopyBufferLayout {
819                offset: 0,
820                bytes_per_row: Some(4),
821                rows_per_image: Some(1),
822            },
823            dummy_size,
824        );
825
826        let dummy_view = dummy_texture.create_view(&wgpu::TextureViewDescriptor::default());
827        let dummy_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
828            address_mode_u: wgpu::AddressMode::ClampToEdge,
829            address_mode_v: wgpu::AddressMode::ClampToEdge,
830            address_mode_w: wgpu::AddressMode::ClampToEdge,
831            mag_filter: wgpu::FilterMode::Linear,
832            min_filter: wgpu::FilterMode::Nearest,
833            mipmap_filter: wgpu::MipmapFilterMode::Nearest,
834            ..Default::default()
835        });
836
837        let dummy_texture_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
838            layout: &texture_bind_group_layout,
839            entries: &[
840                wgpu::BindGroupEntry {
841                    binding: 0,
842                    resource: wgpu::BindingResource::TextureView(&dummy_view),
843                },
844                wgpu::BindGroupEntry {
845                    binding: 1,
846                    resource: wgpu::BindingResource::Sampler(&dummy_sampler),
847                },
848            ],
849            label: Some("Dummy Texture Bind Group"),
850        });
851
852        let dummy_env_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
853            layout: &env_bind_group_layout,
854            entries: &[
855                wgpu::BindGroupEntry {
856                    binding: 0,
857                    resource: wgpu::BindingResource::TextureView(&dummy_view),
858                },
859                wgpu::BindGroupEntry {
860                    binding: 1,
861                    resource: wgpu::BindingResource::Sampler(&dummy_sampler),
862                },
863            ],
864            label: Some("Dummy Env Bind Group"),
865        });
866
867        let mut texture_registry = std::collections::HashMap::new();
868        let mut texture_bind_groups = Vec::new();
869
870        texture_registry.insert("__mega_atlas".to_string(), 0);
871        texture_bind_groups.push(mega_atlas_bind_group.clone());
872
873        // Forge the Anvil (Buffers)
874        let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
875            label: Some("Surtr Vertex Anvil"),
876            size: (MAX_VERTICES * std::mem::size_of::<Vertex>()) as u64,
877            usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
878            mapped_at_creation: false,
879        });
880
881        let index_buffer = device.create_buffer(&wgpu::BufferDescriptor {
882            label: Some("Surtr Index Anvil"),
883            size: (MAX_INDICES * std::mem::size_of::<u32>()) as u64,
884            usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
885            mapped_at_creation: false,
886        });
887
888        // Forge the Heart (Berserker Uniforms)
889        let current_theme = ColorTheme::default();
890        use wgpu::util::DeviceExt;
891        let theme_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
892            label: Some("Surtr Theme Buffer"),
893            contents: bytemuck::bytes_of(&current_theme),
894            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
895        });
896
897        let (width, height, scale_factor) = if let Some((ref window, _, ref config)) = surface_info {
898            (config.width, config.height, window.scale_factor() as f32)
899        } else if let Some((w, h, _)) = headless_info {
900            (w, h, 1.0)
901        } else {
902            (1280, 720, 1.0)
903        };
904
905        let mut current_scene = SceneUniforms::new(
906            width as f32 / scale_factor,
907            height as f32 / scale_factor,
908        );
909        current_scene.scale_factor = scale_factor;
910        let scene_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
911            label: Some("Surtr Scene Buffer"),
912            contents: bytemuck::bytes_of(&current_scene),
913            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
914        });
915
916        let berserker_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
917            layout: &berserker_bind_group_layout,
918            entries: &[
919                wgpu::BindGroupEntry {
920                    binding: 0,
921                    resource: theme_buffer.as_entire_binding(),
922                },
923                wgpu::BindGroupEntry {
924                    binding: 1,
925                    resource: scene_buffer.as_entire_binding(),
926                },
927            ],
928            label: Some("Surtr Berserker Bind Group"),
929        });
930
931        let mut surfaces = std::collections::HashMap::new();
932        let mut current_window = None;
933        let mut headless_context = None;
934
935        if let Some((window, surface, config)) = surface_info {
936            let window_id = window.id();
937            let ctx = Self::create_surface_context(
938                &device,
939                surface,
940                config,
941                &env_bind_group_layout,
942                &texture_bind_group_layout,
943                scale_factor,
944            );
945            surfaces.insert(window_id, ctx);
946            current_window = Some(window_id);
947        } else if let Some((w, h, f)) = headless_info {
948            headless_context = Some(Self::create_headless_context(
949                &device,
950                w,
951                h,
952                f,
953                &env_bind_group_layout,
954                &texture_bind_group_layout,
955            ));
956        }
957
958        Self {
959            instance,
960            adapter,
961            device,
962            queue,
963            surfaces,
964            current_window,
965            headless_context,
966            pipeline,
967            bloom_extract_pipeline,
968            blur_h_pipeline,
969            blur_v_pipeline,
970            composite_pipeline,
971            env_bind_group_layout,
972            text_engine: runic_text::RunicTextEngine::default(),
973            mega_atlas_tex,
974            mega_atlas_view: mega_atlas_view_obj,
975            _mega_atlas_sampler: text_sampler,
976            mega_atlas_bind_group,
977            text_cache: std::collections::HashMap::new(),
978            atlas_packer: ShelfPacker::new(4096, 4096),
979            image_uv_registry: std::collections::HashMap::new(),
980            svg_cache: std::collections::HashMap::new(),
981            dummy_texture_bind_group,
982            dummy_env_bind_group,
983            texture_bind_group_layout,
984            texture_bind_groups,
985            texture_registry,
986            shared_elements: std::collections::HashMap::new(),
987            vertex_buffer,
988            index_buffer,
989            vertices: Vec::with_capacity(MAX_VERTICES),
990            indices: Vec::with_capacity(MAX_INDICES),
991            draw_calls: Vec::new(),
992            current_texture_id: None,
993            opacity_stack: vec![1.0],
994            clip_stack: Vec::new(),
995            slice_stack: Vec::new(),
996            shadow_stack: Vec::new(),
997            theme_buffer,
998            scene_buffer,
999            berserker_bind_group,
1000            berserker_bind_group_layout,
1001            start_time: std::time::Instant::now(),
1002            current_theme,
1003            current_scene,
1004            background_pipeline,
1005            current_z: 0.0,
1006            telemetry: cvkg_core::TelemetryData::default(),
1007            last_frame_start: std::time::Instant::now(),
1008            last_redraw_start: std::time::Instant::now(),
1009            frame_budget: cvkg_core::FrameBudget::default(),
1010            vram_buffers_bytes: 0,
1011            vram_textures_bytes: 0,
1012            _debug_layout: false,
1013            transform_stack: Vec::new(),
1014            redraw_requested: false,
1015        }
1016    }
1017
1018    /// Update VRAM telemetry based on currently allocated resources.
1019    fn update_vram_telemetry(&mut self) {
1020        // Calculate Buffer VRAM
1021        let mut buffer_bytes = 0;
1022        buffer_bytes += (MAX_VERTICES * std::mem::size_of::<Vertex>()) as u64;
1023        buffer_bytes += (MAX_INDICES * std::mem::size_of::<u32>()) as u64;
1024        buffer_bytes += std::mem::size_of::<cvkg_core::ColorTheme>() as u64;
1025        buffer_bytes += std::mem::size_of::<cvkg_core::SceneUniforms>() as u64;
1026        self.vram_buffers_bytes = buffer_bytes;
1027
1028        // Calculate Texture VRAM
1029        let mut texture_bytes = 0;
1030        texture_bytes += 4096 * 4096 * 4; // Mega Atlas (RGBA8)
1031        texture_bytes += 4; // Dummy (RGBA8)
1032        
1033        for ctx in self.surfaces.values() {
1034            let bpp = 4; 
1035            let surface_bytes = (ctx.config.width * ctx.config.height * bpp) as u64;
1036            texture_bytes += surface_bytes * 3; // scene, blur_a, blur_b
1037            texture_bytes += (ctx.config.width * ctx.config.height * 4) as u64; // depth (Depth32Float)
1038        }
1039        
1040        self.vram_textures_bytes = texture_bytes;
1041        
1042        self.telemetry.vram_buffers_mb = buffer_bytes as f32 / 1_048_576.0;
1043        self.telemetry.vram_textures_mb = texture_bytes as f32 / 1_048_576.0;
1044        self.telemetry.vram_pipelines_mb = 0.0; 
1045        self.telemetry.vram_usage_mb = self.telemetry.vram_buffers_mb + self.telemetry.vram_textures_mb;
1046    }
1047
1048    /// Get real-time performance telemetry.
1049    pub fn get_telemetry(&self) -> cvkg_core::TelemetryData {
1050        self.telemetry.clone()
1051    }
1052
1053    /// resize — Reconfigures a specific surface and its internal textures.
1054    pub fn resize(&mut self, window_id: winit::window::WindowId, width: u32, height: u32, scale_factor: f32) {
1055        if width > 0 && height > 0
1056            && let Some(ctx) = self.surfaces.get_mut(&window_id) {
1057                ctx.config.width = width;
1058                ctx.config.height = height;
1059                ctx.scale_factor = scale_factor;
1060                ctx.surface.configure(&self.device, &ctx.config);
1061
1062                // Re-create Muspelheim textures for this surface
1063                let texture_desc = wgpu::TextureDescriptor {
1064                    label: Some("Surtr Scene Texture"),
1065                    size: wgpu::Extent3d { width, height, depth_or_array_layers: 1 },
1066                    mip_level_count: 1,
1067                    sample_count: 1,
1068                    dimension: wgpu::TextureDimension::D2,
1069                    format: ctx.config.format,
1070                    usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
1071                    view_formats: &[],
1072                };
1073
1074                let scene_tex = self.device.create_texture(&texture_desc);
1075                ctx.scene_texture = scene_tex.create_view(&wgpu::TextureViewDescriptor::default());
1076
1077                let blur_tex_a = self.device.create_texture(&texture_desc);
1078                ctx.blur_texture_a = blur_tex_a.create_view(&wgpu::TextureViewDescriptor::default());
1079
1080                let blur_tex_b = self.device.create_texture(&texture_desc);
1081                ctx.blur_texture_b = blur_tex_b.create_view(&wgpu::TextureViewDescriptor::default());
1082
1083                // Re-create bind groups for this surface
1084                ctx.scene_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1085                    layout: &self.env_bind_group_layout,
1086                    entries: &[
1087                        wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&ctx.scene_texture) },
1088                        wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&ctx.sampler) },
1089                    ],
1090                    label: Some("Scene Bind Group Resize"),
1091                });
1092
1093                ctx.scene_texture_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1094                    layout: &self.texture_bind_group_layout,
1095                    entries: &[
1096                        wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&ctx.scene_texture) },
1097                        wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&ctx.sampler) },
1098                    ],
1099                    label: Some("Scene Texture Bind Group Resize"),
1100                });
1101
1102                ctx.blur_bind_group_a = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1103                    layout: &self.texture_bind_group_layout,
1104                    entries: &[
1105                        wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&ctx.blur_texture_a) },
1106                        wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&ctx.sampler) },
1107                    ],
1108                    label: Some("Blur Bind Group A Resize"),
1109                });
1110
1111                ctx.blur_bind_group_b = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1112                    layout: &self.texture_bind_group_layout,
1113                    entries: &[
1114                        wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&ctx.blur_texture_b) },
1115                        wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&ctx.sampler) },
1116                    ],
1117                    label: Some("Blur Bind Group B Resize"),
1118                });
1119
1120                ctx.blur_env_bind_group_a = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1121                    layout: &self.env_bind_group_layout,
1122                    entries: &[
1123                        wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&ctx.blur_texture_a) },
1124                        wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&ctx.sampler) },
1125                    ],
1126                    label: Some("Blur Env Bind Group A Resize"),
1127                });
1128
1129                let depth_texture = self.device.create_texture(&wgpu::TextureDescriptor {
1130                    label: Some("Surtr Depth Texture"),
1131                    size: wgpu::Extent3d { width, height, depth_or_array_layers: 1 },
1132                    mip_level_count: 1,
1133                    sample_count: 1,
1134                    dimension: wgpu::TextureDimension::D2,
1135                    format: wgpu::TextureFormat::Depth32Float,
1136                    usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1137                    view_formats: &[],
1138                });
1139                ctx.depth_texture_view = depth_texture.create_view(&wgpu::TextureViewDescriptor::default());
1140        }
1141    }
1142
1143    /// begin_frame_headless — Strike the flaming sword to begin a new GPU frame for headless rendering.
1144    pub fn begin_frame_headless(&mut self) -> wgpu::CommandEncoder {
1145        self.current_window = None;
1146        self.vertices.clear();
1147        self.indices.clear();
1148        self.draw_calls.clear();
1149        self.shared_elements.clear();
1150        self.current_texture_id = None;
1151        self.opacity_stack = vec![1.0];
1152        self.clip_stack.clear();
1153        self.slice_stack.clear();
1154        self.current_z = 0.0;
1155        
1156        self.last_frame_start = std::time::Instant::now();
1157        self.telemetry.draw_calls = 0;
1158        self.telemetry.vertices = 0;
1159
1160        let ctx = self.headless_context.as_ref().expect("Headless context not initialized");
1161        let time = self.start_time.elapsed().as_secs_f32();
1162        let logical_w = ctx.width as f32 / ctx.scale_factor;
1163        let logical_h = ctx.height as f32 / ctx.scale_factor;
1164        let dt = time - self.current_scene.time;
1165        self.current_scene.time = time;
1166        self.current_scene.delta_time = dt;
1167        self.current_scene.resolution = [logical_w, logical_h];
1168        self.current_scene.scale_factor = ctx.scale_factor;
1169        self.current_scene.proj = glam::Mat4::orthographic_lh(0.0, logical_w, logical_h, 0.0, -1000.0, 1000.0);
1170
1171        self.queue.write_buffer(
1172            &self.scene_buffer,
1173            0,
1174            bytemuck::bytes_of(&self.current_scene),
1175        );
1176
1177        self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
1178            label: Some("Surtr Headless Command Encoder"),
1179        })
1180    }
1181
1182    /// begin_frame — Strike the flaming sword to begin a new GPU frame for a specific window.
1183    pub fn begin_frame(&mut self, window_id: winit::window::WindowId) -> wgpu::CommandEncoder {
1184        self.current_window = Some(window_id);
1185        self.vertices.clear();
1186        self.indices.clear();
1187        self.draw_calls.clear();
1188        self.shared_elements.clear();
1189        self.current_texture_id = None;
1190        self.opacity_stack = vec![1.0];
1191        self.clip_stack.clear();
1192        self.slice_stack.clear();
1193        self.current_z = 0.0;
1194        
1195        self.last_frame_start = std::time::Instant::now();
1196        self.telemetry.draw_calls = 0;
1197        self.telemetry.vertices = 0;
1198
1199        let ctx = self.surfaces.get(&window_id).expect("Window not registered");
1200        let time = self.start_time.elapsed().as_secs_f32();
1201        let logical_w = ctx.config.width as f32 / ctx.scale_factor;
1202        let logical_h = ctx.config.height as f32 / ctx.scale_factor;
1203        let dt = time - self.current_scene.time;
1204        self.current_scene.time = time;
1205        self.current_scene.delta_time = dt;
1206        self.current_scene.resolution = [logical_w, logical_h];
1207        self.current_scene.scale_factor = ctx.scale_factor;
1208        self.current_scene.proj = glam::Mat4::orthographic_lh(0.0, logical_w, logical_h, 0.0, -1000.0, 1000.0);
1209
1210        self.queue.write_buffer(
1211            &self.scene_buffer,
1212            0,
1213            bytemuck::bytes_of(&self.current_scene),
1214        );
1215
1216        self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
1217            label: Some("Surtr Command Encoder"),
1218        })
1219    }
1220
1221
1222
1223
1224    /// register_window — Attaches a new OS window to the shared GPU context.
1225    pub fn register_window(&mut self, window: Arc<winit::window::Window>) {
1226        let size = window.inner_size();
1227        let surface = self.instance.create_surface(window.clone()).expect("Failed to create surface");
1228        let caps = surface.get_capabilities(&self.adapter);
1229        let format = caps.formats[0];
1230        let config = wgpu::SurfaceConfiguration {
1231            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1232            format,
1233            width: size.width,
1234            height: size.height,
1235            present_mode: wgpu::PresentMode::Fifo,
1236            alpha_mode: caps.alpha_modes[0],
1237            view_formats: vec![],
1238            desired_maximum_frame_latency: 2,
1239        };
1240        surface.configure(&self.device, &config);
1241
1242        let ctx = Self::create_surface_context(
1243            &self.device,
1244            surface,
1245            config,
1246            &self.env_bind_group_layout,
1247            &self.texture_bind_group_layout,
1248            window.scale_factor() as f32,
1249        );
1250
1251        self.surfaces.insert(window.id(), ctx);
1252    }
1253
1254    fn create_headless_context(
1255        device: &wgpu::Device,
1256        width: u32,
1257        height: u32,
1258        format: wgpu::TextureFormat,
1259        env_bind_group_layout: &wgpu::BindGroupLayout,
1260        texture_bind_group_layout: &wgpu::BindGroupLayout,
1261    ) -> HeadlessContext {
1262        let texture_desc = wgpu::TextureDescriptor {
1263            label: Some("Surtr Headless Scene Texture"),
1264            size: wgpu::Extent3d {
1265                width,
1266                height,
1267                depth_or_array_layers: 1,
1268            },
1269            mip_level_count: 1,
1270            sample_count: 1,
1271            dimension: wgpu::TextureDimension::D2,
1272            format,
1273            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_SRC,
1274            view_formats: &[],
1275        };
1276
1277        let scene_tex = device.create_texture(&texture_desc);
1278        let scene_texture = scene_tex.create_view(&wgpu::TextureViewDescriptor::default());
1279
1280        let blur_tex_a = device.create_texture(&texture_desc);
1281        let blur_texture_a = blur_tex_a.create_view(&wgpu::TextureViewDescriptor::default());
1282
1283        let blur_tex_b = device.create_texture(&texture_desc);
1284        let blur_texture_b = blur_tex_b.create_view(&wgpu::TextureViewDescriptor::default());
1285
1286        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1287            address_mode_u: wgpu::AddressMode::ClampToEdge,
1288            address_mode_v: wgpu::AddressMode::ClampToEdge,
1289            mag_filter: wgpu::FilterMode::Linear,
1290            min_filter: wgpu::FilterMode::Linear,
1291            ..Default::default()
1292        });
1293
1294        let scene_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1295            layout: env_bind_group_layout,
1296            entries: &[
1297                wgpu::BindGroupEntry {
1298                    binding: 0,
1299                    resource: wgpu::BindingResource::TextureView(&scene_texture),
1300                },
1301                wgpu::BindGroupEntry {
1302                    binding: 1,
1303                    resource: wgpu::BindingResource::Sampler(&sampler),
1304                },
1305            ],
1306            label: Some("Headless Scene Bind Group"),
1307        });
1308
1309        let scene_texture_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1310            layout: texture_bind_group_layout,
1311            entries: &[
1312                wgpu::BindGroupEntry {
1313                    binding: 0,
1314                    resource: wgpu::BindingResource::TextureView(&scene_texture),
1315                },
1316                wgpu::BindGroupEntry {
1317                    binding: 1,
1318                    resource: wgpu::BindingResource::Sampler(&sampler),
1319                },
1320            ],
1321            label: Some("Headless Scene Texture Bind Group"),
1322        });
1323
1324        let blur_bind_group_a = device.create_bind_group(&wgpu::BindGroupDescriptor {
1325            layout: texture_bind_group_layout,
1326            entries: &[
1327                wgpu::BindGroupEntry {
1328                    binding: 0,
1329                    resource: wgpu::BindingResource::TextureView(&blur_texture_a),
1330                },
1331                wgpu::BindGroupEntry {
1332                    binding: 1,
1333                    resource: wgpu::BindingResource::Sampler(&sampler),
1334                },
1335            ],
1336            label: Some("Headless Blur Bind Group A"),
1337        });
1338
1339        let blur_bind_group_b = device.create_bind_group(&wgpu::BindGroupDescriptor {
1340            layout: texture_bind_group_layout,
1341            entries: &[
1342                wgpu::BindGroupEntry {
1343                    binding: 0,
1344                    resource: wgpu::BindingResource::TextureView(&blur_texture_b),
1345                },
1346                wgpu::BindGroupEntry {
1347                    binding: 1,
1348                    resource: wgpu::BindingResource::Sampler(&sampler),
1349                },
1350            ],
1351            label: Some("Headless Blur Bind Group B"),
1352        });
1353
1354        let blur_env_bind_group_a = device.create_bind_group(&wgpu::BindGroupDescriptor {
1355            layout: env_bind_group_layout,
1356            entries: &[
1357                wgpu::BindGroupEntry {
1358                    binding: 0,
1359                    resource: wgpu::BindingResource::TextureView(&blur_texture_a),
1360                },
1361                wgpu::BindGroupEntry {
1362                    binding: 1,
1363                    resource: wgpu::BindingResource::Sampler(&sampler),
1364                },
1365            ],
1366            label: Some("Headless Blur Env Bind Group A"),
1367        });
1368
1369        let depth_texture = device.create_texture(&wgpu::TextureDescriptor {
1370            label: Some("Headless Depth Texture"),
1371            size: wgpu::Extent3d { width, height, depth_or_array_layers: 1 },
1372            mip_level_count: 1,
1373            sample_count: 1,
1374            dimension: wgpu::TextureDimension::D2,
1375            format: wgpu::TextureFormat::Depth32Float,
1376            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1377            view_formats: &[],
1378        });
1379        let depth_texture_view = depth_texture.create_view(&wgpu::TextureViewDescriptor::default());
1380
1381        let output_texture = device.create_texture(&wgpu::TextureDescriptor {
1382            label: Some("Headless Output Texture"),
1383            size: wgpu::Extent3d { width, height, depth_or_array_layers: 1 },
1384            mip_level_count: 1,
1385            sample_count: 1,
1386            dimension: wgpu::TextureDimension::D2,
1387            format,
1388            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::COPY_SRC,
1389            view_formats: &[],
1390        });
1391        let output_view = output_texture.create_view(&wgpu::TextureViewDescriptor::default());
1392
1393        HeadlessContext {
1394            scene_texture,
1395            scene_bind_group,
1396            scene_texture_bind_group,
1397            depth_texture_view,
1398            blur_texture_a,
1399            blur_texture_b,
1400            blur_bind_group_a,
1401            blur_bind_group_b,
1402            blur_env_bind_group_a,
1403            scale_factor: 1.0,
1404            sampler,
1405            width,
1406            height,
1407            output_texture,
1408            output_view,
1409        }
1410    }
1411
1412    fn create_surface_context(
1413        device: &wgpu::Device,
1414        surface: wgpu::Surface<'static>,
1415        config: wgpu::SurfaceConfiguration,
1416        env_bind_group_layout: &wgpu::BindGroupLayout,
1417        texture_bind_group_layout: &wgpu::BindGroupLayout,
1418        scale_factor: f32,
1419    ) -> SurfaceContext {
1420        let width = config.width;
1421        let height = config.height;
1422
1423        let texture_desc = wgpu::TextureDescriptor {
1424            label: Some("Surtr Scene Texture"),
1425            size: wgpu::Extent3d {
1426                width,
1427                height,
1428                depth_or_array_layers: 1,
1429            },
1430            mip_level_count: 1,
1431            sample_count: 1,
1432            dimension: wgpu::TextureDimension::D2,
1433            format: config.format,
1434            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
1435            view_formats: &[],
1436        };
1437
1438        let scene_tex = device.create_texture(&texture_desc);
1439        let scene_texture = scene_tex.create_view(&wgpu::TextureViewDescriptor::default());
1440
1441        let blur_tex_a = device.create_texture(&texture_desc);
1442        let blur_texture_a = blur_tex_a.create_view(&wgpu::TextureViewDescriptor::default());
1443
1444        let blur_tex_b = device.create_texture(&texture_desc);
1445        let blur_texture_b = blur_tex_b.create_view(&wgpu::TextureViewDescriptor::default());
1446
1447        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1448            address_mode_u: wgpu::AddressMode::ClampToEdge,
1449            address_mode_v: wgpu::AddressMode::ClampToEdge,
1450            mag_filter: wgpu::FilterMode::Linear,
1451            min_filter: wgpu::FilterMode::Linear,
1452            ..Default::default()
1453        });
1454
1455        let scene_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1456            layout: env_bind_group_layout,
1457            entries: &[
1458                wgpu::BindGroupEntry {
1459                    binding: 0,
1460                    resource: wgpu::BindingResource::TextureView(&scene_texture),
1461                },
1462                wgpu::BindGroupEntry {
1463                    binding: 1,
1464                    resource: wgpu::BindingResource::Sampler(&sampler),
1465                },
1466            ],
1467            label: Some("Scene Bind Group"),
1468        });
1469
1470        let scene_texture_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1471            layout: texture_bind_group_layout,
1472            entries: &[
1473                wgpu::BindGroupEntry {
1474                    binding: 0,
1475                    resource: wgpu::BindingResource::TextureView(&scene_texture),
1476                },
1477                wgpu::BindGroupEntry {
1478                    binding: 1,
1479                    resource: wgpu::BindingResource::Sampler(&sampler),
1480                },
1481            ],
1482            label: Some("Scene Texture Bind Group"),
1483        });
1484
1485        let blur_bind_group_a = device.create_bind_group(&wgpu::BindGroupDescriptor {
1486            layout: texture_bind_group_layout,
1487            entries: &[
1488                wgpu::BindGroupEntry {
1489                    binding: 0,
1490                    resource: wgpu::BindingResource::TextureView(&blur_texture_a),
1491                },
1492                wgpu::BindGroupEntry {
1493                    binding: 1,
1494                    resource: wgpu::BindingResource::Sampler(&sampler),
1495                },
1496            ],
1497            label: Some("Blur Bind Group A"),
1498        });
1499
1500        let blur_bind_group_b = device.create_bind_group(&wgpu::BindGroupDescriptor {
1501            layout: texture_bind_group_layout,
1502            entries: &[
1503                wgpu::BindGroupEntry {
1504                    binding: 0,
1505                    resource: wgpu::BindingResource::TextureView(&blur_texture_b),
1506                },
1507                wgpu::BindGroupEntry {
1508                    binding: 1,
1509                    resource: wgpu::BindingResource::Sampler(&sampler),
1510                },
1511            ],
1512            label: Some("Blur Bind Group B"),
1513        });
1514
1515        let depth_texture = device.create_texture(&wgpu::TextureDescriptor {
1516            label: Some("Surtr Depth Texture"),
1517            size: wgpu::Extent3d {
1518                width,
1519                height,
1520                depth_or_array_layers: 1,
1521            },
1522            mip_level_count: 1,
1523            sample_count: 1,
1524            dimension: wgpu::TextureDimension::D2,
1525            format: wgpu::TextureFormat::Depth32Float,
1526            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
1527            view_formats: &[],
1528        });
1529        let depth_texture_view = depth_texture.create_view(&wgpu::TextureViewDescriptor::default());
1530
1531        let blur_env_bind_group_a = device.create_bind_group(&wgpu::BindGroupDescriptor {
1532            layout: env_bind_group_layout,
1533            entries: &[
1534                wgpu::BindGroupEntry {
1535                    binding: 0,
1536                    resource: wgpu::BindingResource::TextureView(&blur_texture_a),
1537                },
1538                wgpu::BindGroupEntry {
1539                    binding: 1,
1540                    resource: wgpu::BindingResource::Sampler(&sampler),
1541                },
1542                wgpu::BindGroupEntry {
1543                    binding: 2,
1544                    resource: wgpu::BindingResource::TextureView(&depth_texture_view),
1545                },
1546            ],
1547            label: Some("Blur Env Bind Group A"),
1548        });
1549
1550        SurfaceContext {
1551            surface,
1552            config,
1553            scene_texture,
1554            scene_bind_group,
1555            scene_texture_bind_group,
1556            depth_texture_view,
1557            blur_texture_a,
1558            blur_texture_b,
1559            blur_bind_group_a,
1560            blur_bind_group_b,
1561            blur_env_bind_group_a,
1562            scale_factor,
1563            sampler,
1564        }
1565    }
1566
1567    /// Reset the internal clock (for interactive effects)
1568    pub fn reset_time(&mut self) {
1569        self.start_time = std::time::Instant::now();
1570    }
1571
1572    fn shatter_internal(
1573        &mut self,
1574        rect: Rect,
1575        pieces: u32,
1576        force: f32,
1577        color: [f32; 4],
1578        mode: u32,
1579    ) {
1580        // High-Fidelity Variable Particle Density
1581        let count = (pieces as f32).sqrt().ceil() as u32;
1582        let dw = rect.width / count as f32;
1583        let dh = rect.height / count as f32;
1584
1585        let c = self.apply_opacity(color);
1586
1587        for y in 0..count {
1588            for x in 0..count {
1589                let shard_rect = Rect {
1590                    x: rect.x + x as f32 * dw,
1591                    y: rect.y + y as f32 * dh,
1592                    width: dw,
1593                    height: dh,
1594                };
1595
1596                let uv = Rect {
1597                    x: x as f32 / count as f32,
1598                    y: y as f32 / count as f32,
1599                    width: 1.0 / count as f32,
1600                    height: 1.0 / count as f32,
1601                };
1602
1603                self.fill_rect_with_full_params(shard_rect, c, mode, None, force, uv);
1604            }
1605        }
1606    }
1607
1608    fn recursive_bolt(&mut self, from: [f32; 2], to: [f32; 2], depth: u32, color: [f32; 4]) {
1609        if depth == 0 {
1610            self.draw_lightning_segment(from, to, color);
1611            return;
1612        }
1613
1614        let mid_x = (from[0] + to[0]) * 0.5;
1615        let mid_y = (from[1] + to[1]) * 0.5;
1616
1617        let dx = to[0] - from[0];
1618        let dy = to[1] - from[1];
1619        let len = (dx * dx + dy * dy).sqrt();
1620
1621        // Perpendicular offset for jaggedness
1622        let offset_scale = len * 0.15;
1623        let seed = (from[0] * 12.9898 + from[1] * 78.233 + (depth as f32) * 37.11)
1624            .sin()
1625            .fract();
1626        let offset_x = -dy / len * (seed - 0.5) * offset_scale;
1627        let offset_y = dx / len * (seed - 0.5) * offset_scale;
1628
1629        let mid = [mid_x + offset_x, mid_y + offset_y];
1630
1631        self.recursive_bolt(from, mid, depth - 1, color);
1632        self.recursive_bolt(mid, to, depth - 1, color);
1633
1634        // 20% chance of a secondary branch
1635        if depth > 2 && seed > 0.8 {
1636            let branch_to = [
1637                mid[0] + offset_x * 2.0 + (seed * 100.0).sin() * 50.0,
1638                mid[1] + offset_y * 2.0 + (seed * 100.0).cos() * 50.0,
1639            ];
1640            self.recursive_bolt(mid, branch_to, depth - 2, color);
1641        }
1642    }
1643
1644    fn draw_lightning_segment(&mut self, from: [f32; 2], to: [f32; 2], color: [f32; 4]) {
1645        let dx = to[0] - from[0];
1646        let dy = to[1] - from[1];
1647        let len = (dx * dx + dy * dy).sqrt();
1648        if len < 0.001 {
1649            return;
1650        }
1651
1652        let glow_width = 32.0;
1653        let core_width = 4.0;
1654        let c = self.apply_opacity(color);
1655
1656        // 1. Render Volumetric Glow (Cyan)
1657        let gnx = -dy / len * glow_width * 0.5;
1658        let gny = dx / len * glow_width * 0.5;
1659        let gp1 = [from[0] + gnx, from[1] + gny];
1660        let gp2 = [to[0] + gnx, to[1] + gny];
1661        let gp3 = [to[0] - gnx, to[1] - gny];
1662        let gp4 = [from[0] - gnx, from[1] - gny];
1663        self.push_oriented_quad(
1664            [gp1, gp2, gp3, gp4],
1665            c,
1666            9,
1667            Rect {
1668                x: 0.0,
1669                y: 0.0,
1670                width: 1.0,
1671                height: 1.0,
1672            },
1673        );
1674
1675        // 2. Render Blinding Core (White)
1676        let cnx = -dy / len * core_width * 0.5;
1677        let cny = dx / len * core_width * 0.5;
1678        let cp1 = [from[0] + cnx, from[1] + cny];
1679        let cp2 = [to[0] + cnx, to[1] + cny];
1680        let cp3 = [to[0] - cnx, to[1] - cny];
1681        let cp4 = [from[0] - cnx, from[1] - cny];
1682        self.push_oriented_quad(
1683            [cp1, cp2, cp3, cp4],
1684            [1.0, 1.0, 1.0, c[3]],
1685            0,
1686            Rect {
1687                x: 0.0,
1688                y: 0.0,
1689                width: 1.0,
1690                height: 1.0,
1691            },
1692        );
1693    }
1694
1695    fn push_oriented_quad(
1696        &mut self,
1697        points: [[f32; 2]; 4],
1698        color: [f32; 4],
1699        mode: u32,
1700        uv_rect: Rect,
1701    ) {
1702        let scissor = self.clip_stack.last().copied();
1703        let texture_id = None; // Oriented quads like lightning don't use textures yet
1704
1705        if self.draw_calls.is_empty()
1706            || self.current_texture_id != texture_id
1707            || self.draw_calls.last().unwrap().scissor_rect != scissor
1708        {
1709            self.current_texture_id = texture_id;
1710            self.draw_calls.push(DrawCall {
1711                texture_id,
1712                scissor_rect: scissor,
1713                index_start: self.indices.len() as u32,
1714                index_count: 0,
1715                is_glass: mode == 7,
1716                is_ui: mode == 6,
1717            });
1718        }
1719
1720        let uvs = [
1721            [uv_rect.x, uv_rect.y],
1722            [uv_rect.x + uv_rect.width, uv_rect.y],
1723            [uv_rect.x + uv_rect.width, uv_rect.y + uv_rect.height],
1724            [uv_rect.x, uv_rect.y + uv_rect.height],
1725        ];
1726
1727        let screen = [self.current_width() as f32, self.current_height() as f32];
1728        let rect = Rect {
1729            x: points[0][0],
1730            y: points[0][1],
1731            width: 1.0,
1732            height: 1.0,
1733        };
1734
1735        for i in 0..4 {
1736            let px = points[i][0];
1737            let py = points[i][1];
1738
1739            let (translation, scale_transform, rotation) = self.get_current_transform();
1740            self.vertices.push(Vertex {
1741                position: [px, py, 0.0],
1742                normal: [0.0, 0.0, 1.0],
1743                uv: uvs[i],
1744                color,
1745                mode,
1746                radius: 0.0,
1747                slice: [0.0, 0.0, 0.0, 1.0],
1748                logical: [px - rect.x, py - rect.y],
1749                size: [rect.width, rect.height],
1750                screen,
1751                clip: [-10000.0, -10000.0, 20000.0, 20000.0],
1752                translation,
1753                scale: scale_transform,
1754                rotation,
1755                _pad: 0.0,
1756            });
1757        }
1758
1759        if let Some(call) = self.draw_calls.last_mut() {
1760            call.index_count += 6;
1761        }
1762    }
1763    fn get_texture_id(&self, name: &str) -> Option<u32> {
1764        self.texture_registry.get(name).copied()
1765    }
1766
1767    /// fill_rect_with_mode — Specialized rectangle drawing with mode-specific shader logic.
1768    pub fn fill_rect_with_mode(
1769        &mut self,
1770        rect: Rect,
1771        color: [f32; 4],
1772        mode: u32,
1773        texture_id: Option<u32>,
1774    ) {
1775        self.fill_rect_with_full_params(
1776            rect,
1777            color,
1778            mode,
1779            texture_id,
1780            0.0,
1781            Rect {
1782                x: 0.0,
1783                y: 0.0,
1784                width: 1.0,
1785                height: 1.0,
1786            },
1787        );
1788    }
1789
1790    fn fill_rect_with_full_params(
1791        &mut self,
1792        rect: Rect,
1793        color: [f32; 4],
1794        mode: u32,
1795        texture_id: Option<u32>,
1796        radius: f32,
1797        uv_rect: Rect,
1798    ) {
1799        // If a shadow is active, draw it first
1800        if let Some(shadow) = self.shadow_stack.last().copied() 
1801            && shadow.color[3] > 0.001 {
1802            Renderer::draw_drop_shadow(
1803                self,
1804                rect,
1805                radius,
1806                shadow.color,
1807                shadow.radius,
1808                0.0, // Spread
1809            );
1810        }
1811
1812        let slice = self
1813            .slice_stack
1814            .last()
1815            .copied()
1816            .map(|(a, o)| [a, o, 1.0, 1.0])
1817            .unwrap_or([0.0, 0.0, 0.0, 1.0]);
1818        self.fill_rect_with_full_params_and_slice(
1819            rect, color, mode, texture_id, radius, uv_rect, slice,
1820        );
1821    }
1822
1823    fn fill_rect_with_full_params_and_slice(
1824        &mut self,
1825        rect: Rect,
1826        color: [f32; 4],
1827        mode: u32,
1828        texture_id: Option<u32>,
1829        radius: f32,
1830        uv_rect: Rect,
1831        slice: [f32; 4],
1832    ) {
1833        let scissor = self.clip_stack.last().copied();
1834
1835        let is_glass = mode == 7;
1836        let is_ui = !is_glass;
1837
1838        // Batching: check if we need to start a new DrawCall
1839        let last_call = self.draw_calls.last();
1840        let needs_new_call = self.draw_calls.is_empty()
1841            || self.current_texture_id != texture_id
1842            || last_call.unwrap().scissor_rect != scissor
1843            || last_call.unwrap().is_glass != is_glass
1844            || last_call.unwrap().is_ui != is_ui;
1845
1846        if needs_new_call {
1847            self.current_texture_id = texture_id;
1848            self.draw_calls.push(DrawCall {
1849                texture_id,
1850                scissor_rect: scissor,
1851                index_start: self.indices.len() as u32,
1852                index_count: 0,
1853                is_glass,
1854                is_ui,
1855            });
1856        }
1857
1858        let scale = self.current_scale_factor();
1859        let snap = |v: f32| (v * scale).round() / scale;
1860
1861        let base_idx = self.vertices.len() as u32;
1862        let x1 = snap(rect.x);
1863        let y1 = snap(rect.y);
1864        let x2 = snap(rect.x + rect.width);
1865        let y2 = snap(rect.y + rect.height);
1866        let z = self.current_z;
1867        let normal = [0.0, 0.0, 1.0];
1868        let screen = [self.current_width() as f32, self.current_height() as f32];
1869        let clip_rect = self.clip_stack.last().copied().unwrap_or(cvkg_core::Rect {
1870            x: -10000.0,
1871            y: -10000.0,
1872            width: 20000.0,
1873            height: 20000.0,
1874        });
1875        let clip = [clip_rect.x, clip_rect.y, clip_rect.width, clip_rect.height];
1876
1877        let (translation, scale_transform, rotation) = self.get_current_transform();
1878
1879        self.vertices.push(Vertex {
1880            position: [x1, y1, z],
1881            normal,
1882            uv: [uv_rect.x, uv_rect.y],
1883            color,
1884            mode,
1885            radius,
1886            slice,
1887            logical: [0.0, 0.0],
1888            size: [rect.width, rect.height],
1889            screen,
1890            clip,
1891            translation,
1892            scale: scale_transform,
1893            rotation,
1894            _pad: 0.0,
1895        });
1896        self.vertices.push(Vertex {
1897            position: [x2, y1, z],
1898            normal,
1899            uv: [uv_rect.x + uv_rect.width, uv_rect.y],
1900            color,
1901            mode,
1902            radius,
1903            slice,
1904            logical: [rect.width, 0.0],
1905            size: [rect.width, rect.height],
1906            screen,
1907            clip,
1908            translation,
1909            scale: scale_transform,
1910            rotation,
1911            _pad: 0.0,
1912        });
1913        self.vertices.push(Vertex {
1914            position: [x2, y2, z],
1915            normal,
1916            uv: [uv_rect.x + uv_rect.width, uv_rect.y + uv_rect.height],
1917            color,
1918            mode,
1919            radius,
1920            slice,
1921            logical: [rect.width, rect.height],
1922            size: [rect.width, rect.height],
1923            screen,
1924            clip,
1925            translation,
1926            scale: scale_transform,
1927            rotation,
1928            _pad: 0.0,
1929        });
1930        self.vertices.push(Vertex {
1931            position: [x1, y2, z],
1932            normal,
1933            uv: [uv_rect.x, uv_rect.y + uv_rect.height],
1934            color,
1935            mode,
1936            radius,
1937            slice,
1938            logical: [0.0, rect.height],
1939            size: [rect.width, rect.height],
1940            screen,
1941            clip,
1942            translation,
1943            scale: scale_transform,
1944            rotation,
1945            _pad: 0.0,
1946        });
1947
1948        self.indices.extend_from_slice(&[
1949            base_idx,
1950            base_idx + 1,
1951            base_idx + 2,
1952            base_idx,
1953            base_idx + 2,
1954            base_idx + 3,
1955        ]);
1956
1957        if let Some(call) = self.draw_calls.last_mut() {
1958            call.index_count += 6;
1959        }
1960    }
1961
1962    /// end_frame — Quench the blade by submitting the full Muspelheim multi-pass effect.
1963    pub fn end_frame(&mut self, mut encoder: wgpu::CommandEncoder) {
1964        // Visual Lint: If layout was dirtied during the render phase (layout thrashing),
1965        // draw a 10px red border as a warning flash.
1966        if LAYOUT_DIRTY.swap(false, Ordering::AcqRel)
1967            && let Some(window_id) = self.current_window
1968            && let Some(surface_ctx) = self.surfaces.get(&window_id) {
1969                let w = surface_ctx.config.width as f32;
1970                let h = surface_ctx.config.height as f32;
1971                let border_rect = Rect { x: 0.0, y: 0.0, width: w, height: h };
1972                // Draw a thick red border to signal layout-thrashing
1973                self.stroke_rect(border_rect, [1.0, 0.0, 0.0, 1.0], 10.0);
1974        }
1975
1976        self.queue
1977            .write_buffer(&self.vertex_buffer, 0, bytemuck::cast_slice(&self.vertices));
1978        self.queue
1979            .write_buffer(&self.index_buffer, 0, bytemuck::cast_slice(&self.indices));
1980
1981        let (surface_texture, target_view, ctx_scene_texture, ctx_depth_texture_view, ctx_blur_env_bind_group_a, ctx_scene_texture_bind_group, ctx_blur_texture_a, ctx_blur_texture_b, _ctx_sampler, ctx_blur_bind_group_a, ctx_blur_bind_group_b) = if let Some(window_id) = self.current_window {
1982            let ctx = self.surfaces.get(&window_id).expect("Missing surface context");
1983            let frame = match ctx.surface.get_current_texture() {
1984                wgpu::CurrentSurfaceTexture::Success(t) => t,
1985                wgpu::CurrentSurfaceTexture::Suboptimal(t) => {
1986                    ctx.surface.configure(&self.device, &ctx.config);
1987                    t
1988                }
1989                _ => return, // Silent failure for window issues
1990            };
1991            let view = frame.texture.create_view(&wgpu::TextureViewDescriptor::default());
1992            (Some(frame), view, &ctx.scene_texture, &ctx.depth_texture_view, &ctx.blur_env_bind_group_a, &ctx.scene_texture_bind_group, &ctx.blur_texture_a, &ctx.blur_texture_b, &ctx.sampler, &ctx.blur_bind_group_a, &ctx.blur_bind_group_b)
1993        } else {
1994            let ctx = self.headless_context.as_ref().expect("No headless context for end_frame");
1995            (None, ctx.output_view.clone(), &ctx.scene_texture, &ctx.depth_texture_view, &ctx.blur_env_bind_group_a, &ctx.scene_texture_bind_group, &ctx.blur_texture_a, &ctx.blur_texture_b, &ctx.sampler, &ctx.blur_bind_group_a, &ctx.blur_bind_group_b)
1996        };
1997
1998        // ── Pass 1: Opaque Background & Atmosphere ──────────────────────────
1999        {
2000            let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2001                label: Some("Surtr P1 Opaque Background"),
2002                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2003                    view: ctx_scene_texture,
2004                    resolve_target: None,
2005                    ops: wgpu::Operations {
2006                        load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 0.0 }),
2007                        store: wgpu::StoreOp::Store,
2008                    },
2009                    depth_slice: None,
2010                })],
2011                depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
2012                    view: ctx_depth_texture_view,
2013                    depth_ops: Some(wgpu::Operations {
2014                        load: wgpu::LoadOp::Clear(1.0),
2015                        store: wgpu::StoreOp::Store,
2016                    }),
2017                    stencil_ops: None,
2018                }),
2019                occlusion_query_set: None,
2020                timestamp_writes: None,
2021                multiview_mask: None,
2022            });
2023
2024            // 1a. Background Atmosphere
2025            p.set_pipeline(&self.background_pipeline);
2026            p.set_bind_group(0, &self.dummy_texture_bind_group, &[]);
2027            p.set_bind_group(1, ctx_blur_env_bind_group_a, &[]); // Use previous frame's blur for background depth
2028            p.set_bind_group(2, &self.berserker_bind_group, &[]);
2029            p.draw(0..6, 0..1);
2030
2031            // 1b. Opaque Main Elements (non-glass, non-ui)
2032            if !self.draw_calls.is_empty() {
2033                p.set_pipeline(&self.pipeline);
2034                p.set_vertex_buffer(0, self.vertex_buffer.slice(..));
2035                p.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
2036                p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
2037                p.set_bind_group(2, &self.berserker_bind_group, &[]);
2038
2039                for call in self.draw_calls.iter().filter(|c| !c.is_glass && !c.is_ui) {
2040                    let bg = if let Some(id) = call.texture_id {
2041                        if id == 0 {
2042                            &self.mega_atlas_bind_group
2043                        } else {
2044                            self.texture_bind_groups
2045                                .get(id as usize)
2046                                .unwrap_or(&self.dummy_texture_bind_group)
2047                        }
2048                    } else {
2049                        &self.dummy_texture_bind_group
2050                    };
2051                    p.set_bind_group(0, bg, &[]);
2052                    p.draw_indexed(
2053                        call.index_start..call.index_start + call.index_count,
2054                        0,
2055                        0..1,
2056                    );
2057                    self.telemetry.draw_calls += 1;
2058                    self.telemetry.vertices += call.index_count;
2059                }
2060            }
2061        }
2062
2063        // ── Pass 2: Backdrop Blur (Bifrost) ──────────────────────────────────
2064        // Capture the background into blur_texture_b
2065        {
2066            // First extract into texture_a
2067            let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2068                label: Some("Surtr Blur Extract"),
2069                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2070                    view: ctx_blur_texture_a,
2071                    resolve_target: None,
2072                    ops: wgpu::Operations {
2073                        load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 0.0 }),
2074                        store: wgpu::StoreOp::Store,
2075                    },
2076                    depth_slice: None,
2077                })],
2078                ..Default::default()
2079            });
2080            p.set_pipeline(&self.bloom_extract_pipeline); // Use extract as a direct copy for now
2081            p.set_bind_group(0, ctx_scene_texture_bind_group, &[]);
2082            p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
2083            p.set_bind_group(2, &self.berserker_bind_group, &[]);
2084            p.draw(0..6, 0..1);
2085        }
2086
2087        let blur_iters: u32 = 4;
2088        for _i in 0..blur_iters {
2089            {
2090                let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2091                    label: Some("Blur H"),
2092                    color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2093                        view: ctx_blur_texture_b,
2094                        resolve_target: None,
2095                        ops: wgpu::Operations {
2096                            load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 0.0 }),
2097                            store: wgpu::StoreOp::Store,
2098                        },
2099                        depth_slice: None,
2100                    })],
2101                    ..Default::default()
2102                });
2103                p.set_pipeline(&self.blur_h_pipeline);
2104                p.set_bind_group(0, ctx_blur_bind_group_a, &[]);
2105                p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
2106                p.set_bind_group(2, &self.berserker_bind_group, &[]);
2107                p.draw(0..6, 0..1);
2108            }
2109            {
2110                let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2111                    label: Some("Blur V"),
2112                    color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2113                        view: ctx_blur_texture_a,
2114                        resolve_target: None,
2115                        ops: wgpu::Operations {
2116                            load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 0.0 }),
2117                            store: wgpu::StoreOp::Store,
2118                        },
2119                        depth_slice: None,
2120                    })],
2121                    ..Default::default()
2122                });
2123                p.set_pipeline(&self.blur_v_pipeline);
2124                p.set_bind_group(0, ctx_blur_bind_group_b, &[]);
2125                p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
2126                p.set_bind_group(2, &self.berserker_bind_group, &[]);
2127                p.draw(0..6, 0..1);
2128            }
2129        }
2130
2131        // ── Pass 3: Liquid Glass Elements ───────────────────────────────────
2132        {
2133            let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2134                label: Some("Surtr P3 Liquid Glass"),
2135                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2136                    view: ctx_scene_texture, // RENDER OVER THE OPAQUE BACKGROUND
2137                    resolve_target: None,
2138                    ops: wgpu::Operations {
2139                        load: wgpu::LoadOp::Load,
2140                        store: wgpu::StoreOp::Store,
2141                    },
2142                    depth_slice: None,
2143                })],
2144                depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
2145                    view: ctx_depth_texture_view,
2146                    depth_ops: Some(wgpu::Operations {
2147                        load: wgpu::LoadOp::Load,
2148                        store: wgpu::StoreOp::Store,
2149                    }),
2150                    stencil_ops: None,
2151                }),
2152                occlusion_query_set: None,
2153                timestamp_writes: None,
2154                multiview_mask: None,
2155            });
2156
2157            p.set_pipeline(&self.pipeline);
2158            p.set_vertex_buffer(0, self.vertex_buffer.slice(..));
2159            p.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
2160            p.set_bind_group(1, ctx_blur_env_bind_group_a, &[]); // Sample the freshly blurred backdrop
2161            p.set_bind_group(2, &self.berserker_bind_group, &[]);
2162
2163            for call in self.draw_calls.iter().filter(|c| c.is_glass) {
2164                let bg = if let Some(id) = call.texture_id {
2165                    if id == 0 {
2166                        &self.mega_atlas_bind_group
2167                    } else {
2168                        self.texture_bind_groups
2169                            .get(id as usize)
2170                            .unwrap_or(&self.dummy_texture_bind_group)
2171                    }
2172                } else {
2173                    &self.dummy_texture_bind_group
2174                };
2175                p.set_bind_group(0, bg, &[]);
2176                p.draw_indexed(
2177                    call.index_start..call.index_start + call.index_count,
2178                    0,
2179                    0..1,
2180                );
2181                self.telemetry.draw_calls += 1;
2182                self.telemetry.vertices += call.index_count;
2183            }
2184        }
2185
2186        // ── Pass 4: UI & Text Overlay ──────────────────────────────────────
2187        {
2188            let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2189                label: Some("Surtr P4 UI Layer"),
2190                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2191                    view: ctx_scene_texture,
2192                    resolve_target: None,
2193                    ops: wgpu::Operations {
2194                        load: wgpu::LoadOp::Load,
2195                        store: wgpu::StoreOp::Store,
2196                    },
2197                    depth_slice: None,
2198                })],
2199                depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
2200                    view: ctx_depth_texture_view,
2201                    depth_ops: Some(wgpu::Operations {
2202                        load: wgpu::LoadOp::Load,
2203                        store: wgpu::StoreOp::Store,
2204                    }),
2205                    stencil_ops: None,
2206                }),
2207                occlusion_query_set: None,
2208                timestamp_writes: None,
2209                multiview_mask: None,
2210            });
2211
2212            p.set_pipeline(&self.pipeline);
2213            p.set_vertex_buffer(0, self.vertex_buffer.slice(..));
2214            p.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
2215            p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
2216            p.set_bind_group(2, &self.berserker_bind_group, &[]);
2217
2218            for call in self.draw_calls.iter().filter(|c| c.is_ui) {
2219                let bg = if let Some(id) = call.texture_id {
2220                    if id == 0 {
2221                        &self.mega_atlas_bind_group
2222                    } else {
2223                        self.texture_bind_groups
2224                            .get(id as usize)
2225                            .unwrap_or(&self.dummy_texture_bind_group)
2226                    }
2227                } else {
2228                    &self.dummy_texture_bind_group
2229                };
2230                p.set_bind_group(0, bg, &[]);
2231                p.draw_indexed(
2232                    call.index_start..call.index_start + call.index_count,
2233                    0,
2234                    0..1,
2235                );
2236                self.telemetry.draw_calls += 1;
2237                self.telemetry.vertices += call.index_count;
2238            }
2239        }
2240
2241        // ── Pass 5: Bloom Extract (Complete Scene) ──────────────────────────
2242        {
2243            let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2244                label: Some("Surtr Bloom Extract"),
2245                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2246                    view: ctx_blur_texture_a,
2247                    resolve_target: None,
2248                    ops: wgpu::Operations {
2249                        load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 0.0 }),
2250                        store: wgpu::StoreOp::Store,
2251                    },
2252                    depth_slice: None,
2253                })],
2254                ..Default::default()
2255            });
2256            p.set_pipeline(&self.bloom_extract_pipeline);
2257            p.set_bind_group(0, ctx_scene_texture_bind_group, &[]);
2258            p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
2259            p.set_bind_group(2, &self.berserker_bind_group, &[]);
2260            p.draw(0..6, 0..1);
2261        }
2262
2263        // ── Pass 6: Blur Bloom ──────────────────────────────────────────────
2264        for _ in 0..2 {
2265            {
2266                let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2267                    label: Some("Bloom Blur H"),
2268                    color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2269                        view: ctx_blur_texture_b,
2270                        resolve_target: None,
2271                        ops: wgpu::Operations {
2272                            load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 0.0 }),
2273                            store: wgpu::StoreOp::Store,
2274                        },
2275                        depth_slice: None,
2276                    })],
2277                    ..Default::default()
2278                });
2279                p.set_pipeline(&self.blur_h_pipeline);
2280                p.set_bind_group(0, ctx_blur_bind_group_a, &[]);
2281                p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
2282                p.set_bind_group(2, &self.berserker_bind_group, &[]);
2283                p.draw(0..6, 0..1);
2284            }
2285            {
2286                let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2287                    label: Some("Bloom Blur V"),
2288                    color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2289                        view: ctx_blur_texture_a,
2290                        resolve_target: None,
2291                        ops: wgpu::Operations {
2292                            load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 0.0 }),
2293                            store: wgpu::StoreOp::Store,
2294                        },
2295                        depth_slice: None,
2296                    })],
2297                    ..Default::default()
2298                });
2299                p.set_pipeline(&self.blur_v_pipeline);
2300                p.set_bind_group(0, ctx_blur_bind_group_b, &[]);
2301                p.set_bind_group(1, &self.dummy_env_bind_group, &[]);
2302                p.set_bind_group(2, &self.berserker_bind_group, &[]);
2303                p.draw(0..6, 0..1);
2304            }
2305        }
2306
2307        // ── Pass 7: Composite & Tone Map ────────────────────────────────────
2308        {
2309            let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2310                label: Some("Surtr P7 Composite"),
2311                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2312                    view: &target_view,
2313                    resolve_target: None,
2314                    ops: wgpu::Operations {
2315                        load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
2316                        store: wgpu::StoreOp::Store,
2317                    },
2318                    depth_slice: None,
2319                })],
2320                ..Default::default()
2321            });
2322            p.set_pipeline(&self.composite_pipeline);
2323            // Headless doesn't use these bind groups in composite yet, or does it?
2324            // Wait, we need to use the headless versions.
2325            p.set_bind_group(0, ctx_scene_texture_bind_group, &[]);
2326            p.set_bind_group(1, ctx_blur_env_bind_group_a, &[]); // Bloom overlay
2327            p.set_bind_group(2, &self.berserker_bind_group, &[]);
2328            p.draw(0..6, 0..1);
2329            self.telemetry.draw_calls += 1;
2330        }
2331
2332        self.telemetry.frame_time_ms = self.last_frame_start.elapsed().as_secs_f32() * 1000.0;
2333        self.update_vram_telemetry();
2334        self.queue.submit(Some(encoder.finish()));
2335        if let Some(f) = surface_texture {
2336            f.present();
2337        }
2338    }
2339}
2340
2341impl cvkg_core::ElapsedTime for SurtrRenderer {
2342    fn delta_time(&self) -> f32 {
2343        self.current_scene.delta_time
2344    }
2345
2346    fn elapsed_time(&self) -> f32 {
2347        self.start_time.elapsed().as_secs_f32()
2348    }
2349}
2350
2351impl cvkg_core::Renderer for SurtrRenderer {
2352    /// fill_rect — Standard rectangle drawing method.
2353    fn fill_rect(&mut self, rect: Rect, color: [f32; 4]) {
2354        self.fill_rect_with_mode(rect, self.apply_opacity(color), 0, None);
2355    }
2356
2357    fn fill_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4]) {
2358        self.fill_rect_with_full_params(
2359            rect,
2360            self.apply_opacity(color),
2361            3,
2362            None,
2363            radius,
2364            Rect {
2365                x: 0.0,
2366                y: 0.0,
2367                width: 1.0,
2368                height: 1.0,
2369            },
2370        );
2371    }
2372
2373    fn fill_ellipse(&mut self, rect: Rect, color: [f32; 4]) {
2374        self.fill_rect_with_full_params(
2375            rect,
2376            self.apply_opacity(color),
2377            4,
2378            None,
2379            0.0,
2380            Rect {
2381                x: 0.0,
2382                y: 0.0,
2383                width: 1.0,
2384                height: 1.0,
2385            },
2386        );
2387    }
2388
2389    fn bifrost(&mut self, rect: Rect, blur: f32, _saturation: f32, opacity: f32) {
2390        // Calculate screen-space UVs for high-fidelity global refraction
2391        let screen_uv = Rect {
2392            x: rect.x / self.current_width() as f32,
2393            y: rect.y / self.current_height() as f32,
2394            width: rect.width / self.current_width() as f32,
2395            height: rect.height / self.current_height() as f32,
2396        };
2397        // Use mode 7 for high-fidelity background blur sampling
2398        // Use the blur parameter as corner radius for the glass panel
2399        self.fill_rect_with_full_params(rect, [1.0, 1.0, 1.0, opacity], 7, None, blur, screen_uv);
2400    }
2401
2402    fn gungnir(&mut self, rect: Rect, color: [f32; 4], radius: f32, intensity: f32) {
2403        // Create neon glow effect using additive blending
2404        // This renders a glowing aura around the element
2405        let center_x = rect.x + rect.width * 0.5;
2406        let center_y = rect.y + rect.height * 0.5;
2407        let max_dim = rect.width.max(rect.height) * 0.5 + radius;
2408        
2409        // Draw expanding glow layers
2410        for i in 0..8 {
2411            let alpha = intensity / (i as f32 + 1.0) * 0.3;
2412            let glow_color = [color[0], color[1], color[2], alpha];
2413            self.fill_rect_with_mode(
2414                Rect {
2415                    x: center_x - max_dim - i as f32 * 2.0,
2416                    y: center_y - max_dim - i as f32 * 2.0,
2417                    width: max_dim * 2.0 + i as f32 * 4.0,
2418                    height: max_dim * 2.0 + i as f32 * 4.0,
2419                },
2420                glow_color,
2421                8, // Mode for additive blending
2422                None,
2423            );
2424        }
2425    }
2426
2427    fn stroke_rect(&mut self, rect: Rect, color: [f32; 4], stroke_width: f32) {
2428        let c = self.apply_opacity(color);
2429        let hw = stroke_width;
2430        // Top, bottom, left, right edge bars
2431        self.fill_rect_with_mode(
2432            Rect {
2433                x: rect.x,
2434                y: rect.y,
2435                width: rect.width,
2436                height: hw,
2437            },
2438            c,
2439            1,
2440            None,
2441        );
2442        self.fill_rect_with_mode(
2443            Rect {
2444                x: rect.x,
2445                y: rect.y + rect.height - hw,
2446                width: rect.width,
2447                height: hw,
2448            },
2449            c,
2450            1,
2451            None,
2452        );
2453        self.fill_rect_with_mode(
2454            Rect {
2455                x: rect.x,
2456                y: rect.y,
2457                width: hw,
2458                height: rect.height,
2459            },
2460            c,
2461            1,
2462            None,
2463        );
2464        self.fill_rect_with_mode(
2465            Rect {
2466                x: rect.x + rect.width - hw,
2467                y: rect.y,
2468                width: hw,
2469                height: rect.height,
2470            },
2471            c,
2472            1,
2473            None,
2474        );
2475    }
2476
2477    fn stroke_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4], stroke_width: f32) {
2478        self.fill_rect_with_full_params(
2479            rect,
2480            self.apply_opacity(color),
2481            17,
2482            None,
2483            radius,
2484            Rect {
2485                x: stroke_width,
2486                y: 0.0,
2487                width: 0.0,
2488                height: 0.0,
2489            },
2490        );
2491    }
2492
2493    fn stroke_ellipse(&mut self, _rect: Rect, _color: [f32; 4], _stroke_width: f32) {
2494        // Future: Implement stroked SDFs.
2495    }
2496
2497    fn draw_linear_gradient(
2498        &mut self,
2499        rect: Rect,
2500        start_color: [f32; 4],
2501        end_color: [f32; 4],
2502        angle: f32,
2503    ) {
2504        self.fill_rect_with_full_params_and_slice(
2505            rect,
2506            self.apply_opacity(start_color),
2507            15,
2508            None,
2509            0.0,
2510            Rect {
2511                x: angle,
2512                y: 0.0,
2513                width: 1.0,
2514                height: 1.0,
2515            },
2516            end_color,
2517        );
2518    }
2519
2520    fn draw_radial_gradient(&mut self, rect: Rect, inner_color: [f32; 4], outer_color: [f32; 4]) {
2521        self.fill_rect_with_full_params_and_slice(
2522            rect,
2523            self.apply_opacity(inner_color),
2524            16,
2525            None,
2526            0.0,
2527            Rect {
2528                x: 0.0,
2529                y: 0.0,
2530                width: 1.0,
2531                height: 1.0,
2532            },
2533            outer_color,
2534        );
2535    }
2536
2537    fn draw_drop_shadow(
2538        &mut self,
2539        rect: Rect,
2540        radius: f32,
2541        color: [f32; 4],
2542        blur: f32,
2543        spread: f32,
2544    ) {
2545        let margin = blur + spread;
2546        let inflated = Rect {
2547            x: rect.x - margin,
2548            y: rect.y - margin,
2549            width: rect.width + margin * 2.0,
2550            height: rect.height + margin * 2.0,
2551        };
2552        // uv.x = total margin (for SDF offset), uv.y = blur width (for falloff)
2553        self.fill_rect_with_full_params(
2554            inflated,
2555            self.apply_opacity(color),
2556            18,
2557            None,
2558            radius,
2559            Rect {
2560                x: margin,
2561                y: blur,
2562                width: 0.0,
2563                height: 0.0,
2564            },
2565        );
2566    }
2567
2568    fn stroke_dashed_rounded_rect(
2569        &mut self,
2570        rect: Rect,
2571        radius: f32,
2572        color: [f32; 4],
2573        width: f32,
2574        dash: f32,
2575        gap: f32,
2576    ) {
2577        self.fill_rect_with_full_params(
2578            rect,
2579            self.apply_opacity(color),
2580            19,
2581            None,
2582            radius,
2583            Rect {
2584                x: width,
2585                y: dash,
2586                width: gap,
2587                height: 0.0,
2588            },
2589        );
2590    }
2591
2592    fn draw_9slice(
2593        &mut self,
2594        image_name: &str,
2595        rect: Rect,
2596        left: f32,
2597        top: f32,
2598        right: f32,
2599        bottom: f32,
2600    ) {
2601        let c = self.apply_opacity([1.0, 1.0, 1.0, 1.0]);
2602        let tid = self.get_texture_id(image_name);
2603        self.fill_rect_with_full_params(
2604            rect,
2605            c,
2606            20,
2607            tid,
2608            bottom,
2609            Rect {
2610                x: left,
2611                y: top,
2612                width: right,
2613                height: 0.0,
2614            },
2615        );
2616    }
2617
2618    fn draw_line(
2619        &mut self,
2620        x1: f32,
2621        y1: f32,
2622        x2: f32,
2623        y2: f32,
2624        color: [f32; 4],
2625        stroke_width: f32,
2626    ) {
2627        let dx = x2 - x1;
2628        let dy = y2 - y1;
2629        let len = (dx * dx + dy * dy).sqrt();
2630        if len < 0.001 {
2631            return;
2632        }
2633
2634        let c = self.apply_opacity(color);
2635        let tid = self.get_texture_id("__mega_atlas");
2636
2637        self.fill_rect_with_mode(
2638            Rect {
2639                x: (x1 + x2) / 2.0 - len / 2.0,
2640                y: (y1 + y2) / 2.0 - stroke_width / 2.0,
2641                width: len,
2642                height: stroke_width,
2643            },
2644            c,
2645            1, // Gungnir Mode for glowing lines
2646            tid,
2647        );
2648    }
2649
2650    fn draw_image(&mut self, image_name: &str, rect: Rect) {
2651        let tid = self.get_texture_id("__mega_atlas");
2652        let uv_rect = self.image_uv_registry.get(image_name).copied().unwrap_or(Rect { x: 0.0, y: 0.0, width: 1.0, height: 1.0 });
2653        self.fill_rect_with_full_params(
2654            rect,
2655            [1.0, 1.0, 1.0, 1.0],
2656            2,
2657            tid,
2658            0.0,
2659            uv_rect,
2660        );
2661    }
2662
2663    fn draw_text(&mut self, text: &str, x: f32, y: f32, size: f32, color: [f32; 4]) {
2664        // High-DPI: Shape and rasterize at the physical scale factor for maximum sharpness.
2665        let scaled_size = size * self.current_scale_factor();
2666        let shaped = self.text_engine.shape(text, "sans-serif", scaled_size);
2667        let c = self.apply_opacity(color);
2668
2669        for glyph in shaped.glyphs {
2670            let cache_key = glyph.cache_key;
2671
2672            let (uv_rect, w, h) = if let Some(info) = self.text_cache.get(&cache_key) {
2673                *info
2674            } else {
2675                if let Some(image) = self.text_engine.rasterize(cache_key) {
2676                    let gw = image.width;
2677                    let gh = image.height;
2678
2679                    if let Some((nx, ny)) = self.atlas_packer.pack(gw, gh) {
2680                        let mut rgba_data = Vec::with_capacity((gw * gh * 4) as usize);
2681                        for alpha in &image.data {
2682                            rgba_data.push(255);
2683                            rgba_data.push(255);
2684                            rgba_data.push(255);
2685                            rgba_data.push(*alpha);
2686                        }
2687
2688                        self.queue.write_texture(
2689                            wgpu::TexelCopyTextureInfo {
2690                                texture: &self.mega_atlas_tex,
2691                                mip_level: 0,
2692                                origin: wgpu::Origin3d { x: nx, y: ny, z: 0 },
2693                                aspect: wgpu::TextureAspect::All,
2694                            },
2695                            &rgba_data,
2696                            wgpu::TexelCopyBufferLayout {
2697                                offset: 0,
2698                                bytes_per_row: Some(gw * 4),
2699                                rows_per_image: Some(gh),
2700                            },
2701                            wgpu::Extent3d {
2702                                width: gw,
2703                                height: gh,
2704                                depth_or_array_layers: 1,
2705                            },
2706                        );
2707
2708                        let info = (
2709                            Rect {
2710                                x: nx as f32 / 4096.0,
2711                                y: ny as f32 / 4096.0,
2712                                width: gw as f32 / 4096.0,
2713                                height: gh as f32 / 4096.0,
2714                            },
2715                            gw as f32,
2716                            gh as f32,
2717                        );
2718                        self.text_cache.insert(cache_key, info);
2719                        info
2720                    } else {
2721                        (Rect::zero(), 0.0, 0.0)
2722                    }
2723                } else {
2724                    (Rect::zero(), 0.0, 0.0)
2725                }
2726            };
2727
2728            if w > 0.0 {
2729                // Map physical glyph dimensions and positions back to logical units
2730                // so the logical orthographic projection matrix places them correctly.
2731                let glyph_rect = Rect {
2732                    x: x + glyph.x / self.current_scale_factor(),
2733                    y: y + glyph.y / self.current_scale_factor(),
2734                    width: w / self.current_scale_factor(),
2735                    height: h / self.current_scale_factor(),
2736                };
2737                let tid = self.get_texture_id("__mega_atlas");
2738                self.fill_rect_with_full_params(glyph_rect, c, 6, tid, 0.0, uv_rect);
2739            }
2740        }
2741    }
2742
2743    /// measure_text — Calculates the dimensions of a text string without rendering.
2744    fn measure_text(&mut self, text: &str, size: f32) -> (f32, f32) {
2745        let shaped = self.text_engine.shape(text, "sans-serif", size);
2746        (shaped.width, shaped.height)
2747    }
2748
2749    fn draw_texture(&mut self, texture_id: u32, rect: Rect) {
2750        self.fill_rect_with_full_params(
2751            rect,
2752            [1.0, 1.0, 1.0, 1.0],
2753            2,
2754            Some(texture_id),
2755            0.0,
2756            Rect {
2757                x: 0.0,
2758                y: 0.0,
2759                width: 1.0,
2760                height: 1.0,
2761            },
2762        );
2763    }
2764
2765    /// load_image — Proactively pushes a raw asset into the Mega-Atlas.
2766    fn load_image(&mut self, name: &str, data: &[u8]) {
2767        if self.image_uv_registry.contains_key(name) {
2768            return;
2769        }
2770        let img_result = image::load_from_memory(data);
2771        let img = match img_result {
2772            Ok(img) => img.to_rgba8(),
2773            Err(e) => {
2774                eprintln!("Failed to load image {}: {}", name, e);
2775                image::RgbaImage::from_pixel(1, 1, image::Rgba([0, 0, 0, 255]))
2776            }
2777        };
2778        let (width, height) = img.dimensions();
2779        
2780        // Pack into Mega-Atlas
2781        if let Some((x, y)) = self.atlas_packer.pack(width, height) {
2782            let size = wgpu::Extent3d {
2783                width,
2784                height,
2785                depth_or_array_layers: 1,
2786            };
2787            self.queue.write_texture(
2788                wgpu::TexelCopyTextureInfo {
2789                    texture: &self.mega_atlas_tex,
2790                    mip_level: 0,
2791                    origin: wgpu::Origin3d { x, y, z: 0 },
2792                    aspect: wgpu::TextureAspect::All,
2793                },
2794                &img,
2795                wgpu::TexelCopyBufferLayout {
2796                    offset: 0,
2797                    bytes_per_row: Some(4 * width),
2798                    rows_per_image: Some(height),
2799                },
2800                size,
2801            );
2802
2803            // Store UV rect (logical 0-1)
2804            let uv_rect = Rect {
2805                x: x as f32 / 4096.0,
2806                y: y as f32 / 4096.0,
2807                width: width as f32 / 4096.0,
2808                height: height as f32 / 4096.0,
2809            };
2810            self.image_uv_registry.insert(name.to_string(), uv_rect);
2811            self.texture_registry.insert(name.to_string(), 0);
2812        } else {
2813            eprintln!("Mega-Atlas is FULL! Could not pack image: {}", name);
2814        }
2815    }
2816
2817    fn push_clip_rect(&mut self, rect: Rect) {
2818        self.clip_stack.push(rect);
2819    }
2820
2821    fn pop_clip_rect(&mut self) {
2822        self.clip_stack.pop();
2823    }
2824
2825    fn push_opacity(&mut self, opacity: f32) {
2826        let current = self.opacity_stack.last().copied().unwrap_or(1.0);
2827        self.opacity_stack.push(current * opacity);
2828    }
2829
2830    fn pop_opacity(&mut self) {
2831        self.opacity_stack.pop();
2832    }
2833
2834    fn push_shadow(&mut self, radius: f32, color: [f32; 4], offset: [f32; 2]) {
2835        self.shadow_stack.push(ShadowState {
2836            radius,
2837            color,
2838            _offset: offset,
2839        });
2840    }
2841
2842    fn pop_shadow(&mut self) {
2843        self.shadow_stack.pop();
2844    }
2845
2846    fn push_transform(&mut self, translation: [f32; 2], scale: [f32; 2], rotation: f32) {
2847        let (current_t, current_s, current_r) = self.transform_stack.last().copied().unwrap_or(([0.0, 0.0], [1.0, 1.0], 0.0));
2848        
2849        // Combine transforms (simplified: this doesn't handle full matrix multiplication yet,
2850        // but for basic UI nesting it's often sufficient to just add translation and multiply scale).
2851        // A full implementation would use mat3x3.
2852        let new_t = [current_t[0] + translation[0] * current_s[0], current_t[1] + translation[1] * current_s[1]];
2853        let new_s = [current_s[0] * scale[0], current_s[1] * scale[1]];
2854        let new_r = current_r + rotation;
2855        
2856        self.transform_stack.push((new_t, new_s, new_r));
2857    }
2858
2859    fn pop_transform(&mut self) {
2860        self.transform_stack.pop();
2861    }
2862
2863    fn set_theme(&mut self, theme: ColorTheme) {
2864
2865        self.current_theme = theme;
2866        self.queue
2867            .write_buffer(&self.theme_buffer, 0, bytemuck::bytes_of(&theme));
2868    }
2869
2870    fn set_rage(&mut self, rage: f32) {
2871        self.current_scene.berzerker_rage = rage;
2872        // scene_buffer is updated every frame in begin_frame, so no need to write here
2873    }
2874
2875    fn trigger_shatter_event(&mut self, origin: [f32; 2], force: f32) {
2876        self.current_scene.shatter_origin = origin;
2877        self.current_scene.shatter_time = self.current_scene.time;
2878        self.current_scene.shatter_force = force;
2879    }
2880
2881    /// push_mjolnir_slice — Pushes a geometric clipping plane onto the stack.
2882    /// All subsequent draw calls will be sliced by this plane until it is popped.
2883    fn push_mjolnir_slice(&mut self, angle: f32, offset: f32) {
2884        self.slice_stack.push((angle, offset));
2885    }
2886
2887    /// pop_mjolnir_slice — Removes the top-most geometric clipping plane from the stack.
2888    fn pop_mjolnir_slice(&mut self) {
2889        self.slice_stack.pop();
2890    }
2891
2892    fn mjolnir_shatter(&mut self, rect: Rect, pieces: u32, force: f32, color: [f32; 4]) {
2893        self.shatter_internal(rect, pieces, force, color, 8);
2894    }
2895
2896    fn mjolnir_fluid_shatter(&mut self, rect: Rect, pieces: u32, force: f32, color: [f32; 4]) {
2897        self.shatter_internal(rect, pieces, force, color, 11);
2898    }
2899
2900    fn draw_mjolnir_bolt(&mut self, from: [f32; 2], to: [f32; 2], color: [f32; 4]) {
2901        self.recursive_bolt(from, to, 4, color);
2902    }
2903
2904    fn upload_data_texture(&mut self, id: &str, data: &[f32], width: u32, height: u32) {
2905        let size = wgpu::Extent3d {
2906            width,
2907            height,
2908            depth_or_array_layers: 1,
2909        };
2910        let texture = self.device.create_texture(&wgpu::TextureDescriptor {
2911            label: Some(id),
2912            size,
2913            mip_level_count: 1,
2914            sample_count: 1,
2915            dimension: wgpu::TextureDimension::D2,
2916            format: wgpu::TextureFormat::R32Float,
2917            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
2918            view_formats: &[],
2919        });
2920        self.queue.write_texture(
2921            wgpu::TexelCopyTextureInfo {
2922                texture: &texture,
2923                mip_level: 0,
2924                origin: wgpu::Origin3d::ZERO,
2925                aspect: wgpu::TextureAspect::All,
2926            },
2927            bytemuck::cast_slice(data),
2928            wgpu::TexelCopyBufferLayout {
2929                offset: 0,
2930                bytes_per_row: Some(4 * width),
2931                rows_per_image: Some(height),
2932            },
2933            size,
2934        );
2935        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
2936        let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
2937            address_mode_u: wgpu::AddressMode::ClampToEdge,
2938            address_mode_v: wgpu::AddressMode::ClampToEdge,
2939            mag_filter: wgpu::FilterMode::Linear,
2940            ..Default::default()
2941        });
2942        let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2943            layout: &self.texture_bind_group_layout,
2944            entries: &[
2945                wgpu::BindGroupEntry {
2946                    binding: 0,
2947                    resource: wgpu::BindingResource::TextureView(&view),
2948                },
2949                wgpu::BindGroupEntry {
2950                    binding: 1,
2951                    resource: wgpu::BindingResource::Sampler(&sampler),
2952                },
2953            ],
2954            label: Some(id),
2955        });
2956        self.texture_bind_groups.push(bind_group);
2957        let tid = (self.texture_bind_groups.len() - 1) as u32;
2958        self.texture_registry.insert(id.to_string(), tid);
2959    }
2960
2961    fn draw_heatmap(&mut self, texture_id: &str, rect: Rect, _palette: &str) {
2962        let tid = self.get_texture_id(texture_id);
2963        self.fill_rect_with_mode(rect, [1.0, 1.0, 1.0, 1.0], 12, tid);
2964    }
2965
2966    fn draw_mesh(&mut self, mesh: &Mesh, color: [f32; 4], transform: glam::Mat4) {
2967        let base_idx = self.vertices.len() as u32;
2968        let screen = [self.current_width() as f32, self.current_height() as f32];
2969
2970        for i in 0..mesh.vertices.len() {
2971            let pos = transform.transform_point3(glam::Vec3::from(mesh.vertices[i]));
2972            let norm = transform.transform_vector3(glam::Vec3::from(mesh.normals[i]));
2973
2974            let (translation, scale_transform, rotation) = self.get_current_transform();
2975            self.vertices.push(Vertex {
2976                position: pos.to_array(),
2977                normal: norm.to_array(),
2978                uv: [0.0, 0.0],
2979                color,
2980                mode: 13, // Mode 13: 3D Surface
2981                radius: 0.0,
2982                slice: [0.0, 0.0, 0.0, 1.0],
2983                logical: [0.0, 0.0],
2984                size: [0.0, 0.0],
2985                screen,
2986                clip: [-10000.0, -10000.0, 20000.0, 20000.0],
2987                translation,
2988                scale: scale_transform,
2989                rotation,
2990                _pad: 0.0,
2991            });
2992        }
2993
2994        for idx in &mesh.indices {
2995            self.indices.push(base_idx + idx);
2996        }
2997
2998        if self.draw_calls.is_empty() || self.current_texture_id.is_some() {
2999            self.current_texture_id = None;
3000            self.draw_calls.push(DrawCall {
3001                texture_id: None,
3002                scissor_rect: self.clip_stack.last().copied(),
3003                index_start: (self.indices.len() as u32) - (mesh.indices.len() as u32),
3004                index_count: mesh.indices.len() as u32,
3005                is_glass: false,
3006                is_ui: false,
3007            });
3008        } else {
3009            self.draw_calls.last_mut().unwrap().index_count += mesh.indices.len() as u32;
3010        }
3011    }
3012
3013    fn register_shared_element(&mut self, id: &str, rect: Rect) {
3014        self.shared_elements.insert(id.to_string(), rect);
3015    }
3016
3017    fn set_z_index(&mut self, z: f32) {
3018        self.current_z = z;
3019    }
3020
3021    fn get_z_index(&self) -> f32 {
3022        self.current_z
3023    }
3024
3025
3026    fn request_redraw(&mut self) {
3027        self.redraw_requested = true;
3028    }
3029}
3030
3031// --- SVG Helpers ---
3032
3033fn usvg_to_lyon(path: &usvg::Path) -> lyon::path::Path {
3034    let mut builder = lyon::path::Path::builder();
3035    for segment in path.data().segments() {
3036        match segment {
3037            usvg::tiny_skia_path::PathSegment::MoveTo(p) => {
3038                builder.begin(lyon::math::point(p.x, p.y));
3039            }
3040            usvg::tiny_skia_path::PathSegment::LineTo(p) => {
3041                builder.line_to(lyon::math::point(p.x, p.y));
3042            }
3043            usvg::tiny_skia_path::PathSegment::QuadTo(p1, p) => {
3044                builder.quadratic_bezier_to(
3045                    lyon::math::point(p1.x, p1.y),
3046                    lyon::math::point(p.x, p.y),
3047                );
3048            }
3049            usvg::tiny_skia_path::PathSegment::CubicTo(p1, p2, p) => {
3050                builder.cubic_bezier_to(
3051                    lyon::math::point(p1.x, p1.y),
3052                    lyon::math::point(p2.x, p2.y),
3053                    lyon::math::point(p.x, p.y),
3054                );
3055            }
3056            usvg::tiny_skia_path::PathSegment::Close => {
3057                builder.end(true);
3058            }
3059        }
3060    }
3061    builder.build()
3062}
3063
3064impl SurtrRenderer {
3065    fn get_current_transform(&self) -> ([f32; 2], [f32; 2], f32) {
3066        self.transform_stack
3067            .last()
3068            .cloned()
3069            .unwrap_or(([0.0, 0.0], [1.0, 1.0], 0.0))
3070    }
3071}
3072
3073struct SceneVertexConstructor {
3074    color: [f32; 4],
3075    translation: [f32; 2],
3076    scale: [f32; 2],
3077    rotation: f32,
3078}
3079
3080impl FillVertexConstructor<Vertex> for SceneVertexConstructor {
3081    fn new_vertex(&mut self, vertex: FillVertex) -> Vertex {
3082        Vertex {
3083            position: [vertex.position().x, vertex.position().y, 0.0],
3084            normal: [0.0, 0.0, 1.0],
3085            uv: [0.0, 0.0],
3086            color: self.color,
3087            mode: 0,
3088            radius: 0.0,
3089            slice: [0.0, 0.0, 0.0, 1.0],
3090            logical: [vertex.position().x, vertex.position().y],
3091            size: [1.0, 1.0],
3092            screen: [0.0, 0.0],
3093            clip: [-10000.0, -10000.0, 20000.0, 20000.0],
3094            translation: self.translation,
3095            scale: self.scale,
3096            rotation: self.rotation,
3097            _pad: 0.0,
3098        }
3099    }
3100}
3101
3102impl Drop for SurtrRenderer {
3103    fn drop(&mut self) {
3104        // Ensure GPU is idle before dropping to avoid Swapchain semaphore panics
3105        let _ = self.device.poll(wgpu::PollType::Wait {
3106            submission_index: None,
3107            timeout: None,
3108        });
3109    }
3110}
3111
3112impl cvkg_core::FrameRenderer<wgpu::CommandEncoder> for SurtrRenderer {
3113    fn begin_frame(&mut self) -> wgpu::CommandEncoder {
3114        cvkg_core::begin_render_phase();
3115        let id = self.current_window.expect("No target window set for frame. Call set_target_window first.");
3116        self.begin_frame(id)
3117    }
3118
3119    fn end_frame(&mut self, encoder: wgpu::CommandEncoder) {
3120        self.end_frame(encoder);
3121        cvkg_core::end_render_phase();
3122    }
3123}
3124
3125impl SurtrRenderer {
3126    /// Returns the current effective opacity (product of all stacked values).
3127    fn apply_opacity(&self, mut color: [f32; 4]) -> [f32; 4] {
3128        if let Some(&alpha) = self.opacity_stack.last() {
3129            color[3] *= alpha;
3130        }
3131        color
3132    }
3133
3134    /// load_svg — Parses an SVG file and tessellates its paths into GPU triangles.
3135    pub fn load_svg(&mut self, name: &str, data: &[u8]) {
3136        let opt = usvg::Options::default();
3137        let tree = usvg::Tree::from_data(data, &opt).expect("Failed to parse SVG");
3138        
3139        let view_box = Rect {
3140            x: 0.0,
3141            y: 0.0,
3142            width: tree.size().width(),
3143            height: tree.size().height(),
3144        };
3145
3146        let mut vertices = Vec::new();
3147        let mut indices = Vec::new();
3148        let mut tessellator = FillTessellator::new();
3149
3150        for child in tree.root().children() {
3151            self.tessellate_node(child, &mut tessellator, &mut vertices, &mut indices);
3152        }
3153
3154        self.svg_cache.insert(name.to_string(), SvgModel {
3155            vertices,
3156            indices,
3157            view_box,
3158        });
3159    }
3160
3161    fn tessellate_node(&self, node: &usvg::Node, tessellator: &mut FillTessellator, vertices: &mut Vec<Vertex>, indices: &mut Vec<u32>) {
3162        if let usvg::Node::Group(ref group) = *node {
3163            for child in group.children() {
3164                self.tessellate_node(child, tessellator, vertices, indices);
3165            }
3166        } else if let usvg::Node::Path(ref path) = *node
3167            && let Some(fill) = path.fill() {
3168                let color = match fill.paint() {
3169                    usvg::Paint::Color(c) => [
3170                        c.red as f32 / 255.0,
3171                        c.green as f32 / 255.0,
3172                        c.blue as f32 / 255.0,
3173                        fill.opacity().get(),
3174                    ],
3175                    _ => [1.0, 1.0, 1.0, 1.0],
3176                };
3177
3178                let lyon_path = usvg_to_lyon(path);
3179                let mut buffers: VertexBuffers<Vertex, u32> = VertexBuffers::new();
3180                let base_vertex_idx = vertices.len() as u32;
3181
3182                tessellator.tessellate_path(
3183                    &lyon_path,
3184                    &FillOptions::default(),
3185                    &mut BuffersBuilder::new(&mut buffers, SceneVertexConstructor {
3186                        color,
3187                        translation: [0.0, 0.0],
3188                        scale: [1.0, 1.0],
3189                        rotation: 0.0,
3190                    }),
3191                ).unwrap();
3192
3193                vertices.extend(buffers.vertices);
3194                for idx in buffers.indices {
3195                    indices.push(base_vertex_idx + idx);
3196                }
3197        }
3198    }
3199
3200    /// draw_svg — Renders a pre-loaded SVG icon at the specified logical rect.
3201    pub fn draw_svg(&mut self, name: &str, rect: Rect, color: Option<[f32; 4]>, mode: u32) {
3202        let model = if let Some(m) = self.svg_cache.get(name) {
3203            m.clone()
3204        } else {
3205            return;
3206        };
3207
3208        let _scale_x = rect.width / model.view_box.width;
3209        let _scale_y = rect.height / model.view_box.height;
3210        let base_idx = self.vertices.len() as u32;
3211        let screen = [self.current_width() as f32, self.current_height() as f32];
3212        let clip_rect = self.clip_stack.last().copied().unwrap_or(cvkg_core::Rect {
3213            x: -10000.0,
3214            y: -10000.0,
3215            width: 20000.0,
3216            height: 20000.0,
3217        });
3218        let clip = [clip_rect.x, clip_rect.y, clip_rect.width, clip_rect.height];
3219        let scale = self.current_scale_factor();
3220        let snap = |v: f32| (v * scale).round() / scale;
3221
3222        for v in &model.vertices {
3223            let mut v = *v;
3224            let rel_x = (v.position[0] - model.view_box.x) / model.view_box.width;
3225            let rel_y = (v.position[1] - model.view_box.y) / model.view_box.height;
3226            
3227            v.position[0] = snap(rect.x + rel_x * rect.width);
3228            v.position[1] = snap(rect.y + rel_y * rect.height);
3229            v.position[2] = self.current_z;
3230            v.logical = [v.position[0], v.position[1]];
3231            v.screen = screen;
3232            v.clip = clip;
3233            v.mode = mode;
3234            
3235            if let Some(override_color) = color {
3236                v.color = self.apply_opacity(override_color);
3237            } else {
3238                v.color = self.apply_opacity(v.color);
3239            }
3240            self.vertices.push(v);
3241        }
3242
3243        for idx in &model.indices {
3244            self.indices.push(base_idx + *idx);
3245        }
3246
3247        let is_ui = true;
3248        let is_glass = mode == 7;
3249        let tid = self.get_texture_id("__mega_atlas");
3250
3251        let last_call = self.draw_calls.last();
3252        let needs_new_call = self.draw_calls.is_empty()
3253            || self.current_texture_id != tid
3254            || last_call.unwrap().scissor_rect != self.clip_stack.last().copied()
3255            || last_call.unwrap().is_glass != is_glass
3256            || last_call.unwrap().is_ui != is_ui;
3257
3258        if needs_new_call {
3259            self.current_texture_id = tid;
3260            self.draw_calls.push(DrawCall {
3261                texture_id: tid,
3262                scissor_rect: self.clip_stack.last().copied(),
3263                index_start: (self.indices.len() - model.indices.len()) as u32,
3264                index_count: 0,
3265                is_glass,
3266                is_ui,
3267            });
3268        }
3269
3270        if let Some(call) = self.draw_calls.last_mut() {
3271            call.index_count += model.indices.len() as u32;
3272        }
3273    }
3274
3275    /// forge_headless — Initializes Surtr without a window for visual regression testing.
3276    pub async fn forge_headless(width: u32, height: u32) -> Self {
3277        let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
3278            backends: wgpu::Backends::all(),
3279            flags: wgpu::InstanceFlags::default(),
3280            backend_options: wgpu::BackendOptions::default(),
3281            display: None,
3282            memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
3283        });
3284
3285        let adapter = instance
3286            .request_adapter(&wgpu::RequestAdapterOptions {
3287                power_preference: wgpu::PowerPreference::HighPerformance,
3288                compatible_surface: None,
3289                force_fallback_adapter: false,
3290            })
3291            .await
3292            .expect("Failed to find a suitable GPU for Surtr");
3293
3294        let (device, queue) = adapter
3295            .request_device(&wgpu::DeviceDescriptor {
3296                label: Some("Surtr Headless Forge"),
3297                required_features: wgpu::Features::empty(),
3298                required_limits: wgpu::Limits::default(),
3299                memory_hints: wgpu::MemoryHints::default(),
3300                experimental_features: wgpu::ExperimentalFeatures::disabled(),
3301                trace: wgpu::Trace::Off,
3302            })
3303            .await
3304            .expect("Failed to create Surtr device");
3305
3306        let instance = Arc::new(instance);
3307        let adapter = Arc::new(adapter);
3308        let device = Arc::new(device);
3309        let queue = Arc::new(queue);
3310
3311        Self::forge_internal(
3312            instance,
3313            adapter,
3314            device,
3315            queue,
3316            None,
3317            Some((width, height, wgpu::TextureFormat::Rgba8UnormSrgb))
3318        ).await
3319    }
3320
3321    /// capture_frame — Read back the rendered frame as a byte buffer (RGBA8).
3322    pub async fn capture_frame(&self) -> Vec<u8> {
3323        let ctx = self.headless_context.as_ref().expect("Headless context required for capture");
3324        let u32_size = std::mem::size_of::<u32>() as u32;
3325        let width = ctx.width;
3326        let height = ctx.height;
3327        let bytes_per_row = width * u32_size;
3328        let padding = (256 - (bytes_per_row % 256)) % 256;
3329        let padded_bytes_per_row = bytes_per_row + padding;
3330        
3331        let output_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
3332            label: Some("Capture Buffer"),
3333            size: (padded_bytes_per_row as u64 * height as u64),
3334            usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
3335            mapped_at_creation: false,
3336        });
3337
3338        let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
3339            label: Some("Capture Encoder"),
3340        });
3341
3342        encoder.copy_texture_to_buffer(
3343            wgpu::TexelCopyTextureInfo {
3344                texture: &ctx.output_texture,
3345                mip_level: 0,
3346                origin: wgpu::Origin3d::ZERO,
3347                aspect: wgpu::TextureAspect::All,
3348            },
3349            wgpu::TexelCopyBufferInfo {
3350                buffer: &output_buffer,
3351                layout: wgpu::TexelCopyBufferLayout {
3352                    offset: 0,
3353                    bytes_per_row: Some(padded_bytes_per_row),
3354                    rows_per_image: Some(height),
3355                },
3356            },
3357            wgpu::Extent3d {
3358                width: width,
3359                height: height,
3360                depth_or_array_layers: 1,
3361            },
3362        );
3363
3364        self.queue.submit(Some(encoder.finish()));
3365
3366        let buffer_slice = output_buffer.slice(..);
3367        let (sender, receiver) = futures::channel::oneshot::channel();
3368        buffer_slice.map_async(wgpu::MapMode::Read, move |v| {
3369            let _ = sender.send(v);
3370        });
3371
3372        let _ = self.device.poll(wgpu::PollType::Wait {
3373            submission_index: None,
3374            timeout: None,
3375        });
3376
3377        if let Ok(Ok(_)) = receiver.await {
3378            let data = buffer_slice.get_mapped_range();
3379            let mut result = Vec::with_capacity((width * height * 4) as usize);
3380            
3381            for y in 0..height {
3382                let start = (y * padded_bytes_per_row) as usize;
3383                let end = start + bytes_per_row as usize;
3384                result.extend_from_slice(&data[start..end]);
3385            }
3386            
3387            drop(data);
3388            output_buffer.unmap();
3389            result
3390        } else {
3391            panic!("Failed to capture frame")
3392        }
3393    }
3394
3395    fn current_width(&self) -> u32 {
3396        if let Some(id) = self.current_window {
3397            self.surfaces.get(&id).unwrap().config.width
3398        } else {
3399            self.headless_context.as_ref().unwrap().width
3400        }
3401    }
3402
3403    fn current_height(&self) -> u32 {
3404        if let Some(id) = self.current_window {
3405            self.surfaces.get(&id).unwrap().config.height
3406        } else {
3407            self.headless_context.as_ref().unwrap().height
3408        }
3409    }
3410
3411    fn current_scale_factor(&self) -> f32 {
3412        if let Some(id) = self.current_window {
3413            self.surfaces.get(&id).unwrap().scale_factor
3414        } else {
3415            self.headless_context.as_ref().unwrap().scale_factor
3416        }
3417    }
3418}
3419