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 winit::{
 | 
			
		||||
    dpi::LogicalSize,
 | 
			
		||||
| 
						 | 
				
			
			@ -9,7 +9,7 @@ use winit::{
 | 
			
		|||
 | 
			
		||||
use crate::{
 | 
			
		||||
    emulator::{EmulatorClient, EmulatorCommand, SimId},
 | 
			
		||||
    renderer::GameRenderer,
 | 
			
		||||
    graphics::TextureSink,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use super::{
 | 
			
		||||
| 
						 | 
				
			
			@ -46,15 +46,8 @@ impl GameWindow {
 | 
			
		|||
            .build();
 | 
			
		||||
        let device = &window.device;
 | 
			
		||||
 | 
			
		||||
        let eyes = Arc::new(GameRenderer::create_texture(device, "eye"));
 | 
			
		||||
        client.send_command(EmulatorCommand::SetRenderer(
 | 
			
		||||
            sim_id,
 | 
			
		||||
            GameRenderer {
 | 
			
		||||
                queue: window.queue.clone(),
 | 
			
		||||
                eyes: eyes.clone(),
 | 
			
		||||
            },
 | 
			
		||||
        ));
 | 
			
		||||
        let eyes = eyes.create_view(&wgpu::TextureViewDescriptor::default());
 | 
			
		||||
        let (sink, texture_view) = TextureSink::new(device, window.queue.clone());
 | 
			
		||||
        client.send_command(EmulatorCommand::SetRenderer(sim_id, sink));
 | 
			
		||||
        let sampler = device.create_sampler(&wgpu::SamplerDescriptor::default());
 | 
			
		||||
        let colors = Colors {
 | 
			
		||||
            left: [1.0, 0.0, 0.0, 1.0],
 | 
			
		||||
| 
						 | 
				
			
			@ -103,7 +96,7 @@ impl GameWindow {
 | 
			
		|||
            entries: &[
 | 
			
		||||
                wgpu::BindGroupEntry {
 | 
			
		||||
                    binding: 0,
 | 
			
		||||
                    resource: wgpu::BindingResource::TextureView(&eyes),
 | 
			
		||||
                    resource: wgpu::BindingResource::TextureView(&texture_view),
 | 
			
		||||
                },
 | 
			
		||||
                wgpu::BindGroupEntry {
 | 
			
		||||
                    binding: 1,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,7 +11,7 @@ use std::{
 | 
			
		|||
 | 
			
		||||
use anyhow::Result;
 | 
			
		||||
 | 
			
		||||
use crate::{audio::Audio, renderer::GameRenderer};
 | 
			
		||||
use crate::{audio::Audio, graphics::TextureSink};
 | 
			
		||||
use shrooms_vb_core::Sim;
 | 
			
		||||
pub use shrooms_vb_core::VBKey;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -30,7 +30,10 @@ pub enum SimId {
 | 
			
		|||
    Player2,
 | 
			
		||||
}
 | 
			
		||||
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 {
 | 
			
		||||
            Self::Player1 => 0,
 | 
			
		||||
            Self::Player2 => 1,
 | 
			
		||||
| 
						 | 
				
			
			@ -75,7 +78,7 @@ pub struct Emulator {
 | 
			
		|||
    sims: Vec<Sim>,
 | 
			
		||||
    audio: Audio,
 | 
			
		||||
    commands: mpsc::Receiver<EmulatorCommand>,
 | 
			
		||||
    renderers: HashMap<SimId, GameRenderer>,
 | 
			
		||||
    renderers: HashMap<SimId, TextureSink>,
 | 
			
		||||
    running: Arc<AtomicBool>,
 | 
			
		||||
    has_game: Arc<AtomicBool>,
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -140,11 +143,17 @@ impl Emulator {
 | 
			
		|||
                idle = false;
 | 
			
		||||
                Sim::emulate_many(&mut self.sims);
 | 
			
		||||
            }
 | 
			
		||||
            for (sim_id, renderer) in self.renderers.iter_mut() {
 | 
			
		||||
                if let Some(sim) = self.sims.get_mut(sim_id.to_index()) {
 | 
			
		||||
            for sim_id in SimId::values() {
 | 
			
		||||
                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) {
 | 
			
		||||
                    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)]
 | 
			
		||||
pub enum EmulatorCommand {
 | 
			
		||||
    SetRenderer(SimId, GameRenderer),
 | 
			
		||||
    SetRenderer(SimId, TextureSink),
 | 
			
		||||
    LoadGame(SimId, PathBuf),
 | 
			
		||||
    StartSecondSim(Option<PathBuf>),
 | 
			
		||||
    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 controller;
 | 
			
		||||
mod emulator;
 | 
			
		||||
mod graphics;
 | 
			
		||||
mod input;
 | 
			
		||||
mod renderer;
 | 
			
		||||
 | 
			
		||||
#[derive(Parser)]
 | 
			
		||||
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