[display] Trim the per pixel cost of draw_pixel_at (#19360)

This commit is contained in:
J. Nick Koston
2026-09-16 16:19:47 -05:00
committed by GitHub
parent 845de7b7dd
commit bafa096a1d
15 changed files with 48 additions and 30 deletions
+4 -1
View File
@@ -3,6 +3,7 @@
#include <utility>
#include <numbers>
#include "display_color_utils.h"
#include "esphome/core/application.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
@@ -770,10 +771,12 @@ Rect Display::get_clipping() const {
void Display::clear_clipping_() { this->clipping_rectangle_.clear(); }
void Display::feed_wdt_pixel_slow_() { App.feed_wdt(); }
bool Display::clip(int x, int y) {
if (x < 0 || x >= this->get_width() || y < 0 || y >= this->get_height())
return false;
if (!this->get_clipping().inside(x, y))
if (this->is_point_clipped(x, y))
return false;
return true;
}
+19
View File
@@ -758,6 +758,13 @@ class Display : public PollingComponent {
bool is_clipping() const { return !this->clipping_rectangle_.empty(); }
/// Whether (x, y) falls outside the active clipping rectangle. Tests the
/// stack top in place: get_clipping() is out of line and returns the Rect
/// by value, which per pixel drawing cannot afford.
bool ESPHOME_ALWAYS_INLINE is_point_clipped(int x, int y) const {
return this->is_clipping() && !this->clipping_rectangle_.back().inside(x, y);
}
/** Check if pixel is within region of display.
*/
bool clip(int x, int y);
@@ -774,6 +781,17 @@ class Display : public PollingComponent {
void do_update_();
void clear_clipping_();
/// Watchdog feed for per pixel loops. App.feed_wdt() is already rate
/// limited, but every call reads the clock; only every 256th pixel makes
/// that call, so the real feeds are unchanged and a pixel costs a counter.
/// At 20 us per pixel on the slowest e-paper path that is about 5 ms
/// between clock reads.
void ESPHOME_ALWAYS_INLINE feed_wdt_per_pixel_() {
if (++this->wdt_pixel_counter_ == 0)
this->feed_wdt_pixel_slow_();
}
void feed_wdt_pixel_slow_();
virtual int get_height_internal() = 0;
virtual int get_width_internal() = 0;
@@ -793,6 +811,7 @@ class Display : public PollingComponent {
std::vector<DisplayOnPageChangeTrigger *> on_page_change_triggers_;
bool auto_clear_enabled_{true};
std::vector<Rect> clipping_rectangle_;
uint8_t wdt_pixel_counter_{0};
bool show_test_card_{false};
};
@@ -2,7 +2,6 @@
#include <utility>
#include "esphome/core/application.h"
#include "esphome/core/log.h"
namespace esphome::display {
@@ -44,7 +43,7 @@ int DisplayBuffer::get_height() {
}
void HOT DisplayBuffer::draw_pixel_at(int x, int y, Color color) {
if (!this->get_clipping().inside(x, y))
if (this->is_point_clipped(x, y))
return; // NOLINT
switch (this->rotation_) {
@@ -64,7 +63,7 @@ void HOT DisplayBuffer::draw_pixel_at(int x, int y, Color color) {
break;
}
this->draw_absolute_pixel_internal(x, y, color);
App.feed_wdt();
this->feed_wdt_per_pixel_();
}
} // namespace esphome::display
-10
View File
@@ -63,16 +63,6 @@ bool Rect::equal(Rect rect) const {
return (rect.x == this->x) && (rect.w == this->w) && (rect.y == this->y) && (rect.h == this->h);
}
bool Rect::inside(int16_t test_x, int16_t test_y, bool absolute) const { // NOLINT
if (!this->is_set()) {
return true;
}
if (absolute) {
return test_x >= this->x && test_x < this->x2() && test_y >= this->y && test_y < this->y2();
}
return test_x >= 0 && test_x < this->w && test_y >= 0 && test_y < this->h;
}
bool Rect::inside(Rect rect) const {
if (!this->is_set() || !rect.is_set()) {
return true;
+9 -1
View File
@@ -26,7 +26,15 @@ class Rect {
void shrink(Rect rect);
bool inside(Rect rect) const;
bool inside(int16_t test_x, int16_t test_y, bool absolute = true) const;
bool ESPHOME_ALWAYS_INLINE inside(int16_t test_x, int16_t test_y, bool absolute = true) const {
if (!this->is_set()) {
return true;
}
if (absolute) {
return test_x >= this->x && test_x < this->x2() && test_y >= this->y && test_y < this->y2();
}
return test_x >= 0 && test_x < this->w && test_y >= 0 && test_y < this->h;
}
bool equal(Rect rect) const;
void info(const std::string &prefix = "rect info:");
};
+1 -1
View File
@@ -299,7 +299,7 @@ bool EPaperBase::initialise(bool partial) {
* @return false if the coordinates are out of bounds
*/
bool EPaperBase::rotate_coordinates_(int &x, int &y) {
if (!this->get_clipping().inside(x, y))
if (this->is_point_clipped(x, y))
return false;
if (this->effective_transform_ & SWAP_XY)
std::swap(x, y);
+2 -3
View File
@@ -1,5 +1,4 @@
#include "hub75_component.h"
#include "esphome/core/application.h"
#include <cinttypes>
@@ -124,11 +123,11 @@ void HOT HUB75Display::draw_pixel_at(int x, int y, Color color) {
if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) [[unlikely]]
return;
if (!this->get_clipping().inside(x, y))
if (this->is_point_clipped(x, y))
return;
driver_->set_pixel(x, y, color.r, color.g, color.b);
App.feed_wdt();
this->feed_wdt_per_pixel_();
}
void HOT HUB75Display::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorOrder order,
+2 -2
View File
@@ -855,7 +855,7 @@ void IT8951Display::apply_transform_(int &x, int &y) const {
}
bool IT8951Display::rotate_coordinates_(int &x, int &y) {
if (!this->get_clipping().inside(x, y))
if (this->is_point_clipped(x, y))
return false;
this->apply_transform_(x, y);
if (x >= this->width_ || y >= this->height_ || x < 0 || y < 0)
@@ -929,7 +929,7 @@ void IT8951Display::fill(Color color) {
void HOT IT8951Display::draw_pixel_at(int x, int y, Color color) {
if (this->buffer_ == nullptr)
return;
App.feed_wdt();
this->feed_wdt_per_pixel_();
if (!this->rotate_coordinates_(x, y))
return;
this->write_pixel_native_(static_cast<uint16_t>(x), static_cast<uint16_t>(y), color);
+1 -1
View File
@@ -259,7 +259,7 @@ bool MipiDsi::check_buffer_() {
}
void MipiDsi::draw_pixel_at(int x, int y, Color color) {
if (!this->get_clipping().inside(x, y))
if (this->is_point_clipped(x, y))
return;
switch (this->rotation_) {
+1 -1
View File
@@ -259,7 +259,7 @@ bool MipiRgb::check_buffer_() {
}
void MipiRgb::draw_pixel_at(int x, int y, Color color) {
if (!this->get_clipping().inside(x, y) || this->is_failed())
if (this->is_point_clipped(x, y) || this->is_failed())
return;
switch (this->rotation_) {
+1 -1
View File
@@ -604,7 +604,7 @@ class MipiSpiBuffer
// Draw a pixel at the given coordinates.
void draw_pixel_at(int x, int y, Color color) override {
if (!this->get_clipping().inside(x, y))
if (this->is_point_clipped(x, y))
return;
if constexpr (not HAS_HARDWARE_ROTATION) {
if (this->rotation_ == display::DISPLAY_ROTATION_180_DEGREES) {
+1 -1
View File
@@ -120,7 +120,7 @@ void Pixoo::set_pixel_(uint32_t index, Color color) {
}
void HOT Pixoo::draw_pixel_at(int x, int y, Color color) {
if (!this->get_clipping().inside(x, y))
if (this->is_point_clipped(x, y))
return;
const int side = static_cast<int>(this->model_);
switch (this->rotation_) {
@@ -101,7 +101,7 @@ int RpiDpiRgb::get_height() {
}
void RpiDpiRgb::draw_pixel_at(int x, int y, Color color) {
if (!this->get_clipping().inside(x, y))
if (this->is_point_clipped(x, y))
return; // NOLINT
switch (this->rotation_) {
@@ -124,7 +124,7 @@ void RpiDpiRgb::draw_pixel_at(int x, int y, Color color) {
this->draw_pixels_at(x, y, 1, 1, (const uint8_t *) &pixel, display::COLOR_ORDER_RGB, display::COLOR_BITNESS_565, true,
0, 0, 0);
App.feed_wdt();
this->feed_wdt_per_pixel_();
}
void RpiDpiRgb::dump_config() {
+1 -1
View File
@@ -164,7 +164,7 @@ void Sdl::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *
}
void Sdl::draw_pixel_at(int x, int y, Color color) {
if (this->texture_ == nullptr || !this->get_clipping().inside(x, y))
if (this->texture_ == nullptr || this->is_point_clipped(x, y))
return;
if (this->rotation_ == display::DISPLAY_ROTATION_180_DEGREES) {
+2 -2
View File
@@ -84,7 +84,7 @@ void ST7701S::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8
}
void ST7701S::draw_pixel_at(int x, int y, Color color) {
if (!this->get_clipping().inside(x, y))
if (this->is_point_clipped(x, y))
return; // NOLINT
switch (this->rotation_) {
@@ -107,7 +107,7 @@ void ST7701S::draw_pixel_at(int x, int y, Color color) {
this->draw_pixels_at(x, y, 1, 1, (const uint8_t *) &pixel, display::COLOR_ORDER_RGB, display::COLOR_BITNESS_565, true,
0, 0, 0);
App.feed_wdt();
this->feed_wdt_per_pixel_();
}
void ST7701S::write_command_(uint8_t value) {