Write textures in separate thread; fixes perf
This commit is contained in:
		
							parent
							
								
									f22a74b036
								
							
						
					
					
						commit
						d306f19297
					
				| 
						 | 
					@ -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 {
 | 
				
			||||||
                    if sim.read_pixels(&mut eye_contents) {
 | 
					                    continue;
 | 
				
			||||||
                        idle = false;
 | 
					                };
 | 
				
			||||||
                        renderer.render(&eye_contents);
 | 
					                let Some(sim) = self.sims.get_mut(sim_id.to_index()) else {
 | 
				
			||||||
 | 
					                    continue;
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					                if sim.read_pixels(&mut eye_contents) {
 | 
				
			||||||
 | 
					                    idle = false;
 | 
				
			||||||
 | 
					                    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