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
13thread_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
22pub 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 #[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 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 #[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 GpuRefShared::Ptr(unsafe { &*raw })
67 } else {
68 GpuRefShared::Guard(self.gpu.lock().unwrap_or_else(|p| p.into_inner()))
69 }
70 })
71 }
72
73 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 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 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
133enum 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
158enum 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}