Checkpoint: emojis rendering

This commit is contained in:
Antonio Scandurra 2023-10-04 12:41:21 +02:00
parent cd1c137542
commit 5c750b6880
12 changed files with 453 additions and 427 deletions

View file

@ -48,10 +48,11 @@ fn generate_shader_bindings() -> PathBuf {
"ScaledContentMask".into(), "ScaledContentMask".into(),
"Uniforms".into(), "Uniforms".into(),
"AtlasTile".into(), "AtlasTile".into(),
"Quad".into(),
"QuadInputIndex".into(), "QuadInputIndex".into(),
"Quad".into(),
"SpriteInputIndex".into(),
"MonochromeSprite".into(), "MonochromeSprite".into(),
"MonochromeSpriteInputIndex".into(), "PolychromeSprite".into(),
]); ]);
config.no_includes = true; config.no_includes = true;
config.enumeration.prefix_with_name = true; config.enumeration.prefix_with_name = true;

View file

@ -147,8 +147,7 @@ pub trait PlatformWindow {
fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool; fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool;
fn draw(&self, scene: Scene); fn draw(&self, scene: Scene);
fn monochrome_sprite_atlas(&self) -> Arc<dyn PlatformAtlas>; fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
fn polychrome_sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
} }
pub trait PlatformDispatcher: Send + Sync { pub trait PlatformDispatcher: Send + Sync {
@ -182,6 +181,15 @@ pub enum AtlasKey {
Svg(RenderSvgParams), Svg(RenderSvgParams),
} }
impl AtlasKey {
pub fn is_monochrome(&self) -> bool {
match self {
AtlasKey::Glyph(params) => !params.is_emoji,
AtlasKey::Svg(_) => true,
}
}
}
impl From<RenderGlyphParams> for AtlasKey { impl From<RenderGlyphParams> for AtlasKey {
fn from(params: RenderGlyphParams) -> Self { fn from(params: RenderGlyphParams) -> Self {
Self::Glyph(params) Self::Glyph(params)
@ -250,12 +258,6 @@ pub trait PlatformInputHandler {
#[derive(Copy, Clone, Debug, PartialEq)] #[derive(Copy, Clone, Debug, PartialEq)]
pub struct ScreenId(pub(crate) Uuid); pub struct ScreenId(pub(crate) Uuid);
#[derive(Copy, Clone, Debug)]
pub enum RasterizationOptions {
Alpha,
Bgra,
}
#[derive(Debug)] #[derive(Debug)]
pub struct WindowOptions { pub struct WindowOptions {
pub bounds: WindowBounds, pub bounds: WindowBounds,

View file

@ -5,26 +5,15 @@ use anyhow::{anyhow, Result};
use collections::HashMap; use collections::HashMap;
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};
use etagere::BucketedAtlasAllocator; use etagere::BucketedAtlasAllocator;
use foreign_types::ForeignType; use metal::Device;
use metal::{Device, TextureDescriptor};
use objc::{msg_send, sel, sel_impl};
use parking_lot::Mutex; use parking_lot::Mutex;
pub struct MetalAtlas(Mutex<MetalAtlasState>); pub struct MetalAtlas(Mutex<MetalAtlasState>);
impl MetalAtlas { impl MetalAtlas {
pub fn new( pub fn new(device: Device) -> Self {
size: Size<DevicePixels>,
pixel_format: metal::MTLPixelFormat,
device: Device,
) -> Self {
let texture_descriptor = metal::TextureDescriptor::new();
texture_descriptor.set_pixel_format(pixel_format);
texture_descriptor.set_width(size.width.into());
texture_descriptor.set_height(size.height.into());
MetalAtlas(Mutex::new(MetalAtlasState { MetalAtlas(Mutex::new(MetalAtlasState {
device: AssertSend(device), device: AssertSend(device),
texture_descriptor: AssertSend(texture_descriptor),
textures: Default::default(), textures: Default::default(),
tiles_by_key: Default::default(), tiles_by_key: Default::default(),
})) }))
@ -37,7 +26,6 @@ impl MetalAtlas {
struct MetalAtlasState { struct MetalAtlasState {
device: AssertSend<Device>, device: AssertSend<Device>,
texture_descriptor: AssertSend<TextureDescriptor>,
textures: Vec<MetalAtlasTexture>, textures: Vec<MetalAtlasTexture>,
tiles_by_key: HashMap<AtlasKey, AtlasTile>, tiles_by_key: HashMap<AtlasKey, AtlasTile>,
} }
@ -57,9 +45,15 @@ impl PlatformAtlas for MetalAtlas {
.textures .textures
.iter_mut() .iter_mut()
.rev() .rev()
.find_map(|texture| texture.upload(size, &bytes)) .find_map(|texture| {
if texture.monochrome == key.is_monochrome() {
texture.upload(size, &bytes)
} else {
None
}
})
.or_else(|| { .or_else(|| {
let texture = lock.push_texture(size); let texture = lock.push_texture(size, key.is_monochrome());
texture.upload(size, &bytes) texture.upload(size, &bytes)
}) })
.ok_or_else(|| anyhow!("could not allocate in new texture"))?; .ok_or_else(|| anyhow!("could not allocate in new texture"))?;
@ -74,35 +68,32 @@ impl PlatformAtlas for MetalAtlas {
} }
impl MetalAtlasState { impl MetalAtlasState {
fn push_texture(&mut self, min_size: Size<DevicePixels>) -> &mut MetalAtlasTexture { fn push_texture(
let default_atlas_size = Size { &mut self,
width: self.texture_descriptor.width().into(), min_size: Size<DevicePixels>,
height: self.texture_descriptor.height().into(), monochrome: bool,
) -> &mut MetalAtlasTexture {
const DEFAULT_ATLAS_SIZE: Size<DevicePixels> = Size {
width: DevicePixels(1024),
height: DevicePixels(1024),
}; };
let size;
let metal_texture;
if min_size.width > default_atlas_size.width || min_size.height > default_atlas_size.height let size = min_size.max(&DEFAULT_ATLAS_SIZE);
{ let texture_descriptor = metal::TextureDescriptor::new();
let descriptor = unsafe { texture_descriptor.set_width(size.width.into());
let descriptor_ptr: *mut metal::MTLTextureDescriptor = texture_descriptor.set_height(size.height.into());
msg_send![*self.texture_descriptor, copy]; if monochrome {
metal::TextureDescriptor::from_ptr(descriptor_ptr) texture_descriptor.set_pixel_format(metal::MTLPixelFormat::A8Unorm);
};
descriptor.set_width(min_size.width.into());
descriptor.set_height(min_size.height.into());
descriptor.set_pixel_format(metal::MTLPixelFormat::Depth32Float);
size = min_size;
metal_texture = self.device.new_texture(&descriptor);
} else { } else {
size = default_atlas_size; texture_descriptor.set_pixel_format(metal::MTLPixelFormat::BGRA8Unorm);
metal_texture = self.device.new_texture(&self.texture_descriptor);
} }
let metal_texture = self.device.new_texture(&texture_descriptor);
let atlas_texture = MetalAtlasTexture { let atlas_texture = MetalAtlasTexture {
id: AtlasTextureId(self.textures.len() as u32), id: AtlasTextureId(self.textures.len() as u32),
allocator: etagere::BucketedAtlasAllocator::new(size.into()), allocator: etagere::BucketedAtlasAllocator::new(size.into()),
metal_texture: AssertSend(metal_texture), metal_texture: AssertSend(metal_texture),
monochrome,
}; };
self.textures.push(atlas_texture); self.textures.push(atlas_texture);
self.textures.last_mut().unwrap() self.textures.last_mut().unwrap()
@ -113,6 +104,7 @@ struct MetalAtlasTexture {
id: AtlasTextureId, id: AtlasTextureId,
allocator: BucketedAtlasAllocator, allocator: BucketedAtlasAllocator,
metal_texture: AssertSend<metal::Texture>, metal_texture: AssertSend<metal::Texture>,
monochrome: bool,
} }
impl MetalAtlasTexture { impl MetalAtlasTexture {

View file

@ -18,11 +18,11 @@ pub struct MetalRenderer {
layer: metal::MetalLayer, layer: metal::MetalLayer,
command_queue: CommandQueue, command_queue: CommandQueue,
quads_pipeline_state: metal::RenderPipelineState, quads_pipeline_state: metal::RenderPipelineState,
sprites_pipeline_state: metal::RenderPipelineState, monochrome_sprites_pipeline_state: metal::RenderPipelineState,
polychrome_sprites_pipeline_state: metal::RenderPipelineState,
unit_vertices: metal::Buffer, unit_vertices: metal::Buffer,
instances: metal::Buffer, instances: metal::Buffer,
monochrome_sprite_atlas: Arc<MetalAtlas>, sprite_atlas: Arc<MetalAtlas>,
polychrome_sprite_atlas: Arc<MetalAtlas>,
} }
impl MetalRenderer { impl MetalRenderer {
@ -90,43 +90,35 @@ impl MetalRenderer {
"quad_fragment", "quad_fragment",
PIXEL_FORMAT, PIXEL_FORMAT,
); );
let monochrome_sprites_pipeline_state = build_pipeline_state(
let sprites_pipeline_state = build_pipeline_state(
&device, &device,
&library, &library,
"sprites", "monochrome_sprites",
"monochrome_sprite_vertex", "monochrome_sprite_vertex",
"monochrome_sprite_fragment", "monochrome_sprite_fragment",
PIXEL_FORMAT, PIXEL_FORMAT,
); );
let polychrome_sprites_pipeline_state = build_pipeline_state(
&device,
&library,
"polychrome_sprites",
"polychrome_sprite_vertex",
"polychrome_sprite_fragment",
PIXEL_FORMAT,
);
let command_queue = device.new_command_queue(); let command_queue = device.new_command_queue();
let monochrome_sprite_atlas = Arc::new(MetalAtlas::new( let sprite_atlas = Arc::new(MetalAtlas::new(device.clone()));
Size {
width: DevicePixels(1024),
height: DevicePixels(768),
},
MTLPixelFormat::A8Unorm,
device.clone(),
));
let polychrome_sprite_atlas = Arc::new(MetalAtlas::new(
Size {
width: DevicePixels(1024),
height: DevicePixels(768),
},
MTLPixelFormat::BGRA8Unorm,
device.clone(),
));
Self { Self {
layer, layer,
command_queue, command_queue,
quads_pipeline_state, quads_pipeline_state,
sprites_pipeline_state, monochrome_sprites_pipeline_state,
polychrome_sprites_pipeline_state,
unit_vertices, unit_vertices,
instances, instances,
monochrome_sprite_atlas, sprite_atlas,
polychrome_sprite_atlas,
} }
} }
@ -134,12 +126,8 @@ impl MetalRenderer {
&*self.layer &*self.layer
} }
pub fn monochrome_sprite_atlas(&self) -> &Arc<MetalAtlas> { pub fn sprite_atlas(&self) -> &Arc<MetalAtlas> {
&self.monochrome_sprite_atlas &self.sprite_atlas
}
pub fn polychrome_sprite_atlas(&self) -> &Arc<MetalAtlas> {
&self.polychrome_sprite_atlas
} }
pub fn draw(&mut self, scene: &mut Scene) { pub fn draw(&mut self, scene: &mut Scene) {
@ -304,41 +292,38 @@ impl MetalRenderer {
} }
align_offset(offset); align_offset(offset);
let texture = self.monochrome_sprite_atlas.texture(texture_id); let texture = self.sprite_atlas.texture(texture_id);
let texture_size = size( let texture_size = size(
DevicePixels(texture.width() as i32), DevicePixels(texture.width() as i32),
DevicePixels(texture.height() as i32), DevicePixels(texture.height() as i32),
); );
command_encoder.set_render_pipeline_state(&self.sprites_pipeline_state); command_encoder.set_render_pipeline_state(&self.monochrome_sprites_pipeline_state);
command_encoder.set_vertex_buffer( command_encoder.set_vertex_buffer(
MonochromeSpriteInputIndex::Vertices as u64, SpriteInputIndex::Vertices as u64,
Some(&self.unit_vertices), Some(&self.unit_vertices),
0, 0,
); );
command_encoder.set_vertex_buffer( command_encoder.set_vertex_buffer(
MonochromeSpriteInputIndex::Sprites as u64, SpriteInputIndex::Sprites as u64,
Some(&self.instances), Some(&self.instances),
*offset as u64, *offset as u64,
); );
command_encoder.set_vertex_bytes( command_encoder.set_vertex_bytes(
MonochromeSpriteInputIndex::ViewportSize as u64, SpriteInputIndex::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 _,
); );
command_encoder.set_vertex_bytes( command_encoder.set_vertex_bytes(
MonochromeSpriteInputIndex::AtlasTextureSize as u64, SpriteInputIndex::AtlasTextureSize as u64,
mem::size_of_val(&texture_size) as u64, mem::size_of_val(&texture_size) as u64,
&texture_size as *const Size<DevicePixels> as *const _, &texture_size as *const Size<DevicePixels> as *const _,
); );
command_encoder.set_fragment_buffer( command_encoder.set_fragment_buffer(
MonochromeSpriteInputIndex::Sprites as u64, SpriteInputIndex::Sprites as u64,
Some(&self.instances), Some(&self.instances),
*offset as u64, *offset as u64,
); );
command_encoder.set_fragment_texture( command_encoder.set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture));
MonochromeSpriteInputIndex::AtlasTexture as u64,
Some(&texture),
);
let sprite_bytes_len = mem::size_of::<MonochromeSprite>() * sprites.len(); let sprite_bytes_len = mem::size_of::<MonochromeSprite>() * sprites.len();
let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
@ -373,71 +358,67 @@ impl MetalRenderer {
viewport_size: Size<DevicePixels>, viewport_size: Size<DevicePixels>,
command_encoder: &metal::RenderCommandEncoderRef, command_encoder: &metal::RenderCommandEncoderRef,
) { ) {
todo!() if sprites.is_empty() {
// if sprites.is_empty() { return;
// return; }
// } align_offset(offset);
// align_offset(offset);
// let texture = self.monochrome_sprite_atlas.texture(texture_id); let texture = self.sprite_atlas.texture(texture_id);
// let texture_size = size( let texture_size = size(
// DevicePixels(texture.width() as i32), DevicePixels(texture.width() as i32),
// DevicePixels(texture.height() as i32), DevicePixels(texture.height() as i32),
// ); );
// command_encoder.set_render_pipeline_state(&self.sprites_pipeline_state); command_encoder.set_render_pipeline_state(&self.polychrome_sprites_pipeline_state);
// command_encoder.set_vertex_buffer( command_encoder.set_vertex_buffer(
// MonochromeSpriteInputIndex::Vertices as u64, SpriteInputIndex::Vertices as u64,
// Some(&self.unit_vertices), Some(&self.unit_vertices),
// 0, 0,
// ); );
// command_encoder.set_vertex_buffer( command_encoder.set_vertex_buffer(
// MonochromeSpriteInputIndex::Sprites as u64, SpriteInputIndex::Sprites as u64,
// Some(&self.instances), Some(&self.instances),
// *offset as u64, *offset as u64,
// ); );
// command_encoder.set_vertex_bytes( command_encoder.set_vertex_bytes(
// MonochromeSpriteInputIndex::ViewportSize as u64, SpriteInputIndex::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 _,
// ); );
// command_encoder.set_vertex_bytes( command_encoder.set_vertex_bytes(
// MonochromeSpriteInputIndex::AtlasTextureSize as u64, SpriteInputIndex::AtlasTextureSize as u64,
// mem::size_of_val(&texture_size) as u64, mem::size_of_val(&texture_size) as u64,
// &texture_size as *const Size<DevicePixels> as *const _, &texture_size as *const Size<DevicePixels> as *const _,
// ); );
// command_encoder.set_fragment_buffer( command_encoder.set_fragment_buffer(
// MonochromeSpriteInputIndex::Sprites as u64, SpriteInputIndex::Sprites as u64,
// Some(&self.instances), Some(&self.instances),
// *offset as u64, *offset as u64,
// ); );
// command_encoder.set_fragment_texture( command_encoder.set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture));
// MonochromeSpriteInputIndex::AtlasTexture as u64,
// Some(&texture),
// );
// let sprite_bytes_len = mem::size_of::<MonochromeSprite>() * sprites.len(); let sprite_bytes_len = mem::size_of::<PolychromeSprite>() * sprites.len();
// let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
// unsafe { unsafe {
// ptr::copy_nonoverlapping( ptr::copy_nonoverlapping(
// sprites.as_ptr() as *const u8, sprites.as_ptr() as *const u8,
// buffer_contents, buffer_contents,
// sprite_bytes_len, sprite_bytes_len,
// ); );
// } }
// let next_offset = *offset + sprite_bytes_len; let next_offset = *offset + sprite_bytes_len;
// assert!( assert!(
// next_offset <= INSTANCE_BUFFER_SIZE, next_offset <= INSTANCE_BUFFER_SIZE,
// "instance buffer exhausted" "instance buffer exhausted"
// ); );
// command_encoder.draw_primitives_instanced( command_encoder.draw_primitives_instanced(
// metal::MTLPrimitiveType::Triangle, metal::MTLPrimitiveType::Triangle,
// 0, 0,
// 6, 6,
// sprites.len() as u64, sprites.len() as u64,
// ); );
// *offset = next_offset; *offset = next_offset;
} }
} }
@ -489,7 +470,7 @@ enum QuadInputIndex {
} }
#[repr(C)] #[repr(C)]
enum MonochromeSpriteInputIndex { enum SpriteInputIndex {
Vertices = 0, Vertices = 0,
Sprites = 1, Sprites = 1,
ViewportSize = 2, ViewportSize = 2,

View file

@ -7,7 +7,10 @@ float4 hsla_to_rgba(Hsla hsla);
float4 to_device_position(float2 unit_vertex, Bounds_ScaledPixels bounds, float4 to_device_position(float2 unit_vertex, Bounds_ScaledPixels bounds,
Bounds_ScaledPixels clip_bounds, Bounds_ScaledPixels clip_bounds,
constant Size_DevicePixels *viewport_size); constant Size_DevicePixels *viewport_size);
float quad_sdf(float2 point, Bounds_ScaledPixels bounds, Corners_ScaledPixels corner_radii); float2 to_tile_position(float2 unit_vertex, AtlasTile tile,
constant Size_DevicePixels *atlas_size);
float quad_sdf(float2 point, Bounds_ScaledPixels bounds,
Corners_ScaledPixels corner_radii);
struct QuadVertexOutput { struct QuadVertexOutput {
float4 position [[position]]; float4 position [[position]];
@ -119,27 +122,18 @@ struct MonochromeSpriteVertexOutput {
vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex( vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex(
uint unit_vertex_id [[vertex_id]], uint sprite_id [[instance_id]], uint unit_vertex_id [[vertex_id]], uint sprite_id [[instance_id]],
constant float2 *unit_vertices constant float2 *unit_vertices [[buffer(SpriteInputIndex_Vertices)]],
[[buffer(MonochromeSpriteInputIndex_Vertices)]], constant MonochromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
constant MonochromeSprite *sprites
[[buffer(MonochromeSpriteInputIndex_Sprites)]],
constant Size_DevicePixels *viewport_size constant Size_DevicePixels *viewport_size
[[buffer(MonochromeSpriteInputIndex_ViewportSize)]], [[buffer(SpriteInputIndex_ViewportSize)]],
constant Size_DevicePixels *atlas_size constant Size_DevicePixels *atlas_size
[[buffer(MonochromeSpriteInputIndex_AtlasTextureSize)]]) { [[buffer(SpriteInputIndex_AtlasTextureSize)]]) {
float2 unit_vertex = unit_vertices[unit_vertex_id]; float2 unit_vertex = unit_vertices[unit_vertex_id];
MonochromeSprite sprite = sprites[sprite_id]; MonochromeSprite sprite = sprites[sprite_id];
float4 device_position = to_device_position( float4 device_position = to_device_position(
unit_vertex, sprite.bounds, sprite.content_mask.bounds, viewport_size); unit_vertex, sprite.bounds, sprite.content_mask.bounds, viewport_size);
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
float2 tile_origin =
float2(sprite.tile.bounds.origin.x, sprite.tile.bounds.origin.y);
float2 tile_size =
float2(sprite.tile.bounds.size.width, sprite.tile.bounds.size.height);
float2 tile_position =
(tile_origin + unit_vertex * tile_size) /
float2((float)atlas_size->width, (float)atlas_size->height);
float4 color = hsla_to_rgba(sprite.color); float4 color = hsla_to_rgba(sprite.color);
return MonochromeSpriteVertexOutput{device_position, tile_position, color, return MonochromeSpriteVertexOutput{device_position, tile_position, color,
sprite_id}; sprite_id};
@ -147,22 +141,60 @@ vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex(
fragment float4 monochrome_sprite_fragment( fragment float4 monochrome_sprite_fragment(
MonochromeSpriteVertexOutput input [[stage_in]], MonochromeSpriteVertexOutput input [[stage_in]],
constant MonochromeSprite *sprites constant MonochromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
[[buffer(MonochromeSpriteInputIndex_Sprites)]], texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
texture2d<float> atlas_texture
[[texture(MonochromeSpriteInputIndex_AtlasTexture)]]) {
MonochromeSprite sprite = sprites[input.sprite_id]; MonochromeSprite sprite = sprites[input.sprite_id];
constexpr sampler atlas_texture_sampler(mag_filter::linear, constexpr sampler atlas_texture_sampler(mag_filter::linear,
min_filter::linear); min_filter::linear);
float4 sample = float4 sample =
atlas_texture.sample(atlas_texture_sampler, input.tile_position); atlas_texture.sample(atlas_texture_sampler, input.tile_position);
float clip_distance = float clip_distance = quad_sdf(input.position.xy, sprite.content_mask.bounds,
quad_sdf(input.position.xy, sprite.content_mask.bounds, sprite.content_mask.corner_radii); sprite.content_mask.corner_radii);
float4 color = input.color; float4 color = input.color;
color.a *= sample.a * saturate(0.5 - clip_distance); color.a *= sample.a * saturate(0.5 - clip_distance);
return color; return color;
} }
struct PolychromeSpriteVertexOutput {
float4 position [[position]];
float2 tile_position;
uint sprite_id [[flat]];
};
vertex PolychromeSpriteVertexOutput polychrome_sprite_vertex(
uint unit_vertex_id [[vertex_id]], uint sprite_id [[instance_id]],
constant float2 *unit_vertices [[buffer(SpriteInputIndex_Vertices)]],
constant PolychromeSprite *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];
PolychromeSprite sprite = sprites[sprite_id];
float4 device_position = to_device_position(
unit_vertex, sprite.bounds, sprite.content_mask.bounds, viewport_size);
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
return PolychromeSpriteVertexOutput{device_position, tile_position,
sprite_id};
}
fragment float4 polychrome_sprite_fragment(
PolychromeSpriteVertexOutput input [[stage_in]],
constant PolychromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
PolychromeSprite 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 clip_distance = quad_sdf(input.position.xy, sprite.content_mask.bounds,
sprite.content_mask.corner_radii);
float4 color = sample;
color.a *= saturate(0.5 - clip_distance);
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;
@ -229,6 +261,14 @@ float4 to_device_position(float2 unit_vertex, Bounds_ScaledPixels bounds,
return float4(device_position, 0., 1.); return float4(device_position, 0., 1.);
} }
float2 to_tile_position(float2 unit_vertex, AtlasTile tile,
constant Size_DevicePixels *atlas_size) {
float2 tile_origin = float2(tile.bounds.origin.x, tile.bounds.origin.y);
float2 tile_size = float2(tile.bounds.size.width, tile.bounds.size.height);
return (tile_origin + unit_vertex * tile_size) /
float2((float)atlas_size->width, (float)atlas_size->height);
}
float quad_sdf(float2 point, Bounds_ScaledPixels bounds, float quad_sdf(float2 point, Bounds_ScaledPixels bounds,
Corners_ScaledPixels corner_radii) { Corners_ScaledPixels corner_radii) {
float2 half_size = float2(bounds.size.width, bounds.size.height) / 2.; float2 half_size = float2(bounds.size.width, bounds.size.height) / 2.;

View file

@ -12,7 +12,11 @@ use core_foundation::{
base::{CFRange, TCFType}, base::{CFRange, TCFType},
string::CFString, string::CFString,
}; };
use core_graphics::{base::CGGlyph, color_space::CGColorSpace, context::CGContext}; use core_graphics::{
base::{kCGImageAlphaPremultipliedLast, CGGlyph},
color_space::CGColorSpace,
context::CGContext,
};
use core_text::{font::CTFont, line::CTLine, string_attributes::kCTFontAttributeName}; use core_text::{font::CTFont, line::CTLine, string_attributes::kCTFontAttributeName};
use font_kit::{ use font_kit::{
font::Font as FontKitFont, font::Font as FontKitFont,
@ -262,8 +266,22 @@ impl MacTextSystemState {
bitmap_size.height += DevicePixels(1); bitmap_size.height += DevicePixels(1);
} }
let mut bytes = vec![0; bitmap_size.width.0 as usize * bitmap_size.height.0 as usize]; let mut bytes;
let cx = CGContext::create_bitmap_context( let cx;
if params.is_emoji {
bytes = vec![0; bitmap_size.width.0 as usize * 4 * bitmap_size.height.0 as usize];
cx = CGContext::create_bitmap_context(
Some(bytes.as_mut_ptr() as *mut _),
bitmap_size.width.0 as usize,
bitmap_size.height.0 as usize,
8,
bitmap_size.width.0 as usize * 4,
&CGColorSpace::create_device_rgb(),
kCGImageAlphaPremultipliedLast,
);
} else {
bytes = vec![0; bitmap_size.width.0 as usize * bitmap_size.height.0 as usize];
cx = CGContext::create_bitmap_context(
Some(bytes.as_mut_ptr() as *mut _), Some(bytes.as_mut_ptr() as *mut _),
bitmap_size.width.0 as usize, bitmap_size.width.0 as usize,
bitmap_size.height.0 as usize, bitmap_size.height.0 as usize,
@ -272,6 +290,7 @@ impl MacTextSystemState {
&CGColorSpace::create_device_gray(), &CGColorSpace::create_device_gray(),
kCGImageAlphaOnly, kCGImageAlphaOnly,
); );
}
// Move the origin to bottom left and account for scaling, this // Move the origin to bottom left and account for scaling, this
// makes drawing text consistent with the font-kit's raster_bounds. // makes drawing text consistent with the font-kit's raster_bounds.
@ -303,6 +322,17 @@ impl MacTextSystemState {
cx, cx,
); );
if params.is_emoji {
// Convert from RGBA with premultiplied alpha to BGRA with straight alpha.
for pixel in bytes.chunks_exact_mut(4) {
pixel.swap(0, 2);
let a = pixel[3] as f32 / 255.;
pixel[0] = (pixel[0] as f32 / a) as u8;
pixel[1] = (pixel[1] as f32 / a) as u8;
pixel[2] = (pixel[2] as f32 / a) as u8;
}
}
Ok((bitmap_size.into(), bytes)) Ok((bitmap_size.into(), bytes))
} }
} }

View file

@ -886,12 +886,8 @@ impl PlatformWindow for MacWindow {
} }
} }
fn monochrome_sprite_atlas(&self) -> Arc<dyn PlatformAtlas> { fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
self.0.lock().renderer.monochrome_sprite_atlas().clone() self.0.lock().renderer.sprite_atlas().clone()
}
fn polychrome_sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
self.0.lock().renderer.polychrome_sprite_atlas().clone()
} }
} }

View file

@ -1,4 +1,4 @@
use std::{iter::Peekable, mem}; use std::{iter::Peekable, mem, slice};
use super::{Bounds, Hsla, Point}; use super::{Bounds, Hsla, Point};
use crate::{AtlasTextureId, AtlasTile, Corners, Edges, ScaledContentMask, ScaledPixels}; use crate::{AtlasTextureId, AtlasTile, Corners, Edges, ScaledContentMask, ScaledPixels};
@ -63,42 +63,46 @@ impl SceneLayer {
pub fn batches(&mut self) -> impl Iterator<Item = PrimitiveBatch> { pub fn batches(&mut self) -> impl Iterator<Item = PrimitiveBatch> {
self.quads.sort_unstable(); self.quads.sort_unstable();
self.monochrome_sprites.sort_unstable(); self.monochrome_sprites.sort_unstable();
self.polychrome_sprites.sort_unstable();
BatchIterator::new( BatchIterator {
&self.quads, quads: &self.quads,
self.quads.iter().peekable(), quads_start: 0,
&self.monochrome_sprites, quads_iter: self.quads.iter().peekable(),
self.monochrome_sprites.iter().peekable(), monochrome_sprites: &self.monochrome_sprites,
) monochrome_sprites_start: 0,
monochrome_sprites_iter: self.monochrome_sprites.iter().peekable(),
polychrome_sprites: &self.polychrome_sprites,
polychrome_sprites_start: 0,
polychrome_sprites_iter: self.polychrome_sprites.iter().peekable(),
}
} }
} }
struct BatchIterator<'a, Q, S> struct BatchIterator<'a> {
where
Q: Iterator<Item = &'a Quad>,
S: Iterator<Item = &'a MonochromeSprite>,
{
quads: &'a [Quad], quads: &'a [Quad],
sprites: &'a [MonochromeSprite],
quads_start: usize, quads_start: usize,
sprites_start: usize, quads_iter: Peekable<slice::Iter<'a, Quad>>,
quads_iter: Peekable<Q>, monochrome_sprites: &'a [MonochromeSprite],
sprites_iter: Peekable<S>, monochrome_sprites_start: usize,
monochrome_sprites_iter: Peekable<slice::Iter<'a, MonochromeSprite>>,
polychrome_sprites: &'a [PolychromeSprite],
polychrome_sprites_start: usize,
polychrome_sprites_iter: Peekable<slice::Iter<'a, PolychromeSprite>>,
} }
impl<'a, Q: 'a, S: 'a> Iterator for BatchIterator<'a, Q, S> impl<'a> Iterator for BatchIterator<'a> {
where
Q: Iterator<Item = &'a Quad>,
S: Iterator<Item = &'a MonochromeSprite>,
{
type Item = PrimitiveBatch<'a>; type Item = PrimitiveBatch<'a>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
let mut kinds_and_orders = [ let mut kinds_and_orders = [
(PrimitiveKind::Quad, self.quads_iter.peek().map(|q| q.order)), (PrimitiveKind::Quad, self.quads_iter.peek().map(|q| q.order)),
( (
PrimitiveKind::Sprite, PrimitiveKind::MonochromeSprite,
self.sprites_iter.peek().map(|s| s.order), self.monochrome_sprites_iter.peek().map(|s| s.order),
),
(
PrimitiveKind::PolychromeSprite,
self.polychrome_sprites_iter.peek().map(|s| s.order),
), ),
]; ];
kinds_and_orders.sort_by_key(|(_, order)| order.unwrap_or(u32::MAX)); kinds_and_orders.sort_by_key(|(_, order)| order.unwrap_or(u32::MAX));
@ -123,53 +127,49 @@ where
self.quads_start = quads_end; self.quads_start = quads_end;
Some(PrimitiveBatch::Quads(&self.quads[quads_start..quads_end])) Some(PrimitiveBatch::Quads(&self.quads[quads_start..quads_end]))
} }
PrimitiveKind::Sprite => { PrimitiveKind::MonochromeSprite => {
let texture_id = self.sprites_iter.peek().unwrap().tile.texture_id; let texture_id = self.monochrome_sprites_iter.peek().unwrap().tile.texture_id;
let sprites_start = self.sprites_start; let sprites_start = self.monochrome_sprites_start;
let sprites_end = sprites_start let sprites_end = sprites_start
+ self + self
.sprites_iter .monochrome_sprites_iter
.by_ref() .by_ref()
.take_while(|sprite| { .take_while(|sprite| {
sprite.order <= max_order && sprite.tile.texture_id == texture_id sprite.order <= max_order && sprite.tile.texture_id == texture_id
}) })
.count(); .count();
self.sprites_start = sprites_end; self.monochrome_sprites_start = sprites_end;
Some(PrimitiveBatch::MonochromeSprites { Some(PrimitiveBatch::MonochromeSprites {
texture_id, texture_id,
sprites: &self.sprites[sprites_start..sprites_end], sprites: &self.monochrome_sprites[sprites_start..sprites_end],
}) })
} }
PrimitiveKind::PolychromeSprite => {
let texture_id = self.polychrome_sprites_iter.peek().unwrap().tile.texture_id;
let sprites_start = self.polychrome_sprites_start;
let sprites_end = sprites_start
+ self
.polychrome_sprites_iter
.by_ref()
.take_while(|sprite| {
sprite.order <= max_order && sprite.tile.texture_id == texture_id
})
.count();
self.polychrome_sprites_start = sprites_end;
Some(PrimitiveBatch::PolychromeSprites {
texture_id,
sprites: &self.polychrome_sprites[sprites_start..sprites_end],
})
} }
} }
} }
impl<'a, Q: 'a, S: 'a> BatchIterator<'a, Q, S>
where
Q: Iterator<Item = &'a Quad>,
S: Iterator<Item = &'a MonochromeSprite>,
{
fn new(
quads: &'a [Quad],
quads_iter: Peekable<Q>,
sprites: &'a [MonochromeSprite],
sprites_iter: Peekable<S>,
) -> Self {
Self {
quads,
quads_start: 0,
quads_iter,
sprites,
sprites_start: 0,
sprites_iter,
}
}
} }
#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PrimitiveKind { pub enum PrimitiveKind {
Quad, Quad,
Sprite, MonochromeSprite,
PolychromeSprite,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]

