GPIO High and palette mode support

dv_stick
Mike Bell 2023-05-24 00:30:51 +01:00 zatwierdzone przez Phil Howard
rodzic 5a6aa0186c
commit a7435c6a5e
8 zmienionych plików z 4172 dodań i 2751 usunięć

Wyświetl plik

@ -57,11 +57,15 @@ namespace pimoroni {
swd_load_program(section_addresses, section_data, section_data_len, sizeof(section_addresses) / sizeof(section_addresses[0]), 0x20000001, 0x15004000, true);
ram.init();
write_header(0);
bank = 0;
write_header();
sleep_us(100);
gpio_put(RAM_SEL, 1);
ram.init();
write_header(1);
bank = 1;
write_header();
sleep_us(100);
bank = 0;
gpio_put(RAM_SEL, 0);
@ -78,24 +82,74 @@ namespace pimoroni {
}
void DVDisplay::flip() {
if (pixel_buffer_location.y != -1) {
ram.write(pointToAddress(pixel_buffer_location), pixel_buffer, pixel_buffer_x << 1);
if (use_palette_mode) {
write_palette();
if (pixel_buffer_location.y != -1) {
ram.write(point_to_address_palette(pixel_buffer_location), pixel_buffer, pixel_buffer_x);
pixel_buffer_location.y = -1;
}
}
else if (pixel_buffer_location.y != -1) {
ram.write(point_to_address(pixel_buffer_location), pixel_buffer, pixel_buffer_x << 1);
pixel_buffer_location.y = -1;
}
bank ^= 1;
ram.wait_for_finish_blocking();
while (gpio_get(VSYNC) == 0);
gpio_put(RAM_SEL, bank);
if (rewrite_header) {
write_header();
rewrite_header = false;
}
}
uint8_t DVDisplay::get_driver_gpio() {
uint8_t DVDisplay::get_gpio() {
return i2c.reg_read_uint8(I2C_ADDR, I2C_REG_GPIO);
}
uint8_t DVDisplay::get_driver_gpio_hi() {
uint8_t DVDisplay::get_gpio_hi() {
return i2c.reg_read_uint8(I2C_ADDR, I2C_REG_GPIO_HI);
}
void DVDisplay::i2c_modify_bit(uint8_t reg, uint bit, bool enable) {
uint8_t val = i2c.reg_read_uint8(I2C_ADDR, reg);
if (enable) val |= 1u << bit;
else val &= ~(1u << bit);
i2c.reg_write_uint8(I2C_ADDR, reg, val);
}
void DVDisplay::set_gpio_hi_dir(uint pin, bool output) {
i2c_modify_bit(I2C_REG_GPIO_HI_OE, pin, output);
}
void DVDisplay::set_gpio_hi_dir_all(uint8_t val) {
i2c.reg_write_uint8(I2C_ADDR, I2C_REG_GPIO_HI_OE, val);
}
void DVDisplay::set_gpio_hi(uint pin, bool on) {
i2c_modify_bit(I2C_REG_GPIO_HI_OUT, pin, on);
}
void DVDisplay::set_gpio_hi_all(uint8_t val) {
i2c.reg_write_uint8(I2C_ADDR, I2C_REG_GPIO_HI_OUT, val);
}
void DVDisplay::set_gpio_hi_pull_up(uint pin, bool on) {
i2c_modify_bit(I2C_REG_GPIO_HI_PULL_UP, pin, on);
}
void DVDisplay::set_gpio_hi_pull_up_all(uint8_t val) {
i2c.reg_write_uint8(I2C_ADDR, I2C_REG_GPIO_HI_PULL_UP, val);
}
void DVDisplay::set_gpio_hi_pull_down(uint pin, bool on) {
i2c_modify_bit(I2C_REG_GPIO_HI_PULL_DOWN, pin, on);
}
void DVDisplay::set_gpio_hi_pull_down_all(uint8_t val) {
i2c.reg_write_uint8(I2C_ADDR, I2C_REG_GPIO_HI_PULL_DOWN, val);
}
void DVDisplay::set_led_level(uint8_t level) {
i2c.reg_write_uint8(I2C_ADDR, I2C_REG_LED, level | 0x80);
}
@ -120,19 +174,32 @@ namespace pimoroni {
ram.read_blocking(address, (uint32_t*)data, (len + 1) >> 1);
}
void DVDisplay::write(uint32_t address, size_t len, const uint8_t colour)
{
uint32_t val = colour | ((uint32_t)colour << 16);
val |= val << 8;
ram.write_repeat(address, val, len);
}
void DVDisplay::read(uint32_t address, size_t len, uint8_t *data)
{
ram.read_blocking(address, (uint32_t*)data, len);
}
void DVDisplay::write_pixel(const Point &p, uint16_t colour)
{
if (pixel_buffer_location.y == p.y && pixel_buffer_location.x + pixel_buffer_x == p.x) {
if (pixel_buffer_x & 1) pixel_buffer[pixel_buffer_x >> 1] |= (uint32_t)colour << 16;
else pixel_buffer[pixel_buffer_x >> 1] = colour;
if (++pixel_buffer_x == PIXEL_BUFFER_LEN_IN_WORDS * 2) {
ram.write(pointToAddress(pixel_buffer_location), pixel_buffer, PIXEL_BUFFER_LEN_IN_WORDS * 4);
ram.write(point_to_address(pixel_buffer_location), pixel_buffer, PIXEL_BUFFER_LEN_IN_WORDS * 4);
pixel_buffer_location.y = -1;
}
return;
}
else if (pixel_buffer_location.y != -1) {
ram.write(pointToAddress(pixel_buffer_location), pixel_buffer, pixel_buffer_x << 1);
ram.write(point_to_address(pixel_buffer_location), pixel_buffer, pixel_buffer_x << 1);
}
pixel_buffer_location = p;
pixel_buffer_x = 1;
@ -141,7 +208,7 @@ namespace pimoroni {
void DVDisplay::write_pixel_span(const Point &p, uint l, uint16_t colour)
{
write(pointToAddress(p), l, colour);
write(point_to_address(p), l, colour);
}
void DVDisplay::write_pixel_span(const Point &p, uint l, uint16_t *data)
@ -149,21 +216,92 @@ namespace pimoroni {
uint32_t offset = 0;
if (((uintptr_t)data & 0x2) != 0) {
uint32_t val = *data++;
ram.write(pointToAddress(p), &val, 2);
ram.write(point_to_address(p), &val, 2);
--l;
offset = 2;
}
if (l > 0) {
ram.write(pointToAddress(p) + offset, (uint32_t*)data, l << 1);
ram.write(point_to_address(p) + offset, (uint32_t*)data, l << 1);
}
}
void DVDisplay::read_pixel_span(const Point &p, uint l, uint16_t *data)
{
read(pointToAddress(p), l, data);
read(point_to_address(p), l, data);
}
void DVDisplay::write_header(uint bank)
void DVDisplay::enable_palette(bool enable)
{
use_palette_mode = enable;
rewrite_header = true;
write_header();
write_palette();
}
void DVDisplay::set_palette(RGB888 new_palette[PALETTE_SIZE])
{
for (int i = 0; i < PALETTE_SIZE; ++i) {
set_palette_colour(i, new_palette[i]);
}
}
void DVDisplay::set_palette_colour(uint8_t entry, RGB888 colour)
{
palette[entry * 3] = (colour >> 16) & 0xFF;
palette[entry * 3 + 1] = (colour >> 8) & 0xFF;
palette[entry * 3 + 2] = colour & 0xFF;
}
void DVDisplay::write_palette()
{
uint addr = (height + 7) * 4;
ram.write(addr, (uint32_t*)palette, PALETTE_SIZE * 3);
}
void DVDisplay::write_palette_pixel(const Point &p, uint8_t colour)
{
if (pixel_buffer_location.y == p.y && pixel_buffer_location.x + pixel_buffer_x == p.x) {
if (pixel_buffer_x & 3) pixel_buffer[pixel_buffer_x >> 2] |= (uint32_t)colour << ((pixel_buffer_x & 3) << 3);
else pixel_buffer[pixel_buffer_x >> 2] = colour;
if (++pixel_buffer_x == PIXEL_BUFFER_LEN_IN_WORDS * 4) {
ram.write(point_to_address_palette(pixel_buffer_location), pixel_buffer, PIXEL_BUFFER_LEN_IN_WORDS * 4);
pixel_buffer_location.y = -1;
}
return;
}
else if (pixel_buffer_location.y != -1) {
ram.write(point_to_address_palette(pixel_buffer_location), pixel_buffer, pixel_buffer_x);
}
pixel_buffer_location = p;
pixel_buffer_x = 1;
pixel_buffer[0] = colour;
}
void DVDisplay::write_palette_pixel_span(const Point &p, uint l, uint8_t colour)
{
write(point_to_address_palette(p), l, colour);
}
void DVDisplay::write_palette_pixel_span(const Point &p, uint l, uint8_t* data)
{
uint32_t offset = 0;
while (((uintptr_t)data & 0x3) != 0 && l > 0) {
uint32_t val = *data++;
ram.write(point_to_address_palette(p), &val, 1);
--l;
offset++;
}
if (l > 0) {
ram.write(point_to_address_palette(p) + offset, (uint32_t*)data, l);
}
}
void DVDisplay::read_palette_pixel_span(const Point &p, uint l, uint8_t *data)
{
read(point_to_address_palette(p), l, data);
}
void DVDisplay::write_header()
{
uint32_t buf[8];
uint32_t full_width = width * h_repeat;
@ -173,20 +311,20 @@ namespace pimoroni {
buf[3] = (uint32_t)height << 16;
buf[4] = 0x00000001;
buf[5] = 0x00010000 + height + (bank << 24);
buf[6] = 0x00000000;
buf[6] = 0x00000001;
ram.write(0, buf, 7 * 4);
ram.wait_for_finish_blocking();
uint addr = 4 * 7;
uint line_type = 0x90000000u;
if (use_palette_mode) line_type = 0xa0000000u;
for (int i = 0; i < height; i += 8) {
for (int j = 0; j < 8; ++j) {
buf[j] = 0x90000000 + ((uint32_t)h_repeat << 24) + ((i + j) * width * 2) + base_address;
buf[j] = line_type + ((uint32_t)h_repeat << 24) + ((i + j) * width * 2) + base_address;
}
ram.write(addr, buf, 8 * 4);
ram.wait_for_finish_blocking();
addr += 4 * 8;
}
sleep_us(100);
}
}

Wyświetl plik

@ -15,7 +15,7 @@
namespace pimoroni {
// This is ARGB1555 only for now
class DVDisplay : public IDirectDisplayDriver<uint16_t> {
class DVDisplay : public IDirectDisplayDriver<uint16_t>, public IPaletteDisplayDriver {
//--------------------------------------------------
// Variables
//--------------------------------------------------
@ -41,6 +41,10 @@ namespace pimoroni {
static constexpr uint I2C_REG_GPIO = 0xC0;
static constexpr uint I2C_REG_LED = 0xC1;
static constexpr uint I2C_REG_GPIO_HI = 0xC8;
static constexpr uint I2C_REG_GPIO_HI_OUT = 0xC9;
static constexpr uint I2C_REG_GPIO_HI_OE = 0xCA;
static constexpr uint I2C_REG_GPIO_HI_PULL_UP = 0xCB;
static constexpr uint I2C_REG_GPIO_HI_PULL_DOWN = 0xCC;
static constexpr uint I2C_REG_EDID = 0xED;
static constexpr uint32_t base_address = 0x10000;
@ -51,6 +55,8 @@ namespace pimoroni {
uint8_t v_repeat = 1;
public:
static constexpr int PALETTE_SIZE = 32;
// Valid resolutions are:
// 640x480 (60Hz), 720x480 (60Hz), 720x400 (70Hz), 720x576 (50Hz)
// 800x600 (60Hz), 800x480 (60Hz), 800x450 (60Hz), 960x540 (50Hz), 1280x720 (30Hz)
@ -104,11 +110,31 @@ namespace pimoroni {
void init();
void flip();
uint8_t get_driver_gpio();
uint8_t get_driver_gpio_hi();
// 32 colour palette mode. Note that palette entries range from 0-31,
// but when writing colour values the palette entry is in bits 6-2, so the
// entry value is effectively multiplied by 4.
void enable_palette(bool enable);
void set_palette(RGB888 palette[PALETTE_SIZE]);
void set_palette_colour(uint8_t entry, RGB888 colour);
void write_palette_pixel(const Point &p, uint8_t colour);
void write_palette_pixel_span(const Point &p, uint l, uint8_t colour);
void write_palette_pixel_span(const Point &p, uint l, uint8_t* data);
void read_palette_pixel_span(const Point &p, uint l, uint8_t *data);
bool is_button_b_pressed() { return (get_driver_gpio() & 0x1) != 0x1; }
bool is_button_c_pressed() { return (get_driver_gpio() & 0x2) != 0x2; }
uint8_t get_gpio();
uint8_t get_gpio_hi();
void set_gpio_hi_dir(uint pin, bool output);
void set_gpio_hi_dir_all(uint8_t output_enables);
void set_gpio_hi(uint pin, bool on);
void set_gpio_hi_all(uint8_t vals);
void set_gpio_hi_pull_up(uint pin, bool on);
void set_gpio_hi_pull_up_all(uint8_t vals);
void set_gpio_hi_pull_down(uint pin, bool on);
void set_gpio_hi_pull_down_all(uint8_t vals);
bool is_button_b_pressed() { return (get_gpio() & 0x1) != 0x1; }
bool is_button_c_pressed() { return (get_gpio() & 0x2) != 0x2; }
// Valid LED levels are from 0-127.
void set_led_level(uint8_t level);
@ -119,6 +145,10 @@ namespace pimoroni {
void get_edid(uint8_t* edid);
private:
uint8_t palette[PALETTE_SIZE * 3] alignas(4);
bool use_palette_mode = false;
bool rewrite_header = false;
static constexpr int PIXEL_BUFFER_LEN_IN_WORDS = 16;
uint32_t pixel_buffer[PIXEL_BUFFER_LEN_IN_WORDS];
Point pixel_buffer_location;
@ -126,12 +156,20 @@ namespace pimoroni {
void write(uint32_t address, size_t len, const uint16_t colour);
void read(uint32_t address, size_t len, uint16_t *data);
void write(uint32_t address, size_t len, const uint8_t colour);
void read(uint32_t address, size_t len, uint8_t *data);
void write_header(uint bank);
void write_palette();
void write_header();
uint32_t pointToAddress(const Point &p)
{
void i2c_modify_bit(uint8_t reg, uint bit, bool enable);
uint32_t point_to_address(const Point &p) {
return base_address + ((p.y * (uint32_t)width) + p.x) * 2;
}
uint32_t point_to_address_palette(const Point &p) {
return base_address + (p.y * (uint32_t)width * 2) + p.x;
}
};
}

Wyświetl plik

@ -61,13 +61,21 @@ int main() {
}
#endif
PicoGraphics_PenDV_RGB555 graphics(FRAME_WIDTH, FRAME_HEIGHT, display);
display.enable_palette(true);
PicoGraphics_PenDV_P5 graphics(FRAME_WIDTH, FRAME_HEIGHT, display);
graphics.set_pen(0x001F);
graphics.create_pen(0, 0, 0);
graphics.create_pen(0xFF, 0xFF, 0xFF);
for (int i = 0; i < 25; ++i) {
graphics.create_pen_hsv(i * 0.04f, 1.0f, 1.0f);
}
graphics.set_pen(0xFF, 0, 0);
graphics.clear();
display.flip();
sleep_ms(2000);
graphics.set_pen(0x7C00);
graphics.set_pen(0, 0, 0xFF);
graphics.clear();
display.flip();
@ -76,7 +84,7 @@ int main() {
constexpr int NUM_CIRCLES = 50;
struct Circle {
uint16_t x, y, size, grow;
uint16_t x, y, size, grow, pen;
} circles[NUM_CIRCLES];
for(int i =0 ; i < 50 ; i++)
@ -85,6 +93,7 @@ int main() {
circles[i].grow = std::max(0, (rand() % 50) - 25);
circles[i].x = rand() % graphics.bounds.w;
circles[i].y = rand() % graphics.bounds.h;
circles[i].pen = 2 + (i >> 1);
}
int frames = 0;
@ -94,17 +103,18 @@ int main() {
//}
uint32_t render_start_time = time_us_32();
graphics.set_pen(0xFFFF);
graphics.set_pen(0xFF, 0xFF, 0xFF);
graphics.clear();
#if 0
for (uint i = 0; i < 128; i++) {
for (uint j = 0; j < 256; j++) {
graphics.set_pen((j << 7) | i);
RGB555 col = (j << 7) | i;
graphics.set_pen((col << 3) & 0xF8, (col >> 2) & 0xF8, (col >> 7) & 0xF8);
graphics.pixel(Point(j, i));
}
}
#if 0
for (uint i = 0; i < 128; i++) {
for (uint j = 0; j < 256; j++) {
graphics.set_pen((j << 7) | i);
@ -115,10 +125,12 @@ int main() {
for(int i =0 ; i < NUM_CIRCLES ; i++)
{
graphics.set_pen(0);
graphics.set_pen(0, 0, 0);
graphics.circle(Point(circles[i].x, circles[i].y), circles[i].size);
graphics.set_pen(RGB::from_hsv(i * 0.02f, 1.0f, 1.0f).to_rgb555());
//RGB col = RGB::from_hsv(i * 0.02f, 1.0f, 1.0f);
//graphics.set_pen(col.r, col.g, col.b);
graphics.set_pen(circles[i].pen);
graphics.circle(Point(circles[i].x, circles[i].y), circles[i].size-2);
if (circles[i].grow) {
circles[i].size++;
@ -149,15 +161,19 @@ int main() {
gpio_get(BUTTON_A) == 0 ? "A" : " ",
display.is_button_b_pressed() ? "B" : " ",
display.is_button_c_pressed() ? "C" : " ");
graphics.set_pen(0);
graphics.set_pen(0, 0, 0);
graphics.text(buffer, {500,10}, FRAME_WIDTH - 500, 3);
uint32_t flip_start_time = time_us_32();
display.flip();
uint32_t flip_time = time_us_32() - flip_start_time;
printf("Render: %.3f, flip: %.3f\n", render_time / 1000.f, flip_time / 1000.f);
if (false) printf("Render: %.3f, flip: %.3f\n", render_time / 1000.f, flip_time / 1000.f);
//printf("%02x %02x\n", display.get_gpio(), display.get_gpio_hi());
++frames;
display.set_gpio_hi_pull_up_all(frames & 0x3F);
display.set_gpio_hi_pull_down_all(~(frames & 0x3F));
if (gpio_get(BUTTON_A) == 0) display.set_led_level((uint8_t)frames);
else display.set_led_heartbeat();
}

Wyświetl plik

@ -19,6 +19,7 @@ add_library(pico_graphics
${CMAKE_CURRENT_LIST_DIR}/pico_graphics_pen_rgb888.cpp
${CMAKE_CURRENT_LIST_DIR}/pico_graphics_pen_inky7.cpp
${CMAKE_CURRENT_LIST_DIR}/pico_graphics_pen_dv_rgb555.cpp
${CMAKE_CURRENT_LIST_DIR}/pico_graphics_pen_dv_p5.cpp
)
target_include_directories(pico_graphics INTERFACE ${CMAKE_CURRENT_LIST_DIR})

Wyświetl plik

@ -202,7 +202,8 @@ namespace pimoroni {
PEN_RGB565,
PEN_RGB888,
PEN_INKY7,
PEN_DV_RGB555
PEN_DV_RGB555,
PEN_DV_P5
};
void *frame_buffer;
@ -538,6 +539,12 @@ namespace pimoroni {
virtual void read_pixel_span(const Point &p, uint l, T *data) {};
};
class IPaletteDisplayDriver {
public:
virtual void write_palette_pixel(const Point &p, uint8_t colour) = 0;
virtual void write_palette_pixel_span(const Point &p, uint l, uint8_t colour) = 0;
virtual void set_palette_colour(uint8_t entry, RGB888 colour) = 0;
};
class PicoGraphics_PenInky7 : public PicoGraphics {
public:
@ -607,4 +614,37 @@ namespace pimoroni {
return w * h * sizeof(RGB555);
}
};
class PicoGraphics_PenDV_P5 : public PicoGraphics {
public:
static const uint16_t palette_size = 32;
uint8_t color;
IPaletteDisplayDriver &driver;
RGB palette[palette_size];
bool used[palette_size];
std::array<std::array<uint8_t, 16>, 512> candidate_cache;
bool cache_built = false;
std::array<uint8_t, 16> candidates;
PicoGraphics_PenDV_P5(uint16_t width, uint16_t height, IPaletteDisplayDriver &dv_display);
void set_pen(uint c) override;
void set_pen(uint8_t r, uint8_t g, uint8_t b) override;
int update_pen(uint8_t i, uint8_t r, uint8_t g, uint8_t b) override;
int create_pen(uint8_t r, uint8_t g, uint8_t b) override;
int create_pen_hsv(float h, float s, float v) override;
int reset_pen(uint8_t i) override;
int get_palette_size() override {return palette_size;};
RGB* get_palette() override {return palette;};
void set_pixel(const Point &p) override;
void set_pixel_span(const Point &p, uint l) override;
void get_dither_candidates(const RGB &col, const RGB *palette, size_t len, std::array<uint8_t, 16> &candidates);
void set_pixel_dither(const Point &p, const RGB &c) override;
static size_t buffer_size(uint w, uint h) {
return w * h;
}
};
}

Wyświetl plik

@ -0,0 +1,110 @@
#include "pico_graphics.hpp"
namespace pimoroni {
PicoGraphics_PenDV_P5::PicoGraphics_PenDV_P5(uint16_t width, uint16_t height, IPaletteDisplayDriver &palette_display_driver)
: PicoGraphics(width, height, nullptr),
driver(palette_display_driver)
{
this->pen_type = PEN_DV_P5;
for(auto i = 0u; i < palette_size; i++) {
palette[i] = {
uint8_t(i << 3),
uint8_t(i << 3),
uint8_t(i << 3)
};
driver.set_palette_colour(i, palette[i].to_rgb888());
used[i] = false;
}
cache_built = false;
}
void PicoGraphics_PenDV_P5::set_pen(uint c) {
color = c & 0x1f;
}
void PicoGraphics_PenDV_P5::set_pen(uint8_t r, uint8_t g, uint8_t b) {
int pen = RGB(r, g, b).closest(palette, palette_size);
if(pen != -1) color = pen;
}
int PicoGraphics_PenDV_P5::update_pen(uint8_t i, uint8_t r, uint8_t g, uint8_t b) {
i &= 0x1f;
used[i] = true;
palette[i] = {r, g, b};
cache_built = false;
driver.set_palette_colour(i, palette[i].to_rgb888());
return i;
}
int PicoGraphics_PenDV_P5::create_pen(uint8_t r, uint8_t g, uint8_t b) {
// Create a colour and place it in the palette if there's space
for(auto i = 0u; i < palette_size; i++) {
if(!used[i]) {
palette[i] = {r, g, b};
used[i] = true;
cache_built = false;
driver.set_palette_colour(i, palette[i].to_rgb888());
return i;
}
}
return -1;
}
int PicoGraphics_PenDV_P5::create_pen_hsv(float h, float s, float v) {
RGB p = RGB::from_hsv(h, s, v);
return create_pen(p.r, p.g, p.b);
}
int PicoGraphics_PenDV_P5::reset_pen(uint8_t i) {
palette[i] = {0, 0, 0};
used[i] = false;
cache_built = false;
return i;
}
void PicoGraphics_PenDV_P5::set_pixel(const Point &p) {
driver.write_palette_pixel(p, color << 2);
}
void PicoGraphics_PenDV_P5::set_pixel_span(const Point &p, uint l) {
driver.write_palette_pixel_span(p, l, color << 2);
}
void PicoGraphics_PenDV_P5::get_dither_candidates(const RGB &col, const RGB *palette, size_t len, std::array<uint8_t, 16> &candidates) {
RGB error;
for(size_t i = 0; i < candidates.size(); i++) {
candidates[i] = (col + error).closest(palette, len);
error += (col - palette[candidates[i]]);
}
// sort by a rough approximation of luminance, this ensures that neighbouring
// pixels in the dither matrix are at extreme opposites of luminence
// giving a more balanced output
std::sort(candidates.begin(), candidates.end(), [palette](int a, int b) {
return palette[a].luminance() > palette[b].luminance();
});
}
void PicoGraphics_PenDV_P5::set_pixel_dither(const Point &p, const RGB &c) {
if(!bounds.contains(p)) return;
uint used_palette_entries = 0;
for(auto i = 0u; i < palette_size; i++) {
if(!used[i]) break;
used_palette_entries++;
}
if(!cache_built) {
for(uint i = 0; i < 512; i++) {
RGB cache_col((i & 0x1C0) >> 1, (i & 0x38) << 2, (i & 0x7) << 5);
get_dither_candidates(cache_col, palette, used_palette_entries, candidate_cache[i]);
}
cache_built = true;
}
uint cache_key = ((c.r & 0xE0) << 1) | ((c.g & 0xE0) >> 2) | ((c.b & 0xE0) >> 5);
//get_dither_candidates(c, palette, 256, candidates);
// find the pattern coordinate offset
uint pattern_index = (p.x & 0b11) | ((p.y & 0b11) << 2);
// set the pixel
//color = candidates[pattern[pattern_index]];
color = candidate_cache[cache_key][dither16_pattern[pattern_index]];
set_pixel(p);
}
}