Desktop: Implement GPU accelerated offscreen rendering and improve rendering efficency (#3056)
* WIP accelerade offscreen canvas implementation * Implement vulkan dmabuf import * Add fps printing * Add feature gates * Forgot to add file * Experimental windows support * Cast ptr to isize * Remove testing chrome://flags url * Experimental macos support for texture import * Cleanup code and improve latency / frame pacing * Add path for importing textures on windows through dx12 * Update doc comment * Import textures through metal on macos * Review cleanup --------- Co-authored-by: Timon Schelling <me@timon.zip>
This commit is contained in:
parent
97978c2491
commit
e56f858ced
|
|
@ -2077,14 +2077,19 @@ dependencies = [
|
||||||
name = "graphite-desktop"
|
name = "graphite-desktop"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"ash",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"cef",
|
"cef",
|
||||||
|
"core-foundation",
|
||||||
"derivative",
|
"derivative",
|
||||||
"dirs",
|
"dirs",
|
||||||
"futures",
|
"futures",
|
||||||
"glam",
|
"glam",
|
||||||
"graphite-desktop-wrapper",
|
"graphite-desktop-wrapper",
|
||||||
"include_dir",
|
"include_dir",
|
||||||
|
"libc",
|
||||||
|
"objc2-io-surface",
|
||||||
|
"objc2-metal 0.3.1",
|
||||||
"open",
|
"open",
|
||||||
"rfd",
|
"rfd",
|
||||||
"ron",
|
"ron",
|
||||||
|
|
@ -2093,6 +2098,7 @@ dependencies = [
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"vello",
|
"vello",
|
||||||
"wgpu",
|
"wgpu",
|
||||||
|
"windows",
|
||||||
"winit",
|
"winit",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -3489,7 +3495,7 @@ dependencies = [
|
||||||
"block2 0.5.1",
|
"block2 0.5.1",
|
||||||
"objc2 0.5.2",
|
"objc2 0.5.2",
|
||||||
"objc2-foundation 0.2.2",
|
"objc2-foundation 0.2.2",
|
||||||
"objc2-metal",
|
"objc2-metal 0.2.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -3544,6 +3550,19 @@ dependencies = [
|
||||||
"objc2-core-foundation",
|
"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]]
|
[[package]]
|
||||||
name = "objc2-link-presentation"
|
name = "objc2-link-presentation"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
|
|
@ -3568,6 +3587,20 @@ dependencies = [
|
||||||
"objc2-foundation 0.2.2",
|
"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]]
|
[[package]]
|
||||||
name = "objc2-quartz-core"
|
name = "objc2-quartz-core"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
|
|
@ -3578,7 +3611,7 @@ dependencies = [
|
||||||
"block2 0.5.1",
|
"block2 0.5.1",
|
||||||
"objc2 0.5.2",
|
"objc2 0.5.2",
|
||||||
"objc2-foundation 0.2.2",
|
"objc2-foundation 0.2.2",
|
||||||
"objc2-metal",
|
"objc2-metal 0.2.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,15 @@ edition = "2024"
|
||||||
rust-version = "1.87"
|
rust-version = "1.87"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["gpu"]
|
default = ["gpu", "accelerated_paint"]
|
||||||
gpu = ["graphite-desktop-wrapper/gpu"]
|
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]
|
[dependencies]
|
||||||
# # Local dependencies
|
# # Local dependencies
|
||||||
graphite-desktop-wrapper = { path = "wrapper" }
|
graphite-desktop-wrapper = { path = "wrapper" }
|
||||||
|
|
@ -32,3 +38,26 @@ vello = { workspace = true }
|
||||||
derivative = { workspace = true }
|
derivative = { workspace = true }
|
||||||
rfd = { workspace = true }
|
rfd = { workspace = true }
|
||||||
open = { 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 }
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,19 @@
|
||||||
use crate::CustomEvent;
|
use crate::CustomEvent;
|
||||||
use crate::cef::WindowSize;
|
use crate::cef::WindowSize;
|
||||||
use crate::consts::APP_NAME;
|
use crate::consts::{APP_NAME, CEF_MESSAGE_LOOP_MAX_ITERATIONS};
|
||||||
use crate::render::GraphicsState;
|
use crate::render::GraphicsState;
|
||||||
use graphite_desktop_wrapper::messages::{DesktopFrontendMessage, DesktopWrapperMessage};
|
use graphite_desktop_wrapper::messages::{DesktopFrontendMessage, DesktopWrapperMessage};
|
||||||
use graphite_desktop_wrapper::{DesktopWrapper, NodeGraphExecutionResult, WgpuContext, serialize_frontend_messages};
|
use graphite_desktop_wrapper::{DesktopWrapper, NodeGraphExecutionResult, WgpuContext, serialize_frontend_messages};
|
||||||
|
|
||||||
use rfd::AsyncFileDialog;
|
use rfd::AsyncFileDialog;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::mpsc::Sender;
|
use std::sync::mpsc::Sender;
|
||||||
|
use std::sync::mpsc::SyncSender;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use winit::application::ApplicationHandler;
|
use winit::application::ApplicationHandler;
|
||||||
use winit::dpi::PhysicalSize;
|
use winit::dpi::PhysicalSize;
|
||||||
use winit::event::StartCause;
|
|
||||||
use winit::event::WindowEvent;
|
use winit::event::WindowEvent;
|
||||||
use winit::event_loop::ActiveEventLoop;
|
use winit::event_loop::ActiveEventLoop;
|
||||||
use winit::event_loop::ControlFlow;
|
use winit::event_loop::ControlFlow;
|
||||||
|
|
@ -31,11 +32,23 @@ pub(crate) struct WinitApp {
|
||||||
wgpu_context: WgpuContext,
|
wgpu_context: WgpuContext,
|
||||||
event_loop_proxy: EventLoopProxy<CustomEvent>,
|
event_loop_proxy: EventLoopProxy<CustomEvent>,
|
||||||
desktop_wrapper: DesktopWrapper,
|
desktop_wrapper: DesktopWrapper,
|
||||||
|
last_ui_update: Instant,
|
||||||
|
avg_frame_time: f32,
|
||||||
|
start_render_sender: SyncSender<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WinitApp {
|
impl WinitApp {
|
||||||
pub(crate) fn new(cef_context: cef::Context<cef::Initialized>, window_size_sender: Sender<WindowSize>, wgpu_context: WgpuContext, event_loop_proxy: EventLoopProxy<CustomEvent>) -> Self {
|
pub(crate) fn new(cef_context: cef::Context<cef::Initialized>, window_size_sender: Sender<WindowSize>, wgpu_context: WgpuContext, event_loop_proxy: EventLoopProxy<CustomEvent>) -> 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 {
|
Self {
|
||||||
cef_context,
|
cef_context,
|
||||||
window: None,
|
window: None,
|
||||||
|
|
@ -44,7 +57,10 @@ impl WinitApp {
|
||||||
window_size_sender,
|
window_size_sender,
|
||||||
wgpu_context,
|
wgpu_context,
|
||||||
event_loop_proxy,
|
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<CustomEvent> for WinitApp {
|
||||||
// Set a timeout in case we miss any cef schedule requests
|
// Set a timeout in case we miss any cef schedule requests
|
||||||
let timeout = Instant::now() + Duration::from_millis(10);
|
let timeout = Instant::now() + Duration::from_millis(10);
|
||||||
let wait_until = timeout.min(self.cef_schedule.unwrap_or(timeout));
|
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
|
if let Some(schedule) = self.cef_schedule
|
||||||
&& schedule < Instant::now()
|
&& schedule < Instant::now()
|
||||||
{
|
{
|
||||||
self.cef_schedule = None;
|
self.cef_schedule = None;
|
||||||
self.cef_context.work();
|
// Poll cef message loop multiple times to avoid message loop starvation
|
||||||
}
|
for _ in 0..CEF_MESSAGE_LOOP_MAX_ITERATIONS {
|
||||||
if let StartCause::ResumeTimeReached { .. } = cause {
|
self.cef_context.work();
|
||||||
if let Some(window) = &self.window {
|
|
||||||
window.request_redraw();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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) {
|
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
||||||
|
|
@ -220,6 +233,11 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
|
||||||
if let Some(graphics_state) = self.graphics_state.as_mut() {
|
if let Some(graphics_state) = self.graphics_state.as_mut() {
|
||||||
graphics_state.resize(texture.width(), texture.height());
|
graphics_state.resize(texture.width(), texture.height());
|
||||||
graphics_state.bind_ui_texture(texture);
|
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 {
|
if let Some(window) = &self.window {
|
||||||
window.request_redraw();
|
window.request_redraw();
|
||||||
|
|
@ -251,16 +269,18 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
|
||||||
WindowEvent::RedrawRequested => {
|
WindowEvent::RedrawRequested => {
|
||||||
let Some(ref mut graphics_state) = self.graphics_state else { return };
|
let Some(ref mut graphics_state) = self.graphics_state else { return };
|
||||||
// Only rerender once we have a new ui texture to display
|
// Only rerender once we have a new ui texture to display
|
||||||
|
if let Some(window) = &self.window {
|
||||||
match graphics_state.render() {
|
match graphics_state.render(window.as_ref()) {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(wgpu::SurfaceError::Lost) => {
|
Err(wgpu::SurfaceError::Lost) => {
|
||||||
tracing::warn!("lost surface");
|
tracing::warn!("lost surface");
|
||||||
|
}
|
||||||
|
Err(wgpu::SurfaceError::OutOfMemory) => {
|
||||||
|
event_loop.exit();
|
||||||
|
}
|
||||||
|
Err(e) => tracing::error!("{:?}", e),
|
||||||
}
|
}
|
||||||
Err(wgpu::SurfaceError::OutOfMemory) => {
|
let _ = self.start_render_sender.try_send(());
|
||||||
event_loop.exit();
|
|
||||||
}
|
|
||||||
Err(e) => tracing::error!("{:?}", e),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Currently not supported on wayland see https://github.com/rust-windowing/winit/issues/1881
|
// Currently not supported on wayland see https://github.com/rust-windowing/winit/issues/1881
|
||||||
|
|
|
||||||
|
|
@ -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::CustomEvent;
|
||||||
use crate::render::FrameBufferRef;
|
use crate::render::FrameBufferRef;
|
||||||
use graphite_desktop_wrapper::{WgpuContext, deserialize_editor_message};
|
use graphite_desktop_wrapper::{WgpuContext, deserialize_editor_message};
|
||||||
|
|
@ -10,15 +25,23 @@ mod dirs;
|
||||||
mod input;
|
mod input;
|
||||||
mod internal;
|
mod internal;
|
||||||
mod ipc;
|
mod ipc;
|
||||||
|
mod platform;
|
||||||
mod scheme_handler;
|
mod scheme_handler;
|
||||||
mod utility;
|
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};
|
pub(crate) use context::{Context, InitError, Initialized, Setup, SetupError};
|
||||||
use winit::event_loop::EventLoopProxy;
|
use winit::event_loop::EventLoopProxy;
|
||||||
|
|
||||||
pub(crate) trait CefEventHandler: Clone {
|
pub(crate) trait CefEventHandler: Clone {
|
||||||
fn window_size(&self) -> WindowSize;
|
fn window_size(&self) -> WindowSize;
|
||||||
fn draw<'a>(&self, frame_buffer: FrameBufferRef<'a>);
|
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
|
/// 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.
|
/// [`_cef_browser_process_handler_t::on_schedule_message_pump_work`] for more documentation.
|
||||||
fn schedule_cef_message_loop_work(&self, scheduled_time: Instant);
|
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));
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -86,14 +86,17 @@ impl Context<Setup> {
|
||||||
let mut client = Client::new(BrowserProcessClientImpl::new(render_handler, event_handler.clone()));
|
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!("{GRAPHITE_SCHEME}://{FRONTEND_DOMAIN}/").as_str());
|
||||||
|
// let url = CefString::from("chrome://gpu");
|
||||||
|
|
||||||
let window_info = WindowInfo {
|
let window_info = WindowInfo {
|
||||||
windowless_rendering_enabled: 1,
|
windowless_rendering_enabled: 1,
|
||||||
|
#[cfg(feature = "accelerated_paint")]
|
||||||
|
shared_texture_enabled: if crate::cef::platform::should_enable_hardware_acceleration() { 1 } else { 0 },
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let settings = BrowserSettings {
|
let settings = BrowserSettings {
|
||||||
windowless_frame_rate: 60,
|
windowless_frame_rate: crate::consts::CEF_WINDOWLESS_FRAME_RATE,
|
||||||
background_color: 0x0,
|
background_color: 0x0,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ mod browser_process_app;
|
||||||
mod browser_process_client;
|
mod browser_process_client;
|
||||||
mod browser_process_handler;
|
mod browser_process_handler;
|
||||||
mod browser_process_life_span_handler;
|
mod browser_process_life_span_handler;
|
||||||
mod render_handler;
|
pub mod render_handler;
|
||||||
mod render_process_app;
|
mod render_process_app;
|
||||||
mod render_process_handler;
|
mod render_process_handler;
|
||||||
mod render_process_v8_handler;
|
mod render_process_v8_handler;
|
||||||
|
|
|
||||||
|
|
@ -34,12 +34,28 @@ impl<H: CefEventHandler + Clone> ImplApp for BrowserProcessAppImpl<H> {
|
||||||
|
|
||||||
fn on_before_command_line_processing(&self, _process_type: Option<&cef::CefString>, command_line: Option<&mut cef::CommandLine>) {
|
fn on_before_command_line_processing(&self, _process_type: Option<&cef::CefString>, command_line: Option<&mut cef::CommandLine>) {
|
||||||
if let Some(cmd) = command_line {
|
if let Some(cmd) = command_line {
|
||||||
// Disable GPU acceleration, because it is not supported for Offscreen Rendering and can cause crashes.
|
#[cfg(not(feature = "accelerated_paint"))]
|
||||||
cmd.append_switch(Some(&CefString::from("disable-gpu")));
|
{
|
||||||
cmd.append_switch(Some(&CefString::from("disable-gpu-compositing")));
|
// 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
|
// 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")
|
let use_wayland = env::var("WAYLAND_DISPLAY")
|
||||||
.ok()
|
.ok()
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ pub(crate) struct RenderHandlerImpl<H: CefEventHandler> {
|
||||||
object: *mut RcImpl<_cef_render_handler_t, Self>,
|
object: *mut RcImpl<_cef_render_handler_t, Self>,
|
||||||
event_handler: H,
|
event_handler: H,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<H: CefEventHandler> RenderHandlerImpl<H> {
|
impl<H: CefEventHandler> RenderHandlerImpl<H> {
|
||||||
pub(crate) fn new(event_handler: H) -> Self {
|
pub(crate) fn new(event_handler: H) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
@ -47,6 +48,23 @@ impl<H: CefEventHandler> ImplRenderHandler for RenderHandlerImpl<H> {
|
||||||
self.event_handler.draw(frame_buffer)
|
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 {
|
fn get_raw(&self) -> *mut _cef_render_handler_t {
|
||||||
self.object.cast()
|
self.object.cast()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<wgpu::TextureFormat, TextureImportError> {
|
||||||
|
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<vk::Format, TextureImportError> {
|
||||||
|
match format {
|
||||||
|
cef_color_type_t::CEF_COLOR_TYPE_BGRA_8888 => Ok(vk::Format::B8G8R8A8_UNORM),
|
||||||
|
cef_color_type_t::CEF_COLOR_TYPE_RGBA_8888 => Ok(vk::Format::R8G8B8A8_UNORM),
|
||||||
|
_ => 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<u32> {
|
||||||
|
(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::<api::Vulkan, _, _>(|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::<api::Dx12, _, _>(|device| {
|
||||||
|
is_d3d12 = device.is_some();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
is_d3d12
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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::<api::Dx12, _, _>(|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 = <api::Dx12 as wgpu::hal::Api>::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::<api::Dx12>(
|
||||||
|
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::<api::Vulkan, _, _>(|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 = <api::Vulkan as wgpu::hal::Api>::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::<Vulkan>(
|
||||||
|
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: &<api::Vulkan as wgpu::hal::Api>::Device) -> Result<vk::Image, TextureImportError> {
|
||||||
|
// 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: &<wgpu::hal::api::Dx12 as wgpu::hal::Api>::Device) -> Result<windows::Win32::Graphics::Direct3D12::ID3D12Resource, TextureImportError> {
|
||||||
|
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<ID3D12Resource> = 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()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<std::os::fd::RawFd>,
|
||||||
|
format: cef_color_type_t,
|
||||||
|
modifier: u64,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
strides: Vec<u32>,
|
||||||
|
offsets: Vec<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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::<api::Vulkan, _, _>(|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 = <api::Vulkan as wgpu::hal::Api>::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::<Vulkan>(
|
||||||
|
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: &<api::Vulkan as wgpu::hal::Api>::Device) -> Result<vk::Image, TextureImportError> {
|
||||||
|
// 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<Vec<vk::SubresourceLayout>, 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<std::os::fd::RawFd> {
|
||||||
|
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<u32> {
|
||||||
|
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<u32> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
@ -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::<api::Metal, _, _>(|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 = <api::Metal as wgpu::hal::Api>::Device::texture_from_raw(
|
||||||
|
metal_texture,
|
||||||
|
&wgpu::hal::TextureDescriptor {
|
||||||
|
label: Some("CEF IOSurface Texture"),
|
||||||
|
size: wgpu::Extent3d {
|
||||||
|
width: self.width,
|
||||||
|
height: self.height,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format: format::cef_to_wgpu(self.format)?,
|
||||||
|
usage: wgpu::hal::TextureUses::RESOURCE,
|
||||||
|
memory_flags: wgpu::hal::MemoryFlags::empty(),
|
||||||
|
view_formats: vec![],
|
||||||
|
},
|
||||||
|
None, // drop_callback
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(hal_texture)
|
||||||
|
})
|
||||||
|
}?;
|
||||||
|
|
||||||
|
// Import hal texture into wgpu
|
||||||
|
let texture = unsafe {
|
||||||
|
device.create_texture_from_hal::<Metal>(
|
||||||
|
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: &<api::Metal as wgpu::hal::Api>::Device) -> Result<<api::Metal as wgpu::hal::Api>::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<MTLPixelFormat, TextureImportError> {
|
||||||
|
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::<api::Metal, _, _>(|device| {
|
||||||
|
is_metal = device.is_some();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
is_metal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<wgpu::Texture, TextureImportError>;
|
||||||
|
|
||||||
|
/// 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;
|
||||||
|
}
|
||||||
|
|
@ -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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
pub(crate) static APP_NAME: &str = "Graphite";
|
pub(crate) static APP_NAME: &str = "Graphite";
|
||||||
pub(crate) static APP_ID: &str = "rs.graphite.GraphiteEditor";
|
pub(crate) static APP_ID: &str = "rs.graphite.GraphiteEditor";
|
||||||
pub(crate) static APP_DIRECTORY_NAME: &str = "graphite-editor";
|
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;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::Instant;
|
||||||
|
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
use winit::event_loop::EventLoop;
|
use winit::event_loop::EventLoop;
|
||||||
|
|
||||||
|
|
@ -16,7 +17,7 @@ use app::WinitApp;
|
||||||
mod dirs;
|
mod dirs;
|
||||||
|
|
||||||
use graphite_desktop_wrapper::messages::DesktopWrapperMessage;
|
use graphite_desktop_wrapper::messages::DesktopWrapperMessage;
|
||||||
use graphite_desktop_wrapper::{DesktopWrapper, NodeGraphExecutionResult, WgpuContext};
|
use graphite_desktop_wrapper::{NodeGraphExecutionResult, WgpuContext};
|
||||||
|
|
||||||
pub(crate) enum CustomEvent {
|
pub(crate) enum CustomEvent {
|
||||||
UiUpdate(wgpu::Texture),
|
UiUpdate(wgpu::Texture),
|
||||||
|
|
@ -56,21 +57,6 @@ fn main() {
|
||||||
|
|
||||||
tracing::info!("Cef initialized successfully");
|
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());
|
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();
|
event_loop.run_app(&mut winit_app).unwrap();
|
||||||
|
|
|
||||||
|
|
@ -232,7 +232,7 @@ impl GraphicsState {
|
||||||
self.bind_overlays_texture(texture);
|
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() {
|
if let Some(scene) = self.overlays_scene.take() {
|
||||||
self.render_overlays(scene);
|
self.render_overlays(scene);
|
||||||
}
|
}
|
||||||
|
|
@ -275,6 +275,7 @@ impl GraphicsState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.context.queue.submit(std::iter::once(encoder.finish()));
|
self.context.queue.submit(std::iter::once(encoder.finish()));
|
||||||
|
window.pre_present_notify();
|
||||||
output.present();
|
output.present();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue