SandboXP

Форк
0
/
vga.js 
2330 строк · 71.1 Кб
1
"use strict";
2

3

4
var
5
    /**
6
     * Always 64k
7
     * @const
8
     */
9
    VGA_BANK_SIZE = 64 * 1024,
10

11
    /** @const */
12
    MAX_XRES = 2560,
13

14
    /** @const */
15
    MAX_YRES = 1600,
16

17
    /** @const */
18
    MAX_BPP = 32;
19

20
/** @const */
21
//var VGA_LFB_ADDRESS = 0xFE000000; // set by seabios
22
var VGA_LFB_ADDRESS = 0xE0000000;
23

24
/**
25
 * @const
26
 * Equals the maximum number of pixels for non svga.
27
 * 8 pixels per byte.
28
 */
29
var VGA_PIXEL_BUFFER_SIZE = 8 * VGA_BANK_SIZE;
30

31
/** @const */
32
var VGA_MIN_MEMORY_SIZE = 4 * VGA_BANK_SIZE;
33

34
/**
35
 * @const
36
 * @see {@link http://www.osdever.net/FreeVGA/vga/graphreg.htm#06}
37
 */
38
var VGA_HOST_MEMORY_SPACE_START = Uint32Array.from([
39
    0xA0000,
40
    0xA0000,
41
    0xB0000,
42
    0xB8000,
43
]);
44

45
/**
46
 * @const
47
 * @see {@link http://www.osdever.net/FreeVGA/vga/graphreg.htm#06}
48
 */
49
var VGA_HOST_MEMORY_SPACE_SIZE = Uint32Array.from([
50
    0x20000, // 128K
51
    0x10000, // 64K
52
    0x8000, // 32K
53
    0x8000, // 32K
54
]);
55

56
/**
57
 * @constructor
58
 * @param {CPU} cpu
59
 * @param {BusConnector} bus
60
 * @param {number} vga_memory_size
61
 */
62
function VGAScreen(cpu, bus, vga_memory_size)
63
{
64
    this.cpu = cpu;
65

66
    /** @const @type {BusConnector} */
67
    this.bus = bus;
68

69
    this.vga_memory_size = vga_memory_size;
70

71
    /** @type {number} */
72
    this.cursor_address = 0;
73

74
    /** @type {number} */
75
    this.cursor_scanline_start = 0xE;
76

77
    /** @type {number} */
78
    this.cursor_scanline_end = 0xF;
79

80
    /**
81
     * Number of columns in text mode
82
     * @type {number}
83
     */
84
    this.max_cols = 80;
85

86
    /**
87
     * Number of rows in text mode
88
     * @type {number}
89
     */
90
    this.max_rows = 25;
91

92
    /**
93
     * Width in pixels in graphical mode
94
     * @type {number}
95
     */
96
    this.screen_width = 0;
97

98
    /**
99
     * Height in pixels in graphical mode
100
     * @type {number}
101
     */
102
    this.screen_height = 0;
103

104
    /**
105
     * Logical width in pixels of virtual buffer available for panning
106
     * @type {number}
107
     */
108
    this.virtual_width = 0;
109

110
    /**
111
     * Logical height in pixels of virtual buffer available for panning
112
     * @type {number}
113
     */
114
    this.virtual_height = 0;
115

116
    /**
117
     * The rectangular fragments of the image buffer, and their destination
118
     * locations, to be drawn every screen_fill_buffer during VGA modes.
119
     * @type {Array<Object<string, number>>}
120
     */
121
    this.layers = [];
122

123
    /**
124
     * video memory start address
125
     * @type {number}
126
     */
127
    this.start_address = 0;
128

129
    /**
130
     * Start address - a copy of start_address that only gets updated
131
     * during VSync, used for panning and page flipping
132
     * @type {number}
133
     */
134
    this.start_address_latched = 0;
135

136
    /**
137
     * Unimplemented CRTC registers go here
138
     */
139
    this.crtc = new Uint8Array(0x19);
140

141
    // Implemented CRTC registers:
142

143
    /** @type {number} */
144
    this.crtc_mode = 0;
145

146
    /** @type {number} */
147
    this.horizontal_display_enable_end = 0;
148

149
    /** @type {number} */
150
    this.horizontal_blank_start = 0;
151

152
    /** @type {number} */
153
    this.vertical_display_enable_end = 0;
154

155
    /** @type {number} */
156
    this.vertical_blank_start = 0;
157

158
    /** @type {number} */
159
    this.underline_location_register = 0;
160

161
    /** @type {number} */
162
    this.preset_row_scan = 0;
163

164
    /** @type {number} */
165
    this.offset_register = 0;
166

167
    /** @type {number} */
168
    this.line_compare = 0;
169

170
    // End of CRTC registers
171

172
    /**
173
     * Used for svga, e.g. banked modes
174
     * @type{boolean}
175
     */
176
    this.graphical_mode_is_linear = true;
177

178
    /** @type {boolean} */
179
    this.graphical_mode = false;
180
    setTimeout(() => { bus.send("screen-set-mode", this.graphical_mode); }, 0);
181

182
    /*
183
     * VGA palette containing 256 colors for video mode 13, svga 8bpp, etc.
184
     * Needs to be initialised by the BIOS
185
     */
186
    this.vga256_palette = new Int32Array(256);
187

188
    /**
189
     * VGA read latches
190
     * @type{number}
191
     */
192
    this.latch_dword = 0;
193

194
    /** @type {number} */
195
    this.svga_width = 0;
196

197
    /** @type {number} */
198
    this.svga_height = 0;
199

200
    this.svga_enabled = false;
201

202
    /** @type {number} */
203
    this.svga_bpp = 32;
204

205
    /** @type {number} */
206
    this.svga_bank_offset = 0;
207

208
    /**
209
     * The video buffer offset created by VBE_DISPI_INDEX_Y_OFFSET
210
     * In bytes
211
     * @type {number}
212
     */
213
    this.svga_offset = 0;
214

215
    // Experimental, could probably need some changes
216
    // 01:00.0 VGA compatible controller: NVIDIA Corporation GT216 [GeForce GT 220] (rev a2)
217
    this.pci_space = [
218
        0x34, 0x12, 0x11, 0x11, 0x03, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00,
219
        0x08, VGA_LFB_ADDRESS >>> 8, VGA_LFB_ADDRESS >>> 16, VGA_LFB_ADDRESS >>> 24,
220
                                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf, 0xfe, 0x00, 0x00, 0x00, 0x00,
221
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf4, 0x1a, 0x00, 0x11,
222
        0x00, 0x00, 0xbe, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
223
    ];
224
    this.pci_id = 0x12 << 3;
225
    this.pci_bars = [
226
        {
227
            size: vga_memory_size,
228
        },
229
    ];
230

231
    // TODO: Should be matched with vga bios size and mapping address
232
    // Seabios config for this device:
233
    // CONFIG_VGA_PCI=y
234
    // CONFIG_OVERRIDE_PCI_ID=y
235
    // CONFIG_VGA_VID=0x10de
236
    // CONFIG_VGA_DID=0x0a20
237

238
    this.pci_rom_size = 0x10000;
239
    this.pci_rom_address = 0xFEB00000;
240

241
    this.name = "vga";
242

243
    this.stats = {
244
        is_graphical: false,
245
        res_x: 0,
246
        res_y: 0,
247
        bpp: 0,
248
    };
249

250
    this.index_crtc = 0;
251

252
    // index for setting colors through port 3C9h
253
    this.dac_color_index_write = 0;
254
    this.dac_color_index_read = 0;
255
    this.dac_state = 0;
256

257
    this.dac_map = new Uint8Array(0x10);
258

259
    this.attribute_controller_index = -1;
260
    this.palette_source = 0x20;
261
    this.attribute_mode = 0;
262
    this.color_plane_enable = 0;
263
    this.horizontal_panning = 0;
264
    this.color_select = 0;
265

266
    this.sequencer_index = -1;
267

268
    // bitmap of planes 0-3
269
    this.plane_write_bm = 0xF;
270
    this.sequencer_memory_mode = 0;
271
    this.clocking_mode = 0;
272
    this.graphics_index = -1;
273

274
    this.plane_read = 0, // value 0-3, which plane to read
275
    this.planar_mode = 0;
276
    this.planar_rotate_reg = 0;
277
    this.planar_bitmap = 0xFF;
278
    this.planar_setreset = 0;
279
    this.planar_setreset_enable = 0;
280
    this.miscellaneous_graphics_register = 0;
281

282
    this.color_compare = 0;
283
    this.color_dont_care = 0;
284

285
    this.max_scan_line = 0;
286

287
    this.miscellaneous_output_register = 0xff;
288
    this.port_3DA_value = 0xFF;
289

290

291
    var io = cpu.io;
292

293
    io.register_write(0x3C0, this, this.port3C0_write);
294
    io.register_read(0x3C0, this, this.port3C0_read, this.port3C0_read16);
295

296
    io.register_read(0x3C1, this, this.port3C1_read);
297
    io.register_write(0x3C2, this, this.port3C2_write);
298

299
    io.register_write_consecutive(0x3C4, this, this.port3C4_write, this.port3C5_write);
300

301
    io.register_read(0x3C4, this, this.port3C4_read);
302
    io.register_read(0x3C5, this, this.port3C5_read);
303

304
    io.register_write_consecutive(0x3CE, this, this.port3CE_write, this.port3CF_write);
305

306
    io.register_read(0x3CE, this, this.port3CE_read);
307
    io.register_read(0x3CF, this, this.port3CF_read);
308

309
    io.register_write(0x3C7, this, this.port3C7_write);
310
    io.register_read(0x3C7, this, this.port3C7_read);
311
    io.register_write(0x3C8, this, this.port3C8_write);
312
    io.register_read(0x3C8, this, this.port3C8_read);
313
    io.register_write(0x3C9, this, this.port3C9_write);
314
    io.register_read(0x3C9, this, this.port3C9_read);
315

316
    io.register_read(0x3CC, this, this.port3CC_read);
317

318
    io.register_write_consecutive(0x3D4, this, this.port3D4_write, this.port3D5_write);
319
    io.register_read(0x3D4, this, this.port3D4_read);
320
    io.register_read(0x3D5, this, this.port3D5_read, () => {
321
        dbg_log("Warning: 16-bit read from 3D5", LOG_VGA);
322
        return this.port3D5_read();
323
    });
324

325
    io.register_read(0x3CA, this, function() { dbg_log("3CA read", LOG_VGA); return 0; });
326

327
    io.register_read(0x3DA, this, this.port3DA_read);
328
    io.register_read(0x3BA, this, this.port3DA_read);
329

330

331
    // Bochs VBE Extensions
332
    // http://wiki.osdev.org/Bochs_VBE_Extensions
333
    this.dispi_index = -1;
334
    this.dispi_enable_value = 0;
335

336
    io.register_write(0x1CE, this, undefined, this.port1CE_write);
337

338
    io.register_write(0x1CF, this, undefined, this.port1CF_write);
339
    io.register_read(0x1CF, this, undefined, this.port1CF_read);
340

341
    if(this.vga_memory_size === undefined || this.vga_memory_size < VGA_MIN_MEMORY_SIZE)
342
    {
343
        this.vga_memory_size = VGA_MIN_MEMORY_SIZE;
344
        dbg_log("vga memory size rounded up to " + this.vga_memory_size, LOG_VGA);
345
    }
346
    else if(this.vga_memory_size & (VGA_BANK_SIZE - 1))
347
    {
348
        // round up to next 64k
349
        this.vga_memory_size |= VGA_BANK_SIZE - 1;
350
        this.vga_memory_size++;
351
    }
352

353

354
    const vga_offset = cpu.svga_allocate_memory(this.vga_memory_size);
355
    this.svga_memory = v86util.view(Uint8Array, cpu.wasm_memory, vga_offset, this.vga_memory_size);
356

357
    this.diff_addr_min = this.vga_memory_size;
358
    this.diff_addr_max = 0;
359
    this.diff_plot_min = this.vga_memory_size;
360
    this.diff_plot_max = 0;
361

362
    this.image_data = null;
363

364
    bus.register("screen-fill-buffer", function()
365
    {
366
        this.screen_fill_buffer();
367
    }, this);
368

369
    this.vga_memory = new Uint8Array(4 * VGA_BANK_SIZE);
370
    this.plane0 = new Uint8Array(this.vga_memory.buffer, 0 * VGA_BANK_SIZE, VGA_BANK_SIZE);
371
    this.plane1 = new Uint8Array(this.vga_memory.buffer, 1 * VGA_BANK_SIZE, VGA_BANK_SIZE);
372
    this.plane2 = new Uint8Array(this.vga_memory.buffer, 2 * VGA_BANK_SIZE, VGA_BANK_SIZE);
373
    this.plane3 = new Uint8Array(this.vga_memory.buffer, 3 * VGA_BANK_SIZE, VGA_BANK_SIZE);
374
    this.pixel_buffer = new Uint8Array(VGA_PIXEL_BUFFER_SIZE);
375

376
    var me = this;
377
    io.mmap_register(0xA0000, 0x20000,
378
        function(addr) { return me.vga_memory_read(addr); },
379
        function(addr, value) { me.vga_memory_write(addr, value); }
380
    );
381

382
    cpu.devices.pci.register_device(this);
383
}
384

