#![allow(
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_sign_loss,
clippy::cast_lossless
)]
use std::collections::HashMap;
use egui::epaint::{ClippedPrimitive, ImageDelta, Primitive, Vertex};
use egui::{Color32, ImageData, TextureId};
struct Tex {
w: usize,
h: usize,
px: Vec<Color32>,
}
#[derive(Default)]
pub(crate) struct EguiRaster {
textures: HashMap<TextureId, Tex>,
}
impl EguiRaster {
pub(crate) fn update_textures(&mut self, set: &[(TextureId, ImageDelta)], free: &[TextureId]) {
for (id, delta) in set {
let [pw, ph] = delta.image.size();
let ImageData::Color(img) = &delta.image;
let patch: Vec<Color32> = img.pixels.clone();
match delta.pos {
None => {
self.textures.insert(
*id,
Tex {
w: pw,
h: ph,
px: patch,
},
);
}
Some([ox, oy]) => {
if let Some(tex) = self.textures.get_mut(id) {
for row in 0..ph {
let dy = oy + row;
if dy >= tex.h {
break;
}
for col in 0..pw {
let dx = ox + col;
if dx >= tex.w {
continue;
}
tex.px[dy * tex.w + dx] = patch[row * pw + col];
}
}
}
}
}
}
for id in free {
self.textures.remove(id);
}
}
pub(crate) fn paint(
&self,
fb: &mut [u32],
width: u32,
height: u32,
jobs: &[ClippedPrimitive],
pixels_per_point: f32,
) {
let wf = width as f32;
let hf = height as f32;
for cp in jobs {
let Primitive::Mesh(mesh) = &cp.primitive else {
continue; };
let Some(tex) = self.textures.get(&mesh.texture_id) else {
continue;
};
let clip = ClipRect {
min_x: (cp.clip_rect.min.x * pixels_per_point).floor().max(0.0) as i32,
min_y: (cp.clip_rect.min.y * pixels_per_point).floor().max(0.0) as i32,
max_x: (cp.clip_rect.max.x * pixels_per_point).ceil().min(wf) as i32,
max_y: (cp.clip_rect.max.y * pixels_per_point).ceil().min(hf) as i32,
};
if clip.max_x <= clip.min_x || clip.max_y <= clip.min_y {
continue;
}
for tri in mesh.indices.chunks_exact(3) {
let v0 = &mesh.vertices[tri[0] as usize];
let v1 = &mesh.vertices[tri[1] as usize];
let v2 = &mesh.vertices[tri[2] as usize];
raster_tri(fb, width, height, tex, &clip, v0, v1, v2, pixels_per_point);
}
}
}
}
struct ClipRect {
min_x: i32,
min_y: i32,
max_x: i32,
max_y: i32,
}
#[inline]
fn edge(ax: f32, ay: f32, bx: f32, by: f32, px: f32, py: f32) -> f32 {
(bx - ax) * (py - ay) - (by - ay) * (px - ax)
}
#[allow(clippy::too_many_arguments)]
fn raster_tri(
fb: &mut [u32],
width: u32,
height: u32,
tex: &Tex,
clip: &ClipRect,
v0: &Vertex,
v1: &Vertex,
v2: &Vertex,
ppp: f32,
) {
let (x0, y0) = (v0.pos.x * ppp, v0.pos.y * ppp);
let (x1, y1) = (v1.pos.x * ppp, v1.pos.y * ppp);
let (x2, y2) = (v2.pos.x * ppp, v2.pos.y * ppp);
let area = edge(x0, y0, x1, y1, x2, y2);
if area == 0.0 {
return;
}
let inv_area = 1.0 / area;
let min_x = x0.min(x1).min(x2).floor() as i32;
let max_x = x0.max(x1).max(x2).ceil() as i32;
let min_y = y0.min(y1).min(y2).floor() as i32;
let max_y = y0.max(y1).max(y2).ceil() as i32;
let lo_x = min_x.max(clip.min_x).max(0);
let hi_x = max_x.min(clip.max_x).min(width as i32);
let lo_y = min_y.max(clip.min_y).max(0);
let hi_y = max_y.min(clip.max_y).min(height as i32);
if lo_x >= hi_x || lo_y >= hi_y {
return;
}
let c0 = v0.color.to_array();
let c1 = v1.color.to_array();
let c2 = v2.color.to_array();
let tw = tex.w as f32;
let th = tex.h as f32;
for y in lo_y..hi_y {
let py = y as f32 + 0.5;
let row = (y as usize) * (width as usize);
for x in lo_x..hi_x {
let px = x as f32 + 0.5;
let w0 = edge(x1, y1, x2, y2, px, py) * inv_area;
let w1 = edge(x2, y2, x0, y0, px, py) * inv_area;
let w2 = edge(x0, y0, x1, y1, px, py) * inv_area;
if w0 < 0.0 || w1 < 0.0 || w2 < 0.0 {
continue;
}
let u = w0 * v0.uv.x + w1 * v1.uv.x + w2 * v2.uv.x;
let v = w0 * v0.uv.y + w1 * v1.uv.y + w2 * v2.uv.y;
let vr = w0 * c0[0] as f32 + w1 * c1[0] as f32 + w2 * c2[0] as f32;
let vg = w0 * c0[1] as f32 + w1 * c1[1] as f32 + w2 * c2[1] as f32;
let vb = w0 * c0[2] as f32 + w1 * c1[2] as f32 + w2 * c2[2] as f32;
let va = w0 * c0[3] as f32 + w1 * c1[3] as f32 + w2 * c2[3] as f32;
let tx = (u * tw).clamp(0.0, tw - 1.0) as usize;
let ty = (v * th).clamp(0.0, th - 1.0) as usize;
let texel = tex.px[ty * tex.w + tx].to_array();
let sr = vr * texel[0] as f32 * (1.0 / 255.0);
let sg = vg * texel[1] as f32 * (1.0 / 255.0);
let sb = vb * texel[2] as f32 * (1.0 / 255.0);
let sa = va * texel[3] as f32 * (1.0 / 255.0);
if sa <= 0.0 {
continue;
}
let inv = 1.0 - sa * (1.0 / 255.0);
let idx = row + x as usize;
let dst = fb[idx];
let dr = ((dst >> 16) & 0xff) as f32;
let dg = ((dst >> 8) & 0xff) as f32;
let db = (dst & 0xff) as f32;
let or = (sr + dr * inv).clamp(0.0, 255.0) as u32;
let og = (sg + dg * inv).clamp(0.0, 255.0) as u32;
let ob = (sb + db * inv).clamp(0.0, 255.0) as u32;
fb[idx] = (or << 16) | (og << 8) | ob;
}
}
}