From 210f7c5fc183ec262555a566b1dd418e619696d0 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Fri, 27 May 2016 13:29:44 -0700 Subject: [PATCH] cocoa: Add a new platform-specific API that allows library consumers to specify that only the corners of a window can be transparent. By doing this, we significantly improve performance by allowing the window server to perform occlusion culling under most of the window. This patch relies on the private `CGSRegion` and the private `-[NSCGSWindow setOpaqueRegion:]` APIs. Requires servo/core-graphics-rs#50 and servo/cocoa-rs#129. --- src/api/cocoa/mod.rs | 54 ++++++++++++++++++++++++++++++++++++++++++-- src/os/macos.rs | 13 +++++++++++ 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/src/api/cocoa/mod.rs b/src/api/cocoa/mod.rs index cb892a7fd79..2f15b452746 100644 --- a/src/api/cocoa/mod.rs +++ b/src/api/cocoa/mod.rs @@ -35,6 +35,7 @@ use core_foundation::bundle::{CFBundle, CFBundleGetBundleWithIdentifier}; use core_foundation::bundle::{CFBundleGetFunctionPointerForName}; use core_graphics::display::{CGAssociateMouseAndMouseCursorPosition, CGMainDisplayID, CGDisplayPixelsHigh, CGWarpMouseCursorPosition}; +use core_graphics::private::CGSRegion; use std::ffi::CStr; use std::collections::VecDeque; @@ -78,6 +79,9 @@ struct DelegateState { /// Events that have been retreived with XLib but not dispatched with iterators yet pending_events: Mutex>, + + /// The transparent corner radius of the window, if there is one. + transparent_corner_radius: Option, } struct WindowDelegate { @@ -113,6 +117,9 @@ impl WindowDelegate { (handler)((scale_factor * rect.size.width as f32) as u32, (scale_factor * rect.size.height as f32) as u32); } + + update_opaque_region_of_window_if_necessary(*state.window, + state.transparent_corner_radius); } } @@ -193,6 +200,7 @@ impl Drop for WindowDelegate { pub struct PlatformSpecificWindowBuilderAttributes { pub activation_policy: ActivationPolicy, pub app_name: Option, + pub transparent_corner_radius: Option, } pub struct Window { @@ -310,7 +318,7 @@ impl Window { None => { return Err(OsError(format!("Couldn't create NSApplication"))); }, }; - let window = match Window::create_window(win_attribs) + let window = match Window::create_window(win_attribs, pl_attribs.transparent_corner_radius) { Some(window) => window, None => { return Err(OsError(format!("Couldn't create NSWindow"))); }, @@ -344,6 +352,9 @@ impl Window { } else { window.makeKeyWindow(); } + + update_opaque_region_of_window_if_necessary(*window, + pl_attribs.transparent_corner_radius); } let ds = DelegateState { @@ -352,6 +363,7 @@ impl Window { window: window.clone(), resize_handler: None, pending_events: Mutex::new(VecDeque::new()), + transparent_corner_radius: pl_attribs.transparent_corner_radius, }; let window = Window { @@ -402,7 +414,8 @@ impl Window { } } - fn create_window(attrs: &WindowAttributes) -> Option { + fn create_window(attrs: &WindowAttributes, transparent_corner_radius: Option) + -> Option { unsafe { let screen = match attrs.monitor { Some(ref monitor_id) => { @@ -1127,6 +1140,43 @@ extern fn draw_rect_in_glutin_content_view(this: &Object, _: Sel, _: NSRect) { } } +/// Determines what part of the window is guaranteed to be opaque (via `transparent_corner_radius`) +/// and supplies the Mac OS X window server with that information. +/// +/// This allows the window server to perform occlusion culling. Normal (non-borderless) Cocoa +/// windows use this API internally. +/// +/// NB: This uses two private Cocoa APIs: `-[NSCGSWindow windowWithWindowID]` and +/// `[_NSCGSWindow setOpaqueShape]`. It also uses the private `CGSRegion` API. This code attempts +/// to be defensive, but if Apple changes these APIs in future versions of Mac OS X, this function +/// may need to be updated. +fn update_opaque_region_of_window_if_necessary(window: id, transparent_corner_radius: Option) { + let transparent_corner_radius = match transparent_corner_radius { + Some(transparent_corner_radius) => transparent_corner_radius, + None => return, + }; + + unsafe { + let window_frame = NSRect { + origin: NSPoint { + x: 0.0, + y: 0.0, + }, + size: NSWindow::frame(window).size, + }; + let inset_window_frame = window_frame.inset(0.0, transparent_corner_radius as CGFloat); + let region = CGSRegion::from_rect(inset_window_frame.as_CGRect()); + + let ns_cgs_window = match Class::get("NSCGSWindow") { + Some(window) => window, + None => return, + }; + let window_number = window.windowNumber(); + let cgs_window: id = msg_send![ns_cgs_window, windowWithWindowID:window_number]; + msg_send![cgs_window, setOpaqueShape:region]; + } +} + thread_local! { static WAKEUP_EVENT: id = { unsafe { diff --git a/src/os/macos.rs b/src/os/macos.rs index 877b5b7b9d2..a3d8380787b 100644 --- a/src/os/macos.rs +++ b/src/os/macos.rs @@ -54,6 +54,8 @@ impl From for NSApplicationActivationPolicy { pub trait WindowBuilderExt<'a> { fn with_activation_policy(mut self, activation_policy: ActivationPolicy) -> WindowBuilder<'a>; fn with_app_name(mut self, app_name: String) -> WindowBuilder<'a>; + fn with_transparent_corner_radius(mut self, transparent_corner_radius: u32) + -> WindowBuilder<'a>; } impl<'a> WindowBuilderExt<'a> for WindowBuilder<'a> { @@ -70,4 +72,15 @@ impl<'a> WindowBuilderExt<'a> for WindowBuilder<'a> { self.platform_specific.app_name = Some(app_name); self } + + /// Sets the maximum number of pixels from the corners that transparent content will be drawn + /// + /// This is used as an optimization so that Cocoa won't have to paint what's behind most of the + /// window. It improves performance dramatically when in use when videos, transparent Terminal + /// windows, etc. are behind the window. + fn with_transparent_corner_radius(mut self, transparent_corner_radius: u32) + -> WindowBuilder<'a> { + self.platform_specific.transparent_corner_radius = Some(transparent_corner_radius); + self + } }