Wasm improvments

Former-commit-id: f51fc18327f6adb0011ff2aff2787d513fb6aa37
This commit is contained in:
Michel Heily 2020-04-15 15:32:48 +03:00
parent 0d8a5467e0
commit 90032373a8
10 changed files with 252 additions and 123 deletions

2
Cargo.lock generated
View file

@ -1046,6 +1046,7 @@ dependencies = [
"nom 5.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"num 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"ringbuf 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rustyline 6.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1060,7 +1061,6 @@ dependencies = [
"env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"jni 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"ringbuf 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rustboyadvance-core 0.1.0",
]

View file

@ -13,7 +13,6 @@ crate-type = ["staticlib", "cdylib"]
rustboyadvance-core = {path = "../../rustboyadvance-core/"}
jni = { version = "0.16", default-features = false }
log = {version = "0.4.8", features = ["release_max_level_info", "max_level_debug"]}
ringbuf = "0.2.1"
[target.'cfg(target_os="android")'.dependencies]
android_log = "0.1.3"

View file

@ -20,24 +20,9 @@ use android_log;
#[cfg(not(target_os = "android"))]
use env_logger;
use ringbuf::{Consumer, Producer, RingBuffer};
use rustboyadvance_core::prelude::*;
use rustboyadvance_core::StereoSample;
struct AudioRingBuffer {
pub prod: Producer<i16>,
pub cons: Consumer<i16>,
}
impl AudioRingBuffer {
fn new() -> AudioRingBuffer {
let rb = RingBuffer::new(4096 * 2);
let (prod, cons) = rb.split();
AudioRingBuffer { prod, cons }
}
}
use rustboyadvance_core::util::audio::AudioRingBuffer;
struct Hardware {
jvm: JavaVM,

View file

@ -45,6 +45,7 @@ features = [
'WebGlProgram',
'WebGlShader',
'Window',
'AudioContext',
]

View file

@ -7,93 +7,120 @@
<title>RustBoyAdvance</title>
<style>
body {
background-color: #675ea7;
font-family: "Courier New", Courier, monospace;
background-color: #2c2946;
font-family: Source Code, Roboto, Monospace;
margin: 0;
color: azure;
}
#logo {
header {
padding: 1em;
padding-top: 6em;
background-color: #13121d;
background-image: url("icon.svg");
background-repeat: no-repeat;
background-position: center;
background-size: 400px;
display: flex;
text-align: center;
align-items: center;
justify-content: center;
box-shadow: 0.1px 2px 4px rgba(0, 0, 0, 0.7);
}
header h1 {
color: azure;
padding: .1em;
font-size: medium;
}
#menu {
background-color: #423c6c;
display: flexbox;
box-shadow: 0.1px 2px 4px rgba(0, 0, 0, 0.7);
top: 0.2em;
left: 0.2em;
padding: .1em;
position: absolute;
}
#menu .fileInput {
background-color: #675ea7;
margin: .4em;
#menu ul {
list-style: none;
}
#menu button {
background-color: #736ab1;
box-shadow: 1px 1px 2px rgba(255, 18, 148, 0.2);
font-size: .8em;
border-radius: .15em;
margin: .4em;
#menu li {
margin: 0.2em;
}
#canvas-container {
box-shadow: 0.1px 2px 4px rgba(0, 0, 0, 0.7);
display: inline-block;
text-align: center;
position:fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 70%;
height: 70%;
align-items: center;
#playarea {
justify-content: center;
background-color: #423c6c;
margin: auto;
display: flexbox;
}
#canvas-container.hover {
#playarea.hover {
border: rgba(9, 250, 0, 0.671);
border-style: dashed;
border-width: 0.1em;
}
#game-options {
display: block;
}
#game-options > * {
width: 100%;
margin: 0.2em;
}
#screen {
text-align: center;
margin: auto;
width: 70%;
display: block;
max-width: 960px;
width: 80%;
margin-left: auto;
margin-right: auto;
background-color: black;
image-rendering: optimizeSpeed;
image-rendering: -moz-crisp-edges;
image-rendering: -webkit-crisp-edges;
image-rendering: pixelated;
image-rendering: crisp-edges;
box-shadow: 0.1px 2px 4px rgba(0, 0, 0, 0.7);
padding: 0.2em;
}
.hidden {
display: none,
#screen.hover {
border: rgba(9, 250, 0, 0.671);
border-style: dashed;
border-width: 0.1em;
}
</style>
</head>
<body>
<noscript>This page contains webassembly and javascript content, please enable javascript in your browser.</noscript>
<div id="menu">
<div class="fileInput">
<header>
<div id="logo">
<h1>RustBoyAdvance</h1>
</div>
</header>
<section id="menu">
<ul>
<li class="fileInput">
<label> BIOS </label>
<input type="file" id="bios-file-input" />
</div>
<button id="reloadBios" class="hidden">Reload Bios</button>
<div class="fileInput">
</li>
<li><button id="reloadBios" class="hidden">Reload Bios</button></li>
<li class="fileInput">
<label> ROM </label>
<input type="file" id="rom-file-input" />
</div>
<button id="startEmulator">Start</button>
<button id="fps-test">FPS Test</button>
</li>
<li><button id="startEmulator">Start</button></li>
<li><button id="fps-test">FPS Test</button></li>
<li>
<label>Skip Bios</label>
<input type="checkbox" id="skipBios">
</div>
<div id="canvas-container">
<pre id="fps"></pre>
</li>
<pre> You can also drag rom files directly into the screen </pre>
</ul>
</section>
<section id="playarea">
<pre> Built with WASM</pre>
<canvas id="screen" width="240px" , height="160px"></canvas>
<img id="logo" width="240px" src="icon.svg"></img>
</div>
<section id="game-options">
<span id="fps">FPS</span>
<label>
<label>Max FPS</label>
<input type="checkbox" id="maxFps">
</label>
</section>
</section>
<footer></footer>
<script src="./bootstrap.js"></script>
</body>

