Detailed changes
@@ -2162,7 +2162,7 @@ dependencies = [
[[package]]
name = "blade-graphics"
version = "0.6.0"
-source = "git+https://github.com/kvark/blade?rev=416375211bb0b5826b3584dccdb6a43369e499ad#416375211bb0b5826b3584dccdb6a43369e499ad"
+source = "git+https://github.com/kvark/blade?rev=e0ec4e720957edd51b945b64dd85605ea54bcfe5#e0ec4e720957edd51b945b64dd85605ea54bcfe5"
dependencies = [
"ash",
"ash-window",
@@ -2195,7 +2195,7 @@ dependencies = [
[[package]]
name = "blade-macros"
version = "0.3.0"
-source = "git+https://github.com/kvark/blade?rev=416375211bb0b5826b3584dccdb6a43369e499ad#416375211bb0b5826b3584dccdb6a43369e499ad"
+source = "git+https://github.com/kvark/blade?rev=e0ec4e720957edd51b945b64dd85605ea54bcfe5#e0ec4e720957edd51b945b64dd85605ea54bcfe5"
dependencies = [
"proc-macro2",
"quote",
@@ -2205,7 +2205,7 @@ dependencies = [
[[package]]
name = "blade-util"
version = "0.2.0"
-source = "git+https://github.com/kvark/blade?rev=416375211bb0b5826b3584dccdb6a43369e499ad#416375211bb0b5826b3584dccdb6a43369e499ad"
+source = "git+https://github.com/kvark/blade?rev=e0ec4e720957edd51b945b64dd85605ea54bcfe5#e0ec4e720957edd51b945b64dd85605ea54bcfe5"
dependencies = [
"blade-graphics",
"bytemuck",
@@ -436,9 +436,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"] }
base64 = "0.22"
bitflags = "2.6.0"
-blade-graphics = { git = "https://github.com/kvark/blade", rev = "416375211bb0b5826b3584dccdb6a43369e499ad" }
-blade-macros = { git = "https://github.com/kvark/blade", rev = "416375211bb0b5826b3584dccdb6a43369e499ad" }
-blade-util = { git = "https://github.com/kvark/blade", rev = "416375211bb0b5826b3584dccdb6a43369e499ad" }
+blade-graphics = { git = "https://github.com/kvark/blade", rev = "e0ec4e720957edd51b945b64dd85605ea54bcfe5" }
+blade-macros = { git = "https://github.com/kvark/blade", rev = "e0ec4e720957edd51b945b64dd85605ea54bcfe5" }
+blade-util = { git = "https://github.com/kvark/blade", rev = "e0ec4e720957edd51b945b64dd85605ea54bcfe5" }
blake3 = "1.5.3"
bytes = "1.0"
cargo_metadata = "0.19"
@@ -491,7 +491,7 @@ json_dotpath = "1.1"
jsonschema = "0.30.0"
jsonwebtoken = "9.3"
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"
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
linkify = "0.10.0"
@@ -502,7 +502,7 @@ metal = "0.29"
moka = { version = "0.12.10", features = ["sync"] }
naga = { version = "25.0", features = ["wgsl-in"] }
nanoid = "0.4"
-nbformat = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
+nbformat = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
nix = "0.29"
num-format = "0.4.4"
objc = "0.2"
@@ -543,7 +543,7 @@ reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "951c77
"stream",
] }
rsa = "0.9.6"
-runtimelib = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734", default-features = false, features = [
+runtimelib = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734", default-features = false, features = [
"async-dispatcher-runtime",
] }
rust-embed = { version = "8.4", features = ["include-exclude"] }
@@ -126,7 +126,7 @@ mod macos {
"ContentMask".into(),
"Uniforms".into(),
"AtlasTile".into(),
- "PathInputIndex".into(),
+ "PathRasterizationInputIndex".into(),
"PathVertex_ScaledPixels".into(),
"ShadowInputIndex".into(),
"Shadow".into(),
@@ -1,13 +1,9 @@
use gpui::{
Application, Background, Bounds, ColorSpace, Context, MouseDownEvent, Path, PathBuilder,
- PathStyle, Pixels, Point, Render, SharedString, StrokeOptions, Window, WindowBounds,
- WindowOptions, canvas, div, linear_color_stop, linear_gradient, point, prelude::*, px, rgb,
- size,
+ PathStyle, Pixels, Point, Render, SharedString, StrokeOptions, Window, 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 {
default_lines: Vec<(Path<Pixels>, Background)>,
lines: Vec<Vec<Point<Pixels>>>,
@@ -151,6 +147,8 @@ impl PaintingViewer {
px(320.0 + (i as f32 * 10.0).sin() * 40.0),
));
}
+ let path = builder.build().unwrap();
+ lines.push((path, gpui::green().into()));
Self {
default_lines: lines.clone(),
@@ -185,13 +183,9 @@ fn button(
}
impl Render for PaintingViewer {
- fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
- window.request_animation_frame();
-
+ fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let default_lines = self.default_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;
div()
@@ -228,7 +222,7 @@ impl Render for PaintingViewer {
move |_, _, _| {},
move |_, _, window, _| {
for (path, color) in default_lines {
- window.paint_path(path.clone().scale(scale), color);
+ window.paint_path(path, color);
}
for points in lines {
@@ -304,11 +298,6 @@ fn main() {
cx.open_window(
WindowOptions {
focus: true,
- window_bounds: Some(WindowBounds::Windowed(Bounds::centered(
- None,
- size(DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT),
- cx,
- ))),
..Default::default()
},
|window, cx| cx.new(|cx| PaintingViewer::new(window, cx)),
@@ -336,7 +336,10 @@ impl PathBuilder {
let v1 = buf.vertices[i1];
let v2 = buf.vertices[i2];
- path.push_triangle((v0.into(), v1.into(), v2.into()));
+ path.push_triangle(
+ (v0.into(), v1.into(), v2.into()),
+ (point(0., 1.), point(0., 1.), point(0., 1.)),
+ );
}
path
@@ -794,6 +794,7 @@ pub(crate) struct AtlasTextureId {
pub(crate) enum AtlasTextureKind {
Monochrome = 0,
Polychrome = 1,
+ Path = 2,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
@@ -10,6 +10,8 @@ use etagere::BucketedAtlasAllocator;
use parking_lot::Mutex;
use std::{borrow::Cow, ops, sync::Arc};
+pub(crate) const PATH_TEXTURE_FORMAT: gpu::TextureFormat = gpu::TextureFormat::R16Float;
+
pub(crate) struct BladeAtlas(Mutex<BladeAtlasState>);
struct PendingUpload {
@@ -25,6 +27,7 @@ struct BladeAtlasState {
tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
initializations: Vec<AtlasTextureId>,
uploads: Vec<PendingUpload>,
+ path_sample_count: u32,
}
#[cfg(gles)]
@@ -38,13 +41,13 @@ impl BladeAtlasState {
}
pub struct BladeTextureInfo {
- #[allow(dead_code)]
pub size: gpu::Extent,
pub raw_view: gpu::TextureView,
+ pub msaa_view: Option<gpu::TextureView>,
}
impl BladeAtlas {
- pub(crate) fn new(gpu: &Arc<gpu::Context>) -> Self {
+ pub(crate) fn new(gpu: &Arc<gpu::Context>, path_sample_count: u32) -> Self {
BladeAtlas(Mutex::new(BladeAtlasState {
gpu: Arc::clone(gpu),
upload_belt: BufferBelt::new(BufferBeltDescriptor {
@@ -56,6 +59,7 @@ impl BladeAtlas {
tiles_by_key: Default::default(),
initializations: Vec::new(),
uploads: Vec::new(),
+ path_sample_count,
}))
}
@@ -63,7 +67,6 @@ impl BladeAtlas {
self.0.lock().destroy();
}
- #[allow(dead_code)]
pub(crate) fn clear_textures(&self, texture_kind: AtlasTextureKind) {
let mut lock = self.0.lock();
let textures = &mut lock.storage[texture_kind];
@@ -72,6 +75,19 @@ 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) {
let mut lock = self.0.lock();
lock.flush(gpu_encoder);
@@ -93,6 +109,7 @@ impl BladeAtlas {
depth: 1,
},
raw_view: texture.raw_view,
+ msaa_view: texture.msaa_view,
}
}
}
@@ -183,8 +200,48 @@ impl BladeAtlasState {
format = gpu::TextureFormat::Bgra8UnormSrgb;
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 {
name: "atlas",
format,
@@ -222,6 +279,8 @@ impl BladeAtlasState {
format,
raw,
raw_view,
+ msaa,
+ msaa_view,
live_atlas_keys: 0,
};
@@ -281,6 +340,7 @@ impl BladeAtlasState {
struct BladeAtlasStorage {
monochrome_textures: AtlasTextureList<BladeAtlasTexture>,
polychrome_textures: AtlasTextureList<BladeAtlasTexture>,
+ path_textures: AtlasTextureList<BladeAtlasTexture>,
}
impl ops::Index<AtlasTextureKind> for BladeAtlasStorage {
@@ -289,6 +349,7 @@ impl ops::Index<AtlasTextureKind> for BladeAtlasStorage {
match kind {
crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
+ crate::AtlasTextureKind::Path => &self.path_textures,
}
}
}
@@ -298,6 +359,7 @@ impl ops::IndexMut<AtlasTextureKind> for BladeAtlasStorage {
match kind {
crate::AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
crate::AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
+ crate::AtlasTextureKind::Path => &mut self.path_textures,
}
}
}
@@ -308,6 +370,7 @@ impl ops::Index<AtlasTextureId> for BladeAtlasStorage {
let textures = match id.kind {
crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
+ crate::AtlasTextureKind::Path => &self.path_textures,
};
textures[id.index as usize].as_ref().unwrap()
}
@@ -321,6 +384,9 @@ impl BladeAtlasStorage {
for mut texture in self.polychrome_textures.drain().flatten() {
texture.destroy(gpu);
}
+ for mut texture in self.path_textures.drain().flatten() {
+ texture.destroy(gpu);
+ }
}
}
@@ -329,6 +395,8 @@ struct BladeAtlasTexture {
allocator: BucketedAtlasAllocator,
raw: gpu::Texture,
raw_view: gpu::TextureView,
+ msaa: Option<gpu::Texture>,
+ msaa_view: Option<gpu::TextureView>,
format: gpu::TextureFormat,
live_atlas_keys: u32,
}
@@ -356,6 +424,12 @@ impl BladeAtlasTexture {
fn destroy(&mut self, gpu: &gpu::Context) {
gpu.destroy_texture(self.raw);
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 {
@@ -1,19 +1,24 @@
// Doing `if let` gives you nice scoping with passes/encoders
#![allow(irrefutable_let_patterns)]
-use super::{BladeAtlas, BladeContext};
+use super::{BladeAtlas, BladeContext, PATH_TEXTURE_FORMAT};
use crate::{
- Background, Bounds, ContentMask, DevicePixels, GpuSpecs, MonochromeSprite, PathVertex,
- PolychromeSprite, PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size, Underline,
+ AtlasTextureKind, AtlasTile, Background, Bounds, ContentMask, DevicePixels, GpuSpecs,
+ MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch, Quad,
+ ScaledPixels, Scene, Shadow, Size, Underline,
};
-use blade_graphics::{self as gpu};
+use blade_graphics as gpu;
use blade_util::{BufferBelt, BufferBeltDescriptor};
use bytemuck::{Pod, Zeroable};
+use collections::HashMap;
#[cfg(target_os = "macos")]
use media::core_video::CVMetalTextureCache;
use std::{mem, sync::Arc};
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)]
#[derive(Clone, Copy, Pod, Zeroable)]
@@ -61,9 +66,16 @@ struct ShaderShadowsData {
}
#[derive(blade_macros::ShaderData)]
-struct ShaderPathsData {
+struct ShaderPathRasterizationData {
globals: GlobalParams,
b_path_vertices: gpu::BufferPiece,
+}
+
+#[derive(blade_macros::ShaderData)]
+struct ShaderPathsData {
+ globals: GlobalParams,
+ t_sprite: gpu::TextureView,
+ s_sprite: gpu::Sampler,
b_path_sprites: gpu::BufferPiece,
}
@@ -103,27 +115,13 @@ struct ShaderSurfacesData {
struct PathSprite {
bounds: Bounds<ScaledPixels>,
color: Background,
-}
-
-/// 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,
+ tile: AtlasTile,
}
struct BladePipelines {
quads: gpu::RenderPipeline,
shadows: gpu::RenderPipeline,
+ path_rasterization: gpu::RenderPipeline,
paths: gpu::RenderPipeline,
underlines: gpu::RenderPipeline,
mono_sprites: gpu::RenderPipeline,
@@ -132,7 +130,7 @@ struct BladePipelines {
}
impl BladePipelines {
- fn new(gpu: &gpu::Context, surface_info: gpu::SurfaceInfo, sample_count: u32) -> Self {
+ fn new(gpu: &gpu::Context, surface_info: gpu::SurfaceInfo, path_sample_count: u32) -> Self {
use gpu::ShaderData as _;
log::info!(
@@ -180,10 +178,7 @@ impl BladePipelines {
depth_stencil: None,
fragment: Some(shader.at("fs_quad")),
color_targets,
- multisample_state: gpu::MultisampleState {
- sample_count,
- ..Default::default()
- },
+ multisample_state: gpu::MultisampleState::default(),
}),
shadows: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
name: "shadows",
@@ -197,8 +192,26 @@ impl BladePipelines {
depth_stencil: None,
fragment: Some(shader.at("fs_shadow")),
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 {
- sample_count,
+ sample_count: path_sample_count,
..Default::default()
},
}),
@@ -208,16 +221,13 @@ impl BladePipelines {
vertex: shader.at("vs_path"),
vertex_fetches: &[],
primitive: gpu::PrimitiveState {
- topology: gpu::PrimitiveTopology::TriangleList,
+ topology: gpu::PrimitiveTopology::TriangleStrip,
..Default::default()
},
depth_stencil: None,
fragment: Some(shader.at("fs_path")),
color_targets,
- multisample_state: gpu::MultisampleState {
- sample_count,
- ..Default::default()
- },
+ multisample_state: gpu::MultisampleState::default(),
}),
underlines: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
name: "underlines",
@@ -231,10 +241,7 @@ impl BladePipelines {
depth_stencil: None,
fragment: Some(shader.at("fs_underline")),
color_targets,
- multisample_state: gpu::MultisampleState {
- sample_count,
- ..Default::default()
- },
+ multisample_state: gpu::MultisampleState::default(),
}),
mono_sprites: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
name: "mono-sprites",
@@ -248,10 +255,7 @@ impl BladePipelines {
depth_stencil: None,
fragment: Some(shader.at("fs_mono_sprite")),
color_targets,
- multisample_state: gpu::MultisampleState {
- sample_count,
- ..Default::default()
- },
+ multisample_state: gpu::MultisampleState::default(),
}),
poly_sprites: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
name: "poly-sprites",
@@ -265,10 +269,7 @@ impl BladePipelines {
depth_stencil: None,
fragment: Some(shader.at("fs_poly_sprite")),
color_targets,
- multisample_state: gpu::MultisampleState {
- sample_count,
- ..Default::default()
- },
+ multisample_state: gpu::MultisampleState::default(),
}),
surfaces: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
name: "surfaces",
@@ -282,10 +283,7 @@ impl BladePipelines {
depth_stencil: None,
fragment: Some(shader.at("fs_surface")),
color_targets,
- multisample_state: gpu::MultisampleState {
- sample_count,
- ..Default::default()
- },
+ multisample_state: gpu::MultisampleState::default(),
}),
}
}
@@ -293,6 +291,7 @@ impl BladePipelines {
fn destroy(&mut self, gpu: &gpu::Context) {
gpu.destroy_render_pipeline(&mut self.quads);
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.underlines);
gpu.destroy_render_pipeline(&mut self.mono_sprites);
@@ -318,13 +317,12 @@ pub struct BladeRenderer {
last_sync_point: Option<gpu::SyncPoint>,
pipelines: BladePipelines,
instance_belt: BufferBelt,
+ path_tiles: HashMap<PathId, AtlasTile>,
atlas: Arc<BladeAtlas>,
atlas_sampler: gpu::Sampler,
#[cfg(target_os = "macos")]
core_video_texture_cache: CVMetalTextureCache,
- sample_count: u32,
- texture_msaa: Option<gpu::Texture>,
- texture_view_msaa: Option<gpu::TextureView>,
+ path_sample_count: u32,
}
impl BladeRenderer {
@@ -333,18 +331,6 @@ impl BladeRenderer {
window: &I,
config: BladeSurfaceConfig,
) -> 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 {
size: config.size,
usage: gpu::TextureUsage::TARGET,
@@ -358,27 +344,22 @@ impl BladeRenderer {
.create_surface_configured(window, surface_config)
.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 {
name: "main",
buffer_count: 2,
});
-
- let pipelines = BladePipelines::new(&context.gpu, surface.info(), sample_count);
+ // workaround for https://github.com/zed-industries/zed/issues/26143
+ let path_sample_count = std::env::var("ZED_PATH_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 {
memory: gpu::Memory::Shared,
min_chunk_size: 0x1000,
alignment: 0x40, // Vulkan `minStorageBufferOffsetAlignment` on Intel Xe
});
- let atlas = Arc::new(BladeAtlas::new(&context.gpu));
+ let atlas = Arc::new(BladeAtlas::new(&context.gpu, path_sample_count));
let atlas_sampler = context.gpu.create_sampler(gpu::SamplerDesc {
name: "atlas",
mag_filter: gpu::FilterMode::Linear,
@@ -402,13 +383,12 @@ impl BladeRenderer {
last_sync_point: None,
pipelines,
instance_belt,
+ path_tiles: HashMap::default(),
atlas,
atlas_sampler,
#[cfg(target_os = "macos")]
core_video_texture_cache,
- sample_count,
- texture_msaa,
- texture_view_msaa,
+ path_sample_count,
})
}
@@ -461,24 +441,6 @@ impl BladeRenderer {
self.surface_config.size = gpu_size;
self.gpu
.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;
}
}
@@ -489,7 +451,8 @@ impl BladeRenderer {
self.gpu
.reconfigure_surface(&mut self.surface, self.surface_config);
self.pipelines.destroy(&self.gpu);
- self.pipelines = BladePipelines::new(&self.gpu, self.surface.info(), self.sample_count);
+ self.pipelines =
+ BladePipelines::new(&self.gpu, self.surface.info(), self.path_sample_count);
}
}
@@ -527,6 +490,80 @@ impl BladeRenderer {
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) {
self.wait_for_gpu();
self.atlas.destroy();
@@ -535,26 +572,17 @@ impl BladeRenderer {
self.gpu.destroy_command_encoder(&mut self.command_encoder);
self.pipelines.destroy(&self.gpu);
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) {
self.command_encoder.start();
self.atlas.before_frame(&mut self.command_encoder);
+ self.rasterize_paths(scene.paths());
let frame = {
profiling::scope!("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());
let globals = GlobalParams {
@@ -569,25 +597,14 @@ impl BladeRenderer {
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(
"main",
gpu::RenderTargetSet {
- colors: &[target],
+ colors: &[gpu::RenderTarget {
+ view: frame.texture_view(),
+ init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
+ finish_op: gpu::FinishOp::Store,
+ }],
depth_stencil: None,
},
) {
@@ -622,55 +639,32 @@ impl BladeRenderer {
}
PrimitiveBatch::Paths(paths) => {
let mut encoder = pass.with(&self.pipelines.paths);
-
- 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,
+ // 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),
},
- }));
-
- 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 =
- 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(
- 0,
- &ShaderPathsData {
- globals,
- b_path_vertices,
- b_path_sprites: instance_buf,
- },
- );
-
- for i in 0..paths.len() {
- encoder.draw_indirect(indirect_buf.buffer.at(indirect_buf.offset
- + (i * mem::size_of::<DrawIndirectArgs>()) as u64));
+ tile: (*tile).clone(),
+ }];
+
+ let instance_buf =
+ unsafe { self.instance_belt.alloc_typed(&sprites, &self.gpu) };
+ encoder.bind(
+ 0,
+ &ShaderPathsData {
+ globals,
+ t_sprite: tex_info.raw_view,
+ s_sprite: self.atlas_sampler,
+ b_path_sprites: instance_buf,
+ },
+ );
+ encoder.draw(0, 4, 0, sprites.len() as u32);
}
}
PrimitiveBatch::Underlines(underlines) => {
@@ -823,47 +817,9 @@ impl BladeRenderer {
profiling::scope!("finish");
self.instance_belt.flush(&sync_point);
self.atlas.after_frame(&sync_point);
+ self.atlas.clear_textures(AtlasTextureKind::Path);
self.wait_for_gpu();
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))
-}
@@ -922,23 +922,59 @@ fn fs_shadow(input: ShadowVarying) -> @location(0) vec4<f32> {
return blend_color(input.color, alpha);
}
-// --- paths --- //
+// --- path rasterization --- //
struct PathVertex {
xy_position: vec2<f32>,
+ st_position: vec2<f32>,
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 {
bounds: Bounds,
color: Background,
+ tile: AtlasTile,
}
-var<storage, read> b_path_vertices: array<PathVertex>;
var<storage, read> b_path_sprites: array<PathSprite>;
struct PathVarying {
@builtin(position) position: vec4<f32>,
- @location(0) clip_distances: vec4<f32>,
+ @location(0) tile_position: vec2<f32>,
@location(1) @interpolate(flat) instance_id: u32,
@location(2) @interpolate(flat) color_solid: vec4<f32>,
@location(3) @interpolate(flat) color0: vec4<f32>,
@@ -947,12 +983,13 @@ struct PathVarying {
@vertex
fn vs_path(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) instance_id: u32) -> PathVarying {
- let v = b_path_vertices[vertex_id];
+ let unit_vertex = vec2<f32>(f32(vertex_id & 1u), 0.5 * f32(vertex_id & 2u));
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();
- out.position = to_device_position_impl(v.xy_position);
- out.clip_distances = distance_from_clip_rect_impl(v.xy_position, v.content_mask);
+ out.position = to_device_position(unit_vertex, sprite.bounds);
+ out.tile_position = to_tile_position(unit_vertex, sprite.tile);
out.instance_id = instance_id;
let gradient = prepare_gradient_color(
@@ -969,15 +1006,13 @@ fn vs_path(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) insta
@fragment
fn fs_path(input: PathVarying) -> @location(0) vec4<f32> {
- if any(input.clip_distances < vec4<f32>(0.0)) {
- return vec4<f32>(0.0);
- }
-
+ let sample = textureSample(t_sprite, s_sprite, input.tile_position).r;
+ let mask = 1.0 - abs(1.0 - sample % 2.0);
let sprite = b_path_sprites[input.instance_id];
let background = sprite.color;
let color = gradient_color(background, input.position.xy, sprite.bounds,
input.color_solid, input.color0, input.color1);
- return blend_color(color, 1.0);
+ return blend_color(color, mask);
}
// --- underlines --- //
@@ -13,12 +13,14 @@ use std::borrow::Cow;
pub(crate) struct MetalAtlas(Mutex<MetalAtlasState>);
impl MetalAtlas {
- pub(crate) fn new(device: Device) -> Self {
+ pub(crate) fn new(device: Device, path_sample_count: u32) -> Self {
MetalAtlas(Mutex::new(MetalAtlasState {
device: AssertSend(device),
monochrome_textures: Default::default(),
polychrome_textures: Default::default(),
+ path_textures: Default::default(),
tiles_by_key: Default::default(),
+ path_sample_count,
}))
}
@@ -26,7 +28,10 @@ impl MetalAtlas {
self.0.lock().texture(id).metal_texture.clone()
}
- #[allow(dead_code)]
+ pub(crate) fn msaa_texture(&self, id: AtlasTextureId) -> Option<metal::Texture> {
+ self.0.lock().texture(id).msaa_texture.clone()
+ }
+
pub(crate) fn allocate(
&self,
size: Size<DevicePixels>,
@@ -35,12 +40,12 @@ impl MetalAtlas {
self.0.lock().allocate(size, texture_kind)
}
- #[allow(dead_code)]
pub(crate) fn clear_textures(&self, texture_kind: AtlasTextureKind) {
let mut lock = self.0.lock();
let textures = match texture_kind {
AtlasTextureKind::Monochrome => &mut lock.monochrome_textures,
AtlasTextureKind::Polychrome => &mut lock.polychrome_textures,
+ AtlasTextureKind::Path => &mut lock.path_textures,
};
for texture in textures.iter_mut() {
texture.clear();
@@ -52,7 +57,9 @@ struct MetalAtlasState {
device: AssertSend<Device>,
monochrome_textures: AtlasTextureList<MetalAtlasTexture>,
polychrome_textures: AtlasTextureList<MetalAtlasTexture>,
+ path_textures: AtlasTextureList<MetalAtlasTexture>,
tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
+ path_sample_count: u32,
}
impl PlatformAtlas for MetalAtlas {
@@ -87,6 +94,7 @@ impl PlatformAtlas for MetalAtlas {
let textures = match id.kind {
AtlasTextureKind::Monochrome => &mut lock.monochrome_textures,
AtlasTextureKind::Polychrome => &mut lock.polychrome_textures,
+ AtlasTextureKind::Path => &mut lock.polychrome_textures,
};
let Some(texture_slot) = textures
@@ -120,6 +128,7 @@ impl MetalAtlasState {
let textures = match texture_kind {
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
+ AtlasTextureKind::Path => &mut self.path_textures,
};
if let Some(tile) = textures
@@ -164,14 +173,31 @@ impl MetalAtlasState {
pixel_format = metal::MTLPixelFormat::BGRA8Unorm;
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_usage(usage);
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 {
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
+ AtlasTextureKind::Path => &mut self.path_textures,
};
let index = texture_list.free_list.pop();
@@ -183,6 +209,7 @@ impl MetalAtlasState {
},
allocator: etagere::BucketedAtlasAllocator::new(size.into()),
metal_texture: AssertSend(metal_texture),
+ msaa_texture: AssertSend(msaa_texture),
live_atlas_keys: 0,
};
@@ -199,6 +226,7 @@ impl MetalAtlasState {
let textures = match id.kind {
crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
+ crate::AtlasTextureKind::Path => &self.path_textures,
};
textures[id.index as usize].as_ref().unwrap()
}
@@ -208,6 +236,7 @@ struct MetalAtlasTexture {
id: AtlasTextureId,
allocator: BucketedAtlasAllocator,
metal_texture: AssertSend<metal::Texture>,
+ msaa_texture: AssertSend<Option<metal::Texture>>,
live_atlas_keys: u32,
}
@@ -1,28 +1,27 @@
use super::metal_atlas::MetalAtlas;
use crate::{
- AtlasTextureId, Background, Bounds, ContentMask, DevicePixels, MonochromeSprite, PaintSurface,
- Path, PathVertex, PolychromeSprite, PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size,
- Surface, Underline, point, size,
+ AtlasTextureId, AtlasTextureKind, AtlasTile, Background, Bounds, ContentMask, DevicePixels,
+ MonochromeSprite, PaintSurface, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch,
+ Quad, ScaledPixels, Scene, Shadow, Size, Surface, Underline, point, size,
};
-use anyhow::Result;
+use anyhow::{Context as _, Result};
use block::ConcreteBlock;
use cocoa::{
base::{NO, YES},
foundation::{NSSize, NSUInteger},
quartzcore::AutoresizingMask,
};
+use collections::HashMap;
use core_foundation::base::TCFType;
use core_video::{
metal_texture::CVMetalTextureGetTexture, metal_texture_cache::CVMetalTextureCache,
pixel_buffer::kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,
};
use foreign_types::{ForeignType, ForeignTypeRef};
-use metal::{
- CAMetalLayer, CommandQueue, MTLDrawPrimitivesIndirectArguments, MTLPixelFormat,
- MTLResourceOptions, NSRange,
-};
+use metal::{CAMetalLayer, CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};
use objc::{self, msg_send, sel, sel_impl};
use parking_lot::Mutex;
+use smallvec::SmallVec;
use std::{cell::Cell, ffi::c_void, mem, ptr, sync::Arc};
// Exported to metal
@@ -32,6 +31,9 @@ pub(crate) type PointF = crate::Point<f32>;
const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
#[cfg(feature = "runtime_shaders")]
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 Renderer = MetalRenderer;
@@ -96,7 +98,8 @@ pub(crate) struct MetalRenderer {
layer: metal::MetalLayer,
presents_with_transaction: bool,
command_queue: CommandQueue,
- path_pipeline_state: metal::RenderPipelineState,
+ paths_rasterization_pipeline_state: metal::RenderPipelineState,
+ path_sprites_pipeline_state: metal::RenderPipelineState,
shadows_pipeline_state: metal::RenderPipelineState,
quads_pipeline_state: metal::RenderPipelineState,
underlines_pipeline_state: metal::RenderPipelineState,
@@ -108,8 +111,6 @@ pub(crate) struct MetalRenderer {
instance_buffer_pool: Arc<Mutex<InstanceBufferPool>>,
sprite_atlas: Arc<MetalAtlas>,
core_video_texture_cache: core_video::metal_texture_cache::CVMetalTextureCache,
- sample_count: u64,
- msaa_texture: Option<metal::Texture>,
}
impl MetalRenderer {
@@ -168,19 +169,22 @@ impl MetalRenderer {
MTLResourceOptions::StorageModeManaged,
);
- 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(
+ let paths_rasterization_pipeline_state = build_path_rasterization_pipeline_state(
+ &device,
+ &library,
+ "paths_rasterization",
+ "path_rasterization_vertex",
+ "path_rasterization_fragment",
+ MTLPixelFormat::R16Float,
+ PATH_SAMPLE_COUNT,
+ );
+ let path_sprites_pipeline_state = build_pipeline_state(
&device,
&library,
- "paths",
- "path_vertex",
- "path_fragment",
+ "path_sprites",
+ "path_sprite_vertex",
+ "path_sprite_fragment",
MTLPixelFormat::BGRA8Unorm,
- sample_count,
);
let shadows_pipeline_state = build_pipeline_state(
&device,
@@ -189,7 +193,6 @@ impl MetalRenderer {
"shadow_vertex",
"shadow_fragment",
MTLPixelFormat::BGRA8Unorm,
- sample_count,
);
let quads_pipeline_state = build_pipeline_state(
&device,
@@ -198,7 +201,6 @@ impl MetalRenderer {
"quad_vertex",
"quad_fragment",
MTLPixelFormat::BGRA8Unorm,
- sample_count,
);
let underlines_pipeline_state = build_pipeline_state(
&device,
@@ -207,7 +209,6 @@ impl MetalRenderer {
"underline_vertex",
"underline_fragment",
MTLPixelFormat::BGRA8Unorm,
- sample_count,
);
let monochrome_sprites_pipeline_state = build_pipeline_state(
&device,
@@ -216,7 +217,6 @@ impl MetalRenderer {
"monochrome_sprite_vertex",
"monochrome_sprite_fragment",
MTLPixelFormat::BGRA8Unorm,
- sample_count,
);
let polychrome_sprites_pipeline_state = build_pipeline_state(
&device,
@@ -225,7 +225,6 @@ impl MetalRenderer {
"polychrome_sprite_vertex",
"polychrome_sprite_fragment",
MTLPixelFormat::BGRA8Unorm,
- sample_count,
);
let surfaces_pipeline_state = build_pipeline_state(
&device,
@@ -234,21 +233,20 @@ impl MetalRenderer {
"surface_vertex",
"surface_fragment",
MTLPixelFormat::BGRA8Unorm,
- sample_count,
);
let command_queue = device.new_command_queue();
- let sprite_atlas = Arc::new(MetalAtlas::new(device.clone()));
+ let sprite_atlas = Arc::new(MetalAtlas::new(device.clone(), PATH_SAMPLE_COUNT));
let core_video_texture_cache =
CVMetalTextureCache::new(None, device.clone(), None).unwrap();
- let msaa_texture = create_msaa_texture(&device, &layer, sample_count);
Self {
device,
layer,
presents_with_transaction: false,
command_queue,
- path_pipeline_state,
+ paths_rasterization_pipeline_state,
+ path_sprites_pipeline_state,
shadows_pipeline_state,
quads_pipeline_state,
underlines_pipeline_state,
@@ -259,8 +257,6 @@ impl MetalRenderer {
instance_buffer_pool,
sprite_atlas,
core_video_texture_cache,
- sample_count,
- msaa_texture,
}
}
@@ -293,8 +289,6 @@ impl MetalRenderer {
setDrawableSize: size
];
}
-
- self.msaa_texture = create_msaa_texture(&self.device, &self.layer, self.sample_count);
}
pub fn update_transparency(&self, _transparent: bool) {
@@ -381,23 +375,25 @@ impl MetalRenderer {
let command_queue = self.command_queue.clone();
let command_buffer = command_queue.new_command_buffer();
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 color_attachment = render_pass_descriptor
.color_attachments()
.object_at(0)
.unwrap();
- 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_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_texture(Some(drawable.texture()));
+ color_attachment.set_load_action(metal::MTLLoadAction::Clear);
+ color_attachment.set_store_action(metal::MTLStoreAction::Store);
let alpha = if self.layer.is_opaque() { 1. } else { 0. };
color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., alpha));
let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
@@ -429,6 +425,7 @@ impl MetalRenderer {
),
PrimitiveBatch::Paths(paths) => self.draw_paths(
paths,
+ &path_tiles,
instance_buffer,
&mut instance_offset,
viewport_size,
@@ -496,6 +493,106 @@ impl MetalRenderer {
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(
&self,
shadows: &[Shadow],
@@ -621,6 +718,7 @@ impl MetalRenderer {
fn draw_paths(
&self,
paths: &[Path<ScaledPixels>],
+ tiles_by_path_id: &HashMap<PathId, AtlasTile>,
instance_buffer: &mut InstanceBuffer,
instance_offset: &mut usize,
viewport_size: Size<DevicePixels>,
@@ -630,108 +728,100 @@ impl MetalRenderer {
return true;
}
- command_encoder.set_render_pipeline_state(&self.path_pipeline_state);
+ command_encoder.set_render_pipeline_state(&self.path_sprites_pipeline_state);
+ command_encoder.set_vertex_buffer(
+ SpriteInputIndex::Vertices as u64,
+ Some(&self.unit_vertices),
+ 0,
+ );
+ command_encoder.set_vertex_bytes(
+ SpriteInputIndex::ViewportSize as u64,
+ mem::size_of_val(&viewport_size) as u64,
+ &viewport_size as *const Size<DevicePixels> as *const _,
+ );
- unsafe {
- let base_addr = instance_buffer.metal_buffer.contents();
- 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;
- }
+ 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();
- for v in &path.vertices {
- *(p as *mut PathVertex<ScaledPixels>) = PathVertex {
- xy_position: v.xy_position,
- content_mask: ContentMask {
- bounds: path.content_mask.bounds,
+ 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),
},
- };
- p = p.add(mem::size_of::<PathVertex<ScaledPixels>>());
+ color: path.color,
+ tile: (*tile).clone(),
+ });
+ paths_and_tiles.next();
+ continue;
}
-
- 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);
+ 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),
+ );
- // draw path
- command_encoder.set_vertex_buffer(
- PathInputIndex::Vertices as u64,
- Some(&instance_buffer.metal_buffer),
- vertices_offset as u64,
- );
+ command_encoder.set_vertex_buffer(
+ SpriteInputIndex::Sprites as u64,
+ Some(&instance_buffer.metal_buffer),
+ *instance_offset as u64,
+ );
+ 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(
+ SpriteInputIndex::Sprites as u64,
+ Some(&instance_buffer.metal_buffer),
+ *instance_offset as u64,
+ );
+ command_encoder
+ .set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture));
- command_encoder.set_vertex_bytes(
- PathInputIndex::ViewportSize as u64,
- mem::size_of_val(&viewport_size) as u64,
- &viewport_size as *const Size<DevicePixels> as *const _,
- );
+ let sprite_bytes_len = mem::size_of_val(sprites.as_slice());
+ let next_offset = *instance_offset + sprite_bytes_len;
+ if next_offset > instance_buffer.size {
+ return false;
+ }
- command_encoder.set_vertex_buffer(
- PathInputIndex::Sprites as u64,
- Some(&instance_buffer.metal_buffer),
- sprites_offset,
- );
+ let buffer_contents = unsafe {
+ (instance_buffer.metal_buffer.contents() as *mut u8).add(*instance_offset)
+ };
- command_encoder.set_fragment_buffer(
- PathInputIndex::Sprites as u64,
- Some(&instance_buffer.metal_buffer),
- sprites_offset,
- );
+ unsafe {
+ ptr::copy_nonoverlapping(
+ sprites.as_ptr() as *const u8,
+ buffer_contents,
+ sprite_bytes_len,
+ );
+ }
- for i in 0..paths.len() {
- command_encoder.draw_primitives_indirect(
+ command_encoder.draw_primitives_instanced(
metal::MTLPrimitiveType::Triangle,
- &instance_buffer.metal_buffer,
- icb_offset
- + (i * std::mem::size_of::<MTLDrawPrimitivesIndirectArguments>()) as u64,
+ 0,
+ 6,
+ sprites.len() as u64,
);
+ *instance_offset = next_offset;
+ sprites.clear();
}
-
- *instance_offset = (p as usize) - (base_addr as usize);
}
-
true
}
@@ -1053,7 +1143,6 @@ fn build_pipeline_state(
vertex_fn_name: &str,
fragment_fn_name: &str,
pixel_format: metal::MTLPixelFormat,
- sample_count: u64,
) -> metal::RenderPipelineState {
let vertex_fn = library
.get_function(vertex_fn_name, None)
@@ -1066,7 +1155,6 @@ fn build_pipeline_state(
descriptor.set_label(label);
descriptor.set_vertex_function(Some(vertex_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();
color_attachment.set_pixel_format(pixel_format);
color_attachment.set_blending_enabled(true);
@@ -1082,43 +1170,48 @@ fn build_pipeline_state(
.expect("could not create render pipeline state")
}
-// Align to multiples of 256 make Metal happy.
-fn align_offset(offset: &mut usize) {
- *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;
- }
+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");
- if sample_count <= 1 {
- return None;
+ 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);
- 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);
+ device
+ .new_render_pipeline_state(&descriptor)
+ .expect("could not create render pipeline state")
+}
- let metal_texture = device.new_texture(&texture_descriptor);
- Some(metal_texture)
+// Align to multiples of 256 make Metal happy.
+fn align_offset(offset: &mut usize) {
+ *offset = (*offset).div_ceil(256) * 256;
}
#[repr(C)]
@@ -1162,10 +1255,9 @@ enum SurfaceInputIndex {
}
#[repr(C)]
-enum PathInputIndex {
+enum PathRasterizationInputIndex {
Vertices = 0,
- ViewportSize = 1,
- Sprites = 2,
+ AtlasTextureSize = 1,
}
#[derive(Clone, Debug, Eq, PartialEq)]
@@ -1173,6 +1265,7 @@ enum PathInputIndex {
pub struct PathSprite {
pub bounds: Bounds<ScaledPixels>,
pub color: Background,
+ pub tile: AtlasTile,
}
#[derive(Clone, Debug, Eq, PartialEq)]
@@ -698,27 +698,76 @@ fragment float4 polychrome_sprite_fragment(
return color;
}
-struct PathVertexOutput {
+struct PathRasterizationVertexOutput {
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]];
float4 solid_color [[flat]];
float4 color0 [[flat]];
float4 color1 [[flat]];
- float4 clip_distance;
};
-vertex PathVertexOutput path_vertex(
- uint vertex_id [[vertex_id]],
- constant PathVertex_ScaledPixels *vertices [[buffer(PathInputIndex_Vertices)]],
- uint sprite_id [[instance_id]],
- constant PathSprite *sprites [[buffer(PathInputIndex_Sprites)]],
- constant Size_DevicePixels *input_viewport_size [[buffer(PathInputIndex_ViewportSize)]]) {
- PathVertex_ScaledPixels v = vertices[vertex_id];
- float2 vertex_position = float2(v.xy_position.x, v.xy_position.y);
- float2 viewport_size = float2((float)input_viewport_size->width,
- (float)input_viewport_size->height);
+vertex PathSpriteVertexOutput path_sprite_vertex(
+ uint unit_vertex_id [[vertex_id]], uint sprite_id [[instance_id]],
+ constant float2 *unit_vertices [[buffer(SpriteInputIndex_Vertices)]],
+ constant PathSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
+ constant Size_DevicePixels *viewport_size
+ [[buffer(SpriteInputIndex_ViewportSize)]],
+ constant Size_DevicePixels *atlas_size
+ [[buffer(SpriteInputIndex_AtlasTextureSize)]]) {
+
+ float2 unit_vertex = unit_vertices[unit_vertex_id];
PathSprite sprite = sprites[sprite_id];
- float4 device_position = float4(vertex_position / viewport_size * float2(2., -2.) + float2(-1., 1.), 0., 1.);
+ // Don't apply content mask because it was already accounted for when
+ // 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(
sprite.color.tag,
@@ -728,32 +777,30 @@ vertex PathVertexOutput path_vertex(
sprite.color.colors[1].color
);
- return PathVertexOutput{
+ return PathSpriteVertexOutput{
device_position,
+ tile_position,
sprite_id,
gradient.solid,
gradient.color0,
- 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}
+ gradient.color1
};
}
-fragment float4 path_fragment(
- PathVertexOutput input [[stage_in]],
- constant PathSprite *sprites [[buffer(PathInputIndex_Sprites)]]) {
- if (any(input.clip_distance < float4(0.0))) {
- return float4(0.0);
- }
-
+fragment float4 path_sprite_fragment(
+ PathSpriteVertexOutput input [[stage_in]],
+ constant PathSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
+ texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
+ constexpr sampler atlas_texture_sampler(mag_filter::linear,
+ 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];
Background background = sprite.color;
float4 color = fill_color(background, input.position.xy, sprite.bounds,
input.solid_color, input.color0, input.color1);
+ color.a *= mask;
return color;
}
@@ -341,7 +341,7 @@ impl PlatformAtlas for TestAtlas {
crate::AtlasTile {
texture_id: AtlasTextureId {
index: texture_id,
- kind: crate::AtlasTextureKind::Polychrome,
+ kind: crate::AtlasTextureKind::Path,
},
tile_id: TileId(tile_id),
padding: 0,
@@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
use crate::{
AtlasTextureId, AtlasTile, Background, Bounds, ContentMask, Corners, Edges, Hsla, Pixels,
- Point, Radians, ScaledPixels, Size, bounds_tree::BoundsTree,
+ Point, Radians, ScaledPixels, Size, bounds_tree::BoundsTree, point,
};
use std::{fmt::Debug, iter::Peekable, ops::Range, slice};
@@ -43,7 +43,13 @@ impl Scene {
self.surfaces.clear();
}
- #[allow(dead_code)]
+ #[cfg_attr(
+ all(
+ any(target_os = "linux", target_os = "freebsd"),
+ not(any(feature = "x11", feature = "wayland"))
+ ),
+ allow(dead_code)
+ )]
pub fn paths(&self) -> &[Path<ScaledPixels>] {
&self.paths
}
@@ -683,7 +689,6 @@ pub struct Path<P: Clone + Debug + Default + PartialEq> {
start: Point<P>,
current: Point<P>,
contour_count: usize,
- base_scale: f32,
}
impl Path<Pixels> {
@@ -702,35 +707,25 @@ impl Path<Pixels> {
content_mask: Default::default(),
color: Default::default(),
contour_count: 0,
- base_scale: 1.0,
}
}
- /// Set the base scale of the path.
- 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> {
+ /// Scale this path by the given factor.
+ pub fn scale(&self, factor: f32) -> Path<ScaledPixels> {
Path {
id: self.id,
order: self.order,
- bounds: self.bounds.scale(self.base_scale * factor),
- content_mask: self.content_mask.scale(self.base_scale * factor),
+ bounds: self.bounds.scale(factor),
+ content_mask: self.content_mask.scale(factor),
vertices: self
.vertices
.iter()
- .map(|vertex| vertex.scale(self.base_scale * factor))
+ .map(|vertex| vertex.scale(factor))
.collect(),
- start: self
- .start
- .map(|start| start.scale(self.base_scale * factor)),
- current: self.current.scale(self.base_scale * factor),
+ start: self.start.map(|start| start.scale(factor)),
+ current: self.current.scale(factor),
contour_count: self.contour_count,
color: self.color,
- base_scale: 1.0,
}
}
@@ -745,7 +740,10 @@ impl Path<Pixels> {
pub fn line_to(&mut self, to: Point<Pixels>) {
self.contour_count += 1;
if self.contour_count > 1 {
- self.push_triangle((self.start, self.current, to));
+ self.push_triangle(
+ (self.start, self.current, to),
+ (point(0., 1.), point(0., 1.), point(0., 1.)),
+ );
}
self.current = to;
}
@@ -754,15 +752,25 @@ impl Path<Pixels> {
pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) {
self.contour_count += 1;
if self.contour_count > 1 {
- self.push_triangle((self.start, self.current, to));
+ self.push_triangle(
+ (self.start, self.current, to),
+ (point(0., 1.), point(0., 1.), point(0., 1.)),
+ );
}
- self.push_triangle((self.current, ctrl, to));
+ self.push_triangle(
+ (self.current, ctrl, to),
+ (point(0., 0.), point(0.5, 0.), point(1., 1.)),
+ );
self.current = to;
}
/// Push a triangle to the Path.
- pub fn push_triangle(&mut self, xy: (Point<Pixels>, Point<Pixels>, Point<Pixels>)) {
+ pub fn push_triangle(
+ &mut self,
+ xy: (Point<Pixels>, Point<Pixels>, Point<Pixels>),
+ st: (Point<f32>, Point<f32>, Point<f32>),
+ ) {
self.bounds = self
.bounds
.union(&Bounds {
@@ -780,14 +788,17 @@ impl Path<Pixels> {
self.vertices.push(PathVertex {
xy_position: xy.0,
+ st_position: st.0,
content_mask: Default::default(),
});
self.vertices.push(PathVertex {
xy_position: xy.1,
+ st_position: st.1,
content_mask: Default::default(),
});
self.vertices.push(PathVertex {
xy_position: xy.2,
+ st_position: st.2,
content_mask: Default::default(),
});
}
@@ -803,6 +814,7 @@ impl From<Path<ScaledPixels>> for Primitive {
#[repr(C)]
pub(crate) struct PathVertex<P: Clone + Debug + Default + PartialEq> {
pub(crate) xy_position: Point<P>,
+ pub(crate) st_position: Point<f32>,
pub(crate) content_mask: ContentMask<P>,
}
@@ -810,6 +822,7 @@ impl PathVertex<Pixels> {
pub fn scale(&self, factor: f32) -> PathVertex<ScaledPixels> {
PathVertex {
xy_position: self.xy_position.scale(factor),
+ st_position: self.st_position,
content_mask: self.content_mask.scale(factor),
}
}
@@ -2658,7 +2658,7 @@ impl Window {
path.color = color.opacity(opacity);
self.next_frame
.scene
- .insert_primitive(path.apply_scale(scale_factor));
+ .insert_primitive(path.scale(scale_factor));
}
/// Paint an underline into the scene for the next frame at the current z-index.
@@ -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.
-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 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, you might get a 'Broken Pipe' error. Try using the RADV or Mesa drivers. (See [#13880](https://github.com/zed-industries/zed/issues/13880))