Skip to content

Commit a415e74

Browse files
committed
Experimental macos support for texture import
1 parent 2deb807 commit a415e74

File tree

5 files changed

+302
-8
lines changed

5 files changed

+302
-8
lines changed

Cargo.lock

Lines changed: 32 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

desktop/Cargo.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ rust-version = "1.87"
1111
[features]
1212
default = ["gpu",]
1313
gpu = ["graphite-editor/gpu"]
14-
accelerated_paint = ["ash", "libc", "windows"]
14+
accelerated_paint = ["ash", "libc", "windows", "objc2-io-surface", "objc2-metal", "core-foundation"]
1515

1616
[dependencies]
1717
# # Local dependencies
@@ -54,3 +54,9 @@ windows = { version = "0.58", features = [
5454
"Win32_Graphics_Dxgi_Common",
5555
"Win32_Foundation"
5656
], optional = true }
57+
58+
# macOS-specific dependencies
59+
[target.'cfg(target_os = "macos")'.dependencies]
60+
objc2-io-surface = { version = "0.3", optional = true }
61+
objc2-metal = { version = "0.3", optional = true }
62+
core-foundation = { version = "0.9", optional = true }

desktop/src/cef.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ mod dirs;
99
mod dmabuf;
1010
#[cfg(target_os = "windows")]
1111
mod d3d11;
12+
#[cfg(target_os = "macos")]
13+
mod iosurface;
1214
mod input;
1315
mod internal;
1416
mod ipc;
@@ -21,6 +23,8 @@ use winit::event_loop::EventLoopProxy;
2123

2224
#[cfg(target_os = "windows")]
2325
use crate::cef::d3d11::D3D11SharedTexture;
26+
#[cfg(target_os = "macos")]
27+
use crate::cef::iosurface::IOSurfaceTexture;
2428

2529
pub(crate) trait CefEventHandler: Clone {
2630
fn window_size(&self) -> WindowSize;
@@ -193,8 +197,21 @@ impl CefEventHandler for CefHandler {
193197
}
194198
}
195199
#[cfg(target_os = "macos")]
196-
internal::render_handler::SharedTextureHandle::IOSurface(_handle) => {
197-
tracing::warn!("IOSurface shared texture import not implemented yet");
200+
internal::render_handler::SharedTextureHandle::IOSurface { handle, format, width, height } => {
201+
let iosurface_texture = IOSurfaceTexture {
202+
handle,
203+
width,
204+
height,
205+
format,
206+
};
207+
match iosurface_texture.import_to_wgpu(&self.wgpu_context.device) {
208+
Ok(texture) => {
209+
let _ = self.event_loop_proxy.send_event(CustomEvent::UiUpdate(texture));
210+
}
211+
Err(e) => {
212+
tracing::error!("Failed to import IOSurface texture: {}", e);
213+
}
214+
}
198215
}
199216
}
200217
}

