gpui: Improve path rendering & global multisample anti-aliasing (#29718)
Currently, the rendering path required creating a texture for each path, which wasted a large amount of video memory. In our application, simply drawing some charts resulted in video memory usage as high as 5G. I removed the step of creating path textures and directly drew the paths on the rendering target, adding post-processing global multi-sampling anti-aliasing. Drawing paths no longer requires allocating any additional video memory and also improves the performance of path rendering. Release Notes: - N/A --------- Co-authored-by: Jason Lee <huacnlee@gmail.com>
This commit is contained in:
parent
9dc3ac9657
commit
4fdda8d5a1
16 changed files with 486 additions and 726 deletions
6
Cargo.lock
generated
6
Cargo.lock
generated
|
@ -2076,7 +2076,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "blade-graphics"
|
name = "blade-graphics"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
source = "git+https://github.com/kvark/blade?rev=e0ec4e720957edd51b945b64dd85605ea54bcfe5#e0ec4e720957edd51b945b64dd85605ea54bcfe5"
|
source = "git+https://github.com/kvark/blade?rev=416375211bb0b5826b3584dccdb6a43369e499ad#416375211bb0b5826b3584dccdb6a43369e499ad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ash",
|
"ash",
|
||||||
"ash-window",
|
"ash-window",
|
||||||
|
@ -2109,7 +2109,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "blade-macros"
|
name = "blade-macros"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
source = "git+https://github.com/kvark/blade?rev=e0ec4e720957edd51b945b64dd85605ea54bcfe5#e0ec4e720957edd51b945b64dd85605ea54bcfe5"
|
source = "git+https://github.com/kvark/blade?rev=416375211bb0b5826b3584dccdb6a43369e499ad#416375211bb0b5826b3584dccdb6a43369e499ad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -2119,7 +2119,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "blade-util"
|
name = "blade-util"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "git+https://github.com/kvark/blade?rev=e0ec4e720957edd51b945b64dd85605ea54bcfe5#e0ec4e720957edd51b945b64dd85605ea54bcfe5"
|
source = "git+https://github.com/kvark/blade?rev=416375211bb0b5826b3584dccdb6a43369e499ad#416375211bb0b5826b3584dccdb6a43369e499ad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"blade-graphics",
|
"blade-graphics",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
|
|
|
@ -426,9 +426,9 @@ aws-smithy-runtime-api = { version = "1.7.4", features = ["http-1x", "client"] }
|
||||||
aws-smithy-types = { version = "1.3.0", features = ["http-body-1-x"] }
|
aws-smithy-types = { version = "1.3.0", features = ["http-body-1-x"] }
|
||||||
base64 = "0.22"
|
base64 = "0.22"
|
||||||
bitflags = "2.6.0"
|
bitflags = "2.6.0"
|
||||||
blade-graphics = { git = "https://github.com/kvark/blade", rev = "e0ec4e720957edd51b945b64dd85605ea54bcfe5" }
|
blade-graphics = { git = "https://github.com/kvark/blade", rev = "416375211bb0b5826b3584dccdb6a43369e499ad" }
|
||||||
blade-macros = { git = "https://github.com/kvark/blade", rev = "e0ec4e720957edd51b945b64dd85605ea54bcfe5" }
|
blade-macros = { git = "https://github.com/kvark/blade", rev = "416375211bb0b5826b3584dccdb6a43369e499ad" }
|
||||||
blade-util = { git = "https://github.com/kvark/blade", rev = "e0ec4e720957edd51b945b64dd85605ea54bcfe5" }
|
blade-util = { git = "https://github.com/kvark/blade", rev = "416375211bb0b5826b3584dccdb6a43369e499ad" }
|
||||||
blake3 = "1.5.3"
|
blake3 = "1.5.3"
|
||||||
bytes = "1.0"
|
bytes = "1.0"
|
||||||
cargo_metadata = "0.19"
|
cargo_metadata = "0.19"
|
||||||
|
@ -481,7 +481,7 @@ json_dotpath = "1.1"
|
||||||
jsonschema = "0.30.0"
|
jsonschema = "0.30.0"
|
||||||
jsonwebtoken = "9.3"
|
jsonwebtoken = "9.3"
|
||||||
jupyter-protocol = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
|
jupyter-protocol = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
|
||||||
jupyter-websocket-client = { git = "https://github.com/ConradIrwin/runtimed" ,rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
|
jupyter-websocket-client = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
|
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
|
||||||
linkify = "0.10.0"
|
linkify = "0.10.0"
|
||||||
|
|
|
@ -126,7 +126,7 @@ mod macos {
|
||||||
"ContentMask".into(),
|
"ContentMask".into(),
|
||||||
"Uniforms".into(),
|
"Uniforms".into(),
|
||||||
"AtlasTile".into(),
|
"AtlasTile".into(),
|
||||||
"PathRasterizationInputIndex".into(),
|
"PathInputIndex".into(),
|
||||||
"PathVertex_ScaledPixels".into(),
|
"PathVertex_ScaledPixels".into(),
|
||||||
"ShadowInputIndex".into(),
|
"ShadowInputIndex".into(),
|
||||||
"Shadow".into(),
|
"Shadow".into(),
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
use gpui::{
|
use gpui::{
|
||||||
Application, Background, Bounds, ColorSpace, Context, MouseDownEvent, Path, PathBuilder,
|
Application, Background, Bounds, ColorSpace, Context, MouseDownEvent, Path, PathBuilder,
|
||||||
PathStyle, Pixels, Point, Render, SharedString, StrokeOptions, Window, WindowOptions, canvas,
|
PathStyle, Pixels, Point, Render, SharedString, StrokeOptions, Window, WindowBounds,
|
||||||
div, linear_color_stop, linear_gradient, point, prelude::*, px, rgb, size,
|
WindowOptions, canvas, div, linear_color_stop, linear_gradient, point, prelude::*, px, rgb,
|
||||||
|
size,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const DEFAULT_WINDOW_WIDTH: Pixels = px(1024.0);
|
||||||
|
const DEFAULT_WINDOW_HEIGHT: Pixels = px(768.0);
|
||||||
|
|
||||||
struct PaintingViewer {
|
struct PaintingViewer {
|
||||||
default_lines: Vec<(Path<Pixels>, Background)>,
|
default_lines: Vec<(Path<Pixels>, Background)>,
|
||||||
lines: Vec<Vec<Point<Pixels>>>,
|
lines: Vec<Vec<Point<Pixels>>>,
|
||||||
|
@ -147,8 +151,6 @@ impl PaintingViewer {
|
||||||
px(320.0 + (i as f32 * 10.0).sin() * 40.0),
|
px(320.0 + (i as f32 * 10.0).sin() * 40.0),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let path = builder.build().unwrap();
|
|
||||||
lines.push((path, gpui::green().into()));
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
default_lines: lines.clone(),
|
default_lines: lines.clone(),
|
||||||
|
@ -183,9 +185,13 @@ fn button(
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for PaintingViewer {
|
impl Render for PaintingViewer {
|
||||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
window.request_animation_frame();
|
||||||
|
|
||||||
let default_lines = self.default_lines.clone();
|
let default_lines = self.default_lines.clone();
|
||||||
let lines = self.lines.clone();
|
let lines = self.lines.clone();
|
||||||
|
let window_size = window.bounds().size;
|
||||||
|
let scale = window_size.width / DEFAULT_WINDOW_WIDTH;
|
||||||
let dashed = self.dashed;
|
let dashed = self.dashed;
|
||||||
|
|
||||||
div()
|
div()
|
||||||
|
@ -222,7 +228,7 @@ impl Render for PaintingViewer {
|
||||||
move |_, _, _| {},
|
move |_, _, _| {},
|
||||||
move |_, _, window, _| {
|
move |_, _, window, _| {
|
||||||
for (path, color) in default_lines {
|
for (path, color) in default_lines {
|
||||||
window.paint_path(path, color);
|
window.paint_path(path.clone().scale(scale), color);
|
||||||
}
|
}
|
||||||
|
|
||||||
for points in lines {
|
for points in lines {
|
||||||
|
@ -298,6 +304,11 @@ fn main() {
|
||||||
cx.open_window(
|
cx.open_window(
|
||||||
WindowOptions {
|
WindowOptions {
|
||||||
focus: true,
|
focus: true,
|
||||||
|
window_bounds: Some(WindowBounds::Windowed(Bounds::centered(
|
||||||
|
None,
|
||||||
|
size(DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT),
|
||||||
|
cx,
|
||||||
|
))),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|window, cx| cx.new(|cx| PaintingViewer::new(window, cx)),
|
|window, cx| cx.new(|cx| PaintingViewer::new(window, cx)),
|
||||||
|
|
|
@ -336,10 +336,7 @@ impl PathBuilder {
|
||||||
let v1 = buf.vertices[i1];
|
let v1 = buf.vertices[i1];
|
||||||
let v2 = buf.vertices[i2];
|
let v2 = buf.vertices[i2];
|
||||||
|
|
||||||
path.push_triangle(
|
path.push_triangle((v0.into(), v1.into(), v2.into()));
|
||||||
(v0.into(), v1.into(), v2.into()),
|
|
||||||
(point(0., 1.), point(0., 1.), point(0., 1.)),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
path
|
path
|
||||||
|
|
|
@ -789,7 +789,6 @@ pub(crate) struct AtlasTextureId {
|
||||||
pub(crate) enum AtlasTextureKind {
|
pub(crate) enum AtlasTextureKind {
|
||||||
Monochrome = 0,
|
Monochrome = 0,
|
||||||
Polychrome = 1,
|
Polychrome = 1,
|
||||||
Path = 2,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
|
|
@ -10,8 +10,6 @@ use etagere::BucketedAtlasAllocator;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use std::{borrow::Cow, ops, sync::Arc};
|
use std::{borrow::Cow, ops, sync::Arc};
|
||||||
|
|
||||||
pub(crate) const PATH_TEXTURE_FORMAT: gpu::TextureFormat = gpu::TextureFormat::R16Float;
|
|
||||||
|
|
||||||
pub(crate) struct BladeAtlas(Mutex<BladeAtlasState>);
|
pub(crate) struct BladeAtlas(Mutex<BladeAtlasState>);
|
||||||
|
|
||||||
struct PendingUpload {
|
struct PendingUpload {
|
||||||
|
@ -27,7 +25,6 @@ struct BladeAtlasState {
|
||||||
tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
|
tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
|
||||||
initializations: Vec<AtlasTextureId>,
|
initializations: Vec<AtlasTextureId>,
|
||||||
uploads: Vec<PendingUpload>,
|
uploads: Vec<PendingUpload>,
|
||||||
path_sample_count: u32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(gles)]
|
#[cfg(gles)]
|
||||||
|
@ -41,13 +38,13 @@ impl BladeAtlasState {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct BladeTextureInfo {
|
pub struct BladeTextureInfo {
|
||||||
|
#[allow(dead_code)]
|
||||||
pub size: gpu::Extent,
|
pub size: gpu::Extent,
|
||||||
pub raw_view: gpu::TextureView,
|
pub raw_view: gpu::TextureView,
|
||||||
pub msaa_view: Option<gpu::TextureView>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BladeAtlas {
|
impl BladeAtlas {
|
||||||
pub(crate) fn new(gpu: &Arc<gpu::Context>, path_sample_count: u32) -> Self {
|
pub(crate) fn new(gpu: &Arc<gpu::Context>) -> Self {
|
||||||
BladeAtlas(Mutex::new(BladeAtlasState {
|
BladeAtlas(Mutex::new(BladeAtlasState {
|
||||||
gpu: Arc::clone(gpu),
|
gpu: Arc::clone(gpu),
|
||||||
upload_belt: BufferBelt::new(BufferBeltDescriptor {
|
upload_belt: BufferBelt::new(BufferBeltDescriptor {
|
||||||
|
@ -59,7 +56,6 @@ impl BladeAtlas {
|
||||||
tiles_by_key: Default::default(),
|
tiles_by_key: Default::default(),
|
||||||
initializations: Vec::new(),
|
initializations: Vec::new(),
|
||||||
uploads: Vec::new(),
|
uploads: Vec::new(),
|
||||||
path_sample_count,
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,6 +63,7 @@ impl BladeAtlas {
|
||||||
self.0.lock().destroy();
|
self.0.lock().destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub(crate) fn clear_textures(&self, texture_kind: AtlasTextureKind) {
|
pub(crate) fn clear_textures(&self, texture_kind: AtlasTextureKind) {
|
||||||
let mut lock = self.0.lock();
|
let mut lock = self.0.lock();
|
||||||
let textures = &mut lock.storage[texture_kind];
|
let textures = &mut lock.storage[texture_kind];
|
||||||
|
@ -75,19 +72,6 @@ impl BladeAtlas {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Allocate a rectangle and make it available for rendering immediately (without waiting for `before_frame`)
|
|
||||||
pub fn allocate_for_rendering(
|
|
||||||
&self,
|
|
||||||
size: Size<DevicePixels>,
|
|
||||||
texture_kind: AtlasTextureKind,
|
|
||||||
gpu_encoder: &mut gpu::CommandEncoder,
|
|
||||||
) -> AtlasTile {
|
|
||||||
let mut lock = self.0.lock();
|
|
||||||
let tile = lock.allocate(size, texture_kind);
|
|
||||||
lock.flush_initializations(gpu_encoder);
|
|
||||||
tile
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn before_frame(&self, gpu_encoder: &mut gpu::CommandEncoder) {
|
pub fn before_frame(&self, gpu_encoder: &mut gpu::CommandEncoder) {
|
||||||
let mut lock = self.0.lock();
|
let mut lock = self.0.lock();
|
||||||
lock.flush(gpu_encoder);
|
lock.flush(gpu_encoder);
|
||||||
|
@ -109,7 +93,6 @@ impl BladeAtlas {
|
||||||
depth: 1,
|
depth: 1,
|
||||||
},
|
},
|
||||||
raw_view: texture.raw_view,
|
raw_view: texture.raw_view,
|
||||||
msaa_view: texture.msaa_view,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -200,47 +183,7 @@ impl BladeAtlasState {
|
||||||
format = gpu::TextureFormat::Bgra8UnormSrgb;
|
format = gpu::TextureFormat::Bgra8UnormSrgb;
|
||||||
usage = gpu::TextureUsage::COPY | gpu::TextureUsage::RESOURCE;
|
usage = gpu::TextureUsage::COPY | gpu::TextureUsage::RESOURCE;
|
||||||
}
|
}
|
||||||
AtlasTextureKind::Path => {
|
|
||||||
format = PATH_TEXTURE_FORMAT;
|
|
||||||
usage = gpu::TextureUsage::COPY
|
|
||||||
| gpu::TextureUsage::RESOURCE
|
|
||||||
| gpu::TextureUsage::TARGET;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// We currently only enable MSAA for path textures.
|
|
||||||
let (msaa, msaa_view) = if self.path_sample_count > 1 && kind == AtlasTextureKind::Path {
|
|
||||||
let msaa = self.gpu.create_texture(gpu::TextureDesc {
|
|
||||||
name: "msaa path texture",
|
|
||||||
format,
|
|
||||||
size: gpu::Extent {
|
|
||||||
width: size.width.into(),
|
|
||||||
height: size.height.into(),
|
|
||||||
depth: 1,
|
|
||||||
},
|
|
||||||
array_layer_count: 1,
|
|
||||||
mip_level_count: 1,
|
|
||||||
sample_count: self.path_sample_count,
|
|
||||||
dimension: gpu::TextureDimension::D2,
|
|
||||||
usage: gpu::TextureUsage::TARGET,
|
|
||||||
external: None,
|
|
||||||
});
|
|
||||||
|
|
||||||
(
|
|
||||||
Some(msaa),
|
|
||||||
Some(self.gpu.create_texture_view(
|
|
||||||
msaa,
|
|
||||||
gpu::TextureViewDesc {
|
|
||||||
name: "msaa texture view",
|
|
||||||
format,
|
|
||||||
dimension: gpu::ViewDimension::D2,
|
|
||||||
subresources: &Default::default(),
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(None, None)
|
|
||||||
};
|
|
||||||
|
|
||||||
let raw = self.gpu.create_texture(gpu::TextureDesc {
|
let raw = self.gpu.create_texture(gpu::TextureDesc {
|
||||||
name: "atlas",
|
name: "atlas",
|
||||||
|
@ -279,8 +222,6 @@ impl BladeAtlasState {
|
||||||
format,
|
format,
|
||||||
raw,
|
raw,
|
||||||
raw_view,
|
raw_view,
|
||||||
msaa,
|
|
||||||
msaa_view,
|
|
||||||
live_atlas_keys: 0,
|
live_atlas_keys: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -340,7 +281,6 @@ impl BladeAtlasState {
|
||||||
struct BladeAtlasStorage {
|
struct BladeAtlasStorage {
|
||||||
monochrome_textures: AtlasTextureList<BladeAtlasTexture>,
|
monochrome_textures: AtlasTextureList<BladeAtlasTexture>,
|
||||||
polychrome_textures: AtlasTextureList<BladeAtlasTexture>,
|
polychrome_textures: AtlasTextureList<BladeAtlasTexture>,
|
||||||
path_textures: AtlasTextureList<BladeAtlasTexture>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ops::Index<AtlasTextureKind> for BladeAtlasStorage {
|
impl ops::Index<AtlasTextureKind> for BladeAtlasStorage {
|
||||||
|
@ -349,7 +289,6 @@ impl ops::Index<AtlasTextureKind> for BladeAtlasStorage {
|
||||||
match kind {
|
match kind {
|
||||||
crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
|
crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
|
||||||
crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
|
crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
|
||||||
crate::AtlasTextureKind::Path => &self.path_textures,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -359,7 +298,6 @@ impl ops::IndexMut<AtlasTextureKind> for BladeAtlasStorage {
|
||||||
match kind {
|
match kind {
|
||||||
crate::AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
|
crate::AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
|
||||||
crate::AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
|
crate::AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
|
||||||
crate::AtlasTextureKind::Path => &mut self.path_textures,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -370,7 +308,6 @@ impl ops::Index<AtlasTextureId> for BladeAtlasStorage {
|
||||||
let textures = match id.kind {
|
let textures = match id.kind {
|
||||||
crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
|
crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
|
||||||
crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
|
crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
|
||||||
crate::AtlasTextureKind::Path => &self.path_textures,
|
|
||||||
};
|
};
|
||||||
textures[id.index as usize].as_ref().unwrap()
|
textures[id.index as usize].as_ref().unwrap()
|
||||||
}
|
}
|
||||||
|
@ -384,9 +321,6 @@ impl BladeAtlasStorage {
|
||||||
for mut texture in self.polychrome_textures.drain().flatten() {
|
for mut texture in self.polychrome_textures.drain().flatten() {
|
||||||
texture.destroy(gpu);
|
texture.destroy(gpu);
|
||||||
}
|
}
|
||||||
for mut texture in self.path_textures.drain().flatten() {
|
|
||||||
texture.destroy(gpu);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -395,8 +329,6 @@ struct BladeAtlasTexture {
|
||||||
allocator: BucketedAtlasAllocator,
|
allocator: BucketedAtlasAllocator,
|
||||||
raw: gpu::Texture,
|
raw: gpu::Texture,
|
||||||
raw_view: gpu::TextureView,
|
raw_view: gpu::TextureView,
|
||||||
msaa: Option<gpu::Texture>,
|
|
||||||
msaa_view: Option<gpu::TextureView>,
|
|
||||||
format: gpu::TextureFormat,
|
format: gpu::TextureFormat,
|
||||||
live_atlas_keys: u32,
|
live_atlas_keys: u32,
|
||||||
}
|
}
|
||||||
|
@ -424,12 +356,6 @@ impl BladeAtlasTexture {
|
||||||
fn destroy(&mut self, gpu: &gpu::Context) {
|
fn destroy(&mut self, gpu: &gpu::Context) {
|
||||||
gpu.destroy_texture(self.raw);
|
gpu.destroy_texture(self.raw);
|
||||||
gpu.destroy_texture_view(self.raw_view);
|
gpu.destroy_texture_view(self.raw_view);
|
||||||
if let Some(msaa) = self.msaa {
|
|
||||||
gpu.destroy_texture(msaa);
|
|
||||||
}
|
|
||||||
if let Some(msaa_view) = self.msaa_view {
|
|
||||||
gpu.destroy_texture_view(msaa_view);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bytes_per_pixel(&self) -> u8 {
|
fn bytes_per_pixel(&self) -> u8 {
|
||||||
|
|
|
@ -1,24 +1,19 @@
|
||||||
// Doing `if let` gives you nice scoping with passes/encoders
|
// Doing `if let` gives you nice scoping with passes/encoders
|
||||||
#![allow(irrefutable_let_patterns)]
|
#![allow(irrefutable_let_patterns)]
|
||||||
|
|
||||||
use super::{BladeAtlas, BladeContext, PATH_TEXTURE_FORMAT};
|
use super::{BladeAtlas, BladeContext};
|
||||||
use crate::{
|
use crate::{
|
||||||
AtlasTextureKind, AtlasTile, Background, Bounds, ContentMask, DevicePixels, GpuSpecs,
|
Background, Bounds, ContentMask, DevicePixels, GpuSpecs, MonochromeSprite, PathVertex,
|
||||||
MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch, Quad,
|
PolychromeSprite, PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size, Underline,
|
||||||
ScaledPixels, Scene, Shadow, Size, Underline,
|
|
||||||
};
|
};
|
||||||
use blade_graphics as gpu;
|
use blade_graphics::{self as gpu};
|
||||||
use blade_util::{BufferBelt, BufferBeltDescriptor};
|
use blade_util::{BufferBelt, BufferBeltDescriptor};
|
||||||
use bytemuck::{Pod, Zeroable};
|
use bytemuck::{Pod, Zeroable};
|
||||||
use collections::HashMap;
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
use media::core_video::CVMetalTextureCache;
|
use media::core_video::CVMetalTextureCache;
|
||||||
use std::{mem, sync::Arc};
|
use std::{mem, sync::Arc};
|
||||||
|
|
||||||
const MAX_FRAME_TIME_MS: u32 = 10000;
|
const MAX_FRAME_TIME_MS: u32 = 10000;
|
||||||
// Use 4x MSAA, all devices support it.
|
|
||||||
// https://developer.apple.com/documentation/metal/mtldevice/1433355-supportstexturesamplecount
|
|
||||||
const DEFAULT_PATH_SAMPLE_COUNT: u32 = 4;
|
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||||
|
@ -65,17 +60,10 @@ struct ShaderShadowsData {
|
||||||
b_shadows: gpu::BufferPiece,
|
b_shadows: gpu::BufferPiece,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(blade_macros::ShaderData)]
|
|
||||||
struct ShaderPathRasterizationData {
|
|
||||||
globals: GlobalParams,
|
|
||||||
b_path_vertices: gpu::BufferPiece,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(blade_macros::ShaderData)]
|
#[derive(blade_macros::ShaderData)]
|
||||||
struct ShaderPathsData {
|
struct ShaderPathsData {
|
||||||
globals: GlobalParams,
|
globals: GlobalParams,
|
||||||
t_sprite: gpu::TextureView,
|
b_path_vertices: gpu::BufferPiece,
|
||||||
s_sprite: gpu::Sampler,
|
|
||||||
b_path_sprites: gpu::BufferPiece,
|
b_path_sprites: gpu::BufferPiece,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,13 +103,27 @@ struct ShaderSurfacesData {
|
||||||
struct PathSprite {
|
struct PathSprite {
|
||||||
bounds: Bounds<ScaledPixels>,
|
bounds: Bounds<ScaledPixels>,
|
||||||
color: Background,
|
color: Background,
|
||||||
tile: AtlasTile,
|
}
|
||||||
|
|
||||||
|
/// Argument buffer layout for `draw_indirect` commands.
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Copy, Clone, Debug, Default, Pod, Zeroable)]
|
||||||
|
pub struct DrawIndirectArgs {
|
||||||
|
/// The number of vertices to draw.
|
||||||
|
pub vertex_count: u32,
|
||||||
|
/// The number of instances to draw.
|
||||||
|
pub instance_count: u32,
|
||||||
|
/// The Index of the first vertex to draw.
|
||||||
|
pub first_vertex: u32,
|
||||||
|
/// The instance ID of the first instance to draw.
|
||||||
|
///
|
||||||
|
/// Has to be 0, unless [`Features::INDIRECT_FIRST_INSTANCE`](crate::Features::INDIRECT_FIRST_INSTANCE) is enabled.
|
||||||
|
pub first_instance: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct BladePipelines {
|
struct BladePipelines {
|
||||||
quads: gpu::RenderPipeline,
|
quads: gpu::RenderPipeline,
|
||||||
shadows: gpu::RenderPipeline,
|
shadows: gpu::RenderPipeline,
|
||||||
path_rasterization: gpu::RenderPipeline,
|
|
||||||
paths: gpu::RenderPipeline,
|
paths: gpu::RenderPipeline,
|
||||||
underlines: gpu::RenderPipeline,
|
underlines: gpu::RenderPipeline,
|
||||||
mono_sprites: gpu::RenderPipeline,
|
mono_sprites: gpu::RenderPipeline,
|
||||||
|
@ -130,7 +132,7 @@ struct BladePipelines {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BladePipelines {
|
impl BladePipelines {
|
||||||
fn new(gpu: &gpu::Context, surface_info: gpu::SurfaceInfo, path_sample_count: u32) -> Self {
|
fn new(gpu: &gpu::Context, surface_info: gpu::SurfaceInfo, sample_count: u32) -> Self {
|
||||||
use gpu::ShaderData as _;
|
use gpu::ShaderData as _;
|
||||||
|
|
||||||
log::info!(
|
log::info!(
|
||||||
|
@ -178,7 +180,10 @@ impl BladePipelines {
|
||||||
depth_stencil: None,
|
depth_stencil: None,
|
||||||
fragment: Some(shader.at("fs_quad")),
|
fragment: Some(shader.at("fs_quad")),
|
||||||
color_targets,
|
color_targets,
|
||||||
multisample_state: gpu::MultisampleState::default(),
|
multisample_state: gpu::MultisampleState {
|
||||||
|
sample_count,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
shadows: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
|
shadows: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
|
||||||
name: "shadows",
|
name: "shadows",
|
||||||
|
@ -192,26 +197,8 @@ impl BladePipelines {
|
||||||
depth_stencil: None,
|
depth_stencil: None,
|
||||||
fragment: Some(shader.at("fs_shadow")),
|
fragment: Some(shader.at("fs_shadow")),
|
||||||
color_targets,
|
color_targets,
|
||||||
multisample_state: gpu::MultisampleState::default(),
|
|
||||||
}),
|
|
||||||
path_rasterization: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
|
|
||||||
name: "path_rasterization",
|
|
||||||
data_layouts: &[&ShaderPathRasterizationData::layout()],
|
|
||||||
vertex: shader.at("vs_path_rasterization"),
|
|
||||||
vertex_fetches: &[],
|
|
||||||
primitive: gpu::PrimitiveState {
|
|
||||||
topology: gpu::PrimitiveTopology::TriangleList,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
depth_stencil: None,
|
|
||||||
fragment: Some(shader.at("fs_path_rasterization")),
|
|
||||||
color_targets: &[gpu::ColorTargetState {
|
|
||||||
format: PATH_TEXTURE_FORMAT,
|
|
||||||
blend: Some(gpu::BlendState::ADDITIVE),
|
|
||||||
write_mask: gpu::ColorWrites::default(),
|
|
||||||
}],
|
|
||||||
multisample_state: gpu::MultisampleState {
|
multisample_state: gpu::MultisampleState {
|
||||||
sample_count: path_sample_count,
|
sample_count,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
@ -221,13 +208,16 @@ impl BladePipelines {
|
||||||
vertex: shader.at("vs_path"),
|
vertex: shader.at("vs_path"),
|
||||||
vertex_fetches: &[],
|
vertex_fetches: &[],
|
||||||
primitive: gpu::PrimitiveState {
|
primitive: gpu::PrimitiveState {
|
||||||
topology: gpu::PrimitiveTopology::TriangleStrip,
|
topology: gpu::PrimitiveTopology::TriangleList,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
depth_stencil: None,
|
depth_stencil: None,
|
||||||
fragment: Some(shader.at("fs_path")),
|
fragment: Some(shader.at("fs_path")),
|
||||||
color_targets,
|
color_targets,
|
||||||
multisample_state: gpu::MultisampleState::default(),
|
multisample_state: gpu::MultisampleState {
|
||||||
|
sample_count,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
underlines: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
|
underlines: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
|
||||||
name: "underlines",
|
name: "underlines",
|
||||||
|
@ -241,7 +231,10 @@ impl BladePipelines {
|
||||||
depth_stencil: None,
|
depth_stencil: None,
|
||||||
fragment: Some(shader.at("fs_underline")),
|
fragment: Some(shader.at("fs_underline")),
|
||||||
color_targets,
|
color_targets,
|
||||||
multisample_state: gpu::MultisampleState::default(),
|
multisample_state: gpu::MultisampleState {
|
||||||
|
sample_count,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
mono_sprites: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
|
mono_sprites: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
|
||||||
name: "mono-sprites",
|
name: "mono-sprites",
|
||||||
|
@ -255,7 +248,10 @@ impl BladePipelines {
|
||||||
depth_stencil: None,
|
depth_stencil: None,
|
||||||
fragment: Some(shader.at("fs_mono_sprite")),
|
fragment: Some(shader.at("fs_mono_sprite")),
|
||||||
color_targets,
|
color_targets,
|
||||||
multisample_state: gpu::MultisampleState::default(),
|
multisample_state: gpu::MultisampleState {
|
||||||
|
sample_count,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
poly_sprites: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
|
poly_sprites: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
|
||||||
name: "poly-sprites",
|
name: "poly-sprites",
|
||||||
|
@ -269,7 +265,10 @@ impl BladePipelines {
|
||||||
depth_stencil: None,
|
depth_stencil: None,
|
||||||
fragment: Some(shader.at("fs_poly_sprite")),
|
fragment: Some(shader.at("fs_poly_sprite")),
|
||||||
color_targets,
|
color_targets,
|
||||||
multisample_state: gpu::MultisampleState::default(),
|
multisample_state: gpu::MultisampleState {
|
||||||
|
sample_count,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
surfaces: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
|
surfaces: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
|
||||||
name: "surfaces",
|
name: "surfaces",
|
||||||
|
@ -283,7 +282,10 @@ impl BladePipelines {
|
||||||
depth_stencil: None,
|
depth_stencil: None,
|
||||||
fragment: Some(shader.at("fs_surface")),
|
fragment: Some(shader.at("fs_surface")),
|
||||||
color_targets,
|
color_targets,
|
||||||
multisample_state: gpu::MultisampleState::default(),
|
multisample_state: gpu::MultisampleState {
|
||||||
|
sample_count,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -291,7 +293,6 @@ impl BladePipelines {
|
||||||
fn destroy(&mut self, gpu: &gpu::Context) {
|
fn destroy(&mut self, gpu: &gpu::Context) {
|
||||||
gpu.destroy_render_pipeline(&mut self.quads);
|
gpu.destroy_render_pipeline(&mut self.quads);
|
||||||
gpu.destroy_render_pipeline(&mut self.shadows);
|
gpu.destroy_render_pipeline(&mut self.shadows);
|
||||||
gpu.destroy_render_pipeline(&mut self.path_rasterization);
|
|
||||||
gpu.destroy_render_pipeline(&mut self.paths);
|
gpu.destroy_render_pipeline(&mut self.paths);
|
||||||
gpu.destroy_render_pipeline(&mut self.underlines);
|
gpu.destroy_render_pipeline(&mut self.underlines);
|
||||||
gpu.destroy_render_pipeline(&mut self.mono_sprites);
|
gpu.destroy_render_pipeline(&mut self.mono_sprites);
|
||||||
|
@ -317,12 +318,13 @@ pub struct BladeRenderer {
|
||||||
last_sync_point: Option<gpu::SyncPoint>,
|
last_sync_point: Option<gpu::SyncPoint>,
|
||||||
pipelines: BladePipelines,
|
pipelines: BladePipelines,
|
||||||
instance_belt: BufferBelt,
|
instance_belt: BufferBelt,
|
||||||
path_tiles: HashMap<PathId, AtlasTile>,
|
|
||||||
atlas: Arc<BladeAtlas>,
|
atlas: Arc<BladeAtlas>,
|
||||||
atlas_sampler: gpu::Sampler,
|
atlas_sampler: gpu::Sampler,
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
core_video_texture_cache: CVMetalTextureCache,
|
core_video_texture_cache: CVMetalTextureCache,
|
||||||
path_sample_count: u32,
|
sample_count: u32,
|
||||||
|
texture_msaa: Option<gpu::Texture>,
|
||||||
|
texture_view_msaa: Option<gpu::TextureView>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BladeRenderer {
|
impl BladeRenderer {
|
||||||
|
@ -331,6 +333,18 @@ impl BladeRenderer {
|
||||||
window: &I,
|
window: &I,
|
||||||
config: BladeSurfaceConfig,
|
config: BladeSurfaceConfig,
|
||||||
) -> anyhow::Result<Self> {
|
) -> anyhow::Result<Self> {
|
||||||
|
// workaround for https://github.com/zed-industries/zed/issues/26143
|
||||||
|
let sample_count = std::env::var("ZED_SAMPLE_COUNT")
|
||||||
|
.ok()
|
||||||
|
.or_else(|| std::env::var("ZED_PATH_SAMPLE_COUNT").ok())
|
||||||
|
.and_then(|v| v.parse().ok())
|
||||||
|
.or_else(|| {
|
||||||
|
[4, 2, 1]
|
||||||
|
.into_iter()
|
||||||
|
.find(|count| context.gpu.supports_texture_sample_count(*count))
|
||||||
|
})
|
||||||
|
.unwrap_or(1);
|
||||||
|
|
||||||
let surface_config = gpu::SurfaceConfig {
|
let surface_config = gpu::SurfaceConfig {
|
||||||
size: config.size,
|
size: config.size,
|
||||||
usage: gpu::TextureUsage::TARGET,
|
usage: gpu::TextureUsage::TARGET,
|
||||||
|
@ -344,22 +358,27 @@ impl BladeRenderer {
|
||||||
.create_surface_configured(window, surface_config)
|
.create_surface_configured(window, surface_config)
|
||||||
.map_err(|err| anyhow::anyhow!("Failed to create surface: {err:?}"))?;
|
.map_err(|err| anyhow::anyhow!("Failed to create surface: {err:?}"))?;
|
||||||
|
|
||||||
|
let (texture_msaa, texture_view_msaa) = create_msaa_texture_if_needed(
|
||||||
|
&context.gpu,
|
||||||
|
surface.info().format,
|
||||||
|
config.size.width,
|
||||||
|
config.size.height,
|
||||||
|
sample_count,
|
||||||
|
)
|
||||||
|
.unzip();
|
||||||
|
|
||||||
let command_encoder = context.gpu.create_command_encoder(gpu::CommandEncoderDesc {
|
let command_encoder = context.gpu.create_command_encoder(gpu::CommandEncoderDesc {
|
||||||
name: "main",
|
name: "main",
|
||||||
buffer_count: 2,
|
buffer_count: 2,
|
||||||
});
|
});
|
||||||
// workaround for https://github.com/zed-industries/zed/issues/26143
|
|
||||||
let path_sample_count = std::env::var("ZED_PATH_SAMPLE_COUNT")
|
let pipelines = BladePipelines::new(&context.gpu, surface.info(), sample_count);
|
||||||
.ok()
|
|
||||||
.and_then(|v| v.parse().ok())
|
|
||||||
.unwrap_or(DEFAULT_PATH_SAMPLE_COUNT);
|
|
||||||
let pipelines = BladePipelines::new(&context.gpu, surface.info(), path_sample_count);
|
|
||||||
let instance_belt = BufferBelt::new(BufferBeltDescriptor {
|
let instance_belt = BufferBelt::new(BufferBeltDescriptor {
|
||||||
memory: gpu::Memory::Shared,
|
memory: gpu::Memory::Shared,
|
||||||
min_chunk_size: 0x1000,
|
min_chunk_size: 0x1000,
|
||||||
alignment: 0x40, // Vulkan `minStorageBufferOffsetAlignment` on Intel Xe
|
alignment: 0x40, // Vulkan `minStorageBufferOffsetAlignment` on Intel Xe
|
||||||
});
|
});
|
||||||
let atlas = Arc::new(BladeAtlas::new(&context.gpu, path_sample_count));
|
let atlas = Arc::new(BladeAtlas::new(&context.gpu));
|
||||||
let atlas_sampler = context.gpu.create_sampler(gpu::SamplerDesc {
|
let atlas_sampler = context.gpu.create_sampler(gpu::SamplerDesc {
|
||||||
name: "atlas",
|
name: "atlas",
|
||||||
mag_filter: gpu::FilterMode::Linear,
|
mag_filter: gpu::FilterMode::Linear,
|
||||||
|
@ -383,12 +402,13 @@ impl BladeRenderer {
|
||||||
last_sync_point: None,
|
last_sync_point: None,
|
||||||
pipelines,
|
pipelines,
|
||||||
instance_belt,
|
instance_belt,
|
||||||
path_tiles: HashMap::default(),
|
|
||||||
atlas,
|
atlas,
|
||||||
atlas_sampler,
|
atlas_sampler,
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
core_video_texture_cache,
|
core_video_texture_cache,
|
||||||
path_sample_count,
|
sample_count,
|
||||||
|
texture_msaa,
|
||||||
|
texture_view_msaa,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -441,6 +461,24 @@ impl BladeRenderer {
|
||||||
self.surface_config.size = gpu_size;
|
self.surface_config.size = gpu_size;
|
||||||
self.gpu
|
self.gpu
|
||||||
.reconfigure_surface(&mut self.surface, self.surface_config);
|
.reconfigure_surface(&mut self.surface, self.surface_config);
|
||||||
|
|
||||||
|
if let Some(texture_msaa) = self.texture_msaa {
|
||||||
|
self.gpu.destroy_texture(texture_msaa);
|
||||||
|
}
|
||||||
|
if let Some(texture_view_msaa) = self.texture_view_msaa {
|
||||||
|
self.gpu.destroy_texture_view(texture_view_msaa);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (texture_msaa, texture_view_msaa) = create_msaa_texture_if_needed(
|
||||||
|
&self.gpu,
|
||||||
|
self.surface.info().format,
|
||||||
|
gpu_size.width,
|
||||||
|
gpu_size.height,
|
||||||
|
self.sample_count,
|
||||||
|
)
|
||||||
|
.unzip();
|
||||||
|
self.texture_msaa = texture_msaa;
|
||||||
|
self.texture_view_msaa = texture_view_msaa;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -451,8 +489,7 @@ impl BladeRenderer {
|
||||||
self.gpu
|
self.gpu
|
||||||
.reconfigure_surface(&mut self.surface, self.surface_config);
|
.reconfigure_surface(&mut self.surface, self.surface_config);
|
||||||
self.pipelines.destroy(&self.gpu);
|
self.pipelines.destroy(&self.gpu);
|
||||||
self.pipelines =
|
self.pipelines = BladePipelines::new(&self.gpu, self.surface.info(), self.sample_count);
|
||||||
BladePipelines::new(&self.gpu, self.surface.info(), self.path_sample_count);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -490,80 +527,6 @@ impl BladeRenderer {
|
||||||
objc2::rc::Retained::as_ptr(&self.surface.metal_layer()) as *mut _
|
objc2::rc::Retained::as_ptr(&self.surface.metal_layer()) as *mut _
|
||||||
}
|
}
|
||||||
|
|
||||||
#[profiling::function]
|
|
||||||
fn rasterize_paths(&mut self, paths: &[Path<ScaledPixels>]) {
|
|
||||||
self.path_tiles.clear();
|
|
||||||
let mut vertices_by_texture_id = HashMap::default();
|
|
||||||
|
|
||||||
for path in paths {
|
|
||||||
let clipped_bounds = path
|
|
||||||
.bounds
|
|
||||||
.intersect(&path.content_mask.bounds)
|
|
||||||
.map_origin(|origin| origin.floor())
|
|
||||||
.map_size(|size| size.ceil());
|
|
||||||
let tile = self.atlas.allocate_for_rendering(
|
|
||||||
clipped_bounds.size.map(Into::into),
|
|
||||||
AtlasTextureKind::Path,
|
|
||||||
&mut self.command_encoder,
|
|
||||||
);
|
|
||||||
vertices_by_texture_id
|
|
||||||
.entry(tile.texture_id)
|
|
||||||
.or_insert(Vec::new())
|
|
||||||
.extend(path.vertices.iter().map(|vertex| PathVertex {
|
|
||||||
xy_position: vertex.xy_position - clipped_bounds.origin
|
|
||||||
+ tile.bounds.origin.map(Into::into),
|
|
||||||
st_position: vertex.st_position,
|
|
||||||
content_mask: ContentMask {
|
|
||||||
bounds: tile.bounds.map(Into::into),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
self.path_tiles.insert(path.id, tile);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (texture_id, vertices) in vertices_by_texture_id {
|
|
||||||
let tex_info = self.atlas.get_texture_info(texture_id);
|
|
||||||
let globals = GlobalParams {
|
|
||||||
viewport_size: [tex_info.size.width as f32, tex_info.size.height as f32],
|
|
||||||
premultiplied_alpha: 0,
|
|
||||||
pad: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
let vertex_buf = unsafe { self.instance_belt.alloc_typed(&vertices, &self.gpu) };
|
|
||||||
let frame_view = tex_info.raw_view;
|
|
||||||
let color_target = if let Some(msaa_view) = tex_info.msaa_view {
|
|
||||||
gpu::RenderTarget {
|
|
||||||
view: msaa_view,
|
|
||||||
init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
|
|
||||||
finish_op: gpu::FinishOp::ResolveTo(frame_view),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
gpu::RenderTarget {
|
|
||||||
view: frame_view,
|
|
||||||
init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
|
|
||||||
finish_op: gpu::FinishOp::Store,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if let mut pass = self.command_encoder.render(
|
|
||||||
"paths",
|
|
||||||
gpu::RenderTargetSet {
|
|
||||||
colors: &[color_target],
|
|
||||||
depth_stencil: None,
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
let mut encoder = pass.with(&self.pipelines.path_rasterization);
|
|
||||||
encoder.bind(
|
|
||||||
0,
|
|
||||||
&ShaderPathRasterizationData {
|
|
||||||
globals,
|
|
||||||
b_path_vertices: vertex_buf,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
encoder.draw(0, vertices.len() as u32, 0, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn destroy(&mut self) {
|
pub fn destroy(&mut self) {
|
||||||
self.wait_for_gpu();
|
self.wait_for_gpu();
|
||||||
self.atlas.destroy();
|
self.atlas.destroy();
|
||||||
|
@ -572,17 +535,26 @@ impl BladeRenderer {
|
||||||
self.gpu.destroy_command_encoder(&mut self.command_encoder);
|
self.gpu.destroy_command_encoder(&mut self.command_encoder);
|
||||||
self.pipelines.destroy(&self.gpu);
|
self.pipelines.destroy(&self.gpu);
|
||||||
self.gpu.destroy_surface(&mut self.surface);
|
self.gpu.destroy_surface(&mut self.surface);
|
||||||
|
if let Some(texture_msaa) = self.texture_msaa {
|
||||||
|
self.gpu.destroy_texture(texture_msaa);
|
||||||
|
}
|
||||||
|
if let Some(texture_view_msaa) = self.texture_view_msaa {
|
||||||
|
self.gpu.destroy_texture_view(texture_view_msaa);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(&mut self, scene: &Scene) {
|
pub fn draw(&mut self, scene: &Scene) {
|
||||||
self.command_encoder.start();
|
self.command_encoder.start();
|
||||||
self.atlas.before_frame(&mut self.command_encoder);
|
self.atlas.before_frame(&mut self.command_encoder);
|
||||||
self.rasterize_paths(scene.paths());
|
|
||||||
|
|
||||||
let frame = {
|
let frame = {
|
||||||
profiling::scope!("acquire frame");
|
profiling::scope!("acquire frame");
|
||||||
self.surface.acquire_frame()
|
self.surface.acquire_frame()
|
||||||
};
|
};
|
||||||
|
let frame_view = frame.texture_view();
|
||||||
|
if let Some(texture_msaa) = self.texture_msaa {
|
||||||
|
self.command_encoder.init_texture(texture_msaa);
|
||||||
|
}
|
||||||
self.command_encoder.init_texture(frame.texture());
|
self.command_encoder.init_texture(frame.texture());
|
||||||
|
|
||||||
let globals = GlobalParams {
|
let globals = GlobalParams {
|
||||||
|
@ -597,14 +569,25 @@ impl BladeRenderer {
|
||||||
pad: 0,
|
pad: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let target = if let Some(texture_view_msaa) = self.texture_view_msaa {
|
||||||
|
gpu::RenderTarget {
|
||||||
|
view: texture_view_msaa,
|
||||||
|
init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
|
||||||
|
finish_op: gpu::FinishOp::ResolveTo(frame_view),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
gpu::RenderTarget {
|
||||||
|
view: frame_view,
|
||||||
|
init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
|
||||||
|
finish_op: gpu::FinishOp::Store,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// draw to the target texture
|
||||||
if let mut pass = self.command_encoder.render(
|
if let mut pass = self.command_encoder.render(
|
||||||
"main",
|
"main",
|
||||||
gpu::RenderTargetSet {
|
gpu::RenderTargetSet {
|
||||||
colors: &[gpu::RenderTarget {
|
colors: &[target],
|
||||||
view: frame.texture_view(),
|
|
||||||
init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
|
|
||||||
finish_op: gpu::FinishOp::Store,
|
|
||||||
}],
|
|
||||||
depth_stencil: None,
|
depth_stencil: None,
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
@ -639,32 +622,55 @@ impl BladeRenderer {
|
||||||
}
|
}
|
||||||
PrimitiveBatch::Paths(paths) => {
|
PrimitiveBatch::Paths(paths) => {
|
||||||
let mut encoder = pass.with(&self.pipelines.paths);
|
let mut encoder = pass.with(&self.pipelines.paths);
|
||||||
// todo(linux): group by texture ID
|
|
||||||
for path in paths {
|
|
||||||
let tile = &self.path_tiles[&path.id];
|
|
||||||
let tex_info = self.atlas.get_texture_info(tile.texture_id);
|
|
||||||
let origin = path.bounds.intersect(&path.content_mask.bounds).origin;
|
|
||||||
let sprites = [PathSprite {
|
|
||||||
bounds: Bounds {
|
|
||||||
origin: origin.map(|p| p.floor()),
|
|
||||||
size: tile.bounds.size.map(Into::into),
|
|
||||||
},
|
|
||||||
color: path.color,
|
|
||||||
tile: (*tile).clone(),
|
|
||||||
}];
|
|
||||||
|
|
||||||
|
let mut vertices = Vec::new();
|
||||||
|
let mut sprites = Vec::with_capacity(paths.len());
|
||||||
|
let mut draw_indirect_commands = Vec::with_capacity(paths.len());
|
||||||
|
let mut first_vertex = 0;
|
||||||
|
|
||||||
|
for (i, path) in paths.iter().enumerate() {
|
||||||
|
draw_indirect_commands.push(DrawIndirectArgs {
|
||||||
|
vertex_count: path.vertices.len() as u32,
|
||||||
|
instance_count: 1,
|
||||||
|
first_vertex,
|
||||||
|
first_instance: i as u32,
|
||||||
|
});
|
||||||
|
first_vertex += path.vertices.len() as u32;
|
||||||
|
|
||||||
|
vertices.extend(path.vertices.iter().map(|v| PathVertex {
|
||||||
|
xy_position: v.xy_position,
|
||||||
|
content_mask: ContentMask {
|
||||||
|
bounds: path.content_mask.bounds,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
sprites.push(PathSprite {
|
||||||
|
bounds: path.bounds,
|
||||||
|
color: path.color,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let b_path_vertices =
|
||||||
|
unsafe { self.instance_belt.alloc_typed(&vertices, &self.gpu) };
|
||||||
let instance_buf =
|
let instance_buf =
|
||||||
unsafe { self.instance_belt.alloc_typed(&sprites, &self.gpu) };
|
unsafe { self.instance_belt.alloc_typed(&sprites, &self.gpu) };
|
||||||
|
let indirect_buf = unsafe {
|
||||||
|
self.instance_belt
|
||||||
|
.alloc_typed(&draw_indirect_commands, &self.gpu)
|
||||||
|
};
|
||||||
|
|
||||||
encoder.bind(
|
encoder.bind(
|
||||||
0,
|
0,
|
||||||
&ShaderPathsData {
|
&ShaderPathsData {
|
||||||
globals,
|
globals,
|
||||||
t_sprite: tex_info.raw_view,
|
b_path_vertices,
|
||||||
s_sprite: self.atlas_sampler,
|
|
||||||
b_path_sprites: instance_buf,
|
b_path_sprites: instance_buf,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
encoder.draw(0, 4, 0, sprites.len() as u32);
|
|
||||||
|
for i in 0..paths.len() {
|
||||||
|
encoder.draw_indirect(indirect_buf.buffer.at(indirect_buf.offset
|
||||||
|
+ (i * mem::size_of::<DrawIndirectArgs>()) as u64));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PrimitiveBatch::Underlines(underlines) => {
|
PrimitiveBatch::Underlines(underlines) => {
|
||||||
|
@ -817,9 +823,47 @@ impl BladeRenderer {
|
||||||
profiling::scope!("finish");
|
profiling::scope!("finish");
|
||||||
self.instance_belt.flush(&sync_point);
|
self.instance_belt.flush(&sync_point);
|
||||||
self.atlas.after_frame(&sync_point);
|
self.atlas.after_frame(&sync_point);
|
||||||
self.atlas.clear_textures(AtlasTextureKind::Path);
|
|
||||||
|
|
||||||
self.wait_for_gpu();
|
self.wait_for_gpu();
|
||||||
self.last_sync_point = Some(sync_point);
|
self.last_sync_point = Some(sync_point);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_msaa_texture_if_needed(
|
||||||
|
gpu: &gpu::Context,
|
||||||
|
format: gpu::TextureFormat,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
sample_count: u32,
|
||||||
|
) -> Option<(gpu::Texture, gpu::TextureView)> {
|
||||||
|
if sample_count <= 1 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let texture_msaa = gpu.create_texture(gpu::TextureDesc {
|
||||||
|
name: "msaa",
|
||||||
|
format,
|
||||||
|
size: gpu::Extent {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
depth: 1,
|
||||||
|
},
|
||||||
|
array_layer_count: 1,
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count,
|
||||||
|
dimension: gpu::TextureDimension::D2,
|
||||||
|
usage: gpu::TextureUsage::TARGET,
|
||||||
|
external: None,
|
||||||
|
});
|
||||||
|
let texture_view_msaa = gpu.create_texture_view(
|
||||||
|
texture_msaa,
|
||||||
|
gpu::TextureViewDesc {
|
||||||
|
name: "msaa view",
|
||||||
|
format,
|
||||||
|
dimension: gpu::ViewDimension::D2,
|
||||||
|
subresources: &Default::default(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Some((texture_msaa, texture_view_msaa))
|
||||||
|
}
|
||||||
|
|
|
@ -922,59 +922,23 @@ fn fs_shadow(input: ShadowVarying) -> @location(0) vec4<f32> {
|
||||||
return blend_color(input.color, alpha);
|
return blend_color(input.color, alpha);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- path rasterization --- //
|
// --- paths --- //
|
||||||
|
|
||||||
struct PathVertex {
|
struct PathVertex {
|
||||||
xy_position: vec2<f32>,
|
xy_position: vec2<f32>,
|
||||||
st_position: vec2<f32>,
|
|
||||||
content_mask: Bounds,
|
content_mask: Bounds,
|
||||||
}
|
}
|
||||||
var<storage, read> b_path_vertices: array<PathVertex>;
|
|
||||||
|
|
||||||
struct PathRasterizationVarying {
|
|
||||||
@builtin(position) position: vec4<f32>,
|
|
||||||
@location(0) st_position: vec2<f32>,
|
|
||||||
//TODO: use `clip_distance` once Naga supports it
|
|
||||||
@location(3) clip_distances: vec4<f32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
@vertex
|
|
||||||
fn vs_path_rasterization(@builtin(vertex_index) vertex_id: u32) -> PathRasterizationVarying {
|
|
||||||
let v = b_path_vertices[vertex_id];
|
|
||||||
|
|
||||||
var out = PathRasterizationVarying();
|
|
||||||
out.position = to_device_position_impl(v.xy_position);
|
|
||||||
out.st_position = v.st_position;
|
|
||||||
out.clip_distances = distance_from_clip_rect_impl(v.xy_position, v.content_mask);
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@fragment
|
|
||||||
fn fs_path_rasterization(input: PathRasterizationVarying) -> @location(0) f32 {
|
|
||||||
let dx = dpdx(input.st_position);
|
|
||||||
let dy = dpdy(input.st_position);
|
|
||||||
if (any(input.clip_distances < vec4<f32>(0.0))) {
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
let gradient = 2.0 * input.st_position.xx * vec2<f32>(dx.x, dy.x) - vec2<f32>(dx.y, dy.y);
|
|
||||||
let f = input.st_position.x * input.st_position.x - input.st_position.y;
|
|
||||||
let distance = f / length(gradient);
|
|
||||||
return saturate(0.5 - distance);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- paths --- //
|
|
||||||
|
|
||||||
struct PathSprite {
|
struct PathSprite {
|
||||||
bounds: Bounds,
|
bounds: Bounds,
|
||||||
color: Background,
|
color: Background,
|
||||||
tile: AtlasTile,
|
|
||||||
}
|
}
|
||||||
|
var<storage, read> b_path_vertices: array<PathVertex>;
|
||||||
var<storage, read> b_path_sprites: array<PathSprite>;
|
var<storage, read> b_path_sprites: array<PathSprite>;
|
||||||
|
|
||||||
struct PathVarying {
|
struct PathVarying {
|
||||||
@builtin(position) position: vec4<f32>,
|
@builtin(position) position: vec4<f32>,
|
||||||
@location(0) tile_position: vec2<f32>,
|
@location(0) clip_distances: vec4<f32>,
|
||||||
@location(1) @interpolate(flat) instance_id: u32,
|
@location(1) @interpolate(flat) instance_id: u32,
|
||||||
@location(2) @interpolate(flat) color_solid: vec4<f32>,
|
@location(2) @interpolate(flat) color_solid: vec4<f32>,
|
||||||
@location(3) @interpolate(flat) color0: vec4<f32>,
|
@location(3) @interpolate(flat) color0: vec4<f32>,
|
||||||
|
@ -983,13 +947,12 @@ struct PathVarying {
|
||||||
|
|
||||||
@vertex
|
@vertex
|
||||||
fn vs_path(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) instance_id: u32) -> PathVarying {
|
fn vs_path(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) instance_id: u32) -> PathVarying {
|
||||||
let unit_vertex = vec2<f32>(f32(vertex_id & 1u), 0.5 * f32(vertex_id & 2u));
|
let v = b_path_vertices[vertex_id];
|
||||||
let sprite = b_path_sprites[instance_id];
|
let sprite = b_path_sprites[instance_id];
|
||||||
// Don't apply content mask because it was already accounted for when rasterizing the path.
|
|
||||||
|
|
||||||
var out = PathVarying();
|
var out = PathVarying();
|
||||||
out.position = to_device_position(unit_vertex, sprite.bounds);
|
out.position = to_device_position_impl(v.xy_position);
|
||||||
out.tile_position = to_tile_position(unit_vertex, sprite.tile);
|
out.clip_distances = distance_from_clip_rect_impl(v.xy_position, v.content_mask);
|
||||||
out.instance_id = instance_id;
|
out.instance_id = instance_id;
|
||||||
|
|
||||||
let gradient = prepare_gradient_color(
|
let gradient = prepare_gradient_color(
|
||||||
|
@ -1006,13 +969,15 @@ fn vs_path(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) insta
|
||||||
|
|
||||||
@fragment
|
@fragment
|
||||||
fn fs_path(input: PathVarying) -> @location(0) vec4<f32> {
|
fn fs_path(input: PathVarying) -> @location(0) vec4<f32> {
|
||||||
let sample = textureSample(t_sprite, s_sprite, input.tile_position).r;
|
if any(input.clip_distances < vec4<f32>(0.0)) {
|
||||||
let mask = 1.0 - abs(1.0 - sample % 2.0);
|
return vec4<f32>(0.0);
|
||||||
|
}
|
||||||
|
|
||||||
let sprite = b_path_sprites[input.instance_id];
|
let sprite = b_path_sprites[input.instance_id];
|
||||||
let background = sprite.color;
|
let background = sprite.color;
|
||||||
let color = gradient_color(background, input.position.xy, sprite.bounds,
|
let color = gradient_color(background, input.position.xy, sprite.bounds,
|
||||||
input.color_solid, input.color0, input.color1);
|
input.color_solid, input.color0, input.color1);
|
||||||
return blend_color(color, mask);
|
return blend_color(color, 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- underlines --- //
|
// --- underlines --- //
|
||||||
|
|
|
@ -13,14 +13,12 @@ use std::borrow::Cow;
|
||||||
pub(crate) struct MetalAtlas(Mutex<MetalAtlasState>);
|
pub(crate) struct MetalAtlas(Mutex<MetalAtlasState>);
|
||||||
|
|
||||||
impl MetalAtlas {
|
impl MetalAtlas {
|
||||||
pub(crate) fn new(device: Device, path_sample_count: u32) -> Self {
|
pub(crate) fn new(device: Device) -> Self {
|
||||||
MetalAtlas(Mutex::new(MetalAtlasState {
|
MetalAtlas(Mutex::new(MetalAtlasState {
|
||||||
device: AssertSend(device),
|
device: AssertSend(device),
|
||||||
monochrome_textures: Default::default(),
|
monochrome_textures: Default::default(),
|
||||||
polychrome_textures: Default::default(),
|
polychrome_textures: Default::default(),
|
||||||
path_textures: Default::default(),
|
|
||||||
tiles_by_key: Default::default(),
|
tiles_by_key: Default::default(),
|
||||||
path_sample_count,
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,10 +26,7 @@ impl MetalAtlas {
|
||||||
self.0.lock().texture(id).metal_texture.clone()
|
self.0.lock().texture(id).metal_texture.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn msaa_texture(&self, id: AtlasTextureId) -> Option<metal::Texture> {
|
#[allow(dead_code)]
|
||||||
self.0.lock().texture(id).msaa_texture.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn allocate(
|
pub(crate) fn allocate(
|
||||||
&self,
|
&self,
|
||||||
size: Size<DevicePixels>,
|
size: Size<DevicePixels>,
|
||||||
|
@ -40,12 +35,12 @@ impl MetalAtlas {
|
||||||
self.0.lock().allocate(size, texture_kind)
|
self.0.lock().allocate(size, texture_kind)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub(crate) fn clear_textures(&self, texture_kind: AtlasTextureKind) {
|
pub(crate) fn clear_textures(&self, texture_kind: AtlasTextureKind) {
|
||||||
let mut lock = self.0.lock();
|
let mut lock = self.0.lock();
|
||||||
let textures = match texture_kind {
|
let textures = match texture_kind {
|
||||||
AtlasTextureKind::Monochrome => &mut lock.monochrome_textures,
|
AtlasTextureKind::Monochrome => &mut lock.monochrome_textures,
|
||||||
AtlasTextureKind::Polychrome => &mut lock.polychrome_textures,
|
AtlasTextureKind::Polychrome => &mut lock.polychrome_textures,
|
||||||
AtlasTextureKind::Path => &mut lock.path_textures,
|
|
||||||
};
|
};
|
||||||
for texture in textures.iter_mut() {
|
for texture in textures.iter_mut() {
|
||||||
texture.clear();
|
texture.clear();
|
||||||
|
@ -57,9 +52,7 @@ struct MetalAtlasState {
|
||||||
device: AssertSend<Device>,
|
device: AssertSend<Device>,
|
||||||
monochrome_textures: AtlasTextureList<MetalAtlasTexture>,
|
monochrome_textures: AtlasTextureList<MetalAtlasTexture>,
|
||||||
polychrome_textures: AtlasTextureList<MetalAtlasTexture>,
|
polychrome_textures: AtlasTextureList<MetalAtlasTexture>,
|
||||||
path_textures: AtlasTextureList<MetalAtlasTexture>,
|
|
||||||
tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
|
tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
|
||||||
path_sample_count: u32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlatformAtlas for MetalAtlas {
|
impl PlatformAtlas for MetalAtlas {
|
||||||
|
@ -94,7 +87,6 @@ impl PlatformAtlas for MetalAtlas {
|
||||||
let textures = match id.kind {
|
let textures = match id.kind {
|
||||||
AtlasTextureKind::Monochrome => &mut lock.monochrome_textures,
|
AtlasTextureKind::Monochrome => &mut lock.monochrome_textures,
|
||||||
AtlasTextureKind::Polychrome => &mut lock.polychrome_textures,
|
AtlasTextureKind::Polychrome => &mut lock.polychrome_textures,
|
||||||
AtlasTextureKind::Path => &mut lock.polychrome_textures,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(texture_slot) = textures
|
let Some(texture_slot) = textures
|
||||||
|
@ -128,7 +120,6 @@ impl MetalAtlasState {
|
||||||
let textures = match texture_kind {
|
let textures = match texture_kind {
|
||||||
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
|
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
|
||||||
AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
|
AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
|
||||||
AtlasTextureKind::Path => &mut self.path_textures,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(tile) = textures
|
if let Some(tile) = textures
|
||||||
|
@ -173,31 +164,14 @@ impl MetalAtlasState {
|
||||||
pixel_format = metal::MTLPixelFormat::BGRA8Unorm;
|
pixel_format = metal::MTLPixelFormat::BGRA8Unorm;
|
||||||
usage = metal::MTLTextureUsage::ShaderRead;
|
usage = metal::MTLTextureUsage::ShaderRead;
|
||||||
}
|
}
|
||||||
AtlasTextureKind::Path => {
|
|
||||||
pixel_format = metal::MTLPixelFormat::R16Float;
|
|
||||||
usage = metal::MTLTextureUsage::RenderTarget | metal::MTLTextureUsage::ShaderRead;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
texture_descriptor.set_pixel_format(pixel_format);
|
texture_descriptor.set_pixel_format(pixel_format);
|
||||||
texture_descriptor.set_usage(usage);
|
texture_descriptor.set_usage(usage);
|
||||||
let metal_texture = self.device.new_texture(&texture_descriptor);
|
let metal_texture = self.device.new_texture(&texture_descriptor);
|
||||||
|
|
||||||
// We currently only enable MSAA for path textures.
|
|
||||||
let msaa_texture = if self.path_sample_count > 1 && kind == AtlasTextureKind::Path {
|
|
||||||
let mut descriptor = texture_descriptor.clone();
|
|
||||||
descriptor.set_texture_type(metal::MTLTextureType::D2Multisample);
|
|
||||||
descriptor.set_storage_mode(metal::MTLStorageMode::Private);
|
|
||||||
descriptor.set_sample_count(self.path_sample_count as _);
|
|
||||||
let msaa_texture = self.device.new_texture(&descriptor);
|
|
||||||
Some(msaa_texture)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let texture_list = match kind {
|
let texture_list = match kind {
|
||||||
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
|
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
|
||||||
AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
|
AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
|
||||||
AtlasTextureKind::Path => &mut self.path_textures,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let index = texture_list.free_list.pop();
|
let index = texture_list.free_list.pop();
|
||||||
|
@ -209,7 +183,6 @@ impl MetalAtlasState {
|
||||||
},
|
},
|
||||||
allocator: etagere::BucketedAtlasAllocator::new(size.into()),
|
allocator: etagere::BucketedAtlasAllocator::new(size.into()),
|
||||||
metal_texture: AssertSend(metal_texture),
|
metal_texture: AssertSend(metal_texture),
|
||||||
msaa_texture: AssertSend(msaa_texture),
|
|
||||||
live_atlas_keys: 0,
|
live_atlas_keys: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -226,7 +199,6 @@ impl MetalAtlasState {
|
||||||
let textures = match id.kind {
|
let textures = match id.kind {
|
||||||
crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
|
crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
|
||||||
crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
|
crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
|
||||||
crate::AtlasTextureKind::Path => &self.path_textures,
|
|
||||||
};
|
};
|
||||||
textures[id.index as usize].as_ref().unwrap()
|
textures[id.index as usize].as_ref().unwrap()
|
||||||
}
|
}
|
||||||
|
@ -236,7 +208,6 @@ struct MetalAtlasTexture {
|
||||||
id: AtlasTextureId,
|
id: AtlasTextureId,
|
||||||
allocator: BucketedAtlasAllocator,
|
allocator: BucketedAtlasAllocator,
|
||||||
metal_texture: AssertSend<metal::Texture>,
|
metal_texture: AssertSend<metal::Texture>,
|
||||||
msaa_texture: AssertSend<Option<metal::Texture>>,
|
|
||||||
live_atlas_keys: u32,
|
live_atlas_keys: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,27 +1,28 @@
|
||||||
use super::metal_atlas::MetalAtlas;
|
use super::metal_atlas::MetalAtlas;
|
||||||
use crate::{
|
use crate::{
|
||||||
AtlasTextureId, AtlasTextureKind, AtlasTile, Background, Bounds, ContentMask, DevicePixels,
|
AtlasTextureId, Background, Bounds, ContentMask, DevicePixels, MonochromeSprite, PaintSurface,
|
||||||
MonochromeSprite, PaintSurface, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch,
|
Path, PathVertex, PolychromeSprite, PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size,
|
||||||
Quad, ScaledPixels, Scene, Shadow, Size, Surface, Underline, point, size,
|
Surface, Underline, point, size,
|
||||||
};
|
};
|
||||||
use anyhow::{Context as _, Result};
|
use anyhow::Result;
|
||||||
use block::ConcreteBlock;
|
use block::ConcreteBlock;
|
||||||
use cocoa::{
|
use cocoa::{
|
||||||
base::{NO, YES},
|
base::{NO, YES},
|
||||||
foundation::{NSSize, NSUInteger},
|
foundation::{NSSize, NSUInteger},
|
||||||
quartzcore::AutoresizingMask,
|
quartzcore::AutoresizingMask,
|
||||||
};
|
};
|
||||||
use collections::HashMap;
|
|
||||||
use core_foundation::base::TCFType;
|
use core_foundation::base::TCFType;
|
||||||
use core_video::{
|
use core_video::{
|
||||||
metal_texture::CVMetalTextureGetTexture, metal_texture_cache::CVMetalTextureCache,
|
metal_texture::CVMetalTextureGetTexture, metal_texture_cache::CVMetalTextureCache,
|
||||||
pixel_buffer::kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,
|
pixel_buffer::kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,
|
||||||
};
|
};
|
||||||
use foreign_types::{ForeignType, ForeignTypeRef};
|
use foreign_types::{ForeignType, ForeignTypeRef};
|
||||||
use metal::{CAMetalLayer, CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};
|
use metal::{
|
||||||
|
CAMetalLayer, CommandQueue, MTLDrawPrimitivesIndirectArguments, MTLPixelFormat,
|
||||||
|
MTLResourceOptions, NSRange,
|
||||||
|
};
|
||||||
use objc::{self, msg_send, sel, sel_impl};
|
use objc::{self, msg_send, sel, sel_impl};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use smallvec::SmallVec;
|
|
||||||
use std::{cell::Cell, ffi::c_void, mem, ptr, sync::Arc};
|
use std::{cell::Cell, ffi::c_void, mem, ptr, sync::Arc};
|
||||||
|
|
||||||
// Exported to metal
|
// Exported to metal
|
||||||
|
@ -31,9 +32,6 @@ pub(crate) type PointF = crate::Point<f32>;
|
||||||
const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
|
const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
|
||||||
#[cfg(feature = "runtime_shaders")]
|
#[cfg(feature = "runtime_shaders")]
|
||||||
const SHADERS_SOURCE_FILE: &str = include_str!(concat!(env!("OUT_DIR"), "/stitched_shaders.metal"));
|
const SHADERS_SOURCE_FILE: &str = include_str!(concat!(env!("OUT_DIR"), "/stitched_shaders.metal"));
|
||||||
// Use 4x MSAA, all devices support it.
|
|
||||||
// https://developer.apple.com/documentation/metal/mtldevice/1433355-supportstexturesamplecount
|
|
||||||
const PATH_SAMPLE_COUNT: u32 = 4;
|
|
||||||
|
|
||||||
pub type Context = Arc<Mutex<InstanceBufferPool>>;
|
pub type Context = Arc<Mutex<InstanceBufferPool>>;
|
||||||
pub type Renderer = MetalRenderer;
|
pub type Renderer = MetalRenderer;
|
||||||
|
@ -98,8 +96,7 @@ pub(crate) struct MetalRenderer {
|
||||||
layer: metal::MetalLayer,
|
layer: metal::MetalLayer,
|
||||||
presents_with_transaction: bool,
|
presents_with_transaction: bool,
|
||||||
command_queue: CommandQueue,
|
command_queue: CommandQueue,
|
||||||
paths_rasterization_pipeline_state: metal::RenderPipelineState,
|
path_pipeline_state: metal::RenderPipelineState,
|
||||||
path_sprites_pipeline_state: metal::RenderPipelineState,
|
|
||||||
shadows_pipeline_state: metal::RenderPipelineState,
|
shadows_pipeline_state: metal::RenderPipelineState,
|
||||||
quads_pipeline_state: metal::RenderPipelineState,
|
quads_pipeline_state: metal::RenderPipelineState,
|
||||||
underlines_pipeline_state: metal::RenderPipelineState,
|
underlines_pipeline_state: metal::RenderPipelineState,
|
||||||
|
@ -111,6 +108,8 @@ pub(crate) struct MetalRenderer {
|
||||||
instance_buffer_pool: Arc<Mutex<InstanceBufferPool>>,
|
instance_buffer_pool: Arc<Mutex<InstanceBufferPool>>,
|
||||||
sprite_atlas: Arc<MetalAtlas>,
|
sprite_atlas: Arc<MetalAtlas>,
|
||||||
core_video_texture_cache: core_video::metal_texture_cache::CVMetalTextureCache,
|
core_video_texture_cache: core_video::metal_texture_cache::CVMetalTextureCache,
|
||||||
|
sample_count: u64,
|
||||||
|
msaa_texture: Option<metal::Texture>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MetalRenderer {
|
impl MetalRenderer {
|
||||||
|
@ -169,22 +168,19 @@ impl MetalRenderer {
|
||||||
MTLResourceOptions::StorageModeManaged,
|
MTLResourceOptions::StorageModeManaged,
|
||||||
);
|
);
|
||||||
|
|
||||||
let paths_rasterization_pipeline_state = build_path_rasterization_pipeline_state(
|
let sample_count = [4, 2, 1]
|
||||||
|
.into_iter()
|
||||||
|
.find(|count| device.supports_texture_sample_count(*count))
|
||||||
|
.unwrap_or(1);
|
||||||
|
|
||||||
|
let path_pipeline_state = build_pipeline_state(
|
||||||
&device,
|
&device,
|
||||||
&library,
|
&library,
|
||||||
"paths_rasterization",
|
"paths",
|
||||||
"path_rasterization_vertex",
|
"path_vertex",
|
||||||
"path_rasterization_fragment",
|
"path_fragment",
|
||||||
MTLPixelFormat::R16Float,
|
|
||||||
PATH_SAMPLE_COUNT,
|
|
||||||
);
|
|
||||||
let path_sprites_pipeline_state = build_pipeline_state(
|
|
||||||
&device,
|
|
||||||
&library,
|
|
||||||
"path_sprites",
|
|
||||||
"path_sprite_vertex",
|
|
||||||
"path_sprite_fragment",
|
|
||||||
MTLPixelFormat::BGRA8Unorm,
|
MTLPixelFormat::BGRA8Unorm,
|
||||||
|
sample_count,
|
||||||
);
|
);
|
||||||
let shadows_pipeline_state = build_pipeline_state(
|
let shadows_pipeline_state = build_pipeline_state(
|
||||||
&device,
|
&device,
|
||||||
|
@ -193,6 +189,7 @@ impl MetalRenderer {
|
||||||
"shadow_vertex",
|
"shadow_vertex",
|
||||||
"shadow_fragment",
|
"shadow_fragment",
|
||||||
MTLPixelFormat::BGRA8Unorm,
|
MTLPixelFormat::BGRA8Unorm,
|
||||||
|
sample_count,
|
||||||
);
|
);
|
||||||
let quads_pipeline_state = build_pipeline_state(
|
let quads_pipeline_state = build_pipeline_state(
|
||||||
&device,
|
&device,
|
||||||
|
@ -201,6 +198,7 @@ impl MetalRenderer {
|
||||||
"quad_vertex",
|
"quad_vertex",
|
||||||
"quad_fragment",
|
"quad_fragment",
|
||||||
MTLPixelFormat::BGRA8Unorm,
|
MTLPixelFormat::BGRA8Unorm,
|
||||||
|
sample_count,
|
||||||
);
|
);
|
||||||
let underlines_pipeline_state = build_pipeline_state(
|
let underlines_pipeline_state = build_pipeline_state(
|
||||||
&device,
|
&device,
|
||||||
|
@ -209,6 +207,7 @@ impl MetalRenderer {
|
||||||
"underline_vertex",
|
"underline_vertex",
|
||||||
"underline_fragment",
|
"underline_fragment",
|
||||||
MTLPixelFormat::BGRA8Unorm,
|
MTLPixelFormat::BGRA8Unorm,
|
||||||
|
sample_count,
|
||||||
);
|
);
|
||||||
let monochrome_sprites_pipeline_state = build_pipeline_state(
|
let monochrome_sprites_pipeline_state = build_pipeline_state(
|
||||||
&device,
|
&device,
|
||||||
|
@ -217,6 +216,7 @@ impl MetalRenderer {
|
||||||
"monochrome_sprite_vertex",
|
"monochrome_sprite_vertex",
|
||||||
"monochrome_sprite_fragment",
|
"monochrome_sprite_fragment",
|
||||||
MTLPixelFormat::BGRA8Unorm,
|
MTLPixelFormat::BGRA8Unorm,
|
||||||
|
sample_count,
|
||||||
);
|
);
|
||||||
let polychrome_sprites_pipeline_state = build_pipeline_state(
|
let polychrome_sprites_pipeline_state = build_pipeline_state(
|
||||||
&device,
|
&device,
|
||||||
|
@ -225,6 +225,7 @@ impl MetalRenderer {
|
||||||
"polychrome_sprite_vertex",
|
"polychrome_sprite_vertex",
|
||||||
"polychrome_sprite_fragment",
|
"polychrome_sprite_fragment",
|
||||||
MTLPixelFormat::BGRA8Unorm,
|
MTLPixelFormat::BGRA8Unorm,
|
||||||
|
sample_count,
|
||||||
);
|
);
|
||||||
let surfaces_pipeline_state = build_pipeline_state(
|
let surfaces_pipeline_state = build_pipeline_state(
|
||||||
&device,
|
&device,
|
||||||
|
@ -233,20 +234,21 @@ impl MetalRenderer {
|
||||||
"surface_vertex",
|
"surface_vertex",
|
||||||
"surface_fragment",
|
"surface_fragment",
|
||||||
MTLPixelFormat::BGRA8Unorm,
|
MTLPixelFormat::BGRA8Unorm,
|
||||||
|
sample_count,
|
||||||
);
|
);
|
||||||
|
|
||||||
let command_queue = device.new_command_queue();
|
let command_queue = device.new_command_queue();
|
||||||
let sprite_atlas = Arc::new(MetalAtlas::new(device.clone(), PATH_SAMPLE_COUNT));
|
let sprite_atlas = Arc::new(MetalAtlas::new(device.clone()));
|
||||||
let core_video_texture_cache =
|
let core_video_texture_cache =
|
||||||
CVMetalTextureCache::new(None, device.clone(), None).unwrap();
|
CVMetalTextureCache::new(None, device.clone(), None).unwrap();
|
||||||
|
let msaa_texture = create_msaa_texture(&device, &layer, sample_count);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
device,
|
device,
|
||||||
layer,
|
layer,
|
||||||
presents_with_transaction: false,
|
presents_with_transaction: false,
|
||||||
command_queue,
|
command_queue,
|
||||||
paths_rasterization_pipeline_state,
|
path_pipeline_state,
|
||||||
path_sprites_pipeline_state,
|
|
||||||
shadows_pipeline_state,
|
shadows_pipeline_state,
|
||||||
quads_pipeline_state,
|
quads_pipeline_state,
|
||||||
underlines_pipeline_state,
|
underlines_pipeline_state,
|
||||||
|
@ -257,6 +259,8 @@ impl MetalRenderer {
|
||||||
instance_buffer_pool,
|
instance_buffer_pool,
|
||||||
sprite_atlas,
|
sprite_atlas,
|
||||||
core_video_texture_cache,
|
core_video_texture_cache,
|
||||||
|
sample_count,
|
||||||
|
msaa_texture,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,6 +293,8 @@ impl MetalRenderer {
|
||||||
setDrawableSize: size
|
setDrawableSize: size
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.msaa_texture = create_msaa_texture(&self.device, &self.layer, self.sample_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_transparency(&self, _transparent: bool) {
|
pub fn update_transparency(&self, _transparent: bool) {
|
||||||
|
@ -375,25 +381,23 @@ impl MetalRenderer {
|
||||||
let command_queue = self.command_queue.clone();
|
let command_queue = self.command_queue.clone();
|
||||||
let command_buffer = command_queue.new_command_buffer();
|
let command_buffer = command_queue.new_command_buffer();
|
||||||
let mut instance_offset = 0;
|
let mut instance_offset = 0;
|
||||||
|
|
||||||
let path_tiles = self
|
|
||||||
.rasterize_paths(
|
|
||||||
scene.paths(),
|
|
||||||
instance_buffer,
|
|
||||||
&mut instance_offset,
|
|
||||||
command_buffer,
|
|
||||||
)
|
|
||||||
.with_context(|| format!("rasterizing {} paths", scene.paths().len()))?;
|
|
||||||
|
|
||||||
let render_pass_descriptor = metal::RenderPassDescriptor::new();
|
let render_pass_descriptor = metal::RenderPassDescriptor::new();
|
||||||
let color_attachment = render_pass_descriptor
|
let color_attachment = render_pass_descriptor
|
||||||
.color_attachments()
|
.color_attachments()
|
||||||
.object_at(0)
|
.object_at(0)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
color_attachment.set_texture(Some(drawable.texture()));
|
if let Some(msaa_texture_ref) = self.msaa_texture.as_deref() {
|
||||||
|
color_attachment.set_texture(Some(msaa_texture_ref));
|
||||||
color_attachment.set_load_action(metal::MTLLoadAction::Clear);
|
color_attachment.set_load_action(metal::MTLLoadAction::Clear);
|
||||||
|
color_attachment.set_store_action(metal::MTLStoreAction::MultisampleResolve);
|
||||||
|
color_attachment.set_resolve_texture(Some(drawable.texture()));
|
||||||
|
} else {
|
||||||
|
color_attachment.set_load_action(metal::MTLLoadAction::Clear);
|
||||||
|
color_attachment.set_texture(Some(drawable.texture()));
|
||||||
color_attachment.set_store_action(metal::MTLStoreAction::Store);
|
color_attachment.set_store_action(metal::MTLStoreAction::Store);
|
||||||
|
}
|
||||||
|
|
||||||
let alpha = if self.layer.is_opaque() { 1. } else { 0. };
|
let alpha = if self.layer.is_opaque() { 1. } else { 0. };
|
||||||
color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., alpha));
|
color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., alpha));
|
||||||
let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
|
let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
|
||||||
|
@ -425,7 +429,6 @@ impl MetalRenderer {
|
||||||
),
|
),
|
||||||
PrimitiveBatch::Paths(paths) => self.draw_paths(
|
PrimitiveBatch::Paths(paths) => self.draw_paths(
|
||||||
paths,
|
paths,
|
||||||
&path_tiles,
|
|
||||||
instance_buffer,
|
instance_buffer,
|
||||||
&mut instance_offset,
|
&mut instance_offset,
|
||||||
viewport_size,
|
viewport_size,
|
||||||
|
@ -493,106 +496,6 @@ impl MetalRenderer {
|
||||||
Ok(command_buffer.to_owned())
|
Ok(command_buffer.to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rasterize_paths(
|
|
||||||
&self,
|
|
||||||
paths: &[Path<ScaledPixels>],
|
|
||||||
instance_buffer: &mut InstanceBuffer,
|
|
||||||
instance_offset: &mut usize,
|
|
||||||
command_buffer: &metal::CommandBufferRef,
|
|
||||||
) -> Option<HashMap<PathId, AtlasTile>> {
|
|
||||||
self.sprite_atlas.clear_textures(AtlasTextureKind::Path);
|
|
||||||
|
|
||||||
let mut tiles = HashMap::default();
|
|
||||||
let mut vertices_by_texture_id = HashMap::default();
|
|
||||||
for path in paths {
|
|
||||||
let clipped_bounds = path.bounds.intersect(&path.content_mask.bounds);
|
|
||||||
|
|
||||||
let tile = self
|
|
||||||
.sprite_atlas
|
|
||||||
.allocate(clipped_bounds.size.map(Into::into), AtlasTextureKind::Path)?;
|
|
||||||
vertices_by_texture_id
|
|
||||||
.entry(tile.texture_id)
|
|
||||||
.or_insert(Vec::new())
|
|
||||||
.extend(path.vertices.iter().map(|vertex| PathVertex {
|
|
||||||
xy_position: vertex.xy_position - clipped_bounds.origin
|
|
||||||
+ tile.bounds.origin.map(Into::into),
|
|
||||||
st_position: vertex.st_position,
|
|
||||||
content_mask: ContentMask {
|
|
||||||
bounds: tile.bounds.map(Into::into),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
tiles.insert(path.id, tile);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (texture_id, vertices) in vertices_by_texture_id {
|
|
||||||
align_offset(instance_offset);
|
|
||||||
let vertices_bytes_len = mem::size_of_val(vertices.as_slice());
|
|
||||||
let next_offset = *instance_offset + vertices_bytes_len;
|
|
||||||
if next_offset > instance_buffer.size {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let render_pass_descriptor = metal::RenderPassDescriptor::new();
|
|
||||||
let color_attachment = render_pass_descriptor
|
|
||||||
.color_attachments()
|
|
||||||
.object_at(0)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let texture = self.sprite_atlas.metal_texture(texture_id);
|
|
||||||
let msaa_texture = self.sprite_atlas.msaa_texture(texture_id);
|
|
||||||
|
|
||||||
if let Some(msaa_texture) = msaa_texture {
|
|
||||||
color_attachment.set_texture(Some(&msaa_texture));
|
|
||||||
color_attachment.set_resolve_texture(Some(&texture));
|
|
||||||
color_attachment.set_load_action(metal::MTLLoadAction::Clear);
|
|
||||||
color_attachment.set_store_action(metal::MTLStoreAction::MultisampleResolve);
|
|
||||||
} else {
|
|
||||||
color_attachment.set_texture(Some(&texture));
|
|
||||||
color_attachment.set_load_action(metal::MTLLoadAction::Clear);
|
|
||||||
color_attachment.set_store_action(metal::MTLStoreAction::Store);
|
|
||||||
}
|
|
||||||
color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., 1.));
|
|
||||||
|
|
||||||
let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
|
|
||||||
command_encoder.set_render_pipeline_state(&self.paths_rasterization_pipeline_state);
|
|
||||||
command_encoder.set_vertex_buffer(
|
|
||||||
PathRasterizationInputIndex::Vertices as u64,
|
|
||||||
Some(&instance_buffer.metal_buffer),
|
|
||||||
*instance_offset as u64,
|
|
||||||
);
|
|
||||||
let texture_size = Size {
|
|
||||||
width: DevicePixels::from(texture.width()),
|
|
||||||
height: DevicePixels::from(texture.height()),
|
|
||||||
};
|
|
||||||
command_encoder.set_vertex_bytes(
|
|
||||||
PathRasterizationInputIndex::AtlasTextureSize as u64,
|
|
||||||
mem::size_of_val(&texture_size) as u64,
|
|
||||||
&texture_size as *const Size<DevicePixels> as *const _,
|
|
||||||
);
|
|
||||||
|
|
||||||
let buffer_contents = unsafe {
|
|
||||||
(instance_buffer.metal_buffer.contents() as *mut u8).add(*instance_offset)
|
|
||||||
};
|
|
||||||
unsafe {
|
|
||||||
ptr::copy_nonoverlapping(
|
|
||||||
vertices.as_ptr() as *const u8,
|
|
||||||
buffer_contents,
|
|
||||||
vertices_bytes_len,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
command_encoder.draw_primitives(
|
|
||||||
metal::MTLPrimitiveType::Triangle,
|
|
||||||
0,
|
|
||||||
vertices.len() as u64,
|
|
||||||
);
|
|
||||||
command_encoder.end_encoding();
|
|
||||||
*instance_offset = next_offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(tiles)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_shadows(
|
fn draw_shadows(
|
||||||
&self,
|
&self,
|
||||||
shadows: &[Shadow],
|
shadows: &[Shadow],
|
||||||
|
@ -718,7 +621,6 @@ impl MetalRenderer {
|
||||||
fn draw_paths(
|
fn draw_paths(
|
||||||
&self,
|
&self,
|
||||||
paths: &[Path<ScaledPixels>],
|
paths: &[Path<ScaledPixels>],
|
||||||
tiles_by_path_id: &HashMap<PathId, AtlasTile>,
|
|
||||||
instance_buffer: &mut InstanceBuffer,
|
instance_buffer: &mut InstanceBuffer,
|
||||||
instance_offset: &mut usize,
|
instance_offset: &mut usize,
|
||||||
viewport_size: Size<DevicePixels>,
|
viewport_size: Size<DevicePixels>,
|
||||||
|
@ -728,100 +630,108 @@ impl MetalRenderer {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
command_encoder.set_render_pipeline_state(&self.path_sprites_pipeline_state);
|
command_encoder.set_render_pipeline_state(&self.path_pipeline_state);
|
||||||
command_encoder.set_vertex_buffer(
|
|
||||||
SpriteInputIndex::Vertices as u64,
|
unsafe {
|
||||||
Some(&self.unit_vertices),
|
let base_addr = instance_buffer.metal_buffer.contents();
|
||||||
0,
|
let mut p = (base_addr as *mut u8).add(*instance_offset);
|
||||||
|
let mut draw_indirect_commands = Vec::with_capacity(paths.len());
|
||||||
|
|
||||||
|
// copy vertices
|
||||||
|
let vertices_offset = (p as usize) - (base_addr as usize);
|
||||||
|
let mut first_vertex = 0;
|
||||||
|
for (i, path) in paths.iter().enumerate() {
|
||||||
|
if (p as usize) - (base_addr as usize)
|
||||||
|
+ (mem::size_of::<PathVertex<ScaledPixels>>() * path.vertices.len())
|
||||||
|
> instance_buffer.size
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for v in &path.vertices {
|
||||||
|
*(p as *mut PathVertex<ScaledPixels>) = PathVertex {
|
||||||
|
xy_position: v.xy_position,
|
||||||
|
content_mask: ContentMask {
|
||||||
|
bounds: path.content_mask.bounds,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
p = p.add(mem::size_of::<PathVertex<ScaledPixels>>());
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_indirect_commands.push(MTLDrawPrimitivesIndirectArguments {
|
||||||
|
vertexCount: path.vertices.len() as u32,
|
||||||
|
instanceCount: 1,
|
||||||
|
vertexStart: first_vertex,
|
||||||
|
baseInstance: i as u32,
|
||||||
|
});
|
||||||
|
first_vertex += path.vertices.len() as u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy sprites
|
||||||
|
let sprites_offset = (p as u64) - (base_addr as u64);
|
||||||
|
if (p as usize) - (base_addr as usize) + (mem::size_of::<PathSprite>() * paths.len())
|
||||||
|
> instance_buffer.size
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for path in paths {
|
||||||
|
*(p as *mut PathSprite) = PathSprite {
|
||||||
|
bounds: path.bounds,
|
||||||
|
color: path.color,
|
||||||
|
};
|
||||||
|
p = p.add(mem::size_of::<PathSprite>());
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy indirect commands
|
||||||
|
let icb_bytes_len = mem::size_of_val(draw_indirect_commands.as_slice());
|
||||||
|
let icb_offset = (p as u64) - (base_addr as u64);
|
||||||
|
if (p as usize) - (base_addr as usize) + icb_bytes_len > instance_buffer.size {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ptr::copy_nonoverlapping(
|
||||||
|
draw_indirect_commands.as_ptr() as *const u8,
|
||||||
|
p,
|
||||||
|
icb_bytes_len,
|
||||||
);
|
);
|
||||||
|
p = p.add(icb_bytes_len);
|
||||||
|
|
||||||
|
// draw path
|
||||||
|
command_encoder.set_vertex_buffer(
|
||||||
|
PathInputIndex::Vertices as u64,
|
||||||
|
Some(&instance_buffer.metal_buffer),
|
||||||
|
vertices_offset as u64,
|
||||||
|
);
|
||||||
|
|
||||||
command_encoder.set_vertex_bytes(
|
command_encoder.set_vertex_bytes(
|
||||||
SpriteInputIndex::ViewportSize as u64,
|
PathInputIndex::ViewportSize as u64,
|
||||||
mem::size_of_val(&viewport_size) as u64,
|
mem::size_of_val(&viewport_size) as u64,
|
||||||
&viewport_size as *const Size<DevicePixels> as *const _,
|
&viewport_size as *const Size<DevicePixels> as *const _,
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut prev_texture_id = None;
|
|
||||||
let mut sprites = SmallVec::<[_; 1]>::new();
|
|
||||||
let mut paths_and_tiles = paths
|
|
||||||
.iter()
|
|
||||||
.map(|path| (path, tiles_by_path_id.get(&path.id).unwrap()))
|
|
||||||
.peekable();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
if let Some((path, tile)) = paths_and_tiles.peek() {
|
|
||||||
if prev_texture_id.map_or(true, |texture_id| texture_id == tile.texture_id) {
|
|
||||||
prev_texture_id = Some(tile.texture_id);
|
|
||||||
let origin = path.bounds.intersect(&path.content_mask.bounds).origin;
|
|
||||||
sprites.push(PathSprite {
|
|
||||||
bounds: Bounds {
|
|
||||||
origin: origin.map(|p| p.floor()),
|
|
||||||
size: tile.bounds.size.map(Into::into),
|
|
||||||
},
|
|
||||||
color: path.color,
|
|
||||||
tile: (*tile).clone(),
|
|
||||||
});
|
|
||||||
paths_and_tiles.next();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if sprites.is_empty() {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
align_offset(instance_offset);
|
|
||||||
let texture_id = prev_texture_id.take().unwrap();
|
|
||||||
let texture: metal::Texture = self.sprite_atlas.metal_texture(texture_id);
|
|
||||||
let texture_size = size(
|
|
||||||
DevicePixels(texture.width() as i32),
|
|
||||||
DevicePixels(texture.height() as i32),
|
|
||||||
);
|
|
||||||
|
|
||||||
command_encoder.set_vertex_buffer(
|
command_encoder.set_vertex_buffer(
|
||||||
SpriteInputIndex::Sprites as u64,
|
PathInputIndex::Sprites as u64,
|
||||||
Some(&instance_buffer.metal_buffer),
|
Some(&instance_buffer.metal_buffer),
|
||||||
*instance_offset as u64,
|
sprites_offset,
|
||||||
);
|
|
||||||
command_encoder.set_vertex_bytes(
|
|
||||||
SpriteInputIndex::AtlasTextureSize as u64,
|
|
||||||
mem::size_of_val(&texture_size) as u64,
|
|
||||||
&texture_size as *const Size<DevicePixels> as *const _,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
command_encoder.set_fragment_buffer(
|
command_encoder.set_fragment_buffer(
|
||||||
SpriteInputIndex::Sprites as u64,
|
PathInputIndex::Sprites as u64,
|
||||||
Some(&instance_buffer.metal_buffer),
|
Some(&instance_buffer.metal_buffer),
|
||||||
*instance_offset as u64,
|
sprites_offset,
|
||||||
);
|
);
|
||||||
command_encoder
|
|
||||||
.set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture));
|
|
||||||
|
|
||||||
let sprite_bytes_len = mem::size_of_val(sprites.as_slice());
|
for i in 0..paths.len() {
|
||||||
let next_offset = *instance_offset + sprite_bytes_len;
|
command_encoder.draw_primitives_indirect(
|
||||||
if next_offset > instance_buffer.size {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let buffer_contents = unsafe {
|
|
||||||
(instance_buffer.metal_buffer.contents() as *mut u8).add(*instance_offset)
|
|
||||||
};
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
ptr::copy_nonoverlapping(
|
|
||||||
sprites.as_ptr() as *const u8,
|
|
||||||
buffer_contents,
|
|
||||||
sprite_bytes_len,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
command_encoder.draw_primitives_instanced(
|
|
||||||
metal::MTLPrimitiveType::Triangle,
|
metal::MTLPrimitiveType::Triangle,
|
||||||
0,
|
&instance_buffer.metal_buffer,
|
||||||
6,
|
icb_offset
|
||||||
sprites.len() as u64,
|
+ (i * std::mem::size_of::<MTLDrawPrimitivesIndirectArguments>()) as u64,
|
||||||
);
|
);
|
||||||
*instance_offset = next_offset;
|
|
||||||
sprites.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*instance_offset = (p as usize) - (base_addr as usize);
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1143,6 +1053,7 @@ fn build_pipeline_state(
|
||||||
vertex_fn_name: &str,
|
vertex_fn_name: &str,
|
||||||
fragment_fn_name: &str,
|
fragment_fn_name: &str,
|
||||||
pixel_format: metal::MTLPixelFormat,
|
pixel_format: metal::MTLPixelFormat,
|
||||||
|
sample_count: u64,
|
||||||
) -> metal::RenderPipelineState {
|
) -> metal::RenderPipelineState {
|
||||||
let vertex_fn = library
|
let vertex_fn = library
|
||||||
.get_function(vertex_fn_name, None)
|
.get_function(vertex_fn_name, None)
|
||||||
|
@ -1155,6 +1066,7 @@ fn build_pipeline_state(
|
||||||
descriptor.set_label(label);
|
descriptor.set_label(label);
|
||||||
descriptor.set_vertex_function(Some(vertex_fn.as_ref()));
|
descriptor.set_vertex_function(Some(vertex_fn.as_ref()));
|
||||||
descriptor.set_fragment_function(Some(fragment_fn.as_ref()));
|
descriptor.set_fragment_function(Some(fragment_fn.as_ref()));
|
||||||
|
descriptor.set_sample_count(sample_count);
|
||||||
let color_attachment = descriptor.color_attachments().object_at(0).unwrap();
|
let color_attachment = descriptor.color_attachments().object_at(0).unwrap();
|
||||||
color_attachment.set_pixel_format(pixel_format);
|
color_attachment.set_pixel_format(pixel_format);
|
||||||
color_attachment.set_blending_enabled(true);
|
color_attachment.set_blending_enabled(true);
|
||||||
|
@ -1170,50 +1082,45 @@ fn build_pipeline_state(
|
||||||
.expect("could not create render pipeline state")
|
.expect("could not create render pipeline state")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_path_rasterization_pipeline_state(
|
|
||||||
device: &metal::DeviceRef,
|
|
||||||
library: &metal::LibraryRef,
|
|
||||||
label: &str,
|
|
||||||
vertex_fn_name: &str,
|
|
||||||
fragment_fn_name: &str,
|
|
||||||
pixel_format: metal::MTLPixelFormat,
|
|
||||||
path_sample_count: u32,
|
|
||||||
) -> metal::RenderPipelineState {
|
|
||||||
let vertex_fn = library
|
|
||||||
.get_function(vertex_fn_name, None)
|
|
||||||
.expect("error locating vertex function");
|
|
||||||
let fragment_fn = library
|
|
||||||
.get_function(fragment_fn_name, None)
|
|
||||||
.expect("error locating fragment function");
|
|
||||||
|
|
||||||
let descriptor = metal::RenderPipelineDescriptor::new();
|
|
||||||
descriptor.set_label(label);
|
|
||||||
descriptor.set_vertex_function(Some(vertex_fn.as_ref()));
|
|
||||||
descriptor.set_fragment_function(Some(fragment_fn.as_ref()));
|
|
||||||
if path_sample_count > 1 {
|
|
||||||
descriptor.set_raster_sample_count(path_sample_count as _);
|
|
||||||
descriptor.set_alpha_to_coverage_enabled(true);
|
|
||||||
}
|
|
||||||
let color_attachment = descriptor.color_attachments().object_at(0).unwrap();
|
|
||||||
color_attachment.set_pixel_format(pixel_format);
|
|
||||||
color_attachment.set_blending_enabled(true);
|
|
||||||
color_attachment.set_rgb_blend_operation(metal::MTLBlendOperation::Add);
|
|
||||||
color_attachment.set_alpha_blend_operation(metal::MTLBlendOperation::Add);
|
|
||||||
color_attachment.set_source_rgb_blend_factor(metal::MTLBlendFactor::One);
|
|
||||||
color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One);
|
|
||||||
color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::One);
|
|
||||||
color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::One);
|
|
||||||
|
|
||||||
device
|
|
||||||
.new_render_pipeline_state(&descriptor)
|
|
||||||
.expect("could not create render pipeline state")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Align to multiples of 256 make Metal happy.
|
// Align to multiples of 256 make Metal happy.
|
||||||
fn align_offset(offset: &mut usize) {
|
fn align_offset(offset: &mut usize) {
|
||||||
*offset = (*offset).div_ceil(256) * 256;
|
*offset = (*offset).div_ceil(256) * 256;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_msaa_texture(
|
||||||
|
device: &metal::Device,
|
||||||
|
layer: &metal::MetalLayer,
|
||||||
|
sample_count: u64,
|
||||||
|
) -> Option<metal::Texture> {
|
||||||
|
let viewport_size = layer.drawable_size();
|
||||||
|
let width = viewport_size.width.ceil() as u64;
|
||||||
|
let height = viewport_size.height.ceil() as u64;
|
||||||
|
|
||||||
|
if width == 0 || height == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if sample_count <= 1 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let texture_descriptor = metal::TextureDescriptor::new();
|
||||||
|
texture_descriptor.set_texture_type(metal::MTLTextureType::D2Multisample);
|
||||||
|
|
||||||
|
// MTLStorageMode default is `shared` only for Apple silicon GPUs. Use `private` for Apple and Intel GPUs both.
|
||||||
|
// Reference: https://developer.apple.com/documentation/metal/choosing-a-resource-storage-mode-for-apple-gpus
|
||||||
|
texture_descriptor.set_storage_mode(metal::MTLStorageMode::Private);
|
||||||
|
|
||||||
|
texture_descriptor.set_width(width);
|
||||||
|
texture_descriptor.set_height(height);
|
||||||
|
texture_descriptor.set_pixel_format(layer.pixel_format());
|
||||||
|
texture_descriptor.set_usage(metal::MTLTextureUsage::RenderTarget);
|
||||||
|
texture_descriptor.set_sample_count(sample_count);
|
||||||
|
|
||||||
|
let metal_texture = device.new_texture(&texture_descriptor);
|
||||||
|
Some(metal_texture)
|
||||||
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
enum ShadowInputIndex {
|
enum ShadowInputIndex {
|
||||||
Vertices = 0,
|
Vertices = 0,
|
||||||
|
@ -1255,9 +1162,10 @@ enum SurfaceInputIndex {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
enum PathRasterizationInputIndex {
|
enum PathInputIndex {
|
||||||
Vertices = 0,
|
Vertices = 0,
|
||||||
AtlasTextureSize = 1,
|
ViewportSize = 1,
|
||||||
|
Sprites = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
@ -1265,7 +1173,6 @@ enum PathRasterizationInputIndex {
|
||||||
pub struct PathSprite {
|
pub struct PathSprite {
|
||||||
pub bounds: Bounds<ScaledPixels>,
|
pub bounds: Bounds<ScaledPixels>,
|
||||||
pub color: Background,
|
pub color: Background,
|
||||||
pub tile: AtlasTile,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
|
|
@ -698,76 +698,27 @@ fragment float4 polychrome_sprite_fragment(
|
||||||
return color;
|
return color;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PathRasterizationVertexOutput {
|
struct PathVertexOutput {
|
||||||
float4 position [[position]];
|
float4 position [[position]];
|
||||||
float2 st_position;
|
|
||||||
float clip_rect_distance [[clip_distance]][4];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PathRasterizationFragmentInput {
|
|
||||||
float4 position [[position]];
|
|
||||||
float2 st_position;
|
|
||||||
};
|
|
||||||
|
|
||||||
vertex PathRasterizationVertexOutput path_rasterization_vertex(
|
|
||||||
uint vertex_id [[vertex_id]],
|
|
||||||
constant PathVertex_ScaledPixels *vertices
|
|
||||||
[[buffer(PathRasterizationInputIndex_Vertices)]],
|
|
||||||
constant Size_DevicePixels *atlas_size
|
|
||||||
[[buffer(PathRasterizationInputIndex_AtlasTextureSize)]]) {
|
|
||||||
PathVertex_ScaledPixels v = vertices[vertex_id];
|
|
||||||
float2 vertex_position = float2(v.xy_position.x, v.xy_position.y);
|
|
||||||
float2 viewport_size = float2(atlas_size->width, atlas_size->height);
|
|
||||||
return PathRasterizationVertexOutput{
|
|
||||||
float4(vertex_position / viewport_size * float2(2., -2.) +
|
|
||||||
float2(-1., 1.),
|
|
||||||
0., 1.),
|
|
||||||
float2(v.st_position.x, v.st_position.y),
|
|
||||||
{v.xy_position.x - v.content_mask.bounds.origin.x,
|
|
||||||
v.content_mask.bounds.origin.x + v.content_mask.bounds.size.width -
|
|
||||||
v.xy_position.x,
|
|
||||||
v.xy_position.y - v.content_mask.bounds.origin.y,
|
|
||||||
v.content_mask.bounds.origin.y + v.content_mask.bounds.size.height -
|
|
||||||
v.xy_position.y}};
|
|
||||||
}
|
|
||||||
|
|
||||||
fragment float4 path_rasterization_fragment(PathRasterizationFragmentInput input
|
|
||||||
[[stage_in]]) {
|
|
||||||
float2 dx = dfdx(input.st_position);
|
|
||||||
float2 dy = dfdy(input.st_position);
|
|
||||||
float2 gradient = float2((2. * input.st_position.x) * dx.x - dx.y,
|
|
||||||
(2. * input.st_position.x) * dy.x - dy.y);
|
|
||||||
float f = (input.st_position.x * input.st_position.x) - input.st_position.y;
|
|
||||||
float distance = f / length(gradient);
|
|
||||||
float alpha = saturate(0.5 - distance);
|
|
||||||
return float4(alpha, 0., 0., 1.);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct PathSpriteVertexOutput {
|
|
||||||
float4 position [[position]];
|
|
||||||
float2 tile_position;
|
|
||||||
uint sprite_id [[flat]];
|
uint sprite_id [[flat]];
|
||||||
float4 solid_color [[flat]];
|
float4 solid_color [[flat]];
|
||||||
float4 color0 [[flat]];
|
float4 color0 [[flat]];
|
||||||
float4 color1 [[flat]];
|
float4 color1 [[flat]];
|
||||||
|
float4 clip_distance;
|
||||||
};
|
};
|
||||||
|
|
||||||
vertex PathSpriteVertexOutput path_sprite_vertex(
|
vertex PathVertexOutput path_vertex(
|
||||||
uint unit_vertex_id [[vertex_id]], uint sprite_id [[instance_id]],
|
uint vertex_id [[vertex_id]],
|
||||||
constant float2 *unit_vertices [[buffer(SpriteInputIndex_Vertices)]],
|
constant PathVertex_ScaledPixels *vertices [[buffer(PathInputIndex_Vertices)]],
|
||||||
constant PathSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
|
uint sprite_id [[instance_id]],
|
||||||
constant Size_DevicePixels *viewport_size
|
constant PathSprite *sprites [[buffer(PathInputIndex_Sprites)]],
|
||||||
[[buffer(SpriteInputIndex_ViewportSize)]],
|
constant Size_DevicePixels *input_viewport_size [[buffer(PathInputIndex_ViewportSize)]]) {
|
||||||
constant Size_DevicePixels *atlas_size
|
PathVertex_ScaledPixels v = vertices[vertex_id];
|
||||||
[[buffer(SpriteInputIndex_AtlasTextureSize)]]) {
|
float2 vertex_position = float2(v.xy_position.x, v.xy_position.y);
|
||||||
|
float2 viewport_size = float2((float)input_viewport_size->width,
|
||||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
(float)input_viewport_size->height);
|
||||||
PathSprite sprite = sprites[sprite_id];
|
PathSprite sprite = sprites[sprite_id];
|
||||||
// Don't apply content mask because it was already accounted for when
|
float4 device_position = float4(vertex_position / viewport_size * float2(2., -2.) + float2(-1., 1.), 0., 1.);
|
||||||
// rasterizing the path.
|
|
||||||
float4 device_position =
|
|
||||||
to_device_position(unit_vertex, sprite.bounds, viewport_size);
|
|
||||||
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
|
|
||||||
|
|
||||||
GradientColor gradient = prepare_fill_color(
|
GradientColor gradient = prepare_fill_color(
|
||||||
sprite.color.tag,
|
sprite.color.tag,
|
||||||
|
@ -777,30 +728,32 @@ vertex PathSpriteVertexOutput path_sprite_vertex(
|
||||||
sprite.color.colors[1].color
|
sprite.color.colors[1].color
|
||||||
);
|
);
|
||||||
|
|
||||||
return PathSpriteVertexOutput{
|
return PathVertexOutput{
|
||||||
device_position,
|
device_position,
|
||||||
tile_position,
|
|
||||||
sprite_id,
|
sprite_id,
|
||||||
gradient.solid,
|
gradient.solid,
|
||||||
gradient.color0,
|
gradient.color0,
|
||||||
gradient.color1
|
gradient.color1,
|
||||||
|
{v.xy_position.x - v.content_mask.bounds.origin.x,
|
||||||
|
v.content_mask.bounds.origin.x + v.content_mask.bounds.size.width -
|
||||||
|
v.xy_position.x,
|
||||||
|
v.xy_position.y - v.content_mask.bounds.origin.y,
|
||||||
|
v.content_mask.bounds.origin.y + v.content_mask.bounds.size.height -
|
||||||
|
v.xy_position.y}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fragment float4 path_sprite_fragment(
|
fragment float4 path_fragment(
|
||||||
PathSpriteVertexOutput input [[stage_in]],
|
PathVertexOutput input [[stage_in]],
|
||||||
constant PathSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
|
constant PathSprite *sprites [[buffer(PathInputIndex_Sprites)]]) {
|
||||||
texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
|
if (any(input.clip_distance < float4(0.0))) {
|
||||||
constexpr sampler atlas_texture_sampler(mag_filter::linear,
|
return float4(0.0);
|
||||||
min_filter::linear);
|
}
|
||||||
float4 sample =
|
|
||||||
atlas_texture.sample(atlas_texture_sampler, input.tile_position);
|
|
||||||
float mask = 1. - abs(1. - fmod(sample.r, 2.));
|
|
||||||
PathSprite sprite = sprites[input.sprite_id];
|
PathSprite sprite = sprites[input.sprite_id];
|
||||||
Background background = sprite.color;
|
Background background = sprite.color;
|
||||||
float4 color = fill_color(background, input.position.xy, sprite.bounds,
|
float4 color = fill_color(background, input.position.xy, sprite.bounds,
|
||||||
input.solid_color, input.color0, input.color1);
|
input.solid_color, input.color0, input.color1);
|
||||||
color.a *= mask;
|
|
||||||
return color;
|
return color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -341,7 +341,7 @@ impl PlatformAtlas for TestAtlas {
|
||||||
crate::AtlasTile {
|
crate::AtlasTile {
|
||||||
texture_id: AtlasTextureId {
|
texture_id: AtlasTextureId {
|
||||||
index: texture_id,
|
index: texture_id,
|
||||||
kind: crate::AtlasTextureKind::Path,
|
kind: crate::AtlasTextureKind::Polychrome,
|
||||||
},
|
},
|
||||||
tile_id: TileId(tile_id),
|
tile_id: TileId(tile_id),
|
||||||
padding: 0,
|
padding: 0,
|
||||||
|
|
|
@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
AtlasTextureId, AtlasTile, Background, Bounds, ContentMask, Corners, Edges, Hsla, Pixels,
|
AtlasTextureId, AtlasTile, Background, Bounds, ContentMask, Corners, Edges, Hsla, Pixels,
|
||||||
Point, Radians, ScaledPixels, Size, bounds_tree::BoundsTree, point,
|
Point, Radians, ScaledPixels, Size, bounds_tree::BoundsTree,
|
||||||
};
|
};
|
||||||
use std::{fmt::Debug, iter::Peekable, ops::Range, slice};
|
use std::{fmt::Debug, iter::Peekable, ops::Range, slice};
|
||||||
|
|
||||||
|
@ -43,13 +43,7 @@ impl Scene {
|
||||||
self.surfaces.clear();
|
self.surfaces.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(
|
#[allow(dead_code)]
|
||||||
all(
|
|
||||||
any(target_os = "linux", target_os = "freebsd"),
|
|
||||||
not(any(feature = "x11", feature = "wayland"))
|
|
||||||
),
|
|
||||||
allow(dead_code)
|
|
||||||
)]
|
|
||||||
pub fn paths(&self) -> &[Path<ScaledPixels>] {
|
pub fn paths(&self) -> &[Path<ScaledPixels>] {
|
||||||
&self.paths
|
&self.paths
|
||||||
}
|
}
|
||||||
|
@ -689,6 +683,7 @@ pub struct Path<P: Clone + Debug + Default + PartialEq> {
|
||||||
start: Point<P>,
|
start: Point<P>,
|
||||||
current: Point<P>,
|
current: Point<P>,
|
||||||
contour_count: usize,
|
contour_count: usize,
|
||||||
|
base_scale: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Path<Pixels> {
|
impl Path<Pixels> {
|
||||||
|
@ -707,25 +702,35 @@ impl Path<Pixels> {
|
||||||
content_mask: Default::default(),
|
content_mask: Default::default(),
|
||||||
color: Default::default(),
|
color: Default::default(),
|
||||||
contour_count: 0,
|
contour_count: 0,
|
||||||
|
base_scale: 1.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Scale this path by the given factor.
|
/// Set the base scale of the path.
|
||||||
pub fn scale(&self, factor: f32) -> Path<ScaledPixels> {
|
pub fn scale(mut self, factor: f32) -> Self {
|
||||||
|
self.base_scale = factor;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply a scale to the path.
|
||||||
|
pub(crate) fn apply_scale(&self, factor: f32) -> Path<ScaledPixels> {
|
||||||
Path {
|
Path {
|
||||||
id: self.id,
|
id: self.id,
|
||||||
order: self.order,
|
order: self.order,
|
||||||
bounds: self.bounds.scale(factor),
|
bounds: self.bounds.scale(self.base_scale * factor),
|
||||||
content_mask: self.content_mask.scale(factor),
|
content_mask: self.content_mask.scale(self.base_scale * factor),
|
||||||
vertices: self
|
vertices: self
|
||||||
.vertices
|
.vertices
|
||||||
.iter()
|
.iter()
|
||||||
.map(|vertex| vertex.scale(factor))
|
.map(|vertex| vertex.scale(self.base_scale * factor))
|
||||||
.collect(),
|
.collect(),
|
||||||
start: self.start.map(|start| start.scale(factor)),
|
start: self
|
||||||
current: self.current.scale(factor),
|
.start
|
||||||
|
.map(|start| start.scale(self.base_scale * factor)),
|
||||||
|
current: self.current.scale(self.base_scale * factor),
|
||||||
contour_count: self.contour_count,
|
contour_count: self.contour_count,
|
||||||
color: self.color,
|
color: self.color,
|
||||||
|
base_scale: 1.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -740,10 +745,7 @@ impl Path<Pixels> {
|
||||||
pub fn line_to(&mut self, to: Point<Pixels>) {
|
pub fn line_to(&mut self, to: Point<Pixels>) {
|
||||||
self.contour_count += 1;
|
self.contour_count += 1;
|
||||||
if self.contour_count > 1 {
|
if self.contour_count > 1 {
|
||||||
self.push_triangle(
|
self.push_triangle((self.start, self.current, to));
|
||||||
(self.start, self.current, to),
|
|
||||||
(point(0., 1.), point(0., 1.), point(0., 1.)),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
self.current = to;
|
self.current = to;
|
||||||
}
|
}
|
||||||
|
@ -752,25 +754,15 @@ impl Path<Pixels> {
|
||||||
pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) {
|
pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) {
|
||||||
self.contour_count += 1;
|
self.contour_count += 1;
|
||||||
if self.contour_count > 1 {
|
if self.contour_count > 1 {
|
||||||
self.push_triangle(
|
self.push_triangle((self.start, self.current, to));
|
||||||
(self.start, self.current, to),
|
|
||||||
(point(0., 1.), point(0., 1.), point(0., 1.)),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.push_triangle(
|
self.push_triangle((self.current, ctrl, to));
|
||||||
(self.current, ctrl, to),
|
|
||||||
(point(0., 0.), point(0.5, 0.), point(1., 1.)),
|
|
||||||
);
|
|
||||||
self.current = to;
|
self.current = to;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Push a triangle to the Path.
|
/// Push a triangle to the Path.
|
||||||
pub fn push_triangle(
|
pub fn push_triangle(&mut self, xy: (Point<Pixels>, Point<Pixels>, Point<Pixels>)) {
|
||||||
&mut self,
|
|
||||||
xy: (Point<Pixels>, Point<Pixels>, Point<Pixels>),
|
|
||||||
st: (Point<f32>, Point<f32>, Point<f32>),
|
|
||||||
) {
|
|
||||||
self.bounds = self
|
self.bounds = self
|
||||||
.bounds
|
.bounds
|
||||||
.union(&Bounds {
|
.union(&Bounds {
|
||||||
|
@ -788,17 +780,14 @@ impl Path<Pixels> {
|
||||||
|
|
||||||
self.vertices.push(PathVertex {
|
self.vertices.push(PathVertex {
|
||||||
xy_position: xy.0,
|
xy_position: xy.0,
|
||||||
st_position: st.0,
|
|
||||||
content_mask: Default::default(),
|
content_mask: Default::default(),
|
||||||
});
|
});
|
||||||
self.vertices.push(PathVertex {
|
self.vertices.push(PathVertex {
|
||||||
xy_position: xy.1,
|
xy_position: xy.1,
|
||||||
st_position: st.1,
|
|
||||||
content_mask: Default::default(),
|
content_mask: Default::default(),
|
||||||
});
|
});
|
||||||
self.vertices.push(PathVertex {
|
self.vertices.push(PathVertex {
|
||||||
xy_position: xy.2,
|
xy_position: xy.2,
|
||||||
st_position: st.2,
|
|
||||||
content_mask: Default::default(),
|
content_mask: Default::default(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -814,7 +803,6 @@ impl From<Path<ScaledPixels>> for Primitive {
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub(crate) struct PathVertex<P: Clone + Debug + Default + PartialEq> {
|
pub(crate) struct PathVertex<P: Clone + Debug + Default + PartialEq> {
|
||||||
pub(crate) xy_position: Point<P>,
|
pub(crate) xy_position: Point<P>,
|
||||||
pub(crate) st_position: Point<f32>,
|
|
||||||
pub(crate) content_mask: ContentMask<P>,
|
pub(crate) content_mask: ContentMask<P>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -822,7 +810,6 @@ impl PathVertex<Pixels> {
|
||||||
pub fn scale(&self, factor: f32) -> PathVertex<ScaledPixels> {
|
pub fn scale(&self, factor: f32) -> PathVertex<ScaledPixels> {
|
||||||
PathVertex {
|
PathVertex {
|
||||||
xy_position: self.xy_position.scale(factor),
|
xy_position: self.xy_position.scale(factor),
|
||||||
st_position: self.st_position,
|
|
||||||
content_mask: self.content_mask.scale(factor),
|
content_mask: self.content_mask.scale(factor),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2633,7 +2633,7 @@ impl Window {
|
||||||
path.color = color.opacity(opacity);
|
path.color = color.opacity(opacity);
|
||||||
self.next_frame
|
self.next_frame
|
||||||
.scene
|
.scene
|
||||||
.insert_primitive(path.scale(scale_factor));
|
.insert_primitive(path.apply_scale(scale_factor));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Paint an underline into the scene for the next frame at the current z-index.
|
/// Paint an underline into the scene for the next frame at the current z-index.
|
||||||
|
|
|
@ -148,7 +148,7 @@ On some systems the file `/etc/prime-discrete` can be used to enforce the use of
|
||||||
|
|
||||||
On others, you may be able to the environment variable `DRI_PRIME=1` when running Zed to force the use of the discrete GPU.
|
On others, you may be able to the environment variable `DRI_PRIME=1` when running Zed to force the use of the discrete GPU.
|
||||||
|
|
||||||
If you're using an AMD GPU and Zed crashes when selecting long lines, try setting the `ZED_PATH_SAMPLE_COUNT=0` environment variable. (See [#26143](https://github.com/zed-industries/zed/issues/26143))
|
If you're using an AMD GPU and Zed crashes when selecting long lines, try setting the `ZED_SAMPLE_COUNT=0` environment variable. (See [#26143](https://github.com/zed-industries/zed/issues/26143))
|
||||||
|
|
||||||
If you're using an AMD GPU, you might get a 'Broken Pipe' error. Try using the RADV or Mesa drivers. (See [#13880](https://github.com/zed-industries/zed/issues/13880))
|
If you're using an AMD GPU, you might get a 'Broken Pipe' error. Try using the RADV or Mesa drivers. (See [#13880](https://github.com/zed-industries/zed/issues/13880))
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue