the rest of the plumbing. now it should be possible to use temporal dither. untested

This commit is contained in:
Jeff Epler 2025-03-09 19:50:27 -05:00
parent a5f505241d
commit 4f548a318c
2 changed files with 43 additions and 22 deletions

View file

@ -155,18 +155,20 @@ schedule_sequence make_temporal_dither_schedule(int n_planes,
struct matrix_geometry { struct matrix_geometry {
template <typename Cb> template <typename Cb>
matrix_geometry(size_t pixels_across, size_t n_addr_lines, int n_planes, matrix_geometry(size_t pixels_across, size_t n_addr_lines, int n_planes,
size_t width, size_t height, bool serpentine, const Cb &cb) int n_temporal_planes, size_t width, size_t height,
bool serpentine, const Cb &cb)
: matrix_geometry( : matrix_geometry(
pixels_across, n_addr_lines, n_planes, width, height, pixels_across, n_addr_lines, n_planes, n_temporal_planes, width,
height,
make_matrixmap(width, height, n_addr_lines, serpentine, cb), 2) {} make_matrixmap(width, height, n_addr_lines, serpentine, cb), 2) {}
matrix_geometry(size_t pixels_across, size_t n_addr_lines, int n_planes, matrix_geometry(size_t pixels_across, size_t n_addr_lines, int n_planes,
size_t width, size_t height, matrix_map map, size_t n_lanes, int n_temporal_planes, size_t width, size_t height,
size_t n_temporal_dither = 0) 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, n_temporal_dither, pixels_across)) {} n_planes, n_temporal_planes, pixels_across)) {}
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,

View file

