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