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(),
|
||||
"Uniforms".into(),
|
||||
"AtlasTile".into(),
|
||||
"PathRasterizationInputIndex".into(),
|
||||
"PathVertex_ScaledPixels".into(),
|
||||
"ShadowInputIndex".into(),
|
||||
"Shadow".into(),
|
||||
"QuadInputIndex".into(),
|
||||
|
@ -59,6 +61,7 @@ fn generate_shader_bindings() -> PathBuf {
|
|||
"SpriteInputIndex".into(),
|
||||
"MonochromeSprite".into(),
|
||||
"PolychromeSprite".into(),
|
||||
"PathSprite".into(),
|
||||
]);
|
||||
config.no_includes = true;
|
||||
config.enumeration.prefix_with_name = true;
|
||||
|
|
|
@ -99,12 +99,24 @@ impl MetalAtlasState {
|
|||
let texture_descriptor = metal::TextureDescriptor::new();
|
||||
texture_descriptor.set_width(size.width.into());
|
||||
texture_descriptor.set_height(size.height.into());
|
||||
let pixel_format = match kind {
|
||||
AtlasTextureKind::Monochrome => metal::MTLPixelFormat::A8Unorm,
|
||||
AtlasTextureKind::Polychrome => metal::MTLPixelFormat::BGRA8Unorm,
|
||||
AtlasTextureKind::Path => metal::MTLPixelFormat::R16Float,
|
||||
};
|
||||
let pixel_format;
|
||||
let usage;
|
||||
match kind {
|
||||
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_usage(usage);
|
||||
let metal_texture = self.device.new_texture(&texture_descriptor);
|
||||
|
||||
let textures = match kind {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{
|
||||
point, size, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, ContentMask, DevicePixels,
|
||||
MetalAtlas, MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch, Quad,
|
||||
ScaledPixels, Scene, Shadow, Size, Underline,
|
||||
Hsla, MetalAtlas, MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch,
|
||||
Quad, ScaledPixels, Scene, Shadow, Size, Underline,
|
||||
};
|
||||
use cocoa::{
|
||||
base::{NO, YES},
|
||||
|
@ -11,6 +11,7 @@ use cocoa::{
|
|||
use collections::HashMap;
|
||||
use metal::{CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};
|
||||
use objc::{self, msg_send, sel, sel_impl};
|
||||
use smallvec::SmallVec;
|
||||
use std::{ffi::c_void, mem, ptr, sync::Arc};
|
||||
|
||||
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 {
|
||||
layer: metal::MetalLayer,
|
||||
command_queue: CommandQueue,
|
||||
paths_rasterization_pipeline_state: metal::RenderPipelineState,
|
||||
path_sprites_pipeline_state: metal::RenderPipelineState,
|
||||
shadows_pipeline_state: metal::RenderPipelineState,
|
||||
quads_pipeline_state: metal::RenderPipelineState,
|
||||
underlines_pipeline_state: metal::RenderPipelineState,
|
||||
|
@ -31,8 +34,6 @@ pub(crate) struct MetalRenderer {
|
|||
|
||||
impl MetalRenderer {
|
||||
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() {
|
||||
device
|
||||
} else {
|
||||
|
@ -42,7 +43,7 @@ impl MetalRenderer {
|
|||
|
||||
let layer = metal::MetalLayer::new();
|
||||
layer.set_device(&device);
|
||||
layer.set_pixel_format(PIXEL_FORMAT);
|
||||
layer.set_pixel_format(MTLPixelFormat::BGRA8Unorm);
|
||||
layer.set_presents_with_transaction(true);
|
||||
layer.set_opaque(is_opaque);
|
||||
unsafe {
|
||||
|
@ -86,13 +87,29 @@ impl MetalRenderer {
|
|||
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(
|
||||
&device,
|
||||
&library,
|
||||
"shadows",
|
||||
"shadow_vertex",
|
||||
"shadow_fragment",
|
||||
PIXEL_FORMAT,
|
||||
MTLPixelFormat::BGRA8Unorm,
|
||||
);
|
||||
let quads_pipeline_state = build_pipeline_state(
|
||||
&device,
|
||||
|
@ -100,7 +117,7 @@ impl MetalRenderer {
|
|||
"quads",
|
||||
"quad_vertex",
|
||||
"quad_fragment",
|
||||
PIXEL_FORMAT,
|
||||
MTLPixelFormat::BGRA8Unorm,
|
||||
);
|
||||
let underlines_pipeline_state = build_pipeline_state(
|
||||
&device,
|
||||
|
@ -108,7 +125,7 @@ impl MetalRenderer {
|
|||
"underlines",
|
||||
"underline_vertex",
|
||||
"underline_fragment",
|
||||
PIXEL_FORMAT,
|
||||
MTLPixelFormat::BGRA8Unorm,
|
||||
);
|
||||
let monochrome_sprites_pipeline_state = build_pipeline_state(
|
||||
&device,
|
||||
|
@ -116,7 +133,7 @@ impl MetalRenderer {
|
|||
"monochrome_sprites",
|
||||
"monochrome_sprite_vertex",
|
||||
"monochrome_sprite_fragment",
|
||||
PIXEL_FORMAT,
|
||||
MTLPixelFormat::BGRA8Unorm,
|
||||
);
|
||||
let polychrome_sprites_pipeline_state = build_pipeline_state(
|
||||
&device,
|
||||
|
@ -124,7 +141,7 @@ impl MetalRenderer {
|
|||
"polychrome_sprites",
|
||||
"polychrome_sprite_vertex",
|
||||
"polychrome_sprite_fragment",
|
||||
PIXEL_FORMAT,
|
||||
MTLPixelFormat::BGRA8Unorm,
|
||||
);
|
||||
|
||||
let command_queue = device.new_command_queue();
|
||||
|
@ -133,6 +150,8 @@ impl MetalRenderer {
|
|||
Self {
|
||||
layer,
|
||||
command_queue,
|
||||
paths_rasterization_pipeline_state,
|
||||
path_sprites_pipeline_state,
|
||||
shadows_pipeline_state,
|
||||
quads_pipeline_state,
|
||||
underlines_pipeline_state,
|
||||
|
@ -172,7 +191,7 @@ impl MetalRenderer {
|
|||
let command_buffer = command_queue.new_command_buffer();
|
||||
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 color_attachment = render_pass_descriptor
|
||||
|
@ -208,8 +227,14 @@ impl MetalRenderer {
|
|||
PrimitiveBatch::Quads(quads) => {
|
||||
self.draw_quads(quads, &mut instance_offset, viewport_size, command_encoder);
|
||||
}
|
||||
PrimitiveBatch::Paths(_paths) => {
|
||||
// self.draw_paths(paths, &mut instance_offset, viewport_size, command_encoder);
|
||||
PrimitiveBatch::Paths(paths) => {
|
||||
self.draw_paths(
|
||||
paths,
|
||||
&path_tiles,
|
||||
&mut instance_offset,
|
||||
viewport_size,
|
||||
command_encoder,
|
||||
);
|
||||
}
|
||||
PrimitiveBatch::Underlines(underlines) => {
|
||||
self.draw_underlines(
|
||||
|
@ -258,19 +283,20 @@ impl MetalRenderer {
|
|||
drawable.present();
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn rasterize_paths(
|
||||
&mut self,
|
||||
paths: &[Path<ScaledPixels>],
|
||||
_offset: &mut usize,
|
||||
_command_buffer: &metal::CommandBufferRef,
|
||||
offset: &mut usize,
|
||||
command_buffer: &metal::CommandBufferRef,
|
||||
) -> HashMap<PathId, AtlasTile> {
|
||||
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(path.bounds.size.map(Into::into), AtlasTextureKind::Path);
|
||||
.allocate(clipped_bounds.size.map(Into::into), AtlasTextureKind::Path);
|
||||
vertices_by_texture_id
|
||||
.entry(tile.texture_id)
|
||||
.or_insert(Vec::new())
|
||||
|
@ -279,68 +305,65 @@ impl MetalRenderer {
|
|||
+ tile.bounds.origin.map(Into::into),
|
||||
st_position: vertex.st_position,
|
||||
content_mask: ContentMask {
|
||||
bounds: Bounds {
|
||||
origin: vertex.xy_position - path.bounds.origin
|
||||
+ tile.bounds.origin.map(Into::into),
|
||||
size: vertex.content_mask.bounds.size,
|
||||
},
|
||||
bounds: tile.bounds.map(Into::into),
|
||||
},
|
||||
}));
|
||||
tiles.insert(path.id, tile);
|
||||
}
|
||||
|
||||
for (_texture_id, _vertices) in vertices_by_texture_id {
|
||||
todo!();
|
||||
// align_offset(offset);
|
||||
// let next_offset = *offset + vertices.len() * mem::size_of::<PathVertex<ScaledPixels>>();
|
||||
// assert!(
|
||||
// next_offset <= INSTANCE_BUFFER_SIZE,
|
||||
// "instance buffer exhausted"
|
||||
// );
|
||||
for (texture_id, vertices) in vertices_by_texture_id {
|
||||
align_offset(offset);
|
||||
let next_offset = *offset + vertices.len() * mem::size_of::<PathVertex<ScaledPixels>>();
|
||||
assert!(
|
||||
next_offset <= INSTANCE_BUFFER_SIZE,
|
||||
"instance buffer exhausted"
|
||||
);
|
||||
|
||||
// let render_pass_descriptor = metal::RenderPassDescriptor::new();
|
||||
// let color_attachment = render_pass_descriptor
|
||||
// .color_attachments()
|
||||
// .object_at(0)
|
||||
// .unwrap();
|
||||
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);
|
||||
// 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.path_atlas_pipeline_state);
|
||||
// command_encoder.set_vertex_buffer(
|
||||
// shaders::GPUIPathAtlasVertexInputIndex_GPUIPathAtlasVertexInputIndexVertices as u64,
|
||||
// Some(&self.instances),
|
||||
// *offset as u64,
|
||||
// );
|
||||
// command_encoder.set_vertex_bytes(
|
||||
// shaders::GPUIPathAtlasVertexInputIndex_GPUIPathAtlasVertexInputIndexAtlasSize
|
||||
// as u64,
|
||||
// mem::size_of::<shaders::vector_float2>() as u64,
|
||||
// [vec2i(texture.width() as i32, texture.height() as i32).to_float2()].as_ptr()
|
||||
// as *const c_void,
|
||||
// );
|
||||
let texture = self.sprite_atlas.metal_texture(texture_id);
|
||||
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(&self.instances),
|
||||
*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 {
|
||||
// (self.instances.contents() as *mut u8).add(*offset) as *mut shaders::GPUIPathVertex
|
||||
// };
|
||||
let vertices_bytes_len = mem::size_of::<PathVertex<ScaledPixels>>() * vertices.len();
|
||||
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() {
|
||||
// unsafe {
|
||||
// *buffer_contents.add(ix) = *vertex;
|
||||
// }
|
||||
// }
|
||||
|
||||
// command_encoder.draw_primitives(
|
||||
// metal::MTLPrimitiveType::Triangle,
|
||||
// 0,
|
||||
// vertices.len() as u64,
|
||||
// );
|
||||
// command_encoder.end_encoding();
|
||||
// *offset = next_offset;
|
||||
command_encoder.draw_primitives(
|
||||
metal::MTLPrimitiveType::Triangle,
|
||||
0,
|
||||
vertices.len() as u64,
|
||||
);
|
||||
command_encoder.end_encoding();
|
||||
*offset = next_offset;
|
||||
}
|
||||
|
||||
tiles
|
||||
|
@ -462,6 +485,112 @@ impl MetalRenderer {
|
|||
*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(
|
||||
&mut self,
|
||||
underlines: &[Underline],
|
||||
|
@ -734,3 +863,17 @@ enum SpriteInputIndex {
|
|||
AtlasTextureSize = 3,
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
float h = hsla.h * 6.0; // Now, it's an angle but scaled in [0, 6) range
|
||||
float s = hsla.s;
|
||||
|
|
|
@ -8,7 +8,9 @@ use plane_split::{BspSplitter, Polygon as BspPolygon};
|
|||
use std::{fmt::Debug, iter::Peekable, mem, slice};
|
||||
|
||||
// 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;
|
||||
|
||||
|
@ -62,6 +64,12 @@ impl SceneBuilder {
|
|||
.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() {
|
||||
let z = layer_z_values[underline.order as LayerId as usize];
|
||||
self.splitter.add(
|
||||
|
@ -131,6 +139,16 @@ impl SceneBuilder {
|
|||
}
|
||||
|
||||
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) {
|
||||
*layer_id
|
||||
} else {
|
||||
|
@ -139,7 +157,6 @@ impl SceneBuilder {
|
|||
next_id
|
||||
};
|
||||
|
||||
let primitive = primitive.into();
|
||||
match primitive {
|
||||
Primitive::Shadow(mut shadow) => {
|
||||
shadow.order = layer_id;
|
||||
|
@ -240,6 +257,7 @@ impl<'a> Iterator for BatchIterator<'a> {
|
|||
PrimitiveKind::Shadow,
|
||||
),
|
||||
(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),
|
||||
PrimitiveKind::Underline,
|
||||
|
@ -382,6 +400,30 @@ pub enum Primitive {
|
|||
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)]
|
||||
pub(crate) enum PrimitiveBatch<'a> {
|
||||
Shadows(&'a [Shadow]),
|
||||
|
@ -557,20 +599,29 @@ pub struct Path<P: Clone + Debug> {
|
|||
pub(crate) id: PathId,
|
||||
order: u32,
|
||||
pub(crate) bounds: Bounds<P>,
|
||||
pub(crate) content_mask: ContentMask<P>,
|
||||
pub(crate) vertices: Vec<PathVertex<P>>,
|
||||
start: Option<Point<P>>,
|
||||
pub(crate) color: Hsla,
|
||||
start: Point<P>,
|
||||
current: Point<P>,
|
||||
contour_count: usize,
|
||||
}
|
||||
|
||||
impl Path<Pixels> {
|
||||
pub fn new() -> Self {
|
||||
pub fn new(start: Point<Pixels>) -> Self {
|
||||
Self {
|
||||
id: PathId(0),
|
||||
order: 0,
|
||||
vertices: Vec::new(),
|
||||
start: Default::default(),
|
||||
current: Default::default(),
|
||||
bounds: Default::default(),
|
||||
start,
|
||||
current: start,
|
||||
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,
|
||||
order: self.order,
|
||||
bounds: self.bounds.scale(factor),
|
||||
content_mask: self.content_mask.scale(factor),
|
||||
vertices: self
|
||||
.vertices
|
||||
.iter()
|
||||
|
@ -586,27 +638,36 @@ impl Path<Pixels> {
|
|||
.collect(),
|
||||
start: self.start.map(|start| start.scale(factor)),
|
||||
current: self.current.scale(factor),
|
||||
contour_count: self.contour_count,
|
||||
color: self.color,
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
(start, self.current, to),
|
||||
(self.start, self.current, to),
|
||||
(point(0., 1.), point(0., 1.), point(0., 1.)),
|
||||
);
|
||||
} else {
|
||||
self.start = Some(to);
|
||||
}
|
||||
self.current = to;
|
||||
}
|
||||
|
||||
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.current, ctrl, to),
|
||||
(point(0., 0.), point(0.5, 0.), point(1., 1.)),
|
||||
);
|
||||
self.current = to;
|
||||
}
|
||||
|
||||
fn push_triangle(
|
||||
|
@ -614,12 +675,6 @@ impl Path<Pixels> {
|
|||
xy: (Point<Pixels>, Point<Pixels>, Point<Pixels>),
|
||||
st: (Point<f32>, Point<f32>, Point<f32>),
|
||||
) {
|
||||
if self.vertices.is_empty() {
|
||||
self.bounds = Bounds {
|
||||
origin: xy.0,
|
||||
size: Default::default(),
|
||||
};
|
||||
}
|
||||
self.bounds = self
|
||||
.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 content_mask = self.content_mask();
|
||||
for vertex in &mut path.vertices {
|
||||
vertex.content_mask = content_mask.clone();
|
||||
}
|
||||
path.content_mask = content_mask;
|
||||
path.color = color.into();
|
||||
let window = &mut *self.window;
|
||||
window
|
||||
.scene_builder
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue