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