desktop/src/cef/internal/render_handler.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,12 @@ pub enum SharedTextureHandle {
2525
height: u32,
2626
},
2727
#[cfg(target_os = "macos")]
28-
IOSurface(*mut c_void),
28+
IOSurface {
29+
handle: *mut c_void,
30+
format: cef::sys::cef_color_type_t,
31+
width: u32,
32+
height: u32,
33+
},
2934
#[cfg(target_os = "linux")]
3035
DmaBuf {
3136
fds: Vec<RawFd>,
@@ -113,8 +118,13 @@ impl<H: CefEventHandler> ImplRenderHandler for RenderHandlerImpl<H> {
113118

114119
#[cfg(target_os = "macos")]
115120
{
116-
// Extract IOSurface handle
117-
let shared_handle = SharedTextureHandle::IOSurface(info.shared_texture_handle);
121+
// Extract IOSurface handle with texture metadata
122+
let shared_handle = SharedTextureHandle::IOSurface {
123+
handle: info.shared_texture_handle,
124+
format: *info.format.as_ref(),
125+
width: info.extra.coded_size.width as u32,
126+
height: info.extra.coded_size.height as u32,
127+
};
118128
self.event_handler.on_accelerated_paint(shared_handle);
119129
}
120130
}

desktop/src/cef/iosurface.rs

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
#[cfg(target_os = "macos")]
2+
use std::os::raw::c_void;
3+
4+
#[cfg(all(feature = "accelerated_paint", target_os = "macos"))]
5+
use ash::vk;
6+
#[cfg(all(feature = "accelerated_paint", target_os = "macos"))]
7+
use wgpu::hal::api;
8+
#[cfg(all(feature = "accelerated_paint", target_os = "macos"))]
9+
use objc2_io_surface::{IOSurface, IOSurfaceRef};
10+
#[cfg(all(feature = "accelerated_paint", target_os = "macos"))]
11+
use objc2_metal::{MTLDevice, MTLTexture, MTLTextureDescriptor, MTLPixelFormat};
12+
#[cfg(all(feature = "accelerated_paint", target_os = "macos"))]
13+
use core_foundation::base::{CFType, TCFType};
14+
15+
#[cfg(target_os = "macos")]
16+
pub struct IOSurfaceTexture {
17+
pub handle: *mut c_void,
18+
pub width: u32,
19+
pub height: u32,
20+
pub format: cef::sys::cef_color_type_t,
21+
}
22+
23+
#[cfg(target_os = "macos")]
24+
impl IOSurfaceTexture {
25+
pub fn import_to_wgpu(&self, device: &wgpu::Device) -> Result<wgpu::Texture, String> {
26+
tracing::debug!(
27+
"IOSurface texture import requested: {}x{} handle={:p}",
28+
self.width,
29+
self.height,
30+
self.handle
31+
);
32+
33+
// Try to import via Metal/Vulkan, fallback to CPU texture on failure
34+
#[cfg(feature = "accelerated_paint")]
35+
{
36+
match self.import_via_vulkan(device) {
37+
Ok(texture) => {
38+
tracing::info!("Successfully imported IOSurface texture via Vulkan");
39+
return Ok(texture);
40+
}
41+
Err(e) => {
42+
tracing::warn!("Failed to import IOSurface via Vulkan: {}, falling back to CPU texture", e);
43+
}
44+
}
45+
}
46+
47+
// Fallback: create empty CPU texture with same dimensions
48+
self.create_fallback_texture(device)
49+
}
50+
51+
#[cfg(feature = "accelerated_paint")]
52+
fn import_via_vulkan(&self, device: &wgpu::Device) -> Result<wgpu::Texture, String> {
53+
// Validate handle
54+
if self.handle.is_null() {
55+
return Err("IOSurface handle is null".to_string());
56+
}
57+
58+
// Get wgpu's Vulkan instance and device
59+
use wgpu::{TextureUses, wgc::api::Vulkan};
60+
let hal_texture = unsafe {
61+
device.as_hal::<api::Vulkan, _, _>(|device| {
62+
let Some(device) = device else {
63+
return Err("Device is not using Vulkan backend".to_string());
64+
};
65+
66+
// Import IOSurface handle into Vulkan via Metal
67+
let vk_image = self.import_iosurface_to_vulkan(device).map_err(|e| format!("Failed to create Vulkan image from IOSurface: {}", e))?;
68+
69+
// Wrap VkImage in wgpu-hal texture
70+
let hal_texture = <api::Vulkan as wgpu::hal::Api>::Device::texture_from_raw(
71+
vk_image,
72+
&wgpu::hal::TextureDescriptor {
73+
label: Some("CEF IOSurface Texture"),
74+
size: wgpu::Extent3d {
75+
width: self.width,
76+
height: self.height,
77+
depth_or_array_layers: 1,
78+
},
79+
mip_level_count: 1,
80+
sample_count: 1,
81+
dimension: wgpu::TextureDimension::D2,
82+
format: self.cef_to_hal_format()?,
83+
usage: TextureUses::COPY_DST | TextureUses::RESOURCE,
84+
memory_flags: wgpu::hal::MemoryFlags::empty(),
85+
view_formats: vec![],
86+
},
87+
None, // drop_callback
88+
);
89+
90+
Ok(hal_texture)
91+
})
92+
}?;
93+
94+
// Import hal texture into wgpu
95+
let texture = unsafe {
96+
device.create_texture_from_hal::<Vulkan>(
97+
hal_texture,
98+
&wgpu::TextureDescriptor {
99+
label: Some("CEF IOSurface Texture"),
100+
size: wgpu::Extent3d {
101+
width: self.width,
102+
height: self.height,
103+
depth_or_array_layers: 1,
104+
},
105+
mip_level_count: 1,
106+
sample_count: 1,
107+
dimension: wgpu::TextureDimension::D2,
108+
format: self.cef_to_wgpu_format()?,
109+
usage: wgpu::TextureUsages::TEXTURE_BINDING,
110+
view_formats: &[],
111+
},
112+
)
113+
};
114+
115+
Ok(texture)
116+
}
117+
118+
#[cfg(feature = "accelerated_paint")]
119+
fn import_iosurface_to_vulkan(&self, hal_device: &<api::Vulkan as wgpu::hal::Api>::Device) -> Result<vk::Image, String> {
120+
// Get raw Vulkan handles
121+
let device = hal_device.raw_device();
122+
let _instance = hal_device.shared_instance().raw_instance();
123+
124+
// Validate dimensions
125+
if self.width == 0 || self.height == 0 {
126+
return Err("Invalid IOSurface texture dimensions".to_string());
127+
}
128+
129+
// Convert handle to IOSurface
130+
let iosurface = unsafe {
131+
let cf_type = CFType::wrap_under_get_rule(self.handle as IOSurfaceRef);
132+
IOSurface::from(cf_type)
133+
};
134+
135+
// Create Metal texture from IOSurface
136+
let mtl_texture = self.create_metal_texture_from_iosurface(&iosurface)?;
137+
138+
// Import Metal texture into Vulkan using VK_EXT_metal_objects
139+
self.import_metal_texture_to_vulkan(device, &mtl_texture)
140+
}
141+
142+
#[cfg(feature = "accelerated_paint")]
143+
fn create_metal_texture_from_iosurface(&self, iosurface: &IOSurface) -> Result<MTLTexture, String> {
144+
// Get Metal device (this would need to be obtained from wgpu-hal)
145+
// For now, we'll create a simple fallback
146+
tracing::warn!("Metal texture creation from IOSurface not fully implemented");
147+
Err("Metal texture creation not available".to_string())
148+
}
149+
150+
#[cfg(feature = "accelerated_paint")]
151+
fn import_metal_texture_to_vulkan(&self, device: vk::Device, _mtl_texture: &MTLTexture) -> Result<vk::Image, String> {
152+
// Create external memory image info for Metal objects
153+
let mut external_memory_info = vk::ExternalMemoryImageCreateInfo::default()
154+
.handle_types(vk::ExternalMemoryHandleTypeFlags::MTLTEXTURE_EXT);
155+
156+
// Create image create info
157+
let image_create_info = vk::ImageCreateInfo::default()
158+
.image_type(vk::ImageType::TYPE_2D)
159+
.format(self.cef_to_vk_format()?)
160+
.extent(vk::Extent3D {
161+
width: self.width,
162+
height: self.height,
163+
depth: 1,
164+
})
165+
.mip_levels(1)
166+
.array_layers(1)
167+
.samples(vk::SampleCountFlags::TYPE_1)
168+
.tiling(vk::ImageTiling::OPTIMAL)
169+
.usage(vk::ImageUsageFlags::SAMPLED | vk::ImageUsageFlags::COLOR_ATTACHMENT)
170+
.sharing_mode(vk::SharingMode::EXCLUSIVE)
171+
.push_next(&mut external_memory_info);
172+
173+
// Create the image
174+
let image = unsafe {
175+
device.create_image(&image_create_info, None)
176+
.map_err(|e| format!("Failed to create Vulkan image: {:?}", e))?
177+
};
178+
179+
// Note: The actual Metal-to-Vulkan import would require VK_EXT_metal_objects
180+
// and proper Metal texture handle extraction, which is complex and not
181+
// fully supported in the current objc2 bindings
182+
tracing::warn!("Metal-to-Vulkan texture import not fully implemented");
183+
184+
Ok(image)
185+
}
186+
187+
fn cef_to_vk_format(&self) -> Result<vk::Format, String> {
188+
use cef::sys::cef_color_type_t;
189+
match self.format {
190+
// macOS IOSurfaces are typically BGRA
191+
cef_color_type_t::CEF_COLOR_TYPE_BGRA_8888 => Ok(vk::Format::B8G8R8A8_UNORM),
192+
cef_color_type_t::CEF_COLOR_TYPE_RGBA_8888 => Ok(vk::Format::R8G8B8A8_UNORM),
193+
_ => Err(format!("Unsupported CEF format for Vulkan: {:?}", self.format)),
194+
}
195+
}
196+
197+
fn cef_to_hal_format(&self) -> Result<wgpu::TextureFormat, String> {
198+
use cef::sys::cef_color_type_t;
199+
match self.format {
200+
// macOS IOSurfaces are typically BGRA with sRGB
201+
cef_color_type_t::CEF_COLOR_TYPE_BGRA_8888 => Ok(wgpu::TextureFormat::Bgra8UnormSrgb),
202+
cef_color_type_t::CEF_COLOR_TYPE_RGBA_8888 => Ok(wgpu::TextureFormat::Rgba8UnormSrgb),
203+
_ => Err(format!("Unsupported CEF format for HAL: {:?}", self.format)),
204+
}
205+
}
206+
207+
fn cef_to_wgpu_format(&self) -> Result<wgpu::TextureFormat, String> {
208+
self.cef_to_hal_format()
209+
}
210+
211+
fn create_fallback_texture(&self, device: &wgpu::Device) -> Result<wgpu::Texture, String> {
212+
let format = self.cef_to_wgpu_format()?;
213+
let texture = device.create_texture(&wgpu::TextureDescriptor {
214+
label: Some("CEF IOSurface Texture (fallback)"),
215+
size: wgpu::Extent3d {
216+
width: self.width,
217+
height: self.height,
218+
depth_or_array_layers: 1,
219+
},
220+
mip_level_count: 1,
221+
sample_count: 1,
222+
dimension: wgpu::TextureDimension::D2,
223+
format,
224+
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
225+
view_formats: &[],
226+
});
227+
228+
tracing::warn!("Using fallback CPU texture - IOSurface hardware acceleration not available");
229+
Ok(texture)
230+
}
231+
}

0 commit comments

Comments
 (0)