kinda got text rendering working as well as multi draw call

This commit is contained in:
griffi-gh 2023-12-02 17:05:03 +01:00
parent a6bb48dddd
commit b120e0449b
8 changed files with 354 additions and 68 deletions

View file

@ -0,0 +1,70 @@
use std::time::Instant;
use glam::{UVec2, vec4};
use glium::{backend::glutin::SimpleWindowBuilder, Surface};
use winit::{
event::{Event, WindowEvent},
event_loop::{EventLoopBuilder, ControlFlow}
};
use kubi_ui::{
KubiUi,
element::{
UiElement,
progress_bar::ProgressBar,
container::{Container, Sides, Alignment},
rect::Rect, text::Text
},
interaction::IntoInteractable,
UiSize,
UiDirection, IfModified,
};
use kubi_ui_glium::GliumUiRenderer;
fn main() {
kubi_logging::init();
let event_loop = EventLoopBuilder::new().build().unwrap();
let (window, display) = SimpleWindowBuilder::new().build(&event_loop);
let mut kui = KubiUi::new();
let mut backend = GliumUiRenderer::new(&display);
let instant = Instant::now();
event_loop.run(|event, window_target| {
window_target.set_control_flow(ControlFlow::Poll);
match event {
Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => {
window_target.exit();
},
Event::AboutToWait => {
let mut frame = display.draw();
frame.clear_color_srgb(0.5, 0.5, 0.5, 0.);
let resolution = UVec2::from(display.get_framebuffer_dimensions()).as_vec2();
kui.begin();
kui.add(Container {
gap: 5.,
padding: Sides::all(5.),
align: (Alignment::Begin, Alignment::Begin),
size: (UiSize::Percentage(1.), UiSize::Percentage(1.)),
elements: vec![
Box::new(Text {
text: "Heloworld!Loremipsumsimdoloramet".into(),
..Default::default()
}),
],
..Default::default()
}, resolution);
kui.end();
backend.update(&kui);
backend.draw(&mut frame, resolution);
frame.finish().unwrap();
}
_ => (),
}
}).unwrap();
}

View file

@ -1,11 +1,21 @@
#version 300 es #version 300 es
precision highp float; precision highp float;
precision highp sampler2D;
out vec4 out_color; out vec4 out_color;
in vec4 vtx_color; in vec4 vtx_color;
in vec2 vtx_uv;
uniform bool use_tex;
uniform sampler2D tex;
void main() { void main() {
if (vtx_color.w <= 0.) discard; //if (vtx_color.w <= 0.) discard;
out_color = vtx_color; vec4 tex_color;
if (use_tex) {
tex_color = texture(tex, vtx_uv);
} else {
tex_color = vec4(1.);
}
out_color = tex_color * vtx_color;
} }

View file

@ -3,12 +3,15 @@
precision highp float; precision highp float;
uniform vec2 resolution; uniform vec2 resolution;
in vec2 uv;
in vec4 color; in vec4 color;
in vec2 position; in vec2 position;
out vec4 vtx_color; out vec4 vtx_color;
out vec2 vtx_uv;
void main() { void main() {
vtx_color = color; vtx_color = color;
vtx_uv = uv;
vec2 pos2d = (vec2(2., -2.) * (position / resolution)) + vec2(-1, 1); vec2 pos2d = (vec2(2., -2.) * (position / resolution)) + vec2(-1, 1);
gl_Position = vec4(pos2d, 0., 1.); gl_Position = vec4(pos2d, 0., 1.);
} }

View file

