Compare commits

...

6 commits

15 changed files with 937 additions and 626 deletions

1401
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -21,11 +21,11 @@ nokhwa = { version = "0.10", features = [
"input-avfoundation", "input-avfoundation",
], optional = true } ], optional = true }
send_wrapper = { version = "0.6.0", optional = true } send_wrapper = { version = "0.6.0", optional = true }
winit = { version = "0.29", features = ["rwh_05"] } winit = { version = "0.29.15", features = ["rwh_05"] }
winit_input_helper = "0.15" winit_input_helper = "0.16"
raw-window-handle = { workspace = true } raw-window-handle = { workspace = true }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
image = { version = "0.24", default-features = false, features = ["png"] } image = { version = "0.25.1", default-features = false, features = ["png"] }
bytemuck = "1.14" bytemuck = "1.14"
chrono = "0.4" chrono = "0.4"
log = { workspace = true } log = { workspace = true }

View file

@ -3,7 +3,7 @@ use cpal::{
Stream, Stream,
}; };
use futures::executor; use futures::executor;
use gb_emu_lib::connect::{AudioOutput, DownsampleType}; use gb_emu_lib::connect::{AsyncConsumer, AudioOutput, DownsampleType};
use crate::access_config; use crate::access_config;

View file

@ -27,7 +27,7 @@ nih_plug = { workspace = true, features = [
"vst3", "vst3",
], optional = true } ], optional = true }
baseview = { workspace = true, optional = true } baseview = { workspace = true, optional = true }
async-ringbuf = { version = "0.1", optional = true } async-ringbuf = { version = "0.2.1", optional = true }
futures = { version = "0.3", optional = true } futures = { version = "0.3", optional = true }
keyboard-types = { version = "0.6.2", optional = true } keyboard-types = { version = "0.6.2", optional = true }
raw-window-handle = { workspace = true } raw-window-handle = { workspace = true }

View file

@ -1,4 +1,7 @@
use async_ringbuf::AsyncHeapConsumer; use async_ringbuf::{
traits::{AsyncConsumer, Observer},
AsyncHeapCons,
};
use baseview::Size; use baseview::Size;
use futures::executor; use futures::executor;
use gb_emu_lib::{ use gb_emu_lib::{
@ -51,7 +54,7 @@ struct EmuParams {
} }
struct EmuVars { struct EmuVars {
rx: AsyncHeapConsumer<[f32; 2]>, rx: AsyncHeapCons<[f32; 2]>,
emulator_core: EmulatorCore<[u8; 4]>, emulator_core: EmulatorCore<[u8; 4]>,
serial_tx: Sender<u8>, serial_tx: Sender<u8>,
} }
@ -324,6 +327,7 @@ impl Plugin for GameboyEmu {
.with_show_bootrom(!will_skip_bootrom); .with_show_bootrom(!will_skip_bootrom);
EmulatorCore::init(false, receiver, options) EmulatorCore::init(false, receiver, options)
.expect("couldn't initialize emulator core!")
}; };
emulator_core.run_until_buffer_full(); emulator_core.run_until_buffer_full();

View file

