From 30b4e17ece36d93988e65d8e57227c21f62b4002 Mon Sep 17 00:00:00 2001 From: Birh Burh Date: Sun, 11 Aug 2024 02:48:52 +0200 Subject: [PATCH] native/macos: Re-re-implemented event loop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Reverted macos.rs to this implementation: https://github.com/not-fl3/miniquad/pull/443 (it's probably better compare changes with this PR than with latest commit to understand what was added additionally) - Now using own NSView with NSOpenGLContext instead of NSOpenGLView - For metal backend using redraw instead setNeedsDisplay, because somehow it reduces cpu usage (Cannot find info about enabling vsync like this in MTKView docs) - Fixed freezing on resize by drawing in draw_rect that called during "live resize"™. I don't like this approach, but it blocks main event loop while resizing, so it will not be some kind of concurency of opengl stuff i think - Reducing CPU usage when window is occluded - Added comments to hacky places, there are lots of them imo Fixes: - https://github.com/not-fl3/miniquad/issues/455 - https://github.com/not-fl3/miniquad/issues/470 --- src/native/apple/frameworks.rs | 3 + src/native/macos.rs | 654 +++++++++++++-------------------- 2 files changed, 263 insertions(+), 394 deletions(-) diff --git a/src/native/apple/frameworks.rs b/src/native/apple/frameworks.rs index 15045c76..6b3e506a 100644 --- a/src/native/apple/frameworks.rs +++ b/src/native/apple/frameworks.rs @@ -75,6 +75,7 @@ extern "C" { extern "C" { pub static NSRunLoopCommonModes: ObjcId; pub static NSDefaultRunLoopMode: ObjcId; + pub static NSEventTrackingRunLoopMode: ObjcId; pub static NSProcessInfo: ObjcId; pub fn NSStringFromClass(class: ObjcId) -> ObjcId; @@ -422,6 +423,8 @@ pub enum NSWindowStyleMask { NSFullSizeContentViewWindowMask = 1 << 15, } +pub const NSWindowOcclusionStateVisible: u64 = 1 << 1; + #[repr(u64)] #[derive(Clone, Copy, Debug, PartialEq)] pub enum NSApplicationActivationOptions { diff --git a/src/native/macos.rs b/src/native/macos.rs index d3f6e967..fa513ffe 100644 --- a/src/native/macos.rs +++ b/src/native/macos.rs @@ -5,7 +5,7 @@ use { crate::{ conf::{AppleGfxApi, Icon}, - event::{EventHandler, KeyCode, KeyMods, MouseButton}, + event::{EventHandler, MouseButton}, native::{ apple::{apple_util::*, frameworks::*}, gl, NativeDisplayData, Request, @@ -13,24 +13,19 @@ use { native_display, CursorIcon, }, std::{ - cell::RefCell, collections::HashMap, os::raw::c_void, - sync::{mpsc, Arc, Mutex}, - thread, + sync::mpsc::Receiver, + time::{Duration, Instant}, }, }; -struct MainThreadState { - quit: bool, - view: *mut Object, - cur_msg: Message, -} - pub struct MacosDisplay { window: ObjcId, view: ObjcId, + gl_context: ObjcId, fullscreen: bool, + occluded: bool, // [NSCursor hide]/unhide calls should be balanced // hide/hide/unhide will keep cursor hidden // so need to keep internal cursor state to avoid problems from @@ -44,8 +39,9 @@ pub struct MacosDisplay { event_handler: Option>, f: Option Box>>, modifiers: Modifiers, - state: Arc>, + native_requests: Receiver, update_requested: bool, + last_paint_start_time: Instant, } impl MacosDisplay { @@ -160,6 +156,11 @@ impl MacosDisplay { unsafe fn update_dimensions(&mut self) -> Option<(i32, i32)> { let mut d = native_display().lock().unwrap(); + unsafe { + if self.gl_context != nil { + msg_send_![self.gl_context, update]; + } + } if d.high_dpi { let dpi_scale: f64 = msg_send![self.window, backingScaleFactor]; d.dpi_scale = dpi_scale as f32; @@ -208,63 +209,6 @@ impl MacosDisplay { } } -#[derive(Debug, Clone, Copy)] -enum Message { - Resize { - width: i32, - height: i32, - }, - RawMouseMove { - dx: f32, - dy: f32, - }, - MouseMove { - x: f32, - y: f32, - }, - MouseButtonDown { - button: MouseButton, - x: f32, - y: f32, - }, - MouseButtonUp { - button: MouseButton, - x: f32, - y: f32, - }, - MouseWheel { - dx: f32, - dy: f32, - }, - Character { - character: char, - keymods: KeyMods, - repeat: bool, - }, - KeyDown { - keycode: KeyCode, - keymods: KeyMods, - repeat: bool, - }, - KeyUp { - keycode: KeyCode, - keymods: KeyMods, - }, - Destroy, -} -unsafe impl Send for Message {} - -thread_local! { - static MESSAGES_TX: RefCell>> = RefCell::new(None); -} - -fn send_message(message: Message) { - MESSAGES_TX.with(|tx| { - let mut tx = tx.borrow_mut(); - tx.as_mut().unwrap().send(message).unwrap(); - }) -} - #[derive(Default)] struct Modifiers { left_shift: bool, @@ -349,20 +293,25 @@ pub fn define_cocoa_window_delegate() -> *const Class { extern "C" fn window_did_resize(this: &Object, _: Sel, _: ObjcId) { let payload = get_window_payload(this); if let Some((w, h)) = unsafe { payload.update_dimensions() } { - send_message(Message::Resize { - width: w, - height: h, - }); + if let Some(event_handler) = payload.context() { + event_handler.resize_event(w as _, h as _); + } + } + } + + extern "C" fn window_did_move(this: &Object, _: Sel, _: ObjcId) { + let payload = get_window_payload(this); + unsafe { + msg_send_![payload.gl_context, update]; } } extern "C" fn window_did_change_screen(this: &Object, _: Sel, _: ObjcId) { let payload = get_window_payload(this); if let Some((w, h)) = unsafe { payload.update_dimensions() } { - send_message(Message::Resize { - width: w, - height: h, - }); + if let Some(event_handler) = payload.context() { + event_handler.resize_event(w as _, h as _); + } } } extern "C" fn window_did_enter_fullscreen(this: &Object, _: Sel, _: ObjcId) { @@ -373,6 +322,21 @@ pub fn define_cocoa_window_delegate() -> *const Class { let payload = get_window_payload(this); payload.fullscreen = false; } + extern "C" fn window_did_change_occlusion_state(this: &Object, _: Sel, _: ObjcId) { + unsafe { + let payload = get_window_payload(this); + let responds: bool = msg_send![payload.window, respondsToSelector:sel!(occlusionState)]; + if responds { + let state: u64 = msg_send![payload.window, occlusionState]; + if state & NSWindowOcclusionStateVisible != 0 { + payload.occluded = false; + } else { + payload.occluded = true; + } + } + } + } + let superclass = class!(NSObject); let mut decl = ClassDecl::new("RenderWindowDelegate", superclass).unwrap(); @@ -387,6 +351,10 @@ pub fn define_cocoa_window_delegate() -> *const Class { sel!(windowDidResize:), window_did_resize as extern "C" fn(&Object, Sel, ObjcId), ); + decl.add_method( + sel!(windowDidMove:), + window_did_move as extern "C" fn(&Object, Sel, ObjcId), + ); decl.add_method( sel!(windowDidChangeScreen:), window_did_change_screen as extern "C" fn(&Object, Sel, ObjcId), @@ -399,6 +367,10 @@ pub fn define_cocoa_window_delegate() -> *const Class { sel!(windowDidExitFullScreen:), window_did_exit_fullscreen as extern "C" fn(&Object, Sel, ObjcId), ); + decl.add_method( + sel!(windowDidChangeOcclusionState:), + window_did_change_occlusion_state as extern "C" fn(&Object, Sel, ObjcId), + ); } // Store internal state as user data decl.add_ivar::<*mut c_void>("display_ptr"); @@ -443,17 +415,15 @@ unsafe fn view_base_decl(decl: &mut ClassDecl) { if payload.cursor_grabbed { let dx: f64 = msg_send!(event, deltaX); let dy: f64 = msg_send!(event, deltaY); - send_message(Message::RawMouseMove { - dx: dx as f32, - dy: dy as f32, - }); + if let Some(event_handler) = payload.context() { + event_handler.raw_mouse_motion(dx as f32, dy as f32); + } } else { let point: NSPoint = msg_send!(event, locationInWindow); let point = payload.transform_mouse_point(&point); - send_message(Message::MouseMove { - x: point.0, - y: point.1, - }); + if let Some(event_handler) = payload.context() { + event_handler.mouse_motion_event(point.0, point.1); + } } } } @@ -464,18 +434,12 @@ unsafe fn view_base_decl(decl: &mut ClassDecl) { unsafe { let point: NSPoint = msg_send!(event, locationInWindow); let point = payload.transform_mouse_point(&point); - if down { - send_message(Message::MouseButtonDown { - button: btn, - x: point.0, - y: point.1, - }); - } else { - send_message(Message::MouseButtonUp { - button: btn, - x: point.0, - y: point.1, - }); + if let Some(event_handler) = payload.context() { + if down { + event_handler.mouse_button_down_event(btn, point.0, point.1); + } else { + event_handler.mouse_button_up_event(btn, point.0, point.1); + } } } } @@ -497,7 +461,8 @@ unsafe fn view_base_decl(decl: &mut ClassDecl) { extern "C" fn other_mouse_up(this: &Object, _sel: Sel, event: ObjcId) { fire_mouse_event(this, event, false, MouseButton::Middle); } - extern "C" fn scroll_wheel(_: &Object, _sel: Sel, event: ObjcId) { + extern "C" fn scroll_wheel(this: &Object, _sel: Sel, event: ObjcId) { + let payload = get_window_payload(this); unsafe { let mut dx: f64 = msg_send![event, scrollingDeltaX]; let mut dy: f64 = msg_send![event, scrollingDeltaY]; @@ -506,10 +471,9 @@ unsafe fn view_base_decl(decl: &mut ClassDecl) { dx *= 10.0; dy *= 10.0; } - send_message(Message::MouseWheel { - dx: dx as f32, - dy: dy as f32, - }); + if let Some(event_handler) = payload.context() { + event_handler.mouse_wheel_event(dx as f32, dy as f32); + } } } extern "C" fn reset_cursor_rects(this: &Object, _sel: Sel) { @@ -535,38 +499,36 @@ unsafe fn view_base_decl(decl: &mut ClassDecl) { } } - extern "C" fn key_down(_: &Object, _sel: Sel, event: ObjcId) { + extern "C" fn key_down(this: &Object, _sel: Sel, event: ObjcId) { + let payload = get_window_payload(this); let mods = get_event_key_modifier(event); let repeat: bool = unsafe { msg_send!(event, isARepeat) }; if let Some(key) = get_event_keycode(event) { - send_message(Message::KeyDown { - keycode: key, - keymods: mods, - repeat, - }); + if let Some(event_handler) = payload.context() { + event_handler.key_down_event(key, mods, repeat); + } } if let Some(character) = get_event_char(event) { - send_message(Message::Character { - character, - keymods: mods, - repeat, - }); + if let Some(event_handler) = payload.context() { + event_handler.char_event(character, mods, repeat); + } } } - extern "C" fn key_up(_: &Object, _sel: Sel, event: ObjcId) { + extern "C" fn key_up(this: &Object, _sel: Sel, event: ObjcId) { + let payload = get_window_payload(this); let mods = get_event_key_modifier(event); if let Some(key) = get_event_keycode(event) { - send_message(Message::KeyUp { - keycode: key, - keymods: mods, - }); + if let Some(event_handler) = payload.context() { + event_handler.key_up_event(key, mods); + } } } extern "C" fn flags_changed(this: &Object, _sel: Sel, event: ObjcId) { fn produce_event( + payload: &mut MacosDisplay, keycode: crate::KeyCode, mods: crate::KeyMods, old_pressed: bool, @@ -574,16 +536,13 @@ unsafe fn view_base_decl(decl: &mut ClassDecl) { ) { if new_pressed ^ old_pressed { if new_pressed { - send_message(Message::KeyDown { - keycode, - keymods: mods, - repeat: false, - }); + if let Some(event_handler) = payload.context() { + event_handler.key_down_event(keycode, mods, false); + } } else { - send_message(Message::KeyUp { - keycode, - keymods: mods, - }); + if let Some(event_handler) = payload.context() { + event_handler.key_up_event(keycode, mods); + } } } } @@ -594,48 +553,56 @@ unsafe fn view_base_decl(decl: &mut ClassDecl) { let new_modifiers = Modifiers::new(flags); produce_event( + payload, crate::KeyCode::LeftShift, mods, payload.modifiers.left_shift, new_modifiers.left_shift, ); produce_event( + payload, crate::KeyCode::RightShift, mods, payload.modifiers.right_shift, new_modifiers.right_shift, ); produce_event( + payload, crate::KeyCode::LeftControl, mods, payload.modifiers.left_control, new_modifiers.left_control, ); produce_event( + payload, crate::KeyCode::RightControl, mods, payload.modifiers.right_control, new_modifiers.right_control, ); produce_event( + payload, crate::KeyCode::LeftSuper, mods, payload.modifiers.left_command, new_modifiers.left_command, ); produce_event( + payload, crate::KeyCode::RightSuper, mods, payload.modifiers.right_command, new_modifiers.right_command, ); produce_event( + payload, crate::KeyCode::LeftAlt, mods, payload.modifiers.left_alt, new_modifiers.left_alt, ); produce_event( + payload, crate::KeyCode::RightAlt, mods, payload.modifiers.right_alt, @@ -645,83 +612,6 @@ unsafe fn view_base_decl(decl: &mut ClassDecl) { payload.modifiers = new_modifiers; } - extern "C" fn process_message(this: &Object, _: Sel, _: ObjcId) { - use Message::*; - let payload = get_window_payload(this); - let msg = { - let state = payload.state.lock().unwrap(); - state.cur_msg - }; - match msg { - Destroy => { - let mut state = payload.state.lock().unwrap(); - state.quit = true; - } - MouseMove { x, y } => { - if let Some(ref mut event_handler) = payload.event_handler { - event_handler.mouse_motion_event(x, y); - } - } - RawMouseMove { dx, dy } => { - if let Some(ref mut event_handler) = payload.event_handler { - event_handler.raw_mouse_motion(dx, dy); - } - } - MouseButtonDown { button, x, y } => { - if let Some(ref mut event_handler) = payload.event_handler { - event_handler.mouse_button_down_event(button, x, y); - } - } - MouseButtonUp { button, x, y } => { - if let Some(ref mut event_handler) = payload.event_handler { - event_handler.mouse_button_up_event(button, x, y); - } - } - MouseWheel { dx, dy } => { - if let Some(ref mut event_handler) = payload.event_handler { - event_handler.mouse_wheel_event(dx as f32, dy as f32); - } - } - Character { - character, - keymods, - repeat, - } => { - if let Some(ref mut event_handler) = payload.event_handler { - event_handler.char_event(character, keymods, repeat); - } - } - KeyDown { - keycode, - keymods, - repeat, - } => { - if let Some(ref mut event_handler) = payload.event_handler { - event_handler.key_down_event(keycode, keymods, repeat); - } - } - KeyUp { keycode, keymods } => { - if let Some(ref mut event_handler) = payload.event_handler { - event_handler.key_up_event(keycode, keymods); - } - } - Resize { width, height } => { - if let Some(ref mut event_handler) = payload.event_handler { - event_handler.resize_event(width as _, height as _); - } - } - } - } - - // apparently, its impossible to use performSelectorOnMainThread - // with primitive type argument, so the only way to pass - // YES to setNeedsDisplay - send a no argument message - // https://stackoverflow.com/questions/6120614/passing-primitives-through-performselectoronmainthread - extern "C" fn set_needs_display_hack(this: &Object, _: Sel) { - unsafe { - msg_send_![this, setNeedsDisplay: YES]; - } - } decl.add_method( sel!(canBecomeKey), @@ -789,100 +679,46 @@ unsafe fn view_base_decl(decl: &mut ClassDecl) { flags_changed as extern "C" fn(&Object, Sel, ObjcId), ); decl.add_method(sel!(keyUp:), key_up as extern "C" fn(&Object, Sel, ObjcId)); - decl.add_method( - sel!(processMessage:), - process_message as extern "C" fn(&Object, Sel, ObjcId), - ); - decl.add_method( - sel!(setNeedsDisplayHack), - set_needs_display_hack as extern "C" fn(&Object, Sel), - ); } pub fn define_opengl_view_class() -> *const Class { - //extern "C" fn dealloc(this: &Object, _sel: Sel) {} - extern "C" fn reshape(this: &Object, _sel: Sel) { let payload = get_window_payload(this); - unsafe { - let superclass = superclass(this); - let () = msg_send![super(this, superclass), reshape]; - if let Some((w, h)) = payload.update_dimensions() { - // left this because post_processing.rs turns black after the first resize - // and somehow this makes it draw until manual window resize - // (bug existed even on original macos implementation) - - // send_message(Message::Resize { width: w, height: h }); - if let Some(ref mut event_handler) = payload.event_handler { + if let Some(event_handler) = payload.context() { event_handler.resize_event(w as _, h as _); } } } } - extern "C" fn draw_rect(this: &Object, _sel: Sel, _rect: NSRect) { + extern "C" fn draw_rect(this: &Object, _sel: Sel, _: ObjcId) { + // For opengl draw_rect called only during resize let payload = get_window_payload(this); - let mut updated = false; - - if let Some(event_handler) = payload.context() { - event_handler.update(); - event_handler.draw(); - updated = true; - } - if updated { - payload.update_requested = false; - } - unsafe { - let ctx: ObjcId = msg_send![this, openGLContext]; - assert!(!ctx.is_null()); - let () = msg_send![ctx, flushBuffer]; - - let d = native_display().lock().unwrap(); - if d.quit_requested || d.quit_ordered { - drop(d); - let () = msg_send![payload.window, performClose: nil]; + // Need this update_dimensions so it sets right dpi_scale at the beggining + if let Some((w, h)) = payload.update_dimensions() { + if let Some(event_handler) = payload.context() { + event_handler.resize_event(w as _, h as _); + } + } + let current_runloop = msg_send_![class!(NSRunLoop), currentRunLoop]; + let current_mode: ObjcId = msg_send![current_runloop, currentMode]; + // Not checking name, assuming that this is NSEventTrackingRunLoopMode + if current_mode != nil { + perform_redraw(payload, AppleGfxApi::OpenGl, true); } } } - extern "C" fn prepare_open_gl(this: &Object, _sel: Sel) { - let payload = get_window_payload(this); - unsafe { - let superclass = superclass(this); - let () = msg_send![super(this, superclass), prepareOpenGL]; - let mut swap_interval = 1; - let ctx: ObjcId = msg_send![this, openGLContext]; - let () = msg_send![ctx, - setValues:&mut swap_interval - forParameter:NSOpenGLContextParameterSwapInterval]; - let () = msg_send![ctx, makeCurrentContext]; - } - - gl::load_gl_funcs(|proc| { - let name = std::ffi::CString::new(proc).unwrap(); - - unsafe { get_proc_address(name.as_ptr() as _) } - }); - - let f = payload.f.take().unwrap(); - payload.event_handler = Some(f()); - } - - let superclass = class!(NSOpenGLView); + let superclass = class!(NSView); let mut decl: ClassDecl = ClassDecl::new("RenderViewClass", superclass).unwrap(); unsafe { - //decl.add_method(sel!(dealloc), dealloc as extern "C" fn(&Object, Sel)); - decl.add_method( - sel!(prepareOpenGL), - prepare_open_gl as extern "C" fn(&Object, Sel), - ); decl.add_method(sel!(reshape), reshape as extern "C" fn(&Object, Sel)); decl.add_method( sel!(drawRect:), - draw_rect as extern "C" fn(&Object, Sel, NSRect), + draw_rect as extern "C" fn(&Object, Sel, ObjcId), ); view_base_decl(&mut decl); @@ -898,41 +734,24 @@ pub fn define_metal_view_class() -> *const Class { let mut decl = ClassDecl::new("RenderViewClass", superclass).unwrap(); decl.add_ivar::<*mut c_void>("display_ptr"); - extern "C" fn draw_rect(this: &Object, _sel: Sel, _rect: NSRect) { + extern "C" fn draw_rect(this: &Object, _sel: Sel, _: ObjcId) { let payload = get_window_payload(this); - - if payload.event_handler.is_none() { - let f = payload.f.take().unwrap(); - payload.event_handler = Some(f()); - } - - let mut updated = false; - - if let Some(event_handler) = payload.context() { - event_handler.update(); - event_handler.draw(); - updated = true; - } - if updated { - payload.update_requested = false; - } - unsafe { - let d = native_display().lock().unwrap(); - if d.quit_requested || d.quit_ordered { - drop(d); - let () = msg_send![payload.window, performClose: nil]; + let current_runloop = msg_send_![class!(NSRunLoop), currentRunLoop]; + let current_mode: ObjcId = msg_send![current_runloop, currentMode]; + // Not checking name, assuming that this is NSEventTrackingRunLoopMode + // For metal backend this is nil during regular draw_rect calls + if current_mode != nil { + perform_redraw(payload, AppleGfxApi::Metal, true); } } } unsafe { - //decl.add_method(sel!(dealloc), dealloc as extern "C" fn(&Object, Sel)); decl.add_method( sel!(drawRect:), - draw_rect as extern "C" fn(&Object, Sel, NSRect), + draw_rect as extern "C" fn(&Object, Sel, ObjcId), ); - view_base_decl(&mut decl); } @@ -946,7 +765,7 @@ fn get_window_payload(this: &Object) -> &mut MacosDisplay { } } -unsafe fn create_metal_view(_: NSRect, sample_count: i32, _: bool) -> ObjcId { +unsafe fn create_metal_view(_: &mut MacosDisplay, sample_count: i32, _: bool) -> ObjcId { let mtl_device_obj = MTLCreateSystemDefaultDevice(); let view_class = define_metal_view_class(); let view: ObjcId = msg_send![view_class, alloc]; @@ -959,13 +778,16 @@ unsafe fn create_metal_view(_: NSRect, sample_count: i32, _: bool) -> ObjcId { setDepthStencilPixelFormat: MTLPixelFormat::Depth32Float_Stencil8 ]; let () = msg_send![view, setSampleCount: sample_count]; - let () = msg_send![view, setEnableSetNeedsDisplay: true]; let () = msg_send![view, setPaused: true]; view } -unsafe fn create_opengl_view(window_frame: NSRect, sample_count: i32, high_dpi: bool) -> ObjcId { +unsafe fn create_opengl_view( + display: &mut MacosDisplay, + sample_count: i32, + high_dpi: bool, +) -> ObjcId { use NSOpenGLPixelFormatAttribute::*; let mut attrs: Vec = vec![]; @@ -994,18 +816,22 @@ unsafe fn create_opengl_view(window_frame: NSRect, sample_count: i32, high_dpi: } attrs.push(0); - let glpixelformat_obj: ObjcId = msg_send![class!(NSOpenGLPixelFormat), alloc]; - let glpixelformat_obj: ObjcId = - msg_send![glpixelformat_obj, initWithAttributes: attrs.as_ptr()]; + let glpixelformat_obj = msg_send_![class!(NSOpenGLPixelFormat), alloc]; + let glpixelformat_obj = msg_send_![glpixelformat_obj, initWithAttributes: attrs.as_ptr()]; assert!(!glpixelformat_obj.is_null()); let view_class = define_opengl_view_class(); let view: ObjcId = msg_send![view_class, alloc]; - let view: ObjcId = msg_send![ - view, - initWithFrame: window_frame - pixelFormat: glpixelformat_obj - ]; + let view: ObjcId = msg_send![view, init]; + + let gl_context = msg_send_![class!(NSOpenGLContext), alloc]; + + display.gl_context = msg_send![gl_context, initWithFormat: glpixelformat_obj shareContext: nil]; + + let mut swap_interval = 1; + let () = msg_send![display.gl_context, + setValues:&mut swap_interval + forParameter:NSOpenGLContextParameterSwapInterval]; if high_dpi { let () = msg_send![view, setWantsBestResolutionOpenGLSurface: YES]; @@ -1091,7 +917,8 @@ unsafe fn initialize_menu_bar(ns_app: ObjcId) { // the Application bundle files, ending with the executable name. let running_application = msg_send_![class!(NSRunningApplication), currentApplication]; let application_name = msg_send_![running_application, localizedName]; - let quit_item_title = str_to_nsstring(&format!("Quit {}", nsstring_to_string(application_name))); + let quit_item_title = + str_to_nsstring(&format!("Quit {}", nsstring_to_string(application_name))); let quit_item = msg_send_![class!(NSMenuItem), alloc]; let quit_item = msg_send_![ quit_item, @@ -1102,11 +929,68 @@ unsafe fn initialize_menu_bar(ns_app: ObjcId) { msg_send_![app_menu, addItem: quit_item]; } +unsafe fn perform_redraw( + display: &mut MacosDisplay, + apple_gfx_api: AppleGfxApi, + in_draw_rect: bool, +) { + if display.event_handler.is_none() { + let f = display.f.take().unwrap(); + display.event_handler = Some(f()); + } + + let mut updated = false; + + if let Some(event_handler) = display.context() { + event_handler.update(); + event_handler.draw(); + updated = true; + } + if updated { + display.update_requested = false; + } + + { + let d = native_display().lock().unwrap(); + if d.quit_requested || d.quit_ordered { + drop(d); + let () = msg_send![display.window, performClose: nil]; + } + } + unsafe { + // reduce CPU usage hen window is in background + if display.occluded { + let now = Instant::now(); + let framerate = 60.0; + let period = (1.0 / framerate * 1000.) as u64; + + match Duration::from_millis(period).checked_sub(now - display.last_paint_start_time) { + Some(delay) => { + std::thread::sleep(delay); + } + None => { + display.last_paint_start_time = now; + } + } + } + match apple_gfx_api { + AppleGfxApi::OpenGl => { + msg_send_!(display.gl_context, flushBuffer); + } + AppleGfxApi::Metal => { + if !in_draw_rect { + msg_send_!(display.view, draw); + } + } + }; + } +} + pub unsafe fn run(conf: crate::conf::Conf, f: F) where F: 'static + FnOnce() -> Box, { - let (tx, requests_rx) = std::sync::mpsc::channel(); + let (tx, rx) = std::sync::mpsc::channel(); let clipboard = Box::new(MacosClipboard); crate::set_display(NativeDisplayData { high_dpi: conf.high_dpi, @@ -1115,20 +999,12 @@ where ..NativeDisplayData::new(conf.window_width, conf.window_height, tx, clipboard) }); - let (tx, rx) = std::sync::mpsc::channel(); - - MESSAGES_TX.with(move |messages_tx| *messages_tx.borrow_mut() = Some(tx)); - - let state_original = Arc::new(Mutex::new(MainThreadState { - quit: false, - view: std::ptr::null_mut(), - cur_msg: Message::MouseMove { x: 0., y: 0. }, - })); - let mut display = MacosDisplay { view: std::ptr::null_mut(), window: std::ptr::null_mut(), + gl_context: std::ptr::null_mut(), fullscreen: false, + occluded: false, cursor_shown: true, current_cursor: CursorIcon::Default, cursor_grabbed: false, @@ -1136,9 +1012,10 @@ where gfx_api: conf.platform.apple_gfx_api, f: Some(Box::new(f)), event_handler: None, + native_requests: rx, modifiers: Modifiers::default(), update_requested: true, - state: state_original.clone(), + last_paint_start_time: Instant::now(), }; let app_delegate_class = define_app_delegate(); @@ -1151,7 +1028,6 @@ where setActivationPolicy: NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular as i64 ]; - let () = msg_send![ns_app, activateIgnoringOtherApps: YES]; if let Some(icon) = &conf.icon { set_icon(ns_app, icon); @@ -1163,7 +1039,6 @@ where | NSWindowStyleMask::NSClosableWindowMask as u64 | NSWindowStyleMask::NSMiniaturizableWindowMask as u64 | NSWindowStyleMask::NSResizableWindowMask as u64; - //| NSWindowStyleMask::NSFullSizeContentViewWindowMask as u64; let window_frame = NSRect { origin: NSPoint { x: 0., y: 0. }, @@ -1196,17 +1071,13 @@ where let () = msg_send![window, setAcceptsMouseMovedEvents: YES]; let view = match conf.platform.apple_gfx_api { - AppleGfxApi::OpenGl => create_opengl_view(window_frame, conf.sample_count, conf.high_dpi), - AppleGfxApi::Metal => create_metal_view(window_frame, conf.sample_count, conf.high_dpi), + AppleGfxApi::OpenGl => create_opengl_view(&mut display, conf.sample_count, conf.high_dpi), + AppleGfxApi::Metal => create_metal_view(&mut display, conf.sample_count, conf.high_dpi), }; { let mut d = native_display().lock().unwrap(); d.view = view; } - { - let mut s = state_original.lock().unwrap(); - s.view = view; - } (*view).set_ivar("display_ptr", &mut display as *mut _ as *mut c_void); display.window = window; @@ -1214,6 +1085,18 @@ where let () = msg_send![window, setContentView: view]; + // cannot place it to create_opengl_view, because it should be called after setContentView + if conf.platform.apple_gfx_api == AppleGfxApi::OpenGl { + msg_send_![display.gl_context, setView:view]; + msg_send_![display.gl_context, makeCurrentContext]; + + gl::load_gl_funcs(|proc| { + let name = std::ffi::CString::new(proc).unwrap(); + + get_proc_address(name.as_ptr() as _) + }); + } + let _ = display.update_dimensions(); assert!(!view.is_null()); @@ -1224,78 +1107,61 @@ where let () = msg_send![window, toggleFullScreen: nil]; } + msg_send_![window, orderFront: nil]; + let () = msg_send![ns_app, activateIgnoringOtherApps: YES]; let () = msg_send![window, makeKeyAndOrderFront: nil]; - struct SendHack(F); - unsafe impl Send for SendHack {} - - let state = SendHack(state_original.clone()); - thread::spawn(move || { - let s = state.0; - - loop { - while let Ok(request) = requests_rx.try_recv() { - let s = s.lock().unwrap(); - let view = s.view; - let payload = get_window_payload(view.as_ref().unwrap()); - payload.process_request(request); - } - - let block_on_wait = { - let s = s.lock().unwrap(); - let view = s.view; - - let payload = get_window_payload(view.as_ref().unwrap()); - conf.platform.blocking_event_loop && !payload.update_requested - }; - - if block_on_wait { - let res = rx.recv(); + let () = msg_send![ns_app, finishLaunching]; + + // Found this here: https://github.com/kovidgoyal/kitty/issues/6341#issuecomment-1578348104 + let current_runloop = msg_send_![class!(NSRunLoop), currentRunLoop]; + let timer = match conf.platform.apple_gfx_api { + AppleGfxApi::OpenGl => msg_send_![class!(NSTimer), timerWithTimeInterval:0.016 // ~60FPS + target:view + selector:sel!(setNeedsDisplay:) + userInfo:YES + repeats:YES], + AppleGfxApi::Metal => msg_send_![class!(NSTimer), timerWithTimeInterval:0.016 // ~60FPS + target:view + selector:sel!(draw) + userInfo:YES + repeats:YES], + }; + msg_send_![current_runloop, addTimer:timer forMode:NSEventTrackingRunLoopMode]; + + // Basically reimplementing msg_send![ns_app, run] here + let distant_future: ObjcId = msg_send![class!(NSDate), distantFuture]; + let distant_past: ObjcId = msg_send![class!(NSDate), distantPast]; + let mut done = false; + while !(done || crate::native_display().lock().unwrap().quit_ordered) { + while let Ok(request) = display.native_requests.try_recv() { + display.process_request(request); + } - if let Ok(msg) = res { - let view; - { - let mut s = s.lock().unwrap(); - view = s.view; - s.cur_msg = msg; - } - msg_send_![&*view, performSelectorOnMainThread:sel!(processMessage:) withObject:nil waitUntilDone:YES]; - } - } else { - // process all the messages from the main thread - while let Ok(msg) = rx.try_recv() { - let view; - { - let mut s = s.lock().unwrap(); - view = s.view; - s.cur_msg = msg; - } - msg_send_![&*view, performSelectorOnMainThread:sel!(processMessage:) withObject:nil waitUntilDone:YES]; - } + { + let d = native_display().lock().unwrap(); + if d.quit_requested || d.quit_ordered { + done = true; } + } - let update_requested; - let view; - { - let s = s.lock().unwrap(); - view = s.view; - - let payload = get_window_payload(view.as_ref().unwrap()); - update_requested = payload.update_requested; - } + let block_on_wait = conf.platform.blocking_event_loop && !display.update_requested; + if block_on_wait { + let event: ObjcId = msg_send![ns_app, nextEventMatchingMask: NSEventMask::NSAnyEventMask untilDate: distant_future inMode:NSDefaultRunLoopMode dequeue:YES]; - if !conf.platform.blocking_event_loop || update_requested { - unsafe { - msg_send_![view, performSelectorOnMainThread:sel!(setNeedsDisplayHack) withObject:nil waitUntilDone:NO]; + let () = msg_send![ns_app, sendEvent:event]; + } else { + loop { + let event: ObjcId = msg_send![ns_app, nextEventMatchingMask: NSEventMask::NSAnyEventMask untilDate: distant_past inMode:NSDefaultRunLoopMode dequeue:YES]; + if event == nil { + break; } + let () = msg_send![ns_app, sendEvent:event]; } - thread::yield_now(); } - }); - let () = msg_send![ns_app, run]; - - // run should never return - // but just in case - unreachable!(); + if !conf.platform.blocking_event_loop || display.update_requested { + perform_redraw(&mut display, conf.platform.apple_gfx_api, false); + } + } }