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:
Timon
2026-07-14 14:51:20 -07:00
committed by Keavon Chambers
parent 97a43e66fb
commit ba0a97aefe
82 changed files with 4768 additions and 1291 deletions
+117
View File
@@ -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: &[],
})
}
+136
View File
@@ -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()))
}
}
}
+226
View File
@@ -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))
}
+93
View File
@@ -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) })
}
}
+35
View File
@@ -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
}
}
}
+265
View File
@@ -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)
}
}
+305
View File
@@ -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)
}
}
+175
View File
@@ -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}");
}
}
}
+131
View File
@@ -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);
}
}
+143
View File
@@ -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],
}
+77
View File
@@ -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));
}
+85
View File
@@ -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);
}
}
}
+45
View File
@@ -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;
}
}
}
+155
View File
@@ -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}"),
}
}
}
}
+263
View File
@@ -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,
}
}
}
}