Skip to main content

cvkg_render_native/
renderer.rs

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