WIP: Add anti-aliasing to paths and render them after spriting them

This commit is contained in:
Antonio Scandurra 2021-03-30 16:37:12 +02:00
parent 98845663a9
commit 1903f63c45
4 changed files with 205 additions and 45 deletions

View file

@ -65,12 +65,12 @@ impl Renderer {
MTLResourceOptions::StorageModeManaged,
);
let path_stencil_pixel_format = metal::MTLPixelFormat::Stencil8;
let path_stencil_descriptor = metal::TextureDescriptor::new();
path_stencil_descriptor.set_width(2048);
path_stencil_descriptor.set_height(2048);
path_stencil_descriptor.set_pixel_format(path_stencil_pixel_format);
path_stencil_descriptor.set_usage(metal::MTLTextureUsage::RenderTarget);
path_stencil_descriptor.set_width(1024);
path_stencil_descriptor.set_height(768);
path_stencil_descriptor.set_pixel_format(pixel_format);
path_stencil_descriptor
.set_usage(metal::MTLTextureUsage::RenderTarget | metal::MTLTextureUsage::ShaderRead);
path_stencil_descriptor.set_storage_mode(metal::MTLStorageMode::Private);
let sprite_cache = SpriteCache::new(device.clone(), vec2i(1024, 768), fonts);
@ -105,7 +105,7 @@ impl Renderer {
"path_winding",
"path_winding_vertex",
"path_winding_fragment",
path_stencil_pixel_format,
pixel_format,
)?;
Ok(Self {
device,
@ -128,15 +128,21 @@ impl Renderer {
output: &metal::TextureRef,
) {
let mut offset = 0;
self.render_path_stencils(scene, &mut offset, drawable_size, command_buffer);
self.render_layers(scene, &mut offset, drawable_size, command_buffer, output);
let stencils = self.render_path_stencils(scene, &mut offset, command_buffer);
self.render_layers(
scene,
stencils,
&mut offset,
drawable_size,
command_buffer,
output,
);
}
fn render_path_stencils(
&mut self,
scene: &Scene,
offset: &mut usize,
drawable_size: Vector2F,
command_buffer: &metal::CommandBufferRef,
) -> Vec<PathSprite> {
let mut stencils = Vec::new();
@ -145,16 +151,20 @@ impl Renderer {
for (layer_id, layer) in scene.layers().iter().enumerate() {
for path in layer.paths() {
// Push a PathStencil struct for use later when sampling from the atlas as we draw the content of the layers
let size = path.bounds.size().ceil().to_i32();
let (atlas_id, atlas_origin) = self.path_stencils.allocate(size).unwrap();
let origin = path.bounds.origin() * scene.scale_factor();
let size = (path.bounds.size() * scene.scale_factor()).ceil();
let (atlas_id, atlas_origin) =
self.path_stencils.allocate(size.ceil().to_i32()).unwrap();
let atlas_origin = atlas_origin.to_f32();
stencils.push(PathSprite {
layer_id,
atlas_id,
sprite: shaders::GPUISprite {
origin: path.bounds.origin().to_float2(),
origin: origin.to_float2(),
size: size.to_float2(),
atlas_origin: atlas_origin.to_float2(),
color: path.color.to_uchar4(),
compute_winding: 1,
},
});
@ -172,11 +182,10 @@ impl Renderer {
// Populate the vertices by translating them to their appropriate location in the atlas.
for vertex in &path.vertices {
let xy_position = (vertex.xy_position - path.bounds.origin())
* scene.scale_factor()
+ atlas_origin.to_f32();
let xy_position =
(vertex.xy_position - path.bounds.origin()) * scene.scale_factor();
vertices.push(shaders::GPUIPathVertex {
xy_position: xy_position.to_float2(),
xy_position: (atlas_origin + xy_position).to_float2(),
st_position: vertex.st_position.to_float2(),
});
}
@ -205,25 +214,32 @@ impl Renderer {
);
let render_pass_descriptor = metal::RenderPassDescriptor::new();
let stencil_attachment = render_pass_descriptor.stencil_attachment().unwrap();
let color_attachment = render_pass_descriptor
.color_attachments()
.object_at(0)
.unwrap();
let stencil_texture = self.path_stencils.texture(atlas_id).unwrap();
stencil_attachment.set_texture(Some(stencil_texture));
stencil_attachment.set_load_action(metal::MTLLoadAction::Clear);
stencil_attachment.set_store_action(metal::MTLStoreAction::Store);
color_attachment.set_texture(Some(stencil_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 stencil_attachment = render_pass_descriptor.stencil_attachment().unwrap();
// let stencil_texture = self.path_stencils.texture(atlas_id).unwrap();
// stencil_attachment.set_texture(Some(stencil_texture));
// stencil_attachment.set_load_action(metal::MTLLoadAction::Clear);
// stencil_attachment.set_store_action(metal::MTLStoreAction::Store);
let stencil_descriptor = metal::DepthStencilDescriptor::new();
let front_face_stencil = stencil_descriptor.front_face_stencil().unwrap();
front_face_stencil.set_depth_stencil_pass_operation(metal::MTLStencilOperation::Invert);
front_face_stencil.set_depth_failure_operation(metal::MTLStencilOperation::Keep);
front_face_stencil.set_stencil_compare_function(metal::MTLCompareFunction::Always);
front_face_stencil.set_read_mask(0x1);
front_face_stencil.set_write_mask(0x1);
let depth_stencil_state = self.device.new_depth_stencil_state(&stencil_descriptor);
// let stencil_descriptor = metal::DepthStencilDescriptor::new();
// let front_face_stencil = stencil_descriptor.front_face_stencil().unwrap();
// front_face_stencil.set_depth_stencil_pass_operation(metal::MTLStencilOperation::Invert);
// front_face_stencil.set_depth_failure_operation(metal::MTLStencilOperation::Keep);
// front_face_stencil.set_stencil_compare_function(metal::MTLCompareFunction::Always);
// front_face_stencil.set_read_mask(0x1);
// front_face_stencil.set_write_mask(0x1);
// let depth_stencil_state = self.device.new_depth_stencil_state(&stencil_descriptor);
let winding_command_encoder =
command_buffer.new_render_command_encoder(render_pass_descriptor);
winding_command_encoder.set_depth_stencil_state(&depth_stencil_state);
winding_command_encoder.set_render_pipeline_state(&self.path_stencil_pipeline_state);
winding_command_encoder.set_vertex_buffer(
shaders::GPUIPathWindingVertexInputIndex_GPUIPathWindingVertexInputIndexVertices as u64,
@ -264,6 +280,7 @@ impl Renderer {
fn render_layers(
&mut self,
scene: &Scene,
path_sprites: Vec<PathSprite>,
offset: &mut usize,
drawable_size: Vector2F,
command_buffer: &metal::CommandBufferRef,
@ -289,11 +306,20 @@ impl Renderer {
zfar: 1.0,
});
for layer in scene.layers() {
for (layer_id, layer) in scene.layers().iter().enumerate() {
self.clip(scene, layer, drawable_size, command_encoder);
self.render_shadows(scene, layer, offset, drawable_size, command_encoder);
self.render_quads(scene, layer, offset, drawable_size, command_encoder);
self.render_sprites(scene, layer, offset, drawable_size, command_encoder);
// TODO: Pass sprites relevant to this layer in a more efficient manner.
self.render_path_sprites(
scene,
layer,
path_sprites.iter().filter(|s| s.layer_id == layer_id),
offset,
drawable_size,
command_encoder,
);
self.render_glyph_sprites(scene, layer, offset, drawable_size, command_encoder);
}
command_encoder.end_encoding();
@ -471,7 +497,7 @@ impl Renderer {
);
}
fn render_sprites(
fn render_glyph_sprites(
&mut self,
scene: &Scene,
layer: &Layer,
@ -502,6 +528,7 @@ impl Renderer {
size: sprite.size.to_float2(),
atlas_origin: sprite.atlas_origin.to_float2(),
color: glyph.color.to_uchar4(),
compute_winding: 0,
});
}
}
@ -563,6 +590,87 @@ impl Renderer {
);
}
}
fn render_path_sprites<'a>(
&mut self,
scene: &Scene,
layer: &Layer,
sprites: impl Iterator<Item = &'a PathSprite>,
offset: &mut usize,
drawable_size: Vector2F,
command_encoder: &metal::RenderCommandEncoderRef,
) {
let mut sprites = sprites.peekable();
if sprites.peek().is_none() {
return;
}
let mut sprites_by_atlas = HashMap::new();
for sprite in sprites {
sprites_by_atlas
.entry(sprite.atlas_id)
.or_insert_with(Vec::new)
.push(sprite.sprite);
}
command_encoder.set_render_pipeline_state(&self.sprite_pipeline_state);
command_encoder.set_vertex_buffer(
shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexVertices as u64,
Some(&self.unit_vertices),
0,
);
command_encoder.set_vertex_bytes(
shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexViewportSize as u64,
mem::size_of::<shaders::vector_float2>() as u64,
[drawable_size.to_float2()].as_ptr() as *const c_void,
);
for (atlas_id, sprites) in sprites_by_atlas {
align_offset(offset);
let next_offset = *offset + sprites.len() * mem::size_of::<shaders::GPUISprite>();
assert!(
next_offset <= INSTANCE_BUFFER_SIZE,
"instance buffer exhausted"
);
command_encoder.set_vertex_buffer(
shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexSprites as u64,
Some(&self.instances),
*offset as u64,
);
let texture = self.path_stencils.texture(atlas_id).unwrap();
command_encoder.set_vertex_bytes(
shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexAtlasSize 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,
);
command_encoder.set_fragment_texture(
shaders::GPUISpriteFragmentInputIndex_GPUISpriteFragmentInputIndexAtlas as u64,
Some(texture),
);
unsafe {
let buffer_contents = (self.instances.contents() as *mut u8)
.offset(*offset as isize)
as *mut shaders::GPUISprite;
std::ptr::copy_nonoverlapping(sprites.as_ptr(), buffer_contents, sprites.len());
}
self.instances.did_modify_range(NSRange {
location: *offset as u64,
length: (next_offset - *offset) as u64,
});
*offset = next_offset;
command_encoder.draw_primitives_instanced(
metal::MTLPrimitiveType::Triangle,
0,
6,
sprites.len() as u64,
);
}
}
}
fn align_offset(offset: &mut usize) {
@ -625,13 +733,47 @@ fn build_stencil_pipeline_state(
descriptor.set_label(label);
descriptor.set_vertex_function(Some(vertex_fn.as_ref()));
descriptor.set_fragment_function(Some(fragment_fn.as_ref()));
descriptor.set_stencil_attachment_pixel_format(pixel_format);
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)
.map_err(|message| anyhow!("could not create render pipeline state: {}", message))
}
// fn build_stencil_pipeline_state(
// device: &metal::DeviceRef,
// library: &metal::LibraryRef,
// label: &str,
// vertex_fn_name: &str,
// fragment_fn_name: &str,
// pixel_format: metal::MTLPixelFormat,
// ) -> Result<metal::RenderPipelineState> {
// let vertex_fn = library
// .get_function(vertex_fn_name, None)
// .map_err(|message| anyhow!("error locating vertex function: {}", message))?;
// let fragment_fn = library
// .get_function(fragment_fn_name, None)
// .map_err(|message| anyhow!("error locating fragment function: {}", message))?;
// 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()));
// descriptor.set_stencil_attachment_pixel_format(pixel_format);
// device
// .new_render_pipeline_state(&descriptor)
// .map_err(|message| anyhow!("could not create render pipeline state: {}", message))
// }
mod shaders {
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]

View file

@ -52,6 +52,7 @@ typedef struct {
vector_float2 size;
vector_float2 atlas_origin;
vector_uchar4 color;
uint8_t compute_winding;
} GPUISprite;
typedef enum {

View file

@ -168,6 +168,7 @@ struct SpriteFragmentInput {
float4 position [[position]];
float2 atlas_position;
float4 color [[flat]];
uchar compute_winding [[flat]];
};
vertex SpriteFragmentInput sprite_vertex(
@ -188,6 +189,7 @@ vertex SpriteFragmentInput sprite_vertex(
device_position,
atlas_position,
coloru_to_colorf(sprite.color),
sprite.compute_winding
};
}
@ -197,8 +199,14 @@ fragment float4 sprite_fragment(
) {
constexpr sampler atlas_sampler(mag_filter::linear, min_filter::linear);
float4 color = input.color;
float4 mask = atlas.sample(atlas_sampler, input.atlas_position);
color.a *= mask.a;
float4 sample = atlas.sample(atlas_sampler, input.atlas_position);
float mask;
if (input.compute_winding) {
mask = fmod(sample.r * 255., 2.);
} else {
mask = sample.a;
}
color.a *= mask;
return color;
}
@ -223,9 +231,14 @@ vertex PathWindingFragmentInput path_winding_vertex(
fragment float4 path_winding_fragment(
PathWindingFragmentInput input [[stage_in]]
) {
if (input.st_position.x * input.st_position.x - input.st_position.y > 0.) {
return float4(0.);
} else {
return float4(1.);
}
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) / 255.;
return float4(alpha, 0., 0., 1.);
}

View file

@ -230,25 +230,29 @@ impl BufferElement {
let selection = Selection {
line_height,
start_y: row_range.start as f32 * line_height - scroll_top,
start_y: bounds.origin_y() + row_range.start as f32 * line_height - scroll_top,
lines: row_range
.into_iter()
.map(|row| {
let line_layout = &layout.line_layouts[(row - start_row) as usize];
SelectionLine {
start_x: if row == range_start.row() {
line_layout.x_for_index(range_start.column() as usize)
bounds.origin_x()
+ line_layout.x_for_index(range_start.column() as usize)
- scroll_left
- descent
} else {
-scroll_left
},
end_x: if row == range_end.row() {
line_layout.x_for_index(range_end.column() as usize)
bounds.origin_x()
+ line_layout.x_for_index(range_end.column() as usize)
- scroll_left
- descent
} else {
line_layout.width + corner_radius * 2.0 - scroll_left - descent
bounds.origin_x() + line_layout.width + corner_radius * 2.0
- scroll_left
- descent
},
}
})