diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..e59a580
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,7 @@
+BasedOnStyle: Chromium
+Standard: Cpp11
+
+MacroBlockBegin: "^\
+RENDER_COMMAND_BEGIN$"
+MacroBlockEnd: "^\
+RENDER_COMMAND_END$"
diff --git a/.gitignore b/.gitignore
index 9add192..c12247a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@ build/android/build
build/linux/gltest_x86_64_debug
build/linux/gltest_x86_64_release
build/linux/obj
+out
diff --git a/.gn b/.gn
new file mode 100644
index 0000000..4910cd7
--- /dev/null
+++ b/.gn
@@ -0,0 +1,2 @@
+# The location of the build configuration file.
+buildconfig = "//build/gn/BUILDCONFIG.gn"
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..be96ccc
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,20 @@
+dist: trusty
+
+language: cpp
+
+os: linux
+
+compiler:
+ - gcc
+ - clang
+
+env:
+ - BUILD_CONFIGURATION=debug
+ - BUILD_CONFIGURATION=release
+
+install:
+ - source build/travis.sh
+
+script:
+ - cd build/linux
+ - make BUILD=$BUILD_CONFIGURATION
diff --git a/BUILD.gn b/BUILD.gn
new file mode 100644
index 0000000..8d7591b
--- /dev/null
+++ b/BUILD.gn
@@ -0,0 +1,5 @@
+group("kaliber") {
+ deps = [
+ "//src/demo",
+ ]
+}
diff --git a/README.md b/README.md
index 10ab2d9..71ca0a5 100644
--- a/README.md
+++ b/README.md
@@ -1,17 +1,27 @@
-A simple, cross-platform, multi-threaded 2D game engine with OpenGL renderer written in modern
-C++. Supports Linux and Android platforms.
-
-Build for Linux (gcc or clang):
-
-cd build/linux
+A simple, cross-platform 2D game engine with OpenGL renderer. Supports Linux and
+Android (lolipop+) platforms.
+#### Building the demo
+Linux:
+```text
+cd build/linux
make
-
-Build for Android:
-
-cd build/android
+```
+Android:
+```text
+cd build/android
./gradlew :app:assembleRelease
-
-Build for Android and install (debug):
-
-cd build/android
-./gradlew :app:installDebug
+```
+GN (linux only for now):
+```text
+gn gen --args='is_debug=false' out/release
+ninja -C out/release
+```
+#### Third-party libraries:
+[glew](https://github.com/nigels-com/glew),
+[jsoncpp](https://github.com/open-source-parsers/jsoncpp),
+[minimp3](https://github.com/lieff/minimp3),
+[oboe](https://github.com/google/oboe),
+[r8brain-free-src](https://github.com/avaneev/r8brain-free-src),
+[stb](https://github.com/nothings/stb),
+[texture-compressor](https://github.com/auygun/kaliber/tree/master/src/third_party/texture_compressor),
+[minizip](https://github.com/madler/zlib/tree/master/contrib/minizip)
diff --git a/build/README.md b/build/README.md
new file mode 100644
index 0000000..88ac312
--- /dev/null
+++ b/build/README.md
@@ -0,0 +1 @@
+[![Build Status](https://travis-ci.org/auygun/kaliber.svg?branch=master)](https://travis-ci.org/auygun/kaliber)
diff --git a/build/android/app/CMakeLists.txt b/build/android/app/CMakeLists.txt
index 0560709..458d52a 100644
--- a/build/android/app/CMakeLists.txt
+++ b/build/android/app/CMakeLists.txt
@@ -20,9 +20,10 @@ cmake_minimum_required(VERSION 3.4.1)
set (OBOE_DIR ../../../src/third_party/oboe)
add_subdirectory(${OBOE_DIR} ./oboe-bin)
-
-include(AndroidNdkModules)
-android_ndk_import_module_cpufeatures()
+# cpufeatures
+include_directories(${ANDROID_NDK}/sources/android/cpufeatures)
+add_library(cpufeatures STATIC
+ ${ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c)
# build native_app_glue as a static lib
if (CMAKE_BUILD_TYPE MATCHES Debug)
@@ -66,6 +67,7 @@ add_library(native-activity SHARED
../../../src/engine/audio/audio_base.cc
../../../src/engine/audio/audio_oboe.cc
../../../src/engine/audio/audio_resource.cc
+ ../../../src/engine/drawable.cc
../../../src/engine/engine.cc
../../../src/engine/font.cc
../../../src/engine/image_quad.cc
@@ -74,7 +76,7 @@ add_library(native-activity SHARED
../../../src/engine/platform/asset_file_android.cc
../../../src/engine/platform/asset_file.cc
../../../src/engine/platform/platform_android.cc
- ../../../src/engine/platform/platform.cc
+ ../../../src/engine/platform/platform_base.cc
../../../src/engine/renderer/geometry.cc
../../../src/engine/renderer/render_command.cc
../../../src/engine/renderer/render_resource.cc
@@ -87,7 +89,6 @@ add_library(native-activity SHARED
../../../src/engine/solid_quad.cc
../../../src/engine/sound_player.cc
../../../src/engine/sound.cc
- ../../../src/third_party/android/gestureDetector.cpp
../../../src/third_party/android/gl3stub.c
../../../src/third_party/android/GLContext.cpp
../../../src/third_party/jsoncpp/jsoncpp.cc
diff --git a/build/android/app/src/main/AndroidManifest.xml b/build/android/app/src/main/AndroidManifest.xml
index 4f7f4c1..11dc6af 100644
--- a/build/android/app/src/main/AndroidManifest.xml
+++ b/build/android/app/src/main/AndroidManifest.xml
@@ -5,6 +5,8 @@
android:versionCode="1"
android:versionName="1.0">
+
+
+#include
+#include
+
+#ifdef _DEBUG
+
+#define HERE std::make_tuple(__func__, __FILE__, __LINE__)
+
+// Helper for logging location info, e.g. LOG << LOCATION(from)
+#define LOCATION(from) \
+ std::get<0>(from) << "() [" << [](const char* path) -> std::string { \
+ std::string file_name(path); \
+ size_t last_slash_pos = file_name.find_last_of("\\/"); \
+ if (last_slash_pos != std::string::npos) \
+ file_name = file_name.substr(last_slash_pos + 1); \
+ return file_name; \
+ }(std::get<1>(from)) << ":" \
+ << std::get<2>(from) << "]"
+
+#else
+
+#define HERE nullptr
+#define LOCATION(from) ""
+
+#endif
namespace base {
using Closure = std::function;
+#ifdef _DEBUG
+
+// Provides location info (function name, file name and line number) where of a
+// Closure was constructed.
+using Location = std::tuple;
+
+#else
+
+using Location = std::nullptr_t;
+
+#endif
+
} // namespace base
#endif // CLOSURE_H
diff --git a/src/base/concurrent_stack.h b/src/base/concurrent_stack.h
new file mode 100644
index 0000000..f244e3c
--- /dev/null
+++ b/src/base/concurrent_stack.h
@@ -0,0 +1,91 @@
+#ifndef CONCURRENT_STACK_H
+#define CONCURRENT_STACK_H
+
+#include
+#include
+
+namespace base {
+
+// Lock-free concurrent stack. All methods are thread-safe and can be called on
+// any thread.
+template
+class ConcurrentStack {
+ struct Node {
+ T item;
+ Node* next = nullptr;
+ Node(T item) : item(std::move(item)) {}
+ };
+
+ public:
+ ConcurrentStack() = default;
+
+ ConcurrentStack(ConcurrentStack&& other) { MoveAssign(std::move(other)); }
+
+ ~ConcurrentStack() { Clear(); }
+
+ ConcurrentStack& operator=(ConcurrentStack&& other) {
+ MoveAssign(std::move(other));
+ return *this;
+ }
+
+ void Push(T item) {
+ Node* new_node = new Node(std::move(item));
+ new_node->next = head_.load(std::memory_order_relaxed);
+ while (!head_.compare_exchange_weak(new_node->next, new_node,
+ std::memory_order_release,
+ std::memory_order_relaxed))
+ ;
+ }
+
+ bool Pop(T& item) {
+ Node* head_node = head_.load(std::memory_order_relaxed);
+ if (!head_node)
+ return false;
+ while (!head_.compare_exchange_weak(head_node, head_node->next,
+ std::memory_order_acquire,
+ std::memory_order_relaxed)) {
+ if (!head_node)
+ return false;
+ }
+
+ item = std::move(head_node->item);
+ delete head_node;
+ return true;
+ }
+
+ void Clear() {
+ Node* null_node = nullptr;
+ Node* head_node = head_.load(std::memory_order_relaxed);
+ while (!head_.compare_exchange_weak(head_node, null_node,
+ std::memory_order_relaxed))
+ ;
+
+ while (head_node) {
+ Node* next_node = head_node->next;
+ delete head_node;
+ head_node = next_node;
+ }
+ }
+
+ bool Empty() const { return !head_.load(std::memory_order_relaxed); }
+
+ private:
+ std::atomic head_ = nullptr;
+
+ void MoveAssign(ConcurrentStack&& other) {
+ Node* null_node = nullptr;
+ Node* head_node = other.head_.load(std::memory_order_relaxed);
+ while (!other.head_.compare_exchange_weak(head_node, null_node,
+ std::memory_order_relaxed))
+ ;
+
+ head_.store(head_node, std::memory_order_relaxed);
+ }
+
+ ConcurrentStack(const ConcurrentStack&) = delete;
+ ConcurrentStack& operator=(const ConcurrentStack&) = delete;
+};
+
+} // namespace base
+
+#endif // CONCURRENT_STACK_H
diff --git a/src/base/file.h b/src/base/file.h
index 154c499..042d940 100644
--- a/src/base/file.h
+++ b/src/base/file.h
@@ -4,6 +4,8 @@
#include
#include
+namespace base {
+
namespace internal {
struct ScopedFILECloser {
@@ -15,8 +17,6 @@ struct ScopedFILECloser {
} // namespace internal
-namespace base {
-
// Automatically closes file.
using ScopedFILE = std::unique_ptr;
diff --git a/src/base/log.cc b/src/base/log.cc
index 8eeeba6..16d62b0 100644
--- a/src/base/log.cc
+++ b/src/base/log.cc
@@ -5,17 +5,25 @@
#else
#include
#endif
+#include
+#include
+#include
+
+#include "vecmath.h"
namespace base {
+// Adapted from Chromium's logging implementation.
// This is never instantiated, it's just used for EAT_STREAM_PARAMETERS to have
// an object of the correct type on the LHS of the unused part of the ternary
// operator.
-Log* Log::swallow_stream;
+LogBase* LogBase::swallow_stream;
-Log::Log(const char* file, int line) : file_(file), line_(line) {}
+LogBase::LogBase(const char* file, int line) : file_(file), line_(line) {}
-Log::~Log() {
+LogBase::~LogBase() = default;
+
+void LogBase::Flush() {
stream_ << std::endl;
std::string text(stream_.str());
std::string filename(file_);
@@ -30,23 +38,78 @@ Log::~Log() {
#endif
}
-template <>
-Log& Log::operator<<(const bool& arg) {
- stream_ << (arg ? "true" : "false");
- return *this;
+Log::Log(const char* file, int line) : LogBase(file, line) {}
+
+Log::~Log() {
+ Flush();
+}
+
+LogDiff::LogDiff(const char* file, int line) : LogBase(file, line) {}
+
+LogDiff::~LogDiff() {
+ static std::unordered_map log_map;
+ static std::mutex lock;
+
+ auto key = std::string(file_) + std::to_string(line_);
+ bool flush = true;
+ {
+ std::lock_guard scoped_lock(lock);
+ auto it = log_map.find(key);
+ if (it == log_map.end())
+ log_map[key] = stream_.str();
+ else if (it->second != stream_.str())
+ it->second = stream_.str();
+ else
+ flush = false;
+ }
+
+ if (flush)
+ Flush();
+}
+
+Check::Check(const char* file,
+ int line,
+ bool condition,
+ bool debug,
+ const char* expr)
+ : LogBase(file, line), condition_(condition) {
+ if (!condition_)
+ base() << (debug ? "DCHECK: (" : "CHECK: (") << expr << ") ";
+}
+
+Check::~Check() {
+ if (!condition_) {
+ Flush();
+ std::abort();
+ }
+}
+
+NotReached::NotReached(const char* file, int line) : LogBase(file, line) {
+ base() << "NOTREACHED ";
+}
+
+NotReached::~NotReached() {
+ Flush();
+ std::abort();
}
template <>
-Log& Log::operator<<(const Vector2& arg) {
- stream_ << "(" << arg.x << ", " << arg.y << ")";
- return *this;
+LogBase& operator<<(LogBase& out, const base::Vector2& arg) {
+ out.stream() << "(" << arg.x << ", " << arg.y << ")";
+ return out;
}
template <>
-Log& Log::operator<<(const Vector4& arg) {
- stream_ << "(" << arg.x << ", " << arg.y << ", " << arg.z << ", " << arg.w
- << ")";
- return *this;
+LogBase& operator<<(LogBase& out, const base::Vector3& arg) {
+ out.stream() << "(" << arg.x << ", " << arg.y << ", " << arg.z << ")";
+ return out;
+}
+
+template <>
+LogBase& operator<<(LogBase& out, const base::Vector4& arg) {
+ out.stream() << "(" << arg.x << ", " << arg.y << ", " << arg.z << ", "
+ << arg.w << ")";
+ return out;
}
} // namespace base
diff --git a/src/base/log.h b/src/base/log.h
index a83505f..1180a7e 100644
--- a/src/base/log.h
+++ b/src/base/log.h
@@ -2,48 +2,115 @@
#define LOG_H
#include
-#include "vecmath.h"
-#define EAT_STREAM_PARAMETERS \
- true ? (void)0 : base::Log::Voidify() & (*base::Log::swallow_stream)
-
-#define LOG base::Log(__FILE__, __LINE__)
+// Macros for logging that are active in both debug and release builds. The way
+// to log things is to stream things to LOG.
+// LOG_DIFF can be used to avoid spam and log only if the message differs.
+// CHECK(condition) terminates the process if the condition is false.
+// NOTREACHED annotates unreachable codepaths and terminates the process if
+// reached.
+#define LOG base::Log(__FILE__, __LINE__).base()
+#define LOG_DIFF base::LogDiff(__FILE__, __LINE__).base()
+#define CHECK(expr) \
+ base::Check(__FILE__, __LINE__, static_cast(expr), false, #expr).base()
+#define NOTREACHED base::NotReached(__FILE__, __LINE__).base()
+// Macros for logging which are active only in debug builds.
#ifdef _DEBUG
-#define DLOG base::Log(__FILE__, __LINE__)
+#define DLOG base::Log(__FILE__, __LINE__).base()
+#define DLOG_DIFF base::LogDiff(__FILE__, __LINE__).base()
+#define DCHECK(expr) \
+ base::Check(__FILE__, __LINE__, static_cast(expr), true, #expr).base()
#else
+// "debug mode" logging is compiled away to nothing for release builds.
#define DLOG EAT_STREAM_PARAMETERS
+#define DLOG_DIFF EAT_STREAM_PARAMETERS
+#define DCHECK(expr) EAT_STREAM_PARAMETERS
#endif
+// Adapted from Chromium's logging implementation.
+// Avoid any pointless instructions to be emitted by the compiler.
+#define EAT_STREAM_PARAMETERS \
+ true ? (void)0 : base::LogBase::Voidify() & (*base::LogBase::swallow_stream)
+
namespace base {
-class Log {
+struct Vector2;
+struct Vector3;
+struct Vector4;
+
+class LogBase {
public:
class Voidify {
public:
Voidify() = default;
// This has to be an operator with a precedence lower than << but
// higher than ?:
- void operator&(Log&) {}
+ void operator&(LogBase&) {}
};
- Log(const char* file, int line);
- ~Log();
+ LogBase& base() { return *this; }
- template
- Log& operator<<(const T& arg) {
- stream_ << arg;
- return *this;
- }
+ std::ostream& stream() { return stream_; }
- static Log* swallow_stream;
+ static LogBase* swallow_stream;
- private:
+ protected:
const char* file_;
const int line_;
std::ostringstream stream_;
+
+ LogBase(const char* file, int line);
+ ~LogBase();
+
+ void Flush();
};
+class Log : public LogBase {
+ public:
+ Log(const char* file, int line);
+ ~Log();
+};
+
+class LogDiff : public LogBase {
+ public:
+ LogDiff(const char* file, int line);
+ ~LogDiff();
+};
+
+class Check : public LogBase {
+ public:
+ Check(const char* file,
+ int line,
+ bool condition,
+ bool debug,
+ const char* expr);
+ ~Check();
+
+ private:
+ bool condition_;
+};
+
+class NotReached : public LogBase {
+ public:
+ NotReached(const char* file, int line);
+ ~NotReached();
+};
+
+template
+LogBase& operator<<(LogBase& out, const T& arg) {
+ out.stream() << arg;
+ return out;
+}
+
+// Explicit specialization for internal types.
+template <>
+LogBase& operator<<(LogBase& out, const base::Vector2& arg);
+template <>
+LogBase& operator<<(LogBase& out, const base::Vector3& arg);
+template <>
+LogBase& operator<<(LogBase& out, const base::Vector4& arg);
+
} // namespace base
#endif // LOG_H
diff --git a/src/base/mem.h b/src/base/mem.h
index 81b5c58..ad1bb58 100644
--- a/src/base/mem.h
+++ b/src/base/mem.h
@@ -1,7 +1,6 @@
#ifndef MEM_H
#define MEM_H
-#include
#include
#include
@@ -9,8 +8,12 @@
#include
#endif
+#include "log.h"
+
#define ALIGN_MEM(alignment) __attribute__((aligned(alignment)))
+namespace base {
+
namespace internal {
struct ScopedAlignedFree {
@@ -22,12 +25,8 @@ struct ScopedAlignedFree {
} // namespace internal
-namespace base {
-
template
-struct AlignedMem {
- using ScoppedPtr = std::unique_ptr;
-};
+using AlignedMemPtr = std::unique_ptr;
template
inline void* AlignedAlloc(size_t size) {
@@ -38,8 +37,8 @@ inline void* AlignedAlloc(size_t size) {
if (posix_memalign(&ptr, kAlignment, size))
ptr = NULL;
#endif
- assert(ptr);
- // assert(((unsigned)ptr & (kAlignment - 1)) == 0);
+ DCHECK(ptr);
+ // DCHECK(((unsigned)ptr & (kAlignment - 1)) == 0);
return ptr;
}
diff --git a/src/base/semaphore.h b/src/base/semaphore.h
new file mode 100644
index 0000000..607714a
--- /dev/null
+++ b/src/base/semaphore.h
@@ -0,0 +1,38 @@
+#ifndef SEMAPHORE_H
+#define SEMAPHORE_H
+
+#include
+#include
+
+namespace base {
+
+class Semaphore {
+ public:
+ Semaphore(int count = 0) : count_(count) {}
+
+ void Acquire() {
+ std::unique_lock scoped_lock(mutex_);
+ cv_.wait(scoped_lock, [&]() { return count_ > 0; });
+ --count_;
+ }
+
+ void Release() {
+ {
+ std::lock_guard scoped_lock(mutex_);
+ ++count_;
+ }
+ cv_.notify_one();
+ }
+
+ private:
+ std::condition_variable cv_;
+ std::mutex mutex_;
+ int count_ = 0;
+
+ Semaphore(Semaphore const&) = delete;
+ Semaphore& operator=(Semaphore const&) = delete;
+};
+
+} // namespace base
+
+#endif // SEMAPHORE_H
diff --git a/src/base/spinlock.h b/src/base/spinlock.h
new file mode 100644
index 0000000..c748b1d
--- /dev/null
+++ b/src/base/spinlock.h
@@ -0,0 +1,42 @@
+#ifndef SPINLOCK_H
+#define SPINLOCK_H
+
+#include
+
+#if defined(_MSC_VER)
+#include
+#define spinlock_pause() YieldProcessor()
+#elif defined(__x86_64__) || defined(__i386__)
+#include
+#define spinlock_pause() _mm_pause()
+#elif defined(__arm__) || defined(__aarch64__)
+#define spinlock_pause() __asm__ __volatile__("yield")
+#else
+#define spinlock_pause()
+#endif
+
+namespace base {
+
+class Spinlock {
+ public:
+ void lock() {
+ for (;;) {
+ // Wait for lock to be released without generating cache misses.
+ bool locked = lock_.load(std::memory_order_relaxed);
+ if (!locked &&
+ lock_.compare_exchange_weak(locked, true, std::memory_order_acquire))
+ break;
+ // Reduce contention between hyper-threads.
+ spinlock_pause();
+ }
+ }
+
+ void unlock() { lock_.store(false, std::memory_order_release); }
+
+ private:
+ std::atomic lock_{false};
+};
+
+} // namespace base
+
+#endif // SPINLOCK_H
diff --git a/src/base/task_runner.cc b/src/base/task_runner.cc
index 30261de..3dc9d50 100644
--- a/src/base/task_runner.cc
+++ b/src/base/task_runner.cc
@@ -1,30 +1,96 @@
#include "task_runner.h"
+#include "log.h"
+
+namespace {
+
+void EnqueueTaskAndReplyRelay(const base::Location& from,
+ base::Closure task_cb,
+ base::Closure reply_cb,
+ base::TaskRunner* destination) {
+ task_cb();
+
+ if (reply_cb)
+ destination->EnqueueTask(from, std::move(reply_cb));
+}
+
+} // namespace
+
namespace base {
-void TaskRunner::Enqueue(base::Closure task) {
- std::unique_lock scoped_lock(mutex_);
- thread_tasks_.emplace_back(std::move(task));
+thread_local std::unique_ptr TaskRunner::thread_local_task_runner;
+
+void TaskRunner::CreateThreadLocalTaskRunner() {
+ DCHECK(!thread_local_task_runner);
+
+ thread_local_task_runner = std::make_unique();
}
-void TaskRunner::Run() {
+TaskRunner* TaskRunner::GetThreadLocalTaskRunner() {
+ return thread_local_task_runner.get();
+}
+
+void TaskRunner::EnqueueTask(const Location& from, Closure task) {
+ DCHECK(task) << LOCATION(from);
+
+ std::lock_guard scoped_lock(lock_);
+ queue_.emplace_back(from, std::move(task));
+}
+
+void TaskRunner::EnqueueTaskAndReply(const Location& from,
+ Closure task,
+ Closure reply) {
+ DCHECK(task) << LOCATION(from);
+ DCHECK(reply) << LOCATION(from);
+ DCHECK(thread_local_task_runner) << LOCATION(from);
+
+ auto relay = std::bind(::EnqueueTaskAndReplyRelay, from, std::move(task),
+ std::move(reply), thread_local_task_runner.get());
+
+ std::lock_guard scoped_lock(lock_);
+ queue_.emplace_back(from, std::move(relay));
+}
+
+void TaskRunner::MultiConsumerRun() {
for (;;) {
- base::Closure task;
+ Task task;
{
- std::unique_lock scoped_lock(mutex_);
- if (!thread_tasks_.empty()) {
- task.swap(thread_tasks_.front());
- thread_tasks_.pop_front();
- }
+ std::lock_guard scoped_lock(lock_);
+ if (queue_.empty())
+ return;
+ task.swap(queue_.front());
+ queue_.pop_front();
}
- if (!task)
- break;
- task();
+
+ auto [from, task_cb] = task;
+
+#if 0
+ LOG << __func__ << " from: " << LOCATION(from);
+#endif
+
+ task_cb();
}
}
-bool TaskRunner::IsBoundToCurrentThread() {
- return thread_id_ == std::this_thread::get_id();
+void TaskRunner::SingleConsumerRun() {
+ std::deque queue;
+ {
+ std::lock_guard scoped_lock(lock_);
+ if (queue_.empty())
+ return;
+ queue.swap(queue_);
+ }
+
+ while (!queue.empty()) {
+ auto [from, task_cb] = queue.front();
+ queue.pop_front();
+
+#if 0
+ LOG << __func__ << " from: " << LOCATION(from);
+#endif
+
+ task_cb();
+ }
}
} // namespace base
diff --git a/src/base/task_runner.h b/src/base/task_runner.h
index 943f0ed..3c46af7 100644
--- a/src/base/task_runner.h
+++ b/src/base/task_runner.h
@@ -2,26 +2,76 @@
#define TASK_RUNNER_H
#include
+#include
#include
-#include
+#include
+
#include "closure.h"
namespace base {
+namespace internal {
+
+// Adapted from Chromium project.
+// Adapts a function that produces a result via a return value to
+// one that returns via an output parameter.
+template
+void ReturnAsParamAdapter(std::function func,
+ ReturnType* result) {
+ *result = func();
+}
+
+// Adapts a ReturnType* result to a callblack that expects a ReturnType.
+template
+void ReplyAdapter(std::function callback,
+ ReturnType* result) {
+ callback(std::move(*result));
+ delete result;
+}
+
+} // namespace internal
+
+// Runs queued tasks (in the form of Closure objects). All methods are
+// thread-safe and can be called on any thread.
+// Tasks run in FIFO order. When consumed concurrently by multiple threads, it
+// doesn't guarantee whether tasks overlap, or whether they run on a particular
+// thread.
class TaskRunner {
public:
TaskRunner() = default;
~TaskRunner() = default;
- void Enqueue(base::Closure cb);
- void Run();
+ void EnqueueTask(const Location& from, Closure task);
- bool IsBoundToCurrentThread();
+ void EnqueueTaskAndReply(const Location& from, Closure task, Closure reply);
+
+ template
+ void EnqueueTaskAndReplyWithResult(const Location& from,
+ std::function task,
+ std::function reply) {
+ auto* result = new ReturnType;
+ return EnqueueTaskAndReply(
+ from,
+ std::bind(internal::ReturnAsParamAdapter, std::move(task),
+ result),
+ std::bind(internal::ReplyAdapter, std::move(reply),
+ result));
+ }
+
+ void MultiConsumerRun();
+
+ void SingleConsumerRun();
+
+ static void CreateThreadLocalTaskRunner();
+ static TaskRunner* GetThreadLocalTaskRunner();
private:
- std::thread::id thread_id_ = std::this_thread::get_id();
- std::mutex mutex_;
- std::deque thread_tasks_;
+ using Task = std::tuple;
+
+ std::deque queue_;
+ mutable std::mutex lock_;
+
+ static thread_local std::unique_ptr thread_local_task_runner;
TaskRunner(TaskRunner const&) = delete;
TaskRunner& operator=(TaskRunner const&) = delete;
diff --git a/src/base/worker.cc b/src/base/worker.cc
index e0ae406..b3beb99 100644
--- a/src/base/worker.cc
+++ b/src/base/worker.cc
@@ -1,67 +1,71 @@
#include "worker.h"
+
#include "log.h"
namespace base {
-Worker::Worker(unsigned max_concurrency) : max_concurrency_(max_concurrency) {
- if (max_concurrency_ > std::thread::hardware_concurrency() ||
- max_concurrency_ == 0) {
- max_concurrency_ = std::thread::hardware_concurrency();
- if (max_concurrency_ == 0)
- max_concurrency_ = 1;
- }
+Worker* Worker::singleton = nullptr;
+
+Worker::Worker() {
+ DCHECK(!singleton);
+ singleton = this;
}
-Worker::~Worker() = default;
-
-void Worker::Enqueue(base::Closure task) {
- if (!active_) {
- unsigned concurrency = max_concurrency_;
- while (concurrency--)
- threads_.emplace_back(&Worker::WorkerMain, this);
- active_ = true;
- }
-
- bool notify;
- {
- std::unique_lock scoped_lock(mutex_);
- notify = tasks_.empty();
- tasks_.emplace_back(std::move(task));
- }
- if (notify)
- cv_.notify_all();
+Worker::~Worker() {
+ Shutdown();
+ singleton = nullptr;
}
-void Worker::Join() {
- if (!active_)
+void Worker::Initialize(unsigned max_concurrency) {
+ if (max_concurrency > std::thread::hardware_concurrency() ||
+ max_concurrency == 0) {
+ max_concurrency = std::thread::hardware_concurrency();
+ if (max_concurrency == 0)
+ max_concurrency = 1;
+ }
+
+ while (max_concurrency--)
+ threads_.emplace_back(&Worker::WorkerMain, this);
+}
+
+void Worker::Shutdown() {
+ if (threads_.empty())
return;
- {
- std::unique_lock scoped_lock(mutex_);
- quit_when_idle_ = true;
- }
- cv_.notify_all();
+ quit_.store(true, std::memory_order_relaxed);
+ semaphore_.Release();
+
for (auto& thread : threads_)
thread.join();
threads_.clear();
- active_ = false;
+}
+
+void Worker::EnqueueTask(const Location& from, Closure task) {
+ DCHECK((!threads_.empty()));
+
+ task_runner_.EnqueueTask(from, std::move(task));
+ semaphore_.Release();
+}
+
+void Worker::EnqueueTaskAndReply(const Location& from,
+ Closure task,
+ Closure reply) {
+ DCHECK((!threads_.empty()));
+
+ task_runner_.EnqueueTaskAndReply(from, std::move(task), std::move(reply));
+ semaphore_.Release();
}
void Worker::WorkerMain() {
for (;;) {
- base::Closure task;
- {
- std::unique_lock scoped_lock(mutex_);
- while (tasks_.empty()) {
- if (quit_when_idle_)
- return;
- cv_.wait(scoped_lock);
- }
- task.swap(tasks_.front());
- tasks_.pop_front();
+ semaphore_.Acquire();
+
+ if (quit_.load(std::memory_order_relaxed)) {
+ semaphore_.Release();
+ return;
}
- task();
+ task_runner_.MultiConsumerRun();
}
}
diff --git a/src/base/worker.h b/src/base/worker.h
index 7059941..d9e5cc8 100644
--- a/src/base/worker.h
+++ b/src/base/worker.h
@@ -1,33 +1,53 @@
-#ifndef WORKER_H
-#define WORKER_H
+#ifndef THREAD_POOL_H
+#define THREAD_POOL_H
-#include
-#include
-#include
+#include
#include
#include
+
#include "closure.h"
+#include "semaphore.h"
+#include "task_runner.h"
namespace base {
-// Feed the worker tasks and they will be called on a thread from the pool.
+class TaskRunner;
+
+// Feed the worker tasks (in the form of Closure objects) and they will be
+// called on any thread from the pool.
class Worker {
public:
- Worker(unsigned max_concurrency = 0);
+ Worker();
~Worker();
- void Enqueue(base::Closure task);
- void Join();
+ static Worker& Get() { return *singleton; }
+
+ void Initialize(unsigned max_concurrency = 0);
+
+ void Shutdown();
+
+ void EnqueueTask(const Location& from, Closure task);
+
+ void EnqueueTaskAndReply(const Location& from, Closure task, Closure reply);
+
+ template
+ void EnqueueTaskAndReplyWithResult(const Location& from,
+ std::function task,
+ std::function reply) {
+ task_runner_.EnqueueTaskAndReplyWithResult(from, std::move(task),
+ std::move(reply));
+ semaphore_.Release();
+ }
private:
- bool active_ = false;
- unsigned max_concurrency_ = 0;
-
- std::condition_variable cv_;
- std::mutex mutex_;
std::vector threads_;
- std::deque tasks_;
- bool quit_when_idle_ = false;
+
+ Semaphore semaphore_;
+ std::atomic quit_{false};
+
+ base::TaskRunner task_runner_;
+
+ static Worker* singleton;
void WorkerMain();
@@ -37,4 +57,4 @@ class Worker {
} // namespace base
-#endif // WORKER_H
+#endif // THREAD_POOL_H
diff --git a/src/demo/BUILD.gn b/src/demo/BUILD.gn
new file mode 100644
index 0000000..0dc8762
--- /dev/null
+++ b/src/demo/BUILD.gn
@@ -0,0 +1,25 @@
+executable("demo") {
+ sources = [
+ "credits.cc",
+ "credits.h",
+ "damage_type.h",
+ "demo.cc",
+ "demo.h",
+ "enemy.cc",
+ "enemy.h",
+ "hud.cc",
+ "hud.h",
+ "menu.cc",
+ "menu.h",
+ "player.cc",
+ "player.h",
+ "sky_quad.cc",
+ "sky_quad.h",
+ ]
+
+ deps = [
+ "//src/base",
+ "//src/engine",
+ "//src/third_party",
+ ]
+}
diff --git a/src/demo/credits.cc b/src/demo/credits.cc
index 095f584..f688948 100644
--- a/src/demo/credits.cc
+++ b/src/demo/credits.cc
@@ -2,12 +2,10 @@
#include "../base/log.h"
#include "../base/vecmath.h"
-#include "../base/worker.h"
#include "../engine/engine.h"
#include "../engine/font.h"
#include "../engine/image.h"
#include "../engine/input_event.h"
-#include "../engine/renderer/texture.h"
#include "demo.h"
using namespace base;
@@ -16,11 +14,11 @@ using namespace eng;
namespace {
constexpr char kCreditsLines[Credits::kNumLines][15] = {
- "Credits", "Code:", "Attila Uygun", "Graphics:", "Erkan Erturk"};
+ "Credits", "Code:", "Attila Uygun", "Graphics:", "Erkan Ertürk"};
constexpr float kLineSpaces[Credits::kNumLines - 1] = {1.5f, 0.5f, 1.5f, 0.5f};
-const Vector4 kTextColor = {0.3f, 0.55f, 1.0f, 1};
+const Vector4 kTextColor = {0.80f, 0.87f, 0.93f, 1};
constexpr float kFadeSpeed = 0.2f;
} // namespace
@@ -30,16 +28,19 @@ Credits::Credits() = default;
Credits::~Credits() = default;
bool Credits::Initialize() {
- const Font& font = static_cast(Engine::Get().GetGame())->GetFont();
+ const Font* font = Engine::Get().GetSystemFont();
max_text_width_ = -1;
for (int i = 0; i < kNumLines; ++i) {
int width, height;
- font.CalculateBoundingBox(kCreditsLines[i], width, height);
+ font->CalculateBoundingBox(kCreditsLines[i], width, height);
if (width > max_text_width_)
max_text_width_ = width;
}
+ Engine::Get().SetImageSource("credits",
+ std::bind(&Credits::CreateImage, this));
+
for (int i = 0; i < kNumLines; ++i)
text_animator_.Attach(&text_[i]);
@@ -51,8 +52,7 @@ void Credits::Update(float delta_time) {
}
void Credits::OnInputEvent(std::unique_ptr event) {
- if ((event->GetType() == InputEvent::kTap ||
- event->GetType() == InputEvent::kDragEnd ||
+ if ((event->GetType() == InputEvent::kDragEnd ||
event->GetType() == InputEvent::kNavigateBack) &&
!text_animator_.IsPlaying(Animator::kBlending)) {
Hide();
@@ -61,25 +61,13 @@ void Credits::OnInputEvent(std::unique_ptr event) {
}
}
-void Credits::Draw() {
- for (int i = 0; i < kNumLines; ++i)
- text_[i].Draw();
-}
-
-void Credits::ContextLost() {
- if (tex_)
- tex_->Update(CreateImage());
-}
-
void Credits::Show() {
- tex_ = Engine::Get().CreateRenderResource();
- tex_->Update(CreateImage());
+ Engine::Get().RefreshImage("credits");
for (int i = 0; i < kNumLines; ++i) {
- text_[i].Create(tex_, {1, kNumLines});
+ text_[i].Create("credits", {1, kNumLines});
+ text_[i].SetZOrder(50);
text_[i].SetOffset({0, 0});
- text_[i].SetScale({1, 1});
- text_[i].AutoScale();
text_[i].SetColor(kTextColor * Vector4(1, 1, 1, 0));
text_[i].SetFrame(i);
@@ -107,7 +95,6 @@ void Credits::Hide() {
text_animator_.SetEndCallback(Animator::kBlending, [&]() -> void {
for (int i = 0; i < kNumLines; ++i)
text_[i].Destory();
- tex_.reset();
text_animator_.SetEndCallback(Animator::kBlending, nullptr);
text_animator_.SetVisible(false);
});
@@ -116,23 +103,21 @@ void Credits::Hide() {
}
std::unique_ptr Credits::CreateImage() {
- const Font& font = static_cast(Engine::Get().GetGame())->GetFont();
+ const Font* font = Engine::Get().GetSystemFont();
- int line_height = font.GetLineHeight() + 1;
+ int line_height = font->GetLineHeight() + 1;
auto image = std::make_unique();
image->Create(max_text_width_, line_height * kNumLines);
image->Clear({1, 1, 1, 0});
- Worker worker(kNumLines);
for (int i = 0; i < kNumLines; ++i) {
int w, h;
- font.CalculateBoundingBox(kCreditsLines[i], w, h);
+ font->CalculateBoundingBox(kCreditsLines[i], w, h);
float x = (image->GetWidth() - w) / 2;
float y = line_height * i;
- worker.Enqueue(std::bind(&Font::Print, &font, x, y, kCreditsLines[i],
- image->GetBuffer(), image->GetWidth()));
+ font->Print(x, y, kCreditsLines[i], image->GetBuffer(), image->GetWidth());
}
- worker.Join();
+ image->Compress();
return image;
}
diff --git a/src/demo/credits.h b/src/demo/credits.h
index d5a3447..166232f 100644
--- a/src/demo/credits.h
+++ b/src/demo/credits.h
@@ -10,7 +10,6 @@
namespace eng {
class Image;
class InputEvent;
-class Texture;
} // namespace eng
class Credits {
@@ -26,16 +25,10 @@ class Credits {
void OnInputEvent(std::unique_ptr event);
- void Draw();
-
- void ContextLost();
-
void Show();
void Hide();
private:
- std::shared_ptr tex_;
-
eng::ImageQuad text_[kNumLines];
eng::Animator text_animator_;
diff --git a/src/demo/demo.cc b/src/demo/demo.cc
index 7ea118d..c2cd784 100644
--- a/src/demo/demo.cc
+++ b/src/demo/demo.cc
@@ -80,7 +80,7 @@ void Demo::Update(float delta_time) {
if (add_score_ > 0) {
score_ += add_score_;
add_score_ = 0;
- hud_.PrintScore(score_, true);
+ hud_.SetScore(score_, true);
}
hud_.Update(delta_time);
@@ -93,21 +93,7 @@ void Demo::Update(float delta_time) {
UpdateGameState(delta_time);
}
-void Demo::Draw(float frame_frac) {
- sky_.Draw(frame_frac);
- player_.Draw(frame_frac);
- enemy_.Draw(frame_frac);
- hud_.Draw();
- menu_.Draw();
- credits_.Draw();
-}
-
void Demo::ContextLost() {
- enemy_.ContextLost();
- player_.ContextLost();
- hud_.ContextLost();
- menu_.ContextLost();
- credits_.ContextLost();
sky_.ContextLost();
}
@@ -169,7 +155,7 @@ void Demo::UpdateMenuState(float delta_time) {
Engine::Get().Exit();
break;
default:
- assert(false);
+ NOTREACHED << "- Unknown menu option: " << menu_.selected_option();
}
}
@@ -209,8 +195,8 @@ void Demo::UpdateGameState(float delta_time) {
sky_.SwitchColor(c);
++wave_;
- hud_.PrintScore(score_, true);
- hud_.PrintWave(wave_, true);
+ hud_.SetScore(score_, true);
+ hud_.SetWave(wave_, true);
hud_.SetProgress(1);
float factor = 3 * (log10(5 * (float)wave_) / log10(1.2f)) - 25;
@@ -245,7 +231,7 @@ void Demo::StartNewGame() {
}
void Demo::SetDelayedWork(float seconds, base::Closure cb) {
- assert(delayed_work_cb_ == nullptr);
+ DCHECK(delayed_work_cb_ == nullptr);
delayed_work_cb_ = std::move(cb);
delayed_work_timer_ = seconds;
}
diff --git a/src/demo/demo.h b/src/demo/demo.h
index 6c06aab..a8f1a83 100644
--- a/src/demo/demo.h
+++ b/src/demo/demo.h
@@ -20,8 +20,6 @@ class Demo : public eng::Game {
void Update(float delta_time) override;
- void Draw(float frame_frac) override;
-
void ContextLost() override;
void LostFocus() override;
diff --git a/src/demo/enemy.cc b/src/demo/enemy.cc
index 3584f92..0ef88f5 100644
--- a/src/demo/enemy.cc
+++ b/src/demo/enemy.cc
@@ -1,6 +1,5 @@
#include "enemy.h"
-#include
#include
#include
@@ -10,10 +9,11 @@
#include "../engine/engine.h"
#include "../engine/font.h"
#include "../engine/image.h"
-#include "../engine/renderer/texture.h"
#include "../engine/sound.h"
#include "demo.h"
+using namespace std::string_literals;
+
using namespace base;
using namespace eng;
@@ -44,29 +44,18 @@ void SetupFadeOutAnim(Animator& animator, float delay) {
} // namespace
-Enemy::Enemy()
- : skull_tex_(Engine::Get().CreateRenderResource()),
- bug_tex_(Engine::Get().CreateRenderResource()),
- target_tex_(Engine::Get().CreateRenderResource()),
- blast_tex_(Engine::Get().CreateRenderResource()),
- score_tex_{Engine::Get().CreateRenderResource(),
- Engine::Get().CreateRenderResource(),
- Engine::Get().CreateRenderResource()} {}
+Enemy::Enemy() = default;
Enemy::~Enemy() = default;
bool Enemy::Initialize() {
explosion_sound_ = std::make_shared();
- if (!explosion_sound_->Load("explosion.mp3"))
+ if (!explosion_sound_->Load("explosion.mp3", false))
return false;
return CreateRenderResources();
}
-void Enemy::ContextLost() {
- CreateRenderResources();
-}
-
void Enemy::Update(float delta_time) {
if (!waiting_for_next_wave_) {
if (spawn_factor_interpolator_ < 1) {
@@ -95,25 +84,14 @@ void Enemy::Update(float delta_time) {
}
}
-void Enemy::Draw(float frame_frac) {
- for (auto& e : enemies_) {
- e.sprite.Draw();
- e.target.Draw();
- e.blast.Draw();
- e.health_base.Draw();
- e.health_bar.Draw();
- e.score.Draw();
- }
-}
-
bool Enemy::HasTarget(DamageType damage_type) {
- assert(damage_type > kDamageType_Invalid && damage_type < kDamageType_Any);
+ DCHECK(damage_type > kDamageType_Invalid && damage_type < kDamageType_Any);
return GetTarget(damage_type) ? true : false;
}
Vector2 Enemy::GetTargetPos(DamageType damage_type) {
- assert(damage_type > kDamageType_Invalid && damage_type < kDamageType_Any);
+ DCHECK(damage_type > kDamageType_Invalid && damage_type < kDamageType_Any);
EnemyUnit* target = GetTarget(damage_type);
if (target)
@@ -126,7 +104,7 @@ void Enemy::SelectTarget(DamageType damage_type,
const Vector2& origin,
const Vector2& dir,
float snap_factor) {
- assert(damage_type > kDamageType_Invalid && damage_type < kDamageType_Any);
+ DCHECK(damage_type > kDamageType_Invalid && damage_type < kDamageType_Any);
if (waiting_for_next_wave_)
return;
@@ -171,7 +149,7 @@ void Enemy::SelectTarget(DamageType damage_type,
}
void Enemy::DeselectTarget(DamageType damage_type) {
- assert(damage_type > kDamageType_Invalid && damage_type < kDamageType_Any);
+ DCHECK(damage_type > kDamageType_Invalid && damage_type < kDamageType_Any);
EnemyUnit* target = GetTarget(damage_type);
if (target) {
@@ -182,7 +160,7 @@ void Enemy::DeselectTarget(DamageType damage_type) {
}
void Enemy::HitTarget(DamageType damage_type) {
- assert(damage_type > kDamageType_Invalid && damage_type < kDamageType_Any);
+ DCHECK(damage_type > kDamageType_Invalid && damage_type < kDamageType_Any);
if (waiting_for_next_wave_)
return;
@@ -227,8 +205,8 @@ void Enemy::OnWaveStarted(int wave) {
}
void Enemy::TakeDamage(EnemyUnit* target, int damage) {
- assert(!target->marked_for_removal);
- assert(target->hit_points > 0);
+ DCHECK(!target->marked_for_removal);
+ DCHECK(target->hit_points > 0);
target->blast.SetVisible(true);
target->blast_animator.Play(Animator::kFrames, false);
@@ -313,8 +291,8 @@ void Enemy::Spawn(EnemyType enemy_type,
DamageType damage_type,
const Vector2& pos,
float speed) {
- assert(enemy_type > kEnemyType_Invalid && enemy_type < kEnemyType_Max);
- assert(damage_type > kDamageType_Invalid && damage_type < kDamageType_Max);
+ DCHECK(enemy_type > kEnemyType_Invalid && enemy_type < kEnemyType_Max);
+ DCHECK(damage_type > kDamageType_Invalid && damage_type < kDamageType_Max);
Engine& engine = Engine::Get();
Demo* game = static_cast(engine.GetGame());
@@ -324,15 +302,15 @@ void Enemy::Spawn(EnemyType enemy_type,
e.damage_type = damage_type;
if (enemy_type == kEnemyType_Skull) {
e.total_health = e.hit_points = 1;
- e.sprite.Create(skull_tex_, {10, 13}, 100, 100);
+ e.sprite.Create("skull_tex", {10, 13}, 100, 100);
} else if (enemy_type == kEnemyType_Bug) {
e.total_health = e.hit_points = 2;
- e.sprite.Create(bug_tex_, {10, 4});
+ e.sprite.Create("bug_tex", {10, 4});
} else { // kEnemyType_Tank
e.total_health = e.hit_points = 6;
- e.sprite.Create(skull_tex_, {10, 13}, 100, 100);
+ e.sprite.Create("skull_tex", {10, 13}, 100, 100);
}
- e.sprite.AutoScale();
+ e.sprite.SetZOrder(11);
e.sprite.SetVisible(true);
Vector2 spawn_pos = pos + Vector2(0, e.sprite.GetScale().y / 2);
e.sprite.SetOffset(spawn_pos);
@@ -344,26 +322,28 @@ void Enemy::Spawn(EnemyType enemy_type,
e.sprite_animator.Attach(&e.sprite);
e.sprite_animator.Play(Animator::kFrames, true);
- e.target.Create(target_tex_, {6, 2});
- e.target.AutoScale();
+ e.target.Create("target_tex", {6, 2});
+ e.target.SetZOrder(12);
e.target.SetOffset(spawn_pos);
- e.blast.Create(blast_tex_, {6, 2});
- e.blast.AutoScale();
+ e.blast.Create("blast_tex", {6, 2});
+ e.blast.SetZOrder(12);
e.blast.SetOffset(spawn_pos);
+ e.health_base.SetZOrder(11);
e.health_base.Scale(e.sprite.GetScale() * Vector2(0.6f, 0.01f));
e.health_base.SetOffset(spawn_pos);
e.health_base.PlaceToBottomOf(e.sprite);
e.health_base.SetColor({0.5f, 0.5f, 0.5f, 1});
+ e.health_bar.SetZOrder(11);
e.health_bar.Scale(e.sprite.GetScale() * Vector2(0.6f, 0.01f));
e.health_bar.SetOffset(spawn_pos);
e.health_bar.PlaceToBottomOf(e.sprite);
e.health_bar.SetColor({0.161f, 0.89f, 0.322f, 1});
- e.score.Create(score_tex_[e.enemy_type]);
- e.score.AutoScale();
+ e.score.Create("score_tex"s + std::to_string(e.enemy_type));
+ e.score.SetZOrder(12);
e.score.SetColor({1, 1, 1, 1});
e.score.SetOffset(spawn_pos);
@@ -425,13 +405,14 @@ Enemy::EnemyUnit* Enemy::GetTarget(DamageType damage_type) {
}
int Enemy::GetScore(EnemyType enemy_type) {
- assert(enemy_type > kEnemyType_Invalid && enemy_type < kEnemyType_Max);
+ DCHECK(enemy_type > kEnemyType_Invalid && enemy_type < kEnemyType_Max);
return enemy_scores[enemy_type];
}
-std::unique_ptr Enemy::GetScoreImage(int score) {
+std::unique_ptr Enemy::GetScoreImage(EnemyType enemy_type) {
const Font& font = static_cast(Engine::Get().GetGame())->GetFont();
+ int score = GetScore(enemy_type);
std::string text = std::to_string(score);
int width, height;
font.CalculateBoundingBox(text.c_str(), width, height);
@@ -446,31 +427,17 @@ std::unique_ptr Enemy::GetScoreImage(int score) {
}
bool Enemy::CreateRenderResources() {
- auto skull_image = std::make_unique();
- if (!skull_image->Load("enemy_anims_01_frames_ok.png"))
- return false;
- auto bug_image = std::make_unique();
- if (!bug_image->Load("enemy_anims_02_frames_ok.png"))
- return false;
- auto target_image = std::make_unique();
- if (!target_image->Load("enemy_target_single_ok.png"))
- return false;
- auto blast_image = std::make_unique();
- if (!blast_image->Load("enemy_anims_blast_ok.png"))
- return false;
-
- skull_image->Compress();
- bug_image->Compress();
- target_image->Compress();
- blast_image->Compress();
-
- skull_tex_->Update(std::move(skull_image));
- bug_tex_->Update(std::move(bug_image));
- target_tex_->Update(std::move(target_image));
- blast_tex_->Update(std::move(blast_image));
+ Engine::Get().SetImageSource("skull_tex", "enemy_anims_01_frames_ok.png",
+ true);
+ Engine::Get().SetImageSource("bug_tex", "enemy_anims_02_frames_ok.png", true);
+ Engine::Get().SetImageSource("target_tex", "enemy_target_single_ok.png",
+ true);
+ Engine::Get().SetImageSource("blast_tex", "enemy_anims_blast_ok.png", true);
for (int i = 0; i < kEnemyType_Max; ++i)
- score_tex_[i]->Update(GetScoreImage(GetScore((EnemyType)i)));
+ Engine::Get().SetImageSource(
+ "score_tex"s + std::to_string(i),
+ std::bind(&Enemy::GetScoreImage, this, (EnemyType)i), true);
return true;
}
diff --git a/src/demo/enemy.h b/src/demo/enemy.h
index 0b578de..1881440 100644
--- a/src/demo/enemy.h
+++ b/src/demo/enemy.h
@@ -15,7 +15,6 @@
namespace eng {
class Image;
class Sound;
-class Texture;
} // namespace eng
class Enemy {
@@ -25,12 +24,8 @@ class Enemy {
bool Initialize();
- void ContextLost();
-
void Update(float delta_time);
- void Draw(float frame_frac);
-
bool HasTarget(DamageType damage_type);
base::Vector2 GetTargetPos(DamageType damage_type);
@@ -76,12 +71,6 @@ class Enemy {
eng::SoundPlayer explosion_;
};
- std::shared_ptr skull_tex_;
- std::shared_ptr bug_tex_;
- std::shared_ptr target_tex_;
- std::shared_ptr blast_tex_;
- std::shared_ptr score_tex_[kEnemyType_Max];
-
std::shared_ptr explosion_sound_;
std::list enemies_;
@@ -111,7 +100,7 @@ class Enemy {
int GetScore(EnemyType enemy_type);
- std::unique_ptr GetScoreImage(int score);
+ std::unique_ptr GetScoreImage(EnemyType enemy_type);
bool CreateRenderResources();
};
diff --git a/src/demo/hud.cc b/src/demo/hud.cc
index bda90ab..c5e90d3 100644
--- a/src/demo/hud.cc
+++ b/src/demo/hud.cc
@@ -6,9 +6,10 @@
#include "../engine/engine.h"
#include "../engine/font.h"
#include "../engine/image.h"
-#include "../engine/renderer/texture.h"
#include "demo.h"
+using namespace std::string_literals;
+
using namespace base;
using namespace eng;
@@ -23,10 +24,7 @@ const Vector4 kTextColor = {0.895f, 0.692f, 0.24f, 1};
} // namespace
-Hud::Hud() {
- text_[0].Create(Engine::Get().CreateRenderResource());
- text_[1].Create(Engine::Get().CreateRenderResource());
-}
+Hud::Hud() = default;
Hud::~Hud() = default;
@@ -37,12 +35,14 @@ bool Hud::Initialize() {
int tmp;
font.CalculateBoundingBox("big_enough_text", max_text_width_, tmp);
- for (int i = 0; i < 2; ++i) {
- auto image = CreateImage();
+ Engine::Get().SetImageSource("text0",
+ std::bind(&Hud::CreateScoreImage, this));
+ Engine::Get().SetImageSource("text1", std::bind(&Hud::CreateWaveImage, this));
- text_[i].GetTexture()->Update(std::move(image));
- text_[i].AutoScale();
- text_[i].SetColor(kTextColor);
+ for (int i = 0; i < 2; ++i) {
+ text_[i].Create("text"s + std::to_string(i));
+ text_[i].SetZOrder(30);
+ text_[i].SetColor(kTextColor * Vector4(1, 1, 1, 0));
Vector2 pos = (engine.GetScreenSize() / 2 - text_[i].GetScale() / 2);
pos -= engine.GetScreenSize() * Vector2(kHorizontalMargin, kVerticalMargin);
@@ -51,6 +51,7 @@ bool Hud::Initialize() {
scale -= engine.GetScreenSize() * Vector2(kHorizontalMargin * 4, 0);
scale += text_[0].GetScale() * Vector2(0, 0.3f);
+ progress_bar_[i].SetZOrder(30);
progress_bar_[i].Scale(scale);
progress_bar_[i].Translate(pos * Vector2(0, 1));
progress_bar_[i].SetColor(kPprogressBarColor[i] * Vector4(1, 1, 1, 0));
@@ -79,18 +80,6 @@ void Hud::Update(float delta_time) {
}
}
-void Hud::Draw() {
- for (int i = 0; i < 2; ++i) {
- progress_bar_[i].Draw();
- text_[i].Draw();
- }
-}
-
-void Hud::ContextLost() {
- PrintScore(last_score_, false);
- PrintWave(last_wave_, false);
-}
-
void Hud::Show() {
if (text_[0].IsVisible())
return;
@@ -103,9 +92,9 @@ void Hud::Show() {
}
}
-void Hud::PrintScore(int score, bool flash) {
+void Hud::SetScore(int score, bool flash) {
last_score_ = score;
- Print(0, std::to_string(score));
+ Engine::Get().RefreshImage("text0");
if (flash) {
text_animator_[0].SetEndCallback(Animator::kBlending, text_animator_cb_[0]);
@@ -115,11 +104,9 @@ void Hud::PrintScore(int score, bool flash) {
}
}
-void Hud::PrintWave(int wave, bool flash) {
+void Hud::SetWave(int wave, bool flash) {
last_wave_ = wave;
- std::string text = "wave ";
- text += std::to_string(wave);
- Print(1, text.c_str());
+ Engine::Get().RefreshImage("text1");
if (flash) {
text_animator_[1].SetEndCallback(Animator::kBlending, text_animator_cb_[1]);
@@ -137,7 +124,15 @@ void Hud::SetProgress(float progress) {
progress_bar_[1].Translate({t, 0});
}
-void Hud::Print(int i, const std::string& text) {
+std::unique_ptr Hud::CreateScoreImage() {
+ return Print(0, std::to_string(last_score_));
+}
+
+std::unique_ptr Hud::CreateWaveImage() {
+ return Print(1, "wave "s + std::to_string(last_wave_));
+}
+
+std::unique_ptr Hud::Print(int i, const std::string& text) {
const Font& font = static_cast(Engine::Get().GetGame())->GetFont();
auto image = CreateImage();
@@ -151,7 +146,7 @@ void Hud::Print(int i, const std::string& text) {
font.Print(x, 0, text.c_str(), image->GetBuffer(), image->GetWidth());
- text_[i].GetTexture()->Update(std::move(image));
+ return image;
}
std::unique_ptr Hud::CreateImage() {
diff --git a/src/demo/hud.h b/src/demo/hud.h
index fea2edb..e713cf2 100644
--- a/src/demo/hud.h
+++ b/src/demo/hud.h
@@ -22,14 +22,10 @@ class Hud {
void Update(float delta_time);
- void Draw();
-
- void ContextLost();
-
void Show();
- void PrintScore(int score, bool flash);
- void PrintWave(int wave, bool flash);
+ void SetScore(int score, bool flash);
+ void SetWave(int wave, bool flash);
void SetProgress(float progress);
private:
@@ -46,7 +42,10 @@ class Hud {
int last_wave_ = 0;
float last_progress_ = 0;
- void Print(int i, const std::string& text);
+ std::unique_ptr CreateScoreImage();
+ std::unique_ptr CreateWaveImage();
+
+ std::unique_ptr Print(int i, const std::string& text);
std::unique_ptr CreateImage();
};
diff --git a/src/demo/menu.cc b/src/demo/menu.cc
index c19fff6..0c0aece 100644
--- a/src/demo/menu.cc
+++ b/src/demo/menu.cc
@@ -1,18 +1,15 @@
#include "menu.h"
-#include
#include
#include
#include "../base/collusion_test.h"
#include "../base/interpolation.h"
#include "../base/log.h"
-#include "../base/worker.h"
#include "../engine/engine.h"
#include "../engine/font.h"
#include "../engine/image.h"
#include "../engine/input_event.h"
-#include "../engine/renderer/texture.h"
#include "demo.h"
using namespace base;
@@ -34,7 +31,7 @@ constexpr float kFadeSpeed = 0.2f;
} // namespace
-Menu::Menu() : tex_(Engine::Get().CreateRenderResource()) {}
+Menu::Menu() = default;
Menu::~Menu() = default;
@@ -49,11 +46,12 @@ bool Menu::Initialize() {
max_text_width_ = width;
}
- tex_->Update(CreateImage());
+ if (!CreateRenderResources())
+ return false;
for (int i = 0; i < kOption_Max; ++i) {
- items_[i].text.Create(tex_, {1, 4});
- items_[i].text.AutoScale();
+ items_[i].text.Create("menu_tex", {1, 4});
+ items_[i].text.SetZOrder(40);
items_[i].text.Scale(1.5f);
items_[i].text.SetColor(kColorFadeOut);
items_[i].text.SetVisible(false);
@@ -86,15 +84,12 @@ void Menu::Update(float delta_time) {
}
void Menu::OnInputEvent(std::unique_ptr event) {
- if (event->GetType() == InputEvent::kTap ||
- event->GetType() == InputEvent::kDragStart)
+ if (event->GetType() == InputEvent::kDragStart)
tap_pos_[0] = tap_pos_[1] = event->GetVector(0);
else if (event->GetType() == InputEvent::kDrag)
tap_pos_[1] = event->GetVector(0);
- if ((event->GetType() != InputEvent::kTap &&
- event->GetType() != InputEvent::kDragEnd) ||
- IsAnimating())
+ if (event->GetType() != InputEvent::kDragEnd || IsAnimating())
return;
for (int i = 0; i < kOption_Max; ++i) {
@@ -116,15 +111,6 @@ void Menu::OnInputEvent(std::unique_ptr event) {
}
}
-void Menu::Draw() {
- for (int i = 0; i < kOption_Max; ++i)
- items_[i].text.Draw();
-}
-
-void Menu::ContextLost() {
- tex_->Update(CreateImage());
-}
-
void Menu::SetOptionEnabled(Option o, bool enable) {
int first = -1, last = -1;
for (int i = 0; i < kOption_Max; ++i) {
@@ -181,6 +167,12 @@ void Menu::Hide() {
}
}
+bool Menu::CreateRenderResources() {
+ Engine::Get().SetImageSource("menu_tex", std::bind(&Menu::CreateImage, this));
+
+ return true;
+}
+
std::unique_ptr Menu::CreateImage() {
const Font& font = static_cast(Engine::Get().GetGame())->GetFont();
@@ -191,16 +183,13 @@ std::unique_ptr Menu::CreateImage() {
// Fill the area of each menu item with gradient.
image->GradientV({1.0f, 1.0f, 1.0f, 0}, {.0f, .0f, 1.0f, 0}, line_height);
- base::Worker worker(kOption_Max);
for (int i = 0; i < kOption_Max; ++i) {
int w, h;
font.CalculateBoundingBox(kMenuOption[i], w, h);
float x = (image->GetWidth() - w) / 2;
float y = line_height * i;
- worker.Enqueue(std::bind(&Font::Print, &font, x, y, kMenuOption[i],
- image->GetBuffer(), image->GetWidth()));
+ font.Print(x, y, kMenuOption[i], image->GetBuffer(), image->GetWidth());
}
- worker.Join();
return image;
}
diff --git a/src/demo/menu.h b/src/demo/menu.h
index d0b3027..46a7189 100644
--- a/src/demo/menu.h
+++ b/src/demo/menu.h
@@ -12,7 +12,6 @@
namespace eng {
class Image;
class InputEvent;
-class Texture;
} // namespace eng
class Menu {
@@ -35,10 +34,6 @@ class Menu {
void OnInputEvent(std::unique_ptr event);
- void Draw();
-
- void ContextLost();
-
void SetOptionEnabled(Option o, bool enable);
void Show();
@@ -54,8 +49,6 @@ class Menu {
bool hide = false;
};
- std::shared_ptr tex_;
-
Item items_[kOption_Max];
int max_text_width_ = 0;
@@ -64,6 +57,8 @@ class Menu {
base::Vector2 tap_pos_[2] = {{0, 0}, {0, 0}};
+ bool CreateRenderResources();
+
std::unique_ptr CreateImage();
bool IsAnimating();
diff --git a/src/demo/player.cc b/src/demo/player.cc
index 5f0bbbd..cd8d77e 100644
--- a/src/demo/player.cc
+++ b/src/demo/player.cc
@@ -1,7 +1,5 @@
#include "player.h"
-#include
-
#include "../base/log.h"
#include "../engine/engine.h"
#include "../engine/image.h"
@@ -21,9 +19,7 @@ constexpr int wepon_anim_speed = 48;
} // namespace
-Player::Player()
- : weapon_tex_(Engine::Get().CreateRenderResource()),
- beam_tex_(Engine::Get().CreateRenderResource()) {}
+Player::Player() = default;
Player::~Player() = default;
@@ -34,10 +30,6 @@ bool Player::Initialize() {
return true;
}
-void Player::ContextLost() {
- CreateRenderResources();
-}
-
void Player::Update(float delta_time) {
for (int i = 0; i < 2; ++i) {
warmup_animator_[i].Update(delta_time);
@@ -63,15 +55,6 @@ void Player::OnInputEvent(std::unique_ptr event) {
DragCancel();
}
-void Player::Draw(float frame_frac) {
- for (int i = 0; i < 2; ++i) {
- drag_sign_[i].Draw();
- beam_[i].Draw();
- beam_spark_[i].Draw();
- weapon_[i].Draw();
- }
-}
-
Vector2 Player::GetWeaponPos(DamageType type) const {
return Engine::Get().GetScreenSize() /
Vector2(type == kDamageType_Green ? 3.5f : -3.5f, -2) +
@@ -93,7 +76,7 @@ DamageType Player::GetWeaponType(const Vector2& pos) {
}
}
- assert(closest_weapon != kDamageType_Invalid);
+ DCHECK(closest_weapon != kDamageType_Invalid);
if (closest_dist < weapon_[closest_weapon].GetScale().x * 0.9f)
return closest_weapon;
return kDamageType_Invalid;
@@ -157,27 +140,27 @@ bool Player::IsFiring(DamageType type) {
void Player::SetupWeapons() {
for (int i = 0; i < 2; ++i) {
// Setup draw sign.
- drag_sign_[i].Create(weapon_tex_, {8, 2});
- drag_sign_[i].AutoScale();
+ drag_sign_[i].Create("weapon_tex", {8, 2});
+ drag_sign_[i].SetZOrder(21);
drag_sign_[i].SetFrame(i * 8);
// Setup weapon.
- weapon_[i].Create(weapon_tex_, {8, 2});
- weapon_[i].AutoScale();
+ weapon_[i].Create("weapon_tex", {8, 2});
+ weapon_[i].SetZOrder(24);
weapon_[i].SetVisible(true);
weapon_[i].SetFrame(wepon_warmup_frame[i]);
// Setup beam.
- beam_[i].Create(beam_tex_, {1, 2});
- beam_[i].AutoScale();
+ beam_[i].Create("beam_tex", {1, 2});
+ beam_[i].SetZOrder(22);
beam_[i].SetFrame(i);
beam_[i].PlaceToRightOf(weapon_[i]);
beam_[i].Translate(weapon_[i].GetScale() * Vector2(-0.5f, 0));
beam_[i].SetPivot(beam_[i].GetOffset());
// Setup beam spark.
- beam_spark_[i].Create(weapon_tex_, {8, 2});
- beam_spark_[i].AutoScale();
+ beam_spark_[i].Create("weapon_tex", {8, 2});
+ beam_spark_[i].SetZOrder(23);
beam_spark_[i].SetFrame(i * 8 + 1);
beam_spark_[i].PlaceToRightOf(weapon_[i]);
beam_spark_[i].Translate(weapon_[i].GetScale() * Vector2(-0.5f, 0));
@@ -338,17 +321,8 @@ void Player::NavigateBack() {
}
bool Player::CreateRenderResources() {
- auto weapon_image = std::make_unique();
- if (!weapon_image->Load("enemy_anims_flare_ok.png"))
- return false;
- auto beam_image = std::make_unique();
- if (!beam_image->Load("enemy_ray_ok.png"))
- return false;
+ Engine::Get().SetImageSource("weapon_tex", "enemy_anims_flare_ok.png", true);
+ Engine::Get().SetImageSource("beam_tex", "enemy_ray_ok.png", true);
- weapon_image->Compress();
- beam_image->Compress();
-
- weapon_tex_->Update(std::move(weapon_image));
- beam_tex_->Update(std::move(beam_image));
return true;
}
diff --git a/src/demo/player.h b/src/demo/player.h
index 76d1822..c34bd01 100644
--- a/src/demo/player.h
+++ b/src/demo/player.h
@@ -6,7 +6,6 @@
#include "../base/vecmath.h"
#include "../engine/animator.h"
#include "../engine/image_quad.h"
-#include "../engine/renderer/texture.h"
#include "damage_type.h"
namespace eng {
@@ -20,21 +19,14 @@ class Player {
bool Initialize();
- void ContextLost();
-
void Update(float delta_time);
void OnInputEvent(std::unique_ptr event);
- void Draw(float frame_frac);
-
base::Vector2 GetWeaponPos(DamageType type) const;
base::Vector2 GetWeaponScale() const;
private:
- std::shared_ptr weapon_tex_;
- std::shared_ptr beam_tex_;
-
eng::ImageQuad drag_sign_[2];
eng::ImageQuad weapon_[2];
eng::ImageQuad beam_[2];
diff --git a/src/demo/sky_quad.cc b/src/demo/sky_quad.cc
index b9bab2e..d6b926f 100644
--- a/src/demo/sky_quad.cc
+++ b/src/demo/sky_quad.cc
@@ -31,6 +31,8 @@ bool SkyQuad::Create() {
color_animator_.Attach(this);
+ SetVisible(true);
+
return true;
}
diff --git a/src/demo/sky_quad.h b/src/demo/sky_quad.h
index 271c7bf..72cac08 100644
--- a/src/demo/sky_quad.h
+++ b/src/demo/sky_quad.h
@@ -33,13 +33,15 @@ class SkyQuad : public eng::Animatable {
void SetColor(const base::Vector4& color) override { nebula_color_ = color; }
base::Vector4 GetColor() const override { return nebula_color_; }
- void Draw(float frame_frac);
+ // Drawable interface.
+ void Draw(float frame_frac) override;
+
void ContextLost();
void SwitchColor(const base::Vector4& color);
private:
- std::shared_ptr shader_;
+ std::unique_ptr shader_;
base::Vector2 sky_offset_ = {0, 0};
base::Vector2 last_sky_offset_ = {0, 0};
diff --git a/src/engine/BUILD.gn b/src/engine/BUILD.gn
new file mode 100644
index 0000000..e9fd64e
--- /dev/null
+++ b/src/engine/BUILD.gn
@@ -0,0 +1,95 @@
+source_set("engine") {
+ sources =
+ [
+ "animatable.cc",
+ "animatable.h",
+ "animator.cc",
+ "animator.h",
+ "audio/audio_base.cc",
+ "audio/audio_base.h",
+ "audio/audio_forward.h",
+ "audio/audio_resource.cc",
+ "audio/audio_resource.h",
+ "audio/audio_sample.h",
+ "audio/audio.h",
+ "drawable.cc",
+ "drawable.h",
+ "engine.cc",
+ "engine.h",
+ "font.cc",
+ "font.h",
+ "game_factory.h",
+ "game.h",
+ "image_quad.cc",
+ "image_quad.h",
+ "image.cc",
+ "image.h",
+ "input_event.h",
+ "mesh.cc",
+ "mesh.h",
+ "platform/asset_file.cc",
+ "platform/asset_file.h",
+ "platform/platform_base.cc",
+ "platform/platform_base.h",
+ "platform/platform_forward.h",
+ "platform/platform.h",
+ "renderer/geometry.cc",
+ "renderer/geometry.h",
+ "renderer/opengl.h",
+ "renderer/render_command.cc",
+ "renderer/render_command.h",
+ "renderer/render_resource.cc",
+ "renderer/render_resource.h",
+ "renderer/renderer_types.cc",
+ "renderer/renderer_types.h",
+ "renderer/renderer.cc",
+ "renderer/renderer.h",
+ "renderer/shader.cc",
+ "renderer/shader.h",
+ "renderer/texture.cc",
+ "renderer/texture.h",
+ "shader_source.cc",
+ "shader_source.h",
+ "solid_quad.cc",
+ "solid_quad.h",
+ "sound_player.cc",
+ "sound_player.h",
+ "sound.cc",
+ "sound.h",
+ ]
+
+ ldflags = [] libs = []
+
+ if (target_os == "linux") {
+ sources +=
+ [
+ "audio/audio_alsa.cc",
+ "audio/audio_alsa.h",
+ "platform/asset_file_linux.cc",
+ "platform/platform_linux.cc",
+ "platform/platform_linux.h",
+ "renderer/renderer_linux.cc",
+ ]
+
+#ldflags += ["-L/usr/X11R6/lib"]
+
+ libs += [ "X11", "GL", "asound", "pthread" ]
+ }
+
+ if (target_os == "android") {
+ sources += [
+ "audio/audio_oboe.cc",
+ "audio/audio_oboe.h",
+ "platform/asset_file_android.cc",
+ "platform/platform_android.cc",
+ "platform/platform_android.h",
+ "renderer/renderer_android.cc",
+ ]
+
+#ldflags += ["-L/usr/X11R6/lib"]
+
+#libs += ["X11", "rt", "pthread"]
+ }
+
+ deps = []
+}
diff --git a/src/engine/animatable.h b/src/engine/animatable.h
index 446e3d0..1bf070b 100644
--- a/src/engine/animatable.h
+++ b/src/engine/animatable.h
@@ -2,13 +2,14 @@
#define SHAPE_H
#include "../base/vecmath.h"
+#include "drawable.h"
namespace eng {
-class Animatable {
+class Animatable : public Drawable {
public:
Animatable() = default;
- virtual ~Animatable() = default;
+ ~Animatable() override = default;
void Translate(const base::Vector2& offset);
void Scale(const base::Vector2& scale);
@@ -33,9 +34,6 @@ class Animatable {
virtual void SetColor(const base::Vector4& color) = 0;
virtual base::Vector4 GetColor() const = 0;
- void SetVisible(bool visible) { visible_ = visible; }
- bool IsVisible() const { return visible_; }
-
void PlaceToLeftOf(const Animatable& s) {
Translate({s.GetScale().x / -2.0f + GetScale().x / -2.0f, 0});
}
@@ -58,7 +56,6 @@ class Animatable {
base::Vector2 pivot_ = {0, 0};
base::Vector2 rotation_ = {0, 1};
float theta_ = 0;
- bool visible_ = false;
};
} // namespace eng
diff --git a/src/engine/animator.cc b/src/engine/animator.cc
index 2a34669..4307e36 100644
--- a/src/engine/animator.cc
+++ b/src/engine/animator.cc
@@ -43,6 +43,33 @@ void Animator::Stop(int animation) {
loop_flags_ &= ~animation;
}
+float Animator::GetTime(int animation) {
+ if ((animation & kMovement) != 0)
+ return movement_time_;
+ if ((animation & kRotation) != 0)
+ return rotation_time_;
+ if ((animation & kBlending) != 0)
+ return blending_time_;
+ if ((animation & kFrames) != 0)
+ return frame_time_;
+ return timer_time_;
+}
+
+void Animator::SetTime(int animation, float time) {
+ DCHECK(time >= 0 && time <= 1);
+
+ if ((animation & kMovement) != 0)
+ movement_time_ = time;
+ if ((animation & kRotation) != 0)
+ rotation_time_ = time;
+ if ((animation & kBlending) != 0)
+ blending_time_ = time;
+ if ((animation & kFrames) != 0)
+ frame_time_ = time;
+ if ((animation & kTimer) != 0)
+ timer_time_ = time;
+}
+
void Animator::SetEndCallback(int animation, base::Closure cb) {
if ((inside_cb_ & animation) != 0) {
has_pending_cb_ = true;
diff --git a/src/engine/animator.h b/src/engine/animator.h
index 4c2d952..8d0e3a4 100644
--- a/src/engine/animator.h
+++ b/src/engine/animator.h
@@ -28,47 +28,39 @@ class Animator {
Animator() = default;
~Animator() = default;
- // Attached the given animatable to this animator and sets the start values.
void Attach(Animatable* animatable);
void Play(int animation, bool loop);
void Pause(int animation);
void Stop(int animation);
- // Set callback for the given animations. It's called for each animation once
- // it ends. Not that it's not called for looping animations.
+ // Get/set current time of the given animation.
+ float GetTime(int animation);
+ void SetTime(int animation, float time);
+
+ // Set callback ro be called once animation ends.
void SetEndCallback(int animation, base::Closure cb);
- // Set movement animation parameters. Movement is relative to the attached
- // animatable's current position. Distance is calculated from the magnitude of
- // direction vector. Duration is in seconds.
+ // Distance is the magnitude of direction vector. Duration is in seconds.
void SetMovement(base::Vector2 direction,
float duration,
Interpolator interpolator = nullptr);
- // Set rotation animation parameters. Rotation is relative to the attached
- // animatable's current rotation. Duration is in seconds.
+ // Rotation is in radian. Duration is in seconds.
void SetRotation(float target,
float duration,
Interpolator interpolator = nullptr);
- // Set color blending animation parameters. Color blending animation is
- // absolute. The absolute start colors are obtained from the attached
- // animatables. Duration is in seconds.
void SetBlending(base::Vector4 target,
float duration,
Interpolator interpolator = nullptr);
- // Set frame playback animation parameters. Frame animation is absolute. The
- // absolute start frames are obtained from the attached animatables. Plays
- // count number of frames.
+ // Plays count number of frames.
void SetFrames(int count,
int frames_per_second,
Interpolator interpolator = nullptr);
- // Set Timer parameters. Timer doesn't play any animation. Usefull for
- // triggering a callback after the given seconds passed. Loop parameter is
- // ignored when played.
+ // Triggers a callback after the given seconds passed.
void SetTimer(float duration);
// Set visibility of all attached animatables.
diff --git a/src/engine/audio/audio_alsa.cc b/src/engine/audio/audio_alsa.cc
index cc39e96..a56fddf 100644
--- a/src/engine/audio/audio_alsa.cc
+++ b/src/engine/audio/audio_alsa.cc
@@ -2,6 +2,8 @@
#include
+#include
+
#include "../../base/log.h"
#include "audio_resource.h"
@@ -21,10 +23,10 @@ bool AudioAlsa::Initialize() {
// Contains information about the hardware.
snd_pcm_hw_params_t* hw_params;
- // "default" is usualy PulseAudio. Use "plughw:CARD=PCH" instead for direct
- // hardware device with software format conversion.
- if ((err = snd_pcm_open(&pcm_handle_, "plughw:CARD=PCH",
- SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
+ // TODO: "default" is usualy PulseAudio. Select a device with "plughw" for
+ // direct hardware device with software format conversion.
+ if ((err = snd_pcm_open(&device_, "default", SND_PCM_STREAM_PLAYBACK, 0)) <
+ 0) {
LOG << "Cannot open audio device. Error: " << snd_strerror(err);
return false;
}
@@ -34,65 +36,65 @@ bool AudioAlsa::Initialize() {
snd_pcm_hw_params_alloca(&hw_params);
// Init hw_params with full configuration space.
- if ((err = snd_pcm_hw_params_any(pcm_handle_, hw_params)) < 0) {
+ if ((err = snd_pcm_hw_params_any(device_, hw_params)) < 0) {
LOG << "Cannot initialize hardware parameter structure. Error: "
<< snd_strerror(err);
break;
}
if ((err = snd_pcm_hw_params_set_access(
- pcm_handle_, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
+ device_, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
LOG << "Cannot set access type. Error: " << snd_strerror(err);
break;
}
- if ((err = snd_pcm_hw_params_set_format(pcm_handle_, hw_params,
+ if ((err = snd_pcm_hw_params_set_format(device_, hw_params,
SND_PCM_FORMAT_FLOAT)) < 0) {
LOG << "Cannot set sample format. Error: " << snd_strerror(err);
break;
}
// Disable software resampler.
- if ((err = snd_pcm_hw_params_set_rate_resample(pcm_handle_, hw_params, 0)) <
+ if ((err = snd_pcm_hw_params_set_rate_resample(device_, hw_params, 0)) <
0) {
LOG << "Cannot disbale software resampler. Error: " << snd_strerror(err);
break;
}
unsigned sample_rate = 48000;
- if ((err = snd_pcm_hw_params_set_rate_near(pcm_handle_, hw_params,
- &sample_rate, 0)) < 0) {
+ if ((err = snd_pcm_hw_params_set_rate_near(device_, hw_params, &sample_rate,
+ 0)) < 0) {
LOG << "Cannot set sample rate. Error: " << snd_strerror(err);
break;
}
- if ((err = snd_pcm_hw_params_set_channels(pcm_handle_, hw_params, 2)) < 0) {
+ if ((err = snd_pcm_hw_params_set_channels(device_, hw_params, 2)) < 0) {
LOG << "Cannot set channel count. Error: " << snd_strerror(err);
break;
}
// Set period time to 4 ms. The latency will be 12 ms for 3 perods.
unsigned period_time = 4000;
- if ((err = snd_pcm_hw_params_set_period_time_near(pcm_handle_, hw_params,
+ if ((err = snd_pcm_hw_params_set_period_time_near(device_, hw_params,
&period_time, 0)) < 0) {
LOG << "Cannot set periods. Error: " << snd_strerror(err);
break;
}
unsigned periods = 3;
- if ((err = snd_pcm_hw_params_set_periods_near(pcm_handle_, hw_params,
- &periods, 0)) < 0) {
+ if ((err = snd_pcm_hw_params_set_periods_near(device_, hw_params, &periods,
+ 0)) < 0) {
LOG << "Cannot set periods. Error: " << snd_strerror(err);
break;
}
// Apply HW parameter settings to PCM device and prepare device.
- if ((err = snd_pcm_hw_params(pcm_handle_, hw_params)) < 0) {
+ if ((err = snd_pcm_hw_params(device_, hw_params)) < 0) {
LOG << "Cannot set parameters. Error: " << snd_strerror(err);
break;
}
- if ((err = snd_pcm_prepare(pcm_handle_)) < 0) {
+ if ((err = snd_pcm_prepare(device_)) < 0) {
LOG << "Cannot prepare audio interface for use. Error: "
<< snd_strerror(err);
break;
@@ -126,60 +128,75 @@ bool AudioAlsa::Initialize() {
sample_rate_ = sample_rate;
period_size_ = period_size;
- StartWorker();
+ StartAudioThread();
return true;
} while (false);
- snd_pcm_close(pcm_handle_);
+ snd_pcm_close(device_);
return false;
}
void AudioAlsa::Shutdown() {
LOG << "Shutting down audio system.";
- TerminateWorker();
- snd_pcm_drop(pcm_handle_);
- snd_pcm_close(pcm_handle_);
+
+ TerminateAudioThread();
+ snd_pcm_drop(device_);
+ snd_pcm_close(device_);
+}
+
+void AudioAlsa::Suspend() {
+ DCHECK(!terminate_audio_thread_.load(std::memory_order_relaxed));
+
+ suspend_audio_thread_.store(true, std::memory_order_relaxed);
+}
+
+void AudioAlsa::Resume() {
+ DCHECK(!terminate_audio_thread_.load(std::memory_order_relaxed));
+
+ suspend_audio_thread_.store(false, std::memory_order_relaxed);
}
size_t AudioAlsa::GetSampleRate() {
return sample_rate_;
}
-bool AudioAlsa::StartWorker() {
+bool AudioAlsa::StartAudioThread() {
LOG << "Starting audio thread.";
- std::promise promise;
- std::future future = promise.get_future();
- worker_thread_ =
- std::thread(&AudioAlsa::WorkerMain, this, std::move(promise));
- return future.get();
+ DCHECK(!terminate_audio_thread_.load(std::memory_order_relaxed));
+
+ audio_thread_ = std::thread(&AudioAlsa::AudioThreadMain, this);
}
-void AudioAlsa::TerminateWorker() {
- // Notify worker thread and wait for it to terminate.
- if (terminate_worker_)
+void AudioAlsa::TerminateAudioThread() {
+ if (terminate_audio_thread_.load(std::memory_order_relaxed))
return;
- terminate_worker_ = true;
+
LOG << "Terminating audio thread";
- worker_thread_.join();
+
+ // Notify worker thread and wait for it to terminate.
+ terminate_audio_thread_.store(true, std::memory_order_relaxed);
+ suspend_audio_thread_.store(true, std::memory_order_relaxed);
+ audio_thread_.join();
}
-void AudioAlsa::WorkerMain(std::promise promise) {
- promise.set_value(true);
-
+void AudioAlsa::AudioThreadMain() {
size_t num_frames = period_size_ / (num_channels_ * sizeof(float));
auto buffer = std::make_unique(num_frames * 2);
for (;;) {
- if (terminate_worker_)
- return;
+ while (suspend_audio_thread_.load(std::memory_order_relaxed)) {
+ if (terminate_audio_thread_.load(std::memory_order_relaxed))
+ return;
+ std::this_thread::yield();
+ }
RenderAudio(buffer.get(), num_frames);
- while (snd_pcm_writei(pcm_handle_, buffer.get(), num_frames) < 0) {
- snd_pcm_prepare(pcm_handle_);
- LOG << "Audio buffer underrun!";
+ while (snd_pcm_writei(device_, buffer.get(), num_frames) < 0) {
+ snd_pcm_prepare(device_);
+ DLOG << "Audio buffer underrun!";
}
}
}
diff --git a/src/engine/audio/audio_alsa.h b/src/engine/audio/audio_alsa.h
index fd54f2f..bb20d83 100644
--- a/src/engine/audio/audio_alsa.h
+++ b/src/engine/audio/audio_alsa.h
@@ -1,8 +1,7 @@
#ifndef AUDIO_ALSA_H
#define AUDIO_ALSA_H
-#include
-#include
+#include
#include
#include "audio_base.h"
@@ -22,23 +21,27 @@ class AudioAlsa : public AudioBase {
void Shutdown();
+ void Suspend();
+ void Resume();
+
size_t GetSampleRate();
private:
// Handle for the PCM device.
- snd_pcm_t* pcm_handle_;
+ snd_pcm_t* device_;
- std::thread worker_thread_;
- bool terminate_worker_ = false;
+ std::thread audio_thread_;
+ std::atomic terminate_audio_thread_ = false;
+ std::atomic suspend_audio_thread_ = false;
size_t num_channels_ = 0;
size_t sample_rate_ = 0;
size_t period_size_ = 0;
- bool StartWorker();
- void TerminateWorker();
+ bool StartAudioThread();
+ void TerminateAudioThread();
- void WorkerMain(std::promise promise);
+ void AudioThreadMain();
};
} // namespace eng
diff --git a/src/engine/audio/audio_base.cc b/src/engine/audio/audio_base.cc
index e6ea592..1e24aa8 100644
--- a/src/engine/audio/audio_base.cc
+++ b/src/engine/audio/audio_base.cc
@@ -1,32 +1,33 @@
-#include "audio_base.h"
+#include "audio.h"
#include
#include "../../base/log.h"
+#include "../../base/task_runner.h"
+#include "../../base/worker.h"
#include "../sound.h"
using namespace base;
namespace eng {
-AudioBase::AudioBase() = default;
+AudioBase::AudioBase()
+ : main_thread_task_runner_(TaskRunner::GetThreadLocalTaskRunner()) {}
-AudioBase::~AudioBase() {
- worker_.Join();
-}
+AudioBase::~AudioBase() = default;
void AudioBase::Play(std::shared_ptr sample) {
- std::unique_lock scoped_lock(mutex_);
- samples_[0].push_back(sample);
-}
-
-void AudioBase::Update() {
- task_runner_.Run();
+ if (audio_enabled_) {
+ std::lock_guard scoped_lock(lock_);
+ samples_[0].push_back(sample);
+ } else {
+ sample->active = false;
+ }
}
void AudioBase::RenderAudio(float* output_buffer, size_t num_frames) {
{
- std::unique_lock scoped_lock(mutex_);
+ std::lock_guard scoped_lock(lock_);
samples_[1].splice(samples_[1].end(), samples_[0]);
}
@@ -35,87 +36,98 @@ void AudioBase::RenderAudio(float* output_buffer, size_t num_frames) {
for (auto it = samples_[1].begin(); it != samples_[1].end();) {
AudioSample* sample = it->get();
- unsigned flags = sample->flags;
- bool remove = false;
+ auto sound = sample->sound.get();
+ unsigned flags = sample->flags.load(std::memory_order_relaxed);
if (flags & AudioSample::kStopped) {
- remove = true;
- } else {
- auto sound = sample->sound.get();
-
- const float* src[2] = {const_cast(sound)->GetBuffer(0),
- const_cast(sound)->GetBuffer(1)};
+ sample->marked_for_removal = true;
+ } else if (!sample->marked_for_removal) {
+ const float* src[2] = {sound->GetBuffer(0), sound->GetBuffer(1)};
if (!src[1])
src[1] = src[0]; // mono.
size_t num_samples = sound->GetNumSamples();
- size_t num_channels = sound->num_channels();
size_t src_index = sample->src_index;
- size_t step = sample->step;
+ size_t step = sample->step.load(std::memory_order_relaxed);
size_t accumulator = sample->accumulator;
float amplitude = sample->amplitude;
- float amplitude_inc = sample->amplitude_inc;
- float max_amplitude = sample->max_amplitude;
+ float amplitude_inc =
+ sample->amplitude_inc.load(std::memory_order_relaxed);
+ float max_amplitude =
+ sample->max_amplitude.load(std::memory_order_relaxed);
size_t channel_offset =
- (flags & AudioSample::kSimulateStereo) && num_channels == 1
+ (flags & AudioSample::kSimulateStereo) && !sound->is_streaming_sound()
? sound->hz() / 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 & AudioSample::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 & AudioSample::kLoop)
+ output_buffer[i++] += src[1][ind % num_samples] * amplitude;
+ else
+ i++;
- // Apply amplitude modification.
- amplitude += amplitude_inc;
- if (amplitude <= 0) {
- remove = true;
- break;
- } else if (amplitude > max_amplitude) {
- amplitude = max_amplitude;
+ // Apply amplitude modification.
+ amplitude += amplitude_inc;
+ if (amplitude <= 0) {
+ sample->marked_for_removal = true;
+ break;
+ } else if (amplitude > max_amplitude) {
+ amplitude = max_amplitude;
+ }
+
+ // Basic resampling for variations.
+ accumulator += step;
+ src_index += accumulator / 10;
+ accumulator %= 10;
}
- // Basic resampling for variations.
- accumulator += step;
- src_index += accumulator / 10;
- accumulator %= 10;
-
// Advance source index.
if (src_index >= num_samples) {
if (!sound->is_streaming_sound()) {
- if (flags & AudioSample::kLoop) {
- src_index %= num_samples;
- } else {
- remove = true;
+ src_index %= num_samples;
+
+ if (!(flags & AudioSample::kLoop)) {
+ sample->marked_for_removal = true;
break;
}
- } else if (!sound->IsStreamingInProgress()) {
+ } else if (!sample->streaming_in_progress.load(
+ std::memory_order_acquire)) {
+ if (num_samples)
+ src_index %= num_samples;
+
if (sound->eof()) {
- remove = true;
+ sample->marked_for_removal = true;
break;
}
- src_index = 0;
+ sample->streaming_in_progress.store(true,
+ std::memory_order_relaxed);
// Swap buffers and start streaming in background.
sound->SwapBuffers();
- src[0] = const_cast(sound)->GetBuffer(0);
- src[1] = const_cast(sound)->GetBuffer(1);
+ src[0] = sound->GetBuffer(0);
+ src[1] = sound->GetBuffer(1);
+ if (!src[1])
+ src[1] = src[0]; // mono.
+ num_samples = sound->GetNumSamples();
- worker_.Enqueue(std::bind(&Sound::Stream, sample->sound,
- flags & AudioSample::kLoop));
- } else {
- LOG << "Buffer underrun!";
- src_index = 0;
+ Worker::Get().EnqueueTask(HERE,
+ std::bind(&AudioBase::DoStream, this, *it,
+ flags & AudioSample::kLoop));
+ } else if (num_samples) {
+ DLOG << "Buffer underrun!";
+ src_index %= num_samples;
}
}
}
@@ -125,9 +137,12 @@ void AudioBase::RenderAudio(float* output_buffer, size_t num_frames) {
sample->amplitude = amplitude;
}
- if (remove) {
- task_runner_.Enqueue(sample->end_cb);
- sample->active = false;
+ if (sample->marked_for_removal &&
+ (!sound->is_streaming_sound() ||
+ !sample->streaming_in_progress.load(std::memory_order_relaxed))) {
+ sample->marked_for_removal = false;
+ main_thread_task_runner_->EnqueueTask(
+ HERE, std::bind(&AudioBase::EndCallback, this, *it));
it = samples_[1].erase(it);
} else {
++it;
@@ -135,4 +150,21 @@ void AudioBase::RenderAudio(float* output_buffer, size_t num_frames) {
}
}
+void AudioBase::DoStream(std::shared_ptr sample, bool loop) {
+ sample->sound->Stream(loop);
+
+ // Memory barrier to ensure all memory writes become visible to the audio
+ // thread.
+ sample->streaming_in_progress.store(false, std::memory_order_release);
+}
+
+void AudioBase::EndCallback(std::shared_ptr sample) {
+ AudioSample* s = sample.get();
+
+ s->active = false;
+
+ if (s->end_cb)
+ s->end_cb();
+}
+
} // namespace eng
diff --git a/src/engine/audio/audio_base.h b/src/engine/audio/audio_base.h
index 217bd84..f184546 100644
--- a/src/engine/audio/audio_base.h
+++ b/src/engine/audio/audio_base.h
@@ -3,37 +3,47 @@
#include
#include
-#include
#include "../../base/closure.h"
-#include "../../base/task_runner.h"
-#include "../../base/worker.h"
+#include "../../base/spinlock.h"
#include "audio_sample.h"
+namespace base {
+class TaskRunner;
+}
+
namespace eng {
class Sound;
class AudioBase {
public:
- void Play(std::shared_ptr impl_data);
+ void Play(std::shared_ptr sample);
- void Update();
+ void SetEnableAudio(bool enable) { audio_enabled_ = enable; }
protected:
static constexpr int kChannelCount = 2;
- std::list> samples_[2];
- std::mutex mutex_;
-
- base::Worker worker_{1};
-
- base::TaskRunner task_runner_;
-
AudioBase();
~AudioBase();
void RenderAudio(float* output_buffer, size_t num_frames);
+
+ private:
+ std::list> samples_[2];
+ base::Spinlock lock_;
+
+ base::TaskRunner* main_thread_task_runner_;
+
+ bool audio_enabled_ = true;
+
+ void DoStream(std::shared_ptr sample, bool loop);
+
+ void EndCallback(std::shared_ptr sample);
+
+ AudioBase(const AudioBase&) = delete;
+ AudioBase& operator=(const AudioBase&) = delete;
};
} // namespace eng
diff --git a/src/engine/audio/audio_oboe.cc b/src/engine/audio/audio_oboe.cc
index fc626d8..27e19d0 100644
--- a/src/engine/audio/audio_oboe.cc
+++ b/src/engine/audio/audio_oboe.cc
@@ -20,6 +20,16 @@ bool AudioOboe::Initialize() {
void AudioOboe::Shutdown() {
LOG << "Shutting down audio system.";
+
+ stream_->stop();
+}
+
+void AudioOboe::Suspend() {
+ stream_->pause();
+}
+
+void AudioOboe::Resume() {
+ stream_->start();
}
size_t AudioOboe::GetSampleRate() {
diff --git a/src/engine/audio/audio_oboe.h b/src/engine/audio/audio_oboe.h
index ae91a35..f30564e 100644
--- a/src/engine/audio/audio_oboe.h
+++ b/src/engine/audio/audio_oboe.h
@@ -20,6 +20,9 @@ class AudioOboe : public AudioBase {
void Shutdown();
+ void Suspend();
+ void Resume();
+
size_t GetSampleRate();
private:
diff --git a/src/engine/audio/audio_resource.cc b/src/engine/audio/audio_resource.cc
index abdcda6..418438c 100644
--- a/src/engine/audio/audio_resource.cc
+++ b/src/engine/audio/audio_resource.cc
@@ -5,64 +5,84 @@
#include "audio.h"
#include "audio_sample.h"
+using namespace base;
+
namespace eng {
AudioResource::AudioResource(Audio* audio)
: sample_(std::make_shared()), audio_(audio) {}
AudioResource::~AudioResource() {
- sample_->flags |= AudioSample::kStopped;
+ sample_->flags.fetch_or(AudioSample::kStopped, std::memory_order_relaxed);
}
void AudioResource::Play(std::shared_ptr sound,
float amplitude,
bool reset_pos) {
- if (sample_->active)
+ AudioSample* sample = sample_.get();
+
+ if (sample->active) {
+ if (reset_pos)
+ sample_->flags.fetch_or(AudioSample::kStopped, std::memory_order_relaxed);
+
+ if (reset_pos ||
+ sample->flags.load(std::memory_order_relaxed) & AudioSample::kStopped) {
+ Closure ocb = sample_->end_cb;
+ SetEndCallback([&, sound, amplitude, reset_pos, ocb]() -> void {
+ Play(sound, amplitude, reset_pos);
+ SetEndCallback(ocb);
+ });
+ }
+
return;
+ }
if (reset_pos) {
- sample_->src_index = 0;
- sample_->accumulator = 0;
+ sample->src_index = 0;
+ sample->accumulator = 0;
+ sound->ResetStream();
}
- sample_->flags &= ~AudioSample::kStopped;
- sample_->sound = sound;
- sample_->amplitude = amplitude;
- sample_->active = true;
+
+ sample->active = true;
+ sample_->flags.fetch_and(~AudioSample::kStopped, std::memory_order_relaxed);
+ sample->sound = sound;
+ if (amplitude >= 0)
+ sample->amplitude = amplitude;
audio_->Play(sample_);
}
void AudioResource::Stop() {
- if (!sample_->active)
- return;
-
- sample_->flags |= AudioSample::kStopped;
+ if (sample_->active)
+ sample_->flags.fetch_or(AudioSample::kStopped, std::memory_order_relaxed);
}
void AudioResource::SetLoop(bool loop) {
if (loop)
- sample_->flags |= AudioSample::kLoop;
+ sample_->flags.fetch_or(AudioSample::kLoop, std::memory_order_relaxed);
else
- sample_->flags &= ~AudioSample::kLoop;
+ sample_->flags.fetch_and(AudioSample::kLoop, std::memory_order_relaxed);
}
void AudioResource::SetSimulateStereo(bool simulate) {
if (simulate)
- sample_->flags |= AudioSample::kSimulateStereo;
+ sample_->flags.fetch_or(AudioSample::kSimulateStereo,
+ std::memory_order_relaxed);
else
- sample_->flags &= ~AudioSample::kSimulateStereo;
+ sample_->flags.fetch_and(AudioSample::kSimulateStereo,
+ std::memory_order_relaxed);
}
void AudioResource::SetResampleStep(size_t step) {
- sample_->step = step + 10;
+ sample_->step.store(step + 10, std::memory_order_relaxed);
}
void AudioResource::SetMaxAmplitude(float max_amplitude) {
- sample_->max_amplitude = max_amplitude;
+ sample_->max_amplitude.store(max_amplitude, std::memory_order_relaxed);
}
void AudioResource::SetAmplitudeInc(float amplitude_inc) {
- sample_->amplitude_inc = amplitude_inc;
+ sample_->amplitude_inc.store(amplitude_inc, std::memory_order_relaxed);
}
void AudioResource::SetEndCallback(base::Closure cb) {
diff --git a/src/engine/audio/audio_sample.h b/src/engine/audio/audio_sample.h
index 5d11d7a..bbfb3c3 100644
--- a/src/engine/audio/audio_sample.h
+++ b/src/engine/audio/audio_sample.h
@@ -1,6 +1,7 @@
#ifndef AUDIO_SAMPLE_H
#define AUDIO_SAMPLE_H
+#include
#include
#include "../../base/closure.h"
@@ -12,19 +13,27 @@ class Sound;
struct AudioSample {
enum SampleFlags { kLoop = 1, kStopped = 2, kSimulateStereo = 4 };
- // Read-only accessed by the audio thread.
- std::shared_ptr sound;
- unsigned flags = 0;
- size_t step = 10;
- float amplitude_inc = 0;
- float max_amplitude = 1.0f;
+ // Accessed by main thread only.
+ bool active = false;
base::Closure end_cb;
- // Write accessed by the audio thread.
+ // Accessed by audio thread only.
+ bool marked_for_removal = false;
+
+ // Initialized by main thread, used by audio thread.
+ std::shared_ptr sound;
size_t src_index = 0;
size_t accumulator = 0;
float amplitude = 1.0f;
- bool active = false;
+
+ // Write accessed by main thread, read-only accessed by audio thread.
+ std::atomic flags{0};
+ std::atomic step{10};
+ std::atomic amplitude_inc{0};
+ std::atomic max_amplitude{1.0f};
+
+ // Accessed by audio thread and decoder thread.
+ std::atomic streaming_in_progress{false};
};
} // namespace eng
diff --git a/src/engine/drawable.cc b/src/engine/drawable.cc
new file mode 100644
index 0000000..02eac41
--- /dev/null
+++ b/src/engine/drawable.cc
@@ -0,0 +1,14 @@
+#include "drawable.h"
+#include "engine.h"
+
+namespace eng {
+
+Drawable::Drawable() {
+ Engine::Get().AddDrawable(this);
+}
+
+Drawable::~Drawable() {
+ Engine::Get().RemoveDrawable(this);
+}
+
+} // namespace eng
diff --git a/src/engine/drawable.h b/src/engine/drawable.h
new file mode 100644
index 0000000..70def7b
--- /dev/null
+++ b/src/engine/drawable.h
@@ -0,0 +1,31 @@
+#ifndef DRAWABLE_H
+#define DRAWABLE_H
+
+#include "../base/vecmath.h"
+
+namespace eng {
+
+class Drawable {
+ public:
+ Drawable();
+ virtual ~Drawable();
+
+ Drawable(const Drawable&) = delete;
+ Drawable& operator=(const Drawable&) = delete;
+
+ virtual void Draw(float frame_frac) = 0;
+
+ void SetZOrder(int z) { z_order_ = z; }
+ void SetVisible(bool visible) { visible_ = visible; }
+
+ int GetZOrder() const { return z_order_; }
+ bool IsVisible() const { return visible_; }
+
+ private:
+ bool visible_ = false;
+ int z_order_ = 0;
+};
+
+} // namespace eng
+
+#endif // DRAWABLE_H
diff --git a/src/engine/engine.cc b/src/engine/engine.cc
index f41846d..5f80358 100644
--- a/src/engine/engine.cc
+++ b/src/engine/engine.cc
@@ -1,14 +1,16 @@
#include "engine.h"
#include "../base/log.h"
-#include "../base/worker.h"
#include "../third_party/texture_compressor/texture_compressor.h"
+#include "animator.h"
#include "audio/audio.h"
#include "audio/audio_resource.h"
+#include "drawable.h"
#include "font.h"
#include "game.h"
#include "game_factory.h"
#include "image.h"
+#include "image_quad.h"
#include "input_event.h"
#include "mesh.h"
#include "platform/platform.h"
@@ -27,7 +29,7 @@ Engine* Engine::singleton = nullptr;
Engine::Engine(Platform* platform, Renderer* renderer, Audio* audio)
: platform_(platform), renderer_(renderer), audio_(audio) {
- assert(!singleton);
+ DCHECK(!singleton);
singleton = this;
renderer_->SetContextLostCB(std::bind(&Engine::ContextLost, this));
@@ -35,9 +37,15 @@ Engine::Engine(Platform* platform, Renderer* renderer, Audio* audio)
quad_ = CreateRenderResource();
pass_through_shader_ = CreateRenderResource();
solid_shader_ = CreateRenderResource();
+
+ stats_ = std::make_unique();
+ stats_->SetZOrder(std::numeric_limits::max());
}
Engine::~Engine() {
+ game_.reset();
+ stats_.reset();
+
singleton = nullptr;
}
@@ -83,6 +91,8 @@ bool Engine::Initialize() {
if (!CreateRenderResources())
return false;
+ SetImageSource("stats_tex", std::bind(&Engine::PrintStats, this));
+
game_ = GameFactoryBase::CreateGame("");
if (!game_) {
printf("No game found to run.\n");
@@ -104,45 +114,66 @@ void Engine::Shutdown() {
void Engine::Update(float delta_time) {
seconds_accumulated_ += delta_time;
- audio_->Update();
- renderer_->Update();
-
game_->Update(delta_time);
+ // Destroy unused textures.
+ for (auto& t : textures_) {
+ if (!t.second.persistent && t.second.texture.use_count() == 1)
+ t.second.texture->Destroy();
+ }
+
fps_seconds_ += delta_time;
if (fps_seconds_ >= 1) {
fps_ = renderer_->GetAndResetFPS();
fps_seconds_ = 0;
}
- if (stats_.IsVisible())
- PrintStats();
+ if (stats_->IsVisible()) {
+ RefreshImage("stats_tex");
+ stats_->AutoScale();
+ }
}
void Engine::Draw(float frame_frac) {
- auto cmd = std::make_unique();
- cmd->rgba = {0, 0, 0, 1};
- renderer_->EnqueueCommand(std::move(cmd));
- renderer_->EnqueueCommand(std::make_unique());
+ drawables_.sort(
+ [](auto& a, auto& b) { return a->GetZOrder() < b->GetZOrder(); });
- game_->Draw(frame_frac);
-
- if (stats_.IsVisible())
- stats_.Draw();
+ for (auto d : drawables_) {
+ if (d->IsVisible())
+ d->Draw(frame_frac);
+ }
renderer_->EnqueueCommand(std::make_unique());
}
void Engine::LostFocus() {
+ audio_->Suspend();
+
if (game_)
game_->LostFocus();
}
void Engine::GainedFocus() {
+ audio_->Resume();
+
if (game_)
game_->GainedFocus();
}
+void Engine::AddDrawable(Drawable* drawable) {
+ DCHECK(std::find(drawables_.begin(), drawables_.end(), drawable) ==
+ drawables_.end());
+ drawables_.push_back(drawable);
+}
+
+void Engine::RemoveDrawable(Drawable* drawable) {
+ auto it = std::find(drawables_.begin(), drawables_.end(), drawable);
+ if (it != drawables_.end()) {
+ drawables_.erase(it);
+ return;
+ }
+}
+
void Engine::Exit() {
platform_->Exit();
}
@@ -156,32 +187,117 @@ Vector2 Engine::ToPosition(const Vector2& vec) {
return ToScale(vec) - GetScreenSize() / 2.0f;
}
-std::shared_ptr Engine::CreateAudioResource() {
- return std::make_shared(audio_);
+void Engine::SetImageSource(const std::string& asset_name,
+ const std::string& file_name,
+ bool persistent) {
+ std::shared_ptr texture;
+ auto it = textures_.find(asset_name);
+ if (it != textures_.end()) {
+ texture = it->second.texture;
+ it->second.asset_file = file_name;
+ it->second.create_image = nullptr;
+ it->second.persistent = persistent;
+ } else {
+ texture = CreateRenderResource();
+ textures_[asset_name] = {texture, file_name, nullptr, persistent};
+ }
+
+ if (persistent) {
+ auto image = std::make_unique();
+ if (image->Load(file_name)) {
+ image->Compress();
+ texture->Update(std::move(image));
+ } else {
+ texture->Destroy();
+ }
+ }
+}
+
+void Engine::SetImageSource(const std::string& asset_name,
+ CreateImageCB create_image,
+ bool persistent) {
+ std::shared_ptr texture;
+ auto it = textures_.find(asset_name);
+ if (it != textures_.end()) {
+ texture = it->second.texture;
+ it->second.create_image = create_image;
+ it->second.asset_file.clear();
+ it->second.persistent = persistent;
+ } else {
+ texture = CreateRenderResource();
+ textures_[asset_name] = {texture, "", create_image, persistent};
+ }
+
+ if (persistent) {
+ auto image = create_image();
+ if (image)
+ texture->Update(std::move(image));
+ else
+ texture->Destroy();
+ }
+}
+
+void Engine::RefreshImage(const std::string& asset_name) {
+ auto it = textures_.find(asset_name);
+ if (it == textures_.end())
+ return;
+
+ std::unique_ptr image;
+ if (!it->second.asset_file.empty()) {
+ image = std::make_unique();
+ if (image->Load(it->second.asset_file))
+ image->Compress();
+ else
+ image.reset();
+ } else if (it->second.create_image) {
+ image = it->second.create_image();
+ }
+
+ if (image)
+ it->second.texture->Update(std::move(image));
+ else
+ it->second.texture->Destroy();
+}
+
+std::shared_ptr Engine::GetTexture(const std::string& asset_name) {
+ auto it = textures_.find(asset_name);
+ if (it != textures_.end()) {
+ if (!it->second.texture->IsValid())
+ RefreshImage(it->first);
+ return it->second.texture;
+ }
+
+ std::shared_ptr texture = CreateRenderResource();
+ textures_[asset_name] = {texture};
+
+ return texture;
+}
+
+std::unique_ptr Engine::CreateAudioResource() {
+ return std::make_unique(audio_);
}
void Engine::AddInputEvent(std::unique_ptr event) {
switch (event->GetType()) {
- case InputEvent::kTap:
+ case InputEvent::kDragEnd:
if (((GetScreenSize() / 2) * 0.9f - event->GetVector(0)).Magnitude() <=
0.25f) {
- SetSatsVisible(!stats_.IsVisible());
- // Consume event.
- return;
+ SetSatsVisible(!stats_->IsVisible());
+ // TODO: Enqueue DragCancel so we can consume this event.
}
break;
case InputEvent::kKeyPress:
if (event->GetKeyPress() == 's') {
- SetSatsVisible(!stats_.IsVisible());
+ SetSatsVisible(!stats_->IsVisible());
// Consume event.
return;
}
break;
case InputEvent::kDrag:
- if (stats_.IsVisible()) {
- if ((stats_.GetOffset() - event->GetVector(0)).Magnitude() <=
- stats_.GetScale().y)
- stats_.SetOffset(event->GetVector(0));
+ if (stats_->IsVisible()) {
+ if ((stats_->GetOffset() - event->GetVector(0)).Magnitude() <=
+ stats_->GetScale().y)
+ stats_->SetOffset(event->GetVector(0));
// TODO: Enqueue DragCancel so we can consume this event.
}
break;
@@ -201,6 +317,15 @@ std::unique_ptr Engine::GetNextInputEvent() {
return event;
}
+void Engine::Vibrate(int duration) {
+ if (vibration_enabled_)
+ platform_->Vibrate(duration);
+}
+
+void Engine::SetEnableAudio(bool enable) {
+ audio_->SetEnableAudio(enable);
+}
+
TextureCompressor* Engine::GetTextureCompressor(bool opacity) {
return opacity ? tex_comp_alpha_.get() : tex_comp_opaque_.get();
}
@@ -229,7 +354,7 @@ bool Engine::IsMobile() const {
return platform_->mobile_device();
}
-std::shared_ptr Engine::CreateRenderResourceInternal(
+std::unique_ptr Engine::CreateRenderResourceInternal(
RenderResourceFactoryBase& factory) {
return renderer_->CreateResource(factory);
}
@@ -237,6 +362,9 @@ std::shared_ptr Engine::CreateRenderResourceInternal(
void Engine::ContextLost() {
CreateRenderResources();
+ for (auto& t : textures_)
+ RefreshImage(t.first);
+
game_->ContextLost();
}
@@ -269,14 +397,17 @@ bool Engine::CreateRenderResources() {
}
void Engine::SetSatsVisible(bool visible) {
- stats_.SetVisible(visible);
+ stats_->SetVisible(visible);
if (visible)
- stats_.Create(CreateRenderResource());
+ stats_->Create("stats_tex");
else
- stats_.Destory();
+ stats_->Destory();
}
-void Engine::PrintStats() {
+std::unique_ptr Engine::PrintStats() {
+ if (!stats_->IsVisible())
+ return nullptr;
+
constexpr int width = 200;
std::vector lines;
std::string line;
@@ -297,18 +428,14 @@ void Engine::PrintStats() {
image->Create(image_width, image_height);
image->Clear({1, 1, 1, 0.08f});
- Worker worker(2);
int y = margin;
for (auto& text : lines) {
- worker.Enqueue(std::bind(&Font::Print, system_font_.get(), margin, y,
- text.c_str(), image->GetBuffer(),
- image->GetWidth()));
+ system_font_->Print(margin, y, text.c_str(), image->GetBuffer(),
+ image->GetWidth());
y += line_height + margin;
}
- worker.Join();
- stats_.GetTexture()->Update(std::move(image));
- stats_.AutoScale();
+ return image;
}
} // namespace eng
diff --git a/src/engine/engine.h b/src/engine/engine.h
index 1c40181..89248e9 100644
--- a/src/engine/engine.h
+++ b/src/engine/engine.h
@@ -2,13 +2,15 @@
#define ENGINE_H
#include
+#include
+#include
#include
#include
#include "../base/random.h"
#include "../base/vecmath.h"
#include "audio/audio_forward.h"
-#include "image_quad.h"
+#include "platform/platform_forward.h"
#include "renderer/render_resource.h"
class TextureCompressor;
@@ -18,15 +20,19 @@ namespace eng {
class AudioResource;
class Font;
class Game;
+class Drawable;
class InputEvent;
+class Image;
+class ImageQuad;
class Renderer;
-struct RenderCommand;
-class Platform;
class Geometry;
class Shader;
+class Texture;
class Engine {
public:
+ using CreateImageCB = std::function()>;
+
Engine(Platform* platform, Renderer* renderer, Audio* audio);
~Engine();
@@ -42,6 +48,9 @@ class Engine {
void LostFocus();
void GainedFocus();
+ void AddDrawable(Drawable* drawable);
+ void RemoveDrawable(Drawable* drawable);
+
void Exit();
// Convert size from pixels to viewport scale.
@@ -51,22 +60,42 @@ class Engine {
base::Vector2 ToPosition(const base::Vector2& vec);
template
- std::shared_ptr CreateRenderResource() {
+ std::unique_ptr CreateRenderResource() {
RenderResourceFactory factory;
- return std::dynamic_pointer_cast(CreateRenderResourceInternal(factory));
+ std::unique_ptr resource =
+ CreateRenderResourceInternal(factory);
+ return std::unique_ptr(static_cast(resource.release()));
}
- std::shared_ptr CreateAudioResource();
+ void SetImageSource(const std::string& asset_name,
+ const std::string& file_name,
+ bool persistent = false);
+ void SetImageSource(const std::string& asset_name,
+ CreateImageCB create_image,
+ bool persistent = false);
+
+ void RefreshImage(const std::string& asset_name);
+
+ std::shared_ptr GetTexture(const std::string& asset_name);
+
+ std::unique_ptr