1// Doing `if let` gives you nice scoping with passes/encoders
2#![allow(irrefutable_let_patterns)]
3
4use super::{BladeAtlas, BladeContext, PATH_TEXTURE_FORMAT};
5use crate::{
6 AtlasTextureKind, AtlasTile, Background, Bounds, ContentMask, DevicePixels, GpuSpecs,
7 MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch, Quad,
8 ScaledPixels, Scene, Shadow, Size, Underline,
9};
10use blade_graphics as gpu;
11use blade_util::{BufferBelt, BufferBeltDescriptor};
12use bytemuck::{Pod, Zeroable};
13use collections::HashMap;
14#[cfg(target_os = "macos")]
15use media::core_video::CVMetalTextureCache;
16use std::{mem, sync::Arc};
17
18const MAX_FRAME_TIME_MS: u32 = 10000;
19// Use 4x MSAA, all devices support it.
20// https://developer.apple.com/documentation/metal/mtldevice/1433355-supportstexturesamplecount
21const DEFAULT_PATH_SAMPLE_COUNT: u32 = 4;
22
23#[repr(C)]
24#[derive(Clone, Copy, Pod, Zeroable)]
25struct GlobalParams {
26 viewport_size: [f32; 2],
27 premultiplied_alpha: u32,
28 pad: u32,
29}
30
31//Note: we can't use `Bounds` directly here because
32// it doesn't implement Pod + Zeroable
33#[repr(C)]
34#[derive(Clone, Copy, Pod, Zeroable)]
35struct PodBounds {
36 origin: [f32; 2],
37 size: [f32; 2],
38}
39
40impl From<Bounds<ScaledPixels>> for PodBounds {
41 fn from(bounds: Bounds<ScaledPixels>) -> Self {
42 Self {
43 origin: [bounds.origin.x.0, bounds.origin.y.0],
44 size: [bounds.size.width.0, bounds.size.height.0],
45 }
46 }
47}
48
49#[repr(C)]
50#[derive(Clone, Copy, Pod, Zeroable)]
51struct SurfaceParams {
52 bounds: PodBounds,
53 content_mask: PodBounds,
54}
55
56#[derive(blade_macros::ShaderData)]
57struct ShaderQuadsData {
58 globals: GlobalParams,
59 b_quads: gpu::BufferPiece,
60}
61
62#[derive(blade_macros::ShaderData)]
63struct ShaderShadowsData {
64 globals: GlobalParams,
65 b_shadows: gpu::BufferPiece,
66}
67
68#[derive(blade_macros::ShaderData)]
69struct ShaderPathRasterizationData {
70 globals: GlobalParams,
71 b_path_vertices: gpu::BufferPiece,
72}
73
74#[derive(blade_macros::ShaderData)]
75struct ShaderPathsData {
76 globals: GlobalParams,
77 t_sprite: gpu::TextureView,
78 s_sprite: gpu::Sampler,
79 b_path_sprites: gpu::BufferPiece,
80}
81
82#[derive(blade_macros::ShaderData)]
83struct ShaderUnderlinesData {
84 globals: GlobalParams,
85 b_underlines: gpu::BufferPiece,
86}
87
88#[derive(blade_macros::ShaderData)]
89struct ShaderMonoSpritesData {
90 globals: GlobalParams,
91 t_sprite: gpu::TextureView,
92 s_sprite: gpu::Sampler,
93 b_mono_sprites: gpu::BufferPiece,
94}
95
96#[derive(blade_macros::ShaderData)]
97struct ShaderPolySpritesData {
98 globals: GlobalParams,
99 t_sprite: gpu::TextureView,
100 s_sprite: gpu::Sampler,
101 b_poly_sprites: gpu::BufferPiece,
102}
103
104#[derive(blade_macros::ShaderData)]
105struct ShaderSurfacesData {
106 globals: GlobalParams,
107 surface_locals: SurfaceParams,
108 t_y: gpu::TextureView,
109 t_cb_cr: gpu::TextureView,
110 s_surface: gpu::Sampler,
111}
112
113#[derive(Clone, Debug, Eq, PartialEq)]
114#[repr(C)]
115struct PathSprite {
116 bounds: Bounds<ScaledPixels>,
117 color: Background,
118 tile: AtlasTile,
119}
120
121struct BladePipelines {
122 quads: gpu::RenderPipeline,
123 shadows: gpu::RenderPipeline,
124 path_rasterization: gpu::RenderPipeline,
125 paths: gpu::RenderPipeline,
126 underlines: gpu::RenderPipeline,
127 mono_sprites: gpu::RenderPipeline,
128 poly_sprites: gpu::RenderPipeline,
129 surfaces: gpu::RenderPipeline,
130}
131
132impl BladePipelines {
133 fn new(gpu: &gpu::Context, surface_info: gpu::SurfaceInfo, path_sample_count: u32) -> Self {
134 use gpu::ShaderData as _;
135
136 log::info!(
137 "Initializing Blade pipelines for surface {:?}",
138 surface_info
139 );
140 let shader = gpu.create_shader(gpu::ShaderDesc {
141 source: include_str!("shaders.wgsl"),
142 });
143 shader.check_struct_size::<GlobalParams>();
144 shader.check_struct_size::<SurfaceParams>();
145 shader.check_struct_size::<Quad>();
146 shader.check_struct_size::<Shadow>();
147 assert_eq!(
148 mem::size_of::<PathVertex<ScaledPixels>>(),
149 shader.get_struct_size("PathVertex") as usize,
150 );
151 shader.check_struct_size::<PathSprite>();
152 shader.check_struct_size::<Underline>();
153 shader.check_struct_size::<MonochromeSprite>();
154 shader.check_struct_size::<PolychromeSprite>();
155
156 // See https://apoorvaj.io/alpha-compositing-opengl-blending-and-premultiplied-alpha/
157 let blend_mode = match surface_info.alpha {
158 gpu::AlphaMode::Ignored => gpu::BlendState::ALPHA_BLENDING,
159 gpu::AlphaMode::PreMultiplied => gpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING,
160 gpu::AlphaMode::PostMultiplied => gpu::BlendState::ALPHA_BLENDING,
161 };
162 let color_targets = &[gpu::ColorTargetState {
163 format: surface_info.format,
164 blend: Some(blend_mode),
165 write_mask: gpu::ColorWrites::default(),
166 }];
167
168 Self {
169 quads: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
170 name: "quads",
171 data_layouts: &[&ShaderQuadsData::layout()],
172 vertex: shader.at("vs_quad"),
173 vertex_fetches: &[],
174 primitive: gpu::PrimitiveState {
175 topology: gpu::PrimitiveTopology::TriangleStrip,
176 ..Default::default()
177 },
178 depth_stencil: None,
179 fragment: Some(shader.at("fs_quad")),
180 color_targets,
181 multisample_state: gpu::MultisampleState::default(),
182 }),
183 shadows: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
184 name: "shadows",
185 data_layouts: &[&ShaderShadowsData::layout()],
186 vertex: shader.at("vs_shadow"),
187 vertex_fetches: &[],
188 primitive: gpu::PrimitiveState {
189 topology: gpu::PrimitiveTopology::TriangleStrip,
190 ..Default::default()
191 },
192 depth_stencil: None,
193 fragment: Some(shader.at("fs_shadow")),
194 color_targets,
195 multisample_state: gpu::MultisampleState::default(),
196 }),
197 path_rasterization: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
198 name: "path_rasterization",
199 data_layouts: &[&ShaderPathRasterizationData::layout()],
200 vertex: shader.at("vs_path_rasterization"),
201 vertex_fetches: &[],
202 primitive: gpu::PrimitiveState {
203 topology: gpu::PrimitiveTopology::TriangleList,
204 ..Default::default()
205 },
206 depth_stencil: None,
207 fragment: Some(shader.at("fs_path_rasterization")),
208 color_targets: &[gpu::ColorTargetState {
209 format: PATH_TEXTURE_FORMAT,
210 blend: Some(gpu::BlendState::ADDITIVE),
211 write_mask: gpu::ColorWrites::default(),
212 }],
213 multisample_state: gpu::MultisampleState {
214 sample_count: path_sample_count,
215 ..Default::default()
216 },
217 }),
218 paths: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
219 name: "paths",
220 data_layouts: &[&ShaderPathsData::layout()],
221 vertex: shader.at("vs_path"),
222 vertex_fetches: &[],
223 primitive: gpu::PrimitiveState {
224 topology: gpu::PrimitiveTopology::TriangleStrip,
225 ..Default::default()
226 },
227 depth_stencil: None,
228 fragment: Some(shader.at("fs_path")),
229 color_targets,
230 multisample_state: gpu::MultisampleState::default(),
231 }),
232 underlines: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
233 name: "underlines",
234 data_layouts: &[&ShaderUnderlinesData::layout()],
235 vertex: shader.at("vs_underline"),
236 vertex_fetches: &[],
237 primitive: gpu::PrimitiveState {
238 topology: gpu::PrimitiveTopology::TriangleStrip,
239 ..Default::default()
240 },
241 depth_stencil: None,
242 fragment: Some(shader.at("fs_underline")),
243 color_targets,
244 multisample_state: gpu::MultisampleState::default(),
245 }),
246 mono_sprites: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
247 name: "mono-sprites",
248 data_layouts: &[&ShaderMonoSpritesData::layout()],
249 vertex: shader.at("vs_mono_sprite"),
250 vertex_fetches: &[],
251 primitive: gpu::PrimitiveState {
252 topology: gpu::PrimitiveTopology::TriangleStrip,
253 ..Default::default()
254 },
255 depth_stencil: None,
256 fragment: Some(shader.at("fs_mono_sprite")),
257 color_targets,
258 multisample_state: gpu::MultisampleState::default(),
259 }),
260 poly_sprites: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
261 name: "poly-sprites",
262 data_layouts: &[&ShaderPolySpritesData::layout()],
263 vertex: shader.at("vs_poly_sprite"),
264 vertex_fetches: &[],
265 primitive: gpu::PrimitiveState {
266 topology: gpu::PrimitiveTopology::TriangleStrip,
267 ..Default::default()
268 },
269 depth_stencil: None,
270 fragment: Some(shader.at("fs_poly_sprite")),
271 color_targets,
272 multisample_state: gpu::MultisampleState::default(),
273 }),
274 surfaces: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
275 name: "surfaces",
276 data_layouts: &[&ShaderSurfacesData::layout()],
277 vertex: shader.at("vs_surface"),
278 vertex_fetches: &[],
279 primitive: gpu::PrimitiveState {
280 topology: gpu::PrimitiveTopology::TriangleStrip,
281 ..Default::default()
282 },
283 depth_stencil: None,
284 fragment: Some(shader.at("fs_surface")),
285 color_targets,
286 multisample_state: gpu::MultisampleState::default(),
287 }),
288 }
289 }
290
291 fn destroy(&mut self, gpu: &gpu::Context) {
292 gpu.destroy_render_pipeline(&mut self.quads);
293 gpu.destroy_render_pipeline(&mut self.shadows);
294 gpu.destroy_render_pipeline(&mut self.path_rasterization);
295 gpu.destroy_render_pipeline(&mut self.paths);
296 gpu.destroy_render_pipeline(&mut self.underlines);
297 gpu.destroy_render_pipeline(&mut self.mono_sprites);
298 gpu.destroy_render_pipeline(&mut self.poly_sprites);
299 gpu.destroy_render_pipeline(&mut self.surfaces);
300 }
301}
302
303pub struct BladeSurfaceConfig {
304 pub size: gpu::Extent,
305 pub transparent: bool,
306}
307
308//Note: we could see some of these fields moved into `BladeContext`
309// so that they are shared between windows. E.g. `pipelines`.
310// But that is complicated by the fact that pipelines depend on
311// the format and alpha mode.
312pub struct BladeRenderer {
313 gpu: Arc<gpu::Context>,
314 surface: gpu::Surface,
315 surface_config: gpu::SurfaceConfig,
316 command_encoder: gpu::CommandEncoder,
317 last_sync_point: Option<gpu::SyncPoint>,
318 pipelines: BladePipelines,
319 instance_belt: BufferBelt,
320 path_tiles: HashMap<PathId, AtlasTile>,
321 atlas: Arc<BladeAtlas>,
322 atlas_sampler: gpu::Sampler,
323 #[cfg(target_os = "macos")]
324 core_video_texture_cache: CVMetalTextureCache,
325 path_sample_count: u32,
326}
327
328impl BladeRenderer {
329 pub fn new<I: raw_window_handle::HasWindowHandle + raw_window_handle::HasDisplayHandle>(
330 context: &BladeContext,
331 window: &I,
332 config: BladeSurfaceConfig,
333 ) -> anyhow::Result<Self> {
334 let surface_config = gpu::SurfaceConfig {
335 size: config.size,
336 usage: gpu::TextureUsage::TARGET,
337 display_sync: gpu::DisplaySync::Recent,
338 color_space: gpu::ColorSpace::Linear,
339 allow_exclusive_full_screen: false,
340 transparent: config.transparent,
341 };
342 let surface = context
343 .gpu
344 .create_surface_configured(window, surface_config)
345 .unwrap();
346
347 let command_encoder = context.gpu.create_command_encoder(gpu::CommandEncoderDesc {
348 name: "main",
349 buffer_count: 2,
350 });
351 // workaround for https://github.com/zed-industries/zed/issues/26143
352 let path_sample_count = std::env::var("ZED_PATH_SAMPLE_COUNT")
353 .ok()
354 .and_then(|v| v.parse().ok())
355 .unwrap_or(DEFAULT_PATH_SAMPLE_COUNT);
356 let pipelines = BladePipelines::new(&context.gpu, surface.info(), path_sample_count);
357 let instance_belt = BufferBelt::new(BufferBeltDescriptor {
358 memory: gpu::Memory::Shared,
359 min_chunk_size: 0x1000,
360 alignment: 0x40, // Vulkan `minStorageBufferOffsetAlignment` on Intel Xe
361 });
362 let atlas = Arc::new(BladeAtlas::new(&context.gpu, path_sample_count));
363 let atlas_sampler = context.gpu.create_sampler(gpu::SamplerDesc {
364 name: "atlas",
365 mag_filter: gpu::FilterMode::Linear,
366 min_filter: gpu::FilterMode::Linear,
367 ..Default::default()
368 });
369
370 #[cfg(target_os = "macos")]
371 let core_video_texture_cache = unsafe {
372 CVMetalTextureCache::new(
373 objc2::rc::Retained::as_ptr(&context.gpu.metal_device()) as *mut _
374 )
375 .unwrap()
376 };
377
378 Ok(Self {
379 gpu: Arc::clone(&context.gpu),
380 surface,
381 surface_config,
382 command_encoder,
383 last_sync_point: None,
384 pipelines,
385 instance_belt,
386 path_tiles: HashMap::default(),
387 atlas,
388 atlas_sampler,
389 #[cfg(target_os = "macos")]
390 core_video_texture_cache,
391 path_sample_count,
392 })
393 }
394
395 fn wait_for_gpu(&mut self) {
396 if let Some(last_sp) = self.last_sync_point.take() {
397 if !self.gpu.wait_for(&last_sp, MAX_FRAME_TIME_MS) {
398 log::error!("GPU hung");
399 #[cfg(target_os = "linux")]
400 if self.gpu.device_information().driver_name == "radv" {
401 log::error!(
402 "there's a known bug with amdgpu/radv, try setting ZED_PATH_SAMPLE_COUNT=0 as a workaround"
403 );
404 log::error!(
405 "if that helps you're running into https://github.com/zed-industries/zed/issues/26143"
406 );
407 }
408 log::error!(
409 "your device information is: {:?}",
410 self.gpu.device_information()
411 );
412 while !self.gpu.wait_for(&last_sp, MAX_FRAME_TIME_MS) {}
413 }
414 }
415 }
416
417 pub fn update_drawable_size(&mut self, size: Size<DevicePixels>) {
418 self.update_drawable_size_impl(size, false);
419 }
420
421 /// Like `update_drawable_size` but skips the check that the size has changed. This is useful in
422 /// cases like restoring a window from minimization where the size is the same but the
423 /// renderer's swap chain needs to be recreated.
424 #[cfg_attr(any(target_os = "macos", target_os = "linux"), allow(dead_code))]
425 pub fn update_drawable_size_even_if_unchanged(&mut self, size: Size<DevicePixels>) {
426 self.update_drawable_size_impl(size, true);
427 }
428
429 fn update_drawable_size_impl(&mut self, size: Size<DevicePixels>, always_resize: bool) {
430 let gpu_size = gpu::Extent {
431 width: size.width.0 as u32,
432 height: size.height.0 as u32,
433 depth: 1,
434 };
435
436 if always_resize || gpu_size != self.surface_config.size {
437 self.wait_for_gpu();
438 self.surface_config.size = gpu_size;
439 self.gpu
440 .reconfigure_surface(&mut self.surface, self.surface_config);
441 }
442 }
443
444 pub fn update_transparency(&mut self, transparent: bool) {
445 if transparent != self.surface_config.transparent {
446 self.wait_for_gpu();
447 self.surface_config.transparent = transparent;
448 self.gpu
449 .reconfigure_surface(&mut self.surface, self.surface_config);
450 self.pipelines.destroy(&self.gpu);
451 self.pipelines =
452 BladePipelines::new(&self.gpu, self.surface.info(), self.path_sample_count);
453 }
454 }
455
456 #[cfg_attr(
457 any(target_os = "macos", feature = "wayland", target_os = "windows"),
458 allow(dead_code)
459 )]
460 pub fn viewport_size(&self) -> gpu::Extent {
461 self.surface_config.size
462 }
463
464 pub fn sprite_atlas(&self) -> &Arc<BladeAtlas> {
465 &self.atlas
466 }
467
468 #[cfg_attr(target_os = "macos", allow(dead_code))]
469 pub fn gpu_specs(&self) -> GpuSpecs {
470 let info = self.gpu.device_information();
471
472 GpuSpecs {
473 is_software_emulated: info.is_software_emulated,
474 device_name: info.device_name.clone(),
475 driver_name: info.driver_name.clone(),
476 driver_info: info.driver_info.clone(),
477 }
478 }
479
480 #[cfg(target_os = "macos")]
481 pub fn layer(&self) -> metal::MetalLayer {
482 unsafe { foreign_types::ForeignType::from_ptr(self.layer_ptr()) }
483 }
484
485 #[cfg(target_os = "macos")]
486 pub fn layer_ptr(&self) -> *mut metal::CAMetalLayer {
487 objc2::rc::Retained::as_ptr(&self.surface.metal_layer()) as *mut _
488 }
489
490 #[profiling::function]
491 fn rasterize_paths(&mut self, paths: &[Path<ScaledPixels>]) {
492 self.path_tiles.clear();
493 let mut vertices_by_texture_id = HashMap::default();
494
495 for path in paths {
496 let clipped_bounds = path
497 .bounds
498 .intersect(&path.content_mask.bounds)
499 .map_origin(|origin| origin.floor())
500 .map_size(|size| size.ceil());
501 let tile = self.atlas.allocate_for_rendering(
502 clipped_bounds.size.map(Into::into),
503 AtlasTextureKind::Path,
504 &mut self.command_encoder,
505 );
506 vertices_by_texture_id
507 .entry(tile.texture_id)
508 .or_insert(Vec::new())
509 .extend(path.vertices.iter().map(|vertex| PathVertex {
510 xy_position: vertex.xy_position - clipped_bounds.origin
511 + tile.bounds.origin.map(Into::into),
512 st_position: vertex.st_position,
513 content_mask: ContentMask {
514 bounds: tile.bounds.map(Into::into),
515 },
516 }));
517 self.path_tiles.insert(path.id, tile);
518 }
519
520 for (texture_id, vertices) in vertices_by_texture_id {
521 let tex_info = self.atlas.get_texture_info(texture_id);
522 let globals = GlobalParams {
523 viewport_size: [tex_info.size.width as f32, tex_info.size.height as f32],
524 premultiplied_alpha: 0,
525 pad: 0,
526 };
527
528 let vertex_buf = unsafe { self.instance_belt.alloc_typed(&vertices, &self.gpu) };
529 let frame_view = tex_info.raw_view;
530 let color_target = if let Some(msaa_view) = tex_info.msaa_view {
531 gpu::RenderTarget {
532 view: msaa_view,
533 init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
534 finish_op: gpu::FinishOp::ResolveTo(frame_view),
535 }
536 } else {
537 gpu::RenderTarget {
538 view: frame_view,
539 init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
540 finish_op: gpu::FinishOp::Store,
541 }
542 };
543
544 if let mut pass = self.command_encoder.render(
545 "paths",
546 gpu::RenderTargetSet {
547 colors: &[color_target],
548 depth_stencil: None,
549 },
550 ) {
551 let mut encoder = pass.with(&self.pipelines.path_rasterization);
552 encoder.bind(
553 0,
554 &ShaderPathRasterizationData {
555 globals,
556 b_path_vertices: vertex_buf,
557 },
558 );
559 encoder.draw(0, vertices.len() as u32, 0, 1);
560 }
561 }
562 }
563
564 pub fn destroy(&mut self) {
565 self.wait_for_gpu();
566 self.atlas.destroy();
567 self.gpu.destroy_sampler(self.atlas_sampler);
568 self.instance_belt.destroy(&self.gpu);
569 self.gpu.destroy_command_encoder(&mut self.command_encoder);
570 self.pipelines.destroy(&self.gpu);
571 self.gpu.destroy_surface(&mut self.surface);
572 }
573
574 pub fn draw(&mut self, scene: &Scene) {
575 self.command_encoder.start();
576 self.atlas.before_frame(&mut self.command_encoder);
577 self.rasterize_paths(scene.paths());
578
579 let frame = {
580 profiling::scope!("acquire frame");
581 self.surface.acquire_frame()
582 };
583 self.command_encoder.init_texture(frame.texture());
584
585 let globals = GlobalParams {
586 viewport_size: [
587 self.surface_config.size.width as f32,
588 self.surface_config.size.height as f32,
589 ],
590 premultiplied_alpha: match self.surface.info().alpha {
591 gpu::AlphaMode::Ignored | gpu::AlphaMode::PostMultiplied => 0,
592 gpu::AlphaMode::PreMultiplied => 1,
593 },
594 pad: 0,
595 };
596
597 if let mut pass = self.command_encoder.render(
598 "main",
599 gpu::RenderTargetSet {
600 colors: &[gpu::RenderTarget {
601 view: frame.texture_view(),
602 init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
603 finish_op: gpu::FinishOp::Store,
604 }],
605 depth_stencil: None,
606 },
607 ) {
608 profiling::scope!("render pass");
609 for batch in scene.batches() {
610 match batch {
611 PrimitiveBatch::Quads(quads) => {
612 let instance_buf =
613 unsafe { self.instance_belt.alloc_typed(quads, &self.gpu) };
614 let mut encoder = pass.with(&self.pipelines.quads);
615 encoder.bind(
616 0,
617 &ShaderQuadsData {
618 globals,
619 b_quads: instance_buf,
620 },
621 );
622 encoder.draw(0, 4, 0, quads.len() as u32);
623 }
624 PrimitiveBatch::Shadows(shadows) => {
625 let instance_buf =
626 unsafe { self.instance_belt.alloc_typed(shadows, &self.gpu) };
627 let mut encoder = pass.with(&self.pipelines.shadows);
628 encoder.bind(
629 0,
630 &ShaderShadowsData {
631 globals,
632 b_shadows: instance_buf,
633 },
634 );
635 encoder.draw(0, 4, 0, shadows.len() as u32);
636 }
637 PrimitiveBatch::Paths(paths) => {
638 let mut encoder = pass.with(&self.pipelines.paths);
639 // todo(linux): group by texture ID
640 for path in paths {
641 let tile = &self.path_tiles[&path.id];
642 let tex_info = self.atlas.get_texture_info(tile.texture_id);
643 let origin = path.bounds.intersect(&path.content_mask.bounds).origin;
644 let sprites = [PathSprite {
645 bounds: Bounds {
646 origin: origin.map(|p| p.floor()),
647 size: tile.bounds.size.map(Into::into),
648 },
649 color: path.color,
650 tile: (*tile).clone(),
651 }];
652
653 let instance_buf =
654 unsafe { self.instance_belt.alloc_typed(&sprites, &self.gpu) };
655 encoder.bind(
656 0,
657 &ShaderPathsData {
658 globals,
659 t_sprite: tex_info.raw_view,
660 s_sprite: self.atlas_sampler,
661 b_path_sprites: instance_buf,
662 },
663 );
664 encoder.draw(0, 4, 0, sprites.len() as u32);
665 }
666 }
667 PrimitiveBatch::Underlines(underlines) => {
668 let instance_buf =
669 unsafe { self.instance_belt.alloc_typed(underlines, &self.gpu) };
670 let mut encoder = pass.with(&self.pipelines.underlines);
671 encoder.bind(
672 0,
673 &ShaderUnderlinesData {
674 globals,
675 b_underlines: instance_buf,
676 },
677 );
678 encoder.draw(0, 4, 0, underlines.len() as u32);
679 }
680 PrimitiveBatch::MonochromeSprites {
681 texture_id,
682 sprites,
683 } => {
684 let tex_info = self.atlas.get_texture_info(texture_id);
685 let instance_buf =
686 unsafe { self.instance_belt.alloc_typed(sprites, &self.gpu) };
687 let mut encoder = pass.with(&self.pipelines.mono_sprites);
688 encoder.bind(
689 0,
690 &ShaderMonoSpritesData {
691 globals,
692 t_sprite: tex_info.raw_view,
693 s_sprite: self.atlas_sampler,
694 b_mono_sprites: instance_buf,
695 },
696 );
697 encoder.draw(0, 4, 0, sprites.len() as u32);
698 }
699 PrimitiveBatch::PolychromeSprites {
700 texture_id,
701 sprites,
702 } => {
703 let tex_info = self.atlas.get_texture_info(texture_id);
704 let instance_buf =
705 unsafe { self.instance_belt.alloc_typed(sprites, &self.gpu) };
706 let mut encoder = pass.with(&self.pipelines.poly_sprites);
707 encoder.bind(
708 0,
709 &ShaderPolySpritesData {
710 globals,
711 t_sprite: tex_info.raw_view,
712 s_sprite: self.atlas_sampler,
713 b_poly_sprites: instance_buf,
714 },
715 );
716 encoder.draw(0, 4, 0, sprites.len() as u32);
717 }
718 PrimitiveBatch::Surfaces(surfaces) => {
719 let mut _encoder = pass.with(&self.pipelines.surfaces);
720
721 for surface in surfaces {
722 #[cfg(not(target_os = "macos"))]
723 {
724 let _ = surface;
725 continue;
726 };
727
728 #[cfg(target_os = "macos")]
729 {
730 let (t_y, t_cb_cr) = unsafe {
731 use core_foundation::base::TCFType as _;
732 use std::ptr;
733
734 assert_eq!(
735 surface.image_buffer.get_pixel_format(),
736 core_video::pixel_buffer::kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
737 );
738
739 let y_texture = self
740 .core_video_texture_cache
741 .create_texture_from_image(
742 surface.image_buffer.as_concrete_TypeRef(),
743 ptr::null(),
744 metal::MTLPixelFormat::R8Unorm,
745 surface.image_buffer.get_width_of_plane(0),
746 surface.image_buffer.get_height_of_plane(0),
747 0,
748 )
749 .unwrap();
750 let cb_cr_texture = self
751 .core_video_texture_cache
752 .create_texture_from_image(
753 surface.image_buffer.as_concrete_TypeRef(),
754 ptr::null(),
755 metal::MTLPixelFormat::RG8Unorm,
756 surface.image_buffer.get_width_of_plane(1),
757 surface.image_buffer.get_height_of_plane(1),
758 1,
759 )
760 .unwrap();
761 (
762 gpu::TextureView::from_metal_texture(
763 &objc2::rc::Retained::retain(
764 foreign_types::ForeignTypeRef::as_ptr(
765 y_texture.as_texture_ref(),
766 )
767 as *mut objc2::runtime::ProtocolObject<
768 dyn objc2_metal::MTLTexture,
769 >,
770 )
771 .unwrap(),
772 gpu::TexelAspects::COLOR,
773 ),
774 gpu::TextureView::from_metal_texture(
775 &objc2::rc::Retained::retain(
776 foreign_types::ForeignTypeRef::as_ptr(
777 cb_cr_texture.as_texture_ref(),
778 )
779 as *mut objc2::runtime::ProtocolObject<
780 dyn objc2_metal::MTLTexture,
781 >,
782 )
783 .unwrap(),
784 gpu::TexelAspects::COLOR,
785 ),
786 )
787 };
788
789 _encoder.bind(
790 0,
791 &ShaderSurfacesData {
792 globals,
793 surface_locals: SurfaceParams {
794 bounds: surface.bounds.into(),
795 content_mask: surface.content_mask.bounds.into(),
796 },
797 t_y,
798 t_cb_cr,
799 s_surface: self.atlas_sampler,
800 },
801 );
802
803 _encoder.draw(0, 4, 0, 1);
804 }
805 }
806 }
807 }
808 }
809 }
810
811 self.command_encoder.present(frame);
812 let sync_point = self.gpu.submit(&mut self.command_encoder);
813
814 profiling::scope!("finish");
815 self.instance_belt.flush(&sync_point);
816 self.atlas.after_frame(&sync_point);
817 self.atlas.clear_textures(AtlasTextureKind::Path);
818
819 self.wait_for_gpu();
820 self.last_sync_point = Some(sync_point);
821 }
822}