diff --git a/crates/gpui3/build.rs b/crates/gpui3/build.rs index 15e655ced6..6084948426 100644 --- a/crates/gpui3/build.rs +++ b/crates/gpui3/build.rs @@ -48,10 +48,11 @@ fn generate_shader_bindings() -> PathBuf { "ScaledContentMask".into(), "Uniforms".into(), "AtlasTile".into(), - "Quad".into(), "QuadInputIndex".into(), + "Quad".into(), + "SpriteInputIndex".into(), "MonochromeSprite".into(), - "MonochromeSpriteInputIndex".into(), + "PolychromeSprite".into(), ]); config.no_includes = true; config.enumeration.prefix_with_name = true; diff --git a/crates/gpui3/src/platform.rs b/crates/gpui3/src/platform.rs index 0a35049152..317da964f4 100644 --- a/crates/gpui3/src/platform.rs +++ b/crates/gpui3/src/platform.rs @@ -147,8 +147,7 @@ pub trait PlatformWindow { fn is_topmost_for_position(&self, position: Point) -> bool; fn draw(&self, scene: Scene); - fn monochrome_sprite_atlas(&self) -> Arc; - fn polychrome_sprite_atlas(&self) -> Arc; + fn sprite_atlas(&self) -> Arc; } pub trait PlatformDispatcher: Send + Sync { @@ -182,6 +181,15 @@ pub enum AtlasKey { Svg(RenderSvgParams), } +impl AtlasKey { + pub fn is_monochrome(&self) -> bool { + match self { + AtlasKey::Glyph(params) => !params.is_emoji, + AtlasKey::Svg(_) => true, + } + } +} + impl From for AtlasKey { fn from(params: RenderGlyphParams) -> Self { Self::Glyph(params) @@ -250,12 +258,6 @@ pub trait PlatformInputHandler { #[derive(Copy, Clone, Debug, PartialEq)] pub struct ScreenId(pub(crate) Uuid); -#[derive(Copy, Clone, Debug)] -pub enum RasterizationOptions { - Alpha, - Bgra, -} - #[derive(Debug)] pub struct WindowOptions { pub bounds: WindowBounds, diff --git a/crates/gpui3/src/platform/mac/metal_atlas.rs b/crates/gpui3/src/platform/mac/metal_atlas.rs index 3753be5bfd..f442988768 100644 --- a/crates/gpui3/src/platform/mac/metal_atlas.rs +++ b/crates/gpui3/src/platform/mac/metal_atlas.rs @@ -5,26 +5,15 @@ use anyhow::{anyhow, Result}; use collections::HashMap; use derive_more::{Deref, DerefMut}; use etagere::BucketedAtlasAllocator; -use foreign_types::ForeignType; -use metal::{Device, TextureDescriptor}; -use objc::{msg_send, sel, sel_impl}; +use metal::Device; use parking_lot::Mutex; pub struct MetalAtlas(Mutex); impl MetalAtlas { - pub fn new( - size: Size, - 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()); + pub fn new(device: Device) -> Self { MetalAtlas(Mutex::new(MetalAtlasState { device: AssertSend(device), - texture_descriptor: AssertSend(texture_descriptor), textures: Default::default(), tiles_by_key: Default::default(), })) @@ -37,7 +26,6 @@ impl MetalAtlas { struct MetalAtlasState { device: AssertSend, - texture_descriptor: AssertSend, textures: Vec, tiles_by_key: HashMap, } @@ -57,9 +45,15 @@ impl PlatformAtlas for MetalAtlas { .textures .iter_mut() .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(|| { - let texture = lock.push_texture(size); + let texture = lock.push_texture(size, key.is_monochrome()); texture.upload(size, &bytes) }) .ok_or_else(|| anyhow!("could not allocate in new texture"))?; @@ -74,35 +68,32 @@ impl PlatformAtlas for MetalAtlas { } impl MetalAtlasState { - fn push_texture(&mut self, min_size: Size) -> &mut MetalAtlasTexture { - let default_atlas_size = Size { - width: self.texture_descriptor.width().into(), - height: self.texture_descriptor.height().into(), + fn push_texture( + &mut self, + min_size: Size, + monochrome: bool, + ) -> &mut MetalAtlasTexture { + const DEFAULT_ATLAS_SIZE: Size = 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 descriptor = unsafe { - let descriptor_ptr: *mut metal::MTLTextureDescriptor = - msg_send![*self.texture_descriptor, copy]; - metal::TextureDescriptor::from_ptr(descriptor_ptr) - }; - 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); + let size = min_size.max(&DEFAULT_ATLAS_SIZE); + let texture_descriptor = metal::TextureDescriptor::new(); + texture_descriptor.set_width(size.width.into()); + texture_descriptor.set_height(size.height.into()); + if monochrome { + texture_descriptor.set_pixel_format(metal::MTLPixelFormat::A8Unorm); } else { - size = default_atlas_size; - metal_texture = self.device.new_texture(&self.texture_descriptor); + texture_descriptor.set_pixel_format(metal::MTLPixelFormat::BGRA8Unorm); } + let metal_texture = self.device.new_texture(&texture_descriptor); let atlas_texture = MetalAtlasTexture { id: AtlasTextureId(self.textures.len() as u32), allocator: etagere::BucketedAtlasAllocator::new(size.into()), metal_texture: AssertSend(metal_texture), + monochrome, }; self.textures.push(atlas_texture); self.textures.last_mut().unwrap() @@ -113,6 +104,7 @@ struct MetalAtlasTexture { id: AtlasTextureId, allocator: BucketedAtlasAllocator, metal_texture: AssertSend, + monochrome: bool, } impl MetalAtlasTexture { diff --git a/crates/gpui3/src/platform/mac/metal_renderer.rs b/crates/gpui3/src/platform/mac/metal_renderer.rs index c65e82a0e7..a018aad762 100644 --- a/crates/gpui3/src/platform/mac/metal_renderer.rs +++ b/crates/gpui3/src/platform/mac/metal_renderer.rs @@ -18,11 +18,11 @@ pub struct MetalRenderer { layer: metal::MetalLayer, command_queue: CommandQueue, 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, instances: metal::Buffer, - monochrome_sprite_atlas: Arc, - polychrome_sprite_atlas: Arc, + sprite_atlas: Arc, } impl MetalRenderer { @@ -90,43 +90,35 @@ impl MetalRenderer { "quad_fragment", PIXEL_FORMAT, ); - - let sprites_pipeline_state = build_pipeline_state( + let monochrome_sprites_pipeline_state = build_pipeline_state( &device, &library, - "sprites", + "monochrome_sprites", "monochrome_sprite_vertex", "monochrome_sprite_fragment", 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 monochrome_sprite_atlas = Arc::new(MetalAtlas::new( - 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(), - )); + let sprite_atlas = Arc::new(MetalAtlas::new(device.clone())); Self { layer, command_queue, quads_pipeline_state, - sprites_pipeline_state, + monochrome_sprites_pipeline_state, + polychrome_sprites_pipeline_state, unit_vertices, instances, - monochrome_sprite_atlas, - polychrome_sprite_atlas, + sprite_atlas, } } @@ -134,12 +126,8 @@ impl MetalRenderer { &*self.layer } - pub fn monochrome_sprite_atlas(&self) -> &Arc { - &self.monochrome_sprite_atlas - } - - pub fn polychrome_sprite_atlas(&self) -> &Arc { - &self.polychrome_sprite_atlas + pub fn sprite_atlas(&self) -> &Arc { + &self.sprite_atlas } pub fn draw(&mut self, scene: &mut Scene) { @@ -304,41 +292,38 @@ impl MetalRenderer { } align_offset(offset); - let texture = self.monochrome_sprite_atlas.texture(texture_id); + let texture = self.sprite_atlas.texture(texture_id); let texture_size = size( DevicePixels(texture.width() 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( - MonochromeSpriteInputIndex::Vertices as u64, + SpriteInputIndex::Vertices as u64, Some(&self.unit_vertices), 0, ); command_encoder.set_vertex_buffer( - MonochromeSpriteInputIndex::Sprites as u64, + SpriteInputIndex::Sprites as u64, Some(&self.instances), *offset as u64, ); command_encoder.set_vertex_bytes( - MonochromeSpriteInputIndex::ViewportSize as u64, + SpriteInputIndex::ViewportSize as u64, mem::size_of_val(&viewport_size) as u64, &viewport_size as *const Size as *const _, ); command_encoder.set_vertex_bytes( - MonochromeSpriteInputIndex::AtlasTextureSize as u64, + SpriteInputIndex::AtlasTextureSize as u64, mem::size_of_val(&texture_size) as u64, &texture_size as *const Size as *const _, ); command_encoder.set_fragment_buffer( - MonochromeSpriteInputIndex::Sprites as u64, + SpriteInputIndex::Sprites as u64, Some(&self.instances), *offset as u64, ); - command_encoder.set_fragment_texture( - MonochromeSpriteInputIndex::AtlasTexture as u64, - Some(&texture), - ); + command_encoder.set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture)); let sprite_bytes_len = mem::size_of::() * sprites.len(); let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; @@ -373,71 +358,67 @@ impl MetalRenderer { viewport_size: Size, command_encoder: &metal::RenderCommandEncoderRef, ) { - todo!() - // if sprites.is_empty() { - // return; - // } - // align_offset(offset); + if sprites.is_empty() { + return; + } + align_offset(offset); - // let texture = self.monochrome_sprite_atlas.texture(texture_id); - // let texture_size = size( - // DevicePixels(texture.width() as i32), - // DevicePixels(texture.height() as i32), - // ); - // command_encoder.set_render_pipeline_state(&self.sprites_pipeline_state); - // command_encoder.set_vertex_buffer( - // MonochromeSpriteInputIndex::Vertices as u64, - // Some(&self.unit_vertices), - // 0, - // ); - // command_encoder.set_vertex_buffer( - // MonochromeSpriteInputIndex::Sprites as u64, - // Some(&self.instances), - // *offset as u64, - // ); - // command_encoder.set_vertex_bytes( - // MonochromeSpriteInputIndex::ViewportSize as u64, - // mem::size_of_val(&viewport_size) as u64, - // &viewport_size as *const Size as *const _, - // ); - // command_encoder.set_vertex_bytes( - // MonochromeSpriteInputIndex::AtlasTextureSize as u64, - // mem::size_of_val(&texture_size) as u64, - // &texture_size as *const Size as *const _, - // ); - // command_encoder.set_fragment_buffer( - // MonochromeSpriteInputIndex::Sprites as u64, - // Some(&self.instances), - // *offset as u64, - // ); - // command_encoder.set_fragment_texture( - // MonochromeSpriteInputIndex::AtlasTexture as u64, - // Some(&texture), - // ); + let texture = self.sprite_atlas.texture(texture_id); + let texture_size = size( + DevicePixels(texture.width() as i32), + DevicePixels(texture.height() as i32), + ); + command_encoder.set_render_pipeline_state(&self.polychrome_sprites_pipeline_state); + command_encoder.set_vertex_buffer( + SpriteInputIndex::Vertices as u64, + Some(&self.unit_vertices), + 0, + ); + command_encoder.set_vertex_buffer( + SpriteInputIndex::Sprites as u64, + Some(&self.instances), + *offset as u64, + ); + command_encoder.set_vertex_bytes( + SpriteInputIndex::ViewportSize as u64, + mem::size_of_val(&viewport_size) as u64, + &viewport_size as *const Size as *const _, + ); + command_encoder.set_vertex_bytes( + SpriteInputIndex::AtlasTextureSize as u64, + mem::size_of_val(&texture_size) as u64, + &texture_size as *const Size 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::() * 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 sprite_bytes_len = mem::size_of::() * 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" - // ); + 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; + command_encoder.draw_primitives_instanced( + metal::MTLPrimitiveType::Triangle, + 0, + 6, + sprites.len() as u64, + ); + *offset = next_offset; } } @@ -489,7 +470,7 @@ enum QuadInputIndex { } #[repr(C)] -enum MonochromeSpriteInputIndex { +enum SpriteInputIndex { Vertices = 0, Sprites = 1, ViewportSize = 2, diff --git a/crates/gpui3/src/platform/mac/shaders.metal b/crates/gpui3/src/platform/mac/shaders.metal index 3de028deee..6eeb7f718a 100644 --- a/crates/gpui3/src/platform/mac/shaders.metal +++ b/crates/gpui3/src/platform/mac/shaders.metal @@ -7,7 +7,10 @@ float4 hsla_to_rgba(Hsla hsla); float4 to_device_position(float2 unit_vertex, Bounds_ScaledPixels bounds, Bounds_ScaledPixels clip_bounds, 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 { float4 position [[position]]; @@ -119,27 +122,18 @@ struct MonochromeSpriteVertexOutput { vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex( uint unit_vertex_id [[vertex_id]], uint sprite_id [[instance_id]], - constant float2 *unit_vertices - [[buffer(MonochromeSpriteInputIndex_Vertices)]], - constant MonochromeSprite *sprites - [[buffer(MonochromeSpriteInputIndex_Sprites)]], + constant float2 *unit_vertices [[buffer(SpriteInputIndex_Vertices)]], + constant MonochromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]], constant Size_DevicePixels *viewport_size - [[buffer(MonochromeSpriteInputIndex_ViewportSize)]], + [[buffer(SpriteInputIndex_ViewportSize)]], constant Size_DevicePixels *atlas_size - [[buffer(MonochromeSpriteInputIndex_AtlasTextureSize)]]) { + [[buffer(SpriteInputIndex_AtlasTextureSize)]]) { float2 unit_vertex = unit_vertices[unit_vertex_id]; MonochromeSprite sprite = sprites[sprite_id]; float4 device_position = to_device_position( unit_vertex, sprite.bounds, sprite.content_mask.bounds, viewport_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); + float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size); float4 color = hsla_to_rgba(sprite.color); return MonochromeSpriteVertexOutput{device_position, tile_position, color, sprite_id}; @@ -147,22 +141,60 @@ vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex( fragment float4 monochrome_sprite_fragment( MonochromeSpriteVertexOutput input [[stage_in]], - constant MonochromeSprite *sprites - [[buffer(MonochromeSpriteInputIndex_Sprites)]], - texture2d atlas_texture - [[texture(MonochromeSpriteInputIndex_AtlasTexture)]]) { + constant MonochromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]], + texture2d atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) { MonochromeSprite 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); + float clip_distance = quad_sdf(input.position.xy, sprite.content_mask.bounds, + sprite.content_mask.corner_radii); float4 color = input.color; color.a *= sample.a * saturate(0.5 - clip_distance); 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 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) { float h = hsla.h * 6.0; // Now, it's an angle but scaled in [0, 6) range float s = hsla.s; @@ -229,6 +261,14 @@ float4 to_device_position(float2 unit_vertex, Bounds_ScaledPixels bounds, 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, Corners_ScaledPixels corner_radii) { float2 half_size = float2(bounds.size.width, bounds.size.height) / 2.; diff --git a/crates/gpui3/src/platform/mac/text_system.rs b/crates/gpui3/src/platform/mac/text_system.rs index 7dd13d52fb..0320f91386 100644 --- a/crates/gpui3/src/platform/mac/text_system.rs +++ b/crates/gpui3/src/platform/mac/text_system.rs @@ -12,7 +12,11 @@ use core_foundation::{ base::{CFRange, TCFType}, 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 font_kit::{ font::Font as FontKitFont, @@ -262,16 +266,31 @@ impl MacTextSystemState { bitmap_size.height += DevicePixels(1); } - let mut bytes = vec![0; bitmap_size.width.0 as usize * bitmap_size.height.0 as usize]; - let 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, - &CGColorSpace::create_device_gray(), - kCGImageAlphaOnly, - ); + let mut bytes; + 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 _), + bitmap_size.width.0 as usize, + bitmap_size.height.0 as usize, + 8, + bitmap_size.width.0 as usize, + &CGColorSpace::create_device_gray(), + kCGImageAlphaOnly, + ); + } // Move the origin to bottom left and account for scaling, this // makes drawing text consistent with the font-kit's raster_bounds. @@ -303,6 +322,17 @@ impl MacTextSystemState { 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)) } } diff --git a/crates/gpui3/src/platform/mac/window.rs b/crates/gpui3/src/platform/mac/window.rs index 4cfbe2ef36..6d52e44316 100644 --- a/crates/gpui3/src/platform/mac/window.rs +++ b/crates/gpui3/src/platform/mac/window.rs @@ -886,12 +886,8 @@ impl PlatformWindow for MacWindow { } } - fn monochrome_sprite_atlas(&self) -> Arc { - self.0.lock().renderer.monochrome_sprite_atlas().clone() - } - - fn polychrome_sprite_atlas(&self) -> Arc { - self.0.lock().renderer.polychrome_sprite_atlas().clone() + fn sprite_atlas(&self) -> Arc { + self.0.lock().renderer.sprite_atlas().clone() } } diff --git a/crates/gpui3/src/scene.rs b/crates/gpui3/src/scene.rs index 0918b28f3c..4741f3ead8 100644 --- a/crates/gpui3/src/scene.rs +++ b/crates/gpui3/src/scene.rs @@ -1,4 +1,4 @@ -use std::{iter::Peekable, mem}; +use std::{iter::Peekable, mem, slice}; use super::{Bounds, Hsla, Point}; use crate::{AtlasTextureId, AtlasTile, Corners, Edges, ScaledContentMask, ScaledPixels}; @@ -63,42 +63,46 @@ impl SceneLayer { pub fn batches(&mut self) -> impl Iterator { self.quads.sort_unstable(); self.monochrome_sprites.sort_unstable(); - - BatchIterator::new( - &self.quads, - self.quads.iter().peekable(), - &self.monochrome_sprites, - self.monochrome_sprites.iter().peekable(), - ) + self.polychrome_sprites.sort_unstable(); + BatchIterator { + quads: &self.quads, + quads_start: 0, + quads_iter: self.quads.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> -where - Q: Iterator, - S: Iterator, -{ +struct BatchIterator<'a> { quads: &'a [Quad], - sprites: &'a [MonochromeSprite], quads_start: usize, - sprites_start: usize, - quads_iter: Peekable, - sprites_iter: Peekable, + quads_iter: Peekable>, + monochrome_sprites: &'a [MonochromeSprite], + monochrome_sprites_start: usize, + monochrome_sprites_iter: Peekable>, + polychrome_sprites: &'a [PolychromeSprite], + polychrome_sprites_start: usize, + polychrome_sprites_iter: Peekable>, } -impl<'a, Q: 'a, S: 'a> Iterator for BatchIterator<'a, Q, S> -where - Q: Iterator, - S: Iterator, -{ +impl<'a> Iterator for BatchIterator<'a> { type Item = PrimitiveBatch<'a>; fn next(&mut self) -> Option { let mut kinds_and_orders = [ (PrimitiveKind::Quad, self.quads_iter.peek().map(|q| q.order)), ( - PrimitiveKind::Sprite, - self.sprites_iter.peek().map(|s| s.order), + PrimitiveKind::MonochromeSprite, + 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)); @@ -123,45 +127,40 @@ where self.quads_start = quads_end; Some(PrimitiveBatch::Quads(&self.quads[quads_start..quads_end])) } - PrimitiveKind::Sprite => { - let texture_id = self.sprites_iter.peek().unwrap().tile.texture_id; - let sprites_start = self.sprites_start; + PrimitiveKind::MonochromeSprite => { + let texture_id = self.monochrome_sprites_iter.peek().unwrap().tile.texture_id; + let sprites_start = self.monochrome_sprites_start; let sprites_end = sprites_start + self - .sprites_iter + .monochrome_sprites_iter .by_ref() .take_while(|sprite| { sprite.order <= max_order && sprite.tile.texture_id == texture_id }) .count(); - self.sprites_start = sprites_end; + self.monochrome_sprites_start = sprites_end; Some(PrimitiveBatch::MonochromeSprites { 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, - S: Iterator, -{ - fn new( - quads: &'a [Quad], - quads_iter: Peekable, - sprites: &'a [MonochromeSprite], - sprites_iter: Peekable, - ) -> Self { - Self { - quads, - quads_start: 0, - quads_iter, - sprites, - sprites_start: 0, - sprites_iter, } } } @@ -169,7 +168,8 @@ where #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum PrimitiveKind { Quad, - Sprite, + MonochromeSprite, + PolychromeSprite, } #[derive(Clone, Debug)] diff --git a/crates/gpui3/src/text_system.rs b/crates/gpui3/src/text_system.rs index fb8885ec00..f42a884fe0 100644 --- a/crates/gpui3/src/text_system.rs +++ b/crates/gpui3/src/text_system.rs @@ -36,8 +36,7 @@ pub struct TextSystem { text_layout_cache: Arc, platform_text_system: Arc, font_ids_by_font: RwLock>, - fonts_by_font_id: RwLock>, - font_metrics: RwLock>, + font_metrics: RwLock>, wrapper_pool: Mutex>>, font_runs_pool: Mutex>>, } @@ -49,7 +48,6 @@ impl TextSystem { platform_text_system, font_metrics: 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()), font_runs_pool: Default::default(), } @@ -62,30 +60,20 @@ impl TextSystem { } else { let font_id = self.platform_text_system.font_id(font)?; 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) } } - pub fn with_font(&self, font_id: FontId, f: impl FnOnce(&Self, &Font) -> T) -> Result { - self.fonts_by_font_id - .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> { - self.read_metrics(&font, |metrics| metrics.bounding_box(font_size)) + pub fn bounding_box(&self, font_id: FontId, font_size: Pixels) -> Result> { + self.read_metrics(font_id, |metrics| metrics.bounding_box(font_size)) } pub fn typographic_bounds( &self, - font: &Font, + font_id: FontId, font_size: Pixels, character: char, ) -> Result> { - let font_id = self.font_id(font)?; let glyph_id = self .platform_text_system .glyph_for_char(font_id, character) @@ -93,65 +81,63 @@ impl TextSystem { let bounds = self .platform_text_system .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) }) } - pub fn advance(&self, font: &Font, font_size: Pixels, ch: char) -> Result> { - let font_id = self.font_id(font)?; + pub fn advance(&self, font_id: FontId, font_size: Pixels, ch: char) -> Result> { let glyph_id = self .platform_text_system .glyph_for_char(font_id, ch) .ok_or_else(|| anyhow!("glyph not found for character '{}'", ch))?; - let result = - self.platform_text_system.advance(font_id, glyph_id)? / self.units_per_em(font)? as f32; + let result = self.platform_text_system.advance(font_id, glyph_id)? + / self.units_per_em(font_id)? as f32; Ok(result * font_size) } - pub fn units_per_em(&self, font: &Font) -> Result { - self.read_metrics(font, |metrics| metrics.units_per_em as u32) + pub fn units_per_em(&self, font_id: FontId) -> Result { + self.read_metrics(font_id, |metrics| metrics.units_per_em as u32) } - pub fn cap_height(&self, font: &Font, font_size: Pixels) -> Result { - self.read_metrics(font, |metrics| metrics.cap_height(font_size)) + pub fn cap_height(&self, font_id: FontId, font_size: Pixels) -> Result { + self.read_metrics(font_id, |metrics| metrics.cap_height(font_size)) } - pub fn x_height(&self, font: &Font, font_size: Pixels) -> Result { - self.read_metrics(font, |metrics| metrics.x_height(font_size)) + pub fn x_height(&self, font_id: FontId, font_size: Pixels) -> Result { + self.read_metrics(font_id, |metrics| metrics.x_height(font_size)) } - pub fn ascent(&self, font: &Font, font_size: Pixels) -> Result { - self.read_metrics(font, |metrics| metrics.ascent(font_size)) + pub fn ascent(&self, font_id: FontId, font_size: Pixels) -> Result { + self.read_metrics(font_id, |metrics| metrics.ascent(font_size)) } - pub fn descent(&self, font: &Font, font_size: Pixels) -> Result { - self.read_metrics(font, |metrics| metrics.descent(font_size)) + pub fn descent(&self, font_id: FontId, font_size: Pixels) -> Result { + self.read_metrics(font_id, |metrics| metrics.descent(font_size)) } pub fn baseline_offset( &self, - font: &Font, + font_id: FontId, font_size: Pixels, line_height: Pixels, ) -> Result { - let ascent = self.ascent(font, font_size)?; - let descent = self.descent(font, font_size)?; + let ascent = self.ascent(font_id, font_size)?; + let descent = self.descent(font_id, font_size)?; let padding_top = (line_height - ascent - descent) / 2.; Ok(padding_top + ascent) } - fn read_metrics(&self, font: &Font, read: impl FnOnce(&FontMetrics) -> T) -> Result { + fn read_metrics(&self, font_id: FontId, read: impl FnOnce(&FontMetrics) -> T) -> Result { 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)) } else { - let font_id = self.platform_text_system.font_id(&font)?; let mut lock = RwLockUpgradableReadGuard::upgrade(lock); let metrics = lock - .entry(font.clone()) + .entry(font_id) .or_insert_with(|| self.platform_text_system.font_metrics(font_id)); Ok(read(metrics)) } @@ -390,6 +376,7 @@ pub struct RenderGlyphParams { pub(crate) font_size: Pixels, pub(crate) subpixel_variant: Point, pub(crate) scale_factor: f32, + pub(crate) is_emoji: bool, } impl Eq for RenderGlyphParams {} diff --git a/crates/gpui3/src/text_system/line.rs b/crates/gpui3/src/text_system/line.rs index e4c81531a8..10b10d041e 100644 --- a/crates/gpui3/src/text_system/line.rs +++ b/crates/gpui3/src/text_system/line.rs @@ -109,77 +109,74 @@ impl Line { let text_system = cx.text_system().clone(); for run in &self.layout.runs { - text_system.with_font(run.font_id, |system, font| { - let max_glyph_width = system.bounding_box(font, self.layout.font_size)?.size.width; + let max_glyph_width = text_system + .bounding_box(run.font_id, self.layout.font_size)? + .size + .width; - for glyph in &run.glyphs { - let glyph_origin = origin + baseline_offset + glyph.position; - if glyph_origin.x > visible_bounds.upper_right().x { - break; - } + for glyph in &run.glyphs { + let glyph_origin = origin + baseline_offset + glyph.position; + if glyph_origin.x > visible_bounds.upper_right().x { + break; + } - let mut finished_underline: Option<(Point, UnderlineStyle)> = None; - if glyph.index >= run_end { - if let Some(style_run) = style_runs.next() { - if let Some((_, underline_style)) = &mut underline { - if style_run.underline != *underline_style { - finished_underline = underline.take(); - } + let mut finished_underline: Option<(Point, UnderlineStyle)> = None; + if glyph.index >= run_end { + if let Some(style_run) = style_runs.next() { + if let Some((_, underline_style)) = &mut underline { + if style_run.underline != *underline_style { + finished_underline = underline.take(); } - if style_run.underline.thickness > px(0.) { - underline.get_or_insert(( - point( - glyph_origin.x, - origin.y - + baseline_offset.y - + (self.layout.descent * 0.618), - ), - UnderlineStyle { - color: style_run.underline.color, - thickness: style_run.underline.thickness, - squiggly: style_run.underline.squiggly, - }, - )); - } - - run_end += style_run.len as usize; - color = style_run.color; - } else { - run_end = self.layout.len; - finished_underline = underline.take(); } - } + if style_run.underline.thickness > px(0.) { + underline.get_or_insert(( + point( + glyph_origin.x, + origin.y + baseline_offset.y + (self.layout.descent * 0.618), + ), + UnderlineStyle { + color: style_run.underline.color, + thickness: style_run.underline.thickness, + squiggly: style_run.underline.squiggly, + }, + )); + } - if glyph_origin.x + max_glyph_width < visible_bounds.origin.x { - continue; - } - - if let Some((_underline_origin, _underline_style)) = finished_underline { - todo!() - } - - if glyph.is_emoji { - cx.paint_emoji( - glyph_origin, - layout.order, - run.font_id, - glyph.id, - self.layout.font_size, - )?; + run_end += style_run.len as usize; + color = style_run.color; } else { - cx.paint_glyph( - glyph_origin, - layout.order, - run.font_id, - glyph.id, - self.layout.font_size, - color, - )?; + run_end = self.layout.len; + finished_underline = underline.take(); } } - anyhow::Ok(()) - })??; + if glyph_origin.x + max_glyph_width < visible_bounds.origin.x { + continue; + } + + if let Some((_underline_origin, _underline_style)) = finished_underline { + todo!() + } + + if glyph.is_emoji { + cx.paint_emoji( + glyph_origin, + layout.order, + run.font_id, + glyph.id, + self.layout.font_size, + )?; + } else { + cx.paint_glyph( + glyph_origin, + layout.order, + run.font_id, + glyph.id, + self.layout.font_size, + color, + )?; + } + } } if let Some((_underline_start, _underline_style)) = underline.take() { @@ -281,31 +278,31 @@ impl Line { // }); } - cx.text_system().with_font(run.font_id, |system, font| { - let _glyph_bounds = Bounds { - origin: glyph_origin, - size: system.bounding_box(font, self.layout.font_size)?.size, - }; - // if glyph_bounds.intersects(visible_bounds) { - // if glyph.is_emoji { - // cx.scene().push_image_glyph(scene::ImageGlyph { - // font_id: run.font_id, - // font_size: self.layout.font_size, - // id: glyph.id, - // origin: glyph_bounds.origin() + baseline_offset, - // }); - // } else { - // cx.scene().push_glyph(scene::Glyph { - // font_id: run.font_id, - // font_size: self.layout.font_size, - // id: glyph.id, - // origin: glyph_bounds.origin() + baseline_offset, - // color, - // }); - // } - // } - anyhow::Ok(()) - })??; + let text_system = cx.text_system(); + let _glyph_bounds = Bounds { + origin: glyph_origin, + size: text_system + .bounding_box(run.font_id, self.layout.font_size)? + .size, + }; + // if glyph_bounds.intersects(visible_bounds) { + // if glyph.is_emoji { + // cx.scene().push_image_glyph(scene::ImageGlyph { + // font_id: run.font_id, + // font_size: self.layout.font_size, + // id: glyph.id, + // origin: glyph_bounds.origin() + baseline_offset, + // }); + // } else { + // cx.scene().push_glyph(scene::Glyph { + // font_id: run.font_id, + // font_size: self.layout.font_size, + // id: glyph.id, + // origin: glyph_bounds.origin() + baseline_offset, + // color, + // }); + // } + // } } } diff --git a/crates/gpui3/src/window.rs b/crates/gpui3/src/window.rs index 97297ecd03..180bb896fd 100644 --- a/crates/gpui3/src/window.rs +++ b/crates/gpui3/src/window.rs @@ -16,8 +16,7 @@ pub struct AnyWindow {} pub struct Window { handle: AnyWindowHandle, platform_window: MainThreadOnly>, - monochrome_sprite_atlas: Arc, - polychrome_sprite_atlas: Arc, + sprite_atlas: Arc, rem_size: Pixels, content_size: Size, layout_engine: TaffyLayoutEngine, @@ -36,8 +35,7 @@ impl Window { cx: &mut MainThread, ) -> Self { let platform_window = cx.platform().open_window(handle, options); - let monochrome_sprite_atlas = platform_window.monochrome_sprite_atlas(); - let polychrome_sprite_atlas = platform_window.polychrome_sprite_atlas(); + let sprite_atlas = platform_window.sprite_atlas(); let mouse_position = platform_window.mouse_position(); let content_size = platform_window.content_size(); let scale_factor = platform_window.scale_factor(); @@ -60,8 +58,7 @@ impl Window { Window { handle, platform_window, - monochrome_sprite_atlas, - polychrome_sprite_atlas, + sprite_atlas, rem_size: px(16.), content_size, layout_engine: TaffyLayoutEngine::new(), @@ -231,6 +228,7 @@ impl<'a, 'w> WindowContext<'a, 'w> { font_size, subpixel_variant, scale_factor, + is_emoji: false, }; let raster_bounds = self.text_system().raster_bounds(¶ms)?; @@ -238,7 +236,7 @@ impl<'a, 'w> WindowContext<'a, 'w> { let layer_id = self.current_layer_id(); let tile = self .window - .monochrome_sprite_atlas + .sprite_atlas .get_or_insert_with(¶ms.clone().into(), &mut || { self.text_system().rasterize_glyph(¶ms) })?; @@ -262,6 +260,54 @@ impl<'a, 'w> WindowContext<'a, 'w> { Ok(()) } + pub fn paint_emoji( + &mut self, + origin: Point, + 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(¶ms)?; + if !raster_bounds.is_zero() { + let layer_id = self.current_layer_id(); + let tile = self + .window + .sprite_atlas + .get_or_insert_with(¶ms.clone().into(), &mut || { + self.text_system().rasterize_glyph(¶ms) + })?; + 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( &mut self, bounds: Bounds, @@ -280,13 +326,13 @@ impl<'a, 'w> WindowContext<'a, 'w> { }; let layer_id = self.current_layer_id(); - let tile = self.window.monochrome_sprite_atlas.get_or_insert_with( - ¶ms.clone().into(), - &mut || { - let bytes = self.svg_renderer.render(¶ms)?; - Ok((params.size, bytes)) - }, - )?; + let tile = + self.window + .sprite_atlas + .get_or_insert_with(¶ms.clone().into(), &mut || { + let bytes = self.svg_renderer.render(¶ms)?; + Ok((params.size, bytes)) + })?; let content_mask = self.content_mask().scale(scale_factor); self.window.scene.insert( @@ -303,52 +349,6 @@ impl<'a, 'w> WindowContext<'a, 'w> { Ok(()) } - pub fn paint_emoji( - &mut self, - origin: Point, - 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(¶ms)?; - if !raster_bounds.is_zero() { - let layer_id = self.current_layer_id(); - let tile = self - .window - .polychrome_sprite_atlas - .get_or_insert_with(¶ms.clone().into(), &mut || { - self.text_system().rasterize_glyph(¶ms) - })?; - 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<()> { let unit_entity = self.unit_entity.clone(); self.update_entity(&unit_entity, |_, cx| { diff --git a/crates/storybook2/src/collab_panel.rs b/crates/storybook2/src/collab_panel.rs index 0f4b2f4971..73373f8c03 100644 --- a/crates/storybook2/src/collab_panel.rs +++ b/crates/storybook2/src/collab_panel.rs @@ -51,7 +51,7 @@ impl CollabPanel { //:: https://tailwindcss.com/docs/hover-focus-and-other-states#styling-based-on-parent-state // .group() // List Section Header - .child(self.list_section_header("#CRDB", true, theme)) + .child(self.list_section_header("#CRDB 🗃️", true, theme)) // List Item Large .child(self.list_item( "http://github.com/maxbrunsfeld.png?s=50",