Skip to main content

cvkg_render_native/
renderer.rs

1use std::sync::Arc;
2use winit::window::Window;
3use winit::event_loop::{EventLoop, ControlFlow};
4use cvkg_core::{
5    FrameRenderer, RenderStateSnapshot, Renderer, ColorTheme,
6    RenderIntensityMode, Rect, DrawMaterial, TelemetryData, Mesh,
7};
8use crate::window::{WindowManager, SafeAreaInsets};
9use crate::audio::{RodioAudioEngine, VisualHapticEngine};
10use crate::asset_manager::NativeAssetManager;
11use crate::main_loop::{App, AppEvent};
12
13// Thread-local raw pointer to the locked GpuRenderer for the duration of one render pass.
14// CONTRACT: Set to non-null only while the MutexGuard is live on the call stack.
15// All NativeRenderer draw calls use this pointer to avoid per-call mutex lock overhead.
16// SAFETY: The pointer is valid because the MutexGuard is held for the entire duration the pointer is set.
17thread_local! {
18    pub static GPU_FRAME_PTR: std::cell::Cell<*mut cvkg_render_gpu::GpuRenderer> =
19        const { std::cell::Cell::new(std::ptr::null_mut()) };
20}
21
22/// Native renderer backend implementing the Renderer trait.
23/// It wraps a shared GpuRenderer for high-performance GPU drawing.
24/// During a render pass, GPU_FRAME_PTR is set so draw calls bypass the mutex.
25pub struct NativeRenderer {
26    pub(crate) gpu: Arc<std::sync::Mutex<cvkg_render_gpu::GpuRenderer>>,
27    pub(crate) delta_time: f32,
28    pub(crate) elapsed_time: f32,
29    pub(crate) berserker_mode: RenderIntensityMode,
30    pub(crate) rage: f32,
31    pub(crate) window: Arc<Window>,
32}
33
34impl NativeRenderer {
35    /// Returns a reference to the GPU renderer.
36    /// If GPU_FRAME_PTR is set (we're inside a locked render pass) uses that directly.
37    /// Otherwise falls back to acquiring the mutex (safe for calls outside the render pass).
38    ///
39    /// # Safety
40    /// GPU_FRAME_PTR is only non-null when a MutexGuard is live on the same thread's call stack.
41    #[inline(always)]
42    fn gpu_ref(&mut self) -> impl std::ops::DerefMut<Target = cvkg_render_gpu::GpuRenderer> + '_ {
43        GPU_FRAME_PTR.with(|ptr| {
44            let raw = ptr.get();
45            if !raw.is_null() {
46                // SAFETY: Pointer is valid and the mutex guard is live above us on the call stack.
47                GpuRef::Ptr(unsafe { &mut *raw })
48            } else {
49                GpuRef::Guard(self.gpu.lock().unwrap_or_else(|p| p.into_inner()))
50            }
51        })
52    }
53
54    /// Read-only variant for &self Renderer methods.
55    /// Uses the same thread_local fast path; falls back to mutex for out-of-pass calls.
56    ///
57    /// # Safety
58    /// GPU_FRAME_PTR is only non-null when a MutexGuard is live above us on the call stack.
59    #[inline(always)]
60    fn gpu_ref_shared(&self) -> impl std::ops::Deref<Target = cvkg_render_gpu::GpuRenderer> + '_ {
61        GPU_FRAME_PTR.with(|ptr| {
62            let raw = ptr.get();
63            if !raw.is_null() {
64                // SAFETY: Pointer is valid; the mutex guard is held for the render pass duration.
65                // We only read via this path during &self methods, which is safe.
66                GpuRefShared::Ptr(unsafe { &*raw })
67            } else {
68                GpuRefShared::Guard(self.gpu.lock().unwrap_or_else(|p| p.into_inner()))
69            }
70        })
71    }
72
73    /// Create a new NativeRenderer (internal use by App)
74    pub(crate) fn new(
75        window: Arc<Window>,
76        gpu: Arc<std::sync::Mutex<cvkg_render_gpu::GpuRenderer>>,
77        delta_time: f32,
78        elapsed_time: f32,
79        berserker_mode: RenderIntensityMode,
80        rage: f32,
81    ) -> Self {
82        Self {
83            gpu,
84            delta_time,
85            elapsed_time,
86            berserker_mode,
87            rage,
88            window,
89        }
90    }
91
92    /// Start the CVKG native application with the given view.
93    /// `prewarm_assets` is a list of (name, raw_bytes) pairs uploaded to the GPU
94    /// texture atlas on the first frame before any draw calls.
95    pub fn run<V: cvkg_core::View + 'static>(view: V, prewarm_assets: Option<Vec<(String, Vec<u8>)>>) {
96        let event_loop = EventLoop::<AppEvent>::with_user_event()
97            .build()
98            .expect("failed to create winit event loop: platform initialization failed");
99        event_loop.set_control_flow(ControlFlow::Wait);
100
101        let mut app = App {
102            view,
103            window_manager: WindowManager::new(),
104            gpu: None,
105            asset_manager: std::sync::Arc::new(NativeAssetManager::new()),
106            proxy: event_loop.create_proxy(),
107            start_time: std::time::Instant::now(),
108            last_frame_time: std::time::Instant::now(),
109            berserker_mode: RenderIntensityMode::Normal,
110            rage: 0.0,
111            state_detector: crate::window::WindowStateDetector::new(),
112            frame_budget: cvkg_core::FrameBudgetTracker::default_120fps(),
113            modifiers: winit::keyboard::ModifiersState::default(),
114            audio_engine: None,
115            haptic_engine: Arc::new(VisualHapticEngine::new()),
116            pending_prewarm: prewarm_assets,
117        };
118
119        event_loop.run_app(&mut app).expect("winit event loop terminated with error");
120    }
121
122    /// Convenience: run with a single background image loaded from a file path.
123    /// The image is loaded from disk and pre-warmed on the first frame.
124    /// `image_name` is the key used in `draw_image` / `draw_background_image`.
125    pub fn run_with_background<V: cvkg_core::View + 'static>(view: V, image_name: &str, image_path: &str) {
126        let image_data = std::fs::read(image_path)
127            .unwrap_or_else(|e| panic!("Failed to load background image '{}': {}", image_path, e));
128        let assets = vec![(image_name.to_string(), image_data)];
129        Self::run(view, Some(assets));
130    }
131}
132
133/// Returned by NativeRenderer::gpu_ref() — either a direct pointer ref or a mutex guard.
134enum GpuRef<'a> {
135    Ptr(&'a mut cvkg_render_gpu::GpuRenderer),
136    Guard(std::sync::MutexGuard<'a, cvkg_render_gpu::GpuRenderer>),
137}
138
139impl<'a> std::ops::Deref for GpuRef<'a> {
140    type Target = cvkg_render_gpu::GpuRenderer;
141    fn deref(&self) -> &Self::Target {
142        match self {
143            GpuRef::Ptr(r) => r,
144            GpuRef::Guard(g) => g,
145        }
146    }
147}
148
149impl<'a> std::ops::DerefMut for GpuRef<'a> {
150    fn deref_mut(&mut self) -> &mut Self::Target {
151        match self {
152            GpuRef::Ptr(r) => r,
153            GpuRef::Guard(g) => &mut *g,
154        }
155    }
156}
157
158/// Read-only variant returned by NativeRenderer::gpu_ref_shared().
159enum GpuRefShared<'a> {
160    Ptr(&'a cvkg_render_gpu::GpuRenderer),
161    Guard(std::sync::MutexGuard<'a, cvkg_render_gpu::GpuRenderer>),
162}
163
164impl<'a> std::ops::Deref for GpuRefShared<'a> {
165    type Target = cvkg_render_gpu::GpuRenderer;
166    fn deref(&self) -> &Self::Target {
167        match self {
168            GpuRefShared::Ptr(r) => r,
169            GpuRefShared::Guard(g) => g,
170        }
171    }
172}
173
174impl cvkg_core::ElapsedTime for NativeRenderer {
175    fn delta_time(&self) -> f32 {
176        self.delta_time
177    }
178
179    fn elapsed_time(&self) -> f32 {
180        self.elapsed_time
181    }
182}
183
184impl cvkg_core::Renderer for NativeRenderer {
185    fn fill_rect(&mut self, rect: Rect, color: [f32; 4]) {
186        self.gpu_ref().fill_rect(rect, color);
187    }
188    fn fill_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4]) {
189        self.gpu_ref().fill_rounded_rect(rect, radius, color);
190    }
191    fn fill_ellipse(&mut self, rect: Rect, color: [f32; 4]) {
192        self.gpu_ref().fill_ellipse(rect, color);
193    }
194    fn stroke_rect(&mut self, rect: Rect, color: [f32; 4], stroke_width: f32) {
195        self.gpu_ref().stroke_rect(rect, color, stroke_width);
196    }
197    fn stroke_rounded_rect(
198        &mut self,
199        rect: Rect,
200        radius: f32,
201        color: [f32; 4],
202        stroke_width: f32,
203    ) {
204        self.gpu_ref().stroke_rounded_rect(rect, radius, color, stroke_width);
205    }
206    fn stroke_ellipse(&mut self, rect: Rect, color: [f32; 4], stroke_width: f32) {
207        self.gpu_ref().stroke_ellipse(rect, color, stroke_width);
208    }
209    fn draw_line(
210        &mut self,
211        x1: f32,
212        y1: f32,
213        x2: f32,
214        y2: f32,
215        color: [f32; 4],
216        stroke_width: f32,
217    ) {
218        self.gpu_ref()
219            .draw_line(x1, y1, x2, y2, color, stroke_width);
220    }
221
222    fn fill_glass_rect(&mut self, rect: Rect, radius: f32, blur_radius: f32) {
223        self.gpu_ref()
224            .fill_glass_rect(rect, radius, blur_radius);
225    }
226
227    fn fill_glass_rect_with_intensity(&mut self, rect: Rect, radius: f32, blur_radius: f32, glass_intensity: f32) {
228        self.gpu_ref()
229            .fill_glass_rect_with_intensity(rect, radius, blur_radius, glass_intensity);
230    }
231
232    fn fill_glass_rect_with_pressure(&mut self, rect: Rect, radius: f32, blur_radius: f32, pressure: f32) {
233        self.gpu_ref()
234            .fill_glass_rect_with_intensity(rect, radius, blur_radius, pressure);
235    }
236
237    fn fill_squircle(&mut self, rect: Rect, n: f32, color: [f32; 4]) {
238        self.gpu_ref()
239            .fill_squircle(rect, n, color);
240    }
241
242    fn stroke_squircle(&mut self, rect: Rect, n: f32, color: [f32; 4], stroke_width: f32) {
243        self.gpu_ref()
244            .stroke_squircle(rect, n, color, stroke_width);
245    }
246
247    fn draw_focus_ring(&mut self, rect: Rect, radius: f32, offset: f32, width: f32, color: [f32; 4]) {
248        self.gpu_ref()
249            .draw_focus_ring(rect, radius, offset, width, color);
250    }
251
252    fn draw_linear_gradient(
253        &mut self,
254        rect: Rect,
255        start_color: [f32; 4],
256        end_color: [f32; 4],
257        angle: f32,
258    ) {
259        self.gpu_ref()
260            .draw_linear_gradient(rect, start_color, end_color, angle);
261    }
262    fn draw_radial_gradient(
263        &mut self,
264        rect: Rect,
265        inner_color: [f32; 4],
266        outer_color: [f32; 4],
267    ) {
268        self.gpu_ref()
269            .draw_radial_gradient(rect, inner_color, outer_color);
270    }
271    fn draw_texture(&mut self, texture_id: u32, rect: Rect) {
272        self.gpu_ref()
273            .draw_texture(texture_id, rect);
274    }
275    fn draw_image(&mut self, image_name: &str, rect: Rect) {
276        self.gpu_ref()
277            .draw_image(image_name, rect);
278    }
279    fn load_image(&mut self, name: &str, data: &[u8]) {
280        self.gpu_ref()
281            .load_image(name, data);
282    }
283    fn push_clip_rect(&mut self, rect: Rect) {
284        self.gpu_ref()
285            .push_clip_rect(rect);
286    }
287    fn pop_clip_rect(&mut self) {
288        self.gpu_ref()
289            .pop_clip_rect();
290    }
291    fn push_opacity(&mut self, opacity: f32) {
292        self.gpu_ref()
293            .push_opacity(opacity);
294    }
295    fn draw_3d_cube(&mut self, rect: Rect, color: [f32; 4], rotation: [f32; 3]) {
296        self.gpu_ref()
297            .draw_3d_cube(rect, color, rotation);
298    }
299    fn render_scene_node_3d(
300        &mut self,
301        position: [f32; 3],
302        rotation: [f32; 4],
303        scale: [f32; 3],
304        color: [f32; 4],
305        meshes: &[Mesh],
306    ) {
307        self.gpu_ref()
308            .render_scene_node_3d(position, rotation, scale, color, meshes);
309    }
310    fn pop_opacity(&mut self) {
311        self.gpu_ref()
312            .pop_opacity();
313    }
314    fn bifrost(&mut self, rect: Rect, blur: f32, saturation: f32, opacity: f32) {
315        self.gpu_ref()
316            .bifrost(rect, blur, saturation, opacity);
317    }
318    fn push_mjolnir_slice(&mut self, angle: f32, offset: f32) {
319        self.gpu_ref()
320            .push_mjolnir_slice(angle, offset);
321    }
322    fn pop_mjolnir_slice(&mut self) {
323        self.gpu_ref()
324            .pop_mjolnir_slice();
325    }
326    fn mjolnir_shatter(&mut self, rect: Rect, pieces: u32, force: f32, color: [f32; 4]) {
327        self.gpu_ref()
328            .mjolnir_shatter(rect, pieces, force, color);
329    }
330    fn mjolnir_fluid_shatter(
331        &mut self,
332        rect: Rect,
333        pieces: u32,
334        force: f32,
335        color: [f32; 4],
336    ) {
337        self.gpu_ref()
338            .mjolnir_fluid_shatter(rect, pieces, force, color);
339    }
340    fn draw_mjolnir_bolt(&mut self, from: [f32; 2], to: [f32; 2], color: [f32; 4]) {
341        self.gpu_ref()
342            .draw_mjolnir_bolt(from, to, color);
343    }
344    fn gungnir(&mut self, rect: Rect, color: [f32; 4], radius: f32, intensity: f32) {
345        self.gpu_ref()
346            .gungnir(rect, color, radius, intensity);
347    }
348    fn mani_glow(&mut self, rect: Rect, color: [f32; 4], radius: f32) {
349        self.gpu_ref()
350            .mani_glow(rect, color, radius);
351    }
352    fn register_handler(
353        &mut self,
354        event_type: &str,
355        handler: Arc<dyn Fn(cvkg_core::Event) + Send + Sync>,
356    ) {
357        self.gpu_ref()
358            .register_handler(event_type, handler);
359    }
360    fn push_vnode(&mut self, rect: Rect, name: &'static str) {
361        self.gpu_ref()
362            .push_vnode(rect, name);
363    }
364    fn pop_vnode(&mut self) {
365        self.gpu_ref()
366            .pop_vnode();
367    }
368    fn set_z_index(&mut self, z: f32) {
369        self.gpu_ref()
370            .set_z_index(z);
371    }
372    fn get_z_index(&self) -> f32 {
373        self.gpu_ref_shared()
374            .get_z_index()
375    }
376    fn register_shared_element(&mut self, id: &str, rect: Rect) {
377        self.gpu_ref()
378            .register_shared_element(id, rect);
379    }
380    fn set_material(&mut self, material: DrawMaterial) {
381        self.gpu_ref()
382            .set_material(material);
383    }
384    fn current_material(&self) -> DrawMaterial {
385        self.gpu_ref_shared()
386            .current_material()
387    }
388    fn serialize_svg(&mut self, name: &str) -> Result<String, String> {
389        self.gpu_ref()
390            .serialize_svg(name)
391    }
392    fn apply_svg_filter(
393        &mut self,
394        name: &str,
395        filter_id: &str,
396        region: Rect,
397    ) -> Result<String, String> {
398        self.gpu_ref()
399            .apply_svg_filter(name, filter_id, region)
400    }
401    fn push_shadow(&mut self, radius: f32, color: [f32; 4], offset: [f32; 2]) {
402        self.gpu_ref()
403            .push_shadow(radius, color, offset);
404    }
405    fn pop_shadow(&mut self) {
406        self.gpu_ref()
407            .pop_shadow();
408    }
409    fn push_affine(&mut self, transform: [f32; 6]) {
410        self.gpu_ref()
411            .push_affine(transform);
412    }
413    fn enter_portal(&mut self, z_index: i32) {
414        log::warn!(
415            "Portal rendering (enter_portal) not yet implemented in GPU backend; z_index={}",
416            z_index
417        );
418    }
419    fn exit_portal(&mut self) {
420        log::warn!("Portal rendering (exit_portal) not yet implemented in GPU backend");
421    }
422    fn viewport_size(&self) -> Rect {
423        let size = self.window.inner_size();
424        let scale = self.window.scale_factor();
425        let logical = size.to_logical::<f32>(scale);
426        Rect::new(0.0, 0.0, logical.width, logical.height)
427    }
428    fn announce(&mut self, message: &str, priority: cvkg_core::AnnouncementPriority) {
429        log::info!("Accessibility announcement [{:?}]: {}", priority, message);
430    }
431    fn load_svg(&mut self, name: &str, svg_data: &[u8]) {
432        self.gpu_ref()
433            .load_svg(name, svg_data);
434    }
435    fn draw_svg(&mut self, name: &str, rect: Rect) {
436        self.gpu_ref()
437            .draw_svg(name, rect, None, 0);
438    }
439    fn draw_svg_with_offset(&mut self, name: &str, rect: Rect, animation_time_offset: f32) {
440        self.gpu_ref()
441            .draw_svg_with_offset(name, rect, None, 0, animation_time_offset);
442    }
443    fn get_telemetry(&self) -> TelemetryData {
444        self.gpu_ref_shared()
445            .telemetry
446            .clone()
447    }
448    fn prewarm_vram(&mut self, assets: Vec<(String, Vec<u8>)>) {
449        self.gpu_ref()
450            .prewarm_vram(assets);
451    }
452
453    fn text_scale_factor(&self) -> f32 {
454        self.gpu_ref_shared()
455            .text_scale_factor()
456    }
457
458    fn is_over_budget(&self) -> bool {
459        self.gpu_ref_shared()
460            .is_over_budget()
461    }
462
463    fn draw_text(&mut self, text: &str, x: f32, y: f32, size: f32, color: [f32; 4]) {
464        self.gpu_ref()
465            .draw_text(text, x, y, size, color);
466    }
467
468    fn measure_text(&mut self, text: &str, size: f32) -> (f32, f32) {
469        self.gpu_ref()
470            .measure_text(text, size)
471    }
472
473    fn shape_rich_text(
474        &mut self,
475        spans: &[runic_text::TextSpan],
476        max_width: Option<f32>,
477        align: runic_text::TextAlign,
478        overflow: runic_text::TextOverflow,
479    ) -> Option<runic_text::ShapedText> {
480        self.gpu_ref()
481            .shape_rich_text(spans, max_width, align, overflow)
482    }
483
484    fn draw_shaped_text(&mut self, shaped: &runic_text::ShapedText, x: f32, y: f32) {
485        self.gpu_ref()
486            .draw_shaped_text(shaped, x, y);
487    }
488
489    fn fill_glass_rect_with_tint(
490        &mut self,
491        rect: Rect,
492        radius: f32,
493        blur_radius: f32,
494        tint_color: [f32; 4],
495        glass_intensity: f32,
496    ) {
497        self.gpu_ref()
498            .fill_glass_rect_with_tint(rect, radius, blur_radius, tint_color, glass_intensity);
499    }
500
501    fn set_theme(&mut self, theme: ColorTheme) {
502        self.gpu_ref()
503            .set_theme(theme);
504    }
505
506    fn trigger_shatter_event(&mut self, origin: [f32; 2], force: f32) {
507        self.gpu_ref()
508            .trigger_shatter_event(origin, force);
509    }
510
511    fn set_fireball_pos(&mut self, pos: [f32; 2]) {
512        self.gpu_ref()
513            .set_fireball_pos(pos);
514    }
515
516    fn set_scene(&mut self, scene: &str) {
517        self.gpu_ref()
518            .set_scene(scene);
519    }
520
521    fn set_scene_preset(&mut self, preset: u32) {
522        self.gpu_ref()
523            .set_scene_preset(preset);
524    }
525
526    fn set_default_background_color(&mut self, color: [f32; 4]) {
527        self.gpu_ref()
528            .set_default_background_color(color);
529    }
530    fn push_transform(&mut self, translation: [f32; 2], scale: [f32; 2], rotation: f32) {
531        self.gpu_ref()
532            .push_transform(translation, scale, rotation);
533    }
534    fn pop_transform(&mut self) {
535        self.gpu_ref()
536            .pop_transform();
537    }
538
539    fn set_berserker_mode(&mut self, state: RenderIntensityMode) {
540        self.berserker_mode = state;
541
542        if state == RenderIntensityMode::GodMode {
543            log::info!("ENTERING GOD MODE: Activating Berserker Determinism (High Priority)");
544            #[cfg(target_os = "linux")]
545            unsafe {
546                let _ = libc::setpriority(libc::PRIO_PROCESS, 0, -10);
547            }
548        } else {
549            #[cfg(target_os = "linux")]
550            unsafe {
551                let _ = libc::setpriority(libc::PRIO_PROCESS, 0, 0);
552            }
553        }
554
555        self.gpu_ref()
556            .set_berserker_mode(state);
557    }
558
559    fn set_rage(&mut self, rage: f32) {
560        self.rage = rage;
561        self.gpu_ref()
562            .set_rage(rage);
563    }
564
565    fn memoize(&mut self, id: u64, data_hash: u64, render_fn: &dyn Fn(&mut dyn Renderer)) {
566        self.gpu_ref()
567            .memoize(id, data_hash, render_fn);
568    }
569
570    fn snapshot_render_state(&self) -> RenderStateSnapshot {
571        self.gpu_ref_shared()
572            .snapshot_render_state()
573    }
574
575    fn restore_render_state(&mut self, snap: RenderStateSnapshot) {
576        self.gpu_ref()
577            .restore_render_state(snap);
578    }
579    fn request_redraw(&mut self) {
580        self.window.request_redraw();
581    }
582
583    fn capture_png(&mut self) -> Vec<u8> {
584        log::info!("CAPTURING_FRAME: Initiating GPU readback...");
585        let gpu = self.gpu.lock().unwrap_or_else(|p| p.into_inner());
586        pollster::block_on(gpu.capture_frame()).unwrap_or_else(|e| {
587            log::error!("GPU frame capture failed: {}", e);
588            Vec::new()
589        })
590    }
591
592    fn print(&mut self) {
593        log::info!("PRINT_BRIDGE: Spooling mission status to native printer...");
594        println!("[BRIDGE] PRINTER_READY // SPOOLING_DATA...");
595    }
596}