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
34use cvkg_core::{Rect, ColorTheme, SceneUniforms, Mesh};
35use std::sync::Arc;
36
37// ShieldWall — re-export AccessKit types so callers can build tree updates
38// without depending on accesskit directly.
39pub use accesskit::{
40    ActionHandler, ActionRequest, ActivationHandler, DeactivationHandler,
41    Node, NodeId, Role, Tree, TreeId, TreeUpdate,
42};
43pub use accesskit_winit::Adapter as ShieldWallAdapter;
44
45
46#[repr(C)]
47#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
48pub struct Vertex {
49    pub position: [f32; 3],
50    pub normal:   [f32; 3],
51    pub uv:       [f32; 2],
52    pub color:    [f32; 4],
53    pub mode:     u32,
54    pub radius:   f32,
55    pub slice:    [f32; 4],
56    pub logical:  [f32; 2],
57    pub size:     [f32; 2],
58    pub screen:   [f32; 2],
59    pub clip:     [f32; 4], // [x, y, width, height]
60}
61
62/// Represents a single batched GPU draw call.
63/// Batches are broken whenever the active texture or primitive mode changes.
64#[derive(Debug, Clone)]
65struct DrawCall {
66    pub texture_id: Option<u32>,
67    pub scissor_rect: Option<Rect>,
68    pub index_start: u32,
69    pub index_count: u32,
70    pub is_glass: bool,
71    pub is_ui: bool,
72}
73
74impl Vertex {
75    const ATTRIBUTES: [wgpu::VertexAttribute; 11] = wgpu::vertex_attr_array![
76        0 => Float32x3, // position
77        1 => Float32x3, // normal
78        2 => Float32x2, // uv
79        3 => Float32x4, // color
80        4 => Uint32,    // mode
81        5 => Float32,   // radius
82        6 => Float32x4, // slice
83        7 => Float32x2, // logical
84        8 => Float32x2, // size
85        9 => Float32x2, // screen
86        10 => Float32x4 // clip
87    ];
88
89    fn desc() -> wgpu::VertexBufferLayout<'static> {
90        wgpu::VertexBufferLayout {
91            array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
92            step_mode: wgpu::VertexStepMode::Vertex,
93            attributes: &Self::ATTRIBUTES,
94        }
95    }
96}
97
98/// SurtrRenderer implements the high-performance GPU backend.
99pub struct SurtrRenderer {
100    device: Arc<wgpu::Device>,
101    queue: Arc<wgpu::Queue>,
102    surface: wgpu::Surface<'static>,
103    config: wgpu::SurfaceConfiguration,
104
105    
106    // Text Forge
107    #[allow(dead_code)]
108    font_system: cosmic_text::FontSystem,
109    #[allow(dead_code)]
110    swash_cache: cosmic_text::SwashCache,
111    text_atlas_tex: wgpu::Texture,
112    #[allow(dead_code)]
113    text_atlas_view: wgpu::TextureView,
114    #[allow(dead_code)]
115    text_sampler: wgpu::Sampler,
116    text_cache: std::collections::HashMap<cosmic_text::CacheKey, (Rect, f32, f32)>,
117    text_atlas_pos: (u32, u32),
118    
119    // Niflheim Resources
120    dummy_bind_group: wgpu::BindGroup,
121    texture_bind_group_layout: wgpu::BindGroupLayout,
122    texture_bind_groups: Vec<wgpu::BindGroup>,
123    texture_registry: std::collections::HashMap<String, u32>,
124    shared_elements: std::collections::HashMap<String, cvkg_core::Rect>,
125
126    // The Forge's Anvil (GPU Buffers)
127    vertex_buffer: wgpu::Buffer,
128    index_buffer: wgpu::Buffer,
129    vertices: Vec<Vertex>,
130    indices: Vec<u32>,
131    draw_calls: Vec<DrawCall>,
132    current_texture_id: Option<u32>,
133
134    // Opacity stack: each push multiplies into the current effective alpha.
135    opacity_stack: Vec<f32>,
136    // Clip rect stack: used for batched scissoring.
137    clip_stack: Vec<Rect>,
138    // Mjolnir Slice stack: (angle, offset)
139    slice_stack: Vec<(f32, f32)>,
140
141    // The Forge's Heart (Berserker State)
142    theme_buffer: wgpu::Buffer,
143    scene_buffer: wgpu::Buffer,
144    berserker_bind_group: wgpu::BindGroup,
145    #[allow(dead_code)]
146    berserker_bind_group_layout: wgpu::BindGroupLayout,
147    start_time: std::time::Instant,
148    current_theme: ColorTheme,
149    current_scene: SceneUniforms,
150
151    // Muspelheim Pipelines
152    pipeline: wgpu::RenderPipeline,
153    background_pipeline: wgpu::RenderPipeline,
154    bloom_extract_pipeline: wgpu::RenderPipeline,
155    blur_h_pipeline: wgpu::RenderPipeline,
156    blur_v_pipeline: wgpu::RenderPipeline,
157    composite_pipeline: wgpu::RenderPipeline,
158
159    // Muspelheim Textures & Bind Groups
160    blur_texture_a: wgpu::TextureView,
161    blur_texture_b: wgpu::TextureView,
162    blur_bind_group_a: wgpu::BindGroup,
163    blur_bind_group_b: wgpu::BindGroup,
164    scene_texture: wgpu::TextureView,
165    scene_bind_group: wgpu::BindGroup,
166    scene_texture_bind_group: wgpu::BindGroup,
167}
168
169const MAX_VERTICES: usize = 100_000;
170const MAX_INDICES: usize = 150_000;
171
172impl SurtrRenderer {
173    /// Forge a new SurtrRenderer from a winit window.
174    pub async fn forge(window: Arc<winit::window::Window>) -> Self {
175        let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
176            backends: wgpu::Backends::all(),
177            flags: wgpu::InstanceFlags::default(),
178            backend_options: wgpu::BackendOptions::default(),
179            display: None,
180            memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
181        });
182        
183        let surface = instance.create_surface(window.clone()).expect("Failed to create surface");
184        
185        let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions {
186            power_preference: wgpu::PowerPreference::HighPerformance,
187            compatible_surface: Some(&surface),
188            force_fallback_adapter: false,
189        }).await.expect("Failed to find a suitable GPU for Surtr");
190
191        let (device, queue) = adapter.request_device(
192            &wgpu::DeviceDescriptor {
193                label: Some("Surtr Forge"),
194                required_features: wgpu::Features::empty(),
195                required_limits: wgpu::Limits::default(),
196                memory_hints: wgpu::MemoryHints::default(),
197                experimental_features: wgpu::ExperimentalFeatures::disabled(),
198                trace: wgpu::Trace::Off,
199            },
200        ).await.expect("Failed to create Surtr device");
201
202        let device = Arc::new(device);
203        let queue = Arc::new(queue);
204        
205        let size = window.inner_size();
206        let surface_caps = surface.get_capabilities(&adapter);
207        let surface_format = surface_caps.formats.iter()
208            .find(|f| f.is_srgb())
209            .copied()
210            .unwrap_or(surface_caps.formats[0]);
211            
212        let config = wgpu::SurfaceConfiguration {
213            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
214            format: surface_format,
215            width: size.width,
216            height: size.height,
217            present_mode: wgpu::PresentMode::Fifo,
218            alpha_mode: surface_caps.alpha_modes[0],
219            view_formats: vec![],
220            desired_maximum_frame_latency: 2,
221        };
222        surface.configure(&device, &config);
223
224        // Load the Muspelheim Shaders
225        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
226            label: Some("Muspelheim Main Shader"),
227            source: wgpu::ShaderSource::Wgsl(include_str!("shaders.wgsl").into()),
228        });
229
230        // Niflheim Bind Group Layout (for textures/samplers)
231        let texture_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
232            entries: &[
233                wgpu::BindGroupLayoutEntry {
234                    binding: 0,
235                    visibility: wgpu::ShaderStages::FRAGMENT,
236                    ty: wgpu::BindingType::Texture {
237                        multisampled: false,
238                        view_dimension: wgpu::TextureViewDimension::D2,
239                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
240                    },
241                    count: None,
242                },
243                wgpu::BindGroupLayoutEntry {
244                    binding: 1,
245                    visibility: wgpu::ShaderStages::FRAGMENT,
246                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
247                    count: None,
248                },
249            ],
250            label: Some("Niflheim Texture Bind Group Layout"),
251        });
252
253        // Environment Bind Group Layout (for blurred background / Bifrost)
254        // Environment Bind Group Layout (for blurred background / Bifrost)
255        let env_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
256            entries: &[
257                wgpu::BindGroupLayoutEntry {
258                    binding: 0,
259                    visibility: wgpu::ShaderStages::FRAGMENT,
260                    ty: wgpu::BindingType::Texture {
261                        multisampled: false,
262                        view_dimension: wgpu::TextureViewDimension::D2,
263                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
264                    },
265                    count: None,
266                },
267                wgpu::BindGroupLayoutEntry {
268                    binding: 1,
269                    visibility: wgpu::ShaderStages::FRAGMENT,
270                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
271                    count: None,
272                },
273            ],
274            label: Some("Surtr Environment Bind Group Layout"),
275        });
276
277        let berserker_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
278            entries: &[
279                wgpu::BindGroupLayoutEntry {
280                    binding: 0,
281                    visibility: wgpu::ShaderStages::FRAGMENT,
282                    ty: wgpu::BindingType::Buffer {
283                        ty: wgpu::BufferBindingType::Uniform,
284                        has_dynamic_offset: false,
285                        min_binding_size: None,
286                    },
287                    count: None,
288                },
289                wgpu::BindGroupLayoutEntry {
290                    binding: 1,
291                    visibility: wgpu::ShaderStages::FRAGMENT | wgpu::ShaderStages::VERTEX,
292                    ty: wgpu::BindingType::Buffer {
293                        ty: wgpu::BufferBindingType::Uniform,
294                        has_dynamic_offset: false,
295                        min_binding_size: None,
296                    },
297                    count: None,
298                },
299            ],
300            label: Some("Surtr Berserker Bind Group Layout"),
301        });
302
303        // Pipeline setup
304        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
305            label: Some("Surtr Main Pipeline Layout"),
306            bind_group_layouts: &[Some(&texture_bind_group_layout), Some(&env_bind_group_layout), Some(&berserker_bind_group_layout)],
307            immediate_size: 0,
308        });
309
310        // Specialized layout for post-processing (Bloom Extract, Blur) which only need Group 0 + Globals
311        let post_process_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
312            label: Some("Muspelheim Post Process Layout"),
313            bind_group_layouts: &[
314                Some(&texture_bind_group_layout),
315                Some(&texture_bind_group_layout),
316                Some(&berserker_bind_group_layout),
317            ],
318            immediate_size: 0,
319        });
320
321        // Specialized layout for composite (Blur + Scene)
322        let composite_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
323            label: Some("Muspelheim Composite Layout"),
324            bind_group_layouts: &[
325                Some(&texture_bind_group_layout),
326                Some(&texture_bind_group_layout),
327                Some(&berserker_bind_group_layout),
328            ],
329            immediate_size: 0,
330        });
331
332        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
333            label: Some("Surtr Main Pipeline"),
334            layout: Some(&pipeline_layout),
335            vertex: wgpu::VertexState {
336                module: &shader,
337                entry_point: Some("vs_main"),
338                buffers: &[Vertex::desc()],
339                compilation_options: wgpu::PipelineCompilationOptions::default(),
340            },
341            fragment: Some(wgpu::FragmentState {
342                module: &shader,
343                entry_point: Some("fs_main"),
344                targets: &[Some(wgpu::ColorTargetState {
345                    format: config.format,
346                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
347                    write_mask: wgpu::ColorWrites::ALL,
348                })],
349                compilation_options: wgpu::PipelineCompilationOptions::default(),
350            }),
351            primitive: wgpu::PrimitiveState::default(),
352            depth_stencil: None,
353            multisample: wgpu::MultisampleState::default(),
354            multiview_mask: None,
355            cache: None,
356        });
357
358        let background_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
359            label: Some("Surtr Background Pipeline"),
360            layout: Some(&pipeline_layout),
361            vertex: wgpu::VertexState {
362                module: &shader,
363                entry_point: Some("vs_fullscreen"),
364                buffers: &[],
365                compilation_options: wgpu::PipelineCompilationOptions::default(),
366            },
367            fragment: Some(wgpu::FragmentState {
368                module: &shader,
369                entry_point: Some("fs_background"),
370                targets: &[Some(wgpu::ColorTargetState {
371                    format: config.format,
372                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
373                    write_mask: wgpu::ColorWrites::ALL,
374                })],
375                compilation_options: wgpu::PipelineCompilationOptions::default(),
376            }),
377            primitive: wgpu::PrimitiveState::default(),
378            depth_stencil: None,
379            multisample: wgpu::MultisampleState::default(),
380            multiview_mask: None,
381            cache: None,
382        });
383
384        // Muspelheim Bloom Extract Pipeline
385        let bloom_extract_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
386            label: Some("Muspelheim Bloom Extract"),
387            layout: Some(&post_process_layout),
388            vertex: wgpu::VertexState {
389                module: &shader,
390                entry_point: Some("vs_fullscreen"),
391                buffers: &[],
392                compilation_options: wgpu::PipelineCompilationOptions::default(),
393            },
394            fragment: Some(wgpu::FragmentState {
395                module: &shader,
396                entry_point: Some("fs_bloom_extract"),
397                targets: &[Some(wgpu::ColorTargetState {
398                    format: config.format,
399                    blend: None,
400                    write_mask: wgpu::ColorWrites::ALL,
401                })],
402                compilation_options: wgpu::PipelineCompilationOptions::default(),
403            }),
404            primitive: wgpu::PrimitiveState::default(),
405            depth_stencil: None,
406            multisample: wgpu::MultisampleState::default(),
407            multiview_mask: None,
408            cache: None,
409        });
410
411        // Muspelheim Blur Pipelines (H and V)
412        let blur_h_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
413            label: Some("Muspelheim Horizontal Blur"),
414            layout: Some(&post_process_layout),
415            vertex: wgpu::VertexState {
416                module: &shader,
417                entry_point: Some("vs_fullscreen"),
418                buffers: &[],
419                compilation_options: wgpu::PipelineCompilationOptions::default(),
420            },
421            fragment: Some(wgpu::FragmentState {
422                module: &shader,
423                entry_point: Some("fs_blur_h"),
424                targets: &[Some(wgpu::ColorTargetState {
425                    format: config.format,
426                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
427                    write_mask: wgpu::ColorWrites::ALL,
428                })],
429                compilation_options: wgpu::PipelineCompilationOptions::default(),
430            }),
431            primitive: wgpu::PrimitiveState::default(),
432            depth_stencil: None,
433            multisample: wgpu::MultisampleState::default(),
434            multiview_mask: None,
435            cache: None,
436        });
437
438        let blur_v_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
439            label: Some("Muspelheim Vertical Blur"),
440            layout: Some(&post_process_layout),
441            vertex: wgpu::VertexState {
442                module: &shader,
443                entry_point: Some("vs_fullscreen"),
444                buffers: &[],
445                compilation_options: wgpu::PipelineCompilationOptions::default(),
446            },
447            fragment: Some(wgpu::FragmentState {
448                module: &shader,
449                entry_point: Some("fs_blur_v"),
450                targets: &[Some(wgpu::ColorTargetState {
451                    format: config.format,
452                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
453                    write_mask: wgpu::ColorWrites::ALL,
454                })],
455                compilation_options: wgpu::PipelineCompilationOptions::default(),
456            }),
457            primitive: wgpu::PrimitiveState::default(),
458            depth_stencil: None,
459            multisample: wgpu::MultisampleState::default(),
460            multiview_mask: None,
461            cache: None,
462        });
463
464        // Muspelheim Composite Pipeline (additive blend onto screen)
465        let composite_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
466            label: Some("Muspelheim Composite"),
467            layout: Some(&composite_layout),
468            vertex: wgpu::VertexState {
469                module: &shader,
470                entry_point: Some("vs_fullscreen"),
471                buffers: &[],
472                compilation_options: wgpu::PipelineCompilationOptions::default(),
473            },
474            fragment: Some(wgpu::FragmentState {
475                module: &shader,
476                entry_point: Some("fs_composite"),
477                targets: &[Some(wgpu::ColorTargetState {
478                    format: config.format,
479                    // Additive blend: src + dst — glow lights up the scene
480                    blend: Some(wgpu::BlendState {
481                        color: wgpu::BlendComponent {
482                            src_factor: wgpu::BlendFactor::One,
483                            dst_factor: wgpu::BlendFactor::One,
484                            operation: wgpu::BlendOperation::Add,
485                        },
486                        alpha: wgpu::BlendComponent {
487                            src_factor: wgpu::BlendFactor::One,
488                            dst_factor: wgpu::BlendFactor::One,
489                            operation: wgpu::BlendOperation::Add,
490                        },
491                    }),
492                    write_mask: wgpu::ColorWrites::ALL,
493                })],
494                compilation_options: wgpu::PipelineCompilationOptions::default(),
495            }),
496            primitive: wgpu::PrimitiveState::default(),
497            depth_stencil: None,
498            multisample: wgpu::MultisampleState::default(),
499            multiview_mask: None,
500            cache: None,
501        });
502
503        // Muspelheim Intermediate Textures
504        let blur_tex_desc = wgpu::TextureDescriptor {
505            label: Some("Muspelheim Intermediate"),
506            size: wgpu::Extent3d {
507                width: config.width,
508                height: config.height,
509                depth_or_array_layers: 1,
510            },
511            mip_level_count: 1,
512            sample_count: 1,
513            dimension: wgpu::TextureDimension::D2,
514            format: config.format,
515            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
516            view_formats: &[],
517        };
518        let blur_texture_a_obj = device.create_texture(&blur_tex_desc);
519        let blur_texture_b_obj = device.create_texture(&blur_tex_desc);
520        let blur_texture_a = blur_texture_a_obj.create_view(&wgpu::TextureViewDescriptor::default());
521        let blur_texture_b = blur_texture_b_obj.create_view(&wgpu::TextureViewDescriptor::default());
522
523        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
524            address_mode_u: wgpu::AddressMode::ClampToEdge,
525            address_mode_v: wgpu::AddressMode::ClampToEdge,
526            mag_filter: wgpu::FilterMode::Linear,
527            ..Default::default()
528        });
529
530        let blur_bind_group_a = device.create_bind_group(&wgpu::BindGroupDescriptor {
531            layout: &texture_bind_group_layout,
532            entries: &[
533                wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&blur_texture_a) },
534                wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&sampler) },
535            ],
536            label: Some("Blur Bind Group A"),
537        });
538
539        let blur_bind_group_b = device.create_bind_group(&wgpu::BindGroupDescriptor {
540            layout: &texture_bind_group_layout,
541            entries: &[
542                wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&blur_texture_b) },
543                wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&sampler) },
544            ],
545            label: Some("Blur Bind Group B"),
546        });
547
548        // Forge the Scene Capture Texture
549        let scene_texture_obj = device.create_texture(&blur_tex_desc);
550        let scene_texture = scene_texture_obj.create_view(&wgpu::TextureViewDescriptor::default());
551        let scene_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
552            layout: &env_bind_group_layout,
553            entries: &[
554                wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&scene_texture) },
555                wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&sampler) },
556            ],
557            label: Some("Scene Capture Bind Group"),
558        });
559
560        // Forge the Text Atlas (1024x1024 Alpha-only for speed)
561        let text_atlas_tex = device.create_texture(&wgpu::TextureDescriptor {
562            label: Some("Surtr Text Atlas"),
563            size: wgpu::Extent3d { width: 1024, height: 1024, depth_or_array_layers: 1 },
564            mip_level_count: 1,
565            sample_count: 1,
566            dimension: wgpu::TextureDimension::D2,
567            format: wgpu::TextureFormat::R8Unorm,
568            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
569            view_formats: &[],
570        });
571        let text_atlas = text_atlas_tex.create_view(&wgpu::TextureViewDescriptor::default());
572        let text_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
573            address_mode_u: wgpu::AddressMode::ClampToEdge,
574            address_mode_v: wgpu::AddressMode::ClampToEdge,
575            mag_filter: wgpu::FilterMode::Nearest,
576            min_filter: wgpu::FilterMode::Nearest,
577            ..Default::default()
578        });
579
580        // Clear the text atlas to transparency initially
581        queue.write_texture(
582            wgpu::TexelCopyTextureInfo {
583                texture: &text_atlas_tex,
584                mip_level: 0,
585                origin: wgpu::Origin3d::ZERO,
586                aspect: wgpu::TextureAspect::All,
587            },
588            &vec![0u8; 1024 * 1024],
589            wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(1024), rows_per_image: Some(1024) },
590            wgpu::Extent3d { width: 1024, height: 1024, depth_or_array_layers: 1 },
591        );
592
593        // Forge the Niflheim Dummy Texture (1x1 White)
594        let dummy_size = wgpu::Extent3d {
595            width: 1,
596            height: 1,
597            depth_or_array_layers: 1,
598        };
599        let dummy_texture = device.create_texture(&wgpu::TextureDescriptor {
600            label: Some("Niflheim Dummy Texture"),
601            size: dummy_size,
602            mip_level_count: 1,
603            sample_count: 1,
604            dimension: wgpu::TextureDimension::D2,
605            format: wgpu::TextureFormat::Rgba8UnormSrgb,
606            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
607            view_formats: &[],
608        });
609        queue.write_texture(
610            wgpu::TexelCopyTextureInfo {
611                texture: &dummy_texture,
612                mip_level: 0,
613                origin: wgpu::Origin3d::ZERO,
614                aspect: wgpu::TextureAspect::All,
615            },
616            &[255, 255, 255, 255],
617            wgpu::TexelCopyBufferLayout {
618                offset: 0,
619                bytes_per_row: Some(4),
620                rows_per_image: Some(1),
621            },
622            dummy_size,
623        );
624
625        let dummy_view = dummy_texture.create_view(&wgpu::TextureViewDescriptor::default());
626        let dummy_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
627            address_mode_u: wgpu::AddressMode::ClampToEdge,
628            address_mode_v: wgpu::AddressMode::ClampToEdge,
629            address_mode_w: wgpu::AddressMode::ClampToEdge,
630            mag_filter: wgpu::FilterMode::Linear,
631            min_filter: wgpu::FilterMode::Nearest,
632            mipmap_filter: wgpu::MipmapFilterMode::Nearest,
633            ..Default::default()
634        });
635
636        let dummy_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
637            layout: &texture_bind_group_layout,
638            entries: &[
639                wgpu::BindGroupEntry {
640                    binding: 0,
641                    resource: wgpu::BindingResource::TextureView(&dummy_view),
642                },
643                wgpu::BindGroupEntry {
644                    binding: 1,
645                    resource: wgpu::BindingResource::Sampler(&dummy_sampler),
646                },
647            ],
648            label: Some("Niflheim Dummy Bind Group"),
649        });
650
651        let mut texture_registry = std::collections::HashMap::new();
652        let mut texture_bind_groups = Vec::new();
653
654        let text_atlas_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
655            layout: &texture_bind_group_layout,
656            entries: &[
657                wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&text_atlas) },
658                wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&text_sampler) },
659            ],
660            label: Some("Text Atlas Bind Group"),
661        });
662        texture_registry.insert("__text_atlas".to_string(), texture_bind_groups.len() as u32);
663        texture_bind_groups.push(text_atlas_bg);
664
665        // Forge the Anvil (Buffers)
666        let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
667            label: Some("Surtr Vertex Anvil"),
668            size: (MAX_VERTICES * std::mem::size_of::<Vertex>()) as u64,
669            usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
670            mapped_at_creation: false,
671        });
672
673        let index_buffer = device.create_buffer(&wgpu::BufferDescriptor {
674            label: Some("Surtr Index Anvil"),
675            size: (MAX_INDICES * std::mem::size_of::<u16>()) as u64,
676            usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
677            mapped_at_creation: false,
678        });
679
680
681        // Register atlas
682
683        // Texture registry and bind groups already initialized above.
684
685        // Forge the Heart (Berserker Uniforms)
686        let current_theme = ColorTheme::default();
687        use wgpu::util::DeviceExt;
688        let theme_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
689            label: Some("Surtr Theme Buffer"),
690            contents: bytemuck::bytes_of(&current_theme),
691            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
692        });
693
694        let current_scene = SceneUniforms::new(config.width as f32, config.height as f32);
695        let scene_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
696            label: Some("Surtr Scene Buffer"),
697            contents: bytemuck::bytes_of(&current_scene),
698            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
699        });
700
701        let berserker_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
702            layout: &berserker_bind_group_layout,
703            entries: &[
704                wgpu::BindGroupEntry {
705                    binding: 0,
706                    resource: theme_buffer.as_entire_binding(),
707                },
708                wgpu::BindGroupEntry {
709                    binding: 1,
710                    resource: scene_buffer.as_entire_binding(),
711                },
712            ],
713            label: Some("Surtr Berserker Bind Group"),
714        });
715
716        let scene_texture_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
717            layout: &texture_bind_group_layout,
718            entries: &[
719                wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&scene_texture) },
720                wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&sampler) },
721            ],
722            label: Some("Scene Texture Bind Group (Group 0)"),
723        });
724
725        Self {
726            device,
727            queue,
728            surface,
729            config,
730            pipeline,
731            bloom_extract_pipeline,
732            blur_h_pipeline,
733            blur_v_pipeline,
734            composite_pipeline,
735            blur_texture_a,
736            blur_texture_b,
737            blur_bind_group_a,
738            blur_bind_group_b,
739            scene_texture,
740            scene_bind_group,
741            scene_texture_bind_group,
742            font_system: cosmic_text::FontSystem::new(),
743            swash_cache: cosmic_text::SwashCache::new(),
744            text_atlas_tex,
745            text_atlas_view: text_atlas,
746            text_sampler,
747            text_cache: std::collections::HashMap::new(),
748            text_atlas_pos: (0, 0),
749            dummy_bind_group,
750            texture_bind_group_layout,
751            texture_bind_groups,
752            texture_registry,
753            shared_elements: std::collections::HashMap::new(),
754            vertex_buffer,
755            index_buffer,
756            vertices: Vec::with_capacity(MAX_VERTICES),
757            indices: Vec::with_capacity(MAX_INDICES),
758            draw_calls: Vec::new(),
759            current_texture_id: None,
760            opacity_stack: vec![1.0],
761            clip_stack: Vec::new(),
762            slice_stack: Vec::new(),
763            theme_buffer,
764            scene_buffer,
765            berserker_bind_group,
766            berserker_bind_group_layout,
767            start_time: std::time::Instant::now(),
768            current_theme,
769            current_scene,
770            background_pipeline,
771        }
772    }
773
774    pub fn resize(&mut self, width: u32, height: u32) {
775        if width > 0 && height > 0 {
776            self.config.width = width;
777            self.config.height = height;
778            self.surface.configure(&self.device, &self.config);
779            
780            // Re-create Muspelheim textures
781            let texture_desc = wgpu::TextureDescriptor {
782                label: Some("Surtr Scene Texture"),
783                size: wgpu::Extent3d { width, height, depth_or_array_layers: 1 },
784                mip_level_count: 1,
785                sample_count: 1,
786                dimension: wgpu::TextureDimension::D2,
787                format: self.config.format,
788                usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
789                view_formats: &[],
790            };
791            
792            let scene_tex = self.device.create_texture(&texture_desc);
793            self.scene_texture = scene_tex.create_view(&wgpu::TextureViewDescriptor::default());
794            
795            let blur_tex_a = self.device.create_texture(&texture_desc);
796            self.blur_texture_a = blur_tex_a.create_view(&wgpu::TextureViewDescriptor::default());
797            
798            let blur_tex_b = self.device.create_texture(&texture_desc);
799            self.blur_texture_b = blur_tex_b.create_view(&wgpu::TextureViewDescriptor::default());
800
801            // Re-create bind groups (using existing layouts)
802            let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
803                address_mode_u: wgpu::AddressMode::ClampToEdge,
804                address_mode_v: wgpu::AddressMode::ClampToEdge,
805                mag_filter: wgpu::FilterMode::Linear,
806                min_filter: wgpu::FilterMode::Linear,
807                ..Default::default()
808            });
809
810            // For scene_bind_group, we need the env_bind_group_layout.
811            // Since it's not stored, we re-create it exactly as in forge.
812            let env_layout = self.device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
813                entries: &[
814                    wgpu::BindGroupLayoutEntry {
815                        binding: 0,
816                        visibility: wgpu::ShaderStages::FRAGMENT,
817                        ty: wgpu::BindingType::Texture {
818                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
819                            view_dimension: wgpu::TextureViewDimension::D2,
820                            multisampled: false,
821                        },
822                        count: None,
823                    },
824                    wgpu::BindGroupLayoutEntry {
825                        binding: 1,
826                        visibility: wgpu::ShaderStages::FRAGMENT,
827                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
828                        count: None,
829                    },
830                ],
831                label: Some("Surtr Env Layout Resize"),
832            });
833
834            self.scene_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
835                layout: &env_layout,
836                entries: &[
837                    wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&self.scene_texture) },
838                    wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&sampler) },
839                ],
840                label: Some("Surtr Scene Bind Group Resize"),
841            });
842
843            self.blur_bind_group_a = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
844                layout: &self.texture_bind_group_layout,
845                entries: &[
846                    wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&self.blur_texture_a) },
847                    wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&sampler) },
848                ],
849                label: Some("Surtr Blur Bind Group A Resize"),
850            });
851
852            self.blur_bind_group_b = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
853                layout: &self.texture_bind_group_layout,
854                entries: &[
855                    wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&self.blur_texture_b) },
856                    wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&sampler) },
857                ],
858                label: Some("Surtr Blur Bind Group B Resize"),
859            });
860
861            self.scene_texture_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
862                layout: &self.texture_bind_group_layout,
863                entries: &[
864                    wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&self.scene_texture) },
865                    wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&sampler) },
866                ],
867                label: Some("Scene Texture Bind Group Resize"),
868            });
869            
870            self.current_scene.resolution = [width as f32, height as f32];
871        }
872    }
873
874    /// begin_frame — Strike the flaming sword to begin a new GPU frame.
875    pub fn begin_frame(&mut self) -> wgpu::CommandEncoder {
876        self.vertices.clear();
877        self.indices.clear();
878        self.draw_calls.clear();
879        self.shared_elements.clear(); // Clear registry for the new frame
880        self.current_texture_id = None;
881
882        let time = self.start_time.elapsed().as_secs_f32();
883        let (width, height) = (self.config.width as f32, self.config.height as f32);
884        let dt = time - self.current_scene.time;
885        self.current_scene.time = time;
886        self.current_scene.delta_time = dt;
887        self.current_scene.resolution = [width, height];
888        self.current_scene.proj = glam::Mat4::orthographic_lh(0.0, width, height, 0.0, -100.0, 100.0);
889        
890        self.queue.write_buffer(&self.scene_buffer, 0, bytemuck::bytes_of(&self.current_scene));
891
892        self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
893            label: Some("Surtr's Flaming Sword"),
894        })
895    }
896
897    /// Reset the internal clock (for interactive effects)
898    pub fn reset_time(&mut self) {
899        self.start_time = std::time::Instant::now();
900    }
901
902    fn shatter_internal(&mut self, rect: Rect, pieces: u32, force: f32, color: [f32; 4], mode: u32) {
903        // High-Fidelity Variable Particle Density
904        let count = (pieces as f32).sqrt().ceil() as u32;
905        let dw = rect.width / count as f32;
906        let dh = rect.height / count as f32;
907
908        let c = self.apply_opacity(color);
909
910        for y in 0..count {
911            for x in 0..count {
912                let shard_rect = Rect {
913                    x: rect.x + x as f32 * dw,
914                    y: rect.y + y as f32 * dh,
915                    width: dw,
916                    height: dh,
917                };
918                
919                let uv = Rect {
920                    x: x as f32 / count as f32,
921                    y: y as f32 / count as f32,
922                    width: 1.0 / count as f32,
923                    height: 1.0 / count as f32,
924                };
925
926                self.fill_rect_with_full_params(
927                    shard_rect, 
928                    c, 
929                    mode, 
930                    None, 
931                    force, 
932                    uv
933                );
934            }
935        }
936    }
937
938    fn recursive_bolt(&mut self, from: [f32; 2], to: [f32; 2], depth: u32, color: [f32; 4]) {
939        if depth == 0 {
940            self.draw_lightning_segment(from, to, color);
941            return;
942        }
943
944        let mid_x = (from[0] + to[0]) * 0.5;
945        let mid_y = (from[1] + to[1]) * 0.5;
946        
947        let dx = to[0] - from[0];
948        let dy = to[1] - from[1];
949        let len = (dx * dx + dy * dy).sqrt();
950        
951        // Perpendicular offset for jaggedness
952        let offset_scale = len * 0.15;
953        let seed = (from[0] * 12.9898 + from[1] * 78.233 + (depth as f32) * 37.11).sin().fract();
954        let offset_x = -dy / len * (seed - 0.5) * offset_scale;
955        let offset_y = dx / len * (seed - 0.5) * offset_scale;
956        
957        let mid = [mid_x + offset_x, mid_y + offset_y];
958        
959        self.recursive_bolt(from, mid, depth - 1, color);
960        self.recursive_bolt(mid, to, depth - 1, color);
961        
962        // 20% chance of a secondary branch
963        if depth > 2 && seed > 0.8 {
964            let branch_to = [
965                mid[0] + offset_x * 2.0 + (seed * 100.0).sin() * 50.0,
966                mid[1] + offset_y * 2.0 + (seed * 100.0).cos() * 50.0
967            ];
968            self.recursive_bolt(mid, branch_to, depth - 2, color);
969        }
970    }
971
972    fn draw_lightning_segment(&mut self, from: [f32; 2], to: [f32; 2], color: [f32; 4]) {
973        let dx = to[0] - from[0];
974        let dy = to[1] - from[1];
975        let len = (dx * dx + dy * dy).sqrt();
976        if len < 0.001 { return; }
977        
978        let glow_width = 32.0; 
979        let core_width = 4.0;
980        let c = self.apply_opacity(color);
981        
982        // 1. Render Volumetric Glow (Cyan)
983        let gnx = -dy / len * glow_width * 0.5;
984        let gny = dx / len * glow_width * 0.5;
985        let gp1 = [from[0] + gnx, from[1] + gny];
986        let gp2 = [to[0] + gnx, to[1] + gny];
987        let gp3 = [to[0] - gnx, to[1] - gny];
988        let gp4 = [from[0] - gnx, from[1] - gny];
989        self.push_oriented_quad([gp1, gp2, gp3, gp4], c, 9, Rect { x: 0.0, y: 0.0, width: 1.0, height: 1.0 });
990
991        // 2. Render Blinding Core (White)
992        let cnx = -dy / len * core_width * 0.5;
993        let cny = dx / len * core_width * 0.5;
994        let cp1 = [from[0] + cnx, from[1] + cny];
995        let cp2 = [to[0] + cnx, to[1] + cny];
996        let cp3 = [to[0] - cnx, to[1] - cny];
997        let cp4 = [from[0] - cnx, from[1] - cny];
998        self.push_oriented_quad([cp1, cp2, cp3, cp4], [1.0, 1.0, 1.0, c[3]], 0, Rect { x: 0.0, y: 0.0, width: 1.0, height: 1.0 });
999    }
1000
1001    fn push_oriented_quad(&mut self, points: [[f32; 2]; 4], color: [f32; 4], mode: u32, uv_rect: Rect) {
1002        let scissor = self.clip_stack.last().copied();
1003        let texture_id = None; // Oriented quads like lightning don't use textures yet
1004        
1005        if self.draw_calls.is_empty() || self.current_texture_id != texture_id || self.draw_calls.last().unwrap().scissor_rect != scissor {
1006            self.current_texture_id = texture_id;
1007            self.draw_calls.push(DrawCall {
1008                texture_id,
1009                scissor_rect: scissor,
1010                index_start: self.indices.len() as u32,
1011                index_count: 0,
1012                is_glass: mode == 7,
1013                is_ui: mode == 6,
1014            });
1015        }
1016
1017        let uvs = [
1018            [uv_rect.x, uv_rect.y],
1019            [uv_rect.x + uv_rect.width, uv_rect.y],
1020            [uv_rect.x + uv_rect.width, uv_rect.y + uv_rect.height],
1021            [uv_rect.x, uv_rect.y + uv_rect.height],
1022        ];
1023
1024        let screen = [self.config.width as f32, self.config.height as f32];
1025        let rect = Rect { x: points[0][0], y: points[0][1], width: 1.0, height: 1.0 };
1026
1027        for i in 0..4 {
1028            let px = points[i][0];
1029            let py = points[i][1];
1030            
1031            self.vertices.push(Vertex {
1032                position: [px, py, 0.0],
1033                normal: [0.0, 0.0, 1.0],
1034                uv: uvs[i],
1035                color,
1036                mode,
1037                radius: 0.0,
1038                slice: [0.0, 0.0, 0.0, 1.0],
1039                logical: [px - rect.x, py - rect.y],
1040                size: [rect.width, rect.height], 
1041                screen,
1042                clip: [-10000.0, -10000.0, 20000.0, 20000.0],
1043            });
1044        }
1045
1046        if let Some(call) = self.draw_calls.last_mut() {
1047            call.index_count += 6;
1048        }
1049    }
1050    fn get_texture_id(&self, name: &str) -> Option<u32> {
1051        self.texture_registry.get(name).copied()
1052    }
1053
1054    fn fill_rect_with_mode(&mut self, rect: Rect, color: [f32; 4], mode: u32, texture_id: Option<u32>) {
1055        self.fill_rect_with_full_params(rect, color, mode, texture_id, 0.0, Rect { x: 0.0, y: 0.0, width: 1.0, height: 1.0 });
1056    }
1057
1058    fn fill_rect_with_full_params(&mut self, rect: Rect, color: [f32; 4], mode: u32, texture_id: Option<u32>, radius: f32, uv_rect: Rect) {
1059        let slice = self.slice_stack.last().copied().map(|(a, o)| [a, o, 1.0, 1.0]).unwrap_or([0.0, 0.0, 0.0, 1.0]);
1060        self.fill_rect_with_full_params_and_slice(rect, color, mode, texture_id, radius, uv_rect, slice);
1061    }
1062
1063    fn fill_rect_with_full_params_and_slice(&mut self, rect: Rect, color: [f32; 4], mode: u32, texture_id: Option<u32>, radius: f32, uv_rect: Rect, slice: [f32; 4]) {
1064        let scissor = self.clip_stack.last().copied();
1065        
1066        let is_glass = mode == 7;
1067        let is_ui = mode == 6;
1068
1069        // Batching: check if we need to start a new DrawCall
1070        let last_call = self.draw_calls.last();
1071        let needs_new_call = self.draw_calls.is_empty() 
1072            || self.current_texture_id != texture_id
1073            || last_call.unwrap().scissor_rect != scissor
1074            || last_call.unwrap().is_glass != is_glass
1075            || last_call.unwrap().is_ui != is_ui;
1076
1077        if needs_new_call {
1078            self.current_texture_id = texture_id;
1079            self.draw_calls.push(DrawCall {
1080                texture_id,
1081                scissor_rect: scissor,
1082                index_start: self.indices.len() as u32,
1083                index_count: 0,
1084                is_glass,
1085                is_ui,
1086            });
1087        }
1088
1089        let base_idx = self.vertices.len() as u32;
1090        let x1 = rect.x;
1091        let y1 = rect.y;
1092        let x2 = rect.x + rect.width;
1093        let y2 = rect.y + rect.height;
1094        let z = 0.0;
1095        let normal = [0.0, 0.0, 1.0];
1096        let screen = [self.config.width as f32, self.config.height as f32];
1097        let clip_rect = self.clip_stack.last().copied().unwrap_or(cvkg_core::Rect { x: -10000.0, y: -10000.0, width: 20000.0, height: 20000.0 });
1098        let clip = [clip_rect.x, clip_rect.y, clip_rect.width, clip_rect.height];
1099
1100        self.vertices.push(Vertex { 
1101            position: [x1, y1, z], normal, uv: [uv_rect.x, uv_rect.y], color, mode, radius, 
1102            slice, logical: [0.0, 0.0], size: [rect.width, rect.height], screen, clip
1103        });
1104        self.vertices.push(Vertex { 
1105            position: [x2, y1, z], normal, uv: [uv_rect.x + uv_rect.width, uv_rect.y], color, mode, radius, 
1106            slice, logical: [rect.width, 0.0], size: [rect.width, rect.height], screen, clip
1107        });
1108        self.vertices.push(Vertex { 
1109            position: [x2, y2, z], normal, uv: [uv_rect.x + uv_rect.width, uv_rect.y + uv_rect.height], color, mode, radius, 
1110            slice, logical: [rect.width, rect.height], size: [rect.width, rect.height], screen, clip
1111        });
1112        self.vertices.push(Vertex { 
1113            position: [x1, y2, z], normal, uv: [uv_rect.x, uv_rect.y + uv_rect.height], color, mode, radius, 
1114            slice, logical: [0.0, rect.height], size: [rect.width, rect.height], screen, clip
1115        });
1116
1117        self.indices.extend_from_slice(&[
1118            base_idx, base_idx + 1, base_idx + 2,
1119            base_idx, base_idx + 2, base_idx + 3,
1120        ]);
1121
1122        if let Some(call) = self.draw_calls.last_mut() {
1123            call.index_count += 6;
1124        }
1125    }
1126
1127    /// end_frame — Quench the blade by submitting the full Muspelheim multi-pass effect.
1128    pub fn end_frame(&mut self, mut encoder: wgpu::CommandEncoder) {
1129        self.queue.write_buffer(&self.vertex_buffer, 0, bytemuck::cast_slice(&self.vertices));
1130        self.queue.write_buffer(&self.index_buffer, 0, bytemuck::cast_slice(&self.indices));
1131
1132        let frame = match self.surface.get_current_texture() {
1133            wgpu::CurrentSurfaceTexture::Success(t) => t,
1134            wgpu::CurrentSurfaceTexture::Suboptimal(t) => t,
1135            _ => {
1136                 self.surface.configure(&self.device, &self.config);
1137                 return;
1138            }
1139        };
1140        let screen = frame.texture.create_view(&wgpu::TextureViewDescriptor::default());
1141
1142        // ── Pass 1: Opaque Background & Atmosphere ──────────────────────────
1143        {
1144            let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1145                label: Some("Surtr P1 Opaque Background"),
1146                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1147                    view: &self.scene_texture,
1148                    resolve_target: None,
1149                    ops: wgpu::Operations {
1150                        load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
1151                        store: wgpu::StoreOp::Store,
1152                    },
1153                    depth_slice: None,
1154                })],
1155                depth_stencil_attachment: None,
1156                occlusion_query_set: None,
1157                timestamp_writes: None,
1158                multiview_mask: None,
1159            });
1160
1161            // 1a. Background Atmosphere
1162            p.set_pipeline(&self.background_pipeline);
1163            p.set_bind_group(0, &self.dummy_bind_group, &[]);
1164            p.set_bind_group(1, &self.blur_bind_group_a, &[]); // Use previous frame's blur for background depth
1165            p.set_bind_group(2, &self.berserker_bind_group, &[]);
1166            p.draw(0..6, 0..1);
1167
1168            // 1b. Opaque Main Elements (non-glass, non-ui)
1169            if !self.draw_calls.is_empty() {
1170                p.set_pipeline(&self.pipeline);
1171                p.set_vertex_buffer(0, self.vertex_buffer.slice(..));
1172                p.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
1173                p.set_bind_group(1, &self.dummy_bind_group, &[]); 
1174                p.set_bind_group(2, &self.berserker_bind_group, &[]);
1175                
1176                for call in self.draw_calls.iter().filter(|c| !c.is_glass && !c.is_ui) {
1177                    let bg = if let Some(id) = call.texture_id {
1178                        self.texture_bind_groups.get(id as usize).unwrap_or(&self.dummy_bind_group)
1179                    } else {
1180                        &self.dummy_bind_group
1181                    };
1182                    p.set_bind_group(0, bg, &[]);
1183                    p.draw_indexed(call.index_start..call.index_start + call.index_count, 0, 0..1);
1184                }
1185            }
1186        }
1187
1188        // ── Pass 2: Backdrop Blur (Bifrost) ──────────────────────────────────
1189        // Capture the background into blur_texture_b
1190        {
1191            // First extract into texture_a
1192            let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1193                label: Some("Surtr Blur Extract"),
1194                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1195                    view: &self.blur_texture_a,
1196                    resolve_target: None,
1197                    ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), store: wgpu::StoreOp::Store },
1198                    depth_slice: None,
1199                })],
1200                ..Default::default()
1201            });
1202            p.set_pipeline(&self.bloom_extract_pipeline); // Use extract as a direct copy for now
1203            p.set_bind_group(0, &self.scene_texture_bind_group, &[]); 
1204            p.set_bind_group(1, &self.dummy_bind_group, &[]); 
1205            p.set_bind_group(2, &self.berserker_bind_group, &[]);
1206            p.draw(0..6, 0..1);
1207        }
1208        
1209        let blur_iters: u32 = 4;
1210        for _i in 0..blur_iters {
1211            {
1212                let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1213                    label: Some("Blur H"),
1214                    color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1215                        view: &self.blur_texture_b,
1216                        resolve_target: None,
1217                        ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), store: wgpu::StoreOp::Store },
1218                        depth_slice: None,
1219                    })],
1220                    ..Default::default()
1221                });
1222                p.set_pipeline(&self.blur_h_pipeline);
1223                p.set_bind_group(0, &self.blur_bind_group_a, &[]);
1224                p.set_bind_group(1, &self.dummy_bind_group, &[]);
1225                p.set_bind_group(2, &self.berserker_bind_group, &[]);
1226                p.draw(0..6, 0..1);
1227            }
1228            {
1229                let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1230                    label: Some("Blur V"),
1231                    color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1232                        view: &self.blur_texture_a,
1233                        resolve_target: None,
1234                        ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), store: wgpu::StoreOp::Store },
1235                        depth_slice: None,
1236                    })],
1237                    ..Default::default()
1238                });
1239                p.set_pipeline(&self.blur_v_pipeline);
1240                p.set_bind_group(0, &self.blur_bind_group_b, &[]);
1241                p.set_bind_group(1, &self.dummy_bind_group, &[]);
1242                p.set_bind_group(2, &self.berserker_bind_group, &[]);
1243                p.draw(0..6, 0..1);
1244            }
1245        }
1246
1247        // ── Pass 3: Liquid Glass Elements ───────────────────────────────────
1248        {
1249            let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1250                label: Some("Surtr P3 Liquid Glass"),
1251                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1252                    view: &self.scene_texture, // RENDER OVER THE OPAQUE BACKGROUND
1253                    resolve_target: None,
1254                    ops: wgpu::Operations {
1255                        load: wgpu::LoadOp::Load,
1256                        store: wgpu::StoreOp::Store,
1257                    },
1258                    depth_slice: None,
1259                })],
1260                ..Default::default()
1261            });
1262
1263            p.set_pipeline(&self.pipeline);
1264            p.set_vertex_buffer(0, self.vertex_buffer.slice(..));
1265            p.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
1266            p.set_bind_group(1, &self.blur_bind_group_a, &[]); // Sample the freshly blurred backdrop
1267            p.set_bind_group(2, &self.berserker_bind_group, &[]);
1268
1269            for call in self.draw_calls.iter().filter(|c| c.is_glass) {
1270                let bg = if let Some(id) = call.texture_id {
1271                    self.texture_bind_groups.get(id as usize).unwrap_or(&self.dummy_bind_group)
1272                } else {
1273                    &self.dummy_bind_group
1274                };
1275                p.set_bind_group(0, bg, &[]);
1276                p.draw_indexed(call.index_start..call.index_start + call.index_count, 0, 0..1);
1277            }
1278        }
1279
1280        // ── Pass 4: UI & Text Overlay ──────────────────────────────────────
1281        {
1282            let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1283                label: Some("Surtr P4 UI Layer"),
1284                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1285                    view: &self.scene_texture,
1286                    resolve_target: None,
1287                    ops: wgpu::Operations {
1288                        load: wgpu::LoadOp::Load,
1289                        store: wgpu::StoreOp::Store,
1290                    },
1291                    depth_slice: None,
1292                })],
1293                ..Default::default()
1294            });
1295
1296            p.set_pipeline(&self.pipeline);
1297            p.set_vertex_buffer(0, self.vertex_buffer.slice(..));
1298            p.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
1299            p.set_bind_group(1, &self.dummy_bind_group, &[]); 
1300            p.set_bind_group(2, &self.berserker_bind_group, &[]);
1301
1302            for call in self.draw_calls.iter().filter(|c| c.is_ui) {
1303                let bg = if let Some(id) = call.texture_id {
1304                    self.texture_bind_groups.get(id as usize).unwrap_or(&self.dummy_bind_group)
1305                } else {
1306                    &self.dummy_bind_group
1307                };
1308                p.set_bind_group(0, bg, &[]);
1309                p.draw_indexed(call.index_start..call.index_start + call.index_count, 0, 0..1);
1310            }
1311        }
1312
1313        // ── Pass 5: Bloom Extract (Complete Scene) ──────────────────────────
1314        {
1315            let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1316                label: Some("Surtr Bloom Extract"),
1317                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1318                    view: &self.blur_texture_a,
1319                    resolve_target: None,
1320                    ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), store: wgpu::StoreOp::Store },
1321                    depth_slice: None,
1322                })],
1323                ..Default::default()
1324            });
1325            p.set_pipeline(&self.bloom_extract_pipeline);
1326            p.set_bind_group(0, &self.scene_texture_bind_group, &[]); 
1327            p.set_bind_group(1, &self.dummy_bind_group, &[]); 
1328            p.set_bind_group(2, &self.berserker_bind_group, &[]);
1329            p.draw(0..6, 0..1);
1330        }
1331
1332        // ── Pass 6: Blur Bloom ──────────────────────────────────────────────
1333        for _ in 0..2 {
1334            {
1335                let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1336                    label: Some("Bloom Blur H"),
1337                    color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1338                        view: &self.blur_texture_b,
1339                        resolve_target: None,
1340                        ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), store: wgpu::StoreOp::Store },
1341                        depth_slice: None,
1342                    })],
1343                    ..Default::default()
1344                });
1345                p.set_pipeline(&self.blur_h_pipeline);
1346                p.set_bind_group(0, &self.blur_bind_group_a, &[]);
1347                p.set_bind_group(1, &self.dummy_bind_group, &[]);
1348                p.set_bind_group(2, &self.berserker_bind_group, &[]);
1349                p.draw(0..6, 0..1);
1350            }
1351            {
1352                let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1353                    label: Some("Bloom Blur V"),
1354                    color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1355                        view: &self.blur_texture_a,
1356                        resolve_target: None,
1357                        ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), store: wgpu::StoreOp::Store },
1358                        depth_slice: None,
1359                    })],
1360                    ..Default::default()
1361                });
1362                p.set_pipeline(&self.blur_v_pipeline);
1363                p.set_bind_group(0, &self.blur_bind_group_b, &[]);
1364                p.set_bind_group(1, &self.dummy_bind_group, &[]);
1365                p.set_bind_group(2, &self.berserker_bind_group, &[]);
1366                p.draw(0..6, 0..1);
1367            }
1368        }
1369
1370        // ── Pass 7: Final Composite ─────────────────────────────────────────
1371        {
1372            let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1373                label: Some("Surtr P7 Final Composite"),
1374                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1375                    view: &screen,
1376                    resolve_target: None,
1377                    ops: wgpu::Operations {
1378                        load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1379                        store: wgpu::StoreOp::Store,
1380                    },
1381                    depth_slice: None,
1382                })],
1383                ..Default::default()
1384            });
1385            p.set_pipeline(&self.composite_pipeline);
1386            p.set_bind_group(0, &self.scene_bind_group, &[]); // Main Scene (Group 0 -> t_diffuse)
1387            p.set_bind_group(1, &self.blur_bind_group_a, &[]); // Bloom (Group 1 -> t_env)
1388            p.set_bind_group(2, &self.berserker_bind_group, &[]);
1389            p.draw(0..6, 0..1);
1390        }
1391
1392        self.queue.submit(Some(encoder.finish()));
1393        frame.present();
1394    }
1395}
1396
1397impl cvkg_core::Renderer for SurtrRenderer {
1398    fn fill_rect(&mut self, rect: Rect, color: [f32; 4]) {
1399        self.fill_rect_with_mode(rect, self.apply_opacity(color), 0, None);
1400    }
1401
1402    fn fill_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4]) {
1403        self.fill_rect_with_full_params(rect, self.apply_opacity(color), 3, None, radius, Rect { x: 0.0, y: 0.0, width: 1.0, height: 1.0 });
1404    }
1405
1406    fn fill_ellipse(&mut self, rect: Rect, color: [f32; 4]) {
1407        self.fill_rect_with_full_params(rect, self.apply_opacity(color), 4, None, 0.0, Rect { x: 0.0, y: 0.0, width: 1.0, height: 1.0 });
1408    }
1409
1410    fn bifrost(&mut self, rect: Rect, blur: f32, _saturation: f32, opacity: f32) {
1411        // Calculate screen-space UVs for high-fidelity global refraction
1412        let screen_uv = Rect {
1413            x: rect.x / self.config.width as f32,
1414            y: rect.y / self.config.height as f32,
1415            width: rect.width / self.config.width as f32,
1416            height: rect.height / self.config.height as f32,
1417        };
1418        // Use mode 7 for high-fidelity background blur sampling
1419        // Use the blur parameter as corner radius for the glass panel
1420        self.fill_rect_with_full_params(rect, [1.0, 1.0, 1.0, opacity], 7, None, blur, screen_uv);
1421    }
1422
1423    fn stroke_rect(&mut self, rect: Rect, color: [f32; 4], stroke_width: f32) {
1424        let c = self.apply_opacity(color);
1425        let hw = stroke_width;
1426        // Top, bottom, left, right edge bars
1427        self.fill_rect_with_mode(Rect { x: rect.x, y: rect.y, width: rect.width, height: hw }, c, 1, None);
1428        self.fill_rect_with_mode(Rect { x: rect.x, y: rect.y + rect.height - hw, width: rect.width, height: hw }, c, 1, None);
1429        self.fill_rect_with_mode(Rect { x: rect.x, y: rect.y, width: hw, height: rect.height }, c, 1, None);
1430        self.fill_rect_with_mode(Rect { x: rect.x + rect.width - hw, y: rect.y, width: hw, height: rect.height }, c, 1, None);
1431    }
1432
1433    fn stroke_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4], stroke_width: f32) {
1434        self.fill_rect_with_full_params(rect, self.apply_opacity(color), 17, None, radius, Rect { x: stroke_width, y: 0.0, width: 0.0, height: 0.0 });
1435    }
1436
1437    fn stroke_ellipse(&mut self, _rect: Rect, _color: [f32; 4], _stroke_width: f32) {
1438        // Future: Implement stroked SDFs.
1439    }
1440
1441    fn draw_linear_gradient(&mut self, rect: Rect, start_color: [f32; 4], end_color: [f32; 4], angle: f32) {
1442        self.fill_rect_with_full_params_and_slice(rect, self.apply_opacity(start_color), 15, None, 0.0, Rect { x: angle, y: 0.0, width: 1.0, height: 1.0 }, end_color);
1443    }
1444
1445    fn draw_radial_gradient(&mut self, rect: Rect, inner_color: [f32; 4], outer_color: [f32; 4]) {
1446        self.fill_rect_with_full_params_and_slice(rect, self.apply_opacity(inner_color), 16, None, 0.0, Rect { x: 0.0, y: 0.0, width: 1.0, height: 1.0 }, outer_color);
1447    }
1448
1449    fn draw_drop_shadow(&mut self, rect: Rect, radius: f32, color: [f32; 4], blur: f32, spread: f32) {
1450        let margin = blur + spread;
1451        let inflated = Rect {
1452            x: rect.x - margin,
1453            y: rect.y - margin,
1454            width: rect.width + margin * 2.0,
1455            height: rect.height + margin * 2.0,
1456        };
1457        // uv.x = total margin (for SDF offset), uv.y = blur width (for falloff)
1458        self.fill_rect_with_full_params(inflated, self.apply_opacity(color), 18, None, radius, Rect { x: margin, y: blur, width: 0.0, height: 0.0 });
1459    }
1460
1461    fn stroke_dashed_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4], width: f32, dash: f32, gap: f32) {
1462        self.fill_rect_with_full_params(rect, self.apply_opacity(color), 19, None, radius, Rect { x: width, y: dash, width: gap, height: 0.0 });
1463    }
1464
1465    fn draw_9slice(&mut self, image_name: &str, rect: Rect, left: f32, top: f32, right: f32, bottom: f32) {
1466        let c = self.apply_opacity([1.0, 1.0, 1.0, 1.0]);
1467        let tid = self.get_texture_id(image_name);
1468        self.fill_rect_with_full_params(rect, c, 20, tid, bottom, Rect { x: left, y: top, width: right, height: 0.0 });
1469    }
1470
1471    fn draw_line(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, color: [f32; 4], stroke_width: f32) {
1472        let dx = x2 - x1;
1473        let dy = y2 - y1;
1474        let len = (dx * dx + dy * dy).sqrt();
1475        if len < 0.001 { return; }
1476        
1477        let _angle = dy.atan2(dx).to_degrees();
1478        let c = self.apply_opacity(color);
1479        
1480        // Push an oriented quad by using the Mjolnir Slice infrastructure or 
1481        // by calculating rotated vertices. For now, we use a simple rotation push.
1482        // In a future pass, we will add 'rotation' to the Vertex struct for batching.
1483        // For now, we use the centered rect logic.
1484        self.fill_rect_with_mode(
1485            Rect { x: (x1 + x2) / 2.0 - len / 2.0, y: (y1 + y2) / 2.0 - stroke_width / 2.0, width: len, height: stroke_width },
1486            c,
1487            1, // Gungnir Mode for glowing lines
1488            None
1489        );
1490    }
1491
1492    fn draw_text(&mut self, text: &str, x: f32, y: f32, size: f32, color: [f32; 4]) {
1493        // High-Fidelity Text Forge: Layout -> Rasterize -> Atlas -> Quad
1494        let mut buffer = cosmic_text::Buffer::new(&mut self.font_system, cosmic_text::Metrics::new(size, size));
1495        // Use Basic shaping for 'SYSTEM' readouts to avoid aggressive kerning pull-in
1496        buffer.set_text(&mut self.font_system, text, &cosmic_text::Attrs::new(), cosmic_text::Shaping::Basic);
1497        buffer.shape_until_scroll(&mut self.font_system, false);
1498
1499        let c = self.apply_opacity(color);
1500        
1501        let mut glyph_idx = 0;
1502        for run in buffer.layout_runs() {
1503            for glyph in run.glyphs {
1504                // Adaptive Berserker Tracking: More space for small text, tight for headers
1505                let tracking = (2.8 - 0.22 * (size - 16.0)).max(0.6); 
1506                let x_offset = x + (glyph_idx as f32 * tracking);
1507                
1508                let physical_glyph = glyph.physical((x_offset, y), 1.0);
1509                let cache_key = physical_glyph.cache_key;
1510                
1511                // Check cache or rasterize (Keyed by the full CacheKey to support multiple sizes)
1512                let (uv_rect, w, h) = if let Some(info) = self.text_cache.get(&cache_key) {
1513                    *info
1514                } else {
1515                    // Rasterize new glyph
1516                    if let Some(image) = self.swash_cache.get_image(&mut self.font_system, cache_key) {
1517                        let (gx, _gy) = self.text_atlas_pos;
1518                        let gw = image.placement.width;
1519                        let gh = image.placement.height;
1520                        
1521                        // Simple grid packing (Phase 2 Forge)
1522                        if gx + gw > 1024 {
1523                            self.text_atlas_pos.0 = 0;
1524                            self.text_atlas_pos.1 += 64; // Max glyph height
1525                        }
1526                        let (nx, ny) = self.text_atlas_pos;
1527                        
1528                        self.queue.write_texture(
1529                            wgpu::TexelCopyTextureInfo {
1530                                texture: &self.text_atlas_tex,
1531                                mip_level: 0,
1532                                origin: wgpu::Origin3d { x: nx, y: ny, z: 0 },
1533                                aspect: wgpu::TextureAspect::All,
1534                            },
1535                            &image.data,
1536                            wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(gw), rows_per_image: Some(gh) },
1537                            wgpu::Extent3d { width: gw, height: gh, depth_or_array_layers: 1 },
1538                        );
1539                        
1540                        let info = (Rect { x: nx as f32 / 1024.0, y: ny as f32 / 1024.0, width: gw as f32 / 1024.0, height: gh as f32 / 1024.0 }, gw as f32, gh as f32);
1541                        self.text_cache.insert(cache_key, info);
1542                        self.text_atlas_pos.0 += gw + 2;
1543                        info
1544                    } else {
1545                        (Rect::zero(), 0.0, 0.0)
1546                    }
1547                };
1548                
1549                if w > 0.0 {
1550                    let glyph_rect = Rect {
1551                        x: physical_glyph.x as f32,
1552                        y: physical_glyph.y as f32,
1553                        width: w,
1554                        height: h,
1555                    };
1556                    let tid = self.get_texture_id("__text_atlas");
1557                    self.fill_rect_with_full_params(glyph_rect, c, 6, tid, 0.0, uv_rect); // Mode 6 = Text
1558                }
1559                glyph_idx += 1;
1560            }
1561        }
1562    }
1563
1564    fn measure_text(&mut self, text: &str, size: f32) -> (f32, f32) {
1565        let mut buffer = cosmic_text::Buffer::new(&mut self.font_system, cosmic_text::Metrics::new(size, size));
1566        buffer.set_text(&mut self.font_system, text, &cosmic_text::Attrs::new(), cosmic_text::Shaping::Advanced);
1567        buffer.shape_until_scroll(&mut self.font_system, false);
1568        
1569        let mut width = 0.0f32;
1570        let mut height = 0.0f32;
1571        
1572        for run in buffer.layout_runs() {
1573            width = width.max(run.line_w);
1574            height += size; 
1575        }
1576        
1577        (width, height)
1578    }
1579
1580    fn draw_texture(&mut self, texture_id: u32, rect: Rect) {
1581        self.fill_rect_with_full_params(rect, [1.0, 1.0, 1.0, 1.0], 2, Some(texture_id), 0.0, Rect { x: 0.0, y: 0.0, width: 1.0, height: 1.0 });
1582    }
1583
1584    fn draw_image(&mut self, image_name: &str, rect: Rect) {
1585        let tid = self.get_texture_id(image_name);
1586        self.fill_rect_with_full_params(rect, [1.0, 1.0, 1.0, 1.0], 2, tid, 0.0, Rect { x: 0.0, y: 0.0, width: 1.0, height: 1.0 });
1587    }
1588
1589    fn load_image(&mut self, name: &str, data: &[u8]) {
1590        let img = image::load_from_memory(data).expect("Failed to load image").to_rgba8();
1591        let (width, height) = img.dimensions();
1592        let size = wgpu::Extent3d { width, height, depth_or_array_layers: 1 };
1593        let texture = self.device.create_texture(&wgpu::TextureDescriptor {
1594            label: Some(name),
1595            size,
1596            mip_level_count: 1,
1597            sample_count: 1,
1598            dimension: wgpu::TextureDimension::D2,
1599            format: wgpu::TextureFormat::Rgba8UnormSrgb,
1600            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1601            view_formats: &[],
1602        });
1603        self.queue.write_texture(
1604            wgpu::TexelCopyTextureInfo {
1605                texture: &texture,
1606                mip_level: 0,
1607                origin: wgpu::Origin3d::ZERO,
1608                aspect: wgpu::TextureAspect::All,
1609            },
1610            &img,
1611            wgpu::TexelCopyBufferLayout {
1612                offset: 0,
1613                bytes_per_row: Some(4 * width),
1614                rows_per_image: Some(height),
1615            },
1616            size,
1617        );
1618        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
1619        let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
1620            address_mode_u: wgpu::AddressMode::ClampToEdge,
1621            address_mode_v: wgpu::AddressMode::ClampToEdge,
1622            mag_filter: wgpu::FilterMode::Linear,
1623            ..Default::default()
1624        });
1625        let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1626            layout: &self.texture_bind_group_layout,
1627            entries: &[
1628                wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&view) },
1629                wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&sampler) },
1630            ],
1631            label: Some(name),
1632        });
1633        self.texture_bind_groups.push(bind_group);
1634        let id = (self.texture_bind_groups.len() - 1) as u32;
1635        self.texture_registry.insert(name.to_string(), id);
1636    }
1637
1638    fn push_clip_rect(&mut self, rect: Rect) {
1639        self.clip_stack.push(rect);
1640    }
1641
1642    fn pop_clip_rect(&mut self) {
1643        self.clip_stack.pop();
1644    }
1645
1646    fn push_opacity(&mut self, opacity: f32) {
1647        let current = self.opacity_stack.last().copied().unwrap_or(1.0);
1648        self.opacity_stack.push(current * opacity);
1649    }
1650
1651    fn pop_opacity(&mut self) {
1652        self.opacity_stack.pop();
1653    }
1654
1655    fn set_theme(&mut self, theme: ColorTheme) {
1656        self.current_theme = theme;
1657        self.queue.write_buffer(&self.theme_buffer, 0, bytemuck::bytes_of(&theme));
1658    }
1659
1660    fn set_rage(&mut self, rage: f32) {
1661        self.current_scene.berzerker_rage = rage;
1662        // scene_buffer is updated every frame in begin_frame, so no need to write here
1663    }
1664
1665    fn trigger_shatter_event(&mut self, origin: [f32; 2], force: f32) {
1666        self.current_scene.shatter_origin = origin;
1667        self.current_scene.shatter_time = self.current_scene.time;
1668        self.current_scene.shatter_force = force;
1669    }
1670
1671
1672
1673    fn push_mjolnir_slice(&mut self, angle: f32, offset: f32) {
1674        self.slice_stack.push((angle, offset));
1675    }
1676
1677    fn pop_mjolnir_slice(&mut self) {
1678        self.slice_stack.pop();
1679    }
1680
1681    fn mjolnir_shatter(&mut self, rect: Rect, pieces: u32, force: f32, color: [f32; 4]) {
1682        self.shatter_internal(rect, pieces, force, color, 8);
1683    }
1684
1685    fn mjolnir_fluid_shatter(&mut self, rect: Rect, pieces: u32, force: f32, color: [f32; 4]) {
1686        self.shatter_internal(rect, pieces, force, color, 11);
1687    }
1688
1689    fn draw_mjolnir_bolt(&mut self, from: [f32; 2], to: [f32; 2], color: [f32; 4]) {
1690        self.recursive_bolt(from, to, 4, color);
1691    }
1692
1693    fn upload_data_texture(&mut self, id: &str, data: &[f32], width: u32, height: u32) {
1694        let size = wgpu::Extent3d { width, height, depth_or_array_layers: 1 };
1695        let texture = self.device.create_texture(&wgpu::TextureDescriptor {
1696            label: Some(id),
1697            size,
1698            mip_level_count: 1,
1699            sample_count: 1,
1700            dimension: wgpu::TextureDimension::D2,
1701            format: wgpu::TextureFormat::R32Float,
1702            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1703            view_formats: &[],
1704        });
1705        self.queue.write_texture(
1706            wgpu::TexelCopyTextureInfo {
1707                texture: &texture,
1708                mip_level: 0,
1709                origin: wgpu::Origin3d::ZERO,
1710                aspect: wgpu::TextureAspect::All,
1711            },
1712            bytemuck::cast_slice(data),
1713            wgpu::TexelCopyBufferLayout {
1714                offset: 0,
1715                bytes_per_row: Some(4 * width),
1716                rows_per_image: Some(height),
1717            },
1718            size,
1719        );
1720        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
1721        let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
1722            address_mode_u: wgpu::AddressMode::ClampToEdge,
1723            address_mode_v: wgpu::AddressMode::ClampToEdge,
1724            mag_filter: wgpu::FilterMode::Linear,
1725            ..Default::default()
1726        });
1727        let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1728            layout: &self.texture_bind_group_layout,
1729            entries: &[
1730                wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&view) },
1731                wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&sampler) },
1732            ],
1733            label: Some(id),
1734        });
1735        self.texture_bind_groups.push(bind_group);
1736        let tid = (self.texture_bind_groups.len() - 1) as u32;
1737        self.texture_registry.insert(id.to_string(), tid);
1738    }
1739
1740    fn draw_heatmap(&mut self, texture_id: &str, rect: Rect, _palette: &str) {
1741        let tid = self.get_texture_id(texture_id);
1742        self.fill_rect_with_mode(rect, [1.0, 1.0, 1.0, 1.0], 12, tid);
1743    }
1744
1745    fn draw_mesh(&mut self, mesh: &Mesh, color: [f32; 4], transform: glam::Mat4) {
1746        let base_idx = self.vertices.len() as u32;
1747        let screen = [self.config.width as f32, self.config.height as f32];
1748        
1749        for i in 0..mesh.vertices.len() {
1750            let pos = transform.transform_point3(glam::Vec3::from(mesh.vertices[i]));
1751            let norm = transform.transform_vector3(glam::Vec3::from(mesh.normals[i]));
1752            
1753            self.vertices.push(Vertex {
1754                position: pos.to_array(),
1755                normal: norm.to_array(),
1756                uv: [0.0, 0.0],
1757                color,
1758                mode: 13, // Mode 13: 3D Surface
1759                radius: 0.0,
1760                slice: [0.0, 0.0, 0.0, 1.0],
1761                logical: [0.0, 0.0],
1762                size: [0.0, 0.0],
1763                screen,
1764                clip: [-10000.0, -10000.0, 20000.0, 20000.0],
1765            });
1766        }
1767        
1768        for idx in &mesh.indices {
1769            self.indices.push(base_idx + idx);
1770        }
1771        
1772        if self.draw_calls.is_empty() || self.current_texture_id.is_some() {
1773             self.current_texture_id = None;
1774             self.draw_calls.push(DrawCall {
1775                 texture_id: None,
1776                 scissor_rect: self.clip_stack.last().copied(),
1777                 index_start: (self.indices.len() as u32) - (mesh.indices.len() as u32),
1778                 index_count: mesh.indices.len() as u32,
1779                 is_glass: false,
1780                 is_ui: false,
1781             });
1782        } else {
1783             self.draw_calls.last_mut().unwrap().index_count += mesh.indices.len() as u32;
1784        }
1785    }
1786
1787    fn register_shared_element(&mut self, id: &str, rect: Rect) {
1788        self.shared_elements.insert(id.to_string(), rect);
1789    }
1790}
1791
1792
1793impl Drop for SurtrRenderer {
1794    fn drop(&mut self) {
1795        // Ensure GPU is idle before dropping to avoid Swapchain semaphore panics
1796        let _ = self.device.poll(wgpu::PollType::wait_indefinitely());
1797    }
1798}
1799
1800impl cvkg_core::FrameRenderer<wgpu::CommandEncoder> for SurtrRenderer {
1801    fn begin_frame(&mut self) -> wgpu::CommandEncoder {
1802        self.begin_frame()
1803    }
1804
1805    fn end_frame(&mut self, encoder: wgpu::CommandEncoder) {
1806        self.end_frame(encoder)
1807    }
1808}
1809
1810impl SurtrRenderer {
1811    /// Returns the current effective opacity (product of all stacked values).
1812    fn apply_opacity(&self, mut color: [f32; 4]) -> [f32; 4] {
1813        if let Some(&alpha) = self.opacity_stack.last() {
1814            color[3] *= alpha;
1815        }
1816        color
1817    }
1818}