use alloc::vec::Vec;
use core::cell::RefCell;
use core::hash::{Hash, Hasher};
use sdl2::pixels::PixelFormatEnum;
use sdl2::render::{Canvas as SdlCanvas, Texture as SdlTexture, TextureCreator};
use sdl2::video::{Window, WindowContext};
use crate::cache::{LruCache, MaxSize, WithFactory};
use crate::draw::SwRenderer;
use crate::draw::canvas::Canvas as _;
use crate::draw::font::{CHAR_H, CHAR_W};
use crate::draw::texture::{ColorFormat, Texture as MiruiTexture};
use crate::types::{Color, Fixed, Point, Rect};
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
struct LabelKey {
text_hash: u64,
color_rgba: u32,
}
type CachedTexture = RefCell<SdlTexture<'static>>;
struct RasterCtx<'a> {
text: &'a [u8],
color: &'a Color,
creator: &'a TextureCreator<WindowContext>,
raster_buf: &'a mut Vec<u8>,
}
const DEFAULT_CAPACITY: usize = 128;
type LabelCtor = fn(&LabelKey, RasterCtx<'_>) -> Result<CachedTexture, ()>;
pub struct LabelCache {
cache: WithFactory<LruCache<LabelKey, CachedTexture>, LabelCtor>,
creator: TextureCreator<WindowContext>,
raster_buf: Vec<u8>,
}
impl LabelCache {
pub fn new(creator: TextureCreator<WindowContext>) -> Self {
Self::with_capacity(creator, DEFAULT_CAPACITY)
}
#[allow(dead_code)]
pub fn with_capacity(creator: TextureCreator<WindowContext>, capacity: usize) -> Self {
let cache = LruCache::builder()
.max_size(MaxSize::Count(capacity.max(1)))
.name("sdl_gpu/label")
.build();
Self {
cache: WithFactory::new(cache, rasterize_label as LabelCtor),
creator,
raster_buf: Vec::new(),
}
}
#[allow(clippy::too_many_arguments)]
pub fn draw(
&mut self,
canvas: &mut SdlCanvas<Window>,
pos: &Point,
text: &[u8],
clip: &Rect,
color: &Color,
opa: u8,
scale: Fixed,
) {
if text.is_empty() {
return;
}
let key = LabelKey {
text_hash: hash_bytes(text),
color_rgba: pack_rgba(color),
};
let phys_char_w = (Fixed::from_int(CHAR_W as i32) * scale).to_int().max(1) as u32;
let phys_char_h = (Fixed::from_int(CHAR_H as i32) * scale).to_int().max(1) as u32;
let phys_w = phys_char_w * text.len() as u32;
let phys_h = phys_char_h;
let dst = sdl2::rect::Rect::new(pos.x.to_int(), pos.y.to_int(), phys_w, phys_h);
let (cx0, cy0, cx1, cy1) = clip.pixel_bounds();
let clip_rect = if cx1 > cx0 && cy1 > cy0 {
Some(sdl2::rect::Rect::new(
cx0,
cy0,
(cx1 - cx0) as u32,
(cy1 - cy0) as u32,
))
} else {
None
};
let a = ((color.a as u16) * (opa as u16) / 255) as u8;
let creator = &self.creator;
let raster_buf = &mut self.raster_buf;
let Ok(handle) = self.cache.entry(key).or_insert_with(|ctor, k| {
let ctx = RasterCtx {
text,
color,
creator,
raster_buf,
};
ctor(k, ctx)
}) else {
return;
};
let mut tex = handle.borrow_mut();
tex.set_alpha_mod(a);
if let Some(sdl_clip) = clip_rect {
canvas.set_clip_rect(sdl_clip);
} else {
canvas.set_clip_rect(None);
}
let _ = canvas.copy(&tex, None, Some(dst));
canvas.set_clip_rect(None);
}
#[allow(dead_code)]
pub fn clear(&mut self) {
self.cache.cache_mut().clear();
}
pub(super) fn as_inspect(&self) -> &dyn crate::cache::CacheInspect {
self.cache.cache()
}
pub fn with_creator<R>(&self, f: impl FnOnce(&TextureCreator<WindowContext>) -> R) -> R {
f(&self.creator)
}
}
fn rasterize_label(_key: &LabelKey, ctx: RasterCtx<'_>) -> Result<CachedTexture, ()> {
let logical_w = CHAR_W as usize * ctx.text.len();
let logical_h = CHAR_H as usize;
let byte_stride = logical_w * 4;
let byte_len = byte_stride * logical_h;
ctx.raster_buf.clear();
ctx.raster_buf.resize(byte_len, 0);
{
let tex = MiruiTexture::new(
ctx.raster_buf,
logical_w as u16,
logical_h as u16,
ColorFormat::RGBA8888,
);
let mut sw = SwRenderer::new(tex);
let area = Rect::new(0, 0, logical_w as u16, logical_h as u16);
sw.draw_label(
&Point {
x: Fixed::ZERO,
y: Fixed::ZERO,
},
ctx.text,
&area,
ctx.color,
255,
);
}
let mut new_tex = ctx
.creator
.create_texture_streaming(PixelFormatEnum::RGBA32, logical_w as u32, logical_h as u32)
.map_err(|_| ())?;
new_tex
.update(None, ctx.raster_buf, byte_stride)
.map_err(|_| ())?;
new_tex.set_blend_mode(sdl2::render::BlendMode::Blend);
let new_tex_static: SdlTexture<'static> = unsafe { core::mem::transmute(new_tex) };
Ok(RefCell::new(new_tex_static))
}
fn hash_bytes(bytes: &[u8]) -> u64 {
let mut h = FxHasher::default();
bytes.hash(&mut h);
h.finish()
}
fn pack_rgba(c: &Color) -> u32 {
((c.r as u32) << 24) | ((c.g as u32) << 16) | ((c.b as u32) << 8) | (c.a as u32)
}
#[derive(Default)]
struct FxHasher(u64);
impl Hasher for FxHasher {
fn write(&mut self, bytes: &[u8]) {
for &b in bytes {
self.0 = self.0.rotate_left(5) ^ b as u64 ^ 0x27220a95;
}
}
fn finish(&self) -> u64 {
self.0
}
}