Adafruit_Blinka_Raspberry_P.../src/include/piomatter/render.h
Jeff Epler 68c24fcc00 allow multiple schedules for temporal dithering
(however, there's no code yet to generate such schedules)
2025-03-09 14:04:13 -05:00

248 lines
8 KiB
C++

#pragma once
#include "matrixmap.h"
#include <cassert>
#include <span>
#include <vector>
namespace piomatter {
constexpr int DATA_OVERHEAD = 3;
constexpr int CLOCKS_PER_DATA = 2;
constexpr int DELAY_OVERHEAD = 5;
constexpr int CLOCKS_PER_DELAY = 1;
constexpr uint32_t command_data = 1u << 31;
constexpr uint32_t command_delay = 0;
struct gamma_lut {
gamma_lut(double exponent = 2.2) {
for (int i = 0; i < 256; i++) {
auto v = std::max(i, int(round(1023 * pow(i / 255., exponent))));
lut[i] = v;
}
}
unsigned convert(unsigned v) {
if (v >= std::size(lut))
return 1023;
return lut[v];
}
void convert_rgb888_packed_to_rgb10(std::vector<uint32_t> &result,
std::span<const uint8_t> source) {
result.resize(source.size() / 3);
for (size_t i = 0, j = 0; i < source.size(); i += 3) {
uint32_t r = source[i + 0] & 0xff;
uint32_t g = source[i + 1] & 0xff;
uint32_t b = source[i + 2] & 0xff;
result[j++] = (convert(r) << 20) | (convert(g) << 10) | convert(b);
}
}
void convert_rgb888_to_rgb10(std::vector<uint32_t> &result,
std::span<const uint32_t> source) {
result.resize(source.size());
for (size_t i = 0; i < source.size(); i++) {
uint32_t data = source[i];
uint32_t r = (data >> 16) & 0xff;
uint32_t g = (data >> 8) & 0xff;
uint32_t b = data & 0xff;
result[i] = (convert(r) << 20) | (convert(g) << 10) | convert(b);
}
}
void convert_rgb565_to_rgb10(std::vector<uint32_t> &result,
std::span<const uint16_t> source) {
result.resize(source.size());
for (size_t i = 0; i < source.size(); i++) {
uint32_t data = source[i];
unsigned r5 = (data >> 11) & 0x1f;
unsigned r = (r5 << 3) | (r5 >> 2);
unsigned g6 = (data >> 5) & 0x3f;
unsigned g = (g6 << 2) | (g6 >> 4);
unsigned b5 = (data)&0x1f;
unsigned b = (b5 << 3) | (b5 >> 2);
result[i] = (convert(r) << 20) | (convert(g) << 10) | convert(b);
}
}
uint16_t lut[256];
};
struct colorspace_rgb565 {
using data_type = uint16_t;
static constexpr size_t data_size_in_bytes(size_t n_pixels) {
return sizeof(data_type) * n_pixels;
}
colorspace_rgb565(float gamma = 2.2) : lut{gamma} {}
gamma_lut lut;
const std::span<const uint32_t>
convert(std::span<const data_type> data_in) {
lut.convert_rgb565_to_rgb10(rgb10, data_in);
return rgb10;
}
std::vector<uint32_t> rgb10;
};
struct colorspace_rgb888 {
using data_type = uint32_t;
static constexpr size_t data_size_in_bytes(size_t n_pixels) {
return sizeof(data_type) * n_pixels;
}
colorspace_rgb888(float gamma = 2.2) : lut{gamma} {}
gamma_lut lut;
const std::span<const uint32_t>
convert(std::span<const data_type> data_in) {
lut.convert_rgb888_to_rgb10(rgb10, data_in);
return rgb10;
}
std::vector<uint32_t> rgb10;
};
struct colorspace_rgb888_packed {
using data_type = uint8_t;
static constexpr size_t data_size_in_bytes(size_t n_pixels) {
return sizeof(data_type) * n_pixels * 3;
}
colorspace_rgb888_packed(float gamma = 2.2) : lut{gamma} {}
gamma_lut lut;
const std::span<const uint32_t>
convert(std::span<const data_type> data_in) {
lut.convert_rgb888_packed_to_rgb10(rgb10, data_in);
return rgb10;
}
std::vector<uint32_t> rgb10;
};
struct colorspace_rgb10 {
using data_type = uint32_t;
const std::span<const uint32_t>
convert(std::span<const data_type> data_in) {
return data_in;
}
};
// Render a buffer in linear RGB10 format into a piomatter stream
template <typename pinout>
void protomatter_render_rgb10(std::vector<uint32_t> &result,
const matrix_geometry &matrixmap,
const schedule &sched, const uint32_t *pixels) {
result.clear();
int data_count = 0;
auto do_data_delay = [&](uint32_t data, int32_t delay) {
delay = std::max((delay / CLOCKS_PER_DELAY) - DELAY_OVERHEAD, 1);
assert(delay < 1000000);
assert(!data_count);
result.push_back(command_delay | (delay ? delay - 1 : 0));
result.push_back(data);
};
auto prep_data = [&data_count, &result](uint32_t n) {
assert(!data_count);
assert(n);
assert(n < 60000);
result.push_back(command_data | (n - 1));
data_count = n;
};
int32_t active_time;
auto do_data_clk_active = [&active_time, &data_count, &result](uint32_t d) {
bool active = active_time > 0;
active_time--;
d |= active ? pinout::oe_active : pinout::oe_inactive;
assert(data_count);
data_count--;
result.push_back(d);
};
auto calc_addr_bits = [](int addr) {
uint32_t data = 0;
if (addr & 1)
data |= (1 << pinout::PIN_ADDR[0]);
if (addr & 2)
data |= (1 << pinout::PIN_ADDR[1]);
if (addr & 4)
data |= (1 << pinout::PIN_ADDR[2]);
if constexpr (std::size(pinout::PIN_ADDR) >= 4) {
if (addr & 8)
data |= (1 << pinout::PIN_ADDR[3]);
}
if constexpr (std::size(pinout::PIN_ADDR) >= 5) {
if (addr & 16)
data |= (1 << pinout::PIN_ADDR[4]);
}
return data;
};
// illuminate the right row for data in the shift register (the previous
// address)
const size_t n_addr = 1u << matrixmap.n_addr_lines;
constexpr size_t n_bits = 10u;
const size_t pixels_across = matrixmap.pixels_across;
size_t prev_addr = n_addr - 1;
uint32_t addr_bits = calc_addr_bits(prev_addr);
for (size_t addr = 0; addr < n_addr; addr++) {
uint32_t active_time = sched.back().active_time;
for (auto &schedule_ent : sched) {
uint32_t r_mask = 1 << (20 + schedule_ent.shift);
uint32_t g_mask = 1 << (10 + schedule_ent.shift);
uint32_t b_mask = 1 << (0 + schedule_ent.shift);
prep_data(pixels_across);
auto mapiter = matrixmap.map.begin() +
matrixmap.n_lanes * addr * pixels_across;
for (size_t x = 0; x < pixels_across; x++) {
uint32_t data = addr_bits;
for (size_t px = 0; px < matrixmap.n_lanes; px++) {
assert(mapiter != matrixmap.map.end());
auto pixel0 = pixels[*mapiter++];
auto r_bit = pixel0 & r_mask;
auto g_bit = pixel0 & g_mask;
auto b_bit = pixel0 & b_mask;
if (r_bit)
data |= (1 << pinout::PIN_RGB[px * 3 + 0]);
if (g_bit)
data |= (1 << pinout::PIN_RGB[px * 3 + 1]);
if (b_bit)
data |= (1 << pinout::PIN_RGB[px * 3 + 2]);
}
do_data_clk_active(data);
}
do_data_delay(addr_bits | pinout::oe_active,
active_time * CLOCKS_PER_DATA / CLOCKS_PER_DELAY -
DELAY_OVERHEAD);
do_data_delay(addr_bits | pinout::oe_inactive,
pinout::post_oe_delay);
do_data_delay(addr_bits | pinout::oe_inactive | pinout::lat_bit,
pinout::post_latch_delay);
active_time = schedule_ent.active_time;
// with oe inactive, set address bits to illuminate THIS line
if (addr != prev_addr) {
addr_bits = calc_addr_bits(addr);
do_data_delay(addr_bits | pinout::oe_inactive,
pinout::post_addr_delay);
prev_addr = addr;
}
}
}
}
} // namespace piomatter