This commit is contained in:
griffi-gh 2024-02-25 15:43:38 +01:00
parent cdba2fedd8
commit b064a2cb2b
6 changed files with 72 additions and 67 deletions

View file

@ -116,7 +116,7 @@ impl BufferPair {
pub struct GliumUiRenderer {
context: Rc<Context>,
program: glium::Program,
ui_texture: Option<Rc<SrgbTexture2d>>,
ui_texture: Option<SrgbTexture2d>,
buffer_pair: Option<BufferPair>,
}
@ -143,17 +143,17 @@ impl GliumUiRenderer {
pub fn update_texture_atlas(&mut self, atlas: &TextureAtlasMeta) {
log::trace!("updating ui atlas texture");
self.ui_texture = Some(Rc::new(SrgbTexture2d::new(
self.ui_texture = Some(SrgbTexture2d::new(
&self.context,
RawImage2d::from_raw_rgba(
atlas.data.to_owned(),
(atlas.size.x, atlas.size.y)
)
).unwrap()));
).unwrap());
}
pub fn update(&mut self, hui: &UiInstance) {
if hui.atlas().modified {
if self.ui_texture.is_none() || hui.atlas().modified {
self.update_texture_atlas(&hui.atlas());
}
if hui.draw_call().0 {
@ -181,7 +181,7 @@ impl GliumUiRenderer {
&self.program,
&uniform! {
resolution: resolution.to_array(),
tex: Sampler(self.ui_texture.as_ref().unwrap().as_ref(), SamplerBehavior {
tex: Sampler(self.ui_texture.as_ref().unwrap(), SamplerBehavior {
wrap_function: (SamplerWrapFunction::Clamp, SamplerWrapFunction::Clamp, SamplerWrapFunction::Clamp),
..Default::default()
}),

View file

@ -16,9 +16,10 @@ use std::borrow::Cow;
use fontdue::layout::{Layout, CoordinateSystem, TextStyle};
use glam::{Vec2, Vec4, vec2};
//TODO: circle draw command
/// Available draw commands
/// - Rectangle: Filled, colored rectangle, with optional rounded corners
/// - Circle: Simple filled, colored circle
/// - Rectangle: Filled, colored rectangle, with optional rounded corners and texture
/// - Text: Draw text using the specified font, size, color, and position
#[derive(Clone, Debug, PartialEq)]
pub enum UiDrawCommand {
@ -30,18 +31,11 @@ pub enum UiDrawCommand {
size: Vec2,
///Color (RGBA)
color: Corners<Vec4>,
///Texture
texture: Option<TextureHandle>,
///Rounded corners
rounded_corners: Option<RoundedCorners>,
},
/// Filled, colored circle
Circle {
///Position in pixels
position: Vec2,
///Radius in pixels
radius: f32,
///Color (RGBA)
color: Vec4,
},
/// Draw text using the specified font, size, color, and position
Text {
///Position in pixels
@ -98,7 +92,11 @@ impl UiDrawCall {
let mut draw_call = UiDrawCall::default();
for command in &draw_commands.commands {
match command {
UiDrawCommand::Rectangle { position, size, color, rounded_corners } => {
UiDrawCommand::Rectangle { position, size, color, texture, rounded_corners } => {
let uvs = texture
.map(|x| atlas.get_uv(x))
.flatten()
.unwrap_or(Corners::all(Vec2::ZERO));
let vidx = draw_call.vertices.len() as u32;
if let Some(corner) = rounded_corners.filter(|x| x.radius.max_f32() > 0.0) {
//this code is stupid as fuck
@ -122,25 +120,25 @@ impl UiDrawCall {
draw_call.vertices.push(UiVertex {
position: *position + vec2(x, 1. - y) * corner.radius.top_right + vec2(size.x - corner.radius.top_right, 0.),
color: color.top_right,
uv: vec2(0.0, 0.0),
uv: uvs.top_right,
});
//Bottom-right corner
draw_call.vertices.push(UiVertex {
position: *position + vec2(x - 1., y) * corner.radius.bottom_right + vec2(size.x, size.y - corner.radius.bottom_right),
color: color.bottom_right,
uv: vec2(0.0, 0.0),
uv: uvs.bottom_right,
});
//Bottom-left corner
draw_call.vertices.push(UiVertex {
position: *position + vec2(1. - x, y) * corner.radius.bottom_left + vec2(0., size.y - corner.radius.bottom_left),
color: color.bottom_left,
uv: vec2(0.0, 0.0),
uv: uvs.bottom_left,
});
//Top-left corner
draw_call.vertices.push(UiVertex {
position: *position + vec2(1. - x, 1. - y) * corner.radius.top_left,
color: color.top_left,
uv: vec2(0.0, 0.0),
uv: uvs.top_left,
});
// mental illness:
if i > 0 {
@ -190,29 +188,26 @@ impl UiDrawCall {
UiVertex {
position: *position,
color: color.top_left,
uv: vec2(0.0, 0.0),
uv: uvs.top_left,
},
UiVertex {
position: *position + vec2(size.x, 0.0),
color: color.top_right,
uv: vec2(0.0, 0.0), // vec2(1.0, 0.0),
uv: uvs.top_right,
},
UiVertex {
position: *position + *size,
color: color.bottom_right,
uv: vec2(0.0, 0.0), // vec2(1.0, 1.0),
uv: uvs.bottom_right,
},
UiVertex {
position: *position + vec2(0.0, size.y),
color: color.bottom_left,
uv: vec2(0.0, 0.0), // vec2(0.0, 1.0),
uv: uvs.bottom_left,
},
]);
}
},
UiDrawCommand::Circle { .. } => {
todo!("circle draw command not implemented yet")
},
UiDrawCommand::Text { position, size, color, text, font: font_handle } => {
if text.is_empty() {
continue
@ -226,16 +221,13 @@ impl UiDrawCall {
);
let glyphs = layout.glyphs();
//let mut rpos_x = 0.;
for layout_glyph in glyphs {
if !layout_glyph.char_data.rasterize() {
continue
}
let atlas_size = atlas.meta().size.as_vec2();
let vidx = draw_call.vertices.len() as u32;
let glyph = text_renderer.glyph(atlas, *font_handle, layout_glyph.parent, layout_glyph.key.px as u8);
let uv = atlas.get_uv(glyph.texture);
//rpos_x += glyph.metrics.advance_width;//glyph.metrics.advance_width;
let uv = atlas.get_uv(glyph.texture).unwrap();
draw_call.indices.extend([vidx, vidx + 1, vidx + 2, vidx, vidx + 2, vidx + 3]);
draw_call.vertices.extend([
UiVertex {

View file

@ -4,8 +4,7 @@ use nohash_hasher::BuildNoHashHasher;
use rect_packer::DensePacker;
use crate::rectangle::Corners;
const CHANNEL_COUNT: u32 = 4;
//TODO: make this work
const RGBA_CHANNEL_COUNT: u32 = 4;
const ALLOW_ROTATION: bool = false;
pub struct TextureAtlasMeta<'a> {
@ -14,16 +13,17 @@ pub struct TextureAtlasMeta<'a> {
pub modified: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct TextureHandle {
//TODO automatic cleanup when handle is dropped
//man: Weak<RefCell<TextureAtlasManager>>,
//pub(crate) rc: Rc<()>,
pub(crate) index: u32
}
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Debug)]
pub(crate) struct TextureAllocation {
/// Index of the texture allocation
//pub(crate) rc: Weak<()>,
/// Unique index of the texture allocation
pub index: u32,
/// Position in the texture atlas
@ -44,6 +44,8 @@ pub(crate) struct TextureAtlasManager {
size: UVec2,
data: Vec<u8>,
allocations: HashMap<u32, TextureAllocation, BuildNoHashHasher<u32>>,
/// Items that have been removed from the allocation list, but still affect
remove_queue: Vec<TextureAllocation>,
/// True if the atlas has been modified in a way which requires a texture reupload
/// since the beginning of the current frame
modified: bool,
@ -52,18 +54,16 @@ pub(crate) struct TextureAtlasManager {
impl TextureAtlasManager {
/// Create a new texture atlas with the specified size\
/// 512x512 is a good default size for most applications, and the texture atlas can grow dynamically as needed
/// By default, the texture atlas gets initialized with a single white pixel texture
pub fn new(size: UVec2) -> Self {
let mut tmp = Self {
Self {
packer: DensePacker::new(size.x as i32, size.y as i32),
count: 0,
size,
data: vec![0; (size.x * size.y * CHANNEL_COUNT) as usize],
data: vec![0; (size.x * size.y * RGBA_CHANNEL_COUNT) as usize],
allocations: HashMap::default(),
remove_queue: Vec::new(),
modified: true,
};
tmp.add(1, &[255,255,255,255]);
tmp
}
}
/// Resize the texture atlas to the new size in-place, preserving the existing data
@ -76,12 +76,12 @@ impl TextureAtlasManager {
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);
self.data.resize((new_size.x * new_size.y * RGBA_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) * CHANNEL_COUNT) as usize;
let new_idx = ((y * new_size.x + x) * CHANNEL_COUNT) as usize;
for c in 0..(CHANNEL_COUNT as usize) {
let idx = ((y * self.size.x + x) * RGBA_CHANNEL_COUNT) as usize;
let new_idx = ((y * new_size.x + x) * RGBA_CHANNEL_COUNT) as usize;
for c in 0..(RGBA_CHANNEL_COUNT as usize) {
self.data[new_idx + c] = self.data[idx + c];
}
}
@ -94,6 +94,22 @@ impl TextureAtlasManager {
self.modified = true;
}
/// Ensure that a texture with specified size would fit without resizing on the next allocation attempt\
pub fn ensure_fits(&mut self, size: UVec2) {
// Plan A: try if any of the existing items in the remove queue would fit the texture
// Plan B: purge the remove queue, recreate the packer and try again (might be expensive...!)
// TODO: implement these
// Plan C: resize the atlas
let mut new_size = self.size;
while !self.packer.can_pack(size.x as i32, size.y as i32, true) {
new_size *= 2;
self.packer.resize(new_size.x as i32, new_size.y as i32);
}
if new_size != self.size {
self.resize(new_size);
}
}
/// Allocate a new texture region in the atlas and return a handle to it\
/// Returns None if the texture could not be allocated due to lack of space\
/// Use `allocate` to allocate a texture and resize the atlas if necessary\
@ -118,29 +134,22 @@ impl TextureAtlasManager {
/// This function should never fail under normal circumstances.\
/// May modify the texture data if the atlas is resized
pub fn allocate(&mut self, size: UVec2) -> TextureHandle {
let mut new_size = self.size;
while !self.packer.can_pack(size.x as i32, size.y as i32, true) {
new_size *= 2;
self.packer.resize(new_size.x as i32, new_size.y as i32);
}
if new_size != self.size {
self.resize(new_size);
}
self.ensure_fits(size);
self.try_allocate(size).unwrap()
}
/// Allocate a new texture region in the atlas and copy the data into it\
/// This function may resize the atlas as needed, and should never fail under normal circumstances.
pub fn add(&mut self, width: usize, data: &[u8]) -> TextureHandle {
let size = uvec2(width as u32, (data.len() / (width * CHANNEL_COUNT as usize)) as u32);
let size = uvec2(width as u32, (data.len() / (width * RGBA_CHANNEL_COUNT as usize)) as u32);
let handle: TextureHandle = self.allocate(size);
let allocation = self.allocations.get(&handle.index).unwrap();
assert!(!allocation.rotated, "Rotated textures are not implemented yet");
for y in 0..size.y {
for x in 0..size.x {
let src_idx = (y * size.x + x) * CHANNEL_COUNT;
let dst_idx = ((allocation.position.y + y) * self.size.x + allocation.position.x + x) * CHANNEL_COUNT;
for c in 0..CHANNEL_COUNT as usize {
let src_idx = (y * size.x + x) * RGBA_CHANNEL_COUNT;
let dst_idx = ((allocation.position.y + y) * self.size.x + allocation.position.x + x) * RGBA_CHANNEL_COUNT;
for c in 0..RGBA_CHANNEL_COUNT as usize {
self.data[dst_idx as usize + c] = data[src_idx as usize + c];
}
}
@ -160,8 +169,8 @@ impl TextureAtlasManager {
for y in 0..size.y {
for x in 0..size.x {
let src_idx = (y * size.x + x) as usize;
let dst_idx = (((allocation.position.y + y) * self.size.x + allocation.position.x + x) * CHANNEL_COUNT) as usize;
self.data[dst_idx..(dst_idx + CHANNEL_COUNT as usize)].copy_from_slice(&[255, 255, 255, data[src_idx]]);
let dst_idx = (((allocation.position.y + y) * self.size.x + allocation.position.x + x) * RGBA_CHANNEL_COUNT) as usize;
self.data[dst_idx..(dst_idx + RGBA_CHANNEL_COUNT as usize)].copy_from_slice(&[255, 255, 255, data[src_idx]]);
}
}
self.modified = true;
@ -180,19 +189,19 @@ impl TextureAtlasManager {
self.allocations.get(&handle.index)
}
pub(crate) fn get_uv(&self, handle: TextureHandle) -> Corners<Vec2> {
let info = self.get(handle).unwrap();
pub(crate) fn get_uv(&self, handle: TextureHandle) -> Option<Corners<Vec2>> {
let info = self.get(handle)?;
let atlas_size = self.meta().size.as_vec2();
let p0x = info.position.x as f32 / atlas_size.x;
let p1x = (info.position.x as f32 + info.size.x as f32) / atlas_size.x;
let p0y = info.position.y as f32 / atlas_size.y;
let p1y = (info.position.y as f32 + info.size.y as f32) / atlas_size.y;
Corners {
Some(Corners {
top_left: vec2(p0x, p0y),
top_right: vec2(p1x, p0y),
bottom_left: vec2(p0x, p1y),
bottom_right: vec2(p1x, p1y),
}
})
}
/// Reset the `is_modified` flag

View file

@ -125,6 +125,7 @@ impl UiElement for Container {
position,
size: ctx.measure.size,
color: corner_colors,
texture: None,
rounded_corners: (self.corner_radius.max_f32() > 0.).then_some({
RoundedCorners::from_radius(self.corner_radius)
}),

View file

@ -72,6 +72,7 @@ impl UiElement for ProgressBar {
position: ctx.layout.position,
size: ctx.measure.size,
color: Corners::all(self.color_background),
texture: None,
rounded_corners
});
}
@ -80,6 +81,7 @@ impl UiElement for ProgressBar {
position: ctx.layout.position,
size: ctx.measure.size * vec2(value, 1.0),
color: Corners::all(self.color_foreground),
texture: None,
rounded_corners,
});
}

View file

@ -48,6 +48,7 @@ impl UiElement for Rect {
position: ctx.layout.position,
size: ctx.measure.size,
color: self.color.corners().unwrap(),
texture: None,
rounded_corners: None,
});
}