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