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