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