From 49bbaf51a012cce32d173394038eb981d9f0e278 Mon Sep 17 00:00:00 2001 From: Adam Wulkiewicz Date: Tue, 27 Sep 2022 15:54:15 +0200 Subject: [PATCH] [MOS-550] Improve refresh of the display 1. Implement partial refresh. 2. Implement refresh canceling mechanism. 3. Refactor some parts of the gui and display code. ad 1. - Detect parts of the screen changed since last update and merge them into bigger regions. These regions defines parts of the context sent to the display. - Refresh the region covering all of the parts since this is the most time consuming part and the size of the refreshed region doesn't change the time much. - Refresh the whole screen if deep refresh is requested and previously fast refresh was used. This is needed to prevent unwanted artifacts in some cases. ad 2. - Separate display update and refresh logic. - Divide image display message handling into two handlers, one updating and other one refreshing the screen. - Add cancel refresh message and use it to cancel refresh during update. - Store sum of refresh regions gathered during updates to refresh them all at once at the end. --- .vscode/launch.json | 3 +- .vscode/tasks.json | 38 +++ harmony_changelog.md | 1 + .../application-call/windows/CallWindow.cpp | 4 + module-bsp/board/linux/eink/ED028TC1.c | 4 +- module-bsp/board/linux/eink/ED028TC1.h | 4 +- .../board/linux/eink/LinuxEinkDisplay.cpp | 25 +- .../board/linux/eink/LinuxEinkDisplay.hpp | 8 +- module-bsp/board/rt1051/bsp/eink/ED028TC1.cpp | 41 +-- module-bsp/board/rt1051/bsp/eink/ED028TC1.h | 4 +- .../board/rt1051/bsp/eink/EinkDisplay.cpp | 88 ++++-- .../board/rt1051/bsp/eink/EinkDisplay.hpp | 15 +- .../board/rt1051/bsp/eink/eink_dimensions.cpp | 2 +- .../include/hal/eink/AbstractEinkDisplay.hpp | 13 +- module-gui/gui/core/BoundingBox.cpp | 4 +- module-gui/gui/core/BoundingBox.hpp | 11 +- module-gui/gui/core/Context.cpp | 183 ++++++++++--- module-gui/gui/core/Context.hpp | 89 ++++--- module-gui/gui/core/DrawCommand.cpp | 33 ++- module-gui/gui/core/ImageManager.cpp | 48 ++-- module-gui/gui/core/RawFont.cpp | 28 +- module-gui/gui/core/Renderer.cpp | 6 +- module-gui/gui/core/Renderer.hpp | 14 +- .../gui/core/renderers/RectangleRenderer.cpp | 17 +- module-gui/gui/widgets/Item.cpp | 2 +- module-gui/gui/widgets/Rect.cpp | 2 +- module-gui/test/test-catch/test-context.cpp | 88 +++--- module-services/service-eink/ServiceEink.cpp | 250 +++++++++++++++++- module-services/service-eink/ServiceEink.hpp | 17 ++ .../service-eink/messages/ImageMessage.cpp | 29 +- .../service-eink/messages/ImageMessage.hpp | 32 ++- module-services/service-gui/ServiceGUI.cpp | 20 +- module-services/service-gui/WorkerGUI.cpp | 9 +- .../service-gui/service-gui/ServiceGUI.hpp | 1 + module-utils/log/api/log/debug.hpp | 13 +- pure_changelog.md | 1 + 36 files changed, 837 insertions(+), 310 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 4e7f0ada0411397b02174f3376769ca3f2e5d886..4ba56f32ad8ebb54414902e0e670338ce375c979 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -64,7 +64,7 @@ "cwd": "${workspaceFolder}", "gdbPath": "arm-none-eabi-gdb", // "debuggerArgs": ["-x", "${workspaceFolder}/.gdbinit-1051"], - // "serverArgs": ["-strict", "-ir", "-singlerun", "-speed", "25000"], + "serverArgs": ["-strict", "-ir", "-singlerun", "-speed", "25000"], // "runToEntryPoint": "main", // "runToMain": true, "preLaunchCommands": [], @@ -109,6 +109,7 @@ "serverpath": "JLinkGDBServerCLExe", "cwd": "${workspaceFolder}", "gdbPath": "arm-none-eabi-gdb", + "serverArgs": ["-strict", "-ir", "-singlerun", "-speed", "25000"], "interface": "swd", "device": "MCIMXRT1051", "jlinkscript": "${workspaceFolder}/evkbimxrt1050_sdram_init_T6.jlinkscript", diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 1973bea92676f8da71334e0e50e6d0b554c7d267..021d15cda831849bf194b35c626e7955ded158e8 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -169,6 +169,44 @@ "detail": "Configure cmake project and build with Ninja", }, + { + "type": "shell", + "label": "Configure PurePhone (RT1051, DEV)", + "command": "./configure.sh", + "args": [ + "pure", + "rt1051", + "RelWithDebInfo", + "-DWITH_DEVELOPMENT_FEATURES=ON", + "-G", + "Ninja" + ], + "options": { + "cwd": "${workspaceFolder}" + }, + "group": "build", + "detail": "Run cmake configure script", + }, + + { + "type": "shell", + "label": "Configure BellHybrid (RT1051, DEV)", + "command": "./configure.sh", + "args": [ + "bell", + "rt1051", + "RelWithDebInfo", + "-DWITH_DEVELOPMENT_FEATURES=ON", + "-G", + "Ninja" + ], + "options": { + "cwd": "${workspaceFolder}" + }, + "group": "build", + "detail": "Run cmake configure script", + }, + // { // "type": "shell", // "label": "Run JLink Server for PurePhone (RT1051)", diff --git a/harmony_changelog.md b/harmony_changelog.md index 3db8a5cffd857ccac0c10f5fe1ad7ffaf3fe6c0a..2067209b9e9557dc2b74a717e455df074c2350d2 100644 --- a/harmony_changelog.md +++ b/harmony_changelog.md @@ -23,6 +23,7 @@ #### UI/UX: * UI update (Home Screen settings). +* Improve refreshing of the display. #### Translations: * Completed missing translations. diff --git a/module-apps/application-call/windows/CallWindow.cpp b/module-apps/application-call/windows/CallWindow.cpp index 64fb89d788e55451dfab86c97461d3e6d7c999fa..d974489711813719efb0a91af426f5aba032a43b 100644 --- a/module-apps/application-call/windows/CallWindow.cpp +++ b/module-apps/application-call/windows/CallWindow.cpp @@ -123,6 +123,10 @@ namespace gui app::manager::OnSwitchBehaviour::RunInBackground); }; + sendSmsIcon->setVisible(false); + microphoneIcon->setVisible(false); + speakerIcon->setVisible(false); + // define navigation between icons microphoneIcon->setNavigationItem(NavigationDirection::LEFT, speakerIcon); microphoneIcon->setNavigationItem(NavigationDirection::RIGHT, speakerIcon); diff --git a/module-bsp/board/linux/eink/ED028TC1.c b/module-bsp/board/linux/eink/ED028TC1.c index a7f56d628a4ddc784086bdb16d5aacc890e6045b..c9d87cfe25165817c6eaff598d4c7ea3487891f6 100644 --- a/module-bsp/board/linux/eink/ED028TC1.c +++ b/module-bsp/board/linux/eink/ED028TC1.c @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. +// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md /** @@ -119,7 +119,7 @@ EinkStatus_e EinkResetAndInitialize() return EinkOK; } -EinkStatus_e EinkUpdateFrame(EinkFrame_t frame, uint8_t *buffer) +EinkStatus_e EinkUpdateFrame(EinkFrame_t frame, const uint8_t *buffer) { uint32_t offset_eink = frame.pos_y * BOARD_EINK_DISPLAY_RES_X + frame.pos_x; uint32_t offset_buffer = 0; diff --git a/module-bsp/board/linux/eink/ED028TC1.h b/module-bsp/board/linux/eink/ED028TC1.h index caff2db49fd6765bca0c812aba1afaa0e64062c3..9a5aae72c091a11a50f22636ce713263de3d3d82 100644 --- a/module-bsp/board/linux/eink/ED028TC1.h +++ b/module-bsp/board/linux/eink/ED028TC1.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. +// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md /** @@ -90,7 +90,7 @@ extern "C" * @return EinkNoMem - Could not allocate the temporary buffer * EinkOK - Part of image send successfully */ - EinkStatus_e EinkUpdateFrame(EinkFrame_t frame, uint8_t *buffer); + EinkStatus_e EinkUpdateFrame(EinkFrame_t frame, const uint8_t *buffer); /** * @brief This function sets the waveform to the \ref EinkWaveformINIT to make the display clearing more deep and diff --git a/module-bsp/board/linux/eink/LinuxEinkDisplay.cpp b/module-bsp/board/linux/eink/LinuxEinkDisplay.cpp index ee4b6f5941894ef62b0fe89bc3293619e0891b51..d3a59331d47f728327349bf8d9513c3c9ffff668 100644 --- a/module-bsp/board/linux/eink/LinuxEinkDisplay.cpp +++ b/module-bsp/board/linux/eink/LinuxEinkDisplay.cpp @@ -47,9 +47,30 @@ namespace hal::eink displayColorMode = mode; } - EinkStatus LinuxEinkDisplay::showImage(std::uint8_t *frameBuffer, const EinkRefreshMode refreshMode) + EinkStatus LinuxEinkDisplay::showImageUpdate(const std::vector &updateFrames, + const std::uint8_t *frameBuffer) { - return translateStatus(EinkUpdateFrame(EinkFrame_t{0, 0, size.width, size.height}, frameBuffer)); + for (const EinkFrame &frame : updateFrames) { + const std::uint8_t *buffer = frameBuffer + frame.pos_y * frame.size.width; + const auto status = translateStatus( + EinkUpdateFrame({frame.pos_x, frame.pos_y, frame.size.width, frame.size.height}, buffer)); + if (status != EinkStatus::EinkOK) + return status; + } + return EinkStatus::EinkOK; + } + + EinkStatus LinuxEinkDisplay::showImageRefresh(const EinkFrame &refreshFrame, const EinkRefreshMode refreshMode) + { + return EinkStatus::EinkOK; + } + + EinkStatus LinuxEinkDisplay::showImage(const std::vector &updateFrames, + const EinkFrame &refreshFrame, + const std::uint8_t *frameBuffer, + const EinkRefreshMode refreshMode) + { + return showImageUpdate(updateFrames, frameBuffer); } void LinuxEinkDisplay::prepareEarlyRequest([[maybe_unused]] const EinkRefreshMode refreshMode, diff --git a/module-bsp/board/linux/eink/LinuxEinkDisplay.hpp b/module-bsp/board/linux/eink/LinuxEinkDisplay.hpp index 6167a4808013a9c75a7599f4cf7781bf6bf04a33..0950afcd8fc5f3d9d772e3d707e00638677a8068 100644 --- a/module-bsp/board/linux/eink/LinuxEinkDisplay.hpp +++ b/module-bsp/board/linux/eink/LinuxEinkDisplay.hpp @@ -15,7 +15,13 @@ namespace hal::eink private: void setMode(const EinkDisplayColorMode mode) noexcept override; - EinkStatus showImage(std::uint8_t *frameBuffer, const EinkRefreshMode refreshMode) override; + EinkStatus showImageUpdate(const std::vector &updateFrames, + const std::uint8_t *frameBuffer) override; + EinkStatus showImageRefresh(const EinkFrame &refreshFrame, const EinkRefreshMode refreshMode) override; + EinkStatus showImage(const std::vector &updateFrames, + const EinkFrame &refreshFrame, + const std::uint8_t *frameBuffer, + const EinkRefreshMode refreshMode) override; void prepareEarlyRequest(const EinkRefreshMode refreshMode, const WaveformTemperature behaviour) override; void dither() override; diff --git a/module-bsp/board/rt1051/bsp/eink/ED028TC1.cpp b/module-bsp/board/rt1051/bsp/eink/ED028TC1.cpp index c9b50b7b73229ac8bb9731d31489e81592f00472..923953e4345724aa70bae83e9098ca1c59f6e8d7 100644 --- a/module-bsp/board/rt1051/bsp/eink/ED028TC1.cpp +++ b/module-bsp/board/rt1051/bsp/eink/ED028TC1.cpp @@ -138,7 +138,7 @@ static uint8_t s_einkMaskLut_2Bpp[16] = {0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, * * @return */ -static uint8_t *s_EinkTransformFrameCoordinateSystem_1Bpp(uint8_t *dataIn, +static uint8_t *s_EinkTransformFrameCoordinateSystem_1Bpp(const uint8_t *dataIn, uint16_t windowWidthPx, uint16_t windowHeightPx, uint8_t *dataOut, @@ -187,7 +187,7 @@ static uint8_t *s_EinkTransformFrameCoordinateSystem_1Bpp(uint8_t *dataIn, * * @return */ -static uint8_t *s_EinkTransformFrameCoordinateSystem_2Bpp(uint8_t *dataIn, +static uint8_t *s_EinkTransformFrameCoordinateSystem_2Bpp(const uint8_t *dataIn, uint16_t windowWidthPx, uint16_t windowHeightPx, uint8_t *dataOut, @@ -236,7 +236,7 @@ static uint8_t *s_EinkTransformFrameCoordinateSystem_2Bpp(uint8_t *dataIn, * * @return */ -static uint8_t *s_EinkTransformFrameCoordinateSystem_3Bpp(uint8_t *dataIn, +static uint8_t *s_EinkTransformFrameCoordinateSystem_3Bpp(const uint8_t *dataIn, uint16_t windowWidthPx, uint16_t windowHeightPx, uint8_t *dataOut, @@ -285,7 +285,7 @@ static uint8_t *s_EinkTransformFrameCoordinateSystem_3Bpp(uint8_t *dataIn, * * @return */ -static uint8_t *s_EinkTransformFrameCoordinateSystem_4Bpp(uint8_t *dataIn, +static uint8_t *s_EinkTransformFrameCoordinateSystem_4Bpp(const uint8_t *dataIn, uint16_t windowWidthPx, uint16_t windowHeightPx, uint8_t *dataOut, @@ -334,7 +334,7 @@ static uint8_t *s_EinkTransformFrameCoordinateSystem_4Bpp(uint8_t *dataIn, * * @return */ -static uint8_t *s_EinkTransformAnimationFrameCoordinateSystem_1Bpp(uint8_t *dataIn, +static uint8_t *s_EinkTransformAnimationFrameCoordinateSystem_1Bpp(const uint8_t *dataIn, uint16_t windowWidthPx, uint16_t windowHeightPx, uint8_t *dataOut, @@ -383,7 +383,7 @@ static uint8_t *s_EinkTransformAnimationFrameCoordinateSystem_1Bpp(uint8_t *data * * @return */ -static uint8_t *s_EinkTransformAnimationFrameCoordinateSystem_2Bpp(uint8_t *dataIn, +static uint8_t *s_EinkTransformAnimationFrameCoordinateSystem_2Bpp(const uint8_t *dataIn, uint16_t windowWidthPx, uint16_t windowHeightPx, uint8_t *dataOut, @@ -432,7 +432,7 @@ static uint8_t *s_EinkTransformAnimationFrameCoordinateSystem_2Bpp(uint8_t *data * * @return */ -static uint8_t *s_EinkTransformAnimationFrameCoordinateSystem_3Bpp(uint8_t *dataIn, +static uint8_t *s_EinkTransformAnimationFrameCoordinateSystem_3Bpp(const uint8_t *dataIn, uint16_t windowWidthPx, uint16_t windowHeightPx, uint8_t *dataOut, @@ -487,7 +487,7 @@ static uint8_t *s_EinkTransformAnimationFrameCoordinateSystem_3Bpp(uint8_t *data * It is used when EINK_ROTATE_90_CLOCKWISE is not defined. */ -static uint8_t *s_EinkTransformFrameCoordinateSystemNoRotation_4Bpp(uint8_t *dataIn, +static uint8_t *s_EinkTransformFrameCoordinateSystemNoRotation_4Bpp(const uint8_t *dataIn, uint16_t windowWidthPx, uint16_t windowHeightPx, uint8_t *dataOut, @@ -799,7 +799,10 @@ EinkStatus_e EinkDitherDisplay() return EinkOK; } -EinkStatus_e EinkUpdateFrame(EinkFrame_t frame, uint8_t *buffer, EinkBpp_e bpp, EinkDisplayColorMode_e invertColors) +EinkStatus_e EinkUpdateFrame(EinkFrame_t frame, + const uint8_t *buffer, + EinkBpp_e bpp, + EinkDisplayColorMode_e invertColors) { uint8_t buf[10]; uint8_t pixelsInByte = 8 / bpp; @@ -1004,7 +1007,7 @@ EinkStatus_e EinkRefreshImage(EinkFrame_t frame, EinkDisplayTimingsMode_e refres return EinkOK; } -__attribute__((optimize("O3"))) void EinkARGBToLuminance(uint8_t *dataIn, +__attribute__((optimize("O3"))) void EinkARGBToLuminance(const uint8_t *dataIn, uint8_t *dataOut, uint32_t displayWidth, uint32_t displayHeight) @@ -1037,7 +1040,7 @@ __attribute__((optimize("O3"))) void EinkARGBToLuminance(uint8_t *dataIn, } __attribute__((optimize("O1"))) static uint8_t *s_EinkTransformFrameCoordinateSystem_1Bpp( - uint8_t *dataIn, + const uint8_t *dataIn, uint16_t windowWidthPx, uint16_t windowHeightPx, uint8_t *dataOut, @@ -1084,7 +1087,7 @@ __attribute__((optimize("O1"))) static uint8_t *s_EinkTransformFrameCoordinateSy } __attribute__((optimize("O1"))) static uint8_t *s_EinkTransformFrameCoordinateSystem_2Bpp( - uint8_t *dataIn, + const uint8_t *dataIn, uint16_t windowWidthPx, uint16_t windowHeightPx, uint8_t *dataOut, @@ -1135,7 +1138,7 @@ __attribute__((optimize("O1"))) static uint8_t *s_EinkTransformFrameCoordinateSy } __attribute__((optimize("O1"))) static uint8_t *s_EinkTransformFrameCoordinateSystem_3Bpp( - uint8_t *dataIn, + const uint8_t *dataIn, uint16_t windowWidthPx, uint16_t windowHeightPx, uint8_t *dataOut, @@ -1146,7 +1149,7 @@ __attribute__((optimize("O1"))) static uint8_t *s_EinkTransformFrameCoordinateSy } __attribute__((optimize("O1"))) static uint8_t *s_EinkTransformAnimationFrameCoordinateSystem_1Bpp( - uint8_t *dataIn, + const uint8_t *dataIn, uint16_t windowWidthPx, uint16_t windowHeightPx, uint8_t *dataOut, @@ -1193,7 +1196,7 @@ __attribute__((optimize("O1"))) static uint8_t *s_EinkTransformAnimationFrameCoo } __attribute__((optimize("O1"))) static uint8_t *s_EinkTransformAnimationFrameCoordinateSystem_2Bpp( - uint8_t *dataIn, + const uint8_t *dataIn, uint16_t windowWidthPx, uint16_t windowHeightPx, uint8_t *dataOut, @@ -1242,7 +1245,7 @@ __attribute__((optimize("O1"))) static uint8_t *s_EinkTransformAnimationFrameCoo } __attribute__((optimize("O3"))) static uint8_t *s_EinkTransformAnimationFrameCoordinateSystem_3Bpp( - uint8_t *dataIn, + const uint8_t *dataIn, uint16_t windowWidthPx, uint16_t windowHeightPx, uint8_t *dataOut, @@ -1253,7 +1256,7 @@ __attribute__((optimize("O3"))) static uint8_t *s_EinkTransformAnimationFrameCoo } __attribute__((optimize("O1"))) static uint8_t *s_EinkTransformFrameCoordinateSystem_4Bpp( - uint8_t *dataIn, + const uint8_t *dataIn, uint16_t windowWidthPx, uint16_t windowHeightPx, uint8_t *dataOut, @@ -1300,7 +1303,7 @@ __attribute__((optimize("O1"))) static uint8_t *s_EinkTransformFrameCoordinateSy } __attribute__((optimize("O1"))) static uint8_t *s_EinkTransformFrameCoordinateSystemNoRotation_4Bpp( - uint8_t *dataIn, + const uint8_t *dataIn, uint16_t windowWidthPx, uint16_t windowHeightPx, uint8_t *dataOut, @@ -1315,7 +1318,7 @@ __attribute__((optimize("O1"))) static uint8_t *s_EinkTransformFrameCoordinateSy int32_t inputRow = 0; int32_t inputCol = 0; - for (inputRow = 0; inputRow < windowHeightPx - 1; ++inputRow) { + for (inputRow = 0; inputRow < windowHeightPx; ++inputRow) { for (inputCol = windowWidthPx - 7; inputCol >= 0; inputCol -= pixelsInByte) { // HACK: Did not create the loop for accessing pixels and merging them in the single byte for better // performance. diff --git a/module-bsp/board/rt1051/bsp/eink/ED028TC1.h b/module-bsp/board/rt1051/bsp/eink/ED028TC1.h index ea27a14c245d15816d5556326de5594300787d4e..b6a24e339dcd2c3673b893e529e5331cecd3ffcc 100644 --- a/module-bsp/board/rt1051/bsp/eink/ED028TC1.h +++ b/module-bsp/board/rt1051/bsp/eink/ED028TC1.h @@ -382,7 +382,7 @@ extern "C" * EinkOK - Part of image send successfully */ EinkStatus_e EinkUpdateFrame(EinkFrame_t frame, - uint8_t *buffer, + const uint8_t *buffer, EinkBpp_e bpp, EinkDisplayColorMode_e invertColors); @@ -431,7 +431,7 @@ extern "C" * @param displayWidth [in] - display width in pixels * @param displayHeight [in] - display height in pixels */ - void EinkARGBToLuminance(uint8_t *dataIn, uint8_t *dataOut, uint32_t displayWidth, uint32_t displayHeight); + void EinkARGBToLuminance(const uint8_t *dataIn, uint8_t *dataOut, uint32_t displayWidth, uint32_t displayHeight); #if defined(__cplusplus) } diff --git a/module-bsp/board/rt1051/bsp/eink/EinkDisplay.cpp b/module-bsp/board/rt1051/bsp/eink/EinkDisplay.cpp index 36c249b07d2b2af193786f4b4160db49bd1143c3..d3f13ba4dbba4764b96fdd018661c308e046dd6d 100644 --- a/module-bsp/board/rt1051/bsp/eink/EinkDisplay.cpp +++ b/module-bsp/board/rt1051/bsp/eink/EinkDisplay.cpp @@ -4,10 +4,10 @@ #include "EinkDisplay.hpp" #include - #include #include + #include #include #include @@ -132,17 +132,6 @@ namespace hal::eink delete[] currentWaveform.LUTDData; } - EinkStatus EinkDisplay::updateDisplay(std::uint8_t *frameBuffer, [[maybe_unused]] const EinkRefreshMode refreshMode) - { - return update(frameBuffer); - } - - EinkStatus EinkDisplay::refreshDisplay(const EinkRefreshMode refreshMode) - { - const auto isDeepRefresh = refreshMode == EinkRefreshMode::REFRESH_DEEP; - return refresh(isDeepRefresh ? EinkDisplayTimingsDeepCleanMode : EinkDisplayTimingsFastRefreshMode); - } - EinkStatus EinkDisplay::prepareDisplay(const EinkRefreshMode refreshMode, const WaveformTemperature behaviour) { powerOn(); @@ -160,20 +149,75 @@ namespace hal::eink return setWaveform(EinkWaveforms_e::EinkWaveformDU2, temperature); } - EinkStatus EinkDisplay::showImage(std::uint8_t *frameBuffer, const EinkRefreshMode refreshMode) + EinkStatus EinkDisplay::updateDisplay(EinkFrame frame, const std::uint8_t *frameBuffer) + { + return translateStatus( + EinkUpdateFrame(EinkFrame_t{frame.pos_x, frame.pos_y, frame.size.width, frame.size.height}, + frameBuffer, + getCurrentBitsPerPixelFormat(), + translateDisplayColorMode(displayMode))); + } + + EinkStatus EinkDisplay::refreshDisplay(EinkFrame frame, const EinkRefreshMode refreshMode) + { + const auto refreshTimingsMode = refreshMode == EinkRefreshMode::REFRESH_DEEP + ? EinkDisplayTimingsDeepCleanMode + : EinkDisplayTimingsFastRefreshMode; + + currentWaveform.useCounter += 1; + return translateStatus(EinkRefreshImage( + EinkFrame_t{frame.pos_x, frame.pos_y, frame.size.width, frame.size.height}, refreshTimingsMode)); + } + + EinkStatus EinkDisplay::showImageUpdate(const std::vector &updateFrames, const std::uint8_t *frameBuffer) + { + powerOn(); + + for (const EinkFrame &frame : updateFrames) { + const std::uint8_t *buffer = frameBuffer + frame.pos_y * frame.size.width; + if (const auto status = updateDisplay(frame, buffer); status != EinkStatus::EinkOK) { + return status; + } + } + + return EinkStatus::EinkOK; + } + + EinkStatus EinkDisplay::showImageRefresh(const EinkFrame &refreshFrame, const EinkRefreshMode refreshMode) { if (const auto status = prepareDisplay(refreshMode, WaveformTemperature::KEEP_CURRENT); status != EinkStatus::EinkOK) { return status; } - if (const auto status = updateDisplay(frameBuffer, refreshMode); status != EinkStatus::EinkOK) { + if (const auto status = refreshDisplay(refreshFrame, refreshMode); status != EinkStatus::EinkOK) { + return status; + } + + return EinkStatus::EinkOK; + } + + EinkStatus EinkDisplay::showImage(const std::vector &updateFrames, + const EinkFrame &refreshFrame, + const std::uint8_t *frameBuffer, + const EinkRefreshMode refreshMode) + { + if (const auto status = prepareDisplay(refreshMode, WaveformTemperature::KEEP_CURRENT); + status != EinkStatus::EinkOK) { return status; } - if (const auto status = refreshDisplay(refreshMode); status != EinkStatus::EinkOK) { + for (const EinkFrame &frame : updateFrames) { + const std::uint8_t *buffer = frameBuffer + frame.pos_y * frame.size.width; + if (const auto status = updateDisplay(frame, buffer); status != EinkStatus::EinkOK) { + return status; + } + } + + if (const auto status = refreshDisplay(refreshFrame, refreshMode); status != EinkStatus::EinkOK) { return status; } + return EinkStatus::EinkOK; } @@ -218,14 +262,6 @@ namespace hal::eink EinkFillScreenWithColor(EinkDisplayColorFilling_e::EinkDisplayColorWhite); } - EinkStatus EinkDisplay::update(std::uint8_t *displayBuffer) - { - return translateStatus(EinkUpdateFrame(EinkFrame_t{0, 0, size.width, size.height}, - displayBuffer, - getCurrentBitsPerPixelFormat(), - translateDisplayColorMode(displayMode))); - } - EinkBpp_e EinkDisplay::getCurrentBitsPerPixelFormat() const noexcept { if ((currentWaveform.mode == EinkWaveformA2) || (currentWaveform.mode == EinkWaveformDU2)) { @@ -234,12 +270,6 @@ namespace hal::eink return Eink4Bpp; } - EinkStatus EinkDisplay::refresh(const EinkDisplayTimingsMode_e refreshMode) - { - currentWaveform.useCounter += 1; - return translateStatus(EinkRefreshImage(EinkFrame_t{0, 0, size.width, size.height}, refreshMode)); - } - bool EinkDisplay::isNewWaveformNeeded(const EinkWaveforms_e newMode, const std::int32_t newTemperature) const { constexpr auto lenientTemperatureUseCounter = 50; // arbitrary. not documented diff --git a/module-bsp/board/rt1051/bsp/eink/EinkDisplay.hpp b/module-bsp/board/rt1051/bsp/eink/EinkDisplay.hpp index d85f5c2e7f63878bb351deaa5691954c76c2e759..9002e03b06600f246a809ec04ab05e2c24749e5c 100644 --- a/module-bsp/board/rt1051/bsp/eink/EinkDisplay.hpp +++ b/module-bsp/board/rt1051/bsp/eink/EinkDisplay.hpp @@ -26,7 +26,13 @@ namespace hal::eink ~EinkDisplay() noexcept; void setMode(EinkDisplayColorMode mode) noexcept override; - EinkStatus showImage(std::uint8_t *frameBuffer, const EinkRefreshMode refreshMode) override; + EinkStatus showImageUpdate(const std::vector &updateFrames, + const std::uint8_t *frameBuffer) override; + EinkStatus showImageRefresh(const EinkFrame &refreshFrame, const EinkRefreshMode refreshMode) override; + EinkStatus showImage(const std::vector &updateFrames, + const EinkFrame &refreshFrame, + const std::uint8_t *frameBuffer, + const EinkRefreshMode refreshMode) override; void prepareEarlyRequest(EinkRefreshMode refreshMode, const WaveformTemperature behaviour) override; EinkStatus resetAndInit() override; @@ -48,11 +54,8 @@ namespace hal::eink std::int32_t getLastTemperature() const noexcept; - EinkStatus update(std::uint8_t *displayBuffer); - EinkStatus refresh(const EinkDisplayTimingsMode_e refreshMode); - - EinkStatus updateDisplay(std::uint8_t *frameBuffer, const EinkRefreshMode refreshMode); - EinkStatus refreshDisplay(const EinkRefreshMode refreshMode); + EinkStatus updateDisplay(EinkFrame frame, const std::uint8_t *frameBuffer); + EinkStatus refreshDisplay(EinkFrame frame, const EinkRefreshMode refreshMode); EinkStatus prepareDisplay(const EinkRefreshMode refreshMode, const WaveformTemperature behaviour); FrameSize size; diff --git a/module-bsp/board/rt1051/bsp/eink/eink_dimensions.cpp b/module-bsp/board/rt1051/bsp/eink/eink_dimensions.cpp index 89a2b98820561242cfd29a5ca14c7bf8214d07c0..b4852e24a20cea2543a5c5271d71daa65c8e906c 100644 --- a/module-bsp/board/rt1051/bsp/eink/eink_dimensions.cpp +++ b/module-bsp/board/rt1051/bsp/eink/eink_dimensions.cpp @@ -22,7 +22,7 @@ namespace hal::eink #if defined(EINK_ROTATE_90_CLOCKWISE) return BOARD_EINK_DISPLAY_RES_X - frame.pos_x - frame.width; #else - return BOARD_EINK_DISPLAY_RES_Y - frame.pos_y - frame.height; + return frame.pos_y; #endif } diff --git a/module-bsp/hal/include/hal/eink/AbstractEinkDisplay.hpp b/module-bsp/hal/include/hal/eink/AbstractEinkDisplay.hpp index e988dc36cbba181dee2990a46d40f58314f4a6c3..7282681ec26fdbdc669acbc7bec3c9d8f73dbf0f 100644 --- a/module-bsp/hal/include/hal/eink/AbstractEinkDisplay.hpp +++ b/module-bsp/hal/include/hal/eink/AbstractEinkDisplay.hpp @@ -1,7 +1,10 @@ // Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md +#pragma once + #include +#include #include @@ -65,8 +68,14 @@ namespace hal::eink virtual ~AbstractEinkDisplay() = default; - virtual void setMode(const EinkDisplayColorMode mode) noexcept = 0; - virtual EinkStatus showImage(std::uint8_t *frameBuffer, const EinkRefreshMode refreshMode) = 0; + virtual void setMode(const EinkDisplayColorMode mode) noexcept = 0; + virtual EinkStatus showImageUpdate(const std::vector &updateFrames, + const std::uint8_t *frameBuffer) = 0; + virtual EinkStatus showImageRefresh(const EinkFrame &refreshFrame, const EinkRefreshMode refreshMode) = 0; + virtual EinkStatus showImage(const std::vector &updateFrames, + const EinkFrame &refreshFrame, + const std::uint8_t *frameBuffer, + const EinkRefreshMode refreshMode) = 0; virtual void prepareEarlyRequest(EinkRefreshMode refreshMode, const WaveformTemperature behaviour) = 0; virtual void dither() = 0; diff --git a/module-gui/gui/core/BoundingBox.cpp b/module-gui/gui/core/BoundingBox.cpp index 20f35d3f81516c532f1805a08e78667b601f8969..b9a3124037543d43398d770a00f4377c92a17107 100644 --- a/module-gui/gui/core/BoundingBox.cpp +++ b/module-gui/gui/core/BoundingBox.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. +// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md #include "BoundingBox.hpp" @@ -105,7 +105,7 @@ namespace gui return ss.str(); } - void BoundingBox::sum(const BoundingBox &box) + void BoundingBox::expandSize(const BoundingBox &box) { w = box.w > w ? box.w : w; h = box.h > h ? box.h : h; diff --git a/module-gui/gui/core/BoundingBox.hpp b/module-gui/gui/core/BoundingBox.hpp index 35ac86414879abe88c3249e48228ed03a2402d06..055268a4311f2fa87afeb2095f6b3a5a8ae2dd71 100644 --- a/module-gui/gui/core/BoundingBox.hpp +++ b/module-gui/gui/core/BoundingBox.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. +// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md #pragma once @@ -31,8 +31,8 @@ namespace gui Position x = zero_position, y = zero_position; Length w = zero_size, h = zero_size; }; - BoundingBox(Position x = zero_position, Position y = zero_position, Length w = 0, Length h = 0); - virtual ~BoundingBox() = default; + BoundingBox() = default; + BoundingBox(Position x, Position y, Length w, Length h); static bool intersect(const BoundingBox &box1, const BoundingBox &box2, BoundingBox &result); @@ -43,8 +43,9 @@ namespace gui /// get position in axis - in x get x, in y get y Position pos(gui::Axis axis) const; std::string str() const; - /// logical sum of bounding box by another bounding box values - void sum(const BoundingBox &box); + /// assign width and/or height of bigger bounding box + void expandSize(const BoundingBox &box); + bool operator==(const BoundingBox &box) const; bool operator!=(const BoundingBox &box) const; }; diff --git a/module-gui/gui/core/Context.cpp b/module-gui/gui/core/Context.cpp index 067219f7f5b4afde1e531e9222969f82c4225318..5beb4cee3d5206362a9a88517363b42aeb51e916 100644 --- a/module-gui/gui/core/Context.cpp +++ b/module-gui/gui/core/Context.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. +// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md /* @@ -8,35 +8,29 @@ * Author: robert */ -#include -#include - -#include "BoundingBox.hpp" #include "Context.hpp" +#include +#include +#include +#include + namespace gui { - - Context::Context() : x{0}, y{0}, w{0}, h{0}, data{nullptr} - {} - - Context::Context(uint16_t width, uint16_t height) : x{0}, y{0}, w{width}, h{height}, data(new uint8_t[w * h]) + Context::Context(std::uint16_t width, std::uint16_t height) : w{width}, h{height}, data(new std::uint8_t[w * h]) { - memset(data, 15, w * h); + memset(data.get(), clearColor, w * h); } - Context::~Context() + Context Context::get(std::int16_t gx, std::int16_t gy, std::uint16_t width, std::uint16_t height) const { - if (data != nullptr) - delete[] data; - } + Context retContext = Context(width, height); - Context *Context::get(int16_t gx, int16_t gy, uint16_t width, uint16_t height) - { - Context *retContext = new Context(width, height); - - retContext->x = gx; - retContext->y = gy; + // Copy the whole block if context is fully inside and covering the whole width + if (gx == 0 && width == w && gy >= 0 && std::uint16_t(gy + height) <= h) { + memcpy(retContext.data.get(), data.get() + gy * w, w * height); + return retContext; + } // create bounding boxes for the current context and return context BoundingBox currentBox = BoundingBox(0, 0, w, h); @@ -48,7 +42,7 @@ namespace gui Length sourceOffset = resultBox.y * w + resultBox.x; Length destOffset = (resultBox.y - gy) * width + (resultBox.x - gx); for (Length h = 0; h < resultBox.h; h++) { - memcpy(retContext->data + destOffset, data + sourceOffset, resultBox.w); + memcpy(retContext.data.get() + destOffset, data.get() + sourceOffset, resultBox.w); sourceOffset += w; destOffset += width; } @@ -58,27 +52,38 @@ namespace gui return retContext; } - void Context::insert(int16_t ix, int16_t iy, Context *context) + void Context::insert(std::int16_t ix, std::int16_t iy, const Context &context) { + // Copy the whole block if context is fully inside and covering the whole width + if (ix == 0 && context.w == w && iy >= 0 && std::uint16_t(iy + context.h) <= h) { + memcpy(data.get() + iy * w, context.data.get(), w * context.h); + return; + } + // create bounding boxes for the current context and return context BoundingBox currentBox = BoundingBox(0, 0, w, h); - BoundingBox insertBox = BoundingBox(ix, iy, context->w, context->h); + BoundingBox insertBox = BoundingBox(ix, iy, context.w, context.h); BoundingBox resultBox; // if boxes overlap copy part defined by result from current context to the new context. if (BoundingBox::intersect(currentBox, insertBox, resultBox)) { - Length sourceOffset = (resultBox.y - iy) * context->w + (resultBox.x - ix); + Length sourceOffset = (resultBox.y - iy) * context.w + (resultBox.x - ix); Length destOffset = (resultBox.y) * w + (resultBox.x); for (Length h = 0; h < resultBox.h; h++) { - memcpy(data + destOffset, context->data + sourceOffset, resultBox.w); - sourceOffset += context->w; + memcpy(data.get() + destOffset, context.data.get() + sourceOffset, resultBox.w); + sourceOffset += context.w; destOffset += w; } } } - void Context::insertArea( - int16_t ix, int16_t iy, int16_t iareaX, int16_t iareaY, int16_t iareaW, int16_t iareaH, Context *context) + void Context::insertArea(std::int16_t ix, + std::int16_t iy, + std::int16_t iareaX, + std::int16_t iareaY, + std::int16_t iareaW, + std::int16_t iareaH, + const Context &context) { // create bounding boxes for the current context and return context BoundingBox currentBox = BoundingBox(0, 0, w, h); @@ -87,39 +92,98 @@ namespace gui // if boxes overlap copy part defined by result from current context to the new context. if (BoundingBox::intersect(currentBox, insertBox, resultBox)) { - int16_t xBoxOffset = 0; - int16_t yBoxOffset = 0; + std::int16_t xBoxOffset = 0; + std::int16_t yBoxOffset = 0; if (iareaX < 0) xBoxOffset = iareaX; if (iareaY < 0) yBoxOffset = iareaY; - Length sourceOffset = (resultBox.y - iy - yBoxOffset) * context->w + (resultBox.x - ix - xBoxOffset); + Length sourceOffset = (resultBox.y - iy - yBoxOffset) * context.w + (resultBox.x - ix - xBoxOffset); Length destOffset = (resultBox.y) * w + (resultBox.x); for (Length h = 0; h < resultBox.h; h++) { - memcpy(data + destOffset, context->data + sourceOffset, resultBox.w); - sourceOffset += context->w; + memcpy(data.get() + destOffset, context.data.get() + sourceOffset, resultBox.w); + sourceOffset += context.w; destOffset += w; } } } - void Context::fill(uint8_t colour) + struct LRange + { + LRange(std::uint16_t begin, std::uint16_t end) : begin(begin), end(end) + {} + void expand(LRange const &other) + { + begin = std::min(begin, other.begin); + end = std::max(end, other.end); + } + static LRange inversed(std::uint16_t end) + { + return {end, 0}; + } + std::uint16_t begin, end; + }; + + inline BoundingBox makeBoundingBox(const LRange &rangeX, const LRange &rangeY) + { + return {rangeX.begin, + rangeY.begin, + std::uint16_t(rangeX.end - rangeX.begin), + std::uint16_t(rangeY.end - rangeY.begin)}; + } + + // Currently the algorithm only works properly for width divisible by 8 due to the use of 64b integers. + std::deque gui::Context::linesDiffs(const gui::Context &ctx1, const gui::Context &ctx2) + { + using casted_t = std::uint64_t; + const std::uint16_t w = ctx1.getW(); + const std::uint16_t h = ctx1.getH(); + assert(w == ctx2.getW() && h == ctx2.getH() && w % 8 == 0); + const std::uint16_t cw = w / sizeof(casted_t); + const auto data1 = reinterpret_cast(ctx1.getData()); + const auto data2 = reinterpret_cast(ctx2.getData()); + + std::deque result; + LRange rangeY = LRange::inversed(h); + for (std::uint16_t y = 0; y < h; ++y) { + const auto begin1 = data1 + y * cw; + const auto end1 = begin1 + cw; + const auto begin2 = data2 + y * cw; + if (std::mismatch(begin1, end1, begin2).first != end1) { + if (rangeY.begin == h) { // diff pixels found first time + rangeY.begin = y; + } + } + else { + if (rangeY.begin != h) { // diff pixels found before + rangeY.end = y; + result.push_back(makeBoundingBox({0, w}, rangeY)); + rangeY = LRange::inversed(h); + } + } + } + if (rangeY.begin != h) { // diff pixels found before + rangeY.end = h; + result.push_back(makeBoundingBox({0, w}, rangeY)); + } + return result; + } + + void Context::fill(std::uint8_t colour) { if (data) { - memset(data, colour, w * h); + memset(data.get(), colour, w * h); } - // uint32_t size = 480*600; - // memset( data, colour, size ); } std::ostream &operator<<(std::ostream &out, const Context &c) { - out << "x:" << c.x << "y:" << c.y << "w:" << c.w << "h:" << c.h << std::endl; + out << "w:" << c.w << "h:" << c.h << std::endl; - uint32_t offset = 0; - for (uint32_t y = 0; y < c.h; y++) { - for (uint32_t x = 0; x < c.w; x++) { - uint32_t value = *(c.data + offset); + std::uint32_t offset = 0; + for (std::uint32_t y = 0; y < c.h; y++) { + for (std::uint32_t x = 0; x < c.w; x++) { + std::uint32_t value = *(c.data.get() + offset); std::cout << std::hex << value; offset++; } @@ -128,4 +192,37 @@ namespace gui return out; } + std::string Context::toAsciiScaled(std::uint16_t scale) const + { + scale = std::max(std::uint16_t(1), std::min(scale, std::min(w, h))); + + const std::uint16_t pixelsPerChar = scale * scale; + const std::uint16_t sw = w / scale; + const std::uint16_t sh = h / scale; + const std::uint8_t white = 15; + const char *chars = " .,-\"*^:;+=!?%#@"; + + std::vector accum(sw * sh, 0); + for (std::uint16_t j = 0; j < h; ++j) { + for (std::uint16_t i = 0; i < w; ++i) { + const std::uint8_t c = std::min(data[j * w + i], white); + const auto off = (j / scale) * sw + (i / scale); + accum[off] += c; + } + } + + const std::uint16_t sw_nl = sw + 1; + std::string result(sw_nl * sh - 1, '\n'); // last new line is not needed + + for (std::uint16_t j = 0; j < sh; ++j) { + for (std::uint16_t i = 0; i < sw; ++i) { + const auto off_nl = j * sw_nl + i; + const auto off = j * sw + i; + result[off_nl] = chars[accum[off] / pixelsPerChar]; + } + } + + return result; + } + } /* namespace gui */ diff --git a/module-gui/gui/core/Context.hpp b/module-gui/gui/core/Context.hpp index 7d5582af24dd7da99aa55d0992edec86d6f65b9f..475157460168a55c7af57db82e1436acd3a28c7e 100644 --- a/module-gui/gui/core/Context.hpp +++ b/module-gui/gui/core/Context.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. +// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md /* @@ -11,83 +11,100 @@ #ifndef GUI_CORE_CONTEXT_HPP_ #define GUI_CORE_CONTEXT_HPP_ +#include "BoundingBox.hpp" + +#include "module-gui/gui/Common.hpp" + #include +#include #include -#include "module-gui/gui/Common.hpp" +#include +#include namespace gui { - class Context { - protected: - int16_t x, y; - uint32_t w, h; - uint8_t *data; + static constexpr std::uint8_t clearColor = 15; public: - Context(); - Context(uint16_t width, uint16_t height); - virtual ~Context(); + Context() = default; + Context(std::uint16_t width, std::uint16_t height); /** - * @brief Creates new context using provided coordinates. If there is no common part Context with 0 width and 0 - * height is returned. + * @brief Creates new context using provided coordinates. If there is no common part Context filled with + * clearColor is returned. The size of the returned context is defined by parameters, so it may represent + * area outside the original context. */ - Context *get(int16_t gx, int16_t gy, uint16_t width, uint16_t height); + Context get(std::int16_t gx, std::int16_t gy, std::uint16_t width, std::uint16_t height) const; /** * @brief Pastes provided context into current one. Overlapping content will be inserted into current context. */ - void insert(int16_t ix, int16_t iy, Context *context); + void insert(std::int16_t ix, std::int16_t iy, const Context &context); /** * @brief Pastes provided context into current one. Overlapping content will be inserted into current context. */ - void insertArea( - int16_t ix, int16_t iy, int16_t iareaX, int16_t iareaY, int16_t iareaW, int16_t iareaH, Context *context); + void insertArea(std::int16_t ix, + std::int16_t iy, + std::int16_t iareaX, + std::int16_t iareaY, + std::int16_t iareaW, + std::int16_t iareaH, + const Context &context); + + /** + * @brief Calculate regions of difference between contexts. Each bounding box covers the whole width of the + * context. They are disjoint and sorted by y coordinate. The contexts has to have the same sizes. + */ + static std::deque linesDiffs(const gui::Context &ctx1, const gui::Context &ctx2); + /** * @brief Fills whole context with specified colour; */ - void fill(uint8_t colour); + void fill(std::uint8_t colour); /** * @brief returns pointer to context's data; */ - inline const uint8_t *getData() const + inline const std::uint8_t *getData() const { - return data; + return data.get(); } - inline uint8_t *getData() + inline std::uint8_t *getData() { - return data; + return data.get(); } - inline int16_t getX() const - { - return x; - } - inline int16_t getY() const - { - return y; - } - inline uint16_t getW() const + inline std::uint16_t getW() const { return w; } - inline uint16_t getH() const + inline std::uint16_t getH() const { return h; } + inline BoundingBox getBoundingBox() const + { + return {0, 0, w, h}; + } - inline bool addressInData(const uint8_t *ptr) const + inline std::uint8_t getPixel(const Point point, uint8_t defaultColor = clearColor) const { - return (ptr >= data) && (ptr < data + w * h); + return hasPixel(point) ? data[point.y * w + point.x] : defaultColor; } - inline bool addressInData(const Point point) const noexcept + inline bool hasPixel(const Point point) const noexcept { - return (point.x >= 0 && static_cast(point.x) <= w) && - (point.y >= 0 && static_cast(point.y) <= h); + return (point.x >= 0 && static_cast(point.x) < w) && + (point.y >= 0 && static_cast(point.y) < h); } friend std::ostream &operator<<(std::ostream &out, const Context &c); + + std::string toAsciiScaled(std::uint16_t scale = 15) const; + + private: + std::uint16_t w = 0; + std::uint16_t h = 0; + std::unique_ptr data; }; } /* namespace gui */ diff --git a/module-gui/gui/core/DrawCommand.cpp b/module-gui/gui/core/DrawCommand.cpp index 630e6965f9a88480a02da2ff7108614b8b104d3e..ed4b8139bac41b45446118b830c0542c8c068a81 100644 --- a/module-gui/gui/core/DrawCommand.cpp +++ b/module-gui/gui/core/DrawCommand.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. +// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md #include "DrawCommand.hpp" @@ -56,8 +56,9 @@ namespace gui return; } + Context tempContext; Context *drawingContext = ctx; - Point position; + Point position(0, 0); if (yaps & (RectangleYap::BottomLeft | RectangleYap::TopLeft)) { position.x += yapSize; @@ -76,7 +77,8 @@ namespace gui else { const auto xCtx = areaX < 0 ? origin.x + areaX : origin.x; const auto yCtx = areaY < 0 ? origin.y + areaY : origin.y; - drawingContext = ctx->get(xCtx, yCtx, areaW, areaH); + tempContext = ctx->get(xCtx, yCtx, areaW, areaH); + drawingContext = &tempContext; } if (radius == 0) { @@ -89,8 +91,7 @@ namespace gui } if (drawingContext != ctx) { - ctx->insertArea(origin.x, origin.y, areaX, areaY, width, height, drawingContext); - delete drawingContext; + ctx->insertArea(origin.x, origin.y, areaX, areaY, width, height, tempContext); } } @@ -116,7 +117,7 @@ namespace gui for (position.y = glyphOrigin.y - glyph->yoffset; position.y < glyphMaxY; ++position.y) { for (position.x = glyphOrigin.x; position.x < glyphMaxX; ++position.x) { - if (!ctx->addressInData(position)) { + if (!ctx->hasPixel(position)) { log_warn_glyph( "drawing out of: {x=%d,y=%d} vs {w=%d,h=%d}", position.x, position.y, ctx->getW(), ctx->getH()); return; @@ -138,18 +139,17 @@ namespace gui } // get copy of original context using x,y of draw coordinates and original size of the widget - Context *drawCtx; + Context ctxCopy; bool copyContext = false; Point widgetOrigin = {0, 0}; // check if there is a need of making copy of context to use it as background if ((areaW == width) && (areaH == height)) { - drawCtx = ctx; widgetOrigin = origin; } else { copyContext = true; - drawCtx = ctx->get(origin.x, origin.y, areaW, areaH); + ctxCopy = ctx->get(origin.x, origin.y, areaW, areaH); } // retrieve font used to draw text @@ -164,6 +164,7 @@ namespace gui FontGlyph *glyph = font->getGlyph(idCurrent); // do not start drawing outside of draw context. + Context *drawCtx = copyContext ? &ctxCopy : ctx; if ((widgetOrigin.x + position.x + glyph->xoffset >= drawCtx->getW()) || (widgetOrigin.x + position.x + glyph->xoffset < 0)) { LOG_FATAL("Drawing outside context's X boundary for glyph: %" PRIu32, glyph->id); @@ -188,10 +189,9 @@ namespace gui } // if drawing was performed in temporary context - // reinsert drawCtx into bast context + // reinsert drawCtx into base context if (copyContext) { - ctx->insert(origin.x, origin.y, drawCtx); - delete drawCtx; + ctx->insert(origin.x, origin.y, ctxCopy); } } @@ -269,23 +269,20 @@ namespace gui } // get copy of original context using x,y of draw coordinates and original size of the widget - Context *drawCtx = ctx->get(origin.x, origin.y, areaW, areaH); + Context drawCtx = ctx->get(origin.x, origin.y, areaW, areaH); if (imageMap->getType() == gui::ImageMap::Type::PIXMAP) { auto pixMap = dynamic_cast(imageMap); assert(pixMap); - drawPixMap(drawCtx, pixMap); + drawPixMap(&drawCtx, pixMap); } else if (imageMap->getType() == gui::ImageMap::Type::VECMAP) { auto vecMap = dynamic_cast(imageMap); assert(vecMap); - drawVecMap(drawCtx, vecMap); + drawVecMap(&drawCtx, vecMap); } // reinsert drawCtx into bast context ctx->insert(origin.x, origin.y, drawCtx); - - // remove draw context - delete drawCtx; } } /* namespace gui */ diff --git a/module-gui/gui/core/ImageManager.cpp b/module-gui/gui/core/ImageManager.cpp index b93c8e94a5b0335145c70aa31b7c75f7503ec04f..b44edcf01179a65a6e6d4657aac851f1f1bcd782 100644 --- a/module-gui/gui/core/ImageManager.cpp +++ b/module-gui/gui/core/ImageManager.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. +// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md #include "ImageManager.hpp" @@ -176,31 +176,27 @@ namespace gui // Creation of square with crossed lines as fallback image constexpr auto squareWidth = 15; - std::list> commands; - auto rectangle = std::make_unique(); - rectangle->origin = {0, 0}; - rectangle->width = squareWidth; - rectangle->height = squareWidth; - rectangle->areaX = 0; - rectangle->areaY = 0; - rectangle->areaW = squareWidth; - rectangle->areaH = squareWidth; - commands.emplace_back(std::move(rectangle)); - - auto line1 = std::make_unique(); - line1->start = {0, 0}; - line1->end = {squareWidth, squareWidth}; - commands.emplace_back(std::move(line1)); - - auto line2 = std::make_unique(); - line2->start = {squareWidth - 1, 0}; - line2->end = {0, squareWidth - 1}; - commands.emplace_back(std::move(line2)); - - auto renderContext = std::make_unique(squareWidth, squareWidth); - Renderer().render(renderContext.get(), commands); - - return new PixMap(squareWidth, squareWidth, renderContext->getData()); + DrawRectangle rectangle; + rectangle.origin = {0, 0}; + rectangle.width = squareWidth; + rectangle.height = squareWidth; + rectangle.areaX = 0; + rectangle.areaY = 0; + rectangle.areaW = squareWidth; + rectangle.areaH = squareWidth; + + DrawLine line1; + line1.start = {0, 0}; + line1.end = {squareWidth, squareWidth}; + + DrawLine line2; + line2.start = {squareWidth - 1, 0}; + line2.end = {0, squareWidth - 1}; + + Context renderContext(squareWidth, squareWidth); + Renderer().render(renderContext, rectangle, line1, line2); + + return new PixMap(squareWidth, squareWidth, renderContext.getData()); } auto ImageManager::getImageMapList(std::string ext1, std::string ext2) diff --git a/module-gui/gui/core/RawFont.cpp b/module-gui/gui/core/RawFont.cpp index c3428fb9ad3941aa83a5d5acf153e4c4233b0b3e..3b46849b402e3ef5435a65dcdec3360b47925636 100644 --- a/module-gui/gui/core/RawFont.cpp +++ b/module-gui/gui/core/RawFont.cpp @@ -239,24 +239,22 @@ namespace gui unsupported->xadvance = unsupported->width + (2 * unsupported->xoffset); // use xoffset as margins on the left/right of the glyph // populate with a bitmap (glyph) - auto commandRect = std::make_unique(); - commandRect->origin = {0, 0}; - commandRect->width = unsupported->width; - commandRect->height = unsupported->height; - commandRect->areaX = 0; - commandRect->areaY = 0; - commandRect->areaW = unsupported->width; - commandRect->areaH = unsupported->height; - commandRect->penWidth = unsupported->xoffset; - - auto renderCtx = std::make_unique(unsupported->width, unsupported->height); - std::list> commands; - commands.emplace_back(std::move(commandRect)); - Renderer().render(renderCtx.get(), commands); + DrawRectangle commandRect; + commandRect.origin = {0, 0}; + commandRect.width = unsupported->width; + commandRect.height = unsupported->height; + commandRect.areaX = 0; + commandRect.areaY = 0; + commandRect.areaW = unsupported->width; + commandRect.areaH = unsupported->height; + commandRect.penWidth = unsupported->xoffset; + + Context renderCtx(unsupported->width, unsupported->height); + Renderer().render(renderCtx, commandRect); auto size = unsupported->width * unsupported->height; unsupported->data = new uint8_t[size]; - std::memcpy(unsupported->data, renderCtx->getData(), size); + std::memcpy(unsupported->data, renderCtx.getData(), size); } void RawFont::setFallbackFont(RawFont *fallback) diff --git a/module-gui/gui/core/Renderer.cpp b/module-gui/gui/core/Renderer.cpp index 05773a63aba1a91004949467ab6e490467c4f81d..a023a56ac7643295eb830f3f69aea128a804341e 100644 --- a/module-gui/gui/core/Renderer.cpp +++ b/module-gui/gui/core/Renderer.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. +// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md // for memset @@ -10,12 +10,12 @@ namespace gui { - void Renderer::changeColorScheme(const std::unique_ptr &scheme) + void Renderer::changeColorScheme(const std::unique_ptr &scheme) const { renderer::PixelRenderer::updateColorScheme(scheme); } - void Renderer::render(Context *ctx, std::list> &commands) + void Renderer::render(Context *ctx, const std::list> &commands) const { if (ctx == nullptr) { return; diff --git a/module-gui/gui/core/Renderer.hpp b/module-gui/gui/core/Renderer.hpp index 797c14d31d1405bc342612a2aee5964e07f17a4c..05e2d4e03aff9dce2062a879a9e5d3fbe13ddb26 100644 --- a/module-gui/gui/core/Renderer.hpp +++ b/module-gui/gui/core/Renderer.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. +// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md #pragma once @@ -34,10 +34,14 @@ namespace gui */ public: - virtual ~Renderer() = default; - - void render(Context *ctx, std::list> &commands); - void changeColorScheme(const std::unique_ptr &scheme); + void changeColorScheme(const std::unique_ptr &scheme) const; + void render(Context *ctx, const std::list> &commands) const; + + template + void render(Context &ctx, const Commands &...commands) const + { + (..., commands.draw(&ctx)); + } }; } /* namespace gui */ diff --git a/module-gui/gui/core/renderers/RectangleRenderer.cpp b/module-gui/gui/core/renderers/RectangleRenderer.cpp index 30905129ad07217128a04e08e07caab15e63e73a..fcada59c687a99d19d501e761b2ce50201db305b 100644 --- a/module-gui/gui/core/renderers/RectangleRenderer.cpp +++ b/module-gui/gui/core/renderers/RectangleRenderer.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. +// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md #include "RectangleRenderer.hpp" @@ -13,19 +13,6 @@ namespace gui::renderer { - namespace - { - std::uint8_t getPixelColor(Context *ctx, const Point &point, std::uint8_t defaultColor) - { - const auto contextWidth = ctx->getW(); - const auto position = point.y * contextWidth + point.x; - if (!ctx->addressInData(ctx->getData() + position)) { - return defaultColor; - } - return *(ctx->getData() + position); - } - } // namespace - auto RectangleRenderer::DrawableStyle::from(const DrawRectangle &command) -> DrawableStyle { return DrawableStyle{command.penWidth, @@ -183,7 +170,7 @@ namespace gui::renderer while (!q.empty()) { const auto currPoint = q.front(); q.pop(); - if (const auto color = getPixelColor(ctx, currPoint, PixelRenderer::getColor(borderColor.intensity)); + if (const auto color = ctx->getPixel(currPoint, PixelRenderer::getColor(borderColor.intensity)); color == PixelRenderer::getColor(borderColor.intensity) || color == PixelRenderer::getColor(fillColor.intensity)) { continue; diff --git a/module-gui/gui/widgets/Item.cpp b/module-gui/gui/widgets/Item.cpp index d6e5a4d9942a63702e3b891e98565106058016cb..d507ae066073045e48be213cccfd983828ddcb85 100644 --- a/module-gui/gui/widgets/Item.cpp +++ b/module-gui/gui/widgets/Item.cpp @@ -136,7 +136,7 @@ namespace gui { BoundingBox oldArea = widgetArea; widgetArea = area; - widgetMaximumArea.sum(widgetArea); + widgetMaximumArea.expandSize(widgetArea); contentChanged = false; updateDrawArea(); onDimensionChanged(oldArea, widgetArea); diff --git a/module-gui/gui/widgets/Rect.cpp b/module-gui/gui/widgets/Rect.cpp index 0085611be8f94b8a2b2184fe711f80970f6d7845..8975425ec7148eb9315f83a844c8d35ed18e4489 100644 --- a/module-gui/gui/widgets/Rect.cpp +++ b/module-gui/gui/widgets/Rect.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. +// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md /* diff --git a/module-gui/test/test-catch/test-context.cpp b/module-gui/test/test-catch/test-context.cpp index 49fcd8c4b9ef198e92b0af9de4f3fbf55c2930a2..593733404269a5e97d158b4c3a83b09f4eb6dba5 100644 --- a/module-gui/test/test-catch/test-context.cpp +++ b/module-gui/test/test-catch/test-context.cpp @@ -14,77 +14,63 @@ TEST_CASE("test context size and position") { - auto ctx = new gui::Context(30, 30); - ctx->fill(0); + auto ctx = gui::Context(30, 30); + ctx.fill(0); - gui::Context *test = ctx->get(17, 17, 30, 30); + gui::Context test = ctx.get(17, 17, 30, 30); - delete test; - test = ctx->get(-17, -17, 30, 30); - delete test; - - delete ctx; + test = ctx.get(-17, -17, 30, 30); } TEST_CASE("insertContextTest") { - gui::Context *dstCtx; - gui::Context *insCtx; + gui::Context dstCtx; + gui::Context insCtx; SECTION("RECTANGLE INSIDE") { - dstCtx = new gui::Context(30, 30); - dstCtx->fill(0); - insCtx = new gui::Context(static_cast(28), static_cast(28)); - insCtx->fill(15); - dstCtx->insert(1, 1, insCtx); - delete dstCtx; - delete insCtx; + dstCtx = gui::Context(30, 30); + dstCtx.fill(0); + insCtx = gui::Context(28, 28); + insCtx.fill(15); + dstCtx.insert(1, 1, insCtx); } SECTION("2 COLUMNS ON RIGHT SIDE, TOP AND BOTTOM ROW UNTOUCHED") { - dstCtx = new gui::Context(static_cast(30), static_cast(30)); - dstCtx->fill(0); - insCtx = new gui::Context(static_cast(28), static_cast(28)); - insCtx->fill(15); - dstCtx->insert(28, 1, insCtx); - delete dstCtx; - delete insCtx; + dstCtx = gui::Context(30, 30); + dstCtx.fill(0); + insCtx = gui::Context(28, 28); + insCtx.fill(15); + dstCtx.insert(28, 1, insCtx); } SECTION("2 COLUMNS ON LEFT SIDE, TOP AND BOTTOM ROW UNTOUCHED") { - dstCtx = new gui::Context(static_cast(30), static_cast(30)); - dstCtx->fill(0); - insCtx = new gui::Context(static_cast(28), static_cast(28)); - insCtx->fill(15); - dstCtx->insert(-26, 1, insCtx); - delete dstCtx; - delete insCtx; + dstCtx = gui::Context(30, 30); + dstCtx.fill(0); + insCtx = gui::Context(28, 28); + insCtx.fill(15); + dstCtx.insert(-26, 1, insCtx); } SECTION("2 COLUMNS ON RIGHT SIDE") { - dstCtx = new gui::Context(static_cast(30), static_cast(30)); - dstCtx->fill(0); - insCtx = new gui::Context(static_cast(32), static_cast(32)); - insCtx->fill(15); - dstCtx->insert(28, -1, insCtx); - delete dstCtx; - delete insCtx; + dstCtx = gui::Context(30, 30); + dstCtx.fill(0); + insCtx = gui::Context(32, 32); + insCtx.fill(15); + dstCtx.insert(28, -1, insCtx); } SECTION("2 COLUMNS ON LEFT SIDE") { - dstCtx = new gui::Context(static_cast(30), static_cast(30)); - dstCtx->fill(0); - insCtx = new gui::Context(static_cast(32), static_cast(32)); - insCtx->fill(15); - dstCtx->insert(-30, -1, insCtx); - delete dstCtx; - delete insCtx; + dstCtx = gui::Context(30, 30); + dstCtx.fill(0); + insCtx = gui::Context(32, 32); + insCtx.fill(15); + dstCtx.insert(-30, -1, insCtx); } } @@ -124,7 +110,7 @@ TEST_CASE("Draw vector image test") "ffffffffffffffffffffffffffffffff\n" "ffffffffffffffffffffffffffffffff\n"; - auto context = std::make_unique(static_cast(32), static_cast(32)); + auto context = gui::Context(32, 32); gui::ImageManager::getInstance().init("."); auto id = gui::ImageManager::getInstance().getImageMapID("plus_32px_W_M"); @@ -138,13 +124,13 @@ TEST_CASE("Draw vector image test") drawCommand.imageID = id; drawCommand.areaH = 32; drawCommand.areaW = 32; - drawCommand.draw(context.get()); + drawCommand.draw(&context); std::string dump; - uint32_t offset = 0; - for (uint32_t y = 0; y < context->getH(); y++) { - for (uint32_t x = 0; x < context->getW(); x++) { - uint32_t value = *(context->getData() + offset); + std::uint32_t offset = 0; + for (std::uint32_t y = 0; y < context.getH(); y++) { + for (std::uint32_t x = 0; x < context.getW(); x++) { + std::uint32_t value = *(context.getData() + offset); std::stringstream stream; stream << std::hex << value; dump.append(stream.str()); diff --git a/module-services/service-eink/ServiceEink.cpp b/module-services/service-eink/ServiceEink.cpp index e44221ddf5cfed18d2ddfc3482d384126701f944..9c83dc8a397d8474df057b7af0bf59634476d358 100644 --- a/module-services/service-eink/ServiceEink.cpp +++ b/module-services/service-eink/ServiceEink.cpp @@ -22,6 +22,8 @@ #include #include "Utils.hpp" +#include + namespace service::eink { namespace @@ -67,6 +69,12 @@ namespace service::eink connect(typeid(ImageMessage), [this](sys::Message *request) -> sys::MessagePointer { return handleImageMessage(request); }); + connect(typeid(RefreshMessage), + [this](sys::Message *request) -> sys::MessagePointer { return handleRefreshMessage(request); }); + + connect(typeid(CancelRefreshMessage), + [this](sys::Message *request) -> sys::MessagePointer { return handleCancelRefreshMessage(request); }); + connect(typeid(PrepareDisplayEarlyRequest), [this](sys::Message *request) -> sys::MessagePointer { return handlePrepareEarlyRequest(request); }); @@ -175,27 +183,257 @@ namespace service::eink display->setMode(hal::eink::EinkDisplayColorMode::EinkDisplayColorModeStandard); } internal::StaticData::get().setInvertedMode(invertedModeRequested); + + previousContext.reset(); + previousRefreshMode = hal::eink::EinkRefreshMode::REFRESH_NONE; + } + +#if DEBUG_EINK_REFRESH == 1 + inline std::string debug_toString(const gui::BoundingBox &bb) + { + return bb.str(); + } + + inline std::string debug_toString(const hal::eink::EinkFrame &f) + { + std::stringstream ss; + ss << '{' << f.pos_x << ' ' << f.pos_y << ' ' << f.size.width << ' ' << f.size.height << "}"; + return ss.str(); + } + + template + inline void debug_handleImageMessage(const char *name, const Container &container) + { + std::stringstream ss; + for (auto const &el : container) + ss << debug_toString(el) << ' '; + LOG_INFO("%s: %s", name, ss.str().c_str()); + } +#endif + + // Merge boxes if the gap between them is smaller than the threshold + template + inline auto mergeBoundingBoxes(const BoxesContainer &boxes, std::uint16_t gapThreshold) + { + std::vector mergedBoxes; + if (boxes.empty()) + return mergedBoxes; + mergedBoxes.reserve(boxes.size()); + gui::BoundingBox merged = boxes.front(); + for (std::size_t i = 1; i < boxes.size(); ++i) { + const auto &bb = boxes[i]; + const auto gap = bb.y - (merged.y + merged.h); + if (gap < gapThreshold) { + merged.h = (bb.y + bb.h) - merged.y; + } + else { + mergedBoxes.push_back(merged); + merged = bb; + } + } + mergedBoxes.push_back(merged); + return mergedBoxes; } + // Enlarge each box to match alignment-wide grid in y coordinate + template + inline auto makeAlignedFrames(const BoxesContainer &boxes, std::uint16_t alignment) + { + std::vector frames; + if (boxes.empty()) + return frames; + frames.reserve(boxes.size()); + auto a = alignment; + for (const auto &bb : boxes) { + auto f = hal::eink::EinkFrame{ + std::uint16_t(bb.x), std::uint16_t(bb.y), {std::uint16_t(bb.w), std::uint16_t(bb.h)}}; + auto y = f.pos_y; + auto h = f.size.height; + f.pos_y = y / a * a; + f.size.height = (y - f.pos_y + h + (a - 1)) / a * a; + frames.push_back(f); + } + return frames; + } + + // Return frames representing difference of contexts + inline auto calculateUpdateFrames(const gui::Context &context, const gui::Context &previousContext) + { + std::vector updateFrames; + // Each bounding box cover the whole width of the context. They are disjoint and sorted by y coordinate. + const auto diffBoundingBoxes = gui::Context::linesDiffs(context, previousContext); + if (!diffBoundingBoxes.empty()) { + const std::uint16_t gapThreshold = context.getH() / 4; + const std::uint16_t alignment = 8; + + const auto mergedBoxes = mergeBoundingBoxes(diffBoundingBoxes, gapThreshold); + updateFrames = makeAlignedFrames(mergedBoxes, alignment); + +#if DEBUG_EINK_REFRESH == 1 + debug_handleImageMessage("Diff boxes", diffBoundingBoxes); + debug_handleImageMessage("Merged boxes", mergedBoxes); +#endif + } + return updateFrames; + } + + inline auto expandFrame(hal::eink::EinkFrame &frame, const hal::eink::EinkFrame &other) + { + const auto x = std::min(frame.pos_x, other.pos_x); + const auto y = std::min(frame.pos_y, other.pos_y); + const auto xmax1 = frame.pos_x + frame.size.width; + const auto xmax2 = other.pos_x + other.size.width; + const auto w = std::max(xmax1, xmax2) - x; + const auto ymax1 = frame.pos_y + frame.size.height; + const auto ymax2 = other.pos_y + other.size.height; + const auto h = std::max(ymax1, ymax2) - y; + frame.pos_x = x; + frame.pos_y = y; + frame.size.width = w; + frame.size.height = h; + } + +#if DEBUG_EINK_REFRESH == 1 + TickType_t tick1, tick2, tick3; +#endif + sys::MessagePointer ServiceEink::handleImageMessage(sys::Message *request) { +#if DEBUG_EINK_REFRESH == 1 + tick1 = xTaskGetTickCount(); +#endif + const auto message = static_cast(request); if (isInState(State::Suspended)) { LOG_WARN("Received image while suspended, ignoring"); return sys::MessageNone{}; } - utils::time::Scoped measurement("ImageMessage"); displayPowerOffTimer.stop(); auto displayPowerOffTimerReload = gsl::finally([this]() { displayPowerOffTimer.start(); }); - eInkSentinel->HoldMinimumFrequency(); - auto status = display->showImage(message->getData(), translateToEinkRefreshMode(message->getRefreshMode())); - if (status != hal::eink::EinkStatus::EinkOK) { - LOG_ERROR("Error during drawing image on eink: %s", magic_enum::enum_name(status).data()); + const gui::Context &ctx = *message->getContext(); + auto refreshMode = translateToEinkRefreshMode(message->getRefreshMode()); + + // Calculate update and refresh frames based on areas changed since last update + std::vector updateFrames; + hal::eink::EinkFrame refreshFrame{0, 0, {ctx.getW(), ctx.getH()}}; + bool isRefreshRequired = true; + if (!previousContext) { + updateFrames = {refreshFrame}; + previousContext.reset(new gui::Context(ctx.get(0, 0, ctx.getW(), ctx.getH()))); } + else { + updateFrames = calculateUpdateFrames(ctx, *previousContext); + if (refreshMode > previousRefreshMode) { + previousContext->insert(0, 0, ctx); + } + else if (updateFrames.empty()) { + isRefreshRequired = false; + } + else { + refreshFrame = updateFrames.front(); + refreshFrame.size.height = + updateFrames.back().pos_y + updateFrames.back().size.height - updateFrames.front().pos_y; + previousContext->insert(0, 0, ctx); + } + } + + // If parts of the screen were changed, update eink + bool isImageUpdated = false; + if (!updateFrames.empty()) { + +#if DEBUG_EINK_REFRESH == 1 + debug_handleImageMessage("Update frames", updateFrames); + debug_handleImageMessage("Refresh frame", std::vector({refreshFrame})); +#endif + + eInkSentinel->HoldMinimumFrequency(); + + auto status = display->showImageUpdate(updateFrames, ctx.getData()); + if (status == hal::eink::EinkStatus::EinkOK) { + isImageUpdated = true; + } + else { + previousContext.reset(); + refreshMode = hal::eink::EinkRefreshMode::REFRESH_NONE; + LOG_ERROR("Error during drawing image on eink: %s", magic_enum::enum_name(status).data()); + } + } + + previousRefreshMode = refreshMode; + +#if DEBUG_EINK_REFRESH == 1 + LOG_INFO("Update contextId: %d, mode: %d", (int)message->getContextId(), (int)refreshMode); + tick2 = xTaskGetTickCount(); + LOG_INFO("Time to update: %d", (int)(tick2 - tick1)); +#endif + + // Refresh is required if: + // - there are parts of the screen that were changed + // - deep refresh is requested after fast refresh even if nothing has changed + // - previous refresh was canceled + if (isImageUpdated || isRefreshRequired || isRefreshFramesSumValid) { + if (refreshMode > refreshModeSum) { + refreshModeSum = refreshMode; + } + if (!isRefreshFramesSumValid) { + refreshFramesSum = refreshFrame; + isRefreshFramesSumValid = true; + } + else { + expandFrame(refreshFramesSum, refreshFrame); + } + einkDisplayState = EinkDisplayState::NeedRefresh; + auto msg = std::make_shared( + message->getContextId(), refreshFrame, refreshMode, message->sender); + bus.sendUnicast(msg, this->GetName()); + return sys::MessageNone{}; + } + else { + einkDisplayState = EinkDisplayState::Idle; + return std::make_shared(message->getContextId()); + } + } - return std::make_shared(message->getContextId()); + sys::MessagePointer ServiceEink::handleRefreshMessage(sys::Message *request) + { + const auto message = static_cast(request); + + if (einkDisplayState == EinkDisplayState::NeedRefresh) { + const auto status = display->showImageRefresh(refreshFramesSum, refreshModeSum); + if (status != hal::eink::EinkStatus::EinkOK) { + previousContext.reset(); + previousRefreshMode = hal::eink::EinkRefreshMode::REFRESH_NONE; + LOG_ERROR("Error during drawing image on eink: %s", magic_enum::enum_name(status).data()); + } + + einkDisplayState = EinkDisplayState::Idle; + isRefreshFramesSumValid = false; + refreshModeSum = hal::eink::EinkRefreshMode::REFRESH_NONE; + +#if DEBUG_EINK_REFRESH == 1 + LOG_INFO("Refresh contextId: %d, mode: %d", message->getContextId(), (int)message->getRefreshMode()); +#endif + } + +#if DEBUG_EINK_REFRESH == 1 + tick3 = xTaskGetTickCount(); + LOG_INFO("Time to refresh: %d", (int)(tick3 - tick1)); +#endif + + auto msg = std::make_shared(message->getContextId()); + bus.sendUnicast(msg, message->getOriginalSender()); + + return sys::MessageNone{}; + } + + sys::MessagePointer ServiceEink::handleCancelRefreshMessage(sys::Message *request) + { + LOG_INFO("Refresh cancel"); + if (einkDisplayState == EinkDisplayState::NeedRefresh) + einkDisplayState = EinkDisplayState::Canceled; + return sys::MessageNone{}; } sys::MessagePointer ServiceEink::handlePrepareEarlyRequest(sys::Message *message) diff --git a/module-services/service-eink/ServiceEink.hpp b/module-services/service-eink/ServiceEink.hpp index 5c64b171a2a03ce30e56a722a385486bd793f259..5a92c706b01e36327ba4f9887a4b8cafd53d0342 100644 --- a/module-services/service-eink/ServiceEink.hpp +++ b/module-services/service-eink/ServiceEink.hpp @@ -3,6 +3,7 @@ #pragma once +#include #include #include #include @@ -48,6 +49,13 @@ namespace service::eink Suspended }; + enum class EinkDisplayState + { + Idle, + NeedRefresh, + Canceled + }; + void setState(State state) noexcept; bool isInState(State state) const noexcept; @@ -58,6 +66,8 @@ namespace service::eink sys::MessagePointer handleEinkModeChangedMessage(sys::Message *message); sys::MessagePointer handleImageMessage(sys::Message *message); + sys::MessagePointer handleRefreshMessage(sys::Message *message); + sys::MessagePointer handleCancelRefreshMessage(sys::Message *message); sys::MessagePointer handlePrepareEarlyRequest(sys::Message *message); void initStaticData(); @@ -68,6 +78,13 @@ namespace service::eink sys::TimerHandle displayPowerOffTimer; std::shared_ptr eInkSentinel; std::unique_ptr settings; + + std::unique_ptr<::gui::Context> previousContext; + hal::eink::EinkRefreshMode previousRefreshMode = hal::eink::EinkRefreshMode::REFRESH_NONE; + EinkDisplayState einkDisplayState = EinkDisplayState::Idle; + hal::eink::EinkFrame refreshFramesSum; + hal::eink::EinkRefreshMode refreshModeSum = hal::eink::EinkRefreshMode::REFRESH_NONE; + bool isRefreshFramesSumValid = false; }; } // namespace service::eink diff --git a/module-services/service-eink/messages/ImageMessage.cpp b/module-services/service-eink/messages/ImageMessage.cpp index 7225d769f0681c49b42f2e23285ae4d8b6ff980f..ab81dd10fd9083cb2ef1d4ebcd229cc2cc69cd41 100644 --- a/module-services/service-eink/messages/ImageMessage.cpp +++ b/module-services/service-eink/messages/ImageMessage.cpp @@ -10,9 +10,9 @@ namespace service::eink : contextId{contextId}, context{context}, refreshMode{refreshMode} {} - auto ImageMessage::getData() noexcept -> std::uint8_t * + auto ImageMessage::getContext() noexcept -> ::gui::Context * { - return context->getData(); + return context; } auto ImageMessage::getRefreshMode() const noexcept -> ::gui::RefreshModes @@ -32,4 +32,29 @@ namespace service::eink { return contextId; } + + RefreshMessage::RefreshMessage(int contextId, + hal::eink::EinkFrame refreshFrame, + hal::eink::EinkRefreshMode refreshMode, + const std::string &originalSender) + : contextId(contextId), refreshFrame(refreshFrame), refreshMode(refreshMode), originalSender(originalSender) + {} + + auto RefreshMessage::getContextId() const noexcept -> int + { + return contextId; + } + auto RefreshMessage::getRefreshFrame() noexcept -> hal::eink::EinkFrame + { + return refreshFrame; + } + auto RefreshMessage::getRefreshMode() const noexcept -> hal::eink::EinkRefreshMode + { + return refreshMode; + } + auto RefreshMessage::getOriginalSender() const noexcept -> const std::string & + { + return originalSender; + } + } // namespace service::eink diff --git a/module-services/service-eink/messages/ImageMessage.hpp b/module-services/service-eink/messages/ImageMessage.hpp index 1b0d32297349d6fc95ccda062c3d23679e4a40f8..8e2a8fdcdf237b46fd40fc95d54aebacaf9f2ccf 100644 --- a/module-services/service-eink/messages/ImageMessage.hpp +++ b/module-services/service-eink/messages/ImageMessage.hpp @@ -1,14 +1,16 @@ -// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. +// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md #pragma once #include "EinkMessage.hpp" -#include +#include #include #include +#include + namespace service::eink { class ImageMessage : public EinkMessage @@ -17,7 +19,7 @@ namespace service::eink ImageMessage(int contextId, ::gui::Context *context, ::gui::RefreshModes refreshMode); [[nodiscard]] auto getContextId() const noexcept -> int; - [[nodiscard]] auto getData() noexcept -> std::uint8_t *; + [[nodiscard]] auto getContext() noexcept -> ::gui::Context *; [[nodiscard]] auto getRefreshMode() const noexcept -> ::gui::RefreshModes; private: @@ -42,4 +44,28 @@ namespace service::eink private: int contextId; }; + + class RefreshMessage : public EinkMessage + { + public: + RefreshMessage(int contextId, + hal::eink::EinkFrame refreshBox, + hal::eink::EinkRefreshMode refreshMode, + const std::string &originalSender); + + [[nodiscard]] auto getContextId() const noexcept -> int; + [[nodiscard]] auto getRefreshFrame() noexcept -> hal::eink::EinkFrame; + [[nodiscard]] auto getRefreshMode() const noexcept -> hal::eink::EinkRefreshMode; + [[nodiscard]] auto getOriginalSender() const noexcept -> const std::string &; + + private: + int contextId; + hal::eink::EinkFrame refreshFrame; + hal::eink::EinkRefreshMode refreshMode; + std::string originalSender; + }; + + class CancelRefreshMessage : public EinkMessage + {}; + } // namespace service::eink diff --git a/module-services/service-gui/ServiceGUI.cpp b/module-services/service-gui/ServiceGUI.cpp index a8296500b447fa1c642d88b722d4f819812d5d39..f0dd9512730078a0f46b4b8815ed11cb75558171 100644 --- a/module-services/service-gui/ServiceGUI.cpp +++ b/module-services/service-gui/ServiceGUI.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. +// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md #include "ServiceGUI.hpp" @@ -184,11 +184,18 @@ namespace service::gui cache.invalidate(); } const auto context = contextPool->peekContext(contextId); +#if DEBUG_EINK_REFRESH == 1 + LOG_INFO("Rendering finished, send, contextId: %d, mode: %d", contextId, (int)refreshMode); +#endif sendOnDisplay(context, contextId, refreshMode); } else { cache.cache({contextId, refreshMode}); contextPool->returnContext(contextId); +#if DEBUG_EINK_REFRESH == 1 + LOG_INFO("Rendering finished, cancel, contextId: %d, mode: %d", contextId, (int)refreshMode); +#endif + sendCancelRefresh(); } return sys::MessageNone{}; } @@ -196,12 +203,17 @@ namespace service::gui void ServiceGUI::sendOnDisplay(::gui::Context *context, int contextId, ::gui::RefreshModes refreshMode) { isDisplaying = true; - std::shared_ptr imageMsg; - imageMsg = std::make_shared(contextId, context, refreshMode); - bus.sendUnicast(imageMsg, service::name::eink); + auto msg = std::make_shared(contextId, context, refreshMode); + bus.sendUnicast(std::move(msg), service::name::eink); scheduleContextRelease(contextId); } + void ServiceGUI::sendCancelRefresh() + { + auto msg = std::make_shared(); + bus.sendUnicast(std::move(msg), service::name::eink); + } + void ServiceGUI::scheduleContextRelease(int contextId) { // Whenever the response from ServiceEink doesn't come, the context has to be released automatically after a diff --git a/module-services/service-gui/WorkerGUI.cpp b/module-services/service-gui/WorkerGUI.cpp index 15f3e76cebd26e25f9e926e2dba699f8ff429cff..ba565e9b5f3a56d45ee35b543ce8fe50065a4aa5 100644 --- a/module-services/service-gui/WorkerGUI.cpp +++ b/module-services/service-gui/WorkerGUI.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. +// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md #include "WorkerGUI.hpp" @@ -9,6 +9,7 @@ #include #include +#include #include "messages/RenderingFinished.hpp" namespace service::gui @@ -53,7 +54,13 @@ namespace service::gui void WorkerGUI::render(DrawCommandsQueue::CommandList &commands, ::gui::RefreshModes refreshMode) { const auto [contextId, context] = guiService->contextPool->borrowContext(); // Waits for the context. + renderer.render(context, commands); + +#if DEBUG_EINK_REFRESH == 1 + LOG_INFO("Render ContextId: %d\n%s", contextId, context->toAsciiScaled().c_str()); +#endif + onRenderingFinished(contextId, refreshMode); } diff --git a/module-services/service-gui/service-gui/ServiceGUI.hpp b/module-services/service-gui/service-gui/ServiceGUI.hpp index 0cda0f530e8fe22614620ec83ae279876e75b567..2a195478922774b2d795962cab8fc3d944f6afa6 100644 --- a/module-services/service-gui/service-gui/ServiceGUI.hpp +++ b/module-services/service-gui/service-gui/ServiceGUI.hpp @@ -59,6 +59,7 @@ namespace service::gui void notifyRenderColorSchemeChange(::gui::ColorScheme &&scheme); void enqueueDrawCommands(DrawCommandsQueue::QueueItem &&item); void sendOnDisplay(::gui::Context *context, int contextId, ::gui::RefreshModes refreshMode); + void sendCancelRefresh(); void scheduleContextRelease(int contextId); bool isNextFrameReady() const noexcept; bool isAnyFrameBeingRenderedOrDisplayed() const noexcept; diff --git a/module-utils/log/api/log/debug.hpp b/module-utils/log/api/log/debug.hpp index 139880d9c286eb56c01c125e9c8f234ee9328a71..5e82214e3f51eaf1f48c9075df8079baa45df82b 100644 --- a/module-utils/log/api/log/debug.hpp +++ b/module-utils/log/api/log/debug.hpp @@ -4,19 +4,20 @@ #pragma once #define DEBUG_APPLICATION_MANAGEMENT 0 /// show verbose logs in ApplicationManager -#define DEBUG_SCOPED_TIMINGS 0 /// show timings in measured functions -#define DEBUG_CELLULAR_UART 0 /// show full modem uart communication #define DEBUG_BLUETOOTH_HCI_COMS 0 /// show communication with BT module - transactions #define DEBUG_BLUETOOTH_HCI_BYTES 0 /// show communication with BT module - all the HCI bytes -#define DEBUG_SERVICE_MESSAGES 0 /// show messages prior to handling in service +#define DEBUG_CELLULAR_UART 0 /// show full modem uart communication #define DEBUG_DB_MODEL_DATA 0 /// show messages prior to handling in service -#define DEBUG_SIM_IMPORT_DATA 0 /// show messages connected to sim data imports +#define DEBUG_EINK_REFRESH 0 /// show refresh information #define DEBUG_FONT 0 /// show Font debug messages #define DEBUG_GUI_TEXT 0 /// show basic debug messages for gui::Text - warning this can be hard on cpu #define DEBUG_GUI_TEXT_LINES 0 /// show extended debug messages for gui::Text - lines building #define DEBUG_GUI_TEXT_CURSOR 0 /// show extended debug messages for gui::Text - cursor handling #define DEBUG_INPUT_EVENTS 0 /// show input events prints in system -#define DEBUG_TIMER 0 /// debug timers system utility +#define DEBUG_MISSING_ASSETS 0 /// show debug concerning missing assets +#define DEBUG_SCOPED_TIMINGS 0 /// show timings in measured functions #define DEBUG_SETTINGS_DB 0 /// show extensive settings logs for all applications #define DEBUG_SERVICE_CELLULAR 0 /// show various logs in cellular service -#define DEBUG_MISSING_ASSETS 0 /// show debug concerning missing assets +#define DEBUG_SERVICE_MESSAGES 0 /// show messages prior to handling in service +#define DEBUG_SIM_IMPORT_DATA 0 /// show messages connected to sim data imports +#define DEBUG_TIMER 0 /// debug timers system utility diff --git a/pure_changelog.md b/pure_changelog.md index ab57e0ddef3991c8fdce4b64d0b9358a65be0405..30d375f60fb78c107a5de6c0cf72c122e2924159 100644 --- a/pure_changelog.md +++ b/pure_changelog.md @@ -7,6 +7,7 @@ * Changed USB logging * Separated system volume from Bluetooth device volume for A2DP * Made windows flow in SIM cards settings more robust +* Improve refreshing of the display. ### Fixed * Fixed wrong time displayed on password locked screen with 'Quotes' or 'Logo' wallpaper