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