- Various fixes for sound system.
- Code cleanup and refactoring.
- Move boilerplate code to engine.
- Texture management is moved to engine. Game code uses asset names and
  doesn't know about textures.
- Multi-touch support.
- GN build (WIP)
- Add LOG_DIFF, CHECK, DCHECK and NOTREACHED.
- Implement global thread-pool for Worker.
- Use TaskRunner in ThreadPool.
- Font: Cover all ASCII chars.
- Option to disable audio and vibration.
This commit is contained in:
Attila Uygun 2020-07-01 00:23:07 +02:00
parent 05466725af
commit 9d92d01be1
97 changed files with 2602 additions and 1635 deletions

7
.clang-format Normal file
View File

@ -0,0 +1,7 @@
BasedOnStyle: Chromium
Standard: Cpp11
MacroBlockBegin: "^\
RENDER_COMMAND_BEGIN$"
MacroBlockEnd: "^\
RENDER_COMMAND_END$"

1
.gitignore vendored
View File

@ -6,3 +6,4 @@ build/android/build
build/linux/gltest_x86_64_debug
build/linux/gltest_x86_64_release
build/linux/obj
out

2
.gn Normal file
View File

@ -0,0 +1,2 @@
# The location of the build configuration file.
buildconfig = "//build/gn/BUILDCONFIG.gn"

20
.travis.yml Normal file
View File

@ -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

5
BUILD.gn Normal file
View File

@ -0,0 +1,5 @@
group("kaliber") {
deps = [
"//src/demo",
]
}

View File

@ -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)

1
build/README.md Normal file
View File

