1// Doing `if let` gives you nice scoping with passes/encoders
2#![allow(irrefutable_let_patterns)]
3
4use super::{BladeAtlas, BladeContext};
5use crate::{
6 Background, Bounds, DevicePixels, GpuSpecs, MonochromeSprite, Path, Point, PolychromeSprite,
7 PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size, Underline,
8};
9use blade_graphics as gpu;
10use blade_util::{BufferBelt, BufferBeltDescriptor};
11use bytemuck::{Pod, Zeroable};
12#[cfg(target_os = "macos")]
13use media::core_video::CVMetalTextureCache;
14use std::sync::Arc;
15
16const MAX_FRAME_TIME_MS: u32 = 10000;
17
18#[repr(C)]
19#[derive(Clone, Copy, Pod, Zeroable)]
20struct GlobalParams {
21 viewport_size: [f32; 2],
22 premultiplied_alpha: u32,
23 pad: u32,
24}
25
26//Note: we can't use `Bounds` directly here because
27// it doesn't implement Pod + Zeroable
28#[repr(C)]
29#[derive(Clone, Copy, Pod, Zeroable)]
30struct PodBounds {
31 origin: [f32; 2],
32 size: [f32; 2],
33}
34
35impl From<Bounds<ScaledPixels>> for PodBounds {
36 fn from(bounds: Bounds<ScaledPixels>) -> Self {
37 Self {
38 origin: [bounds.origin.x.0, bounds.origin.y.0],
39 size: [bounds.size.width.0, bounds.size.height.0],
40 }
41 }
42}
43
44#[repr(C)]
45#[derive(Clone, Copy, Pod, Zeroable)]
46struct SurfaceParams {
47 bounds: PodBounds,
48 content_mask: PodBounds,
49}
50
51#[derive(blade_macros::ShaderData)]
52struct ShaderQuadsData {
53 globals: GlobalParams,
54 b_quads: gpu::BufferPiece,
55}
56
57#[derive(blade_macros::ShaderData)]
58struct ShaderShadowsData {
59 globals: GlobalParams,
60 b_shadows: gpu::BufferPiece,
61}
62
63#[derive(blade_macros::ShaderData)]
64struct ShaderPathRasterizationData {
65 globals: GlobalParams,
66 b_path_vertices: gpu::BufferPiece,
67}
68
69#[derive(blade_macros::ShaderData)]
70struct ShaderPathsData {
71 globals: GlobalParams,
72 t_sprite: gpu::TextureView,
73 s_sprite: gpu::Sampler,
74 b_path_sprites: gpu::BufferPiece,
75}
76
77#[derive(blade_macros::ShaderData)]
78struct ShaderUnderlinesData {
79 globals: GlobalParams,
80 b_underlines: gpu::BufferPiece,
81}
82
83#[derive(blade_macros::ShaderData)]
84struct ShaderMonoSpritesData {
85 globals: GlobalParams,
86 t_sprite: gpu::TextureView,
87 s_sprite: gpu::Sampler,
88 b_mono_sprites: gpu::BufferPiece,
89}
90
91#[derive(blade_macros::ShaderData)]
92struct ShaderPolySpritesData {
93 globals: GlobalParams,
94 t_sprite: gpu::TextureView,
95 s_sprite: gpu::Sampler,
96 b_poly_sprites: gpu::BufferPiece,
97}
98
99#[derive(blade_macros::ShaderData)]
100struct ShaderSurfacesData {
101 globals: GlobalParams,
102 surface_locals: SurfaceParams,
103 t_y: gpu::TextureView,
104 t_cb_cr: gpu::TextureView,
105 s_surface: gpu::Sampler,
106}
107
108#[derive(Clone, Debug, Eq, PartialEq)]
109#[repr(C)]
110struct PathSprite {
111 bounds: Bounds<ScaledPixels>,
112}
113
114#[derive(Clone, Debug)]
115#[repr(C)]
116struct PathRasterizationVertex {
117 xy_position: Point<ScaledPixels>,
118 st_position: Point<f32>,
119 color: Background,
120 bounds: Bounds<ScaledPixels>,
121}
122
123struct BladePipelines {
124 quads: gpu::RenderPipeline,
125 shadows: gpu::RenderPipeline,
126 path_rasterization: gpu::RenderPipeline,
127 paths: gpu::RenderPipeline,
128 underlines: gpu::RenderPipeline,
129 mono_sprites: gpu::RenderPipeline,
130 poly_sprites: gpu::RenderPipeline,
131 surfaces: gpu::RenderPipeline,
132}
133
134impl BladePipelines {
135 fn new(gpu: &gpu::Context, surface_info: gpu::SurfaceInfo, path_sample_count: u32) -> Self {
136 use gpu::ShaderData as _;
137
138 log::info!(
139 "Initializing Blade pipelines for surface {:?}",
140 surface_info
141 );
142 let shader = gpu.create_shader(gpu::ShaderDesc {
143 source: include_str!("shaders.wgsl"),
144 });
145 shader.check_struct_size::<GlobalParams>();
146 shader.check_struct_size::<SurfaceParams>();
147 shader.check_struct_size::<Quad>();
148 shader.check_struct_size::<Shadow>();
149 shader.check_struct_size::<PathRasterizationVertex>();
150 shader.check_struct_size::<PathSprite>();
151 shader.check_struct_size::<Underline>();
152 shader.check_struct_size::<MonochromeSprite>();
153 shader.check_struct_size::<PolychromeSprite>();
154
155 // See https://apoorvaj.io/alpha-compositing-opengl-blending-and-premultiplied-alpha/
156 let blend_mode = match surface_info.alpha {
157 gpu::AlphaMode::Ignored => gpu::BlendState::ALPHA_BLENDING,
158 gpu::AlphaMode::PreMultiplied => gpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING,
159 gpu::AlphaMode::PostMultiplied => gpu::BlendState::ALPHA_BLENDING,
160 };
161 let color_targets = &[gpu::ColorTargetState {
162 format: surface_info.format,
163 blend: Some(blend_mode),
164 write_mask: gpu::ColorWrites::default(),
165 }];
166
167 Self {
168 quads: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
169 name: "quads",
170 data_layouts: &[&ShaderQuadsData::layout()],
171 vertex: shader.at("vs_quad"),
172 vertex_fetches: &[],
173 primitive: gpu::PrimitiveState {
174 topology: gpu::PrimitiveTopology::TriangleStrip,
175 ..Default::default()
176 },
177 depth_stencil: None,
178 fragment: Some(shader.at("fs_quad")),
179 color_targets,
180 multisample_state: gpu::MultisampleState::default(),
181 }),
182 shadows: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
183 name: "shadows",
184 data_layouts: &[&ShaderShadowsData::layout()],
185 vertex: shader.at("vs_shadow"),
186 vertex_fetches: &[],
187 primitive: gpu::PrimitiveState {
188 topology: gpu::PrimitiveTopology::TriangleStrip,
189 ..Default::default()
190 },
191 depth_stencil: None,
192 fragment: Some(shader.at("fs_shadow")),
193 color_targets,
194 multisample_state: gpu::MultisampleState::default(),
195 }),
196 path_rasterization: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
197 name: "path_rasterization",
198 data_layouts: &[&ShaderPathRasterizationData::layout()],
199 vertex: shader.at("vs_path_rasterization"),
200 vertex_fetches: &[],
201 primitive: gpu::PrimitiveState {
202 topology: gpu::PrimitiveTopology::TriangleList,
203 ..Default::default()
204 },
205 depth_stencil: None,
206 fragment: Some(shader.at("fs_path_rasterization")),
207 // The original implementation was using ADDITIVE blende mode,
208 // I don't know why
209 // color_targets: &[gpu::ColorTargetState {
210 // format: PATH_TEXTURE_FORMAT,
211 // blend: Some(gpu::BlendState::ADDITIVE),
212 // write_mask: gpu::ColorWrites::default(),
213 // }],
214 color_targets: &[gpu::ColorTargetState {
215 format: surface_info.format,
216 blend: Some(gpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
217 write_mask: gpu::ColorWrites::default(),
218 }],
219 multisample_state: gpu::MultisampleState {
220 sample_count: path_sample_count,
221 ..Default::default()
222 },
223 }),
224 paths: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
225 name: "paths",
226 data_layouts: &[&ShaderPathsData::layout()],
227 vertex: shader.at("vs_path"),
228 vertex_fetches: &[],
229 primitive: gpu::PrimitiveState {
230 topology: gpu::PrimitiveTopology::TriangleStrip,
231 ..Default::default()
232 },
233 depth_stencil: None,
234 fragment: Some(shader.at("fs_path")),
235 color_targets: &[gpu::ColorTargetState {
236 format: surface_info.format,
237 blend: Some(gpu::BlendState {
238 color: gpu::BlendComponent::OVER,
239 alpha: gpu::BlendComponent::ADDITIVE,
240 }),
241 write_mask: gpu::ColorWrites::default(),
242 }],
243 multisample_state: gpu::MultisampleState::default(),
244 }),
245 underlines: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
246 name: "underlines",
247 data_layouts: &[&ShaderUnderlinesData::layout()],
248 vertex: shader.at("vs_underline"),
249 vertex_fetches: &[],
250 primitive: gpu::PrimitiveState {
251 topology: gpu::PrimitiveTopology::TriangleStrip,
252 ..Default::default()
253 },
254 depth_stencil: None,
255 fragment: Some(shader.at("fs_underline")),
256 color_targets,
257 multisample_state: gpu::MultisampleState::default(),
258 }),
259 mono_sprites: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
260 name: "mono-sprites",
261 data_layouts: &[&ShaderMonoSpritesData::layout()],
262 vertex: shader.at("vs_mono_sprite"),
263 vertex_fetches: &[],
264 primitive: gpu::PrimitiveState {
265 topology: gpu::PrimitiveTopology::TriangleStrip,
266 ..Default::default()
267 },
268 depth_stencil: None,
269 fragment: Some(shader.at("fs_mono_sprite")),
270 color_targets,
271 multisample_state: gpu::MultisampleState::default(),
272 }),
273 poly_sprites: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
274 name: "poly-sprites",
275 data_layouts: &[&ShaderPolySpritesData::layout()],
276 vertex: shader.at("vs_poly_sprite"),
277 vertex_fetches: &[],
278 primitive: gpu::PrimitiveState {
279 topology: gpu::PrimitiveTopology::TriangleStrip,
280 ..Default::default()
281 },
282 depth_stencil: None,
283 fragment: Some(shader.at("fs_poly_sprite")),
284 color_targets,
285 multisample_state: gpu::MultisampleState::default(),
286 }),
287 surfaces: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
288 name: "surfaces",
289 data_layouts: &[&ShaderSurfacesData::layout()],
290 vertex: shader.at("vs_surface"),
291 vertex_fetches: &[],
292 primitive: gpu::PrimitiveState {
293 topology: gpu::PrimitiveTopology::TriangleStrip,
294 ..Default::default()
295 },
296 depth_stencil: None,
297 fragment: Some(shader.at("fs_surface")),
298 color_targets,
299 multisample_state: gpu::MultisampleState::default(),
300 }),
301 }
302 }
303
304 fn destroy(&mut self, gpu: &gpu::Context) {
305 gpu.destroy_render_pipeline(&mut self.quads);
306 gpu.destroy_render_pipeline(&mut self.shadows);
307 gpu.destroy_render_pipeline(&mut self.path_rasterization);
308 gpu.destroy_render_pipeline(&mut self.paths);
309 gpu.destroy_render_pipeline(&mut self.underlines);
310 gpu.destroy_render_pipeline(&mut self.mono_sprites);
311 gpu.destroy_render_pipeline(&mut self.poly_sprites);
312 gpu.destroy_render_pipeline(&mut self.surfaces);
313 }
314}
315
316pub struct BladeSurfaceConfig {
317 pub size: gpu::Extent,
318 pub transparent: bool,
319}
320
321//Note: we could see some of these fields moved into `BladeContext`
322// so that they are shared between windows. E.g. `pipelines`.
323// But that is complicated by the fact that pipelines depend on
324// the format and alpha mode.
325pub struct BladeRenderer {
326 gpu: Arc<gpu::Context>,
327 surface: gpu::Surface,
328 surface_config: gpu::SurfaceConfig,
329 command_encoder: gpu::CommandEncoder,
330 last_sync_point: Option<gpu::SyncPoint>,
331 pipelines: BladePipelines,
332 instance_belt: BufferBelt,
333 atlas: Arc<BladeAtlas>,
334 atlas_sampler: gpu::Sampler,
335 #[cfg(target_os = "macos")]
336 core_video_texture_cache: CVMetalTextureCache,
337 path_sample_count: u32,
338 path_intermediate_texture: gpu::Texture,
339 path_intermediate_texture_view: gpu::TextureView,
340 path_intermediate_msaa_texture: Option<gpu::Texture>,
341 path_intermediate_msaa_texture_view: Option<gpu::TextureView>,
342}
343
344impl BladeRenderer {
345 pub fn new<I: raw_window_handle::HasWindowHandle + raw_window_handle::HasDisplayHandle>(
346 context: &BladeContext,
347 window: &I,
348 config: BladeSurfaceConfig,
349 ) -> anyhow::Result<Self> {
350 let surface_config = gpu::SurfaceConfig {
351 size: config.size,
352 usage: gpu::TextureUsage::TARGET,
353 display_sync: gpu::DisplaySync::Recent,
354 color_space: gpu::ColorSpace::Linear,
355 allow_exclusive_full_screen: false,
356 transparent: config.transparent,
357 };
358 let surface = context
359 .gpu
360 .create_surface_configured(window, surface_config)
361 .map_err(|err| anyhow::anyhow!("Failed to create surface: {err:?}"))?;
362
363 let command_encoder = context.gpu.create_command_encoder(gpu::CommandEncoderDesc {
364 name: "main",
365 buffer_count: 2,
366 });
367 // workaround for https://github.com/zed-industries/zed/issues/26143
368 let path_sample_count = std::env::var("ZED_PATH_SAMPLE_COUNT")
369 .ok()
370 .and_then(|v| v.parse().ok())
371 .or_else(|| {
372 [4, 2, 1]
373 .into_iter()
374 .find(|count| context.gpu.supports_texture_sample_count(*count))
375 })
376 .unwrap_or(1);
377 let pipelines = BladePipelines::new(&context.gpu, surface.info(), path_sample_count);
378 let instance_belt = BufferBelt::new(BufferBeltDescriptor {
379 memory: gpu::Memory::Shared,
380 min_chunk_size: 0x1000,
381 alignment: 0x40, // Vulkan `minStorageBufferOffsetAlignment` on Intel Xe
382 });
383 let atlas = Arc::new(BladeAtlas::new(&context.gpu));
384 let atlas_sampler = context.gpu.create_sampler(gpu::SamplerDesc {
385 name: "path rasterization sampler",
386 mag_filter: gpu::FilterMode::Linear,
387 min_filter: gpu::FilterMode::Linear,
388 ..Default::default()
389 });
390
391 let (path_intermediate_texture, path_intermediate_texture_view) =
392 create_path_intermediate_texture(
393 &context.gpu,
394 surface.info().format,
395 config.size.width,
396 config.size.height,
397 );
398 let (path_intermediate_msaa_texture, path_intermediate_msaa_texture_view) =
399 create_msaa_texture_if_needed(
400 &context.gpu,
401 surface.info().format,
402 config.size.width,
403 config.size.height,
404 path_sample_count,
405 )
406 .unzip();
407
408 #[cfg(target_os = "macos")]
409 let core_video_texture_cache = unsafe {
410 CVMetalTextureCache::new(
411 objc2::rc::Retained::as_ptr(&context.gpu.metal_device()) as *mut _
412 )
413 .unwrap()
414 };
415
416 Ok(Self {
417 gpu: Arc::clone(&context.gpu),
418 surface,
419 surface_config,
420 command_encoder,
421 last_sync_point: None,
422 pipelines,
423 instance_belt,
424 atlas,
425 atlas_sampler,
426 #[cfg(target_os = "macos")]
427 core_video_texture_cache,
428 path_sample_count,
429 path_intermediate_texture,
430 path_intermediate_texture_view,
431 path_intermediate_msaa_texture,
432 path_intermediate_msaa_texture_view,
433 })
434 }
435
436 fn wait_for_gpu(&mut self) {
437 if let Some(last_sp) = self.last_sync_point.take()
438 && !self.gpu.wait_for(&last_sp, MAX_FRAME_TIME_MS) {
439 log::error!("GPU hung");
440 #[cfg(target_os = "linux")]
441 if self.gpu.device_information().driver_name == "radv" {
442 log::error!(
443 "there's a known bug with amdgpu/radv, try setting ZED_PATH_SAMPLE_COUNT=0 as a workaround"
444 );
445 log::error!(
446 "if that helps you're running into https://github.com/zed-industries/zed/issues/26143"
447 );
448 }
449 log::error!(
450 "your device information is: {:?}",
451 self.gpu.device_information()
452 );
453 while !self.gpu.wait_for(&last_sp, MAX_FRAME_TIME_MS) {}
454 }
455 }
456
457 pub fn update_drawable_size(&mut self, size: Size<DevicePixels>) {
458 self.update_drawable_size_impl(size, false);
459 }
460
461 /// Like `update_drawable_size` but skips the check that the size has changed. This is useful in
462 /// cases like restoring a window from minimization where the size is the same but the
463 /// renderer's swap chain needs to be recreated.
464 #[cfg_attr(
465 any(target_os = "macos", target_os = "linux", target_os = "freebsd"),
466 allow(dead_code)
467 )]
468 pub fn update_drawable_size_even_if_unchanged(&mut self, size: Size<DevicePixels>) {
469 self.update_drawable_size_impl(size, true);
470 }
471
472 fn update_drawable_size_impl(&mut self, size: Size<DevicePixels>, always_resize: bool) {
473 let gpu_size = gpu::Extent {
474 width: size.width.0 as u32,
475 height: size.height.0 as u32,
476 depth: 1,
477 };
478
479 if always_resize || gpu_size != self.surface_config.size {
480 self.wait_for_gpu();
481 self.surface_config.size = gpu_size;
482 self.gpu
483 .reconfigure_surface(&mut self.surface, self.surface_config);
484 self.gpu.destroy_texture(self.path_intermediate_texture);
485 self.gpu
486 .destroy_texture_view(self.path_intermediate_texture_view);
487 if let Some(msaa_texture) = self.path_intermediate_msaa_texture {
488 self.gpu.destroy_texture(msaa_texture);
489 }
490 if let Some(msaa_view) = self.path_intermediate_msaa_texture_view {
491 self.gpu.destroy_texture_view(msaa_view);
492 }
493 let (path_intermediate_texture, path_intermediate_texture_view) =
494 create_path_intermediate_texture(
495 &self.gpu,
496 self.surface.info().format,
497 gpu_size.width,
498 gpu_size.height,
499 );
500 self.path_intermediate_texture = path_intermediate_texture;
501 self.path_intermediate_texture_view = path_intermediate_texture_view;
502 let (path_intermediate_msaa_texture, path_intermediate_msaa_texture_view) =
503 create_msaa_texture_if_needed(
504 &self.gpu,
505 self.surface.info().format,
506 gpu_size.width,
507 gpu_size.height,
508 self.path_sample_count,
509 )
510 .unzip();
511 self.path_intermediate_msaa_texture = path_intermediate_msaa_texture;
512 self.path_intermediate_msaa_texture_view = path_intermediate_msaa_texture_view;
513 }
514 }
515
516 pub fn update_transparency(&mut self, transparent: bool) {
517 if transparent != self.surface_config.transparent {
518 self.wait_for_gpu();
519 self.surface_config.transparent = transparent;
520 self.gpu
521 .reconfigure_surface(&mut self.surface, self.surface_config);
522 self.pipelines.destroy(&self.gpu);
523 self.pipelines =
524 BladePipelines::new(&self.gpu, self.surface.info(), self.path_sample_count);
525 }
526 }
527
528 #[cfg_attr(
529 any(target_os = "macos", feature = "wayland", target_os = "windows"),
530 allow(dead_code)
531 )]
532 pub fn viewport_size(&self) -> gpu::Extent {
533 self.surface_config.size
534 }
535
536 pub fn sprite_atlas(&self) -> &Arc<BladeAtlas> {
537 &self.atlas
538 }
539
540 #[cfg_attr(target_os = "macos", allow(dead_code))]
541 pub fn gpu_specs(&self) -> GpuSpecs {
542 let info = self.gpu.device_information();
543
544 GpuSpecs {
545 is_software_emulated: info.is_software_emulated,
546 device_name: info.device_name.clone(),
547 driver_name: info.driver_name.clone(),
548 driver_info: info.driver_info.clone(),
549 }
550 }
551
552 #[cfg(target_os = "macos")]
553 pub fn layer(&self) -> metal::MetalLayer {
554 unsafe { foreign_types::ForeignType::from_ptr(self.layer_ptr()) }
555 }
556
557 #[cfg(target_os = "macos")]
558 pub fn layer_ptr(&self) -> *mut metal::CAMetalLayer {
559 objc2::rc::Retained::as_ptr(&self.surface.metal_layer()) as *mut _
560 }
561
562 #[profiling::function]
563 fn draw_paths_to_intermediate(
564 &mut self,
565 paths: &[Path<ScaledPixels>],
566 width: f32,
567 height: f32,
568 ) {
569 self.command_encoder
570 .init_texture(self.path_intermediate_texture);
571 if let Some(msaa_texture) = self.path_intermediate_msaa_texture {
572 self.command_encoder.init_texture(msaa_texture);
573 }
574
575 let target = if let Some(msaa_view) = self.path_intermediate_msaa_texture_view {
576 gpu::RenderTarget {
577 view: msaa_view,
578 init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
579 finish_op: gpu::FinishOp::ResolveTo(self.path_intermediate_texture_view),
580 }
581 } else {
582 gpu::RenderTarget {
583 view: self.path_intermediate_texture_view,
584 init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
585 finish_op: gpu::FinishOp::Store,
586 }
587 };
588 if let mut pass = self.command_encoder.render(
589 "rasterize paths",
590 gpu::RenderTargetSet {
591 colors: &[target],
592 depth_stencil: None,
593 },
594 ) {
595 let globals = GlobalParams {
596 viewport_size: [width, height],
597 premultiplied_alpha: 0,
598 pad: 0,
599 };
600 let mut encoder = pass.with(&self.pipelines.path_rasterization);
601
602 let mut vertices = Vec::new();
603 for path in paths {
604 vertices.extend(path.vertices.iter().map(|v| PathRasterizationVertex {
605 xy_position: v.xy_position,
606 st_position: v.st_position,
607 color: path.color,
608 bounds: path.clipped_bounds(),
609 }));
610 }
611 let vertex_buf = unsafe { self.instance_belt.alloc_typed(&vertices, &self.gpu) };
612 encoder.bind(
613 0,
614 &ShaderPathRasterizationData {
615 globals,
616 b_path_vertices: vertex_buf,
617 },
618 );
619 encoder.draw(0, vertices.len() as u32, 0, 1);
620 }
621 }
622
623 pub fn destroy(&mut self) {
624 self.wait_for_gpu();
625 self.atlas.destroy();
626 self.gpu.destroy_sampler(self.atlas_sampler);
627 self.instance_belt.destroy(&self.gpu);
628 self.gpu.destroy_command_encoder(&mut self.command_encoder);
629 self.pipelines.destroy(&self.gpu);
630 self.gpu.destroy_surface(&mut self.surface);
631 self.gpu.destroy_texture(self.path_intermediate_texture);
632 self.gpu
633 .destroy_texture_view(self.path_intermediate_texture_view);
634 if let Some(msaa_texture) = self.path_intermediate_msaa_texture {
635 self.gpu.destroy_texture(msaa_texture);
636 }
637 if let Some(msaa_view) = self.path_intermediate_msaa_texture_view {
638 self.gpu.destroy_texture_view(msaa_view);
639 }
640 }
641
642 pub fn draw(&mut self, scene: &Scene) {
643 self.command_encoder.start();
644 self.atlas.before_frame(&mut self.command_encoder);
645
646 let frame = {
647 profiling::scope!("acquire frame");
648 self.surface.acquire_frame()
649 };
650 self.command_encoder.init_texture(frame.texture());
651
652 let globals = GlobalParams {
653 viewport_size: [
654 self.surface_config.size.width as f32,
655 self.surface_config.size.height as f32,
656 ],
657 premultiplied_alpha: match self.surface.info().alpha {
658 gpu::AlphaMode::Ignored | gpu::AlphaMode::PostMultiplied => 0,
659 gpu::AlphaMode::PreMultiplied => 1,
660 },
661 pad: 0,
662 };
663
664 let mut pass = self.command_encoder.render(
665 "main",
666 gpu::RenderTargetSet {
667 colors: &[gpu::RenderTarget {
668 view: frame.texture_view(),
669 init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
670 finish_op: gpu::FinishOp::Store,
671 }],
672 depth_stencil: None,
673 },
674 );
675
676 profiling::scope!("render pass");
677 for batch in scene.batches() {
678 match batch {
679 PrimitiveBatch::Quads(quads) => {
680 let instance_buf = unsafe { self.instance_belt.alloc_typed(quads, &self.gpu) };
681 let mut encoder = pass.with(&self.pipelines.quads);
682 encoder.bind(
683 0,
684 &ShaderQuadsData {
685 globals,
686 b_quads: instance_buf,
687 },
688 );
689 encoder.draw(0, 4, 0, quads.len() as u32);
690 }
691 PrimitiveBatch::Shadows(shadows) => {
692 let instance_buf =
693 unsafe { self.instance_belt.alloc_typed(shadows, &self.gpu) };
694 let mut encoder = pass.with(&self.pipelines.shadows);
695 encoder.bind(
696 0,
697 &ShaderShadowsData {
698 globals,
699 b_shadows: instance_buf,
700 },
701 );
702 encoder.draw(0, 4, 0, shadows.len() as u32);
703 }
704 PrimitiveBatch::Paths(paths) => {
705 let Some(first_path) = paths.first() else {
706 continue;
707 };
708 drop(pass);
709 self.draw_paths_to_intermediate(
710 paths,
711 self.surface_config.size.width as f32,
712 self.surface_config.size.height as f32,
713 );
714 pass = self.command_encoder.render(
715 "main",
716 gpu::RenderTargetSet {
717 colors: &[gpu::RenderTarget {
718 view: frame.texture_view(),
719 init_op: gpu::InitOp::Load,
720 finish_op: gpu::FinishOp::Store,
721 }],
722 depth_stencil: None,
723 },
724 );
725 let mut encoder = pass.with(&self.pipelines.paths);
726 // When copying paths from the intermediate texture to the drawable,
727 // each pixel must only be copied once, in case of transparent paths.
728 //
729 // If all paths have the same draw order, then their bounds are all
730 // disjoint, so we can copy each path's bounds individually. If this
731 // batch combines different draw orders, we perform a single copy
732 // for a minimal spanning rect.
733 let sprites = if paths.last().unwrap().order == first_path.order {
734 paths
735 .iter()
736 .map(|path| PathSprite {
737 bounds: path.clipped_bounds(),
738 })
739 .collect()
740 } else {
741 let mut bounds = first_path.clipped_bounds();
742 for path in paths.iter().skip(1) {
743 bounds = bounds.union(&path.clipped_bounds());
744 }
745 vec![PathSprite { bounds }]
746 };
747 let instance_buf =
748 unsafe { self.instance_belt.alloc_typed(&sprites, &self.gpu) };
749 encoder.bind(
750 0,
751 &ShaderPathsData {
752 globals,
753 t_sprite: self.path_intermediate_texture_view,
754 s_sprite: self.atlas_sampler,
755 b_path_sprites: instance_buf,
756 },
757 );
758 encoder.draw(0, 4, 0, sprites.len() as u32);
759 }
760 PrimitiveBatch::Underlines(underlines) => {
761 let instance_buf =
762 unsafe { self.instance_belt.alloc_typed(underlines, &self.gpu) };
763 let mut encoder = pass.with(&self.pipelines.underlines);
764 encoder.bind(
765 0,
766 &ShaderUnderlinesData {
767 globals,
768 b_underlines: instance_buf,
769 },
770 );
771 encoder.draw(0, 4, 0, underlines.len() as u32);
772 }
773 PrimitiveBatch::MonochromeSprites {
774 texture_id,
775 sprites,
776 } => {
777 let tex_info = self.atlas.get_texture_info(texture_id);
778 let instance_buf =
779 unsafe { self.instance_belt.alloc_typed(sprites, &self.gpu) };
780 let mut encoder = pass.with(&self.pipelines.mono_sprites);
781 encoder.bind(
782 0,
783 &ShaderMonoSpritesData {
784 globals,
785 t_sprite: tex_info.raw_view,
786 s_sprite: self.atlas_sampler,
787 b_mono_sprites: instance_buf,
788 },
789 );
790 encoder.draw(0, 4, 0, sprites.len() as u32);
791 }
792 PrimitiveBatch::PolychromeSprites {
793 texture_id,
794 sprites,
795 } => {
796 let tex_info = self.atlas.get_texture_info(texture_id);
797 let instance_buf =
798 unsafe { self.instance_belt.alloc_typed(sprites, &self.gpu) };
799 let mut encoder = pass.with(&self.pipelines.poly_sprites);
800 encoder.bind(
801 0,
802 &ShaderPolySpritesData {
803 globals,
804 t_sprite: tex_info.raw_view,
805 s_sprite: self.atlas_sampler,
806 b_poly_sprites: instance_buf,
807 },
808 );
809 encoder.draw(0, 4, 0, sprites.len() as u32);
810 }
811 PrimitiveBatch::Surfaces(surfaces) => {
812 let mut _encoder = pass.with(&self.pipelines.surfaces);
813
814 for surface in surfaces {
815 #[cfg(not(target_os = "macos"))]
816 {
817 let _ = surface;
818 continue;
819 };
820
821 #[cfg(target_os = "macos")]
822 {
823 let (t_y, t_cb_cr) = unsafe {
824 use core_foundation::base::TCFType as _;
825 use std::ptr;
826
827 assert_eq!(
828 surface.image_buffer.get_pixel_format(),
829 core_video::pixel_buffer::kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
830 );
831
832 let y_texture = self
833 .core_video_texture_cache
834 .create_texture_from_image(
835 surface.image_buffer.as_concrete_TypeRef(),
836 ptr::null(),
837 metal::MTLPixelFormat::R8Unorm,
838 surface.image_buffer.get_width_of_plane(0),
839 surface.image_buffer.get_height_of_plane(0),
840 0,
841 )
842 .unwrap();
843 let cb_cr_texture = self
844 .core_video_texture_cache
845 .create_texture_from_image(
846 surface.image_buffer.as_concrete_TypeRef(),
847 ptr::null(),
848 metal::MTLPixelFormat::RG8Unorm,
849 surface.image_buffer.get_width_of_plane(1),
850 surface.image_buffer.get_height_of_plane(1),
851 1,
852 )
853 .unwrap();
854 (
855 gpu::TextureView::from_metal_texture(
856 &objc2::rc::Retained::retain(
857 foreign_types::ForeignTypeRef::as_ptr(
858 y_texture.as_texture_ref(),
859 )
860 as *mut objc2::runtime::ProtocolObject<
861 dyn objc2_metal::MTLTexture,
862 >,
863 )
864 .unwrap(),
865 gpu::TexelAspects::COLOR,
866 ),
867 gpu::TextureView::from_metal_texture(
868 &objc2::rc::Retained::retain(
869 foreign_types::ForeignTypeRef::as_ptr(
870 cb_cr_texture.as_texture_ref(),
871 )
872 as *mut objc2::runtime::ProtocolObject<
873 dyn objc2_metal::MTLTexture,
874 >,
875 )
876 .unwrap(),
877 gpu::TexelAspects::COLOR,
878 ),
879 )
880 };
881
882 _encoder.bind(
883 0,
884 &ShaderSurfacesData {
885 globals,
886 surface_locals: SurfaceParams {
887 bounds: surface.bounds.into(),
888 content_mask: surface.content_mask.bounds.into(),
889 },
890 t_y,
891 t_cb_cr,
892 s_surface: self.atlas_sampler,
893 },
894 );
895
896 _encoder.draw(0, 4, 0, 1);
897 }
898 }
899 }
900 }
901 }
902 drop(pass);
903
904 self.command_encoder.present(frame);
905 let sync_point = self.gpu.submit(&mut self.command_encoder);
906
907 profiling::scope!("finish");
908 self.instance_belt.flush(&sync_point);
909 self.atlas.after_frame(&sync_point);
910
911 self.wait_for_gpu();
912 self.last_sync_point = Some(sync_point);
913 }
914}
915
916fn create_path_intermediate_texture(
917 gpu: &gpu::Context,
918 format: gpu::TextureFormat,
919 width: u32,
920 height: u32,
921) -> (gpu::Texture, gpu::TextureView) {
922 let texture = gpu.create_texture(gpu::TextureDesc {
923 name: "path intermediate",
924 format,
925 size: gpu::Extent {
926 width,
927 height,
928 depth: 1,
929 },
930 array_layer_count: 1,
931 mip_level_count: 1,
932 sample_count: 1,
933 dimension: gpu::TextureDimension::D2,
934 usage: gpu::TextureUsage::COPY | gpu::TextureUsage::RESOURCE | gpu::TextureUsage::TARGET,
935 external: None,
936 });
937 let texture_view = gpu.create_texture_view(
938 texture,
939 gpu::TextureViewDesc {
940 name: "path intermediate view",
941 format,
942 dimension: gpu::ViewDimension::D2,
943 subresources: &Default::default(),
944 },
945 );
946 (texture, texture_view)
947}
948
949fn create_msaa_texture_if_needed(
950 gpu: &gpu::Context,
951 format: gpu::TextureFormat,
952 width: u32,
953 height: u32,
954 sample_count: u32,
955) -> Option<(gpu::Texture, gpu::TextureView)> {
956 if sample_count <= 1 {
957 return None;
958 }
959 let texture_msaa = gpu.create_texture(gpu::TextureDesc {
960 name: "path intermediate msaa",
961 format,
962 size: gpu::Extent {
963 width,
964 height,
965 depth: 1,
966 },
967 array_layer_count: 1,
968 mip_level_count: 1,
969 sample_count,
970 dimension: gpu::TextureDimension::D2,
971 usage: gpu::TextureUsage::TARGET,
972 external: None,
973 });
974 let texture_view_msaa = gpu.create_texture_view(
975 texture_msaa,
976 gpu::TextureViewDesc {
977 name: "path intermediate msaa view",
978 format,
979 dimension: gpu::ViewDimension::D2,
980 subresources: &Default::default(),
981 },
982 );
983
984 Some((texture_msaa, texture_view_msaa))
985}