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