text: new atlased rendering finally works!
removed old render-to-texture rendering
This commit is contained in:
parent
da3d6983a7
commit
ecea30cadb
@ -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)
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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(())
|
||||
}
|
||||
|
||||
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
);
|
||||
|
||||
@ -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 {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user