@ -1,14 +1,17 @@
use std::rc::Rc;
use glam::Vec2; use glam::Vec2;
use glium::{ use glium::{
Surface, DrawParameters, Blend, Surface, DrawParameters, Blend,
Program, VertexBuffer, IndexBuffer, Program, VertexBuffer, IndexBuffer,
backend::Facade, backend::{Facade, Context},
texture::{SrgbTexture2d, RawImage2d},
index::PrimitiveType, index::PrimitiveType,
implement_vertex, uniform, texture::{SrgbTexture2d, RawImage2d}, implement_vertex,
uniform, uniforms::DynamicUniforms,
}; };
use kubi_ui::{ use kubi_ui::{
KubiUi, KubiUi,
draw::{UiDrawPlan, UiVertex}, draw::{UiDrawPlan, UiVertex, BindTexture},
text::FontTextureInfo, IfModified, text::FontTextureInfo, IfModified,
}; };
@ -36,10 +39,10 @@ impl From<UiVertex> for Vertex {
implement_vertex!(Vertex, position, color, uv); implement_vertex!(Vertex, position, color, uv);
struct BufferPair { struct BufferPair {
vertex_buffer: glium::VertexBuffer<Vertex>, pub vertex_buffer: glium::VertexBuffer<Vertex>,
index_buffer: glium::IndexBuffer<u32>, pub index_buffer: glium::IndexBuffer<u32>,
vertex_count: usize, pub vertex_count: usize,
index_count: usize, pub index_count: usize,
} }
impl BufferPair { impl BufferPair {
@ -102,65 +105,121 @@ impl BufferPair {
} }
} }
pub struct GliumUiRenderer { struct GlDrawCall {
program: glium::Program, active: bool,
buffer: BufferPair, buffer: BufferPair,
bind_texture: Option<Rc<SrgbTexture2d>>,
}
pub struct GliumUiRenderer {
context: Rc<Context>,
program: glium::Program,
font_texture: Option<Rc<SrgbTexture2d>>,
plan: Vec<GlDrawCall>,
} }
impl GliumUiRenderer { impl GliumUiRenderer {
pub fn new<F: Facade>(facade: &F) -> Self { pub fn new<F: Facade>(facade: &F) -> Self {
log::info!("init glium backend for ui"); log::info!("init glium backend for kui");
log::debug!("init program");
let program = Program::from_source(facade, VERTEX_SHADER, FRAGMENT_SHADER, None).unwrap();
Self { Self {
program, program: Program::from_source(facade, VERTEX_SHADER, FRAGMENT_SHADER, None).unwrap(),
buffer: BufferPair::new(facade) context: Rc::clone(facade.get_context()),
font_texture: None,
plan: vec![]
} }
} }
pub fn update_draw_plan(&mut self, plan: &UiDrawPlan) { pub fn update_draw_plan(&mut self, plan: &UiDrawPlan) {
assert!(plan.calls.len() == 1, "multiple draw calls not supported yet"); if plan.calls.len() > self.plan.len() {
let data_vtx = &plan.calls[0].vertices.iter().copied().map(Vertex::from).collect::<Vec<_>>(); self.plan.resize_with(plan.calls.len(), || {
let data_idx = &plan.calls[0].indices; GlDrawCall {
self.buffer.write_data(data_vtx, data_idx); buffer: BufferPair::new(&self.context),
bind_texture: None,
active: false,
}
});
} else {
for step in &mut self.plan[plan.calls.len()..] {
step.active = false;
}
}
for (idx, call) in plan.calls.iter().enumerate() {
let data_vtx = &call.vertices.iter().copied().map(Vertex::from).collect::<Vec<_>>()[..];
let data_idx = &call.indices[..];
self.plan[idx].active = true;
self.plan[idx].buffer.write_data(data_vtx, data_idx);
self.plan[idx].bind_texture = match call.bind_texture {
Some(BindTexture::FontTexture) => {
const NO_FNT_TEX: &str = "Font texture exists in draw plan but not yet inited. Make sure to call update_font_texture() *before* update_draw_plan()";
Some(Rc::clone(self.font_texture.as_ref().expect(NO_FNT_TEX)))
},
None => None,
}
}
} }
pub fn update_font_texture(&mut self, font_texture: &FontTextureInfo) { pub fn update_font_texture(&mut self, font_texture: &FontTextureInfo) {
//HACK: get context from buffer log::debug!("updating font texture");
let ctx = self.buffer.index_buffer.get_context(); self.font_texture = Some(Rc::new(SrgbTexture2d::new(
SrgbTexture2d::new(ctx, RawImage2d::from_raw_rgb( &self.context,
RawImage2d::from_raw_rgba(
font_texture.data.to_owned(), font_texture.data.to_owned(),
(font_texture.size.x, font_texture.size.y) (font_texture.size.x, font_texture.size.y)
)).unwrap(); )
).unwrap()));
} }
pub fn update(&mut self, kui: &KubiUi) { pub fn update(&mut self, kui: &KubiUi) {
if let Some(plan) = kui.draw_plan().if_modified() {
self.update_draw_plan(plan);
}
if let Some(texture) = kui.font_texture().if_modified() { if let Some(texture) = kui.font_texture().if_modified() {
self.update_font_texture(texture); self.update_font_texture(texture);
} }
if let Some(plan) = kui.draw_plan().if_modified() {
self.update_draw_plan(plan);
}
} }
pub fn draw(&self, frame: &mut glium::Frame, resolution: Vec2) { pub fn draw(&self, frame: &mut glium::Frame, resolution: Vec2) {
if self.buffer.is_empty() {
return
}
let params = DrawParameters { let params = DrawParameters {
blend: Blend::alpha_blending(), blend: Blend::alpha_blending(),
..Default::default() ..Default::default()
}; };
for step in &self.plan {
if !step.active {
continue
}
if step.buffer.is_empty() {
continue
}
let vtx_buffer = step.buffer.vertex_buffer.slice(0..step.buffer.vertex_count).unwrap();
let idx_buffer = step.buffer.index_buffer.slice(0..step.buffer.index_count).unwrap();
if let Some(bind_texture) = step.bind_texture.as_ref() {
frame.draw( frame.draw(
self.buffer.vertex_buffer.slice(0..self.buffer.vertex_count).unwrap(), vtx_buffer,
self.buffer.index_buffer.slice(0..self.buffer.index_count).unwrap(), idx_buffer,
&self.program, &self.program,
&uniform! { &uniform! {
resolution: resolution.to_array(), resolution: resolution.to_array(),
tex: bind_texture.as_ref(),
use_tex: true,
},
&params,
).unwrap();
} else {
frame.draw(
vtx_buffer,
idx_buffer,
&self.program,
&uniform! {
resolution: resolution.to_array(),
use_tex: false,
}, },
&params, &params,
).unwrap(); ).unwrap();
} }
} }
}
}

