gpui: Improve path rendering & global multisample anti-aliasing (#29718)

Currently, the rendering path required creating a texture for each path,
which wasted a large amount of video memory. In our application, simply
drawing some charts resulted in video memory usage as high as 5G.

I removed the step of creating path textures and directly drew the paths
on the rendering target, adding post-processing global multi-sampling
anti-aliasing. Drawing paths no longer requires allocating any
additional video memory and also improves the performance of path
rendering.

Release Notes:

- N/A

---------

Co-authored-by: Jason Lee <huacnlee@gmail.com>
This commit is contained in:
Sunli 2025-07-03 00:41:42 +08:00 committed by GitHub
parent 9dc3ac9657
commit 4fdda8d5a1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 486 additions and 726 deletions

6
Cargo.lock generated
View file

@ -2076,7 +2076,7 @@ dependencies = [
[[package]] [[package]]
name = "blade-graphics" name = "blade-graphics"
version = "0.6.0" version = "0.6.0"
source = "git+https://github.com/kvark/blade?rev=e0ec4e720957edd51b945b64dd85605ea54bcfe5#e0ec4e720957edd51b945b64dd85605ea54bcfe5" source = "git+https://github.com/kvark/blade?rev=416375211bb0b5826b3584dccdb6a43369e499ad#416375211bb0b5826b3584dccdb6a43369e499ad"
dependencies = [ dependencies = [
"ash", "ash",
"ash-window", "ash-window",
@ -2109,7 +2109,7 @@ dependencies = [
[[package]] [[package]]
name = "blade-macros" name = "blade-macros"
version = "0.3.0" version = "0.3.0"
source = "git+https://github.com/kvark/blade?rev=e0ec4e720957edd51b945b64dd85605ea54bcfe5#e0ec4e720957edd51b945b64dd85605ea54bcfe5" source = "git+https://github.com/kvark/blade?rev=416375211bb0b5826b3584dccdb6a43369e499ad#416375211bb0b5826b3584dccdb6a43369e499ad"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2119,7 +2119,7 @@ dependencies = [
[[package]] [[package]]
name = "blade-util" name = "blade-util"
version = "0.2.0" version = "0.2.0"
source = "git+https://github.com/kvark/blade?rev=e0ec4e720957edd51b945b64dd85605ea54bcfe5#e0ec4e720957edd51b945b64dd85605ea54bcfe5" source = "git+https://github.com/kvark/blade?rev=416375211bb0b5826b3584dccdb6a43369e499ad#416375211bb0b5826b3584dccdb6a43369e499ad"
dependencies = [ dependencies = [
"blade-graphics", "blade-graphics",
"bytemuck", "bytemuck",

View file

@ -426,9 +426,9 @@ aws-smithy-runtime-api = { version = "1.7.4", features = ["http-1x", "client"] }
aws-smithy-types = { version = "1.3.0", features = ["http-body-1-x"] } aws-smithy-types = { version = "1.3.0", features = ["http-body-1-x"] }
base64 = "0.22" base64 = "0.22"
bitflags = "2.6.0" bitflags = "2.6.0"
blade-graphics = { git = "https://github.com/kvark/blade", rev = "e0ec4e720957edd51b945b64dd85605ea54bcfe5" } blade-graphics = { git = "https://github.com/kvark/blade", rev = "416375211bb0b5826b3584dccdb6a43369e499ad" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "e0ec4e720957edd51b945b64dd85605ea54bcfe5" } blade-macros = { git = "https://github.com/kvark/blade", rev = "416375211bb0b5826b3584dccdb6a43369e499ad" }
blade-util = { git = "https://github.com/kvark/blade", rev = "e0ec4e720957edd51b945b64dd85605ea54bcfe5" } blade-util = { git = "https://github.com/kvark/blade", rev = "416375211bb0b5826b3584dccdb6a43369e499ad" }
blake3 = "1.5.3" blake3 = "1.5.3"
bytes = "1.0" bytes = "1.0"
cargo_metadata = "0.19" cargo_metadata = "0.19"
@ -481,7 +481,7 @@ json_dotpath = "1.1"
jsonschema = "0.30.0" jsonschema = "0.30.0"
jsonwebtoken = "9.3" jsonwebtoken = "9.3"
jupyter-protocol = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" } jupyter-protocol = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
jupyter-websocket-client = { git = "https://github.com/ConradIrwin/runtimed" ,rev = "7130c804216b6914355d15d0b91ea91f6babd734" } jupyter-websocket-client = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
libc = "0.2" libc = "0.2"
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] } libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
linkify = "0.10.0" linkify = "0.10.0"

View file

@ -126,7 +126,7 @@ mod macos {
"ContentMask".into(), "ContentMask".into(),
"Uniforms".into(), "Uniforms".into(),
"AtlasTile".into(), "AtlasTile".into(),
"PathRasterizationInputIndex".into(), "PathInputIndex".into(),
"PathVertex_ScaledPixels".into(), "PathVertex_ScaledPixels".into(),
"ShadowInputIndex".into(), "ShadowInputIndex".into(),
"Shadow".into(), "Shadow".into(),

View file

@ -1,9 +1,13 @@
use gpui::{ use gpui::{
Application, Background, Bounds, ColorSpace, Context, MouseDownEvent, Path, PathBuilder, Application, Background, Bounds, ColorSpace, Context, MouseDownEvent, Path, PathBuilder,
PathStyle, Pixels, Point, Render, SharedString, StrokeOptions, Window, WindowOptions, canvas, PathStyle, Pixels, Point, Render, SharedString, StrokeOptions, Window, WindowBounds,
div, linear_color_stop, linear_gradient, point, prelude::*, px, rgb, size, WindowOptions, canvas, div, linear_color_stop, linear_gradient, point, prelude::*, px, rgb,
size,
}; };
const DEFAULT_WINDOW_WIDTH: Pixels = px(1024.0);
const DEFAULT_WINDOW_HEIGHT: Pixels = px(768.0);
struct PaintingViewer { struct PaintingViewer {
default_lines: Vec<(Path<Pixels>, Background)>, default_lines: Vec<(Path<Pixels>, Background)>,
lines: Vec<Vec<Point<Pixels>>>, lines: Vec<Vec<Point<Pixels>>>,
@ -147,8 +151,6 @@ impl PaintingViewer {
px(320.0 + (i as f32 * 10.0).sin() * 40.0), px(320.0 + (i as f32 * 10.0).sin() * 40.0),
)); ));
} }
let path = builder.build().unwrap();
lines.push((path, gpui::green().into()));
Self { Self {
default_lines: lines.clone(), default_lines: lines.clone(),
@ -183,9 +185,13 @@ fn button(
} }
impl Render for PaintingViewer { impl Render for PaintingViewer {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
window.request_animation_frame();
let default_lines = self.default_lines.clone(); let default_lines = self.default_lines.clone();
let lines = self.lines.clone(); let lines = self.lines.clone();
let window_size = window.bounds().size;
let scale = window_size.width / DEFAULT_WINDOW_WIDTH;
let dashed = self.dashed; let dashed = self.dashed;
div() div()
@ -222,7 +228,7 @@ impl Render for PaintingViewer {
move |_, _, _| {}, move |_, _, _| {},
move |_, _, window, _| { move |_, _, window, _| {
for (path, color) in default_lines { for (path, color) in default_lines {
window.paint_path(path, color); window.paint_path(path.clone().scale(scale), color);
} }
for points in lines { for points in lines {
@ -298,6 +304,11 @@ fn main() {
cx.open_window( cx.open_window(
WindowOptions { WindowOptions {
focus: true, focus: true,
window_bounds: Some(WindowBounds::Windowed(Bounds::centered(
None,
size(DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT),
cx,
))),
..Default::default() ..Default::default()
}, },
|window, cx| cx.new(|cx| PaintingViewer::new(window, cx)), |window, cx| cx.new(|cx| PaintingViewer::new(window, cx)),

View file

@ -336,10 +336,7 @@ impl PathBuilder {
let v1 = buf.vertices[i1]; let v1 = buf.vertices[i1];
let v2 = buf.vertices[i2]; let v2 = buf.vertices[i2];
path.push_triangle( path.push_triangle((v0.into(), v1.into(), v2.into()));
(v0.into(), v1.into(), v2.into()),
(point(0., 1.), point(0., 1.), point(0., 1.)),
);
} }
path path

View file

@ -789,7 +789,6 @@ pub(crate) struct AtlasTextureId {
pub(crate) enum AtlasTextureKind { pub(crate) enum AtlasTextureKind {
Monochrome = 0, Monochrome = 0,
Polychrome = 1, Polychrome = 1,
Path = 2,
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]

View file

@ -10,8 +10,6 @@ use etagere::BucketedAtlasAllocator;
use parking_lot::Mutex; use parking_lot::Mutex;
use std::{borrow::Cow, ops, sync::Arc}; use std::{borrow::Cow, ops, sync::Arc};
pub(crate) const PATH_TEXTURE_FORMAT: gpu::TextureFormat = gpu::TextureFormat::R16Float;
pub(crate) struct BladeAtlas(Mutex<BladeAtlasState>); pub(crate) struct BladeAtlas(Mutex<BladeAtlasState>);
struct PendingUpload { struct PendingUpload {
@ -27,7 +25,6 @@ struct BladeAtlasState {
tiles_by_key: FxHashMap<AtlasKey, AtlasTile>, tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
initializations: Vec<AtlasTextureId>, initializations: Vec<AtlasTextureId>,
uploads: Vec<PendingUpload>, uploads: Vec<PendingUpload>,
path_sample_count: u32,
} }
#[cfg(gles)] #[cfg(gles)]
@ -41,13 +38,13 @@ impl BladeAtlasState {
} }
pub struct BladeTextureInfo { pub struct BladeTextureInfo {
#[allow(dead_code)]
pub size: gpu::Extent, pub size: gpu::Extent,
pub raw_view: gpu::TextureView, pub raw_view: gpu::TextureView,
pub msaa_view: Option<gpu::TextureView>,
} }
impl BladeAtlas { impl BladeAtlas {
pub(crate) fn new(gpu: &Arc<gpu::Context>, path_sample_count: u32) -> Self { pub(crate) fn new(gpu: &Arc<gpu::Context>) -> Self {
BladeAtlas(Mutex::new(BladeAtlasState { BladeAtlas(Mutex::new(BladeAtlasState {
gpu: Arc::clone(gpu), gpu: Arc::clone(gpu),
upload_belt: BufferBelt::new(BufferBeltDescriptor { upload_belt: BufferBelt::new(BufferBeltDescriptor {
@ -59,7 +56,6 @@ impl BladeAtlas {
tiles_by_key: Default::default(), tiles_by_key: Default::default(),
initializations: Vec::new(), initializations: Vec::new(),
uploads: Vec::new(), uploads: Vec::new(),
path_sample_count,
})) }))
} }
@ -67,6 +63,7 @@ impl BladeAtlas {
self.0.lock().destroy(); self.0.lock().destroy();
} }
#[allow(dead_code)]
pub(crate) fn clear_textures(&self, texture_kind: AtlasTextureKind) { pub(crate) fn clear_textures(&self, texture_kind: AtlasTextureKind) {
let mut lock = self.0.lock(); let mut lock = self.0.lock();
let textures = &mut lock.storage[texture_kind]; let textures = &mut lock.storage[texture_kind];
@ -75,19 +72,6 @@ impl BladeAtlas {
} }
} }
/// Allocate a rectangle and make it available for rendering immediately (without waiting for `before_frame`)
pub fn allocate_for_rendering(
&self,
size: Size<DevicePixels>,
texture_kind: AtlasTextureKind,
gpu_encoder: &mut gpu::CommandEncoder,
) -> AtlasTile {
let mut lock = self.0.lock();
let tile = lock.allocate(size, texture_kind);
lock.flush_initializations(gpu_encoder);
tile
}
pub fn before_frame(&self, gpu_encoder: &mut gpu::CommandEncoder) { pub fn before_frame(&self, gpu_encoder: &mut gpu::CommandEncoder) {
let mut lock = self.0.lock(); let mut lock = self.0.lock();
lock.flush(gpu_encoder); lock.flush(gpu_encoder);
@ -109,7 +93,6 @@ impl BladeAtlas {
depth: 1, depth: 1,
}, },
raw_view: texture.raw_view, raw_view: texture.raw_view,
msaa_view: texture.msaa_view,
} }
} }
} }
@ -200,47 +183,7 @@ impl BladeAtlasState {
format = gpu::TextureFormat::Bgra8UnormSrgb; format = gpu::TextureFormat::Bgra8UnormSrgb;
usage = gpu::TextureUsage::COPY | gpu::TextureUsage::RESOURCE; usage = gpu::TextureUsage::COPY | gpu::TextureUsage::RESOURCE;
} }
AtlasTextureKind::Path => {
format = PATH_TEXTURE_FORMAT;
usage = gpu::TextureUsage::COPY
| gpu::TextureUsage::RESOURCE
| gpu::TextureUsage::TARGET;
} }
}
// We currently only enable MSAA for path textures.
let (msaa, msaa_view) = if self.path_sample_count > 1 && kind == AtlasTextureKind::Path {
let msaa = self.gpu.create_texture(gpu::TextureDesc {
name: "msaa path texture",
format,
size: gpu::Extent {
width: size.width.into(),
height: size.height.into(),
depth: 1,
},
array_layer_count: 1,
mip_level_count: 1,
sample_count: self.path_sample_count,
dimension: gpu::TextureDimension::D2,
usage: gpu::TextureUsage::TARGET,
external: None,
});
(
Some(msaa),
Some(self.gpu.create_texture_view(
msaa,
gpu::TextureViewDesc {
name: "msaa texture view",
format,
dimension: gpu::ViewDimension::D2,
subresources: &Default::default(),
},
)),
)
} else {
(None, None)
};
let raw = self.gpu.create_texture(gpu::TextureDesc { let raw = self.gpu.create_texture(gpu::TextureDesc {
name: "atlas", name: "atlas",
@ -279,8 +222,6 @@ impl BladeAtlasState {
format, format,
raw, raw,
raw_view, raw_view,
msaa,
msaa_view,
live_atlas_keys: 0, live_atlas_keys: 0,
}; };
@ -340,7 +281,6 @@ impl BladeAtlasState {
struct BladeAtlasStorage { struct BladeAtlasStorage {
monochrome_textures: AtlasTextureList<BladeAtlasTexture>, monochrome_textures: AtlasTextureList<BladeAtlasTexture>,
polychrome_textures: AtlasTextureList<BladeAtlasTexture>, polychrome_textures: AtlasTextureList<BladeAtlasTexture>,
path_textures: AtlasTextureList<BladeAtlasTexture>,
} }
impl ops::Index<AtlasTextureKind> for BladeAtlasStorage { impl ops::Index<AtlasTextureKind> for BladeAtlasStorage {
@ -349,7 +289,6 @@ impl ops::Index<AtlasTextureKind> for BladeAtlasStorage {
match kind { match kind {
crate::AtlasTextureKind::Monochrome => &self.monochrome_textures, crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
crate::AtlasTextureKind::Polychrome => &self.polychrome_textures, crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
crate::AtlasTextureKind::Path => &self.path_textures,
} }
} }
} }
@ -359,7 +298,6 @@ impl ops::IndexMut<AtlasTextureKind> for BladeAtlasStorage {
match kind { match kind {
crate::AtlasTextureKind::Monochrome => &mut self.monochrome_textures, crate::AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
crate::AtlasTextureKind::Polychrome => &mut self.polychrome_textures, crate::AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
crate::AtlasTextureKind::Path => &mut self.path_textures,
} }
} }
} }
@ -370,7 +308,6 @@ impl ops::Index<AtlasTextureId> for BladeAtlasStorage {
let textures = match id.kind { let textures = match id.kind {
crate::AtlasTextureKind::Monochrome => &self.monochrome_textures, crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
crate::AtlasTextureKind::Polychrome => &self.polychrome_textures, crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
crate::AtlasTextureKind::Path => &self.path_textures,
}; };
textures[id.index as usize].as_ref().unwrap() textures[id.index as usize].as_ref().unwrap()
} }
@ -384,9 +321,6 @@ impl BladeAtlasStorage {
for mut texture in self.polychrome_textures.drain().flatten() { for mut texture in self.polychrome_textures.drain().flatten() {
texture.destroy(gpu); texture.destroy(gpu);
} }
for mut texture in self.path_textures.drain().flatten() {
texture.destroy(gpu);
}
} }
} }
@ -395,8 +329,6 @@ struct BladeAtlasTexture {
allocator: BucketedAtlasAllocator, allocator: BucketedAtlasAllocator,
raw: gpu::Texture, raw: gpu::Texture,
raw_view: gpu::TextureView, raw_view: gpu::TextureView,
msaa: Option<gpu::Texture>,
msaa_view: Option<gpu::TextureView>,
format: gpu::TextureFormat, format: gpu::TextureFormat,
live_atlas_keys: u32, live_atlas_keys: u32,
} }
@ -424,12 +356,6 @@ impl BladeAtlasTexture {
fn destroy(&mut self, gpu: &gpu::Context) { fn destroy(&mut self, gpu: &gpu::Context) {
gpu.destroy_texture(self.raw); gpu.destroy_texture(self.raw);
gpu.destroy_texture_view(self.raw_view); gpu.destroy_texture_view(self.raw_view);
if let Some(msaa) = self.msaa {
gpu.destroy_texture(msaa);
}
if let Some(msaa_view) = self.msaa_view {
gpu.destroy_texture_view(msaa_view);
}
} }
fn bytes_per_pixel(&self) -> u8 { fn bytes_per_pixel(&self) -> u8 {

View file

@ -1,24 +1,19 @@
// Doing `if let` gives you nice scoping with passes/encoders // Doing `if let` gives you nice scoping with passes/encoders
#![allow(irrefutable_let_patterns)] #![allow(irrefutable_let_patterns)]
use super::{BladeAtlas, BladeContext, PATH_TEXTURE_FORMAT}; use super::{BladeAtlas, BladeContext};
use crate::{ use crate::{
AtlasTextureKind, AtlasTile, Background, Bounds, ContentMask, DevicePixels, GpuSpecs, Background, Bounds, ContentMask, DevicePixels, GpuSpecs, MonochromeSprite, PathVertex,
MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch, Quad, PolychromeSprite, PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size, Underline,
ScaledPixels, Scene, Shadow, Size, Underline,
}; };
use blade_graphics as gpu; use blade_graphics::{self as gpu};
use blade_util::{BufferBelt, BufferBeltDescriptor}; use blade_util::{BufferBelt, BufferBeltDescriptor};
use bytemuck::{Pod, Zeroable}; use bytemuck::{Pod, Zeroable};
use collections::HashMap;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
use media::core_video::CVMetalTextureCache; use media::core_video::CVMetalTextureCache;
use std::{mem, sync::Arc}; use std::{mem, sync::Arc};
const MAX_FRAME_TIME_MS: u32 = 10000; const MAX_FRAME_TIME_MS: u32 = 10000;
// Use 4x MSAA, all devices support it.
// https://developer.apple.com/documentation/metal/mtldevice/1433355-supportstexturesamplecount
const DEFAULT_PATH_SAMPLE_COUNT: u32 = 4;
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)] #[derive(Clone, Copy, Pod, Zeroable)]
@ -65,17 +60,10 @@ struct ShaderShadowsData {
b_shadows: gpu::BufferPiece, b_shadows: gpu::BufferPiece,
} }
#[derive(blade_macros::ShaderData)]
struct ShaderPathRasterizationData {
globals: GlobalParams,
b_path_vertices: gpu::BufferPiece,
}
#[derive(blade_macros::ShaderData)] #[derive(blade_macros::ShaderData)]
struct ShaderPathsData { struct ShaderPathsData {
globals: GlobalParams, globals: GlobalParams,
t_sprite: gpu::TextureView, b_path_vertices: gpu::BufferPiece,
s_sprite: gpu::Sampler,
b_path_sprites: gpu::BufferPiece, b_path_sprites: gpu::BufferPiece,
} }
@ -115,13 +103,27 @@ struct ShaderSurfacesData {
struct PathSprite { struct PathSprite {
bounds: Bounds<ScaledPixels>, bounds: Bounds<ScaledPixels>,
color: Background, color: Background,
tile: AtlasTile, }
/// Argument buffer layout for `draw_indirect` commands.
#[repr(C)]
#[derive(Copy, Clone, Debug, Default, Pod, Zeroable)]
pub struct DrawIndirectArgs {
/// The number of vertices to draw.
pub vertex_count: u32,
/// The number of instances to draw.
pub instance_count: u32,
/// The Index of the first vertex to draw.
pub first_vertex: u32,
/// The instance ID of the first instance to draw.
///
/// Has to be 0, unless [`Features::INDIRECT_FIRST_INSTANCE`](crate::Features::INDIRECT_FIRST_INSTANCE) is enabled.
pub first_instance: u32,
} }
struct BladePipelines { struct BladePipelines {
quads: gpu::RenderPipeline, quads: gpu::RenderPipeline,
shadows: gpu::RenderPipeline, shadows: gpu::RenderPipeline,
path_rasterization: gpu::RenderPipeline,
paths: gpu::RenderPipeline, paths: gpu::RenderPipeline,
underlines: gpu::RenderPipeline, underlines: gpu::RenderPipeline,
mono_sprites: gpu::RenderPipeline, mono_sprites: gpu::RenderPipeline,
@ -130,7 +132,7 @@ struct BladePipelines {
} }
impl BladePipelines { impl BladePipelines {
fn new(gpu: &gpu::Context, surface_info: gpu::SurfaceInfo, path_sample_count: u32) -> Self { fn new(gpu: &gpu::Context, surface_info: gpu::SurfaceInfo, sample_count: u32) -> Self {
use gpu::ShaderData as _; use gpu::ShaderData as _;
log::info!( log::info!(
@ -178,7 +180,10 @@ impl BladePipelines {
depth_stencil: None, depth_stencil: None,
fragment: Some(shader.at("fs_quad")), fragment: Some(shader.at("fs_quad")),
color_targets, color_targets,
multisample_state: gpu::MultisampleState::default(), multisample_state: gpu::MultisampleState {
sample_count,
..Default::default()
},
}), }),
shadows: gpu.create_render_pipeline(gpu::RenderPipelineDesc { shadows: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
name: "shadows", name: "shadows",
@ -192,26 +197,8 @@ impl BladePipelines {
depth_stencil: None, depth_stencil: None,
fragment: Some(shader.at("fs_shadow")), fragment: Some(shader.at("fs_shadow")),
color_targets, color_targets,
multisample_state: gpu::MultisampleState::default(),
}),
path_rasterization: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
name: "path_rasterization",
data_layouts: &[&ShaderPathRasterizationData::layout()],
vertex: shader.at("vs_path_rasterization"),
vertex_fetches: &[],
primitive: gpu::PrimitiveState {
topology: gpu::PrimitiveTopology::TriangleList,
..Default::default()
},
depth_stencil: None,
fragment: Some(shader.at("fs_path_rasterization")),
color_targets: &[gpu::ColorTargetState {
format: PATH_TEXTURE_FORMAT,
blend: Some(gpu::BlendState::ADDITIVE),
write_mask: gpu::ColorWrites::default(),
}],
multisample_state: gpu::MultisampleState { multisample_state: gpu::MultisampleState {
sample_count: path_sample_count, sample_count,
..Default::default() ..Default::default()
}, },
}), }),
@ -221,13 +208,16 @@ impl BladePipelines {
vertex: shader.at("vs_path"), vertex: shader.at("vs_path"),
vertex_fetches: &[], vertex_fetches: &[],
primitive: gpu::PrimitiveState { primitive: gpu::PrimitiveState {
topology: gpu::PrimitiveTopology::TriangleStrip, topology: gpu::PrimitiveTopology::TriangleList,
..Default::default() ..Default::default()
}, },
depth_stencil: None, depth_stencil: None,
fragment: Some(shader.at("fs_path")), fragment: Some(shader.at("fs_path")),
color_targets, color_targets,
multisample_state: gpu::MultisampleState::default(), multisample_state: gpu::MultisampleState {
sample_count,
..Default::default()
},
}), }),
underlines: gpu.create_render_pipeline(gpu::RenderPipelineDesc { underlines: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
name: "underlines", name: "underlines",
@ -241,7 +231,10 @@ impl BladePipelines {
depth_stencil: None, depth_stencil: None,
fragment: Some(shader.at("fs_underline")), fragment: Some(shader.at("fs_underline")),
color_targets, color_targets,
multisample_state: gpu::MultisampleState::default(), multisample_state: gpu::MultisampleState {
sample_count,
..Default::default()
},
}), }),
mono_sprites: gpu.create_render_pipeline(gpu::RenderPipelineDesc { mono_sprites: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
name: "mono-sprites", name: "mono-sprites",
@ -255,7 +248,10 @@ impl BladePipelines {
depth_stencil: None, depth_stencil: None,
fragment: Some(shader.at("fs_mono_sprite")), fragment: Some(shader.at("fs_mono_sprite")),
color_targets, color_targets,
multisample_state: gpu::MultisampleState::default(), multisample_state: gpu::MultisampleState {
sample_count,
..Default::default()
},
}), }),
poly_sprites: gpu.create_render_pipeline(gpu::RenderPipelineDesc { poly_sprites: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
name: "poly-sprites", name: "poly-sprites",
@ -269,7 +265,10 @@ impl BladePipelines {
depth_stencil: None, depth_stencil: None,
fragment: Some(shader.at("fs_poly_sprite")), fragment: Some(shader.at("fs_poly_sprite")),
color_targets, color_targets,
multisample_state: gpu::MultisampleState::default(), multisample_state: gpu::MultisampleState {
sample_count,
..Default::default()
},
}), }),
surfaces: gpu.create_render_pipeline(gpu::RenderPipelineDesc { surfaces: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
name: "surfaces", name: "surfaces",
@ -283,7 +282,10 @@ impl BladePipelines {
depth_stencil: None, depth_stencil: None,
fragment: Some(shader.at("fs_surface")), fragment: Some(shader.at("fs_surface")),
color_targets, color_targets,
multisample_state: gpu::MultisampleState::default(), multisample_state: gpu::MultisampleState {
sample_count,
..Default::default()
},
}), }),
} }
} }
@ -291,7 +293,6 @@ impl BladePipelines {
fn destroy(&mut self, gpu: &gpu::Context) { fn destroy(&mut self, gpu: &gpu::Context) {
gpu.destroy_render_pipeline(&mut self.quads); gpu.destroy_render_pipeline(&mut self.quads);
gpu.destroy_render_pipeline(&mut self.shadows); gpu.destroy_render_pipeline(&mut self.shadows);
gpu.destroy_render_pipeline(&mut self.path_rasterization);
gpu.destroy_render_pipeline(&mut self.paths); gpu.destroy_render_pipeline(&mut self.paths);
gpu.destroy_render_pipeline(&mut self.underlines); gpu.destroy_render_pipeline(&mut self.underlines);
gpu.destroy_render_pipeline(&mut self.mono_sprites); gpu.destroy_render_pipeline(&mut self.mono_sprites);
@ -317,12 +318,13 @@ pub struct BladeRenderer {
last_sync_point: Option<gpu::SyncPoint>, last_sync_point: Option<gpu::SyncPoint>,
pipelines: BladePipelines, pipelines: BladePipelines,
instance_belt: BufferBelt, instance_belt: BufferBelt,
path_tiles: HashMap<PathId, AtlasTile>,
atlas: Arc<BladeAtlas>, atlas: Arc<BladeAtlas>,
atlas_sampler: gpu::Sampler, atlas_sampler: gpu::Sampler,
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
core_video_texture_cache: CVMetalTextureCache, core_video_texture_cache: CVMetalTextureCache,
path_sample_count: u32, sample_count: u32,
texture_msaa: Option<gpu::Texture>,
texture_view_msaa: Option<gpu::TextureView>,
} }
impl BladeRenderer { impl BladeRenderer {
@ -331,6 +333,18 @@ impl BladeRenderer {
window: &I, window: &I,
config: BladeSurfaceConfig, config: BladeSurfaceConfig,
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
// workaround for https://github.com/zed-industries/zed/issues/26143
let sample_count = std::env::var("ZED_SAMPLE_COUNT")
.ok()
.or_else(|| std::env::var("ZED_PATH_SAMPLE_COUNT").ok())
.and_then(|v| v.parse().ok())
.or_else(|| {
[4, 2, 1]
.into_iter()
.find(|count| context.gpu.supports_texture_sample_count(*count))
})
.unwrap_or(1);
let surface_config = gpu::SurfaceConfig { let surface_config = gpu::SurfaceConfig {
size: config.size, size: config.size,
usage: gpu::TextureUsage::TARGET, usage: gpu::TextureUsage::TARGET,
@ -344,22 +358,27 @@ impl BladeRenderer {
.create_surface_configured(window, surface_config) .create_surface_configured(window, surface_config)
.map_err(|err| anyhow::anyhow!("Failed to create surface: {err:?}"))?; .map_err(|err| anyhow::anyhow!("Failed to create surface: {err:?}"))?;
let (texture_msaa, texture_view_msaa) = create_msaa_texture_if_needed(
&context.gpu,
surface.info().format,
config.size.width,
config.size.height,
sample_count,
)
.unzip();
let command_encoder = context.gpu.create_command_encoder(gpu::CommandEncoderDesc { let command_encoder = context.gpu.create_command_encoder(gpu::CommandEncoderDesc {
name: "main", name: "main",
buffer_count: 2, buffer_count: 2,
}); });
// workaround for https://github.com/zed-industries/zed/issues/26143
let path_sample_count = std::env::var("ZED_PATH_SAMPLE_COUNT") let pipelines = BladePipelines::new(&context.gpu, surface.info(), sample_count);
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(DEFAULT_PATH_SAMPLE_COUNT);
let pipelines = BladePipelines::new(&context.gpu, surface.info(), path_sample_count);
let instance_belt = BufferBelt::new(BufferBeltDescriptor { let instance_belt = BufferBelt::new(BufferBeltDescriptor {
memory: gpu::Memory::Shared, memory: gpu::Memory::Shared,
min_chunk_size: 0x1000, min_chunk_size: 0x1000,
alignment: 0x40, // Vulkan `minStorageBufferOffsetAlignment` on Intel Xe alignment: 0x40, // Vulkan `minStorageBufferOffsetAlignment` on Intel Xe
}); });
let atlas = Arc::new(BladeAtlas::new(&context.gpu, path_sample_count)); let atlas = Arc::new(BladeAtlas::new(&context.gpu));
let atlas_sampler = context.gpu.create_sampler(gpu::SamplerDesc { let atlas_sampler = context.gpu.create_sampler(gpu::SamplerDesc {
name: "atlas", name: "atlas",
mag_filter: gpu::FilterMode::Linear, mag_filter: gpu::FilterMode::Linear,
@ -383,12 +402,13 @@ impl BladeRenderer {
last_sync_point: None, last_sync_point: None,
pipelines, pipelines,
instance_belt, instance_belt,
path_tiles: HashMap::default(),
atlas, atlas,
atlas_sampler, atlas_sampler,
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
core_video_texture_cache, core_video_texture_cache,
path_sample_count, sample_count,
texture_msaa,
texture_view_msaa,
}) })
} }
@ -441,6 +461,24 @@ impl BladeRenderer {
self.surface_config.size = gpu_size; self.surface_config.size = gpu_size;
self.gpu self.gpu
.reconfigure_surface(&mut self.surface, self.surface_config); .reconfigure_surface(&mut self.surface, self.surface_config);
if let Some(texture_msaa) = self.texture_msaa {
self.gpu.destroy_texture(texture_msaa);
}
if let Some(texture_view_msaa) = self.texture_view_msaa {
self.gpu.destroy_texture_view(texture_view_msaa);
}
let (texture_msaa, texture_view_msaa) = create_msaa_texture_if_needed(
&self.gpu,
self.surface.info().format,
gpu_size.width,
gpu_size.height,
self.sample_count,
)
.unzip();
self.texture_msaa = texture_msaa;
self.texture_view_msaa = texture_view_msaa;
} }
} }
@ -451,8 +489,7 @@ impl BladeRenderer {
self.gpu self.gpu
.reconfigure_surface(&mut self.surface, self.surface_config); .reconfigure_surface(&mut self.surface, self.surface_config);
self.pipelines.destroy(&self.gpu); self.pipelines.destroy(&self.gpu);
self.pipelines = self.pipelines = BladePipelines::new(&self.gpu, self.surface.info(), self.sample_count);
BladePipelines::new(&self.gpu, self.surface.info(), self.path_sample_count);
} }
} }
@ -490,80 +527,6 @@ impl BladeRenderer {
objc2::rc::Retained::as_ptr(&self.surface.metal_layer()) as *mut _ objc2::rc::Retained::as_ptr(&self.surface.metal_layer()) as *mut _
} }
#[profiling::function]
fn rasterize_paths(&mut self, paths: &[Path<ScaledPixels>]) {
self.path_tiles.clear();
let mut vertices_by_texture_id = HashMap::default();
for path in paths {
let clipped_bounds = path
.bounds
.intersect(&path.content_mask.bounds)
.map_origin(|origin| origin.floor())
.map_size(|size| size.ceil());
let tile = self.atlas.allocate_for_rendering(
clipped_bounds.size.map(Into::into),
AtlasTextureKind::Path,
&mut self.command_encoder,
);
vertices_by_texture_id
.entry(tile.texture_id)
.or_insert(Vec::new())
.extend(path.vertices.iter().map(|vertex| PathVertex {
xy_position: vertex.xy_position - clipped_bounds.origin
+ tile.bounds.origin.map(Into::into),
st_position: vertex.st_position,
content_mask: ContentMask {
bounds: tile.bounds.map(Into::into),
},
}));
self.path_tiles.insert(path.id, tile);
}
for (texture_id, vertices) in vertices_by_texture_id {
let tex_info = self.atlas.get_texture_info(texture_id);
let globals = GlobalParams {
viewport_size: [tex_info.size.width as f32, tex_info.size.height as f32],
premultiplied_alpha: 0,
pad: 0,
};
let vertex_buf = unsafe { self.instance_belt.alloc_typed(&vertices, &self.gpu) };
let frame_view = tex_info.raw_view;
let color_target = if let Some(msaa_view) = tex_info.msaa_view {
gpu::RenderTarget {
view: msaa_view,
init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
finish_op: gpu::FinishOp::ResolveTo(frame_view),
}
} else {
gpu::RenderTarget {
view: frame_view,
init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
finish_op: gpu::FinishOp::Store,
}
};
if let mut pass = self.command_encoder.render(
"paths",
gpu::RenderTargetSet {
colors: &[color_target],
depth_stencil: None,
},
) {
let mut encoder = pass.with(&self.pipelines.path_rasterization);
encoder.bind(
0,
&ShaderPathRasterizationData {
globals,
b_path_vertices: vertex_buf,
},
);
encoder.draw(0, vertices.len() as u32, 0, 1);
}
}
}
pub fn destroy(&mut self) { pub fn destroy(&mut self) {
self.wait_for_gpu(); self.wait_for_gpu();
self.atlas.destroy(); self.atlas.destroy();
@ -572,17 +535,26 @@ impl BladeRenderer {
self.gpu.destroy_command_encoder(&mut self.command_encoder); self.gpu.destroy_command_encoder(&mut self.command_encoder);
self.pipelines.destroy(&self.gpu); self.pipelines.destroy(&self.gpu);
self.gpu.destroy_surface(&mut self.surface); self.gpu.destroy_surface(&mut self.surface);
if let Some(texture_msaa) = self.texture_msaa {
self.gpu.destroy_texture(texture_msaa);
}
if let Some(texture_view_msaa) = self.texture_view_msaa {
self.gpu.destroy_texture_view(texture_view_msaa);
}
} }
pub fn draw(&mut self, scene: &Scene) { pub fn draw(&mut self, scene: &Scene) {
self.command_encoder.start(); self.command_encoder.start();
self.atlas.before_frame(&mut self.command_encoder); self.atlas.before_frame(&mut self.command_encoder);
self.rasterize_paths(scene.paths());
let frame = { let frame = {
profiling::scope!("acquire frame"); profiling::scope!("acquire frame");
self.surface.acquire_frame() self.surface.acquire_frame()
}; };
let frame_view = frame.texture_view();
if let Some(texture_msaa) = self.texture_msaa {
self.command_encoder.init_texture(texture_msaa);
}
self.command_encoder.init_texture(frame.texture()); self.command_encoder.init_texture(frame.texture());
let globals = GlobalParams { let globals = GlobalParams {
@ -597,14 +569,25 @@ impl BladeRenderer {
pad: 0, pad: 0,
}; };
let target = if let Some(texture_view_msaa) = self.texture_view_msaa {
gpu::RenderTarget {
view: texture_view_msaa,
init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
finish_op: gpu::FinishOp::ResolveTo(frame_view),
}
} else {
gpu::RenderTarget {
view: frame_view,
init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
finish_op: gpu::FinishOp::Store,
}
};
// draw to the target texture
if let mut pass = self.command_encoder.render( if let mut pass = self.command_encoder.render(
"main", "main",
gpu::RenderTargetSet { gpu::RenderTargetSet {
colors: &[gpu::RenderTarget { colors: &[target],
view: frame.texture_view(),
init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
finish_op: gpu::FinishOp::Store,
}],
depth_stencil: None, depth_stencil: None,
}, },
) { ) {
@ -639,32 +622,55 @@ impl BladeRenderer {
} }
PrimitiveBatch::Paths(paths) => { PrimitiveBatch::Paths(paths) => {
let mut encoder = pass.with(&self.pipelines.paths); let mut encoder = pass.with(&self.pipelines.paths);
// todo(linux): group by texture ID
for path in paths {
let tile = &self.path_tiles[&path.id];
let tex_info = self.atlas.get_texture_info(tile.texture_id);
let origin = path.bounds.intersect(&path.content_mask.bounds).origin;
let sprites = [PathSprite {
bounds: Bounds {
origin: origin.map(|p| p.floor()),
size: tile.bounds.size.map(Into::into),
},
color: path.color,
tile: (*tile).clone(),
}];
let mut vertices = Vec::new();
let mut sprites = Vec::with_capacity(paths.len());
let mut draw_indirect_commands = Vec::with_capacity(paths.len());
let mut first_vertex = 0;
for (i, path) in paths.iter().enumerate() {
draw_indirect_commands.push(DrawIndirectArgs {
vertex_count: path.vertices.len() as u32,
instance_count: 1,
first_vertex,
first_instance: i as u32,
});
first_vertex += path.vertices.len() as u32;
vertices.extend(path.vertices.iter().map(|v| PathVertex {
xy_position: v.xy_position,
content_mask: ContentMask {
bounds: path.content_mask.bounds,
},
}));
sprites.push(PathSprite {
bounds: path.bounds,
color: path.color,
});
}
let b_path_vertices =
unsafe { self.instance_belt.alloc_typed(&vertices, &self.gpu) };
let instance_buf = let instance_buf =
unsafe { self.instance_belt.alloc_typed(&sprites, &self.gpu) }; unsafe { self.instance_belt.alloc_typed(&sprites, &self.gpu) };
let indirect_buf = unsafe {
self.instance_belt
.alloc_typed(&draw_indirect_commands, &self.gpu)
};
encoder.bind( encoder.bind(
0, 0,
&ShaderPathsData { &ShaderPathsData {
globals, globals,
t_sprite: tex_info.raw_view, b_path_vertices,
s_sprite: self.atlas_sampler,
b_path_sprites: instance_buf, b_path_sprites: instance_buf,
}, },
); );
encoder.draw(0, 4, 0, sprites.len() as u32);
for i in 0..paths.len() {
encoder.draw_indirect(indirect_buf.buffer.at(indirect_buf.offset
+ (i * mem::size_of::<DrawIndirectArgs>()) as u64));
} }
} }
PrimitiveBatch::Underlines(underlines) => { PrimitiveBatch::Underlines(underlines) => {
@ -817,9 +823,47 @@ impl BladeRenderer {
profiling::scope!("finish"); profiling::scope!("finish");
self.instance_belt.flush(&sync_point); self.instance_belt.flush(&sync_point);
self.atlas.after_frame(&sync_point); self.atlas.after_frame(&sync_point);
self.atlas.clear_textures(AtlasTextureKind::Path);
self.wait_for_gpu(); self.wait_for_gpu();
self.last_sync_point = Some(sync_point); self.last_sync_point = Some(sync_point);
} }
} }
fn create_msaa_texture_if_needed(
gpu: &gpu::Context,
format: gpu::TextureFormat,
width: u32,
height: u32,
sample_count: u32,
) -> Option<(gpu::Texture, gpu::TextureView)> {
if sample_count <= 1 {
return None;
}
let texture_msaa = gpu.create_texture(gpu::TextureDesc {
name: "msaa",
format,
size: gpu::Extent {
width,
height,
depth: 1,
},
array_layer_count: 1,
mip_level_count: 1,
sample_count,
dimension: gpu::TextureDimension::D2,
usage: gpu::TextureUsage::TARGET,
external: None,
});
let texture_view_msaa = gpu.create_texture_view(
texture_msaa,
gpu::TextureViewDesc {
name: "msaa view",
format,
dimension: gpu::ViewDimension::D2,
subresources: &Default::default(),
},
);
Some((texture_msaa, texture_view_msaa))
}

View file

@ -922,59 +922,23 @@ fn fs_shadow(input: ShadowVarying) -> @location(0) vec4<f32> {
return blend_color(input.color, alpha); return blend_color(input.color, alpha);
} }
// --- path rasterization --- // // --- paths --- //
struct PathVertex { struct PathVertex {
xy_position: vec2<f32>, xy_position: vec2<f32>,
st_position: vec2<f32>,
content_mask: Bounds, content_mask: Bounds,
} }
var<storage, read> b_path_vertices: array<PathVertex>;
struct PathRasterizationVarying {
@builtin(position) position: vec4<f32>,
@location(0) st_position: vec2<f32>,
//TODO: use `clip_distance` once Naga supports it
@location(3) clip_distances: vec4<f32>,
}
@vertex
fn vs_path_rasterization(@builtin(vertex_index) vertex_id: u32) -> PathRasterizationVarying {
let v = b_path_vertices[vertex_id];
var out = PathRasterizationVarying();
out.position = to_device_position_impl(v.xy_position);
out.st_position = v.st_position;
out.clip_distances = distance_from_clip_rect_impl(v.xy_position, v.content_mask);
return out;
}
@fragment
fn fs_path_rasterization(input: PathRasterizationVarying) -> @location(0) f32 {
let dx = dpdx(input.st_position);
let dy = dpdy(input.st_position);
if (any(input.clip_distances < vec4<f32>(0.0))) {
return 0.0;
}
let gradient = 2.0 * input.st_position.xx * vec2<f32>(dx.x, dy.x) - vec2<f32>(dx.y, dy.y);
let f = input.st_position.x * input.st_position.x - input.st_position.y;
let distance = f / length(gradient);
return saturate(0.5 - distance);
}
// --- paths --- //
struct PathSprite { struct PathSprite {
bounds: Bounds, bounds: Bounds,
color: Background, color: Background,
tile: AtlasTile,
} }
var<storage, read> b_path_vertices: array<PathVertex>;
var<storage, read> b_path_sprites: array<PathSprite>; var<storage, read> b_path_sprites: array<PathSprite>;
struct PathVarying { struct PathVarying {
@builtin(position) position: vec4<f32>, @builtin(position) position: vec4<f32>,
@location(0) tile_position: vec2<f32>, @location(0) clip_distances: vec4<f32>,
@location(1) @interpolate(flat) instance_id: u32, @location(1) @interpolate(flat) instance_id: u32,
@location(2) @interpolate(flat) color_solid: vec4<f32>, @location(2) @interpolate(flat) color_solid: vec4<f32>,
@location(3) @interpolate(flat) color0: vec4<f32>, @location(3) @interpolate(flat) color0: vec4<f32>,
@ -983,13 +947,12 @@ struct PathVarying {
@vertex @vertex
fn vs_path(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) instance_id: u32) -> PathVarying { fn vs_path(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) instance_id: u32) -> PathVarying {
let unit_vertex = vec2<f32>(f32(vertex_id & 1u), 0.5 * f32(vertex_id & 2u)); let v = b_path_vertices[vertex_id];
let sprite = b_path_sprites[instance_id]; let sprite = b_path_sprites[instance_id];
// Don't apply content mask because it was already accounted for when rasterizing the path.
var out = PathVarying(); var out = PathVarying();
out.position = to_device_position(unit_vertex, sprite.bounds); out.position = to_device_position_impl(v.xy_position);
out.tile_position = to_tile_position(unit_vertex, sprite.tile); out.clip_distances = distance_from_clip_rect_impl(v.xy_position, v.content_mask);
out.instance_id = instance_id; out.instance_id = instance_id;
let gradient = prepare_gradient_color( let gradient = prepare_gradient_color(
@ -1006,13 +969,15 @@ fn vs_path(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) insta
@fragment @fragment
fn fs_path(input: PathVarying) -> @location(0) vec4<f32> { fn fs_path(input: PathVarying) -> @location(0) vec4<f32> {
let sample = textureSample(t_sprite, s_sprite, input.tile_position).r; if any(input.clip_distances < vec4<f32>(0.0)) {
let mask = 1.0 - abs(1.0 - sample % 2.0); return vec4<f32>(0.0);
}
let sprite = b_path_sprites[input.instance_id]; let sprite = b_path_sprites[input.instance_id];
let background = sprite.color; let background = sprite.color;
let color = gradient_color(background, input.position.xy, sprite.bounds, let color = gradient_color(background, input.position.xy, sprite.bounds,
input.color_solid, input.color0, input.color1); input.color_solid, input.color0, input.color1);
return blend_color(color, mask); return blend_color(color, 1.0);
} }
// --- underlines --- // // --- underlines --- //

View file

@ -13,14 +13,12 @@ use std::borrow::Cow;
pub(crate) struct MetalAtlas(Mutex<MetalAtlasState>); pub(crate) struct MetalAtlas(Mutex<MetalAtlasState>);
impl MetalAtlas { impl MetalAtlas {
pub(crate) fn new(device: Device, path_sample_count: u32) -> Self { pub(crate) fn new(device: Device) -> Self {
MetalAtlas(Mutex::new(MetalAtlasState { MetalAtlas(Mutex::new(MetalAtlasState {
device: AssertSend(device), device: AssertSend(device),
monochrome_textures: Default::default(), monochrome_textures: Default::default(),
polychrome_textures: Default::default(), polychrome_textures: Default::default(),
path_textures: Default::default(),
tiles_by_key: Default::default(), tiles_by_key: Default::default(),
path_sample_count,
})) }))
} }
@ -28,10 +26,7 @@ impl MetalAtlas {
self.0.lock().texture(id).metal_texture.clone() self.0.lock().texture(id).metal_texture.clone()
} }
pub(crate) fn msaa_texture(&self, id: AtlasTextureId) -> Option<metal::Texture> { #[allow(dead_code)]
self.0.lock().texture(id).msaa_texture.clone()
}
pub(crate) fn allocate( pub(crate) fn allocate(
&self, &self,
size: Size<DevicePixels>, size: Size<DevicePixels>,
@ -40,12 +35,12 @@ impl MetalAtlas {
self.0.lock().allocate(size, texture_kind) self.0.lock().allocate(size, texture_kind)
} }
#[allow(dead_code)]
pub(crate) fn clear_textures(&self, texture_kind: AtlasTextureKind) { pub(crate) fn clear_textures(&self, texture_kind: AtlasTextureKind) {
let mut lock = self.0.lock(); let mut lock = self.0.lock();
let textures = match texture_kind { let textures = match texture_kind {
AtlasTextureKind::Monochrome => &mut lock.monochrome_textures, AtlasTextureKind::Monochrome => &mut lock.monochrome_textures,
AtlasTextureKind::Polychrome => &mut lock.polychrome_textures, AtlasTextureKind::Polychrome => &mut lock.polychrome_textures,
AtlasTextureKind::Path => &mut lock.path_textures,
}; };
for texture in textures.iter_mut() { for texture in textures.iter_mut() {
texture.clear(); texture.clear();
@ -57,9 +52,7 @@ struct MetalAtlasState {
device: AssertSend<Device>, device: AssertSend<Device>,
monochrome_textures: AtlasTextureList<MetalAtlasTexture>, monochrome_textures: AtlasTextureList<MetalAtlasTexture>,
polychrome_textures: AtlasTextureList<MetalAtlasTexture>, polychrome_textures: AtlasTextureList<MetalAtlasTexture>,
path_textures: AtlasTextureList<MetalAtlasTexture>,
tiles_by_key: FxHashMap<AtlasKey, AtlasTile>, tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
path_sample_count: u32,
} }
impl PlatformAtlas for MetalAtlas { impl PlatformAtlas for MetalAtlas {
@ -94,7 +87,6 @@ impl PlatformAtlas for MetalAtlas {
let textures = match id.kind { let textures = match id.kind {
AtlasTextureKind::Monochrome => &mut lock.monochrome_textures, AtlasTextureKind::Monochrome => &mut lock.monochrome_textures,
AtlasTextureKind::Polychrome => &mut lock.polychrome_textures, AtlasTextureKind::Polychrome => &mut lock.polychrome_textures,
AtlasTextureKind::Path => &mut lock.polychrome_textures,
}; };
let Some(texture_slot) = textures let Some(texture_slot) = textures
@ -128,7 +120,6 @@ impl MetalAtlasState {
let textures = match texture_kind { let textures = match texture_kind {
AtlasTextureKind::Monochrome => &mut self.monochrome_textures, AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
AtlasTextureKind::Polychrome => &mut self.polychrome_textures, AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
AtlasTextureKind::Path => &mut self.path_textures,
}; };
if let Some(tile) = textures if let Some(tile) = textures
@ -173,31 +164,14 @@ impl MetalAtlasState {
pixel_format = metal::MTLPixelFormat::BGRA8Unorm; pixel_format = metal::MTLPixelFormat::BGRA8Unorm;
usage = metal::MTLTextureUsage::ShaderRead; usage = metal::MTLTextureUsage::ShaderRead;
} }
AtlasTextureKind::Path => {
pixel_format = metal::MTLPixelFormat::R16Float;
usage = metal::MTLTextureUsage::RenderTarget | metal::MTLTextureUsage::ShaderRead;
}
} }
texture_descriptor.set_pixel_format(pixel_format); texture_descriptor.set_pixel_format(pixel_format);
texture_descriptor.set_usage(usage); texture_descriptor.set_usage(usage);
let metal_texture = self.device.new_texture(&texture_descriptor); let metal_texture = self.device.new_texture(&texture_descriptor);
// We currently only enable MSAA for path textures.
let msaa_texture = if self.path_sample_count > 1 && kind == AtlasTextureKind::Path {
let mut descriptor = texture_descriptor.clone();
descriptor.set_texture_type(metal::MTLTextureType::D2Multisample);
descriptor.set_storage_mode(metal::MTLStorageMode::Private);
descriptor.set_sample_count(self.path_sample_count as _);
let msaa_texture = self.device.new_texture(&descriptor);
Some(msaa_texture)
} else {
None
};
let texture_list = match kind { let texture_list = match kind {
AtlasTextureKind::Monochrome => &mut self.monochrome_textures, AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
AtlasTextureKind::Polychrome => &mut self.polychrome_textures, AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
AtlasTextureKind::Path => &mut self.path_textures,
}; };
let index = texture_list.free_list.pop(); let index = texture_list.free_list.pop();
@ -209,7 +183,6 @@ impl MetalAtlasState {
}, },
allocator: etagere::BucketedAtlasAllocator::new(size.into()), allocator: etagere::BucketedAtlasAllocator::new(size.into()),
metal_texture: AssertSend(metal_texture), metal_texture: AssertSend(metal_texture),
msaa_texture: AssertSend(msaa_texture),
live_atlas_keys: 0, live_atlas_keys: 0,
}; };
@ -226,7 +199,6 @@ impl MetalAtlasState {
let textures = match id.kind { let textures = match id.kind {
crate::AtlasTextureKind::Monochrome => &self.monochrome_textures, crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
crate::AtlasTextureKind::Polychrome => &self.polychrome_textures, crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
crate::AtlasTextureKind::Path => &self.path_textures,
}; };
textures[id.index as usize].as_ref().unwrap() textures[id.index as usize].as_ref().unwrap()
} }
@ -236,7 +208,6 @@ struct MetalAtlasTexture {
id: AtlasTextureId, id: AtlasTextureId,
allocator: BucketedAtlasAllocator, allocator: BucketedAtlasAllocator,
metal_texture: AssertSend<metal::Texture>, metal_texture: AssertSend<metal::Texture>,
msaa_texture: AssertSend<Option<metal::Texture>>,
live_atlas_keys: u32, live_atlas_keys: u32,
} }

View file

@ -1,27 +1,28 @@
use super::metal_atlas::MetalAtlas; use super::metal_atlas::MetalAtlas;
use crate::{ use crate::{
AtlasTextureId, AtlasTextureKind, AtlasTile, Background, Bounds, ContentMask, DevicePixels, AtlasTextureId, Background, Bounds, ContentMask, DevicePixels, MonochromeSprite, PaintSurface,
MonochromeSprite, PaintSurface, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch, Path, PathVertex, PolychromeSprite, PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size,
Quad, ScaledPixels, Scene, Shadow, Size, Surface, Underline, point, size, Surface, Underline, point, size,
}; };
use anyhow::{Context as _, Result}; use anyhow::Result;
use block::ConcreteBlock; use block::ConcreteBlock;
use cocoa::{ use cocoa::{
base::{NO, YES}, base::{NO, YES},
foundation::{NSSize, NSUInteger}, foundation::{NSSize, NSUInteger},
quartzcore::AutoresizingMask, quartzcore::AutoresizingMask,
}; };
use collections::HashMap;
use core_foundation::base::TCFType; use core_foundation::base::TCFType;
use core_video::{ use core_video::{
metal_texture::CVMetalTextureGetTexture, metal_texture_cache::CVMetalTextureCache, metal_texture::CVMetalTextureGetTexture, metal_texture_cache::CVMetalTextureCache,
pixel_buffer::kCVPixelFormatType_420YpCbCr8BiPlanarFullRange, pixel_buffer::kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,
}; };
use foreign_types::{ForeignType, ForeignTypeRef}; use foreign_types::{ForeignType, ForeignTypeRef};
use metal::{CAMetalLayer, CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange}; use metal::{
CAMetalLayer, CommandQueue, MTLDrawPrimitivesIndirectArguments, MTLPixelFormat,
MTLResourceOptions, NSRange,
};
use objc::{self, msg_send, sel, sel_impl}; use objc::{self, msg_send, sel, sel_impl};
use parking_lot::Mutex; use parking_lot::Mutex;
use smallvec::SmallVec;
use std::{cell::Cell, ffi::c_void, mem, ptr, sync::Arc}; use std::{cell::Cell, ffi::c_void, mem, ptr, sync::Arc};
// Exported to metal // Exported to metal
@ -31,9 +32,6 @@ pub(crate) type PointF = crate::Point<f32>;
const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib")); const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
#[cfg(feature = "runtime_shaders")] #[cfg(feature = "runtime_shaders")]
const SHADERS_SOURCE_FILE: &str = include_str!(concat!(env!("OUT_DIR"), "/stitched_shaders.metal")); const SHADERS_SOURCE_FILE: &str = include_str!(concat!(env!("OUT_DIR"), "/stitched_shaders.metal"));
// Use 4x MSAA, all devices support it.
// https://developer.apple.com/documentation/metal/mtldevice/1433355-supportstexturesamplecount
const PATH_SAMPLE_COUNT: u32 = 4;
pub type Context = Arc<Mutex<InstanceBufferPool>>; pub type Context = Arc<Mutex<InstanceBufferPool>>;
pub type Renderer = MetalRenderer; pub type Renderer = MetalRenderer;
@ -98,8 +96,7 @@ pub(crate) struct MetalRenderer {
layer: metal::MetalLayer, layer: metal::MetalLayer,
presents_with_transaction: bool, presents_with_transaction: bool,
command_queue: CommandQueue, command_queue: CommandQueue,
paths_rasterization_pipeline_state: metal::RenderPipelineState, path_pipeline_state: metal::RenderPipelineState,
path_sprites_pipeline_state: metal::RenderPipelineState,
shadows_pipeline_state: metal::RenderPipelineState, shadows_pipeline_state: metal::RenderPipelineState,
quads_pipeline_state: metal::RenderPipelineState, quads_pipeline_state: metal::RenderPipelineState,
underlines_pipeline_state: metal::RenderPipelineState, underlines_pipeline_state: metal::RenderPipelineState,
@ -111,6 +108,8 @@ pub(crate) struct MetalRenderer {
instance_buffer_pool: Arc<Mutex<InstanceBufferPool>>, instance_buffer_pool: Arc<Mutex<InstanceBufferPool>>,
sprite_atlas: Arc<MetalAtlas>, sprite_atlas: Arc<MetalAtlas>,
core_video_texture_cache: core_video::metal_texture_cache::CVMetalTextureCache, core_video_texture_cache: core_video::metal_texture_cache::CVMetalTextureCache,
sample_count: u64,
msaa_texture: Option<metal::Texture>,
} }
impl MetalRenderer { impl MetalRenderer {
@ -169,22 +168,19 @@ impl MetalRenderer {
MTLResourceOptions::StorageModeManaged, MTLResourceOptions::StorageModeManaged,
); );
let paths_rasterization_pipeline_state = build_path_rasterization_pipeline_state( let sample_count = [4, 2, 1]
.into_iter()
.find(|count| device.supports_texture_sample_count(*count))
.unwrap_or(1);
let path_pipeline_state = build_pipeline_state(
&device, &device,
&library, &library,
"paths_rasterization", "paths",
"path_rasterization_vertex", "path_vertex",
"path_rasterization_fragment", "path_fragment",
MTLPixelFormat::R16Float,
PATH_SAMPLE_COUNT,
);
let path_sprites_pipeline_state = build_pipeline_state(
&device,
&library,
"path_sprites",
"path_sprite_vertex",
"path_sprite_fragment",
MTLPixelFormat::BGRA8Unorm, MTLPixelFormat::BGRA8Unorm,
sample_count,
); );
let shadows_pipeline_state = build_pipeline_state( let shadows_pipeline_state = build_pipeline_state(
&device, &device,
@ -193,6 +189,7 @@ impl MetalRenderer {
"shadow_vertex", "shadow_vertex",
"shadow_fragment", "shadow_fragment",
MTLPixelFormat::BGRA8Unorm, MTLPixelFormat::BGRA8Unorm,
sample_count,
); );
let quads_pipeline_state = build_pipeline_state( let quads_pipeline_state = build_pipeline_state(
&device, &device,
@ -201,6 +198,7 @@ impl MetalRenderer {
"quad_vertex", "quad_vertex",
"quad_fragment", "quad_fragment",
MTLPixelFormat::BGRA8Unorm, MTLPixelFormat::BGRA8Unorm,
sample_count,
); );
let underlines_pipeline_state = build_pipeline_state( let underlines_pipeline_state = build_pipeline_state(
&device, &device,
@ -209,6 +207,7 @@ impl MetalRenderer {
"underline_vertex", "underline_vertex",
"underline_fragment", "underline_fragment",
MTLPixelFormat::BGRA8Unorm, MTLPixelFormat::BGRA8Unorm,
sample_count,
); );
let monochrome_sprites_pipeline_state = build_pipeline_state( let monochrome_sprites_pipeline_state = build_pipeline_state(
&device, &device,
@ -217,6 +216,7 @@ impl MetalRenderer {
"monochrome_sprite_vertex", "monochrome_sprite_vertex",
"monochrome_sprite_fragment", "monochrome_sprite_fragment",
MTLPixelFormat::BGRA8Unorm, MTLPixelFormat::BGRA8Unorm,
sample_count,
); );
let polychrome_sprites_pipeline_state = build_pipeline_state( let polychrome_sprites_pipeline_state = build_pipeline_state(
&device, &device,
@ -225,6 +225,7 @@ impl MetalRenderer {
"polychrome_sprite_vertex", "polychrome_sprite_vertex",
"polychrome_sprite_fragment", "polychrome_sprite_fragment",
MTLPixelFormat::BGRA8Unorm, MTLPixelFormat::BGRA8Unorm,
sample_count,
); );
let surfaces_pipeline_state = build_pipeline_state( let surfaces_pipeline_state = build_pipeline_state(
&device, &device,
@ -233,20 +234,21 @@ impl MetalRenderer {
"surface_vertex", "surface_vertex",
"surface_fragment", "surface_fragment",
MTLPixelFormat::BGRA8Unorm, MTLPixelFormat::BGRA8Unorm,
sample_count,
); );
let command_queue = device.new_command_queue(); let command_queue = device.new_command_queue();
let sprite_atlas = Arc::new(MetalAtlas::new(device.clone(), PATH_SAMPLE_COUNT)); let sprite_atlas = Arc::new(MetalAtlas::new(device.clone()));
let core_video_texture_cache = let core_video_texture_cache =
CVMetalTextureCache::new(None, device.clone(), None).unwrap(); CVMetalTextureCache::new(None, device.clone(), None).unwrap();
let msaa_texture = create_msaa_texture(&device, &layer, sample_count);
Self { Self {
device, device,
layer, layer,
presents_with_transaction: false, presents_with_transaction: false,
command_queue, command_queue,
paths_rasterization_pipeline_state, path_pipeline_state,
path_sprites_pipeline_state,
shadows_pipeline_state, shadows_pipeline_state,
quads_pipeline_state, quads_pipeline_state,
underlines_pipeline_state, underlines_pipeline_state,
@ -257,6 +259,8 @@ impl MetalRenderer {
instance_buffer_pool, instance_buffer_pool,
sprite_atlas, sprite_atlas,
core_video_texture_cache, core_video_texture_cache,
sample_count,
msaa_texture,
} }
} }
@ -289,6 +293,8 @@ impl MetalRenderer {
setDrawableSize: size setDrawableSize: size
]; ];
} }
self.msaa_texture = create_msaa_texture(&self.device, &self.layer, self.sample_count);
} }
pub fn update_transparency(&self, _transparent: bool) { pub fn update_transparency(&self, _transparent: bool) {
@ -375,25 +381,23 @@ impl MetalRenderer {
let command_queue = self.command_queue.clone(); let command_queue = self.command_queue.clone();
let command_buffer = command_queue.new_command_buffer(); let command_buffer = command_queue.new_command_buffer();
let mut instance_offset = 0; let mut instance_offset = 0;
let path_tiles = self
.rasterize_paths(
scene.paths(),
instance_buffer,
&mut instance_offset,
command_buffer,
)
.with_context(|| format!("rasterizing {} paths", scene.paths().len()))?;
let render_pass_descriptor = metal::RenderPassDescriptor::new(); let render_pass_descriptor = metal::RenderPassDescriptor::new();
let color_attachment = render_pass_descriptor let color_attachment = render_pass_descriptor
.color_attachments() .color_attachments()
.object_at(0) .object_at(0)
.unwrap(); .unwrap();
color_attachment.set_texture(Some(drawable.texture())); if let Some(msaa_texture_ref) = self.msaa_texture.as_deref() {
color_attachment.set_texture(Some(msaa_texture_ref));
color_attachment.set_load_action(metal::MTLLoadAction::Clear); color_attachment.set_load_action(metal::MTLLoadAction::Clear);
color_attachment.set_store_action(metal::MTLStoreAction::MultisampleResolve);
color_attachment.set_resolve_texture(Some(drawable.texture()));
} else {
color_attachment.set_load_action(metal::MTLLoadAction::Clear);
color_attachment.set_texture(Some(drawable.texture()));
color_attachment.set_store_action(metal::MTLStoreAction::Store); color_attachment.set_store_action(metal::MTLStoreAction::Store);
}
let alpha = if self.layer.is_opaque() { 1. } else { 0. }; let alpha = if self.layer.is_opaque() { 1. } else { 0. };
color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., alpha)); color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., alpha));
let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor); let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
@ -425,7 +429,6 @@ impl MetalRenderer {
), ),
PrimitiveBatch::Paths(paths) => self.draw_paths( PrimitiveBatch::Paths(paths) => self.draw_paths(
paths, paths,
&path_tiles,
instance_buffer, instance_buffer,
&mut instance_offset, &mut instance_offset,
viewport_size, viewport_size,
@ -493,106 +496,6 @@ impl MetalRenderer {
Ok(command_buffer.to_owned()) Ok(command_buffer.to_owned())
} }
fn rasterize_paths(
&self,
paths: &[Path<ScaledPixels>],
instance_buffer: &mut InstanceBuffer,
instance_offset: &mut usize,
command_buffer: &metal::CommandBufferRef,
) -> Option<HashMap<PathId, AtlasTile>> {
self.sprite_atlas.clear_textures(AtlasTextureKind::Path);
let mut tiles = HashMap::default();
let mut vertices_by_texture_id = HashMap::default();
for path in paths {
let clipped_bounds = path.bounds.intersect(&path.content_mask.bounds);
let tile = self
.sprite_atlas
.allocate(clipped_bounds.size.map(Into::into), AtlasTextureKind::Path)?;
vertices_by_texture_id
.entry(tile.texture_id)
.or_insert(Vec::new())
.extend(path.vertices.iter().map(|vertex| PathVertex {
xy_position: vertex.xy_position - clipped_bounds.origin
+ tile.bounds.origin.map(Into::into),
st_position: vertex.st_position,
content_mask: ContentMask {
bounds: tile.bounds.map(Into::into),
},
}));
tiles.insert(path.id, tile);
}
for (texture_id, vertices) in vertices_by_texture_id {
align_offset(instance_offset);
let vertices_bytes_len = mem::size_of_val(vertices.as_slice());
let next_offset = *instance_offset + vertices_bytes_len;
if next_offset > instance_buffer.size {
return None;
}
let render_pass_descriptor = metal::RenderPassDescriptor::new();
let color_attachment = render_pass_descriptor
.color_attachments()
.object_at(0)
.unwrap();
let texture = self.sprite_atlas.metal_texture(texture_id);
let msaa_texture = self.sprite_atlas.msaa_texture(texture_id);
if let Some(msaa_texture) = msaa_texture {
color_attachment.set_texture(Some(&msaa_texture));
color_attachment.set_resolve_texture(Some(&texture));
color_attachment.set_load_action(metal::MTLLoadAction::Clear);
color_attachment.set_store_action(metal::MTLStoreAction::MultisampleResolve);
} else {
color_attachment.set_texture(Some(&texture));
color_attachment.set_load_action(metal::MTLLoadAction::Clear);
color_attachment.set_store_action(metal::MTLStoreAction::Store);
}
color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., 1.));
let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
command_encoder.set_render_pipeline_state(&self.paths_rasterization_pipeline_state);
command_encoder.set_vertex_buffer(
PathRasterizationInputIndex::Vertices as u64,
Some(&instance_buffer.metal_buffer),
*instance_offset as u64,
);
let texture_size = Size {
width: DevicePixels::from(texture.width()),
height: DevicePixels::from(texture.height()),
};
command_encoder.set_vertex_bytes(
PathRasterizationInputIndex::AtlasTextureSize as u64,
mem::size_of_val(&texture_size) as u64,
&texture_size as *const Size<DevicePixels> as *const _,
);
let buffer_contents = unsafe {
(instance_buffer.metal_buffer.contents() as *mut u8).add(*instance_offset)
};
unsafe {
ptr::copy_nonoverlapping(
vertices.as_ptr() as *const u8,
buffer_contents,
vertices_bytes_len,
);
}
command_encoder.draw_primitives(
metal::MTLPrimitiveType::Triangle,
0,
vertices.len() as u64,
);
command_encoder.end_encoding();
*instance_offset = next_offset;
}
Some(tiles)
}
fn draw_shadows( fn draw_shadows(
&self, &self,
shadows: &[Shadow], shadows: &[Shadow],
@ -718,7 +621,6 @@ impl MetalRenderer {
fn draw_paths( fn draw_paths(
&self, &self,
paths: &[Path<ScaledPixels>], paths: &[Path<ScaledPixels>],
tiles_by_path_id: &HashMap<PathId, AtlasTile>,
instance_buffer: &mut InstanceBuffer, instance_buffer: &mut InstanceBuffer,
instance_offset: &mut usize, instance_offset: &mut usize,
viewport_size: Size<DevicePixels>, viewport_size: Size<DevicePixels>,
@ -728,100 +630,108 @@ impl MetalRenderer {
return true; return true;
} }
command_encoder.set_render_pipeline_state(&self.path_sprites_pipeline_state); command_encoder.set_render_pipeline_state(&self.path_pipeline_state);
command_encoder.set_vertex_buffer(
SpriteInputIndex::Vertices as u64, unsafe {
Some(&self.unit_vertices), let base_addr = instance_buffer.metal_buffer.contents();
0, let mut p = (base_addr as *mut u8).add(*instance_offset);
let mut draw_indirect_commands = Vec::with_capacity(paths.len());
// copy vertices
let vertices_offset = (p as usize) - (base_addr as usize);
let mut first_vertex = 0;
for (i, path) in paths.iter().enumerate() {
if (p as usize) - (base_addr as usize)
+ (mem::size_of::<PathVertex<ScaledPixels>>() * path.vertices.len())
> instance_buffer.size
{
return false;
}
for v in &path.vertices {
*(p as *mut PathVertex<ScaledPixels>) = PathVertex {
xy_position: v.xy_position,
content_mask: ContentMask {
bounds: path.content_mask.bounds,
},
};
p = p.add(mem::size_of::<PathVertex<ScaledPixels>>());
}
draw_indirect_commands.push(MTLDrawPrimitivesIndirectArguments {
vertexCount: path.vertices.len() as u32,
instanceCount: 1,
vertexStart: first_vertex,
baseInstance: i as u32,
});
first_vertex += path.vertices.len() as u32;
}
// copy sprites
let sprites_offset = (p as u64) - (base_addr as u64);
if (p as usize) - (base_addr as usize) + (mem::size_of::<PathSprite>() * paths.len())
> instance_buffer.size
{
return false;
}
for path in paths {
*(p as *mut PathSprite) = PathSprite {
bounds: path.bounds,
color: path.color,
};
p = p.add(mem::size_of::<PathSprite>());
}
// copy indirect commands
let icb_bytes_len = mem::size_of_val(draw_indirect_commands.as_slice());
let icb_offset = (p as u64) - (base_addr as u64);
if (p as usize) - (base_addr as usize) + icb_bytes_len > instance_buffer.size {
return false;
}
ptr::copy_nonoverlapping(
draw_indirect_commands.as_ptr() as *const u8,
p,
icb_bytes_len,
); );
p = p.add(icb_bytes_len);
// draw path
command_encoder.set_vertex_buffer(
PathInputIndex::Vertices as u64,
Some(&instance_buffer.metal_buffer),
vertices_offset as u64,
);
command_encoder.set_vertex_bytes( command_encoder.set_vertex_bytes(
SpriteInputIndex::ViewportSize as u64, PathInputIndex::ViewportSize as u64,
mem::size_of_val(&viewport_size) as u64, mem::size_of_val(&viewport_size) as u64,
&viewport_size as *const Size<DevicePixels> as *const _, &viewport_size as *const Size<DevicePixels> as *const _,
); );
let mut prev_texture_id = None;
let mut sprites = SmallVec::<[_; 1]>::new();
let mut paths_and_tiles = paths
.iter()
.map(|path| (path, tiles_by_path_id.get(&path.id).unwrap()))
.peekable();
loop {
if let Some((path, tile)) = paths_and_tiles.peek() {
if prev_texture_id.map_or(true, |texture_id| texture_id == tile.texture_id) {
prev_texture_id = Some(tile.texture_id);
let origin = path.bounds.intersect(&path.content_mask.bounds).origin;
sprites.push(PathSprite {
bounds: Bounds {
origin: origin.map(|p| p.floor()),
size: tile.bounds.size.map(Into::into),
},
color: path.color,
tile: (*tile).clone(),
});
paths_and_tiles.next();
continue;
}
}
if sprites.is_empty() {
break;
} else {
align_offset(instance_offset);
let texture_id = prev_texture_id.take().unwrap();
let texture: metal::Texture = self.sprite_atlas.metal_texture(texture_id);
let texture_size = size(
DevicePixels(texture.width() as i32),
DevicePixels(texture.height() as i32),
);
command_encoder.set_vertex_buffer( command_encoder.set_vertex_buffer(
SpriteInputIndex::Sprites as u64, PathInputIndex::Sprites as u64,
Some(&instance_buffer.metal_buffer), Some(&instance_buffer.metal_buffer),
*instance_offset as u64, sprites_offset,
);
command_encoder.set_vertex_bytes(
SpriteInputIndex::AtlasTextureSize as u64,
mem::size_of_val(&texture_size) as u64,
&texture_size as *const Size<DevicePixels> as *const _,
); );
command_encoder.set_fragment_buffer( command_encoder.set_fragment_buffer(
SpriteInputIndex::Sprites as u64, PathInputIndex::Sprites as u64,
Some(&instance_buffer.metal_buffer), Some(&instance_buffer.metal_buffer),
*instance_offset as u64, sprites_offset,
); );
command_encoder
.set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture));
let sprite_bytes_len = mem::size_of_val(sprites.as_slice()); for i in 0..paths.len() {
let next_offset = *instance_offset + sprite_bytes_len; command_encoder.draw_primitives_indirect(
if next_offset > instance_buffer.size {
return false;
}
let buffer_contents = unsafe {
(instance_buffer.metal_buffer.contents() as *mut u8).add(*instance_offset)
};
unsafe {
ptr::copy_nonoverlapping(
sprites.as_ptr() as *const u8,
buffer_contents,
sprite_bytes_len,
);
}
command_encoder.draw_primitives_instanced(
metal::MTLPrimitiveType::Triangle, metal::MTLPrimitiveType::Triangle,
0, &instance_buffer.metal_buffer,
6, icb_offset
sprites.len() as u64, + (i * std::mem::size_of::<MTLDrawPrimitivesIndirectArguments>()) as u64,
); );
*instance_offset = next_offset;
sprites.clear();
} }
*instance_offset = (p as usize) - (base_addr as usize);
} }
true true
} }
@ -1143,6 +1053,7 @@ fn build_pipeline_state(
vertex_fn_name: &str, vertex_fn_name: &str,
fragment_fn_name: &str, fragment_fn_name: &str,
pixel_format: metal::MTLPixelFormat, pixel_format: metal::MTLPixelFormat,
sample_count: u64,
) -> metal::RenderPipelineState { ) -> metal::RenderPipelineState {
let vertex_fn = library let vertex_fn = library
.get_function(vertex_fn_name, None) .get_function(vertex_fn_name, None)
@ -1155,6 +1066,7 @@ fn build_pipeline_state(
descriptor.set_label(label); descriptor.set_label(label);
descriptor.set_vertex_function(Some(vertex_fn.as_ref())); descriptor.set_vertex_function(Some(vertex_fn.as_ref()));
descriptor.set_fragment_function(Some(fragment_fn.as_ref())); descriptor.set_fragment_function(Some(fragment_fn.as_ref()));
descriptor.set_sample_count(sample_count);
let color_attachment = descriptor.color_attachments().object_at(0).unwrap(); let color_attachment = descriptor.color_attachments().object_at(0).unwrap();
color_attachment.set_pixel_format(pixel_format); color_attachment.set_pixel_format(pixel_format);
color_attachment.set_blending_enabled(true); color_attachment.set_blending_enabled(true);
@ -1170,50 +1082,45 @@ fn build_pipeline_state(
.expect("could not create render pipeline state") .expect("could not create render pipeline state")
} }
fn build_path_rasterization_pipeline_state(
device: &metal::DeviceRef,
library: &metal::LibraryRef,
label: &str,
vertex_fn_name: &str,
fragment_fn_name: &str,
pixel_format: metal::MTLPixelFormat,
path_sample_count: u32,
) -> metal::RenderPipelineState {
let vertex_fn = library
.get_function(vertex_fn_name, None)
.expect("error locating vertex function");
let fragment_fn = library
.get_function(fragment_fn_name, None)
.expect("error locating fragment function");
let descriptor = metal::RenderPipelineDescriptor::new();
descriptor.set_label(label);
descriptor.set_vertex_function(Some(vertex_fn.as_ref()));
descriptor.set_fragment_function(Some(fragment_fn.as_ref()));
if path_sample_count > 1 {
descriptor.set_raster_sample_count(path_sample_count as _);
descriptor.set_alpha_to_coverage_enabled(true);
}
let color_attachment = descriptor.color_attachments().object_at(0).unwrap();
color_attachment.set_pixel_format(pixel_format);
color_attachment.set_blending_enabled(true);
color_attachment.set_rgb_blend_operation(metal::MTLBlendOperation::Add);
color_attachment.set_alpha_blend_operation(metal::MTLBlendOperation::Add);
color_attachment.set_source_rgb_blend_factor(metal::MTLBlendFactor::One);
color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One);
color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::One);
color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::One);
device
.new_render_pipeline_state(&descriptor)
.expect("could not create render pipeline state")
}
// Align to multiples of 256 make Metal happy. // Align to multiples of 256 make Metal happy.
fn align_offset(offset: &mut usize) { fn align_offset(offset: &mut usize) {
*offset = (*offset).div_ceil(256) * 256; *offset = (*offset).div_ceil(256) * 256;
} }
fn create_msaa_texture(
device: &metal::Device,
layer: &metal::MetalLayer,
sample_count: u64,
) -> Option<metal::Texture> {
let viewport_size = layer.drawable_size();
let width = viewport_size.width.ceil() as u64;
let height = viewport_size.height.ceil() as u64;
if width == 0 || height == 0 {
return None;
}
if sample_count <= 1 {
return None;
}
let texture_descriptor = metal::TextureDescriptor::new();
texture_descriptor.set_texture_type(metal::MTLTextureType::D2Multisample);
// MTLStorageMode default is `shared` only for Apple silicon GPUs. Use `private` for Apple and Intel GPUs both.
// Reference: https://developer.apple.com/documentation/metal/choosing-a-resource-storage-mode-for-apple-gpus
texture_descriptor.set_storage_mode(metal::MTLStorageMode::Private);
texture_descriptor.set_width(width);
texture_descriptor.set_height(height);
texture_descriptor.set_pixel_format(layer.pixel_format());
texture_descriptor.set_usage(metal::MTLTextureUsage::RenderTarget);
texture_descriptor.set_sample_count(sample_count);
let metal_texture = device.new_texture(&texture_descriptor);
Some(metal_texture)
}
#[repr(C)] #[repr(C)]
enum ShadowInputIndex { enum ShadowInputIndex {
Vertices = 0, Vertices = 0,
@ -1255,9 +1162,10 @@ enum SurfaceInputIndex {
} }
#[repr(C)] #[repr(C)]
enum PathRasterizationInputIndex { enum PathInputIndex {
Vertices = 0, Vertices = 0,
AtlasTextureSize = 1, ViewportSize = 1,
Sprites = 2,
} }
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
@ -1265,7 +1173,6 @@ enum PathRasterizationInputIndex {
pub struct PathSprite { pub struct PathSprite {
pub bounds: Bounds<ScaledPixels>, pub bounds: Bounds<ScaledPixels>,
pub color: Background, pub color: Background,
pub tile: AtlasTile,
} }
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]

View file

@ -698,76 +698,27 @@ fragment float4 polychrome_sprite_fragment(
return color; return color;
} }
struct PathRasterizationVertexOutput { struct PathVertexOutput {
float4 position [[position]]; float4 position [[position]];
float2 st_position;
float clip_rect_distance [[clip_distance]][4];
};
struct PathRasterizationFragmentInput {
float4 position [[position]];
float2 st_position;
};
vertex PathRasterizationVertexOutput path_rasterization_vertex(
uint vertex_id [[vertex_id]],
constant PathVertex_ScaledPixels *vertices
[[buffer(PathRasterizationInputIndex_Vertices)]],
constant Size_DevicePixels *atlas_size
[[buffer(PathRasterizationInputIndex_AtlasTextureSize)]]) {
PathVertex_ScaledPixels v = vertices[vertex_id];
float2 vertex_position = float2(v.xy_position.x, v.xy_position.y);
float2 viewport_size = float2(atlas_size->width, atlas_size->height);
return PathRasterizationVertexOutput{
float4(vertex_position / viewport_size * float2(2., -2.) +
float2(-1., 1.),
0., 1.),
float2(v.st_position.x, v.st_position.y),
{v.xy_position.x - v.content_mask.bounds.origin.x,
v.content_mask.bounds.origin.x + v.content_mask.bounds.size.width -
v.xy_position.x,
v.xy_position.y - v.content_mask.bounds.origin.y,
v.content_mask.bounds.origin.y + v.content_mask.bounds.size.height -
v.xy_position.y}};
}
fragment float4 path_rasterization_fragment(PathRasterizationFragmentInput input
[[stage_in]]) {
float2 dx = dfdx(input.st_position);
float2 dy = dfdy(input.st_position);
float2 gradient = float2((2. * input.st_position.x) * dx.x - dx.y,
(2. * input.st_position.x) * dy.x - dy.y);
float f = (input.st_position.x * input.st_position.x) - input.st_position.y;
float distance = f / length(gradient);
float alpha = saturate(0.5 - distance);
return float4(alpha, 0., 0., 1.);
}
struct PathSpriteVertexOutput {
float4 position [[position]];
float2 tile_position;
uint sprite_id [[flat]]; uint sprite_id [[flat]];
float4 solid_color [[flat]]; float4 solid_color [[flat]];
float4 color0 [[flat]]; float4 color0 [[flat]];
float4 color1 [[flat]]; float4 color1 [[flat]];
float4 clip_distance;
}; };
vertex PathSpriteVertexOutput path_sprite_vertex( vertex PathVertexOutput path_vertex(
uint unit_vertex_id [[vertex_id]], uint sprite_id [[instance_id]], uint vertex_id [[vertex_id]],
constant float2 *unit_vertices [[buffer(SpriteInputIndex_Vertices)]], constant PathVertex_ScaledPixels *vertices [[buffer(PathInputIndex_Vertices)]],
constant PathSprite *sprites [[buffer(SpriteInputIndex_Sprites)]], uint sprite_id [[instance_id]],
constant Size_DevicePixels *viewport_size constant PathSprite *sprites [[buffer(PathInputIndex_Sprites)]],
[[buffer(SpriteInputIndex_ViewportSize)]], constant Size_DevicePixels *input_viewport_size [[buffer(PathInputIndex_ViewportSize)]]) {
constant Size_DevicePixels *atlas_size PathVertex_ScaledPixels v = vertices[vertex_id];
[[buffer(SpriteInputIndex_AtlasTextureSize)]]) { float2 vertex_position = float2(v.xy_position.x, v.xy_position.y);
float2 viewport_size = float2((float)input_viewport_size->width,
float2 unit_vertex = unit_vertices[unit_vertex_id]; (float)input_viewport_size->height);
PathSprite sprite = sprites[sprite_id]; PathSprite sprite = sprites[sprite_id];
// Don't apply content mask because it was already accounted for when float4 device_position = float4(vertex_position / viewport_size * float2(2., -2.) + float2(-1., 1.), 0., 1.);
// rasterizing the path.
float4 device_position =
to_device_position(unit_vertex, sprite.bounds, viewport_size);
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
GradientColor gradient = prepare_fill_color( GradientColor gradient = prepare_fill_color(
sprite.color.tag, sprite.color.tag,
@ -777,30 +728,32 @@ vertex PathSpriteVertexOutput path_sprite_vertex(
sprite.color.colors[1].color sprite.color.colors[1].color
); );
return PathSpriteVertexOutput{ return PathVertexOutput{
device_position, device_position,
tile_position,
sprite_id, sprite_id,
gradient.solid, gradient.solid,
gradient.color0, gradient.color0,
gradient.color1 gradient.color1,
{v.xy_position.x - v.content_mask.bounds.origin.x,
v.content_mask.bounds.origin.x + v.content_mask.bounds.size.width -
v.xy_position.x,
v.xy_position.y - v.content_mask.bounds.origin.y,
v.content_mask.bounds.origin.y + v.content_mask.bounds.size.height -
v.xy_position.y}
}; };
} }
fragment float4 path_sprite_fragment( fragment float4 path_fragment(
PathSpriteVertexOutput input [[stage_in]], PathVertexOutput input [[stage_in]],
constant PathSprite *sprites [[buffer(SpriteInputIndex_Sprites)]], constant PathSprite *sprites [[buffer(PathInputIndex_Sprites)]]) {
texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) { if (any(input.clip_distance < float4(0.0))) {
constexpr sampler atlas_texture_sampler(mag_filter::linear, return float4(0.0);
min_filter::linear); }
float4 sample =
atlas_texture.sample(atlas_texture_sampler, input.tile_position);
float mask = 1. - abs(1. - fmod(sample.r, 2.));
PathSprite sprite = sprites[input.sprite_id]; PathSprite sprite = sprites[input.sprite_id];
Background background = sprite.color; Background background = sprite.color;
float4 color = fill_color(background, input.position.xy, sprite.bounds, float4 color = fill_color(background, input.position.xy, sprite.bounds,
input.solid_color, input.color0, input.color1); input.solid_color, input.color0, input.color1);
color.a *= mask;
return color; return color;
} }

View file

@ -341,7 +341,7 @@ impl PlatformAtlas for TestAtlas {
crate::AtlasTile { crate::AtlasTile {
texture_id: AtlasTextureId { texture_id: AtlasTextureId {
index: texture_id, index: texture_id,
kind: crate::AtlasTextureKind::Path, kind: crate::AtlasTextureKind::Polychrome,
}, },
tile_id: TileId(tile_id), tile_id: TileId(tile_id),
padding: 0, padding: 0,

View file

@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
use crate::{ use crate::{
AtlasTextureId, AtlasTile, Background, Bounds, ContentMask, Corners, Edges, Hsla, Pixels, AtlasTextureId, AtlasTile, Background, Bounds, ContentMask, Corners, Edges, Hsla, Pixels,
Point, Radians, ScaledPixels, Size, bounds_tree::BoundsTree, point, Point, Radians, ScaledPixels, Size, bounds_tree::BoundsTree,
}; };
use std::{fmt::Debug, iter::Peekable, ops::Range, slice}; use std::{fmt::Debug, iter::Peekable, ops::Range, slice};
@ -43,13 +43,7 @@ impl Scene {
self.surfaces.clear(); self.surfaces.clear();
} }
#[cfg_attr( #[allow(dead_code)]
all(
any(target_os = "linux", target_os = "freebsd"),
not(any(feature = "x11", feature = "wayland"))
),
allow(dead_code)
)]
pub fn paths(&self) -> &[Path<ScaledPixels>] { pub fn paths(&self) -> &[Path<ScaledPixels>] {
&self.paths &self.paths
} }
@ -689,6 +683,7 @@ pub struct Path<P: Clone + Debug + Default + PartialEq> {
start: Point<P>, start: Point<P>,
current: Point<P>, current: Point<P>,
contour_count: usize, contour_count: usize,
base_scale: f32,
} }
impl Path<Pixels> { impl Path<Pixels> {
@ -707,25 +702,35 @@ impl Path<Pixels> {
content_mask: Default::default(), content_mask: Default::default(),
color: Default::default(), color: Default::default(),
contour_count: 0, contour_count: 0,
base_scale: 1.0,
} }
} }
/// Scale this path by the given factor. /// Set the base scale of the path.
pub fn scale(&self, factor: f32) -> Path<ScaledPixels> { pub fn scale(mut self, factor: f32) -> Self {
self.base_scale = factor;
self
}
/// Apply a scale to the path.
pub(crate) fn apply_scale(&self, factor: f32) -> Path<ScaledPixels> {
Path { Path {
id: self.id, id: self.id,
order: self.order, order: self.order,
bounds: self.bounds.scale(factor), bounds: self.bounds.scale(self.base_scale * factor),
content_mask: self.content_mask.scale(factor), content_mask: self.content_mask.scale(self.base_scale * factor),
vertices: self vertices: self
.vertices .vertices
.iter() .iter()
.map(|vertex| vertex.scale(factor)) .map(|vertex| vertex.scale(self.base_scale * factor))
.collect(), .collect(),
start: self.start.map(|start| start.scale(factor)), start: self
current: self.current.scale(factor), .start
.map(|start| start.scale(self.base_scale * factor)),
current: self.current.scale(self.base_scale * factor),
contour_count: self.contour_count, contour_count: self.contour_count,
color: self.color, color: self.color,
base_scale: 1.0,
} }
} }
@ -740,10 +745,7 @@ impl Path<Pixels> {
pub fn line_to(&mut self, to: Point<Pixels>) { pub fn line_to(&mut self, to: Point<Pixels>) {
self.contour_count += 1; self.contour_count += 1;
if self.contour_count > 1 { if self.contour_count > 1 {
self.push_triangle( self.push_triangle((self.start, self.current, to));
(self.start, self.current, to),
(point(0., 1.), point(0., 1.), point(0., 1.)),
);
} }
self.current = to; self.current = to;
} }
@ -752,25 +754,15 @@ impl Path<Pixels> {
pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) { pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) {
self.contour_count += 1; self.contour_count += 1;
if self.contour_count > 1 { if self.contour_count > 1 {
self.push_triangle( self.push_triangle((self.start, self.current, to));
(self.start, self.current, to),
(point(0., 1.), point(0., 1.), point(0., 1.)),
);
} }
self.push_triangle( self.push_triangle((self.current, ctrl, to));
(self.current, ctrl, to),
(point(0., 0.), point(0.5, 0.), point(1., 1.)),
);
self.current = to; self.current = to;
} }
/// Push a triangle to the Path. /// Push a triangle to the Path.
pub fn push_triangle( pub fn push_triangle(&mut self, xy: (Point<Pixels>, Point<Pixels>, Point<Pixels>)) {
&mut self,
xy: (Point<Pixels>, Point<Pixels>, Point<Pixels>),
st: (Point<f32>, Point<f32>, Point<f32>),
) {
self.bounds = self self.bounds = self
.bounds .bounds
.union(&Bounds { .union(&Bounds {
@ -788,17 +780,14 @@ impl Path<Pixels> {
self.vertices.push(PathVertex { self.vertices.push(PathVertex {
xy_position: xy.0, xy_position: xy.0,
st_position: st.0,
content_mask: Default::default(), content_mask: Default::default(),
}); });
self.vertices.push(PathVertex { self.vertices.push(PathVertex {
xy_position: xy.1, xy_position: xy.1,
st_position: st.1,
content_mask: Default::default(), content_mask: Default::default(),
}); });
self.vertices.push(PathVertex { self.vertices.push(PathVertex {
xy_position: xy.2, xy_position: xy.2,
st_position: st.2,
content_mask: Default::default(), content_mask: Default::default(),
}); });
} }
@ -814,7 +803,6 @@ impl From<Path<ScaledPixels>> for Primitive {
#[repr(C)] #[repr(C)]
pub(crate) struct PathVertex<P: Clone + Debug + Default + PartialEq> { pub(crate) struct PathVertex<P: Clone + Debug + Default + PartialEq> {
pub(crate) xy_position: Point<P>, pub(crate) xy_position: Point<P>,
pub(crate) st_position: Point<f32>,
pub(crate) content_mask: ContentMask<P>, pub(crate) content_mask: ContentMask<P>,
} }
@ -822,7 +810,6 @@ impl PathVertex<Pixels> {
pub fn scale(&self, factor: f32) -> PathVertex<ScaledPixels> { pub fn scale(&self, factor: f32) -> PathVertex<ScaledPixels> {
PathVertex { PathVertex {
xy_position: self.xy_position.scale(factor), xy_position: self.xy_position.scale(factor),
st_position: self.st_position,
content_mask: self.content_mask.scale(factor), content_mask: self.content_mask.scale(factor),
} }
} }

View file

@ -2633,7 +2633,7 @@ impl Window {
path.color = color.opacity(opacity); path.color = color.opacity(opacity);
self.next_frame self.next_frame
.scene .scene
.insert_primitive(path.scale(scale_factor)); .insert_primitive(path.apply_scale(scale_factor));
} }
/// Paint an underline into the scene for the next frame at the current z-index. /// Paint an underline into the scene for the next frame at the current z-index.

View file

@ -148,7 +148,7 @@ On some systems the file `/etc/prime-discrete` can be used to enforce the use of
On others, you may be able to the environment variable `DRI_PRIME=1` when running Zed to force the use of the discrete GPU. On others, you may be able to the environment variable `DRI_PRIME=1` when running Zed to force the use of the discrete GPU.
If you're using an AMD GPU and Zed crashes when selecting long lines, try setting the `ZED_PATH_SAMPLE_COUNT=0` environment variable. (See [#26143](https://github.com/zed-industries/zed/issues/26143)) If you're using an AMD GPU and Zed crashes when selecting long lines, try setting the `ZED_SAMPLE_COUNT=0` environment variable. (See [#26143](https://github.com/zed-industries/zed/issues/26143))
If you're using an AMD GPU, you might get a 'Broken Pipe' error. Try using the RADV or Mesa drivers. (See [#13880](https://github.com/zed-industries/zed/issues/13880)) If you're using an AMD GPU, you might get a 'Broken Pipe' error. Try using the RADV or Mesa drivers. (See [#13880](https://github.com/zed-industries/zed/issues/13880))