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