View file

@ -1,5 +1,6 @@
import * as wasm from "rustboyadvance-wasm";
var fps_text = document.getElementById('fps');
var canvas = document.getElementById("screen");
var ctx = canvas.getContext('2d');
var intervalId = 0;
@ -37,6 +38,68 @@ function ensureFilesLoaded() {
return true;
}
const convertAudioBuffer = buffer => {
let length = buffer.length;
const floatArray = new Float32Array(length);
for (let i = 0; i < length; i++) {
floatArray[i] = (buffer[i] - 32767) / 32767;
}
return floatArray;
}
var fpsCounter = (function() {
var lastLoop = (new Date).getMilliseconds();
var count = 0;
var fps = 0;
return function() {
var currentLoop = (new Date).getMilliseconds();
if (lastLoop > currentLoop) {
fps = count;
count = 0;
} else {
count += 1;
}
lastLoop = currentLoop;
return fps;
}
}());
// Create our audio context
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
console.log("audio context " + audioContext);
const playAudio = emulator => {
let audioData = emulator.collect_audio_samples();
let frameCount = audioData.length / 2;
const audioBuffer = audioContext.createBuffer(
2,
frameCount,
audioContext.sampleRate
);
for (let channel = 0; channel < 2; channel++) {
let nowBuffering = audioBuffer.getChannelData(channel);
for (let i = 0; i < frameCount; i++) {
// audio data frames are interleaved
nowBuffering[i] = audioData[i*2 + channel];
}
}
const audioSource = audioContext.createBufferSource();
audioSource.buffer = audioBuffer;
audioSource.connect(audioContext.destination);
audioSource.start();
}
const emulatorLoop = function() {
emulator.run_frame(ctx);
fps_text.innerHTML = fpsCounter();
playAudio(emulator);
}
function startEmulator() {
if (!ensureFilesLoaded()) {
return;
@ -55,29 +118,7 @@ function startEmulator() {
emulator.skip_bios();
}
var fpsCounter = (function() {
var lastLoop = (new Date).getMilliseconds();
var count = 0;
var fps = 0;
return function() {
var currentLoop = (new Date).getMilliseconds();
if (lastLoop > currentLoop) {
fps = count;
count = 0;
} else {
count += 1;
}
lastLoop = currentLoop;
return fps;
}
}());
let fps_text = document.getElementById('fps')
intervalId = setInterval(function() {
emulator.run_frame(ctx);
fps_text.innerHTML = fpsCounter();
}, 16);
intervalId = setInterval(emulatorLoop, 16);
}
const biosCached = localStorage.getItem("biosCached");
@ -129,7 +170,7 @@ function loadRom(romFile) {
return promise;
};
let dropArea = document.getElementById('canvas-container');
let dropArea = document.getElementById('screen');
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropArea.addEventListener(eventName,
@ -169,22 +210,26 @@ document.getElementById("startEmulator").addEventListener('click', e => {
}
}, false);
['keydown', 'keyup'].forEach(eventName => {
window.addEventListener(eventName,
e => {
// prevent default events
e.preventDefault();
e.stopPropagation();
}, false)
});
document.getElementById("maxFps").addEventListener('change', e => {
if (intervalId != 0) {
let checked = e.target.checked;
clearInterval(intervalId);
if (checked) {
intervalId = setInterval(emulatorLoop, 0);
} else {
intervalId = setInterval(emulatorLoop, 16);
}
window.addEventListener("keydown", e => {
}
})
document.addEventListener("keydown", e => {
if (null != emulator) {
emulator.key_down(e.key)
}
}, false);
window.addEventListener("keyup", e => {
document.addEventListener("keyup", e => {
if (null != emulator) {
emulator.key_up(e.key)
}

View file

@ -3,10 +3,15 @@ use std::rc::Rc;
use wasm_bindgen::prelude::*;
use wasm_bindgen::Clamped;
use js_sys::Float32Array;
use web_sys::CanvasRenderingContext2d;
use web_sys::AudioContext;
use rustboyadvance_core::core::keypad as gba_keypad;
use rustboyadvance_core::prelude::*;
use rustboyadvance_core::util::audio::AudioRingBuffer;
use bit::BitIndex;
@ -19,6 +24,29 @@ pub struct Emulator {
struct Interface {
frame: Vec<u8>,
keyinput: u16,
sample_rate: i32,
audio_ctx: AudioContext,
audio_ring_buffer: AudioRingBuffer,
}
impl Drop for Interface {
fn drop(&mut self) {
let _ = self.audio_ctx.clone();
}
}
impl Interface {
fn new(audio_ctx: AudioContext) -> Result<Interface, JsValue> {
Ok(
Interface {
frame: vec![0; 240 * 160 * 4],
keyinput: gba_keypad::KEYINPUT_ALL_RELEASED,
sample_rate: audio_ctx.sample_rate() as i32,
audio_ctx: audio_ctx,
audio_ring_buffer: AudioRingBuffer::new(),
}
)
}
}
impl VideoInterface for Interface {
@ -34,7 +62,21 @@ impl VideoInterface for Interface {
}
}
impl AudioInterface for Interface {}
fn convert_sample(s: i16) -> f32 {
((s as f32) / 32767_f32)
}
impl AudioInterface for Interface {
fn get_sample_rate(&self) -> i32 {
self.sample_rate
}
fn push_sample(&mut self, samples: StereoSample<i16>) {
self.audio_ring_buffer.prod.push(samples.0).unwrap();
self.audio_ring_buffer.prod.push(samples.1).unwrap();
}
}
impl InputInterface for Interface {
fn poll(&mut self) -> u16 {
@ -45,18 +87,16 @@ impl InputInterface for Interface {
#[wasm_bindgen]
impl Emulator {
#[wasm_bindgen(constructor)]
pub fn new(bios: &[u8], rom: &[u8]) -> Emulator {
pub fn new(bios: &[u8], rom: &[u8]) -> Result<Emulator, JsValue> {
let audio_ctx = web_sys::AudioContext::new()?;
let interface = Rc::new(RefCell::new(Interface::new(audio_ctx)?));
let gamepak = GamepakBuilder::new()
.take_buffer(rom.to_vec().into_boxed_slice())
.without_backup_to_file()
.build()
.unwrap();
let interface = Rc::new(RefCell::new(Interface {
frame: vec![0; 240 * 160 * 4],
keyinput: gba_keypad::KEYINPUT_ALL_RELEASED,
}));
let gba = GameBoyAdvance::new(
bios.to_vec().into_boxed_slice(),
gamepak,
@ -65,7 +105,7 @@ impl Emulator {
interface.clone(),
);
Emulator { gba, interface }
Ok( Emulator { gba, interface } )
}
pub fn skip_bios(&mut self) {
@ -129,4 +169,16 @@ impl Emulator {
}
}
}
pub fn collect_audio_samples(&self) -> Result<Float32Array, JsValue> {
let mut interface = self.interface.borrow_mut();
let consumer = &mut interface.audio_ring_buffer.cons;
let mut samples = Vec::with_capacity(consumer.len());
while let Some(sample) = consumer.pop() {
samples.push(convert_sample(sample));
}
Ok(Float32Array::from(samples.as_slice()))
}
}

View file

@ -29,6 +29,7 @@ arrayvec = "0.5.1"
rustyline = {version = "6.0.0", optional = true}
nom = {version = "5.0.0", optional = true}
gdbstub = { version = "0.1.2", optional = true, features = ["std"] }
ringbuf = "0.2.1"
[target.'cfg(target_arch="wasm32")'.dependencies]
instant = { version = "0.1.2", features = [ "wasm-bindgen" ] }

View file

@ -65,5 +65,5 @@ pub mod prelude {
#[cfg(feature = "debugger")]
pub use super::debugger::Debugger;
pub use super::util::{read_bin_file, write_bin_file};
pub use super::{AudioInterface, InputInterface, VideoInterface};
pub use super::{AudioInterface, StereoSample, InputInterface, VideoInterface};
}

View file

@ -127,3 +127,22 @@ macro_rules! host_breakpoint {
}
};
}
pub mod audio {
use ringbuf::{Consumer, Producer, RingBuffer};
pub struct AudioRingBuffer {
pub prod: Producer<i16>,
pub cons: Consumer<i16>,
}
impl AudioRingBuffer {
pub fn new() -> AudioRingBuffer {
let rb = RingBuffer::new(4096 * 2);
let (prod, cons) = rb.split();
AudioRingBuffer { prod, cons }
}
}
}