mirror of https://github.com/auygun/kaliber.git
Update.
- 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:
parent
05466725af
commit
9d92d01be1
|
@ -0,0 +1,7 @@
|
|||
BasedOnStyle: Chromium
|
||||
Standard: Cpp11
|
||||
|
||||
MacroBlockBegin: "^\
|
||||
RENDER_COMMAND_BEGIN$"
|
||||
MacroBlockEnd: "^\
|
||||
RENDER_COMMAND_END$"
|
|
@ -6,3 +6,4 @@ build/android/build
|
|||
build/linux/gltest_x86_64_debug
|
||||
build/linux/gltest_x86_64_release
|
||||
build/linux/obj
|
||||
out
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
# The location of the build configuration file.
|
||||
buildconfig = "//build/gn/BUILDCONFIG.gn"
|
|
@ -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
|
40
README.md
40
README.md
|
@ -1,17 +1,27 @@
|
|||
A simple, cross-platform, multi-threaded 2D game engine with OpenGL renderer written in modern
|
||||
C++. Supports Linux and Android platforms.
|
||||
|
||||
Build for Linux (gcc or clang):
|
||||
|
||||
cd build/linux
|
||||
A simple, cross-platform 2D game engine with OpenGL renderer. Supports Linux and
|
||||
Android (lolipop+) platforms.
|
||||
#### Building the demo
|
||||
Linux:
|
||||
```text
|
||||
cd build/linux
|
||||
make
|
||||
|
||||
Build for Android:
|
||||
|
||||
cd build/android
|
||||
```
|
||||
Android:
|
||||
```text
|
||||
cd build/android
|
||||
./gradlew :app:assembleRelease
|
||||
|
||||
Build for Android and install (debug):
|
||||
|
||||
cd build/android
|
||||
./gradlew :app:installDebug
|
||||
```
|
||||
GN (linux only for now):
|
||||
```text
|
||||
gn gen --args='is_debug=false' out/release
|
||||
ninja -C out/release
|
||||
```
|
||||
#### Third-party libraries:
|
||||
[glew](https://github.com/nigels-com/glew),
|
||||
[jsoncpp](https://github.com/open-source-parsers/jsoncpp),
|
||||
[minimp3](https://github.com/lieff/minimp3),
|
||||
[oboe](https://github.com/google/oboe),
|
||||
[r8brain-free-src](https://github.com/avaneev/r8brain-free-src),
|
||||
[stb](https://github.com/nothings/stb),
|
||||
[texture-compressor](https://github.com/auygun/kaliber/tree/master/src/third_party/texture_compressor),
|
||||
[minizip](https://github.com/madler/zlib/tree/master/contrib/minizip)
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
[![Build Status](https://travis-ci.org/auygun/kaliber.svg?branch=master)](https://travis-ci.org/auygun/kaliber)
|
|
@ -20,9 +20,10 @@ cmake_minimum_required(VERSION 3.4.1)
|
|||
set (OBOE_DIR ../../../src/third_party/oboe)
|
||||
add_subdirectory(${OBOE_DIR} ./oboe-bin)
|
||||
|
||||
|
||||
include(AndroidNdkModules)
|
||||
android_ndk_import_module_cpufeatures()
|
||||
# cpufeatures
|
||||
include_directories(${ANDROID_NDK}/sources/android/cpufeatures)
|
||||
add_library(cpufeatures STATIC
|
||||
${ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c)
|
||||
|
||||
# build native_app_glue as a static lib
|
||||
if (CMAKE_BUILD_TYPE MATCHES Debug)
|
||||
|
@ -66,6 +67,7 @@ add_library(native-activity SHARED
|
|||
../../../src/engine/audio/audio_base.cc
|
||||
../../../src/engine/audio/audio_oboe.cc
|
||||
../../../src/engine/audio/audio_resource.cc
|
||||
../../../src/engine/drawable.cc
|
||||
../../../src/engine/engine.cc
|
||||
../../../src/engine/font.cc
|
||||
../../../src/engine/image_quad.cc
|
||||
|
@ -74,7 +76,7 @@ add_library(native-activity SHARED
|
|||
../../../src/engine/platform/asset_file_android.cc
|
||||
../../../src/engine/platform/asset_file.cc
|
||||
../../../src/engine/platform/platform_android.cc
|
||||
../../../src/engine/platform/platform.cc
|
||||
../../../src/engine/platform/platform_base.cc
|
||||
../../../src/engine/renderer/geometry.cc
|
||||
../../../src/engine/renderer/render_command.cc
|
||||
../../../src/engine/renderer/render_resource.cc
|
||||
|
@ -87,7 +89,6 @@ add_library(native-activity SHARED
|
|||
../../../src/engine/solid_quad.cc
|
||||
../../../src/engine/sound_player.cc
|
||||
../../../src/engine/sound.cc
|
||||
../../../src/third_party/android/gestureDetector.cpp
|
||||
../../../src/third_party/android/gl3stub.c
|
||||
../../../src/third_party/android/GLContext.cpp
|
||||
../../../src/third_party/jsoncpp/jsoncpp.cc
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
android:versionCode="1"
|
||||
android:versionName="1.0">
|
||||
|
||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||
|
||||
<!-- This .apk has no Java code itself, so set hasCode to false. -->
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
|
|
|
@ -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" ]
|
||||
}
|
||||
}
|
|
@ -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")
|
|
@ -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}}"
|
||||
}
|
||||
}
|
|
@ -25,7 +25,7 @@ INTERMEDIATE_DIR := $(OUTPUT_DIR)/obj
|
|||
BUILD_DIR := $(INTERMEDIATE_DIR)/$(BUILD)
|
||||
|
||||
ARFLAGS = r
|
||||
LDFLAGS = -lX11 -lGL -lz -pthread -lasound
|
||||
LDFLAGS = -lX11 -lGL -pthread -lasound
|
||||
|
||||
# Always enable debug information.
|
||||
CFLAGS += -g
|
||||
|
@ -36,6 +36,7 @@ CFLAGS += -MD -MP -MT $@
|
|||
# Predefined flags.
|
||||
ifeq ($(BUILD), debug)
|
||||
CFLAGS += -D_DEBUG
|
||||
CFLAGS += -D_GLIBCXX_DEBUG
|
||||
endif
|
||||
|
||||
# Enable compiler optimizations for everything except debug.
|
||||
|
@ -83,6 +84,7 @@ GLTEST_SRC := \
|
|||
$(SRC_ROOT)/engine/audio/audio_alsa.cc \
|
||||
$(SRC_ROOT)/engine/audio/audio_base.cc \
|
||||
$(SRC_ROOT)/engine/audio/audio_resource.cc \
|
||||
$(SRC_ROOT)/engine/drawable.cc \
|
||||
$(SRC_ROOT)/engine/engine.cc \
|
||||
$(SRC_ROOT)/engine/font.cc \
|
||||
$(SRC_ROOT)/engine/image_quad.cc \
|
||||
|
@ -90,8 +92,8 @@ GLTEST_SRC := \
|
|||
$(SRC_ROOT)/engine/mesh.cc \
|
||||
$(SRC_ROOT)/engine/platform/asset_file_linux.cc \
|
||||
$(SRC_ROOT)/engine/platform/asset_file.cc \
|
||||
$(SRC_ROOT)/engine/platform/platform_base.cc \
|
||||
$(SRC_ROOT)/engine/platform/platform_linux.cc \
|
||||
$(SRC_ROOT)/engine/platform/platform.cc \
|
||||
$(SRC_ROOT)/engine/renderer/geometry.cc \
|
||||
$(SRC_ROOT)/engine/renderer/render_command.cc \
|
||||
$(SRC_ROOT)/engine/renderer/render_resource.cc \
|
||||
|
@ -106,8 +108,6 @@ GLTEST_SRC := \
|
|||
$(SRC_ROOT)/engine/sound.cc \
|
||||
$(SRC_ROOT)/third_party/glew/glew.c \
|
||||
$(SRC_ROOT)/third_party/jsoncpp/jsoncpp.cc \
|
||||
$(SRC_ROOT)/third_party/minizip/ioapi.c \
|
||||
$(SRC_ROOT)/third_party/minizip/unzip.c \
|
||||
$(SRC_ROOT)/third_party/r8b/pffft.cpp \
|
||||
$(SRC_ROOT)/third_party/r8b/r8bbase.cpp \
|
||||
$(SRC_ROOT)/third_party/texture_compressor/dxt_encoder_internals.cc \
|
||||
|
|
|
@ -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
|
21
src/LICENSE
21
src/LICENSE
|
@ -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.
|
|
@ -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 = []
|
||||
}
|
|
@ -2,11 +2,47 @@
|
|||
#define CLOSURE_H
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
#ifdef _DEBUG
|
||||
|
||||
#define HERE std::make_tuple(__func__, __FILE__, __LINE__)
|
||||
|
||||
// Helper for logging location info, e.g. LOG << LOCATION(from)
|
||||
#define LOCATION(from) \
|
||||
std::get<0>(from) << "() [" << [](const char* path) -> std::string { \
|
||||
std::string file_name(path); \
|
||||
size_t last_slash_pos = file_name.find_last_of("\\/"); \
|
||||
if (last_slash_pos != std::string::npos) \
|
||||
file_name = file_name.substr(last_slash_pos + 1); \
|
||||
return file_name; \
|
||||
}(std::get<1>(from)) << ":" \
|
||||
<< std::get<2>(from) << "]"
|
||||
|
||||
#else
|
||||
|
||||
#define HERE nullptr
|
||||
#define LOCATION(from) ""
|
||||
|
||||
#endif
|
||||
|
||||
namespace base {
|
||||
|
||||
using Closure = std::function<void()>;
|
||||
|
||||
#ifdef _DEBUG
|
||||
|
||||
// Provides location info (function name, file name and line number) where of a
|
||||
// Closure was constructed.
|
||||
using Location = std::tuple<const char*, const char*, int>;
|
||||
|
||||
#else
|
||||
|
||||
using Location = std::nullptr_t;
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace base
|
||||
|
||||
#endif // CLOSURE_H
|
||||
|
|
|
@ -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
|
|
@ -4,6 +4,8 @@
|
|||
#include <cstdio>
|
||||
#include <memory>
|
||||
|
||||
namespace base {
|
||||
|
||||
namespace internal {
|
||||
|
||||
struct ScopedFILECloser {
|
||||
|
@ -15,8 +17,6 @@ struct ScopedFILECloser {
|
|||
|
||||
} // namespace internal
|
||||
|
||||
namespace base {
|
||||
|
||||
// Automatically closes file.
|
||||
using ScopedFILE = std::unique_ptr<FILE, internal::ScopedFILECloser>;
|
||||
|
||||
|
|
|
@ -5,17 +5,25 @@
|
|||
#else
|
||||
#include <cstdio>
|
||||
#endif
|
||||
#include <cstdlib>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "vecmath.h"
|
||||
|
||||
namespace base {
|
||||
|
||||
// Adapted from Chromium's logging implementation.
|
||||
// This is never instantiated, it's just used for EAT_STREAM_PARAMETERS to have
|
||||
// an object of the correct type on the LHS of the unused part of the ternary
|
||||
// operator.
|
||||
Log* Log::swallow_stream;
|
||||
LogBase* LogBase::swallow_stream;
|
||||
|
||||
Log::Log(const char* file, int line) : file_(file), line_(line) {}
|
||||
LogBase::LogBase(const char* file, int line) : file_(file), line_(line) {}
|
||||
|
||||
Log::~Log() {
|
||||
LogBase::~LogBase() = default;
|
||||
|
||||
void LogBase::Flush() {
|
||||
stream_ << std::endl;
|
||||
std::string text(stream_.str());
|
||||
std::string filename(file_);
|
||||
|
@ -30,23 +38,78 @@ Log::~Log() {
|
|||
#endif
|
||||
}
|
||||
|
||||
template <>
|
||||
Log& Log::operator<<<bool>(const bool& arg) {
|
||||
stream_ << (arg ? "true" : "false");
|
||||
return *this;
|
||||
Log::Log(const char* file, int line) : LogBase(file, line) {}
|
||||
|
||||
Log::~Log() {
|
||||
Flush();
|
||||
}
|
||||
|
||||
LogDiff::LogDiff(const char* file, int line) : LogBase(file, line) {}
|
||||
|
||||
LogDiff::~LogDiff() {
|
||||
static std::unordered_map<std::string, std::string> log_map;
|
||||
static std::mutex lock;
|
||||
|
||||
auto key = std::string(file_) + std::to_string(line_);
|
||||
bool flush = true;
|
||||
{
|
||||
std::lock_guard<std::mutex> scoped_lock(lock);
|
||||
auto it = log_map.find(key);
|
||||
if (it == log_map.end())
|
||||
log_map[key] = stream_.str();
|
||||
else if (it->second != stream_.str())
|
||||
it->second = stream_.str();
|
||||
else
|
||||
flush = false;
|
||||
}
|
||||
|
||||
if (flush)
|
||||
Flush();
|
||||
}
|
||||
|
||||
Check::Check(const char* file,
|
||||
int line,
|
||||
bool condition,
|
||||
bool debug,
|
||||
const char* expr)
|
||||
: LogBase(file, line), condition_(condition) {
|
||||
if (!condition_)
|
||||
base() << (debug ? "DCHECK: (" : "CHECK: (") << expr << ") ";
|
||||
}
|
||||
|
||||
Check::~Check() {
|
||||
if (!condition_) {
|
||||
Flush();
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
NotReached::NotReached(const char* file, int line) : LogBase(file, line) {
|
||||
base() << "NOTREACHED ";
|
||||
}
|
||||
|
||||
NotReached::~NotReached() {
|
||||
Flush();
|
||||
std::abort();
|
||||
}
|
||||
|
||||
template <>
|
||||
Log& Log::operator<<<Vector2>(const Vector2& arg) {
|
||||
stream_ << "(" << arg.x << ", " << arg.y << ")";
|
||||
return *this;
|
||||
LogBase& operator<<(LogBase& out, const base::Vector2& arg) {
|
||||
out.stream() << "(" << arg.x << ", " << arg.y << ")";
|
||||
return out;
|
||||
}
|
||||
|
||||
template <>
|
||||
Log& Log::operator<<<Vector4>(const Vector4& arg) {
|
||||
stream_ << "(" << arg.x << ", " << arg.y << ", " << arg.z << ", " << arg.w
|
||||
<< ")";
|
||||
return *this;
|
||||
LogBase& operator<<(LogBase& out, const base::Vector3& arg) {
|
||||
out.stream() << "(" << arg.x << ", " << arg.y << ", " << arg.z << ")";
|
||||
return out;
|
||||
}
|
||||
|
||||
template <>
|
||||
LogBase& operator<<(LogBase& out, const base::Vector4& arg) {
|
||||
out.stream() << "(" << arg.x << ", " << arg.y << ", " << arg.z << ", "
|
||||
<< arg.w << ")";
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace base
|
||||
|
|
101
src/base/log.h
101
src/base/log.h
|
@ -2,48 +2,115 @@
|
|||
#define LOG_H
|
||||
|
||||
#include <sstream>
|
||||
#include "vecmath.h"
|
||||
|
||||
#define EAT_STREAM_PARAMETERS \
|
||||
true ? (void)0 : base::Log::Voidify() & (*base::Log::swallow_stream)
|
||||
|
||||
#define LOG base::Log(__FILE__, __LINE__)
|
||||
// Macros for logging that are active in both debug and release builds. The way
|
||||
// to log things is to stream things to LOG.
|
||||
// LOG_DIFF can be used to avoid spam and log only if the message differs.
|
||||
// CHECK(condition) terminates the process if the condition is false.
|
||||
// NOTREACHED annotates unreachable codepaths and terminates the process if
|
||||
// reached.
|
||||
#define LOG base::Log(__FILE__, __LINE__).base()
|
||||
#define LOG_DIFF base::LogDiff(__FILE__, __LINE__).base()
|
||||
#define CHECK(expr) \
|
||||
base::Check(__FILE__, __LINE__, static_cast<bool>(expr), false, #expr).base()
|
||||
#define NOTREACHED base::NotReached(__FILE__, __LINE__).base()
|
||||
|
||||
// Macros for logging which are active only in debug builds.
|
||||
#ifdef _DEBUG
|
||||
#define DLOG base::Log(__FILE__, __LINE__)
|
||||
#define DLOG base::Log(__FILE__, __LINE__).base()
|
||||
#define DLOG_DIFF base::LogDiff(__FILE__, __LINE__).base()
|
||||
#define DCHECK(expr) \
|
||||
base::Check(__FILE__, __LINE__, static_cast<bool>(expr), true, #expr).base()
|
||||
#else
|
||||
// "debug mode" logging is compiled away to nothing for release builds.
|
||||
#define DLOG EAT_STREAM_PARAMETERS
|
||||
#define DLOG_DIFF EAT_STREAM_PARAMETERS
|
||||
#define DCHECK(expr) EAT_STREAM_PARAMETERS
|
||||
#endif
|
||||
|
||||
// Adapted from Chromium's logging implementation.
|
||||
// Avoid any pointless instructions to be emitted by the compiler.
|
||||
#define EAT_STREAM_PARAMETERS \
|
||||
true ? (void)0 : base::LogBase::Voidify() & (*base::LogBase::swallow_stream)
|
||||
|
||||
namespace base {
|
||||
|
||||
class Log {
|
||||
struct Vector2;
|
||||
struct Vector3;
|
||||
struct Vector4;
|
||||
|
||||
class LogBase {
|
||||
public:
|
||||
class Voidify {
|
||||
public:
|
||||
Voidify() = default;
|
||||
// This has to be an operator with a precedence lower than << but
|
||||
// higher than ?:
|
||||
void operator&(Log&) {}
|
||||
void operator&(LogBase&) {}
|
||||
};
|
||||
|
||||
Log(const char* file, int line);
|
||||
~Log();
|
||||
LogBase& base() { return *this; }
|
||||
|
||||
template <typename T>
|
||||
Log& operator<<(const T& arg) {
|
||||
stream_ << arg;
|
||||
return *this;
|
||||
}
|
||||
std::ostream& stream() { return stream_; }
|
||||
|
||||
static Log* swallow_stream;
|
||||
static LogBase* swallow_stream;
|
||||
|
||||
private:
|
||||
protected:
|
||||
const char* file_;
|
||||
const int line_;
|
||||
std::ostringstream stream_;
|
||||
|
||||
LogBase(const char* file, int line);
|
||||
~LogBase();
|
||||
|
||||
void Flush();
|
||||
};
|
||||
|
||||
class Log : public LogBase {
|
||||
public:
|
||||
Log(const char* file, int line);
|
||||
~Log();
|
||||
};
|
||||
|
||||
class LogDiff : public LogBase {
|
||||
public:
|
||||
LogDiff(const char* file, int line);
|
||||
~LogDiff();
|
||||
};
|
||||
|
||||
class Check : public LogBase {
|
||||
public:
|
||||
Check(const char* file,
|
||||
int line,
|
||||
bool condition,
|
||||
bool debug,
|
||||
const char* expr);
|
||||
~Check();
|
||||
|
||||
private:
|
||||
bool condition_;
|
||||
};
|
||||
|
||||
class NotReached : public LogBase {
|
||||
public:
|
||||
NotReached(const char* file, int line);
|
||||
~NotReached();
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
LogBase& operator<<(LogBase& out, const T& arg) {
|
||||
out.stream() << arg;
|
||||
return out;
|
||||
}
|
||||
|
||||
// Explicit specialization for internal types.
|
||||
template <>
|
||||
LogBase& operator<<(LogBase& out, const base::Vector2& arg);
|
||||
template <>
|
||||
LogBase& operator<<(LogBase& out, const base::Vector3& arg);
|
||||
template <>
|
||||
LogBase& operator<<(LogBase& out, const base::Vector4& arg);
|
||||
|
||||
} // namespace base
|
||||
|
||||
#endif // LOG_H
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#ifndef MEM_H
|
||||
#define MEM_H
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
|
||||
|
@ -9,8 +8,12 @@
|
|||
#include <malloc.h>
|
||||
#endif
|
||||
|
||||
#include "log.h"
|
||||
|
||||
#define ALIGN_MEM(alignment) __attribute__((aligned(alignment)))
|
||||
|
||||
namespace base {
|
||||
|
||||
namespace internal {
|
||||
|
||||
struct ScopedAlignedFree {
|
||||
|
@ -22,12 +25,8 @@ struct ScopedAlignedFree {
|
|||
|
||||
} // namespace internal
|
||||
|
||||
namespace base {
|
||||
|
||||
template <typename T>
|
||||
struct AlignedMem {
|
||||
using ScoppedPtr = std::unique_ptr<T, internal::ScopedAlignedFree>;
|
||||
};
|
||||
using AlignedMemPtr = std::unique_ptr<T, internal::ScopedAlignedFree>;
|
||||
|
||||
template <int kAlignment>
|
||||
inline void* AlignedAlloc(size_t size) {
|
||||
|
@ -38,8 +37,8 @@ inline void* AlignedAlloc(size_t size) {
|
|||
if (posix_memalign(&ptr, kAlignment, size))
|
||||
ptr = NULL;
|
||||
#endif
|
||||
assert(ptr);
|
||||
// assert(((unsigned)ptr & (kAlignment - 1)) == 0);
|
||||
DCHECK(ptr);
|
||||
// DCHECK(((unsigned)ptr & (kAlignment - 1)) == 0);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -1,30 +1,96 @@
|
|||
#include "task_runner.h"
|
||||
|
||||
#include "log.h"
|
||||
|
||||
namespace {
|
||||
|
||||
void EnqueueTaskAndReplyRelay(const base::Location& from,
|
||||
base::Closure task_cb,
|
||||
base::Closure reply_cb,
|
||||
base::TaskRunner* destination) {
|
||||
task_cb();
|
||||
|
||||
if (reply_cb)
|
||||
destination->EnqueueTask(from, std::move(reply_cb));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace base {
|
||||
|
||||
void TaskRunner::Enqueue(base::Closure task) {
|
||||
std::unique_lock<std::mutex> scoped_lock(mutex_);
|
||||
thread_tasks_.emplace_back(std::move(task));
|
||||
thread_local std::unique_ptr<TaskRunner> TaskRunner::thread_local_task_runner;
|
||||
|
||||
void TaskRunner::CreateThreadLocalTaskRunner() {
|
||||
DCHECK(!thread_local_task_runner);
|
||||
|
||||
thread_local_task_runner = std::make_unique<TaskRunner>();
|
||||
}
|
||||
|
||||
void TaskRunner::Run() {
|
||||
TaskRunner* TaskRunner::GetThreadLocalTaskRunner() {
|
||||
return thread_local_task_runner.get();
|
||||
}
|
||||
|
||||
void TaskRunner::EnqueueTask(const Location& from, Closure task) {
|
||||
DCHECK(task) << LOCATION(from);
|
||||
|
||||
std::lock_guard<std::mutex> scoped_lock(lock_);
|
||||
queue_.emplace_back(from, std::move(task));
|
||||
}
|
||||
|
||||
void TaskRunner::EnqueueTaskAndReply(const Location& from,
|
||||
Closure task,
|
||||
Closure reply) {
|
||||
DCHECK(task) << LOCATION(from);
|
||||
DCHECK(reply) << LOCATION(from);
|
||||
DCHECK(thread_local_task_runner) << LOCATION(from);
|
||||
|
||||
auto relay = std::bind(::EnqueueTaskAndReplyRelay, from, std::move(task),
|
||||
std::move(reply), thread_local_task_runner.get());
|
||||
|
||||
std::lock_guard<std::mutex> scoped_lock(lock_);
|
||||
queue_.emplace_back(from, std::move(relay));
|
||||
}
|
||||
|
||||
void TaskRunner::MultiConsumerRun() {
|
||||
for (;;) {
|
||||
base::Closure task;
|
||||
Task task;
|
||||
{
|
||||
std::unique_lock<std::mutex> scoped_lock(mutex_);
|
||||
if (!thread_tasks_.empty()) {
|
||||
task.swap(thread_tasks_.front());
|
||||
thread_tasks_.pop_front();
|
||||
}
|
||||
std::lock_guard<std::mutex> scoped_lock(lock_);
|
||||
if (queue_.empty())
|
||||
return;
|
||||
task.swap(queue_.front());
|
||||
queue_.pop_front();
|
||||
}
|
||||
if (!task)
|
||||
break;
|
||||
task();
|
||||
|
||||
auto [from, task_cb] = task;
|
||||
|
||||
#if 0
|
||||
LOG << __func__ << " from: " << LOCATION(from);
|
||||
#endif
|
||||
|
||||
task_cb();
|
||||
}
|
||||
}
|
||||
|
||||
bool TaskRunner::IsBoundToCurrentThread() {
|
||||
return thread_id_ == std::this_thread::get_id();
|
||||
void TaskRunner::SingleConsumerRun() {
|
||||
std::deque<Task> queue;
|
||||
{
|
||||
std::lock_guard<std::mutex> scoped_lock(lock_);
|
||||
if (queue_.empty())
|
||||
return;
|
||||
queue.swap(queue_);
|
||||
}
|
||||
|
||||
while (!queue.empty()) {
|
||||
auto [from, task_cb] = queue.front();
|
||||
queue.pop_front();
|
||||
|
||||
#if 0
|
||||
LOG << __func__ << " from: " << LOCATION(from);
|
||||
#endif
|
||||
|
||||
task_cb();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace base
|
||||
|
|
|
@ -2,26 +2,76 @@
|
|||
#define TASK_RUNNER_H
|
||||
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <tuple>
|
||||
|
||||
#include "closure.h"
|
||||
|
||||
namespace base {
|
||||
|
||||
namespace internal {
|
||||
|
||||
// Adapted from Chromium project.
|
||||
// Adapts a function that produces a result via a return value to
|
||||
// one that returns via an output parameter.
|
||||
template <typename ReturnType>
|
||||
void ReturnAsParamAdapter(std::function<ReturnType()> func,
|
||||
ReturnType* result) {
|
||||
*result = func();
|
||||
}
|
||||
|
||||
// Adapts a ReturnType* result to a callblack that expects a ReturnType.
|
||||
template <typename ReturnType>
|
||||
void ReplyAdapter(std::function<void(ReturnType)> callback,
|
||||
ReturnType* result) {
|
||||
callback(std::move(*result));
|
||||
delete result;
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
||||
// Runs queued tasks (in the form of Closure objects). All methods are
|
||||
// thread-safe and can be called on any thread.
|
||||
// Tasks run in FIFO order. When consumed concurrently by multiple threads, it
|
||||
// doesn't guarantee whether tasks overlap, or whether they run on a particular
|
||||
// thread.
|
||||
class TaskRunner {
|
||||
public:
|
||||
TaskRunner() = default;
|
||||
~TaskRunner() = default;
|
||||
|
||||
void Enqueue(base::Closure cb);
|
||||
void Run();
|
||||
void EnqueueTask(const Location& from, Closure task);
|
||||
|
||||
bool IsBoundToCurrentThread();
|
||||
void EnqueueTaskAndReply(const Location& from, Closure task, Closure reply);
|
||||
|
||||
template <typename ReturnType>
|
||||
void EnqueueTaskAndReplyWithResult(const Location& from,
|
||||
std::function<ReturnType()> task,
|
||||
std::function<void(ReturnType)> reply) {
|
||||
auto* result = new ReturnType;
|
||||
return EnqueueTaskAndReply(
|
||||
from,
|
||||
std::bind(internal::ReturnAsParamAdapter<ReturnType>, std::move(task),
|
||||
result),
|
||||
std::bind(internal::ReplyAdapter<ReturnType>, std::move(reply),
|
||||
result));
|
||||
}
|
||||
|
||||
void MultiConsumerRun();
|
||||
|
||||
void SingleConsumerRun();
|
||||
|
||||
static void CreateThreadLocalTaskRunner();
|
||||
static TaskRunner* GetThreadLocalTaskRunner();
|
||||
|
||||
private:
|
||||
std::thread::id thread_id_ = std::this_thread::get_id();
|
||||
std::mutex mutex_;
|
||||
std::deque<base::Closure> thread_tasks_;
|
||||
using Task = std::tuple<Location, Closure>;
|
||||
|
||||
std::deque<Task> queue_;
|
||||
mutable std::mutex lock_;
|
||||
|
||||
static thread_local std::unique_ptr<TaskRunner> thread_local_task_runner;
|
||||
|
||||
TaskRunner(TaskRunner const&) = delete;
|
||||
TaskRunner& operator=(TaskRunner const&) = delete;
|
||||
|
|
|
@ -1,67 +1,71 @@
|
|||
#include "worker.h"
|
||||
|
||||
#include "log.h"
|
||||
|
||||
namespace base {
|
||||
|
||||
Worker::Worker(unsigned max_concurrency) : max_concurrency_(max_concurrency) {
|
||||
if (max_concurrency_ > std::thread::hardware_concurrency() ||
|
||||
max_concurrency_ == 0) {
|
||||
max_concurrency_ = std::thread::hardware_concurrency();
|
||||
if (max_concurrency_ == 0)
|
||||
max_concurrency_ = 1;
|
||||
}
|
||||
Worker* Worker::singleton = nullptr;
|
||||
|
||||
Worker::Worker() {
|
||||
DCHECK(!singleton);
|
||||
singleton = this;
|
||||
}
|
||||
|
||||
Worker::~Worker() = default;
|
||||
|
||||
void Worker::Enqueue(base::Closure task) {
|
||||
if (!active_) {
|
||||
unsigned concurrency = max_concurrency_;
|
||||
while (concurrency--)
|
||||
threads_.emplace_back(&Worker::WorkerMain, this);
|
||||
active_ = true;
|
||||
}
|
||||
|
||||
bool notify;
|
||||
{
|
||||
std::unique_lock<std::mutex> scoped_lock(mutex_);
|
||||
notify = tasks_.empty();
|
||||
tasks_.emplace_back(std::move(task));
|
||||
}
|
||||
if (notify)
|
||||
cv_.notify_all();
|
||||
Worker::~Worker() {
|
||||
Shutdown();
|
||||
singleton = nullptr;
|
||||
}
|
||||
|
||||
void Worker::Join() {
|
||||
if (!active_)
|
||||
void Worker::Initialize(unsigned max_concurrency) {
|
||||
if (max_concurrency > std::thread::hardware_concurrency() ||
|
||||
max_concurrency == 0) {
|
||||
max_concurrency = std::thread::hardware_concurrency();
|
||||
if (max_concurrency == 0)
|
||||
max_concurrency = 1;
|
||||
}
|
||||
|
||||
while (max_concurrency--)
|
||||
threads_.emplace_back(&Worker::WorkerMain, this);
|
||||
}
|
||||
|
||||
void Worker::Shutdown() {
|
||||
if (threads_.empty())
|
||||
return;
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> scoped_lock(mutex_);
|
||||
quit_when_idle_ = true;
|
||||
}
|
||||
cv_.notify_all();
|
||||
quit_.store(true, std::memory_order_relaxed);
|
||||
semaphore_.Release();
|
||||
|
||||
for (auto& thread : threads_)
|
||||
thread.join();
|
||||
threads_.clear();
|
||||
active_ = false;
|
||||
}
|
||||
|
||||
void Worker::EnqueueTask(const Location& from, Closure task) {
|
||||
DCHECK((!threads_.empty()));
|
||||
|
||||
task_runner_.EnqueueTask(from, std::move(task));
|
||||
semaphore_.Release();
|
||||
}
|
||||
|
||||
void Worker::EnqueueTaskAndReply(const Location& from,
|
||||
Closure task,
|
||||
Closure reply) {
|
||||
DCHECK((!threads_.empty()));
|
||||
|
||||
task_runner_.EnqueueTaskAndReply(from, std::move(task), std::move(reply));
|
||||
semaphore_.Release();
|
||||
}
|
||||
|
||||
void Worker::WorkerMain() {
|
||||
for (;;) {
|
||||
base::Closure task;
|
||||
{
|
||||
std::unique_lock<std::mutex> scoped_lock(mutex_);
|
||||
while (tasks_.empty()) {
|
||||
if (quit_when_idle_)
|
||||
return;
|
||||
cv_.wait(scoped_lock);
|
||||
}
|
||||
task.swap(tasks_.front());
|
||||
tasks_.pop_front();
|
||||
semaphore_.Acquire();
|
||||
|
||||
if (quit_.load(std::memory_order_relaxed)) {
|
||||
semaphore_.Release();
|
||||
return;
|
||||
}
|
||||
|
||||
task();
|
||||
task_runner_.MultiConsumerRun();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,33 +1,53 @@
|
|||
#ifndef WORKER_H
|
||||
#define WORKER_H
|
||||
#ifndef THREAD_POOL_H
|
||||
#define THREAD_POOL_H
|
||||
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "closure.h"
|
||||
#include "semaphore.h"
|
||||
#include "task_runner.h"
|
||||
|
||||
namespace base {
|
||||
|
||||
// Feed the worker tasks and they will be called on a thread from the pool.
|
||||
class TaskRunner;
|
||||
|
||||
// Feed the worker tasks (in the form of Closure objects) and they will be
|
||||
// called on any thread from the pool.
|
||||
class Worker {
|
||||
public:
|
||||
Worker(unsigned max_concurrency = 0);
|
||||
Worker();
|
||||
~Worker();
|
||||
|
||||
void Enqueue(base::Closure task);
|
||||
void Join();
|
||||
static Worker& Get() { return *singleton; }
|
||||
|
||||
void Initialize(unsigned max_concurrency = 0);
|
||||
|
||||
void Shutdown();
|
||||
|
||||
void EnqueueTask(const Location& from, Closure task);
|
||||
|
||||
void EnqueueTaskAndReply(const Location& from, Closure task, Closure reply);
|
||||
|
||||
template <typename ReturnType>
|
||||
void EnqueueTaskAndReplyWithResult(const Location& from,
|
||||
std::function<ReturnType()> task,
|
||||
std::function<void(ReturnType)> reply) {
|
||||
task_runner_.EnqueueTaskAndReplyWithResult(from, std::move(task),
|
||||
std::move(reply));
|
||||
semaphore_.Release();
|
||||
}
|
||||
|
||||
private:
|
||||
bool active_ = false;
|
||||
unsigned max_concurrency_ = 0;
|
||||
|
||||
std::condition_variable cv_;
|
||||
std::mutex mutex_;
|
||||
std::vector<std::thread> threads_;
|
||||
std::deque<base::Closure> tasks_;
|
||||
bool quit_when_idle_ = false;
|
||||
|
||||
Semaphore semaphore_;
|
||||
std::atomic<bool> quit_{false};
|
||||
|
||||
base::TaskRunner task_runner_;
|
||||
|
||||
static Worker* singleton;
|
||||
|
||||
void WorkerMain();
|
||||
|
||||
|
@ -37,4 +57,4 @@ class Worker {
|
|||
|
||||
} // namespace base
|
||||
|
||||
#endif // WORKER_H
|
||||
#endif // THREAD_POOL_H
|
||||
|
|
|
@ -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",
|
||||
]
|
||||
}
|
|
@ -2,12 +2,10 @@
|
|||
|
||||
#include "../base/log.h"
|
||||
#include "../base/vecmath.h"
|
||||
#include "../base/worker.h"
|
||||
#include "../engine/engine.h"
|
||||
#include "../engine/font.h"
|
||||
#include "../engine/image.h"
|
||||
#include "../engine/input_event.h"
|
||||
#include "../engine/renderer/texture.h"
|
||||
#include "demo.h"
|
||||
|
||||
using namespace base;
|
||||
|
@ -16,11 +14,11 @@ using namespace eng;
|
|||
namespace {
|
||||
|
||||
constexpr char kCreditsLines[Credits::kNumLines][15] = {
|
||||
"Credits", "Code:", "Attila Uygun", "Graphics:", "Erkan Erturk"};
|
||||
"Credits", "Code:", "Attila Uygun", "Graphics:", "Erkan Ertürk"};
|
||||
|
||||
constexpr float kLineSpaces[Credits::kNumLines - 1] = {1.5f, 0.5f, 1.5f, 0.5f};
|
||||
|
||||
const Vector4 kTextColor = {0.3f, 0.55f, 1.0f, 1};
|
||||
const Vector4 kTextColor = {0.80f, 0.87f, 0.93f, 1};
|
||||
constexpr float kFadeSpeed = 0.2f;
|
||||
|
||||
} // namespace
|
||||
|
@ -30,16 +28,19 @@ Credits::Credits() = default;
|
|||
Credits::~Credits() = default;
|
||||
|
||||
bool Credits::Initialize() {
|
||||
const Font& font = static_cast<Demo*>(Engine::Get().GetGame())->GetFont();
|
||||
const Font* font = Engine::Get().GetSystemFont();
|
||||
|
||||
max_text_width_ = -1;
|
||||
for (int i = 0; i < kNumLines; ++i) {
|
||||
int width, height;
|
||||
font.CalculateBoundingBox(kCreditsLines[i], width, height);
|
||||
font->CalculateBoundingBox(kCreditsLines[i], width, height);
|
||||
if (width > max_text_width_)
|
||||
max_text_width_ = width;
|
||||
}
|
||||
|
||||
Engine::Get().SetImageSource("credits",
|
||||
std::bind(&Credits::CreateImage, this));
|
||||
|
||||
for (int i = 0; i < kNumLines; ++i)
|
||||
text_animator_.Attach(&text_[i]);
|
||||
|
||||
|
@ -51,8 +52,7 @@ void Credits::Update(float delta_time) {
|
|||
}
|
||||
|
||||
void Credits::OnInputEvent(std::unique_ptr<InputEvent> event) {
|
||||
if ((event->GetType() == InputEvent::kTap ||
|
||||
event->GetType() == InputEvent::kDragEnd ||
|
||||
if ((event->GetType() == InputEvent::kDragEnd ||
|
||||
event->GetType() == InputEvent::kNavigateBack) &&
|
||||
!text_animator_.IsPlaying(Animator::kBlending)) {
|
||||
Hide();
|
||||
|
@ -61,25 +61,13 @@ void Credits::OnInputEvent(std::unique_ptr<InputEvent> event) {
|
|||
}
|
||||
}
|
||||
|
||||
void Credits::Draw() {
|
||||
for (int i = 0; i < kNumLines; ++i)
|
||||
text_[i].Draw();
|
||||
}
|
||||
|
||||
void Credits::ContextLost() {
|
||||
if (tex_)
|
||||
tex_->Update(CreateImage());
|
||||
}
|
||||
|
||||
void Credits::Show() {
|
||||
tex_ = Engine::Get().CreateRenderResource<Texture>();
|
||||
tex_->Update(CreateImage());
|
||||
Engine::Get().RefreshImage("credits");
|
||||
|
||||
for (int i = 0; i < kNumLines; ++i) {
|
||||
text_[i].Create(tex_, {1, kNumLines});
|
||||
text_[i].Create("credits", {1, kNumLines});
|
||||
text_[i].SetZOrder(50);
|
||||
text_[i].SetOffset({0, 0});
|
||||
text_[i].SetScale({1, 1});
|
||||
text_[i].AutoScale();
|
||||
text_[i].SetColor(kTextColor * Vector4(1, 1, 1, 0));
|
||||
text_[i].SetFrame(i);
|
||||
|
||||
|
@ -107,7 +95,6 @@ void Credits::Hide() {
|
|||
text_animator_.SetEndCallback(Animator::kBlending, [&]() -> void {
|
||||
for (int i = 0; i < kNumLines; ++i)
|
||||
text_[i].Destory();
|
||||
tex_.reset();
|
||||
text_animator_.SetEndCallback(Animator::kBlending, nullptr);
|
||||
text_animator_.SetVisible(false);
|
||||
});
|
||||
|
@ -116,23 +103,21 @@ void Credits::Hide() {
|
|||
}
|
||||
|
||||
std::unique_ptr<Image> Credits::CreateImage() {
|
||||
const Font& font = static_cast<Demo*>(Engine::Get().GetGame())->GetFont();
|
||||
const Font* font = Engine::Get().GetSystemFont();
|
||||
|
||||
int line_height = font.GetLineHeight() + 1;
|
||||
int line_height = font->GetLineHeight() + 1;
|
||||
auto image = std::make_unique<Image>();
|
||||
image->Create(max_text_width_, line_height * kNumLines);
|
||||
image->Clear({1, 1, 1, 0});
|
||||
|
||||
Worker worker(kNumLines);
|
||||
for (int i = 0; i < kNumLines; ++i) {
|
||||
int w, h;
|
||||
font.CalculateBoundingBox(kCreditsLines[i], w, h);
|
||||
font->CalculateBoundingBox(kCreditsLines[i], w, h);
|
||||
float x = (image->GetWidth() - w) / 2;
|
||||
float y = line_height * i;
|
||||
worker.Enqueue(std::bind(&Font::Print, &font, x, y, kCreditsLines[i],
|
||||
image->GetBuffer(), image->GetWidth()));
|
||||
font->Print(x, y, kCreditsLines[i], image->GetBuffer(), image->GetWidth());
|
||||
}
|
||||
worker.Join();
|
||||
|
||||
image->Compress();
|
||||
return image;
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
namespace eng {
|
||||
class Image;
|
||||
class InputEvent;
|
||||
class Texture;
|
||||
} // namespace eng
|
||||
|
||||
class Credits {
|
||||
|
@ -26,16 +25,10 @@ class Credits {
|
|||
|
||||
void OnInputEvent(std::unique_ptr<eng::InputEvent> event);
|
||||
|
||||
void Draw();
|
||||
|
||||
void ContextLost();
|
||||
|
||||
void Show();
|
||||
void Hide();
|
||||
|
||||
private:
|
||||
std::shared_ptr<eng::Texture> tex_;
|
||||
|
||||
eng::ImageQuad text_[kNumLines];
|
||||
eng::Animator text_animator_;
|
||||
|
||||
|
|
|
@ -80,7 +80,7 @@ void Demo::Update(float delta_time) {
|
|||
if (add_score_ > 0) {
|
||||
score_ += add_score_;
|
||||
add_score_ = 0;
|
||||
hud_.PrintScore(score_, true);
|
||||
hud_.SetScore(score_, true);
|
||||
}
|
||||
|
||||
hud_.Update(delta_time);
|
||||
|
@ -93,21 +93,7 @@ void Demo::Update(float delta_time) {
|
|||
UpdateGameState(delta_time);
|
||||
}
|
||||
|
||||
void Demo::Draw(float frame_frac) {
|
||||
sky_.Draw(frame_frac);
|
||||
player_.Draw(frame_frac);
|
||||
enemy_.Draw(frame_frac);
|
||||
hud_.Draw();
|
||||
menu_.Draw();
|
||||
credits_.Draw();
|
||||
}
|
||||
|
||||
void Demo::ContextLost() {
|
||||
enemy_.ContextLost();
|
||||
player_.ContextLost();
|
||||
hud_.ContextLost();
|
||||
menu_.ContextLost();
|
||||
credits_.ContextLost();
|
||||
sky_.ContextLost();
|
||||
}
|
||||
|
||||
|
@ -169,7 +155,7 @@ void Demo::UpdateMenuState(float delta_time) {
|
|||
Engine::Get().Exit();
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
NOTREACHED << "- Unknown menu option: " << menu_.selected_option();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -209,8 +195,8 @@ void Demo::UpdateGameState(float delta_time) {
|
|||
sky_.SwitchColor(c);
|
||||
|
||||
++wave_;
|
||||
hud_.PrintScore(score_, true);
|
||||
hud_.PrintWave(wave_, true);
|
||||
hud_.SetScore(score_, true);
|
||||
hud_.SetWave(wave_, true);
|
||||
hud_.SetProgress(1);
|
||||
|
||||
float factor = 3 * (log10(5 * (float)wave_) / log10(1.2f)) - 25;
|
||||
|
@ -245,7 +231,7 @@ void Demo::StartNewGame() {
|
|||
}
|
||||
|
||||
void Demo::SetDelayedWork(float seconds, base::Closure cb) {
|
||||
assert(delayed_work_cb_ == nullptr);
|
||||
DCHECK(delayed_work_cb_ == nullptr);
|
||||
delayed_work_cb_ = std::move(cb);
|
||||
delayed_work_timer_ = seconds;
|
||||
}
|
||||
|
|
|
@ -20,8 +20,6 @@ class Demo : public eng::Game {
|
|||
|
||||
void Update(float delta_time) override;
|
||||
|
||||
void Draw(float frame_frac) override;
|
||||
|
||||
void ContextLost() override;
|
||||
|
||||
void LostFocus() override;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#include "enemy.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
|
||||
|
@ -10,10 +9,11 @@
|
|||
#include "../engine/engine.h"
|
||||
#include "../engine/font.h"
|
||||
#include "../engine/image.h"
|
||||
#include "../engine/renderer/texture.h"
|
||||
#include "../engine/sound.h"
|
||||
#include "demo.h"
|
||||
|
||||
using namespace std::string_literals;
|
||||
|
||||
using namespace base;
|
||||
using namespace eng;
|
||||
|
||||
|
@ -44,29 +44,18 @@ void SetupFadeOutAnim(Animator& animator, float delay) {
|
|||
|
||||
} // namespace
|
||||
|
||||
Enemy::Enemy()
|
||||
: skull_tex_(Engine::Get().CreateRenderResource<Texture>()),
|
||||
bug_tex_(Engine::Get().CreateRenderResource<Texture>()),
|
||||
target_tex_(Engine::Get().CreateRenderResource<Texture>()),
|
||||
blast_tex_(Engine::Get().CreateRenderResource<Texture>()),
|
||||
score_tex_{Engine::Get().CreateRenderResource<Texture>(),
|
||||
Engine::Get().CreateRenderResource<Texture>(),
|
||||
Engine::Get().CreateRenderResource<Texture>()} {}
|
||||
Enemy::Enemy() = default;
|
||||
|
||||
Enemy::~Enemy() = default;
|
||||
|
||||
bool Enemy::Initialize() {
|
||||
explosion_sound_ = std::make_shared<Sound>();
|
||||
if (!explosion_sound_->Load("explosion.mp3"))
|
||||
if (!explosion_sound_->Load("explosion.mp3", false))
|
||||
return false;
|
||||
|
||||
return CreateRenderResources();
|
||||
}
|
||||
|
||||
void Enemy::ContextLost() {
|
||||
CreateRenderResources();
|
||||
}
|
||||
|
||||
void Enemy::Update(float delta_time) {
|
||||
if (!waiting_for_next_wave_) {
|
||||
if (spawn_factor_interpolator_ < 1) {
|
||||
|
@ -95,25 +84,14 @@ void Enemy::Update(float delta_time) {
|
|||
}
|
||||
}
|
||||
|
||||
void Enemy::Draw(float frame_frac) {
|
||||
for (auto& e : enemies_) {
|
||||
e.sprite.Draw();
|
||||
e.target.Draw();
|
||||
e.blast.Draw();
|
||||
e.health_base.Draw();
|
||||
e.health_bar.Draw();
|
||||
e.score.Draw();
|
||||
}
|
||||
}
|
||||
|
||||
bool Enemy::HasTarget(DamageType damage_type) {
|
||||
assert(damage_type > kDamageType_Invalid && damage_type < kDamageType_Any);
|
||||
DCHECK(damage_type > kDamageType_Invalid && damage_type < kDamageType_Any);
|
||||
|
||||
return GetTarget(damage_type) ? true : false;
|
||||
}
|
||||
|
||||
Vector2 Enemy::GetTargetPos(DamageType damage_type) {
|
||||
assert(damage_type > kDamageType_Invalid && damage_type < kDamageType_Any);
|
||||
DCHECK(damage_type > kDamageType_Invalid && damage_type < kDamageType_Any);
|
||||
|
||||
EnemyUnit* target = GetTarget(damage_type);
|
||||
if (target)
|
||||
|
@ -126,7 +104,7 @@ void Enemy::SelectTarget(DamageType damage_type,
|
|||
const Vector2& origin,
|
||||
const Vector2& dir,
|
||||
float snap_factor) {
|
||||
assert(damage_type > kDamageType_Invalid && damage_type < kDamageType_Any);
|
||||
DCHECK(damage_type > kDamageType_Invalid && damage_type < kDamageType_Any);
|
||||
|
||||
if (waiting_for_next_wave_)
|
||||
return;
|
||||
|
@ -171,7 +149,7 @@ void Enemy::SelectTarget(DamageType damage_type,
|
|||
}
|
||||
|
||||
void Enemy::DeselectTarget(DamageType damage_type) {
|
||||
assert(damage_type > kDamageType_Invalid && damage_type < kDamageType_Any);
|
||||
DCHECK(damage_type > kDamageType_Invalid && damage_type < kDamageType_Any);
|
||||
|
||||
EnemyUnit* target = GetTarget(damage_type);
|
||||
if (target) {
|
||||
|
@ -182,7 +160,7 @@ void Enemy::DeselectTarget(DamageType damage_type) {
|
|||
}
|
||||
|
||||
void Enemy::HitTarget(DamageType damage_type) {
|
||||
assert(damage_type > kDamageType_Invalid && damage_type < kDamageType_Any);
|
||||
DCHECK(damage_type > kDamageType_Invalid && damage_type < kDamageType_Any);
|
||||
|
||||
if (waiting_for_next_wave_)
|
||||
return;
|
||||
|
@ -227,8 +205,8 @@ void Enemy::OnWaveStarted(int wave) {
|
|||
}
|
||||
|
||||
void Enemy::TakeDamage(EnemyUnit* target, int damage) {
|
||||
assert(!target->marked_for_removal);
|
||||
assert(target->hit_points > 0);
|
||||
DCHECK(!target->marked_for_removal);
|
||||
DCHECK(target->hit_points > 0);
|
||||
|
||||
target->blast.SetVisible(true);
|
||||
target->blast_animator.Play(Animator::kFrames, false);
|
||||
|
@ -313,8 +291,8 @@ void Enemy::Spawn(EnemyType enemy_type,
|
|||
DamageType damage_type,
|
||||
const Vector2& pos,
|
||||
float speed) {
|
||||
assert(enemy_type > kEnemyType_Invalid && enemy_type < kEnemyType_Max);
|
||||
assert(damage_type > kDamageType_Invalid && damage_type < kDamageType_Max);
|
||||
DCHECK(enemy_type > kEnemyType_Invalid && enemy_type < kEnemyType_Max);
|
||||
DCHECK(damage_type > kDamageType_Invalid && damage_type < kDamageType_Max);
|
||||
|
||||
Engine& engine = Engine::Get();
|
||||
Demo* game = static_cast<Demo*>(engine.GetGame());
|
||||
|
@ -324,15 +302,15 @@ void Enemy::Spawn(EnemyType enemy_type,
|
|||
e.damage_type = damage_type;
|
||||
if (enemy_type == kEnemyType_Skull) {
|
||||
e.total_health = e.hit_points = 1;
|
||||
e.sprite.Create(skull_tex_, {10, 13}, 100, 100);
|
||||
e.sprite.Create("skull_tex", {10, 13}, 100, 100);
|
||||
} else if (enemy_type == kEnemyType_Bug) {
|
||||
e.total_health = e.hit_points = 2;
|
||||
e.sprite.Create(bug_tex_, {10, 4});
|
||||
e.sprite.Create("bug_tex", {10, 4});
|
||||
} else { // kEnemyType_Tank
|
||||
e.total_health = e.hit_points = 6;
|
||||
e.sprite.Create(skull_tex_, {10, 13}, 100, 100);
|
||||
e.sprite.Create("skull_tex", {10, 13}, 100, 100);
|
||||
}
|
||||
e.sprite.AutoScale();
|
||||
e.sprite.SetZOrder(11);
|
||||
e.sprite.SetVisible(true);
|
||||
Vector2 spawn_pos = pos + Vector2(0, e.sprite.GetScale().y / 2);
|
||||
e.sprite.SetOffset(spawn_pos);
|
||||
|
@ -344,26 +322,28 @@ void Enemy::Spawn(EnemyType enemy_type,
|
|||
e.sprite_animator.Attach(&e.sprite);
|
||||
e.sprite_animator.Play(Animator::kFrames, true);
|
||||
|
||||
e.target.Create(target_tex_, {6, 2});
|
||||
e.target.AutoScale();
|
||||
e.target.Create("target_tex", {6, 2});
|
||||
e.target.SetZOrder(12);
|
||||
e.target.SetOffset(spawn_pos);
|
||||
|
||||
e.blast.Create(blast_tex_, {6, 2});
|
||||
e.blast.AutoScale();
|
||||
e.blast.Create("blast_tex", {6, 2});
|
||||
e.blast.SetZOrder(12);
|
||||
e.blast.SetOffset(spawn_pos);
|
||||
|
||||
e.health_base.SetZOrder(11);
|
||||
e.health_base.Scale(e.sprite.GetScale() * Vector2(0.6f, 0.01f));
|
||||
e.health_base.SetOffset(spawn_pos);
|
||||
e.health_base.PlaceToBottomOf(e.sprite);
|
||||
e.health_base.SetColor({0.5f, 0.5f, 0.5f, 1});
|
||||
|
||||
e.health_bar.SetZOrder(11);
|
||||
e.health_bar.Scale(e.sprite.GetScale() * Vector2(0.6f, 0.01f));
|
||||
e.health_bar.SetOffset(spawn_pos);
|
||||
e.health_bar.PlaceToBottomOf(e.sprite);
|
||||
e.health_bar.SetColor({0.161f, 0.89f, 0.322f, 1});
|
||||
|
||||
e.score.Create(score_tex_[e.enemy_type]);
|
||||
e.score.AutoScale();
|
||||
e.score.Create("score_tex"s + std::to_string(e.enemy_type));
|
||||
e.score.SetZOrder(12);
|
||||
e.score.SetColor({1, 1, 1, 1});
|
||||
e.score.SetOffset(spawn_pos);
|
||||
|
||||
|
@ -425,13 +405,14 @@ Enemy::EnemyUnit* Enemy::GetTarget(DamageType damage_type) {
|
|||
}
|
||||
|
||||
int Enemy::GetScore(EnemyType enemy_type) {
|
||||
assert(enemy_type > kEnemyType_Invalid && enemy_type < kEnemyType_Max);
|
||||
DCHECK(enemy_type > kEnemyType_Invalid && enemy_type < kEnemyType_Max);
|
||||
return enemy_scores[enemy_type];
|
||||
}
|
||||
|
||||
std::unique_ptr<Image> Enemy::GetScoreImage(int score) {
|
||||
std::unique_ptr<Image> Enemy::GetScoreImage(EnemyType enemy_type) {
|
||||
const Font& font = static_cast<Demo*>(Engine::Get().GetGame())->GetFont();
|
||||
|
||||
int score = GetScore(enemy_type);
|
||||
std::string text = std::to_string(score);
|
||||
int width, height;
|
||||
font.CalculateBoundingBox(text.c_str(), width, height);
|
||||
|
@ -446,31 +427,17 @@ std::unique_ptr<Image> Enemy::GetScoreImage(int score) {
|
|||
}
|
||||
|
||||
bool Enemy::CreateRenderResources() {
|
||||
auto skull_image = std::make_unique<Image>();
|
||||
if (!skull_image->Load("enemy_anims_01_frames_ok.png"))
|
||||
return false;
|
||||
auto bug_image = std::make_unique<Image>();
|
||||
if (!bug_image->Load("enemy_anims_02_frames_ok.png"))
|
||||
return false;
|
||||
auto target_image = std::make_unique<Image>();
|
||||
if (!target_image->Load("enemy_target_single_ok.png"))
|
||||
return false;
|
||||
auto blast_image = std::make_unique<Image>();
|
||||
if (!blast_image->Load("enemy_anims_blast_ok.png"))
|
||||
return false;
|
||||
|
||||
skull_image->Compress();
|
||||
bug_image->Compress();
|
||||
target_image->Compress();
|
||||
blast_image->Compress();
|
||||
|
||||
skull_tex_->Update(std::move(skull_image));
|
||||
bug_tex_->Update(std::move(bug_image));
|
||||
target_tex_->Update(std::move(target_image));
|
||||
blast_tex_->Update(std::move(blast_image));
|
||||
Engine::Get().SetImageSource("skull_tex", "enemy_anims_01_frames_ok.png",
|
||||
true);
|
||||
Engine::Get().SetImageSource("bug_tex", "enemy_anims_02_frames_ok.png", true);
|
||||
Engine::Get().SetImageSource("target_tex", "enemy_target_single_ok.png",
|
||||
true);
|
||||
Engine::Get().SetImageSource("blast_tex", "enemy_anims_blast_ok.png", true);
|
||||
|
||||
for (int i = 0; i < kEnemyType_Max; ++i)
|
||||
score_tex_[i]->Update(GetScoreImage(GetScore((EnemyType)i)));
|
||||
Engine::Get().SetImageSource(
|
||||
"score_tex"s + std::to_string(i),
|
||||
std::bind(&Enemy::GetScoreImage, this, (EnemyType)i), true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
namespace eng {
|
||||
class Image;
|
||||
class Sound;
|
||||
class Texture;
|
||||
} // namespace eng
|
||||
|
||||
class Enemy {
|
||||
|
@ -25,12 +24,8 @@ class Enemy {
|
|||
|
||||
bool Initialize();
|
||||
|
||||
void ContextLost();
|
||||
|
||||
void Update(float delta_time);
|
||||
|
||||
void Draw(float frame_frac);
|
||||
|
||||
bool HasTarget(DamageType damage_type);
|
||||
base::Vector2 GetTargetPos(DamageType damage_type);
|
||||
|
||||
|
@ -76,12 +71,6 @@ class Enemy {
|
|||
eng::SoundPlayer explosion_;
|
||||
};
|
||||
|
||||
std::shared_ptr<eng::Texture> skull_tex_;
|
||||
std::shared_ptr<eng::Texture> bug_tex_;
|
||||
std::shared_ptr<eng::Texture> target_tex_;
|
||||
std::shared_ptr<eng::Texture> blast_tex_;
|
||||
std::shared_ptr<eng::Texture> score_tex_[kEnemyType_Max];
|
||||
|
||||
std::shared_ptr<eng::Sound> explosion_sound_;
|
||||
|
||||
std::list<EnemyUnit> enemies_;
|
||||
|
@ -111,7 +100,7 @@ class Enemy {
|
|||
|
||||
int GetScore(EnemyType enemy_type);
|
||||
|
||||
std::unique_ptr<eng::Image> GetScoreImage(int score);
|
||||
std::unique_ptr<eng::Image> GetScoreImage(EnemyType enemy_type);
|
||||
|
||||
bool CreateRenderResources();
|
||||
};
|
||||
|
|
|
@ -6,9 +6,10 @@
|
|||
#include "../engine/engine.h"
|
||||
#include "../engine/font.h"
|
||||
#include "../engine/image.h"
|
||||
#include "../engine/renderer/texture.h"
|
||||
#include "demo.h"
|
||||
|
||||
using namespace std::string_literals;
|
||||
|
||||
using namespace base;
|
||||
using namespace eng;
|
||||
|
||||
|
@ -23,10 +24,7 @@ const Vector4 kTextColor = {0.895f, 0.692f, 0.24f, 1};
|
|||
|
||||
} // namespace
|
||||
|
||||
Hud::Hud() {
|
||||
text_[0].Create(Engine::Get().CreateRenderResource<Texture>());
|
||||
text_[1].Create(Engine::Get().CreateRenderResource<Texture>());
|
||||
}
|
||||
Hud::Hud() = default;
|
||||
|
||||
Hud::~Hud() = default;
|
||||
|
||||
|
@ -37,12 +35,14 @@ bool Hud::Initialize() {
|
|||
int tmp;
|
||||
font.CalculateBoundingBox("big_enough_text", max_text_width_, tmp);
|
||||
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
auto image = CreateImage();
|
||||
Engine::Get().SetImageSource("text0",
|
||||
std::bind(&Hud::CreateScoreImage, this));
|
||||
Engine::Get().SetImageSource("text1", std::bind(&Hud::CreateWaveImage, this));
|
||||
|
||||
text_[i].GetTexture()->Update(std::move(image));
|
||||
text_[i].AutoScale();
|
||||
text_[i].SetColor(kTextColor);
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
text_[i].Create("text"s + std::to_string(i));
|
||||
text_[i].SetZOrder(30);
|
||||
text_[i].SetColor(kTextColor * Vector4(1, 1, 1, 0));
|
||||
|
||||
Vector2 pos = (engine.GetScreenSize() / 2 - text_[i].GetScale() / 2);
|
||||
pos -= engine.GetScreenSize() * Vector2(kHorizontalMargin, kVerticalMargin);
|
||||
|
@ -51,6 +51,7 @@ bool Hud::Initialize() {
|
|||
scale -= engine.GetScreenSize() * Vector2(kHorizontalMargin * 4, 0);
|
||||
scale += text_[0].GetScale() * Vector2(0, 0.3f);
|
||||
|
||||
progress_bar_[i].SetZOrder(30);
|
||||
progress_bar_[i].Scale(scale);
|
||||
progress_bar_[i].Translate(pos * Vector2(0, 1));
|
||||
progress_bar_[i].SetColor(kPprogressBarColor[i] * Vector4(1, 1, 1, 0));
|
||||
|
@ -79,18 +80,6 @@ void Hud::Update(float delta_time) {
|
|||
}
|
||||
}
|
||||
|
||||
void Hud::Draw() {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
progress_bar_[i].Draw();
|
||||
text_[i].Draw();
|
||||
}
|
||||
}
|
||||
|
||||
void Hud::ContextLost() {
|
||||
PrintScore(last_score_, false);
|
||||
PrintWave(last_wave_, false);
|
||||
}
|
||||
|
||||
void Hud::Show() {
|
||||
if (text_[0].IsVisible())
|
||||
return;
|
||||
|
@ -103,9 +92,9 @@ void Hud::Show() {
|
|||
}
|
||||
}
|
||||
|
||||
void Hud::PrintScore(int score, bool flash) {
|
||||
void Hud::SetScore(int score, bool flash) {
|
||||
last_score_ = score;
|
||||
Print(0, std::to_string(score));
|
||||
Engine::Get().RefreshImage("text0");
|
||||
|
||||
if (flash) {
|
||||
text_animator_[0].SetEndCallback(Animator::kBlending, text_animator_cb_[0]);
|
||||
|
@ -115,11 +104,9 @@ void Hud::PrintScore(int score, bool flash) {
|
|||
}
|
||||
}
|
||||
|
||||
void Hud::PrintWave(int wave, bool flash) {
|
||||
void Hud::SetWave(int wave, bool flash) {
|
||||
last_wave_ = wave;
|
||||
std::string text = "wave ";
|
||||
text += std::to_string(wave);
|
||||
Print(1, text.c_str());
|
||||
Engine::Get().RefreshImage("text1");
|
||||
|
||||
if (flash) {
|
||||
text_animator_[1].SetEndCallback(Animator::kBlending, text_animator_cb_[1]);
|
||||
|
@ -137,7 +124,15 @@ void Hud::SetProgress(float progress) {
|
|||
progress_bar_[1].Translate({t, 0});
|
||||
}
|
||||
|
||||
void Hud::Print(int i, const std::string& text) {
|
||||
std::unique_ptr<eng::Image> Hud::CreateScoreImage() {
|
||||
return Print(0, std::to_string(last_score_));
|
||||
}
|
||||
|
||||
std::unique_ptr<eng::Image> Hud::CreateWaveImage() {
|
||||
return Print(1, "wave "s + std::to_string(last_wave_));
|
||||
}
|
||||
|
||||
std::unique_ptr<Image> Hud::Print(int i, const std::string& text) {
|
||||
const Font& font = static_cast<Demo*>(Engine::Get().GetGame())->GetFont();
|
||||
|
||||
auto image = CreateImage();
|
||||
|
@ -151,7 +146,7 @@ void Hud::Print(int i, const std::string& text) {
|
|||
|
||||
font.Print(x, 0, text.c_str(), image->GetBuffer(), image->GetWidth());
|
||||
|
||||
text_[i].GetTexture()->Update(std::move(image));
|
||||
return image;
|
||||
}
|
||||
|
||||
std::unique_ptr<Image> Hud::CreateImage() {
|
||||
|
|
|
@ -22,14 +22,10 @@ class Hud {
|
|||
|
||||
void Update(float delta_time);
|
||||
|
||||
void Draw();
|
||||
|
||||
void ContextLost();
|
||||
|
||||
void Show();
|
||||
|
||||
void PrintScore(int score, bool flash);
|
||||
void PrintWave(int wave, bool flash);
|
||||
void SetScore(int score, bool flash);
|
||||
void SetWave(int wave, bool flash);
|
||||
void SetProgress(float progress);
|
||||
|
||||
private:
|
||||
|
@ -46,7 +42,10 @@ class Hud {
|
|||
int last_wave_ = 0;
|
||||
float last_progress_ = 0;
|
||||
|
||||
void Print(int i, const std::string& text);
|
||||
std::unique_ptr<eng::Image> CreateScoreImage();
|
||||
std::unique_ptr<eng::Image> CreateWaveImage();
|
||||
|
||||
std::unique_ptr<eng::Image> Print(int i, const std::string& text);
|
||||
|
||||
std::unique_ptr<eng::Image> CreateImage();
|
||||
};
|
||||
|
|
|
@ -1,18 +1,15 @@
|
|||
#include "menu.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
#include "../base/collusion_test.h"
|
||||
#include "../base/interpolation.h"
|
||||
#include "../base/log.h"
|
||||
#include "../base/worker.h"
|
||||
#include "../engine/engine.h"
|
||||
#include "../engine/font.h"
|
||||
#include "../engine/image.h"
|
||||
#include "../engine/input_event.h"
|
||||
#include "../engine/renderer/texture.h"
|
||||
#include "demo.h"
|
||||
|
||||
using namespace base;
|
||||
|
@ -34,7 +31,7 @@ constexpr float kFadeSpeed = 0.2f;
|
|||
|
||||
} // namespace
|
||||
|
||||
Menu::Menu() : tex_(Engine::Get().CreateRenderResource<Texture>()) {}
|
||||
Menu::Menu() = default;
|
||||
|
||||
Menu::~Menu() = default;
|
||||
|
||||
|
@ -49,11 +46,12 @@ bool Menu::Initialize() {
|
|||
max_text_width_ = width;
|
||||
}
|
||||
|
||||
tex_->Update(CreateImage());
|
||||
if (!CreateRenderResources())
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < kOption_Max; ++i) {
|
||||
items_[i].text.Create(tex_, {1, 4});
|
||||
items_[i].text.AutoScale();
|
||||
items_[i].text.Create("menu_tex", {1, 4});
|
||||
items_[i].text.SetZOrder(40);
|
||||
items_[i].text.Scale(1.5f);
|
||||
items_[i].text.SetColor(kColorFadeOut);
|
||||
items_[i].text.SetVisible(false);
|
||||
|
@ -86,15 +84,12 @@ void Menu::Update(float delta_time) {
|
|||
}
|
||||
|
||||
void Menu::OnInputEvent(std::unique_ptr<InputEvent> event) {
|
||||
if (event->GetType() == InputEvent::kTap ||
|
||||
event->GetType() == InputEvent::kDragStart)
|
||||
if (event->GetType() == InputEvent::kDragStart)
|
||||
tap_pos_[0] = tap_pos_[1] = event->GetVector(0);
|
||||
else if (event->GetType() == InputEvent::kDrag)
|
||||
tap_pos_[1] = event->GetVector(0);
|
||||
|
||||
if ((event->GetType() != InputEvent::kTap &&
|
||||
event->GetType() != InputEvent::kDragEnd) ||
|
||||
IsAnimating())
|
||||
if (event->GetType() != InputEvent::kDragEnd || IsAnimating())
|
||||
return;
|
||||
|
||||
for (int i = 0; i < kOption_Max; ++i) {
|
||||
|
@ -116,15 +111,6 @@ void Menu::OnInputEvent(std::unique_ptr<InputEvent> event) {
|
|||
}
|
||||
}
|
||||
|
||||
void Menu::Draw() {
|
||||
for (int i = 0; i < kOption_Max; ++i)
|
||||
items_[i].text.Draw();
|
||||
}
|
||||
|
||||
void Menu::ContextLost() {
|
||||
tex_->Update(CreateImage());
|
||||
}
|
||||
|
||||
void Menu::SetOptionEnabled(Option o, bool enable) {
|
||||
int first = -1, last = -1;
|
||||
for (int i = 0; i < kOption_Max; ++i) {
|
||||
|
@ -181,6 +167,12 @@ void Menu::Hide() {
|
|||
}
|
||||
}
|
||||
|
||||
bool Menu::CreateRenderResources() {
|
||||
Engine::Get().SetImageSource("menu_tex", std::bind(&Menu::CreateImage, this));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<Image> Menu::CreateImage() {
|
||||
const Font& font = static_cast<Demo*>(Engine::Get().GetGame())->GetFont();
|
||||
|
||||
|
@ -191,16 +183,13 @@ std::unique_ptr<Image> Menu::CreateImage() {
|
|||
// Fill the area of each menu item with gradient.
|
||||
image->GradientV({1.0f, 1.0f, 1.0f, 0}, {.0f, .0f, 1.0f, 0}, line_height);
|
||||
|
||||
base::Worker worker(kOption_Max);
|
||||
for (int i = 0; i < kOption_Max; ++i) {
|
||||
int w, h;
|
||||
font.CalculateBoundingBox(kMenuOption[i], w, h);
|
||||
float x = (image->GetWidth() - w) / 2;
|
||||
float y = line_height * i;
|
||||
worker.Enqueue(std::bind(&Font::Print, &font, x, y, kMenuOption[i],
|
||||
image->GetBuffer(), image->GetWidth()));
|
||||
font.Print(x, y, kMenuOption[i], image->GetBuffer(), image->GetWidth());
|
||||
}
|
||||
worker.Join();
|
||||
|
||||
return image;
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
namespace eng {
|
||||
class Image;
|
||||
class InputEvent;
|
||||
class Texture;
|
||||
} // namespace eng
|
||||
|
||||
class Menu {
|
||||
|
@ -35,10 +34,6 @@ class Menu {
|
|||
|
||||
void OnInputEvent(std::unique_ptr<eng::InputEvent> event);
|
||||
|
||||
void Draw();
|
||||
|
||||
void ContextLost();
|
||||
|
||||
void SetOptionEnabled(Option o, bool enable);
|
||||
|
||||
void Show();
|
||||
|
@ -54,8 +49,6 @@ class Menu {
|
|||
bool hide = false;
|
||||
};
|
||||
|
||||
std::shared_ptr<eng::Texture> tex_;
|
||||
|
||||
Item items_[kOption_Max];
|
||||
|
||||
int max_text_width_ = 0;
|
||||
|
@ -64,6 +57,8 @@ class Menu {
|
|||
|
||||
base::Vector2 tap_pos_[2] = {{0, 0}, {0, 0}};
|
||||
|
||||
bool CreateRenderResources();
|
||||
|
||||
std::unique_ptr<eng::Image> CreateImage();
|
||||
|
||||
bool IsAnimating();
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
#include "player.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "../base/log.h"
|
||||
#include "../engine/engine.h"
|
||||
#include "../engine/image.h"
|
||||
|
@ -21,9 +19,7 @@ constexpr int wepon_anim_speed = 48;
|
|||
|
||||
} // namespace
|
||||
|
||||
Player::Player()
|
||||
: weapon_tex_(Engine::Get().CreateRenderResource<Texture>()),
|
||||
beam_tex_(Engine::Get().CreateRenderResource<Texture>()) {}
|
||||
Player::Player() = default;
|
||||
|
||||
Player::~Player() = default;
|
||||
|
||||
|
@ -34,10 +30,6 @@ bool Player::Initialize() {
|
|||
return true;
|
||||
}
|
||||
|
||||
void Player::ContextLost() {
|
||||
CreateRenderResources();
|
||||
}
|
||||
|
||||
void Player::Update(float delta_time) {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
warmup_animator_[i].Update(delta_time);
|
||||
|
@ -63,15 +55,6 @@ void Player::OnInputEvent(std::unique_ptr<InputEvent> event) {
|
|||
DragCancel();
|
||||
}
|
||||
|
||||
void Player::Draw(float frame_frac) {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
drag_sign_[i].Draw();
|
||||
beam_[i].Draw();
|
||||
beam_spark_[i].Draw();
|
||||
weapon_[i].Draw();
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 Player::GetWeaponPos(DamageType type) const {
|
||||
return Engine::Get().GetScreenSize() /
|
||||
Vector2(type == kDamageType_Green ? 3.5f : -3.5f, -2) +
|
||||
|
@ -93,7 +76,7 @@ DamageType Player::GetWeaponType(const Vector2& pos) {
|
|||
}
|
||||
}
|
||||
|
||||
assert(closest_weapon != kDamageType_Invalid);
|
||||
DCHECK(closest_weapon != kDamageType_Invalid);
|
||||
if (closest_dist < weapon_[closest_weapon].GetScale().x * 0.9f)
|
||||
return closest_weapon;
|
||||
return kDamageType_Invalid;
|
||||
|
@ -157,27 +140,27 @@ bool Player::IsFiring(DamageType type) {
|
|||
void Player::SetupWeapons() {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
// Setup draw sign.
|
||||
drag_sign_[i].Create(weapon_tex_, {8, 2});
|
||||
drag_sign_[i].AutoScale();
|
||||
drag_sign_[i].Create("weapon_tex", {8, 2});
|
||||
drag_sign_[i].SetZOrder(21);
|
||||
drag_sign_[i].SetFrame(i * 8);
|
||||
|
||||
// Setup weapon.
|
||||
weapon_[i].Create(weapon_tex_, {8, 2});
|
||||
weapon_[i].AutoScale();
|
||||
weapon_[i].Create("weapon_tex", {8, 2});
|
||||
weapon_[i].SetZOrder(24);
|
||||
weapon_[i].SetVisible(true);
|
||||
weapon_[i].SetFrame(wepon_warmup_frame[i]);
|
||||
|
||||
// Setup beam.
|
||||
beam_[i].Create(beam_tex_, {1, 2});
|
||||
beam_[i].AutoScale();
|
||||
beam_[i].Create("beam_tex", {1, 2});
|
||||
beam_[i].SetZOrder(22);
|
||||
beam_[i].SetFrame(i);
|
||||
beam_[i].PlaceToRightOf(weapon_[i]);
|
||||
beam_[i].Translate(weapon_[i].GetScale() * Vector2(-0.5f, 0));
|
||||
beam_[i].SetPivot(beam_[i].GetOffset());
|
||||
|
||||
// Setup beam spark.
|
||||
beam_spark_[i].Create(weapon_tex_, {8, 2});
|
||||
beam_spark_[i].AutoScale();
|
||||
beam_spark_[i].Create("weapon_tex", {8, 2});
|
||||
beam_spark_[i].SetZOrder(23);
|
||||
beam_spark_[i].SetFrame(i * 8 + 1);
|
||||
beam_spark_[i].PlaceToRightOf(weapon_[i]);
|
||||
beam_spark_[i].Translate(weapon_[i].GetScale() * Vector2(-0.5f, 0));
|
||||
|
@ -338,17 +321,8 @@ void Player::NavigateBack() {
|
|||
}
|
||||
|
||||
bool Player::CreateRenderResources() {
|
||||
auto weapon_image = std::make_unique<Image>();
|
||||
if (!weapon_image->Load("enemy_anims_flare_ok.png"))
|
||||
return false;
|
||||
auto beam_image = std::make_unique<Image>();
|
||||
if (!beam_image->Load("enemy_ray_ok.png"))
|
||||
return false;
|
||||
Engine::Get().SetImageSource("weapon_tex", "enemy_anims_flare_ok.png", true);
|
||||
Engine::Get().SetImageSource("beam_tex", "enemy_ray_ok.png", true);
|
||||
|
||||
weapon_image->Compress();
|
||||
beam_image->Compress();
|
||||
|
||||
weapon_tex_->Update(std::move(weapon_image));
|
||||
beam_tex_->Update(std::move(beam_image));
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
#include "../base/vecmath.h"
|
||||
#include "../engine/animator.h"
|
||||
#include "../engine/image_quad.h"
|
||||
#include "../engine/renderer/texture.h"
|
||||
#include "damage_type.h"
|
||||
|
||||
namespace eng {
|
||||
|
@ -20,21 +19,14 @@ class Player {
|
|||
|
||||
bool Initialize();
|
||||
|
||||
void ContextLost();
|
||||
|
||||
void Update(float delta_time);
|
||||
|
||||
void OnInputEvent(std::unique_ptr<eng::InputEvent> event);
|
||||
|
||||
void Draw(float frame_frac);
|
||||
|
||||
base::Vector2 GetWeaponPos(DamageType type) const;
|
||||
base::Vector2 GetWeaponScale() const;
|
||||
|
||||
private:
|
||||
std::shared_ptr<eng::Texture> weapon_tex_;
|
||||
std::shared_ptr<eng::Texture> beam_tex_;
|
||||
|
||||
eng::ImageQuad drag_sign_[2];
|
||||
eng::ImageQuad weapon_[2];
|
||||
eng::ImageQuad beam_[2];
|
||||
|
|
|
@ -31,6 +31,8 @@ bool SkyQuad::Create() {
|
|||
|
||||
color_animator_.Attach(this);
|
||||
|
||||
SetVisible(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -33,13 +33,15 @@ class SkyQuad : public eng::Animatable {
|
|||
void SetColor(const base::Vector4& color) override { nebula_color_ = color; }
|
||||
base::Vector4 GetColor() const override { return nebula_color_; }
|
||||
|
||||
void Draw(float frame_frac);
|
||||
// Drawable interface.
|
||||
void Draw(float frame_frac) override;
|
||||
|
||||
void ContextLost();
|
||||
|
||||
void SwitchColor(const base::Vector4& color);
|
||||
|
||||
private:
|
||||
std::shared_ptr<eng::Shader> shader_;
|
||||
std::unique_ptr<eng::Shader> shader_;
|
||||
|
||||
base::Vector2 sky_offset_ = {0, 0};
|
||||
base::Vector2 last_sky_offset_ = {0, 0};
|
||||
|
|
|
@ -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 = []
|
||||
}
|
|
@ -2,13 +2,14 @@
|
|||
#define SHAPE_H
|
||||
|
||||
#include "../base/vecmath.h"
|
||||
#include "drawable.h"
|
||||
|
||||
namespace eng {
|
||||
|
||||
class Animatable {
|
||||
class Animatable : public Drawable {
|
||||
public:
|
||||
Animatable() = default;
|
||||
virtual ~Animatable() = default;
|
||||
~Animatable() override = default;
|
||||
|
||||
void Translate(const base::Vector2& offset);
|
||||
void Scale(const base::Vector2& scale);
|
||||
|
@ -33,9 +34,6 @@ class Animatable {
|
|||
virtual void SetColor(const base::Vector4& color) = 0;
|
||||
virtual base::Vector4 GetColor() const = 0;
|
||||
|
||||
void SetVisible(bool visible) { visible_ = visible; }
|
||||
bool IsVisible() const { return visible_; }
|
||||
|
||||
void PlaceToLeftOf(const Animatable& s) {
|
||||
Translate({s.GetScale().x / -2.0f + GetScale().x / -2.0f, 0});
|
||||
}
|
||||
|
@ -58,7 +56,6 @@ class Animatable {
|
|||
base::Vector2 pivot_ = {0, 0};
|
||||
base::Vector2 rotation_ = {0, 1};
|
||||
float theta_ = 0;
|
||||
bool visible_ = false;
|
||||
};
|
||||
|
||||
} // namespace eng
|
||||
|
|
|
@ -43,6 +43,33 @@ void Animator::Stop(int animation) {
|
|||
loop_flags_ &= ~animation;
|
||||
}
|
||||
|
||||
float Animator::GetTime(int animation) {
|
||||
if ((animation & kMovement) != 0)
|
||||
return movement_time_;
|
||||
if ((animation & kRotation) != 0)
|
||||
return rotation_time_;
|
||||
if ((animation & kBlending) != 0)
|
||||
return blending_time_;
|
||||
if ((animation & kFrames) != 0)
|
||||
return frame_time_;
|
||||
return timer_time_;
|
||||
}
|
||||
|
||||
void Animator::SetTime(int animation, float time) {
|
||||
DCHECK(time >= 0 && time <= 1);
|
||||
|
||||
if ((animation & kMovement) != 0)
|
||||
movement_time_ = time;
|
||||
if ((animation & kRotation) != 0)
|
||||
rotation_time_ = time;
|
||||
if ((animation & kBlending) != 0)
|
||||
blending_time_ = time;
|
||||
if ((animation & kFrames) != 0)
|
||||
frame_time_ = time;
|
||||
if ((animation & kTimer) != 0)
|
||||
timer_time_ = time;
|
||||
}
|
||||
|
||||
void Animator::SetEndCallback(int animation, base::Closure cb) {
|
||||
if ((inside_cb_ & animation) != 0) {
|
||||
has_pending_cb_ = true;
|
||||
|
|
|
@ -28,47 +28,39 @@ class Animator {
|
|||
Animator() = default;
|
||||
~Animator() = default;
|
||||
|
||||
// Attached the given animatable to this animator and sets the start values.
|
||||
void Attach(Animatable* animatable);
|
||||
|
||||
void Play(int animation, bool loop);
|
||||
void Pause(int animation);
|
||||
void Stop(int animation);
|
||||
|
||||
// Set callback for the given animations. It's called for each animation once
|
||||
// it ends. Not that it's not called for looping animations.
|
||||
// Get/set current time of the given animation.
|
||||
float GetTime(int animation);
|
||||
void SetTime(int animation, float time);
|
||||
|
||||
// Set callback ro be called once animation ends.
|
||||
void SetEndCallback(int animation, base::Closure cb);
|
||||
|
||||
// Set movement animation parameters. Movement is relative to the attached
|
||||
// animatable's current position. Distance is calculated from the magnitude of
|
||||
// direction vector. Duration is in seconds.
|
||||
// Distance is the magnitude of direction vector. Duration is in seconds.
|
||||
void SetMovement(base::Vector2 direction,
|
||||
float duration,
|
||||
Interpolator interpolator = nullptr);
|
||||
|
||||
// Set rotation animation parameters. Rotation is relative to the attached
|
||||
// animatable's current rotation. Duration is in seconds.
|
||||
// Rotation is in radian. Duration is in seconds.
|
||||
void SetRotation(float target,
|
||||
float duration,
|
||||
Interpolator interpolator = nullptr);
|
||||
|
||||
// Set color blending animation parameters. Color blending animation is
|
||||
// absolute. The absolute start colors are obtained from the attached
|
||||
// animatables. Duration is in seconds.
|
||||
void SetBlending(base::Vector4 target,
|
||||
float duration,
|
||||
Interpolator interpolator = nullptr);
|
||||
|
||||
// Set frame playback animation parameters. Frame animation is absolute. The
|
||||
// absolute start frames are obtained from the attached animatables. Plays
|
||||
// count number of frames.
|
||||
// Plays count number of frames.
|
||||
void SetFrames(int count,
|
||||
int frames_per_second,
|
||||
Interpolator interpolator = nullptr);
|
||||
|
||||
// Set Timer parameters. Timer doesn't play any animation. Usefull for
|
||||
// triggering a callback after the given seconds passed. Loop parameter is
|
||||
// ignored when played.
|
||||
// Triggers a callback after the given seconds passed.
|
||||
void SetTimer(float duration);
|
||||
|
||||
// Set visibility of all attached animatables.
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "../../base/log.h"
|
||||
#include "audio_resource.h"
|
||||
|
||||
|
@ -21,10 +23,10 @@ bool AudioAlsa::Initialize() {
|
|||
// Contains information about the hardware.
|
||||
snd_pcm_hw_params_t* hw_params;
|
||||
|
||||
// "default" is usualy PulseAudio. Use "plughw:CARD=PCH" instead for direct
|
||||
// hardware device with software format conversion.
|
||||
if ((err = snd_pcm_open(&pcm_handle_, "plughw:CARD=PCH",
|
||||
SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
|
||||
// TODO: "default" is usualy PulseAudio. Select a device with "plughw" for
|
||||
// direct hardware device with software format conversion.
|
||||
if ((err = snd_pcm_open(&device_, "default", SND_PCM_STREAM_PLAYBACK, 0)) <
|
||||
0) {
|
||||
LOG << "Cannot open audio device. Error: " << snd_strerror(err);
|
||||
return false;
|
||||
}
|
||||
|
@ -34,65 +36,65 @@ bool AudioAlsa::Initialize() {
|
|||
snd_pcm_hw_params_alloca(&hw_params);
|
||||
|
||||
// Init hw_params with full configuration space.
|
||||
if ((err = snd_pcm_hw_params_any(pcm_handle_, hw_params)) < 0) {
|
||||
if ((err = snd_pcm_hw_params_any(device_, hw_params)) < 0) {
|
||||
LOG << "Cannot initialize hardware parameter structure. Error: "
|
||||
<< snd_strerror(err);
|
||||
break;
|
||||
}
|
||||
|
||||
if ((err = snd_pcm_hw_params_set_access(
|
||||
pcm_handle_, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
|
||||
device_, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
|
||||
LOG << "Cannot set access type. Error: " << snd_strerror(err);
|
||||
break;
|
||||
}
|
||||
|
||||
if ((err = snd_pcm_hw_params_set_format(pcm_handle_, hw_params,
|
||||
if ((err = snd_pcm_hw_params_set_format(device_, hw_params,
|
||||
SND_PCM_FORMAT_FLOAT)) < 0) {
|
||||
LOG << "Cannot set sample format. Error: " << snd_strerror(err);
|
||||
break;
|
||||
}
|
||||
|
||||
// Disable software resampler.
|
||||
if ((err = snd_pcm_hw_params_set_rate_resample(pcm_handle_, hw_params, 0)) <
|
||||
if ((err = snd_pcm_hw_params_set_rate_resample(device_, hw_params, 0)) <
|
||||
0) {
|
||||
LOG << "Cannot disbale software resampler. Error: " << snd_strerror(err);
|
||||
break;
|
||||
}
|
||||
|
||||
unsigned sample_rate = 48000;
|
||||
if ((err = snd_pcm_hw_params_set_rate_near(pcm_handle_, hw_params,
|
||||
&sample_rate, 0)) < 0) {
|
||||
if ((err = snd_pcm_hw_params_set_rate_near(device_, hw_params, &sample_rate,
|
||||
0)) < 0) {
|
||||
LOG << "Cannot set sample rate. Error: " << snd_strerror(err);
|
||||
break;
|
||||
}
|
||||
|
||||
if ((err = snd_pcm_hw_params_set_channels(pcm_handle_, hw_params, 2)) < 0) {
|
||||
if ((err = snd_pcm_hw_params_set_channels(device_, hw_params, 2)) < 0) {
|
||||
LOG << "Cannot set channel count. Error: " << snd_strerror(err);
|
||||
break;
|
||||
}
|
||||
|
||||
// Set period time to 4 ms. The latency will be 12 ms for 3 perods.
|
||||
unsigned period_time = 4000;
|
||||
if ((err = snd_pcm_hw_params_set_period_time_near(pcm_handle_, hw_params,
|
||||
if ((err = snd_pcm_hw_params_set_period_time_near(device_, hw_params,
|
||||
&period_time, 0)) < 0) {
|
||||
LOG << "Cannot set periods. Error: " << snd_strerror(err);
|
||||
break;
|
||||
}
|
||||
|
||||
unsigned periods = 3;
|
||||
if ((err = snd_pcm_hw_params_set_periods_near(pcm_handle_, hw_params,
|
||||
&periods, 0)) < 0) {
|
||||
if ((err = snd_pcm_hw_params_set_periods_near(device_, hw_params, &periods,
|
||||
0)) < 0) {
|
||||
LOG << "Cannot set periods. Error: " << snd_strerror(err);
|
||||
break;
|
||||
}
|
||||
|
||||
// Apply HW parameter settings to PCM device and prepare device.
|
||||
if ((err = snd_pcm_hw_params(pcm_handle_, hw_params)) < 0) {
|
||||
if ((err = snd_pcm_hw_params(device_, hw_params)) < 0) {
|
||||
LOG << "Cannot set parameters. Error: " << snd_strerror(err);
|
||||
break;
|
||||
}
|
||||
|
||||
if ((err = snd_pcm_prepare(pcm_handle_)) < 0) {
|
||||
if ((err = snd_pcm_prepare(device_)) < 0) {
|
||||
LOG << "Cannot prepare audio interface for use. Error: "
|
||||
<< snd_strerror(err);
|
||||
break;
|
||||
|
@ -126,60 +128,75 @@ bool AudioAlsa::Initialize() {
|
|||
sample_rate_ = sample_rate;
|
||||
period_size_ = period_size;
|
||||
|
||||
StartWorker();
|
||||
StartAudioThread();
|
||||
|
||||
return true;
|
||||
} while (false);
|
||||
|
||||
snd_pcm_close(pcm_handle_);
|
||||
snd_pcm_close(device_);
|
||||
return false;
|
||||
}
|
||||
|
||||
void AudioAlsa::Shutdown() {
|
||||
LOG << "Shutting down audio system.";
|
||||
TerminateWorker();
|
||||
snd_pcm_drop(pcm_handle_);
|
||||
snd_pcm_close(pcm_handle_);
|
||||
|
||||
TerminateAudioThread();
|
||||
snd_pcm_drop(device_);
|
||||
snd_pcm_close(device_);
|
||||
}
|
||||
|
||||
void AudioAlsa::Suspend() {
|
||||
DCHECK(!terminate_audio_thread_.load(std::memory_order_relaxed));
|
||||
|
||||
suspend_audio_thread_.store(true, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void AudioAlsa::Resume() {
|
||||
DCHECK(!terminate_audio_thread_.load(std::memory_order_relaxed));
|
||||
|
||||
suspend_audio_thread_.store(false, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
size_t AudioAlsa::GetSampleRate() {
|
||||
return sample_rate_;
|
||||
}
|
||||
|
||||
bool AudioAlsa::StartWorker() {
|
||||
bool AudioAlsa::StartAudioThread() {
|
||||
LOG << "Starting audio thread.";
|
||||
|
||||
std::promise<bool> promise;
|
||||
std::future<bool> future = promise.get_future();
|
||||
worker_thread_ =
|
||||
std::thread(&AudioAlsa::WorkerMain, this, std::move(promise));
|
||||
return future.get();
|
||||
DCHECK(!terminate_audio_thread_.load(std::memory_order_relaxed));
|
||||
|
||||
audio_thread_ = std::thread(&AudioAlsa::AudioThreadMain, this);
|
||||
}
|
||||
|
||||
void AudioAlsa::TerminateWorker() {
|
||||
// Notify worker thread and wait for it to terminate.
|
||||
if (terminate_worker_)
|
||||
void AudioAlsa::TerminateAudioThread() {
|
||||
if (terminate_audio_thread_.load(std::memory_order_relaxed))
|
||||
return;
|
||||
terminate_worker_ = true;
|
||||
|
||||
LOG << "Terminating audio thread";
|
||||
worker_thread_.join();
|
||||
|
||||
// Notify worker thread and wait for it to terminate.
|
||||
terminate_audio_thread_.store(true, std::memory_order_relaxed);
|
||||
suspend_audio_thread_.store(true, std::memory_order_relaxed);
|
||||
audio_thread_.join();
|
||||
}
|
||||
|
||||
void AudioAlsa::WorkerMain(std::promise<bool> promise) {
|
||||
promise.set_value(true);
|
||||
|
||||
void AudioAlsa::AudioThreadMain() {
|
||||
size_t num_frames = period_size_ / (num_channels_ * sizeof(float));
|
||||
auto buffer = std::make_unique<float[]>(num_frames * 2);
|
||||
|
||||
for (;;) {
|
||||
if (terminate_worker_)
|
||||
return;
|
||||
while (suspend_audio_thread_.load(std::memory_order_relaxed)) {
|
||||
if (terminate_audio_thread_.load(std::memory_order_relaxed))
|
||||
return;
|
||||
std::this_thread::yield();
|
||||
}
|
||||
|
||||
RenderAudio(buffer.get(), num_frames);
|
||||
|
||||
while (snd_pcm_writei(pcm_handle_, buffer.get(), num_frames) < 0) {
|
||||
snd_pcm_prepare(pcm_handle_);
|
||||
LOG << "Audio buffer underrun!";
|
||||
while (snd_pcm_writei(device_, buffer.get(), num_frames) < 0) {
|
||||
snd_pcm_prepare(device_);
|
||||
DLOG << "Audio buffer underrun!";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
#ifndef AUDIO_ALSA_H
|
||||
#define AUDIO_ALSA_H
|
||||
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
|
||||
#include "audio_base.h"
|
||||
|
@ -22,23 +21,27 @@ class AudioAlsa : public AudioBase {
|
|||
|
||||
void Shutdown();
|
||||
|
||||
void Suspend();
|
||||
void Resume();
|
||||
|
||||
size_t GetSampleRate();
|
||||
|
||||
private:
|
||||
// Handle for the PCM device.
|
||||
snd_pcm_t* pcm_handle_;
|
||||
snd_pcm_t* device_;
|
||||
|
||||
std::thread worker_thread_;
|
||||
bool terminate_worker_ = false;
|
||||
std::thread audio_thread_;
|
||||
std::atomic<bool> terminate_audio_thread_ = false;
|
||||
std::atomic<bool> suspend_audio_thread_ = false;
|
||||
|
||||
size_t num_channels_ = 0;
|
||||
size_t sample_rate_ = 0;
|
||||
size_t period_size_ = 0;
|
||||
|
||||
bool StartWorker();
|
||||
void TerminateWorker();
|
||||
bool StartAudioThread();
|
||||
void TerminateAudioThread();
|
||||
|
||||
void WorkerMain(std::promise<bool> promise);
|
||||
void AudioThreadMain();
|
||||
};
|
||||
|
||||
} // namespace eng
|
||||
|
|
|
@ -1,32 +1,33 @@
|
|||
#include "audio_base.h"
|
||||
#include "audio.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "../../base/log.h"
|
||||
#include "../../base/task_runner.h"
|
||||
#include "../../base/worker.h"
|
||||
#include "../sound.h"
|
||||
|
||||
using namespace base;
|
||||
|
||||
namespace eng {
|
||||
|
||||
AudioBase::AudioBase() = default;
|
||||
AudioBase::AudioBase()
|
||||
: main_thread_task_runner_(TaskRunner::GetThreadLocalTaskRunner()) {}
|
||||
|
||||
AudioBase::~AudioBase() {
|
||||
worker_.Join();
|
||||
}
|
||||
AudioBase::~AudioBase() = default;
|
||||
|
||||
void AudioBase::Play(std::shared_ptr<AudioSample> sample) {
|
||||
std::unique_lock<std::mutex> scoped_lock(mutex_);
|
||||
samples_[0].push_back(sample);
|
||||
}
|
||||
|
||||
void AudioBase::Update() {
|
||||
task_runner_.Run();
|
||||
if (audio_enabled_) {
|
||||
std::lock_guard<Spinlock> scoped_lock(lock_);
|
||||
samples_[0].push_back(sample);
|
||||
} else {
|
||||
sample->active = false;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioBase::RenderAudio(float* output_buffer, size_t num_frames) {
|
||||
{
|
||||
std::unique_lock<std::mutex> scoped_lock(mutex_);
|
||||
std::lock_guard<Spinlock> scoped_lock(lock_);
|
||||
samples_[1].splice(samples_[1].end(), samples_[0]);
|
||||
}
|
||||
|
||||
|
@ -35,87 +36,98 @@ void AudioBase::RenderAudio(float* output_buffer, size_t num_frames) {
|
|||
for (auto it = samples_[1].begin(); it != samples_[1].end();) {
|
||||
AudioSample* sample = it->get();
|
||||
|
||||
unsigned flags = sample->flags;
|
||||
bool remove = false;
|
||||
auto sound = sample->sound.get();
|
||||
unsigned flags = sample->flags.load(std::memory_order_relaxed);
|
||||
|
||||
if (flags & AudioSample::kStopped) {
|
||||
remove = true;
|
||||
} else {
|
||||
auto sound = sample->sound.get();
|
||||
|
||||
const float* src[2] = {const_cast<const Sound*>(sound)->GetBuffer(0),
|
||||
const_cast<const Sound*>(sound)->GetBuffer(1)};
|
||||
sample->marked_for_removal = true;
|
||||
} else if (!sample->marked_for_removal) {
|
||||
const float* src[2] = {sound->GetBuffer(0), sound->GetBuffer(1)};
|
||||
if (!src[1])
|
||||
src[1] = src[0]; // mono.
|
||||
|
||||
size_t num_samples = sound->GetNumSamples();
|
||||
size_t num_channels = sound->num_channels();
|
||||
size_t src_index = sample->src_index;
|
||||
size_t step = sample->step;
|
||||
size_t step = sample->step.load(std::memory_order_relaxed);
|
||||
size_t accumulator = sample->accumulator;
|
||||
float amplitude = sample->amplitude;
|
||||
float amplitude_inc = sample->amplitude_inc;
|
||||
float max_amplitude = sample->max_amplitude;
|
||||
float amplitude_inc =
|
||||
sample->amplitude_inc.load(std::memory_order_relaxed);
|
||||
float max_amplitude =
|
||||
sample->max_amplitude.load(std::memory_order_relaxed);
|
||||
|
||||
size_t channel_offset =
|
||||
(flags & AudioSample::kSimulateStereo) && num_channels == 1
|
||||
(flags & AudioSample::kSimulateStereo) && !sound->is_streaming_sound()
|
||||
? sound->hz() / 10
|
||||
: 0;
|
||||
|
||||
DCHECK(num_samples || sound->is_streaming_sound());
|
||||
|
||||
for (size_t i = 0; i < num_frames * kChannelCount;) {
|
||||
// Mix the 1st channel.
|
||||
output_buffer[i++] += src[0][src_index] * amplitude;
|
||||
if (num_samples) {
|
||||
// Mix the 1st channel.
|
||||
output_buffer[i++] += src[0][src_index] * amplitude;
|
||||
|
||||
// Mix the 2nd channel. Offset the source index for stereo simulation.
|
||||
size_t ind = channel_offset + src_index;
|
||||
if (ind < num_samples)
|
||||
output_buffer[i++] += src[1][ind] * amplitude;
|
||||
else if (flags & AudioSample::kLoop)
|
||||
output_buffer[i++] += src[1][ind % num_samples] * amplitude;
|
||||
else
|
||||
i++;
|
||||
// Mix the 2nd channel. Offset the source index for stereo simulation.
|
||||
size_t ind = channel_offset + src_index;
|
||||
if (ind < num_samples)
|
||||
output_buffer[i++] += src[1][ind] * amplitude;
|
||||
else if (flags & AudioSample::kLoop)
|
||||
output_buffer[i++] += src[1][ind % num_samples] * amplitude;
|
||||
else
|
||||
i++;
|
||||
|
||||
// Apply amplitude modification.
|
||||
amplitude += amplitude_inc;
|
||||
if (amplitude <= 0) {
|
||||
remove = true;
|
||||
break;
|
||||
} else if (amplitude > max_amplitude) {
|
||||
amplitude = max_amplitude;
|
||||
// Apply amplitude modification.
|
||||
amplitude += amplitude_inc;
|
||||
if (amplitude <= 0) {
|
||||
sample->marked_for_removal = true;
|
||||
break;
|
||||
} else if (amplitude > max_amplitude) {
|
||||
amplitude = max_amplitude;
|
||||
}
|
||||
|
||||
// Basic resampling for variations.
|
||||
accumulator += step;
|
||||
src_index += accumulator / 10;
|
||||
accumulator %= 10;
|
||||
}
|
||||
|
||||
// Basic resampling for variations.
|
||||
accumulator += step;
|
||||
src_index += accumulator / 10;
|
||||
accumulator %= 10;
|
||||
|
||||
// Advance source index.
|
||||
if (src_index >= num_samples) {
|
||||
if (!sound->is_streaming_sound()) {
|
||||
if (flags & AudioSample::kLoop) {
|
||||
src_index %= num_samples;
|
||||
} else {
|
||||
remove = true;
|
||||
src_index %= num_samples;
|
||||
|
||||
if (!(flags & AudioSample::kLoop)) {
|
||||
sample->marked_for_removal = true;
|
||||
break;
|
||||
}
|
||||
} else if (!sound->IsStreamingInProgress()) {
|
||||
} else if (!sample->streaming_in_progress.load(
|
||||
std::memory_order_acquire)) {
|
||||
if (num_samples)
|
||||
src_index %= num_samples;
|
||||
|
||||
if (sound->eof()) {
|
||||
remove = true;
|
||||
sample->marked_for_removal = true;
|
||||
break;
|
||||
}
|
||||
|
||||
src_index = 0;
|
||||
sample->streaming_in_progress.store(true,
|
||||
std::memory_order_relaxed);
|
||||
|
||||
// Swap buffers and start streaming in background.
|
||||
sound->SwapBuffers();
|
||||
src[0] = const_cast<const Sound*>(sound)->GetBuffer(0);
|
||||
src[1] = const_cast<const Sound*>(sound)->GetBuffer(1);
|
||||
src[0] = sound->GetBuffer(0);
|
||||
src[1] = sound->GetBuffer(1);
|
||||
if (!src[1])
|
||||
src[1] = src[0]; // mono.
|
||||
num_samples = sound->GetNumSamples();
|
||||
|
||||
worker_.Enqueue(std::bind(&Sound::Stream, sample->sound,
|
||||
flags & AudioSample::kLoop));
|
||||
} else {
|
||||
LOG << "Buffer underrun!";
|
||||
src_index = 0;
|
||||
Worker::Get().EnqueueTask(HERE,
|
||||
std::bind(&AudioBase::DoStream, this, *it,
|
||||
flags & AudioSample::kLoop));
|
||||
} else if (num_samples) {
|
||||
DLOG << "Buffer underrun!";
|
||||
src_index %= num_samples;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -125,9 +137,12 @@ void AudioBase::RenderAudio(float* output_buffer, size_t num_frames) {
|
|||
sample->amplitude = amplitude;
|
||||
}
|
||||
|
||||
if (remove) {
|
||||
task_runner_.Enqueue(sample->end_cb);
|
||||
sample->active = false;
|
||||
if (sample->marked_for_removal &&
|
||||
(!sound->is_streaming_sound() ||
|
||||
!sample->streaming_in_progress.load(std::memory_order_relaxed))) {
|
||||
sample->marked_for_removal = false;
|
||||
main_thread_task_runner_->EnqueueTask(
|
||||
HERE, std::bind(&AudioBase::EndCallback, this, *it));
|
||||
it = samples_[1].erase(it);
|
||||
} else {
|
||||
++it;
|
||||
|
@ -135,4 +150,21 @@ void AudioBase::RenderAudio(float* output_buffer, size_t num_frames) {
|
|||
}
|
||||
}
|
||||
|
||||
void AudioBase::DoStream(std::shared_ptr<AudioSample> sample, bool loop) {
|
||||
sample->sound->Stream(loop);
|
||||
|
||||
// Memory barrier to ensure all memory writes become visible to the audio
|
||||
// thread.
|
||||
sample->streaming_in_progress.store(false, std::memory_order_release);
|
||||
}
|
||||
|
||||
void AudioBase::EndCallback(std::shared_ptr<AudioSample> sample) {
|
||||
AudioSample* s = sample.get();
|
||||
|
||||
s->active = false;
|
||||
|
||||
if (s->end_cb)
|
||||
s->end_cb();
|
||||
}
|
||||
|
||||
} // namespace eng
|
||||
|
|
|
@ -3,37 +3,47 @@
|
|||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
#include "../../base/closure.h"
|
||||
#include "../../base/task_runner.h"
|
||||
#include "../../base/worker.h"
|
||||
#include "../../base/spinlock.h"
|
||||
#include "audio_sample.h"
|
||||
|
||||
namespace base {
|
||||
class TaskRunner;
|
||||
}
|
||||
|
||||
namespace eng {
|
||||
|
||||
class Sound;
|
||||
|
||||
class AudioBase {
|
||||
public:
|
||||
void Play(std::shared_ptr<AudioSample> impl_data);
|
||||
void Play(std::shared_ptr<AudioSample> sample);
|
||||
|
||||
void Update();
|
||||
void SetEnableAudio(bool enable) { audio_enabled_ = enable; }
|
||||
|
||||
protected:
|
||||
static constexpr int kChannelCount = 2;
|
||||
|
||||
std::list<std::shared_ptr<AudioSample>> samples_[2];
|
||||
std::mutex mutex_;
|
||||
|
||||
base::Worker worker_{1};
|
||||
|
||||
base::TaskRunner task_runner_;
|
||||
|
||||
AudioBase();
|
||||
~AudioBase();
|
||||
|
||||
void RenderAudio(float* output_buffer, size_t num_frames);
|
||||
|
||||
private:
|
||||
std::list<std::shared_ptr<AudioSample>> samples_[2];
|
||||
base::Spinlock lock_;
|
||||
|
||||
base::TaskRunner* main_thread_task_runner_;
|
||||
|
||||
bool audio_enabled_ = true;
|
||||
|
||||
void DoStream(std::shared_ptr<AudioSample> sample, bool loop);
|
||||
|
||||
void EndCallback(std::shared_ptr<AudioSample> sample);
|
||||
|
||||
AudioBase(const AudioBase&) = delete;
|
||||
AudioBase& operator=(const AudioBase&) = delete;
|
||||
};
|
||||
|
||||
} // namespace eng
|
||||
|
|
|
@ -20,6 +20,16 @@ bool AudioOboe::Initialize() {
|
|||
|
||||
void AudioOboe::Shutdown() {
|
||||
LOG << "Shutting down audio system.";
|
||||
|
||||
stream_->stop();
|
||||
}
|
||||
|
||||
void AudioOboe::Suspend() {
|
||||
stream_->pause();
|
||||
}
|
||||
|
||||
void AudioOboe::Resume() {
|
||||
stream_->start();
|
||||
}
|
||||
|
||||
size_t AudioOboe::GetSampleRate() {
|
||||
|
|
|
@ -20,6 +20,9 @@ class AudioOboe : public AudioBase {
|
|||
|
||||
void Shutdown();
|
||||
|
||||
void Suspend();
|
||||
void Resume();
|
||||
|
||||
size_t GetSampleRate();
|
||||
|
||||
private:
|
||||
|
|
|
@ -5,64 +5,84 @@
|
|||
#include "audio.h"
|
||||
#include "audio_sample.h"
|
||||
|
||||
using namespace base;
|
||||
|
||||
namespace eng {
|
||||
|
||||
AudioResource::AudioResource(Audio* audio)
|
||||
: sample_(std::make_shared<AudioSample>()), audio_(audio) {}
|
||||
|
||||
AudioResource::~AudioResource() {
|
||||
sample_->flags |= AudioSample::kStopped;
|
||||
sample_->flags.fetch_or(AudioSample::kStopped, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void AudioResource::Play(std::shared_ptr<Sound> sound,
|
||||
float amplitude,
|
||||
bool reset_pos) {
|
||||
if (sample_->active)
|
||||
AudioSample* sample = sample_.get();
|
||||
|
||||
if (sample->active) {
|
||||
if (reset_pos)
|
||||
sample_->flags.fetch_or(AudioSample::kStopped, std::memory_order_relaxed);
|
||||
|
||||
if (reset_pos ||
|
||||
sample->flags.load(std::memory_order_relaxed) & AudioSample::kStopped) {
|
||||
Closure ocb = sample_->end_cb;
|
||||
SetEndCallback([&, sound, amplitude, reset_pos, ocb]() -> void {
|
||||
Play(sound, amplitude, reset_pos);
|
||||
SetEndCallback(ocb);
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (reset_pos) {
|
||||
sample_->src_index = 0;
|
||||
sample_->accumulator = 0;
|
||||
sample->src_index = 0;
|
||||
sample->accumulator = 0;
|
||||
sound->ResetStream();
|
||||
}
|
||||
sample_->flags &= ~AudioSample::kStopped;
|
||||
sample_->sound = sound;
|
||||
sample_->amplitude = amplitude;
|
||||
sample_->active = true;
|
||||
|
||||
sample->active = true;
|
||||
sample_->flags.fetch_and(~AudioSample::kStopped, std::memory_order_relaxed);
|
||||
sample->sound = sound;
|
||||
if (amplitude >= 0)
|
||||
sample->amplitude = amplitude;
|
||||
|
||||
audio_->Play(sample_);
|
||||
}
|
||||
|
||||
void AudioResource::Stop() {
|
||||
if (!sample_->active)
|
||||
return;
|
||||
|
||||
sample_->flags |= AudioSample::kStopped;
|
||||
if (sample_->active)
|
||||
sample_->flags.fetch_or(AudioSample::kStopped, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void AudioResource::SetLoop(bool loop) {
|
||||
if (loop)
|
||||
sample_->flags |= AudioSample::kLoop;
|
||||
sample_->flags.fetch_or(AudioSample::kLoop, std::memory_order_relaxed);
|
||||
else
|
||||
sample_->flags &= ~AudioSample::kLoop;
|
||||
sample_->flags.fetch_and(AudioSample::kLoop, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void AudioResource::SetSimulateStereo(bool simulate) {
|
||||
if (simulate)
|
||||
sample_->flags |= AudioSample::kSimulateStereo;
|
||||
sample_->flags.fetch_or(AudioSample::kSimulateStereo,
|
||||
std::memory_order_relaxed);
|
||||
else
|
||||
sample_->flags &= ~AudioSample::kSimulateStereo;
|
||||
sample_->flags.fetch_and(AudioSample::kSimulateStereo,
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void AudioResource::SetResampleStep(size_t step) {
|
||||
sample_->step = step + 10;
|
||||
sample_->step.store(step + 10, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void AudioResource::SetMaxAmplitude(float max_amplitude) {
|
||||
sample_->max_amplitude = max_amplitude;
|
||||
sample_->max_amplitude.store(max_amplitude, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void AudioResource::SetAmplitudeInc(float amplitude_inc) {
|
||||
sample_->amplitude_inc = amplitude_inc;
|
||||
sample_->amplitude_inc.store(amplitude_inc, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void AudioResource::SetEndCallback(base::Closure cb) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#ifndef AUDIO_SAMPLE_H
|
||||
#define AUDIO_SAMPLE_H
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
|
||||
#include "../../base/closure.h"
|
||||
|
@ -12,19 +13,27 @@ class Sound;
|
|||
struct AudioSample {
|
||||
enum SampleFlags { kLoop = 1, kStopped = 2, kSimulateStereo = 4 };
|
||||
|
||||
// Read-only accessed by the audio thread.
|
||||
std::shared_ptr<Sound> sound;
|
||||
unsigned flags = 0;
|
||||
size_t step = 10;
|
||||
float amplitude_inc = 0;
|
||||
float max_amplitude = 1.0f;
|
||||
// Accessed by main thread only.
|
||||
bool active = false;
|
||||
base::Closure end_cb;
|
||||
|
||||
// Write accessed by the audio thread.
|
||||
// Accessed by audio thread only.
|
||||
bool marked_for_removal = false;
|
||||
|
||||
// Initialized by main thread, used by audio thread.
|
||||
std::shared_ptr<Sound> sound;
|
||||
size_t src_index = 0;
|
||||
size_t accumulator = 0;
|
||||
float amplitude = 1.0f;
|
||||
bool active = false;
|
||||
|
||||
// Write accessed by main thread, read-only accessed by audio thread.
|
||||
std::atomic<unsigned> flags{0};
|
||||
std::atomic<size_t> step{10};
|
||||
std::atomic<float> amplitude_inc{0};
|
||||
std::atomic<float> max_amplitude{1.0f};
|
||||
|
||||
// Accessed by audio thread and decoder thread.
|
||||
std::atomic<bool> streaming_in_progress{false};
|
||||
};
|
||||
|
||||
} // namespace eng
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -1,14 +1,16 @@
|
|||
#include "engine.h"
|
||||
|
||||
#include "../base/log.h"
|
||||
#include "../base/worker.h"
|
||||
#include "../third_party/texture_compressor/texture_compressor.h"
|
||||
#include "animator.h"
|
||||
#include "audio/audio.h"
|
||||
#include "audio/audio_resource.h"
|
||||
#include "drawable.h"
|
||||
#include "font.h"
|
||||
#include "game.h"
|
||||
#include "game_factory.h"
|
||||
#include "image.h"
|
||||
#include "image_quad.h"
|
||||
#include "input_event.h"
|
||||
#include "mesh.h"
|
||||
#include "platform/platform.h"
|
||||
|
@ -27,7 +29,7 @@ Engine* Engine::singleton = nullptr;
|
|||
|
||||
Engine::Engine(Platform* platform, Renderer* renderer, Audio* audio)
|
||||
: platform_(platform), renderer_(renderer), audio_(audio) {
|
||||
assert(!singleton);
|
||||
DCHECK(!singleton);
|
||||
singleton = this;
|
||||
|
||||
renderer_->SetContextLostCB(std::bind(&Engine::ContextLost, this));
|
||||
|
@ -35,9 +37,15 @@ Engine::Engine(Platform* platform, Renderer* renderer, Audio* audio)
|
|||
quad_ = CreateRenderResource<Geometry>();
|
||||
pass_through_shader_ = CreateRenderResource<Shader>();
|
||||
solid_shader_ = CreateRenderResource<Shader>();
|
||||
|
||||
stats_ = std::make_unique<ImageQuad>();
|
||||
stats_->SetZOrder(std::numeric_limits<int>::max());
|
||||
}
|
||||
|
||||
Engine::~Engine() {
|
||||
game_.reset();
|
||||
stats_.reset();
|
||||
|
||||
singleton = nullptr;
|
||||
}
|
||||
|
||||
|
@ -83,6 +91,8 @@ bool Engine::Initialize() {
|
|||
if (!CreateRenderResources())
|
||||
return false;
|
||||
|
||||
SetImageSource("stats_tex", std::bind(&Engine::PrintStats, this));
|
||||
|
||||
game_ = GameFactoryBase::CreateGame("");
|
||||
if (!game_) {
|
||||
printf("No game found to run.\n");
|
||||
|
@ -104,45 +114,66 @@ void Engine::Shutdown() {
|
|||
void Engine::Update(float delta_time) {
|
||||
seconds_accumulated_ += delta_time;
|
||||
|
||||
audio_->Update();
|
||||
renderer_->Update();
|
||||
|
||||
game_->Update(delta_time);
|
||||
|
||||
// Destroy unused textures.
|
||||
for (auto& t : textures_) {
|
||||
if (!t.second.persistent && t.second.texture.use_count() == 1)
|
||||
t.second.texture->Destroy();
|
||||
}
|
||||
|
||||
fps_seconds_ += delta_time;
|
||||
if (fps_seconds_ >= 1) {
|
||||
fps_ = renderer_->GetAndResetFPS();
|
||||
fps_seconds_ = 0;
|
||||
}
|
||||
|
||||
if (stats_.IsVisible())
|
||||
PrintStats();
|
||||
if (stats_->IsVisible()) {
|
||||
RefreshImage("stats_tex");
|
||||
stats_->AutoScale();
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::Draw(float frame_frac) {
|
||||
auto cmd = std::make_unique<CmdClear>();
|
||||
cmd->rgba = {0, 0, 0, 1};
|
||||
renderer_->EnqueueCommand(std::move(cmd));
|
||||
renderer_->EnqueueCommand(std::make_unique<CmdEableBlend>());
|
||||
drawables_.sort(
|
||||
[](auto& a, auto& b) { return a->GetZOrder() < b->GetZOrder(); });
|
||||
|
||||
game_->Draw(frame_frac);
|
||||
|
||||
if (stats_.IsVisible())
|
||||
stats_.Draw();
|
||||
for (auto d : drawables_) {
|
||||
if (d->IsVisible())
|
||||
d->Draw(frame_frac);
|
||||
}
|
||||
|
||||
renderer_->EnqueueCommand(std::make_unique<CmdPresent>());
|
||||
}
|
||||
|
||||
void Engine::LostFocus() {
|
||||
audio_->Suspend();
|
||||
|
||||
if (game_)
|
||||
game_->LostFocus();
|
||||
}
|
||||
|
||||
void Engine::GainedFocus() {
|
||||
audio_->Resume();
|
||||
|
||||
if (game_)
|
||||
game_->GainedFocus();
|
||||
}
|
||||
|
||||
void Engine::AddDrawable(Drawable* drawable) {
|
||||
DCHECK(std::find(drawables_.begin(), drawables_.end(), drawable) ==
|
||||
drawables_.end());
|
||||
drawables_.push_back(drawable);
|
||||
}
|
||||
|
||||
void Engine::RemoveDrawable(Drawable* drawable) {
|
||||
auto it = std::find(drawables_.begin(), drawables_.end(), drawable);
|
||||
if (it != drawables_.end()) {
|
||||
drawables_.erase(it);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::Exit() {
|
||||
platform_->Exit();
|
||||
}
|
||||
|
@ -156,32 +187,117 @@ Vector2 Engine::ToPosition(const Vector2& vec) {
|
|||
return ToScale(vec) - GetScreenSize() / 2.0f;
|
||||
}
|
||||
|
||||
std::shared_ptr<AudioResource> Engine::CreateAudioResource() {
|
||||
return std::make_shared<AudioResource>(audio_);
|
||||
void Engine::SetImageSource(const std::string& asset_name,
|
||||
const std::string& file_name,
|
||||
bool persistent) {
|
||||
std::shared_ptr<Texture> texture;
|
||||
auto it = textures_.find(asset_name);
|
||||
if (it != textures_.end()) {
|
||||
texture = it->second.texture;
|
||||
it->second.asset_file = file_name;
|
||||
it->second.create_image = nullptr;
|
||||
it->second.persistent = persistent;
|
||||
} else {
|
||||
texture = CreateRenderResource<Texture>();
|
||||
textures_[asset_name] = {texture, file_name, nullptr, persistent};
|
||||
}
|
||||
|
||||
if (persistent) {
|
||||
auto image = std::make_unique<Image>();
|
||||
if (image->Load(file_name)) {
|
||||
image->Compress();
|
||||
texture->Update(std::move(image));
|
||||
} else {
|
||||
texture->Destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::SetImageSource(const std::string& asset_name,
|
||||
CreateImageCB create_image,
|
||||
bool persistent) {
|
||||
std::shared_ptr<Texture> texture;
|
||||
auto it = textures_.find(asset_name);
|
||||
if (it != textures_.end()) {
|
||||
texture = it->second.texture;
|
||||
it->second.create_image = create_image;
|
||||
it->second.asset_file.clear();
|
||||
it->second.persistent = persistent;
|
||||
} else {
|
||||
texture = CreateRenderResource<Texture>();
|
||||
textures_[asset_name] = {texture, "", create_image, persistent};
|
||||
}
|
||||
|
||||
if (persistent) {
|
||||
auto image = create_image();
|
||||
if (image)
|
||||
texture->Update(std::move(image));
|
||||
else
|
||||
texture->Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::RefreshImage(const std::string& asset_name) {
|
||||
auto it = textures_.find(asset_name);
|
||||
if (it == textures_.end())
|
||||
return;
|
||||
|
||||
std::unique_ptr<Image> image;
|
||||
if (!it->second.asset_file.empty()) {
|
||||
image = std::make_unique<Image>();
|
||||
if (image->Load(it->second.asset_file))
|
||||
image->Compress();
|
||||
else
|
||||
image.reset();
|
||||
} else if (it->second.create_image) {
|
||||
image = it->second.create_image();
|
||||
}
|
||||
|
||||
if (image)
|
||||
it->second.texture->Update(std::move(image));
|
||||
else
|
||||
it->second.texture->Destroy();
|
||||
}
|
||||
|
||||
std::shared_ptr<Texture> Engine::GetTexture(const std::string& asset_name) {
|
||||
auto it = textures_.find(asset_name);
|
||||
if (it != textures_.end()) {
|
||||
if (!it->second.texture->IsValid())
|
||||
RefreshImage(it->first);
|
||||
return it->second.texture;
|
||||
}
|
||||
|
||||
std::shared_ptr<Texture> texture = CreateRenderResource<Texture>();
|
||||
textures_[asset_name] = {texture};
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioResource> Engine::CreateAudioResource() {
|
||||
return std::make_unique<AudioResource>(audio_);
|
||||
}
|
||||
|
||||
void Engine::AddInputEvent(std::unique_ptr<InputEvent> event) {
|
||||
switch (event->GetType()) {
|
||||
case InputEvent::kTap:
|
||||
case InputEvent::kDragEnd:
|
||||
if (((GetScreenSize() / 2) * 0.9f - event->GetVector(0)).Magnitude() <=
|
||||
0.25f) {
|
||||
SetSatsVisible(!stats_.IsVisible());
|
||||
// Consume event.
|
||||
return;
|
||||
SetSatsVisible(!stats_->IsVisible());
|
||||
// TODO: Enqueue DragCancel so we can consume this event.
|
||||
}
|
||||
break;
|
||||
case InputEvent::kKeyPress:
|
||||
if (event->GetKeyPress() == 's') {
|
||||
SetSatsVisible(!stats_.IsVisible());
|
||||
SetSatsVisible(!stats_->IsVisible());
|
||||
// Consume event.
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case InputEvent::kDrag:
|
||||
if (stats_.IsVisible()) {
|
||||
if ((stats_.GetOffset() - event->GetVector(0)).Magnitude() <=
|
||||
stats_.GetScale().y)
|
||||
stats_.SetOffset(event->GetVector(0));
|
||||
if (stats_->IsVisible()) {
|
||||
if ((stats_->GetOffset() - event->GetVector(0)).Magnitude() <=
|
||||
stats_->GetScale().y)
|
||||
stats_->SetOffset(event->GetVector(0));
|
||||
// TODO: Enqueue DragCancel so we can consume this event.
|
||||
}
|
||||
break;
|
||||
|
@ -201,6 +317,15 @@ std::unique_ptr<InputEvent> Engine::GetNextInputEvent() {
|
|||
return event;
|
||||
}
|
||||
|
||||
void Engine::Vibrate(int duration) {
|
||||
if (vibration_enabled_)
|
||||
platform_->Vibrate(duration);
|
||||
}
|
||||
|
||||
void Engine::SetEnableAudio(bool enable) {
|
||||
audio_->SetEnableAudio(enable);
|
||||
}
|
||||
|
||||
TextureCompressor* Engine::GetTextureCompressor(bool opacity) {
|
||||
return opacity ? tex_comp_alpha_.get() : tex_comp_opaque_.get();
|
||||
}
|
||||
|
@ -229,7 +354,7 @@ bool Engine::IsMobile() const {
|
|||
return platform_->mobile_device();
|
||||
}
|
||||
|
||||
std::shared_ptr<RenderResource> Engine::CreateRenderResourceInternal(
|
||||
std::unique_ptr<RenderResource> Engine::CreateRenderResourceInternal(
|
||||
RenderResourceFactoryBase& factory) {
|
||||
return renderer_->CreateResource(factory);
|
||||
}
|
||||
|
@ -237,6 +362,9 @@ std::shared_ptr<RenderResource> Engine::CreateRenderResourceInternal(
|
|||
void Engine::ContextLost() {
|
||||
CreateRenderResources();
|
||||
|
||||
for (auto& t : textures_)
|
||||
RefreshImage(t.first);
|
||||
|
||||
game_->ContextLost();
|
||||
}
|
||||
|
||||
|
@ -269,14 +397,17 @@ bool Engine::CreateRenderResources() {
|
|||
}
|
||||
|
||||
void Engine::SetSatsVisible(bool visible) {
|
||||
stats_.SetVisible(visible);
|
||||
stats_->SetVisible(visible);
|
||||
if (visible)
|
||||
stats_.Create(CreateRenderResource<Texture>());
|
||||
stats_->Create("stats_tex");
|
||||
else
|
||||
stats_.Destory();
|
||||
stats_->Destory();
|
||||
}
|
||||
|
||||
void Engine::PrintStats() {
|
||||
std::unique_ptr<Image> Engine::PrintStats() {
|
||||
if (!stats_->IsVisible())
|
||||
return nullptr;
|
||||
|
||||
constexpr int width = 200;
|
||||
std::vector<std::string> lines;
|
||||
std::string line;
|
||||
|
@ -297,18 +428,14 @@ void Engine::PrintStats() {
|
|||
image->Create(image_width, image_height);
|
||||
image->Clear({1, 1, 1, 0.08f});
|
||||
|
||||
Worker worker(2);
|
||||
int y = margin;
|
||||
for (auto& text : lines) {
|
||||
worker.Enqueue(std::bind(&Font::Print, system_font_.get(), margin, y,
|
||||
text.c_str(), image->GetBuffer(),
|
||||
image->GetWidth()));
|
||||
system_font_->Print(margin, y, text.c_str(), image->GetBuffer(),
|
||||
image->GetWidth());
|
||||
y += line_height + margin;
|
||||
}
|
||||
worker.Join();
|
||||
|
||||
stats_.GetTexture()->Update(std::move(image));
|
||||
stats_.AutoScale();
|
||||
return image;
|
||||
}
|
||||
|
||||
} // namespace eng
|
||||
|
|
|
@ -2,13 +2,15 @@
|
|||
#define ENGINE_H
|
||||
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "../base/random.h"
|
||||
#include "../base/vecmath.h"
|
||||
#include "audio/audio_forward.h"
|
||||
#include "image_quad.h"
|
||||
#include "platform/platform_forward.h"
|
||||
#include "renderer/render_resource.h"
|
||||
|
||||
class TextureCompressor;
|
||||
|
@ -18,15 +20,19 @@ namespace eng {
|
|||
class AudioResource;
|
||||
class Font;
|
||||
class Game;
|
||||
class Drawable;
|
||||
class InputEvent;
|
||||
class Image;
|
||||
class ImageQuad;
|
||||
class Renderer;
|
||||
struct RenderCommand;
|
||||
class Platform;
|
||||
class Geometry;
|
||||
class Shader;
|
||||
class Texture;
|
||||
|
||||
class Engine {
|
||||
public:
|
||||
using CreateImageCB = std::function<std::unique_ptr<Image>()>;
|
||||
|
||||
Engine(Platform* platform, Renderer* renderer, Audio* audio);
|
||||
~Engine();
|
||||
|
||||
|
@ -42,6 +48,9 @@ class Engine {
|
|||
void LostFocus();
|
||||
void GainedFocus();
|
||||
|
||||
void AddDrawable(Drawable* drawable);
|
||||
void RemoveDrawable(Drawable* drawable);
|
||||
|
||||
void Exit();
|
||||
|
||||
// Convert size from pixels to viewport scale.
|
||||
|
@ -51,22 +60,42 @@ class Engine {
|
|||
base::Vector2 ToPosition(const base::Vector2& vec);
|
||||
|
||||
template <typename T>
|
||||
std::shared_ptr<T> CreateRenderResource() {
|
||||
std::unique_ptr<T> CreateRenderResource() {
|
||||
RenderResourceFactory<T> factory;
|
||||
return std::dynamic_pointer_cast<T>(CreateRenderResourceInternal(factory));
|
||||
std::unique_ptr<RenderResource> resource =
|
||||
CreateRenderResourceInternal(factory);
|
||||
return std::unique_ptr<T>(static_cast<T*>(resource.release()));
|
||||
}
|
||||
|
||||
std::shared_ptr<AudioResource> CreateAudioResource();
|
||||
void SetImageSource(const std::string& asset_name,
|
||||
const std::string& file_name,
|
||||
bool persistent = false);
|
||||
void SetImageSource(const std::string& asset_name,
|
||||
CreateImageCB create_image,
|
||||
bool persistent = false);
|
||||
|
||||
void RefreshImage(const std::string& asset_name);
|
||||
|
||||
std::shared_ptr<Texture> GetTexture(const std::string& asset_name);
|
||||
|
||||
std::unique_ptr<AudioResource> CreateAudioResource();
|
||||
|
||||
void AddInputEvent(std::unique_ptr<InputEvent> event);
|
||||
std::unique_ptr<InputEvent> GetNextInputEvent();
|
||||
|
||||
// Vibrate (if supported by the platform) for the specified duration.
|
||||
void Vibrate(int duration);
|
||||
|
||||
void SetImageDpi(float dpi) { image_dpi_ = dpi; }
|
||||
|
||||
void SetEnableAudio(bool enable);
|
||||
|
||||
void SetEnableVibration(bool enable) { vibration_enabled_ = enable; }
|
||||
|
||||
// Access to the render resources.
|
||||
std::shared_ptr<Geometry> GetQuad() { return quad_; }
|
||||
std::shared_ptr<Shader> GetPassThroughShader() {
|
||||
return pass_through_shader_;
|
||||
}
|
||||
std::shared_ptr<Shader> GetSolidShader() { return solid_shader_; }
|
||||
Geometry* GetQuad() { return quad_.get(); }
|
||||
Shader* GetPassThroughShader() { return pass_through_shader_.get(); }
|
||||
Shader* GetSolidShader() { return solid_shader_.get(); }
|
||||
|
||||
const Font* GetSystemFont() { return system_font_.get(); }
|
||||
|
||||
|
@ -95,7 +124,19 @@ class Engine {
|
|||
|
||||
float seconds_accumulated() const { return seconds_accumulated_; }
|
||||
|
||||
float image_dpi() const { return image_dpi_; }
|
||||
|
||||
private:
|
||||
// Class holding information about texture resources managed by engine.
|
||||
// Texture is created from an image asset if asset_file is valid. Otherwise
|
||||
// texture is created from the image returned by create_image callback.
|
||||
struct TextureResource {
|
||||
std::shared_ptr<Texture> texture;
|
||||
std::string asset_file;
|
||||
CreateImageCB create_image;
|
||||
bool persistent = false;
|
||||
};
|
||||
|
||||
static Engine* singleton;
|
||||
|
||||
Platform* platform_ = nullptr;
|
||||
|
@ -106,9 +147,9 @@ class Engine {
|
|||
|
||||
std::unique_ptr<Game> game_;
|
||||
|
||||
std::shared_ptr<Geometry> quad_;
|
||||
std::shared_ptr<Shader> pass_through_shader_;
|
||||
std::shared_ptr<Shader> solid_shader_;
|
||||
std::unique_ptr<Geometry> quad_;
|
||||
std::unique_ptr<Shader> pass_through_shader_;
|
||||
std::unique_ptr<Shader> solid_shader_;
|
||||
|
||||
base::Vector2 screen_size_ = {0, 0};
|
||||
base::Matrix4x4 projection_;
|
||||
|
@ -118,18 +159,27 @@ class Engine {
|
|||
std::unique_ptr<TextureCompressor> tex_comp_opaque_;
|
||||
std::unique_ptr<TextureCompressor> tex_comp_alpha_;
|
||||
|
||||
ImageQuad stats_;
|
||||
std::list<Drawable*> drawables_;
|
||||
|
||||
// Textures mapped by asset name.
|
||||
std::unordered_map<std::string, TextureResource> textures_;
|
||||
|
||||
std::unique_ptr<ImageQuad> stats_;
|
||||
|
||||
float fps_seconds_ = 0;
|
||||
int fps_ = 0;
|
||||
|
||||
float seconds_accumulated_ = 0.0f;
|
||||
|
||||
float image_dpi_ = 200;
|
||||
|
||||
bool vibration_enabled_ = true;
|
||||
|
||||
std::deque<std::unique_ptr<InputEvent>> input_queue_;
|
||||
|
||||
base::Random random_;
|
||||
|
||||
std::shared_ptr<RenderResource> CreateRenderResourceInternal(
|
||||
std::unique_ptr<RenderResource> CreateRenderResourceInternal(
|
||||
RenderResourceFactoryBase& factory);
|
||||
|
||||
void ContextLost();
|
||||
|
@ -137,7 +187,7 @@ class Engine {
|
|||
bool CreateRenderResources();
|
||||
|
||||
void SetSatsVisible(bool visible);
|
||||
void PrintStats();
|
||||
std::unique_ptr<Image> PrintStats();
|
||||
|
||||
Engine(const Engine&) = delete;
|
||||
Engine& operator=(const Engine&) = delete;
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
#include "font.h"
|
||||
|
||||
#include <codecvt>
|
||||
#include <locale>
|
||||
|
||||
#include "../base/log.h"
|
||||
#include "engine.h"
|
||||
#include "platform/asset_file.h"
|
||||
|
@ -123,9 +126,12 @@ void Font::CalculateBoundingBox(const std::string& text,
|
|||
|
||||
float x = 0, y = 0;
|
||||
|
||||
const char* ptr = text.c_str();
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>,char16_t> convert;
|
||||
std::u16string u16text = convert.from_bytes(text);
|
||||
const char16_t* ptr = u16text.c_str();
|
||||
|
||||
while (*ptr) {
|
||||
if (*ptr >= kFirstChar /*&& *ptr < (kFirstChar + kNumChars)*/) {
|
||||
if (*ptr >= kFirstChar && *ptr < (kFirstChar + kNumChars)) {
|
||||
stbtt_aligned_quad q;
|
||||
stbtt_GetBakedQuad(glyph_info_, kGlyphSize, kGlyphSize, *ptr - kFirstChar,
|
||||
&x, &y, &q, 1);
|
||||
|
@ -140,9 +146,8 @@ void Font::CalculateBoundingBox(const std::string& text,
|
|||
x1 = ix1;
|
||||
if (iy1 > y1)
|
||||
y1 = iy1;
|
||||
|
||||
++ptr;
|
||||
}
|
||||
++ptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -168,9 +173,12 @@ void Font::Print(int x,
|
|||
|
||||
float fx = (float)x, fy = (float)y + (float)yoff_;
|
||||
|
||||
const char* ptr = text.c_str();
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>,char16_t> convert;
|
||||
std::u16string u16text = convert.from_bytes(text);
|
||||
const char16_t* ptr = u16text.c_str();
|
||||
|
||||
while (*ptr) {
|
||||
if (*ptr >= kFirstChar /*&& *ptr < (kFirstChar + kNumChars)*/) {
|
||||
if (*ptr >= kFirstChar && *ptr < (kFirstChar + kNumChars)) {
|
||||
stbtt_aligned_quad q;
|
||||
stbtt_GetBakedQuad(glyph_info_, kGlyphSize, kGlyphSize, *ptr - kFirstChar,
|
||||
&fx, &fy, &q, 1);
|
||||
|
@ -184,9 +192,8 @@ void Font::Print(int x,
|
|||
|
||||
StretchBlit_I8_to_RGBA32(ix0, iy0, ix1, iy1, iu0, iv0, iu1, iv1, buffer,
|
||||
width, glyph_cache_.get(), kGlyphSize);
|
||||
|
||||
++ptr;
|
||||
}
|
||||
++ptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ class Font {
|
|||
enum Constants {
|
||||
kGlyphSize = 512,
|
||||
kFirstChar = 32, // ' ' (space)
|
||||
kNumChars = 96 // Covers almost all ASCII chars.
|
||||
kNumChars = 224 // Covers all ASCII chars.
|
||||
};
|
||||
|
||||
std::unique_ptr<uint8_t[]> glyph_cache_; // Image data.
|
||||
|
|
|
@ -12,8 +12,6 @@ class Game {
|
|||
|
||||
virtual void Update(float delta_time) = 0;
|
||||
|
||||
virtual void Draw(float frame_frac) = 0;
|
||||
|
||||
virtual void ContextLost() = 0;
|
||||
|
||||
virtual void LostFocus() = 0;
|
||||
|
|
|
@ -46,12 +46,10 @@ class Image {
|
|||
void GradientV(const base::Vector4& c1, const base::Vector4& c2, int height);
|
||||
|
||||
private:
|
||||
base::AlignedMem<uint8_t[]>::ScoppedPtr buffer_;
|
||||
base::AlignedMemPtr<uint8_t[]> buffer_;
|
||||
int width_ = 0;
|
||||
int height_ = 0;
|
||||
Format format_ = kRGBA32;
|
||||
|
||||
std::string name_;
|
||||
};
|
||||
|
||||
} // namespace eng
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#include "image_quad.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "../base/log.h"
|
||||
#include "engine.h"
|
||||
#include "renderer/geometry.h"
|
||||
#include "renderer/shader.h"
|
||||
|
@ -11,14 +10,20 @@ using namespace base;
|
|||
|
||||
namespace eng {
|
||||
|
||||
void ImageQuad::Create(std::shared_ptr<Texture> texture,
|
||||
void ImageQuad::Create(const std::string& asset_name,
|
||||
std::array<int, 2> num_frames,
|
||||
int frame_width,
|
||||
int frame_height) {
|
||||
texture_ = texture;
|
||||
texture_ = Engine::Get().GetTexture(asset_name);
|
||||
|
||||
num_frames_ = std::move(num_frames);
|
||||
frame_width_ = frame_width;
|
||||
frame_height_ = frame_height;
|
||||
|
||||
if ((frame_width_ > 0 && frame_height_ > 0) || texture_->IsValid())
|
||||
AutoScale();
|
||||
|
||||
asset_name_ = asset_name;
|
||||
}
|
||||
|
||||
void ImageQuad::Destory() {
|
||||
|
@ -26,13 +31,15 @@ void ImageQuad::Destory() {
|
|||
}
|
||||
|
||||
void ImageQuad::AutoScale() {
|
||||
auto& engine = Engine::Get();
|
||||
Vector2 dimensions = {GetFrameWidth(), GetFrameHeight()};
|
||||
SetScale(Engine::Get().ToScale(dimensions));
|
||||
Scale((float)Engine::Get().GetDeviceDpi() / 200.0f);
|
||||
SetScale(engine.ToScale(dimensions));
|
||||
Scale((float)engine.GetDeviceDpi() / engine.image_dpi());
|
||||
}
|
||||
|
||||
void ImageQuad::SetFrame(size_t frame) {
|
||||
assert(frame < GetNumFrames());
|
||||
DCHECK(frame < GetNumFrames())
|
||||
<< "asset: " << asset_name_ << " frame: " << frame;
|
||||
current_frame_ = frame;
|
||||
}
|
||||
|
||||
|
@ -40,8 +47,10 @@ size_t ImageQuad::GetNumFrames() const {
|
|||
return num_frames_[0] * num_frames_[1];
|
||||
}
|
||||
|
||||
void ImageQuad::Draw() {
|
||||
if (!IsVisible() || !texture_ || !texture_->IsValid())
|
||||
void ImageQuad::Draw(float frame_frac) {
|
||||
DCHECK(IsVisible());
|
||||
|
||||
if (!texture_ || !texture_->IsValid())
|
||||
return;
|
||||
|
||||
texture_->Activate();
|
||||
|
@ -49,8 +58,7 @@ void ImageQuad::Draw() {
|
|||
Vector2 tex_scale = {GetFrameWidth() / texture_->GetWidth(),
|
||||
GetFrameHeight() / texture_->GetHeight()};
|
||||
|
||||
std::shared_ptr<Geometry> quad = Engine::Get().GetQuad();
|
||||
std::shared_ptr<Shader> shader = Engine::Get().GetPassThroughShader();
|
||||
Shader* shader = Engine::Get().GetPassThroughShader();
|
||||
|
||||
shader->Activate();
|
||||
shader->SetUniform("offset", offset_);
|
||||
|
@ -63,7 +71,7 @@ void ImageQuad::Draw() {
|
|||
shader->SetUniform("color", color_);
|
||||
shader->SetUniform("texture", 0);
|
||||
|
||||
quad->Draw();
|
||||
Engine::Get().GetQuad()->Draw();
|
||||
}
|
||||
|
||||
float ImageQuad::GetFrameWidth() const {
|
||||
|
@ -78,9 +86,8 @@ float ImageQuad::GetFrameHeight() const {
|
|||
|
||||
// Return the uv offset for the given frame.
|
||||
Vector2 ImageQuad::GetUVOffset(int frame) const {
|
||||
assert(frame < num_frames_[0] * num_frames_[1]);
|
||||
if (num_frames_[0] == 1 && num_frames_[1] == 1)
|
||||
return {0, 0};
|
||||
DCHECK(frame < GetNumFrames())
|
||||
<< "asset: " << asset_name_ << " frame: " << frame;
|
||||
return {(float)(frame % num_frames_[0]), (float)(frame / num_frames_[0])};
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace eng {
|
||||
|
||||
|
@ -16,7 +17,7 @@ class ImageQuad : public Animatable {
|
|||
ImageQuad() = default;
|
||||
~ImageQuad() override = default;
|
||||
|
||||
void Create(std::shared_ptr<Texture> texture,
|
||||
void Create(const std::string& asset_name,
|
||||
std::array<int, 2> num_frames = {1, 1},
|
||||
int frame_width = 0,
|
||||
int frame_height = 0);
|
||||
|
@ -32,9 +33,8 @@ class ImageQuad : public Animatable {
|
|||
void SetColor(const base::Vector4& color) override { color_ = color; }
|
||||
base::Vector4 GetColor() const override { return color_; }
|
||||
|
||||
void Draw();
|
||||
|
||||
std::shared_ptr<Texture> GetTexture() { return texture_; }
|
||||
// Drawable interface.
|
||||
void Draw(float frame_frac) override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<Texture> texture_;
|
||||
|
@ -46,6 +46,8 @@ class ImageQuad : public Animatable {
|
|||
|
||||
base::Vector4 color_ = {1, 1, 1, 1};
|
||||
|
||||
std::string asset_name_;
|
||||
|
||||
float GetFrameWidth() const;
|
||||
float GetFrameHeight() const;
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#ifndef INPUT_EVENT_H
|
||||
#define INPUT_EVENT_H
|
||||
|
||||
#include <cassert>
|
||||
#include "../base/log.h"
|
||||
#include "../base/vecmath.h"
|
||||
|
||||
namespace eng {
|
||||
|
@ -10,37 +10,38 @@ class InputEvent {
|
|||
public:
|
||||
enum Type {
|
||||
kInvalid,
|
||||
kTap,
|
||||
kDoubleTap,
|
||||
kDragStart,
|
||||
kDrag,
|
||||
kDragEnd,
|
||||
kDragCancel,
|
||||
kPinchStart,
|
||||
kPinch,
|
||||
kNavigateBack,
|
||||
kKeyPress,
|
||||
kType_Max // Not a type.
|
||||
};
|
||||
|
||||
InputEvent(Type type, size_t pointer_id)
|
||||
: type_(type), pointer_id_(pointer_id) {}
|
||||
InputEvent(Type type, size_t pointer_id, const base::Vector2& vec)
|
||||
: type_(type), pointer_id_(pointer_id), vec_(vec) {}
|
||||
InputEvent(Type type) : type_(type) {}
|
||||
InputEvent(Type type, const base::Vector2& vec1)
|
||||
: type_(type), vec_{vec1, {0, 0}} {}
|
||||
InputEvent(Type type, const base::Vector2& vec1, const base::Vector2& vec2)
|
||||
: type_(type), vec_{vec1, vec2} {}
|
||||
InputEvent(Type type, char key) : type_(type), key_(key) {}
|
||||
~InputEvent() = default;
|
||||
|
||||
Type GetType() { return type_; }
|
||||
base::Vector2 GetVector(size_t i) {
|
||||
assert(i < 2);
|
||||
return vec_[i];
|
||||
Type GetType() const { return type_; }
|
||||
|
||||
size_t GetPointerId() const { return pointer_id_; }
|
||||
|
||||
base::Vector2 GetVector(size_t i) const {
|
||||
DCHECK(i < 2);
|
||||
return vec_;
|
||||
}
|
||||
char GetKeyPress() { return key_; }
|
||||
|
||||
char GetKeyPress() const { return key_; }
|
||||
|
||||
private:
|
||||
Type type_ = kInvalid;
|
||||
base::Vector2 vec_[2] = {{0, 0}, {0, 0}};
|
||||
size_t pointer_id_ = 0;
|
||||
base::Vector2 vec_ = {0, 0};
|
||||
char key_ = 0;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#include "mesh.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <cassert>
|
||||
|
||||
#include "../base/log.h"
|
||||
#include "../third_party/jsoncpp/json.h"
|
||||
|
@ -134,8 +133,7 @@ bool Mesh::Load(const std::string& file_name) {
|
|||
*((unsigned short*)dst) = (unsigned short)vertices[i].asUInt();
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
return false;
|
||||
NOTREACHED << "- Unknown data type: " << data_type;
|
||||
}
|
||||
dst += type_size;
|
||||
++i;
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
#include <assert.h>
|
||||
#include <string>
|
||||
#include "../../base/log.h"
|
||||
#include "asset_file.h"
|
||||
|
|
|
@ -1,88 +1,20 @@
|
|||
#ifndef PLATFORM_H
|
||||
#define PLATFORM_H
|
||||
|
||||
#include <exception>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "../../base/timer.h"
|
||||
#include "../audio/audio_forward.h"
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
struct android_app;
|
||||
struct AInputEvent;
|
||||
|
||||
namespace ndk_helper {
|
||||
class TapDetector;
|
||||
class PinchDetector;
|
||||
class DragDetector;
|
||||
} // namespace ndk_helper
|
||||
#include "platform_android.h"
|
||||
#elif defined(__linux__)
|
||||
#include "platform_linux.h"
|
||||
#endif
|
||||
|
||||
namespace eng {
|
||||
|
||||
class Renderer;
|
||||
class Engine;
|
||||
|
||||
class Platform {
|
||||
public:
|
||||
Platform();
|
||||
~Platform();
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
void Initialize(android_app* app);
|
||||
using Platform = PlatformAndroid;
|
||||
#elif defined(__linux__)
|
||||
void Initialize();
|
||||
using Platform = PlatformLinux;
|
||||
#endif
|
||||
|
||||
void Shutdown();
|
||||
|
||||
void Update();
|
||||
|
||||
void RunMainLoop();
|
||||
|
||||
void Exit();
|
||||
|
||||
int GetDeviceDpi() const { return device_dpi_; }
|
||||
|
||||
const std::string& GetRootPath() const { return root_path_; }
|
||||
|
||||
bool mobile_device() const { return mobile_device_; }
|
||||
|
||||
static class InternalError : public std::exception {
|
||||
} internal_error;
|
||||
|
||||
private:
|
||||
base::Timer timer_;
|
||||
|
||||
bool mobile_device_ = false;
|
||||
int device_dpi_ = 200;
|
||||
std::string root_path_;
|
||||
|
||||
bool has_focus_ = false;
|
||||
bool should_exit_ = false;
|
||||
|
||||
std::unique_ptr<Audio> audio_;
|
||||
std::unique_ptr<Renderer> renderer_;
|
||||
std::unique_ptr<Engine> engine_;
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
android_app* app_ = nullptr;
|
||||
|
||||
std::unique_ptr<ndk_helper::TapDetector> tap_detector_;
|
||||
std::unique_ptr<ndk_helper::PinchDetector> pinch_detector_;
|
||||
std::unique_ptr<ndk_helper::DragDetector> drag_detector_;
|
||||
|
||||
static int32_t HandleInput(android_app* app, AInputEvent* event);
|
||||
static void HandleCmd(android_app* app, int32_t cmd);
|
||||
#else
|
||||
|
||||
#endif
|
||||
|
||||
Platform(const Platform&) = delete;
|
||||
Platform& operator=(const Platform&) = delete;
|
||||
};
|
||||
|
||||
} // namespace eng
|
||||
|
||||
#endif // PLATFORM_H
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
#include "platform.h"
|
||||
#include "platform_android.h"
|
||||
|
||||
#include <android_native_app_glue.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "../../base/file.h"
|
||||
#include "../../base/log.h"
|
||||
#include "../../third_party/android/gestureDetector.h"
|
||||
#include "../audio/audio_oboe.h"
|
||||
#include "../../base/task_runner.h"
|
||||
#include "../audio/audio.h"
|
||||
#include "../engine.h"
|
||||
#include "../input_event.h"
|
||||
#include "../renderer/renderer.h"
|
||||
|
@ -44,6 +45,39 @@ std::string GetApkPath(ANativeActivity* activity) {
|
|||
return apk_path;
|
||||
}
|
||||
|
||||
void Vibrate(ANativeActivity* activity, int duration) {
|
||||
JNIEnv* env = nullptr;
|
||||
activity->vm->AttachCurrentThread(&env, nullptr);
|
||||
|
||||
jclass activity_clazz = env->GetObjectClass(activity->clazz);
|
||||
jclass context_clazz = env->FindClass("android/content/Context");
|
||||
|
||||
jfieldID vibrator_service_id = env->GetStaticFieldID(
|
||||
context_clazz, "VIBRATOR_SERVICE", "Ljava/lang/String;");
|
||||
jobject vibrator_service_str =
|
||||
env->GetStaticObjectField(context_clazz, vibrator_service_id);
|
||||
|
||||
jmethodID get_system_service_id =
|
||||
env->GetMethodID(activity_clazz, "getSystemService",
|
||||
"(Ljava/lang/String;)Ljava/lang/Object;");
|
||||
jobject vibrator_service_obj = env->CallObjectMethod(
|
||||
activity->clazz, get_system_service_id, vibrator_service_str);
|
||||
|
||||
jclass vibrator_service_clazz = env->GetObjectClass(vibrator_service_obj);
|
||||
jmethodID vibrate_id =
|
||||
env->GetMethodID(vibrator_service_clazz, "vibrate", "(J)V");
|
||||
|
||||
jlong length = duration;
|
||||
env->CallVoidMethod(vibrator_service_obj, vibrate_id, length);
|
||||
|
||||
env->DeleteLocalRef(vibrator_service_obj);
|
||||
env->DeleteLocalRef(vibrator_service_clazz);
|
||||
env->DeleteLocalRef(vibrator_service_str);
|
||||
env->DeleteLocalRef(context_clazz);
|
||||
env->DeleteLocalRef(activity_clazz);
|
||||
activity->vm->DetachCurrentThread();
|
||||
}
|
||||
|
||||
int32_t getDensityDpi(android_app* app) {
|
||||
AConfiguration* config = AConfiguration_new();
|
||||
AConfiguration_fromAssetManager(config, app->activity->assetManager);
|
||||
|
@ -56,11 +90,11 @@ int32_t getDensityDpi(android_app* app) {
|
|||
|
||||
namespace eng {
|
||||
|
||||
Platform::Platform() = default;
|
||||
Platform::~Platform() = default;
|
||||
PlatformAndroid::PlatformAndroid() = default;
|
||||
PlatformAndroid::~PlatformAndroid() = default;
|
||||
|
||||
int32_t Platform::HandleInput(android_app* app, AInputEvent* event) {
|
||||
Platform* platform = reinterpret_cast<Platform*>(app->userData);
|
||||
int32_t PlatformAndroid::HandleInput(android_app* app, AInputEvent* event) {
|
||||
PlatformAndroid* platform = reinterpret_cast<PlatformAndroid*>(app->userData);
|
||||
|
||||
if (!platform->engine_)
|
||||
return 0;
|
||||
|
@ -73,88 +107,74 @@ int32_t Platform::HandleInput(android_app* app, AInputEvent* event) {
|
|||
platform->engine_->AddInputEvent(std::move(input_event));
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) {
|
||||
ndk_helper::GESTURE_STATE tap_state =
|
||||
platform->tap_detector_->Detect(event);
|
||||
ndk_helper::GESTURE_STATE drag_state =
|
||||
platform->drag_detector_->Detect(event);
|
||||
ndk_helper::GESTURE_STATE pinch_state =
|
||||
platform->pinch_detector_->Detect(event);
|
||||
|
||||
// Tap detector has a priority over other detectors
|
||||
if (tap_state == ndk_helper::GESTURE_STATE_ACTION) {
|
||||
platform->engine_->AddInputEvent(
|
||||
std::make_unique<InputEvent>(InputEvent::kDragCancel));
|
||||
// Detect tap
|
||||
Vector2 v;
|
||||
platform->tap_detector_->GetPointer(v);
|
||||
v = platform->engine_->ToPosition(v);
|
||||
// DLOG << "Tap: " << v;
|
||||
auto input_event =
|
||||
std::make_unique<InputEvent>(InputEvent::kTap, v * Vector2(1, -1));
|
||||
platform->engine_->AddInputEvent(std::move(input_event));
|
||||
} else {
|
||||
// Handle drag state
|
||||
if (drag_state & ndk_helper::GESTURE_STATE_START) {
|
||||
// Otherwise, start dragging
|
||||
Vector2 v;
|
||||
platform->drag_detector_->GetPointer(v);
|
||||
v = platform->engine_->ToPosition(v);
|
||||
// DLOG << "drag-start: " << v;
|
||||
auto input_event = std::make_unique<InputEvent>(InputEvent::kDragStart,
|
||||
v * Vector2(1, -1));
|
||||
platform->engine_->AddInputEvent(std::move(input_event));
|
||||
} else if (drag_state & ndk_helper::GESTURE_STATE_MOVE) {
|
||||
Vector2 v;
|
||||
platform->drag_detector_->GetPointer(v);
|
||||
v = platform->engine_->ToPosition(v);
|
||||
// DLOG << "drag: " << v;
|
||||
auto input_event =
|
||||
std::make_unique<InputEvent>(InputEvent::kDrag, v * Vector2(1, -1));
|
||||
platform->engine_->AddInputEvent(std::move(input_event));
|
||||
} else if (drag_state & ndk_helper::GESTURE_STATE_END) {
|
||||
// DLOG << "drag-end!";
|
||||
auto input_event = std::make_unique<InputEvent>(InputEvent::kDragEnd);
|
||||
platform->engine_->AddInputEvent(std::move(input_event));
|
||||
}
|
||||
|
||||
// Handle pinch state
|
||||
if (pinch_state & ndk_helper::GESTURE_STATE_START) {
|
||||
platform->engine_->AddInputEvent(
|
||||
std::make_unique<InputEvent>(InputEvent::kDragCancel));
|
||||
// Start new pinch
|
||||
Vector2 v1;
|
||||
Vector2 v2;
|
||||
platform->pinch_detector_->GetPointers(v1, v2);
|
||||
v1 = platform->engine_->ToPosition(v1);
|
||||
v2 = platform->engine_->ToPosition(v2);
|
||||
// DLOG << "pinch-start: " << v1 << " " << v2;
|
||||
auto input_event = std::make_unique<InputEvent>(
|
||||
InputEvent::kPinchStart, v1 * Vector2(1, -1), v2 * Vector2(1, -1));
|
||||
platform->engine_->AddInputEvent(std::move(input_event));
|
||||
} else if (pinch_state & ndk_helper::GESTURE_STATE_MOVE) {
|
||||
// Multi touch
|
||||
// Start new pinch
|
||||
Vector2 v1;
|
||||
Vector2 v2;
|
||||
platform->pinch_detector_->GetPointers(v1, v2);
|
||||
v1 = platform->engine_->ToPosition(v1);
|
||||
v2 = platform->engine_->ToPosition(v2);
|
||||
// DLOG << "pinch: " << v1 << " " << v2;
|
||||
auto input_event = std::make_unique<InputEvent>(
|
||||
InputEvent::kPinch, v1 * Vector2(1, -1), v2 * Vector2(1, -1));
|
||||
platform->engine_->AddInputEvent(std::move(input_event));
|
||||
}
|
||||
} else if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) {
|
||||
int32_t action = AMotionEvent_getAction(event);
|
||||
int32_t index = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >>
|
||||
AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
|
||||
uint32_t flags = action & AMOTION_EVENT_ACTION_MASK;
|
||||
int32_t count = AMotionEvent_getPointerCount(event);
|
||||
int32_t pointer_id = AMotionEvent_getPointerId(event, index);
|
||||
Vector2 pos[2] = {platform->pointer_pos_[0], platform->pointer_pos_[1]};
|
||||
for (auto i = 0; i < count; ++i) {
|
||||
int32_t id = AMotionEvent_getPointerId(event, i);
|
||||
pos[id] = {AMotionEvent_getX(event, i), AMotionEvent_getY(event, i)};
|
||||
pos[id] = platform->engine_->ToPosition(pos[id]);
|
||||
}
|
||||
|
||||
if (pointer_id >= 2)
|
||||
return 0;
|
||||
|
||||
std::unique_ptr<InputEvent> input_event;
|
||||
|
||||
switch (flags) {
|
||||
case AMOTION_EVENT_ACTION_DOWN:
|
||||
case AMOTION_EVENT_ACTION_POINTER_DOWN:
|
||||
DLOG << "AMOTION_EVENT_ACTION_DOWN - pointer_id: " << pointer_id;
|
||||
platform->pointer_pos_[pointer_id] = pos[pointer_id];
|
||||
platform->pointer_down_[pointer_id] = true;
|
||||
input_event =
|
||||
std::make_unique<InputEvent>(InputEvent::kDragStart, pointer_id,
|
||||
pos[pointer_id] * Vector2(1, -1));
|
||||
break;
|
||||
|
||||
case AMOTION_EVENT_ACTION_UP:
|
||||
case AMOTION_EVENT_ACTION_POINTER_UP:
|
||||
DLOG << "AMOTION_EVENT_ACTION_UP - pointer_id: " << pointer_id;
|
||||
platform->pointer_pos_[pointer_id] = pos[pointer_id];
|
||||
platform->pointer_down_[pointer_id] = false;
|
||||
input_event = std::make_unique<InputEvent>(
|
||||
InputEvent::kDragEnd, pointer_id, pos[pointer_id] * Vector2(1, -1));
|
||||
break;
|
||||
|
||||
case AMOTION_EVENT_ACTION_MOVE:
|
||||
if (platform->pointer_down_[0] && pos[0] != platform->pointer_pos_[0]) {
|
||||
platform->pointer_pos_[0] = pos[0];
|
||||
input_event = std::make_unique<InputEvent>(InputEvent::kDrag, 0,
|
||||
pos[0] * Vector2(1, -1));
|
||||
}
|
||||
if (platform->pointer_down_[1] && pos[1] != platform->pointer_pos_[1]) {
|
||||
platform->pointer_pos_[1] = pos[1];
|
||||
input_event = std::make_unique<InputEvent>(InputEvent::kDrag, 1,
|
||||
pos[1] * Vector2(1, -1));
|
||||
}
|
||||
break;
|
||||
|
||||
case AMOTION_EVENT_ACTION_CANCEL:
|
||||
input_event = std::make_unique<InputEvent>(InputEvent::kDragCancel);
|
||||
break;
|
||||
}
|
||||
|
||||
if (input_event) {
|
||||
platform->engine_->AddInputEvent(std::move(input_event));
|
||||
return 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Platform::HandleCmd(android_app* app, int32_t cmd) {
|
||||
Platform* platform = reinterpret_cast<Platform*>(app->userData);
|
||||
void PlatformAndroid::HandleCmd(android_app* app, int32_t cmd) {
|
||||
PlatformAndroid* platform = reinterpret_cast<PlatformAndroid*>(app->userData);
|
||||
|
||||
switch (cmd) {
|
||||
case APP_CMD_SAVE_STATE:
|
||||
|
@ -216,26 +236,11 @@ void Platform::HandleCmd(android_app* app, int32_t cmd) {
|
|||
}
|
||||
}
|
||||
|
||||
void Platform::Initialize(android_app* app) {
|
||||
LOG << "Initializing platform.";
|
||||
void PlatformAndroid::Initialize(android_app* app) {
|
||||
PlatformBase::Initialize();
|
||||
|
||||
app_ = app;
|
||||
|
||||
audio_ = std::make_unique<AudioOboe>();
|
||||
if (!audio_->Initialize()) {
|
||||
LOG << "Failed to initialize audio system.";
|
||||
throw internal_error;
|
||||
}
|
||||
|
||||
renderer_ = std::make_unique<Renderer>();
|
||||
|
||||
tap_detector_ = std::make_unique<ndk_helper::TapDetector>();
|
||||
drag_detector_ = std::make_unique<ndk_helper::DragDetector>();
|
||||
pinch_detector_ = std::make_unique<ndk_helper::PinchDetector>();
|
||||
|
||||
tap_detector_->SetConfiguration(app_->config);
|
||||
drag_detector_->SetConfiguration(app_->config);
|
||||
pinch_detector_->SetConfiguration(app_->config);
|
||||
|
||||
mobile_device_ = true;
|
||||
|
||||
root_path_ = GetApkPath(app->activity);
|
||||
|
@ -245,13 +250,13 @@ void Platform::Initialize(android_app* app) {
|
|||
LOG << "Device DPI: " << device_dpi_;
|
||||
|
||||
app->userData = reinterpret_cast<void*>(this);
|
||||
app->onAppCmd = Platform::HandleCmd;
|
||||
app->onInputEvent = Platform::HandleInput;
|
||||
app->onAppCmd = PlatformAndroid::HandleCmd;
|
||||
app->onInputEvent = PlatformAndroid::HandleInput;
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
void Platform::Update() {
|
||||
void PlatformAndroid::Update() {
|
||||
int id;
|
||||
int events;
|
||||
android_poll_source* source;
|
||||
|
@ -270,19 +275,23 @@ void Platform::Update() {
|
|||
}
|
||||
}
|
||||
|
||||
void Platform::Exit() {
|
||||
void PlatformAndroid::Exit() {
|
||||
ANativeActivity_finish(app_->activity);
|
||||
}
|
||||
|
||||
void PlatformAndroid::Vibrate(int duration) {
|
||||
::Vibrate(app_->activity, duration);
|
||||
}
|
||||
|
||||
} // namespace eng
|
||||
|
||||
void android_main(android_app* app) {
|
||||
eng::Platform platform;
|
||||
eng::PlatformAndroid platform;
|
||||
try {
|
||||
platform.Initialize(app);
|
||||
platform.RunMainLoop();
|
||||
platform.Shutdown();
|
||||
} catch (eng::Platform::InternalError& e) {
|
||||
} catch (eng::PlatformBase::InternalError& e) {
|
||||
}
|
||||
_exit(0);
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -3,6 +3,7 @@
|
|||
#include <thread>
|
||||
|
||||
#include "../../base/log.h"
|
||||
#include "../../base/task_runner.h"
|
||||
#include "../audio/audio.h"
|
||||
#include "../engine.h"
|
||||
#include "../renderer/renderer.h"
|
||||
|
@ -10,18 +11,41 @@
|
|||
// Save battery on mobile devices.
|
||||
#define USE_SLEEP
|
||||
|
||||
using namespace base;
|
||||
|
||||
namespace eng {
|
||||
|
||||
Platform::InternalError Platform::internal_error;
|
||||
PlatformBase::InternalError PlatformBase::internal_error;
|
||||
|
||||
void Platform::Shutdown() {
|
||||
PlatformBase::PlatformBase() = default;
|
||||
|
||||
PlatformBase::~PlatformBase() = default;
|
||||
|
||||
void PlatformBase::Initialize() {
|
||||
LOG << "Initializing platform.";
|
||||
|
||||
worker_.Initialize();
|
||||
TaskRunner::CreateThreadLocalTaskRunner();
|
||||
|
||||
audio_ = std::make_unique<Audio>();
|
||||
if (!audio_->Initialize()) {
|
||||
LOG << "Failed to initialize audio system.";
|
||||
throw internal_error;
|
||||
}
|
||||
|
||||
renderer_ = std::make_unique<Renderer>();
|
||||
}
|
||||
|
||||
void PlatformBase::Shutdown() {
|
||||
LOG << "Shutting down platform.";
|
||||
|
||||
audio_->Shutdown();
|
||||
renderer_->Shutdown();
|
||||
}
|
||||
|
||||
void Platform::RunMainLoop() {
|
||||
engine_ = std::make_unique<Engine>(this, renderer_.get(), audio_.get());
|
||||
void PlatformBase::RunMainLoop() {
|
||||
engine_ = std::make_unique<Engine>(static_cast<Platform*>(this),
|
||||
renderer_.get(), audio_.get());
|
||||
if (!engine_->Initialize()) {
|
||||
LOG << "Failed to initialize the engine.";
|
||||
throw internal_error;
|
||||
|
@ -59,13 +83,17 @@ void Platform::RunMainLoop() {
|
|||
|
||||
// Subdivide the frame time.
|
||||
while (accumulator >= time_step) {
|
||||
Update();
|
||||
TaskRunner::GetThreadLocalTaskRunner()->SingleConsumerRun();
|
||||
|
||||
static_cast<Platform*>(this)->Update();
|
||||
engine_->Update(time_step);
|
||||
|
||||
if (should_exit_) {
|
||||
worker_.Shutdown();
|
||||
engine_->Shutdown();
|
||||
engine_.reset();
|
||||
return;
|
||||
}
|
||||
engine_->Update(time_step);
|
||||
accumulator -= time_step;
|
||||
};
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -1,11 +1,14 @@
|
|||
#include "platform.h"
|
||||
#include "platform_linux.h"
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "../../base/log.h"
|
||||
#include "../../base/task_runner.h"
|
||||
#include "../../base/vecmath.h"
|
||||
#include "../audio/audio_alsa.h"
|
||||
#include "../audio/audio.h"
|
||||
#include "../engine.h"
|
||||
#include "../input_event.h"
|
||||
#include "../renderer/renderer.h"
|
||||
|
@ -14,39 +17,30 @@ using namespace base;
|
|||
|
||||
namespace eng {
|
||||
|
||||
Platform::Platform() = default;
|
||||
Platform::~Platform() = default;
|
||||
PlatformLinux::PlatformLinux() = default;
|
||||
PlatformLinux::~PlatformLinux() = default;
|
||||
|
||||
void PlatformLinux::Initialize() {
|
||||
PlatformBase::Initialize();
|
||||
|
||||
void Platform::Initialize() {
|
||||
root_path_ = "../../";
|
||||
LOG << "Root path: " << root_path_.c_str();
|
||||
|
||||
audio_ = std::make_unique<AudioAlsa>();
|
||||
if (!audio_->Initialize()) {
|
||||
LOG << "Failed to initialize audio system.";
|
||||
throw internal_error;
|
||||
}
|
||||
|
||||
renderer_ = std::make_unique<Renderer>();
|
||||
if (!renderer_->Initialize()) {
|
||||
LOG << "Failed to initialize renderer.";
|
||||
throw internal_error;
|
||||
}
|
||||
LOG << "Initialized the renderer.";
|
||||
|
||||
Display* display = renderer_->display();
|
||||
Window window = renderer_->window();
|
||||
XSelectInput(
|
||||
display, window,
|
||||
KeyPressMask | Button1MotionMask | ButtonPressMask | ButtonReleaseMask);
|
||||
XSelectInput(display, window,
|
||||
KeyPressMask | Button1MotionMask | ButtonPressMask |
|
||||
ButtonReleaseMask | FocusChangeMask);
|
||||
Atom WM_DELETE_WINDOW = XInternAtom(display, "WM_DELETE_WINDOW", false);
|
||||
XSetWMProtocols(display, window, &WM_DELETE_WINDOW, 1);
|
||||
}
|
||||
|
||||
void Platform::Update() {
|
||||
if (!engine_)
|
||||
return;
|
||||
|
||||
void PlatformLinux::Update() {
|
||||
Display* display = renderer_->display();
|
||||
while (XPending(display)) {
|
||||
XEvent e;
|
||||
|
@ -55,7 +49,7 @@ void Platform::Update() {
|
|||
case KeyPress: {
|
||||
KeySym key = XLookupKeysym(&e.xkey, 0);
|
||||
auto input_event =
|
||||
std::make_unique<InputEvent>(InputEvent::kKeyPress, key);
|
||||
std::make_unique<InputEvent>(InputEvent::kKeyPress, (char)key);
|
||||
engine_->AddInputEvent(std::move(input_event));
|
||||
// TODO: e.xkey.state & (ShiftMask | ControlMask | Mod1Mask | Mod4Mask))
|
||||
break;
|
||||
|
@ -64,8 +58,8 @@ void Platform::Update() {
|
|||
Vector2 v(e.xmotion.x, e.xmotion.y);
|
||||
v = engine_->ToPosition(v);
|
||||
// DLOG << "drag: " << v;
|
||||
auto input_event =
|
||||
std::make_unique<InputEvent>(InputEvent::kDrag, v * Vector2(1, -1));
|
||||
auto input_event = std::make_unique<InputEvent>(InputEvent::kDrag, 0,
|
||||
v * Vector2(1, -1));
|
||||
engine_->AddInputEvent(std::move(input_event));
|
||||
break;
|
||||
}
|
||||
|
@ -75,19 +69,30 @@ void Platform::Update() {
|
|||
v = engine_->ToPosition(v);
|
||||
// DLOG << "drag-start: " << v;
|
||||
auto input_event = std::make_unique<InputEvent>(
|
||||
InputEvent::kDragStart, v * Vector2(1, -1));
|
||||
InputEvent::kDragStart, 0, v * Vector2(1, -1));
|
||||
engine_->AddInputEvent(std::move(input_event));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ButtonRelease: {
|
||||
if (e.xbutton.button == 1) {
|
||||
Vector2 v(e.xbutton.x, e.xbutton.y);
|
||||
v = engine_->ToPosition(v);
|
||||
// DLOG << "drag-end!";
|
||||
auto input_event = std::make_unique<InputEvent>(InputEvent::kDragEnd);
|
||||
auto input_event = std::make_unique<InputEvent>(
|
||||
InputEvent::kDragEnd, 0, v * Vector2(1, -1));
|
||||
engine_->AddInputEvent(std::move(input_event));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case FocusOut: {
|
||||
engine_->LostFocus();
|
||||
break;
|
||||
}
|
||||
case FocusIn: {
|
||||
engine_->GainedFocus();
|
||||
break;
|
||||
}
|
||||
case ClientMessage: {
|
||||
// WM_DELETE_WINDOW is the only registered type for now.
|
||||
should_exit_ = true;
|
||||
|
@ -97,19 +102,19 @@ void Platform::Update() {
|
|||
}
|
||||
}
|
||||
|
||||
void Platform::Exit() {
|
||||
void PlatformLinux::Exit() {
|
||||
should_exit_ = true;
|
||||
}
|
||||
|
||||
} // namespace eng
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
eng::Platform platform;
|
||||
eng::PlatformLinux platform;
|
||||
try {
|
||||
platform.Initialize();
|
||||
platform.RunMainLoop();
|
||||
platform.Shutdown();
|
||||
} catch (eng::Platform::InternalError& e) {
|
||||
} catch (eng::PlatformBase::InternalError& e) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
|
|
|
@ -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
|
|
@ -1,7 +1,5 @@
|
|||
#include "geometry.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "../engine.h"
|
||||
#include "../mesh.h"
|
||||
#include "render_command.h"
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
#elif defined(__linux__)
|
||||
#include "../../third_party/glew/glew.h"
|
||||
#include "../../third_party/glew/glxew.h"
|
||||
|
||||
// Define the missing format for the etc1
|
||||
#ifndef GL_ETC1_RGB8_OES
|
||||
|
|
|
@ -14,8 +14,6 @@
|
|||
|
||||
namespace eng {
|
||||
|
||||
RENDER_COMMAND_IMPL(CmdEableBlend, false);
|
||||
RENDER_COMMAND_IMPL(CmdClear, false);
|
||||
RENDER_COMMAND_IMPL(CmdPresent, false);
|
||||
RENDER_COMMAND_IMPL(CmdUpdateTexture, true);
|
||||
RENDER_COMMAND_IMPL(CmdDestoryTexture, true);
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <array>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "../../base/hash.h"
|
||||
#include "../../base/vecmath.h"
|
||||
#include "renderer_types.h"
|
||||
|
@ -14,11 +15,13 @@ class Image;
|
|||
class ShaderSource;
|
||||
class Mesh;
|
||||
|
||||
#define RENDER_COMMAND_BEGIN(NAME) \
|
||||
#define RENDER_COMMAND_BEGIN(NAME) \
|
||||
struct NAME : RenderCommand { \
|
||||
static constexpr CommandId CMD_ID = HHASH(#NAME); \
|
||||
NAME();
|
||||
#define RENDER_COMMAND_END };
|
||||
#define RENDER_COMMAND_END \
|
||||
} \
|
||||
;
|
||||
|
||||
struct RenderCommand {
|
||||
using CommandId = size_t;
|
||||
|
@ -44,13 +47,6 @@ struct RenderCommand {
|
|||
#endif
|
||||
};
|
||||
|
||||
RENDER_COMMAND_BEGIN(CmdEableBlend)
|
||||
RENDER_COMMAND_END
|
||||
|
||||
RENDER_COMMAND_BEGIN(CmdClear)
|
||||
std::array<float, 4> rgba;
|
||||
RENDER_COMMAND_END
|
||||
|
||||
RENDER_COMMAND_BEGIN(CmdPresent)
|
||||
RENDER_COMMAND_END
|
||||
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
#include "render_resource.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "renderer.h"
|
||||
|
||||
namespace eng {
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
#include "renderer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <sstream>
|
||||
|
||||
#include "../../base/log.h"
|
||||
#include "../../base/vecmath.h"
|
||||
#ifdef THREADED_RENDERING
|
||||
#include "../../base/task_runner.h"
|
||||
#endif // THREADED_RENDERING
|
||||
#include "../image.h"
|
||||
#include "../mesh.h"
|
||||
#include "../shader_source.h"
|
||||
|
@ -15,6 +17,8 @@
|
|||
#include "shader.h"
|
||||
#include "texture.h"
|
||||
|
||||
using namespace base;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr GLenum kGlPrimitive[eng::kPrimitive_Max] = {GL_TRIANGLES,
|
||||
|
@ -31,20 +35,17 @@ const std::string kAttributeNames[eng::kAttribType_Max] = {
|
|||
|
||||
namespace eng {
|
||||
|
||||
Renderer::Renderer() = default;
|
||||
|
||||
Renderer::~Renderer() {
|
||||
TerminateWorker();
|
||||
}
|
||||
|
||||
void Renderer::SetContextLostCB(base::Closure cb) {
|
||||
context_lost_cb_ = std::move(cb);
|
||||
}
|
||||
|
||||
void Renderer::Update() {
|
||||
#ifdef THREADED_RENDERING
|
||||
task_runner_.Run();
|
||||
Renderer::Renderer()
|
||||
: main_thread_task_runner_(TaskRunner::GetThreadLocalTaskRunner()) {}
|
||||
#else
|
||||
Renderer::Renderer() = default;
|
||||
#endif // THREADED_RENDERING
|
||||
|
||||
Renderer::~Renderer() = default;
|
||||
|
||||
void Renderer::SetContextLostCB(Closure cb) {
|
||||
context_lost_cb_ = std::move(cb);
|
||||
}
|
||||
|
||||
void Renderer::ContextLost() {
|
||||
|
@ -59,7 +60,7 @@ void Renderer::ContextLost() {
|
|||
InvalidateAllResources();
|
||||
|
||||
#ifdef THREADED_RENDERING
|
||||
task_runner_.Enqueue(context_lost_cb_);
|
||||
main_thread_task_runner_->EnqueueTask(HERE, context_lost_cb_);
|
||||
#else
|
||||
context_lost_cb_();
|
||||
#endif // THREADED_RENDERING
|
||||
|
@ -79,7 +80,7 @@ std::unique_ptr<RenderResource> Renderer::CreateResource(
|
|||
else if (factory.IsTypeOf<Texture>())
|
||||
impl_data = std::make_shared<TextureOpenGL>();
|
||||
else
|
||||
assert(false);
|
||||
NOTREACHED << "- Unknown resource type.";
|
||||
|
||||
unsigned resource_id = ++last_id;
|
||||
auto resource = factory.Create(resource_id, impl_data, this);
|
||||
|
@ -105,7 +106,7 @@ void Renderer::EnqueueCommand(std::unique_ptr<RenderCommand> cmd) {
|
|||
return;
|
||||
}
|
||||
|
||||
bool new_frame = cmd->cmd_id == HHASH("CmdPresent");
|
||||
bool new_frame = cmd->cmd_id == CmdPresent::CMD_ID;
|
||||
draw_commands_[1].push_back(std::move(cmd));
|
||||
if (new_frame) {
|
||||
render_queue_size_ = draw_commands_[1].size();
|
||||
|
@ -174,7 +175,8 @@ bool Renderer::InitCommon() {
|
|||
if (extensions.find("GL_OES_vertex_array_object") != extensions.end()) {
|
||||
// This extension seems to be broken on older PowerVR drivers.
|
||||
if (!strstr(renderer, "PowerVR SGX 53") &&
|
||||
!strstr(renderer, "PowerVR SGX 54")) {
|
||||
!strstr(renderer, "PowerVR SGX 54") &&
|
||||
!strstr(renderer, "Android Emulator")) {
|
||||
vertex_array_objects_ = true;
|
||||
}
|
||||
}
|
||||
|
@ -201,27 +203,33 @@ bool Renderer::InitCommon() {
|
|||
|
||||
glViewport(0, 0, screen_width_, screen_height_);
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
glClearColor(0, 0, 0, 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Renderer::InvalidateAllResources() {
|
||||
for (auto& r : resources_) {
|
||||
r.second->Destroy();
|
||||
}
|
||||
for (auto& r : resources_)
|
||||
r.second->Destroy();
|
||||
}
|
||||
|
||||
bool Renderer::StartWorker() {
|
||||
bool Renderer::StartRenderThread() {
|
||||
#ifdef THREADED_RENDERING
|
||||
LOG << "Starting render thread.";
|
||||
|
||||
global_commands_.clear();
|
||||
draw_commands_[0].clear();
|
||||
draw_commands_[1].clear();
|
||||
terminate_worker_ = false;
|
||||
terminate_render_thread_ = false;
|
||||
|
||||
std::promise<bool> promise;
|
||||
std::future<bool> future = promise.get_future();
|
||||
worker_thread_ = std::thread(&Renderer::WorkerMain, this, std::move(promise));
|
||||
render_thread_ =
|
||||
std::thread(&Renderer::RenderThreadMain, this, std::move(promise));
|
||||
future.wait();
|
||||
return future.get();
|
||||
#else
|
||||
LOG << "Single threaded rendering.";
|
||||
|
@ -229,18 +237,18 @@ bool Renderer::StartWorker() {
|
|||
#endif // THREADED_RENDERING
|
||||
}
|
||||
|
||||
void Renderer::TerminateWorker() {
|
||||
void Renderer::TerminateRenderThread() {
|
||||
#ifdef THREADED_RENDERING
|
||||
DCHECK(!terminate_render_thread_);
|
||||
|
||||
// Notify worker thread and wait for it to terminate.
|
||||
{
|
||||
std::unique_lock<std::mutex> scoped_lock(mutex_);
|
||||
if (terminate_worker_)
|
||||
return;
|
||||
terminate_worker_ = true;
|
||||
terminate_render_thread_ = true;
|
||||
}
|
||||
cv_.notify_one();
|
||||
LOG << "Terminating render thread";
|
||||
worker_thread_.join();
|
||||
render_thread_.join();
|
||||
#else
|
||||
ShutdownInternal();
|
||||
#endif // THREADED_RENDERING
|
||||
|
@ -248,7 +256,7 @@ void Renderer::TerminateWorker() {
|
|||
|
||||
#ifdef THREADED_RENDERING
|
||||
|
||||
void Renderer::WorkerMain(std::promise<bool> promise) {
|
||||
void Renderer::RenderThreadMain(std::promise<bool> promise) {
|
||||
promise.set_value(InitInternal());
|
||||
|
||||
std::deque<std::unique_ptr<RenderCommand>> cq[2];
|
||||
|
@ -257,9 +265,9 @@ void Renderer::WorkerMain(std::promise<bool> promise) {
|
|||
std::unique_lock<std::mutex> scoped_lock(mutex_);
|
||||
cv_.wait(scoped_lock, [&]() -> bool {
|
||||
return !global_commands_.empty() || !draw_commands_[0].empty() ||
|
||||
terminate_worker_;
|
||||
terminate_render_thread_;
|
||||
});
|
||||
if (terminate_worker_) {
|
||||
if (terminate_render_thread_) {
|
||||
ShutdownInternal();
|
||||
return;
|
||||
}
|
||||
|
@ -272,17 +280,11 @@ void Renderer::WorkerMain(std::promise<bool> promise) {
|
|||
LOG << "draw queue size: " << (int)cq[1].size();
|
||||
#endif
|
||||
|
||||
while (!cq[0].empty()) {
|
||||
std::unique_ptr<RenderCommand> cmd;
|
||||
cmd.swap(cq[0].front());
|
||||
cq[0].pop_front();
|
||||
ProcessCommand(cmd.get());
|
||||
}
|
||||
while (!cq[1].empty()) {
|
||||
std::unique_ptr<RenderCommand> cmd;
|
||||
cmd.swap(cq[1].front());
|
||||
cq[1].pop_front();
|
||||
ProcessCommand(cmd.get());
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
while (!cq[i].empty()) {
|
||||
ProcessCommand(cq[i].front().get());
|
||||
cq[i].pop_front();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -295,77 +297,59 @@ void Renderer::ProcessCommand(RenderCommand* cmd) {
|
|||
#endif
|
||||
|
||||
switch (cmd->cmd_id) {
|
||||
case HHASH("CmdEableBlend"):
|
||||
HandleCmdEnableBlend(cmd);
|
||||
break;
|
||||
case HHASH("CmdClear"):
|
||||
HandleCmdClear(cmd);
|
||||
break;
|
||||
case HHASH("CmdPresent"):
|
||||
case CmdPresent::CMD_ID:
|
||||
HandleCmdPresent(cmd);
|
||||
break;
|
||||
case HHASH("CmdUpdateTexture"):
|
||||
case CmdUpdateTexture::CMD_ID:
|
||||
HandleCmdUpdateTexture(cmd);
|
||||
break;
|
||||
case HHASH("CmdDestoryTexture"):
|
||||
case CmdDestoryTexture::CMD_ID:
|
||||
HandleCmdDestoryTexture(cmd);
|
||||
break;
|
||||
case HHASH("CmdActivateTexture"):
|
||||
case CmdActivateTexture::CMD_ID:
|
||||
HandleCmdActivateTexture(cmd);
|
||||
break;
|
||||
case HHASH("CmdCreateGeometry"):
|
||||
case CmdCreateGeometry::CMD_ID:
|
||||
HandleCmdCreateGeometry(cmd);
|
||||
break;
|
||||
case HHASH("CmdDestroyGeometry"):
|
||||
case CmdDestroyGeometry::CMD_ID:
|
||||
HandleCmdDestroyGeometry(cmd);
|
||||
break;
|
||||
case HHASH("CmdDrawGeometry"):
|
||||
case CmdDrawGeometry::CMD_ID:
|
||||
HandleCmdDrawGeometry(cmd);
|
||||
break;
|
||||
case HHASH("CmdCreateShader"):
|
||||
case CmdCreateShader::CMD_ID:
|
||||
HandleCmdCreateShader(cmd);
|
||||
break;
|
||||
case HHASH("CmdDestroyShader"):
|
||||
case CmdDestroyShader::CMD_ID:
|
||||
HandleCmdDestroyShader(cmd);
|
||||
break;
|
||||
case HHASH("CmdActivateShader"):
|
||||
case CmdActivateShader::CMD_ID:
|
||||
HandleCmdActivateShader(cmd);
|
||||
break;
|
||||
case HHASH("CmdSetUniformVec2"):
|
||||
case CmdSetUniformVec2::CMD_ID:
|
||||
HandleCmdSetUniformVec2(cmd);
|
||||
break;
|
||||
case HHASH("CmdSetUniformVec3"):
|
||||
case CmdSetUniformVec3::CMD_ID:
|
||||
HandleCmdSetUniformVec3(cmd);
|
||||
break;
|
||||
case HHASH("CmdSetUniformVec4"):
|
||||
case CmdSetUniformVec4::CMD_ID:
|
||||
HandleCmdSetUniformVec4(cmd);
|
||||
break;
|
||||
case HHASH("CmdSetUniformMat4"):
|
||||
case CmdSetUniformMat4::CMD_ID:
|
||||
HandleCmdSetUniformMat4(cmd);
|
||||
break;
|
||||
case HHASH("CmdSetUniformFloat"):
|
||||
case CmdSetUniformFloat::CMD_ID:
|
||||
HandleCmdSetUniformFloat(cmd);
|
||||
break;
|
||||
case HHASH("CmdSetUniformInt"):
|
||||
case CmdSetUniformInt::CMD_ID:
|
||||
HandleCmdSetUniformInt(cmd);
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
NOTREACHED << "- Unknown render command: " << cmd->cmd_id;
|
||||
}
|
||||
}
|
||||
|
||||
void Renderer::HandleCmdEnableBlend(RenderCommand* cmd) {
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
}
|
||||
|
||||
void Renderer::HandleCmdClear(RenderCommand* cmd) {
|
||||
auto* c = static_cast<CmdClear*>(cmd);
|
||||
glClearColor(c->rgba[0], c->rgba[1], c->rgba[2], c->rgba[3]);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
}
|
||||
|
||||
void Renderer::HandleCmdUpdateTexture(RenderCommand* cmd) {
|
||||
auto* c = static_cast<CmdUpdateTexture*>(cmd);
|
||||
auto impl_data = reinterpret_cast<TextureOpenGL*>(c->impl_data.get());
|
||||
|
@ -379,7 +363,7 @@ void Renderer::HandleCmdUpdateTexture(RenderCommand* cmd) {
|
|||
|
||||
glBindTexture(GL_TEXTURE_2D, gl_id);
|
||||
if (c->image->IsCompressed()) {
|
||||
GLenum format;
|
||||
GLenum format = 0;
|
||||
switch (c->image->GetFormat()) {
|
||||
case Image::kDXT1:
|
||||
format = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
|
||||
|
@ -399,15 +383,23 @@ void Renderer::HandleCmdUpdateTexture(RenderCommand* cmd) {
|
|||
break;
|
||||
#endif
|
||||
default:
|
||||
assert(false);
|
||||
return;
|
||||
NOTREACHED << "- Unhandled texure format: " << c->image->GetFormat();
|
||||
}
|
||||
|
||||
glCompressedTexImage2D(GL_TEXTURE_2D, 0, format, c->image->GetWidth(),
|
||||
c->image->GetHeight(), 0, c->image->GetSize(),
|
||||
c->image->GetBuffer());
|
||||
|
||||
// Sometimes the first glCompressedTexImage2D call after context-lost
|
||||
// generates GL_INVALID_VALUE.
|
||||
GLenum err = glGetError();
|
||||
if (err == GL_INVALID_VALUE) {
|
||||
glCompressedTexImage2D(GL_TEXTURE_2D, 0, format, c->image->GetWidth(),
|
||||
c->image->GetHeight(), 0, c->image->GetSize(),
|
||||
c->image->GetBuffer());
|
||||
err = glGetError();
|
||||
}
|
||||
|
||||
if (err != GL_NO_ERROR)
|
||||
LOG << "GL ERROR after glCompressedTexImage2D: " << (int)err;
|
||||
} else {
|
||||
|
|
|
@ -19,15 +19,8 @@
|
|||
#endif // THREADED_RENDERING
|
||||
|
||||
#include "opengl.h"
|
||||
#if defined(__linux__) && !defined(__ANDROID__)
|
||||
#include <X11/Xlib.h>
|
||||
#include "../../third_party/glew/glxew.h"
|
||||
#endif
|
||||
|
||||
#include "../../base/closure.h"
|
||||
#ifdef THREADED_RENDERING
|
||||
#include "../../base/task_runner.h"
|
||||
#endif // THREADED_RENDERING
|
||||
#include "render_resource.h"
|
||||
#include "renderer_types.h"
|
||||
|
||||
|
@ -35,6 +28,12 @@
|
|||
struct ANativeWindow;
|
||||
#endif
|
||||
|
||||
#ifdef THREADED_RENDERING
|
||||
namespace base {
|
||||
class TaskRunner;
|
||||
}
|
||||
#endif // THREADED_RENDERING
|
||||
|
||||
namespace eng {
|
||||
|
||||
struct RenderCommand;
|
||||
|
@ -55,8 +54,6 @@ class Renderer {
|
|||
|
||||
void Shutdown();
|
||||
|
||||
void Update();
|
||||
|
||||
void ContextLost();
|
||||
|
||||
std::unique_ptr<RenderResource> CreateResource(
|
||||
|
@ -152,10 +149,10 @@ class Renderer {
|
|||
|
||||
std::condition_variable cv_;
|
||||
std::mutex mutex_;
|
||||
std::thread worker_thread_;
|
||||
bool terminate_worker_ = false;
|
||||
std::thread render_thread_;
|
||||
bool terminate_render_thread_ = false;
|
||||
|
||||
base::TaskRunner task_runner_;
|
||||
base::TaskRunner* main_thread_task_runner_;
|
||||
#endif // THREADED_RENDERING
|
||||
|
||||
// Stats.
|
||||
|
@ -178,17 +175,15 @@ class Renderer {
|
|||
|
||||
void InvalidateAllResources();
|
||||
|
||||
bool StartWorker();
|
||||
void TerminateWorker();
|
||||
bool StartRenderThread();
|
||||
void TerminateRenderThread();
|
||||
|
||||
#ifdef THREADED_RENDERING
|
||||
void WorkerMain(std::promise<bool> promise);
|
||||
void RenderThreadMain(std::promise<bool> promise);
|
||||
#endif // THREADED_RENDERING
|
||||
|
||||
void ProcessCommand(RenderCommand* cmd);
|
||||
|
||||
void HandleCmdEnableBlend(RenderCommand* cmd);
|
||||
void HandleCmdClear(RenderCommand* cmd);
|
||||
void HandleCmdPresent(RenderCommand* cmd);
|
||||
void HandleCmdUpdateTexture(RenderCommand* cmd);
|
||||
void HandleCmdDestoryTexture(RenderCommand* cmd);
|
||||
|
|
|
@ -2,17 +2,25 @@
|
|||
|
||||
#include <android/native_window.h>
|
||||
|
||||
#include "../../base/log.h"
|
||||
#include "../../third_party/android/GLContext.h"
|
||||
|
||||
namespace eng {
|
||||
|
||||
bool Renderer::Initialize(ANativeWindow* window) {
|
||||
LOG << "Initializing renderer.";
|
||||
|
||||
window_ = window;
|
||||
return StartWorker();
|
||||
return StartRenderThread();
|
||||
}
|
||||
|
||||
void Renderer::Shutdown() {
|
||||
TerminateWorker();
|
||||
if (terminate_render_thread_)
|
||||
return;
|
||||
|
||||
LOG << "Shutting down renderer.";
|
||||
|
||||
TerminateRenderThread();
|
||||
}
|
||||
|
||||
bool Renderer::InitInternal() {
|
||||
|
@ -51,7 +59,9 @@ void Renderer::ShutdownInternal() {
|
|||
void Renderer::HandleCmdPresent(RenderCommand* cmd) {
|
||||
if (EGL_SUCCESS != ndk_helper::GLContext::GetInstance()->Swap()) {
|
||||
ContextLost();
|
||||
return;
|
||||
}
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
}
|
||||
|
||||
} // namespace eng
|
||||
|
|
|
@ -1,20 +1,21 @@
|
|||
#include "renderer.h"
|
||||
|
||||
#include <X11/Xutil.h>
|
||||
|
||||
#include "../../base/log.h"
|
||||
#include "../../third_party/glew/glew.h"
|
||||
|
||||
namespace eng {
|
||||
|
||||
bool Renderer::Initialize() {
|
||||
LOG << "Initializing renderer.";
|
||||
|
||||
if (!CreateWindow())
|
||||
return false;
|
||||
return StartWorker();
|
||||
return StartRenderThread();
|
||||
}
|
||||
|
||||
void Renderer::Shutdown() {
|
||||
TerminateWorker();
|
||||
LOG << "Shutting down renderer.";
|
||||
|
||||
TerminateRenderThread();
|
||||
DestroyWindow();
|
||||
}
|
||||
|
||||
|
@ -91,8 +92,10 @@ void Renderer::ShutdownInternal() {
|
|||
}
|
||||
|
||||
void Renderer::HandleCmdPresent(RenderCommand* cmd) {
|
||||
if (display_)
|
||||
if (display_) {
|
||||
glXSwapBuffers(display_, window_);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace eng
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
#include "shader.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "../shader_source.h"
|
||||
#include "render_command.h"
|
||||
#include "renderer.h"
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
#include "texture.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "../../engine/image.h"
|
||||
#include "../../base/log.h"
|
||||
#include "../image.h"
|
||||
#include "render_command.h"
|
||||
#include "renderer.h"
|
||||
|
||||
|
@ -34,6 +33,7 @@ void Texture::Destroy() {
|
|||
cmd->impl_data = impl_data_;
|
||||
renderer_->EnqueueCommand(std::move(cmd));
|
||||
valid_ = false;
|
||||
DLOG << "Texture destroyed. resource_id: " << resource_id_;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,18 +2,17 @@
|
|||
|
||||
#include <memory>
|
||||
|
||||
#include "../base/log.h"
|
||||
#include "engine.h"
|
||||
#include "renderer/geometry.h"
|
||||
#include "renderer/shader.h"
|
||||
|
||||
namespace eng {
|
||||
|
||||
void SolidQuad::Draw() {
|
||||
if (!IsVisible())
|
||||
return;
|
||||
void SolidQuad::Draw(float frame_frac) {
|
||||
DCHECK(IsVisible());
|
||||
|
||||
std::shared_ptr<Geometry> quad = Engine::Get().GetQuad();
|
||||
std::shared_ptr<Shader> shader = Engine::Get().GetSolidShader();
|
||||
Shader* shader = Engine::Get().GetSolidShader();
|
||||
|
||||
shader->Activate();
|
||||
shader->SetUniform("offset", offset_);
|
||||
|
@ -23,7 +22,7 @@ void SolidQuad::Draw() {
|
|||
shader->SetUniform("projection", Engine::Get().GetProjectionMarix());
|
||||
shader->SetUniform("color", color_);
|
||||
|
||||
quad->Draw();
|
||||
Engine::Get().GetQuad()->Draw();
|
||||
}
|
||||
|
||||
} // namespace eng
|
||||
|
|
|
@ -17,7 +17,8 @@ class SolidQuad : public Animatable {
|
|||
void SetColor(const base::Vector4& color) override { color_ = color; }
|
||||
base::Vector4 GetColor() const override { return color_; }
|
||||
|
||||
void Draw();
|
||||
// Drawable interface.
|
||||
void Draw(float frame_frac) override;
|
||||
|
||||
private:
|
||||
base::Vector4 color_ = {1, 1, 1, 1};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#include "sound.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <array>
|
||||
|
||||
#include "../base/log.h"
|
||||
#define MINIMP3_ONLY_MP3
|
||||
|
@ -17,8 +17,31 @@ using namespace base;
|
|||
|
||||
namespace {
|
||||
|
||||
constexpr size_t kMinSamplesForStreaming = 500000;
|
||||
constexpr size_t kMaxSamplesPerDecode = MINIMP3_MAX_SAMPLES_PER_FRAME * 50;
|
||||
constexpr size_t kMaxSamplesPerChunk = MINIMP3_MAX_SAMPLES_PER_FRAME * 10;
|
||||
|
||||
template <typename T>
|
||||
std::array<std::unique_ptr<T[]>, 2> Deinterleave(size_t num_channels,
|
||||
size_t num_samples,
|
||||
float* input_buffer) {
|
||||
std::array<std::unique_ptr<T[]>, 2> channels;
|
||||
|
||||
if (num_channels == 1) {
|
||||
// Single channel.
|
||||
channels[0] = std::make_unique<T[]>(num_samples);
|
||||
for (int i = 0; i < num_samples; ++i)
|
||||
channels[0].get()[i] = input_buffer[i];
|
||||
} else {
|
||||
// Deinterleave into separate channels.
|
||||
channels[0] = std::make_unique<T[]>(num_samples);
|
||||
channels[1] = std::make_unique<T[]>(num_samples);
|
||||
for (int i = 0, j = 0; i < num_samples * 2; i += 2) {
|
||||
channels[0].get()[j] = input_buffer[i];
|
||||
channels[1].get()[j++] = input_buffer[i + 1];
|
||||
}
|
||||
}
|
||||
|
||||
return channels;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
@ -31,7 +54,7 @@ Sound::~Sound() {
|
|||
mp3dec_ex_close(mp3_dec_.get());
|
||||
}
|
||||
|
||||
bool Sound::Load(const std::string& file_name) {
|
||||
bool Sound::Load(const std::string& file_name, bool stream) {
|
||||
size_t buffer_size = 0;
|
||||
encoded_data_ = AssetFile::ReadWholeFile(file_name.c_str(),
|
||||
Engine::Get().GetRootPath().c_str(),
|
||||
|
@ -43,7 +66,8 @@ bool Sound::Load(const std::string& file_name) {
|
|||
|
||||
if (mp3_dec_)
|
||||
mp3dec_ex_close(mp3_dec_.get());
|
||||
mp3_dec_ = std::make_unique<mp3dec_ex_t>();
|
||||
else
|
||||
mp3_dec_ = std::make_unique<mp3dec_ex_t>();
|
||||
|
||||
int err = mp3dec_ex_open_buf(mp3_dec_.get(),
|
||||
reinterpret_cast<uint8_t*>(encoded_data_.get()),
|
||||
|
@ -53,10 +77,7 @@ bool Sound::Load(const std::string& file_name) {
|
|||
return false;
|
||||
}
|
||||
|
||||
is_streaming_sound_ =
|
||||
mp3_dec_->samples / mp3_dec_->info.channels > kMinSamplesForStreaming
|
||||
? true
|
||||
: false;
|
||||
is_streaming_sound_ = stream;
|
||||
|
||||
LOG << (is_streaming_sound_ ? "Streaming " : "Loading ") << file_name << ". "
|
||||
<< mp3_dec_->samples << " samples, " << mp3_dec_->info.channels
|
||||
|
@ -66,30 +87,42 @@ bool Sound::Load(const std::string& file_name) {
|
|||
|
||||
num_channels_ = mp3_dec_->info.channels;
|
||||
hz_ = mp3_dec_->info.hz;
|
||||
num_samples_back_ = 0;
|
||||
num_samples_back_ = cur_sample_back_ = 0;
|
||||
eof_ = false;
|
||||
|
||||
assert(num_channels_ > 0 && num_channels_ <= 2);
|
||||
DCHECK(num_channels_ > 0 && num_channels_ <= 2);
|
||||
|
||||
size_t system_hz = Engine::Get().GetAudioSampleRate();
|
||||
|
||||
// Fill up buffers.
|
||||
if (is_streaming_sound_) {
|
||||
resampler_ = std::make_unique<r8b::CDSPResampler16>(hz_, system_hz,
|
||||
kMaxSamplesPerDecode);
|
||||
kMaxSamplesPerChunk);
|
||||
|
||||
StreamInternal(kMaxSamplesPerDecode, false);
|
||||
SwapBuffersInternal();
|
||||
StreamInternal(kMaxSamplesPerDecode, false);
|
||||
// Fill up buffers.
|
||||
StreamInternal(kMaxSamplesPerChunk, false);
|
||||
SwapBuffers();
|
||||
StreamInternal(kMaxSamplesPerChunk, false);
|
||||
|
||||
if (eof_) {
|
||||
// Sample is smaller than buffer. No need to stream.
|
||||
is_streaming_sound_ = false;
|
||||
mp3dec_ex_close(mp3_dec_.get());
|
||||
mp3_dec_.reset();
|
||||
encoded_data_.reset();
|
||||
resampler_.reset();
|
||||
}
|
||||
} else {
|
||||
resampler_ = std::make_unique<r8b::CDSPResampler16>(hz_, system_hz,
|
||||
mp3_dec_->samples);
|
||||
|
||||
// Decode entire file.
|
||||
StreamInternal(mp3_dec_->samples, false);
|
||||
SwapBuffersInternal();
|
||||
SwapBuffers();
|
||||
eof_ = true;
|
||||
|
||||
// We are done with decoding for non-streaming sound.
|
||||
mp3dec_ex_close(mp3_dec_.get());
|
||||
mp3_dec_.reset();
|
||||
encoded_data_.reset();
|
||||
resampler_.reset();
|
||||
}
|
||||
|
@ -98,42 +131,44 @@ bool Sound::Load(const std::string& file_name) {
|
|||
}
|
||||
|
||||
bool Sound::Stream(bool loop) {
|
||||
assert(is_streaming_sound_);
|
||||
DCHECK(is_streaming_sound_);
|
||||
|
||||
return StreamInternal(kMaxSamplesPerDecode, loop);
|
||||
return StreamInternal(kMaxSamplesPerChunk, loop);
|
||||
}
|
||||
|
||||
void Sound::SwapBuffers() {
|
||||
assert(is_streaming_sound_);
|
||||
front_buffer_[0].swap(back_buffer_[0]);
|
||||
front_buffer_[1].swap(back_buffer_[1]);
|
||||
|
||||
SwapBuffersInternal();
|
||||
|
||||
// Memory barrier to ensure all memory writes become visible to the decoder
|
||||
// thread.
|
||||
streaming_in_progress_.store(true, std::memory_order_release);
|
||||
cur_sample_front_ = cur_sample_back_;
|
||||
num_samples_front_ = num_samples_back_;
|
||||
num_samples_back_ = 0;
|
||||
}
|
||||
|
||||
size_t Sound::IsStreamingInProgress() const {
|
||||
assert(is_streaming_sound_);
|
||||
|
||||
return streaming_in_progress_.load(std::memory_order_acquire);
|
||||
void Sound::ResetStream() {
|
||||
if (is_streaming_sound_ && cur_sample_front_ != 0) {
|
||||
// Seek to 0 and ivalidate decoded data.
|
||||
mp3dec_ex_seek(mp3_dec_.get(), 0);
|
||||
eof_ = false;
|
||||
num_samples_back_ = num_samples_front_ = 0;
|
||||
cur_sample_front_ = cur_sample_back_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
size_t Sound::GetSize() const {
|
||||
return num_samples_front_ * sizeof(mp3d_sample_t);
|
||||
}
|
||||
|
||||
float* Sound::GetBuffer(int channel) {
|
||||
float* Sound::GetBuffer(int channel) const {
|
||||
return front_buffer_[channel].get();
|
||||
}
|
||||
|
||||
bool Sound::StreamInternal(size_t num_samples, bool loop) {
|
||||
auto buffer = std::make_unique<float[]>(num_samples);
|
||||
|
||||
cur_sample_back_ = mp3_dec_->cur_sample;
|
||||
|
||||
for (;;) {
|
||||
size_t samples_read =
|
||||
mp3dec_ex_read(mp3_dec_.get(), buffer.get(), num_samples);
|
||||
if (samples_read != num_samples && mp3_dec_->last_error) {
|
||||
LOG << "mp3 decode error: " << mp3_dec_->last_error;
|
||||
eof_ = true;
|
||||
return false;
|
||||
}
|
||||
|
@ -152,57 +187,43 @@ bool Sound::StreamInternal(size_t num_samples, bool loop) {
|
|||
else
|
||||
eof_ = true;
|
||||
|
||||
// Memory barrier to ensure all memory writes become visible to the audio
|
||||
// thread.
|
||||
streaming_in_progress_.store(false, std::memory_order_release);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Sound::SwapBuffersInternal() {
|
||||
front_buffer_[0].swap(back_buffer_[0]);
|
||||
front_buffer_[1].swap(back_buffer_[1]);
|
||||
|
||||
num_samples_front_ = num_samples_back_;
|
||||
num_samples_back_ = 0;
|
||||
}
|
||||
|
||||
void Sound::Preprocess(std::unique_ptr<float[]> input_buffer) {
|
||||
std::unique_ptr<double[]> channels[2];
|
||||
|
||||
// r8b resampler supports only double floating point type.
|
||||
if (num_channels_ == 1) {
|
||||
// Single channel.
|
||||
channels[0] = std::make_unique<double[]>(num_samples_back_);
|
||||
for (int i = 0; i < num_samples_back_; ++i)
|
||||
channels[0].get()[i] = input_buffer.get()[i];
|
||||
} else {
|
||||
// Deinterleave into separate channels.
|
||||
channels[0] = std::make_unique<double[]>(num_samples_back_);
|
||||
channels[1] = std::make_unique<double[]>(num_samples_back_);
|
||||
for (int i = 0, j = 0; i < num_samples_back_ * 2; i += 2) {
|
||||
channels[0].get()[j] = input_buffer.get()[i];
|
||||
channels[1].get()[j++] = input_buffer.get()[i + 1];
|
||||
}
|
||||
}
|
||||
|
||||
size_t system_hz = Engine::Get().GetAudioSampleRate();
|
||||
size_t resampled_num_samples =
|
||||
((float)system_hz / (float)hz_) * num_samples_back_;
|
||||
|
||||
if (!back_buffer_[0]) {
|
||||
back_buffer_[0] = std::make_unique<float[]>(resampled_num_samples);
|
||||
if (system_hz == hz_) {
|
||||
auto channels = Deinterleave<float>(num_channels_, num_samples_back_,
|
||||
input_buffer.get());
|
||||
|
||||
// No need for resmapling.
|
||||
back_buffer_[0] = std::move(channels[0]);
|
||||
if (num_channels_ == 2)
|
||||
back_buffer_[1] = std::make_unique<float[]>(resampled_num_samples);
|
||||
}
|
||||
back_buffer_[1] = std::move(channels[1]);
|
||||
} else {
|
||||
// r8b resampler supports only double floating point type.
|
||||
auto channels = Deinterleave<double>(num_channels_, num_samples_back_,
|
||||
input_buffer.get());
|
||||
|
||||
// Resample to match the system sample rate if needed. Output from the
|
||||
// resampler is converted from double to float.
|
||||
for (int i = 0; i < num_channels_; ++i) {
|
||||
resampler_->oneshot(channels[i].get(), num_samples_back_,
|
||||
back_buffer_[i].get(), resampled_num_samples);
|
||||
size_t resampled_num_samples =
|
||||
((float)system_hz / (float)hz_) * num_samples_back_;
|
||||
|
||||
if (!back_buffer_[0]) {
|
||||
if (max_samples_ < resampled_num_samples)
|
||||
max_samples_ = resampled_num_samples;
|
||||
back_buffer_[0] = std::make_unique<float[]>(max_samples_);
|
||||
if (num_channels_ == 2)
|
||||
back_buffer_[1] = std::make_unique<float[]>(max_samples_);
|
||||
}
|
||||
|
||||
// Resample to match the system sample rate.
|
||||
for (int i = 0; i < num_channels_; ++i) {
|
||||
resampler_->oneshot(channels[i].get(), num_samples_back_,
|
||||
back_buffer_[i].get(), resampled_num_samples);
|
||||
}
|
||||
num_samples_back_ = resampled_num_samples;
|
||||
}
|
||||
num_samples_back_ = resampled_num_samples;
|
||||
}
|
||||
|
||||
} // namespace eng
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
#define SOUND_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
|
@ -16,50 +15,43 @@ namespace eng {
|
|||
|
||||
// Class for streaming and non-streaming sound assets. Loads and decodes mp3
|
||||
// files. Resamples the decoded audio to match the system sample rate if
|
||||
// necessary. Can be shared between multiple audio resources and played
|
||||
// simultaneously.
|
||||
//
|
||||
// Streaming starts automatically for big files and it's done from memory. It
|
||||
// loads the entire mp3 file and decodes small chunks on demand. Streaming sound
|
||||
// cannot be shared between multiple audio resources.
|
||||
// necessary. Non-streaming sounds Can be shared between multiple audio
|
||||
// resources and played simultaneously.
|
||||
class Sound {
|
||||
public:
|
||||
Sound();
|
||||
~Sound();
|
||||
|
||||
bool Load(const std::string& file_name);
|
||||
bool Load(const std::string& file_name, bool stream);
|
||||
|
||||
bool Stream(bool loop);
|
||||
|
||||
void SwapBuffers();
|
||||
|
||||
size_t IsStreamingInProgress() const;
|
||||
void ResetStream();
|
||||
|
||||
// Buffer size per channel.
|
||||
size_t GetSize() const;
|
||||
|
||||
const float* GetBuffer(int channel) const {
|
||||
return front_buffer_[channel].get();
|
||||
}
|
||||
float* GetBuffer(int channel);
|
||||
|
||||
bool IsValid() const { return !!front_buffer_[0]; }
|
||||
float* GetBuffer(int channel) const;
|
||||
|
||||
size_t GetNumSamples() const { return num_samples_front_; }
|
||||
|
||||
size_t num_channels() const { return num_channels_; }
|
||||
size_t hz() const { return hz_; }
|
||||
|
||||
bool is_streaming_sound() { return is_streaming_sound_; }
|
||||
bool is_streaming_sound() const { return is_streaming_sound_; }
|
||||
|
||||
bool eof() const { return eof_; }
|
||||
|
||||
private:
|
||||
// Buffer holding decoded audio.
|
||||
std::unique_ptr<float[]> back_buffer_[2];
|
||||
std::unique_ptr<float[]> front_buffer_[2];
|
||||
|
||||
size_t num_samples_back_ = 0;
|
||||
size_t num_samples_front_ = 0;
|
||||
size_t max_samples_ = 0;
|
||||
|
||||
size_t cur_sample_front_ = 0;
|
||||
size_t cur_sample_back_ = 0;
|
||||
|
||||
size_t num_channels_ = 0;
|
||||
size_t hz_ = 0;
|
||||
|
@ -71,14 +63,11 @@ class Sound {
|
|||
std::unique_ptr<r8b::CDSPResampler16> resampler_;
|
||||
|
||||
bool eof_ = false;
|
||||
std::atomic<bool> streaming_in_progress_ = false;
|
||||
|
||||
bool is_streaming_sound_ = false;
|
||||
|
||||
bool StreamInternal(size_t num_samples, bool loop);
|
||||
|
||||
void SwapBuffersInternal();
|
||||
|
||||
void Preprocess(std::unique_ptr<float[]> input_buffer);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "sound_player.h"
|
||||
|
||||
#include "../base/interpolation.h"
|
||||
#include "../base/log.h"
|
||||
#include "audio/audio_resource.h"
|
||||
#include "engine.h"
|
||||
#include "sound.h"
|
||||
|
@ -14,31 +15,43 @@ SoundPlayer::SoundPlayer() : resource_(Engine::Get().CreateAudioResource()) {}
|
|||
SoundPlayer::~SoundPlayer() = default;
|
||||
|
||||
void SoundPlayer::SetSound(std::shared_ptr<Sound> sound) {
|
||||
CHECK(!sound->is_streaming_sound()) << "Streaming sound cannot be shared.";
|
||||
|
||||
sound_ = sound;
|
||||
}
|
||||
|
||||
void SoundPlayer::Play(bool loop) {
|
||||
resource_->SetAmplitudeInc(0);
|
||||
resource_->SetLoop(loop);
|
||||
resource_->Play(sound_, max_amplitude_, true);
|
||||
void SoundPlayer::SetSound(std::unique_ptr<Sound> sound) {
|
||||
sound_ = std::move(sound);
|
||||
}
|
||||
|
||||
void SoundPlayer::Resume(bool fade_in) {
|
||||
if (fade_in)
|
||||
resource_->SetAmplitudeInc(0.0001f);
|
||||
resource_->Play(sound_, fade_in ? 0 : max_amplitude_, false);
|
||||
void SoundPlayer::Play(bool loop, float fade_in_duration) {
|
||||
if (sound_) {
|
||||
int step = variate_ ? Engine::Get().GetRandomGenerator().Roll(3) - 2 : 0;
|
||||
resource_->SetResampleStep(step);
|
||||
resource_->SetLoop(loop);
|
||||
if (fade_in_duration > 0)
|
||||
resource_->SetAmplitudeInc(1.0f / (sound_->hz() * fade_in_duration));
|
||||
else
|
||||
resource_->SetAmplitudeInc(0);
|
||||
resource_->Play(sound_, fade_in_duration > 0 ? 0 : max_amplitude_, true);
|
||||
}
|
||||
}
|
||||
|
||||
void SoundPlayer::Stop(bool fade_out) {
|
||||
if (fade_out)
|
||||
resource_->SetAmplitudeInc(-0.0001f);
|
||||
void SoundPlayer::Resume(float fade_in_duration) {
|
||||
if (fade_in_duration > 0)
|
||||
resource_->SetAmplitudeInc(1.0f / (sound_->hz() * fade_in_duration));
|
||||
resource_->Play(sound_, fade_in_duration > 0 ? 0 : -1, false);
|
||||
}
|
||||
|
||||
void SoundPlayer::Stop(float fade_out_duration) {
|
||||
if (fade_out_duration > 0)
|
||||
resource_->SetAmplitudeInc(-1.0f / (sound_->hz() * fade_out_duration));
|
||||
else
|
||||
resource_->Stop();
|
||||
}
|
||||
|
||||
void SoundPlayer::SetVariate(bool variate) {
|
||||
int step = variate ? Engine::Get().GetRandomGenerator().Roll(3) - 2 : 0;
|
||||
resource_->SetResampleStep(step);
|
||||
variate_ = variate;
|
||||
}
|
||||
|
||||
void SoundPlayer::SetSimulateStereo(bool simulate) {
|
||||
|
|
|
@ -16,19 +16,19 @@ class SoundPlayer {
|
|||
~SoundPlayer();
|
||||
|
||||
void SetSound(std::shared_ptr<Sound> sound);
|
||||
void SetSound(std::unique_ptr<Sound> sound);
|
||||
|
||||
void Play(bool loop);
|
||||
void Play(bool loop, float fade_in_duration = 0);
|
||||
|
||||
void Resume(bool fade_in);
|
||||
void Resume(float fade_in_duration = 0);
|
||||
|
||||
void Stop(bool fade_out);
|
||||
void Stop(float fade_out_duration = 0);
|
||||
|
||||
// Picks a random variation of the sound or the original sound if "variate" is
|
||||
// false. Variations are obtained by slightly up or down sampling.
|
||||
void SetVariate(bool variate);
|
||||
|
||||
// Enable or disable stereo simulation effect. Valid for mono samples only.
|
||||
// Disabled by default.
|
||||
// Enable or disable stereo simulation effect. Disabled by default.
|
||||
void SetSimulateStereo(bool simulate);
|
||||
|
||||
void SetMaxAplitude(float max_amplitude);
|
||||
|
@ -37,11 +37,13 @@ class SoundPlayer {
|
|||
void SetEndCallback(base::Closure cb);
|
||||
|
||||
private:
|
||||
std::shared_ptr<AudioResource> resource_;
|
||||
std::unique_ptr<AudioResource> resource_;
|
||||
std::shared_ptr<Sound> sound_;
|
||||
|
||||
float max_amplitude_ = 1.0f;
|
||||
|
||||
bool variate_ = false;
|
||||
|
||||
SoundPlayer(const SoundPlayer&) = delete;
|
||||
SoundPlayer& operator=(const SoundPlayer&) = delete;
|
||||
};
|
||||
|
|
|
@ -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 = []
|
||||
}
|
|
@ -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
|
|
@ -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_ */
|
|
@ -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.
|
Loading…
Reference in New Issue