performane optimisations
This commit is contained in:
Generated
+1
@@ -341,6 +341,7 @@ name = "osgui"
|
|||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"minifb",
|
"minifb",
|
||||||
|
"once_cell",
|
||||||
"rusttype",
|
"rusttype",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user