Skip to content

Commit

Permalink
Add VGA graphical text mode
Browse files Browse the repository at this point in the history
  • Loading branch information
chschnell authored and copy committed Sep 22, 2024
1 parent c1ef714 commit f92e6b4
Show file tree
Hide file tree
Showing 8 changed files with 747 additions and 23 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ CARGO_FLAGS_SAFE=\
CARGO_FLAGS=$(CARGO_FLAGS_SAFE) -C target-feature=+bulk-memory -C target-feature=+multivalue -C target-feature=+simd128

CORE_FILES=const.js config.js io.js main.js lib.js buffer.js ide.js pci.js floppy.js \
memory.js dma.js pit.js vga.js ps2.js rtc.js uart.js \
memory.js dma.js pit.js vga.js vga_text.js ps2.js rtc.js uart.js \
acpi.js apic.js ioapic.js \
state.js ne2k.js sb16.js virtio.js virtio_console.js virtio_net.js \
bus.js log.js cpu.js debug.js \
Expand Down
1 change: 1 addition & 0 deletions debug.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<script src="src/dma.js"></script>
<script src="src/pit.js"></script>
<script src="src/vga.js"></script>
<script src="src/vga_text.js"></script>
<script src="src/ps2.js"></script>
<script src="src/rtc.js"></script>
<script src="src/uart.js"></script>
Expand Down
5 changes: 4 additions & 1 deletion src/browser/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -1694,7 +1694,10 @@
}

