forked from Makuna/NeoPixelBus
Double buffer and shader fixes (#273)
* DoubleBufferFixes * Add NeoBitmapFile Render
This commit is contained in:
@@ -57,8 +57,9 @@ License along with NeoPixel. If not, see
|
|||||||
#include "internal/NeoBufferMethods.h"
|
#include "internal/NeoBufferMethods.h"
|
||||||
#include "internal/NeoBuffer.h"
|
#include "internal/NeoBuffer.h"
|
||||||
#include "internal/NeoSpriteSheet.h"
|
#include "internal/NeoSpriteSheet.h"
|
||||||
#include "internal/NeoBitmapFile.h"
|
|
||||||
#include "internal/NeoDib.h"
|
#include "internal/NeoDib.h"
|
||||||
|
#include "internal/NeoBitmapFile.h"
|
||||||
|
|
||||||
#include "internal/NeoEase.h"
|
#include "internal/NeoEase.h"
|
||||||
#include "internal/NeoGamma.h"
|
#include "internal/NeoGamma.h"
|
||||||
|
|
||||||
@@ -145,14 +146,14 @@ public:
|
|||||||
Dirty();
|
Dirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Show()
|
void Show(bool maintainBufferConsistency = true)
|
||||||
{
|
{
|
||||||
if (!IsDirty())
|
if (!IsDirty())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_method.Update();
|
_method.Update(maintainBufferConsistency);
|
||||||
|
|
||||||
ResetDirty();
|
ResetDirty();
|
||||||
}
|
}
|
||||||
|
@@ -68,7 +68,7 @@ public:
|
|||||||
digitalWrite(_pinData, LOW);
|
digitalWrite(_pinData, LOW);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Update()
|
void Update(bool)
|
||||||
{
|
{
|
||||||
// start frame
|
// start frame
|
||||||
for (int startFrameByte = 0; startFrameByte < 4; startFrameByte++)
|
for (int startFrameByte = 0; startFrameByte < 4; startFrameByte++)
|
||||||
|
@@ -60,7 +60,7 @@ public:
|
|||||||
digitalWrite(_pinData, LOW);
|
digitalWrite(_pinData, LOW);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Update()
|
void Update(bool)
|
||||||
{
|
{
|
||||||
// start frame
|
// start frame
|
||||||
for (int startFrameByte = 0; startFrameByte < 4; startFrameByte++)
|
for (int startFrameByte = 0; startFrameByte < 4; startFrameByte++)
|
||||||
|
@@ -63,7 +63,7 @@ public:
|
|||||||
SPI.begin();
|
SPI.begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Update()
|
void Update(bool)
|
||||||
{
|
{
|
||||||
SPI.beginTransaction(SPISettings(20000000L, MSBFIRST, SPI_MODE0));
|
SPI.beginTransaction(SPISettings(20000000L, MSBFIRST, SPI_MODE0));
|
||||||
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
|
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
|
||||||
|
@@ -66,7 +66,7 @@ public:
|
|||||||
_endTime = micros();
|
_endTime = micros();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Update()
|
void Update(bool)
|
||||||
{
|
{
|
||||||
// Data latch = 50+ microsecond pause in the output stream. Rather than
|
// 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
|
// put a delay at the end of the function, the ending time is noted and
|
||||||
|
@@ -148,7 +148,7 @@ public:
|
|||||||
_endTime = micros();
|
_endTime = micros();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Update()
|
void Update(bool)
|
||||||
{
|
{
|
||||||
// Data latch = 50+ microsecond pause in the output stream. Rather than
|
// 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
|
// put a delay at the end of the function, the ending time is noted and
|
||||||
|
@@ -189,7 +189,9 @@ public:
|
|||||||
return color;
|
return color;
|
||||||
};
|
};
|
||||||
|
|
||||||
void Blt(NeoBufferContext<T_COLOR_FEATURE> destBuffer,
|
|
||||||
|
template <typename T_SHADER> void Render(NeoBufferContext<T_COLOR_FEATURE> destBuffer,
|
||||||
|
T_SHADER& shader,
|
||||||
uint16_t indexPixel,
|
uint16_t indexPixel,
|
||||||
int16_t xSrc,
|
int16_t xSrc,
|
||||||
int16_t ySrc,
|
int16_t ySrc,
|
||||||
@@ -208,6 +210,7 @@ public:
|
|||||||
{
|
{
|
||||||
if (readPixel(&color))
|
if (readPixel(&color))
|
||||||
{
|
{
|
||||||
|
color = shader.Apply(indexPixel, color);
|
||||||
xSrc++;
|
xSrc++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -216,8 +219,20 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Blt(NeoBufferContext<T_COLOR_FEATURE> destBuffer,
|
void Blt(NeoBufferContext<T_COLOR_FEATURE> destBuffer,
|
||||||
|
uint16_t indexPixel,
|
||||||
|
int16_t xSrc,
|
||||||
|
int16_t ySrc,
|
||||||
|
int16_t wSrc)
|
||||||
|
{
|
||||||
|
NeoShaderNop<typename T_COLOR_FEATURE::ColorObject> shaderNop;
|
||||||
|
|
||||||
|
Render<NeoShaderNop<typename T_COLOR_FEATURE::ColorObject>>(destBuffer, shaderNop, indexPixel, xSrc, ySrc, wSrc);
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T_SHADER> void Render(NeoBufferContext<T_COLOR_FEATURE> destBuffer,
|
||||||
|
T_SHADER& shader,
|
||||||
int16_t xDest,
|
int16_t xDest,
|
||||||
int16_t yDest,
|
int16_t yDest,
|
||||||
int16_t xSrc,
|
int16_t xSrc,
|
||||||
@@ -238,15 +253,16 @@ public:
|
|||||||
{
|
{
|
||||||
for (int16_t x = 0; x < wSrc; x++)
|
for (int16_t x = 0; x < wSrc; x++)
|
||||||
{
|
{
|
||||||
|
uint16_t indexDest = layoutMap(xDest + x, yDest + y);
|
||||||
|
|
||||||
if ((uint16_t)xFile < _width)
|
if ((uint16_t)xFile < _width)
|
||||||
{
|
{
|
||||||
if (readPixel(&color))
|
if (readPixel(&color))
|
||||||
{
|
{
|
||||||
|
color = shader.Apply(indexDest, color);
|
||||||
xFile++;
|
xFile++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t indexDest = layoutMap(xDest + x, yDest + y);
|
|
||||||
|
|
||||||
if (indexDest < destPixelCount)
|
if (indexDest < destPixelCount)
|
||||||
{
|
{
|
||||||
@@ -257,6 +273,28 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void Blt(NeoBufferContext<T_COLOR_FEATURE> destBuffer,
|
||||||
|
int16_t xDest,
|
||||||
|
int16_t yDest,
|
||||||
|
int16_t xSrc,
|
||||||
|
int16_t ySrc,
|
||||||
|
int16_t wSrc,
|
||||||
|
int16_t hSrc,
|
||||||
|
LayoutMapCallback layoutMap)
|
||||||
|
{
|
||||||
|
NeoShaderNop<typename T_COLOR_FEATURE::ColorObject> shaderNop;
|
||||||
|
|
||||||
|
Render<NeoShaderNop<typename T_COLOR_FEATURE::ColorObject>>(destBuffer,
|
||||||
|
shaderNop,
|
||||||
|
xDest,
|
||||||
|
yDest,
|
||||||
|
xSrc,
|
||||||
|
ySrc,
|
||||||
|
wSrc,
|
||||||
|
hSrc,
|
||||||
|
layoutMap);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
T_FILE_METHOD _file;
|
T_FILE_METHOD _file;
|
||||||
|
@@ -25,6 +25,32 @@ License along with NeoPixel. If not, see
|
|||||||
-------------------------------------------------------------------------*/
|
-------------------------------------------------------------------------*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
template<typename T_COLOR_OBJECT> 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
|
class NeoShaderBase
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -118,7 +144,8 @@ public:
|
|||||||
Dirty();
|
Dirty();
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T_COLOR_FEATURE, typename T_SHADER> void Render(NeoBufferContext<T_COLOR_FEATURE> destBuffer, T_SHADER& shader)
|
template <typename T_COLOR_FEATURE, typename T_SHADER> void Render(NeoBufferContext<T_COLOR_FEATURE> destBuffer,
|
||||||
|
T_SHADER& shader)
|
||||||
{
|
{
|
||||||
if (IsDirty() || shader.IsDirty())
|
if (IsDirty() || shader.IsDirty())
|
||||||
{
|
{
|
||||||
|
@@ -128,7 +128,7 @@ public:
|
|||||||
i2sSetPins(T_BUS::I2sBusNumber, _pin, -1, -1, -1);
|
i2sSetPins(T_BUS::I2sBusNumber, _pin, -1, -1, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Update()
|
void Update(bool)
|
||||||
{
|
{
|
||||||
// wait for not actively sending data
|
// wait for not actively sending data
|
||||||
while (!IsReadyToUpdate())
|
while (!IsReadyToUpdate())
|
||||||
|
@@ -51,7 +51,7 @@ public:
|
|||||||
public:
|
public:
|
||||||
// ClkDiv of 2 provides for good resolution and plenty of reset resolution; but
|
// 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
|
// a ClkDiv of 1 will provide enough space for the longest reset and does show
|
||||||
// little better pulse accuracy
|
// little better pulse accuracy
|
||||||
const static uint8_t RmtClockDivider = 2;
|
const static uint8_t RmtClockDivider = 2;
|
||||||
|
|
||||||
inline constexpr static uint32_t FromNs(uint32_t ns)
|
inline constexpr static uint32_t FromNs(uint32_t ns)
|
||||||
@@ -207,20 +207,26 @@ public:
|
|||||||
rmt_driver_install(T_CHANNEL::RmtChannelNumber, 0, 0);
|
rmt_driver_install(T_CHANNEL::RmtChannelNumber, 0, 0);
|
||||||
rmt_translator_init(T_CHANNEL::RmtChannelNumber, _translate);
|
rmt_translator_init(T_CHANNEL::RmtChannelNumber, _translate);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Update(bool maintainBufferConsistency)
|
void Update(bool maintainBufferConsistency)
|
||||||
{
|
{
|
||||||
// wait for not actively sending data
|
// wait for not actively sending data
|
||||||
// this will time out at 10 seconds, an arbitrarily long period of time
|
// this will time out at 10 seconds, an arbitrarily long period of time
|
||||||
// and do nothing if this happens
|
// and do nothing if this happens
|
||||||
if (ESP_OK == rmt_wait_tx_done(T_CHANNEL::RmtChannelNumber, 10000 / portTICK_PERIOD_MS))
|
if (ESP_OK == rmt_wait_tx_done(T_CHANNEL::RmtChannelNumber, 10000 / portTICK_PERIOD_MS))
|
||||||
{
|
{
|
||||||
// copy editing to sending,
|
// now start the RMT transmit with the editing buffer before we swap
|
||||||
// this maintains the contract that colors present before this will
|
|
||||||
// be the same as it just was using GetPixelColor
|
|
||||||
rmt_write_sample(T_CHANNEL::RmtChannelNumber, _pixelsEditing, _pixelsSize, false);
|
rmt_write_sample(T_CHANNEL::RmtChannelNumber, _pixelsEditing, _pixelsSize, false);
|
||||||
|
|
||||||
// now start the RMT transmit
|
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);
|
std::swap(_pixelsSending, _pixelsEditing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -184,7 +184,7 @@ protected:
|
|||||||
T_UARTFEATURE::Init(uartBaud);
|
T_UARTFEATURE::Init(uartBaud);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdateUart()
|
void UpdateUart(bool)
|
||||||
{
|
{
|
||||||
// Since the UART can finish sending queued bytes in the FIFO in
|
// Since the UART can finish sending queued bytes in the FIFO in
|
||||||
// the background, instead of waiting for the FIFO to flush
|
// the background, instead of waiting for the FIFO to flush
|
||||||
@@ -221,12 +221,12 @@ protected:
|
|||||||
NeoEsp8266AsyncUart(uint16_t pixelCount, size_t elementSize) :
|
NeoEsp8266AsyncUart(uint16_t pixelCount, size_t elementSize) :
|
||||||
NeoEsp8266UartBase(pixelCount, elementSize)
|
NeoEsp8266UartBase(pixelCount, elementSize)
|
||||||
{
|
{
|
||||||
_asyncPixels = (uint8_t*)malloc(_sizePixels);
|
_pixelsSending = (uint8_t*)malloc(_sizePixels);
|
||||||
}
|
}
|
||||||
|
|
||||||
~NeoEsp8266AsyncUart()
|
~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())
|
while (_context.IsSending())
|
||||||
{
|
{
|
||||||
yield();
|
yield();
|
||||||
@@ -234,7 +234,7 @@ protected:
|
|||||||
// detach context, which will disable intr, may disable ISR
|
// detach context, which will disable intr, may disable ISR
|
||||||
_context.Detach(T_UARTFEATURE::Index);
|
_context.Detach(T_UARTFEATURE::Index);
|
||||||
|
|
||||||
free(_asyncPixels);
|
free(_pixelsSending);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ICACHE_RAM_ATTR InitializeUart(uint32_t uartBaud)
|
void ICACHE_RAM_ATTR InitializeUart(uint32_t uartBaud)
|
||||||
@@ -245,7 +245,7 @@ protected:
|
|||||||
_context.Attach(T_UARTFEATURE::Index);
|
_context.Attach(T_UARTFEATURE::Index);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdateUart()
|
void UpdateUart(bool maintainBufferConsistency)
|
||||||
{
|
{
|
||||||
// Instruct ESP8266 hardware uart to send the pixels asynchronously
|
// Instruct ESP8266 hardware uart to send the pixels asynchronously
|
||||||
_context.StartSending(T_UARTFEATURE::Index,
|
_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
|
// Annotate when we started to send bytes, so we can calculate when we are ready to send again
|
||||||
_startTime = micros();
|
_startTime = micros();
|
||||||
|
|
||||||
// Copy the pixels to the idle buffer and swap them
|
if (maintainBufferConsistency)
|
||||||
memcpy(_asyncPixels, _pixels, _sizePixels);
|
{
|
||||||
std::swap(_asyncPixels, _pixels);
|
// 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:
|
private:
|
||||||
T_UARTCONTEXT _context;
|
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
|
class NeoEsp8266UartSpeed800KbpsBase
|
||||||
@@ -334,7 +341,7 @@ public:
|
|||||||
this->_startTime = micros() - getPixelTime();
|
this->_startTime = micros() - getPixelTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Update()
|
void Update(bool maintainBufferConsistency)
|
||||||
{
|
{
|
||||||
// Data latch = 50+ microsecond pause in the output stream. Rather than
|
// 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
|
// put a delay at the end of the function, the ending time is noted and
|
||||||
@@ -346,7 +353,7 @@ public:
|
|||||||
{
|
{
|
||||||
yield();
|
yield();
|
||||||
}
|
}
|
||||||
this->UpdateUart();
|
this->UpdateUart(maintainBufferConsistency);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t* getPixels() const
|
uint8_t* getPixels() const
|
||||||
|
@@ -113,7 +113,7 @@ public:
|
|||||||
_endTime = micros();
|
_endTime = micros();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Update()
|
void Update(bool)
|
||||||
{
|
{
|
||||||
// Data latch = 50+ microsecond pause in the output stream. Rather than
|
// 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
|
// put a delay at the end of the function, the ending time is noted and
|
||||||
|
Reference in New Issue
Block a user