@ -9,19 +9,19 @@ identifier = "com.alexjanka.TWINC.gui"
osx_file_extensions = [[["Game Boy ROM", "Viewer"], ["gb", "gbc"]]] osx_file_extensions = [[["Game Boy ROM", "Viewer"], ["gb", "gbc"]]]
[features] [features]
default = ["macos-ui", "crossplatform-ui", "force-crossplatform-ui"] default = ["macos-ui", "crossplatform-ui"]
macos-ui = ["cacao", "objc", "uuid"] macos-ui = ["cacao", "objc", "uuid"]
crossplatform-ui = ["gtk", "adw", "glib-build-tools"] crossplatform-ui = ["gtk", "adw", "glib-build-tools"]
force-crossplatform-ui = ["crossplatform-ui"] force-crossplatform-ui = ["crossplatform-ui"]
[dependencies] [dependencies]
adw = { version = "0.6.0", package = "libadwaita", features = [ adw = { version = "0.7.0", package = "libadwaita", features = [
"v1_4", "v1_4",
"gtk_v4_12", "gtk_v4_6",
], optional = true } ], optional = true }
frontend-common = { workspace = true } frontend-common = { workspace = true }
gb-emu-lib = { workspace = true } gb-emu-lib = { workspace = true }
gtk = { version = "0.8.0", package = "gtk4", features = [ gtk = { version = "0.9.0", package = "gtk4", features = [
"v4_12", "v4_12",
], optional = true } ], optional = true }
twinc_emu_vst = { path = "../gb-vst", default-features = false } twinc_emu_vst = { path = "../gb-vst", default-features = false }
@ -40,4 +40,4 @@ uuid = { version = "1.6", features = ["v4", "fast-rng"], optional = true }
[build-dependencies] [build-dependencies]
glib-build-tools = { version = "0.19.0", optional = true } glib-build-tools = { version = "0.20.0", optional = true }

View file

@ -301,7 +301,14 @@ impl CacaoWindowManager {
if state { if state {
let is_running = self.is_emulator_running(); let is_running = self.is_emulator_running();
if is_running { if is_running {
let new_layer_window = new_layer_window(self); let new_layer_window: Sender<RendererMessage<[u8; 4]>> =
match new_layer_window(self) {
Ok(t) => t,
Err(e) => {
log::error!("couldn't create tile window: {e:?}");
return;
}
};
if let Some(ref handles) = self.handles { if let Some(ref handles) = self.handles {
handles handles
.sender .sender
@ -316,7 +323,13 @@ impl CacaoWindowManager {
if state { if state {
let is_running = self.is_emulator_running(); let is_running = self.is_emulator_running();
if is_running { if is_running {
let new_tile_window = new_tile_window(self); let new_tile_window = match new_tile_window(self) {
Ok(t) => t,
Err(e) => {
log::error!("couldn't create tile window: {e:?}");
return;
}
};
if let Some(ref handles) = self.handles { if let Some(ref handles) = self.handles {
handles handles
.sender .sender

View file

@ -170,7 +170,13 @@ impl Dispatcher for TwincUiApp {
let (output, stream) = audio::create_output(false); let (output, stream) = audio::create_output(false);
let mut window_manager = self.current_game.write().unwrap(); let mut window_manager = self.current_game.write().unwrap();
window_manager.update_handles(sender, stream); window_manager.update_handles(sender, stream);
let mut core = frontend_common::run(prepared, &mut *window_manager, output); let mut core = match frontend_common::run(prepared, &mut *window_manager, output) {
Ok(c) => c,
Err(e) => {
log::error!("couldn't create emulator core: {e:?}");
return;
}
};
let handle = std::thread::Builder::new() let handle = std::thread::Builder::new()
.name(String::from("EmuCore")) .name(String::from("EmuCore"))
.spawn(move || loop { .spawn(move || loop {

View file

@ -4,6 +4,7 @@ use std::path::PathBuf;
use cacao::button::Button; use cacao::button::Button;
use cacao::filesystem::FileSelectPanel; use cacao::filesystem::FileSelectPanel;
use cacao::image::{Image, ImageView};
use cacao::input::TextField; use cacao::input::TextField;
use cacao::layout::{Layout, LayoutConstraint}; use cacao::layout::{Layout, LayoutConstraint};
use cacao::select::Select; use cacao::select::Select;
@ -205,7 +206,7 @@ pub struct PickerView<T>
where where
T: ToString, T: ToString,
{ {
pub view: View, pub _view: View,
pub select: Select, pub select: Select,
pub title: Label, pub title: Label,
_p: PhantomData<T>, _p: PhantomData<T>,
@ -243,7 +244,7 @@ where
]); ]);
Self { Self {
view, _view: view,
select, select,
title, title,
_p: PhantomData, _p: PhantomData,
@ -485,3 +486,43 @@ impl StepperViewToggle {
self.enabled = !self.enabled; self.enabled = !self.enabled;
} }
} }
pub struct ImageViewWrapper {
view: View,
image_view: ImageView,
_image: Image,
}
impl ImageViewWrapper {
pub fn new(image: Image) -> Self {
let view = View::new();
let image_view = ImageView::new();
image_view.set_image(&image);
view.add_subview(&image_view);
LayoutConstraint::activate(&[
image_view
.leading
.constraint_equal_to(&view.leading)
.offset(10.),
image_view.top.constraint_equal_to(&view.top).offset(10.),
image_view
.bottom
.constraint_equal_to(&view.bottom)
.offset(-10.),
]);
Self {
view,
image_view,
_image: image,
}
}
pub fn view(&self) -> &View {
&self.view
}
pub fn image_view(&self) -> &ImageView {
&self.image_view
}
}

View file

@ -4,7 +4,7 @@ version = "0.5.1"
edition = "2021" edition = "2021"
[features] [features]
default = [] default = ["config", "librashader", "wgpu-renderer"]
clocked-serial = [] clocked-serial = []
librashader = [ librashader = [
"dep:librashader", "dep:librashader",
@ -34,9 +34,9 @@ error-colour = []
[dependencies] [dependencies]
rand = "0.8.5" rand = "0.8.5"
async-ringbuf = "0.1" async-ringbuf = "0.2.1"
futures = "0.3" futures = "0.3.30"
itertools = "0.12" itertools = "0.13.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_with = "3.0" serde_with = "3.0"
bytemuck = "1.14" bytemuck = "1.14"
@ -51,13 +51,16 @@ librashader-common = { workspace = true, optional = true }
directories = { version = "5.0", optional = true } directories = { version = "5.0", optional = true }
ron = { version = "0.8", optional = true } ron = { version = "0.8", optional = true }
lazy_static = "1.4" lazy_static = "1.4"
wgpu = { version = "0.20", optional = true } wgpu = { version = "22.0.0", optional = true }
thiserror = { workspace = true } thiserror = { workspace = true }
log = { workspace = true } log = { workspace = true }
anyhow = "1.0.86" anyhow = "1.0.86"
[build-dependencies] [build-dependencies]
naga = { version = "0.19", optional = true, features = ["wgsl-in", "spv-out"] } naga = { version = "22.0.0", optional = true, features = [
"wgsl-in",
"spv-out",
] }
[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies]
ash-molten = { version = "0.16.0", optional = true } ash-molten = { version = "0.19.0", optional = true }

View file

@ -1,8 +1,11 @@
use async_ringbuf::{traits::Split, AsyncHeapCons, AsyncHeapProd, AsyncHeapRb};
use std::fs; use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::mpsc::Sender; use std::sync::mpsc::Sender;
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
pub use async_ringbuf::traits::consumer::AsyncConsumer;
pub use crate::error::RomHeaderError; pub use crate::error::RomHeaderError;
pub use crate::processor::memory::mmio::gpu::Colour; pub use crate::processor::memory::mmio::gpu::Colour;
pub use crate::processor::memory::mmio::joypad::{JoypadButtons, JoypadState}; pub use crate::processor::memory::mmio::joypad::{JoypadButtons, JoypadState};
@ -13,7 +16,6 @@ pub use crate::processor::memory::rom::{
}; };
pub use crate::processor::memory::Rom; pub use crate::processor::memory::Rom;
pub use crate::{HEIGHT, WIDTH}; pub use crate::{HEIGHT, WIDTH};
use async_ringbuf::{AsyncHeapConsumer, AsyncHeapProducer, AsyncHeapRb};
#[derive(Debug)] #[derive(Debug)]
pub enum EmulatorMessage<ColourFormat> pub enum EmulatorMessage<ColourFormat>
@ -98,7 +100,7 @@ pub struct ResolutionData {
pub struct AudioOutput { pub struct AudioOutput {
pub sample_rate: f32, pub sample_rate: f32,
pub send_rb: AsyncHeapProducer<[f32; 2]>, pub send_rb: AsyncHeapProd<[f32; 2]>,
pub downsample_type: DownsampleType, pub downsample_type: DownsampleType,
} }
@ -107,7 +109,7 @@ impl AudioOutput {
sample_rate: f32, sample_rate: f32,
buffers_per_frame: usize, buffers_per_frame: usize,
downsample_type: DownsampleType, downsample_type: DownsampleType,
) -> (Self, AsyncHeapConsumer<[f32; 2]>) { ) -> (Self, AsyncHeapCons<[f32; 2]>) {
let rb_len = (sample_rate as usize / 60) / buffers_per_frame; let rb_len = (sample_rate as usize / 60) / buffers_per_frame;
let rb = AsyncHeapRb::<[f32; 2]>::new(rb_len); let rb = AsyncHeapRb::<[f32; 2]>::new(rb_len);

View file

@ -7,6 +7,7 @@ use crate::{
processor::memory::addresses::{AddressMarker, AudioAddress, WaveRamAddress}, processor::memory::addresses::{AddressMarker, AudioAddress, WaveRamAddress},
util::{get_bit, set_or_clear_bit}, util::{get_bit, set_or_clear_bit},
}; };
use async_ringbuf::traits::{AsyncProducer, Observer};
use futures::executor; use futures::executor;
use itertools::izip; use itertools::izip;

View file

@ -97,6 +97,7 @@ impl RendererBackend for WgpuBackend {
required_features: wgpu::Features::ADDRESS_MODE_CLAMP_TO_BORDER, required_features: wgpu::Features::ADDRESS_MODE_CLAMP_TO_BORDER,
required_limits: wgpu::Limits::default(), required_limits: wgpu::Limits::default(),
label: None, label: None,
memory_hints: wgpu::MemoryHints::default(),
}, },
None, None,
) )

View file

@ -6,14 +6,14 @@ use gb_emu_lib::connect::{EmulatorCoreTrait, RomFile};
mod common; mod common;
#[test] #[test]
fn cpu_instrs() -> Result<(), String> { fn cpu_instrs() -> anyhow::Result<()> {
run_blargg_test("cpu_instrs\n\n01:ok 02:ok 03:ok 04:ok 05:ok 06:ok 07:ok 08:ok 09:ok 10:ok 11:ok \n\nPassed all tests", include_bytes!( run_blargg_test("cpu_instrs\n\n01:ok 02:ok 03:ok 04:ok 05:ok 06:ok 07:ok 08:ok 09:ok 10:ok 11:ok \n\nPassed all tests", include_bytes!(
"../../test-roms/blargg/cpu_instrs/cpu_instrs.gb" "../../test-roms/blargg/cpu_instrs/cpu_instrs.gb"
),None) ),None)
} }
#[test] #[test]
fn instr_timing() -> Result<(), String> { fn instr_timing() -> anyhow::Result<()> {
run_blargg_test( run_blargg_test(
"instr_timing\n\n\nPassed", "instr_timing\n\n\nPassed",
include_bytes!("../../test-roms/blargg/instr_timing/instr_timing.gb"), include_bytes!("../../test-roms/blargg/instr_timing/instr_timing.gb"),
@ -22,7 +22,7 @@ fn instr_timing() -> Result<(), String> {
} }
#[test] #[test]
fn mem_timing() -> Result<(), String> { fn mem_timing() -> anyhow::Result<()> {
run_blargg_test( run_blargg_test(
"mem_timing\n\n\nPassed", "mem_timing\n\n\nPassed",
include_bytes!("../../test-roms/blargg/mem_timing/mem_timing.gb"), include_bytes!("../../test-roms/blargg/mem_timing/mem_timing.gb"),
@ -30,11 +30,17 @@ fn mem_timing() -> Result<(), String> {
) )
} }
#[derive(Debug, thiserror::Error)]
enum TestError {
#[error("Timeout")]
Timeout(String),
}
fn run_blargg_test<const N: usize>( fn run_blargg_test<const N: usize>(
correct_output: &str, correct_output: &str,
rom: &[u8; N], rom: &[u8; N],
extra_end: Option<Vec<&str>>, extra_end: Option<Vec<&str>>,
) -> Result<(), String> { ) -> anyhow::Result<()> {
let mut emu = emulator_setup(RomFile::Raw(rom.to_vec()))?; let mut emu = emulator_setup(RomFile::Raw(rom.to_vec()))?;
let mut end_strings = extra_end.unwrap_or_default(); let mut end_strings = extra_end.unwrap_or_default();
@ -54,7 +60,7 @@ fn run_blargg_test<const N: usize>(
} }
} }
if began.elapsed() > timeout { if began.elapsed() > timeout {
return Err(format!("Test timed out: output was {chars}")); return Err(TestError::Timeout(format!("Test timed out: output was {chars}")).into());
} }
} }

View file

@ -1,6 +1,5 @@
use std::sync::mpsc::{channel, Receiver, Sender}; use std::sync::mpsc::{channel, Receiver, Sender};
use async_ringbuf::AsyncHeapConsumer;
use gb_emu_lib::{ use gb_emu_lib::{
connect::{AudioOutput, EmulatorMessage, EmulatorOptions, RomFile}, connect::{AudioOutput, EmulatorMessage, EmulatorOptions, RomFile},
EmulatorCore, EmulatorCore,
@ -8,16 +7,14 @@ use gb_emu_lib::{
pub struct TestEmulator { pub struct TestEmulator {
pub core: EmulatorCore<[u8; 4]>, pub core: EmulatorCore<[u8; 4]>,
pub sender: Sender<EmulatorMessage<[u8; 4]>>, pub _sender: Sender<EmulatorMessage<[u8; 4]>>,
pub audio_rx: AsyncHeapConsumer<[f32; 2]>, pub _audio_rx: async_ringbuf::AsyncHeapCons<[f32; 2]>,
pub serial_rx: Receiver<u8>, pub serial_rx: Receiver<u8>,
} }
pub fn emulator_setup(rom_file: RomFile) -> Result<TestEmulator, String> { pub fn emulator_setup(rom_file: RomFile) -> anyhow::Result<TestEmulator> {
let (sender, receiver) = channel::<EmulatorMessage<[u8; 4]>>(); let (sender, receiver) = channel::<EmulatorMessage<[u8; 4]>>();
let rom = rom_file let rom = rom_file.load(gb_emu_lib::connect::SramType::None)?;
.load(gb_emu_lib::connect::SramType::None)
.map_err(|_e| String::from("Error reading ROM: {_e:?}"))?;
let (audio_output, audio_rx) = AudioOutput::new( let (audio_output, audio_rx) = AudioOutput::new(
48000., 48000.,
1, 1,
@ -40,14 +37,12 @@ pub fn emulator_setup(rom_file: RomFile) -> Result<TestEmulator, String> {
include_bytes!("../../../sameboy-bootroms/dmg_boot.bin").to_vec(), include_bytes!("../../../sameboy-bootroms/dmg_boot.bin").to_vec(),
))); )));
let core = EmulatorCore::init(true, receiver, options); let core = EmulatorCore::init(true, receiver, options)?;
sender sender.send(EmulatorMessage::Start)?;
.send(EmulatorMessage::Start)
.map_err(|_e| String::from("Error sending message: {_e:?}"))?;
Ok(TestEmulator { Ok(TestEmulator {
core, core,
sender, _sender: sender,
audio_rx, _audio_rx: audio_rx,
serial_rx, serial_rx,
}) })
} }