"use strict"; var /** * Always 64k * @const */ VGA_BANK_SIZE = 64 * 1024, /** @const */ MAX_XRES = 2560, /** @const */ MAX_YRES = 1600, /** @const */ MAX_BPP = 32; /** @const */ //var VGA_LFB_ADDRESS = 0xFE000000; // set by seabios var VGA_LFB_ADDRESS = 0xE0000000; /** @const */ var VGA_PIXEL_BUFFER_START = 4 * VGA_BANK_SIZE; /** * @const * Equals the maximum number of pixels for non svga. * 8 pixels per byte. */ var VGA_PIXEL_BUFFER_SIZE = 8 * VGA_BANK_SIZE; /** @const */ var VGA_MIN_MEMORY_SIZE = VGA_PIXEL_BUFFER_START + VGA_PIXEL_BUFFER_SIZE; /** * @const * @see {@link http://www.osdever.net/FreeVGA/vga/graphreg.htm#06} */ var VGA_HOST_MEMORY_SPACE_START = Uint32Array.from([ 0xA0000, 0xA0000, 0xB0000, 0xB8000, ]); /** * @const * @see {@link http://www.osdever.net/FreeVGA/vga/graphreg.htm#06} */ var VGA_HOST_MEMORY_SPACE_SIZE = Uint32Array.from([ 0x20000, // 128K 0x10000, // 64K 0x8000, // 32K 0x8000, // 32K ]); /** * @constructor * @param {CPU} cpu * @param {BusConnector} bus * @param {number} vga_memory_size */ function VGAScreen(cpu, bus, vga_memory_size) { /** @const @type {BusConnector} */ this.bus = bus; this.vga_memory_size = vga_memory_size; /** @type {number} */ this.cursor_address = 0; /** @type {number} */ this.cursor_scanline_start = 0xE; /** @type {number} */ this.cursor_scanline_end = 0xF; /** * Number of columns in text mode * @type {number} */ this.max_cols = 80; /** * Number of rows in text mode * @type {number} */ this.max_rows = 25; /** * Width in pixels in graphical mode * @type {number} */ this.screen_width = 0; /** * Height in pixels in graphical mode * @type {number} */ this.screen_height = 0; /** * Logical width in pixels of virtual buffer available for panning * @type {number} */ this.virtual_width = 0; /** * Logical height in pixels of virtual buffer available for panning * @type {number} */ this.virtual_height = 0; /** * The rectangular fragments of the image buffer, and their destination * locations, to be drawn every screen_fill_buffer during VGA modes. * @type {Array>} */ this.layers = []; /** * video memory start address * @type {number} */ this.start_address = 0; /** * Start address - a copy of start_address that only gets updated * during VSync, used for panning and page flipping * @type {number} */ this.start_address_latched = 0; /** * Unimplemented CRTC registers go here */ this.crtc = new Uint8Array(0x19); // Implemented CRTC registers: /** @type {number} */ this.crtc_mode = 0; /** @type {number} */ this.horizontal_display_enable_end = 0; /** @type {number} */ this.horizontal_blank_start = 0; /** @type {number} */ this.vertical_display_enable_end = 0; /** @type {number} */ this.vertical_blank_start = 0; /** @type {number} */ this.underline_location_register = 0; /** @type {number} */ this.preset_row_scan = 0; /** @type {number} */ this.offset_register = 0; /** @type {number} */ this.line_compare = 0; // End of CRTC registers /** * Used for svga, e.g. banked modes * @type{boolean} */ this.graphical_mode_is_linear = true; /** @type {boolean} */ this.graphical_mode = false; setTimeout(() => { bus.send("screen-set-mode", this.graphical_mode); }, 0); /* * VGA palette containing 256 colors for video mode 13, svga 8bpp, etc. * Needs to be initialised by the BIOS */ this.vga256_palette = new Int32Array(256); /** * VGA read latches * @type{number} */ this.latch_dword = 0; /** @type {number} */ this.svga_width = 0; /** @type {number} */ this.svga_height = 0; this.svga_enabled = false; /** @type {number} */ this.svga_bpp = 32; /** @type {number} */ this.svga_bank_offset = 0; /** * The video buffer offset created by VBE_DISPI_INDEX_Y_OFFSET * In bytes * @type {number} */ this.svga_offset = 0; // Experimental, could probably need some changes // 01:00.0 VGA compatible controller: NVIDIA Corporation GT216 [GeForce GT 220] (rev a2) this.pci_space = [ 0x34, 0x12, 0x11, 0x11, 0x03, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x08, VGA_LFB_ADDRESS >>> 8, VGA_LFB_ADDRESS >>> 16, VGA_LFB_ADDRESS >>> 24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf4, 0x1a, 0x00, 0x11, 0x00, 0x00, 0xbe, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; this.pci_id = 0x12 << 3; this.pci_bars = [ { size: vga_memory_size, }, ]; // TODO: Should be matched with vga bios size and mapping address // Seabios config for this device: // CONFIG_VGA_PCI=y // CONFIG_OVERRIDE_PCI_ID=y // CONFIG_VGA_VID=0x10de // CONFIG_VGA_DID=0x0a20 this.pci_rom_size = 0x10000; this.pci_rom_address = 0xFEB00000; this.name = "vga"; this.stats = { is_graphical: false, res_x: 0, res_y: 0, bpp: 0, }; this.index_crtc = 0; // index for setting colors through port 3C9h this.dac_color_index_write = 0; this.dac_color_index_read = 0; this.dac_state = 0; this.dac_map = new Uint8Array(0x10); this.attribute_controller_index = -1; this.palette_source = 0x20; this.attribute_mode = 0; this.color_plane_enable = 0; this.horizontal_panning = 0; this.color_select = 0; this.sequencer_index = -1; // bitmap of planes 0-3 this.plane_write_bm = 0xF; this.sequencer_memory_mode = 0; this.clocking_mode = 0; this.graphics_index = -1; this.plane_read = 0, // value 0-3, which plane to read this.planar_mode = 0; this.planar_rotate_reg = 0; this.planar_bitmap = 0xFF; this.planar_setreset = 0; this.planar_setreset_enable = 0; this.miscellaneous_graphics_register = 0; this.color_compare = 0; this.color_dont_care = 0; this.max_scan_line = 0; this.miscellaneous_output_register = 0xff; this.port_3DA_value = 0xFF; var io = cpu.io; io.register_write(0x3C0, this, this.port3C0_write); io.register_read(0x3C0, this, this.port3C0_read, this.port3C0_read16); io.register_read(0x3C1, this, this.port3C1_read); io.register_write(0x3C2, this, this.port3C2_write); io.register_write_consecutive(0x3C4, this, this.port3C4_write, this.port3C5_write); io.register_read(0x3C4, this, this.port3C4_read); io.register_read(0x3C5, this, this.port3C5_read); io.register_write_consecutive(0x3CE, this, this.port3CE_write, this.port3CF_write); io.register_read(0x3CE, this, this.port3CE_read); io.register_read(0x3CF, this, this.port3CF_read); io.register_write(0x3C7, this, this.port3C7_write); io.register_read(0x3C7, this, this.port3C7_read); io.register_write(0x3C8, this, this.port3C8_write); io.register_read(0x3C8, this, this.port3C8_read); io.register_write(0x3C9, this, this.port3C9_write); io.register_read(0x3C9, this, this.port3C9_read); io.register_read(0x3CC, this, this.port3CC_read); io.register_write_consecutive(0x3D4, this, this.port3D4_write, this.port3D5_write); io.register_read(0x3D4, this, this.port3D4_read); io.register_read(0x3D5, this, this.port3D5_read, () => { dbg_log("Warning: 16-bit read from 3D5", LOG_VGA); return this.port3D5_read(); }); io.register_read(0x3CA, this, function() { dbg_log("3CA read", LOG_VGA); return 0; }); io.register_read(0x3DA, this, this.port3DA_read); io.register_read(0x3BA, this, this.port3DA_read); // Bochs VBE Extensions // http://wiki.osdev.org/Bochs_VBE_Extensions this.dispi_index = -1; this.dispi_enable_value = 0; io.register_write(0x1CE, this, undefined, this.port1CE_write); io.register_write(0x1CF, this, undefined, this.port1CF_write); io.register_read(0x1CF, this, undefined, this.port1CF_read); if(this.vga_memory_size === undefined || this.vga_memory_size < VGA_MIN_MEMORY_SIZE) { this.vga_memory_size = VGA_MIN_MEMORY_SIZE; dbg_log("vga memory size rounded up to " + this.vga_memory_size, LOG_VGA); } else if(this.vga_memory_size & (VGA_BANK_SIZE - 1)) { // round up to next 64k this.vga_memory_size |= VGA_BANK_SIZE - 1; this.vga_memory_size++; } this.svga_memory = new Uint8Array(this.vga_memory_size); this.diff_addr_min = this.vga_memory_size; this.diff_addr_max = 0; this.diff_plot_min = this.vga_memory_size; this.diff_plot_max = 0; this.dest_buffer = undefined; bus.register("screen-tell-buffer", function(data) { if(this.dest_buffer && data[0]) { data[0].set(this.dest_buffer.subarray(0, data[0].length)); } this.dest_buffer = data[0]; }, this); bus.register("screen-fill-buffer", function() { this.screen_fill_buffer(); }, this); this.svga_memory16 = new Uint16Array(this.svga_memory.buffer); this.svga_memory32 = new Int32Array(this.svga_memory.buffer); this.vga_memory = new Uint8Array(this.svga_memory.buffer, 0, 4 * VGA_BANK_SIZE); this.plane0 = new Uint8Array(this.svga_memory.buffer, 0 * VGA_BANK_SIZE, VGA_BANK_SIZE); this.plane1 = new Uint8Array(this.svga_memory.buffer, 1 * VGA_BANK_SIZE, VGA_BANK_SIZE); this.plane2 = new Uint8Array(this.svga_memory.buffer, 2 * VGA_BANK_SIZE, VGA_BANK_SIZE); this.plane3 = new Uint8Array(this.svga_memory.buffer, 3 * VGA_BANK_SIZE, VGA_BANK_SIZE); this.pixel_buffer = new Uint8Array(this.svga_memory.buffer, VGA_PIXEL_BUFFER_START, VGA_PIXEL_BUFFER_SIZE); var me = this; io.mmap_register(0xA0000, 0x20000, function(addr) { return me.vga_memory_read(addr); }, function(addr, value) { me.vga_memory_write(addr, value); } ); io.mmap_register(VGA_LFB_ADDRESS, this.vga_memory_size, function(addr) { return me.svga_memory_read8(addr); }, function(addr, value) { me.svga_memory_write8(addr, value); }, function(addr) { return me.svga_memory_read32(addr); }, function(addr, value) { me.svga_memory_write32(addr, value); } ); cpu.devices.pci.register_device(this); } VGAScreen.prototype.get_state = function() { var state = []; state[0] = this.vga_memory_size; state[1] = this.cursor_address; state[2] = this.cursor_scanline_start; state[3] = this.cursor_scanline_end; state[4] = this.max_cols; state[5] = this.max_rows; state[6] = this.layers.map(layer => [ layer.screen_x, layer.screen_y, layer.buffer_x, layer.buffer_y, layer.buffer_width, layer.buffer_height, ]); state[7] = this.dac_state; state[8] = this.start_address; state[9] = this.graphical_mode; state[10] = this.vga256_palette; state[11] = this.latch_dword; state[12] = this.color_compare; state[13] = this.color_dont_care; state[14] = this.miscellaneous_graphics_register; state[15] = this.svga_width; state[16] = this.svga_height; state[17] = this.crtc_mode; state[18] = this.svga_enabled; state[19] = this.svga_bpp; state[20] = this.svga_bank_offset; state[21] = this.svga_offset; state[22] = this.index_crtc; state[23] = this.dac_color_index_write; state[24] = this.dac_color_index_read; state[25] = this.dac_map; state[26] = this.sequencer_index; state[27] = this.plane_write_bm; state[28] = this.sequencer_memory_mode; state[29] = this.graphics_index; state[30] = this.plane_read; state[31] = this.planar_mode; state[32] = this.planar_rotate_reg; state[33] = this.planar_bitmap; state[34] = this.max_scan_line; state[35] = this.miscellaneous_output_register; state[36] = this.port_3DA_value; state[37] = this.dispi_index; state[38] = this.dispi_enable_value; state[39] = this.svga_memory; state[40] = this.graphical_mode_is_linear; state[41] = this.attribute_controller_index; state[42] = this.offset_register; state[43] = this.planar_setreset; state[44] = this.planar_setreset_enable; state[45] = this.start_address_latched; state[46] = this.crtc; state[47] = this.horizontal_display_enable_end; state[48] = this.horizontal_blank_start; state[49] = this.vertical_display_enable_end; state[50] = this.vertical_blank_start; state[51] = this.underline_location_register; state[52] = this.preset_row_scan; state[53] = this.offset_register; state[54] = this.palette_source; state[55] = this.attribute_mode; state[56] = this.color_plane_enable; state[57] = this.horizontal_panning; state[58] = this.color_select; state[59] = this.clocking_mode; state[60] = this.line_compare; return state; }; VGAScreen.prototype.set_state = function(state) { this.vga_memory_size = state[0]; this.cursor_address = state[1]; this.cursor_scanline_start = state[2]; this.cursor_scanline_end = state[3]; this.max_cols = state[4]; this.max_rows = state[5]; this.layers = state[6].map(layer => ({ screen_x: layer[0], screen_y: layer[1], buffer_x: layer[2], buffer_y: layer[3], buffer_width: layer[4], buffer_height: layer[5], })); this.dac_state = state[7]; this.start_address = state[8]; this.graphical_mode = state[9]; this.vga256_palette = state[10]; this.latch_dword = state[11]; this.color_compare = state[12]; this.color_dont_care = state[13]; this.miscellaneous_graphics_register = state[14]; this.svga_width = state[15]; this.svga_height = state[16]; this.crtc_mode = state[17]; this.svga_enabled = state[18]; this.svga_bpp = state[19]; this.svga_bank_offset = state[20]; this.svga_offset = state[21]; this.index_crtc = state[22]; this.dac_color_index_write = state[23]; this.dac_color_index_read = state[24]; this.dac_map = state[25]; this.sequencer_index = state[26]; this.plane_write_bm = state[27]; this.sequencer_memory_mode = state[28]; this.graphics_index = state[29]; this.plane_read = state[30]; this.planar_mode = state[31]; this.planar_rotate_reg = state[32]; this.planar_bitmap = state[33]; this.max_scan_line = state[34]; this.miscellaneous_output_register = state[35]; this.port_3DA_value = state[36]; this.dispi_index = state[37]; this.dispi_enable_value = state[38]; this.svga_memory.set(state[39]); this.graphical_mode_is_linear = state[40]; this.attribute_controller_index = state[41]; this.offset_register = state[42]; this.planar_setreset = state[43]; this.planar_setreset_enable = state[44]; this.start_address_latched = state[45]; this.crtc.set(state[46]); this.horizontal_display_enable_end = state[47]; this.horizontal_blank_start = state[48]; this.vertical_display_enable_end = state[49]; this.vertical_blank_start = state[50]; this.underline_location_register = state[51]; this.preset_row_scan = state[52]; this.offset_register = state[53]; this.palette_source = state[54]; this.attribute_mode = state[55]; this.color_plane_enable = state[56]; this.horizontal_panning = state[57]; this.color_select = state[58]; this.clocking_mode = state[59]; this.line_compare = state[60]; this.bus.send("screen-set-mode", this.graphical_mode); if(this.graphical_mode) { // Ensure set_size_graphical will update this.screen_width = 0; this.screen_height = 0; if(this.svga_enabled) { this.set_size_graphical(this.svga_width, this.svga_height, this.svga_bpp, this.svga_width, this.svga_height); this.update_layers(); } else { this.update_vga_size(); this.complete_replot(); } } else { this.set_size_text(this.max_cols, this.max_rows); this.update_cursor_scanline(); this.update_cursor(); } this.complete_redraw(); }; VGAScreen.prototype.vga_memory_read = function(addr) { if(this.svga_enabled && this.graphical_mode_is_linear) { addr -= 0xA0000; addr |= this.svga_bank_offset; return this.svga_memory[addr]; } var memory_space_select = this.miscellaneous_graphics_register >> 2 & 0x3; addr -= VGA_HOST_MEMORY_SPACE_START[memory_space_select]; // VGA chip only decodes addresses within the selected memory space. if(addr < 0 || addr >= VGA_HOST_MEMORY_SPACE_SIZE[memory_space_select]) { dbg_log("vga read outside memory space: addr:" + h(addr), LOG_VGA); return 0; } this.latch_dword = this.plane0[addr]; this.latch_dword |= this.plane1[addr] << 8; this.latch_dword |= this.plane2[addr] << 16; this.latch_dword |= this.plane3[addr] << 24; if(this.planar_mode & 0x08) { // read mode 1 var reading = 0xFF; if(this.color_dont_care & 0x1) { reading &= this.plane0[addr] ^ ~(this.color_compare & 0x1 ? 0xFF : 0x00); } if(this.color_dont_care & 0x2) { reading &= this.plane1[addr] ^ ~(this.color_compare & 0x2 ? 0xFF : 0x00); } if(this.color_dont_care & 0x4) { reading &= this.plane2[addr] ^ ~(this.color_compare & 0x4 ? 0xFF : 0x00); } if(this.color_dont_care & 0x8) { reading &= this.plane3[addr] ^ ~(this.color_compare & 0x8 ? 0xFF : 0x00); } return reading; } else { // read mode 0 var plane = this.plane_read; if(!this.graphical_mode) { // We currently put all text data linearly plane = 0; } else if(this.sequencer_memory_mode & 0x8) { // Chain 4 plane = addr & 0x3; addr &= ~0x3; } else if(this.planar_mode & 0x10) { // Odd/Even host read plane = addr & 0x1; addr &= ~0x1; } return this.vga_memory[plane << 16 | addr]; } }; VGAScreen.prototype.vga_memory_write = function(addr, value) { if(this.svga_enabled && this.graphical_mode && this.graphical_mode_is_linear) { // vbe banked mode addr -= 0xA0000; this.vga_memory_write_graphical_linear(addr, value); return; } var memory_space_select = this.miscellaneous_graphics_register >> 2 & 0x3; addr -= VGA_HOST_MEMORY_SPACE_START[memory_space_select]; if(addr < 0 || addr >= VGA_HOST_MEMORY_SPACE_SIZE[memory_space_select]) { dbg_log("vga write outside memory space: addr:" + h(addr) + ", value:" + h(value), LOG_VGA); return; } if(this.graphical_mode) { this.vga_memory_write_graphical(addr, value); } else { if(!(this.plane_write_bm & 0x3)) { // Ignore writes to font planes. return; } this.vga_memory_write_text_mode(addr, value); } }; VGAScreen.prototype.vga_memory_write_graphical_linear = function(addr, value) { addr |= this.svga_bank_offset; this.diff_addr_min = addr < this.diff_addr_min ? addr : this.diff_addr_min; this.diff_addr_max = addr > this.diff_addr_max ? addr : this.diff_addr_max; this.svga_memory[addr] = value; }; VGAScreen.prototype.vga_memory_write_graphical = function(addr, value) { var plane_dword; var write_mode = this.planar_mode & 3; var bitmask = this.apply_feed(this.planar_bitmap); var setreset_dword = this.apply_expand(this.planar_setreset); var setreset_enable_dword = this.apply_expand(this.planar_setreset_enable); // Write modes - see http://www.osdever.net/FreeVGA/vga/graphreg.htm#05 switch(write_mode) { case 0: value = this.apply_rotate(value); plane_dword = this.apply_feed(value); plane_dword = this.apply_setreset(plane_dword, setreset_enable_dword); plane_dword = this.apply_logical(plane_dword, this.latch_dword); plane_dword = this.apply_bitmask(plane_dword, bitmask); break; case 1: plane_dword = this.latch_dword; break; case 2: plane_dword = this.apply_expand(value); plane_dword = this.apply_logical(plane_dword, this.latch_dword); plane_dword = this.apply_bitmask(plane_dword, bitmask); break; case 3: value = this.apply_rotate(value); bitmask &= this.apply_feed(value); plane_dword = setreset_dword; plane_dword = this.apply_bitmask(plane_dword, bitmask); break; } var plane_select = 0xF; switch(this.sequencer_memory_mode & 0xC) { // Odd/Even (aka chain 2) case 0x0: plane_select = 0x5 << (addr & 0x1); addr &= ~0x1; break; // Chain 4 // Note: FreeVGA may have mistakenly stated that this bit field is // for system read only, yet the IBM Open Source Graphics Programmer's // Reference Manual explicitly states "both read and write". case 0x8: case 0xC: plane_select = 1 << (addr & 0x3); addr &= ~0x3; break; } // Plane masks take precedence // See: http://www.osdever.net/FreeVGA/vga/seqreg.htm#02 plane_select &= this.plane_write_bm; if(plane_select & 0x1) this.plane0[addr] = (plane_dword >> 0) & 0xFF; if(plane_select & 0x2) this.plane1[addr] = (plane_dword >> 8) & 0xFF; if(plane_select & 0x4) this.plane2[addr] = (plane_dword >> 16) & 0xFF; if(plane_select & 0x8) this.plane3[addr] = (plane_dword >> 24) & 0xFF; var pixel_addr = this.vga_addr_to_pixel(addr); this.partial_replot(pixel_addr, pixel_addr + 7); }; /** * Copies data_byte into the four planes, with each plane * represented by an 8-bit field inside the dword. * @param {number} data_byte * @return {number} 32-bit number representing the bytes for each plane. */ VGAScreen.prototype.apply_feed = function(data_byte) { var dword = data_byte; dword |= data_byte << 8; dword |= data_byte << 16; dword |= data_byte << 24; return dword; }; /** * Expands bits 0 to 3 to ocupy bits 0 to 31. Each * bit is expanded to 0xFF if set or 0x00 if clear. * @param {number} data_byte * @return {number} 32-bit number representing the bytes for each plane. */ VGAScreen.prototype.apply_expand = function(data_byte) { var dword = data_byte & 0x1 ? 0xFF : 0x00; dword |= (data_byte & 0x2 ? 0xFF : 0x00) << 8; dword |= (data_byte & 0x4 ? 0xFF : 0x00) << 16; dword |= (data_byte & 0x8 ? 0xFF : 0x00) << 24; return dword; }; /** * Planar Write - Barrel Shifter * @param {number} data_byte * @return {number} * @see {@link http://www.phatcode.net/res/224/files/html/ch25/25-01.html#Heading3} * @see {@link http://www.osdever.net/FreeVGA/vga/graphreg.htm#03} */ VGAScreen.prototype.apply_rotate = function(data_byte) { var wrapped = data_byte | (data_byte << 8); var count = this.planar_rotate_reg & 0x7; var shifted = wrapped >>> count; return shifted & 0xFF; }; /** * Planar Write - Set / Reset Circuitry * @param {number} data_dword * @param {number} enable_dword * @return {number} * @see {@link http://www.phatcode.net/res/224/files/html/ch25/25-03.html#Heading5} * @see {@link http://www.osdever.net/FreeVGA/vga/graphreg.htm#00} */ VGAScreen.prototype.apply_setreset = function(data_dword, enable_dword) { var setreset_dword = this.apply_expand(this.planar_setreset); data_dword |= enable_dword & setreset_dword; data_dword &= ~enable_dword | setreset_dword; return data_dword; }; /** * Planar Write - ALU Unit * @param {number} data_dword * @param {number} latch_dword * @return {number} * @see {@link http://www.phatcode.net/res/224/files/html/ch24/24-01.html#Heading3} * @see {@link http://www.osdever.net/FreeVGA/vga/graphreg.htm#03} */ VGAScreen.prototype.apply_logical = function(data_dword, latch_dword) { switch(this.planar_rotate_reg & 0x18) { case 0x08: return data_dword & latch_dword; case 0x10: return data_dword | latch_dword; case 0x18: return data_dword ^ latch_dword; } return data_dword; }; /** * Planar Write - Bitmask Unit * @param {number} data_dword * @param {number} bitmask_dword * @return {number} * @see {@link http://www.phatcode.net/res/224/files/html/ch25/25-01.html#Heading2} * @see {@link http://www.osdever.net/FreeVGA/vga/graphreg.htm#08} */ VGAScreen.prototype.apply_bitmask = function(data_dword, bitmask_dword) { var plane_dword = bitmask_dword & data_dword; plane_dword |= ~bitmask_dword & this.latch_dword; return plane_dword; }; VGAScreen.prototype.text_mode_redraw = function() { var addr = this.start_address << 1, chr, color; for(var row = 0; row < this.max_rows; row++) { for(var col = 0; col < this.max_cols; col++) { chr = this.vga_memory[addr]; color = this.vga_memory[addr | 1]; this.bus.send("screen-put-char", [row, col, chr, this.vga256_palette[color >> 4 & 0xF], this.vga256_palette[color & 0xF]]); addr += 2; } } }; VGAScreen.prototype.vga_memory_write_text_mode = function(addr, value) { var memory_start = (addr >> 1) - this.start_address, row = memory_start / this.max_cols | 0, col = memory_start % this.max_cols, chr, color; // XXX: Should handle 16 bit write if possible if(addr & 1) { color = value; chr = this.vga_memory[addr & ~1]; } else { chr = value; color = this.vga_memory[addr | 1]; } this.bus.send("screen-put-char", [row, col, chr, this.vga256_palette[color >> 4 & 0xF], this.vga256_palette[color & 0xF]]); this.vga_memory[addr] = value; }; VGAScreen.prototype.update_cursor = function() { var row = (this.cursor_address - this.start_address) / this.max_cols | 0, col = (this.cursor_address - this.start_address) % this.max_cols; row = Math.min(this.max_rows - 1, row); this.bus.send("screen-update-cursor", [row, col]); }; VGAScreen.prototype.svga_memory_read8 = function(addr) { return this.svga_memory[addr & 0xFFFFFFF]; }; VGAScreen.prototype.svga_memory_read32 = function(addr) { addr &= 0xFFFFFFF; if(addr & 3) { return this.svga_memory[addr] | this.svga_memory[addr + 1] << 8 | this.svga_memory[addr + 2] << 16 | this.svga_memory[addr + 3] << 24; } else { return this.svga_memory32[addr >> 2]; } }; VGAScreen.prototype.svga_memory_write8 = function(addr, value) { addr &= 0xFFFFFFF; this.svga_memory[addr] = value; this.diff_addr_min = addr < this.diff_addr_min ? addr : this.diff_addr_min; this.diff_addr_max = addr > this.diff_addr_max ? addr : this.diff_addr_max; }; VGAScreen.prototype.svga_memory_write32 = function(addr, value) { addr &= 0xFFFFFFF; this.diff_addr_min = addr < this.diff_addr_min ? addr : this.diff_addr_min; this.diff_addr_max = addr + 3 > this.diff_addr_max ? addr + 3 : this.diff_addr_max; this.svga_memory[addr] = value; this.svga_memory[addr + 1] = value >> 8; this.svga_memory[addr + 2] = value >> 16; this.svga_memory[addr + 3] = value >> 24; }; VGAScreen.prototype.complete_redraw = function() { dbg_log("complete redraw", LOG_VGA); if(this.graphical_mode) { this.diff_addr_min = 0; if(this.svga_enabled) { this.diff_addr_max = this.vga_memory_size; } else { this.diff_addr_max = VGA_PIXEL_BUFFER_SIZE; } } else { this.text_mode_redraw(); } }; VGAScreen.prototype.complete_replot = function() { dbg_log("complete replot", LOG_VGA); if(!this.graphical_mode || this.svga_enabled) { return; } this.diff_plot_min = 0; this.diff_plot_max = VGA_PIXEL_BUFFER_SIZE; this.complete_redraw(); }; VGAScreen.prototype.partial_redraw = function(min, max) { if(min < this.diff_addr_min) this.diff_addr_min = min; if(max > this.diff_addr_max) this.diff_addr_max = max; }; VGAScreen.prototype.partial_replot = function(min, max) { if(min < this.diff_plot_min) this.diff_plot_min = min; if(max > this.diff_plot_max) this.diff_plot_max = max; this.partial_redraw(min, max); }; VGAScreen.prototype.reset_diffs = function() { this.diff_addr_min = this.vga_memory_size; this.diff_addr_max = 0; this.diff_plot_min = this.vga_memory_size; this.diff_plot_max = 0; }; VGAScreen.prototype.destroy = function() { }; VGAScreen.prototype.vga_bytes_per_line = function() { var bytes_per_line = this.offset_register << 2; if(this.underline_location_register & 0x40) bytes_per_line <<= 1; else if(this.crtc_mode & 0x40) bytes_per_line >>>= 1; return bytes_per_line; }; VGAScreen.prototype.vga_addr_shift_count = function() { // Count in multiples of 0x40 for convenience // Left shift 2 for word mode - 2 bytes per dot clock var shift_count = 0x80; // Left shift 3 for byte mode - 1 byte per dot clock shift_count += ~this.underline_location_register & this.crtc_mode & 0x40; // Left shift 1 for doubleword mode - 4 bytes per dot clock shift_count -= this.underline_location_register & 0x40; // But shift one less if PEL width mode - 2 dot clocks per pixel shift_count -= this.attribute_mode & 0x40; return shift_count >>> 6; }; VGAScreen.prototype.vga_addr_to_pixel = function(addr) { var shift_count = this.vga_addr_shift_count(); // Undo effects of substituted bits 13 and 14 // Assumptions: // - max_scan_line register is set to the values shown below // - Each scan line stays within the offset alignment // - No panning and no page flipping after drawing if(~this.crtc_mode & 0x3) { var pixel_addr = addr - this.start_address; // Remove substituted bits pixel_addr &= this.crtc_mode << 13 | ~0x6000; // Convert to 1 pixel per address pixel_addr <<= shift_count; // Decompose address var row = pixel_addr / this.virtual_width | 0; var col = pixel_addr % this.virtual_width; switch(this.crtc_mode & 0x3) { case 0x2: // Alternating rows using bit 13 // Assumes max scan line = 1 row = row << 1 | (addr >> 13 & 0x1); break; case 0x1: // Alternating rows using bit 14 // Assumes max scan line = 3 row = row << 1 | (addr >> 14 & 0x1); break; case 0x0: // Cycling through rows using bit 13 and 14 // Assumes max scan line = 3 row = row << 2 | (addr >> 13 & 0x3); break; } // Reassemble address return row * this.virtual_width + col + (this.start_address << shift_count); } else { // Convert to 1 pixel per address return addr << shift_count; } }; VGAScreen.prototype.scan_line_to_screen_row = function(scan_line) { // Double scanning. The clock to the row scan counter is halved // so it is not affected by the memory address bit substitutions below if(this.max_scan_line & 0x80) { scan_line >>>= 1; } // Maximum scan line, aka scan lines per character row // This is the number of repeats - 1 for graphic modes var repeat_factor = 1 + (this.max_scan_line & 0x1F); scan_line = Math.ceil(scan_line / repeat_factor); // Odd and Even Row Scan Counter // Despite repeated address counter values, because bit 13 of the shifted // address is substituted with bit 0 of the row scan counter, a different // display buffer address is generated instead of repeated // Assumes maximum scan line register is set to 2 or 4. // Note: can't assert this as register values may not be fully programmed. if(!(this.crtc_mode & 0x1)) { scan_line <<= 1; } // Undo effects of substituted bit 14 // Assumes maximum scan line register is set to 2 or 4 // Note: can't assert this as register values may not be fully programmed. // Other maximum scan line register values would result in weird addressing // anyway if(!(this.crtc_mode & 0x2)) { scan_line <<= 1; } return scan_line; }; /** * @param {number} cols_count * @param {number} rows_count */ VGAScreen.prototype.set_size_text = function(cols_count, rows_count) { this.max_cols = cols_count; this.max_rows = rows_count; this.bus.send("screen-set-size-text", [cols_count, rows_count]); }; VGAScreen.prototype.set_size_graphical = function(width, height, bpp, virtual_width, virtual_height) { var needs_update = !this.stats.is_graphical || this.stats.bpp !== bpp || this.screen_width !== width || this.screen_height !== height || this.virtual_width !== virtual_width || this.virtual_height !== virtual_height; if(needs_update) { this.screen_width = width; this.screen_height = height; this.virtual_width = virtual_width; this.virtual_height = virtual_height; this.stats.bpp = bpp; this.stats.is_graphical = true; this.stats.res_x = width; this.stats.res_y = height; this.bus.send("screen-set-size-graphical", [width, height, virtual_width, virtual_height, bpp]); } }; VGAScreen.prototype.update_vga_size = function() { if(this.svga_enabled) { return; } var horizontal_characters = Math.min(1 + this.horizontal_display_enable_end, this.horizontal_blank_start); var vertical_scans = Math.min(1 + this.vertical_display_enable_end, this.vertical_blank_start); if(!horizontal_characters || !vertical_scans) { // Don't update if width or height is zero. // These happen when registers are not fully configured yet. return; } if(this.graphical_mode) { var screen_width = horizontal_characters << 3; // Offset is half the number of bytes/words/dwords (depending on clocking mode) // of display memory that each logical line occupies. // However, the number of pixels latched, regardless of addressing mode, // should always 8 pixels per character clock (except for 8 bit PEL width, in which // case 4 pixels). var virtual_width = this.offset_register << 4; // Pixel Width / PEL Width / Clock Select if(this.attribute_mode & 0x40) { screen_width >>>= 1; virtual_width >>>= 1; } var screen_height = this.scan_line_to_screen_row(vertical_scans); // The virtual buffer height is however many rows of data that can fit. // Previously drawn graphics outside of current memory address space can // still be drawn by setting start_address. The address at // VGA_HOST_MEMORY_SPACE_START[memory_space_select] is mapped to the first // byte of the frame buffer. Verified on some hardware. // Depended on by: Windows 98 start screen var available_bytes = VGA_HOST_MEMORY_SPACE_SIZE[0]; var virtual_height = Math.ceil(available_bytes / this.vga_bytes_per_line()); this.set_size_graphical(screen_width, screen_height, 8, virtual_width, virtual_height); this.update_vertical_retrace(); this.update_layers(); } else { if(this.max_scan_line & 0x80) { // Double scanning means that half of those scan lines // are just repeats vertical_scans >>>= 1; } var height = vertical_scans / (1 + (this.max_scan_line & 0x1F)) | 0; if(horizontal_characters && height) { this.set_size_text(horizontal_characters, height); } } }; VGAScreen.prototype.update_layers = function() { if(!this.graphical_mode) { this.text_mode_redraw(); } if(this.svga_enabled) { this.layers = []; return; } if(!this.virtual_width || !this.screen_width) { // Avoid division by zero return; } if(!this.palette_source || (this.clocking_mode & 0x20)) { // Palette source and screen disable bits = draw nothing // See http://www.phatcode.net/res/224/files/html/ch29/29-05.html#Heading6 // and http://www.osdever.net/FreeVGA/vga/seqreg.htm#01 this.layers = []; this.bus.send("screen-clear"); return; } var start_addr = this.start_address_latched; var pixel_panning = this.horizontal_panning; if(this.attribute_mode & 0x40) { pixel_panning >>>= 1; } var byte_panning = this.preset_row_scan >> 5 & 0x3; var pixel_addr_start = this.vga_addr_to_pixel(start_addr + byte_panning); var start_buffer_row = pixel_addr_start / this.virtual_width | 0; var start_buffer_col = pixel_addr_start % this.virtual_width + pixel_panning; var split_screen_row = this.scan_line_to_screen_row(1 + this.line_compare); split_screen_row = Math.min(split_screen_row, this.screen_height); var split_buffer_height = this.screen_height - split_screen_row; this.layers = []; for(var x = -start_buffer_col, y = 0; x < this.screen_width; x += this.virtual_width, y++) { this.layers.push({ screen_x: x, screen_y: 0, buffer_x: 0, buffer_y: start_buffer_row + y, buffer_width: this.virtual_width, buffer_height: split_screen_row, }); } var start_split_col = 0; if(!(this.attribute_mode & 0x20)) { // Pixel panning mode. Allow panning for the lower split screen start_split_col = this.vga_addr_to_pixel(byte_panning) + pixel_panning; } for(var x = -start_split_col, y = 0; x < this.screen_width; x += this.virtual_width, y++) { this.layers.push({ screen_x: x, screen_y: split_screen_row, buffer_x: 0, buffer_y: y, buffer_width: this.virtual_width, buffer_height: split_buffer_height, }); } }; VGAScreen.prototype.update_vertical_retrace = function() { // Emulate behaviour during VSync/VRetrace this.port_3DA_value |= 0x8; if(this.start_address_latched !== this.start_address) { this.start_address_latched = this.start_address; this.update_layers(); } }; VGAScreen.prototype.update_cursor_scanline = function() { this.bus.send("screen-update-cursor-scanline", [this.cursor_scanline_start, this.cursor_scanline_end]); }; /** * Attribute controller register / index write * @see {@link http://www.osdever.net/FreeVGA/vga/attrreg.htm} * @see {@link http://www.mcamafia.de/pdf/ibm_vgaxga_trm2.pdf} page 89 * @see {@link https://01.org/sites/default/files/documentation/intel-gfx-prm-osrc-hsw-display_0.pdf} page 48 */ VGAScreen.prototype.port3C0_write = function(value) { if(this.attribute_controller_index === -1) { dbg_log("attribute controller index register: " + h(value), LOG_VGA); this.attribute_controller_index = value & 0x1F; dbg_log("attribute actual index: " + h(this.attribute_controller_index), LOG_VGA); if(this.palette_source !== (value & 0x20)) { // A method of blanking the screen. // See http://www.phatcode.net/res/224/files/html/ch29/29-05.html#Heading6 this.palette_source = value & 0x20; this.update_layers(); } } else { if(this.attribute_controller_index < 0x10) { dbg_log("internal palette: " + h(this.attribute_controller_index) + " -> " + h(value), LOG_VGA); this.dac_map[this.attribute_controller_index] = value; if(!(this.attribute_mode & 0x40)) { this.complete_redraw(); } } else switch(this.attribute_controller_index) { case 0x10: dbg_log("3C0 / attribute mode control: " + h(value), LOG_VGA); if(this.attribute_mode !== value) { var previous_mode = this.attribute_mode; this.attribute_mode = value; var is_graphical = (value & 0x1) > 0; if(!this.svga_enabled && this.graphical_mode !== is_graphical) { this.graphical_mode = is_graphical; this.bus.send("screen-set-mode", this.graphical_mode); } if((previous_mode ^ value) & 0x40) { // PEL width changed. Pixel Buffer now invalidated this.complete_replot(); } this.update_vga_size(); // Data stored in image buffer are invalidated this.complete_redraw(); } break; case 0x12: dbg_log("3C0 / color plane enable: " + h(value), LOG_VGA); if(this.color_plane_enable !== value) { this.color_plane_enable = value; // Data stored in image buffer are invalidated this.complete_redraw(); } break; case 0x13: dbg_log("3C0 / horizontal panning: " + h(value), LOG_VGA); if(this.horizontal_panning !== value) { this.horizontal_panning = value & 0xF; this.update_layers(); } break; case 0x14: dbg_log("3C0 / color select: " + h(value), LOG_VGA); if(this.color_select !== value) { this.color_select = value; // Data stored in image buffer are invalidated this.complete_redraw(); } break; default: dbg_log("3C0 / attribute controller write " + h(this.attribute_controller_index) + ": " + h(value), LOG_VGA); } this.attribute_controller_index = -1; } }; VGAScreen.prototype.port3C0_read = function() { dbg_log("3C0 read", LOG_VGA); var result = this.attribute_controller_index | this.palette_source; return result; }; VGAScreen.prototype.port3C0_read16 = function() { dbg_log("3C0 read16", LOG_VGA); return this.port3C0_read() & 0xFF | this.port3C1_read() << 8 & 0xFF00; }; VGAScreen.prototype.port3C1_read = function() { if(this.attribute_controller_index < 0x10) { dbg_log("3C1 / internal palette read: " + h(this.attribute_controller_index) + " -> " + h(this.dac_map[this.attribute_controller_index]), LOG_VGA); return this.dac_map[this.attribute_controller_index] & 0xFF; } switch(this.attribute_controller_index) { case 0x10: dbg_log("3C1 / attribute mode read: " + h(this.attribute_mode), LOG_VGA); return this.attribute_mode; case 0x12: dbg_log("3C1 / color plane enable read: " + h(this.color_plane_enable), LOG_VGA); return this.color_plane_enable; case 0x13: dbg_log("3C1 / horizontal panning read: " + h(this.horizontal_panning), LOG_VGA); return this.horizontal_panning; case 0x14: dbg_log("3C1 / color select read: " + h(this.color_select), LOG_VGA); return this.color_select; default: dbg_log("3C1 / attribute controller read " + h(this.attribute_controller_index), LOG_VGA); } return 0xFF; }; VGAScreen.prototype.port3C2_write = function(value) { dbg_log("3C2 / miscellaneous output register = " + h(value), LOG_VGA); this.miscellaneous_output_register = value; }; VGAScreen.prototype.port3C4_write = function(value) { this.sequencer_index = value; }; VGAScreen.prototype.port3C4_read = function() { return this.sequencer_index; }; /** * Sequencer register writes * @see {@link http://www.osdever.net/FreeVGA/vga/seqreg.htm} * @see {@link http://www.mcamafia.de/pdf/ibm_vgaxga_trm2.pdf} page 47 * @see {@link https://01.org/sites/default/files/documentation/intel-gfx-prm-osrc-hsw-display_0.pdf} page 19 */ VGAScreen.prototype.port3C5_write = function(value) { switch(this.sequencer_index) { case 0x01: dbg_log("clocking mode: " + h(value), LOG_VGA); var previous_clocking_mode = this.clocking_mode; this.clocking_mode = value; if((previous_clocking_mode ^ value) & 0x20) { // Screen disable bit modified this.update_layers(); } break; case 0x02: dbg_log("plane write mask: " + h(value), LOG_VGA); this.plane_write_bm = value; break; case 0x04: dbg_log("sequencer memory mode: " + h(value), LOG_VGA); this.sequencer_memory_mode = value; break; default: dbg_log("3C5 / sequencer write " + h(this.sequencer_index) + ": " + h(value), LOG_VGA); } }; VGAScreen.prototype.port3C5_read = function() { dbg_log("3C5 / sequencer read " + h(this.sequencer_index), LOG_VGA); switch(this.sequencer_index) { case 0x01: return this.clocking_mode; case 0x02: return this.plane_write_bm; case 0x04: return this.sequencer_memory_mode; case 0x06: return 0x12; default: } return 0; }; VGAScreen.prototype.port3C7_write = function(index) { // index for reading the DAC dbg_log("3C7 write: " + h(index), LOG_VGA); this.dac_color_index_read = index * 3; this.dac_state &= 0x0; }; VGAScreen.prototype.port3C7_read = function() { // prepared to accept reads or writes return this.dac_state; }; VGAScreen.prototype.port3C8_write = function(index) { this.dac_color_index_write = index * 3; this.dac_state |= 0x3; }; VGAScreen.prototype.port3C8_read = function() { return this.dac_color_index_write / 3 & 0xFF; }; /** * DAC color palette register writes * @see {@link http://www.osdever.net/FreeVGA/vga/colorreg.htm} * @see {@link http://www.mcamafia.de/pdf/ibm_vgaxga_trm2.pdf} page 104 * @see {@link https://01.org/sites/default/files/documentation/intel-gfx-prm-osrc-hsw-display_0.pdf} page 57 */ VGAScreen.prototype.port3C9_write = function(color_byte) { var index = this.dac_color_index_write / 3 | 0, offset = this.dac_color_index_write % 3, color = this.vga256_palette[index]; color_byte = (color_byte & 0x3F) * 255 / 63 | 0; if(offset === 0) { color = color & ~0xFF0000 | color_byte << 16; } else if(offset === 1) { color = color & ~0xFF00 | color_byte << 8; } else { color = color & ~0xFF | color_byte; dbg_log("dac set color, index=" + h(index) + " value=" + h(color), LOG_VGA); } if(this.vga256_palette[index] !== color) { this.vga256_palette[index] = color; this.complete_redraw(); } this.dac_color_index_write++; }; VGAScreen.prototype.port3C9_read = function() { dbg_log("3C9 read", LOG_VGA); var index = this.dac_color_index_read / 3 | 0; var offset = this.dac_color_index_read % 3; var color = this.vga256_palette[index]; this.dac_color_index_read++; return (color >> (2 - offset) * 8 & 0xFF) / 255 * 63 | 0; }; VGAScreen.prototype.port3CC_read = function() { dbg_log("3CC read", LOG_VGA); return this.miscellaneous_output_register; }; VGAScreen.prototype.port3CE_write = function(value) { this.graphics_index = value; }; VGAScreen.prototype.port3CE_read = function() { return this.graphics_index; }; /** * Graphics controller register writes * @see {@link http://www.osdever.net/FreeVGA/vga/graphreg.htm} * @see {@link http://www.mcamafia.de/pdf/ibm_vgaxga_trm2.pdf} page 78 * @see {@link https://01.org/sites/default/files/documentation/intel-gfx-prm-osrc-hsw-display_0.pdf} page 29 */ VGAScreen.prototype.port3CF_write = function(value) { switch(this.graphics_index) { case 0: this.planar_setreset = value; dbg_log("plane set/reset: " + h(value), LOG_VGA); break; case 1: this.planar_setreset_enable = value; dbg_log("plane set/reset enable: " + h(value), LOG_VGA); break; case 2: this.color_compare = value; dbg_log("color compare: " + h(value), LOG_VGA); break; case 3: this.planar_rotate_reg = value; dbg_log("plane rotate: " + h(value), LOG_VGA); break; case 4: this.plane_read = value; dbg_log("plane read: " + h(value), LOG_VGA); break; case 5: var previous_planar_mode = this.planar_mode; this.planar_mode = value; dbg_log("planar mode: " + h(value), LOG_VGA); if((previous_planar_mode ^ value) & 0x60) { // Shift mode modified. Pixel buffer invalidated this.complete_replot(); } break; case 6: dbg_log("miscellaneous graphics register: " + h(value), LOG_VGA); if(this.miscellaneous_graphics_register !== value) { this.miscellaneous_graphics_register = value; this.update_vga_size(); } break; case 7: this.color_dont_care = value; dbg_log("color don't care: " + h(value), LOG_VGA); break; case 8: this.planar_bitmap = value; dbg_log("planar bitmap: " + h(value), LOG_VGA); break; default: dbg_log("3CF / graphics write " + h(this.graphics_index) + ": " + h(value), LOG_VGA); } }; VGAScreen.prototype.port3CF_read = function() { dbg_log("3CF / graphics read " + h(this.graphics_index), LOG_VGA); switch(this.graphics_index) { case 0: return this.planar_setreset; case 1: return this.planar_setreset_enable; case 2: return this.color_compare; case 3: return this.planar_rotate_reg; case 4: return this.plane_read; case 5: return this.planar_mode; case 6: return this.miscellaneous_graphics_register; case 7: return this.color_dont_care; case 8: return this.planar_bitmap; default: } return 0; }; VGAScreen.prototype.port3D4_write = function(register) { dbg_log("3D4 / crtc index: " + register, LOG_VGA); this.index_crtc = register; }; VGAScreen.prototype.port3D4_read = function() { dbg_log("3D4 read / crtc index: " + this.index_crtc, LOG_VGA); return this.index_crtc; }; /** * CRT controller register writes * @see {@link http://www.osdever.net/FreeVGA/vga/crtcreg.htm} * @see {@link http://www.mcamafia.de/pdf/ibm_vgaxga_trm2.pdf} page 55 * @see {@link https://01.org/sites/default/files/documentation/intel-gfx-prm-osrc-hsw-display_0.pdf} page 63 */ VGAScreen.prototype.port3D5_write = function(value) { switch(this.index_crtc) { case 0x1: dbg_log("3D5 / hdisp enable end write: " + h(value), LOG_VGA); if(this.horizontal_display_enable_end !== value) { this.horizontal_display_enable_end = value; this.update_vga_size(); } break; case 0x2: if(this.horizontal_blank_start !== value) { this.horizontal_blank_start = value; this.update_vga_size(); } break; case 0x7: dbg_log("3D5 / overflow register write: " + h(value), LOG_VGA); var previous_vertical_display_enable_end = this.vertical_display_enable_end; this.vertical_display_enable_end &= 0xFF; this.vertical_display_enable_end |= (value << 3 & 0x200) | (value << 7 & 0x100); if(previous_vertical_display_enable_end != this.vertical_display_enable_end) { this.update_vga_size(); } this.line_compare = (this.line_compare & 0x2FF) | (value << 4 & 0x100); var previous_vertical_blank_start = this.vertical_blank_start; this.vertical_blank_start = (this.vertical_blank_start & 0x2FF) | (value << 5 & 0x100); if(previous_vertical_blank_start !== this.vertical_blank_start) { this.update_vga_size(); } this.update_layers(); break; case 0x8: dbg_log("3D5 / preset row scan write: " + h(value), LOG_VGA); this.preset_row_scan = value; this.update_layers(); break; case 0x9: dbg_log("3D5 / max scan line write: " + h(value), LOG_VGA); this.max_scan_line = value; this.line_compare = (this.line_compare & 0x1FF) | (value << 3 & 0x200); var previous_vertical_blank_start = this.vertical_blank_start; this.vertical_blank_start = (this.vertical_blank_start & 0x1FF) | (value << 4 & 0x200); if(previous_vertical_blank_start !== this.vertical_blank_start) { this.update_vga_size(); } this.update_layers(); break; case 0xA: dbg_log("3D5 / cursor scanline start write: " + h(value), LOG_VGA); this.cursor_scanline_start = value; this.update_cursor_scanline(); break; case 0xB: dbg_log("3D5 / cursor scanline end write: " + h(value), LOG_VGA); this.cursor_scanline_end = value; this.update_cursor_scanline(); break; case 0xC: if((this.start_address >> 8 & 0xFF) !== value) { this.start_address = this.start_address & 0xff | value << 8; this.update_layers(); if(~this.crtc_mode & 0x3) { // Address substitution implementation depends on the // starting row and column, so the pixel buffer is invalidated. this.complete_replot(); } } dbg_log("3D5 / start addr hi write: " + h(value) + " -> " + h(this.start_address, 4), LOG_VGA); break; case 0xD: if((this.start_address & 0xFF) !== value) { this.start_address = this.start_address & 0xff00 | value; this.update_layers(); if(~this.crtc_mode & 0x3) { // Address substitution implementation depends on the // starting row and column, so the pixel buffer is invalidated. this.complete_replot(); } } dbg_log("3D5 / start addr lo write: " + h(value) + " -> " + h(this.start_address, 4), LOG_VGA); break; case 0xE: dbg_log("3D5 / cursor address hi write: " + h(value), LOG_VGA); this.cursor_address = this.cursor_address & 0xFF | value << 8; this.update_cursor(); break; case 0xF: dbg_log("3D5 / cursor address lo write: " + h(value), LOG_VGA); this.cursor_address = this.cursor_address & 0xFF00 | value; this.update_cursor(); break; case 0x12: dbg_log("3D5 / vdisp enable end write: " + h(value), LOG_VGA); if((this.vertical_display_enable_end & 0xFF) !== value) { this.vertical_display_enable_end = (this.vertical_display_enable_end & 0x300) | value; this.update_vga_size(); } break; case 0x13: dbg_log("3D5 / offset register write: " + h(value), LOG_VGA); if(this.offset_register !== value) { this.offset_register = value; this.update_vga_size(); if(~this.crtc_mode & 0x3) { // Address substitution implementation depends on the // virtual width, so the pixel buffer is invalidated. this.complete_replot(); } } break; case 0x14: dbg_log("3D5 / underline location write: " + h(value), LOG_VGA); if(this.underline_location_register !== value) { var previous_underline = this.underline_location_register; this.underline_location_register = value; this.update_vga_size(); if((previous_underline ^ value) & 0x40) { // Doubleword addressing changed. Pixel buffer invalidated. this.complete_replot(); } } break; case 0x15: dbg_log("3D5 / vertical blank start write: " + h(value), LOG_VGA); if((this.vertical_blank_start & 0xFF) !== value) { this.vertical_blank_start = (this.vertical_blank_start & 0x300) | value; this.update_vga_size(); } break; case 0x17: dbg_log("3D5 / crtc mode write: " + h(value), LOG_VGA); if(this.crtc_mode !== value) { var previous_mode = this.crtc_mode; this.crtc_mode = value; this.update_vga_size(); if((previous_mode ^ value) & 0x43) { // Word/byte addressing changed or address substitution changed. // Pixel buffer invalidated. this.complete_replot(); } } break; case 0x18: dbg_log("3D5 / line compare write: " + h(value), LOG_VGA); this.line_compare = (this.line_compare & 0x300) | value; this.update_layers(); break; default: if(this.index_crtc < this.crtc.length) { this.crtc[this.index_crtc] = value; } dbg_log("3D5 / CRTC write " + h(this.index_crtc) + ": " + h(value), LOG_VGA); } }; VGAScreen.prototype.port3D5_read = function() { dbg_log("3D5 read " + h(this.index_crtc), LOG_VGA); switch(this.index_crtc) { case 0x1: return this.horizontal_display_enable_end; case 0x2: return this.horizontal_blank_start; case 0x7: return (this.vertical_display_enable_end >> 7 & 0x2) | (this.vertical_blank_start >> 5 & 0x8) | (this.line_compare >> 4 & 0x10) | (this.vertical_display_enable_end >> 3 & 0x40); case 0x8: return this.preset_row_scan; case 0x9: return this.max_scan_line; case 0xA: return this.cursor_scanline_start; case 0xB: return this.cursor_scanline_end; case 0xC: return this.start_address & 0xFF; case 0xD: return this.start_address >> 8; case 0xE: return this.cursor_address >> 8; case 0xF: return this.cursor_address & 0xFF; case 0x12: return this.vertical_display_enable_end & 0xFF; case 0x13: return this.offset_register; case 0x14: return this.underline_location_register; case 0x15: return this.vertical_blank_start & 0xFF; case 0x17: return this.crtc_mode; case 0x18: return this.line_compare & 0xFF; } if(this.index_crtc < this.crtc.length) { return this.crtc[this.index_crtc]; } else { return 0; } }; VGAScreen.prototype.port3DA_read = function() { dbg_log("3DA read - status 1 and clear attr index", LOG_VGA); var value = this.port_3DA_value; // Status register, bit 3 set by update_vertical_retrace // during screen-fill-buffer if(!this.graphical_mode) { // But screen-fill-buffer may not get triggered in text mode // so toggle it manually here if(this.port_3DA_value & 1) { this.port_3DA_value ^= 8; } this.port_3DA_value ^= 1; } else { this.port_3DA_value ^= 1; this.port_3DA_value &= 1; } this.attribute_controller_index = -1; return value; }; VGAScreen.prototype.svga_bytes_per_line = function() { var bits = this.svga_bpp === 15 ? 16 : this.svga_bpp; return this.svga_width * bits / 8; }; VGAScreen.prototype.port1CE_write = function(value) { this.dispi_index = value; }; VGAScreen.prototype.port1CF_write = function(value) { dbg_log("1CF / dispi write " + h(this.dispi_index) + ": " + h(value), LOG_VGA); switch(this.dispi_index) { case 1: this.svga_width = value; if(this.svga_width > MAX_XRES) { dbg_log("svga_width reduced from " + this.svga_width + " to " + MAX_XRES, LOG_VGA); this.svga_width = MAX_XRES; } break; case 2: this.svga_height = value; if(this.svga_height > MAX_YRES) { dbg_log("svga_height reduced from " + this.svga_height + " to " + MAX_YRES, LOG_VGA); this.svga_height = MAX_YRES; } break; case 3: this.svga_bpp = value; break; case 4: // enable, options this.svga_enabled = (value & 1) === 1; this.dispi_enable_value = value; break; case 5: this.svga_bank_offset = value << 16; break; case 9: // y offset this.svga_offset = value * this.svga_bytes_per_line(); dbg_log("SVGA offset: " + h(this.svga_offset) + " y=" + h(value), LOG_VGA); this.complete_redraw(); break; default: } if(this.svga_enabled && (!this.svga_width || !this.svga_height)) { dbg_log("SVGA: disabled because of invalid width/height: " + this.svga_width + "x" + this.svga_height, LOG_VGA); this.svga_enabled = false; } dbg_assert(this.svga_bpp !== 4, "unimplemented svga bpp: 4"); dbg_assert(this.svga_bpp !== 15, "unimplemented svga bpp: 15"); dbg_assert(this.svga_bpp === 4 || this.svga_bpp === 8 || this.svga_bpp === 15 || this.svga_bpp === 16 || this.svga_bpp === 24 || this.svga_bpp === 32, "unexpected svga bpp: " + this.svga_bpp); dbg_log("SVGA: enabled=" + this.svga_enabled + ", " + this.svga_width + "x" + this.svga_height + "x" + this.svga_bpp, LOG_VGA); if(this.svga_enabled && this.dispi_index === 4) { this.set_size_graphical(this.svga_width, this.svga_height, this.svga_bpp, this.svga_width, this.svga_height); this.bus.send("screen-set-mode", true); this.graphical_mode = true; this.graphical_mode_is_linear = true; } if(!this.svga_enabled) { this.svga_bank_offset = 0; } this.update_layers(); }; VGAScreen.prototype.port1CF_read = function() { dbg_log("1CF / dispi read " + h(this.dispi_index), LOG_VGA); return this.svga_register_read(this.dispi_index); }; VGAScreen.prototype.svga_register_read = function(n) { switch(n) { case 0: // id return 0xB0C0; case 1: return this.dispi_enable_value & 2 ? MAX_XRES : this.svga_width; case 2: return this.dispi_enable_value & 2 ? MAX_YRES : this.svga_height; case 3: return this.dispi_enable_value & 2 ? MAX_BPP : this.svga_bpp; case 4: return this.dispi_enable_value; case 5: return this.svga_bank_offset >>> 16; case 6: // virtual width if(this.screen_width) { return this.screen_width; } else { return 1; // seabios/windows98 divide exception } break; case 8: // x offset return 0; case 0x0A: // memory size in 64 kilobyte banks return this.vga_memory_size / VGA_BANK_SIZE | 0; } return 0xFF; }; /** * Transfers graphics from VGA Planes to the Pixel Buffer * VGA Planes represent data stored on actual hardware. * Pixel Buffer caches the 4-bit or 8-bit color indices for each pixel. */ VGAScreen.prototype.vga_replot = function() { // Round to multiple of 8 towards extreme var start = this.diff_plot_min & ~0xF; var end = Math.min((this.diff_plot_max | 0xF), VGA_PIXEL_BUFFER_SIZE - 1); var addr_shift = this.vga_addr_shift_count(); var addr_substitution = ~this.crtc_mode & 0x3; var shift_mode = this.planar_mode & 0x60; var pel_width = this.attribute_mode & 0x40; for(var pixel_addr = start; pixel_addr <= end;) { var addr = pixel_addr >>> addr_shift; if(addr_substitution) { var row = pixel_addr / this.virtual_width | 0; var col = pixel_addr - this.virtual_width * row; switch(addr_substitution) { case 0x1: // Alternating rows using bit 13 // Assumes max scan line = 1 addr = (row & 0x1) << 13; row >>>= 1; break; case 0x2: // Alternating rows using bit 14 // Assumes max scan line = 3 addr = (row & 0x1) << 14; row >>>= 1; break; case 0x3: // Cycling through rows using bit 13 and 14 // Assumes max scan line = 3 addr = (row & 0x3) << 13; row >>>= 2; break; } addr |= (row * this.virtual_width + col >>> addr_shift) + this.start_address; } var byte0 = this.plane0[addr]; var byte1 = this.plane1[addr]; var byte2 = this.plane2[addr]; var byte3 = this.plane3[addr]; var shift_loads = new Uint8Array(8); switch(shift_mode) { // Planar Shift Mode // See http://www.osdever.net/FreeVGA/vga/vgaseq.htm case 0x00: // Shift these, so that the bits for the color are in // the correct position in the for loop byte0 <<= 0; byte1 <<= 1; byte2 <<= 2; byte3 <<= 3; for(var i = 7; i >= 0; i--) { shift_loads[7 - i] = byte0 >> i & 1 | byte1 >> i & 2 | byte2 >> i & 4 | byte3 >> i & 8; } break; // Packed Shift Mode, aka Interleaved Shift Mode // Video Modes 4h and 5h case 0x20: shift_loads[0] = (byte0 >> 6 & 0x3) | (byte2 >> 4 & 0xC); shift_loads[1] = (byte0 >> 4 & 0x3) | (byte2 >> 2 & 0xC); shift_loads[2] = (byte0 >> 2 & 0x3) | (byte2 >> 0 & 0xC); shift_loads[3] = (byte0 >> 0 & 0x3) | (byte2 << 2 & 0xC); shift_loads[4] = (byte1 >> 6 & 0x3) | (byte3 >> 4 & 0xC); shift_loads[5] = (byte1 >> 4 & 0x3) | (byte3 >> 2 & 0xC); shift_loads[6] = (byte1 >> 2 & 0x3) | (byte3 >> 0 & 0xC); shift_loads[7] = (byte1 >> 0 & 0x3) | (byte3 << 2 & 0xC); break; // 256-Color Shift Mode // Video Modes 13h and unchained 256 color case 0x40: case 0x60: shift_loads[0] = byte0 >> 4 & 0xF; shift_loads[1] = byte0 >> 0 & 0xF; shift_loads[2] = byte1 >> 4 & 0xF; shift_loads[3] = byte1 >> 0 & 0xF; shift_loads[4] = byte2 >> 4 & 0xF; shift_loads[5] = byte2 >> 0 & 0xF; shift_loads[6] = byte3 >> 4 & 0xF; shift_loads[7] = byte3 >> 0 & 0xF; break; } if(pel_width) { // Assemble from two sets of 4 bits. for(var i = 0, j = 0; i < 4; i++, pixel_addr++, j += 2) { this.pixel_buffer[pixel_addr] = (shift_loads[j] << 4) | shift_loads[j + 1]; } } else { for(var i = 0; i < 8; i++, pixel_addr++) { this.pixel_buffer[pixel_addr] = shift_loads[i]; } } } }; /** * Transfers graphics from Pixel Buffer to Destination Image Buffer. * The 4-bit/8-bit color indices in the Pixel Buffer are passed through * the internal palette (dac_map) and the DAC palette (vga256_palette) to * obtain the final 32 bit color that the Canvas API uses. */ VGAScreen.prototype.vga_redraw = function() { var start = this.diff_addr_min; var end = Math.min(this.diff_addr_max, VGA_PIXEL_BUFFER_SIZE - 1); var buffer = this.dest_buffer; // Closure compiler if(!buffer) return; var mask = 0xFF; var colorset = 0x00; if(this.attribute_mode & 0x80) { // Palette bits 5/4 select mask &= 0xCF; colorset |= this.color_select << 4 & 0x30; } if(this.attribute_mode & 0x40) { // 8 bit mode for(var pixel_addr = start; pixel_addr <= end; pixel_addr++) { var color256 = (this.pixel_buffer[pixel_addr] & mask) | colorset; var color = this.vga256_palette[color256]; buffer[pixel_addr] = color & 0xFF00 | color << 16 | color >> 16 | 0xFF000000; } } else { // 4 bit mode // Palette bits 7/6 select mask &= 0x3F; colorset |= this.color_select << 4 & 0xC0; for(var pixel_addr = start; pixel_addr <= end; pixel_addr++) { var color16 = this.pixel_buffer[pixel_addr] & this.color_plane_enable; var color256 = (this.dac_map[color16] & mask) | colorset; var color = this.vga256_palette[color256]; buffer[pixel_addr] = color & 0xFF00 | color << 16 | color >> 16 | 0xFF000000; } } }; VGAScreen.prototype.screen_fill_buffer = function() { if(!this.graphical_mode) { // text mode // Update retrace behaviour anyway - programs waiting for signal before // changing to graphical mode this.update_vertical_retrace(); return; } if(!this.dest_buffer) { dbg_log("Cannot fill buffer: No destination buffer", LOG_VGA); // Update retrace behaviour anyway this.update_vertical_retrace(); return; } if(this.diff_addr_max < this.diff_addr_min && this.diff_plot_max < this.diff_plot_min) { // No pixels to update this.bus.send("screen-fill-buffer-end", this.layers); this.update_vertical_retrace(); return; } if(this.svga_enabled) { var bpp = this.svga_bpp; var buffer = this.dest_buffer; var start = this.diff_addr_min; var end = this.diff_addr_max; switch(bpp) { case 32: var start_pixel = (start - this.svga_offset) >> 2; var end_pixel = ((end - this.svga_offset) >> 2) + 1; var addr = start >> 2; for(var i = start_pixel; i < end_pixel; i++) { var dword = this.svga_memory32[addr++]; buffer[i] = dword << 16 | dword >> 16 & 0xFF | dword & 0xFF00 | 0xFF000000; } break; case 24: start -= start % 3; end += 3 - end % 3; dbg_assert(this.svga_offset % 3 === 0); var start_pixel = (start - this.svga_offset) / 3 | 0; var end_pixel = ((end - this.svga_offset) / 3 | 0) + 1; var addr = start; for(var i = start_pixel; addr < end; i++) { var red = this.svga_memory[addr++]; var green = this.svga_memory[addr++]; var blue = this.svga_memory[addr++]; buffer[i] = red << 16 | green << 8 | blue | 0xFF000000; } break; case 16: var start_pixel = (start - this.svga_offset) >> 1; var end_pixel = ((end - this.svga_offset) >> 1) + 1; var addr = start >> 1; for(var i = start_pixel; i < end_pixel; i++) { var word = this.svga_memory16[addr++]; var blue = (word >> 11) * 0xFF / 0x1F | 0; var green = (word >> 5 & 0x3F) * 0xFF / 0x3F | 0; var red = (word & 0x1F) * 0xFF / 0x1F | 0; buffer[i] = red << 16 | green << 8 | blue | 0xFF000000; } break; case 8: var start_pixel = start - this.svga_offset; var end_pixel = end - this.svga_offset + 1; var addr = start; for(var i = start; i <= end; i++) { var color = this.vga256_palette[this.svga_memory[addr++]]; buffer[i] = color & 0xFF00 | color << 16 | color >> 16 | 0xFF000000; } break; default: dbg_assert(false, "Unsupported BPP: " + bpp); } var min_y = start_pixel / this.svga_width | 0; var max_y = end_pixel / this.svga_width | 0; this.bus.send("screen-fill-buffer-end", [{ screen_x: 0, screen_y: min_y, buffer_x: 0, buffer_y: min_y, buffer_width: this.svga_width, buffer_height: max_y - min_y + 1, }]); } else { this.vga_replot(); this.vga_redraw(); this.bus.send("screen-fill-buffer-end", this.layers); } this.reset_diffs(); this.update_vertical_retrace(); };