const emulator = new V86({
screen_container: $("screen_container"),
screen: {
container: $("screen_container"),
use_graphical_text: false,
},
net_device: {
type: settings.net_device_type || "ne2k",
relay_url: settings.relay_url,
Expand Down
18 changes: 14 additions & 4 deletions src/browser/screen.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function ScreenAdapter(options, screen_fill_buffer)
changed_rows,

// are we in graphical mode now?
is_graphical = false,
is_graphical = !!options.use_graphical_text,

// Index 0: ASCII code
// Index 1: Blinking
Expand Down Expand Up @@ -134,9 +134,19 @@ function ScreenAdapter(options, screen_fill_buffer)

this.init = function()
{
// not necessary, because this gets initialized by the bios early,
// but nicer to look at
this.set_size_text(80, 25);
// initialize with mode and size presets as expected by the bios
// to avoid flickering during early startup
this.set_mode(is_graphical);

if(is_graphical)
{
// assume 80x25 with 9x16 font
this.set_size_graphical(720, 400, 720, 400);
}
else
{
this.set_size_text(80, 25);
}

this.timer();
};
Expand Down
4 changes: 3 additions & 1 deletion src/browser/starter.js
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ V86.prototype.continue_init = async function(emulator, options)
settings.cpuid_level = options.cpuid_level;
settings.virtio_console = options.virtio_console;
settings.virtio_net = options.virtio_net;
settings.screen_options = options.screen_options;

const relay_url = options.network_relay_url || options.net_device && options.net_device.relay_url;
if(relay_url)
Expand Down Expand Up @@ -345,13 +346,14 @@ V86.prototype.continue_init = async function(emulator, options)

if(screen_options.container)
{
this.screen_adapter = new ScreenAdapter(screen_options, () => this.v86.cpu.devices.vga.screen_fill_buffer());
this.screen_adapter = new ScreenAdapter(screen_options, () => this.v86.cpu.devices.vga && this.v86.cpu.devices.vga.screen_fill_buffer());
}
else
{
this.screen_adapter = new DummyScreenAdapter();
}
settings.screen = this.screen_adapter;
settings.screen_options = screen_options;

if(options.serial_container)
{
Expand Down
2 changes: 1 addition & 1 deletion src/cpu.js
Original file line number Diff line number Diff line change
Expand Up @@ -940,7 +940,7 @@ CPU.prototype.init = function(settings, device_bus)

this.devices.dma = new DMA(this);

this.devices.vga = new VGAScreen(this, device_bus, settings.screen, settings.vga_memory_size || 8 * 1024 * 1024);
this.devices.vga = new VGAScreen(this, device_bus, settings.screen, settings.vga_memory_size || 8 * 1024 * 1024, settings.screen_options || {});

this.devices.ps2 = new PS2(this, device_bus);

Expand Down
110 changes: 95 additions & 15 deletions src/vga.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,9 @@ const VGA_HOST_MEMORY_SPACE_SIZE = Uint32Array.from([
* @param {BusConnector} bus
* @param {ScreenAdapter|DummyScreenAdapter} screen
* @param {number} vga_memory_size
* @param {Object} options
*/
function VGAScreen(cpu, bus, screen, vga_memory_size)
function VGAScreen(cpu, bus, screen, vga_memory_size, options)
{
this.cpu = cpu;

Expand Down Expand Up @@ -166,7 +167,6 @@ function VGAScreen(cpu, bus, screen, vga_memory_size)

/** @type {boolean} */
this.graphical_mode = false;
this.screen.set_mode(this.graphical_mode);

/*
* VGA palette containing 256 colors for video mode 13, svga 8bpp, etc.
Expand Down Expand Up @@ -377,9 +377,41 @@ function VGAScreen(cpu, bus, screen, vga_memory_size)
(addr, value) => this.vga_memory_write(addr, value),
);

if(options.use_graphical_text)
{
this.graphical_text = new GraphicalText(this);
}

cpu.devices.pci.register_device(this);
}

VGAScreen.prototype.grab_text_content = function(keep_whitespace)
{
var addr = this.start_address << 1;
const split_screen_row = this.scan_line_to_screen_row(this.line_compare);
const row_offset = Math.max(0, (this.offset_register * 2 - this.max_cols) * 2);
const text_rows = [];

for(var row = 0; row < this.max_rows; row++)
{
if(row === split_screen_row)
{
addr = 0;
}

let line = "";
for(var col = 0; col < this.max_cols; col++, addr += 2)
{
line += String.fromCodePoint(this.vga_memory[addr]);
}

text_rows.push(keep_whitespace ? line : line.trimEnd());
addr += row_offset;
}

return text_rows;
};

VGAScreen.prototype.get_state = function()
{
var state = [];
Expand Down Expand Up @@ -519,7 +551,7 @@ VGAScreen.prototype.set_state = function(state)
this.dac_mask = state[62] === undefined ? 0xFF : state[62];
this.character_map_select = state[63] === undefined ? 0 : state[63];

this.screen.set_mode(this.graphical_mode);
this.screen.set_mode(this.graphical_mode || !!this.graphical_text);

if(this.graphical_mode)
{
Expand Down Expand Up @@ -823,6 +855,11 @@ VGAScreen.prototype.apply_bitmask = function(data_dword, bitmask_dword)

VGAScreen.prototype.text_mode_redraw = function()
{
if(this.graphical_text)
{
return;
}

const split_screen_row = this.scan_line_to_screen_row(this.line_compare);
const row_offset = Math.max(0, (this.offset_register * 2 - this.max_cols) * 2);
const blink_flag = this.attribute_mode & 1 << 3;
Expand Down Expand Up @@ -898,15 +935,23 @@ VGAScreen.prototype.vga_memory_write_text_mode = function(addr, value)
chr = value;
color = this.vga_memory[addr | 1];
}

const blink_flag = this.attribute_mode & 1 << 3;
const blinking = blink_flag && (color & 1 << 7);
const bg_color_mask = blink_flag ? 7 : 0xF;

this.bus.send("screen-put-char", [row, col, chr]);

this.screen.put_char(row, col, chr, blinking,
this.vga256_palette[this.dac_mask & this.dac_map[color >> 4 & bg_color_mask]],
this.vga256_palette[this.dac_mask & this.dac_map[color & 0xF]]);
if(this.graphical_text)
{
this.graphical_text.invalidate_row(row);
}
else
{
this.screen.put_char(row, col, chr, blinking,
this.vga256_palette[this.dac_mask & this.dac_map[color >> 4 & bg_color_mask]],
this.vga256_palette[this.dac_mask & this.dac_map[color & 0xF]]);
}
};

VGAScreen.prototype.update_cursor = function()
Expand All @@ -927,9 +972,16 @@ VGAScreen.prototype.update_cursor = function()
}

dbg_assert(row >= 0 && col >= 0);

// NOTE: is allowed to be out of bounds
this.screen.update_cursor(row, col);

if(this.graphical_text)
{
this.graphical_text.set_cursor_pos(row, col);
}
else
{
this.screen.update_cursor(row, col);
}
};

VGAScreen.prototype.complete_redraw = function()
Expand Down Expand Up @@ -1122,8 +1174,16 @@ VGAScreen.prototype.set_size_text = function(cols_count, rows_count)
this.max_cols = cols_count;
this.max_rows = rows_count;

this.screen.set_size_text(cols_count, rows_count);
this.bus.send("screen-set-size", [cols_count, rows_count, 0]);

if(this.graphical_text)
{
this.graphical_text.set_size(rows_count, cols_count);
}
else
{
this.screen.set_size_text(cols_count, rows_count);
}
};

VGAScreen.prototype.set_size_graphical = function(width, height, virtual_width, virtual_height, bpp)
Expand Down Expand Up @@ -1341,7 +1401,15 @@ VGAScreen.prototype.update_cursor_scanline = function()
const start = Math.min(max, this.cursor_scanline_start & 0x1F);
const end = Math.min(max, this.cursor_scanline_end & 0x1F);
const visible = !disabled && start < end;
this.screen.update_cursor_scanline(start, end, visible);

if(this.graphical_text)
{
this.graphical_text.set_cursor_attr(start, end, visible);
}
else
{
this.screen.update_cursor_scanline(start, end, visible);
}
};

/**
Expand Down Expand Up @@ -1388,11 +1456,11 @@ VGAScreen.prototype.port3C0_write = function(value)
var previous_mode = this.attribute_mode;
this.attribute_mode = value;

var is_graphical = (value & 0x1) > 0;
const is_graphical = (value & 0x1) !== 0;
if(!this.svga_enabled && this.graphical_mode !== is_graphical)
{
this.graphical_mode = is_graphical;
this.screen.set_mode(this.graphical_mode);
this.screen.set_mode(this.graphical_mode || !!this.graphical_text);
}

if((previous_mode ^ value) & 0x40)
Expand Down Expand Up @@ -1528,6 +1596,7 @@ VGAScreen.prototype.port3C5_write = function(value)
if(this.graphical_text && previous_plane_write_bm !== 0xf && (previous_plane_write_bm & 0x4) && !(this.plane_write_bm & 0x4))
{
// End of font plane 2 write access (initial value of plane_write_bm assumed to be 0xf)
this.graphical_text.invalidate_font_shape();
}
break;
case 0x03:
Expand All @@ -1536,6 +1605,7 @@ VGAScreen.prototype.port3C5_write = function(value)
this.character_map_select = value;
if(this.graphical_text && previous_character_map_select !== this.character_map_select)
{
this.graphical_text.set_character_map(this.character_map_select);
}
break;
case 0x04:
Expand Down Expand Up @@ -2425,9 +2495,19 @@ 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();
if(this.graphical_text)
{
const image_data = this.graphical_text.render();
this.screen.update_buffer([{
image_data: image_data,
screen_x: 0,
screen_y: 0,
buffer_x: 0,
buffer_y: 0,
buffer_width: image_data.width,
buffer_height: image_data.height
}]);
}
return;
}

Expand Down
Loading

0 comments on commit f92e6b4

Please sign in to comment.