@ -0,0 +1 @@
[![Build Status](https://travis-ci.org/auygun/kaliber.svg?branch=master)](https://travis-ci.org/auygun/kaliber)

View File

@ -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

View File

@ -5,6 +5,8 @@
android:versionCode="1"
android:versionName="1.0">
<uses-permission android:name="android.permission.VIBRATE"/>
<!-- This .apk has no Java code itself, so set hasCode to false. -->
<application
android:allowBackup="false"

39
build/gn/BUILD.gn Normal file
View File

@ -0,0 +1,39 @@
config("compiler_defaults") {
if (current_os == "linux") {
cflags = [
"-fPIC",
"-pthread",
"-g",
]
cflags_cc = [
"-std=c++17",
]
}
}
config("executable_ldconfig") {
ldflags = [
"-Wl,-rpath=\$ORIGIN/",
"-Wl,-rpath-link=",
]
}
config("debug") {
defines = [ "_DEBUG" ]
if (current_os == "linux") {
if (current_cpu == "x64") {
defines += [ "_GLIBCXX_DEBUG=1" ]
}
}
}
config("release") {
cflags = []
defines = [ "NDEBUG" ]
if (current_os == "linux") {
cflags += [ "-Ofast" ]
}
}

41
build/gn/BUILDCONFIG.gn Normal file
View File

@ -0,0 +1,41 @@
declare_args() {
is_debug = false
}
if (target_os == "") {
target_os = host_os
}
if (target_cpu == "") {
target_cpu = host_cpu
}
if (current_cpu == "") {
current_cpu = target_cpu
}
if (current_os == "") {
current_os = target_os
}
_compiler_default_config = [ "//build/gn:compiler_defaults" ]
# Debug/release-related defines.
if (is_debug) {
_compiler_default_config += [ "//build/gn:debug" ]
} else {
_compiler_default_config += [ "//build/gn:release" ]
}
set_defaults("executable") {
configs = _compiler_default_config
configs += [ "//build/gn:executable_ldconfig" ]
}
set_defaults("static_library") {
configs = _compiler_default_config
}
set_defaults("shared_library") {
configs = _compiler_default_config
}
set_defaults("source_set") {
configs = _compiler_default_config
}
set_default_toolchain("//build/gn/toolchain:clang")

151
build/gn/toolchain/BUILD.gn Normal file
View File

@ -0,0 +1,151 @@
toolchain("clang") {
tool("cc") {
depfile = "{{output}}.d"
command = "clang -MMD -MF $depfile {{defines}} {{include_dirs}} {{cflags}} {{cflags_c}} -c {{source}} -o {{output}}"
depsformat = "gcc"
description = "CC {{output}}"
outputs =
[ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ]
}
tool("cxx") {
depfile = "{{output}}.d"
command = "clang++ -MMD -MF $depfile {{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}} -c {{source}} -o {{output}}"
depsformat = "gcc"
description = "CXX {{output}}"
outputs =
[ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ]
}
tool("alink") {
rspfile = "{{output}}.rsp"
command = "rm -f {{output}} && ar rcs {{output}} @$rspfile"
description = "AR {{target_output_name}}{{output_extension}}"
rspfile_content = "{{inputs}}"
outputs =
[ "{{target_out_dir}}/{{target_output_name}}{{output_extension}}" ]
default_output_extension = ".a"
output_prefix = "lib"
}
tool("solink") {
soname = "{{target_output_name}}{{output_extension}}" # e.g. "libfoo.so".
sofile = "{{output_dir}}/$soname"
rspfile = soname + ".rsp"
command = "clang++ -shared {{ldflags}} -o $sofile -Wl,-soname=$soname @$rspfile"
rspfile_content = "-Wl,--whole-archive {{inputs}} {{solibs}} -Wl,--no-whole-archive {{libs}}"
description = "SOLINK $soname"
# Use this for {{output_extension}} expansions unless a target manually
# overrides it (in which case {{output_extension}} will be what the target
# specifies).
default_output_extension = ".so"
# Use this for {{output_dir}} expansions unless a target manually overrides
# it (in which case {{output_dir}} will be what the target specifies).
default_output_dir = "{{root_out_dir}}"
outputs = [ sofile ]
link_output = sofile
depend_output = sofile
output_prefix = "lib"
}
tool("link") {
outfile = "{{target_output_name}}{{output_extension}}"
rspfile = "$outfile.rsp"
command = "clang++ {{ldflags}} -o $outfile -Wl,--start-group @$rspfile {{solibs}} -Wl,--end-group {{libs}}"
description = "LINK $outfile"
default_output_dir = "{{root_out_dir}}"
rspfile_content = "{{inputs}}"
outputs = [ outfile ]
}
tool("stamp") {
command = "touch {{output}}"
description = "STAMP {{output}}"
}
tool("copy") {
command = "cp -af {{source}} {{output}}"
description = "COPY {{source}} {{output}}"
}
}
toolchain("gcc") {
tool("cc") {
depfile = "{{output}}.d"
command = "gcc -MMD -MF $depfile {{defines}} {{include_dirs}} {{cflags}} {{cflags_c}} -c {{source}} -o {{output}}"
depsformat = "gcc"
description = "CC {{output}}"
outputs =
[ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ]
}
tool("cxx") {
depfile = "{{output}}.d"
command = "g++ -MMD -MF $depfile {{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}} -c {{source}} -o {{output}}"
depsformat = "gcc"
description = "CXX {{output}}"
outputs =
[ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ]
}
tool("alink") {
rspfile = "{{output}}.rsp"
command = "rm -f {{output}} && ar rcs {{output}} @$rspfile"
description = "AR {{target_output_name}}{{output_extension}}"
rspfile_content = "{{inputs}}"
outputs =
[ "{{target_out_dir}}/{{target_output_name}}{{output_extension}}" ]
default_output_extension = ".a"
output_prefix = "lib"
}
tool("solink") {
soname = "{{target_output_name}}{{output_extension}}" # e.g. "libfoo.so".
sofile = "{{output_dir}}/$soname"
rspfile = soname + ".rsp"
command = "g++ -shared {{ldflags}} -o $sofile -Wl,-soname=$soname @$rspfile"
rspfile_content = "-Wl,--whole-archive {{inputs}} {{solibs}} -Wl,--no-whole-archive {{libs}}"
description = "SOLINK $soname"
# Use this for {{output_extension}} expansions unless a target manually
# overrides it (in which case {{output_extension}} will be what the target
# specifies).
default_output_extension = ".so"
# Use this for {{output_dir}} expansions unless a target manually overrides
# it (in which case {{output_dir}} will be what the target specifies).
default_output_dir = "{{root_out_dir}}"
outputs = [ sofile ]
link_output = sofile
depend_output = sofile
output_prefix = "lib"
}
tool("link") {
outfile = "{{target_output_name}}{{output_extension}}"
rspfile = "$outfile.rsp"
command = "g++ {{ldflags}} -o $outfile -Wl,--start-group @$rspfile {{solibs}} -Wl,--end-group {{libs}}"
description = "LINK $outfile"
default_output_dir = "{{root_out_dir}}"
rspfile_content = "{{inputs}}"
outputs = [ outfile ]
}
tool("stamp") {
command = "touch {{output}}"
description = "STAMP {{output}}"
}
tool("copy") {
command = "cp -af {{source}} {{output}}"
description = "COPY {{source}} {{output}}"
}
}

View File

@ -25,7 +25,7 @@ INTERMEDIATE_DIR := $(OUTPUT_DIR)/obj
BUILD_DIR := $(INTERMEDIATE_DIR)/$(BUILD)
ARFLAGS = r
LDFLAGS = -lX11 -lGL -lz -pthread -lasound
LDFLAGS = -lX11 -lGL -pthread -lasound
# Always enable debug information.
CFLAGS += -g
@ -36,6 +36,7 @@ CFLAGS += -MD -MP -MT $@
# Predefined flags.
ifeq ($(BUILD), debug)
CFLAGS += -D_DEBUG
CFLAGS += -D_GLIBCXX_DEBUG
endif
# Enable compiler optimizations for everything except debug.
@ -83,6 +84,7 @@ GLTEST_SRC := \
$(SRC_ROOT)/engine/audio/audio_alsa.cc \
$(SRC_ROOT)/engine/audio/audio_base.cc \
$(SRC_ROOT)/engine/audio/audio_resource.cc \
$(SRC_ROOT)/engine/drawable.cc \
$(SRC_ROOT)/engine/engine.cc \
$(SRC_ROOT)/engine/font.cc \
$(SRC_ROOT)/engine/image_quad.cc \
@ -90,8 +92,8 @@ GLTEST_SRC := \
$(SRC_ROOT)/engine/mesh.cc \
$(SRC_ROOT)/engine/platform/asset_file_linux.cc \
$(SRC_ROOT)/engine/platform/asset_file.cc \
$(SRC_ROOT)/engine/platform/platform_base.cc \
$(SRC_ROOT)/engine/platform/platform_linux.cc \
$(SRC_ROOT)/engine/platform/platform.cc \
$(SRC_ROOT)/engine/renderer/geometry.cc \
$(SRC_ROOT)/engine/renderer/render_command.cc \
$(SRC_ROOT)/engine/renderer/render_resource.cc \
@ -106,8 +108,6 @@ GLTEST_SRC := \
$(SRC_ROOT)/engine/sound.cc \
$(SRC_ROOT)/third_party/glew/glew.c \
$(SRC_ROOT)/third_party/jsoncpp/jsoncpp.cc \
$(SRC_ROOT)/third_party/minizip/ioapi.c \
$(SRC_ROOT)/third_party/minizip/unzip.c \
$(SRC_ROOT)/third_party/r8b/pffft.cpp \
$(SRC_ROOT)/third_party/r8b/r8bbase.cpp \
$(SRC_ROOT)/third_party/texture_compressor/dxt_encoder_internals.cc \

11
build/travis.sh Normal file
View File

@ -0,0 +1,11 @@
#!/bin/bash
sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
sudo apt-get update -qq
sudo apt-get install -qq g++-7
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-7 90
sudo apt-get install -qq -y libasound2-dev
sudo apt-get install -qq build-essential xorg-dev libc++-dev

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2020 Attila Uygun
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

35
src/base/BUILD.gn Normal file
View File

@ -0,0 +1,35 @@
source_set("base") {
sources = [
"closure.h",
"collusion_test.cc",
"collusion_test.h",
"file.h",
"hash.h",
"interpolation.h",
"log.cc",
"log.h",
"mem.h",
"misc.h",
"random.cc",
"random.h",
"task_runner.cc",
"task_runner.h",
"timer.cc",
"timer.h",
"vecmath.cc",
"vecmath.h",
"worker.cc",
"worker.h",
]
# ldflags = []
# libs = []
if (target_os == "linux") {
# ldflags += [ "-L/usr/X11R6/lib" ]
# libs += [ "X11", "rt", "pthread" ]
}
deps = []
}

View File

@ -2,11 +2,47 @@
#define CLOSURE_H
#include <functional>
#include <string>
#include <tuple>
#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<void()>;
#ifdef _DEBUG
// Provides location info (function name, file name and line number) where of a
// Closure was constructed.
using Location = std::tuple<const char*, const char*, int>;
#else
using Location = std::nullptr_t;
#endif
} // namespace base
#endif // CLOSURE_H

View File

@ -0,0 +1,91 @@
#ifndef CONCURRENT_STACK_H
#define CONCURRENT_STACK_H
#include <atomic>
#include <utility>
namespace base {
// Lock-free concurrent stack. All methods are thread-safe and can be called on
// any thread.
template <typename T>
class ConcurrentStack {
struct Node {
T item;
Node* next = nullptr;
Node(T item) : item(std::move(item)) {}
};
public:
ConcurrentStack() = default;
ConcurrentStack(ConcurrentStack<T>&& other) { MoveAssign(std::move(other)); }
~ConcurrentStack() { Clear(); }
ConcurrentStack<T>& operator=(ConcurrentStack<T>&& 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<Node*> head_ = nullptr;
void MoveAssign(ConcurrentStack<T>&& 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<T>&) = delete;
ConcurrentStack<T>& operator=(const ConcurrentStack<T>&) = delete;
};
} // namespace base
#endif // CONCURRENT_STACK_H

View File

@ -4,6 +4,8 @@
#include <cstdio>
#include <memory>
namespace base {
namespace internal {
struct ScopedFILECloser {
@ -15,8 +17,6 @@ struct ScopedFILECloser {
} // namespace internal
namespace base {
// Automatically closes file.
using ScopedFILE = std::unique_ptr<FILE, internal::ScopedFILECloser>;

View File

@ -5,17 +5,25 @@
#else
#include <cstdio>
#endif
#include <cstdlib>
#include <mutex>
#include <unordered_map>
#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<<<bool>(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<std::string, std::string> log_map;
static std::mutex lock;
auto key = std::string(file_) + std::to_string(line_);
bool flush = true;
{
std::lock_guard<std::mutex> 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<<<Vector2>(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<<<Vector4>(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

View File

@ -2,48 +2,115 @@
#define LOG_H
#include <sstream>
#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<bool>(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<bool>(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 <typename T>
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 <typename T>
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

View File

@ -1,7 +1,6 @@
#ifndef MEM_H
#define MEM_H
#include <cassert>
#include <cstdlib>
#include <memory>
@ -9,8 +8,12 @@
#include <malloc.h>
#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 <typename T>
struct AlignedMem {
using ScoppedPtr = std::unique_ptr<T, internal::ScopedAlignedFree>;
};
using AlignedMemPtr = std::unique_ptr<T, internal::ScopedAlignedFree>;
template <int kAlignment>
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;
}

38
src/base/semaphore.h Normal file
View File

@ -0,0 +1,38 @@
#ifndef SEMAPHORE_H
#define SEMAPHORE_H
#include <condition_variable>
#include <mutex>
namespace base {
class Semaphore {
public:
Semaphore(int count = 0) : count_(count) {}
void Acquire() {
std::unique_lock<std::mutex> scoped_lock(mutex_);
cv_.wait(scoped_lock, [&]() { return count_ > 0; });
--count_;
}
void Release() {
{
std::lock_guard<std::mutex> 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

42
src/base/spinlock.h Normal file
View File

@ -0,0 +1,42 @@
#ifndef SPINLOCK_H
#define SPINLOCK_H
#include <atomic>
#if defined(_MSC_VER)
#include <intrin.h>
#define spinlock_pause() YieldProcessor()
#elif defined(__x86_64__) || defined(__i386__)
#include <immintrin.h>
#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<bool> lock_{false};
};
} // namespace base
#endif // SPINLOCK_H

View File

@ -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<std::mutex> scoped_lock(mutex_);
thread_tasks_.emplace_back(std::move(task));
thread_local std::unique_ptr<TaskRunner> TaskRunner::thread_local_task_runner;
void TaskRunner::CreateThreadLocalTaskRunner() {
DCHECK(!thread_local_task_runner);
thread_local_task_runner = std::make_unique<TaskRunner>();
}
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<std::mutex> 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<std::mutex> scoped_lock(lock_);
queue_.emplace_back(from, std::move(relay));
}
void TaskRunner::MultiConsumerRun() {
for (;;) {
base::Closure task;
Task task;
{
std::unique_lock<std::mutex> scoped_lock(mutex_);
if (!thread_tasks_.empty()) {
task.swap(thread_tasks_.front());
thread_tasks_.pop_front();
}
std::lock_guard<std::mutex> 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<Task> queue;
{
std::lock_guard<std::mutex> 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

View File

@ -2,26 +2,76 @@
#define TASK_RUNNER_H
#include <deque>
#include <memory>
#include <mutex>
#include <thread>
#include <tuple>
#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 <typename ReturnType>
void ReturnAsParamAdapter(std::function<ReturnType()> func,
ReturnType* result) {
*result = func();
}
// Adapts a ReturnType* result to a callblack that expects a ReturnType.
template <typename ReturnType>
void ReplyAdapter(std::function<void(ReturnType)> 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 <typename ReturnType>
void EnqueueTaskAndReplyWithResult(const Location& from,
std::function<ReturnType()> task,
std::function<void(ReturnType)> reply) {
auto* result = new ReturnType;
return EnqueueTaskAndReply(
from,
std::bind(internal::ReturnAsParamAdapter<ReturnType>, std::move(task),
result),
std::bind(internal::ReplyAdapter<ReturnType>, 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<base::Closure> thread_tasks_;
using Task = std::tuple<Location, Closure>;
std::deque<Task> queue_;
mutable std::mutex lock_;
static thread_local std::unique_ptr<TaskRunner> thread_local_task_runner;
TaskRunner(TaskRunner const&) = delete;
TaskRunner& operator=(TaskRunner const&) = delete;

View File

@ -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<std::mutex> 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<std::mutex> 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<std::mutex> 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();
}
}

View File

@ -1,33 +1,53 @@
#ifndef WORKER_H
#define WORKER_H
#ifndef THREAD_POOL_H
#define THREAD_POOL_H
#include <condition_variable>
#include <deque>
#include <mutex>
#include <atomic>
#include <thread>
#include <vector>
#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 <typename ReturnType>
void EnqueueTaskAndReplyWithResult(const Location& from,
std::function<ReturnType()> task,
std::function<void(ReturnType)> 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<std::thread> threads_;
std::deque<base::Closure> tasks_;
bool quit_when_idle_ = false;
Semaphore semaphore_;
std::atomic<bool> 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

25
src/demo/BUILD.gn Normal file
View File

@ -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",
]
}

View File

@ -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<Demo*>(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<InputEvent> 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<InputEvent> 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<Texture>();
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<Image> Credits::CreateImage() {
const Font& font = static_cast<Demo*>(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>();
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;
}

View File

@ -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<eng::InputEvent> event);
void Draw();
void ContextLost();
void Show();
void Hide();
private:
std::shared_ptr<eng::Texture> tex_;
eng::ImageQuad text_[kNumLines];
eng::Animator text_animator_;

View File

@ -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;
}

View File

@ -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;

View File

@ -1,6 +1,5 @@
#include "enemy.h"
#include <cassert>
#include <functional>
#include <limits>
@ -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<Texture>()),
bug_tex_(Engine::Get().CreateRenderResource<Texture>()),
target_tex_(Engine::Get().CreateRenderResource<Texture>()),
blast_tex_(Engine::Get().CreateRenderResource<Texture>()),
score_tex_{Engine::Get().CreateRenderResource<Texture>(),
Engine::Get().CreateRenderResource<Texture>(),
Engine::Get().CreateRenderResource<Texture>()} {}
Enemy::Enemy() = default;
Enemy::~Enemy() = default;
bool Enemy::Initialize() {
explosion_sound_ = std::make_shared<Sound>();
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<Demo*>(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<Image> Enemy::GetScoreImage(int score) {
std::unique_ptr<Image> Enemy::GetScoreImage(EnemyType enemy_type) {
const Font& font = static_cast<Demo*>(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<Image> Enemy::GetScoreImage(int score) {
}
bool Enemy::CreateRenderResources() {
auto skull_image = std::make_unique<Image>();
if (!skull_image->Load("enemy_anims_01_frames_ok.png"))
return false;
auto bug_image = std::make_unique<Image>();
if (!bug_image->Load("enemy_anims_02_frames_ok.png"))
return false;
auto target_image = std::make_unique<Image>();
if (!target_image->Load("enemy_target_single_ok.png"))
return false;
auto blast_image = std::make_unique<Image>();
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;
}

View File

@ -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<eng::Texture> skull_tex_;
std::shared_ptr<eng::Texture> bug_tex_;
std::shared_ptr<eng::Texture> target_tex_;
std::shared_ptr<eng::Texture> blast_tex_;
std::shared_ptr<eng::Texture> score_tex_[kEnemyType_Max];
std::shared_ptr<eng::Sound> explosion_sound_;
std::list<EnemyUnit> enemies_;
@ -111,7 +100,7 @@ class Enemy {
int GetScore(EnemyType enemy_type);
std::unique_ptr<eng::Image> GetScoreImage(int score);
std::unique_ptr<eng::Image> GetScoreImage(EnemyType enemy_type);
bool CreateRenderResources();
};

View File

@ -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<Texture>());
text_[1].Create(Engine::Get().CreateRenderResource<Texture>());
}
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<eng::Image> Hud::CreateScoreImage() {
return Print(0, std::to_string(last_score_));
}
std::unique_ptr<eng::Image> Hud::CreateWaveImage() {
return Print(1, "wave "s + std::to_string(last_wave_));
}
std::unique_ptr<Image> Hud::Print(int i, const std::string& text) {
const Font& font = static_cast<Demo*>(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<Image> Hud::CreateImage() {

View File

@ -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<eng::Image> CreateScoreImage();
std::unique_ptr<eng::Image> CreateWaveImage();
std::unique_ptr<eng::Image> Print(int i, const std::string& text);
std::unique_ptr<eng::Image> CreateImage();
};

View File

@ -1,18 +1,15 @@
#include "menu.h"
#include <cassert>
#include <cmath>
#include <vector>
#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<Texture>()) {}
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<InputEvent> 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<InputEvent> 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<Image> Menu::CreateImage() {
const Font& font = static_cast<Demo*>(Engine::Get().GetGame())->GetFont();
@ -191,16 +183,13 @@ std::unique_ptr<Image> 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;
}

View File

@ -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<eng::InputEvent> 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<eng::Texture> 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<eng::Image> CreateImage();
bool IsAnimating();

View File

@ -1,7 +1,5 @@
#include "player.h"
#include <cassert>
#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<Texture>()),
beam_tex_(Engine::Get().CreateRenderResource<Texture>()) {}
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<InputEvent> 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<Image>();
if (!weapon_image->Load("enemy_anims_flare_ok.png"))
return false;
auto beam_image = std::make_unique<Image>();
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;
}

View File

@ -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<eng::InputEvent> event);
void Draw(float frame_frac);
base::Vector2 GetWeaponPos(DamageType type) const;
base::Vector2 GetWeaponScale() const;
private:
std::shared_ptr<eng::Texture> weapon_tex_;
std::shared_ptr<eng::Texture> beam_tex_;
eng::ImageQuad drag_sign_[2];
eng::ImageQuad weapon_[2];
eng::ImageQuad beam_[2];

View File

@ -31,6 +31,8 @@ bool SkyQuad::Create() {
color_animator_.Attach(this);
SetVisible(true);
return true;
}

View File

@ -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<eng::Shader> shader_;
std::unique_ptr<eng::Shader> shader_;
base::Vector2 sky_offset_ = {0, 0};
base::Vector2 last_sky_offset_ = {0, 0};

95
src/engine/BUILD.gn Normal file
View File

@ -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 = []
}

View File

@ -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

View File

@ -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;

View File

@ -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.

View File

@ -2,6 +2,8 @@
#include <alsa/asoundlib.h>
#include <memory>
#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<bool> promise;
std::future<bool> 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<bool> promise) {
promise.set_value(true);
void AudioAlsa::AudioThreadMain() {
size_t num_frames = period_size_ / (num_channels_ * sizeof(float));
auto buffer = std::make_unique<float[]>(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!";
}
}
}

View File

@ -1,8 +1,7 @@
#ifndef AUDIO_ALSA_H
#define AUDIO_ALSA_H
#include <future>
#include <memory>
#include <atomic>
#include <thread>
#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<bool> terminate_audio_thread_ = false;
std::atomic<bool> 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<bool> promise);
void AudioThreadMain();
};
} // namespace eng

View File

@ -1,32 +1,33 @@
#include "audio_base.h"
#include "audio.h"
#include <cstring>
#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<AudioSample> sample) {
std::unique_lock<std::mutex> scoped_lock(mutex_);
samples_[0].push_back(sample);
}
void AudioBase::Update() {
task_runner_.Run();
if (audio_enabled_) {
std::lock_guard<Spinlock> 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<std::mutex> scoped_lock(mutex_);
std::lock_guard<Spinlock> 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<const Sound*>(sound)->GetBuffer(0),
const_cast<const Sound*>(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<const Sound*>(sound)->GetBuffer(0);
src[1] = const_cast<const Sound*>(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<AudioSample> 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<AudioSample> sample) {
AudioSample* s = sample.get();
s->active = false;
if (s->end_cb)
s->end_cb();
}
} // namespace eng

View File

@ -3,37 +3,47 @@
#include <list>
#include <memory>
#include <mutex>
#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<AudioSample> impl_data);
void Play(std::shared_ptr<AudioSample> sample);
void Update();
void SetEnableAudio(bool enable) { audio_enabled_ = enable; }
protected:
static constexpr int kChannelCount = 2;
std::list<std::shared_ptr<AudioSample>> 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<std::shared_ptr<AudioSample>> samples_[2];
base::Spinlock lock_;
base::TaskRunner* main_thread_task_runner_;
bool audio_enabled_ = true;
void DoStream(std::shared_ptr<AudioSample> sample, bool loop);
void EndCallback(std::shared_ptr<AudioSample> sample);
AudioBase(const AudioBase&) = delete;
AudioBase& operator=(const AudioBase&) = delete;
};
} // namespace eng

View File

@ -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() {

View File

@ -20,6 +20,9 @@ class AudioOboe : public AudioBase {
void Shutdown();
void Suspend();
void Resume();
size_t GetSampleRate();
private:

View File

@ -5,64 +5,84 @@
#include "audio.h"
#include "audio_sample.h"
using namespace base;
namespace eng {
AudioResource::AudioResource(Audio* audio)
: sample_(std::make_shared<AudioSample>()), 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> 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) {

View File

@ -1,6 +1,7 @@
#ifndef AUDIO_SAMPLE_H
#define AUDIO_SAMPLE_H
#include <atomic>
#include <memory>
#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> 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> 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<unsigned> flags{0};
std::atomic<size_t> step{10};
std::atomic<float> amplitude_inc{0};
std::atomic<float> max_amplitude{1.0f};
// Accessed by audio thread and decoder thread.
std::atomic<bool> streaming_in_progress{false};
};
} // namespace eng

14
src/engine/drawable.cc Normal file
View File

@ -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

31
src/engine/drawable.h Normal file
View File

@ -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

View File

@ -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<Geometry>();
pass_through_shader_ = CreateRenderResource<Shader>();
solid_shader_ = CreateRenderResource<Shader>();
stats_ = std::make_unique<ImageQuad>();
stats_->SetZOrder(std::numeric_limits<int>::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<CmdClear>();
cmd->rgba = {0, 0, 0, 1};
renderer_->EnqueueCommand(std::move(cmd));
renderer_->EnqueueCommand(std::make_unique<CmdEableBlend>());
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<CmdPresent>());
}
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<AudioResource> Engine::CreateAudioResource() {
return std::make_shared<AudioResource>(audio_);
void Engine::SetImageSource(const std::string& asset_name,
const std::string& file_name,
bool persistent) {
std::shared_ptr<Texture> 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<Texture>();
textures_[asset_name] = {texture, file_name, nullptr, persistent};
}
if (persistent) {
auto image = std::make_unique<Image>();
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> 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<Texture>();
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> image;
if (!it->second.asset_file.empty()) {
image = std::make_unique<Image>();
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<Texture> 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> texture = CreateRenderResource<Texture>();
textures_[asset_name] = {texture};
return texture;
}
std::unique_ptr<AudioResource> Engine::CreateAudioResource() {
return std::make_unique<AudioResource>(audio_);
}
void Engine::AddInputEvent(std::unique_ptr<InputEvent> 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<InputEvent> 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<RenderResource> Engine::CreateRenderResourceInternal(
std::unique_ptr<RenderResource> Engine::CreateRenderResourceInternal(
RenderResourceFactoryBase& factory) {
return renderer_->CreateResource(factory);
}
@ -237,6 +362,9 @@ std::shared_ptr<RenderResource> 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<Texture>());
stats_->Create("stats_tex");
else
stats_.Destory();
stats_->Destory();
}
void Engine::PrintStats() {
std::unique_ptr<Image> Engine::PrintStats() {
if (!stats_->IsVisible())
return nullptr;
constexpr int width = 200;
std::vector<std::string> 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

View File

@ -2,13 +2,15 @@
#define ENGINE_H
#include <deque>
#include <functional>
#include <list>
#include <memory>
#include <unordered_map>
#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<std::unique_ptr<Image>()>;
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 <typename T>
std::shared_ptr<T> CreateRenderResource() {
std::unique_ptr<T> CreateRenderResource() {
RenderResourceFactory<T> factory;
return std::dynamic_pointer_cast<T>(CreateRenderResourceInternal(factory));
std::unique_ptr<RenderResource> resource =
CreateRenderResourceInternal(factory);
return std::unique_ptr<T>(static_cast<T*>(resource.release()));
}
std::shared_ptr<AudioResource> 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<Texture> GetTexture(const std::string& asset_name);
std::unique_ptr<AudioResource> CreateAudioResource();
void AddInputEvent(std::unique_ptr<InputEvent> event);
std::unique_ptr<InputEvent> 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<Geometry> GetQuad() { return quad_; }
std::shared_ptr<Shader> GetPassThroughShader() {
return pass_through_shader_;
}
std::shared_ptr<Shader> 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> 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> game_;
std::shared_ptr<Geometry> quad_;
std::shared_ptr<Shader> pass_through_shader_;
std::shared_ptr<Shader> solid_shader_;
std::unique_ptr<Geometry> quad_;
std::unique_ptr<Shader> pass_through_shader_;
std::unique_ptr<Shader> solid_shader_;
base::Vector2 screen_size_ = {0, 0};
base::Matrix4x4 projection_;
@ -118,18 +159,27 @@ class Engine {
std::unique_ptr<TextureCompressor> tex_comp_opaque_;
std::unique_ptr<TextureCompressor> tex_comp_alpha_;
ImageQuad stats_;
std::list<Drawable*> drawables_;
// Textures mapped by asset name.
std::unordered_map<std::string, TextureResource> textures_;
std::unique_ptr<ImageQuad> stats_;
float fps_seconds_ = 0;
int fps_ = 0;
float seconds_accumulated_ = 0.0f;
float image_dpi_ = 200;
bool vibration_enabled_ = true;
std::deque<std::unique_ptr<InputEvent>> input_queue_;
base::Random random_;
std::shared_ptr<RenderResource> CreateRenderResourceInternal(
std::unique_ptr<RenderResource> CreateRenderResourceInternal(
RenderResourceFactoryBase& factory);
void ContextLost();
@ -137,7 +187,7 @@ class Engine {
bool CreateRenderResources();
void SetSatsVisible(bool visible);
void PrintStats();
std::unique_ptr<Image> PrintStats();
Engine(const Engine&) = delete;
Engine& operator=(const Engine&) = delete;

View File

@ -1,5 +1,8 @@
#include "font.h"
#include <codecvt>
#include <locale>
#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<std::codecvt_utf8_utf16<char16_t>,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<std::codecvt_utf8_utf16<char16_t>,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;
}
}

View File

@ -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<uint8_t[]> glyph_cache_; // Image data.

View File

@ -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;

View File

@ -46,12 +46,10 @@ class Image {
void GradientV(const base::Vector4& c1, const base::Vector4& c2, int height);
private:
base::AlignedMem<uint8_t[]>::ScoppedPtr buffer_;
base::AlignedMemPtr<uint8_t[]> buffer_;
int width_ = 0;
int height_ = 0;
Format format_ = kRGBA32;
std::string name_;
};
} // namespace eng

View File

@ -1,7 +1,6 @@
#include "image_quad.h"
#include <cassert>
#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> texture,
void ImageQuad::Create(const std::string& asset_name,
std::array<int, 2> 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<Geometry> quad = Engine::Get().GetQuad();
std::shared_ptr<Shader> 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])};
}

View File

@ -6,6 +6,7 @@
#include <array>
#include <memory>
#include <string>
namespace eng {
@ -16,7 +17,7 @@ class ImageQuad : public Animatable {
ImageQuad() = default;
~ImageQuad() override = default;
void Create(std::shared_ptr<Texture> texture,
void Create(const std::string& asset_name,
std::array<int, 2> 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<Texture> GetTexture() { return texture_; }
// Drawable interface.
void Draw(float frame_frac) override;
private:
std::shared_ptr<Texture> 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;

View File

@ -1,7 +1,7 @@
#ifndef INPUT_EVENT_H
#define INPUT_EVENT_H
#include <cassert>
#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;
};

View File

@ -1,7 +1,6 @@
#include "mesh.h"
#include <string.h>
#include <cassert>
#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;

View File

@ -1,4 +1,3 @@
#include <assert.h>
#include <string>
#include "../../base/log.h"
#include "asset_file.h"

View File

@ -1,88 +1,20 @@
#ifndef PLATFORM_H
#define PLATFORM_H
#include <exception>
#include <memory>
#include <string>
#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<Audio> audio_;
std::unique_ptr<Renderer> renderer_;
std::unique_ptr<Engine> engine_;
#if defined(__ANDROID__)
android_app* app_ = nullptr;
std::unique_ptr<ndk_helper::TapDetector> tap_detector_;
std::unique_ptr<ndk_helper::PinchDetector> pinch_detector_;
std::unique_ptr<ndk_helper::DragDetector> drag_detector_;
static int32_t HandleInput(android_app* app, AInputEvent* event);
static void HandleCmd(android_app* app, int32_t cmd);
#else
#endif
Platform(const Platform&) = delete;
Platform& operator=(const Platform&) = delete;
};
} // namespace eng
#endif // PLATFORM_H

View File

@ -1,13 +1,14 @@
#include "platform.h"
#include "platform_android.h"
#include <android_native_app_glue.h>
#include <unistd.h>
#include <memory>
#include <string>
#include "../../base/file.h"
#include "../../base/log.h"
#include "../../third_party/android/gestureDetector.h"
#include "../audio/audio_oboe.h"
#include "../../base/task_runner.h"
#include "../audio/audio.h"
#include "../engine.h"
#include "../input_event.h"
#include "../renderer/renderer.h"
@ -44,6 +45,39 @@ std::string GetApkPath(ANativeActivity* activity) {
return apk_path;
}
void Vibrate(ANativeActivity* activity, int duration) {
JNIEnv* env = nullptr;
activity->vm->AttachCurrentThread(&env, nullptr);
jclass activity_clazz = env->GetObjectClass(activity->clazz);
jclass context_clazz = env->FindClass("android/content/Context");
jfieldID vibrator_service_id = env->GetStaticFieldID(
context_clazz, "VIBRATOR_SERVICE", "Ljava/lang/String;");
jobject vibrator_service_str =
env->GetStaticObjectField(context_clazz, vibrator_service_id);
jmethodID get_system_service_id =
env->GetMethodID(activity_clazz, "getSystemService",
"(Ljava/lang/String;)Ljava/lang/Object;");
jobject vibrator_service_obj = env->CallObjectMethod(
activity->clazz, get_system_service_id, vibrator_service_str);
jclass vibrator_service_clazz = env->GetObjectClass(vibrator_service_obj);
jmethodID vibrate_id =
env->GetMethodID(vibrator_service_clazz, "vibrate", "(J)V");
jlong length = duration;
env->CallVoidMethod(vibrator_service_obj, vibrate_id, length);
env->DeleteLocalRef(vibrator_service_obj);
env->DeleteLocalRef(vibrator_service_clazz);
env->DeleteLocalRef(vibrator_service_str);
env->DeleteLocalRef(context_clazz);
env->DeleteLocalRef(activity_clazz);
activity->vm->DetachCurrentThread();
}
int32_t getDensityDpi(android_app* app) {
AConfiguration* config = AConfiguration_new();
AConfiguration_fromAssetManager(config, app->activity->assetManager);
@ -56,11 +90,11 @@ int32_t getDensityDpi(android_app* app) {
namespace eng {
Platform::Platform() = default;
Platform::~Platform() = default;
PlatformAndroid::PlatformAndroid() = default;
PlatformAndroid::~PlatformAndroid() = default;
int32_t Platform::HandleInput(android_app* app, AInputEvent* event) {
Platform* platform = reinterpret_cast<Platform*>(app->userData);
int32_t PlatformAndroid::HandleInput(android_app* app, AInputEvent* event) {
PlatformAndroid* platform = reinterpret_cast<PlatformAndroid*>(app->userData);
if (!platform->engine_)
return 0;
@ -73,88 +107,74 @@ int32_t Platform::HandleInput(android_app* app, AInputEvent* event) {
platform->engine_->AddInputEvent(std::move(input_event));
}
return 1;
}
if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) {
ndk_helper::GESTURE_STATE tap_state =
platform->tap_detector_->Detect(event);
ndk_helper::GESTURE_STATE drag_state =
platform->drag_detector_->Detect(event);
ndk_helper::GESTURE_STATE pinch_state =
platform->pinch_detector_->Detect(event);
// Tap detector has a priority over other detectors
if (tap_state == ndk_helper::GESTURE_STATE_ACTION) {
platform->engine_->AddInputEvent(
std::make_unique<InputEvent>(InputEvent::kDragCancel));
// Detect tap
Vector2 v;
platform->tap_detector_->GetPointer(v);
v = platform->engine_->ToPosition(v);
// DLOG << "Tap: " << v;
auto input_event =
std::make_unique<InputEvent>(InputEvent::kTap, v * Vector2(1, -1));
platform->engine_->AddInputEvent(std::move(input_event));
} else {
// Handle drag state
if (drag_state & ndk_helper::GESTURE_STATE_START) {
// Otherwise, start dragging
Vector2 v;
platform->drag_detector_->GetPointer(v);
v = platform->engine_->ToPosition(v);
// DLOG << "drag-start: " << v;
auto input_event = std::make_unique<InputEvent>(InputEvent::kDragStart,
v * Vector2(1, -1));
platform->engine_->AddInputEvent(std::move(input_event));
} else if (drag_state & ndk_helper::GESTURE_STATE_MOVE) {
Vector2 v;
platform->drag_detector_->GetPointer(v);
v = platform->engine_->ToPosition(v);
// DLOG << "drag: " << v;
auto input_event =
std::make_unique<InputEvent>(InputEvent::kDrag, v * Vector2(1, -1));
platform->engine_->AddInputEvent(std::move(input_event));
} else if (drag_state & ndk_helper::GESTURE_STATE_END) {
// DLOG << "drag-end!";
auto input_event = std::make_unique<InputEvent>(InputEvent::kDragEnd);
platform->engine_->AddInputEvent(std::move(input_event));
}
// Handle pinch state
if (pinch_state & ndk_helper::GESTURE_STATE_START) {
platform->engine_->AddInputEvent(
std::make_unique<InputEvent>(InputEvent::kDragCancel));
// Start new pinch
Vector2 v1;
Vector2 v2;
platform->pinch_detector_->GetPointers(v1, v2);
v1 = platform->engine_->ToPosition(v1);
v2 = platform->engine_->ToPosition(v2);
// DLOG << "pinch-start: " << v1 << " " << v2;
auto input_event = std::make_unique<InputEvent>(
InputEvent::kPinchStart, v1 * Vector2(1, -1), v2 * Vector2(1, -1));
platform->engine_->AddInputEvent(std::move(input_event));
} else if (pinch_state & ndk_helper::GESTURE_STATE_MOVE) {
// Multi touch
// Start new pinch
Vector2 v1;
Vector2 v2;
platform->pinch_detector_->GetPointers(v1, v2);
v1 = platform->engine_->ToPosition(v1);
v2 = platform->engine_->ToPosition(v2);
// DLOG << "pinch: " << v1 << " " << v2;
auto input_event = std::make_unique<InputEvent>(
InputEvent::kPinch, v1 * Vector2(1, -1), v2 * Vector2(1, -1));
platform->engine_->AddInputEvent(std::move(input_event));
}
} else if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) {
int32_t action = AMotionEvent_getAction(event);
int32_t index = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >>
AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
uint32_t flags = action & AMOTION_EVENT_ACTION_MASK;
int32_t count = AMotionEvent_getPointerCount(event);
int32_t pointer_id = AMotionEvent_getPointerId(event, index);
Vector2 pos[2] = {platform->pointer_pos_[0], platform->pointer_pos_[1]};
for (auto i = 0; i < count; ++i) {
int32_t id = AMotionEvent_getPointerId(event, i);
pos[id] = {AMotionEvent_getX(event, i), AMotionEvent_getY(event, i)};
pos[id] = platform->engine_->ToPosition(pos[id]);
}
if (pointer_id >= 2)
return 0;
std::unique_ptr<InputEvent> input_event;
switch (flags) {
case AMOTION_EVENT_ACTION_DOWN:
case AMOTION_EVENT_ACTION_POINTER_DOWN:
DLOG << "AMOTION_EVENT_ACTION_DOWN - pointer_id: " << pointer_id;
platform->pointer_pos_[pointer_id] = pos[pointer_id];
platform->pointer_down_[pointer_id] = true;
input_event =
std::make_unique<InputEvent>(InputEvent::kDragStart, pointer_id,
pos[pointer_id] * Vector2(1, -1));
break;
case AMOTION_EVENT_ACTION_UP:
case AMOTION_EVENT_ACTION_POINTER_UP:
DLOG << "AMOTION_EVENT_ACTION_UP - pointer_id: " << pointer_id;
platform->pointer_pos_[pointer_id] = pos[pointer_id];
platform->pointer_down_[pointer_id] = false;
input_event = std::make_unique<InputEvent>(
InputEvent::kDragEnd, pointer_id, pos[pointer_id] * Vector2(1, -1));
break;
case AMOTION_EVENT_ACTION_MOVE:
if (platform->pointer_down_[0] && pos[0] != platform->pointer_pos_[0]) {
platform->pointer_pos_[0] = pos[0];
input_event = std::make_unique<InputEvent>(InputEvent::kDrag, 0,
pos[0] * Vector2(1, -1));
}
if (platform->pointer_down_[1] && pos[1] != platform->pointer_pos_[1]) {
platform->pointer_pos_[1] = pos[1];
input_event = std::make_unique<InputEvent>(InputEvent::kDrag, 1,
pos[1] * Vector2(1, -1));
}
break;
case AMOTION_EVENT_ACTION_CANCEL:
input_event = std::make_unique<InputEvent>(InputEvent::kDragCancel);
break;
}
if (input_event) {
platform->engine_->AddInputEvent(std::move(input_event));
return 1;
}
return 1;
}
return 0;
}
void Platform::HandleCmd(android_app* app, int32_t cmd) {
Platform* platform = reinterpret_cast<Platform*>(app->userData);
void PlatformAndroid::HandleCmd(android_app* app, int32_t cmd) {
PlatformAndroid* platform = reinterpret_cast<PlatformAndroid*>(app->userData);
switch (cmd) {
case APP_CMD_SAVE_STATE:
@ -216,26 +236,11 @@ void Platform::HandleCmd(android_app* app, int32_t cmd) {
}
}
void Platform::Initialize(android_app* app) {
LOG << "Initializing platform.";
void PlatformAndroid::Initialize(android_app* app) {
PlatformBase::Initialize();
app_ = app;
audio_ = std::make_unique<AudioOboe>();
if (!audio_->Initialize()) {
LOG << "Failed to initialize audio system.";
throw internal_error;
}
renderer_ = std::make_unique<Renderer>();
tap_detector_ = std::make_unique<ndk_helper::TapDetector>();
drag_detector_ = std::make_unique<ndk_helper::DragDetector>();
pinch_detector_ = std::make_unique<ndk_helper::PinchDetector>();
tap_detector_->SetConfiguration(app_->config);
drag_detector_->SetConfiguration(app_->config);
pinch_detector_->SetConfiguration(app_->config);
mobile_device_ = true;
root_path_ = GetApkPath(app->activity);
@ -245,13 +250,13 @@ void Platform::Initialize(android_app* app) {
LOG << "Device DPI: " << device_dpi_;
app->userData = reinterpret_cast<void*>(this);
app->onAppCmd = Platform::HandleCmd;
app->onInputEvent = Platform::HandleInput;
app->onAppCmd = PlatformAndroid::HandleCmd;
app->onInputEvent = PlatformAndroid::HandleInput;
Update();
}
void Platform::Update() {
void PlatformAndroid::Update() {
int id;
int events;
android_poll_source* source;
@ -270,19 +275,23 @@ void Platform::Update() {
}
}
void Platform::Exit() {
void PlatformAndroid::Exit() {
ANativeActivity_finish(app_->activity);
}
void PlatformAndroid::Vibrate(int duration) {
::Vibrate(app_->activity, duration);
}
} // namespace eng
void android_main(android_app* app) {
eng::Platform platform;
eng::PlatformAndroid platform;
try {
platform.Initialize(app);
platform.RunMainLoop();
platform.Shutdown();
} catch (eng::Platform::InternalError& e) {
} catch (eng::PlatformBase::InternalError& e) {
}
_exit(0);
}

View File

@ -0,0 +1,37 @@
#ifndef PLATFORM_ANDROID_H
#define PLATFORM_ANDROID_H
#include "../../base/vecmath.h"
#include "platform_base.h"
struct android_app;
struct AInputEvent;
namespace eng {
class PlatformAndroid : public PlatformBase {
public:
PlatformAndroid();
~PlatformAndroid();
void Initialize(android_app* app);
void Update();
void Exit();
void Vibrate(int duration);
private:
android_app* app_ = nullptr;
base::Vector2 pointer_pos_[2] = {{0, 0}, {0, 0}};
bool pointer_down_[2] = {false, false};
static int32_t HandleInput(android_app* app, AInputEvent* event);
static void HandleCmd(android_app* app, int32_t cmd);
};
} // namespace eng
#endif // PLATFORM_ANDROID_H

View File

@ -3,6 +3,7 @@
#include <thread>
#include "../../base/log.h"
#include "../../base/task_runner.h"
#include "../audio/audio.h"
#include "../engine.h"
#include "../renderer/renderer.h"
@ -10,18 +11,41 @@
// Save battery on mobile devices.
#define USE_SLEEP
using namespace base;
namespace eng {
Platform::InternalError Platform::internal_error;
PlatformBase::InternalError PlatformBase::internal_error;
void Platform::Shutdown() {
PlatformBase::PlatformBase() = default;
PlatformBase::~PlatformBase() = default;
void PlatformBase::Initialize() {
LOG << "Initializing platform.";
worker_.Initialize();
TaskRunner::CreateThreadLocalTaskRunner();
audio_ = std::make_unique<Audio>();
if (!audio_->Initialize()) {
LOG << "Failed to initialize audio system.";
throw internal_error;
}
renderer_ = std::make_unique<Renderer>();
}
void PlatformBase::Shutdown() {
LOG << "Shutting down platform.";
audio_->Shutdown();
renderer_->Shutdown();
}
void Platform::RunMainLoop() {
engine_ = std::make_unique<Engine>(this, renderer_.get(), audio_.get());
void PlatformBase::RunMainLoop() {
engine_ = std::make_unique<Engine>(static_cast<Platform*>(this),
renderer_.get(), audio_.get());
if (!engine_->Initialize()) {
LOG << "Failed to initialize the engine.";
throw internal_error;
@ -59,13 +83,17 @@ void Platform::RunMainLoop() {
// Subdivide the frame time.
while (accumulator >= time_step) {
Update();
TaskRunner::GetThreadLocalTaskRunner()->SingleConsumerRun();
static_cast<Platform*>(this)->Update();
engine_->Update(time_step);
if (should_exit_) {
worker_.Shutdown();
engine_->Shutdown();
engine_.reset();
return;
}
engine_->Update(time_step);
accumulator -= time_step;
};

View File

@ -0,0 +1,59 @@
#ifndef PLATFORM_BASE_H
#define PLATFORM_BASE_H
#include <exception>
#include <memory>
#include <string>
#include "../../base/timer.h"
#include "../../base/worker.h"
#include "../audio/audio_forward.h"
namespace eng {
class Renderer;
class Engine;
class PlatformBase {
public:
void Initialize();
void Shutdown();
void RunMainLoop();
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;
protected:
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<Audio> audio_;
std::unique_ptr<Renderer> renderer_;
std::unique_ptr<Engine> engine_;
base::Worker worker_;
PlatformBase();
~PlatformBase();
PlatformBase(const PlatformBase&) = delete;
PlatformBase& operator=(const PlatformBase&) = delete;
};
} // namespace eng
#endif // PLATFORM_BASE_H

View File

@ -0,0 +1,16 @@
#ifndef PLATFORM_FORWARD_H
#define PLATFORM_FORWARD_H
namespace eng {
#if defined(__ANDROID__)
class PlatformAndroid;
using Platform = PlatformAndroid;
#elif defined(__linux__)
class PlatformLinux;
using Platform = PlatformLinux;
#endif
} // namespace eng
#endif // PLATFORM_FORWARD_H

View File

@ -1,11 +1,14 @@
#include "platform.h"
#include "platform_linux.h"
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <memory>
#include "../../base/log.h"
#include "../../base/task_runner.h"
#include "../../base/vecmath.h"
#include "../audio/audio_alsa.h"
#include "../audio/audio.h"
#include "../engine.h"
#include "../input_event.h"
#include "../renderer/renderer.h"
@ -14,39 +17,30 @@ using namespace base;
namespace eng {
Platform::Platform() = default;
Platform::~Platform() = default;
PlatformLinux::PlatformLinux() = default;
PlatformLinux::~PlatformLinux() = default;
void PlatformLinux::Initialize() {
PlatformBase::Initialize();
void Platform::Initialize() {
root_path_ = "../../";
LOG << "Root path: " << root_path_.c_str();
audio_ = std::make_unique<AudioAlsa>();
if (!audio_->Initialize()) {
LOG << "Failed to initialize audio system.";
throw internal_error;
}
renderer_ = std::make_unique<Renderer>();
if (!renderer_->Initialize()) {
LOG << "Failed to initialize renderer.";
throw internal_error;
}
LOG << "Initialized the renderer.";
Display* display = renderer_->display();
Window window = renderer_->window();
XSelectInput(
display, window,
KeyPressMask | Button1MotionMask | ButtonPressMask | ButtonReleaseMask);
XSelectInput(display, window,
KeyPressMask | Button1MotionMask | ButtonPressMask |
ButtonReleaseMask | FocusChangeMask);
Atom WM_DELETE_WINDOW = XInternAtom(display, "WM_DELETE_WINDOW", false);
XSetWMProtocols(display, window, &WM_DELETE_WINDOW, 1);
}
void Platform::Update() {
if (!engine_)
return;
void PlatformLinux::Update() {
Display* display = renderer_->display();
while (XPending(display)) {
XEvent e;
@ -55,7 +49,7 @@ void Platform::Update() {
case KeyPress: {
KeySym key = XLookupKeysym(&e.xkey, 0);
auto input_event =
std::make_unique<InputEvent>(InputEvent::kKeyPress, key);
std::make_unique<InputEvent>(InputEvent::kKeyPress, (char)key);
engine_->AddInputEvent(std::move(input_event));
// TODO: e.xkey.state & (ShiftMask | ControlMask | Mod1Mask | Mod4Mask))
break;
@ -64,8 +58,8 @@ void Platform::Update() {
Vector2 v(e.xmotion.x, e.xmotion.y);
v = engine_->ToPosition(v);
// DLOG << "drag: " << v;
auto input_event =
std::make_unique<InputEvent>(InputEvent::kDrag, v * Vector2(1, -1));
auto input_event = std::make_unique<InputEvent>(InputEvent::kDrag, 0,
v * Vector2(1, -1));
engine_->AddInputEvent(std::move(input_event));
break;
}
@ -75,19 +69,30 @@ void Platform::Update() {
v = engine_->ToPosition(v);
// DLOG << "drag-start: " << v;
auto input_event = std::make_unique<InputEvent>(
InputEvent::kDragStart, v * Vector2(1, -1));
InputEvent::kDragStart, 0, v * Vector2(1, -1));
engine_->AddInputEvent(std::move(input_event));
}
break;
}
case ButtonRelease: {
if (e.xbutton.button == 1) {
Vector2 v(e.xbutton.x, e.xbutton.y);
v = engine_->ToPosition(v);
// DLOG << "drag-end!";
auto input_event = std::make_unique<InputEvent>(InputEvent::kDragEnd);
auto input_event = std::make_unique<InputEvent>(
InputEvent::kDragEnd, 0, v * Vector2(1, -1));
engine_->AddInputEvent(std::move(input_event));
}
break;
}
case FocusOut: {
engine_->LostFocus();
break;
}
case FocusIn: {
engine_->GainedFocus();
break;
}
case ClientMessage: {
// WM_DELETE_WINDOW is the only registered type for now.
should_exit_ = true;
@ -97,19 +102,19 @@ void Platform::Update() {
}
}
void Platform::Exit() {
void PlatformLinux::Exit() {
should_exit_ = true;
}
} // namespace eng
int main(int argc, char** argv) {
eng::Platform platform;
eng::PlatformLinux platform;
try {
platform.Initialize();
platform.RunMainLoop();
platform.Shutdown();
} catch (eng::Platform::InternalError& e) {
} catch (eng::PlatformBase::InternalError& e) {
return -1;
}
return 0;

View File

@ -0,0 +1,24 @@
#ifndef PLATFORM_LINUX_H
#define PLATFORM_LINUX_H
#include "platform_base.h"
namespace eng {
class PlatformLinux : public PlatformBase {
public:
PlatformLinux();
~PlatformLinux();
void Initialize();
void Update();
void Exit();
void Vibrate(int duration) {}
};
} // namespace eng
#endif // PLATFORM_LINUX_H

View File

@ -1,7 +1,5 @@
#include "geometry.h"
#include <cassert>
#include "../engine.h"
#include "../mesh.h"
#include "render_command.h"

View File

@ -10,6 +10,7 @@
#elif defined(__linux__)
#include "../../third_party/glew/glew.h"
#include "../../third_party/glew/glxew.h"
// Define the missing format for the etc1
#ifndef GL_ETC1_RGB8_OES

View File

@ -14,8 +14,6 @@
namespace eng {
RENDER_COMMAND_IMPL(CmdEableBlend, false);
RENDER_COMMAND_IMPL(CmdClear, false);
RENDER_COMMAND_IMPL(CmdPresent, false);
RENDER_COMMAND_IMPL(CmdUpdateTexture, true);
RENDER_COMMAND_IMPL(CmdDestoryTexture, true);

View File

@ -4,6 +4,7 @@
#include <array>
#include <memory>
#include <string>
#include "../../base/hash.h"
#include "../../base/vecmath.h"
#include "renderer_types.h"
@ -14,11 +15,13 @@ class Image;
class ShaderSource;
class Mesh;
#define RENDER_COMMAND_BEGIN(NAME) \
#define RENDER_COMMAND_BEGIN(NAME) \
struct NAME : RenderCommand { \
static constexpr CommandId CMD_ID = HHASH(#NAME); \
NAME();
#define RENDER_COMMAND_END };
#define RENDER_COMMAND_END \
} \
;
struct RenderCommand {
using CommandId = size_t;
@ -44,13 +47,6 @@ struct RenderCommand {
#endif
};
RENDER_COMMAND_BEGIN(CmdEableBlend)
RENDER_COMMAND_END
RENDER_COMMAND_BEGIN(CmdClear)
std::array<float, 4> rgba;
RENDER_COMMAND_END
RENDER_COMMAND_BEGIN(CmdPresent)
RENDER_COMMAND_END

View File

@ -1,7 +1,5 @@
#include "render_resource.h"
#include <cassert>
#include "renderer.h"
namespace eng {

View File

@ -1,12 +1,14 @@
#include "renderer.h"
#include <algorithm>
#include <cassert>
#include <cstring>
#include <sstream>
#include "../../base/log.h"
#include "../../base/vecmath.h"
#ifdef THREADED_RENDERING
#include "../../base/task_runner.h"
#endif // THREADED_RENDERING
#include "../image.h"
#include "../mesh.h"
#include "../shader_source.h"
@ -15,6 +17,8 @@
#include "shader.h"
#include "texture.h"
using namespace base;
namespace {
constexpr GLenum kGlPrimitive[eng::kPrimitive_Max] = {GL_TRIANGLES,
@ -31,20 +35,17 @@ const std::string kAttributeNames[eng::kAttribType_Max] = {
namespace eng {
Renderer::Renderer() = default;
Renderer::~Renderer() {
TerminateWorker();
}
void Renderer::SetContextLostCB(base::Closure cb) {
context_lost_cb_ = std::move(cb);
}
void Renderer::Update() {
#ifdef THREADED_RENDERING
task_runner_.Run();
Renderer::Renderer()
: main_thread_task_runner_(TaskRunner::GetThreadLocalTaskRunner()) {}
#else
Renderer::Renderer() = default;
#endif // THREADED_RENDERING
Renderer::~Renderer() = default;
void Renderer::SetContextLostCB(Closure cb) {
context_lost_cb_ = std::move(cb);
}
void Renderer::ContextLost() {
@ -59,7 +60,7 @@ void Renderer::ContextLost() {
InvalidateAllResources();
#ifdef THREADED_RENDERING
task_runner_.Enqueue(context_lost_cb_);
main_thread_task_runner_->EnqueueTask(HERE, context_lost_cb_);
#else
context_lost_cb_();
#endif // THREADED_RENDERING
@ -79,7 +80,7 @@ std::unique_ptr<RenderResource> Renderer::CreateResource(
else if (factory.IsTypeOf<Texture>())
impl_data = std::make_shared<TextureOpenGL>();
else
assert(false);
NOTREACHED << "- Unknown resource type.";
unsigned resource_id = ++last_id;
auto resource = factory.Create(resource_id, impl_data, this);
@ -105,7 +106,7 @@ void Renderer::EnqueueCommand(std::unique_ptr<RenderCommand> cmd) {
return;
}
bool new_frame = cmd->cmd_id == HHASH("CmdPresent");
bool new_frame = cmd->cmd_id == CmdPresent::CMD_ID;
draw_commands_[1].push_back(std::move(cmd));
if (new_frame) {
render_queue_size_ = draw_commands_[1].size();
@ -174,7 +175,8 @@ bool Renderer::InitCommon() {
if (extensions.find("GL_OES_vertex_array_object") != extensions.end()) {
// This extension seems to be broken on older PowerVR drivers.
if (!strstr(renderer, "PowerVR SGX 53") &&
!strstr(renderer, "PowerVR SGX 54")) {
!strstr(renderer, "PowerVR SGX 54") &&
!strstr(renderer, "Android Emulator")) {
vertex_array_objects_ = true;
}
}
@ -201,27 +203,33 @@ bool Renderer::InitCommon() {
glViewport(0, 0, screen_width_, screen_height_);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glClearColor(0, 0, 0, 1);
return true;
}
void Renderer::InvalidateAllResources() {
for (auto& r : resources_) {
r.second->Destroy();
}
for (auto& r : resources_)
r.second->Destroy();
}
bool Renderer::StartWorker() {
bool Renderer::StartRenderThread() {
#ifdef THREADED_RENDERING
LOG << "Starting render thread.";
global_commands_.clear();
draw_commands_[0].clear();
draw_commands_[1].clear();
terminate_worker_ = false;
terminate_render_thread_ = false;
std::promise<bool> promise;
std::future<bool> future = promise.get_future();
worker_thread_ = std::thread(&Renderer::WorkerMain, this, std::move(promise));
render_thread_ =
std::thread(&Renderer::RenderThreadMain, this, std::move(promise));
future.wait();
return future.get();
#else
LOG << "Single threaded rendering.";
@ -229,18 +237,18 @@ bool Renderer::StartWorker() {
#endif // THREADED_RENDERING
}
void Renderer::TerminateWorker() {
void Renderer::TerminateRenderThread() {
#ifdef THREADED_RENDERING
DCHECK(!terminate_render_thread_);
// Notify worker thread and wait for it to terminate.
{
std::unique_lock<std::mutex> scoped_lock(mutex_);
if (terminate_worker_)
return;
terminate_worker_ = true;
terminate_render_thread_ = true;
}
cv_.notify_one();
LOG << "Terminating render thread";
worker_thread_.join();
render_thread_.join();
#else
ShutdownInternal();
#endif // THREADED_RENDERING
@ -248,7 +256,7 @@ void Renderer::TerminateWorker() {
#ifdef THREADED_RENDERING
void Renderer::WorkerMain(std::promise<bool> promise) {
void Renderer::RenderThreadMain(std::promise<bool> promise) {
promise.set_value(InitInternal());
std::deque<std::unique_ptr<RenderCommand>> cq[2];
@ -257,9 +265,9 @@ void Renderer::WorkerMain(std::promise<bool> promise) {
std::unique_lock<std::mutex> scoped_lock(mutex_);
cv_.wait(scoped_lock, [&]() -> bool {
return !global_commands_.empty() || !draw_commands_[0].empty() ||
terminate_worker_;
terminate_render_thread_;
});
if (terminate_worker_) {
if (terminate_render_thread_) {
ShutdownInternal();
return;
}
@ -272,17 +280,11 @@ void Renderer::WorkerMain(std::promise<bool> promise) {
LOG << "draw queue size: " << (int)cq[1].size();
#endif
while (!cq[0].empty()) {
std::unique_ptr<RenderCommand> cmd;
cmd.swap(cq[0].front());
cq[0].pop_front();
ProcessCommand(cmd.get());
}
while (!cq[1].empty()) {
std::unique_ptr<RenderCommand> cmd;
cmd.swap(cq[1].front());
cq[1].pop_front();
ProcessCommand(cmd.get());
for (int i = 0; i < 2; ++i) {
while (!cq[i].empty()) {
ProcessCommand(cq[i].front().get());
cq[i].pop_front();
}
}
}
}
@ -295,77 +297,59 @@ void Renderer::ProcessCommand(RenderCommand* cmd) {
#endif
switch (cmd->cmd_id) {
case HHASH("CmdEableBlend"):
HandleCmdEnableBlend(cmd);
break;
case HHASH("CmdClear"):
HandleCmdClear(cmd);
break;
case HHASH("CmdPresent"):
case CmdPresent::CMD_ID:
HandleCmdPresent(cmd);
break;
case HHASH("CmdUpdateTexture"):
case CmdUpdateTexture::CMD_ID:
HandleCmdUpdateTexture(cmd);
break;
case HHASH("CmdDestoryTexture"):
case CmdDestoryTexture::CMD_ID:
HandleCmdDestoryTexture(cmd);
break;
case HHASH("CmdActivateTexture"):
case CmdActivateTexture::CMD_ID:
HandleCmdActivateTexture(cmd);
break;
case HHASH("CmdCreateGeometry"):
case CmdCreateGeometry::CMD_ID:
HandleCmdCreateGeometry(cmd);
break;
case HHASH("CmdDestroyGeometry"):
case CmdDestroyGeometry::CMD_ID:
HandleCmdDestroyGeometry(cmd);
break;
case HHASH("CmdDrawGeometry"):
case CmdDrawGeometry::CMD_ID:
HandleCmdDrawGeometry(cmd);
break;
case HHASH("CmdCreateShader"):
case CmdCreateShader::CMD_ID:
HandleCmdCreateShader(cmd);
break;
case HHASH("CmdDestroyShader"):
case CmdDestroyShader::CMD_ID:
HandleCmdDestroyShader(cmd);
break;
case HHASH("CmdActivateShader"):
case CmdActivateShader::CMD_ID:
HandleCmdActivateShader(cmd);
break;
case HHASH("CmdSetUniformVec2"):
case CmdSetUniformVec2::CMD_ID:
HandleCmdSetUniformVec2(cmd);
break;
case HHASH("CmdSetUniformVec3"):
case CmdSetUniformVec3::CMD_ID:
HandleCmdSetUniformVec3(cmd);
break;
case HHASH("CmdSetUniformVec4"):
case CmdSetUniformVec4::CMD_ID:
HandleCmdSetUniformVec4(cmd);
break;
case HHASH("CmdSetUniformMat4"):
case CmdSetUniformMat4::CMD_ID:
HandleCmdSetUniformMat4(cmd);
break;
case HHASH("CmdSetUniformFloat"):
case CmdSetUniformFloat::CMD_ID:
HandleCmdSetUniformFloat(cmd);
break;
case HHASH("CmdSetUniformInt"):
case CmdSetUniformInt::CMD_ID:
HandleCmdSetUniformInt(cmd);
break;
default:
assert(false);
break;
NOTREACHED << "- Unknown render command: " << cmd->cmd_id;
}
}
void Renderer::HandleCmdEnableBlend(RenderCommand* cmd) {
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
void Renderer::HandleCmdClear(RenderCommand* cmd) {
auto* c = static_cast<CmdClear*>(cmd);
glClearColor(c->rgba[0], c->rgba[1], c->rgba[2], c->rgba[3]);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
void Renderer::HandleCmdUpdateTexture(RenderCommand* cmd) {
auto* c = static_cast<CmdUpdateTexture*>(cmd);
auto impl_data = reinterpret_cast<TextureOpenGL*>(c->impl_data.get());
@ -379,7 +363,7 @@ void Renderer::HandleCmdUpdateTexture(RenderCommand* cmd) {
glBindTexture(GL_TEXTURE_2D, gl_id);
if (c->image->IsCompressed()) {
GLenum format;
GLenum format = 0;
switch (c->image->GetFormat()) {
case Image::kDXT1:
format = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
@ -399,15 +383,23 @@ void Renderer::HandleCmdUpdateTexture(RenderCommand* cmd) {
break;
#endif
default:
assert(false);
return;
NOTREACHED << "- Unhandled texure format: " << c->image->GetFormat();
}
glCompressedTexImage2D(GL_TEXTURE_2D, 0, format, c->image->GetWidth(),
c->image->GetHeight(), 0, c->image->GetSize(),
c->image->GetBuffer());
// Sometimes the first glCompressedTexImage2D call after context-lost
// generates GL_INVALID_VALUE.
GLenum err = glGetError();
if (err == GL_INVALID_VALUE) {
glCompressedTexImage2D(GL_TEXTURE_2D, 0, format, c->image->GetWidth(),
c->image->GetHeight(), 0, c->image->GetSize(),
c->image->GetBuffer());
err = glGetError();
}
if (err != GL_NO_ERROR)
LOG << "GL ERROR after glCompressedTexImage2D: " << (int)err;
} else {

View File

@ -19,15 +19,8 @@
#endif // THREADED_RENDERING
#include "opengl.h"
#if defined(__linux__) && !defined(__ANDROID__)
#include <X11/Xlib.h>
#include "../../third_party/glew/glxew.h"
#endif
#include "../../base/closure.h"
#ifdef THREADED_RENDERING
#include "../../base/task_runner.h"
#endif // THREADED_RENDERING
#include "render_resource.h"
#include "renderer_types.h"
@ -35,6 +28,12 @@
struct ANativeWindow;
#endif
#ifdef THREADED_RENDERING
namespace base {
class TaskRunner;
}
#endif // THREADED_RENDERING
namespace eng {
struct RenderCommand;
@ -55,8 +54,6 @@ class Renderer {
void Shutdown();
void Update();
void ContextLost();
std::unique_ptr<RenderResource> CreateResource(
@ -152,10 +149,10 @@ class Renderer {
std::condition_variable cv_;
std::mutex mutex_;
std::thread worker_thread_;
bool terminate_worker_ = false;
std::thread render_thread_;
bool terminate_render_thread_ = false;
base::TaskRunner task_runner_;
base::TaskRunner* main_thread_task_runner_;
#endif // THREADED_RENDERING
// Stats.
@ -178,17 +175,15 @@ class Renderer {
void InvalidateAllResources();
bool StartWorker();
void TerminateWorker();
bool StartRenderThread();
void TerminateRenderThread();
#ifdef THREADED_RENDERING
void WorkerMain(std::promise<bool> promise);
void RenderThreadMain(std::promise<bool> promise);
#endif // THREADED_RENDERING
void ProcessCommand(RenderCommand* cmd);
void HandleCmdEnableBlend(RenderCommand* cmd);
void HandleCmdClear(RenderCommand* cmd);
void HandleCmdPresent(RenderCommand* cmd);
void HandleCmdUpdateTexture(RenderCommand* cmd);
void HandleCmdDestoryTexture(RenderCommand* cmd);

View File

@ -2,17 +2,25 @@
#include <android/native_window.h>
#include "../../base/log.h"
#include "../../third_party/android/GLContext.h"
namespace eng {
bool Renderer::Initialize(ANativeWindow* window) {
LOG << "Initializing renderer.";
window_ = window;
return StartWorker();
return StartRenderThread();
}
void Renderer::Shutdown() {
TerminateWorker();
if (terminate_render_thread_)
return;
LOG << "Shutting down renderer.";
TerminateRenderThread();
}
bool Renderer::InitInternal() {
@ -51,7 +59,9 @@ void Renderer::ShutdownInternal() {
void Renderer::HandleCmdPresent(RenderCommand* cmd) {
if (EGL_SUCCESS != ndk_helper::GLContext::GetInstance()->Swap()) {
ContextLost();
return;
}
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
} // namespace eng

View File

@ -1,20 +1,21 @@
#include "renderer.h"
#include <X11/Xutil.h>
#include "../../base/log.h"
#include "../../third_party/glew/glew.h"
namespace eng {
bool Renderer::Initialize() {
LOG << "Initializing renderer.";
if (!CreateWindow())
return false;
return StartWorker();
return StartRenderThread();
}
void Renderer::Shutdown() {
TerminateWorker();
LOG << "Shutting down renderer.";
TerminateRenderThread();
DestroyWindow();
}
@ -91,8 +92,10 @@ void Renderer::ShutdownInternal() {
}
void Renderer::HandleCmdPresent(RenderCommand* cmd) {
if (display_)
if (display_) {
glXSwapBuffers(display_, window_);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
}
} // namespace eng

View File

@ -1,7 +1,5 @@
#include "shader.h"
#include <cassert>
#include "../shader_source.h"
#include "render_command.h"
#include "renderer.h"

View File

@ -1,8 +1,7 @@
#include "texture.h"
#include <cassert>
#include "../../engine/image.h"
#include "../../base/log.h"
#include "../image.h"
#include "render_command.h"
#include "renderer.h"
@ -34,6 +33,7 @@ void Texture::Destroy() {
cmd->impl_data = impl_data_;
renderer_->EnqueueCommand(std::move(cmd));
valid_ = false;
DLOG << "Texture destroyed. resource_id: " << resource_id_;
}
}

View File

@ -2,18 +2,17 @@
#include <memory>
#include "../base/log.h"
#include "engine.h"
#include "renderer/geometry.h"
#include "renderer/shader.h"
namespace eng {
void SolidQuad::Draw() {
if (!IsVisible())
return;
void SolidQuad::Draw(float frame_frac) {
DCHECK(IsVisible());
std::shared_ptr<Geometry> quad = Engine::Get().GetQuad();
std::shared_ptr<Shader> shader = Engine::Get().GetSolidShader();
Shader* shader = Engine::Get().GetSolidShader();
shader->Activate();
shader->SetUniform("offset", offset_);
@ -23,7 +22,7 @@ void SolidQuad::Draw() {
shader->SetUniform("projection", Engine::Get().GetProjectionMarix());
shader->SetUniform("color", color_);
quad->Draw();
Engine::Get().GetQuad()->Draw();
}
} // namespace eng

View File

@ -17,7 +17,8 @@ class SolidQuad : public Animatable {
void SetColor(const base::Vector4& color) override { color_ = color; }
base::Vector4 GetColor() const override { return color_; }
void Draw();
// Drawable interface.
void Draw(float frame_frac) override;
private:
base::Vector4 color_ = {1, 1, 1, 1};

View File

@ -1,6 +1,6 @@
#include "sound.h"
#include <cassert>
#include <array>
#include "../base/log.h"
#define MINIMP3_ONLY_MP3
@ -17,8 +17,31 @@ using namespace base;
namespace {
constexpr size_t kMinSamplesForStreaming = 500000;
constexpr size_t kMaxSamplesPerDecode = MINIMP3_MAX_SAMPLES_PER_FRAME * 50;
constexpr size_t kMaxSamplesPerChunk = MINIMP3_MAX_SAMPLES_PER_FRAME * 10;
template <typename T>
std::array<std::unique_ptr<T[]>, 2> Deinterleave(size_t num_channels,
size_t num_samples,
float* input_buffer) {
std::array<std::unique_ptr<T[]>, 2> channels;
if (num_channels == 1) {
// Single channel.
channels[0] = std::make_unique<T[]>(num_samples);
for (int i = 0; i < num_samples; ++i)
channels[0].get()[i] = input_buffer[i];
} else {
// Deinterleave into separate channels.
channels[0] = std::make_unique<T[]>(num_samples);
channels[1] = std::make_unique<T[]>(num_samples);
for (int i = 0, j = 0; i < num_samples * 2; i += 2) {
channels[0].get()[j] = input_buffer[i];
channels[1].get()[j++] = input_buffer[i + 1];
}
}
return channels;
}
} // namespace
@ -31,7 +54,7 @@ Sound::~Sound() {
mp3dec_ex_close(mp3_dec_.get());
}
bool Sound::Load(const std::string& file_name) {
bool Sound::Load(const std::string& file_name, bool stream) {
size_t buffer_size = 0;
encoded_data_ = AssetFile::ReadWholeFile(file_name.c_str(),
Engine::Get().GetRootPath().c_str(),
@ -43,7 +66,8 @@ bool Sound::Load(const std::string& file_name) {
if (mp3_dec_)
mp3dec_ex_close(mp3_dec_.get());
mp3_dec_ = std::make_unique<mp3dec_ex_t>();
else
mp3_dec_ = std::make_unique<mp3dec_ex_t>();
int err = mp3dec_ex_open_buf(mp3_dec_.get(),
reinterpret_cast<uint8_t*>(encoded_data_.get()),
@ -53,10 +77,7 @@ bool Sound::Load(const std::string& file_name) {
return false;
}
is_streaming_sound_ =
mp3_dec_->samples / mp3_dec_->info.channels > kMinSamplesForStreaming
? true
: false;
is_streaming_sound_ = stream;
LOG << (is_streaming_sound_ ? "Streaming " : "Loading ") << file_name << ". "
<< mp3_dec_->samples << " samples, " << mp3_dec_->info.channels
@ -66,30 +87,42 @@ bool Sound::Load(const std::string& file_name) {
num_channels_ = mp3_dec_->info.channels;
hz_ = mp3_dec_->info.hz;
num_samples_back_ = 0;
num_samples_back_ = cur_sample_back_ = 0;
eof_ = false;
assert(num_channels_ > 0 && num_channels_ <= 2);
DCHECK(num_channels_ > 0 && num_channels_ <= 2);
size_t system_hz = Engine::Get().GetAudioSampleRate();
// Fill up buffers.
if (is_streaming_sound_) {
resampler_ = std::make_unique<r8b::CDSPResampler16>(hz_, system_hz,
kMaxSamplesPerDecode);
kMaxSamplesPerChunk);
StreamInternal(kMaxSamplesPerDecode, false);
SwapBuffersInternal();
StreamInternal(kMaxSamplesPerDecode, false);
// Fill up buffers.
StreamInternal(kMaxSamplesPerChunk, false);
SwapBuffers();
StreamInternal(kMaxSamplesPerChunk, false);
if (eof_) {
// Sample is smaller than buffer. No need to stream.
is_streaming_sound_ = false;
mp3dec_ex_close(mp3_dec_.get());
mp3_dec_.reset();
encoded_data_.reset();
resampler_.reset();
}
} else {
resampler_ = std::make_unique<r8b::CDSPResampler16>(hz_, system_hz,
mp3_dec_->samples);
// Decode entire file.
StreamInternal(mp3_dec_->samples, false);
SwapBuffersInternal();
SwapBuffers();
eof_ = true;
// We are done with decoding for non-streaming sound.
mp3dec_ex_close(mp3_dec_.get());
mp3_dec_.reset();
encoded_data_.reset();
resampler_.reset();
}
@ -98,42 +131,44 @@ bool Sound::Load(const std::string& file_name) {
}
bool Sound::Stream(bool loop) {
assert(is_streaming_sound_);
DCHECK(is_streaming_sound_);
return StreamInternal(kMaxSamplesPerDecode, loop);
return StreamInternal(kMaxSamplesPerChunk, loop);
}
void Sound::SwapBuffers() {
assert(is_streaming_sound_);
front_buffer_[0].swap(back_buffer_[0]);
front_buffer_[1].swap(back_buffer_[1]);
SwapBuffersInternal();
// Memory barrier to ensure all memory writes become visible to the decoder
// thread.
streaming_in_progress_.store(true, std::memory_order_release);
cur_sample_front_ = cur_sample_back_;
num_samples_front_ = num_samples_back_;
num_samples_back_ = 0;
}
size_t Sound::IsStreamingInProgress() const {
assert(is_streaming_sound_);
return streaming_in_progress_.load(std::memory_order_acquire);
void Sound::ResetStream() {
if (is_streaming_sound_ && cur_sample_front_ != 0) {
// Seek to 0 and ivalidate decoded data.
mp3dec_ex_seek(mp3_dec_.get(), 0);
eof_ = false;
num_samples_back_ = num_samples_front_ = 0;
cur_sample_front_ = cur_sample_back_ = 0;
}
}
size_t Sound::GetSize() const {
return num_samples_front_ * sizeof(mp3d_sample_t);
}
float* Sound::GetBuffer(int channel) {
float* Sound::GetBuffer(int channel) const {
return front_buffer_[channel].get();
}
bool Sound::StreamInternal(size_t num_samples, bool loop) {
auto buffer = std::make_unique<float[]>(num_samples);
cur_sample_back_ = mp3_dec_->cur_sample;
for (;;) {
size_t samples_read =
mp3dec_ex_read(mp3_dec_.get(), buffer.get(), num_samples);
if (samples_read != num_samples && mp3_dec_->last_error) {
LOG << "mp3 decode error: " << mp3_dec_->last_error;
eof_ = true;
return false;
}
@ -152,57 +187,43 @@ bool Sound::StreamInternal(size_t num_samples, bool loop) {
else
eof_ = true;
// Memory barrier to ensure all memory writes become visible to the audio
// thread.
streaming_in_progress_.store(false, std::memory_order_release);
return true;
}
void Sound::SwapBuffersInternal() {
front_buffer_[0].swap(back_buffer_[0]);
front_buffer_[1].swap(back_buffer_[1]);
num_samples_front_ = num_samples_back_;
num_samples_back_ = 0;
}
void Sound::Preprocess(std::unique_ptr<float[]> input_buffer) {
std::unique_ptr<double[]> channels[2];
// r8b resampler supports only double floating point type.
if (num_channels_ == 1) {
// Single channel.
channels[0] = std::make_unique<double[]>(num_samples_back_);
for (int i = 0; i < num_samples_back_; ++i)
channels[0].get()[i] = input_buffer.get()[i];
} else {
// Deinterleave into separate channels.
channels[0] = std::make_unique<double[]>(num_samples_back_);
channels[1] = std::make_unique<double[]>(num_samples_back_);
for (int i = 0, j = 0; i < num_samples_back_ * 2; i += 2) {
channels[0].get()[j] = input_buffer.get()[i];
channels[1].get()[j++] = input_buffer.get()[i + 1];
}
}
size_t system_hz = Engine::Get().GetAudioSampleRate();
size_t resampled_num_samples =
((float)system_hz / (float)hz_) * num_samples_back_;
if (!back_buffer_[0]) {
back_buffer_[0] = std::make_unique<float[]>(resampled_num_samples);
if (system_hz == hz_) {
auto channels = Deinterleave<float>(num_channels_, num_samples_back_,
input_buffer.get());
// No need for resmapling.
back_buffer_[0] = std::move(channels[0]);
if (num_channels_ == 2)
back_buffer_[1] = std::make_unique<float[]>(resampled_num_samples);
}
back_buffer_[1] = std::move(channels[1]);
} else {
// r8b resampler supports only double floating point type.
auto channels = Deinterleave<double>(num_channels_, num_samples_back_,
input_buffer.get());
// Resample to match the system sample rate if needed. Output from the
// resampler is converted from double to float.
for (int i = 0; i < num_channels_; ++i) {
resampler_->oneshot(channels[i].get(), num_samples_back_,
back_buffer_[i].get(), resampled_num_samples);
size_t resampled_num_samples =
((float)system_hz / (float)hz_) * num_samples_back_;
if (!back_buffer_[0]) {
if (max_samples_ < resampled_num_samples)
max_samples_ = resampled_num_samples;
back_buffer_[0] = std::make_unique<float[]>(max_samples_);
if (num_channels_ == 2)
back_buffer_[1] = std::make_unique<float[]>(max_samples_);
}
// Resample to match the system sample rate.
for (int i = 0; i < num_channels_; ++i) {
resampler_->oneshot(channels[i].get(), num_samples_back_,
back_buffer_[i].get(), resampled_num_samples);
}
num_samples_back_ = resampled_num_samples;
}
num_samples_back_ = resampled_num_samples;
}
} // namespace eng

View File

@ -2,7 +2,6 @@
#define SOUND_H
#include <stdint.h>
#include <atomic>
#include <memory>
#include <string>
@ -16,50 +15,43 @@ namespace eng {
// Class for streaming and non-streaming sound assets. Loads and decodes mp3
// files. Resamples the decoded audio to match the system sample rate if
// necessary. Can be shared between multiple audio resources and played
// simultaneously.
//
// Streaming starts automatically for big files and it's done from memory. It
// loads the entire mp3 file and decodes small chunks on demand. Streaming sound
// cannot be shared between multiple audio resources.
// necessary. Non-streaming sounds Can be shared between multiple audio
// resources and played simultaneously.
class Sound {
public:
Sound();
~Sound();
bool Load(const std::string& file_name);
bool Load(const std::string& file_name, bool stream);
bool Stream(bool loop);
void SwapBuffers();
size_t IsStreamingInProgress() const;
void ResetStream();
// Buffer size per channel.
size_t GetSize() const;
const float* GetBuffer(int channel) const {
return front_buffer_[channel].get();
}
float* GetBuffer(int channel);
bool IsValid() const { return !!front_buffer_[0]; }
float* GetBuffer(int channel) const;
size_t GetNumSamples() const { return num_samples_front_; }
size_t num_channels() const { return num_channels_; }
size_t hz() const { return hz_; }
bool is_streaming_sound() { return is_streaming_sound_; }
bool is_streaming_sound() const { return is_streaming_sound_; }
bool eof() const { return eof_; }
private:
// Buffer holding decoded audio.
std::unique_ptr<float[]> back_buffer_[2];
std::unique_ptr<float[]> front_buffer_[2];
size_t num_samples_back_ = 0;
size_t num_samples_front_ = 0;
size_t max_samples_ = 0;
size_t cur_sample_front_ = 0;
size_t cur_sample_back_ = 0;
size_t num_channels_ = 0;
size_t hz_ = 0;
@ -71,14 +63,11 @@ class Sound {
std::unique_ptr<r8b::CDSPResampler16> resampler_;
bool eof_ = false;
std::atomic<bool> streaming_in_progress_ = false;
bool is_streaming_sound_ = false;
bool StreamInternal(size_t num_samples, bool loop);
void SwapBuffersInternal();
void Preprocess(std::unique_ptr<float[]> input_buffer);
};

View File

@ -1,6 +1,7 @@
#include "sound_player.h"
#include "../base/interpolation.h"
#include "../base/log.h"
#include "audio/audio_resource.h"
#include "engine.h"
#include "sound.h"
@ -14,31 +15,43 @@ SoundPlayer::SoundPlayer() : resource_(Engine::Get().CreateAudioResource()) {}
SoundPlayer::~SoundPlayer() = default;
void SoundPlayer::SetSound(std::shared_ptr<Sound> sound) {
CHECK(!sound->is_streaming_sound()) << "Streaming sound cannot be shared.";
sound_ = sound;
}
void SoundPlayer::Play(bool loop) {
resource_->SetAmplitudeInc(0);
resource_->SetLoop(loop);
resource_->Play(sound_, max_amplitude_, true);
void SoundPlayer::SetSound(std::unique_ptr<Sound> sound) {
sound_ = std::move(sound);
}
void SoundPlayer::Resume(bool fade_in) {
if (fade_in)
resource_->SetAmplitudeInc(0.0001f);
resource_->Play(sound_, fade_in ? 0 : max_amplitude_, false);
void SoundPlayer::Play(bool loop, float fade_in_duration) {
if (sound_) {
int step = variate_ ? Engine::Get().GetRandomGenerator().Roll(3) - 2 : 0;
resource_->SetResampleStep(step);
resource_->SetLoop(loop);
if (fade_in_duration > 0)
resource_->SetAmplitudeInc(1.0f / (sound_->hz() * fade_in_duration));
else
resource_->SetAmplitudeInc(0);
resource_->Play(sound_, fade_in_duration > 0 ? 0 : max_amplitude_, true);
}
}
void SoundPlayer::Stop(bool fade_out) {
if (fade_out)
resource_->SetAmplitudeInc(-0.0001f);
void SoundPlayer::Resume(float fade_in_duration) {
if (fade_in_duration > 0)
resource_->SetAmplitudeInc(1.0f / (sound_->hz() * fade_in_duration));
resource_->Play(sound_, fade_in_duration > 0 ? 0 : -1, false);
}
void SoundPlayer::Stop(float fade_out_duration) {
if (fade_out_duration > 0)
resource_->SetAmplitudeInc(-1.0f / (sound_->hz() * fade_out_duration));
else
resource_->Stop();
}
void SoundPlayer::SetVariate(bool variate) {
int step = variate ? Engine::Get().GetRandomGenerator().Roll(3) - 2 : 0;
resource_->SetResampleStep(step);
variate_ = variate;
}
void SoundPlayer::SetSimulateStereo(bool simulate) {

View File

@ -16,19 +16,19 @@ class SoundPlayer {
~SoundPlayer();
void SetSound(std::shared_ptr<Sound> sound);
void SetSound(std::unique_ptr<Sound> sound);
void Play(bool loop);
void Play(bool loop, float fade_in_duration = 0);
void Resume(bool fade_in);
void Resume(float fade_in_duration = 0);
void Stop(bool fade_out);
void Stop(float fade_out_duration = 0);
// Picks a random variation of the sound or the original sound if "variate" is
// false. Variations are obtained by slightly up or down sampling.
void SetVariate(bool variate);
// Enable or disable stereo simulation effect. Valid for mono samples only.
// Disabled by default.
// Enable or disable stereo simulation effect. Disabled by default.
void SetSimulateStereo(bool simulate);
void SetMaxAplitude(float max_amplitude);
@ -37,11 +37,13 @@ class SoundPlayer {
void SetEndCallback(base::Closure cb);
private:
std::shared_ptr<AudioResource> resource_;
std::unique_ptr<AudioResource> resource_;
std::shared_ptr<Sound> sound_;
float max_amplitude_ = 1.0f;
bool variate_ = false;
SoundPlayer(const SoundPlayer&) = delete;
SoundPlayer& operator=(const SoundPlayer&) = delete;
};

180
src/third_party/BUILD.gn vendored Normal file
View File

@ -0,0 +1,180 @@
source_set("third_party") {
sources = [
"jsoncpp/json.h",
"jsoncpp/jsoncpp.cc",
"r8b/CDSPBlockConvolver.h",
"r8b/CDSPFIRFilter.h",
"r8b/CDSPFracInterpolator.h",
"r8b/CDSPHBDownsampler.h",
"r8b/CDSPHBUpsampler.h",
"r8b/CDSPProcessor.h",
"r8b/CDSPRealFFT.h",
"r8b/CDSPResampler.h",
"r8b/CDSPSincFilterGen.h",
"r8b/fft4g.h",
"r8b/pffft.cpp",
"r8b/pffft.h",
"r8b/r8bbase.cpp",
"r8b/r8bbase.h",
"r8b/r8bconf.h",
"stb/stb_image.h",
"stb/stb_truetype.h",
"texture_compressor/dxt_encoder_implementation_autogen.h",
"texture_compressor/dxt_encoder_internals.cc",
"texture_compressor/dxt_encoder_internals.h",
"texture_compressor/dxt_encoder.cc",
"texture_compressor/dxt_encoder.h",
"texture_compressor/texture_compressor_etc1.cc",
"texture_compressor/texture_compressor_etc1.h",
"texture_compressor/texture_compressor.cc",
"texture_compressor/texture_compressor.h",
]
# ldflags = []
# libs = []
if (target_os == "linux") {
sources += [
"glew/glew.c",
"glew/glew.h",
"glew/glxew.h",
]
# ldflags += [ "-L/usr/X11R6/lib" ]
# libs += [ "X11", "rt", "pthread" ]
}
if (target_os == "android") {
sources += [
"android/gl3stub.c",
"android/gl3stub.h",
"android/GLContext.cpp",
"android/GLContext.h",
"minimp3/minimp3_ex.h",
"minimp3/minimp3.h",
"minizip/ioapi.c",
"minizip/ioapi.h",
"minizip/unzip.c",
"minizip/unzip.h",
"oboe/include/oboe/AudioStream.h",
"oboe/include/oboe/AudioStreamBase.h",
"oboe/include/oboe/AudioStreamBuilder.h",
"oboe/include/oboe/AudioStreamCallback.h",
"oboe/include/oboe/Definitions.h",
"oboe/include/oboe/LatencyTuner.h",
"oboe/include/oboe/Oboe.h",
"oboe/include/oboe/ResultWithValue.h",
"oboe/include/oboe/StabilizedCallback.h",
"oboe/include/oboe/Utilities.h",
"oboe/include/oboe/Version.h",
"oboe/src/aaudio/AAudioLoader.cpp",
"oboe/src/aaudio/AAudioLoader.h",
"oboe/src/aaudio/AudioStreamAAudio.cpp",
"oboe/src/aaudio/AudioStreamAAudio.h",
"oboe/src/common/AudioClock.h",
"oboe/src/common/AudioSourceCaller.cpp",
"oboe/src/common/AudioSourceCaller.h",
"oboe/src/common/AudioStream.cpp",
"oboe/src/common/AudioStreamBuilder.cpp",
"oboe/src/common/DataConversionFlowGraph.cpp",
"oboe/src/common/DataConversionFlowGraph.h",
"oboe/src/common/FilterAudioStream.cpp",
"oboe/src/common/FilterAudioStream.h",
"oboe/src/common/FixedBlockAdapter.cpp",
"oboe/src/common/FixedBlockAdapter.h",
"oboe/src/common/FixedBlockReader.cpp",
"oboe/src/common/FixedBlockReader.h",
"oboe/src/common/FixedBlockWriter.cpp",
"oboe/src/common/FixedBlockWriter.h",
"oboe/src/common/LatencyTuner.cpp",
"oboe/src/common/MonotonicCounter.h",
"oboe/src/common/OboeDebug.h",
"oboe/src/common/QuirksManager.cpp",
"oboe/src/common/QuirksManager.h",
"oboe/src/common/SourceFloatCaller.cpp",
"oboe/src/common/SourceFloatCaller.h",
"oboe/src/common/SourceI16Caller.cpp",
"oboe/src/common/SourceI16Caller.h",
"oboe/src/common/StabilizedCallback.cpp",
"oboe/src/common/Trace.cpp",
"oboe/src/common/Trace.h",
"oboe/src/common/Utilities.cpp",
"oboe/src/common/Version.cpp",
"oboe/src/fifo/FifoBuffer.cpp",
"oboe/src/fifo/FifoBuffer.h",
"oboe/src/fifo/FifoController.cpp",
"oboe/src/fifo/FifoController.h",
"oboe/src/fifo/FifoControllerBase.cpp",
"oboe/src/fifo/FifoControllerBase.h",
"oboe/src/fifo/FifoControllerIndirect.cpp",
"oboe/src/fifo/FifoControllerIndirect.h",
"oboe/src/flowgraph/ClipToRange.cpp",
"oboe/src/flowgraph/ClipToRange.h",
"oboe/src/flowgraph/FlowGraphNode.cpp",
"oboe/src/flowgraph/FlowGraphNode.h",
"oboe/src/flowgraph/ManyToMultiConverter.cpp",
"oboe/src/flowgraph/ManyToMultiConverter.h",
"oboe/src/flowgraph/MonoToMultiConverter.cpp",
"oboe/src/flowgraph/MonoToMultiConverter.h",
"oboe/src/flowgraph/RampLinear.cpp",
"oboe/src/flowgraph/RampLinear.h",
"oboe/src/flowgraph/resampler/HyperbolicCosineWindow.h",
"oboe/src/flowgraph/resampler/IntegerRatio.cpp",
"oboe/src/flowgraph/resampler/IntegerRatio.h",
"oboe/src/flowgraph/resampler/KaiserWindow.h",
"oboe/src/flowgraph/resampler/LinearResampler.cpp",
"oboe/src/flowgraph/resampler/LinearResampler.h",
"oboe/src/flowgraph/resampler/MultiChannelResampler.cpp",
"oboe/src/flowgraph/resampler/MultiChannelResampler.h",
"oboe/src/flowgraph/resampler/PolyphaseResampler.cpp",
"oboe/src/flowgraph/resampler/PolyphaseResampler.h",
"oboe/src/flowgraph/resampler/PolyphaseResamplerMono.cpp",
"oboe/src/flowgraph/resampler/PolyphaseResamplerMono.h",
"oboe/src/flowgraph/resampler/PolyphaseResamplerStereo.cpp",
"oboe/src/flowgraph/resampler/PolyphaseResamplerStereo.h",
"oboe/src/flowgraph/resampler/SincResampler.cpp",
"oboe/src/flowgraph/resampler/SincResampler.h",
"oboe/src/flowgraph/resampler/SincResamplerStereo.cpp",
"oboe/src/flowgraph/resampler/SincResamplerStereo.h",
"oboe/src/flowgraph/SampleRateConverter.cpp",
"oboe/src/flowgraph/SampleRateConverter.h",
"oboe/src/flowgraph/SinkFloat.cpp",
"oboe/src/flowgraph/SinkFloat.h",
"oboe/src/flowgraph/SinkI16.cpp",
"oboe/src/flowgraph/SinkI16.h",
"oboe/src/flowgraph/SinkI24.cpp",
"oboe/src/flowgraph/SinkI24.h",
"oboe/src/flowgraph/SourceFloat.cpp",
"oboe/src/flowgraph/SourceFloat.h",
"oboe/src/flowgraph/SourceI16.cpp",
"oboe/src/flowgraph/SourceI16.h",
"oboe/src/flowgraph/SourceI24.cpp",
"oboe/src/flowgraph/SourceI24.h",
"oboe/src/opensles/AudioInputStreamOpenSLES.cpp",
"oboe/src/opensles/AudioInputStreamOpenSLES.h",
"oboe/src/opensles/AudioOutputStreamOpenSLES.cpp",
"oboe/src/opensles/AudioOutputStreamOpenSLES.h",
"oboe/src/opensles/AudioStreamBuffered.cpp",
"oboe/src/opensles/AudioStreamBuffered.h",
"oboe/src/opensles/AudioStreamOpenSLES.cpp",
"oboe/src/opensles/AudioStreamOpenSLES.h",
"oboe/src/opensles/EngineOpenSLES.cpp",
"oboe/src/opensles/EngineOpenSLES.h",
"oboe/src/opensles/OpenSLESUtilities.cpp",
"oboe/src/opensles/OpenSLESUtilities.h",
"oboe/src/opensles/OutputMixerOpenSLES.cpp",
"oboe/src/opensles/OutputMixerOpenSLES.h",
"texture_compressor/dxt_encoder_neon.cc",
"texture_compressor/dxt_encoder_neon.h",
"texture_compressor/texture_compressor_etc1_neon.cc",
"texture_compressor/texture_compressor_etc1_neon.h",
]
ldflags += [ "-L/usr/X11R6/lib" ]
libs += [ "X11", "rt", "pthread" ]
}
deps = []
}

View File

@ -1,317 +0,0 @@
/*
* Copyright 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "gestureDetector.h"
#include <android/log.h>
#define LOGI(...) \
((void)__android_log_print( \
ANDROID_LOG_INFO, "gltest", \
__VA_ARGS__))
#define LOGW(...) \
((void)__android_log_print( \
ANDROID_LOG_WARN, "gltest", \
__VA_ARGS__))
#define LOGE(...) \
((void)__android_log_print( \
ANDROID_LOG_ERROR, "gltest", \
__VA_ARGS__))
using base::Vector2;
//--------------------------------------------------------------------------------
// gestureDetector.cpp
//--------------------------------------------------------------------------------
namespace ndk_helper {
//--------------------------------------------------------------------------------
// includes
//--------------------------------------------------------------------------------
//--------------------------------------------------------------------------------
// GestureDetector
//--------------------------------------------------------------------------------
GestureDetector::GestureDetector() { dp_factor_ = 1.f; }
void GestureDetector::SetConfiguration(AConfiguration* config) {
dp_factor_ = 160.f / AConfiguration_getDensity(config);
}
//--------------------------------------------------------------------------------
// TapDetector
//--------------------------------------------------------------------------------
TapDetector::TapDetector() : down_x_(0), down_y_(0) {}
GESTURE_STATE TapDetector::Detect(const AInputEvent* motion_event) {
if (AMotionEvent_getPointerCount(motion_event) > 1) {
// Only support single touch
return false;
}
int32_t action = AMotionEvent_getAction(motion_event);
unsigned int flags = action & AMOTION_EVENT_ACTION_MASK;
switch (flags) {
case AMOTION_EVENT_ACTION_DOWN:
down_pointer_id_ = AMotionEvent_getPointerId(motion_event, 0);
down_x_ = AMotionEvent_getX(motion_event, 0);
down_y_ = AMotionEvent_getY(motion_event, 0);
break;
case AMOTION_EVENT_ACTION_UP: {
int64_t eventTime = AMotionEvent_getEventTime(motion_event);
int64_t downTime = AMotionEvent_getDownTime(motion_event);
if (eventTime - downTime <= TAP_TIMEOUT) {
if (down_pointer_id_ == AMotionEvent_getPointerId(motion_event, 0)) {
float x = AMotionEvent_getX(motion_event, 0) - down_x_;
float y = AMotionEvent_getY(motion_event, 0) - down_y_;
if (x * x + y * y < TOUCH_SLOP * TOUCH_SLOP * dp_factor_) {
LOGI("TapDetector: Tap detected");
return GESTURE_STATE_ACTION;
}
}
}
break;
}
}
return GESTURE_STATE_NONE;
}
//--------------------------------------------------------------------------------
// DoubletapDetector
//--------------------------------------------------------------------------------
DoubletapDetector::DoubletapDetector()
: last_tap_time_(0), last_tap_x_(0), last_tap_y_(0) {}
GESTURE_STATE DoubletapDetector::Detect(const AInputEvent* motion_event) {
if (AMotionEvent_getPointerCount(motion_event) > 1) {
// Only support single double tap
return false;
}
bool tap_detected = tap_detector_.Detect(motion_event);
int32_t action = AMotionEvent_getAction(motion_event);
unsigned int flags = action & AMOTION_EVENT_ACTION_MASK;
switch (flags) {
case AMOTION_EVENT_ACTION_DOWN: {
int64_t eventTime = AMotionEvent_getEventTime(motion_event);
if (eventTime - last_tap_time_ <= DOUBLE_TAP_TIMEOUT) {
float x = AMotionEvent_getX(motion_event, 0) - last_tap_x_;
float y = AMotionEvent_getY(motion_event, 0) - last_tap_y_;
if (x * x + y * y < DOUBLE_TAP_SLOP * DOUBLE_TAP_SLOP * dp_factor_) {
LOGI("DoubletapDetector: Doubletap detected");
return GESTURE_STATE_ACTION;
}
}
break;
}
case AMOTION_EVENT_ACTION_UP:
if (tap_detected) {
last_tap_time_ = AMotionEvent_getEventTime(motion_event);
last_tap_x_ = AMotionEvent_getX(motion_event, 0);
last_tap_y_ = AMotionEvent_getY(motion_event, 0);
}
break;
}
return GESTURE_STATE_NONE;
}
void DoubletapDetector::SetConfiguration(AConfiguration* config) {
dp_factor_ = 160.f / AConfiguration_getDensity(config);
tap_detector_.SetConfiguration(config);
}
//--------------------------------------------------------------------------------
// PinchDetector
//--------------------------------------------------------------------------------
int32_t PinchDetector::FindIndex(const AInputEvent* event, int32_t id) {
int32_t count = AMotionEvent_getPointerCount(event);
for (auto i = 0; i < count; ++i) {
if (id == AMotionEvent_getPointerId(event, i)) return i;
}
return -1;
}
GESTURE_STATE PinchDetector::Detect(const AInputEvent* event) {
GESTURE_STATE ret = GESTURE_STATE_NONE;
int32_t action = AMotionEvent_getAction(event);
uint32_t flags = action & AMOTION_EVENT_ACTION_MASK;
event_ = event;
int32_t count = AMotionEvent_getPointerCount(event);
switch (flags) {
case AMOTION_EVENT_ACTION_DOWN:
vec_pointers_.push_back(AMotionEvent_getPointerId(event, 0));
break;
case AMOTION_EVENT_ACTION_POINTER_DOWN: {
int32_t iIndex = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >>
AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
vec_pointers_.push_back(AMotionEvent_getPointerId(event, iIndex));
if (count == 2) {
// Start new pinch
ret = GESTURE_STATE_START;
}
} break;
case AMOTION_EVENT_ACTION_UP:
vec_pointers_.pop_back();
break;
case AMOTION_EVENT_ACTION_POINTER_UP: {
int32_t index = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >>
AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
int32_t released_pointer_id = AMotionEvent_getPointerId(event, index);
std::vector<int32_t>::iterator it = vec_pointers_.begin();
std::vector<int32_t>::iterator it_end = vec_pointers_.end();
int32_t i = 0;
for (; it != it_end; ++it, ++i) {
if (*it == released_pointer_id) {
vec_pointers_.erase(it);
break;
}
}
if (i <= 1) {
// Reset pinch or drag
if (count != 2) {
// Start new pinch
ret = GESTURE_STATE_START | GESTURE_STATE_END;
}
}
} break;
case AMOTION_EVENT_ACTION_MOVE:
switch (count) {
case 1:
break;
default:
// Multi touch
ret = GESTURE_STATE_MOVE;
break;
}
break;
case AMOTION_EVENT_ACTION_CANCEL:
break;
}
return ret;
}
bool PinchDetector::GetPointers(Vector2& v1, Vector2& v2) {
if (vec_pointers_.size() < 2) return false;
int32_t index = FindIndex(event_, vec_pointers_[0]);
if (index == -1) return false;
float x = AMotionEvent_getX(event_, index);
float y = AMotionEvent_getY(event_, index);
index = FindIndex(event_, vec_pointers_[1]);
if (index == -1) return false;
float x2 = AMotionEvent_getX(event_, index);
float y2 = AMotionEvent_getY(event_, index);
v1 = Vector2(x, y);
v2 = Vector2(x2, y2);
return true;
}
//--------------------------------------------------------------------------------
// DragDetector
//--------------------------------------------------------------------------------
int32_t DragDetector::FindIndex(const AInputEvent* event, int32_t id) {
int32_t count = AMotionEvent_getPointerCount(event);
for (auto i = 0; i < count; ++i) {
if (id == AMotionEvent_getPointerId(event, i)) return i;
}
return -1;
}
GESTURE_STATE DragDetector::Detect(const AInputEvent* event) {
GESTURE_STATE ret = GESTURE_STATE_NONE;
int32_t action = AMotionEvent_getAction(event);
int32_t index = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >>
AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
uint32_t flags = action & AMOTION_EVENT_ACTION_MASK;
event_ = event;
int32_t count = AMotionEvent_getPointerCount(event);
switch (flags) {
case AMOTION_EVENT_ACTION_DOWN:
vec_pointers_.push_back(AMotionEvent_getPointerId(event, 0));
ret = GESTURE_STATE_START;
break;
case AMOTION_EVENT_ACTION_POINTER_DOWN:
vec_pointers_.push_back(AMotionEvent_getPointerId(event, index));
break;
case AMOTION_EVENT_ACTION_UP:
vec_pointers_.pop_back();
ret = GESTURE_STATE_END;
break;
case AMOTION_EVENT_ACTION_POINTER_UP: {
int32_t released_pointer_id = AMotionEvent_getPointerId(event, index);
auto it = vec_pointers_.begin();
auto it_end = vec_pointers_.end();
int32_t i = 0;
for (; it != it_end; ++it, ++i) {
if (*it == released_pointer_id) {
vec_pointers_.erase(it);
break;
}
}
if (i <= 1) {
// Reset pinch or drag
if (count == 2) {
ret = GESTURE_STATE_START;
}
}
break;
}
case AMOTION_EVENT_ACTION_MOVE:
switch (count) {
case 1:
// Drag
ret = GESTURE_STATE_MOVE;
break;
default:
break;
}
break;
case AMOTION_EVENT_ACTION_CANCEL:
break;
}
return ret;
}
bool DragDetector::GetPointer(Vector2& v) {
if (vec_pointers_.size() < 1) return false;
int32_t iIndex = FindIndex(event_, vec_pointers_[0]);
if (iIndex == -1) return false;
float x = AMotionEvent_getX(event_, iIndex);
float y = AMotionEvent_getY(event_, iIndex);
v = Vector2(x, y);
return true;
}
} // namespace ndkHelper

View File

@ -1,145 +0,0 @@
/*
* Copyright 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//--------------------------------------------------------------------------------
// gestureDetector.h
//--------------------------------------------------------------------------------
#ifndef GESTUREDETECTOR_H_
#define GESTUREDETECTOR_H_
#include <vector>
#include <android/sensor.h>
#include <android/log.h>
#include <android_native_app_glue.h>
#include <android/native_window_jni.h>
#include "../../base/vecmath.h"
namespace ndk_helper {
//--------------------------------------------------------------------------------
// Constants
//--------------------------------------------------------------------------------
const int32_t DOUBLE_TAP_TIMEOUT = 300 * 1000000;
const int32_t TAP_TIMEOUT = 180 * 1000000;
const int32_t DOUBLE_TAP_SLOP = 100;
const int32_t TOUCH_SLOP = 8;
enum {
GESTURE_STATE_NONE = 0,
GESTURE_STATE_START = 1,
GESTURE_STATE_MOVE = 2,
GESTURE_STATE_END = 4,
GESTURE_STATE_ACTION = (GESTURE_STATE_START | GESTURE_STATE_END),
};
typedef int32_t GESTURE_STATE;
/******************************************************************
* Base class of Gesture Detectors
* GestureDetectors handles input events and detect gestures
* Note that different detectors may detect gestures with an event at
* same time. The caller needs to manage gesture priority accordingly
*
*/
class GestureDetector {
protected:
float dp_factor_;
public:
GestureDetector();
virtual ~GestureDetector() {}
virtual void SetConfiguration(AConfiguration* config);
virtual GESTURE_STATE Detect(const AInputEvent* motion_event) = 0;
};
/******************************************************************
* Tap gesture detector
* Returns GESTURE_STATE_ACTION when a tap gesture is detected
*
*/
class TapDetector : public GestureDetector {
private:
int32_t down_pointer_id_;
float down_x_;
float down_y_;
public:
TapDetector();
virtual ~TapDetector() {}
virtual GESTURE_STATE Detect(const AInputEvent* motion_event);
void GetPointer(base::Vector2& v) { v.x = down_x_; v.y = down_y_; }
};
/******************************************************************
* Pinch gesture detector
* Returns GESTURE_STATE_ACTION when a double-tap gesture is detected
*
*/
class DoubletapDetector : public GestureDetector {
private:
TapDetector tap_detector_;
int64_t last_tap_time_;
float last_tap_x_;
float last_tap_y_;
public:
DoubletapDetector();
virtual ~DoubletapDetector() {}
virtual GESTURE_STATE Detect(const AInputEvent* motion_event);
virtual void SetConfiguration(AConfiguration* config);
void GetPointer(base::Vector2& v) { v.x = last_tap_x_; v.y = last_tap_y_; }
};
/******************************************************************
* Double gesture detector
* Returns pinch gesture state when a pinch gesture is detected
* The class handles multiple touches more than 2
* When the finger 1,2,3 are tapped and then finger 1 is released,
* the detector start new pinch gesture with finger 2 & 3.
*/
class PinchDetector : public GestureDetector {
private:
int32_t FindIndex(const AInputEvent* event, int32_t id);
const AInputEvent* event_;
std::vector<int32_t> vec_pointers_;
public:
PinchDetector() {}
virtual ~PinchDetector() {}
virtual GESTURE_STATE Detect(const AInputEvent* event);
bool GetPointers(base::Vector2& v1, base::Vector2& v2);
};
/******************************************************************
* Drag gesture detector
* Returns drag gesture state when a drag-tap gesture is detected
*
*/
class DragDetector : public GestureDetector {
private:
int32_t FindIndex(const AInputEvent* event, int32_t id);
const AInputEvent* event_;
std::vector<int32_t> vec_pointers_;
public:
DragDetector() : event_(nullptr) {}
virtual ~DragDetector() {}
virtual GESTURE_STATE Detect(const AInputEvent* event);
bool GetPointer(base::Vector2& v);
};
} // namespace ndkHelper
#endif /* GESTUREDETECTOR_H_ */

View File

@ -0,0 +1,6 @@
Real-time texture compression code salvaged from chromium project repository.
Implements ATC, DXT and ETC1 compression, optimized for NEON.
It was used in chromium project for compositor tiles to reduce the GPU memory
usage for low to mid-end mobile devices. It got obsolete with the GPU rasterizer
introduced in chromium 37 and removed.