385
VGAScreen.prototype.get_state = function()
386
{
387
    var state = [];
388

389
    state[0] = this.vga_memory_size;
390
    state[1] = this.cursor_address;
391
    state[2] = this.cursor_scanline_start;
392
    state[3] = this.cursor_scanline_end;
393
    state[4] = this.max_cols;
394
    state[5] = this.max_rows;
395
    state[6] = this.vga_memory;
396
    state[7] = this.dac_state;
397
    state[8] = this.start_address;
398
    state[9] = this.graphical_mode;
399
    state[10] = this.vga256_palette;
400
    state[11] = this.latch_dword;
401
    state[12] = this.color_compare;
402
    state[13] = this.color_dont_care;
403
    state[14] = this.miscellaneous_graphics_register;
404
    state[15] = this.svga_width;
405
    state[16] = this.svga_height;
406
    state[17] = this.crtc_mode;
407
    state[18] = this.svga_enabled;
408
    state[19] = this.svga_bpp;
409
    state[20] = this.svga_bank_offset;
410
    state[21] = this.svga_offset;
411
    state[22] = this.index_crtc;
412
    state[23] = this.dac_color_index_write;
413
    state[24] = this.dac_color_index_read;
414
    state[25] = this.dac_map;
415
    state[26] = this.sequencer_index;
416
    state[27] = this.plane_write_bm;
417
    state[28] = this.sequencer_memory_mode;
418
    state[29] = this.graphics_index;
419
    state[30] = this.plane_read;
420
    state[31] = this.planar_mode;
421
    state[32] = this.planar_rotate_reg;
422
    state[33] = this.planar_bitmap;
423
    state[34] = this.max_scan_line;
424
    state[35] = this.miscellaneous_output_register;
425
    state[36] = this.port_3DA_value;
426
    state[37] = this.dispi_index;
427
    state[38] = this.dispi_enable_value;
428
    state[39] = this.svga_memory;
429
    state[40] = this.graphical_mode_is_linear;
430
    state[41] = this.attribute_controller_index;
431
    state[42] = this.offset_register;
432
    state[43] = this.planar_setreset;
433
    state[44] = this.planar_setreset_enable;
434
    state[45] = this.start_address_latched;
435
    state[46] = this.crtc;
436
    state[47] = this.horizontal_display_enable_end;
437
    state[48] = this.horizontal_blank_start;
438
    state[49] = this.vertical_display_enable_end;
439
    state[50] = this.vertical_blank_start;
440
    state[51] = this.underline_location_register;
441
    state[52] = this.preset_row_scan;
442
    state[53] = this.offset_register;
443
    state[54] = this.palette_source;
444
    state[55] = this.attribute_mode;
445
    state[56] = this.color_plane_enable;
446
    state[57] = this.horizontal_panning;
447
    state[58] = this.color_select;
448
    state[59] = this.clocking_mode;
449
    state[60] = this.line_compare;
450
    state[61] = this.pixel_buffer;
451

452
    return state;
453
};
454

455
VGAScreen.prototype.set_state = function(state)
456
{
457
    this.vga_memory_size = state[0];
458
    this.cursor_address = state[1];
459
    this.cursor_scanline_start = state[2];
460
    this.cursor_scanline_end = state[3];
461
    this.max_cols = state[4];
462
    this.max_rows = state[5];
463
    state[6] && this.vga_memory.set(state[6]);
464
    this.dac_state = state[7];
465
    this.start_address = state[8];
466
    this.graphical_mode = state[9];
467
    this.vga256_palette = state[10];
468
    this.latch_dword = state[11];
469
    this.color_compare = state[12];
470
    this.color_dont_care = state[13];
471
    this.miscellaneous_graphics_register = state[14];
472
    this.svga_width = state[15];
473
    this.svga_height = state[16];
474
    this.crtc_mode = state[17];
475
    this.svga_enabled = state[18];
476
    this.svga_bpp = state[19];
477
    this.svga_bank_offset = state[20];
478
    this.svga_offset = state[21];
479
    this.index_crtc = state[22];
480
    this.dac_color_index_write = state[23];
481
    this.dac_color_index_read = state[24];
482
    this.dac_map = state[25];
483
    this.sequencer_index = state[26];
484
    this.plane_write_bm = state[27];
485
    this.sequencer_memory_mode = state[28];
486
    this.graphics_index = state[29];
487
    this.plane_read = state[30];
488
    this.planar_mode = state[31];
489
    this.planar_rotate_reg = state[32];
490
    this.planar_bitmap = state[33];
491
    this.max_scan_line = state[34];
492
    this.miscellaneous_output_register = state[35];
493
    this.port_3DA_value = state[36];
494
    this.dispi_index = state[37];
495
    this.dispi_enable_value = state[38];
496
    this.svga_memory.set(state[39]);
497
    this.graphical_mode_is_linear = state[40];
498
    this.attribute_controller_index = state[41];
499
    this.offset_register = state[42];
500
    this.planar_setreset = state[43];
501
    this.planar_setreset_enable = state[44];
502
    this.start_address_latched = state[45];
503
    this.crtc.set(state[46]);
504
    this.horizontal_display_enable_end = state[47];
505
    this.horizontal_blank_start = state[48];
506
    this.vertical_display_enable_end = state[49];
507
    this.vertical_blank_start = state[50];
508
    this.underline_location_register = state[51];
509
    this.preset_row_scan = state[52];
510
    this.offset_register = state[53];
511
    this.palette_source = state[54];
512
    this.attribute_mode = state[55];
513
    this.color_plane_enable = state[56];
514
    this.horizontal_panning = state[57];
515
    this.color_select = state[58];
516
    this.clocking_mode = state[59];
517
    this.line_compare = state[60];
518
    state[61] && this.pixel_buffer.set(state[61]);
519

520
    this.bus.send("screen-set-mode", this.graphical_mode);
521

522
    if(this.graphical_mode)
523
    {
524
        // Ensure set_size_graphical will update
525
        this.screen_width = 0;
526
        this.screen_height = 0;
527

528
        if(this.svga_enabled)
529
        {
530
            this.set_size_graphical(this.svga_width, this.svga_height,
531
                this.svga_bpp, this.svga_width, this.svga_height);
532
            this.update_layers();
533
        }
534
        else
535
        {
536
            this.update_vga_size();
537
            this.update_layers();
538
            this.complete_replot();
539
        }
540
    }
541
    else
542
    {
543
        this.set_size_text(this.max_cols, this.max_rows);
544
        this.update_cursor_scanline();
545
        this.update_cursor();
546
    }
547
    this.complete_redraw();
548
};
549

550
VGAScreen.prototype.vga_memory_read = function(addr)
551
{
552
    if(this.svga_enabled && this.graphical_mode_is_linear)
553
    {
554
        return this.cpu.read8((addr - 0xA0000 | this.svga_bank_offset) + VGA_LFB_ADDRESS | 0);
555
    }
556

557
    var memory_space_select = this.miscellaneous_graphics_register >> 2 & 0x3;
558
    addr -= VGA_HOST_MEMORY_SPACE_START[memory_space_select];
559

560
    // VGA chip only decodes addresses within the selected memory space.
561
    if(addr < 0 || addr >= VGA_HOST_MEMORY_SPACE_SIZE[memory_space_select])
562
    {
563
        dbg_log("vga read outside memory space: addr:" + h(addr), LOG_VGA);
564
        return 0;
565
    }
566

567
    this.latch_dword = this.plane0[addr];
568
    this.latch_dword |= this.plane1[addr] << 8;
569
    this.latch_dword |= this.plane2[addr] << 16;
570
    this.latch_dword |= this.plane3[addr] << 24;
571

572
    if(this.planar_mode & 0x08)
573
    {
574
        // read mode 1
575
        var reading = 0xFF;
576

577
        if(this.color_dont_care & 0x1)
578
        {
579
            reading &= this.plane0[addr] ^ ~(this.color_compare & 0x1 ? 0xFF : 0x00);
580
        }
581
        if(this.color_dont_care & 0x2)
582
        {
583
            reading &= this.plane1[addr] ^ ~(this.color_compare & 0x2 ? 0xFF : 0x00);
584
        }
585
        if(this.color_dont_care & 0x4)
586
        {
587
            reading &= this.plane2[addr] ^ ~(this.color_compare & 0x4 ? 0xFF : 0x00);
588
        }
589
        if(this.color_dont_care & 0x8)
590
        {
591
            reading &= this.plane3[addr] ^ ~(this.color_compare & 0x8 ? 0xFF : 0x00);
592
        }
593

594
        return reading;
595
    }
596
    else
597
    {
598
        // read mode 0
599

600
        var plane = this.plane_read;
601
        if(!this.graphical_mode)
602
        {
603
            // We currently put all text data linearly
604
            plane = 0;
605
        }
606
        else if(this.sequencer_memory_mode & 0x8)
607
        {
608
            // Chain 4
609
            plane = addr & 0x3;
610
            addr &= ~0x3;
611
        }
612
        else if(this.planar_mode & 0x10)
613
        {
614
            // Odd/Even host read
615
            plane = addr & 0x1;
616
            addr &= ~0x1;
617
        }
618
        return this.vga_memory[plane << 16 | addr];
619
    }
620
};
621

