/ src / include / piomatter / render.h
render.h
  1  #pragma once
  2  
  3  #include "matrixmap.h"
  4  #include <cassert>
  5  #include <span>
  6  #include <vector>
  7  
  8  namespace piomatter {
  9  
 10  constexpr unsigned DATA_OVERHEAD = 3;
 11  constexpr unsigned CLOCKS_PER_DATA = 2;
 12  constexpr unsigned DELAY_OVERHEAD = 5;
 13  constexpr unsigned CLOCKS_PER_DELAY = 1;
 14  
 15  constexpr uint32_t command_data = 1u << 31;
 16  constexpr uint32_t command_delay = 0;
 17  
 18  struct gamma_lut {
 19      gamma_lut(double exponent = 2.2) {
 20          for (int i = 0; i < 256; i++) {
 21              auto v = std::max(i, int(round(1023 * pow(i / 255, exponent))));
 22              lut[i] = v;
 23          }
 24      }
 25  
 26      unsigned convert(unsigned v) {
 27          if (v >= std::size(lut))
 28              return 1023;
 29          return lut[v];
 30      }
 31  
 32      void convert_rgb888_packed_to_rgb10(std::vector<uint32_t> &result,
 33                                          std::span<const uint8_t> source) {
 34          result.resize(source.size() / 3);
 35          for (size_t i = 0, j = 0; i < source.size(); i += 3) {
 36              uint32_t r = source[i + 0] & 0xff;
 37              uint32_t g = source[i + 1] & 0xff;
 38              uint32_t b = source[i + 2] & 0xff;
 39              result[j++] = (convert(r) << 20) | (convert(g) << 10) | convert(b);
 40          }
 41      }
 42  
 43      void convert_rgb888_to_rgb10(std::vector<uint32_t> &result,
 44                                   std::span<const uint32_t> source) {
 45          result.resize(source.size());
 46          for (size_t i = 0; i < source.size(); i++) {
 47              uint32_t data = source[i];
 48              uint32_t r = (data >> 16) & 0xff;
 49              uint32_t g = (data >> 8) & 0xff;
 50              uint32_t b = data & 0xff;
 51              result[i] = (convert(r) << 20) | (convert(g) << 10) | convert(b);
 52          }
 53      }
 54  
 55      void convert_rgb565_to_rgb10(std::vector<uint32_t> &result,
 56                                   std::span<const uint16_t> source) {
 57          result.resize(source.size());
 58          for (size_t i = 0; i < source.size(); i++) {
 59              uint32_t data = source[i];
 60              unsigned r5 = (data >> 11) & 0x1f;
 61              unsigned r = (r5 << 3) | (r5 >> 2);
 62              unsigned g6 = (data >> 5) & 0x3f;
 63              unsigned g = (g6 << 2) | (g6 >> 4);
 64              unsigned b5 = (data)&0x1f;
 65              unsigned b = (b5 << 3) | (b5 >> 2);
 66  
 67              result[i] = (convert(r) << 20) | (convert(g) << 10) | convert(b);
 68          }
 69      }
 70  
 71      uint16_t lut[256];
 72  };
 73  
 74  struct colorspace_rgb565 {
 75      using data_type = uint16_t;
 76      static constexpr size_t data_size_in_bytes(size_t n_pixels) {
 77          return sizeof(data_type) * n_pixels;
 78      }
 79  
 80      colorspace_rgb565(float gamma = 2.2) : lut{gamma} {}
 81      gamma_lut lut;
 82      const std::span<const uint32_t>
 83      convert(std::span<const data_type> data_in) {
 84          lut.convert_rgb565_to_rgb10(rgb10, data_in);
 85          return rgb10;
 86      }
 87      std::vector<uint32_t> rgb10;
 88  };
 89  
 90  struct colorspace_rgb888 {
 91      using data_type = uint32_t;
 92      static constexpr size_t data_size_in_bytes(size_t n_pixels) {
 93          return sizeof(data_type) * n_pixels;
 94      }
 95  
 96      colorspace_rgb888(float gamma = 2.2) : lut{gamma} {}
 97      gamma_lut lut;
 98      const std::span<const uint32_t>
 99      convert(std::span<const data_type> data_in) {
100          lut.convert_rgb888_to_rgb10(rgb10, data_in);
101          return rgb10;
102      }
103      std::vector<uint32_t> rgb10;
104  };
105  
106  struct colorspace_rgb888_packed {
107      using data_type = uint8_t;
108      static constexpr size_t data_size_in_bytes(size_t n_pixels) {
109          return sizeof(data_type) * n_pixels * 3;
110      }
111  
112      colorspace_rgb888_packed(float gamma = 2.2) : lut{gamma} {}
113      gamma_lut lut;
114      const std::span<const uint32_t>
115      convert(std::span<const data_type> data_in) {
116          lut.convert_rgb888_packed_to_rgb10(rgb10, data_in);
117          return rgb10;
118      }
119      std::vector<uint32_t> rgb10;
120  };
121  
122  struct colorspace_rgb10 {
123      using data_type = uint32_t;
124  
125      const std::span<const uint32_t>
126      convert(std::span<const data_type> data_in) {
127          return data_in;
128      }
129  };
130  
131  // Render a buffer in linear RGB10 format into a piomatter stream
132  template <typename pinout>
133  void protomatter_render_rgb10(std::vector<uint32_t> &result,
134                                const matrix_geometry &matrixmap,
135                                const uint32_t *pixels) {
136      result.clear();
137  
138      int data_count = 0;
139  
140      auto do_delay = [&](uint32_t delay) {
141          if (delay == 0)
142              return;
143          assert(delay < 1000000);
144          assert(!data_count);
145          result.push_back(command_delay | (delay ? delay - 1 : 0));
146      };
147  
148      auto prep_data = [&data_count, &result](uint32_t n) {
149          assert(!data_count);
150          assert(n);
151          assert(n < 60000);
152          result.push_back(command_data | (n - 1));
153          data_count = n;
154      };
155  
156      auto do_data = [&](uint32_t d) {
157          assert(data_count);
158          data_count--;
159          result.push_back(d);
160      };
161  
162      auto do_data_delay = [&](uint32_t d, uint32_t delay) {
163          prep_data(1);
164          do_data(d);
165          do_delay(delay);
166      };
167  
168      auto calc_addr_bits = [](int addr) {
169          uint32_t data = 0;
170          if (addr & 1)
171              data |= (1 << pinout::PIN_ADDR[0]);
172          if (addr & 2)
173              data |= (1 << pinout::PIN_ADDR[1]);
174          if (addr & 4)
175              data |= (1 << pinout::PIN_ADDR[2]);
176          if constexpr (std::size(pinout::PIN_ADDR) >= 4) {
177              if (addr & 8)
178                  data |= (1 << pinout::PIN_ADDR[3]);
179          }
180          if constexpr (std::size(pinout::PIN_ADDR) >= 5) {
181              if (addr & 16)
182                  data |= (1 << pinout::PIN_ADDR[4]);
183          }
184          return data;
185      };
186  
187      auto add_pixels = [&do_data, &result](uint32_t addr_bits, bool r0, bool g0,
188                                            bool b0, bool r1, bool g1, bool b1,
189                                            bool active) {
190          uint32_t data =
191              (active ? pinout::oe_active : pinout::oe_inactive) | addr_bits;
192          if (r0)
193              data |= (1 << pinout::PIN_RGB[0]);
194          if (g0)
195              data |= (1 << pinout::PIN_RGB[1]);
196          if (b0)
197              data |= (1 << pinout::PIN_RGB[2]);
198          if (r1)
199              data |= (1 << pinout::PIN_RGB[3]);
200          if (g1)
201              data |= (1 << pinout::PIN_RGB[4]);
202          if (b1)
203              data |= (1 << pinout::PIN_RGB[5]);
204  
205          do_data(data);
206          do_data(data | pinout::clk_bit);
207      };
208  
209      int last_bit = 0;
210      // illuminate the right row for data in the shift register (the previous
211      // address)
212  
213      const size_t n_addr = 1u << matrixmap.n_addr_lines;
214      const int n_planes = matrixmap.n_planes;
215      constexpr size_t n_bits = 10u;
216      unsigned offset = n_bits - n_planes;
217      const size_t pixels_across = matrixmap.pixels_across;
218  
219      size_t prev_addr = n_addr - 1;
220      uint32_t addr_bits = calc_addr_bits(prev_addr);
221  
222      for (size_t addr = 0; addr < n_addr; addr++) {
223          // printf("addr=%zu/%zu\n", addr, n_addr);
224          for (int bit = n_planes - 1; bit >= 0; bit--) {
225              // printf("bit=%d/%d\n", bit, n_planes);
226              uint32_t r = 1 << (20 + offset + bit);
227              uint32_t g = 1 << (10 + offset + bit);
228              uint32_t b = 1 << (0 + offset + bit);
229  
230              // the shortest /OE we can do is one DATA_OVERHEAD...
231              // TODO: should make sure desired duration of MSB is at least
232              // `pixels_across`
233              uint32_t desired_duration = 1 << last_bit;
234              last_bit = bit;
235  
236              prep_data(2 * pixels_across);
237              auto mapiter = matrixmap.map.begin() + 2 * addr * pixels_across;
238              for (size_t x = 0; x < pixels_across; x++) {
239                  assert(mapiter != matrixmap.map.end());
240                  auto pixel0 = pixels[*mapiter++];
241                  auto r0 = pixel0 & r;
242                  auto g0 = pixel0 & g;
243                  auto b0 = pixel0 & b;
244                  assert(mapiter != matrixmap.map.end());
245                  auto pixel1 = pixels[*mapiter++];
246                  auto r1 = pixel1 & r;
247                  auto g1 = pixel1 & g;
248                  auto b1 = pixel1 & b;
249  
250                  add_pixels(addr_bits, r0, g0, b0, r1, g1, b1,
251                             x < desired_duration);
252              }
253  
254              // hold /OE low until desired time has elapsed to illuminate the
255              // LAST line
256              int remain = desired_duration - pixels_across;
257              if (remain > 0) {
258                  do_data_delay(addr_bits | pinout::oe_active,
259                                remain * CLOCKS_PER_DATA - DELAY_OVERHEAD);
260              }
261  
262              do_data_delay(addr_bits | pinout::oe_inactive,
263                            pinout::post_oe_delay);
264              do_data_delay(addr_bits | pinout::oe_inactive | pinout::lat_bit,
265                            pinout::post_latch_delay);
266  
267              // with oe inactive, set address bits to illuminate THIS line
268              if (addr != prev_addr) {
269                  addr_bits = calc_addr_bits(addr);
270                  do_data_delay(addr_bits | pinout::oe_inactive,
271                                pinout::post_addr_delay);
272                  prev_addr = addr;
273              }
274          }
275      }
276  }
277  
278  } // namespace piomatter