Make temporal dithering schedules actually work
10/4 planes gives 100fps on the active3 spiral demo on 3 64x64 panels, or 1.2 megapixels/second. And to my eye, there's no brightness shimmer. All the below settings "look good" to my eye, higher FPS ones tend to look better to a camera. In "10/4" mode with my camera at 100FPS it still looks solid but because the beat frequency between the dither pattern and the shutter is pretty pronounced I can see it shift between the sub-frames. There's still some brightness variation between modes, with more planes being a little brighter than fewer planes. 10/0: 50fps 10/2: 72fps 10/4: 100fps 8/0: 92fps 8/2: 109fps 8/4: 134fps 5/0: 135fps 5/2: 153fps 5/4: 210fps
This commit is contained in:
parent
e2c5bc3467
commit
2442bee476
5 changed files with 67 additions and 30 deletions
|
|
@ -34,7 +34,6 @@ def make_pixelmap_multilane(width, height, n_addr_lines, n_lanes):
|
||||||
for lane in range(n_lanes):
|
for lane in range(n_lanes):
|
||||||
y = addr + lane * n_addr
|
y = addr + lane * n_addr
|
||||||
m.append(x + width * y)
|
m.append(x + width * y)
|
||||||
print(m)
|
|
||||||
return m
|
return m
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -42,7 +41,7 @@ canvas = Image.new('RGB', (width, height), (0, 0, 0))
|
||||||
draw = ImageDraw.Draw(canvas)
|
draw = ImageDraw.Draw(canvas)
|
||||||
|
|
||||||
pixelmap = make_pixelmap_multilane(width, height, n_addr_lines, n_lanes)
|
pixelmap = make_pixelmap_multilane(width, height, n_addr_lines, n_lanes)
|
||||||
geometry = piomatter.Geometry(width=width, height=height, n_addr_lines=n_addr_lines, n_planes=8, map=pixelmap, n_lanes=n_lanes)
|
geometry = piomatter.Geometry(width=width, height=height, n_addr_lines=n_addr_lines, n_planes=10, n_temporal_planes=4, map=pixelmap, n_lanes=n_lanes)
|
||||||
framebuffer = np.asarray(canvas) + 0 # Make a mutable copy
|
framebuffer = np.asarray(canvas) + 0 # Make a mutable copy
|
||||||
matrix = piomatter.PioMatter(colorspace=piomatter.Colorspace.RGB888Packed,
|
matrix = piomatter.PioMatter(colorspace=piomatter.Colorspace.RGB888Packed,
|
||||||
pinout=piomatter.Pinout.Active3,
|
pinout=piomatter.Pinout.Active3,
|
||||||
|
|
@ -122,6 +121,7 @@ try:
|
||||||
draw.circle((x, pen_radius + ((step+1) * (pen_radius* 2) + (2 * (step+1)))), pen_radius, color)
|
draw.circle((x, pen_radius + ((step+1) * (pen_radius* 2) + (2 * (step+1)))), pen_radius, color)
|
||||||
update_matrix()
|
update_matrix()
|
||||||
|
|
||||||
|
print(matrix.fps)
|
||||||
clearing = not clearing
|
clearing = not clearing
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
|
|
|
||||||
|
|
@ -88,19 +88,34 @@ struct schedule_entry {
|
||||||
using schedule = std::vector<schedule_entry>;
|
using schedule = std::vector<schedule_entry>;
|
||||||
using schedule_sequence = std::vector<schedule>;
|
using schedule_sequence = std::vector<schedule>;
|
||||||
|
|
||||||
|
schedule_sequence rescale_schedule(schedule_sequence ss, size_t pixels_across) {
|
||||||
|
uint32_t max_active_time = 0;
|
||||||
|
for (auto &s : ss) {
|
||||||
|
for (auto &ent : s) {
|
||||||
|
max_active_time = std::max(ent.active_time, max_active_time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (max_active_time == 0 || max_active_time >= pixels_across) {
|
||||||
|
return ss;
|
||||||
|
}
|
||||||
|
int scale = (pixels_across + max_active_time - 1) / max_active_time;
|
||||||
|
for (auto &s : ss) {
|
||||||
|
for (auto &ent : s) {
|
||||||
|
ent.active_time *= scale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ss;
|
||||||
|
}
|
||||||
|
|
||||||
schedule_sequence make_simple_schedule(int n_planes, size_t pixels_across) {
|
schedule_sequence make_simple_schedule(int n_planes, size_t pixels_across) {
|
||||||
if (n_planes < 1 || n_planes > 10) {
|
if (n_planes < 1 || n_planes > 10) {
|
||||||
throw std::range_error("n_planes out of range");
|
throw std::range_error("n_planes out of range");
|
||||||
}
|
}
|
||||||
schedule result;
|
schedule result;
|
||||||
size_t max_count = 1 << n_planes;
|
|
||||||
while (max_count < pixels_across)
|
|
||||||
max_count <<= 1;
|
|
||||||
|
|
||||||
for (int i = 0; i < n_planes; i++) {
|
for (int i = 0; i < n_planes; i++) {
|
||||||
result.emplace_back(10 - i, max_count >> i);
|
result.emplace_back(9 - i, (1 << (n_planes - i - 1)));
|
||||||
}
|
}
|
||||||
return {result};
|
return rescale_schedule({result}, pixels_across);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make a temporal dither schedule. All the top `n_planes` are shown everytime,
|
// Make a temporal dither schedule. All the top `n_planes` are shown everytime,
|
||||||
|
|
@ -117,40 +132,49 @@ schedule_sequence make_temporal_dither_schedule(int n_planes,
|
||||||
return make_simple_schedule(n_planes, pixels_across);
|
return make_simple_schedule(n_planes, pixels_across);
|
||||||
}
|
}
|
||||||
if (n_temporal_planes >= n_planes) {
|
if (n_temporal_planes >= n_planes) {
|
||||||
throw std::range_error("n_temporal_planes out of range");
|
throw std::range_error("n_temporal_planes can't exceed n_planes");
|
||||||
}
|
}
|
||||||
if (n_temporal_planes != 2 && n_temporal_planes != 4) {
|
if (n_temporal_planes != 2 && n_temporal_planes != 4) {
|
||||||
throw std::range_error("n_temporal_planes out of range");
|
// the code can generate a schedule for 8 temporal planes, but it
|
||||||
|
// flickers intolerably
|
||||||
|
throw std::range_error("n_temporal_planes must be 0, 1, 2, or 4");
|
||||||
}
|
}
|
||||||
|
|
||||||
int n_real_planes = n_planes - n_temporal_planes;
|
int n_real_planes = n_planes - n_temporal_planes;
|
||||||
printf("n_planes = %d n_temporal_planes=%d n_real_planes=%d\n", n_planes,
|
|
||||||
n_temporal_planes, n_real_planes);
|
|
||||||
uint32_t max_count = 2 << n_real_planes;
|
|
||||||
uint32_t temporal_count = 1;
|
|
||||||
while (max_count < pixels_across) {
|
|
||||||
max_count <<= 1;
|
|
||||||
temporal_count <<= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
schedule base_sched;
|
schedule base_sched;
|
||||||
for (int j = 0; j < n_real_planes; j++) {
|
for (int j = 0; j < n_real_planes; j++) {
|
||||||
base_sched.emplace_back(10 - j, max_count >> j);
|
base_sched.emplace_back(
|
||||||
|
9 - j, (1 << (n_temporal_planes + n_real_planes - j - 1)) /
|
||||||
|
n_temporal_planes);
|
||||||
}
|
}
|
||||||
|
|
||||||
schedule_sequence result;
|
schedule_sequence result;
|
||||||
|
|
||||||
auto add_sched = [&result, &base_sched](int plane, int count) {
|
auto add_sched = [&result, &base_sched](int plane, int count) {
|
||||||
auto sched = base_sched;
|
auto sched = base_sched;
|
||||||
sched.emplace_back(10 - plane, count);
|
sched.emplace_back(9 - plane, count);
|
||||||
result.emplace_back(sched);
|
result.emplace_back(sched);
|
||||||
};
|
};
|
||||||
|
|
||||||
for (int i = 0; i < n_temporal_planes; i++) {
|
for (int i = 0; i < n_temporal_planes; i++) {
|
||||||
add_sched(n_real_planes + i, temporal_count << i);
|
add_sched(n_real_planes + i, 1 << (n_temporal_planes - i - 1));
|
||||||
}
|
}
|
||||||
|
#if 0
|
||||||
|
std::vector<uint32_t> counts(10, 0);
|
||||||
|
for (auto s : result) {
|
||||||
|
for(auto t: s) {
|
||||||
|
counts[t.shift] += t.active_time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto s : counts) {
|
||||||
|
printf("%d ", s);
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
return result;
|
return rescale_schedule(result, pixels_across);
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct matrix_geometry {
|
struct matrix_geometry {
|
||||||
|
|
@ -168,8 +192,8 @@ struct matrix_geometry {
|
||||||
matrix_map map, size_t n_lanes)
|
matrix_map map, size_t n_lanes)
|
||||||
: matrix_geometry(pixels_across, n_addr_lines, width, height, map,
|
: matrix_geometry(pixels_across, n_addr_lines, width, height, map,
|
||||||
n_lanes,
|
n_lanes,
|
||||||
make_temporal_dither_schedule(
|
make_temporal_dither_schedule(n_planes, pixels_across,
|
||||||
n_planes, n_temporal_planes, pixels_across)) {}
|
n_temporal_planes)) {}
|
||||||
|
|
||||||
matrix_geometry(size_t pixels_across, size_t n_addr_lines, size_t width,
|
matrix_geometry(size_t pixels_across, size_t n_addr_lines, size_t width,
|
||||||
size_t height, matrix_map map, size_t n_lanes,
|
size_t height, matrix_map map, size_t n_lanes,
|
||||||
|
|
|
||||||
|
|
@ -67,9 +67,12 @@ struct piomatter : piomatter_base {
|
||||||
auto &bufseq = buffers[buffer_idx];
|
auto &bufseq = buffers[buffer_idx];
|
||||||
bufseq.resize(geometry.schedules.size());
|
bufseq.resize(geometry.schedules.size());
|
||||||
auto converted = converter.convert(framebuffer);
|
auto converted = converter.convert(framebuffer);
|
||||||
|
auto old_active_time = geometry.schedules.back().back().active_time;
|
||||||
for (size_t i = 0; i < geometry.schedules.size(); i++) {
|
for (size_t i = 0; i < geometry.schedules.size(); i++) {
|
||||||
protomatter_render_rgb10<pinout>(
|
protomatter_render_rgb10<pinout>(bufseq[i], geometry,
|
||||||
bufseq[i], geometry, geometry.schedules[i], converted.data());
|
geometry.schedules[i],
|
||||||
|
old_active_time, converted.data());
|
||||||
|
old_active_time = geometry.schedules[i].back().active_time;
|
||||||
}
|
}
|
||||||
manager.put_filled_buffer(buffer_idx);
|
manager.put_filled_buffer(buffer_idx);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,8 @@ struct colorspace_rgb10 {
|
||||||
template <typename pinout>
|
template <typename pinout>
|
||||||
void protomatter_render_rgb10(std::vector<uint32_t> &result,
|
void protomatter_render_rgb10(std::vector<uint32_t> &result,
|
||||||
const matrix_geometry &matrixmap,
|
const matrix_geometry &matrixmap,
|
||||||
const schedule &sched, const uint32_t *pixels) {
|
const schedule &sched, uint32_t old_active_time,
|
||||||
|
const uint32_t *pixels) {
|
||||||
result.clear();
|
result.clear();
|
||||||
|
|
||||||
int data_count = 0;
|
int data_count = 0;
|
||||||
|
|
@ -153,7 +154,7 @@ void protomatter_render_rgb10(std::vector<uint32_t> &result,
|
||||||
data_count = n;
|
data_count = n;
|
||||||
};
|
};
|
||||||
|
|
||||||
int32_t active_time;
|
int32_t active_time = old_active_time;
|
||||||
|
|
||||||
auto do_data_clk_active = [&active_time, &data_count, &result](uint32_t d) {
|
auto do_data_clk_active = [&active_time, &data_count, &result](uint32_t d) {
|
||||||
bool active = active_time > 0;
|
bool active = active_time > 0;
|
||||||
|
|
@ -193,7 +194,6 @@ void protomatter_render_rgb10(std::vector<uint32_t> &result,
|
||||||
uint32_t addr_bits = calc_addr_bits(prev_addr);
|
uint32_t addr_bits = calc_addr_bits(prev_addr);
|
||||||
|
|
||||||
for (size_t addr = 0; addr < n_addr; addr++) {
|
for (size_t addr = 0; addr < n_addr; addr++) {
|
||||||
uint32_t active_time = sched.back().active_time;
|
|
||||||
for (auto &schedule_ent : sched) {
|
for (auto &schedule_ent : sched) {
|
||||||
uint32_t r_mask = 1 << (20 + schedule_ent.shift);
|
uint32_t r_mask = 1 << (20 + schedule_ent.shift);
|
||||||
uint32_t g_mask = 1 << (10 + schedule_ent.shift);
|
uint32_t g_mask = 1 << (10 + schedule_ent.shift);
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,11 @@ int main(int argc, char **argv) {
|
||||||
test_temporal_dither_schedule(5, 1, 2);
|
test_temporal_dither_schedule(5, 1, 2);
|
||||||
test_temporal_dither_schedule(5, 1, 4);
|
test_temporal_dither_schedule(5, 1, 4);
|
||||||
|
|
||||||
|
test_simple_dither_schedule(6, 1);
|
||||||
|
test_temporal_dither_schedule(6, 1, 0);
|
||||||
|
test_temporal_dither_schedule(6, 1, 2);
|
||||||
|
test_temporal_dither_schedule(6, 1, 4);
|
||||||
|
|
||||||
test_simple_dither_schedule(5, 16);
|
test_simple_dither_schedule(5, 16);
|
||||||
test_temporal_dither_schedule(5, 16, 2);
|
test_temporal_dither_schedule(5, 16, 2);
|
||||||
test_temporal_dither_schedule(5, 16, 4);
|
test_temporal_dither_schedule(5, 16, 4);
|
||||||
|
|
@ -132,9 +137,14 @@ int main(int argc, char **argv) {
|
||||||
test_temporal_dither_schedule(5, 24, 2);
|
test_temporal_dither_schedule(5, 24, 2);
|
||||||
test_temporal_dither_schedule(5, 24, 4);
|
test_temporal_dither_schedule(5, 24, 4);
|
||||||
|
|
||||||
|
test_simple_dither_schedule(10, 24);
|
||||||
|
test_temporal_dither_schedule(10, 24, 8);
|
||||||
|
|
||||||
|
test_temporal_dither_schedule(5, 128, 4);
|
||||||
|
test_temporal_dither_schedule(5, 192, 4);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
piomatter::matrix_geometry geometry(128, 4, 10, 64, 64, true,
|
piomatter::matrix_geometry geometry(128, 4, 10, 0, 64, 64, true,
|
||||||
piomatter::orientation_normal);
|
piomatter::orientation_normal);
|
||||||
piomatter::piomatter p(std::span(&pixels[0][0], 64 * 64), geometry);
|
piomatter::piomatter p(std::span(&pixels[0][0], 64 * 64), geometry);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue