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 fn rasterize_paths(&mut self, paths: &[Path<ScaledPixels>]) {
448 self.path_tiles.clear();
449 let mut vertices_by_texture_id = HashMap::default();
450
451 for path in paths {
452 let clipped_bounds = path.bounds.intersect(&path.content_mask.bounds);
453 let tile = self
454 .atlas
455 .allocate(clipped_bounds.size.map(Into::into), AtlasTextureKind::Path);
456 vertices_by_texture_id
457 .entry(tile.texture_id)
458 .or_insert(Vec::new())
459 .extend(path.vertices.iter().map(|vertex| PathVertex {
460 xy_position: vertex.xy_position - clipped_bounds.origin
461 + tile.bounds.origin.map(Into::into),
462 st_position: vertex.st_position,
463 content_mask: ContentMask {
464 bounds: tile.bounds.map(Into::into),
465 },
466 }));
467 self.path_tiles.insert(path.id, tile);
468 }
469
470 for (texture_id, vertices) in vertices_by_texture_id {
471 let tex_info = self.atlas.get_texture_info(texture_id);
472 let globals = GlobalParams {
473 viewport_size: [tex_info.size.width as f32, tex_info.size.height as f32],
474 pad: [0; 2],
475 };
476
477 let vertex_buf = unsafe { self.instance_belt.alloc_data(&vertices, &self.gpu) };
478 let mut pass = self.command_encoder.render(gpu::RenderTargetSet {
479 colors: &[gpu::RenderTarget {
480 view: tex_info.raw_view,
481 init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
482 finish_op: gpu::FinishOp::Store,
483 }],
484 depth_stencil: None,
485 });
486
487 let mut encoder = pass.with(&self.pipelines.path_rasterization);
488 encoder.bind(
489 0,
490 &ShaderPathRasterizationData {
491 globals,
492 b_path_vertices: vertex_buf,
493 },
494 );
495 encoder.draw(0, vertices.len() as u32, 0, 1);
496 }
497 }
498
499 pub fn destroy(&mut self) {
500 self.wait_for_gpu();
501 self.atlas.destroy();
502 self.instance_belt.destroy(&self.gpu);
503 self.gpu.destroy_command_encoder(&mut self.command_encoder);
504 }
505
506 pub fn draw(&mut self, scene: &Scene) {
507 let frame = self.gpu.acquire_frame();
508 self.command_encoder.start();
509 self.command_encoder.init_texture(frame.texture());
510
511 self.atlas.before_frame(&mut self.command_encoder);
512 self.rasterize_paths(scene.paths());
513
514 let globals = GlobalParams {
515 viewport_size: [
516 self.viewport_size.width as f32,
517 self.viewport_size.height as f32,
518 ],
519 pad: [0; 2],
520 };
521
522 if let mut pass = self.command_encoder.render(gpu::RenderTargetSet {
523 colors: &[gpu::RenderTarget {
524 view: frame.texture_view(),
525 init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
526 finish_op: gpu::FinishOp::Store,
527 }],
528 depth_stencil: None,
529 }) {
530 for batch in scene.batches() {
531 match batch {
532 PrimitiveBatch::Quads(quads) => {
533 let instance_buf =
534 unsafe { self.instance_belt.alloc_data(quads, &self.gpu) };
535 let mut encoder = pass.with(&self.pipelines.quads);
536 encoder.bind(
537 0,
538 &ShaderQuadsData {
539 globals,
540 b_quads: instance_buf,
541 },
542 );
543 encoder.draw(0, 4, 0, quads.len() as u32);
544 }
545 PrimitiveBatch::Shadows(shadows) => {
546 let instance_buf =
547 unsafe { self.instance_belt.alloc_data(shadows, &self.gpu) };
548 let mut encoder = pass.with(&self.pipelines.shadows);
549 encoder.bind(
550 0,
551 &ShaderShadowsData {
552 globals,
553 b_shadows: instance_buf,
554 },
555 );
556 encoder.draw(0, 4, 0, shadows.len() as u32);
557 }
558 PrimitiveBatch::Paths(paths) => {
559 let mut encoder = pass.with(&self.pipelines.paths);
560 //todo!(linux): group by texture ID
561 for path in paths {
562 let tile = &self.path_tiles[&path.id];
563 let tex_info = self.atlas.get_texture_info(tile.texture_id);
564 let origin = path.bounds.intersect(&path.content_mask.bounds).origin;
565 let sprites = [PathSprite {
566 bounds: Bounds {
567 origin: origin.map(|p| p.floor()),
568 size: tile.bounds.size.map(Into::into),
569 },
570 color: path.color,
571 tile: (*tile).clone(),
572 }];
573
574 let instance_buf =
575 unsafe { self.instance_belt.alloc_data(&sprites, &self.gpu) };
576 encoder.bind(
577 0,
578 &ShaderPathsData {
579 globals,
580 t_sprite: tex_info.raw_view,
581 s_sprite: self.atlas_sampler,
582 b_path_sprites: instance_buf,
583 },
584 );
585 encoder.draw(0, 4, 0, sprites.len() as u32);
586 }
587 }
588 PrimitiveBatch::Underlines(underlines) => {
589 let instance_buf =
590 unsafe { self.instance_belt.alloc_data(underlines, &self.gpu) };
591 let mut encoder = pass.with(&self.pipelines.underlines);
592 encoder.bind(
593 0,
594 &ShaderUnderlinesData {
595 globals,
596 b_underlines: instance_buf,
597 },
598 );
599 encoder.draw(0, 4, 0, underlines.len() as u32);
600 }
601 PrimitiveBatch::MonochromeSprites {
602 texture_id,
603 sprites,
604 } => {
605 let tex_info = self.atlas.get_texture_info(texture_id);
606 let instance_buf =
607 unsafe { self.instance_belt.alloc_data(sprites, &self.gpu) };
608 let mut encoder = pass.with(&self.pipelines.mono_sprites);
609 encoder.bind(
610 0,
611 &ShaderMonoSpritesData {
612 globals,
613 t_sprite: tex_info.raw_view,
614 s_sprite: self.atlas_sampler,
615 b_mono_sprites: instance_buf,
616 },
617 );
618 encoder.draw(0, 4, 0, sprites.len() as u32);
619 }
620 PrimitiveBatch::PolychromeSprites {
621 texture_id,
622 sprites,
623 } => {
624 let tex_info = self.atlas.get_texture_info(texture_id);
625 let instance_buf =
626 unsafe { self.instance_belt.alloc_data(sprites, &self.gpu) };
627 let mut encoder = pass.with(&self.pipelines.poly_sprites);
628 encoder.bind(
629 0,
630 &ShaderPolySpritesData {
631 globals,
632 t_sprite: tex_info.raw_view,
633 s_sprite: self.atlas_sampler,
634 b_poly_sprites: instance_buf,
635 },
636 );
637 encoder.draw(0, 4, 0, sprites.len() as u32);
638 }
639 PrimitiveBatch::Surfaces(surfaces) => {
640 let mut _encoder = pass.with(&self.pipelines.surfaces);
641
642 for surface in surfaces {
643 #[cfg(not(target_os = "macos"))]
644 {
645 let _ = surface;
646 continue;
647 };
648
649 #[cfg(target_os = "macos")]
650 {
651 let (t_y, t_cb_cr) = {
652 use core_foundation::base::TCFType as _;
653 use std::ptr;
654
655 assert_eq!(
656 surface.image_buffer.pixel_format_type(),
657 media::core_video::kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
658 );
659
660 let y_texture = unsafe {
661 self.core_video_texture_cache
662 .create_texture_from_image(
663 surface.image_buffer.as_concrete_TypeRef(),
664 ptr::null(),
665 metal::MTLPixelFormat::R8Unorm,
666 surface.image_buffer.plane_width(0),
667 surface.image_buffer.plane_height(0),
668 0,
669 )
670 .unwrap()
671 };
672 let cb_cr_texture = unsafe {
673 self.core_video_texture_cache
674 .create_texture_from_image(
675 surface.image_buffer.as_concrete_TypeRef(),
676 ptr::null(),
677 metal::MTLPixelFormat::RG8Unorm,
678 surface.image_buffer.plane_width(1),
679 surface.image_buffer.plane_height(1),
680 1,
681 )
682 .unwrap()
683 };
684 (
685 gpu::TextureView::from_metal_texture(
686 y_texture.as_texture_ref(),
687 ),
688 gpu::TextureView::from_metal_texture(
689 cb_cr_texture.as_texture_ref(),
690 ),
691 )
692 };
693
694 _encoder.bind(
695 0,
696 &ShaderSurfacesData {
697 globals,
698 surface_locals: SurfaceParams {
699 bounds: surface.bounds.into(),
700 content_mask: surface.content_mask.bounds.into(),
701 },
702 t_y,
703 t_cb_cr,
704 s_surface: self.atlas_sampler,
705 },
706 );
707
708 _encoder.draw(0, 4, 0, 1);
709 }
710 }
711 }
712 }
713 }
714 }
715
716 self.command_encoder.present(frame);
717 let sync_point = self.gpu.submit(&mut self.command_encoder);
718
719 self.instance_belt.flush(&sync_point);
720 self.atlas.after_frame(&sync_point);
721 self.atlas.clear_textures(AtlasTextureKind::Path);
722
723 self.wait_for_gpu();
724 self.last_sync_point = Some(sync_point);
725 }
726}