Compare commits
No commits in common. "d306f19297b3021915760e8b55663aef6a45cc52" and "544990c58f528d130189db622448427b266b4088" have entirely different histories.
d306f19297
...
544990c58f
1
build.rs
1
build.rs
|
@ -5,6 +5,7 @@ fn main() {
|
||||||
cc::Build::new()
|
cc::Build::new()
|
||||||
.include(Path::new("shrooms-vb-core/core"))
|
.include(Path::new("shrooms-vb-core/core"))
|
||||||
.opt_level(2)
|
.opt_level(2)
|
||||||
|
.flag_if_supported("-flto")
|
||||||
.flag_if_supported("-fno-strict-aliasing")
|
.flag_if_supported("-fno-strict-aliasing")
|
||||||
.file(Path::new("shrooms-vb-core/core/vb.c"))
|
.file(Path::new("shrooms-vb-core/core/vb.c"))
|
||||||
.compile("vb");
|
.compile("vb");
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::time::Instant;
|
use std::{sync::Arc, time::Instant};
|
||||||
use wgpu::util::DeviceExt as _;
|
use wgpu::util::DeviceExt as _;
|
||||||
use winit::{
|
use winit::{
|
||||||
dpi::LogicalSize,
|
dpi::LogicalSize,
|
||||||
|
@ -9,7 +9,7 @@ use winit::{
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
emulator::{EmulatorClient, EmulatorCommand, SimId},
|
emulator::{EmulatorClient, EmulatorCommand, SimId},
|
||||||
graphics::TextureSink,
|
renderer::GameRenderer,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
|
@ -46,8 +46,15 @@ impl GameWindow {
|
||||||
.build();
|
.build();
|
||||||
let device = &window.device;
|
let device = &window.device;
|
||||||
|
|
||||||
let (sink, texture_view) = TextureSink::new(device, window.queue.clone());
|
let eyes = Arc::new(GameRenderer::create_texture(device, "eye"));
|
||||||
client.send_command(EmulatorCommand::SetRenderer(sim_id, sink));
|
client.send_command(EmulatorCommand::SetRenderer(
|
||||||
|
sim_id,
|
||||||
|
GameRenderer {
|
||||||
|
queue: window.queue.clone(),
|
||||||
|
eyes: eyes.clone(),
|
||||||
|
},
|
||||||
|
));
|
||||||
|
let eyes = eyes.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
let sampler = device.create_sampler(&wgpu::SamplerDescriptor::default());
|
let sampler = device.create_sampler(&wgpu::SamplerDescriptor::default());
|
||||||
let colors = Colors {
|
let colors = Colors {
|
||||||
left: [1.0, 0.0, 0.0, 1.0],
|
left: [1.0, 0.0, 0.0, 1.0],
|
||||||
|
@ -96,7 +103,7 @@ impl GameWindow {
|
||||||
entries: &[
|
entries: &[
|
||||||
wgpu::BindGroupEntry {
|
wgpu::BindGroupEntry {
|
||||||
binding: 0,
|
binding: 0,
|
||||||
resource: wgpu::BindingResource::TextureView(&texture_view),
|
resource: wgpu::BindingResource::TextureView(&eyes),
|
||||||
},
|
},
|
||||||
wgpu::BindGroupEntry {
|
wgpu::BindGroupEntry {
|
||||||
binding: 1,
|
binding: 1,
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
@ -97,7 +95,7 @@ impl Audio {
|
||||||
}
|
}
|
||||||
|
|
||||||
while self.sample_sink.slots() < self.sampler.output_frames_max() * 2 {
|
while self.sample_sink.slots() < self.sampler.output_frames_max() * 2 {
|
||||||
std::thread::sleep(Duration::from_micros(500));
|
std::hint::spin_loop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ use std::{
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
use crate::{audio::Audio, graphics::TextureSink};
|
use crate::{audio::Audio, renderer::GameRenderer};
|
||||||
use shrooms_vb_core::Sim;
|
use shrooms_vb_core::Sim;
|
||||||
pub use shrooms_vb_core::VBKey;
|
pub use shrooms_vb_core::VBKey;
|
||||||
|
|
||||||
|
@ -30,10 +30,7 @@ pub enum SimId {
|
||||||
Player2,
|
Player2,
|
||||||
}
|
}
|
||||||
impl SimId {
|
impl SimId {
|
||||||
pub const fn values() -> [Self; 2] {
|
pub fn to_index(self) -> usize {
|
||||||
[Self::Player1, Self::Player2]
|
|
||||||
}
|
|
||||||
pub const fn to_index(self) -> usize {
|
|
||||||
match self {
|
match self {
|
||||||
Self::Player1 => 0,
|
Self::Player1 => 0,
|
||||||
Self::Player2 => 1,
|
Self::Player2 => 1,
|
||||||
|
@ -78,7 +75,7 @@ pub struct Emulator {
|
||||||
sims: Vec<Sim>,
|
sims: Vec<Sim>,
|
||||||
audio: Audio,
|
audio: Audio,
|
||||||
commands: mpsc::Receiver<EmulatorCommand>,
|
commands: mpsc::Receiver<EmulatorCommand>,
|
||||||
renderers: HashMap<SimId, TextureSink>,
|
renderers: HashMap<SimId, GameRenderer>,
|
||||||
running: Arc<AtomicBool>,
|
running: Arc<AtomicBool>,
|
||||||
has_game: Arc<AtomicBool>,
|
has_game: Arc<AtomicBool>,
|
||||||
}
|
}
|
||||||
|
@ -143,17 +140,11 @@ impl Emulator {
|
||||||
idle = false;
|
idle = false;
|
||||||
Sim::emulate_many(&mut self.sims);
|
Sim::emulate_many(&mut self.sims);
|
||||||
}
|
}
|
||||||
for sim_id in SimId::values() {
|
for (sim_id, renderer) in self.renderers.iter_mut() {
|
||||||
let Some(renderer) = self.renderers.get_mut(&sim_id) else {
|
if let Some(sim) = self.sims.get_mut(sim_id.to_index()) {
|
||||||
continue;
|
if sim.read_pixels(&mut eye_contents) {
|
||||||
};
|
idle = false;
|
||||||
let Some(sim) = self.sims.get_mut(sim_id.to_index()) else {
|
renderer.render(&eye_contents);
|
||||||
continue;
|
|
||||||
};
|
|
||||||
if sim.read_pixels(&mut eye_contents) {
|
|
||||||
idle = false;
|
|
||||||
if renderer.queue_render(&eye_contents).is_err() {
|
|
||||||
self.renderers.remove(&sim_id);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -235,7 +226,7 @@ impl Emulator {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum EmulatorCommand {
|
pub enum EmulatorCommand {
|
||||||
SetRenderer(SimId, TextureSink),
|
SetRenderer(SimId, GameRenderer),
|
||||||
LoadGame(SimId, PathBuf),
|
LoadGame(SimId, PathBuf),
|
||||||
StartSecondSim(Option<PathBuf>),
|
StartSecondSim(Option<PathBuf>),
|
||||||
StopSecondSim,
|
StopSecondSim,
|
||||||
|
|
143
src/graphics.rs
143
src/graphics.rs
|
@ -1,143 +0,0 @@
|
||||||
use std::{
|
|
||||||
sync::{
|
|
||||||
atomic::{AtomicU64, Ordering},
|
|
||||||
mpsc, Arc, Mutex, MutexGuard,
|
|
||||||
},
|
|
||||||
thread,
|
|
||||||
};
|
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
|
||||||
use itertools::Itertools as _;
|
|
||||||
use wgpu::{
|
|
||||||
Device, Extent3d, ImageCopyTexture, ImageDataLayout, Origin3d, Queue, Texture,
|
|
||||||
TextureDescriptor, TextureFormat, TextureUsages, TextureView, TextureViewDescriptor,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct TextureSink {
|
|
||||||
buffers: Arc<BufferPool>,
|
|
||||||
sink: mpsc::Sender<u64>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TextureSink {
|
|
||||||
pub fn new(device: &Device, queue: Arc<Queue>) -> (Self, TextureView) {
|
|
||||||
let texture = Self::create_texture(device);
|
|
||||||
let view = texture.create_view(&TextureViewDescriptor::default());
|
|
||||||
let buffers = Arc::new(BufferPool::new());
|
|
||||||
let (sink, source) = mpsc::channel();
|
|
||||||
let bufs = buffers.clone();
|
|
||||||
thread::spawn(move || {
|
|
||||||
let mut local_buf = vec![0; 384 * 224 * 2];
|
|
||||||
while let Ok(id) = source.recv() {
|
|
||||||
{
|
|
||||||
let Some(bytes) = bufs.read(id) else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
local_buf.copy_from_slice(bytes.as_slice());
|
|
||||||
}
|
|
||||||
Self::write_texture(&queue, &texture, local_buf.as_slice());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let sink = Self { buffers, sink };
|
|
||||||
(sink, view)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn queue_render(&mut self, bytes: &[u8]) -> Result<()> {
|
|
||||||
let id = {
|
|
||||||
let (mut buf, id) = self.buffers.write()?;
|
|
||||||
buf.copy_from_slice(bytes);
|
|
||||||
id
|
|
||||||
};
|
|
||||||
self.sink.send(id)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_texture(device: &Device) -> Texture {
|
|
||||||
let desc = TextureDescriptor {
|
|
||||||
label: Some("eyes"),
|
|
||||||
size: Extent3d {
|
|
||||||
width: 384,
|
|
||||||
height: 224,
|
|
||||||
depth_or_array_layers: 1,
|
|
||||||
},
|
|
||||||
mip_level_count: 1,
|
|
||||||
sample_count: 1,
|
|
||||||
dimension: wgpu::TextureDimension::D2,
|
|
||||||
format: TextureFormat::Rg8Unorm,
|
|
||||||
usage: TextureUsages::COPY_SRC
|
|
||||||
| TextureUsages::COPY_DST
|
|
||||||
| TextureUsages::TEXTURE_BINDING,
|
|
||||||
view_formats: &[TextureFormat::Rg8Unorm],
|
|
||||||
};
|
|
||||||
device.create_texture(&desc)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_texture(queue: &Queue, texture: &Texture, bytes: &[u8]) {
|
|
||||||
let texture = ImageCopyTexture {
|
|
||||||
texture,
|
|
||||||
mip_level: 0,
|
|
||||||
origin: Origin3d::ZERO,
|
|
||||||
aspect: wgpu::TextureAspect::All,
|
|
||||||
};
|
|
||||||
let size = Extent3d {
|
|
||||||
width: 384,
|
|
||||||
height: 224,
|
|
||||||
depth_or_array_layers: 1,
|
|
||||||
};
|
|
||||||
let data_layout = ImageDataLayout {
|
|
||||||
offset: 0,
|
|
||||||
bytes_per_row: Some(384 * 2),
|
|
||||||
rows_per_image: Some(224),
|
|
||||||
};
|
|
||||||
queue.write_texture(texture, bytes, data_layout, size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct BufferPool {
|
|
||||||
buffers: [Buffer; 3],
|
|
||||||
}
|
|
||||||
impl BufferPool {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
buffers: std::array::from_fn(|i| Buffer::new(i as u64)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read(&self, id: u64) -> Option<MutexGuard<'_, Vec<u8>>> {
|
|
||||||
let buf = self
|
|
||||||
.buffers
|
|
||||||
.iter()
|
|
||||||
.find(|buf| buf.id.load(Ordering::Acquire) == id)?;
|
|
||||||
buf.data.lock().ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write(&self) -> Result<(MutexGuard<'_, Vec<u8>>, u64)> {
|
|
||||||
let (min, max) = self
|
|
||||||
.buffers
|
|
||||||
.iter()
|
|
||||||
.minmax_by_key(|buf| buf.id.load(Ordering::Acquire))
|
|
||||||
.into_option()
|
|
||||||
.unwrap();
|
|
||||||
let Ok(lock) = min.data.lock() else {
|
|
||||||
bail!("lock was poisoned")
|
|
||||||
};
|
|
||||||
let id = max.id.load(Ordering::Acquire) + 1;
|
|
||||||
min.id.store(id, Ordering::Release);
|
|
||||||
Ok((lock, id))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct Buffer {
|
|
||||||
data: Mutex<Vec<u8>>,
|
|
||||||
id: AtomicU64,
|
|
||||||
}
|
|
||||||
impl Buffer {
|
|
||||||
fn new(id: u64) -> Self {
|
|
||||||
Self {
|
|
||||||
data: Mutex::new(vec![0; 384 * 224 * 2]),
|
|
||||||
id: AtomicU64::new(id),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -11,8 +11,8 @@ mod app;
|
||||||
mod audio;
|
mod audio;
|
||||||
mod controller;
|
mod controller;
|
||||||
mod emulator;
|
mod emulator;
|
||||||
mod graphics;
|
|
||||||
mod input;
|
mod input;
|
||||||
|
mod renderer;
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
struct Args {
|
struct Args {
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use wgpu::{
|
||||||
|
Extent3d, ImageCopyTexture, ImageDataLayout, Origin3d, Queue, Texture, TextureDescriptor,
|
||||||
|
TextureFormat, TextureUsages,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct GameRenderer {
|
||||||
|
pub queue: Arc<Queue>,
|
||||||
|
pub eyes: Arc<Texture>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GameRenderer {
|
||||||
|
pub fn render(&self, buffer: &[u8]) {
|
||||||
|
let texture = ImageCopyTexture {
|
||||||
|
texture: &self.eyes,
|
||||||
|
mip_level: 0,
|
||||||
|
origin: Origin3d::ZERO,
|
||||||
|
aspect: wgpu::TextureAspect::All,
|
||||||
|
};
|
||||||
|
let size = Extent3d {
|
||||||
|
width: 384,
|
||||||
|
height: 224,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
};
|
||||||
|
let data_layout = ImageDataLayout {
|
||||||
|
offset: 0,
|
||||||
|
bytes_per_row: Some(384 * 2),
|
||||||
|
rows_per_image: Some(224),
|
||||||
|
};
|
||||||
|
self.queue.write_texture(texture, buffer, data_layout, size);
|
||||||
|
}
|
||||||
|
pub fn create_texture(device: &wgpu::Device, name: &str) -> Texture {
|
||||||
|
let desc = TextureDescriptor {
|
||||||
|
label: Some(name),
|
||||||
|
size: Extent3d {
|
||||||
|
width: 384,
|
||||||
|
height: 224,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format: TextureFormat::Rg8Unorm,
|
||||||
|
usage: TextureUsages::COPY_SRC
|
||||||
|
| TextureUsages::COPY_DST
|
||||||
|
| TextureUsages::TEXTURE_BINDING,
|
||||||
|
view_formats: &[TextureFormat::Rg8Unorm],
|
||||||
|
};
|
||||||
|
device.create_texture(&desc)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue