Desktop: Fixup MacOS build (#3295)
Some checks failed
Editor: Dev & CI / build (push) Has been cancelled
Editor: Dev & CI / cargo-deny (push) Has been cancelled

desktop mac os fixup

Co-authored-by: Dennis Kobert <dennis@kobert.dev>
This commit is contained in:
Timon 2025-10-20 13:31:36 +00:00 committed by GitHub
parent 5acf50c1ef
commit 39f4ccf8e0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 218 additions and 183 deletions

49
Cargo.lock generated
View file

@ -991,6 +991,16 @@ dependencies = [
"libc",
]
[[package]]
name = "core-foundation"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
@ -1004,7 +1014,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf"
dependencies = [
"bitflags 1.3.2",
"core-foundation",
"core-foundation 0.9.4",
"libc",
]
@ -2253,7 +2263,7 @@ dependencies = [
"cef",
"cef-dll-sys",
"clap",
"core-foundation",
"core-foundation 0.10.1",
"derivative",
"dirs",
"futures",
@ -2261,8 +2271,8 @@ dependencies = [
"graphite-desktop-embedded-resources",
"graphite-desktop-wrapper",
"libc",
"objc2-io-surface",
"objc2-metal",
"metal",
"objc",
"open",
"rand 0.9.2",
"rfd",
@ -3720,33 +3730,6 @@ dependencies = [
"objc2-core-foundation",
]
[[package]]
name = "objc2-io-surface"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c"
dependencies = [
"bitflags 2.9.3",
"libc",
"objc2",
"objc2-core-foundation",
"objc2-foundation",
]
[[package]]
name = "objc2-metal"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f246c183239540aab1782457b35ab2040d4259175bd1d0c58e46ada7b47a874"
dependencies = [
"bitflags 2.9.3",
"block2",
"dispatch2",
"objc2",
"objc2-core-foundation",
"objc2-foundation",
]
[[package]]
name = "objc2-ui-kit"
version = "0.3.1"
@ -4968,7 +4951,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [
"bitflags 2.9.3",
"core-foundation",
"core-foundation 0.9.4",
"core-foundation-sys",
"libc",
"security-framework-sys",
@ -5532,7 +5515,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
dependencies = [
"bitflags 2.9.3",
"core-foundation",
"core-foundation 0.9.4",
"system-configuration-sys",
]

View file

@ -28,8 +28,6 @@ members = [
"proc-macros",
]
default-members = [
"desktop",
"desktop/wrapper",
"editor",
"frontend/wasm",
"libraries/dyn-any",

View file

@ -18,7 +18,7 @@ gpu = ["graphite-desktop-wrapper/gpu"]
accelerated_paint = ["accelerated_paint_dmabuf", "accelerated_paint_d3d11", "accelerated_paint_iosurface"]
accelerated_paint_dmabuf = ["libc", "ash"]
accelerated_paint_d3d11 = ["windows", "ash"]
accelerated_paint_iosurface = ["objc2-io-surface", "objc2-metal", "core-foundation"]
accelerated_paint_iosurface = ["metal", "objc", "core-foundation"]
[dependencies]
# Local dependencies
@ -66,9 +66,9 @@ windows = { version = "0.58.0", features = [
# macOS-specific dependencies
[target.'cfg(target_os = "macos")'.dependencies]
objc2-io-surface = { version = "0.3", optional = true }
objc2-metal = { version = "0.3", optional = true }
core-foundation = { version = "0.9", optional = true }
metal = { version = "0.31.0", optional = true }
objc = { version = "0.2", optional = true }
core-foundation = { version = "0.10", optional = true }
# Linux-specific dependencies
[target.'cfg(target_os = "linux")'.dependencies]

View file

@ -1,3 +1,4 @@
#[cfg(not(target_os = "macos"))]
mod multithreaded;
mod singlethreaded;

View file

@ -1,4 +1,4 @@
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use cef::args::Args;
use cef::sys::{CEF_API_VERSION_LAST, cef_resultcode_t};
@ -25,12 +25,23 @@ unsafe impl<H: CefEventHandler> Send for CefContextBuilder<H> {}
impl<H: CefEventHandler> CefContextBuilder<H> {
pub(crate) fn new() -> Self {
Self::new_inner(false)
}
pub(crate) fn new_helper() -> Self {
Self::new_inner(true)
}
fn new_inner(helper: bool) -> Self {
#[cfg(target_os = "macos")]
let _loader = {
let loader = library_loader::LibraryLoader::new(&std::env::current_exe().unwrap(), false);
let loader = cef::library_loader::LibraryLoader::new(&std::env::current_exe().unwrap(), helper);
assert!(loader.load());
loader
};
#[cfg(not(target_os = "macos"))]
let _ = helper;
let _ = api_hash(CEF_API_VERSION_LAST, 0);
let args = Args::new();
@ -62,17 +73,29 @@ impl<H: CefEventHandler> CefContextBuilder<H> {
}
}
fn common_settings(instance_dir: &Path) -> Settings {
Settings {
windowless_rendering_enabled: 1,
root_cache_path: instance_dir.to_str().map(CefString::from).unwrap(),
cache_path: CefString::from(""),
disable_signal_handlers: 1,
..Default::default()
}
}
#[cfg(target_os = "macos")]
pub(crate) fn initialize(self, event_handler: H, disable_gpu_acceleration: bool) -> Result<impl CefContext, InitError> {
let instance_dir = create_instance_dir();
let exe = std::env::current_exe().expect("cannot get current exe path");
let app_root = exe.parent().and_then(|p| p.parent()).expect("bad path structure").parent().expect("bad path structure");
let settings = Settings {
windowless_rendering_enabled: 1,
main_bundle_path: CefString::from(app_root.to_str().unwrap()),
multi_threaded_message_loop: 0,
external_message_pump: 1,
root_cache_path: instance_dir.to_str().map(CefString::from).unwrap(),
cache_path: CefString::from(""),
..Default::default()
no_sandbox: 1, // GPU helper crashes when running with sandbox
..Self::common_settings(&instance_dir)
};
self.initialize_inner(&event_handler, settings)?;
@ -85,11 +108,8 @@ impl<H: CefEventHandler> CefContextBuilder<H> {
let instance_dir = create_instance_dir();
let settings = Settings {
windowless_rendering_enabled: 1,
multi_threaded_message_loop: 1,
root_cache_path: instance_dir.to_str().map(CefString::from).unwrap(),
cache_path: CefString::from(""),
..Default::default()
..Self::common_settings(&instance_dir)
};
self.initialize_inner(&event_handler, settings)?;

View file

@ -13,6 +13,8 @@ mod scheme_handler_factory;
mod display_handler;
pub(super) mod render_handler;
#[cfg(not(target_os = "macos"))]
pub(super) mod task;
pub(super) use browser_process_app::BrowserProcessAppImpl;

View file

@ -66,6 +66,12 @@ impl<H: CefEventHandler + Clone> ImplApp for BrowserProcessAppImpl<H> {
cmd.append_switch_with_value(Some(&CefString::from("ozone-platform")), Some(&CefString::from("wayland")));
}
}
#[cfg(target_os = "macos")]
{
// Hide user prompt asking for keychain access
cmd.append_switch(Some(&CefString::from("use-mock-keychain")));
}
}
}

View file

@ -19,8 +19,13 @@ impl<H: CefEventHandler> DisplayHandlerImpl<H> {
}
}
#[cfg(not(target_os = "macos"))]
type CefCursorHandle = cef::CursorHandle;
#[cfg(target_os = "macos")]
type CefCursorHandle = *mut u8;
impl<H: CefEventHandler> ImplDisplayHandler for DisplayHandlerImpl<H> {
fn on_cursor_change(&self, _browser: Option<&mut cef::Browser>, _cursor: cef::CursorHandle, cursor_type: cef::CursorType, _custom_cursor_info: Option<&cef::CursorInfo>) -> ::std::os::raw::c_int {
fn on_cursor_change(&self, _browser: Option<&mut cef::Browser>, _cursor: CefCursorHandle, cursor_type: cef::CursorType, _custom_cursor_info: Option<&cef::CursorInfo>) -> ::std::os::raw::c_int {
let cursor = match cursor_type.into() {
CT_POINTER => CursorIcon::Default,
CT_CROSS => CursorIcon::Crosshair,

View file

@ -1,7 +1,6 @@
//! Common utilities and traits for texture import across platforms
use crate::cef::texture_import::*;
use ash::vk;
use cef::sys::cef_color_type_t;
use wgpu::Device;
@ -20,10 +19,10 @@ pub mod format {
#[cfg(not(target_os = "macos"))]
/// Convert CEF color type to Vulkan format
pub fn cef_to_vulkan(format: cef_color_type_t) -> Result<vk::Format, TextureImportError> {
pub fn cef_to_vulkan(format: cef_color_type_t) -> Result<ash::vk::Format, TextureImportError> {
match format {
cef_color_type_t::CEF_COLOR_TYPE_BGRA_8888 => Ok(vk::Format::B8G8R8A8_UNORM),
cef_color_type_t::CEF_COLOR_TYPE_RGBA_8888 => Ok(vk::Format::R8G8B8A8_UNORM),
cef_color_type_t::CEF_COLOR_TYPE_BGRA_8888 => Ok(ash::vk::Format::B8G8R8A8_UNORM),
cef_color_type_t::CEF_COLOR_TYPE_RGBA_8888 => Ok(ash::vk::Format::R8G8B8A8_UNORM),
_ => Err(TextureImportError::UnsupportedFormat { format }),
}
}
@ -63,8 +62,10 @@ pub mod texture {
}
/// Common Vulkan utilities
#[cfg(not(target_os = "macos"))]
pub mod vulkan {
use super::*;
use ash::vk;
/// Find a suitable memory type index for Vulkan allocation
pub fn find_memory_type_index(type_filter: u32, properties: vk::MemoryPropertyFlags, mem_properties: &vk::PhysicalDeviceMemoryProperties) -> Option<u32> {
@ -72,7 +73,6 @@ pub mod vulkan {
}
/// Check if the wgpu device is using Vulkan backend
#[cfg(not(target_os = "macos"))]
pub fn is_vulkan_backend(device: &Device) -> bool {
use wgpu::hal::api;
let mut is_vulkan = false;

View file

@ -3,9 +3,8 @@
use super::common::{format, texture};
use super::{TextureImportError, TextureImportResult, TextureImporter};
use cef::{AcceleratedPaintInfo, sys::cef_color_type_t};
use core_foundation::base::{CFType, TCFType};
use objc2_io_surface::{IOSurface, IOSurfaceRef};
use objc2_metal::{MTLDevice, MTLPixelFormat, MTLTexture, MTLTextureDescriptor, MTLTextureType, MTLTextureUsage};
use metal::foreign_types::ForeignType;
use metal::{MTLPixelFormat, MTLTextureType, MTLTextureUsage, Texture};
use std::os::raw::c_void;
use wgpu::hal::api;
@ -19,7 +18,7 @@ pub struct IOSurfaceImporter {
impl TextureImporter for IOSurfaceImporter {
fn new(info: &AcceleratedPaintInfo) -> Self {
Self {
handle: info.shared_texture_handle,
handle: info.shared_texture_io_surface,
format: *info.format.as_ref(),
width: info.extra.coded_size.width as u32,
height: info.extra.coded_size.height as u32,
@ -58,37 +57,31 @@ impl TextureImporter for IOSurfaceImporter {
impl IOSurfaceImporter {
fn import_via_metal(&self, device: &wgpu::Device) -> TextureImportResult {
// Get wgpu's Metal device
use wgpu::{hal::Api, wgc::api::Metal};
use wgpu::wgc::api::Metal;
let hal_texture = unsafe {
device.as_hal::<api::Metal, _, _>(|device| {
let Some(device) = device else {
device.as_hal::<api::Metal, _, _>(|hal_device| {
let Some(hal_device) = hal_device else {
return Err(TextureImportError::HardwareUnavailable {
reason: "Device is not using Metal backend".to_string(),
});
};
// Import IOSurface handle into Metal texture
let metal_texture = self.import_iosurface_to_metal(device)?;
let metal_texture = self.import_iosurface_to_metal_texture(hal_device)?;
// Wrap Metal texture in wgpu-hal texture
// texture_from_raw signature: (texture, format, texture_type, mip_levels, sample_count, copy_extent)
let hal_texture = <api::Metal as wgpu::hal::Api>::Device::texture_from_raw(
metal_texture,
&wgpu::hal::TextureDescriptor {
label: Some("CEF IOSurface Texture"),
size: wgpu::Extent3d {
width: self.width,
height: self.height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: format::cef_to_wgpu(self.format)?,
usage: wgpu::hal::TextureUses::RESOURCE,
memory_flags: wgpu::hal::MemoryFlags::empty(),
view_formats: vec![],
format::cef_to_wgpu(self.format)?,
MTLTextureType::D2,
1, // mip_level_count
1, // sample_count
wgpu::hal::CopyExtent {
width: self.width,
height: self.height,
depth: 1,
},
None, // drop_callback
);
Ok(hal_texture)
@ -119,46 +112,61 @@ impl IOSurfaceImporter {
Ok(texture)
}
fn import_iosurface_to_metal(&self, hal_device: &<api::Metal as wgpu::hal::Api>::Device) -> Result<<api::Metal as wgpu::hal::Api>::Texture, TextureImportError> {
fn import_iosurface_to_metal_texture(&self, hal_device: &<api::Metal as wgpu::hal::Api>::Device) -> Result<Texture, TextureImportError> {
// Validate dimensions
if self.width == 0 || self.height == 0 {
return Err(TextureImportError::InvalidHandle("Invalid IOSurface texture dimensions".to_string()));
}
// Convert handle to IOSurface
let iosurface = unsafe {
let cf_type = CFType::wrap_under_get_rule(self.handle as IOSurfaceRef);
IOSurface::from(cf_type)
};
// Get the Metal device from wgpu-hal
let metal_device = hal_device.raw_device();
// Convert CEF format to Metal pixel format
let metal_format = self.cef_to_metal_format(self.format)?;
// Create Metal texture descriptor
let texture_descriptor = MTLTextureDescriptor::new();
texture_descriptor.setTextureType(MTLTextureType::Type2D);
texture_descriptor.setPixelFormat(metal_format);
texture_descriptor.setWidth(self.width as usize);
texture_descriptor.setHeight(self.height as usize);
texture_descriptor.setDepth(1);
texture_descriptor.setMipmapLevelCount(1);
texture_descriptor.setSampleCount(1);
texture_descriptor.setUsage(MTLTextureUsage::ShaderRead);
// Create Metal texture from IOSurface using objc runtime
// We need to use raw objc because the metal crate doesn't expose IOSurface creation directly
#[allow(unexpected_cfgs)] // Suppress objc crate internal cfg warnings
unsafe {
use objc::runtime::Object;
use objc::{class, msg_send, sel, sel_impl};
// Create Metal texture from IOSurface
let metal_texture = unsafe { metal_device.newTextureWithDescriptor_iosurface_plane(&texture_descriptor, &iosurface, 0) };
let iosurface = self.handle;
let Some(metal_texture) = metal_texture else {
return Err(TextureImportError::PlatformError {
message: "Failed to create Metal texture from IOSurface".to_string(),
});
};
// Create texture descriptor using NSObject/Objective-C
let descriptor_class = class!(MTLTextureDescriptor);
let descriptor: *mut Object = msg_send![descriptor_class, new];
tracing::trace!("Successfully created Metal texture from IOSurface");
Ok(metal_texture)
// Set descriptor properties
let _: () = msg_send![descriptor, setTextureType: MTLTextureType::D2];
let _: () = msg_send![descriptor, setPixelFormat: metal_format];
let _: () = msg_send![descriptor, setWidth: self.width as u64];
let _: () = msg_send![descriptor, setHeight: self.height as u64];
let _: () = msg_send![descriptor, setDepth: 1u64];
let _: () = msg_send![descriptor, setMipmapLevelCount: 1u64];
let _: () = msg_send![descriptor, setSampleCount: 1u64];
let _: () = msg_send![descriptor, setArrayLength: 1u64];
let _: () = msg_send![descriptor, setUsage: MTLTextureUsage::ShaderRead.bits()];
// Get device pointer
let device_ptr = metal_device.lock().as_ptr();
// Call newTextureWithDescriptor:iosurface:plane:
let metal_texture: *mut Object = msg_send![device_ptr, newTextureWithDescriptor:descriptor iosurface:iosurface plane:0u64];
// Release the descriptor
let _: () = msg_send![descriptor, release];
if metal_texture.is_null() {
return Err(TextureImportError::PlatformError {
message: "Failed to create Metal texture from IOSurface".to_string(),
});
}
// Cast to correct type and wrap in metal::Texture
let mtl_texture = metal_texture as *mut metal::MTLTexture;
Ok(Texture::from_ptr(mtl_texture))
}
}
fn cef_to_metal_format(&self, format: cef_color_type_t) -> Result<MTLPixelFormat, TextureImportError> {

View file

@ -10,13 +10,6 @@
//! - **Windows**: D3D11 shared textures via Vulkan interop
//! - **macOS**: IOSurface via Metal native API
//!
//! # Usage
//!
//! ```no_run
//! // Import texture with automatic platform detection
//! let texture = shared_handle.import_texture(&device)?;
//! ```
//!
//! # Features
//!
//! - `accelerated_paint` - Base feature for texture import
@ -54,6 +47,7 @@ pub enum TextureImportError {
HardwareUnavailable { reason: String },
#[error("Vulkan operation failed: {operation}")]
#[cfg(not(target_os = "macos"))]
VulkanError { operation: String },
#[error("Platform-specific error: {message}")]

80
desktop/src/lib.rs Normal file
View file

@ -0,0 +1,80 @@
use clap::Parser;
use std::process::exit;
use tracing_subscriber::EnvFilter;
use winit::event_loop::EventLoop;
pub(crate) mod consts;
mod app;
mod cef;
mod cli;
mod dirs;
mod event;
mod native_window;
mod persist;
mod render;
mod gpu_context;
use app::App;
use cef::CefHandler;
use cli::Cli;
use event::CreateAppEventSchedulerEventLoopExt;
pub fn start() {
tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).init();
let cef_context_builder = cef::CefContextBuilder::<CefHandler>::new();
if cef_context_builder.is_sub_process() {
// We are in a CEF subprocess
// This will block until the CEF subprocess quits
let error = cef_context_builder.execute_sub_process();
tracing::warn!("Cef subprocess failed with error: {error}");
return;
}
let cli = Cli::parse();
let wgpu_context = futures::executor::block_on(gpu_context::create_wgpu_context());
let event_loop = EventLoop::new().unwrap();
let (app_event_sender, app_event_receiver) = std::sync::mpsc::channel();
let app_event_scheduler = event_loop.create_app_event_scheduler(app_event_sender);
let (window_size_sender, window_size_receiver) = std::sync::mpsc::channel();
let cef_handler = cef::CefHandler::new(wgpu_context.clone(), app_event_scheduler.clone(), window_size_receiver);
let cef_context = match cef_context_builder.initialize(cef_handler, cli.disable_ui_acceleration) {
Ok(c) => {
tracing::info!("CEF initialized successfully");
c
}
Err(cef::InitError::AlreadyRunning) => {
tracing::error!("Another instance is already running, Exiting.");
exit(0);
}
Err(cef::InitError::InitializationFailed(code)) => {
tracing::error!("Cef initialization failed with code: {code}");
exit(1);
}
Err(cef::InitError::BrowserCreationFailed) => {
tracing::error!("Failed to create CEF browser");
exit(1);
}
Err(cef::InitError::RequestContextCreationFailed) => {
tracing::error!("Failed to create CEF request context");
exit(1);
}
};
let mut app = App::new(Box::new(cef_context), window_size_sender, wgpu_context, app_event_receiver, app_event_scheduler, cli.files);
event_loop.run_app(&mut app).unwrap();
}
pub fn start_helper() {
let cef_context_builder = cef::CefContextBuilder::<CefHandler>::new_helper();
assert!(cef_context_builder.is_sub_process());
cef_context_builder.execute_sub_process();
}

View file

@ -1,74 +1,3 @@
use clap::Parser;
use std::process::exit;
use tracing_subscriber::EnvFilter;
use winit::event_loop::EventLoop;
pub(crate) mod consts;
mod app;
mod cef;
mod cli;
mod dirs;
mod event;
mod native_window;
mod persist;
mod render;
mod gpu_context;
use app::App;
use cef::CefHandler;
use cli::Cli;
use event::CreateAppEventSchedulerEventLoopExt;
fn main() {
tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).init();
let cef_context_builder = cef::CefContextBuilder::<CefHandler>::new();
if cef_context_builder.is_sub_process() {
// We are in a CEF subprocess
// This will block until the CEF subprocess quits
let error = cef_context_builder.execute_sub_process();
tracing::warn!("Cef subprocess failed with error: {error}");
return;
}
let cli = Cli::parse();
let wgpu_context = futures::executor::block_on(gpu_context::create_wgpu_context());
let event_loop = EventLoop::new().unwrap();
let (app_event_sender, app_event_receiver) = std::sync::mpsc::channel();
let app_event_scheduler = event_loop.create_app_event_scheduler(app_event_sender);
let (window_size_sender, window_size_receiver) = std::sync::mpsc::channel();
let cef_handler = cef::CefHandler::new(wgpu_context.clone(), app_event_scheduler.clone(), window_size_receiver);
let cef_context = match cef_context_builder.initialize(cef_handler, cli.disable_ui_acceleration) {
Ok(c) => {
tracing::info!("CEF initialized successfully");
c
}
Err(cef::InitError::AlreadyRunning) => {
tracing::error!("Another instance is already running, Exiting.");
exit(0);
}
Err(cef::InitError::InitializationFailed(code)) => {
tracing::error!("Cef initialization failed with code: {code}");
exit(1);
}
Err(cef::InitError::BrowserCreationFailed) => {
tracing::error!("Failed to create CEF browser");
exit(1);
}
Err(cef::InitError::RequestContextCreationFailed) => {
tracing::error!("Failed to create CEF request context");
exit(1);
}
};
let mut app = App::new(Box::new(cef_context), window_size_sender, wgpu_context, app_event_receiver, app_event_scheduler, cli.files);
event_loop.run_app(&mut app).unwrap();
graphite_desktop::start();
}

View file

@ -50,6 +50,15 @@ impl NativeWindowHandle {
}
}
#[cfg(target_os = "macos")]
{
let mac_window = winit::platform::macos::WindowAttributesMacOS::default()
.with_titlebar_transparent(true)
.with_fullsize_content_view(true)
.with_title_hidden(true);
window = window.with_platform_attributes(Box::new(mac_window));
}
window
}
#[allow(unused_variables)]