Adds gemmasdl2
-> needs work but starts and runs and steps through the 1-chip8 -> has debugging level code for determining what buttons are being pushed
This commit is contained in:
parent
a467c8e6b0
commit
902d5c1875
758
Cargo.lock
generated
758
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
11
Cargo.toml
11
Cargo.toml
@ -1,5 +1,5 @@
|
||||
[workspace]
|
||||
members = ["gemma", "gemmaegui", "gemmaimgui", "gemmatelnet" ]
|
||||
members = ["gemma", "gemmaegui", "gemmaimgui", "gemmatelnet", "gemmasdl2" ]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.dependencies]
|
||||
@ -14,10 +14,5 @@ egui = { version = "0.29" }
|
||||
eframe = { version = "0.29" }
|
||||
|
||||
# IMGUI
|
||||
glium = { version = "0.34.0" }
|
||||
image = { version = "0.23" }
|
||||
imgui = { version = "0.12.0" }
|
||||
imgui-glium-renderer = { version = "0.12.0" }
|
||||
imgui-winit-support = { version = "0.12.0" }
|
||||
winit = { version = "0.27", features = ["x11", "mint"]}
|
||||
copypasta = { version = "0.8" }
|
||||
|
||||
# EXPERIMENT
|
||||
|
||||
@ -681,8 +681,6 @@ impl Chip8CpuInstructions {
|
||||
|
||||
input.registers.poke(0xf, target);
|
||||
|
||||
|
||||
|
||||
}
|
||||
Chip8CpuInstructions::SkpVx(x) => {
|
||||
// Ex9E - SKP Vx
|
||||
|
||||
@ -10,4 +10,5 @@ pub const CHIP8_KEYBOARD: [[u8; 4]; 4] = [
|
||||
[0x04, 0x05, 0x06, 0x0D],
|
||||
[0x07, 0x08, 0x09, 0x0E],
|
||||
[0x0A, 0x00, 0x0B, 0x0F]
|
||||
];
|
||||
];
|
||||
|
||||
|
||||
@ -5,15 +5,15 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
gemma = { path = "../gemma" }
|
||||
glium.workspace = true
|
||||
image.workspace = true
|
||||
imgui.workspace = true
|
||||
imgui-glium-renderer.workspace = true
|
||||
imgui-winit-support.workspace = true
|
||||
winit.workspace = true
|
||||
pretty_env_logger.workspace = true
|
||||
copypasta.workspace = true
|
||||
rand.workspace = true
|
||||
log.workspace = true
|
||||
chrono.workspace = true
|
||||
dimensioned.workspace = true
|
||||
imgui-glium-renderer = { version = "0.12.0" }
|
||||
imgui-winit-support = { version = "0.12.0" }
|
||||
glium = { version = "0.34.0" }
|
||||
image = { version = "0.23" }
|
||||
imgui = { version = "0.12.0" }
|
||||
winit = { version = "0.27", features = ["x11", "mint"]}
|
||||
copypasta = { version = "0.8" }
|
||||
|
||||
14
gemmasdl2/Cargo.toml
Normal file
14
gemmasdl2/Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "gemmasdl2"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
gemma = { path = "../gemma" }
|
||||
egui_sdl2_platform = "0.3.0"
|
||||
gl = "0.14"
|
||||
egui = "0.27"
|
||||
egui_glow = "0.27"
|
||||
|
||||
pollster = "0.2"
|
||||
anyhow = "1.0"
|
||||
133
gemmasdl2/src/bin/gemmasdl2.rs
Normal file
133
gemmasdl2/src/bin/gemmasdl2.rs
Normal file
@ -0,0 +1,133 @@
|
||||
mod support;
|
||||
use std::{sync::Arc, time::Instant};
|
||||
use anyhow::Context;
|
||||
use egui::TextBuffer;
|
||||
use egui_glow::glow::{HasContext, COLOR_BUFFER_BIT};
|
||||
use egui_sdl2_platform::sdl2;
|
||||
use sdl2::event::{Event, WindowEvent};
|
||||
use gemma::chip8::computer::Chip8Computer;
|
||||
use support::timestep::TimeStep;
|
||||
use crate::support::gemma_egui_support::GemmaEguiSupport;
|
||||
|
||||
const SCREEN_WIDTH: u32 = 800;
|
||||
const SCREEN_HEIGHT: u32 = 480;
|
||||
|
||||
async fn run() -> anyhow::Result<String> {
|
||||
// Initialize SDL2 and video subsystem
|
||||
let sdl = sdl2::init().map_err(|e| anyhow::anyhow!("Failed to create SDL context: {}", e))?;
|
||||
let mut video = sdl.video().map_err(|e| anyhow::anyhow!("Failed to initialize SDL video subsystem: {}", e))?;
|
||||
|
||||
|
||||
// Create SDL2 window and OpenGL context
|
||||
let window = video.window("Window", SCREEN_WIDTH, SCREEN_HEIGHT)
|
||||
.opengl()
|
||||
.position_centered()
|
||||
.build()?;
|
||||
let _gl_context = window.gl_create_context().expect("Failed to create GL context");
|
||||
|
||||
// Load OpenGL functions
|
||||
let gl = unsafe {
|
||||
egui_glow::painter::Context::from_loader_function(|name| {
|
||||
video.gl_get_proc_address(name) as *const _
|
||||
})
|
||||
};
|
||||
let mut painter = egui_glow::Painter::new(Arc::new(gl), "", None)?;
|
||||
|
||||
// Setup Egui and SDL2 platform
|
||||
let mut platform = egui_sdl2_platform::Platform::new(window.size())?;
|
||||
let mut event_pump = sdl.event_pump().map_err(|e| anyhow::anyhow!("Failed to get SDL event pump: {}", e))?;
|
||||
|
||||
// Initial settings
|
||||
let mut color = [0.0, 0.0, 0.0, 1.0]; // Background color
|
||||
let start_time = Instant::now();
|
||||
let mut timestep = TimeStep::new();
|
||||
|
||||
let mut computer = Chip8Computer::new();
|
||||
let mut is_running: bool = false;
|
||||
computer.load_bytes_to_memory(0x200, &std::fs::read("resources/roms/3-corax+.ch8")?);
|
||||
|
||||
// Main loop
|
||||
'main: loop {
|
||||
// Update the Egui platform with the current time
|
||||
platform.update_time(start_time.elapsed().as_secs_f64());
|
||||
|
||||
// Begin Egui frame
|
||||
let ctx = platform.context();
|
||||
|
||||
if is_running {
|
||||
computer.step_system();
|
||||
}
|
||||
|
||||
egui::Window::new("Hello, world!").show(&ctx, |ui| {
|
||||
GemmaEguiSupport::video_view(&computer, ui);
|
||||
GemmaEguiSupport::memory_view(&computer, ui);
|
||||
GemmaEguiSupport::registers_view(&computer, ui);
|
||||
});
|
||||
|
||||
// Process Egui frame
|
||||
let full_output = platform.end_frame(&mut video)?;
|
||||
let paint_jobs = platform.tessellate(&full_output);
|
||||
|
||||
// Clear the screen with the current color
|
||||
unsafe {
|
||||
painter.gl().clear_color(color[0], color[1], color[2], 1.0);
|
||||
painter.gl().clear(COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
// Paint Egui outputs and update textures
|
||||
let size = window.size();
|
||||
painter.paint_and_update_textures([size.0, size.1], 1.0, paint_jobs.as_slice(), &full_output.textures_delta);
|
||||
window.gl_swap_window();
|
||||
|
||||
// Run the timestep logic
|
||||
timestep.run_this(|_| {});
|
||||
|
||||
// Handle SDL2 events
|
||||
for event in event_pump.poll_iter() {
|
||||
match event {
|
||||
Event::Quit { .. }
|
||||
| Event::KeyDown { keycode: Some(sdl2::keyboard::Keycode::Escape), .. } => break 'main,
|
||||
Event::Window { window_id, win_event, .. } if window_id == window.id() => {
|
||||
if let WindowEvent::Close = win_event {
|
||||
break 'main;
|
||||
}
|
||||
}
|
||||
Event::KeyUp { keycode: Some(sdl2::keyboard::Keycode::F3), .. } => {
|
||||
println!("USER PRESSED F3 -> running");
|
||||
is_running = true;
|
||||
}
|
||||
Event::KeyUp { keycode: Some(sdl2::keyboard::Keycode::F4), .. } => {
|
||||
println!("USER PRESSED F4 -> stopping");
|
||||
is_running = false;
|
||||
}
|
||||
Event::KeyDown { keycode: Some(sdl2::keyboard::Keycode::F5), .. } => {
|
||||
println!("USER PRESSED F5 -> Step");
|
||||
computer.step_system();
|
||||
}
|
||||
Event::KeyDown { keycode: Some(sdl2::keyboard::Keycode::F6), .. } => {
|
||||
println!("USER PRESSED F6 -> RESET");
|
||||
computer.reset();
|
||||
}
|
||||
Event::ControllerButtonDown { which, .. } => {
|
||||
println!("PLAYER {which} DOWN");
|
||||
}
|
||||
Event::ControllerButtonDown { button, .. } => {
|
||||
println!("BUTTON {:?}", button);
|
||||
}
|
||||
_ => platform.handle_event(&event, &sdl, &video),
|
||||
}
|
||||
}
|
||||
|
||||
// Optionally log the frame rate
|
||||
if let Some(fps) = timestep.frame_rate() {
|
||||
println!("{:?} fps", fps);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(("").parse()?)
|
||||
}
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
pollster::block_on(run())?;
|
||||
Ok(())
|
||||
}
|
||||
146
gemmasdl2/src/bin/support/gemma_egui_support.rs
Normal file
146
gemmasdl2/src/bin/support/gemma_egui_support.rs
Normal file
@ -0,0 +1,146 @@
|
||||
use std::fs::read_dir;
|
||||
use std::ops::Index;
|
||||
use std::path::{Display, PathBuf};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use egui::{Align, Color32, ComboBox, Direction, Pos2, Response, TextBuffer};
|
||||
use egui::Rect;
|
||||
use egui::Vec2;
|
||||
use egui::Ui;
|
||||
use egui::WidgetType::SelectableLabel;
|
||||
use crate::Chip8Computer;
|
||||
|
||||
const CELL_WIDTH: f32 = 5.0;
|
||||
const CELL_HEIGHT: f32 = 5.0;
|
||||
|
||||
pub struct EGuiFileList {}
|
||||
impl EGuiFileList {
|
||||
pub fn display_path(root: PathBuf, selected_filename: &mut String, ui: &mut Ui) {
|
||||
let mut working_filename = selected_filename.clone();
|
||||
ui.with_layout(egui::Layout::left_to_right(egui::Align::TOP), |ui| {
|
||||
// ui.label(format!("Displaying {}", root.to_str().unwrap_or("Unable to Load Path")));
|
||||
ComboBox::from_label("Select ROM")
|
||||
.selected_text(selected_filename.clone())
|
||||
.show_ui(ui, |ui| {
|
||||
|
||||
let mut sorted_options = vec![];
|
||||
for option in read_dir(root.as_path()).unwrap() {
|
||||
let to_push = option.unwrap().file_name().into_string().unwrap_or( String::new());
|
||||
sorted_options.push(to_push);
|
||||
}
|
||||
|
||||
sorted_options.sort();
|
||||
for item in sorted_options {
|
||||
// Add each option to the ComboBox
|
||||
if ui.selectable_label(selected_filename.eq(&item.as_str()), item.clone()).clicked()
|
||||
{
|
||||
*selected_filename = item;
|
||||
}
|
||||
}
|
||||
});
|
||||
// Display the selected option
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GemmaEguiSupport {}
|
||||
|
||||
impl GemmaEguiSupport {
|
||||
pub fn controls_view(mut system: &mut Chip8Computer, ui: &mut Ui) {
|
||||
ui.with_layout(egui::Layout::left_to_right(egui::Align::TOP), |ui| {
|
||||
if ui.button("Start").clicked() {
|
||||
println!("Start");
|
||||
// state.is_running = true;
|
||||
}
|
||||
if ui.button("Step").clicked() {
|
||||
system.step_system();
|
||||
}
|
||||
|
||||
if ui.button("Stop").clicked() {
|
||||
println!("STOP");
|
||||
// state.is_running = false;
|
||||
}
|
||||
if ui.button("Reset").clicked() {
|
||||
system.reset();
|
||||
// state.is_running = false;
|
||||
}
|
||||
});
|
||||
|
||||
// if ui.button(format!("Load {}", state.selected_rom_filename)).clicked() {
|
||||
// load the bin...
|
||||
// let read_bin = std::fs::read(PathBuf::from(format!("resources/roms/{}", state.selected_rom_filename))).unwrap();
|
||||
// ...then feed the system.
|
||||
// system.load_bytes_to_memory(0x200, &read_bin);
|
||||
// println!("Loaded {}", state.selected_rom_filename);
|
||||
// }
|
||||
// EGuiFileList::display_path(PathBuf::from("resources/roms"), &mut state.selected_rom_filename, ui);
|
||||
|
||||
|
||||
|
||||
ui.with_layout(egui::Layout::left_to_right(Align::TOP), |ui| {
|
||||
// ui.checkbox(&mut state.display_memory, "Display Memory");
|
||||
// ui.checkbox(&mut state.display_video, "Display Video");
|
||||
// ui.checkbox(&mut state.display_registers, "Display Registers");
|
||||
});
|
||||
}
|
||||
|
||||
pub fn registers_view(system: &Chip8Computer, ui: &mut Ui) {
|
||||
ui.label(format!("V0-7: {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} ",
|
||||
system.registers.peek(0x00),
|
||||
system.registers.peek(0x01),
|
||||
system.registers.peek(0x02),
|
||||
system.registers.peek(0x03),
|
||||
system.registers.peek(0x04),
|
||||
system.registers.peek(0x05),
|
||||
system.registers.peek(0x06),
|
||||
system.registers.peek(0x07)
|
||||
));
|
||||
ui.label(format!("V8-F: {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} ",
|
||||
system.registers.peek(0x08),
|
||||
system.registers.peek(0x09),
|
||||
system.registers.peek(0x0A),
|
||||
system.registers.peek(0x0B),
|
||||
system.registers.peek(0x0C),
|
||||
system.registers.peek(0x0D),
|
||||
system.registers.peek(0x0E),
|
||||
system.registers.peek(0x0F)
|
||||
));
|
||||
ui.label(format!("PC: {:04x}\tI: {:04x}", system.registers.peek_pc(), system.registers.peek_i()));
|
||||
}
|
||||
|
||||
pub fn video_view(system: &Chip8Computer, ui: &mut Ui) {
|
||||
let (_resp, painter) = ui.allocate_painter(Vec2::new(350.0, 165.0), egui::Sense::hover());
|
||||
for current_row in 0..32 {
|
||||
for current_col in 0..64 {
|
||||
let data_offset = current_row * 64 + current_col;
|
||||
let x_offset = current_col as f32 * CELL_WIDTH;
|
||||
let y_offset = current_row as f32 * CELL_HEIGHT;
|
||||
let origin = Pos2::new(x_offset, y_offset);
|
||||
let colour = if system.video_memory.peek(data_offset) {
|
||||
Color32::RED
|
||||
} else {
|
||||
Color32::WHITE
|
||||
};
|
||||
let rect = Rect::from_min_size(origin, Vec2::new(CELL_WIDTH, CELL_HEIGHT));
|
||||
painter.rect_filled(rect, 0.0, colour);
|
||||
// println!("Cell {current_col}x{current_row} at {}x{} -> {}",
|
||||
// origin.x, origin.y,
|
||||
// system.video_memory.peek(data_offset));
|
||||
}
|
||||
}
|
||||
// thread::sleep(Duration::from_secs(1));
|
||||
}
|
||||
|
||||
pub fn memory_view(system: &Chip8Computer, ui: &mut Ui) {
|
||||
ui.label("Memory View");
|
||||
|
||||
for i in (0..=0x200).step_by(16) {
|
||||
ui.with_layout(egui::Layout::left_to_right(egui::Align::TOP), |ui| {
|
||||
for y in 0..16 {
|
||||
ui.label(format!("{:02x}", system.memory.peek((i + y) as u16)).as_str());
|
||||
}
|
||||
});
|
||||
}
|
||||
ui.label("Should have **something** to adjust the 'memory window'");
|
||||
}
|
||||
}
|
||||
2
gemmasdl2/src/bin/support/mod.rs
Normal file
2
gemmasdl2/src/bin/support/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod timestep;
|
||||
pub mod gemma_egui_support;
|
||||
95
gemmasdl2/src/bin/support/timestep.rs
Normal file
95
gemmasdl2/src/bin/support/timestep.rs
Normal file
@ -0,0 +1,95 @@
|
||||
|
||||
//! Gameplay speed control. It attempts to mimic/limit progression to
|
||||
//! the 30 tics per second Doom used.
|
||||
|
||||
use std::{fmt, time::Instant};
|
||||
|
||||
const MS_PER_UPDATE: f32 = 28.57;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TimeStep {
|
||||
last_time: Instant,
|
||||
delta_time: f32,
|
||||
frame_count: u32,
|
||||
frame_time: f32,
|
||||
run_tics: u32,
|
||||
last_tics: u32,
|
||||
lag: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FrameData {
|
||||
pub tics: u32,
|
||||
pub frames: u32,
|
||||
}
|
||||
|
||||
impl fmt::Display for FrameData {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_fmt(format_args!(
|
||||
"FrameData (per-second):\n - tics: {}\n - fps: {}",
|
||||
self.tics, self.frames
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl TimeStep {
|
||||
pub fn new() -> TimeStep {
|
||||
TimeStep {
|
||||
last_time: Instant::now(),
|
||||
delta_time: 0.0,
|
||||
frame_count: 0,
|
||||
frame_time: 0.0,
|
||||
run_tics: 0,
|
||||
last_tics: 0,
|
||||
lag: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delta(&mut self) -> f32 {
|
||||
let current_time = Instant::now();
|
||||
let delta = current_time.duration_since(self.last_time).as_micros() as f32 * 0.001;
|
||||
self.last_time = current_time;
|
||||
self.delta_time = delta;
|
||||
delta
|
||||
}
|
||||
|
||||
/// Increments self time and returns current lag. `run_this` is run only for
|
||||
/// `n` tics available.
|
||||
pub fn run_this(&mut self, mut run_this: impl FnMut(f32)) {
|
||||
let dt = self.delta();
|
||||
self.lag += dt;
|
||||
while self.lag >= MS_PER_UPDATE {
|
||||
run_this(dt);
|
||||
self.lag -= MS_PER_UPDATE;
|
||||
self.run_tics += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn frame_rate(&mut self) -> Option<FrameData> {
|
||||
self.frame_count += 1;
|
||||
self.frame_time += self.delta_time;
|
||||
let tmp;
|
||||
let tmp2;
|
||||
// per second
|
||||
if self.frame_time >= 1000.0 {
|
||||
tmp = self.frame_count;
|
||||
tmp2 = self.last_tics;
|
||||
self.frame_count = 0;
|
||||
self.frame_time = 0.0;
|
||||
self.last_tics = self.run_tics;
|
||||
return Some(FrameData {
|
||||
tics: self.run_tics - tmp2,
|
||||
frames: tmp,
|
||||
});
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TimeStep {
|
||||
// shutup clippy!
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user