This repository has been archived by the owner on Dec 24, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 91
/
Copy pathvideo.js
8632 lines (8122 loc) · 403 KB
/
video.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/**
* @fileoverview Implements the PCx86 Video component
* @author <a href="mailto:[email protected]">Jeff Parsons</a>
* @copyright © 2012-2019 Jeff Parsons
*
* This file is part of PCjs, a computer emulation software project at <https://www.pcjs.org>.
*
* PCjs is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCjs. If not,
* see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every modified copy of this work
* and to display that copyright notice when the software starts running; see COPYRIGHT in
* <https://www.pcjs.org/modules/shared/lib/defines.js>.
*
* Some PCjs files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of PCjs
* for purposes of the GNU General Public License, and the author does not claim any copyright
* as to their contents.
*/
"use strict";
if (typeof module !== "undefined") {
var Str = require("../../shared/lib/strlib");
var Web = require("../../shared/lib/weblib");
var DumpAPI = require("../../shared/lib/dumpapi");
var Component = require("../../shared/lib/component");
var State = require("../../shared/lib/state");
var PCX86 = require("./defines");
var Memory = require("./memory");
var Messages = require("./messages");
var ChipSet = require("./chipset");
var Keyboard = require("./keyboard");
var Mouse = require("./mouse");
var Controller = require("./bus").Controller;
var X86 = require("./x86");
}
/*
* MDA/CGA Support
* ---------------
*
* Since there's a lot of similarity between the MDA and CGA (eg, their text-mode video buffer
* format, and their use of the 6845 CRT controller), since the MDA ROM contains the fonts used
* by both devices, and since the same ROM BIOS supports both (in fact, the BIOS indiscriminately
* initializes both, regardless which is actually installed), this same component emulates both
* devices.
*
* When no model is specified, this component supports the ability to dynamically switch between
* MDA and CGA emulation, by simply toggling the SW1 motherboard "monitor type" switch settings
* and resetting the machine. In that model-less configuration, we install I/O port handlers for
* both MDA and CGA cards, regardless which monitor type is initially selected.
*
* To simulate an IBM PC containing both an MDA and CGA (ie, a "dual display" system), the machine
* configuration simply defines two video components, one with model "mda" and the other with model
* "cga", resulting in two displays; setting a specific model forces each instance of this component
* to register only those I/O ports belonging to that model.
*
* In a single-display system, dynamically switching cards (ie, between MDA and CGA) creates some
* visual challenges. For one, the MDA prefers a native screen size of 720x350, as it supports only
* one video mode, 80x25, with a 9x14 cell size. The CGA, on the other hand, has an 8x8 cell size,
* so when using an MDA-size screen, an 80x25 CGA screen will end up with 40-pixel borders on the
* left and right, and 75-pixel borders on the top and bottom. The result is a rather tiny CGA font
* surrounded by lots of wasted space, so it's best to turn on font scaling (see the "scale" property)
* and go with a larger screen size of, say, 960x400 (50% larger in width, 100% larger in height).
*
* I've also added support for font-doubling in createFont(). We use the 8x8 font for 80-column
* modes and the "doubled" 16x16 font for 40-column modes OR whenever the screen is large enough
* to use the 16x16 font, since font rendering without scaling provides the sharpest results.
* In fact, there's special logic in setDimensions() to ignore fScaleFont in certain cases (eg,
* 40-column modes, to improve sharpness and avoid stretching the font beyond readability).
*
* Graphics modes, on the other hand, are always scaled to the screen size. Pixels are captured
* in an off-screen buffer, which is then drawn to match the size of the virtual screen.
*
* TODO: Whenever there are borders, they should be filled with the CGA's overscan colors. However,
* in the case of graphics modes (and text modes whenever font scaling is enabled), we don't reserve
* any space for borders, so if borders are important, explicit border support will be required.
*
* EGA Support
* -----------
*
* EGA support piggy-backs on the existing MDA/CGA support. All the existing MDA/CGA port handlers
* now refer to either cardMono or cardColor (instead of directly to cardMDA or cardCGA), enabling
* the handlers to be redirected to cardMDA, cardCGA or cardEGA as appropriate.
*
* Note that an MDA card supported only a Monochrome Display and a CGA card supported only a Color
* Display (well, OK, *or* a TV monitor, which we don't currently support), but the EGA is much
* more flexible: the Enhanced Color Display was the preferred display, but the EGA also supported
* older displays; a Color Display on EGA wasn't ideal (same low resolutions but with more colors),
* but the EGA also brought high-resolution graphics to Monochrome displays, which was nice. Anyway,
* while all those EGA/monitor combinations will be nice to support, our virtual display support
* will focus initially on the Enhanced Color Display.
*
* TODO: Add support for jumpers P1 and P3 (see EGA TechRef p.85). P1 selects either 5-color-output
* for a CGA monitor or 6-color-output for an EGA monitor; we would presumably use this only to
* control certain assumptions about the virtual display's capabilities (ie, Color Display vs. Enhanced
* Color Display). P3 can switch all the I/O ports from 0x3nn to 0x2nn; the default is 0x3nn, and
* that's the only port range the EGA ROM supports as well.
*
* For quick reference, IBM EGA register values for the standard EGA modes, from pages 63-68 of the
* "IBM Enhanced Graphics Adapter" (http://minuszerodegrees.net/oa/OA - IBM Enhanced Graphics Adapter.pdf).
*
* WARNING: Some of these value are not programmed exactly as-is; for example, the CURSCANB values are
* adjusted +1 by the ROM BIOS in most cases, due to an EGA idiosyncrasy that IBM may not have intended.
*
* INT 0x10 Mode Requested: 00 01 02 03 04 05 06 07 0D 0E 0F 10 0F^ 10^ 00* 01* 02* 03*
*
* BIOSMODE: 01 01 03 03 04 04 06 07 0D 0E 0F 10 0F 10 01 01 03 03
* CRTC[0x00]: HTOTAL 37 37 70 70 37 37 70 60 37 70 60 5B 60 5B 2D 2D 5B 5B
* CRTC[0x01]: HDEND 27 27 4F 4F 27 27 4F 4F 27 4F 4F 4F 4F 4F 27 27 4F 4F
* CRTC[0x02]: HBSTART 2D 2D 5C %C 2D 2D 59 56 2D 56 56 53 56 53 2B 2B 53 53
* CRTC[0x03]: HBEND 37 37 2F 2F 37 37 2D 3A 37 2D 1A 17 3A 37 2D 2D 37 37
* CRTC[0x04]: HRSTART 31 31 5F 5F 30 30 5E 51 30 5E 50 50 50 52 28 28 51 51
* CRTC[0x05]: HREND 15 15 07 07 14 14 06 60 14 06 E0 BA 60 00 6D 6D 5B 5B
* CRTC[0x06]: VTOTAL 04 04 04 04 04 04 04 70 04 04 70 6C 70 6C 6C 6C 6C 6C
* CRTC[0x07]: OVERFLOW 11 11 11 11 11 11 11 1F 11 11 1F 1F 1F 1F 1F 1F 1F 1F
* CRTC[0x08]: PRESCAN 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
* CRTC[0x09]: MAXSCAN 07 07 07 07 01 01 01 0D 00 00 00 00 00 00 0D 0D 0D 0D
* CRTC[0x0A]: CURSCAN 06 06 06 06 00 00 00 0B 00 00 00 00 00 00 0B 0B 0B 0B
* CRTC[0x0B]: CURSCANB 07 07 07 07 00 00 00 0C 00 00 00 00 00 00 0C 0C 0C 0C
* CRTC[0x0C]: STARTHI -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
* CRTC[0x0D]: STARTLO -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
* CRTC[0x0E]: CURSORHI -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
* CRTC[0x0F]: CURSORLO -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
* CRTC[0x10]: VRSTART E1 E1 E1 E1 E1 E1 E0 5E E1 E0 5E 5E 5E 5E 5E 5E 5E 5E
* CRTC[0x11]: VREND 24 24 24 24 24 24 23 2E 24 23 2E 2B 2E 2B 2B 2B 2B 2B
* CRTC[0x12]: VDEND C7 C7 C7 C7 C7 C7 C7 5D C7 C7 5D 5D 5D 5D 5D 5D 5D 5D
* CRTC[0x13]: OFFSET 14 14 28 28 14 14 28 28 14 28 14 14 28 28 14 14 28 28
* CRTC[0x14]: UNDERLINE 08 08 08 08 00 00 00 0D 00 00 0D 0F 0D 0F 0F 0F 0F 0F
* CRTC[0x15]: VBSTART E0 E0 E0 E0 E0 E0 DF 5E E0 DF 5E 5F 5E 5F 5E 5E 5E 5E
* CRTC[0x16]: VBEND F0 F0 F0 F0 F0 F0 EF 6E F0 EF 6E 0A 6E 0A 0A 0A 0A 0A
* CRTC[0x17]: MODECTRL A3 A3 A3 A3 A2 A2 C2 A3 E3 E3 8B 8B E3 E3 A3 A3 A3 A3
* CRTC[0x18]: LINECOMP FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
* GRC[0x00]: SRESET 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
* GRC[0x01]: ESRESET 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
* GRC[0x02]: COLORCOMP 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
* GRC[0x03]: DATAROT 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
* GRC[0x04]: READMAP 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
* GRC[0x05]: MODE 10 10 10 10 30 30 00 10 00 00 10 10 00 00 10 10 10 10
* GRC[0x06]: MISC 0E 0E 0E 0E 0F 0F 0D 0A 05 05 07 07 05 05 0E 0E 0E 0E
* GRC[0x07]: COLORDC 00 00 00 00 00 00 00 00 0F 0F 0F 0F 0F 0F 00 00 00 00
* GRC[0x08]: BITMASK FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
* SEQ[0x00]: RESET 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03
* SEQ[0x01]: CLKMODE 0B 0B 01 01 0B 0B 01 00 0B 01 05 05 01 01 0B 0B 01 01
* SEQ[0x02]: MAPMASK 03 03 03 03 03 03 01 03 0F 0F 0F 0F 0F 0F 03 03 03 03
* SEQ[0x03]: CHARMAP 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
* SEQ[0x04]: MEMMODE 03 03 03 03 02 02 06 03 06 06 00 00 06 06 03 03 03 03
* ATC[0x00]: PAL00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
* ATC[0x01]: PAL01 01 01 01 01 13 13 17 08 01 01 08 01 08 01 01 01 01 01
* ATC[0x02]: PAL02 02 02 02 02 15 15 17 08 02 02 00 00 00 02 02 02 02 02
* ATC[0x03]: PAL03 03 03 03 03 17 17 17 08 03 03 00 00 00 03 03 03 03 03
* ATC[0x04]: PAL04 04 04 04 04 02 02 17 08 04 04 18 04 18 04 04 04 04 04
* ATC[0x05]: PAL05 05 05 05 05 04 04 17 08 05 05 18 07 18 05 05 05 05 05
* ATC[0x06]: PAL06 06 06 06 06 06 06 17 08 06 06 00 00 00 06 14 14 14 14
* ATC[0x07]: PAL07 07 07 07 07 07 07 17 08 07 07 00 00 00 07 07 07 07 07
* ATC[0x08]: PAL08 10 10 10 10 10 10 17 10 10 10 00 00 00 38 38 38 38 38
* ATC[0x09]: PAL09 11 11 11 11 11 11 17 18 11 11 08 01 08 39 39 39 39 39
* ATC[0x0A]: PAL0A 12 12 12 12 12 12 17 18 12 12 00 00 00 3A 3A 3A 3A 3A
* ATC[0x0B]: PAL0B 13 13 13 13 13 13 17 18 13 13 00 00 00 3B 3B 3B 3B 3B
* ATC[0x0C]: PAL0C 14 14 14 14 14 14 17 18 14 14 00 04 00 3C 3C 3C 3C 3C
* ATC[0x0D]: PAL0D 15 15 15 15 15 15 17 18 15 15 18 07 18 3D 3D 3D 3D 3D
* ATC[0x0E]: PAL0E 16 16 16 16 16 16 17 18 16 16 00 00 00 3E 3E 3E 3E 3E
* ATC[0x0F]: PAL0F 17 17 17 17 17 17 18 17 17 00 00 00 3F 3F 3F 3F 3F 3F
* ATC[0x10]: MODE 08 08 08 08 01 01 01 0E 01 01 0B 0B 0B 01 08 08 08 08
* ATC[0x11]: OVERSCAN 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
* ATC[0x12]: PLANES 0F 0F 0F 0F 03 03 01 0F 0F 0F 05 05 05 0F 0F 0F 0F 0F
* ATC[0x13]: HPAN 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
* VGA Support
* -----------
*
* VGA support further piggy-backs on the existing EGA support, by adding the extra registers, I/O port
* handlers, etc, that the VGA requires; any differences in registers common to both EGA and VGA are handled on
* a case-by-case basis, usually according to the Video.CARD value stored in nCard.
*
* More will be said here about PCjs VGA support later. But first, a word from IBM: "Video Graphics Array [VGA]
* Programming Considerations":
*
* Certain internal timings must be guaranteed by the user, in order to have the CRTC perform properly.
* This is due to the physical design of the chip. These timings can be guaranteed by ensuring that the
* rules listed below are followed when programming the CRTC.
*
* 1. The Horizontal Total [HTOTAL] register (R0) must be greater than or equal to a value of
* 25 decimal.
*
* 2. The minimum positive pulse width of the HSYNC output must be four character clock units.
*
* 3. Register R5, Horizontal Sync End [HREND], must be programmed such that the HSYNC
* output goes to a logic 0 a minimum of one character clock time before the 'horizontal display enable'
* signal goes to a logical 1.
*
* 4. Register R16, Vsync Start [VRSTART], must be a minimum of one horizontal scan line greater
* than register R18 [VDEND]. Register R18 defines where the 'vertical display enable' signal ends.
*
* When bit 5 of the Attribute Mode Control register equals 1, a successful line compare (see Line Compare
* [LINECOMP] register) in the CRT Controller forces the output of the PEL Panning register to 0's until Vsync
* occurs. When Vsync occurs, the output returns to the programmed value. This allows the portion of the screen
* indicated by the Line Compare register to be operated on by the PEL Panning register.
*
* A write to the Character Map Select register becomes valid on the next whole character line. No deformed
* characters are displayed by changing character generators in the middle of a character scan line.
*
* For 256-color 320 x 200 graphics mode hex 13, the attribute controller is configured so that the 8-bit attribute
* stored in video memory for each PEL becomes the 8-bit address (P0 - P7) into the integrated DAC. The user should
* not modify the contents of the internal Palette registers when using this mode.
*
* The following sequence should be followed when accessing any of the Attribute Data registers pointed to by the
* Attribute Index register:
*
* 1. Disable interrupts
* 2. Reset read/write flip/flop
* 3. Write to Index register
* 4. Read from or write to a data register
* 5. Enable interrupts
*
* The Color Select register in the Attribute Controller section may be used to rapidly switch between sets of colors
* in the video DAC. When bit 7 of the Attribute Mode Control register equals 0, the 8-bit color value presented to the
* video DAC is composed of 6 bits from the internal Palette registers and bits 2 and 3 from the Color Select register.
* When bit 7 of the Attribute Mode Control register equals 1, the 8-bit color value presented to the video DAC is
* composed of the lower four bits from the internal Palette registers and the four bits in the Color Select register.
* By changing the value in the Color Select register, software rapidly switches between sets of colors in the video DAC.
* Note that BIOS does not support multiple sets of colors in the video DAC. The user must load these colors if this
* function is to be used. Also see the Attribute Controller block diagram on page 4-26. Note that the above discussion
* applies to all modes except 256 Color Graphics mode. In this mode the Color Select register is not used to switch
* between sets of colors.
*
* An application that saves the "Video State" must store the 4 bytes of information contained in the system microprocessor
* latches in the graphics controller subsection. These latches are loaded with 32 bits from video memory (8 bits per map)
* each time the system microprocessor does a read from video memory. The application needs to:
*
* 1. Use write mode 1 to write the values in the latches to a location in video memory that is not part of
* the display buffer. The last location in the address range is a good choice.
*
* 2. Save the values of the latches by reading them back from video memory.
*
* Note: If in a chain 4 or odd/even mode, it will be necessary to reconfigure the memory organization as four
* sequential maps prior to performing the sequence above. BIOS provides support for completely saving and
* restoring video state. See the IBM Personal System/2 and Personal Computer BIOS Interface Technical Reference
* for more information.
*
* The description of the Horizontal PEL Panning register includes a figure showing the number of PELs shifted left
* for each valid value of the PEL Panning register and each valid video mode. Further panning beyond that shown in
* the figure may be accomplished by changing the start address in the CRT Controller registers, Start Address High
* and Start Address Low. The sequence involved in further panning would be as follows:
*
* 1. Use the PEL Panning register to shift the maximum number of bits to the left. See Figure 4-103 on page
* 4-106 for the appropriate values.
*
* 2. Increment the start address.
*
* 3. If you are not using Modes 0 + , 1 + , 2 + , 3 + ,7, or7 + , set the PEL Panning register to 0. If you
* are using these modes, set the PEL Panning register to 8. The screen will now be shifted one PEL left
* of the position it was in at the end of step 1. Step 1 through Step 3 may be repeated as desired.
*
* The Line Compare register (CRTC register hex 18) should be programmed with even values in 200 line modes when
* used in split screen applications that scroll a second screen on top of a first screen. This is a requirement
* imposed by the scan doubling logic in the CRTC.
*
* If the Cursor Start register (CRTC register hex 0A) is programmed with a value greater than that in the Cursor End
* register (CRTC register hex 0B), then no cursor is displayed. A split cursor is not possible.
*
* In 8-dot character modes, the underline attribute produces a solid line across adjacent characters, as in the IBM
* Color/Graphics Monitor Adapter, Monochrome Display Adapter and the Enhanced Graphics Adapter. In 9-dot modes, the
* underline across adjacent characters is dashed, as in the IBM 327X display terminals. In 9-dot modes, the line
* graphics characters (C0 - DF character codes) have solid underlines.
*
* For compatibility with the IBM Enhanced Graphics Adapter (EGA), the internal VGA palette is programmed the same
* as the EGA. The video DAC is programmed by BIOS so that the compatible values in the internal VGA palette produce
* a color compatible with what was produced by EGA. Mode hex 13 (256 colors) is programmed so that the first 16
* locations in the DAC produce compatible colors.
*
* Summing: When BIOS is used to load the video DAC palette for a color mode and a monochrome display is connected
* to the system unit, the color palette is changed. The colors are summed to produce shades of gray that allow
* color applications to produce a readable screen.
*
* There are 4 bits that should not be modified unless the sequencer is reset by setting bit 1 of the Reset register
* to 0. These bits are:
*
* • Bit 3, or bit 0 of the Clocking Mode register
* • Bit 3, or bit 2 of the Miscellaneous Output register
*
* Also, for quick reference, IBM VGA register values for the standard VGA modes (from http://www.pcjs.org/blog/2015/06/01/):
*
* INT 0x10 Mode Requested: 00 01 02 03 04 05 06 0D 0E 10 12 13
*
* BIOSMODE: 01 01 03 03 04 04 06 0D 0E 10 12 13
* CRTC[0x00]: HTOTAL 2D 2D 5F 5F 2D 2D 5F 2D 5F 5F 5F 5F
* CRTC[0x01]: HDEND 27 27 4F 4F 27 27 4F 27 4F 4F 4F 4F
* CRTC[0x02]: HBSTART 28 28 50 50 28 28 50 28 50 50 50 50
* CRTC[0x03]: HBEND 90 90 82 82 90 90 82 90 82 82 82 82
* CRTC[0x04]: HRSTART 2B 2B 55 55 2B 2B 54 2B 54 54 54 54
* CRTC[0x05]: HREND A0 A0 81 81 80 80 80 80 80 80 80 80
* CRTC[0x06]: VTOTAL BF BF BF BF BF BF BF BF BF BF 0B BF
* CRTC[0x07]: OVERFLOW 1F 1F 1F 1F 1F 1F 1F 1F 1F 1F 3E 1F
* CRTC[0x08]: PRESCAN 00 00 00 00 00 00 00 00 00 00 00 00
* CRTC[0x09]: MAXSCAN 4F 4F 4F 4F C1 C1 C1 C0 C0 40 40 41
* CRTC[0x0A]: CURSCAN 0D 0D 0D 0D 00 00 00 00 00 00 00 00
* CRTC[0x0B]: CURSCANB 0E 0E 0E 0E 00 00 00 00 00 00 00 00
* CRTC[0x0C]: STARTHI 00 00 00 00 00 00 00 00 00 00 00 00
* CRTC[0x0D]: STARTLO 00 00 00 00 00 00 00 00 00 00 00 00
* CRTC[0x0E]: CURSORHI 01 01 01 01 01 01 01 01 01 01 01 00
* CRTC[0x0F]: CURSORLO 19 19 41 41 19 19 41 19 41 41 E1 A2
* CRTC[0x10]: VRSTART 9C 9C 9C 9C 9C 9C 9C 9C 9C 83 EA 9C
* CRTC[0x11]: VREND 8E 8E 8E 8E 8E 8E 8E 8E 8E 85 8C 8E
* CRTC[0x12]: VDEND 8F 8F 8F 8F 8F 8F 8F 8F 8F 5D DF 8F
* CRTC[0x13]: OFFSET 14 14 28 28 14 14 28 14 28 28 28 28
* CRTC[0x14]: UNDERLINE 1F 1F 1F 1F 00 00 00 00 00 0F 00 40
* CRTC[0x15]: VBSTART 96 96 96 96 96 96 96 96 96 63 E7 96
* CRTC[0x16]: VBEND B9 B9 B9 B9 B9 B9 B9 B9 B9 BA 04 B9
* CRTC[0x17]: MODECTRL A3 A3 A3 A3 A2 A2 C2 E3 E3 E3 E3 A3
* CRTC[0x18]: LINECOMP FF FF FF FF FF FF FF FF FF FF FF FF
* GRC[0x00]: SRESET 00 00 00 00 00 00 00 00 00 00 00 00
* GRC[0x01]: ESRESET 00 00 00 00 00 00 00 00 00 00 00 00
* GRC[0x02]: COLORCOMP 00 00 00 00 00 00 00 00 00 00 00 00
* GRC[0x03]: DATAROT 00 00 00 00 00 00 00 00 00 00 00 00
* GRC[0x04]: READMAP 00 00 00 00 00 00 00 00 00 00 00 00
* GRC[0x05]: MODE 10 10 10 10 30 30 00 00 00 00 00 40
* GRC[0x06]: MISC 0E 0E 0E 0E 0F 0F 0D 05 05 05 05 05
* GRC[0x07]: COLORDC 00 00 00 00 00 00 00 0F 0F 0F 0F 0F
* GRC[0x08]: BITMASK FF FF FF FF FF FF FF FF FF FF FF FF
* SEQ[0x00]: RESET 03 03 03 03 03 03 03 03 03 03 03 03
* SEQ[0x01]: CLKMODE 08 08 00 00 09 09 01 09 01 01 01 01
* SEQ[0x02]: MAPMASK 03 03 03 03 03 03 01 0F 0F 0F 0F 0F
* SEQ[0x03]: CHARMAP 00 00 00 00 00 00 00 00 00 00 00 00
* SEQ[0x04]: MEMMODE 03 03 03 03 02 02 06 06 06 06 06 0E
* ATC[0x00]: PAL00 00 00 00 00 00 00 00 00 00 00 00 00
* ATC[0x01]: PAL01 01 01 01 01 13 13 17 01 01 01 01 01
* ATC[0x02]: PAL02 02 02 02 02 15 15 17 02 02 02 02 02
* ATC[0x03]: PAL03 03 03 03 03 17 17 17 03 03 03 03 03
* ATC[0x04]: PAL04 04 04 04 04 02 02 17 04 04 04 04 04
* ATC[0x05]: PAL05 05 05 05 05 04 04 17 05 05 05 05 05
* ATC[0x06]: PAL06 14 14 14 14 06 06 17 06 06 14 14 06
* ATC[0x07]: PAL07 07 07 07 07 07 07 17 07 07 07 07 07
* ATC[0x08]: PAL08 38 38 38 38 10 10 17 10 10 38 38 08
* ATC[0x09]: PAL09 39 39 39 39 11 11 17 11 11 39 39 09
* ATC[0x0A]: PAL0A 3A 3A 3A 3A 12 12 17 12 12 3A 3A 0A
* ATC[0x0B]: PAL0B 3B 3B 3B 3B 13 13 17 13 13 3B 3B 0B
* ATC[0x0C]: PAL0C 3C 3C 3C 3C 14 14 17 14 14 3C 3C 0C
* ATC[0x0D]: PAL0D 3D 3D 3D 3D 15 15 17 15 15 3D 3D 0D
* ATC[0x0E]: PAL0E 3E 3E 3E 3E 16 16 17 16 16 3E 3E 0E
* ATC[0x0F]: PAL0F 3F 3F 3F 3F 17 17 17 17 17 3F 3F 0F
* ATC[0x10]: MODE 0C 0C 0C 0C 01 01 01 01 01 01 01 41
* ATC[0x11]: OVERSCAN 00 00 00 00 00 00 00 00 00 00 00 00
* ATC[0x12]: PLANES 0F 0F 0F 0F 03 03 01 0F 0F 0F 0F 0F
* ATC[0x13]: HPAN 08 08 08 08 00 00 00 00 00 00 00 00
*/
/**
* @class Card
* @property {DebuggerX86} dbg
* @unrestricted (allows the class to define properties, both dot and named, outside of the constructor)
*/
class Card extends Controller {
/**
* Card(video, nCard, data, cbMemory)
*
* Creates an object representing an initial video card state;
* can also restore a video card from state data created by saveCard().
*
* WARNING: Since Card objects are low-level objects that have no UI requirements,
* they do not inherit from the Component class, so you should only use class methods
* of Component, such as Component.assert(), or methods of the parent (video) object.
*
* @this {Card}
* @param {Video} [video]
* @param {number} [nCard] (see Video.CARD.*)
* @param {Array|null} [data]
* @param {number} [cbMemory] is specified if the card must allocate its own memory buffer
*/
constructor(video, nCard, data, cbMemory)
{
super();
/*
* If a card was originally not present (eg, EGA), then the state will be empty,
* so we need to detect that case and continue indicating that the card is not present.
*/
if (nCard !== undefined && (!data || data.length)) {
this.video = video;
let specs = Video.cardSpecs[nCard];
let nMonitorType = video.nMonitorType || specs[5];
if (!data || data.length < 6) {
data = [false, 0, null, null, 0, new Array(nCard < Video.CARD.EGA? Card.CRTC.TOTAL_REGS : Card.CRTC.EGA.TOTAL_REGS)];
}
/*
* If a Debugger is present, we want to stash a bit more info in each Card.
*/
if (DEBUGGER) {
this.dbg = video.dbg;
this.type = specs[0];
this.port = specs[1];
}
this.nCard = nCard;
this.addrBuffer = specs[2]; // default (physical) video buffer address
this.sizeBuffer = specs[3]; // default video buffer length (this is the total size, not the current visible size;
// this.cbScreen is calculated on the fly to reflect the latter)
/*
* If no memory size is specified, then setMode() will use addMemory() to automatically add enough
* memory blocks to cover the video buffer specified above; otherwise, it instructs addMemory() to call
* getMemoryBuffer(), which will return a portion of the buffer (adwMemory) allocated below. This allows
* a card like the EGA to move/resize its video buffer as needed, as well as giving it total control over
* the underlying memory.
*/
this.cbMemory = cbMemory || specs[4];
/*
* All of our cardSpec video buffer sizes are based on the default text mode (eg, 4Kb for an MDA, 16Kb for
* a CGA), but for a card with 64Kb or more of memory (ie, any EGA card), the default text mode video buffer
* size should be dynamically recalculated as the smaller of: cbMemory divided by 4, or 32Kb.
*/
if (this.cbMemory >= 0x10000 && this.addrBuffer >= 0xB0000) {
this.sizeBuffer = Math.min(this.cbMemory >> 2, 0x8000);
}
this.fActive = data[0];
this.regMode = data[1]; // see MDA.MODE* or CGA.MODE_*
// use MDA.MODE.HIRES | MDA.MODE.VIDEO_ENABLE | MDA.MODE.BLINK_ENABLE
// if you want to test blinking immediately after the initial power-on reset)
this.regColor = data[2]; // see CGA.COLOR.* (undefined on MDA)
this.regStatus = data[3]; // see MDA.STATUS.* or CGA.STATUS.*
this.regCRTIndx = data[4] & 0xff;
this.regCRTPrev = (data[4] >> 8) & 0xff;
this.regCRTData = data[5];
this.nCRTCRegs = Card.CRTC.TOTAL_REGS;
this.asCRTCRegs = DEBUGGER? Card.CRTC.REGS : [];
this.offStart = this.regCRTData[Card.CRTC.STARTLO] | (this.regCRTData[Card.CRTC.STARTHI] << 8);
this.rowStart = 0; // initialize to zero and let the first latchStartAddress() call update it
this.addrMaskHigh = 0x3F; // card-specific mask for the high (bits 8 and up) of CRTC address registers
if (nCard < Video.CARD.EGA) {
this.initMemory(data[6], data[8]);
this.setMemoryAccess(Card.ACCESS.READ.PAIRS | Card.ACCESS.WRITE.PAIRS);
} else {
this.addrMaskHigh = 0xFF;
this.nCRTCRegs = Card.CRTC.EGA.TOTAL_REGS;
this.asCRTCRegs = DEBUGGER? Card.CRTC.EGA_REGS : [];
this.initEGA(data[6], nMonitorType);
}
let monitorSpecs = Video.monitorSpecs[nMonitorType] || Video.monitorSpecs[ChipSet.MONITOR.MONO];
/*
* nCyclesVertPeriod determines how frequently startVerticalRetrace() is called. That function
* snaps the current cycle count in nCyclesVertRetrace. Then whenever getRetraceBits() is called,
* it subtracts nCyclesVertRetrace from the current cycle count, and whenever the delta exceeds
* nCyclesVertPeriod - nCyclesVertActive, vertical retrace has ended. Similarly, horizontal retrace
* ends whenever that delta MOD nCyclesHorzPeriod exceeds nCyclesHorzActive.
*/
let nCyclesDefault = video.cpu.getBaseCyclesPerSecond();
this.nCyclesHorzPeriod = (nCyclesDefault / monitorSpecs.nHorzPeriodsPerSec)|0;
this.nCyclesHorzActive = (this.nCyclesHorzPeriod * monitorSpecs.percentHorzActive / 100)|0;
this.nCyclesVertActive = (this.nCyclesHorzPeriod * monitorSpecs.nHorzPeriodsPerFrame)|0;
this.nCyclesVertPeriod = (this.nCyclesVertActive / (monitorSpecs.percentVertActive / 100))|0;
this.nCyclesVertRetrace = (data[7] || 0);
this.nCountVertRetrace = 0;
}
}
/**
* initEGA(data)
*
* Another one of my frustrations with JSON is that it encodes empty arrays with non-zero lengths as
* arrays of nulls, which means that any uninitialized register arrays whose elements were all originally
* undefined come back via the JSON round-trip as *initialized* arrays whose elements are now all null.
*
* I'm a bit surprised, because JavaScript purists tell us to always use the '===' operator (eg, use
* 'aReg[i] === undefined' to determine if an element is initialized), but because of this JSON stupidity,
* that would require all such tests to become 'aReg[i] === undefined || aReg[i] === null'. I'm puzzled
* why the coercion of '==' is considered evil but JSON's coercion of undefined to null is perfectly fine.
*
* The simple solution is to change such comparisons to 'aReg[i] == null', because undefined is coerced
* to null, whereas numeric values are not.
*
* [What do I mean by "another" frustration? Let me talk to you some day about disallowing hex constants,
* or insisting that property names be quoted, or refusing to allow comments. I think it's fine for
* JSON.stringify() to produce output that adheres to rules like that -- although some parameters to control
* the output would be nice -- but it's completely unnecessary for JSON.parse() to refuse to parse objects
* that are perfectly valid.]
*
* @this {Card}
* @param {Array|undefined} data
* @param {number} nMonitorType
*/
initEGA(data, nMonitorType)
{
if (data === undefined) {
data = [
/* 0*/ false,
/* 1*/ 0,
/* 2*/ new Array(Card.ATC.TOTAL_REGS),
/* 3*/ 0,
/* 4*/ (nMonitorType == ChipSet.MONITOR.MONO? 0: Card.MISC.IO_SELECT),
/* 5*/ 0,
/* 6*/ 0,
/* 7*/ new Array(Card.SEQ.TOTAL_REGS),
/* 8*/ 0,
/* 9*/ 0,
/*10*/ 0,
/*11*/ new Array(Card.GRC.TOTAL_REGS),
/*12*/ 0,
/*13*/ [this.addrBuffer, this.sizeBuffer, this.cbMemory],
/*14*/ null,
/*
* Card.ACCESS.WRITE.MODE0 by itself is a pretty good default, but if we choose to "randomize" the screen with
* text characters prior to starting the machine, defaulting to Card.ACCESS.WRITE.EVENODD is more faithful to how
* characters and attributes are typically stored (ie, in planes 0 and 1, respectively).
*
* Card.ACCESS.READ.MODE0 | Card.ACCESS.READ.EVENODD | Card.ACCESS.WRITE.MODE0 | Card.ACCESS.WRITE.EVENODD | Card.ACCESS.V2
*
* Unfortunately, a typical ROM BIOS will almost immediately write to one of the original MDA or CGA mode registers,
* changing the default mode, so we may as well initialize the card to byte-pair access.
*/
/*15*/ Card.ACCESS.READ.PAIRS | Card.ACCESS.WRITE.PAIRS | Card.ACCESS.V2,
/*16*/ 0,
/*17*/ 0xffffffff|0,
/*18*/ 0,
/*19*/ 0xffffffff|0,
/*20*/ 0,
/*21*/ 0xffffffff|0,
/*22*/ 0,
/*23*/ 0,
/*24*/ 0,
/*25*/ 0,
/*26*/ Card.VGA_ENABLE.ENABLED,
/*27*/ Card.DAC.MASK.DEFAULT,
/*28*/ 0,
/*29*/ 0,
/*30*/ Card.DAC.STATE.MODE_WRITE,
/*31*/ new Array(Card.DAC.TOTAL_REGS)
];
}
this.fATCData = data[0];
this.regATCIndx = data[1];
this.regATCData = data[2];
this.asATCRegs = DEBUGGER? Card.ATC.REGS : [];
this.regStatus0 = data[3]; // aka STATUS0 (not to be confused with this.regStatus, which the EGA refers to as STATUS1)
this.regMisc = data[4];
this.regFeat = data[5]; // for feature control bits, see Card.FEAT_CTRL.BITS; for feature status bits, see Card.STATUS0.FEAT
this.regSEQIndx = data[6];
this.regSEQData = data[7];
this.asSEQRegs = DEBUGGER? Card.SEQ.REGS : [];
this.regGRCPos1 = data[8];
this.regGRCPos2 = data[9];
this.regGRCIndx = data[10];
this.regGRCData = data[11];
this.asGRCRegs = DEBUGGER? Card.GRC.REGS : [];
this.latches = data[12];
/*
* Since we originally neglected to save/restore the card's active video buffer address and length,
* we're now stashing all that information in data[13]. So if we're presented with an old data entry
* that contains only the card's memory size, fix it up.
*
* TODO: This code just creates the required array; the correct video buffer address and length would
* still need to be calculated from the current GRC registers; checkMode() knows how to do that, but I'm
* not prepared to shoehorn in a call to checkMode() here, and potentially create more issues, for an
* old problem that will eventually disappear anyway.
*/
let a = data[13];
if (typeof a == "number") {
a = [this.addrBuffer, this.sizeBuffer, a];
}
this.addrBuffer = a[0];
this.sizeBuffer = a[1];
this.video.assert(this.cbMemory === a[2]);
this.initMemory(data[14], this.cbMemory >> 2);
let nAccess = data[15];
if (nAccess) {
if (nAccess & Card.ACCESS.V2) {
nAccess &= ~Card.ACCESS.V2;
} else {
this.video.assert(Card.ACCESS.V1[nAccess & 0xff00] !== undefined && Card.ACCESS.V1[nAccess & 0xff] !== undefined);
nAccess = Card.ACCESS.V1[nAccess & 0xff00] | Card.ACCESS.V1[nAccess & 0xff];
}
}
this.setMemoryAccess(nAccess);
/*
* nReadMapShift must perfectly track how the GRC.READMAP register is programmed, so that Card.ACCESS.READ.MODE0
* memory read functions read the appropriate plane. This default is not terribly critical, unless Card.ACCESS.WRITE.MODE0
* is chosen as our default AND you want the screen randomizer to work.
*/
this.nReadMapShift = data[16];
/*
* Similarly, nSeqMapMask must perfectly track how the SEQ.MAPMASK register is programmed, so that memory write
* functions write the appropriate plane(s). Again, this default is not terribly critical, unless Card.ACCESS.WRITE.MODE0
* is chosen as our default AND you want the screen randomizer to work.
*/
this.nSeqMapMask = data[17];
this.nDataRotate = data[18];
this.nBitMapMask = data[19];
this.nSetMapData = data[20];
this.nSetMapMask = data[21];
this.nSetMapBits = data[22];
this.nColorCompare = data[23];
this.nColorDontCare = data[24];
this.offStart = data[25]; // this is the last CRTC start address latched from CRTC.STARTHI,CRTC.STARTLO
if (this.nCard == Video.CARD.VGA) {
this.regVGAEnable = data[26];
this.regDACMask = data[27];
this.regDACAddr = data[28];
this.regDACShift = data[29];
this.regDACState = data[30];
this.regDACData = data[31];
}
/*
* While every Video memory block maintains its own DIRTY flag, used by the Bus cleanMemory() function to
* quickly determine if anything changed within a given block, we supplement that information at the Card level
* in certain memory controller functions that we know are used to modify font data in plane 2.
*
* Whenever plane 2 is modified, one of bits 0-7 in bitsDirtyBanks is modified as well, indicating which of
* the corresponding font "banks" was modified. The EGA supports only four font banks (0, 2, 4, and 6), while
* the VGA supports four additional "interleaved" banks (1, 3, 5, and 7).
*
* NOTE: Our bank numbers (0-7) should not be confused with EGA INT 10h (AH=11h) "Character Generator Routine"
* block numbers 0 to 3, which must be multiplied by 2 to obtain the corresponding bank number; VGA block numbers
* 4 to 7 must also be multiplied by 2 and then reduced by 7 to produce the correct interleaved bank number.
*/
this.bitsDirtyBanks = 0;
}
/**
* initMemory(data, length)
*
* If we're restoring an older MDA or CGA buffer, where 4 bytes were stored in every dword instead of 2, then the
* final size of adwMemory will be cbMemory >> 2. Newer MDA and CGA buffers store only 2 bytes in every dword, to
* make them compatible with their EGA and VGA counterparts, so their final size should be cbMemory >> 1.
*
* When we detect the older format, we must convert it to the newer.
*
* @this {Card}
* @param {Array|null} data
* @param {number} [length]
*/
initMemory(data, length)
{
let cdw = this.cbMemory >> 2;
this.adwMemory = data;
if (!this.adwMemory || !this.adwMemory.length) {
if (!length) length = cdw * 2;
this.adwMemory = new Array(length);
}
else {
cdw = length || cdw;
if (this.adwMemory.length < cdw) {
this.adwMemory = State.decompressEvenOdd(this.adwMemory, cdw);
}
}
if (!length) {
let adwOld = this.adwMemory;
let adwNew = new Array(this.adwMemory.length * 2);
this.video.assert(this.nCard == Video.CARD.MDA && adwOld.length == 1024 || this.nCard == Video.CARD.CGA && adwOld.length == 4096);
for (let i = 0, j = 0; i < this.adwMemory.length; i++, j += 2) {
adwNew[j] = adwOld[i] & 0xffff;
adwNew[j+1] = (adwOld[i] >> 16) & 0xffff;
}
this.adwMemory = adwNew;
} else {
this.video.assert(length == this.adwMemory.length);
}
}
/**
* saveCard()
*
* @this {Card}
* @return {Array}
*/
saveCard()
{
let data = [];
if (this.nCard !== undefined) {
data[0] = this.fActive;
data[1] = this.regMode;
data[2] = this.regColor;
data[3] = this.regStatus;
data[4] = this.regCRTIndx | (this.regCRTPrev << 8);
data[5] = this.regCRTData;
data[6] = (this.nCard < Video.CARD.EGA? State.compressEvenOdd(this.adwMemory) : this.saveEGA());
data[7] = this.nCyclesVertRetrace;
data[8] = this.adwMemory.length;
}
return data;
}
/**
* saveEGA()
*
* @this {Card}
* @return {Array}
*/
saveEGA()
{
let data = [];
data[0] = this.fATCData;
data[1] = this.regATCIndx;
data[2] = this.regATCData;
data[3] = this.regStatus0;
data[4] = this.regMisc;
data[5] = this.regFeat;
data[6] = this.regSEQIndx;
data[7] = this.regSEQData;
data[8] = this.regGRCPos1;
data[9] = this.regGRCPos2;
data[10] = this.regGRCIndx;
data[11] = this.regGRCData;
data[12] = this.latches;
data[13] = [this.addrBuffer, this.sizeBuffer, this.cbMemory];
data[14] = State.compressEvenOdd(this.adwMemory);
data[15] = this.nAccess | Card.ACCESS.V2;
data[16] = this.nReadMapShift;
data[17] = this.nSeqMapMask;
data[18] = this.nDataRotate;
data[19] = this.nBitMapMask;
data[20] = this.nSetMapData;
data[21] = this.nSetMapMask;
data[22] = this.nSetMapBits;
data[23] = this.nColorCompare;
data[24] = this.nColorDontCare;
data[25] = this.offStart;
if (this.nCard == Video.CARD.VGA) {
data[26] = this.regVGAEnable;
data[27] = this.regDACMask;
data[28] = this.regDACAddr;
data[29] = this.regDACShift;
data[30] = this.regDACState;
data[31] = this.regDACData;
}
return data;
}
/**
* dumpRegs()
*
* Since we don't pre-allocate the register arrays (eg, ATC, CRTC, GRC, etc) on a Card, we can't
* rely on their array length, so we instead rely on the number of register names supplied in asRegs.
*
* @this {Card}
* @param {string} sName
* @param {number} iReg
* @param {Array} [aRegs]
* @param {Array} [asRegs]
*/
dumpRegs(sName, iReg, aRegs, asRegs)
{
if (DEBUGGER) {
if (!aRegs) {
this.dbg.println(sName + ": " + Str.toHex(iReg, 2));
return;
}
let i, s = "";
let nRegs = (asRegs? asRegs.length : aRegs.length);
for (i = 0; i < nRegs; i++) {
/*
* In the case of the CRTC, we call the helper function getCRTCReg() to automatically concatenate
* the extended bits of certain registers, so that we don't have to "mentally" concatenate them.
*/
let reg = (aRegs === this.regCRTData)? this.getCRTCReg(i) : aRegs[i];
if (s) s += '\n';
let sRegName = (asRegs? asRegs[i] : sName.substr(1) + Str.toDec(i, 3));
s += Str.sprintf("%s[%02X]: %-12s %*X%s (%*d)", sName, i, sRegName, (asRegs? 4 : 6), reg, (i === iReg? '*' : ' '), (asRegs? 4 : 6), reg);
}
this.dbg.println(s);
}
}
/**
* dumpVideoCard()
*
* @this {Card}
*/
dumpVideoCard()
{
if (DEBUGGER) {
/*
* Start with registers that are common to all cards....
*/
this.dumpRegs("CRTC", this.regCRTIndx, this.regCRTData, this.asCRTCRegs);
if (this.nCard >= Video.CARD.EGA) {
this.dumpRegs(" GRC", this.regGRCIndx, this.regGRCData, this.asGRCRegs);
this.dumpRegs(" SEQ", this.regSEQIndx, this.regSEQData, this.asSEQRegs);
this.dumpRegs(" ATC", this.regATCIndx, this.regATCData, this.asATCRegs);
this.dumpRegs(" ATCINDX", this.regATCIndx);
this.dbg.println(" ATCDATA: " + this.fATCData);
this.dumpRegs(" FEAT", this.regFeat);
this.dumpRegs(" MISC", this.regMisc);
this.dumpRegs(" STATUS0", this.regStatus0);
/*
* There are few more EGA regs we could dump, like GRCPos1, GRCPos2, but does anyone care?
*/
if (this.nCard == Video.CARD.VGA) {
this.dumpRegs(" DAC", this.regDACAddr, this.regDACData);
}
}
/*
* TODO: This simply dumps the last value read from the STATUS1 register, not necessarily
* its current state; consider dumping getRetraceBits() instead of (or in addition to) this.
*/
this.dumpRegs(" STATUS1", this.regStatus);
if (this.nCard == Video.CARD.MDA || this.nCard == Video.CARD.CGA) {
this.dumpRegs(" MODEREG", this.regMode);
}
if (this.nCard == Video.CARD.CGA) {
this.dumpRegs(" COLOR", this.regColor);
}
if (this.nCard >= Video.CARD.EGA) {
this.dbg.println(" LATCHES: " + Str.toHex(this.latches));
this.dbg.println(" ACCESS: " + Str.toHex(this.nAccess, 4));
this.dbg.println(" PLANE2: " + Str.toHex(this.bitsDirtyBanks, 2));
this.dbg.println("Use 'd video [addr]' to dump video memory");
/*
* There are few more EGA regs we could dump, like GRCPos1, GRCPos2, but does anyone care?
*/
}
}
}
/**
* dumpVideoBuffer(asArgs)
*
* Rather than requiring the first parameter to ALWAYS be a frame buffer address OR a frame buffer
* offset, we'll just make a guess as to what the user intended and support BOTH; basically, if the
* value is less than the frame buffer address, we'll assume it's an offset.
*
* Also, we allow some special options to be encoded in asArgs: 'l' followed by a number means
* print that many rows of data. 'n' followed by a number (1-8) means print only that number of
* memory locations per row, and then adjust the starting address of the next row by the number
* of bytes per row (or whatever is specified by the 'w' option) so that the dump reflects a
* rectangular chunk of video data. Finally, if asArgs contains 'p' followed by a number (0-3),
* we display only the bits from that plane for each memory location, in binary instead of hex.
*
* For example, assuming a standard VGA frame buffer with 640x480 pixels across 38400 (0x9600) memory
* locations, the following command will dump a vertical swath of bits from plane 0 that is 32 (0x20)
* rows tall and 8 columns wide, from roughly the center of the screen (0x4B00 + 0x28 - 2 = 0x4B26).
*
* d video 4b26 l20 n8 p0
*
* Subsequent commands that omit a starting address or offset will continue where the last dump
* left off; eg:
*
* d video n8 p0
*
* To dump a chunk of off-screen memory starting at 0x9600, where the Windows VGA driver typically
* stores a copy of the video memory containing the current mouse pointer:
*
* d video 9600 l20 n5 w5 p0
*
* Alternatively, you could use decimal values:
*
* d video 9600 l32. n5. w5. p0.
*
* NOTE: If these commands look suspiciously like weird Hayes modem command strings, trust me,
* that is ENTIRELY coincidental (but mildly amusing).
*
* TODO: Make these options more general-purpose (it currently assumes a conventional VGA planar layout).
*
* @this {Card}
* @param {Array.<string>} asArgs (all numeric arguments default to base 16 unless otherwise specified)
*/
dumpVideoBuffer(asArgs)
{
if (DEBUGGER) {
if (!this.adwMemory) {
this.dbg.println("no buffer");
return;
}
let i, j, idw, fColAdjust = false;
let l = 8, n = 8, p = -1, w = this.video.nCols >> 3;
for (i = 0; i < asArgs.length; i++) {
let s = asArgs[i];
if (!i) {
idw = Str.parseInt(s, 16);
continue;
}
let ch = s.charAt(0);
j = Str.parseInt(s.substr(1), 16);
switch(ch) {
case 'l':
l = j;
break;
case 'n':
if (j >= 1 && j <= 8) {
n = j;
fColAdjust = true;
}
break;
case 'p':
if (j >= 0 && j <= 3) p = j;
break;
case 'w':
if (j < w) w = j;
break;
default:
this.dbg.println("unrecognized argument: " + s);
break;
}
}
if (idw === undefined) {
idw = this.prevDump || 0;
} else if (idw >= this.addrBuffer) {
idw -= this.addrBuffer;
}
let sDump = "";
for (i = 0; i < l; i++) {
let sData = Str.toHex(this.addrBuffer + idw) + ":";
for (j = 0; j < n && idw < this.adwMemory.length; j++) {
let dw = this.adwMemory[idw++];
sData += ' ' + ((p < 0)? Str.toHex(dw, 8) : Str.toBin((dw >> (p << 3)), 8));
}
if (fColAdjust) idw += w - n;
if (sDump) sDump += "\n";
sDump += sData;
}
if (sDump) this.dbg.println(sDump);
this.prevDump = idw;
}
}
/**
* getMemoryBuffer(addr)
*
* If we passed a controller object (ie, this card) to addMemory(), then each allocated Memory block
* will call this function to obtain a buffer.
*
* @this {Card}
* @param {number} addr
* @return {Array} containing the buffer (and the offset within that buffer that corresponds to the requested block)
*/
getMemoryBuffer(addr)
{
return [this.adwMemory, addr - this.addrBuffer];
}
/**
* getMemoryAccess()
*
* WARNING: This is a public method, whereas most Card methods are private to the Video component;
* because a Card also acts as a Memory controller, it must provide getMemoryAccess() to the Memory component.
*
* Return the last set of memory access functions recorded by setMemoryAccess().
*
* @this {Card}
* @return {Array.<function()>}
*/
getMemoryAccess()
{
return this.afnAccess;
}
/**
* setMemoryAccess(nAccess)
*
* This transforms the memory access value that getCardAccess() returns into the best available set of
* memory access functions, which are then returned via getMemoryAccess() to any memory blocks we allocate
* or modify.
*
* @this {Card}
* @param {number|undefined} nAccess
*/
setMemoryAccess(nAccess)
{
if (nAccess != null && nAccess != this.nAccess) {
let nReadAccess = nAccess & Card.ACCESS.READ.MASK;
let fnReadByte = Card.ACCESS.afn[nReadAccess];