622
VGAScreen.prototype.vga_memory_write = function(addr, value)
623
{
624
    if(this.svga_enabled && this.graphical_mode && this.graphical_mode_is_linear)
625
    {
626
        // vbe banked mode
627
        this.cpu.write8((addr - 0xA0000 | this.svga_bank_offset) + VGA_LFB_ADDRESS | 0, value);
628
        return;
629
    }
630

631
    var memory_space_select = this.miscellaneous_graphics_register >> 2 & 0x3;
632
    addr -= VGA_HOST_MEMORY_SPACE_START[memory_space_select];
633

634
    if(addr < 0 || addr >= VGA_HOST_MEMORY_SPACE_SIZE[memory_space_select])
635
    {
636
        dbg_log("vga write outside memory space: addr:" + h(addr) + ", value:" + h(value), LOG_VGA);
637
        return;
638
    }
639

640
    if(this.graphical_mode)
641
    {
642
        this.vga_memory_write_graphical(addr, value);
643
    }
644
    else
645
    {
646
        if(!(this.plane_write_bm & 0x3))
647
        {
648
            // Ignore writes to font planes.
649
            return;
650
        }
651
        this.vga_memory_write_text_mode(addr, value);
652
    }
653
};
654

655
VGAScreen.prototype.vga_memory_write_graphical = function(addr, value)
656
{
657
    var plane_dword;
658
    var write_mode = this.planar_mode & 3;
659
    var bitmask = this.apply_feed(this.planar_bitmap);
660
    var setreset_dword = this.apply_expand(this.planar_setreset);
661
    var setreset_enable_dword = this.apply_expand(this.planar_setreset_enable);
662

663
    // Write modes - see http://www.osdever.net/FreeVGA/vga/graphreg.htm#05
664
    switch(write_mode)
665
    {
666
        case 0:
667
            value = this.apply_rotate(value);
668
            plane_dword = this.apply_feed(value);
669
            plane_dword = this.apply_setreset(plane_dword, setreset_enable_dword);
670
            plane_dword = this.apply_logical(plane_dword, this.latch_dword);
671
            plane_dword = this.apply_bitmask(plane_dword, bitmask);
672
            break;
673
        case 1:
674
            plane_dword = this.latch_dword;
675
            break;
676
        case 2:
677
            plane_dword = this.apply_expand(value);
678
            plane_dword = this.apply_logical(plane_dword, this.latch_dword);
679
            plane_dword = this.apply_bitmask(plane_dword, bitmask);
680
            break;
681
        case 3:
682
            value = this.apply_rotate(value);
683
            bitmask &= this.apply_feed(value);
684
            plane_dword = setreset_dword;
685
            plane_dword = this.apply_bitmask(plane_dword, bitmask);
686
            break;
687
    }
688

689
    var plane_select = 0xF;
690

691
    switch(this.sequencer_memory_mode & 0xC)
692
    {
693
        // Odd/Even (aka chain 2)
694
        case 0x0:
695
            plane_select = 0x5 << (addr & 0x1);
696
            addr &= ~0x1;
697
            break;
698

699
        // Chain 4
700
        // Note: FreeVGA may have mistakenly stated that this bit field is
701
        // for system read only, yet the IBM Open Source Graphics Programmer's
702
        // Reference Manual explicitly states "both read and write".
703
        case 0x8:
704
        case 0xC:
705
            plane_select = 1 << (addr & 0x3);
706
            addr &= ~0x3;
707
            break;
708
    }
709

710
    // Plane masks take precedence
711
    // See: http://www.osdever.net/FreeVGA/vga/seqreg.htm#02
712
    plane_select &= this.plane_write_bm;
713

714
    if(plane_select & 0x1) this.plane0[addr] = (plane_dword >> 0) & 0xFF;
715
    if(plane_select & 0x2) this.plane1[addr] = (plane_dword >> 8) & 0xFF;
716
    if(plane_select & 0x4) this.plane2[addr] = (plane_dword >> 16) & 0xFF;
717
    if(plane_select & 0x8) this.plane3[addr] = (plane_dword >> 24) & 0xFF;
718

719
    var pixel_addr = this.vga_addr_to_pixel(addr);
720
    this.partial_replot(pixel_addr, pixel_addr + 7);
721
};
722

723
/**
724
 * Copies data_byte into the four planes, with each plane
725
 * represented by an 8-bit field inside the dword.
726
 * @param {number} data_byte
727
 * @return {number} 32-bit number representing the bytes for each plane.
728
 */
729
VGAScreen.prototype.apply_feed = function(data_byte)
730
{
731
    var dword = data_byte;
732
    dword |= data_byte << 8;
733
    dword |= data_byte << 16;
734
    dword |= data_byte << 24;
735
    return dword;
736
};
737

738
/**
739
 * Expands bits 0 to 3 to ocupy bits 0 to 31. Each
740
 * bit is expanded to 0xFF if set or 0x00 if clear.
741
 * @param {number} data_byte
742
 * @return {number} 32-bit number representing the bytes for each plane.
743
 */
744
VGAScreen.prototype.apply_expand = function(data_byte)
745
{
746
    var dword = data_byte & 0x1 ? 0xFF : 0x00;
747
    dword |= (data_byte & 0x2 ? 0xFF : 0x00) << 8;
748
    dword |= (data_byte & 0x4 ? 0xFF : 0x00) << 16;
749
    dword |= (data_byte & 0x8 ? 0xFF : 0x00) << 24;
750
    return dword;
751
};
752

753
/**
754
 * Planar Write - Barrel Shifter
755
 * @param {number} data_byte
756
 * @return {number}
757
 * @see {@link http://www.phatcode.net/res/224/files/html/ch25/25-01.html#Heading3}
758
 * @see {@link http://www.osdever.net/FreeVGA/vga/graphreg.htm#03}
759
 */
760
VGAScreen.prototype.apply_rotate = function(data_byte)
761
{
762
    var wrapped = data_byte | (data_byte << 8);
763
    var count = this.planar_rotate_reg & 0x7;
764
    var shifted = wrapped >>> count;
765
    return shifted & 0xFF;
766
};
767

768
/**
769
 * Planar Write - Set / Reset Circuitry
770
 * @param {number} data_dword
771
 * @param {number} enable_dword
772
 * @return {number}
773
 * @see {@link http://www.phatcode.net/res/224/files/html/ch25/25-03.html#Heading5}
774
 * @see {@link http://www.osdever.net/FreeVGA/vga/graphreg.htm#00}
775
 */
776
VGAScreen.prototype.apply_setreset = function(data_dword, enable_dword)
777
{
778
    var setreset_dword = this.apply_expand(this.planar_setreset);
779
    data_dword |= enable_dword & setreset_dword;
780
    data_dword &= ~enable_dword | setreset_dword;
781
    return data_dword;
782
};
783

784
/**
785
 * Planar Write - ALU Unit
786
 * @param {number} data_dword
787
 * @param {number} latch_dword
788
 * @return {number}
789
 * @see {@link http://www.phatcode.net/res/224/files/html/ch24/24-01.html#Heading3}
790
 * @see {@link http://www.osdever.net/FreeVGA/vga/graphreg.htm#03}
791
 */
792
VGAScreen.prototype.apply_logical = function(data_dword, latch_dword)
793
{
794
    switch(this.planar_rotate_reg & 0x18)
795
    {
796
        case 0x08:
797
            return data_dword & latch_dword;
798
        case 0x10:
799
            return data_dword | latch_dword;
800
        case 0x18:
801
            return data_dword ^ latch_dword;
802
    }
803
    return data_dword;
804
};
805

806
/**
807
 * Planar Write - Bitmask Unit
808
 * @param {number} data_dword
809
 * @param {number} bitmask_dword
810
 * @return {number}
811
 * @see {@link http://www.phatcode.net/res/224/files/html/ch25/25-01.html#Heading2}
812
 * @see {@link http://www.osdever.net/FreeVGA/vga/graphreg.htm#08}
813
 */
814
VGAScreen.prototype.apply_bitmask = function(data_dword, bitmask_dword)
815
{
816
    var plane_dword = bitmask_dword & data_dword;
817
    plane_dword |= ~bitmask_dword & this.latch_dword;
818
    return plane_dword;
819
};
820

821
VGAScreen.prototype.text_mode_redraw = function()
822
{
823
    var addr = this.start_address << 1,
824
        chr,
825
        color;
826

827
    for(var row = 0; row < this.max_rows; row++)
828
    {
829
        for(var col = 0; col < this.max_cols; col++)
830
        {
831
            chr = this.vga_memory[addr];
832
            color = this.vga_memory[addr | 1];
833

834
            this.bus.send("screen-put-char", [row, col, chr,
835
                this.vga256_palette[color >> 4 & 0xF], this.vga256_palette[color & 0xF]]);
836

837
            addr += 2;
838
        }
839
    }
840
};
841

842
VGAScreen.prototype.vga_memory_write_text_mode = function(addr, value)
843
{
844
    var memory_start = (addr >> 1) - this.start_address,
845
        row = memory_start / this.max_cols | 0,
846
        col = memory_start % this.max_cols,
847
        chr,
848
        color;
849

850
    // XXX: Should handle 16 bit write if possible
851
    if(addr & 1)
852
    {
853
        color = value;
854
        chr = this.vga_memory[addr & ~1];
855
    }
856
    else
857
    {
858
        chr = value;
859
        color = this.vga_memory[addr | 1];
860
    }
861

862
    this.bus.send("screen-put-char", [row, col, chr,
863
            this.vga256_palette[color >> 4 & 0xF], this.vga256_palette[color & 0xF]]);
864

865
    this.vga_memory[addr] = value;
866
};
867

868
VGAScreen.prototype.update_cursor = function()
869
{
870
    var row = (this.cursor_address - this.start_address) / this.max_cols | 0,
871
        col = (this.cursor_address - this.start_address) % this.max_cols;
872

873
    row = Math.min(this.max_rows - 1, row);
874

875
    this.bus.send("screen-update-cursor", [row, col]);
876
};
877

878
VGAScreen.prototype.complete_redraw = function()
879
{
880
    dbg_log("complete redraw", LOG_VGA);
881

882
    if(this.graphical_mode)
883
    {
884
        if(this.svga_enabled)
885
        {
886
            this.cpu.svga_mark_dirty();
887
        }
888
        else
889
        {
890
            this.diff_addr_min = 0;
891
            this.diff_addr_max = VGA_PIXEL_BUFFER_SIZE;
892
        }
893
    }
894
    else
895
    {
896
        this.text_mode_redraw();
897
    }
898
};
899

900
VGAScreen.prototype.complete_replot = function()
901
{
902
    dbg_log("complete replot", LOG_VGA);
903

904
    if(!this.graphical_mode || this.svga_enabled)
905
    {
906
        return;
907
    }
908

909
    this.diff_plot_min = 0;
910
    this.diff_plot_max = VGA_PIXEL_BUFFER_SIZE;
911

912
    this.complete_redraw();
913
};
914

