commit 4f802b33e2ecd0b6fe837437ad5df6a5a4a5f5c4 Author: Franco Colmenarez Date: Sun Jun 26 17:49:35 2022 -0500 First commit: flag registers and imgui example code (stolen from gh) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4ef3c07 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +/Cargo.lock +imgui.ini diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0fae707 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,3 @@ +[workspace] +members = ["snes-core", "snes-frontend"] +resolver = "2" diff --git a/snes-core/Cargo.lock b/snes-core/Cargo.lock new file mode 100644 index 0000000..343e898 --- /dev/null +++ b/snes-core/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "snes" +version = "0.1.0" diff --git a/snes-core/Cargo.toml b/snes-core/Cargo.toml new file mode 100644 index 0000000..6add9bf --- /dev/null +++ b/snes-core/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "snes-core" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/snes-core/src/bus.rs b/snes-core/src/bus.rs new file mode 100644 index 0000000..e69de29 diff --git a/snes-core/src/cpu.rs b/snes-core/src/cpu.rs new file mode 100644 index 0000000..e10d5fd --- /dev/null +++ b/snes-core/src/cpu.rs @@ -0,0 +1,185 @@ +struct Registers { + sp: u16, // Stack pointer + x: u16, // Index X + y: u16, // Index Y + a: u16, // Accumulator + p: u8, // Status register + d: u16, // Direct page register + pbr: u8, // Program bank register + dbr: u8, // Data bank register + pc: u16, // Program counter +} + +impl Registers { + pub fn new() -> Self { + Self { + sp: 0, + x: 0, + y: 0, + a: 0, + p: 0, + d: 0, + pbr: 0, + dbr: 0, + pc: 0, + } + } + + pub fn get_p(&self) -> u8 { + self.p + } + + pub fn set_p(&mut self, val: u8) { + self.p = val; + } + + pub fn get_carry_flag(&self) -> bool { + (self.p & 0b0000_0001) == 1 + } + + pub fn set_carry_flag(&mut self, val: bool) { + self.p = self.p & 0b1111_1110; + self.p = self.p | (val as u8); + } + + pub fn get_zero_flag(&self) -> bool { + ((self.p & 0b0000_0010) >> 1) == 1 + } + + pub fn set_zero_flag(&mut self, val: bool) { + self.p = self.p & 0b1111_1101; + self.p = self.p | ((val as u8) << 1); + } + + pub fn get_irq_disable_flag(&self) -> bool { + ((self.p & 0b0000_0100) >> 2) == 1 + } + + pub fn set_irq_disable_flag(&mut self, val: bool) { + self.p = self.p & 0b1111_1011; + self.p = self.p | ((val as u8) << 2); + } + + pub fn get_decimal_mode_flag(&self) -> bool { + ((self.p & 0b0000_1000) >> 3) == 1 + } + + pub fn set_decimal_mode_flag(&mut self, val: bool) { + self.p = self.p & 0b1111_0111; + self.p = self.p | ((val as u8) << 3); + } + + pub fn get_break_instruction_flag(&self) -> bool { + ((self.p & 0b0001_0000) >> 4) == 1 + } + + pub fn set_break_instruction_flag(&mut self, val: bool) { + self.p = self.p & 0b1110_1111; + self.p = self.p | ((val as u8) << 4); + } + + pub fn get_overflow_flag(&self) -> bool { + ((self.p & 0b0100_0000) >> 6) == 1 + } + + pub fn set_overflow_flag(&mut self, val: bool) { + self.p = self.p & 0b1011_1111; + self.p = self.p | ((val as u8) << 6); + } + + pub fn get_negative_flag(&self) -> bool { + ((self.p & 0b1000_0000) >> 7) == 1 + } + + pub fn set_negative_flag(&mut self, val: bool) { + self.p = self.p & 0b0111_1111; + self.p = self.p | ((val as u8) << 7); + } +} + +pub struct CPU { + registers: Registers, + cycles: usize, +} + +impl CPU { + pub fn new() -> Self { + Self { + registers: Registers::new(), + cycles: 0, + } + } + + pub fn adc_const(&mut self, value: u16) { + self.cycles += 2; + } +} + + +#[cfg(test)] +mod cpu_instructions_tests { + use super::*; + + + #[test] + fn test_adc_const() { + let mut cpu = CPU::new(); + assert!(true); + } +} + + +#[cfg(test)] +mod cpu_registers_tests { + use super::*; + + + #[test] + fn test_status_registers() { + let mut registers = Registers::new(); + registers.set_p(0x00); + + registers.set_carry_flag(true); + assert!(registers.get_carry_flag()); + assert_eq!(registers.get_p(), 0b0000_0001); + registers.set_carry_flag(false); + assert!(!registers.get_carry_flag()); + assert_eq!(registers.get_p(), 0b0000_0000); + + registers.set_zero_flag(true); + assert!(registers.get_zero_flag()); + assert_eq!(registers.get_p(), 0b0000_0010); + registers.set_zero_flag(false); + assert!(!registers.get_zero_flag()); + assert_eq!(registers.get_p(), 0b0000_0000); + + registers.set_irq_disable_flag(true); + assert!(registers.get_irq_disable_flag()); + assert_eq!(registers.get_p(), 0b0000_0100); + registers.set_irq_disable_flag(false); + assert!(!registers.get_irq_disable_flag()); + assert_eq!(registers.get_p(), 0b0000_0000); + + registers.set_decimal_mode_flag(true); + assert!(registers.get_decimal_mode_flag()); + assert_eq!(registers.get_p(), 0b0000_1000); + registers.set_decimal_mode_flag(false); + assert!(!registers.get_decimal_mode_flag()); + assert_eq!(registers.get_p(), 0b0000_0000); + + registers.set_break_instruction_flag(true); + assert!(registers.get_break_instruction_flag()); + assert_eq!(registers.get_p(), 0b0001_0000); + registers.set_break_instruction_flag(false); + assert!(!registers.get_break_instruction_flag()); + assert_eq!(registers.get_p(), 0b0000_0000); + + registers.set_overflow_flag(true); + assert!(registers.get_overflow_flag()); + assert_eq!(registers.get_p(), 0b0100_0000); + registers.set_overflow_flag(false); + assert!(!registers.get_overflow_flag()); + assert_eq!(registers.get_p(), 0b0000_0000); + } +} + diff --git a/snes-core/src/lib.rs b/snes-core/src/lib.rs new file mode 100644 index 0000000..3bfb62f --- /dev/null +++ b/snes-core/src/lib.rs @@ -0,0 +1 @@ +pub mod cpu; diff --git a/snes-core/src/ppu.rs b/snes-core/src/ppu.rs new file mode 100644 index 0000000..e69de29 diff --git a/snes-core/src/spc700.rs b/snes-core/src/spc700.rs new file mode 100644 index 0000000..e69de29 diff --git a/snes-frontend/Cargo.lock b/snes-frontend/Cargo.lock new file mode 100644 index 0000000..343e898 --- /dev/null +++ b/snes-frontend/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "snes" +version = "0.1.0" diff --git a/snes-frontend/Cargo.toml b/snes-frontend/Cargo.toml new file mode 100644 index 0000000..77e2036 --- /dev/null +++ b/snes-frontend/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "snes-frontend" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +winit = "0.26.1" +imgui = "0.8.2" +wgpu = "0.12.0" +imgui-wgpu = "0.19.0" +pollster = "0.2.5" +[dependencies.imgui-winit-support] +version = "0.8.2" +features = ["winit-26"] +default-features = false diff --git a/snes-frontend/src/main.rs b/snes-frontend/src/main.rs new file mode 100644 index 0000000..cfd6853 --- /dev/null +++ b/snes-frontend/src/main.rs @@ -0,0 +1,231 @@ +extern crate imgui_winit_support; + +use imgui::*; +use imgui_wgpu::{Renderer, RendererConfig}; +use pollster::block_on; + +use std::time::Instant; +use winit::{ + dpi::LogicalSize, + event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::Window, +}; + +fn main() { + // Set up window and GPU + let event_loop = EventLoop::new(); + + let instance = wgpu::Instance::new(wgpu::Backends::PRIMARY); + + let (window, size, surface) = { + let version = env!("CARGO_PKG_VERSION"); + + let window = Window::new(&event_loop).unwrap(); + window.set_inner_size(LogicalSize { + width: 1280.0, + height: 720.0, + }); + window.set_title(&format!("imgui-wgpu {}", version)); + let size = window.inner_size(); + + let surface = unsafe { instance.create_surface(&window) }; + + (window, size, surface) + }; + + let hidpi_factor = window.scale_factor(); + + let adapter = block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::HighPerformance, + compatible_surface: Some(&surface), + force_fallback_adapter: false, + })) + .unwrap(); + + let (device, queue) = + block_on(adapter.request_device(&wgpu::DeviceDescriptor::default(), None)).unwrap(); + + // Set up swap chain + let surface_desc = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: wgpu::TextureFormat::Bgra8UnormSrgb, + width: size.width as u32, + height: size.height as u32, + present_mode: wgpu::PresentMode::Mailbox, + }; + + surface.configure(&device, &surface_desc); + + // Set up dear imgui + let mut imgui = imgui::Context::create(); + let mut platform = imgui_winit_support::WinitPlatform::init(&mut imgui); + platform.attach_window( + imgui.io_mut(), + &window, + imgui_winit_support::HiDpiMode::Default, + ); + imgui.set_ini_filename(None); + + let font_size = (13.0 * hidpi_factor) as f32; + imgui.io_mut().font_global_scale = (1.0 / hidpi_factor) as f32; + + imgui.fonts().add_font(&[FontSource::DefaultFontData { + config: Some(imgui::FontConfig { + oversample_h: 1, + pixel_snap_h: true, + size_pixels: font_size, + ..Default::default() + }), + }]); + + // + // Set up dear imgui wgpu renderer + // + let clear_color = wgpu::Color { + r: 0.1, + g: 0.2, + b: 0.3, + a: 1.0, + }; + + let renderer_config = RendererConfig { + texture_format: surface_desc.format, + ..Default::default() + }; + + let mut renderer = Renderer::new(&mut imgui, &device, &queue, renderer_config); + + let mut last_frame = Instant::now(); + let mut demo_open = true; + + let mut last_cursor = None; + + // Event loop + event_loop.run(move |event, _, control_flow| { + *control_flow = if cfg!(feature = "metal-auto-capture") { + ControlFlow::Exit + } else { + ControlFlow::Poll + }; + match event { + Event::WindowEvent { + event: WindowEvent::Resized(_), + .. + } => { + let size = window.inner_size(); + + let surface_desc = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: wgpu::TextureFormat::Bgra8UnormSrgb, + width: size.width as u32, + height: size.height as u32, + present_mode: wgpu::PresentMode::Mailbox, + }; + + surface.configure(&device, &surface_desc); + } + Event::WindowEvent { + event: + WindowEvent::KeyboardInput { + input: + KeyboardInput { + virtual_keycode: Some(VirtualKeyCode::Escape), + state: ElementState::Pressed, + .. + }, + .. + }, + .. + } + | Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => { + *control_flow = ControlFlow::Exit; + } + Event::MainEventsCleared => window.request_redraw(), + Event::RedrawEventsCleared => { + let delta_s = last_frame.elapsed(); + let now = Instant::now(); + imgui.io_mut().update_delta_time(now - last_frame); + last_frame = now; + + let frame = match surface.get_current_texture() { + Ok(frame) => frame, + Err(e) => { + eprintln!("dropped frame: {:?}", e); + return; + } + }; + platform + .prepare_frame(imgui.io_mut(), &window) + .expect("Failed to prepare frame"); + let ui = imgui.frame(); + + { + let window = imgui::Window::new("Hello world"); + window + .size([300.0, 100.0], Condition::FirstUseEver) + .build(&ui, || { + ui.text("Hello world!"); + ui.text("This...is...imgui-rs on WGPU!"); + ui.separator(); + let mouse_pos = ui.io().mouse_pos; + ui.text(format!( + "Mouse Position: ({:.1},{:.1})", + mouse_pos[0], mouse_pos[1] + )); + }); + + let window = imgui::Window::new("Hello too"); + window + .size([400.0, 200.0], Condition::FirstUseEver) + .position([400.0, 200.0], Condition::FirstUseEver) + .build(&ui, || { + ui.text(format!("Frametime: {:?}", delta_s)); + }); + + ui.show_demo_window(&mut demo_open); + } + + let mut encoder: wgpu::CommandEncoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + + if last_cursor != Some(ui.mouse_cursor()) { + last_cursor = Some(ui.mouse_cursor()); + platform.prepare_render(&ui, &window); + } + + let view = frame + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(clear_color), + store: true, + }, + }], + depth_stencil_attachment: None, + }); + + renderer + .render(ui.render(), &queue, &device, &mut rpass) + .expect("Rendering failed"); + + drop(rpass); + + queue.submit(Some(encoder.finish())); + + frame.present(); + } + _ => (), + } + + platform.handle_event(imgui.io_mut(), &window, &event); + }); +}