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 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) -> 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}
326
327impl BladeRenderer {
328 pub fn new<I: raw_window_handle::HasWindowHandle + raw_window_handle::HasDisplayHandle>(
329 context: &BladeContext,
330 window: &I,
331 config: BladeSurfaceConfig,
332 ) -> anyhow::Result<Self> {
333 let surface_config = gpu::SurfaceConfig {
334 size: config.size,
335 usage: gpu::TextureUsage::TARGET,
336 display_sync: gpu::DisplaySync::Recent,
337 color_space: gpu::ColorSpace::Linear,
338 allow_exclusive_full_screen: false,
339 transparent: config.transparent,
340 };
341 let surface = context
342 .gpu
343 .create_surface_configured(window, surface_config)
344 .unwrap();
345
346 let command_encoder = context.gpu.create_command_encoder(gpu::CommandEncoderDesc {
347 name: "main",
348 buffer_count: 2,
349 });
350 let pipelines = BladePipelines::new(&context.gpu, surface.info());
351 let instance_belt = BufferBelt::new(BufferBeltDescriptor {
352 memory: gpu::Memory::Shared,
353 min_chunk_size: 0x1000,
354 alignment: 0x40, // Vulkan `minStorageBufferOffsetAlignment` on Intel Xe
355 });
356 let atlas = Arc::new(BladeAtlas::new(&context.gpu, PATH_SAMPLE_COUNT));
357 let atlas_sampler = context.gpu.create_sampler(gpu::SamplerDesc {
358 name: "atlas",
359 mag_filter: gpu::FilterMode::Linear,
360 min_filter: gpu::FilterMode::Linear,
361 ..Default::default()
362 });
363
364 #[cfg(target_os = "macos")]
365 let core_video_texture_cache = unsafe {
366 CVMetalTextureCache::new(
367 objc2::rc::Retained::as_ptr(&context.gpu.metal_device()) as *mut _
368 )
369 .unwrap()
370 };
371
372 Ok(Self {
373 gpu: Arc::clone(&context.gpu),
374 surface,
375 surface_config,
376 command_encoder,
377 last_sync_point: None,
378 pipelines,
379 instance_belt,
380 path_tiles: HashMap::default(),
381 atlas,
382 atlas_sampler,
383 #[cfg(target_os = "macos")]
384 core_video_texture_cache,
385 })
386 }
387
388 fn wait_for_gpu(&mut self) {
389 if let Some(last_sp) = self.last_sync_point.take() {
390 if !self.gpu.wait_for(&last_sp, MAX_FRAME_TIME_MS) {
391 log::error!("GPU hung");
392 while !self.gpu.wait_for(&last_sp, MAX_FRAME_TIME_MS) {}
393 }
394 }
395 }
396
397 pub fn update_drawable_size(&mut self, size: Size<DevicePixels>) {
398 self.update_drawable_size_impl(size, false);
399 }
400
401 /// Like `update_drawable_size` but skips the check that the size has changed. This is useful in
402 /// cases like restoring a window from minimization where the size is the same but the
403 /// renderer's swap chain needs to be recreated.
404 #[cfg_attr(any(target_os = "macos", target_os = "linux"), allow(dead_code))]
405 pub fn update_drawable_size_even_if_unchanged(&mut self, size: Size<DevicePixels>) {
406 self.update_drawable_size_impl(size, true);
407 }
408
409 fn update_drawable_size_impl(&mut self, size: Size<DevicePixels>, always_resize: bool) {
410 let gpu_size = gpu::Extent {
411 width: size.width.0 as u32,
412 height: size.height.0 as u32,
413 depth: 1,
414 };
415
416 if always_resize || gpu_size != self.surface_config.size {
417 self.wait_for_gpu();
418 self.surface_config.size = gpu_size;
419 self.gpu
420 .reconfigure_surface(&mut self.surface, self.surface_config);
421 }
422 }
423
424 pub fn update_transparency(&mut self, transparent: bool) {
425 if transparent != self.surface_config.transparent {
426 self.wait_for_gpu();
427 self.surface_config.transparent = transparent;
428 self.gpu
429 .reconfigure_surface(&mut self.surface, self.surface_config);
430 self.pipelines.destroy(&self.gpu);
431 self.pipelines = BladePipelines::new(&self.gpu, self.surface.info());
432 }
433 }
434
435 #[cfg_attr(any(target_os = "macos", feature = "wayland"), allow(dead_code))]
436 pub fn viewport_size(&self) -> gpu::Extent {
437 self.surface_config.size
438 }
439
440 pub fn sprite_atlas(&self) -> &Arc<BladeAtlas> {
441 &self.atlas
442 }
443
444 #[cfg_attr(target_os = "macos", allow(dead_code))]
445 pub fn gpu_specs(&self) -> GpuSpecs {
446 let info = self.gpu.device_information();
447
448 GpuSpecs {
449 is_software_emulated: info.is_software_emulated,
450 device_name: info.device_name.clone(),
451 driver_name: info.driver_name.clone(),
452 driver_info: info.driver_info.clone(),
453 }
454 }
455
456 #[cfg(target_os = "macos")]
457 pub fn layer(&self) -> metal::MetalLayer {
458 unsafe { foreign_types::ForeignType::from_ptr(self.layer_ptr()) }
459 }
460
461 #[cfg(target_os = "macos")]
462 pub fn layer_ptr(&self) -> *mut metal::CAMetalLayer {
463 objc2::rc::Retained::as_ptr(&self.surface.metal_layer()) as *mut _
464 }
465
466 #[profiling::function]
467 fn rasterize_paths(&mut self, paths: &[Path<ScaledPixels>]) {
468 self.path_tiles.clear();
469 let mut vertices_by_texture_id = HashMap::default();
470
471 for path in paths {
472 let clipped_bounds = path
473 .bounds
474 .intersect(&path.content_mask.bounds)
475 .map_origin(|origin| origin.floor())
476 .map_size(|size| size.ceil());
477 let tile = self.atlas.allocate_for_rendering(
478 clipped_bounds.size.map(Into::into),
479 AtlasTextureKind::Path,
480 &mut self.command_encoder,
481 );
482 vertices_by_texture_id
483 .entry(tile.texture_id)
484 .or_insert(Vec::new())
485 .extend(path.vertices.iter().map(|vertex| PathVertex {
486 xy_position: vertex.xy_position - clipped_bounds.origin
487 + tile.bounds.origin.map(Into::into),
488 st_position: vertex.st_position,
489 content_mask: ContentMask {
490 bounds: tile.bounds.map(Into::into),
491 },
492 }));
493 self.path_tiles.insert(path.id, tile);
494 }
495
496 for (texture_id, vertices) in vertices_by_texture_id {
497 let tex_info = self.atlas.get_texture_info(texture_id);
498 let globals = GlobalParams {
499 viewport_size: [tex_info.size.width as f32, tex_info.size.height as f32],
500 premultiplied_alpha: 0,
501 pad: 0,
502 };
503
504 let vertex_buf = unsafe { self.instance_belt.alloc_typed(&vertices, &self.gpu) };
505 let frame_view = tex_info.raw_view;
506 let color_target = if let Some(msaa_view) = tex_info.msaa_view {
507 gpu::RenderTarget {
508 view: msaa_view,
509 init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
510 finish_op: gpu::FinishOp::ResolveTo(frame_view),
511 }
512 } else {
513 gpu::RenderTarget {
514 view: frame_view,
515 init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
516 finish_op: gpu::FinishOp::Store,
517 }
518 };
519
520 if let mut pass = self.command_encoder.render(
521 "paths",
522 gpu::RenderTargetSet {
523 colors: &[color_target],
524 depth_stencil: None,
525 },
526 ) {
527 let mut encoder = pass.with(&self.pipelines.path_rasterization);
528 encoder.bind(
529 0,
530 &ShaderPathRasterizationData {
531 globals,
532 b_path_vertices: vertex_buf,
533 },
534 );
535 encoder.draw(0, vertices.len() as u32, 0, 1);
536 }
537 }
538 }
539
540 pub fn destroy(&mut self) {
541 self.wait_for_gpu();
542 self.atlas.destroy();
543 self.gpu.destroy_sampler(self.atlas_sampler);
544 self.instance_belt.destroy(&self.gpu);
545 self.gpu.destroy_command_encoder(&mut self.command_encoder);
546 self.pipelines.destroy(&self.gpu);
547 self.gpu.destroy_surface(&mut self.surface);
548 }
549
550 pub fn draw(&mut self, scene: &Scene) {
551 self.command_encoder.start();
552 self.atlas.before_frame(&mut self.command_encoder);
553 self.rasterize_paths(scene.paths());
554
555 let frame = {
556 profiling::scope!("acquire frame");
557 self.surface.acquire_frame()
558 };
559 self.command_encoder.init_texture(frame.texture());
560
561 let globals = GlobalParams {
562 viewport_size: [
563 self.surface_config.size.width as f32,
564 self.surface_config.size.height as f32,
565 ],
566 premultiplied_alpha: match self.surface.info().alpha {
567 gpu::AlphaMode::Ignored | gpu::AlphaMode::PostMultiplied => 0,
568 gpu::AlphaMode::PreMultiplied => 1,
569 },
570 pad: 0,
571 };
572
573 if let mut pass = self.command_encoder.render(
574 "main",
575 gpu::RenderTargetSet {
576 colors: &[gpu::RenderTarget {
577 view: frame.texture_view(),
578 init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
579 finish_op: gpu::FinishOp::Store,
580 }],
581 depth_stencil: None,
582 },
583 ) {
584 profiling::scope!("render pass");
585 for batch in scene.batches() {
586 match batch {
587 PrimitiveBatch::Quads(quads) => {
588 let instance_buf =
589 unsafe { self.instance_belt.alloc_typed(quads, &self.gpu) };
590 let mut encoder = pass.with(&self.pipelines.quads);
591 encoder.bind(
592 0,
593 &ShaderQuadsData {
594 globals,
595 b_quads: instance_buf,
596 },
597 );
598 encoder.draw(0, 4, 0, quads.len() as u32);
599 }
600 PrimitiveBatch::Shadows(shadows) => {
601 let instance_buf =
602 unsafe { self.instance_belt.alloc_typed(shadows, &self.gpu) };
603 let mut encoder = pass.with(&self.pipelines.shadows);
604 encoder.bind(
605 0,
606 &ShaderShadowsData {
607 globals,
608 b_shadows: instance_buf,
609 },
610 );
611 encoder.draw(0, 4, 0, shadows.len() as u32);
612 }
613 PrimitiveBatch::Paths(paths) => {
614 let mut encoder = pass.with(&self.pipelines.paths);
615 // todo(linux): group by texture ID
616 for path in paths {
617 let tile = &self.path_tiles[&path.id];
618 let tex_info = self.atlas.get_texture_info(tile.texture_id);
619 let origin = path.bounds.intersect(&path.content_mask.bounds).origin;
620 let sprites = [PathSprite {
621 bounds: Bounds {
622 origin: origin.map(|p| p.floor()),
623 size: tile.bounds.size.map(Into::into),
624 },
625 color: path.color,
626 tile: (*tile).clone(),
627 }];
628
629 let instance_buf =
630 unsafe { self.instance_belt.alloc_typed(&sprites, &self.gpu) };
631 encoder.bind(
632 0,
633 &ShaderPathsData {
634 globals,
635 t_sprite: tex_info.raw_view,
636 s_sprite: self.atlas_sampler,
637 b_path_sprites: instance_buf,
638 },
639 );
640 encoder.draw(0, 4, 0, sprites.len() as u32);
641 }
642 }
643 PrimitiveBatch::Underlines(underlines) => {
644 let instance_buf =
645 unsafe { self.instance_belt.alloc_typed(underlines, &self.gpu) };
646 let mut encoder = pass.with(&self.pipelines.underlines);
647 encoder.bind(
648 0,
649 &ShaderUnderlinesData {
650 globals,
651 b_underlines: instance_buf,
652 },
653 );
654 encoder.draw(0, 4, 0, underlines.len() as u32);
655 }
656 PrimitiveBatch::MonochromeSprites {
657 texture_id,
658 sprites,
659 } => {
660 let tex_info = self.atlas.get_texture_info(texture_id);
661 let instance_buf =
662 unsafe { self.instance_belt.alloc_typed(sprites, &self.gpu) };
663 let mut encoder = pass.with(&self.pipelines.mono_sprites);
664 encoder.bind(
665 0,
666 &ShaderMonoSpritesData {
667 globals,
668 t_sprite: tex_info.raw_view,
669 s_sprite: self.atlas_sampler,
670 b_mono_sprites: instance_buf,
671 },
672 );
673 encoder.draw(0, 4, 0, sprites.len() as u32);
674 }
675 PrimitiveBatch::PolychromeSprites {
676 texture_id,
677 sprites,
678 } => {
679 let tex_info = self.atlas.get_texture_info(texture_id);
680 let instance_buf =
681 unsafe { self.instance_belt.alloc_typed(sprites, &self.gpu) };
682 let mut encoder = pass.with(&self.pipelines.poly_sprites);
683 encoder.bind(
684 0,
685 &ShaderPolySpritesData {
686 globals,
687 t_sprite: tex_info.raw_view,
688 s_sprite: self.atlas_sampler,
689 b_poly_sprites: instance_buf,
690 },
691 );
692 encoder.draw(0, 4, 0, sprites.len() as u32);
693 }
694 PrimitiveBatch::Surfaces(surfaces) => {
695 let mut _encoder = pass.with(&self.pipelines.surfaces);
696
697 for surface in surfaces {
698 #[cfg(not(target_os = "macos"))]
699 {
700 let _ = surface;
701 continue;
702 };
703
704 #[cfg(target_os = "macos")]
705 {
706 let (t_y, t_cb_cr) = unsafe {
707 use core_foundation::base::TCFType as _;
708 use std::ptr;
709
710 assert_eq!(
711 surface.image_buffer.pixel_format_type(),
712 media::core_video::kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
713 );
714
715 let y_texture = self
716 .core_video_texture_cache
717 .create_texture_from_image(
718 surface.image_buffer.as_concrete_TypeRef(),
719 ptr::null(),
720 metal::MTLPixelFormat::R8Unorm,
721 surface.image_buffer.plane_width(0),
722 surface.image_buffer.plane_height(0),
723 0,
724 )
725 .unwrap();
726 let cb_cr_texture = self
727 .core_video_texture_cache
728 .create_texture_from_image(
729 surface.image_buffer.as_concrete_TypeRef(),
730 ptr::null(),
731 metal::MTLPixelFormat::RG8Unorm,
732 surface.image_buffer.plane_width(1),
733 surface.image_buffer.plane_height(1),
734 1,
735 )
736 .unwrap();
737 (
738 gpu::TextureView::from_metal_texture(
739 &objc2::rc::Retained::retain(
740 foreign_types::ForeignTypeRef::as_ptr(
741 y_texture.as_texture_ref(),
742 )
743 as *mut objc2::runtime::ProtocolObject<
744 dyn objc2_metal::MTLTexture,
745 >,
746 )
747 .unwrap(),
748 ),
749 gpu::TextureView::from_metal_texture(
750 &objc2::rc::Retained::retain(
751 foreign_types::ForeignTypeRef::as_ptr(
752 cb_cr_texture.as_texture_ref(),
753 )
754 as *mut objc2::runtime::ProtocolObject<
755 dyn objc2_metal::MTLTexture,
756 >,
757 )
758 .unwrap(),
759 ),
760 )
761 };
762
763 _encoder.bind(
764 0,
765 &ShaderSurfacesData {
766 globals,
767 surface_locals: SurfaceParams {
768 bounds: surface.bounds.into(),
769 content_mask: surface.content_mask.bounds.into(),
770 },
771 t_y,
772 t_cb_cr,
773 s_surface: self.atlas_sampler,
774 },
775 );
776
777 _encoder.draw(0, 4, 0, 1);
778 }
779 }
780 }
781 }
782 }
783 }
784
785 self.command_encoder.present(frame);
786 let sync_point = self.gpu.submit(&mut self.command_encoder);
787
788 profiling::scope!("finish");
789 self.instance_belt.flush(&sync_point);
790 self.atlas.after_frame(&sync_point);
791 self.atlas.clear_textures(AtlasTextureKind::Path);
792
793 self.wait_for_gpu();
794 self.last_sync_point = Some(sync_point);
795 }
796}