use romy_core::input::*;
use romy_core::runtime::*;
use romy_core::*;
use sample::Sample;
use std::collections::VecDeque;
use std::io::Cursor;
use std::sync::{Arc, RwLock};
use std::time::{Duration, Instant};
use cpal::traits::{DeviceTrait, EventLoopTrait, HostTrait};
use sample::signal::Signal;
pub use wgpu;
use wgpu::Surface;
use winit::dpi::LogicalSize;
use winit::event::{DeviceEvent, ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent};
use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::WindowBuilder;
pub fn run_standalone(app: Box<dyn Game>, info: Info) {
run(
RunBundle {
game: Box::new(GameMutMap::new(app)),
info,
},
|_| None,
);
}
struct RomyGame {
bundle: RunBundle,
start_time: Instant,
step: Duration,
steps: u128,
}
impl RomyGame {
fn new(bundle: RunBundle) -> Self {
let step = Duration::from_nanos(u64::from(bundle.info.step_interval()));
Self {
bundle,
start_time: Instant::now(),
step,
steps: 0,
}
}
}
fn get_spv(bytes: &[u8]) -> Vec<u32> {
wgpu::read_spirv(Cursor::new(bytes)).unwrap()
}
fn convert_virtual_key_code(code: VirtualKeyCode) -> Option<KeyCode> {
match code {
VirtualKeyCode::Key1 => Some(KeyCode::_1),
VirtualKeyCode::Key2 => Some(KeyCode::_2),
VirtualKeyCode::Key3 => Some(KeyCode::_3),
VirtualKeyCode::Key4 => Some(KeyCode::_4),
VirtualKeyCode::Key5 => Some(KeyCode::_5),
VirtualKeyCode::Key6 => Some(KeyCode::_6),
VirtualKeyCode::Key7 => Some(KeyCode::_7),
VirtualKeyCode::Key8 => Some(KeyCode::_8),
VirtualKeyCode::Key9 => Some(KeyCode::_9),
VirtualKeyCode::Key0 => Some(KeyCode::_0),
VirtualKeyCode::A => Some(KeyCode::A),
VirtualKeyCode::B => Some(KeyCode::B),
VirtualKeyCode::C => Some(KeyCode::C),
VirtualKeyCode::D => Some(KeyCode::D),
VirtualKeyCode::E => Some(KeyCode::E),
VirtualKeyCode::F => Some(KeyCode::F),
VirtualKeyCode::G => Some(KeyCode::G),
VirtualKeyCode::H => Some(KeyCode::H),
VirtualKeyCode::I => Some(KeyCode::I),
VirtualKeyCode::J => Some(KeyCode::J),
VirtualKeyCode::K => Some(KeyCode::K),
VirtualKeyCode::L => Some(KeyCode::L),
VirtualKeyCode::M => Some(KeyCode::M),
VirtualKeyCode::N => Some(KeyCode::N),
VirtualKeyCode::O => Some(KeyCode::O),
VirtualKeyCode::P => Some(KeyCode::P),
VirtualKeyCode::Q => Some(KeyCode::Q),
VirtualKeyCode::R => Some(KeyCode::R),
VirtualKeyCode::S => Some(KeyCode::S),
VirtualKeyCode::T => Some(KeyCode::T),
VirtualKeyCode::U => Some(KeyCode::U),
VirtualKeyCode::V => Some(KeyCode::V),
VirtualKeyCode::W => Some(KeyCode::W),
VirtualKeyCode::X => Some(KeyCode::X),
VirtualKeyCode::Y => Some(KeyCode::Y),
VirtualKeyCode::Z => Some(KeyCode::Z),
VirtualKeyCode::Up => Some(KeyCode::Up),
VirtualKeyCode::Down => Some(KeyCode::Down),
VirtualKeyCode::Left => Some(KeyCode::Left),
VirtualKeyCode::Right => Some(KeyCode::Right),
VirtualKeyCode::LBracket => Some(KeyCode::LeftBracket),
VirtualKeyCode::RBracket => Some(KeyCode::RightBracket),
VirtualKeyCode::Slash => Some(KeyCode::Slash),
VirtualKeyCode::Backslash => Some(KeyCode::Backslash),
VirtualKeyCode::Comma => Some(KeyCode::Comma),
VirtualKeyCode::Period => Some(KeyCode::Period),
VirtualKeyCode::Semicolon => Some(KeyCode::Semicolon),
_ => None,
}
}
struct Rusty {
device: wgpu::Device,
queue: wgpu::Queue,
surface: wgpu::Surface,
vertex_shader_module: wgpu::ShaderModule,
fragment_shader_module: wgpu::ShaderModule,
texture_extent: wgpu::Extent3d,
texture: wgpu::Texture,
swap_chain: wgpu::SwapChain,
render_pipeline: wgpu::RenderPipeline,
bind_group: wgpu::BindGroup,
pixels: Vec<u8>,
buffer_width: u32,
buffer_height: u32,
title_prefix: String,
sample_rate: Arc<RwLock<u32>>,
sound_queue: Arc<RwLock<VecDeque<f32>>>,
window: winit::window::Window,
}
impl Rusty {
#[allow(clippy::too_many_arguments)]
fn create_pipeline(
device: &wgpu::Device,
surface: &wgpu::Surface,
vertex_shader_module: &wgpu::ShaderModule,
fragment_shader_module: &wgpu::ShaderModule,
width: u32,
height: u32,
buffer_width: u32,
buffer_height: u32,
) -> (
wgpu::Extent3d,
wgpu::Texture,
wgpu::SwapChain,
wgpu::RenderPipeline,
wgpu::BindGroup,
Vec<u8>,
) {
let texture_format = wgpu::TextureFormat::Rgba8UnormSrgb;
let texture_extent = wgpu::Extent3d {
width: buffer_width,
height: buffer_height,
depth: 1,
};
let texture = device.create_texture(&wgpu::TextureDescriptor {
size: texture_extent,
array_layer_count: 1,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: texture_format,
usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::COPY_DST,
});
let texture_view = texture.create_default_view();
let swap_chain = device.create_swap_chain(
&surface,
&wgpu::SwapChainDescriptor {
usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
format: wgpu::TextureFormat::Bgra8UnormSrgb,
width,
height,
present_mode: wgpu::PresentMode::Vsync,
},
);
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Nearest,
min_filter: wgpu::FilterMode::Nearest,
mipmap_filter: wgpu::FilterMode::Nearest,
lod_min_clamp: 0.0,
lod_max_clamp: 1.0,
compare_function: wgpu::CompareFunction::Always,
});
let width_f = width as f32;
let height_f = height as f32;
let buffer_width_f = buffer_width as f32;
let buffer_height_f = buffer_height as f32;
let scale = (width_f / buffer_width_f).min(height_f / buffer_height_f);
let scale_width = buffer_width_f * scale / width_f;
let scale_height = buffer_height_f * scale / height_f;
let transform = [
scale_width,
0.0,
0.0,
0.0,
0.0,
scale_height,
0.0,
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
0.0,
0.0,
1.0,
];
let uniform_buffer = device
.create_buffer_mapped(16, wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST)
.fill_from_slice(&transform);
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
bindings: &[
wgpu::BindGroupLayoutBinding {
binding: 0,
visibility: wgpu::ShaderStage::FRAGMENT,
ty: wgpu::BindingType::SampledTexture {
multisampled: false,
dimension: wgpu::TextureViewDimension::D2,
},
},
wgpu::BindGroupLayoutBinding {
binding: 1,
visibility: wgpu::ShaderStage::FRAGMENT,
ty: wgpu::BindingType::Sampler,
},
wgpu::BindGroupLayoutBinding {
binding: 2,
visibility: wgpu::ShaderStage::VERTEX,
ty: wgpu::BindingType::UniformBuffer { dynamic: false },
},
],
});
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &bind_group_layout,
bindings: &[
wgpu::Binding {
binding: 0,
resource: wgpu::BindingResource::TextureView(&texture_view),
},
wgpu::Binding {
binding: 1,
resource: wgpu::BindingResource::Sampler(&sampler),
},
wgpu::Binding {
binding: 2,
resource: wgpu::BindingResource::Buffer {
buffer: &uniform_buffer,
range: 0..64,
},
},
],
});
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
bind_group_layouts: &[&bind_group_layout],
});
let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
layout: &pipeline_layout,
vertex_stage: wgpu::ProgrammableStageDescriptor {
module: &vertex_shader_module,
entry_point: "main",
},
fragment_stage: Some(wgpu::ProgrammableStageDescriptor {
module: &fragment_shader_module,
entry_point: "main",
}),
rasterization_state: Some(wgpu::RasterizationStateDescriptor {
front_face: wgpu::FrontFace::Ccw,
cull_mode: wgpu::CullMode::None,
depth_bias: 0,
depth_bias_slope_scale: 0.0,
depth_bias_clamp: 0.0,
}),
primitive_topology: wgpu::PrimitiveTopology::TriangleList,
color_states: &[wgpu::ColorStateDescriptor {
format: wgpu::TextureFormat::Bgra8UnormSrgb,
color_blend: wgpu::BlendDescriptor::REPLACE,
alpha_blend: wgpu::BlendDescriptor::REPLACE,
write_mask: wgpu::ColorWrite::ALL,
}],
depth_stencil_state: None,
index_format: wgpu::IndexFormat::Uint16,
vertex_buffers: &[],
sample_count: 1,
sample_mask: !0,
alpha_to_coverage_enabled: false,
});
let capacity = (buffer_width * buffer_height * 4) as usize;
let mut pixels: Vec<u8> = Vec::with_capacity(capacity);
pixels.resize_with(capacity, Default::default);
(
texture_extent,
texture,
swap_chain,
render_pipeline,
bind_group,
pixels,
)
}
fn new() -> (Self, winit::event_loop::EventLoop<()>) {
let title_prefix = String::from("Romy");
let buffer_width = 128;
let buffer_height = 128;
let sample_rate = Arc::new(RwLock::new(44100));
let sound_queue = Arc::new(RwLock::new(VecDeque::<f32>::new()));
let request_adapter_options = wgpu::RequestAdapterOptions::default();
let device_descriptor = wgpu::DeviceDescriptor::default();
let adapter = wgpu::Adapter::request(&request_adapter_options).unwrap();
let (device, queue) = adapter.request_device(&device_descriptor);
let vertex_shader_module =
device.create_shader_module(&get_spv(include_bytes!("./shaders/vertex.spv")));
let fragment_shader_module =
device.create_shader_module(&get_spv(include_bytes!("./shaders/fragment.spv")));
let event_loop = EventLoop::new();
let window = {
let size = LogicalSize::new(640.0, 480.0);
WindowBuilder::new()
.with_title(title_prefix.clone())
.with_inner_size(size)
.with_min_inner_size(size)
.build(&event_loop)
.unwrap()
};
let surface = Surface::create(&window);
let size = window.inner_size();
let (width, height) = size.into();
let (texture_extent, texture, swap_chain, render_pipeline, bind_group, pixels) =
Self::create_pipeline(
&device,
&surface,
&vertex_shader_module,
&fragment_shader_module,
buffer_width,
buffer_height,
width,
height,
);
(
Rusty {
device,
surface,
queue,
vertex_shader_module,
fragment_shader_module,
buffer_width,
buffer_height,
title_prefix,
sample_rate,
sound_queue,
window,
texture_extent,
texture,
swap_chain,
render_pipeline,
bind_group,
pixels,
},
event_loop,
)
}
fn recreate_pipeline(&mut self) {
let size = self
.window
.inner_size();
let (width, height) = size.into();
let (texture_extent, texture, swap_chain, render_pipeline, bind_group, pixels) =
Self::create_pipeline(
&self.device,
&self.surface,
&self.vertex_shader_module,
&self.fragment_shader_module,
width,
height,
self.buffer_width,
self.buffer_height,
);
self.texture_extent = texture_extent;
self.texture = texture;
self.swap_chain = swap_chain;
self.render_pipeline = render_pipeline;
self.bind_group = bind_group;
self.pixels = pixels;
}
fn run<F>(
mut self,
bundle: RunBundle,
load_new: F,
event_loop: winit::event_loop::EventLoop<()>,
) where
F: 'static + Fn(&str) -> Option<RunBundle>,
{
let mut game = RomyGame::new(bundle);
let sound_queue_thread = self.sound_queue.clone();
let sample_rate_thread = self.sample_rate.clone();
std::thread::spawn(move || {
let sound_queue = sound_queue_thread;
let sample_rate = sample_rate_thread;
let host = cpal::default_host();
let device = host
.default_output_device()
.expect("failed to find a default output device");
let format = device.default_output_format().unwrap();
{
let mut sample_rate = sample_rate.write().unwrap();
*sample_rate = format.sample_rate.0;
}
let audio_event_loop = host.event_loop();
let stream_id = audio_event_loop
.build_output_stream(&device, &format)
.unwrap();
audio_event_loop.play_stream(stream_id).unwrap();
audio_event_loop.run(|id, result| {
let data = match result {
Ok(data) => data,
Err(err) => {
eprintln!("an error occurred on stream {:?}: {}", id, err);
return;
}
};
match data {
cpal::StreamData::Output {
buffer: cpal::UnknownTypeOutputBuffer::U16(mut buffer),
} => {
let mut sound_queue = sound_queue.write().unwrap();
for sample in buffer.chunks_mut(format.channels as usize) {
let s = sound_queue.pop_front();
for out in sample.iter_mut() {
*out = match s {
Some(s) => s.to_sample::<u16>(),
None => 0,
};
}
}
}
cpal::StreamData::Output {
buffer: cpal::UnknownTypeOutputBuffer::I16(mut buffer),
} => {
let mut sound_queue = sound_queue.write().unwrap();
for sample in buffer.chunks_mut(format.channels as usize) {
let s = sound_queue.pop_front();
for out in sample.iter_mut() {
*out = match s {
Some(s) => s.to_sample::<i16>(),
None => 0,
};
}
}
}
cpal::StreamData::Output {
buffer: cpal::UnknownTypeOutputBuffer::F32(mut buffer),
} => {
let mut sound_queue = sound_queue.write().unwrap();
for sample in buffer.chunks_mut(format.channels as usize) {
let s = sound_queue.pop_front();
for out in sample.iter_mut() {
*out = match s {
Some(s) => s,
None => 0.0,
};
}
}
}
_ => (),
}
});
});
let mut keyboard = Keyboard::default();
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Poll;
match event {
Event::MainEventsCleared => {
self.window.request_redraw();
}
Event::WindowEvent {
event:
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Pressed,
virtual_keycode: Some(code),
..
},
..
},
..
} => {
let code = convert_virtual_key_code(code);
if let Some(code) = code {
keyboard.key_down(code);
}
}
Event::WindowEvent {
event:
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(code),
..
},
..
},
..
} => {
let code = convert_virtual_key_code(code);
if let Some(code) = code {
keyboard.key_up(code);
}
}
Event::RedrawRequested(_) => {
let size = self
.window
.inner_size();
let (width, height): (u32, u32) = size.into();
let app = &mut game.bundle.game;
let time_span = Instant::now().duration_since(game.start_time);
let step_offset = (time_span.as_micros() % game.step.as_micros()) as f32
/ game.step.as_micros() as f32;
let render = app.draw(&DrawArguments::new(
width as i32,
height as i32,
step_offset,
));
if self.buffer_width != render.width() as u32
|| self.buffer_height != render.height() as u32
{
self.buffer_width = render.width() as u32;
self.buffer_height = render.height() as u32;
self.recreate_pipeline();
}
let source = render.pixels8();
self.pixels.clone_from_slice(&source[..]);
let swap_chain_texture = self.swap_chain.get_next_texture();
let mut encoder = self
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor { todo: 0 });
let buffer = self
.device
.create_buffer_mapped(self.pixels.len(), wgpu::BufferUsage::COPY_SRC)
.fill_from_slice(&self.pixels);
encoder.copy_buffer_to_texture(
wgpu::BufferCopyView {
buffer: &buffer,
offset: 0,
row_pitch: self.texture_extent.width * 4,
image_height: self.texture_extent.height,
},
wgpu::TextureCopyView {
texture: &self.texture,
mip_level: 0,
array_layer: 0,
origin: wgpu::Origin3d {
x: 0.0,
y: 0.0,
z: 0.0,
},
},
self.texture_extent,
);
{
let mut render_pass =
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
attachment: &swap_chain_texture.view,
resolve_target: None,
load_op: wgpu::LoadOp::Clear,
store_op: wgpu::StoreOp::Store,
clear_color: wgpu::Color::BLACK,
}],
depth_stencil_attachment: None,
});
render_pass.set_pipeline(&self.render_pipeline);
render_pass.set_bind_group(0, &self.bind_group, &[]);
render_pass.draw(0..6, 0..1);
}
self.queue.submit(&[encoder.finish()]);
}
Event::WindowEvent {
event: WindowEvent::DroppedFile(file_path),
..
} => {
if let Some(bundle) = load_new(file_path.to_str().unwrap()) {
self.window.set_title(&format!(
"{}: {}",
self.title_prefix,
bundle.info.name()
));
game = RomyGame::new(bundle);
}
}
Event::WindowEvent {
event: WindowEvent::Resized(_),
..
} => {
self.recreate_pipeline();
}
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
_ => (),
}
let mut input = InputCollection::new();
input.add_input(InputDevice::Keyboard(keyboard.clone()));
let time_span = Instant::now().duration_since(game.start_time);
let expected_steps = time_span.as_micros() / game.step.as_micros();
let app = &mut game.bundle.game;
let info = &game.bundle.info;
if expected_steps - game.steps > 0 {
app.step(&StepArguments::new(input.get_input_arguments(&info)));
let audio = app.render_audio(&RenderAudioArguments {});
if audio.sample_rate() as u32 % game.bundle.info.steps_per_second() != 0 {
panic!("Sample rate not divisible by steps per second");
}
let mut signal = sample::signal::from_interleaved_samples_iter::<_, [f32; 1]>(
audio.samples().iter().cloned(),
);
let interpolate = sample::interpolate::Linear::from_source(&mut signal);
let sample_rate = { *self.sample_rate.read().unwrap() };
let signal =
signal.from_hz_to_hz(interpolate, audio.sample_rate() as f64, sample_rate as f64);
let mut sound_queue = self.sound_queue.write().unwrap();
for frame in signal.until_exhausted() {
sound_queue.push_back(frame[0]);
}
game.steps += 1;
}
});
}
}
pub fn run<F>(bundle: RunBundle, load_new: F)
where
F: 'static + Fn(&str) -> Option<RunBundle>,
{
let (rusty, event_loop) = Rusty::new();
rusty.run(bundle, load_new, event_loop);
}