From 2442bee4764b8714667d55c23e55c3b39d0884f6 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Mon, 10 Mar 2025 09:42:23 -0500 Subject: [PATCH] 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 --- examples/rainbow_spiral_active3.py | 4 +- src/include/piomatter/matrixmap.h | 68 ++++++++++++++++++++---------- src/include/piomatter/piomatter.h | 7 ++- src/include/piomatter/render.h | 6 +-- src/protodemo.c | 12 +++++- 5 files changed, 67 insertions(+), 30 deletions(-) diff --git a/examples/rainbow_spiral_active3.py b/examples/rainbow_spiral_active3.py index 438c9a2..941fb96 100644 --- a/examples/rainbow_spiral_active3.py +++ b/examples/rainbow_spiral_active3.py @@ -34,7 +34,6 @@ def make_pixelmap_multilane(width, height, n_addr_lines, n_lanes): for lane in range(n_lanes): y = addr + lane * n_addr m.append(x + width * y) - print(m) return m @@ -42,7 +41,7 @@ canvas = Image.new('RGB', (width, height), (0, 0, 0)) draw = ImageDraw.Draw(canvas) 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 matrix = piomatter.PioMatter(colorspace=piomatter.Colorspace.RGB888Packed, 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) update_matrix() + print(matrix.fps) clearing = not clearing except KeyboardInterrupt: diff --git a/src/include/piomatter/matrixmap.h b/src/include/piomatter/matrixmap.h index 9167014..f721a88 100644 --- a/src/include/piomatter/matrixmap.h +++ b/src/include/piomatter/matrixmap.h @@ -88,19 +88,34 @@ struct schedule_entry { using schedule = std::vector; using schedule_sequence = std::vector; +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) { if (n_planes < 1 || n_planes > 10) { throw std::range_error("n_planes out of range"); } 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++) { - 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, @@ -117,40 +132,49 @@ schedule_sequence make_temporal_dither_schedule(int n_planes, return make_simple_schedule(n_planes, pixels_across); } 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) { - 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; - 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; 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; auto add_sched = [&result, &base_sched](int plane, int count) { auto sched = base_sched; - sched.emplace_back(10 - plane, count); + sched.emplace_back(9 - plane, count); result.emplace_back(sched); }; 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 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 { @@ -168,8 +192,8 @@ struct matrix_geometry { matrix_map map, size_t n_lanes) : matrix_geometry(pixels_across, n_addr_lines, width, height, map, n_lanes, - make_temporal_dither_schedule( - n_planes, n_temporal_planes, pixels_across)) {} + make_temporal_dither_schedule(n_planes, pixels_across, + n_temporal_planes)) {} matrix_geometry(size_t pixels_across, size_t n_addr_lines, size_t width, size_t height, matrix_map map, size_t n_lanes, diff --git a/src/include/piomatter/piomatter.h b/src/include/piomatter/piomatter.h index 837c9a3..ba9bcae 100644 --- a/src/include/piomatter/piomatter.h +++ b/src/include/piomatter/piomatter.h @@ -67,9 +67,12 @@ struct piomatter : piomatter_base { auto &bufseq = buffers[buffer_idx]; bufseq.resize(geometry.schedules.size()); 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++) { - protomatter_render_rgb10( - bufseq[i], geometry, geometry.schedules[i], converted.data()); + protomatter_render_rgb10(bufseq[i], geometry, + geometry.schedules[i], + old_active_time, converted.data()); + old_active_time = geometry.schedules[i].back().active_time; } manager.put_filled_buffer(buffer_idx); } diff --git a/src/include/piomatter/render.h b/src/include/piomatter/render.h index 1758abf..d6e6e7a 100644 --- a/src/include/piomatter/render.h +++ b/src/include/piomatter/render.h @@ -132,7 +132,8 @@ struct colorspace_rgb10 { template void protomatter_render_rgb10(std::vector &result, 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(); int data_count = 0; @@ -153,7 +154,7 @@ void protomatter_render_rgb10(std::vector &result, 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) { bool active = active_time > 0; @@ -193,7 +194,6 @@ void protomatter_render_rgb10(std::vector &result, 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); diff --git a/src/protodemo.c b/src/protodemo.c index e1186dc..b5899b3 100644 --- a/src/protodemo.c +++ b/src/protodemo.c @@ -124,6 +124,11 @@ int main(int argc, char **argv) { test_temporal_dither_schedule(5, 1, 2); 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_temporal_dither_schedule(5, 16, 2); 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, 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; - 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::piomatter p(std::span(&pixels[0][0], 64 * 64), geometry);