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 .map_err(|err| anyhow::anyhow!("Failed to create surface: {err:?}"))?;
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(
425 any(target_os = "macos", target_os = "linux", target_os = "freebsd"),
426 allow(dead_code)
427 )]
428 pub fn update_drawable_size_even_if_unchanged(&mut self, size: Size<DevicePixels>) {
429 self.update_drawable_size_impl(size, true);
430 }
431
432 fn update_drawable_size_impl(&mut self, size: Size<DevicePixels>, always_resize: bool) {
433 let gpu_size = gpu::Extent {
434 width: size.width.0 as u32,
435 height: size.height.0 as u32,
436 depth: 1,
437 };
438
439 if always_resize || gpu_size != self.surface_config.size {
440 self.wait_for_gpu();
441 self.surface_config.size = gpu_size;
442 self.gpu
443 .reconfigure_surface(&mut self.surface, self.surface_config);
444 }
445 }
446
447 pub fn update_transparency(&mut self, transparent: bool) {
448 if transparent != self.surface_config.transparent {
449 self.wait_for_gpu();
450 self.surface_config.transparent = transparent;
451 self.gpu
452 .reconfigure_surface(&mut self.surface, self.surface_config);
453 self.pipelines.destroy(&self.gpu);
454 self.pipelines =
455 BladePipelines::new(&self.gpu, self.surface.info(), self.path_sample_count);
456 }
457 }
458
459 #[cfg_attr(
460 any(target_os = "macos", feature = "wayland", target_os = "windows"),
461 allow(dead_code)
462 )]
463 pub fn viewport_size(&self) -> gpu::Extent {
464 self.surface_config.size
465 }
466
467 pub fn sprite_atlas(&self) -> &Arc<BladeAtlas> {
468 &self.atlas
469 }
470
471 #[cfg_attr(target_os = "macos", allow(dead_code))]
472 pub fn gpu_specs(&self) -> GpuSpecs {
473 let info = self.gpu.device_information();
474
475 GpuSpecs {
476 is_software_emulated: info.is_software_emulated,
477 device_name: info.device_name.clone(),
478 driver_name: info.driver_name.clone(),
479 driver_info: info.driver_info.clone(),
480 }
481 }
482
483 #[cfg(target_os = "macos")]
484 pub fn layer(&self) -> metal::MetalLayer {
485 unsafe { foreign_types::ForeignType::from_ptr(self.layer_ptr()) }
486 }
487
488 #[cfg(target_os = "macos")]
489 pub fn layer_ptr(&self) -> *mut metal::CAMetalLayer {
490 objc2::rc::Retained::as_ptr(&self.surface.metal_layer()) as *mut _
491 }
492
493 #[profiling::function]
494 fn rasterize_paths(&mut self, paths: &[Path<ScaledPixels>]) {
495 self.path_tiles.clear();
496 let mut vertices_by_texture_id = HashMap::default();
497
498 for path in paths {
499 let clipped_bounds = path
500 .bounds
501 .intersect(&path.content_mask.bounds)
502 .map_origin(|origin| origin.floor())
503 .map_size(|size| size.ceil());
504 let tile = self.atlas.allocate_for_rendering(
505 clipped_bounds.size.map(Into::into),
506 AtlasTextureKind::Path,
507 &mut self.command_encoder,
508 );
509 vertices_by_texture_id
510 .entry(tile.texture_id)
511 .or_insert(Vec::new())
512 .extend(path.vertices.iter().map(|vertex| PathVertex {
513 xy_position: vertex.xy_position - clipped_bounds.origin
514 + tile.bounds.origin.map(Into::into),
515 st_position: vertex.st_position,
516 content_mask: ContentMask {
517 bounds: tile.bounds.map(Into::into),
518 },
519 }));
520 self.path_tiles.insert(path.id, tile);
521 }
522
523 for (texture_id, vertices) in vertices_by_texture_id {
524 let tex_info = self.atlas.get_texture_info(texture_id);
525 let globals = GlobalParams {
526 viewport_size: [tex_info.size.width as f32, tex_info.size.height as f32],
527 premultiplied_alpha: 0,
528 pad: 0,
529 };
530
531 let vertex_buf = unsafe { self.instance_belt.alloc_typed(&vertices, &self.gpu) };
532 let frame_view = tex_info.raw_view;
533 let color_target = if let Some(msaa_view) = tex_info.msaa_view {
534 gpu::RenderTarget {
535 view: msaa_view,
536 init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
537 finish_op: gpu::FinishOp::ResolveTo(frame_view),
538 }
539 } else {
540 gpu::RenderTarget {
541 view: frame_view,
542 init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
543 finish_op: gpu::FinishOp::Store,
544 }
545 };
546
547 if let mut pass = self.command_encoder.render(
548 "paths",
549 gpu::RenderTargetSet {
550 colors: &[color_target],
551 depth_stencil: None,
552 },
553 ) {
554 let mut encoder = pass.with(&self.pipelines.path_rasterization);
555 encoder.bind(
556 0,
557 &ShaderPathRasterizationData {
558 globals,
559 b_path_vertices: vertex_buf,
560 },
561 );
562 encoder.draw(0, vertices.len() as u32, 0, 1);
563 }
564 }
565 }
566
567 pub fn destroy(&mut self) {
568 self.wait_for_gpu();
569 self.atlas.destroy();
570 self.gpu.destroy_sampler(self.atlas_sampler);
571 self.instance_belt.destroy(&self.gpu);
572 self.gpu.destroy_command_encoder(&mut self.command_encoder);
573 self.pipelines.destroy(&self.gpu);
574 self.gpu.destroy_surface(&mut self.surface);
575 }
576
577 pub fn draw(&mut self, scene: &Scene) {
578 self.command_encoder.start();
579 self.atlas.before_frame(&mut self.command_encoder);
580 self.rasterize_paths(scene.paths());
581
582 let frame = {
583 profiling::scope!("acquire frame");
584 self.surface.acquire_frame()
585 };
586 self.command_encoder.init_texture(frame.texture());
587
588 let globals = GlobalParams {
589 viewport_size: [
590 self.surface_config.size.width as f32,
591 self.surface_config.size.height as f32,
592 ],
593 premultiplied_alpha: match self.surface.info().alpha {
594 gpu::AlphaMode::Ignored | gpu::AlphaMode::PostMultiplied => 0,
595 gpu::AlphaMode::PreMultiplied => 1,
596 },
597 pad: 0,
598 };
599
600 if let mut pass = self.command_encoder.render(
601 "main",
602 gpu::RenderTargetSet {
603 colors: &[gpu::RenderTarget {
604 view: frame.texture_view(),
605 init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
606 finish_op: gpu::FinishOp::Store,
607 }],
608 depth_stencil: None,
609 },
610 ) {
611 profiling::scope!("render pass");
612 for batch in scene.batches() {
613 match batch {
614 PrimitiveBatch::Quads(quads) => {
615 let instance_buf =
616 unsafe { self.instance_belt.alloc_typed(quads, &self.gpu) };
617 let mut encoder = pass.with(&self.pipelines.quads);
618 encoder.bind(
619 0,
620 &ShaderQuadsData {
621 globals,
622 b_quads: instance_buf,
623 },
624 );
625 encoder.draw(0, 4, 0, quads.len() as u32);
626 }
627 PrimitiveBatch::Shadows(shadows) => {
628 let instance_buf =
629 unsafe { self.instance_belt.alloc_typed(shadows, &self.gpu) };
630 let mut encoder = pass.with(&self.pipelines.shadows);
631 encoder.bind(
632 0,
633 &ShaderShadowsData {
634 globals,
635 b_shadows: instance_buf,
636 },
637 );
638 encoder.draw(0, 4, 0, shadows.len() as u32);
639 }
640 PrimitiveBatch::Paths(paths) => {
641 let mut encoder = pass.with(&self.pipelines.paths);
642 // todo(linux): group by texture ID
643 for path in paths {
644 let tile = &self.path_tiles[&path.id];
645 let tex_info = self.atlas.get_texture_info(tile.texture_id);
646 let origin = path.bounds.intersect(&path.content_mask.bounds).origin;
647 let sprites = [PathSprite {
648 bounds: Bounds {
649 origin: origin.map(|p| p.floor()),
650 size: tile.bounds.size.map(Into::into),
651 },
652 color: path.color,
653 tile: (*tile).clone(),
654 }];
655
656 let instance_buf =
657 unsafe { self.instance_belt.alloc_typed(&sprites, &self.gpu) };
658 encoder.bind(
659 0,
660 &ShaderPathsData {
661 globals,
662 t_sprite: tex_info.raw_view,
663 s_sprite: self.atlas_sampler,
664 b_path_sprites: instance_buf,
665 },
666 );
667 encoder.draw(0, 4, 0, sprites.len() as u32);
668 }
669 }
670 PrimitiveBatch::Underlines(underlines) => {
671 let instance_buf =
672 unsafe { self.instance_belt.alloc_typed(underlines, &self.gpu) };
673 let mut encoder = pass.with(&self.pipelines.underlines);
674 encoder.bind(
675 0,
676 &ShaderUnderlinesData {
677 globals,
678 b_underlines: instance_buf,
679 },
680 );
681 encoder.draw(0, 4, 0, underlines.len() as u32);
682 }
683 PrimitiveBatch::MonochromeSprites {
684 texture_id,
685 sprites,
686 } => {
687 let tex_info = self.atlas.get_texture_info(texture_id);
688 let instance_buf =
689 unsafe { self.instance_belt.alloc_typed(sprites, &self.gpu) };
690 let mut encoder = pass.with(&self.pipelines.mono_sprites);
691 encoder.bind(
692 0,
693 &ShaderMonoSpritesData {
694 globals,
695 t_sprite: tex_info.raw_view,
696 s_sprite: self.atlas_sampler,
697 b_mono_sprites: instance_buf,
698 },
699 );
700 encoder.draw(0, 4, 0, sprites.len() as u32);
701 }
702 PrimitiveBatch::PolychromeSprites {
703 texture_id,
704 sprites,
705 } => {
706 let tex_info = self.atlas.get_texture_info(texture_id);
707 let instance_buf =
708 unsafe { self.instance_belt.alloc_typed(sprites, &self.gpu) };
709 let mut encoder = pass.with(&self.pipelines.poly_sprites);
710 encoder.bind(
711 0,
712 &ShaderPolySpritesData {
713 globals,
714 t_sprite: tex_info.raw_view,
715 s_sprite: self.atlas_sampler,
716 b_poly_sprites: instance_buf,
717 },
718 );
719 encoder.draw(0, 4, 0, sprites.len() as u32);
720 }
721 PrimitiveBatch::Surfaces(surfaces) => {
722 let mut _encoder = pass.with(&self.pipelines.surfaces);
723
724 for surface in surfaces {
725 #[cfg(not(target_os = "macos"))]
726 {
727 let _ = surface;
728 continue;
729 };
730
731 #[cfg(target_os = "macos")]
732 {
733 let (t_y, t_cb_cr) = unsafe {
734 use core_foundation::base::TCFType as _;
735 use std::ptr;
736
737 assert_eq!(
738 surface.image_buffer.get_pixel_format(),
739 core_video::pixel_buffer::kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
740 );
741
742 let y_texture = self
743 .core_video_texture_cache
744 .create_texture_from_image(
745 surface.image_buffer.as_concrete_TypeRef(),
746 ptr::null(),
747 metal::MTLPixelFormat::R8Unorm,
748 surface.image_buffer.get_width_of_plane(0),
749 surface.image_buffer.get_height_of_plane(0),
750 0,
751 )
752 .unwrap();
753 let cb_cr_texture = self
754 .core_video_texture_cache
755 .create_texture_from_image(
756 surface.image_buffer.as_concrete_TypeRef(),
757 ptr::null(),
758 metal::MTLPixelFormat::RG8Unorm,
759 surface.image_buffer.get_width_of_plane(1),
760 surface.image_buffer.get_height_of_plane(1),
761 1,
762 )
763 .unwrap();
764 (
765 gpu::TextureView::from_metal_texture(
766 &objc2::rc::Retained::retain(
767 foreign_types::ForeignTypeRef::as_ptr(
768 y_texture.as_texture_ref(),
769 )
770 as *mut objc2::runtime::ProtocolObject<
771 dyn objc2_metal::MTLTexture,
772 >,
773 )
774 .unwrap(),
775 gpu::TexelAspects::COLOR,
776 ),
777 gpu::TextureView::from_metal_texture(
778 &objc2::rc::Retained::retain(
779 foreign_types::ForeignTypeRef::as_ptr(
780 cb_cr_texture.as_texture_ref(),
781 )
782 as *mut objc2::runtime::ProtocolObject<
783 dyn objc2_metal::MTLTexture,
784 >,
785 )
786 .unwrap(),
787 gpu::TexelAspects::COLOR,
788 ),
789 )
790 };
791
792 _encoder.bind(
793 0,
794 &ShaderSurfacesData {
795 globals,
796 surface_locals: SurfaceParams {
797 bounds: surface.bounds.into(),
798 content_mask: surface.content_mask.bounds.into(),
799 },
800 t_y,
801 t_cb_cr,
802 s_surface: self.atlas_sampler,
803 },
804 );
805
806 _encoder.draw(0, 4, 0, 1);
807 }
808 }
809 }
810 }
811 }
812 }
813
814 self.command_encoder.present(frame);
815 let sync_point = self.gpu.submit(&mut self.command_encoder);
816
817 profiling::scope!("finish");
818 self.instance_belt.flush(&sync_point);
819 self.atlas.after_frame(&sync_point);
820 self.atlas.clear_textures(AtlasTextureKind::Path);
821
822 self.wait_for_gpu();
823 self.last_sync_point = Some(sync_point);
824 }
825}