openMSX
V9990BitmapConverter.cc
Go to the documentation of this file.
2#include "V9990VRAM.hh"
3#include "V9990.hh"
4#include "unreachable.hh"
5#include "narrow.hh"
6#include <array>
7#include <cassert>
8#include <cstdint>
9#include <concepts>
10
11namespace openmsx {
12
14 V9990& vdp_,
15 std::span<const Pixel, 64> palette64_, std::span<const int16_t, 64> palette64_32768_,
16 std::span<const Pixel, 256> palette256_, std::span<const int16_t, 256> palette256_32768_,
17 std::span<const Pixel, 32768> palette32768_)
18 : vdp(vdp_), vram(vdp.getVRAM())
19 , palette64 (palette64_ ), palette64_32768 (palette64_32768_ )
20 , palette256(palette256_), palette256_32768(palette256_32768_)
21 , palette32768(palette32768_)
22{
23 setColorMode(V9990ColorMode::PP, V9990DisplayMode::B0); // initialize with dummy values
24}
25
26template<bool YJK, bool PAL, bool SKIP, std::unsigned_integral Pixel, typename ColorLookup>
27static inline void draw_YJK_YUV_PAL(
28 ColorLookup color, const V9990VRAM& vram,
29 Pixel* __restrict& out, unsigned& address, int firstX = 0)
30{
31 std::array<byte, 4> data;
32 for (auto& d : data) {
33 d = vram.readVRAMBx(address++);
34 }
35
36 int u = (data[2] & 7) + ((data[3] & 3) << 3) - ((data[3] & 4) << 3);
37 int v = (data[0] & 7) + ((data[1] & 3) << 3) - ((data[1] & 4) << 3);
38
39 for (auto i : xrange(SKIP ? firstX : 0, 4)) {
40 if (PAL && (data[i] & 0x08)) {
41 *out++ = color.lookup64(data[i] >> 4);
42 } else {
43 int y = (data[i] & 0xF8) >> 3;
44 int r = std::clamp(y + u, 0, 31);
45 int g = std::clamp((5 * y - 2 * u - v) / 4, 0, 31);
46 int b = std::clamp(y + v, 0, 31);
47 // The only difference between YUV and YJK is that
48 // green and blue are swapped.
49 if constexpr (YJK) std::swap(g, b);
50 *out++ = color.lookup32768((g << 10) + (r << 5) + b);
51 }
52 }
53}
54
55template<std::unsigned_integral Pixel, typename ColorLookup>
56static void rasterBYUV(
57 ColorLookup color, const V9990& vdp, const V9990VRAM& vram,
58 std::span<Pixel> buf, unsigned x, unsigned y)
59{
60 Pixel* __restrict out = buf.data();
61 int nrPixels = narrow<int>(buf.size());
62 unsigned address = (x & ~3) + y * vdp.getImageWidth();
63 if (x & 3) {
64 draw_YJK_YUV_PAL<false, false, true>(
65 color, vram, out, address, x & 3);
66 nrPixels -= narrow<int>(4 - (x & 3));
67 }
68 for (; nrPixels > 0; nrPixels -= 4) {
69 draw_YJK_YUV_PAL<false, false, false>(
70 color, vram, out, address);
71 }
72 // Note: this can draw up to 3 pixels too many, but that's ok.
73}
74
75template<std::unsigned_integral Pixel, typename ColorLookup>
76static void rasterBYUVP(
77 ColorLookup color, const V9990& vdp, const V9990VRAM& vram,
78 std::span<Pixel> buf, unsigned x, unsigned y)
79{
80 // TODO this mode cannot be shown in B4 and higher resolution modes
81 // (So the dual palette for B4 modes is not an issue here.)
82 Pixel* __restrict out = buf.data();
83 int nrPixels = narrow<int>(buf.size());
84 unsigned address = (x & ~3) + y * vdp.getImageWidth();
85 if (x & 3) {
86 draw_YJK_YUV_PAL<false, true, true>(
87 color, vram, out, address, x & 3);
88 nrPixels -= narrow<int>(4 - (x & 3));
89 }
90 for (; nrPixels > 0; nrPixels -= 4) {
91 draw_YJK_YUV_PAL<false, true, false>(
92 color, vram, out, address);
93 }
94 // Note: this can draw up to 3 pixels too many, but that's ok.
95}
96
97template<std::unsigned_integral Pixel, typename ColorLookup>
98static void rasterBYJK(
99 ColorLookup color, const V9990& vdp, const V9990VRAM& vram,
100 std::span<Pixel> buf, unsigned x, unsigned y)
101{
102 Pixel* __restrict out = buf.data();
103 int nrPixels = narrow<int>(buf.size());
104 unsigned address = (x & ~3) + y * vdp.getImageWidth();
105 if (x & 3) {
106 draw_YJK_YUV_PAL<true, false, true>(
107 color, vram, out, address, x & 3);
108 nrPixels -= narrow<int>(4 - (x & 3));
109 }
110 for (; nrPixels > 0; nrPixels -= 4) {
111 draw_YJK_YUV_PAL<true, false, false>(
112 color, vram, out, address);
113 }
114 // Note: this can draw up to 3 pixels too many, but that's ok.
115}
116
117template<std::unsigned_integral Pixel, typename ColorLookup>
118static void rasterBYJKP(
119 ColorLookup color, const V9990& vdp, const V9990VRAM& vram,
120 std::span<Pixel> buf, unsigned x, unsigned y)
121{
122 // TODO this mode cannot be shown in B4 and higher resolution modes
123 // (So the dual palette for B4 modes is not an issue here.)
124 Pixel* __restrict out = buf.data();
125 int nrPixels = narrow<int>(buf.size());
126 unsigned address = (x & ~3) + y * vdp.getImageWidth();
127 if (x & 3) {
128 draw_YJK_YUV_PAL<true, true, true>(
129 color, vram, out, address, x & 3);
130 nrPixels -= narrow<int>(4 - (x & 3));
131 }
132 for (; nrPixels > 0; nrPixels -= 4) {
133 draw_YJK_YUV_PAL<true, true, false>(
134 color, vram, out, address);
135 }
136 // Note: this can draw up to 3 pixels too many, but that's ok.
137}
138
139template<std::unsigned_integral Pixel, typename ColorLookup>
140static void rasterBD16(
141 ColorLookup color, const V9990& vdp, const V9990VRAM& vram,
142 std::span<Pixel> buf, unsigned x, unsigned y)
143{
144 Pixel* __restrict out = buf.data();
145 int nrPixels = narrow<int>(buf.size());
146 unsigned address = 2 * (x + y * vdp.getImageWidth());
147 if (vdp.isSuperimposing()) {
148 auto transparent = color.lookup256(0);
149 for (; nrPixels > 0; --nrPixels) {
150 byte high = vram.readVRAMBx(address + 1);
151 if (high & 0x80) {
152 *out = transparent;
153 } else {
154 byte low = vram.readVRAMBx(address + 0);
155 *out = color.lookup32768(low + 256 * high);
156 }
157 address += 2;
158 out += 1;
159 }
160 } else {
161 for (; nrPixels > 0; --nrPixels) {
162 byte low = vram.readVRAMBx(address++);
163 byte high = vram.readVRAMBx(address++);
164 *out++ = color.lookup32768((low + 256 * high) & 0x7FFF);
165 }
166 }
167}
168
169template<std::unsigned_integral Pixel, typename ColorLookup>
170static void rasterBD8(
171 ColorLookup color, const V9990& vdp, const V9990VRAM& vram,
172 std::span<Pixel> buf, unsigned x, unsigned y)
173{
174 Pixel* __restrict out = buf.data();
175 int nrPixels = narrow<int>(buf.size());
176 unsigned address = x + y * vdp.getImageWidth();
177 for (; nrPixels > 0; --nrPixels) {
178 *out++ = color.lookup256(vram.readVRAMBx(address++));
179 }
180}
181
182template<std::unsigned_integral Pixel, typename ColorLookup>
183static void rasterBP6(
184 ColorLookup color, const V9990& vdp, const V9990VRAM& vram,
185 std::span<Pixel> buf, unsigned x, unsigned y)
186{
187 Pixel* __restrict out = buf.data();
188 int nrPixels = narrow<int>(buf.size());
189 unsigned address = x + y * vdp.getImageWidth();
190 for (; nrPixels > 0; --nrPixels) {
191 *out++ = color.lookup64(vram.readVRAMBx(address++) & 0x3F);
192 }
193}
194
195template<std::unsigned_integral Pixel, typename ColorLookup>
196static void rasterBP4(
197 ColorLookup color, const V9990& vdp, const V9990VRAM& vram,
198 std::span<Pixel> buf, unsigned x, unsigned y)
199{
200 Pixel* __restrict out = buf.data();
201 int nrPixels = narrow<int>(buf.size());
202 assert(nrPixels > 0);
203 unsigned address = (x + y * vdp.getImageWidth()) / 2;
204 color.set64Offset((vdp.getPaletteOffset() & 0xC) << 2);
205 if (x & 1) {
206 byte data = vram.readVRAMBx(address++);
207 *out++ = color.lookup64(data & 0x0F);
208 --nrPixels;
209 }
210 for (; nrPixels > 0; nrPixels -= 2) {
211 byte data = vram.readVRAMBx(address++);
212 *out++ = color.lookup64(data >> 4);
213 *out++ = color.lookup64(data & 0x0F);
214 }
215 // Note: this possibly draws 1 pixel too many, but that's ok.
216}
217template<std::unsigned_integral Pixel, typename ColorLookup>
218static void rasterBP4HiRes(
219 ColorLookup color, const V9990& vdp, const V9990VRAM& vram,
220 std::span<Pixel> buf, unsigned x, unsigned y)
221{
222 // Verified on real HW:
223 // Bit PLT05 in palette offset is ignored, instead for even pixels
224 // bit 'PLT05' is '0', for odd pixels it's '1'.
225 Pixel* __restrict out = buf.data();
226 int nrPixels = narrow<int>(buf.size());
227 unsigned address = (x + y * vdp.getImageWidth()) / 2;
228 color.set64Offset((vdp.getPaletteOffset() & 0x4) << 2);
229 if (x & 1) {
230 byte data = vram.readVRAMBx(address++);
231 *out++ = color.lookup64(32 | (data & 0x0F));
232 --nrPixels;
233 }
234 for (; nrPixels > 0; nrPixels -= 2) {
235 byte data = vram.readVRAMBx(address++);
236 *out++ = color.lookup64( 0 | (data >> 4 ));
237 *out++ = color.lookup64(32 | (data & 0x0F));
238 }
239 // Note: this possibly draws 1 pixel too many, but that's ok.
240}
241
242template<std::unsigned_integral Pixel, typename ColorLookup>
243static void rasterBP2(
244 ColorLookup color, const V9990& vdp, const V9990VRAM& vram,
245 std::span<Pixel> buf, unsigned x, unsigned y)
246{
247 Pixel* __restrict out = buf.data();
248 int nrPixels = narrow<int>(buf.size());
249 assert(nrPixels > 0);
250 unsigned address = (x + y * vdp.getImageWidth()) / 4;
251 color.set64Offset(vdp.getPaletteOffset() << 2);
252 if (x & 3) {
253 byte data = vram.readVRAMBx(address++);
254 if ((x & 3) <= 1) *out++ = color.lookup64((data & 0x30) >> 4);
255 if ((x & 3) <= 2) *out++ = color.lookup64((data & 0x0C) >> 2);
256 if (true) *out++ = color.lookup64((data & 0x03) >> 0);
257 nrPixels -= narrow<int>(4 - (x & 3));
258 }
259 for (; nrPixels > 0; nrPixels -= 4) {
260 byte data = vram.readVRAMBx(address++);
261 *out++ = color.lookup64((data & 0xC0) >> 6);
262 *out++ = color.lookup64((data & 0x30) >> 4);
263 *out++ = color.lookup64((data & 0x0C) >> 2);
264 *out++ = color.lookup64((data & 0x03) >> 0);
265 }
266 // Note: this can draw up to 3 pixels too many, but that's ok.
267}
268template<std::unsigned_integral Pixel, typename ColorLookup>
269static void rasterBP2HiRes(
270 ColorLookup color, const V9990& vdp, const V9990VRAM& vram,
271 std::span<Pixel> buf, unsigned x, unsigned y)
272{
273 // Verified on real HW:
274 // Bit PLT05 in palette offset is ignored, instead for even pixels
275 // bit 'PLT05' is '0', for odd pixels it's '1'.
276 Pixel* __restrict out = buf.data();
277 int nrPixels = narrow<int>(buf.size());
278 assert(nrPixels > 0);
279 unsigned address = (x + y * vdp.getImageWidth()) / 4;
280 color.set64Offset((vdp.getPaletteOffset() & 0x7) << 2);
281 if (x & 3) {
282 byte data = vram.readVRAMBx(address++);
283 if ((x & 3) <= 1) *out++ = color.lookup64(32 | ((data & 0x30) >> 4));
284 if ((x & 3) <= 2) *out++ = color.lookup64( 0 | ((data & 0x0C) >> 2));
285 if (true) *out++ = color.lookup64(32 | ((data & 0x03) >> 0));
286 nrPixels -= narrow<int>(4 - (x & 3));
287 }
288 for (; nrPixels > 0; nrPixels -= 4) {
289 byte data = vram.readVRAMBx(address++);
290 *out++ = color.lookup64( 0 | ((data & 0xC0) >> 6));
291 *out++ = color.lookup64(32 | ((data & 0x30) >> 4));
292 *out++ = color.lookup64( 0 | ((data & 0x0C) >> 2));
293 *out++ = color.lookup64(32 | ((data & 0x03) >> 0));
294 }
295 // Note: this can draw up to 3 pixels too many, but that's ok.
296}
297
298// Helper class to translate V9990 palette indices into host Pixel values.
299template<std::unsigned_integral Pixel>
301{
302public:
303 PaletteLookup(std::span<const Pixel, 64> palette64_,
304 std::span<const Pixel, 256> palette256_,
305 std::span<const Pixel, 32768> palette32768_)
306 : palette64Base(palette64_)
307 , palette64(palette64_)
308 , palette256(palette256_)
309 , palette32768(palette32768_)
310 {
311 }
312
313 void set64Offset(size_t offset) { palette64 = palette64Base.subspan(offset); }
314 [[nodiscard]] Pixel lookup64 (size_t idx) const { return palette64 [idx]; }
315 [[nodiscard]] Pixel lookup256 (size_t idx) const { return palette256 [idx]; }
316 [[nodiscard]] Pixel lookup32768(size_t idx) const { return palette32768[idx]; }
317
318private:
319 std::span<const Pixel, 64> palette64Base;
320 std::span<const Pixel> palette64;
321 std::span<const Pixel, 256> palette256;
322 std::span<const Pixel, 32768> palette32768;
323};
324
325// Helper class to translate V9990 palette indices (64-entry, 256-entry and
326// 32768-entry palettes) into V9990 32768-entry palette indices.
328{
329public:
330 IndexLookup(std::span<const int16_t, 64> palette64_, std::span<const int16_t, 256> palette256_)
331 : palette64_32768Base(palette64_)
332 , palette64_32768(palette64_)
333 , palette256_32768(palette256_)
334 {
335 }
336
337 void set64Offset(size_t offset) { palette64_32768 = palette64_32768Base.subspan(offset); }
338 [[nodiscard]] int16_t lookup64 (size_t idx) const { return palette64_32768 [idx]; }
339 [[nodiscard]] int16_t lookup256 (size_t idx) const { return palette256_32768[idx]; }
340 [[nodiscard]] int16_t lookup32768(size_t idx) const { return int16_t(idx); }
341
342private:
343 std::span<const int16_t, 64> palette64_32768Base;
344 std::span<const int16_t> palette64_32768;
345 std::span<const int16_t, 256> palette256_32768;
346};
347
348template<std::unsigned_integral Pixel, typename ColorLookup>
349static void raster(V9990ColorMode colorMode, bool highRes,
350 ColorLookup color, const V9990& vdp, const V9990VRAM& vram,
351 std::span<Pixel> out, unsigned x, unsigned y)
352{
353 switch (colorMode) {
354 using enum V9990ColorMode;
355 case BYUV: return rasterBYUV <Pixel>(color, vdp, vram, out, x, y);
356 case BYUVP: return rasterBYUVP<Pixel>(color, vdp, vram, out, x, y);
357 case BYJK: return rasterBYJK <Pixel>(color, vdp, vram, out, x, y);
358 case BYJKP: return rasterBYJKP<Pixel>(color, vdp, vram, out, x, y);
359 case BD16: return rasterBD16 <Pixel>(color, vdp, vram, out, x, y);
360 case BD8: return rasterBD8 <Pixel>(color, vdp, vram, out, x, y);
361 case BP6: return rasterBP6 <Pixel>(color, vdp, vram, out, x, y);
362 case BP4: return highRes ? rasterBP4HiRes<Pixel>(color, vdp, vram, out, x, y)
363 : rasterBP4 <Pixel>(color, vdp, vram, out, x, y);
364 case BP2: return highRes ? rasterBP2HiRes<Pixel>(color, vdp, vram, out, x, y)
365 : rasterBP2 <Pixel>(color, vdp, vram, out, x, y);
366 default: UNREACHABLE;
367 }
368}
369
370// Missing details in the V9990 application manual (reverse engineered from
371// tests on a real gfx9000):
372// * Cursor 0 is drawn on top of cursor 1 (IOW cursor 0 has higher priority).
373// This remains the case when both cursors use the 'EOR' feature (it's not
374// so that EOR is applied twice).
375// * The CC1,CC0,EOR bits in the cursor attribute table work like this:
376// (CC1,CC0):
377// when (0,0): pick the resulting color from bitmap rendering (this is
378// a 15 bit RGB value)
379// when (x,y): pick the color from palette with index R#28:x:y (this is
380// also a 15 bit RGB color)
381// (EOR):
382// when 0: use the above color unchanged
383// when 1: flip all the bits in the above color (IOW XOR with 0x7fff)
384// From this follows:
385// (CC1,CC0,EOR)==(0,0,0):
386// Results in an invisible cursor: each pixel is colored the same as the
387// corresponding background pixel.
388// (CC1,CC0,EOR)==(0,0,1):
389// This is the only combination where the cursor is drawn using multiple
390// colors, each pixel is the complement of the corresponding background
391// pixel.
392// (CC1,CC0,EOR)==(x,y,0):
393// This is the 'usual' configuration, cursor is drawn with a specific
394// color from the palette (also when bitmap rendering is not using the
395// palette, e.g. YJK or BD8 mode).
396// (CC1,CC0,EOR)==(x,y,1):
397// This undocumented mode draws the cursor with a single color which is
398// the complement of a specific palette color.
400{
401public:
402 CursorInfo(const V9990& vdp, const V9990VRAM& vram, std::span<const int16_t, 64> palette64_32768,
403 unsigned attrAddr, unsigned patAddr,
404 int displayY, bool drawCursor)
405 {
406 if (!drawCursor) return;
407
408 unsigned attrY = vram.readVRAMBx(attrAddr + 0) +
409 (vram.readVRAMBx(attrAddr + 2) & 1) * 256;
410 attrY += vdp.isInterlaced() ? 2 : 1; // 1 or 2 lines later
411 unsigned cursorLine = (displayY - attrY) & 511;
412 if (cursorLine >= 32) return;
413
414 byte attr = vram.readVRAMBx(attrAddr + 6);
415 if ((attr & 0x10) || ((attr & 0xe0) == 0x00)) {
416 // don't display
417 return;
418 }
419
420 pattern = (vram.readVRAMBx(patAddr + 4 * cursorLine + 0) << 24)
421 + (vram.readVRAMBx(patAddr + 4 * cursorLine + 1) << 16)
422 + (vram.readVRAMBx(patAddr + 4 * cursorLine + 2) << 8)
423 + (vram.readVRAMBx(patAddr + 4 * cursorLine + 3) << 0);
424 if (pattern == 0) {
425 // optimization, completely transparent line
426 return;
427 }
428
429 // mark cursor visible
430 x = vram.readVRAMBx(attrAddr + 4) + (attr & 3) * 256;
431
432 doXor = (attr & 0xe0) == 0x20;
433
434 auto colorIdx = vdp.getSpritePaletteOffset() + (attr >> 6);
435 color = palette64_32768[colorIdx];
436 if (attr & 0x20) color ^= 0x7fff;
437 }
438
439 [[nodiscard]] bool isVisible() const {
440 return x != unsigned(-1);
441 }
442 [[nodiscard]] bool dot() const {
443 return (x == 0) && (pattern & 0x80000000);
444 }
445 void shift() {
446 if (x) {
447 --x;
448 } else {
449 pattern <<= 1;
450 }
451 }
452
453public:
454 unsigned x{unsigned(-1)}; // not visible
455 uint32_t pattern{0};
456 int16_t color{0};
457 bool doXor{false};
458};
459
461 std::span<Pixel> dst, unsigned x, unsigned y,
462 int cursorY, bool drawCursors) const
463{
464 assert(dst.size() <= 1024);
465
466 CursorInfo cursor0(vdp, vram, palette64_32768, 0x7fe00, 0x7ff00, cursorY, drawCursors);
467 CursorInfo cursor1(vdp, vram, palette64_32768, 0x7fe08, 0x7ff80, cursorY, drawCursors);
468
469 if (cursor0.isVisible() || cursor1.isVisible()) {
470 // raster background into a temporary buffer
471 std::array<uint16_t, 1024 + 3> buf; // allow to draw upto 3 pixels too many, e.g. see rasterBP2()
472 raster(colorMode, highRes,
473 IndexLookup(palette64_32768, palette256_32768),
474 vdp, vram,
475 subspan(buf, 0, dst.size()), x, y);
476
477 // draw sprites in this buffer
478 // TODO can be optimized
479 // TODO probably goes wrong when startX != 0
480 // TODO investigate dual palette in B4 and higher modes
481 // TODO check X-roll behavior
482 for (auto i : xrange(dst.size())) {
483 if (cursor0.dot()) {
484 if (cursor0.doXor) {
485 buf[i] ^= 0x7fff;
486 } else {
487 buf[i] = cursor0.color;
488 }
489 } else if (cursor1.dot()) {
490 if (cursor1.doXor) {
491 buf[i] ^= 0x7fff;
492 } else {
493 buf[i] = cursor1.color;
494 }
495 }
496 cursor0.shift();
497 cursor1.shift();
498 if ((cursor0.pattern == 0) && (cursor1.pattern == 0)) break;
499 }
500
501 // copy buffer to destination, translate from V9990 to host colors
502 for (auto i : xrange(dst.size())) {
503 dst[i] = palette32768[buf[i]];
504 }
505 } else {
506 // Optimization: no cursor(s) visible on this line, directly draw to destination
507 raster(colorMode, highRes,
508 PaletteLookup<Pixel>(palette64, palette256, palette32768),
509 vdp, vram, dst, x, y);
510 }
511}
512
513} // namespace openmsx
int g
CursorInfo(const V9990 &vdp, const V9990VRAM &vram, std::span< const int16_t, 64 > palette64_32768, unsigned attrAddr, unsigned patAddr, int displayY, bool drawCursor)
int16_t lookup64(size_t idx) const
IndexLookup(std::span< const int16_t, 64 > palette64_, std::span< const int16_t, 256 > palette256_)
void set64Offset(size_t offset)
int16_t lookup256(size_t idx) const
int16_t lookup32768(size_t idx) const
Pixel lookup256(size_t idx) const
Pixel lookup64(size_t idx) const
void set64Offset(size_t offset)
Pixel lookup32768(size_t idx) const
PaletteLookup(std::span< const Pixel, 64 > palette64_, std::span< const Pixel, 256 > palette256_, std::span< const Pixel, 32768 > palette32768_)
void convertLine(std::span< Pixel > dst, unsigned x, unsigned y, int cursorY, bool drawCursors) const
Convert a line of VRAM into host pixels.
V9990BitmapConverter(V9990 &vdp, std::span< const Pixel, 64 > palette64, std::span< const int16_t, 64 > palette64_32768, std::span< const Pixel, 256 > palette256, std::span< const int16_t, 256 > palette256_32768, std::span< const Pixel, 32768 > palette32768)
void setColorMode(V9990ColorMode colorMode_, V9990DisplayMode display)
Set a different rendering mode.
Video RAM for the V9990.
Definition V9990VRAM.hh:17
byte readVRAMBx(unsigned address) const
Definition V9990VRAM.hh:55
Implementation of the Yamaha V9990 VDP as used in the GFX9000 cartridge by Sunrise.
Definition V9990.hh:35
bool isInterlaced() const
Get interlace status.
Definition V9990.hh:62
byte getSpritePaletteOffset() const
return sprite palette offset
Definition V9990.hh:310
This file implemented 3 utility functions:
Definition Autofire.cc:11
CharacterConverter::Pixel Pixel
constexpr auto subspan(Range &&range, size_t offset, size_t count=std::dynamic_extent)
Definition ranges.hh:481
#define UNREACHABLE
constexpr auto xrange(T e)
Definition xrange.hh:132