Transfer Servo IOSurface ports over Mach
This commit is contained in:
Generated
+3
@@ -2255,6 +2255,7 @@ dependencies = [
|
||||
"gpui-component-assets",
|
||||
"image",
|
||||
"io-surface",
|
||||
"mach2",
|
||||
"objc2",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation",
|
||||
@@ -2268,6 +2269,7 @@ dependencies = [
|
||||
"tracing-subscriber",
|
||||
"ureq",
|
||||
"url",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2309,6 +2311,7 @@ dependencies = [
|
||||
"glow",
|
||||
"image",
|
||||
"log",
|
||||
"mach2",
|
||||
"objc2-io-surface",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
||||
@@ -35,10 +35,12 @@ core-foundation = "0.10"
|
||||
# version it built against.
|
||||
core-video = "0.4"
|
||||
io-surface = "0.16"
|
||||
mach2 = "0.6"
|
||||
objc2 = "0.6"
|
||||
objc2-core-foundation = { version = "0.3.2", features = ["CFBase", "CFDictionary", "CFNumber", "CFString"] }
|
||||
objc2-foundation = { version = "0.3.1", features = ["NSDictionary", "NSString", "NSValue"] }
|
||||
objc2-io-surface = "0.3.2"
|
||||
uuid.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
ffi::CString,
|
||||
mem,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use mach2::{
|
||||
bootstrap::{bootstrap_port, bootstrap_register},
|
||||
kern_return::KERN_SUCCESS,
|
||||
mach_port::{mach_port_allocate, mach_port_destroy, mach_port_insert_right},
|
||||
message::{
|
||||
MACH_MSG_PORT_DESCRIPTOR, MACH_MSG_SUCCESS, MACH_MSG_TIMEOUT_NONE, MACH_MSG_TYPE_MAKE_SEND,
|
||||
MACH_RCV_MSG, MACH_RCV_TIMED_OUT, MACH_RCV_TIMEOUT, mach_msg, mach_msg_body_t,
|
||||
mach_msg_header_t, mach_msg_port_descriptor_t, mach_msg_trailer_t,
|
||||
},
|
||||
port::{MACH_PORT_NULL, MACH_PORT_RIGHT_RECEIVE, mach_port_t},
|
||||
traps::mach_task_self,
|
||||
};
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
const IOSURFACE_PORT_MESSAGE_ID: i32 = 0x454c_5901;
|
||||
const SERVICE_PREFIX: &str = "com.ely.browser.iosurface";
|
||||
|
||||
pub(crate) struct IOSurfaceMachReceiver {
|
||||
service_name: String,
|
||||
receive_port: mach_port_t,
|
||||
pending_ports: BTreeMap<u64, mach_port_t>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub(crate) enum IOSurfaceMachError {
|
||||
#[error("Mach service name contains an interior nul byte")]
|
||||
InvalidServiceName,
|
||||
|
||||
#[error("mach_port_allocate returned {code}")]
|
||||
AllocatePort { code: i32 },
|
||||
|
||||
#[error("mach_port_insert_right returned {code}")]
|
||||
InsertSendRight { code: i32 },
|
||||
|
||||
#[error("bootstrap_register returned {code}")]
|
||||
RegisterService { code: i32 },
|
||||
|
||||
#[error("mach_msg receive timed out for IOSurface surface {surface_id:#x}")]
|
||||
ReceiveTimedOut { surface_id: u64 },
|
||||
|
||||
#[error("mach_msg receive returned {code}")]
|
||||
Receive { code: i32 },
|
||||
|
||||
#[error("received unexpected Mach message id {message_id}")]
|
||||
UnexpectedMessage { message_id: i32 },
|
||||
|
||||
#[error("received invalid IOSurface Mach message")]
|
||||
InvalidMessage,
|
||||
}
|
||||
|
||||
impl IOSurfaceMachReceiver {
|
||||
pub(crate) fn new() -> Result<Self, IOSurfaceMachError> {
|
||||
let service_name = unique_service_name();
|
||||
let service_name_c = CString::new(service_name.as_str())
|
||||
.map_err(|_| IOSurfaceMachError::InvalidServiceName)?;
|
||||
let mut receive_port = MACH_PORT_NULL;
|
||||
|
||||
#[expect(unsafe_code)]
|
||||
let task = unsafe { mach_task_self() };
|
||||
#[expect(unsafe_code)]
|
||||
let allocate =
|
||||
unsafe { mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, &mut receive_port) };
|
||||
if allocate != KERN_SUCCESS {
|
||||
return Err(IOSurfaceMachError::AllocatePort { code: allocate });
|
||||
}
|
||||
|
||||
#[expect(unsafe_code)]
|
||||
let insert = unsafe {
|
||||
mach_port_insert_right(task, receive_port, receive_port, MACH_MSG_TYPE_MAKE_SEND)
|
||||
};
|
||||
if insert != KERN_SUCCESS {
|
||||
destroy_port(receive_port);
|
||||
return Err(IOSurfaceMachError::InsertSendRight { code: insert });
|
||||
}
|
||||
|
||||
#[expect(unsafe_code)]
|
||||
#[allow(deprecated)]
|
||||
let register = unsafe {
|
||||
bootstrap_register(bootstrap_port, service_name_c.as_ptr() as *mut _, receive_port)
|
||||
};
|
||||
if register != KERN_SUCCESS {
|
||||
destroy_port(receive_port);
|
||||
return Err(IOSurfaceMachError::RegisterService { code: register });
|
||||
}
|
||||
|
||||
Ok(Self { service_name, receive_port, pending_ports: BTreeMap::new() })
|
||||
}
|
||||
|
||||
pub(crate) fn service_name(&self) -> &str {
|
||||
self.service_name.as_str()
|
||||
}
|
||||
|
||||
pub(crate) fn receive_port_for_surface(
|
||||
&mut self,
|
||||
surface_id: u64,
|
||||
timeout: Duration,
|
||||
) -> Result<mach_port_t, IOSurfaceMachError> {
|
||||
if let Some(port) = self.pending_ports.remove(&surface_id) {
|
||||
return Ok(port);
|
||||
}
|
||||
|
||||
let deadline = Instant::now() + timeout;
|
||||
loop {
|
||||
let remaining = deadline.saturating_duration_since(Instant::now());
|
||||
if remaining.is_zero() {
|
||||
return Err(IOSurfaceMachError::ReceiveTimedOut { surface_id });
|
||||
}
|
||||
let Some(received) = self.receive_one(remaining)? else {
|
||||
return Err(IOSurfaceMachError::ReceiveTimedOut { surface_id });
|
||||
};
|
||||
if received.surface_id == surface_id {
|
||||
return Ok(received.mach_port);
|
||||
}
|
||||
self.pending_ports.insert(received.surface_id, received.mach_port);
|
||||
}
|
||||
}
|
||||
|
||||
fn receive_one(
|
||||
&self,
|
||||
timeout: Duration,
|
||||
) -> Result<Option<ReceivedSurfacePort>, IOSurfaceMachError> {
|
||||
#[expect(unsafe_code)]
|
||||
let mut received_message: ReceivedIOSurfacePortMessage = unsafe { mem::zeroed() };
|
||||
let timeout_ms = timeout_millis(timeout);
|
||||
#[expect(unsafe_code)]
|
||||
let result = unsafe {
|
||||
mach_msg(
|
||||
&mut received_message.message.header,
|
||||
MACH_RCV_MSG | MACH_RCV_TIMEOUT,
|
||||
0,
|
||||
mem::size_of::<ReceivedIOSurfacePortMessage>() as u32,
|
||||
self.receive_port,
|
||||
timeout_ms,
|
||||
MACH_PORT_NULL,
|
||||
)
|
||||
};
|
||||
|
||||
if result == MACH_RCV_TIMED_OUT {
|
||||
return Ok(None);
|
||||
}
|
||||
if result != MACH_MSG_SUCCESS {
|
||||
return Err(IOSurfaceMachError::Receive { code: result });
|
||||
}
|
||||
let message = &mut received_message.message;
|
||||
if message.header.msgh_id != IOSURFACE_PORT_MESSAGE_ID {
|
||||
destroy_message(message);
|
||||
return Err(IOSurfaceMachError::UnexpectedMessage {
|
||||
message_id: message.header.msgh_id,
|
||||
});
|
||||
}
|
||||
if message.body.msgh_descriptor_count != 1
|
||||
|| message.surface_port.type_ != MACH_MSG_PORT_DESCRIPTOR as u8
|
||||
|| message.surface_port.name == MACH_PORT_NULL
|
||||
{
|
||||
destroy_message(message);
|
||||
return Err(IOSurfaceMachError::InvalidMessage);
|
||||
}
|
||||
|
||||
Ok(Some(ReceivedSurfacePort {
|
||||
surface_id: message.surface_id,
|
||||
mach_port: message.surface_port.name,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for IOSurfaceMachReceiver {
|
||||
fn drop(&mut self) {
|
||||
for port in std::mem::take(&mut self.pending_ports).into_values() {
|
||||
deallocate_port(port);
|
||||
}
|
||||
destroy_port(self.receive_port);
|
||||
}
|
||||
}
|
||||
|
||||
struct ReceivedSurfacePort {
|
||||
surface_id: u64,
|
||||
mach_port: mach_port_t,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct IOSurfacePortMessage {
|
||||
header: mach_msg_header_t,
|
||||
body: mach_msg_body_t,
|
||||
surface_port: mach_msg_port_descriptor_t,
|
||||
surface_id: u64,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct ReceivedIOSurfacePortMessage {
|
||||
message: IOSurfacePortMessage,
|
||||
_trailer: mach_msg_trailer_t,
|
||||
}
|
||||
|
||||
fn unique_service_name() -> String {
|
||||
format!("{SERVICE_PREFIX}.{}", Uuid::now_v7().as_simple())
|
||||
}
|
||||
|
||||
fn timeout_millis(timeout: Duration) -> u32 {
|
||||
u32::try_from(timeout.as_millis()).unwrap_or(u32::MAX).max(MACH_MSG_TIMEOUT_NONE + 1)
|
||||
}
|
||||
|
||||
fn destroy_message(message: &mut IOSurfacePortMessage) {
|
||||
#[expect(unsafe_code)]
|
||||
unsafe {
|
||||
mach2::message::mach_msg_destroy(&mut message.header);
|
||||
}
|
||||
}
|
||||
|
||||
fn destroy_port(port: mach_port_t) {
|
||||
#[expect(unsafe_code)]
|
||||
let task = unsafe { mach_task_self() };
|
||||
#[expect(unsafe_code)]
|
||||
unsafe {
|
||||
let _ = mach_port_destroy(task, port);
|
||||
}
|
||||
}
|
||||
|
||||
fn deallocate_port(port: mach_port_t) {
|
||||
#[expect(unsafe_code)]
|
||||
let task = unsafe { mach_task_self() };
|
||||
#[expect(unsafe_code)]
|
||||
unsafe {
|
||||
let _ = mach2::mach_port::mach_port_deallocate(task, port);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@ pub mod download_checksums;
|
||||
pub mod download_files;
|
||||
pub mod http_downloads;
|
||||
#[cfg(target_os = "macos")]
|
||||
pub(crate) mod iosurface_mach;
|
||||
#[cfg(target_os = "macos")]
|
||||
pub(crate) mod iosurface_metal;
|
||||
pub mod plugin_package_store;
|
||||
pub mod plugin_packages;
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::{
|
||||
io::{self, BufRead, BufReader, Read, Write},
|
||||
path::PathBuf,
|
||||
process::{Child, ChildStdin, ChildStdout, Stdio},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
/// Environment variable that lets the user pick the rendering context
|
||||
@@ -18,13 +19,16 @@ use thiserror::Error;
|
||||
mod wire;
|
||||
|
||||
use super::servo_sidecar_command::{
|
||||
SidecarCommandError, default_sidecar_command, rendering_context_from_env,
|
||||
SidecarCommandError, SidecarRenderingContext, default_sidecar_command,
|
||||
rendering_context_from_env,
|
||||
};
|
||||
use wire::{
|
||||
LiveFrameReport, LiveRequest, LiveResponse, LiveSurfaceHandle, log_frame_perf,
|
||||
log_iosurface_current, log_iosurface_handle,
|
||||
};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use super::iosurface_mach::{IOSurfaceMachError, IOSurfaceMachReceiver};
|
||||
#[cfg(target_os = "macos")]
|
||||
use super::iosurface_metal::IOSurfaceCache;
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -39,6 +43,8 @@ pub(crate) struct ServoLiveClient {
|
||||
/// software-path tabs never trigger construction.
|
||||
#[cfg(target_os = "macos")]
|
||||
iosurface_cache: IOSurfaceCache,
|
||||
#[cfg(target_os = "macos")]
|
||||
iosurface_receiver: Option<IOSurfaceMachReceiver>,
|
||||
}
|
||||
|
||||
impl ServoLiveClient {
|
||||
@@ -48,9 +54,18 @@ impl ServoLiveClient {
|
||||
return Err(ServoLiveError::SidecarBinaryUnavailable { path: path.to_path_buf() });
|
||||
}
|
||||
|
||||
let rendering_context = rendering_context_from_env();
|
||||
let mut command = command_target.command();
|
||||
command.arg("live").arg("--profile-data-dir").arg(profile_data_dir);
|
||||
command.arg("--rendering-context").arg(rendering_context_from_env().cli_arg());
|
||||
command.arg("--rendering-context").arg(rendering_context.cli_arg());
|
||||
#[cfg(target_os = "macos")]
|
||||
let iosurface_receiver = if rendering_context == SidecarRenderingContext::Hardware {
|
||||
let receiver = IOSurfaceMachReceiver::new()?;
|
||||
command.arg("--iosurface-mach-service").arg(receiver.service_name());
|
||||
Some(receiver)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut child = command
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
@@ -67,6 +82,8 @@ impl ServoLiveClient {
|
||||
stdout: BufReader::new(stdout),
|
||||
#[cfg(target_os = "macos")]
|
||||
iosurface_cache: IOSurfaceCache::new(),
|
||||
#[cfg(target_os = "macos")]
|
||||
iosurface_receiver,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -195,7 +212,13 @@ impl ServoLiveClient {
|
||||
&mut self,
|
||||
handle: &LiveSurfaceHandle,
|
||||
) -> Result<(), ServoLiveError> {
|
||||
match self.iosurface_cache.import(handle.mach_port_name, handle.surface_id) {
|
||||
let mach_port_name = match self.iosurface_receiver.as_mut() {
|
||||
Some(receiver) => {
|
||||
receiver.receive_port_for_surface(handle.surface_id, Duration::from_secs(1))?
|
||||
}
|
||||
None => handle.mach_port_name,
|
||||
};
|
||||
match self.iosurface_cache.import(mach_port_name, handle.surface_id) {
|
||||
Ok(()) => tracing::info!(
|
||||
target: "ely::servo::iosurface",
|
||||
surface_id = handle.surface_id,
|
||||
@@ -212,7 +235,7 @@ impl ServoLiveClient {
|
||||
);
|
||||
return Err(ServoLiveError::IOSurfaceImportFailed {
|
||||
surface_id: handle.surface_id,
|
||||
mach_port_name: handle.mach_port_name,
|
||||
mach_port_name,
|
||||
message: error.to_string(),
|
||||
});
|
||||
}
|
||||
@@ -438,6 +461,10 @@ pub(crate) enum ServoLiveError {
|
||||
#[error("servo live IOSurface {surface_id:#x} was selected before its pixel buffer import")]
|
||||
IOSurfacePixelBufferMissing { surface_id: u64 },
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[error(transparent)]
|
||||
IOSurfaceMach(#[from] IOSurfaceMachError),
|
||||
|
||||
#[error(transparent)]
|
||||
Json(#[from] serde_json::Error),
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ hardware-render = [
|
||||
"dep:glow",
|
||||
"dep:image",
|
||||
"dep:log",
|
||||
"dep:mach2",
|
||||
"dep:surfman",
|
||||
"dep:objc2-io-surface",
|
||||
]
|
||||
@@ -39,6 +40,7 @@ thiserror.workspace = true
|
||||
url = { workspace = true, optional = true }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
mach2 = { version = "0.6", optional = true }
|
||||
objc2-io-surface = { version = "0.3.2", optional = true }
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -14,6 +14,9 @@ use thiserror::Error;
|
||||
|
||||
#[path = "ely_servo_sidecar/args.rs"]
|
||||
mod args;
|
||||
#[cfg(all(feature = "hardware-render", target_os = "macos"))]
|
||||
#[path = "ely_servo_sidecar/iosurface_mach.rs"]
|
||||
mod iosurface_mach;
|
||||
#[path = "ely_servo_sidecar/live.rs"]
|
||||
mod live;
|
||||
#[path = "ely_servo_sidecar/live_output.rs"]
|
||||
|
||||
@@ -15,6 +15,7 @@ pub(super) enum SidecarCommand {
|
||||
|
||||
pub(super) struct LiveArgs {
|
||||
pub(super) profile_data_dir: PathBuf,
|
||||
pub(super) iosurface_mach_service: Option<String>,
|
||||
/// Rendering context the host's webviews are built against.
|
||||
/// Defaults to [`RenderingContextKind::Software`], which keeps
|
||||
/// the binary's behaviour bit-identical to pre-flag builds.
|
||||
@@ -137,6 +138,7 @@ fn parse_command(
|
||||
fn parse_live_args(args: impl IntoIterator<Item = String>) -> Result<LiveArgs, SidecarArgsError> {
|
||||
let mut args = args.into_iter();
|
||||
let mut profile_data_dir = None;
|
||||
let mut iosurface_mach_service = None;
|
||||
let mut rendering_context_kind = RenderingContextKind::default();
|
||||
|
||||
while let Some(name) = args.next() {
|
||||
@@ -155,6 +157,10 @@ fn parse_live_args(args: impl IntoIterator<Item = String>) -> Result<LiveArgs, S
|
||||
_ => return Err(SidecarArgsError::InvalidRenderingContext { value }),
|
||||
};
|
||||
}
|
||||
"--iosurface-mach-service" => {
|
||||
iosurface_mach_service =
|
||||
Some(next_argument(&mut args, "--iosurface-mach-service")?);
|
||||
}
|
||||
_ => return Err(SidecarArgsError::UnknownArgument { value: name }),
|
||||
}
|
||||
}
|
||||
@@ -162,6 +168,7 @@ fn parse_live_args(args: impl IntoIterator<Item = String>) -> Result<LiveArgs, S
|
||||
Ok(LiveArgs {
|
||||
profile_data_dir: profile_data_dir
|
||||
.ok_or(SidecarArgsError::MissingRequiredArgument { name: "--profile-data-dir" })?,
|
||||
iosurface_mach_service,
|
||||
rendering_context_kind,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -159,6 +159,13 @@ fn live_accepts_explicit_hardware_rendering_context() -> Result<(), SidecarArgsE
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn live_accepts_iosurface_mach_service_name() -> Result<(), SidecarArgsError> {
|
||||
let args = parse_live(&["--iosurface-mach-service", "com.ely.test.iosurface"])?;
|
||||
assert_eq!(args.iosurface_mach_service.as_deref(), Some("com.ely.test.iosurface"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn live_rejects_unknown_rendering_context_value() {
|
||||
assert!(matches!(
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
use std::{ffi::CString, mem, time::Duration};
|
||||
|
||||
use mach2::{
|
||||
bootstrap::{bootstrap_look_up, bootstrap_port},
|
||||
kern_return::KERN_SUCCESS,
|
||||
mach_port::mach_port_deallocate,
|
||||
message::{
|
||||
MACH_MSG_SUCCESS, MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MOVE_SEND, MACH_MSGH_BITS,
|
||||
MACH_MSGH_BITS_COMPLEX, MACH_SEND_MSG, MACH_SEND_TIMEOUT, mach_msg, mach_msg_body_t,
|
||||
mach_msg_header_t, mach_msg_port_descriptor_t,
|
||||
},
|
||||
port::{MACH_PORT_NULL, mach_port_t},
|
||||
traps::mach_task_self,
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
use super::live_protocol::{LiveOutcome, LiveSidecarError};
|
||||
|
||||
const IOSURFACE_PORT_MESSAGE_ID: i32 = 0x454c_5901;
|
||||
const SEND_TIMEOUT: Duration = Duration::from_secs(1);
|
||||
|
||||
pub(super) struct IOSurfaceMachSender {
|
||||
send_port: mach_port_t,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub(super) enum IOSurfaceMachError {
|
||||
#[error("Mach service name contains an interior nul byte")]
|
||||
InvalidServiceName,
|
||||
|
||||
#[error("bootstrap_look_up returned {code}")]
|
||||
LookupService { code: i32 },
|
||||
|
||||
#[error("mach_msg send returned {code}")]
|
||||
Send { code: i32 },
|
||||
}
|
||||
|
||||
impl IOSurfaceMachSender {
|
||||
pub(super) fn connect(service_name: &str) -> Result<Self, IOSurfaceMachError> {
|
||||
let service_name =
|
||||
CString::new(service_name).map_err(|_| IOSurfaceMachError::InvalidServiceName)?;
|
||||
let mut send_port = MACH_PORT_NULL;
|
||||
#[expect(unsafe_code)]
|
||||
let result =
|
||||
unsafe { bootstrap_look_up(bootstrap_port, service_name.as_ptr(), &mut send_port) };
|
||||
if result != KERN_SUCCESS {
|
||||
return Err(IOSurfaceMachError::LookupService { code: result });
|
||||
}
|
||||
Ok(Self { send_port })
|
||||
}
|
||||
|
||||
pub(super) fn send_surface_port(
|
||||
&mut self,
|
||||
surface_id: u64,
|
||||
mach_port: mach_port_t,
|
||||
) -> Result<(), IOSurfaceMachError> {
|
||||
let mut message = IOSurfacePortMessage {
|
||||
header: mach_msg_header_t {
|
||||
msgh_bits: MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0) | MACH_MSGH_BITS_COMPLEX,
|
||||
msgh_size: mem::size_of::<IOSurfacePortMessage>() as u32,
|
||||
msgh_remote_port: self.send_port,
|
||||
msgh_local_port: MACH_PORT_NULL,
|
||||
msgh_voucher_port: MACH_PORT_NULL,
|
||||
msgh_id: IOSURFACE_PORT_MESSAGE_ID,
|
||||
},
|
||||
body: mach_msg_body_t { msgh_descriptor_count: 1 },
|
||||
surface_port: mach_msg_port_descriptor_t::new(mach_port, MACH_MSG_TYPE_MOVE_SEND),
|
||||
surface_id,
|
||||
};
|
||||
#[expect(unsafe_code)]
|
||||
let result = unsafe {
|
||||
mach_msg(
|
||||
&mut message.header,
|
||||
MACH_SEND_MSG | MACH_SEND_TIMEOUT,
|
||||
message.header.msgh_size,
|
||||
0,
|
||||
MACH_PORT_NULL,
|
||||
timeout_millis(SEND_TIMEOUT),
|
||||
MACH_PORT_NULL,
|
||||
)
|
||||
};
|
||||
if result != MACH_MSG_SUCCESS {
|
||||
destroy_message(&mut message);
|
||||
return Err(IOSurfaceMachError::Send { code: result });
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for IOSurfaceMachSender {
|
||||
fn drop(&mut self) {
|
||||
#[expect(unsafe_code)]
|
||||
let task = unsafe { mach_task_self() };
|
||||
#[expect(unsafe_code)]
|
||||
unsafe {
|
||||
let _ = mach_port_deallocate(task, self.send_port);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn send_surface_port_if_needed(
|
||||
sender: Option<&mut IOSurfaceMachSender>,
|
||||
outcome: &mut Result<LiveOutcome, LiveSidecarError>,
|
||||
) {
|
||||
let (Some(sender), Ok(live_outcome)) = (sender, outcome.as_ref()) else {
|
||||
return;
|
||||
};
|
||||
let Some(handle) = live_outcome.response.surface_handle else {
|
||||
return;
|
||||
};
|
||||
if let Err(error) = sender.send_surface_port(handle.surface_id, handle.mach_port_name) {
|
||||
*outcome = Err(LiveSidecarError::IOSurfaceMach(error));
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct IOSurfacePortMessage {
|
||||
header: mach_msg_header_t,
|
||||
body: mach_msg_body_t,
|
||||
surface_port: mach_msg_port_descriptor_t,
|
||||
surface_id: u64,
|
||||
}
|
||||
|
||||
fn timeout_millis(timeout: Duration) -> u32 {
|
||||
u32::try_from(timeout.as_millis()).unwrap_or(u32::MAX).max(1)
|
||||
}
|
||||
|
||||
fn destroy_message(message: &mut IOSurfacePortMessage) {
|
||||
#[expect(unsafe_code)]
|
||||
unsafe {
|
||||
mach2::message::mach_msg_destroy(&mut message.header);
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,8 @@ use ely_servo_host::{
|
||||
};
|
||||
|
||||
use super::args::LiveArgs;
|
||||
#[cfg(all(feature = "hardware-render", target_os = "macos"))]
|
||||
use super::iosurface_mach::{IOSurfaceMachSender, send_surface_port_if_needed};
|
||||
use super::live_output::{populate_surface_fields, write_outcome};
|
||||
pub(super) use super::live_protocol::LiveSidecarError;
|
||||
use super::live_protocol::{
|
||||
@@ -28,14 +30,19 @@ const LIVE_FRAME_WAIT_TIMEOUT: Duration = Duration::from_millis(250);
|
||||
const LIVE_FRAME_WAIT_INTERVAL: Duration = Duration::from_millis(2);
|
||||
|
||||
pub(super) fn run_live(args: LiveArgs) -> Result<(), LiveSidecarError> {
|
||||
fs::create_dir_all(&args.profile_data_dir)?;
|
||||
let rendering_context_kind = args.rendering_context_kind;
|
||||
let LiveArgs { profile_data_dir, iosurface_mach_service, rendering_context_kind } = args;
|
||||
fs::create_dir_all(&profile_data_dir)?;
|
||||
let context_label = rendering_context_label(rendering_context_kind);
|
||||
let mut host = SoftwareServoHost::new_with_config_dir_and_kind(
|
||||
ServoSurfaceSize::new(1, 1),
|
||||
Some(args.profile_data_dir),
|
||||
Some(profile_data_dir),
|
||||
rendering_context_kind,
|
||||
)?;
|
||||
#[cfg(all(feature = "hardware-render", target_os = "macos"))]
|
||||
let mut iosurface_mach_sender =
|
||||
iosurface_mach_service.as_deref().map(IOSurfaceMachSender::connect).transpose()?;
|
||||
#[cfg(not(all(feature = "hardware-render", target_os = "macos")))]
|
||||
let _ = iosurface_mach_service;
|
||||
let mut sessions = HashMap::new();
|
||||
let mut perf =
|
||||
FramePerfAggregator::new(context_label, FramePerfAggregator::DEFAULT_WINDOW_SIZE);
|
||||
@@ -56,7 +63,7 @@ pub(super) fn run_live(args: LiveArgs) -> Result<(), LiveSidecarError> {
|
||||
// matching stop is the `stdout.flush()` inside
|
||||
// `write_outcome`.
|
||||
let frame_started_at = Instant::now();
|
||||
let outcome = match serde_json::from_str::<LiveRequest>(&line) {
|
||||
let mut outcome = match serde_json::from_str::<LiveRequest>(&line) {
|
||||
Ok(request) => handle_request(
|
||||
&mut host,
|
||||
&mut sessions,
|
||||
@@ -66,6 +73,8 @@ pub(super) fn run_live(args: LiveArgs) -> Result<(), LiveSidecarError> {
|
||||
),
|
||||
Err(error) => Err(LiveSidecarError::Json(error)),
|
||||
};
|
||||
#[cfg(all(feature = "hardware-render", target_os = "macos"))]
|
||||
send_surface_port_if_needed(iosurface_mach_sender.as_mut(), &mut outcome);
|
||||
write_outcome(&mut stdout, &mut perf, &mut pending_summary, outcome, frame_started_at)?;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ use ely_servo_host::{
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
#[cfg(all(feature = "hardware-render", target_os = "macos"))]
|
||||
use super::iosurface_mach::IOSurfaceMachError;
|
||||
use super::perf::FramePerfSummary;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -242,6 +244,10 @@ pub(super) enum LiveSidecarError {
|
||||
|
||||
#[error(transparent)]
|
||||
Json(#[from] serde_json::Error),
|
||||
|
||||
#[cfg(all(feature = "hardware-render", target_os = "macos"))]
|
||||
#[error(transparent)]
|
||||
IOSurfaceMach(#[from] IOSurfaceMachError),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in New Issue
Block a user