Checkpoint: beziers
This commit is contained in:
parent
fe60f264c4
commit
a4afb72535
6 changed files with 397 additions and 97 deletions
|
@ -50,6 +50,8 @@ fn generate_shader_bindings() -> PathBuf {
|
||||||
"ContentMask".into(),
|
"ContentMask".into(),
|
||||||
"Uniforms".into(),
|
"Uniforms".into(),
|
||||||
"AtlasTile".into(),
|
"AtlasTile".into(),
|
||||||
|
"PathRasterizationInputIndex".into(),
|
||||||
|
"PathVertex_ScaledPixels".into(),
|
||||||
"ShadowInputIndex".into(),
|
"ShadowInputIndex".into(),
|
||||||
"Shadow".into(),
|
"Shadow".into(),
|
||||||
"QuadInputIndex".into(),
|
"QuadInputIndex".into(),
|
||||||
|
@ -59,6 +61,7 @@ fn generate_shader_bindings() -> PathBuf {
|
||||||
"SpriteInputIndex".into(),
|
"SpriteInputIndex".into(),
|
||||||
"MonochromeSprite".into(),
|
"MonochromeSprite".into(),
|
||||||
"PolychromeSprite".into(),
|
"PolychromeSprite".into(),
|
||||||
|
"PathSprite".into(),
|
||||||
]);
|
]);
|
||||||
config.no_includes = true;
|
config.no_includes = true;
|
||||||
config.enumeration.prefix_with_name = true;
|
config.enumeration.prefix_with_name = true;
|
||||||
|
|
|
@ -99,12 +99,24 @@ impl MetalAtlasState {
|
||||||
let texture_descriptor = metal::TextureDescriptor::new();
|
let texture_descriptor = metal::TextureDescriptor::new();
|
||||||
texture_descriptor.set_width(size.width.into());
|
texture_descriptor.set_width(size.width.into());
|
||||||
texture_descriptor.set_height(size.height.into());
|
texture_descriptor.set_height(size.height.into());
|
||||||
let pixel_format = match kind {
|
let pixel_format;
|
||||||
AtlasTextureKind::Monochrome => metal::MTLPixelFormat::A8Unorm,
|
let usage;
|
||||||
AtlasTextureKind::Polychrome => metal::MTLPixelFormat::BGRA8Unorm,
|
match kind {
|
||||||
AtlasTextureKind::Path => metal::MTLPixelFormat::R16Float,
|
AtlasTextureKind::Monochrome => {
|
||||||
};
|
pixel_format = metal::MTLPixelFormat::A8Unorm;
|
||||||
|
usage = metal::MTLTextureUsage::ShaderRead;
|
||||||
|
}
|
||||||
|
AtlasTextureKind::Polychrome => {
|
||||||
|
pixel_format = metal::MTLPixelFormat::BGRA8Unorm;
|
||||||
|
usage = metal::MTLTextureUsage::ShaderRead;
|
||||||
|
}
|
||||||
|
AtlasTextureKind::Path => {
|
||||||
|
pixel_format = metal::MTLPixelFormat::R16Float;
|
||||||
|
usage = metal::MTLTextureUsage::RenderTarget | metal::MTLTextureUsage::ShaderRead;
|
||||||
|
}
|
||||||
|
}
|
||||||
texture_descriptor.set_pixel_format(pixel_format);
|
texture_descriptor.set_pixel_format(pixel_format);
|
||||||
|
texture_descriptor.set_usage(usage);
|
||||||
let metal_texture = self.device.new_texture(&texture_descriptor);
|
let metal_texture = self.device.new_texture(&texture_descriptor);
|
||||||
|
|
||||||
let textures = match kind {
|
let textures = match kind {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
point, size, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, ContentMask, DevicePixels,
|
point, size, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, ContentMask, DevicePixels,
|
||||||
MetalAtlas, MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch, Quad,
|
Hsla, MetalAtlas, MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch,
|
||||||
ScaledPixels, Scene, Shadow, Size, Underline,
|
Quad, ScaledPixels, Scene, Shadow, Size, Underline,
|
||||||
};
|
};
|
||||||
use cocoa::{
|
use cocoa::{
|
||||||
base::{NO, YES},
|
base::{NO, YES},
|
||||||
|
@ -11,6 +11,7 @@ use cocoa::{
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use metal::{CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};
|
use metal::{CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};
|
||||||
use objc::{self, msg_send, sel, sel_impl};
|
use objc::{self, msg_send, sel, sel_impl};
|
||||||
|
use smallvec::SmallVec;
|
||||||
use std::{ffi::c_void, mem, ptr, sync::Arc};
|
use std::{ffi::c_void, mem, ptr, sync::Arc};
|
||||||
|
|
||||||
const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
|
const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
|
||||||
|
@ -19,6 +20,8 @@ const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decisio
|
||||||
pub(crate) struct MetalRenderer {
|
pub(crate) struct MetalRenderer {
|
||||||
layer: metal::MetalLayer,
|
layer: metal::MetalLayer,
|
||||||
command_queue: CommandQueue,
|
command_queue: CommandQueue,
|
||||||
|
paths_rasterization_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,
|
||||||
|
@ -31,8 +34,6 @@ pub(crate) struct MetalRenderer {
|
||||||
|
|
||||||
impl MetalRenderer {
|
impl MetalRenderer {
|
||||||
pub fn new(is_opaque: bool) -> Self {
|
pub fn new(is_opaque: bool) -> Self {
|
||||||
const PIXEL_FORMAT: MTLPixelFormat = MTLPixelFormat::BGRA8Unorm;
|
|
||||||
|
|
||||||
let device: metal::Device = if let Some(device) = metal::Device::system_default() {
|
let device: metal::Device = if let Some(device) = metal::Device::system_default() {
|
||||||
device
|
device
|
||||||
} else {
|
} else {
|
||||||
|
@ -42,7 +43,7 @@ impl MetalRenderer {
|
||||||
|
|
||||||
let layer = metal::MetalLayer::new();
|
let layer = metal::MetalLayer::new();
|
||||||
layer.set_device(&device);
|
layer.set_device(&device);
|
||||||
layer.set_pixel_format(PIXEL_FORMAT);
|
layer.set_pixel_format(MTLPixelFormat::BGRA8Unorm);
|
||||||
layer.set_presents_with_transaction(true);
|
layer.set_presents_with_transaction(true);
|
||||||
layer.set_opaque(is_opaque);
|
layer.set_opaque(is_opaque);
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -86,13 +87,29 @@ impl MetalRenderer {
|
||||||
MTLResourceOptions::StorageModeManaged,
|
MTLResourceOptions::StorageModeManaged,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let paths_rasterization_pipeline_state = build_pipeline_state(
|
||||||
|
&device,
|
||||||
|
&library,
|
||||||
|
"paths_rasterization",
|
||||||
|
"path_rasterization_vertex",
|
||||||
|
"path_rasterization_fragment",
|
||||||
|
MTLPixelFormat::R16Float,
|
||||||
|
);
|
||||||
|
let path_sprites_pipeline_state = build_pipeline_state(
|
||||||
|
&device,
|
||||||
|
&library,
|
||||||
|
"path_sprites",
|
||||||
|
"path_sprite_vertex",
|
||||||
|
"path_sprite_fragment",
|
||||||
|
MTLPixelFormat::BGRA8Unorm,
|
||||||
|
);
|
||||||
let shadows_pipeline_state = build_pipeline_state(
|
let shadows_pipeline_state = build_pipeline_state(
|
||||||
&device,
|
&device,
|
||||||
&library,
|
&library,
|
||||||
"shadows",
|
"shadows",
|
||||||
"shadow_vertex",
|
"shadow_vertex",
|
||||||
"shadow_fragment",
|
"shadow_fragment",
|
||||||
PIXEL_FORMAT,
|
MTLPixelFormat::BGRA8Unorm,
|
||||||
);
|
);
|
||||||
let quads_pipeline_state = build_pipeline_state(
|
let quads_pipeline_state = build_pipeline_state(
|
||||||
&device,
|
&device,
|
||||||
|
@ -100,7 +117,7 @@ impl MetalRenderer {
|
||||||
"quads",
|
"quads",
|
||||||
"quad_vertex",
|
"quad_vertex",
|
||||||
"quad_fragment",
|
"quad_fragment",
|
||||||
PIXEL_FORMAT,
|
MTLPixelFormat::BGRA8Unorm,
|
||||||
);
|
);
|
||||||
let underlines_pipeline_state = build_pipeline_state(
|
let underlines_pipeline_state = build_pipeline_state(
|
||||||
&device,
|
&device,
|
||||||
|
@ -108,7 +125,7 @@ impl MetalRenderer {
|
||||||
"underlines",
|
"underlines",
|
||||||
"underline_vertex",
|
"underline_vertex",
|
||||||
"underline_fragment",
|
"underline_fragment",
|
||||||
PIXEL_FORMAT,
|
MTLPixelFormat::BGRA8Unorm,
|
||||||
);
|
);
|
||||||
let monochrome_sprites_pipeline_state = build_pipeline_state(
|
let monochrome_sprites_pipeline_state = build_pipeline_state(
|
||||||
&device,
|
&device,
|
||||||
|
@ -116,7 +133,7 @@ impl MetalRenderer {
|
||||||
"monochrome_sprites",
|
"monochrome_sprites",
|
||||||
"monochrome_sprite_vertex",
|
"monochrome_sprite_vertex",
|
||||||
"monochrome_sprite_fragment",
|
"monochrome_sprite_fragment",
|
||||||
PIXEL_FORMAT,
|
MTLPixelFormat::BGRA8Unorm,
|
||||||
);
|
);
|
||||||
let polychrome_sprites_pipeline_state = build_pipeline_state(
|
let polychrome_sprites_pipeline_state = build_pipeline_state(
|
||||||
&device,
|
&device,
|
||||||
|
@ -124,7 +141,7 @@ impl MetalRenderer {
|
||||||
"polychrome_sprites",
|
"polychrome_sprites",
|
||||||
"polychrome_sprite_vertex",
|
"polychrome_sprite_vertex",
|
||||||
"polychrome_sprite_fragment",
|
"polychrome_sprite_fragment",
|
||||||
PIXEL_FORMAT,
|
MTLPixelFormat::BGRA8Unorm,
|
||||||
);
|
);
|
||||||
|
|
||||||
let command_queue = device.new_command_queue();
|
let command_queue = device.new_command_queue();
|
||||||
|
@ -133,6 +150,8 @@ impl MetalRenderer {
|
||||||
Self {
|
Self {
|
||||||
layer,
|
layer,
|
||||||
command_queue,
|
command_queue,
|
||||||
|
paths_rasterization_pipeline_state,
|
||||||
|
path_sprites_pipeline_state,
|
||||||
shadows_pipeline_state,
|
shadows_pipeline_state,
|
||||||
quads_pipeline_state,
|
quads_pipeline_state,
|
||||||
underlines_pipeline_state,
|
underlines_pipeline_state,
|
||||||
|
@ -172,7 +191,7 @@ impl MetalRenderer {
|
||||||
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(), &mut instance_offset, &command_buffer);
|
let path_tiles = self.rasterize_paths(scene.paths(), &mut instance_offset, &command_buffer);
|
||||||
|
|
||||||
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
|
||||||
|
@ -208,8 +227,14 @@ impl MetalRenderer {
|
||||||
PrimitiveBatch::Quads(quads) => {
|
PrimitiveBatch::Quads(quads) => {
|
||||||
self.draw_quads(quads, &mut instance_offset, viewport_size, command_encoder);
|
self.draw_quads(quads, &mut instance_offset, viewport_size, command_encoder);
|
||||||
}
|
}
|
||||||
PrimitiveBatch::Paths(_paths) => {
|
PrimitiveBatch::Paths(paths) => {
|
||||||
// self.draw_paths(paths, &mut instance_offset, viewport_size, command_encoder);
|
self.draw_paths(
|
||||||
|
paths,
|
||||||
|
&path_tiles,
|
||||||
|
&mut instance_offset,
|
||||||
|
viewport_size,
|
||||||
|
command_encoder,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
PrimitiveBatch::Underlines(underlines) => {
|
PrimitiveBatch::Underlines(underlines) => {
|
||||||
self.draw_underlines(
|
self.draw_underlines(
|
||||||
|
@ -258,19 +283,20 @@ impl MetalRenderer {
|
||||||
drawable.present();
|
drawable.present();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn rasterize_paths(
|
fn rasterize_paths(
|
||||||
&mut self,
|
&mut self,
|
||||||
paths: &[Path<ScaledPixels>],
|
paths: &[Path<ScaledPixels>],
|
||||||
_offset: &mut usize,
|
offset: &mut usize,
|
||||||
_command_buffer: &metal::CommandBufferRef,
|
command_buffer: &metal::CommandBufferRef,
|
||||||
) -> HashMap<PathId, AtlasTile> {
|
) -> HashMap<PathId, AtlasTile> {
|
||||||
let mut tiles = HashMap::default();
|
let mut tiles = HashMap::default();
|
||||||
let mut vertices_by_texture_id = HashMap::default();
|
let mut vertices_by_texture_id = HashMap::default();
|
||||||
for path in paths {
|
for path in paths {
|
||||||
|
let clipped_bounds = path.bounds.intersect(&path.content_mask.bounds);
|
||||||
|
|
||||||
let tile = self
|
let tile = self
|
||||||
.sprite_atlas
|
.sprite_atlas
|
||||||
.allocate(path.bounds.size.map(Into::into), AtlasTextureKind::Path);
|
.allocate(clipped_bounds.size.map(Into::into), AtlasTextureKind::Path);
|
||||||
vertices_by_texture_id
|
vertices_by_texture_id
|
||||||
.entry(tile.texture_id)
|
.entry(tile.texture_id)
|
||||||
.or_insert(Vec::new())
|
.or_insert(Vec::new())
|
||||||
|
@ -279,68 +305,65 @@ impl MetalRenderer {
|
||||||
+ tile.bounds.origin.map(Into::into),
|
+ tile.bounds.origin.map(Into::into),
|
||||||
st_position: vertex.st_position,
|
st_position: vertex.st_position,
|
||||||
content_mask: ContentMask {
|
content_mask: ContentMask {
|
||||||
bounds: Bounds {
|
bounds: tile.bounds.map(Into::into),
|
||||||
origin: vertex.xy_position - path.bounds.origin
|
|
||||||
+ tile.bounds.origin.map(Into::into),
|
|
||||||
size: vertex.content_mask.bounds.size,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
tiles.insert(path.id, tile);
|
tiles.insert(path.id, tile);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (_texture_id, _vertices) in vertices_by_texture_id {
|
for (texture_id, vertices) in vertices_by_texture_id {
|
||||||
todo!();
|
align_offset(offset);
|
||||||
// align_offset(offset);
|
let next_offset = *offset + vertices.len() * mem::size_of::<PathVertex<ScaledPixels>>();
|
||||||
// let next_offset = *offset + vertices.len() * mem::size_of::<PathVertex<ScaledPixels>>();
|
assert!(
|
||||||
// assert!(
|
next_offset <= INSTANCE_BUFFER_SIZE,
|
||||||
// next_offset <= INSTANCE_BUFFER_SIZE,
|
"instance buffer exhausted"
|
||||||
// "instance buffer exhausted"
|
);
|
||||||
// );
|
|
||||||
|
|
||||||
// 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();
|
||||||
|
|
||||||
// let texture = self.sprite_atlas.metal_texture(texture_id);
|
let texture = self.sprite_atlas.metal_texture(texture_id);
|
||||||
// color_attachment.set_texture(Some(&texture));
|
color_attachment.set_texture(Some(&texture));
|
||||||
// color_attachment.set_load_action(metal::MTLLoadAction::Clear);
|
color_attachment.set_load_action(metal::MTLLoadAction::Clear);
|
||||||
// color_attachment.set_store_action(metal::MTLStoreAction::Store);
|
color_attachment.set_store_action(metal::MTLStoreAction::Store);
|
||||||
// color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., 1.));
|
color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., 1.));
|
||||||
// let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
|
let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
|
||||||
// command_encoder.set_render_pipeline_state(&self.path_atlas_pipeline_state);
|
command_encoder.set_render_pipeline_state(&self.paths_rasterization_pipeline_state);
|
||||||
// command_encoder.set_vertex_buffer(
|
command_encoder.set_vertex_buffer(
|
||||||
// shaders::GPUIPathAtlasVertexInputIndex_GPUIPathAtlasVertexInputIndexVertices as u64,
|
PathRasterizationInputIndex::Vertices as u64,
|
||||||
// Some(&self.instances),
|
Some(&self.instances),
|
||||||
// *offset as u64,
|
*offset as u64,
|
||||||
// );
|
);
|
||||||
// command_encoder.set_vertex_bytes(
|
let texture_size = Size {
|
||||||
// shaders::GPUIPathAtlasVertexInputIndex_GPUIPathAtlasVertexInputIndexAtlasSize
|
width: DevicePixels::from(texture.width()),
|
||||||
// as u64,
|
height: DevicePixels::from(texture.height()),
|
||||||
// mem::size_of::<shaders::vector_float2>() as u64,
|
};
|
||||||
// [vec2i(texture.width() as i32, texture.height() as i32).to_float2()].as_ptr()
|
command_encoder.set_vertex_bytes(
|
||||||
// as *const c_void,
|
PathRasterizationInputIndex::AtlasTextureSize as u64,
|
||||||
// );
|
mem::size_of_val(&texture_size) as u64,
|
||||||
|
&texture_size as *const Size<DevicePixels> as *const _,
|
||||||
|
);
|
||||||
|
|
||||||
// let buffer_contents = unsafe {
|
let vertices_bytes_len = mem::size_of::<PathVertex<ScaledPixels>>() * vertices.len();
|
||||||
// (self.instances.contents() as *mut u8).add(*offset) as *mut shaders::GPUIPathVertex
|
let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
|
||||||
// };
|
unsafe {
|
||||||
|
ptr::copy_nonoverlapping(
|
||||||
|
vertices.as_ptr() as *const u8,
|
||||||
|
buffer_contents,
|
||||||
|
vertices_bytes_len,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// for (ix, vertex) in vertices.iter().enumerate() {
|
command_encoder.draw_primitives(
|
||||||
// unsafe {
|
metal::MTLPrimitiveType::Triangle,
|
||||||
// *buffer_contents.add(ix) = *vertex;
|
0,
|
||||||
// }
|
vertices.len() as u64,
|
||||||
// }
|
);
|
||||||
|
command_encoder.end_encoding();
|
||||||
// command_encoder.draw_primitives(
|
*offset = next_offset;
|
||||||
// metal::MTLPrimitiveType::Triangle,
|
|
||||||
// 0,
|
|
||||||
// vertices.len() as u64,
|
|
||||||
// );
|
|
||||||
// command_encoder.end_encoding();
|
|
||||||
// *offset = next_offset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tiles
|
tiles
|
||||||
|
@ -462,6 +485,112 @@ impl MetalRenderer {
|
||||||
*offset = next_offset;
|
*offset = next_offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn draw_paths(
|
||||||
|
&mut self,
|
||||||
|
paths: &[Path<ScaledPixels>],
|
||||||
|
tiles_by_path_id: &HashMap<PathId, AtlasTile>,
|
||||||
|
offset: &mut usize,
|
||||||
|
viewport_size: Size<DevicePixels>,
|
||||||
|
command_encoder: &metal::RenderCommandEncoderRef,
|
||||||
|
) {
|
||||||
|
if paths.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
command_encoder.set_render_pipeline_state(&self.path_sprites_pipeline_state);
|
||||||
|
command_encoder.set_vertex_buffer(
|
||||||
|
SpriteInputIndex::Vertices as u64,
|
||||||
|
Some(&self.unit_vertices),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
command_encoder.set_vertex_bytes(
|
||||||
|
SpriteInputIndex::ViewportSize as u64,
|
||||||
|
mem::size_of_val(&viewport_size) as u64,
|
||||||
|
&viewport_size as *const Size<DevicePixels> as *const _,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut prev_texture_id = None;
|
||||||
|
let mut sprites = SmallVec::<[_; 1]>::new();
|
||||||
|
let mut paths_and_tiles = paths
|
||||||
|
.into_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);
|
||||||
|
sprites.push(PathSprite {
|
||||||
|
bounds: Bounds {
|
||||||
|
origin: path.bounds.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(offset);
|
||||||
|
let texture_id = prev_texture_id.take().unwrap();
|
||||||
|
let texture: metal::Texture = self.sprite_atlas.metal_texture(texture_id);
|
||||||
|
let texture_size = size(
|
||||||
|
DevicePixels(texture.width() as i32),
|
||||||
|
DevicePixels(texture.height() as i32),
|
||||||
|
);
|
||||||
|
|
||||||
|
command_encoder.set_vertex_buffer(
|
||||||
|
SpriteInputIndex::Sprites as u64,
|
||||||
|
Some(&self.instances),
|
||||||
|
*offset as u64,
|
||||||
|
);
|
||||||
|
command_encoder.set_vertex_bytes(
|
||||||
|
SpriteInputIndex::AtlasTextureSize as u64,
|
||||||
|
mem::size_of_val(&texture_size) as u64,
|
||||||
|
&texture_size as *const Size<DevicePixels> as *const _,
|
||||||
|
);
|
||||||
|
command_encoder.set_fragment_buffer(
|
||||||
|
SpriteInputIndex::Sprites as u64,
|
||||||
|
Some(&self.instances),
|
||||||
|
*offset as u64,
|
||||||
|
);
|
||||||
|
command_encoder
|
||||||
|
.set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture));
|
||||||
|
|
||||||
|
let sprite_bytes_len = mem::size_of::<MonochromeSprite>() * sprites.len();
|
||||||
|
let buffer_contents =
|
||||||
|
unsafe { (self.instances.contents() as *mut u8).add(*offset) };
|
||||||
|
unsafe {
|
||||||
|
ptr::copy_nonoverlapping(
|
||||||
|
sprites.as_ptr() as *const u8,
|
||||||
|
buffer_contents,
|
||||||
|
sprite_bytes_len,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let next_offset = *offset + sprite_bytes_len;
|
||||||
|
assert!(
|
||||||
|
next_offset <= INSTANCE_BUFFER_SIZE,
|
||||||
|
"instance buffer exhausted"
|
||||||
|
);
|
||||||
|
|
||||||
|
command_encoder.draw_primitives_instanced(
|
||||||
|
metal::MTLPrimitiveType::Triangle,
|
||||||
|
0,
|
||||||
|
6,
|
||||||
|
sprites.len() as u64,
|
||||||
|
);
|
||||||
|
*offset = next_offset;
|
||||||
|
sprites.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn draw_underlines(
|
fn draw_underlines(
|
||||||
&mut self,
|
&mut self,
|
||||||
underlines: &[Underline],
|
underlines: &[Underline],
|
||||||
|
@ -734,3 +863,17 @@ enum SpriteInputIndex {
|
||||||
AtlasTextureSize = 3,
|
AtlasTextureSize = 3,
|
||||||
AtlasTexture = 4,
|
AtlasTexture = 4,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
enum PathRasterizationInputIndex {
|
||||||
|
Vertices = 0,
|
||||||
|
AtlasTextureSize = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct PathSprite {
|
||||||
|
pub bounds: Bounds<ScaledPixels>,
|
||||||
|
pub color: Hsla,
|
||||||
|
pub tile: AtlasTile,
|
||||||
|
}
|
||||||
|
|
|
@ -336,6 +336,94 @@ fragment float4 polychrome_sprite_fragment(
|
||||||
return color;
|
return color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct PathRasterizationVertexOutput {
|
||||||
|
float4 position [[position]];
|
||||||
|
float2 st_position;
|
||||||
|
float clip_rect_distance [[clip_distance]][4];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PathRasterizationFragmentInput {
|
||||||
|
float4 position [[position]];
|
||||||
|
float2 st_position;
|
||||||
|
};
|
||||||
|
|
||||||
|
vertex PathRasterizationVertexOutput path_rasterization_vertex(
|
||||||
|
uint vertex_id [[vertex_id]],
|
||||||
|
constant PathVertex_ScaledPixels *vertices
|
||||||
|
[[buffer(PathRasterizationInputIndex_Vertices)]],
|
||||||
|
constant Size_DevicePixels *atlas_size
|
||||||
|
[[buffer(PathRasterizationInputIndex_AtlasTextureSize)]]) {
|
||||||
|
PathVertex_ScaledPixels v = vertices[vertex_id];
|
||||||
|
float2 vertex_position = float2(v.xy_position.x, v.xy_position.y);
|
||||||
|
float2 viewport_size = float2(atlas_size->width, atlas_size->height);
|
||||||
|
return PathRasterizationVertexOutput{
|
||||||
|
float4(vertex_position / viewport_size * float2(2., -2.) +
|
||||||
|
float2(-1., 1.),
|
||||||
|
0., 1.),
|
||||||
|
float2(v.st_position.x, v.st_position.y),
|
||||||
|
{v.xy_position.x - v.content_mask.bounds.origin.x,
|
||||||
|
v.content_mask.bounds.origin.x + v.content_mask.bounds.size.width -
|
||||||
|
v.xy_position.x,
|
||||||
|
v.xy_position.y - v.content_mask.bounds.origin.y,
|
||||||
|
v.content_mask.bounds.origin.y + v.content_mask.bounds.size.height -
|
||||||
|
v.xy_position.y}};
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment float4 path_rasterization_fragment(PathRasterizationFragmentInput input
|
||||||
|
[[stage_in]]) {
|
||||||
|
float2 dx = dfdx(input.st_position);
|
||||||
|
float2 dy = dfdy(input.st_position);
|
||||||
|
float2 gradient = float2((2. * input.st_position.x) * dx.x - dx.y,
|
||||||
|
(2. * input.st_position.x) * dy.x - dy.y);
|
||||||
|
float f = (input.st_position.x * input.st_position.x) - input.st_position.y;
|
||||||
|
float distance = f / length(gradient);
|
||||||
|
float alpha = saturate(0.5 - distance);
|
||||||
|
return float4(alpha, 0., 0., 1.);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PathSpriteVertexOutput {
|
||||||
|
float4 position [[position]];
|
||||||
|
float2 tile_position;
|
||||||
|
float4 color [[flat]];
|
||||||
|
uint sprite_id [[flat]];
|
||||||
|
};
|
||||||
|
|
||||||
|
vertex PathSpriteVertexOutput path_sprite_vertex(
|
||||||
|
uint unit_vertex_id [[vertex_id]], uint sprite_id [[instance_id]],
|
||||||
|
constant float2 *unit_vertices [[buffer(SpriteInputIndex_Vertices)]],
|
||||||
|
constant PathSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
|
||||||
|
constant Size_DevicePixels *viewport_size
|
||||||
|
[[buffer(SpriteInputIndex_ViewportSize)]],
|
||||||
|
constant Size_DevicePixels *atlas_size
|
||||||
|
[[buffer(SpriteInputIndex_AtlasTextureSize)]]) {
|
||||||
|
|
||||||
|
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||||
|
PathSprite sprite = sprites[sprite_id];
|
||||||
|
// Don't apply content mask because it was already accounted for when
|
||||||
|
// rasterizing the path.
|
||||||
|
float4 device_position = to_device_position(unit_vertex, sprite.bounds,
|
||||||
|
sprite.bounds, viewport_size);
|
||||||
|
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
|
||||||
|
float4 color = hsla_to_rgba(sprite.color);
|
||||||
|
return PathSpriteVertexOutput{device_position, tile_position, color,
|
||||||
|
sprite_id};
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment float4 path_sprite_fragment(
|
||||||
|
PathSpriteVertexOutput input [[stage_in]],
|
||||||
|
constant PathSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
|
||||||
|
texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
|
||||||
|
PathSprite sprite = sprites[input.sprite_id];
|
||||||
|
constexpr sampler atlas_texture_sampler(mag_filter::linear,
|
||||||
|
min_filter::linear);
|
||||||
|
float4 sample =
|
||||||
|
atlas_texture.sample(atlas_texture_sampler, input.tile_position);
|
||||||
|
float mask = 1. - abs(1. - fmod(sample.r, 2.));
|
||||||
|
float4 color = input.color;
|
||||||
|
color.a *= mask;
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
float4 hsla_to_rgba(Hsla hsla) {
|
float4 hsla_to_rgba(Hsla hsla) {
|
||||||
float h = hsla.h * 6.0; // Now, it's an angle but scaled in [0, 6) range
|
float h = hsla.h * 6.0; // Now, it's an angle but scaled in [0, 6) range
|
||||||
float s = hsla.s;
|
float s = hsla.s;
|
||||||
|
|
|
@ -8,7 +8,9 @@ use plane_split::{BspSplitter, Polygon as BspPolygon};
|
||||||
use std::{fmt::Debug, iter::Peekable, mem, slice};
|
use std::{fmt::Debug, iter::Peekable, mem, slice};
|
||||||
|
|
||||||
// Exported to metal
|
// Exported to metal
|
||||||
pub type PointF = Point<f32>;
|
pub(crate) type PointF = Point<f32>;
|
||||||
|
#[allow(non_camel_case_types, unused)]
|
||||||
|
pub(crate) type PathVertex_ScaledPixels = PathVertex<ScaledPixels>;
|
||||||
|
|
||||||
pub type LayerId = u32;
|
pub type LayerId = u32;
|
||||||
|
|
||||||
|
@ -62,6 +64,12 @@ impl SceneBuilder {
|
||||||
.add(quad.bounds.to_bsp_polygon(z, (PrimitiveKind::Quad, ix)));
|
.add(quad.bounds.to_bsp_polygon(z, (PrimitiveKind::Quad, ix)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (ix, path) in self.paths.iter().enumerate() {
|
||||||
|
let z = layer_z_values[path.order as LayerId as usize];
|
||||||
|
self.splitter
|
||||||
|
.add(path.bounds.to_bsp_polygon(z, (PrimitiveKind::Path, ix)));
|
||||||
|
}
|
||||||
|
|
||||||
for (ix, underline) in self.underlines.iter().enumerate() {
|
for (ix, underline) in self.underlines.iter().enumerate() {
|
||||||
let z = layer_z_values[underline.order as LayerId as usize];
|
let z = layer_z_values[underline.order as LayerId as usize];
|
||||||
self.splitter.add(
|
self.splitter.add(
|
||||||
|
@ -131,6 +139,16 @@ impl SceneBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert(&mut self, order: &StackingOrder, primitive: impl Into<Primitive>) {
|
pub fn insert(&mut self, order: &StackingOrder, primitive: impl Into<Primitive>) {
|
||||||
|
let primitive = primitive.into();
|
||||||
|
let clipped_bounds = primitive
|
||||||
|
.bounds()
|
||||||
|
.intersect(&primitive.content_mask().bounds);
|
||||||
|
if clipped_bounds.size.width <= ScaledPixels(0.)
|
||||||
|
|| clipped_bounds.size.height <= ScaledPixels(0.)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let layer_id = if let Some(layer_id) = self.layers_by_order.get(order) {
|
let layer_id = if let Some(layer_id) = self.layers_by_order.get(order) {
|
||||||
*layer_id
|
*layer_id
|
||||||
} else {
|
} else {
|
||||||
|
@ -139,7 +157,6 @@ impl SceneBuilder {
|
||||||
next_id
|
next_id
|
||||||
};
|
};
|
||||||
|
|
||||||
let primitive = primitive.into();
|
|
||||||
match primitive {
|
match primitive {
|
||||||
Primitive::Shadow(mut shadow) => {
|
Primitive::Shadow(mut shadow) => {
|
||||||
shadow.order = layer_id;
|
shadow.order = layer_id;
|
||||||
|
@ -240,6 +257,7 @@ impl<'a> Iterator for BatchIterator<'a> {
|
||||||
PrimitiveKind::Shadow,
|
PrimitiveKind::Shadow,
|
||||||
),
|
),
|
||||||
(self.quads_iter.peek().map(|q| q.order), PrimitiveKind::Quad),
|
(self.quads_iter.peek().map(|q| q.order), PrimitiveKind::Quad),
|
||||||
|
(self.paths_iter.peek().map(|q| q.order), PrimitiveKind::Path),
|
||||||
(
|
(
|
||||||
self.underlines_iter.peek().map(|u| u.order),
|
self.underlines_iter.peek().map(|u| u.order),
|
||||||
PrimitiveKind::Underline,
|
PrimitiveKind::Underline,
|
||||||
|
@ -382,6 +400,30 @@ pub enum Primitive {
|
||||||
PolychromeSprite(PolychromeSprite),
|
PolychromeSprite(PolychromeSprite),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Primitive {
|
||||||
|
pub fn bounds(&self) -> &Bounds<ScaledPixels> {
|
||||||
|
match self {
|
||||||
|
Primitive::Shadow(shadow) => &shadow.bounds,
|
||||||
|
Primitive::Quad(quad) => &quad.bounds,
|
||||||
|
Primitive::Path(path) => &path.bounds,
|
||||||
|
Primitive::Underline(underline) => &underline.bounds,
|
||||||
|
Primitive::MonochromeSprite(sprite) => &sprite.bounds,
|
||||||
|
Primitive::PolychromeSprite(sprite) => &sprite.bounds,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn content_mask(&self) -> &ContentMask<ScaledPixels> {
|
||||||
|
match self {
|
||||||
|
Primitive::Shadow(shadow) => &shadow.content_mask,
|
||||||
|
Primitive::Quad(quad) => &quad.content_mask,
|
||||||
|
Primitive::Path(path) => &path.content_mask,
|
||||||
|
Primitive::Underline(underline) => &underline.content_mask,
|
||||||
|
Primitive::MonochromeSprite(sprite) => &sprite.content_mask,
|
||||||
|
Primitive::PolychromeSprite(sprite) => &sprite.content_mask,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) enum PrimitiveBatch<'a> {
|
pub(crate) enum PrimitiveBatch<'a> {
|
||||||
Shadows(&'a [Shadow]),
|
Shadows(&'a [Shadow]),
|
||||||
|
@ -557,20 +599,29 @@ pub struct Path<P: Clone + Debug> {
|
||||||
pub(crate) id: PathId,
|
pub(crate) id: PathId,
|
||||||
order: u32,
|
order: u32,
|
||||||
pub(crate) bounds: Bounds<P>,
|
pub(crate) bounds: Bounds<P>,
|
||||||
|
pub(crate) content_mask: ContentMask<P>,
|
||||||
pub(crate) vertices: Vec<PathVertex<P>>,
|
pub(crate) vertices: Vec<PathVertex<P>>,
|
||||||
start: Option<Point<P>>,
|
pub(crate) color: Hsla,
|
||||||
|
start: Point<P>,
|
||||||
current: Point<P>,
|
current: Point<P>,
|
||||||
|
contour_count: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Path<Pixels> {
|
impl Path<Pixels> {
|
||||||
pub fn new() -> Self {
|
pub fn new(start: Point<Pixels>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: PathId(0),
|
id: PathId(0),
|
||||||
order: 0,
|
order: 0,
|
||||||
vertices: Vec::new(),
|
vertices: Vec::new(),
|
||||||
start: Default::default(),
|
start,
|
||||||
current: Default::default(),
|
current: start,
|
||||||
bounds: Default::default(),
|
bounds: Bounds {
|
||||||
|
origin: start,
|
||||||
|
size: Default::default(),
|
||||||
|
},
|
||||||
|
content_mask: Default::default(),
|
||||||
|
color: Default::default(),
|
||||||
|
contour_count: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -579,6 +630,7 @@ impl Path<Pixels> {
|
||||||
id: self.id,
|
id: self.id,
|
||||||
order: self.order,
|
order: self.order,
|
||||||
bounds: self.bounds.scale(factor),
|
bounds: self.bounds.scale(factor),
|
||||||
|
content_mask: self.content_mask.scale(factor),
|
||||||
vertices: self
|
vertices: self
|
||||||
.vertices
|
.vertices
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -586,27 +638,36 @@ impl Path<Pixels> {
|
||||||
.collect(),
|
.collect(),
|
||||||
start: self.start.map(|start| start.scale(factor)),
|
start: self.start.map(|start| start.scale(factor)),
|
||||||
current: self.current.scale(factor),
|
current: self.current.scale(factor),
|
||||||
|
contour_count: self.contour_count,
|
||||||
|
color: self.color,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn line_to(&mut self, to: Point<Pixels>) {
|
pub fn line_to(&mut self, to: Point<Pixels>) {
|
||||||
if let Some(start) = self.start {
|
self.contour_count += 1;
|
||||||
|
if self.contour_count > 1 {
|
||||||
self.push_triangle(
|
self.push_triangle(
|
||||||
(start, self.current, to),
|
(self.start, self.current, to),
|
||||||
(point(0., 1.), point(0., 1.), point(0., 1.)),
|
(point(0., 1.), point(0., 1.), point(0., 1.)),
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
self.start = Some(to);
|
|
||||||
}
|
}
|
||||||
self.current = to;
|
self.current = to;
|
||||||
}
|
}
|
||||||
|
|
||||||
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.line_to(to);
|
self.contour_count += 1;
|
||||||
|
if self.contour_count > 1 {
|
||||||
|
self.push_triangle(
|
||||||
|
(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.)),
|
(point(0., 0.), point(0.5, 0.), point(1., 1.)),
|
||||||
);
|
);
|
||||||
|
self.current = to;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_triangle(
|
fn push_triangle(
|
||||||
|
@ -614,12 +675,6 @@ impl Path<Pixels> {
|
||||||
xy: (Point<Pixels>, Point<Pixels>, Point<Pixels>),
|
xy: (Point<Pixels>, Point<Pixels>, Point<Pixels>),
|
||||||
st: (Point<f32>, Point<f32>, Point<f32>),
|
st: (Point<f32>, Point<f32>, Point<f32>),
|
||||||
) {
|
) {
|
||||||
if self.vertices.is_empty() {
|
|
||||||
self.bounds = Bounds {
|
|
||||||
origin: xy.0,
|
|
||||||
size: Default::default(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
self.bounds = self
|
self.bounds = self
|
||||||
.bounds
|
.bounds
|
||||||
.union(&Bounds {
|
.union(&Bounds {
|
||||||
|
|
|
@ -363,12 +363,11 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn paint_path(&mut self, mut path: Path<Pixels>) {
|
pub fn paint_path(&mut self, mut path: Path<Pixels>, color: impl Into<Hsla>) {
|
||||||
let scale_factor = self.scale_factor();
|
let scale_factor = self.scale_factor();
|
||||||
let content_mask = self.content_mask();
|
let content_mask = self.content_mask();
|
||||||
for vertex in &mut path.vertices {
|
path.content_mask = content_mask;
|
||||||
vertex.content_mask = content_mask.clone();
|
path.color = color.into();
|
||||||
}
|
|
||||||
let window = &mut *self.window;
|
let window = &mut *self.window;
|
||||||
window
|
window
|
||||||
.scene_builder
|
.scene_builder
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue