Add double-buffered 8-bit framebuf (requires GFX 1.11.4)

pull/46/head
Phillip Burgess 2023-01-16 16:00:14 -08:00
rodzic e12f1a3d91
commit c192ac459f
4 zmienionych plików z 198 dodań i 4 usunięć

Wyświetl plik

@ -0,0 +1,51 @@
// Double-buffered 8-bit Adafruit_GFX-compatible framebuffer for PicoDVI.
// Allows animation without redraw flicker. Requires Adafruit_GFX 1.11.4
#include <PicoDVI.h>
//DVIGFX8x2 display(320, 240, dvi_timing_640x480p_60hz, VREG_VOLTAGE_1_20, pimoroni_demo_hdmi_cfg);
DVIGFX8x2 display(400, 240, dvi_timing_800x480p_60hz, VREG_VOLTAGE_1_30, pimoroni_demo_hdmi_cfg);
#define N_BALLS 100 // 1-254 (not 255)
struct {
int16_t pos[2];
int8_t vel[2];
} ball[N_BALLS];
void setup() {
Serial.begin(115200);
//while(!Serial);
if (!display.begin()) { // Blink LED if insufficient RAM
pinMode(LED_BUILTIN, OUTPUT);
for (;;) digitalWrite(LED_BUILTIN, (millis() / 500) & 1);
}
// Randomize initial ball positions, velocities and colors
for (int i=0; i<N_BALLS; i++) {
display.setColor(i+1, 64 + random(192), 64 + random(192), 64 + random(192));
ball[i].pos[0] = 10 + random(display.width() - 20);
ball[i].pos[1] = 10 + random(display.height() - 20);
do {
ball[i].vel[0] = 2 - random(5);
ball[i].vel[1] = 2 - random(5);
} while ((ball[i].vel[0] == 0) && (ball[i].vel[1] == 0));
}
display.setColor(255, 0xFFFF); // Last palette entry = white
display.swap(false, true); // Duplicate same palette into front & back buffers
}
void loop() {
// Clear back framebuffer and draw balls (circles) there.
display.fillScreen(0);
for (int i=0; i<N_BALLS; i++) {
display.fillCircle(ball[i].pos[0] - 10, ball[i].pos[1] - 10, 20, i + 1);
// After drawing each one, update positions, bounce off edges.
ball[i].pos[0] += ball[i].vel[0];
if ((ball[i].pos[0] <= 0) || (ball[i].pos[0] >= display.width())) ball[i].vel[0] *= -1;
ball[i].pos[1] += ball[i].vel[1];
if ((ball[i].pos[1] <= 0) || (ball[i].pos[1] >= display.height())) ball[i].vel[1] *= -1;
}
// Swap front/back buffers, do not duplicate current screen state to next frame,
// we'll draw it new from scratch each time.
display.swap();
}

Wyświetl plik

@ -0,0 +1,24 @@
// 8-bit Adafruit_GFX-compatible framebuffer for PicoDVI.
#include <PicoDVI.h>
DVIGFX8 display(320, 240, dvi_timing_640x480p_60hz, VREG_VOLTAGE_1_20, pimoroni_demo_hdmi_cfg);
//DVIGFX8 display(400, 240, dvi_timing_800x480p_60hz, VREG_VOLTAGE_1_30, pimoroni_demo_hdmi_cfg);
void setup() {
Serial.begin(115200);
//while(!Serial);
if (!display.begin()) { // Blink LED if insufficient RAM
pinMode(LED_BUILTIN, OUTPUT);
for (;;) digitalWrite(LED_BUILTIN, (millis() / 500) & 1);
}
// Randomize color palette. First entry is left black, last is set white.
for (int i=1; i<255; i++) display.setColor(i, random(65536));
display.setColor(255, 0xFFFF);
}
void loop() {
// Draw random lines
display.drawLine(random(display.width()), random(display.height()), random(display.width()), random(display.height()), random(256));
}

Wyświetl plik

