ZIm/crates/gpui/src/platform/blade/blade_renderer.rs
Mikayla Maki 8a307e7b89
Switch fully to Rust Livekit (redux) (#27126)
Swift bindings BEGONE

Release Notes:

- Switched from using the Swift LiveKit bindings, to the Rust bindings,
fixing https://github.com/zed-industries/zed/issues/9396, a crash when
leaving a collaboration session, and making Zed easier to build.

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Co-authored-by: Michael Sloan <michael@zed.dev>
2025-03-28 17:58:23 +00:00

813 lines
32 KiB
Rust

// Doing `if let` gives you nice scoping with passes/encoders
#![allow(irrefutable_let_patterns)]
use super::{BladeAtlas, BladeContext, PATH_TEXTURE_FORMAT};
use crate::{
AtlasTextureKind, AtlasTile, Background, Bounds, ContentMask, DevicePixels, GpuSpecs,
MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch, Quad,
ScaledPixels, Scene, Shadow, Size, Underline,
};
use blade_graphics as gpu;
use blade_util::{BufferBelt, BufferBeltDescriptor};
use bytemuck::{Pod, Zeroable};
use collections::HashMap;
#[cfg(target_os = "macos")]
use media::core_video::CVMetalTextureCache;
use std::{mem, sync::Arc};
const MAX_FRAME_TIME_MS: u32 = 10000;
// Use 4x MSAA, all devices support it.
// https://developer.apple.com/documentation/metal/mtldevice/1433355-supportstexturesamplecount
const DEFAULT_PATH_SAMPLE_COUNT: u32 = 4;
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
struct GlobalParams {
viewport_size: [f32; 2],
premultiplied_alpha: u32,
pad: u32,
}
//Note: we can't use `Bounds` directly here because
// it doesn't implement Pod + Zeroable
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
struct PodBounds {
origin: [f32; 2],
size: [f32; 2],
}
impl From<Bounds<ScaledPixels>> for PodBounds {
fn from(bounds: Bounds<ScaledPixels>) -> Self {
Self {
origin: [bounds.origin.x.0, bounds.origin.y.0],
size: [bounds.size.width.0, bounds.size.height.0],
}
}
}
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
struct SurfaceParams {
bounds: PodBounds,
content_mask: PodBounds,
}
#[derive(blade_macros::ShaderData)]
struct ShaderQuadsData {
globals: GlobalParams,
b_quads: gpu::BufferPiece,
}
#[derive(blade_macros::ShaderData)]
struct ShaderShadowsData {
globals: GlobalParams,
b_shadows: gpu::BufferPiece,
}
#[derive(blade_macros::ShaderData)]
struct ShaderPathRasterizationData {
globals: GlobalParams,
b_path_vertices: gpu::BufferPiece,
}
#[derive(blade_macros::ShaderData)]
struct ShaderPathsData {
globals: GlobalParams,
t_sprite: gpu::TextureView,
s_sprite: gpu::Sampler,
b_path_sprites: gpu::BufferPiece,
}
#[derive(blade_macros::ShaderData)]
struct ShaderUnderlinesData {
globals: GlobalParams,
b_underlines: gpu::BufferPiece,
}
#[derive(blade_macros::ShaderData)]
struct ShaderMonoSpritesData {
globals: GlobalParams,
t_sprite: gpu::TextureView,
s_sprite: gpu::Sampler,
b_mono_sprites: gpu::BufferPiece,
}
#[derive(blade_macros::ShaderData)]
struct ShaderPolySpritesData {
globals: GlobalParams,
t_sprite: gpu::TextureView,
s_sprite: gpu::Sampler,
b_poly_sprites: gpu::BufferPiece,
}
#[derive(blade_macros::ShaderData)]
struct ShaderSurfacesData {
globals: GlobalParams,
surface_locals: SurfaceParams,
t_y: gpu::TextureView,
t_cb_cr: gpu::TextureView,
s_surface: gpu::Sampler,
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[repr(C)]
struct PathSprite {
bounds: Bounds<ScaledPixels>,
color: Background,
tile: AtlasTile,
}
struct BladePipelines {
quads: gpu::RenderPipeline,
shadows: gpu::RenderPipeline,
path_rasterization: gpu::RenderPipeline,
paths: gpu::RenderPipeline,
underlines: gpu::RenderPipeline,
mono_sprites: gpu::RenderPipeline,
poly_sprites: gpu::RenderPipeline,
surfaces: gpu::RenderPipeline,
}
impl BladePipelines {
fn new(gpu: &gpu::Context, surface_info: gpu::SurfaceInfo, path_sample_count: u32) -> Self {
use gpu::ShaderData as _;
log::info!(
"Initializing Blade pipelines for surface {:?}",
surface_info
);
let shader = gpu.create_shader(gpu::ShaderDesc {
source: include_str!("shaders.wgsl"),
});
shader.check_struct_size::<GlobalParams>();
shader.check_struct_size::<SurfaceParams>();
shader.check_struct_size::<Quad>();
shader.check_struct_size::<Shadow>();
assert_eq!(
mem::size_of::<PathVertex<ScaledPixels>>(),
shader.get_struct_size("PathVertex") as usize,
);
shader.check_struct_size::<PathSprite>();
shader.check_struct_size::<Underline>();
shader.check_struct_size::<MonochromeSprite>();
shader.check_struct_size::<PolychromeSprite>();
// See https://apoorvaj.io/alpha-compositing-opengl-blending-and-premultiplied-alpha/
let blend_mode = match surface_info.alpha {
gpu::AlphaMode::Ignored => gpu::BlendState::ALPHA_BLENDING,
gpu::AlphaMode::PreMultiplied => gpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING,
gpu::AlphaMode::PostMultiplied => gpu::BlendState::ALPHA_BLENDING,
};
let color_targets = &[gpu::ColorTargetState {
format: surface_info.format,
blend: Some(blend_mode),
write_mask: gpu::ColorWrites::default(),
}];
Self {
quads: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
name: "quads",
data_layouts: &[&ShaderQuadsData::layout()],
vertex: shader.at("vs_quad"),
vertex_fetches: &[],
primitive: gpu::PrimitiveState {
topology: gpu::PrimitiveTopology::TriangleStrip,
..Default::default()
},
depth_stencil: None,
fragment: Some(shader.at("fs_quad")),
color_targets,
multisample_state: gpu::MultisampleState::default(),
}),
shadows: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
name: "shadows",
data_layouts: &[&ShaderShadowsData::layout()],
vertex: shader.at("vs_shadow"),
vertex_fetches: &[],
primitive: gpu::PrimitiveState {
topology: gpu::PrimitiveTopology::TriangleStrip,
..Default::default()
},
depth_stencil: None,
fragment: Some(shader.at("fs_shadow")),
color_targets,
multisample_state: gpu::MultisampleState::default(),
}),
path_rasterization: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
name: "path_rasterization",
data_layouts: &[&ShaderPathRasterizationData::layout()],
vertex: shader.at("vs_path_rasterization"),
vertex_fetches: &[],
primitive: gpu::PrimitiveState {
topology: gpu::PrimitiveTopology::TriangleList,
..Default::default()
},
depth_stencil: None,
fragment: Some(shader.at("fs_path_rasterization")),
color_targets: &[gpu::ColorTargetState {
format: PATH_TEXTURE_FORMAT,
blend: Some(gpu::BlendState::ADDITIVE),
write_mask: gpu::ColorWrites::default(),
}],
multisample_state: gpu::MultisampleState {
sample_count: path_sample_count,
..Default::default()
},
}),
paths: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
name: "paths",
data_layouts: &[&ShaderPathsData::layout()],
vertex: shader.at("vs_path"),
vertex_fetches: &[],
primitive: gpu::PrimitiveState {
topology: gpu::PrimitiveTopology::TriangleStrip,
..Default::default()
},
depth_stencil: None,
fragment: Some(shader.at("fs_path")),
color_targets,
multisample_state: gpu::MultisampleState::default(),
}),
underlines: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
name: "underlines",
data_layouts: &[&ShaderUnderlinesData::layout()],
vertex: shader.at("vs_underline"),
vertex_fetches: &[],
primitive: gpu::PrimitiveState {
topology: gpu::PrimitiveTopology::TriangleStrip,
..Default::default()
},
depth_stencil: None,
fragment: Some(shader.at("fs_underline")),
color_targets,
multisample_state: gpu::MultisampleState::default(),
}),
mono_sprites: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
name: "mono-sprites",
data_layouts: &[&ShaderMonoSpritesData::layout()],
vertex: shader.at("vs_mono_sprite"),
vertex_fetches: &[],
primitive: gpu::PrimitiveState {
topology: gpu::PrimitiveTopology::TriangleStrip,
..Default::default()
},
depth_stencil: None,
fragment: Some(shader.at("fs_mono_sprite")),
color_targets,
multisample_state: gpu::MultisampleState::default(),
}),
poly_sprites: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
name: "poly-sprites",
data_layouts: &[&ShaderPolySpritesData::layout()],
vertex: shader.at("vs_poly_sprite"),
vertex_fetches: &[],
primitive: gpu::PrimitiveState {
topology: gpu::PrimitiveTopology::TriangleStrip,
..Default::default()
},
depth_stencil: None,
fragment: Some(shader.at("fs_poly_sprite")),
color_targets,
multisample_state: gpu::MultisampleState::default(),
}),
surfaces: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
name: "surfaces",
data_layouts: &[&ShaderSurfacesData::layout()],
vertex: shader.at("vs_surface"),
vertex_fetches: &[],
primitive: gpu::PrimitiveState {
topology: gpu::PrimitiveTopology::TriangleStrip,
..Default::default()
},
depth_stencil: None,
fragment: Some(shader.at("fs_surface")),
color_targets,
multisample_state: gpu::MultisampleState::default(),
}),
}
}
fn destroy(&mut self, gpu: &gpu::Context) {
gpu.destroy_render_pipeline(&mut self.quads);
gpu.destroy_render_pipeline(&mut self.shadows);
gpu.destroy_render_pipeline(&mut self.path_rasterization);
gpu.destroy_render_pipeline(&mut self.paths);
gpu.destroy_render_pipeline(&mut self.underlines);
gpu.destroy_render_pipeline(&mut self.mono_sprites);
gpu.destroy_render_pipeline(&mut self.poly_sprites);
gpu.destroy_render_pipeline(&mut self.surfaces);
}
}
pub struct BladeSurfaceConfig {
pub size: gpu::Extent,
pub transparent: bool,
}
//Note: we could see some of these fields moved into `BladeContext`
// so that they are shared between windows. E.g. `pipelines`.
// But that is complicated by the fact that pipelines depend on
// the format and alpha mode.
pub struct BladeRenderer {
gpu: Arc<gpu::Context>,
surface: gpu::Surface,
surface_config: gpu::SurfaceConfig,
command_encoder: gpu::CommandEncoder,
last_sync_point: Option<gpu::SyncPoint>,
pipelines: BladePipelines,
instance_belt: BufferBelt,
path_tiles: HashMap<PathId, AtlasTile>,
atlas: Arc<BladeAtlas>,
atlas_sampler: gpu::Sampler,
#[cfg(target_os = "macos")]
core_video_texture_cache: CVMetalTextureCache,
path_sample_count: u32,
}
impl BladeRenderer {
pub fn new<I: raw_window_handle::HasWindowHandle + raw_window_handle::HasDisplayHandle>(
context: &BladeContext,
window: &I,
config: BladeSurfaceConfig,
) -> anyhow::Result<Self> {
let surface_config = gpu::SurfaceConfig {
size: config.size,
usage: gpu::TextureUsage::TARGET,
display_sync: gpu::DisplaySync::Recent,
color_space: gpu::ColorSpace::Linear,
allow_exclusive_full_screen: false,
transparent: config.transparent,
};
let surface = context
.gpu
.create_surface_configured(window, surface_config)
.unwrap();
let command_encoder = context.gpu.create_command_encoder(gpu::CommandEncoderDesc {
name: "main",
buffer_count: 2,
});
// workaround for https://github.com/zed-industries/zed/issues/26143
let path_sample_count = std::env::var("ZED_PATH_SAMPLE_COUNT")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(DEFAULT_PATH_SAMPLE_COUNT);
let pipelines = BladePipelines::new(&context.gpu, surface.info(), path_sample_count);
let instance_belt = BufferBelt::new(BufferBeltDescriptor {
memory: gpu::Memory::Shared,
min_chunk_size: 0x1000,
alignment: 0x40, // Vulkan `minStorageBufferOffsetAlignment` on Intel Xe
});
let atlas = Arc::new(BladeAtlas::new(&context.gpu, path_sample_count));
let atlas_sampler = context.gpu.create_sampler(gpu::SamplerDesc {
name: "atlas",
mag_filter: gpu::FilterMode::Linear,
min_filter: gpu::FilterMode::Linear,
..Default::default()
});
#[cfg(target_os = "macos")]
let core_video_texture_cache = unsafe {
CVMetalTextureCache::new(
objc2::rc::Retained::as_ptr(&context.gpu.metal_device()) as *mut _
)
.unwrap()
};
Ok(Self {
gpu: Arc::clone(&context.gpu),
surface,
surface_config,
command_encoder,
last_sync_point: None,
pipelines,
instance_belt,
path_tiles: HashMap::default(),
atlas,
atlas_sampler,
#[cfg(target_os = "macos")]
core_video_texture_cache,
path_sample_count,
})
}
fn wait_for_gpu(&mut self) {
if let Some(last_sp) = self.last_sync_point.take() {
if !self.gpu.wait_for(&last_sp, MAX_FRAME_TIME_MS) {
log::error!("GPU hung");
#[cfg(target_os = "linux")]
if self.gpu.device_information().driver_name == "radv" {
log::error!("there's a known bug with amdgpu/radv, try setting ZED_PATH_SAMPLE_COUNT=0 as a workaround");
log::error!("if that helps you're running into https://github.com/zed-industries/zed/issues/26143");
}
log::error!(
"your device information is: {:?}",
self.gpu.device_information()
);
while !self.gpu.wait_for(&last_sp, MAX_FRAME_TIME_MS) {}
}
}
}
pub fn update_drawable_size(&mut self, size: Size<DevicePixels>) {
self.update_drawable_size_impl(size, false);
}
/// Like `update_drawable_size` but skips the check that the size has changed. This is useful in
/// cases like restoring a window from minimization where the size is the same but the
/// renderer's swap chain needs to be recreated.
#[cfg_attr(any(target_os = "macos", target_os = "linux"), allow(dead_code))]
pub fn update_drawable_size_even_if_unchanged(&mut self, size: Size<DevicePixels>) {
self.update_drawable_size_impl(size, true);
}
fn update_drawable_size_impl(&mut self, size: Size<DevicePixels>, always_resize: bool) {
let gpu_size = gpu::Extent {
width: size.width.0 as u32,
height: size.height.0 as u32,
depth: 1,
};
if always_resize || gpu_size != self.surface_config.size {
self.wait_for_gpu();
self.surface_config.size = gpu_size;
self.gpu
.reconfigure_surface(&mut self.surface, self.surface_config);
}
}
pub fn update_transparency(&mut self, transparent: bool) {
if transparent != self.surface_config.transparent {
self.wait_for_gpu();
self.surface_config.transparent = transparent;
self.gpu
.reconfigure_surface(&mut self.surface, self.surface_config);
self.pipelines.destroy(&self.gpu);
self.pipelines =
BladePipelines::new(&self.gpu, self.surface.info(), self.path_sample_count);
}
}
#[cfg_attr(any(target_os = "macos", feature = "wayland"), allow(dead_code))]
pub fn viewport_size(&self) -> gpu::Extent {
self.surface_config.size
}
pub fn sprite_atlas(&self) -> &Arc<BladeAtlas> {
&self.atlas
}
#[cfg_attr(target_os = "macos", allow(dead_code))]
pub fn gpu_specs(&self) -> GpuSpecs {
let info = self.gpu.device_information();
GpuSpecs {
is_software_emulated: info.is_software_emulated,
device_name: info.device_name.clone(),
driver_name: info.driver_name.clone(),
driver_info: info.driver_info.clone(),
}
}
#[cfg(target_os = "macos")]
pub fn layer(&self) -> metal::MetalLayer {
unsafe { foreign_types::ForeignType::from_ptr(self.layer_ptr()) }
}
#[cfg(target_os = "macos")]
pub fn layer_ptr(&self) -> *mut metal::CAMetalLayer {
objc2::rc::Retained::as_ptr(&self.surface.metal_layer()) as *mut _
}
#[profiling::function]
fn rasterize_paths(&mut self, paths: &[Path<ScaledPixels>]) {
self.path_tiles.clear();
let mut vertices_by_texture_id = HashMap::default();
for path in paths {
let clipped_bounds = path
.bounds
.intersect(&path.content_mask.bounds)
.map_origin(|origin| origin.floor())
.map_size(|size| size.ceil());
let tile = self.atlas.allocate_for_rendering(
clipped_bounds.size.map(Into::into),
AtlasTextureKind::Path,
&mut self.command_encoder,
);
vertices_by_texture_id
.entry(tile.texture_id)
.or_insert(Vec::new())
.extend(path.vertices.iter().map(|vertex| PathVertex {
xy_position: vertex.xy_position - clipped_bounds.origin
+ tile.bounds.origin.map(Into::into),
st_position: vertex.st_position,
content_mask: ContentMask {
bounds: tile.bounds.map(Into::into),
},
}));
self.path_tiles.insert(path.id, tile);
}
for (texture_id, vertices) in vertices_by_texture_id {
let tex_info = self.atlas.get_texture_info(texture_id);
let globals = GlobalParams {
viewport_size: [tex_info.size.width as f32, tex_info.size.height as f32],
premultiplied_alpha: 0,
pad: 0,
};
let vertex_buf = unsafe { self.instance_belt.alloc_typed(&vertices, &self.gpu) };
let frame_view = tex_info.raw_view;
let color_target = if let Some(msaa_view) = tex_info.msaa_view {
gpu::RenderTarget {
view: msaa_view,
init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
finish_op: gpu::FinishOp::ResolveTo(frame_view),
}
} else {
gpu::RenderTarget {
view: frame_view,
init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
finish_op: gpu::FinishOp::Store,
}
};
if let mut pass = self.command_encoder.render(
"paths",
gpu::RenderTargetSet {
colors: &[color_target],
depth_stencil: None,
},
) {
let mut encoder = pass.with(&self.pipelines.path_rasterization);
encoder.bind(
0,
&ShaderPathRasterizationData {
globals,
b_path_vertices: vertex_buf,
},
);
encoder.draw(0, vertices.len() as u32, 0, 1);
}
}
}
pub fn destroy(&mut self) {
self.wait_for_gpu();
self.atlas.destroy();
self.gpu.destroy_sampler(self.atlas_sampler);
self.instance_belt.destroy(&self.gpu);
self.gpu.destroy_command_encoder(&mut self.command_encoder);
self.pipelines.destroy(&self.gpu);
self.gpu.destroy_surface(&mut self.surface);
}
pub fn draw(&mut self, scene: &Scene) {
self.command_encoder.start();
self.atlas.before_frame(&mut self.command_encoder);
self.rasterize_paths(scene.paths());
let frame = {
profiling::scope!("acquire frame");
self.surface.acquire_frame()
};
self.command_encoder.init_texture(frame.texture());
let globals = GlobalParams {
viewport_size: [
self.surface_config.size.width as f32,
self.surface_config.size.height as f32,
],
premultiplied_alpha: match self.surface.info().alpha {
gpu::AlphaMode::Ignored | gpu::AlphaMode::PostMultiplied => 0,
gpu::AlphaMode::PreMultiplied => 1,
},
pad: 0,
};
if let mut pass = self.command_encoder.render(
"main",
gpu::RenderTargetSet {
colors: &[gpu::RenderTarget {
view: frame.texture_view(),
init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
finish_op: gpu::FinishOp::Store,
}],
depth_stencil: None,
},
) {
profiling::scope!("render pass");
for batch in scene.batches() {
match batch {
PrimitiveBatch::Quads(quads) => {
let instance_buf =
unsafe { self.instance_belt.alloc_typed(quads, &self.gpu) };
let mut encoder = pass.with(&self.pipelines.quads);
encoder.bind(
0,
&ShaderQuadsData {
globals,
b_quads: instance_buf,
},
);
encoder.draw(0, 4, 0, quads.len() as u32);
}
PrimitiveBatch::Shadows(shadows) => {
let instance_buf =
unsafe { self.instance_belt.alloc_typed(shadows, &self.gpu) };
let mut encoder = pass.with(&self.pipelines.shadows);
encoder.bind(
0,
&ShaderShadowsData {
globals,
b_shadows: instance_buf,
},
);
encoder.draw(0, 4, 0, shadows.len() as u32);
}
PrimitiveBatch::Paths(paths) => {
let mut encoder = pass.with(&self.pipelines.paths);
// todo(linux): group by texture ID
for path in paths {
let tile = &self.path_tiles[&path.id];
let tex_info = self.atlas.get_texture_info(tile.texture_id);
let origin = path.bounds.intersect(&path.content_mask.bounds).origin;
let sprites = [PathSprite {
bounds: Bounds {
origin: origin.map(|p| p.floor()),
size: tile.bounds.size.map(Into::into),
},
color: path.color,
tile: (*tile).clone(),
}];
let instance_buf =
unsafe { self.instance_belt.alloc_typed(&sprites, &self.gpu) };
encoder.bind(
0,
&ShaderPathsData {
globals,
t_sprite: tex_info.raw_view,
s_sprite: self.atlas_sampler,
b_path_sprites: instance_buf,
},
);
encoder.draw(0, 4, 0, sprites.len() as u32);
}
}
PrimitiveBatch::Underlines(underlines) => {
let instance_buf =
unsafe { self.instance_belt.alloc_typed(underlines, &self.gpu) };
let mut encoder = pass.with(&self.pipelines.underlines);
encoder.bind(
0,
&ShaderUnderlinesData {
globals,
b_underlines: instance_buf,
},
);
encoder.draw(0, 4, 0, underlines.len() as u32);
}
PrimitiveBatch::MonochromeSprites {
texture_id,
sprites,
} => {
let tex_info = self.atlas.get_texture_info(texture_id);
let instance_buf =
unsafe { self.instance_belt.alloc_typed(sprites, &self.gpu) };
let mut encoder = pass.with(&self.pipelines.mono_sprites);
encoder.bind(
0,
&ShaderMonoSpritesData {
globals,
t_sprite: tex_info.raw_view,
s_sprite: self.atlas_sampler,
b_mono_sprites: instance_buf,
},
);
encoder.draw(0, 4, 0, sprites.len() as u32);
}
PrimitiveBatch::PolychromeSprites {
texture_id,
sprites,
} => {
let tex_info = self.atlas.get_texture_info(texture_id);
let instance_buf =
unsafe { self.instance_belt.alloc_typed(sprites, &self.gpu) };
let mut encoder = pass.with(&self.pipelines.poly_sprites);
encoder.bind(
0,
&ShaderPolySpritesData {
globals,
t_sprite: tex_info.raw_view,
s_sprite: self.atlas_sampler,
b_poly_sprites: instance_buf,
},
);
encoder.draw(0, 4, 0, sprites.len() as u32);
}
PrimitiveBatch::Surfaces(surfaces) => {
let mut _encoder = pass.with(&self.pipelines.surfaces);
for surface in surfaces {
#[cfg(not(target_os = "macos"))]
{
let _ = surface;
continue;
};
#[cfg(target_os = "macos")]
{
let (t_y, t_cb_cr) = unsafe {
use core_foundation::base::TCFType as _;
use std::ptr;
assert_eq!(
surface.image_buffer.get_pixel_format(),
core_video::pixel_buffer::kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
);
let y_texture = self
.core_video_texture_cache
.create_texture_from_image(
surface.image_buffer.as_concrete_TypeRef(),
ptr::null(),
metal::MTLPixelFormat::R8Unorm,
surface.image_buffer.get_width_of_plane(0),
surface.image_buffer.get_height_of_plane(0),
0,
)
.unwrap();
let cb_cr_texture = self
.core_video_texture_cache
.create_texture_from_image(
surface.image_buffer.as_concrete_TypeRef(),
ptr::null(),
metal::MTLPixelFormat::RG8Unorm,
surface.image_buffer.get_width_of_plane(1),
surface.image_buffer.get_height_of_plane(1),
1,
)
.unwrap();
(
gpu::TextureView::from_metal_texture(
&objc2::rc::Retained::retain(
foreign_types::ForeignTypeRef::as_ptr(
y_texture.as_texture_ref(),
)
as *mut objc2::runtime::ProtocolObject<
dyn objc2_metal::MTLTexture,
>,
)
.unwrap(),
),
gpu::TextureView::from_metal_texture(
&objc2::rc::Retained::retain(
foreign_types::ForeignTypeRef::as_ptr(
cb_cr_texture.as_texture_ref(),
)
as *mut objc2::runtime::ProtocolObject<
dyn objc2_metal::MTLTexture,
>,
)
.unwrap(),
),
)
};
_encoder.bind(
0,
&ShaderSurfacesData {
globals,
surface_locals: SurfaceParams {
bounds: surface.bounds.into(),
content_mask: surface.content_mask.bounds.into(),
},
t_y,
t_cb_cr,
s_surface: self.atlas_sampler,
},
);
_encoder.draw(0, 4, 0, 1);
}
}
}
}
}
}
self.command_encoder.present(frame);
let sync_point = self.gpu.submit(&mut self.command_encoder);
profiling::scope!("finish");
self.instance_belt.flush(&sync_point);
self.atlas.after_frame(&sync_point);
self.atlas.clear_textures(AtlasTextureKind::Path);
self.wait_for_gpu();
self.last_sync_point = Some(sync_point);
}
}