jsbeeb 1.12.0 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/6847.js ADDED
@@ -0,0 +1,724 @@
1
+ "use strict";
2
+ import * as utils from "./utils.js";
3
+ import * as fontData from "./6847_fontdata.js";
4
+
5
+ const VDISPENABLE = 1 << 0,
6
+ HDISPENABLE = 1 << 1,
7
+ FRAMESKIPENABLE = 1 << 5;
8
+ // , EVERYTHINGENABLED = VDISPENABLE | HDISPENABLE | FRAMESKIPENABLE
9
+ /*
10
+ http://mdfs.net/Docs/Comp/Acorn/Atom/atap25.htm
11
+
12
+ 25.5 Input/Output Port Allocations
13
+ The 8255 Programmable Peripheral Interface Adapter contains three 8-bit ports, and all but one of these lines is used by the ATOM.
14
+
15
+ Port A - #B000
16
+ Output bits: Function:
17
+ 0 - 3 Keyboard row
18
+ 4 - 7 Graphics mode
19
+
20
+
21
+
22
+ Hardware: PPIA 8255
23
+
24
+ output b000 0 - 3 keyboard row, 4 - 7 graphics mode
25
+ b002 0 cas output, 1 enable 2.4kHz, 2 buzzer, 3 colour set
26
+
27
+ input b001 0 - 5 keyboard column, 6 CTRL key, 7 SHIFT key
28
+ b002 4 2.4kHz input, 5 cas input, 6 REPT key, 7 60 Hz input
29
+
30
+
31
+
32
+ AG AS INTEXT INV GM2 GM1 GM0
33
+ -- -- ------ --- --- --- ---
34
+ 0 0 0 0 X X X Internal Alphanumerics
35
+ 0 0 0 1 X X X Internal Alphanumerics Inverted
36
+ 0 0 1 0 X X X External Alphanumerics
37
+ 0 0 1 1 X X X External Alphanumerics Inverted
38
+ 0 1 0 X X X X Semigraphics 4
39
+ 0 1 1 X X X X Semigraphics 6
40
+ 1 X X X 0 0 0 Graphics CG1 (64x64x4) (16 bpr) #10
41
+ 1 X X X 0 0 1 Graphics RG1 (128x64x2) (16 bpr) #30
42
+ 1 X X X 0 1 0 Graphics CG2 (128x64x4) (32 bpr) #50
43
+ 1 X X X 0 1 1 Graphics RG2 (128x96x2) (16 bpr) #70
44
+ 1 X X X 1 0 0 Graphics CG3 (128x96x4) (32 bpr) #90
45
+ 1 X X X 1 0 1 Graphics RG3 (128x192x2) (16 bpr) #b0
46
+ 1 X X X 1 1 0 Graphics CG6 (128x192x4) (32 bpr) #d0
47
+ 1 X X X 1 1 1 Graphics RG6 (256x192x2) (32 bpr) #f0
48
+
49
+ http://members.casema.nl/hhaydn/howel/logic/6847_clone.htm
50
+
51
+ 256 = 256/8 = 32 32x1bpp = reg1:32 0x20
52
+ 128 = 128/8 = 16 16x2bpp = reg1:32 0x20
53
+
54
+ 128 = 128/8 = 16 16x1bpp = reg1:16 (xscale*2) 0x10
55
+ 64 = 64/8 = 8 8x2bpp = reg1:16 (xscale*2) 0x10
56
+
57
+
58
+ # Data on PORTA used to set the screen mode; 0x10 means graphics mode, and then 0x20, 0x40, 0x80 are used to set the graphics mode.
59
+ # Data on PORTC used to set the colour select bit, which is used in conjunction with the graphics mode bits to select the colour of the screen.
60
+ # css = 0 means Black/Green
61
+ # css = 1 means Black/Orange
62
+
63
+ # PORTA is #b000 and PORTC is #b002
64
+ # bits 4-7 on #b000 are used to set the graphics mode, )
65
+ # and bit 3 on #b002 is used to set the colour select bit. (CSS connected to PC3 on 8255 via)
66
+ # eight bits 0-7
67
+
68
+
69
+ // video mode constants
70
+ //ppia PORTA
71
+ MODE_GM2 = 0x80; // only used if AG is 1 (bit 7)
72
+ MODE_GM1 = 0x40; // only used if AG is 1 (bit 6)
73
+ MODE_GM0 = 0x20; // only used if AG is 1 (bit 5)
74
+ MODE_AG = 0x10; // alpha or graphics (bit 4)
75
+
76
+ //ppia PORTC
77
+ MODE_CSS = 0x08; // colour select (bit 3)
78
+
79
+ when reading a byte from the VDG, looking at
80
+ // WITHIN THE VIDEO MEMORY (bit 6, 6, 7) (AS, INTEXT, INV resp.)
81
+ (INT/EXT & A/S connected to D6, and INV connected to D7 from CPU
82
+
83
+ This means D6 switches between internal alphanumeric, and semigraphics 6 (SG6)
84
+
85
+ // A/S-INT/EXT, INV can be changed character by character, and CSS on interrupt from CPU
86
+ // these not used if AG is 1, GM not used if AG is 0
87
+
88
+ for bits
89
+
90
+ CG1, CG2, CG3, CG6, RG6 are 2bpp
91
+ RG1, RG2, RG3 are 1bpp
92
+
93
+ ?#B002=8
94
+ F.I=0TO255;I?#8000=I;N.
95
+
96
+ text colours are black/green (css=0) and black/orange (css=1); border black always
97
+ css not used for semigraphics 4 : THIS IS NOT AVAILABLE ON ATOM?
98
+ css is used for semigraphics 6,
99
+ they use 4 bits and 6 bits for luminance (0 off, 1 on)
100
+ and the last 2 or 3 bits are used for colour
101
+ (UNAVAILABLE) SG4 : 3 bits : 8 colours + black: green, yellow, blue, red , buff, cyan, magenta, orange
102
+ (PARTIALLY AVAILABLE) SG6 : 2 bits : 4 colours + black: css = 0, green, yellow, blue, red, css=1, buff, cyan, magenta, orange
103
+ only 1 bit is used of SG6 - to get yellow/red, cyan/orange
104
+ */
105
+
106
+ // const MODE_AG = 0x80,
107
+ // MODE_GM2 = 0x40,
108
+ // MODE_GM1 = 0x20,
109
+ // MODE_GM0 = 0x10;
110
+
111
+ // constant for the graphics mode
112
+ const MODE_AG = 0x10; // graphics mode
113
+
114
+ export class Video6847 {
115
+ constructor(video) {
116
+ this.video = video;
117
+ this.ppia = null; // set during reset
118
+
119
+ this.levelDEW = false;
120
+ this.levelDISPTMG = false;
121
+
122
+ // 8 colours (alpha on MSB)
123
+ //
124
+ this.collook = utils.makeFast32(
125
+ new Uint32Array([
126
+ 0xff000000, // #00000000, // black
127
+ 0xff03b91e, // #00ff00, // green
128
+ 0xff00ffff, // #ffff00, // yellow
129
+ 0xffff083b, // #3b08ff, // blue
130
+ 0xff0516b9, // #b91605, // red
131
+ 0xff018eb4, // #b48e01, // buff
132
+ 0xffeb9200, // #0092eb, // cyan
133
+ 0xffff1cff, // #ff1cff, // magenta
134
+ 0xff005bbd, // #bd5b00, // orange
135
+
136
+ 0xff000600, // dark green (char background) #000600
137
+ 0xff000d1c, // dark orange (char background) #1c0d00
138
+ ]),
139
+ );
140
+
141
+ // { 0, 0, 0 }, /*Black 0*/
142
+ // { 0, 63, 0 }, /*Green 1*/
143
+ // { 63, 63, 0 }, /*Yellow 2*/
144
+ // { 0, 0, 63 }, /*Blue 3 */
145
+ // { 63, 0, 0 }, /*Red 4 */
146
+ // { 63, 63, 63 }, /*Buff 5*/
147
+ // { 0, 63, 63 }, /*Cyan 6*/
148
+ // { 63, 0, 63 }, /*Magenta 7*/
149
+ // { 63, 32, 0 }, /*Orange 8 - can be red on the Atom*/
150
+
151
+ // /* dark green 9 */
152
+ // /* dark orange 10 */
153
+
154
+ this.interlacedSyncAndVideo = false;
155
+ // The 6847 blitters always write two framebuffer lines per pixel row,
156
+ // so Video.clearPaintBuffer() must clear both lines.
157
+ this.doubledScanlines = true;
158
+
159
+ this.regs = new Uint8Array(32);
160
+ this.bitmapX = 0;
161
+ this.bitmapY = 0;
162
+ this.frameCount = 0;
163
+ this.inHSync = false;
164
+ this.inVSync = false;
165
+
166
+ this.horizCounter = 0;
167
+ this.vertCounter = 0;
168
+ this.scanlineCounter = 0;
169
+ this.addr = 0;
170
+ this.lineStartAddr = 0;
171
+ this.nextLineStartAddr = 0;
172
+
173
+ this.pixelsPerChar = 8;
174
+
175
+ this.bitmapPxPerPixel = 2; // each pixel is 2 bitmap pixels wide and high
176
+ this.pixelsPerBit = this.bitmapPxPerPixel;
177
+ this.bpp = 1;
178
+
179
+ this.cpuAddr = 0;
180
+ this.dispEnabled = 0;
181
+
182
+ //PAL based = 312 lines
183
+ //NTSC based = 262 lines
184
+ // initialiser is outside the function to improve performance
185
+ this.modes = {
186
+ //perchar , pixpb,lines, bpp
187
+ 0xf0: [8, 1, 1, 1], //clear4 256x192x2, pixels 1w1h MAIN MENU
188
+ 0xb0: [16, 2, 1, 1], //clear3 128x192x2, pixels 2w1h BABIES
189
+ 0x70: [16, 2, 2, 1], //clear2 128x96x2, pixels 2w2h 3D ASTEROIDS
190
+ 0x30: [16, 2, 3, 1], //clear1 128x64x2 , pixels 3w4h (2w4h) 3D MAZE
191
+ 0xd0: [8, 2, 1, 2], //?#B000=#D0 128x192x4,pixels 2w1h CHUCKIE EGG
192
+ 0x90: [8, 2, 2, 2], //?#B000=#90 128x96x4, pixels 2w2h FLAPPY BIRD
193
+ 0x50: [8, 2, 3, 2], //?#B000=#50 128x64x4 , pixels 3w3h (4w3h) BREAKOUT (maingame)
194
+ 0x10: [16, 4, 3, 2], //?#B000=#10 64x64x4 , pixels 4w3h FIZZLE BRICKS
195
+ 0x00: [8, -1, 12, 1], // clear0 //0,0 not used on Mode 0 (uses blitChar), pixelsPerBit, bpp
196
+ };
197
+
198
+ // THESE MIDDLE THREE NUMBERS - THE SECOND AND THIRD ONE AFFECT THE VSYNC TIMING
199
+ // first is end of frame - i.e. full lines to display
200
+ // second is vsync start based on vertcounter
201
+ // third is total lines in a full frame
202
+ // vpulsewidth is how far through the individual scanline to end the vsync - i.e. sub scanline tine
203
+
204
+ this.lastmode = 0xff;
205
+
206
+ this.lastseconds = 0;
207
+
208
+ this.vdg_cycles = 0;
209
+ this.charTime = 0;
210
+
211
+ this.bordercolour = 0x00; // 0x00 black 0x01 // green or orange depending on CSS
212
+
213
+ this.init();
214
+
215
+ this.reset(null, null); // bit daft but it creates the members.
216
+
217
+ this.clearPaintBuffer();
218
+ this.paint();
219
+ }
220
+
221
+ init() {
222
+ this.curGlyphs = fontData.makeCharsAtom();
223
+ }
224
+
225
+ reset(cpu, ppia) {
226
+ this.cpu = cpu;
227
+ this.ppia = ppia;
228
+ }
229
+
230
+ // USE PAINT from VIDEO
231
+ paint() {
232
+ this.video.paint();
233
+ }
234
+
235
+ clearPaintBuffer() {
236
+ this.video.interlacedSyncAndVideo = this.interlacedSyncAndVideo;
237
+ this.video.doubledScanlines = this.doubledScanlines;
238
+ this.video.frameCount = this.frameCount;
239
+ this.video.bitmapX = this.bitmapX;
240
+ this.video.bitmapY = this.bitmapY;
241
+ this.video.clearPaintBuffer();
242
+ }
243
+ // END
244
+
245
+ paintAndClear() {
246
+ // skip 5 frames
247
+ if (this.dispEnabled & FRAMESKIPENABLE) {
248
+ this.paint();
249
+ this.clearPaintBuffer();
250
+ }
251
+ this.dispEnabled &= ~FRAMESKIPENABLE;
252
+ let enable = FRAMESKIPENABLE;
253
+ if (this.frameCount % 5) enable = 0;
254
+ this.dispEnabled |= enable;
255
+
256
+ this.frameCount++;
257
+ this.bitmapY = 0;
258
+ }
259
+
260
+ /*
261
+ Snow is caused by the CPU changing the address bus for 500ns at the
262
+ same time as the VDG is expecting to get data from the address it has
263
+ requested, which takes 1100ns (1.1us). This only occurs if the cpu is
264
+ access graphics memory. So while the VDG is generating a scanline, the address
265
+ will change for 500ns based on the CPU memory accesses (read or write) and
266
+ the VDG will read 'noise' from the CPU addressed memory location instead.
267
+ */
268
+
269
+ cpuAddrAccess(addr) {
270
+ // CPU has read from memory here
271
+ // VDG can read from this address for a cycle if it was video memory
272
+ // to generate snow
273
+ this.cpuAddr = addr;
274
+ }
275
+
276
+ // atom video memory is 0x8000->0x9fff (8k but only bottom 6k used)
277
+ // effecively goes up to 0x9800
278
+ readVideoMem() {
279
+ let cpuaddr = this.cpuAddr;
280
+
281
+ this.cpuAddr = 0; // reset the memory access by cpu but if it tries again it'll be set again
282
+
283
+ // during a vdg cycle, cpu might be active
284
+ if (this.vdg_cycles >= 0 && this.vdg_cycles < 1) {
285
+ if (cpuaddr >= 0x8000 && cpuaddr <= 0x9800) {
286
+ return this.cpu.videoRead(cpuaddr);
287
+ }
288
+ }
289
+
290
+ let memAddr = this.addr & 0x1fff;
291
+ memAddr |= 0x8000;
292
+ return this.cpu.videoRead(memAddr);
293
+ }
294
+
295
+ dispEnableSet(flag) {
296
+ this.dispEnabled |= flag;
297
+ }
298
+
299
+ dispEnableClear(flag) {
300
+ this.dispEnabled &= ~flag;
301
+ }
302
+
303
+ setValuesFromMode(mode) {
304
+ mode = mode & 0xf0;
305
+ // In text mode (AG=0), the GM bits are ignored per MC6847 datasheet.
306
+ // Force mode to 0x00 so we don't index into undefined modes table entries.
307
+ if (!(mode & MODE_AG)) mode = 0x00;
308
+
309
+ // if no change in mode then do nothing
310
+ if (this.lastmode === mode) return;
311
+
312
+ this.lastmode = mode;
313
+
314
+ this.pixelsPerChar = this.modes[mode][0]; // 8 pixels per element
315
+ this.pixelsPerBit = this.bitmapPxPerPixel * this.modes[mode][1];
316
+ let linesPerRow = this.modes[mode][2]; // move to reg9
317
+ this.bpp = this.modes[mode][3];
318
+
319
+ this.charLinesreg9 = linesPerRow - 1; //2 - scanlines per char
320
+
321
+ //NEED TO RESET THE LINE IF
322
+ //MODE SWITCH MID FRAME
323
+ this.scanlineCounter = 0;
324
+ this.lineStartAddr = this.nextLineStartAddr;
325
+ }
326
+
327
+ // this is usually called from 'video' so 'this'
328
+ // is a reference to 'video'
329
+ polltimeFacade(clocks) {
330
+ if (this.video6847 != undefined) {
331
+ this.video6847.polltime(clocks);
332
+ return;
333
+ }
334
+ throw new Error("Video6847 not attached — polltimeFacade called before video.video6847 was initialised");
335
+ }
336
+
337
+ // ATOM uses 6847 chip
338
+ polltime(clocks) {
339
+ const mode = this.ppia.portapins & 0xf0;
340
+ const css = (this.ppia.portcpins & 0x08) >>> 2;
341
+ this.setValuesFromMode(mode);
342
+ // Note: Polltime is called from the CPU many times during a frame. Once the VDG has drawn a bit of a frame
343
+ // it returns, and then comes back here later to continue the same frame. That's how the snow is able to work
344
+ // it doesn't draw the whole frame. It regularly gives control back to the CPU.
345
+
346
+ const vdgcharclock = this.pixelsPerChar / 2; // 4 or 8
347
+ // The MC6847 datasheet specifies 3.579545 MHz, but 3.638004 is used here
348
+ // as an empirical correction to align VSync/interrupt timing with the Atom's
349
+ // 1 MHz CPU clock. TODO: revisit once full integration is testable.
350
+ const vdgclock = 3.638004;
351
+ this.vdg_cycles += clocks * vdgclock;
352
+
353
+ const vdgframelines = 262; // 312 PAL (but no pal on standard atom) 262; // NTSC
354
+ const vdglinetime = 228; // vdg cycles to do a line; not 227.5
355
+
356
+ // full bordered width is 185.5 cycles
357
+ // (185.5+42 = 227.5)
358
+ const HBNK = 42; // left border start (16.5+25.5)
359
+ const leftborder = 29; // 29.5
360
+ const displayH = 128; //cycles - 186 cycles including borders - 228 for full horizontal
361
+ const rightborder = 29; //28.5
362
+
363
+ // total = 29+29+128+42 = 228
364
+
365
+ const vertblank = 13; //13
366
+ const topborder = 25; //25
367
+ const displayV = 192;
368
+ // let bottomborder = 26; // 26 + 6 = 32 = time in vsync
369
+ // let vertretrace = 6;
370
+ // ALL ADDS UP TO 262
371
+
372
+ // in vsync for 32 lines = bottomborder+vertretrace
373
+ // out vsync for 230 lines = vertblank+topborder+displayV
374
+
375
+ while (this.vdg_cycles >= 1) {
376
+ this.vdg_cycles -= 1;
377
+ this.charTime -= 1;
378
+
379
+ let nextChar = this.charTime <= 0;
380
+
381
+ if (nextChar) {
382
+ this.charTime += vdgcharclock;
383
+ this.bitmapX += this.pixelsPerChar * this.bitmapPxPerPixel;
384
+ }
385
+
386
+ if (this.inHSync) {
387
+ // Start at -ve pos because new character is added before the pixel render
388
+ this.bitmapX = -this.pixelsPerChar * this.bitmapPxPerPixel;
389
+
390
+ this.bitmapY += this.bitmapPxPerPixel;
391
+
392
+ if (this.bitmapY >= 768) {
393
+ // Arbitrary moment when TV will give up and start flyback in the absence of an explicit VSync signal
394
+ this.paintAndClear();
395
+ }
396
+ this.inHSync = false;
397
+ }
398
+
399
+ // right border - record addr for next FULL line
400
+ if (this.horizCounter === HBNK + leftborder + displayH) this.nextLineStartAddr = this.addr;
401
+
402
+ // Stop drawing outside the right border
403
+ if (this.horizCounter === HBNK + leftborder + displayH) {
404
+ this.dispEnableClear(HDISPENABLE);
405
+ }
406
+
407
+ // left border - start the next line (not necessarily the FULL line)
408
+ if (this.horizCounter === HBNK + leftborder) {
409
+ this.dispEnableSet(HDISPENABLE);
410
+ this.addr = this.lineStartAddr;
411
+ this.charTime = 0;
412
+ }
413
+
414
+ // vertcounter runs from 0 to 262
415
+ // got to the end, start again
416
+ if (this.vertCounter === vertblank + topborder) {
417
+ this.scanlineCounter = 0;
418
+ this.nextLineStartAddr = 0;
419
+ this.lineStartAddr = this.nextLineStartAddr;
420
+ this.dispEnableSet(VDISPENABLE);
421
+ }
422
+
423
+ if (this.vertCounter === vertblank + topborder + displayV) {
424
+ this.dispEnableClear(VDISPENABLE);
425
+ }
426
+
427
+ // reached end of the vdgline - start the hsync
428
+ if (this.horizCounter === HBNK + leftborder + displayH + rightborder && !this.inHSync) {
429
+ this.inHSync = true;
430
+ }
431
+
432
+ // image between 0 and 191 inc.
433
+ // vsync start (1) at line 192
434
+ // vsync end (0) at line 224
435
+ // 32 lines between inVSync and !inVSync
436
+
437
+ let vSyncEnding = false;
438
+ let vSyncStarting = false;
439
+
440
+ if (this.vertCounter === vertblank + topborder + displayV && !this.inVSync) {
441
+ vSyncStarting = true;
442
+ this.inVSync = true;
443
+ // Frame Sync is high normally and
444
+ // goes low when in VSync
445
+ // in VSync for 32 lines, and
446
+ // out of VSync for 262-32 lines
447
+ }
448
+
449
+ if (this.vertCounter === 0 && this.inVSync) {
450
+ vSyncEnding = true;
451
+ this.inVSync = false;
452
+ }
453
+
454
+ if (!vSyncStarting && vSyncEnding) {
455
+ this.paintAndClear();
456
+ }
457
+
458
+ if (vSyncStarting || vSyncEnding) {
459
+ this.ppia.setVBlankInt(this.inVSync);
460
+
461
+ // if (vSyncEnding)
462
+ // {
463
+ // let seconds = this.cpu.cycleSeconds+this.cpu.currentCycles/1000000.0;
464
+ // let diff = seconds - this.lastseconds;
465
+ // $("#vdg_text").html(
466
+ // "FPS "+(1/diff).toFixed(5)+" ("+diff.toFixed(5)+")<br>"
467
+ // );
468
+ // this.lastseconds = seconds ?? 0;
469
+ // }
470
+
471
+ // fix the border colour at the end/start of a frame
472
+ // it won't change within a frame
473
+ let AGM = (mode & MODE_AG) === 0;
474
+
475
+ if (AGM)
476
+ this.bordercolour = 0x00; // black
477
+ else this.bordercolour = 0x01; // green orange
478
+ }
479
+
480
+ if (nextChar) {
481
+ // once the whole of the Vertical and Horizontal is complete then do this
482
+ let insideBorder = (this.dispEnabled & (HDISPENABLE | VDISPENABLE)) === (HDISPENABLE | VDISPENABLE);
483
+ if (insideBorder) {
484
+ // read from video memory - uses this.addr
485
+ let dat = this.readVideoMem();
486
+
487
+ let offset = this.bitmapY;
488
+ offset = offset * 1024 + this.bitmapX;
489
+ // Render data depending on display enable state.
490
+ if (this.bitmapX >= 0 && this.bitmapX < 1024 && this.bitmapY < 625) {
491
+ {
492
+ // TODO: Add in the INTEXT modifiers to mode (if necessary)
493
+ // blit into the fb32 buffer which is painted by VIDEO
494
+ if ((mode & MODE_AG) === 0)
495
+ // MODE_AG - bit 4; 0x10 is the AG bit
496
+ this.blitChar(this.video.fb32, dat, offset, this.pixelsPerChar, css);
497
+ else this.blitPixels(this.video.fb32, dat, offset, css);
498
+ }
499
+ }
500
+ } else {
501
+ // draw border (black, or green/buff depending on bordercolour and CSS)
502
+ if (this.bitmapX >= 0 && this.bitmapX < 1024 && this.bitmapY >= 0 && this.bitmapY < 625) {
503
+ const offset = this.bitmapY * 1024 + this.bitmapX;
504
+ this.blitBorder(this.video.fb32, this.bordercolour, offset, css);
505
+ }
506
+ }
507
+
508
+ this.addr = (this.addr + 1) & 0x1fff;
509
+ }
510
+
511
+ // end of horizontal line
512
+ if (this.horizCounter === vdglinetime) {
513
+ let completedCharVertical = this.scanlineCounter === this.charLinesreg9; // regs9 - scanlines per char // 9 Maximum Raster Address
514
+
515
+ //keep drawing same memory addresses until end of scanlines
516
+ if (completedCharVertical) {
517
+ this.lineStartAddr = this.nextLineStartAddr;
518
+ }
519
+
520
+ this.scanlineCounter += 1;
521
+
522
+ if (completedCharVertical) {
523
+ // this.hadVSyncThisRow = false;
524
+ this.scanlineCounter = 0;
525
+ }
526
+
527
+ // end of vertical frame was detected
528
+ if (this.vertCounter >= vdgframelines) {
529
+ this.vertCounter = 0;
530
+ } else {
531
+ this.vertCounter = (this.vertCounter + 1) & 0x1ff;
532
+ }
533
+
534
+ // start new horizontal line
535
+ this.horizCounter = 0;
536
+ } else {
537
+ this.horizCounter = (this.horizCounter + 1) & 0xff;
538
+ }
539
+
540
+ // // dump some data to CSV
541
+ // let a = this.cpu.cycleSeconds*1000000.0+this.cpu.currentCycles;
542
+ // let b = this.horizCounter;
543
+ // let c = this.vertCounter;
544
+ // let d = this.inVSync;
545
+ // let e = css;
546
+
547
+ // //using template literals for strings substitution
548
+ // if (this.cpu.cycleSeconds==10 && this.cpu.currentCycles<60000 && this.horizCounter == 0)
549
+ // {
550
+ // $("#csv_output").append(
551
+ // `<br>${a},${b},${c},${d},${e}`);
552
+ // }
553
+ } // matches while
554
+ }
555
+
556
+ blitBorder(buf, data, destOffset, css) {
557
+ const bpp = this.bpp;
558
+ const pixelsPerBit = this.pixelsPerBit / bpp;
559
+ const numPixels = 8 * pixelsPerBit; //per char
560
+ const fb32 = buf;
561
+ const colour = data === 0x00 ? this.collook[0] : this.collook[css ? 5 : 1];
562
+ for (let i = 0; i < numPixels; ++i) {
563
+ const n = numPixels - 1 - i; // pixels in reverse order
564
+ fb32[destOffset + n] = fb32[destOffset + n + 1024] = colour;
565
+ }
566
+ }
567
+
568
+ blitPixels(buf, data, destOffset, css) {
569
+ // let scanline = this.scanlineCounterT;
570
+ // bitpattern from data is either 4 or 8 pixels in raw graphics
571
+ // either white and black
572
+ // or 3 colours and black (in pairs of bits)
573
+ // that is: all graphics modes are 1bpp or 2bpp giving 2 colours or 4 colours
574
+
575
+ const bitdef = data;
576
+ const bpp = this.bpp;
577
+ const pixelsPerBit = this.pixelsPerBit / bpp;
578
+ let colour; // see 'collook' // alpha, blue, green, red
579
+
580
+ // MODE NEED TO CHANGE THE RASTER SCAN
581
+ // currently 32 x 16 - 256 x 192 (with 12 lines per row)
582
+
583
+ // can get wide with 16 pixels
584
+
585
+ destOffset |= 0;
586
+ let fb32 = buf;
587
+
588
+ const numPixels = 8 * pixelsPerBit; //per char
589
+ // draw two,four pixels for each bit in the data to fill the width.
590
+
591
+ for (let i = 0; i < numPixels; i++) {
592
+ const n = numPixels - 1 - i; // pixels in reverse order
593
+
594
+ // get bits in pairs or singles
595
+ const j = Math.floor(i / pixelsPerBit);
596
+
597
+ // get just one bit
598
+ // RG modes
599
+ if (bpp === 1) {
600
+ // get a bit
601
+ const cval = (bitdef >>> j) & 0x1;
602
+ // CSS=0: green foreground, CSS=1: buff foreground
603
+ colour = cval !== 0 ? this.collook[css ? 5 : 1] : this.collook[0];
604
+
605
+ // two bitmap lines per 1 pixel
606
+ fb32[destOffset + n + 1024] = fb32[destOffset + n] = colour;
607
+ } // CG modes
608
+ else {
609
+ //let cval = (bitdef>>>(j&0xe))&0x3;
610
+ const cval = (bitdef >>> (j & 0xe)) & 0x3;
611
+
612
+ // 2 or 4 - green/yellow/blue/red
613
+ let colindex = 1 + (cval | (css << 1));
614
+ colour = this.collook[colindex];
615
+
616
+ // two bitmap lines per 1 pixel
617
+ fb32[destOffset + n + 1024] = fb32[destOffset + n] = colour;
618
+ }
619
+ }
620
+ }
621
+
622
+ blitChar(buf, data, destOffset, numPixels, css) {
623
+ const scanline = this.scanlineCounter;
624
+ const chr = data & 0x7f;
625
+
626
+ // character set is just the pixel data
627
+ // 0 - 63 is alphachars green (0x00-0x3f) bits 0-5 for character
628
+ // 64-127 is 16 alphagraphics in 4 colours green/yellow/blue/red (0x40-0x7f) bit 6,7 (0xC0)
629
+ // bit 7 set is inverted and the alphagraphics again
630
+ // 128 - 191 is alphachars inverted green (0x80-0xbf)
631
+ // 192-255 is alphagraphics in different colours buff/cyan/magenta/orange (0xc0-0xff) bit 6,7 set (0xC0)
632
+
633
+ // in the data; bits 0-5 are the pixels
634
+ // bit 7 and CSS give the colour
635
+ // bit 6 indicates TEXT or GRAPHICS
636
+ // thus green and blue (css=0) alphagraphics cannot be accessed
637
+ // and buff and magenta (css=1) alphagraphics cannot be accessed
638
+ // bits 7,6
639
+ // [00] green text (or orange)
640
+ // [01] yellow graphics (or cyan)
641
+ // [10] inv green text (or inv orange)
642
+ // [11] red graphics (or orange)
643
+
644
+ // invert the char if bit 7 is set
645
+ const inv = !!(data & 0x80);
646
+ // graphics if bit 6 is set
647
+ const agmode = !!(data & 0x40);
648
+
649
+ //bitpattern for chars is in rows; each char is 12 rows deep
650
+ let chardef = this.curGlyphs[chr * 12 + scanline];
651
+
652
+ if (inv && !agmode) chardef = ~chardef;
653
+
654
+ numPixels |= 0;
655
+ numPixels *= this.bitmapPxPerPixel;
656
+
657
+ // can get wide with 16 pixels
658
+ const pixelsPerBit = numPixels / 8;
659
+
660
+ destOffset |= 0;
661
+ let fb32 = buf;
662
+ for (let i = 0; i < numPixels; ++i) {
663
+ const n = numPixels - 1 - i; // pixels in reverse order
664
+ const j = i / pixelsPerBit;
665
+ // text is either green/black or buff/black - nothing else
666
+ // css is 2 or 0 on input
667
+ let fgcol = this.collook[css ? 5 : 1];
668
+
669
+ if (agmode) {
670
+ // alphagraphics 6
671
+ // inv css | colour
672
+ // 0 0 | yellow (2) 10 + 0000
673
+ // 1 0 | red (4) 10 + 0010
674
+ // 0 2 | cyan (6) 10 + 0100
675
+ // 1 2 | orange (8) 10 + 0110
676
+ // css is 2 or 0 on input
677
+ fgcol = this.collook[2 + ((inv | css) << 1)];
678
+ }
679
+
680
+ const luminance = (chardef >>> j) & 0x1;
681
+ const colour = luminance ? fgcol : this.collook[css ? 10 : 9]; //dark orange or green
682
+ fb32[destOffset + n] = fb32[destOffset + n + 1024] = // two lines
683
+ colour;
684
+ }
685
+ }
686
+ }
687
+
688
+ /* Video modes:
689
+ Alpha internal
690
+ Alpha external
691
+ SemiGraphics Four 0011 CLEAR 1
692
+ SemiGraphics Six 0111 CLEAR 2
693
+
694
+ 0001 ?#B000=#10 1a
695
+ Colour Graphics 1 - 8 colours (CSS/C1/C0) - 64x64
696
+ 1024 bytes - 2bpp (4x3) - 4 pixels x 3 rows
697
+
698
+ 0011 clear 1
699
+ Resolution Graphics 1 - 4 colours (Lx) - 128x64
700
+ 1024 bytes - 1bpp (3x3)
701
+
702
+ 0101 ?#B000=#50 2a
703
+ Colour Graphics 2 - 8 colours (CSS/C1/C0) - 128x64
704
+ 2048 bytes - 2bpp (3x3)
705
+ 1011 CLEAR 2
706
+ Resolution Graphics 2 - 4 colours (Lx) - 128x96
707
+ 1536 bytes - 1bpp (2x2)
708
+
709
+ 1001 ?#B000=#90 3a
710
+ Colour Graphics 3 - 8 colours (CSS/C1/C0) - 128x96
711
+ 3072 bytes - 2bpp (2x2)
712
+ 1011 clear 3
713
+ Resolution Graphics 3 - 4 colours (Lx)- 128x192
714
+ 3072 bytes - 1bpp (2x1)
715
+
716
+ 110 ?#B000=#d0 4a
717
+ Colour Graphics 6 - 8 colours (CSS/C1/C0) - 128x192
718
+ 6144 bytes - 2bpp (4x1)
719
+ 1111 clear 4
720
+ Resolution Graphics 6 - 4 colours (Lx) 256x192
721
+ 6144 bytes - 1bpp (8x1)
722
+
723
+
724
+ */