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