1 module dcpu16.emulator.devices.lem1802;
2 
3 import dcpu16.emulator.idevice;
4 import dcpu16.emulator;
5 
6 enum CHAR_SIZE_X = 4;
7 enum CHAR_SIZE_Y = 8;
8 enum X_RESOLUTION = 32;
9 enum Y_RESOLUTION = 12;
10 enum X_PIXELS = X_RESOLUTION * CHAR_SIZE_X;
11 enum Y_PIXELS = Y_RESOLUTION * CHAR_SIZE_Y;
12 enum PIXELS_NUM = X_PIXELS * Y_PIXELS;
13 static assert(PIXELS_NUM == 128 * 96);
14 
15 class LEM1802 : IDevice
16 {
17     override uint id() const pure { return 0x7349f615; };
18     override uint manufacturer() const pure { return 0x1c6c8b36; };
19     override ushort ver() const pure { return 0x1802; };
20 
21     private const(ushort)* screen;
22     private const(ushort)* font;
23     private const(ushort)* palette;
24     private ubyte borderColor;
25     bool isBlinkingVisible = true;
26     void delegate(InterruptAction) onInterruptAction;
27     private long splashTimeRemaining; /// in hnsecs (hectoseconds)
28 
29     bool isDisconnected() const { return screen is null; }
30 
31     this(Computer comp)
32     {
33         screen = &comp.mem[0x8000]; // de facto standard
34 
35         reset();
36     }
37 
38     override void reset()
39     {
40         font = defaultFont.ptr;
41         palette = defaultPalette.ptr;
42         borderColor = 0;
43         splashTimeRemaining = 25_000_000; // 2.5 seconds
44     }
45 
46     override void handleHardwareInterrupt(Computer comp)
47     {
48         auto action = cast(InterruptAction) comp.cpu.regs.A;
49 
50         with(InterruptAction)
51         with(comp)
52         with(cpu.regs)
53         switch(action)
54         {
55             case MEM_MAP_SCREEN:
56                 if(B != 0)
57                     screen = &mem[B];
58                 break;
59 
60             case MEM_MAP_FONT:
61                 font = (B == 0) ? defaultFont.ptr : &mem[B];
62                 break;
63 
64             case MEM_MAP_PALETTE:
65                 palette = (B == 0) ? defaultPalette.ptr : &mem[B];
66                 break;
67 
68             case SET_BORDER_COLOR:
69                 borderColor = B & 0xF;
70                 break;
71 
72             case MEM_DUMP_FONT:
73                 dump(mem, font[0 .. defaultFont.length], B);
74                 return;
75 
76             case MEM_DUMP_PALETTE:
77                 dump(mem, palette[0 .. defaultPalette.length], B);
78                 return;
79 
80             default:
81                 break;
82         }
83 
84         if(onInterruptAction !is null)
85             onInterruptAction(action);
86     }
87 
88     private void dump(Memory mem, const(ushort)[] from, ushort to) pure
89     {
90         // cutt to fit into memory, if necessary
91         if(to + from.length > mem.length)
92             from.length = mem.length - to;
93 
94         mem[to .. to + from.length] = from[];
95     }
96 
97     const(Symbol) getSymbol(size_t idx) const
98     {
99         assert(idx < 386);
100         assert(!isSplashDisplayed);
101 
102         return Symbol(screen[idx]);
103     }
104 
105     const(Symbol) getSymbol(uint x, uint y) const
106     {
107         return getSymbol(x + y * X_RESOLUTION);
108     }
109 
110     alias SymbolBitmap = ushort[2];
111 
112     bool getPixel(uint x, uint y) const
113     {
114         assert(!isSplashDisplayed);
115 
116         auto symbolX = x / CHAR_SIZE_X;
117         auto symbolY = y / CHAR_SIZE_Y;
118 
119         auto s = getSymbol(symbolX, symbolY);
120         SymbolBitmap bitmap = getSymbolBitmap(s.character);
121 
122         auto relativeX = x % CHAR_SIZE_X;
123         auto relativeY = y % CHAR_SIZE_Y;
124 
125         return getPixelOfSymbol(bitmap, relativeX, relativeY);
126     }
127 
128     static bool getPixelOfSymbol(SymbolBitmap symbolBitmap, uint relativeX, uint relativeY) pure
129     {
130         assert(relativeX < CHAR_SIZE_X);
131         assert(relativeY < CHAR_SIZE_Y);
132 
133         union CharBitArray
134         {
135             ushort[2] for_ctor;
136             ubyte[4] ub_arr;
137 
138             this(ushort[2] from) pure
139             {
140                 import std.bitmanip: swapEndian;
141 
142                 for_ctor[0] = swapEndian(from[0]);
143                 for_ctor[1] = swapEndian(from[1]);
144             }
145         }
146 
147         auto bitArray = CharBitArray(symbolBitmap);
148         relativeY %= CHAR_SIZE_Y;
149 
150         assert(relativeY < 8);
151 
152         ubyte ul = bitArray.ub_arr[relativeX];
153         return bt(ul, cast(ubyte) relativeY) != 0;
154     }
155     unittest
156     {
157         /// 'F'
158         ushort[2] f_img =
159             [
160                 0b11111111_00001001,
161                 0b00001001_00000000
162             ];
163 
164         immutable bool[][] pending =
165         [
166             [1,1,1,0],
167             [1,0,0,0],
168             [1,0,0,0],
169             [1,1,1,0],
170             [1,0,0,0],
171             [1,0,0,0],
172             [1,0,0,0],
173             [1,0,0,0],
174         ];
175 
176         foreach(y; 0 .. 8)
177             foreach(x; 0 .. 4)
178                 assert(pending[y][x] == getPixelOfSymbol(f_img, x, y));
179     }
180 
181     SymbolBitmap getSymbolBitmap(ubyte character) pure const
182     {
183         const ushort* ptr = &font[character * 2];
184 
185         return ptr[0 .. 2];
186     }
187 
188     void forEachPixel(void delegate(ubyte x, ubyte y, PaletteColor c) dg) const
189     {
190         assert(!isSplashDisplayed);
191 
192         for(ubyte y = 0; y < Y_RESOLUTION * CHAR_SIZE_Y; y++)
193         {
194             for(ubyte x = 0; x < X_RESOLUTION * CHAR_SIZE_X; x++)
195             {
196                 const s = getSymbol(x / CHAR_SIZE_X, y / CHAR_SIZE_Y);
197 
198                 PaletteColor c;
199 
200                 bool visible = isBlinkingVisible | !s.blinking;
201 
202                 c = (visible && getPixel(x, y))
203                     ? getColor(s.foreground)
204                     : getColor(s.background);
205 
206                 dg(x, y, c);
207             }
208         }
209     }
210 
211     // TODO: make it more faster
212     RGB[PIXELS_NUM] getRgbFrame() const
213     {
214         RGB[PIXELS_NUM] ret;
215         size_t currPixel;
216 
217         forEachPixel(
218             (x, y, c)
219             {
220                 ret[currPixel] = c.toRGB;
221                 currPixel++;
222             }
223         );
224 
225         assert(currPixel == PIXELS_NUM);
226 
227         return ret;
228     }
229 
230     PaletteColor getColor(ubyte paletteIdx) const
231     {
232         ushort c = isSplashDisplayed ? defaultPalette[paletteIdx] : palette[paletteIdx];
233 
234         return PaletteColor(c);
235     }
236 
237     PaletteColor getBorderColor() const
238     {
239         if(isSplashDisplayed)
240             return getColor(0x7);
241         else
242             return getColor(borderColor);
243     }
244 
245     /**
246      * Turn switch state of blinking symbols
247      *
248      * Recommended calling interval:
249      * The Spectrum's 'FLASH' effect: every 16 frames (50 fps), the ink and paper of all flashing bytes is swapped
250     */
251     void switchBlink()
252     {
253         isBlinkingVisible = !isBlinkingVisible;
254     }
255 
256     void splashClock(long interval)
257     {
258         if(splashTimeRemaining > 0)
259             splashTimeRemaining -= interval;
260     }
261 
262     bool isSplashDisplayed() const pure
263     {
264         return splashTimeRemaining > 0;
265     }
266 
267     void forEachSplashPixel(void delegate(ubyte x, ubyte y, PaletteColor c) dg) const
268     {
269         assert(isSplashDisplayed);
270 
271         foreach(ubyte y; 0 .. Y_PIXELS)
272             foreach(ubyte x; 0 .. X_PIXELS)
273             {
274                 enum fakeBorderWidth = 8;
275                 enum fakeBorderHeight = 7;
276                 PaletteColor c;
277 
278                 if(
279                     (x >= fakeBorderWidth && x <= X_PIXELS - fakeBorderWidth) &&
280                     (y >= fakeBorderHeight && y <= Y_PIXELS - fakeBorderHeight)
281                 )
282                 {
283                     if(splashTimeRemaining > 15_000_000)
284                     {
285                         enum yPixels1_3 = (Y_PIXELS - fakeBorderHeight*2)/3; // 1/3 of pixels on this "zx" screen
286                         auto relY = y - fakeBorderHeight;
287 
288                         if
289                         (
290                             splashTimeRemaining < 20_800_000 &&
291                             (x % 4 == 0) // red line at every column
292                         )
293                         {
294                             if
295                             (
296                                 // upper dotted lines
297                                 (relY % 4 == 0 && splashTimeRemaining > 20_000_000) ||
298                                 // dotted lines at middle of the screen
299                                 (relY % 4 == 0 && relY >= yPixels1_3 && splashTimeRemaining > 19_700_000) ||
300                                 // solid lines at bottom
301                                 (relY % 2 == 0 && relY >= yPixels1_3*2 && splashTimeRemaining > 19_300_000)
302                             )
303                             {
304                                 c = getColor(0x4); // red lines
305                             }
306                             else
307                             {
308                                 c = getColor(0x0);
309                             }
310                         }
311                     }
312                     else
313                         c = getCopyrightPixels(x, y);
314                 }
315                 else // draw fake border
316                 {
317                     c = getColor(0x7);
318                 }
319 
320                 dg(x, y, c);
321             }
322     }
323 
324     private PaletteColor getCopyrightPixels(byte x, byte y) const
325     {
326         enum w = 93;
327         enum h = 3;
328         enum letterWidth = 3;
329 
330         // start coords
331         enum x0 = CHAR_SIZE_X * 2;
332         enum y0 = letterWidth * 29;
333 
334         x -= x0;
335         y -= y0;
336 
337         bool pixel;
338 
339         if(
340             (x >= 0 && x < w) &&
341             (y >= 0 && y < h)
342         )
343         {
344             pixel = nyaResearchLtd[x + y * w] != 0;
345         }
346 
347         return pixel
348             ? getColor(0x0)
349             : getColor(0x7); // default ZX background color
350     }
351 }
352 
353 unittest
354 {
355     auto c = new Computer;
356     auto d = new LEM1802(c);
357     d.splashTimeRemaining = 0;
358     c.attachDevice = d;
359 
360 
361     c.mem[0x8000] = 0b0111_1110_0_0000000 + '1'; // colored nonblinking '1'
362     c.mem[0x8001] = '2';
363     c.mem[0x8002] = '3';
364     c.mem[0x8000 + 32] = 'a';
365     c.mem[0x8000 + 33] = 0b0101_0110_1_0000000 + 'b';
366     c.mem[0x8000 + 34] = 'c';
367 
368     assert(d.getSymbol(0).character == '1');
369     assert(d.getSymbol(1).character == '2');
370     assert(d.getSymbol(2).character == '3');
371     assert(d.getSymbol(32).character == 'a');
372     assert(d.getSymbol(33).character == 'b');
373     assert(d.getSymbol(34).character == 'c');
374 
375     assert(d.getPixel(5, 4) == true);
376     assert(d.getPixel(6, 4) == false);
377 
378     auto f = d.getRgbFrame;
379 
380     assert(f[0] == RGB(255, 255, 85));
381     assert(f[1] == RGB(170, 170, 170));
382     assert(f[4] == RGB(0, 0, 0));
383     assert(f[1028] == RGB(170, 0, 170));
384     assert(f[1029] == RGB(170, 85, 0));
385 }
386 
387 import std.bitmanip: bitfields;
388 
389 struct Symbol
390 {
391     union
392     {
393         ushort word;
394 
395         mixin(bitfields!(
396             ubyte, "character",  7,
397             bool,  "blinking",   1,
398             ubyte, "background", 4,
399             ubyte, "foreground", 4,
400         ));
401     }
402 }
403 
404 struct PaletteColor
405 {
406     union
407     {
408         ushort word;
409 
410         mixin(bitfields!(
411             ubyte, "b", 4,
412             ubyte, "g", 4,
413             ubyte, "r", 4,
414             ubyte, "",  4,
415         ));
416     }
417 
418     RGB toRGB() const
419     {
420         RGB s;
421 
422         s.r += r; s.r *= 17;
423         s.g += g; s.g *= 17;
424         s.b += b; s.b *= 17;
425 
426         return s;
427     }
428 }
429 
430 enum InterruptAction : ushort
431 {
432     MEM_MAP_SCREEN,
433     MEM_MAP_FONT,
434     MEM_MAP_PALETTE,
435     SET_BORDER_COLOR,
436     MEM_DUMP_FONT,
437     MEM_DUMP_PALETTE,
438 }
439 
440 struct RGB
441 {
442     ubyte r, g, b;
443 }
444 
445 private immutable ushort[256] defaultFont =
446 [
447     0xb79e, 0x388e, 0x722c, 0x75f4, 0x19bb, 0x7f8f, 0x85f9, 0xb158,
448     0x242e, 0x2400, 0x082a, 0x0800, 0x0008, 0x0000, 0x0808, 0x0808,
449     0x00ff, 0x0000, 0x00f8, 0x0808, 0x08f8, 0x0000, 0x080f, 0x0000,
450     0x000f, 0x0808, 0x00ff, 0x0808, 0x08f8, 0x0808, 0x08ff, 0x0000,
451     0x080f, 0x0808, 0x08ff, 0x0808, 0x6633, 0x99cc, 0x9933, 0x66cc,
452     0xfef8, 0xe080, 0x7f1f, 0x0701, 0x0107, 0x1f7f, 0x80e0, 0xf8fe,
453     0x5500, 0xaa00, 0x55aa, 0x55aa, 0xffaa, 0xff55, 0x0f0f, 0x0f0f,
454     0xf0f0, 0xf0f0, 0x0000, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff,
455     0x0000, 0x0000, 0x005f, 0x0000, 0x0300, 0x0300, 0x3e14, 0x3e00,
456     0x266b, 0x3200, 0x611c, 0x4300, 0x3629, 0x7650, 0x0002, 0x0100,
457     0x1c22, 0x4100, 0x4122, 0x1c00, 0x1408, 0x1400, 0x081c, 0x0800,
458     0x4020, 0x0000, 0x0808, 0x0800, 0x0040, 0x0000, 0x601c, 0x0300,
459     0x3e49, 0x3e00, 0x427f, 0x4000, 0x6259, 0x4600, 0x2249, 0x3600,
460     0x0f08, 0x7f00, 0x2745, 0x3900, 0x3e49, 0x3200, 0x6119, 0x0700,
461     0x3649, 0x3600, 0x2649, 0x3e00, 0x0024, 0x0000, 0x4024, 0x0000,
462     0x0814, 0x2200, 0x1414, 0x1400, 0x2214, 0x0800, 0x0259, 0x0600,
463     0x3e59, 0x5e00, 0x7e09, 0x7e00, 0x7f49, 0x3600, 0x3e41, 0x2200,
464     0x7f41, 0x3e00, 0x7f49, 0x4100, 0x7f09, 0x0100, 0x3e41, 0x7a00,
465     0x7f08, 0x7f00, 0x417f, 0x4100, 0x2040, 0x3f00, 0x7f08, 0x7700,
466     0x7f40, 0x4000, 0x7f06, 0x7f00, 0x7f01, 0x7e00, 0x3e41, 0x3e00,
467     0x7f09, 0x0600, 0x3e61, 0x7e00, 0x7f09, 0x7600, 0x2649, 0x3200,
468     0x017f, 0x0100, 0x3f40, 0x7f00, 0x1f60, 0x1f00, 0x7f30, 0x7f00,
469     0x7708, 0x7700, 0x0778, 0x0700, 0x7149, 0x4700, 0x007f, 0x4100,
470     0x031c, 0x6000, 0x417f, 0x0000, 0x0201, 0x0200, 0x8080, 0x8000,
471     0x0001, 0x0200, 0x2454, 0x7800, 0x7f44, 0x3800, 0x3844, 0x2800,
472     0x3844, 0x7f00, 0x3854, 0x5800, 0x087e, 0x0900, 0x4854, 0x3c00,
473     0x7f04, 0x7800, 0x047d, 0x0000, 0x2040, 0x3d00, 0x7f10, 0x6c00,
474     0x017f, 0x0000, 0x7c18, 0x7c00, 0x7c04, 0x7800, 0x3844, 0x3800,
475     0x7c14, 0x0800, 0x0814, 0x7c00, 0x7c04, 0x0800, 0x4854, 0x2400,
476     0x043e, 0x4400, 0x3c40, 0x7c00, 0x1c60, 0x1c00, 0x7c30, 0x7c00,
477     0x6c10, 0x6c00, 0x4c50, 0x3c00, 0x6454, 0x4c00, 0x0836, 0x4100,
478     0x0077, 0x0000, 0x4136, 0x0800, 0x0201, 0x0201, 0x0205, 0x0200,
479 ];
480 
481 private immutable ushort[16] defaultPalette =
482 [
483 	0x000, 0x00a, 0x0a0, 0x0aa,
484 	0xa00, 0xa0a, 0xa50, 0xaaa,
485 	0x555, 0x55f, 0x5f5, 0x5ff,
486 	0xf55, 0xf5f, 0xff5, 0xfff
487 ];
488 
489 private immutable nyaResearchLtd = cast(immutable ubyte[]) import("splash_string.data");
490 
491 // TODO: Phobos core.bitop.bt is glitches in release versions, so I made my own
492 /// Checks bit
493 private bool bt(ubyte value, ubyte bit) pure
494 {
495     assert(bit < 8);
496 
497     auto t = value >>> bit;
498     return t % 2 != 0;
499 }