Implement multiplayer #2
|
@ -1,4 +1,4 @@
|
||||||
use std::{sync::Arc, time::Instant};
|
use std::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},
|
||||||
renderer::GameRenderer,
|
graphics::TextureSink,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
|
@ -46,15 +46,8 @@ impl GameWindow {
|
||||||
.build();
|
.build();
|
||||||
let device = &window.device;
|
let device = &window.device;
|
||||||
|
|
||||||
let eyes = Arc::new(GameRenderer::create_texture(device, "eye"));
|
let (sink, texture_view) = TextureSink::new(device, window.queue.clone());
|
||||||
client.send_command(EmulatorCommand::SetRenderer(
|
client.send_command(EmulatorCommand::SetRenderer(sim_id, sink));
|
||||||
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],
|
||||||
|
@ -103,7 +96,7 @@ impl GameWindow {
|
||||||
entries: &[
|
entries: &[
|
||||||
wgpu::BindGroupEntry {
|
wgpu::BindGroupEntry {
|
||||||
binding: 0,
|
binding: 0,
|
||||||
resource: wgpu::BindingResource::TextureView(&eyes),
|
resource: wgpu::BindingResource::TextureView(&texture_view),
|
||||||
},
|
},
|
||||||
wgpu::BindGroupEntry {
|
wgpu::BindGroupEntry {
|
||||||
binding: 1,
|
binding: 1,
|
||||||
|
|
|
@ -11,7 +11,7 @@ use std::{
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
use crate::{audio::Audio, renderer::GameRenderer};
|
use crate::{audio::Audio, graphics::TextureSink};
|
||||||
use shrooms_vb_core::Sim;
|
use shrooms_vb_core::Sim;
|
||||||
pub use shrooms_vb_core::VBKey;
|
pub use shrooms_vb_core::VBKey;
|
||||||
|
|
||||||
|
@ -30,7 +30,10 @@ pub enum SimId {
|
||||||
Player2,
|
Player2,
|
||||||
}
|
}
|
||||||
impl SimId {
|
impl SimId {
|
||||||
pub fn to_index(self) -> usize {
|
pub const fn values() -> [Self; 2] {
|
||||||
|
[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,
|
||||||
|
@ -75,7 +78,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, GameRenderer>,
|
renderers: HashMap<SimId, TextureSink>,
|
||||||
running: Arc<AtomicBool>,
|
running: Arc<AtomicBool>,
|
||||||
has_game: Arc<AtomicBool>,
|
has_game: Arc<AtomicBool>,
|
||||||
}
|
}
|
||||||
|
@ -140,11 +143,17 @@ impl Emulator {
|
||||||
idle = false;
|
idle = false;
|
||||||
Sim::emulate_many(&mut self.sims);
|
Sim::emulate_many(&mut self.sims);
|
||||||
}
|
}
|
||||||
for (sim_id, renderer) in self.renderers.iter_mut() {
|
for sim_id in SimId::values() {
|
||||||
if let Some(sim) = self.sims.get_mut(sim_id.to_index()) {
|
let Some(renderer) = self.renderers.get_mut(&sim_id) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Some(sim) = self.sims.get_mut(sim_id.to_index()) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
if sim.read_pixels(&mut eye_contents) {
|
if sim.read_pixels(&mut eye_contents) {
|
||||||
idle = false;
|
idle = false;
|
||||||
renderer.render(&eye_contents);
|
if renderer.queue_render(&eye_contents).is_err() {
|
||||||
|
self.renderers.remove(&sim_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -226,7 +235,7 @@ impl Emulator {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum EmulatorCommand {
|
pub enum EmulatorCommand {
|
||||||
SetRenderer(SimId, GameRenderer),
|
SetRenderer(SimId, TextureSink),
|
||||||
LoadGame(SimId, PathBuf),
|
LoadGame(SimId, PathBuf),
|
||||||
StartSecondSim(Option<PathBuf>),
|
StartSecondSim(Option<PathBuf>),
|
||||||
StopSecondSim,
|
StopSecondSim,
|
||||||
|
|
|
@ -0,0 +1,143 @@
|
||||||
|
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 {
|
||||||
|
|
|
@ -1,53 +0,0 @@
|
||||||
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