mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-23 18:32:17 +00:00
WIP
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
parent
247afa1666
commit
ad7974608b
14 changed files with 311 additions and 117 deletions
47
Cargo.lock
generated
47
Cargo.lock
generated
|
@ -1,5 +1,11 @@
|
||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
|
[[package]]
|
||||||
|
name = "adler32"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "0.7.15"
|
version = "0.7.15"
|
||||||
|
@ -424,6 +430,15 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crc32fast"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 1.0.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-channel"
|
name = "crossbeam-channel"
|
||||||
version = "0.4.4"
|
version = "0.4.4"
|
||||||
|
@ -477,6 +492,16 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deflate"
|
||||||
|
version = "0.8.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174"
|
||||||
|
dependencies = [
|
||||||
|
"adler32",
|
||||||
|
"byteorder",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dirs"
|
name = "dirs"
|
||||||
version = "3.0.1"
|
version = "3.0.1"
|
||||||
|
@ -776,6 +801,7 @@ dependencies = [
|
||||||
"pathfinder_color",
|
"pathfinder_color",
|
||||||
"pathfinder_geometry",
|
"pathfinder_geometry",
|
||||||
"pin-project",
|
"pin-project",
|
||||||
|
"png",
|
||||||
"rand 0.8.3",
|
"rand 0.8.3",
|
||||||
"replace_with",
|
"replace_with",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
|
@ -923,6 +949,15 @@ dependencies = [
|
||||||
"objc",
|
"objc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miniz_oxide"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435"
|
||||||
|
dependencies = [
|
||||||
|
"adler32",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nb-connect"
|
name = "nb-connect"
|
||||||
version = "1.0.3"
|
version = "1.0.3"
|
||||||
|
@ -1103,6 +1138,18 @@ version = "0.3.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
|
checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "png"
|
||||||
|
version = "0.16.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"crc32fast",
|
||||||
|
"deflate",
|
||||||
|
"miniz_oxide",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "polling"
|
name = "polling"
|
||||||
version = "2.0.2"
|
version = "2.0.2"
|
||||||
|
|
|
@ -14,6 +14,7 @@ parking_lot = "0.11.1"
|
||||||
pathfinder_color = "0.5"
|
pathfinder_color = "0.5"
|
||||||
pathfinder_geometry = "0.5"
|
pathfinder_geometry = "0.5"
|
||||||
pin-project = "1.0.5"
|
pin-project = "1.0.5"
|
||||||
|
png = "0.16"
|
||||||
rand = "0.8.3"
|
rand = "0.8.3"
|
||||||
replace_with = "0.1.7"
|
replace_with = "0.1.7"
|
||||||
smallvec = "1.6.1"
|
smallvec = "1.6.1"
|
||||||
|
|
|
@ -89,7 +89,8 @@ fn generate_shader_bindings() {
|
||||||
.whitelist_type("GPUIQuad")
|
.whitelist_type("GPUIQuad")
|
||||||
.whitelist_type("GPUIShadowInputIndex")
|
.whitelist_type("GPUIShadowInputIndex")
|
||||||
.whitelist_type("GPUIShadow")
|
.whitelist_type("GPUIShadow")
|
||||||
.whitelist_type("GPUISpriteInputIndex")
|
.whitelist_type("GPUISpriteVertexInputIndex")
|
||||||
|
.whitelist_type("GPUISpriteFragmentInputIndex")
|
||||||
.whitelist_type("GPUISprite")
|
.whitelist_type("GPUISprite")
|
||||||
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
|
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
|
||||||
.generate()
|
.generate()
|
||||||
|
|
|
@ -620,6 +620,7 @@ impl MutableAppContext {
|
||||||
title: "Zed".into(),
|
title: "Zed".into(),
|
||||||
},
|
},
|
||||||
self.foreground.clone(),
|
self.foreground.clone(),
|
||||||
|
self.fonts.clone(),
|
||||||
) {
|
) {
|
||||||
Err(e) => log::error!("error opening window: {}", e),
|
Err(e) => log::error!("error opening window: {}", e),
|
||||||
Ok(mut window) => {
|
Ok(mut window) => {
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
use crate::geometry::vector::{vec2f, Vector2F};
|
use crate::geometry::{
|
||||||
|
rect::RectI,
|
||||||
|
vector::{vec2f, Vector2F, Vector2I},
|
||||||
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
|
use cocoa::appkit::CGPoint;
|
||||||
|
use core_graphics::{base::CGGlyph, color_space::CGColorSpace, context::CGContext};
|
||||||
use parking_lot::{RwLock, RwLockUpgradableReadGuard};
|
use parking_lot::{RwLock, RwLockUpgradableReadGuard};
|
||||||
|
|
||||||
pub use font_kit::properties::{Properties, Weight};
|
pub use font_kit::properties::{Properties, Weight};
|
||||||
|
@ -9,6 +14,9 @@ use font_kit::{
|
||||||
use ordered_float::OrderedFloat;
|
use ordered_float::OrderedFloat;
|
||||||
use std::{collections::HashMap, sync::Arc};
|
use std::{collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
|
#[allow(non_upper_case_globals)]
|
||||||
|
const kCGImageAlphaOnly: u32 = 7;
|
||||||
|
|
||||||
pub type GlyphId = u32;
|
pub type GlyphId = u32;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
|
@ -179,47 +187,43 @@ impl FontCache {
|
||||||
self.scale_metric(self.metric(font_id, |m| m.descent), font_id, font_size)
|
self.scale_metric(self.metric(font_id, |m| m.descent), font_id, font_size)
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub fn render_emoji(&self, glyph_id: GlyphId, font_size: f32) -> Result<Pattern> {
|
pub fn render_glyph(
|
||||||
// let key = (glyph_id, OrderedFloat(font_size));
|
&self,
|
||||||
|
font_id: FontId,
|
||||||
|
font_size: f32,
|
||||||
|
glyph_id: GlyphId,
|
||||||
|
scale_factor: f32,
|
||||||
|
) -> Option<(Vector2I, Vec<u8>)> {
|
||||||
|
let native_font = self.native_font(font_id, font_size);
|
||||||
|
let glyph_id = glyph_id as CGGlyph;
|
||||||
|
let glyph_bounds =
|
||||||
|
native_font.get_bounding_rects_for_glyphs(Default::default(), &[glyph_id]);
|
||||||
|
let position = CGPoint::new(-glyph_bounds.origin.x, -glyph_bounds.origin.y);
|
||||||
|
let width = (glyph_bounds.size.width * scale_factor as f64).ceil() as usize;
|
||||||
|
let height = (glyph_bounds.size.height * scale_factor as f64).ceil() as usize;
|
||||||
|
|
||||||
// {
|
if width == 0 || height == 0 {
|
||||||
// if let Some(image) = self.0.read().emoji_images.get(&key) {
|
None
|
||||||
// return Ok(image.clone());
|
} else {
|
||||||
// }
|
let mut ctx = CGContext::create_bitmap_context(
|
||||||
// }
|
None,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
8,
|
||||||
|
width,
|
||||||
|
&CGColorSpace::create_device_gray(),
|
||||||
|
kCGImageAlphaOnly,
|
||||||
|
);
|
||||||
|
ctx.scale(scale_factor as f64, scale_factor as f64);
|
||||||
|
native_font.draw_glyphs(&[glyph_id], &[position], ctx.clone());
|
||||||
|
ctx.flush();
|
||||||
|
|
||||||
// let font_id = self.emoji_font_id()?;
|
Some((
|
||||||
// let bounding_box = self.bounding_box(font_id, font_size);
|
Vector2I::new(width as i32, height as i32),
|
||||||
// let width = (4.0 * bounding_box.x()) as usize;
|
Vec::from(ctx.data()),
|
||||||
// let height = (4.0 * bounding_box.y()) as usize;
|
))
|
||||||
// let mut ctx = CGContext::create_bitmap_context(
|
}
|
||||||
// None,
|
}
|
||||||
// width,
|
|
||||||
// height,
|
|
||||||
// 8,
|
|
||||||
// width * 4,
|
|
||||||
// &CGColorSpace::create_device_rgb(),
|
|
||||||
// kCGImageAlphaPremultipliedLast | kCGBitmapByteOrderDefault,
|
|
||||||
// );
|
|
||||||
// ctx.scale(4.0, 4.0);
|
|
||||||
|
|
||||||
// let native_font = self.native_font(font_id, font_size);
|
|
||||||
// let glyph = glyph_id.0 as CGGlyph;
|
|
||||||
// let glyph_bounds = native_font.get_bounding_rects_for_glyphs(Default::default(), &[glyph]);
|
|
||||||
// let position = CGPoint::new(glyph_bounds.origin.x, -glyph_bounds.origin.y);
|
|
||||||
|
|
||||||
// native_font.draw_glyphs(&[glyph], &[position], ctx.clone());
|
|
||||||
|
|
||||||
// ctx.flush();
|
|
||||||
|
|
||||||
// let image = Pattern::from_image(Image::new(
|
|
||||||
// vec2i(ctx.width() as i32, ctx.height() as i32),
|
|
||||||
// Arc::new(u8_slice_to_color_slice(&ctx.data()).into()),
|
|
||||||
// ));
|
|
||||||
// self.0.write().emoji_images.insert(key, image.clone());
|
|
||||||
|
|
||||||
// Ok(image)
|
|
||||||
// }
|
|
||||||
|
|
||||||
fn emoji_font_id(&self) -> Result<FontId> {
|
fn emoji_font_id(&self) -> Result<FontId> {
|
||||||
let state = self.0.upgradable_read();
|
let state = self.0.upgradable_read();
|
||||||
|
@ -285,3 +289,31 @@ fn push_font(state: &mut FontCacheState, font: Font) -> FontId {
|
||||||
state.fonts_by_name.insert(name, font_id);
|
state.fonts_by_name.insert(name, font_id);
|
||||||
font_id
|
font_id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::{fs::File, io::BufWriter, path::Path};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_render_glyph() {
|
||||||
|
let cache = FontCache::new();
|
||||||
|
let family_id = cache.load_family(&["Fira Code"]).unwrap();
|
||||||
|
let font_id = cache.select_font(family_id, &Default::default()).unwrap();
|
||||||
|
let glyph_id = cache.font(font_id).glyph_for_char('m').unwrap();
|
||||||
|
let (size, bytes) = cache.render_glyph(font_id, 16.0, glyph_id, 1.).unwrap();
|
||||||
|
|
||||||
|
let path = Path::new(r"/Users/as-cii/Desktop/image.png");
|
||||||
|
let file = File::create(path).unwrap();
|
||||||
|
let ref mut w = BufWriter::new(file);
|
||||||
|
|
||||||
|
let mut encoder = png::Encoder::new(w, size.x() as u32, size.y() as u32);
|
||||||
|
encoder.set_color(png::ColorType::Grayscale);
|
||||||
|
encoder.set_depth(png::BitDepth::Eight);
|
||||||
|
let mut writer = encoder.write_header().unwrap();
|
||||||
|
|
||||||
|
writer.write_image_data(&bytes).unwrap(); // Save
|
||||||
|
dbg!(size, bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use super::{BoolExt as _, Dispatcher, Window};
|
use super::{BoolExt as _, Dispatcher, Window};
|
||||||
use crate::{executor, platform};
|
use crate::{executor, platform, FontCache};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use cocoa::base::id;
|
use cocoa::base::id;
|
||||||
use objc::{class, msg_send, sel, sel_impl};
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
|
@ -33,7 +33,8 @@ impl platform::App for App {
|
||||||
&self,
|
&self,
|
||||||
options: platform::WindowOptions,
|
options: platform::WindowOptions,
|
||||||
executor: Rc<executor::Foreground>,
|
executor: Rc<executor::Foreground>,
|
||||||
|
font_cache: Arc<FontCache>,
|
||||||
) -> Result<Box<dyn platform::Window>> {
|
) -> Result<Box<dyn platform::Window>> {
|
||||||
Ok(Box::new(Window::open(options, executor)?))
|
Ok(Box::new(Window::open(options, executor, font_cache)?))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,17 +3,16 @@ use crate::{
|
||||||
color::ColorU,
|
color::ColorU,
|
||||||
geometry::vector::{vec2i, Vector2I},
|
geometry::vector::{vec2i, Vector2I},
|
||||||
scene::Layer,
|
scene::Layer,
|
||||||
Scene,
|
FontCache, Scene,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use metal::{MTLResourceOptions, NSRange};
|
use metal::{MTLResourceOptions, NSRange};
|
||||||
use shaders::{ToFloat2 as _, ToUchar4 as _, ToUint2 as _};
|
use shaders::{ToFloat2 as _, ToUchar4 as _};
|
||||||
use std::{collections::HashMap, ffi::c_void, mem};
|
use std::{collections::HashMap, ffi::c_void, mem, sync::Arc};
|
||||||
|
|
||||||
const SHADERS_METALLIB: &'static [u8] =
|
const SHADERS_METALLIB: &'static [u8] =
|
||||||
include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
|
include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
|
||||||
const INSTANCE_BUFFER_SIZE: usize = 1024 * 1024; // This is an arbitrary decision. There's probably a more optimal value.
|
const INSTANCE_BUFFER_SIZE: usize = 1024 * 1024; // This is an arbitrary decision. There's probably a more optimal value.
|
||||||
const ATLAS_SIZE: Vector2I = vec2i(1024, 768);
|
|
||||||
|
|
||||||
pub struct Renderer {
|
pub struct Renderer {
|
||||||
sprite_cache: SpriteCache,
|
sprite_cache: SpriteCache,
|
||||||
|
@ -25,7 +24,11 @@ pub struct Renderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Renderer {
|
impl Renderer {
|
||||||
pub fn new(device: metal::Device, pixel_format: metal::MTLPixelFormat) -> Result<Self> {
|
pub fn new(
|
||||||
|
device: metal::Device,
|
||||||
|
pixel_format: metal::MTLPixelFormat,
|
||||||
|
font_cache: Arc<FontCache>,
|
||||||
|
) -> Result<Self> {
|
||||||
let library = device
|
let library = device
|
||||||
.new_library_with_data(SHADERS_METALLIB)
|
.new_library_with_data(SHADERS_METALLIB)
|
||||||
.map_err(|message| anyhow!("error building metal library: {}", message))?;
|
.map_err(|message| anyhow!("error building metal library: {}", message))?;
|
||||||
|
@ -48,8 +51,9 @@ impl Renderer {
|
||||||
MTLResourceOptions::StorageModeManaged,
|
MTLResourceOptions::StorageModeManaged,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let atlas_size: Vector2I = vec2i(1024, 768);
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
sprite_cache: SpriteCache::new(device, ATLAS_SIZE),
|
sprite_cache: SpriteCache::new(device.clone(), atlas_size, font_cache),
|
||||||
quad_pipeline_state: build_pipeline_state(
|
quad_pipeline_state: build_pipeline_state(
|
||||||
&device,
|
&device,
|
||||||
&library,
|
&library,
|
||||||
|
@ -261,43 +265,35 @@ impl Renderer {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
align_offset(offset);
|
let mut sprites_by_atlas = HashMap::new();
|
||||||
let next_offset = *offset + layer.glyphs().len() * mem::size_of::<shaders::GPUISprite>();
|
|
||||||
assert!(
|
|
||||||
next_offset <= INSTANCE_BUFFER_SIZE,
|
|
||||||
"instance buffer exhausted"
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut sprites = HashMap::new();
|
|
||||||
for glyph in layer.glyphs() {
|
for glyph in layer.glyphs() {
|
||||||
let (atlas, bounds) =
|
if let Some((atlas, bounds)) = self.sprite_cache.render_glyph(
|
||||||
self.sprite_cache
|
glyph.font_id,
|
||||||
.render_glyph(glyph.font_id, glyph.font_size, glyph.glyph_id);
|
glyph.font_size,
|
||||||
sprites
|
glyph.id,
|
||||||
.entry(atlas)
|
scene.scale_factor(),
|
||||||
.or_insert_with(Vec::new)
|
) {
|
||||||
.push(shaders::GPUISprite {
|
sprites_by_atlas
|
||||||
origin: glyph.origin.to_float2(),
|
.entry(atlas)
|
||||||
size: bounds.size().to_uint2(),
|
.or_insert_with(Vec::new)
|
||||||
atlas_origin: bounds.origin().to_uint2(),
|
.push(shaders::GPUISprite {
|
||||||
color: glyph.color.to_uchar4(),
|
origin: (glyph.origin * scene.scale_factor()).to_float2(),
|
||||||
});
|
size: (bounds.size().to_f32() * scene.scale_factor()).to_float2(),
|
||||||
|
atlas_origin: bounds.origin().to_float2(),
|
||||||
|
color: glyph.color.to_uchar4(),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.command_encoder
|
ctx.command_encoder
|
||||||
.set_render_pipeline_state(&self.sprite_pipeline_state);
|
.set_render_pipeline_state(&self.sprite_pipeline_state);
|
||||||
ctx.command_encoder.set_vertex_buffer(
|
ctx.command_encoder.set_vertex_buffer(
|
||||||
shaders::GPUISpriteInputIndex_GPUISpriteInputIndexVertices as u64,
|
shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexVertices as u64,
|
||||||
Some(&self.unit_vertices),
|
Some(&self.unit_vertices),
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
ctx.command_encoder.set_vertex_buffer(
|
|
||||||
shaders::GPUISpriteInputIndex_GPUISpriteInputIndexSprites as u64,
|
|
||||||
Some(&self.instances),
|
|
||||||
*offset as u64,
|
|
||||||
);
|
|
||||||
ctx.command_encoder.set_vertex_bytes(
|
ctx.command_encoder.set_vertex_bytes(
|
||||||
shaders::GPUISpriteInputIndex_GPUISpriteInputIndexUniforms as u64,
|
shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexUniforms as u64,
|
||||||
mem::size_of::<shaders::GPUIUniforms>() as u64,
|
mem::size_of::<shaders::GPUIUniforms>() as u64,
|
||||||
[shaders::GPUIUniforms {
|
[shaders::GPUIUniforms {
|
||||||
viewport_size: ctx.drawable_size.to_float2(),
|
viewport_size: ctx.drawable_size.to_float2(),
|
||||||
|
@ -310,8 +306,41 @@ impl Renderer {
|
||||||
as *mut shaders::GPUISprite
|
as *mut shaders::GPUISprite
|
||||||
};
|
};
|
||||||
|
|
||||||
for glyph in layer.glyphs() {
|
for (atlas_id, sprites) in sprites_by_atlas {
|
||||||
let sprite = self.sprite_cache.rasterize_glyph();
|
align_offset(offset);
|
||||||
|
let next_offset = *offset + sprites.len() * mem::size_of::<shaders::GPUISprite>();
|
||||||
|
assert!(
|
||||||
|
next_offset <= INSTANCE_BUFFER_SIZE,
|
||||||
|
"instance buffer exhausted"
|
||||||
|
);
|
||||||
|
|
||||||
|
ctx.command_encoder.set_vertex_buffer(
|
||||||
|
shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexSprites as u64,
|
||||||
|
Some(&self.instances),
|
||||||
|
*offset as u64,
|
||||||
|
);
|
||||||
|
|
||||||
|
let texture = self.sprite_cache.atlas_texture(atlas_id).unwrap();
|
||||||
|
ctx.command_encoder.set_fragment_texture(
|
||||||
|
shaders::GPUISpriteFragmentInputIndex_GPUISpriteFragmentInputIndexAtlas as u64,
|
||||||
|
Some(texture),
|
||||||
|
);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
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;
|
||||||
|
|
||||||
|
ctx.command_encoder.draw_primitives_instanced(
|
||||||
|
metal::MTLPrimitiveType::Triangle,
|
||||||
|
0,
|
||||||
|
6,
|
||||||
|
sprites.len() as u64,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -377,10 +406,6 @@ mod shaders {
|
||||||
fn to_uchar4(&self) -> vector_uchar4;
|
fn to_uchar4(&self) -> vector_uchar4;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ToUint2 {
|
|
||||||
fn to_uint2(&self) -> vector_uint2;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToFloat2 for (f32, f32) {
|
impl ToFloat2 for (f32, f32) {
|
||||||
fn to_float2(&self) -> vector_float2 {
|
fn to_float2(&self) -> vector_float2 {
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -403,6 +428,15 @@ mod shaders {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ToFloat2 for Vector2I {
|
||||||
|
fn to_float2(&self) -> vector_float2 {
|
||||||
|
let mut output = self.y() as vector_float2;
|
||||||
|
output <<= 32;
|
||||||
|
output |= self.x() as vector_float2;
|
||||||
|
output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ToUchar4 for ColorU {
|
impl ToUchar4 for ColorU {
|
||||||
fn to_uchar4(&self) -> vector_uchar4 {
|
fn to_uchar4(&self) -> vector_uchar4 {
|
||||||
let mut vec = self.a as vector_uchar4;
|
let mut vec = self.a as vector_uchar4;
|
||||||
|
@ -415,13 +449,4 @@ mod shaders {
|
||||||
vec
|
vec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToUint2 for Vector2I {
|
|
||||||
fn to_uint2(&self) -> vector_uint2 {
|
|
||||||
let mut output = self.y() as vector_uint2;
|
|
||||||
output <<= 32;
|
|
||||||
output |= self.x() as vector_uint2;
|
|
||||||
output
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,14 +37,18 @@ typedef struct {
|
||||||
} GPUIShadow;
|
} GPUIShadow;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
GPUISpriteInputIndexVertices = 0,
|
GPUISpriteVertexInputIndexVertices = 0,
|
||||||
GPUISpriteInputIndexSprites = 1,
|
GPUISpriteVertexInputIndexSprites = 1,
|
||||||
GPUISpriteInputIndexUniforms = 2,
|
GPUISpriteVertexInputIndexUniforms = 2,
|
||||||
} GPUISpriteInputIndex;
|
} GPUISpriteVertexInputIndex;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
GPUISpriteFragmentInputIndexAtlas = 0,
|
||||||
|
} GPUISpriteFragmentInputIndex;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
vector_float2 origin;
|
vector_float2 origin;
|
||||||
vector_uint2 size;
|
vector_float2 size;
|
||||||
vector_uint2 atlas_origin;
|
vector_float2 atlas_origin;
|
||||||
vector_uchar4 color;
|
vector_uchar4 color;
|
||||||
} GPUISprite;
|
} GPUISprite;
|
||||||
|
|
|
@ -56,8 +56,7 @@ vertex QuadFragmentInput quad_vertex(
|
||||||
}
|
}
|
||||||
|
|
||||||
fragment float4 quad_fragment(
|
fragment float4 quad_fragment(
|
||||||
QuadFragmentInput input [[stage_in]],
|
QuadFragmentInput input [[stage_in]]
|
||||||
constant GPUIUniforms *uniforms [[buffer(GPUIQuadInputIndexUniforms)]]
|
|
||||||
) {
|
) {
|
||||||
float2 half_size = input.quad.size / 2.;
|
float2 half_size = input.quad.size / 2.;
|
||||||
float2 center = input.quad.origin + half_size;
|
float2 center = input.quad.origin + half_size;
|
||||||
|
@ -115,8 +114,7 @@ vertex ShadowFragmentInput shadow_vertex(
|
||||||
}
|
}
|
||||||
|
|
||||||
fragment float4 shadow_fragment(
|
fragment float4 shadow_fragment(
|
||||||
ShadowFragmentInput input [[stage_in]],
|
ShadowFragmentInput input [[stage_in]]
|
||||||
constant GPUIUniforms *uniforms [[buffer(GPUIShadowInputIndexUniforms)]]
|
|
||||||
) {
|
) {
|
||||||
float sigma = input.shadow.sigma;
|
float sigma = input.shadow.sigma;
|
||||||
float corner_radius = input.shadow.corner_radius;
|
float corner_radius = input.shadow.corner_radius;
|
||||||
|
@ -141,3 +139,39 @@ fragment float4 shadow_fragment(
|
||||||
|
|
||||||
return float4(1., 1., 1., alpha) * coloru_to_colorf(input.shadow.color);
|
return float4(1., 1., 1., alpha) * coloru_to_colorf(input.shadow.color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct SpriteFragmentInput {
|
||||||
|
float4 position [[position]];
|
||||||
|
float2 atlas_position;
|
||||||
|
float4 color [[flat]];
|
||||||
|
};
|
||||||
|
|
||||||
|
vertex SpriteFragmentInput sprite_vertex(
|
||||||
|
uint unit_vertex_id [[vertex_id]],
|
||||||
|
uint sprite_id [[instance_id]],
|
||||||
|
constant float2 *unit_vertices [[buffer(GPUISpriteVertexInputIndexVertices)]],
|
||||||
|
constant GPUISprite *sprites [[buffer(GPUISpriteVertexInputIndexSprites)]],
|
||||||
|
constant GPUIUniforms *uniforms [[buffer(GPUISpriteVertexInputIndexUniforms)]]
|
||||||
|
) {
|
||||||
|
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||||
|
GPUISprite sprite = sprites[sprite_id];
|
||||||
|
float2 position = unit_vertex * sprite.size + sprite.origin;
|
||||||
|
float2 atlas_position = unit_vertex * sprite.size + sprite.atlas_origin;
|
||||||
|
float4 device_position = to_device_position(position, uniforms->viewport_size);
|
||||||
|
|
||||||
|
return SpriteFragmentInput {
|
||||||
|
device_position,
|
||||||
|
atlas_position,
|
||||||
|
coloru_to_colorf(sprite.color),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment float4 sprite_fragment(
|
||||||
|
SpriteFragmentInput input [[stage_in]],
|
||||||
|
texture2d<float> atlas [[ texture(GPUISpriteFragmentInputIndexAtlas) ]]
|
||||||
|
) {
|
||||||
|
constexpr sampler atlas_sampler(mag_filter::linear, min_filter::linear);
|
||||||
|
float4 color = input.color;
|
||||||
|
color.a *= atlas.sample(atlas_sampler, input.atlas_position).r;
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,10 @@ use std::{collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
fonts::{FontId, GlyphId},
|
fonts::{FontId, GlyphId},
|
||||||
geometry::{rect::RectI, vector::Vector2I},
|
geometry::{
|
||||||
|
rect::RectI,
|
||||||
|
vector::{vec2i, Vector2I},
|
||||||
|
},
|
||||||
FontCache,
|
FontCache,
|
||||||
};
|
};
|
||||||
use etagere::BucketedAtlasAllocator;
|
use etagere::BucketedAtlasAllocator;
|
||||||
|
@ -17,19 +20,21 @@ struct GlyphDescriptor {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SpriteCache {
|
pub struct SpriteCache {
|
||||||
font_cache: Arc<FontCache>,
|
|
||||||
device: metal::Device,
|
device: metal::Device,
|
||||||
size: Vector2I,
|
atlas_size: Vector2I,
|
||||||
|
font_cache: Arc<FontCache>,
|
||||||
atlasses: Vec<Atlas>,
|
atlasses: Vec<Atlas>,
|
||||||
glyphs: HashMap<GlyphDescriptor, (usize, RectI)>,
|
glyphs: HashMap<GlyphDescriptor, Option<(usize, RectI)>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpriteCache {
|
impl SpriteCache {
|
||||||
pub fn new(device: metal::Device, size: Vector2I) -> Self {
|
pub fn new(device: metal::Device, size: Vector2I, font_cache: Arc<FontCache>) -> Self {
|
||||||
|
let atlasses = vec![Atlas::new(&device, size)];
|
||||||
Self {
|
Self {
|
||||||
device,
|
device,
|
||||||
size,
|
atlas_size: size,
|
||||||
atlasses: vec![Atlas::new(&device, size)],
|
font_cache,
|
||||||
|
atlasses,
|
||||||
glyphs: Default::default(),
|
glyphs: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,7 +44,12 @@ impl SpriteCache {
|
||||||
font_id: FontId,
|
font_id: FontId,
|
||||||
font_size: f32,
|
font_size: f32,
|
||||||
glyph_id: GlyphId,
|
glyph_id: GlyphId,
|
||||||
) -> (usize, RectI) {
|
scale_factor: f32,
|
||||||
|
) -> Option<(usize, RectI)> {
|
||||||
|
let font_cache = &self.font_cache;
|
||||||
|
let atlasses = &mut self.atlasses;
|
||||||
|
let atlas_size = self.atlas_size;
|
||||||
|
let device = &self.device;
|
||||||
self.glyphs
|
self.glyphs
|
||||||
.entry(GlyphDescriptor {
|
.entry(GlyphDescriptor {
|
||||||
font_id,
|
font_id,
|
||||||
|
@ -47,12 +57,27 @@ impl SpriteCache {
|
||||||
glyph_id,
|
glyph_id,
|
||||||
})
|
})
|
||||||
.or_insert_with(|| {
|
.or_insert_with(|| {
|
||||||
let rendered_glyph = self.font_cache.render_glyph(font_id, font_size, glyph_id);
|
let (size, mask) =
|
||||||
// let atlas = self.atlasses.last_mut().unwrap();
|
font_cache.render_glyph(font_id, font_size, glyph_id, scale_factor)?;
|
||||||
todo!()
|
assert!(size.x() < atlas_size.x());
|
||||||
|
assert!(size.y() < atlas_size.y());
|
||||||
|
|
||||||
|
let atlas = atlasses.last_mut().unwrap();
|
||||||
|
if let Some(bounds) = atlas.try_insert(size, &mask) {
|
||||||
|
Some((atlasses.len() - 1, bounds))
|
||||||
|
} else {
|
||||||
|
let mut atlas = Atlas::new(device, atlas_size);
|
||||||
|
let bounds = atlas.try_insert(size, &mask).unwrap();
|
||||||
|
atlasses.push(atlas);
|
||||||
|
Some((atlasses.len() - 1, bounds))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.clone()
|
.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn atlas_texture(&self, atlas_id: usize) -> Option<&metal::TextureRef> {
|
||||||
|
self.atlasses.get(atlas_id).map(|a| a.texture.as_ref())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Atlas {
|
struct Atlas {
|
||||||
|
@ -72,4 +97,24 @@ impl Atlas {
|
||||||
texture: device.new_texture(&descriptor),
|
texture: device.new_texture(&descriptor),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn try_insert(&mut self, size: Vector2I, mask: &[u8]) -> Option<RectI> {
|
||||||
|
let allocation = self
|
||||||
|
.allocator
|
||||||
|
.allocate(etagere::size2(size.x(), size.y()))?;
|
||||||
|
|
||||||
|
let bounds = allocation.rectangle;
|
||||||
|
let region = metal::MTLRegion::new_2d(
|
||||||
|
bounds.min.x as u64,
|
||||||
|
bounds.min.y as u64,
|
||||||
|
bounds.width() as u64,
|
||||||
|
bounds.height() as u64,
|
||||||
|
);
|
||||||
|
self.texture
|
||||||
|
.replace_region(region, 0, mask.as_ptr() as *const _, size.x() as u64);
|
||||||
|
Some(RectI::from_points(
|
||||||
|
vec2i(bounds.min.x, bounds.min.y),
|
||||||
|
vec2i(bounds.max.x, bounds.max.y),
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use crate::{
|
||||||
executor,
|
executor,
|
||||||
geometry::vector::Vector2F,
|
geometry::vector::Vector2F,
|
||||||
platform::{self, Event, WindowContext},
|
platform::{self, Event, WindowContext},
|
||||||
Scene,
|
FontCache, Scene,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use cocoa::{
|
use cocoa::{
|
||||||
|
@ -31,6 +31,7 @@ use std::{
|
||||||
ffi::c_void,
|
ffi::c_void,
|
||||||
mem, ptr,
|
mem, ptr,
|
||||||
rc::{Rc, Weak},
|
rc::{Rc, Weak},
|
||||||
|
sync::Arc,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -140,6 +141,7 @@ impl Window {
|
||||||
pub fn open(
|
pub fn open(
|
||||||
options: platform::WindowOptions,
|
options: platform::WindowOptions,
|
||||||
executor: Rc<executor::Foreground>,
|
executor: Rc<executor::Foreground>,
|
||||||
|
font_cache: Arc<FontCache>,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
const PIXEL_FORMAT: metal::MTLPixelFormat = metal::MTLPixelFormat::BGRA8Unorm;
|
const PIXEL_FORMAT: metal::MTLPixelFormat = metal::MTLPixelFormat::BGRA8Unorm;
|
||||||
|
|
||||||
|
@ -192,7 +194,7 @@ impl Window {
|
||||||
synthetic_drag_counter: 0,
|
synthetic_drag_counter: 0,
|
||||||
executor,
|
executor,
|
||||||
scene_to_render: Default::default(),
|
scene_to_render: Default::default(),
|
||||||
renderer: Renderer::new(device, PIXEL_FORMAT)?,
|
renderer: Renderer::new(device.clone(), PIXEL_FORMAT, font_cache)?,
|
||||||
command_queue: device.new_command_queue(),
|
command_queue: device.new_command_queue(),
|
||||||
device,
|
device,
|
||||||
layer,
|
layer,
|
||||||
|
|
|
@ -9,7 +9,7 @@ pub mod current {
|
||||||
use crate::{
|
use crate::{
|
||||||
executor,
|
executor,
|
||||||
geometry::{rect::RectF, vector::Vector2F},
|
geometry::{rect::RectF, vector::Vector2F},
|
||||||
Scene,
|
FontCache, Scene,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use async_task::Runnable;
|
use async_task::Runnable;
|
||||||
|
@ -32,6 +32,7 @@ pub trait App {
|
||||||
&self,
|
&self,
|
||||||
options: WindowOptions,
|
options: WindowOptions,
|
||||||
executor: Rc<executor::Foreground>,
|
executor: Rc<executor::Foreground>,
|
||||||
|
font_cache: Arc<FontCache>,
|
||||||
) -> Result<Box<dyn Window>>;
|
) -> Result<Box<dyn Window>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ pub struct Shadow {
|
||||||
pub struct Glyph {
|
pub struct Glyph {
|
||||||
pub font_id: FontId,
|
pub font_id: FontId,
|
||||||
pub font_size: f32,
|
pub font_size: f32,
|
||||||
pub glyph_id: GlyphId,
|
pub id: GlyphId,
|
||||||
pub origin: Vector2F,
|
pub origin: Vector2F,
|
||||||
pub color: ColorU,
|
pub color: ColorU,
|
||||||
}
|
}
|
||||||
|
|
|
@ -228,7 +228,7 @@ impl Line {
|
||||||
ctx.scene.push_glyph(scene::Glyph {
|
ctx.scene.push_glyph(scene::Glyph {
|
||||||
font_id: run.font_id,
|
font_id: run.font_id,
|
||||||
font_size: self.font_size,
|
font_size: self.font_size,
|
||||||
glyph_id: glyph.id,
|
id: glyph.id,
|
||||||
origin: glyph_origin,
|
origin: glyph_origin,
|
||||||
color,
|
color,
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue