Skip to content

Commit 27b6360

Browse files
committed
Import textures through metal on macos
1 parent 176b888 commit 27b6360

File tree

9 files changed

+94
-84
lines changed

9 files changed

+94
-84
lines changed

desktop/Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ default = ["gpu", "accelerated_paint"]
1313
gpu = ["graphite-desktop-wrapper/gpu"]
1414

1515
# Hardware acceleration features
16-
accelerated_paint = ["ash", "accelerated_paint_dmabuf", "accelerated_paint_d3d11", "accelerated_paint_iosurface"]
17-
accelerated_paint_dmabuf = ["libc"]
18-
accelerated_paint_d3d11 = ["windows"]
16+
accelerated_paint = ["accelerated_paint_dmabuf", "accelerated_paint_d3d11", "accelerated_paint_iosurface"]
17+
accelerated_paint_dmabuf = ["libc", "ash"]
18+
accelerated_paint_d3d11 = ["windows", "ash"]
1919
accelerated_paint_iosurface = ["objc2-io-surface", "objc2-metal", "core-foundation"]
2020

2121
[dependencies]

desktop/src/app.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
use crate::CustomEvent;
22
use crate::cef::WindowSize;
3-
use crate::consts::APP_NAME;
3+
use crate::consts::{APP_NAME, CEF_MESSAGE_LOOP_MAX_ITERATIONS};
44
use crate::render::GraphicsState;
55
use graphite_desktop_wrapper::messages::{DesktopFrontendMessage, DesktopWrapperMessage};
66
use graphite_desktop_wrapper::{DesktopWrapper, NodeGraphExecutionResult, WgpuContext, serialize_frontend_messages};
7+
78
use rfd::AsyncFileDialog;
89
use std::sync::Arc;
910
use std::sync::mpsc::Sender;
@@ -174,7 +175,7 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
174175
{
175176
self.cef_schedule = None;
176177
// Poll cef message loop multiple times to avoid message loop starvation
177-
for _ in 0..10 {
178+
for _ in 0..CEF_MESSAGE_LOOP_MAX_ITERATIONS {
178179
self.cef_context.work();
179180
}
180181
}

desktop/src/cef/context.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@ impl Context<Setup> {
8686
let mut client = Client::new(BrowserProcessClientImpl::new(render_handler, event_handler.clone()));
8787

8888
let url = CefString::from(format!("{GRAPHITE_SCHEME}://{FRONTEND_DOMAIN}/").as_str());
89-
// let url = CefString::from(format!("chrome://gpu").as_str());
9089

9190
let window_info = WindowInfo {
9291
windowless_rendering_enabled: 1,
@@ -96,7 +95,7 @@ impl Context<Setup> {
9695
};
9796

9897
let settings = BrowserSettings {
99-
windowless_frame_rate: 120,
98+
windowless_frame_rate: crate::consts::CEF_WINDOWLESS_FRAME_RATE,
10099
background_color: 0x0,
101100
..Default::default()
102101
};

desktop/src/cef/texture_import/common.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ pub mod format {
5050
}
5151
}
5252

53-
#[cfg(feature = "accelerated_paint")]
53+
#[cfg(all(feature = "accelerated_paint", not(target_os = "macos")))]
5454
/// Convert CEF color type to Vulkan format
5555
pub fn cef_to_vulkan(format: cef_color_type_t) -> Result<vk::Format, TextureImportError> {
5656
match format {
@@ -84,7 +84,12 @@ pub mod texture {
8484
view_formats: &[],
8585
});
8686

87-
tracing::warn!("Using fallback CPU texture - hardware acceleration not available");
87+
tracing::warn!(
88+
"Using fallback CPU texture for CEF rendering ({}x{}, {:?}) - hardware acceleration failed or unavailable. Consider checking GPU driver support.",
89+
width,
90+
height,
91+
format
92+
);
8893
Ok(texture)
8994
}
9095
}
@@ -105,6 +110,7 @@ pub mod vulkan {
105110
}
106111

107112
/// Check if the wgpu device is using Vulkan backend
113+
#[cfg(not(target_os = "macos"))]
108114
pub fn is_vulkan_backend(device: &Device) -> bool {
109115
use wgpu::hal::api;
110116
let mut is_vulkan = false;

desktop/src/cef/texture_import/d3d11.rs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ impl TextureImporter for D3D11Importer {
3838
}
3939
}
4040
}
41-
41+
4242
// Try Vulkan as fallback
4343
if vulkan::is_vulkan_backend(device) {
4444
match self.import_via_vulkan(device) {
@@ -298,13 +298,11 @@ impl D3D11Importer {
298298
// Open D3D11 shared handle on D3D12 device
299299
unsafe {
300300
let mut shared_resource: Option<ID3D12Resource> = None;
301-
d3d12_device.OpenSharedHandle(
302-
windows::Win32::Foundation::HANDLE(self.handle as isize),
303-
&ID3D12Resource::IID,
304-
&mut shared_resource as *mut _ as *mut _,
305-
).map_err(|e| TextureImportError::PlatformError {
306-
message: format!("Failed to open D3D11 shared handle on D3D12: {:?}", e),
307-
})?;
301+
d3d12_device
302+
.OpenSharedHandle(windows::Win32::Foundation::HANDLE(self.handle as isize), &ID3D12Resource::IID, &mut shared_resource as *mut _ as *mut _)
303+
.map_err(|e| TextureImportError::PlatformError {
304+
message: format!("Failed to open D3D11 shared handle on D3D12: {:?}", e),
305+
})?;
308306

309307
shared_resource.ok_or_else(|| TextureImportError::InvalidHandle("Failed to get D3D12 resource from shared handle".to_string()))
310308
}

desktop/src/cef/texture_import/iosurface.rs

Lines changed: 67 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@
33
#[cfg(target_os = "macos")]
44
use std::os::raw::c_void;
55

6-
#[cfg(all(feature = "accelerated_paint", target_os = "macos"))]
7-
use ash::vk;
86
#[cfg(all(feature = "accelerated_paint", target_os = "macos"))]
97
use core_foundation::base::{CFType, TCFType};
108
#[cfg(all(feature = "accelerated_paint", target_os = "macos"))]
119
use objc2_io_surface::{IOSurface, IOSurfaceRef};
1210
#[cfg(all(feature = "accelerated_paint", target_os = "macos"))]
11+
use objc2_metal::{MTLDevice, MTLPixelFormat, MTLTexture, MTLTextureDescriptor, MTLTextureType, MTLTextureUsage};
12+
#[cfg(all(feature = "accelerated_paint", target_os = "macos"))]
1313
use wgpu::hal::api;
1414

15-
use super::common::{TextureImportError, TextureImportResult, TextureImporter, format, texture, vulkan};
15+
use super::common::{TextureImportError, TextureImportResult, TextureImporter, format, texture};
1616
use cef::sys::cef_color_type_t;
1717

1818
#[cfg(target_os = "macos")]
@@ -30,13 +30,13 @@ impl TextureImporter for IOSurfaceImporter {
3030
#[cfg(feature = "accelerated_paint")]
3131
{
3232
if self.supports_hardware_acceleration(device) {
33-
match self.import_via_vulkan(device) {
33+
match self.import_via_metal(device) {
3434
Ok(texture) => {
35-
tracing::trace!("Successfully imported IOSurface texture via Vulkan");
35+
tracing::trace!("Successfully imported IOSurface texture via Metal");
3636
return Ok(texture);
3737
}
3838
Err(e) => {
39-
tracing::warn!("Failed to import IOSurface via Vulkan: {}, falling back to CPU texture", e);
39+
tracing::warn!("Failed to import IOSurface via Metal: {}, falling back to CPU texture", e);
4040
}
4141
}
4242
}
@@ -54,8 +54,8 @@ impl TextureImporter for IOSurfaceImporter {
5454
return false;
5555
}
5656

57-
// Check if wgpu is using Vulkan backend
58-
vulkan::is_vulkan_backend(device)
57+
// Check if wgpu is using Metal backend
58+
self.is_metal_backend(device)
5959
}
6060
#[cfg(not(feature = "accelerated_paint"))]
6161
{
@@ -68,23 +68,23 @@ impl TextureImporter for IOSurfaceImporter {
6868
#[cfg(target_os = "macos")]
6969
impl IOSurfaceImporter {
7070
#[cfg(feature = "accelerated_paint")]
71-
fn import_via_vulkan(&self, device: &wgpu::Device) -> TextureImportResult {
72-
// Get wgpu's Vulkan instance and device
73-
use wgpu::{TextureUses, wgc::api::Vulkan};
71+
fn import_via_metal(&self, device: &wgpu::Device) -> TextureImportResult {
72+
// Get wgpu's Metal device
73+
use wgpu::{hal::Api, wgc::api::Metal};
7474
let hal_texture = unsafe {
75-
device.as_hal::<api::Vulkan, _, _>(|device| {
75+
device.as_hal::<api::Metal, _, _>(|device| {
7676
let Some(device) = device else {
7777
return Err(TextureImportError::HardwareUnavailable {
78-
reason: "Device is not using Vulkan backend".to_string(),
78+
reason: "Device is not using Metal backend".to_string(),
7979
});
8080
};
8181

82-
// Import IOSurface handle into Vulkan via Metal
83-
let vk_image = self.import_iosurface_to_vulkan(device)?;
82+
// Import IOSurface handle into Metal texture
83+
let metal_texture = self.import_iosurface_to_metal(device)?;
8484

85-
// Wrap VkImage in wgpu-hal texture
86-
let hal_texture = <api::Vulkan as wgpu::hal::Api>::Device::texture_from_raw(
87-
vk_image,
85+
// Wrap Metal texture in wgpu-hal texture
86+
let hal_texture = <api::Metal as wgpu::hal::Api>::Device::texture_from_raw(
87+
metal_texture,
8888
&wgpu::hal::TextureDescriptor {
8989
label: Some("CEF IOSurface Texture"),
9090
size: wgpu::Extent3d {
@@ -96,7 +96,7 @@ impl IOSurfaceImporter {
9696
sample_count: 1,
9797
dimension: wgpu::TextureDimension::D2,
9898
format: format::cef_to_wgpu(self.format)?,
99-
usage: TextureUses::COPY_DST | TextureUses::RESOURCE,
99+
usage: wgpu::hal::TextureUses::RESOURCE,
100100
memory_flags: wgpu::hal::MemoryFlags::empty(),
101101
view_formats: vec![],
102102
},
@@ -109,7 +109,7 @@ impl IOSurfaceImporter {
109109

110110
// Import hal texture into wgpu
111111
let texture = unsafe {
112-
device.create_texture_from_hal::<Vulkan>(
112+
device.create_texture_from_hal::<Metal>(
113113
hal_texture,
114114
&wgpu::TextureDescriptor {
115115
label: Some("CEF IOSurface Texture"),
@@ -132,64 +132,66 @@ impl IOSurfaceImporter {
132132
}
133133

134134
#[cfg(feature = "accelerated_paint")]
135-
fn import_iosurface_to_vulkan(&self, hal_device: &<api::Vulkan as wgpu::hal::Api>::Device) -> Result<vk::Image, TextureImportError> {
136-
// Get raw Vulkan handles
137-
let device = hal_device.raw_device();
138-
let _instance = hal_device.shared_instance().raw_instance();
139-
135+
fn import_iosurface_to_metal(&self, hal_device: &<api::Metal as wgpu::hal::Api>::Device) -> Result<<api::Metal as wgpu::hal::Api>::Texture, TextureImportError> {
140136
// Validate dimensions
141137
if self.width == 0 || self.height == 0 {
142138
return Err(TextureImportError::InvalidHandle("Invalid IOSurface texture dimensions".to_string()));
143139
}
144140

145141
// Convert handle to IOSurface
146-
let _iosurface = unsafe {
142+
let iosurface = unsafe {
147143
let cf_type = CFType::wrap_under_get_rule(self.handle as IOSurfaceRef);
148144
IOSurface::from(cf_type)
149145
};
150146

151-
// Note: Full Metal-to-Vulkan import would require:
152-
// 1. Creating Metal texture from IOSurface
153-
// 2. Using VK_EXT_metal_objects to import Metal texture into Vulkan
154-
// 3. Proper synchronization between Metal and Vulkan
155-
//
156-
// This is complex and not fully supported by current objc2 bindings.
157-
// For now, we create a minimal Vulkan image and rely on fallback.
158-
159-
// Create external memory image info for Metal objects
160-
let mut external_memory_info = vk::ExternalMemoryImageCreateInfo::default().handle_types(vk::ExternalMemoryHandleTypeFlags::MTLTEXTURE_EXT);
161-
162-
// Create image create info
163-
let image_create_info = vk::ImageCreateInfo::default()
164-
.image_type(vk::ImageType::TYPE_2D)
165-
.format(format::cef_to_vulkan(self.format)?)
166-
.extent(vk::Extent3D {
167-
width: self.width,
168-
height: self.height,
169-
depth: 1,
170-
})
171-
.mip_levels(1)
172-
.array_layers(1)
173-
.samples(vk::SampleCountFlags::TYPE_1)
174-
.tiling(vk::ImageTiling::OPTIMAL)
175-
.usage(vk::ImageUsageFlags::SAMPLED | vk::ImageUsageFlags::COLOR_ATTACHMENT)
176-
.sharing_mode(vk::SharingMode::EXCLUSIVE)
177-
.push_next(&mut external_memory_info);
178-
179-
// Create the image
180-
let image = unsafe {
181-
device.create_image(&image_create_info, None).map_err(|e| TextureImportError::VulkanError {
182-
operation: format!("Failed to create Vulkan image: {:?}", e),
183-
})?
147+
// Get the Metal device from wgpu-hal
148+
let metal_device = hal_device.raw_device();
149+
150+
// Convert CEF format to Metal pixel format
151+
let metal_format = self.cef_to_metal_format(self.format)?;
152+
153+
// Create Metal texture descriptor
154+
let texture_descriptor = MTLTextureDescriptor::new();
155+
texture_descriptor.setTextureType(MTLTextureType::Type2D);
156+
texture_descriptor.setPixelFormat(metal_format);
157+
texture_descriptor.setWidth(self.width as usize);
158+
texture_descriptor.setHeight(self.height as usize);
159+
texture_descriptor.setDepth(1);
160+
texture_descriptor.setMipmapLevelCount(1);
161+
texture_descriptor.setSampleCount(1);
162+
texture_descriptor.setUsage(MTLTextureUsage::ShaderRead);
163+
164+
// Create Metal texture from IOSurface
165+
let metal_texture = unsafe { metal_device.newTextureWithDescriptor_iosurface_plane(&texture_descriptor, &iosurface, 0) };
166+
167+
let Some(metal_texture) = metal_texture else {
168+
return Err(TextureImportError::PlatformError {
169+
message: "Failed to create Metal texture from IOSurface".to_string(),
170+
});
184171
};
185172

186-
// Note: The actual Metal-to-Vulkan import would require VK_EXT_metal_objects
187-
// and proper Metal texture handle extraction, which is complex and not
188-
// fully supported in the current objc2 bindings. For now, we return the
189-
// image and rely on fallback behavior.
173+
tracing::trace!("Successfully created Metal texture from IOSurface");
174+
Ok(metal_texture)
175+
}
190176

191-
tracing::warn!("Metal-to-Vulkan texture import not fully implemented");
177+
#[cfg(feature = "accelerated_paint")]
178+
fn cef_to_metal_format(&self, format: cef_color_type_t) -> Result<MTLPixelFormat, TextureImportError> {
179+
match format {
180+
cef_color_type_t::CEF_COLOR_TYPE_BGRA_8888 => Ok(MTLPixelFormat::BGRA8Unorm_sRGB),
181+
cef_color_type_t::CEF_COLOR_TYPE_RGBA_8888 => Ok(MTLPixelFormat::RGBA8Unorm_sRGB),
182+
_ => Err(TextureImportError::UnsupportedFormat { format }),
183+
}
184+
}
192185

193-
Ok(image)
186+
#[cfg(feature = "accelerated_paint")]
187+
fn is_metal_backend(&self, device: &wgpu::Device) -> bool {
188+
use wgpu::hal::api;
189+
let mut is_metal = false;
190+
unsafe {
191+
device.as_hal::<api::Metal, _, _>(|device| {
192+
is_metal = device.is_some();
193+
});
194+
}
195+
is_metal
194196
}
195197
}

desktop/src/cef/texture_import/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
//!
99
//! - **Linux**: DMA-BUF via Vulkan external memory
1010
//! - **Windows**: D3D11 shared textures via Vulkan interop
11-
//! - **macOS**: IOSurface via Metal/Vulkan interop
11+
//! - **macOS**: IOSurface via Metal native API
1212
//!
1313
//! # Usage
1414
//!

desktop/src/consts.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
pub(crate) static APP_NAME: &str = "Graphite";
22
pub(crate) static APP_ID: &str = "rs.graphite.GraphiteEditor";
33
pub(crate) static APP_DIRECTORY_NAME: &str = "graphite-editor";
4+
5+
// CEF configuration constants
6+
pub(crate) const CEF_WINDOWLESS_FRAME_RATE: i32 = 120;
7+
pub(crate) const CEF_MESSAGE_LOOP_MAX_ITERATIONS: usize = 10;

desktop/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use app::WinitApp;
1717
mod dirs;
1818

1919
use graphite_desktop_wrapper::messages::DesktopWrapperMessage;
20-
use graphite_desktop_wrapper::{DesktopWrapper, NodeGraphExecutionResult, WgpuContext};
20+
use graphite_desktop_wrapper::{NodeGraphExecutionResult, WgpuContext};
2121

2222
pub(crate) enum CustomEvent {
2323
UiUpdate(wgpu::Texture),

0 commit comments

Comments
 (0)