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