Revert "Revert recent anti-aliasing improvements (#24289)" and fix selection top right corner radius issue (#24342)

Release Notes:

- N/A

----

To fix #24289 mention issue and revert PathBuilder and MSAA.

I'm sorry about of this, in #22808 I was forgotten this bit of detail.


![image](https://github.com/user-attachments/assets/112afda2-088c-41d0-83bd-808f6cd2f9d5)

So, add `move_to` here, we can fix the selection top right corner radius
issue.

## After change

<img width="1383" alt="image"
src="https://github.com/user-attachments/assets/28ea103c-d652-41d6-bbe0-7fd042d81e77"
/>
This commit is contained in:
Jason Lee 2025-02-06 17:03:23 +08:00 committed by GitHub
parent 1f2205d75c
commit f08b1d78ec
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 586 additions and 113 deletions

76
Cargo.lock generated
View file

@ -1868,7 +1868,7 @@ dependencies = [
[[package]] [[package]]
name = "blade-graphics" name = "blade-graphics"
version = "0.6.0" version = "0.6.0"
source = "git+https://github.com/kvark/blade?rev=091a8401033847bb9b6ace3fcf70448d069621c5#091a8401033847bb9b6ace3fcf70448d069621c5" source = "git+https://github.com/kvark/blade?rev=b16f5c7bd873c7126f48c82c39e7ae64602ae74f#b16f5c7bd873c7126f48c82c39e7ae64602ae74f"
dependencies = [ dependencies = [
"ash", "ash",
"ash-window", "ash-window",
@ -1900,7 +1900,7 @@ dependencies = [
[[package]] [[package]]
name = "blade-macros" name = "blade-macros"
version = "0.3.0" version = "0.3.0"
source = "git+https://github.com/kvark/blade?rev=091a8401033847bb9b6ace3fcf70448d069621c5#091a8401033847bb9b6ace3fcf70448d069621c5" source = "git+https://github.com/kvark/blade?rev=b16f5c7bd873c7126f48c82c39e7ae64602ae74f#b16f5c7bd873c7126f48c82c39e7ae64602ae74f"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1910,7 +1910,7 @@ dependencies = [
[[package]] [[package]]
name = "blade-util" name = "blade-util"
version = "0.2.0" version = "0.2.0"
source = "git+https://github.com/kvark/blade?rev=091a8401033847bb9b6ace3fcf70448d069621c5#091a8401033847bb9b6ace3fcf70448d069621c5" source = "git+https://github.com/kvark/blade?rev=b16f5c7bd873c7126f48c82c39e7ae64602ae74f#b16f5c7bd873c7126f48c82c39e7ae64602ae74f"
dependencies = [ dependencies = [
"blade-graphics", "blade-graphics",
"bytemuck", "bytemuck",
@ -4725,6 +4725,12 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d" checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d"
[[package]]
name = "float_next_after"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8"
[[package]] [[package]]
name = "flume" name = "flume"
version = "0.11.1" version = "0.11.1"
@ -5476,6 +5482,7 @@ dependencies = [
"inventory", "inventory",
"itertools 0.14.0", "itertools 0.14.0",
"log", "log",
"lyon",
"media", "media",
"metal", "metal",
"naga", "naga",
@ -7498,6 +7505,69 @@ dependencies = [
"url", "url",
] ]
[[package]]
name = "lyon"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e7f9cda98b5430809e63ca5197b06c7d191bf7e26dfc467d5a3f0290e2a74f"
dependencies = [
"lyon_algorithms",
"lyon_extra",
"lyon_tessellation",
]
[[package]]
name = "lyon_algorithms"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f13c9be19d257c7d37e70608ed858e8eab4b2afcea2e3c9a622e892acbf43c08"
dependencies = [
"lyon_path",
"num-traits",
]
[[package]]
name = "lyon_extra"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ca94c7bf1e2557c2798989c43416822c12fc5dcc5e17cc3307ef0e71894a955"
dependencies = [
"lyon_path",
"thiserror 1.0.69",
]
[[package]]
name = "lyon_geom"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8af69edc087272df438b3ee436c4bb6d7c04aa8af665cfd398feae627dbd8570"
dependencies = [
"arrayvec",
"euclid",
"num-traits",
]
[[package]]
name = "lyon_path"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e0b8aec2f58586f6eef237985b9a9b7cb3a3aff4417c575075cf95bf925252e"
dependencies = [
"lyon_geom",
"num-traits",
]
[[package]]
name = "lyon_tessellation"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "579d42360a4b09846eff2feef28f538696c7d6c7439bfa65874ff3cbe0951b2c"
dependencies = [
"float_next_after",
"lyon_path",
"num-traits",
]
[[package]] [[package]]
name = "mac" name = "mac"
version = "0.1.1" version = "0.1.1"

View file

@ -376,9 +376,9 @@ async-watch = "0.3.1"
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] } async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
base64 = "0.22" base64 = "0.22"
bitflags = "2.6.0" bitflags = "2.6.0"
blade-graphics = { git = "https://github.com/kvark/blade", rev = "091a8401033847bb9b6ace3fcf70448d069621c5" } blade-graphics = { git = "https://github.com/kvark/blade", rev = "b16f5c7bd873c7126f48c82c39e7ae64602ae74f" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "091a8401033847bb9b6ace3fcf70448d069621c5" } blade-macros = { git = "https://github.com/kvark/blade", rev = "b16f5c7bd873c7126f48c82c39e7ae64602ae74f" }
blade-util = { git = "https://github.com/kvark/blade", rev = "091a8401033847bb9b6ace3fcf70448d069621c5" } blade-util = { git = "https://github.com/kvark/blade", rev = "b16f5c7bd873c7126f48c82c39e7ae64602ae74f" }
naga = { version = "23.1.0", features = ["wgsl-in"] } naga = { version = "23.1.0", features = ["wgsl-in"] }
blake3 = "1.5.3" blake3 = "1.5.3"
bytes = "1.0" bytes = "1.0"

View file

@ -8149,8 +8149,9 @@ impl HighlightedRange {
}; };
let top_curve_width = curve_width(first_line.start_x, first_line.end_x); let top_curve_width = curve_width(first_line.start_x, first_line.end_x);
let mut path = gpui::Path::new(first_top_right - top_curve_width); let mut builder = gpui::PathBuilder::fill();
path.curve_to(first_top_right + curve_height, first_top_right); builder.move_to(first_top_right - top_curve_width);
builder.curve_to(first_top_right + curve_height, first_top_right);
let mut iter = lines.iter().enumerate().peekable(); let mut iter = lines.iter().enumerate().peekable();
while let Some((ix, line)) = iter.next() { while let Some((ix, line)) = iter.next() {
@ -8161,42 +8162,42 @@ impl HighlightedRange {
match next_top_right.x.partial_cmp(&bottom_right.x).unwrap() { match next_top_right.x.partial_cmp(&bottom_right.x).unwrap() {
Ordering::Equal => { Ordering::Equal => {
path.line_to(bottom_right); builder.line_to(bottom_right);
} }
Ordering::Less => { Ordering::Less => {
let curve_width = curve_width(next_top_right.x, bottom_right.x); let curve_width = curve_width(next_top_right.x, bottom_right.x);
path.line_to(bottom_right - curve_height); builder.line_to(bottom_right - curve_height);
if self.corner_radius > Pixels::ZERO { if self.corner_radius > Pixels::ZERO {
path.curve_to(bottom_right - curve_width, bottom_right); builder.curve_to(bottom_right - curve_width, bottom_right);
} }
path.line_to(next_top_right + curve_width); builder.line_to(next_top_right + curve_width);
if self.corner_radius > Pixels::ZERO { if self.corner_radius > Pixels::ZERO {
path.curve_to(next_top_right + curve_height, next_top_right); builder.curve_to(next_top_right + curve_height, next_top_right);
} }
} }
Ordering::Greater => { Ordering::Greater => {
let curve_width = curve_width(bottom_right.x, next_top_right.x); let curve_width = curve_width(bottom_right.x, next_top_right.x);
path.line_to(bottom_right - curve_height); builder.line_to(bottom_right - curve_height);
if self.corner_radius > Pixels::ZERO { if self.corner_radius > Pixels::ZERO {
path.curve_to(bottom_right + curve_width, bottom_right); builder.curve_to(bottom_right + curve_width, bottom_right);
} }
path.line_to(next_top_right - curve_width); builder.line_to(next_top_right - curve_width);
if self.corner_radius > Pixels::ZERO { if self.corner_radius > Pixels::ZERO {
path.curve_to(next_top_right + curve_height, next_top_right); builder.curve_to(next_top_right + curve_height, next_top_right);
} }
} }
} }
} else { } else {
let curve_width = curve_width(line.start_x, line.end_x); let curve_width = curve_width(line.start_x, line.end_x);
path.line_to(bottom_right - curve_height); builder.line_to(bottom_right - curve_height);
if self.corner_radius > Pixels::ZERO { if self.corner_radius > Pixels::ZERO {
path.curve_to(bottom_right - curve_width, bottom_right); builder.curve_to(bottom_right - curve_width, bottom_right);
} }
let bottom_left = point(line.start_x, bottom_right.y); let bottom_left = point(line.start_x, bottom_right.y);
path.line_to(bottom_left + curve_width); builder.line_to(bottom_left + curve_width);
if self.corner_radius > Pixels::ZERO { if self.corner_radius > Pixels::ZERO {
path.curve_to(bottom_left - curve_height, bottom_left); builder.curve_to(bottom_left - curve_height, bottom_left);
} }
} }
} }
@ -8204,24 +8205,26 @@ impl HighlightedRange {
if first_line.start_x > last_line.start_x { if first_line.start_x > last_line.start_x {
let curve_width = curve_width(last_line.start_x, first_line.start_x); let curve_width = curve_width(last_line.start_x, first_line.start_x);
let second_top_left = point(last_line.start_x, start_y + self.line_height); let second_top_left = point(last_line.start_x, start_y + self.line_height);
path.line_to(second_top_left + curve_height); builder.line_to(second_top_left + curve_height);
if self.corner_radius > Pixels::ZERO { if self.corner_radius > Pixels::ZERO {
path.curve_to(second_top_left + curve_width, second_top_left); builder.curve_to(second_top_left + curve_width, second_top_left);
} }
let first_bottom_left = point(first_line.start_x, second_top_left.y); let first_bottom_left = point(first_line.start_x, second_top_left.y);
path.line_to(first_bottom_left - curve_width); builder.line_to(first_bottom_left - curve_width);
if self.corner_radius > Pixels::ZERO { if self.corner_radius > Pixels::ZERO {
path.curve_to(first_bottom_left - curve_height, first_bottom_left); builder.curve_to(first_bottom_left - curve_height, first_bottom_left);
} }
} }
path.line_to(first_top_left + curve_height); builder.line_to(first_top_left + curve_height);
if self.corner_radius > Pixels::ZERO { if self.corner_radius > Pixels::ZERO {
path.curve_to(first_top_left + top_curve_width, first_top_left); builder.curve_to(first_top_left + top_curve_width, first_top_left);
} }
path.line_to(first_top_right - top_curve_width); builder.line_to(first_top_right - top_curve_width);
window.paint_path(path, self.color); if let Ok(path) = builder.build() {
window.paint_path(path, self.color);
}
} }
} }

View file

@ -108,6 +108,7 @@ thiserror.workspace = true
util.workspace = true util.workspace = true
uuid.workspace = true uuid.workspace = true
waker-fn = "1.2.0" waker-fn = "1.2.0"
lyon = "1.0"
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
block = "0.1" block = "0.1"
@ -205,6 +206,7 @@ rand.workspace = true
util = { workspace = true, features = ["test-support"] } util = { workspace = true, features = ["test-support"] }
http_client = { workspace = true, features = ["test-support"] } http_client = { workspace = true, features = ["test-support"] }
unicode-segmentation.workspace = true unicode-segmentation.workspace = true
lyon = { version = "1.0", features = ["extra"] }
[target.'cfg(target_os = "windows")'.build-dependencies] [target.'cfg(target_os = "windows")'.build-dependencies]
embed-resource = "3.0" embed-resource = "3.0"

View file

@ -218,13 +218,17 @@ impl Render for GradientViewer {
let height = square_bounds.size.height; let height = square_bounds.size.height;
let horizontal_offset = height; let horizontal_offset = height;
let vertical_offset = px(30.); let vertical_offset = px(30.);
let mut path = gpui::Path::new(square_bounds.bottom_left()); let mut builder = gpui::PathBuilder::fill();
path.line_to(square_bounds.origin + point(horizontal_offset, vertical_offset)); builder.move_to(square_bounds.bottom_left());
path.line_to( builder
.line_to(square_bounds.origin + point(horizontal_offset, vertical_offset));
builder.line_to(
square_bounds.top_right() + point(-horizontal_offset, vertical_offset), square_bounds.top_right() + point(-horizontal_offset, vertical_offset),
); );
path.line_to(square_bounds.bottom_right());
path.line_to(square_bounds.bottom_left()); builder.line_to(square_bounds.bottom_right());
builder.line_to(square_bounds.bottom_left());
let path = builder.build().unwrap();
window.paint_path( window.paint_path(
path, path,
linear_gradient( linear_gradient(

View file

@ -1,46 +1,62 @@
use gpui::{ use gpui::{
canvas, div, point, prelude::*, px, size, App, Application, Bounds, Context, MouseDownEvent, canvas, div, linear_color_stop, linear_gradient, point, prelude::*, px, rgb, size, Application,
Path, Pixels, Point, Render, Window, WindowOptions, Background, Bounds, ColorSpace, Context, MouseDownEvent, Path, PathBuilder, PathStyle, Pixels,
Point, Render, StrokeOptions, Window, WindowOptions,
}; };
struct PaintingViewer { struct PaintingViewer {
default_lines: Vec<Path<Pixels>>, default_lines: Vec<(Path<Pixels>, Background)>,
lines: Vec<Vec<Point<Pixels>>>, lines: Vec<Vec<Point<Pixels>>>,
start: Point<Pixels>, start: Point<Pixels>,
_painting: bool, _painting: bool,
} }
impl PaintingViewer { impl PaintingViewer {
fn new() -> Self { fn new(_window: &mut Window, _cx: &mut Context<Self>) -> Self {
let mut lines = vec![]; let mut lines = vec![];
// draw a line // draw a Rust logo
let mut path = Path::new(point(px(50.), px(180.))); let mut builder = lyon::path::Path::svg_builder();
path.line_to(point(px(100.), px(120.))); lyon::extra::rust_logo::build_logo_path(&mut builder);
// go back to close the path // move down the Path
path.line_to(point(px(100.), px(121.))); let mut builder: PathBuilder = builder.into();
path.line_to(point(px(50.), px(181.))); builder.translate(point(px(10.), px(100.)));
lines.push(path); builder.scale(0.9);
let path = builder.build().unwrap();
lines.push((path, gpui::black().into()));
// draw a lightening bolt ⚡ // draw a lightening bolt ⚡
let mut path = Path::new(point(px(150.), px(200.))); let mut builder = PathBuilder::fill();
path.line_to(point(px(200.), px(125.))); builder.move_to(point(px(150.), px(200.)));
path.line_to(point(px(200.), px(175.))); builder.line_to(point(px(200.), px(125.)));
path.line_to(point(px(250.), px(100.))); builder.line_to(point(px(200.), px(175.)));
lines.push(path); builder.line_to(point(px(250.), px(100.)));
let path = builder.build().unwrap();
lines.push((path, rgb(0x1d4ed8).into()));
// draw a ⭐ // draw a ⭐
let mut path = Path::new(point(px(350.), px(100.))); let mut builder = PathBuilder::fill();
path.line_to(point(px(370.), px(160.))); builder.move_to(point(px(350.), px(100.)));
path.line_to(point(px(430.), px(160.))); builder.line_to(point(px(370.), px(160.)));
path.line_to(point(px(380.), px(200.))); builder.line_to(point(px(430.), px(160.)));
path.line_to(point(px(400.), px(260.))); builder.line_to(point(px(380.), px(200.)));
path.line_to(point(px(350.), px(220.))); builder.line_to(point(px(400.), px(260.)));
path.line_to(point(px(300.), px(260.))); builder.line_to(point(px(350.), px(220.)));
path.line_to(point(px(320.), px(200.))); builder.line_to(point(px(300.), px(260.)));
path.line_to(point(px(270.), px(160.))); builder.line_to(point(px(320.), px(200.)));
path.line_to(point(px(330.), px(160.))); builder.line_to(point(px(270.), px(160.)));
path.line_to(point(px(350.), px(100.))); builder.line_to(point(px(330.), px(160.)));
lines.push(path); 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),
));
let square_bounds = Bounds { let square_bounds = Bounds {
origin: point(px(450.), px(100.)), origin: point(px(450.), px(100.)),
@ -49,18 +65,42 @@ impl PaintingViewer {
let height = square_bounds.size.height; let height = square_bounds.size.height;
let horizontal_offset = height; let horizontal_offset = height;
let vertical_offset = px(30.); let vertical_offset = px(30.);
let mut path = Path::new(square_bounds.bottom_left()); let mut builder = PathBuilder::fill();
path.curve_to( builder.move_to(square_bounds.bottom_left());
builder.curve_to(
square_bounds.origin + point(horizontal_offset, vertical_offset), square_bounds.origin + point(horizontal_offset, vertical_offset),
square_bounds.origin + point(px(0.0), vertical_offset), square_bounds.origin + point(px(0.0), vertical_offset),
); );
path.line_to(square_bounds.top_right() + point(-horizontal_offset, vertical_offset)); builder.line_to(square_bounds.top_right() + point(-horizontal_offset, vertical_offset));
path.curve_to( builder.curve_to(
square_bounds.bottom_right(), square_bounds.bottom_right(),
square_bounds.top_right() + point(px(0.0), vertical_offset), square_bounds.top_right() + point(px(0.0), vertical_offset),
); );
path.line_to(square_bounds.bottom_left()); builder.line_to(square_bounds.bottom_left());
lines.push(path); let path = builder.build().unwrap();
lines.push((
path,
linear_gradient(
180.,
linear_color_stop(gpui::blue(), 0.4),
linear_color_stop(gpui::red(), 1.),
),
));
// draw a wave
let options = StrokeOptions::default()
.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.)));
for i in 0..50 {
builder.line_to(point(
px(40.0 + i as f32 * 10.0),
px(320.0 + (i as f32 * 10.0).sin() * 40.0),
));
}
let path = builder.build().unwrap();
lines.push((path, gpui::green().into()));
Self { Self {
default_lines: lines.clone(), default_lines: lines.clone(),
@ -115,27 +155,28 @@ impl Render for PaintingViewer {
canvas( canvas(
move |_, _, _| {}, move |_, _, _| {},
move |_, _, window, _| { move |_, _, window, _| {
const STROKE_WIDTH: Pixels = px(2.0);
for path in default_lines { for (path, color) in default_lines {
window.paint_path(path, gpui::black()); window.paint_path(path, color);
} }
for points in lines { for points in lines {
let mut path = Path::new(points[0]); if points.len() < 2 {
for p in points.iter().skip(1) { continue;
path.line_to(*p);
} }
let mut last = points.last().unwrap(); let mut builder = PathBuilder::stroke(px(1.));
for p in points.iter().rev() { for (i, p) in points.into_iter().enumerate() {
let mut offset_x = px(0.); if i == 0 {
if last.x == p.x { builder.move_to(p);
offset_x = STROKE_WIDTH; } else {
builder.line_to(p);
} }
path.line_to(point(p.x + offset_x, p.y + STROKE_WIDTH));
last = p;
} }
window.paint_path(path, gpui::black()); if let Ok(path) = builder.build() {
window.paint_path(path, gpui::black());
}
} }
}, },
) )
@ -185,13 +226,13 @@ impl Render for PaintingViewer {
} }
fn main() { fn main() {
Application::new().run(|cx: &mut App| { Application::new().run(|cx| {
cx.open_window( cx.open_window(
WindowOptions { WindowOptions {
focus: true, focus: true,
..Default::default() ..Default::default()
}, },
|_, cx| cx.new(|_| PaintingViewer::new()), |window, cx| cx.new(|cx| PaintingViewer::new(window, cx)),
) )
.unwrap(); .unwrap();
cx.activate(true); cx.activate(true);

View file

@ -82,6 +82,7 @@ mod input;
mod interactive; mod interactive;
mod key_dispatch; mod key_dispatch;
mod keymap; mod keymap;
mod path_builder;
mod platform; mod platform;
pub mod prelude; pub mod prelude;
mod scene; mod scene;
@ -135,6 +136,7 @@ pub use input::*;
pub use interactive::*; pub use interactive::*;
use key_dispatch::*; use key_dispatch::*;
pub use keymap::*; pub use keymap::*;
pub use path_builder::*;
pub use platform::*; pub use platform::*;
pub use refineable::*; pub use refineable::*;
pub use scene::*; pub use scene::*;

View file

@ -0,0 +1,241 @@
use anyhow::Error;
use etagere::euclid::Vector2D;
use lyon::geom::Angle;
use lyon::tessellation::{
BuffersBuilder, FillTessellator, FillVertex, StrokeTessellator, StrokeVertex, VertexBuffers,
};
pub use lyon::math::Transform;
pub use lyon::tessellation::{FillOptions, FillRule, StrokeOptions};
use crate::{point, px, Path, Pixels, Point};
/// Style of the PathBuilder
pub enum PathStyle {
/// Stroke style
Stroke(StrokeOptions),
/// Fill style
Fill(FillOptions),
}
/// A [`Path`] builder.
pub struct PathBuilder {
raw: lyon::path::builder::WithSvg<lyon::path::BuilderImpl>,
transform: Option<lyon::math::Transform>,
/// PathStyle of the PathBuilder
pub style: PathStyle,
}
impl From<lyon::path::Builder> for PathBuilder {
fn from(builder: lyon::path::Builder) -> Self {
Self {
raw: builder.with_svg(),
..Default::default()
}
}
}
impl From<lyon::path::builder::WithSvg<lyon::path::BuilderImpl>> for PathBuilder {
fn from(raw: lyon::path::builder::WithSvg<lyon::path::BuilderImpl>) -> Self {
Self {
raw,
..Default::default()
}
}
}
impl From<lyon::math::Point> for Point<Pixels> {
fn from(p: lyon::math::Point) -> Self {
point(px(p.x), px(p.y))
}
}
impl From<Point<Pixels>> for lyon::math::Point {
fn from(p: Point<Pixels>) -> Self {
lyon::math::point(p.x.0, p.y.0)
}
}
impl Default for PathBuilder {
fn default() -> Self {
Self {
raw: lyon::path::Path::builder().with_svg(),
style: PathStyle::Fill(FillOptions::default()),
transform: None,
}
}
}
impl PathBuilder {
/// Creates a new [`PathBuilder`] to build a Stroke path.
pub fn stroke(width: Pixels) -> Self {
Self {
style: PathStyle::Stroke(StrokeOptions::default().with_line_width(width.0)),
..Self::default()
}
}
/// Creates a new [`PathBuilder`] to build a Fill path.
pub fn fill() -> Self {
Self::default()
}
/// Sets the style of the [`PathBuilder`].
pub fn with_style(self, style: PathStyle) -> Self {
Self { style, ..self }
}
/// Move the current point to the given point.
#[inline]
pub fn move_to(&mut self, to: Point<Pixels>) {
self.raw.move_to(to.into());
}
/// Draw a straight line from the current point to the given point.
#[inline]
pub fn line_to(&mut self, to: Point<Pixels>) {
self.raw.line_to(to.into());
}
/// Draw a curve from the current point to the given point, using the given control point.
#[inline]
pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) {
self.raw.quadratic_bezier_to(ctrl.into(), to.into());
}
/// Adds a cubic Bézier to the [`Path`] given its two control points
/// and its end point.
#[inline]
pub fn cubic_bezier_to(
&mut self,
to: Point<Pixels>,
control_a: Point<Pixels>,
control_b: Point<Pixels>,
) {
self.raw
.cubic_bezier_to(control_a.into(), control_b.into(), to.into());
}
/// Close the current sub-path.
#[inline]
pub fn close(&mut self) {
self.raw.close();
}
/// Applies a transform to the path.
#[inline]
pub fn transform(&mut self, transform: Transform) {
self.transform = Some(transform);
}
/// Applies a translation to the path.
#[inline]
pub fn translate(&mut self, to: Point<Pixels>) {
if let Some(transform) = self.transform {
self.transform = Some(transform.then_translate(Vector2D::new(to.x.0, to.y.0)));
} else {
self.transform = Some(Transform::translation(to.x.0, to.y.0))
}
}
/// Applies a scale to the path.
#[inline]
pub fn scale(&mut self, scale: f32) {
if let Some(transform) = self.transform {
self.transform = Some(transform.then_scale(scale, scale));
} else {
self.transform = Some(Transform::scale(scale, scale));
}
}
/// Applies a rotation to the path.
///
/// The `angle` is in degrees value in the range 0.0 to 360.0.
#[inline]
pub fn rotate(&mut self, angle: f32) {
let radians = angle.to_radians();
if let Some(transform) = self.transform {
self.transform = Some(transform.then_rotate(Angle::radians(radians)));
} else {
self.transform = Some(Transform::rotation(Angle::radians(radians)));
}
}
/// Builds into a [`Path`].
#[inline]
pub fn build(self) -> Result<Path<Pixels>, Error> {
let path = if let Some(transform) = self.transform {
self.raw.build().transformed(&transform)
} else {
self.raw.build()
};
match self.style {
PathStyle::Stroke(options) => Self::tessellate_stroke(&path, &options),
PathStyle::Fill(options) => Self::tessellate_fill(&path, &options),
}
}
fn tessellate_fill(
path: &lyon::path::Path,
options: &FillOptions,
) -> Result<Path<Pixels>, Error> {
// Will contain the result of the tessellation.
let mut buf: VertexBuffers<lyon::math::Point, u16> = VertexBuffers::new();
let mut tessellator = FillTessellator::new();
// Compute the tessellation.
tessellator.tessellate_path(
path,
options,
&mut BuffersBuilder::new(&mut buf, |vertex: FillVertex| vertex.position()),
)?;
Ok(Self::build_path(buf))
}
fn tessellate_stroke(
path: &lyon::path::Path,
options: &StrokeOptions,
) -> Result<Path<Pixels>, Error> {
// Will contain the result of the tessellation.
let mut buf: VertexBuffers<lyon::math::Point, u16> = VertexBuffers::new();
let mut tessellator = StrokeTessellator::new();
// Compute the tessellation.
tessellator.tessellate_path(
path,
options,
&mut BuffersBuilder::new(&mut buf, |vertex: StrokeVertex| vertex.position()),
)?;
Ok(Self::build_path(buf))
}
/// Builds a [`Path`] from a [`lyon::VertexBuffers`].
pub fn build_path(buf: VertexBuffers<lyon::math::Point, u16>) -> Path<Pixels> {
if buf.vertices.is_empty() {
return Path::new(Point::default());
}
let first_point = buf.vertices[0];
let mut path = Path::new(first_point.into());
for i in 0..buf.indices.len() / 3 {
let i0 = buf.indices[i * 3] as usize;
let i1 = buf.indices[i * 3 + 1] as usize;
let i2 = buf.indices[i * 3 + 2] as usize;
let v0 = buf.vertices[i0];
let v1 = buf.vertices[i1];
let v2 = buf.vertices[i2];
path.push_triangle(
(v0.into(), v1.into(), v2.into()),
(point(0., 1.), point(0., 1.), point(0., 1.)),
);
}
path
}
}

View file

@ -27,6 +27,7 @@ struct BladeAtlasState {
tiles_by_key: FxHashMap<AtlasKey, AtlasTile>, tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
initializations: Vec<AtlasTextureId>, initializations: Vec<AtlasTextureId>,
uploads: Vec<PendingUpload>, uploads: Vec<PendingUpload>,
path_sample_count: u32,
} }
#[cfg(gles)] #[cfg(gles)]
@ -42,10 +43,11 @@ impl BladeAtlasState {
pub struct BladeTextureInfo { pub struct BladeTextureInfo {
pub size: gpu::Extent, pub size: gpu::Extent,
pub raw_view: gpu::TextureView, pub raw_view: gpu::TextureView,
pub msaa_view: Option<gpu::TextureView>,
} }
impl BladeAtlas { impl BladeAtlas {
pub(crate) fn new(gpu: &Arc<gpu::Context>) -> Self { pub(crate) fn new(gpu: &Arc<gpu::Context>, path_sample_count: u32) -> Self {
BladeAtlas(Mutex::new(BladeAtlasState { BladeAtlas(Mutex::new(BladeAtlasState {
gpu: Arc::clone(gpu), gpu: Arc::clone(gpu),
upload_belt: BufferBelt::new(BufferBeltDescriptor { upload_belt: BufferBelt::new(BufferBeltDescriptor {
@ -57,6 +59,7 @@ impl BladeAtlas {
tiles_by_key: Default::default(), tiles_by_key: Default::default(),
initializations: Vec::new(), initializations: Vec::new(),
uploads: Vec::new(), uploads: Vec::new(),
path_sample_count,
})) }))
} }
@ -106,6 +109,7 @@ impl BladeAtlas {
depth: 1, depth: 1,
}, },
raw_view: texture.raw_view, raw_view: texture.raw_view,
msaa_view: texture.msaa_view,
} }
} }
} }
@ -204,6 +208,39 @@ impl BladeAtlasState {
} }
} }
// 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,
});
(
Some(msaa),
Some(self.gpu.create_texture_view(
msaa,
gpu::TextureViewDesc {
name: "msaa texture view",
format,
dimension: gpu::ViewDimension::D2,
subresources: &Default::default(),
},
)),
)
} else {
(None, None)
};
let raw = self.gpu.create_texture(gpu::TextureDesc { let raw = self.gpu.create_texture(gpu::TextureDesc {
name: "atlas", name: "atlas",
format, format,
@ -240,6 +277,8 @@ impl BladeAtlasState {
format, format,
raw, raw,
raw_view, raw_view,
msaa,
msaa_view,
live_atlas_keys: 0, live_atlas_keys: 0,
}; };
@ -354,6 +393,8 @@ struct BladeAtlasTexture {
allocator: BucketedAtlasAllocator, allocator: BucketedAtlasAllocator,
raw: gpu::Texture, raw: gpu::Texture,
raw_view: gpu::TextureView, raw_view: gpu::TextureView,
msaa: Option<gpu::Texture>,
msaa_view: Option<gpu::TextureView>,
format: gpu::TextureFormat, format: gpu::TextureFormat,
live_atlas_keys: u32, live_atlas_keys: u32,
} }
@ -381,6 +422,12 @@ impl BladeAtlasTexture {
fn destroy(&mut self, gpu: &gpu::Context) { fn destroy(&mut self, gpu: &gpu::Context) {
gpu.destroy_texture(self.raw); gpu.destroy_texture(self.raw);
gpu.destroy_texture_view(self.raw_view); gpu.destroy_texture_view(self.raw_view);
if let Some(msaa) = self.msaa {
gpu.destroy_texture(msaa);
}
if let Some(msaa_view) = self.msaa_view {
gpu.destroy_texture_view(msaa_view);
}
} }
fn bytes_per_pixel(&self) -> u8 { fn bytes_per_pixel(&self) -> u8 {

View file

@ -7,16 +7,18 @@ use crate::{
MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch, Quad, MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch, Quad,
ScaledPixels, Scene, Shadow, Size, Underline, ScaledPixels, Scene, Shadow, Size, Underline,
}; };
use blade_graphics as gpu;
use blade_util::{BufferBelt, BufferBeltDescriptor};
use bytemuck::{Pod, Zeroable}; use bytemuck::{Pod, Zeroable};
use collections::HashMap; use collections::HashMap;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
use media::core_video::CVMetalTextureCache; use media::core_video::CVMetalTextureCache;
use blade_graphics as gpu;
use blade_util::{BufferBelt, BufferBeltDescriptor};
use std::{mem, sync::Arc}; use std::{mem, sync::Arc};
const MAX_FRAME_TIME_MS: u32 = 10000; const MAX_FRAME_TIME_MS: u32 = 10000;
// Use 4x MSAA, all devices support it.
// https://developer.apple.com/documentation/metal/mtldevice/1433355-supportstexturesamplecount
const PATH_SAMPLE_COUNT: u32 = 4;
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)] #[derive(Clone, Copy, Pod, Zeroable)]
@ -208,7 +210,10 @@ impl BladePipelines {
blend: Some(gpu::BlendState::ADDITIVE), blend: Some(gpu::BlendState::ADDITIVE),
write_mask: gpu::ColorWrites::default(), write_mask: gpu::ColorWrites::default(),
}], }],
multisample_state: gpu::MultisampleState::default(), multisample_state: gpu::MultisampleState {
sample_count: PATH_SAMPLE_COUNT,
..Default::default()
},
}), }),
paths: gpu.create_render_pipeline(gpu::RenderPipelineDesc { paths: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
name: "paths", name: "paths",
@ -348,7 +353,7 @@ impl BladeRenderer {
min_chunk_size: 0x1000, min_chunk_size: 0x1000,
alignment: 0x40, // Vulkan `minStorageBufferOffsetAlignment` on Intel Xe alignment: 0x40, // Vulkan `minStorageBufferOffsetAlignment` on Intel Xe
}); });
let atlas = Arc::new(BladeAtlas::new(&context.gpu)); let atlas = Arc::new(BladeAtlas::new(&context.gpu, PATH_SAMPLE_COUNT));
let atlas_sampler = context.gpu.create_sampler(gpu::SamplerDesc { let atlas_sampler = context.gpu.create_sampler(gpu::SamplerDesc {
name: "atlas", name: "atlas",
mag_filter: gpu::FilterMode::Linear, mag_filter: gpu::FilterMode::Linear,
@ -497,27 +502,38 @@ impl BladeRenderer {
}; };
let vertex_buf = unsafe { self.instance_belt.alloc_typed(&vertices, &self.gpu) }; let vertex_buf = unsafe { self.instance_belt.alloc_typed(&vertices, &self.gpu) };
let mut pass = self.command_encoder.render( 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", "paths",
gpu::RenderTargetSet { gpu::RenderTargetSet {
colors: &[gpu::RenderTarget { colors: &[color_target],
view: tex_info.raw_view,
init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
finish_op: gpu::FinishOp::Store,
}],
depth_stencil: None, depth_stencil: None,
}, },
); ) {
let mut encoder = pass.with(&self.pipelines.path_rasterization);
let mut encoder = pass.with(&self.pipelines.path_rasterization); encoder.bind(
encoder.bind( 0,
0, &ShaderPathRasterizationData {
&ShaderPathRasterizationData { globals,
globals, b_path_vertices: vertex_buf,
b_path_vertices: vertex_buf, },
}, );
); encoder.draw(0, vertices.len() as u32, 0, 1);
encoder.draw(0, vertices.len() as u32, 0, 1); }
} }
} }

View file

@ -13,13 +13,14 @@ use std::borrow::Cow;
pub(crate) struct MetalAtlas(Mutex<MetalAtlasState>); pub(crate) struct MetalAtlas(Mutex<MetalAtlasState>);
impl MetalAtlas { impl MetalAtlas {
pub(crate) fn new(device: Device) -> Self { pub(crate) fn new(device: Device, path_sample_count: u32) -> Self {
MetalAtlas(Mutex::new(MetalAtlasState { MetalAtlas(Mutex::new(MetalAtlasState {
device: AssertSend(device), device: AssertSend(device),
monochrome_textures: Default::default(), monochrome_textures: Default::default(),
polychrome_textures: Default::default(), polychrome_textures: Default::default(),
path_textures: Default::default(), path_textures: Default::default(),
tiles_by_key: Default::default(), tiles_by_key: Default::default(),
path_sample_count,
})) }))
} }
@ -27,6 +28,10 @@ impl MetalAtlas {
self.0.lock().texture(id).metal_texture.clone() self.0.lock().texture(id).metal_texture.clone()
} }
pub(crate) fn msaa_texture(&self, id: AtlasTextureId) -> Option<metal::Texture> {
self.0.lock().texture(id).msaa_texture.clone()
}
pub(crate) fn allocate( pub(crate) fn allocate(
&self, &self,
size: Size<DevicePixels>, size: Size<DevicePixels>,
@ -54,6 +59,7 @@ struct MetalAtlasState {
polychrome_textures: AtlasTextureList<MetalAtlasTexture>, polychrome_textures: AtlasTextureList<MetalAtlasTexture>,
path_textures: AtlasTextureList<MetalAtlasTexture>, path_textures: AtlasTextureList<MetalAtlasTexture>,
tiles_by_key: FxHashMap<AtlasKey, AtlasTile>, tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
path_sample_count: u32,
} }
impl PlatformAtlas for MetalAtlas { impl PlatformAtlas for MetalAtlas {
@ -176,6 +182,18 @@ impl MetalAtlasState {
texture_descriptor.set_usage(usage); texture_descriptor.set_usage(usage);
let metal_texture = self.device.new_texture(&texture_descriptor); let metal_texture = self.device.new_texture(&texture_descriptor);
// We currently only enable MSAA for path textures.
let msaa_texture = if self.path_sample_count > 1 && kind == AtlasTextureKind::Path {
let mut descriptor = texture_descriptor.clone();
descriptor.set_texture_type(metal::MTLTextureType::D2Multisample);
descriptor.set_storage_mode(metal::MTLStorageMode::Private);
descriptor.set_sample_count(self.path_sample_count as _);
let msaa_texture = self.device.new_texture(&descriptor);
Some(msaa_texture)
} else {
None
};
let texture_list = match kind { let texture_list = match kind {
AtlasTextureKind::Monochrome => &mut self.monochrome_textures, AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
AtlasTextureKind::Polychrome => &mut self.polychrome_textures, AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
@ -191,6 +209,7 @@ impl MetalAtlasState {
}, },
allocator: etagere::BucketedAtlasAllocator::new(size.into()), allocator: etagere::BucketedAtlasAllocator::new(size.into()),
metal_texture: AssertSend(metal_texture), metal_texture: AssertSend(metal_texture),
msaa_texture: AssertSend(msaa_texture),
live_atlas_keys: 0, live_atlas_keys: 0,
}; };
@ -217,6 +236,7 @@ struct MetalAtlasTexture {
id: AtlasTextureId, id: AtlasTextureId,
allocator: BucketedAtlasAllocator, allocator: BucketedAtlasAllocator,
metal_texture: AssertSend<metal::Texture>, metal_texture: AssertSend<metal::Texture>,
msaa_texture: AssertSend<Option<metal::Texture>>,
live_atlas_keys: u32, live_atlas_keys: u32,
} }

View file

@ -28,6 +28,9 @@ pub(crate) type PointF = crate::Point<f32>;
const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib")); const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
#[cfg(feature = "runtime_shaders")] #[cfg(feature = "runtime_shaders")]
const SHADERS_SOURCE_FILE: &str = include_str!(concat!(env!("OUT_DIR"), "/stitched_shaders.metal")); const SHADERS_SOURCE_FILE: &str = include_str!(concat!(env!("OUT_DIR"), "/stitched_shaders.metal"));
// Use 4x MSAA, all devices support it.
// https://developer.apple.com/documentation/metal/mtldevice/1433355-supportstexturesamplecount
const PATH_SAMPLE_COUNT: u32 = 4;
pub type Context = Arc<Mutex<InstanceBufferPool>>; pub type Context = Arc<Mutex<InstanceBufferPool>>;
pub type Renderer = MetalRenderer; pub type Renderer = MetalRenderer;
@ -170,6 +173,7 @@ impl MetalRenderer {
"path_rasterization_vertex", "path_rasterization_vertex",
"path_rasterization_fragment", "path_rasterization_fragment",
MTLPixelFormat::R16Float, MTLPixelFormat::R16Float,
PATH_SAMPLE_COUNT,
); );
let path_sprites_pipeline_state = build_pipeline_state( let path_sprites_pipeline_state = build_pipeline_state(
&device, &device,
@ -229,7 +233,7 @@ impl MetalRenderer {
); );
let command_queue = device.new_command_queue(); 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 = let core_video_texture_cache =
unsafe { CVMetalTextureCache::new(device.as_ptr()).unwrap() }; unsafe { CVMetalTextureCache::new(device.as_ptr()).unwrap() };
@ -531,10 +535,20 @@ impl MetalRenderer {
.unwrap(); .unwrap();
let texture = self.sprite_atlas.metal_texture(texture_id); let texture = self.sprite_atlas.metal_texture(texture_id);
color_attachment.set_texture(Some(&texture)); let msaa_texture = self.sprite_atlas.msaa_texture(texture_id);
color_attachment.set_load_action(metal::MTLLoadAction::Clear);
color_attachment.set_store_action(metal::MTLStoreAction::Store); 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.)); color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., 1.));
let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor); 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_render_pipeline_state(&self.paths_rasterization_pipeline_state);
command_encoder.set_vertex_buffer( command_encoder.set_vertex_buffer(
@ -1160,6 +1174,7 @@ fn build_path_rasterization_pipeline_state(
vertex_fn_name: &str, vertex_fn_name: &str,
fragment_fn_name: &str, fragment_fn_name: &str,
pixel_format: metal::MTLPixelFormat, pixel_format: metal::MTLPixelFormat,
path_sample_count: u32,
) -> metal::RenderPipelineState { ) -> metal::RenderPipelineState {
let vertex_fn = library let vertex_fn = library
.get_function(vertex_fn_name, None) .get_function(vertex_fn_name, None)
@ -1172,6 +1187,10 @@ fn build_path_rasterization_pipeline_state(
descriptor.set_label(label); descriptor.set_label(label);
descriptor.set_vertex_function(Some(vertex_fn.as_ref())); descriptor.set_vertex_function(Some(vertex_fn.as_ref()));
descriptor.set_fragment_function(Some(fragment_fn.as_ref())); descriptor.set_fragment_function(Some(fragment_fn.as_ref()));
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(); let color_attachment = descriptor.color_attachments().object_at(0).unwrap();
color_attachment.set_pixel_format(pixel_format); color_attachment.set_pixel_format(pixel_format);
color_attachment.set_blending_enabled(true); color_attachment.set_blending_enabled(true);

View file

@ -715,6 +715,13 @@ impl Path<Pixels> {
} }
} }
/// Move the start, current point to the given point.
pub fn move_to(&mut self, to: Point<Pixels>) {
self.contour_count += 1;
self.start = to;
self.current = to;
}
/// Draw a straight line from the current point to the given point. /// Draw a straight line from the current point to the given point.
pub fn line_to(&mut self, to: Point<Pixels>) { pub fn line_to(&mut self, to: Point<Pixels>) {
self.contour_count += 1; self.contour_count += 1;
@ -744,7 +751,8 @@ impl Path<Pixels> {
self.current = to; self.current = to;
} }
fn push_triangle( /// Push a triangle to the Path.
pub fn push_triangle(
&mut self, &mut self,
xy: (Point<Pixels>, Point<Pixels>, Point<Pixels>), xy: (Point<Pixels>, Point<Pixels>, Point<Pixels>),
st: (Point<f32>, Point<f32>, Point<f32>), st: (Point<f32>, Point<f32>, Point<f32>),