use core::marker::PhantomData;
use crate::blit::PixelFmt;
use crate::hwcore::addr::{DmaAddr, PhysAddr};
#[derive(Debug)]
pub struct FrameBuffer {
addr: PhysAddr,
width: u32,
height: u32,
stride_bytes: u32,
format: PixelFmt,
bank: u8,
}
const BANK_OUT_OF_RANGE: u8 = u8::MAX;
impl FrameBuffer {
pub unsafe fn from_phys(
addr: PhysAddr,
width: u32,
height: u32,
stride_bytes: u32,
format: PixelFmt,
) -> Self {
let bank = addr.sdram_bank().unwrap_or(BANK_OUT_OF_RANGE);
Self {
addr,
width,
height,
stride_bytes,
format,
bank,
}
}
#[inline]
pub fn addr(&self) -> PhysAddr {
self.addr
}
pub fn dma_addr(&self) -> DmaAddr {
let align = pixel_size(self.format);
DmaAddr::from_phys(self.addr, align)
.or_else(|_| DmaAddr::from_phys(self.addr, 1))
.expect("byte-aligned DmaAddr always valid for a non-zero alignment")
}
#[inline]
pub fn width(&self) -> u32 {
self.width
}
#[inline]
pub fn height(&self) -> u32 {
self.height
}
#[inline]
pub fn stride_bytes(&self) -> u32 {
self.stride_bytes
}
#[inline]
pub fn format(&self) -> PixelFmt {
self.format
}
#[inline]
pub fn bank(&self) -> Option<u8> {
if self.bank == BANK_OUT_OF_RANGE {
None
} else {
Some(self.bank)
}
}
#[inline]
pub fn byte_len(&self) -> usize {
self.stride_bytes as usize * self.height as usize
}
}
#[inline]
pub const fn pixel_size(fmt: PixelFmt) -> usize {
match fmt {
PixelFmt::Argb8888 => 4,
PixelFmt::Rgb565 => 2,
PixelFmt::L8 | PixelFmt::A8 | PixelFmt::A4 => 1,
}
}
pub struct FrontBuffer<'a> {
fb: &'a FrameBuffer,
}
impl<'a> FrontBuffer<'a> {
#[inline]
pub fn wrap(fb: &'a FrameBuffer) -> Self {
Self { fb }
}
#[inline]
pub fn framebuffer(&self) -> &FrameBuffer {
self.fb
}
#[inline]
pub fn addr(&self) -> PhysAddr {
self.fb.addr()
}
#[inline]
pub fn dma_addr(&self) -> DmaAddr {
self.fb.dma_addr()
}
#[inline]
pub fn width(&self) -> u32 {
self.fb.width()
}
#[inline]
pub fn height(&self) -> u32 {
self.fb.height()
}
#[inline]
pub fn stride_bytes(&self) -> u32 {
self.fb.stride_bytes()
}
#[inline]
pub fn format(&self) -> PixelFmt {
self.fb.format()
}
}
pub struct BackBuffer<'a> {
fb: &'a mut FrameBuffer,
}
impl<'a> BackBuffer<'a> {
#[inline]
pub fn wrap(fb: &'a mut FrameBuffer) -> Self {
Self { fb }
}
#[inline]
pub fn framebuffer(&self) -> &FrameBuffer {
self.fb
}
#[inline]
pub fn framebuffer_mut(&mut self) -> &mut FrameBuffer {
self.fb
}
#[inline]
pub fn addr(&self) -> PhysAddr {
self.fb.addr()
}
#[inline]
pub fn dma_addr(&self) -> DmaAddr {
self.fb.dma_addr()
}
#[inline]
pub fn width(&self) -> u32 {
self.fb.width()
}
#[inline]
pub fn height(&self) -> u32 {
self.fb.height()
}
#[inline]
pub fn stride_bytes(&self) -> u32 {
self.fb.stride_bytes()
}
#[inline]
pub fn format(&self) -> PixelFmt {
self.fb.format()
}
#[inline]
pub fn dma_dst<'b>(&'b mut self) -> BorrowedForDma<'b, BackBuffer<'a>> {
BorrowedForDma {
inner: self,
_phantom: PhantomData,
}
}
pub unsafe fn cpu_slice(&mut self) -> &mut [u8] {
let len = self.fb.byte_len();
unsafe { self.fb.addr.as_mut_slice(len) }
}
}
pub struct BorrowedForDma<'a, T: ?Sized + 'a> {
inner: &'a mut T,
_phantom: PhantomData<&'a mut T>,
}
impl<'a, 'fb> BorrowedForDma<'a, BackBuffer<'fb>> {
#[inline]
pub fn dma_addr(&self) -> DmaAddr {
self.inner.dma_addr()
}
#[inline]
pub fn width(&self) -> u32 {
self.inner.width()
}
#[inline]
pub fn height(&self) -> u32 {
self.inner.height()
}
#[inline]
pub fn stride_bytes(&self) -> u32 {
self.inner.stride_bytes()
}
#[inline]
pub fn format(&self) -> PixelFmt {
self.inner.format()
}
}
pub struct InFlight<'dma, T: ?Sized + 'dma> {
borrow: BorrowedForDma<'dma, T>,
}
impl<'dma, T: ?Sized + 'dma> InFlight<'dma, T> {
#[inline]
pub fn new(borrow: BorrowedForDma<'dma, T>) -> Self {
Self { borrow }
}
#[inline]
pub fn into_borrow(self) -> BorrowedForDma<'dma, T> {
self.borrow
}
}
#[derive(Debug)]
pub struct Scanout {
front: FrameBuffer,
back: FrameBuffer,
}
#[derive(Debug)]
pub struct BankCollision {
pub front: FrameBuffer,
pub back: FrameBuffer,
pub bank: u8,
}
impl Scanout {
pub fn try_new(front: FrameBuffer, back: FrameBuffer) -> Result<Self, BankCollision> {
if let (Some(fb), Some(bb)) = (front.bank(), back.bank())
&& fb == bb
{
return Err(BankCollision {
front,
back,
bank: fb,
});
}
Ok(Self { front, back })
}
#[inline]
pub fn front(&self) -> FrontBuffer<'_> {
FrontBuffer::wrap(&self.front)
}
#[inline]
pub fn back_mut(&mut self) -> BackBuffer<'_> {
BackBuffer::wrap(&mut self.back)
}
#[inline]
pub fn swap(&mut self) {
core::mem::swap(&mut self.front, &mut self.back);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hwcore::addr::{SDRAM_BANK_STRIDE, SDRAM_BANK2_BASE};
const W: u32 = 480;
const H: u32 = 272;
const STRIDE: u32 = W * 4;
fn mk(addr: u32) -> FrameBuffer {
unsafe { FrameBuffer::from_phys(PhysAddr::new(addr), W, H, STRIDE, PixelFmt::Argb8888) }
}
#[test]
fn bank_disjoint_framebuffers_pair_successfully() {
let front = mk(SDRAM_BANK2_BASE);
let back = mk(SDRAM_BANK2_BASE + SDRAM_BANK_STRIDE);
let sc = Scanout::try_new(front, back).expect("disjoint banks");
assert_eq!(sc.front().width(), W);
}
#[test]
fn same_bank_framebuffers_are_rejected() {
let front = mk(SDRAM_BANK2_BASE);
let back = mk(SDRAM_BANK2_BASE + 0x100); let err = Scanout::try_new(front, back).unwrap_err();
assert_eq!(err.bank, 0);
}
#[test]
fn frontbuffer_reports_geometry() {
let fb = mk(SDRAM_BANK2_BASE);
let front = FrontBuffer::wrap(&fb);
assert_eq!(front.width(), W);
assert_eq!(front.height(), H);
assert_eq!(front.stride_bytes(), STRIDE);
assert_eq!(front.format(), PixelFmt::Argb8888);
}
#[test]
fn backbuffer_dma_dst_reborrow_is_well_formed() {
let mut fb = mk(SDRAM_BANK2_BASE);
let mut back = BackBuffer::wrap(&mut fb);
{
let dst = back.dma_dst();
assert_eq!(dst.width(), W);
assert_eq!(dst.stride_bytes(), STRIDE);
}
let _dst2 = back.dma_dst();
}
#[test]
fn scanout_swap_exchanges_buffers() {
let mut sc = Scanout::try_new(
mk(SDRAM_BANK2_BASE),
mk(SDRAM_BANK2_BASE + SDRAM_BANK_STRIDE),
)
.unwrap();
let before = sc.front().addr();
sc.swap();
let after = sc.front().addr();
assert_ne!(before, after);
}
#[test]
fn inflight_round_trips_borrow() {
let mut fb = mk(SDRAM_BANK2_BASE);
let mut back = BackBuffer::wrap(&mut fb);
let dst = back.dma_dst();
let inflight = InFlight::new(dst);
let released = inflight.into_borrow();
assert_eq!(released.width(), W);
}
#[test]
fn pixel_size_matches_format() {
assert_eq!(pixel_size(PixelFmt::Argb8888), 4);
assert_eq!(pixel_size(PixelFmt::Rgb565), 2);
assert_eq!(pixel_size(PixelFmt::L8), 1);
}
}