performane optimisations

This commit is contained in:
2025-07-03 23:26:10 +02:00
parent e260a38028
commit c2f25e1a89
6 changed files with 129 additions and 120 deletions
Generated
+1
View File
@@ -341,6 +341,7 @@ name = "osgui"
version = "0.0.1" version = "0.0.1"
dependencies = [ dependencies = [
"minifb", "minifb",
"once_cell",
"rusttype", "rusttype",
] ]
+1
View File
@@ -10,5 +10,6 @@ name = "osgui"
[dependencies] [dependencies]
minifb = "0.28.0" minifb = "0.28.0"
once_cell = "1.21.3"
rusttype = "0.9.3" rusttype = "0.9.3"
+4 -17
View File
@@ -1,16 +1,11 @@
use std::sync::Arc; use std::sync::Arc;
use crate::{ use crate::{component, extensions::Extension, style::Transform, widget::Widget};
component,
extensions::Extension,
style::{Position, Transform},
widget::Widget,
};
pub struct VelocityExtension; pub struct VelocityExtension;
impl VelocityExtension { impl VelocityExtension {
fn apply_velocity(ticks: usize, velocity: i32, x: &mut usize) { fn apply_velocity(ticks: usize, velocity: i32, x: &mut i32) {
if velocity.abs() != 0 && ticks as i32 % (1000 / velocity.abs()) == 0 { if velocity.abs() != 0 && ticks as i32 % (1000 / velocity.abs()) == 0 {
if velocity > 0 { if velocity > 0 {
*x += 1; *x += 1;
@@ -25,16 +20,8 @@ impl VelocityExtension {
fn apply_velocity_xy(ticks: usize, widget: &Arc<Widget>) { fn apply_velocity_xy(ticks: usize, widget: &Arc<Widget>) {
if let Some(velocity) = widget.get::<Velocity>() { if let Some(velocity) = widget.get::<Velocity>() {
if let Some(mut t) = widget.get::<Transform>() { if let Some(mut t) = widget.get::<Transform>() {
match &mut t.x { Self::apply_velocity(ticks, velocity.0, &mut t.mx);
Position::Const(x) => Self::apply_velocity(ticks, velocity.0, x), Self::apply_velocity(ticks, velocity.1, &mut t.my);
_ => {}
}
match &mut t.y {
Position::Const(y) => Self::apply_velocity(ticks, velocity.1, y),
_ => {}
}
widget.set_component(t); widget.set_component(t);
} }
} }
+24 -11
View File
@@ -1,6 +1,5 @@
use std::sync::{Arc, Mutex};
use minifb::Window; use minifb::Window;
use std::sync::{Arc, Mutex};
use crate::{ use crate::{
extensions::Extension, extensions::Extension,
@@ -24,8 +23,8 @@ pub struct Screen {
} }
impl Screen { impl Screen {
pub fn new(win: Window) -> Screen { pub fn new(win: Window) -> Self {
Screen { Self {
window: win, window: win,
widgets: Vec::new(), widgets: Vec::new(),
extensions: Vec::new(), extensions: Vec::new(),
@@ -42,7 +41,7 @@ impl Screen {
} }
pub fn run(&mut self) -> std::io::Result<()> { pub fn run(&mut self) -> std::io::Result<()> {
for elem in &mut self.widgets { for elem in &self.widgets {
elem.component(Transform::new()); elem.component(Transform::new());
} }
@@ -50,17 +49,30 @@ impl Screen {
ext.lock().unwrap().init(&self.widgets); ext.lock().unwrap().init(&self.widgets);
} }
let (w, h) = self.window.get_size();
let mut scope = RenderScope::new(w, h);
let mut last_fps_check = std::time::Instant::now();
let mut frame_count = 0;
while self.window.is_open() { while self.window.is_open() {
self.render(); self.render(&mut scope);
std::thread::sleep(std::time::Duration::from_millis(28)); frame_count += 1;
// Print FPS every second
if last_fps_check.elapsed() >= std::time::Duration::from_secs(1) {
println!("{}", frame_count);
frame_count = 0;
last_fps_check = std::time::Instant::now();
}
} }
Ok(()) Ok(())
} }
fn render(&mut self) { fn render(&mut self, scope: &mut RenderScope) {
let (w, h) = self.window.get_size(); let (w, h) = self.window.get_size();
let mut scope = RenderScope::new(w, h); scope.resize_if_needed(w, h);
for ext in &self.extensions { for ext in &self.extensions {
ext.lock() ext.lock()
@@ -74,7 +86,7 @@ impl Screen {
scope.set_transform(&t); scope.set_transform(&t);
} }
elem.0.lock().unwrap().render(&mut scope); elem.0.lock().unwrap().render(scope);
if let Some(t) = elem.get() { if let Some(t) = elem.get() {
scope.set_transform(&t); scope.set_transform(&t);
@@ -90,7 +102,8 @@ impl Screen {
} }
self.window self.window
.update_with_buffer(&scope.get_buffer1d(), w, h) .update_with_buffer(scope.get_buffer1d(), w, h)
.unwrap(); .unwrap();
scope.clear_buffer();
} }
} }
+7 -2
View File
@@ -1,7 +1,10 @@
use minifb::{Window, WindowOptions}; use minifb::{Window, WindowOptions};
use osgui::{ use osgui::{
elements::div::{Div, DivStyle}, elements::div::{Div, DivStyle},
extensions::mouse::{MouseExtension, OnClick}, extensions::{
mouse::{MouseExtension, OnClick},
velocity::{Velocity, VelocityExtension},
},
style::Transform, style::Transform,
Screen, Screen,
}; };
@@ -20,6 +23,7 @@ fn main() {
let mut app = Screen::new(window); let mut app = Screen::new(window);
app.extension(MouseExtension::new()); app.extension(MouseExtension::new());
app.extension(VelocityExtension);
milestones(&mut app); milestones(&mut app);
@@ -49,6 +53,7 @@ fn milestones(app: &mut Screen) {
osgui::style::Position::Center, osgui::style::Position::Center,
osgui::style::Position::Const(145 + ((194 + 30) * i)), osgui::style::Position::Const(145 + ((194 + 30) * i)),
)) ))
.component(OnClick(|_| println!("Clicked!"))); .component(OnClick(|_| println!("Clicked!")))
.component(Velocity(50, 0));
} }
} }
+91 -89
View File
@@ -1,3 +1,4 @@
use once_cell::sync::Lazy;
use rusttype::{Font, Scale}; use rusttype::{Font, Scale};
use crate::{ use crate::{
@@ -6,6 +7,7 @@ use crate::{
}; };
const FONT: &[u8] = include_bytes!("../assets/DejaVuSans.ttf"); const FONT: &[u8] = include_bytes!("../assets/DejaVuSans.ttf");
static FONT_OBJ: Lazy<Font<'static>> = Lazy::new(|| Font::try_from_bytes(FONT).unwrap());
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum RenderMethod { pub enum RenderMethod {
@@ -22,13 +24,13 @@ pub struct RenderScope {
parent_width: usize, parent_width: usize,
parent_height: usize, parent_height: usize,
max_size: (usize, usize), max_size: (usize, usize),
buffer: Vec<Vec<u32>>, buffer: Vec<u32>,
} }
impl RenderScope { impl RenderScope {
pub fn new(w: usize, h: usize) -> Self { pub fn new(w: usize, h: usize) -> Self {
Self { Self {
buffer: vec![vec![0; w]; h], buffer: vec![0; w * h],
render_stack: Vec::new(), render_stack: Vec::new(),
parent_width: w, parent_width: w,
parent_height: h, parent_height: h,
@@ -43,8 +45,7 @@ impl RenderScope {
} }
self.render_stack self.render_stack
.push(RenderMethod::Text(text.to_string(), x, y, scale, color)); .push(RenderMethod::Text(text.to_string(), x, y, scale, color));
let font = Font::try_from_bytes(FONT).unwrap(); let (w, h) = utils::measure_text(&FONT_OBJ, text, Scale::uniform(scale));
let (w, h) = utils::measure_text(&font, &text, Scale::uniform(scale));
self.transform.width = self.transform.width.max(w as usize); self.transform.width = self.transform.width.max(w as usize);
self.transform.height = self.transform.height.max(h as usize); self.transform.height = self.transform.height.max(h as usize);
self.update_size(); self.update_size();
@@ -97,123 +98,115 @@ impl RenderScope {
// After draw // After draw
impl RenderScope { impl RenderScope {
pub fn draw(&mut self) { pub fn draw(&mut self) {
let stride = self.parent_width;
let mut tmp = std::mem::take(&mut self.buffer); let mut tmp = std::mem::take(&mut self.buffer);
self.draw_buf(&mut tmp); self.draw_buf(&mut tmp, stride);
self.buffer = tmp; self.buffer = tmp;
} }
pub fn draw_buf(&self, buf: &mut Vec<Vec<u32>>) { pub fn draw_buf(&mut self, buf: &mut [u32], stride: usize) {
let offset_x = self.transform.x; let offset_x = self.transform.x;
let offset_y = self.transform.y; let offset_y = self.transform.y;
for m in &self.render_stack { for m in &mut self.render_stack {
match m { match m {
&RenderMethod::Rectangle(px, py, width, height, color) => { /* ───────────── Rectangle ───────────── */
for y in offset_y + py..(offset_y + py + height).min(self.parent_height) { RenderMethod::Rectangle(px, py, width, height, color) => {
for x in offset_x + px..(offset_x + px + width).min(self.parent_width) { let xs = offset_x + *px;
buf[y][x] = color; let xe = (xs + *width).min(self.parent_width);
} let ys = offset_y + *py;
let ye = (ys + *height).min(self.parent_height);
for y in ys..ye {
let start = y * stride + xs;
let end = start + (xe - xs);
buf[start..end].fill(*color);
} }
} }
&RenderMethod::RoundedRectangle(px, py, width, height, radius, color) => { /* ─────────── Rounded Rectangle ─────────── */
let radius = radius as i32; RenderMethod::RoundedRectangle(px, py, width, height, radius, color) => {
let w = width as i32; let r = *radius as i32;
let h = height as i32; let ww = *width as i32;
let start_x = offset_x as i32 + px as i32; let hh = *height as i32;
let start_y = offset_y as i32 + py as i32; let sx = (offset_x + *px) as i32;
let sy = (offset_y + *py) as i32;
for y in 0..h { for y in 0..hh {
for x in 0..w { for x in 0..ww {
let dx = x; // corners ⇒ circle test
let dy = y; let in_tl = x < r && y < r;
let in_tr = x >= ww - r && y < r;
let in_bl = x < r && y >= hh - r;
let in_br = x >= ww - r && y >= hh - r;
let in_top_left = dx < radius && dy < radius; let ok = if in_tl {
let in_top_right = dx >= w - radius && dy < radius; (x - r + 1).pow(2) + (y - r + 1).pow(2) <= r.pow(2)
let in_bottom_left = dx < radius && dy >= h - radius; } else if in_tr {
let in_bottom_right = dx >= w - radius && dy >= h - radius; (x - (ww - r)).pow(2) + (y - r + 1).pow(2) <= r.pow(2)
} else if in_bl {
let dist = |cx: i32, cy: i32| -> bool { (x - r + 1).pow(2) + (y - (hh - r)).pow(2) <= r.pow(2)
let dx = dx - cx; } else if in_br {
let dy = dy - cy; (x - (ww - r)).pow(2) + (y - (hh - r)).pow(2) <= r.pow(2)
dx * dx + dy * dy <= radius * radius } else {
true
}; };
let draw = match ( if ok {
in_top_left, let gx = sx + x;
in_top_right, let gy = sy + y;
in_bottom_left, if gx >= 0
in_bottom_right, && gx < self.parent_width as i32
) { && gy >= 0
(true, _, _, _) => dist(radius - 1, radius - 1), && gy < self.parent_height as i32
(_, true, _, _) => dist(w - radius, radius - 1),
(_, _, true, _) => dist(radius - 1, h - radius),
(_, _, _, true) => dist(w - radius, h - radius),
_ => true, // middle or edges outside corner
};
if draw {
let global_x = start_x + x;
let global_y = start_y + y;
if global_x >= 0
&& global_x < self.parent_width as i32
&& global_y >= 0
&& global_y < self.parent_height as i32
{ {
buf[global_y as usize][global_x as usize] = color; buf[(gy as usize) * stride + gx as usize] = *color;
} }
} }
} }
} }
} }
RenderMethod::Text(text, px, py, scale, base_color) => { /* ────────────────── Text ───────────────── */
let font = Font::try_from_bytes(FONT).unwrap(); RenderMethod::Text(text, px, py, scale, base) => {
let scale = Scale::uniform(*scale); let scale = Scale::uniform(*scale);
let v_metrics = font.v_metrics(scale); let ascent = FONT_OBJ.v_metrics(scale).ascent;
let glyphs: Vec<_> = font let r0 = ((*base >> 16) & 0xFF) as f32;
.layout( let g0 = ((*base >> 8) & 0xFF) as f32;
let b0 = (*base & 0xFF) as f32;
for glyph in FONT_OBJ.layout(
text, text,
scale, scale,
rusttype::point( rusttype::point((offset_x + *px) as f32, (offset_y + *py) as f32 + ascent),
(offset_x + px) as f32, ) {
(offset_y + py) as f32 + v_metrics.ascent,
),
)
.collect();
let r_base = ((base_color >> 16) & 0xFF) as f32;
let g_base = ((base_color >> 8) & 0xFF) as f32;
let b_base = (base_color & 0xFF) as f32;
for glyph in glyphs {
if let Some(bb) = glyph.pixel_bounding_box() { if let Some(bb) = glyph.pixel_bounding_box() {
glyph.draw(|gx, gy, v| { glyph.draw(|gx, gy, v| {
if v < 0.5 { if v < 0.5 {
return; return;
} }
let x = (gx as i32 + bb.min.x + *px as i32) as usize; let x = gx as i32 + bb.min.x;
let y = (gy as i32 + bb.min.y + *py as i32) as usize; let y = gy as i32 + bb.min.y;
if let Some(row) = buf.get_mut(y) { let idx = (y as usize) * stride + x as usize;
if let Some(col) = row.get_mut(x) { if let Some(p) = buf.get_mut(idx) {
let r = (r_base * v) as u32; let r = (r0 * v) as u32;
let g = (g_base * v) as u32; let g = (g0 * v) as u32;
let b = (b_base * v) as u32; let b = (b0 * v) as u32;
*col = (r << 16) | (g << 8) | b; *p = (r << 16) | (g << 8) | b;
}
} }
}); });
} }
} }
} }
/* ───────────────── Merge ───────────────── */
RenderMethod::Merge(scope) => { RenderMethod::Merge(scope) => {
let mut scope = scope.clone();
scope.transform.x += self.transform.x; scope.transform.x += self.transform.x;
scope.transform.y += self.transform.y; scope.transform.y += self.transform.y;
scope.draw_buf(buf); scope.draw_buf(buf, stride);
} }
} }
} }
@@ -223,6 +216,14 @@ impl RenderScope {
// Update or variable functions // Update or variable functions
impl RenderScope { impl RenderScope {
pub fn resize_if_needed(&mut self, w: usize, h: usize) {
if self.parent_width != w || self.parent_height != h {
self.parent_width = w;
self.parent_height = h;
self.buffer.resize(w * h, 0);
}
}
pub fn set_transform(&mut self, transform: &Transform) { pub fn set_transform(&mut self, transform: &Transform) {
transform.use_dimensions(&mut self.transform); transform.use_dimensions(&mut self.transform);
transform.use_position(self.parent_width, self.parent_height, &mut self.transform); transform.use_position(self.parent_width, self.parent_height, &mut self.transform);
@@ -270,18 +271,19 @@ impl RenderScope {
(self.parent_width, self.parent_height) (self.parent_width, self.parent_height)
} }
pub fn get_buffer1d(&self) -> Vec<u32> { pub fn get_buffer1d(&self) -> &Vec<u32> {
self.buffer &self.buffer
.iter()
.flat_map(|row| row.iter().copied())
.collect()
} }
pub fn get_buffer(&self) -> Vec<Vec<u32>> { pub fn get_buffer(&self) -> Vec<&[u32]> {
self.buffer.clone() self.buffer.chunks(self.parent_width).collect()
} }
pub fn get_buffer_mut(&mut self) -> &mut Vec<Vec<u32>> { pub fn get_buffer_mut(&mut self) -> Vec<&mut [u32]> {
&mut self.buffer self.buffer.chunks_mut(self.parent_width).collect()
}
pub fn clear_buffer(&mut self) {
self.buffer.fill(0);
} }
} }