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):
|
||||
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:
|
||||
|
|
|
|||
|
|
@ -88,19 +88,34 @@ struct schedule_entry {
|
|||
using schedule = std::vector<schedule_entry>;
|
||||
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) {
|
||||
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<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 {
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<pinout>(
|
||||
bufseq[i], geometry, geometry.schedules[i], converted.data());
|
||||
protomatter_render_rgb10<pinout>(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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,7 +132,8 @@ struct colorspace_rgb10 {
|
|||
template <typename pinout>
|
||||
void protomatter_render_rgb10(std::vector<uint32_t> &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<uint32_t> &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<uint32_t> &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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue