feat(servo): isolate profiles with hardware sidecars
This commit is contained in:
+47
-15
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user