Compare commits
	
		
			19 Commits
		
	
	
		
			3ab512d663
			...
			main
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 8a352ca807 | |||
| 068e4124a6 | |||
| fedae65cc8 | |||
| 41586d8cc4 | |||
| 79740ac245 | |||
| d6bfde081e | |||
| d2f57ee66b | |||
| 3841d3a98c | |||
| 85940c8744 | |||
| c7adf54976 | |||
| f0f9003247 | |||
| 5eba097ad4 | |||
| 756c806c2a | |||
| 77af5453f0 | |||
| 41bc4f7afe | |||
| 36e46d3780 | |||
| 926c66746d | |||
| cf2357f917 | |||
| 3fb7edd333 | 
							
								
								
									
										2
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
								
							@@ -1,6 +1,6 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    "recommendations": [
 | 
					    "recommendations": [
 | 
				
			||||||
        "matklad.rust-analyzer",
 | 
					        "rust-lang.rust-analyzer",
 | 
				
			||||||
        "tamasfe.even-better-toml",
 | 
					        "tamasfe.even-better-toml",
 | 
				
			||||||
        "serayuzgur.crates",
 | 
					        "serayuzgur.crates",
 | 
				
			||||||
        "vadimcn.vscode-lldb",
 | 
					        "vadimcn.vscode-lldb",
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2512
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2512
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										23
									
								
								Cargo.toml
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								Cargo.toml
									
									
									
									
									
								
							@@ -8,22 +8,21 @@ edition = "2021"
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
[dependencies]
 | 
					[dependencies]
 | 
				
			||||||
anyhow = "1.0"
 | 
					anyhow = "1.0"
 | 
				
			||||||
bitfield = "0.13"
 | 
					bitfield = "0.17"
 | 
				
			||||||
clap = "2.33"
 | 
					clap = { version = "4.0", features = ["cargo"] }
 | 
				
			||||||
gilrs = "0.8"
 | 
					gilrs = "0.11"
 | 
				
			||||||
winit = "0.26"
 | 
					winit = "0.28"
 | 
				
			||||||
egui = "0.15"
 | 
					egui = "0.21"
 | 
				
			||||||
wgpu = "0.11"
 | 
					egui_wgpu_backend = "0.22"
 | 
				
			||||||
egui_wgpu_backend = "0.14"
 | 
					egui_winit_platform = "0.18"
 | 
				
			||||||
egui_winit_platform = "0.12"
 | 
					pollster = "0.3"
 | 
				
			||||||
pollster = "0.2"
 | 
					rodio = "0.19"
 | 
				
			||||||
rodio = "0.14"
 | 
					rtrb = "0.3"
 | 
				
			||||||
rtrb = "0.2"
 | 
					 | 
				
			||||||
directories-next = "2.0"
 | 
					directories-next = "2.0"
 | 
				
			||||||
tracing = "0.1"
 | 
					tracing = "0.1"
 | 
				
			||||||
tracing-subscriber = { version = "0.3", features = ["std", "env-filter"] }
 | 
					tracing-subscriber = { version = "0.3", features = ["std", "env-filter"] }
 | 
				
			||||||
thiserror = "1.0"
 | 
					thiserror = "1.0"
 | 
				
			||||||
once_cell= "1.8"
 | 
					once_cell = "1.10"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[profile.release]
 | 
					[profile.release]
 | 
				
			||||||
debug = true
 | 
					debug = true
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										11
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								README.md
									
									
									
									
									
								
							@@ -1,10 +1,9 @@
 | 
				
			|||||||
# Rekai's Gameboy Emulator
 | 
					# Rekai's Gameboy Emulator
 | 
				
			||||||
[](https://ci.paoda.moe/paoda/gb)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Status
 | 
					### Status
 | 
				
			||||||
* From [Blargg Test ROMs](https://github.com/L-P/blargg-test-roms/)
 | 
					* From [Blargg Test ROMs](https://github.com/L-P/blargg-test-roms/)
 | 
				
			||||||
    * [x] cpu_instrs
 | 
					    * [x] cpu_instrs
 | 
				
			||||||
    * [ ] instr_timing (kind of)
 | 
					    * [x] instr_timing
 | 
				
			||||||
    * [x] mem_timing
 | 
					    * [x] mem_timing
 | 
				
			||||||
    * [x] mem_timing-2
 | 
					    * [x] mem_timing-2
 | 
				
			||||||
    * [ ] dmg_sound (partial) 
 | 
					    * [ ] dmg_sound (partial) 
 | 
				
			||||||
@@ -21,9 +20,15 @@
 | 
				
			|||||||
Supports: ROM-only, MBC1, MBC2, MBC3 and MBC5 games.
 | 
					Supports: ROM-only, MBC1, MBC2, MBC3 and MBC5 games.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Compiling
 | 
				
			||||||
 | 
					This project was last successfully built on [Rust 1.64.0](https://github.com/rust-lang/rust/tree/1.64.0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. `git clone https://github.com/paoda/gb`
 | 
				
			||||||
 | 
					2. `cd gb`
 | 
				
			||||||
 | 
					3. `cargo run --release`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Controls
 | 
					### Controls
 | 
				
			||||||
Controls are defined [here](https://git.musuka.dev/paoda/gb/src/branch/main/src/joypad.rs#L114)
 | 
					Controls are defined [here](https://github.com/paoda/gb/blob/85940c874460b9cb31bf9b8211bf7dda596d114a/src/joypad.rs#L114)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Key | Button
 | 
					Key | Button
 | 
				
			||||||
--- | ---
 | 
					--- | ---
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -169,7 +169,7 @@ pub(super) mod ch4 {
 | 
				
			|||||||
            match byte & 0x01 {
 | 
					            match byte & 0x01 {
 | 
				
			||||||
                0b00 => Self::Short,
 | 
					                0b00 => Self::Short,
 | 
				
			||||||
                0b01 => Self::Long,
 | 
					                0b01 => Self::Long,
 | 
				
			||||||
                _ => unreachable!("{:#04X} is not a valid value for CounterWidth"),
 | 
					                _ => unreachable!("{:#04X} is not a valid value for CounterWidth", byte),
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -44,7 +44,8 @@ pub fn save_and_exit(cpu: &Cpu, control_flow: &mut ControlFlow) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#[inline]
 | 
					#[inline]
 | 
				
			||||||
pub fn pixel_buf(cpu: &Cpu) -> &[u8; GB_HEIGHT * GB_WIDTH * 4] {
 | 
					pub fn pixel_buf(cpu: &Cpu) -> &[u8; GB_HEIGHT * GB_WIDTH * 4] {
 | 
				
			||||||
    cpu.bus.ppu.frame_buf.as_ref()
 | 
					    use crate::ppu::Device;
 | 
				
			||||||
 | 
					    cpu.bus.ppu.frame_buf.get(Device::Host)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn from_boot_rom<P: AsRef<Path>>(path: P) -> std::io::Result<Cpu> {
 | 
					pub fn from_boot_rom<P: AsRef<Path>>(path: P) -> std::io::Result<Cpu> {
 | 
				
			||||||
@@ -64,8 +65,7 @@ pub fn rom_title(cpu: &Cpu) -> &str {
 | 
				
			|||||||
    cpu.bus
 | 
					    cpu.bus
 | 
				
			||||||
        .cart
 | 
					        .cart
 | 
				
			||||||
        .as_ref()
 | 
					        .as_ref()
 | 
				
			||||||
        .map(|c| c.title.as_deref())
 | 
					        .and_then(|c| c.title.as_deref())
 | 
				
			||||||
        .flatten()
 | 
					 | 
				
			||||||
        .unwrap_or(DEFAULT_TITLE)
 | 
					        .unwrap_or(DEFAULT_TITLE)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										545
									
								
								src/gui.rs
									
									
									
									
									
								
							
							
						
						
									
										545
									
								
								src/gui.rs
									
									
									
									
									
								
							@@ -1,18 +1,20 @@
 | 
				
			|||||||
use egui::{ClippedMesh, CtxRef, TextureId};
 | 
					use egui::{Context, TextureId};
 | 
				
			||||||
use egui_wgpu_backend::{BackendError, RenderPass, ScreenDescriptor};
 | 
					use egui_wgpu_backend::{wgpu, RenderPass, ScreenDescriptor};
 | 
				
			||||||
use egui_winit_platform::Platform;
 | 
					use egui_winit_platform::Platform;
 | 
				
			||||||
use wgpu::{
 | 
					use wgpu::{
 | 
				
			||||||
    Adapter, CommandEncoder, Device, Extent3d, FilterMode, Instance, Queue, RequestDeviceError,
 | 
					    Adapter, CommandEncoder, Device, Extent3d, FilterMode, Instance, Queue, RequestDeviceError,
 | 
				
			||||||
    Surface, SurfaceConfiguration, SurfaceTexture, Texture, TextureFormat, TextureUsages,
 | 
					    Surface, SurfaceCapabilities, SurfaceConfiguration, Texture, TextureView,
 | 
				
			||||||
    TextureView,
 | 
					    TextureViewDescriptor,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use winit::dpi::PhysicalSize;
 | 
				
			||||||
use winit::error::OsError;
 | 
					use winit::error::OsError;
 | 
				
			||||||
use winit::event::{ElementState, KeyboardInput};
 | 
					use winit::event::{ElementState, Event, KeyboardInput};
 | 
				
			||||||
use winit::event_loop::EventLoop;
 | 
					use winit::event_loop::{ControlFlow, EventLoop};
 | 
				
			||||||
use winit::window::Window;
 | 
					use winit::window::Window;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::cpu::Cpu;
 | 
					use crate::cpu::Cpu;
 | 
				
			||||||
use crate::{GB_HEIGHT, GB_WIDTH};
 | 
					use crate::{emu, GB_HEIGHT, GB_WIDTH};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const EGUI_DIMENSIONS: (usize, usize) = (1280, 720);
 | 
					const EGUI_DIMENSIONS: (usize, usize) = (1280, 720);
 | 
				
			||||||
const FILTER_MODE: FilterMode = FilterMode::Nearest;
 | 
					const FILTER_MODE: FilterMode = FilterMode::Nearest;
 | 
				
			||||||
@@ -20,219 +22,163 @@ const WINDOW_TITLE: &str = "DMG-01 Emulator";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const SCALE: f32 = 3.0;
 | 
					const SCALE: f32 = 3.0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Holds GUI State
 | 
					pub struct Gui {
 | 
				
			||||||
#[derive(Debug, Clone)]
 | 
					    pub should_quit: bool,
 | 
				
			||||||
pub struct GuiState {
 | 
					 | 
				
			||||||
    /// When true, egui winit should exit the application
 | 
					 | 
				
			||||||
    pub quit: bool,
 | 
					 | 
				
			||||||
    pub title: String,
 | 
					    pub title: String,
 | 
				
			||||||
    pub mode: EmuMode,
 | 
					    pub mode: EmuMode,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    window: Window,
 | 
				
			||||||
 | 
					    platform: Platform,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    surface: Surface,
 | 
				
			||||||
 | 
					    device: Device,
 | 
				
			||||||
 | 
					    queue: Queue,
 | 
				
			||||||
 | 
					    surface_config: SurfaceConfiguration,
 | 
				
			||||||
 | 
					    render_pass: RenderPass,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    texture: Texture,
 | 
				
			||||||
 | 
					    texture_size: Extent3d,
 | 
				
			||||||
 | 
					    texture_id: TextureId,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl GuiState {
 | 
					impl Gui {
 | 
				
			||||||
    pub fn new(title: String) -> Self {
 | 
					    pub fn new<T>(title: String, event_loop: &EventLoop<T>, cpu: &Cpu) -> Self {
 | 
				
			||||||
 | 
					        use wgpu::InstanceDescriptor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let window = build_window(event_loop).expect("build window");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let instance = Instance::new(InstanceDescriptor::default());
 | 
				
			||||||
 | 
					        let surface = unsafe {
 | 
				
			||||||
 | 
					            instance
 | 
				
			||||||
 | 
					                .create_surface(&window)
 | 
				
			||||||
 | 
					                .expect("create wgpu instance surface")
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let adapter = request_adapter(&instance, &surface).expect("request adaptor");
 | 
				
			||||||
 | 
					        let (device, queue) = request_device(&adapter).expect("request device");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let capabilities = surface.get_capabilities(&adapter);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let surface_config = surface_config(&window, capabilities);
 | 
				
			||||||
 | 
					        surface.configure(&device, &surface_config);
 | 
				
			||||||
 | 
					        let platform = platform(&window);
 | 
				
			||||||
 | 
					        let mut render_pass = RenderPass::new(&device, surface_config.format, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let texture_size = texture_size();
 | 
				
			||||||
 | 
					        let texture = create_texture(&device, texture_size);
 | 
				
			||||||
 | 
					        write_to_texture(&queue, &texture, emu::pixel_buf(cpu), texture_size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let view = texture.create_view(&TextureViewDescriptor::default());
 | 
				
			||||||
 | 
					        let texture_id = expose_texture_to_egui(&mut render_pass, &device, &view);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
 | 
					            should_quit: Default::default(),
 | 
				
			||||||
            title,
 | 
					            title,
 | 
				
			||||||
            quit: Default::default(),
 | 
					 | 
				
			||||||
            mode: EmuMode::Running,
 | 
					            mode: EmuMode::Running,
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
 | 
					            window,
 | 
				
			||||||
pub enum EmuMode {
 | 
					            platform,
 | 
				
			||||||
    Running,
 | 
					 | 
				
			||||||
    StepFrame,
 | 
					 | 
				
			||||||
    Stopped,
 | 
					 | 
				
			||||||
    Step,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// To avoid using an [Option<KeyboardInput>] to keep track of user input from winit,
 | 
					            surface,
 | 
				
			||||||
/// we can use a "default" value. However, in order for this to work the chosen "default"
 | 
					            device,
 | 
				
			||||||
/// value must be an **unused** key, so that it is ignored by the emulator.
 | 
					            queue,
 | 
				
			||||||
pub fn unused_key() -> KeyboardInput {
 | 
					            surface_config,
 | 
				
			||||||
    #![allow(deprecated)]
 | 
					            render_pass,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    KeyboardInput {
 | 
					 | 
				
			||||||
        scancode: Default::default(),
 | 
					 | 
				
			||||||
        state: ElementState::Released,
 | 
					 | 
				
			||||||
        virtual_keycode: Default::default(),
 | 
					 | 
				
			||||||
        modifiers: Default::default(), // this argument is deprecated
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn build_window<T>(event_loop: &EventLoop<T>) -> Result<Window, OsError> {
 | 
					 | 
				
			||||||
    use winit::dpi::PhysicalSize;
 | 
					 | 
				
			||||||
    use winit::window::WindowBuilder;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    WindowBuilder::new()
 | 
					 | 
				
			||||||
        .with_decorations(true)
 | 
					 | 
				
			||||||
        .with_resizable(true)
 | 
					 | 
				
			||||||
        .with_transparent(false)
 | 
					 | 
				
			||||||
        .with_title(WINDOW_TITLE)
 | 
					 | 
				
			||||||
        .with_inner_size(PhysicalSize {
 | 
					 | 
				
			||||||
            width: EGUI_DIMENSIONS.0 as f32,
 | 
					 | 
				
			||||||
            height: EGUI_DIMENSIONS.1 as f32,
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
        .build(event_loop)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn create_surface(window: &Window) -> (Instance, Surface) {
 | 
					 | 
				
			||||||
    use wgpu::Backends;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let instance = Instance::new(Backends::PRIMARY);
 | 
					 | 
				
			||||||
    let surface = unsafe { instance.create_surface(window) };
 | 
					 | 
				
			||||||
    (instance, surface)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn request_adapter(instance: &Instance, surface: &Surface) -> Option<Adapter> {
 | 
					 | 
				
			||||||
    use wgpu::{PowerPreference, RequestAdapterOptions};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pollster::block_on(instance.request_adapter(&RequestAdapterOptions {
 | 
					 | 
				
			||||||
        power_preference: PowerPreference::HighPerformance,
 | 
					 | 
				
			||||||
        force_fallback_adapter: false, // TODO: What do I want to do with this?
 | 
					 | 
				
			||||||
        compatible_surface: Some(surface),
 | 
					 | 
				
			||||||
    }))
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn request_device(adapter: &Adapter) -> Result<(Device, Queue), RequestDeviceError> {
 | 
					 | 
				
			||||||
    use wgpu::{DeviceDescriptor, Features, Limits};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pollster::block_on(adapter.request_device(
 | 
					 | 
				
			||||||
        &DeviceDescriptor {
 | 
					 | 
				
			||||||
            label: None,
 | 
					 | 
				
			||||||
            features: Features::default(),
 | 
					 | 
				
			||||||
            limits: Limits::default(),
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        None,
 | 
					 | 
				
			||||||
    ))
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn surface_config(window: &Window, format: TextureFormat) -> SurfaceConfiguration {
 | 
					 | 
				
			||||||
    use wgpu::PresentMode;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let size = window.inner_size();
 | 
					 | 
				
			||||||
    SurfaceConfiguration {
 | 
					 | 
				
			||||||
        usage: TextureUsages::RENDER_ATTACHMENT,
 | 
					 | 
				
			||||||
        format,
 | 
					 | 
				
			||||||
        width: size.width as u32,
 | 
					 | 
				
			||||||
        height: size.height as u32,
 | 
					 | 
				
			||||||
        present_mode: PresentMode::Mailbox,
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn platform_desc(window: &Window) -> Platform {
 | 
					 | 
				
			||||||
    use egui::FontDefinitions;
 | 
					 | 
				
			||||||
    use egui_winit_platform::PlatformDescriptor;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let size = window.inner_size();
 | 
					 | 
				
			||||||
    Platform::new(PlatformDescriptor {
 | 
					 | 
				
			||||||
        physical_width: size.width as u32,
 | 
					 | 
				
			||||||
        physical_height: size.height as u32,
 | 
					 | 
				
			||||||
        scale_factor: window.scale_factor(),
 | 
					 | 
				
			||||||
        font_definitions: FontDefinitions::default(),
 | 
					 | 
				
			||||||
        ..Default::default()
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn texture_size() -> Extent3d {
 | 
					 | 
				
			||||||
    Extent3d {
 | 
					 | 
				
			||||||
        width: GB_WIDTH as u32,
 | 
					 | 
				
			||||||
        height: GB_HEIGHT as u32,
 | 
					 | 
				
			||||||
        ..Default::default()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn create_texture(device: &Device, size: Extent3d) -> Texture {
 | 
					 | 
				
			||||||
    use wgpu::{TextureDescriptor, TextureDimension};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    device.create_texture(&TextureDescriptor {
 | 
					 | 
				
			||||||
        size,
 | 
					 | 
				
			||||||
        mip_level_count: 1,
 | 
					 | 
				
			||||||
        sample_count: 1,
 | 
					 | 
				
			||||||
        dimension: TextureDimension::D2,
 | 
					 | 
				
			||||||
        format: TextureFormat::Rgba8UnormSrgb,
 | 
					 | 
				
			||||||
        usage: TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING,
 | 
					 | 
				
			||||||
        label: Some("gb_pixel_buffer"),
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[inline]
 | 
					 | 
				
			||||||
pub fn write_to_texture(
 | 
					 | 
				
			||||||
    queue: &Queue,
 | 
					 | 
				
			||||||
    texture: &Texture,
 | 
					 | 
				
			||||||
    data: &[u8; GB_WIDTH * 4 * GB_HEIGHT],
 | 
					 | 
				
			||||||
    size: Extent3d,
 | 
					 | 
				
			||||||
) {
 | 
					 | 
				
			||||||
    use std::num::NonZeroU32;
 | 
					 | 
				
			||||||
    use wgpu::{ImageCopyTexture, ImageDataLayout, Origin3d, TextureAspect};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    queue.write_texture(
 | 
					 | 
				
			||||||
        ImageCopyTexture {
 | 
					 | 
				
			||||||
            texture,
 | 
					            texture,
 | 
				
			||||||
            mip_level: 0,
 | 
					            texture_size,
 | 
				
			||||||
            origin: Origin3d::ZERO,
 | 
					            texture_id,
 | 
				
			||||||
            aspect: TextureAspect::All,
 | 
					        }
 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        data,
 | 
					 | 
				
			||||||
        ImageDataLayout {
 | 
					 | 
				
			||||||
            offset: 0,
 | 
					 | 
				
			||||||
            bytes_per_row: NonZeroU32::new(4 * GB_WIDTH as u32),
 | 
					 | 
				
			||||||
            rows_per_image: NonZeroU32::new(GB_HEIGHT as u32),
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        size,
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn expose_texture_to_egui(
 | 
					    pub fn maybe_quit(&self, cpu: &Cpu, control_flow: &mut ControlFlow) {
 | 
				
			||||||
    render_pass: &mut RenderPass,
 | 
					        if self.should_quit {
 | 
				
			||||||
    device: &Device,
 | 
					            emu::save_and_exit(cpu, control_flow);
 | 
				
			||||||
    texture: &Texture,
 | 
					        }
 | 
				
			||||||
) -> TextureId {
 | 
					    }
 | 
				
			||||||
    render_pass.egui_texture_from_wgpu_texture(device, texture, FILTER_MODE)
 | 
					
 | 
				
			||||||
 | 
					    pub fn request_redraw(&self) {
 | 
				
			||||||
 | 
					        self.window.request_redraw();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn handle_event<T>(&mut self, event: &Event<T>) {
 | 
				
			||||||
 | 
					        self.platform.handle_event(event);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn update_time(&mut self, elapsed_seconds: f64) {
 | 
				
			||||||
 | 
					        self.platform.update_time(elapsed_seconds);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn resize(&mut self, size: PhysicalSize<u32>) {
 | 
				
			||||||
 | 
					        // See: https://github.com/rust-windowing/winit/issues/208
 | 
				
			||||||
 | 
					        if size.width > 0 && size.height > 0 {
 | 
				
			||||||
 | 
					            self.surface_config.width = size.width;
 | 
				
			||||||
 | 
					            self.surface_config.height = size.height;
 | 
				
			||||||
 | 
					            self.surface.configure(&self.device, &self.surface_config);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn paint(&mut self, cpu: &Cpu) {
 | 
				
			||||||
 | 
					        use wgpu::{Color, SurfaceError};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let data = emu::pixel_buf(cpu);
 | 
				
			||||||
 | 
					        write_to_texture(&self.queue, &self.texture, data, self.texture_size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let output_frame = match self.surface.get_current_texture() {
 | 
				
			||||||
 | 
					            Ok(frame) => frame,
 | 
				
			||||||
 | 
					            Err(SurfaceError::Outdated) => return, // Occurs on minimization on Windows
 | 
				
			||||||
 | 
					            Err(e) => {
 | 
				
			||||||
 | 
					                eprintln!("Dropped frame with error: {}", e);
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let output_view = output_frame
 | 
				
			||||||
 | 
					            .texture
 | 
				
			||||||
 | 
					            .create_view(&TextureViewDescriptor::default());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Begin to draw Egui components
 | 
				
			||||||
 | 
					        self.platform.begin_frame();
 | 
				
			||||||
 | 
					        self.draw_egui(cpu, &self.platform.context(), self.texture_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // End the UI frame. We could now handle the output and draw the UI with the backend.
 | 
				
			||||||
 | 
					        let full_output = self.platform.end_frame(Some(&self.window));
 | 
				
			||||||
 | 
					        let paint_jobs = self.platform.context().tessellate(full_output.shapes);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let mut encoder = create_command_encoder(&self.device);
 | 
				
			||||||
 | 
					        let screen_descriptor = create_screen_descriptor(&self.window, &self.surface_config);
 | 
				
			||||||
 | 
					        let tdelta = full_output.textures_delta;
 | 
				
			||||||
 | 
					        // Upload all resources for the GPU.
 | 
				
			||||||
 | 
					        self.render_pass
 | 
				
			||||||
 | 
					            .add_textures(&self.device, &self.queue, &tdelta)
 | 
				
			||||||
 | 
					            .expect("add texture ok");
 | 
				
			||||||
 | 
					        self.render_pass
 | 
				
			||||||
 | 
					            .update_buffers(&self.device, &self.queue, &paint_jobs, &screen_descriptor);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Record all render passes.
 | 
				
			||||||
 | 
					        self.render_pass
 | 
				
			||||||
 | 
					            .execute(
 | 
				
			||||||
 | 
					                &mut encoder,
 | 
				
			||||||
 | 
					                &output_view,
 | 
				
			||||||
 | 
					                &paint_jobs,
 | 
				
			||||||
 | 
					                &screen_descriptor,
 | 
				
			||||||
 | 
					                Some(Color::BLACK),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .expect("execute render pass");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Submit the commands.
 | 
				
			||||||
 | 
					        self.queue.submit(std::iter::once(encoder.finish()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Redraw egui
 | 
				
			||||||
 | 
					        output_frame.present();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.render_pass
 | 
				
			||||||
 | 
					            .remove_textures(tdelta)
 | 
				
			||||||
 | 
					            .expect("remove texture delta");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[inline]
 | 
					    #[inline]
 | 
				
			||||||
pub fn create_view(frame: &SurfaceTexture) -> TextureView {
 | 
					    pub fn draw_egui(&mut self, cpu: &Cpu, ctx: &Context, texture_id: TextureId) {
 | 
				
			||||||
    use wgpu::TextureViewDescriptor;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    frame.texture.create_view(&TextureViewDescriptor::default())
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[inline]
 | 
					 | 
				
			||||||
pub fn create_command_encoder(device: &Device) -> CommandEncoder {
 | 
					 | 
				
			||||||
    use wgpu::CommandEncoderDescriptor;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    device.create_command_encoder(&CommandEncoderDescriptor {
 | 
					 | 
				
			||||||
        label: Some("encoder"),
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[inline]
 | 
					 | 
				
			||||||
pub fn create_screen_descriptor(
 | 
					 | 
				
			||||||
    window: &Window,
 | 
					 | 
				
			||||||
    config: &SurfaceConfiguration,
 | 
					 | 
				
			||||||
) -> ScreenDescriptor {
 | 
					 | 
				
			||||||
    ScreenDescriptor {
 | 
					 | 
				
			||||||
        physical_width: config.width,
 | 
					 | 
				
			||||||
        physical_height: config.height,
 | 
					 | 
				
			||||||
        scale_factor: window.scale_factor() as f32,
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[inline]
 | 
					 | 
				
			||||||
pub fn execute_render_pass(
 | 
					 | 
				
			||||||
    render_pass: &mut RenderPass,
 | 
					 | 
				
			||||||
    encoder: &mut CommandEncoder,
 | 
					 | 
				
			||||||
    view: &TextureView,
 | 
					 | 
				
			||||||
    jobs: Vec<ClippedMesh>,
 | 
					 | 
				
			||||||
    descriptor: &ScreenDescriptor,
 | 
					 | 
				
			||||||
) -> Result<(), BackendError> {
 | 
					 | 
				
			||||||
    render_pass.execute(encoder, view, &jobs, descriptor, Some(wgpu::Color::BLACK))
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[inline]
 | 
					 | 
				
			||||||
pub fn draw_egui(cpu: &Cpu, app: &mut GuiState, ctx: &CtxRef, texture_id: TextureId) {
 | 
					 | 
				
			||||||
        use crate::{cpu, instruction, ppu};
 | 
					        use crate::{cpu, instruction, ppu};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        fn selectable_text(ui: &mut egui::Ui, mut text: &str) -> egui::Response {
 | 
					        fn selectable_text(ui: &mut egui::Ui, mut text: &str) -> egui::Response {
 | 
				
			||||||
@@ -240,13 +186,13 @@ pub fn draw_egui(cpu: &Cpu, app: &mut GuiState, ctx: &CtxRef, texture_id: Textur
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
 | 
					        egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
 | 
				
			||||||
        egui::menu::menu(ui, "File", |ui| {
 | 
					            ui.menu_button("File", |ui| {
 | 
				
			||||||
                if ui.button("Quit").clicked() {
 | 
					                if ui.button("Quit").clicked() {
 | 
				
			||||||
                app.quit = true;
 | 
					                    self.should_quit = true;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        egui::Window::new(&app.title).show(ctx, |ui| {
 | 
					            egui::Window::new(&self.title).show(ctx, |ui| {
 | 
				
			||||||
                ui.image(
 | 
					                ui.image(
 | 
				
			||||||
                    texture_id,
 | 
					                    texture_id,
 | 
				
			||||||
                    [GB_WIDTH as f32 * SCALE, GB_HEIGHT as f32 * SCALE],
 | 
					                    [GB_WIDTH as f32 * SCALE, GB_HEIGHT as f32 * SCALE],
 | 
				
			||||||
@@ -259,12 +205,12 @@ pub fn draw_egui(cpu: &Cpu, app: &mut GuiState, ctx: &CtxRef, texture_id: Textur
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            egui::Window::new("Settings").show(ctx, |ui| {
 | 
					            egui::Window::new("Settings").show(ctx, |ui| {
 | 
				
			||||||
                egui::ComboBox::from_label("Emulation Mode")
 | 
					                egui::ComboBox::from_label("Emulation Mode")
 | 
				
			||||||
                .selected_text(format!("{:?}", app.mode))
 | 
					                    .selected_text(format!("{:?}", self.mode))
 | 
				
			||||||
                    .show_ui(ui, |ui| {
 | 
					                    .show_ui(ui, |ui| {
 | 
				
			||||||
                    ui.selectable_value(&mut app.mode, EmuMode::Running, "Running");
 | 
					                        ui.selectable_value(&mut self.mode, EmuMode::Running, "Running");
 | 
				
			||||||
                    ui.selectable_value(&mut app.mode, EmuMode::Stopped, "Stopped");
 | 
					                        ui.selectable_value(&mut self.mode, EmuMode::Stopped, "Stopped");
 | 
				
			||||||
                    ui.selectable_value(&mut app.mode, EmuMode::StepFrame, "Step Frame");
 | 
					                        ui.selectable_value(&mut self.mode, EmuMode::StepFrame, "Step Frame");
 | 
				
			||||||
                    ui.selectable_value(&mut app.mode, EmuMode::Step, "Step");
 | 
					                        ui.selectable_value(&mut self.mode, EmuMode::Step, "Step");
 | 
				
			||||||
                    })
 | 
					                    })
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -322,12 +268,13 @@ pub fn draw_egui(cpu: &Cpu, app: &mut GuiState, ctx: &CtxRef, texture_id: Textur
 | 
				
			|||||||
                ui.horizontal(|ui| {
 | 
					                ui.horizontal(|ui| {
 | 
				
			||||||
                    let ie = cpu.int_enable();
 | 
					                    let ie = cpu.int_enable();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                let r_len = ctx.fonts().glyph_width(egui::TextStyle::Body, 'R');
 | 
					                    // TODO: Reimplement this
 | 
				
			||||||
                let e_len = ctx.fonts().glyph_width(egui::TextStyle::Body, 'E');
 | 
					                    // let r_len = ctx.fonts().glyph_width(egui::TextStyle::Body, 'R');
 | 
				
			||||||
                let q_len = ctx.fonts().glyph_width(egui::TextStyle::Body, 'Q');
 | 
					                    // let e_len = ctx.fonts().glyph_width(egui::TextStyle::Body, 'E');
 | 
				
			||||||
 | 
					                    // let q_len = ctx.fonts().glyph_width(egui::TextStyle::Body, 'Q');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    ui.label("IE:");
 | 
					                    ui.label("IE:");
 | 
				
			||||||
                ui.add_space(q_len - (e_len - r_len));
 | 
					                    // ui.add_space(q_len - (e_len - r_len));
 | 
				
			||||||
                    let _ = ui.selectable_label(ie & 0b01 == 0x01, "VBlank");
 | 
					                    let _ = ui.selectable_label(ie & 0b01 == 0x01, "VBlank");
 | 
				
			||||||
                    let _ = ui.selectable_label(ie >> 1 & 0x01 == 0x01, "LCD STAT");
 | 
					                    let _ = ui.selectable_label(ie >> 1 & 0x01 == 0x01, "LCD STAT");
 | 
				
			||||||
                    let _ = ui.selectable_label(ie >> 2 & 0x01 == 0x01, "Timer");
 | 
					                    let _ = ui.selectable_label(ie >> 2 & 0x01 == 0x01, "Timer");
 | 
				
			||||||
@@ -348,6 +295,172 @@ pub fn draw_egui(cpu: &Cpu, app: &mut GuiState, ctx: &CtxRef, texture_id: Textur
 | 
				
			|||||||
            })
 | 
					            })
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, Copy, PartialEq, Eq)]
 | 
				
			||||||
 | 
					pub enum EmuMode {
 | 
				
			||||||
 | 
					    Running,
 | 
				
			||||||
 | 
					    StepFrame,
 | 
				
			||||||
 | 
					    Stopped,
 | 
				
			||||||
 | 
					    Step,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// To avoid using an [Option<KeyboardInput>] to keep track of user input from winit,
 | 
				
			||||||
 | 
					/// we can use a "default" value. However, in order for this to work the chosen "default"
 | 
				
			||||||
 | 
					/// value must be an **unused** key, so that it is ignored by the emulator.
 | 
				
			||||||
 | 
					pub fn unused_key() -> KeyboardInput {
 | 
				
			||||||
 | 
					    #![allow(deprecated)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    KeyboardInput {
 | 
				
			||||||
 | 
					        scancode: Default::default(),
 | 
				
			||||||
 | 
					        state: ElementState::Released,
 | 
				
			||||||
 | 
					        virtual_keycode: Default::default(),
 | 
				
			||||||
 | 
					        modifiers: Default::default(), // this argument is deprecated
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn build_window<T>(event_loop: &EventLoop<T>) -> Result<Window, OsError> {
 | 
				
			||||||
 | 
					    use winit::window::WindowBuilder;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    WindowBuilder::new()
 | 
				
			||||||
 | 
					        .with_decorations(true)
 | 
				
			||||||
 | 
					        .with_resizable(true)
 | 
				
			||||||
 | 
					        .with_transparent(false)
 | 
				
			||||||
 | 
					        .with_title(WINDOW_TITLE)
 | 
				
			||||||
 | 
					        .with_inner_size(PhysicalSize {
 | 
				
			||||||
 | 
					            width: EGUI_DIMENSIONS.0 as f32,
 | 
				
			||||||
 | 
					            height: EGUI_DIMENSIONS.1 as f32,
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .build(event_loop)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn request_adapter(instance: &Instance, surface: &Surface) -> Option<Adapter> {
 | 
				
			||||||
 | 
					    use wgpu::{PowerPreference, RequestAdapterOptions};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pollster::block_on(instance.request_adapter(&RequestAdapterOptions {
 | 
				
			||||||
 | 
					        power_preference: PowerPreference::HighPerformance,
 | 
				
			||||||
 | 
					        compatible_surface: Some(surface),
 | 
				
			||||||
 | 
					        force_fallback_adapter: false, // TODO: What do I want to do with this?
 | 
				
			||||||
 | 
					    }))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn request_device(adapter: &Adapter) -> Result<(Device, Queue), RequestDeviceError> {
 | 
				
			||||||
 | 
					    use wgpu::{DeviceDescriptor, Features, Limits};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pollster::block_on(adapter.request_device(
 | 
				
			||||||
 | 
					        &DeviceDescriptor {
 | 
				
			||||||
 | 
					            features: Features::default(),
 | 
				
			||||||
 | 
					            limits: Limits::default(),
 | 
				
			||||||
 | 
					            label: None,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        None,
 | 
				
			||||||
 | 
					    ))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn surface_config(window: &Window, capabilities: SurfaceCapabilities) -> SurfaceConfiguration {
 | 
				
			||||||
 | 
					    use egui_wgpu_backend::wgpu::{PresentMode, TextureFormat, TextureUsages};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let size = window.inner_size();
 | 
				
			||||||
 | 
					    SurfaceConfiguration {
 | 
				
			||||||
 | 
					        usage: TextureUsages::RENDER_ATTACHMENT,
 | 
				
			||||||
 | 
					        format: capabilities.formats[0],
 | 
				
			||||||
 | 
					        width: size.width as u32,
 | 
				
			||||||
 | 
					        height: size.height as u32,
 | 
				
			||||||
 | 
					        present_mode: PresentMode::Fifo,
 | 
				
			||||||
 | 
					        alpha_mode: capabilities.alpha_modes[0],
 | 
				
			||||||
 | 
					        view_formats: vec![TextureFormat::Rgba8UnormSrgb],
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn platform(window: &Window) -> Platform {
 | 
				
			||||||
 | 
					    use egui::FontDefinitions;
 | 
				
			||||||
 | 
					    use egui_winit_platform::PlatformDescriptor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let size = window.inner_size();
 | 
				
			||||||
 | 
					    Platform::new(PlatformDescriptor {
 | 
				
			||||||
 | 
					        physical_width: size.width as u32,
 | 
				
			||||||
 | 
					        physical_height: size.height as u32,
 | 
				
			||||||
 | 
					        scale_factor: window.scale_factor(),
 | 
				
			||||||
 | 
					        font_definitions: FontDefinitions::default(),
 | 
				
			||||||
 | 
					        ..Default::default()
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn texture_size() -> Extent3d {
 | 
				
			||||||
 | 
					    Extent3d {
 | 
				
			||||||
 | 
					        width: GB_WIDTH as u32,
 | 
				
			||||||
 | 
					        height: GB_HEIGHT as u32,
 | 
				
			||||||
 | 
					        ..Default::default()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn create_texture(device: &Device, size: Extent3d) -> Texture {
 | 
				
			||||||
 | 
					    use wgpu::{TextureDescriptor, TextureDimension, TextureFormat, TextureUsages};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    device.create_texture(&TextureDescriptor {
 | 
				
			||||||
 | 
					        size,
 | 
				
			||||||
 | 
					        mip_level_count: 1,
 | 
				
			||||||
 | 
					        sample_count: 1,
 | 
				
			||||||
 | 
					        dimension: TextureDimension::D2,
 | 
				
			||||||
 | 
					        format: TextureFormat::Bgra8Unorm,
 | 
				
			||||||
 | 
					        usage: TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING,
 | 
				
			||||||
 | 
					        label: Some("gb_pixel_buffer"),
 | 
				
			||||||
 | 
					        view_formats: &[TextureFormat::Bgra8Unorm],
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[inline]
 | 
				
			||||||
 | 
					fn write_to_texture(
 | 
				
			||||||
 | 
					    queue: &Queue,
 | 
				
			||||||
 | 
					    texture: &Texture,
 | 
				
			||||||
 | 
					    data: &[u8; GB_WIDTH * 4 * GB_HEIGHT],
 | 
				
			||||||
 | 
					    size: Extent3d,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    use std::num::NonZeroU32;
 | 
				
			||||||
 | 
					    use wgpu::{ImageCopyTexture, ImageDataLayout, Origin3d, TextureAspect};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    queue.write_texture(
 | 
				
			||||||
 | 
					        ImageCopyTexture {
 | 
				
			||||||
 | 
					            texture,
 | 
				
			||||||
 | 
					            mip_level: 0,
 | 
				
			||||||
 | 
					            origin: Origin3d::ZERO,
 | 
				
			||||||
 | 
					            aspect: TextureAspect::All,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        data,
 | 
				
			||||||
 | 
					        ImageDataLayout {
 | 
				
			||||||
 | 
					            offset: 0,
 | 
				
			||||||
 | 
					            bytes_per_row: NonZeroU32::new(4 * GB_WIDTH as u32),
 | 
				
			||||||
 | 
					            rows_per_image: NonZeroU32::new(GB_HEIGHT as u32),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        size,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn expose_texture_to_egui(
 | 
				
			||||||
 | 
					    render_pass: &mut RenderPass,
 | 
				
			||||||
 | 
					    device: &Device,
 | 
				
			||||||
 | 
					    view: &TextureView,
 | 
				
			||||||
 | 
					) -> TextureId {
 | 
				
			||||||
 | 
					    render_pass.egui_texture_from_wgpu_texture(device, view, FILTER_MODE)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[inline]
 | 
				
			||||||
 | 
					fn create_command_encoder(device: &Device) -> CommandEncoder {
 | 
				
			||||||
 | 
					    use wgpu::CommandEncoderDescriptor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    device.create_command_encoder(&CommandEncoderDescriptor {
 | 
				
			||||||
 | 
					        label: Some("encoder"),
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[inline]
 | 
				
			||||||
 | 
					fn create_screen_descriptor(window: &Window, config: &SurfaceConfiguration) -> ScreenDescriptor {
 | 
				
			||||||
 | 
					    ScreenDescriptor {
 | 
				
			||||||
 | 
					        physical_width: config.width,
 | 
				
			||||||
 | 
					        physical_height: config.height,
 | 
				
			||||||
 | 
					        scale_factor: window.scale_factor() as f32,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub mod kbd {
 | 
					pub mod kbd {
 | 
				
			||||||
    use winit::event::{ElementState, KeyboardInput, VirtualKeyCode};
 | 
					    use winit::event::{ElementState, KeyboardInput, VirtualKeyCode};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1654,7 +1654,9 @@ impl Instruction {
 | 
				
			|||||||
            0o304 | 0o314 | 0o324 | 0o334 => CALL(jump_cond((byte >> 3) & 0x03)),
 | 
					            0o304 | 0o314 | 0o324 | 0o334 => CALL(jump_cond((byte >> 3) & 0x03)),
 | 
				
			||||||
            // PUSH r16
 | 
					            // PUSH r16
 | 
				
			||||||
            0o305 | 0o325 | 0o345 | 0o365 => PUSH(group3((byte >> 4) & 0x03)),
 | 
					            0o305 | 0o325 | 0o345 | 0o365 => PUSH(group3((byte >> 4) & 0x03)),
 | 
				
			||||||
 | 
					            // CALL u16
 | 
				
			||||||
            0o315 => CALL(JpCond::Always),
 | 
					            0o315 => CALL(JpCond::Always),
 | 
				
			||||||
 | 
					            // ADD, ADC, SUB, SBC, AND, XOR, OR, and CP
 | 
				
			||||||
            0o306 | 0o316 | 0o326 | 0o336 | 0o346 | 0o356 | 0o366 | 0o376 => {
 | 
					            0o306 | 0o316 | 0o326 | 0o336 | 0o346 | 0o356 | 0o366 | 0o376 => {
 | 
				
			||||||
                alu_imm_instr((byte >> 3) & 0x07)
 | 
					                alu_imm_instr((byte >> 3) & 0x07)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@@ -2071,7 +2073,7 @@ mod table {
 | 
				
			|||||||
            0b101 => L,
 | 
					            0b101 => L,
 | 
				
			||||||
            0b110 => IndirectHL,
 | 
					            0b110 => IndirectHL,
 | 
				
			||||||
            0b111 => A,
 | 
					            0b111 => A,
 | 
				
			||||||
            _ => unreachable!("{:#04X} is not a valid Register"),
 | 
					            _ => unreachable!("{:#04X} is not a valid Register", code),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -2153,8 +2155,11 @@ mod table {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub(crate) mod dbg {
 | 
					pub(crate) mod dbg {
 | 
				
			||||||
 | 
					    use std::borrow::Cow;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    use super::add::{Source as AddSource, Target as AddTarget};
 | 
					    use super::add::{Source as AddSource, Target as AddTarget};
 | 
				
			||||||
    use super::jump::JpCond;
 | 
					    use super::alu::Source as AluSource;
 | 
				
			||||||
 | 
					    use super::jump::{JpCond, JpLoc};
 | 
				
			||||||
    use super::load::{Source as LDSource, Target as LDTarget};
 | 
					    use super::load::{Source as LDSource, Target as LDTarget};
 | 
				
			||||||
    use super::{AllRegisters, BusIo, Cpu, Instruction, RegisterPair};
 | 
					    use super::{AllRegisters, BusIo, Cpu, Instruction, RegisterPair};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -2188,28 +2193,140 @@ pub(crate) mod dbg {
 | 
				
			|||||||
        sm83_asm
 | 
					        sm83_asm
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn disasm(cpu: &Cpu, pc: u16, instr: Instruction) -> String {
 | 
					    pub(crate) fn new_disasm(cpu: &Cpu, limit: u8) -> String {
 | 
				
			||||||
 | 
					        let mut assembly = String::new();
 | 
				
			||||||
 | 
					        let mut pc = cpu.register_pair(RegisterPair::PC);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for _ in 0..limit {
 | 
				
			||||||
 | 
					            let opcode = cpu.read_byte(pc);
 | 
				
			||||||
 | 
					            pc += 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let maybe_instr = if opcode == 0xCB {
 | 
				
			||||||
 | 
					                let opcode = cpu.read_byte(pc);
 | 
				
			||||||
 | 
					                pc += 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Instruction::prefixed(opcode)
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                Instruction::unprefixed(opcode)
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            match maybe_instr {
 | 
				
			||||||
 | 
					                Instruction::Invalid => {}
 | 
				
			||||||
 | 
					                instr => {
 | 
				
			||||||
 | 
					                    let output = format!("${:04X} {}\n", pc - 1, disasm(cpu, pc, instr));
 | 
				
			||||||
 | 
					                    assembly.push_str(&output);
 | 
				
			||||||
 | 
					                    pc += delta::pc_inc_count(instr);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assembly
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO: It might be better if I pass in a mutable writer instead of usnig a String
 | 
				
			||||||
 | 
					    fn disasm(cpu: &Cpu, pc: u16, instr: Instruction) -> Cow<str> {
 | 
				
			||||||
        use Instruction::*;
 | 
					        use Instruction::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let imm_byte = cpu.read_byte(pc + 1);
 | 
					        let imm_byte = cpu.read_byte(pc + 1);
 | 
				
			||||||
        let imm_word = (cpu.read_byte(pc + 2) as u16) << 8 | imm_byte as u16;
 | 
					        let imm_word = (cpu.read_byte(pc + 2) as u16) << 8 | imm_byte as u16;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        match instr {
 | 
					        match instr {
 | 
				
			||||||
            NOP => "NOP".to_string(),
 | 
					            // Unprefixed Instructions
 | 
				
			||||||
 | 
					            NOP => "NOP".into(),
 | 
				
			||||||
            LD(LDTarget::IndirectImmediateWord, LDSource::SP) => {
 | 
					            LD(LDTarget::IndirectImmediateWord, LDSource::SP) => {
 | 
				
			||||||
                format!("LD ({:#06X}), SP", imm_word)
 | 
					                format!("LD ({:#06X}), SP", imm_word).into()
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            STOP => "STOP".to_string(),
 | 
					            STOP => "STOP".into(),
 | 
				
			||||||
            JR(JpCond::Always) => format!("JR {}", imm_byte as i8),
 | 
					            JR(JpCond::Always) => format!("JR {}", imm_byte as i8).into(),
 | 
				
			||||||
            JR(cond) => format!("JR {:?} {}", cond, imm_byte as i8),
 | 
					            JR(cond) => format!("JR {:?} {}", cond, imm_byte as i8).into(),
 | 
				
			||||||
            LD(LDTarget::Group1(rp), LDSource::ImmediateWord) => {
 | 
					            LD(LDTarget::Group1(rp), LDSource::ImmediateWord) => {
 | 
				
			||||||
                format!("LD {:?} {:#06X}", rp, imm_word)
 | 
					                format!("LD {:?} {:#06X}", rp, imm_word).into()
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            ADD(AddTarget::HL, AddSource::Group1(rp)) => format!("ADD HL, {:?}", rp),
 | 
					            ADD(AddTarget::HL, AddSource::Group1(rp)) => format!("ADD HL, {:?}", rp).into(),
 | 
				
			||||||
            LD(LDTarget::IndirectGroup2(rp), LDSource::A) => format!("LD ({:?}), A", rp),
 | 
					            LD(LDTarget::IndirectGroup2(rp), LDSource::A) => format!("LD ({:?}), A", rp).into(),
 | 
				
			||||||
            LD(LDTarget::A, LDSource::IndirectGroup2(rp)) => format!("LD A, ({:?})", rp),
 | 
					            LD(LDTarget::A, LDSource::IndirectGroup2(rp)) => format!("LD A, ({:?})", rp).into(),
 | 
				
			||||||
 | 
					            INC(AllRegisters::Group1(rp)) => format!("INC {:?}", rp).into(),
 | 
				
			||||||
 | 
					            DEC(AllRegisters::Group1(rp)) => format!("DEC {:?}", rp).into(),
 | 
				
			||||||
 | 
					            INC(AllRegisters::Register(reg)) => format!("INC {:?}", reg).into(),
 | 
				
			||||||
 | 
					            DEC(AllRegisters::Register(reg)) => format!("DEC {:?}", reg).into(),
 | 
				
			||||||
 | 
					            LD(LDTarget::Register(reg), LDSource::ImmediateByte) => {
 | 
				
			||||||
 | 
					                format!("LD {:?}, {:#04X}", reg, imm_byte).into()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            RLCA => "RLCA".into(),
 | 
				
			||||||
 | 
					            RRCA => "RRCA".into(),
 | 
				
			||||||
 | 
					            RLA => "RLA".into(),
 | 
				
			||||||
 | 
					            RRA => "RRA".into(),
 | 
				
			||||||
 | 
					            DAA => "DAA".into(),
 | 
				
			||||||
 | 
					            CPL => "CPL".into(),
 | 
				
			||||||
 | 
					            SCF => "SCF".into(),
 | 
				
			||||||
 | 
					            CCF => "CCF".into(),
 | 
				
			||||||
 | 
					            HALT => "HALT".into(),
 | 
				
			||||||
 | 
					            LD(LDTarget::Register(left), LDSource::Register(right)) => {
 | 
				
			||||||
 | 
					                format!("LD {:?}, {:?}", left, right).into()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            ADD(AddTarget::A, AddSource::Register(reg)) => format!("ADD A, {:?}", reg).into(),
 | 
				
			||||||
 | 
					            ADC(AluSource::Register(reg)) => format!("ADC {:?}", reg).into(),
 | 
				
			||||||
 | 
					            SUB(AluSource::Register(reg)) => format!("SUB {:?}", reg).into(),
 | 
				
			||||||
 | 
					            SBC(AluSource::Register(reg)) => format!("SBC {:?}", reg).into(),
 | 
				
			||||||
 | 
					            AND(AluSource::Register(reg)) => format!("AND {:?}", reg).into(),
 | 
				
			||||||
 | 
					            XOR(AluSource::Register(reg)) => format!("XOR {:?}", reg).into(),
 | 
				
			||||||
 | 
					            OR(AluSource::Register(reg)) => format!("OR {:?}", reg).into(),
 | 
				
			||||||
 | 
					            CP(AluSource::Register(reg)) => format!("CP {:?}", reg).into(),
 | 
				
			||||||
 | 
					            RET(JpCond::Always) => "RET".into(),
 | 
				
			||||||
 | 
					            RET(cond) => format!("RET {:?}", cond).into(),
 | 
				
			||||||
 | 
					            LD(LDTarget::IoWithImmediateOffset, LDSource::A) => {
 | 
				
			||||||
 | 
					                format!("LD ({:#06X}) , A", 0xFF00 + imm_byte as u16).into()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            ADD(AddTarget::SP, AddSource::ImmediateSignedByte) => {
 | 
				
			||||||
 | 
					                format!("ADD SP, {}", imm_byte as i8).into()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            LD(LDTarget::A, LDSource::IoWithImmediateOffset) => {
 | 
				
			||||||
 | 
					                format!("LD A, ({:#06X})", 0xFF00 + imm_byte as u16).into()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            LDHL => format!("LD HL, SP + {}", imm_byte as i8).into(),
 | 
				
			||||||
 | 
					            POP(rp) => format!("POP {:?}", rp).into(),
 | 
				
			||||||
 | 
					            RETI => "RETI".into(),
 | 
				
			||||||
 | 
					            JP(JpCond::Always, JpLoc::HL) => "JP HL".into(),
 | 
				
			||||||
 | 
					            LD(LDTarget::SP, LDSource::HL) => "LD SP, HL".into(),
 | 
				
			||||||
 | 
					            JP(JpCond::Always, JpLoc::ImmediateWord) => format!("JP {:#06X}", imm_word).into(),
 | 
				
			||||||
 | 
					            JP(cond, JpLoc::ImmediateWord) => format!("JP {:?} {:#06X}", cond, imm_word).into(),
 | 
				
			||||||
 | 
					            LD(LDTarget::IoWithC, LDSource::A) => "LD (0xFF00 + C), A".into(),
 | 
				
			||||||
 | 
					            LD(LDTarget::IndirectImmediateWord, LDSource::A) => {
 | 
				
			||||||
 | 
					                format!("LD ({:#06X}), A", imm_byte).into()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            LD(LDTarget::A, LDSource::IoWithC) => "LD A, (0xFF00 + C)".into(),
 | 
				
			||||||
 | 
					            LD(LDTarget::A, LDSource::IndirectImmediateWord) => {
 | 
				
			||||||
 | 
					                format!("LD A, ({:#06X})", imm_word).into()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            DI => "DI".into(),
 | 
				
			||||||
 | 
					            EI => "EI".into(),
 | 
				
			||||||
 | 
					            CALL(cond) => format!("CALL {:?} {:#06X}", cond, imm_word).into(),
 | 
				
			||||||
 | 
					            PUSH(rp) => format!("PUSH {:?}", rp).into(),
 | 
				
			||||||
 | 
					            ADD(AddTarget::A, AddSource::ImmediateByte) => {
 | 
				
			||||||
 | 
					                format!("ADD A, {:#04X}", imm_byte).into()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            ADC(AluSource::ImmediateByte) => format!("ADC {:#04X}", imm_byte).into(),
 | 
				
			||||||
 | 
					            SUB(AluSource::ImmediateByte) => format!("SUB {:#04X}", imm_byte).into(),
 | 
				
			||||||
 | 
					            SBC(AluSource::ImmediateByte) => format!("SBC {:#04X}", imm_byte).into(),
 | 
				
			||||||
 | 
					            AND(AluSource::ImmediateByte) => format!("AND {:#04X}", imm_byte).into(),
 | 
				
			||||||
 | 
					            XOR(AluSource::ImmediateByte) => format!("XOR {:#04X}", imm_byte).into(),
 | 
				
			||||||
 | 
					            OR(AluSource::ImmediateByte) => format!("OR {:#04X}", imm_byte).into(),
 | 
				
			||||||
 | 
					            CP(AluSource::ImmediateByte) => format!("CP {:#04X}", imm_byte).into(),
 | 
				
			||||||
 | 
					            RST(v) => format!("RST {:#04X}", v).into(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            _ => todo!(),
 | 
					            // Prefixed Instructions
 | 
				
			||||||
 | 
					            RLC(reg) => format!("RLC {:?}", reg).into(),
 | 
				
			||||||
 | 
					            RRC(reg) => format!("RRC {:?}", reg).into(),
 | 
				
			||||||
 | 
					            RL(reg) => format!("RL {:?}", reg).into(),
 | 
				
			||||||
 | 
					            RR(reg) => format!("RR {:?}", reg).into(),
 | 
				
			||||||
 | 
					            SLA(reg) => format!("SLA {:?}", reg).into(),
 | 
				
			||||||
 | 
					            SRA(reg) => format!("SRA {:?}", reg).into(),
 | 
				
			||||||
 | 
					            SWAP(reg) => format!("SWAP {:?}", reg).into(),
 | 
				
			||||||
 | 
					            SRL(reg) => format!("SRL {:?}", reg).into(),
 | 
				
			||||||
 | 
					            BIT(bit, reg) => format!("BIT {}, {:?}", bit, reg).into(),
 | 
				
			||||||
 | 
					            RES(bit, reg) => format!("RES {}, {:?}", bit, reg).into(),
 | 
				
			||||||
 | 
					            SET(bit, reg) => format!("SET {}, {:?}", bit, reg).into(),
 | 
				
			||||||
 | 
					            _ => unreachable!("{:?} is an illegal instruction", instr),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										134
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										134
									
								
								src/main.rs
									
									
									
									
									
								
							@@ -1,39 +1,28 @@
 | 
				
			|||||||
 | 
					use std::path::PathBuf;
 | 
				
			||||||
use std::time::Instant;
 | 
					use std::time::Instant;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg};
 | 
					use clap::{arg, command, value_parser};
 | 
				
			||||||
use egui_wgpu_backend::RenderPass;
 | 
					use gb::gui::{EmuMode, Gui};
 | 
				
			||||||
use gb::gui::EmuMode;
 | 
					 | 
				
			||||||
use gb::{emu, gui};
 | 
					use gb::{emu, gui};
 | 
				
			||||||
use gilrs::Gilrs;
 | 
					use gilrs::Gilrs;
 | 
				
			||||||
use gui::GuiState;
 | 
					 | 
				
			||||||
use rodio::{OutputStream, Sink};
 | 
					use rodio::{OutputStream, Sink};
 | 
				
			||||||
use tracing_subscriber::EnvFilter;
 | 
					use tracing_subscriber::EnvFilter;
 | 
				
			||||||
use winit::event::{Event, WindowEvent};
 | 
					use winit::event::{Event, WindowEvent};
 | 
				
			||||||
use winit::event_loop::EventLoop;
 | 
					use winit::event_loop::{EventLoop, EventLoopBuilder};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const AUDIO_ENABLED: bool = true;
 | 
					const AUDIO_ENABLED: bool = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn main() {
 | 
					fn main() {
 | 
				
			||||||
    let app = App::new(crate_name!())
 | 
					    let m = command!()
 | 
				
			||||||
        .version(crate_version!())
 | 
					 | 
				
			||||||
        .author(crate_authors!())
 | 
					 | 
				
			||||||
        .about(crate_description!());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let m = app
 | 
					 | 
				
			||||||
        .arg(
 | 
					        .arg(
 | 
				
			||||||
            Arg::with_name("rom")
 | 
					            arg!(-b --boot <FILE> "path to boot ROM")
 | 
				
			||||||
                .value_name("ROM_FILE")
 | 
					                .required(false)
 | 
				
			||||||
                .takes_value(true)
 | 
					                .value_parser(value_parser!(PathBuf)),
 | 
				
			||||||
                .index(1)
 | 
					 | 
				
			||||||
                .help("Path to the Game ROM"),
 | 
					 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        .arg(
 | 
					        .arg(
 | 
				
			||||||
            Arg::with_name("boot")
 | 
					            arg!([ROM_FILE] "path to game ROM")
 | 
				
			||||||
                .short("b")
 | 
					                .required(true)
 | 
				
			||||||
                .long("boot")
 | 
					                .value_parser(value_parser!(PathBuf)),
 | 
				
			||||||
                .value_name("FILE")
 | 
					 | 
				
			||||||
                .takes_value(true)
 | 
					 | 
				
			||||||
                .help("Path to Boot ROM"),
 | 
					 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        .get_matches();
 | 
					        .get_matches();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -46,25 +35,8 @@ fn main() {
 | 
				
			|||||||
        .with_env_filter(EnvFilter::from_default_env())
 | 
					        .with_env_filter(EnvFilter::from_default_env())
 | 
				
			||||||
        .init();
 | 
					        .init();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // --Here lies a lot of winit + wgpu Boilerplate--
 | 
					    // Init CPU
 | 
				
			||||||
    let event_loop: EventLoop<Event<()>> = EventLoop::with_user_event();
 | 
					    let mut cpu = match m.get_one::<PathBuf>("boot") {
 | 
				
			||||||
    let window = gui::build_window(&event_loop).expect("build window");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let (instance, surface) = gui::create_surface(&window);
 | 
					 | 
				
			||||||
    let adapter = gui::request_adapter(&instance, &surface).expect("request adaptor");
 | 
					 | 
				
			||||||
    let (device, queue) = gui::request_device(&adapter).expect("request device");
 | 
					 | 
				
			||||||
    let format = surface
 | 
					 | 
				
			||||||
        .get_preferred_format(&adapter)
 | 
					 | 
				
			||||||
        .expect("get surface format");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let mut config = gui::surface_config(&window, format);
 | 
					 | 
				
			||||||
    surface.configure(&device, &config);
 | 
					 | 
				
			||||||
    let mut platform = gui::platform_desc(&window);
 | 
					 | 
				
			||||||
    let mut render_pass = RenderPass::new(&device, format, 1);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // We interrupt your boiler plate to initialize the emulator so that
 | 
					 | 
				
			||||||
    // we can copy it's empty pixel buffer to the GPU
 | 
					 | 
				
			||||||
    let mut cpu = match m.value_of("boot") {
 | 
					 | 
				
			||||||
        Some(path) => {
 | 
					        Some(path) => {
 | 
				
			||||||
            tracing::info!("User-provided boot ROM");
 | 
					            tracing::info!("User-provided boot ROM");
 | 
				
			||||||
            emu::from_boot_rom(path).expect("initialize emulator with custom boot rom")
 | 
					            emu::from_boot_rom(path).expect("initialize emulator with custom boot rom")
 | 
				
			||||||
@@ -75,20 +47,12 @@ fn main() {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Set up the wgpu (and then EGUI) texture we'll be working with.
 | 
					 | 
				
			||||||
    let texture_size = gui::texture_size();
 | 
					 | 
				
			||||||
    let texture = gui::create_texture(&device, texture_size);
 | 
					 | 
				
			||||||
    gui::write_to_texture(&queue, &texture, emu::pixel_buf(&cpu), texture_size);
 | 
					 | 
				
			||||||
    let texture_id = gui::expose_texture_to_egui(&mut render_pass, &device, &texture);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Load ROM if filepath was provided
 | 
					    // Load ROM if filepath was provided
 | 
				
			||||||
    if let Some(path) = m.value_of("rom") {
 | 
					    if let Some(path) = m.get_one::<PathBuf>("ROM_FILE") {
 | 
				
			||||||
        tracing::info!("User-provided cartridge ROM");
 | 
					        tracing::info!("User-provided cartridge ROM");
 | 
				
			||||||
        emu::read_game_rom(&mut cpu, path).expect("read game rom from path");
 | 
					        emu::read_game_rom(&mut cpu, path).expect("read game rom from path");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    emu::load_save(&mut cpu);
 | 
					    emu::load_save(&mut cpu);
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let rom_title = emu::rom_title(&cpu).to_string();
 | 
					    let rom_title = emu::rom_title(&cpu).to_string();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    tracing::info!("Initialize Gamepad");
 | 
					    tracing::info!("Initialize Gamepad");
 | 
				
			||||||
@@ -115,20 +79,20 @@ fn main() {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Set up state for the Immediate-mode GUI
 | 
					    // Set up state for the Immediate-mode GUI
 | 
				
			||||||
    let mut app = GuiState::new(rom_title);
 | 
					    let event_loop: EventLoop<Event<()>> = EventLoopBuilder::with_user_event().build();
 | 
				
			||||||
    let mut last_key = gui::unused_key();
 | 
					
 | 
				
			||||||
 | 
					    let mut app = Gui::new(rom_title, &event_loop, &cpu);
 | 
				
			||||||
 | 
					    let mut last_key = gui::unused_key(); // TODO: Fix this awful impl
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // used for egui animations
 | 
					    // used for egui animations
 | 
				
			||||||
    let start_time = Instant::now();
 | 
					    let start_time = Instant::now();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    event_loop.run(move |event, _, control_flow| {
 | 
					    event_loop.run(move |event, _, control_flow| {
 | 
				
			||||||
        platform.handle_event(&event);
 | 
					        app.handle_event(&event);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        match event {
 | 
					        match event {
 | 
				
			||||||
            Event::MainEventsCleared => {
 | 
					            Event::MainEventsCleared => {
 | 
				
			||||||
                if app.quit {
 | 
					                app.maybe_quit(&cpu, control_flow);
 | 
				
			||||||
                    emu::save_and_exit(&cpu, control_flow);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                match app.mode {
 | 
					                match app.mode {
 | 
				
			||||||
                    EmuMode::Running => emu::run_frame(&mut cpu, &mut gamepad, last_key),
 | 
					                    EmuMode::Running => emu::run_frame(&mut cpu, &mut gamepad, last_key),
 | 
				
			||||||
@@ -144,63 +108,15 @@ fn main() {
 | 
				
			|||||||
                // Input has been consumed, reset it
 | 
					                // Input has been consumed, reset it
 | 
				
			||||||
                last_key = gui::unused_key();
 | 
					                last_key = gui::unused_key();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                window.request_redraw();
 | 
					                app.request_redraw();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            Event::RedrawRequested(..) => {
 | 
					            Event::RedrawRequested(..) => {
 | 
				
			||||||
                platform.update_time(start_time.elapsed().as_secs_f64());
 | 
					                app.update_time(start_time.elapsed().as_secs_f64());
 | 
				
			||||||
 | 
					                app.paint(&cpu);
 | 
				
			||||||
                let data = emu::pixel_buf(&cpu);
 | 
					 | 
				
			||||||
                gui::write_to_texture(&queue, &texture, data, texture_size);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                let output_frame = match surface.get_current_texture() {
 | 
					 | 
				
			||||||
                    Ok(frame) => frame,
 | 
					 | 
				
			||||||
                    Err(e) => {
 | 
					 | 
				
			||||||
                        eprintln!("Dropped frame with error: {}", e);
 | 
					 | 
				
			||||||
                        return;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                };
 | 
					 | 
				
			||||||
                let output_view = gui::create_view(&output_frame);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // Begin to draw Egui components
 | 
					 | 
				
			||||||
                platform.begin_frame();
 | 
					 | 
				
			||||||
                gui::draw_egui(&cpu, &mut app, &platform.context(), texture_id);
 | 
					 | 
				
			||||||
                // End the UI frame. We could now handle the output and draw the UI with the backend.
 | 
					 | 
				
			||||||
                let (_, paint_commands) = platform.end_frame(Some(&window));
 | 
					 | 
				
			||||||
                let paint_jobs = platform.context().tessellate(paint_commands);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                let mut encoder = gui::create_command_encoder(&device);
 | 
					 | 
				
			||||||
                let screen_descriptor = gui::create_screen_descriptor(&window, &config);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // Upload all resources for the GPU.
 | 
					 | 
				
			||||||
                render_pass.update_texture(&device, &queue, &platform.context().texture());
 | 
					 | 
				
			||||||
                render_pass.update_user_textures(&device, &queue);
 | 
					 | 
				
			||||||
                render_pass.update_buffers(&device, &queue, &paint_jobs, &screen_descriptor);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // Record all render passes.
 | 
					 | 
				
			||||||
                gui::execute_render_pass(
 | 
					 | 
				
			||||||
                    &mut render_pass,
 | 
					 | 
				
			||||||
                    &mut encoder,
 | 
					 | 
				
			||||||
                    &output_view,
 | 
					 | 
				
			||||||
                    paint_jobs,
 | 
					 | 
				
			||||||
                    &screen_descriptor,
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                .expect("record render passes");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // Submit the commands.
 | 
					 | 
				
			||||||
                queue.submit(std::iter::once(encoder.finish()));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // Redraw egui
 | 
					 | 
				
			||||||
                output_frame.present();
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            Event::WindowEvent { event, .. } => match event {
 | 
					            Event::WindowEvent { event, .. } => match event {
 | 
				
			||||||
                WindowEvent::Resized(size) => {
 | 
					                WindowEvent::Resized(size) => app.resize(size),
 | 
				
			||||||
                    config.width = size.width;
 | 
					                WindowEvent::CloseRequested => emu::save_and_exit(&cpu, control_flow),
 | 
				
			||||||
                    config.height = size.height;
 | 
					 | 
				
			||||||
                    surface.configure(&device, &config);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                WindowEvent::CloseRequested => {
 | 
					 | 
				
			||||||
                    emu::save_and_exit(&cpu, control_flow);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                WindowEvent::KeyboardInput { input, .. } => last_key = input,
 | 
					                WindowEvent::KeyboardInput { input, .. } => last_key = input,
 | 
				
			||||||
                _ => {}
 | 
					                _ => {}
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										73
									
								
								src/ppu.rs
									
									
									
									
									
								
							
							
						
						
									
										73
									
								
								src/ppu.rs
									
									
									
									
									
								
							@@ -58,7 +58,7 @@ pub struct Ppu {
 | 
				
			|||||||
    fetch: PixelFetcher,
 | 
					    fetch: PixelFetcher,
 | 
				
			||||||
    fifo: PixelFifo,
 | 
					    fifo: PixelFifo,
 | 
				
			||||||
    obj_buffer: ObjectBuffer,
 | 
					    obj_buffer: ObjectBuffer,
 | 
				
			||||||
    pub(crate) frame_buf: Box<[u8; (GB_WIDTH * 4) * GB_HEIGHT]>,
 | 
					    pub(crate) frame_buf: FrameBuffer,
 | 
				
			||||||
    win_stat: WindowStatus,
 | 
					    win_stat: WindowStatus,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    scanline_start: bool,
 | 
					    scanline_start: bool,
 | 
				
			||||||
@@ -83,7 +83,9 @@ impl Ppu {
 | 
				
			|||||||
        if !self.ctrl.lcd_enabled() {
 | 
					        if !self.ctrl.lcd_enabled() {
 | 
				
			||||||
            if self.dot > 0 {
 | 
					            if self.dot > 0 {
 | 
				
			||||||
                // Check ensures this expensive operation only happens once
 | 
					                // Check ensures this expensive operation only happens once
 | 
				
			||||||
                self.frame_buf.copy_from_slice(BLANK_SCREEN.as_ref());
 | 
					                self.frame_buf
 | 
				
			||||||
 | 
					                    .get_mut(Device::Guest)
 | 
				
			||||||
 | 
					                    .copy_from_slice(BLANK_SCREEN.as_ref());
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            self.stat.set_mode(PpuMode::HBlank);
 | 
					            self.stat.set_mode(PpuMode::HBlank);
 | 
				
			||||||
@@ -178,6 +180,9 @@ impl Ppu {
 | 
				
			|||||||
                            self.int.set_lcd_stat(true);
 | 
					                            self.int.set_lcd_stat(true);
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        // Screen is done drawing
 | 
				
			||||||
 | 
					                        self.frame_buf.swap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        PpuMode::VBlank
 | 
					                        PpuMode::VBlank
 | 
				
			||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
                        if self.stat.oam_int() {
 | 
					                        if self.stat.oam_int() {
 | 
				
			||||||
@@ -411,8 +416,8 @@ impl Ppu {
 | 
				
			|||||||
                let x = self.x_pos as usize;
 | 
					                let x = self.x_pos as usize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                let i = (GB_WIDTH * 4) * y + (x * 4);
 | 
					                let i = (GB_WIDTH * 4) * y + (x * 4);
 | 
				
			||||||
                self.frame_buf[i..(i + rgba.len())].copy_from_slice(&rgba);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                self.frame_buf.get_mut(Device::Guest)[i..(i + rgba.len())].copy_from_slice(&rgba);
 | 
				
			||||||
                self.x_pos += 1;
 | 
					                self.x_pos += 1;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -474,7 +479,7 @@ impl Default for Ppu {
 | 
				
			|||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
            vram: Box::new([0u8; VRAM_SIZE]),
 | 
					            vram: Box::new([0u8; VRAM_SIZE]),
 | 
				
			||||||
            dot: Default::default(),
 | 
					            dot: Default::default(),
 | 
				
			||||||
            frame_buf: Box::new([0; GB_WIDTH * GB_HEIGHT * 4]),
 | 
					            frame_buf: FrameBuffer::new().expect("create frame buffers"),
 | 
				
			||||||
            int: Default::default(),
 | 
					            int: Default::default(),
 | 
				
			||||||
            ctrl: LCDControl(0),
 | 
					            ctrl: LCDControl(0),
 | 
				
			||||||
            monochrome: Default::default(),
 | 
					            monochrome: Default::default(),
 | 
				
			||||||
@@ -949,3 +954,63 @@ pub(crate) mod dbg {
 | 
				
			|||||||
        ppu.dot
 | 
					        ppu.dot
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug)]
 | 
				
			||||||
 | 
					pub struct FrameBuffer {
 | 
				
			||||||
 | 
					    buf: [Box<[u8; Self::FRAME_LEN]>; 2],
 | 
				
			||||||
 | 
					    current: bool,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(PartialEq)]
 | 
				
			||||||
 | 
					pub enum Device {
 | 
				
			||||||
 | 
					    Guest,
 | 
				
			||||||
 | 
					    Host,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl FrameBuffer {
 | 
				
			||||||
 | 
					    const FRAME_LEN: usize = GB_WIDTH * std::mem::size_of::<u32>() * GB_HEIGHT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn new() -> Result<Self, FrameBufferError> {
 | 
				
			||||||
 | 
					        Ok(Self {
 | 
				
			||||||
 | 
					            buf: [
 | 
				
			||||||
 | 
					                vec![0; Self::FRAME_LEN]
 | 
				
			||||||
 | 
					                    .into_boxed_slice()
 | 
				
			||||||
 | 
					                    .try_into()
 | 
				
			||||||
 | 
					                    .map_err(|_| FrameBufferError::TryFrom)?,
 | 
				
			||||||
 | 
					                vec![0; Self::FRAME_LEN]
 | 
				
			||||||
 | 
					                    .into_boxed_slice()
 | 
				
			||||||
 | 
					                    .try_into()
 | 
				
			||||||
 | 
					                    .map_err(|_| FrameBufferError::TryFrom)?,
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            current: false,
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn swap(&mut self) {
 | 
				
			||||||
 | 
					        self.current = !self.current;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn get_mut(&mut self, device: Device) -> &mut [u8; Self::FRAME_LEN] {
 | 
				
			||||||
 | 
					        let idx = match device {
 | 
				
			||||||
 | 
					            Device::Guest => self.current,
 | 
				
			||||||
 | 
					            Device::Host => !self.current,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        &mut *self.buf[idx as usize]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn get(&self, device: Device) -> &[u8; Self::FRAME_LEN] {
 | 
				
			||||||
 | 
					        let idx = match device {
 | 
				
			||||||
 | 
					            Device::Guest => self.current,
 | 
				
			||||||
 | 
					            Device::Host => !self.current,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        &*self.buf[idx as usize]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, thiserror::Error)]
 | 
				
			||||||
 | 
					pub enum FrameBufferError {
 | 
				
			||||||
 | 
					    #[error("Failed to coerce boxed slice to boxed array")]
 | 
				
			||||||
 | 
					    TryFrom,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -335,7 +335,7 @@ impl From<ObjectPaletteKind> for u8 {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone, Copy, PartialEq)]
 | 
					#[derive(Debug, Clone, Copy, Eq, PartialEq)]
 | 
				
			||||||
pub enum RenderPriority {
 | 
					pub enum RenderPriority {
 | 
				
			||||||
    Object = 0,
 | 
					    Object = 0,
 | 
				
			||||||
    BackgroundAndWindow = 1,
 | 
					    BackgroundAndWindow = 1,
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user