Compare commits

..

No commits in common. "7b637a95d90da3046b76e37eb54bae9045e3cb94" and "f6f67d7e53344c600c56f191a440445040e19029" have entirely different histories.

29 changed files with 328 additions and 353 deletions

View File

@ -49,6 +49,7 @@ project(kaliber)
add_library(kaliber SHARED
../../../src/base/collusion_test.cc
../../../src/base/log.cc
../../../src/base/sinc_resampler.cc
../../../src/base/task_runner.cc
../../../src/base/thread_pool.cc
../../../src/base/timer.cc
@ -61,10 +62,8 @@ add_library(kaliber SHARED
../../../src/demo/sky_quad.cc
../../../src/engine/animatable.cc
../../../src/engine/animator.cc
../../../src/engine/audio/audio_bus.cc
../../../src/engine/audio/audio_mixer.cc
../../../src/engine/audio/audio_sink_oboe.cc
../../../src/engine/audio/sinc_resampler.cc
../../../src/engine/drawable.cc
../../../src/engine/engine.cc
../../../src/engine/font.cc

View File

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

View File

@ -1,6 +0,0 @@
#!/bin/bash
BUILD="$1"
if [[ -z "$1" ]]; then
BUILD="debug"
fi
adb logcat | ndk-stack -sym ./app/build/intermediates/merged_native_libs/"$BUILD"/out/lib/arm64-v8a

View File

@ -78,6 +78,7 @@ objs_from_src_in = $(call objs_from_src, $(shell find $(1) -name "*.cc" -o -name
BASE_SRC := \
$(SRC_ROOT)/base/collusion_test.cc \
$(SRC_ROOT)/base/log.cc \
$(SRC_ROOT)/base/sinc_resampler.cc \
$(SRC_ROOT)/base/task_runner.cc \
$(SRC_ROOT)/base/thread_pool.cc \
$(SRC_ROOT)/base/timer.cc
@ -94,10 +95,8 @@ $(BASE_LIB): $(BASE_OBJS)
ENGINE_SRC := \
$(SRC_ROOT)/engine/animatable.cc \
$(SRC_ROOT)/engine/animator.cc \
$(SRC_ROOT)/engine/audio/audio_bus.cc \
$(SRC_ROOT)/engine/audio/audio_mixer.cc \
$(SRC_ROOT)/engine/audio/audio_sink_alsa.cc \
$(SRC_ROOT)/engine/audio/sinc_resampler.cc \
$(SRC_ROOT)/engine/drawable.cc \
$(SRC_ROOT)/engine/engine.cc \
$(SRC_ROOT)/engine/font.cc \

View File

@ -74,7 +74,7 @@
// Note: we're glossing over how the sub-sample handling works with
// |virtual_source_idx_|, etc.
#include "engine/audio/sinc_resampler.h"
#include "base/sinc_resampler.h"
#include <cmath>
#include <cstring>
@ -94,9 +94,7 @@
#include <arm_neon.h>
#endif
using namespace base;
namespace eng {
namespace base {
namespace {
@ -259,14 +257,14 @@ void SincResampler::InitializeKernel() {
for (int i = 0; i < kernel_size_; ++i) {
const int idx = i + offset_idx * kernel_size_;
const float pre_sinc =
kPiFloat * (i - kernel_size_ / 2 - subsample_offset);
base::kPiFloat * (i - kernel_size_ / 2 - subsample_offset);
kernel_pre_sinc_storage_[idx] = pre_sinc;
// Compute Blackman window, matching the offset of the sinc().
const float x = (i - subsample_offset) / kernel_size_;
const float window =
static_cast<float>(kA0 - kA1 * cos(2.0 * kPiDouble * x) +
kA2 * cos(4.0 * kPiDouble * x));
static_cast<float>(kA0 - kA1 * cos(2.0 * base::kPiDouble * x) +
kA2 * cos(4.0 * base::kPiDouble * x));
kernel_window_storage_[idx] = window;
// Compute the sinc with offset, then window the sinc() function and store
@ -547,4 +545,4 @@ float SincResampler::Convolve_NEON(const int kernel_size,
}
#endif
} // namespace eng
} // namespace base

View File

@ -1,18 +1,16 @@
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Modified for Kaliber engine.
#ifndef ENGINE_AUDIO__SINC_RESAMPLER_H
#define ENGINE_AUDIO__SINC_RESAMPLER_H
#ifndef BASE_SINC_RESAMPLER_H
#define BASE_SINC_RESAMPLER_H
#include <functional>
#include <memory>
#include "base/mem.h"
namespace eng {
namespace base {
// SincResampler is a high-quality single-channel sample-rate converter.
class SincResampler {
@ -165,12 +163,12 @@ class SincResampler {
// Contains kKernelOffsetCount kernels back-to-back, each of size
// `kernel_size_`. The kernel offsets are sub-sample shifts of a windowed sinc
// shifted from 0.0 to 1.0 sample.
base::AlignedMemPtr<float[]> kernel_storage_;
base::AlignedMemPtr<float[]> kernel_pre_sinc_storage_;
base::AlignedMemPtr<float[]> kernel_window_storage_;
AlignedMemPtr<float[]> kernel_storage_;
AlignedMemPtr<float[]> kernel_pre_sinc_storage_;
AlignedMemPtr<float[]> kernel_window_storage_;
// Data from the source is copied into this buffer for each processing pass.
base::AlignedMemPtr<float[]> input_buffer_;
AlignedMemPtr<float[]> input_buffer_;
// Stores the runtime selection of which Convolve function to use.
using ConvolveProc =
@ -186,6 +184,6 @@ class SincResampler {
float* r4_;
};
} // namespace eng
} // namespace base
#endif // ENGINE_AUDIO__SINC_RESAMPLER_H
#endif // BASE_SINC_RESAMPLER_H

View File

@ -261,15 +261,15 @@ void Demo::EnterGameOverState() {
hud_.ShowMessage("Game Over", 3);
state_ = kGameOver;
if (boss_fight_) {
music_.Resume(10);
boss_music_.Stop(10);
}
SetDelayedWork(1, [&]() -> void {
enemy_.RemoveAll();
// hud_.Hide();
SetDelayedWork(3, [&]() -> void {
if (saved_data_.root().get("music", Json::Value(true)).asBool()) {
if (boss_fight_) {
music_.Resume(10);
boss_music_.Stop(10);
}
}
wave_ = 0;
boss_fight_ = false;
EnterMenuState();
@ -396,6 +396,14 @@ void Demo::StartNextStage(bool boss) {
enemy_.PauseProgress();
enemy_.StopAllEnemyUnits();
if (boss) {
boss_music_.Play(true, 10);
music_.Stop(10);
} else if (boss_fight_) {
music_.Resume(10);
boss_music_.Stop(10);
}
SetDelayedWork(1.25f, [&, boss]() -> void {
enemy_.KillAllEnemyUnits();
@ -405,10 +413,6 @@ void Demo::StartNextStage(bool boss) {
hud_.HideProgress();
if (saved_data_.root().get("music", Json::Value(true)).asBool()) {
boss_music_.Play(true, 10);
music_.Stop(10);
}
boss_fight_ = true;
DLOG << "Boss fight.";
} else {
@ -451,13 +455,8 @@ void Demo::StartNextStage(bool boss) {
hud_.Show();
hud_.SetProgress(1);
if (boss_fight_) {
if (boss_fight_)
player_.TakeDamage(-1);
if (saved_data_.root().get("music", Json::Value(true)).asBool()) {
music_.Resume(10);
boss_music_.Stop(10);
}
}
total_enemies_ = 23.0897f * log((float)wave_ + 1.0f) - 10.0f;
last_num_enemies_killed_ = 0;

View File

@ -350,8 +350,8 @@ void Enemy::HitTarget(DamageType damage_type) {
if (target->damage_type != kDamageType_Any &&
target->damage_type != damage_type) {
// No shield until wave 8.
if (wave_ <= 8)
// No shield until wave 4.
if (wave_ <= 3)
return;
if (!target->shield_active) {

View File

@ -22,7 +22,7 @@ using namespace eng;
namespace {
constexpr char kVersionStr[] = "Version 1.0.3";
constexpr char kVersionStr[] = "Version 1.0.2";
constexpr char kMenuOption[Menu::kOption_Max][10] = {"continue", "start",
"credits", "exit"};

View File

@ -1,112 +0,0 @@
#include "engine/audio/audio_bus.h"
#include "base/log.h"
#include "engine/audio/sinc_resampler.h"
#include "engine/engine.h"
using namespace base;
namespace eng {
namespace {
template <typename T>
std::array<std::unique_ptr<T[]>, 2> Deinterleave(size_t num_channels,
size_t num_samples,
float* input_buffer) {
std::array<std::unique_ptr<T[]>, 2> channels;
if (num_channels == 1) {
// Single channel.
if constexpr (std::is_same<float, T>::value) {
channels[0] = std::make_unique<T[]>(num_samples);
memcpy(channels[0].get(), input_buffer, num_samples * sizeof(float));
} else {
channels[0] = std::make_unique<T[]>(num_samples);
for (int i = 0; i < num_samples; ++i)
channels[0].get()[i] = static_cast<T>(input_buffer[i]);
}
} else {
// Deinterleave into separate channels.
channels[0] = std::make_unique<T[]>(num_samples);
channels[1] = std::make_unique<T[]>(num_samples);
for (size_t i = 0, j = 0; i < num_samples * 2; i += 2) {
channels[0].get()[j] = static_cast<T>(input_buffer[i]);
channels[1].get()[j++] = static_cast<T>(input_buffer[i + 1]);
}
}
return channels;
}
std::unique_ptr<SincResampler> CreateResampler(int src_samle_rate,
int dst_sample_rate,
size_t num_samples) {
const double io_ratio = static_cast<double>(src_samle_rate) /
static_cast<double>(dst_sample_rate);
auto resampler = std::make_unique<SincResampler>(io_ratio, num_samples);
resampler->PrimeWithSilence();
return resampler;
}
} // namespace
AudioBus::AudioBus() = default;
AudioBus::~AudioBus() = default;
void AudioBus::SetAudioConfig(size_t num_channels, size_t sample_rate) {
num_channels_ = num_channels;
sample_rate_ = sample_rate;
}
void AudioBus::FromInterleaved(std::unique_ptr<float[]> input_buffer,
size_t samples_per_channel) {
auto channels = Deinterleave<float>(num_channels_, samples_per_channel,
input_buffer.get());
size_t hw_sample_rate = Engine::Get().GetAudioHardwareSampleRate();
if (hw_sample_rate == sample_rate_) {
// No need for resmapling.
channel_data_[0] = std::move(channels[0]);
if (num_channels_ == 2)
channel_data_[1] = std::move(channels[1]);
samples_per_channel_ = samples_per_channel;
} else {
if (!resampler_[0]) {
for (size_t i = 0; i < num_channels_; ++i) {
resampler_[i] =
CreateResampler(sample_rate_, hw_sample_rate, samples_per_channel);
}
}
size_t num_resampled_samples =
(size_t)(((float)hw_sample_rate / (float)sample_rate_) *
samples_per_channel);
DCHECK(num_resampled_samples <= (size_t)resampler_[0]->ChunkSize());
if (!channel_data_[0]) {
channel_data_[0] = std::make_unique<float[]>(num_resampled_samples);
if (num_channels_ == 2)
channel_data_[1] = std::make_unique<float[]>(num_resampled_samples);
}
samples_per_channel_ = num_resampled_samples;
// Resample to match the system sample rate.
for (size_t i = 0; i < num_channels_; ++i) {
resampler_[i]->Resample(num_resampled_samples, channel_data_[i].get(),
[&](int frames, float* destination) {
memcpy(destination, channels[i].get(),
frames * sizeof(float));
});
}
if (IsEndOfStream()) {
// We are done with the resampler.
for (size_t i = 0; i < num_channels_; ++i)
resampler_[i].reset();
}
}
}
} // namespace eng

View File

@ -1,45 +0,0 @@
#ifndef ENGINE_AUDIO_AUDIO_BUS_H
#define ENGINE_AUDIO_AUDIO_BUS_H
#include <memory>
namespace eng {
class SincResampler;
class AudioBus {
public:
AudioBus();
virtual ~AudioBus();
virtual void Stream(bool loop) = 0;
virtual void SwapBuffers() = 0;
virtual void ResetStream() = 0;
virtual bool IsEndOfStream() const = 0;
float* GetChannelData(int channel) const {
return channel_data_[channel].get();
}
size_t samples_per_channel() const { return samples_per_channel_; }
int sample_rate() const { return sample_rate_; }
int num_channels() const { return num_channels_; }
protected:
void SetAudioConfig(size_t num_channels, size_t sample_rate);
void FromInterleaved(std::unique_ptr<float[]> input_buffer,
size_t samples_per_channel);
private:
std::unique_ptr<float[]> channel_data_[2];
size_t samples_per_channel_ = 0;
size_t sample_rate_ = 0;
size_t num_channels_ = 0;
std::unique_ptr<SincResampler> resampler_[2];
};
} // namespace eng
#endif // ENGINE_AUDIO_AUDIO_BUS_H

View File

@ -5,7 +5,7 @@
#include "base/log.h"
#include "base/task_runner.h"
#include "base/thread_pool.h"
#include "engine/audio/audio_bus.h"
#include "engine/sound.h"
#if defined(__ANDROID__)
#include "engine/audio/audio_sink_oboe.h"
@ -49,7 +49,7 @@ void AudioMixer::DestroyResource(uint64_t resource_id) {
}
void AudioMixer::Play(uint64_t resource_id,
std::shared_ptr<AudioBus> sound,
std::shared_ptr<Sound> sound,
float amplitude,
bool reset_pos) {
if (!audio_enabled_)
@ -76,8 +76,6 @@ void AudioMixer::Play(uint64_t resource_id,
it->second->src_index = 0;
it->second->accumulator = 0;
sound->ResetStream();
} else if (it->second->src_index >= sound->samples_per_channel()) {
return;
}
it->second->active = true;
@ -163,7 +161,7 @@ void AudioMixer::Resume() {
audio_sink_->Resume();
}
size_t AudioMixer::GetHardwareSampleRate() {
int AudioMixer::GetHardwareSampleRate() {
return audio_sink_->GetHardwareSampleRate();
}
@ -184,12 +182,11 @@ void AudioMixer::RenderAudio(float* output_buffer, size_t num_frames) {
if (flags & kStopped) {
marked_for_removal = true;
} else {
const float* src[2] = {sound->GetChannelData(0),
sound->GetChannelData(1)};
const float* src[2] = {sound->GetBuffer(0), sound->GetBuffer(1)};
if (!src[1])
src[1] = src[0]; // mono.
size_t num_samples = sound->samples_per_channel();
size_t num_samples = sound->GetNumSamples();
size_t src_index = it->get()->src_index;
size_t step = it->get()->step.load(std::memory_order_relaxed);
size_t accumulator = it->get()->accumulator;
@ -198,59 +195,71 @@ void AudioMixer::RenderAudio(float* output_buffer, size_t num_frames) {
it->get()->amplitude_inc.load(std::memory_order_relaxed);
float max_amplitude =
it->get()->max_amplitude.load(std::memory_order_relaxed);
size_t channel_offset =
(flags & kSimulateStereo) ? sound->sample_rate() / 10 : 0;
DCHECK(num_samples > 0);
size_t channel_offset =
(flags & kSimulateStereo) && !sound->is_streaming_sound()
? sound->sample_rate() / 10
: 0;
DCHECK(num_samples || sound->is_streaming_sound());
for (size_t i = 0; i < num_frames * kChannelCount;) {
// Mix the 1st channel.
output_buffer[i++] += src[0][src_index] * amplitude;
if (num_samples) {
// Mix the 1st channel.
output_buffer[i++] += src[0][src_index] * amplitude;
// Mix the 2nd channel. Offset the source index for stereo simulation.
size_t ind = channel_offset + src_index;
if (ind < num_samples)
output_buffer[i++] += src[1][ind] * amplitude;
else if (flags & kLoop)
output_buffer[i++] += src[1][ind % num_samples] * amplitude;
else
i++;
// Mix the 2nd channel. Offset the source index for stereo simulation.
size_t ind = channel_offset + src_index;
if (ind < num_samples)
output_buffer[i++] += src[1][ind] * amplitude;
else if (flags & kLoop)
output_buffer[i++] += src[1][ind % num_samples] * amplitude;
else
i++;
// Apply amplitude modification.
amplitude += amplitude_inc;
if (amplitude <= 0) {
marked_for_removal = true;
break;
} else if (amplitude > max_amplitude) {
amplitude = max_amplitude;
// Apply amplitude modification.
amplitude += amplitude_inc;
if (amplitude <= 0) {
marked_for_removal = true;
break;
} else if (amplitude > max_amplitude) {
amplitude = max_amplitude;
}
// Advance source index. Apply basic resampling for variations.
accumulator += step;
src_index += accumulator / 100;
accumulator %= 100;
}
// Advance source index. Apply basic resampling for variations.
accumulator += step;
src_index += accumulator / 100;
accumulator %= 100;
// Remove, loop or stream if the source data is consumed
if (src_index >= num_samples) {
src_index %= num_samples;
if (num_samples)
src_index %= num_samples;
if (sound->IsEndOfStream()) {
marked_for_removal = true;
break;
}
if (!sound->is_streaming_sound()) {
if (!(flags & kLoop)) {
marked_for_removal = true;
break;
}
} else if (!it->get()->streaming_in_progress.load(
std::memory_order_acquire)) {
if (sound->eof()) {
DCHECK((flags & kLoop) == 0);
marked_for_removal = true;
break;
}
if (!it->get()->streaming_in_progress.load(
std::memory_order_acquire)) {
it->get()->streaming_in_progress.store(true,
std::memory_order_relaxed);
// Swap buffers and start streaming in background.
sound->SwapBuffers();
src[0] = sound->GetChannelData(0);
src[1] = sound->GetChannelData(1);
src[0] = sound->GetBuffer(0);
src[1] = sound->GetBuffer(1);
if (!src[1])
src[1] = src[0]; // mono.
num_samples = sound->samples_per_channel();
num_samples = sound->GetNumSamples();
ThreadPool::Get().PostTask(
HERE,
@ -275,7 +284,8 @@ void AudioMixer::RenderAudio(float* output_buffer, size_t num_frames) {
}
for (auto it = end_list_.begin(); it != end_list_.end();) {
if (!it->get()->streaming_in_progress.load(std::memory_order_relaxed)) {
if ((!it->get()->sound->is_streaming_sound() ||
!it->get()->streaming_in_progress.load(std::memory_order_relaxed))) {
main_thread_task_runner_->PostTask(
HERE, std::bind(&AudioMixer::EndCallback, this, *it));
it = end_list_.erase(it);

View File

@ -17,7 +17,7 @@ class TaskRunner;
namespace eng {
class AudioSink;
class AudioBus;
class Sound;
// Mix and render audio with low overhead. A platform specific AudioSink
// implementation is expected to periodically call RenderAudio() in a background
@ -31,7 +31,7 @@ class AudioMixer : public AudioSinkDelegate {
void DestroyResource(uint64_t resource_id);
void Play(uint64_t resource_id,
std::shared_ptr<AudioBus> sound,
std::shared_ptr<Sound> sound,
float amplitude,
bool reset_pos);
void Stop(uint64_t resource_id);
@ -48,7 +48,7 @@ class AudioMixer : public AudioSinkDelegate {
void Suspend();
void Resume();
size_t GetHardwareSampleRate();
int GetHardwareSampleRate();
private:
enum SampleFlags { kLoop = 1, kStopped = 2, kSimulateStereo = 4 };
@ -62,7 +62,7 @@ class AudioMixer : public AudioSinkDelegate {
base::Closure restart_cb;
// Initialized by main thread, used by audio thread.
std::shared_ptr<AudioBus> sound;
std::shared_ptr<Sound> sound;
size_t src_index = 0;
size_t accumulator = 0;
float amplitude = 1.0f;

View File

@ -15,7 +15,7 @@ class AudioSink {
virtual void Suspend() = 0;
virtual void Resume() = 0;
virtual size_t GetHardwareSampleRate() = 0;
virtual int GetHardwareSampleRate() = 0;
private:
AudioSink(const AudioSink&) = delete;

View File

@ -152,7 +152,7 @@ void AudioSinkAlsa::Resume() {
suspend_audio_thread_.store(false, std::memory_order_relaxed);
}
size_t AudioSinkAlsa::GetHardwareSampleRate() {
int AudioSinkAlsa::GetHardwareSampleRate() {
return sample_rate_;
}

View File

@ -22,7 +22,7 @@ class AudioSinkAlsa final : public AudioSink {
void Suspend() final;
void Resume() final;
size_t GetHardwareSampleRate() final;
int GetHardwareSampleRate() final;
private:
// Handle for the PCM device.
@ -33,7 +33,7 @@ class AudioSinkAlsa final : public AudioSink {
std::atomic<bool> suspend_audio_thread_{false};
size_t num_channels_ = 0;
size_t sample_rate_ = 0;
int sample_rate_ = 0;
size_t period_size_ = 0;
AudioSinkDelegate* delegate_ = nullptr;

View File

@ -29,7 +29,7 @@ void AudioSinkOboe::Resume() {
stream_->start();
}
size_t AudioSinkOboe::GetHardwareSampleRate() {
int AudioSinkOboe::GetHardwareSampleRate() {
return stream_->getSampleRate();
}

View File

@ -22,7 +22,7 @@ class AudioSinkOboe final : public AudioSink {
void Suspend() final;
void Resume() final;
size_t GetHardwareSampleRate() final;
int GetHardwareSampleRate() final;
private:
class StreamCallback final : public oboe::AudioStreamCallback {

View File

@ -22,6 +22,8 @@
#include "engine/shader_source.h"
#include "third_party/texture_compressor/texture_compressor.h"
#define USE_VULKAN_RENDERER 1
using namespace base;
namespace eng {
@ -73,17 +75,6 @@ void Engine::Run() {
float frame_frac = 0.0f;
for (;;) {
TaskRunner::GetThreadLocalTaskRunner()->SingleConsumerRun();
platform_->Update();
if (platform_->should_exit())
return;
if (!renderer_->IsInitialzed()) {
timer_.Reset();
continue;
}
Draw(frame_frac);
// Accumulate time.
@ -92,8 +83,13 @@ void Engine::Run() {
// Subdivide the frame time using fixed time steps.
while (accumulator >= time_step_) {
TaskRunner::GetThreadLocalTaskRunner()->SingleConsumerRun();
platform_->Update();
Update(time_step_);
accumulator -= time_step_;
if (platform_->should_exit())
return;
};
// Calculate frame fraction from remainder of the frame time.
@ -474,7 +470,7 @@ const std::string& Engine::GetSharedDataPath() const {
return platform_->GetSharedDataPath();
}
size_t Engine::GetAudioHardwareSampleRate() {
int Engine::GetAudioHardwareSampleRate() {
return audio_mixer_->GetHardwareSampleRate();
}
@ -523,13 +519,13 @@ void Engine::AddInputEvent(std::unique_ptr<InputEvent> event) {
case InputEvent::kDragEnd:
if (((GetScreenSize() / 2) * 0.9f - event->GetVector()).Length() <=
0.25f) {
SetStatsVisible(!stats_->IsVisible());
SetSatsVisible(!stats_->IsVisible());
// TODO: Enqueue DragCancel so we can consume this event.
}
break;
case InputEvent::kKeyPress:
if (event->GetKeyPress() == 's') {
SetStatsVisible(!stats_->IsVisible());
SetSatsVisible(!stats_->IsVisible());
// Consume event.
return;
}
@ -623,7 +619,7 @@ void Engine::ContextLost() {
game_->ContextLost();
}
void Engine::SetStatsVisible(bool visible) {
void Engine::SetSatsVisible(bool visible) {
stats_->SetVisible(visible);
if (visible)
stats_->Create("stats_tex");

View File

@ -137,7 +137,7 @@ class Engine : public PlatformObserver {
const std::string& GetSharedDataPath() const;
size_t GetAudioHardwareSampleRate();
int GetAudioHardwareSampleRate();
bool IsMobile() const;
@ -199,6 +199,7 @@ class Engine : public PlatformObserver {
int fps_ = 0;
float seconds_accumulated_ = 0.0f;
float time_step_ = 1.0f / 60.0f;
size_t tick_ = 0;
@ -234,7 +235,7 @@ class Engine : public PlatformObserver {
void ContextLost();
void SetStatsVisible(bool visible);
void SetSatsVisible(bool visible);
std::unique_ptr<Image> PrintStats();
Engine(const Engine&) = delete;

View File

@ -53,8 +53,6 @@ RendererOpenGL::~RendererOpenGL() {
}
void RendererOpenGL::Shutdown() {
is_initialized_ = false;
#ifdef THREADED_RENDERING
if (terminate_render_thread_)
return;
@ -315,8 +313,6 @@ bool RendererOpenGL::InitCommon() {
glClearColor(0, 0, 0, 1);
is_initialized_ = true;
return true;
}

View File

@ -40,11 +40,9 @@ class RendererOpenGL final : public Renderer {
RendererOpenGL(base::Closure context_lost_cb);
~RendererOpenGL() final;
bool Initialize(Platform* platform) final;
virtual bool Initialize(Platform* platform) final;
void Shutdown() final;
bool IsInitialzed() const final { return is_initialized_; }
uint64_t CreateGeometry(std::unique_ptr<Mesh> mesh) final;
void DestroyGeometry(uint64_t resource_id) final;
void Draw(uint64_t resource_id) final;
@ -126,8 +124,6 @@ class RendererOpenGL final : public Renderer {
bool vertex_array_objects_ = false;
bool npot_ = false;
bool is_initialized_ = false;
#ifdef THREADED_RENDERING
// Global commands are independent from frames and guaranteed to be processed.
std::deque<std::unique_ptr<RenderCommand>> global_commands_;

View File

@ -26,8 +26,6 @@ class Renderer {
virtual bool Initialize(Platform* platform) = 0;
virtual void Shutdown() = 0;
virtual bool IsInitialzed() const = 0;
virtual uint64_t CreateGeometry(std::unique_ptr<Mesh> mesh) = 0;
virtual void DestroyGeometry(uint64_t resource_id) = 0;
virtual void Draw(uint64_t resource_id) = 0;

View File

@ -907,7 +907,7 @@ bool RendererVulkan::InitializeInternal() {
}
// In this simple engine we use only one descriptor set layout that is for
// textures. We use push constants for everything else.
// textures. We use push contants for everything else.
VkDescriptorSetLayoutBinding ds_layout_binding;
ds_layout_binding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
ds_layout_binding.descriptorCount = 1;

View File

@ -23,11 +23,9 @@ class RendererVulkan final : public Renderer {
RendererVulkan(base::Closure context_lost_cb);
~RendererVulkan() final;
bool Initialize(Platform* platform) final;
virtual bool Initialize(Platform* platform) final;
void Shutdown() final;
bool IsInitialzed() const final { return device_ != VK_NULL_HANDLE; }
uint64_t CreateGeometry(std::unique_ptr<Mesh> mesh) final;
void DestroyGeometry(uint64_t resource_id) final;
void Draw(uint64_t resource_id) final;

View File

@ -3,6 +3,7 @@
#include <array>
#include "base/log.h"
#include "base/sinc_resampler.h"
#define MINIMP3_ONLY_MP3
#define MINIMP3_ONLY_SIMD
#define MINIMP3_FLOAT_OUTPUT
@ -14,9 +15,52 @@
using namespace base;
namespace eng {
namespace {
constexpr size_t kMaxSamplesPerChunk = MINIMP3_MAX_SAMPLES_PER_FRAME;
constexpr size_t kMaxSamplesPerChunk = MINIMP3_MAX_SAMPLES_PER_FRAME * 10;
template <typename T>
std::array<std::unique_ptr<T[]>, 2> Deinterleave(size_t num_channels,
size_t num_samples,
float* input_buffer) {
std::array<std::unique_ptr<T[]>, 2> channels;
if (num_channels == 1) {
// Single channel.
if constexpr (std::is_same<float, T>::value) {
channels[0] = std::make_unique<T[]>(num_samples);
memcpy(channels[0].get(), input_buffer, num_samples * sizeof(float));
} else {
channels[0] = std::make_unique<T[]>(num_samples);
for (int i = 0; i < num_samples; ++i)
channels[0].get()[i] = static_cast<T>(input_buffer[i]);
}
} else {
// Deinterleave into separate channels.
channels[0] = std::make_unique<T[]>(num_samples);
channels[1] = std::make_unique<T[]>(num_samples);
for (size_t i = 0, j = 0; i < num_samples * 2; i += 2) {
channels[0].get()[j] = static_cast<T>(input_buffer[i]);
channels[1].get()[j++] = static_cast<T>(input_buffer[i + 1]);
}
}
return channels;
}
std::unique_ptr<SincResampler> CreateResampler(int src_samle_rate,
int dst_sample_rate,
size_t num_samples) {
const double io_ratio = static_cast<double>(src_samle_rate) /
static_cast<double>(dst_sample_rate);
auto resampler = std::make_unique<SincResampler>(io_ratio, num_samples);
resampler->PrimeWithSilence();
return resampler;
}
} // namespace
namespace eng {
Sound::Sound() = default;
@ -48,98 +92,164 @@ bool Sound::Load(const std::string& file_name, bool stream) {
return false;
}
LOG << (stream ? "Streaming " : "Loaded ") << file_name << ". "
is_streaming_sound_ = stream;
LOG << (is_streaming_sound_ ? "Streaming " : "Loaded ") << file_name << ". "
<< mp3_dec_->samples << " samples, " << mp3_dec_->info.channels
<< " channels, " << mp3_dec_->info.hz << " hz, "
<< "layer " << mp3_dec_->info.layer << ", "
<< "avg_bitrate_kbps " << mp3_dec_->info.bitrate_kbps;
SetAudioConfig(mp3_dec_->info.channels, mp3_dec_->info.hz);
num_channels_ = mp3_dec_->info.channels;
sample_rate_ = mp3_dec_->info.hz;
num_samples_back_ = cur_sample_back_ = 0;
eof_ = false;
samples_per_channel_ = 0;
eos_ = false;
DCHECK(num_channels_ > 0 && num_channels_ <= 2);
DCHECK(mp3_dec_->info.channels > 0 && mp3_dec_->info.channels <= 2);
hw_sample_rate_ = Engine::Get().GetAudioHardwareSampleRate();
if (stream) {
// Fill up the buffer.
StreamInternal(kMaxSamplesPerChunk * mp3_dec_->info.channels, false);
if (is_streaming_sound_) {
if (sample_rate_ != hw_sample_rate_) {
for (int i = 0; i < mp3_dec_->info.channels; ++i) {
resampler_[i] =
CreateResampler(sample_rate_, hw_sample_rate_,
(int)kMaxSamplesPerChunk / mp3_dec_->info.channels);
}
}
// Fill up buffers.
StreamInternal(kMaxSamplesPerChunk, false);
SwapBuffers();
StreamInternal(kMaxSamplesPerChunk * mp3_dec_->info.channels, false);
StreamInternal(kMaxSamplesPerChunk, false);
// No need to stream if sample fits into the buffer.
if (eos_)
stream = false;
if (eof_)
is_streaming_sound_ = false;
} else {
if (sample_rate_ != hw_sample_rate_) {
for (int i = 0; i < mp3_dec_->info.channels; ++i) {
resampler_[i] =
CreateResampler(sample_rate_, hw_sample_rate_,
mp3_dec_->samples / mp3_dec_->info.channels);
}
}
// Decode entire file.
StreamInternal(mp3_dec_->samples, false);
SwapBuffers();
eos_ = true;
eof_ = true;
}
if (!stream) {
if (!is_streaming_sound_) {
// We are done with decoding.
encoded_data_.reset();
for (int i = 0; i < mp3_dec_->info.channels; ++i)
resampler_[i].reset();
mp3dec_ex_close(mp3_dec_.get());
mp3_dec_.reset();
}
read_pos_ = 0;
return true;
}
void Sound::Stream(bool loop) {
DCHECK(mp3_dec_);
StreamInternal(kMaxSamplesPerChunk * mp3_dec_->info.channels, loop);
bool Sound::Stream(bool loop) {
DCHECK(is_streaming_sound_);
return StreamInternal(kMaxSamplesPerChunk, loop);
}
void Sound::SwapBuffers() {
FromInterleaved(std::move(interleaved_data_), samples_per_channel_);
samples_per_channel_ = 0;
front_buffer_[0].swap(back_buffer_[0]);
front_buffer_[1].swap(back_buffer_[1]);
cur_sample_front_ = cur_sample_back_;
num_samples_front_ = num_samples_back_;
num_samples_back_ = 0;
}
void Sound::ResetStream() {
if (mp3_dec_ && read_pos_ != 0) {
// Seek to 0 and stream.
if (is_streaming_sound_ && cur_sample_front_ != 0) {
// Seek to 0 and ivalidate decoded data.
mp3dec_ex_seek(mp3_dec_.get(), 0);
eos_ = false;
StreamInternal(kMaxSamplesPerChunk * mp3_dec_->info.channels, false);
SwapBuffers();
StreamInternal(kMaxSamplesPerChunk * mp3_dec_->info.channels, false);
read_pos_ = 0;
eof_ = false;
num_samples_back_ = num_samples_front_ = 0;
cur_sample_front_ = cur_sample_back_ = 0;
}
}
void Sound::StreamInternal(size_t num_samples, bool loop) {
float* Sound::GetBuffer(int channel) const {
return front_buffer_[channel].get();
}
bool Sound::StreamInternal(size_t num_samples, bool loop) {
auto buffer = std::make_unique<float[]>(num_samples);
size_t samples_read_per_channel = 0;
read_pos_ = mp3_dec_->cur_sample;
cur_sample_back_ = mp3_dec_->cur_sample;
for (;;) {
size_t samples_read =
mp3dec_ex_read(mp3_dec_.get(), buffer.get(), num_samples);
if (samples_read != num_samples && mp3_dec_->last_error) {
LOG << "mp3 decode error: " << mp3_dec_->last_error;
break;
eof_ = true;
return false;
}
if (samples_read == 0 && loop) {
samples_read_per_channel = samples_read / mp3_dec_->info.channels;
if (!samples_read_per_channel && loop) {
mp3dec_ex_seek(mp3_dec_.get(), 0);
loop = false;
continue;
}
samples_read_per_channel = samples_read / mp3_dec_->info.channels;
break;
}
if (samples_read_per_channel > 0) {
interleaved_data_ = std::move(buffer);
samples_per_channel_ = samples_read_per_channel;
if (samples_read_per_channel) {
Preprocess(std::move(buffer), samples_read_per_channel);
} else {
samples_per_channel_ = 0;
eos_ = true;
num_samples_back_ = 0;
eof_ = true;
}
return true;
}
void Sound::Preprocess(std::unique_ptr<float[]> input_buffer,
size_t samples_per_channel) {
auto channels = Deinterleave<float>(num_channels_, samples_per_channel,
input_buffer.get());
if (hw_sample_rate_ == sample_rate_) {
// No need for resmapling.
back_buffer_[0] = std::move(channels[0]);
if (num_channels_ == 2)
back_buffer_[1] = std::move(channels[1]);
num_samples_back_ = samples_per_channel;
} else {
size_t num_resampled_samples = resampler_[0]->ChunkSize();
DCHECK(num_resampled_samples ==
(size_t)(((float)hw_sample_rate_ / (float)sample_rate_) *
samples_per_channel));
if (!back_buffer_[0]) {
if (max_samples_ < num_resampled_samples)
max_samples_ = num_resampled_samples;
back_buffer_[0] = std::make_unique<float[]>(max_samples_);
if (num_channels_ == 2)
back_buffer_[1] = std::make_unique<float[]>(max_samples_);
}
num_samples_back_ = num_resampled_samples;
// Resample to match the system sample rate.
for (size_t i = 0; i < num_channels_; ++i) {
resampler_[i]->Resample(num_resampled_samples, back_buffer_[i].get(),
[&](int frames, float* destination) {
memcpy(destination, channels[i].get(),
frames * sizeof(float));
});
}
}
}

View File

@ -5,40 +5,73 @@
#include <memory>
#include <string>
#include "engine/audio/audio_bus.h"
typedef struct mp3dec_ex mp3dec_ex_t;
namespace base {
class SincResampler;
} // namespace base
namespace eng {
// Class for streaming and non-streaming sound assets. Loads and decodes mp3
// files. Resamples the decoded audio to match the system sample rate if
// necessary. Non-streaming sounds Can be shared between multiple audio
// resources and played simultaneously.
class Sound final : public AudioBus {
class Sound {
public:
Sound();
~Sound() final;
~Sound();
bool Load(const std::string& file_name, bool stream);
// AudioBus interface
void Stream(bool loop) final;
void SwapBuffers() final;
void ResetStream() final;
bool IsEndOfStream() const final { return eos_; }
bool Stream(bool loop);
void SwapBuffers();
void ResetStream();
float* GetBuffer(int channel) const;
size_t GetNumSamples() const { return num_samples_front_; }
size_t num_channels() const { return num_channels_; }
int sample_rate() const { return sample_rate_; }
bool is_streaming_sound() const { return is_streaming_sound_; }
bool eof() const { return eof_; }
private:
// Buffer holding decoded audio.
std::unique_ptr<float[]> interleaved_data_;
size_t samples_per_channel_ = 0;
std::unique_ptr<float[]> back_buffer_[2];
std::unique_ptr<float[]> front_buffer_[2];
size_t num_samples_back_ = 0;
size_t num_samples_front_ = 0;
size_t max_samples_ = 0;
size_t cur_sample_front_ = 0;
size_t cur_sample_back_ = 0;
size_t num_channels_ = 0;
int sample_rate_ = 0;
int hw_sample_rate_ = 0;
std::unique_ptr<char[]> encoded_data_;
std::unique_ptr<mp3dec_ex_t> mp3_dec_;
uint64_t read_pos_ = 0;
bool eos_ = false;
void StreamInternal(size_t num_samples, bool loop);
std::unique_ptr<mp3dec_ex_t> mp3_dec_;
std::unique_ptr<base::SincResampler> resampler_[2];
bool eof_ = false;
bool is_streaming_sound_ = false;
bool StreamInternal(size_t num_samples, bool loop);
void Preprocess(std::unique_ptr<float[]> input_buffer,
size_t samples_per_channel);
};
} // namespace eng

View File

@ -2,9 +2,9 @@
#include "base/interpolation.h"
#include "base/log.h"
#include "engine/audio/audio_bus.h"
#include "engine/audio/audio_mixer.h"
#include "engine/engine.h"
#include "engine/sound.h"
using namespace base;
@ -17,10 +17,16 @@ SoundPlayer::~SoundPlayer() {
Engine::Get().GetAudioMixer()->DestroyResource(resource_id_);
}
void SoundPlayer::SetSound(std::shared_ptr<AudioBus> sound) {
void SoundPlayer::SetSound(std::shared_ptr<Sound> sound) {
CHECK(!sound->is_streaming_sound()) << "Streaming sound cannot be shared.";
sound_ = sound;
}
void SoundPlayer::SetSound(std::unique_ptr<Sound> sound) {
sound_ = std::move(sound);
}
void SoundPlayer::Play(bool loop, float fade_in_duration) {
if (!sound_)
return;

View File

@ -7,14 +7,15 @@
namespace eng {
class AudioBus;
class Sound;
class SoundPlayer {
public:
SoundPlayer();
~SoundPlayer();
void SetSound(std::shared_ptr<AudioBus> sound);
void SetSound(std::shared_ptr<Sound> sound);
void SetSound(std::unique_ptr<Sound> sound);
void Play(bool loop, float fade_in_duration = 0);
@ -36,7 +37,7 @@ class SoundPlayer {
private:
uint64_t resource_id_ = 0;
std::shared_ptr<AudioBus> sound_;
std::shared_ptr<Sound> sound_;
float max_amplitude_ = 1.0f;