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