1use romy_core::input::*;
2use romy_core::runtime::*;
3use romy_core::*;
4use std::cmp::min;
5use std::collections::VecDeque;
6use std::io::Cursor;
7use std::sync::{Arc, RwLock};
8use std::time::{Duration, Instant};
9
10use cpal::traits::{DeviceTrait, EventLoopTrait, HostTrait};
11pub use wgpu;
12use wgpu::Surface;
13use winit::dpi::LogicalSize;
14use winit::event::{DeviceEvent, ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent};
15use winit::event_loop::{ControlFlow, EventLoop};
16use winit::window::WindowBuilder;
17
18pub fn run_standalone(app: Box<dyn Game>, info: Info) {
19 run(
20 RunBundle {
21 game: Box::new(GameMutMap::new(app)),
22 info,
23 },
24 |_| None,
25 );
26}
27
28struct RomyGame {
29 bundle: RunBundle,
30 start_time: Instant,
31 step: Duration,
32 steps: u128,
33}
34
35impl RomyGame {
36 fn new(bundle: RunBundle) -> Self {
37 let step = Duration::from_nanos(u64::from(bundle.info.step_interval()));
38
39 Self {
40 bundle,
41 start_time: Instant::now(),
42 step,
43 steps: 0,
44 }
45 }
46}
47
48fn get_spv(bytes: &[u8]) -> Vec<u32> {
49 wgpu::read_spirv(Cursor::new(bytes)).unwrap()
50}
51
52fn convert_virtual_key_code(code: VirtualKeyCode) -> Option<KeyCode> {
53 match code {
54 VirtualKeyCode::Key1 => Some(KeyCode::_1),
55 VirtualKeyCode::Key2 => Some(KeyCode::_2),
56 VirtualKeyCode::Key3 => Some(KeyCode::_3),
57 VirtualKeyCode::Key4 => Some(KeyCode::_4),
58 VirtualKeyCode::Key5 => Some(KeyCode::_5),
59 VirtualKeyCode::Key6 => Some(KeyCode::_6),
60 VirtualKeyCode::Key7 => Some(KeyCode::_7),
61 VirtualKeyCode::Key8 => Some(KeyCode::_8),
62 VirtualKeyCode::Key9 => Some(KeyCode::_9),
63 VirtualKeyCode::Key0 => Some(KeyCode::_0),
64 VirtualKeyCode::A => Some(KeyCode::A),
65 VirtualKeyCode::B => Some(KeyCode::B),
66 VirtualKeyCode::C => Some(KeyCode::C),
67 VirtualKeyCode::D => Some(KeyCode::D),
68 VirtualKeyCode::E => Some(KeyCode::E),
69 VirtualKeyCode::F => Some(KeyCode::F),
70 VirtualKeyCode::G => Some(KeyCode::G),
71 VirtualKeyCode::H => Some(KeyCode::H),
72 VirtualKeyCode::I => Some(KeyCode::I),
73 VirtualKeyCode::J => Some(KeyCode::J),
74 VirtualKeyCode::K => Some(KeyCode::K),
75 VirtualKeyCode::L => Some(KeyCode::L),
76 VirtualKeyCode::M => Some(KeyCode::M),
77 VirtualKeyCode::N => Some(KeyCode::N),
78 VirtualKeyCode::O => Some(KeyCode::O),
79 VirtualKeyCode::P => Some(KeyCode::P),
80 VirtualKeyCode::Q => Some(KeyCode::Q),
81 VirtualKeyCode::R => Some(KeyCode::R),
82 VirtualKeyCode::S => Some(KeyCode::S),
83 VirtualKeyCode::T => Some(KeyCode::T),
84 VirtualKeyCode::U => Some(KeyCode::U),
85 VirtualKeyCode::V => Some(KeyCode::V),
86 VirtualKeyCode::W => Some(KeyCode::W),
87 VirtualKeyCode::X => Some(KeyCode::X),
88 VirtualKeyCode::Y => Some(KeyCode::Y),
89 VirtualKeyCode::Z => Some(KeyCode::Z),
90 VirtualKeyCode::Up => Some(KeyCode::Up),
91 VirtualKeyCode::Down => Some(KeyCode::Down),
92 VirtualKeyCode::Left => Some(KeyCode::Left),
93 VirtualKeyCode::Right => Some(KeyCode::Right),
94 VirtualKeyCode::LBracket => Some(KeyCode::LeftBracket),
95 VirtualKeyCode::RBracket => Some(KeyCode::RightBracket),
96 VirtualKeyCode::Slash => Some(KeyCode::Slash),
97 VirtualKeyCode::Backslash => Some(KeyCode::Backslash),
98 VirtualKeyCode::Comma => Some(KeyCode::Comma),
99 VirtualKeyCode::Period => Some(KeyCode::Period),
100 VirtualKeyCode::Semicolon => Some(KeyCode::Semicolon),
101 _ => None,
102 }
103}
104
105struct Rusty {
106 device: wgpu::Device,
107 queue: wgpu::Queue,
108 surface: wgpu::Surface,
109 vertex_shader_module: wgpu::ShaderModule,
110 fragment_shader_module: wgpu::ShaderModule,
111 texture_extent: wgpu::Extent3d,
112 texture: wgpu::Texture,
113 swap_chain: wgpu::SwapChain,
114 render_pipeline: wgpu::RenderPipeline,
115 bind_group: wgpu::BindGroup,
116 pixels: Vec<u8>,
117 buffer_width: u32,
118 buffer_height: u32,
119 title_prefix: String,
120 sample_rate: Arc<RwLock<u32>>,
121 sound_queue: Arc<RwLock<VecDeque<f32>>>,
122 window: winit::window::Window,
123}
124
125impl Rusty {
126 #[allow(clippy::too_many_arguments)]
127 fn create_pipeline(
128 device: &wgpu::Device,
129 surface: &wgpu::Surface,
130 vertex_shader_module: &wgpu::ShaderModule,
131 fragment_shader_module: &wgpu::ShaderModule,
132 width: u32,
133 height: u32,
134 buffer_width: u32,
135 buffer_height: u32,
136 ) -> (
137 wgpu::Extent3d,
138 wgpu::Texture,
139 wgpu::SwapChain,
140 wgpu::RenderPipeline,
141 wgpu::BindGroup,
142 Vec<u8>,
143 ) {
144 let texture_format = wgpu::TextureFormat::Rgba8UnormSrgb;
145 let texture_extent = wgpu::Extent3d {
146 width: buffer_width,
147 height: buffer_height,
148 depth: 1,
149 };
150
151 let texture = device.create_texture(&wgpu::TextureDescriptor {
152 size: texture_extent,
153 array_layer_count: 1,
154 mip_level_count: 1,
155 sample_count: 1,
156 dimension: wgpu::TextureDimension::D2,
157 format: texture_format,
158 usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::COPY_DST,
159 });
160
161 let texture_view = texture.create_default_view();
162
163 let swap_chain = device.create_swap_chain(
164 &surface,
165 &wgpu::SwapChainDescriptor {
166 usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
167 format: wgpu::TextureFormat::Bgra8UnormSrgb,
168 width,
169 height,
170 present_mode: wgpu::PresentMode::Vsync,
171 },
172 );
173
174 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
175 address_mode_u: wgpu::AddressMode::ClampToEdge,
176 address_mode_v: wgpu::AddressMode::ClampToEdge,
177 address_mode_w: wgpu::AddressMode::ClampToEdge,
178 mag_filter: wgpu::FilterMode::Nearest,
179 min_filter: wgpu::FilterMode::Nearest,
180 mipmap_filter: wgpu::FilterMode::Nearest,
181 lod_min_clamp: 0.0,
182 lod_max_clamp: 1.0,
183 compare_function: wgpu::CompareFunction::Always,
184 });
185
186 let width_f = width as f32;
187 let height_f = height as f32;
188 let buffer_width_f = buffer_width as f32;
189 let buffer_height_f = buffer_height as f32;
190
191 let scale = (width_f / buffer_width_f).min(height_f / buffer_height_f);
192
193 let scale_width = buffer_width_f * scale / width_f;
194 let scale_height = buffer_height_f * scale / height_f;
195
196 let transform = [
197 scale_width,
198 0.0,
199 0.0,
200 0.0,
201 0.0,
202 scale_height,
203 0.0,
204 0.0,
205 0.0,
206 0.0,
207 1.0,
208 0.0,
209 0.0,
210 0.0,
211 0.0,
212 1.0,
213 ];
214
215 let uniform_buffer = device
216 .create_buffer_mapped(16, wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST)
217 .fill_from_slice(&transform);
218
219 let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
220 bindings: &[
221 wgpu::BindGroupLayoutBinding {
222 binding: 0,
223 visibility: wgpu::ShaderStage::FRAGMENT,
224 ty: wgpu::BindingType::SampledTexture {
225 multisampled: false,
226 dimension: wgpu::TextureViewDimension::D2,
227 },
228 },
229 wgpu::BindGroupLayoutBinding {
230 binding: 1,
231 visibility: wgpu::ShaderStage::FRAGMENT,
232 ty: wgpu::BindingType::Sampler,
233 },
234 wgpu::BindGroupLayoutBinding {
235 binding: 2,
236 visibility: wgpu::ShaderStage::VERTEX,
237 ty: wgpu::BindingType::UniformBuffer { dynamic: false },
238 },
239 ],
240 });
241
242 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
243 layout: &bind_group_layout,
244 bindings: &[
245 wgpu::Binding {
246 binding: 0,
247 resource: wgpu::BindingResource::TextureView(&texture_view),
248 },
249 wgpu::Binding {
250 binding: 1,
251 resource: wgpu::BindingResource::Sampler(&sampler),
252 },
253 wgpu::Binding {
254 binding: 2,
255 resource: wgpu::BindingResource::Buffer {
256 buffer: &uniform_buffer,
257 range: 0..64,
258 },
259 },
260 ],
261 });
262
263 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
264 bind_group_layouts: &[&bind_group_layout],
265 });
266
267 let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
268 layout: &pipeline_layout,
269 vertex_stage: wgpu::ProgrammableStageDescriptor {
270 module: &vertex_shader_module,
271 entry_point: "main",
272 },
273 fragment_stage: Some(wgpu::ProgrammableStageDescriptor {
274 module: &fragment_shader_module,
275 entry_point: "main",
276 }),
277 rasterization_state: Some(wgpu::RasterizationStateDescriptor {
278 front_face: wgpu::FrontFace::Ccw,
279 cull_mode: wgpu::CullMode::None,
280 depth_bias: 0,
281 depth_bias_slope_scale: 0.0,
282 depth_bias_clamp: 0.0,
283 }),
284 primitive_topology: wgpu::PrimitiveTopology::TriangleList,
285 color_states: &[wgpu::ColorStateDescriptor {
286 format: wgpu::TextureFormat::Bgra8UnormSrgb,
287 color_blend: wgpu::BlendDescriptor::REPLACE,
288 alpha_blend: wgpu::BlendDescriptor::REPLACE,
289 write_mask: wgpu::ColorWrite::ALL,
290 }],
291 depth_stencil_state: None,
292 index_format: wgpu::IndexFormat::Uint16,
293 vertex_buffers: &[],
294 sample_count: 1,
295 sample_mask: !0,
296 alpha_to_coverage_enabled: false,
297 });
298
299 let capacity = (buffer_width * buffer_height * 4) as usize;
300 let mut pixels: Vec<u8> = Vec::with_capacity(capacity);
301 pixels.resize_with(capacity, Default::default);
302
303 (
304 texture_extent,
305 texture,
306 swap_chain,
307 render_pipeline,
308 bind_group,
309 pixels,
310 )
311 }
312
313 fn new() -> (Self, winit::event_loop::EventLoop<()>) {
314 let title_prefix = String::from("Romy");
315 let buffer_width = 128;
316 let buffer_height = 128;
317 let sample_rate = Arc::new(RwLock::new(44100));
318 let sound_queue = Arc::new(RwLock::new(VecDeque::<f32>::new()));
319
320 let request_adapter_options = wgpu::RequestAdapterOptions::default();
321 let device_descriptor = wgpu::DeviceDescriptor::default();
322 let adapter = wgpu::Adapter::request(&request_adapter_options).unwrap();
323 let (device, queue) = adapter.request_device(&device_descriptor);
324 let vertex_shader_module =
325 device.create_shader_module(&get_spv(include_bytes!("./shaders/vertex.spv")));
326 let fragment_shader_module =
327 device.create_shader_module(&get_spv(include_bytes!("./shaders/fragment.spv")));
328
329 let event_loop = EventLoop::new();
330 let window = {
331 WindowBuilder::new()
332 .with_title(title_prefix.clone())
333 .build(&event_loop)
334 .unwrap()
335 };
336
337 let surface = Surface::create(&window);
338
339 let size = window.inner_size();
340 let (width, height) = size.into();
341
342 let (texture_extent, texture, swap_chain, render_pipeline, bind_group, pixels) =
343 Self::create_pipeline(
344 &device,
345 &surface,
346 &vertex_shader_module,
347 &fragment_shader_module,
348 width,
349 height,
350 buffer_width,
351 buffer_height,
352 );
353
354 (
355 Rusty {
356 device,
357 surface,
358 queue,
359 vertex_shader_module,
360 fragment_shader_module,
361 buffer_width,
362 buffer_height,
363 title_prefix,
364 sample_rate,
365 sound_queue,
366 window,
367 texture_extent,
368 texture,
369 swap_chain,
370 render_pipeline,
371 bind_group,
372 pixels,
373 },
374 event_loop,
375 )
376 }
377
378 fn recreate_pipeline(&mut self) {
379 let size = self.window.inner_size();
380 let (width, height) = size.into();
381
382 let (texture_extent, texture, swap_chain, render_pipeline, bind_group, pixels) =
383 Self::create_pipeline(
384 &self.device,
385 &self.surface,
386 &self.vertex_shader_module,
387 &self.fragment_shader_module,
388 width,
389 height,
390 self.buffer_width,
391 self.buffer_height,
392 );
393
394 self.texture_extent = texture_extent;
395 self.texture = texture;
396 self.swap_chain = swap_chain;
397 self.render_pipeline = render_pipeline;
398 self.bind_group = bind_group;
399 self.pixels = pixels;
400 }
401
402 fn run<F>(
403 mut self,
404 bundle: RunBundle,
405 load_new: F,
406 event_loop: winit::event_loop::EventLoop<()>,
407 ) where
408 F: 'static + Fn(&str) -> Option<RunBundle>,
409 {
410 let mut game = RomyGame::new(bundle);
411
412 let sound_queue_thread = self.sound_queue.clone();
413 let sample_rate_thread = self.sample_rate.clone();
414
415 std::thread::spawn(move || {
416 let sound_queue = sound_queue_thread;
417 let sample_rate = sample_rate_thread;
418
419 let host = cpal::default_host();
420 let device = host
421 .default_output_device()
422 .expect("failed to find a default output device");
423
424 let mut format = device.default_output_format().unwrap();
425 {
426 let mut sample_rate = sample_rate.write().unwrap();
427 *sample_rate = format.sample_rate.0;
428 }
429
430 let audio_event_loop = host.event_loop();
431 let stream_id = audio_event_loop
432 .build_output_stream(&device, &format)
433 .unwrap();
434 audio_event_loop.play_stream(stream_id).unwrap();
435
436 audio_event_loop.run(|id, result| {
437 let data = match result {
438 Ok(data) => data,
439 Err(err) => {
440 eprintln!("an error occurred on stream {:?}: {}", id, err);
441 return;
442 }
443 };
444
445 match data {
446 cpal::StreamData::Output {
447 buffer: cpal::UnknownTypeOutputBuffer::U16(mut buffer),
448 } => {
449 let mut sound_queue = sound_queue.write().unwrap();
450 for sample in buffer.chunks_mut(format.channels as usize) {
451 let s = sound_queue.pop_front();
452 for out in sample.iter_mut() {
453 *out = match s {
454 Some(s) => {
455 let s = s / 2.0 + 0.5;
456 (s * std::u16::MAX as f32) as u16
457 }
458 None => 0,
459 };
460 }
461 }
462 }
463 cpal::StreamData::Output {
464 buffer: cpal::UnknownTypeOutputBuffer::I16(mut buffer),
465 } => {
466 let mut sound_queue = sound_queue.write().unwrap();
467 for sample in buffer.chunks_mut(format.channels as usize) {
468 let s = sound_queue.pop_front();
469 for out in sample.iter_mut() {
470 *out = match s {
471 Some(s) => {
472 if s < 0.0 {
473 (s * std::i16::MIN as f32) as i16
474 } else {
475 (s * std::i16::MAX as f32) as i16
476 }
477 }
478 None => 0,
479 };
480 }
481 }
482 }
483 cpal::StreamData::Output {
484 buffer: cpal::UnknownTypeOutputBuffer::F32(mut buffer),
485 } => {
486 let mut sound_queue = sound_queue.write().unwrap();
487 for sample in buffer.chunks_mut(format.channels as usize) {
488 let s = sound_queue.pop_front();
489 for out in sample.iter_mut() {
490 *out = match s {
491 Some(s) => s,
492 None => 0.0,
493 };
494 }
495 }
496 }
497 _ => (),
498 }
499 });
500 });
501
502 let mut keyboard = Keyboard::default();
503
504 event_loop.run(move |event, _, control_flow| {
505 *control_flow = ControlFlow::Poll;
506
507 match event {
508 Event::MainEventsCleared => {
509 self.window.request_redraw();
510 }
511 Event::DeviceEvent {
512 event:
513 DeviceEvent::Key(
514 KeyboardInput {
515 state: ElementState::Pressed,
516 virtual_keycode: Some(code),
517 ..
518 },
519 ..,
520 ),
521 ..
522 } => {
523 let code = convert_virtual_key_code(code);
524 if let Some(code) = code {
525 keyboard.key_down(code);
526 }
527 }
528 Event::DeviceEvent {
529 event:
530 DeviceEvent::Key(
531 KeyboardInput {
532 state: ElementState::Released,
533 virtual_keycode: Some(code),
534 ..
535 },
536 ..,
537 ),
538 ..
539 } => {
540 let code = convert_virtual_key_code(code);
541 if let Some(code) = code {
542 keyboard.key_up(code);
543 }
544 }
545 Event::WindowEvent {
546 event:
547 WindowEvent::KeyboardInput {
548 input:
549 KeyboardInput {
550 state: ElementState::Pressed,
551 virtual_keycode: Some(code),
552 ..
553 },
554 ..
555 },
556 ..
557 } => {
558 let code = convert_virtual_key_code(code);
559 if let Some(code) = code {
560 keyboard.key_down(code);
561 }
562 }
563 Event::WindowEvent {
564 event:
565 WindowEvent::KeyboardInput {
566 input:
567 KeyboardInput {
568 state: ElementState::Released,
569 virtual_keycode: Some(code),
570 ..
571 },
572 ..
573 },
574 ..
575 } => {
576 let code = convert_virtual_key_code(code);
577 if let Some(code) = code {
578 keyboard.key_up(code);
579 }
580 }
581 Event::RedrawRequested(_) => {
582 let size = self.window.inner_size();
583
584 let (width, height): (u32, u32) = size.into();
585
586 let app = &mut game.bundle.game;
587
588 let time_span = Instant::now().duration_since(game.start_time);
589 let step_offset = (time_span.as_micros() % game.step.as_micros()) as f32
590 / game.step.as_micros() as f32;
591
592 let render = app.draw(&DrawArguments::new(
593 width as i32,
594 height as i32,
595 step_offset,
596 ));
597 if self.buffer_width != render.width() as u32
598 || self.buffer_height != render.height() as u32
599 {
600 self.buffer_width = render.width() as u32;
601 self.buffer_height = render.height() as u32;
602 self.recreate_pipeline();
603 }
604
605 let source = render.pixels8();
606 self.pixels.clone_from_slice(&source[..]);
607
608 let swap_chain_texture = self.swap_chain.get_next_texture();
609
610 let mut encoder = self
611 .device
612 .create_command_encoder(&wgpu::CommandEncoderDescriptor { todo: 0 });
613
614 let buffer = self
615 .device
616 .create_buffer_mapped(self.pixels.len(), wgpu::BufferUsage::COPY_SRC)
617 .fill_from_slice(&self.pixels);
618
619 encoder.copy_buffer_to_texture(
620 wgpu::BufferCopyView {
621 buffer: &buffer,
622 offset: 0,
623 row_pitch: self.texture_extent.width * 4,
624 image_height: self.texture_extent.height,
625 },
626 wgpu::TextureCopyView {
627 texture: &self.texture,
628 mip_level: 0,
629 array_layer: 0,
630 origin: wgpu::Origin3d {
631 x: 0.0,
632 y: 0.0,
633 z: 0.0,
634 },
635 },
636 self.texture_extent,
637 );
638
639 {
640 let mut render_pass =
641 encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
642 color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
643 attachment: &swap_chain_texture.view,
644 resolve_target: None,
645 load_op: wgpu::LoadOp::Clear,
646 store_op: wgpu::StoreOp::Store,
647 clear_color: wgpu::Color::BLACK,
648 }],
649 depth_stencil_attachment: None,
650 });
651 render_pass.set_pipeline(&self.render_pipeline);
652 render_pass.set_bind_group(0, &self.bind_group, &[]);
653 render_pass.draw(0..6, 0..1);
654 }
655
656 self.queue.submit(&[encoder.finish()]);
657 }
658 Event::WindowEvent {
659 event: WindowEvent::DroppedFile(file_path),
660 ..
661 } => {
662 if let Some(bundle) = load_new(file_path.to_str().unwrap()) {
663 self.window.set_title(&format!(
664 "{}: {}",
665 self.title_prefix,
666 bundle.info.name()
667 ));
668 game = RomyGame::new(bundle);
669 }
670 }
671 Event::WindowEvent {
672 event: WindowEvent::Resized(_),
673 ..
674 } => {
675 self.recreate_pipeline();
676 }
677 Event::WindowEvent {
678 event: WindowEvent::CloseRequested,
679 ..
680 } => *control_flow = ControlFlow::Exit,
681 _ => (),
682 }
683
684 let mut input = InputCollection::new();
685 input.add_input(InputDevice::Keyboard(keyboard.clone()));
686
687 let time_span = Instant::now().duration_since(game.start_time);
688 let expected_steps = time_span.as_micros() / game.step.as_micros();
689 let app = &mut game.bundle.game;
690 let info = &game.bundle.info;
691
692 if game.steps < expected_steps {
693 app.step(&StepArguments::new(input.get_input_arguments(&info)));
694
695 let audio = app.render_audio(&RenderAudioArguments {});
696
697 if audio.sample_rate() as u32 % game.bundle.info.steps_per_second() != 0 {
698 panic!("Sample rate not divisible by steps per second");
699 }
700
701 let expected_sample_count =
702 audio.sample_rate() as u32 / game.bundle.info.steps_per_second();
703 if expected_sample_count != audio.samples().len() as u32 {
704 panic!("Incorrect number of samples supplied");
705 }
706 let sample_rate = { *self.sample_rate.read().unwrap() };
707 let sample_count = sample_rate / game.bundle.info.steps_per_second();
708
709 let mut sound_queue = self.sound_queue.write().unwrap();
710 let ratio = expected_sample_count as f32 / sample_count as f32;
711
712 for t in 0..sample_count {
713 let t = t as f32 * ratio;
714 let low = audio.samples()[t.floor() as usize];
715 let high = audio.samples()
716 [min(t.ceil() as usize, (expected_sample_count - 1) as usize)];
717 sound_queue.push_back((low + high) / 2.0);
718 }
719
720 game.steps += 1;
721 }
722 });
723 }
724}
725
726pub fn run<F>(bundle: RunBundle, load_new: F)
734where
735 F: 'static + Fn(&str) -> Option<RunBundle>,
736{
737 let (rusty, event_loop) = Rusty::new();
738 rusty.run(bundle, load_new, event_loop);
739}