Detailed changes
@@ -128,6 +128,7 @@ mod macos {
"AtlasTile".into(),
"PathRasterizationInputIndex".into(),
"PathVertex_ScaledPixels".into(),
+ "PathRasterizationVertex".into(),
"ShadowInputIndex".into(),
"Shadow".into(),
"QuadInputIndex".into(),
@@ -1,11 +1,12 @@
use gpui::{
Application, Background, Bounds, ColorSpace, Context, MouseDownEvent, Path, PathBuilder,
PathStyle, Pixels, Point, Render, SharedString, StrokeOptions, Window, WindowOptions, canvas,
- div, linear_color_stop, linear_gradient, point, prelude::*, px, rgb, size,
+ div, linear_color_stop, linear_gradient, point, prelude::*, px, quad, rgb, size,
};
struct PaintingViewer {
default_lines: Vec<(Path<Pixels>, Background)>,
+ background_quads: Vec<(Bounds<Pixels>, Background)>,
lines: Vec<Vec<Point<Pixels>>>,
start: Point<Pixels>,
dashed: bool,
@@ -16,12 +17,148 @@ impl PaintingViewer {
fn new(_window: &mut Window, _cx: &mut Context<Self>) -> Self {
let mut lines = vec![];
+ // Black squares beneath transparent paths.
+ let background_quads = vec![
+ (
+ Bounds {
+ origin: point(px(70.), px(70.)),
+ size: size(px(40.), px(40.)),
+ },
+ gpui::black().into(),
+ ),
+ (
+ Bounds {
+ origin: point(px(170.), px(70.)),
+ size: size(px(40.), px(40.)),
+ },
+ gpui::black().into(),
+ ),
+ (
+ Bounds {
+ origin: point(px(270.), px(70.)),
+ size: size(px(40.), px(40.)),
+ },
+ gpui::black().into(),
+ ),
+ (
+ Bounds {
+ origin: point(px(370.), px(70.)),
+ size: size(px(40.), px(40.)),
+ },
+ gpui::black().into(),
+ ),
+ (
+ Bounds {
+ origin: point(px(450.), px(50.)),
+ size: size(px(80.), px(80.)),
+ },
+ gpui::black().into(),
+ ),
+ ];
+
+ // 50% opaque red path that extends across black quad.
+ let mut builder = PathBuilder::fill();
+ builder.move_to(point(px(50.), px(50.)));
+ builder.line_to(point(px(130.), px(50.)));
+ builder.line_to(point(px(130.), px(130.)));
+ builder.line_to(point(px(50.), px(130.)));
+ builder.close();
+ let path = builder.build().unwrap();
+ let mut red = rgb(0xFF0000);
+ red.a = 0.5;
+ lines.push((path, red.into()));
+
+ // 50% opaque blue path that extends across black quad.
+ let mut builder = PathBuilder::fill();
+ builder.move_to(point(px(150.), px(50.)));
+ builder.line_to(point(px(230.), px(50.)));
+ builder.line_to(point(px(230.), px(130.)));
+ builder.line_to(point(px(150.), px(130.)));
+ builder.close();
+ let path = builder.build().unwrap();
+ let mut blue = rgb(0x0000FF);
+ blue.a = 0.5;
+ lines.push((path, blue.into()));
+
+ // 50% opaque green path that extends across black quad.
+ let mut builder = PathBuilder::fill();
+ builder.move_to(point(px(250.), px(50.)));
+ builder.line_to(point(px(330.), px(50.)));
+ builder.line_to(point(px(330.), px(130.)));
+ builder.line_to(point(px(250.), px(130.)));
+ builder.close();
+ let path = builder.build().unwrap();
+ let mut green = rgb(0x00FF00);
+ green.a = 0.5;
+ lines.push((path, green.into()));
+
+ // 50% opaque black path that extends across black quad.
+ let mut builder = PathBuilder::fill();
+ builder.move_to(point(px(350.), px(50.)));
+ builder.line_to(point(px(430.), px(50.)));
+ builder.line_to(point(px(430.), px(130.)));
+ builder.line_to(point(px(350.), px(130.)));
+ builder.close();
+ let path = builder.build().unwrap();
+ let mut black = rgb(0x000000);
+ black.a = 0.5;
+ lines.push((path, black.into()));
+
+ // Two 50% opaque red circles overlapping - center should be darker red
+ let mut builder = PathBuilder::fill();
+ let center = point(px(530.), px(85.));
+ let radius = px(30.);
+ builder.move_to(point(center.x + radius, center.y));
+ builder.arc_to(
+ point(radius, radius),
+ px(0.),
+ false,
+ false,
+ point(center.x - radius, center.y),
+ );
+ builder.arc_to(
+ point(radius, radius),
+ px(0.),
+ false,
+ false,
+ point(center.x + radius, center.y),
+ );
+ builder.close();
+ let path = builder.build().unwrap();
+ let mut red1 = rgb(0xFF0000);
+ red1.a = 0.5;
+ lines.push((path, red1.into()));
+
+ let mut builder = PathBuilder::fill();
+ let center = point(px(570.), px(85.));
+ let radius = px(30.);
+ builder.move_to(point(center.x + radius, center.y));
+ builder.arc_to(
+ point(radius, radius),
+ px(0.),
+ false,
+ false,
+ point(center.x - radius, center.y),
+ );
+ builder.arc_to(
+ point(radius, radius),
+ px(0.),
+ false,
+ false,
+ point(center.x + radius, center.y),
+ );
+ builder.close();
+ let path = builder.build().unwrap();
+ let mut red2 = rgb(0xFF0000);
+ red2.a = 0.5;
+ lines.push((path, red2.into()));
+
// draw a Rust logo
let mut builder = lyon::path::Path::svg_builder();
lyon::extra::rust_logo::build_logo_path(&mut builder);
// move down the Path
let mut builder: PathBuilder = builder.into();
- builder.translate(point(px(10.), px(100.)));
+ builder.translate(point(px(10.), px(200.)));
builder.scale(0.9);
let path = builder.build().unwrap();
lines.push((path, gpui::black().into()));
@@ -30,10 +167,10 @@ impl PaintingViewer {
let mut builder = PathBuilder::fill();
builder.add_polygon(
&[
- point(px(150.), px(200.)),
- point(px(200.), px(125.)),
- point(px(200.), px(175.)),
- point(px(250.), px(100.)),
+ point(px(150.), px(300.)),
+ point(px(200.), px(225.)),
+ point(px(200.), px(275.)),
+ point(px(250.), px(200.)),
],
false,
);
@@ -42,17 +179,17 @@ impl PaintingViewer {
// draw a ⭐
let mut builder = PathBuilder::fill();
- builder.move_to(point(px(350.), px(100.)));
- builder.line_to(point(px(370.), px(160.)));
- builder.line_to(point(px(430.), px(160.)));
- builder.line_to(point(px(380.), px(200.)));
- builder.line_to(point(px(400.), px(260.)));
- builder.line_to(point(px(350.), px(220.)));
- builder.line_to(point(px(300.), px(260.)));
- builder.line_to(point(px(320.), px(200.)));
- builder.line_to(point(px(270.), px(160.)));
- builder.line_to(point(px(330.), px(160.)));
- builder.line_to(point(px(350.), px(100.)));
+ builder.move_to(point(px(350.), px(200.)));
+ builder.line_to(point(px(370.), px(260.)));
+ builder.line_to(point(px(430.), px(260.)));
+ builder.line_to(point(px(380.), px(300.)));
+ builder.line_to(point(px(400.), px(360.)));
+ builder.line_to(point(px(350.), px(320.)));
+ builder.line_to(point(px(300.), px(360.)));
+ builder.line_to(point(px(320.), px(300.)));
+ builder.line_to(point(px(270.), px(260.)));
+ builder.line_to(point(px(330.), px(260.)));
+ builder.line_to(point(px(350.), px(200.)));
let path = builder.build().unwrap();
lines.push((
path,
@@ -66,7 +203,7 @@ impl PaintingViewer {
// draw linear gradient
let square_bounds = Bounds {
- origin: point(px(450.), px(100.)),
+ origin: point(px(450.), px(200.)),
size: size(px(200.), px(80.)),
};
let height = square_bounds.size.height;
@@ -96,31 +233,31 @@ impl PaintingViewer {
// draw a pie chart
let center = point(px(96.), px(96.));
- let pie_center = point(px(775.), px(155.));
+ let pie_center = point(px(775.), px(255.));
let segments = [
(
- point(px(871.), px(155.)),
- point(px(747.), px(63.)),
+ point(px(871.), px(255.)),
+ point(px(747.), px(163.)),
rgb(0x1374e9),
),
(
- point(px(747.), px(63.)),
- point(px(679.), px(163.)),
+ point(px(747.), px(163.)),
+ point(px(679.), px(263.)),
rgb(0xe13527),
),
(
- point(px(679.), px(163.)),
- point(px(754.), px(249.)),
+ point(px(679.), px(263.)),
+ point(px(754.), px(349.)),
rgb(0x0751ce),
),
(
- point(px(754.), px(249.)),
- point(px(854.), px(210.)),
+ point(px(754.), px(349.)),
+ point(px(854.), px(310.)),
rgb(0x209742),
),
(
- point(px(854.), px(210.)),
- point(px(871.), px(155.)),
+ point(px(854.), px(310.)),
+ point(px(871.), px(255.)),
rgb(0xfbc10a),
),
];
@@ -140,11 +277,11 @@ impl PaintingViewer {
.with_line_width(1.)
.with_line_join(lyon::path::LineJoin::Bevel);
let mut builder = PathBuilder::stroke(px(1.)).with_style(PathStyle::Stroke(options));
- builder.move_to(point(px(40.), px(320.)));
+ builder.move_to(point(px(40.), px(420.)));
for i in 1..50 {
builder.line_to(point(
px(40.0 + i as f32 * 10.0),
- px(320.0 + (i as f32 * 10.0).sin() * 40.0),
+ px(420.0 + (i as f32 * 10.0).sin() * 40.0),
));
}
let path = builder.build().unwrap();
@@ -152,6 +289,7 @@ impl PaintingViewer {
Self {
default_lines: lines.clone(),
+ background_quads,
lines: vec![],
start: point(px(0.), px(0.)),
dashed: false,
@@ -185,6 +323,7 @@ fn button(
impl Render for PaintingViewer {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let default_lines = self.default_lines.clone();
+ let background_quads = self.background_quads.clone();
let lines = self.lines.clone();
let dashed = self.dashed;
@@ -221,6 +360,19 @@ impl Render for PaintingViewer {
canvas(
move |_, _, _| {},
move |_, _, window, _| {
+ // First draw background quads
+ for (bounds, color) in background_quads.iter() {
+ window.paint_quad(quad(
+ *bounds,
+ px(0.),
+ *color,
+ px(0.),
+ gpui::transparent_black(),
+ Default::default(),
+ ));
+ }
+
+ // Then draw the default paths on top
for (path, color) in default_lines {
window.paint_path(path, color);
}
@@ -303,6 +455,10 @@ fn main() {
|window, cx| cx.new(|cx| PaintingViewer::new(window, cx)),
)
.unwrap();
+ cx.on_window_closed(|cx| {
+ cx.quit();
+ })
+ .detach();
cx.activate(true);
});
}
@@ -0,0 +1,92 @@
+use gpui::{
+ Application, Background, Bounds, ColorSpace, Context, Path, PathBuilder, Pixels, Render,
+ TitlebarOptions, Window, WindowBounds, 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)>,
+ _painting: bool,
+}
+
+impl PaintingViewer {
+ fn new(_window: &mut Window, _cx: &mut Context<Self>) -> Self {
+ let mut lines = vec![];
+
+ // draw a lightening bolt ⚡
+ for _ in 0..2000 {
+ // draw a ⭐
+ let mut builder = PathBuilder::fill();
+ builder.move_to(point(px(350.), px(100.)));
+ builder.line_to(point(px(370.), px(160.)));
+ builder.line_to(point(px(430.), px(160.)));
+ builder.line_to(point(px(380.), px(200.)));
+ builder.line_to(point(px(400.), px(260.)));
+ builder.line_to(point(px(350.), px(220.)));
+ builder.line_to(point(px(300.), px(260.)));
+ builder.line_to(point(px(320.), px(200.)));
+ builder.line_to(point(px(270.), px(160.)));
+ builder.line_to(point(px(330.), px(160.)));
+ builder.line_to(point(px(350.), px(100.)));
+ let path = builder.build().unwrap();
+ lines.push((
+ path,
+ linear_gradient(
+ 180.,
+ linear_color_stop(rgb(0xFACC15), 0.7),
+ linear_color_stop(rgb(0xD56D0C), 1.),
+ )
+ .color_space(ColorSpace::Oklab),
+ ));
+ }
+
+ Self {
+ default_lines: lines,
+ _painting: false,
+ }
+ }
+}
+
+impl Render for PaintingViewer {
+ fn render(&mut self, window: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
+ window.request_animation_frame();
+ let lines = self.default_lines.clone();
+ div().size_full().child(
+ canvas(
+ move |_, _, _| {},
+ move |_, _, window, _| {
+ for (path, color) in lines {
+ window.paint_path(path, color);
+ }
+ },
+ )
+ .size_full(),
+ )
+ }
+}
+
+fn main() {
+ Application::new().run(|cx| {
+ cx.open_window(
+ WindowOptions {
+ titlebar: Some(TitlebarOptions {
+ title: Some("Vulkan".into()),
+ ..Default::default()
+ }),
+ 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)),
+ )
+ .unwrap();
+ cx.activate(true);
+ });
+}
@@ -809,7 +809,6 @@ pub(crate) struct AtlasTextureId {
pub(crate) enum AtlasTextureKind {
Monochrome = 0,
Polychrome = 1,
- Path = 2,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
@@ -10,8 +10,6 @@ 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 {
@@ -27,7 +25,6 @@ struct BladeAtlasState {
tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
initializations: Vec<AtlasTextureId>,
uploads: Vec<PendingUpload>,
- path_sample_count: u32,
}
#[cfg(gles)]
@@ -41,13 +38,11 @@ impl BladeAtlasState {
}
pub struct BladeTextureInfo {
- 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>, path_sample_count: u32) -> Self {
+ pub(crate) fn new(gpu: &Arc<gpu::Context>) -> Self {
BladeAtlas(Mutex::new(BladeAtlasState {
gpu: Arc::clone(gpu),
upload_belt: BufferBelt::new(BufferBeltDescriptor {
@@ -59,7 +54,6 @@ impl BladeAtlas {
tiles_by_key: Default::default(),
initializations: Vec::new(),
uploads: Vec::new(),
- path_sample_count,
}))
}
@@ -67,27 +61,6 @@ impl BladeAtlas {
self.0.lock().destroy();
}
- pub(crate) fn clear_textures(&self, texture_kind: AtlasTextureKind) {
- let mut lock = self.0.lock();
- let textures = &mut lock.storage[texture_kind];
- for texture in textures.iter_mut() {
- texture.clear();
- }
- }
-
- /// 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);
@@ -101,15 +74,8 @@ impl BladeAtlas {
pub fn get_texture_info(&self, id: AtlasTextureId) -> BladeTextureInfo {
let lock = self.0.lock();
let texture = &lock.storage[id];
- let size = texture.allocator.size();
BladeTextureInfo {
- size: gpu::Extent {
- width: size.width as u32,
- height: size.height as u32,
- depth: 1,
- },
raw_view: texture.raw_view,
- msaa_view: texture.msaa_view,
}
}
}
@@ -200,48 +166,8 @@ 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,
@@ -279,8 +205,6 @@ impl BladeAtlasState {
format,
raw,
raw_view,
- msaa,
- msaa_view,
live_atlas_keys: 0,
};
@@ -340,7 +264,6 @@ impl BladeAtlasState {
struct BladeAtlasStorage {
monochrome_textures: AtlasTextureList<BladeAtlasTexture>,
polychrome_textures: AtlasTextureList<BladeAtlasTexture>,
- path_textures: AtlasTextureList<BladeAtlasTexture>,
}
impl ops::Index<AtlasTextureKind> for BladeAtlasStorage {
@@ -349,7 +272,6 @@ 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,
}
}
}
@@ -359,7 +281,6 @@ 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,
}
}
}
@@ -370,7 +291,6 @@ 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()
}
@@ -384,9 +304,6 @@ 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);
- }
}
}
@@ -395,17 +312,11 @@ 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,
}
impl BladeAtlasTexture {
- fn clear(&mut self) {
- self.allocator.clear();
- }
-
fn allocate(&mut self, size: Size<DevicePixels>) -> Option<AtlasTile> {
let allocation = self.allocator.allocate(size.into())?;
let tile = AtlasTile {
@@ -424,12 +335,6 @@ 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,24 +1,19 @@
// Doing `if let` gives you nice scoping with passes/encoders
#![allow(irrefutable_let_patterns)]
-use super::{BladeAtlas, BladeContext, PATH_TEXTURE_FORMAT};
+use super::{BladeAtlas, BladeContext};
use crate::{
- AtlasTextureKind, AtlasTile, Background, Bounds, ContentMask, DevicePixels, GpuSpecs,
- MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch, Quad,
- ScaledPixels, Scene, Shadow, Size, Underline,
+ Background, Bounds, DevicePixels, GpuSpecs, MonochromeSprite, Path, Point, PolychromeSprite,
+ PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size, Underline,
};
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};
+use std::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)]
@@ -114,8 +109,15 @@ struct ShaderSurfacesData {
#[repr(C)]
struct PathSprite {
bounds: Bounds<ScaledPixels>,
+}
+
+#[derive(Clone, Debug)]
+#[repr(C)]
+struct PathRasterizationVertex {
+ xy_position: Point<ScaledPixels>,
+ st_position: Point<f32>,
color: Background,
- tile: AtlasTile,
+ bounds: Bounds<ScaledPixels>,
}
struct BladePipelines {
@@ -144,10 +146,7 @@ impl BladePipelines {
shader.check_struct_size::<SurfaceParams>();
shader.check_struct_size::<Quad>();
shader.check_struct_size::<Shadow>();
- assert_eq!(
- mem::size_of::<PathVertex<ScaledPixels>>(),
- shader.get_struct_size("PathVertex") as usize,
- );
+ shader.check_struct_size::<PathRasterizationVertex>();
shader.check_struct_size::<PathSprite>();
shader.check_struct_size::<Underline>();
shader.check_struct_size::<MonochromeSprite>();
@@ -205,9 +204,16 @@ impl BladePipelines {
},
depth_stencil: None,
fragment: Some(shader.at("fs_path_rasterization")),
+ // The original implementation was using ADDITIVE blende mode,
+ // I don't know why
+ // color_targets: &[gpu::ColorTargetState {
+ // format: PATH_TEXTURE_FORMAT,
+ // blend: Some(gpu::BlendState::ADDITIVE),
+ // write_mask: gpu::ColorWrites::default(),
+ // }],
color_targets: &[gpu::ColorTargetState {
- format: PATH_TEXTURE_FORMAT,
- blend: Some(gpu::BlendState::ADDITIVE),
+ format: surface_info.format,
+ blend: Some(gpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
write_mask: gpu::ColorWrites::default(),
}],
multisample_state: gpu::MultisampleState {
@@ -226,7 +232,14 @@ impl BladePipelines {
},
depth_stencil: None,
fragment: Some(shader.at("fs_path")),
- color_targets,
+ color_targets: &[gpu::ColorTargetState {
+ format: surface_info.format,
+ blend: Some(gpu::BlendState {
+ color: gpu::BlendComponent::OVER,
+ alpha: gpu::BlendComponent::ADDITIVE,
+ }),
+ write_mask: gpu::ColorWrites::default(),
+ }],
multisample_state: gpu::MultisampleState::default(),
}),
underlines: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
@@ -317,12 +330,15 @@ 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,
path_sample_count: u32,
+ path_intermediate_texture: gpu::Texture,
+ path_intermediate_texture_view: gpu::TextureView,
+ path_intermediate_msaa_texture: Option<gpu::Texture>,
+ path_intermediate_msaa_texture_view: Option<gpu::TextureView>,
}
impl BladeRenderer {
@@ -352,21 +368,43 @@ impl BladeRenderer {
let path_sample_count = std::env::var("ZED_PATH_SAMPLE_COUNT")
.ok()
.and_then(|v| v.parse().ok())
- .unwrap_or(DEFAULT_PATH_SAMPLE_COUNT);
+ .or_else(|| {
+ [4, 2, 1]
+ .into_iter()
+ .find(|count| context.gpu.supports_texture_sample_count(*count))
+ })
+ .unwrap_or(1);
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, path_sample_count));
+ let atlas = Arc::new(BladeAtlas::new(&context.gpu));
let atlas_sampler = context.gpu.create_sampler(gpu::SamplerDesc {
- name: "atlas",
+ name: "path rasterization sampler",
mag_filter: gpu::FilterMode::Linear,
min_filter: gpu::FilterMode::Linear,
..Default::default()
});
+ let (path_intermediate_texture, path_intermediate_texture_view) =
+ create_path_intermediate_texture(
+ &context.gpu,
+ surface.info().format,
+ config.size.width,
+ config.size.height,
+ );
+ let (path_intermediate_msaa_texture, path_intermediate_msaa_texture_view) =
+ create_msaa_texture_if_needed(
+ &context.gpu,
+ surface.info().format,
+ config.size.width,
+ config.size.height,
+ path_sample_count,
+ )
+ .unzip();
+
#[cfg(target_os = "macos")]
let core_video_texture_cache = unsafe {
CVMetalTextureCache::new(
@@ -383,12 +421,15 @@ impl BladeRenderer {
last_sync_point: None,
pipelines,
instance_belt,
- path_tiles: HashMap::default(),
atlas,
atlas_sampler,
#[cfg(target_os = "macos")]
core_video_texture_cache,
path_sample_count,
+ path_intermediate_texture,
+ path_intermediate_texture_view,
+ path_intermediate_msaa_texture,
+ path_intermediate_msaa_texture_view,
})
}
@@ -441,6 +482,35 @@ impl BladeRenderer {
self.surface_config.size = gpu_size;
self.gpu
.reconfigure_surface(&mut self.surface, self.surface_config);
+ self.gpu.destroy_texture(self.path_intermediate_texture);
+ self.gpu
+ .destroy_texture_view(self.path_intermediate_texture_view);
+ if let Some(msaa_texture) = self.path_intermediate_msaa_texture {
+ self.gpu.destroy_texture(msaa_texture);
+ }
+ if let Some(msaa_view) = self.path_intermediate_msaa_texture_view {
+ self.gpu.destroy_texture_view(msaa_view);
+ }
+ let (path_intermediate_texture, path_intermediate_texture_view) =
+ create_path_intermediate_texture(
+ &self.gpu,
+ self.surface.info().format,
+ gpu_size.width,
+ gpu_size.height,
+ );
+ self.path_intermediate_texture = path_intermediate_texture;
+ self.path_intermediate_texture_view = path_intermediate_texture_view;
+ let (path_intermediate_msaa_texture, path_intermediate_msaa_texture_view) =
+ create_msaa_texture_if_needed(
+ &self.gpu,
+ self.surface.info().format,
+ gpu_size.width,
+ gpu_size.height,
+ self.path_sample_count,
+ )
+ .unzip();
+ self.path_intermediate_msaa_texture = path_intermediate_msaa_texture;
+ self.path_intermediate_msaa_texture_view = path_intermediate_msaa_texture_view;
}
}
@@ -491,76 +561,63 @@ impl BladeRenderer {
}
#[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);
+ fn draw_paths_to_intermediate(
+ &mut self,
+ paths: &[Path<ScaledPixels>],
+ width: f32,
+ height: f32,
+ ) {
+ self.command_encoder
+ .init_texture(self.path_intermediate_texture);
+ if let Some(msaa_texture) = self.path_intermediate_msaa_texture {
+ self.command_encoder.init_texture(msaa_texture);
}
- for (texture_id, vertices) in vertices_by_texture_id {
- let tex_info = self.atlas.get_texture_info(texture_id);
+ let target = if let Some(msaa_view) = self.path_intermediate_msaa_texture_view {
+ gpu::RenderTarget {
+ view: msaa_view,
+ init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
+ finish_op: gpu::FinishOp::ResolveTo(self.path_intermediate_texture_view),
+ }
+ } else {
+ gpu::RenderTarget {
+ view: self.path_intermediate_texture_view,
+ init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
+ finish_op: gpu::FinishOp::Store,
+ }
+ };
+ if let mut pass = self.command_encoder.render(
+ "rasterize paths",
+ gpu::RenderTargetSet {
+ colors: &[target],
+ depth_stencil: None,
+ },
+ ) {
let globals = GlobalParams {
- viewport_size: [tex_info.size.width as f32, tex_info.size.height as f32],
+ viewport_size: [width, height],
premultiplied_alpha: 0,
pad: 0,
};
-
+ let mut encoder = pass.with(&self.pipelines.path_rasterization);
+
+ let mut vertices = Vec::new();
+ for path in paths {
+ vertices.extend(path.vertices.iter().map(|v| PathRasterizationVertex {
+ xy_position: v.xy_position,
+ st_position: v.st_position,
+ color: path.color,
+ bounds: path.bounds.intersect(&path.content_mask.bounds),
+ }));
+ }
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,
+ encoder.bind(
+ 0,
+ &ShaderPathRasterizationData {
+ globals,
+ b_path_vertices: vertex_buf,
},
- ) {
- 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);
- }
+ );
+ encoder.draw(0, vertices.len() as u32, 0, 1);
}
}
@@ -572,12 +629,20 @@ impl BladeRenderer {
self.gpu.destroy_command_encoder(&mut self.command_encoder);
self.pipelines.destroy(&self.gpu);
self.gpu.destroy_surface(&mut self.surface);
+ self.gpu.destroy_texture(self.path_intermediate_texture);
+ self.gpu
+ .destroy_texture_view(self.path_intermediate_texture_view);
+ if let Some(msaa_texture) = self.path_intermediate_msaa_texture {
+ self.gpu.destroy_texture(msaa_texture);
+ }
+ if let Some(msaa_view) = self.path_intermediate_msaa_texture_view {
+ self.gpu.destroy_texture_view(msaa_view);
+ }
}
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");
@@ -597,7 +662,7 @@ impl BladeRenderer {
pad: 0,
};
- if let mut pass = self.command_encoder.render(
+ let mut pass = self.command_encoder.render(
"main",
gpu::RenderTargetSet {
colors: &[gpu::RenderTarget {
@@ -607,209 +672,235 @@ impl BladeRenderer {
}],
depth_stencil: None,
},
- ) {
- profiling::scope!("render pass");
- for batch in scene.batches() {
- match batch {
- PrimitiveBatch::Quads(quads) => {
- let instance_buf =
- unsafe { self.instance_belt.alloc_typed(quads, &self.gpu) };
- let mut encoder = pass.with(&self.pipelines.quads);
- encoder.bind(
- 0,
- &ShaderQuadsData {
- globals,
- b_quads: instance_buf,
- },
- );
- encoder.draw(0, 4, 0, quads.len() as u32);
- }
- PrimitiveBatch::Shadows(shadows) => {
- let instance_buf =
- unsafe { self.instance_belt.alloc_typed(shadows, &self.gpu) };
- let mut encoder = pass.with(&self.pipelines.shadows);
- encoder.bind(
- 0,
- &ShaderShadowsData {
- globals,
- b_shadows: instance_buf,
- },
- );
- encoder.draw(0, 4, 0, shadows.len() as u32);
- }
- PrimitiveBatch::Paths(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 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);
+ profiling::scope!("render pass");
+ for batch in scene.batches() {
+ match batch {
+ PrimitiveBatch::Quads(quads) => {
+ let instance_buf = unsafe { self.instance_belt.alloc_typed(quads, &self.gpu) };
+ let mut encoder = pass.with(&self.pipelines.quads);
+ encoder.bind(
+ 0,
+ &ShaderQuadsData {
+ globals,
+ b_quads: instance_buf,
+ },
+ );
+ encoder.draw(0, 4, 0, quads.len() as u32);
+ }
+ PrimitiveBatch::Shadows(shadows) => {
+ let instance_buf =
+ unsafe { self.instance_belt.alloc_typed(shadows, &self.gpu) };
+ let mut encoder = pass.with(&self.pipelines.shadows);
+ encoder.bind(
+ 0,
+ &ShaderShadowsData {
+ globals,
+ b_shadows: instance_buf,
+ },
+ );
+ encoder.draw(0, 4, 0, shadows.len() as u32);
+ }
+ PrimitiveBatch::Paths(paths) => {
+ let Some(first_path) = paths.first() else {
+ continue;
+ };
+ drop(pass);
+ self.draw_paths_to_intermediate(
+ paths,
+ self.surface_config.size.width as f32,
+ self.surface_config.size.height as f32,
+ );
+ pass = self.command_encoder.render(
+ "main",
+ gpu::RenderTargetSet {
+ colors: &[gpu::RenderTarget {
+ view: frame.texture_view(),
+ init_op: gpu::InitOp::Load,
+ finish_op: gpu::FinishOp::Store,
+ }],
+ depth_stencil: None,
+ },
+ );
+ let mut encoder = pass.with(&self.pipelines.paths);
+ // When copying paths from the intermediate texture to the drawable,
+ // each pixel must only be copied once, in case of transparent paths.
+ //
+ // If all paths have the same draw order, then their bounds are all
+ // disjoint, so we can copy each path's bounds individually. If this
+ // batch combines different draw orders, we perform a single copy
+ // for a minimal spanning rect.
+ let sprites = if paths.last().unwrap().order == first_path.order {
+ paths
+ .iter()
+ .map(|path| PathSprite {
+ bounds: path.bounds,
+ })
+ .collect()
+ } else {
+ let mut bounds = first_path.bounds;
+ for path in paths.iter().skip(1) {
+ bounds = bounds.union(&path.bounds);
}
- }
- PrimitiveBatch::Underlines(underlines) => {
- let instance_buf =
- unsafe { self.instance_belt.alloc_typed(underlines, &self.gpu) };
- let mut encoder = pass.with(&self.pipelines.underlines);
- encoder.bind(
- 0,
- &ShaderUnderlinesData {
- globals,
- b_underlines: instance_buf,
- },
- );
- encoder.draw(0, 4, 0, underlines.len() as u32);
- }
- PrimitiveBatch::MonochromeSprites {
- texture_id,
- sprites,
- } => {
- let tex_info = self.atlas.get_texture_info(texture_id);
- let instance_buf =
- unsafe { self.instance_belt.alloc_typed(sprites, &self.gpu) };
- let mut encoder = pass.with(&self.pipelines.mono_sprites);
- encoder.bind(
- 0,
- &ShaderMonoSpritesData {
- globals,
- t_sprite: tex_info.raw_view,
- s_sprite: self.atlas_sampler,
- b_mono_sprites: instance_buf,
- },
- );
- encoder.draw(0, 4, 0, sprites.len() as u32);
- }
- PrimitiveBatch::PolychromeSprites {
- texture_id,
- sprites,
- } => {
- let tex_info = self.atlas.get_texture_info(texture_id);
- let instance_buf =
- unsafe { self.instance_belt.alloc_typed(sprites, &self.gpu) };
- let mut encoder = pass.with(&self.pipelines.poly_sprites);
- encoder.bind(
- 0,
- &ShaderPolySpritesData {
- globals,
- t_sprite: tex_info.raw_view,
- s_sprite: self.atlas_sampler,
- b_poly_sprites: instance_buf,
- },
- );
- encoder.draw(0, 4, 0, sprites.len() as u32);
- }
- PrimitiveBatch::Surfaces(surfaces) => {
- let mut _encoder = pass.with(&self.pipelines.surfaces);
-
- for surface in surfaces {
- #[cfg(not(target_os = "macos"))]
- {
- let _ = surface;
- continue;
- };
-
- #[cfg(target_os = "macos")]
- {
- let (t_y, t_cb_cr) = unsafe {
- use core_foundation::base::TCFType as _;
- use std::ptr;
-
- assert_eq!(
+ vec![PathSprite { bounds }]
+ };
+ let instance_buf =
+ unsafe { self.instance_belt.alloc_typed(&sprites, &self.gpu) };
+ encoder.bind(
+ 0,
+ &ShaderPathsData {
+ globals,
+ t_sprite: self.path_intermediate_texture_view,
+ s_sprite: self.atlas_sampler,
+ b_path_sprites: instance_buf,
+ },
+ );
+ encoder.draw(0, 4, 0, sprites.len() as u32);
+ }
+ PrimitiveBatch::Underlines(underlines) => {
+ let instance_buf =
+ unsafe { self.instance_belt.alloc_typed(underlines, &self.gpu) };
+ let mut encoder = pass.with(&self.pipelines.underlines);
+ encoder.bind(
+ 0,
+ &ShaderUnderlinesData {
+ globals,
+ b_underlines: instance_buf,
+ },
+ );
+ encoder.draw(0, 4, 0, underlines.len() as u32);
+ }
+ PrimitiveBatch::MonochromeSprites {
+ texture_id,
+ sprites,
+ } => {
+ let tex_info = self.atlas.get_texture_info(texture_id);
+ let instance_buf =
+ unsafe { self.instance_belt.alloc_typed(sprites, &self.gpu) };
+ let mut encoder = pass.with(&self.pipelines.mono_sprites);
+ encoder.bind(
+ 0,
+ &ShaderMonoSpritesData {
+ globals,
+ t_sprite: tex_info.raw_view,
+ s_sprite: self.atlas_sampler,
+ b_mono_sprites: instance_buf,
+ },
+ );
+ encoder.draw(0, 4, 0, sprites.len() as u32);
+ }
+ PrimitiveBatch::PolychromeSprites {
+ texture_id,
+ sprites,
+ } => {
+ let tex_info = self.atlas.get_texture_info(texture_id);
+ let instance_buf =
+ unsafe { self.instance_belt.alloc_typed(sprites, &self.gpu) };
+ let mut encoder = pass.with(&self.pipelines.poly_sprites);
+ encoder.bind(
+ 0,
+ &ShaderPolySpritesData {
+ globals,
+ t_sprite: tex_info.raw_view,
+ s_sprite: self.atlas_sampler,
+ b_poly_sprites: instance_buf,
+ },
+ );
+ encoder.draw(0, 4, 0, sprites.len() as u32);
+ }
+ PrimitiveBatch::Surfaces(surfaces) => {
+ let mut _encoder = pass.with(&self.pipelines.surfaces);
+
+ for surface in surfaces {
+ #[cfg(not(target_os = "macos"))]
+ {
+ let _ = surface;
+ continue;
+ };
+
+ #[cfg(target_os = "macos")]
+ {
+ let (t_y, t_cb_cr) = unsafe {
+ use core_foundation::base::TCFType as _;
+ use std::ptr;
+
+ assert_eq!(
surface.image_buffer.get_pixel_format(),
core_video::pixel_buffer::kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
);
- let y_texture = self
- .core_video_texture_cache
- .create_texture_from_image(
- surface.image_buffer.as_concrete_TypeRef(),
- ptr::null(),
- metal::MTLPixelFormat::R8Unorm,
- surface.image_buffer.get_width_of_plane(0),
- surface.image_buffer.get_height_of_plane(0),
- 0,
- )
- .unwrap();
- let cb_cr_texture = self
- .core_video_texture_cache
- .create_texture_from_image(
- surface.image_buffer.as_concrete_TypeRef(),
- ptr::null(),
- metal::MTLPixelFormat::RG8Unorm,
- surface.image_buffer.get_width_of_plane(1),
- surface.image_buffer.get_height_of_plane(1),
- 1,
- )
- .unwrap();
- (
- gpu::TextureView::from_metal_texture(
- &objc2::rc::Retained::retain(
- foreign_types::ForeignTypeRef::as_ptr(
- y_texture.as_texture_ref(),
- )
- as *mut objc2::runtime::ProtocolObject<
- dyn objc2_metal::MTLTexture,
- >,
+ let y_texture = self
+ .core_video_texture_cache
+ .create_texture_from_image(
+ surface.image_buffer.as_concrete_TypeRef(),
+ ptr::null(),
+ metal::MTLPixelFormat::R8Unorm,
+ surface.image_buffer.get_width_of_plane(0),
+ surface.image_buffer.get_height_of_plane(0),
+ 0,
+ )
+ .unwrap();
+ let cb_cr_texture = self
+ .core_video_texture_cache
+ .create_texture_from_image(
+ surface.image_buffer.as_concrete_TypeRef(),
+ ptr::null(),
+ metal::MTLPixelFormat::RG8Unorm,
+ surface.image_buffer.get_width_of_plane(1),
+ surface.image_buffer.get_height_of_plane(1),
+ 1,
+ )
+ .unwrap();
+ (
+ gpu::TextureView::from_metal_texture(
+ &objc2::rc::Retained::retain(
+ foreign_types::ForeignTypeRef::as_ptr(
+ y_texture.as_texture_ref(),
)
- .unwrap(),
- gpu::TexelAspects::COLOR,
- ),
- gpu::TextureView::from_metal_texture(
- &objc2::rc::Retained::retain(
- foreign_types::ForeignTypeRef::as_ptr(
- cb_cr_texture.as_texture_ref(),
- )
- as *mut objc2::runtime::ProtocolObject<
- dyn objc2_metal::MTLTexture,
- >,
+ as *mut objc2::runtime::ProtocolObject<
+ dyn objc2_metal::MTLTexture,
+ >,
+ )
+ .unwrap(),
+ gpu::TexelAspects::COLOR,
+ ),
+ gpu::TextureView::from_metal_texture(
+ &objc2::rc::Retained::retain(
+ foreign_types::ForeignTypeRef::as_ptr(
+ cb_cr_texture.as_texture_ref(),
)
- .unwrap(),
- gpu::TexelAspects::COLOR,
- ),
- )
- };
-
- _encoder.bind(
- 0,
- &ShaderSurfacesData {
- globals,
- surface_locals: SurfaceParams {
- bounds: surface.bounds.into(),
- content_mask: surface.content_mask.bounds.into(),
- },
- t_y,
- t_cb_cr,
- s_surface: self.atlas_sampler,
+ as *mut objc2::runtime::ProtocolObject<
+ dyn objc2_metal::MTLTexture,
+ >,
+ )
+ .unwrap(),
+ gpu::TexelAspects::COLOR,
+ ),
+ )
+ };
+
+ _encoder.bind(
+ 0,
+ &ShaderSurfacesData {
+ globals,
+ surface_locals: SurfaceParams {
+ bounds: surface.bounds.into(),
+ content_mask: surface.content_mask.bounds.into(),
},
- );
+ t_y,
+ t_cb_cr,
+ s_surface: self.atlas_sampler,
+ },
+ );
- _encoder.draw(0, 4, 0, 1);
- }
+ _encoder.draw(0, 4, 0, 1);
}
}
}
}
}
+ drop(pass);
self.command_encoder.present(frame);
let sync_point = self.gpu.submit(&mut self.command_encoder);
@@ -817,9 +908,79 @@ 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_path_intermediate_texture(
+ gpu: &gpu::Context,
+ format: gpu::TextureFormat,
+ width: u32,
+ height: u32,
+) -> (gpu::Texture, gpu::TextureView) {
+ let texture = gpu.create_texture(gpu::TextureDesc {
+ name: "path intermediate",
+ format,
+ size: gpu::Extent {
+ width,
+ height,
+ depth: 1,
+ },
+ array_layer_count: 1,
+ mip_level_count: 1,
+ sample_count: 1,
+ dimension: gpu::TextureDimension::D2,
+ usage: gpu::TextureUsage::COPY | gpu::TextureUsage::RESOURCE | gpu::TextureUsage::TARGET,
+ external: None,
+ });
+ let texture_view = gpu.create_texture_view(
+ texture,
+ gpu::TextureViewDesc {
+ name: "path intermediate view",
+ format,
+ dimension: gpu::ViewDimension::D2,
+ subresources: &Default::default(),
+ },
+ );
+ (texture, texture_view)
+}
+
+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: "path intermediate 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: "path intermediate msaa view",
+ format,
+ dimension: gpu::ViewDimension::D2,
+ subresources: &Default::default(),
+ },
+ );
+
+ Some((texture_msaa, texture_view_msaa))
+}
@@ -924,16 +924,19 @@ fn fs_shadow(input: ShadowVarying) -> @location(0) vec4<f32> {
// --- path rasterization --- //
-struct PathVertex {
+struct PathRasterizationVertex {
xy_position: vec2<f32>,
st_position: vec2<f32>,
- content_mask: Bounds,
+ color: Background,
+ bounds: Bounds,
}
-var<storage, read> b_path_vertices: array<PathVertex>;
+
+var<storage, read> b_path_vertices: array<PathRasterizationVertex>;
struct PathRasterizationVarying {
@builtin(position) position: vec4<f32>,
@location(0) st_position: vec2<f32>,
+ @location(1) vertex_id: u32,
//TODO: use `clip_distance` once Naga supports it
@location(3) clip_distances: vec4<f32>,
}
@@ -945,40 +948,54 @@ fn vs_path_rasterization(@builtin(vertex_index) vertex_id: u32) -> PathRasteriza
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);
+ out.vertex_id = vertex_id;
+ out.clip_distances = distance_from_clip_rect_impl(v.xy_position, v.bounds);
return out;
}
@fragment
-fn fs_path_rasterization(input: PathRasterizationVarying) -> @location(0) f32 {
+fn fs_path_rasterization(input: PathRasterizationVarying) -> @location(0) vec4<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;
+ return vec4<f32>(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);
+ let v = b_path_vertices[input.vertex_id];
+ let background = v.color;
+ let bounds = v.bounds;
+
+ var alpha: f32;
+ if (length(vec2<f32>(dx.x, dy.x)) < 0.001) {
+ // If the gradient is too small, return a solid color.
+ alpha = 1.0;
+ } else {
+ 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);
+ alpha = saturate(0.5 - distance);
+ }
+ let gradient_color = prepare_gradient_color(
+ background.tag,
+ background.color_space,
+ background.solid,
+ background.colors,
+ );
+ let color = gradient_color(background, input.position.xy, bounds,
+ gradient_color.solid, gradient_color.color0, gradient_color.color1);
+ return vec4<f32>(color.rgb * color.a * alpha, color.a * alpha);
}
// --- paths --- //
struct PathSprite {
bounds: Bounds,
- color: Background,
- tile: AtlasTile,
}
var<storage, read> b_path_sprites: array<PathSprite>;
struct PathVarying {
@builtin(position) position: 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>,
- @location(4) @interpolate(flat) color1: vec4<f32>,
+ @location(0) texture_coords: vec2<f32>,
}
@vertex
@@ -986,33 +1003,22 @@ fn vs_path(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) insta
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.
+ let device_position = to_device_position(unit_vertex, sprite.bounds);
+ // For screen-space intermediate texture, convert screen position to texture coordinates
+ let screen_position = sprite.bounds.origin + unit_vertex * sprite.bounds.size;
+ let texture_coords = screen_position / globals.viewport_size;
var out = PathVarying();
- out.position = to_device_position(unit_vertex, sprite.bounds);
- out.tile_position = to_tile_position(unit_vertex, sprite.tile);
- out.instance_id = instance_id;
+ out.position = device_position;
+ out.texture_coords = texture_coords;
- let gradient = prepare_gradient_color(
- sprite.color.tag,
- sprite.color.color_space,
- sprite.color.solid,
- sprite.color.colors
- );
- out.color_solid = gradient.solid;
- out.color0 = gradient.color0;
- out.color1 = gradient.color1;
return out;
}
@fragment
fn fs_path(input: PathVarying) -> @location(0) vec4<f32> {
- 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, mask);
+ let sample = textureSample(t_sprite, s_sprite, input.texture_coords);
+ return sample;
}
// --- underlines --- //
@@ -13,53 +13,25 @@ use std::borrow::Cow;
pub(crate) struct MetalAtlas(Mutex<MetalAtlasState>);
impl MetalAtlas {
- pub(crate) fn new(device: Device, path_sample_count: u32) -> Self {
+ pub(crate) fn new(device: Device) -> 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,
}))
}
pub(crate) fn metal_texture(&self, id: AtlasTextureId) -> metal::Texture {
self.0.lock().texture(id).metal_texture.clone()
}
-
- 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>,
- texture_kind: AtlasTextureKind,
- ) -> Option<AtlasTile> {
- self.0.lock().allocate(size, texture_kind)
- }
-
- 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();
- }
- }
}
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 {
@@ -94,7 +66,6 @@ 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
@@ -128,7 +99,6 @@ 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
@@ -173,31 +143,14 @@ 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();
@@ -209,7 +162,6 @@ impl MetalAtlasState {
},
allocator: etagere::BucketedAtlasAllocator::new(size.into()),
metal_texture: AssertSend(metal_texture),
- msaa_texture: AssertSend(msaa_texture),
live_atlas_keys: 0,
};
@@ -226,7 +178,6 @@ 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()
}
@@ -236,15 +187,10 @@ struct MetalAtlasTexture {
id: AtlasTextureId,
allocator: BucketedAtlasAllocator,
metal_texture: AssertSend<metal::Texture>,
- msaa_texture: AssertSend<Option<metal::Texture>>,
live_atlas_keys: u32,
}
impl MetalAtlasTexture {
- fn clear(&mut self) {
- self.allocator.clear();
- }
-
fn allocate(&mut self, size: Size<DevicePixels>) -> Option<AtlasTile> {
let allocation = self.allocator.allocate(size.into())?;
let tile = AtlasTile {
@@ -1,27 +1,30 @@
use super::metal_atlas::MetalAtlas;
use crate::{
- AtlasTextureId, AtlasTextureKind, AtlasTile, Background, Bounds, ContentMask, DevicePixels,
- MonochromeSprite, PaintSurface, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch,
- Quad, ScaledPixels, Scene, Shadow, Size, Surface, Underline, point, size,
+ AtlasTextureId, Background, Bounds, ContentMask, DevicePixels, MonochromeSprite, PaintSurface,
+ Path, Point, PolychromeSprite, PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size,
+ Surface, Underline, point, size,
};
-use anyhow::{Context as _, Result};
+use anyhow::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, MTLPixelFormat, MTLResourceOptions, NSRange};
+use metal::{
+ CAMetalLayer, CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange,
+ RenderPassColorAttachmentDescriptorRef,
+};
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
@@ -111,6 +114,17 @@ pub(crate) struct MetalRenderer {
instance_buffer_pool: Arc<Mutex<InstanceBufferPool>>,
sprite_atlas: Arc<MetalAtlas>,
core_video_texture_cache: core_video::metal_texture_cache::CVMetalTextureCache,
+ path_intermediate_texture: Option<metal::Texture>,
+ path_intermediate_msaa_texture: Option<metal::Texture>,
+ path_sample_count: u32,
+}
+
+#[repr(C)]
+pub struct PathRasterizationVertex {
+ pub xy_position: Point<ScaledPixels>,
+ pub st_position: Point<f32>,
+ pub color: Background,
+ pub bounds: Bounds<ScaledPixels>,
}
impl MetalRenderer {
@@ -175,10 +189,10 @@ impl MetalRenderer {
"paths_rasterization",
"path_rasterization_vertex",
"path_rasterization_fragment",
- MTLPixelFormat::R16Float,
+ MTLPixelFormat::BGRA8Unorm,
PATH_SAMPLE_COUNT,
);
- let path_sprites_pipeline_state = build_pipeline_state(
+ let path_sprites_pipeline_state = build_path_sprite_pipeline_state(
&device,
&library,
"path_sprites",
@@ -236,7 +250,7 @@ impl MetalRenderer {
);
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 =
CVMetalTextureCache::new(None, device.clone(), None).unwrap();
@@ -257,6 +271,9 @@ impl MetalRenderer {
instance_buffer_pool,
sprite_atlas,
core_video_texture_cache,
+ path_intermediate_texture: None,
+ path_intermediate_msaa_texture: None,
+ path_sample_count: PATH_SAMPLE_COUNT,
}
}
@@ -289,6 +306,31 @@ impl MetalRenderer {
setDrawableSize: size
];
}
+ let device_pixels_size = Size {
+ width: DevicePixels(size.width as i32),
+ height: DevicePixels(size.height as i32),
+ };
+ self.update_path_intermediate_textures(device_pixels_size);
+ }
+
+ fn update_path_intermediate_textures(&mut self, size: Size<DevicePixels>) {
+ let texture_descriptor = metal::TextureDescriptor::new();
+ texture_descriptor.set_width(size.width.0 as u64);
+ texture_descriptor.set_height(size.height.0 as u64);
+ texture_descriptor.set_pixel_format(metal::MTLPixelFormat::BGRA8Unorm);
+ texture_descriptor
+ .set_usage(metal::MTLTextureUsage::RenderTarget | metal::MTLTextureUsage::ShaderRead);
+ self.path_intermediate_texture = Some(self.device.new_texture(&texture_descriptor));
+
+ if self.path_sample_count > 1 {
+ let mut msaa_descriptor = texture_descriptor.clone();
+ msaa_descriptor.set_texture_type(metal::MTLTextureType::D2Multisample);
+ msaa_descriptor.set_storage_mode(metal::MTLStorageMode::Private);
+ msaa_descriptor.set_sample_count(self.path_sample_count as _);
+ self.path_intermediate_msaa_texture = Some(self.device.new_texture(&msaa_descriptor));
+ } else {
+ self.path_intermediate_msaa_texture = None;
+ }
}
pub fn update_transparency(&self, _transparent: bool) {
@@ -374,38 +416,18 @@ impl MetalRenderer {
) -> Result<metal::CommandBuffer> {
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();
-
- 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);
+ let mut instance_offset = 0;
- command_encoder.set_viewport(metal::MTLViewport {
- originX: 0.0,
- originY: 0.0,
- width: i32::from(viewport_size.width) as f64,
- height: i32::from(viewport_size.height) as f64,
- znear: 0.0,
- zfar: 1.0,
- });
+ let mut command_encoder = new_command_encoder(
+ command_buffer,
+ drawable,
+ viewport_size,
+ |color_attachment| {
+ color_attachment.set_load_action(metal::MTLLoadAction::Clear);
+ color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., alpha));
+ },
+ );
for batch in scene.batches() {
let ok = match batch {
@@ -414,29 +436,53 @@ impl MetalRenderer {
instance_buffer,
&mut instance_offset,
viewport_size,
- command_encoder,
+ &command_encoder,
),
PrimitiveBatch::Quads(quads) => self.draw_quads(
quads,
instance_buffer,
&mut instance_offset,
viewport_size,
- command_encoder,
- ),
- PrimitiveBatch::Paths(paths) => self.draw_paths(
- paths,
- &path_tiles,
- instance_buffer,
- &mut instance_offset,
- viewport_size,
- command_encoder,
+ &command_encoder,
),
+ PrimitiveBatch::Paths(paths) => {
+ command_encoder.end_encoding();
+
+ let did_draw = self.draw_paths_to_intermediate(
+ paths,
+ instance_buffer,
+ &mut instance_offset,
+ viewport_size,
+ command_buffer,
+ );
+
+ command_encoder = new_command_encoder(
+ command_buffer,
+ drawable,
+ viewport_size,
+ |color_attachment| {
+ color_attachment.set_load_action(metal::MTLLoadAction::Load);
+ },
+ );
+
+ if did_draw {
+ self.draw_paths_from_intermediate(
+ paths,
+ instance_buffer,
+ &mut instance_offset,
+ viewport_size,
+ &command_encoder,
+ )
+ } else {
+ false
+ }
+ }
PrimitiveBatch::Underlines(underlines) => self.draw_underlines(
underlines,
instance_buffer,
&mut instance_offset,
viewport_size,
- command_encoder,
+ &command_encoder,
),
PrimitiveBatch::MonochromeSprites {
texture_id,
@@ -447,7 +493,7 @@ impl MetalRenderer {
instance_buffer,
&mut instance_offset,
viewport_size,
- command_encoder,
+ &command_encoder,
),
PrimitiveBatch::PolychromeSprites {
texture_id,
@@ -458,17 +504,16 @@ impl MetalRenderer {
instance_buffer,
&mut instance_offset,
viewport_size,
- command_encoder,
+ &command_encoder,
),
PrimitiveBatch::Surfaces(surfaces) => self.draw_surfaces(
surfaces,
instance_buffer,
&mut instance_offset,
viewport_size,
- command_encoder,
+ &command_encoder,
),
};
-
if !ok {
command_encoder.end_encoding();
anyhow::bail!(
@@ -493,104 +538,90 @@ impl MetalRenderer {
Ok(command_buffer.to_owned())
}
- fn rasterize_paths(
+ fn draw_paths_to_intermediate(
&self,
paths: &[Path<ScaledPixels>],
instance_buffer: &mut InstanceBuffer,
instance_offset: &mut usize,
+ viewport_size: Size<DevicePixels>,
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);
+ ) -> bool {
+ if paths.is_empty() {
+ return true;
}
+ let Some(intermediate_texture) = &self.path_intermediate_texture else {
+ return false;
+ };
- 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 render_pass_descriptor = metal::RenderPassDescriptor::new();
+ let color_attachment = render_pass_descriptor
+ .color_attachments()
+ .object_at(0)
+ .unwrap();
+ color_attachment.set_load_action(metal::MTLLoadAction::Clear);
+ color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., 0.));
- 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 _,
- );
+ if let Some(msaa_texture) = &self.path_intermediate_msaa_texture {
+ color_attachment.set_texture(Some(msaa_texture));
+ color_attachment.set_resolve_texture(Some(intermediate_texture));
+ color_attachment.set_store_action(metal::MTLStoreAction::MultisampleResolve);
+ } else {
+ color_attachment.set_texture(Some(intermediate_texture));
+ color_attachment.set_store_action(metal::MTLStoreAction::Store);
+ }
- 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,
- );
- }
+ 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.draw_primitives(
- metal::MTLPrimitiveType::Triangle,
- 0,
- vertices.len() as u64,
- );
+ align_offset(instance_offset);
+ let mut vertices = Vec::new();
+ for path in paths {
+ vertices.extend(path.vertices.iter().map(|v| PathRasterizationVertex {
+ xy_position: v.xy_position,
+ st_position: v.st_position,
+ color: path.color,
+ bounds: path.bounds.intersect(&path.content_mask.bounds),
+ }));
+ }
+ 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 {
command_encoder.end_encoding();
- *instance_offset = next_offset;
+ return false;
}
+ command_encoder.set_vertex_buffer(
+ PathRasterizationInputIndex::Vertices as u64,
+ Some(&instance_buffer.metal_buffer),
+ *instance_offset as u64,
+ );
+ command_encoder.set_vertex_bytes(
+ PathRasterizationInputIndex::ViewportSize as u64,
+ mem::size_of_val(&viewport_size) as u64,
+ &viewport_size as *const Size<DevicePixels> as *const _,
+ );
+ command_encoder.set_fragment_buffer(
+ PathRasterizationInputIndex::Vertices as u64,
+ Some(&instance_buffer.metal_buffer),
+ *instance_offset as u64,
+ );
+ 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,
+ );
+ *instance_offset = next_offset;
- Some(tiles)
+ command_encoder.end_encoding();
+ true
}
fn draw_shadows(
@@ -715,18 +746,21 @@ impl MetalRenderer {
true
}
- fn draw_paths(
+ fn draw_paths_from_intermediate(
&self,
paths: &[Path<ScaledPixels>],
- tiles_by_path_id: &HashMap<PathId, AtlasTile>,
instance_buffer: &mut InstanceBuffer,
instance_offset: &mut usize,
viewport_size: Size<DevicePixels>,
command_encoder: &metal::RenderCommandEncoderRef,
) -> bool {
- if paths.is_empty() {
+ let Some(ref first_path) = paths.first() else {
return true;
- }
+ };
+
+ let Some(ref intermediate_texture) = self.path_intermediate_texture else {
+ return false;
+ };
command_encoder.set_render_pipeline_state(&self.path_sprites_pipeline_state);
command_encoder.set_vertex_buffer(
@@ -740,88 +774,65 @@ impl MetalRenderer {
&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();
+ command_encoder.set_fragment_texture(
+ SpriteInputIndex::AtlasTexture as u64,
+ Some(intermediate_texture),
+ );
- 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;
- }
+ // When copying paths from the intermediate texture to the drawable,
+ // each pixel must only be copied once, in case of transparent paths.
+ //
+ // If all paths have the same draw order, then their bounds are all
+ // disjoint, so we can copy each path's bounds individually. If this
+ // batch combines different draw orders, we perform a single copy
+ // for a minimal spanning rect.
+ let sprites;
+ if paths.last().unwrap().order == first_path.order {
+ sprites = paths
+ .iter()
+ .map(|path| PathSprite {
+ bounds: path.bounds,
+ })
+ .collect();
+ } else {
+ let mut bounds = first_path.bounds;
+ for path in paths.iter().skip(1) {
+ bounds = bounds.union(&path.bounds);
}
+ sprites = vec![PathSprite { bounds }];
+ }
- 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(
- 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));
+ align_offset(instance_offset);
+ 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;
+ }
- 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(
+ SpriteInputIndex::Sprites as u64,
+ Some(&instance_buffer.metal_buffer),
+ *instance_offset as u64,
+ );
- let buffer_contents = unsafe {
- (instance_buffer.metal_buffer.contents() as *mut u8).add(*instance_offset)
- };
+ 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,
+ );
+ }
- unsafe {
- ptr::copy_nonoverlapping(
- sprites.as_ptr() as *const u8,
- buffer_contents,
- sprite_bytes_len,
- );
- }
+ command_encoder.draw_primitives_instanced(
+ metal::MTLPrimitiveType::Triangle,
+ 0,
+ 6,
+ sprites.len() as u64,
+ );
+ *instance_offset = next_offset;
- command_encoder.draw_primitives_instanced(
- metal::MTLPrimitiveType::Triangle,
- 0,
- 6,
- sprites.len() as u64,
- );
- *instance_offset = next_offset;
- sprites.clear();
- }
- }
true
}
@@ -1136,6 +1147,33 @@ impl MetalRenderer {
}
}
+fn new_command_encoder<'a>(
+ command_buffer: &'a metal::CommandBufferRef,
+ drawable: &'a metal::MetalDrawableRef,
+ viewport_size: Size<DevicePixels>,
+ configure_color_attachment: impl Fn(&RenderPassColorAttachmentDescriptorRef),
+) -> &'a metal::RenderCommandEncoderRef {
+ let render_pass_descriptor = metal::RenderPassDescriptor::new();
+ let color_attachment = render_pass_descriptor
+ .color_attachments()
+ .object_at(0)
+ .unwrap();
+ color_attachment.set_texture(Some(drawable.texture()));
+ color_attachment.set_store_action(metal::MTLStoreAction::Store);
+ configure_color_attachment(color_attachment);
+
+ let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
+ command_encoder.set_viewport(metal::MTLViewport {
+ originX: 0.0,
+ originY: 0.0,
+ width: i32::from(viewport_size.width) as f64,
+ height: i32::from(viewport_size.height) as f64,
+ znear: 0.0,
+ zfar: 1.0,
+ });
+ command_encoder
+}
+
fn build_pipeline_state(
device: &metal::DeviceRef,
library: &metal::LibraryRef,
@@ -1170,6 +1208,40 @@ fn build_pipeline_state(
.expect("could not create render pipeline state")
}
+fn build_path_sprite_pipeline_state(
+ device: &metal::DeviceRef,
+ library: &metal::LibraryRef,
+ label: &str,
+ vertex_fn_name: &str,
+ fragment_fn_name: &str,
+ pixel_format: metal::MTLPixelFormat,
+) -> 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()));
+ 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::OneMinusSourceAlpha);
+ color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::One);
+
+ device
+ .new_render_pipeline_state(&descriptor)
+ .expect("could not create render pipeline state")
+}
+
fn build_path_rasterization_pipeline_state(
device: &metal::DeviceRef,
library: &metal::LibraryRef,
@@ -1192,7 +1264,7 @@ fn build_path_rasterization_pipeline_state(
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);
+ descriptor.set_alpha_to_coverage_enabled(false);
}
let color_attachment = descriptor.color_attachments().object_at(0).unwrap();
color_attachment.set_pixel_format(pixel_format);
@@ -1201,8 +1273,8 @@ fn build_path_rasterization_pipeline_state(
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);
+ color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
+ color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
device
.new_render_pipeline_state(&descriptor)
@@ -1257,15 +1329,13 @@ enum SurfaceInputIndex {
#[repr(C)]
enum PathRasterizationInputIndex {
Vertices = 0,
- AtlasTextureSize = 1,
+ ViewportSize = 1,
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[repr(C)]
pub struct PathSprite {
pub bounds: Bounds<ScaledPixels>,
- pub color: Background,
- pub tile: AtlasTile,
}
#[derive(Clone, Debug, Eq, PartialEq)]
@@ -701,107 +701,117 @@ fragment float4 polychrome_sprite_fragment(
struct PathRasterizationVertexOutput {
float4 position [[position]];
float2 st_position;
+ uint vertex_id [[flat]];
float clip_rect_distance [[clip_distance]][4];
};
struct PathRasterizationFragmentInput {
float4 position [[position]];
float2 st_position;
+ uint vertex_id [[flat]];
};
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];
+ uint vertex_id [[vertex_id]],
+ constant PathRasterizationVertex *vertices [[buffer(PathRasterizationInputIndex_Vertices)]],
+ constant Size_DevicePixels *atlas_size [[buffer(PathRasterizationInputIndex_ViewportSize)]]
+) {
+ PathRasterizationVertex 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);
+ float4 position = float4(
+ vertex_position * float2(2. / atlas_size->width, -2. / atlas_size->height) + float2(-1., 1.),
+ 0.,
+ 1.
+ );
return PathRasterizationVertexOutput{
- float4(vertex_position / viewport_size * float2(2., -2.) +
- float2(-1., 1.),
- 0., 1.),
+ position,
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}};
+ vertex_id,
+ {
+ v.xy_position.x - v.bounds.origin.x,
+ v.bounds.origin.x + v.bounds.size.width - v.xy_position.x,
+ v.xy_position.y - v.bounds.origin.y,
+ v.bounds.origin.y + v.bounds.size.height - v.xy_position.y
+ }
+ };
}
-fragment float4 path_rasterization_fragment(PathRasterizationFragmentInput input
- [[stage_in]]) {
+fragment float4 path_rasterization_fragment(
+ PathRasterizationFragmentInput input [[stage_in]],
+ constant PathRasterizationVertex *vertices [[buffer(PathRasterizationInputIndex_Vertices)]]
+) {
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.);
+
+ PathRasterizationVertex v = vertices[input.vertex_id];
+ Background background = v.color;
+ Bounds_ScaledPixels path_bounds = v.bounds;
+ float alpha;
+ if (length(float2(dx.x, dy.x)) < 0.001) {
+ alpha = 1.0;
+ } else {
+ 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);
+ alpha = saturate(0.5 - distance);
+ }
+
+ GradientColor gradient_color = prepare_fill_color(
+ background.tag,
+ background.color_space,
+ background.solid,
+ background.colors[0].color,
+ background.colors[1].color
+ );
+
+ float4 color = fill_color(
+ background,
+ input.position.xy,
+ path_bounds,
+ gradient_color.solid,
+ gradient_color.color0,
+ gradient_color.color1
+ );
+ return float4(color.rgb * color.a * alpha, alpha * color.a);
}
struct PathSpriteVertexOutput {
float4 position [[position]];
- float2 tile_position;
- uint sprite_id [[flat]];
- float4 solid_color [[flat]];
- float4 color0 [[flat]];
- float4 color1 [[flat]];
+ float2 texture_coords;
};
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)]]) {
-
+ 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)]]
+) {
float2 unit_vertex = unit_vertices[unit_vertex_id];
PathSprite sprite = sprites[sprite_id];
// 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,
- sprite.color.color_space,
- sprite.color.solid,
- sprite.color.colors[0].color,
- sprite.color.colors[1].color
- );
+ float2 screen_position = float2(sprite.bounds.origin.x, sprite.bounds.origin.y) + unit_vertex * float2(sprite.bounds.size.width, sprite.bounds.size.height);
+ float2 texture_coords = screen_position / float2(viewport_size->width, viewport_size->height);
return PathSpriteVertexOutput{
device_position,
- tile_position,
- sprite_id,
- gradient.solid,
- gradient.color0,
- gradient.color1
+ texture_coords
};
}
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;
+ PathSpriteVertexOutput input [[stage_in]],
+ texture2d<float> intermediate_texture [[texture(SpriteInputIndex_AtlasTexture)]]
+) {
+ constexpr sampler intermediate_texture_sampler(mag_filter::linear, min_filter::linear);
+ return intermediate_texture.sample(intermediate_texture_sampler, input.texture_coords);
}
struct SurfaceVertexOutput {
@@ -341,7 +341,7 @@ impl PlatformAtlas for TestAtlas {
crate::AtlasTile {
texture_id: AtlasTextureId {
index: texture_id,
- kind: crate::AtlasTextureKind::Path,
+ kind: crate::AtlasTextureKind::Monochrome,
},
tile_id: TileId(tile_id),
padding: 0,
@@ -43,17 +43,6 @@ impl Scene {
self.surfaces.clear();
}
- #[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
- }
-
pub fn len(&self) -> usize {
self.paint_operations.len()
}
@@ -681,7 +670,7 @@ pub(crate) struct PathId(pub(crate) usize);
#[derive(Clone, Debug)]
pub struct Path<P: Clone + Debug + Default + PartialEq> {
pub(crate) id: PathId,
- order: DrawOrder,
+ pub(crate) order: DrawOrder,
pub(crate) bounds: Bounds<P>,
pub(crate) content_mask: ContentMask<P>,
pub(crate) vertices: Vec<PathVertex<P>>,