blade_renderer.rs

   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}