text: new atlased rendering finally works!

removed old render-to-texture rendering
This commit is contained in:
Carter Anderson 2020-06-20 12:39:12 -07:00
parent da3d6983a7
commit ecea30cadb
6 changed files with 101 additions and 71 deletions

View File

@ -37,6 +37,10 @@ impl<T> Handle<T> {
}
}
/// Gets a handle for the given type that has this handle's id. This is useful when an
/// asset is derived from another asset. In this case, a common handle can be used to
/// correlate them.
/// NOTE: This pattern might eventually be replaced by a more formal asset dependency system.
pub fn as_handle<U>(&self) -> Handle<U> {
Handle::from_id(self.id)
}

View File

@ -6,9 +6,16 @@ layout(location = 2) in vec2 Vertex_Uv;
layout(location = 0) out vec2 v_Uv;
// TODO: remove UI shader def and replace with generic "Camera" when its easier to manually bind global RenderResourceBindings
#ifdef UI_CAMERA
layout(set = 0, binding = 0) uniform UiCamera {
mat4 ViewProj;
};
# else
layout(set = 0, binding = 0) uniform Camera2d {
mat4 ViewProj;
};
#endif
// TODO: merge dimensions into "sprites" buffer when that is supported in the Uniforms derive abstraction
layout(set = 1, binding = 0) uniform TextureAtlas_size {

View File

@ -1,9 +1,10 @@
use crate::{Font, FontAtlasSet};
use ab_glyph::{Glyph, PxScale, ScaleFont};
use bevy_asset::Assets;
use bevy_render::{
draw::{Draw, DrawContext, DrawError, Drawable},
mesh,
pipeline::PipelineSpecialization,
pipeline::{PipelineSpecialization, ShaderSpecialization},
render_resource::{
AssetRenderResourceBindings, BindGroup, BufferUsage, RenderResourceBindings,
RenderResourceId,
@ -12,13 +13,13 @@ use bevy_render::{
};
use bevy_sprite::{TextureAtlas, TextureAtlasSprite};
use glam::Vec3;
use std::collections::HashSet;
pub struct TextStyle {
pub font_size: f32,
pub color: Color,
}
#[allow(dead_code)]
pub struct DrawableText<'a> {
font: &'a Font,
font_atlas_set: &'a FontAtlasSet,
@ -56,10 +57,16 @@ impl<'a> DrawableText<'a> {
impl<'a> Drawable for DrawableText<'a> {
fn draw(&mut self, draw: &mut Draw, context: &mut DrawContext) -> Result<(), DrawError> {
let mut shader_defs = HashSet::new();
shader_defs.insert("UI_CAMERA".to_string());
context.set_pipeline(
draw,
bevy_sprite::SPRITE_SHEET_PIPELINE_HANDLE,
PipelineSpecialization::empty(),
// TODO: remove this shader def specialization when its easier to manually bind global render resources to specific bind groups
&PipelineSpecialization {
shader_specialization: ShaderSpecialization { shader_defs },
..Default::default()
},
)?;
let render_resource_context = &**context.render_resource_context;
@ -80,37 +87,73 @@ impl<'a> Drawable for DrawableText<'a> {
}
}
let current_position = Vec3::new(0.0, 0.0, 0.0);
// set global bindings
context.set_bind_groups_from_bindings(draw, &mut [self.render_resource_bindings])?;
// NOTE: this uses ab_glyph apis directly. it _might_ be a good idea to add our own layer on top
let font = &self.font.font;
let scale = PxScale::from(self.style.font_size);
let scaled_font = ab_glyph::Font::as_scaled(&font, scale);
let mut caret = self.position;
let mut last_glyph: Option<Glyph> = None;
// set local per-character bindings
for character in self.text.chars() {
if character.is_control() {
continue;
}
let glyph = scaled_font.scaled_glyph(character);
if let Some(last_glyph) = last_glyph.take() {
caret.set_x(caret.x() + scaled_font.kern(last_glyph.id, glyph.id));
}
if let Some(glyph_atlas_info) = self
.font_atlas_set
.get_glyph_atlas_info(self.style.font_size, character)
{
let atlas_render_resource_bindings = self
.asset_render_resource_bindings
.get_mut(glyph_atlas_info.texture_atlas)
.unwrap();
context
.set_bind_groups_from_bindings(draw, &mut [atlas_render_resource_bindings])?;
let sprite_buffer = TextureAtlasSprite {
index: glyph_atlas_info.char_index,
position: current_position,
scale: 1.0,
};
if let Some(outlined) = scaled_font.outline_glyph(glyph.clone()) {
let texture_atlas = self
.texture_atlases
.get(&glyph_atlas_info.texture_atlas)
.unwrap();
let glyph_rect = texture_atlas.textures[glyph_atlas_info.char_index as usize];
let glyph_width = glyph_rect.width();
let glyph_height = glyph_rect.height();
let atlas_render_resource_bindings = self
.asset_render_resource_bindings
.get_mut(glyph_atlas_info.texture_atlas)
.unwrap();
context.set_bind_groups_from_bindings(
draw,
&mut [atlas_render_resource_bindings],
)?;
let sprite_buffer = context
.shared_buffers
.get_buffer(&sprite_buffer, BufferUsage::UNIFORM)
.unwrap();
let sprite_bind_group = BindGroup::build().add_binding(0, sprite_buffer).finish();
context.create_bind_group_resource(2, &sprite_bind_group)?;
draw.set_bind_group(2, &sprite_bind_group);
draw.draw_indexed(indices.clone(), 0, 0..1);
let bounds = outlined.px_bounds();
let offset = scaled_font.descent() + glyph_height;
let sprite_buffer = TextureAtlasSprite {
index: glyph_atlas_info.char_index,
position: caret
+ Vec3::new(
0.0 + glyph_width / 2.0 + bounds.min.x,
glyph_height / 2.0 - bounds.min.y - offset,
0.0,
),
scale: 1.0,
};
let sprite_buffer = context
.shared_buffers
.get_buffer(&sprite_buffer, BufferUsage::UNIFORM)
.unwrap();
let sprite_bind_group =
BindGroup::build().add_binding(0, sprite_buffer).finish();
context.create_bind_group_resource(2, &sprite_bind_group)?;
draw.set_bind_group(2, &sprite_bind_group);
draw.draw_indexed(indices.clone(), 0, 0..1);
}
}
caret.set_x(caret.x() + scaled_font.h_advance(glyph.id));
last_glyph = Some(glyph);
}
Ok(())
}

View File

@ -18,12 +18,12 @@ pub struct UiEntity {
impl Default for UiEntity {
fn default() -> Self {
UiEntity {
mesh: QUAD_HANDLE,
render_pipelines: RenderPipelines::from_handles(&[UI_PIPELINE_HANDLE]),
node: Default::default(),
quad: Default::default(),
mesh: QUAD_HANDLE,
material: Default::default(),
draw: Default::default(),
render_pipelines: RenderPipelines::from_handles(&[UI_PIPELINE_HANDLE]),
}
}
}
@ -32,24 +32,17 @@ impl Default for UiEntity {
pub struct LabelEntity {
pub node: Node,
pub quad: Quad,
pub mesh: Handle<Mesh>, // TODO: maybe abstract this out
pub material: Handle<ColorMaterial>,
pub draw: Draw,
pub render_pipelines: RenderPipelines,
pub label: Label,
}
impl Default for LabelEntity {
fn default() -> Self {
LabelEntity {
label: Label::default(),
node: Default::default(),
quad: Default::default(),
mesh: QUAD_HANDLE,
// NOTE: labels each get their own material.
material: Handle::new(), // TODO: maybe abstract this out
draw: Default::default(),
render_pipelines: RenderPipelines::from_handles(&[UI_PIPELINE_HANDLE]),
label: Label::default(),
}
}
}

View File

@ -5,10 +5,10 @@ use bevy_render::{
texture::Texture,
Color,
};
use bevy_sprite::{ColorMaterial, ComMut, Quad, TextureAtlas};
use bevy_sprite::{ComMut, Quad, TextureAtlas};
use bevy_text::{DrawableText, Font, FontAtlasSet, TextStyle};
use legion::prelude::{Com, Res, ResMut};
use glam::Vec3;
use legion::prelude::{Com, Res, ResMut};
pub struct Label {
pub text: String,
@ -32,46 +32,29 @@ impl Default for Label {
impl Label {
// PERF: this is horrendously inefficient. (1) new texture per label per frame (2) no atlas
pub fn label_system(
mut color_materials: ResMut<Assets<ColorMaterial>>,
mut textures: ResMut<Assets<Texture>>,
fonts: Res<Assets<Font>>,
mut font_atlas_sets: ResMut<Assets<FontAtlasSet>>,
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
label: Com<Label>,
quad: Com<Quad>,
color_material_handle: Com<Handle<ColorMaterial>>,
) {
// ensure the texture is at least 1x1
let width = quad.size.x().max(1.0);
let height = quad.size.y().max(1.0);
if let Some(font) = fonts.get(&label.font) {
let font_atlases = font_atlas_sets
.get_or_insert_with(Handle::from_id(label.font.id), || {
FontAtlasSet::new(label.font)
});
font_atlases.add_glyphs_to_atlas(
&fonts,
&mut texture_atlases,
&mut textures,
label.style.font_size,
&label.text,
);
let material = color_materials.get_or_insert_with(*color_material_handle, || {
ColorMaterial::from(Handle::<Texture>::new())
let font_atlases = font_atlas_sets
.get_or_insert_with(Handle::from_id(label.font.id), || {
FontAtlasSet::new(label.font)
});
let texture = font.render_text(
&label.text,
label.style.color,
label.style.font_size,
width as usize,
height as usize,
);
material.texture = Some(textures.add(texture));
}
// TODO: this call results in one or more TextureAtlases, whose render resources are created in the RENDER_GRAPH_SYSTEMS
// stage. That logic runs _before_ the DRAW stage, which means we cant call add_glyphs_to_atlas in the draw stage
// without or render resources being a frame behind. Therefore glyph atlasing either needs its own system or the TextureAtlas
// resource generation needs to happen AFTER the render graph systems. maybe draw systems should execute within the
// render graph so ordering like this can be taken into account? Maybe the RENDER_GRAPH_SYSTEMS stage should be removed entirely
// in favor of node.update()? Regardless, in the immediate short term the current approach is fine.
font_atlases.add_glyphs_to_atlas(
&fonts,
&mut texture_atlases,
&mut textures,
label.style.font_size,
&label.text,
);
}
pub fn draw_label_system(
@ -85,6 +68,7 @@ impl Label {
label: Com<Label>,
quad: Com<Quad>,
) {
let position = quad.position - quad.size / 2.0;
let mut drawable_text = DrawableText::new(
fonts.get(&label.font).unwrap(),
font_atlas_sets
@ -93,7 +77,7 @@ impl Label {
&texture_atlases,
&mut render_resource_bindings,
&mut asset_render_resource_bindings,
Vec3::new(quad.position.x(), quad.position.y(), 0.0),
Vec3::new(position.x(), position.y(), 0.0),
&label.style,
&label.text,
);

View File

@ -15,7 +15,7 @@ fn main() {
fn text_update_system(diagnostics: Res<Diagnostics>, mut label: ComMut<Label>) {
if let Some(fps) = diagnostics.get(FrameTimeDiagnosticsPlugin::FPS) {
if let Some(average) = fps.average() {
label.text = format!("FPS: {}", average);
label.text = format!("FPS: {:.2}", average);
}
}
}
@ -25,7 +25,6 @@ fn setup(command_buffer: &mut CommandBuffer, asset_server: Res<AssetServer>) {
command_buffer
.build()
// 2d camera
.add_entity(OrthographicCameraEntity::default())
.add_entity(OrthographicCameraEntity::ui())
// texture
.add_entity(LabelEntity {