915
VGAScreen.prototype.partial_redraw = function(min, max)
916
{
917
    if(min < this.diff_addr_min) this.diff_addr_min = min;
918
    if(max > this.diff_addr_max) this.diff_addr_max = max;
919
};
920

921
VGAScreen.prototype.partial_replot = function(min, max)
922
{
923
    if(min < this.diff_plot_min) this.diff_plot_min = min;
924
    if(max > this.diff_plot_max) this.diff_plot_max = max;
925

926
    this.partial_redraw(min, max);
927
};
928

929
VGAScreen.prototype.reset_diffs = function()
930
{
931
    this.diff_addr_min = this.vga_memory_size;
932
    this.diff_addr_max = 0;
933
    this.diff_plot_min = this.vga_memory_size;
934
    this.diff_plot_max = 0;
935
};
936

937
VGAScreen.prototype.destroy = function()
938
{
939

940
};
941

942
VGAScreen.prototype.vga_bytes_per_line = function()
943
{
944
    var bytes_per_line = this.offset_register << 2;
945
    if(this.underline_location_register & 0x40) bytes_per_line <<= 1;
946
    else if(this.crtc_mode & 0x40) bytes_per_line >>>= 1;
947
    return bytes_per_line;
948
};
949

950
VGAScreen.prototype.vga_addr_shift_count = function()
951
{
952
    // Count in multiples of 0x40 for convenience
953
    // Left shift 2 for word mode - 2 bytes per dot clock
954
    var shift_count = 0x80;
955

956
    // Left shift 3 for byte mode - 1 byte per dot clock
957
    shift_count += ~this.underline_location_register & this.crtc_mode & 0x40;
958

959
    // Left shift 1 for doubleword mode - 4 bytes per dot clock
960
    shift_count -= this.underline_location_register & 0x40;
961

962
    // But shift one less if PEL width mode - 2 dot clocks per pixel
963
    shift_count -= this.attribute_mode & 0x40;
964

965
    return shift_count >>> 6;
966
};
967

968
VGAScreen.prototype.vga_addr_to_pixel = function(addr)
969
{
970
    var shift_count = this.vga_addr_shift_count();
971

972
    // Undo effects of substituted bits 13 and 14
973
    // Assumptions:
974
    //  - max_scan_line register is set to the values shown below
975
    //  - Each scan line stays within the offset alignment
976
    //  - No panning and no page flipping after drawing
977
    if(~this.crtc_mode & 0x3)
978
    {
979
        var pixel_addr = addr - this.start_address;
980

981
        // Remove substituted bits
982
        pixel_addr &= this.crtc_mode << 13 | ~0x6000;
983

984
        // Convert to 1 pixel per address
985
        pixel_addr <<= shift_count;
986

987
        // Decompose address
988
        var row = pixel_addr / this.virtual_width | 0;
989
        var col = pixel_addr % this.virtual_width;
990

991
        switch(this.crtc_mode & 0x3)
992
        {
993
            case 0x2:
994
                // Alternating rows using bit 13
995
                // Assumes max scan line = 1
996
                row = row << 1 | (addr >> 13 & 0x1);
997
                break;
998
            case 0x1:
999
                // Alternating rows using bit 14
1000
                // Assumes max scan line = 3
1001
                row = row << 1 | (addr >> 14 & 0x1);
1002
                break;
1003
            case 0x0:
1004
                // Cycling through rows using bit 13 and 14
1005
                // Assumes max scan line = 3
1006
                row = row << 2 | (addr >> 13 & 0x3);
1007
                break;
1008
        }
1009

1010
        // Reassemble address
1011
        return row * this.virtual_width + col + (this.start_address << shift_count);
1012
    }
1013
    else
1014
    {
1015
        // Convert to 1 pixel per address
1016
        return addr << shift_count;
1017
    }
1018
};
1019

1020
VGAScreen.prototype.scan_line_to_screen_row = function(scan_line)
1021
{
1022
    // Double scanning. The clock to the row scan counter is halved
1023
    // so it is not affected by the memory address bit substitutions below
1024
    if(this.max_scan_line & 0x80)
1025
    {
1026
        scan_line >>>= 1;
1027
    }
1028

1029
    // Maximum scan line, aka scan lines per character row
1030
    // This is the number of repeats - 1 for graphic modes
1031
    var repeat_factor = 1 + (this.max_scan_line & 0x1F);
1032
    scan_line = Math.ceil(scan_line / repeat_factor);
1033

1034
    // Odd and Even Row Scan Counter
1035
    // Despite repeated address counter values, because bit 13 of the shifted
1036
    // address is substituted with bit 0 of the row scan counter, a different
1037
    // display buffer address is generated instead of repeated
1038
    // Assumes maximum scan line register is set to 2 or 4.
1039
    // Note: can't assert this as register values may not be fully programmed.
1040
    if(!(this.crtc_mode & 0x1))
1041
    {
1042
        scan_line <<= 1;
1043
    }
1044

1045
    // Undo effects of substituted bit 14
1046
    // Assumes maximum scan line register is set to 2 or 4
1047
    // Note: can't assert this as register values may not be fully programmed.
1048
    // Other maximum scan line register values would result in weird addressing
1049
    // anyway
1050
    if(!(this.crtc_mode & 0x2))
1051
    {
1052
        scan_line <<= 1;
1053
    }
1054

1055
    return scan_line;
1056
};
1057

1058
/**
1059
 * @param {number} cols_count
1060
 * @param {number} rows_count
1061
 */
1062
VGAScreen.prototype.set_size_text = function(cols_count, rows_count)
1063
{
1064
    this.max_cols = cols_count;
1065
    this.max_rows = rows_count;
1066

1067
    this.bus.send("screen-set-size-text", [cols_count, rows_count]);
1068
};
1069

1070
VGAScreen.prototype.set_size_graphical = function(width, height, bpp, virtual_width, virtual_height)
1071
{
1072
    var needs_update = !this.stats.is_graphical ||
1073
        this.stats.bpp !== bpp ||
1074
        this.screen_width !== width ||
1075
        this.screen_height !== height ||
1076
        this.virtual_width !== virtual_width ||
1077
        this.virtual_height !== virtual_height;
1078

1079
    if(needs_update)
1080
    {
1081
        this.screen_width = width;
1082
        this.screen_height = height;
1083
        this.virtual_width = virtual_width;
1084
        this.virtual_height = virtual_height;
1085

1086
        this.stats.bpp = bpp;
1087
        this.stats.is_graphical = true;
1088
        this.stats.res_x = width;
1089
        this.stats.res_y = height;
1090

1091
        if (typeof ImageData !== "undefined")
1092
        {
1093
            const size = virtual_width * virtual_height;
1094
            const offset = this.cpu.svga_allocate_dest_buffer(size) >>> 0;
1095

1096
            this.dest_buffet_offset = offset;
1097
            this.image_data = new ImageData(new Uint8ClampedArray(this.cpu.wasm_memory.buffer, offset, 4 * size), virtual_width, virtual_height);
1098

1099
            this.cpu.svga_mark_dirty();
1100
        }
1101
        else
1102
        {
1103
            // TODO: nodejs
1104
        }
1105

1106
        this.bus.send("screen-set-size-graphical", [width, height, virtual_width, virtual_height, bpp]);
1107
    }
1108
};
1109

1110
VGAScreen.prototype.update_vga_size = function()
1111
{
1112
    if(this.svga_enabled)
1113
    {
1114
        return;
1115
    }
1116

1117
    var horizontal_characters = Math.min(1 + this.horizontal_display_enable_end,
1118
        this.horizontal_blank_start);
1119
    var vertical_scans = Math.min(1 + this.vertical_display_enable_end,
1120
        this.vertical_blank_start);
1121

1122
    if(!horizontal_characters || !vertical_scans)
1123
    {
1124
        // Don't update if width or height is zero.
1125
        // These happen when registers are not fully configured yet.
1126
        return;
1127
    }
1128

1129
    if(this.graphical_mode)
1130
    {
1131
        var screen_width = horizontal_characters << 3;
1132

1133
        // Offset is half the number of bytes/words/dwords (depending on clocking mode)
1134
        // of display memory that each logical line occupies.
1135
        // However, the number of pixels latched, regardless of addressing mode,
1136
        // should always 8 pixels per character clock (except for 8 bit PEL width, in which
1137
        // case 4 pixels).
1138
        var virtual_width = this.offset_register << 4;
1139

1140
        // Pixel Width / PEL Width / Clock Select
1141
        if(this.attribute_mode & 0x40)
1142
        {
1143
            screen_width >>>= 1;
1144
            virtual_width >>>= 1;
1145
        }
1146

1147
        var screen_height = this.scan_line_to_screen_row(vertical_scans);
1148

1149
        // The virtual buffer height is however many rows of data that can fit.
1150
        // Previously drawn graphics outside of current memory address space can
1151
        // still be drawn by setting start_address. The address at
1152
        // VGA_HOST_MEMORY_SPACE_START[memory_space_select] is mapped to the first
1153
        // byte of the frame buffer. Verified on some hardware.
1154
        // Depended on by: Windows 98 start screen
1155
        var available_bytes = VGA_HOST_MEMORY_SPACE_SIZE[0];
1156

1157
        var virtual_height = Math.ceil(available_bytes / this.vga_bytes_per_line());
1158

1159
        this.set_size_graphical(screen_width, screen_height, 8,
1160
            virtual_width, virtual_height);
1161

1162
        this.update_vertical_retrace();
1163
        this.update_layers();
1164
    }
1165
    else
1166
    {
1167
        if(this.max_scan_line & 0x80)
1168
        {
1169
            // Double scanning means that half of those scan lines
1170
            // are just repeats
1171
            vertical_scans >>>= 1;
1172
        }
1173

1174
        var height = vertical_scans / (1 + (this.max_scan_line & 0x1F)) | 0;
1175

1176
        if(horizontal_characters && height)
1177
        {
1178
            this.set_size_text(horizontal_characters, height);
1179
        }
1180
    }
1181
};
1182

1183
VGAScreen.prototype.update_layers = function()
1184
{
1185
    if(!this.graphical_mode)
1186
    {
1187
        this.text_mode_redraw();
1188
    }
1189

1190
    if(this.svga_enabled)
1191
    {
1192
        this.layers = [];
1193
        return;
1194
    }
1195

1196
    if(!this.virtual_width || !this.screen_width)
1197
    {
1198
        // Avoid division by zero
1199
        return;
1200
    }
1201

1202
    if(!this.palette_source || (this.clocking_mode & 0x20))
1203
    {
1204
        // Palette source and screen disable bits = draw nothing
1205
        // See http://www.phatcode.net/res/224/files/html/ch29/29-05.html#Heading6
1206
        // and http://www.osdever.net/FreeVGA/vga/seqreg.htm#01
1207
        this.layers = [];
1208
        this.bus.send("screen-clear");
1209
        return;
1210
    }
1211

1212
    var start_addr = this.start_address_latched;
1213

1214
    var pixel_panning = this.horizontal_panning;
1215
    if(this.attribute_mode & 0x40)
1216
    {
1217
        pixel_panning >>>= 1;
1218
    }
1219

1220
    var byte_panning = this.preset_row_scan >> 5 & 0x3;
1221
    var pixel_addr_start = this.vga_addr_to_pixel(start_addr + byte_panning);
1222

1223
    var start_buffer_row = pixel_addr_start / this.virtual_width | 0;
1224
    var start_buffer_col = pixel_addr_start % this.virtual_width + pixel_panning;
1225

1226
    var split_screen_row = this.scan_line_to_screen_row(1 + this.line_compare);
1227
    split_screen_row = Math.min(split_screen_row, this.screen_height);
1228

1229
    var split_buffer_height = this.screen_height - split_screen_row;
1230

1231
    this.layers = [];
1232

1233
    for(var x = -start_buffer_col, y = 0; x < this.screen_width; x += this.virtual_width, y++)
1234
    {
1235
        this.layers.push({
1236
            image_data: this.image_data,
1237
            screen_x: x,
1238
            screen_y: 0,
1239
            buffer_x: 0,
1240
            buffer_y: start_buffer_row + y,
1241
            buffer_width: this.virtual_width,
1242
            buffer_height: split_screen_row,
1243
        });
1244
    }
1245

1246
    var start_split_col = 0;
1247
    if(!(this.attribute_mode & 0x20))
1248
    {
1249
        // Pixel panning mode. Allow panning for the lower split screen
1250
        start_split_col = this.vga_addr_to_pixel(byte_panning) + pixel_panning;
1251
    }
1252

1253
    for(var x = -start_split_col, y = 0; x < this.screen_width; x += this.virtual_width, y++)
1254
    {
1255
        this.layers.push({
1256
            image_data: this.image_data,
1257
            screen_x: x,
1258
            screen_y: split_screen_row,
1259
            buffer_x: 0,
1260
            buffer_y: y,
1261
            buffer_width: this.virtual_width,
1262
            buffer_height: split_buffer_height,
1263
        });
1264
    }
1265
};
1266

1267
VGAScreen.prototype.update_vertical_retrace = function()
1268
{
1269
    // Emulate behaviour during VSync/VRetrace
1270
    this.port_3DA_value |= 0x8;
1271
    if(this.start_address_latched !== this.start_address)
1272
    {
1273
        this.start_address_latched = this.start_address;
1274
        this.update_layers();
1275
    }
1276
};
1277

1278
VGAScreen.prototype.update_cursor_scanline = function()
1279
{
1280
    this.bus.send("screen-update-cursor-scanline", [this.cursor_scanline_start, this.cursor_scanline_end]);
1281
};
1282

1283
/**
1284
 * Attribute controller register / index write
1285
 * @see {@link http://www.osdever.net/FreeVGA/vga/attrreg.htm}
1286
 * @see {@link http://www.mcamafia.de/pdf/ibm_vgaxga_trm2.pdf} page 89
1287
 * @see {@link https://01.org/sites/default/files/documentation/intel-gfx-prm-osrc-hsw-display_0.pdf} page 48
1288
 */
1289
VGAScreen.prototype.port3C0_write = function(value)
1290
{
1291
    if(this.attribute_controller_index === -1)
1292
    {
1293
        dbg_log("attribute controller index register: " + h(value), LOG_VGA);
1294
        this.attribute_controller_index = value & 0x1F;
1295
        dbg_log("attribute actual index: " + h(this.attribute_controller_index), LOG_VGA);
1296

1297
        if(this.palette_source !== (value & 0x20))
1298
        {
1299
            // A method of blanking the screen.
1300
            // See http://www.phatcode.net/res/224/files/html/ch29/29-05.html#Heading6
1301
            this.palette_source = value & 0x20;
1302
            this.update_layers();
1303
        }
1304
    }
1305
    else
1306
    {
1307
        if(this.attribute_controller_index < 0x10)
1308
        {
1309
            dbg_log("internal palette: " + h(this.attribute_controller_index) + " -> " + h(value), LOG_VGA);
1310
            this.dac_map[this.attribute_controller_index] = value;
1311

1312
            if(!(this.attribute_mode & 0x40))
1313
            {
1314
                this.complete_redraw();
1315
            }
1316
        }
1317
        else
1318
        switch(this.attribute_controller_index)
1319
        {
1320
            case 0x10:
1321
                dbg_log("3C0 / attribute mode control: " + h(value), LOG_VGA);
1322
                if(this.attribute_mode !== value)
1323
                {
1324
                    var previous_mode = this.attribute_mode;
1325
                    this.attribute_mode = value;
1326

1327
                    var is_graphical = (value & 0x1) > 0;
1328
                    if(!this.svga_enabled && this.graphical_mode !== is_graphical)
1329
                    {
1330
                        this.graphical_mode = is_graphical;
1331
                        this.bus.send("screen-set-mode", this.graphical_mode);
1332
                    }
1333

1334
                    if((previous_mode ^ value) & 0x40)
1335
                    {
1336
                        // PEL width changed. Pixel Buffer now invalidated
1337
                        this.complete_replot();
1338
                    }
1339

1340
                    this.update_vga_size();
1341

1342
                    // Data stored in image buffer are invalidated
1343
                    this.complete_redraw();
1344
                }
1345
                break;
1346
            case 0x12:
1347
                dbg_log("3C0 / color plane enable: " + h(value), LOG_VGA);
1348
                if(this.color_plane_enable !== value)
1349
                {
1350
                    this.color_plane_enable = value;
1351

1352
                    // Data stored in image buffer are invalidated
1353
                    this.complete_redraw();
1354
                }
1355
                break;
1356
            case 0x13:
1357
                dbg_log("3C0 / horizontal panning: " + h(value), LOG_VGA);
1358
                if(this.horizontal_panning !== value)
1359
                {
1360
                    this.horizontal_panning = value & 0xF;
1361
                    this.update_layers();
1362
                }
1363
                break;
1364
            case 0x14:
1365
                dbg_log("3C0 / color select: " + h(value), LOG_VGA);
1366
                if(this.color_select !== value)
1367
                {
1368
                    this.color_select = value;
1369

1370
                    // Data stored in image buffer are invalidated
1371
                    this.complete_redraw();
1372
                }
1373
                break;
1374
            default:
1375
                dbg_log("3C0 / attribute controller write " + h(this.attribute_controller_index) + ": " + h(value), LOG_VGA);
1376
        }
1377

1378
        this.attribute_controller_index = -1;
1379
    }
1380
};
1381

1382
VGAScreen.prototype.port3C0_read = function()
1383
{
1384
    dbg_log("3C0 read", LOG_VGA);
1385
    var result = this.attribute_controller_index | this.palette_source;
1386
    return result;
1387
};
1388

1389
VGAScreen.prototype.port3C0_read16 = function()
1390
{
1391
    dbg_log("3C0 read16", LOG_VGA);
1392
    return this.port3C0_read() & 0xFF | this.port3C1_read() << 8 & 0xFF00;
1393
};
1394

1395
VGAScreen.prototype.port3C1_read = function()
1396
{
1397
    if(this.attribute_controller_index < 0x10)
1398
    {
1399
        dbg_log("3C1 / internal palette read: " + h(this.attribute_controller_index) +
1400
            " -> " + h(this.dac_map[this.attribute_controller_index]), LOG_VGA);
1401
        return this.dac_map[this.attribute_controller_index] & 0xFF;
1402
    }
1403

1404
    switch(this.attribute_controller_index)
1405
    {
1406
        case 0x10:
1407
            dbg_log("3C1 / attribute mode read: " + h(this.attribute_mode), LOG_VGA);
1408
            return this.attribute_mode;
1409
        case 0x12:
1410
            dbg_log("3C1 / color plane enable read: " + h(this.color_plane_enable), LOG_VGA);
1411
            return this.color_plane_enable;
1412
        case 0x13:
1413
            dbg_log("3C1 / horizontal panning read: " + h(this.horizontal_panning), LOG_VGA);
1414
            return this.horizontal_panning;
1415
        case 0x14:
1416
            dbg_log("3C1 / color select read: " + h(this.color_select), LOG_VGA);
1417
            return this.color_select;
1418
        default:
1419
            dbg_log("3C1 / attribute controller read " + h(this.attribute_controller_index), LOG_VGA);
1420
    }
1421
    return 0xFF;
1422

1423
};
1424

1425
VGAScreen.prototype.port3C2_write = function(value)
1426
{
1427
    dbg_log("3C2 / miscellaneous output register = " + h(value), LOG_VGA);
1428
    this.miscellaneous_output_register = value;
1429
};
1430

1431
VGAScreen.prototype.port3C4_write = function(value)
1432
{
1433
    this.sequencer_index = value;
1434
};
1435

1436
VGAScreen.prototype.port3C4_read = function()
1437
{
1438
    return this.sequencer_index;
1439
};
1440

1441
/**
1442
 * Sequencer register writes
1443
 * @see {@link http://www.osdever.net/FreeVGA/vga/seqreg.htm}
1444
 * @see {@link http://www.mcamafia.de/pdf/ibm_vgaxga_trm2.pdf} page 47
1445
 * @see {@link https://01.org/sites/default/files/documentation/intel-gfx-prm-osrc-hsw-display_0.pdf} page 19
1446
 */
1447
VGAScreen.prototype.port3C5_write = function(value)
1448
{
1449
    switch(this.sequencer_index)
1450
    {
1451
        case 0x01:
1452
            dbg_log("clocking mode: " + h(value), LOG_VGA);
1453
            var previous_clocking_mode = this.clocking_mode;
1454
            this.clocking_mode = value;
1455
            if((previous_clocking_mode ^ value) & 0x20)
1456
            {
1457
                // Screen disable bit modified
1458
                this.update_layers();
1459
            }
1460
            break;
1461
        case 0x02:
1462
            dbg_log("plane write mask: " + h(value), LOG_VGA);
1463
            this.plane_write_bm = value;
1464
            break;
1465
        case 0x04:
1466
            dbg_log("sequencer memory mode: " + h(value), LOG_VGA);
1467
            this.sequencer_memory_mode = value;
1468
            break;
1469
        default:
1470
            dbg_log("3C5 / sequencer write " + h(this.sequencer_index) + ": " + h(value), LOG_VGA);
1471
    }
1472
};
1473

1474
VGAScreen.prototype.port3C5_read = function()
1475
{
1476
    dbg_log("3C5 / sequencer read " + h(this.sequencer_index), LOG_VGA);
1477

1478
    switch(this.sequencer_index)
1479
    {
1480
        case 0x01:
1481
            return this.clocking_mode;
1482
        case 0x02:
1483
            return this.plane_write_bm;
1484
        case 0x04:
1485
            return this.sequencer_memory_mode;
1486
        case 0x06:
1487
            return 0x12;
1488
        default:
1489
    }
1490
    return 0;
1491
};
1492

1493
VGAScreen.prototype.port3C7_write = function(index)
1494
{
1495
    // index for reading the DAC
1496
    dbg_log("3C7 write: " + h(index), LOG_VGA);
1497
    this.dac_color_index_read = index * 3;
1498
    this.dac_state &= 0x0;
1499
};
1500

