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