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 CreateAudioResource(); void AddInputEvent(std::unique_ptr event); std::unique_ptr GetNextInputEvent(); + // Vibrate (if supported by the platform) for the specified duration. + void Vibrate(int duration); + + void SetImageDpi(float dpi) { image_dpi_ = dpi; } + + void SetEnableAudio(bool enable); + + void SetEnableVibration(bool enable) { vibration_enabled_ = enable; } + // Access to the render resources. - std::shared_ptr GetQuad() { return quad_; } - std::shared_ptr GetPassThroughShader() { - return pass_through_shader_; - } - std::shared_ptr GetSolidShader() { return solid_shader_; } + Geometry* GetQuad() { return quad_.get(); } + Shader* GetPassThroughShader() { return pass_through_shader_.get(); } + Shader* GetSolidShader() { return solid_shader_.get(); } const Font* GetSystemFont() { return system_font_.get(); } @@ -95,7 +124,19 @@ class Engine { float seconds_accumulated() const { return seconds_accumulated_; } + float image_dpi() const { return image_dpi_; } + private: + // Class holding information about texture resources managed by engine. + // Texture is created from an image asset if asset_file is valid. Otherwise + // texture is created from the image returned by create_image callback. + struct TextureResource { + std::shared_ptr texture; + std::string asset_file; + CreateImageCB create_image; + bool persistent = false; + }; + static Engine* singleton; Platform* platform_ = nullptr; @@ -106,9 +147,9 @@ class Engine { std::unique_ptr game_; - std::shared_ptr quad_; - std::shared_ptr pass_through_shader_; - std::shared_ptr solid_shader_; + std::unique_ptr quad_; + std::unique_ptr pass_through_shader_; + std::unique_ptr solid_shader_; base::Vector2 screen_size_ = {0, 0}; base::Matrix4x4 projection_; @@ -118,18 +159,27 @@ class Engine { std::unique_ptr tex_comp_opaque_; std::unique_ptr tex_comp_alpha_; - ImageQuad stats_; + std::list drawables_; + + // Textures mapped by asset name. + std::unordered_map textures_; + + std::unique_ptr stats_; float fps_seconds_ = 0; int fps_ = 0; float seconds_accumulated_ = 0.0f; + float image_dpi_ = 200; + + bool vibration_enabled_ = true; + std::deque> input_queue_; base::Random random_; - std::shared_ptr CreateRenderResourceInternal( + std::unique_ptr CreateRenderResourceInternal( RenderResourceFactoryBase& factory); void ContextLost(); @@ -137,7 +187,7 @@ class Engine { bool CreateRenderResources(); void SetSatsVisible(bool visible); - void PrintStats(); + std::unique_ptr PrintStats(); Engine(const Engine&) = delete; Engine& operator=(const Engine&) = delete; diff --git a/src/engine/font.cc b/src/engine/font.cc index 057a8a6..4e633ee 100644 --- a/src/engine/font.cc +++ b/src/engine/font.cc @@ -1,5 +1,8 @@ #include "font.h" +#include +#include + #include "../base/log.h" #include "engine.h" #include "platform/asset_file.h" @@ -123,9 +126,12 @@ void Font::CalculateBoundingBox(const std::string& text, float x = 0, y = 0; - const char* ptr = text.c_str(); + std::wstring_convert,char16_t> convert; + std::u16string u16text = convert.from_bytes(text); + const char16_t* ptr = u16text.c_str(); + while (*ptr) { - if (*ptr >= kFirstChar /*&& *ptr < (kFirstChar + kNumChars)*/) { + if (*ptr >= kFirstChar && *ptr < (kFirstChar + kNumChars)) { stbtt_aligned_quad q; stbtt_GetBakedQuad(glyph_info_, kGlyphSize, kGlyphSize, *ptr - kFirstChar, &x, &y, &q, 1); @@ -140,9 +146,8 @@ void Font::CalculateBoundingBox(const std::string& text, x1 = ix1; if (iy1 > y1) y1 = iy1; - - ++ptr; } + ++ptr; } } @@ -168,9 +173,12 @@ void Font::Print(int x, float fx = (float)x, fy = (float)y + (float)yoff_; - const char* ptr = text.c_str(); + std::wstring_convert,char16_t> convert; + std::u16string u16text = convert.from_bytes(text); + const char16_t* ptr = u16text.c_str(); + while (*ptr) { - if (*ptr >= kFirstChar /*&& *ptr < (kFirstChar + kNumChars)*/) { + if (*ptr >= kFirstChar && *ptr < (kFirstChar + kNumChars)) { stbtt_aligned_quad q; stbtt_GetBakedQuad(glyph_info_, kGlyphSize, kGlyphSize, *ptr - kFirstChar, &fx, &fy, &q, 1); @@ -184,9 +192,8 @@ void Font::Print(int x, StretchBlit_I8_to_RGBA32(ix0, iy0, ix1, iy1, iu0, iv0, iu1, iv1, buffer, width, glyph_cache_.get(), kGlyphSize); - - ++ptr; } + ++ptr; } } diff --git a/src/engine/font.h b/src/engine/font.h index 7f5f604..c66afed 100644 --- a/src/engine/font.h +++ b/src/engine/font.h @@ -39,7 +39,7 @@ class Font { enum Constants { kGlyphSize = 512, kFirstChar = 32, // ' ' (space) - kNumChars = 96 // Covers almost all ASCII chars. + kNumChars = 224 // Covers all ASCII chars. }; std::unique_ptr glyph_cache_; // Image data. diff --git a/src/engine/game.h b/src/engine/game.h index 5ae6525..08f123c 100644 --- a/src/engine/game.h +++ b/src/engine/game.h @@ -12,8 +12,6 @@ class Game { virtual void Update(float delta_time) = 0; - virtual void Draw(float frame_frac) = 0; - virtual void ContextLost() = 0; virtual void LostFocus() = 0; diff --git a/src/engine/image.h b/src/engine/image.h index 5835d7b..aa387c4 100644 --- a/src/engine/image.h +++ b/src/engine/image.h @@ -46,12 +46,10 @@ class Image { void GradientV(const base::Vector4& c1, const base::Vector4& c2, int height); private: - base::AlignedMem::ScoppedPtr buffer_; + base::AlignedMemPtr buffer_; int width_ = 0; int height_ = 0; Format format_ = kRGBA32; - - std::string name_; }; } // namespace eng diff --git a/src/engine/image_quad.cc b/src/engine/image_quad.cc index 038cdf0..a9c6b18 100644 --- a/src/engine/image_quad.cc +++ b/src/engine/image_quad.cc @@ -1,7 +1,6 @@ #include "image_quad.h" -#include - +#include "../base/log.h" #include "engine.h" #include "renderer/geometry.h" #include "renderer/shader.h" @@ -11,14 +10,20 @@ using namespace base; namespace eng { -void ImageQuad::Create(std::shared_ptr texture, +void ImageQuad::Create(const std::string& asset_name, std::array num_frames, int frame_width, int frame_height) { - texture_ = texture; + texture_ = Engine::Get().GetTexture(asset_name); + num_frames_ = std::move(num_frames); frame_width_ = frame_width; frame_height_ = frame_height; + + if ((frame_width_ > 0 && frame_height_ > 0) || texture_->IsValid()) + AutoScale(); + + asset_name_ = asset_name; } void ImageQuad::Destory() { @@ -26,13 +31,15 @@ void ImageQuad::Destory() { } void ImageQuad::AutoScale() { + auto& engine = Engine::Get(); Vector2 dimensions = {GetFrameWidth(), GetFrameHeight()}; - SetScale(Engine::Get().ToScale(dimensions)); - Scale((float)Engine::Get().GetDeviceDpi() / 200.0f); + SetScale(engine.ToScale(dimensions)); + Scale((float)engine.GetDeviceDpi() / engine.image_dpi()); } void ImageQuad::SetFrame(size_t frame) { - assert(frame < GetNumFrames()); + DCHECK(frame < GetNumFrames()) + << "asset: " << asset_name_ << " frame: " << frame; current_frame_ = frame; } @@ -40,8 +47,10 @@ size_t ImageQuad::GetNumFrames() const { return num_frames_[0] * num_frames_[1]; } -void ImageQuad::Draw() { - if (!IsVisible() || !texture_ || !texture_->IsValid()) +void ImageQuad::Draw(float frame_frac) { + DCHECK(IsVisible()); + + if (!texture_ || !texture_->IsValid()) return; texture_->Activate(); @@ -49,8 +58,7 @@ void ImageQuad::Draw() { Vector2 tex_scale = {GetFrameWidth() / texture_->GetWidth(), GetFrameHeight() / texture_->GetHeight()}; - std::shared_ptr quad = Engine::Get().GetQuad(); - std::shared_ptr shader = Engine::Get().GetPassThroughShader(); + Shader* shader = Engine::Get().GetPassThroughShader(); shader->Activate(); shader->SetUniform("offset", offset_); @@ -63,7 +71,7 @@ void ImageQuad::Draw() { shader->SetUniform("color", color_); shader->SetUniform("texture", 0); - quad->Draw(); + Engine::Get().GetQuad()->Draw(); } float ImageQuad::GetFrameWidth() const { @@ -78,9 +86,8 @@ float ImageQuad::GetFrameHeight() const { // Return the uv offset for the given frame. Vector2 ImageQuad::GetUVOffset(int frame) const { - assert(frame < num_frames_[0] * num_frames_[1]); - if (num_frames_[0] == 1 && num_frames_[1] == 1) - return {0, 0}; + DCHECK(frame < GetNumFrames()) + << "asset: " << asset_name_ << " frame: " << frame; return {(float)(frame % num_frames_[0]), (float)(frame / num_frames_[0])}; } diff --git a/src/engine/image_quad.h b/src/engine/image_quad.h index e09a2a8..2742971 100644 --- a/src/engine/image_quad.h +++ b/src/engine/image_quad.h @@ -6,6 +6,7 @@ #include #include +#include namespace eng { @@ -16,7 +17,7 @@ class ImageQuad : public Animatable { ImageQuad() = default; ~ImageQuad() override = default; - void Create(std::shared_ptr texture, + void Create(const std::string& asset_name, std::array num_frames = {1, 1}, int frame_width = 0, int frame_height = 0); @@ -32,9 +33,8 @@ class ImageQuad : public Animatable { void SetColor(const base::Vector4& color) override { color_ = color; } base::Vector4 GetColor() const override { return color_; } - void Draw(); - - std::shared_ptr GetTexture() { return texture_; } + // Drawable interface. + void Draw(float frame_frac) override; private: std::shared_ptr texture_; @@ -46,6 +46,8 @@ class ImageQuad : public Animatable { base::Vector4 color_ = {1, 1, 1, 1}; + std::string asset_name_; + float GetFrameWidth() const; float GetFrameHeight() const; diff --git a/src/engine/input_event.h b/src/engine/input_event.h index c022eb8..ef4b54e 100644 --- a/src/engine/input_event.h +++ b/src/engine/input_event.h @@ -1,7 +1,7 @@ #ifndef INPUT_EVENT_H #define INPUT_EVENT_H -#include +#include "../base/log.h" #include "../base/vecmath.h" namespace eng { @@ -10,37 +10,38 @@ class InputEvent { public: enum Type { kInvalid, - kTap, - kDoubleTap, kDragStart, kDrag, kDragEnd, kDragCancel, - kPinchStart, - kPinch, kNavigateBack, kKeyPress, kType_Max // Not a type. }; + InputEvent(Type type, size_t pointer_id) + : type_(type), pointer_id_(pointer_id) {} + InputEvent(Type type, size_t pointer_id, const base::Vector2& vec) + : type_(type), pointer_id_(pointer_id), vec_(vec) {} InputEvent(Type type) : type_(type) {} - InputEvent(Type type, const base::Vector2& vec1) - : type_(type), vec_{vec1, {0, 0}} {} - InputEvent(Type type, const base::Vector2& vec1, const base::Vector2& vec2) - : type_(type), vec_{vec1, vec2} {} InputEvent(Type type, char key) : type_(type), key_(key) {} ~InputEvent() = default; - Type GetType() { return type_; } - base::Vector2 GetVector(size_t i) { - assert(i < 2); - return vec_[i]; + Type GetType() const { return type_; } + + size_t GetPointerId() const { return pointer_id_; } + + base::Vector2 GetVector(size_t i) const { + DCHECK(i < 2); + return vec_; } - char GetKeyPress() { return key_; } + + char GetKeyPress() const { return key_; } private: Type type_ = kInvalid; - base::Vector2 vec_[2] = {{0, 0}, {0, 0}}; + size_t pointer_id_ = 0; + base::Vector2 vec_ = {0, 0}; char key_ = 0; }; diff --git a/src/engine/mesh.cc b/src/engine/mesh.cc index 6ed9dea..b2301f2 100644 --- a/src/engine/mesh.cc +++ b/src/engine/mesh.cc @@ -1,7 +1,6 @@ #include "mesh.h" #include -#include #include "../base/log.h" #include "../third_party/jsoncpp/json.h" @@ -134,8 +133,7 @@ bool Mesh::Load(const std::string& file_name) { *((unsigned short*)dst) = (unsigned short)vertices[i].asUInt(); break; default: - assert(false); - return false; + NOTREACHED << "- Unknown data type: " << data_type; } dst += type_size; ++i; diff --git a/src/engine/platform/asset_file_android.cc b/src/engine/platform/asset_file_android.cc index 515d73e..0aec418 100644 --- a/src/engine/platform/asset_file_android.cc +++ b/src/engine/platform/asset_file_android.cc @@ -1,4 +1,3 @@ -#include #include #include "../../base/log.h" #include "asset_file.h" diff --git a/src/engine/platform/platform.h b/src/engine/platform/platform.h index 32e23d3..b5b3e53 100644 --- a/src/engine/platform/platform.h +++ b/src/engine/platform/platform.h @@ -1,88 +1,20 @@ #ifndef PLATFORM_H #define PLATFORM_H -#include -#include -#include - -#include "../../base/timer.h" -#include "../audio/audio_forward.h" - #if defined(__ANDROID__) -struct android_app; -struct AInputEvent; - -namespace ndk_helper { -class TapDetector; -class PinchDetector; -class DragDetector; -} // namespace ndk_helper +#include "platform_android.h" +#elif defined(__linux__) +#include "platform_linux.h" #endif namespace eng { -class Renderer; -class Engine; - -class Platform { - public: - Platform(); - ~Platform(); - #if defined(__ANDROID__) - void Initialize(android_app* app); +using Platform = PlatformAndroid; #elif defined(__linux__) - void Initialize(); +using Platform = PlatformLinux; #endif - void Shutdown(); - - void Update(); - - void RunMainLoop(); - - void Exit(); - - int GetDeviceDpi() const { return device_dpi_; } - - const std::string& GetRootPath() const { return root_path_; } - - bool mobile_device() const { return mobile_device_; } - - static class InternalError : public std::exception { - } internal_error; - - private: - base::Timer timer_; - - bool mobile_device_ = false; - int device_dpi_ = 200; - std::string root_path_; - - bool has_focus_ = false; - bool should_exit_ = false; - - std::unique_ptr