View file

@ -36,8 +36,7 @@ pub struct TextSystem {
text_layout_cache: Arc<TextLayoutCache>, text_layout_cache: Arc<TextLayoutCache>,
platform_text_system: Arc<dyn PlatformTextSystem>, platform_text_system: Arc<dyn PlatformTextSystem>,
font_ids_by_font: RwLock<HashMap<Font, FontId>>, font_ids_by_font: RwLock<HashMap<Font, FontId>>,
fonts_by_font_id: RwLock<HashMap<FontId, Font>>, font_metrics: RwLock<HashMap<FontId, FontMetrics>>,
font_metrics: RwLock<HashMap<Font, FontMetrics>>,
wrapper_pool: Mutex<HashMap<FontIdWithSize, Vec<LineWrapper>>>, wrapper_pool: Mutex<HashMap<FontIdWithSize, Vec<LineWrapper>>>,
font_runs_pool: Mutex<Vec<Vec<(usize, FontId)>>>, font_runs_pool: Mutex<Vec<Vec<(usize, FontId)>>>,
} }
@ -49,7 +48,6 @@ impl TextSystem {
platform_text_system, platform_text_system,
font_metrics: RwLock::new(HashMap::default()), font_metrics: RwLock::new(HashMap::default()),
font_ids_by_font: RwLock::new(HashMap::default()), font_ids_by_font: RwLock::new(HashMap::default()),
fonts_by_font_id: RwLock::new(HashMap::default()),
wrapper_pool: Mutex::new(HashMap::default()), wrapper_pool: Mutex::new(HashMap::default()),
font_runs_pool: Default::default(), font_runs_pool: Default::default(),
} }
@ -62,30 +60,20 @@ impl TextSystem {
} else { } else {
let font_id = self.platform_text_system.font_id(font)?; let font_id = self.platform_text_system.font_id(font)?;
self.font_ids_by_font.write().insert(font.clone(), font_id); self.font_ids_by_font.write().insert(font.clone(), font_id);
self.fonts_by_font_id.write().insert(font_id, font.clone());
Ok(font_id) Ok(font_id)
} }
} }
pub fn with_font<T>(&self, font_id: FontId, f: impl FnOnce(&Self, &Font) -> T) -> Result<T> { pub fn bounding_box(&self, font_id: FontId, font_size: Pixels) -> Result<Bounds<Pixels>> {
self.fonts_by_font_id self.read_metrics(font_id, |metrics| metrics.bounding_box(font_size))
.read()
.get(&font_id)
.ok_or_else(|| anyhow!("font not found"))
.map(|font| f(self, font))
}
pub fn bounding_box(&self, font: &Font, font_size: Pixels) -> Result<Bounds<Pixels>> {
self.read_metrics(&font, |metrics| metrics.bounding_box(font_size))
} }
pub fn typographic_bounds( pub fn typographic_bounds(
&self, &self,
font: &Font, font_id: FontId,
font_size: Pixels, font_size: Pixels,
character: char, character: char,
) -> Result<Bounds<Pixels>> { ) -> Result<Bounds<Pixels>> {
let font_id = self.font_id(font)?;
let glyph_id = self let glyph_id = self
.platform_text_system .platform_text_system
.glyph_for_char(font_id, character) .glyph_for_char(font_id, character)
@ -93,65 +81,63 @@ impl TextSystem {
let bounds = self let bounds = self
.platform_text_system .platform_text_system
.typographic_bounds(font_id, glyph_id)?; .typographic_bounds(font_id, glyph_id)?;
self.read_metrics(font, |metrics| { self.read_metrics(font_id, |metrics| {
(bounds / metrics.units_per_em as f32 * font_size.0).map(px) (bounds / metrics.units_per_em as f32 * font_size.0).map(px)
}) })
} }
pub fn advance(&self, font: &Font, font_size: Pixels, ch: char) -> Result<Size<Pixels>> { pub fn advance(&self, font_id: FontId, font_size: Pixels, ch: char) -> Result<Size<Pixels>> {
let font_id = self.font_id(font)?;
let glyph_id = self let glyph_id = self
.platform_text_system .platform_text_system
.glyph_for_char(font_id, ch) .glyph_for_char(font_id, ch)
.ok_or_else(|| anyhow!("glyph not found for character '{}'", ch))?; .ok_or_else(|| anyhow!("glyph not found for character '{}'", ch))?;
let result = let result = self.platform_text_system.advance(font_id, glyph_id)?
self.platform_text_system.advance(font_id, glyph_id)? / self.units_per_em(font)? as f32; / self.units_per_em(font_id)? as f32;
Ok(result * font_size) Ok(result * font_size)
} }
pub fn units_per_em(&self, font: &Font) -> Result<u32> { pub fn units_per_em(&self, font_id: FontId) -> Result<u32> {
self.read_metrics(font, |metrics| metrics.units_per_em as u32) self.read_metrics(font_id, |metrics| metrics.units_per_em as u32)
} }
pub fn cap_height(&self, font: &Font, font_size: Pixels) -> Result<Pixels> { pub fn cap_height(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
self.read_metrics(font, |metrics| metrics.cap_height(font_size)) self.read_metrics(font_id, |metrics| metrics.cap_height(font_size))
} }
pub fn x_height(&self, font: &Font, font_size: Pixels) -> Result<Pixels> { pub fn x_height(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
self.read_metrics(font, |metrics| metrics.x_height(font_size)) self.read_metrics(font_id, |metrics| metrics.x_height(font_size))
} }
pub fn ascent(&self, font: &Font, font_size: Pixels) -> Result<Pixels> { pub fn ascent(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
self.read_metrics(font, |metrics| metrics.ascent(font_size)) self.read_metrics(font_id, |metrics| metrics.ascent(font_size))
} }
pub fn descent(&self, font: &Font, font_size: Pixels) -> Result<Pixels> { pub fn descent(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
self.read_metrics(font, |metrics| metrics.descent(font_size)) self.read_metrics(font_id, |metrics| metrics.descent(font_size))
} }
pub fn baseline_offset( pub fn baseline_offset(
&self, &self,
font: &Font, font_id: FontId,
font_size: Pixels, font_size: Pixels,
line_height: Pixels, line_height: Pixels,
) -> Result<Pixels> { ) -> Result<Pixels> {
let ascent = self.ascent(font, font_size)?; let ascent = self.ascent(font_id, font_size)?;
let descent = self.descent(font, font_size)?; let descent = self.descent(font_id, font_size)?;
let padding_top = (line_height - ascent - descent) / 2.; let padding_top = (line_height - ascent - descent) / 2.;
Ok(padding_top + ascent) Ok(padding_top + ascent)
} }
fn read_metrics<T>(&self, font: &Font, read: impl FnOnce(&FontMetrics) -> T) -> Result<T> { fn read_metrics<T>(&self, font_id: FontId, read: impl FnOnce(&FontMetrics) -> T) -> Result<T> {
let lock = self.font_metrics.upgradable_read(); let lock = self.font_metrics.upgradable_read();
if let Some(metrics) = lock.get(font) { if let Some(metrics) = lock.get(&font_id) {
Ok(read(metrics)) Ok(read(metrics))
} else { } else {
let font_id = self.platform_text_system.font_id(&font)?;
let mut lock = RwLockUpgradableReadGuard::upgrade(lock); let mut lock = RwLockUpgradableReadGuard::upgrade(lock);
let metrics = lock let metrics = lock
.entry(font.clone()) .entry(font_id)
.or_insert_with(|| self.platform_text_system.font_metrics(font_id)); .or_insert_with(|| self.platform_text_system.font_metrics(font_id));
Ok(read(metrics)) Ok(read(metrics))
} }
@ -390,6 +376,7 @@ pub struct RenderGlyphParams {
pub(crate) font_size: Pixels, pub(crate) font_size: Pixels,
pub(crate) subpixel_variant: Point<u8>, pub(crate) subpixel_variant: Point<u8>,
pub(crate) scale_factor: f32, pub(crate) scale_factor: f32,
pub(crate) is_emoji: bool,
} }
impl Eq for RenderGlyphParams {} impl Eq for RenderGlyphParams {}

View file

@ -109,8 +109,10 @@ impl Line {
let text_system = cx.text_system().clone(); let text_system = cx.text_system().clone();
for run in &self.layout.runs { for run in &self.layout.runs {
text_system.with_font(run.font_id, |system, font| { let max_glyph_width = text_system
let max_glyph_width = system.bounding_box(font, self.layout.font_size)?.size.width; .bounding_box(run.font_id, self.layout.font_size)?
.size
.width;
for glyph in &run.glyphs { for glyph in &run.glyphs {
let glyph_origin = origin + baseline_offset + glyph.position; let glyph_origin = origin + baseline_offset + glyph.position;
@ -130,9 +132,7 @@ impl Line {
underline.get_or_insert(( underline.get_or_insert((
point( point(
glyph_origin.x, glyph_origin.x,
origin.y origin.y + baseline_offset.y + (self.layout.descent * 0.618),
+ baseline_offset.y
+ (self.layout.descent * 0.618),
), ),
UnderlineStyle { UnderlineStyle {
color: style_run.underline.color, color: style_run.underline.color,
@ -177,9 +177,6 @@ impl Line {
)?; )?;
} }
} }
anyhow::Ok(())
})??;
} }
if let Some((_underline_start, _underline_style)) = underline.take() { if let Some((_underline_start, _underline_style)) = underline.take() {
@ -281,10 +278,12 @@ impl Line {
// }); // });
} }
cx.text_system().with_font(run.font_id, |system, font| { let text_system = cx.text_system();
let _glyph_bounds = Bounds { let _glyph_bounds = Bounds {
origin: glyph_origin, origin: glyph_origin,
size: system.bounding_box(font, self.layout.font_size)?.size, size: text_system
.bounding_box(run.font_id, self.layout.font_size)?
.size,
}; };
// if glyph_bounds.intersects(visible_bounds) { // if glyph_bounds.intersects(visible_bounds) {
// if glyph.is_emoji { // if glyph.is_emoji {
@ -304,8 +303,6 @@ impl Line {
// }); // });
// } // }
// } // }
anyhow::Ok(())
})??;
} }
} }

View file

@ -16,8 +16,7 @@ pub struct AnyWindow {}
pub struct Window { pub struct Window {
handle: AnyWindowHandle, handle: AnyWindowHandle,
platform_window: MainThreadOnly<Box<dyn PlatformWindow>>, platform_window: MainThreadOnly<Box<dyn PlatformWindow>>,
monochrome_sprite_atlas: Arc<dyn PlatformAtlas>, sprite_atlas: Arc<dyn PlatformAtlas>,
polychrome_sprite_atlas: Arc<dyn PlatformAtlas>,
rem_size: Pixels, rem_size: Pixels,
content_size: Size<Pixels>, content_size: Size<Pixels>,
layout_engine: TaffyLayoutEngine, layout_engine: TaffyLayoutEngine,
@ -36,8 +35,7 @@ impl Window {
cx: &mut MainThread<AppContext>, cx: &mut MainThread<AppContext>,
) -> Self { ) -> Self {
let platform_window = cx.platform().open_window(handle, options); let platform_window = cx.platform().open_window(handle, options);
let monochrome_sprite_atlas = platform_window.monochrome_sprite_atlas(); let sprite_atlas = platform_window.sprite_atlas();
let polychrome_sprite_atlas = platform_window.polychrome_sprite_atlas();
let mouse_position = platform_window.mouse_position(); let mouse_position = platform_window.mouse_position();
let content_size = platform_window.content_size(); let content_size = platform_window.content_size();
let scale_factor = platform_window.scale_factor(); let scale_factor = platform_window.scale_factor();
@ -60,8 +58,7 @@ impl Window {
Window { Window {
handle, handle,
platform_window, platform_window,
monochrome_sprite_atlas, sprite_atlas,
polychrome_sprite_atlas,
rem_size: px(16.), rem_size: px(16.),
content_size, content_size,
layout_engine: TaffyLayoutEngine::new(), layout_engine: TaffyLayoutEngine::new(),
@ -231,6 +228,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
font_size, font_size,
subpixel_variant, subpixel_variant,
scale_factor, scale_factor,
is_emoji: false,
}; };
let raster_bounds = self.text_system().raster_bounds(&params)?; let raster_bounds = self.text_system().raster_bounds(&params)?;
@ -238,7 +236,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
let layer_id = self.current_layer_id(); let layer_id = self.current_layer_id();
let tile = self let tile = self
.window .window
.monochrome_sprite_atlas .sprite_atlas
.get_or_insert_with(&params.clone().into(), &mut || { .get_or_insert_with(&params.clone().into(), &mut || {
self.text_system().rasterize_glyph(&params) self.text_system().rasterize_glyph(&params)
})?; })?;
@ -262,6 +260,54 @@ impl<'a, 'w> WindowContext<'a, 'w> {
Ok(()) Ok(())
} }
pub fn paint_emoji(
&mut self,
origin: Point<Pixels>,
order: u32,
font_id: FontId,
glyph_id: GlyphId,
font_size: Pixels,
) -> Result<()> {
let scale_factor = self.scale_factor();
let glyph_origin = origin.scale(scale_factor);
let params = RenderGlyphParams {
font_id,
glyph_id,
font_size,
// We don't render emojis with subpixel variants.
subpixel_variant: Default::default(),
scale_factor,
is_emoji: true,
};
let raster_bounds = self.text_system().raster_bounds(&params)?;
if !raster_bounds.is_zero() {
let layer_id = self.current_layer_id();
let tile = self
.window
.sprite_atlas
.get_or_insert_with(&params.clone().into(), &mut || {
self.text_system().rasterize_glyph(&params)
})?;
let bounds = Bounds {
origin: glyph_origin.map(|px| px.floor()) + raster_bounds.origin.map(Into::into),
size: tile.bounds.size.map(Into::into),
};
let content_mask = self.content_mask().scale(scale_factor);
self.window.scene.insert(
layer_id,
PolychromeSprite {
order,
bounds,
content_mask,
tile,
},
);
}
Ok(())
}
pub fn paint_svg( pub fn paint_svg(
&mut self, &mut self,
bounds: Bounds<Pixels>, bounds: Bounds<Pixels>,
@ -280,13 +326,13 @@ impl<'a, 'w> WindowContext<'a, 'w> {
}; };
let layer_id = self.current_layer_id(); let layer_id = self.current_layer_id();
let tile = self.window.monochrome_sprite_atlas.get_or_insert_with( let tile =
&params.clone().into(), self.window
&mut || { .sprite_atlas
.get_or_insert_with(&params.clone().into(), &mut || {
let bytes = self.svg_renderer.render(&params)?; let bytes = self.svg_renderer.render(&params)?;
Ok((params.size, bytes)) Ok((params.size, bytes))
}, })?;
)?;
let content_mask = self.content_mask().scale(scale_factor); let content_mask = self.content_mask().scale(scale_factor);
self.window.scene.insert( self.window.scene.insert(
@ -303,52 +349,6 @@ impl<'a, 'w> WindowContext<'a, 'w> {
Ok(()) Ok(())
} }
pub fn paint_emoji(
&mut self,
origin: Point<Pixels>,
order: u32,
font_id: FontId,
glyph_id: GlyphId,
font_size: Pixels,
) -> Result<()> {
let scale_factor = self.scale_factor();
let glyph_origin = origin.scale(scale_factor);
let params = RenderGlyphParams {
font_id,
glyph_id,
font_size,
subpixel_variant: Default::default(),
scale_factor,
};
let raster_bounds = self.text_system().raster_bounds(&params)?;
if !raster_bounds.is_zero() {
let layer_id = self.current_layer_id();
let tile = self
.window
.polychrome_sprite_atlas
.get_or_insert_with(&params.clone().into(), &mut || {
self.text_system().rasterize_glyph(&params)
})?;
let bounds = Bounds {
origin: glyph_origin.map(|px| px.floor()) + raster_bounds.origin.map(Into::into),
size: tile.bounds.size.map(Into::into),
};
let content_mask = self.content_mask().scale(scale_factor);
self.window.scene.insert(
layer_id,
PolychromeSprite {
order,
bounds,
content_mask,
tile,
},
);
}
Ok(())
}
pub(crate) fn draw(&mut self) -> Result<()> { pub(crate) fn draw(&mut self) -> Result<()> {
let unit_entity = self.unit_entity.clone(); let unit_entity = self.unit_entity.clone();
self.update_entity(&unit_entity, |_, cx| { self.update_entity(&unit_entity, |_, cx| {

View file

@ -51,7 +51,7 @@ impl CollabPanel {
//:: https://tailwindcss.com/docs/hover-focus-and-other-states#styling-based-on-parent-state //:: https://tailwindcss.com/docs/hover-focus-and-other-states#styling-based-on-parent-state
// .group() // .group()
// List Section Header // List Section Header
.child(self.list_section_header("#CRDB", true, theme)) .child(self.list_section_header("#CRDB 🗃️", true, theme))
// List Item Large // List Item Large
.child(self.list_item( .child(self.list_item(
"http://github.com/maxbrunsfeld.png?s=50", "http://github.com/maxbrunsfeld.png?s=50",