View file

@ -1,4 +1,4 @@
use crate::{IfModified, text::TextRenderer}; use crate::{IfModified, text::{TextRenderer, FontHandle}};
use std::borrow::Cow; use std::borrow::Cow;
use glam::{Vec2, Vec4, vec2}; use glam::{Vec2, Vec4, vec2};
@ -63,22 +63,75 @@ pub struct UiDrawPlan {
pub calls: Vec<UiDrawCall> pub calls: Vec<UiDrawCall>
} }
struct CallSwapper {
calls: Vec<UiDrawCall>,
call: UiDrawCall,
}
impl CallSwapper {
pub fn new() -> Self {
Self {
calls: vec![],
call: UiDrawCall::default(),
}
}
pub fn current(&self) -> &UiDrawCall {
&self.call
}
pub fn current_mut(&mut self) -> &mut UiDrawCall {
&mut self.call
}
pub fn swap(&mut self) {
self.calls.push(std::mem::replace(&mut self.call, UiDrawCall::default()));
}
pub fn finish(mut self) -> Vec<UiDrawCall> {
self.calls.push(self.call);
self.calls
}
}
impl UiDrawPlan { impl UiDrawPlan {
pub fn build(calls: &UiDrawCommands, tr: &mut TextRenderer) -> Self { pub fn build(draw_commands: &UiDrawCommands, tr: &mut TextRenderer) -> Self {
let mut call = UiDrawCall::default(); let mut swapper = CallSwapper::new();
for command in &calls.commands { let mut prev_command = None;
for command in &draw_commands.commands {
let do_swap = if let Some(prev_command) = prev_command {
std::mem::discriminant(prev_command) != std::mem::discriminant(command)
} else {
false
};
if do_swap {
swapper.swap();
}
if do_swap || prev_command.is_none() {
match command {
UiDrawCommand::Rectangle { .. } => (),
UiDrawCommand::Text { .. } => {
swapper.current_mut().bind_texture = Some(BindTexture::FontTexture);
}
}
}
let vidx = swapper.current().vertices.len() as u32;
match command { match command {
UiDrawCommand::Rectangle { position, size, color } => { UiDrawCommand::Rectangle { position, size, color } => {
let idx = call.vertices.len() as u32; swapper.current_mut().indices.extend([vidx, vidx + 1, vidx + 2, vidx, vidx + 2, vidx + 3]);
call.indices.extend([idx, idx + 1, idx + 2, idx, idx + 2, idx + 3]); swapper.current_mut().vertices.extend([
call.vertices.extend([
UiVertex { UiVertex {
position: *position, position: *position,
color: *color, color: *color,
uv: vec2(0.0, 0.0), uv: vec2(0.0, 0.0),
}, },
UiVertex { UiVertex {
position: *position + Vec2::new(size.x, 0.0), position: *position + vec2(size.x, 0.0),
color: *color, color: *color,
uv: vec2(1.0, 0.0), uv: vec2(1.0, 0.0),
}, },
@ -88,19 +141,45 @@ impl UiDrawPlan {
uv: vec2(1.0, 1.0), uv: vec2(1.0, 1.0),
}, },
UiVertex { UiVertex {
position: *position + Vec2::new(0.0, size.y), position: *position + vec2(0.0, size.y),
color: *color, color: *color,
uv: vec2(0.0, 1.0), uv: vec2(0.0, 1.0),
}, },
]); ]);
}, },
UiDrawCommand::Text { position, size, color, text } => { UiDrawCommand::Text { position, size, color, text } => {
todo!() for char in text.chars() {
tr.glyph(FontHandle(0), char, *size);
}
swapper.current_mut().indices.extend([vidx, vidx + 1, vidx + 2, vidx, vidx + 2, vidx + 3]);
swapper.current_mut().vertices.extend([
UiVertex {
position: *position,
color: *color,
uv: vec2(0.0, 0.0),
},
UiVertex {
position: *position + vec2(32., 0.0),
color: *color,
uv: vec2(1.0, 0.0),
},
UiVertex {
position: *position + vec2(32., 32.),
color: *color,
uv: vec2(1.0, 1.0),
},
UiVertex {
position: *position + vec2(0.0, 32.),
color: *color,
uv: vec2(0.0, 1.0),
},
]);
} }
} }
prev_command = Some(command);
} }
Self { Self {
calls: vec![call] calls: swapper.finish()
} }
} }
} }