@ -173,19 +173,33 @@ Describe the geometry of a set of panels
The number of pixels in the shift register is automatically computed from these values. The number of pixels in the shift register is automatically computed from these values.
``n_planes`` controls the color depth of the panel. This is separate from the framebuffer
layout. Decreasing ``n_planes`` can increase FPS at the cost of reduced color fidelity.
The default, 10, is the maximum value.
``n_temporal_planes`` controls temporal dithering of the panel. The acceptable values
are 0 (the default), 2, and 4. A higher setting can increase FPS at the cost of
slightly increasing the variation of brightness across subsequent frames.
For simple panels with just 1 connector (2 color lanes), the following constructor arguments are available:
``serpentine`` controls the arrangement of multiple panels when they are stacked in rows. ``serpentine`` controls the arrangement of multiple panels when they are stacked in rows.
If it is `True`, then each row goes in the opposite direction of the previous row. If it is `True`, then each row goes in the opposite direction of the previous row.
If this is specified, ``n_lanes`` cannot be, and 2 lanes are always used.
``rotation`` controls the orientation of the panel(s). Must be one of the ``Orientation`` ``rotation`` controls the orientation of the panel(s). Must be one of the ``Orientation``
constants. Default is ``Orientation.Normal``. constants. Default is ``Orientation.Normal``.
``n_planes`` controls the color depth of the panel. This is separate from the framebuffer For panels with more than 2 lanes, or using a custom pixel mapping, the following constructor arguments are available:
layout. Decreasing ``n_planes`` can increase FPS at the cost of reduced color fidelity.
The default, 10, is the maximum value. ``n_lanes`` controls how many color lanes are used. A single 16-pin HUB75 connector has 2 color lanes.
If 2 or 3 connectors are used, then there are 4 or 6 lanes.
``map`` is a Python list of integers giving the framebuffer pixel indices for each matrix pixel.
)pbdoc") )pbdoc")
.def(py::init([](size_t width, size_t height, size_t n_addr_lines, .def(py::init([](size_t width, size_t height, size_t n_addr_lines,
bool serpentine, piomatter::orientation rotation, bool serpentine, piomatter::orientation rotation,
size_t n_planes) { size_t n_planes, size_t n_temporal_planes) {
size_t n_lines = 2 << n_addr_lines; size_t n_lines = 2 << n_addr_lines;
size_t pixels_across = width * height / n_lines; size_t pixels_across = width * height / n_lines;
size_t odd = (width * height) % n_lines; size_t odd = (width * height) % n_lines;
@ -201,33 +215,37 @@ The default, 10, is the maximum value.
switch (rotation) { switch (rotation) {
case piomatter::orientation::normal: case piomatter::orientation::normal:
return piomatter::matrix_geometry( return piomatter::matrix_geometry(
pixels_across, n_addr_lines, n_planes, width, height, pixels_across, n_addr_lines, n_planes,
serpentine, piomatter::orientation_normal); n_temporal_planes, width, height, serpentine,
piomatter::orientation_normal);
case piomatter::orientation::r180: case piomatter::orientation::r180:
return piomatter::matrix_geometry( return piomatter::matrix_geometry(
pixels_across, n_addr_lines, n_planes, width, height, pixels_across, n_addr_lines, n_planes,
serpentine, piomatter::orientation_r180); n_temporal_planes, width, height, serpentine,
piomatter::orientation_r180);
case piomatter::orientation::ccw: case piomatter::orientation::ccw:
return piomatter::matrix_geometry( return piomatter::matrix_geometry(
pixels_across, n_addr_lines, n_planes, width, height, pixels_across, n_addr_lines, n_planes,
serpentine, piomatter::orientation_ccw); n_temporal_planes, width, height, serpentine,
piomatter::orientation_ccw);
case piomatter::orientation::cw: case piomatter::orientation::cw:
return piomatter::matrix_geometry( return piomatter::matrix_geometry(
pixels_across, n_addr_lines, n_planes, width, height, pixels_across, n_addr_lines, n_planes,
serpentine, piomatter::orientation_cw); n_temporal_planes, width, height, serpentine,
piomatter::orientation_cw);
} }
throw std::runtime_error("invalid rotation"); throw std::runtime_error("invalid rotation");
}), }),
py::arg("width"), py::arg("height"), py::arg("n_addr_lines"), py::arg("width"), py::arg("height"), py::arg("n_addr_lines"),
py::arg("serpentine") = true, py::arg("serpentine") = true,
py::arg("rotation") = piomatter::orientation::normal, py::arg("rotation") = piomatter::orientation::normal,
py::arg("n_planes") = 10u) py::arg("n_planes") = 10u, py::arg("n_temporal_planes") = 2)
.def(py::init([](size_t width, size_t height, size_t n_addr_lines, .def(py::init([](size_t width, size_t height, size_t n_addr_lines,
piomatter::matrix_map map, size_t n_planes, piomatter::matrix_map map, size_t n_planes,
size_t n_lanes) { size_t n_temporal_planes, size_t n_lanes) {
size_t n_lines = n_lanes << n_addr_lines; size_t n_lines = n_lanes << n_addr_lines;
size_t pixels_across = width * height / n_lines; size_t pixels_across = width * height / n_lines;
for (auto el : map) { for (auto el : map) {
@ -236,11 +254,12 @@ The default, 10, is the maximum value.
} }
} }
return piomatter::matrix_geometry(pixels_across, n_addr_lines, return piomatter::matrix_geometry(pixels_across, n_addr_lines,
n_planes, width, height, map, n_planes, n_temporal_planes,
n_lanes); width, height, map, n_lanes);
}), }),
py::arg("width"), py::arg("height"), py::arg("n_addr_lines"), py::arg("width"), py::arg("height"), py::arg("n_addr_lines"),
py::arg("map"), py::arg("n_planes") = 10u, py::arg("n_lanes") = 2) py::arg("map"), py::arg("n_planes") = 10u,
py::arg("n_temporal_planes") = 0u, py::arg("n_lanes") = 2)
.def_readonly("width", &piomatter::matrix_geometry::width) .def_readonly("width", &piomatter::matrix_geometry::width)
.def_readonly("height", &piomatter::matrix_geometry::height); .def_readonly("height", &piomatter::matrix_geometry::height);