diff --git a/Extensions/MemoryArena.cpp b/Extensions/MemoryArena.cpp new file mode 100644 index 00000000..9c4e7cfc --- /dev/null +++ b/Extensions/MemoryArena.cpp @@ -0,0 +1,28 @@ +// MemoryArena.cpp +#include "MemoryArena.hpp" +#include +#include + +MemoryArena::MemoryArena(size_t initialSize) : memory(initialSize), currentOffset(0) {} + +MemoryArena::~MemoryArena() = default; + +void* MemoryArena::allocate(size_t size) { + if (currentOffset + size > memory.size()) { + size_t newSize = std::max(memory.size() * 2, currentOffset + size); + memory.resize(newSize); + } + + void* ptr = &memory[currentOffset]; + currentOffset += size; + return ptr; +} + +void MemoryArena::reset() { + currentOffset = 0; +} + +void MemoryArena::resetToZero() { + std::fill(memory.begin(), memory.end(), 0); + currentOffset = 0; +} diff --git a/Extensions/MemoryArena.hpp b/Extensions/MemoryArena.hpp new file mode 100644 index 00000000..e1378b9c --- /dev/null +++ b/Extensions/MemoryArena.hpp @@ -0,0 +1,17 @@ +// MemoryArena.hpp +#pragma once +#include + +class MemoryArena { +public: + explicit MemoryArena(size_t initialSize); + ~MemoryArena(); + + void* allocate(size_t size); + void reset(); + void resetToZero(); + +private: + std::vector memory; + size_t currentOffset; +}; diff --git a/Extensions/Sprite.cpp b/Extensions/Sprite.cpp index af841cc1..64c9fe24 100644 --- a/Extensions/Sprite.cpp +++ b/Extensions/Sprite.cpp @@ -111,6 +111,151 @@ void* TFT_eSprite::createSprite(int16_t w, int16_t h, uint8_t frames) } +void TFT_eSprite::createPaletteWithArena(uint16_t colorMap[], uint8_t colors, MemoryArena& arena) +{ + if (!_created) return; + + if (colorMap == nullptr) + { + // Create a color map using the default FLASH map + createPaletteWithArena(default_4bit_palette, 16, arena); + return; + } + + // Allocate and clear memory for 16 color map + if (_colorMap == nullptr) + { + _colorMap = (uint16_t*)arena.allocate(16 * sizeof(uint16_t)); + memset(_colorMap, 0, 16 * sizeof(uint16_t)); + } + + if (colors > 16) colors = 16; + + // Copy map colors + for (uint8_t i = 0; i < colors; i++) + { + _colorMap[i] = colorMap[i]; + } +} + +void TFT_eSprite::createPaletteWithArena(const uint16_t colorMap[], uint8_t colors, MemoryArena& arena) +{ + if (!_created) return; + + if (colorMap == nullptr) + { + // Create a color map using the default FLASH map + colorMap = default_4bit_palette; + } + + // Allocate and clear memory for 16 color map + if (_colorMap == nullptr) + { + _colorMap = (uint16_t*)arena.allocate(16 * sizeof(uint16_t)); + memset(_colorMap, 0, 16 * sizeof(uint16_t)); + } + + if (colors > 16) colors = 16; + + // Copy map colors + for (uint8_t i = 0; i < colors; i++) + { + _colorMap[i] = pgm_read_word(&colorMap[i]); + } +} + + +void* TFT_eSprite::callocSpriteWithArena(int16_t w, int16_t h, uint8_t frames, MemoryArena& arena) { + // Add one extra "off screen" pixel to point out-of-bounds setWindow() coordinates + uint8_t* ptr8 = nullptr; + if (frames > 2) frames = 2; // Currently restricted to 2 frame buffers + if (frames < 1) frames = 1; + + if (_bpp == 16) + ptr8 = (uint8_t*)arena.allocate(frames * w * h * sizeof(uint16_t) + frames); + else if (_bpp == 8) + ptr8 = (uint8_t*)arena.allocate(frames * w * h * sizeof(uint8_t) + frames); + else if (_bpp == 4) + { + w = (w + 1) & 0xFFFE; // width needs to be multiple of 2, with an extra "off screen" pixel + _iwidth = w; + ptr8 = (uint8_t*)arena.allocate(((frames * w * h) >> 1) + frames); + } + else // Must be 1 bpp + { + //_dwidth and _dheight will be set by createSprite() + w = (w+7) & 0xFFF8; // width should be the multiple of 8 bits to be compatible with epdpaint + _iwidth = w; + ptr8 = (uint8_t*)arena.allocate(frames * (w>>3) * h + frames); + } + + return ptr8; +} + +void* TFT_eSprite::createSpriteWithArena(int16_t w, int16_t h, uint8_t frames, MemoryArena& arena) { + + if ( _created ) return _img8_1; + + if ( w < 1 || h < 1 ) return nullptr; + + _isArenaAllocated = true; + + _iwidth = _dwidth = _bitwidth = w; + _iheight = _dheight = h; + + cursor_x = 0; + cursor_y = 0; + + // Default scroll rectangle and gap fill colour + _sx = 0; + _sy = 0; + _sw = w; + _sh = h; + _scolor = TFT_BLACK; + + _img8 = (uint8_t*) callocSpriteWithArena(w, h, frames, arena); + _img8_1 = _img8; + _img8_2 = _img8; + _img = (uint16_t*) _img8; + _img4 = _img8; + + if ( (_bpp == 16) && (frames > 1) ) { + _img8_2 = _img8 + (w * h * 2 + 1); + } + + // ESP32 only 16bpp check + //if (esp_ptr_dma_capable(_img8_1)) Serial.println("DMA capable Sprite pointer _img8_1"); + //else Serial.println("Not a DMA capable Sprite pointer _img8_1"); + //if (esp_ptr_dma_capable(_img8_2)) Serial.println("DMA capable Sprite pointer _img8_2"); + //else Serial.println("Not a DMA capable Sprite pointer _img8_2"); + + if ( (_bpp == 8) && (frames > 1) ) { + _img8_2 = _img8 + (w * h + 1); + } + + // This is to make it clear what pointer size is expected to be used + // but casting in the user sketch is needed due to the use of void* + if ( (_bpp == 1) && (frames > 1) ) + { + w = (w+7) & 0xFFF8; + _img8_2 = _img8 + ( (w>>3) * h + 1 ); + } + + if (_img8) + { + _created = true; + if ( (_bpp == 4) && (_colorMap == nullptr)) createPaletteWithArena(default_4bit_palette, 0, arena); + + rotation = 0; + setViewport(0, 0, _dwidth, _dheight); + setPivot(_iwidth/2, _iheight/2); + return _img8_1; + } + + return nullptr; +} + + /*************************************************************************************** ** Function name: getPointer ** Description: Returns pointer to start of sprite memory area @@ -217,6 +362,7 @@ void* TFT_eSprite::callocSprite(int16_t w, int16_t h, uint8_t frames) } + /*************************************************************************************** ** Function name: createPalette (from RAM array) ** Description: Set a palette for a 4-bit per pixel sprite @@ -374,13 +520,13 @@ void TFT_eSprite::deleteSprite(void) { if (_colorMap != nullptr) { - free(_colorMap); + if (not _isArenaAllocated) free(_colorMap); _colorMap = nullptr; } if (_created) { - free(_img8_1); + if (not _isArenaAllocated) free(_img8_1); _img8 = nullptr; _created = false; _vpOoB = true; // TFT_eSPI class write() uses this to check for valid sprite @@ -395,7 +541,6 @@ void TFT_eSprite::deleteSprite(void) #define FP_SCALE 10 bool TFT_eSprite::pushRotated(int16_t angle, uint32_t transp) { - if ( !_created || _tft->_vpOoB) return false; // Bounding box parameters int16_t min_x; diff --git a/Extensions/Sprite.h b/Extensions/Sprite.h index 67e29de7..a2017edd 100644 --- a/Extensions/Sprite.h +++ b/Extensions/Sprite.h @@ -5,6 +5,8 @@ // graphics are written to the Sprite rather than the TFT. ***************************************************************************************/ +#include "MemoryArena.hpp" + class TFT_eSprite : public TFT_eSPI { public: @@ -20,6 +22,12 @@ class TFT_eSprite : public TFT_eSPI { // - 1 byte per pixel for 8-bit colour (332 RGB format) // - 2 bytes per pixel for 16-bit color depth (565 RGB format) void* createSprite(int16_t width, int16_t height, uint8_t frames = 1); + + void createPaletteWithArena(uint16_t colorMap[], uint8_t colors, MemoryArena& arena); + void createPaletteWithArena(const uint16_t colorMap[], uint8_t colors, MemoryArena& arena); + + void* callocSpriteWithArena(int16_t w, int16_t h, uint8_t frames, MemoryArena& arena); + void* createSpriteWithArena(int16_t w, int16_t h, uint8_t frames, MemoryArena& arena); // Returns a pointer to the sprite or nullptr if not created, user must cast to pointer type void* getPointer(void); @@ -161,6 +169,7 @@ class TFT_eSprite : public TFT_eSPI { protected: + bool _isArenaAllocated; uint8_t _bpp; // bits per pixel (1, 4, 8 or 16) uint16_t *_img; // pointer to 16-bit sprite uint8_t *_img8; // pointer to 1 and 8-bit sprite frame 1 or frame 2 diff --git a/TFT_eSPI.cpp b/TFT_eSPI.cpp index d537faeb..8c134709 100644 --- a/TFT_eSPI.cpp +++ b/TFT_eSPI.cpp @@ -6147,6 +6147,8 @@ void TFT_eSPI::getSetup(setup_t &tft_settings) #include "Extensions/Sprite.cpp" +#include "Extensions/MemoryArena.cpp" + #ifdef SMOOTH_FONT #include "Extensions/Smooth_font.cpp" #endif