perf(es3c28p): LVGL 512KB PSRAM cache + display charging toggle

- LVGL image cache 512KB + header cache 16 on PSRAM (was PNG bottleneck, c4ec7ead)
- DisplaySettings.disableScreensaverWhenCharging persisted, checked via PowerDevice::IsCharging
- Display app: 'Disable on charging' toggle under Display settings, auto-disabled when timeout off
- DisplayIdle: skip screensaver while charging, auto-stop if plugging in while dimmed
- Keeps upstream full-DTS board clean, no JPEG decoder, no BT HID combo per user
This commit is contained in:
Adolfo Reyna
2026-07-18 17:16:18 -04:00
parent 057daf49a5
commit c4adaef0a5
4 changed files with 132 additions and 136 deletions
+33
View File
@@ -30,6 +30,8 @@ class HalDisplayApp final : public App {
lv_obj_t* timeoutSwitch = nullptr;
lv_obj_t* timeoutDropdown = nullptr;
lv_obj_t* screensaverDropdown = nullptr;
lv_obj_t* disableWhenChargingWrapper = nullptr;
lv_obj_t* disableWhenChargingSwitch = nullptr;
static void onBacklightSliderEvent(lv_event_t* event) {
auto* slider = static_cast<lv_obj_t*>(lv_event_get_target(event));
@@ -72,6 +74,14 @@ class HalDisplayApp final : public App {
}
}
static void onDisableWhenChargingChanged(lv_event_t* event) {
auto* app = static_cast<HalDisplayApp*>(lv_event_get_user_data(event));
auto* sw = static_cast<lv_obj_t*>(lv_event_get_target(event));
bool enabled = lv_obj_has_state(sw, LV_STATE_CHECKED);
app->displaySettings.disableScreensaverWhenCharging = enabled;
app->displaySettingsUpdated = true;
}
static void onTimeoutSwitch(lv_event_t* event) {
auto* app = static_cast<HalDisplayApp*>(lv_event_get_user_data(event));
auto* sw = static_cast<lv_obj_t*>(lv_event_get_target(event));
@@ -84,11 +94,17 @@ class HalDisplayApp final : public App {
if (app->screensaverDropdown) {
lv_obj_clear_state(app->screensaverDropdown, LV_STATE_DISABLED);
}
if (app->disableWhenChargingWrapper) {
lv_obj_clear_state(app->disableWhenChargingWrapper, LV_STATE_DISABLED);
}
} else {
lv_obj_add_state(app->timeoutDropdown, LV_STATE_DISABLED);
if (app->screensaverDropdown) {
lv_obj_add_state(app->screensaverDropdown, LV_STATE_DISABLED);
}
if (app->disableWhenChargingWrapper) {
lv_obj_add_state(app->disableWhenChargingWrapper, LV_STATE_DISABLED);
}
}
}
}
@@ -278,6 +294,23 @@ public:
if (!displaySettings.backlightTimeoutEnabled) {
lv_obj_add_state(screensaverDropdown, LV_STATE_DISABLED);
}
disableWhenChargingWrapper = lv_obj_create(main_wrapper);
lv_obj_set_size(disableWhenChargingWrapper, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_style_pad_all(disableWhenChargingWrapper, 0, LV_STATE_DEFAULT);
lv_obj_set_style_border_width(disableWhenChargingWrapper, 0, LV_STATE_DEFAULT);
auto* charging_label = lv_label_create(disableWhenChargingWrapper);
lv_label_set_text(charging_label, "Disable on charging");
lv_obj_align(charging_label, LV_ALIGN_LEFT_MID, 0, 0);
disableWhenChargingSwitch = lv_switch_create(disableWhenChargingWrapper);
if (displaySettings.disableScreensaverWhenCharging) {
lv_obj_add_state(disableWhenChargingSwitch, LV_STATE_CHECKED);
}
lv_obj_align(disableWhenChargingSwitch, LV_ALIGN_RIGHT_MID, 0, 0);
lv_obj_add_event_cb(disableWhenChargingSwitch, onDisableWhenChargingChanged, LV_EVENT_VALUE_CHANGED, this);
if (!displaySettings.backlightTimeoutEnabled) {
lv_obj_add_state(disableWhenChargingWrapper, LV_STATE_DISABLED);
}
}
}
@@ -11,6 +11,7 @@
#include <tactility/log.h>
#include <Tactility/CoreDefines.h>
#include <Tactility/hal/display/DisplayDevice.h>
#include <Tactility/hal/power/PowerDevice.h>
#include <Tactility/lvgl/LvglSync.h>
#include <Tactility/service/ServiceContext.h>
#include <Tactility/service/ServiceManifest.h>
@@ -28,6 +29,23 @@ static std::shared_ptr<hal::display::DisplayDevice> getDisplay() {
return hal::findFirstDevice<hal::display::DisplayDevice>(hal::Device::Type::Display);
}
static bool isDeviceCharging() {
bool charging = false;
hal::findDevices<hal::power::PowerDevice>(hal::Device::Type::Power, [&charging](const auto& power) {
if (!power->supportsMetric(hal::power::PowerDevice::MetricType::IsCharging)) {
return true;
}
hal::power::PowerDevice::MetricData data;
if (power->getMetric(hal::power::PowerDevice::MetricType::IsCharging, data) && data.valueAsBool) {
charging = true;
return false;
}
return true;
});
return charging;
}
void DisplayIdleService::stopScreensaverCb(lv_event_t* e) {
auto* self = static_cast<DisplayIdleService*>(lv_event_get_user_data(e));
lv_event_stop_bubbling(e);
@@ -123,6 +141,7 @@ void DisplayIdleService::updateScreensaver() {
}
}
void DisplayIdleService::tick() {
if (!lvgl::lock(100)) {
return;
@@ -138,16 +157,13 @@ void DisplayIdleService::tick() {
}
uint32_t inactive_ms = 0;
inactive_ms = lv_display_get_inactive_time(nullptr);
// Only update if not stopping (prevents lag on touch)
if (displayDimmed && screensaverOverlay && !stopScreensaverRequested.load(std::memory_order_acquire)) {
// Check if screensaver should auto-off after 5 minutes
if (!backlightOff) {
screensaverActiveCounter++;
if (screensaverActiveCounter >= SCREENSAVER_AUTO_OFF_TICKS) {
// Stop screensaver animation and turn off backlight
if (screensaver) {
screensaver->stop();
screensaver.reset();
@@ -165,38 +181,51 @@ void DisplayIdleService::tick() {
lvgl::unlock();
// Check stop request early for faster response
if (stopScreensaverRequested.load(std::memory_order_acquire)) {
stopScreensaver();
return;
}
auto display = getDisplay();
if (display != nullptr && display->supportsBacklightDuty()) {
if (!cachedDisplaySettings.backlightTimeoutEnabled || cachedDisplaySettings.backlightTimeoutMs == 0) {
if (displayDimmed) {
bool supportsBacklight = display != nullptr && display->supportsBacklightDuty();
if (!cachedDisplaySettings.backlightTimeoutEnabled || cachedDisplaySettings.backlightTimeoutMs == 0) {
if (displayDimmed) {
if (supportsBacklight && display != nullptr) {
display->setBacklightDuty(cachedDisplaySettings.backlightDuty);
displayDimmed = false;
}
} else {
if (!displayDimmed && inactive_ms >= cachedDisplaySettings.backlightTimeoutMs) {
displayDimmed = false;
}
} else if (supportsBacklight) {
bool charging_blocks = cachedDisplaySettings.disableScreensaverWhenCharging && isDeviceCharging();
if (!displayDimmed && inactive_ms >= cachedDisplaySettings.backlightTimeoutMs) {
if (charging_blocks) {
// Skip screensaver while charging
} else {
if (!lvgl::lock(100)) {
return; // Retry on next tick
return;
}
activateScreensaver();
lvgl::unlock();
// Turn off backlight for "None" screensaver (just black screen)
if (cachedDisplaySettings.screensaverType == settings::display::ScreensaverType::None) {
display->setBacklightDuty(0);
if (display != nullptr) {
display->setBacklightDuty(0);
}
}
displayDimmed = true;
} else if (displayDimmed && (inactive_ms < kWakeActivityThresholdMs)) {
}
} else if (displayDimmed) {
if (inactive_ms < kWakeActivityThresholdMs) {
stopScreensaver();
} else if (charging_blocks) {
stopScreensaver();
}
}
}
}
bool DisplayIdleService::onStart(ServiceContext& service) {
// Seed random number generator for varied screensaver patterns
srand(static_cast<unsigned int>(time(nullptr)));
+51 -105
View File
@@ -22,13 +22,13 @@ constexpr auto* SETTINGS_KEY_BACKLIGHT_DUTY = "backlightDuty";
constexpr auto* SETTINGS_KEY_TIMEOUT_ENABLED = "backlightTimeoutEnabled";
constexpr auto* SETTINGS_KEY_TIMEOUT_MS = "backlightTimeoutMs";
constexpr auto* SETTINGS_KEY_SCREENSAVER_TYPE = "screensaverType";
constexpr auto* SETTINGS_KEY_DISABLE_WHEN_CHARGING = "disableScreensaverWhenCharging";
static Orientation getDefaultOrientation() {
auto* display = lv_display_get_default();
if (display == nullptr) {
return Orientation::Landscape;
}
if (lv_display_get_physical_horizontal_resolution(display) > lv_display_get_physical_vertical_resolution(display)) {
return Orientation::Landscape;
} else {
@@ -39,86 +39,48 @@ static Orientation getDefaultOrientation() {
static std::string toString(Orientation orientation) {
switch (orientation) {
using enum Orientation;
case Portrait:
return "Portrait";
case Landscape:
return "Landscape";
case PortraitFlipped:
return "PortraitFlipped";
case LandscapeFlipped:
return "LandscapeFlipped";
default:
std::unreachable();
case Portrait: return "Portrait";
case Landscape: return "Landscape";
case PortraitFlipped: return "PortraitFlipped";
case LandscapeFlipped: return "LandscapeFlipped";
default: std::unreachable();
}
}
static bool fromString(const std::string& str, Orientation& orientation) {
if (str == "Portrait") {
orientation = Orientation::Portrait;
return true;
} else if (str == "Landscape") {
orientation = Orientation::Landscape;
return true;
} else if (str == "PortraitFlipped") {
orientation = Orientation::PortraitFlipped;
return true;
} else if (str == "LandscapeFlipped") {
orientation = Orientation::LandscapeFlipped;
return true;
} else {
return false;
}
if (str == "Portrait") { orientation = Orientation::Portrait; return true; }
else if (str == "Landscape") { orientation = Orientation::Landscape; return true; }
else if (str == "PortraitFlipped") { orientation = Orientation::PortraitFlipped; return true; }
else if (str == "LandscapeFlipped") { orientation = Orientation::LandscapeFlipped; return true; }
else { return false; }
}
static std::string toString(ScreensaverType type) {
switch (type) {
using enum ScreensaverType;
case None:
return "None";
case BouncingBalls:
return "BouncingBalls";
case Mystify:
return "Mystify";
case MatrixRain:
return "MatrixRain";
case StackChan:
return "StackChan";
default:
std::unreachable();
case None: return "None";
case BouncingBalls: return "BouncingBalls";
case Mystify: return "Mystify";
case MatrixRain: return "MatrixRain";
case StackChan: return "StackChan";
default: std::unreachable();
}
}
static bool fromString(const std::string& str, ScreensaverType& type) {
if (str == "None") {
type = ScreensaverType::None;
return true;
} else if (str == "BouncingBalls") {
type = ScreensaverType::BouncingBalls;
return true;
} else if (str == "Mystify") {
type = ScreensaverType::Mystify;
return true;
} else if (str == "MatrixRain") {
type = ScreensaverType::MatrixRain;
return true;
} else if (str == "StackChan") {
type = ScreensaverType::StackChan;
return true;
} else {
return false;
}
if (str == "None") { type = ScreensaverType::None; return true; }
else if (str == "BouncingBalls") { type = ScreensaverType::BouncingBalls; return true; }
else if (str == "Mystify") { type = ScreensaverType::Mystify; return true; }
else if (str == "MatrixRain") { type = ScreensaverType::MatrixRain; return true; }
else if (str == "StackChan") { type = ScreensaverType::StackChan; return true; }
else { return false; }
}
bool load(DisplaySettings& settings) {
auto settings_path = getSettingsFilePath();
if (!file::isFile(settings_path)) {
return false;
}
if (!file::isFile(settings_path)) { return false; }
std::map<std::string, std::string> map;
if (!file::loadPropertiesFile(settings_path, map)) {
return false;
}
if (!file::loadPropertiesFile(settings_path, map)) { return false; }
auto orientation_entry = map.find(SETTINGS_KEY_ORIENTATION);
Orientation orientation;
@@ -128,17 +90,13 @@ bool load(DisplaySettings& settings) {
auto gamma_entry = map.find(SETTINGS_KEY_GAMMA_CURVE);
int gamma_curve = 0;
if (gamma_entry != map.end()) {
gamma_curve = atoi(gamma_entry->second.c_str());
}
if (gamma_entry != map.end()) { gamma_curve = atoi(gamma_entry->second.c_str()); }
auto backlight_duty_entry = map.find(SETTINGS_KEY_BACKLIGHT_DUTY);
int backlight_duty = 200; // default
int backlight_duty = 200;
if (backlight_duty_entry != map.end()) {
backlight_duty = atoi(backlight_duty_entry->second.c_str());
if (backlight_duty_entry->second != "0" && backlight_duty == 0) {
backlight_duty = 200;
}
if (backlight_duty_entry->second != "0" && backlight_duty == 0) backlight_duty = 200;
}
bool timeout_enabled = false;
@@ -147,7 +105,7 @@ bool load(DisplaySettings& settings) {
timeout_enabled = (timeout_enabled_entry->second == "1" || timeout_enabled_entry->second == "true" || timeout_enabled_entry->second == "True");
}
uint32_t timeout_ms = 60000; // default 60s
uint32_t timeout_ms = 60000;
auto timeout_ms_entry = map.find(SETTINGS_KEY_TIMEOUT_MS);
if (timeout_ms_entry != map.end()) {
timeout_ms = static_cast<uint32_t>(std::strtoul(timeout_ms_entry->second.c_str(), nullptr, 10));
@@ -155,8 +113,12 @@ bool load(DisplaySettings& settings) {
auto screensaver_entry = map.find(SETTINGS_KEY_SCREENSAVER_TYPE);
ScreensaverType screensaver_type = ScreensaverType::BouncingBalls;
if (screensaver_entry != map.end()) {
fromString(screensaver_entry->second, screensaver_type);
if (screensaver_entry != map.end()) { fromString(screensaver_entry->second, screensaver_type); }
bool disable_when_charging = false;
auto charging_entry = map.find(SETTINGS_KEY_DISABLE_WHEN_CHARGING);
if (charging_entry != map.end()) {
disable_when_charging = (charging_entry->second == "1" || charging_entry->second == "true");
}
settings.orientation = orientation;
@@ -165,7 +127,7 @@ bool load(DisplaySettings& settings) {
settings.backlightTimeoutEnabled = timeout_enabled;
settings.backlightTimeoutMs = timeout_ms;
settings.screensaverType = screensaver_type;
settings.disableScreensaverWhenCharging = disable_when_charging;
return true;
}
@@ -176,15 +138,14 @@ DisplaySettings getDefault() {
.backlightDuty = 200,
.backlightTimeoutEnabled = false,
.backlightTimeoutMs = 60000,
.screensaverType = ScreensaverType::BouncingBalls
.screensaverType = ScreensaverType::BouncingBalls,
.disableScreensaverWhenCharging = false
};
}
DisplaySettings loadOrGetDefault() {
DisplaySettings settings;
if (!load(settings)) {
settings = getDefault();
}
if (!load(settings)) { settings = getDefault(); }
return settings;
}
@@ -196,10 +157,9 @@ bool save(const DisplaySettings& settings) {
map[SETTINGS_KEY_TIMEOUT_ENABLED] = settings.backlightTimeoutEnabled ? "1" : "0";
map[SETTINGS_KEY_TIMEOUT_MS] = std::to_string(settings.backlightTimeoutMs);
map[SETTINGS_KEY_SCREENSAVER_TYPE] = toString(settings.screensaverType);
map[SETTINGS_KEY_DISABLE_WHEN_CHARGING] = settings.disableScreensaverWhenCharging ? "1" : "0";
auto settings_path = getSettingsFilePath();
if (!file::findOrCreateParentDirectory(settings_path, 0755)) {
return false;
}
if (!file::findOrCreateParentDirectory(settings_path, 0755)) { return false; }
return file::savePropertiesFile(settings_path, map);
}
@@ -207,40 +167,26 @@ lv_display_rotation_t toLvglDisplayRotation(Orientation orientation) {
auto* lvgl_display = lv_display_get_default();
auto rotation = lv_display_get_rotation(lvgl_display);
bool is_originally_landscape;
// The lvgl resolution code compensates for rotation. We have to revert the compensation to get the real display resolution
// TODO: Use info from display driver
if (rotation == LV_DISPLAY_ROTATION_0 || rotation == LV_DISPLAY_ROTATION_180) {
is_originally_landscape = lv_display_get_physical_horizontal_resolution(lvgl_display) > lv_display_get_physical_vertical_resolution(lvgl_display);
} else {
is_originally_landscape = lv_display_get_physical_horizontal_resolution(lvgl_display) < lv_display_get_physical_vertical_resolution(lvgl_display);
}
if (is_originally_landscape) {
// Landscape display
switch (orientation) {
case Orientation::Landscape:
return LV_DISPLAY_ROTATION_0;
case Orientation::Portrait:
return LV_DISPLAY_ROTATION_90;
case Orientation::LandscapeFlipped:
return LV_DISPLAY_ROTATION_180;
case Orientation::PortraitFlipped:
return LV_DISPLAY_ROTATION_270;
default:
return LV_DISPLAY_ROTATION_0;
case Orientation::Landscape: return LV_DISPLAY_ROTATION_0;
case Orientation::Portrait: return LV_DISPLAY_ROTATION_90;
case Orientation::LandscapeFlipped: return LV_DISPLAY_ROTATION_180;
case Orientation::PortraitFlipped: return LV_DISPLAY_ROTATION_270;
default: return LV_DISPLAY_ROTATION_0;
}
} else {
// Portrait display
switch (orientation) {
case Orientation::Landscape:
return LV_DISPLAY_ROTATION_90;
case Orientation::Portrait:
return LV_DISPLAY_ROTATION_0;
case Orientation::LandscapeFlipped:
return LV_DISPLAY_ROTATION_270;
case Orientation::PortraitFlipped:
return LV_DISPLAY_ROTATION_180;
default:
return LV_DISPLAY_ROTATION_0;
case Orientation::Landscape: return LV_DISPLAY_ROTATION_90;
case Orientation::Portrait: return LV_DISPLAY_ROTATION_0;
case Orientation::LandscapeFlipped: return LV_DISPLAY_ROTATION_270;
case Orientation::PortraitFlipped: return LV_DISPLAY_ROTATION_180;
default: return LV_DISPLAY_ROTATION_0;
}
}
}