chore: drop pixels-rs and add egui w/ wgpu backend

This commit is contained in:
Rekai Nyangadzayi Musuka 2021-11-15 21:34:24 -04:00
parent ce5d58e1d2
commit 6f5e863645
9 changed files with 501 additions and 195 deletions

166
Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -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()

View File

@ -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(),
},
_ => {}

View File

@ -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 {

238
src/gui.rs Normal file
View File

@ -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],
);
})
});
}

View File

@ -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(_) => {}
},
}
}

View File

@ -9,6 +9,7 @@ mod bus;
mod cartridge;
mod cpu;
pub mod emu;
pub mod gui;
mod high_ram;
mod instruction;
mod interrupt;

View File

@ -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)?)
}