#![allow(clippy::comparison_chain)]
use std::sync::Arc;
use std::thread;
use std::time::{Duration, Instant};
use vgaemu::screen;
use vgaemu::{CRTReg, GCReg, GeneralReg, SCReg, AttributeReg};
const LOGICAL_SCREEN_WIDTH: usize = 672 / 8; const LOGICAL_SCREEN_HEIGHT: usize = 384; const PAGE1: usize = 1; const PAGE0_OFFSET: usize = 0; const PAGE1_OFFSET: usize = LOGICAL_SCREEN_WIDTH * LOGICAL_SCREEN_HEIGHT; const BALL_WIDTH: usize = 24 / 8; const BALL_HEIGHT: usize = 24; const BLANK_OFFSET: usize = PAGE1_OFFSET * 2; const BALL_OFFSET: usize = BLANK_OFFSET + (BALL_WIDTH * BALL_HEIGHT); const NUM_BALLS: usize = 4;
const VSYNC_MASK: u8 = 0x08;
const DE_MASK: u8 = 0x01;
const BALL_0_CONTROL: [i16; 13] = [10, 1, 4, 10, -1, 4, 10, -1, -4, 10, 1, -4, 0];
const BALL_1_CONTORL: [i16; 13] = [12, -1, 1, 28, -1, -1, 12, 1, -1, 28, 1, 1, 0];
const BALL_2_CONTORL: [i16; 13] = [20, 0, -1, 40, 0, 1, 20, 0, -1, 0, 0, 0, 0];
const BALL_3_CONTORL: [i16; 13] = [8, 1, 0, 52, -1, 0, 44, 1, 0, 0, 0, 0, 0];
const BALL_CONTROL_STRING: [[i16; 13]; 4] = [
BALL_0_CONTROL,
BALL_1_CONTORL,
BALL_2_CONTORL,
BALL_3_CONTORL,
];
const PANNING_CONTROL_STRING : [i16; 13] = [32, 1, 0, 34, 0, 1, 32, -1, 0, 34, 0, -1, 0];
const UPDATE_RATE_SAMPLES: usize = 1000;
const POLL_WAIT_MICROS : u64 = 500;
struct PanningState {
hpan: i16,
panning_rep: i16,
panning_x_inc: i16,
panning_y_inc: i16,
panning_start_offset: usize,
panning_control: usize,
}
fn initial_panning_state() -> PanningState {
PanningState {
hpan: 0,
panning_rep: 1,
panning_x_inc: 1,
panning_y_inc: 1,
panning_start_offset: 0,
panning_control: 0,
}
}
pub fn main() {
let vga = vgaemu::new(0x10);
draw_border(&vga, PAGE0_OFFSET);
draw_border(&vga, PAGE1_OFFSET);
let plane_1_data = vec![
0x00, 0x3c, 0x00, 0x01, 0xff, 0x80, 0x07, 0xff, 0xe0, 0x0f, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xfe, 0x3f, 0xff, 0xfc, 0x3f, 0xff, 0xfc, 0x1f, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
let plane_2_data = vec![
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xf8, 0x3f, 0xff, 0xfc, 0x3f, 0xff, 0xfc, 0x7f, 0xff, 0xfe, 0x7f, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xf0, 0x07, 0xff, 0xe0, 0x01, 0xff, 0x80, 0x00, 0x3c, 0x00, ];
let plane_3_data = vec![
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xfe, 0x7f, 0xff, 0xfe, 0x3f, 0xff, 0xfc, 0x3f, 0xff, 0xfc, 0x1f, 0xff, 0xf8, 0x0f, 0xff, 0xf0, 0x07, 0xff, 0xe0, 0x01, 0xff, 0x80, 0x00, 0x3c, 0x00, ];
let plane_4_data = vec![
0x00, 0x3c, 0x00, 0x01, 0xff, 0x80, 0x07, 0xff, 0xe0, 0x0f, 0xff, 0xf0, 0x1f, 0xff, 0xf8, 0x3f, 0xff, 0xfc, 0x3f, 0xff, 0xfc, 0x7f, 0xff, 0xfe, 0x7f, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xfe, 0x7f, 0xff, 0xfe, 0x3f, 0xff, 0xfc, 0x3f, 0xff, 0xfc, 0x1f, 0xff, 0xf8, 0x0f, 0xff, 0xf0, 0x07, 0xff, 0xe0, 0x01, 0xff, 0x80, 0x00, 0x3c, 0x00, ];
vga.set_sc_data(SCReg::MapMask, 0x01);
vga.write_mem_chunk(BALL_OFFSET, &plane_1_data);
vga.set_sc_data(SCReg::MapMask, 0x02);
vga.write_mem_chunk(BALL_OFFSET, &plane_2_data);
vga.set_sc_data(SCReg::MapMask, 0x04);
vga.write_mem_chunk(BALL_OFFSET, &plane_3_data);
vga.set_sc_data(SCReg::MapMask, 0x08);
vga.write_mem_chunk(BALL_OFFSET, &plane_4_data);
vga.set_sc_data(SCReg::MapMask, 0x0F);
for i in 0..(BALL_WIDTH * BALL_HEIGHT) {
vga.write_mem(BLANK_OFFSET + i, 0x00);
}
vga.set_crt_data(CRTReg::Offset, (LOGICAL_SCREEN_WIDTH / 2) as u8);
let mut gc_mode = vga.get_gc_data(GCReg::GraphicsMode);
gc_mode &= 0xFC;
gc_mode |= 0x01;
vga.set_gc_data(GCReg::GraphicsMode, gc_mode);
let vga_m = Arc::new(vga);
let vga_t = vga_m.clone();
thread::spawn(move || {
let mut ball_x = [15, 50, 40, 70];
let mut ball_y = [40, 200, 110, 300];
let mut last_ball_x = [15, 50, 40, 70];
let mut last_ball_y = [40, 200, 110, 300];
let mut ball_x_inc = [1, 1, 1, 1];
let mut ball_y_inc = [8, 8, 8, 8];
let mut ball_rep = [1, 1, 1, 1];
let mut ball_control = [0, 0, 0, 0];
let mut current_page = PAGE1;
let mut current_page_offset = PAGE1_OFFSET;
let mut panning_state = initial_panning_state();
let mut updates = Vec::with_capacity(UPDATE_RATE_SAMPLES);
for _ in 0..UPDATE_RATE_SAMPLES {
updates.push(Instant::now());
}
loop {
for bx in (0..NUM_BALLS).rev() {
draw_ball(
&vga_t,
BLANK_OFFSET,
current_page_offset,
last_ball_x[bx],
last_ball_y[bx],
);
let mut ax = ball_x[bx];
last_ball_x[bx] = ax;
ax = ball_y[bx];
last_ball_y[bx] = ax;
ball_rep[bx] -= 1;
if ball_rep[bx] == 0 {
let mut bc_ptr = ball_control[bx];
if BALL_CONTROL_STRING[bx][bc_ptr] == 0 {
bc_ptr = 0;
}
ball_rep[bx] = BALL_CONTROL_STRING[bx][bc_ptr];
ball_x_inc[bx] = BALL_CONTROL_STRING[bx][bc_ptr + 1];
ball_y_inc[bx] = BALL_CONTROL_STRING[bx][bc_ptr + 2];
ball_control[bx] = bc_ptr + 3;
}
ball_x[bx] = (ball_x[bx] as i16 + ball_x_inc[bx]) as usize;
ball_y[bx] = (ball_y[bx] as i16 + ball_y_inc[bx]) as usize;
draw_ball(
&vga_t,
BALL_OFFSET,
current_page_offset,
ball_x[bx],
ball_y[bx],
);
}
adjust_panning(&mut panning_state);
wait_display_enable(&vga_t);
let addr_parts = (current_page_offset + panning_state.panning_start_offset).to_le_bytes();
vga_t.set_crt_data(CRTReg::StartAdressLow, addr_parts[0]);
vga_t.set_crt_data(CRTReg::StartAdressHigh, addr_parts[1]);
wait_vsync(&vga_t);
vga_t.set_attribute_reg(AttributeReg::HorizontalPixelPanning, panning_state.hpan as u8);
current_page ^= 1;
if current_page == 0 {
current_page_offset = PAGE0_OFFSET;
} else {
current_page_offset = PAGE1_OFFSET
}
}
});
let options : screen::Options = vgaemu::screen::Options { show_frame_rate: true, ..Default::default() };
screen::start(vga_m, options).unwrap();
}
fn draw_ball(vga: &vgaemu::VGA, src_offset: usize, page_offset: usize, x: usize, y: usize) {
let offset = page_offset + (y * LOGICAL_SCREEN_WIDTH + x);
let mut si = src_offset;
let mut di = offset;
for _ in 0..BALL_HEIGHT {
let mut dix = di;
for _ in 0..BALL_WIDTH {
vga.read_mem(si);
vga.write_mem(dix, 0x00);
si += 1;
dix += 1;
}
di += LOGICAL_SCREEN_WIDTH;
}
}
fn draw_border(vga: &vgaemu::VGA, offset: usize) {
let mut di = offset;
for _ in 0..(LOGICAL_SCREEN_HEIGHT / 16) {
vga.set_sc_data(SCReg::MapMask, 0x0c);
draw_border_block(vga, di);
di += LOGICAL_SCREEN_WIDTH * 8;
vga.set_sc_data(SCReg::MapMask, 0x0e);
draw_border_block(vga, di);
di += LOGICAL_SCREEN_WIDTH * 8;
}
di = offset + LOGICAL_SCREEN_WIDTH - 1;
for _ in 0..(LOGICAL_SCREEN_HEIGHT / 16) {
vga.set_sc_data(SCReg::MapMask, 0x0e);
draw_border_block(vga, di);
di += LOGICAL_SCREEN_WIDTH * 8;
vga.set_sc_data(SCReg::MapMask, 0x0c);
draw_border_block(vga, di);
di += LOGICAL_SCREEN_WIDTH * 8;
}
di = offset;
for _ in 0..((LOGICAL_SCREEN_WIDTH - 2) / 2) {
di += 1;
vga.set_sc_data(SCReg::MapMask, 0x0e);
draw_border_block(vga, di);
di += 1;
vga.set_sc_data(SCReg::MapMask, 0x0c);
draw_border_block(vga, di);
}
di = offset + (LOGICAL_SCREEN_HEIGHT - 8) * LOGICAL_SCREEN_WIDTH;
for _ in 0..((LOGICAL_SCREEN_WIDTH - 2) / 2) {
di += 1;
vga.set_sc_data(SCReg::MapMask, 0x0e);
draw_border_block(vga, di);
di += 1;
vga.set_sc_data(SCReg::MapMask, 0x0c);
draw_border_block(vga, di);
}
}
fn draw_border_block(vga: &vgaemu::VGA, offset: usize) {
let mut di = offset;
for _ in 0..8 {
vga.write_mem(di, 0xff);
di += LOGICAL_SCREEN_WIDTH;
}
}
fn adjust_panning(state : &mut PanningState) {
state.panning_rep -= 1;
if state.panning_rep <= 0 {
let ax = PANNING_CONTROL_STRING[state.panning_control];
if ax == 0 { state.panning_control = 0;
}
state.panning_rep = PANNING_CONTROL_STRING[state.panning_control];
state.panning_x_inc = PANNING_CONTROL_STRING[state.panning_control+1];
state.panning_y_inc = PANNING_CONTROL_STRING[state.panning_control+2];
state.panning_control += 3;
}
if state.panning_x_inc < 0 {
state.hpan -= 1;
if state.hpan < 0 {
state.hpan = 7;
state.panning_start_offset -= 1;
}
} else if state.panning_x_inc > 0 {
state.hpan += 1;
if state.hpan >= 8 {
state.hpan = 0;
state.panning_start_offset += 1;
}
}
if state.panning_y_inc < 0 {
state.panning_start_offset -= LOGICAL_SCREEN_WIDTH;
} else if state.panning_y_inc > 0 {
state.panning_start_offset += LOGICAL_SCREEN_WIDTH;
}
}
fn wait_display_enable(vga: &Arc<vgaemu::VGA>) {
loop {
let in1 = vga.get_general_reg(GeneralReg::InputStatus1);
if in1 & DE_MASK == 0 {
break;
}
thread::sleep(Duration::from_micros(POLL_WAIT_MICROS));
}
}
fn wait_vsync(vga: &Arc<vgaemu::VGA>) {
loop {
let in1 = vga.get_general_reg(GeneralReg::InputStatus1);
if in1 & VSYNC_MASK != 0 {
break;
}
thread::sleep(Duration::from_micros(POLL_WAIT_MICROS));
}
}