Start on image rendering

This commit is contained in:
Antonio Scandurra
2021-09-14 16:48:44 +02:00
parent 201b923248
commit 96ade8668f
13 changed files with 510 additions and 75 deletions
+72 -12
View File
@@ -1,4 +1,7 @@
use crate::geometry::vector::{vec2i, Vector2I};
use crate::geometry::{
rect::RectI,
vector::{vec2i, Vector2I},
};
use etagere::BucketedAtlasAllocator;
use foreign_types::ForeignType;
use metal::{self, Device, TextureDescriptor};
@@ -11,6 +14,12 @@ pub struct AtlasAllocator {
free_atlases: Vec<Atlas>,
}
#[derive(Copy, Clone)]
pub struct AllocId {
pub atlas_id: usize,
alloc_id: etagere::AllocId,
}
impl AtlasAllocator {
pub fn new(device: Device, texture_descriptor: TextureDescriptor) -> Self {
let mut me = Self {
@@ -31,20 +40,40 @@ impl AtlasAllocator {
)
}
pub fn allocate(&mut self, requested_size: Vector2I) -> anyhow::Result<(usize, Vector2I)> {
let origin = self
pub fn allocate(&mut self, requested_size: Vector2I) -> (AllocId, Vector2I) {
let (alloc_id, origin) = self
.atlases
.last_mut()
.unwrap()
.allocate(requested_size)
.unwrap_or_else(|| {
let mut atlas = self.new_atlas(requested_size);
let origin = atlas.allocate(requested_size).unwrap();
let (id, origin) = atlas.allocate(requested_size).unwrap();
self.atlases.push(atlas);
origin
(id, origin)
});
Ok((self.atlases.len() - 1, origin))
let id = AllocId {
atlas_id: self.atlases.len() - 1,
alloc_id,
};
(id, origin)
}
pub fn upload(&mut self, size: Vector2I, bytes: &[u8]) -> (AllocId, RectI) {
let (alloc_id, origin) = self.allocate(size);
let bounds = RectI::new(origin, size);
self.atlases[alloc_id.atlas_id].upload(bounds, bytes);
(alloc_id, bounds)
}
pub fn deallocate(&mut self, id: AllocId) {
if let Some(atlas) = self.atlases.get_mut(id.atlas_id) {
atlas.deallocate(id.alloc_id);
if atlas.is_empty() {
self.free_atlases.push(self.atlases.remove(id.atlas_id));
}
}
}
pub fn clear(&mut self) {
@@ -102,13 +131,44 @@ impl Atlas {
vec2i(size.width, size.height)
}
fn allocate(&mut self, size: Vector2I) -> Option<Vector2I> {
let origin = self
fn allocate(&mut self, size: Vector2I) -> Option<(etagere::AllocId, Vector2I)> {
let alloc = self
.allocator
.allocate(etagere::Size::new(size.x(), size.y()))?
.rectangle
.min;
Some(vec2i(origin.x, origin.y))
.allocate(etagere::Size::new(size.x(), size.y()))?;
let origin = alloc.rectangle.min;
Some((alloc.id, vec2i(origin.x, origin.y)))
}
fn upload(&mut self, bounds: RectI, bytes: &[u8]) {
let region = metal::MTLRegion::new_2d(
bounds.origin().x() as u64,
bounds.origin().y() as u64,
bounds.size().x() as u64,
bounds.size().y() as u64,
);
self.texture.replace_region(
region,
0,
bytes.as_ptr() as *const _,
(bounds.size().x() * self.bytes_per_pixel() as i32) as u64,
);
}
fn bytes_per_pixel(&self) -> u8 {
use metal::MTLPixelFormat::*;
match self.texture.pixel_format() {
A8Unorm | R8Unorm => 1,
RGBA8Unorm | BGRA8Unorm => 4,
_ => unimplemented!(),
}
}
fn deallocate(&mut self, id: etagere::AllocId) {
self.allocator.deallocate(id);
}
fn is_empty(&self) -> bool {
self.allocator.is_empty()
}
fn clear(&mut self) {
+154 -19
View File
@@ -1,13 +1,15 @@
use super::{atlas::AtlasAllocator, sprite_cache::SpriteCache};
use super::{
atlas::{self, AtlasAllocator},
sprite_cache::SpriteCache,
};
use crate::{
color::Color,
geometry::{
rect::RectF,
rect::{RectF, RectI},
vector::{vec2f, vec2i, Vector2F},
},
platform,
scene::{Glyph, Icon, Layer, Quad, Shadow},
Scene,
scene::{Glyph, Icon, Image, Layer, Quad, Scene, Shadow},
};
use cocoa::foundation::NSUInteger;
use metal::{MTLPixelFormat, MTLResourceOptions, NSRange};
@@ -21,9 +23,13 @@ const INSTANCE_BUFFER_SIZE: usize = 1024 * 1024; // This is an arbitrary decisio
pub struct Renderer {
sprite_cache: SpriteCache,
path_atlases: AtlasAllocator,
image_atlases: AtlasAllocator,
prev_rendered_images: HashMap<usize, (atlas::AllocId, RectI)>,
curr_rendered_images: HashMap<usize, (atlas::AllocId, RectI)>,
quad_pipeline_state: metal::RenderPipelineState,
shadow_pipeline_state: metal::RenderPipelineState,
sprite_pipeline_state: metal::RenderPipelineState,
image_pipeline_state: metal::RenderPipelineState,
path_atlas_pipeline_state: metal::RenderPipelineState,
unit_vertices: metal::Buffer,
instances: metal::Buffer,
@@ -64,7 +70,10 @@ impl Renderer {
);
let sprite_cache = SpriteCache::new(device.clone(), vec2i(1024, 768), fonts);
let path_atlases = build_path_atlas_allocator(MTLPixelFormat::R8Unorm, &device);
let path_atlases =
AtlasAllocator::new(device.clone(), build_path_atlas_texture_descriptor());
let image_atlases =
AtlasAllocator::new(device.clone(), build_image_atlas_texture_descriptor());
let quad_pipeline_state = build_pipeline_state(
&device,
&library,
@@ -89,6 +98,14 @@ impl Renderer {
"sprite_fragment",
pixel_format,
);
let image_pipeline_state = build_pipeline_state(
&device,
&library,
"image",
"image_vertex",
"image_fragment",
pixel_format,
);
let path_atlas_pipeline_state = build_path_atlas_pipeline_state(
&device,
&library,
@@ -100,9 +117,13 @@ impl Renderer {
Self {
sprite_cache,
path_atlases,
image_atlases,
prev_rendered_images: Default::default(),
curr_rendered_images: Default::default(),
quad_pipeline_state,
shadow_pipeline_state,
sprite_pipeline_state,
image_pipeline_state,
path_atlas_pipeline_state,
unit_vertices,
instances,
@@ -117,6 +138,12 @@ impl Renderer {
output: &metal::TextureRef,
) {
let mut offset = 0;
mem::swap(
&mut self.curr_rendered_images,
&mut self.prev_rendered_images,
);
let path_sprites = self.render_path_atlases(scene, &mut offset, command_buffer);
self.render_layers(
scene,
@@ -130,6 +157,11 @@ impl Renderer {
location: 0,
length: offset as NSUInteger,
});
for (id, _) in self.prev_rendered_images.values() {
self.image_atlases.deallocate(*id);
}
self.prev_rendered_images.clear();
}
fn render_path_atlases(
@@ -146,11 +178,11 @@ impl Renderer {
for path in layer.paths() {
let origin = path.bounds.origin() * scene.scale_factor();
let size = (path.bounds.size() * scene.scale_factor()).ceil();
let (atlas_id, atlas_origin) = self.path_atlases.allocate(size.to_i32()).unwrap();
let (alloc_id, atlas_origin) = self.path_atlases.allocate(size.to_i32());
let atlas_origin = atlas_origin.to_f32();
sprites.push(PathSprite {
layer_id,
atlas_id,
atlas_id: alloc_id.atlas_id,
shader_data: shaders::GPUISprite {
origin: origin.floor().to_float2(),
target_size: size.to_float2(),
@@ -162,7 +194,7 @@ impl Renderer {
});
if let Some(current_atlas_id) = current_atlas_id {
if atlas_id != current_atlas_id {
if alloc_id.atlas_id != current_atlas_id {
self.render_paths_to_atlas(
offset,
&vertices,
@@ -173,7 +205,7 @@ impl Renderer {
}
}
current_atlas_id = Some(atlas_id);
current_atlas_id = Some(alloc_id.atlas_id);
for vertex in &path.vertices {
let xy_position =
@@ -316,6 +348,13 @@ impl Renderer {
drawable_size,
command_encoder,
);
self.render_images(
layer.images(),
scale_factor,
offset,
drawable_size,
command_encoder,
);
self.render_quads(
layer.underlines(),
scale_factor,
@@ -602,6 +641,97 @@ impl Renderer {
}
}
fn render_images(
&mut self,
images: &[Image],
scale_factor: f32,
offset: &mut usize,
drawable_size: Vector2F,
command_encoder: &metal::RenderCommandEncoderRef,
) {
if images.is_empty() {
return;
}
let mut images_by_atlas = HashMap::new();
for image in images {
let origin = image.bounds.origin() * scale_factor;
let target_size = image.bounds.size() * scale_factor;
let (alloc_id, atlas_bounds) = self
.prev_rendered_images
.remove(&image.data.id)
.or_else(|| self.curr_rendered_images.get(&image.data.id).copied())
.unwrap_or_else(|| {
self.image_atlases
.upload(image.data.size(), image.data.as_bytes())
});
self.curr_rendered_images
.insert(image.data.id, (alloc_id, atlas_bounds));
images_by_atlas
.entry(alloc_id.atlas_id)
.or_insert_with(Vec::new)
.push(shaders::GPUIImage {
origin: origin.to_float2(),
target_size: target_size.to_float2(),
source_size: atlas_bounds.size().to_float2(),
atlas_origin: atlas_bounds.origin().to_float2(),
});
}
command_encoder.set_render_pipeline_state(&self.image_pipeline_state);
command_encoder.set_vertex_buffer(
shaders::GPUIImageVertexInputIndex_GPUIImageVertexInputIndexVertices as u64,
Some(&self.unit_vertices),
0,
);
command_encoder.set_vertex_bytes(
shaders::GPUIImageVertexInputIndex_GPUIImageVertexInputIndexViewportSize as u64,
mem::size_of::<shaders::vector_float2>() as u64,
[drawable_size.to_float2()].as_ptr() as *const c_void,
);
for (atlas_id, images) in images_by_atlas {
align_offset(offset);
let next_offset = *offset + images.len() * mem::size_of::<shaders::GPUIImage>();
assert!(
next_offset <= INSTANCE_BUFFER_SIZE,
"instance buffer exhausted"
);
let texture = self.image_atlases.texture(atlas_id).unwrap();
command_encoder.set_vertex_buffer(
shaders::GPUIImageVertexInputIndex_GPUIImageVertexInputIndexImages as u64,
Some(&self.instances),
*offset as u64,
);
command_encoder.set_vertex_bytes(
shaders::GPUIImageVertexInputIndex_GPUIImageVertexInputIndexAtlasSize as u64,
mem::size_of::<shaders::vector_float2>() as u64,
[vec2i(texture.width() as i32, texture.height() as i32).to_float2()].as_ptr()
as *const c_void,
);
command_encoder.set_fragment_texture(
shaders::GPUIImageFragmentInputIndex_GPUIImageFragmentInputIndexAtlas as u64,
Some(texture),
);
unsafe {
let buffer_contents = (self.instances.contents() as *mut u8)
.offset(*offset as isize)
as *mut shaders::GPUIImage;
std::ptr::copy_nonoverlapping(images.as_ptr(), buffer_contents, images.len());
}
command_encoder.draw_primitives_instanced(
metal::MTLPrimitiveType::Triangle,
0,
6,
images.len() as u64,
);
*offset = next_offset;
}
}
fn render_path_sprites(
&mut self,
layer_id: usize,
@@ -708,19 +838,23 @@ impl Renderer {
}
}
fn build_path_atlas_allocator(
pixel_format: MTLPixelFormat,
device: &metal::Device,
) -> AtlasAllocator {
fn build_path_atlas_texture_descriptor() -> metal::TextureDescriptor {
let texture_descriptor = metal::TextureDescriptor::new();
texture_descriptor.set_width(2048);
texture_descriptor.set_height(2048);
texture_descriptor.set_pixel_format(pixel_format);
texture_descriptor.set_pixel_format(MTLPixelFormat::R8Unorm);
texture_descriptor
.set_usage(metal::MTLTextureUsage::RenderTarget | metal::MTLTextureUsage::ShaderRead);
texture_descriptor.set_storage_mode(metal::MTLStorageMode::Private);
let path_atlases = AtlasAllocator::new(device.clone(), texture_descriptor);
path_atlases
texture_descriptor
}
fn build_image_atlas_texture_descriptor() -> metal::TextureDescriptor {
let texture_descriptor = metal::TextureDescriptor::new();
texture_descriptor.set_width(2048);
texture_descriptor.set_height(2048);
texture_descriptor.set_pixel_format(MTLPixelFormat::BGRA8Unorm);
texture_descriptor
}
fn align_offset(offset: &mut usize) {
@@ -803,9 +937,10 @@ mod shaders {
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
use pathfinder_geometry::vector::Vector2I;
use crate::{color::Color, geometry::vector::Vector2F};
use crate::{
color::Color,
geometry::vector::{Vector2F, Vector2I},
};
use std::mem;
include!(concat!(env!("OUT_DIR"), "/shaders.rs"));
+41 -10
View File
@@ -1,16 +1,19 @@
#include <simd/simd.h>
typedef struct {
typedef struct
{
vector_float2 viewport_size;
} GPUIUniforms;
typedef enum {
typedef enum
{
GPUIQuadInputIndexVertices = 0,
GPUIQuadInputIndexQuads = 1,
GPUIQuadInputIndexUniforms = 2,
} GPUIQuadInputIndex;
typedef struct {
typedef struct
{
vector_float2 origin;
vector_float2 size;
vector_uchar4 background_color;
@@ -22,13 +25,15 @@ typedef struct {
float corner_radius;
} GPUIQuad;
typedef enum {
typedef enum
{
GPUIShadowInputIndexVertices = 0,
GPUIShadowInputIndexShadows = 1,
GPUIShadowInputIndexUniforms = 2,
} GPUIShadowInputIndex;
typedef struct {
typedef struct
{
vector_float2 origin;
vector_float2 size;
float corner_radius;
@@ -36,18 +41,21 @@ typedef struct {
vector_uchar4 color;
} GPUIShadow;
typedef enum {
typedef enum
{
GPUISpriteVertexInputIndexVertices = 0,
GPUISpriteVertexInputIndexSprites = 1,
GPUISpriteVertexInputIndexViewportSize = 2,
GPUISpriteVertexInputIndexAtlasSize = 3,
} GPUISpriteVertexInputIndex;
typedef enum {
typedef enum
{
GPUISpriteFragmentInputIndexAtlas = 0,
} GPUISpriteFragmentInputIndex;
typedef struct {
typedef struct
{
vector_float2 origin;
vector_float2 target_size;
vector_float2 source_size;
@@ -56,14 +64,37 @@ typedef struct {
uint8_t compute_winding;
} GPUISprite;
typedef enum {
typedef enum
{
GPUIPathAtlasVertexInputIndexVertices = 0,
GPUIPathAtlasVertexInputIndexAtlasSize = 1,
} GPUIPathAtlasVertexInputIndex;
typedef struct {
typedef struct
{
vector_float2 xy_position;
vector_float2 st_position;
vector_float2 clip_rect_origin;
vector_float2 clip_rect_size;
} GPUIPathVertex;
typedef enum
{
GPUIImageVertexInputIndexVertices = 0,
GPUIImageVertexInputIndexImages = 1,
GPUIImageVertexInputIndexViewportSize = 2,
GPUIImageVertexInputIndexAtlasSize = 3,
} GPUIImageVertexInputIndex;
typedef enum
{
GPUIImageFragmentInputIndexAtlas = 0,
} GPUIImageFragmentInputIndex;
typedef struct
{
vector_float2 origin;
vector_float2 target_size;
vector_float2 source_size;
vector_float2 atlas_origin;
} GPUIImage;
@@ -217,6 +217,39 @@ fragment float4 sprite_fragment(
return color;
}
struct ImageFragmentInput {
float4 position [[position]];
float2 atlas_position;
};
vertex ImageFragmentInput image_vertex(
uint unit_vertex_id [[vertex_id]],
uint image_id [[instance_id]],
constant float2 *unit_vertices [[buffer(GPUIImageVertexInputIndexVertices)]],
constant GPUIImage *images [[buffer(GPUIImageVertexInputIndexImages)]],
constant float2 *viewport_size [[buffer(GPUIImageVertexInputIndexViewportSize)]],
constant float2 *atlas_size [[buffer(GPUIImageVertexInputIndexAtlasSize)]]
) {
float2 unit_vertex = unit_vertices[unit_vertex_id];
GPUIImage image = images[image_id];
float2 position = unit_vertex * image.target_size + image.origin;
float4 device_position = to_device_position(position, *viewport_size);
float2 atlas_position = (unit_vertex * image.source_size + image.atlas_origin) / *atlas_size;
return ImageFragmentInput {
device_position,
atlas_position,
};
}
fragment float4 image_fragment(
ImageFragmentInput input [[stage_in]],
texture2d<float> atlas [[ texture(GPUIImageFragmentInputIndexAtlas) ]]
) {
constexpr sampler atlas_sampler(mag_filter::linear, min_filter::linear);
return atlas.sample(atlas_sampler, input.atlas_position);
}
struct PathAtlasVertexOutput {
float4 position [[position]];
float2 st_position;