mirror of
https://github.com/FyroxEngine/Fyrox.git
synced 2026-10-12 02:02:20 +00:00
added WebAudio backend for rg3d-sound, moved some dependencies to rg3d-core
- #85
This commit is contained in:
@@ -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" }
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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::{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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.
@@ -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 = []
|
||||
@@ -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;
|
||||
|
||||
@@ -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"]
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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() {
|
||||
|
||||
@@ -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()),
|
||||
));
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -32,6 +32,7 @@ mod sprite_renderer;
|
||||
mod ssao;
|
||||
mod ui_renderer;
|
||||
|
||||
use crate::core::instant;
|
||||
use crate::renderer::fxaa::FxaaRenderer;
|
||||
use crate::{
|
||||
core::{
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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
@@ -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,5 +1,6 @@
|
||||
//! Contains all structures and methods to operate with physics world.
|
||||
|
||||
use crate::core::instant;
|
||||
use crate::{
|
||||
core::{
|
||||
arrayvec::{Array, ArrayVec},
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user