@ -136,10 +136,8 @@ bool DVIGFX8::begin(void) {
mainloop = dvi_scanbuf_main_16bpp; // in libdvi
dvi0.scanline_callback = scanline_callback_GFX8;
PicoDVI::begin();
for (int i = 0; i < WIDTH; i++) {
row565[0][i] = palette[bufptr[i]];
row565[1][i] = palette[bufptr[i + WIDTH]];
}
// No need to initialize the row565 buffer contents as that memory is
// cleared on canvas alloc, and the initial palette state is also all 0.
uint16_t *b16 = row565[0];
queue_add_blocking_u32(&dvi0.q_colour_valid, &b16);
b16 = row565[1];
@ -149,3 +147,93 @@ bool DVIGFX8::begin(void) {
}
return false;
}
// DVIGFX8x2 (8-bit, color-indexed, double-buffered for animation)
// requires latest Adafruit_GFX as it plays games with the canvas pointer,
// wasn't possible until that was made protected (vs private). This is very
// similar to DVIGFX8 but effectively has two canvases and palettes ("front"
// and "back"). Drawing and palette-setting operations ONLY apply to the
// "back" state. Call swap() to switch the front/back buffers at the next
// vertical sync, for flicker-free and tear-free animation.
DVIGFX8x2::DVIGFX8x2(const uint16_t w, const uint16_t h,
const struct dvi_timing &t, vreg_voltage v,
const struct dvi_serialiser_cfg &c)
: PicoDVI(t, v, c), GFXcanvas8(w, h * 2 + 4) {
HEIGHT = _height = h;
buffer_save = buffer;
}
DVIGFX8x2::~DVIGFX8x2(void) {
buffer = buffer_save; // Restore pointer so canvas destructor works
gfxptr = NULL;
}
static void scanline_callback_GFX8x2(void) {
((DVIGFX8x2 *)gfxptr)->_scanline_callback();
}
void __not_in_flash_func(DVIGFX8x2::_scanline_callback)(void) {
uint16_t *b16;
while (queue_try_remove_u32(&dvi0.q_colour_free, &b16))
; // Discard returned pointer(s)
b16 = row565[rowidx]; // Next row to send
queue_add_blocking_u32(&dvi0.q_colour_valid, &b16); // Send it
if (++scanline >= HEIGHT) { // Next scanline...end of screen reached?
if (swap_wait) { // Swap buffers?
back_index = 1 - back_index; // Yes plz
buffer = buffer_save + WIDTH * HEIGHT * back_index;
swap_wait = 0;
}
scanline = 0;
}
// Refresh from front buffer
uint8_t *b8 = buffer_save + WIDTH * HEIGHT * (1 - back_index) +
WIDTH * scanline; // New src
rowidx = (rowidx + 1) & 1; // Swap row565[] bufs
b16 = row565[rowidx]; // New dest
for (int i = 0; i < WIDTH; i++)
b16[i] = palette[1 - back_index][b8[i]];
}
bool DVIGFX8x2::begin(void) {
uint8_t *bufptr = getBuffer();
if ((bufptr)) {
gfxptr = this;
row565[0] = (uint16_t *)&bufptr[WIDTH * HEIGHT * 2];
row565[1] = row565[0] + WIDTH;
memset(palette, 0, sizeof palette);
// mainloop = mainloop8;
mainloop = dvi_scanbuf_main_16bpp; // in libdvi
dvi0.scanline_callback = scanline_callback_GFX8x2;
PicoDVI::begin();
bufptr += WIDTH * HEIGHT; // Initial front buffer is index 1
// No need to initialize the row565 buffer contents as that memory is
// cleared on canvas alloc, and the initial palette state is also all 0.
uint16_t *b16 = row565[0];
queue_add_blocking_u32(&dvi0.q_colour_valid, &b16);
b16 = row565[1];
queue_add_blocking_u32(&dvi0.q_colour_valid, &b16);
wait_begin = false; // Set core 1 in motion
return true;
}
return false;
}
void DVIGFX8x2::swap(bool copy_framebuffer, bool copy_palette) {
// Request buffer swap at next frame end, wait for it to happen.
for (swap_wait = 1; swap_wait;)
;
if ((copy_framebuffer)) {
uint32_t bufsize = WIDTH * HEIGHT;
memcpy(buffer_save + bufsize * back_index,
buffer_save + bufsize * (1 - back_index), bufsize);
}
if ((copy_palette)) {
memcpy(palette[back_index], palette[1 - back_index], sizeof(palette[0]));
}
}

Wyświetl plik

@ -61,3 +61,34 @@ protected:
uint16_t scanline = 2; // First 2 scanlines are set up before DVI start
uint8_t rowidx = 1; // Alternate 0/1 for which row565[] is active
};
class DVIGFX8x2 : public PicoDVI, public GFXcanvas8 {
public:
DVIGFX8x2(const uint16_t w = 320, const uint16_t h = 240,
const struct dvi_timing &t = dvi_timing_640x480p_60hz,
vreg_voltage v = VREG_VOLTAGE_1_10,
const struct dvi_serialiser_cfg &c = pimoroni_demo_hdmi_cfg);
~DVIGFX8x2(void);
bool begin(void);
uint16_t *getPalette(void) { return palette[back_index]; }
void setColor(uint8_t idx, uint16_t color) {
palette[back_index][idx] = color;
}
void setColor(uint8_t idx, uint8_t red, uint8_t green, uint8_t blue) {
palette[back_index][idx] =
((red & 0xF8) << 8) | ((green & 0xFC) << 3) | (blue >> 3);
}
uint16_t getColor(uint8_t idx) { return palette[back_index][idx]; }
void swap(bool copy_framebuffer = false, bool copy_palette = false);
void _scanline_callback(void);
protected:
uint16_t palette[2][256]; // Double-buffered palette
uint16_t *row565[2]; // 2 scanlines of 16-bit RGB565 data
uint16_t scanline = 2; // First 2 scanlines are set up before DVI start
uint8_t rowidx = 1; // Alternate 0/1 for which row565[] is active
uint8_t *buffer_save; // Original canvas buffer pointer
uint8_t back_index = 0; // Which of 2 buffers receives draw ops
volatile bool swap_wait = 0; // For syncronizing front/back buffer swap
};