added WebAudio backend for rg3d-sound, moved some dependencies to rg3d-core

- #85
This commit is contained in:
Dmitry
2021-04-30 17:32:06 +03:00
parent 120bac885d
commit cf726033af
44 changed files with 312 additions and 128 deletions
-2
View File
@@ -28,12 +28,10 @@ lexical = "5.2.0"
inflate = "0.4.5"
serde = { version = "^1.0.0", features = ["derive"], optional = true }
lazy_static = "1.4.0"
futures = {version = "0.3.13", features = ["thread-pool"] }
ddsfile = "0.4.0"
rapier3d = {version = "0.7" }
rayon = "1.5.0"
tbc = "0.3.0"
instant = {version = "0.1.9", features = ["wasm-bindgen"] }
# Temporary, until https://github.com/grovesNL/glow/pull/157 is merged and new glow (0.10?) published on crates.io
glow = { git = "https://github.com/mrDIMAS/glow", branch = "main" }
+1 -1
View File
@@ -121,7 +121,7 @@ modern games at a decent frame rate.
- Windows - **full support**
- Linux - **full support**
- macOS - **full support**
- WebAssembly - **partial support**: any help is appreciated ([tracking issue](https://github.com/mrDIMAS/rg3d/issues/85)).
- WebAssembly - **full support**
## Compiler version
+1
View File
@@ -34,6 +34,7 @@ use std::{
use crate::shared::create_camera;
use rg3d::core::algebra::{UnitQuaternion, Vector2, Vector3};
use rg3d::core::futures;
// Create our own engine type aliases. These specializations are needed
// because engine provides a way to extend UI with custom nodes and messages.
+1 -1
View File
@@ -8,8 +8,8 @@ extern crate rg3d;
pub mod shared;
use rg3d::core::futures::executor::block_on;
use rg3d::engine::resource_manager::ResourceManager;
use rg3d::futures::executor::block_on;
use rg3d::resource::texture::TextureMagnificationFilter;
use rg3d::tbc::color::Rgb8;
use rg3d::{
+2 -2
View File
@@ -83,7 +83,7 @@ async fn create_scene(resource_manager: ResourceManager) -> GameScene {
// create instance of it - much more efficient is to load it once and then make copies
// of it. In case of models it is very efficient because single vertex and index buffer
// can be used for all models instances, so memory footprint on GPU will be lower.
let (model_resource, walk_animation_resource) = rg3d::futures::join!(
let (model_resource, walk_animation_resource) = rg3d::core::futures::join!(
resource_manager.request_model("examples/data/mutant.FBX"),
resource_manager.request_model("examples/data/walk.fbx")
);
@@ -202,7 +202,7 @@ fn main() {
scene,
camera,
animations,
} = rg3d::futures::executor::block_on(create_scene(engine.resource_manager.clone()));
} = rg3d::core::futures::executor::block_on(create_scene(engine.resource_manager.clone()));
// Add scene to engine - engine will take ownership over scene and will return
// you a handle to scene which can be used later on to borrow it and do some
+1
View File
@@ -10,6 +10,7 @@ extern crate rg3d;
pub mod shared;
use crate::shared::create_camera;
use rg3d::core::futures;
use rg3d::{
core::{
algebra::{UnitQuaternion, Vector2, Vector3},
+1 -1
View File
@@ -147,7 +147,7 @@ fn main() {
scene,
model_handle,
camera,
} = rg3d::futures::executor::block_on(create_scene(engine.resource_manager.clone()));
} = rg3d::core::futures::executor::block_on(create_scene(engine.resource_manager.clone()));
// Add scene to engine - engine will take ownership over scene and will return
// you a handle to scene which can be used later on to borrow it and do some
+1 -1
View File
@@ -150,7 +150,7 @@ fn main() {
agent,
cursor,
camera,
} = rg3d::futures::executor::block_on(create_scene(engine.resource_manager.clone()));
} = rg3d::core::futures::executor::block_on(create_scene(engine.resource_manager.clone()));
// Add scene to engine - engine will take ownership over scene and will return
// you a handle to scene which can be used later on to borrow it and do some
+3 -3
View File
@@ -123,10 +123,10 @@ fn save(game: &mut Game) {
visitor.save_binary(Path::new(SAVE_FILE)).unwrap();
}
fn load(game: &mut Game) {
async fn load(game: &mut Game) {
if Path::new(SAVE_FILE).exists() {
// Loading a game is even simpler - just 2 lines.
let mut visitor = Visitor::load_binary(SAVE_FILE).unwrap();
let mut visitor = Visitor::load_binary(SAVE_FILE).await.unwrap();
game.visit("Game", &mut visitor).unwrap();
}
}
@@ -306,7 +306,7 @@ fn main() {
// Save/load bound to classic F5 and F9 keys.
match code {
VirtualKeyCode::F5 => save(&mut game),
VirtualKeyCode::F9 => load(&mut game),
VirtualKeyCode::F9 => rg3d::core::futures::executor::block_on(load(&mut game)),
_ => ()
};
}
+1 -1
View File
@@ -103,7 +103,7 @@ fn main() {
// Create test scene.
let GameScene { scene, pivot } =
rg3d::futures::executor::block_on(create_scene(engine.resource_manager.clone()));
rg3d::core::futures::executor::block_on(create_scene(engine.resource_manager.clone()));
// Add scene to engine - engine will take ownership over scene and will return
// you a handle to scene which can be used later on to borrow it and do some
+2 -2
View File
@@ -57,7 +57,7 @@ pub async fn create_camera(
graph: &mut Graph,
) -> Handle<Node> {
// Load skybox textures in parallel.
let (front, back, left, right, top, bottom) = rg3d::futures::join!(
let (front, back, left, right, top, bottom) = rg3d::core::futures::join!(
resource_manager
.request_texture("examples/data/skyboxes/DarkStormy/DarkStormyFront2048.png"),
resource_manager
@@ -732,7 +732,7 @@ pub fn create_scene_async(resource_manager: ResourceManager) -> Arc<Mutex<SceneL
// Spawn separate thread which will create scene by loading various assets.
std::thread::spawn(move || {
futures::executor::block_on(async move {
rg3d::core::futures::executor::block_on(async move {
let mut scene = Scene::new();
// Set ambient light.
+2 -2
View File
@@ -83,7 +83,7 @@ async fn create_scene(resource_manager: ResourceManager) -> GameScene {
// create instance of it - much more efficient is to load it once and then make copies
// of it. In case of models it is very efficient because single vertex and index buffer
// can be used for all models instances, so memory footprint on GPU will be lower.
let (model_resource, walk_animation_resource) = rg3d::futures::join!(
let (model_resource, walk_animation_resource) = rg3d::core::futures::join!(
resource_manager.request_model("examples/data/mutant.FBX"),
resource_manager.request_model("examples/data/walk.fbx")
);
@@ -165,7 +165,7 @@ fn main() {
scene,
model_handle,
walk_animation,
} = rg3d::futures::executor::block_on(create_scene(engine.resource_manager.clone()));
} = rg3d::core::futures::executor::block_on(create_scene(engine.resource_manager.clone()));
// Add scene to engine - engine will take ownership over scene and will return
// you a handle to scene which can be used later on to borrow it and do some
+4 -4
View File
@@ -52,13 +52,13 @@ fn main() {
];
// Request foot step sound buffer from resources directory.
let footstep_buffers = rg3d::futures::executor::block_on(rg3d::futures::future::join_all(
footstep_paths.iter().map(|&path| {
let footstep_buffers = rg3d::core::futures::executor::block_on(
rg3d::core::futures::future::join_all(footstep_paths.iter().map(|&path| {
game.engine
.resource_manager
.request_sound_buffer(path, false)
}),
))
})),
)
.into_iter()
.map(|r| r.unwrap())
.collect::<Vec<_>>();
+1 -1
View File
@@ -358,7 +358,7 @@ fn main() {
scene,
model_handle,
walk_animation,
} = rg3d::futures::executor::block_on(create_scene(engine.resource_manager.clone()));
} = rg3d::core::futures::executor::block_on(create_scene(engine.resource_manager.clone()));
// Add scene to engine - engine will take ownership over scene and will return
// you a handle to scene which can be used later on to borrow it and do some
+19 -3
View File
@@ -39,6 +39,9 @@ use rg3d::animation::Animation;
use rg3d::resource::texture::TextureWrapMode;
use rg3d::scene::camera::SkyBox;
use rg3d::scene::graph::Graph;
use rg3d::sound::buffer::SoundBuffer;
use rg3d::sound::source::generic::GenericSourceBuilder;
use rg3d::sound::source::Status;
use std::sync::Mutex;
use std::{
panic,
@@ -135,7 +138,7 @@ pub async fn create_camera(
graph: &mut Graph,
) -> Handle<Node> {
// Load skybox textures in parallel.
let (front, back, left, right, top, bottom) = rg3d::futures::join!(
let (front, back, left, right, top, bottom) = rg3d::core::futures::join!(
resource_manager.request_texture("/data/textures/DarkStormyFront2048.png"),
resource_manager.request_texture("/data/textures/DarkStormyBack2048.png"),
resource_manager.request_texture("/data/textures/DarkStormyLeft2048.png"),
@@ -177,6 +180,19 @@ pub async fn create_camera(
async fn create_scene(resource_manager: ResourceManager, context: Arc<Mutex<SceneContext>>) {
let mut scene = Scene::new();
let music = GenericSourceBuilder::new(
resource_manager
.request_sound_buffer("/data/music.ogg", false)
.await
.unwrap()
.into(),
)
.with_status(Status::Playing)
.build_source()
.unwrap();
scene.sound_context.state().add_source(music);
scene.ambient_lighting_color = Color::opaque(200, 200, 200);
create_camera(
@@ -196,7 +212,7 @@ async fn create_scene(resource_manager: ResourceManager, context: Arc<Mutex<Scen
.with_radius(20.0)
.build(&mut scene.graph);
let (model_resource, walk_animation_resource) = rg3d::futures::join!(
let (model_resource, walk_animation_resource) = rg3d::core::futures::join!(
resource_manager.request_model("/data/mutant.FBX"),
resource_manager.request_model("/data/walk.fbx")
);
@@ -287,7 +303,7 @@ pub fn main() {
// Create simple user interface that will show some useful info.
let debug_text = create_ui(&mut engine.user_interface.build_ctx());
let clock = rg3d::instant::Instant::now();
let clock = rg3d::core::instant::Instant::now();
let fixed_timestep = 1.0 / 60.0;
let mut elapsed_time = 0.0;
Binary file not shown.
+3 -2
View File
@@ -17,14 +17,15 @@ memoffset = "0.6.1"
lazy_static = "1.4.0"
nalgebra = "0.25.3"
arrayvec = "0.5.2"
futures = {version = "0.3.13", features = ["thread-pool"] }
uuid = { version = "0.8.2", features = ["v4","wasm-bindgen"] }
instant = {version = "0.1.9", features = ["wasm-bindgen"] }
[target.'cfg(target_arch = "wasm32")'.dependencies]
web-sys = { version = "0.3.50", features = ["Request", "Window", "Response"] }
web-sys = { version = "0.3.50", features = ["Request", "Window", "Response", "AudioContext", "AudioBuffer", "AudioContextOptions", "AudioNode", "AudioBufferSourceNode", "AudioDestinationNode"] }
wasm-bindgen = "0.2.73"
wasm-bindgen-futures = "0.4.23"
js-sys = "0.3.50"
[features]
enable_profiler = []
+2
View File
@@ -28,6 +28,8 @@ pub mod profiler;
pub mod quadtree;
pub mod rectpack;
pub mod visitor;
pub use futures;
pub use instant;
#[cfg(target_arch = "wasm32")]
pub use js_sys;
+1 -1
View File
@@ -27,4 +27,4 @@ coreaudio-sys = { version = "0.2.5" }
core-foundation-sys = { version = "0.8.2" }
[features]
enable_profiler = ["rg3d-core/enable_profiler"]
enable_profiler = ["rg3d-core/enable_profiler"]
+1
View File
@@ -23,6 +23,7 @@ Examples can be found in `./examples`. Make sure you run examples with `--releas
- Windows (DirectSound)
- Linux (alsa)
- macOS (CoreAudio)
- WebAssembly (WebAudio)
## HRTF
+9 -6
View File
@@ -1,5 +1,6 @@
use rg3d_core::algebra::Point3;
use rg3d_sound::engine::SoundEngine;
use rg3d_sound::futures::executor::block_on;
use rg3d_sound::{
algebra::{UnitQuaternion, Vector3},
buffer::{DataSource, SoundBuffer},
@@ -31,9 +32,10 @@ fn main() {
.set_renderer(Renderer::HrtfRenderer(HrtfRenderer::new(hrir_sphere)));
// Create some sounds.
let sound_buffer =
SoundBuffer::new_generic(DataSource::from_file("examples/data/door_open.wav").unwrap())
.unwrap();
let sound_buffer = SoundBuffer::new_generic(
block_on(DataSource::from_file("examples/data/door_open.wav")).unwrap(),
)
.unwrap();
let source = SpatialSourceBuilder::new(
GenericSourceBuilder::new(sound_buffer)
.with_status(Status::Playing)
@@ -43,9 +45,10 @@ fn main() {
.build_source();
context.state().add_source(source);
let sound_buffer =
SoundBuffer::new_generic(DataSource::from_file("examples/data/helicopter.wav").unwrap())
.unwrap();
let sound_buffer = SoundBuffer::new_generic(
block_on(DataSource::from_file("examples/data/helicopter.wav")).unwrap(),
)
.unwrap();
let source = SpatialSourceBuilder::new(
GenericSourceBuilder::new(sound_buffer)
.with_status(Status::Playing)
+5 -2
View File
@@ -1,5 +1,6 @@
use rg3d_core::algebra::Point3;
use rg3d_sound::engine::SoundEngine;
use rg3d_sound::futures::executor::block_on;
use rg3d_sound::{
algebra::{UnitQuaternion, Vector3},
buffer::DataSource,
@@ -22,8 +23,10 @@ fn main() {
engine.lock().unwrap().add_context(context.clone());
// Load sound buffer.
let drop_buffer =
SoundBuffer::new_generic(DataSource::from_file("examples/data/drop.wav").unwrap()).unwrap();
let drop_buffer = SoundBuffer::new_generic(
block_on(DataSource::from_file("examples/data/drop.wav")).unwrap(),
)
.unwrap();
// Create spatial source - spatial sources can be positioned in space.
let source = SpatialSourceBuilder::new(
+7 -3
View File
@@ -18,9 +18,13 @@ fn main() {
engine.lock().unwrap().add_context(context.clone());
// Load sound buffer.
let door_open_buffer =
SoundBuffer::new_generic(DataSource::from_file("examples/data/door_open.wav").unwrap())
.unwrap();
let door_open_buffer = SoundBuffer::new_generic(
rg3d_sound::futures::executor::block_on(DataSource::from_file(
"examples/data/door_open.wav",
))
.unwrap(),
)
.unwrap();
// Create generic source (without spatial effects) using that buffer.
let source = GenericSourceBuilder::new(door_open_buffer)
+5 -2
View File
@@ -1,3 +1,4 @@
use rg3d_core::futures::executor::block_on;
use rg3d_sound::engine::SoundEngine;
use rg3d_sound::{
algebra::{Point3, UnitQuaternion, Vector3},
@@ -21,8 +22,10 @@ fn main() {
engine.lock().unwrap().add_context(context.clone());
// Load sound buffer.
let drop_buffer =
SoundBuffer::new_generic(DataSource::from_file("examples/data/drop.wav").unwrap()).unwrap();
let drop_buffer = SoundBuffer::new_generic(
block_on(DataSource::from_file("examples/data/drop.wav")).unwrap(),
)
.unwrap();
// Create spatial source - spatial sources can be positioned in space.
let source = SpatialSourceBuilder::new(
+9 -5
View File
@@ -1,4 +1,5 @@
use rg3d_sound::engine::SoundEngine;
use rg3d_sound::futures::executor::block_on;
use rg3d_sound::{
algebra::{Point3, UnitQuaternion, Vector3},
buffer::{DataSource, SoundBuffer},
@@ -38,9 +39,10 @@ fn main() {
let reverb_handle = context.state().add_effect(Effect::Reverb(reverb));
// Create some sounds.
let sound_buffer =
SoundBuffer::new_generic(DataSource::from_file("examples/data/door_open.wav").unwrap())
.unwrap();
let sound_buffer = SoundBuffer::new_generic(
block_on(DataSource::from_file("examples/data/door_open.wav")).unwrap(),
)
.unwrap();
let source = SpatialSourceBuilder::new(
GenericSourceBuilder::new(sound_buffer)
.with_status(Status::Playing)
@@ -57,8 +59,10 @@ fn main() {
.effect_mut(reverb_handle)
.add_input(EffectInput::direct(door_sound));
let sound_buffer =
SoundBuffer::new_generic(DataSource::from_file("examples/data/drop.wav").unwrap()).unwrap();
let sound_buffer = SoundBuffer::new_generic(
block_on(DataSource::from_file("examples/data/drop.wav")).unwrap(),
)
.unwrap();
let source = SpatialSourceBuilder::new(
GenericSourceBuilder::new(sound_buffer)
.with_status(Status::Playing)
+5 -3
View File
@@ -1,4 +1,5 @@
use rg3d_sound::engine::SoundEngine;
use rg3d_sound::futures::executor::block_on;
use rg3d_sound::{
buffer::{DataSource, SoundBuffer},
context::Context,
@@ -17,9 +18,10 @@ fn main() {
engine.lock().unwrap().add_context(context.clone());
// Load sound buffer.
let waterfall_buffer =
SoundBuffer::new_streaming(DataSource::from_file("examples/data/waterfall.ogg").unwrap())
.unwrap();
let waterfall_buffer = SoundBuffer::new_streaming(
block_on(DataSource::from_file("examples/data/waterfall.ogg")).unwrap(),
)
.unwrap();
// Create flat source (without spatial effects) using that buffer.
let source = GenericSourceBuilder::new(waterfall_buffer)
+3 -3
View File
@@ -24,7 +24,7 @@ use rg3d_core::{
use std::sync::MutexGuard;
use std::{
sync::{Arc, Mutex},
time::{self, Duration},
time::Duration,
};
/// Sample rate for output device.
@@ -210,7 +210,7 @@ impl State {
}
pub(crate) fn render(&mut self, master_gain: f32, buf: &mut [(f32, f32)]) {
let last_time = time::Instant::now();
let last_time = rg3d_core::instant::Instant::now();
for i in 0..self.sources.get_capacity() {
if let Some(source) = self.sources.at(i) {
@@ -250,7 +250,7 @@ impl State {
*right *= global_gain;
}
self.render_duration = time::Instant::now() - last_time;
self.render_duration = rg3d_core::instant::Instant::now() - last_time;
}
}
+8 -5
View File
@@ -40,7 +40,10 @@ pub fn check(err_code: c_int) -> Result<(), SoundError> {
}
impl AlsaSoundDevice {
pub fn new(buffer_len_bytes: u32, callback: Box<FeedCallback>) -> Result<Self, SoundError> {
pub fn new<F: FnMut(&mut [(f32, f32)]) + Send + 'static>(
buffer_len_bytes: u32,
callback: F,
) -> Result<Self, SoundError> {
unsafe {
let name = CString::new("default").unwrap();
// 16-bit stereo is 4 bytes, so frame count is bufferHalfSize / 4
@@ -112,7 +115,7 @@ impl AlsaSoundDevice {
Ok(Self {
playback_device,
frame_count,
callback,
callback: Box::new(callback),
out_data: vec![Default::default(); samples_per_channel],
mix_buffer: vec![(0.0, 0.0); samples_per_channel],
})
@@ -121,12 +124,12 @@ impl AlsaSoundDevice {
}
impl Device for AlsaSoundDevice {
fn get_mix_context(&mut self) -> MixContext {
MixContext {
fn get_mix_context(&mut self) -> Option<MixContext> {
Some(MixContext {
mix_buffer: self.mix_buffer.as_mut_slice(),
out_data: &mut self.out_data,
callback: &mut self.callback,
}
})
}
fn run(&mut self) {
+9 -9
View File
@@ -62,9 +62,9 @@ unsafe extern "C" fn audio_queue_callback(
}
impl CoreaudioSoundDevice {
pub fn new(
pub fn new<F: FnMut(&mut [(f32, f32)]) + Send + 'static>(
buffer_len_bytes: u32,
fill_callback: Box<FeedCallback>,
fill_callback: F,
) -> Result<Self, SoundError> {
// 16-bit linear PCM
let desc = AudioStreamBasicDescription {
@@ -82,7 +82,7 @@ impl CoreaudioSoundDevice {
// create data at fixed memory location
let samples_per_channel = buffer_len_bytes as usize / size_of::<NativeSample>();
let mut inner = Box::new(Inner {
fill_callback,
fill_callback: Box::new(fill_callback),
out_data: vec![Default::default(); samples_per_channel],
mix_buffer: vec![(0.0, 0.0); samples_per_channel],
queue: std::ptr::null_mut(),
@@ -156,12 +156,12 @@ impl CoreaudioSoundDevice {
}
impl Device for CoreaudioSoundDevice {
fn get_mix_context(&mut self) -> MixContext {
MixContext {
fn get_mix_context(&mut self) -> Option<MixContext> {
Some(MixContext {
mix_buffer: &mut self.inner.mix_buffer,
out_data: &mut self.inner.out_data,
callback: &mut self.inner.fill_callback,
}
})
}
fn run(&mut self) {
@@ -174,12 +174,12 @@ impl Device for CoreaudioSoundDevice {
}
impl Device for Inner {
fn get_mix_context(&mut self) -> MixContext {
MixContext {
fn get_mix_context(&mut self) -> Option<MixContext> {
Some(MixContext {
mix_buffer: &mut self.mix_buffer,
out_data: &mut self.out_data,
callback: &mut self.fill_callback,
}
})
}
fn run(&mut self) {
+8 -5
View File
@@ -59,7 +59,10 @@ fn check<S: Into<String>>(code: i32, message: S) -> Result<(), SoundError> {
}
impl DirectSoundDevice {
pub fn new(buffer_len_bytes: u32, callback: Box<FeedCallback>) -> Result<Self, SoundError> {
pub fn new<F: FnMut(&mut [(f32, f32)]) + Send + 'static>(
buffer_len_bytes: u32,
callback: F,
) -> Result<Self, SoundError> {
unsafe {
let mut direct_sound = std::ptr::null_mut();
check(
@@ -146,7 +149,7 @@ impl DirectSoundDevice {
mix_buffer: vec![(0.0, 0.0); samples_per_channel],
notify_points,
buffer_len_bytes,
callback,
callback: Box::new(callback),
})
}
}
@@ -186,12 +189,12 @@ unsafe fn write(
}
impl Device for DirectSoundDevice {
fn get_mix_context(&mut self) -> MixContext {
MixContext {
fn get_mix_context(&mut self) -> Option<MixContext> {
Some(MixContext {
mix_buffer: self.mix_buffer.as_mut_slice(),
out_data: &mut self.out_data,
callback: &mut self.callback,
}
})
}
fn run(&mut self) {
+6 -19
View File
@@ -6,30 +6,17 @@ use crate::{
};
use std::mem::size_of;
pub struct DummySoundDevice {
callback: Box<FeedCallback>,
out_data: Vec<NativeSample>,
mix_buffer: Vec<(f32, f32)>,
}
pub struct DummySoundDevice {}
impl DummySoundDevice {
pub fn new(buffer_len_bytes: u32, callback: Box<FeedCallback>) -> Result<Self, SoundError> {
let samples_per_channel = buffer_len_bytes as usize / size_of::<NativeSample>();
Ok(Self {
callback,
out_data: vec![Default::default(); samples_per_channel],
mix_buffer: vec![(0.0, 0.0); samples_per_channel],
})
impl DummySoundDevice<F: FnMut(&mut [(f32, f32)]) + Send + 'static> {
pub fn new(_buffer_len_bytes: u32, _callback: F) -> Result<Self, SoundError> {
Ok(Self)
}
}
impl Device for DummySoundDevice {
fn get_mix_context(&mut self) -> MixContext {
MixContext {
mix_buffer: self.mix_buffer.as_mut_slice(),
out_data: &mut self.out_data,
callback: &mut self.callback,
}
fn get_mix_context(&mut self) -> Option<MixContext> {
None
}
fn run(&mut self) {
+30 -19
View File
@@ -15,9 +15,12 @@ mod alsa;
mod coreaudio;
// The dummy target works on all platforms
#[cfg(not(any(target_os = "windows", target_os = "linux")))]
#[cfg(not(any(target_os = "windows", target_os = "linux", target_arch = "wasm32")))]
mod dummy;
#[cfg(target_arch = "wasm32")]
mod web;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct NativeSample {
@@ -52,38 +55,39 @@ fn sample_to_i16(sample: f32) -> i16 {
}
trait Device {
fn get_mix_context(&mut self) -> MixContext;
fn get_mix_context(&mut self) -> Option<MixContext>;
fn run(&mut self);
fn mix(&mut self) {
let context = self.get_mix_context();
if let Some(context) = self.get_mix_context() {
// Clear mixer buffer.
for (left, right) in context.mix_buffer.iter_mut() {
*left = 0.0;
*right = 0.0;
}
// Clear mixer buffer.
for (left, right) in context.mix_buffer.iter_mut() {
*left = 0.0;
*right = 0.0;
}
// Fill it.
(context.callback)(context.mix_buffer);
// Fill it.
(context.callback)(context.mix_buffer);
// Convert to i16 - device expects samples in this format.
assert_eq!(context.mix_buffer.len(), context.out_data.len());
for ((left, right), ref mut out_sample) in context.mix_buffer.iter().zip(context.out_data) {
out_sample.left = sample_to_i16(*left);
out_sample.right = sample_to_i16(*right);
// Convert to i16 - device expects samples in this format.
assert_eq!(context.mix_buffer.len(), context.out_data.len());
for ((left, right), ref mut out_sample) in
context.mix_buffer.iter().zip(context.out_data)
{
out_sample.left = sample_to_i16(*left);
out_sample.right = sample_to_i16(*right);
}
}
}
}
/// Transfer ownership of device to separate mixer thread. It will
/// call the callback with a specified rate to get data to send to a physical device.
pub(in crate) fn run_device(
pub(in crate) fn run_device<F: FnMut(&mut [(f32, f32)]) + Send + 'static>(
#[allow(unused_variables)] buffer_len_bytes: u32,
#[allow(unused_variables)] callback: Box<FeedCallback>,
#[allow(unused_variables)] callback: F,
) {
// TODO: Add WASM backend.
#[cfg(not(target_arch = "wasm32"))]
{
std::thread::spawn(move || {
@@ -99,4 +103,11 @@ pub(in crate) fn run_device(
device.run()
});
}
#[cfg(target_arch = "wasm32")]
{
let mut device = web::WebAudioDevice::new(buffer_len_bytes, callback);
device.run();
std::mem::forget(device);
}
}
+134
View File
@@ -0,0 +1,134 @@
use crate::{
context::SAMPLE_RATE,
device::{Device, MixContext, NativeSample},
};
use rg3d_core::{
wasm_bindgen::{self, prelude::*, JsCast},
web_sys::{AudioContext, AudioContextOptions},
};
use std::{
mem::size_of,
sync::{Arc, Mutex, RwLock},
};
pub struct WebAudioDevice {
ctx: Arc<AudioContext>,
onended: Vec<Arc<RwLock<Option<Closure<dyn FnMut()>>>>>,
buffer_duration_secs: f32,
}
impl WebAudioDevice {
pub fn new<F: FnMut(&mut [(f32, f32)]) + Send + 'static>(
buffer_len_bytes: u32,
callback: F,
) -> Self {
let callback = Arc::new(Mutex::new(callback));
let mut options = AudioContextOptions::new();
options.sample_rate(SAMPLE_RATE as f32);
let ctx = Arc::new(AudioContext::new_with_context_options(&options).unwrap());
let samples_per_channel = buffer_len_bytes as usize / size_of::<NativeSample>();
let buffer_duration_secs = samples_per_channel as f32 / (SAMPLE_RATE as f32);
let mut onended: Vec<Arc<RwLock<Option<Closure<dyn FnMut()>>>>> = Vec::new();
let time = Arc::new(RwLock::new(0.0f32));
for _ in 0..2 {
let buffer = ctx
.create_buffer(2, samples_per_channel as u32, SAMPLE_RATE as f32)
.unwrap();
let onended_closure: Arc<RwLock<Option<Closure<dyn FnMut()>>>> =
Arc::new(RwLock::new(None));
let ctx_clone = ctx.clone();
let onended_closure_clone = onended_closure.clone();
let time = time.clone();
let callback = callback.clone();
onended_closure
.write()
.unwrap()
.replace(Closure::wrap(Box::new(move || {
let mut mix_buffer = vec![(0.0f32, 0.0f32); samples_per_channel];
let current_time = ctx_clone.current_time() as f32;
let raw_time = *time.read().unwrap();
let start_time = if raw_time > f32::EPSILON {
raw_time
} else {
current_time + 0.025
};
callback.lock().unwrap()(&mut mix_buffer);
// Fill left channel.
let channel_samples = mix_buffer.iter().map(|s| s.0).collect::<Vec<_>>();
buffer.copy_to_channel(&channel_samples, 0).unwrap();
// Fill right channel.
let channel_samples = mix_buffer.iter().map(|s| s.1).collect::<Vec<_>>();
buffer.copy_to_channel(&channel_samples, 1).unwrap();
// Create source.
let source = ctx_clone.create_buffer_source().unwrap();
source.set_buffer(Some(&buffer));
source
.connect_with_audio_node(&ctx_clone.destination())
.unwrap();
source.set_onended(Some(
onended_closure_clone
.read()
.unwrap()
.as_ref()
.unwrap()
.as_ref()
.unchecked_ref(),
));
source.start_with_when(start_time as f64).unwrap();
*time.write().unwrap() = start_time + buffer_duration_secs;
})));
onended.push(onended_closure);
}
Self {
ctx,
onended,
buffer_duration_secs,
}
}
}
impl Drop for WebAudioDevice {
fn drop(&mut self) {
let _ = self.ctx.close().unwrap();
}
}
impl Device for WebAudioDevice {
fn get_mix_context(&mut self) -> Option<MixContext<'_>> {
None
}
fn run(&mut self) {
let window = rg3d_core::web_sys::window().unwrap();
let mut offset_ms = 10;
let time_step_ms = (self.buffer_duration_secs * 1_000.0) as i32;
for on_ended_closure in self.onended.iter() {
window
.set_timeout_with_callback_and_timeout_and_arguments_0(
on_ended_closure
.read()
.unwrap()
.as_ref()
.unwrap()
.as_ref()
.unchecked_ref(),
offset_ms,
)
.unwrap();
offset_ms += time_step_ms;
}
}
}
+2 -2
View File
@@ -30,14 +30,14 @@ impl SoundEngine {
// in Arc<Mutex<>>
device::run_device(4 * Context::SAMPLES_PER_CHANNEL as u32, {
let state = engine.clone();
Box::new(move |buf| {
move |buf| {
if let Ok(mut state) = state.lock() {
let master_gain = state.master_gain;
for context in state.contexts.iter_mut() {
context.state().render(master_gain, buf);
}
}
})
}
});
engine
+1
View File
@@ -86,6 +86,7 @@ pub mod source;
// Reexport some modules because there some types of them in public API.
pub use hrtf;
pub use rg3d_core::algebra;
pub use rg3d_core::futures;
pub use rg3d_core::math;
pub use rg3d_core::pool;
+2 -1
View File
@@ -7,6 +7,7 @@ pub mod error;
pub mod resource_manager;
use crate::core::algebra::Vector2;
use crate::core::instant;
use crate::resource::texture::TextureKind;
use crate::utils::log::{Log, MessageKind};
use crate::{
@@ -231,7 +232,7 @@ impl<M: MessageData, C: Control<M, C>> Visit for Engine<M, C> {
self.scenes.visit("Scenes", visitor)?;
if visitor.is_reading() {
futures::executor::block_on(self.resource_manager.reload_resources());
crate::core::futures::executor::block_on(self.resource_manager.reload_resources());
let mut sound_engine = self.sound_engine.lock().unwrap();
for scene in self.scenes.iter_mut() {
+10 -9
View File
@@ -1,5 +1,8 @@
//! Resource manager controls loading and lifetime of resource in the engine.
#[cfg(not(target_arch = "wasm32"))]
use crate::core::futures::executor::ThreadPool;
use crate::core::instant;
use crate::{
core::visitor::{Visit, VisitResult, Visitor},
resource::{
@@ -13,8 +16,6 @@ use crate::{
sound::buffer::{DataSource, SoundBuffer},
utils::log::{Log, MessageKind},
};
#[cfg(not(target_arch = "wasm32"))]
use futures::executor::ThreadPool;
use std::{
borrow::Cow,
ops::{Deref, DerefMut},
@@ -626,7 +627,7 @@ impl ResourceManager {
textures
};
futures::future::join_all(textures).await;
crate::core::futures::future::join_all(textures).await;
}
/// Reloads every loaded model. This method is asynchronous, internally it uses thread pool
@@ -661,7 +662,7 @@ impl ResourceManager {
models
};
futures::future::join_all(models).await;
crate::core::futures::future::join_all(models).await;
Log::writeln(
MessageKind::Information,
@@ -713,14 +714,14 @@ impl ResourceManager {
sound_buffers
};
futures::future::join_all(buffers).await;
crate::core::futures::future::join_all(buffers).await;
}
/// Reloads all loaded resources. Normally it should never be called, because it is **very** heavy
/// method! This method is asynchronous, it uses all available CPU power to reload resources as
/// fast as possible.
pub async fn reload_resources(&self) {
futures::join!(
crate::core::futures::join!(
self.reload_textures(),
self.reload_models(),
self.reload_sound_buffers()
@@ -992,13 +993,13 @@ impl Visit for ResourceManagerState {
fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
visitor.enter_region(name)?;
futures::executor::block_on(futures::future::join_all(
crate::core::futures::executor::block_on(crate::core::futures::future::join_all(
self.textures.iter().map(|t| t.value.clone()),
));
futures::executor::block_on(futures::future::join_all(
crate::core::futures::executor::block_on(crate::core::futures::future::join_all(
self.models.iter().map(|m| m.value.clone()),
));
futures::executor::block_on(futures::future::join_all(
crate::core::futures::executor::block_on(crate::core::futures::future::join_all(
self.sound_buffers.iter().map(|m| m.value.clone()),
));
-2
View File
@@ -36,8 +36,6 @@ pub use tbc;
#[cfg(target_arch = "wasm32")]
pub use winit::*;
pub use futures;
pub use instant;
pub use rapier3d as physics;
pub use rg3d_core as core;
pub use rg3d_sound as sound;
+1
View File
@@ -32,6 +32,7 @@ mod sprite_renderer;
mod ssao;
mod ui_renderer;
use crate::core::instant;
use crate::renderer::fxaa::FxaaRenderer;
use crate::{
core::{
+1 -1
View File
@@ -43,7 +43,7 @@ use std::{
sync::{Arc, RwLock},
};
use instant::Instant;
use crate::core::instant::Instant;
/// Input angles in degrees
fn quat_from_euler(euler: Vector3<f32>) -> UnitQuaternion<f32> {
+6 -4
View File
@@ -20,18 +20,20 @@
//! will automatically provide you info about metrics of texture, but it won't give you
//! access to pixels of render target.
use crate::core::io;
use crate::{
core::visitor::{Visit, VisitError, VisitResult, Visitor},
core::{
futures::io::Error,
io,
visitor::{Visit, VisitError, VisitResult, Visitor},
},
resource::{Resource, ResourceData, ResourceState},
};
use ddsfile::{Caps2, D3DFormat};
use futures::io::Error;
use image::{ColorType, DynamicImage, GenericImageView, ImageError};
use rg3d_core::io::FileLoadError;
use std::io::Cursor;
use std::{
borrow::Cow,
io::Cursor,
path::{Path, PathBuf},
};
+2 -1
View File
@@ -15,6 +15,7 @@ pub mod physics;
pub mod sprite;
pub mod transform;
use crate::core::instant;
use crate::core::pool::Ticket;
use crate::scene::physics::PhysicsPerformanceStatistics;
use crate::{
@@ -1171,7 +1172,7 @@ impl Scene {
}
}
let _ = futures::future::join_all(resources).await;
let _ = crate::core::futures::future::join_all(resources).await;
// Restore pointers to resources. Scene saves only paths to resources, here we must
// find real resources instead.
+1
View File
@@ -1,5 +1,6 @@
//! Contains all structures and methods to operate with physics world.
use crate::core::instant;
use crate::{
core::{
arrayvec::{Array, ArrayVec},
+1
View File
@@ -1,6 +1,7 @@
//! UV Map generator. Used to generate second texture coordinates for lightmaps.
//!
//! Current implementation uses simple planar mapping.
use crate::core::instant;
use crate::{
core::{
algebra::Vector2,