Initial commit
I should've made a git repo for this much sooner, oops :p
This commit is contained in:
commit
9b21b663f4
1881
Cargo.lock
generated
Normal file
1881
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
14
Cargo.toml
Normal file
14
Cargo.toml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
[package]
|
||||||
|
name = "fox32"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["ry"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
log = "0.4"
|
||||||
|
pixels = "0.9.0"
|
||||||
|
rodio = "0.14.0"
|
||||||
|
winit = "0.26"
|
||||||
|
winit_input_helper = "0.11"
|
46
encoding.md
Normal file
46
encoding.md
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
# Encoding
|
||||||
|
```
|
||||||
|
size instr . cond dest src <src> <dest>
|
||||||
|
xx xxxxxx 0 xxx xx xx <8,16,32 bits> <8,16,32 bits>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
# Size table
|
||||||
|
If instruction doesn't allow variable sizes or size was not specified, set size bits to 0b11
|
||||||
|
| | |
|
||||||
|
| :--: | -------------- |
|
||||||
|
| 0b00 | byte (8 bits) |
|
||||||
|
| 0b01 | half (16 bits) |
|
||||||
|
| 0b10 | word (32 bits) |
|
||||||
|
|
||||||
|
# Instruction table
|
||||||
|
| 0x | -0 | -1 | -2 | -3 | -4 | -5 | -6 | -7 | -8 | -9 | -A | -B | -C | -D | -E | -F |
|
||||||
|
| :-: | ---- | ------------- | ------------- | ------------- | ------------- | ------------- | ------------- | -------------- | ---- | ----- | -------------- | --- | --- | --- | --- | --- |
|
||||||
|
| 0- | NOP | ADD[.8,16,32] | MUL[.8,16,32] | AND[.8,16,32] | SLA[.8,16,32] | SRA[.8,16,32] | BSE[.8,16,32] | CMP[.8,16,32] | JMP | RJMP | PUSH[.8,16,32] | IN | ISE | | | |
|
||||||
|
| 1- | HALT | INC[.8,16,32] | POW[.8,16,32] | OR[.8,16,32] | | SRL[.8,16,32] | BCL[.8,16,32] | MOV[.8,16,32] | CALL | RCALL | POP[.8,16,32] | OUT | ICL | | | |
|
||||||
|
| 2- | BRK | SUB[.8,16,32] | DIV[.8,16,32] | XOR[.8,16,32] | ROL[.8,16,32] | ROR[.8,16,32] | BTS[.8,16,32] | MOVZ[.8,16,32] | LOOP | RLOOP | RET | | | | | |
|
||||||
|
| 3- | | DEC[.8,16,32] | REM[.8,16,32] | NOT[.8,16,32] | | | | | | RTA | RETI | | | | | |
|
||||||
|
|
||||||
|
# Condition table
|
||||||
|
| | |
|
||||||
|
| :---: | -------- |
|
||||||
|
| 0b000 | (always) |
|
||||||
|
| 0b001 | IFZ |
|
||||||
|
| 0b010 | IFNZ |
|
||||||
|
| 0b011 | IFC |
|
||||||
|
| 0b100 | IFNC |
|
||||||
|
|
||||||
|
# Destination table
|
||||||
|
| | |
|
||||||
|
| :--: | ------------------- |
|
||||||
|
| 0b00 | register |
|
||||||
|
| 0b01 | register (pointer) |
|
||||||
|
| 0b10 | immediate (pointer) |
|
||||||
|
|
||||||
|
# Source table
|
||||||
|
| | |
|
||||||
|
| :--: | ------------------- |
|
||||||
|
| 0b00 | register |
|
||||||
|
| 0b01 | register (pointer) |
|
||||||
|
| 0b10 | immediate |
|
||||||
|
| 0b11 | immediate (pointer) |
|
2953
src/cpu.rs
Normal file
2953
src/cpu.rs
Normal file
File diff suppressed because it is too large
Load Diff
421
src/main.rs
Normal file
421
src/main.rs
Normal file
|
@ -0,0 +1,421 @@
|
||||||
|
// main.rs
|
||||||
|
|
||||||
|
pub mod cpu;
|
||||||
|
pub mod mouse;
|
||||||
|
use cpu::{Bus, CPU, Memory, Interrupt, IO};
|
||||||
|
use mouse::Mouse;
|
||||||
|
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use std::env;
|
||||||
|
use std::fs::read;
|
||||||
|
//use std::process::exit;
|
||||||
|
use std::sync::{Arc, mpsc, Mutex};
|
||||||
|
use std::io::{stdout, Write};
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
|
use log::error;
|
||||||
|
use pixels::{Pixels, SurfaceTexture};
|
||||||
|
use winit::dpi::LogicalSize;
|
||||||
|
use winit::event::{Event, VirtualKeyCode};
|
||||||
|
use winit::event_loop::{ControlFlow, EventLoop};
|
||||||
|
use winit::window::WindowBuilder;
|
||||||
|
use winit_input_helper::WinitInputHelper;
|
||||||
|
|
||||||
|
use rodio::{OutputStream, buffer::SamplesBuffer, Sink};
|
||||||
|
|
||||||
|
const WIDTH: usize = 640;
|
||||||
|
const HEIGHT: usize = 480;
|
||||||
|
|
||||||
|
pub struct Display {
|
||||||
|
background: Vec<u8>,
|
||||||
|
overlays: Arc<Mutex<Vec<Overlay>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Default)]
|
||||||
|
pub struct Overlay {
|
||||||
|
enabled: bool,
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
x: usize,
|
||||||
|
y: usize,
|
||||||
|
framebuffer_pointer: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IO for Bus {
|
||||||
|
fn read_io(&mut self, port: u32) -> u32 {
|
||||||
|
match port {
|
||||||
|
0x02000000..=0x0200031F => { // overlay port
|
||||||
|
let overlay_lock = self.memory.overlays.lock().unwrap();
|
||||||
|
let overlay_number = (port & 0x000000FF) as usize;
|
||||||
|
let setting = (port & 0x0000FF00) >> 8;
|
||||||
|
|
||||||
|
match setting {
|
||||||
|
0x00 => {
|
||||||
|
// we're reading the position of this overlay
|
||||||
|
let x = overlay_lock[overlay_number].x as u32;
|
||||||
|
let y = overlay_lock[overlay_number].y as u32;
|
||||||
|
(y << 16) | x
|
||||||
|
}
|
||||||
|
0x01 => {
|
||||||
|
// we're reading the size of this overlay
|
||||||
|
let width = overlay_lock[overlay_number].width as u32;
|
||||||
|
let height = overlay_lock[overlay_number].height as u32;
|
||||||
|
(height << 16) | width
|
||||||
|
}
|
||||||
|
0x02 => {
|
||||||
|
// we're reading the framebuffer pointer of this overlay
|
||||||
|
overlay_lock[overlay_number].framebuffer_pointer + 0x02000000
|
||||||
|
}
|
||||||
|
0x03 => {
|
||||||
|
// we're reading the enable status of this overlay
|
||||||
|
overlay_lock[overlay_number].enabled as u32
|
||||||
|
}
|
||||||
|
_ => panic!("invalid overlay control port"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0x02000400..=0x02000401 => { // mouse port
|
||||||
|
let setting = (port & 0x000000FF) as u8;
|
||||||
|
match setting {
|
||||||
|
0x00 => {
|
||||||
|
// we're reading the button states
|
||||||
|
let mut byte: u8 = 0x00;
|
||||||
|
let mut mouse_lock = self.mouse.lock().expect("failed to lock the mouse mutex");
|
||||||
|
if mouse_lock.click {
|
||||||
|
byte |= 0b01;
|
||||||
|
mouse_lock.click = false;
|
||||||
|
}
|
||||||
|
if mouse_lock.held {
|
||||||
|
byte |= 0b10;
|
||||||
|
} else {
|
||||||
|
byte &= !0b10;
|
||||||
|
}
|
||||||
|
byte as u32
|
||||||
|
}
|
||||||
|
0x01 => {
|
||||||
|
// we're reading the position
|
||||||
|
let mouse_lock = self.mouse.lock().expect("failed to lock the mouse mutex");
|
||||||
|
let x = mouse_lock.x as u32;
|
||||||
|
let y = mouse_lock.y as u32;
|
||||||
|
(y << 16) | x
|
||||||
|
}
|
||||||
|
_ => panic!("invalid mouse control port"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn write_io(&mut self, port: u32, word: u32) {
|
||||||
|
match port {
|
||||||
|
0x00000000 => { // terminal output port
|
||||||
|
print!("{}", word as u8 as char);
|
||||||
|
stdout().flush().expect("could not flush stdout");
|
||||||
|
}
|
||||||
|
0x02000000..=0x0200031F => { // overlay port
|
||||||
|
let mut overlay_lock = self.memory.overlays.lock().unwrap();
|
||||||
|
let overlay_number = (port & 0x000000FF) as usize;
|
||||||
|
let setting = (port & 0x0000FF00) >> 8;
|
||||||
|
|
||||||
|
match setting {
|
||||||
|
0x00 => {
|
||||||
|
// we're setting the position of this overlay
|
||||||
|
let x = (word & 0x0000FFFF) as usize;
|
||||||
|
let y = ((word & 0xFFFF0000) >> 16) as usize;
|
||||||
|
overlay_lock[overlay_number].x = x;
|
||||||
|
overlay_lock[overlay_number].y = y;
|
||||||
|
}
|
||||||
|
0x01 => {
|
||||||
|
// we're setting the size of this overlay
|
||||||
|
let width = (word & 0x0000FFFF) as usize;
|
||||||
|
let height = ((word & 0xFFFF0000) >> 16) as usize;
|
||||||
|
overlay_lock[overlay_number].width = width;
|
||||||
|
overlay_lock[overlay_number].height = height;
|
||||||
|
}
|
||||||
|
0x02 => {
|
||||||
|
// we're setting the framebuffer pointer of this overlay
|
||||||
|
if word < 0x02000000 {
|
||||||
|
panic!("overlay framebuffer must be within shared memory");
|
||||||
|
}
|
||||||
|
overlay_lock[overlay_number].framebuffer_pointer = word - 0x02000000;
|
||||||
|
}
|
||||||
|
0x03 => {
|
||||||
|
// we're setting the enable status of this overlay
|
||||||
|
overlay_lock[overlay_number].enabled = word != 0;
|
||||||
|
}
|
||||||
|
_ => panic!("invalid overlay control port"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0x02000320 => { // audio port
|
||||||
|
if word < 0x02000000 {
|
||||||
|
panic!("audio buffer must be within shared memory");
|
||||||
|
}
|
||||||
|
let address = word as usize - 0x02000000;
|
||||||
|
let shared_memory_lock = self.memory.shared_memory.lock().unwrap();
|
||||||
|
|
||||||
|
let length = u32::from_le_bytes(shared_memory_lock[address..address+4].try_into().unwrap()) as usize;
|
||||||
|
let start_address = address + 4;
|
||||||
|
let end_address = start_address + length;
|
||||||
|
|
||||||
|
let audio_data: Vec<i16> = shared_memory_lock[start_address..end_address].to_vec().chunks_exact(2).map(|x| ((x[1] as i16) << 8) | x[0] as i16).collect();
|
||||||
|
let builder = thread::Builder::new().name("audio".to_string());
|
||||||
|
builder.spawn({
|
||||||
|
move || {
|
||||||
|
let buffer = SamplesBuffer::new(1, 44100, audio_data);
|
||||||
|
let (_stream, stream_handle) = OutputStream::try_default().unwrap();
|
||||||
|
let sink = Sink::try_new(&stream_handle).unwrap();
|
||||||
|
sink.append(buffer);
|
||||||
|
sink.sleep_until_end();
|
||||||
|
}
|
||||||
|
}).unwrap();
|
||||||
|
}
|
||||||
|
0x02000400..=0x02000401 => { // mouse port
|
||||||
|
let setting = (port & 0x000000FF) as u8;
|
||||||
|
match setting {
|
||||||
|
0x00 => {
|
||||||
|
// we're setting the button states
|
||||||
|
let mut mouse_lock = self.mouse.lock().expect("failed to lock the mouse mutex");
|
||||||
|
mouse_lock.click = word & (1 << 0) != 0;
|
||||||
|
mouse_lock.held = word & (1 << 1) != 0;
|
||||||
|
}
|
||||||
|
0x01 => {
|
||||||
|
// we're setting the position
|
||||||
|
let mut mouse_lock = self.mouse.lock().expect("failed to lock the mouse mutex");
|
||||||
|
let x = (word & 0x0000FFFF) as u16;
|
||||||
|
let y = ((word & 0xFFFF0000) >> 16) as u16;
|
||||||
|
mouse_lock.x = x;
|
||||||
|
mouse_lock.y = y;
|
||||||
|
}
|
||||||
|
_ => panic!("invalid mouse control port"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => return,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args: Vec<String> = env::args().collect();
|
||||||
|
/*if args.len() != 2 {
|
||||||
|
println!("fox32\nUsage: {} <binary>", &args[0]);
|
||||||
|
exit(1);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
let mut display = Display::new();
|
||||||
|
let mouse = Arc::new(Mutex::new(Mouse::new()));
|
||||||
|
|
||||||
|
// 32 MiB of shared memory
|
||||||
|
// add some "bonus bytes" at the end of memory to prevent dumb errors - (lua was here)
|
||||||
|
// see the ImmediatePtr match arm in read_source() in cpu.rs for more info
|
||||||
|
// basically all immediate pointers read 32 bits, even if the opcode size is smaller
|
||||||
|
// so attempting to read something like the last byte of shared memory (0x03FFFFFF) would previously panic
|
||||||
|
let shared_memory = Arc::new(Mutex::new(vec![0u8; 0x0200000F]));
|
||||||
|
|
||||||
|
let mut cpu = {
|
||||||
|
let cpu_shared_memory = Arc::clone(&shared_memory);
|
||||||
|
let cpu_overlays = Arc::clone(&display.overlays);
|
||||||
|
//let cpu_read_only_memory = include_bytes!("../rom.bin").to_vec();
|
||||||
|
let cpu_read_only_memory = read("rom.bin").unwrap();
|
||||||
|
// 32 MiB of CPU-only memory
|
||||||
|
let memory = Memory::new(0x02000000, cpu_shared_memory, cpu_overlays, cpu_read_only_memory);
|
||||||
|
|
||||||
|
let cpu_mouse = Arc::clone(&mouse);
|
||||||
|
|
||||||
|
let bus = Bus { memory, mouse: cpu_mouse };
|
||||||
|
CPU::new(bus)
|
||||||
|
};
|
||||||
|
|
||||||
|
if args.len() >= 2 {
|
||||||
|
let input_file_name = &args[1];
|
||||||
|
let opcodes = read(input_file_name).unwrap();
|
||||||
|
//println!("{:02X?}", opcodes);
|
||||||
|
let mut address = 0u32;
|
||||||
|
for byte in opcodes.iter() {
|
||||||
|
cpu.bus.memory.write_8(address, *byte);
|
||||||
|
address += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let event_loop = EventLoop::new();
|
||||||
|
let mut input = WinitInputHelper::new();
|
||||||
|
let window = {
|
||||||
|
let size = LogicalSize::new(WIDTH as f64, HEIGHT as f64);
|
||||||
|
WindowBuilder::new()
|
||||||
|
.with_title("fox32")
|
||||||
|
.with_inner_size(size)
|
||||||
|
.with_min_inner_size(size)
|
||||||
|
.build(&event_loop)
|
||||||
|
.unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
window.set_cursor_visible(false);
|
||||||
|
|
||||||
|
let mut pixels = {
|
||||||
|
let window_size = window.inner_size();
|
||||||
|
let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, &window);
|
||||||
|
Pixels::new(WIDTH as u32, HEIGHT as u32, surface_texture).unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
let (interrupt_sender, interrupt_receiver) = mpsc::channel::<Interrupt>();
|
||||||
|
|
||||||
|
let builder = thread::Builder::new().name("cpu".to_string());
|
||||||
|
builder.spawn({
|
||||||
|
move || {
|
||||||
|
loop {
|
||||||
|
while !cpu.halted {
|
||||||
|
if let Ok(interrupt) = interrupt_receiver.try_recv() {
|
||||||
|
cpu.interrupt(interrupt);
|
||||||
|
}
|
||||||
|
cpu.execute_memory_instruction();
|
||||||
|
}
|
||||||
|
if !cpu.interrupts_enabled {
|
||||||
|
// the cpu was halted and interrupts are disabled
|
||||||
|
// at this point, the cpu is dead and cannot resume, break out of the loop
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if let Ok(interrupt) = interrupt_receiver.recv() {
|
||||||
|
cpu.halted = false;
|
||||||
|
cpu.interrupt(interrupt);
|
||||||
|
} else {
|
||||||
|
// sender is closed, break
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!("CPU halted");
|
||||||
|
}
|
||||||
|
}).unwrap();
|
||||||
|
|
||||||
|
event_loop.run(move |event, _, control_flow| {
|
||||||
|
*control_flow = ControlFlow::Poll;
|
||||||
|
|
||||||
|
// draw the current frame
|
||||||
|
if let Event::MainEventsCleared = event {
|
||||||
|
// update internal state and request a redraw
|
||||||
|
|
||||||
|
let mut shared_memory_lock = shared_memory.lock().expect("failed to lock the shared memory mutex");
|
||||||
|
//shared_memory_lock[0x01FFFFFF] = shared_memory_lock[0x01FFFFFF].overflowing_add(1).0; // increment vsync counter
|
||||||
|
let _ = interrupt_sender.send(Interrupt::Request(0xFF)); // vsync interrupt
|
||||||
|
display.update(&mut *shared_memory_lock);
|
||||||
|
drop(shared_memory_lock);
|
||||||
|
window.request_redraw();
|
||||||
|
|
||||||
|
display.draw(pixels.get_frame());
|
||||||
|
if pixels
|
||||||
|
.render()
|
||||||
|
.map_err(|e| error!("pixels.render() failed: {}", e))
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
*control_flow = ControlFlow::Exit;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle input events
|
||||||
|
if input.update(&event) {
|
||||||
|
// close events
|
||||||
|
if input.key_pressed(VirtualKeyCode::Escape) || input.quit() {
|
||||||
|
*control_flow = ControlFlow::Exit;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// resize the window
|
||||||
|
if let Some(size) = input.window_resized() {
|
||||||
|
pixels.resize_surface(size.width, size.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mouse_pixel = input.mouse().map(|(mx, my)| {
|
||||||
|
let (x, y) = pixels.window_pos_to_pixel((mx, my)).unwrap_or_else(|pos| pixels.clamp_pixel_pos(pos));
|
||||||
|
(x as u16, y as u16)
|
||||||
|
}).unwrap_or_default();
|
||||||
|
|
||||||
|
// TODO: allow right click
|
||||||
|
let mut mouse_lock = mouse.lock().expect("failed to lock the mouse mutex");
|
||||||
|
mouse_lock.x = mouse_pixel.0;
|
||||||
|
mouse_lock.y = mouse_pixel.1;
|
||||||
|
mouse_lock.held = input.mouse_held(0);
|
||||||
|
if input.mouse_pressed(0) {
|
||||||
|
println!("Mouse click at {:?}", mouse_pixel);
|
||||||
|
mouse_lock.click = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
background: vec![0; (HEIGHT*WIDTH*4) as usize],
|
||||||
|
overlays: Arc::new(Mutex::new(vec![Overlay { enabled: false, width: 16, height: 16, x: 0, y: 0, framebuffer_pointer: 0 }; 32])),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, shared_memory: &mut [u8]) {
|
||||||
|
let overlay_lock = self.overlays.lock().unwrap();
|
||||||
|
|
||||||
|
for i in 0..(HEIGHT*WIDTH*4) as usize {
|
||||||
|
self.background[i] = shared_memory[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
for index in 0..=31 {
|
||||||
|
if overlay_lock[index].enabled {
|
||||||
|
blit_overlay(&mut self.background, &overlay_lock[index], shared_memory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(&self, frame: &mut [u8]) {
|
||||||
|
for (i, pixel) in frame.chunks_exact_mut(4).enumerate() {
|
||||||
|
//let x = (i % WIDTH as usize) as i16;
|
||||||
|
//let y = (i / WIDTH as usize) as i16;
|
||||||
|
|
||||||
|
let i = i * 4;
|
||||||
|
|
||||||
|
let slice = &self.background[i..i+4];
|
||||||
|
pixel.copy_from_slice(slice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// modified from https://github.com/parasyte/pixels/blob/main/examples/invaders/simple-invaders/src/sprites.rs
|
||||||
|
fn blit_overlay(framebuffer: &mut [u8], overlay: &Overlay, shared_memory: &mut [u8]) {
|
||||||
|
//assert!(overlay.x + overlay.width <= WIDTH);
|
||||||
|
//assert!(overlay.y + overlay.height <= HEIGHT);
|
||||||
|
|
||||||
|
let mut width = overlay.width * 4;
|
||||||
|
let mut height = overlay.height;
|
||||||
|
|
||||||
|
// FIXME: this is a hack, and it only allows overlays to go off-screen on the bottom and right sides
|
||||||
|
// it also completely fucks up the image on the right side :p
|
||||||
|
if overlay.x + overlay.width > WIDTH {
|
||||||
|
let difference = (overlay.x + overlay.width) - WIDTH;
|
||||||
|
width = width - (difference * 4);
|
||||||
|
//println!("width: {}, difference: {}", width, difference);
|
||||||
|
}
|
||||||
|
if overlay.y + overlay.height > HEIGHT {
|
||||||
|
let difference = (overlay.y + overlay.height) - HEIGHT;
|
||||||
|
height = height - difference;
|
||||||
|
//println!("height: {}, difference: {}", height, difference);
|
||||||
|
}
|
||||||
|
|
||||||
|
let overlay_framebuffer = &shared_memory[overlay.framebuffer_pointer as usize..(overlay.framebuffer_pointer+((width as u32)*(height as u32))) as usize];
|
||||||
|
|
||||||
|
let mut overlay_framebuffer_index = 0;
|
||||||
|
for y in 0..height {
|
||||||
|
let index = overlay.x * 4 + overlay.y * WIDTH * 4 + y * WIDTH * 4;
|
||||||
|
// merge overlay onto screen
|
||||||
|
// this is such a dumb hack
|
||||||
|
let mut zipped = framebuffer[index..index + width].iter_mut().zip(&overlay_framebuffer[overlay_framebuffer_index..overlay_framebuffer_index + width]);
|
||||||
|
while let Some((left, &right)) = zipped.next() {
|
||||||
|
let (left_0, &right_0) = (left, &right);
|
||||||
|
let (left_1, &right_1) = zipped.next().unwrap();
|
||||||
|
let (left_2, &right_2) = zipped.next().unwrap();
|
||||||
|
let (left_3, &right_3) = zipped.next().unwrap();
|
||||||
|
// ensure that the alpha byte is greater than zero, meaning that this pixel shouldn't be transparent
|
||||||
|
if right_3 > 0 {
|
||||||
|
*left_0 = right_0;
|
||||||
|
*left_1 = right_1;
|
||||||
|
*left_2 = right_2;
|
||||||
|
*left_3 = right_3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
overlay_framebuffer_index += width;
|
||||||
|
}
|
||||||
|
}
|
14
src/mouse.rs
Normal file
14
src/mouse.rs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
// mouse.rs
|
||||||
|
|
||||||
|
pub struct Mouse {
|
||||||
|
pub x: u16,
|
||||||
|
pub y: u16,
|
||||||
|
pub click: bool,
|
||||||
|
pub held: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mouse {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Mouse { x: 0, y: 0, click: false, held: false }
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user