Created
November 15, 2023 14:30
-
-
Save InfiniteCoder01/2c1e9d7afd038c7cea39c5849eb72cab to your computer and use it in GitHub Desktop.
Just a random heavily-paralellized piece of code that can draw to a framebuffer. You can make a library out of it
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use fontdue::layout::Layout; | |
use rayon::{prelude::*, slice::ParallelSliceMut}; | |
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] | |
pub struct Color { | |
pub r: u8, | |
pub g: u8, | |
pub b: u8, | |
} | |
impl Color { | |
pub const WHITE: Color = Color::from_rgb(0xffffff); | |
pub const BLACK: Color = Color::from_rgb(0x000000); | |
pub const RED: Color = Color::from_rgb(0xff0000); | |
pub const GREEN: Color = Color::from_rgb(0x00ff00); | |
pub const BLUE: Color = Color::from_rgb(0x0000ff); | |
pub const YELLOW: Color = Color::from_rgb(0xffff00); | |
pub const CYAN: Color = Color::from_rgb(0x00ffff); | |
pub const MAGENTA: Color = Color::from_rgb(0xff00ff); | |
pub const fn new(r: u8, g: u8, b: u8) -> Self { | |
Self { r, g, b } | |
} | |
pub const fn from_rgb(rgb: u32) -> Self { | |
let r = (rgb >> 16) & 0xff; | |
let g = (rgb >> 8) & 0xff; | |
let b = rgb & 0xff; | |
Self::new(r as _, g as _, b as _) | |
} | |
pub const fn grayscale(color: u8) -> Self { | |
Self::new(color, color, color) | |
} | |
/// Sets the pixel of this [`Color`]. | |
/// | |
/// # Safety | |
/// Use this only in [`Frame::parallel_region`] | |
/// . | |
#[inline(always)] | |
pub unsafe fn set_pixel(self, pixel: &mut [u8]) { | |
*pixel.get_unchecked_mut(0) = self.r; | |
*pixel.get_unchecked_mut(1) = self.g; | |
*pixel.get_unchecked_mut(2) = self.b; | |
} | |
/// Blends the pixel of this [`Color`] with the pixel of the given [`Color`] by the given alpha value. | |
/// | |
/// # Safety | |
/// Use this only in [`Frame::parallel_region`] | |
#[inline(always)] | |
#[rustfmt::skip] | |
pub unsafe fn blend_pixel(&self, pixel: &mut [u8], alpha: u8) { | |
let inverse_alpha = 255 - alpha as u32; | |
*pixel.get_unchecked_mut(0) = ((*pixel.get_unchecked(0) as u32 * inverse_alpha + self.r as u32 * alpha as u32) / 255) as _; | |
*pixel.get_unchecked_mut(1) = ((*pixel.get_unchecked(1) as u32 * inverse_alpha + self.g as u32 * alpha as u32) / 255) as _; | |
*pixel.get_unchecked_mut(2) = ((*pixel.get_unchecked(2) as u32 * inverse_alpha + self.b as u32 * alpha as u32) / 255) as _; | |
} | |
} | |
struct ThreadPtr<T>(*mut T); | |
unsafe impl<T> Send for ThreadPtr<T> {} | |
unsafe impl<T> Sync for ThreadPtr<T> {} | |
impl<T> Clone for ThreadPtr<T> { | |
fn clone(&self) -> Self { | |
Self(self.0) | |
} | |
} | |
// * ------------------------------------- Frame ------------------------------------ * // | |
pub struct Frame<'a> { | |
buffer: &'a mut [u8], | |
pub width: usize, | |
pub height: usize, | |
} | |
impl<'a> Frame<'a> { | |
pub fn new(buffer: &'a mut [u8], width: usize, height: usize) -> Self { | |
Self { | |
buffer, | |
width, | |
height, | |
} | |
} | |
pub fn clear(&mut self, color: Color) { | |
self.buffer.par_chunks_exact_mut(3).for_each(|pixel| { | |
pixel[0] = color.r; | |
pixel[1] = color.g; | |
pixel[2] = color.b; | |
}); | |
} | |
/// This scary function just calls shader on each pixel of the region in parallel | |
#[inline(always)] | |
pub fn parallel_region( | |
&mut self, | |
x0: i32, | |
y0: i32, | |
width: usize, | |
height: usize, | |
shader: impl Fn(usize, usize, &mut [u8]) + Sync + Send, | |
) { | |
let (x, y) = (x0.max(0) as usize, y0.max(0) as usize); | |
let x = x.min(self.width - 1); | |
let buffer = ThreadPtr(self.buffer.as_mut_ptr()); | |
(y.min(self.height - 1)..(y + height).min(self.height)) | |
.into_par_iter() | |
.for_each(|y| { | |
let index = y * self.width + x; | |
let row = unsafe { | |
std::slice::from_raw_parts_mut( | |
buffer.clone().0.add(index * 3), | |
width.min(self.width - x) * 3, | |
) | |
}; | |
let y = (y as i32 - y0) as _; | |
row.par_chunks_exact_mut(3) | |
.enumerate() | |
.for_each(|(x, pixel)| shader(x, y, pixel)); | |
}); | |
} | |
pub fn fill_rect(&mut self, x: i32, y: i32, width: usize, height: usize, color: Color) { | |
self.parallel_region(x, y, width, height, |_, _, pixel| unsafe { | |
color.set_pixel(pixel); | |
}); | |
} | |
pub fn fill_circle(&mut self, x: i32, y: i32, radius: usize, color: Color) { | |
self.parallel_region( | |
x - radius as i32, | |
y - radius as i32, | |
radius * 2, | |
radius * 2, | |
|x, y, pixel| unsafe { | |
let radius = radius as i32; | |
let (x, y) = (x as i32 - radius, y as i32 - radius); | |
color.blend_pixel(pixel, (x * x + y * y <= radius * radius) as u8 * 255); | |
}, | |
); | |
} | |
pub fn draw_layout_text( | |
&mut self, | |
x: i32, | |
y: i32, | |
layout: &Layout, | |
color: Color, | |
fonts: &[fontdue::Font], | |
) { | |
let frame = ThreadPtr(self as *mut Self); | |
layout.glyphs().par_iter().for_each(|glyph| { | |
if glyph.parent == '\n' { | |
return; | |
} | |
let (metrics, bitmap) = | |
fonts[glyph.font_index].rasterize_indexed(glyph.key.glyph_index, glyph.key.px); | |
unsafe { &mut (*frame.clone().0) }.parallel_region( | |
x + glyph.x as i32, | |
y + glyph.y as i32, | |
metrics.width, | |
metrics.height, | |
|u, v, pixel| unsafe { | |
color.blend_pixel(pixel, bitmap[u + v * metrics.width]); | |
}, | |
); | |
}); | |
} | |
pub fn draw_text( | |
&mut self, | |
x: i32, | |
y: i32, | |
text: &str, | |
color: Color, | |
size: f32, | |
fonts: &[fontdue::Font], | |
) { | |
let mut layout = Layout::new(fontdue::layout::CoordinateSystem::PositiveYDown); | |
layout.append(fonts, &fontdue::layout::TextStyle::new(text, size, 0)); | |
self.draw_layout_text(x, y, &layout, color, fonts); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment