chore: drop pixels-rs and add egui w/ wgpu backend
This commit is contained in:
parent
ce5d58e1d2
commit
6f5e863645
|
@ -2,6 +2,16 @@
|
|||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "ab_glyph"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20b228f2c198f98d4337ceb560333fb12cbb2f4948a953bf8c57d09deb219603"
|
||||
dependencies = [
|
||||
"ab_glyph_rasterizer",
|
||||
"owned_ttf_parser 0.13.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ab_glyph_rasterizer"
|
||||
version = "0.1.4"
|
||||
|
@ -14,6 +24,17 @@ version = "0.4.7"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e"
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "alsa"
|
||||
version = "0.5.0"
|
||||
|
@ -88,6 +109,12 @@ dependencies = [
|
|||
"libloading 0.7.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atomic_refcell"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73b5e5f48b927f04e952dedc932f31995a65a0bf65ec971c74436e51bf6e970d"
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
|
@ -678,6 +705,66 @@ version = "1.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
|
||||
|
||||
[[package]]
|
||||
name = "egui"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c8d416a3343cbfc6f4d17bb1cba46b4d7efecb9ee541967763e0b5e04e5fae7"
|
||||
dependencies = [
|
||||
"ahash 0.7.6",
|
||||
"epaint",
|
||||
"nohash-hasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "egui_wgpu_backend"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe7d53df01ecac5951500cd5637b99c0334dad353fce9b203a9bf66b5f791a6b"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"epi",
|
||||
"wgpu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "egui_winit_platform"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0ae306ae9cd296ed3178d518fd0d9edf45f176cfdfe31ff77b7051c5f1d5278"
|
||||
dependencies = [
|
||||
"egui",
|
||||
"winit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "emath"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24a1aaa922d55da6a2bf32957c3d153e7fb9d52ed8d69777a75092240172eb6e"
|
||||
|
||||
[[package]]
|
||||
name = "epaint"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16bb4d3b8bbbd132c99d2a5efec8567e8b6d09b742f758ae6cf1e4b104fe0231"
|
||||
dependencies = [
|
||||
"ab_glyph",
|
||||
"ahash 0.7.6",
|
||||
"atomic_refcell",
|
||||
"emath",
|
||||
"nohash-hasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "epi"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f5e4e08127f9b86e2c450c96a3032764b63546eb170c2fc54684dc70ff3fc82"
|
||||
dependencies = [
|
||||
"egui",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
|
@ -716,14 +803,17 @@ dependencies = [
|
|||
"bitfield",
|
||||
"clap",
|
||||
"directories-next",
|
||||
"egui",
|
||||
"egui_wgpu_backend",
|
||||
"egui_winit_platform",
|
||||
"gilrs",
|
||||
"pixels",
|
||||
"pollster",
|
||||
"rodio",
|
||||
"rtrb",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"wgpu",
|
||||
"winit",
|
||||
"winit_input_helper",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -832,7 +922,7 @@ version = "0.9.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"ahash 0.4.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1285,6 +1375,12 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nohash-hasher"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "5.1.2"
|
||||
|
@ -1420,7 +1516,16 @@ version = "0.6.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f923fb806c46266c02ab4a5b239735c144bdeda724a50ed058e5226f594cde3"
|
||||
dependencies = [
|
||||
"ttf-parser",
|
||||
"ttf-parser 0.6.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "owned_ttf_parser"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65ee3f72636e6f164cc41c9f9057f4e58c4e13507699ea7f5e5242b64b8198ee"
|
||||
dependencies = [
|
||||
"ttf-parser 0.13.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1466,20 +1571,6 @@ version = "0.2.7"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443"
|
||||
|
||||
[[package]]
|
||||
name = "pixels"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94ab1e297051c39cc7b7511e7e2b3ab151f14aff6a44e73bdde652b1e2190950"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"pollster",
|
||||
"raw-window-handle",
|
||||
"thiserror",
|
||||
"ultraviolet",
|
||||
"wgpu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.20"
|
||||
|
@ -1643,7 +1734,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "dc7c727aded0be18c5b80c1640eae0ac8e396abf6fa8477d96cb37d18ee5ec59"
|
||||
dependencies = [
|
||||
"ab_glyph_rasterizer",
|
||||
"owned_ttf_parser",
|
||||
"owned_ttf_parser 0.6.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1663,15 +1754,6 @@ version = "1.0.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
||||
|
||||
[[package]]
|
||||
name = "safe_arch"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1ff3d6d9696af502cc3110dacce942840fb06ff4514cad92236ecc455f2ce05"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
|
@ -2031,13 +2113,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "3e5d7cd7ab3e47dda6e56542f4bbf3824c15234958c6e1bd6aaa347e93499fdc"
|
||||
|
||||
[[package]]
|
||||
name = "ultraviolet"
|
||||
version = "0.8.1"
|
||||
name = "ttf-parser"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16b9e3507eba17043af05c8a72fce3ec2c24b58945f45732e71dbc6646d904a7"
|
||||
dependencies = [
|
||||
"wide",
|
||||
]
|
||||
checksum = "3e835d06ed78a500d3d0e431a20c18ff5544b3f6e11376e834370cfd35e8948e"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
|
@ -2324,16 +2403,6 @@ dependencies = [
|
|||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wide"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46bbe7c604a27ca0b05c5503221e76da628225b568e6f1280b42dbad3b72d89b"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"safe_arch",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
|
@ -2397,15 +2466,6 @@ dependencies = [
|
|||
"x11-dl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winit_input_helper"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d8af691f04e6d8a892e80a2176221b2c13d5832a8929d8c0fed1e3e3d4fe831"
|
||||
dependencies = [
|
||||
"winit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x11-dl"
|
||||
version = "2.19.1"
|
||||
|
|
|
@ -11,9 +11,12 @@ anyhow = "1.0"
|
|||
bitfield = "0.13"
|
||||
clap = "2.33"
|
||||
gilrs = "0.8"
|
||||
pixels = "0.7"
|
||||
winit = "0.25"
|
||||
winit_input_helper = "0.10"
|
||||
egui = "0.15"
|
||||
wgpu = "0.11"
|
||||
egui_wgpu_backend = "0.14"
|
||||
egui_winit_platform = "0.11"
|
||||
pollster = "0.2"
|
||||
rodio = "0.14"
|
||||
rtrb = "0.2"
|
||||
directories-next = "2.0"
|
||||
|
|
|
@ -73,7 +73,10 @@ impl Bus {
|
|||
fn tick(&mut self, limit: u8) {
|
||||
for _ in 0..limit {
|
||||
self.timer.tick();
|
||||
self.cart.as_mut().map(|cart| cart.tick());
|
||||
|
||||
if let Some(c) = self.cart.as_mut() {
|
||||
c.tick()
|
||||
}
|
||||
self.ppu.tick();
|
||||
self.apu.tick(self.timer.divider);
|
||||
self.dma_tick()
|
||||
|
|
|
@ -545,7 +545,7 @@ impl MBCIo for MBC3 {
|
|||
}
|
||||
Minute => self.rtc.min = byte & 0x3F,
|
||||
Hour => self.rtc.hr = byte & 0x1F,
|
||||
DayLow => self.rtc.day_low = byte & 0xFF,
|
||||
DayLow => self.rtc.day_low = byte,
|
||||
DayHigh => self.rtc.day_high = (byte & 0xC1).into(),
|
||||
},
|
||||
_ => {}
|
||||
|
|
|
@ -8,14 +8,14 @@ use std::fs::File;
|
|||
use std::io::{Read, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
use winit_input_helper::WinitInputHelper;
|
||||
use winit::event::KeyboardInput;
|
||||
|
||||
pub const SM83_CYCLE_TIME: Duration = Duration::from_nanos(1_000_000_000 / SM83_CLOCK_SPEED);
|
||||
pub const CYCLES_IN_FRAME: Cycle = 456 * 154; // 456 Cycles times 154 scanlines
|
||||
pub(crate) const SM83_CLOCK_SPEED: u64 = 0x40_0000; // Hz which is 4.194304Mhz
|
||||
const DEFAULT_TITLE: &str = "DMG-01 Emulator";
|
||||
|
||||
pub fn run_frame(emu: &mut Emulator, gamepad: &mut Gilrs, key: &WinitInputHelper) -> Cycle {
|
||||
pub fn run_frame(emu: &mut Emulator, gamepad: &mut Gilrs, key: KeyboardInput) -> Cycle {
|
||||
let mut elapsed = 0;
|
||||
|
||||
if let Some(event) = gamepad.next_event() {
|
||||
|
@ -30,8 +30,8 @@ pub fn run_frame(emu: &mut Emulator, gamepad: &mut Gilrs, key: &WinitInputHelper
|
|||
elapsed
|
||||
}
|
||||
|
||||
pub fn draw_frame(emu: &Emulator, buf: &mut [u8; GB_HEIGHT * GB_WIDTH * 4]) {
|
||||
buf.copy_from_slice(emu.cpu.bus.ppu.frame_buf.as_ref());
|
||||
pub fn pixel_buf(emu: &Emulator) -> &[u8; GB_HEIGHT * GB_WIDTH * 4] {
|
||||
emu.cpu.bus.ppu.frame_buf.as_ref()
|
||||
}
|
||||
|
||||
pub struct Emulator {
|
||||
|
|
|
@ -0,0 +1,238 @@
|
|||
use egui::{ClippedMesh, CtxRef, TextureId};
|
||||
use egui_wgpu_backend::{BackendError, RenderPass, ScreenDescriptor};
|
||||
use egui_winit_platform::Platform;
|
||||
use wgpu::{
|
||||
Adapter, CommandEncoder, Device, Extent3d, FilterMode, Instance, Queue, RequestDeviceError,
|
||||
Surface, SurfaceConfiguration, SurfaceTexture, Texture, TextureFormat, TextureUsages,
|
||||
TextureView,
|
||||
};
|
||||
use winit::error::OsError;
|
||||
use winit::event::{ElementState, KeyboardInput};
|
||||
use winit::event_loop::EventLoop;
|
||||
use winit::window::Window;
|
||||
|
||||
use crate::{GB_HEIGHT, GB_WIDTH};
|
||||
|
||||
const EGUI_DIMENSIONS: (usize, usize) = (1280, 720);
|
||||
const FILTER_MODE: FilterMode = FilterMode::Nearest;
|
||||
|
||||
const SCALE: f32 = 3.0;
|
||||
|
||||
/// Holds GUI State
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GuiState {
|
||||
/// When true, egui winit should exit the application
|
||||
pub quit: bool,
|
||||
pub title: String,
|
||||
}
|
||||
|
||||
impl GuiState {
|
||||
pub fn new(title: String) -> Self {
|
||||
Self {
|
||||
title,
|
||||
quit: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This is meant to serve as a Default value. Therefore this KeyboardInput should **always** represent an **unused** key
|
||||
/// performing an **unused** action
|
||||
///
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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("DMG-01 Emulator")
|
||||
.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,
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn expose_texture_to_egui(
|
||||
render_pass: &mut RenderPass,
|
||||
device: &Device,
|
||||
texture: &Texture,
|
||||
) -> TextureId {
|
||||
render_pass.egui_texture_from_wgpu_texture(device, texture, FILTER_MODE)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn create_view(frame: &SurfaceTexture) -> TextureView {
|
||||
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(app: &mut GuiState, ctx: &CtxRef, texture_id: TextureId) {
|
||||
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
|
||||
egui::menu::menu(ui, "File", |ui| {
|
||||
if ui.button("Quit").clicked() {
|
||||
app.quit = true;
|
||||
}
|
||||
});
|
||||
|
||||
egui::Window::new(&app.title).show(ctx, |ui| {
|
||||
ui.image(
|
||||
texture_id,
|
||||
[GB_WIDTH as f32 * SCALE, GB_HEIGHT as f32 * SCALE],
|
||||
);
|
||||
})
|
||||
});
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
use gilrs::{Button, Event as GamepadEvent, EventType as GamepadEventType};
|
||||
use winit_input_helper::WinitInputHelper;
|
||||
use winit::event::{ElementState, KeyboardInput, VirtualKeyCode};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Joypad {
|
||||
|
@ -111,68 +111,33 @@ impl ButtonEvent {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn handle_keyboard_input(pad: &mut Joypad, input: &WinitInputHelper) {
|
||||
use winit::event::VirtualKeyCode;
|
||||
|
||||
// TODO: What do I have to do to get a match statement here?
|
||||
|
||||
pub(crate) fn handle_keyboard_input(pad: &mut Joypad, key: KeyboardInput) {
|
||||
let state = &mut pad.ext;
|
||||
let irq = &mut pad.interrupt;
|
||||
|
||||
if input.key_pressed(VirtualKeyCode::Down) {
|
||||
state.dpad_down.update(true, irq);
|
||||
}
|
||||
if input.key_released(VirtualKeyCode::Down) {
|
||||
state.dpad_down.update(false, irq);
|
||||
}
|
||||
|
||||
if input.key_pressed(VirtualKeyCode::Up) {
|
||||
state.dpad_up.update(true, irq);
|
||||
}
|
||||
if input.key_released(VirtualKeyCode::Up) {
|
||||
state.dpad_up.update(false, irq);
|
||||
}
|
||||
|
||||
if input.key_pressed(VirtualKeyCode::Left) {
|
||||
state.dpad_left.update(true, irq);
|
||||
}
|
||||
if input.key_released(VirtualKeyCode::Left) {
|
||||
state.dpad_left.update(false, irq);
|
||||
}
|
||||
|
||||
if input.key_pressed(VirtualKeyCode::Right) {
|
||||
state.dpad_right.update(true, irq);
|
||||
}
|
||||
if input.key_released(VirtualKeyCode::Right) {
|
||||
state.dpad_right.update(false, irq);
|
||||
}
|
||||
|
||||
if input.key_pressed(VirtualKeyCode::Return) {
|
||||
state.start.update(true, irq);
|
||||
}
|
||||
if input.key_released(VirtualKeyCode::Return) {
|
||||
state.start.update(false, irq);
|
||||
}
|
||||
|
||||
if input.key_pressed(VirtualKeyCode::RShift) {
|
||||
state.select.update(true, irq);
|
||||
}
|
||||
if input.key_released(VirtualKeyCode::RShift) {
|
||||
state.select.update(false, irq);
|
||||
}
|
||||
|
||||
if input.key_pressed(VirtualKeyCode::Z) {
|
||||
state.south.update(true, irq);
|
||||
}
|
||||
if input.key_released(VirtualKeyCode::Z) {
|
||||
state.south.update(false, irq);
|
||||
}
|
||||
|
||||
if input.key_pressed(VirtualKeyCode::X) {
|
||||
state.east.update(true, irq);
|
||||
}
|
||||
if input.key_released(VirtualKeyCode::X) {
|
||||
state.east.update(false, irq);
|
||||
match key.state {
|
||||
ElementState::Pressed => match key.virtual_keycode {
|
||||
Some(VirtualKeyCode::Down) => state.dpad_down.update(true, irq),
|
||||
Some(VirtualKeyCode::Up) => state.dpad_up.update(true, irq),
|
||||
Some(VirtualKeyCode::Left) => state.dpad_left.update(true, irq),
|
||||
Some(VirtualKeyCode::Right) => state.dpad_right.update(true, irq),
|
||||
Some(VirtualKeyCode::Return) => state.start.update(true, irq),
|
||||
Some(VirtualKeyCode::RShift) => state.select.update(true, irq),
|
||||
Some(VirtualKeyCode::Z) => state.south.update(true, irq),
|
||||
Some(VirtualKeyCode::X) => state.east.update(true, irq),
|
||||
None | Some(_) => {}
|
||||
},
|
||||
ElementState::Released => match key.virtual_keycode {
|
||||
Some(VirtualKeyCode::Down) => state.dpad_down.update(false, irq),
|
||||
Some(VirtualKeyCode::Up) => state.dpad_up.update(false, irq),
|
||||
Some(VirtualKeyCode::Left) => state.dpad_left.update(false, irq),
|
||||
Some(VirtualKeyCode::Right) => state.dpad_right.update(false, irq),
|
||||
Some(VirtualKeyCode::Return) => state.start.update(false, irq),
|
||||
Some(VirtualKeyCode::RShift) => state.select.update(false, irq),
|
||||
Some(VirtualKeyCode::Z) => state.south.update(false, irq),
|
||||
Some(VirtualKeyCode::X) => state.east.update(false, irq),
|
||||
None | Some(_) => {}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ mod bus;
|
|||
mod cartridge;
|
||||
mod cpu;
|
||||
pub mod emu;
|
||||
pub mod gui;
|
||||
mod high_ram;
|
||||
mod instruction;
|
||||
mod interrupt;
|
||||
|
|
184
src/main.rs
184
src/main.rs
|
@ -1,21 +1,18 @@
|
|||
use anyhow::Result;
|
||||
use std::time::Instant;
|
||||
|
||||
use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg};
|
||||
use gb::emu::{Emulator, CYCLES_IN_FRAME};
|
||||
use gb::{Cycle, GB_HEIGHT, GB_WIDTH};
|
||||
use egui_wgpu_backend::RenderPass;
|
||||
use gb::emu::Emulator;
|
||||
use gb::gui::GuiState;
|
||||
use gilrs::Gilrs;
|
||||
use pixels::{PixelsBuilder, SurfaceTexture};
|
||||
use rodio::{OutputStream, Sink};
|
||||
use tracing_subscriber::EnvFilter;
|
||||
use winit::dpi::{LogicalSize, PhysicalSize};
|
||||
use winit::event::{Event, VirtualKeyCode};
|
||||
use winit::event::{Event, WindowEvent};
|
||||
use winit::event_loop::{ControlFlow, EventLoop};
|
||||
use winit::window::{Window, WindowBuilder};
|
||||
use winit_input_helper::WinitInputHelper;
|
||||
|
||||
const WINDOW_SCALE: usize = 3;
|
||||
const AUDIO_ENABLED: bool = true;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
fn main() {
|
||||
let app = App::new(crate_name!())
|
||||
.version(crate_version!())
|
||||
.author(crate_authors!())
|
||||
|
@ -48,10 +45,28 @@ fn main() -> Result<()> {
|
|||
.with_env_filter(EnvFilter::from_default_env())
|
||||
.init();
|
||||
|
||||
// --Here lies a lot of Winit + WGPU Boilerplate--
|
||||
let event_loop: EventLoop<Event<()>> = EventLoop::with_user_event();
|
||||
let window = gb::gui::build_window(&event_loop).expect("build window");
|
||||
|
||||
let (instance, surface) = gb::gui::create_surface(&window);
|
||||
let adapter = gb::gui::request_adapter(&instance, &surface).expect("request adaptor");
|
||||
let (device, queue) = gb::gui::request_device(&adapter).expect("request device");
|
||||
let format = surface
|
||||
.get_preferred_format(&adapter)
|
||||
.expect("get surface format");
|
||||
|
||||
let mut config = gb::gui::surface_config(&window, format);
|
||||
surface.configure(&device, &config);
|
||||
let mut platform = gb::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 emu = match m.value_of("boot") {
|
||||
Some(path) => {
|
||||
tracing::info!("User-provided boot ROM");
|
||||
Emulator::from_boot_rom(path)?
|
||||
Emulator::from_boot_rom(path).expect("initialize emulator with custom boot rom")
|
||||
}
|
||||
None => {
|
||||
tracing::info!("Built-in boot ROM");
|
||||
|
@ -59,39 +74,33 @@ fn main() -> Result<()> {
|
|||
}
|
||||
};
|
||||
|
||||
// Set up the WGPU (and then EGUI) texture we'll be working with.
|
||||
let texture_size = gb::gui::texture_size();
|
||||
let texture = gb::gui::create_texture(&device, texture_size);
|
||||
gb::gui::write_to_texture(&queue, &texture, gb::emu::pixel_buf(&emu), texture_size);
|
||||
let texture_id = gb::gui::expose_texture_to_egui(&mut render_pass, &device, &texture);
|
||||
|
||||
// Load ROM if filepath was provided
|
||||
if let Some(path) = m.value_of("rom") {
|
||||
tracing::info!("User-provided cartridge ROM");
|
||||
emu.read_game_rom(path)?;
|
||||
emu.read_game_rom(path).expect("read game rom from path");
|
||||
}
|
||||
|
||||
// Load Save file if it exists
|
||||
// Load Save File if it exists
|
||||
// FIXME: Shouldn't the API be better than this?
|
||||
emu.try_load_sav().expect("Load save if exists");
|
||||
let rom_title = emu.title();
|
||||
let rom_title = emu.title().to_string();
|
||||
|
||||
tracing::info!("Initialize Gamepad");
|
||||
let mut gamepad = Gilrs::new().expect("Initialize Controller Support");
|
||||
|
||||
// Initialize GUI
|
||||
let event_loop = EventLoop::new();
|
||||
let mut input = WinitInputHelper::new();
|
||||
let window = create_window(&event_loop, rom_title)?;
|
||||
|
||||
let mut pixels = {
|
||||
let size = window.inner_size();
|
||||
let surface_texture = SurfaceTexture::new(size.width, size.height, &window);
|
||||
|
||||
PixelsBuilder::new(GB_WIDTH as u32, GB_HEIGHT as u32, surface_texture)
|
||||
.enable_vsync(false)
|
||||
.build()?
|
||||
};
|
||||
|
||||
// Initialize Audio
|
||||
let (_stream, stream_handle) = OutputStream::try_default().expect("Initialized Audio");
|
||||
|
||||
if AUDIO_ENABLED {
|
||||
let (prod, cons) = gb::spsc_init();
|
||||
let sink = {
|
||||
let s = Sink::try_new(&stream_handle)?;
|
||||
let s = Sink::try_new(&stream_handle).expect("create sink from audio stream handle");
|
||||
s.append(cons);
|
||||
s.set_volume(0.1);
|
||||
s
|
||||
|
@ -105,58 +114,85 @@ fn main() -> Result<()> {
|
|||
});
|
||||
}
|
||||
|
||||
let mut cycle_count: Cycle = Default::default();
|
||||
// Set up state for the Immediate-mode GUI
|
||||
let mut app = GuiState::new(rom_title);
|
||||
let mut last_key = gb::gui::unused_key();
|
||||
|
||||
// used for egui animations
|
||||
let start_time = Instant::now();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
if let Event::RedrawRequested(_) = event {
|
||||
if pixels.render().is_err() {
|
||||
emu.try_write_sav().expect("Write game save if need be");
|
||||
platform.handle_event(&event);
|
||||
|
||||
*control_flow = ControlFlow::Exit;
|
||||
return;
|
||||
}
|
||||
}
|
||||
match event {
|
||||
Event::MainEventsCleared => {
|
||||
if app.quit {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
|
||||
if input.update(&event) {
|
||||
if input.key_pressed(VirtualKeyCode::Escape) || input.quit() {
|
||||
emu.try_write_sav().expect("Write game save if need be");
|
||||
gb::emu::run_frame(&mut emu, &mut gamepad, last_key);
|
||||
|
||||
*control_flow = ControlFlow::Exit;
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(size) = input.window_resized() {
|
||||
pixels.resize_surface(size.width, size.height);
|
||||
}
|
||||
|
||||
cycle_count += gb::emu::run_frame(&mut emu, &mut gamepad, &input);
|
||||
|
||||
if cycle_count >= CYCLES_IN_FRAME {
|
||||
cycle_count %= CYCLES_IN_FRAME;
|
||||
|
||||
let buf: &mut [u8; GB_WIDTH * GB_HEIGHT * 4] = pixels
|
||||
.get_frame()
|
||||
.try_into()
|
||||
.expect("Size of Pixel Buffer is GB_WIDTH * GB_HEIGHT * 4");
|
||||
|
||||
gb::emu::draw_frame(&emu, buf);
|
||||
window.request_redraw();
|
||||
}
|
||||
Event::RedrawRequested(..) => {
|
||||
platform.update_time(start_time.elapsed().as_secs_f64());
|
||||
|
||||
let data = gb::emu::pixel_buf(&emu);
|
||||
gb::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 = gb::gui::create_view(&output_frame);
|
||||
|
||||
// Begin to draw Egui components
|
||||
platform.begin_frame();
|
||||
gb::gui::draw_egui(&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 = gb::gui::create_command_encoder(&device);
|
||||
let screen_descriptor = gb::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.
|
||||
gb::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 {
|
||||
WindowEvent::Resized(size) => {
|
||||
config.width = size.width;
|
||||
config.height = size.height;
|
||||
surface.configure(&device, &config);
|
||||
}
|
||||
WindowEvent::CloseRequested => {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
WindowEvent::KeyboardInput { input, .. } => last_key = input,
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn create_window(event_loop: &EventLoop<()>, title: &str) -> Result<Window> {
|
||||
let logical = LogicalSize::new(GB_WIDTH as f64, GB_HEIGHT as f64);
|
||||
let physical = PhysicalSize::new(
|
||||
(GB_WIDTH * WINDOW_SCALE) as f32,
|
||||
(GB_HEIGHT * WINDOW_SCALE) as f32,
|
||||
);
|
||||
|
||||
Ok(WindowBuilder::new()
|
||||
.with_title(title)
|
||||
.with_min_inner_size(logical)
|
||||
.with_inner_size(physical)
|
||||
.with_resizable(true)
|
||||
.build(event_loop)?)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue