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
+7 -7
View File
@@ -201,18 +201,18 @@ mod macos {
PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.metallib");
println!("cargo:rerun-if-changed={}", shader_path);
let output = Command::new("xcrun")
let mut command = Command::new("xcrun");
command.args(["-sdk", "macosx", "metal"]);
if env::var("DEBUG").as_deref() == Ok("true") {
command.args(["-gline-tables-only", "-MO"]);
}
let output = command
.args([
"-sdk",
"macosx",
"metal",
"-gline-tables-only",
"-mmacosx-version-min=10.15.7",
"-MO",
"-c",
shader_path,
"-include",
(header_path.to_str().unwrap()),
header_path.to_str().unwrap(),
"-o",
])
.arg(&air_output_path)
+72 -1
View File
@@ -5,6 +5,33 @@ use crate::{
#[cfg(target_os = "macos")]
use core_video::pixel_buffer::CVPixelBuffer;
use refineable::Refineable;
use std::{fmt, sync::Arc};
/// An owner retained until the GPU finishes reading a surface frame.
///
/// Attach a lease with [`Surface::lease`] when the surface's producer may
/// recycle its backing storage independently of the CoreVideo pixel buffer.
#[derive(Clone)]
pub struct SurfaceLease(Arc<dyn Send + Sync>);
impl SurfaceLease {
/// Create a lease from a thread-safe shared owner.
pub fn from_arc<T>(owner: Arc<T>) -> Self
where
T: Send + Sync + 'static,
{
Self(owner)
}
}
impl fmt::Debug for SurfaceLease {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("SurfaceLease")
.field("strong_count", &Arc::strong_count(&self.0))
.finish()
}
}
/// A source of a surface's content.
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -24,6 +51,7 @@ impl From<CVPixelBuffer> for SurfaceSource {
/// A surface element.
pub struct Surface {
source: SurfaceSource,
lease: Option<SurfaceLease>,
object_fit: ObjectFit,
corner_radii: Option<Corners<Pixels>>,
style: StyleRefinement,
@@ -33,6 +61,7 @@ pub struct Surface {
pub fn surface(source: impl Into<SurfaceSource>) -> Surface {
Surface {
source: source.into(),
lease: None,
object_fit: ObjectFit::Contain,
corner_radii: None,
style: Default::default(),
@@ -40,6 +69,12 @@ pub fn surface(source: impl Into<SurfaceSource>) -> Surface {
}
impl Surface {
/// Retain an owner until the GPU completes this surface frame.
pub fn lease(mut self, lease: SurfaceLease) -> Self {
self.lease = Some(lease);
self
}
/// Set the object fit for the image.
pub fn object_fit(mut self, object_fit: ObjectFit) -> Self {
self.object_fit = object_fit;
@@ -110,7 +145,12 @@ impl Element for Surface {
.corner_radii
.unwrap_or_else(|| style.corner_radii.to_pixels(window.rem_size()))
.clamp_radii_for_quad_size(new_bounds.size);
window.paint_surface(new_bounds, corner_radii, surface.clone());
window.paint_surface_with_lease(
new_bounds,
corner_radii,
surface.clone(),
self.lease.clone(),
);
}
#[allow(unreachable_patterns)]
_ => {}
@@ -131,3 +171,34 @@ impl Styled for Surface {
&mut self.style
}
}
#[cfg(test)]
mod tests {
use std::sync::{
Arc,
atomic::{AtomicUsize, Ordering},
};
use super::SurfaceLease;
struct DropProbe(Arc<AtomicUsize>);
impl Drop for DropProbe {
fn drop(&mut self) {
self.0.fetch_add(1, Ordering::SeqCst);
}
}
#[test]
fn surface_lease_retains_its_owner() {
let drops = Arc::new(AtomicUsize::new(0));
let owner = Arc::new(DropProbe(drops.clone()));
let lease = SurfaceLease::from_arc(owner.clone());
drop(owner);
assert_eq!(drops.load(Ordering::SeqCst), 0);
drop(lease);
assert_eq!(drops.load(Ordering::SeqCst), 1);
}
}
+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
}
+2
View File
@@ -661,6 +661,8 @@ pub(crate) struct PaintSurface {
pub corner_radii: Corners<ScaledPixels>,
#[cfg(target_os = "macos")]
pub image_buffer: core_video::pixel_buffer::CVPixelBuffer,
#[cfg(target_os = "macos")]
pub lease: Option<crate::SurfaceLease>,
}
impl From<PaintSurface> for Primitive {
+5
View File
@@ -35,6 +35,11 @@ use std::{
pin::Pin,
};
#[cfg(all(target_os = "macos", feature = "test-support", not(feature = "macos-blade")))]
pub use crate::metal_renderer::{
MetalSurfaceTestSubmission, submit_surface_to_metal_for_test,
};
/// Run the given test function with the configured parameters.
/// This is intended for use with the `gpui::test` macro
/// and generally should not be used directly.
+12
View File
@@ -3184,6 +3184,17 @@ impl Window {
bounds: Bounds<Pixels>,
corner_radii: Corners<Pixels>,
image_buffer: CVPixelBuffer,
) {
self.paint_surface_with_lease(bounds, corner_radii, image_buffer, None);
}
#[cfg(target_os = "macos")]
pub(crate) fn paint_surface_with_lease(
&mut self,
bounds: Bounds<Pixels>,
corner_radii: Corners<Pixels>,
image_buffer: CVPixelBuffer,
lease: Option<crate::SurfaceLease>,
) {
use crate::PaintSurface;
@@ -3199,6 +3210,7 @@ impl Window {
content_mask,
corner_radii,
image_buffer,
lease,
});
}