1501
VGAScreen.prototype.port3C7_read = function()
1502
{
1503
    // prepared to accept reads or writes
1504
    return this.dac_state;
1505
};
1506

1507
VGAScreen.prototype.port3C8_write = function(index)
1508
{
1509
    this.dac_color_index_write = index * 3;
1510
    this.dac_state |= 0x3;
1511
};
1512

1513
VGAScreen.prototype.port3C8_read = function()
1514
{
1515
    return this.dac_color_index_write / 3 & 0xFF;
1516
};
1517

1518
/**
1519
 * DAC color palette register writes
1520
 * @see {@link http://www.osdever.net/FreeVGA/vga/colorreg.htm}
1521
 * @see {@link http://www.mcamafia.de/pdf/ibm_vgaxga_trm2.pdf} page 104
1522
 * @see {@link https://01.org/sites/default/files/documentation/intel-gfx-prm-osrc-hsw-display_0.pdf} page 57
1523
 */
1524
VGAScreen.prototype.port3C9_write = function(color_byte)
1525
{
1526
    var index = this.dac_color_index_write / 3 | 0,
1527
        offset = this.dac_color_index_write % 3,
1528
        color = this.vga256_palette[index];
1529

1530
    color_byte = (color_byte & 0x3F) * 255 / 63 | 0;
1531

1532
    if(offset === 0)
1533
    {
1534
        color = color & ~0xFF0000 | color_byte << 16;
1535
    }
1536
    else if(offset === 1)
1537
    {
1538
        color = color & ~0xFF00 | color_byte << 8;
1539
    }
1540
    else
1541
    {
1542
        color = color & ~0xFF | color_byte;
1543
        dbg_log("dac set color, index=" + h(index) + " value=" + h(color), LOG_VGA);
1544
    }
1545

1546
    if(this.vga256_palette[index] !== color)
1547
    {
1548
        this.vga256_palette[index] = color;
1549
        this.complete_redraw();
1550
    }
1551
    this.dac_color_index_write++;
1552
};
1553

1554
VGAScreen.prototype.port3C9_read = function()
1555
{
1556
    dbg_log("3C9 read", LOG_VGA);
1557

1558
    var index = this.dac_color_index_read / 3 | 0;
1559
    var offset = this.dac_color_index_read % 3;
1560
    var color = this.vga256_palette[index];
1561

1562
    this.dac_color_index_read++;
1563
    return (color >> (2 - offset) * 8 & 0xFF) / 255 * 63 | 0;
1564
};
1565

1566
VGAScreen.prototype.port3CC_read = function()
1567
{
1568
    dbg_log("3CC read", LOG_VGA);
1569
    return this.miscellaneous_output_register;
1570
};
1571

1572
VGAScreen.prototype.port3CE_write = function(value)
1573
{
1574
    this.graphics_index = value;
1575
};
1576

1577
VGAScreen.prototype.port3CE_read = function()
1578
{
1579
    return this.graphics_index;
1580
};
1581

1582
/**
1583
 * Graphics controller register writes
1584
 * @see {@link http://www.osdever.net/FreeVGA/vga/graphreg.htm}
1585
 * @see {@link http://www.mcamafia.de/pdf/ibm_vgaxga_trm2.pdf} page 78
1586
 * @see {@link https://01.org/sites/default/files/documentation/intel-gfx-prm-osrc-hsw-display_0.pdf} page 29
1587
 */
1588
VGAScreen.prototype.port3CF_write = function(value)
1589
{
1590
    switch(this.graphics_index)
1591
    {
1592
        case 0:
1593
            this.planar_setreset = value;
1594
            dbg_log("plane set/reset: " + h(value), LOG_VGA);
1595
            break;
1596
        case 1:
1597
            this.planar_setreset_enable = value;
1598
            dbg_log("plane set/reset enable: " + h(value), LOG_VGA);
1599
            break;
1600
        case 2:
1601
            this.color_compare = value;
1602
            dbg_log("color compare: " + h(value), LOG_VGA);
1603
            break;
1604
        case 3:
1605
            this.planar_rotate_reg = value;
1606
            dbg_log("plane rotate: " + h(value), LOG_VGA);
1607
            break;
1608
        case 4:
1609
            this.plane_read = value;
1610
            dbg_log("plane read: " + h(value), LOG_VGA);
1611
            break;
1612
        case 5:
1613
            var previous_planar_mode = this.planar_mode;
1614
            this.planar_mode = value;
1615
            dbg_log("planar mode: " + h(value), LOG_VGA);
1616
            if((previous_planar_mode ^ value) & 0x60)
1617
            {
1618
                // Shift mode modified. Pixel buffer invalidated
1619
                this.complete_replot();
1620
            }
1621
            break;
1622
        case 6:
1623
            dbg_log("miscellaneous graphics register: " + h(value), LOG_VGA);
1624
            if(this.miscellaneous_graphics_register !== value)
1625
            {
1626
                this.miscellaneous_graphics_register = value;
1627
                this.update_vga_size();
1628
            }
1629
            break;
1630
        case 7:
1631
            this.color_dont_care = value;
1632
            dbg_log("color don't care: " + h(value), LOG_VGA);
1633
            break;
1634
        case 8:
1635
            this.planar_bitmap = value;
1636
            dbg_log("planar bitmap: " + h(value), LOG_VGA);
1637
            break;
1638
        default:
1639
            dbg_log("3CF / graphics write " + h(this.graphics_index) + ": " + h(value), LOG_VGA);
1640
    }
1641
};
1642

1643
VGAScreen.prototype.port3CF_read = function()
1644
{
1645
    dbg_log("3CF / graphics read " + h(this.graphics_index), LOG_VGA);
1646

1647
    switch(this.graphics_index)
1648
    {
1649
        case 0:
1650
            return this.planar_setreset;
1651
        case 1:
1652
            return this.planar_setreset_enable;
1653
        case 2:
1654
            return this.color_compare;
1655
        case 3:
1656
            return this.planar_rotate_reg;
1657
        case 4:
1658
            return this.plane_read;
1659
        case 5:
1660
            return this.planar_mode;
1661
        case 6:
1662
            return this.miscellaneous_graphics_register;
1663
        case 7:
1664
            return this.color_dont_care;
1665
        case 8:
1666
            return this.planar_bitmap;
1667
        default:
1668
    }
1669
    return 0;
1670
};
1671

1672
VGAScreen.prototype.port3D4_write = function(register)
1673
{
1674
    dbg_log("3D4 / crtc index: " + register, LOG_VGA);
1675
    this.index_crtc = register;
1676
};
1677

1678
VGAScreen.prototype.port3D4_read = function()
1679
{
1680
    dbg_log("3D4 read / crtc index: " + this.index_crtc, LOG_VGA);
1681
    return this.index_crtc;
1682
};
1683

1684
/**
1685
 * CRT controller register writes
1686
 * @see {@link http://www.osdever.net/FreeVGA/vga/crtcreg.htm}
1687
 * @see {@link http://www.mcamafia.de/pdf/ibm_vgaxga_trm2.pdf} page 55
1688
 * @see {@link https://01.org/sites/default/files/documentation/intel-gfx-prm-osrc-hsw-display_0.pdf} page 63
1689
 */
1690
VGAScreen.prototype.port3D5_write = function(value)
1691
{
1692
    switch(this.index_crtc)
1693
    {
1694
        case 0x1:
1695
            dbg_log("3D5 / hdisp enable end write: " + h(value), LOG_VGA);
1696
            if(this.horizontal_display_enable_end !== value)
1697
            {
1698
                this.horizontal_display_enable_end = value;
1699
                this.update_vga_size();
1700
            }
1701
            break;
1702
        case 0x2:
1703
            if(this.horizontal_blank_start !== value)
1704
            {
1705
                this.horizontal_blank_start = value;
1706
                this.update_vga_size();
1707
            }
1708
            break;
1709
        case 0x7:
1710
            dbg_log("3D5 / overflow register write: " + h(value), LOG_VGA);
1711
            var previous_vertical_display_enable_end = this.vertical_display_enable_end;
1712
            this.vertical_display_enable_end &= 0xFF;
1713
            this.vertical_display_enable_end |= (value << 3 & 0x200) | (value << 7 & 0x100);
1714
            if(previous_vertical_display_enable_end != this.vertical_display_enable_end)
1715
            {
1716
                this.update_vga_size();
1717
            }
1718
            this.line_compare = (this.line_compare & 0x2FF) | (value << 4 & 0x100);
1719

1720
            var previous_vertical_blank_start = this.vertical_blank_start;
1721
            this.vertical_blank_start = (this.vertical_blank_start & 0x2FF) | (value << 5 & 0x100);
1722
            if(previous_vertical_blank_start !== this.vertical_blank_start)
1723
            {
1724
                this.update_vga_size();
1725
            }
1726
            this.update_layers();
1727
            break;
1728
        case 0x8:
1729
            dbg_log("3D5 / preset row scan write: " + h(value), LOG_VGA);
1730
            this.preset_row_scan = value;
1731
            this.update_layers();
1732
            break;
1733
        case 0x9:
1734
            dbg_log("3D5 / max scan line write: " + h(value), LOG_VGA);
1735
            this.max_scan_line = value;
1736
            this.line_compare = (this.line_compare & 0x1FF) | (value << 3 & 0x200);
1737

1738
            var previous_vertical_blank_start = this.vertical_blank_start;
1739
            this.vertical_blank_start = (this.vertical_blank_start & 0x1FF) | (value << 4 & 0x200);
1740
            if(previous_vertical_blank_start !== this.vertical_blank_start)
1741
            {
1742
                this.update_vga_size();
1743
            }
1744

1745
            this.update_layers();
1746
            break;
1747
        case 0xA:
1748
            dbg_log("3D5 / cursor scanline start write: " + h(value), LOG_VGA);
1749
            this.cursor_scanline_start = value;
1750
            this.update_cursor_scanline();
1751
            break;
1752
        case 0xB:
1753
            dbg_log("3D5 / cursor scanline end write: " + h(value), LOG_VGA);
1754
            this.cursor_scanline_end = value;
1755
            this.update_cursor_scanline();
1756
            break;
1757
        case 0xC:
1758
            if((this.start_address >> 8 & 0xFF) !== value)
1759
            {
1760
                this.start_address = this.start_address & 0xff | value << 8;
1761
                this.update_layers();
1762
                if(~this.crtc_mode &  0x3)
1763
                {
1764
                    // Address substitution implementation depends on the
1765
                    // starting row and column, so the pixel buffer is invalidated.
1766
                    this.complete_replot();
1767
                }
1768
            }
1769
            dbg_log("3D5 / start addr hi write: " + h(value) + " -> " + h(this.start_address, 4), LOG_VGA);
1770
            break;
1771
        case 0xD:
1772
            if((this.start_address & 0xFF) !== value)
1773
            {
1774
                this.start_address = this.start_address & 0xff00 | value;
1775
                this.update_layers();
1776
                if(~this.crtc_mode &  0x3)
1777
                {
1778
                    // Address substitution implementation depends on the
1779
                    // starting row and column, so the pixel buffer is invalidated.
1780
                    this.complete_replot();
1781
                }
1782
            }
1783
            dbg_log("3D5 / start addr lo write: " + h(value) + " -> " + h(this.start_address, 4), LOG_VGA);
1784
            break;
1785
        case 0xE:
1786
            dbg_log("3D5 / cursor address hi write: " + h(value), LOG_VGA);
1787
            this.cursor_address = this.cursor_address & 0xFF | value << 8;
1788
            this.update_cursor();
1789
            break;
1790
        case 0xF:
1791
            dbg_log("3D5 / cursor address lo write: " + h(value), LOG_VGA);
1792
            this.cursor_address = this.cursor_address & 0xFF00 | value;
1793
            this.update_cursor();
1794
            break;
1795
        case 0x12:
1796
            dbg_log("3D5 / vdisp enable end write: " + h(value), LOG_VGA);
1797
            if((this.vertical_display_enable_end & 0xFF) !== value)
1798
            {
1799
                this.vertical_display_enable_end = (this.vertical_display_enable_end & 0x300) | value;
1800
                this.update_vga_size();
1801
            }
1802
            break;
1803
        case 0x13:
1804
            dbg_log("3D5 / offset register write: " + h(value), LOG_VGA);
1805
            if(this.offset_register !== value)
1806
            {
1807
                this.offset_register = value;
1808
                this.update_vga_size();
1809

1810
                if(~this.crtc_mode & 0x3)
1811
                {
1812
                    // Address substitution implementation depends on the
1813
                    // virtual width, so the pixel buffer is invalidated.
1814
                    this.complete_replot();
1815
                }
1816
            }
1817
            break;
1818
        case 0x14:
1819
            dbg_log("3D5 / underline location write: " + h(value), LOG_VGA);
1820
            if(this.underline_location_register !== value)
1821
            {
1822
                var previous_underline = this.underline_location_register;
1823

1824
                this.underline_location_register = value;
1825
                this.update_vga_size();
1826

1827
                if((previous_underline ^ value) & 0x40)
1828
                {
1829
                    // Doubleword addressing changed. Pixel buffer invalidated.
1830
                    this.complete_replot();
1831
                }
1832
            }
1833
            break;
1834
        case 0x15:
1835
            dbg_log("3D5 / vertical blank start write: " + h(value), LOG_VGA);
1836
            if((this.vertical_blank_start & 0xFF) !== value)
1837
            {
1838
                this.vertical_blank_start = (this.vertical_blank_start & 0x300) | value;
1839
                this.update_vga_size();
1840
            }
1841
            break;
1842
        case 0x17:
1843
            dbg_log("3D5 / crtc mode write: " + h(value), LOG_VGA);
1844
            if(this.crtc_mode !== value)
1845
            {
1846
                var previous_mode = this.crtc_mode;
1847

1848
                this.crtc_mode = value;
1849
                this.update_vga_size();
1850

1851
                if((previous_mode ^ value) & 0x43)
1852
                {
1853
                    // Word/byte addressing changed or address substitution changed.
1854
                    // Pixel buffer invalidated.
1855
                    this.complete_replot();
1856
                }
1857
            }
1858
            break;
1859
        case 0x18:
1860
            dbg_log("3D5 / line compare write: " + h(value), LOG_VGA);
1861
            this.line_compare = (this.line_compare & 0x300) | value;
1862
            this.update_layers();
1863
            break;
1864
        default:
1865
            if(this.index_crtc < this.crtc.length)
1866
            {
1867
                this.crtc[this.index_crtc] = value;
1868
            }
1869
            dbg_log("3D5 / CRTC write " + h(this.index_crtc) + ": " + h(value), LOG_VGA);
1870
    }
1871

1872
};
1873

1874
VGAScreen.prototype.port3D5_read = function()
1875
{
1876
    dbg_log("3D5 read " + h(this.index_crtc), LOG_VGA);
1877

1878
    switch(this.index_crtc)
1879
    {
1880
        case 0x1:
1881
            return this.horizontal_display_enable_end;
1882
        case 0x2:
1883
            return this.horizontal_blank_start;
1884
        case 0x7:
1885
            return (this.vertical_display_enable_end >> 7 & 0x2) |
1886
                (this.vertical_blank_start >> 5 & 0x8) |
1887
                (this.line_compare >> 4 & 0x10) |
1888
                (this.vertical_display_enable_end >> 3 & 0x40);
1889
        case 0x8:
1890
            return this.preset_row_scan;
1891
        case 0x9:
1892
            return this.max_scan_line;
1893
        case 0xA:
1894
            return this.cursor_scanline_start;
1895
        case 0xB:
1896
            return this.cursor_scanline_end;
1897
        case 0xC:
1898
            return this.start_address & 0xFF;
1899
        case 0xD:
1900
            return this.start_address >> 8;
1901
        case 0xE:
1902
            return this.cursor_address >> 8;
1903
        case 0xF:
1904
            return this.cursor_address & 0xFF;
1905
        case 0x12:
1906
            return this.vertical_display_enable_end & 0xFF;
1907
        case 0x13:
1908
            return this.offset_register;
1909
        case 0x14:
1910
            return this.underline_location_register;
1911
        case 0x15:
1912
            return this.vertical_blank_start & 0xFF;
1913
        case 0x17:
1914
            return this.crtc_mode;
1915
        case 0x18:
1916
            return this.line_compare & 0xFF;
1917
    }
1918

1919
    if(this.index_crtc < this.crtc.length)
1920
    {
1921
        return this.crtc[this.index_crtc];
1922
    }
1923
    else
1924
    {
1925
        return 0;
1926
    }
1927
};
1928

1929
VGAScreen.prototype.port3DA_read = function()
1930
{
1931
    dbg_log("3DA read - status 1 and clear attr index", LOG_VGA);
1932

1933
    var value = this.port_3DA_value;
1934

1935
    // Status register, bit 3 set by update_vertical_retrace
1936
    // during screen-fill-buffer
1937
    if(!this.graphical_mode)
1938
    {
1939
        // But screen-fill-buffer may not get triggered in text mode
1940
        // so toggle it manually here
1941
        if(this.port_3DA_value & 1)
1942
        {
1943
            this.port_3DA_value ^= 8;
1944
        }
1945
        this.port_3DA_value ^= 1;
1946
    }
1947
    else
1948
    {
1949
        this.port_3DA_value ^= 1;
1950
        this.port_3DA_value &= 1;
1951
    }
1952
    this.attribute_controller_index = -1;
1953
    return value;
1954
};
1955

1956
VGAScreen.prototype.port1CE_write = function(value)
1957
{
1958
    this.dispi_index = value;
1959
};
1960

1961
VGAScreen.prototype.port1CF_write = function(value)
1962
{
1963
    dbg_log("1CF / dispi write " + h(this.dispi_index) + ": " + h(value), LOG_VGA);
1964

1965
    switch(this.dispi_index)
1966
    {
1967
        case 1:
1968
            this.svga_width = value;
1969
            if(this.svga_width > MAX_XRES)
1970
            {
1971
                dbg_log("svga_width reduced from " + this.svga_width + " to " + MAX_XRES, LOG_VGA);
1972
                this.svga_width = MAX_XRES;
1973
            }
1974
            break;
1975
        case 2:
1976
            this.svga_height = value;
1977
            if(this.svga_height > MAX_YRES)
1978
            {
1979
                dbg_log("svga_height reduced from " + this.svga_height + " to " + MAX_YRES, LOG_VGA);
1980
                this.svga_height = MAX_YRES;
1981
            }
1982
            break;
1983
        case 3:
1984
            this.svga_bpp = value;
1985
            break;
1986
        case 4:
1987
            // enable, options
1988
            this.svga_enabled = (value & 1) === 1;
1989
            this.dispi_enable_value = value;
1990
            break;
1991
        case 5:
1992
            dbg_log("SVGA bank offset: " + h(value << 16));
1993
            this.svga_bank_offset = value << 16;
1994
            break;
1995
        case 9:
1996
            // y offset
1997
            const offset = value * this.svga_width;
1998
            dbg_log("SVGA offset: " + h(offset) + " y=" + h(value), LOG_VGA);
1999
            if(this.svga_offset !== offset)
2000
            {
2001
                this.svga_offset = offset;
2002
                this.complete_redraw();
2003
            }
2004
            break;
2005
        default:
2006
    }
2007

2008
    if(this.svga_enabled && (!this.svga_width || !this.svga_height))
2009
    {
2010
        dbg_log("SVGA: disabled because of invalid width/height: " + this.svga_width + "x" + this.svga_height, LOG_VGA);
2011
        this.svga_enabled = false;
2012
    }
2013

2014
    dbg_assert(this.svga_bpp !== 4, "unimplemented svga bpp: 4");
2015
    dbg_assert(this.svga_bpp === 4 || this.svga_bpp === 8 ||
2016
               this.svga_bpp === 15 || this.svga_bpp === 16 ||
2017
               this.svga_bpp === 24 || this.svga_bpp === 32,
2018
               "unexpected svga bpp: " + this.svga_bpp);
2019

2020
    dbg_log("SVGA: enabled=" + this.svga_enabled + ", " + this.svga_width + "x" + this.svga_height + "x" + this.svga_bpp, LOG_VGA);
2021

2022
    if(this.svga_enabled && this.dispi_index === 4)
2023
    {
2024
        this.set_size_graphical(this.svga_width, this.svga_height, this.svga_bpp, this.svga_width, this.svga_height);
2025
        this.bus.send("screen-set-mode", true);
2026
        this.graphical_mode = true;
2027
        this.graphical_mode_is_linear = true;
2028
    }
2029

2030
    if(!this.svga_enabled)
2031
    {
2032
        this.svga_bank_offset = 0;
2033
    }
2034

2035
    this.update_layers();
2036
};
2037

2038
VGAScreen.prototype.port1CF_read = function()
2039
{
2040
    dbg_log("1CF / dispi read " + h(this.dispi_index), LOG_VGA);
2041
    return this.svga_register_read(this.dispi_index);
2042
};
2043

2044
VGAScreen.prototype.svga_register_read = function(n)
2045
{
2046
    switch(n)
2047
    {
2048
        case 0:
2049
            // id
2050
            return 0xB0C0;
2051
        case 1:
2052
            return this.dispi_enable_value & 2 ? MAX_XRES : this.svga_width;
2053
        case 2:
2054
            return this.dispi_enable_value & 2 ? MAX_YRES : this.svga_height;
2055
        case 3:
2056
            return this.dispi_enable_value & 2 ? MAX_BPP : this.svga_bpp;
2057
        case 4:
2058
            return this.dispi_enable_value;
2059
        case 5:
2060
            return this.svga_bank_offset >>> 16;
2061
        case 6:
2062
            // virtual width
2063
            if(this.screen_width)
2064
            {
2065
                return this.screen_width;
2066
            }
2067
            else
2068
            {
2069
                return 1; // seabios/windows98 divide exception
2070
            }
2071
            break;
2072

2073
        case 8:
2074
            // x offset
2075
            return 0;
2076
        case 0x0A:
2077
            // memory size in 64 kilobyte banks
2078
            return this.vga_memory_size / VGA_BANK_SIZE | 0;
2079
    }
2080

2081
    return 0xFF;
2082
};
2083

