ColorUtil::read_packed<BITNESS>() lives next to to_color() so drivers
with their own blit loop (it8951 carries a copy of the same switch)
can pick it up; the comments say where the folding happens.
The per pixel fallback of draw_pixels_at() scaled every channel through
esp_scale() with a divisor derived from the runtime bitness, three real
divisions per pixel, and switched on the bitness per pixel as well. The
loop is now instantiated per bitness, so the divisor is a constant the
compiler turns into a multiply and shift with identical results, and
the 888 case scales by nothing. Same pixels drawn.
A recording display runs the row and column walks over a source with
nonzero offsets and padding for 332, 565 and 888 in both byte orders
and requires the same pixel set, plus a check that the orders differ.
With two call sites GCC at -Os stopped inlining to_color(), which cost
a call and spills per pixel; flatten keeps it inlined, and the row loop
now measures faster than before the change.
The per pixel fallback always walked rows, so on a display that swaps
x and y before writing every frame buffer write landed a row apart. It
now asks pixel_axes_swapped() once and walks columns in that case, the
same rule Image::draw() uses; the column walk is only built with PSRAM.
The source pixel decode moves into a helper shared by both loops.
The benchmark now draws once at each rotation and requires the rotated
frame to be the transpose of the plain one before timing, so a wrong
traversal fails the run instead of passing silently.
Without PSRAM the frame buffer is in uncached RAM where a strided write
costs nothing, so the second copy of the loops bought only flash.
Adds the transparent binary and grayscale paths to the host benchmark.
for_each_pixel() holds the loop order once; the per format bodies are
always-inline lambdas, which keeps the inner loop identical to dev
(GCC at -Os otherwise leaves them out of line and calls per pixel).
Members read in the loop are copied to locals so the opaque virtual
draw_pixel_at() does not force a reload per pixel. The hub75 override
goes, its rotation is forced to 0 at codegen. The host benchmark's
frame is square so the rotated case stays in bounds.
Rotation alone is the wrong signal: mipi_spi with hardware rotation,
epaper_spi and it8951 with a swap_xy transform, and hub75 write the
frame buffer differently from what the rotation implies. The display
now answers through pixel_axes_swapped() and Image::draw() picks the
loop order from that. The four per-format loops collapse into one body
with the format resolved at compile time.
A rotated display swaps x and y before the frame buffer write, so the
row walk strides the frame buffer there and a strided write costs more
than a strided read. The loop order is now a compile time template
picked from the display rotation, so both orders keep the plain loop
shape. Adds a rotated case to the host benchmark.
Own the frame buffer so nothing leaks per trial, size the pixel data
once for the widest format, and drop the manifest override the build
does not need.
Image::draw() iterated columns in the outer loop over a row-major
pixel buffer, so every pixel read and every frame buffer write jumped
by a full row. Walking rows keeps both accesses sequential regardless
of image shape or display rotation. The output is unchanged.
Adds a host benchmark for the four pixel formats plus the alpha path.