diff --git a/src/ZeDMD.cpp b/src/ZeDMD.cpp index ce3aec6..c6427a2 100644 --- a/src/ZeDMD.cpp +++ b/src/ZeDMD.cpp @@ -17,6 +17,7 @@ ZeDMD::ZeDMD() { m_pScaledFrameBuffer = nullptr; m_pCommandBuffer = nullptr; m_pPlanes = nullptr; + m_pRgb565Buffer = nullptr; m_pZeDMDComm = new ZeDMDComm(); m_pZeDMDWiFi = new ZeDMDWiFi(); @@ -41,6 +42,10 @@ ZeDMD::~ZeDMD() { if (m_pPlanes) { delete m_pPlanes; } + + if (m_pRgb565Buffer) { + delete m_pRgb565Buffer; + } } void ZeDMD::SetLogCallback(ZeDMD_LogCallback callback, const void* userData) { @@ -187,6 +192,7 @@ bool ZeDMD::Open() { m_pCommandBuffer = (uint8_t*)malloc(ZEDMD_MAX_WIDTH * ZEDMD_MAX_HEIGHT * 3 + 192); m_pPlanes = (uint8_t*)malloc(ZEDMD_MAX_WIDTH * ZEDMD_MAX_HEIGHT * 3); + m_pRgb565Buffer = (uint16_t*)malloc(ZEDMD_MAX_WIDTH * ZEDMD_MAX_HEIGHT); m_hd = (m_pZeDMDComm->GetWidth() == 256); @@ -262,6 +268,8 @@ void ZeDMD::ClearScreen() { } else if (m_wifi) { m_pZeDMDWiFi->QueueCommand(ZEDMD_COMM_COMMAND::ClearScreen); } + // "Blank" the frame buffer. + memset(m_pFrameBuffer, 0, ZEDMD_MAX_WIDTH * ZEDMD_MAX_HEIGHT * 3); } void ZeDMD::RenderGray2(uint8_t* pFrame) { @@ -388,7 +396,7 @@ void ZeDMD::RenderRgb24(uint8_t* pFrame) { if (m_wifi) { m_pZeDMDWiFi->QueueCommand(ZEDMD_COMM_COMMAND::RGB24ZonesStream, m_pPlanes, bufferSize, width, height); - } else if (m_hd || m_rgb24Streaming) { + } else if (m_hd || m_rgb24Streaming || m_streaming) { m_pZeDMDComm->QueueCommand(ZEDMD_COMM_COMMAND::RGB24ZonesStream, m_pPlanes, bufferSize, width, height); } else if (m_usb) { @@ -397,6 +405,29 @@ void ZeDMD::RenderRgb24(uint8_t* pFrame) { } } +void ZeDMD::RenderRgb24EncodedAs565(uint8_t* pFrame) { + if (!m_usb || !UpdateFrameBuffer24(pFrame)) { + return; + } + + uint16_t width; + uint16_t height; + + int bufferSize = Scale(m_pPlanes, m_pFrameBuffer, 3, &width, &height); + int rgb565BufferSize = bufferSize / 3; + for (uint16_t i = 0; i < rgb565BufferSize; i++) { + m_pRgb565Buffer[i] = (((uint16_t)(m_pPlanes[i * 3] & 0xF8)) << 8) | + (((uint16_t)(m_pPlanes[i * 3 + 1] & 0xFC)) << 3) | + (m_pPlanes[i * 3 + 2] >> 3); + } + + if (m_usb) { + m_pZeDMDComm->QueueRgb565Command(ZEDMD_COMM_COMMAND::RGB565ZonesStream, + m_pRgb565Buffer, rgb565BufferSize, width, + height); + } +} + bool ZeDMD::UpdateFrameBuffer8(uint8_t* pFrame) { if (!memcmp(m_pFrameBuffer, pFrame, m_romWidth * m_romHeight)) { return false; @@ -867,3 +898,7 @@ ZEDMDAPI void ZeDMD_RenderColoredGray6(ZeDMD* pZeDMD, uint8_t* frame, ZEDMDAPI void ZeDMD_RenderRgb24(ZeDMD* pZeDMD, uint8_t* frame) { return pZeDMD->RenderRgb24(frame); } + +ZEDMDAPI void ZeDMD_RenderRgb24EncodedAs565(ZeDMD* pZeDMD, uint8_t* frame) { + return pZeDMD->RenderRgb24EncodedAs565(frame); +} diff --git a/src/ZeDMD.h b/src/ZeDMD.h index 2341cd8..a64fb34 100644 --- a/src/ZeDMD.h +++ b/src/ZeDMD.h @@ -1,8 +1,8 @@ #pragma once #define ZEDMD_VERSION_MAJOR 0 // X Digits -#define ZEDMD_VERSION_MINOR 4 // Max 2 Digits -#define ZEDMD_VERSION_PATCH 1 // Max 2 Digits +#define ZEDMD_VERSION_MINOR 5 // Max 2 Digits +#define ZEDMD_VERSION_PATCH 0 // Max 2 Digits #define _ZEDMD_STR(x) #x #define ZEDMD_STR(x) _ZEDMD_STR(x) @@ -78,6 +78,7 @@ class ZEDMDAPI ZeDMD { void RenderColoredGray6(uint8_t* frame, uint8_t* palette, uint8_t* rotations); void RenderColoredGray6(uint8_t* frame, uint8_t* rotations); void RenderRgb24(uint8_t* frame); + void RenderRgb24EncodedAs565(uint8_t* frame); private: bool UpdateFrameBuffer8(uint8_t* pFrame); @@ -111,6 +112,7 @@ class ZEDMDAPI ZeDMD { uint8_t* m_pScaledFrameBuffer; uint8_t* m_pCommandBuffer; uint8_t* m_pPlanes; + uint16_t* m_pRgb565Buffer; uint8_t m_palette4[4 * 3] = {0}; uint8_t m_palette16[16 * 3] = {0}; @@ -166,6 +168,8 @@ extern ZEDMDAPI void ZeDMD_RenderGray4(ZeDMD* pZeDMD, uint8_t* frame); extern ZEDMDAPI void ZeDMD_RenderColoredGray6(ZeDMD* pZeDMD, uint8_t* frame, uint8_t* rotations); extern ZEDMDAPI void ZeDMD_RenderRgb24(ZeDMD* pZeDMD, uint8_t* frame); +extern ZEDMDAPI void ZeDMD_RenderRgb24EncodedAs565(ZeDMD* pZeDMD, + uint8_t* frame); #ifdef __cplusplus } diff --git a/src/ZeDMDComm.cpp b/src/ZeDMDComm.cpp index c3f2796..898f5c9 100644 --- a/src/ZeDMDComm.cpp +++ b/src/ZeDMDComm.cpp @@ -46,6 +46,8 @@ void ZeDMDComm::Run() { int8_t lastStreamId = -1; while (IsConnected()) { + bool frame_completed = false; + m_frameQueueMutex.lock(); if (m_frames.empty()) { @@ -89,12 +91,14 @@ void ZeDMDComm::Run() { if (frame.streamId != lastStreamId) { if (lastStreamId != -1) { m_frameCounter--; + frame_completed = true; } lastStreamId = frame.streamId; } } else { m_frameCounter--; + frame_completed = true; } m_frameQueueMutex.unlock(); @@ -112,6 +116,7 @@ void ZeDMDComm::Run() { } if (!success) { + // Allow ZeDMD to empty its buffers. std::this_thread::sleep_for(std::chrono::milliseconds(2)); } } @@ -147,6 +152,8 @@ void ZeDMDComm::QueueCommand(char command, uint8_t* data, int size, m_delayedFrameReady = true; m_delayedFrameMutex.unlock(); m_lastStreamId = -1; + // Next streaming needs to be complete. + memset(m_zoneHashes, 0, sizeof(m_zoneHashes)); } // delayed streamed zones else if (streamId != -1 && delayed) { @@ -162,6 +169,10 @@ void ZeDMDComm::QueueCommand(char command, uint8_t* data, int size, } m_frames.push(frame); m_frameQueueMutex.unlock(); + if (streamId == -1) { + // Next streaming needs to be complete. + memset(m_zoneHashes, 0, sizeof(m_zoneHashes)); + } } } @@ -203,7 +214,7 @@ void ZeDMDComm::QueueCommand(char command, uint8_t* data, int size, m_delayedFrameMutex.unlock(); // A delayed frame needs to be complete. - memset(m_zoneHashes, 0, 128); + memset(m_zoneHashes, 0, sizeof(m_zoneHashes)); } for (uint16_t y = 0; y < height; y += m_zoneHeight) { @@ -241,6 +252,78 @@ void ZeDMDComm::QueueCommand(char command, uint8_t* data, int size, } } +void ZeDMDComm::QueueRgb565Command(char command, uint16_t* data, int size, + uint16_t width, uint16_t height) { + uint8_t buffer[256 * 16 * 2 + 16]; + uint16_t bufferSize = 0; + uint8_t idx = 0; + uint8_t zone[16 * 8 * 2] = {0}; + uint16_t zonesBytesLimit = 0; + if (m_zonesBytesLimit) { + while (zonesBytesLimit < m_zonesBytesLimit) { + zonesBytesLimit += m_zoneWidth * m_zoneHeight * 2 + 1; + } + } else { + zonesBytesLimit = width * m_zoneHeight * 2 + 16; + } + + if (++m_streamId > 64) { + m_streamId = 0; + } + + bool delayed = false; + if (FillDelayed()) { + delayed = true; + m_delayedFrameMutex.lock(); + m_delayedFrameReady = false; + while (m_delayedFrames.size() > 0) { + m_delayedFrames.pop(); + } + + m_delayedFrameMutex.unlock(); + // A delayed frame needs to be complete. + memset(m_zoneHashes, 0, sizeof(m_zoneHashes)); + } + + for (uint16_t y = 0; y < height; y += m_zoneHeight) { + for (uint16_t x = 0; x < width; x += m_zoneWidth) { + for (uint8_t zy = 0; zy < m_zoneHeight; zy++) { + for (uint8_t zx = 0; zx < m_zoneWidth; zx++) { + zone[(zy * m_zoneWidth + zx) * 2] = + data[((y + zy) * width) + x + zx] >> 8; + zone[(zy * m_zoneWidth + zx) * 2 + 1] = + data[((y + zy) * width) + x + zx] & 0xFF; + } + } + + uint64_t hash = komihash(zone, m_zoneWidth * m_zoneHeight * 2, 0); + if (hash != m_zoneHashes[idx]) { + m_zoneHashes[idx] = hash; + + buffer[bufferSize++] = idx; + memcpy(&buffer[bufferSize], zone, m_zoneWidth * m_zoneHeight * 2); + bufferSize += m_zoneWidth * m_zoneHeight * 2; + + if (bufferSize >= zonesBytesLimit) { + QueueCommand(command, buffer, bufferSize, m_streamId, delayed); + bufferSize = 0; + } + } + idx++; + } + } + + if (bufferSize > 0) { + QueueCommand(command, buffer, bufferSize, m_streamId, delayed); + } + + if (delayed) { + m_delayedFrameMutex.lock(); + m_delayedFrameReady = true; + m_delayedFrameMutex.unlock(); + } +} + bool ZeDMDComm::FillDelayed() { uint8_t count = 0; bool delayed = false; @@ -400,7 +483,7 @@ bool ZeDMDComm::Connect(char* pDevice) { ZEDMD_COMM_SERIAL_READ_TIMEOUT) && data[0] == 'R') { data[0] = ZEDMD_COMM_COMMAND::Chunk; - data[1] = ZEDMD_COMM_MAX_SERIAL_WRITE_AT_ONCE / 256; + data[1] = ZEDMD_COMM_MAX_SERIAL_WRITE_AT_ONCE / 32; sp_blocking_write(m_pSerialPort, (void*)CTRL_CHARS_HEADER, 6, ZEDMD_COMM_SERIAL_WRITE_TIMEOUT); sp_blocking_write(m_pSerialPort, (void*)data, 2, diff --git a/src/ZeDMDComm.h b/src/ZeDMDComm.h index 26ef76a..2387525 100644 --- a/src/ZeDMDComm.h +++ b/src/ZeDMDComm.h @@ -50,6 +50,7 @@ typedef enum { RGB24 = 0x03, RGB24ZonesStream = 0x04, + RGB565ZonesStream = 0x05, Gray2 = 0x08, ColGray4 = 0x09, ColGray6 = 0x0b, @@ -69,11 +70,11 @@ struct ZeDMDFrame { #define ZEDMD_COMM_BAUD_RATE 921600 #if defined(_WIN32) || defined(_WIN64) -#define ZEDMD_COMM_MAX_SERIAL_WRITE_AT_ONCE 8192 +#define ZEDMD_COMM_MAX_SERIAL_WRITE_AT_ONCE 1888 #define ZEDMD_COMM_SERIAL_READ_TIMEOUT 16 #define ZEDMD_COMM_SERIAL_WRITE_TIMEOUT 16 #else -#define ZEDMD_COMM_MAX_SERIAL_WRITE_AT_ONCE 4096 +#define ZEDMD_COMM_MAX_SERIAL_WRITE_AT_ONCE 1888 #define ZEDMD_COMM_SERIAL_READ_TIMEOUT 32 #define ZEDMD_COMM_SERIAL_WRITE_TIMEOUT 16 #endif @@ -107,6 +108,8 @@ class ZeDMDComm { void Run(); void QueueCommand(char command, uint8_t* buffer, int size, uint16_t width, uint16_t height); + void QueueRgb565Command(char command, uint16_t* buffer, int size, + uint16_t width, uint16_t height); void QueueCommand(char command, uint8_t* buffer, int size, int8_t streamId = -1, bool delayed = false); void QueueCommand(char command); diff --git a/src/test.cpp b/src/test.cpp index a7fab3d..f898fb7 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -1,5 +1,5 @@ #include - +#include #include #include @@ -42,40 +42,97 @@ int main(int argc, const char* argv[]) { pZeDMD->SetLogCallback(LogCallback, nullptr); if (pZeDMD->Open(128, 32)) { + //pZeDMD->EnableDebug(); + uint8_t* pImage2 = CreateImage(2); uint8_t* pImage4 = CreateImage(4); uint8_t* pImage24 = CreateImageRGB24(); - for (int i = 0; i < 20; i++) { + for (int i = 0; i < 3; i++) { printf("Render loop: %d\n", i); + printf("Grey2\n"); pZeDMD->SetDefaultPalette(2); pZeDMD->RenderGray2(pImage2); - std::this_thread::sleep_for(std::chrono::milliseconds(200)); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + pZeDMD->ClearScreen(); + printf("Grey4\n"); pZeDMD->SetDefaultPalette(4); pZeDMD->RenderGray4(pImage4); - std::this_thread::sleep_for(std::chrono::milliseconds(200)); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + pZeDMD->ClearScreen(); + + printf("RGB24 Streaming\n"); + pZeDMD->RenderRgb24(pImage24); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + + printf("RGB24 Streaming\n"); + pZeDMD->RenderRgb24(pImage24); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + pZeDMD->ClearScreen(); + printf("RGB24 Streaming\n"); pZeDMD->RenderRgb24(pImage24); - std::this_thread::sleep_for(std::chrono::milliseconds(200)); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + pZeDMD->ClearScreen(); } - pZeDMD->EnforceStreaming(); + pZeDMD->DisableRGB24Streaming(); - for (int i = 0; i < 20; i++) { + for (int i = 0; i < 3; i++) { printf("Render loop: %d\n", i); + printf("Grey2\n"); pZeDMD->SetDefaultPalette(2); pZeDMD->RenderGray2(pImage2); - std::this_thread::sleep_for(std::chrono::milliseconds(200)); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + pZeDMD->ClearScreen(); + printf("Grey4\n"); pZeDMD->SetDefaultPalette(4); pZeDMD->RenderGray4(pImage4); - std::this_thread::sleep_for(std::chrono::milliseconds(200)); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + pZeDMD->ClearScreen(); + + printf("RGB24\n"); + pZeDMD->RenderRgb24(pImage24); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + pZeDMD->ClearScreen(); + printf("RGB24 Streaming\n"); pZeDMD->RenderRgb24(pImage24); - std::this_thread::sleep_for(std::chrono::milliseconds(200)); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + pZeDMD->ClearScreen(); + + } + + pZeDMD->EnforceStreaming(); + + for (int i = 0; i < 3; i++) { + printf("Streaming render loop: %d\n", i); + + printf("Grey2\n"); + pZeDMD->SetDefaultPalette(2); + pZeDMD->RenderGray2(pImage2); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + pZeDMD->ClearScreen(); + + printf("Grey4\n"); + pZeDMD->SetDefaultPalette(4); + pZeDMD->RenderGray4(pImage4); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + pZeDMD->ClearScreen(); + + printf("RGB24\n"); + pZeDMD->RenderRgb24(pImage24); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + pZeDMD->ClearScreen(); + + printf("RGB24 as RGB565\n"); + pZeDMD->RenderRgb24EncodedAs565(pImage24); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + pZeDMD->ClearScreen(); } free(pImage2);