Skip to content
Snippets Groups Projects
Commit e6f86919 authored by John Mitchell's avatar John Mitchell
Browse files

Removed broken example for now..

parent 72b65951
No related branches found
No related tags found
No related merge requests found
use bevy::{
math::Vec2,
prelude::{
App as BevyApp, AssetServer, Camera2dBundle, Commands, Component, Handle, Input, KeyCode,
PluginGroup, Query, Res, ResMut, Sprite, SpriteBundle, Transform, With, Without,
},
render::color::Color,
window::{WindowDescriptor, WindowPlugin},
DefaultPlugins,
};
use kayak_font::{bevy::KayakFontPlugin, Alignment, KayakFont};
use renderer::{FontRenderPlugin, Text};
mod renderer;
const FONT_SIZE: f32 = 24.0;
const INITIAL_SIZE: Vec2 = Vec2::from_array([400.0, 300.0]);
const INITIAL_POS: Vec2 = Vec2::from_array([-200.0, 0.0]);
const INSTRUCTIONS: &str =
"Press 'A' and 'D' to shrink and grow the text box.\nPress \"Space\" to cycle text alignment.";
#[derive(Component)]
struct Instructions;
fn startup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(Camera2dBundle::default());
let font_handle: Handle<KayakFont> = asset_server.load("roboto.kttf");
// let font_handle: Handle<KayakFont> = asset_server.load("roboto.kayak_font");
commands
.spawn(Text {
horz_alignment: Alignment::Start,
color: Color::WHITE,
content: "Hello World! This text should wrap because it's kinda-super-long. How cool is that?!\nHere's a new line.\n\tHere's a tab.".into(),
font_size: FONT_SIZE,
line_height: FONT_SIZE * 1.2, // Firefox method of calculating default line heights see: https://developer.mozilla.org/en-US/docs/Web/CSS/line-height
position: INITIAL_POS,
size: INITIAL_SIZE,
})
.insert(font_handle.clone());
commands.spawn(SpriteBundle {
sprite: Sprite {
color: Color::DARK_GRAY,
custom_size: Some(INITIAL_SIZE),
..Default::default()
},
transform: Transform::from_xyz(
(INITIAL_SIZE.x / 2.0) + INITIAL_POS.x,
(-INITIAL_SIZE.y / 4.0) - 20.0,
-0.05,
),
..Default::default()
});
commands.spawn((
Text {
horz_alignment: Alignment::Middle,
color: Color::WHITE,
content: INSTRUCTIONS.into(),
font_size: 32.0,
line_height: 32.0 * 1.2, // Firefox method of calculating default line heights see: https://developer.mozilla.org/en-US/docs/Web/CSS/line-height
position: Vec2::new(-360.0, 250.0),
size: Vec2::new(720.0, 200.0),
},
Instructions,
font_handle.clone(),
));
}
fn control_text(
keyboard_input: ResMut<Input<KeyCode>>,
mut text_box: Query<&mut Text, Without<Instructions>>,
mut instructions: Query<&mut Text, With<Instructions>>,
mut bg: Query<&mut Sprite>,
) {
let speed =
if keyboard_input.pressed(KeyCode::LShift) || keyboard_input.pressed(KeyCode::RShift) {
2.5
} else {
1.0
};
if keyboard_input.just_pressed(KeyCode::Space) {
for mut text in text_box.iter_mut() {
let next = match text.horz_alignment {
Alignment::Start => Alignment::Middle,
Alignment::Middle => Alignment::End,
Alignment::End => Alignment::Start,
};
text.horz_alignment = next;
}
}
let speed = if keyboard_input.pressed(KeyCode::D) {
speed
} else if keyboard_input.pressed(KeyCode::A) {
-speed
} else {
return;
};
for mut text in text_box.iter_mut() {
text.size.x += speed;
text.position.x -= speed / 2.0;
let mut instructions = instructions.single_mut();
instructions.content = String::from(INSTRUCTIONS);
instructions
.content
.push_str(&format!("\nSize: {}", text.size.x));
}
for mut sprite in bg.iter_mut() {
sprite.custom_size.as_mut().unwrap().x += speed;
}
}
fn main() {
BevyApp::new()
.add_plugins(DefaultPlugins.set(WindowPlugin {
window: WindowDescriptor {
width: 1270.0,
height: 720.0,
title: String::from("UI Example"),
..Default::default()
},
..Default::default()
}))
.add_plugin(KayakFontPlugin)
.add_plugin(FontRenderPlugin)
.add_startup_system(startup)
.add_system(control_text)
.run();
}
use bevy::{
math::Vec2,
prelude::{Assets, Commands, Handle, Query, Rect, Res},
render::Extract,
};
use kayak_font::{KayakFont, TextProperties};
use super::{
pipeline::{ExtractCharBundle, ExtractedChar},
Text,
};
pub fn extract(
mut commands: Commands,
fonts: Extract<Res<Assets<KayakFont>>>,
texts: Extract<Query<(&Text, &Handle<KayakFont>)>>,
) {
let mut extracted_texts = Vec::new();
for (text, font_handle) in texts.iter() {
if let Some(font) = fonts.get(font_handle) {
let properties = TextProperties {
font_size: text.font_size,
line_height: text.line_height,
max_size: (text.size.x, text.size.y),
alignment: text.horz_alignment,
..Default::default()
};
let text_layout = font.measure(&text.content, properties);
for glyph_rect in text_layout.glyphs() {
let mut position = Vec2::from(glyph_rect.position);
position.y *= -1.0;
position += text.position;
let size = Vec2::from(glyph_rect.size);
extracted_texts.push(ExtractCharBundle {
extracted_quad: ExtractedChar {
font_handle: Some(font_handle.clone()),
rect: Rect {
min: position,
max: position + size,
},
color: text.color,
vertex_index: 0,
char_id: font.get_char_id(glyph_rect.content).unwrap(),
z_index: 0.0,
},
});
}
}
}
commands.spawn_batch(extracted_texts);
}
use bevy::{
core_pipeline::core_2d::Transparent2d,
prelude::{Assets, HandleUntyped, Plugin, Res, ResMut},
reflect::TypeUuid,
render::{
render_asset::RenderAssets,
render_phase::DrawFunctions,
render_resource::Shader,
renderer::{RenderDevice, RenderQueue},
texture::Image,
RenderApp, RenderStage,
},
};
use kayak_font::bevy::FontTextureCache;
use self::pipeline::{DrawUI, FontPipeline, QuadMeta};
mod extract;
pub mod pipeline;
mod text;
pub use text::*;
pub const FONT_SHADER_HANDLE: HandleUntyped =
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 7604018236855288450);
pub struct FontRenderPlugin;
impl Plugin for FontRenderPlugin {
fn build(&self, app: &mut bevy::prelude::App) {
let mut shaders = app.world.get_resource_mut::<Assets<Shader>>().unwrap();
let unified_shader = Shader::from_wgsl(include_str!("shader.wgsl"));
shaders.set_untracked(FONT_SHADER_HANDLE, unified_shader);
let render_app = app.sub_app_mut(RenderApp);
render_app
.init_resource::<QuadMeta>()
.init_resource::<FontPipeline>()
.add_system_to_stage(RenderStage::Extract, extract::extract)
.add_system_to_stage(RenderStage::Prepare, pipeline::prepare_quads)
.add_system_to_stage(RenderStage::Queue, pipeline::queue_quads)
.add_system_to_stage(RenderStage::Queue, create_and_update_font_cache_texture);
let draw_quad = DrawUI::new(&mut render_app.world);
render_app
.world
.get_resource::<DrawFunctions<Transparent2d>>()
.unwrap()
.write()
.add(draw_quad);
}
}
fn create_and_update_font_cache_texture(
device: Res<RenderDevice>,
queue: Res<RenderQueue>,
pipeline: Res<FontPipeline>,
mut font_texture_cache: ResMut<FontTextureCache>,
images: Res<RenderAssets<Image>>,
) {
font_texture_cache.process_new(&device, &queue, pipeline.into_inner(), &images);
}
use bevy::{
core_pipeline::core_2d::Transparent2d,
ecs::system::{
lifetimeless::{Read, SQuery, SRes},
SystemState,
},
math::{Mat4, Quat, Vec2, Vec3, Vec4},
prelude::{
Bundle, Component, Entity, FromWorld, Handle, Query, Rect, Res, ResMut, Resource, World,
},
render::{
color::Color,
render_phase::{Draw, DrawFunctions, RenderPhase, TrackedRenderPass},
render_resource::{
BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout,
BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, BlendComponent,
BlendFactor, BlendOperation, BlendState, BufferBindingType, BufferSize, BufferUsages,
BufferVec, CachedRenderPipelineId, ColorTargetState, ColorWrites, FragmentState,
FrontFace, MultisampleState, PipelineCache, PolygonMode, PrimitiveState,
PrimitiveTopology, RenderPipelineDescriptor, SamplerBindingType, Shader, ShaderStages,
ShaderType, TextureFormat, TextureSampleType, TextureViewDimension, VertexAttribute,
VertexBufferLayout, VertexFormat, VertexState, VertexStepMode,
},
renderer::{RenderDevice, RenderQueue},
texture::{BevyDefault, GpuImage},
view::{ViewUniformOffset, ViewUniforms},
},
utils::FloatOrd,
};
use bytemuck::{Pod, Zeroable};
use kayak_font::{
bevy::{FontRenderingPipeline, FontTextureCache},
KayakFont,
};
use super::FONT_SHADER_HANDLE;
#[derive(Resource)]
pub struct FontPipeline {
view_layout: BindGroupLayout,
pub(crate) font_image_layout: BindGroupLayout,
pipeline: CachedRenderPipelineId,
empty_font_texture: (GpuImage, BindGroup),
}
const QUAD_VERTEX_POSITIONS: &[Vec3] = &[
Vec3::from_array([0.0, 0.0, 0.0]),
Vec3::from_array([1.0, 1.0, 0.0]),
Vec3::from_array([0.0, 1.0, 0.0]),
Vec3::from_array([0.0, 0.0, 0.0]),
Vec3::from_array([1.0, 0.0, 0.0]),
Vec3::from_array([1.0, 1.0, 0.0]),
];
impl FontRenderingPipeline for FontPipeline {
fn get_font_image_layout(&self) -> &BindGroupLayout {
&self.font_image_layout
}
}
impl FromWorld for FontPipeline {
fn from_world(world: &mut World) -> Self {
let world = world.cell();
let render_device = world.get_resource::<RenderDevice>().unwrap();
let mut pipeline_cache = world.get_resource_mut::<PipelineCache>().unwrap();
let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
entries: &[BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: true,
min_binding_size: BufferSize::new(144),
},
count: None,
}],
label: Some("ui_view_layout"),
});
// Used by fonts
let font_image_layout =
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
entries: &[
BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Texture {
multisampled: false,
sample_type: TextureSampleType::Float { filterable: true },
view_dimension: TextureViewDimension::D2Array,
},
count: None,
},
BindGroupLayoutEntry {
binding: 1,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Sampler(SamplerBindingType::Filtering),
count: None,
},
],
label: Some("text_image_layout"),
});
let vertex_buffer_layout = VertexBufferLayout {
array_stride: 60,
step_mode: VertexStepMode::Vertex,
attributes: vec![
VertexAttribute {
format: VertexFormat::Float32x3,
offset: 0,
shader_location: 0,
},
VertexAttribute {
format: VertexFormat::Float32x4,
offset: 12,
shader_location: 1,
},
VertexAttribute {
format: VertexFormat::Float32x4,
offset: 28,
shader_location: 2,
},
VertexAttribute {
format: VertexFormat::Float32x4,
offset: 44,
shader_location: 3,
},
],
};
let empty_font_texture = FontTextureCache::get_empty(&render_device, &font_image_layout);
let pipeline_desc = RenderPipelineDescriptor {
vertex: VertexState {
shader: FONT_SHADER_HANDLE.typed::<Shader>(),
entry_point: "vertex".into(),
shader_defs: vec![],
buffers: vec![vertex_buffer_layout],
},
fragment: Some(FragmentState {
shader: FONT_SHADER_HANDLE.typed::<Shader>(),
shader_defs: vec![],
entry_point: "fragment".into(),
targets: vec![Some(ColorTargetState {
format: TextureFormat::bevy_default(),
blend: Some(BlendState {
color: BlendComponent {
src_factor: BlendFactor::SrcAlpha,
dst_factor: BlendFactor::OneMinusSrcAlpha,
operation: BlendOperation::Add,
},
alpha: BlendComponent {
src_factor: BlendFactor::One,
dst_factor: BlendFactor::One,
operation: BlendOperation::Add,
},
}),
write_mask: ColorWrites::ALL,
})],
}),
layout: Some(vec![view_layout.clone(), font_image_layout.clone()]),
primitive: PrimitiveState {
front_face: FrontFace::Ccw,
cull_mode: None,
polygon_mode: PolygonMode::Fill,
conservative: false,
topology: PrimitiveTopology::TriangleList,
strip_index_format: None,
unclipped_depth: false,
},
depth_stencil: None,
multisample: MultisampleState {
count: 4,
mask: !0,
alpha_to_coverage_enabled: false,
},
label: Some("font_pipeline".into()),
};
FontPipeline {
pipeline: pipeline_cache.queue_render_pipeline(pipeline_desc),
view_layout,
font_image_layout,
empty_font_texture,
}
}
}
#[derive(Debug, Bundle)]
pub struct ExtractCharBundle {
pub(crate) extracted_quad: ExtractedChar,
}
#[derive(Debug, Component, Clone)]
pub struct ExtractedChar {
pub rect: Rect,
pub color: Color,
pub vertex_index: usize,
pub char_id: u32,
pub z_index: f32,
pub font_handle: Option<Handle<KayakFont>>,
}
#[repr(C)]
#[derive(Copy, Clone, Pod, Zeroable)]
struct QuadVertex {
pub position: [f32; 3],
pub color: [f32; 4],
pub uv: [f32; 4],
pub pos_size: [f32; 4],
}
#[repr(C)]
#[derive(Copy, Clone, ShaderType)]
struct QuadType {
pub t: i32,
}
#[derive(Resource)]
pub struct QuadMeta {
vertices: BufferVec<QuadVertex>,
view_bind_group: Option<BindGroup>,
}
impl Default for QuadMeta {
fn default() -> Self {
Self {
vertices: BufferVec::new(BufferUsages::VERTEX),
view_bind_group: None,
}
}
}
pub fn prepare_quads(
render_device: Res<RenderDevice>,
render_queue: Res<RenderQueue>,
mut sprite_meta: ResMut<QuadMeta>,
mut extracted_quads: Query<&mut ExtractedChar>,
) {
let extracted_sprite_len = extracted_quads.iter_mut().len();
// don't create buffers when there are no quads
if extracted_sprite_len == 0 {
return;
}
sprite_meta.vertices.clear();
sprite_meta.vertices.reserve(
extracted_sprite_len * QUAD_VERTEX_POSITIONS.len(),
&render_device,
);
for (i, mut extracted_sprite) in extracted_quads.iter_mut().enumerate() {
let sprite_rect = extracted_sprite.rect;
let color = extracted_sprite.color.as_linear_rgba_f32();
let uv_min = Vec2::ZERO;
let uv_max = Vec2::ONE;
let bottom_left = Vec4::new(uv_min.x, uv_max.y, extracted_sprite.char_id as f32, 0.0);
let top_left = Vec4::new(uv_min.x, uv_min.y, extracted_sprite.char_id as f32, 0.0);
let top_right = Vec4::new(uv_max.x, uv_min.y, extracted_sprite.char_id as f32, 0.0);
let bottom_right = Vec4::new(uv_max.x, uv_max.y, extracted_sprite.char_id as f32, 0.0);
let uvs: [[f32; 4]; 6] = [
bottom_left.into(),
top_right.into(),
top_left.into(),
bottom_left.into(),
bottom_right.into(),
top_right.into(),
];
extracted_sprite.vertex_index = i;
for (index, vertex_position) in QUAD_VERTEX_POSITIONS.iter().enumerate() {
let world = Mat4::from_scale_rotation_translation(
sprite_rect.size().extend(1.0),
Quat::default(),
sprite_rect.min.extend(0.0),
);
let final_position = (world * Vec3::from(*vertex_position).extend(1.0)).truncate();
sprite_meta.vertices.push(QuadVertex {
position: final_position.into(),
color,
uv: uvs[index],
pos_size: [
sprite_rect.min.x,
sprite_rect.min.y,
sprite_rect.size().x,
sprite_rect.size().y,
],
});
}
}
sprite_meta
.vertices
.write_buffer(&render_device, &render_queue);
}
pub fn queue_quads(
draw_functions: Res<DrawFunctions<Transparent2d>>,
render_device: Res<RenderDevice>,
mut sprite_meta: ResMut<QuadMeta>,
view_uniforms: Res<ViewUniforms>,
quad_pipeline: Res<FontPipeline>,
mut extracted_sprites: Query<(Entity, &ExtractedChar)>,
mut views: Query<&mut RenderPhase<Transparent2d>>,
) {
if let Some(view_binding) = view_uniforms.uniforms.binding() {
sprite_meta.view_bind_group = Some(render_device.create_bind_group(&BindGroupDescriptor {
entries: &[BindGroupEntry {
binding: 0,
resource: view_binding,
}],
label: Some("quad_view_bind_group"),
layout: &quad_pipeline.view_layout,
}));
let draw_quad = draw_functions.read().get_id::<DrawUI>().unwrap();
for mut transparent_phase in views.iter_mut() {
for (entity, quad) in extracted_sprites.iter_mut() {
transparent_phase.add(Transparent2d {
draw_function: draw_quad,
pipeline: quad_pipeline.pipeline,
entity,
sort_key: FloatOrd(quad.z_index),
batch_range: None,
});
}
}
}
}
pub struct DrawUI {
params: SystemState<(
SRes<QuadMeta>,
SRes<FontPipeline>,
SRes<PipelineCache>,
SRes<FontTextureCache>,
SQuery<Read<ViewUniformOffset>>,
SQuery<Read<ExtractedChar>>,
)>,
}
impl DrawUI {
pub fn new(world: &mut World) -> Self {
Self {
params: SystemState::new(world),
}
}
}
impl Draw<Transparent2d> for DrawUI {
fn draw<'w>(
&mut self,
world: &'w World,
pass: &mut TrackedRenderPass<'w>,
view: Entity,
item: &Transparent2d,
) {
let (quad_meta, unified_pipeline, pipelines, font_texture_cache, views, quads) =
self.params.get(world);
let view_uniform = views.get(view).unwrap();
let quad_meta = quad_meta.into_inner();
let extracted_quad = quads.get(item.entity).unwrap();
if let Some(pipeline) = pipelines.into_inner().get_render_pipeline(item.pipeline) {
pass.set_render_pipeline(pipeline);
pass.set_vertex_buffer(0, quad_meta.vertices.buffer().unwrap().slice(..));
pass.set_bind_group(
0,
quad_meta.view_bind_group.as_ref().unwrap(),
&[view_uniform.offset],
);
let unified_pipeline = unified_pipeline.into_inner();
if let Some(font_handle) = extracted_quad.font_handle.as_ref() {
if let Some(image_bindings) =
font_texture_cache.into_inner().get_binding(font_handle)
{
pass.set_bind_group(1, image_bindings, &[]);
} else {
pass.set_bind_group(1, &unified_pipeline.empty_font_texture.1, &[]);
}
} else {
pass.set_bind_group(1, &unified_pipeline.empty_font_texture.1, &[]);
}
pass.draw(
(extracted_quad.vertex_index * QUAD_VERTEX_POSITIONS.len()) as u32
..((extracted_quad.vertex_index + 1) * QUAD_VERTEX_POSITIONS.len()) as u32,
0..1,
);
}
}
}
struct View {
view_proj: mat4x4<f32>,
world_position: vec3<f32>,
}
@group(0) @binding(0)
var<uniform> view: View;
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) color: vec4<f32>,
@location(1) uv: vec3<f32>,
@location(2) pos: vec2<f32>,
@location(3) size: vec2<f32>,
@location(4) screen_position: vec2<f32>,
@location(5) border_radius: f32,
};
@vertex
fn vertex(
@location(0) vertex_position: vec3<f32>,
@location(1) vertex_color: vec4<f32>,
@location(2) vertex_uv: vec4<f32>,
@location(3) vertex_pos_size: vec4<f32>,
) -> VertexOutput {
var out: VertexOutput;
out.color = vertex_color;
out.pos = vertex_pos_size.xy;
out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
out.screen_position = (view.view_proj * vec4<f32>(vertex_position, 1.0)).xy;
out.uv = vertex_uv.xyz;
out.size = vertex_pos_size.zw;
out.border_radius = vertex_uv.w;
return out;
}
@group(1) @binding(0)
var font_texture: texture_2d_array<f32>;
@group(1) @binding(1)
var font_sampler: sampler;
@fragment
fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
var px_range = 4.5;
var tex_dimensions = textureDimensions(font_texture);
var msdf_unit = vec2<f32>(px_range, px_range) / vec2<f32>(f32(tex_dimensions.x), f32(tex_dimensions.y));
var x = textureSample(font_texture, font_sampler, vec2<f32>(in.uv.x, in.uv.y), i32(in.uv.z));
var v = max(min(x.r, x.g), min(max(x.r, x.g), x.b));
var sig_dist = (v - 0.5) * dot(msdf_unit, 0.5 / fwidth(in.uv.xy));
var a = clamp(sig_dist + 0.5, 0.0, 1.0);
return vec4<f32>(in.color.rgb, a);
}
\ No newline at end of file
use bevy::{math::Vec2, prelude::Component, render::color::Color};
use kayak_font::Alignment;
#[derive(Component)]
pub struct Text {
pub horz_alignment: Alignment,
pub content: String,
pub position: Vec2,
pub size: Vec2,
pub font_size: f32,
pub line_height: f32,
pub color: Color,
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment