mirror of
https://github.com/griffi-gh/hUI.git
synced 2024-11-21 22:58:42 -06:00
WIP single draw call architecture
This commit is contained in:
parent
3bcbe0ae6e
commit
f54b218cbb
|
@ -11,7 +11,7 @@ use glium::{
|
||||||
};
|
};
|
||||||
use hui::{
|
use hui::{
|
||||||
UiInstance,
|
UiInstance,
|
||||||
draw::{UiDrawPlan, UiVertex, BindTexture},
|
draw::{UiDrawCall, UiVertex, BindTexture},
|
||||||
text::FontTextureInfo, IfModified,
|
text::FontTextureInfo, IfModified,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ struct BufferPair {
|
||||||
|
|
||||||
impl BufferPair {
|
impl BufferPair {
|
||||||
pub fn new<F: Facade>(facade: &F) -> Self {
|
pub fn new<F: Facade>(facade: &F) -> Self {
|
||||||
log::debug!("init ui buffers...");
|
log::debug!("init ui buffers (empty)...");
|
||||||
Self {
|
Self {
|
||||||
vertex_buffer: VertexBuffer::empty_dynamic(facade, 1024).unwrap(),
|
vertex_buffer: VertexBuffer::empty_dynamic(facade, 1024).unwrap(),
|
||||||
index_buffer: IndexBuffer::empty_dynamic(facade, PrimitiveType::TrianglesList, 1024).unwrap(),
|
index_buffer: IndexBuffer::empty_dynamic(facade, PrimitiveType::TrianglesList, 1024).unwrap(),
|
||||||
|
@ -57,6 +57,16 @@ impl BufferPair {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_with_data<F: Facade>(facade: &F, vtx: &[Vertex], idx: &[u32]) -> Self {
|
||||||
|
log::debug!("init ui buffers (data)...");
|
||||||
|
Self {
|
||||||
|
vertex_buffer: VertexBuffer::dynamic(facade, vtx).unwrap(),
|
||||||
|
index_buffer: IndexBuffer::dynamic(facade, PrimitiveType::TrianglesList, idx).unwrap(),
|
||||||
|
vertex_count: 0,
|
||||||
|
index_count: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn ensure_buffer_size(&mut self, need_vtx: usize, need_idx: usize) {
|
pub fn ensure_buffer_size(&mut self, need_vtx: usize, need_idx: usize) {
|
||||||
let current_vtx_size = self.vertex_buffer.get_size() / std::mem::size_of::<Vertex>();
|
let current_vtx_size = self.vertex_buffer.get_size() / std::mem::size_of::<Vertex>();
|
||||||
let current_idx_size = self.index_buffer.get_size() / std::mem::size_of::<u32>();
|
let current_idx_size = self.index_buffer.get_size() / std::mem::size_of::<u32>();
|
||||||
|
@ -106,18 +116,12 @@ impl BufferPair {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct GlDrawCall {
|
|
||||||
active: bool,
|
|
||||||
buffer: BufferPair,
|
|
||||||
bind_texture: Option<Rc<SrgbTexture2d>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct GliumUiRenderer {
|
pub struct GliumUiRenderer {
|
||||||
context: Rc<Context>,
|
context: Rc<Context>,
|
||||||
program: glium::Program,
|
program: glium::Program,
|
||||||
program_tex: glium::Program,
|
program_tex: glium::Program,
|
||||||
font_texture: Option<Rc<SrgbTexture2d>>,
|
ui_texture: Option<Rc<SrgbTexture2d>>,
|
||||||
plan: Vec<GlDrawCall>,
|
buffer_pair: Option<BufferPair>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GliumUiRenderer {
|
impl GliumUiRenderer {
|
||||||
|
@ -127,44 +131,33 @@ impl GliumUiRenderer {
|
||||||
program: Program::from_source(facade, VERTEX_SHADER, FRAGMENT_SHADER, None).unwrap(),
|
program: Program::from_source(facade, VERTEX_SHADER, FRAGMENT_SHADER, None).unwrap(),
|
||||||
program_tex: Program::from_source(facade, VERTEX_SHADER, FRAGMENT_SHADER_TEX, None).unwrap(),
|
program_tex: Program::from_source(facade, VERTEX_SHADER, FRAGMENT_SHADER_TEX, None).unwrap(),
|
||||||
context: Rc::clone(facade.get_context()),
|
context: Rc::clone(facade.get_context()),
|
||||||
font_texture: None,
|
ui_texture: None,
|
||||||
plan: vec![]
|
buffer_pair: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_draw_plan(&mut self, plan: &UiDrawPlan) {
|
pub fn update_draw_plan(&mut self, call: &UiDrawCall) {
|
||||||
if plan.calls.len() > self.plan.len() {
|
let data_vtx = &call.vertices.iter().copied().map(Vertex::from).collect::<Vec<_>>()[..];
|
||||||
self.plan.resize_with(plan.calls.len(), || {
|
let data_idx = &call.indices[..];
|
||||||
GlDrawCall {
|
if let Some(buffer) = &mut self.buffer_pair {
|
||||||
buffer: BufferPair::new(&self.context),
|
buffer.write_data(data_vtx, data_idx);
|
||||||
bind_texture: None,
|
} else if !call.indices.is_empty() {
|
||||||
active: false,
|
self.buffer_pair = Some(BufferPair::new_with_data(&self.context, data_vtx, data_idx));
|
||||||
}
|
|
||||||
});
|
|
||||||
} 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)))
|
|
||||||
},
|
|
||||||
Some(BindTexture::UserDefined(_)) => todo!("user defined textures are not implemented yet"),
|
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// self.plan[0].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)))
|
||||||
|
// },
|
||||||
|
// Some(BindTexture::UserDefined(_)) => todo!("user defined textures are not implemented yet"),
|
||||||
|
// None => None,
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_font_texture(&mut self, font_texture: &FontTextureInfo) {
|
pub fn update_ui_texture(&mut self, font_texture: &FontTextureInfo) {
|
||||||
log::debug!("updating font texture");
|
log::debug!("updating font texture");
|
||||||
self.font_texture = Some(Rc::new(SrgbTexture2d::new(
|
self.ui_texture = Some(Rc::new(SrgbTexture2d::new(
|
||||||
&self.context,
|
&self.context,
|
||||||
RawImage2d::from_raw_rgba(
|
RawImage2d::from_raw_rgba(
|
||||||
font_texture.data.to_owned(),
|
font_texture.data.to_owned(),
|
||||||
|
@ -175,9 +168,9 @@ impl GliumUiRenderer {
|
||||||
|
|
||||||
pub fn update(&mut self, hui: &UiInstance) {
|
pub fn update(&mut self, hui: &UiInstance) {
|
||||||
if let Some(texture) = hui.font_texture().if_modified() {
|
if let Some(texture) = hui.font_texture().if_modified() {
|
||||||
self.update_font_texture(texture);
|
self.update_ui_texture(texture);
|
||||||
}
|
}
|
||||||
if let Some(plan) = hui.draw_plan().if_modified() {
|
if let Some(plan) = hui.draw_call().if_modified() {
|
||||||
self.update_draw_plan(plan);
|
self.update_draw_plan(plan);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -188,43 +181,41 @@ impl GliumUiRenderer {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
for step in &self.plan {
|
if let Some(buffer) = &self.buffer_pair {
|
||||||
if !step.active {
|
if buffer.is_empty() {
|
||||||
continue
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if step.buffer.is_empty() {
|
let vtx_buffer = buffer.vertex_buffer.slice(0..buffer.vertex_count).unwrap();
|
||||||
continue
|
let idx_buffer = buffer.index_buffer.slice(0..buffer.index_count).unwrap();
|
||||||
}
|
|
||||||
|
|
||||||
let vtx_buffer = step.buffer.vertex_buffer.slice(0..step.buffer.vertex_count).unwrap();
|
frame.draw(
|
||||||
let idx_buffer = step.buffer.index_buffer.slice(0..step.buffer.index_count).unwrap();
|
vtx_buffer,
|
||||||
|
idx_buffer,
|
||||||
|
&self.program_tex,
|
||||||
|
&uniform! {
|
||||||
|
resolution: resolution.to_array(),
|
||||||
|
tex: Sampler(self.ui_texture.as_ref().unwrap().as_ref(), SamplerBehavior {
|
||||||
|
wrap_function: (SamplerWrapFunction::Clamp, SamplerWrapFunction::Clamp, SamplerWrapFunction::Clamp),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
¶ms,
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
if let Some(bind_texture) = step.bind_texture.as_ref() {
|
// if let Some(bind_texture) = call.bind_texture.as_ref() {
|
||||||
frame.draw(
|
|
||||||
vtx_buffer,
|
// } else {
|
||||||
idx_buffer,
|
// frame.draw(
|
||||||
&self.program_tex,
|
// vtx_buffer,
|
||||||
&uniform! {
|
// idx_buffer,
|
||||||
resolution: resolution.to_array(),
|
// &self.program,
|
||||||
tex: Sampler(bind_texture.as_ref(), SamplerBehavior {
|
// &uniform! {
|
||||||
wrap_function: (SamplerWrapFunction::Clamp, SamplerWrapFunction::Clamp, SamplerWrapFunction::Clamp),
|
// resolution: resolution.to_array(),
|
||||||
..Default::default()
|
// },
|
||||||
}),
|
// ¶ms,
|
||||||
},
|
// ).unwrap();
|
||||||
¶ms,
|
// }
|
||||||
).unwrap();
|
|
||||||
} else {
|
|
||||||
frame.draw(
|
|
||||||
vtx_buffer,
|
|
||||||
idx_buffer,
|
|
||||||
&self.program,
|
|
||||||
&uniform! {
|
|
||||||
resolution: resolution.to_array(),
|
|
||||||
},
|
|
||||||
¶ms,
|
|
||||||
).unwrap();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
118
hui/src/draw.rs
118
hui/src/draw.rs
|
@ -6,6 +6,7 @@ use crate::{
|
||||||
IfModified
|
IfModified
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod atlas;
|
||||||
mod corner_radius;
|
mod corner_radius;
|
||||||
|
|
||||||
pub use corner_radius::RoundedCorners;
|
pub use corner_radius::RoundedCorners;
|
||||||
|
@ -54,16 +55,6 @@ pub enum UiDrawCommand {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UiDrawCommand {
|
|
||||||
fn texture_eq_index(&self) -> u64 {
|
|
||||||
match self {
|
|
||||||
UiDrawCommand::Rectangle { .. } |
|
|
||||||
UiDrawCommand::Circle { .. } => u64::MAX - 1,
|
|
||||||
UiDrawCommand::Text { .. } => u64::MAX - 2,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// List of draw commands
|
/// List of draw commands
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct UiDrawCommandList {
|
pub struct UiDrawCommandList {
|
||||||
|
@ -110,80 +101,22 @@ pub struct UiVertex {
|
||||||
pub struct UiDrawCall {
|
pub struct UiDrawCall {
|
||||||
pub vertices: Vec<UiVertex>,
|
pub vertices: Vec<UiVertex>,
|
||||||
pub indices: Vec<u32>,
|
pub indices: Vec<u32>,
|
||||||
pub bind_texture: Option<BindTexture>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a complete UI rendering plan (a list of optimized draw calls).
|
impl UiDrawCall {
|
||||||
#[derive(Default)]
|
|
||||||
pub struct UiDrawPlan {
|
|
||||||
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::take(&mut self.call));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn finish(mut self) -> Vec<UiDrawCall> {
|
|
||||||
self.calls.push(self.call);
|
|
||||||
self.calls
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UiDrawPlan {
|
|
||||||
/// Tesselate the UI and build a complete draw plan from a list of draw commands
|
/// Tesselate the UI and build a complete draw plan from a list of draw commands
|
||||||
pub fn build(draw_commands: &UiDrawCommandList, tr: &mut TextRenderer) -> Self {
|
pub fn build(draw_commands: &UiDrawCommandList, tr: &mut TextRenderer) -> Self {
|
||||||
let mut swapper = CallSwapper::new();
|
let mut draw_call = UiDrawCall::default();
|
||||||
let mut prev_command: Option<&UiDrawCommand> = None;
|
|
||||||
for command in &draw_commands.commands {
|
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)
|
|
||||||
prev_command.texture_eq_index() != command.texture_eq_index()
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
if do_swap {
|
|
||||||
swapper.swap();
|
|
||||||
}
|
|
||||||
|
|
||||||
if do_swap || prev_command.is_none() {
|
|
||||||
swapper.current_mut().bind_texture = match command {
|
|
||||||
UiDrawCommand::Rectangle { .. } |
|
|
||||||
UiDrawCommand::Circle { .. } => None,
|
|
||||||
UiDrawCommand::Text { .. } => Some(BindTexture::FontTexture),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match command {
|
match command {
|
||||||
UiDrawCommand::Rectangle { position, size, color, rounded_corners } => {
|
UiDrawCommand::Rectangle { position, size, color, rounded_corners } => {
|
||||||
let vidx = swapper.current().vertices.len() as u32;
|
let vidx = draw_call.vertices.len() as u32;
|
||||||
if let Some(corner) = rounded_corners.filter(|x| x.radius.max_f32() > 0.0) {
|
if let Some(corner) = rounded_corners.filter(|x| x.radius.max_f32() > 0.0) {
|
||||||
//this code is stupid as fuck
|
//this code is stupid as fuck
|
||||||
|
|
||||||
//Random vert in the center for no reason
|
//Random vert in the center for no reason
|
||||||
//lol
|
//lol
|
||||||
swapper.current_mut().vertices.push(UiVertex {
|
draw_call.vertices.push(UiVertex {
|
||||||
position: *position + *size * vec2(0.5, 0.5),
|
position: *position + *size * vec2(0.5, 0.5),
|
||||||
color: (color.bottom_left + color.bottom_right + color.top_left + color.top_right) / 4.,
|
color: (color.bottom_left + color.bottom_right + color.top_left + color.top_right) / 4.,
|
||||||
uv: vec2(0., 0.),
|
uv: vec2(0., 0.),
|
||||||
|
@ -197,32 +130,32 @@ impl UiDrawPlan {
|
||||||
let x = angle.sin();
|
let x = angle.sin();
|
||||||
let y = angle.cos();
|
let y = angle.cos();
|
||||||
//Top-right corner
|
//Top-right corner
|
||||||
swapper.current_mut().vertices.push(UiVertex {
|
draw_call.vertices.push(UiVertex {
|
||||||
position: *position + vec2(x, 1. - y) * corner.radius.top_right + vec2(size.x - corner.radius.top_right, 0.),
|
position: *position + vec2(x, 1. - y) * corner.radius.top_right + vec2(size.x - corner.radius.top_right, 0.),
|
||||||
color: color.top_right,
|
color: color.top_right,
|
||||||
uv: vec2(0.0, 0.0),
|
uv: vec2(0.0, 0.0),
|
||||||
});
|
});
|
||||||
//Bottom-right corner
|
//Bottom-right corner
|
||||||
swapper.current_mut().vertices.push(UiVertex {
|
draw_call.vertices.push(UiVertex {
|
||||||
position: *position + vec2(x - 1., y) * corner.radius.bottom_right + vec2(size.x, size.y - corner.radius.bottom_right),
|
position: *position + vec2(x - 1., y) * corner.radius.bottom_right + vec2(size.x, size.y - corner.radius.bottom_right),
|
||||||
color: color.bottom_right,
|
color: color.bottom_right,
|
||||||
uv: vec2(0.0, 0.0),
|
uv: vec2(0.0, 0.0),
|
||||||
});
|
});
|
||||||
//Bottom-left corner
|
//Bottom-left corner
|
||||||
swapper.current_mut().vertices.push(UiVertex {
|
draw_call.vertices.push(UiVertex {
|
||||||
position: *position + vec2(1. - x, y) * corner.radius.bottom_left + vec2(0., size.y - corner.radius.bottom_left),
|
position: *position + vec2(1. - x, y) * corner.radius.bottom_left + vec2(0., size.y - corner.radius.bottom_left),
|
||||||
color: color.bottom_left,
|
color: color.bottom_left,
|
||||||
uv: vec2(0.0, 0.0),
|
uv: vec2(0.0, 0.0),
|
||||||
});
|
});
|
||||||
//Top-left corner
|
//Top-left corner
|
||||||
swapper.current_mut().vertices.push(UiVertex {
|
draw_call.vertices.push(UiVertex {
|
||||||
position: *position + vec2(1. - x, 1. - y) * corner.radius.top_left,
|
position: *position + vec2(1. - x, 1. - y) * corner.radius.top_left,
|
||||||
color: color.top_left,
|
color: color.top_left,
|
||||||
uv: vec2(0.0, 0.0),
|
uv: vec2(0.0, 0.0),
|
||||||
});
|
});
|
||||||
// mental illness:
|
// mental illness:
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
swapper.current_mut().indices.extend([
|
draw_call.indices.extend([
|
||||||
//Top-right corner
|
//Top-right corner
|
||||||
vidx,
|
vidx,
|
||||||
vidx + 1 + (i - 1) * 4,
|
vidx + 1 + (i - 1) * 4,
|
||||||
|
@ -244,7 +177,7 @@ impl UiDrawPlan {
|
||||||
}
|
}
|
||||||
//Fill in the rest
|
//Fill in the rest
|
||||||
//mental illness 2:
|
//mental illness 2:
|
||||||
swapper.current_mut().indices.extend([
|
draw_call.indices.extend([
|
||||||
//Top
|
//Top
|
||||||
vidx,
|
vidx,
|
||||||
vidx + 4,
|
vidx + 4,
|
||||||
|
@ -263,8 +196,8 @@ impl UiDrawPlan {
|
||||||
vidx + 2,
|
vidx + 2,
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
swapper.current_mut().indices.extend([vidx, vidx + 1, vidx + 2, vidx, vidx + 2, vidx + 3]);
|
draw_call.indices.extend([vidx, vidx + 1, vidx + 2, vidx, vidx + 2, vidx + 3]);
|
||||||
swapper.current_mut().vertices.extend([
|
draw_call.vertices.extend([
|
||||||
UiVertex {
|
UiVertex {
|
||||||
position: *position,
|
position: *position,
|
||||||
color: color.top_left,
|
color: color.top_left,
|
||||||
|
@ -313,15 +246,15 @@ impl UiDrawPlan {
|
||||||
tr.font_texture().size.x as f32,
|
tr.font_texture().size.x as f32,
|
||||||
tr.font_texture().size.y as f32
|
tr.font_texture().size.y as f32
|
||||||
);
|
);
|
||||||
let vidx = swapper.current().vertices.len() as u32;
|
let vidx = draw_call.vertices.len() as u32;
|
||||||
let glyph = tr.glyph(*font, layout_glyph.parent, layout_glyph.key.px as u8);
|
let glyph = tr.glyph(*font, layout_glyph.parent, layout_glyph.key.px as u8);
|
||||||
//rpos_x += glyph.metrics.advance_width;//glyph.metrics.advance_width;
|
//rpos_x += glyph.metrics.advance_width;//glyph.metrics.advance_width;
|
||||||
swapper.current_mut().indices.extend([vidx, vidx + 1, vidx + 2, vidx, vidx + 2, vidx + 3]);
|
draw_call.indices.extend([vidx, vidx + 1, vidx + 2, vidx, vidx + 2, vidx + 3]);
|
||||||
let p0x = glyph.position.x as f32 / font_texture_size.0;
|
let p0x = glyph.position.x as f32 / font_texture_size.0;
|
||||||
let p1x = (glyph.position.x + glyph.size.x as i32) as f32 / font_texture_size.0;
|
let p1x = (glyph.position.x + glyph.size.x as i32) as f32 / font_texture_size.0;
|
||||||
let p0y = glyph.position.y as f32 / font_texture_size.1;
|
let p0y = glyph.position.y as f32 / font_texture_size.1;
|
||||||
let p1y = (glyph.position.y + glyph.size.y as i32) as f32 / font_texture_size.1;
|
let p1y = (glyph.position.y + glyph.size.y as i32) as f32 / font_texture_size.1;
|
||||||
swapper.current_mut().vertices.extend([
|
draw_call.vertices.extend([
|
||||||
UiVertex {
|
UiVertex {
|
||||||
position: *position + vec2(layout_glyph.x, layout_glyph.y),
|
position: *position + vec2(layout_glyph.x, layout_glyph.y),
|
||||||
color: *color,
|
color: *color,
|
||||||
|
@ -347,27 +280,24 @@ impl UiDrawPlan {
|
||||||
feature = "pixel_perfect_text",
|
feature = "pixel_perfect_text",
|
||||||
not(feature = "pixel_perfect")
|
not(feature = "pixel_perfect")
|
||||||
))] {
|
))] {
|
||||||
for vtx in &mut swapper.current_mut().vertices[(vidx as usize)..] {
|
for vtx in &mut draw_call.vertices[(vidx as usize)..] {
|
||||||
vtx.position = vtx.position.round()
|
vtx.position = vtx.position.round()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(feature = "pixel_perfect")]
|
|
||||||
swapper.current_mut().vertices.iter_mut().for_each(|v| {
|
|
||||||
v.position = v.position.round()
|
|
||||||
});
|
|
||||||
prev_command = Some(command);
|
|
||||||
}
|
|
||||||
Self {
|
|
||||||
calls: swapper.finish()
|
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "pixel_perfect")]
|
||||||
|
draw_call.vertices.iter_mut().for_each(|v| {
|
||||||
|
v.position = v.position.round()
|
||||||
|
});
|
||||||
|
draw_call
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IfModified<UiDrawPlan> for (bool, &UiDrawPlan) {
|
impl IfModified<UiDrawCall> for (bool, &UiDrawCall) {
|
||||||
fn if_modified(&self) -> Option<&UiDrawPlan> {
|
fn if_modified(&self) -> Option<&UiDrawCall> {
|
||||||
match self.0 {
|
match self.0 {
|
||||||
true => Some(self.1),
|
true => Some(self.1),
|
||||||
false => None,
|
false => None,
|
||||||
|
|
106
hui/src/draw/atlas.rs
Normal file
106
hui/src/draw/atlas.rs
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
use glam::UVec2;
|
||||||
|
use hashbrown::HashMap;
|
||||||
|
use nohash_hasher::BuildNoHashHasher;
|
||||||
|
use rect_packer::DensePacker;
|
||||||
|
|
||||||
|
const CHANNEL_COUNT: u32 = 4;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub struct TextureHandle {
|
||||||
|
//TODO automatic cleanup when handle is dropped
|
||||||
|
//man: Weak<RefCell<TextureAtlasManager>>,
|
||||||
|
pub(crate) index: u32
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub(crate) struct TextureAllocation {
|
||||||
|
/// Index of the texture allocation
|
||||||
|
pub index: u32,
|
||||||
|
|
||||||
|
/// Position in the texture atlas
|
||||||
|
pub position: UVec2,
|
||||||
|
|
||||||
|
/// Requested texture size
|
||||||
|
pub size: UVec2,
|
||||||
|
|
||||||
|
/// True if the texture was rotated by 90 degrees
|
||||||
|
pub rotated: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct TextureAtlasManager {
|
||||||
|
packer: DensePacker,
|
||||||
|
count: u32,
|
||||||
|
size: UVec2,
|
||||||
|
data: Vec<u8>,
|
||||||
|
allocations: HashMap<u32, TextureAllocation, BuildNoHashHasher<u32>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextureAtlasManager {
|
||||||
|
pub fn new(size: UVec2) -> Self {
|
||||||
|
Self {
|
||||||
|
packer: DensePacker::new(size.x as i32, size.y as i32),
|
||||||
|
count: 0,
|
||||||
|
size: UVec2::new(0, 0),
|
||||||
|
data: Vec::new(),
|
||||||
|
allocations: HashMap::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resize(&mut self, new_size: UVec2) {
|
||||||
|
if new_size.x > self.size.x && new_size.y > self.size.y{
|
||||||
|
self.packer.resize(new_size.x as i32, new_size.y as i32);
|
||||||
|
//Resize the data array in-place
|
||||||
|
self.data.resize((new_size.x * new_size.y * CHANNEL_COUNT) as usize, 0);
|
||||||
|
for y in (1..self.size.y).rev() {
|
||||||
|
for x in (0..self.size.x).rev() {
|
||||||
|
let idx = (y * self.size.x + x) as usize;
|
||||||
|
let new_idx = (y * new_size.x + x) as usize;
|
||||||
|
self.data[new_idx] = self.data[idx];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//If scaling down, just recreate the atlas from scratch (since we need to re-pack everything anyway)
|
||||||
|
todo!("Atlas downscaling is not implemented yet");
|
||||||
|
}
|
||||||
|
self.size = new_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allocate a new texture region in the atlas
|
||||||
|
pub fn allocate(&mut self, size: UVec2) -> Option<TextureHandle> {
|
||||||
|
let result = self.packer.pack(size.x as i32, size.y as i32, true)?;
|
||||||
|
let index = self.count;
|
||||||
|
self.count += 1;
|
||||||
|
let allocation = TextureAllocation {
|
||||||
|
index,
|
||||||
|
position: UVec2::new(result.x as u32, result.y as u32),
|
||||||
|
size,
|
||||||
|
//If the size does not match the requested size, the texture was rotated
|
||||||
|
rotated: result.width != size.x as i32,
|
||||||
|
};
|
||||||
|
self.allocations.insert_unique_unchecked(index, allocation);
|
||||||
|
Some(TextureHandle { index })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allocate a new texture region in the atlas and copy the data into it
|
||||||
|
pub fn add(&mut self, width: u32, data: &[u8]) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn modify(&mut self, handle: TextureHandle) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(&mut self, handle: TextureHandle) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, handle: TextureHandle) -> Option<&TextureAllocation> {
|
||||||
|
self.allocations.get(&handle.index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TextureAtlasManager {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new(UVec2::new(512, 512))
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ use crate:: {
|
||||||
element::{MeasureContext, ProcessContext, UiElement},
|
element::{MeasureContext, ProcessContext, UiElement},
|
||||||
event::UiEvent,
|
event::UiEvent,
|
||||||
state::StateRepo,
|
state::StateRepo,
|
||||||
draw::{UiDrawCommandList, UiDrawPlan},
|
draw::{UiDrawCommandList, UiDrawCall},
|
||||||
text::{TextRenderer, FontTextureInfo, FontHandle},
|
text::{TextRenderer, FontTextureInfo, FontHandle},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -17,8 +17,8 @@ pub struct UiInstance {
|
||||||
//event_queue: VecDeque<UiEvent>,
|
//event_queue: VecDeque<UiEvent>,
|
||||||
prev_draw_commands: UiDrawCommandList,
|
prev_draw_commands: UiDrawCommandList,
|
||||||
draw_commands: UiDrawCommandList,
|
draw_commands: UiDrawCommandList,
|
||||||
draw_plan: UiDrawPlan,
|
draw_call: UiDrawCall,
|
||||||
draw_plan_modified: bool,
|
draw_call_modified: bool,
|
||||||
text_renderer: TextRenderer,
|
text_renderer: TextRenderer,
|
||||||
events: VecDeque<UiEvent>,
|
events: VecDeque<UiEvent>,
|
||||||
}
|
}
|
||||||
|
@ -35,8 +35,8 @@ impl UiInstance {
|
||||||
// root_elements: Vec::new(),
|
// root_elements: Vec::new(),
|
||||||
prev_draw_commands: UiDrawCommandList::default(),
|
prev_draw_commands: UiDrawCommandList::default(),
|
||||||
draw_commands: UiDrawCommandList::default(),
|
draw_commands: UiDrawCommandList::default(),
|
||||||
draw_plan: UiDrawPlan::default(),
|
draw_call: UiDrawCall::default(),
|
||||||
draw_plan_modified: false,
|
draw_call_modified: false,
|
||||||
// ftm: FontTextureManager::default(),
|
// ftm: FontTextureManager::default(),
|
||||||
text_renderer: TextRenderer::new(),
|
text_renderer: TextRenderer::new(),
|
||||||
events: VecDeque::new(),
|
events: VecDeque::new(),
|
||||||
|
@ -78,7 +78,7 @@ impl UiInstance {
|
||||||
/// You must call this function at the beginning of the frame, before adding any elements
|
/// You must call this function at the beginning of the frame, before adding any elements
|
||||||
pub fn begin(&mut self) {
|
pub fn begin(&mut self) {
|
||||||
std::mem::swap(&mut self.prev_draw_commands, &mut self.draw_commands);
|
std::mem::swap(&mut self.prev_draw_commands, &mut self.draw_commands);
|
||||||
self.draw_plan_modified = false;
|
self.draw_call_modified = false;
|
||||||
self.draw_commands.commands.clear();
|
self.draw_commands.commands.clear();
|
||||||
self.text_renderer.reset_frame();
|
self.text_renderer.reset_frame();
|
||||||
}
|
}
|
||||||
|
@ -90,18 +90,18 @@ impl UiInstance {
|
||||||
if self.draw_commands.commands == self.prev_draw_commands.commands {
|
if self.draw_commands.commands == self.prev_draw_commands.commands {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.draw_plan = UiDrawPlan::build(&self.draw_commands, &mut self.text_renderer);
|
self.draw_call = UiDrawCall::build(&self.draw_commands, &mut self.text_renderer);
|
||||||
self.draw_plan_modified = true;
|
self.draw_call_modified = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the draw plan (a list of draw calls) for the current frame
|
/// Get the draw call for the current frame
|
||||||
///
|
///
|
||||||
/// This function should only be used by the render backend.\
|
/// This function should only be used by the render backend.\
|
||||||
/// You should not call this directly unless you're implementing a custom render backend
|
/// You should not call this directly unless you're implementing a custom render backend
|
||||||
///
|
///
|
||||||
/// Returns a tuple with a boolean indicating if the draw plan was modified since the last frame
|
/// Returns a tuple with a boolean indicating if the draw plan was modified since the last frame
|
||||||
pub fn draw_plan(&self) -> (bool, &UiDrawPlan) {
|
pub fn draw_call(&self) -> (bool, &UiDrawCall) {
|
||||||
(self.draw_plan_modified, &self.draw_plan)
|
(self.draw_call_modified, &self.draw_call)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the font texture for the current frame
|
/// Get the font texture for the current frame
|
||||||
|
|
Loading…
Reference in a new issue