mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-21 00:58:11 +08:00
Desktop: Isolate CEF-rendered UI into separate crate and process (#4321)
* Extract CEF rendered UI into a separate process and crate * Review * Review * Review * Review * Remove necessary workarounds * Block on frame copy ack * Crop and resample frames correctly * Skip blank frames * Fix deps * Fix fmt * Fix clippy warning * Review * Fix todo
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
use cef::sys::cef_color_type_t;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(crate) mod d3d11;
|
||||
#[cfg(target_os = "linux")]
|
||||
pub(crate) mod dmabuf;
|
||||
#[cfg(target_os = "macos")]
|
||||
pub(crate) mod iosurface;
|
||||
|
||||
pub(crate) type TextureImportResult = Result<wgpu::Texture, TextureImportError>;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub(crate) enum TextureImportError {
|
||||
#[error("Invalid texture handle: {0}")]
|
||||
InvalidHandle(String),
|
||||
#[error("Unsupported texture format: {format:?}")]
|
||||
UnsupportedFormat { format: cef_color_type_t },
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
#[error("Hardware acceleration not available: {reason}")]
|
||||
HardwareUnavailable { reason: String },
|
||||
#[error("Vulkan operation failed: {operation}")]
|
||||
#[cfg(target_os = "linux")]
|
||||
VulkanError { operation: String },
|
||||
#[error("Platform-specific error: {message}")]
|
||||
PlatformError { message: String },
|
||||
}
|
||||
|
||||
impl From<wgpu::hal::DeviceError> for TextureImportError {
|
||||
fn from(e: wgpu::hal::DeviceError) -> Self {
|
||||
TextureImportError::PlatformError {
|
||||
message: format!("wgpu-hal DeviceError: {:?}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default, serde::Serialize, serde::Deserialize)]
|
||||
pub(crate) struct ContentRect {
|
||||
pub(crate) x: u32,
|
||||
pub(crate) y: u32,
|
||||
pub(crate) width: u32,
|
||||
pub(crate) height: u32,
|
||||
pub(crate) source_width: u32,
|
||||
pub(crate) source_height: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) enum ContentMapping {
|
||||
Identity,
|
||||
Scaled(ContentRect),
|
||||
}
|
||||
|
||||
impl ContentRect {
|
||||
pub(crate) fn mapping(self, width: u32, height: u32) -> ContentMapping {
|
||||
let valid = self.width > 0
|
||||
&& self.height > 0
|
||||
&& self.source_width > 0
|
||||
&& self.source_height > 0
|
||||
&& self.x.checked_add(self.width).is_some_and(|right| right <= width)
|
||||
&& self.y.checked_add(self.height).is_some_and(|bottom| bottom <= height);
|
||||
let full = self.x == 0 && self.y == 0 && (self.width, self.height) == (width, height) && (self.source_width, self.source_height) == (width, height);
|
||||
if valid && !full { ContentMapping::Scaled(self) } else { ContentMapping::Identity }
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&cef::AcceleratedPaintInfo> for ContentRect {
|
||||
type Error = TextureImportError;
|
||||
|
||||
fn try_from(info: &cef::AcceleratedPaintInfo) -> Result<Self, Self::Error> {
|
||||
let invalid = || TextureImportError::InvalidHandle("Failed to create content rect".into());
|
||||
let content = &info.extra.content_rect;
|
||||
let width = u32::try_from(content.width).ok().filter(|&width| width > 0).ok_or_else(invalid)?;
|
||||
let height = u32::try_from(content.height).ok().filter(|&height| height > 0).ok_or_else(invalid)?;
|
||||
let source = &info.extra.source_size;
|
||||
let (source_width, source_height) = if info.extra.has_source_size != 0 && source.width > 0 && source.height > 0 {
|
||||
(source.width as u32, source.height as u32)
|
||||
} else {
|
||||
(width, height)
|
||||
};
|
||||
Ok(Self {
|
||||
x: content.x.max(0) as u32,
|
||||
y: content.y.max(0) as u32,
|
||||
width,
|
||||
height,
|
||||
source_width,
|
||||
source_height,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait TextureImporter {
|
||||
fn import_to_wgpu(&self, device: &wgpu::Device) -> TextureImportResult;
|
||||
}
|
||||
|
||||
fn wgpu_format(format: cef_color_type_t) -> Result<wgpu::TextureFormat, TextureImportError> {
|
||||
match format {
|
||||
cef_color_type_t::CEF_COLOR_TYPE_BGRA_8888 => Ok(wgpu::TextureFormat::Bgra8Unorm),
|
||||
cef_color_type_t::CEF_COLOR_TYPE_RGBA_8888 => Ok(wgpu::TextureFormat::Rgba8Unorm),
|
||||
_ => Err(TextureImportError::UnsupportedFormat { format }),
|
||||
}
|
||||
}
|
||||
|
||||
fn texture_descriptor(width: u32, height: u32, format: cef_color_type_t, label: &'static str) -> Result<wgpu::TextureDescriptor<'static>, TextureImportError> {
|
||||
Ok(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(format)?,
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_SRC,
|
||||
view_formats: &[],
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
use super::{TextureImportError, TextureImportResult, TextureImporter, texture_descriptor, wgpu_format};
|
||||
use cef::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 import_to_wgpu(&self, device: &wgpu::Device) -> TextureImportResult {
|
||||
if self.handle.is_null() {
|
||||
return Err(TextureImportError::InvalidHandle("Null D3D11 shared texture handle".to_string()));
|
||||
}
|
||||
|
||||
let is_d3d12_backend = unsafe { device.as_hal::<api::Dx12>().is_some() };
|
||||
|
||||
if is_d3d12_backend {
|
||||
let texture = self.import_via_d3d12(device)?;
|
||||
return Ok(texture);
|
||||
}
|
||||
|
||||
let texture = self.import_via_vulkan(device)?;
|
||||
tracing::trace!("Successfully imported D3D11 shared texture via Vulkan");
|
||||
Ok(texture)
|
||||
}
|
||||
}
|
||||
|
||||
impl D3D11Importer {
|
||||
pub fn from_parts(handle: u64, width: u32, height: u32, format: cef_color_type_t) -> Self {
|
||||
Self {
|
||||
handle: handle as *mut c_void,
|
||||
format,
|
||||
width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
|
||||
fn import_via_d3d12(&self, device: &wgpu::Device) -> TextureImportResult {
|
||||
use wgpu::hal::api;
|
||||
let hal_texture = unsafe {
|
||||
let hal_device_guard = device.as_hal::<api::Dx12>();
|
||||
let Some(hal_device) = hal_device_guard else {
|
||||
return Err(TextureImportError::HardwareUnavailable {
|
||||
reason: "Device is not using D3D12 backend".to_string(),
|
||||
});
|
||||
};
|
||||
|
||||
let d3d12_resource = self.import_d3d11_handle_to_d3d12(&hal_device)?;
|
||||
|
||||
let hal_texture = <api::Dx12 as wgpu::hal::Api>::Device::texture_from_raw(
|
||||
d3d12_resource,
|
||||
wgpu_format(self.format)?,
|
||||
wgpu::TextureDimension::D2,
|
||||
wgpu::Extent3d {
|
||||
width: self.width,
|
||||
height: self.height,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
1, // mip_level_count
|
||||
1, // sample_count
|
||||
);
|
||||
|
||||
Ok::<_, TextureImportError>(hal_texture)
|
||||
}?;
|
||||
|
||||
let texture = unsafe { device.create_texture_from_hal::<api::Dx12>(hal_texture, &texture_descriptor(self.width, self.height, self.format, "CEF D3D11→D3D12 Shared Texture")?) };
|
||||
|
||||
Ok(texture)
|
||||
}
|
||||
|
||||
fn import_via_vulkan(&self, device: &wgpu::Device) -> TextureImportResult {
|
||||
use wgpu::{TextureUses, wgc::api::Vulkan};
|
||||
let hal_texture = unsafe {
|
||||
let hal_device_guard = device.as_hal::<Vulkan>();
|
||||
let Some(hal_device) = hal_device_guard else {
|
||||
return Err(TextureImportError::HardwareUnavailable {
|
||||
reason: "Device is not using Vulkan backend".to_string(),
|
||||
});
|
||||
};
|
||||
|
||||
let hal_texture = <Vulkan as wgpu::hal::Api>::Device::texture_from_d3d11_shared_handle(
|
||||
&hal_device,
|
||||
windows::Win32::Foundation::HANDLE(self.handle),
|
||||
&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: wgpu_format(self.format)?,
|
||||
usage: TextureUses::COPY_DST | TextureUses::COPY_SRC | TextureUses::RESOURCE,
|
||||
memory_flags: wgpu::hal::MemoryFlags::empty(),
|
||||
view_formats: vec![],
|
||||
},
|
||||
)
|
||||
.map_err(|e| TextureImportError::PlatformError {
|
||||
message: format!("Failed to import D3D11 shared handle into Vulkan: {:?}", e),
|
||||
})?;
|
||||
|
||||
Ok::<_, TextureImportError>(hal_texture)
|
||||
}?;
|
||||
|
||||
let texture = unsafe { device.create_texture_from_hal::<Vulkan>(hal_texture, &texture_descriptor(self.width, self.height, self.format, "CEF D3D11 Shared Texture")?) };
|
||||
|
||||
Ok(texture)
|
||||
}
|
||||
|
||||
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::*;
|
||||
|
||||
let d3d12_device = hal_device.raw_device();
|
||||
|
||||
if self.width == 0 || self.height == 0 {
|
||||
return Err(TextureImportError::InvalidHandle("Invalid D3D11 texture dimensions".to_string()));
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let mut shared_resource: Option<ID3D12Resource> = None;
|
||||
d3d12_device
|
||||
.OpenSharedHandle(windows::Win32::Foundation::HANDLE(self.handle), &mut shared_resource)
|
||||
.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,226 @@
|
||||
use super::{TextureImportError, TextureImportResult, TextureImporter, texture_descriptor, wgpu_format};
|
||||
use ash::vk;
|
||||
use cef::sys::cef_color_type_t;
|
||||
use wgpu::hal::api;
|
||||
|
||||
pub struct DmaBufImporter {
|
||||
fds: Vec<std::os::fd::OwnedFd>,
|
||||
format: cef_color_type_t,
|
||||
modifier: u64,
|
||||
width: u32,
|
||||
height: u32,
|
||||
strides: Vec<u32>,
|
||||
offsets: Vec<u32>,
|
||||
}
|
||||
|
||||
impl TextureImporter for DmaBufImporter {
|
||||
fn import_to_wgpu(&self, device: &wgpu::Device) -> TextureImportResult {
|
||||
if self.fds.len() != 1 {
|
||||
return Err(TextureImportError::InvalidHandle(format!("Expected exactly one DMA-BUF plane fd, got {}", self.fds.len())));
|
||||
}
|
||||
|
||||
if self.strides.len() != self.fds.len() || self.offsets.len() != self.fds.len() {
|
||||
return Err(TextureImportError::InvalidHandle(format!(
|
||||
"DMA-BUF plane count mismatch: {} fds, {} strides, {} offsets",
|
||||
self.fds.len(),
|
||||
self.strides.len(),
|
||||
self.offsets.len()
|
||||
)));
|
||||
}
|
||||
|
||||
let texture = self.import_via_vulkan(device)?;
|
||||
tracing::trace!("Successfully imported DMA-BUF texture via Vulkan");
|
||||
Ok(texture)
|
||||
}
|
||||
}
|
||||
|
||||
impl DmaBufImporter {
|
||||
pub fn from_parts(fds: Vec<std::os::fd::OwnedFd>, strides: Vec<u32>, offsets: Vec<u32>, modifier: u64, width: u32, height: u32, format: cef_color_type_t) -> Self {
|
||||
Self {
|
||||
fds,
|
||||
format,
|
||||
modifier,
|
||||
width,
|
||||
height,
|
||||
strides,
|
||||
offsets,
|
||||
}
|
||||
}
|
||||
|
||||
fn import_via_vulkan(&self, device: &wgpu::Device) -> TextureImportResult {
|
||||
use wgpu::{TextureUses, wgc::api::Vulkan};
|
||||
let hal_texture = unsafe {
|
||||
let hal_device_guard = device.as_hal::<api::Vulkan>();
|
||||
let Some(hal_device) = hal_device_guard else {
|
||||
return Err(TextureImportError::HardwareUnavailable {
|
||||
reason: "Device is not using Vulkan backend".to_string(),
|
||||
});
|
||||
};
|
||||
|
||||
let (vk_image, device_memory) = self.create_vulkan_image_from_dmabuf(&hal_device)?;
|
||||
|
||||
let hal_texture = <api::Vulkan as wgpu::hal::Api>::Device::texture_from_raw(
|
||||
&hal_device,
|
||||
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: wgpu_format(self.format)?,
|
||||
usage: TextureUses::COPY_DST | TextureUses::COPY_SRC | TextureUses::RESOURCE,
|
||||
memory_flags: wgpu::hal::MemoryFlags::empty(),
|
||||
view_formats: vec![],
|
||||
},
|
||||
None,
|
||||
wgpu::hal::vulkan::TextureMemory::Dedicated(device_memory),
|
||||
);
|
||||
|
||||
Ok::<_, TextureImportError>(hal_texture)
|
||||
}?;
|
||||
|
||||
let texture = unsafe { device.create_texture_from_hal::<Vulkan>(hal_texture, &texture_descriptor(self.width, self.height, self.format, "CEF DMA-BUF Texture")?) };
|
||||
|
||||
Ok(texture)
|
||||
}
|
||||
|
||||
fn create_vulkan_image_from_dmabuf(&self, hal_device: &<api::Vulkan as wgpu::hal::Api>::Device) -> Result<(vk::Image, vk::DeviceMemory), TextureImportError> {
|
||||
let device = hal_device.raw_device();
|
||||
let instance = hal_device.shared_instance().raw_instance();
|
||||
|
||||
if self.width == 0 || self.height == 0 {
|
||||
return Err(TextureImportError::InvalidHandle("Invalid DMA-BUF dimensions".to_string()));
|
||||
}
|
||||
|
||||
let image_create_info = vk::ImageCreateInfo::default()
|
||||
.image_type(vk::ImageType::TYPE_2D)
|
||||
.format(vulkan_format(self.format)?)
|
||||
.extent(vk::Extent3D {
|
||||
width: self.width,
|
||||
height: self.height,
|
||||
depth: 1,
|
||||
})
|
||||
.mip_levels(1)
|
||||
.array_layers(1)
|
||||
.samples(vk::SampleCountFlags::TYPE_1)
|
||||
.tiling(vk::ImageTiling::DRM_FORMAT_MODIFIER_EXT)
|
||||
.usage(vk::ImageUsageFlags::SAMPLED | vk::ImageUsageFlags::COLOR_ATTACHMENT | vk::ImageUsageFlags::TRANSFER_SRC)
|
||||
.sharing_mode(vk::SharingMode::EXCLUSIVE);
|
||||
|
||||
let plane_layouts = self
|
||||
.offsets
|
||||
.iter()
|
||||
.zip(&self.strides)
|
||||
.map(|(&offset, &stride)| vk::SubresourceLayout {
|
||||
offset: offset as u64,
|
||||
size: 0, // Will be calculated by driver
|
||||
row_pitch: stride as u64,
|
||||
array_pitch: 0,
|
||||
depth_pitch: 0,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let mut drm_format_modifier = vk::ImageDrmFormatModifierExplicitCreateInfoEXT::default()
|
||||
.drm_format_modifier(self.modifier)
|
||||
.plane_layouts(&plane_layouts);
|
||||
|
||||
let mut external_memory_info = vk::ExternalMemoryImageCreateInfo::default().handle_types(vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT);
|
||||
|
||||
let image_create_info = image_create_info.push_next(&mut drm_format_modifier).push_next(&mut external_memory_info);
|
||||
|
||||
let image = unsafe {
|
||||
device.create_image(&image_create_info, None).map_err(|e| TextureImportError::VulkanError {
|
||||
operation: format!("Failed to create Vulkan image: {e:?}"),
|
||||
})?
|
||||
};
|
||||
|
||||
let memory_requirements = unsafe { device.get_image_memory_requirements(image) };
|
||||
|
||||
// Duplicate the file descriptor
|
||||
let dup_fd = unsafe { libc::dup(std::os::fd::AsRawFd::as_raw_fd(&self.fds[0])) };
|
||||
if dup_fd == -1 {
|
||||
// SAFETY: the image was created above and never bound or returned.
|
||||
unsafe { device.destroy_image(image, None) };
|
||||
return Err(TextureImportError::PlatformError {
|
||||
message: "Failed to duplicate DMA-BUF file descriptor".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
let external_memory_fd = ash::khr::external_memory_fd::Device::new(instance, device);
|
||||
let mut fd_properties = vk::MemoryFdPropertiesKHR::default();
|
||||
if let Err(e) = unsafe { external_memory_fd.get_memory_fd_properties(vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT, dup_fd, &mut fd_properties) } {
|
||||
// SAFETY: import failed and the fd is still ours, need to clean up the image and close the fd
|
||||
unsafe {
|
||||
device.destroy_image(image, None);
|
||||
libc::close(dup_fd);
|
||||
}
|
||||
return Err(TextureImportError::VulkanError {
|
||||
operation: format!("Failed to query DMA-BUF fd memory properties: {e:?}"),
|
||||
});
|
||||
}
|
||||
|
||||
let mut import_memory_fd = vk::ImportMemoryFdInfoKHR::default().handle_type(vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT).fd(dup_fd);
|
||||
|
||||
let memory_properties = unsafe { instance.get_physical_device_memory_properties(hal_device.raw_physical_device()) };
|
||||
|
||||
let compatible_type_bits = memory_requirements.memory_type_bits & fd_properties.memory_type_bits;
|
||||
let Some(memory_type_index) = find_memory_type_index(compatible_type_bits, vk::MemoryPropertyFlags::empty(), &memory_properties) else {
|
||||
// SAFETY: import failed and the fd is still ours, need to clean up the image and close the fd
|
||||
unsafe {
|
||||
device.destroy_image(image, None);
|
||||
libc::close(dup_fd);
|
||||
}
|
||||
return Err(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 = match unsafe { device.allocate_memory(&allocate_info, None) } {
|
||||
Ok(memory) => memory,
|
||||
Err(e) => {
|
||||
// SAFETY: import failed and the fd is still ours, need to clean up the image and close the fd
|
||||
unsafe {
|
||||
device.destroy_image(image, None);
|
||||
libc::close(dup_fd);
|
||||
}
|
||||
return Err(TextureImportError::VulkanError {
|
||||
operation: format!("Failed to allocate memory for DMA-BUF: {e:?}"),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(e) = unsafe { device.bind_image_memory(image, device_memory, 0) } {
|
||||
// SAFETY: import failed, need to clean up the image and free the memory
|
||||
unsafe {
|
||||
device.destroy_image(image, None);
|
||||
device.free_memory(device_memory, None);
|
||||
}
|
||||
return Err(TextureImportError::VulkanError {
|
||||
operation: format!("Failed to bind memory to image: {e:?}"),
|
||||
});
|
||||
}
|
||||
|
||||
Ok((image, device_memory))
|
||||
}
|
||||
}
|
||||
|
||||
fn vulkan_format(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 }),
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
use super::{TextureImportError, TextureImportResult, TextureImporter, texture_descriptor};
|
||||
use cef::sys::cef_color_type_t;
|
||||
use objc2::rc::Retained;
|
||||
use objc2_io_surface::IOSurfaceRef;
|
||||
use objc2_metal::{MTLDevice, MTLPixelFormat, MTLStorageMode, MTLTextureDescriptor, MTLTextureType, MTLTextureUsage};
|
||||
use wgpu::TextureDescriptor;
|
||||
|
||||
use std::os::raw::c_void;
|
||||
|
||||
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 import_to_wgpu(&self, device: &wgpu::Device) -> TextureImportResult {
|
||||
let texture = self.import_via_metal(device)?;
|
||||
tracing::trace!("Successfully imported IOSurface texture via Metal");
|
||||
Ok(texture)
|
||||
}
|
||||
}
|
||||
|
||||
impl IOSurfaceImporter {
|
||||
pub fn from_parts(handle: *mut c_void, width: u32, height: u32, format: cef_color_type_t) -> Self {
|
||||
Self { handle, format, width, height }
|
||||
}
|
||||
|
||||
fn get_metal_desc(&self, texture_desc: &TextureDescriptor) -> Result<Retained<MTLTextureDescriptor>, TextureImportError> {
|
||||
if self.width == 0 || self.height == 0 {
|
||||
return Err(TextureImportError::InvalidHandle("Invalid IOSurface texture dimensions".to_string()));
|
||||
}
|
||||
|
||||
let metal_desc = MTLTextureDescriptor::new();
|
||||
unsafe {
|
||||
metal_desc.setWidth(texture_desc.size.width as _);
|
||||
metal_desc.setHeight(texture_desc.size.height as _);
|
||||
metal_desc.setArrayLength(texture_desc.array_layer_count() as _);
|
||||
metal_desc.setMipmapLevelCount(texture_desc.mip_level_count as _);
|
||||
metal_desc.setSampleCount(texture_desc.sample_count as _);
|
||||
metal_desc.setTextureType(MTLTextureType::Type2D);
|
||||
metal_desc.setPixelFormat(match texture_desc.format {
|
||||
wgpu::TextureFormat::Rgba8Unorm => MTLPixelFormat::RGBA8Unorm,
|
||||
wgpu::TextureFormat::Bgra8Unorm => MTLPixelFormat::BGRA8Unorm,
|
||||
_ => unimplemented!(),
|
||||
});
|
||||
metal_desc.setUsage(MTLTextureUsage::ShaderRead);
|
||||
metal_desc.setStorageMode(MTLStorageMode::Managed);
|
||||
}
|
||||
|
||||
Ok(metal_desc)
|
||||
}
|
||||
|
||||
fn import_via_metal(&self, device: &wgpu::Device) -> TextureImportResult {
|
||||
let io_surface = std::ptr::NonNull::new(self.handle.cast::<IOSurfaceRef>()).ok_or(TextureImportError::InvalidHandle("Invalid IOSurface handle".to_string()))?;
|
||||
|
||||
let texture_desc = texture_descriptor(self.width, self.height, self.format, "Cef Texture")?;
|
||||
let hal_tex = {
|
||||
let metal_desc = self.get_metal_desc(&texture_desc)?;
|
||||
|
||||
let texture = unsafe {
|
||||
let hal_device_guard = device.as_hal::<wgpu::wgc::api::Metal>();
|
||||
let Some(hal_device) = hal_device_guard else {
|
||||
return Err(TextureImportError::InvalidHandle("Failed to get Metal device from wgpu".to_string()));
|
||||
};
|
||||
|
||||
let texture = hal_device
|
||||
.raw_device()
|
||||
.newTextureWithDescriptor_iosurface_plane(metal_desc.as_ref(), io_surface.as_ref(), 0)
|
||||
.ok_or(TextureImportError::InvalidHandle("Invalid IOSurface handle".to_string()))?;
|
||||
|
||||
let hal_tex = <wgpu::wgc::api::Metal as wgpu::hal::Api>::Device::texture_from_raw(
|
||||
texture,
|
||||
texture_desc.format,
|
||||
MTLTextureType::Type2D,
|
||||
texture_desc.array_layer_count(),
|
||||
texture_desc.mip_level_count,
|
||||
wgpu::hal::CopyExtent {
|
||||
width: texture_desc.size.width,
|
||||
height: texture_desc.size.height,
|
||||
depth: texture_desc.array_layer_count(),
|
||||
},
|
||||
);
|
||||
|
||||
Ok::<_, TextureImportError>(hal_tex)
|
||||
}?;
|
||||
texture
|
||||
};
|
||||
|
||||
Ok(unsafe { device.create_texture_from_hal::<wgpu::wgc::api::Metal>(hal_tex, &texture_desc) })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
#[cfg(target_os = "linux")]
|
||||
mod linux;
|
||||
#[cfg(target_os = "linux")]
|
||||
pub(crate) use linux::*;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
mod win;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(crate) use win::*;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
mod mac;
|
||||
#[cfg(target_os = "macos")]
|
||||
pub(crate) use mac::*;
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
pub(crate) enum RecvResult {
|
||||
Frame(WireFrame),
|
||||
WouldBlock,
|
||||
#[cfg_attr(target_os = "macos", allow(dead_code))]
|
||||
Closed,
|
||||
}
|
||||
|
||||
/// Decode the wire representation of `cef_color_type_t` (its `u32` discriminant),
|
||||
/// logging unknown discriminants.
|
||||
fn wire_color_type(format: u32) -> Option<cef::sys::cef_color_type_t> {
|
||||
match format {
|
||||
0 => Some(cef::sys::cef_color_type_t::CEF_COLOR_TYPE_RGBA_8888),
|
||||
1 => Some(cef::sys::cef_color_type_t::CEF_COLOR_TYPE_BGRA_8888),
|
||||
_ => {
|
||||
tracing::error!("Unknown color type {format} in accelerated frame");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
use ipc_channel::ipc::IpcSender;
|
||||
use std::os::fd::{AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use super::RecvResult;
|
||||
use crate::frames::surface::FrameSurface;
|
||||
use crate::remote::HostConfig;
|
||||
use crate::remote::messages::EventMessage;
|
||||
pub(crate) const FRAME_SOCKET_CHILD_FD: RawFd = 3;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
struct FrameDescriptor {
|
||||
seq: u64,
|
||||
modifier: u64,
|
||||
width: u32,
|
||||
height: u32,
|
||||
format: u32,
|
||||
plane_count: u32,
|
||||
strides: [u32; 4],
|
||||
offsets: [u32; 4],
|
||||
content_x: u32,
|
||||
content_y: u32,
|
||||
content_width: u32,
|
||||
content_height: u32,
|
||||
source_width: u32,
|
||||
source_height: u32,
|
||||
}
|
||||
|
||||
const DESCRIPTOR_BYTES: usize = std::mem::size_of::<FrameDescriptor>();
|
||||
const MAX_PLANES: usize = 4;
|
||||
|
||||
pub(crate) fn socketpair() -> std::io::Result<(OwnedFd, OwnedFd)> {
|
||||
let mut fds = [0 as RawFd; 2];
|
||||
// SAFETY: socketpair call; on success fds are owned by us.
|
||||
let result = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_SEQPACKET | libc::SOCK_CLOEXEC, 0, fds.as_mut_ptr()) };
|
||||
if result != 0 {
|
||||
return Err(std::io::Error::last_os_error());
|
||||
}
|
||||
// SAFETY: socketpair succeeded, so both fds are valid and not owned elsewhere.
|
||||
Ok(unsafe { (OwnedFd::from_raw_fd(fds[0]), OwnedFd::from_raw_fd(fds[1])) })
|
||||
}
|
||||
|
||||
pub(crate) struct PlaneSender {
|
||||
socket: OwnedFd,
|
||||
}
|
||||
|
||||
impl PlaneSender {
|
||||
pub(crate) fn from_config(config: &HostConfig, _events: Arc<Mutex<IpcSender<EventMessage>>>) -> Option<Self> {
|
||||
let fd = config.frame_socket_fd?;
|
||||
// SAFETY: the spawner dup2'd this fd for us; nothing else owns it.
|
||||
let socket = unsafe { OwnedFd::from_raw_fd(fd) };
|
||||
|
||||
// Restore CLOEXEC so subprocesses don't inherit the socket.
|
||||
// SAFETY: plain fcntl on an fd we own.
|
||||
if unsafe { libc::fcntl(socket.as_raw_fd(), libc::F_SETFD, libc::FD_CLOEXEC) } != 0 {
|
||||
tracing::warn!("Failed to set CLOEXEC on the frame socket: {}", std::io::Error::last_os_error());
|
||||
}
|
||||
Some(Self { socket })
|
||||
}
|
||||
|
||||
pub(crate) fn stage(&self, info: &cef::AcceleratedPaintInfo) -> Option<StagedFrame> {
|
||||
let coded_size = &info.extra.coded_size;
|
||||
if coded_size.width <= 0 || coded_size.height <= 0 {
|
||||
tracing::error!("Accelerated paint delivered an invalid coded size: {}x{}", coded_size.width, coded_size.height);
|
||||
return None;
|
||||
}
|
||||
|
||||
let plane_count = (info.plane_count.max(0) as usize).min(info.planes.len());
|
||||
let mut fds = Vec::with_capacity(plane_count);
|
||||
let mut strides = [0u32; 4];
|
||||
let mut offsets = [0u32; 4];
|
||||
for (i, plane) in info.planes[..plane_count].iter().enumerate() {
|
||||
// SAFETY: CEF keeps the plane fds valid for the `on_accelerated_paint` callback.
|
||||
let fd = unsafe { BorrowedFd::borrow_raw(plane.fd) };
|
||||
match fd.try_clone_to_owned() {
|
||||
Ok(owned) => fds.push(owned),
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to duplicate DMA-BUF plane fd: {e}");
|
||||
return None;
|
||||
}
|
||||
}
|
||||
strides[i] = plane.stride;
|
||||
offsets[i] = plane.offset as u32;
|
||||
}
|
||||
|
||||
let content = crate::frames::import::ContentRect::try_from(info).unwrap_or_default();
|
||||
Some(StagedFrame {
|
||||
descriptor: FrameDescriptor {
|
||||
seq: 0,
|
||||
modifier: info.modifier,
|
||||
width: coded_size.width as u32,
|
||||
height: coded_size.height as u32,
|
||||
format: *info.format.as_ref() as u32,
|
||||
plane_count: plane_count as u32,
|
||||
strides,
|
||||
offsets,
|
||||
content_x: content.x,
|
||||
content_y: content.y,
|
||||
content_width: content.width,
|
||||
content_height: content.height,
|
||||
source_width: content.source_width,
|
||||
source_height: content.source_height,
|
||||
},
|
||||
fds,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn send(&self, seq: u64, frame: StagedFrame) -> std::io::Result<()> {
|
||||
let mut descriptor = frame.descriptor;
|
||||
descriptor.seq = seq;
|
||||
|
||||
debug_assert!(frame.fds.len() <= MAX_PLANES);
|
||||
let fd_bytes = frame.fds.len() * std::mem::size_of::<RawFd>();
|
||||
let mut iov = libc::iovec {
|
||||
iov_base: &descriptor as *const FrameDescriptor as *mut libc::c_void,
|
||||
iov_len: DESCRIPTOR_BYTES,
|
||||
};
|
||||
let mut cmsg_buffer = [0u8; unsafe { libc::CMSG_SPACE((MAX_PLANES * std::mem::size_of::<RawFd>()) as u32) } as usize];
|
||||
let mut msg: libc::msghdr = unsafe { std::mem::zeroed() };
|
||||
msg.msg_iov = &mut iov;
|
||||
msg.msg_iovlen = 1;
|
||||
msg.msg_control = cmsg_buffer.as_mut_ptr().cast();
|
||||
msg.msg_controllen = unsafe { libc::CMSG_SPACE(fd_bytes as u32) } as _;
|
||||
unsafe {
|
||||
let cmsg = libc::CMSG_FIRSTHDR(&msg);
|
||||
(*cmsg).cmsg_level = libc::SOL_SOCKET;
|
||||
(*cmsg).cmsg_type = libc::SCM_RIGHTS;
|
||||
(*cmsg).cmsg_len = libc::CMSG_LEN(fd_bytes as u32) as _;
|
||||
let data = libc::CMSG_DATA(cmsg) as *mut RawFd;
|
||||
for (i, fd) in frame.fds.iter().enumerate() {
|
||||
data.add(i).write_unaligned(fd.as_raw_fd());
|
||||
}
|
||||
}
|
||||
loop {
|
||||
// SAFETY: msg and everything it points to are valid for the duration of the call.
|
||||
let sent = unsafe { libc::sendmsg(self.socket.as_raw_fd(), &msg, libc::MSG_NOSIGNAL) };
|
||||
if sent >= 0 {
|
||||
return Ok(());
|
||||
}
|
||||
let error = std::io::Error::last_os_error();
|
||||
if error.kind() != std::io::ErrorKind::Interrupted {
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct StagedFrame {
|
||||
descriptor: FrameDescriptor,
|
||||
fds: Vec<OwnedFd>,
|
||||
}
|
||||
|
||||
pub(crate) struct PlaneReceiver {
|
||||
socket: OwnedFd,
|
||||
}
|
||||
|
||||
impl PlaneReceiver {
|
||||
pub(crate) fn new(socket: OwnedFd) -> Self {
|
||||
Self { socket }
|
||||
}
|
||||
|
||||
pub(crate) fn recv_blocking(&self) -> std::io::Result<RecvResult> {
|
||||
self.recv(false)
|
||||
}
|
||||
|
||||
pub(crate) fn try_recv(&self) -> std::io::Result<RecvResult> {
|
||||
self.recv(true)
|
||||
}
|
||||
|
||||
fn recv(&self, nonblocking: bool) -> std::io::Result<RecvResult> {
|
||||
let mut descriptor: FrameDescriptor = bytemuck::Zeroable::zeroed();
|
||||
let mut iov = libc::iovec {
|
||||
iov_base: (&mut descriptor as *mut FrameDescriptor).cast(),
|
||||
iov_len: DESCRIPTOR_BYTES,
|
||||
};
|
||||
// SAFETY: pure size computation.
|
||||
let mut cmsg_buffer = [0u8; unsafe { libc::CMSG_SPACE((MAX_PLANES * std::mem::size_of::<RawFd>()) as u32) } as usize];
|
||||
|
||||
let mut msg: libc::msghdr = unsafe { std::mem::zeroed() };
|
||||
msg.msg_iov = &mut iov;
|
||||
msg.msg_iovlen = 1;
|
||||
msg.msg_control = cmsg_buffer.as_mut_ptr().cast();
|
||||
msg.msg_controllen = cmsg_buffer.len() as _;
|
||||
|
||||
let flags = libc::MSG_CMSG_CLOEXEC | if nonblocking { libc::MSG_DONTWAIT } else { 0 };
|
||||
let received = loop {
|
||||
// SAFETY: msg and everything it points to are valid for the duration of the call.
|
||||
let received = unsafe { libc::recvmsg(self.socket.as_raw_fd(), &mut msg, flags) };
|
||||
if received >= 0 {
|
||||
break received;
|
||||
}
|
||||
let error = std::io::Error::last_os_error();
|
||||
match error.kind() {
|
||||
std::io::ErrorKind::Interrupted => continue,
|
||||
std::io::ErrorKind::WouldBlock => return Ok(RecvResult::WouldBlock),
|
||||
_ => return Err(error),
|
||||
}
|
||||
};
|
||||
|
||||
let mut fds = Vec::new();
|
||||
// SAFETY: traversing the cmsgs recvmsg just filled; SCM_RIGHTS payload is fds now owned by us.
|
||||
unsafe {
|
||||
let mut cmsg = libc::CMSG_FIRSTHDR(&msg);
|
||||
while !cmsg.is_null() {
|
||||
if (*cmsg).cmsg_level == libc::SOL_SOCKET && (*cmsg).cmsg_type == libc::SCM_RIGHTS {
|
||||
let data = libc::CMSG_DATA(cmsg) as *const RawFd;
|
||||
let count = ((*cmsg).cmsg_len as usize - libc::CMSG_LEN(0) as usize) / std::mem::size_of::<RawFd>();
|
||||
for i in 0..count {
|
||||
fds.push(OwnedFd::from_raw_fd(data.add(i).read_unaligned()));
|
||||
}
|
||||
}
|
||||
cmsg = libc::CMSG_NXTHDR(&msg, cmsg);
|
||||
}
|
||||
}
|
||||
|
||||
if received == 0 {
|
||||
return Ok(RecvResult::Closed);
|
||||
}
|
||||
if received as usize != DESCRIPTOR_BYTES || (msg.msg_flags & libc::MSG_CTRUNC) != 0 {
|
||||
return Err(std::io::Error::other(format!(
|
||||
"malformed frame message: {received} bytes, flags {:#x} ({} fds)",
|
||||
msg.msg_flags,
|
||||
fds.len()
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(RecvResult::Frame(WireFrame { descriptor, fds }))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct WireFrame {
|
||||
descriptor: FrameDescriptor,
|
||||
fds: Vec<OwnedFd>,
|
||||
}
|
||||
|
||||
impl WireFrame {
|
||||
pub(crate) fn seq(&self) -> u64 {
|
||||
self.descriptor.seq
|
||||
}
|
||||
|
||||
pub(crate) fn import(self, surface: &FrameSurface) -> Option<wgpu::Texture> {
|
||||
let descriptor = self.descriptor;
|
||||
let format = super::wire_color_type(descriptor.format)?;
|
||||
let plane_count = (descriptor.plane_count as usize).min(self.fds.len());
|
||||
let content = crate::frames::import::ContentRect {
|
||||
x: descriptor.content_x,
|
||||
y: descriptor.content_y,
|
||||
width: descriptor.content_width,
|
||||
height: descriptor.content_height,
|
||||
source_width: descriptor.source_width,
|
||||
source_height: descriptor.source_height,
|
||||
};
|
||||
let importer = crate::frames::import::dmabuf::DmaBufImporter::from_parts(
|
||||
self.fds,
|
||||
descriptor.strides[..plane_count].to_vec(),
|
||||
descriptor.offsets[..plane_count].to_vec(),
|
||||
descriptor.modifier,
|
||||
descriptor.width,
|
||||
descriptor.height,
|
||||
format,
|
||||
);
|
||||
surface.import_texture(importer, content)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
use ipc_channel::ipc::IpcSender;
|
||||
use std::ffi::CString;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use mach2::kern_return::KERN_SUCCESS;
|
||||
use mach2::message::{
|
||||
MACH_MSG_PORT_DESCRIPTOR, MACH_MSG_SUCCESS, MACH_MSG_TIMEOUT_NONE, MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MOVE_SEND, MACH_MSGH_BITS_COMPLEX, MACH_RCV_MSG, MACH_RCV_TIMED_OUT, MACH_RCV_TIMEOUT,
|
||||
MACH_SEND_MSG, mach_msg, mach_msg_body_t, mach_msg_header_t,
|
||||
};
|
||||
use mach2::port::{MACH_PORT_NULL, mach_port_t};
|
||||
use mach2::traps::mach_task_self;
|
||||
use objc2_io_surface::IOSurfaceRef;
|
||||
|
||||
use super::RecvResult;
|
||||
use crate::frames::surface::FrameSurface;
|
||||
use crate::remote::HostConfig;
|
||||
use crate::remote::messages::EventMessage;
|
||||
|
||||
// From libSystem, stable since 10.0, not coverd by mach2
|
||||
unsafe extern "C" {
|
||||
static bootstrap_port: mach_port_t;
|
||||
fn bootstrap_check_in(bp: mach_port_t, service_name: *const std::ffi::c_char, sp: *mut mach_port_t) -> mach2::kern_return::kern_return_t;
|
||||
fn bootstrap_look_up(bp: mach_port_t, service_name: *const std::ffi::c_char, sp: *mut mach_port_t) -> mach2::kern_return::kern_return_t;
|
||||
}
|
||||
|
||||
// `mach_msg_port_descriptor_t` kernel ABI
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
struct PortDescriptor {
|
||||
name: mach_port_t,
|
||||
pad1: u32,
|
||||
pad2: u16,
|
||||
disposition: u8,
|
||||
descriptor_type: u8,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
struct FrameDescriptor {
|
||||
seq: u64,
|
||||
width: u32,
|
||||
height: u32,
|
||||
format: u32,
|
||||
content_x: u32,
|
||||
content_y: u32,
|
||||
content_width: u32,
|
||||
content_height: u32,
|
||||
source_width: u32,
|
||||
source_height: u32,
|
||||
_pad: u32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct FrameMessage {
|
||||
header: mach_msg_header_t,
|
||||
body: mach_msg_body_t,
|
||||
surface: PortDescriptor,
|
||||
descriptor: FrameDescriptor,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct FrameMessageBuffer {
|
||||
message: FrameMessage,
|
||||
trailer: [u8; 64],
|
||||
}
|
||||
|
||||
struct SendRight(mach_port_t);
|
||||
|
||||
// SAFETY: mach port names are task-wide; rights may be used from any thread.
|
||||
unsafe impl Send for SendRight {}
|
||||
unsafe impl Sync for SendRight {}
|
||||
|
||||
impl Drop for SendRight {
|
||||
fn drop(&mut self) {
|
||||
// SAFETY: we own one reference on this send right.
|
||||
unsafe { mach2::mach_port::mach_port_deallocate(mach_task_self(), self.0) };
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn create_service(name: &str) -> std::io::Result<mach_port_t> {
|
||||
let c_name = CString::new(name).map_err(std::io::Error::other)?;
|
||||
let mut port: mach_port_t = MACH_PORT_NULL;
|
||||
// SAFETY: plain bootstrap call; on success we own the service's receive right.
|
||||
let result = unsafe { bootstrap_check_in(bootstrap_port, c_name.as_ptr(), &mut port) };
|
||||
if result != KERN_SUCCESS {
|
||||
return Err(std::io::Error::other(format!("bootstrap_check_in failed: {result:#x}")));
|
||||
}
|
||||
Ok(port)
|
||||
}
|
||||
|
||||
fn look_up_service(name: &str) -> std::io::Result<SendRight> {
|
||||
let c_name = CString::new(name).map_err(std::io::Error::other)?;
|
||||
let mut port: mach_port_t = MACH_PORT_NULL;
|
||||
// SAFETY: plain bootstrap call; on success we own a send right.
|
||||
let result = unsafe { bootstrap_look_up(bootstrap_port, c_name.as_ptr(), &mut port) };
|
||||
if result != KERN_SUCCESS {
|
||||
return Err(std::io::Error::other(format!("bootstrap_look_up failed: {result:#x}")));
|
||||
}
|
||||
Ok(SendRight(port))
|
||||
}
|
||||
|
||||
pub(crate) struct PlaneSender {
|
||||
service: SendRight,
|
||||
}
|
||||
|
||||
impl PlaneSender {
|
||||
pub(crate) fn from_config(config: &HostConfig, _events: Arc<Mutex<IpcSender<EventMessage>>>) -> Option<Self> {
|
||||
let name = config.frame_service.as_deref()?;
|
||||
match look_up_service(name) {
|
||||
Ok(service) => Some(Self { service }),
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to look up the accelerated frame service, falling back to software frames: {e}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn stage(&self, info: &cef::AcceleratedPaintInfo) -> Option<StagedFrame> {
|
||||
let coded_size = &info.extra.coded_size;
|
||||
if coded_size.width <= 0 || coded_size.height <= 0 {
|
||||
tracing::error!("Accelerated paint delivered an invalid coded size: {}x{}", coded_size.width, coded_size.height);
|
||||
return None;
|
||||
}
|
||||
|
||||
let Some(surface) = std::ptr::NonNull::new(info.shared_texture_io_surface.cast::<IOSurfaceRef>()) else {
|
||||
tracing::error!("Accelerated paint delivered a null IOSurface");
|
||||
return None;
|
||||
};
|
||||
|
||||
// SAFETY: CEF keeps the surface valid for the `on_accelerated_paint` callback.
|
||||
let port = unsafe { surface.as_ref() }.create_mach_port();
|
||||
if port == MACH_PORT_NULL {
|
||||
tracing::error!("Failed to wrap the IOSurface in a mach port");
|
||||
return None;
|
||||
}
|
||||
|
||||
let content = crate::frames::import::ContentRect::try_from(info).unwrap_or_default();
|
||||
Some(StagedFrame {
|
||||
descriptor: FrameDescriptor {
|
||||
seq: 0,
|
||||
width: coded_size.width as u32,
|
||||
height: coded_size.height as u32,
|
||||
format: *info.format.as_ref() as u32,
|
||||
content_x: content.x,
|
||||
content_y: content.y,
|
||||
content_width: content.width,
|
||||
content_height: content.height,
|
||||
source_width: content.source_width,
|
||||
source_height: content.source_height,
|
||||
_pad: 0,
|
||||
},
|
||||
surface: SendRight(port),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn send(&self, seq: u64, frame: StagedFrame) -> std::io::Result<()> {
|
||||
let mut descriptor = frame.descriptor;
|
||||
descriptor.seq = seq;
|
||||
let mut message = FrameMessage {
|
||||
header: mach_msg_header_t {
|
||||
msgh_bits: MACH_MSG_TYPE_COPY_SEND | MACH_MSGH_BITS_COMPLEX,
|
||||
msgh_size: std::mem::size_of::<FrameMessage>() as u32,
|
||||
msgh_remote_port: self.service.0,
|
||||
msgh_local_port: MACH_PORT_NULL,
|
||||
msgh_voucher_port: MACH_PORT_NULL,
|
||||
msgh_id: 0,
|
||||
},
|
||||
body: mach_msg_body_t { msgh_descriptor_count: 1 },
|
||||
surface: PortDescriptor {
|
||||
name: frame.surface.0,
|
||||
pad1: 0,
|
||||
pad2: 0,
|
||||
disposition: MACH_MSG_TYPE_MOVE_SEND as u8,
|
||||
descriptor_type: MACH_MSG_PORT_DESCRIPTOR as u8,
|
||||
},
|
||||
descriptor,
|
||||
};
|
||||
|
||||
// SAFETY: message is a well-formed complex message of the declared size.
|
||||
let result = unsafe {
|
||||
mach_msg(
|
||||
&mut message.header,
|
||||
MACH_SEND_MSG,
|
||||
std::mem::size_of::<FrameMessage>() as u32,
|
||||
0,
|
||||
MACH_PORT_NULL,
|
||||
MACH_MSG_TIMEOUT_NONE,
|
||||
MACH_PORT_NULL,
|
||||
)
|
||||
};
|
||||
if result != MACH_MSG_SUCCESS {
|
||||
return Err(std::io::Error::other(format!("mach_msg send failed: {result:#x}")));
|
||||
}
|
||||
|
||||
// Kernel took ownership of the surface and moves it to the receiver. We must not drop.
|
||||
std::mem::forget(frame.surface);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct StagedFrame {
|
||||
descriptor: FrameDescriptor,
|
||||
surface: SendRight,
|
||||
}
|
||||
|
||||
pub(crate) struct PlaneReceiver {
|
||||
port: mach_port_t,
|
||||
}
|
||||
|
||||
impl PlaneReceiver {
|
||||
pub(crate) fn new(port: mach_port_t) -> Self {
|
||||
Self { port }
|
||||
}
|
||||
|
||||
pub(crate) fn recv_blocking(&self) -> std::io::Result<RecvResult> {
|
||||
self.recv(false)
|
||||
}
|
||||
|
||||
pub(crate) fn try_recv(&self) -> std::io::Result<RecvResult> {
|
||||
self.recv(true)
|
||||
}
|
||||
|
||||
fn recv(&self, nonblocking: bool) -> std::io::Result<RecvResult> {
|
||||
// SAFETY: zeroed is a valid representation for these plain-data structs.
|
||||
let mut buffer: FrameMessageBuffer = unsafe { std::mem::zeroed() };
|
||||
let (options, timeout) = if nonblocking {
|
||||
(MACH_RCV_MSG | MACH_RCV_TIMEOUT, 0)
|
||||
} else {
|
||||
(MACH_RCV_MSG, MACH_MSG_TIMEOUT_NONE)
|
||||
};
|
||||
|
||||
// SAFETY: the buffer is large enough for the message plus the basic trailer.
|
||||
let result = unsafe {
|
||||
mach_msg(
|
||||
&mut buffer.message.header,
|
||||
options,
|
||||
0,
|
||||
std::mem::size_of::<FrameMessageBuffer>() as u32,
|
||||
self.port,
|
||||
timeout,
|
||||
MACH_PORT_NULL,
|
||||
)
|
||||
};
|
||||
if result == MACH_RCV_TIMED_OUT {
|
||||
return Ok(RecvResult::WouldBlock);
|
||||
}
|
||||
if result != MACH_MSG_SUCCESS {
|
||||
return Err(std::io::Error::other(format!("mach_msg receive failed: {result:#x}")));
|
||||
}
|
||||
|
||||
let received_complex = buffer.message.header.msgh_bits & MACH_MSGH_BITS_COMPLEX != 0;
|
||||
let descriptor_count = if received_complex { buffer.message.body.msgh_descriptor_count } else { 0 };
|
||||
let surface = (descriptor_count == 1 && buffer.message.surface.descriptor_type == MACH_MSG_PORT_DESCRIPTOR as u8).then(|| SendRight(buffer.message.surface.name));
|
||||
|
||||
if buffer.message.header.msgh_size as usize != std::mem::size_of::<FrameMessage>() {
|
||||
return Err(std::io::Error::other(format!("malformed frame message: {} bytes", buffer.message.header.msgh_size)));
|
||||
}
|
||||
let Some(surface) = surface else {
|
||||
return Err(std::io::Error::other("frame message carried no surface port"));
|
||||
};
|
||||
|
||||
Ok(RecvResult::Frame(WireFrame {
|
||||
descriptor: buffer.message.descriptor,
|
||||
surface,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct WireFrame {
|
||||
descriptor: FrameDescriptor,
|
||||
surface: SendRight,
|
||||
}
|
||||
|
||||
impl WireFrame {
|
||||
pub(crate) fn seq(&self) -> u64 {
|
||||
self.descriptor.seq
|
||||
}
|
||||
|
||||
pub(crate) fn import(self, surface: &FrameSurface) -> Option<wgpu::Texture> {
|
||||
let WireFrame { descriptor, surface: port } = self;
|
||||
let format = super::wire_color_type(descriptor.format)?;
|
||||
|
||||
// Lookup takes its own reference on the surface, port can be dropped.
|
||||
let io_surface = IOSurfaceRef::lookup_from_mach_port(port.0);
|
||||
drop(port);
|
||||
|
||||
let Some(io_surface) = io_surface else {
|
||||
tracing::error!("Failed to look up the IOSurface for frame {}", descriptor.seq);
|
||||
return None;
|
||||
};
|
||||
let io_surface_ref: &IOSurfaceRef = &io_surface;
|
||||
|
||||
let content = crate::frames::import::ContentRect {
|
||||
x: descriptor.content_x,
|
||||
y: descriptor.content_y,
|
||||
width: descriptor.content_width,
|
||||
height: descriptor.content_height,
|
||||
source_width: descriptor.source_width,
|
||||
source_height: descriptor.source_height,
|
||||
};
|
||||
let importer = crate::frames::import::iosurface::IOSurfaceImporter::from_parts(io_surface_ref as *const _ as *mut std::os::raw::c_void, descriptor.width, descriptor.height, format);
|
||||
surface.import_texture(importer, content)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
use ipc_channel::ipc::IpcSender;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use windows::Win32::Foundation::{CloseHandle, DUPLICATE_CLOSE_SOURCE, DUPLICATE_SAME_ACCESS, DuplicateHandle, HANDLE};
|
||||
use windows::Win32::System::Threading::{GetCurrentProcess, OpenProcess, PROCESS_DUP_HANDLE};
|
||||
|
||||
use crate::frames::import::ContentRect;
|
||||
use crate::frames::surface::FrameSurface;
|
||||
use crate::remote::HostConfig;
|
||||
use crate::remote::messages::EventMessage;
|
||||
|
||||
struct MainProcess(HANDLE);
|
||||
|
||||
// SAFETY: process handles may be used and closed from any thread.
|
||||
unsafe impl Send for MainProcess {}
|
||||
unsafe impl Sync for MainProcess {}
|
||||
|
||||
impl MainProcess {
|
||||
fn open(pid: u32) -> windows::core::Result<Self> {
|
||||
// SAFETY: plain OpenProcess call; on success the handle is ours to close.
|
||||
unsafe { OpenProcess(PROCESS_DUP_HANDLE, false, pid).map(Self) }
|
||||
}
|
||||
|
||||
fn duplicate_into(&self, handle: HANDLE) -> windows::core::Result<u64> {
|
||||
let mut target = HANDLE::default();
|
||||
// SAFETY: both process handles are valid; `target` receives the duplicate.
|
||||
unsafe { DuplicateHandle(GetCurrentProcess(), handle, self.0, &mut target, 0, false, DUPLICATE_SAME_ACCESS)? };
|
||||
Ok(target.0 as u64)
|
||||
}
|
||||
|
||||
fn close_in_main(&self, handle: u64) {
|
||||
let mut reclaimed = HANDLE::default();
|
||||
// SAFETY: `handle` came from `duplicate_into` and is valid.
|
||||
unsafe {
|
||||
if let Err(e) = DuplicateHandle(self.0, HANDLE(handle as _), GetCurrentProcess(), &mut reclaimed, 0, false, DUPLICATE_CLOSE_SOURCE) {
|
||||
tracing::warn!("Failed to reclaim a frame handle from the main process: {e}");
|
||||
}
|
||||
if !reclaimed.is_invalid() {
|
||||
let _ = CloseHandle(reclaimed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for MainProcess {
|
||||
fn drop(&mut self) {
|
||||
// SAFETY: we own the process handle.
|
||||
unsafe {
|
||||
let _ = CloseHandle(self.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct PlaneSender {
|
||||
main: Arc<MainProcess>,
|
||||
events: Arc<Mutex<IpcSender<EventMessage>>>,
|
||||
}
|
||||
|
||||
impl PlaneSender {
|
||||
pub(crate) fn from_config(config: &HostConfig, events: Arc<Mutex<IpcSender<EventMessage>>>) -> Option<Self> {
|
||||
match MainProcess::open(config.main_pid) {
|
||||
Ok(main) => Some(Self { main: Arc::new(main), events }),
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to open the main process for handle duplication, falling back to software frames: {e}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn stage(&self, info: &cef::AcceleratedPaintInfo) -> Option<StagedFrame> {
|
||||
let coded_size = &info.extra.coded_size;
|
||||
if coded_size.width <= 0 || coded_size.height <= 0 {
|
||||
tracing::error!("Accelerated paint delivered an invalid coded size: {}x{}", coded_size.width, coded_size.height);
|
||||
return None;
|
||||
}
|
||||
|
||||
let handle = match self.main.duplicate_into(HANDLE(info.shared_texture_handle)) {
|
||||
Ok(handle) => handle,
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to duplicate the shared texture handle into the main process: {e}");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
Some(StagedFrame {
|
||||
handle: HandleInMain { handle, main: self.main.clone() },
|
||||
width: coded_size.width as u32,
|
||||
height: coded_size.height as u32,
|
||||
format: *info.format.as_ref() as u32,
|
||||
content: ContentRect::try_from(info).ok(),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn send(&self, seq: u64, frame: StagedFrame) -> std::io::Result<()> {
|
||||
let message = EventMessage::AcceleratedFrame {
|
||||
seq,
|
||||
handle: frame.handle.handle,
|
||||
width: frame.width,
|
||||
height: frame.height,
|
||||
format: frame.format,
|
||||
content: frame.content,
|
||||
};
|
||||
let sender = self.events.lock().map_err(|_| std::io::Error::other("the host message sender lock is poisoned"))?;
|
||||
match sender.send(message) {
|
||||
Ok(()) => {
|
||||
// Dropping the handle would reclaim it. We must not drop.
|
||||
std::mem::forget(frame.handle);
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(std::io::Error::other(e.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct StagedFrame {
|
||||
handle: HandleInMain,
|
||||
width: u32,
|
||||
height: u32,
|
||||
format: u32,
|
||||
content: Option<ContentRect>,
|
||||
}
|
||||
|
||||
struct HandleInMain {
|
||||
handle: u64,
|
||||
main: Arc<MainProcess>,
|
||||
}
|
||||
|
||||
impl Drop for HandleInMain {
|
||||
fn drop(&mut self) {
|
||||
self.main.close_in_main(self.handle);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct WireFrame {
|
||||
seq: u64,
|
||||
handle: ReceivedHandle,
|
||||
width: u32,
|
||||
height: u32,
|
||||
format: u32,
|
||||
content: Option<ContentRect>,
|
||||
}
|
||||
|
||||
impl WireFrame {
|
||||
pub(crate) fn new(seq: u64, handle: u64, width: u32, height: u32, format: u32, content: Option<ContentRect>) -> Self {
|
||||
Self {
|
||||
seq,
|
||||
handle: ReceivedHandle(handle),
|
||||
width,
|
||||
height,
|
||||
format,
|
||||
content,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn seq(&self) -> u64 {
|
||||
self.seq
|
||||
}
|
||||
|
||||
pub(crate) fn import(self, surface: &FrameSurface) -> Option<wgpu::Texture> {
|
||||
let format = super::wire_color_type(self.format)?;
|
||||
let content = self.content.unwrap_or_default();
|
||||
surface.import_texture(crate::frames::import::d3d11::D3D11Importer::from_parts(self.handle.0, self.width, self.height, format), content)
|
||||
}
|
||||
}
|
||||
|
||||
struct ReceivedHandle(u64);
|
||||
|
||||
impl Drop for ReceivedHandle {
|
||||
fn drop(&mut self) {
|
||||
// SAFETY: the host duplicated this handle into our process for us to own.
|
||||
if let Err(e) = unsafe { CloseHandle(HANDLE(self.0 as _)) } {
|
||||
tracing::warn!("Failed to close a remote frame handle: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
use ipc_channel::ipc::{IpcSender, IpcSharedMemory};
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::FrameSurface;
|
||||
#[cfg(feature = "accelerated_paint")]
|
||||
use super::plane;
|
||||
use super::sink::FrameSink;
|
||||
use crate::UiEvent;
|
||||
use crate::events::EventQueue;
|
||||
use crate::remote::messages::HostControlMessage;
|
||||
|
||||
pub(crate) enum PendingFrame {
|
||||
Software {
|
||||
seq: u64,
|
||||
segment: u32,
|
||||
width: u32,
|
||||
height: u32,
|
||||
},
|
||||
#[cfg(all(target_os = "windows", feature = "accelerated_paint"))]
|
||||
Accelerated(plane::WireFrame),
|
||||
}
|
||||
|
||||
impl PendingFrame {
|
||||
pub(crate) fn seq(&self) -> u64 {
|
||||
match self {
|
||||
PendingFrame::Software { seq, .. } => *seq,
|
||||
#[cfg(all(target_os = "windows", feature = "accelerated_paint"))]
|
||||
PendingFrame::Accelerated(frame) => frame.seq(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct SegmentTable(Vec<Option<IpcSharedMemory>>);
|
||||
|
||||
impl SegmentTable {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self(Vec::new())
|
||||
}
|
||||
|
||||
pub(crate) fn advertise(&mut self, index: u32, shm: IpcSharedMemory) {
|
||||
let index = index as usize;
|
||||
if self.0.len() <= index {
|
||||
self.0.resize_with(index + 1, || None);
|
||||
}
|
||||
self.0[index] = Some(shm);
|
||||
}
|
||||
|
||||
fn frame(&self, seq: u64, segment: u32, width: u32, height: u32) -> Option<&[u8]> {
|
||||
let frame_bytes = width as usize * height as usize * 4;
|
||||
match self.0.get(segment as usize).and_then(Option::as_ref) {
|
||||
Some(shm) if shm.len() >= frame_bytes => Some(&shm[..frame_bytes]),
|
||||
Some(shm) => {
|
||||
tracing::error!("Frame {seq} needs {frame_bytes} bytes but segment {segment} holds {}", shm.len());
|
||||
None
|
||||
}
|
||||
None => {
|
||||
tracing::error!("Frame {seq} references unadvertised segment {segment}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct FrameConsumer {
|
||||
surface: FrameSurface,
|
||||
events: EventQueue,
|
||||
sender: IpcSender<HostControlMessage>,
|
||||
sink: Arc<FrameSink>,
|
||||
}
|
||||
|
||||
impl FrameConsumer {
|
||||
pub(crate) fn new(surface: FrameSurface, events: EventQueue, sender: IpcSender<HostControlMessage>) -> Self {
|
||||
Self {
|
||||
surface,
|
||||
events,
|
||||
sender,
|
||||
sink: Arc::new(FrameSink::new()),
|
||||
}
|
||||
}
|
||||
|
||||
fn deliver(&self, seq: u64, install: impl FnOnce(&FrameSurface) -> Option<wgpu::Texture>) {
|
||||
self.sink.deliver(&self.sender, seq, || match install(&self.surface) {
|
||||
Some(texture) => {
|
||||
self.events.send(UiEvent::Frame(texture));
|
||||
true
|
||||
}
|
||||
None => false,
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn deliver_pending(&self, frame: PendingFrame, segments: &SegmentTable) {
|
||||
match frame {
|
||||
PendingFrame::Software { seq, segment, width, height } => {
|
||||
self.deliver(seq, |surface| {
|
||||
segments.frame(seq, segment, width, height).and_then(|pixels| surface.upload_buffer(pixels, width, height))
|
||||
});
|
||||
}
|
||||
#[cfg(all(target_os = "windows", feature = "accelerated_paint"))]
|
||||
PendingFrame::Accelerated(frame) => self.deliver_accelerated(frame),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "accelerated_paint")]
|
||||
pub(crate) fn deliver_accelerated(&self, frame: plane::WireFrame) {
|
||||
let seq = frame.seq();
|
||||
self.deliver(seq, |surface| frame.import(surface));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(any(target_os = "linux", target_os = "macos"), feature = "accelerated_paint"))]
|
||||
pub(crate) fn plane_receiver_loop(receiver: plane::PlaneReceiver, consumer: FrameConsumer) {
|
||||
loop {
|
||||
let mut frame = loop {
|
||||
match receiver.recv_blocking() {
|
||||
Ok(plane::RecvResult::Frame(frame)) => break frame,
|
||||
Ok(plane::RecvResult::WouldBlock) => continue,
|
||||
Ok(plane::RecvResult::Closed) => return,
|
||||
Err(e) => {
|
||||
tracing::error!("Accelerated frame plane failed: {e}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
// Drain any newer frames that have arrived since the blocking receive
|
||||
while let Ok(plane::RecvResult::Frame(newer)) = receiver.try_recv() {
|
||||
frame = newer;
|
||||
}
|
||||
consumer.deliver_accelerated(frame);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
use std::sync::{Arc, OnceLock};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(super) struct Resampler {
|
||||
device: wgpu::Device,
|
||||
pipeline: Arc<OnceLock<Pipeline>>,
|
||||
}
|
||||
|
||||
struct Pipeline {
|
||||
format: wgpu::TextureFormat,
|
||||
sampler: wgpu::Sampler,
|
||||
layout: wgpu::BindGroupLayout,
|
||||
pipeline: wgpu::RenderPipeline,
|
||||
}
|
||||
|
||||
impl Resampler {
|
||||
pub(super) fn new(device: wgpu::Device) -> Self {
|
||||
Self {
|
||||
device,
|
||||
pipeline: Arc::new(OnceLock::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn encode(&self, encoder: &mut wgpu::CommandEncoder, source: &wgpu::Texture, content_origin: wgpu::Origin3d, content_size: wgpu::Extent3d, target: &wgpu::Texture) {
|
||||
let pipeline = self.pipeline.get_or_init(|| Pipeline::new(&self.device, target.format()));
|
||||
debug_assert_eq!(pipeline.format, target.format());
|
||||
|
||||
let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("CEF Resample Bind Group"),
|
||||
layout: &pipeline.layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(&source.create_view(&Default::default())),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(&pipeline.sampler),
|
||||
},
|
||||
],
|
||||
});
|
||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("CEF Resample Pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: &target.create_view(&Default::default()),
|
||||
depth_slice: None,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
multiview_mask: None,
|
||||
});
|
||||
pass.set_pipeline(&pipeline.pipeline);
|
||||
pass.set_immediates(
|
||||
0,
|
||||
bytemuck::bytes_of(&Immediates {
|
||||
content_origin: [content_origin.x as f32, content_origin.y as f32],
|
||||
content_size: [content_size.width as f32, content_size.height as f32],
|
||||
}),
|
||||
);
|
||||
pass.set_bind_group(0, &bind_group, &[]);
|
||||
pass.draw(0..3, 0..1);
|
||||
}
|
||||
}
|
||||
|
||||
impl Pipeline {
|
||||
fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self {
|
||||
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
label: Some("CEF Resample Sampler"),
|
||||
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||
mag_filter: wgpu::FilterMode::Linear,
|
||||
min_filter: wgpu::FilterMode::Linear,
|
||||
..Default::default()
|
||||
});
|
||||
let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("CEF Resample Bind Group Layout"),
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
});
|
||||
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("CEF Resample Pipeline Layout"),
|
||||
bind_group_layouts: &[Some(&layout)],
|
||||
immediate_size: std::mem::size_of::<Immediates>() as u32,
|
||||
});
|
||||
let shader = device.create_shader_module(wgpu::include_wgsl!("resample.wgsl"));
|
||||
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("CEF Resample Pipeline"),
|
||||
layout: Some(&pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: Some("vs_main"),
|
||||
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||
buffers: &[],
|
||||
},
|
||||
primitive: wgpu::PrimitiveState::default(),
|
||||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState::default(),
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: Some("fs_main"),
|
||||
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format,
|
||||
blend: None,
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
});
|
||||
Self { format, sampler, layout, pipeline }
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
struct Immediates {
|
||||
content_origin: [f32; 2],
|
||||
content_size: [f32; 2],
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// =============
|
||||
// VERTEX SHADER
|
||||
// =============
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) tex_coords: vec2<f32>,
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
let pos = array(
|
||||
vec2f(-1.0, -1.0),
|
||||
vec2f(3.0, -1.0),
|
||||
vec2f(-1.0, 3.0),
|
||||
);
|
||||
let xy = pos[vertex_index];
|
||||
out.clip_position = vec4f(xy, 0.0, 1.0);
|
||||
let coords = xy / 2. + 0.5;
|
||||
out.tex_coords = vec2f(coords.x, 1. - coords.y);
|
||||
return out;
|
||||
}
|
||||
|
||||
// ===============
|
||||
// FRAGMENT SHADER
|
||||
// ===============
|
||||
|
||||
struct Immediates {
|
||||
content_origin: vec2<f32>,
|
||||
content_size: vec2<f32>,
|
||||
};
|
||||
|
||||
var<immediate> immediates: Immediates;
|
||||
|
||||
@group(0) @binding(0)
|
||||
var t_frame: texture_2d<f32>;
|
||||
@group(0) @binding(1)
|
||||
var s_frame: sampler;
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let sample_pos = in.tex_coords * immediates.content_size;
|
||||
let nearest = floor(sample_pos - 0.5) + 0.5;
|
||||
let t = sample_pos - nearest;
|
||||
|
||||
// Catmull-Rom spline interpolation based sampeling
|
||||
// See https://gist.github.com/TheRealMJP/c83b8c0f46b63f3a88a5986f4fa982b1
|
||||
|
||||
let weight_before = t * (-0.5 + t * (1.0 - 0.5 * t));
|
||||
let weight_nearest = 1.0 + t * t * (-2.5 + 1.5 * t);
|
||||
let weight_next = t * (0.5 + t * (2.0 - 1.5 * t));
|
||||
let weight_after = t * t * (-0.5 + 0.5 * t);
|
||||
|
||||
let weight_middle = weight_nearest + weight_next;
|
||||
let middle = nearest + weight_next / weight_middle;
|
||||
|
||||
let frame_size = vec2<f32>(textureDimensions(t_frame));
|
||||
let content_min = vec2<f32>(0.5);
|
||||
let content_max = immediates.content_size - 0.5;
|
||||
let uv_before = (immediates.content_origin + clamp(nearest - 1.0, content_min, content_max)) / frame_size;
|
||||
let uv_middle = (immediates.content_origin + clamp(middle, content_min, content_max)) / frame_size;
|
||||
let uv_after = (immediates.content_origin + clamp(nearest + 2.0, content_min, content_max)) / frame_size;
|
||||
|
||||
var color = vec4<f32>(0.0);
|
||||
color += textureSampleLevel(t_frame, s_frame, vec2<f32>(uv_before.x, uv_before.y), 0.0) * weight_before.x * weight_before.y;
|
||||
color += textureSampleLevel(t_frame, s_frame, vec2<f32>(uv_middle.x, uv_before.y), 0.0) * weight_middle.x * weight_before.y;
|
||||
color += textureSampleLevel(t_frame, s_frame, vec2<f32>(uv_after.x, uv_before.y), 0.0) * weight_after.x * weight_before.y;
|
||||
color += textureSampleLevel(t_frame, s_frame, vec2<f32>(uv_before.x, uv_middle.y), 0.0) * weight_before.x * weight_middle.y;
|
||||
color += textureSampleLevel(t_frame, s_frame, vec2<f32>(uv_middle.x, uv_middle.y), 0.0) * weight_middle.x * weight_middle.y;
|
||||
color += textureSampleLevel(t_frame, s_frame, vec2<f32>(uv_after.x, uv_middle.y), 0.0) * weight_after.x * weight_middle.y;
|
||||
color += textureSampleLevel(t_frame, s_frame, vec2<f32>(uv_before.x, uv_after.y), 0.0) * weight_before.x * weight_after.y;
|
||||
color += textureSampleLevel(t_frame, s_frame, vec2<f32>(uv_middle.x, uv_after.y), 0.0) * weight_middle.x * weight_after.y;
|
||||
color += textureSampleLevel(t_frame, s_frame, vec2<f32>(uv_after.x, uv_after.y), 0.0) * weight_after.x * weight_after.y;
|
||||
|
||||
return clamp(color, vec4<f32>(0.0), vec4<f32>(1.0));
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::{Arc, Condvar, Mutex, PoisonError};
|
||||
#[cfg(feature = "accelerated_paint")]
|
||||
use std::time::Instant;
|
||||
|
||||
use crate::consts::FRAMES_IN_FLIGHT_LIMIT;
|
||||
|
||||
pub(crate) struct SequenceState {
|
||||
last_sent: AtomicU64,
|
||||
last_acked: AtomicU64,
|
||||
ack_lock: Mutex<()>,
|
||||
ack_signal: Condvar,
|
||||
}
|
||||
|
||||
impl SequenceState {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
last_sent: AtomicU64::new(0),
|
||||
last_acked: AtomicU64::new(0),
|
||||
ack_lock: Mutex::new(()),
|
||||
ack_signal: Condvar::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn claim(self: &Arc<Self>) -> Option<FrameSequenceClaim> {
|
||||
let last_sent = self.last_sent.load(Ordering::Relaxed);
|
||||
let last_acked = self.last_acked.load(Ordering::Relaxed);
|
||||
if last_sent.saturating_sub(last_acked) >= FRAMES_IN_FLIGHT_LIMIT {
|
||||
return None;
|
||||
}
|
||||
let seq = last_sent + 1;
|
||||
self.last_sent.store(seq, Ordering::Relaxed);
|
||||
Some(FrameSequenceClaim {
|
||||
seq,
|
||||
sequence: self.clone(),
|
||||
commited: false,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn ack(&self, seq: u64) {
|
||||
self.last_acked.fetch_max(seq, Ordering::Relaxed);
|
||||
drop(self.ack_lock.lock().unwrap_or_else(PoisonError::into_inner));
|
||||
self.ack_signal.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct FrameSequenceClaim {
|
||||
seq: u64,
|
||||
commited: bool,
|
||||
sequence: Arc<SequenceState>,
|
||||
}
|
||||
|
||||
impl FrameSequenceClaim {
|
||||
pub(crate) fn seq(&self) -> u64 {
|
||||
self.seq
|
||||
}
|
||||
|
||||
pub(crate) fn commit(mut self) {
|
||||
self.commited = true;
|
||||
}
|
||||
|
||||
#[cfg(feature = "accelerated_paint")]
|
||||
pub(crate) fn wait_for_ack(&self) -> bool {
|
||||
let deadline = Instant::now() + crate::consts::FRAME_ACK_TIMEOUT;
|
||||
let mut guard = self.sequence.ack_lock.lock().unwrap_or_else(PoisonError::into_inner);
|
||||
loop {
|
||||
if self.sequence.last_acked.load(Ordering::Relaxed) >= self.seq {
|
||||
return true;
|
||||
}
|
||||
let Some(remaining) = deadline.checked_duration_since(Instant::now()) else {
|
||||
return false;
|
||||
};
|
||||
guard = self.sequence.ack_signal.wait_timeout(guard, remaining).unwrap_or_else(PoisonError::into_inner).0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for FrameSequenceClaim {
|
||||
fn drop(&mut self) {
|
||||
// Roll back the claim if it was never committed to free the sequence number
|
||||
if !self.commited {
|
||||
let _ = self.sequence.last_sent.compare_exchange(self.seq, self.seq - 1, Ordering::Relaxed, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
use ipc_channel::ipc::IpcSender;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use crate::remote::messages::HostControlMessage;
|
||||
|
||||
pub(super) struct FrameSink {
|
||||
state: Mutex<FrameSinkState>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct FrameSinkState {
|
||||
newest_installed: u64,
|
||||
last_acked: u64,
|
||||
}
|
||||
|
||||
impl FrameSink {
|
||||
pub(super) fn new() -> Self {
|
||||
Self {
|
||||
state: Mutex::new(FrameSinkState::default()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn deliver(&self, sender: &IpcSender<HostControlMessage>, seq: u64, install: impl FnOnce() -> bool) {
|
||||
let Ok(mut state) = self.state.lock() else {
|
||||
tracing::error!("Failed to lock the frame sink");
|
||||
return;
|
||||
};
|
||||
|
||||
if seq > 1 && seq - 1 > state.last_acked {
|
||||
if let Err(e) = sender.send(HostControlMessage::FrameAck { seq: seq - 1 }) {
|
||||
tracing::debug!("Failed to ack superseded frames to CEF host: {e}");
|
||||
}
|
||||
state.last_acked = seq - 1;
|
||||
}
|
||||
if seq > state.newest_installed && install() {
|
||||
state.newest_installed = seq;
|
||||
}
|
||||
if seq > state.last_acked {
|
||||
if let Err(e) = sender.send(HostControlMessage::FrameAck { seq }) {
|
||||
tracing::debug!("Failed to ack frame to CEF host: {e}");
|
||||
}
|
||||
state.last_acked = seq;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
use ipc_channel::ipc::{IpcSender, IpcSharedMemory};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[cfg(feature = "accelerated_paint")]
|
||||
use super::plane;
|
||||
use super::sequence::{FrameSequenceClaim, SequenceState};
|
||||
use crate::consts::{FRAME_SEGMENT_GRANULARITY, FRAME_SEGMENT_POOL_SIZE};
|
||||
use crate::remote::messages::EventMessage;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct FrameStreamer(Arc<StreamerInner>);
|
||||
|
||||
struct StreamerInner {
|
||||
events: Arc<Mutex<IpcSender<EventMessage>>>,
|
||||
sequence: Arc<SequenceState>,
|
||||
#[cfg(feature = "accelerated_paint")]
|
||||
plane: Option<plane::PlaneSender>,
|
||||
staged: Mutex<Staged>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Staged {
|
||||
segments: Vec<IpcSharedMemory>,
|
||||
pending_adverts: Vec<(u32, IpcSharedMemory)>,
|
||||
buffer: Option<StagedBuffer>,
|
||||
#[cfg(feature = "accelerated_paint")]
|
||||
accelerated: Option<(FrameSequenceClaim, plane::StagedFrame)>,
|
||||
}
|
||||
|
||||
struct StagedBuffer {
|
||||
claim: FrameSequenceClaim,
|
||||
segment: u32,
|
||||
width: u32,
|
||||
height: u32,
|
||||
}
|
||||
|
||||
impl FrameStreamer {
|
||||
pub(crate) fn new(events: Arc<Mutex<IpcSender<EventMessage>>>, sequence: Arc<SequenceState>, #[cfg(feature = "accelerated_paint")] plane: Option<plane::PlaneSender>) -> Self {
|
||||
Self(Arc::new(StreamerInner {
|
||||
events,
|
||||
sequence,
|
||||
#[cfg(feature = "accelerated_paint")]
|
||||
plane,
|
||||
staged: Mutex::new(Staged::default()),
|
||||
}))
|
||||
}
|
||||
|
||||
pub(crate) fn stage_buffer(&self, buffer: &[u8], width: u32, height: u32) {
|
||||
debug_assert_eq!(buffer.len(), width as usize * height as usize * 4);
|
||||
if buffer.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(claim) = self.0.sequence.claim() else {
|
||||
return;
|
||||
};
|
||||
let segment = (claim.seq() % FRAME_SEGMENT_POOL_SIZE) as u32;
|
||||
|
||||
let Ok(mut staged) = self.0.staged.lock() else {
|
||||
tracing::error!("Failed to lock the frame staging state");
|
||||
return;
|
||||
};
|
||||
let staged = &mut *staged;
|
||||
if staged.segments.len() < FRAME_SEGMENT_POOL_SIZE as usize {
|
||||
staged.segments.resize_with(FRAME_SEGMENT_POOL_SIZE as usize, || IpcSharedMemory::from_bytes(&[]));
|
||||
}
|
||||
|
||||
let backing = &mut staged.segments[segment as usize];
|
||||
if backing.len() < buffer.len() {
|
||||
let capacity = buffer.len().next_multiple_of(FRAME_SEGMENT_GRANULARITY);
|
||||
*backing = IpcSharedMemory::from_byte(0, capacity);
|
||||
staged.pending_adverts.push((segment, backing.clone()));
|
||||
}
|
||||
|
||||
unsafe { backing.deref_mut()[..buffer.len()].copy_from_slice(buffer) };
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
if !staged.pending_adverts.iter().any(|(index, _)| *index == segment) {
|
||||
staged.pending_adverts.push((segment, backing.clone()));
|
||||
}
|
||||
|
||||
staged.buffer = Some(StagedBuffer { claim, segment, width, height });
|
||||
}
|
||||
|
||||
#[cfg(feature = "accelerated_paint")]
|
||||
pub(crate) fn stage_texture(&self, info: &cef::AcceleratedPaintInfo) {
|
||||
let Some(plane) = &self.0.plane else {
|
||||
tracing::error!("Accelerated paint delivered without a frame plane");
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(claim) = self.0.sequence.claim() else {
|
||||
return;
|
||||
};
|
||||
let Some(frame) = plane.stage(info) else {
|
||||
return;
|
||||
};
|
||||
let Ok(mut staged) = self.0.staged.lock() else {
|
||||
tracing::error!("Failed to lock the frame staging state");
|
||||
return;
|
||||
};
|
||||
staged.accelerated = Some((claim, frame));
|
||||
}
|
||||
|
||||
pub(crate) fn publish(&self) {
|
||||
let Ok(mut staged) = self.0.staged.lock() else {
|
||||
tracing::error!("Failed to lock the frame staging state");
|
||||
return;
|
||||
};
|
||||
let adverts = std::mem::take(&mut staged.pending_adverts);
|
||||
let software = staged.buffer.take();
|
||||
#[cfg(feature = "accelerated_paint")]
|
||||
let accelerated = staged.accelerated.take();
|
||||
drop(staged);
|
||||
|
||||
#[cfg(feature = "accelerated_paint")]
|
||||
if let Some((claim, frame)) = accelerated
|
||||
&& let Some(plane) = &self.0.plane
|
||||
{
|
||||
match plane.send(claim.seq(), frame) {
|
||||
Ok(()) => {
|
||||
if !claim.wait_for_ack() {
|
||||
tracing::warn!("Accelerated frame {} was not acked", claim.seq());
|
||||
}
|
||||
claim.commit();
|
||||
}
|
||||
Err(e) => tracing::debug!("Failed to send accelerated frame to main process: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
if adverts.is_empty() && software.is_none() {
|
||||
return;
|
||||
}
|
||||
let Ok(sender) = self.0.events.lock() else {
|
||||
tracing::error!("Failed to lock host message sender");
|
||||
return;
|
||||
};
|
||||
for (index, shm) in adverts {
|
||||
if let Err(e) = sender.send(EventMessage::AdvertiseFrameSegment { index, shm }) {
|
||||
tracing::debug!("Failed to send frame segment to main process: {e}");
|
||||
}
|
||||
}
|
||||
if let Some(StagedBuffer { claim, segment, width, height }) = software {
|
||||
match sender.send(EventMessage::SoftwareFrame {
|
||||
seq: claim.seq(),
|
||||
segment,
|
||||
width,
|
||||
height,
|
||||
}) {
|
||||
Ok(()) => claim.commit(),
|
||||
Err(e) => tracing::debug!("Failed to send frame to main process: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[cfg(feature = "accelerated_paint")]
|
||||
use super::import::ContentMapping;
|
||||
#[cfg(feature = "accelerated_paint")]
|
||||
use super::resample::Resampler;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct FrameSurface {
|
||||
device: wgpu::Device,
|
||||
queue: wgpu_sync::Queue,
|
||||
slot: Arc<Mutex<Option<wgpu::Texture>>>,
|
||||
#[cfg(feature = "accelerated_paint")]
|
||||
resampler: Resampler,
|
||||
}
|
||||
|
||||
impl FrameSurface {
|
||||
pub(crate) fn new(device: wgpu::Device, queue: wgpu_sync::Queue) -> Self {
|
||||
Self {
|
||||
#[cfg(feature = "accelerated_paint")]
|
||||
resampler: Resampler::new(device.clone()),
|
||||
device,
|
||||
queue,
|
||||
slot: Arc::new(Mutex::new(None)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn upload_buffer(&self, buffer: &[u8], width: u32, height: u32) -> Option<wgpu::Texture> {
|
||||
debug_assert_eq!(buffer.len(), width as usize * height as usize * 4);
|
||||
|
||||
let Ok(mut slot) = self.slot.lock() else {
|
||||
tracing::error!("Failed to lock the frame surface");
|
||||
return None;
|
||||
};
|
||||
|
||||
if buffer.chunks_exact(4).take(width as usize).all(|pixel| pixel[3] == 0) {
|
||||
tracing::debug!("Skipping fully transparent frame");
|
||||
return None;
|
||||
}
|
||||
|
||||
if slot.as_ref().is_none_or(|texture| texture.width() != width || texture.height() != height) {
|
||||
*slot = Some(self.device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("CEF Texture"),
|
||||
size: wgpu::Extent3d {
|
||||
width,
|
||||
height,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::Bgra8Unorm,
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
||||
view_formats: &[],
|
||||
}));
|
||||
}
|
||||
let texture = slot.as_ref()?;
|
||||
|
||||
self.queue.write_texture(
|
||||
wgpu::TexelCopyTextureInfo {
|
||||
texture,
|
||||
mip_level: 0,
|
||||
origin: wgpu::Origin3d::ZERO,
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
},
|
||||
buffer,
|
||||
wgpu::TexelCopyBufferLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: Some(4 * width),
|
||||
rows_per_image: None,
|
||||
},
|
||||
wgpu::Extent3d {
|
||||
width,
|
||||
height,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
);
|
||||
|
||||
Some(texture.clone())
|
||||
}
|
||||
|
||||
#[cfg(feature = "accelerated_paint")]
|
||||
pub(crate) fn import_texture(&self, importer: impl crate::frames::import::TextureImporter, content_rect: crate::frames::import::ContentRect) -> Option<wgpu::Texture> {
|
||||
let imported = match importer.import_to_wgpu(&self.device) {
|
||||
Ok(texture) => texture,
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to import remote accelerated frame: {e}");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("CEF Frame Copy Encoder"),
|
||||
});
|
||||
let output = match content_rect.mapping(imported.width(), imported.height()) {
|
||||
ContentMapping::Identity => {
|
||||
let output = self.device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("CEF Imported Frame Copy"),
|
||||
size: imported.size(),
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: imported.format(),
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::COPY_DST,
|
||||
view_formats: &[],
|
||||
});
|
||||
encoder.copy_texture_to_texture(
|
||||
wgpu::TexelCopyTextureInfo {
|
||||
texture: &imported,
|
||||
mip_level: 0,
|
||||
origin: wgpu::Origin3d::ZERO,
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
},
|
||||
wgpu::TexelCopyTextureInfo {
|
||||
texture: &output,
|
||||
mip_level: 0,
|
||||
origin: wgpu::Origin3d::ZERO,
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
},
|
||||
imported.size(),
|
||||
);
|
||||
output
|
||||
}
|
||||
ContentMapping::Scaled(content_rect) => {
|
||||
let output = self.device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("CEF Imported Scaled Frame Copy"),
|
||||
size: wgpu::Extent3d {
|
||||
width: content_rect.source_width,
|
||||
height: content_rect.source_height,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: imported.format(),
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
view_formats: &[],
|
||||
});
|
||||
let size = wgpu::Extent3d {
|
||||
width: content_rect.width,
|
||||
height: content_rect.height,
|
||||
depth_or_array_layers: 1,
|
||||
};
|
||||
self.resampler.encode(
|
||||
&mut encoder,
|
||||
&imported,
|
||||
wgpu::Origin3d {
|
||||
x: content_rect.x,
|
||||
y: content_rect.y,
|
||||
z: 0,
|
||||
},
|
||||
size,
|
||||
&output,
|
||||
);
|
||||
output
|
||||
}
|
||||
};
|
||||
|
||||
let blank_check = blank_check::encode_readback(&self.device, &mut encoder, &output);
|
||||
|
||||
let submission = self.queue.submit([encoder.finish()]);
|
||||
|
||||
let blank_check = blank_check.map();
|
||||
|
||||
let _ = self.device.poll(wgpu::PollType::Wait {
|
||||
submission_index: Some(submission),
|
||||
timeout: None,
|
||||
});
|
||||
|
||||
if blank_check.check_is_blank() {
|
||||
tracing::debug!("Skipping fully transparent accelerated frame");
|
||||
return None;
|
||||
}
|
||||
|
||||
let Ok(mut slot) = self.slot.lock() else {
|
||||
tracing::error!("Failed to lock the frame surface");
|
||||
return None;
|
||||
};
|
||||
*slot = Some(output.clone());
|
||||
Some(output)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "accelerated_paint")]
|
||||
mod blank_check {
|
||||
use std::sync::mpsc;
|
||||
|
||||
const STRIP_BYTES: u32 = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
|
||||
const STRIP_TEXELS: u32 = STRIP_BYTES / 4;
|
||||
|
||||
pub(super) fn encode_readback(device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, texture: &wgpu::Texture) -> PendingBlankCheck {
|
||||
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("CEF Blank Check"),
|
||||
size: STRIP_BYTES as u64,
|
||||
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
let width = texture.width().min(STRIP_TEXELS);
|
||||
encoder.copy_texture_to_buffer(
|
||||
wgpu::TexelCopyTextureInfo {
|
||||
texture,
|
||||
mip_level: 0,
|
||||
origin: wgpu::Origin3d {
|
||||
x: (texture.width() - width) / 2,
|
||||
y: 0,
|
||||
z: 0,
|
||||
},
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
},
|
||||
wgpu::TexelCopyBufferInfo {
|
||||
buffer: &buffer,
|
||||
layout: wgpu::TexelCopyBufferLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: Some(STRIP_BYTES),
|
||||
rows_per_image: None,
|
||||
},
|
||||
},
|
||||
wgpu::Extent3d {
|
||||
width,
|
||||
height: 1,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
);
|
||||
PendingBlankCheck { buffer, width }
|
||||
}
|
||||
|
||||
pub(super) struct PendingBlankCheck {
|
||||
buffer: wgpu::Buffer,
|
||||
width: u32,
|
||||
}
|
||||
|
||||
impl PendingBlankCheck {
|
||||
pub(super) fn map(self) -> MappedBlankCheck {
|
||||
let (sender, receiver) = mpsc::channel();
|
||||
self.buffer.slice(..u64::from(self.width) * 4).map_async(wgpu::MapMode::Read, move |result| {
|
||||
let _ = sender.send(result);
|
||||
});
|
||||
MappedBlankCheck {
|
||||
buffer: self.buffer,
|
||||
width: self.width,
|
||||
receiver,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct MappedBlankCheck {
|
||||
buffer: wgpu::Buffer,
|
||||
width: u32,
|
||||
receiver: mpsc::Receiver<Result<(), wgpu::BufferAsyncError>>,
|
||||
}
|
||||
|
||||
impl MappedBlankCheck {
|
||||
pub(super) fn check_is_blank(self) -> bool {
|
||||
match self.receiver.try_recv() {
|
||||
Ok(Ok(())) => {
|
||||
let slice = self.buffer.slice(..u64::from(self.width) * 4);
|
||||
slice.get_mapped_range().chunks_exact(4).all(|texel| texel[3] == 0)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user