From 2d3609e15d4ae4a67bee1ac6ae68778fad7c4da7 Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Wed, 12 Jun 2019 23:10:42 -0700 Subject: [PATCH] Double buffer and shader fixes (#273) * DoubleBufferFixes * Add NeoBitmapFile Render --- src/NeoPixelBus.h | 7 +++-- src/internal/DotStarAvrMethod.h | 2 +- src/internal/DotStarGenericMethod.h | 2 +- src/internal/DotStarSpiMethod.h | 2 +- src/internal/NeoArmMethod.h | 2 +- src/internal/NeoAvrMethod.h | 2 +- src/internal/NeoBitmapFile.h | 46 ++++++++++++++++++++++++++--- src/internal/NeoDib.h | 29 +++++++++++++++++- src/internal/NeoEsp32I2sMethod.h | 2 +- src/internal/NeoEsp32RmtMethod.h | 22 +++++++++----- src/internal/NeoEsp8266UartMethod.h | 29 +++++++++++------- src/internal/NeoEspBitBangMethod.h | 2 +- 12 files changed, 113 insertions(+), 34 deletions(-) diff --git a/src/NeoPixelBus.h b/src/NeoPixelBus.h index 08c6c19..3c08f83 100644 --- a/src/NeoPixelBus.h +++ b/src/NeoPixelBus.h @@ -57,8 +57,9 @@ License along with NeoPixel. If not, see #include "internal/NeoBufferMethods.h" #include "internal/NeoBuffer.h" #include "internal/NeoSpriteSheet.h" -#include "internal/NeoBitmapFile.h" #include "internal/NeoDib.h" +#include "internal/NeoBitmapFile.h" + #include "internal/NeoEase.h" #include "internal/NeoGamma.h" @@ -145,14 +146,14 @@ public: Dirty(); } - void Show() + void Show(bool maintainBufferConsistency = true) { if (!IsDirty()) { return; } - _method.Update(); + _method.Update(maintainBufferConsistency); ResetDirty(); } diff --git a/src/internal/DotStarAvrMethod.h b/src/internal/DotStarAvrMethod.h index 66bd00b..a627d0a 100644 --- a/src/internal/DotStarAvrMethod.h +++ b/src/internal/DotStarAvrMethod.h @@ -68,7 +68,7 @@ public: digitalWrite(_pinData, LOW); } - void Update() + void Update(bool) { // start frame for (int startFrameByte = 0; startFrameByte < 4; startFrameByte++) diff --git a/src/internal/DotStarGenericMethod.h b/src/internal/DotStarGenericMethod.h index d59d0e0..8b3fe33 100644 --- a/src/internal/DotStarGenericMethod.h +++ b/src/internal/DotStarGenericMethod.h @@ -60,7 +60,7 @@ public: digitalWrite(_pinData, LOW); } - void Update() + void Update(bool) { // start frame for (int startFrameByte = 0; startFrameByte < 4; startFrameByte++) diff --git a/src/internal/DotStarSpiMethod.h b/src/internal/DotStarSpiMethod.h index 7d4173c..7c51816 100644 --- a/src/internal/DotStarSpiMethod.h +++ b/src/internal/DotStarSpiMethod.h @@ -63,7 +63,7 @@ public: SPI.begin(); } - void Update() + void Update(bool) { SPI.beginTransaction(SPISettings(20000000L, MSBFIRST, SPI_MODE0)); #if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) diff --git a/src/internal/NeoArmMethod.h b/src/internal/NeoArmMethod.h index 32c2d25..58fefb6 100644 --- a/src/internal/NeoArmMethod.h +++ b/src/internal/NeoArmMethod.h @@ -66,7 +66,7 @@ public: _endTime = micros(); } - void Update() + void Update(bool) { // Data latch = 50+ microsecond pause in the output stream. Rather than // put a delay at the end of the function, the ending time is noted and diff --git a/src/internal/NeoAvrMethod.h b/src/internal/NeoAvrMethod.h index f8dc7db..7b44ee8 100644 --- a/src/internal/NeoAvrMethod.h +++ b/src/internal/NeoAvrMethod.h @@ -148,7 +148,7 @@ public: _endTime = micros(); } - void Update() + void Update(bool) { // Data latch = 50+ microsecond pause in the output stream. Rather than // put a delay at the end of the function, the ending time is noted and diff --git a/src/internal/NeoBitmapFile.h b/src/internal/NeoBitmapFile.h index cef8c3c..870696f 100644 --- a/src/internal/NeoBitmapFile.h +++ b/src/internal/NeoBitmapFile.h @@ -189,7 +189,9 @@ public: return color; }; - void Blt(NeoBufferContext destBuffer, + + template void Render(NeoBufferContext destBuffer, + T_SHADER& shader, uint16_t indexPixel, int16_t xSrc, int16_t ySrc, @@ -208,6 +210,7 @@ public: { if (readPixel(&color)) { + color = shader.Apply(indexPixel, color); xSrc++; } } @@ -216,8 +219,20 @@ public: } } } - + void Blt(NeoBufferContext destBuffer, + uint16_t indexPixel, + int16_t xSrc, + int16_t ySrc, + int16_t wSrc) + { + NeoShaderNop shaderNop; + + Render>(destBuffer, shaderNop, indexPixel, xSrc, ySrc, wSrc); + }; + + template void Render(NeoBufferContext destBuffer, + T_SHADER& shader, int16_t xDest, int16_t yDest, int16_t xSrc, @@ -238,15 +253,16 @@ public: { for (int16_t x = 0; x < wSrc; x++) { + uint16_t indexDest = layoutMap(xDest + x, yDest + y); + if ((uint16_t)xFile < _width) { if (readPixel(&color)) { + color = shader.Apply(indexDest, color); xFile++; } } - - uint16_t indexDest = layoutMap(xDest + x, yDest + y); if (indexDest < destPixelCount) { @@ -257,6 +273,28 @@ public: } }; + void Blt(NeoBufferContext destBuffer, + int16_t xDest, + int16_t yDest, + int16_t xSrc, + int16_t ySrc, + int16_t wSrc, + int16_t hSrc, + LayoutMapCallback layoutMap) + { + NeoShaderNop shaderNop; + + Render>(destBuffer, + shaderNop, + xDest, + yDest, + xSrc, + ySrc, + wSrc, + hSrc, + layoutMap); + }; + private: T_FILE_METHOD _file; diff --git a/src/internal/NeoDib.h b/src/internal/NeoDib.h index 7327061..2ec93eb 100644 --- a/src/internal/NeoDib.h +++ b/src/internal/NeoDib.h @@ -25,6 +25,32 @@ License along with NeoPixel. If not, see -------------------------------------------------------------------------*/ #pragma once +template class NeoShaderNop +{ +public: + NeoShaderNop() + { + } + + bool IsDirty() const + { + return true; + }; + + void Dirty() + { + }; + + void ResetDirty() + { + }; + + T_COLOR_OBJECT Apply(uint16_t, T_COLOR_OBJECT color) + { + return color; + }; +}; + class NeoShaderBase { public: @@ -118,7 +144,8 @@ public: Dirty(); }; - template void Render(NeoBufferContext destBuffer, T_SHADER& shader) + template void Render(NeoBufferContext destBuffer, + T_SHADER& shader) { if (IsDirty() || shader.IsDirty()) { diff --git a/src/internal/NeoEsp32I2sMethod.h b/src/internal/NeoEsp32I2sMethod.h index 2918de7..9e5f5f6 100644 --- a/src/internal/NeoEsp32I2sMethod.h +++ b/src/internal/NeoEsp32I2sMethod.h @@ -128,7 +128,7 @@ public: i2sSetPins(T_BUS::I2sBusNumber, _pin, -1, -1, -1); } - void Update() + void Update(bool) { // wait for not actively sending data while (!IsReadyToUpdate()) diff --git a/src/internal/NeoEsp32RmtMethod.h b/src/internal/NeoEsp32RmtMethod.h index b70d614..ebbbf25 100644 --- a/src/internal/NeoEsp32RmtMethod.h +++ b/src/internal/NeoEsp32RmtMethod.h @@ -51,7 +51,7 @@ public: // ClkDiv of 2 provides for good resolution and plenty of reset resolution; but // a ClkDiv of 1 will provide enough space for the longest reset and does show // little better pulse accuracy - const static uint8_t RmtClockDivider = 1; + const static uint8_t RmtClockDivider = 2; inline constexpr static uint32_t FromNs(uint32_t ns) { @@ -207,20 +207,26 @@ public: rmt_translator_init(T_CHANNEL::RmtChannelNumber, _translate); } - void Update() + void Update(bool maintainBufferConsistency) { // wait for not actively sending data // this will time out at 10 seconds, an arbitrarily long period of time // and do nothing if this happens if (ESP_OK == rmt_wait_tx_done(T_CHANNEL::RmtChannelNumber, 10000 / portTICK_PERIOD_MS)) { - // copy editing to sending, - // this maintains the contract that colors present before this will - // be the same as it just was using GetPixelColor - memcpy(_pixelsSending, _pixelsEditing, _pixelsSize); + // now start the RMT transmit with the editing buffer before we swap + rmt_write_sample(T_CHANNEL::RmtChannelNumber, _pixelsEditing, _pixelsSize, false); - // now start the RMT transmit - rmt_write_sample(T_CHANNEL::RmtChannelNumber, _pixelsSending, _pixelsSize, false); + if (maintainBufferConsistency) + { + // copy editing to sending, + // this maintains the contract that "colors present before will + // be the same after", otherwise GetPixelColor will be inconsistent + memcpy(_pixelsSending, _pixelsEditing, _pixelsSize); + } + + // swap so the user can modify without affecting the async operation + std::swap(_pixelsSending, _pixelsEditing); } } diff --git a/src/internal/NeoEsp8266UartMethod.h b/src/internal/NeoEsp8266UartMethod.h index 2c88a7f..1b2c6c5 100644 --- a/src/internal/NeoEsp8266UartMethod.h +++ b/src/internal/NeoEsp8266UartMethod.h @@ -184,7 +184,7 @@ protected: T_UARTFEATURE::Init(uartBaud); } - void UpdateUart() + void UpdateUart(bool) { // Since the UART can finish sending queued bytes in the FIFO in // the background, instead of waiting for the FIFO to flush @@ -221,12 +221,12 @@ protected: NeoEsp8266AsyncUart(uint16_t pixelCount, size_t elementSize) : NeoEsp8266UartBase(pixelCount, elementSize) { - _asyncPixels = (uint8_t*)malloc(_sizePixels); + _pixelsSending = (uint8_t*)malloc(_sizePixels); } ~NeoEsp8266AsyncUart() { - // Remember: the UART interrupt can be sending data from _asyncPixels in the background + // Remember: the UART interrupt can be sending data from _pixelsSending in the background while (_context.IsSending()) { yield(); @@ -234,7 +234,7 @@ protected: // detach context, which will disable intr, may disable ISR _context.Detach(T_UARTFEATURE::Index); - free(_asyncPixels); + free(_pixelsSending); } void ICACHE_RAM_ATTR InitializeUart(uint32_t uartBaud) @@ -245,7 +245,7 @@ protected: _context.Attach(T_UARTFEATURE::Index); } - void UpdateUart() + void UpdateUart(bool maintainBufferConsistency) { // Instruct ESP8266 hardware uart to send the pixels asynchronously _context.StartSending(T_UARTFEATURE::Index, @@ -255,15 +255,22 @@ protected: // Annotate when we started to send bytes, so we can calculate when we are ready to send again _startTime = micros(); - // Copy the pixels to the idle buffer and swap them - memcpy(_asyncPixels, _pixels, _sizePixels); - std::swap(_asyncPixels, _pixels); + if (maintainBufferConsistency) + { + // copy editing to sending, + // this maintains the contract that "colors present before will + // be the same after", otherwise GetPixelColor will be inconsistent + memcpy(_pixelsSending, _pixels, _sizePixels); + } + + // swap so the user can modify without affecting the async operation + std::swap(_pixelsSending, _pixels); } private: T_UARTCONTEXT _context; - uint8_t* _asyncPixels; // Holds a copy of LED color values taken when UpdateUart began + uint8_t* _pixelsSending; // Holds a copy of LED color values taken when UpdateUart began }; class NeoEsp8266UartSpeed800KbpsBase @@ -334,7 +341,7 @@ public: this->_startTime = micros() - getPixelTime(); } - void Update() + void Update(bool maintainBufferConsistency) { // Data latch = 50+ microsecond pause in the output stream. Rather than // put a delay at the end of the function, the ending time is noted and @@ -346,7 +353,7 @@ public: { yield(); } - this->UpdateUart(); + this->UpdateUart(maintainBufferConsistency); } uint8_t* getPixels() const diff --git a/src/internal/NeoEspBitBangMethod.h b/src/internal/NeoEspBitBangMethod.h index df8ab80..d832df9 100644 --- a/src/internal/NeoEspBitBangMethod.h +++ b/src/internal/NeoEspBitBangMethod.h @@ -113,7 +113,7 @@ public: _endTime = micros(); } - void Update() + void Update(bool) { // Data latch = 50+ microsecond pause in the output stream. Rather than // put a delay at the end of the function, the ending time is noted and