diff --git a/Cargo.lock b/Cargo.lock index 9eab3f3b59..eea2f94344 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2077,14 +2077,19 @@ dependencies = [ name = "graphite-desktop" version = "0.1.0" dependencies = [ + "ash", "bytemuck", "cef", + "core-foundation", "derivative", "dirs", "futures", "glam", "graphite-desktop-wrapper", "include_dir", + "libc", + "objc2-io-surface", + "objc2-metal 0.3.1", "open", "rfd", "ron", @@ -2093,6 +2098,7 @@ dependencies = [ "tracing-subscriber", "vello", "wgpu", + "windows", "winit", ] @@ -3489,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]] @@ -3544,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" @@ -3568,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" @@ -3578,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 8c4e3000b1..1ddf52622e 100644 --- a/desktop/Cargo.toml +++ b/desktop/Cargo.toml @@ -9,9 +9,15 @@ edition = "2024" rust-version = "1.87" [features] -default = ["gpu"] +default = ["gpu", "accelerated_paint"] gpu = ["graphite-desktop-wrapper/gpu"] +# Hardware acceleration features +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] # # Local dependencies graphite-desktop-wrapper = { path = "wrapper" } @@ -32,3 +38,26 @@ vello = { workspace = true } derivative = { workspace = true } rfd = { workspace = true } open = { workspace = true } + +# Hardware acceleration dependencies +ash = { version = "0.38", optional = true } + +# Windows-specific dependencies +[target.'cfg(windows)'.dependencies] +windows = { version = "0.58", features = [ + "Win32_Graphics_Direct3D11", + "Win32_Graphics_Direct3D12", + "Win32_Graphics_Dxgi", + "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 } + +# 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 ad38bff85b..df18bcfaa9 100644 --- a/desktop/src/app.rs +++ b/desktop/src/app.rs @@ -1,18 +1,19 @@ 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; +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,11 +32,23 @@ pub(crate) struct WinitApp { wgpu_context: WgpuContext, event_loop_proxy: EventLoopProxy, desktop_wrapper: DesktopWrapper, + 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, @@ -44,7 +57,10 @@ 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, } } @@ -152,23 +168,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..CEF_MESSAGE_LOOP_MAX_ITERATIONS { + 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) { @@ -220,6 +233,11 @@ 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(); + if elapsed < 0.5 { + self.avg_frame_time = (self.avg_frame_time * 3. + elapsed) / 4.; + } } if let Some(window) = &self.window { window.request_redraw(); @@ -251,16 +269,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 d8a4d99576..7e3ac80843 100644 --- a/desktop/src/cef.rs +++ b/desktop/src/cef.rs @@ -1,3 +1,18 @@ +//! 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 either Vulkan or D3D12 interop (`accelerated_paint_d3d11` feature) +//! - **macOS**: IOSurface via Metal/Vulkan interop (`accelerated_paint_iosurface` feature) +//! +//! +//! 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}; @@ -10,15 +25,23 @@ mod dirs; mod input; mod internal; mod ipc; +mod platform; mod scheme_handler; mod utility; +#[cfg(feature = "accelerated_paint")] +mod texture_import; +#[cfg(feature = "accelerated_paint")] +use texture_import::SharedTextureHandle; + pub(crate) use context::{Context, InitError, Initialized, Setup, SetupError}; 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 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); @@ -128,4 +151,16 @@ impl CefEventHandler for CefHandler { }; let _ = self.event_loop_proxy.send_event(CustomEvent::DesktopWrapperMessage(desktop_wrapper_message)); } + + #[cfg(feature = "accelerated_paint")] + 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)); + } + Err(e) => { + tracing::error!("Failed to import shared texture: {}", e); + } + } + } } diff --git a/desktop/src/cef/context.rs b/desktop/src/cef/context.rs index 5a0f642c2a..c5791568b4 100644 --- a/desktop/src/cef/context.rs +++ b/desktop/src/cef/context.rs @@ -86,14 +86,17 @@ 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, + #[cfg(feature = "accelerated_paint")] + shared_texture_enabled: if crate::cef::platform::should_enable_hardware_acceleration() { 1 } else { 0 }, ..Default::default() }; let settings = BrowserSettings { - windowless_frame_rate: 60, + windowless_frame_rate: crate::consts::CEF_WINDOWLESS_FRAME_RATE, background_color: 0x0, ..Default::default() }; 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..b417922eaa 100644 --- a/desktop/src/cef/internal/browser_process_app.rs +++ b/desktop/src/cef/internal/browser_process_app.rs @@ -34,12 +34,28 @@ 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"))); + } + + #[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 21d818a091..c430630dac 100644 --- a/desktop/src/cef/internal/render_handler.rs +++ b/desktop/src/cef/internal/render_handler.rs @@ -9,6 +9,7 @@ pub(crate) struct RenderHandlerImpl { object: *mut RcImpl<_cef_render_handler_t, Self>, event_handler: H, } + impl RenderHandlerImpl { pub(crate) fn new(event_handler: H) -> Self { Self { @@ -47,6 +48,23 @@ 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>) { + use crate::cef::texture_import::shared_texture_handle::SharedTextureHandle; + + if type_ != PaintElementType::default() { + return; + } + + let shared_handle = SharedTextureHandle::new(info.unwrap()); + if let SharedTextureHandle::Unsupported = shared_handle { + tracing::error!("Platform does not support accelerated painting"); + return; + } + + self.event_handler.draw_gpu(shared_handle); + } + fn get_raw(&self) -> *mut _cef_render_handler_t { self.object.cast() } diff --git a/desktop/src/cef/platform.rs b/desktop/src/cef/platform.rs new file mode 100644 index 0000000000..7df5d5f982 --- /dev/null +++ b/desktop/src/cef/platform.rs @@ -0,0 +1,59 @@ +#[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 + } +} diff --git a/desktop/src/cef/texture_import/common.rs b/desktop/src/cef/texture_import/common.rs new file mode 100644 index 0000000000..ec5951f46d --- /dev/null +++ b/desktop/src/cef/texture_import/common.rs @@ -0,0 +1,99 @@ +//! 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; + +/// 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(not(target_os = "macos"))] + /// 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 for CEF rendering ({}x{}, {:?}) - hardware acceleration failed or unavailable. Consider checking GPU driver support.", + width, + height, + format + ); + Ok(texture) + } +} + +/// 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 { + (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 + #[cfg(not(target_os = "macos"))] + 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 + } + + /// 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 + } +} diff --git a/desktop/src/cef/texture_import/d3d11.rs b/desktop/src/cef/texture_import/d3d11.rs new file mode 100644 index 0000000000..d384706d46 --- /dev/null +++ b/desktop/src/cef/texture_import/d3d11.rs @@ -0,0 +1,297 @@ +//! Windows D3D11 shared texture import implementation + +use super::common::{format, texture, vulkan}; +use super::{TextureImportError, TextureImportResult, TextureImporter}; +use ash::vk; +use cef::{AcceleratedPaintInfo, sys::cef_color_type_t}; +use std::os::raw::c_void; +use wgpu::hal::api; + +pub struct D3D11Importer { + pub handle: *mut c_void, + pub format: cef_color_type_t, + pub width: u32, + pub height: u32, +} + +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 + 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); + } + } + } + } + + // Fallback to CPU texture + texture::create_fallback(device, self.width, self.height, self.format, "CEF D3D11 Texture (fallback)") + } + + fn supports_hardware_acceleration(&self, device: &wgpu::Device) -> bool { + // 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) + } +} + +impl D3D11Importer { + 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) + } + + 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 D3D11 shared handle into Vulkan + let vk_image = self.import_d3d11_handle_to_vulkan(device)?; + + // 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: 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 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: format::cef_to_wgpu(self.format)?, + usage: wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: &[], + }, + ) + }; + + Ok(texture) + } + + 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(TextureImportError::InvalidHandle("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(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 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 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_type_index = + 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) + .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| 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| TextureImportError::VulkanError { + operation: format!("Failed to bind memory to image: {:?}", e), + })?; + } + + Ok(image) + } + + 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 new file mode 100644 index 0000000000..a72e07ccd4 --- /dev/null +++ b/desktop/src/cef/texture_import/dmabuf.rs @@ -0,0 +1,273 @@ +//! Linux DMA-BUF texture import implementation + +use super::common::{format, texture, vulkan}; +use super::{TextureImportError, TextureImportResult, TextureImporter}; +use ash::vk; +use cef::{AcceleratedPaintInfo, sys::cef_color_type_t}; +use wgpu::hal::api; + +pub(crate) struct DmaBufImporter { + fds: Vec, + format: cef_color_type_t, + modifier: u64, + width: u32, + height: u32, + strides: Vec, + offsets: Vec, +} + +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 + 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 to CPU texture + texture::create_fallback(device, self.width, self.height, self.format, "CEF DMA-BUF Texture (fallback)") + } + + fn supports_hardware_acceleration(&self, device: &wgpu::Device) -> bool { + // Check if we have valid file descriptors + if self.fds.is_empty() { + 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 wgpu is using Vulkan backend + vulkan::is_vulkan_backend(device) + } +} + +impl DmaBufImporter { + 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(), + }); + }; + + // Create VkImage from DMA-BUF using external memory + let vk_image = self.create_vulkan_image_from_dmabuf(device)?; + + // 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: 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 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: format::cef_to_wgpu(self.format)?, + usage: wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: &[], + }, + ) + }; + + Ok(texture) + } + + 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(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(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::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| 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 + let dup_fd = unsafe { libc::dup(self.fds[0]) }; + if dup_fd == -1 { + 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 = + 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| 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| TextureImportError::VulkanError { + operation: format!("Failed to bind memory to image: {e:?}"), + })?; + } + + Ok(image) + } + + fn create_subresource_layouts(&self) -> Result, TextureImportError> { + 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) + } +} + +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 new file mode 100644 index 0000000000..65b4fdcc2a --- /dev/null +++ b/desktop/src/cef/texture_import/iosurface.rs @@ -0,0 +1,182 @@ +//! macOS IOSurface texture import implementation + +use super::common::{format, texture}; +use super::{TextureImportError, TextureImportResult, TextureImporter}; +use cef::{AcceleratedPaintInfo, sys::cef_color_type_t}; +use core_foundation::base::{CFType, TCFType}; +use objc2_io_surface::{IOSurface, IOSurfaceRef}; +use objc2_metal::{MTLDevice, MTLPixelFormat, MTLTexture, MTLTextureDescriptor, MTLTextureType, MTLTextureUsage}; +use std::os::raw::c_void; +use wgpu::hal::api; + +pub struct IOSurfaceImporter { + pub handle: *mut c_void, + pub format: cef_color_type_t, + pub width: u32, + pub height: u32, +} + +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 + 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); + } + } + } + + // Fallback to CPU texture + texture::create_fallback(device, self.width, self.height, self.format, "CEF IOSurface Texture (fallback)") + } + + fn supports_hardware_acceleration(&self, device: &wgpu::Device) -> bool { + // Check if handle is valid + if self.handle.is_null() { + return false; + } + + // Check if wgpu is using Metal backend + self.is_metal_backend(device) + } +} + +impl IOSurfaceImporter { + 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| { + let Some(device) = device else { + return Err(TextureImportError::HardwareUnavailable { + reason: "Device is not using Metal backend".to_string(), + }); + }; + + // Import IOSurface handle into Metal texture + let metal_texture = self.import_iosurface_to_metal(device)?; + + // 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 { + width: self.width, + height: self.height, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: format::cef_to_wgpu(self.format)?, + usage: wgpu::hal::TextureUses::RESOURCE, + memory_flags: wgpu::hal::MemoryFlags::empty(), + view_formats: vec![], + }, + 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) + } + + 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 cf_type = CFType::wrap_under_get_rule(self.handle as IOSurfaceRef); + IOSurface::from(cf_type) + }; + + // Get the Metal device from wgpu-hal + let metal_device = hal_device.raw_device(); + + // Convert CEF format to Metal pixel format + let metal_format = self.cef_to_metal_format(self.format)?; + + // Create Metal texture descriptor + let texture_descriptor = MTLTextureDescriptor::new(); + texture_descriptor.setTextureType(MTLTextureType::Type2D); + texture_descriptor.setPixelFormat(metal_format); + texture_descriptor.setWidth(self.width as usize); + texture_descriptor.setHeight(self.height as usize); + texture_descriptor.setDepth(1); + texture_descriptor.setMipmapLevelCount(1); + texture_descriptor.setSampleCount(1); + texture_descriptor.setUsage(MTLTextureUsage::ShaderRead); + + // Create Metal texture from IOSurface + 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(), + }); + }; + + tracing::trace!("Successfully created Metal texture from IOSurface"); + Ok(metal_texture) + } + + 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 }), + } + } + + 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 new file mode 100644 index 0000000000..485acf4d11 --- /dev/null +++ b/desktop/src/cef/texture_import/mod.rs @@ -0,0 +1,75 @@ +//! 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 native API +//! +//! # Usage +//! +//! ```no_run +//! // Import texture with automatic platform detection +//! let texture = shared_handle.import_texture(&device)?; +//! ``` +//! +//! # Features +//! +//! - `accelerated_paint` - Base feature for texture import +//! - `accelerated_paint_dmabuf` - Linux DMA-BUF support +//! - `accelerated_paint_d3d11` - Windows D3D11 support +//! - `accelerated_paint_iosurface` - macOS IOSurface support + +pub(crate) mod common; + +pub(crate) mod shared_texture_handle; +pub(crate) use shared_texture_handle::SharedTextureHandle; + +#[cfg(target_os = "linux")] +pub(crate) mod dmabuf; + +#[cfg(target_os = "windows")] +pub(crate) mod d3d11; + +#[cfg(target_os = "macos")] +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; + + /// 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 49543588fb..3babf73019 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 = 60; +pub(crate) const CEF_MESSAGE_LOOP_MAX_ITERATIONS: usize = 10; diff --git a/desktop/src/main.rs b/desktop/src/main.rs index afcff648e3..ba79e9b0e0 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; @@ -16,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), @@ -56,21 +57,6 @@ fn main() { tracing::info!("Cef initialized successfully"); - let rendering_loop_proxy = event_loop.create_proxy(); - let target_fps = 60; - 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(); - 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(())