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> {
|
pub fn as_handle<U>(&self) -> Handle<U> {
|
||||||
Handle::from_id(self.id)
|
Handle::from_id(self.id)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user