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> { pub fn as_handle<U>(&self) -> Handle<U> {
Handle::from_id(self.id) 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; 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 { layout(set = 0, binding = 0) uniform Camera2d {
mat4 ViewProj; mat4 ViewProj;
}; };
#endif
// TODO: merge dimensions into "sprites" buffer when that is supported in the Uniforms derive abstraction // TODO: merge dimensions into "sprites" buffer when that is supported in the Uniforms derive abstraction
layout(set = 1, binding = 0) uniform TextureAtlas_size { layout(set = 1, binding = 0) uniform TextureAtlas_size {

View File

@ -1,9 +1,10 @@
use crate::{Font, FontAtlasSet}; use crate::{Font, FontAtlasSet};
use ab_glyph::{Glyph, PxScale, ScaleFont};
use bevy_asset::Assets; use bevy_asset::Assets;
use bevy_render::{ use bevy_render::{
draw::{Draw, DrawContext, DrawError, Drawable}, draw::{Draw, DrawContext, DrawError, Drawable},
mesh, mesh,
pipeline::PipelineSpecialization, pipeline::{PipelineSpecialization, ShaderSpecialization},
render_resource::{ render_resource::{
AssetRenderResourceBindings, BindGroup, BufferUsage, RenderResourceBindings, AssetRenderResourceBindings, BindGroup, BufferUsage, RenderResourceBindings,
RenderResourceId, RenderResourceId,
@ -12,13 +13,13 @@ use bevy_render::{
}; };
use bevy_sprite::{TextureAtlas, TextureAtlasSprite}; use bevy_sprite::{TextureAtlas, TextureAtlasSprite};
use glam::Vec3; use glam::Vec3;
use std::collections::HashSet;
pub struct TextStyle { pub struct TextStyle {
pub font_size: f32, pub font_size: f32,
pub color: Color, pub color: Color,
} }
#[allow(dead_code)]
pub struct DrawableText<'a> { pub struct DrawableText<'a> {
font: &'a Font, font: &'a Font,
font_atlas_set: &'a FontAtlasSet, font_atlas_set: &'a FontAtlasSet,
@ -56,10 +57,16 @@ impl<'a> DrawableText<'a> {
impl<'a> Drawable for DrawableText<'a> { impl<'a> Drawable for DrawableText<'a> {
fn draw(&mut self, draw: &mut Draw, context: &mut DrawContext) -> Result<(), DrawError> { 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( context.set_pipeline(
draw, draw,
bevy_sprite::SPRITE_SHEET_PIPELINE_HANDLE, 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; let render_resource_context = &**context.render_resource_context;
@ -80,25 +87,57 @@ impl<'a> Drawable for DrawableText<'a> {
} }
} }
let current_position = Vec3::new(0.0, 0.0, 0.0);
// set global bindings // set global bindings
context.set_bind_groups_from_bindings(draw, &mut [self.render_resource_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 // set local per-character bindings
for character in self.text.chars() { 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 if let Some(glyph_atlas_info) = self
.font_atlas_set .font_atlas_set
.get_glyph_atlas_info(self.style.font_size, character) .get_glyph_atlas_info(self.style.font_size, character)
{ {
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 let atlas_render_resource_bindings = self
.asset_render_resource_bindings .asset_render_resource_bindings
.get_mut(glyph_atlas_info.texture_atlas) .get_mut(glyph_atlas_info.texture_atlas)
.unwrap(); .unwrap();
context context.set_bind_groups_from_bindings(
.set_bind_groups_from_bindings(draw, &mut [atlas_render_resource_bindings])?; draw,
&mut [atlas_render_resource_bindings],
)?;
let bounds = outlined.px_bounds();
let offset = scaled_font.descent() + glyph_height;
let sprite_buffer = TextureAtlasSprite { let sprite_buffer = TextureAtlasSprite {
index: glyph_atlas_info.char_index, index: glyph_atlas_info.char_index,
position: current_position, 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, scale: 1.0,
}; };
@ -106,12 +145,16 @@ impl<'a> Drawable for DrawableText<'a> {
.shared_buffers .shared_buffers
.get_buffer(&sprite_buffer, BufferUsage::UNIFORM) .get_buffer(&sprite_buffer, BufferUsage::UNIFORM)
.unwrap(); .unwrap();
let sprite_bind_group = BindGroup::build().add_binding(0, sprite_buffer).finish(); let sprite_bind_group =
BindGroup::build().add_binding(0, sprite_buffer).finish();
context.create_bind_group_resource(2, &sprite_bind_group)?; context.create_bind_group_resource(2, &sprite_bind_group)?;
draw.set_bind_group(2, &sprite_bind_group); draw.set_bind_group(2, &sprite_bind_group);
draw.draw_indexed(indices.clone(), 0, 0..1); draw.draw_indexed(indices.clone(), 0, 0..1);
} }
} }
caret.set_x(caret.x() + scaled_font.h_advance(glyph.id));
last_glyph = Some(glyph);
}
Ok(()) Ok(())
} }
} }

View File

@ -18,12 +18,12 @@ pub struct UiEntity {
impl Default for UiEntity { impl Default for UiEntity {
fn default() -> Self { fn default() -> Self {
UiEntity { UiEntity {
mesh: QUAD_HANDLE,
render_pipelines: RenderPipelines::from_handles(&[UI_PIPELINE_HANDLE]),
node: Default::default(), node: Default::default(),
quad: Default::default(), quad: Default::default(),
mesh: QUAD_HANDLE,
material: Default::default(), material: Default::default(),
draw: 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 struct LabelEntity {
pub node: Node, pub node: Node,
pub quad: Quad, pub quad: Quad,
pub mesh: Handle<Mesh>, // TODO: maybe abstract this out
pub material: Handle<ColorMaterial>,
pub draw: Draw, pub draw: Draw,
pub render_pipelines: RenderPipelines,
pub label: Label, pub label: Label,
} }
impl Default for LabelEntity { impl Default for LabelEntity {
fn default() -> Self { fn default() -> Self {
LabelEntity { LabelEntity {
label: Label::default(),
node: Default::default(), node: Default::default(),
quad: 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(), 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, texture::Texture,
Color, Color,
}; };
use bevy_sprite::{ColorMaterial, ComMut, Quad, TextureAtlas}; use bevy_sprite::{ComMut, Quad, TextureAtlas};
use bevy_text::{DrawableText, Font, FontAtlasSet, TextStyle}; use bevy_text::{DrawableText, Font, FontAtlasSet, TextStyle};
use legion::prelude::{Com, Res, ResMut};
use glam::Vec3; use glam::Vec3;
use legion::prelude::{Com, Res, ResMut};
pub struct Label { pub struct Label {
pub text: String, pub text: String,
@ -32,24 +32,22 @@ impl Default for Label {
impl Label { impl Label {
// PERF: this is horrendously inefficient. (1) new texture per label per frame (2) no atlas // PERF: this is horrendously inefficient. (1) new texture per label per frame (2) no atlas
pub fn label_system( pub fn label_system(
mut color_materials: ResMut<Assets<ColorMaterial>>,
mut textures: ResMut<Assets<Texture>>, mut textures: ResMut<Assets<Texture>>,
fonts: Res<Assets<Font>>, fonts: Res<Assets<Font>>,
mut font_atlas_sets: ResMut<Assets<FontAtlasSet>>, mut font_atlas_sets: ResMut<Assets<FontAtlasSet>>,
mut texture_atlases: ResMut<Assets<TextureAtlas>>, mut texture_atlases: ResMut<Assets<TextureAtlas>>,
label: Com<Label>, 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 let font_atlases = font_atlas_sets
.get_or_insert_with(Handle::from_id(label.font.id), || { .get_or_insert_with(Handle::from_id(label.font.id), || {
FontAtlasSet::new(label.font) FontAtlasSet::new(label.font)
}); });
// 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( font_atlases.add_glyphs_to_atlas(
&fonts, &fonts,
&mut texture_atlases, &mut texture_atlases,
@ -57,21 +55,6 @@ impl Label {
label.style.font_size, label.style.font_size,
&label.text, &label.text,
); );
let material = color_materials.get_or_insert_with(*color_material_handle, || {
ColorMaterial::from(Handle::<Texture>::new())
});
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));
}
} }
pub fn draw_label_system( pub fn draw_label_system(
@ -85,6 +68,7 @@ impl Label {
label: Com<Label>, label: Com<Label>,
quad: Com<Quad>, quad: Com<Quad>,
) { ) {
let position = quad.position - quad.size / 2.0;
let mut drawable_text = DrawableText::new( let mut drawable_text = DrawableText::new(
fonts.get(&label.font).unwrap(), fonts.get(&label.font).unwrap(),
font_atlas_sets font_atlas_sets
@ -93,7 +77,7 @@ impl Label {
&texture_atlases, &texture_atlases,
&mut render_resource_bindings, &mut render_resource_bindings,
&mut asset_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.style,
&label.text, &label.text,
); );

View File

@ -15,7 +15,7 @@ fn main() {
fn text_update_system(diagnostics: Res<Diagnostics>, mut label: ComMut<Label>) { fn text_update_system(diagnostics: Res<Diagnostics>, mut label: ComMut<Label>) {
if let Some(fps) = diagnostics.get(FrameTimeDiagnosticsPlugin::FPS) { if let Some(fps) = diagnostics.get(FrameTimeDiagnosticsPlugin::FPS) {
if let Some(average) = fps.average() { 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 command_buffer
.build() .build()
// 2d camera // 2d camera
.add_entity(OrthographicCameraEntity::default())
.add_entity(OrthographicCameraEntity::ui()) .add_entity(OrthographicCameraEntity::ui())
// texture // texture
.add_entity(LabelEntity { .add_entity(LabelEntity {