Skip to content

Commit 1aff0d2

Browse files
Optimize drawing performance
1 parent f64c1ba commit 1aff0d2

File tree

4 files changed

+83
-56
lines changed

4 files changed

+83
-56
lines changed

components/hub75/src/color/color_convert.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,11 @@ HUB75_CONST HUB75_IRAM inline constexpr uint8_t scale_6bit_to_8bit(uint8_t val6)
6161
* @param g8 Output: 8-bit green component (0-255)
6262
* @param b8 Output: 8-bit blue component (0-255)
6363
*/
64-
HUB75_IRAM inline void extract_rgb888_from_format(const uint8_t *buffer, size_t pixel_idx, Hub75PixelFormat format,
65-
Hub75ColorOrder color_order, bool big_endian, uint8_t &r8,
66-
uint8_t &g8, uint8_t &b8) {
64+
__attribute__((always_inline)) static inline void extract_rgb888_from_format(const uint8_t *buffer, size_t pixel_idx,
65+
Hub75PixelFormat format,
66+
Hub75ColorOrder color_order,
67+
bool big_endian, uint8_t &r8, uint8_t &g8,
68+
uint8_t &b8) {
6769
switch (format) {
6870
case Hub75PixelFormat::RGB565: {
6971
// 16-bit RGB565

components/hub75/src/panels/rotation.h

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ class RotationTransform {
2525
// @param phys_w Physical (unrotated) display width
2626
// @param phys_h Physical (unrotated) display height
2727
// @return Coordinates in unrotated (physical) space
28-
static HUB75_CONST HUB75_IRAM constexpr Coords apply(Coords c, Hub75Rotation rotation, uint16_t phys_w,
29-
uint16_t phys_h) {
28+
__attribute__((always_inline)) HUB75_CONST static constexpr Coords apply(Coords c, Hub75Rotation rotation,
29+
uint16_t phys_w, uint16_t phys_h) {
3030
switch (rotation) {
3131
case Hub75Rotation::ROTATE_0:
3232
return c;
@@ -51,7 +51,7 @@ class RotationTransform {
5151
// Check if rotation swaps width and height (90° or 270°)
5252
// @param rotation Rotation angle
5353
// @return true if dimensions are swapped
54-
static HUB75_CONST constexpr bool swaps_dimensions(Hub75Rotation rotation) {
54+
__attribute__((always_inline)) HUB75_CONST static constexpr bool swaps_dimensions(Hub75Rotation rotation) {
5555
return rotation == Hub75Rotation::ROTATE_90 || rotation == Hub75Rotation::ROTATE_270;
5656
}
5757

@@ -60,7 +60,9 @@ class RotationTransform {
6060
// @param phys_h Physical (unrotated) height
6161
// @param rotation Current rotation
6262
// @return User-facing width after rotation
63-
static HUB75_CONST constexpr uint16_t get_rotated_width(uint16_t phys_w, uint16_t phys_h, Hub75Rotation rotation) {
63+
__attribute__((always_inline)) HUB75_CONST static constexpr uint16_t get_rotated_width(uint16_t phys_w,
64+
uint16_t phys_h,
65+
Hub75Rotation rotation) {
6466
return swaps_dimensions(rotation) ? phys_h : phys_w;
6567
}
6668

@@ -69,7 +71,9 @@ class RotationTransform {
6971
// @param phys_h Physical (unrotated) height
7072
// @param rotation Current rotation
7173
// @return User-facing height after rotation
72-
static HUB75_CONST constexpr uint16_t get_rotated_height(uint16_t phys_w, uint16_t phys_h, Hub75Rotation rotation) {
74+
__attribute__((always_inline)) HUB75_CONST static constexpr uint16_t get_rotated_height(uint16_t phys_w,
75+
uint16_t phys_h,
76+
Hub75Rotation rotation) {
7377
return swaps_dimensions(rotation) ? phys_w : phys_h;
7478
}
7579
};

components/hub75/src/platforms/i2s/i2s_dma.cpp

Lines changed: 64 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ constexpr uint16_t RGB_MASK = RGB_UPPER_MASK | RGB_LOWER_MASK; // 0x003F
7171
// Bit clear masks
7272
constexpr uint16_t OE_CLEAR_MASK = ~(1 << OE_BIT);
7373

74+
// Pre-computed bit masks for BCM bit planes (avoids shift per iteration)
75+
static constexpr uint16_t BCM_BIT_MASKS[12] = {0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020,
76+
0x0040, 0x0080, 0x0100, 0x0200, 0x0400, 0x0800};
77+
7478
// ESP32 I2S TX FIFO position adjustment
7579
// In 16-bit parallel mode with tx_fifo_mod=1, the FIFO outputs 16-bit words in swapped pairs.
7680
// The FIFO reads 32-bit words from memory and outputs them as two 16-bit chunks in reversed order.
@@ -902,61 +906,80 @@ HUB75_IRAM void I2sDma::draw_pixels(uint16_t x, uint16_t y, uint16_t w, uint16_t
902906
h = rotated_height - y;
903907
}
904908

905-
// Process each pixel based on format
909+
// Pre-compute pixel stride for pointer arithmetic (avoids multiply per pixel)
910+
const size_t pixel_stride = (format == Hub75PixelFormat::RGB888) ? 3
911+
: (format == Hub75PixelFormat::RGB565) ? 2
912+
: /* RGB888_32 */ 4;
913+
914+
// Check if we can use identity fast path (no coordinate transforms needed)
915+
const bool identity_transform = (rotation_ == Hub75Rotation::ROTATE_0) && !needs_layout_remap_ && !needs_scan_remap_;
916+
917+
// Pre-compute bit plane stride (bytes between bit planes)
918+
const size_t bit_plane_stride = dma_width_ * 2;
919+
920+
// Process each pixel
921+
const uint8_t *pixel_ptr = buffer;
906922
for (uint16_t dy = 0; dy < h; dy++) {
907923
for (uint16_t dx = 0; dx < w; dx++) {
908924
uint16_t px = x + dx;
909925
uint16_t py = y + dy;
926+
uint16_t row;
927+
bool is_lower;
928+
929+
// Fast path: identity transform (no rotation, standard layout, standard scan)
930+
if (identity_transform) {
931+
// Simple row/half calculation without modulo (subtraction is cheaper)
932+
if (py < num_rows_) {
933+
row = py;
934+
is_lower = false;
935+
} else {
936+
row = py - num_rows_;
937+
is_lower = true;
938+
}
939+
px = fifo_adjust_x(px);
940+
} else {
941+
// Full coordinate transformation pipeline
942+
auto transformed = transform_coordinate(px, py, rotation_, needs_layout_remap_, needs_scan_remap_, layout_,
943+
scan_wiring_, panel_width_, panel_height_, layout_rows_, layout_cols_,
944+
virtual_width_, virtual_height_, dma_width_, num_rows_);
945+
px = fifo_adjust_x(transformed.x);
946+
row = transformed.row;
947+
is_lower = transformed.is_lower;
948+
}
910949

911-
// Coordinate transformation pipeline (rotation + layout + scan remapping)
912-
auto transformed = transform_coordinate(px, py, rotation_, needs_layout_remap_, needs_scan_remap_, layout_,
913-
scan_wiring_, panel_width_, panel_height_, layout_rows_, layout_cols_,
914-
virtual_width_, virtual_height_, dma_width_, num_rows_);
915-
px = fifo_adjust_x(transformed.x);
916-
const uint16_t row = transformed.row;
917-
const bool is_lower = transformed.is_lower;
918-
919-
const size_t pixel_idx = (dy * w) + dx;
920-
uint8_t r8 = 0, g8 = 0, b8 = 0;
921-
922-
// Extract RGB888 from pixel format
923-
extract_rgb888_from_format(buffer, pixel_idx, format, color_order, big_endian, r8, g8, b8);
950+
// Extract RGB888 from pixel format (always_inline will inline the switch)
951+
uint8_t r8, g8, b8;
952+
extract_rgb888_from_format(pixel_ptr, 0, format, color_order, big_endian, r8, g8, b8);
953+
pixel_ptr += pixel_stride;
924954

925955
// Apply LUT correction
926956
const uint16_t r_corrected = lut_[r8];
927957
const uint16_t g_corrected = lut_[g8];
928958
const uint16_t b_corrected = lut_[b8];
929959

930-
// Update all bit planes for this pixel
960+
// Pre-compute bit patterns for all bit planes (eliminates 24 branches in bit loop)
961+
uint16_t upper_patterns[HUB75_BIT_DEPTH];
962+
uint16_t lower_patterns[HUB75_BIT_DEPTH];
931963
for (int bit = 0; bit < bit_depth_; bit++) {
932-
uint16_t *buf = (uint16_t *) (target_buffers[row].data + (bit * dma_width_ * 2));
933-
934-
const uint16_t mask = (1 << bit);
935-
uint16_t word = buf[px]; // Read existing word (preserves control bits)
964+
const uint16_t mask = BCM_BIT_MASKS[bit];
965+
upper_patterns[bit] = ((r_corrected & mask) ? (1 << R1_BIT) : 0) | ((g_corrected & mask) ? (1 << G1_BIT) : 0) |
966+
((b_corrected & mask) ? (1 << B1_BIT) : 0);
967+
lower_patterns[bit] = ((r_corrected & mask) ? (1 << R2_BIT) : 0) | ((g_corrected & mask) ? (1 << G2_BIT) : 0) |
968+
((b_corrected & mask) ? (1 << B2_BIT) : 0);
969+
}
936970

937-
// Clear and update RGB bits for appropriate half
938-
// IMPORTANT: Only modify RGB bits (0-5), preserve control bits (6-12)
939-
if (is_lower) {
940-
// Lower half: R2, G2, B2
941-
word &= ~RGB_LOWER_MASK;
942-
if (r_corrected & mask)
943-
word |= (1 << R2_BIT);
944-
if (g_corrected & mask)
945-
word |= (1 << G2_BIT);
946-
if (b_corrected & mask)
947-
word |= (1 << B2_BIT);
948-
} else {
949-
// Upper half: R1, G1, B1
950-
word &= ~RGB_UPPER_MASK;
951-
if (r_corrected & mask)
952-
word |= (1 << R1_BIT);
953-
if (g_corrected & mask)
954-
word |= (1 << G1_BIT);
955-
if (b_corrected & mask)
956-
word |= (1 << B1_BIT);
971+
// Update all bit planes using pre-computed patterns (is_lower hoisted outside loop)
972+
uint8_t *base_ptr = target_buffers[row].data;
973+
if (is_lower) {
974+
for (int bit = 0; bit < bit_depth_; bit++) {
975+
uint16_t *buf = (uint16_t *) (base_ptr + (bit * bit_plane_stride));
976+
buf[px] = (buf[px] & ~RGB_LOWER_MASK) | lower_patterns[bit];
977+
}
978+
} else {
979+
for (int bit = 0; bit < bit_depth_; bit++) {
980+
uint16_t *buf = (uint16_t *) (base_ptr + (bit * bit_plane_stride));
981+
buf[px] = (buf[px] & ~RGB_UPPER_MASK) | upper_patterns[bit];
957982
}
958-
959-
buf[px] = word;
960983
}
961984
}
962985
}

components/hub75/src/platforms/platform_dma.h

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,11 @@ class PlatformDma {
7979
* @param num_rows Number of row pairs (panel_height / 2)
8080
* @return Transformed coordinates with row index and half indicator
8181
*/
82-
static HUB75_IRAM inline TransformedCoords transform_coordinate(uint16_t px, uint16_t py, Hub75Rotation rotation,
83-
bool needs_layout_remap, bool needs_scan_remap,
84-
Hub75PanelLayout layout, Hub75ScanWiring scan_wiring,
85-
uint16_t panel_width, uint16_t panel_height,
86-
uint16_t layout_rows, uint16_t layout_cols,
87-
uint16_t phys_width, uint16_t phys_height,
88-
uint16_t dma_width, uint16_t num_rows) {
82+
__attribute__((always_inline)) static inline TransformedCoords transform_coordinate(
83+
uint16_t px, uint16_t py, Hub75Rotation rotation, bool needs_layout_remap, bool needs_scan_remap,
84+
Hub75PanelLayout layout, Hub75ScanWiring scan_wiring, uint16_t panel_width, uint16_t panel_height,
85+
uint16_t layout_rows, uint16_t layout_cols, uint16_t phys_width, uint16_t phys_height, uint16_t dma_width,
86+
uint16_t num_rows) {
8987
Coords c = {.x = px, .y = py};
9088

9189
// Step 1: Rotation transform (FIRST - convert rotated user coords to physical)

0 commit comments

Comments
 (0)