Skip to content

software renderer: support aligned dirty regions - #12656

Open
okhsunrog wants to merge 4 commits into
slint-ui:masterfrom
okhsunrog:dirty-region-alignment
Open

software renderer: support aligned dirty regions#12656
okhsunrog wants to merge 4 commits into
slint-ui:masterfrom
okhsunrog:dirty-region-alignment

Conversation

@okhsunrog

@okhsunrog okhsunrog commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

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 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 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 a debug_assert).

  • Add DirtyRegionAlignment and SoftwareRenderer::set_dirty_region_alignment() / dirty_region_alignment() to the Rust API
  • Expand the logical dirty region before partial rendering so the pixels added by alignment are actually repainted, in both the render() and render_by_line() paths
  • Snap the resulting physical region to the grid in panel coordinates, i.e. after RenderingRotation, so rotated screens align on the panel's native axes
  • Expose the setting in the C++ platform API as SoftwareRenderer::set_dirty_region_alignment(horizontal, vertical)
  • Fix PhysicalRegion::count including rectangles that were entirely clipped out by the screen bounds — such regions previously reported empty boxes; this fix applies with the default alignment too
  • Add unit tests for grid snapping, rotated panel axes, and the clipped-rectangle count fix, plus a regression test that renders with garbage-initialized buffers and verifies the expanded pixels in line-buffer, reused-buffer, and swapped-buffers modes

Verified 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.

okhsunrog and others added 4 commits July 27, 2026 12:19
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.
@okhsunrog

Copy link
Copy Markdown
Contributor Author

@tronical @ogoffart what do you think about this one? I can change the API if you have other preferences

@tronical

Copy link
Copy Markdown
Member

I wonder if this should be a function of TargetPixelBuffer (experimental right now). Somehow this doesn't feel like a renderer specific attribute but more of the target buffer we're rendering to (as used by the scanout that has these requirements).

Cool that you're tracking this down, btw :)

@okhsunrog

Copy link
Copy Markdown
Contributor Author

I wonder if this should be a function of TargetPixelBuffer (experimental right now). Somehow this doesn't feel like a renderer specific attribute but more of the target buffer we're rendering to (as used by the scanout that has these requirements).

I'm not sure, what about render_by_line? It's needed by many MCUs

@ogoffart ogoffart left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pre-existing: I wonder if the duplicated code between render and prepare_scene could be simplified.

okhsunrog added a commit to okhsunrog/waveshare-esp32s3-amoled-2-16 that referenced this pull request Jul 28, 2026
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants