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