2084
/**
2085
 * Transfers graphics from VGA Planes to the Pixel Buffer
2086
 * VGA Planes represent data stored on actual hardware.
2087
 * Pixel Buffer caches the 4-bit or 8-bit color indices for each pixel.
2088
 */
2089
VGAScreen.prototype.vga_replot = function()
2090
{
2091
    // Round to multiple of 8 towards extreme
2092
    var start = this.diff_plot_min & ~0xF;
2093
    var end = Math.min((this.diff_plot_max | 0xF), VGA_PIXEL_BUFFER_SIZE - 1);
2094

2095
    var addr_shift = this.vga_addr_shift_count();
2096
    var addr_substitution = ~this.crtc_mode & 0x3;
2097

2098
    var shift_mode = this.planar_mode & 0x60;
2099
    var pel_width = this.attribute_mode & 0x40;
2100

2101
    for(var pixel_addr = start; pixel_addr <= end;)
2102
    {
2103
        var addr = pixel_addr >>> addr_shift;
2104
        if(addr_substitution)
2105
        {
2106
            var row = pixel_addr / this.virtual_width | 0;
2107
            var col = pixel_addr - this.virtual_width * row;
2108

2109
            switch(addr_substitution)
2110
            {
2111
                case 0x1:
2112
                    // Alternating rows using bit 13
2113
                    // Assumes max scan line = 1
2114
                    addr = (row & 0x1) << 13;
2115
                    row >>>= 1;
2116
                    break;
2117
                case 0x2:
2118
                    // Alternating rows using bit 14
2119
                    // Assumes max scan line = 3
2120
                    addr = (row & 0x1) << 14;
2121
                    row >>>= 1;
2122
                    break;
2123
                case 0x3:
2124
                    // Cycling through rows using bit 13 and 14
2125
                    // Assumes max scan line = 3
2126
                    addr = (row & 0x3) << 13;
2127
                    row >>>= 2;
2128
                    break;
2129
            }
2130

2131
            addr |= (row * this.virtual_width + col >>> addr_shift) + this.start_address;
2132
        }
2133

2134
        var byte0 = this.plane0[addr];
2135
        var byte1 = this.plane1[addr];
2136
        var byte2 = this.plane2[addr];
2137
        var byte3 = this.plane3[addr];
2138

2139
        var shift_loads = new Uint8Array(8);
2140
        switch(shift_mode)
2141
        {
2142
            // Planar Shift Mode
2143
            // See http://www.osdever.net/FreeVGA/vga/vgaseq.htm
2144
            case 0x00:
2145
                // Shift these, so that the bits for the color are in
2146
                // the correct position in the for loop
2147
                byte0 <<= 0;
2148
                byte1 <<= 1;
2149
                byte2 <<= 2;
2150
                byte3 <<= 3;
2151

2152
                for(var i = 7; i >= 0; i--)
2153
                {
2154
                    shift_loads[7 - i] =
2155
                            byte0 >> i & 1 |
2156
                            byte1 >> i & 2 |
2157
                            byte2 >> i & 4 |
2158
                            byte3 >> i & 8;
2159
                }
2160
                break;
2161

2162
            // Packed Shift Mode, aka Interleaved Shift Mode
2163
            // Video Modes 4h and 5h
2164
            case 0x20:
2165
                shift_loads[0] = (byte0 >> 6 & 0x3) | (byte2 >> 4 & 0xC);
2166
                shift_loads[1] = (byte0 >> 4 & 0x3) | (byte2 >> 2 & 0xC);
2167
                shift_loads[2] = (byte0 >> 2 & 0x3) | (byte2 >> 0 & 0xC);
2168
                shift_loads[3] = (byte0 >> 0 & 0x3) | (byte2 << 2 & 0xC);
2169

2170
                shift_loads[4] = (byte1 >> 6 & 0x3) | (byte3 >> 4 & 0xC);
2171
                shift_loads[5] = (byte1 >> 4 & 0x3) | (byte3 >> 2 & 0xC);
2172
                shift_loads[6] = (byte1 >> 2 & 0x3) | (byte3 >> 0 & 0xC);
2173
                shift_loads[7] = (byte1 >> 0 & 0x3) | (byte3 << 2 & 0xC);
2174
                break;
2175

2176
            // 256-Color Shift Mode
2177
            // Video Modes 13h and unchained 256 color
2178
            case 0x40:
2179
            case 0x60:
2180
                shift_loads[0] = byte0 >> 4 & 0xF;
2181
                shift_loads[1] = byte0 >> 0 & 0xF;
2182
                shift_loads[2] = byte1 >> 4 & 0xF;
2183
                shift_loads[3] = byte1 >> 0 & 0xF;
2184
                shift_loads[4] = byte2 >> 4 & 0xF;
2185
                shift_loads[5] = byte2 >> 0 & 0xF;
2186
                shift_loads[6] = byte3 >> 4 & 0xF;
2187
                shift_loads[7] = byte3 >> 0 & 0xF;
2188
                break;
2189
        }
2190

2191
        if(pel_width)
2192
        {
2193
            // Assemble from two sets of 4 bits.
2194
            for(var i = 0, j = 0; i < 4; i++, pixel_addr++, j += 2)
2195
            {
2196
                this.pixel_buffer[pixel_addr] = (shift_loads[j] << 4) | shift_loads[j + 1];
2197
            }
2198
        }
2199
        else
2200
        {
2201
            for(var i = 0; i < 8; i++, pixel_addr++)
2202
            {
2203
                this.pixel_buffer[pixel_addr] = shift_loads[i];
2204
            }
2205
        }
2206
    }
2207
};
2208

2209
/**
2210
 * Transfers graphics from Pixel Buffer to Destination Image Buffer.
2211
 * The 4-bit/8-bit color indices in the Pixel Buffer are passed through
2212
 * the internal palette (dac_map) and the DAC palette (vga256_palette) to
2213
 * obtain the final 32 bit color that the Canvas API uses.
2214
 */
2215
VGAScreen.prototype.vga_redraw = function()
2216
{
2217
    var start = this.diff_addr_min;
2218
    var end = Math.min(this.diff_addr_max, VGA_PIXEL_BUFFER_SIZE - 1);
2219
    const buffer = new Int32Array(this.cpu.wasm_memory.buffer, this.dest_buffet_offset, this.virtual_width * this.virtual_height);
2220

2221
    var mask = 0xFF;
2222
    var colorset = 0x00;
2223
    if(this.attribute_mode & 0x80)
2224
    {
2225
        // Palette bits 5/4 select
2226
        mask &= 0xCF;
2227
        colorset |= this.color_select << 4 & 0x30;
2228
    }
2229

2230
    if(this.attribute_mode & 0x40)
2231
    {
2232
        // 8 bit mode
2233

2234
        for(var pixel_addr = start; pixel_addr <= end; pixel_addr++)
2235
        {
2236
            var color256 = (this.pixel_buffer[pixel_addr] & mask) | colorset;
2237
            var color = this.vga256_palette[color256];
2238

2239
            buffer[pixel_addr] = color & 0xFF00 | color << 16 | color >> 16 | 0xFF000000;
2240
        }
2241
    }
2242
    else
2243
    {
2244
        // 4 bit mode
2245

2246
        // Palette bits 7/6 select
2247
        mask &= 0x3F;
2248
        colorset |= this.color_select << 4 & 0xC0;
2249

2250
        for(var pixel_addr = start; pixel_addr <= end; pixel_addr++)
2251
        {
2252
            var color16 = this.pixel_buffer[pixel_addr] & this.color_plane_enable;
2253
            var color256 = (this.dac_map[color16] & mask) | colorset;
2254
            var color = this.vga256_palette[color256];
2255

2256
            buffer[pixel_addr] = color & 0xFF00 | color << 16 | color >> 16 | 0xFF000000;
2257
        }
2258
    }
2259
};
2260

2261
VGAScreen.prototype.screen_fill_buffer = function()
2262
{
2263
    if(!this.graphical_mode)
2264
    {
2265
        // text mode
2266
        // Update retrace behaviour anyway - programs waiting for signal before
2267
        // changing to graphical mode
2268
        this.update_vertical_retrace();
2269
        return;
2270
    }
2271

2272
    if(this.image_data.data.byteLength === 0)
2273
    {
2274
        // wasm memory resized
2275
        const buffer = new Uint8ClampedArray(this.cpu.wasm_memory.buffer, this.dest_buffet_offset, 4 * this.virtual_width * this.virtual_height);
2276
        this.image_data = new ImageData(buffer, this.virtual_width, this.virtual_height);
2277
        this.update_layers();
2278
    }
2279

2280
    if(this.svga_enabled)
2281
    {
2282
        let min_y = 0;
2283
        let max_y = this.svga_height;
2284

2285
        if(this.svga_bpp === 8)
2286
        {
2287
            // XXX: Slow, should be ported to rust, but it doesn't have access to vga256_palette
2288
            // XXX: Doesn't take svga_offset into account
2289
            const buffer = new Int32Array(this.cpu.wasm_memory.buffer, this.dest_buffet_offset, this.screen_width * this.screen_height);
2290
            const svga_memory = new Uint8Array(this.cpu.wasm_memory.buffer, this.svga_memory.byteOffset, this.vga_memory_size);
2291

2292
            for(var i = 0; i < buffer.length; i++)
2293
            {
2294
                var color = this.vga256_palette[svga_memory[i]];
2295
                buffer[i] = color & 0xFF00 | color << 16 | color >> 16 | 0xFF000000;
2296
            }
2297
        }
2298
        else
2299
        {
2300
            this.cpu.svga_fill_pixel_buffer(this.svga_bpp, this.svga_offset);
2301

2302
            const bytes_per_pixel = this.svga_bpp === 15 ? 2 : this.svga_bpp / 8;
2303
            min_y = (((this.cpu.svga_dirty_bitmap_min_offset[0] / bytes_per_pixel | 0) - this.svga_offset) / this.svga_width | 0);
2304
            max_y = (((this.cpu.svga_dirty_bitmap_max_offset[0] / bytes_per_pixel | 0) - this.svga_offset) / this.svga_width | 0) + 1;
2305
        }
2306

2307
        if(min_y < max_y)
2308
        {
2309
            min_y = Math.max(min_y, 0);
2310
            max_y = Math.min(max_y, this.svga_height);
2311

2312
            this.bus.send("screen-fill-buffer-end", [{
2313
                image_data: this.image_data,
2314
                screen_x: 0, screen_y: min_y,
2315
                buffer_x: 0, buffer_y: min_y,
2316
                buffer_width: this.svga_width,
2317
                buffer_height: max_y - min_y,
2318
            }]);
2319
        }
2320
    }
2321
    else
2322
    {
2323
        this.vga_replot();
2324
        this.vga_redraw();
2325
        this.bus.send("screen-fill-buffer-end", this.layers);
2326
    }
2327

2328
    this.reset_diffs();
2329
    this.update_vertical_retrace();
2330
};

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.