software renderer: support aligned dirty regions - #12656
Conversation
Some display controllers cannot program arbitrary GRAM address windows. The CO5300, SH8601A, RM67162, and RM690B0 require both the start coordinate and the window extent to be divisible by two on each axis. Packed display transports can impose other granularities, such as an eight-pixel horizontal grid for byte-addressed e-paper. A platform adapter cannot safely align only the PhysicalRegion returned after rendering. Pixels added by that rounding are not guaranteed to have been repainted and may contain stale framebuffer contents, particularly with reused or swapped buffers. The renderer must expand the damage before drawing and return the matching physical region. Add DirtyRegionAlignment to configure a physical per-axis grid. Expand logical damage before partial rendering, snap the resulting physical region to that grid, and apply the alignment in panel coordinates so that rendering rotation is handled correctly. Expose the setting through the Rust and C++ software-renderer APIs, while retaining 1x1 as the default. Rewriting the region conversion also fixes PhysicalRegion::count including rectangles that were entirely clipped out by the screen bounds; such regions previously reported empty boxes. Add coverage for region snapping and rotated panel axes, plus a regression test that verifies the expanded pixels in line-buffer, reused-buffer, and two-buffer swapped-buffer rendering.
|
I wonder if this should be a function of Cool that you're tracking this down, btw :) |
I'm not sure, what about render_by_line? It's needed by many MCUs |
ogoffart
left a comment
There was a problem hiding this comment.
Thanks for this PR
Thisis indeed something that's needed. especially for monochrome screen that byte 8 pixels into one byte.
And this is indeed very useful with the line by line renderer as well.
An alternative API could be to add an extension point to the traits TargetPixelBuffer and LineBufferProvider
For example
trait TargetPixelBuffer /*or LineBufferProvider*/ {
// ... existing stuff ...
fn adjust_dirty_region(&self, region: PhysicalRegion) -> PhysicalRegion {
region
}
}But maybe that's overengineering it?
| count: renderer.dirty_region.iter().count(), | ||
| }; | ||
| drop(i); | ||
| let alignment = software_renderer.dirty_region_alignment.get(); |
There was a problem hiding this comment.
pre-existing: I wonder if the duplicated code between render and prepare_scene could be simplified.
Take slint and slint-build from the branch behind slint-ui/slint#12656 rather than a sibling checkout, so the project can be cloned and built on its own. Cargo.lock pins the exact commit; both move back to crates.io once the dirty-region alignment API lands in a release. Drop the CI step that cloned the sibling repository and rewrite the README build section. Track whether a touch is outstanding and, while one is, race the CST9220 interrupt against a 250 ms timeout. A dropped release edge would otherwise latch the UI in a pressed state forever. On timeout the controller is re-read rather than trusted: after a full timeout of silence INT is idle, so the read cannot race a report, and a finger that is still down keeps the press alive regardless of whether the controller repeats reports for a stationary touch. Send full-stride rectangles as transfers as large as the DMA buffer allows instead of one per row. Their rows are contiguous in the framebuffer, so a full-frame update needs 60 transfers rather than 480. SpiDmaBus::half_duplex_write rejects anything larger than the buffer it was built with, so the chunk size is exported and the DMA buffers are sized from it. Log and request a redraw when a region upload fails instead of panicking. The framebuffer still holds the frame the panel never received, so the damage has to be re-sent rather than dropped. Lean on the guarantees Slint already provides for the region it returns: rectangles are clipped to the window, non-empty, and snapped to the requested alignment. Keep the panel invariant as a debug assertion, which still catches the one case Slint cannot, the panel dimensions drifting apart from the window size. Reuse the time crate for month lengths, and record the board pin assignments as documentation since esp-hal addresses pins by type and the constants could never drive the wiring.
Some display controllers cannot program arbitrary GRAM address windows: the CO5300, SH8601A, RM67162, and RM690B0 require both the start coordinate and the extent of the address window to be even on each axis, and packed display transports can impose other granularities (e.g. an eight-pixel horizontal grid for byte-addressed e-paper). A platform adapter cannot fix this up by rounding the
PhysicalRegionreturned after rendering — pixels added by that rounding are not guaranteed to have been repainted and may contain stale framebuffer contents, particularly with reused or swapped buffers. The renderer itself has to expand the damage before drawing and return a matching physical region. This PR adds an opt-in per-axis alignment grid to the software renderer; the default of 1×1 keeps the existing behavior. Screen dimensions must be multiples of the configured alignment (checked with adebug_assert).DirtyRegionAlignmentandSoftwareRenderer::set_dirty_region_alignment()/dirty_region_alignment()to the Rust APIrender()andrender_by_line()pathsRenderingRotation, so rotated screens align on the panel's native axesSoftwareRenderer::set_dirty_region_alignment(horizontal, vertical)PhysicalRegion::countincluding rectangles that were entirely clipped out by the screen bounds — such regions previously reported empty boxes; this fix applies with the default alignment tooVerified on physical hardware with a CO5300-driven Waveshare ESP32-S3-Touch-AMOLED-2.16 (480×480 AMOLED, QSPI, RGB565 partial uploads from a PSRAM framebuffer) using a 2×2 alignment: https://github.com/okhsunrog/waveshare-esp32s3-amoled-2-16. Both the full-frame and aligned partial-frame upload paths render correctly on the panel.