View file

@ -12,6 +12,7 @@ mod builtin {
pub mod container; pub mod container;
pub mod spacer; pub mod spacer;
pub mod progress_bar; pub mod progress_bar;
pub mod text;
} }
#[cfg(feature = "builtin_elements")] #[cfg(feature = "builtin_elements")]

View file

@ -0,0 +1,56 @@
use std::borrow::Cow;
use glam::{vec2, Vec4};
use crate::{
LayoutInfo,
UiSize,
element::UiElement,
state::StateRepo,
measure::Response,
draw::UiDrawCommand
};
pub struct Text {
pub text: Cow<'static, str>,
pub size: (UiSize, UiSize),
pub color: Vec4,
}
impl Default for Text {
fn default() -> Self {
Self {
text: "".into(),
size: (UiSize::Percentage(1.), UiSize::Pixels(32.)),
color: Vec4::new(1., 1., 1., 1.),
}
}
}
impl UiElement for Text {
fn measure(&self, _state: &StateRepo, layout: &LayoutInfo) -> Response {
Response {
size: vec2(
match self.size.0 {
UiSize::Auto => layout.max_size.x,
UiSize::Percentage(percentage) => layout.max_size.x * percentage,
UiSize::Pixels(pixels) => pixels,
},
match self.size.1 {
UiSize::Auto => layout.max_size.y,
UiSize::Percentage(percentage) => layout.max_size.y * percentage,
UiSize::Pixels(pixels) => pixels,
},
),
hints: Default::default(),
user_data: None
}
}
fn process(&self, _measure: &Response, _state: &mut StateRepo, layout: &LayoutInfo, draw: &mut Vec<UiDrawCommand>) {
draw.push(UiDrawCommand::Text {
text: self.text.clone(),
position: layout.position,
size: 32,
color: self.color,
});
}
}

View file

@ -60,7 +60,7 @@ impl FontTextureManager {
FontTextureManager { FontTextureManager {
glyph_cache: HashMap::new(), glyph_cache: HashMap::new(),
packer: DensePacker::new(size.x as i32, size.y as i32), packer: DensePacker::new(size.x as i32, size.y as i32),
font_texture: vec![0; (size.x * size.y) as usize], font_texture: vec![0; (size.x * size.y * 4) as usize],
font_texture_size: size, font_texture_size: size,
modified: false, modified: false,
} }
@ -79,17 +79,18 @@ impl FontTextureManager {
} }
/// Either looks up the glyph in the cache or renders it and adds it to the cache. /// Either looks up the glyph in the cache or renders it and adds it to the cache.
fn glyph_allocate(&mut self, font_manager: &FontManager, font_handle: FontHandle, character: char, size: u8) -> (bool, Arc<GlyphCacheEntry>) { pub fn glyph(&mut self, font_manager: &FontManager, font_handle: FontHandle, character: char, size: u8) -> Arc<GlyphCacheEntry> {
let key = GlyphCacheKey { let key = GlyphCacheKey {
font_index: font_handle.0, font_index: font_handle.0,
character, character,
size, size,
}; };
if let Some(entry) = self.glyph_cache.get(&key) { if let Some(entry) = self.glyph_cache.get(&key) {
return (false, Arc::clone(entry)); return Arc::clone(entry);
} }
let font = font_manager.get(font_handle).unwrap(); let font = font_manager.get(font_handle).unwrap();
let (metrics, bitmap) = font.rasterize(character, size as f32); let (metrics, bitmap) = font.rasterize(character, size as f32);
log::debug!("rasterized glyph: {:?}, {:?}", metrics, bitmap);
let texture_position = self.packer.pack(metrics.width as i32, metrics.height as i32, false).unwrap(); let texture_position = self.packer.pack(metrics.width as i32, metrics.height as i32, false).unwrap();
let texture_size = uvec2(metrics.width as u32, metrics.height as u32); let texture_size = uvec2(metrics.width as u32, metrics.height as u32);
let entry = Arc::new(GlyphCacheEntry { let entry = Arc::new(GlyphCacheEntry {
@ -99,30 +100,37 @@ impl FontTextureManager {
size: texture_size, size: texture_size,
}); });
self.glyph_cache.insert_unique_unchecked(key, Arc::clone(&entry)); self.glyph_cache.insert_unique_unchecked(key, Arc::clone(&entry));
(true, entry) self.glyph_place(&entry);
self.modified = true;
entry
} }
/// Place glyph onto the font texture. /// Place glyph onto the font texture.
fn glyph_place(&mut self, entry: &GlyphCacheEntry) { fn glyph_place(&mut self, entry: &GlyphCacheEntry) {
let tex_size = self.font_texture_size; let tex_size = self.font_texture_size;
let GlyphCacheEntry { size, position, .. } = entry; let GlyphCacheEntry { size, position, data, .. } = entry;
//println!("{size:?} {position:?}");
for y in 0..size.y { for y in 0..size.y {
for x in 0..size.x { for x in 0..size.x {
let src = (size.x * y + x) as usize; let src = (size.x * y + x) as usize;
let dst = (tex_size.x * (y + position.y as u32) + (x + position.x as u32)) as usize; let dst = (tex_size.x * (y + position.y as u32) + (x + position.x as u32)) as usize * 4;
self.font_texture[dst] = entry.data[src]; self.font_texture[dst..=(dst + 3)].copy_from_slice(&[255, 0, 0, data[src]]);
self.font_texture[dst] = data[src];
//print!("{} ", if data[src] > 0 {'#'} else {'.'});
//print!("{src} {dst} / ");
} }
//println!();
} }
} }
pub fn glyph(&mut self, font_manager: &FontManager, font_handle: FontHandle, character: char, size: u8) -> Arc<GlyphCacheEntry> { // pub fn glyph(&mut self, font_manager: &FontManager, font_handle: FontHandle, character: char, size: u8) -> Arc<GlyphCacheEntry> {
let (is_new, glyph) = self.glyph_allocate(font_manager, font_handle, character, size); // let (is_new, glyph) = self.glyph_allocate(font_manager, font_handle, character, size);
if is_new { // if is_new {
self.glyph_place(&glyph); // self.glyph_place(&glyph);
self.modified = true; // self.modified = true;
} // }
glyph // glyph
} // }
} }
impl Default for FontTextureManager { impl Default for FontTextureManager {