WIP single draw call architecture

This commit is contained in:
griffi-gh 2024-02-21 20:13:58 +01:00
parent 3bcbe0ae6e
commit f54b218cbb
4 changed files with 208 additions and 181 deletions

View file

@ -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() {
self.plan.resize_with(plan.calls.len(), || {
GlDrawCall {
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_vtx = &call.vertices.iter().copied().map(Vertex::from).collect::<Vec<_>>()[..];
let data_idx = &call.indices[..]; let data_idx = &call.indices[..];
self.plan[idx].active = true; if let Some(buffer) = &mut self.buffer_pair {
self.plan[idx].buffer.write_data(data_vtx, data_idx); buffer.write_data(data_vtx, data_idx);
self.plan[idx].bind_texture = match call.bind_texture { } else if !call.indices.is_empty() {
Some(BindTexture::FontTexture) => { self.buffer_pair = Some(BufferPair::new_with_data(&self.context, data_vtx, data_idx));
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) { // 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_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();
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(
vtx_buffer, vtx_buffer,
idx_buffer, idx_buffer,
&self.program_tex, &self.program_tex,
&uniform! { &uniform! {
resolution: resolution.to_array(), resolution: resolution.to_array(),
tex: Sampler(bind_texture.as_ref(), SamplerBehavior { tex: Sampler(self.ui_texture.as_ref().unwrap().as_ref(), SamplerBehavior {
wrap_function: (SamplerWrapFunction::Clamp, SamplerWrapFunction::Clamp, SamplerWrapFunction::Clamp), wrap_function: (SamplerWrapFunction::Clamp, SamplerWrapFunction::Clamp, SamplerWrapFunction::Clamp),
..Default::default() ..Default::default()
}), }),
}, },
&params, &params,
).unwrap(); ).unwrap();
} else {
frame.draw( // if let Some(bind_texture) = call.bind_texture.as_ref() {
vtx_buffer,
idx_buffer, // } else {
&self.program, // frame.draw(
&uniform! { // vtx_buffer,
resolution: resolution.to_array(), // idx_buffer,
}, // &self.program,
&params, // &uniform! {
).unwrap(); // resolution: resolution.to_array(),
} // },
// &params,
// ).unwrap();
// }
} }
} }
} }

View file

@ -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")] #[cfg(feature = "pixel_perfect")]
swapper.current_mut().vertices.iter_mut().for_each(|v| { draw_call.vertices.iter_mut().for_each(|v| {
v.position = v.position.round() v.position = v.position.round()
}); });
prev_command = Some(command); draw_call
}
Self {
calls: swapper.finish()
}
} }
} }
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
View 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))
}
}

View file

@ -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