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, Material3D, Mesh, Rect, RenderIntensityMode,
7 RenderStateSnapshot, Renderer, TelemetryData, Transform3D,
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
15thread_local! {
16 pub(crate) static GPU_FRAME_PTR: std::cell::Cell<*mut cvkg_render_gpu::GpuRenderer> =
25 const { std::cell::Cell::new(std::ptr::null_mut()) };
26}
27
28pub(crate) struct GpuFramePtrGuard;
40
41impl GpuFramePtrGuard {
42 pub(crate) unsafe fn set(ptr: *mut cvkg_render_gpu::GpuRenderer) -> Self {
47 GPU_FRAME_PTR.with(|cell| cell.set(ptr));
48 Self
49 }
50}
51
52impl Drop for GpuFramePtrGuard {
53 fn drop(&mut self) {
54 GPU_FRAME_PTR.with(|cell| cell.set(std::ptr::null_mut()));
55 }
56}
57
58pub struct NativeRenderer {
62 pub(crate) gpu: Arc<std::sync::Mutex<cvkg_render_gpu::GpuRenderer>>,
63 pub(crate) delta_time: f32,
64 pub(crate) elapsed_time: f32,
65 pub(crate) berserker_mode: RenderIntensityMode,
66 pub(crate) rage: f32,
67 pub(crate) window: Arc<Window>,
68}
69
70impl NativeRenderer {
71 #[inline(always)]
79 fn gpu_ref(&mut self) -> impl std::ops::DerefMut<Target = cvkg_render_gpu::GpuRenderer> + '_ {
80 GPU_FRAME_PTR.with(|ptr| {
81 let raw = ptr.get();
82 if !raw.is_null() {
83 GpuRef::Ptr(unsafe { &mut *raw })
85 } else {
86 GpuRef::Guard(self.gpu.lock().unwrap_or_else(|p| p.into_inner()))
87 }
88 })
89 }
90
91 #[inline(always)]
97 fn gpu_ref_shared(&self) -> impl std::ops::Deref<Target = cvkg_render_gpu::GpuRenderer> + '_ {
98 GPU_FRAME_PTR.with(|ptr| {
99 let raw = ptr.get();
100 if !raw.is_null() {
101 GpuRefShared::Ptr(unsafe { &*raw })
104 } else {
105 GpuRefShared::Guard(self.gpu.lock().unwrap_or_else(|p| p.into_inner()))
106 }
107 })
108 }
109
110 pub(crate) fn new(
112 window: Arc<Window>,
113 gpu: Arc<std::sync::Mutex<cvkg_render_gpu::GpuRenderer>>,
114 delta_time: f32,
115 elapsed_time: f32,
116 berserker_mode: RenderIntensityMode,
117 rage: f32,
118 ) -> Self {
119 Self {
120 gpu,
121 delta_time,
122 elapsed_time,
123 berserker_mode,
124 rage,
125 window,
126 }
127 }
128
129 pub fn run<V: cvkg_core::View + 'static>(
133 view: V,
134 prewarm_assets: Option<Vec<(String, Vec<u8>)>>,
135 ) {
136 env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info"))
137 .format_timestamp_millis()
138 .try_init()
139 .ok();
140
141 let event_loop = EventLoop::<AppEvent>::with_user_event()
142 .with_any_thread(true)
143 .build()
144 .expect("failed to create winit event loop: platform initialization failed");
145 event_loop.set_control_flow(ControlFlow::Wait);
146
147 let mut app = App {
148 view,
149 window_manager: WindowManager::new(),
150 gpu: None,
151 asset_manager: std::sync::Arc::new(NativeAssetManager::new()),
152 proxy: event_loop.create_proxy(),
153 start_time: std::time::Instant::now(),
154 last_frame_time: std::time::Instant::now(),
155 berserker_mode: RenderIntensityMode::Normal,
156 rage: 0.0,
157 state_detector: crate::window::WindowStateDetector::new(),
158 frame_budget: cvkg_core::FrameBudgetTracker::default_120fps(),
159 modifiers: winit::keyboard::ModifiersState::default(),
160 audio_engine: None,
161 haptic_engine: Arc::new(VisualHapticEngine::new()),
162 pending_prewarm: prewarm_assets,
163 };
164
165 event_loop
166 .run_app(&mut app)
167 .expect("winit event loop terminated with error");
168 }
169
170 pub fn run_with_background<V: cvkg_core::View + 'static>(
174 view: V,
175 image_name: &str,
176 image_path: &str,
177 ) {
178 let image_data = match std::fs::read(image_path) {
179 Ok(data) => data,
180 Err(e) => {
181 tracing::error!("Failed to load background image '{}': {}", image_path, e);
182 Self::run(view, None);
184 return;
185 }
186 };
187 let assets = vec![(image_name.to_string(), image_data)];
188 Self::run(view, Some(assets));
189 }
190}
191
192enum GpuRef<'a> {
194 Ptr(&'a mut cvkg_render_gpu::GpuRenderer),
195 Guard(std::sync::MutexGuard<'a, cvkg_render_gpu::GpuRenderer>),
196}
197
198impl<'a> std::ops::Deref for GpuRef<'a> {
199 type Target = cvkg_render_gpu::GpuRenderer;
200 fn deref(&self) -> &Self::Target {
201 match self {
202 GpuRef::Ptr(r) => r,
203 GpuRef::Guard(g) => g,
204 }
205 }
206}
207
208impl<'a> std::ops::DerefMut for GpuRef<'a> {
209 fn deref_mut(&mut self) -> &mut Self::Target {
210 match self {
211 GpuRef::Ptr(r) => r,
212 GpuRef::Guard(g) => &mut *g,
213 }
214 }
215}
216
217enum GpuRefShared<'a> {
219 Ptr(&'a cvkg_render_gpu::GpuRenderer),
220 Guard(std::sync::MutexGuard<'a, cvkg_render_gpu::GpuRenderer>),
221}
222
223impl<'a> std::ops::Deref for GpuRefShared<'a> {
224 type Target = cvkg_render_gpu::GpuRenderer;
225 fn deref(&self) -> &Self::Target {
226 match self {
227 GpuRefShared::Ptr(r) => r,
228 GpuRefShared::Guard(g) => g,
229 }
230 }
231}
232
233impl cvkg_core::ElapsedTime for NativeRenderer {
234 fn delta_time(&self) -> f32 {
235 self.delta_time
236 }
237
238 fn elapsed_time(&self) -> f32 {
239 self.elapsed_time
240 }
241}
242
243impl cvkg_core::RendererErrorHandler for NativeRenderer {}
244
245impl cvkg_core::Renderer for NativeRenderer {
246 fn begin_world_space_panel(
247 &mut self,
248 node_id: u64,
249 transform: &cvkg_core::Transform3D,
250 glass: Option<cvkg_materials::GlassMaterial>,
251 pixels_per_unit: f32,
252 world_size: (f32, f32),
253 ) {
254 self.gpu_ref().begin_world_space_panel(
255 node_id,
256 transform,
257 glass,
258 pixels_per_unit,
259 world_size,
260 );
261 }
262
263 fn end_world_space_panel(&mut self, node_id: u64) {
264 self.gpu_ref().end_world_space_panel(node_id);
265 }
266
267 fn draw_mesh_3d(&mut self, mesh: &Mesh, material: &Material3D, transform: &Transform3D) {
268 self.gpu_ref().draw_mesh_3d(mesh, material, transform);
269 }
270
271 fn fill_rect(&mut self, rect: Rect, color: [f32; 4]) {
272 self.gpu_ref().fill_rect(rect, color);
273 }
274 fn fill_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4]) {
275 self.gpu_ref().fill_rounded_rect(rect, radius, color);
276 }
277 fn fill_ellipse(&mut self, rect: Rect, color: [f32; 4]) {
278 self.gpu_ref().fill_ellipse(rect, color);
279 }
280 fn stroke_rect(&mut self, rect: Rect, color: [f32; 4], stroke_width: f32) {
281 self.gpu_ref().stroke_rect(rect, color, stroke_width);
282 }
283 fn stroke_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4], stroke_width: f32) {
284 self.gpu_ref()
285 .stroke_rounded_rect(rect, radius, color, stroke_width);
286 }
287 fn stroke_ellipse(&mut self, rect: Rect, color: [f32; 4], stroke_width: f32) {
288 self.gpu_ref().stroke_ellipse(rect, color, stroke_width);
289 }
290 fn draw_line(
291 &mut self,
292 x1: f32,
293 y1: f32,
294 x2: f32,
295 y2: f32,
296 color: [f32; 4],
297 stroke_width: f32,
298 ) {
299 self.gpu_ref()
300 .draw_line(x1, y1, x2, y2, color, stroke_width);
301 }
302
303 fn fill_glass_rect(&mut self, rect: Rect, radius: f32, blur_radius: f32) {
304 self.gpu_ref().fill_glass_rect(rect, radius, blur_radius);
305 }
306
307 fn fill_glass_rect_with_intensity(
308 &mut self,
309 rect: Rect,
310 radius: f32,
311 blur_radius: f32,
312 glass_intensity: f32,
313 ) {
314 self.gpu_ref()
315 .fill_glass_rect_with_intensity(rect, radius, blur_radius, glass_intensity);
316 }
317
318 fn fill_glass_rect_with_pressure(
319 &mut self,
320 rect: Rect,
321 radius: f32,
322 blur_radius: f32,
323 pressure: f32,
324 ) {
325 self.gpu_ref()
326 .fill_glass_rect_with_intensity(rect, radius, blur_radius, pressure);
327 }
328
329 fn fill_squircle(&mut self, rect: Rect, n: f32, color: [f32; 4]) {
330 self.gpu_ref().fill_squircle(rect, n, color);
331 }
332
333 fn stroke_squircle(&mut self, rect: Rect, n: f32, color: [f32; 4], stroke_width: f32) {
334 self.gpu_ref().stroke_squircle(rect, n, color, stroke_width);
335 }
336
337 fn draw_focus_ring(
338 &mut self,
339 rect: Rect,
340 radius: f32,
341 offset: f32,
342 width: f32,
343 color: [f32; 4],
344 ) {
345 self.gpu_ref()
346 .draw_focus_ring(rect, radius, offset, width, color);
347 }
348
349 fn draw_linear_gradient(
350 &mut self,
351 rect: Rect,
352 start_color: [f32; 4],
353 end_color: [f32; 4],
354 angle: f32,
355 ) {
356 self.gpu_ref()
357 .draw_linear_gradient(rect, start_color, end_color, angle);
358 }
359 fn draw_radial_gradient(&mut self, rect: Rect, inner_color: [f32; 4], outer_color: [f32; 4]) {
360 self.gpu_ref()
361 .draw_radial_gradient(rect, inner_color, outer_color);
362 }
363 fn draw_texture(&mut self, texture_id: u32, rect: Rect) {
364 self.gpu_ref().draw_texture(texture_id, rect);
365 }
366 fn draw_image(&mut self, image_name: &str, rect: Rect) {
367 self.gpu_ref().draw_image(image_name, rect);
368 }
369 fn load_image(&mut self, name: &str, data: &[u8]) {
370 self.gpu_ref().load_image(name, data);
371 }
372 fn push_clip_rect(&mut self, rect: Rect) {
373 self.gpu_ref().push_clip_rect(rect);
374 }
375 fn pop_clip_rect(&mut self) {
376 self.gpu_ref().pop_clip_rect();
377 }
378 fn push_opacity(&mut self, opacity: f32) {
379 self.gpu_ref().push_opacity(opacity);
380 }
381 fn draw_3d_cube(&mut self, rect: Rect, color: [f32; 4], rotation: [f32; 3]) {
382 self.gpu_ref().draw_3d_cube(rect, color, rotation);
383 }
384 fn render_scene_node_3d(
385 &mut self,
386 position: [f32; 3],
387 rotation: [f32; 4],
388 scale: [f32; 3],
389 color: [f32; 4],
390 meshes: &[Mesh],
391 ) {
392 self.gpu_ref()
393 .render_scene_node_3d(position, rotation, scale, color, meshes);
394 }
395 fn pop_opacity(&mut self) {
396 self.gpu_ref().pop_opacity();
397 }
398 fn bifrost(&mut self, rect: Rect, blur: f32, saturation: f32, opacity: f32) {
399 self.gpu_ref().bifrost(rect, blur, saturation, opacity);
400 }
401 fn push_mjolnir_slice(&mut self, angle: f32, offset: f32) {
402 self.gpu_ref().push_mjolnir_slice(angle, offset);
403 }
404 fn pop_mjolnir_slice(&mut self) {
405 self.gpu_ref().pop_mjolnir_slice();
406 }
407 fn mjolnir_shatter(&mut self, rect: Rect, pieces: u32, force: f32, color: [f32; 4]) {
408 self.gpu_ref().mjolnir_shatter(rect, pieces, force, color);
409 }
410 fn mjolnir_fluid_shatter(&mut self, rect: Rect, pieces: u32, force: f32, color: [f32; 4]) {
411 self.gpu_ref()
412 .mjolnir_fluid_shatter(rect, pieces, force, color);
413 }
414 fn draw_mjolnir_bolt(&mut self, from: [f32; 2], to: [f32; 2], color: [f32; 4]) {
415 self.gpu_ref().draw_mjolnir_bolt(from, to, color);
416 }
417 fn gungnir(&mut self, rect: Rect, color: [f32; 4], radius: f32, intensity: f32) {
418 self.gpu_ref().gungnir(rect, color, radius, intensity);
419 }
420 fn mani_glow(&mut self, rect: Rect, color: [f32; 4], radius: f32) {
421 self.gpu_ref().mani_glow(rect, color, radius);
422 }
423 fn register_handler(
424 &mut self,
425 event_type: &str,
426 handler: Arc<dyn Fn(cvkg_core::Event) + Send + Sync>,
427 ) {
428 self.gpu_ref().register_handler(event_type, handler);
429 }
430 fn push_vnode(&mut self, rect: Rect, name: &'static str) {
431 self.gpu_ref().push_vnode(rect, name);
432 }
433 fn pop_vnode(&mut self) {
434 self.gpu_ref().pop_vnode();
435 }
436 fn set_z_index(&mut self, z: f32) {
437 self.gpu_ref().set_z_index(z);
438 }
439 fn get_z_index(&self) -> f32 {
440 self.gpu_ref_shared().get_z_index()
441 }
442 fn register_shared_element(&mut self, id: &str, rect: Rect) {
443 self.gpu_ref().register_shared_element(id, rect);
444 }
445 fn set_material(&mut self, material: DrawMaterial) {
446 self.gpu_ref().set_material(material);
447 }
448 fn current_material(&self) -> DrawMaterial {
449 self.gpu_ref_shared().current_material()
450 }
451 fn serialize_svg(&mut self, name: &str) -> Result<String, String> {
452 self.gpu_ref().serialize_svg(name)
453 }
454 fn apply_svg_filter(
455 &mut self,
456 name: &str,
457 filter_id: &str,
458 region: Rect,
459 ) -> Result<String, String> {
460 self.gpu_ref().apply_svg_filter(name, filter_id, region)
461 }
462 fn push_shadow(&mut self, radius: f32, color: [f32; 4], offset: [f32; 2]) {
463 self.gpu_ref().push_shadow(radius, color, offset);
464 }
465 fn pop_shadow(&mut self) {
466 self.gpu_ref().pop_shadow();
467 }
468 fn push_affine(&mut self, transform: [f32; 6]) {
469 self.gpu_ref().push_affine(transform);
470 }
471 fn enter_portal(&mut self, z_index: i32) {
472 tracing::warn!(
473 "Portal rendering (enter_portal) not yet implemented in GPU backend; z_index={}",
474 z_index
475 );
476 }
477 fn exit_portal(&mut self) {
478 tracing::warn!("Portal rendering (exit_portal) not yet implemented in GPU backend");
479 }
480 fn viewport_size(&self) -> Rect {
481 let size = self.window.inner_size();
482 let scale = self.window.scale_factor();
483 let logical = size.to_logical::<f32>(scale);
484 Rect::new(0.0, 0.0, logical.width, logical.height)
485 }
486 fn announce(&mut self, message: &str, priority: cvkg_core::AnnouncementPriority) {
487 tracing::info!("Accessibility announcement [{:?}]: {}", priority, message);
488 }
489 fn load_svg(&mut self, name: &str, svg_data: &[u8]) {
490 self.gpu_ref().load_svg(name, svg_data);
491 }
492 fn draw_svg(&mut self, name: &str, rect: Rect) {
493 self.gpu_ref().draw_svg(name, rect, None, 0);
494 }
495 fn draw_svg_with_offset(&mut self, name: &str, rect: Rect, animation_time_offset: f32) {
496 self.gpu_ref()
497 .draw_svg_with_offset(name, rect, None, 0, animation_time_offset);
498 }
499 fn get_telemetry(&self) -> TelemetryData {
500 self.gpu_ref_shared().telemetry.clone()
501 }
502 fn prewarm_vram(&mut self, assets: Vec<(String, Vec<u8>)>) {
503 self.gpu_ref().prewarm_vram(assets);
504 }
505
506 fn text_scale_factor(&self) -> f32 {
507 self.gpu_ref_shared().text_scale_factor()
508 }
509
510 fn is_over_budget(&self) -> bool {
511 self.gpu_ref_shared().is_over_budget()
512 }
513
514 fn draw_text(
515 &mut self,
516 text: &str,
517 rect: &Rect,
518 size: f32,
519 color: [f32; 4],
520 h_align: cvkg_core::TextHAlign,
521 v_align: cvkg_core::TextVAlign,
522 ) {
523 self.gpu_ref()
524 .draw_text(text, rect, size, color, h_align, v_align);
525 }
526
527 fn measure_text(&mut self, text: &str, size: f32) -> (f32, f32) {
528 self.gpu_ref().measure_text(text, size)
529 }
530
531 fn shape_rich_text(
532 &mut self,
533 spans: &[runic_text::TextSpan],
534 max_width: Option<f32>,
535 align: runic_text::TextAlign,
536 overflow: runic_text::TextOverflow,
537 ) -> Option<runic_text::ShapedText> {
538 self.gpu_ref()
539 .shape_rich_text(spans, max_width, align, overflow)
540 }
541
542 fn draw_shaped_text(&mut self, shaped: &runic_text::ShapedText, x: f32, y: f32) {
543 self.gpu_ref().draw_shaped_text(shaped, x, y);
544 }
545
546 fn fill_glass_rect_with_tint(
547 &mut self,
548 rect: Rect,
549 radius: f32,
550 blur_radius: f32,
551 tint_color: [f32; 4],
552 glass_intensity: f32,
553 ) {
554 self.gpu_ref().fill_glass_rect_with_tint(
555 rect,
556 radius,
557 blur_radius,
558 tint_color,
559 glass_intensity,
560 );
561 }
562
563 fn set_theme(&mut self, theme: ColorTheme) {
564 self.gpu_ref().set_theme(theme);
565 }
566
567 fn trigger_shatter_event(&mut self, origin: [f32; 2], force: f32) {
568 self.gpu_ref().trigger_shatter_event(origin, force);
569 }
570
571 fn set_fireball_pos(&mut self, pos: [f32; 2]) {
572 self.gpu_ref().set_fireball_pos(pos);
573 }
574
575 fn set_scene(&mut self, scene: &str) {
576 self.gpu_ref().set_scene(scene);
577 }
578
579 fn set_scene_preset(&mut self, preset: u32) {
580 self.gpu_ref().set_scene_preset(preset);
581 }
582
583 fn set_default_background_color(&mut self, color: [f32; 4]) {
584 self.gpu_ref().set_default_background_color(color);
585 }
586 fn push_transform(&mut self, translation: [f32; 2], scale: [f32; 2], rotation: f32) {
587 self.gpu_ref().push_transform(translation, scale, rotation);
588 }
589 fn pop_transform(&mut self) {
590 self.gpu_ref().pop_transform();
591 }
592
593 fn set_berserker_mode(&mut self, state: RenderIntensityMode) {
594 self.berserker_mode = state;
595
596 if state == RenderIntensityMode::GodMode {
597 tracing::info!("ENTERING GOD MODE: Activating Berserker Determinism (High Priority)");
598 #[cfg(target_os = "linux")]
599 unsafe {
600 let ret = libc::setpriority(libc::PRIO_PROCESS, 0, -10);
601 if ret != 0 {
602 tracing::warn!(
603 "GodMode: setpriority failed (errno: {}) — need CAP_SYS_NIO",
604 std::io::Error::last_os_error()
605 );
606 }
607 }
608 } else {
609 #[cfg(target_os = "linux")]
610 unsafe {
611 let _ = libc::setpriority(libc::PRIO_PROCESS, 0, 0);
612 }
613 }
614
615 self.gpu_ref().set_berserker_mode(state);
616 }
617
618 fn set_rage(&mut self, rage: f32) {
619 self.rage = rage;
620 self.gpu_ref().set_rage(rage);
621 }
622
623 fn memoize(&mut self, id: u64, data_hash: u64, render_fn: &dyn Fn(&mut dyn Renderer)) {
624 self.gpu_ref().memoize(id, data_hash, render_fn);
625 }
626
627 fn snapshot_render_state(&self) -> RenderStateSnapshot {
628 self.gpu_ref_shared().snapshot_render_state()
629 }
630
631 fn restore_render_state(&mut self, snap: RenderStateSnapshot) {
632 self.gpu_ref().restore_render_state(snap);
633 }
634 fn request_redraw(&mut self) {
635 self.window.request_redraw();
636 }
637
638 fn capture_png(&mut self) -> Vec<u8> {
639 tracing::info!("CAPTURING_FRAME: Initiating GPU readback...");
640 let gpu = self.gpu_ref();
643 pollster::block_on(gpu.capture_frame()).unwrap_or_else(|e| {
644 tracing::error!("GPU frame capture failed: {}", e);
645 Vec::new()
646 })
647 }
648
649 fn print(&mut self) {
650 tracing::info!("PRINT_BRIDGE: Spooling mission status to native printer...");
651 tracing::debug!("[BRIDGE] PRINTER_READY // SPOOLING_DATA...");
652 }
653}