Compare commits

...

4 Commits

Author SHA1 Message Date
Attila Uygun c90d039944 Refactoring audio code 2023-05-18 22:25:11 +02:00
Attila Uygun be1d562a07 Refactoring Animator class. 2023-05-18 22:25:01 +02:00
Attila Uygun cda1d750f8 Manage custom shaders in Engine 2023-05-17 11:46:19 +02:00
Attila Uygun 7dd7d7bfd7 Remove unused assets 2023-05-17 08:22:11 +02:00
33 changed files with 437 additions and 370 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

View File

@ -62,8 +62,8 @@ add_library(kaliber SHARED
../../../src/demo/sky_quad.cc ../../../src/demo/sky_quad.cc
../../../src/engine/animatable.cc ../../../src/engine/animatable.cc
../../../src/engine/animator.cc ../../../src/engine/animator.cc
../../../src/engine/audio/audio_base.cc ../../../src/engine/audio/audio_driver_oboe.cc
../../../src/engine/audio/audio_oboe.cc ../../../src/engine/audio/audio_mixer.cc
../../../src/engine/drawable.cc ../../../src/engine/drawable.cc
../../../src/engine/engine.cc ../../../src/engine/engine.cc
../../../src/engine/font.cc ../../../src/engine/font.cc

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="17" android:versionCode="18"
android:versionName="1.0.2"> android:versionName="1.0.2">
<application <application

View File

@ -95,8 +95,8 @@ $(BASE_LIB): $(BASE_OBJS)
ENGINE_SRC := \ ENGINE_SRC := \
$(SRC_ROOT)/engine/animatable.cc \ $(SRC_ROOT)/engine/animatable.cc \
$(SRC_ROOT)/engine/animator.cc \ $(SRC_ROOT)/engine/animator.cc \
$(SRC_ROOT)/engine/audio/audio_alsa.cc \ $(SRC_ROOT)/engine/audio/audio_driver_alsa.cc \
$(SRC_ROOT)/engine/audio/audio_base.cc \ $(SRC_ROOT)/engine/audio/audio_mixer.cc \
$(SRC_ROOT)/engine/drawable.cc \ $(SRC_ROOT)/engine/drawable.cc \
$(SRC_ROOT)/engine/engine.cc \ $(SRC_ROOT)/engine/engine.cc \
$(SRC_ROOT)/engine/font.cc \ $(SRC_ROOT)/engine/font.cc \

View File

@ -50,6 +50,10 @@ Demo::~Demo() {
bool Demo::Initialize() { bool Demo::Initialize() {
saved_data_.Load(kSaveFileName); saved_data_.Load(kSaveFileName);
Engine::Get().LoadCustomShader("sky_without_nebula",
"sky_without_nebula.glsl");
Engine::Get().LoadCustomShader("sky", "sky.glsl");
if (!font_.Load("PixelCaps!.ttf")) if (!font_.Load("PixelCaps!.ttf"))
return false; return false;
@ -162,11 +166,6 @@ void Demo::Update(float delta_time) {
UpdateGameState(delta_time); UpdateGameState(delta_time);
} }
void Demo::ContextLost() {
sky_.ContextLost();
enemy_.ContextLost();
}
void Demo::LostFocus() {} void Demo::LostFocus() {}
void Demo::GainedFocus(bool from_interstitial_ad) { void Demo::GainedFocus(bool from_interstitial_ad) {

View File

@ -27,7 +27,7 @@ class Demo final : public eng::Game {
void Update(float delta_time) final; void Update(float delta_time) final;
void ContextLost() final; void ContextLost() final {}
void LostFocus() final; void LostFocus() final;

View File

@ -13,8 +13,6 @@
#include "engine/font.h" #include "engine/font.h"
#include "engine/image.h" #include "engine/image.h"
#include "engine/renderer/geometry.h" #include "engine/renderer/geometry.h"
#include "engine/renderer/shader.h"
#include "engine/shader_source.h"
#include "engine/sound.h" #include "engine/sound.h"
#include "demo/demo.h" #include "demo/demo.h"
@ -76,8 +74,7 @@ float SnapSpawnPosX(int col) {
} // namespace } // namespace
Enemy::Enemy() Enemy::Enemy() = default;
: chromatic_aberration_(Engine::Get().CreateRenderResource<Shader>()) {}
Enemy::~Enemy() = default; Enemy::~Enemy() = default;
@ -205,10 +202,6 @@ void Enemy::Pause(bool pause) {
boss_animator_.PauseOrResumeAll(pause); boss_animator_.PauseOrResumeAll(pause);
} }
void Enemy::ContextLost() {
CreateShaders();
}
bool Enemy::HasTarget(DamageType damage_type) { bool Enemy::HasTarget(DamageType damage_type) {
DCHECK(damage_type > kDamageType_Invalid && damage_type < kDamageType_Any); DCHECK(damage_type > kDamageType_Invalid && damage_type < kDamageType_Any);
@ -423,7 +416,7 @@ void Enemy::StopAllEnemyUnits(bool chromatic_aberration_effect) {
continue; continue;
if (chromatic_aberration_effect) { if (chromatic_aberration_effect) {
e.sprite.SetCustomShader(chromatic_aberration_.get()); e.sprite.SetCustomShader("chromatic_aberration");
e.chromatic_aberration_active_ = true; e.chromatic_aberration_active_ = true;
} }
@ -1193,9 +1186,6 @@ std::unique_ptr<Image> Enemy::GetScoreImage(EnemyType enemy_type) {
} }
bool Enemy::CreateRenderResources() { bool Enemy::CreateRenderResources() {
if (!CreateShaders())
return false;
Engine::Get().SetImageSource("skull_tex", "enemy_anims_01_frames_ok.png", Engine::Get().SetImageSource("skull_tex", "enemy_anims_01_frames_ok.png",
true); true);
Engine::Get().SetImageSource("bug_tex", "enemy_anims_02_frames_ok.png", true); Engine::Get().SetImageSource("bug_tex", "enemy_anims_02_frames_ok.png", true);
@ -1213,16 +1203,9 @@ bool Enemy::CreateRenderResources() {
"score_tex"s + std::to_string(i), "score_tex"s + std::to_string(i),
std::bind(&Enemy::GetScoreImage, this, (EnemyType)i), true); std::bind(&Enemy::GetScoreImage, this, (EnemyType)i), true);
return true; Engine::Get().LoadCustomShader("chromatic_aberration",
} "chromatic_aberration.glsl");
bool Enemy::CreateShaders() {
auto source = std::make_unique<ShaderSource>();
if (!source->Load("chromatic_aberration.glsl"))
return false;
chromatic_aberration_->Create(std::move(source),
Engine::Get().GetQuad()->vertex_description(),
Engine::Get().GetQuad()->primitive(), false);
return true; return true;
} }

View File

@ -15,7 +15,6 @@
namespace eng { namespace eng {
class Image; class Image;
class Shader;
class Sound; class Sound;
} // namespace eng } // namespace eng
@ -30,8 +29,6 @@ class Enemy {
void Pause(bool pause); void Pause(bool pause);
void ContextLost();
bool HasTarget(DamageType damage_type); bool HasTarget(DamageType damage_type);
base::Vector2f GetTargetPos(DamageType damage_type); base::Vector2f GetTargetPos(DamageType damage_type);
@ -106,7 +103,6 @@ class Enemy {
eng::SoundPlayer hit; eng::SoundPlayer hit;
}; };
std::unique_ptr<eng::Shader> chromatic_aberration_;
float chromatic_aberration_offset_ = 0; float chromatic_aberration_offset_ = 0;
eng::ImageQuad boss_; eng::ImageQuad boss_;
@ -169,7 +165,6 @@ class Enemy {
std::unique_ptr<eng::Image> GetScoreImage(EnemyType enemy_type); std::unique_ptr<eng::Image> GetScoreImage(EnemyType enemy_type);
bool CreateRenderResources(); bool CreateRenderResources();
bool CreateShaders();
void TranslateEnemyUnit(EnemyUnit& e, const base::Vector2f& delta); void TranslateEnemyUnit(EnemyUnit& e, const base::Vector2f& delta);
}; };

View File

@ -6,14 +6,12 @@
#include "engine/engine.h" #include "engine/engine.h"
#include "engine/renderer/geometry.h" #include "engine/renderer/geometry.h"
#include "engine/renderer/shader.h" #include "engine/renderer/shader.h"
#include "engine/shader_source.h"
using namespace base; using namespace base;
using namespace eng; using namespace eng;
SkyQuad::SkyQuad() SkyQuad::SkyQuad()
: shader_(Engine::Get().CreateRenderResource<Shader>()), : sky_offset_{
sky_offset_{
0, Lerp(0.0f, 10.0f, Engine::Get().GetRandomGenerator().Rand())} { 0, Lerp(0.0f, 10.0f, Engine::Get().GetRandomGenerator().Rand())} {
last_sky_offset_ = sky_offset_; last_sky_offset_ = sky_offset_;
} }
@ -22,10 +20,9 @@ SkyQuad::~SkyQuad() = default;
bool SkyQuad::Create(bool without_nebula) { bool SkyQuad::Create(bool without_nebula) {
without_nebula_ = without_nebula; without_nebula_ = without_nebula;
if (!CreateShaders())
return false;
scale_ = Engine::Get().GetScreenSize(); scale_ = Engine::Get().GetScreenSize();
shader_ = Engine::Get().GetCustomShader(
without_nebula ? "sky_without_nebula" : "sky");
color_animator_.Attach(this); color_animator_.Attach(this);
@ -54,10 +51,6 @@ void SkyQuad::Draw(float frame_frac) {
Engine::Get().GetQuad()->Draw(); Engine::Get().GetQuad()->Draw();
} }
void SkyQuad::ContextLost() {
CreateShaders();
}
void SkyQuad::SwitchColor(const Vector4f& color) { void SkyQuad::SwitchColor(const Vector4f& color) {
color_animator_.Pause(Animator::kBlending); color_animator_.Pause(Animator::kBlending);
color_animator_.SetTime(Animator::kBlending, 0); color_animator_.SetTime(Animator::kBlending, 0);
@ -66,17 +59,6 @@ void SkyQuad::SwitchColor(const Vector4f& color) {
color_animator_.Play(Animator::kBlending, false); color_animator_.Play(Animator::kBlending, false);
} }
bool SkyQuad::CreateShaders() {
Engine& engine = Engine::Get();
auto source = std::make_unique<ShaderSource>();
if (!source->Load(without_nebula_ ? "sky_without_nebula.glsl" : "sky.glsl"))
return false;
shader_->Create(std::move(source), engine.GetQuad()->vertex_description(),
Engine::Get().GetQuad()->primitive(), false);
return true;
}
void SkyQuad::SetSpeed(float speed) { void SkyQuad::SetSpeed(float speed) {
speed_ = speed; speed_ = speed;
} }

View File

@ -36,8 +36,6 @@ class SkyQuad : public eng::Animatable {
// Drawable interface. // Drawable interface.
void Draw(float frame_frac) final; void Draw(float frame_frac) final;
void ContextLost();
void SwitchColor(const base::Vector4f& color); void SwitchColor(const base::Vector4f& color);
void SetSpeed(float speed); void SetSpeed(float speed);
@ -45,7 +43,7 @@ class SkyQuad : public eng::Animatable {
const base::Vector4f& nebula_color() { return nebula_color_; } const base::Vector4f& nebula_color() { return nebula_color_; }
private: private:
std::unique_ptr<eng::Shader> shader_; eng::Shader* shader_;
base::Vector2f sky_offset_ = {0, 0}; base::Vector2f sky_offset_ = {0, 0};
base::Vector2f last_sky_offset_ = {0, 0}; base::Vector2f last_sky_offset_ = {0, 0};
@ -57,8 +55,6 @@ class SkyQuad : public eng::Animatable {
float speed_ = 0; float speed_ = 0;
bool without_nebula_ = false; bool without_nebula_ = false;
bool CreateShaders();
}; };
#endif // DEMO_SKY_QUAD_H #endif // DEMO_SKY_QUAD_H

View File

@ -126,10 +126,10 @@ void Animator::SetMovement(Vector2f direction,
a.movement_last_pos = {0, 0}; a.movement_last_pos = {0, 0};
} }
void Animator::SetRotation(float trget, void Animator::SetRotation(float target,
float duration, float duration,
Interpolator interpolator) { Interpolator interpolator) {
rotation_target_ = trget; rotation_target_ = target;
rotation_speed_ = 1.0f / duration; rotation_speed_ = 1.0f / duration;
rotation_interpolator_ = std::move(interpolator); rotation_interpolator_ = std::move(interpolator);
@ -153,7 +153,7 @@ void Animator::SetFrames(int count,
frame_count_ = count; frame_count_ = count;
frame_speed_ = (float)frames_per_second / (float)count; frame_speed_ = (float)frames_per_second / (float)count;
for (auto& a : elements_) for (auto& a : elements_)
a.frame_start_ = a.animatable->GetFrame(); a.frame_start = a.animatable->GetFrame();
frame_interpolator_ = std::move(interpolator); frame_interpolator_ = std::move(interpolator);
} }
@ -180,66 +180,43 @@ void Animator::Update(float delta_time) {
UpdateAnimTime(delta_time, kFrames, frame_speed_, frame_time_, frame_cb_); UpdateAnimTime(delta_time, kFrames, frame_speed_, frame_time_, frame_cb_);
if (play_flags_ & kTimer) if (play_flags_ & kTimer)
UpdateAnimTime(delta_time, kTimer, timer_speed_, timer_time_, timer_cb_); UpdateAnimTime(delta_time, kTimer, timer_speed_, timer_time_, timer_cb_);
did_evaluate_ = false;
} }
void Animator::Evaluate(float frame_frac_time) { void Animator::Evaluate(float frame_frac) {
did_evaluate_ = true; if (play_flags_ & kMovement)
EvaluateAnimation<Vector2f, true>(
frame_frac, movement_speed_, movement_time_, movement_interpolator_,
movement_direction_, [](Element& a, Vector2f pos) {
a.animatable->Translate(pos - a.movement_last_pos);
a.movement_last_pos = pos;
});
Vector2f pos = {0, 0}; if (play_flags_ & kRotation)
if (play_flags_ & kMovement) { EvaluateAnimation<float, true>(
float time = movement_time_ + movement_speed_ * frame_frac_time; frame_frac, rotation_speed_, rotation_time_, rotation_interpolator_,
float interpolated_time = rotation_target_, [](Element& a, float theta) {
movement_interpolator_ ? movement_interpolator_(time) : time; a.animatable->Rotate(theta - a.rotation_last_theta);
pos = base::Lerp({0, 0}, movement_direction_, interpolated_time); a.rotation_last_theta = theta;
} });
float theta = 0; if (play_flags_ & kBlending)
if (play_flags_ & kRotation) { EvaluateAnimation<float, false>(
float time = rotation_time_ + rotation_speed_ * frame_frac_time; frame_frac, blending_speed_, blending_time_, blending_interpolator_, {},
float interpolated_time = [&](Element& a, float interpolated_time) {
rotation_interpolator_ ? rotation_interpolator_(time) : time; Vector4f blending =
theta = base::Lerp(0.0f, rotation_target_, interpolated_time); base::Lerp(a.blending_start, blending_target_, interpolated_time);
} a.animatable->SetColor(blending);
});
float blending_itime = 0; if (play_flags_ & kFrames)
if (play_flags_ & kBlending) { EvaluateAnimation<float, false>(
float time = blending_time_ + blending_speed_ * frame_frac_time; frame_frac, frame_speed_, frame_time_, frame_interpolator_, {},
blending_itime = [&](Element& a, float interpolated_time) {
blending_interpolator_ ? blending_interpolator_(time) : time; int target = a.frame_start + frame_count_;
} int frame = base::Lerp(a.frame_start, target, interpolated_time);
if (frame < target)
float frame_itime = 0; a.animatable->SetFrame(frame);
if (play_flags_ & kFrames) { });
float time = frame_time_ + frame_speed_ * frame_frac_time;
frame_itime = frame_interpolator_ ? frame_interpolator_(time) : time;
}
for (auto& a : elements_) {
if (play_flags_ & kMovement) {
a.animatable->Translate(pos - a.movement_last_pos);
a.movement_last_pos = pos;
}
if (play_flags_ & kRotation) {
a.animatable->Rotate(theta - a.rotation_last_theta);
a.rotation_last_theta = theta;
}
if (play_flags_ & kBlending) {
Vector4f blending =
base::Lerp(a.blending_start, blending_target_, blending_itime);
a.animatable->SetColor(blending);
}
if (play_flags_ & kFrames) {
int target = a.frame_start_ + frame_count_;
int frame = base::Lerp(a.frame_start_, target, frame_itime);
if (frame < target)
a.animatable->SetFrame(frame);
}
}
} }
void Animator::UpdateAnimTime(float delta_time, void Animator::UpdateAnimTime(float delta_time,
@ -252,8 +229,8 @@ void Animator::UpdateAnimTime(float delta_time,
if (loop_flags_ & anim) { if (loop_flags_ & anim) {
anim_time = fmod(anim_time, 1.0f); anim_time = fmod(anim_time, 1.0f);
} else { } else {
if (!did_evaluate_) anim_time = 1.0f;
Evaluate(0); Evaluate(0);
anim_time = 0; anim_time = 0;
play_flags_ &= ~anim; play_flags_ &= ~anim;
if (cb) { if (cb) {
@ -269,4 +246,24 @@ void Animator::UpdateAnimTime(float delta_time,
} }
} }
template <typename T, bool relative>
void Animator::EvaluateAnimation(float frame_frac,
float anim_speed,
float anim_time,
Interpolator& interpolator,
T target,
ResultSetter<T> set_result) {
float time = anim_time + anim_speed * frame_frac;
float interpolated_time = interpolator ? interpolator(time) : time;
if constexpr (relative) {
T result = base::Lerp(T{0}, target, interpolated_time);
for (auto& a : elements_)
set_result(a, result);
} else {
for (auto& a : elements_)
set_result(a, interpolated_time);
}
}
} // namespace eng } // namespace eng

View File

@ -40,7 +40,7 @@ class Animator {
float GetTime(int animation); float GetTime(int animation);
void SetTime(int animation, float time, bool force_update = false); void SetTime(int animation, float time, bool force_update = false);
// Set callback ro be called once animation ends. // Set callback to be called once animation ends.
void SetEndCallback(int animation, base::Closure cb); void SetEndCallback(int animation, base::Closure cb);
// Distance is the magnitude of direction vector. Duration is in seconds. // Distance is the magnitude of direction vector. Duration is in seconds.
@ -69,7 +69,7 @@ class Animator {
void SetVisible(bool visible); void SetVisible(bool visible);
void Update(float delta_time); void Update(float delta_time);
void Evaluate(float frame_frac_time); void Evaluate(float frame_frac);
bool IsPlaying(int animation) const { return play_flags_ & animation; } bool IsPlaying(int animation) const { return play_flags_ & animation; }
@ -79,7 +79,7 @@ class Animator {
base::Vector2f movement_last_pos = {0, 0}; base::Vector2f movement_last_pos = {0, 0};
float rotation_last_theta = 0; float rotation_last_theta = 0;
base::Vector4f blending_start = {0, 0, 0, 0}; base::Vector4f blending_start = {0, 0, 0, 0};
int frame_start_ = 0; int frame_start = 0;
}; };
unsigned int play_flags_ = 0; unsigned int play_flags_ = 0;
@ -120,13 +120,22 @@ class Animator {
base::Closure pending_cb_; base::Closure pending_cb_;
Flags inside_cb_ = kNone; Flags inside_cb_ = kNone;
bool did_evaluate_ = false;
void UpdateAnimTime(float delta_time, void UpdateAnimTime(float delta_time,
int anim, int anim,
float anim_speed, float anim_speed,
float& anim_time, float& anim_time,
base::Closure& cb); base::Closure& cb);
template <typename T>
using ResultSetter = std::function<void(Element&, T)>;
template <typename T, bool relative>
void EvaluateAnimation(float frame_frac,
float anim_speed,
float anim_time,
Interpolator& interpolator,
T target,
ResultSetter<T> set_result);
}; };
} // namespace eng } // namespace eng

View File

@ -1,20 +0,0 @@
#ifndef ENGINE_AUDIO_AUDIO_H
#define ENGINE_AUDIO_AUDIO_H
#if defined(__ANDROID__)
#include "engine/audio/audio_oboe.h"
#elif defined(__linux__)
#include "engine/audio/audio_alsa.h"
#endif
namespace eng {
#if defined(__ANDROID__)
using Audio = AudioOboe;
#elif defined(__linux__)
using Audio = AudioAlsa;
#endif
} // namespace eng
#endif // ENGINE_AUDIO_AUDIO_H

View File

@ -1,47 +0,0 @@
#ifndef ENGINE_AUDIO_AUDIO_ALSA_H
#define ENGINE_AUDIO_AUDIO_ALSA_H
#include <atomic>
#include <thread>
#include "engine/audio/audio_base.h"
typedef struct _snd_pcm snd_pcm_t;
namespace eng {
class AudioAlsa : public AudioBase {
public:
AudioAlsa();
~AudioAlsa();
bool Initialize();
void Shutdown();
void Suspend();
void Resume();
int GetHardwareSampleRate();
private:
// Handle for the PCM device.
snd_pcm_t* device_;
std::thread audio_thread_;
std::atomic<bool> terminate_audio_thread_{false};
std::atomic<bool> suspend_audio_thread_{false};
size_t num_channels_ = 0;
int sample_rate_ = 0;
size_t period_size_ = 0;
void StartAudioThread();
void TerminateAudioThread();
void AudioThreadMain();
};
} // namespace eng
#endif // ENGINE_AUDIO_AUDIO_ALSA_H

View File

@ -0,0 +1,35 @@
#ifndef ENGINE_AUDIO_AUDIO_DRIVER_H
#define ENGINE_AUDIO_AUDIO_DRIVER_H
#include "engine/audio/audio_driver.h"
namespace eng {
class AudioDriverDelegate;
// Models an audio sink sending mixed audio to the audio driver. Audio data from
// the mixer source is delivered on a pull model using AudioDriverDelegate.
class AudioDriver {
public:
AudioDriver() = default;
virtual ~AudioDriver() = default;
virtual void SetDelegate(AudioDriverDelegate* delegate) = 0;
virtual bool Initialize() = 0;
virtual void Shutdown() = 0;
virtual void Suspend() = 0;
virtual void Resume() = 0;
virtual int GetHardwareSampleRate() = 0;
private:
AudioDriver(const AudioDriver&) = delete;
AudioDriver& operator=(const AudioDriver&) = delete;
};
} // namespace eng
#endif // ENGINE_AUDIO_AUDIO_DRIVER_H

View File

@ -1,20 +1,26 @@
#include "engine/audio/audio_alsa.h" #include "engine/audio/audio_driver_alsa.h"
#include <memory> #include <memory>
#include <alsa/asoundlib.h> #include <alsa/asoundlib.h>
#include "base/log.h" #include "base/log.h"
#include "engine/audio/audio_driver_delegate.h"
using namespace base; using namespace base;
namespace eng { namespace eng {
AudioAlsa::AudioAlsa() = default; AudioDriverAlsa::AudioDriverAlsa() = default;
AudioAlsa::~AudioAlsa() = default; AudioDriverAlsa::~AudioDriverAlsa() = default;
bool AudioAlsa::Initialize() { void AudioDriverAlsa::SetDelegate(AudioDriverDelegate* delegate) {
delegate_ = delegate;
Resume();
}
bool AudioDriverAlsa::Initialize() {
LOG << "Initializing audio system."; LOG << "Initializing audio system.";
int err; int err;
@ -136,51 +142,43 @@ bool AudioAlsa::Initialize() {
return false; return false;
} }
void AudioAlsa::Shutdown() { void AudioDriverAlsa::Shutdown() {
LOG << "Shutting down audio system."; LOG << "Shutting down audio system.";
TerminateAudioThread(); TerminateAudioThread();
snd_pcm_drop(device_); snd_pcm_drop(device_);
snd_pcm_close(device_); snd_pcm_close(device_);
} }
void AudioAlsa::Suspend() { void AudioDriverAlsa::Suspend() {
DCHECK(!terminate_audio_thread_.load(std::memory_order_relaxed));
suspend_audio_thread_.store(true, std::memory_order_relaxed); suspend_audio_thread_.store(true, std::memory_order_relaxed);
} }
void AudioAlsa::Resume() { void AudioDriverAlsa::Resume() {
DCHECK(!terminate_audio_thread_.load(std::memory_order_relaxed));
suspend_audio_thread_.store(false, std::memory_order_relaxed); suspend_audio_thread_.store(false, std::memory_order_relaxed);
} }
int AudioAlsa::GetHardwareSampleRate() { int AudioDriverAlsa::GetHardwareSampleRate() {
return sample_rate_; return sample_rate_;
} }
void AudioAlsa::StartAudioThread() { void AudioDriverAlsa::StartAudioThread() {
LOG << "Starting audio thread."; LOG << "Starting audio thread.";
terminate_audio_thread_.store(false, std::memory_order_relaxed);
DCHECK(!terminate_audio_thread_.load(std::memory_order_relaxed)); suspend_audio_thread_.store(true, std::memory_order_relaxed);
audio_thread_ = std::thread(&AudioDriverAlsa::AudioThreadMain, this);
audio_thread_ = std::thread(&AudioAlsa::AudioThreadMain, this);
} }
void AudioAlsa::TerminateAudioThread() { void AudioDriverAlsa::TerminateAudioThread() {
if (terminate_audio_thread_.load(std::memory_order_relaxed)) if (terminate_audio_thread_.load(std::memory_order_relaxed))
return; return;
LOG << "Terminating audio thread"; LOG << "Terminating audio thread";
// Notify worker thread and wait for it to terminate.
terminate_audio_thread_.store(true, std::memory_order_relaxed); terminate_audio_thread_.store(true, std::memory_order_relaxed);
suspend_audio_thread_.store(true, std::memory_order_relaxed); suspend_audio_thread_.store(true, std::memory_order_relaxed);
audio_thread_.join(); audio_thread_.join();
} }
void AudioAlsa::AudioThreadMain() { void AudioDriverAlsa::AudioThreadMain() {
size_t num_frames = period_size_ / (num_channels_ * sizeof(float)); size_t num_frames = period_size_ / (num_channels_ * sizeof(float));
auto buffer = std::make_unique<float[]>(num_frames * 2); auto buffer = std::make_unique<float[]>(num_frames * 2);
@ -192,11 +190,11 @@ void AudioAlsa::AudioThreadMain() {
std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::this_thread::sleep_for(std::chrono::milliseconds(1));
} }
RenderAudio(buffer.get(), num_frames); delegate_->RenderAudio(buffer.get(), num_frames);
while (snd_pcm_writei(device_, buffer.get(), num_frames) < 0) { while (snd_pcm_writei(device_, buffer.get(), num_frames) < 0) {
snd_pcm_prepare(device_); snd_pcm_prepare(device_);
DLOG << "Audio buffer underrun!"; DLOG << "Alsa buffer underrun!";
} }
} }
} }

View File

@ -0,0 +1,51 @@
#ifndef ENGINE_AUDIO_AUDIO_DRIVER_ALSA_H
#define ENGINE_AUDIO_AUDIO_DRIVER_ALSA_H
#include <atomic>
#include <thread>
#include "engine/audio/audio_driver.h"
typedef struct _snd_pcm snd_pcm_t;
namespace eng {
class AudioDriverAlsa final : public AudioDriver {
public:
AudioDriverAlsa();
~AudioDriverAlsa() final;
void SetDelegate(AudioDriverDelegate* delegate) final;
bool Initialize() final;
void Shutdown() final;
void Suspend() final;
void Resume() final;
int GetHardwareSampleRate() final;
private:
// Handle for the PCM device.
snd_pcm_t* device_;
std::thread audio_thread_;
std::atomic<bool> terminate_audio_thread_{false};
std::atomic<bool> suspend_audio_thread_{false};
size_t num_channels_ = 0;
int sample_rate_ = 0;
size_t period_size_ = 0;
AudioDriverDelegate* delegate_ = nullptr;
void StartAudioThread();
void TerminateAudioThread();
void AudioThreadMain();
};
} // namespace eng
#endif // ENGINE_AUDIO_AUDIO_DRIVER_ALSA_H

View File

@ -0,0 +1,18 @@
#ifndef ENGINE_AUDIO_AUDIO_DRIVER_DELEGATE_H
#define ENGINE_AUDIO_AUDIO_DRIVER_DELEGATE_H
#include <stddef.h>
namespace eng {
class AudioDriverDelegate {
public:
AudioDriverDelegate() = default;
virtual ~AudioDriverDelegate() = default;
virtual void RenderAudio(float* output_buffer, size_t num_frames) = 0;
};
} // namespace eng
#endif // ENGINE_AUDIO_AUDIO_DRIVER_DELEGATE_H

View File

@ -1,62 +1,70 @@
#include "engine/audio/audio_oboe.h" #include "engine/audio/audio_driver_oboe.h"
#include "base/log.h" #include "base/log.h"
#include "engine/audio/audio_driver_delegate.h"
#include "third_party/oboe/include/oboe/Oboe.h" #include "third_party/oboe/include/oboe/Oboe.h"
using namespace base; using namespace base;
namespace eng { namespace eng {
AudioOboe::AudioOboe() : callback_(std::make_unique<StreamCallback>(this)) {} AudioDriverOboe::AudioDriverOboe()
: callback_(std::make_unique<StreamCallback>(this)) {}
AudioOboe::~AudioOboe() = default; AudioDriverOboe::~AudioDriverOboe() = default;
bool AudioOboe::Initialize() { void AudioDriverOboe::SetDelegate(AudioDriverDelegate* delegate) {
LOG << "Initializing audio system."; delegate_ = delegate;
Resume();
return RestartStream();
} }
void AudioOboe::Shutdown() { bool AudioDriverOboe::Initialize() {
LOG << "Initializing audio system.";
return RestartStream(true);
}
void AudioDriverOboe::Shutdown() {
LOG << "Shutting down audio system."; LOG << "Shutting down audio system.";
stream_->stop(); stream_->stop();
} }
void AudioOboe::Suspend() { void AudioDriverOboe::Suspend() {
stream_->stop(); stream_->stop();
} }
void AudioOboe::Resume() { void AudioDriverOboe::Resume() {
RestartStream(); RestartStream();
} }
int AudioOboe::GetHardwareSampleRate() { int AudioDriverOboe::GetHardwareSampleRate() {
return stream_->getSampleRate(); return stream_->getSampleRate();
} }
AudioOboe::StreamCallback::StreamCallback(AudioOboe* audio) : audio_(audio) {} AudioDriverOboe::StreamCallback::StreamCallback(AudioDriverOboe* driver)
: driver_(driver) {}
AudioOboe::StreamCallback::~StreamCallback() = default; AudioDriverOboe::StreamCallback::~StreamCallback() = default;
oboe::DataCallbackResult AudioOboe::StreamCallback::onAudioReady( oboe::DataCallbackResult AudioDriverOboe::StreamCallback::onAudioReady(
oboe::AudioStream* oboe_stream, oboe::AudioStream* oboe_stream,
void* audio_data, void* audio_data,
int32_t num_frames) { int32_t num_frames) {
float* output_buffer = static_cast<float*>(audio_data); float* output_buffer = static_cast<float*>(audio_data);
audio_->RenderAudio(output_buffer, num_frames); driver_->delegate_->RenderAudio(output_buffer, num_frames);
return oboe::DataCallbackResult::Continue; return oboe::DataCallbackResult::Continue;
} }
void AudioOboe::StreamCallback::onErrorAfterClose( void AudioDriverOboe::StreamCallback::onErrorAfterClose(
oboe::AudioStream* oboe_stream, oboe::AudioStream* oboe_stream,
oboe::Result error) { oboe::Result error) {
LOG << "Error after close. Error: " << oboe::convertToText(error); LOG << "Error after close. Error: " << oboe::convertToText(error);
audio_->RestartStream(); driver_->RestartStream();
} }
bool AudioOboe::RestartStream() { bool AudioDriverOboe::RestartStream(bool suspended) {
oboe::AudioStreamBuilder builder; oboe::AudioStreamBuilder builder;
oboe::Result result = oboe::Result result =
builder.setSharingMode(oboe::SharingMode::Exclusive) builder.setSharingMode(oboe::SharingMode::Exclusive)
@ -80,7 +88,8 @@ bool AudioOboe::RestartStream() {
return false; return false;
} }
stream_->start(); if (!suspended)
stream_->start();
return true; return true;
} }

View File

@ -1,33 +1,37 @@
#ifndef ENGINE_AUDIO_AUDIO_OBOE_H #ifndef ENGINE_AUDIO_AUDIO_DRIVER_OBOE_H
#define ENGINE_AUDIO_AUDIO_OBOE_H #define ENGINE_AUDIO_AUDIO_DRIVER_OBOE_H
#include <memory> #include <memory>
#include "third_party/oboe/include/oboe/AudioStream.h" #include "third_party/oboe/include/oboe/AudioStream.h"
#include "third_party/oboe/include/oboe/AudioStreamCallback.h" #include "third_party/oboe/include/oboe/AudioStreamCallback.h"
#include "engine/audio/audio_base.h" #include "engine/audio/audio_driver.h"
namespace eng { namespace eng {
class AudioOboe : public AudioBase { class AudioDriverOboe final : public AudioDriver {
public: public:
AudioOboe(); AudioDriverOboe();
~AudioOboe(); ~AudioDriverOboe() final;
bool Initialize(); void SetDelegate(AudioDriverDelegate* delegate) final;
void Shutdown(); bool Initialize() final;
void Suspend(); void Shutdown() final;
void Resume();
int GetHardwareSampleRate(); void Suspend() final;
void Resume() final;
int GetHardwareSampleRate() final;
private: private:
static constexpr int kChannelCount = 2;
class StreamCallback final : public oboe::AudioStreamCallback { class StreamCallback final : public oboe::AudioStreamCallback {
public: public:
StreamCallback(AudioOboe* audio); StreamCallback(AudioDriverOboe* audio);
~StreamCallback() final; ~StreamCallback() final;
oboe::DataCallbackResult onAudioReady(oboe::AudioStream* oboe_stream, oboe::DataCallbackResult onAudioReady(oboe::AudioStream* oboe_stream,
@ -38,15 +42,17 @@ class AudioOboe : public AudioBase {
oboe::Result error) final; oboe::Result error) final;
private: private:
AudioOboe* audio_; AudioDriverOboe* driver_;
}; };
oboe::ManagedStream stream_; oboe::ManagedStream stream_;
std::unique_ptr<StreamCallback> callback_; std::unique_ptr<StreamCallback> callback_;
bool RestartStream(); AudioDriverDelegate* delegate_ = nullptr;
bool RestartStream(bool suspended = false);
}; };
} // namespace eng } // namespace eng
#endif // ENGINE_AUDIO_AUDIO_OBOE_H #endif // ENGINE_AUDIO_AUDIO_DRIVER_OBOE_H

View File

@ -1,16 +0,0 @@
#ifndef ENGINE_AUDIO_AUDIO_FORWARD_H
#define ENGINE_AUDIO_AUDIO_FORWARD_H
namespace eng {
#if defined(__ANDROID__)
class AudioOboe;
using Audio = AudioOboe;
#elif defined(__linux__)
class AudioAlsa;
using Audio = AudioAlsa;
#endif
} // namespace eng
#endif // ENGINE_AUDIO_AUDIO_FORWARD_H

View File

@ -1,4 +1,4 @@
#include "engine/audio/audio.h" #include "engine/audio/audio_mixer.h"
#include <cstring> #include <cstring>
@ -11,30 +11,33 @@ using namespace base;
namespace eng { namespace eng {
AudioBase::AudioBase() AudioMixer::AudioMixer()
: main_thread_task_runner_(TaskRunner::GetThreadLocalTaskRunner()) {} : main_thread_task_runner_(TaskRunner::GetThreadLocalTaskRunner()) {}
AudioBase::~AudioBase() = default; AudioMixer::~AudioMixer() = default;
uint64_t AudioBase::CreateResource() { uint64_t AudioMixer::CreateResource() {
uint64_t resource_id = ++last_resource_id_; uint64_t resource_id = ++last_resource_id_;
resources_[resource_id] = std::make_shared<Resource>(); resources_[resource_id] = std::make_shared<Resource>();
return resource_id; return resource_id;
} }
void AudioBase::DestroyResource(uint64_t resource_id) { void AudioMixer::DestroyResource(uint64_t resource_id) {
auto it = resources_.find(resource_id); auto it = resources_.find(resource_id);
if (it == resources_.end()) if (it == resources_.end())
return; return;
it->second->flags.fetch_or(kStopped, std::memory_order_relaxed); if (it->second->active) {
it->second->restart_cb = nullptr;
it->second->flags.fetch_or(kStopped, std::memory_order_relaxed);
}
resources_.erase(it); resources_.erase(it);
} }
void AudioBase::Play(uint64_t resource_id, void AudioMixer::Play(uint64_t resource_id,
std::shared_ptr<Sound> sound, std::shared_ptr<Sound> sound,
float amplitude, float amplitude,
bool reset_pos) { bool reset_pos) {
if (!audio_enabled_) if (!audio_enabled_)
return; return;
@ -46,15 +49,11 @@ void AudioBase::Play(uint64_t resource_id,
if (reset_pos) if (reset_pos)
it->second->flags.fetch_or(kStopped, std::memory_order_relaxed); it->second->flags.fetch_or(kStopped, std::memory_order_relaxed);
if (it->second->flags.load(std::memory_order_relaxed) & kStopped) { if (it->second->flags.load(std::memory_order_relaxed) & kStopped)
Closure ocb = std::move(it->second->end_cb); it->second->restart_cb = [&, resource_id, sound, amplitude,
SetEndCallback( reset_pos]() -> void {
resource_id, Play(resource_id, sound, amplitude, reset_pos);
[&, resource_id, sound, amplitude, reset_pos, ocb]() -> void { };
Play(resource_id, sound, amplitude, reset_pos);
SetEndCallback(resource_id, std::move(ocb));
});
}
return; return;
} }
@ -75,16 +74,18 @@ void AudioBase::Play(uint64_t resource_id,
play_list_[0].push_back(it->second); play_list_[0].push_back(it->second);
} }
void AudioBase::Stop(uint64_t resource_id) { void AudioMixer::Stop(uint64_t resource_id) {
auto it = resources_.find(resource_id); auto it = resources_.find(resource_id);
if (it == resources_.end()) if (it == resources_.end())
return; return;
if (it->second->active) if (it->second->active) {
it->second->restart_cb = nullptr;
it->second->flags.fetch_or(kStopped, std::memory_order_relaxed); it->second->flags.fetch_or(kStopped, std::memory_order_relaxed);
}
} }
void AudioBase::SetLoop(uint64_t resource_id, bool loop) { void AudioMixer::SetLoop(uint64_t resource_id, bool loop) {
auto it = resources_.find(resource_id); auto it = resources_.find(resource_id);
if (it == resources_.end()) if (it == resources_.end())
return; return;
@ -95,7 +96,7 @@ void AudioBase::SetLoop(uint64_t resource_id, bool loop) {
it->second->flags.fetch_and(~kLoop, std::memory_order_relaxed); it->second->flags.fetch_and(~kLoop, std::memory_order_relaxed);
} }
void AudioBase::SetSimulateStereo(uint64_t resource_id, bool simulate) { void AudioMixer::SetSimulateStereo(uint64_t resource_id, bool simulate) {
auto it = resources_.find(resource_id); auto it = resources_.find(resource_id);
if (it == resources_.end()) if (it == resources_.end())
return; return;
@ -106,7 +107,7 @@ void AudioBase::SetSimulateStereo(uint64_t resource_id, bool simulate) {
it->second->flags.fetch_and(~kSimulateStereo, std::memory_order_relaxed); it->second->flags.fetch_and(~kSimulateStereo, std::memory_order_relaxed);
} }
void AudioBase::SetResampleStep(uint64_t resource_id, size_t step) { void AudioMixer::SetResampleStep(uint64_t resource_id, size_t step) {
auto it = resources_.find(resource_id); auto it = resources_.find(resource_id);
if (it == resources_.end()) if (it == resources_.end())
return; return;
@ -114,7 +115,7 @@ void AudioBase::SetResampleStep(uint64_t resource_id, size_t step) {
it->second->step.store(step + 100, std::memory_order_relaxed); it->second->step.store(step + 100, std::memory_order_relaxed);
} }
void AudioBase::SetMaxAmplitude(uint64_t resource_id, float max_amplitude) { void AudioMixer::SetMaxAmplitude(uint64_t resource_id, float max_amplitude) {
auto it = resources_.find(resource_id); auto it = resources_.find(resource_id);
if (it == resources_.end()) if (it == resources_.end())
return; return;
@ -122,7 +123,7 @@ void AudioBase::SetMaxAmplitude(uint64_t resource_id, float max_amplitude) {
it->second->max_amplitude.store(max_amplitude, std::memory_order_relaxed); it->second->max_amplitude.store(max_amplitude, std::memory_order_relaxed);
} }
void AudioBase::SetAmplitudeInc(uint64_t resource_id, float amplitude_inc) { void AudioMixer::SetAmplitudeInc(uint64_t resource_id, float amplitude_inc) {
auto it = resources_.find(resource_id); auto it = resources_.find(resource_id);
if (it == resources_.end()) if (it == resources_.end())
return; return;
@ -130,7 +131,7 @@ void AudioBase::SetAmplitudeInc(uint64_t resource_id, float amplitude_inc) {
it->second->amplitude_inc.store(amplitude_inc, std::memory_order_relaxed); it->second->amplitude_inc.store(amplitude_inc, std::memory_order_relaxed);
} }
void AudioBase::SetEndCallback(uint64_t resource_id, base::Closure cb) { void AudioMixer::SetEndCallback(uint64_t resource_id, base::Closure cb) {
auto it = resources_.find(resource_id); auto it = resources_.find(resource_id);
if (it == resources_.end()) if (it == resources_.end())
return; return;
@ -138,7 +139,7 @@ void AudioBase::SetEndCallback(uint64_t resource_id, base::Closure cb) {
it->second->end_cb = std::move(cb); it->second->end_cb = std::move(cb);
} }
void AudioBase::RenderAudio(float* output_buffer, size_t num_frames) { void AudioMixer::RenderAudio(float* output_buffer, size_t num_frames) {
{ {
std::unique_lock<std::mutex> scoped_lock(lock_, std::try_to_lock); std::unique_lock<std::mutex> scoped_lock(lock_, std::try_to_lock);
if (scoped_lock) if (scoped_lock)
@ -207,20 +208,18 @@ void AudioBase::RenderAudio(float* output_buffer, size_t num_frames) {
// Remove, loop or stream if the source data is consumed // Remove, loop or stream if the source data is consumed
if (src_index >= num_samples) { if (src_index >= num_samples) {
if (!sound->is_streaming_sound()) { if (num_samples)
src_index %= num_samples; src_index %= num_samples;
if (!sound->is_streaming_sound()) {
if (!(flags & kLoop)) { if (!(flags & kLoop)) {
marked_for_removal = true; marked_for_removal = true;
break; break;
} }
} else if (!it->get()->streaming_in_progress.load( } else if (!it->get()->streaming_in_progress.load(
std::memory_order_acquire)) { std::memory_order_acquire)) {
if (num_samples)
src_index %= num_samples;
// Looping streaming sounds never return eof.
if (sound->eof()) { if (sound->eof()) {
DCHECK((flags & kLoop) == 0);
marked_for_removal = true; marked_for_removal = true;
break; break;
} }
@ -238,10 +237,9 @@ void AudioBase::RenderAudio(float* output_buffer, size_t num_frames) {
ThreadPool::Get().PostTask( ThreadPool::Get().PostTask(
HERE, HERE,
std::bind(&AudioBase::DoStream, this, *it, flags & kLoop)); std::bind(&AudioMixer::DoStream, this, *it, flags & kLoop));
} else if (num_samples) { } else if (num_samples) {
DLOG << "Buffer underrun!"; DLOG << "Mixer buffer underrun!";
src_index %= num_samples;
} }
} }
} }
@ -263,7 +261,7 @@ void AudioBase::RenderAudio(float* output_buffer, size_t num_frames) {
if ((!it->get()->sound->is_streaming_sound() || if ((!it->get()->sound->is_streaming_sound() ||
!it->get()->streaming_in_progress.load(std::memory_order_relaxed))) { !it->get()->streaming_in_progress.load(std::memory_order_relaxed))) {
main_thread_task_runner_->PostTask( main_thread_task_runner_->PostTask(
HERE, std::bind(&AudioBase::EndCallback, this, *it)); HERE, std::bind(&AudioMixer::EndCallback, this, *it));
it = end_list_.erase(it); it = end_list_.erase(it);
} else { } else {
++it; ++it;
@ -271,19 +269,20 @@ void AudioBase::RenderAudio(float* output_buffer, size_t num_frames) {
} }
} }
void AudioBase::DoStream(std::shared_ptr<Resource> resource, bool loop) { void AudioMixer::DoStream(std::shared_ptr<Resource> resource, bool loop) {
resource->sound->Stream(loop); resource->sound->Stream(loop);
// Memory barrier to ensure all memory writes become visible to the audio
// thread.
resource->streaming_in_progress.store(false, std::memory_order_release); resource->streaming_in_progress.store(false, std::memory_order_release);
} }
void AudioBase::EndCallback(std::shared_ptr<Resource> resource) { void AudioMixer::EndCallback(std::shared_ptr<Resource> resource) {
resource->active = false; resource->active = false;
if (resource->end_cb) if (resource->end_cb)
resource->end_cb(); resource->end_cb();
if (resource->restart_cb) {
resource->restart_cb();
resource->restart_cb = nullptr;
}
} }
} // namespace eng } // namespace eng

View File

@ -1,12 +1,14 @@
#ifndef ENGINE_AUDIO_AUDIO_BASE_H #ifndef ENGINE_AUDIO_AUDIO_MIXER_H
#define ENGINE_AUDIO_AUDIO_BASE_H #define ENGINE_AUDIO_AUDIO_MIXER_H
#include <atomic>
#include <list> #include <list>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <unordered_map> #include <unordered_map>
#include "base/closure.h" #include "base/closure.h"
#include "engine/audio/audio_driver_delegate.h"
namespace base { namespace base {
class TaskRunner; class TaskRunner;
@ -16,11 +18,14 @@ namespace eng {
class Sound; class Sound;
// Class representing the end-point for rendered audio. A platform specific // Mix and render audio with low overhead. A platform specific AudioDriver
// implementation is expected to periodically call RenderAudio() in a background // implementation is expected to periodically call Render() in a background
// thread. // thread.
class AudioBase { class AudioMixer : public AudioDriverDelegate {
public: public:
AudioMixer();
~AudioMixer();
uint64_t CreateResource(); uint64_t CreateResource();
void DestroyResource(uint64_t resource_id); void DestroyResource(uint64_t resource_id);
@ -39,21 +44,15 @@ class AudioBase {
void SetEnableAudio(bool enable) { audio_enabled_ = enable; } void SetEnableAudio(bool enable) { audio_enabled_ = enable; }
protected:
static constexpr int kChannelCount = 2;
AudioBase();
~AudioBase();
void RenderAudio(float* output_buffer, size_t num_frames);
private: private:
enum SampleFlags { kLoop = 1, kStopped = 2, kSimulateStereo = 4 }; enum SampleFlags { kLoop = 1, kStopped = 2, kSimulateStereo = 4 };
static constexpr int kChannelCount = 2;
struct Resource { struct Resource {
// Accessed by main thread only. // Accessed by main thread only.
bool active = false; bool active = false;
base::Closure end_cb; base::Closure end_cb;
base::Closure restart_cb;
// Initialized by main thread, used by audio thread. // Initialized by main thread, used by audio thread.
std::shared_ptr<Sound> sound; std::shared_ptr<Sound> sound;
@ -83,14 +82,17 @@ class AudioBase {
bool audio_enabled_ = true; bool audio_enabled_ = true;
// AudioDriverDelegate implementation
void RenderAudio(float* output_buffer, size_t num_frames) final;
void DoStream(std::shared_ptr<Resource> sample, bool loop); void DoStream(std::shared_ptr<Resource> sample, bool loop);
void EndCallback(std::shared_ptr<Resource> sample); void EndCallback(std::shared_ptr<Resource> sample);
AudioBase(const AudioBase&) = delete; AudioMixer(const AudioMixer&) = delete;
AudioBase& operator=(const AudioBase&) = delete; AudioMixer& operator=(const AudioMixer&) = delete;
}; };
} // namespace eng } // namespace eng
#endif // ENGINE_AUDIO_AUDIO_BASE_H #endif // ENGINE_AUDIO_AUDIO_MIXER_H

View File

@ -2,7 +2,8 @@
#include "base/log.h" #include "base/log.h"
#include "engine/animator.h" #include "engine/animator.h"
#include "engine/audio/audio.h" #include "engine/audio/audio_driver.h"
#include "engine/audio/audio_mixer.h"
#include "engine/drawable.h" #include "engine/drawable.h"
#include "engine/font.h" #include "engine/font.h"
#include "engine/game.h" #include "engine/game.h"
@ -25,11 +26,17 @@ namespace eng {
Engine* Engine::singleton = nullptr; Engine* Engine::singleton = nullptr;
Engine::Engine(Platform* platform, Renderer* renderer, Audio* audio) Engine::Engine(Platform* platform,
: platform_(platform), renderer_(renderer), audio_(audio) { Renderer* renderer,
AudioDriver* audio_driver)
: platform_(platform),
renderer_(renderer),
audio_driver_(audio_driver),
audio_mixer_{std::make_unique<AudioMixer>()} {
DCHECK(!singleton); DCHECK(!singleton);
singleton = this; singleton = this;
audio_driver_->SetDelegate(audio_mixer_.get());
renderer_->SetContextLostCB(std::bind(&Engine::ContextLost, this)); renderer_->SetContextLostCB(std::bind(&Engine::ContextLost, this));
quad_ = CreateRenderResource<Geometry>(); quad_ = CreateRenderResource<Geometry>();
@ -109,6 +116,7 @@ void Engine::Shutdown() {
game_.reset(); game_.reset();
stats_->Destory(); stats_->Destory();
textures_.clear(); textures_.clear();
shaders_.clear();
} }
void Engine::Update(float delta_time) { void Engine::Update(float delta_time) {
@ -150,14 +158,14 @@ void Engine::Draw(float frame_frac) {
} }
void Engine::LostFocus() { void Engine::LostFocus() {
audio_->Suspend(); audio_driver_->Suspend();
if (game_) if (game_)
game_->LostFocus(); game_->LostFocus();
} }
void Engine::GainedFocus(bool from_interstitial_ad) { void Engine::GainedFocus(bool from_interstitial_ad) {
audio_->Resume(); audio_driver_->Resume();
if (game_) if (game_)
game_->GainedFocus(from_interstitial_ad); game_->GainedFocus(from_interstitial_ad);
@ -277,6 +285,42 @@ void Engine::ReleaseTexture(const std::string& asset_name) {
it->second.texture->Destroy(); it->second.texture->Destroy();
} }
void Engine::LoadCustomShader(const std::string& asset_name,
const std::string& file_name) {
if (shaders_.contains(asset_name)) {
DLOG << "Shader already exists: " << asset_name;
return;
}
auto& s = shaders_[asset_name] = {CreateRenderResource<Shader>(), file_name};
auto source = std::make_unique<ShaderSource>();
if (!source->Load(file_name))
return;
s.shader->Create(std::move(source), quad_->vertex_description(),
quad_->primitive(), false);
}
Shader* Engine::GetCustomShader(const std::string& asset_name) {
auto it = shaders_.find(asset_name);
if (it == shaders_.end()) {
DLOG << "Shader not found: " << asset_name;
return nullptr;
}
return it->second.shader.get();
}
void Engine::RemoveCustomShader(const std::string& asset_name) {
auto it = shaders_.find(asset_name);
if (it == shaders_.end()) {
DLOG << "Shader not found: " << asset_name;
return;
}
shaders_.erase(it);
}
void Engine::AddInputEvent(std::unique_ptr<InputEvent> event) { void Engine::AddInputEvent(std::unique_ptr<InputEvent> event) {
if (replaying_) if (replaying_)
return; return;
@ -399,7 +443,7 @@ void Engine::SetKeepScreenOn(bool keep_screen_on) {
} }
void Engine::SetEnableAudio(bool enable) { void Engine::SetEnableAudio(bool enable) {
audio_->SetEnableAudio(enable); audio_mixer_->SetEnableAudio(enable);
} }
TextureCompressor* Engine::GetTextureCompressor(bool opacity) { TextureCompressor* Engine::GetTextureCompressor(bool opacity) {
@ -437,7 +481,7 @@ const std::string& Engine::GetSharedDataPath() const {
} }
int Engine::GetAudioHardwareSampleRate() { int Engine::GetAudioHardwareSampleRate() {
return audio_->GetHardwareSampleRate(); return audio_driver_->GetHardwareSampleRate();
} }
bool Engine::IsMobile() const { bool Engine::IsMobile() const {
@ -452,6 +496,13 @@ void Engine::ContextLost() {
RefreshImage(t.first); RefreshImage(t.first);
} }
for (auto& s : shaders_) {
auto source = std::make_unique<ShaderSource>();
if (source->Load(s.second.file_name))
s.second.shader->Create(std::move(source), quad_->vertex_description(),
quad_->primitive(), false);
}
game_->ContextLost(); game_->ContextLost();
} }

View File

@ -9,7 +9,6 @@
#include "base/random.h" #include "base/random.h"
#include "base/vecmath.h" #include "base/vecmath.h"
#include "engine/audio/audio_forward.h"
#include "engine/persistent_data.h" #include "engine/persistent_data.h"
class TextureCompressor; class TextureCompressor;
@ -17,6 +16,8 @@ class TextureCompressor;
namespace eng { namespace eng {
class Animator; class Animator;
class AudioDriver;
class AudioMixer;
class Font; class Font;
class Game; class Game;
class Drawable; class Drawable;
@ -33,7 +34,7 @@ class Engine {
public: public:
using CreateImageCB = std::function<std::unique_ptr<Image>()>; using CreateImageCB = std::function<std::unique_ptr<Image>()>;
Engine(Platform* platform, Renderer* renderer, Audio* audio); Engine(Platform* platform, Renderer* renderer, AudioDriver* audio_driver);
~Engine(); ~Engine();
static Engine& Get(); static Engine& Get();
@ -79,6 +80,11 @@ class Engine {
Texture* AcquireTexture(const std::string& asset_name); Texture* AcquireTexture(const std::string& asset_name);
void ReleaseTexture(const std::string& asset_name); void ReleaseTexture(const std::string& asset_name);
void LoadCustomShader(const std::string& asset_name,
const std::string& file_name);
Shader* GetCustomShader(const std::string& asset_name);
void RemoveCustomShader(const std::string& asset_name);
void AddInputEvent(std::unique_ptr<InputEvent> event); void AddInputEvent(std::unique_ptr<InputEvent> event);
std::unique_ptr<InputEvent> GetNextInputEvent(); std::unique_ptr<InputEvent> GetNextInputEvent();
@ -102,7 +108,7 @@ class Engine {
void SetEnableVibration(bool enable) { vibration_enabled_ = enable; } void SetEnableVibration(bool enable) { vibration_enabled_ = enable; }
Audio* GetAudio() { return audio_; } AudioMixer* GetAudioMixer() { return audio_mixer_.get(); }
// Access to the render resources. // Access to the render resources.
Geometry* GetQuad() { return quad_.get(); } Geometry* GetQuad() { return quad_.get(); }
@ -158,14 +164,19 @@ class Engine {
size_t use_count = 0; size_t use_count = 0;
}; };
// Class holding information about shader resources managed by engine.
struct ShaderResource {
std::unique_ptr<Shader> shader;
std::string file_name;
};
static Engine* singleton; static Engine* singleton;
Platform* platform_ = nullptr; Platform* platform_ = nullptr;
Renderer* renderer_ = nullptr; Renderer* renderer_ = nullptr;
AudioDriver* audio_driver_ = nullptr;
Audio* audio_ = nullptr; std::unique_ptr<AudioMixer> audio_mixer_;
std::unique_ptr<Game> game_; std::unique_ptr<Game> game_;
std::unique_ptr<Geometry> quad_; std::unique_ptr<Geometry> quad_;
@ -184,8 +195,9 @@ class Engine {
std::list<Animator*> animators_; std::list<Animator*> animators_;
// Textures mapped by asset name. // Managed render resources mapped by asset name.
std::unordered_map<std::string, TextureResource> textures_; std::unordered_map<std::string, TextureResource> textures_;
std::unordered_map<std::string, ShaderResource> shaders_;
std::unique_ptr<ImageQuad> stats_; std::unique_ptr<ImageQuad> stats_;

View File

@ -49,8 +49,8 @@ void ImageQuad::AutoScale() {
SetSize(size); SetSize(size);
} }
void ImageQuad::SetCustomShader(Shader* shader) { void ImageQuad::SetCustomShader(const std::string& asset_name) {
custom_shader_ = shader; custom_shader_ = Engine::Get().GetCustomShader(asset_name);
custom_uniforms_.clear(); custom_uniforms_.clear();
} }

View File

@ -28,7 +28,7 @@ class ImageQuad final : public Animatable {
void AutoScale(); void AutoScale();
void SetCustomShader(Shader* shader); void SetCustomShader(const std::string& asset_name);
template <typename T> template <typename T>
void SetCustomUniform(const std::string& name, T value) { void SetCustomUniform(const std::string& name, T value) {

View File

@ -2,11 +2,16 @@
#include "base/log.h" #include "base/log.h"
#include "base/task_runner.h" #include "base/task_runner.h"
#include "engine/audio/audio.h"
#include "engine/engine.h" #include "engine/engine.h"
#include "engine/renderer/opengl/renderer_opengl.h" #include "engine/renderer/opengl/renderer_opengl.h"
#include "engine/renderer/vulkan/renderer_vulkan.h" #include "engine/renderer/vulkan/renderer_vulkan.h"
#if defined(__ANDROID__)
#include "engine/audio/audio_driver_oboe.h"
#elif defined(__linux__)
#include "engine/audio/audio_driver_alsa.h"
#endif
using namespace base; using namespace base;
namespace eng { namespace eng {
@ -23,9 +28,13 @@ void Platform::InitializeCommon() {
thread_pool_.Initialize(); thread_pool_.Initialize();
TaskRunner::CreateThreadLocalTaskRunner(); TaskRunner::CreateThreadLocalTaskRunner();
audio_ = std::make_unique<Audio>(); #if defined(__ANDROID__)
if (!audio_->Initialize()) { audio_driver_ = std::make_unique<AudioDriverOboe>();
LOG << "Failed to initialize audio system."; #elif defined(__linux__)
audio_driver_ = std::make_unique<AudioDriverAlsa>();
#endif
if (!audio_driver_->Initialize()) {
LOG << "Failed to initialize audio driver.";
throw internal_error; throw internal_error;
} }
@ -41,12 +50,13 @@ void Platform::InitializeCommon() {
void Platform::ShutdownCommon() { void Platform::ShutdownCommon() {
LOG << "Shutting down platform."; LOG << "Shutting down platform.";
audio_->Shutdown(); audio_driver_->Shutdown();
renderer_->Shutdown(); renderer_->Shutdown();
} }
void Platform::RunMainLoop() { void Platform::RunMainLoop() {
engine_ = std::make_unique<Engine>(this, renderer_.get(), audio_.get()); engine_ =
std::make_unique<Engine>(this, renderer_.get(), audio_driver_.get());
if (!engine_->Initialize()) { if (!engine_->Initialize()) {
LOG << "Failed to initialize the engine."; LOG << "Failed to initialize the engine.";
throw internal_error; throw internal_error;

View File

@ -7,7 +7,6 @@
#include "base/thread_pool.h" #include "base/thread_pool.h"
#include "base/timer.h" #include "base/timer.h"
#include "engine/audio/audio_forward.h"
#if defined(__ANDROID__) #if defined(__ANDROID__)
@ -25,6 +24,7 @@ struct AInputEvent;
namespace eng { namespace eng {
class AudioDriver;
class Renderer; class Renderer;
class Engine; class Engine;
@ -80,7 +80,7 @@ class Platform {
bool has_focus_ = false; bool has_focus_ = false;
bool should_exit_ = false; bool should_exit_ = false;
std::unique_ptr<Audio> audio_; std::unique_ptr<AudioDriver> audio_driver_;
std::unique_ptr<Renderer> renderer_; std::unique_ptr<Renderer> renderer_;
std::unique_ptr<Engine> engine_; std::unique_ptr<Engine> engine_;

View File

@ -6,7 +6,6 @@
#include "base/log.h" #include "base/log.h"
#include "base/task_runner.h" #include "base/task_runner.h"
#include "engine/audio/audio.h"
#include "engine/engine.h" #include "engine/engine.h"
#include "engine/input_event.h" #include "engine/input_event.h"
#include "engine/renderer/renderer.h" #include "engine/renderer/renderer.h"

View File

@ -3,7 +3,6 @@
#include "base/log.h" #include "base/log.h"
#include "base/task_runner.h" #include "base/task_runner.h"
#include "base/vecmath.h" #include "base/vecmath.h"
#include "engine/audio/audio.h"
#include "engine/engine.h" #include "engine/engine.h"
#include "engine/input_event.h" #include "engine/input_event.h"
#include "engine/renderer/renderer.h" #include "engine/renderer/renderer.h"

View File

@ -2,7 +2,7 @@
#include "base/interpolation.h" #include "base/interpolation.h"
#include "base/log.h" #include "base/log.h"
#include "engine/audio/audio.h" #include "engine/audio/audio_mixer.h"
#include "engine/engine.h" #include "engine/engine.h"
#include "engine/sound.h" #include "engine/sound.h"
@ -11,10 +11,10 @@ using namespace base;
namespace eng { namespace eng {
SoundPlayer::SoundPlayer() SoundPlayer::SoundPlayer()
: resource_id_(Engine::Get().GetAudio()->CreateResource()) {} : resource_id_(Engine::Get().GetAudioMixer()->CreateResource()) {}
SoundPlayer::~SoundPlayer() { SoundPlayer::~SoundPlayer() {
Engine::Get().GetAudio()->DestroyResource(resource_id_); Engine::Get().GetAudioMixer()->DestroyResource(resource_id_);
} }
void SoundPlayer::SetSound(std::shared_ptr<Sound> sound) { void SoundPlayer::SetSound(std::shared_ptr<Sound> sound) {
@ -32,14 +32,14 @@ void SoundPlayer::Play(bool loop, float fade_in_duration) {
return; return;
int step = variate_ ? Engine::Get().GetRandomGenerator().Roll(3) - 2 : 0; int step = variate_ ? Engine::Get().GetRandomGenerator().Roll(3) - 2 : 0;
Engine::Get().GetAudio()->SetResampleStep(resource_id_, step * 12); Engine::Get().GetAudioMixer()->SetResampleStep(resource_id_, step * 12);
Engine::Get().GetAudio()->SetLoop(resource_id_, loop); Engine::Get().GetAudioMixer()->SetLoop(resource_id_, loop);
if (fade_in_duration > 0) if (fade_in_duration > 0)
Engine::Get().GetAudio()->SetAmplitudeInc( Engine::Get().GetAudioMixer()->SetAmplitudeInc(
resource_id_, 1.0f / (sound_->sample_rate() * fade_in_duration)); resource_id_, 1.0f / (sound_->sample_rate() * fade_in_duration));
else else
Engine::Get().GetAudio()->SetAmplitudeInc(resource_id_, 0); Engine::Get().GetAudioMixer()->SetAmplitudeInc(resource_id_, 0);
Engine::Get().GetAudio()->Play( Engine::Get().GetAudioMixer()->Play(
resource_id_, sound_, fade_in_duration > 0 ? 0 : max_amplitude_, true); resource_id_, sound_, fade_in_duration > 0 ? 0 : max_amplitude_, true);
} }
@ -48,10 +48,10 @@ void SoundPlayer::Resume(float fade_in_duration) {
return; return;
if (fade_in_duration > 0) if (fade_in_duration > 0)
Engine::Get().GetAudio()->SetAmplitudeInc( Engine::Get().GetAudioMixer()->SetAmplitudeInc(
resource_id_, 1.0f / (sound_->sample_rate() * fade_in_duration)); resource_id_, 1.0f / (sound_->sample_rate() * fade_in_duration));
Engine::Get().GetAudio()->Play(resource_id_, sound_, Engine::Get().GetAudioMixer()->Play(resource_id_, sound_,
fade_in_duration > 0 ? 0 : -1, false); fade_in_duration > 0 ? 0 : -1, false);
} }
void SoundPlayer::Stop(float fade_out_duration) { void SoundPlayer::Stop(float fade_out_duration) {
@ -59,10 +59,10 @@ void SoundPlayer::Stop(float fade_out_duration) {
return; return;
if (fade_out_duration > 0) if (fade_out_duration > 0)
Engine::Get().GetAudio()->SetAmplitudeInc( Engine::Get().GetAudioMixer()->SetAmplitudeInc(
resource_id_, -1.0f / (sound_->sample_rate() * fade_out_duration)); resource_id_, -1.0f / (sound_->sample_rate() * fade_out_duration));
else else
Engine::Get().GetAudio()->Stop(resource_id_); Engine::Get().GetAudioMixer()->Stop(resource_id_);
} }
void SoundPlayer::SetVariate(bool variate) { void SoundPlayer::SetVariate(bool variate) {
@ -70,16 +70,16 @@ void SoundPlayer::SetVariate(bool variate) {
} }
void SoundPlayer::SetSimulateStereo(bool simulate) { void SoundPlayer::SetSimulateStereo(bool simulate) {
Engine::Get().GetAudio()->SetSimulateStereo(resource_id_, simulate); Engine::Get().GetAudioMixer()->SetSimulateStereo(resource_id_, simulate);
} }
void SoundPlayer::SetMaxAplitude(float max_amplitude) { void SoundPlayer::SetMaxAplitude(float max_amplitude) {
max_amplitude_ = max_amplitude; max_amplitude_ = max_amplitude;
Engine::Get().GetAudio()->SetMaxAmplitude(resource_id_, max_amplitude); Engine::Get().GetAudioMixer()->SetMaxAmplitude(resource_id_, max_amplitude);
} }
void SoundPlayer::SetEndCallback(base::Closure cb) { void SoundPlayer::SetEndCallback(base::Closure cb) {
Engine::Get().GetAudio()->SetEndCallback(resource_id_, cb); Engine::Get().GetAudioMixer()->SetEndCallback(resource_id_, cb);
} }
} // namespace eng } // namespace eng