co-authored by
Nathan Sobo
parent
247afa1666
commit
ad7974608b
@@ -1,5 +1,5 @@
|
||||
use super::{BoolExt as _, Dispatcher, Window};
|
||||
use crate::{executor, platform};
|
||||
use crate::{executor, platform, FontCache};
|
||||
use anyhow::Result;
|
||||
use cocoa::base::id;
|
||||
use objc::{class, msg_send, sel, sel_impl};
|
||||
@@ -33,7 +33,8 @@ impl platform::App for App {
|
||||
&self,
|
||||
options: platform::WindowOptions,
|
||||
executor: Rc<executor::Foreground>,
|
||||
font_cache: Arc<FontCache>,
|
||||
) -> 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,
|
||||
geometry::vector::{vec2i, Vector2I},
|
||||
scene::Layer,
|
||||
Scene,
|
||||
FontCache, Scene,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use metal::{MTLResourceOptions, NSRange};
|
||||
use shaders::{ToFloat2 as _, ToUchar4 as _, ToUint2 as _};
|
||||
use std::{collections::HashMap, ffi::c_void, mem};
|
||||
use shaders::{ToFloat2 as _, ToUchar4 as _};
|
||||
use std::{collections::HashMap, ffi::c_void, mem, sync::Arc};
|
||||
|
||||
const SHADERS_METALLIB: &'static [u8] =
|
||||
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 ATLAS_SIZE: Vector2I = vec2i(1024, 768);
|
||||
|
||||
pub struct Renderer {
|
||||
sprite_cache: SpriteCache,
|
||||
@@ -25,7 +24,11 @@ pub struct 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
|
||||
.new_library_with_data(SHADERS_METALLIB)
|
||||
.map_err(|message| anyhow!("error building metal library: {}", message))?;
|
||||
@@ -48,8 +51,9 @@ impl Renderer {
|
||||
MTLResourceOptions::StorageModeManaged,
|
||||
);
|
||||
|
||||
let atlas_size: Vector2I = vec2i(1024, 768);
|
||||
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(
|
||||
&device,
|
||||
&library,
|
||||
@@ -261,43 +265,35 @@ impl Renderer {
|
||||
return;
|
||||
}
|
||||
|
||||
align_offset(offset);
|
||||
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();
|
||||
let mut sprites_by_atlas = HashMap::new();
|
||||
for glyph in layer.glyphs() {
|
||||
let (atlas, bounds) =
|
||||
self.sprite_cache
|
||||
.render_glyph(glyph.font_id, glyph.font_size, glyph.glyph_id);
|
||||
sprites
|
||||
.entry(atlas)
|
||||
.or_insert_with(Vec::new)
|
||||
.push(shaders::GPUISprite {
|
||||
origin: glyph.origin.to_float2(),
|
||||
size: bounds.size().to_uint2(),
|
||||
atlas_origin: bounds.origin().to_uint2(),
|
||||
color: glyph.color.to_uchar4(),
|
||||
});
|
||||
if let Some((atlas, bounds)) = self.sprite_cache.render_glyph(
|
||||
glyph.font_id,
|
||||
glyph.font_size,
|
||||
glyph.id,
|
||||
scene.scale_factor(),
|
||||
) {
|
||||
sprites_by_atlas
|
||||
.entry(atlas)
|
||||
.or_insert_with(Vec::new)
|
||||
.push(shaders::GPUISprite {
|
||||
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
|
||||
.set_render_pipeline_state(&self.sprite_pipeline_state);
|
||||
ctx.command_encoder.set_vertex_buffer(
|
||||
shaders::GPUISpriteInputIndex_GPUISpriteInputIndexVertices as u64,
|
||||
shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexVertices as u64,
|
||||
Some(&self.unit_vertices),
|
||||
0,
|
||||
);
|
||||
ctx.command_encoder.set_vertex_buffer(
|
||||
shaders::GPUISpriteInputIndex_GPUISpriteInputIndexSprites as u64,
|
||||
Some(&self.instances),
|
||||
*offset as u64,
|
||||
);
|
||||
ctx.command_encoder.set_vertex_bytes(
|
||||
shaders::GPUISpriteInputIndex_GPUISpriteInputIndexUniforms as u64,
|
||||
shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexUniforms as u64,
|
||||
mem::size_of::<shaders::GPUIUniforms>() as u64,
|
||||
[shaders::GPUIUniforms {
|
||||
viewport_size: ctx.drawable_size.to_float2(),
|
||||
@@ -310,8 +306,41 @@ impl Renderer {
|
||||
as *mut shaders::GPUISprite
|
||||
};
|
||||
|
||||
for glyph in layer.glyphs() {
|
||||
let sprite = self.sprite_cache.rasterize_glyph();
|
||||
for (atlas_id, sprites) in sprites_by_atlas {
|
||||
align_offset(offset);
|
||||
let next_offset = *offset + sprites.len() * mem::size_of::<shaders::GPUISprite>();
|
||||
assert!(
|
||||
next_offset <= INSTANCE_BUFFER_SIZE,
|
||||
"instance buffer exhausted"
|
||||
);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
pub trait ToUint2 {
|
||||
fn to_uint2(&self) -> vector_uint2;
|
||||
}
|
||||
|
||||
impl ToFloat2 for (f32, f32) {
|
||||
fn to_float2(&self) -> vector_float2 {
|
||||
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 {
|
||||
fn to_uchar4(&self) -> vector_uchar4 {
|
||||
let mut vec = self.a as vector_uchar4;
|
||||
@@ -415,13 +449,4 @@ mod shaders {
|
||||
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;
|
||||
|
||||
typedef enum {
|
||||
GPUISpriteInputIndexVertices = 0,
|
||||
GPUISpriteInputIndexSprites = 1,
|
||||
GPUISpriteInputIndexUniforms = 2,
|
||||
} GPUISpriteInputIndex;
|
||||
GPUISpriteVertexInputIndexVertices = 0,
|
||||
GPUISpriteVertexInputIndexSprites = 1,
|
||||
GPUISpriteVertexInputIndexUniforms = 2,
|
||||
} GPUISpriteVertexInputIndex;
|
||||
|
||||
typedef enum {
|
||||
GPUISpriteFragmentInputIndexAtlas = 0,
|
||||
} GPUISpriteFragmentInputIndex;
|
||||
|
||||
typedef struct {
|
||||
vector_float2 origin;
|
||||
vector_uint2 size;
|
||||
vector_uint2 atlas_origin;
|
||||
vector_float2 size;
|
||||
vector_float2 atlas_origin;
|
||||
vector_uchar4 color;
|
||||
} GPUISprite;
|
||||
|
||||
@@ -56,8 +56,7 @@ vertex QuadFragmentInput quad_vertex(
|
||||
}
|
||||
|
||||
fragment float4 quad_fragment(
|
||||
QuadFragmentInput input [[stage_in]],
|
||||
constant GPUIUniforms *uniforms [[buffer(GPUIQuadInputIndexUniforms)]]
|
||||
QuadFragmentInput input [[stage_in]]
|
||||
) {
|
||||
float2 half_size = input.quad.size / 2.;
|
||||
float2 center = input.quad.origin + half_size;
|
||||
@@ -115,8 +114,7 @@ vertex ShadowFragmentInput shadow_vertex(
|
||||
}
|
||||
|
||||
fragment float4 shadow_fragment(
|
||||
ShadowFragmentInput input [[stage_in]],
|
||||
constant GPUIUniforms *uniforms [[buffer(GPUIShadowInputIndexUniforms)]]
|
||||
ShadowFragmentInput input [[stage_in]]
|
||||
) {
|
||||
float sigma = input.shadow.sigma;
|
||||
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);
|
||||
}
|
||||
|
||||
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::{
|
||||
fonts::{FontId, GlyphId},
|
||||
geometry::{rect::RectI, vector::Vector2I},
|
||||
geometry::{
|
||||
rect::RectI,
|
||||
vector::{vec2i, Vector2I},
|
||||
},
|
||||
FontCache,
|
||||
};
|
||||
use etagere::BucketedAtlasAllocator;
|
||||
@@ -17,19 +20,21 @@ struct GlyphDescriptor {
|
||||
}
|
||||
|
||||
pub struct SpriteCache {
|
||||
font_cache: Arc<FontCache>,
|
||||
device: metal::Device,
|
||||
size: Vector2I,
|
||||
atlas_size: Vector2I,
|
||||
font_cache: Arc<FontCache>,
|
||||
atlasses: Vec<Atlas>,
|
||||
glyphs: HashMap<GlyphDescriptor, (usize, RectI)>,
|
||||
glyphs: HashMap<GlyphDescriptor, Option<(usize, RectI)>>,
|
||||
}
|
||||
|
||||
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 {
|
||||
device,
|
||||
size,
|
||||
atlasses: vec![Atlas::new(&device, size)],
|
||||
atlas_size: size,
|
||||
font_cache,
|
||||
atlasses,
|
||||
glyphs: Default::default(),
|
||||
}
|
||||
}
|
||||
@@ -39,7 +44,12 @@ impl SpriteCache {
|
||||
font_id: FontId,
|
||||
font_size: f32,
|
||||
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
|
||||
.entry(GlyphDescriptor {
|
||||
font_id,
|
||||
@@ -47,12 +57,27 @@ impl SpriteCache {
|
||||
glyph_id,
|
||||
})
|
||||
.or_insert_with(|| {
|
||||
let rendered_glyph = self.font_cache.render_glyph(font_id, font_size, glyph_id);
|
||||
// let atlas = self.atlasses.last_mut().unwrap();
|
||||
todo!()
|
||||
let (size, mask) =
|
||||
font_cache.render_glyph(font_id, font_size, glyph_id, scale_factor)?;
|
||||
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()
|
||||
}
|
||||
|
||||
pub fn atlas_texture(&self, atlas_id: usize) -> Option<&metal::TextureRef> {
|
||||
self.atlasses.get(atlas_id).map(|a| a.texture.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
struct Atlas {
|
||||
@@ -72,4 +97,24 @@ impl Atlas {
|
||||
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,
|
||||
geometry::vector::Vector2F,
|
||||
platform::{self, Event, WindowContext},
|
||||
Scene,
|
||||
FontCache, Scene,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use cocoa::{
|
||||
@@ -31,6 +31,7 @@ use std::{
|
||||
ffi::c_void,
|
||||
mem, ptr,
|
||||
rc::{Rc, Weak},
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
@@ -140,6 +141,7 @@ impl Window {
|
||||
pub fn open(
|
||||
options: platform::WindowOptions,
|
||||
executor: Rc<executor::Foreground>,
|
||||
font_cache: Arc<FontCache>,
|
||||
) -> Result<Self> {
|
||||
const PIXEL_FORMAT: metal::MTLPixelFormat = metal::MTLPixelFormat::BGRA8Unorm;
|
||||
|
||||
@@ -192,7 +194,7 @@ impl Window {
|
||||
synthetic_drag_counter: 0,
|
||||
executor,
|
||||
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(),
|
||||
device,
|
||||
layer,
|
||||
|
||||
@@ -9,7 +9,7 @@ pub mod current {
|
||||
use crate::{
|
||||
executor,
|
||||
geometry::{rect::RectF, vector::Vector2F},
|
||||
Scene,
|
||||
FontCache, Scene,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use async_task::Runnable;
|
||||
@@ -32,6 +32,7 @@ pub trait App {
|
||||
&self,
|
||||
options: WindowOptions,
|
||||
executor: Rc<executor::Foreground>,
|
||||
font_cache: Arc<FontCache>,
|
||||
) -> Result<Box<dyn Window>>;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user