- 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_debug
build/linux/gltest_x86_64_release build/linux/gltest_x86_64_release
build/linux/obj 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 A simple, cross-platform 2D game engine with OpenGL renderer. Supports Linux and
C++. Supports Linux and Android platforms. Android (lolipop+) platforms.
#### Building the demo
Build for Linux (gcc or clang): Linux:
```text
cd build/linux cd build/linux
make make
```
Build for Android: Android:
```text
cd build/android cd build/android
./gradlew :app:assembleRelease ./gradlew :app:assembleRelease
```
Build for Android and install (debug): GN (linux only for now):
```text
cd build/android gn gen --args='is_debug=false' out/release
./gradlew :app:installDebug 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) set (OBOE_DIR ../../../src/third_party/oboe)
add_subdirectory(${OBOE_DIR} ./oboe-bin) add_subdirectory(${OBOE_DIR} ./oboe-bin)
# cpufeatures
include(AndroidNdkModules) include_directories(${ANDROID_NDK}/sources/android/cpufeatures)
android_ndk_import_module_cpufeatures() add_library(cpufeatures STATIC
${ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c)
# build native_app_glue as a static lib # build native_app_glue as a static lib
if (CMAKE_BUILD_TYPE MATCHES Debug) 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_base.cc
../../../src/engine/audio/audio_oboe.cc ../../../src/engine/audio/audio_oboe.cc
../../../src/engine/audio/audio_resource.cc ../../../src/engine/audio/audio_resource.cc
../../../src/engine/drawable.cc
../../../src/engine/engine.cc ../../../src/engine/engine.cc
../../../src/engine/font.cc ../../../src/engine/font.cc
../../../src/engine/image_quad.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_android.cc
../../../src/engine/platform/asset_file.cc ../../../src/engine/platform/asset_file.cc
../../../src/engine/platform/platform_android.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/geometry.cc
../../../src/engine/renderer/render_command.cc ../../../src/engine/renderer/render_command.cc
../../../src/engine/renderer/render_resource.cc ../../../src/engine/renderer/render_resource.cc
@ -87,7 +89,6 @@ add_library(native-activity SHARED
../../../src/engine/solid_quad.cc ../../../src/engine/solid_quad.cc
../../../src/engine/sound_player.cc ../../../src/engine/sound_player.cc
../../../src/engine/sound.cc ../../../src/engine/sound.cc
../../../src/third_party/android/gestureDetector.cpp
../../../src/third_party/android/gl3stub.c ../../../src/third_party/android/gl3stub.c
../../../src/third_party/android/GLContext.cpp ../../../src/third_party/android/GLContext.cpp
../../../src/third_party/jsoncpp/jsoncpp.cc ../../../src/third_party/jsoncpp/jsoncpp.cc

View File

@ -5,6 +5,8 @@
android:versionCode="1" android:versionCode="1"
android:versionName="1.0"> android:versionName="1.0">
<uses-permission android:name="android.permission.VIBRATE"/>
<!-- This .apk has no Java code itself, so set hasCode to false. --> <!-- This .apk has no Java code itself, so set hasCode to false. -->
<application <application
android:allowBackup="false" 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) BUILD_DIR := $(INTERMEDIATE_DIR)/$(BUILD)
ARFLAGS = r ARFLAGS = r
LDFLAGS = -lX11 -lGL -lz -pthread -lasound LDFLAGS = -lX11 -lGL -pthread -lasound
# Always enable debug information. # Always enable debug information.
CFLAGS += -g CFLAGS += -g
@ -36,6 +36,7 @@ CFLAGS += -MD -MP -MT $@
# Predefined flags. # Predefined flags.
ifeq ($(BUILD), debug) ifeq ($(BUILD), debug)
CFLAGS += -D_DEBUG CFLAGS += -D_DEBUG
CFLAGS += -D_GLIBCXX_DEBUG
endif endif
# Enable compiler optimizations for everything except debug. # 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_alsa.cc \
$(SRC_ROOT)/engine/audio/audio_base.cc \ $(SRC_ROOT)/engine/audio/audio_base.cc \
$(SRC_ROOT)/engine/audio/audio_resource.cc \ $(SRC_ROOT)/engine/audio/audio_resource.cc \
$(SRC_ROOT)/engine/drawable.cc \
$(SRC_ROOT)/engine/engine.cc \ $(SRC_ROOT)/engine/engine.cc \
$(SRC_ROOT)/engine/font.cc \ $(SRC_ROOT)/engine/font.cc \
$(SRC_ROOT)/engine/image_quad.cc \ $(SRC_ROOT)/engine/image_quad.cc \
@ -90,8 +92,8 @@ GLTEST_SRC := \
$(SRC_ROOT)/engine/mesh.cc \ $(SRC_ROOT)/engine/mesh.cc \
$(SRC_ROOT)/engine/platform/asset_file_linux.cc \ $(SRC_ROOT)/engine/platform/asset_file_linux.cc \
$(SRC_ROOT)/engine/platform/asset_file.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_linux.cc \
$(SRC_ROOT)/engine/platform/platform.cc \
$(SRC_ROOT)/engine/renderer/geometry.cc \ $(SRC_ROOT)/engine/renderer/geometry.cc \
$(SRC_ROOT)/engine/renderer/render_command.cc \ $(SRC_ROOT)/engine/renderer/render_command.cc \
$(SRC_ROOT)/engine/renderer/render_resource.cc \ $(SRC_ROOT)/engine/renderer/render_resource.cc \
@ -106,8 +108,6 @@ GLTEST_SRC := \
$(SRC_ROOT)/engine/sound.cc \ $(SRC_ROOT)/engine/sound.cc \
$(SRC_ROOT)/third_party/glew/glew.c \ $(SRC_ROOT)/third_party/glew/glew.c \
$(SRC_ROOT)/third_party/jsoncpp/jsoncpp.cc \ $(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/pffft.cpp \
$(SRC_ROOT)/third_party/r8b/r8bbase.cpp \ $(SRC_ROOT)/third_party/r8b/r8bbase.cpp \
$(SRC_ROOT)/third_party/texture_compressor/dxt_encoder_internals.cc \ $(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 #define CLOSURE_H
#include <functional> #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 { namespace base {
using Closure = std::function<void()>; 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 } // namespace base
#endif // CLOSURE_H #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 <cstdio>
#include <memory> #include <memory>
namespace base {
namespace internal { namespace internal {
struct ScopedFILECloser { struct ScopedFILECloser {
@ -15,8 +17,6 @@ struct ScopedFILECloser {
} // namespace internal } // namespace internal
namespace base {
// Automatically closes file. // Automatically closes file.
using ScopedFILE = std::unique_ptr<FILE, internal::ScopedFILECloser>; using ScopedFILE = std::unique_ptr<FILE, internal::ScopedFILECloser>;

View File

@ -5,17 +5,25 @@
#else #else
#include <cstdio> #include <cstdio>
#endif #endif
#include <cstdlib>
#include <mutex>
#include <unordered_map>
#include "vecmath.h"
namespace base { namespace base {
// Adapted from Chromium's logging implementation.
// This is never instantiated, it's just used for EAT_STREAM_PARAMETERS to have // 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 // an object of the correct type on the LHS of the unused part of the ternary
// operator. // 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; stream_ << std::endl;
std::string text(stream_.str()); std::string text(stream_.str());
std::string filename(file_); std::string filename(file_);
@ -30,23 +38,78 @@ Log::~Log() {
#endif #endif
} }
template <> Log::Log(const char* file, int line) : LogBase(file, line) {}
Log& Log::operator<<<bool>(const bool& arg) {
stream_ << (arg ? "true" : "false"); Log::~Log() {
return *this; 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 <> template <>
Log& Log::operator<<<Vector2>(const Vector2& arg) { LogBase& operator<<(LogBase& out, const base::Vector2& arg) {
stream_ << "(" << arg.x << ", " << arg.y << ")"; out.stream() << "(" << arg.x << ", " << arg.y << ")";
return *this; return out;
} }
template <> template <>
Log& Log::operator<<<Vector4>(const Vector4& arg) { LogBase& operator<<(LogBase& out, const base::Vector3& arg) {
stream_ << "(" << arg.x << ", " << arg.y << ", " << arg.z << ", " << arg.w out.stream() << "(" << arg.x << ", " << arg.y << ", " << arg.z << ")";
<< ")"; return out;
return *this; }
template <>
LogBase& operator<<(LogBase& out, const base::Vector4& arg) {
out.stream() << "(" << arg.x << ", " << arg.y << ", " << arg.z << ", "
<< arg.w << ")";
return out;
} }
} // namespace base } // namespace base

View File

@ -2,48 +2,115 @@
#define LOG_H #define LOG_H
#include <sstream> #include <sstream>
#include "vecmath.h"
#define EAT_STREAM_PARAMETERS \ // Macros for logging that are active in both debug and release builds. The way
true ? (void)0 : base::Log::Voidify() & (*base::Log::swallow_stream) // to log things is to stream things to LOG.
// LOG_DIFF can be used to avoid spam and log only if the message differs.
#define LOG base::Log(__FILE__, __LINE__) // 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 #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 #else
// "debug mode" logging is compiled away to nothing for release builds.
#define DLOG EAT_STREAM_PARAMETERS #define DLOG EAT_STREAM_PARAMETERS
#define DLOG_DIFF EAT_STREAM_PARAMETERS
#define DCHECK(expr) EAT_STREAM_PARAMETERS
#endif #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 { namespace base {
class Log { struct Vector2;
struct Vector3;
struct Vector4;
class LogBase {
public: public:
class Voidify { class Voidify {
public: public:
Voidify() = default; Voidify() = default;
// This has to be an operator with a precedence lower than << but // This has to be an operator with a precedence lower than << but
// higher than ?: // higher than ?:
void operator&(Log&) {} void operator&(LogBase&) {}
}; };
Log(const char* file, int line); LogBase& base() { return *this; }
~Log();
template <typename T> std::ostream& stream() { return stream_; }
Log& operator<<(const T& arg) {
stream_ << arg;
return *this;
}
static Log* swallow_stream; static LogBase* swallow_stream;
private: protected:
const char* file_; const char* file_;
const int line_; const int line_;
std::ostringstream stream_; 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 } // namespace base
#endif // LOG_H #endif // LOG_H

View File

@ -1,7 +1,6 @@
#ifndef MEM_H #ifndef MEM_H
#define MEM_H #define MEM_H
#include <cassert>
#include <cstdlib> #include <cstdlib>
#include <memory> #include <memory>
@ -9,8 +8,12 @@
#include <malloc.h> #include <malloc.h>
#endif #endif
#include "log.h"
#define ALIGN_MEM(alignment) __attribute__((aligned(alignment))) #define ALIGN_MEM(alignment) __attribute__((aligned(alignment)))
namespace base {
namespace internal { namespace internal {
struct ScopedAlignedFree { struct ScopedAlignedFree {
@ -22,12 +25,8 @@ struct ScopedAlignedFree {
} // namespace internal } // namespace internal
namespace base {
template <typename T> template <typename T>
struct AlignedMem { using AlignedMemPtr = std::unique_ptr<T, internal::ScopedAlignedFree>;
using ScoppedPtr = std::unique_ptr<T, internal::ScopedAlignedFree>;
};
template <int kAlignment> template <int kAlignment>
inline void* AlignedAlloc(size_t size) { inline void* AlignedAlloc(size_t size) {
@ -38,8 +37,8 @@ inline void* AlignedAlloc(size_t size) {
if (posix_memalign(&ptr, kAlignment, size)) if (posix_memalign(&ptr, kAlignment, size))
ptr = NULL; ptr = NULL;
#endif #endif
assert(ptr); DCHECK(ptr);
// assert(((unsigned)ptr & (kAlignment - 1)) == 0); // DCHECK(((unsigned)ptr & (kAlignment - 1)) == 0);
return ptr; 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 "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 { namespace base {
void TaskRunner::Enqueue(base::Closure task) { thread_local std::unique_ptr<TaskRunner> TaskRunner::thread_local_task_runner;
std::unique_lock<std::mutex> scoped_lock(mutex_);
thread_tasks_.emplace_back(std::move(task)); 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 (;;) { for (;;) {
base::Closure task; Task task;
{ {
std::unique_lock<std::mutex> scoped_lock(mutex_); std::lock_guard<std::mutex> scoped_lock(lock_);
if (!thread_tasks_.empty()) { if (queue_.empty())
task.swap(thread_tasks_.front()); return;
thread_tasks_.pop_front(); task.swap(queue_.front());
} queue_.pop_front();
} }
if (!task)
break; auto [from, task_cb] = task;
task();
#if 0
LOG << __func__ << " from: " << LOCATION(from);
#endif
task_cb();
} }
} }
bool TaskRunner::IsBoundToCurrentThread() { void TaskRunner::SingleConsumerRun() {
return thread_id_ == std::this_thread::get_id(); 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 } // namespace base

View File

@ -2,26 +2,76 @@
#define TASK_RUNNER_H #define TASK_RUNNER_H
#include <deque> #include <deque>
#include <memory>
#include <mutex> #include <mutex>
#include <thread> #include <tuple>
#include "closure.h" #include "closure.h"
namespace base { 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 { class TaskRunner {
public: public:
TaskRunner() = default; TaskRunner() = default;
~TaskRunner() = default; ~TaskRunner() = default;
void Enqueue(base::Closure cb); void EnqueueTask(const Location& from, Closure task);
void Run();
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: private:
std::thread::id thread_id_ = std::this_thread::get_id(); using Task = std::tuple<Location, Closure>;
std::mutex mutex_;
std::deque<base::Closure> thread_tasks_; std::deque<Task> queue_;
mutable std::mutex lock_;
static thread_local std::unique_ptr<TaskRunner> thread_local_task_runner;
TaskRunner(TaskRunner const&) = delete; TaskRunner(TaskRunner const&) = delete;
TaskRunner& operator=(TaskRunner const&) = delete; TaskRunner& operator=(TaskRunner const&) = delete;

View File

@ -1,67 +1,71 @@
#include "worker.h" #include "worker.h"
#include "log.h" #include "log.h"
namespace base { namespace base {
Worker::Worker(unsigned max_concurrency) : max_concurrency_(max_concurrency) { Worker* Worker::singleton = nullptr;
if (max_concurrency_ > std::thread::hardware_concurrency() ||
max_concurrency_ == 0) { Worker::Worker() {
max_concurrency_ = std::thread::hardware_concurrency(); DCHECK(!singleton);
if (max_concurrency_ == 0) singleton = this;
max_concurrency_ = 1;
}
} }
Worker::~Worker() = default; Worker::~Worker() {
Shutdown();
void Worker::Enqueue(base::Closure task) { singleton = nullptr;
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();
} }
void Worker::Join() { void Worker::Initialize(unsigned max_concurrency) {
if (!active_) 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; return;
{ quit_.store(true, std::memory_order_relaxed);
std::unique_lock<std::mutex> scoped_lock(mutex_); semaphore_.Release();
quit_when_idle_ = true;
}
cv_.notify_all();
for (auto& thread : threads_) for (auto& thread : threads_)
thread.join(); thread.join();
threads_.clear(); 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() { void Worker::WorkerMain() {
for (;;) { for (;;) {
base::Closure task; semaphore_.Acquire();
{
std::unique_lock<std::mutex> scoped_lock(mutex_); if (quit_.load(std::memory_order_relaxed)) {
while (tasks_.empty()) { semaphore_.Release();
if (quit_when_idle_) return;
return;
cv_.wait(scoped_lock);
}
task.swap(tasks_.front());
tasks_.pop_front();
} }
task(); task_runner_.MultiConsumerRun();
} }
} }

View File

@ -1,33 +1,53 @@
#ifndef WORKER_H #ifndef THREAD_POOL_H
#define WORKER_H #define THREAD_POOL_H
#include <condition_variable> #include <atomic>
#include <deque>
#include <mutex>
#include <thread> #include <thread>
#include <vector> #include <vector>
#include "closure.h" #include "closure.h"
#include "semaphore.h"
#include "task_runner.h"
namespace base { 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 { class Worker {
public: public:
Worker(unsigned max_concurrency = 0); Worker();
~Worker(); ~Worker();
void Enqueue(base::Closure task); static Worker& Get() { return *singleton; }
void Join();
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: private:
bool active_ = false;
unsigned max_concurrency_ = 0;
std::condition_variable cv_;
std::mutex mutex_;
std::vector<std::thread> threads_; 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(); void WorkerMain();
@ -37,4 +57,4 @@ class Worker {
} // namespace base } // 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/log.h"
#include "../base/vecmath.h" #include "../base/vecmath.h"
#include "../base/worker.h"
#include "../engine/engine.h" #include "../engine/engine.h"
#include "../engine/font.h" #include "../engine/font.h"
#include "../engine/image.h" #include "../engine/image.h"
#include "../engine/input_event.h" #include "../engine/input_event.h"
#include "../engine/renderer/texture.h"
#include "demo.h" #include "demo.h"
using namespace base; using namespace base;
@ -16,11 +14,11 @@ using namespace eng;
namespace { namespace {
constexpr char kCreditsLines[Credits::kNumLines][15] = { 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}; 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; constexpr float kFadeSpeed = 0.2f;
} // namespace } // namespace
@ -30,16 +28,19 @@ Credits::Credits() = default;
Credits::~Credits() = default; Credits::~Credits() = default;
bool Credits::Initialize() { bool Credits::Initialize() {
const Font& font = static_cast<Demo*>(Engine::Get().GetGame())->GetFont(); const Font* font = Engine::Get().GetSystemFont();
max_text_width_ = -1; max_text_width_ = -1;
for (int i = 0; i < kNumLines; ++i) { for (int i = 0; i < kNumLines; ++i) {
int width, height; int width, height;
font.CalculateBoundingBox(kCreditsLines[i], width, height); font->CalculateBoundingBox(kCreditsLines[i], width, height);
if (width > max_text_width_) if (width > max_text_width_)
max_text_width_ = width; max_text_width_ = width;
} }
Engine::Get().SetImageSource("credits",
std::bind(&Credits::CreateImage, this));
for (int i = 0; i < kNumLines; ++i) for (int i = 0; i < kNumLines; ++i)
text_animator_.Attach(&text_[i]); text_animator_.Attach(&text_[i]);
@ -51,8 +52,7 @@ void Credits::Update(float delta_time) {
} }
void Credits::OnInputEvent(std::unique_ptr<InputEvent> event) { void Credits::OnInputEvent(std::unique_ptr<InputEvent> event) {
if ((event->GetType() == InputEvent::kTap || if ((event->GetType() == InputEvent::kDragEnd ||
event->GetType() == InputEvent::kDragEnd ||
event->GetType() == InputEvent::kNavigateBack) && event->GetType() == InputEvent::kNavigateBack) &&
!text_animator_.IsPlaying(Animator::kBlending)) { !text_animator_.IsPlaying(Animator::kBlending)) {
Hide(); 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() { void Credits::Show() {
tex_ = Engine::Get().CreateRenderResource<Texture>(); Engine::Get().RefreshImage("credits");
tex_->Update(CreateImage());
for (int i = 0; i < kNumLines; ++i) { 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].SetOffset({0, 0});
text_[i].SetScale({1, 1});
text_[i].AutoScale();
text_[i].SetColor(kTextColor * Vector4(1, 1, 1, 0)); text_[i].SetColor(kTextColor * Vector4(1, 1, 1, 0));
text_[i].SetFrame(i); text_[i].SetFrame(i);
@ -107,7 +95,6 @@ void Credits::Hide() {
text_animator_.SetEndCallback(Animator::kBlending, [&]() -> void { text_animator_.SetEndCallback(Animator::kBlending, [&]() -> void {
for (int i = 0; i < kNumLines; ++i) for (int i = 0; i < kNumLines; ++i)
text_[i].Destory(); text_[i].Destory();
tex_.reset();
text_animator_.SetEndCallback(Animator::kBlending, nullptr); text_animator_.SetEndCallback(Animator::kBlending, nullptr);
text_animator_.SetVisible(false); text_animator_.SetVisible(false);
}); });
@ -116,23 +103,21 @@ void Credits::Hide() {
} }
std::unique_ptr<Image> Credits::CreateImage() { 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>(); auto image = std::make_unique<Image>();
image->Create(max_text_width_, line_height * kNumLines); image->Create(max_text_width_, line_height * kNumLines);
image->Clear({1, 1, 1, 0}); image->Clear({1, 1, 1, 0});
Worker worker(kNumLines);
for (int i = 0; i < kNumLines; ++i) { for (int i = 0; i < kNumLines; ++i) {
int w, h; int w, h;
font.CalculateBoundingBox(kCreditsLines[i], w, h); font->CalculateBoundingBox(kCreditsLines[i], w, h);
float x = (image->GetWidth() - w) / 2; float x = (image->GetWidth() - w) / 2;
float y = line_height * i; float y = line_height * i;
worker.Enqueue(std::bind(&Font::Print, &font, x, y, kCreditsLines[i], font->Print(x, y, kCreditsLines[i], image->GetBuffer(), image->GetWidth());
image->GetBuffer(), image->GetWidth()));
} }
worker.Join();
image->Compress();
return image; return image;
} }

View File

@ -10,7 +10,6 @@
namespace eng { namespace eng {
class Image; class Image;
class InputEvent; class InputEvent;
class Texture;
} // namespace eng } // namespace eng
class Credits { class Credits {
@ -26,16 +25,10 @@ class Credits {
void OnInputEvent(std::unique_ptr<eng::InputEvent> event); void OnInputEvent(std::unique_ptr<eng::InputEvent> event);
void Draw();
void ContextLost();
void Show(); void Show();
void Hide(); void Hide();
private: private:
std::shared_ptr<eng::Texture> tex_;
eng::ImageQuad text_[kNumLines]; eng::ImageQuad text_[kNumLines];
eng::Animator text_animator_; eng::Animator text_animator_;

View File

@ -80,7 +80,7 @@ void Demo::Update(float delta_time) {
if (add_score_ > 0) { if (add_score_ > 0) {
score_ += add_score_; score_ += add_score_;
add_score_ = 0; add_score_ = 0;
hud_.PrintScore(score_, true); hud_.SetScore(score_, true);
} }
hud_.Update(delta_time); hud_.Update(delta_time);
@ -93,21 +93,7 @@ void Demo::Update(float delta_time) {
UpdateGameState(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() { void Demo::ContextLost() {
enemy_.ContextLost();
player_.ContextLost();
hud_.ContextLost();
menu_.ContextLost();
credits_.ContextLost();
sky_.ContextLost(); sky_.ContextLost();
} }
@ -169,7 +155,7 @@ void Demo::UpdateMenuState(float delta_time) {
Engine::Get().Exit(); Engine::Get().Exit();
break; break;
default: default:
assert(false); NOTREACHED << "- Unknown menu option: " << menu_.selected_option();
} }
} }
@ -209,8 +195,8 @@ void Demo::UpdateGameState(float delta_time) {
sky_.SwitchColor(c); sky_.SwitchColor(c);
++wave_; ++wave_;
hud_.PrintScore(score_, true); hud_.SetScore(score_, true);
hud_.PrintWave(wave_, true); hud_.SetWave(wave_, true);
hud_.SetProgress(1); hud_.SetProgress(1);
float factor = 3 * (log10(5 * (float)wave_) / log10(1.2f)) - 25; 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) { 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_cb_ = std::move(cb);
delayed_work_timer_ = seconds; delayed_work_timer_ = seconds;
} }

View File

@ -20,8 +20,6 @@ class Demo : public eng::Game {
void Update(float delta_time) override; void Update(float delta_time) override;
void Draw(float frame_frac) override;
void ContextLost() override; void ContextLost() override;
void LostFocus() override; void LostFocus() override;

View File

@ -1,6 +1,5 @@
#include "enemy.h" #include "enemy.h"
#include <cassert>
#include <functional> #include <functional>
#include <limits> #include <limits>
@ -10,10 +9,11 @@
#include "../engine/engine.h" #include "../engine/engine.h"
#include "../engine/font.h" #include "../engine/font.h"
#include "../engine/image.h" #include "../engine/image.h"
#include "../engine/renderer/texture.h"
#include "../engine/sound.h" #include "../engine/sound.h"
#include "demo.h" #include "demo.h"
using namespace std::string_literals;
using namespace base; using namespace base;
using namespace eng; using namespace eng;
@ -44,29 +44,18 @@ void SetupFadeOutAnim(Animator& animator, float delay) {
} // namespace } // namespace
Enemy::Enemy() Enemy::Enemy() = default;
: 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() { bool Enemy::Initialize() {
explosion_sound_ = std::make_shared<Sound>(); explosion_sound_ = std::make_shared<Sound>();
if (!explosion_sound_->Load("explosion.mp3")) if (!explosion_sound_->Load("explosion.mp3", false))
return false; return false;
return CreateRenderResources(); return CreateRenderResources();
} }
void Enemy::ContextLost() {
CreateRenderResources();
}
void Enemy::Update(float delta_time) { void Enemy::Update(float delta_time) {
if (!waiting_for_next_wave_) { if (!waiting_for_next_wave_) {
if (spawn_factor_interpolator_ < 1) { 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) { 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; return GetTarget(damage_type) ? true : false;
} }
Vector2 Enemy::GetTargetPos(DamageType damage_type) { 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); EnemyUnit* target = GetTarget(damage_type);
if (target) if (target)
@ -126,7 +104,7 @@ void Enemy::SelectTarget(DamageType damage_type,
const Vector2& origin, const Vector2& origin,
const Vector2& dir, const Vector2& dir,
float snap_factor) { 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_) if (waiting_for_next_wave_)
return; return;
@ -171,7 +149,7 @@ void Enemy::SelectTarget(DamageType damage_type,
} }
void Enemy::DeselectTarget(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); EnemyUnit* target = GetTarget(damage_type);
if (target) { if (target) {
@ -182,7 +160,7 @@ void Enemy::DeselectTarget(DamageType damage_type) {
} }
void Enemy::HitTarget(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_) if (waiting_for_next_wave_)
return; return;
@ -227,8 +205,8 @@ void Enemy::OnWaveStarted(int wave) {
} }
void Enemy::TakeDamage(EnemyUnit* target, int damage) { void Enemy::TakeDamage(EnemyUnit* target, int damage) {
assert(!target->marked_for_removal); DCHECK(!target->marked_for_removal);
assert(target->hit_points > 0); DCHECK(target->hit_points > 0);
target->blast.SetVisible(true); target->blast.SetVisible(true);
target->blast_animator.Play(Animator::kFrames, false); target->blast_animator.Play(Animator::kFrames, false);
@ -313,8 +291,8 @@ void Enemy::Spawn(EnemyType enemy_type,
DamageType damage_type, DamageType damage_type,
const Vector2& pos, const Vector2& pos,
float speed) { float speed) {
assert(enemy_type > kEnemyType_Invalid && enemy_type < kEnemyType_Max); DCHECK(enemy_type > kEnemyType_Invalid && enemy_type < kEnemyType_Max);
assert(damage_type > kDamageType_Invalid && damage_type < kDamageType_Max); DCHECK(damage_type > kDamageType_Invalid && damage_type < kDamageType_Max);
Engine& engine = Engine::Get(); Engine& engine = Engine::Get();
Demo* game = static_cast<Demo*>(engine.GetGame()); Demo* game = static_cast<Demo*>(engine.GetGame());
@ -324,15 +302,15 @@ void Enemy::Spawn(EnemyType enemy_type,
e.damage_type = damage_type; e.damage_type = damage_type;
if (enemy_type == kEnemyType_Skull) { if (enemy_type == kEnemyType_Skull) {
e.total_health = e.hit_points = 1; 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) { } else if (enemy_type == kEnemyType_Bug) {
e.total_health = e.hit_points = 2; e.total_health = e.hit_points = 2;
e.sprite.Create(bug_tex_, {10, 4}); e.sprite.Create("bug_tex", {10, 4});
} else { // kEnemyType_Tank } else { // kEnemyType_Tank
e.total_health = e.hit_points = 6; 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); e.sprite.SetVisible(true);
Vector2 spawn_pos = pos + Vector2(0, e.sprite.GetScale().y / 2); Vector2 spawn_pos = pos + Vector2(0, e.sprite.GetScale().y / 2);
e.sprite.SetOffset(spawn_pos); e.sprite.SetOffset(spawn_pos);
@ -344,26 +322,28 @@ void Enemy::Spawn(EnemyType enemy_type,
e.sprite_animator.Attach(&e.sprite); e.sprite_animator.Attach(&e.sprite);
e.sprite_animator.Play(Animator::kFrames, true); e.sprite_animator.Play(Animator::kFrames, true);
e.target.Create(target_tex_, {6, 2}); e.target.Create("target_tex", {6, 2});
e.target.AutoScale(); e.target.SetZOrder(12);
e.target.SetOffset(spawn_pos); e.target.SetOffset(spawn_pos);
e.blast.Create(blast_tex_, {6, 2}); e.blast.Create("blast_tex", {6, 2});
e.blast.AutoScale(); e.blast.SetZOrder(12);
e.blast.SetOffset(spawn_pos); 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.Scale(e.sprite.GetScale() * Vector2(0.6f, 0.01f));
e.health_base.SetOffset(spawn_pos); e.health_base.SetOffset(spawn_pos);
e.health_base.PlaceToBottomOf(e.sprite); e.health_base.PlaceToBottomOf(e.sprite);
e.health_base.SetColor({0.5f, 0.5f, 0.5f, 1}); 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.Scale(e.sprite.GetScale() * Vector2(0.6f, 0.01f));
e.health_bar.SetOffset(spawn_pos); e.health_bar.SetOffset(spawn_pos);
e.health_bar.PlaceToBottomOf(e.sprite); e.health_bar.PlaceToBottomOf(e.sprite);
e.health_bar.SetColor({0.161f, 0.89f, 0.322f, 1}); e.health_bar.SetColor({0.161f, 0.89f, 0.322f, 1});
e.score.Create(score_tex_[e.enemy_type]); e.score.Create("score_tex"s + std::to_string(e.enemy_type));
e.score.AutoScale(); e.score.SetZOrder(12);
e.score.SetColor({1, 1, 1, 1}); e.score.SetColor({1, 1, 1, 1});
e.score.SetOffset(spawn_pos); e.score.SetOffset(spawn_pos);
@ -425,13 +405,14 @@ Enemy::EnemyUnit* Enemy::GetTarget(DamageType damage_type) {
} }
int Enemy::GetScore(EnemyType enemy_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]; 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(); const Font& font = static_cast<Demo*>(Engine::Get().GetGame())->GetFont();
int score = GetScore(enemy_type);
std::string text = std::to_string(score); std::string text = std::to_string(score);
int width, height; int width, height;
font.CalculateBoundingBox(text.c_str(), width, height); font.CalculateBoundingBox(text.c_str(), width, height);
@ -446,31 +427,17 @@ std::unique_ptr<Image> Enemy::GetScoreImage(int score) {
} }
bool Enemy::CreateRenderResources() { bool Enemy::CreateRenderResources() {
auto skull_image = std::make_unique<Image>(); Engine::Get().SetImageSource("skull_tex", "enemy_anims_01_frames_ok.png",
if (!skull_image->Load("enemy_anims_01_frames_ok.png")) true);
return false; Engine::Get().SetImageSource("bug_tex", "enemy_anims_02_frames_ok.png", true);
auto bug_image = std::make_unique<Image>(); Engine::Get().SetImageSource("target_tex", "enemy_target_single_ok.png",
if (!bug_image->Load("enemy_anims_02_frames_ok.png")) true);
return false; Engine::Get().SetImageSource("blast_tex", "enemy_anims_blast_ok.png", true);
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));
for (int i = 0; i < kEnemyType_Max; ++i) 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; return true;
} }

View File

@ -15,7 +15,6 @@
namespace eng { namespace eng {
class Image; class Image;
class Sound; class Sound;
class Texture;
} // namespace eng } // namespace eng
class Enemy { class Enemy {
@ -25,12 +24,8 @@ class Enemy {
bool Initialize(); bool Initialize();
void ContextLost();
void Update(float delta_time); void Update(float delta_time);
void Draw(float frame_frac);
bool HasTarget(DamageType damage_type); bool HasTarget(DamageType damage_type);
base::Vector2 GetTargetPos(DamageType damage_type); base::Vector2 GetTargetPos(DamageType damage_type);
@ -76,12 +71,6 @@ class Enemy {
eng::SoundPlayer explosion_; 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::shared_ptr<eng::Sound> explosion_sound_;
std::list<EnemyUnit> enemies_; std::list<EnemyUnit> enemies_;
@ -111,7 +100,7 @@ class Enemy {
int GetScore(EnemyType enemy_type); int GetScore(EnemyType enemy_type);
std::unique_ptr<eng::Image> GetScoreImage(int score); std::unique_ptr<eng::Image> GetScoreImage(EnemyType enemy_type);
bool CreateRenderResources(); bool CreateRenderResources();
}; };

View File

@ -6,9 +6,10 @@
#include "../engine/engine.h" #include "../engine/engine.h"
#include "../engine/font.h" #include "../engine/font.h"
#include "../engine/image.h" #include "../engine/image.h"
#include "../engine/renderer/texture.h"
#include "demo.h" #include "demo.h"
using namespace std::string_literals;
using namespace base; using namespace base;
using namespace eng; using namespace eng;
@ -23,10 +24,7 @@ const Vector4 kTextColor = {0.895f, 0.692f, 0.24f, 1};
} // namespace } // namespace
Hud::Hud() { Hud::Hud() = default;
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; int tmp;
font.CalculateBoundingBox("big_enough_text", max_text_width_, tmp); font.CalculateBoundingBox("big_enough_text", max_text_width_, tmp);
for (int i = 0; i < 2; ++i) { Engine::Get().SetImageSource("text0",
auto image = CreateImage(); std::bind(&Hud::CreateScoreImage, this));
Engine::Get().SetImageSource("text1", std::bind(&Hud::CreateWaveImage, this));
text_[i].GetTexture()->Update(std::move(image)); for (int i = 0; i < 2; ++i) {
text_[i].AutoScale(); text_[i].Create("text"s + std::to_string(i));
text_[i].SetColor(kTextColor); text_[i].SetZOrder(30);
text_[i].SetColor(kTextColor * Vector4(1, 1, 1, 0));
Vector2 pos = (engine.GetScreenSize() / 2 - text_[i].GetScale() / 2); Vector2 pos = (engine.GetScreenSize() / 2 - text_[i].GetScale() / 2);
pos -= engine.GetScreenSize() * Vector2(kHorizontalMargin, kVerticalMargin); pos -= engine.GetScreenSize() * Vector2(kHorizontalMargin, kVerticalMargin);
@ -51,6 +51,7 @@ bool Hud::Initialize() {
scale -= engine.GetScreenSize() * Vector2(kHorizontalMargin * 4, 0); scale -= engine.GetScreenSize() * Vector2(kHorizontalMargin * 4, 0);
scale += text_[0].GetScale() * Vector2(0, 0.3f); scale += text_[0].GetScale() * Vector2(0, 0.3f);
progress_bar_[i].SetZOrder(30);
progress_bar_[i].Scale(scale); progress_bar_[i].Scale(scale);
progress_bar_[i].Translate(pos * Vector2(0, 1)); progress_bar_[i].Translate(pos * Vector2(0, 1));
progress_bar_[i].SetColor(kPprogressBarColor[i] * Vector4(1, 1, 1, 0)); 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() { void Hud::Show() {
if (text_[0].IsVisible()) if (text_[0].IsVisible())
return; 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; last_score_ = score;
Print(0, std::to_string(score)); Engine::Get().RefreshImage("text0");
if (flash) { if (flash) {
text_animator_[0].SetEndCallback(Animator::kBlending, text_animator_cb_[0]); 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; last_wave_ = wave;
std::string text = "wave "; Engine::Get().RefreshImage("text1");
text += std::to_string(wave);
Print(1, text.c_str());
if (flash) { if (flash) {
text_animator_[1].SetEndCallback(Animator::kBlending, text_animator_cb_[1]); 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}); 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(); const Font& font = static_cast<Demo*>(Engine::Get().GetGame())->GetFont();
auto image = CreateImage(); 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()); 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() { std::unique_ptr<Image> Hud::CreateImage() {

View File

@ -22,14 +22,10 @@ class Hud {
void Update(float delta_time); void Update(float delta_time);
void Draw();
void ContextLost();
void Show(); void Show();
void PrintScore(int score, bool flash); void SetScore(int score, bool flash);
void PrintWave(int wave, bool flash); void SetWave(int wave, bool flash);
void SetProgress(float progress); void SetProgress(float progress);
private: private:
@ -46,7 +42,10 @@ class Hud {
int last_wave_ = 0; int last_wave_ = 0;
float last_progress_ = 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(); std::unique_ptr<eng::Image> CreateImage();
}; };

View File

@ -1,18 +1,15 @@
#include "menu.h" #include "menu.h"
#include <cassert>
#include <cmath> #include <cmath>
#include <vector> #include <vector>
#include "../base/collusion_test.h" #include "../base/collusion_test.h"
#include "../base/interpolation.h" #include "../base/interpolation.h"
#include "../base/log.h" #include "../base/log.h"
#include "../base/worker.h"
#include "../engine/engine.h" #include "../engine/engine.h"
#include "../engine/font.h" #include "../engine/font.h"
#include "../engine/image.h" #include "../engine/image.h"
#include "../engine/input_event.h" #include "../engine/input_event.h"
#include "../engine/renderer/texture.h"
#include "demo.h" #include "demo.h"
using namespace base; using namespace base;
@ -34,7 +31,7 @@ constexpr float kFadeSpeed = 0.2f;
} // namespace } // namespace
Menu::Menu() : tex_(Engine::Get().CreateRenderResource<Texture>()) {} Menu::Menu() = default;
Menu::~Menu() = default; Menu::~Menu() = default;
@ -49,11 +46,12 @@ bool Menu::Initialize() {
max_text_width_ = width; max_text_width_ = width;
} }
tex_->Update(CreateImage()); if (!CreateRenderResources())
return false;
for (int i = 0; i < kOption_Max; ++i) { for (int i = 0; i < kOption_Max; ++i) {
items_[i].text.Create(tex_, {1, 4}); items_[i].text.Create("menu_tex", {1, 4});
items_[i].text.AutoScale(); items_[i].text.SetZOrder(40);
items_[i].text.Scale(1.5f); items_[i].text.Scale(1.5f);
items_[i].text.SetColor(kColorFadeOut); items_[i].text.SetColor(kColorFadeOut);
items_[i].text.SetVisible(false); items_[i].text.SetVisible(false);
@ -86,15 +84,12 @@ void Menu::Update(float delta_time) {
} }
void Menu::OnInputEvent(std::unique_ptr<InputEvent> event) { void Menu::OnInputEvent(std::unique_ptr<InputEvent> event) {
if (event->GetType() == InputEvent::kTap || if (event->GetType() == InputEvent::kDragStart)
event->GetType() == InputEvent::kDragStart)
tap_pos_[0] = tap_pos_[1] = event->GetVector(0); tap_pos_[0] = tap_pos_[1] = event->GetVector(0);
else if (event->GetType() == InputEvent::kDrag) else if (event->GetType() == InputEvent::kDrag)
tap_pos_[1] = event->GetVector(0); tap_pos_[1] = event->GetVector(0);
if ((event->GetType() != InputEvent::kTap && if (event->GetType() != InputEvent::kDragEnd || IsAnimating())
event->GetType() != InputEvent::kDragEnd) ||
IsAnimating())
return; return;
for (int i = 0; i < kOption_Max; ++i) { 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) { void Menu::SetOptionEnabled(Option o, bool enable) {
int first = -1, last = -1; int first = -1, last = -1;
for (int i = 0; i < kOption_Max; ++i) { 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() { std::unique_ptr<Image> Menu::CreateImage() {
const Font& font = static_cast<Demo*>(Engine::Get().GetGame())->GetFont(); 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. // 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); 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) { for (int i = 0; i < kOption_Max; ++i) {
int w, h; int w, h;
font.CalculateBoundingBox(kMenuOption[i], w, h); font.CalculateBoundingBox(kMenuOption[i], w, h);
float x = (image->GetWidth() - w) / 2; float x = (image->GetWidth() - w) / 2;
float y = line_height * i; float y = line_height * i;
worker.Enqueue(std::bind(&Font::Print, &font, x, y, kMenuOption[i], font.Print(x, y, kMenuOption[i], image->GetBuffer(), image->GetWidth());
image->GetBuffer(), image->GetWidth()));
} }
worker.Join();
return image; return image;
} }

View File

@ -12,7 +12,6 @@
namespace eng { namespace eng {
class Image; class Image;
class InputEvent; class InputEvent;
class Texture;
} // namespace eng } // namespace eng
class Menu { class Menu {
@ -35,10 +34,6 @@ class Menu {
void OnInputEvent(std::unique_ptr<eng::InputEvent> event); void OnInputEvent(std::unique_ptr<eng::InputEvent> event);
void Draw();
void ContextLost();
void SetOptionEnabled(Option o, bool enable); void SetOptionEnabled(Option o, bool enable);
void Show(); void Show();
@ -54,8 +49,6 @@ class Menu {
bool hide = false; bool hide = false;
}; };
std::shared_ptr<eng::Texture> tex_;
Item items_[kOption_Max]; Item items_[kOption_Max];
int max_text_width_ = 0; int max_text_width_ = 0;
@ -64,6 +57,8 @@ class Menu {
base::Vector2 tap_pos_[2] = {{0, 0}, {0, 0}}; base::Vector2 tap_pos_[2] = {{0, 0}, {0, 0}};
bool CreateRenderResources();
std::unique_ptr<eng::Image> CreateImage(); std::unique_ptr<eng::Image> CreateImage();
bool IsAnimating(); bool IsAnimating();

View File

@ -1,7 +1,5 @@
#include "player.h" #include "player.h"
#include <cassert>
#include "../base/log.h" #include "../base/log.h"
#include "../engine/engine.h" #include "../engine/engine.h"
#include "../engine/image.h" #include "../engine/image.h"
@ -21,9 +19,7 @@ constexpr int wepon_anim_speed = 48;
} // namespace } // namespace
Player::Player() Player::Player() = default;
: 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; return true;
} }
void Player::ContextLost() {
CreateRenderResources();
}
void Player::Update(float delta_time) { void Player::Update(float delta_time) {
for (int i = 0; i < 2; ++i) { for (int i = 0; i < 2; ++i) {
warmup_animator_[i].Update(delta_time); warmup_animator_[i].Update(delta_time);
@ -63,15 +55,6 @@ void Player::OnInputEvent(std::unique_ptr<InputEvent> event) {
DragCancel(); 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 { Vector2 Player::GetWeaponPos(DamageType type) const {
return Engine::Get().GetScreenSize() / return Engine::Get().GetScreenSize() /
Vector2(type == kDamageType_Green ? 3.5f : -3.5f, -2) + 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) if (closest_dist < weapon_[closest_weapon].GetScale().x * 0.9f)
return closest_weapon; return closest_weapon;
return kDamageType_Invalid; return kDamageType_Invalid;
@ -157,27 +140,27 @@ bool Player::IsFiring(DamageType type) {
void Player::SetupWeapons() { void Player::SetupWeapons() {
for (int i = 0; i < 2; ++i) { for (int i = 0; i < 2; ++i) {
// Setup draw sign. // Setup draw sign.
drag_sign_[i].Create(weapon_tex_, {8, 2}); drag_sign_[i].Create("weapon_tex", {8, 2});
drag_sign_[i].AutoScale(); drag_sign_[i].SetZOrder(21);
drag_sign_[i].SetFrame(i * 8); drag_sign_[i].SetFrame(i * 8);
// Setup weapon. // Setup weapon.
weapon_[i].Create(weapon_tex_, {8, 2}); weapon_[i].Create("weapon_tex", {8, 2});
weapon_[i].AutoScale(); weapon_[i].SetZOrder(24);
weapon_[i].SetVisible(true); weapon_[i].SetVisible(true);
weapon_[i].SetFrame(wepon_warmup_frame[i]); weapon_[i].SetFrame(wepon_warmup_frame[i]);
// Setup beam. // Setup beam.
beam_[i].Create(beam_tex_, {1, 2}); beam_[i].Create("beam_tex", {1, 2});
beam_[i].AutoScale(); beam_[i].SetZOrder(22);
beam_[i].SetFrame(i); beam_[i].SetFrame(i);
beam_[i].PlaceToRightOf(weapon_[i]); beam_[i].PlaceToRightOf(weapon_[i]);
beam_[i].Translate(weapon_[i].GetScale() * Vector2(-0.5f, 0)); beam_[i].Translate(weapon_[i].GetScale() * Vector2(-0.5f, 0));
beam_[i].SetPivot(beam_[i].GetOffset()); beam_[i].SetPivot(beam_[i].GetOffset());
// Setup beam spark. // Setup beam spark.
beam_spark_[i].Create(weapon_tex_, {8, 2}); beam_spark_[i].Create("weapon_tex", {8, 2});
beam_spark_[i].AutoScale(); beam_spark_[i].SetZOrder(23);
beam_spark_[i].SetFrame(i * 8 + 1); beam_spark_[i].SetFrame(i * 8 + 1);
beam_spark_[i].PlaceToRightOf(weapon_[i]); beam_spark_[i].PlaceToRightOf(weapon_[i]);
beam_spark_[i].Translate(weapon_[i].GetScale() * Vector2(-0.5f, 0)); beam_spark_[i].Translate(weapon_[i].GetScale() * Vector2(-0.5f, 0));
@ -338,17 +321,8 @@ void Player::NavigateBack() {
} }
bool Player::CreateRenderResources() { bool Player::CreateRenderResources() {
auto weapon_image = std::make_unique<Image>(); Engine::Get().SetImageSource("weapon_tex", "enemy_anims_flare_ok.png", true);
if (!weapon_image->Load("enemy_anims_flare_ok.png")) Engine::Get().SetImageSource("beam_tex", "enemy_ray_ok.png", true);
return false;
auto beam_image = std::make_unique<Image>();
if (!beam_image->Load("enemy_ray_ok.png"))
return false;
weapon_image->Compress();
beam_image->Compress();
weapon_tex_->Update(std::move(weapon_image));
beam_tex_->Update(std::move(beam_image));
return true; return true;
} }

View File

@ -6,7 +6,6 @@
#include "../base/vecmath.h" #include "../base/vecmath.h"
#include "../engine/animator.h" #include "../engine/animator.h"
#include "../engine/image_quad.h" #include "../engine/image_quad.h"
#include "../engine/renderer/texture.h"
#include "damage_type.h" #include "damage_type.h"
namespace eng { namespace eng {
@ -20,21 +19,14 @@ class Player {
bool Initialize(); bool Initialize();
void ContextLost();
void Update(float delta_time); void Update(float delta_time);
void OnInputEvent(std::unique_ptr<eng::InputEvent> event); void OnInputEvent(std::unique_ptr<eng::InputEvent> event);
void Draw(float frame_frac);
base::Vector2 GetWeaponPos(DamageType type) const; base::Vector2 GetWeaponPos(DamageType type) const;
base::Vector2 GetWeaponScale() const; base::Vector2 GetWeaponScale() const;
private: private:
std::shared_ptr<eng::Texture> weapon_tex_;
std::shared_ptr<eng::Texture> beam_tex_;
eng::ImageQuad drag_sign_[2]; eng::ImageQuad drag_sign_[2];
eng::ImageQuad weapon_[2]; eng::ImageQuad weapon_[2];
eng::ImageQuad beam_[2]; eng::ImageQuad beam_[2];

View File

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

View File

@ -33,13 +33,15 @@ class SkyQuad : public eng::Animatable {
void SetColor(const base::Vector4& color) override { nebula_color_ = color; } void SetColor(const base::Vector4& color) override { nebula_color_ = color; }
base::Vector4 GetColor() const override { return nebula_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 ContextLost();
void SwitchColor(const base::Vector4& color); void SwitchColor(const base::Vector4& color);
private: private:
std::shared_ptr<eng::Shader> shader_; std::unique_ptr<eng::Shader> shader_;
base::Vector2 sky_offset_ = {0, 0}; base::Vector2 sky_offset_ = {0, 0};
base::Vector2 last_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 #define SHAPE_H
#include "../base/vecmath.h" #include "../base/vecmath.h"
#include "drawable.h"
namespace eng { namespace eng {
class Animatable { class Animatable : public Drawable {
public: public:
Animatable() = default; Animatable() = default;
virtual ~Animatable() = default; ~Animatable() override = default;
void Translate(const base::Vector2& offset); void Translate(const base::Vector2& offset);
void Scale(const base::Vector2& scale); void Scale(const base::Vector2& scale);
@ -33,9 +34,6 @@ class Animatable {
virtual void SetColor(const base::Vector4& color) = 0; virtual void SetColor(const base::Vector4& color) = 0;
virtual base::Vector4 GetColor() const = 0; virtual base::Vector4 GetColor() const = 0;
void SetVisible(bool visible) { visible_ = visible; }
bool IsVisible() const { return visible_; }
void PlaceToLeftOf(const Animatable& s) { void PlaceToLeftOf(const Animatable& s) {
Translate({s.GetScale().x / -2.0f + GetScale().x / -2.0f, 0}); Translate({s.GetScale().x / -2.0f + GetScale().x / -2.0f, 0});
} }
@ -58,7 +56,6 @@ class Animatable {
base::Vector2 pivot_ = {0, 0}; base::Vector2 pivot_ = {0, 0};
base::Vector2 rotation_ = {0, 1}; base::Vector2 rotation_ = {0, 1};
float theta_ = 0; float theta_ = 0;
bool visible_ = false;
}; };
} // namespace eng } // namespace eng

View File

@ -43,6 +43,33 @@ void Animator::Stop(int animation) {
loop_flags_ &= ~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) { void Animator::SetEndCallback(int animation, base::Closure cb) {
if ((inside_cb_ & animation) != 0) { if ((inside_cb_ & animation) != 0) {
has_pending_cb_ = true; has_pending_cb_ = true;

View File

@ -28,47 +28,39 @@ class Animator {
Animator() = default; Animator() = default;
~Animator() = default; ~Animator() = default;
// Attached the given animatable to this animator and sets the start values.
void Attach(Animatable* animatable); void Attach(Animatable* animatable);
void Play(int animation, bool loop); void Play(int animation, bool loop);
void Pause(int animation); void Pause(int animation);
void Stop(int animation); void Stop(int animation);
// Set callback for the given animations. It's called for each animation once // Get/set current time of the given animation.
// it ends. Not that it's not called for looping animations. 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); void SetEndCallback(int animation, base::Closure cb);
// Set movement animation parameters. Movement is relative to the attached // Distance is the magnitude of direction vector. Duration is in seconds.
// animatable's current position. Distance is calculated from the magnitude of
// direction vector. Duration is in seconds.
void SetMovement(base::Vector2 direction, void SetMovement(base::Vector2 direction,
float duration, float duration,
Interpolator interpolator = nullptr); Interpolator interpolator = nullptr);
// Set rotation animation parameters. Rotation is relative to the attached // Rotation is in radian. Duration is in seconds.
// animatable's current rotation. Duration is in seconds.
void SetRotation(float target, void SetRotation(float target,
float duration, float duration,
Interpolator interpolator = nullptr); 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, void SetBlending(base::Vector4 target,
float duration, float duration,
Interpolator interpolator = nullptr); Interpolator interpolator = nullptr);
// Set frame playback animation parameters. Frame animation is absolute. The // Plays count number of frames.
// absolute start frames are obtained from the attached animatables. Plays
// count number of frames.
void SetFrames(int count, void SetFrames(int count,
int frames_per_second, int frames_per_second,
Interpolator interpolator = nullptr); Interpolator interpolator = nullptr);
// Set Timer parameters. Timer doesn't play any animation. Usefull for // Triggers a callback after the given seconds passed.
// triggering a callback after the given seconds passed. Loop parameter is
// ignored when played.
void SetTimer(float duration); void SetTimer(float duration);
// Set visibility of all attached animatables. // Set visibility of all attached animatables.

View File

@ -2,6 +2,8 @@
#include <alsa/asoundlib.h> #include <alsa/asoundlib.h>
#include <memory>
#include "../../base/log.h" #include "../../base/log.h"
#include "audio_resource.h" #include "audio_resource.h"
@ -21,10 +23,10 @@ bool AudioAlsa::Initialize() {
// Contains information about the hardware. // Contains information about the hardware.
snd_pcm_hw_params_t* hw_params; snd_pcm_hw_params_t* hw_params;
// "default" is usualy PulseAudio. Use "plughw:CARD=PCH" instead for direct // TODO: "default" is usualy PulseAudio. Select a device with "plughw" for
// hardware device with software format conversion. // direct hardware device with software format conversion.
if ((err = snd_pcm_open(&pcm_handle_, "plughw:CARD=PCH", if ((err = snd_pcm_open(&device_, "default", SND_PCM_STREAM_PLAYBACK, 0)) <
SND_PCM_STREAM_PLAYBACK, 0)) < 0) { 0) {
LOG << "Cannot open audio device. Error: " << snd_strerror(err); LOG << "Cannot open audio device. Error: " << snd_strerror(err);
return false; return false;
} }
@ -34,65 +36,65 @@ bool AudioAlsa::Initialize() {
snd_pcm_hw_params_alloca(&hw_params); snd_pcm_hw_params_alloca(&hw_params);
// Init hw_params with full configuration space. // 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: " LOG << "Cannot initialize hardware parameter structure. Error: "
<< snd_strerror(err); << snd_strerror(err);
break; break;
} }
if ((err = snd_pcm_hw_params_set_access( 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); LOG << "Cannot set access type. Error: " << snd_strerror(err);
break; 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) { SND_PCM_FORMAT_FLOAT)) < 0) {
LOG << "Cannot set sample format. Error: " << snd_strerror(err); LOG << "Cannot set sample format. Error: " << snd_strerror(err);
break; break;
} }
// Disable software resampler. // 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) { 0) {
LOG << "Cannot disbale software resampler. Error: " << snd_strerror(err); LOG << "Cannot disbale software resampler. Error: " << snd_strerror(err);
break; break;
} }
unsigned sample_rate = 48000; unsigned sample_rate = 48000;
if ((err = snd_pcm_hw_params_set_rate_near(pcm_handle_, hw_params, if ((err = snd_pcm_hw_params_set_rate_near(device_, hw_params, &sample_rate,
&sample_rate, 0)) < 0) { 0)) < 0) {
LOG << "Cannot set sample rate. Error: " << snd_strerror(err); LOG << "Cannot set sample rate. Error: " << snd_strerror(err);
break; 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); LOG << "Cannot set channel count. Error: " << snd_strerror(err);
break; break;
} }
// Set period time to 4 ms. The latency will be 12 ms for 3 perods. // Set period time to 4 ms. The latency will be 12 ms for 3 perods.
unsigned period_time = 4000; 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) { &period_time, 0)) < 0) {
LOG << "Cannot set periods. Error: " << snd_strerror(err); LOG << "Cannot set periods. Error: " << snd_strerror(err);
break; break;
} }
unsigned periods = 3; unsigned periods = 3;
if ((err = snd_pcm_hw_params_set_periods_near(pcm_handle_, hw_params, if ((err = snd_pcm_hw_params_set_periods_near(device_, hw_params, &periods,
&periods, 0)) < 0) { 0)) < 0) {
LOG << "Cannot set periods. Error: " << snd_strerror(err); LOG << "Cannot set periods. Error: " << snd_strerror(err);
break; break;
} }
// Apply HW parameter settings to PCM device and prepare device. // 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); LOG << "Cannot set parameters. Error: " << snd_strerror(err);
break; break;
} }
if ((err = snd_pcm_prepare(pcm_handle_)) < 0) { if ((err = snd_pcm_prepare(device_)) < 0) {
LOG << "Cannot prepare audio interface for use. Error: " LOG << "Cannot prepare audio interface for use. Error: "
<< snd_strerror(err); << snd_strerror(err);
break; break;
@ -126,60 +128,75 @@ bool AudioAlsa::Initialize() {
sample_rate_ = sample_rate; sample_rate_ = sample_rate;
period_size_ = period_size; period_size_ = period_size;
StartWorker(); StartAudioThread();
return true; return true;
} while (false); } while (false);
snd_pcm_close(pcm_handle_); snd_pcm_close(device_);
return false; return false;
} }
void AudioAlsa::Shutdown() { void AudioAlsa::Shutdown() {
LOG << "Shutting down audio system."; LOG << "Shutting down audio system.";
TerminateWorker();
snd_pcm_drop(pcm_handle_); TerminateAudioThread();
snd_pcm_close(pcm_handle_); 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() { size_t AudioAlsa::GetSampleRate() {
return sample_rate_; return sample_rate_;
} }
bool AudioAlsa::StartWorker() { bool AudioAlsa::StartAudioThread() {
LOG << "Starting audio thread."; LOG << "Starting audio thread.";
std::promise<bool> promise; DCHECK(!terminate_audio_thread_.load(std::memory_order_relaxed));
std::future<bool> future = promise.get_future();
worker_thread_ = audio_thread_ = std::thread(&AudioAlsa::AudioThreadMain, this);
std::thread(&AudioAlsa::WorkerMain, this, std::move(promise));
return future.get();
} }
void AudioAlsa::TerminateWorker() { void AudioAlsa::TerminateAudioThread() {
// Notify worker thread and wait for it to terminate. if (terminate_audio_thread_.load(std::memory_order_relaxed))
if (terminate_worker_)
return; return;
terminate_worker_ = true;
LOG << "Terminating audio thread"; 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) { void AudioAlsa::AudioThreadMain() {
promise.set_value(true);
size_t num_frames = period_size_ / (num_channels_ * sizeof(float)); size_t num_frames = period_size_ / (num_channels_ * sizeof(float));
auto buffer = std::make_unique<float[]>(num_frames * 2); auto buffer = std::make_unique<float[]>(num_frames * 2);
for (;;) { for (;;) {
if (terminate_worker_) while (suspend_audio_thread_.load(std::memory_order_relaxed)) {
return; if (terminate_audio_thread_.load(std::memory_order_relaxed))
return;
std::this_thread::yield();
}
RenderAudio(buffer.get(), num_frames); RenderAudio(buffer.get(), num_frames);
while (snd_pcm_writei(pcm_handle_, buffer.get(), num_frames) < 0) { while (snd_pcm_writei(device_, buffer.get(), num_frames) < 0) {
snd_pcm_prepare(pcm_handle_); snd_pcm_prepare(device_);
LOG << "Audio buffer underrun!"; DLOG << "Audio buffer underrun!";
} }
} }
} }

View File

@ -1,8 +1,7 @@
#ifndef AUDIO_ALSA_H #ifndef AUDIO_ALSA_H
#define AUDIO_ALSA_H #define AUDIO_ALSA_H
#include <future> #include <atomic>
#include <memory>
#include <thread> #include <thread>
#include "audio_base.h" #include "audio_base.h"
@ -22,23 +21,27 @@ class AudioAlsa : public AudioBase {
void Shutdown(); void Shutdown();
void Suspend();
void Resume();
size_t GetSampleRate(); size_t GetSampleRate();
private: private:
// Handle for the PCM device. // Handle for the PCM device.
snd_pcm_t* pcm_handle_; snd_pcm_t* device_;
std::thread worker_thread_; std::thread audio_thread_;
bool terminate_worker_ = false; std::atomic<bool> terminate_audio_thread_ = false;
std::atomic<bool> suspend_audio_thread_ = false;
size_t num_channels_ = 0; size_t num_channels_ = 0;
size_t sample_rate_ = 0; size_t sample_rate_ = 0;
size_t period_size_ = 0; size_t period_size_ = 0;
bool StartWorker(); bool StartAudioThread();
void TerminateWorker(); void TerminateAudioThread();
void WorkerMain(std::promise<bool> promise); void AudioThreadMain();
}; };
} // namespace eng } // namespace eng

View File

@ -1,32 +1,33 @@
#include "audio_base.h" #include "audio.h"
#include <cstring> #include <cstring>
#include "../../base/log.h" #include "../../base/log.h"
#include "../../base/task_runner.h"
#include "../../base/worker.h"
#include "../sound.h" #include "../sound.h"
using namespace base; using namespace base;
namespace eng { namespace eng {
AudioBase::AudioBase() = default; AudioBase::AudioBase()
: main_thread_task_runner_(TaskRunner::GetThreadLocalTaskRunner()) {}
AudioBase::~AudioBase() { AudioBase::~AudioBase() = default;
worker_.Join();
}
void AudioBase::Play(std::shared_ptr<AudioSample> sample) { void AudioBase::Play(std::shared_ptr<AudioSample> sample) {
std::unique_lock<std::mutex> scoped_lock(mutex_); if (audio_enabled_) {
samples_[0].push_back(sample); std::lock_guard<Spinlock> scoped_lock(lock_);
} samples_[0].push_back(sample);
} else {
void AudioBase::Update() { sample->active = false;
task_runner_.Run(); }
} }
void AudioBase::RenderAudio(float* output_buffer, size_t num_frames) { 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]); 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();) { for (auto it = samples_[1].begin(); it != samples_[1].end();) {
AudioSample* sample = it->get(); AudioSample* sample = it->get();
unsigned flags = sample->flags; auto sound = sample->sound.get();
bool remove = false; unsigned flags = sample->flags.load(std::memory_order_relaxed);
if (flags & AudioSample::kStopped) { if (flags & AudioSample::kStopped) {
remove = true; sample->marked_for_removal = true;
} else { } else if (!sample->marked_for_removal) {
auto sound = sample->sound.get(); const float* src[2] = {sound->GetBuffer(0), sound->GetBuffer(1)};
const float* src[2] = {const_cast<const Sound*>(sound)->GetBuffer(0),
const_cast<const Sound*>(sound)->GetBuffer(1)};
if (!src[1]) if (!src[1])
src[1] = src[0]; // mono. src[1] = src[0]; // mono.
size_t num_samples = sound->GetNumSamples(); size_t num_samples = sound->GetNumSamples();
size_t num_channels = sound->num_channels();
size_t src_index = sample->src_index; 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; size_t accumulator = sample->accumulator;
float amplitude = sample->amplitude; float amplitude = sample->amplitude;
float amplitude_inc = sample->amplitude_inc; float amplitude_inc =
float max_amplitude = sample->max_amplitude; sample->amplitude_inc.load(std::memory_order_relaxed);
float max_amplitude =
sample->max_amplitude.load(std::memory_order_relaxed);
size_t channel_offset = size_t channel_offset =
(flags & AudioSample::kSimulateStereo) && num_channels == 1 (flags & AudioSample::kSimulateStereo) && !sound->is_streaming_sound()
? sound->hz() / 10 ? sound->hz() / 10
: 0; : 0;
DCHECK(num_samples || sound->is_streaming_sound());
for (size_t i = 0; i < num_frames * kChannelCount;) { for (size_t i = 0; i < num_frames * kChannelCount;) {
// Mix the 1st channel. if (num_samples) {
output_buffer[i++] += src[0][src_index] * amplitude; // Mix the 1st channel.
output_buffer[i++] += src[0][src_index] * amplitude;
// Mix the 2nd channel. Offset the source index for stereo simulation. // Mix the 2nd channel. Offset the source index for stereo simulation.
size_t ind = channel_offset + src_index; size_t ind = channel_offset + src_index;
if (ind < num_samples) if (ind < num_samples)
output_buffer[i++] += src[1][ind] * amplitude; output_buffer[i++] += src[1][ind] * amplitude;
else if (flags & AudioSample::kLoop) else if (flags & AudioSample::kLoop)
output_buffer[i++] += src[1][ind % num_samples] * amplitude; output_buffer[i++] += src[1][ind % num_samples] * amplitude;
else else
i++; i++;
// Apply amplitude modification. // Apply amplitude modification.
amplitude += amplitude_inc; amplitude += amplitude_inc;
if (amplitude <= 0) { if (amplitude <= 0) {
remove = true; sample->marked_for_removal = true;
break; break;
} else if (amplitude > max_amplitude) { } else if (amplitude > max_amplitude) {
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. // Advance source index.
if (src_index >= num_samples) { if (src_index >= num_samples) {
if (!sound->is_streaming_sound()) { if (!sound->is_streaming_sound()) {
if (flags & AudioSample::kLoop) { src_index %= num_samples;
src_index %= num_samples;
} else { if (!(flags & AudioSample::kLoop)) {
remove = true; sample->marked_for_removal = true;
break; 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()) { if (sound->eof()) {
remove = true; sample->marked_for_removal = true;
break; break;
} }
src_index = 0; sample->streaming_in_progress.store(true,
std::memory_order_relaxed);
// Swap buffers and start streaming in background. // Swap buffers and start streaming in background.
sound->SwapBuffers(); sound->SwapBuffers();
src[0] = const_cast<const Sound*>(sound)->GetBuffer(0); src[0] = sound->GetBuffer(0);
src[1] = const_cast<const Sound*>(sound)->GetBuffer(1); 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, Worker::Get().EnqueueTask(HERE,
flags & AudioSample::kLoop)); std::bind(&AudioBase::DoStream, this, *it,
} else { flags & AudioSample::kLoop));
LOG << "Buffer underrun!"; } else if (num_samples) {
src_index = 0; DLOG << "Buffer underrun!";
src_index %= num_samples;
} }
} }
} }
@ -125,9 +137,12 @@ void AudioBase::RenderAudio(float* output_buffer, size_t num_frames) {
sample->amplitude = amplitude; sample->amplitude = amplitude;
} }
if (remove) { if (sample->marked_for_removal &&
task_runner_.Enqueue(sample->end_cb); (!sound->is_streaming_sound() ||
sample->active = false; !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); it = samples_[1].erase(it);
} else { } else {
++it; ++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 } // namespace eng

View File

@ -3,37 +3,47 @@
#include <list> #include <list>
#include <memory> #include <memory>
#include <mutex>
#include "../../base/closure.h" #include "../../base/closure.h"
#include "../../base/task_runner.h" #include "../../base/spinlock.h"
#include "../../base/worker.h"
#include "audio_sample.h" #include "audio_sample.h"
namespace base {
class TaskRunner;
}
namespace eng { namespace eng {
class Sound; class Sound;
class AudioBase { class AudioBase {
public: 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: protected:
static constexpr int kChannelCount = 2; 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();
~AudioBase(); ~AudioBase();
void RenderAudio(float* output_buffer, size_t num_frames); 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 } // namespace eng

View File

@ -20,6 +20,16 @@ bool AudioOboe::Initialize() {
void AudioOboe::Shutdown() { void AudioOboe::Shutdown() {
LOG << "Shutting down audio system."; LOG << "Shutting down audio system.";
stream_->stop();
}
void AudioOboe::Suspend() {
stream_->pause();
}
void AudioOboe::Resume() {
stream_->start();
} }
size_t AudioOboe::GetSampleRate() { size_t AudioOboe::GetSampleRate() {

View File

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

View File

@ -5,64 +5,84 @@
#include "audio.h" #include "audio.h"
#include "audio_sample.h" #include "audio_sample.h"
using namespace base;
namespace eng { namespace eng {
AudioResource::AudioResource(Audio* audio) AudioResource::AudioResource(Audio* audio)
: sample_(std::make_shared<AudioSample>()), audio_(audio) {} : sample_(std::make_shared<AudioSample>()), audio_(audio) {}
AudioResource::~AudioResource() { AudioResource::~AudioResource() {
sample_->flags |= AudioSample::kStopped; sample_->flags.fetch_or(AudioSample::kStopped, std::memory_order_relaxed);
} }
void AudioResource::Play(std::shared_ptr<Sound> sound, void AudioResource::Play(std::shared_ptr<Sound> sound,
float amplitude, float amplitude,
bool reset_pos) { 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; return;
}
if (reset_pos) { if (reset_pos) {
sample_->src_index = 0; sample->src_index = 0;
sample_->accumulator = 0; sample->accumulator = 0;
sound->ResetStream();
} }
sample_->flags &= ~AudioSample::kStopped;
sample_->sound = sound; sample->active = true;
sample_->amplitude = amplitude; sample_->flags.fetch_and(~AudioSample::kStopped, std::memory_order_relaxed);
sample_->active = true; sample->sound = sound;
if (amplitude >= 0)
sample->amplitude = amplitude;
audio_->Play(sample_); audio_->Play(sample_);
} }
void AudioResource::Stop() { void AudioResource::Stop() {
if (!sample_->active) if (sample_->active)
return; sample_->flags.fetch_or(AudioSample::kStopped, std::memory_order_relaxed);
sample_->flags |= AudioSample::kStopped;
} }
void AudioResource::SetLoop(bool loop) { void AudioResource::SetLoop(bool loop) {
if (loop) if (loop)
sample_->flags |= AudioSample::kLoop; sample_->flags.fetch_or(AudioSample::kLoop, std::memory_order_relaxed);
else else
sample_->flags &= ~AudioSample::kLoop; sample_->flags.fetch_and(AudioSample::kLoop, std::memory_order_relaxed);
} }
void AudioResource::SetSimulateStereo(bool simulate) { void AudioResource::SetSimulateStereo(bool simulate) {
if (simulate) if (simulate)
sample_->flags |= AudioSample::kSimulateStereo; sample_->flags.fetch_or(AudioSample::kSimulateStereo,
std::memory_order_relaxed);
else else
sample_->flags &= ~AudioSample::kSimulateStereo; sample_->flags.fetch_and(AudioSample::kSimulateStereo,
std::memory_order_relaxed);
} }
void AudioResource::SetResampleStep(size_t step) { 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) { 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) { 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) { void AudioResource::SetEndCallback(base::Closure cb) {

View File

@ -1,6 +1,7 @@
#ifndef AUDIO_SAMPLE_H #ifndef AUDIO_SAMPLE_H
#define AUDIO_SAMPLE_H #define AUDIO_SAMPLE_H
#include <atomic>
#include <memory> #include <memory>
#include "../../base/closure.h" #include "../../base/closure.h"
@ -12,19 +13,27 @@ class Sound;
struct AudioSample { struct AudioSample {
enum SampleFlags { kLoop = 1, kStopped = 2, kSimulateStereo = 4 }; enum SampleFlags { kLoop = 1, kStopped = 2, kSimulateStereo = 4 };
// Read-only accessed by the audio thread. // Accessed by main thread only.
std::shared_ptr<Sound> sound; bool active = false;
unsigned flags = 0;
size_t step = 10;
float amplitude_inc = 0;
float max_amplitude = 1.0f;
base::Closure end_cb; 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 src_index = 0;
size_t accumulator = 0; size_t accumulator = 0;
float amplitude = 1.0f; 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 } // 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 "engine.h"
#include "../base/log.h" #include "../base/log.h"
#include "../base/worker.h"
#include "../third_party/texture_compressor/texture_compressor.h" #include "../third_party/texture_compressor/texture_compressor.h"
#include "animator.h"
#include "audio/audio.h" #include "audio/audio.h"
#include "audio/audio_resource.h" #include "audio/audio_resource.h"
#include "drawable.h"
#include "font.h" #include "font.h"
#include "game.h" #include "game.h"
#include "game_factory.h" #include "game_factory.h"
#include "image.h" #include "image.h"
#include "image_quad.h"
#include "input_event.h" #include "input_event.h"
#include "mesh.h" #include "mesh.h"
#include "platform/platform.h" #include "platform/platform.h"
@ -27,7 +29,7 @@ Engine* Engine::singleton = nullptr;
Engine::Engine(Platform* platform, Renderer* renderer, Audio* audio) Engine::Engine(Platform* platform, Renderer* renderer, Audio* audio)
: platform_(platform), renderer_(renderer), audio_(audio) { : platform_(platform), renderer_(renderer), audio_(audio) {
assert(!singleton); DCHECK(!singleton);
singleton = this; singleton = this;
renderer_->SetContextLostCB(std::bind(&Engine::ContextLost, this)); renderer_->SetContextLostCB(std::bind(&Engine::ContextLost, this));
@ -35,9 +37,15 @@ Engine::Engine(Platform* platform, Renderer* renderer, Audio* audio)
quad_ = CreateRenderResource<Geometry>(); quad_ = CreateRenderResource<Geometry>();
pass_through_shader_ = CreateRenderResource<Shader>(); pass_through_shader_ = CreateRenderResource<Shader>();
solid_shader_ = CreateRenderResource<Shader>(); solid_shader_ = CreateRenderResource<Shader>();
stats_ = std::make_unique<ImageQuad>();
stats_->SetZOrder(std::numeric_limits<int>::max());
} }
Engine::~Engine() { Engine::~Engine() {
game_.reset();
stats_.reset();
singleton = nullptr; singleton = nullptr;
} }
@ -83,6 +91,8 @@ bool Engine::Initialize() {
if (!CreateRenderResources()) if (!CreateRenderResources())
return false; return false;
SetImageSource("stats_tex", std::bind(&Engine::PrintStats, this));
game_ = GameFactoryBase::CreateGame(""); game_ = GameFactoryBase::CreateGame("");
if (!game_) { if (!game_) {
printf("No game found to run.\n"); printf("No game found to run.\n");
@ -104,45 +114,66 @@ void Engine::Shutdown() {
void Engine::Update(float delta_time) { void Engine::Update(float delta_time) {
seconds_accumulated_ += delta_time; seconds_accumulated_ += delta_time;
audio_->Update();
renderer_->Update();
game_->Update(delta_time); 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; fps_seconds_ += delta_time;
if (fps_seconds_ >= 1) { if (fps_seconds_ >= 1) {
fps_ = renderer_->GetAndResetFPS(); fps_ = renderer_->GetAndResetFPS();
fps_seconds_ = 0; fps_seconds_ = 0;
} }
if (stats_.IsVisible()) if (stats_->IsVisible()) {
PrintStats(); RefreshImage("stats_tex");
stats_->AutoScale();
}
} }
void Engine::Draw(float frame_frac) { void Engine::Draw(float frame_frac) {
auto cmd = std::make_unique<CmdClear>(); drawables_.sort(
cmd->rgba = {0, 0, 0, 1}; [](auto& a, auto& b) { return a->GetZOrder() < b->GetZOrder(); });
renderer_->EnqueueCommand(std::move(cmd));
renderer_->EnqueueCommand(std::make_unique<CmdEableBlend>());
game_->Draw(frame_frac); for (auto d : drawables_) {
if (d->IsVisible())
if (stats_.IsVisible()) d->Draw(frame_frac);
stats_.Draw(); }
renderer_->EnqueueCommand(std::make_unique<CmdPresent>()); renderer_->EnqueueCommand(std::make_unique<CmdPresent>());
} }
void Engine::LostFocus() { void Engine::LostFocus() {
audio_->Suspend();
if (game_) if (game_)
game_->LostFocus(); game_->LostFocus();
} }
void Engine::GainedFocus() { void Engine::GainedFocus() {
audio_->Resume();
if (game_) if (game_)
game_->GainedFocus(); 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() { void Engine::Exit() {
platform_->Exit(); platform_->Exit();
} }
@ -156,32 +187,117 @@ Vector2 Engine::ToPosition(const Vector2& vec) {
return ToScale(vec) - GetScreenSize() / 2.0f; return ToScale(vec) - GetScreenSize() / 2.0f;
} }
std::shared_ptr<AudioResource> Engine::CreateAudioResource() { void Engine::SetImageSource(const std::string& asset_name,
return std::make_shared<AudioResource>(audio_); 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) { void Engine::AddInputEvent(std::unique_ptr<InputEvent> event) {
switch (event->GetType()) { switch (event->GetType()) {
case InputEvent::kTap: case InputEvent::kDragEnd:
if (((GetScreenSize() / 2) * 0.9f - event->GetVector(0)).Magnitude() <= if (((GetScreenSize() / 2) * 0.9f - event->GetVector(0)).Magnitude() <=
0.25f) { 0.25f) {
SetSatsVisible(!stats_.IsVisible()); SetSatsVisible(!stats_->IsVisible());
// Consume event. // TODO: Enqueue DragCancel so we can consume this event.
return;
} }
break; break;
case InputEvent::kKeyPress: case InputEvent::kKeyPress:
if (event->GetKeyPress() == 's') { if (event->GetKeyPress() == 's') {
SetSatsVisible(!stats_.IsVisible()); SetSatsVisible(!stats_->IsVisible());
// Consume event. // Consume event.
return; return;
} }
break; break;
case InputEvent::kDrag: case InputEvent::kDrag:
if (stats_.IsVisible()) { if (stats_->IsVisible()) {
if ((stats_.GetOffset() - event->GetVector(0)).Magnitude() <= if ((stats_->GetOffset() - event->GetVector(0)).Magnitude() <=
stats_.GetScale().y) stats_->GetScale().y)
stats_.SetOffset(event->GetVector(0)); stats_->SetOffset(event->GetVector(0));
// TODO: Enqueue DragCancel so we can consume this event. // TODO: Enqueue DragCancel so we can consume this event.
} }
break; break;
@ -201,6 +317,15 @@ std::unique_ptr<InputEvent> Engine::GetNextInputEvent() {
return event; 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) { TextureCompressor* Engine::GetTextureCompressor(bool opacity) {
return opacity ? tex_comp_alpha_.get() : tex_comp_opaque_.get(); return opacity ? tex_comp_alpha_.get() : tex_comp_opaque_.get();
} }
@ -229,7 +354,7 @@ bool Engine::IsMobile() const {
return platform_->mobile_device(); return platform_->mobile_device();
} }
std::shared_ptr<RenderResource> Engine::CreateRenderResourceInternal( std::unique_ptr<RenderResource> Engine::CreateRenderResourceInternal(
RenderResourceFactoryBase& factory) { RenderResourceFactoryBase& factory) {
return renderer_->CreateResource(factory); return renderer_->CreateResource(factory);
} }
@ -237,6 +362,9 @@ std::shared_ptr<RenderResource> Engine::CreateRenderResourceInternal(
void Engine::ContextLost() { void Engine::ContextLost() {
CreateRenderResources(); CreateRenderResources();
for (auto& t : textures_)
RefreshImage(t.first);
game_->ContextLost(); game_->ContextLost();
} }
@ -269,14 +397,17 @@ bool Engine::CreateRenderResources() {
} }
void Engine::SetSatsVisible(bool visible) { void Engine::SetSatsVisible(bool visible) {
stats_.SetVisible(visible); stats_->SetVisible(visible);
if (visible) if (visible)
stats_.Create(CreateRenderResource<Texture>()); stats_->Create("stats_tex");
else else
stats_.Destory(); stats_->Destory();
} }
void Engine::PrintStats() { std::unique_ptr<Image> Engine::PrintStats() {
if (!stats_->IsVisible())
return nullptr;
constexpr int width = 200; constexpr int width = 200;
std::vector<std::string> lines; std::vector<std::string> lines;
std::string line; std::string line;
@ -297,18 +428,14 @@ void Engine::PrintStats() {
image->Create(image_width, image_height); image->Create(image_width, image_height);
image->Clear({1, 1, 1, 0.08f}); image->Clear({1, 1, 1, 0.08f});
Worker worker(2);
int y = margin; int y = margin;
for (auto& text : lines) { for (auto& text : lines) {
worker.Enqueue(std::bind(&Font::Print, system_font_.get(), margin, y, system_font_->Print(margin, y, text.c_str(), image->GetBuffer(),
text.c_str(), image->GetBuffer(), image->GetWidth());
image->GetWidth()));
y += line_height + margin; y += line_height + margin;
} }
worker.Join();
stats_.GetTexture()->Update(std::move(image)); return image;
stats_.AutoScale();
} }
} // namespace eng } // namespace eng

View File

@ -2,13 +2,15 @@
#define ENGINE_H #define ENGINE_H
#include <deque> #include <deque>
#include <functional>
#include <list>
#include <memory> #include <memory>
#include <unordered_map> #include <unordered_map>
#include "../base/random.h" #include "../base/random.h"
#include "../base/vecmath.h" #include "../base/vecmath.h"
#include "audio/audio_forward.h" #include "audio/audio_forward.h"
#include "image_quad.h" #include "platform/platform_forward.h"
#include "renderer/render_resource.h" #include "renderer/render_resource.h"
class TextureCompressor; class TextureCompressor;
@ -18,15 +20,19 @@ namespace eng {
class AudioResource; class AudioResource;
class Font; class Font;
class Game; class Game;
class Drawable;
class InputEvent; class InputEvent;
class Image;
class ImageQuad;
class Renderer; class Renderer;
struct RenderCommand;
class Platform;
class Geometry; class Geometry;
class Shader; class Shader;
class Texture;
class Engine { class Engine {
public: public:
using CreateImageCB = std::function<std::unique_ptr<Image>()>;
Engine(Platform* platform, Renderer* renderer, Audio* audio); Engine(Platform* platform, Renderer* renderer, Audio* audio);
~Engine(); ~Engine();
@ -42,6 +48,9 @@ class Engine {
void LostFocus(); void LostFocus();
void GainedFocus(); void GainedFocus();
void AddDrawable(Drawable* drawable);
void RemoveDrawable(Drawable* drawable);
void Exit(); void Exit();
// Convert size from pixels to viewport scale. // Convert size from pixels to viewport scale.
@ -51,22 +60,42 @@ class Engine {
base::Vector2 ToPosition(const base::Vector2& vec); base::Vector2 ToPosition(const base::Vector2& vec);
template <typename T> template <typename T>
std::shared_ptr<T> CreateRenderResource() { std::unique_ptr<T> CreateRenderResource() {
RenderResourceFactory<T> factory; 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); void AddInputEvent(std::unique_ptr<InputEvent> event);
std::unique_ptr<InputEvent> GetNextInputEvent(); 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. // Access to the render resources.
std::shared_ptr<Geometry> GetQuad() { return quad_; } Geometry* GetQuad() { return quad_.get(); }
std::shared_ptr<Shader> GetPassThroughShader() { Shader* GetPassThroughShader() { return pass_through_shader_.get(); }
return pass_through_shader_; Shader* GetSolidShader() { return solid_shader_.get(); }
}
std::shared_ptr<Shader> GetSolidShader() { return solid_shader_; }
const Font* GetSystemFont() { return system_font_.get(); } const Font* GetSystemFont() { return system_font_.get(); }
@ -95,7 +124,19 @@ class Engine {
float seconds_accumulated() const { return seconds_accumulated_; } float seconds_accumulated() const { return seconds_accumulated_; }
float image_dpi() const { return image_dpi_; }
private: 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; static Engine* singleton;
Platform* platform_ = nullptr; Platform* platform_ = nullptr;
@ -106,9 +147,9 @@ class Engine {
std::unique_ptr<Game> game_; std::unique_ptr<Game> game_;
std::shared_ptr<Geometry> quad_; std::unique_ptr<Geometry> quad_;
std::shared_ptr<Shader> pass_through_shader_; std::unique_ptr<Shader> pass_through_shader_;
std::shared_ptr<Shader> solid_shader_; std::unique_ptr<Shader> solid_shader_;
base::Vector2 screen_size_ = {0, 0}; base::Vector2 screen_size_ = {0, 0};
base::Matrix4x4 projection_; base::Matrix4x4 projection_;
@ -118,18 +159,27 @@ class Engine {
std::unique_ptr<TextureCompressor> tex_comp_opaque_; std::unique_ptr<TextureCompressor> tex_comp_opaque_;
std::unique_ptr<TextureCompressor> tex_comp_alpha_; 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; float fps_seconds_ = 0;
int fps_ = 0; int fps_ = 0;
float seconds_accumulated_ = 0.0f; float seconds_accumulated_ = 0.0f;
float image_dpi_ = 200;
bool vibration_enabled_ = true;
std::deque<std::unique_ptr<InputEvent>> input_queue_; std::deque<std::unique_ptr<InputEvent>> input_queue_;
base::Random random_; base::Random random_;
std::shared_ptr<RenderResource> CreateRenderResourceInternal( std::unique_ptr<RenderResource> CreateRenderResourceInternal(
RenderResourceFactoryBase& factory); RenderResourceFactoryBase& factory);
void ContextLost(); void ContextLost();
@ -137,7 +187,7 @@ class Engine {
bool CreateRenderResources(); bool CreateRenderResources();
void SetSatsVisible(bool visible); void SetSatsVisible(bool visible);
void PrintStats(); std::unique_ptr<Image> PrintStats();
Engine(const Engine&) = delete; Engine(const Engine&) = delete;
Engine& operator=(const Engine&) = delete; Engine& operator=(const Engine&) = delete;

View File

@ -1,5 +1,8 @@
#include "font.h" #include "font.h"
#include <codecvt>
#include <locale>
#include "../base/log.h" #include "../base/log.h"
#include "engine.h" #include "engine.h"
#include "platform/asset_file.h" #include "platform/asset_file.h"
@ -123,9 +126,12 @@ void Font::CalculateBoundingBox(const std::string& text,
float x = 0, y = 0; 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) { while (*ptr) {
if (*ptr >= kFirstChar /*&& *ptr < (kFirstChar + kNumChars)*/) { if (*ptr >= kFirstChar && *ptr < (kFirstChar + kNumChars)) {
stbtt_aligned_quad q; stbtt_aligned_quad q;
stbtt_GetBakedQuad(glyph_info_, kGlyphSize, kGlyphSize, *ptr - kFirstChar, stbtt_GetBakedQuad(glyph_info_, kGlyphSize, kGlyphSize, *ptr - kFirstChar,
&x, &y, &q, 1); &x, &y, &q, 1);
@ -140,9 +146,8 @@ void Font::CalculateBoundingBox(const std::string& text,
x1 = ix1; x1 = ix1;
if (iy1 > y1) if (iy1 > y1)
y1 = iy1; y1 = iy1;
++ptr;
} }
++ptr;
} }
} }
@ -168,9 +173,12 @@ void Font::Print(int x,
float fx = (float)x, fy = (float)y + (float)yoff_; 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) { while (*ptr) {
if (*ptr >= kFirstChar /*&& *ptr < (kFirstChar + kNumChars)*/) { if (*ptr >= kFirstChar && *ptr < (kFirstChar + kNumChars)) {
stbtt_aligned_quad q; stbtt_aligned_quad q;
stbtt_GetBakedQuad(glyph_info_, kGlyphSize, kGlyphSize, *ptr - kFirstChar, stbtt_GetBakedQuad(glyph_info_, kGlyphSize, kGlyphSize, *ptr - kFirstChar,
&fx, &fy, &q, 1); &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, StretchBlit_I8_to_RGBA32(ix0, iy0, ix1, iy1, iu0, iv0, iu1, iv1, buffer,
width, glyph_cache_.get(), kGlyphSize); width, glyph_cache_.get(), kGlyphSize);
++ptr;
} }
++ptr;
} }
} }

View File

@ -39,7 +39,7 @@ class Font {
enum Constants { enum Constants {
kGlyphSize = 512, kGlyphSize = 512,
kFirstChar = 32, // ' ' (space) 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. 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 Update(float delta_time) = 0;
virtual void Draw(float frame_frac) = 0;
virtual void ContextLost() = 0; virtual void ContextLost() = 0;
virtual void LostFocus() = 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); void GradientV(const base::Vector4& c1, const base::Vector4& c2, int height);
private: private:
base::AlignedMem<uint8_t[]>::ScoppedPtr buffer_; base::AlignedMemPtr<uint8_t[]> buffer_;
int width_ = 0; int width_ = 0;
int height_ = 0; int height_ = 0;
Format format_ = kRGBA32; Format format_ = kRGBA32;
std::string name_;
}; };
} // namespace eng } // namespace eng

View File

@ -1,7 +1,6 @@
#include "image_quad.h" #include "image_quad.h"
#include <cassert> #include "../base/log.h"
#include "engine.h" #include "engine.h"
#include "renderer/geometry.h" #include "renderer/geometry.h"
#include "renderer/shader.h" #include "renderer/shader.h"
@ -11,14 +10,20 @@ using namespace base;
namespace eng { namespace eng {
void ImageQuad::Create(std::shared_ptr<Texture> texture, void ImageQuad::Create(const std::string& asset_name,
std::array<int, 2> num_frames, std::array<int, 2> num_frames,
int frame_width, int frame_width,
int frame_height) { int frame_height) {
texture_ = texture; texture_ = Engine::Get().GetTexture(asset_name);
num_frames_ = std::move(num_frames); num_frames_ = std::move(num_frames);
frame_width_ = frame_width; frame_width_ = frame_width;
frame_height_ = frame_height; frame_height_ = frame_height;
if ((frame_width_ > 0 && frame_height_ > 0) || texture_->IsValid())
AutoScale();
asset_name_ = asset_name;
} }
void ImageQuad::Destory() { void ImageQuad::Destory() {
@ -26,13 +31,15 @@ void ImageQuad::Destory() {
} }
void ImageQuad::AutoScale() { void ImageQuad::AutoScale() {
auto& engine = Engine::Get();
Vector2 dimensions = {GetFrameWidth(), GetFrameHeight()}; Vector2 dimensions = {GetFrameWidth(), GetFrameHeight()};
SetScale(Engine::Get().ToScale(dimensions)); SetScale(engine.ToScale(dimensions));
Scale((float)Engine::Get().GetDeviceDpi() / 200.0f); Scale((float)engine.GetDeviceDpi() / engine.image_dpi());
} }
void ImageQuad::SetFrame(size_t frame) { void ImageQuad::SetFrame(size_t frame) {
assert(frame < GetNumFrames()); DCHECK(frame < GetNumFrames())
<< "asset: " << asset_name_ << " frame: " << frame;
current_frame_ = frame; current_frame_ = frame;
} }
@ -40,8 +47,10 @@ size_t ImageQuad::GetNumFrames() const {
return num_frames_[0] * num_frames_[1]; return num_frames_[0] * num_frames_[1];
} }
void ImageQuad::Draw() { void ImageQuad::Draw(float frame_frac) {
if (!IsVisible() || !texture_ || !texture_->IsValid()) DCHECK(IsVisible());
if (!texture_ || !texture_->IsValid())
return; return;
texture_->Activate(); texture_->Activate();
@ -49,8 +58,7 @@ void ImageQuad::Draw() {
Vector2 tex_scale = {GetFrameWidth() / texture_->GetWidth(), Vector2 tex_scale = {GetFrameWidth() / texture_->GetWidth(),
GetFrameHeight() / texture_->GetHeight()}; GetFrameHeight() / texture_->GetHeight()};
std::shared_ptr<Geometry> quad = Engine::Get().GetQuad(); Shader* shader = Engine::Get().GetPassThroughShader();
std::shared_ptr<Shader> shader = Engine::Get().GetPassThroughShader();
shader->Activate(); shader->Activate();
shader->SetUniform("offset", offset_); shader->SetUniform("offset", offset_);
@ -63,7 +71,7 @@ void ImageQuad::Draw() {
shader->SetUniform("color", color_); shader->SetUniform("color", color_);
shader->SetUniform("texture", 0); shader->SetUniform("texture", 0);
quad->Draw(); Engine::Get().GetQuad()->Draw();
} }
float ImageQuad::GetFrameWidth() const { float ImageQuad::GetFrameWidth() const {
@ -78,9 +86,8 @@ float ImageQuad::GetFrameHeight() const {
// Return the uv offset for the given frame. // Return the uv offset for the given frame.
Vector2 ImageQuad::GetUVOffset(int frame) const { Vector2 ImageQuad::GetUVOffset(int frame) const {
assert(frame < num_frames_[0] * num_frames_[1]); DCHECK(frame < GetNumFrames())
if (num_frames_[0] == 1 && num_frames_[1] == 1) << "asset: " << asset_name_ << " frame: " << frame;
return {0, 0};
return {(float)(frame % num_frames_[0]), (float)(frame / num_frames_[0])}; return {(float)(frame % num_frames_[0]), (float)(frame / num_frames_[0])};
} }

View File

@ -6,6 +6,7 @@
#include <array> #include <array>
#include <memory> #include <memory>
#include <string>
namespace eng { namespace eng {
@ -16,7 +17,7 @@ class ImageQuad : public Animatable {
ImageQuad() = default; ImageQuad() = default;
~ImageQuad() override = 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}, std::array<int, 2> num_frames = {1, 1},
int frame_width = 0, int frame_width = 0,
int frame_height = 0); int frame_height = 0);
@ -32,9 +33,8 @@ class ImageQuad : public Animatable {
void SetColor(const base::Vector4& color) override { color_ = color; } void SetColor(const base::Vector4& color) override { color_ = color; }
base::Vector4 GetColor() const override { return color_; } base::Vector4 GetColor() const override { return color_; }
void Draw(); // Drawable interface.
void Draw(float frame_frac) override;
std::shared_ptr<Texture> GetTexture() { return texture_; }
private: private:
std::shared_ptr<Texture> texture_; std::shared_ptr<Texture> texture_;
@ -46,6 +46,8 @@ class ImageQuad : public Animatable {
base::Vector4 color_ = {1, 1, 1, 1}; base::Vector4 color_ = {1, 1, 1, 1};
std::string asset_name_;
float GetFrameWidth() const; float GetFrameWidth() const;
float GetFrameHeight() const; float GetFrameHeight() const;

View File

@ -1,7 +1,7 @@
#ifndef INPUT_EVENT_H #ifndef INPUT_EVENT_H
#define INPUT_EVENT_H #define INPUT_EVENT_H
#include <cassert> #include "../base/log.h"
#include "../base/vecmath.h" #include "../base/vecmath.h"
namespace eng { namespace eng {
@ -10,37 +10,38 @@ class InputEvent {
public: public:
enum Type { enum Type {
kInvalid, kInvalid,
kTap,
kDoubleTap,
kDragStart, kDragStart,
kDrag, kDrag,
kDragEnd, kDragEnd,
kDragCancel, kDragCancel,
kPinchStart,
kPinch,
kNavigateBack, kNavigateBack,
kKeyPress, kKeyPress,
kType_Max // Not a type. 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) : 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(Type type, char key) : type_(type), key_(key) {}
~InputEvent() = default; ~InputEvent() = default;
Type GetType() { return type_; } Type GetType() const { return type_; }
base::Vector2 GetVector(size_t i) {
assert(i < 2); size_t GetPointerId() const { return pointer_id_; }
return vec_[i];
base::Vector2 GetVector(size_t i) const {
DCHECK(i < 2);
return vec_;
} }
char GetKeyPress() { return key_; }
char GetKeyPress() const { return key_; }
private: private:
Type type_ = kInvalid; Type type_ = kInvalid;
base::Vector2 vec_[2] = {{0, 0}, {0, 0}}; size_t pointer_id_ = 0;
base::Vector2 vec_ = {0, 0};
char key_ = 0; char key_ = 0;
}; };

View File

@ -1,7 +1,6 @@
#include "mesh.h" #include "mesh.h"
#include <string.h> #include <string.h>
#include <cassert>
#include "../base/log.h" #include "../base/log.h"
#include "../third_party/jsoncpp/json.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(); *((unsigned short*)dst) = (unsigned short)vertices[i].asUInt();
break; break;
default: default:
assert(false); NOTREACHED << "- Unknown data type: " << data_type;
return false;
} }
dst += type_size; dst += type_size;
++i; ++i;

View File

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

View File

@ -1,88 +1,20 @@
#ifndef PLATFORM_H #ifndef PLATFORM_H
#define PLATFORM_H #define PLATFORM_H
#include <exception>
#include <memory>
#include <string>
#include "../../base/timer.h"
#include "../audio/audio_forward.h"
#if defined(__ANDROID__) #if defined(__ANDROID__)
struct android_app; #include "platform_android.h"
struct AInputEvent; #elif defined(__linux__)
#include "platform_linux.h"
namespace ndk_helper {
class TapDetector;
class PinchDetector;
class DragDetector;
} // namespace ndk_helper
#endif #endif
namespace eng { namespace eng {
class Renderer;
class Engine;
class Platform {
public:
Platform();
~Platform();
#if defined(__ANDROID__) #if defined(__ANDROID__)
void Initialize(android_app* app); using Platform = PlatformAndroid;
#elif defined(__linux__) #elif defined(__linux__)
void Initialize(); using Platform = PlatformLinux;
#endif #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 } // namespace eng
#endif // PLATFORM_H #endif // PLATFORM_H

View File

@ -1,13 +1,14 @@
#include "platform.h" #include "platform_android.h"
#include <android_native_app_glue.h> #include <android_native_app_glue.h>
#include <unistd.h> #include <unistd.h>
#include <memory>
#include <string> #include <string>
#include "../../base/file.h"
#include "../../base/log.h" #include "../../base/log.h"
#include "../../third_party/android/gestureDetector.h" #include "../../base/task_runner.h"
#include "../audio/audio_oboe.h" #include "../audio/audio.h"
#include "../engine.h" #include "../engine.h"
#include "../input_event.h" #include "../input_event.h"
#include "../renderer/renderer.h" #include "../renderer/renderer.h"
@ -44,6 +45,39 @@ std::string GetApkPath(ANativeActivity* activity) {
return apk_path; 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) { int32_t getDensityDpi(android_app* app) {
AConfiguration* config = AConfiguration_new(); AConfiguration* config = AConfiguration_new();
AConfiguration_fromAssetManager(config, app->activity->assetManager); AConfiguration_fromAssetManager(config, app->activity->assetManager);
@ -56,11 +90,11 @@ int32_t getDensityDpi(android_app* app) {
namespace eng { namespace eng {
Platform::Platform() = default; PlatformAndroid::PlatformAndroid() = default;
Platform::~Platform() = default; PlatformAndroid::~PlatformAndroid() = default;
int32_t Platform::HandleInput(android_app* app, AInputEvent* event) { int32_t PlatformAndroid::HandleInput(android_app* app, AInputEvent* event) {
Platform* platform = reinterpret_cast<Platform*>(app->userData); PlatformAndroid* platform = reinterpret_cast<PlatformAndroid*>(app->userData);
if (!platform->engine_) if (!platform->engine_)
return 0; return 0;
@ -73,88 +107,74 @@ int32_t Platform::HandleInput(android_app* app, AInputEvent* event) {
platform->engine_->AddInputEvent(std::move(input_event)); platform->engine_->AddInputEvent(std::move(input_event));
} }
return 1; return 1;
} } else if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) {
int32_t action = AMotionEvent_getAction(event);
if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) { int32_t index = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >>
ndk_helper::GESTURE_STATE tap_state = AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
platform->tap_detector_->Detect(event); uint32_t flags = action & AMOTION_EVENT_ACTION_MASK;
ndk_helper::GESTURE_STATE drag_state = int32_t count = AMotionEvent_getPointerCount(event);
platform->drag_detector_->Detect(event); int32_t pointer_id = AMotionEvent_getPointerId(event, index);
ndk_helper::GESTURE_STATE pinch_state = Vector2 pos[2] = {platform->pointer_pos_[0], platform->pointer_pos_[1]};
platform->pinch_detector_->Detect(event); for (auto i = 0; i < count; ++i) {
int32_t id = AMotionEvent_getPointerId(event, i);
// Tap detector has a priority over other detectors pos[id] = {AMotionEvent_getX(event, i), AMotionEvent_getY(event, i)};
if (tap_state == ndk_helper::GESTURE_STATE_ACTION) { pos[id] = platform->engine_->ToPosition(pos[id]);
platform->engine_->AddInputEvent( }
std::make_unique<InputEvent>(InputEvent::kDragCancel));
// Detect tap if (pointer_id >= 2)
Vector2 v; return 0;
platform->tap_detector_->GetPointer(v);
v = platform->engine_->ToPosition(v); std::unique_ptr<InputEvent> input_event;
// DLOG << "Tap: " << v;
auto input_event = switch (flags) {
std::make_unique<InputEvent>(InputEvent::kTap, v * Vector2(1, -1)); case AMOTION_EVENT_ACTION_DOWN:
platform->engine_->AddInputEvent(std::move(input_event)); case AMOTION_EVENT_ACTION_POINTER_DOWN:
} else { DLOG << "AMOTION_EVENT_ACTION_DOWN - pointer_id: " << pointer_id;
// Handle drag state platform->pointer_pos_[pointer_id] = pos[pointer_id];
if (drag_state & ndk_helper::GESTURE_STATE_START) { platform->pointer_down_[pointer_id] = true;
// Otherwise, start dragging input_event =
Vector2 v; std::make_unique<InputEvent>(InputEvent::kDragStart, pointer_id,
platform->drag_detector_->GetPointer(v); pos[pointer_id] * Vector2(1, -1));
v = platform->engine_->ToPosition(v); break;
// DLOG << "drag-start: " << v;
auto input_event = std::make_unique<InputEvent>(InputEvent::kDragStart, case AMOTION_EVENT_ACTION_UP:
v * Vector2(1, -1)); case AMOTION_EVENT_ACTION_POINTER_UP:
platform->engine_->AddInputEvent(std::move(input_event)); DLOG << "AMOTION_EVENT_ACTION_UP - pointer_id: " << pointer_id;
} else if (drag_state & ndk_helper::GESTURE_STATE_MOVE) { platform->pointer_pos_[pointer_id] = pos[pointer_id];
Vector2 v; platform->pointer_down_[pointer_id] = false;
platform->drag_detector_->GetPointer(v); input_event = std::make_unique<InputEvent>(
v = platform->engine_->ToPosition(v); InputEvent::kDragEnd, pointer_id, pos[pointer_id] * Vector2(1, -1));
// DLOG << "drag: " << v; break;
auto input_event =
std::make_unique<InputEvent>(InputEvent::kDrag, v * Vector2(1, -1)); case AMOTION_EVENT_ACTION_MOVE:
platform->engine_->AddInputEvent(std::move(input_event)); if (platform->pointer_down_[0] && pos[0] != platform->pointer_pos_[0]) {
} else if (drag_state & ndk_helper::GESTURE_STATE_END) { platform->pointer_pos_[0] = pos[0];
// DLOG << "drag-end!"; input_event = std::make_unique<InputEvent>(InputEvent::kDrag, 0,
auto input_event = std::make_unique<InputEvent>(InputEvent::kDragEnd); pos[0] * Vector2(1, -1));
platform->engine_->AddInputEvent(std::move(input_event)); }
} if (platform->pointer_down_[1] && pos[1] != platform->pointer_pos_[1]) {
platform->pointer_pos_[1] = pos[1];
// Handle pinch state input_event = std::make_unique<InputEvent>(InputEvent::kDrag, 1,
if (pinch_state & ndk_helper::GESTURE_STATE_START) { pos[1] * Vector2(1, -1));
platform->engine_->AddInputEvent( }
std::make_unique<InputEvent>(InputEvent::kDragCancel)); break;
// Start new pinch
Vector2 v1; case AMOTION_EVENT_ACTION_CANCEL:
Vector2 v2; input_event = std::make_unique<InputEvent>(InputEvent::kDragCancel);
platform->pinch_detector_->GetPointers(v1, v2); break;
v1 = platform->engine_->ToPosition(v1); }
v2 = platform->engine_->ToPosition(v2);
// DLOG << "pinch-start: " << v1 << " " << v2; if (input_event) {
auto input_event = std::make_unique<InputEvent>( platform->engine_->AddInputEvent(std::move(input_event));
InputEvent::kPinchStart, v1 * Vector2(1, -1), v2 * Vector2(1, -1)); return 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));
}
} }
return 1;
} }
return 0; return 0;
} }
void Platform::HandleCmd(android_app* app, int32_t cmd) { void PlatformAndroid::HandleCmd(android_app* app, int32_t cmd) {
Platform* platform = reinterpret_cast<Platform*>(app->userData); PlatformAndroid* platform = reinterpret_cast<PlatformAndroid*>(app->userData);
switch (cmd) { switch (cmd) {
case APP_CMD_SAVE_STATE: case APP_CMD_SAVE_STATE:
@ -216,26 +236,11 @@ void Platform::HandleCmd(android_app* app, int32_t cmd) {
} }
} }
void Platform::Initialize(android_app* app) { void PlatformAndroid::Initialize(android_app* app) {
LOG << "Initializing platform."; PlatformBase::Initialize();
app_ = app; 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; mobile_device_ = true;
root_path_ = GetApkPath(app->activity); root_path_ = GetApkPath(app->activity);
@ -245,13 +250,13 @@ void Platform::Initialize(android_app* app) {
LOG << "Device DPI: " << device_dpi_; LOG << "Device DPI: " << device_dpi_;
app->userData = reinterpret_cast<void*>(this); app->userData = reinterpret_cast<void*>(this);
app->onAppCmd = Platform::HandleCmd; app->onAppCmd = PlatformAndroid::HandleCmd;
app->onInputEvent = Platform::HandleInput; app->onInputEvent = PlatformAndroid::HandleInput;
Update(); Update();
} }
void Platform::Update() { void PlatformAndroid::Update() {
int id; int id;
int events; int events;
android_poll_source* source; android_poll_source* source;
@ -270,19 +275,23 @@ void Platform::Update() {
} }
} }
void Platform::Exit() { void PlatformAndroid::Exit() {
ANativeActivity_finish(app_->activity); ANativeActivity_finish(app_->activity);
} }
void PlatformAndroid::Vibrate(int duration) {
::Vibrate(app_->activity, duration);
}
} // namespace eng } // namespace eng
void android_main(android_app* app) { void android_main(android_app* app) {
eng::Platform platform; eng::PlatformAndroid platform;
try { try {
platform.Initialize(app); platform.Initialize(app);
platform.RunMainLoop(); platform.RunMainLoop();
platform.Shutdown(); platform.Shutdown();
} catch (eng::Platform::InternalError& e) { } catch (eng::PlatformBase::InternalError& e) {
} }
_exit(0); _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 <thread>
#include "../../base/log.h" #include "../../base/log.h"
#include "../../base/task_runner.h"
#include "../audio/audio.h" #include "../audio/audio.h"
#include "../engine.h" #include "../engine.h"
#include "../renderer/renderer.h" #include "../renderer/renderer.h"
@ -10,18 +11,41 @@
// Save battery on mobile devices. // Save battery on mobile devices.
#define USE_SLEEP #define USE_SLEEP
using namespace base;
namespace eng { 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."; LOG << "Shutting down platform.";
audio_->Shutdown(); audio_->Shutdown();
renderer_->Shutdown(); renderer_->Shutdown();
} }
void Platform::RunMainLoop() { void PlatformBase::RunMainLoop() {
engine_ = std::make_unique<Engine>(this, renderer_.get(), audio_.get()); engine_ = std::make_unique<Engine>(static_cast<Platform*>(this),
renderer_.get(), audio_.get());
if (!engine_->Initialize()) { if (!engine_->Initialize()) {
LOG << "Failed to initialize the engine."; LOG << "Failed to initialize the engine.";
throw internal_error; throw internal_error;
@ -59,13 +83,17 @@ void Platform::RunMainLoop() {
// Subdivide the frame time. // Subdivide the frame time.
while (accumulator >= time_step) { while (accumulator >= time_step) {
Update(); TaskRunner::GetThreadLocalTaskRunner()->SingleConsumerRun();
static_cast<Platform*>(this)->Update();
engine_->Update(time_step);
if (should_exit_) { if (should_exit_) {
worker_.Shutdown();
engine_->Shutdown(); engine_->Shutdown();
engine_.reset(); engine_.reset();
return; return;
} }
engine_->Update(time_step);
accumulator -= 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/Xlib.h>
#include <X11/Xutil.h> #include <X11/Xutil.h>
#include <memory>
#include "../../base/log.h" #include "../../base/log.h"
#include "../../base/task_runner.h"
#include "../../base/vecmath.h" #include "../../base/vecmath.h"
#include "../audio/audio_alsa.h" #include "../audio/audio.h"
#include "../engine.h" #include "../engine.h"
#include "../input_event.h" #include "../input_event.h"
#include "../renderer/renderer.h" #include "../renderer/renderer.h"
@ -14,39 +17,30 @@ using namespace base;
namespace eng { namespace eng {
Platform::Platform() = default; PlatformLinux::PlatformLinux() = default;
Platform::~Platform() = default; PlatformLinux::~PlatformLinux() = default;
void PlatformLinux::Initialize() {
PlatformBase::Initialize();
void Platform::Initialize() {
root_path_ = "../../"; root_path_ = "../../";
LOG << "Root path: " << root_path_.c_str(); 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()) { if (!renderer_->Initialize()) {
LOG << "Failed to initialize renderer."; LOG << "Failed to initialize renderer.";
throw internal_error; throw internal_error;
} }
LOG << "Initialized the renderer.";
Display* display = renderer_->display(); Display* display = renderer_->display();
Window window = renderer_->window(); Window window = renderer_->window();
XSelectInput( XSelectInput(display, window,
display, window, KeyPressMask | Button1MotionMask | ButtonPressMask |
KeyPressMask | Button1MotionMask | ButtonPressMask | ButtonReleaseMask); ButtonReleaseMask | FocusChangeMask);
Atom WM_DELETE_WINDOW = XInternAtom(display, "WM_DELETE_WINDOW", false); Atom WM_DELETE_WINDOW = XInternAtom(display, "WM_DELETE_WINDOW", false);
XSetWMProtocols(display, window, &WM_DELETE_WINDOW, 1); XSetWMProtocols(display, window, &WM_DELETE_WINDOW, 1);
} }
void Platform::Update() { void PlatformLinux::Update() {
if (!engine_)
return;
Display* display = renderer_->display(); Display* display = renderer_->display();
while (XPending(display)) { while (XPending(display)) {
XEvent e; XEvent e;
@ -55,7 +49,7 @@ void Platform::Update() {
case KeyPress: { case KeyPress: {
KeySym key = XLookupKeysym(&e.xkey, 0); KeySym key = XLookupKeysym(&e.xkey, 0);
auto input_event = auto input_event =
std::make_unique<InputEvent>(InputEvent::kKeyPress, key); std::make_unique<InputEvent>(InputEvent::kKeyPress, (char)key);
engine_->AddInputEvent(std::move(input_event)); engine_->AddInputEvent(std::move(input_event));
// TODO: e.xkey.state & (ShiftMask | ControlMask | Mod1Mask | Mod4Mask)) // TODO: e.xkey.state & (ShiftMask | ControlMask | Mod1Mask | Mod4Mask))
break; break;
@ -64,8 +58,8 @@ void Platform::Update() {
Vector2 v(e.xmotion.x, e.xmotion.y); Vector2 v(e.xmotion.x, e.xmotion.y);
v = engine_->ToPosition(v); v = engine_->ToPosition(v);
// DLOG << "drag: " << v; // DLOG << "drag: " << v;
auto input_event = auto input_event = std::make_unique<InputEvent>(InputEvent::kDrag, 0,
std::make_unique<InputEvent>(InputEvent::kDrag, v * Vector2(1, -1)); v * Vector2(1, -1));
engine_->AddInputEvent(std::move(input_event)); engine_->AddInputEvent(std::move(input_event));
break; break;
} }
@ -75,19 +69,30 @@ void Platform::Update() {
v = engine_->ToPosition(v); v = engine_->ToPosition(v);
// DLOG << "drag-start: " << v; // DLOG << "drag-start: " << v;
auto input_event = std::make_unique<InputEvent>( 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)); engine_->AddInputEvent(std::move(input_event));
} }
break; break;
} }
case ButtonRelease: { case ButtonRelease: {
if (e.xbutton.button == 1) { if (e.xbutton.button == 1) {
Vector2 v(e.xbutton.x, e.xbutton.y);
v = engine_->ToPosition(v);
// DLOG << "drag-end!"; // 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)); engine_->AddInputEvent(std::move(input_event));
} }
break; break;
} }
case FocusOut: {
engine_->LostFocus();
break;
}
case FocusIn: {
engine_->GainedFocus();
break;
}
case ClientMessage: { case ClientMessage: {
// WM_DELETE_WINDOW is the only registered type for now. // WM_DELETE_WINDOW is the only registered type for now.
should_exit_ = true; should_exit_ = true;
@ -97,19 +102,19 @@ void Platform::Update() {
} }
} }
void Platform::Exit() { void PlatformLinux::Exit() {
should_exit_ = true; should_exit_ = true;
} }
} // namespace eng } // namespace eng
int main(int argc, char** argv) { int main(int argc, char** argv) {
eng::Platform platform; eng::PlatformLinux platform;
try { try {
platform.Initialize(); platform.Initialize();
platform.RunMainLoop(); platform.RunMainLoop();
platform.Shutdown(); platform.Shutdown();
} catch (eng::Platform::InternalError& e) { } catch (eng::PlatformBase::InternalError& e) {
return -1; return -1;
} }
return 0; 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 "geometry.h"
#include <cassert>
#include "../engine.h" #include "../engine.h"
#include "../mesh.h" #include "../mesh.h"
#include "render_command.h" #include "render_command.h"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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