pm-kit: small-tile incremental updates (sub-rect blit) to cut tearing
Validated against GeeekPi's lv_port_disp.c (vendor LVGL driver for this kit): it flushes per-dirty-rectangle windowed writes at 62.5MHz, no TE — exactly this approach. Fix blit_rect (1024-byte buffer; the 512-byte one was the black-screen bug), switch incremental updates from full-width 144-row bands to changed 40x40 tiles. Full repaints still use the proven full blit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
eef535f9ef
commit
1eca3ee0fe
1 changed files with 73 additions and 38 deletions
|
|
@ -38,6 +38,12 @@ const XTAL_FREQ_HZ: u32 = 12_000_000;
|
|||
const WIDTH: u16 = 320;
|
||||
const HEIGHT: u16 = 480;
|
||||
|
||||
/// Dirty-tile grid for incremental updates. TILE wide is sub-screen-width so each tile blit writes
|
||||
/// faster than the panel scan crosses it → minimal tearing. 320/40=8 cols, 480/40=12 rows = 96 tiles.
|
||||
const TILE: u16 = 40;
|
||||
const NX: usize = WIDTH as usize / TILE as usize;
|
||||
const NY: usize = HEIGHT as usize / TILE as usize;
|
||||
|
||||
/// Direct ST7796 driver — a faithful port of the proven MicroPython driver in `pico/main.py`,
|
||||
/// which runs this exact panel on this exact board. Per-command CS framing (CS low → command →
|
||||
/// data → CS high), MADCTL 0x48, INVON, NO row/column offset, big-endian RGB565. This replaces
|
||||
|
|
@ -138,6 +144,40 @@ where
|
|||
fn blit(&mut self, px: &[Rgb565]) {
|
||||
self.blit_band(px, 0, HEIGHT);
|
||||
}
|
||||
|
||||
/// Blit a sub-rectangle `(x,y,w,h)` gathered from the full framebuffer. Used for small tile
|
||||
/// updates: sub-width writes finish a region faster than the panel scan crosses it (datasheet
|
||||
/// §10.8 Example 1), so the visible tear is tiny vs a full-width band. Same streaming loop and
|
||||
/// 1024-byte buffer as `blit_band` (the 512-byte buffer was what broke the earlier version).
|
||||
fn blit_rect(&mut self, fb: &[Rgb565], x: u16, y: u16, w: u16, h: u16) {
|
||||
let x1 = x + w - 1;
|
||||
let y1 = y + h - 1;
|
||||
self.cmd(0x2A, &[(x >> 8) as u8, (x & 0xFF) as u8, (x1 >> 8) as u8, (x1 & 0xFF) as u8]);
|
||||
self.cmd(0x2B, &[(y >> 8) as u8, (y & 0xFF) as u8, (y1 >> 8) as u8, (y1 & 0xFF) as u8]);
|
||||
let _ = self.cs.set_low();
|
||||
let _ = self.dc.set_low();
|
||||
let _ = self.spi.write(&[0x2C]); // RAMWR
|
||||
let _ = self.dc.set_high();
|
||||
let mut buf = [0u8; 1024];
|
||||
let mut n = 0;
|
||||
for r in 0..h as usize {
|
||||
let row = (y as usize + r) * WIDTH as usize + x as usize;
|
||||
for c in 0..w as usize {
|
||||
let raw = fb[row + c].into_storage();
|
||||
buf[n] = (raw >> 8) as u8;
|
||||
buf[n + 1] = (raw & 0xFF) as u8;
|
||||
n += 2;
|
||||
if n == buf.len() {
|
||||
let _ = self.spi.write(&buf);
|
||||
n = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if n > 0 {
|
||||
let _ = self.spi.write(&buf[..n]);
|
||||
}
|
||||
let _ = self.cs.set_high();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -275,7 +315,7 @@ fn main() -> ! {
|
|||
// changed vs the last frame. Full-width windows (CASET 0..319) behave exactly like the proven
|
||||
// full-screen blit — unlike sub-width tiles, which the panel's MX bit mirror-maps wrongly.
|
||||
let mut fb = FrameBuf { px: unsafe { &mut *core::ptr::addr_of_mut!(FRAMEBUF) } };
|
||||
let mut row_hash = [0u64; HEIGHT as usize]; // FNV-1a of each row at its last blit (0 → force first paint)
|
||||
let mut tile_hash = [0u64; NX * NY]; // FNV-1a of each tile at its last blit (0 → force first paint)
|
||||
|
||||
let mut bar_start = timer.get_counter().ticks();
|
||||
let mut last_step = usize::MAX; // audio: click on a new step
|
||||
|
|
@ -392,51 +432,46 @@ fn main() -> ! {
|
|||
} else {
|
||||
pm_ui::draw_metronome(&mut fb, &screen).ok();
|
||||
}
|
||||
#[inline(always)]
|
||||
fn row_fnv(row: &[Rgb565]) -> u64 {
|
||||
// FNV-1a of one TILE×TILE block at grid cell (tx, ty)
|
||||
fn tile_fnv(px: &[Rgb565], tx: usize, ty: usize) -> u64 {
|
||||
let mut h = 0xcbf29ce484222325u64; // FNV-1a offset basis
|
||||
for &p in row {
|
||||
h ^= p.into_storage() as u64;
|
||||
let x0 = tx * TILE as usize;
|
||||
let y0 = ty * TILE as usize;
|
||||
for r in 0..TILE as usize {
|
||||
let base = (y0 + r) * WIDTH as usize + x0;
|
||||
for c in 0..TILE as usize {
|
||||
h ^= px[base + c].into_storage() as u64;
|
||||
h = h.wrapping_mul(0x100000001b3);
|
||||
}
|
||||
}
|
||||
h
|
||||
}
|
||||
let w = WIDTH as usize;
|
||||
if force_full {
|
||||
// known-good full-screen blit; seed the row hashes so increments diff against it
|
||||
// known-good full-screen blit; seed the tile hashes so increments diff against it
|
||||
st.blit(&fb.px[..]);
|
||||
for yy in 0..HEIGHT as usize {
|
||||
row_hash[yy] = row_fnv(&fb.px[yy * w..yy * w + w]);
|
||||
for ty in 0..NY {
|
||||
for tx in 0..NX {
|
||||
tile_hash[ty * NX + tx] = tile_fnv(&fb.px[..], tx, ty);
|
||||
}
|
||||
}
|
||||
force_full = false;
|
||||
info!("FULL blit (seeded {} rows)", HEIGHT as u32);
|
||||
info!("FULL blit (seeded {} tiles)", (NX * NY) as u32);
|
||||
} else {
|
||||
// ---- dirty row-band blit: contiguous runs of changed rows, full-width ----
|
||||
let mut pushed_rows = 0u32;
|
||||
let mut y = 0usize;
|
||||
while y < HEIGHT as usize {
|
||||
let h = row_fnv(&fb.px[y * w..y * w + w]);
|
||||
if h != row_hash[y] {
|
||||
let ylo = y;
|
||||
row_hash[y] = h;
|
||||
y += 1;
|
||||
while y < HEIGHT as usize {
|
||||
let hh = row_fnv(&fb.px[y * w..y * w + w]);
|
||||
if hh == row_hash[y] {
|
||||
break; // an unchanged row ends the dirty run
|
||||
}
|
||||
row_hash[y] = hh;
|
||||
y += 1;
|
||||
}
|
||||
let band = (y - ylo) as u16;
|
||||
st.blit_band(&fb.px[ylo * w..y * w], ylo as u16, band);
|
||||
pushed_rows += band as u32;
|
||||
} else {
|
||||
y += 1;
|
||||
// ---- dirty-tile blit: push only the changed TILE×TILE blocks (sub-width = less tearing) ----
|
||||
let mut pushed = 0u32;
|
||||
for ty in 0..NY {
|
||||
for tx in 0..NX {
|
||||
let h = tile_fnv(&fb.px[..], tx, ty);
|
||||
let idx = ty * NX + tx;
|
||||
if h != tile_hash[idx] {
|
||||
tile_hash[idx] = h;
|
||||
st.blit_rect(&fb.px[..], (tx * TILE as usize) as u16, (ty * TILE as usize) as u16, TILE, TILE);
|
||||
pushed += 1;
|
||||
}
|
||||
}
|
||||
if pushed_rows > 0 {
|
||||
info!("blit {} / {} rows (bands)", pushed_rows, HEIGHT as u32);
|
||||
}
|
||||
if pushed > 0 {
|
||||
info!("blit {} / {} tiles", pushed, (NX * NY) as u32);
|
||||
}
|
||||
}
|
||||
drawn_step = cur_step;
|
||||
|
|
|
|||
Loading…
Reference in a new issue