From 117dd9e2c1297cedc5496eddfaa26665940d5149 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Thu, 21 Aug 2025 13:54:11 +0000 Subject: [PATCH 01/14] WIP accelerade offscreen canvas implementation --- desktop/Cargo.toml | 3 +- desktop/src/cef.rs | 32 +++++ desktop/src/cef/context.rs | 2 + desktop/src/cef/dmabuf.rs | 59 ++++++++++ desktop/src/cef/internal.rs | 2 +- .../src/cef/internal/browser_process_app.rs | 17 ++- desktop/src/cef/internal/render_handler.rs | 110 ++++++++++++++++++ desktop/src/cef/platform.rs | 67 +++++++++++ 8 files changed, 287 insertions(+), 5 deletions(-) create mode 100644 desktop/src/cef/dmabuf.rs create mode 100644 desktop/src/cef/platform.rs diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml index 8c4e3000b1..a4452b4384 100644 --- a/desktop/Cargo.toml +++ b/desktop/Cargo.toml @@ -9,8 +9,9 @@ edition = "2024" rust-version = "1.87" [features] -default = ["gpu"] +default = ["gpu", "accelerated_paint"] gpu = ["graphite-desktop-wrapper/gpu"] +accelerated_paint = [] [dependencies] # # Local dependencies diff --git a/desktop/src/cef.rs b/desktop/src/cef.rs index d8a4d99576..efa5e1c9b0 100644 --- a/desktop/src/cef.rs +++ b/desktop/src/cef.rs @@ -7,9 +7,11 @@ use std::time::Instant; mod context; mod dirs; +mod dmabuf; mod input; mod internal; mod ipc; +mod platform; mod scheme_handler; mod utility; @@ -19,6 +21,8 @@ use winit::event_loop::EventLoopProxy; pub(crate) trait CefEventHandler: Clone { fn window_size(&self) -> WindowSize; fn draw<'a>(&self, frame_buffer: FrameBufferRef<'a>); + #[cfg(feature = "accelerated_paint")] + fn on_accelerated_paint(&self, shared_handle: internal::render_handler::SharedTextureHandle); /// Scheudule the main event loop to run the cef event loop after the timeout /// [`_cef_browser_process_handler_t::on_schedule_message_pump_work`] for more documentation. fn schedule_cef_message_loop_work(&self, scheduled_time: Instant); @@ -128,4 +132,32 @@ impl CefEventHandler for CefHandler { }; let _ = self.event_loop_proxy.send_event(CustomEvent::DesktopWrapperMessage(desktop_wrapper_message)); } + + #[cfg(feature = "accelerated_paint")] + fn on_accelerated_paint(&self, shared_handle: internal::render_handler::SharedTextureHandle) { + use crate::cef::dmabuf::DmaBufTexture; + + match shared_handle { + #[cfg(target_os = "linux")] + internal::render_handler::SharedTextureHandle::DmaBuf { fds, format, modifier, width, height, strides, offsets } => { + let dmabuf_texture = DmaBufTexture { fds, format, modifier, width, height, strides, offsets }; + match dmabuf_texture.import_to_wgpu(&self.wgpu_context.device) { + Ok(texture) => { + let _ = self.event_loop_proxy.send_event(CustomEvent::UiUpdate(texture)); + } + Err(e) => { + tracing::error!("Failed to import DMA-BUF texture: {}", e); + } + } + } + #[cfg(target_os = "windows")] + internal::render_handler::SharedTextureHandle::D3D11(_handle) => { + tracing::warn!("D3D11 shared texture import not implemented yet"); + } + #[cfg(target_os = "macos")] + internal::render_handler::SharedTextureHandle::IOSurface(_handle) => { + tracing::warn!("IOSurface shared texture import not implemented yet"); + } + } + } } diff --git a/desktop/src/cef/context.rs b/desktop/src/cef/context.rs index 5a0f642c2a..005a17e7e7 100644 --- a/desktop/src/cef/context.rs +++ b/desktop/src/cef/context.rs @@ -89,6 +89,8 @@ impl Context { let window_info = WindowInfo { windowless_rendering_enabled: 1, + #[cfg(feature = "accelerated_paint")] + shared_texture_enabled: if crate::cef::platform::should_enable_hardware_acceleration() { 1 } else { 0 }, ..Default::default() }; diff --git a/desktop/src/cef/dmabuf.rs b/desktop/src/cef/dmabuf.rs new file mode 100644 index 0000000000..163f060060 --- /dev/null +++ b/desktop/src/cef/dmabuf.rs @@ -0,0 +1,59 @@ +use std::os::fd::RawFd; + +#[cfg(target_os = "linux")] +pub struct DmaBufTexture { + pub fds: Vec, + pub format: u32, + pub modifier: u64, + pub width: u32, + pub height: u32, + pub strides: Vec, + pub offsets: Vec, +} + +#[cfg(target_os = "linux")] +impl DmaBufTexture { + pub fn import_to_wgpu(&self, device: &wgpu::Device) -> Result { + // For now, return an error - full DMA-BUF support requires complex Vulkan integration + // This would need to: + // 1. Use wgpu's Vulkan backend to access VkDevice + // 2. Create VkImage from DMA-BUF using VK_EXT_image_drm_format_modifier + // 3. Import the VkImage into wgpu using create_texture_from_hal + + tracing::debug!("DMA-BUF import requested: {}x{} format={:#x} modifier={:#x} planes={}", + self.width, self.height, self.format, self.modifier, self.fds.len()); + + // For now, create a fallback CPU-based texture with the same dimensions + let format = drm_format_to_wgpu(self.format)?; + let texture = device.create_texture(&wgpu::TextureDescriptor { + label: Some("CEF DMA-BUF Texture (fallback)"), + 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, + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + view_formats: &[], + }); + + // Note: This fallback texture is empty - a real implementation would + // need to either import the DMA-BUF directly or copy the contents + tracing::warn!("DMA-BUF import created fallback texture - hardware acceleration not fully implemented"); + + Ok(texture) + } +} + +#[cfg(target_os = "linux")] +fn drm_format_to_wgpu(drm_format: u32) -> Result { + // Based on OBS's drm-format.cpp + match drm_format { + 0x34325241 => Ok(wgpu::TextureFormat::Bgra8UnormSrgb), // DRM_FORMAT_ARGB8888 + 0x34324241 => Ok(wgpu::TextureFormat::Rgba8UnormSrgb), // DRM_FORMAT_ABGR8888 + _ => Err(format!("Unsupported DRM format: {:#x}", drm_format)), + } +} diff --git a/desktop/src/cef/internal.rs b/desktop/src/cef/internal.rs index 03a520e669..1f5030be72 100644 --- a/desktop/src/cef/internal.rs +++ b/desktop/src/cef/internal.rs @@ -2,7 +2,7 @@ mod browser_process_app; mod browser_process_client; mod browser_process_handler; mod browser_process_life_span_handler; -mod render_handler; +pub mod render_handler; mod render_process_app; mod render_process_handler; mod render_process_v8_handler; diff --git a/desktop/src/cef/internal/browser_process_app.rs b/desktop/src/cef/internal/browser_process_app.rs index 82999c5872..ce1a8e366c 100644 --- a/desktop/src/cef/internal/browser_process_app.rs +++ b/desktop/src/cef/internal/browser_process_app.rs @@ -34,9 +34,20 @@ impl ImplApp for BrowserProcessAppImpl { fn on_before_command_line_processing(&self, _process_type: Option<&cef::CefString>, command_line: Option<&mut cef::CommandLine>) { if let Some(cmd) = command_line { - // Disable GPU acceleration, because it is not supported for Offscreen Rendering and can cause crashes. - cmd.append_switch(Some(&CefString::from("disable-gpu"))); - cmd.append_switch(Some(&CefString::from("disable-gpu-compositing"))); + #[cfg(not(feature = "accelerated_paint"))] + { + // Disable GPU acceleration when accelerated_paint feature is not enabled + cmd.append_switch(Some(&CefString::from("disable-gpu"))); + cmd.append_switch(Some(&CefString::from("disable-gpu-compositing"))); + } + + #[cfg(feature = "accelerated_paint")] + { + // Enable GPU acceleration switches for better performance + cmd.append_switch(Some(&CefString::from("enable-gpu-rasterization"))); + cmd.append_switch(Some(&CefString::from("enable-accelerated-2d-canvas"))); + // Don't disable GPU - let CEF use hardware acceleration + } // Tell CEF to use Wayland if available #[cfg(not(any(target_os = "macos", target_os = "windows")))] diff --git a/desktop/src/cef/internal/render_handler.rs b/desktop/src/cef/internal/render_handler.rs index 21d818a091..73b846ab3a 100644 --- a/desktop/src/cef/internal/render_handler.rs +++ b/desktop/src/cef/internal/render_handler.rs @@ -5,15 +5,43 @@ use cef::{Browser, ImplRenderHandler, PaintElementType, Rect, WrapRenderHandler} use crate::cef::CefEventHandler; use crate::render::FrameBufferRef; +#[cfg(target_os = "linux")] +use std::os::fd::RawFd; +#[cfg(all(feature = "accelerated_paint", any(target_os = "windows", target_os = "macos")))] +use std::os::raw::c_void; + pub(crate) struct RenderHandlerImpl { object: *mut RcImpl<_cef_render_handler_t, Self>, event_handler: H, + #[cfg(feature = "accelerated_paint")] + last_shared_handle: Option, +} + +#[cfg(feature = "accelerated_paint")] +pub enum SharedTextureHandle { + #[cfg(target_os = "windows")] + D3D11(*mut c_void), + #[cfg(target_os = "macos")] + IOSurface(*mut c_void), + #[cfg(target_os = "linux")] + DmaBuf { + fds: Vec, + format: u32, + modifier: u64, + width: u32, + height: u32, + strides: Vec, + offsets: Vec, + }, } + impl RenderHandlerImpl { pub(crate) fn new(event_handler: H) -> Self { Self { object: std::ptr::null_mut(), event_handler, + #[cfg(feature = "accelerated_paint")] + last_shared_handle: None, } } } @@ -47,6 +75,44 @@ impl ImplRenderHandler for RenderHandlerImpl { self.event_handler.draw(frame_buffer) } + #[cfg(feature = "accelerated_paint")] + fn on_accelerated_paint(&self, _browser: Option<&mut Browser>, type_: PaintElementType, _dirty_rect_count: usize, _dirty_rects: Option<&Rect>, info: Option<&cef::AcceleratedPaintInfo>) { + if type_ != PaintElementType::default() { + return; + } + let info = info.unwrap(); + + #[cfg(target_os = "linux")] + { + // Extract DMA-BUF information + let shared_handle = SharedTextureHandle::DmaBuf { + fds: extract_fds_from_info(info), + format: (*info.format.as_ref()) as u32, + modifier: info.modifier, + width: info.extra.coded_size.width as u32, + height: info.extra.coded_size.height as u32, + strides: extract_strides_from_info(info), + offsets: extract_offsets_from_info(info), + }; + + self.event_handler.on_accelerated_paint(shared_handle); + } + + #[cfg(target_os = "windows")] + { + // Extract D3D11 shared handle + let shared_handle = SharedTextureHandle::D3D11(info.shared_texture_handle); + self.event_handler.on_accelerated_paint(shared_handle); + } + + #[cfg(target_os = "macos")] + { + // Extract IOSurface handle + let shared_handle = SharedTextureHandle::IOSurface(info.shared_texture_handle); + self.event_handler.on_accelerated_paint(shared_handle); + } + } + fn get_raw(&self) -> *mut _cef_render_handler_t { self.object.cast() } @@ -61,6 +127,8 @@ impl Clone for RenderHandlerImpl { Self { object: self.object, event_handler: self.event_handler.clone(), + #[cfg(feature = "accelerated_paint")] + last_shared_handle: None, } } } @@ -77,3 +145,45 @@ impl WrapRenderHandler for RenderHandlerImpl { self.object = object; } } + +#[cfg(all(feature = "accelerated_paint", target_os = "linux"))] +fn extract_fds_from_info(info: &cef::AcceleratedPaintInfo) -> Vec { + let plane_count = info.plane_count as usize; + let mut fds = Vec::with_capacity(plane_count); + + for i in 0..plane_count { + if let Some(plane) = info.planes.get(i) { + fds.push(plane.fd); + } + } + + fds +} + +#[cfg(all(feature = "accelerated_paint", target_os = "linux"))] +fn extract_strides_from_info(info: &cef::AcceleratedPaintInfo) -> Vec { + let plane_count = info.plane_count as usize; + let mut strides = Vec::with_capacity(plane_count); + + for i in 0..plane_count { + if let Some(plane) = info.planes.get(i) { + strides.push(plane.stride); + } + } + + strides +} + +#[cfg(all(feature = "accelerated_paint", target_os = "linux"))] +fn extract_offsets_from_info(info: &cef::AcceleratedPaintInfo) -> Vec { + let plane_count = info.plane_count as usize; + let mut offsets = Vec::with_capacity(plane_count); + + for i in 0..plane_count { + if let Some(plane) = info.planes.get(i) { + offsets.push(plane.offset as u32); + } + } + + offsets +} diff --git a/desktop/src/cef/platform.rs b/desktop/src/cef/platform.rs new file mode 100644 index 0000000000..f15fdee401 --- /dev/null +++ b/desktop/src/cef/platform.rs @@ -0,0 +1,67 @@ +#[cfg(feature = "accelerated_paint")] +pub fn should_enable_hardware_acceleration() -> bool { + #[cfg(target_os = "linux")] + { + // Check if running on Wayland or X11 + let has_wayland = std::env::var("WAYLAND_DISPLAY") + .ok() + .filter(|var| !var.is_empty()) + .or_else(|| std::env::var("WAYLAND_SOCKET").ok()) + .filter(|var| !var.is_empty()) + .is_some(); + + let has_x11 = std::env::var("DISPLAY") + .ok() + .filter(|var| !var.is_empty()) + .is_some(); + + if !has_wayland && !has_x11 { + tracing::warn!("No display server detected, disabling hardware acceleration"); + return false; + } + + // Check for NVIDIA proprietary driver (known to have issues) + if let Ok(driver_info) = std::fs::read_to_string("/proc/driver/nvidia/version") { + if driver_info.contains("NVIDIA") { + tracing::warn!("NVIDIA proprietary driver detected, hardware acceleration may be unstable"); + // Still return true but with warning + } + } + + // Check for basic GPU capabilities + if has_wayland { + tracing::info!("Wayland detected, enabling hardware acceleration"); + true + } else if has_x11 { + tracing::info!("X11 detected, enabling hardware acceleration"); + true + } else { + false + } + } + + #[cfg(target_os = "windows")] + { + // Windows generally has good D3D11 support + tracing::info!("Windows detected, enabling hardware acceleration"); + true + } + + #[cfg(target_os = "macos")] + { + // macOS has good Metal/IOSurface support + tracing::info!("macOS detected, enabling hardware acceleration"); + true + } + + #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))] + { + tracing::warn!("Unsupported platform for hardware acceleration"); + false + } +} + +#[cfg(not(feature = "accelerated_paint"))] +pub fn should_enable_hardware_acceleration() -> bool { + false +} \ No newline at end of file From 5bf91b91b500175a5f2ec7a5d64c6a2041da7476 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Thu, 21 Aug 2025 13:54:11 +0000 Subject: [PATCH 02/14] Implement vulkan dmabuf import --- Cargo.lock | 2 + desktop/Cargo.toml | 6 +- desktop/src/cef/context.rs | 2 +- desktop/src/cef/dmabuf.rs | 255 +++++++++++++++++++-- desktop/src/cef/internal/render_handler.rs | 22 +- desktop/src/main.rs | 4 +- 6 files changed, 256 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9eab3f3b59..6057e7010e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2077,6 +2077,7 @@ dependencies = [ name = "graphite-desktop" version = "0.1.0" dependencies = [ + "ash", "bytemuck", "cef", "derivative", @@ -2085,6 +2086,7 @@ dependencies = [ "glam", "graphite-desktop-wrapper", "include_dir", + "libc", "open", "rfd", "ron", diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml index a4452b4384..4d3815788e 100644 --- a/desktop/Cargo.toml +++ b/desktop/Cargo.toml @@ -11,7 +11,7 @@ rust-version = "1.87" [features] default = ["gpu", "accelerated_paint"] gpu = ["graphite-desktop-wrapper/gpu"] -accelerated_paint = [] +accelerated_paint = ["ash", "libc"] [dependencies] # # Local dependencies @@ -33,3 +33,7 @@ vello = { workspace = true } derivative = { workspace = true } rfd = { workspace = true } open = { workspace = true } + +# Hardware acceleration dependencies +ash = { version = "0.38", optional = true } +libc = { version = "0.2", optional = true } diff --git a/desktop/src/cef/context.rs b/desktop/src/cef/context.rs index 005a17e7e7..cc44d8d2cb 100644 --- a/desktop/src/cef/context.rs +++ b/desktop/src/cef/context.rs @@ -95,7 +95,7 @@ impl Context { }; let settings = BrowserSettings { - windowless_frame_rate: 60, + windowless_frame_rate: 120, background_color: 0x0, ..Default::default() }; diff --git a/desktop/src/cef/dmabuf.rs b/desktop/src/cef/dmabuf.rs index 163f060060..d4dda49edf 100644 --- a/desktop/src/cef/dmabuf.rs +++ b/desktop/src/cef/dmabuf.rs @@ -1,9 +1,16 @@ use std::os::fd::RawFd; +#[cfg(all(feature = "accelerated_paint", target_os = "linux"))] +use ash::vk; +#[cfg(all(feature = "accelerated_paint", target_os = "linux"))] +use wgpu::hal::api; +#[cfg(all(feature = "accelerated_paint", target_os = "linux"))] +extern crate libc; + #[cfg(target_os = "linux")] pub struct DmaBufTexture { pub fds: Vec, - pub format: u32, + pub format: cef::sys::cef_color_type_t, pub modifier: u64, pub width: u32, pub height: u32, @@ -14,16 +21,30 @@ pub struct DmaBufTexture { #[cfg(target_os = "linux")] impl DmaBufTexture { pub fn import_to_wgpu(&self, device: &wgpu::Device) -> Result { - // For now, return an error - full DMA-BUF support requires complex Vulkan integration - // This would need to: - // 1. Use wgpu's Vulkan backend to access VkDevice - // 2. Create VkImage from DMA-BUF using VK_EXT_image_drm_format_modifier - // 3. Import the VkImage into wgpu using create_texture_from_hal - - tracing::debug!("DMA-BUF import requested: {}x{} format={:#x} modifier={:#x} planes={}", - self.width, self.height, self.format, self.modifier, self.fds.len()); - - // For now, create a fallback CPU-based texture with the same dimensions + tracing::debug!( + "DMA-BUF import requested: {}x{} format={:?} modifier={:#x} planes={}", + self.width, + self.height, + self.format, + self.modifier, + self.fds.len() + ); + + // Try to import via Vulkan, fallback to CPU texture on failure + #[cfg(feature = "accelerated_paint")] + { + match self.import_via_vulkan(device) { + Ok(texture) => { + tracing::info!("Successfully imported DMA-BUF texture via Vulkan"); + return Ok(texture); + } + Err(e) => { + tracing::warn!("Failed to import DMA-BUF via Vulkan: {}, falling back to CPU texture", e); + } + } + } + + // Fallback: create empty CPU texture with same dimensions let format = drm_format_to_wgpu(self.format)?; let texture = device.create_texture(&wgpu::TextureDescriptor { label: Some("CEF DMA-BUF Texture (fallback)"), @@ -39,21 +60,219 @@ impl DmaBufTexture { usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, view_formats: &[], }); + + tracing::warn!("Using fallback CPU texture - DMA-BUF hardware acceleration not available"); + Ok(texture) + } + + #[cfg(feature = "accelerated_paint")] + fn import_via_vulkan(&self, device: &wgpu::Device) -> Result { + // Validate file descriptors before proceeding + if self.fds.is_empty() { + return Err("No DMA-BUF file descriptors provided".to_string()); + } - // Note: This fallback texture is empty - a real implementation would - // need to either import the DMA-BUF directly or copy the contents - tracing::warn!("DMA-BUF import created fallback texture - hardware acceleration not fully implemented"); + for &fd in &self.fds { + if fd < 0 { + return Err(format!("Invalid file descriptor: {}", fd)); + } + // Check if file descriptor is valid by testing with fcntl + let flags = unsafe { libc::fcntl(fd, libc::F_GETFD) }; + if flags == -1 { + return Err(format!("File descriptor {} is not valid", fd)); + } + } + // Get wgpu's Vulkan instance and device + use wgpu::{TextureUses, wgc::api::Vulkan}; + let hal_texture = unsafe { + device.as_hal::(|device| { + let Some(device) = device else { + return Err("Device is not using Vulkan backend".to_string()); + }; + + // Create VkImage from DMA-BUF using external memory + let vk_image = self.create_vulkan_image_from_dmabuf(device).map_err(|e| format!("Failed to create Vulkan image from DMA-BUF: {}", e))?; + + // Wrap VkImage in wgpu-hal texture + let hal_texture = ::Device::texture_from_raw( + vk_image, + &wgpu::hal::TextureDescriptor { + label: Some("CEF DMA-BUF 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: cef_to_hal_format(self.format)?, + usage: TextureUses::COPY_DST | TextureUses::RESOURCE, + memory_flags: wgpu::hal::MemoryFlags::empty(), + view_formats: vec![], + }, + None, // drop_callback + ); + + Ok(hal_texture) + }) + }?; + + // Import hal texture into wgpu + let texture = unsafe { + device.create_texture_from_hal::( + hal_texture, + &wgpu::TextureDescriptor { + label: Some("CEF DMA-BUF 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: drm_format_to_wgpu(self.format)?, + usage: wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: &[], + }, + ) + }; + Ok(texture) } + + #[cfg(feature = "accelerated_paint")] + fn create_vulkan_image_from_dmabuf(&self, hal_device: &::Device) -> Result { + // Get raw Vulkan handles + let device = hal_device.raw_device(); + let _instance = hal_device.shared_instance().raw_instance(); + + // Validate dimensions + if self.width == 0 || self.height == 0 { + return Err("Invalid DMA-BUF dimensions".to_string()); + } + + // Create external memory image + let image_create_info = vk::ImageCreateInfo::default() + .image_type(vk::ImageType::TYPE_2D) + .format(cef_to_vk_format(self.format)?) + .extent(vk::Extent3D { + width: self.width, + height: self.height, + depth: 1, + }) + .mip_levels(1) + .array_layers(1) + .samples(vk::SampleCountFlags::TYPE_1) + .tiling(vk::ImageTiling::DRM_FORMAT_MODIFIER_EXT) + .usage(vk::ImageUsageFlags::SAMPLED | vk::ImageUsageFlags::COLOR_ATTACHMENT) + .sharing_mode(vk::SharingMode::EXCLUSIVE); + + // Set up DRM format modifier + let plane_layouts = self.create_subresource_layouts()?; + let mut drm_format_modifier = vk::ImageDrmFormatModifierExplicitCreateInfoEXT::default() + .drm_format_modifier(self.modifier) + .plane_layouts(&plane_layouts); + + let image_create_info = image_create_info.push_next(&mut drm_format_modifier); + + // Create the image + let image = unsafe { device.create_image(&image_create_info, None).map_err(|e| format!("Failed to create Vulkan image: {:?}", e))? }; + + // Import memory from DMA-BUF + let memory_requirements = unsafe { device.get_image_memory_requirements(image) }; + + // Duplicate the file descriptor to avoid ownership issues + // CEF owns the original FDs and will close them when the AcceleratedPaintInfo is destroyed + let dup_fd = unsafe { libc::dup(self.fds[0]) }; + if dup_fd == -1 { + return Err("Failed to duplicate DMA-BUF file descriptor".to_string()); + } + + let mut import_memory_fd = vk::ImportMemoryFdInfoKHR::default().handle_type(vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT).fd(dup_fd); + + // Find a suitable memory type + let memory_properties = unsafe { hal_device.shared_instance().raw_instance().get_physical_device_memory_properties(hal_device.raw_physical_device()) }; + let memory_type_index = find_memory_type_index(memory_requirements.memory_type_bits, vk::MemoryPropertyFlags::empty(), &memory_properties) + .ok_or("Failed to find suitable memory type for DMA-BUF")?; + + let allocate_info = vk::MemoryAllocateInfo::default() + .allocation_size(memory_requirements.size) + .memory_type_index(memory_type_index) + .push_next(&mut import_memory_fd); + + let device_memory = unsafe { device.allocate_memory(&allocate_info, None).map_err(|e| format!("Failed to allocate memory for DMA-BUF: {:?}", e))? }; + + // Bind memory to image + unsafe { + device.bind_image_memory(image, device_memory, 0).map_err(|e| format!("Failed to bind memory to image: {:?}", e))?; + } + + Ok(image) + } + + #[cfg(feature = "accelerated_paint")] + fn create_subresource_layouts(&self) -> Result, String> { + let mut layouts = Vec::new(); + + for i in 0..self.fds.len() { + layouts.push(vk::SubresourceLayout { + offset: self.offsets.get(i).copied().unwrap_or(0) as u64, + size: 0, // Will be calculated by driver + row_pitch: self.strides.get(i).copied().unwrap_or(0) as u64, + array_pitch: 0, + depth_pitch: 0, + }); + } + + Ok(layouts) + } } #[cfg(target_os = "linux")] -fn drm_format_to_wgpu(drm_format: u32) -> Result { +fn drm_format_to_wgpu(drm_format: cef::sys::cef_color_type_t) -> Result { // Based on OBS's drm-format.cpp + + use cef::sys::cef_color_type_t; match drm_format { - 0x34325241 => Ok(wgpu::TextureFormat::Bgra8UnormSrgb), // DRM_FORMAT_ARGB8888 - 0x34324241 => Ok(wgpu::TextureFormat::Rgba8UnormSrgb), // DRM_FORMAT_ABGR8888 - _ => Err(format!("Unsupported DRM format: {:#x}", drm_format)), + cef_color_type_t::CEF_COLOR_TYPE_BGRA_8888 => Ok(wgpu::TextureFormat::Bgra8UnormSrgb), // DRM_FORMAT_ARGB8888 + cef_color_type_t::CEF_COLOR_TYPE_RGBA_8888 => Ok(wgpu::TextureFormat::Rgba8UnormSrgb), // DRM_FORMAT_ABGR8888 + _ => Err(format!("Unsupported DRM format: {:?}", drm_format)), + } +} + +#[cfg(all(feature = "accelerated_paint", target_os = "linux"))] +fn cef_to_vk_format(cef_format: cef::sys::cef_color_type_t) -> Result { + use cef::sys::cef_color_type_t; + match cef_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), + _ => Err(format!("Unsupported CEF format for Vulkan: {:?}", cef_format)), + } +} + +#[cfg(all(feature = "accelerated_paint", target_os = "linux"))] +fn cef_to_hal_format(cef_format: cef::sys::cef_color_type_t) -> Result { + use cef::sys::cef_color_type_t; + match cef_format { + cef_color_type_t::CEF_COLOR_TYPE_BGRA_8888 => Ok(wgpu::TextureFormat::Bgra8UnormSrgb), + cef_color_type_t::CEF_COLOR_TYPE_RGBA_8888 => Ok(wgpu::TextureFormat::Rgba8UnormSrgb), + _ => Err(format!("Unsupported CEF format for HAL: {:?}", cef_format)), + } +} + +#[cfg(all(feature = "accelerated_paint", target_os = "linux"))] +fn find_memory_type_index( + type_filter: u32, + properties: vk::MemoryPropertyFlags, + mem_properties: &vk::PhysicalDeviceMemoryProperties, +) -> Option { + for i in 0..mem_properties.memory_type_count { + if (type_filter & (1 << i)) != 0 && mem_properties.memory_types[i as usize].property_flags.contains(properties) { + return Some(i); + } } + None } diff --git a/desktop/src/cef/internal/render_handler.rs b/desktop/src/cef/internal/render_handler.rs index 73b846ab3a..5cc463648d 100644 --- a/desktop/src/cef/internal/render_handler.rs +++ b/desktop/src/cef/internal/render_handler.rs @@ -13,8 +13,6 @@ use std::os::raw::c_void; pub(crate) struct RenderHandlerImpl { object: *mut RcImpl<_cef_render_handler_t, Self>, event_handler: H, - #[cfg(feature = "accelerated_paint")] - last_shared_handle: Option, } #[cfg(feature = "accelerated_paint")] @@ -26,7 +24,7 @@ pub enum SharedTextureHandle { #[cfg(target_os = "linux")] DmaBuf { fds: Vec, - format: u32, + format: cef::sys::cef_color_type_t, modifier: u64, width: u32, height: u32, @@ -40,8 +38,6 @@ impl RenderHandlerImpl { Self { object: std::ptr::null_mut(), event_handler, - #[cfg(feature = "accelerated_paint")] - last_shared_handle: None, } } } @@ -87,7 +83,7 @@ impl ImplRenderHandler for RenderHandlerImpl { // Extract DMA-BUF information let shared_handle = SharedTextureHandle::DmaBuf { fds: extract_fds_from_info(info), - format: (*info.format.as_ref()) as u32, + format: *info.format.as_ref(), modifier: info.modifier, width: info.extra.coded_size.width as u32, height: info.extra.coded_size.height as u32, @@ -127,8 +123,6 @@ impl Clone for RenderHandlerImpl { Self { object: self.object, event_handler: self.event_handler.clone(), - #[cfg(feature = "accelerated_paint")] - last_shared_handle: None, } } } @@ -150,13 +144,13 @@ impl WrapRenderHandler for RenderHandlerImpl { fn extract_fds_from_info(info: &cef::AcceleratedPaintInfo) -> Vec { let plane_count = info.plane_count as usize; let mut fds = Vec::with_capacity(plane_count); - + for i in 0..plane_count { if let Some(plane) = info.planes.get(i) { fds.push(plane.fd); } } - + fds } @@ -164,13 +158,13 @@ fn extract_fds_from_info(info: &cef::AcceleratedPaintInfo) -> Vec { fn extract_strides_from_info(info: &cef::AcceleratedPaintInfo) -> Vec { let plane_count = info.plane_count as usize; let mut strides = Vec::with_capacity(plane_count); - + for i in 0..plane_count { if let Some(plane) = info.planes.get(i) { strides.push(plane.stride); } } - + strides } @@ -178,12 +172,12 @@ fn extract_strides_from_info(info: &cef::AcceleratedPaintInfo) -> Vec { fn extract_offsets_from_info(info: &cef::AcceleratedPaintInfo) -> Vec { let plane_count = info.plane_count as usize; let mut offsets = Vec::with_capacity(plane_count); - + for i in 0..plane_count { if let Some(plane) = info.planes.get(i) { offsets.push(plane.offset as u32); } } - + offsets } diff --git a/desktop/src/main.rs b/desktop/src/main.rs index afcff648e3..7d61dd7e2e 100644 --- a/desktop/src/main.rs +++ b/desktop/src/main.rs @@ -57,7 +57,7 @@ fn main() { tracing::info!("Cef initialized successfully"); let rendering_loop_proxy = event_loop.create_proxy(); - let target_fps = 60; + let target_fps = 120; std::thread::spawn(move || { loop { let last_render = Instant::now(); @@ -67,6 +67,8 @@ fn main() { let frame_time = Duration::from_secs_f32((target_fps as f32).recip()); let sleep = last_render + frame_time - Instant::now(); + + let _ = rendering_loop_proxy.send_event(CustomEvent::ScheduleBrowserWork(Instant::now())); std::thread::sleep(sleep); } }); From c7f6ace0b3592948684e93d6f553430c056c5d53 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Thu, 21 Aug 2025 13:54:11 +0000 Subject: [PATCH 03/14] Add fps printing --- desktop/src/app.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/desktop/src/app.rs b/desktop/src/app.rs index ad38bff85b..072748890a 100644 --- a/desktop/src/app.rs +++ b/desktop/src/app.rs @@ -31,6 +31,9 @@ pub(crate) struct WinitApp { wgpu_context: WgpuContext, event_loop_proxy: EventLoopProxy, desktop_wrapper: DesktopWrapper, + editor: Editor, + last_ui_update: Instant, + avg_frame_time: f32, } impl WinitApp { @@ -45,6 +48,8 @@ impl WinitApp { wgpu_context, event_loop_proxy, desktop_wrapper, + last_ui_update: Instant::now(), + avg_frame_time: 0., } } @@ -220,6 +225,10 @@ impl ApplicationHandler for WinitApp { if let Some(graphics_state) = self.graphics_state.as_mut() { graphics_state.resize(texture.width(), texture.height()); graphics_state.bind_ui_texture(texture); + let elapsed = self.last_ui_update.elapsed().as_secs_f32(); + self.last_ui_update = Instant::now(); + self.avg_frame_time = (self.avg_frame_time * 3. + elapsed) / 4.; + println!("ui fps: {:.2}", 1. / self.avg_frame_time); } if let Some(window) = &self.window { window.request_redraw(); From d07378569d8654c384626917ecb811432375f869 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Thu, 21 Aug 2025 13:54:11 +0000 Subject: [PATCH 04/14] Add feature gates --- desktop/src/cef.rs | 23 ++++++++++++++++++++--- desktop/src/cef/context.rs | 1 + 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/desktop/src/cef.rs b/desktop/src/cef.rs index efa5e1c9b0..09a39ced73 100644 --- a/desktop/src/cef.rs +++ b/desktop/src/cef.rs @@ -135,12 +135,29 @@ impl CefEventHandler for CefHandler { #[cfg(feature = "accelerated_paint")] fn on_accelerated_paint(&self, shared_handle: internal::render_handler::SharedTextureHandle) { + #[cfg(target_os = "linux")] use crate::cef::dmabuf::DmaBufTexture; - + match shared_handle { #[cfg(target_os = "linux")] - internal::render_handler::SharedTextureHandle::DmaBuf { fds, format, modifier, width, height, strides, offsets } => { - let dmabuf_texture = DmaBufTexture { fds, format, modifier, width, height, strides, offsets }; + internal::render_handler::SharedTextureHandle::DmaBuf { + fds, + format, + modifier, + width, + height, + strides, + offsets, + } => { + let dmabuf_texture = DmaBufTexture { + fds, + format, + modifier, + width, + height, + strides, + offsets, + }; match dmabuf_texture.import_to_wgpu(&self.wgpu_context.device) { Ok(texture) => { let _ = self.event_loop_proxy.send_event(CustomEvent::UiUpdate(texture)); diff --git a/desktop/src/cef/context.rs b/desktop/src/cef/context.rs index cc44d8d2cb..0e3253a409 100644 --- a/desktop/src/cef/context.rs +++ b/desktop/src/cef/context.rs @@ -86,6 +86,7 @@ impl Context { let mut client = Client::new(BrowserProcessClientImpl::new(render_handler, event_handler.clone())); let url = CefString::from(format!("{GRAPHITE_SCHEME}://{FRONTEND_DOMAIN}/").as_str()); + let url = CefString::from(format!("chrome://gpu").as_str()); let window_info = WindowInfo { windowless_rendering_enabled: 1, From fd3d797e4505c962755bc74d51800d7dab8f34ab Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Thu, 21 Aug 2025 13:54:11 +0000 Subject: [PATCH 05/14] Forgot to add file --- desktop/src/cef/dmabuf.rs | 53 ++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/desktop/src/cef/dmabuf.rs b/desktop/src/cef/dmabuf.rs index d4dda49edf..9b40413d1e 100644 --- a/desktop/src/cef/dmabuf.rs +++ b/desktop/src/cef/dmabuf.rs @@ -1,3 +1,4 @@ +#[cfg(target_os = "linux")] use std::os::fd::RawFd; #[cfg(all(feature = "accelerated_paint", target_os = "linux"))] @@ -71,7 +72,7 @@ impl DmaBufTexture { if self.fds.is_empty() { return Err("No DMA-BUF file descriptors provided".to_string()); } - + for &fd in &self.fds { if fd < 0 { return Err(format!("Invalid file descriptor: {}", fd)); @@ -82,7 +83,7 @@ impl DmaBufTexture { return Err(format!("File descriptor {} is not valid", fd)); } } - + // Get wgpu's Vulkan instance and device use wgpu::{TextureUses, wgc::api::Vulkan}; let hal_texture = unsafe { @@ -96,24 +97,24 @@ impl DmaBufTexture { // Wrap VkImage in wgpu-hal texture let hal_texture = ::Device::texture_from_raw( - vk_image, - &wgpu::hal::TextureDescriptor { - label: Some("CEF DMA-BUF 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: cef_to_hal_format(self.format)?, - usage: TextureUses::COPY_DST | TextureUses::RESOURCE, - memory_flags: wgpu::hal::MemoryFlags::empty(), - view_formats: vec![], + vk_image, + &wgpu::hal::TextureDescriptor { + label: Some("CEF DMA-BUF Texture"), + size: wgpu::Extent3d { + width: self.width, + height: self.height, + depth_or_array_layers: 1, }, - None, // drop_callback - ); + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: cef_to_hal_format(self.format)?, + usage: TextureUses::COPY_DST | TextureUses::RESOURCE, + memory_flags: wgpu::hal::MemoryFlags::empty(), + view_formats: vec![], + }, + None, // drop_callback + ); Ok(hal_texture) }) @@ -153,7 +154,7 @@ impl DmaBufTexture { if self.width == 0 || self.height == 0 { return Err("Invalid DMA-BUF dimensions".to_string()); } - + // Create external memory image let image_create_info = vk::ImageCreateInfo::default() .image_type(vk::ImageType::TYPE_2D) @@ -190,13 +191,13 @@ impl DmaBufTexture { if dup_fd == -1 { return Err("Failed to duplicate DMA-BUF file descriptor".to_string()); } - + let mut import_memory_fd = vk::ImportMemoryFdInfoKHR::default().handle_type(vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT).fd(dup_fd); // Find a suitable memory type let memory_properties = unsafe { hal_device.shared_instance().raw_instance().get_physical_device_memory_properties(hal_device.raw_physical_device()) }; - let memory_type_index = find_memory_type_index(memory_requirements.memory_type_bits, vk::MemoryPropertyFlags::empty(), &memory_properties) - .ok_or("Failed to find suitable memory type for DMA-BUF")?; + let memory_type_index = + find_memory_type_index(memory_requirements.memory_type_bits, vk::MemoryPropertyFlags::empty(), &memory_properties).ok_or("Failed to find suitable memory type for DMA-BUF")?; let allocate_info = vk::MemoryAllocateInfo::default() .allocation_size(memory_requirements.size) @@ -264,11 +265,7 @@ fn cef_to_hal_format(cef_format: cef::sys::cef_color_type_t) -> Result Option { +fn find_memory_type_index(type_filter: u32, properties: vk::MemoryPropertyFlags, mem_properties: &vk::PhysicalDeviceMemoryProperties) -> Option { for i in 0..mem_properties.memory_type_count { if (type_filter & (1 << i)) != 0 && mem_properties.memory_types[i as usize].property_flags.contains(properties) { return Some(i); From c217ec06fb5722d3b18148174adc041dde0f7cc1 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Thu, 21 Aug 2025 13:54:11 +0000 Subject: [PATCH 06/14] Experimental windows support --- Cargo.lock | 1 + desktop/Cargo.toml | 11 +- desktop/src/cef.rs | 22 +- desktop/src/cef/d3d11.rs | 248 +++++++++++++++++++++ desktop/src/cef/internal/render_handler.rs | 16 +- 5 files changed, 292 insertions(+), 6 deletions(-) create mode 100644 desktop/src/cef/d3d11.rs diff --git a/Cargo.lock b/Cargo.lock index 6057e7010e..2ef5f412c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2095,6 +2095,7 @@ dependencies = [ "tracing-subscriber", "vello", "wgpu", + "windows", "winit", ] diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml index 4d3815788e..185d5682b5 100644 --- a/desktop/Cargo.toml +++ b/desktop/Cargo.toml @@ -11,7 +11,7 @@ rust-version = "1.87" [features] default = ["gpu", "accelerated_paint"] gpu = ["graphite-desktop-wrapper/gpu"] -accelerated_paint = ["ash", "libc"] +accelerated_paint = ["ash", "libc", "windows"] [dependencies] # # Local dependencies @@ -37,3 +37,12 @@ open = { workspace = true } # Hardware acceleration dependencies ash = { version = "0.38", optional = true } libc = { version = "0.2", optional = true } + +# Windows-specific dependencies +[target.'cfg(windows)'.dependencies] +windows = { version = "0.58", features = [ + "Win32_Graphics_Direct3D11", + "Win32_Graphics_Dxgi", + "Win32_Graphics_Dxgi_Common", + "Win32_Foundation" +], optional = true } diff --git a/desktop/src/cef.rs b/desktop/src/cef.rs index 09a39ced73..fd7db31663 100644 --- a/desktop/src/cef.rs +++ b/desktop/src/cef.rs @@ -8,6 +8,8 @@ use std::time::Instant; mod context; mod dirs; mod dmabuf; +#[cfg(target_os = "windows")] +mod d3d11; mod input; mod internal; mod ipc; @@ -18,6 +20,9 @@ mod utility; pub(crate) use context::{Context, InitError, Initialized, Setup, SetupError}; use winit::event_loop::EventLoopProxy; +#[cfg(target_os = "windows")] +use crate::cef::d3d11::D3D11SharedTexture; + pub(crate) trait CefEventHandler: Clone { fn window_size(&self) -> WindowSize; fn draw<'a>(&self, frame_buffer: FrameBufferRef<'a>); @@ -168,8 +173,21 @@ impl CefEventHandler for CefHandler { } } #[cfg(target_os = "windows")] - internal::render_handler::SharedTextureHandle::D3D11(_handle) => { - tracing::warn!("D3D11 shared texture import not implemented yet"); + internal::render_handler::SharedTextureHandle::D3D11 { handle, format, width, height } => { + let d3d11_texture = D3D11SharedTexture { + handle, + width, + height, + format, + }; + match d3d11_texture.import_to_wgpu(&self.wgpu_context.device) { + Ok(texture) => { + let _ = self.event_loop_proxy.send_event(CustomEvent::UiUpdate(texture)); + } + Err(e) => { + tracing::error!("Failed to import D3D11 shared texture: {}", e); + } + } } #[cfg(target_os = "macos")] internal::render_handler::SharedTextureHandle::IOSurface(_handle) => { diff --git a/desktop/src/cef/d3d11.rs b/desktop/src/cef/d3d11.rs new file mode 100644 index 0000000000..8310f5404a --- /dev/null +++ b/desktop/src/cef/d3d11.rs @@ -0,0 +1,248 @@ +#[cfg(target_os = "windows")] +use std::os::raw::c_void; + +#[cfg(all(feature = "accelerated_paint", target_os = "windows"))] +use ash::vk; +#[cfg(all(feature = "accelerated_paint", target_os = "windows"))] +use wgpu::hal::api; +#[cfg(all(feature = "accelerated_paint", target_os = "windows"))] +use windows::Win32::Graphics::{Direct3D11::*, Dxgi::*}; + +#[cfg(target_os = "windows")] +pub struct D3D11SharedTexture { + pub handle: *mut c_void, + pub width: u32, + pub height: u32, + pub format: cef::sys::cef_color_type_t, +} + +#[cfg(target_os = "windows")] +impl D3D11SharedTexture { + pub fn import_to_wgpu(&self, device: &wgpu::Device) -> Result { + tracing::debug!( + "D3D11 shared texture import requested: {}x{} handle={:p}", + self.width, + self.height, + self.handle + ); + + // Try to import via Vulkan, fallback to CPU texture on failure + #[cfg(feature = "accelerated_paint")] + { + match self.import_via_vulkan(device) { + Ok(texture) => { + tracing::info!("Successfully imported D3D11 shared texture via Vulkan"); + return Ok(texture); + } + Err(e) => { + tracing::warn!("Failed to import D3D11 via Vulkan: {}, falling back to CPU texture", e); + } + } + } + + // Fallback: create empty CPU texture with same dimensions + self.create_fallback_texture(device) + } + + #[cfg(feature = "accelerated_paint")] + fn import_via_vulkan(&self, device: &wgpu::Device) -> Result { + // Validate handle + if self.handle.is_null() { + return Err("D3D11 shared texture handle is null".to_string()); + } + + // Get wgpu's Vulkan instance and device + use wgpu::{TextureUses, wgc::api::Vulkan}; + let hal_texture = unsafe { + device.as_hal::(|device| { + let Some(device) = device else { + return Err("Device is not using Vulkan backend".to_string()); + }; + + // Import D3D11 shared handle into Vulkan + let vk_image = self.import_d3d11_handle_to_vulkan(device).map_err(|e| format!("Failed to create Vulkan image from D3D11 handle: {}", e))?; + + // Wrap VkImage in wgpu-hal texture + let hal_texture = ::Device::texture_from_raw( + vk_image, + &wgpu::hal::TextureDescriptor { + label: Some("CEF D3D11 Shared 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: self.cef_to_hal_format()?, + usage: TextureUses::COPY_DST | TextureUses::RESOURCE, + memory_flags: wgpu::hal::MemoryFlags::empty(), + view_formats: vec![], + }, + None, // drop_callback + ); + + Ok(hal_texture) + }) + }?; + + // Import hal texture into wgpu + let texture = unsafe { + device.create_texture_from_hal::( + hal_texture, + &wgpu::TextureDescriptor { + label: Some("CEF D3D11 Shared 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: self.cef_to_wgpu_format()?, + usage: wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: &[], + }, + ) + }; + + Ok(texture) + } + + #[cfg(feature = "accelerated_paint")] + fn import_d3d11_handle_to_vulkan(&self, hal_device: &::Device) -> Result { + // Get raw Vulkan handles + let device = hal_device.raw_device(); + let _instance = hal_device.shared_instance().raw_instance(); + + // Validate dimensions + if self.width == 0 || self.height == 0 { + return Err("Invalid D3D11 texture dimensions".to_string()); + } + + // Create external memory image info + let mut external_memory_info = vk::ExternalMemoryImageCreateInfo::default() + .handle_types(vk::ExternalMemoryHandleTypeFlags::D3D11_TEXTURE); + + // Create image create info + let image_create_info = vk::ImageCreateInfo::default() + .image_type(vk::ImageType::TYPE_2D) + .format(self.cef_to_vk_format()?) + .extent(vk::Extent3D { + width: self.width, + height: self.height, + depth: 1, + }) + .mip_levels(1) + .array_layers(1) + .samples(vk::SampleCountFlags::TYPE_1) + .tiling(vk::ImageTiling::OPTIMAL) + .usage(vk::ImageUsageFlags::SAMPLED | vk::ImageUsageFlags::COLOR_ATTACHMENT) + .sharing_mode(vk::SharingMode::EXCLUSIVE) + .push_next(&mut external_memory_info); + + // Create the image + let image = unsafe { + device.create_image(&image_create_info, None) + .map_err(|e| format!("Failed to create Vulkan image: {:?}", e))? + }; + + // Get memory requirements + let memory_requirements = unsafe { device.get_image_memory_requirements(image) }; + + // Import D3D11 handle + let mut import_memory_win32 = vk::ImportMemoryWin32HandleInfoKHR::default() + .handle_type(vk::ExternalMemoryHandleTypeFlags::D3D11_TEXTURE) + .handle(self.handle); + + // Find a suitable memory type + let memory_properties = unsafe { + hal_device.shared_instance().raw_instance().get_physical_device_memory_properties(hal_device.raw_physical_device()) + }; + + let memory_type_index = find_memory_type_index( + memory_requirements.memory_type_bits, + vk::MemoryPropertyFlags::empty(), + &memory_properties, + ).ok_or("Failed to find suitable memory type for D3D11 texture")?; + + let allocate_info = vk::MemoryAllocateInfo::default() + .allocation_size(memory_requirements.size) + .memory_type_index(memory_type_index) + .push_next(&mut import_memory_win32); + + let device_memory = unsafe { + device.allocate_memory(&allocate_info, None) + .map_err(|e| format!("Failed to allocate memory for D3D11 texture: {:?}", e))? + }; + + // Bind memory to image + unsafe { + device.bind_image_memory(image, device_memory, 0) + .map_err(|e| format!("Failed to bind memory to image: {:?}", e))?; + } + + Ok(image) + } + + fn cef_to_vk_format(&self) -> Result { + use cef::sys::cef_color_type_t; + match self.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), + _ => Err(format!("Unsupported CEF format for Vulkan: {:?}", self.format)), + } + } + + fn cef_to_hal_format(&self) -> Result { + use cef::sys::cef_color_type_t; + match self.format { + cef_color_type_t::CEF_COLOR_TYPE_BGRA_8888 => Ok(wgpu::TextureFormat::Bgra8UnormSrgb), + cef_color_type_t::CEF_COLOR_TYPE_RGBA_8888 => Ok(wgpu::TextureFormat::Rgba8UnormSrgb), + _ => Err(format!("Unsupported CEF format for HAL: {:?}", self.format)), + } + } + + fn cef_to_wgpu_format(&self) -> Result { + self.cef_to_hal_format() + } + + fn create_fallback_texture(&self, device: &wgpu::Device) -> Result { + let format = self.cef_to_wgpu_format()?; + let texture = device.create_texture(&wgpu::TextureDescriptor { + label: Some("CEF D3D11 Texture (fallback)"), + 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, + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + view_formats: &[], + }); + + tracing::warn!("Using fallback CPU texture - D3D11 hardware acceleration not available"); + Ok(texture) + } +} + +#[cfg(all(feature = "accelerated_paint", target_os = "windows"))] +fn find_memory_type_index( + type_filter: u32, + properties: vk::MemoryPropertyFlags, + mem_properties: &vk::PhysicalDeviceMemoryProperties, +) -> Option { + for i in 0..mem_properties.memory_type_count { + if (type_filter & (1 << i)) != 0 + && mem_properties.memory_types[i as usize].property_flags.contains(properties) + { + return Some(i); + } + } + None +} \ No newline at end of file diff --git a/desktop/src/cef/internal/render_handler.rs b/desktop/src/cef/internal/render_handler.rs index 5cc463648d..a184c1a0d4 100644 --- a/desktop/src/cef/internal/render_handler.rs +++ b/desktop/src/cef/internal/render_handler.rs @@ -18,7 +18,12 @@ pub(crate) struct RenderHandlerImpl { #[cfg(feature = "accelerated_paint")] pub enum SharedTextureHandle { #[cfg(target_os = "windows")] - D3D11(*mut c_void), + D3D11 { + handle: *mut c_void, + format: cef::sys::cef_color_type_t, + width: u32, + height: u32, + }, #[cfg(target_os = "macos")] IOSurface(*mut c_void), #[cfg(target_os = "linux")] @@ -96,8 +101,13 @@ impl ImplRenderHandler for RenderHandlerImpl { #[cfg(target_os = "windows")] { - // Extract D3D11 shared handle - let shared_handle = SharedTextureHandle::D3D11(info.shared_texture_handle); + // Extract D3D11 shared handle with texture metadata + let shared_handle = SharedTextureHandle::D3D11 { + handle: info.shared_texture_handle, + format: *info.format.as_ref(), + width: info.extra.coded_size.width as u32, + height: info.extra.coded_size.height as u32, + }; self.event_handler.on_accelerated_paint(shared_handle); } From ea5c1a62a06ca637ac36e64ac054c7f3107ed253 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Thu, 21 Aug 2025 13:54:11 +0000 Subject: [PATCH 07/14] Cast ptr to isize --- desktop/src/cef/d3d11.rs | 50 +++++++++++++--------------------------- 1 file changed, 16 insertions(+), 34 deletions(-) diff --git a/desktop/src/cef/d3d11.rs b/desktop/src/cef/d3d11.rs index 8310f5404a..a16353005c 100644 --- a/desktop/src/cef/d3d11.rs +++ b/desktop/src/cef/d3d11.rs @@ -19,12 +19,7 @@ pub struct D3D11SharedTexture { #[cfg(target_os = "windows")] impl D3D11SharedTexture { pub fn import_to_wgpu(&self, device: &wgpu::Device) -> Result { - tracing::debug!( - "D3D11 shared texture import requested: {}x{} handle={:p}", - self.width, - self.height, - self.handle - ); + tracing::debug!("D3D11 shared texture import requested: {}x{} handle={:p}", self.width, self.height, self.handle); // Try to import via Vulkan, fallback to CPU texture on failure #[cfg(feature = "accelerated_paint")] @@ -60,7 +55,9 @@ impl D3D11SharedTexture { }; // Import D3D11 shared handle into Vulkan - let vk_image = self.import_d3d11_handle_to_vulkan(device).map_err(|e| format!("Failed to create Vulkan image from D3D11 handle: {}", e))?; + let vk_image = self + .import_d3d11_handle_to_vulkan(device) + .map_err(|e| format!("Failed to create Vulkan image from D3D11 handle: {}", e))?; // Wrap VkImage in wgpu-hal texture let hal_texture = ::Device::texture_from_raw( @@ -123,8 +120,7 @@ impl D3D11SharedTexture { } // Create external memory image info - let mut external_memory_info = vk::ExternalMemoryImageCreateInfo::default() - .handle_types(vk::ExternalMemoryHandleTypeFlags::D3D11_TEXTURE); + let mut external_memory_info = vk::ExternalMemoryImageCreateInfo::default().handle_types(vk::ExternalMemoryHandleTypeFlags::D3D11_TEXTURE); // Create image create info let image_create_info = vk::ImageCreateInfo::default() @@ -144,10 +140,7 @@ impl D3D11SharedTexture { .push_next(&mut external_memory_info); // Create the image - let image = unsafe { - device.create_image(&image_create_info, None) - .map_err(|e| format!("Failed to create Vulkan image: {:?}", e))? - }; + let image = unsafe { device.create_image(&image_create_info, None).map_err(|e| format!("Failed to create Vulkan image: {:?}", e))? }; // Get memory requirements let memory_requirements = unsafe { device.get_image_memory_requirements(image) }; @@ -155,18 +148,13 @@ impl D3D11SharedTexture { // Import D3D11 handle let mut import_memory_win32 = vk::ImportMemoryWin32HandleInfoKHR::default() .handle_type(vk::ExternalMemoryHandleTypeFlags::D3D11_TEXTURE) - .handle(self.handle); + .handle(self.handle as isize); // Find a suitable memory type - let memory_properties = unsafe { - hal_device.shared_instance().raw_instance().get_physical_device_memory_properties(hal_device.raw_physical_device()) - }; + let memory_properties = unsafe { hal_device.shared_instance().raw_instance().get_physical_device_memory_properties(hal_device.raw_physical_device()) }; - let memory_type_index = find_memory_type_index( - memory_requirements.memory_type_bits, - vk::MemoryPropertyFlags::empty(), - &memory_properties, - ).ok_or("Failed to find suitable memory type for D3D11 texture")?; + let memory_type_index = + find_memory_type_index(memory_requirements.memory_type_bits, vk::MemoryPropertyFlags::empty(), &memory_properties).ok_or("Failed to find suitable memory type for D3D11 texture")?; let allocate_info = vk::MemoryAllocateInfo::default() .allocation_size(memory_requirements.size) @@ -174,14 +162,14 @@ impl D3D11SharedTexture { .push_next(&mut import_memory_win32); let device_memory = unsafe { - device.allocate_memory(&allocate_info, None) + device + .allocate_memory(&allocate_info, None) .map_err(|e| format!("Failed to allocate memory for D3D11 texture: {:?}", e))? }; // Bind memory to image unsafe { - device.bind_image_memory(image, device_memory, 0) - .map_err(|e| format!("Failed to bind memory to image: {:?}", e))?; + device.bind_image_memory(image, device_memory, 0).map_err(|e| format!("Failed to bind memory to image: {:?}", e))?; } Ok(image) @@ -232,17 +220,11 @@ impl D3D11SharedTexture { } #[cfg(all(feature = "accelerated_paint", target_os = "windows"))] -fn find_memory_type_index( - type_filter: u32, - properties: vk::MemoryPropertyFlags, - mem_properties: &vk::PhysicalDeviceMemoryProperties, -) -> Option { +fn find_memory_type_index(type_filter: u32, properties: vk::MemoryPropertyFlags, mem_properties: &vk::PhysicalDeviceMemoryProperties) -> Option { for i in 0..mem_properties.memory_type_count { - if (type_filter & (1 << i)) != 0 - && mem_properties.memory_types[i as usize].property_flags.contains(properties) - { + if (type_filter & (1 << i)) != 0 && mem_properties.memory_types[i as usize].property_flags.contains(properties) { return Some(i); } } None -} \ No newline at end of file +} From 9857fa1924f36df6fe165637fe8cdaa7756ae033 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Thu, 21 Aug 2025 13:54:11 +0000 Subject: [PATCH 08/14] Remove testing chrome://flags url --- desktop/src/cef/context.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desktop/src/cef/context.rs b/desktop/src/cef/context.rs index 0e3253a409..79ad57e374 100644 --- a/desktop/src/cef/context.rs +++ b/desktop/src/cef/context.rs @@ -86,7 +86,7 @@ impl Context { let mut client = Client::new(BrowserProcessClientImpl::new(render_handler, event_handler.clone())); let url = CefString::from(format!("{GRAPHITE_SCHEME}://{FRONTEND_DOMAIN}/").as_str()); - let url = CefString::from(format!("chrome://gpu").as_str()); + // let url = CefString::from(format!("chrome://gpu").as_str()); let window_info = WindowInfo { windowless_rendering_enabled: 1, From f2c5c17234e59749f83695355112e1f4a34894ef Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Thu, 21 Aug 2025 13:54:11 +0000 Subject: [PATCH 09/14] Experimental macos support for texture import --- Cargo.lock | 34 ++- desktop/Cargo.toml | 7 + desktop/src/cef.rs | 21 +- desktop/src/cef/internal/render_handler.rs | 16 +- desktop/src/cef/iosurface.rs | 231 +++++++++++++++++++++ 5 files changed, 302 insertions(+), 7 deletions(-) create mode 100644 desktop/src/cef/iosurface.rs diff --git a/Cargo.lock b/Cargo.lock index 2ef5f412c0..eea2f94344 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2080,6 +2080,7 @@ dependencies = [ "ash", "bytemuck", "cef", + "core-foundation", "derivative", "dirs", "futures", @@ -2087,6 +2088,8 @@ dependencies = [ "graphite-desktop-wrapper", "include_dir", "libc", + "objc2-io-surface", + "objc2-metal 0.3.1", "open", "rfd", "ron", @@ -3492,7 +3495,7 @@ dependencies = [ "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", - "objc2-metal", + "objc2-metal 0.2.2", ] [[package]] @@ -3547,6 +3550,19 @@ 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.1", + "libc", + "objc2 0.6.1", + "objc2-core-foundation", + "objc2-foundation 0.3.1", +] + [[package]] name = "objc2-link-presentation" version = "0.2.2" @@ -3571,6 +3587,20 @@ dependencies = [ "objc2-foundation 0.2.2", ] +[[package]] +name = "objc2-metal" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f246c183239540aab1782457b35ab2040d4259175bd1d0c58e46ada7b47a874" +dependencies = [ + "bitflags 2.9.1", + "block2 0.6.1", + "dispatch2", + "objc2 0.6.1", + "objc2-core-foundation", + "objc2-foundation 0.3.1", +] + [[package]] name = "objc2-quartz-core" version = "0.2.2" @@ -3581,7 +3611,7 @@ dependencies = [ "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", - "objc2-metal", + "objc2-metal 0.2.2", ] [[package]] diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml index 185d5682b5..cca83b4e07 100644 --- a/desktop/Cargo.toml +++ b/desktop/Cargo.toml @@ -12,6 +12,7 @@ rust-version = "1.87" default = ["gpu", "accelerated_paint"] gpu = ["graphite-desktop-wrapper/gpu"] accelerated_paint = ["ash", "libc", "windows"] +accelerated_paint = ["ash", "libc", "windows", "objc2-io-surface", "objc2-metal", "core-foundation"] [dependencies] # # Local dependencies @@ -46,3 +47,9 @@ windows = { version = "0.58", features = [ "Win32_Graphics_Dxgi_Common", "Win32_Foundation" ], optional = true } + +# 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 } diff --git a/desktop/src/cef.rs b/desktop/src/cef.rs index fd7db31663..99d0a12c7d 100644 --- a/desktop/src/cef.rs +++ b/desktop/src/cef.rs @@ -10,6 +10,8 @@ mod dirs; mod dmabuf; #[cfg(target_os = "windows")] mod d3d11; +#[cfg(target_os = "macos")] +mod iosurface; mod input; mod internal; mod ipc; @@ -22,6 +24,8 @@ use winit::event_loop::EventLoopProxy; #[cfg(target_os = "windows")] use crate::cef::d3d11::D3D11SharedTexture; +#[cfg(target_os = "macos")] +use crate::cef::iosurface::IOSurfaceTexture; pub(crate) trait CefEventHandler: Clone { fn window_size(&self) -> WindowSize; @@ -190,8 +194,21 @@ impl CefEventHandler for CefHandler { } } #[cfg(target_os = "macos")] - internal::render_handler::SharedTextureHandle::IOSurface(_handle) => { - tracing::warn!("IOSurface shared texture import not implemented yet"); + internal::render_handler::SharedTextureHandle::IOSurface { handle, format, width, height } => { + let iosurface_texture = IOSurfaceTexture { + handle, + width, + height, + format, + }; + match iosurface_texture.import_to_wgpu(&self.wgpu_context.device) { + Ok(texture) => { + let _ = self.event_loop_proxy.send_event(CustomEvent::UiUpdate(texture)); + } + Err(e) => { + tracing::error!("Failed to import IOSurface texture: {}", e); + } + } } } } diff --git a/desktop/src/cef/internal/render_handler.rs b/desktop/src/cef/internal/render_handler.rs index a184c1a0d4..80d4c3a7e4 100644 --- a/desktop/src/cef/internal/render_handler.rs +++ b/desktop/src/cef/internal/render_handler.rs @@ -25,7 +25,12 @@ pub enum SharedTextureHandle { height: u32, }, #[cfg(target_os = "macos")] - IOSurface(*mut c_void), + IOSurface { + handle: *mut c_void, + format: cef::sys::cef_color_type_t, + width: u32, + height: u32, + }, #[cfg(target_os = "linux")] DmaBuf { fds: Vec, @@ -113,8 +118,13 @@ impl ImplRenderHandler for RenderHandlerImpl { #[cfg(target_os = "macos")] { - // Extract IOSurface handle - let shared_handle = SharedTextureHandle::IOSurface(info.shared_texture_handle); + // Extract IOSurface handle with texture metadata + let shared_handle = SharedTextureHandle::IOSurface { + handle: info.shared_texture_handle, + format: *info.format.as_ref(), + width: info.extra.coded_size.width as u32, + height: info.extra.coded_size.height as u32, + }; self.event_handler.on_accelerated_paint(shared_handle); } } diff --git a/desktop/src/cef/iosurface.rs b/desktop/src/cef/iosurface.rs new file mode 100644 index 0000000000..bc7bda6dae --- /dev/null +++ b/desktop/src/cef/iosurface.rs @@ -0,0 +1,231 @@ +#[cfg(target_os = "macos")] +use std::os::raw::c_void; + +#[cfg(all(feature = "accelerated_paint", target_os = "macos"))] +use ash::vk; +#[cfg(all(feature = "accelerated_paint", target_os = "macos"))] +use wgpu::hal::api; +#[cfg(all(feature = "accelerated_paint", target_os = "macos"))] +use objc2_io_surface::{IOSurface, IOSurfaceRef}; +#[cfg(all(feature = "accelerated_paint", target_os = "macos"))] +use objc2_metal::{MTLDevice, MTLTexture, MTLTextureDescriptor, MTLPixelFormat}; +#[cfg(all(feature = "accelerated_paint", target_os = "macos"))] +use core_foundation::base::{CFType, TCFType}; + +#[cfg(target_os = "macos")] +pub struct IOSurfaceTexture { + pub handle: *mut c_void, + pub width: u32, + pub height: u32, + pub format: cef::sys::cef_color_type_t, +} + +#[cfg(target_os = "macos")] +impl IOSurfaceTexture { + pub fn import_to_wgpu(&self, device: &wgpu::Device) -> Result { + tracing::debug!( + "IOSurface texture import requested: {}x{} handle={:p}", + self.width, + self.height, + self.handle + ); + + // Try to import via Metal/Vulkan, fallback to CPU texture on failure + #[cfg(feature = "accelerated_paint")] + { + match self.import_via_vulkan(device) { + Ok(texture) => { + tracing::info!("Successfully imported IOSurface texture via Vulkan"); + return Ok(texture); + } + Err(e) => { + tracing::warn!("Failed to import IOSurface via Vulkan: {}, falling back to CPU texture", e); + } + } + } + + // Fallback: create empty CPU texture with same dimensions + self.create_fallback_texture(device) + } + + #[cfg(feature = "accelerated_paint")] + fn import_via_vulkan(&self, device: &wgpu::Device) -> Result { + // Validate handle + if self.handle.is_null() { + return Err("IOSurface handle is null".to_string()); + } + + // Get wgpu's Vulkan instance and device + use wgpu::{TextureUses, wgc::api::Vulkan}; + let hal_texture = unsafe { + device.as_hal::(|device| { + let Some(device) = device else { + return Err("Device is not using Vulkan backend".to_string()); + }; + + // Import IOSurface handle into Vulkan via Metal + let vk_image = self.import_iosurface_to_vulkan(device).map_err(|e| format!("Failed to create Vulkan image from IOSurface: {}", e))?; + + // Wrap VkImage in wgpu-hal texture + let hal_texture = ::Device::texture_from_raw( + vk_image, + &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: self.cef_to_hal_format()?, + usage: TextureUses::COPY_DST | TextureUses::RESOURCE, + memory_flags: wgpu::hal::MemoryFlags::empty(), + view_formats: vec![], + }, + None, // drop_callback + ); + + Ok(hal_texture) + }) + }?; + + // Import hal texture into wgpu + let texture = unsafe { + device.create_texture_from_hal::( + hal_texture, + &wgpu::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: self.cef_to_wgpu_format()?, + usage: wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: &[], + }, + ) + }; + + Ok(texture) + } + + #[cfg(feature = "accelerated_paint")] + fn import_iosurface_to_vulkan(&self, hal_device: &::Device) -> Result { + // Get raw Vulkan handles + let device = hal_device.raw_device(); + let _instance = hal_device.shared_instance().raw_instance(); + + // Validate dimensions + if self.width == 0 || self.height == 0 { + return Err("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) + }; + + // Create Metal texture from IOSurface + let mtl_texture = self.create_metal_texture_from_iosurface(&iosurface)?; + + // Import Metal texture into Vulkan using VK_EXT_metal_objects + self.import_metal_texture_to_vulkan(device, &mtl_texture) + } + + #[cfg(feature = "accelerated_paint")] + fn create_metal_texture_from_iosurface(&self, iosurface: &IOSurface) -> Result { + // Get Metal device (this would need to be obtained from wgpu-hal) + // For now, we'll create a simple fallback + tracing::warn!("Metal texture creation from IOSurface not fully implemented"); + Err("Metal texture creation not available".to_string()) + } + + #[cfg(feature = "accelerated_paint")] + fn import_metal_texture_to_vulkan(&self, device: vk::Device, _mtl_texture: &MTLTexture) -> Result { + // Create external memory image info for Metal objects + let mut external_memory_info = vk::ExternalMemoryImageCreateInfo::default() + .handle_types(vk::ExternalMemoryHandleTypeFlags::MTLTEXTURE_EXT); + + // Create image create info + let image_create_info = vk::ImageCreateInfo::default() + .image_type(vk::ImageType::TYPE_2D) + .format(self.cef_to_vk_format()?) + .extent(vk::Extent3D { + width: self.width, + height: self.height, + depth: 1, + }) + .mip_levels(1) + .array_layers(1) + .samples(vk::SampleCountFlags::TYPE_1) + .tiling(vk::ImageTiling::OPTIMAL) + .usage(vk::ImageUsageFlags::SAMPLED | vk::ImageUsageFlags::COLOR_ATTACHMENT) + .sharing_mode(vk::SharingMode::EXCLUSIVE) + .push_next(&mut external_memory_info); + + // Create the image + let image = unsafe { + device.create_image(&image_create_info, None) + .map_err(|e| format!("Failed to create Vulkan image: {:?}", e))? + }; + + // Note: The actual Metal-to-Vulkan import would require VK_EXT_metal_objects + // and proper Metal texture handle extraction, which is complex and not + // fully supported in the current objc2 bindings + tracing::warn!("Metal-to-Vulkan texture import not fully implemented"); + + Ok(image) + } + + fn cef_to_vk_format(&self) -> Result { + use cef::sys::cef_color_type_t; + match self.format { + // macOS IOSurfaces are typically BGRA + 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), + _ => Err(format!("Unsupported CEF format for Vulkan: {:?}", self.format)), + } + } + + fn cef_to_hal_format(&self) -> Result { + use cef::sys::cef_color_type_t; + match self.format { + // macOS IOSurfaces are typically BGRA with sRGB + cef_color_type_t::CEF_COLOR_TYPE_BGRA_8888 => Ok(wgpu::TextureFormat::Bgra8UnormSrgb), + cef_color_type_t::CEF_COLOR_TYPE_RGBA_8888 => Ok(wgpu::TextureFormat::Rgba8UnormSrgb), + _ => Err(format!("Unsupported CEF format for HAL: {:?}", self.format)), + } + } + + fn cef_to_wgpu_format(&self) -> Result { + self.cef_to_hal_format() + } + + fn create_fallback_texture(&self, device: &wgpu::Device) -> Result { + let format = self.cef_to_wgpu_format()?; + let texture = device.create_texture(&wgpu::TextureDescriptor { + label: Some("CEF IOSurface Texture (fallback)"), + 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, + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + view_formats: &[], + }); + + tracing::warn!("Using fallback CPU texture - IOSurface hardware acceleration not available"); + Ok(texture) + } +} \ No newline at end of file From b822be0a2aba596e111e433fb981a5dc4465d7cf Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Thu, 21 Aug 2025 13:54:11 +0000 Subject: [PATCH 10/14] Cleanup code and improve latency / frame pacing --- desktop/Cargo.toml | 5 +- desktop/src/app.rs | 60 +++-- desktop/src/cef.rs | 111 +++------ .../src/cef/internal/browser_process_app.rs | 2 +- desktop/src/cef/internal/render_handler.rs | 2 +- desktop/src/cef/iosurface.rs | 231 ------------------ desktop/src/cef/platform.rs | 24 +- desktop/src/cef/texture_import/common.rs | 169 +++++++++++++ desktop/src/cef/{ => texture_import}/d3d11.rs | 166 ++++++------- .../src/cef/{ => texture_import}/dmabuf.rs | 192 +++++++-------- desktop/src/cef/texture_import/iosurface.rs | 209 ++++++++++++++++ desktop/src/cef/texture_import/mod.rs | 41 ++++ desktop/src/main.rs | 20 +- desktop/src/render/graphics_state.rs | 3 +- 14 files changed, 679 insertions(+), 556 deletions(-) delete mode 100644 desktop/src/cef/iosurface.rs create mode 100644 desktop/src/cef/texture_import/common.rs rename desktop/src/cef/{ => texture_import}/d3d11.rs (50%) rename desktop/src/cef/{ => texture_import}/dmabuf.rs (50%) create mode 100644 desktop/src/cef/texture_import/iosurface.rs create mode 100644 desktop/src/cef/texture_import/mod.rs diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml index cca83b4e07..5c0a0a53ca 100644 --- a/desktop/Cargo.toml +++ b/desktop/Cargo.toml @@ -37,7 +37,6 @@ open = { workspace = true } # Hardware acceleration dependencies ash = { version = "0.38", optional = true } -libc = { version = "0.2", optional = true } # Windows-specific dependencies [target.'cfg(windows)'.dependencies] @@ -53,3 +52,7 @@ windows = { version = "0.58", features = [ objc2-io-surface = { version = "0.3", optional = true } objc2-metal = { version = "0.3", optional = true } core-foundation = { version = "0.9", optional = true } + +# Linux-specific dependencies +[target.'cfg(target_os = "linux")'.dependencies] +libc = { version = "0.2", optional = true } diff --git a/desktop/src/app.rs b/desktop/src/app.rs index 072748890a..0156de6165 100644 --- a/desktop/src/app.rs +++ b/desktop/src/app.rs @@ -7,12 +7,12 @@ use graphite_desktop_wrapper::{DesktopWrapper, NodeGraphExecutionResult, WgpuCon use rfd::AsyncFileDialog; use std::sync::Arc; use std::sync::mpsc::Sender; +use std::sync::mpsc::SyncSender; use std::thread; use std::time::Duration; use std::time::Instant; use winit::application::ApplicationHandler; use winit::dpi::PhysicalSize; -use winit::event::StartCause; use winit::event::WindowEvent; use winit::event_loop::ActiveEventLoop; use winit::event_loop::ControlFlow; @@ -31,14 +31,25 @@ pub(crate) struct WinitApp { wgpu_context: WgpuContext, event_loop_proxy: EventLoopProxy, desktop_wrapper: DesktopWrapper, - editor: Editor, last_ui_update: Instant, avg_frame_time: f32, + start_render_sender: SyncSender<()>, } impl WinitApp { pub(crate) fn new(cef_context: cef::Context, window_size_sender: Sender, wgpu_context: WgpuContext, event_loop_proxy: EventLoopProxy) -> Self { let desktop_wrapper = DesktopWrapper::new(); + + let rendering_loop_proxy = event_loop_proxy.clone(); + let (start_render_sender, start_render_receiver) = std::sync::mpsc::sync_channel(1); + std::thread::spawn(move || { + loop { + let result = futures::executor::block_on(DesktopWrapper::execute_node_graph()); + let _ = rendering_loop_proxy.send_event(CustomEvent::NodeGraphExecutionResult(result)); + let _ = start_render_receiver.recv(); + } + }); + Self { cef_context, window: None, @@ -50,6 +61,7 @@ impl WinitApp { desktop_wrapper, last_ui_update: Instant::now(), avg_frame_time: 0., + start_render_sender, } } @@ -157,23 +169,20 @@ impl ApplicationHandler for WinitApp { // Set a timeout in case we miss any cef schedule requests let timeout = Instant::now() + Duration::from_millis(10); let wait_until = timeout.min(self.cef_schedule.unwrap_or(timeout)); - self.cef_context.work(); - - event_loop.set_control_flow(ControlFlow::WaitUntil(wait_until)); - } - - fn new_events(&mut self, _event_loop: &ActiveEventLoop, cause: StartCause) { if let Some(schedule) = self.cef_schedule && schedule < Instant::now() { self.cef_schedule = None; - self.cef_context.work(); - } - if let StartCause::ResumeTimeReached { .. } = cause { - if let Some(window) = &self.window { - window.request_redraw(); + // Poll cef message loop multiple times to avoid message loop starvation + for _ in 0..10 { + self.cef_context.work(); } } + if let Some(window) = &self.window.as_ref() { + window.request_redraw(); + } + + event_loop.set_control_flow(ControlFlow::WaitUntil(wait_until)); } fn resumed(&mut self, event_loop: &ActiveEventLoop) { @@ -227,8 +236,9 @@ impl ApplicationHandler for WinitApp { graphics_state.bind_ui_texture(texture); let elapsed = self.last_ui_update.elapsed().as_secs_f32(); self.last_ui_update = Instant::now(); - self.avg_frame_time = (self.avg_frame_time * 3. + elapsed) / 4.; - println!("ui fps: {:.2}", 1. / self.avg_frame_time); + if elapsed < 0.5 { + self.avg_frame_time = (self.avg_frame_time * 3. + elapsed) / 4.; + } } if let Some(window) = &self.window { window.request_redraw(); @@ -260,16 +270,18 @@ impl ApplicationHandler for WinitApp { WindowEvent::RedrawRequested => { let Some(ref mut graphics_state) = self.graphics_state else { return }; // Only rerender once we have a new ui texture to display - - match graphics_state.render() { - Ok(_) => {} - Err(wgpu::SurfaceError::Lost) => { - tracing::warn!("lost surface"); - } - Err(wgpu::SurfaceError::OutOfMemory) => { - event_loop.exit(); + if let Some(window) = &self.window { + match graphics_state.render(window.as_ref()) { + Ok(_) => {} + Err(wgpu::SurfaceError::Lost) => { + tracing::warn!("lost surface"); + } + Err(wgpu::SurfaceError::OutOfMemory) => { + event_loop.exit(); + } + Err(e) => tracing::error!("{:?}", e), } - Err(e) => tracing::error!("{:?}", e), + let _ = self.start_render_sender.try_send(()); } } // Currently not supported on wayland see https://github.com/rust-windowing/winit/issues/1881 diff --git a/desktop/src/cef.rs b/desktop/src/cef.rs index 99d0a12c7d..6c4d940b27 100644 --- a/desktop/src/cef.rs +++ b/desktop/src/cef.rs @@ -1,3 +1,33 @@ +//! CEF (Chromium Embedded Framework) integration for Graphite Desktop +//! +//! This module provides CEF browser integration with hardware-accelerated texture sharing. +//! +//! # Hardware Acceleration +//! +//! The texture import system supports platform-specific hardware acceleration: +//! +//! - **Linux**: DMA-BUF via Vulkan external memory (`accelerated_paint_dmabuf` feature) +//! - **Windows**: D3D11 shared textures via Vulkan interop (`accelerated_paint_d3d11` feature) +//! - **macOS**: IOSurface via Metal/Vulkan interop (`accelerated_paint_iosurface` feature) +//! +//! ## Feature Configuration +//! +//! Enable hardware acceleration with: +//! ```toml +//! [dependencies] +//! graphite-desktop = { features = ["accelerated_paint"] } +//! ``` +//! +//! Or enable platform-specific support: +//! ```toml +//! [dependencies] +//! graphite-desktop = { features = ["accelerated_paint_linux"] } # Linux only +//! graphite-desktop = { features = ["accelerated_paint_windows"] } # Windows only +//! graphite-desktop = { features = ["accelerated_paint_macos"] } # macOS only +//! ``` +//! +//! The system gracefully falls back to CPU textures when hardware acceleration is unavailable. + use crate::CustomEvent; use crate::render::FrameBufferRef; use graphite_desktop_wrapper::{WgpuContext, deserialize_editor_message}; @@ -7,26 +37,18 @@ use std::time::Instant; mod context; mod dirs; -mod dmabuf; -#[cfg(target_os = "windows")] -mod d3d11; -#[cfg(target_os = "macos")] -mod iosurface; mod input; mod internal; mod ipc; mod platform; mod scheme_handler; +#[cfg(feature = "accelerated_paint")] +mod texture_import; mod utility; pub(crate) use context::{Context, InitError, Initialized, Setup, SetupError}; use winit::event_loop::EventLoopProxy; -#[cfg(target_os = "windows")] -use crate::cef::d3d11::D3D11SharedTexture; -#[cfg(target_os = "macos")] -use crate::cef::iosurface::IOSurfaceTexture; - pub(crate) trait CefEventHandler: Clone { fn window_size(&self) -> WindowSize; fn draw<'a>(&self, frame_buffer: FrameBufferRef<'a>); @@ -144,71 +166,12 @@ impl CefEventHandler for CefHandler { #[cfg(feature = "accelerated_paint")] fn on_accelerated_paint(&self, shared_handle: internal::render_handler::SharedTextureHandle) { - #[cfg(target_os = "linux")] - use crate::cef::dmabuf::DmaBufTexture; - - match shared_handle { - #[cfg(target_os = "linux")] - internal::render_handler::SharedTextureHandle::DmaBuf { - fds, - format, - modifier, - width, - height, - strides, - offsets, - } => { - let dmabuf_texture = DmaBufTexture { - fds, - format, - modifier, - width, - height, - strides, - offsets, - }; - match dmabuf_texture.import_to_wgpu(&self.wgpu_context.device) { - Ok(texture) => { - let _ = self.event_loop_proxy.send_event(CustomEvent::UiUpdate(texture)); - } - Err(e) => { - tracing::error!("Failed to import DMA-BUF texture: {}", e); - } - } - } - #[cfg(target_os = "windows")] - internal::render_handler::SharedTextureHandle::D3D11 { handle, format, width, height } => { - let d3d11_texture = D3D11SharedTexture { - handle, - width, - height, - format, - }; - match d3d11_texture.import_to_wgpu(&self.wgpu_context.device) { - Ok(texture) => { - let _ = self.event_loop_proxy.send_event(CustomEvent::UiUpdate(texture)); - } - Err(e) => { - tracing::error!("Failed to import D3D11 shared texture: {}", e); - } - } + match self::texture_import::import_texture(shared_handle, &self.wgpu_context.device) { + Ok(texture) => { + let _ = self.event_loop_proxy.send_event(CustomEvent::UiUpdate(texture)); } - #[cfg(target_os = "macos")] - internal::render_handler::SharedTextureHandle::IOSurface { handle, format, width, height } => { - let iosurface_texture = IOSurfaceTexture { - handle, - width, - height, - format, - }; - match iosurface_texture.import_to_wgpu(&self.wgpu_context.device) { - Ok(texture) => { - let _ = self.event_loop_proxy.send_event(CustomEvent::UiUpdate(texture)); - } - Err(e) => { - tracing::error!("Failed to import IOSurface texture: {}", e); - } - } + Err(e) => { + tracing::error!("Failed to import shared texture: {}", e); } } } diff --git a/desktop/src/cef/internal/browser_process_app.rs b/desktop/src/cef/internal/browser_process_app.rs index ce1a8e366c..9290b1e76c 100644 --- a/desktop/src/cef/internal/browser_process_app.rs +++ b/desktop/src/cef/internal/browser_process_app.rs @@ -40,7 +40,7 @@ impl ImplApp for BrowserProcessAppImpl { cmd.append_switch(Some(&CefString::from("disable-gpu"))); cmd.append_switch(Some(&CefString::from("disable-gpu-compositing"))); } - + #[cfg(feature = "accelerated_paint")] { // Enable GPU acceleration switches for better performance diff --git a/desktop/src/cef/internal/render_handler.rs b/desktop/src/cef/internal/render_handler.rs index 80d4c3a7e4..7fcaca6619 100644 --- a/desktop/src/cef/internal/render_handler.rs +++ b/desktop/src/cef/internal/render_handler.rs @@ -5,7 +5,7 @@ use cef::{Browser, ImplRenderHandler, PaintElementType, Rect, WrapRenderHandler} use crate::cef::CefEventHandler; use crate::render::FrameBufferRef; -#[cfg(target_os = "linux")] +#[cfg(all(target_os = "linux", feature = "accelerated_paint"))] use std::os::fd::RawFd; #[cfg(all(feature = "accelerated_paint", any(target_os = "windows", target_os = "macos")))] use std::os::raw::c_void; diff --git a/desktop/src/cef/iosurface.rs b/desktop/src/cef/iosurface.rs deleted file mode 100644 index bc7bda6dae..0000000000 --- a/desktop/src/cef/iosurface.rs +++ /dev/null @@ -1,231 +0,0 @@ -#[cfg(target_os = "macos")] -use std::os::raw::c_void; - -#[cfg(all(feature = "accelerated_paint", target_os = "macos"))] -use ash::vk; -#[cfg(all(feature = "accelerated_paint", target_os = "macos"))] -use wgpu::hal::api; -#[cfg(all(feature = "accelerated_paint", target_os = "macos"))] -use objc2_io_surface::{IOSurface, IOSurfaceRef}; -#[cfg(all(feature = "accelerated_paint", target_os = "macos"))] -use objc2_metal::{MTLDevice, MTLTexture, MTLTextureDescriptor, MTLPixelFormat}; -#[cfg(all(feature = "accelerated_paint", target_os = "macos"))] -use core_foundation::base::{CFType, TCFType}; - -#[cfg(target_os = "macos")] -pub struct IOSurfaceTexture { - pub handle: *mut c_void, - pub width: u32, - pub height: u32, - pub format: cef::sys::cef_color_type_t, -} - -#[cfg(target_os = "macos")] -impl IOSurfaceTexture { - pub fn import_to_wgpu(&self, device: &wgpu::Device) -> Result { - tracing::debug!( - "IOSurface texture import requested: {}x{} handle={:p}", - self.width, - self.height, - self.handle - ); - - // Try to import via Metal/Vulkan, fallback to CPU texture on failure - #[cfg(feature = "accelerated_paint")] - { - match self.import_via_vulkan(device) { - Ok(texture) => { - tracing::info!("Successfully imported IOSurface texture via Vulkan"); - return Ok(texture); - } - Err(e) => { - tracing::warn!("Failed to import IOSurface via Vulkan: {}, falling back to CPU texture", e); - } - } - } - - // Fallback: create empty CPU texture with same dimensions - self.create_fallback_texture(device) - } - - #[cfg(feature = "accelerated_paint")] - fn import_via_vulkan(&self, device: &wgpu::Device) -> Result { - // Validate handle - if self.handle.is_null() { - return Err("IOSurface handle is null".to_string()); - } - - // Get wgpu's Vulkan instance and device - use wgpu::{TextureUses, wgc::api::Vulkan}; - let hal_texture = unsafe { - device.as_hal::(|device| { - let Some(device) = device else { - return Err("Device is not using Vulkan backend".to_string()); - }; - - // Import IOSurface handle into Vulkan via Metal - let vk_image = self.import_iosurface_to_vulkan(device).map_err(|e| format!("Failed to create Vulkan image from IOSurface: {}", e))?; - - // Wrap VkImage in wgpu-hal texture - let hal_texture = ::Device::texture_from_raw( - vk_image, - &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: self.cef_to_hal_format()?, - usage: TextureUses::COPY_DST | TextureUses::RESOURCE, - memory_flags: wgpu::hal::MemoryFlags::empty(), - view_formats: vec![], - }, - None, // drop_callback - ); - - Ok(hal_texture) - }) - }?; - - // Import hal texture into wgpu - let texture = unsafe { - device.create_texture_from_hal::( - hal_texture, - &wgpu::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: self.cef_to_wgpu_format()?, - usage: wgpu::TextureUsages::TEXTURE_BINDING, - view_formats: &[], - }, - ) - }; - - Ok(texture) - } - - #[cfg(feature = "accelerated_paint")] - fn import_iosurface_to_vulkan(&self, hal_device: &::Device) -> Result { - // Get raw Vulkan handles - let device = hal_device.raw_device(); - let _instance = hal_device.shared_instance().raw_instance(); - - // Validate dimensions - if self.width == 0 || self.height == 0 { - return Err("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) - }; - - // Create Metal texture from IOSurface - let mtl_texture = self.create_metal_texture_from_iosurface(&iosurface)?; - - // Import Metal texture into Vulkan using VK_EXT_metal_objects - self.import_metal_texture_to_vulkan(device, &mtl_texture) - } - - #[cfg(feature = "accelerated_paint")] - fn create_metal_texture_from_iosurface(&self, iosurface: &IOSurface) -> Result { - // Get Metal device (this would need to be obtained from wgpu-hal) - // For now, we'll create a simple fallback - tracing::warn!("Metal texture creation from IOSurface not fully implemented"); - Err("Metal texture creation not available".to_string()) - } - - #[cfg(feature = "accelerated_paint")] - fn import_metal_texture_to_vulkan(&self, device: vk::Device, _mtl_texture: &MTLTexture) -> Result { - // Create external memory image info for Metal objects - let mut external_memory_info = vk::ExternalMemoryImageCreateInfo::default() - .handle_types(vk::ExternalMemoryHandleTypeFlags::MTLTEXTURE_EXT); - - // Create image create info - let image_create_info = vk::ImageCreateInfo::default() - .image_type(vk::ImageType::TYPE_2D) - .format(self.cef_to_vk_format()?) - .extent(vk::Extent3D { - width: self.width, - height: self.height, - depth: 1, - }) - .mip_levels(1) - .array_layers(1) - .samples(vk::SampleCountFlags::TYPE_1) - .tiling(vk::ImageTiling::OPTIMAL) - .usage(vk::ImageUsageFlags::SAMPLED | vk::ImageUsageFlags::COLOR_ATTACHMENT) - .sharing_mode(vk::SharingMode::EXCLUSIVE) - .push_next(&mut external_memory_info); - - // Create the image - let image = unsafe { - device.create_image(&image_create_info, None) - .map_err(|e| format!("Failed to create Vulkan image: {:?}", e))? - }; - - // Note: The actual Metal-to-Vulkan import would require VK_EXT_metal_objects - // and proper Metal texture handle extraction, which is complex and not - // fully supported in the current objc2 bindings - tracing::warn!("Metal-to-Vulkan texture import not fully implemented"); - - Ok(image) - } - - fn cef_to_vk_format(&self) -> Result { - use cef::sys::cef_color_type_t; - match self.format { - // macOS IOSurfaces are typically BGRA - 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), - _ => Err(format!("Unsupported CEF format for Vulkan: {:?}", self.format)), - } - } - - fn cef_to_hal_format(&self) -> Result { - use cef::sys::cef_color_type_t; - match self.format { - // macOS IOSurfaces are typically BGRA with sRGB - cef_color_type_t::CEF_COLOR_TYPE_BGRA_8888 => Ok(wgpu::TextureFormat::Bgra8UnormSrgb), - cef_color_type_t::CEF_COLOR_TYPE_RGBA_8888 => Ok(wgpu::TextureFormat::Rgba8UnormSrgb), - _ => Err(format!("Unsupported CEF format for HAL: {:?}", self.format)), - } - } - - fn cef_to_wgpu_format(&self) -> Result { - self.cef_to_hal_format() - } - - fn create_fallback_texture(&self, device: &wgpu::Device) -> Result { - let format = self.cef_to_wgpu_format()?; - let texture = device.create_texture(&wgpu::TextureDescriptor { - label: Some("CEF IOSurface Texture (fallback)"), - 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, - usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, - view_formats: &[], - }); - - tracing::warn!("Using fallback CPU texture - IOSurface hardware acceleration not available"); - Ok(texture) - } -} \ No newline at end of file diff --git a/desktop/src/cef/platform.rs b/desktop/src/cef/platform.rs index f15fdee401..7df5d5f982 100644 --- a/desktop/src/cef/platform.rs +++ b/desktop/src/cef/platform.rs @@ -9,17 +9,14 @@ pub fn should_enable_hardware_acceleration() -> bool { .or_else(|| std::env::var("WAYLAND_SOCKET").ok()) .filter(|var| !var.is_empty()) .is_some(); - - let has_x11 = std::env::var("DISPLAY") - .ok() - .filter(|var| !var.is_empty()) - .is_some(); - + + let has_x11 = std::env::var("DISPLAY").ok().filter(|var| !var.is_empty()).is_some(); + if !has_wayland && !has_x11 { tracing::warn!("No display server detected, disabling hardware acceleration"); return false; } - + // Check for NVIDIA proprietary driver (known to have issues) if let Ok(driver_info) = std::fs::read_to_string("/proc/driver/nvidia/version") { if driver_info.contains("NVIDIA") { @@ -27,7 +24,7 @@ pub fn should_enable_hardware_acceleration() -> bool { // Still return true but with warning } } - + // Check for basic GPU capabilities if has_wayland { tracing::info!("Wayland detected, enabling hardware acceleration"); @@ -39,29 +36,24 @@ pub fn should_enable_hardware_acceleration() -> bool { false } } - + #[cfg(target_os = "windows")] { // Windows generally has good D3D11 support tracing::info!("Windows detected, enabling hardware acceleration"); true } - + #[cfg(target_os = "macos")] { // macOS has good Metal/IOSurface support tracing::info!("macOS detected, enabling hardware acceleration"); true } - + #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))] { tracing::warn!("Unsupported platform for hardware acceleration"); false } } - -#[cfg(not(feature = "accelerated_paint"))] -pub fn should_enable_hardware_acceleration() -> bool { - false -} \ No newline at end of file diff --git a/desktop/src/cef/texture_import/common.rs b/desktop/src/cef/texture_import/common.rs new file mode 100644 index 0000000000..12f4d7619c --- /dev/null +++ b/desktop/src/cef/texture_import/common.rs @@ -0,0 +1,169 @@ +//! Common utilities and traits for texture import across platforms + +use cef::sys::cef_color_type_t; +use wgpu::Device; + +#[cfg(feature = "accelerated_paint")] +use ash::vk; + +/// Result type for texture import operations +pub type TextureImportResult = Result; + +/// Errors that can occur during texture import +#[derive(Debug, thiserror::Error)] +pub enum TextureImportError { + #[error("Invalid texture handle: {0}")] + InvalidHandle(String), + + #[error("Unsupported texture format: {format:?}")] + UnsupportedFormat { format: cef_color_type_t }, + + #[error("Hardware acceleration not available: {reason}")] + HardwareUnavailable { reason: String }, + + #[error("Vulkan operation failed: {operation}")] + VulkanError { operation: String }, + + #[error("Platform-specific error: {message}")] + PlatformError { message: String }, + + #[error("Texture creation failed: {reason}")] + TextureCreationFailed { reason: String }, +} + +/// Trait for platform-specific texture importers +pub trait TextureImporter { + /// Import the texture into wgpu, with automatic fallback to CPU texture + fn import_to_wgpu(&self, device: &Device) -> TextureImportResult; + + /// Get texture dimensions + fn dimensions(&self) -> (u32, u32); + + /// Get texture format + fn format(&self) -> cef_color_type_t; + + /// Check if hardware acceleration is available for this texture + fn supports_hardware_acceleration(&self, device: &Device) -> bool; + + /// Platform name for logging purposes + fn platform_name(&self) -> &'static str; +} + +/// Common format conversion utilities +pub mod format { + use super::*; + + /// Convert CEF color type to wgpu texture format + pub fn cef_to_wgpu(format: cef_color_type_t) -> Result { + match format { + cef_color_type_t::CEF_COLOR_TYPE_BGRA_8888 => Ok(wgpu::TextureFormat::Bgra8UnormSrgb), + cef_color_type_t::CEF_COLOR_TYPE_RGBA_8888 => Ok(wgpu::TextureFormat::Rgba8UnormSrgb), + _ => Err(TextureImportError::UnsupportedFormat { format }), + } + } + + #[cfg(feature = "accelerated_paint")] + /// Convert CEF color type to Vulkan format + pub fn cef_to_vulkan(format: cef_color_type_t) -> Result { + 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), + _ => Err(TextureImportError::UnsupportedFormat { format }), + } + } +} + +/// Common texture creation utilities +pub mod texture { + use super::*; + + /// Create a fallback CPU texture with the given dimensions and format + pub fn create_fallback(device: &Device, width: u32, height: u32, format: cef_color_type_t, label: &str) -> TextureImportResult { + let wgpu_format = format::cef_to_wgpu(format)?; + + let texture = device.create_texture(&wgpu::TextureDescriptor { + label: Some(label), + size: wgpu::Extent3d { + width, + height, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu_format, + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + view_formats: &[], + }); + + tracing::warn!("Using fallback CPU texture - hardware acceleration not available"); + Ok(texture) + } +} + +#[cfg(feature = "accelerated_paint")] +/// Common Vulkan utilities +pub mod vulkan { + use super::*; + + /// 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 { + for i in 0..mem_properties.memory_type_count { + if (type_filter & (1 << i)) != 0 && mem_properties.memory_types[i as usize].property_flags.contains(properties) { + return Some(i); + } + } + None + } + + /// Check if the wgpu device is using Vulkan backend + pub fn is_vulkan_backend(device: &Device) -> bool { + use wgpu::hal::api; + let mut is_vulkan = false; + unsafe { + device.as_hal::(|device| { + is_vulkan = device.is_some(); + }); + } + is_vulkan + } +} + +/// Import a texture using the appropriate platform-specific importer +pub fn import_texture(shared_handle: crate::cef::internal::render_handler::SharedTextureHandle, device: &Device) -> TextureImportResult { + match shared_handle { + #[cfg(target_os = "linux")] + crate::cef::internal::render_handler::SharedTextureHandle::DmaBuf { + fds, + format, + modifier, + width, + height, + strides, + offsets, + } => { + let importer = super::dmabuf::DmaBufImporter { + fds, + format, + modifier, + width, + height, + strides, + offsets, + }; + importer.import_to_wgpu(device) + } + + #[cfg(target_os = "windows")] + crate::cef::internal::render_handler::SharedTextureHandle::D3D11 { handle, format, width, height } => { + let importer = super::d3d11::D3D11Importer { handle, format, width, height }; + importer.import_to_wgpu(device) + } + + #[cfg(target_os = "macos")] + crate::cef::internal::render_handler::SharedTextureHandle::IOSurface { handle, format, width, height } => { + let importer = super::iosurface::IOSurfaceImporter { handle, format, width, height }; + importer.import_to_wgpu(device) + } + } +} diff --git a/desktop/src/cef/d3d11.rs b/desktop/src/cef/texture_import/d3d11.rs similarity index 50% rename from desktop/src/cef/d3d11.rs rename to desktop/src/cef/texture_import/d3d11.rs index a16353005c..f68adeac39 100644 --- a/desktop/src/cef/d3d11.rs +++ b/desktop/src/cef/texture_import/d3d11.rs @@ -1,3 +1,5 @@ +//! Windows D3D11 shared texture import implementation + #[cfg(target_os = "windows")] use std::os::raw::c_void; @@ -5,59 +7,90 @@ use std::os::raw::c_void; use ash::vk; #[cfg(all(feature = "accelerated_paint", target_os = "windows"))] use wgpu::hal::api; -#[cfg(all(feature = "accelerated_paint", target_os = "windows"))] -use windows::Win32::Graphics::{Direct3D11::*, Dxgi::*}; + +use super::common::{TextureImportError, TextureImportResult, TextureImporter, format, texture, vulkan}; +use cef::sys::cef_color_type_t; #[cfg(target_os = "windows")] -pub struct D3D11SharedTexture { +pub struct D3D11Importer { pub handle: *mut c_void, + pub format: cef_color_type_t, pub width: u32, pub height: u32, - pub format: cef::sys::cef_color_type_t, } #[cfg(target_os = "windows")] -impl D3D11SharedTexture { - pub fn import_to_wgpu(&self, device: &wgpu::Device) -> Result { +impl TextureImporter for D3D11Importer { + fn import_to_wgpu(&self, device: &wgpu::Device) -> TextureImportResult { tracing::debug!("D3D11 shared texture import requested: {}x{} handle={:p}", self.width, self.height, self.handle); - // Try to import via Vulkan, fallback to CPU texture on failure + // Try hardware acceleration first #[cfg(feature = "accelerated_paint")] { - match self.import_via_vulkan(device) { - Ok(texture) => { - tracing::info!("Successfully imported D3D11 shared texture via Vulkan"); - return Ok(texture); - } - Err(e) => { - tracing::warn!("Failed to import D3D11 via Vulkan: {}, falling back to CPU texture", e); + if self.supports_hardware_acceleration(device) { + match self.import_via_vulkan(device) { + Ok(texture) => { + tracing::info!("Successfully imported D3D11 shared texture via Vulkan"); + return Ok(texture); + } + Err(e) => { + tracing::warn!("Failed to import D3D11 via Vulkan: {}, falling back to CPU texture", e); + } } } } - // Fallback: create empty CPU texture with same dimensions - self.create_fallback_texture(device) + // Fallback to CPU texture + texture::create_fallback(device, self.width, self.height, self.format, "CEF D3D11 Texture (fallback)") } - #[cfg(feature = "accelerated_paint")] - fn import_via_vulkan(&self, device: &wgpu::Device) -> Result { - // Validate handle - if self.handle.is_null() { - return Err("D3D11 shared texture handle is null".to_string()); + fn dimensions(&self) -> (u32, u32) { + (self.width, self.height) + } + + fn format(&self) -> cef_color_type_t { + self.format + } + + fn supports_hardware_acceleration(&self, device: &wgpu::Device) -> bool { + #[cfg(feature = "accelerated_paint")] + { + // Check if handle is valid + if self.handle.is_null() { + return false; + } + + // Check if wgpu is using Vulkan backend + vulkan::is_vulkan_backend(device) + } + #[cfg(not(feature = "accelerated_paint"))] + { + let _ = device; + false } + } + fn platform_name(&self) -> &'static str { + "Windows D3D11" + } +} + +#[cfg(target_os = "windows")] +impl D3D11Importer { + #[cfg(feature = "accelerated_paint")] + fn import_via_vulkan(&self, device: &wgpu::Device) -> TextureImportResult { // Get wgpu's Vulkan instance and device use wgpu::{TextureUses, wgc::api::Vulkan}; let hal_texture = unsafe { device.as_hal::(|device| { let Some(device) = device else { - return Err("Device is not using Vulkan backend".to_string()); + return Err(TextureImportError::HardwareUnavailable { + reason: "Device is not using Vulkan backend".to_string(), + }); }; // Import D3D11 shared handle into Vulkan - let vk_image = self - .import_d3d11_handle_to_vulkan(device) - .map_err(|e| format!("Failed to create Vulkan image from D3D11 handle: {}", e))?; + let vk_image = self.import_d3d11_handle_to_vulkan(device)?; // Wrap VkImage in wgpu-hal texture let hal_texture = ::Device::texture_from_raw( @@ -72,7 +105,7 @@ impl D3D11SharedTexture { mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, - format: self.cef_to_hal_format()?, + format: format::cef_to_wgpu(self.format)?, usage: TextureUses::COPY_DST | TextureUses::RESOURCE, memory_flags: wgpu::hal::MemoryFlags::empty(), view_formats: vec![], @@ -98,7 +131,7 @@ impl D3D11SharedTexture { mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, - format: self.cef_to_wgpu_format()?, + format: format::cef_to_wgpu(self.format)?, usage: wgpu::TextureUsages::TEXTURE_BINDING, view_formats: &[], }, @@ -109,14 +142,14 @@ impl D3D11SharedTexture { } #[cfg(feature = "accelerated_paint")] - fn import_d3d11_handle_to_vulkan(&self, hal_device: &::Device) -> Result { + fn import_d3d11_handle_to_vulkan(&self, hal_device: &::Device) -> Result { // Get raw Vulkan handles let device = hal_device.raw_device(); let _instance = hal_device.shared_instance().raw_instance(); // Validate dimensions if self.width == 0 || self.height == 0 { - return Err("Invalid D3D11 texture dimensions".to_string()); + return Err(TextureImportError::InvalidHandle("Invalid D3D11 texture dimensions".to_string())); } // Create external memory image info @@ -125,7 +158,7 @@ impl D3D11SharedTexture { // Create image create info let image_create_info = vk::ImageCreateInfo::default() .image_type(vk::ImageType::TYPE_2D) - .format(self.cef_to_vk_format()?) + .format(format::cef_to_vulkan(self.format)?) .extent(vk::Extent3D { width: self.width, height: self.height, @@ -140,7 +173,11 @@ impl D3D11SharedTexture { .push_next(&mut external_memory_info); // Create the image - let image = unsafe { device.create_image(&image_create_info, None).map_err(|e| format!("Failed to create Vulkan image: {:?}", e))? }; + let image = unsafe { + device.create_image(&image_create_info, None).map_err(|e| TextureImportError::VulkanError { + operation: format!("Failed to create Vulkan image: {:?}", e), + })? + }; // Get memory requirements let memory_requirements = unsafe { device.get_image_memory_requirements(image) }; @@ -154,7 +191,9 @@ impl D3D11SharedTexture { let memory_properties = unsafe { hal_device.shared_instance().raw_instance().get_physical_device_memory_properties(hal_device.raw_physical_device()) }; let memory_type_index = - find_memory_type_index(memory_requirements.memory_type_bits, vk::MemoryPropertyFlags::empty(), &memory_properties).ok_or("Failed to find suitable memory type for D3D11 texture")?; + vulkan::find_memory_type_index(memory_requirements.memory_type_bits, vk::MemoryPropertyFlags::empty(), &memory_properties).ok_or_else(|| TextureImportError::VulkanError { + operation: "Failed to find suitable memory type for D3D11 texture".to_string(), + })?; let allocate_info = vk::MemoryAllocateInfo::default() .allocation_size(memory_requirements.size) @@ -162,69 +201,18 @@ impl D3D11SharedTexture { .push_next(&mut import_memory_win32); let device_memory = unsafe { - device - .allocate_memory(&allocate_info, None) - .map_err(|e| format!("Failed to allocate memory for D3D11 texture: {:?}", e))? + device.allocate_memory(&allocate_info, None).map_err(|e| TextureImportError::VulkanError { + operation: format!("Failed to allocate memory for D3D11 texture: {:?}", e), + })? }; // Bind memory to image unsafe { - device.bind_image_memory(image, device_memory, 0).map_err(|e| format!("Failed to bind memory to image: {:?}", e))?; + device.bind_image_memory(image, device_memory, 0).map_err(|e| TextureImportError::VulkanError { + operation: format!("Failed to bind memory to image: {:?}", e), + })?; } Ok(image) } - - fn cef_to_vk_format(&self) -> Result { - use cef::sys::cef_color_type_t; - match self.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), - _ => Err(format!("Unsupported CEF format for Vulkan: {:?}", self.format)), - } - } - - fn cef_to_hal_format(&self) -> Result { - use cef::sys::cef_color_type_t; - match self.format { - cef_color_type_t::CEF_COLOR_TYPE_BGRA_8888 => Ok(wgpu::TextureFormat::Bgra8UnormSrgb), - cef_color_type_t::CEF_COLOR_TYPE_RGBA_8888 => Ok(wgpu::TextureFormat::Rgba8UnormSrgb), - _ => Err(format!("Unsupported CEF format for HAL: {:?}", self.format)), - } - } - - fn cef_to_wgpu_format(&self) -> Result { - self.cef_to_hal_format() - } - - fn create_fallback_texture(&self, device: &wgpu::Device) -> Result { - let format = self.cef_to_wgpu_format()?; - let texture = device.create_texture(&wgpu::TextureDescriptor { - label: Some("CEF D3D11 Texture (fallback)"), - 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, - usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, - view_formats: &[], - }); - - tracing::warn!("Using fallback CPU texture - D3D11 hardware acceleration not available"); - Ok(texture) - } -} - -#[cfg(all(feature = "accelerated_paint", target_os = "windows"))] -fn find_memory_type_index(type_filter: u32, properties: vk::MemoryPropertyFlags, mem_properties: &vk::PhysicalDeviceMemoryProperties) -> Option { - for i in 0..mem_properties.memory_type_count { - if (type_filter & (1 << i)) != 0 && mem_properties.memory_types[i as usize].property_flags.contains(properties) { - return Some(i); - } - } - None } diff --git a/desktop/src/cef/dmabuf.rs b/desktop/src/cef/texture_import/dmabuf.rs similarity index 50% rename from desktop/src/cef/dmabuf.rs rename to desktop/src/cef/texture_import/dmabuf.rs index 9b40413d1e..97ad97fdfe 100644 --- a/desktop/src/cef/dmabuf.rs +++ b/desktop/src/cef/texture_import/dmabuf.rs @@ -1,3 +1,5 @@ +//! Linux DMA-BUF texture import implementation + #[cfg(target_os = "linux")] use std::os::fd::RawFd; @@ -8,10 +10,13 @@ use wgpu::hal::api; #[cfg(all(feature = "accelerated_paint", target_os = "linux"))] extern crate libc; +use super::common::{TextureImportError, TextureImportResult, TextureImporter, format, texture, vulkan}; +use cef::sys::cef_color_type_t; + #[cfg(target_os = "linux")] -pub struct DmaBufTexture { +pub struct DmaBufImporter { pub fds: Vec, - pub format: cef::sys::cef_color_type_t, + pub format: cef_color_type_t, pub modifier: u64, pub width: u32, pub height: u32, @@ -20,8 +25,8 @@ pub struct DmaBufTexture { } #[cfg(target_os = "linux")] -impl DmaBufTexture { - pub fn import_to_wgpu(&self, device: &wgpu::Device) -> Result { +impl TextureImporter for DmaBufImporter { + fn import_to_wgpu(&self, device: &wgpu::Device) -> TextureImportResult { tracing::debug!( "DMA-BUF import requested: {}x{} format={:?} modifier={:#x} planes={}", self.width, @@ -31,69 +36,84 @@ impl DmaBufTexture { self.fds.len() ); - // Try to import via Vulkan, fallback to CPU texture on failure + // Try hardware acceleration first #[cfg(feature = "accelerated_paint")] { - match self.import_via_vulkan(device) { - Ok(texture) => { - tracing::info!("Successfully imported DMA-BUF texture via Vulkan"); - return Ok(texture); - } - Err(e) => { - tracing::warn!("Failed to import DMA-BUF via Vulkan: {}, falling back to CPU texture", e); + if self.supports_hardware_acceleration(device) { + match self.import_via_vulkan(device) { + Ok(texture) => { + tracing::info!("Successfully imported DMA-BUF texture via Vulkan"); + return Ok(texture); + } + Err(e) => { + tracing::warn!("Failed to import DMA-BUF via Vulkan: {}, falling back to CPU texture", e); + } } } } - // Fallback: create empty CPU texture with same dimensions - let format = drm_format_to_wgpu(self.format)?; - let texture = device.create_texture(&wgpu::TextureDescriptor { - label: Some("CEF DMA-BUF Texture (fallback)"), - 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, - usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, - view_formats: &[], - }); - - tracing::warn!("Using fallback CPU texture - DMA-BUF hardware acceleration not available"); - Ok(texture) + // Fallback to CPU texture + texture::create_fallback(device, self.width, self.height, self.format, "CEF DMA-BUF Texture (fallback)") } - #[cfg(feature = "accelerated_paint")] - fn import_via_vulkan(&self, device: &wgpu::Device) -> Result { - // Validate file descriptors before proceeding - if self.fds.is_empty() { - return Err("No DMA-BUF file descriptors provided".to_string()); - } + fn dimensions(&self) -> (u32, u32) { + (self.width, self.height) + } - for &fd in &self.fds { - if fd < 0 { - return Err(format!("Invalid file descriptor: {}", fd)); + fn format(&self) -> cef_color_type_t { + self.format + } + + fn supports_hardware_acceleration(&self, device: &wgpu::Device) -> bool { + #[cfg(feature = "accelerated_paint")] + { + // Check if we have valid file descriptors + if self.fds.is_empty() { + return false; } - // Check if file descriptor is valid by testing with fcntl - let flags = unsafe { libc::fcntl(fd, libc::F_GETFD) }; - if flags == -1 { - return Err(format!("File descriptor {} is not valid", fd)); + + for &fd in &self.fds { + if fd < 0 { + return false; + } + // Check if file descriptor is valid + let flags = unsafe { libc::fcntl(fd, libc::F_GETFD) }; + if flags == -1 { + return false; + } } + + // Check if wgpu is using Vulkan backend + vulkan::is_vulkan_backend(device) } + #[cfg(not(feature = "accelerated_paint"))] + { + let _ = device; + false + } + } + + fn platform_name(&self) -> &'static str { + "Linux DMA-BUF" + } +} +#[cfg(target_os = "linux")] +impl DmaBufImporter { + #[cfg(feature = "accelerated_paint")] + fn import_via_vulkan(&self, device: &wgpu::Device) -> TextureImportResult { // Get wgpu's Vulkan instance and device use wgpu::{TextureUses, wgc::api::Vulkan}; let hal_texture = unsafe { device.as_hal::(|device| { let Some(device) = device else { - return Err("Device is not using Vulkan backend".to_string()); + return Err(TextureImportError::HardwareUnavailable { + reason: "Device is not using Vulkan backend".to_string(), + }); }; // Create VkImage from DMA-BUF using external memory - let vk_image = self.create_vulkan_image_from_dmabuf(device).map_err(|e| format!("Failed to create Vulkan image from DMA-BUF: {}", e))?; + let vk_image = self.create_vulkan_image_from_dmabuf(device)?; // Wrap VkImage in wgpu-hal texture let hal_texture = ::Device::texture_from_raw( @@ -108,7 +128,7 @@ impl DmaBufTexture { mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, - format: cef_to_hal_format(self.format)?, + format: format::cef_to_wgpu(self.format)?, usage: TextureUses::COPY_DST | TextureUses::RESOURCE, memory_flags: wgpu::hal::MemoryFlags::empty(), view_formats: vec![], @@ -134,7 +154,7 @@ impl DmaBufTexture { mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, - format: drm_format_to_wgpu(self.format)?, + format: format::cef_to_wgpu(self.format)?, usage: wgpu::TextureUsages::TEXTURE_BINDING, view_formats: &[], }, @@ -145,20 +165,20 @@ impl DmaBufTexture { } #[cfg(feature = "accelerated_paint")] - fn create_vulkan_image_from_dmabuf(&self, hal_device: &::Device) -> Result { + fn create_vulkan_image_from_dmabuf(&self, hal_device: &::Device) -> Result { // Get raw Vulkan handles let device = hal_device.raw_device(); let _instance = hal_device.shared_instance().raw_instance(); // Validate dimensions if self.width == 0 || self.height == 0 { - return Err("Invalid DMA-BUF dimensions".to_string()); + return Err(TextureImportError::InvalidHandle("Invalid DMA-BUF dimensions".to_string())); } // Create external memory image let image_create_info = vk::ImageCreateInfo::default() .image_type(vk::ImageType::TYPE_2D) - .format(cef_to_vk_format(self.format)?) + .format(format::cef_to_vulkan(self.format)?) .extent(vk::Extent3D { width: self.width, height: self.height, @@ -180,42 +200,56 @@ impl DmaBufTexture { let image_create_info = image_create_info.push_next(&mut drm_format_modifier); // Create the image - let image = unsafe { device.create_image(&image_create_info, None).map_err(|e| format!("Failed to create Vulkan image: {:?}", e))? }; + let image = unsafe { + device.create_image(&image_create_info, None).map_err(|e| TextureImportError::VulkanError { + operation: format!("Failed to create Vulkan image: {:?}", e), + })? + }; // Import memory from DMA-BUF let memory_requirements = unsafe { device.get_image_memory_requirements(image) }; // Duplicate the file descriptor to avoid ownership issues - // CEF owns the original FDs and will close them when the AcceleratedPaintInfo is destroyed let dup_fd = unsafe { libc::dup(self.fds[0]) }; if dup_fd == -1 { - return Err("Failed to duplicate DMA-BUF file descriptor".to_string()); + return Err(TextureImportError::PlatformError { + message: "Failed to duplicate DMA-BUF file descriptor".to_string(), + }); } let mut import_memory_fd = vk::ImportMemoryFdInfoKHR::default().handle_type(vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT).fd(dup_fd); // Find a suitable memory type let memory_properties = unsafe { hal_device.shared_instance().raw_instance().get_physical_device_memory_properties(hal_device.raw_physical_device()) }; + let memory_type_index = - find_memory_type_index(memory_requirements.memory_type_bits, vk::MemoryPropertyFlags::empty(), &memory_properties).ok_or("Failed to find suitable memory type for DMA-BUF")?; + vulkan::find_memory_type_index(memory_requirements.memory_type_bits, vk::MemoryPropertyFlags::empty(), &memory_properties).ok_or_else(|| TextureImportError::VulkanError { + operation: "Failed to find suitable memory type for DMA-BUF".to_string(), + })?; let allocate_info = vk::MemoryAllocateInfo::default() .allocation_size(memory_requirements.size) .memory_type_index(memory_type_index) .push_next(&mut import_memory_fd); - let device_memory = unsafe { device.allocate_memory(&allocate_info, None).map_err(|e| format!("Failed to allocate memory for DMA-BUF: {:?}", e))? }; + let device_memory = unsafe { + device.allocate_memory(&allocate_info, None).map_err(|e| TextureImportError::VulkanError { + operation: format!("Failed to allocate memory for DMA-BUF: {:?}", e), + })? + }; // Bind memory to image unsafe { - device.bind_image_memory(image, device_memory, 0).map_err(|e| format!("Failed to bind memory to image: {:?}", e))?; + device.bind_image_memory(image, device_memory, 0).map_err(|e| TextureImportError::VulkanError { + operation: format!("Failed to bind memory to image: {:?}", e), + })?; } Ok(image) } #[cfg(feature = "accelerated_paint")] - fn create_subresource_layouts(&self) -> Result, String> { + fn create_subresource_layouts(&self) -> Result, TextureImportError> { let mut layouts = Vec::new(); for i in 0..self.fds.len() { @@ -231,45 +265,3 @@ impl DmaBufTexture { Ok(layouts) } } - -#[cfg(target_os = "linux")] -fn drm_format_to_wgpu(drm_format: cef::sys::cef_color_type_t) -> Result { - // Based on OBS's drm-format.cpp - - use cef::sys::cef_color_type_t; - match drm_format { - cef_color_type_t::CEF_COLOR_TYPE_BGRA_8888 => Ok(wgpu::TextureFormat::Bgra8UnormSrgb), // DRM_FORMAT_ARGB8888 - cef_color_type_t::CEF_COLOR_TYPE_RGBA_8888 => Ok(wgpu::TextureFormat::Rgba8UnormSrgb), // DRM_FORMAT_ABGR8888 - _ => Err(format!("Unsupported DRM format: {:?}", drm_format)), - } -} - -#[cfg(all(feature = "accelerated_paint", target_os = "linux"))] -fn cef_to_vk_format(cef_format: cef::sys::cef_color_type_t) -> Result { - use cef::sys::cef_color_type_t; - match cef_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), - _ => Err(format!("Unsupported CEF format for Vulkan: {:?}", cef_format)), - } -} - -#[cfg(all(feature = "accelerated_paint", target_os = "linux"))] -fn cef_to_hal_format(cef_format: cef::sys::cef_color_type_t) -> Result { - use cef::sys::cef_color_type_t; - match cef_format { - cef_color_type_t::CEF_COLOR_TYPE_BGRA_8888 => Ok(wgpu::TextureFormat::Bgra8UnormSrgb), - cef_color_type_t::CEF_COLOR_TYPE_RGBA_8888 => Ok(wgpu::TextureFormat::Rgba8UnormSrgb), - _ => Err(format!("Unsupported CEF format for HAL: {:?}", cef_format)), - } -} - -#[cfg(all(feature = "accelerated_paint", target_os = "linux"))] -fn find_memory_type_index(type_filter: u32, properties: vk::MemoryPropertyFlags, mem_properties: &vk::PhysicalDeviceMemoryProperties) -> Option { - for i in 0..mem_properties.memory_type_count { - if (type_filter & (1 << i)) != 0 && mem_properties.memory_types[i as usize].property_flags.contains(properties) { - return Some(i); - } - } - None -} diff --git a/desktop/src/cef/texture_import/iosurface.rs b/desktop/src/cef/texture_import/iosurface.rs new file mode 100644 index 0000000000..3bd892ff28 --- /dev/null +++ b/desktop/src/cef/texture_import/iosurface.rs @@ -0,0 +1,209 @@ +//! macOS IOSurface texture import implementation + +#[cfg(target_os = "macos")] +use std::os::raw::c_void; + +#[cfg(all(feature = "accelerated_paint", target_os = "macos"))] +use ash::vk; +#[cfg(all(feature = "accelerated_paint", target_os = "macos"))] +use core_foundation::base::{CFType, TCFType}; +#[cfg(all(feature = "accelerated_paint", target_os = "macos"))] +use objc2_io_surface::{IOSurface, IOSurfaceRef}; +#[cfg(all(feature = "accelerated_paint", target_os = "macos"))] +use wgpu::hal::api; + +use super::common::{TextureImportError, TextureImportResult, TextureImporter, format, texture, vulkan}; +use cef::sys::cef_color_type_t; + +#[cfg(target_os = "macos")] +pub struct IOSurfaceImporter { + pub handle: *mut c_void, + pub format: cef_color_type_t, + pub width: u32, + pub height: u32, +} + +#[cfg(target_os = "macos")] +impl TextureImporter for IOSurfaceImporter { + fn import_to_wgpu(&self, device: &wgpu::Device) -> TextureImportResult { + tracing::debug!("IOSurface texture import requested: {}x{} handle={:p}", self.width, self.height, self.handle); + + // Try hardware acceleration first + #[cfg(feature = "accelerated_paint")] + { + if self.supports_hardware_acceleration(device) { + match self.import_via_vulkan(device) { + Ok(texture) => { + tracing::trace!("Successfully imported IOSurface texture via Vulkan"); + return Ok(texture); + } + Err(e) => { + tracing::warn!("Failed to import IOSurface via Vulkan: {}, falling back to CPU texture", e); + } + } + } + } + + // Fallback to CPU texture + texture::create_fallback(device, self.width, self.height, self.format, "CEF IOSurface Texture (fallback)") + } + + fn dimensions(&self) -> (u32, u32) { + (self.width, self.height) + } + + fn format(&self) -> cef_color_type_t { + self.format + } + + fn supports_hardware_acceleration(&self, device: &wgpu::Device) -> bool { + #[cfg(feature = "accelerated_paint")] + { + // Check if handle is valid + if self.handle.is_null() { + return false; + } + + // Check if wgpu is using Vulkan backend + vulkan::is_vulkan_backend(device) + } + #[cfg(not(feature = "accelerated_paint"))] + { + let _ = device; + false + } + } + + fn platform_name(&self) -> &'static str { + "macOS IOSurface" + } +} + +#[cfg(target_os = "macos")] +impl IOSurfaceImporter { + #[cfg(feature = "accelerated_paint")] + fn import_via_vulkan(&self, device: &wgpu::Device) -> TextureImportResult { + // Get wgpu's Vulkan instance and device + use wgpu::{TextureUses, wgc::api::Vulkan}; + let hal_texture = unsafe { + device.as_hal::(|device| { + let Some(device) = device else { + return Err(TextureImportError::HardwareUnavailable { + reason: "Device is not using Vulkan backend".to_string(), + }); + }; + + // Import IOSurface handle into Vulkan via Metal + let vk_image = self.import_iosurface_to_vulkan(device)?; + + // Wrap VkImage in wgpu-hal texture + let hal_texture = ::Device::texture_from_raw( + vk_image, + &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: TextureUses::COPY_DST | TextureUses::RESOURCE, + memory_flags: wgpu::hal::MemoryFlags::empty(), + view_formats: vec![], + }, + None, // drop_callback + ); + + Ok(hal_texture) + }) + }?; + + // Import hal texture into wgpu + let texture = unsafe { + device.create_texture_from_hal::( + hal_texture, + &wgpu::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::TextureUsages::TEXTURE_BINDING, + view_formats: &[], + }, + ) + }; + + Ok(texture) + } + + #[cfg(feature = "accelerated_paint")] + fn import_iosurface_to_vulkan(&self, hal_device: &::Device) -> Result { + // Get raw Vulkan handles + let device = hal_device.raw_device(); + let _instance = hal_device.shared_instance().raw_instance(); + + // 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) + }; + + // Note: Full Metal-to-Vulkan import would require: + // 1. Creating Metal texture from IOSurface + // 2. Using VK_EXT_metal_objects to import Metal texture into Vulkan + // 3. Proper synchronization between Metal and Vulkan + // + // This is complex and not fully supported by current objc2 bindings. + // For now, we create a minimal Vulkan image and rely on fallback. + + // Create external memory image info for Metal objects + let mut external_memory_info = vk::ExternalMemoryImageCreateInfo::default().handle_types(vk::ExternalMemoryHandleTypeFlags::MTLTEXTURE_EXT); + + // Create image create info + let image_create_info = vk::ImageCreateInfo::default() + .image_type(vk::ImageType::TYPE_2D) + .format(format::cef_to_vulkan(self.format)?) + .extent(vk::Extent3D { + width: self.width, + height: self.height, + depth: 1, + }) + .mip_levels(1) + .array_layers(1) + .samples(vk::SampleCountFlags::TYPE_1) + .tiling(vk::ImageTiling::OPTIMAL) + .usage(vk::ImageUsageFlags::SAMPLED | vk::ImageUsageFlags::COLOR_ATTACHMENT) + .sharing_mode(vk::SharingMode::EXCLUSIVE) + .push_next(&mut external_memory_info); + + // Create the image + let image = unsafe { + device.create_image(&image_create_info, None).map_err(|e| TextureImportError::VulkanError { + operation: format!("Failed to create Vulkan image: {:?}", e), + })? + }; + + // Note: The actual Metal-to-Vulkan import would require VK_EXT_metal_objects + // and proper Metal texture handle extraction, which is complex and not + // fully supported in the current objc2 bindings. For now, we return the + // image and rely on fallback behavior. + + tracing::warn!("Metal-to-Vulkan texture import not fully implemented"); + + Ok(image) + } +} diff --git a/desktop/src/cef/texture_import/mod.rs b/desktop/src/cef/texture_import/mod.rs new file mode 100644 index 0000000000..2811f9714e --- /dev/null +++ b/desktop/src/cef/texture_import/mod.rs @@ -0,0 +1,41 @@ +//! Unified texture import system for CEF hardware acceleration +//! +//! This module provides a platform-agnostic interface for importing shared textures +//! from CEF into wgpu, with automatic fallback to CPU textures when hardware +//! acceleration is not available. +//! +//! # Supported Platforms +//! +//! - **Linux**: DMA-BUF via Vulkan external memory +//! - **Windows**: D3D11 shared textures via Vulkan interop +//! - **macOS**: IOSurface via Metal/Vulkan interop +//! +//! # Usage +//! +//! ```rust +//! use crate::cef::texture_import; +//! +//! // Import texture with automatic platform detection +//! let texture = texture_import::import_texture(shared_handle, &device)?; +//! ``` +//! +//! # Features +//! +//! - `accelerated_paint` - Base feature for texture import +//! - `accelerated_paint_dmabuf` - Linux DMA-BUF support +//! - `accelerated_paint_d3d11` - Windows D3D11 support +//! - `accelerated_paint_iosurface` - macOS IOSurface support + +pub mod common; + +#[cfg(target_os = "linux")] +pub mod dmabuf; + +#[cfg(target_os = "windows")] +pub mod d3d11; + +#[cfg(target_os = "macos")] +pub mod iosurface; + +// Re-export commonly used types +pub use common::{TextureImportError, import_texture}; diff --git a/desktop/src/main.rs b/desktop/src/main.rs index 7d61dd7e2e..2a0d2cdefc 100644 --- a/desktop/src/main.rs +++ b/desktop/src/main.rs @@ -1,5 +1,6 @@ use std::process::exit; -use std::time::{Duration, Instant}; +use std::time::Instant; + use tracing_subscriber::EnvFilter; use winit::event_loop::EventLoop; @@ -56,23 +57,6 @@ fn main() { tracing::info!("Cef initialized successfully"); - let rendering_loop_proxy = event_loop.create_proxy(); - let target_fps = 120; - std::thread::spawn(move || { - loop { - let last_render = Instant::now(); - - let result = futures::executor::block_on(DesktopWrapper::execute_node_graph()); - let _ = rendering_loop_proxy.send_event(CustomEvent::NodeGraphExecutionResult(result)); - - let frame_time = Duration::from_secs_f32((target_fps as f32).recip()); - let sleep = last_render + frame_time - Instant::now(); - - let _ = rendering_loop_proxy.send_event(CustomEvent::ScheduleBrowserWork(Instant::now())); - std::thread::sleep(sleep); - } - }); - let mut winit_app = WinitApp::new(cef_context, window_size_sender, wgpu_context, event_loop.create_proxy()); event_loop.run_app(&mut winit_app).unwrap(); diff --git a/desktop/src/render/graphics_state.rs b/desktop/src/render/graphics_state.rs index b93ec7a829..fdec45a609 100644 --- a/desktop/src/render/graphics_state.rs +++ b/desktop/src/render/graphics_state.rs @@ -232,7 +232,7 @@ impl GraphicsState { self.bind_overlays_texture(texture); } - pub(crate) fn render(&mut self) -> Result<(), wgpu::SurfaceError> { + pub(crate) fn render(&mut self, window: &Window) -> Result<(), wgpu::SurfaceError> { if let Some(scene) = self.overlays_scene.take() { self.render_overlays(scene); } @@ -275,6 +275,7 @@ impl GraphicsState { } } self.context.queue.submit(std::iter::once(encoder.finish())); + window.pre_present_notify(); output.present(); Ok(()) From 5a299adf512f109aa113cc4a6d1d134219a4e8ac Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Thu, 21 Aug 2025 13:54:11 +0000 Subject: [PATCH 11/14] Add path for importing textures on windows through dx12 --- desktop/Cargo.toml | 9 +- desktop/src/cef/texture_import/common.rs | 25 ++-- desktop/src/cef/texture_import/d3d11.rs | 138 ++++++++++++++++---- desktop/src/cef/texture_import/dmabuf.rs | 21 --- desktop/src/cef/texture_import/iosurface.rs | 14 -- desktop/src/cef/texture_import/mod.rs | 6 +- 6 files changed, 139 insertions(+), 74 deletions(-) diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml index 5c0a0a53ca..6962ea7a24 100644 --- a/desktop/Cargo.toml +++ b/desktop/Cargo.toml @@ -11,8 +11,12 @@ rust-version = "1.87" [features] default = ["gpu", "accelerated_paint"] gpu = ["graphite-desktop-wrapper/gpu"] -accelerated_paint = ["ash", "libc", "windows"] -accelerated_paint = ["ash", "libc", "windows", "objc2-io-surface", "objc2-metal", "core-foundation"] + +# Hardware acceleration features +accelerated_paint = ["ash", "accelerated_paint_dmabuf", "accelerated_paint_d3d11", "accelerated_paint_iosurface"] +accelerated_paint_dmabuf = ["libc"] +accelerated_paint_d3d11 = ["windows"] +accelerated_paint_iosurface = ["objc2-io-surface", "objc2-metal", "core-foundation"] [dependencies] # # Local dependencies @@ -42,6 +46,7 @@ ash = { version = "0.38", optional = true } [target.'cfg(windows)'.dependencies] windows = { version = "0.58", features = [ "Win32_Graphics_Direct3D11", + "Win32_Graphics_Direct3D12", "Win32_Graphics_Dxgi", "Win32_Graphics_Dxgi_Common", "Win32_Foundation" diff --git a/desktop/src/cef/texture_import/common.rs b/desktop/src/cef/texture_import/common.rs index 12f4d7619c..29156eca4c 100644 --- a/desktop/src/cef/texture_import/common.rs +++ b/desktop/src/cef/texture_import/common.rs @@ -26,9 +26,6 @@ pub enum TextureImportError { #[error("Platform-specific error: {message}")] PlatformError { message: String }, - - #[error("Texture creation failed: {reason}")] - TextureCreationFailed { reason: String }, } /// Trait for platform-specific texture importers @@ -36,17 +33,8 @@ pub trait TextureImporter { /// Import the texture into wgpu, with automatic fallback to CPU texture fn import_to_wgpu(&self, device: &Device) -> TextureImportResult; - /// Get texture dimensions - fn dimensions(&self) -> (u32, u32); - - /// Get texture format - fn format(&self) -> cef_color_type_t; - /// Check if hardware acceleration is available for this texture fn supports_hardware_acceleration(&self, device: &Device) -> bool; - - /// Platform name for logging purposes - fn platform_name(&self) -> &'static str; } /// Common format conversion utilities @@ -127,6 +115,19 @@ pub mod vulkan { } is_vulkan } + + /// Check if the wgpu device is using D3D12 backend + #[cfg(target_os = "windows")] + pub fn is_d3d12_backend(device: &Device) -> bool { + use wgpu::hal::api; + let mut is_d3d12 = false; + unsafe { + device.as_hal::(|device| { + is_d3d12 = device.is_some(); + }); + } + is_d3d12 + } } /// Import a texture using the appropriate platform-specific importer diff --git a/desktop/src/cef/texture_import/d3d11.rs b/desktop/src/cef/texture_import/d3d11.rs index f68adeac39..14ea7825eb 100644 --- a/desktop/src/cef/texture_import/d3d11.rs +++ b/desktop/src/cef/texture_import/d3d11.rs @@ -22,19 +22,33 @@ pub struct D3D11Importer { #[cfg(target_os = "windows")] impl TextureImporter for D3D11Importer { fn import_to_wgpu(&self, device: &wgpu::Device) -> TextureImportResult { - tracing::debug!("D3D11 shared texture import requested: {}x{} handle={:p}", self.width, self.height, self.handle); - // Try hardware acceleration first #[cfg(feature = "accelerated_paint")] { if self.supports_hardware_acceleration(device) { - match self.import_via_vulkan(device) { - Ok(texture) => { - tracing::info!("Successfully imported D3D11 shared texture via Vulkan"); - return Ok(texture); + // Try D3D12 first (most efficient on Windows) + if vulkan::is_d3d12_backend(device) { + match self.import_via_d3d12(device) { + Ok(texture) => { + tracing::info!("Successfully imported D3D11 shared texture via D3D12"); + return Ok(texture); + } + Err(e) => { + tracing::warn!("Failed to import D3D11 via D3D12: {}, trying Vulkan fallback", e); + } } - Err(e) => { - tracing::warn!("Failed to import D3D11 via Vulkan: {}, falling back to CPU texture", e); + } + + // Try Vulkan as fallback + if vulkan::is_vulkan_backend(device) { + match self.import_via_vulkan(device) { + Ok(texture) => { + tracing::info!("Successfully imported D3D11 shared texture via Vulkan"); + return Ok(texture); + } + Err(e) => { + tracing::warn!("Failed to import D3D11 via Vulkan: {}, falling back to CPU texture", e); + } } } } @@ -44,14 +58,6 @@ impl TextureImporter for D3D11Importer { texture::create_fallback(device, self.width, self.height, self.format, "CEF D3D11 Texture (fallback)") } - fn dimensions(&self) -> (u32, u32) { - (self.width, self.height) - } - - fn format(&self) -> cef_color_type_t { - self.format - } - fn supports_hardware_acceleration(&self, device: &wgpu::Device) -> bool { #[cfg(feature = "accelerated_paint")] { @@ -60,8 +66,8 @@ impl TextureImporter for D3D11Importer { return false; } - // Check if wgpu is using Vulkan backend - vulkan::is_vulkan_backend(device) + // Check if wgpu is using D3D12 or Vulkan backend + vulkan::is_d3d12_backend(device) || vulkan::is_vulkan_backend(device) } #[cfg(not(feature = "accelerated_paint"))] { @@ -69,14 +75,74 @@ impl TextureImporter for D3D11Importer { false } } - - fn platform_name(&self) -> &'static str { - "Windows D3D11" - } } #[cfg(target_os = "windows")] impl D3D11Importer { + #[cfg(feature = "accelerated_paint")] + fn import_via_d3d12(&self, device: &wgpu::Device) -> TextureImportResult { + // Get wgpu's D3D12 device + use wgpu::hal::api; + let hal_texture = unsafe { + device.as_hal::(|device| { + let Some(device) = device else { + return Err(TextureImportError::HardwareUnavailable { + reason: "Device is not using D3D12 backend".to_string(), + }); + }; + + // Import D3D11 shared handle directly into D3D12 resource + let d3d12_resource = self.import_d3d11_handle_to_d3d12(device)?; + + // Wrap D3D12 resource in wgpu-hal texture + let hal_texture = ::Device::texture_from_raw( + d3d12_resource, + &wgpu::hal::TextureDescriptor { + label: Some("CEF D3D11→D3D12 Shared 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::TextureUses::COPY_DST | wgpu::TextureUses::RESOURCE, + memory_flags: wgpu::hal::MemoryFlags::empty(), + view_formats: vec![], + }, + None, // drop_callback + ); + + Ok(hal_texture) + }) + }?; + + // Import hal texture into wgpu + let texture = unsafe { + device.create_texture_from_hal::( + hal_texture, + &wgpu::TextureDescriptor { + label: Some("CEF D3D11→D3D12 Shared 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::TextureUsages::TEXTURE_BINDING, + view_formats: &[], + }, + ) + }; + + Ok(texture) + } + #[cfg(feature = "accelerated_paint")] fn import_via_vulkan(&self, device: &wgpu::Device) -> TextureImportResult { // Get wgpu's Vulkan instance and device @@ -215,4 +281,32 @@ impl D3D11Importer { Ok(image) } + + #[cfg(feature = "accelerated_paint")] + fn import_d3d11_handle_to_d3d12(&self, hal_device: &::Device) -> Result { + use windows::Win32::Graphics::Direct3D12::*; + use windows::core::*; + + // Get D3D12 device from wgpu-hal + let d3d12_device = hal_device.raw_device(); + + // Validate dimensions + if self.width == 0 || self.height == 0 { + return Err(TextureImportError::InvalidHandle("Invalid D3D11 texture dimensions".to_string())); + } + + // Open D3D11 shared handle on D3D12 device + unsafe { + let mut shared_resource: Option = None; + d3d12_device.OpenSharedHandle( + windows::Win32::Foundation::HANDLE(self.handle as isize), + &ID3D12Resource::IID, + &mut shared_resource as *mut _ as *mut _, + ).map_err(|e| TextureImportError::PlatformError { + message: format!("Failed to open D3D11 shared handle on D3D12: {:?}", e), + })?; + + shared_resource.ok_or_else(|| TextureImportError::InvalidHandle("Failed to get D3D12 resource from shared handle".to_string())) + } + } } diff --git a/desktop/src/cef/texture_import/dmabuf.rs b/desktop/src/cef/texture_import/dmabuf.rs index 97ad97fdfe..08f523b3af 100644 --- a/desktop/src/cef/texture_import/dmabuf.rs +++ b/desktop/src/cef/texture_import/dmabuf.rs @@ -27,15 +27,6 @@ pub struct DmaBufImporter { #[cfg(target_os = "linux")] impl TextureImporter for DmaBufImporter { fn import_to_wgpu(&self, device: &wgpu::Device) -> TextureImportResult { - tracing::debug!( - "DMA-BUF import requested: {}x{} format={:?} modifier={:#x} planes={}", - self.width, - self.height, - self.format, - self.modifier, - self.fds.len() - ); - // Try hardware acceleration first #[cfg(feature = "accelerated_paint")] { @@ -56,14 +47,6 @@ impl TextureImporter for DmaBufImporter { texture::create_fallback(device, self.width, self.height, self.format, "CEF DMA-BUF Texture (fallback)") } - fn dimensions(&self) -> (u32, u32) { - (self.width, self.height) - } - - fn format(&self) -> cef_color_type_t { - self.format - } - fn supports_hardware_acceleration(&self, device: &wgpu::Device) -> bool { #[cfg(feature = "accelerated_paint")] { @@ -92,10 +75,6 @@ impl TextureImporter for DmaBufImporter { false } } - - fn platform_name(&self) -> &'static str { - "Linux DMA-BUF" - } } #[cfg(target_os = "linux")] diff --git a/desktop/src/cef/texture_import/iosurface.rs b/desktop/src/cef/texture_import/iosurface.rs index 3bd892ff28..7567b09400 100644 --- a/desktop/src/cef/texture_import/iosurface.rs +++ b/desktop/src/cef/texture_import/iosurface.rs @@ -26,8 +26,6 @@ pub struct IOSurfaceImporter { #[cfg(target_os = "macos")] impl TextureImporter for IOSurfaceImporter { fn import_to_wgpu(&self, device: &wgpu::Device) -> TextureImportResult { - tracing::debug!("IOSurface texture import requested: {}x{} handle={:p}", self.width, self.height, self.handle); - // Try hardware acceleration first #[cfg(feature = "accelerated_paint")] { @@ -48,14 +46,6 @@ impl TextureImporter for IOSurfaceImporter { texture::create_fallback(device, self.width, self.height, self.format, "CEF IOSurface Texture (fallback)") } - fn dimensions(&self) -> (u32, u32) { - (self.width, self.height) - } - - fn format(&self) -> cef_color_type_t { - self.format - } - fn supports_hardware_acceleration(&self, device: &wgpu::Device) -> bool { #[cfg(feature = "accelerated_paint")] { @@ -73,10 +63,6 @@ impl TextureImporter for IOSurfaceImporter { false } } - - fn platform_name(&self) -> &'static str { - "macOS IOSurface" - } } #[cfg(target_os = "macos")] diff --git a/desktop/src/cef/texture_import/mod.rs b/desktop/src/cef/texture_import/mod.rs index 2811f9714e..dc61a65ca5 100644 --- a/desktop/src/cef/texture_import/mod.rs +++ b/desktop/src/cef/texture_import/mod.rs @@ -7,7 +7,7 @@ //! # Supported Platforms //! //! - **Linux**: DMA-BUF via Vulkan external memory -//! - **Windows**: D3D11 shared textures via Vulkan interop +//! - **Windows**: D3D11 shared textures via Vulkan interop //! - **macOS**: IOSurface via Metal/Vulkan interop //! //! # Usage @@ -22,7 +22,7 @@ //! # Features //! //! - `accelerated_paint` - Base feature for texture import -//! - `accelerated_paint_dmabuf` - Linux DMA-BUF support +//! - `accelerated_paint_dmabuf` - Linux DMA-BUF support //! - `accelerated_paint_d3d11` - Windows D3D11 support //! - `accelerated_paint_iosurface` - macOS IOSurface support @@ -38,4 +38,4 @@ pub mod d3d11; pub mod iosurface; // Re-export commonly used types -pub use common::{TextureImportError, import_texture}; +pub use common::import_texture; From 87d681f8ed5a6364432280fa85b00d350f2198bd Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Thu, 21 Aug 2025 13:54:11 +0000 Subject: [PATCH 12/14] Update doc comment --- desktop/src/cef.rs | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/desktop/src/cef.rs b/desktop/src/cef.rs index 6c4d940b27..8de55f1367 100644 --- a/desktop/src/cef.rs +++ b/desktop/src/cef.rs @@ -7,24 +7,9 @@ //! The texture import system supports platform-specific hardware acceleration: //! //! - **Linux**: DMA-BUF via Vulkan external memory (`accelerated_paint_dmabuf` feature) -//! - **Windows**: D3D11 shared textures via Vulkan interop (`accelerated_paint_d3d11` feature) +//! - **Windows**: D3D11 shared textures via either Vulkan or D3D12 interop (`accelerated_paint_d3d11` feature) //! - **macOS**: IOSurface via Metal/Vulkan interop (`accelerated_paint_iosurface` feature) //! -//! ## Feature Configuration -//! -//! Enable hardware acceleration with: -//! ```toml -//! [dependencies] -//! graphite-desktop = { features = ["accelerated_paint"] } -//! ``` -//! -//! Or enable platform-specific support: -//! ```toml -//! [dependencies] -//! graphite-desktop = { features = ["accelerated_paint_linux"] } # Linux only -//! graphite-desktop = { features = ["accelerated_paint_windows"] } # Windows only -//! graphite-desktop = { features = ["accelerated_paint_macos"] } # macOS only -//! ``` //! //! The system gracefully falls back to CPU textures when hardware acceleration is unavailable. From 27576cc86de120aae5cf459ac9af90e8f7513f57 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Thu, 21 Aug 2025 13:54:11 +0000 Subject: [PATCH 13/14] Import textures through metal on macos --- desktop/Cargo.toml | 6 +- desktop/src/app.rs | 5 +- desktop/src/cef/context.rs | 3 +- desktop/src/cef/texture_import/common.rs | 10 +- desktop/src/cef/texture_import/d3d11.rs | 14 +-- desktop/src/cef/texture_import/iosurface.rs | 132 ++++++++++---------- desktop/src/cef/texture_import/mod.rs | 2 +- desktop/src/consts.rs | 4 + desktop/src/main.rs | 2 +- 9 files changed, 94 insertions(+), 84 deletions(-) diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml index 6962ea7a24..1ddf52622e 100644 --- a/desktop/Cargo.toml +++ b/desktop/Cargo.toml @@ -13,9 +13,9 @@ default = ["gpu", "accelerated_paint"] gpu = ["graphite-desktop-wrapper/gpu"] # Hardware acceleration features -accelerated_paint = ["ash", "accelerated_paint_dmabuf", "accelerated_paint_d3d11", "accelerated_paint_iosurface"] -accelerated_paint_dmabuf = ["libc"] -accelerated_paint_d3d11 = ["windows"] +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"] [dependencies] diff --git a/desktop/src/app.rs b/desktop/src/app.rs index 0156de6165..59912fc972 100644 --- a/desktop/src/app.rs +++ b/desktop/src/app.rs @@ -1,9 +1,10 @@ use crate::CustomEvent; use crate::cef::WindowSize; -use crate::consts::APP_NAME; +use crate::consts::{APP_NAME, CEF_MESSAGE_LOOP_MAX_ITERATIONS}; use crate::render::GraphicsState; use graphite_desktop_wrapper::messages::{DesktopFrontendMessage, DesktopWrapperMessage}; use graphite_desktop_wrapper::{DesktopWrapper, NodeGraphExecutionResult, WgpuContext, serialize_frontend_messages}; + use rfd::AsyncFileDialog; use std::sync::Arc; use std::sync::mpsc::Sender; @@ -174,7 +175,7 @@ impl ApplicationHandler for WinitApp { { self.cef_schedule = None; // Poll cef message loop multiple times to avoid message loop starvation - for _ in 0..10 { + for _ in 0..CEF_MESSAGE_LOOP_MAX_ITERATIONS { self.cef_context.work(); } } diff --git a/desktop/src/cef/context.rs b/desktop/src/cef/context.rs index 79ad57e374..b50e4f913f 100644 --- a/desktop/src/cef/context.rs +++ b/desktop/src/cef/context.rs @@ -86,7 +86,6 @@ impl Context { let mut client = Client::new(BrowserProcessClientImpl::new(render_handler, event_handler.clone())); let url = CefString::from(format!("{GRAPHITE_SCHEME}://{FRONTEND_DOMAIN}/").as_str()); - // let url = CefString::from(format!("chrome://gpu").as_str()); let window_info = WindowInfo { windowless_rendering_enabled: 1, @@ -96,7 +95,7 @@ impl Context { }; let settings = BrowserSettings { - windowless_frame_rate: 120, + windowless_frame_rate: crate::consts::CEF_WINDOWLESS_FRAME_RATE, background_color: 0x0, ..Default::default() }; diff --git a/desktop/src/cef/texture_import/common.rs b/desktop/src/cef/texture_import/common.rs index 29156eca4c..97f5d6f4a5 100644 --- a/desktop/src/cef/texture_import/common.rs +++ b/desktop/src/cef/texture_import/common.rs @@ -50,7 +50,7 @@ pub mod format { } } - #[cfg(feature = "accelerated_paint")] + #[cfg(all(feature = "accelerated_paint", not(target_os = "macos")))] /// Convert CEF color type to Vulkan format pub fn cef_to_vulkan(format: cef_color_type_t) -> Result { match format { @@ -84,7 +84,12 @@ pub mod texture { view_formats: &[], }); - tracing::warn!("Using fallback CPU texture - hardware acceleration not available"); + tracing::warn!( + "Using fallback CPU texture for CEF rendering ({}x{}, {:?}) - hardware acceleration failed or unavailable. Consider checking GPU driver support.", + width, + height, + format + ); Ok(texture) } } @@ -105,6 +110,7 @@ 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; diff --git a/desktop/src/cef/texture_import/d3d11.rs b/desktop/src/cef/texture_import/d3d11.rs index 14ea7825eb..7bf6799a99 100644 --- a/desktop/src/cef/texture_import/d3d11.rs +++ b/desktop/src/cef/texture_import/d3d11.rs @@ -38,7 +38,7 @@ impl TextureImporter for D3D11Importer { } } } - + // Try Vulkan as fallback if vulkan::is_vulkan_backend(device) { match self.import_via_vulkan(device) { @@ -298,13 +298,11 @@ impl D3D11Importer { // Open D3D11 shared handle on D3D12 device unsafe { let mut shared_resource: Option = None; - d3d12_device.OpenSharedHandle( - windows::Win32::Foundation::HANDLE(self.handle as isize), - &ID3D12Resource::IID, - &mut shared_resource as *mut _ as *mut _, - ).map_err(|e| TextureImportError::PlatformError { - message: format!("Failed to open D3D11 shared handle on D3D12: {:?}", e), - })?; + d3d12_device + .OpenSharedHandle(windows::Win32::Foundation::HANDLE(self.handle as isize), &ID3D12Resource::IID, &mut shared_resource as *mut _ as *mut _) + .map_err(|e| TextureImportError::PlatformError { + message: format!("Failed to open D3D11 shared handle on D3D12: {:?}", e), + })?; shared_resource.ok_or_else(|| TextureImportError::InvalidHandle("Failed to get D3D12 resource from shared handle".to_string())) } diff --git a/desktop/src/cef/texture_import/iosurface.rs b/desktop/src/cef/texture_import/iosurface.rs index 7567b09400..0642717782 100644 --- a/desktop/src/cef/texture_import/iosurface.rs +++ b/desktop/src/cef/texture_import/iosurface.rs @@ -3,16 +3,16 @@ #[cfg(target_os = "macos")] use std::os::raw::c_void; -#[cfg(all(feature = "accelerated_paint", target_os = "macos"))] -use ash::vk; #[cfg(all(feature = "accelerated_paint", target_os = "macos"))] use core_foundation::base::{CFType, TCFType}; #[cfg(all(feature = "accelerated_paint", target_os = "macos"))] use objc2_io_surface::{IOSurface, IOSurfaceRef}; #[cfg(all(feature = "accelerated_paint", target_os = "macos"))] +use objc2_metal::{MTLDevice, MTLPixelFormat, MTLTexture, MTLTextureDescriptor, MTLTextureType, MTLTextureUsage}; +#[cfg(all(feature = "accelerated_paint", target_os = "macos"))] use wgpu::hal::api; -use super::common::{TextureImportError, TextureImportResult, TextureImporter, format, texture, vulkan}; +use super::common::{TextureImportError, TextureImportResult, TextureImporter, format, texture}; use cef::sys::cef_color_type_t; #[cfg(target_os = "macos")] @@ -30,13 +30,13 @@ impl TextureImporter for IOSurfaceImporter { #[cfg(feature = "accelerated_paint")] { if self.supports_hardware_acceleration(device) { - match self.import_via_vulkan(device) { + match self.import_via_metal(device) { Ok(texture) => { - tracing::trace!("Successfully imported IOSurface texture via Vulkan"); + tracing::trace!("Successfully imported IOSurface texture via Metal"); return Ok(texture); } Err(e) => { - tracing::warn!("Failed to import IOSurface via Vulkan: {}, falling back to CPU texture", e); + tracing::warn!("Failed to import IOSurface via Metal: {}, falling back to CPU texture", e); } } } @@ -54,8 +54,8 @@ impl TextureImporter for IOSurfaceImporter { return false; } - // Check if wgpu is using Vulkan backend - vulkan::is_vulkan_backend(device) + // Check if wgpu is using Metal backend + self.is_metal_backend(device) } #[cfg(not(feature = "accelerated_paint"))] { @@ -68,23 +68,23 @@ impl TextureImporter for IOSurfaceImporter { #[cfg(target_os = "macos")] impl IOSurfaceImporter { #[cfg(feature = "accelerated_paint")] - fn import_via_vulkan(&self, device: &wgpu::Device) -> TextureImportResult { - // Get wgpu's Vulkan instance and device - use wgpu::{TextureUses, wgc::api::Vulkan}; + fn import_via_metal(&self, device: &wgpu::Device) -> TextureImportResult { + // Get wgpu's Metal device + use wgpu::{hal::Api, wgc::api::Metal}; let hal_texture = unsafe { - device.as_hal::(|device| { + device.as_hal::(|device| { let Some(device) = device else { return Err(TextureImportError::HardwareUnavailable { - reason: "Device is not using Vulkan backend".to_string(), + reason: "Device is not using Metal backend".to_string(), }); }; - // Import IOSurface handle into Vulkan via Metal - let vk_image = self.import_iosurface_to_vulkan(device)?; + // Import IOSurface handle into Metal texture + let metal_texture = self.import_iosurface_to_metal(device)?; - // Wrap VkImage in wgpu-hal texture - let hal_texture = ::Device::texture_from_raw( - vk_image, + // Wrap Metal texture in wgpu-hal texture + let hal_texture = ::Device::texture_from_raw( + metal_texture, &wgpu::hal::TextureDescriptor { label: Some("CEF IOSurface Texture"), size: wgpu::Extent3d { @@ -96,7 +96,7 @@ impl IOSurfaceImporter { sample_count: 1, dimension: wgpu::TextureDimension::D2, format: format::cef_to_wgpu(self.format)?, - usage: TextureUses::COPY_DST | TextureUses::RESOURCE, + usage: wgpu::hal::TextureUses::RESOURCE, memory_flags: wgpu::hal::MemoryFlags::empty(), view_formats: vec![], }, @@ -109,7 +109,7 @@ impl IOSurfaceImporter { // Import hal texture into wgpu let texture = unsafe { - device.create_texture_from_hal::( + device.create_texture_from_hal::( hal_texture, &wgpu::TextureDescriptor { label: Some("CEF IOSurface Texture"), @@ -132,64 +132,66 @@ impl IOSurfaceImporter { } #[cfg(feature = "accelerated_paint")] - fn import_iosurface_to_vulkan(&self, hal_device: &::Device) -> Result { - // Get raw Vulkan handles - let device = hal_device.raw_device(); - let _instance = hal_device.shared_instance().raw_instance(); - + fn import_iosurface_to_metal(&self, hal_device: &::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 iosurface = unsafe { let cf_type = CFType::wrap_under_get_rule(self.handle as IOSurfaceRef); IOSurface::from(cf_type) }; - // Note: Full Metal-to-Vulkan import would require: - // 1. Creating Metal texture from IOSurface - // 2. Using VK_EXT_metal_objects to import Metal texture into Vulkan - // 3. Proper synchronization between Metal and Vulkan - // - // This is complex and not fully supported by current objc2 bindings. - // For now, we create a minimal Vulkan image and rely on fallback. - - // Create external memory image info for Metal objects - let mut external_memory_info = vk::ExternalMemoryImageCreateInfo::default().handle_types(vk::ExternalMemoryHandleTypeFlags::MTLTEXTURE_EXT); - - // Create image create info - let image_create_info = vk::ImageCreateInfo::default() - .image_type(vk::ImageType::TYPE_2D) - .format(format::cef_to_vulkan(self.format)?) - .extent(vk::Extent3D { - width: self.width, - height: self.height, - depth: 1, - }) - .mip_levels(1) - .array_layers(1) - .samples(vk::SampleCountFlags::TYPE_1) - .tiling(vk::ImageTiling::OPTIMAL) - .usage(vk::ImageUsageFlags::SAMPLED | vk::ImageUsageFlags::COLOR_ATTACHMENT) - .sharing_mode(vk::SharingMode::EXCLUSIVE) - .push_next(&mut external_memory_info); - - // Create the image - let image = unsafe { - device.create_image(&image_create_info, None).map_err(|e| TextureImportError::VulkanError { - operation: format!("Failed to create Vulkan image: {:?}", e), - })? + // 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 + let metal_texture = unsafe { metal_device.newTextureWithDescriptor_iosurface_plane(&texture_descriptor, &iosurface, 0) }; + + let Some(metal_texture) = metal_texture else { + return Err(TextureImportError::PlatformError { + message: "Failed to create Metal texture from IOSurface".to_string(), + }); }; - // Note: The actual Metal-to-Vulkan import would require VK_EXT_metal_objects - // and proper Metal texture handle extraction, which is complex and not - // fully supported in the current objc2 bindings. For now, we return the - // image and rely on fallback behavior. + tracing::trace!("Successfully created Metal texture from IOSurface"); + Ok(metal_texture) + } - tracing::warn!("Metal-to-Vulkan texture import not fully implemented"); + #[cfg(feature = "accelerated_paint")] + fn cef_to_metal_format(&self, format: cef_color_type_t) -> Result { + match format { + cef_color_type_t::CEF_COLOR_TYPE_BGRA_8888 => Ok(MTLPixelFormat::BGRA8Unorm_sRGB), + cef_color_type_t::CEF_COLOR_TYPE_RGBA_8888 => Ok(MTLPixelFormat::RGBA8Unorm_sRGB), + _ => Err(TextureImportError::UnsupportedFormat { format }), + } + } - Ok(image) + #[cfg(feature = "accelerated_paint")] + fn is_metal_backend(&self, device: &wgpu::Device) -> bool { + use wgpu::hal::api; + let mut is_metal = false; + unsafe { + device.as_hal::(|device| { + is_metal = device.is_some(); + }); + } + is_metal } } diff --git a/desktop/src/cef/texture_import/mod.rs b/desktop/src/cef/texture_import/mod.rs index dc61a65ca5..0344725e2d 100644 --- a/desktop/src/cef/texture_import/mod.rs +++ b/desktop/src/cef/texture_import/mod.rs @@ -8,7 +8,7 @@ //! //! - **Linux**: DMA-BUF via Vulkan external memory //! - **Windows**: D3D11 shared textures via Vulkan interop -//! - **macOS**: IOSurface via Metal/Vulkan interop +//! - **macOS**: IOSurface via Metal native API //! //! # Usage //! diff --git a/desktop/src/consts.rs b/desktop/src/consts.rs index 49543588fb..f1c23028cc 100644 --- a/desktop/src/consts.rs +++ b/desktop/src/consts.rs @@ -1,3 +1,7 @@ pub(crate) static APP_NAME: &str = "Graphite"; pub(crate) static APP_ID: &str = "rs.graphite.GraphiteEditor"; pub(crate) static APP_DIRECTORY_NAME: &str = "graphite-editor"; + +// CEF configuration constants +pub(crate) const CEF_WINDOWLESS_FRAME_RATE: i32 = 120; +pub(crate) const CEF_MESSAGE_LOOP_MAX_ITERATIONS: usize = 10; diff --git a/desktop/src/main.rs b/desktop/src/main.rs index 2a0d2cdefc..ba79e9b0e0 100644 --- a/desktop/src/main.rs +++ b/desktop/src/main.rs @@ -17,7 +17,7 @@ use app::WinitApp; mod dirs; use graphite_desktop_wrapper::messages::DesktopWrapperMessage; -use graphite_desktop_wrapper::{DesktopWrapper, NodeGraphExecutionResult, WgpuContext}; +use graphite_desktop_wrapper::{NodeGraphExecutionResult, WgpuContext}; pub(crate) enum CustomEvent { UiUpdate(wgpu::Texture), From 082c1b63e21933623ef789d013ab3e1ded55d70d Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Thu, 21 Aug 2025 13:54:11 +0000 Subject: [PATCH 14/14] Review cleanup --- desktop/src/app.rs | 4 +- desktop/src/cef.rs | 11 +- desktop/src/cef/context.rs | 1 + .../src/cef/internal/browser_process_app.rs | 9 +- desktop/src/cef/internal/render_handler.rs | 120 +------------- desktop/src/cef/texture_import/common.rs | 85 +--------- desktop/src/cef/texture_import/d3d11.rs | 91 +++++------ desktop/src/cef/texture_import/dmabuf.rs | 149 +++++++++++------- desktop/src/cef/texture_import/iosurface.rs | 69 ++++---- desktop/src/cef/texture_import/mod.rs | 54 +++++-- .../texture_import/shared_texture_handle.rs | 45 ++++++ desktop/src/consts.rs | 2 +- 12 files changed, 271 insertions(+), 369 deletions(-) create mode 100644 desktop/src/cef/texture_import/shared_texture_handle.rs diff --git a/desktop/src/app.rs b/desktop/src/app.rs index 59912fc972..df18bcfaa9 100644 --- a/desktop/src/app.rs +++ b/desktop/src/app.rs @@ -39,8 +39,6 @@ pub(crate) struct WinitApp { impl WinitApp { pub(crate) fn new(cef_context: cef::Context, window_size_sender: Sender, wgpu_context: WgpuContext, event_loop_proxy: EventLoopProxy) -> Self { - let desktop_wrapper = DesktopWrapper::new(); - let rendering_loop_proxy = event_loop_proxy.clone(); let (start_render_sender, start_render_receiver) = std::sync::mpsc::sync_channel(1); std::thread::spawn(move || { @@ -59,7 +57,7 @@ impl WinitApp { window_size_sender, wgpu_context, event_loop_proxy, - desktop_wrapper, + desktop_wrapper: DesktopWrapper::new(), last_ui_update: Instant::now(), avg_frame_time: 0., start_render_sender, diff --git a/desktop/src/cef.rs b/desktop/src/cef.rs index 8de55f1367..7e3ac80843 100644 --- a/desktop/src/cef.rs +++ b/desktop/src/cef.rs @@ -27,9 +27,12 @@ mod internal; mod ipc; mod platform; mod scheme_handler; +mod utility; + #[cfg(feature = "accelerated_paint")] mod texture_import; -mod utility; +#[cfg(feature = "accelerated_paint")] +use texture_import::SharedTextureHandle; pub(crate) use context::{Context, InitError, Initialized, Setup, SetupError}; use winit::event_loop::EventLoopProxy; @@ -38,7 +41,7 @@ pub(crate) trait CefEventHandler: Clone { fn window_size(&self) -> WindowSize; fn draw<'a>(&self, frame_buffer: FrameBufferRef<'a>); #[cfg(feature = "accelerated_paint")] - fn on_accelerated_paint(&self, shared_handle: internal::render_handler::SharedTextureHandle); + fn draw_gpu(&self, shared_texture: SharedTextureHandle); /// Scheudule the main event loop to run the cef event loop after the timeout /// [`_cef_browser_process_handler_t::on_schedule_message_pump_work`] for more documentation. fn schedule_cef_message_loop_work(&self, scheduled_time: Instant); @@ -150,8 +153,8 @@ impl CefEventHandler for CefHandler { } #[cfg(feature = "accelerated_paint")] - fn on_accelerated_paint(&self, shared_handle: internal::render_handler::SharedTextureHandle) { - match self::texture_import::import_texture(shared_handle, &self.wgpu_context.device) { + fn draw_gpu(&self, shared_texture: SharedTextureHandle) { + match shared_texture.import_texture(&self.wgpu_context.device) { Ok(texture) => { let _ = self.event_loop_proxy.send_event(CustomEvent::UiUpdate(texture)); } diff --git a/desktop/src/cef/context.rs b/desktop/src/cef/context.rs index b50e4f913f..c5791568b4 100644 --- a/desktop/src/cef/context.rs +++ b/desktop/src/cef/context.rs @@ -86,6 +86,7 @@ impl Context { let mut client = Client::new(BrowserProcessClientImpl::new(render_handler, event_handler.clone())); let url = CefString::from(format!("{GRAPHITE_SCHEME}://{FRONTEND_DOMAIN}/").as_str()); + // let url = CefString::from("chrome://gpu"); let window_info = WindowInfo { windowless_rendering_enabled: 1, diff --git a/desktop/src/cef/internal/browser_process_app.rs b/desktop/src/cef/internal/browser_process_app.rs index 9290b1e76c..b417922eaa 100644 --- a/desktop/src/cef/internal/browser_process_app.rs +++ b/desktop/src/cef/internal/browser_process_app.rs @@ -46,11 +46,16 @@ impl ImplApp for BrowserProcessAppImpl { // Enable GPU acceleration switches for better performance cmd.append_switch(Some(&CefString::from("enable-gpu-rasterization"))); cmd.append_switch(Some(&CefString::from("enable-accelerated-2d-canvas"))); - // Don't disable GPU - let CEF use hardware acceleration + } + + #[cfg(all(feature = "accelerated_paint", target_os = "linux"))] + { + // Use Vulkan for accelerated painting + cmd.append_switch_with_value(Some(&CefString::from("use-angle")), Some(&CefString::from("vulkan"))); } // Tell CEF to use Wayland if available - #[cfg(not(any(target_os = "macos", target_os = "windows")))] + #[cfg(target_os = "linux")] { let use_wayland = env::var("WAYLAND_DISPLAY") .ok() diff --git a/desktop/src/cef/internal/render_handler.rs b/desktop/src/cef/internal/render_handler.rs index 7fcaca6619..c430630dac 100644 --- a/desktop/src/cef/internal/render_handler.rs +++ b/desktop/src/cef/internal/render_handler.rs @@ -5,44 +5,11 @@ use cef::{Browser, ImplRenderHandler, PaintElementType, Rect, WrapRenderHandler} use crate::cef::CefEventHandler; use crate::render::FrameBufferRef; -#[cfg(all(target_os = "linux", feature = "accelerated_paint"))] -use std::os::fd::RawFd; -#[cfg(all(feature = "accelerated_paint", any(target_os = "windows", target_os = "macos")))] -use std::os::raw::c_void; - pub(crate) struct RenderHandlerImpl { object: *mut RcImpl<_cef_render_handler_t, Self>, event_handler: H, } -#[cfg(feature = "accelerated_paint")] -pub enum SharedTextureHandle { - #[cfg(target_os = "windows")] - D3D11 { - handle: *mut c_void, - format: cef::sys::cef_color_type_t, - width: u32, - height: u32, - }, - #[cfg(target_os = "macos")] - IOSurface { - handle: *mut c_void, - format: cef::sys::cef_color_type_t, - width: u32, - height: u32, - }, - #[cfg(target_os = "linux")] - DmaBuf { - fds: Vec, - format: cef::sys::cef_color_type_t, - modifier: u64, - width: u32, - height: u32, - strides: Vec, - offsets: Vec, - }, -} - impl RenderHandlerImpl { pub(crate) fn new(event_handler: H) -> Self { Self { @@ -83,50 +50,19 @@ impl ImplRenderHandler for RenderHandlerImpl { #[cfg(feature = "accelerated_paint")] fn on_accelerated_paint(&self, _browser: Option<&mut Browser>, type_: PaintElementType, _dirty_rect_count: usize, _dirty_rects: Option<&Rect>, info: Option<&cef::AcceleratedPaintInfo>) { + use crate::cef::texture_import::shared_texture_handle::SharedTextureHandle; + if type_ != PaintElementType::default() { return; } - let info = info.unwrap(); - - #[cfg(target_os = "linux")] - { - // Extract DMA-BUF information - let shared_handle = SharedTextureHandle::DmaBuf { - fds: extract_fds_from_info(info), - format: *info.format.as_ref(), - modifier: info.modifier, - width: info.extra.coded_size.width as u32, - height: info.extra.coded_size.height as u32, - strides: extract_strides_from_info(info), - offsets: extract_offsets_from_info(info), - }; - - self.event_handler.on_accelerated_paint(shared_handle); - } - #[cfg(target_os = "windows")] - { - // Extract D3D11 shared handle with texture metadata - let shared_handle = SharedTextureHandle::D3D11 { - handle: info.shared_texture_handle, - format: *info.format.as_ref(), - width: info.extra.coded_size.width as u32, - height: info.extra.coded_size.height as u32, - }; - self.event_handler.on_accelerated_paint(shared_handle); + let shared_handle = SharedTextureHandle::new(info.unwrap()); + if let SharedTextureHandle::Unsupported = shared_handle { + tracing::error!("Platform does not support accelerated painting"); + return; } - #[cfg(target_os = "macos")] - { - // Extract IOSurface handle with texture metadata - let shared_handle = SharedTextureHandle::IOSurface { - handle: info.shared_texture_handle, - format: *info.format.as_ref(), - width: info.extra.coded_size.width as u32, - height: info.extra.coded_size.height as u32, - }; - self.event_handler.on_accelerated_paint(shared_handle); - } + self.event_handler.draw_gpu(shared_handle); } fn get_raw(&self) -> *mut _cef_render_handler_t { @@ -159,45 +95,3 @@ impl WrapRenderHandler for RenderHandlerImpl { self.object = object; } } - -#[cfg(all(feature = "accelerated_paint", target_os = "linux"))] -fn extract_fds_from_info(info: &cef::AcceleratedPaintInfo) -> Vec { - let plane_count = info.plane_count as usize; - let mut fds = Vec::with_capacity(plane_count); - - for i in 0..plane_count { - if let Some(plane) = info.planes.get(i) { - fds.push(plane.fd); - } - } - - fds -} - -#[cfg(all(feature = "accelerated_paint", target_os = "linux"))] -fn extract_strides_from_info(info: &cef::AcceleratedPaintInfo) -> Vec { - let plane_count = info.plane_count as usize; - let mut strides = Vec::with_capacity(plane_count); - - for i in 0..plane_count { - if let Some(plane) = info.planes.get(i) { - strides.push(plane.stride); - } - } - - strides -} - -#[cfg(all(feature = "accelerated_paint", target_os = "linux"))] -fn extract_offsets_from_info(info: &cef::AcceleratedPaintInfo) -> Vec { - let plane_count = info.plane_count as usize; - let mut offsets = Vec::with_capacity(plane_count); - - for i in 0..plane_count { - if let Some(plane) = info.planes.get(i) { - offsets.push(plane.offset as u32); - } - } - - offsets -} diff --git a/desktop/src/cef/texture_import/common.rs b/desktop/src/cef/texture_import/common.rs index 97f5d6f4a5..ec5951f46d 100644 --- a/desktop/src/cef/texture_import/common.rs +++ b/desktop/src/cef/texture_import/common.rs @@ -1,42 +1,10 @@ //! 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; -#[cfg(feature = "accelerated_paint")] -use ash::vk; - -/// Result type for texture import operations -pub type TextureImportResult = Result; - -/// Errors that can occur during texture import -#[derive(Debug, thiserror::Error)] -pub enum TextureImportError { - #[error("Invalid texture handle: {0}")] - InvalidHandle(String), - - #[error("Unsupported texture format: {format:?}")] - UnsupportedFormat { format: cef_color_type_t }, - - #[error("Hardware acceleration not available: {reason}")] - HardwareUnavailable { reason: String }, - - #[error("Vulkan operation failed: {operation}")] - VulkanError { operation: String }, - - #[error("Platform-specific error: {message}")] - PlatformError { message: String }, -} - -/// Trait for platform-specific texture importers -pub trait TextureImporter { - /// Import the texture into wgpu, with automatic fallback to CPU texture - fn import_to_wgpu(&self, device: &Device) -> TextureImportResult; - - /// Check if hardware acceleration is available for this texture - fn supports_hardware_acceleration(&self, device: &Device) -> bool; -} - /// Common format conversion utilities pub mod format { use super::*; @@ -50,7 +18,7 @@ pub mod format { } } - #[cfg(all(feature = "accelerated_paint", not(target_os = "macos")))] + #[cfg(not(target_os = "macos"))] /// Convert CEF color type to Vulkan format pub fn cef_to_vulkan(format: cef_color_type_t) -> Result { match format { @@ -94,19 +62,13 @@ pub mod texture { } } -#[cfg(feature = "accelerated_paint")] /// Common Vulkan utilities pub mod vulkan { use super::*; /// 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 { - for i in 0..mem_properties.memory_type_count { - if (type_filter & (1 << i)) != 0 && mem_properties.memory_types[i as usize].property_flags.contains(properties) { - return Some(i); - } - } - None + (0..mem_properties.memory_type_count).find(|&i| (type_filter & (1 << i)) != 0 && mem_properties.memory_types[i as usize].property_flags.contains(properties)) } /// Check if the wgpu device is using Vulkan backend @@ -135,42 +97,3 @@ pub mod vulkan { is_d3d12 } } - -/// Import a texture using the appropriate platform-specific importer -pub fn import_texture(shared_handle: crate::cef::internal::render_handler::SharedTextureHandle, device: &Device) -> TextureImportResult { - match shared_handle { - #[cfg(target_os = "linux")] - crate::cef::internal::render_handler::SharedTextureHandle::DmaBuf { - fds, - format, - modifier, - width, - height, - strides, - offsets, - } => { - let importer = super::dmabuf::DmaBufImporter { - fds, - format, - modifier, - width, - height, - strides, - offsets, - }; - importer.import_to_wgpu(device) - } - - #[cfg(target_os = "windows")] - crate::cef::internal::render_handler::SharedTextureHandle::D3D11 { handle, format, width, height } => { - let importer = super::d3d11::D3D11Importer { handle, format, width, height }; - importer.import_to_wgpu(device) - } - - #[cfg(target_os = "macos")] - crate::cef::internal::render_handler::SharedTextureHandle::IOSurface { handle, format, width, height } => { - let importer = super::iosurface::IOSurfaceImporter { handle, format, width, height }; - importer.import_to_wgpu(device) - } - } -} diff --git a/desktop/src/cef/texture_import/d3d11.rs b/desktop/src/cef/texture_import/d3d11.rs index 7bf6799a99..d384706d46 100644 --- a/desktop/src/cef/texture_import/d3d11.rs +++ b/desktop/src/cef/texture_import/d3d11.rs @@ -1,17 +1,12 @@ //! Windows D3D11 shared texture import implementation -#[cfg(target_os = "windows")] -use std::os::raw::c_void; - -#[cfg(all(feature = "accelerated_paint", target_os = "windows"))] +use super::common::{format, texture, vulkan}; +use super::{TextureImportError, TextureImportResult, TextureImporter}; use ash::vk; -#[cfg(all(feature = "accelerated_paint", target_os = "windows"))] +use cef::{AcceleratedPaintInfo, sys::cef_color_type_t}; +use std::os::raw::c_void; use wgpu::hal::api; -use super::common::{TextureImportError, TextureImportResult, TextureImporter, format, texture, vulkan}; -use cef::sys::cef_color_type_t; - -#[cfg(target_os = "windows")] pub struct D3D11Importer { pub handle: *mut c_void, pub format: cef_color_type_t, @@ -19,36 +14,41 @@ pub struct D3D11Importer { pub height: u32, } -#[cfg(target_os = "windows")] impl TextureImporter for D3D11Importer { + fn new(info: &AcceleratedPaintInfo) -> Self { + Self { + handle: info.shared_texture_handle, + format: *info.format.as_ref(), + width: info.extra.coded_size.width as u32, + height: info.extra.coded_size.height as u32, + } + } + fn import_to_wgpu(&self, device: &wgpu::Device) -> TextureImportResult { // Try hardware acceleration first - #[cfg(feature = "accelerated_paint")] - { - if self.supports_hardware_acceleration(device) { - // Try D3D12 first (most efficient on Windows) - if vulkan::is_d3d12_backend(device) { - match self.import_via_d3d12(device) { - Ok(texture) => { - tracing::info!("Successfully imported D3D11 shared texture via D3D12"); - return Ok(texture); - } - Err(e) => { - tracing::warn!("Failed to import D3D11 via D3D12: {}, trying Vulkan fallback", e); - } + if self.supports_hardware_acceleration(device) { + // Try D3D12 first (most efficient on Windows) + if vulkan::is_d3d12_backend(device) { + match self.import_via_d3d12(device) { + Ok(texture) => { + tracing::info!("Successfully imported D3D11 shared texture via D3D12"); + return Ok(texture); + } + Err(e) => { + tracing::warn!("Failed to import D3D11 via D3D12: {}, trying Vulkan fallback", e); } } + } - // Try Vulkan as fallback - if vulkan::is_vulkan_backend(device) { - match self.import_via_vulkan(device) { - Ok(texture) => { - tracing::info!("Successfully imported D3D11 shared texture via Vulkan"); - return Ok(texture); - } - Err(e) => { - tracing::warn!("Failed to import D3D11 via Vulkan: {}, falling back to CPU texture", e); - } + // Try Vulkan as fallback + if vulkan::is_vulkan_backend(device) { + match self.import_via_vulkan(device) { + Ok(texture) => { + tracing::info!("Successfully imported D3D11 shared texture via Vulkan"); + return Ok(texture); + } + Err(e) => { + tracing::warn!("Failed to import D3D11 via Vulkan: {}, falling back to CPU texture", e); } } } @@ -59,27 +59,17 @@ impl TextureImporter for D3D11Importer { } fn supports_hardware_acceleration(&self, device: &wgpu::Device) -> bool { - #[cfg(feature = "accelerated_paint")] - { - // Check if handle is valid - if self.handle.is_null() { - return false; - } - - // Check if wgpu is using D3D12 or Vulkan backend - vulkan::is_d3d12_backend(device) || vulkan::is_vulkan_backend(device) - } - #[cfg(not(feature = "accelerated_paint"))] - { - let _ = device; - false + // Check if handle is valid + if self.handle.is_null() { + return false; } + + // Check if wgpu is using D3D12 or Vulkan backend + vulkan::is_d3d12_backend(device) || vulkan::is_vulkan_backend(device) } } -#[cfg(target_os = "windows")] impl D3D11Importer { - #[cfg(feature = "accelerated_paint")] fn import_via_d3d12(&self, device: &wgpu::Device) -> TextureImportResult { // Get wgpu's D3D12 device use wgpu::hal::api; @@ -143,7 +133,6 @@ impl D3D11Importer { Ok(texture) } - #[cfg(feature = "accelerated_paint")] fn import_via_vulkan(&self, device: &wgpu::Device) -> TextureImportResult { // Get wgpu's Vulkan instance and device use wgpu::{TextureUses, wgc::api::Vulkan}; @@ -207,7 +196,6 @@ impl D3D11Importer { Ok(texture) } - #[cfg(feature = "accelerated_paint")] fn import_d3d11_handle_to_vulkan(&self, hal_device: &::Device) -> Result { // Get raw Vulkan handles let device = hal_device.raw_device(); @@ -282,7 +270,6 @@ impl D3D11Importer { Ok(image) } - #[cfg(feature = "accelerated_paint")] fn import_d3d11_handle_to_d3d12(&self, hal_device: &::Device) -> Result { use windows::Win32::Graphics::Direct3D12::*; use windows::core::*; diff --git a/desktop/src/cef/texture_import/dmabuf.rs b/desktop/src/cef/texture_import/dmabuf.rs index 08f523b3af..a72e07ccd4 100644 --- a/desktop/src/cef/texture_import/dmabuf.rs +++ b/desktop/src/cef/texture_import/dmabuf.rs @@ -1,44 +1,44 @@ //! Linux DMA-BUF texture import implementation -#[cfg(target_os = "linux")] -use std::os::fd::RawFd; - -#[cfg(all(feature = "accelerated_paint", target_os = "linux"))] +use super::common::{format, texture, vulkan}; +use super::{TextureImportError, TextureImportResult, TextureImporter}; use ash::vk; -#[cfg(all(feature = "accelerated_paint", target_os = "linux"))] +use cef::{AcceleratedPaintInfo, sys::cef_color_type_t}; use wgpu::hal::api; -#[cfg(all(feature = "accelerated_paint", target_os = "linux"))] -extern crate libc; - -use super::common::{TextureImportError, TextureImportResult, TextureImporter, format, texture, vulkan}; -use cef::sys::cef_color_type_t; - -#[cfg(target_os = "linux")] -pub struct DmaBufImporter { - pub fds: Vec, - pub format: cef_color_type_t, - pub modifier: u64, - pub width: u32, - pub height: u32, - pub strides: Vec, - pub offsets: Vec, + +pub(crate) struct DmaBufImporter { + fds: Vec, + format: cef_color_type_t, + modifier: u64, + width: u32, + height: u32, + strides: Vec, + offsets: Vec, } -#[cfg(target_os = "linux")] impl TextureImporter for DmaBufImporter { + fn new(info: &AcceleratedPaintInfo) -> Self { + Self { + fds: extract_fds_from_info(info), + format: *info.format.as_ref(), + modifier: info.modifier, + width: info.extra.coded_size.width as u32, + height: info.extra.coded_size.height as u32, + strides: extract_strides_from_info(info), + offsets: extract_offsets_from_info(info), + } + } + fn import_to_wgpu(&self, device: &wgpu::Device) -> TextureImportResult { // Try hardware acceleration first - #[cfg(feature = "accelerated_paint")] - { - if self.supports_hardware_acceleration(device) { - match self.import_via_vulkan(device) { - Ok(texture) => { - tracing::info!("Successfully imported DMA-BUF texture via Vulkan"); - return Ok(texture); - } - Err(e) => { - tracing::warn!("Failed to import DMA-BUF via Vulkan: {}, falling back to CPU texture", e); - } + if self.supports_hardware_acceleration(device) { + match self.import_via_vulkan(device) { + Ok(texture) => { + tracing::info!("Successfully imported DMA-BUF texture via Vulkan"); + return Ok(texture); + } + Err(e) => { + tracing::warn!("Failed to import DMA-BUF via Vulkan: {}, falling back to CPU texture", e); } } } @@ -48,38 +48,28 @@ impl TextureImporter for DmaBufImporter { } fn supports_hardware_acceleration(&self, device: &wgpu::Device) -> bool { - #[cfg(feature = "accelerated_paint")] - { - // Check if we have valid file descriptors - if self.fds.is_empty() { + // Check if we have valid file descriptors + if self.fds.is_empty() { + return false; + } + + for &fd in &self.fds { + if fd < 0 { return false; } - - for &fd in &self.fds { - if fd < 0 { - return false; - } - // Check if file descriptor is valid - let flags = unsafe { libc::fcntl(fd, libc::F_GETFD) }; - if flags == -1 { - return false; - } + // Check if file descriptor is valid + let flags = unsafe { libc::fcntl(fd, libc::F_GETFD) }; + if flags == -1 { + return false; } - - // Check if wgpu is using Vulkan backend - vulkan::is_vulkan_backend(device) - } - #[cfg(not(feature = "accelerated_paint"))] - { - let _ = device; - false } + + // Check if wgpu is using Vulkan backend + vulkan::is_vulkan_backend(device) } } -#[cfg(target_os = "linux")] impl DmaBufImporter { - #[cfg(feature = "accelerated_paint")] fn import_via_vulkan(&self, device: &wgpu::Device) -> TextureImportResult { // Get wgpu's Vulkan instance and device use wgpu::{TextureUses, wgc::api::Vulkan}; @@ -143,7 +133,6 @@ impl DmaBufImporter { Ok(texture) } - #[cfg(feature = "accelerated_paint")] fn create_vulkan_image_from_dmabuf(&self, hal_device: &::Device) -> Result { // Get raw Vulkan handles let device = hal_device.raw_device(); @@ -181,7 +170,7 @@ impl DmaBufImporter { // Create the image let image = unsafe { device.create_image(&image_create_info, None).map_err(|e| TextureImportError::VulkanError { - operation: format!("Failed to create Vulkan image: {:?}", e), + operation: format!("Failed to create Vulkan image: {e:?}"), })? }; @@ -213,21 +202,20 @@ impl DmaBufImporter { let device_memory = unsafe { device.allocate_memory(&allocate_info, None).map_err(|e| TextureImportError::VulkanError { - operation: format!("Failed to allocate memory for DMA-BUF: {:?}", e), + operation: format!("Failed to allocate memory for DMA-BUF: {e:?}"), })? }; // Bind memory to image unsafe { device.bind_image_memory(image, device_memory, 0).map_err(|e| TextureImportError::VulkanError { - operation: format!("Failed to bind memory to image: {:?}", e), + operation: format!("Failed to bind memory to image: {e:?}"), })?; } Ok(image) } - #[cfg(feature = "accelerated_paint")] fn create_subresource_layouts(&self) -> Result, TextureImportError> { let mut layouts = Vec::new(); @@ -244,3 +232,42 @@ impl DmaBufImporter { Ok(layouts) } } + +fn extract_fds_from_info(info: &cef::AcceleratedPaintInfo) -> Vec { + let plane_count = info.plane_count as usize; + let mut fds = Vec::with_capacity(plane_count); + + for i in 0..plane_count { + if let Some(plane) = info.planes.get(i) { + fds.push(plane.fd); + } + } + + fds +} + +fn extract_strides_from_info(info: &cef::AcceleratedPaintInfo) -> Vec { + let plane_count = info.plane_count as usize; + let mut strides = Vec::with_capacity(plane_count); + + for i in 0..plane_count { + if let Some(plane) = info.planes.get(i) { + strides.push(plane.stride); + } + } + + strides +} + +fn extract_offsets_from_info(info: &cef::AcceleratedPaintInfo) -> Vec { + let plane_count = info.plane_count as usize; + let mut offsets = Vec::with_capacity(plane_count); + + for i in 0..plane_count { + if let Some(plane) = info.planes.get(i) { + offsets.push(plane.offset as u32); + } + } + + offsets +} diff --git a/desktop/src/cef/texture_import/iosurface.rs b/desktop/src/cef/texture_import/iosurface.rs index 0642717782..65b4fdcc2a 100644 --- a/desktop/src/cef/texture_import/iosurface.rs +++ b/desktop/src/cef/texture_import/iosurface.rs @@ -1,21 +1,14 @@ //! macOS IOSurface texture import implementation -#[cfg(target_os = "macos")] -use std::os::raw::c_void; - -#[cfg(all(feature = "accelerated_paint", target_os = "macos"))] +use super::common::{format, texture}; +use super::{TextureImportError, TextureImportResult, TextureImporter}; +use cef::{AcceleratedPaintInfo, sys::cef_color_type_t}; use core_foundation::base::{CFType, TCFType}; -#[cfg(all(feature = "accelerated_paint", target_os = "macos"))] use objc2_io_surface::{IOSurface, IOSurfaceRef}; -#[cfg(all(feature = "accelerated_paint", target_os = "macos"))] use objc2_metal::{MTLDevice, MTLPixelFormat, MTLTexture, MTLTextureDescriptor, MTLTextureType, MTLTextureUsage}; -#[cfg(all(feature = "accelerated_paint", target_os = "macos"))] +use std::os::raw::c_void; use wgpu::hal::api; -use super::common::{TextureImportError, TextureImportResult, TextureImporter, format, texture}; -use cef::sys::cef_color_type_t; - -#[cfg(target_os = "macos")] pub struct IOSurfaceImporter { pub handle: *mut c_void, pub format: cef_color_type_t, @@ -23,21 +16,26 @@ pub struct IOSurfaceImporter { pub height: u32, } -#[cfg(target_os = "macos")] impl TextureImporter for IOSurfaceImporter { + fn new(info: &AcceleratedPaintInfo) -> Self { + Self { + handle: info.shared_texture_handle, + format: *info.format.as_ref(), + width: info.extra.coded_size.width as u32, + height: info.extra.coded_size.height as u32, + } + } + fn import_to_wgpu(&self, device: &wgpu::Device) -> TextureImportResult { // Try hardware acceleration first - #[cfg(feature = "accelerated_paint")] - { - if self.supports_hardware_acceleration(device) { - match self.import_via_metal(device) { - Ok(texture) => { - tracing::trace!("Successfully imported IOSurface texture via Metal"); - return Ok(texture); - } - Err(e) => { - tracing::warn!("Failed to import IOSurface via Metal: {}, falling back to CPU texture", e); - } + if self.supports_hardware_acceleration(device) { + match self.import_via_metal(device) { + Ok(texture) => { + tracing::trace!("Successfully imported IOSurface texture via Metal"); + return Ok(texture); + } + Err(e) => { + tracing::warn!("Failed to import IOSurface via Metal: {}, falling back to CPU texture", e); } } } @@ -47,27 +45,17 @@ impl TextureImporter for IOSurfaceImporter { } fn supports_hardware_acceleration(&self, device: &wgpu::Device) -> bool { - #[cfg(feature = "accelerated_paint")] - { - // Check if handle is valid - if self.handle.is_null() { - return false; - } - - // Check if wgpu is using Metal backend - self.is_metal_backend(device) - } - #[cfg(not(feature = "accelerated_paint"))] - { - let _ = device; - false + // Check if handle is valid + if self.handle.is_null() { + return false; } + + // Check if wgpu is using Metal backend + self.is_metal_backend(device) } } -#[cfg(target_os = "macos")] impl IOSurfaceImporter { - #[cfg(feature = "accelerated_paint")] fn import_via_metal(&self, device: &wgpu::Device) -> TextureImportResult { // Get wgpu's Metal device use wgpu::{hal::Api, wgc::api::Metal}; @@ -131,7 +119,6 @@ impl IOSurfaceImporter { Ok(texture) } - #[cfg(feature = "accelerated_paint")] fn import_iosurface_to_metal(&self, hal_device: &::Device) -> Result<::Texture, TextureImportError> { // Validate dimensions if self.width == 0 || self.height == 0 { @@ -174,7 +161,6 @@ impl IOSurfaceImporter { Ok(metal_texture) } - #[cfg(feature = "accelerated_paint")] fn cef_to_metal_format(&self, format: cef_color_type_t) -> Result { match format { cef_color_type_t::CEF_COLOR_TYPE_BGRA_8888 => Ok(MTLPixelFormat::BGRA8Unorm_sRGB), @@ -183,7 +169,6 @@ impl IOSurfaceImporter { } } - #[cfg(feature = "accelerated_paint")] fn is_metal_backend(&self, device: &wgpu::Device) -> bool { use wgpu::hal::api; let mut is_metal = false; diff --git a/desktop/src/cef/texture_import/mod.rs b/desktop/src/cef/texture_import/mod.rs index 0344725e2d..485acf4d11 100644 --- a/desktop/src/cef/texture_import/mod.rs +++ b/desktop/src/cef/texture_import/mod.rs @@ -12,11 +12,9 @@ //! //! # Usage //! -//! ```rust -//! use crate::cef::texture_import; -//! +//! ```no_run //! // Import texture with automatic platform detection -//! let texture = texture_import::import_texture(shared_handle, &device)?; +//! let texture = shared_handle.import_texture(&device)?; //! ``` //! //! # Features @@ -26,16 +24,52 @@ //! - `accelerated_paint_d3d11` - Windows D3D11 support //! - `accelerated_paint_iosurface` - macOS IOSurface support -pub mod common; +pub(crate) mod common; + +pub(crate) mod shared_texture_handle; +pub(crate) use shared_texture_handle::SharedTextureHandle; #[cfg(target_os = "linux")] -pub mod dmabuf; +pub(crate) mod dmabuf; #[cfg(target_os = "windows")] -pub mod d3d11; +pub(crate) mod d3d11; #[cfg(target_os = "macos")] -pub mod iosurface; +pub(crate) mod iosurface; + +/// Result type for texture import operations +pub type TextureImportResult = Result; + +/// Errors that can occur during texture import +#[derive(Debug, thiserror::Error)] +pub enum TextureImportError { + #[error("Invalid texture handle: {0}")] + InvalidHandle(String), + + #[error("Unsupported texture format: {format:?}")] + UnsupportedFormat { format: cef::sys::cef_color_type_t }, + + #[error("Hardware acceleration not available: {reason}")] + HardwareUnavailable { reason: String }, + + #[error("Vulkan operation failed: {operation}")] + VulkanError { operation: String }, + + #[error("Platform-specific error: {message}")] + PlatformError { message: String }, + + #[error("Unsupported platform for texture import")] + UnsupportedPlatform, +} + +/// Trait for platform-specific texture importers +pub trait TextureImporter { + fn new(info: &cef::AcceleratedPaintInfo) -> Self; + + /// Import the texture into wgpu, with automatic fallback to CPU texture + fn import_to_wgpu(&self, device: &wgpu::Device) -> TextureImportResult; -// Re-export commonly used types -pub use common::import_texture; + /// Check if hardware acceleration is available for this texture + fn supports_hardware_acceleration(&self, device: &wgpu::Device) -> bool; +} diff --git a/desktop/src/cef/texture_import/shared_texture_handle.rs b/desktop/src/cef/texture_import/shared_texture_handle.rs new file mode 100644 index 0000000000..b19ee7f6f8 --- /dev/null +++ b/desktop/src/cef/texture_import/shared_texture_handle.rs @@ -0,0 +1,45 @@ +use cef::AcceleratedPaintInfo; + +use super::{TextureImportError, TextureImportResult, TextureImporter}; + +pub(crate) enum SharedTextureHandle { + #[cfg(target_os = "linux")] + DmaBuf(super::dmabuf::DmaBufImporter), + #[cfg(target_os = "windows")] + D3D11(super::d3d11::D3D11Importer), + #[cfg(target_os = "macos")] + IOSurface(super::iosurface::IOSurfaceImporter), + Unsupported, +} + +impl SharedTextureHandle { + pub(crate) fn new(info: &AcceleratedPaintInfo) -> Self { + // Extract DMA-BUF information + #[cfg(target_os = "linux")] + return Self::DmaBuf(super::dmabuf::DmaBufImporter::new(info)); + + // Extract D3D11 shared handle with texture metadata + #[cfg(target_os = "windows")] + return Self::D3D11(super::d3d11::D3D11Importer::new(info)); + + // Extract IOSurface handle with texture metadata + #[cfg(target_os = "macos")] + return Self::IOSurface(super::iosurface::IOSurfaceImporter::new(info)); + + #[allow(unreachable_code)] + Self::Unsupported + } + + /// Import a texture using the appropriate platform-specific importer + pub(crate) fn import_texture(self, device: &wgpu::Device) -> TextureImportResult { + match self { + #[cfg(target_os = "linux")] + SharedTextureHandle::DmaBuf(importer) => importer.import_to_wgpu(device), + #[cfg(target_os = "windows")] + SharedTextureHandle::D3D11(importer) => importer.import_to_wgpu(device), + #[cfg(target_os = "macos")] + SharedTextureHandle::IOSurface(importer) => importer.import_to_wgpu(device), + SharedTextureHandle::Unsupported => Err(TextureImportError::UnsupportedPlatform), + } + } +} diff --git a/desktop/src/consts.rs b/desktop/src/consts.rs index f1c23028cc..3babf73019 100644 --- a/desktop/src/consts.rs +++ b/desktop/src/consts.rs @@ -3,5 +3,5 @@ pub(crate) static APP_ID: &str = "rs.graphite.GraphiteEditor"; pub(crate) static APP_DIRECTORY_NAME: &str = "graphite-editor"; // CEF configuration constants -pub(crate) const CEF_WINDOWLESS_FRAME_RATE: i32 = 120; +pub(crate) const CEF_WINDOWLESS_FRAME_RATE: i32 = 60; pub(crate) const CEF_MESSAGE_LOOP_MAX_ITERATIONS: usize = 10;