feat(servo): isolate profiles with hardware sidecars

This commit is contained in:
2026-07-09 20:27:22 -04:00
parent 78dc86b18e
commit c28ec2bee8
92 changed files with 10306 additions and 1485 deletions
+47 -15
View File
@@ -384,19 +384,17 @@ impl MetalRenderer {
let mut instance_buffer = self.instance_buffer_pool.lock().acquire(&self.device);
let command_buffer =
self.draw_primitives(scene, &mut instance_buffer, drawable, viewport_size);
self.draw_primitives(scene, &mut instance_buffer, drawable.texture(), viewport_size);
match command_buffer {
Ok(command_buffer) => {
let instance_buffer_pool = self.instance_buffer_pool.clone();
let instance_buffer = Cell::new(Some(instance_buffer));
let block = ConcreteBlock::new(move |_| {
if let Some(instance_buffer) = instance_buffer.take() {
instance_buffer_pool.lock().release(instance_buffer);
}
});
let block = block.copy();
command_buffer.add_completed_handler(&block);
retain_frame_resources_until_completion(
command_buffer.as_ref(),
scene,
instance_buffer,
self.instance_buffer_pool.clone(),
None,
);
if self.presents_with_transaction {
command_buffer.commit();
@@ -433,7 +431,7 @@ impl MetalRenderer {
&mut self,
scene: &Scene,
instance_buffer: &mut InstanceBuffer,
drawable: &metal::MetalDrawableRef,
target_texture: &metal::TextureRef,
viewport_size: Size<DevicePixels>,
) -> Result<metal::CommandBuffer> {
let command_queue = self.command_queue.clone();
@@ -443,7 +441,7 @@ impl MetalRenderer {
let mut command_encoder = new_command_encoder(
command_buffer,
drawable,
target_texture,
viewport_size,
|color_attachment| {
color_attachment.set_load_action(metal::MTLLoadAction::Clear);
@@ -480,7 +478,7 @@ impl MetalRenderer {
command_encoder = new_command_encoder(
command_buffer,
drawable,
target_texture,
viewport_size,
|color_attachment| {
color_attachment.set_load_action(metal::MTLLoadAction::Load);
@@ -1197,9 +1195,36 @@ impl MetalRenderer {
}
}
fn retain_frame_resources_until_completion(
command_buffer: &metal::CommandBufferRef,
scene: &Scene,
instance_buffer: InstanceBuffer,
instance_buffer_pool: Arc<Mutex<InstanceBufferPool>>,
completion_tx: Option<std::sync::mpsc::Sender<()>>,
) {
let instance_buffer = Cell::new(Some(instance_buffer));
let surface_leases = Cell::new(Some(
scene
.surfaces
.iter()
.filter_map(|surface| surface.lease.clone())
.collect::<Vec<_>>(),
));
let block = ConcreteBlock::new(move |_| {
let _ = surface_leases.take();
if let Some(instance_buffer) = instance_buffer.take() {
instance_buffer_pool.lock().release(instance_buffer);
}
if let Some(completion_tx) = completion_tx.as_ref() {
let _ = completion_tx.send(());
}
});
command_buffer.add_completed_handler(&block.copy());
}
fn new_command_encoder<'a>(
command_buffer: &'a metal::CommandBufferRef,
drawable: &'a metal::MetalDrawableRef,
target_texture: &'a metal::TextureRef,
viewport_size: Size<DevicePixels>,
configure_color_attachment: impl Fn(&RenderPassColorAttachmentDescriptorRef),
) -> &'a metal::RenderCommandEncoderRef {
@@ -1208,7 +1233,7 @@ fn new_command_encoder<'a>(
.color_attachments()
.object_at(0)
.unwrap();
color_attachment.set_texture(Some(drawable.texture()));
color_attachment.set_texture(Some(target_texture));
color_attachment.set_store_action(metal::MTLStoreAction::Store);
configure_color_attachment(color_attachment);
@@ -1224,6 +1249,13 @@ fn new_command_encoder<'a>(
command_encoder
}
#[cfg(feature = "test-support")]
#[path = "metal_renderer_test_support.rs"]
mod test_support;
#[cfg(feature = "test-support")]
pub use test_support::{MetalSurfaceTestSubmission, submit_surface_to_metal_for_test};
fn build_pipeline_state(
device: &metal::DeviceRef,
library: &metal::LibraryRef,
@@ -0,0 +1,162 @@
use std::{
ffi::CStr,
os::raw::c_char,
sync::{Arc, mpsc},
time::Duration,
};
use core_video::pixel_buffer::CVPixelBuffer;
use metal::{MTLCommandBufferStatus, MTLPixelFormat, MTLTextureUsage, TextureDescriptor};
use parking_lot::Mutex;
use objc::{msg_send, runtime::Object, sel, sel_impl};
use super::{
InstanceBufferPool, MetalRenderer, retain_frame_resources_until_completion,
};
use crate::{
Bounds, ContentMask, Corners, DevicePixels, PaintSurface, ScaledPixels, Scene, SurfaceLease,
point, size,
};
const COMPLETION_TIMEOUT: Duration = Duration::from_secs(5);
const COMPLETION_GATE_VALUE: u64 = 1;
/// A gated offscreen Metal submission that uses GPUI's production surface pipeline.
pub struct MetalSurfaceTestSubmission {
gate: metal::SharedEvent,
command_buffer: metal::CommandBuffer,
completion_rx: mpsc::Receiver<()>,
_target_texture: metal::Texture,
finished: bool,
}
impl MetalSurfaceTestSubmission {
/// Releases the GPU gate and waits for the production completion handler.
pub fn finish(mut self) -> crate::Result<()> {
self.gate.set_signaled_value(COMPLETION_GATE_VALUE);
self.command_buffer.wait_until_completed();
self.completion_rx.recv_timeout(COMPLETION_TIMEOUT)?;
anyhow::ensure!(
self.command_buffer.status() == MTLCommandBufferStatus::Completed,
"Metal surface test submission ended with status {:?}: {}",
self.command_buffer.status(),
command_buffer_error(self.command_buffer.as_ref()),
);
self.finished = true;
Ok(())
}
}
impl Drop for MetalSurfaceTestSubmission {
fn drop(&mut self) {
if !self.finished {
self.gate.set_signaled_value(COMPLETION_GATE_VALUE);
}
}
}
/// Submits a CoreVideo surface through GPUI's production BGRA Metal pipeline.
pub fn submit_surface_to_metal_for_test(
image_buffer: CVPixelBuffer,
lease: SurfaceLease,
) -> crate::Result<MetalSurfaceTestSubmission> {
let width = u32::try_from(image_buffer.get_width())?;
let height = u32::try_from(image_buffer.get_height())?;
anyhow::ensure!(width > 0 && height > 0, "surface dimensions must be positive");
let viewport_size = size(
DevicePixels(i32::try_from(width)?),
DevicePixels(i32::try_from(height)?),
);
let instance_buffer_pool = Arc::new(Mutex::new(InstanceBufferPool::default()));
let mut renderer = MetalRenderer::new(instance_buffer_pool.clone());
let target_texture = build_target_texture(&renderer.device, width, height);
let mut scene = build_surface_scene(image_buffer, lease, width, height);
scene.finish();
let mut instance_buffer = instance_buffer_pool.lock().acquire(&renderer.device);
let command_buffer = renderer.draw_primitives(
&scene,
&mut instance_buffer,
target_texture.as_ref(),
viewport_size,
)?;
let gate = renderer.device.new_shared_event();
gate.set_signaled_value(0);
command_buffer.encode_wait_for_event(&gate, COMPLETION_GATE_VALUE);
let (completion_tx, completion_rx) = mpsc::channel();
retain_frame_resources_until_completion(
command_buffer.as_ref(),
&scene,
instance_buffer,
instance_buffer_pool,
Some(completion_tx),
);
command_buffer.commit();
anyhow::ensure!(
!matches!(
command_buffer.status(),
MTLCommandBufferStatus::Completed | MTLCommandBufferStatus::Error
),
"gated Metal surface submission ended early with status {:?}: {error}",
command_buffer.status(),
error = command_buffer_error(command_buffer.as_ref()),
);
Ok(MetalSurfaceTestSubmission {
gate,
command_buffer,
completion_rx,
_target_texture: target_texture,
finished: false,
})
}
fn command_buffer_error(command_buffer: &metal::CommandBufferRef) -> String {
#[expect(unsafe_code)]
unsafe {
let error: *mut Object = msg_send![command_buffer, error];
if error.is_null() {
return "no Metal error description".to_string();
}
let description: *mut Object = msg_send![error, localizedDescription];
if description.is_null() {
return "Metal error description was null".to_string();
}
let utf8: *const c_char = msg_send![description, UTF8String];
if utf8.is_null() {
return "Metal error description had no UTF-8 representation".to_string();
}
CStr::from_ptr(utf8).to_string_lossy().into_owned()
}
}
fn build_target_texture(device: &metal::DeviceRef, width: u32, height: u32) -> metal::Texture {
let descriptor = TextureDescriptor::new();
descriptor.set_width(u64::from(width));
descriptor.set_height(u64::from(height));
descriptor.set_pixel_format(MTLPixelFormat::BGRA8Unorm);
descriptor.set_storage_mode(metal::MTLStorageMode::Private);
descriptor.set_usage(MTLTextureUsage::RenderTarget | MTLTextureUsage::ShaderRead);
device.new_texture(&descriptor)
}
fn build_surface_scene(
image_buffer: CVPixelBuffer,
lease: SurfaceLease,
width: u32,
height: u32,
) -> Scene {
let bounds = Bounds::new(
point(ScaledPixels::default(), ScaledPixels::default()),
size(ScaledPixels::from(width as f32), ScaledPixels::from(height as f32)),
);
let mut scene = Scene::default();
scene.insert_primitive(PaintSurface {
order: 0,
bounds,
content_mask: ContentMask { bounds },
corner_radii: Corners::default(),
image_buffer,
lease: Some(lease),
});
scene
}