Optimize grouping of path sprites by layer/atlas

Avoid filtering sprites redundantly on every layer and allocating intermediate vectors to group by atlas.

Co-Authored-By: Max Brunsfeld <maxbrunsfeld@gmail.com>
This commit is contained in:
Nathan Sobo 2021-03-30 18:03:00 -06:00
parent 4cdce070c4
commit 77b6a29f5f
2 changed files with 88 additions and 58 deletions

View File

@ -3,7 +3,7 @@ use crate::{
color::ColorU,
geometry::{
rect::RectF,
vector::{vec2f, vec2i, Vector2F, Vector2I},
vector::{vec2f, vec2i, Vector2F},
},
platform,
scene::Layer,
@ -13,7 +13,7 @@ use anyhow::{anyhow, Result};
use cocoa::foundation::NSUInteger;
use metal::{MTLPixelFormat, MTLResourceOptions, NSRange};
use shaders::{ToFloat2 as _, ToUchar4 as _};
use std::{collections::HashMap, ffi::c_void, mem, sync::Arc};
use std::{collections::HashMap, ffi::c_void, iter::Peekable, mem, sync::Arc};
const SHADERS_METALLIB: &'static [u8] =
include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
@ -34,7 +34,7 @@ pub struct Renderer {
struct PathSprite {
layer_id: usize,
atlas_id: usize,
sprite: shaders::GPUISprite,
shader_data: shaders::GPUISprite,
}
impl Renderer {
@ -152,7 +152,7 @@ impl Renderer {
stencils.push(PathSprite {
layer_id,
atlas_id,
sprite: shaders::GPUISprite {
shader_data: shaders::GPUISprite {
origin: origin.floor().to_float2(),
size: size.to_float2(),
atlas_origin: atlas_origin.to_float2(),
@ -286,15 +286,15 @@ impl Renderer {
zfar: 1.0,
});
let mut path_sprites = path_sprites.into_iter().peekable();
for (layer_id, layer) in scene.layers().iter().enumerate() {
self.clip(scene, layer, drawable_size, command_encoder);
self.render_shadows(scene, layer, offset, drawable_size, command_encoder);
self.render_quads(scene, layer, offset, drawable_size, command_encoder);
// TODO: Pass sprites relevant to this layer in a more efficient manner.
self.render_path_sprites(
scene,
layer,
path_sprites.iter().filter(|s| s.layer_id == layer_id),
layer_id,
&mut path_sprites,
offset,
drawable_size,
command_encoder,
@ -573,26 +573,12 @@ impl Renderer {
fn render_path_sprites<'a>(
&mut self,
scene: &Scene,
layer: &Layer,
sprites: impl Iterator<Item = &'a PathSprite>,
layer_id: usize,
sprites: &mut Peekable<impl Iterator<Item = PathSprite>>,
offset: &mut usize,
drawable_size: Vector2F,
command_encoder: &metal::RenderCommandEncoderRef,
) {
let mut sprites = sprites.peekable();
if sprites.peek().is_none() {
return;
}
let mut sprites_by_atlas = HashMap::new();
for sprite in sprites {
sprites_by_atlas
.entry(sprite.atlas_id)
.or_insert_with(Vec::new)
.push(sprite.sprite);
}
command_encoder.set_render_pipeline_state(&self.sprite_pipeline_state);
command_encoder.set_vertex_buffer(
shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexVertices as u64,
@ -605,52 +591,95 @@ impl Renderer {
[drawable_size.to_float2()].as_ptr() as *const c_void,
);
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"
);
let mut atlas_id = None;
let mut atlas_sprite_count = 0;
align_offset(offset);
command_encoder.set_vertex_buffer(
shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexSprites as u64,
Some(&self.instances),
*offset as u64,
);
while let Some(sprite) = sprites.peek() {
if sprite.layer_id != layer_id {
break;
}
let texture = self.path_atlases.texture(atlas_id).unwrap();
command_encoder.set_vertex_bytes(
shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexAtlasSize 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::GPUISpriteFragmentInputIndex_GPUISpriteFragmentInputIndexAtlas as u64,
Some(texture),
);
let sprite = sprites.next().unwrap();
if let Some(atlas_id) = atlas_id.as_mut() {
if sprite.atlas_id != *atlas_id {
self.render_path_sprites_for_atlas(
offset,
*atlas_id,
atlas_sprite_count,
command_encoder,
);
*atlas_id = sprite.atlas_id;
atlas_sprite_count = 0;
align_offset(offset);
}
} else {
atlas_id = Some(sprite.atlas_id);
}
unsafe {
let buffer_contents = (self.instances.contents() as *mut u8)
.offset(*offset as isize)
as *mut shaders::GPUISprite;
std::ptr::copy_nonoverlapping(sprites.as_ptr(), buffer_contents, sprites.len());
*buffer_contents.offset(atlas_sprite_count as isize) = sprite.shader_data;
}
self.instances.did_modify_range(NSRange {
location: *offset as u64,
length: (next_offset - *offset) as u64,
});
*offset = next_offset;
command_encoder.draw_primitives_instanced(
metal::MTLPrimitiveType::Triangle,
0,
6,
sprites.len() as u64,
atlas_sprite_count += 1;
}
if let Some(atlas_id) = atlas_id {
self.render_path_sprites_for_atlas(
offset,
atlas_id,
atlas_sprite_count,
command_encoder,
);
}
}
fn render_path_sprites_for_atlas<'a>(
&mut self,
offset: &mut usize,
atlas_id: usize,
sprite_count: usize,
command_encoder: &metal::RenderCommandEncoderRef,
) {
let next_offset = *offset + sprite_count * mem::size_of::<shaders::GPUISprite>();
assert!(
next_offset <= INSTANCE_BUFFER_SIZE,
"instance buffer exhausted"
);
command_encoder.set_vertex_buffer(
shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexSprites as u64,
Some(&self.instances),
*offset as u64,
);
let texture = self.path_atlases.texture(atlas_id).unwrap();
command_encoder.set_fragment_texture(
shaders::GPUISpriteFragmentInputIndex_GPUISpriteFragmentInputIndexAtlas as u64,
Some(texture),
);
command_encoder.set_vertex_bytes(
shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexAtlasSize 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,
);
self.instances.did_modify_range(NSRange {
location: *offset as u64,
length: (next_offset - *offset) as u64,
});
*offset = next_offset;
command_encoder.draw_primitives_instanced(
metal::MTLPrimitiveType::Triangle,
0,
6,
sprite_count as u64,
);
}
}
fn build_path_atlas_allocator(

View File

@ -1076,6 +1076,7 @@ impl BufferView {
}
fn blink_cursors(&mut self, epoch: usize, ctx: &mut ViewContext<Self>) {
return;
if epoch == self.blink_epoch && self.focused && !self.blinking_paused {
self.cursors_visible = !self.cursors_visible;
ctx.notify();