Compare commits

..

No commits in common. "master" and "release-1.0.4" have entirely different histories.

216 changed files with 13086 additions and 103427 deletions

View File

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

4
.gitignore vendored
View File

@ -4,4 +4,8 @@ build/android/*.idsig
build/android/app/.cxx build/android/app/.cxx
build/android/app/build build/android/app/build
build/android/build build/android/build
build/linux/demo_x86_64_debug
build/linux/demo_x86_64_release
build/linux/obj
build/linux/woom
out out

2
.gn
View File

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

View File

@ -1,36 +0,0 @@
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [
"OS_LINUX"
]
},
{
"name": "Mac",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [
"OS_MAC"
],
"macFrameworkPath": [
"/System/Library/Frameworks",
"/Library/Frameworks"
]
},
{
"name": "Win32",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [
"OS_WIN"
]
}
],
"version": 4
}

21
.vscode/launch.json vendored
View File

@ -5,13 +5,13 @@
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{ {
"name": "Debug demo - Linux", "name": "(gdb) Launch",
"type": "cppdbg", "type": "cppdbg",
"request": "launch", "request": "launch",
"program": "${workspaceFolder}/out/debug/demo", "program": "${workspaceFolder}/build/linux/demo_x86_64_debug",
"args": [], "args": [],
"stopAtEntry": false, "stopAtEntry": false,
"cwd": "${workspaceFolder}/out/debug", "cwd": "${workspaceFolder}/build/linux",
"environment": [], "environment": [],
"console": "externalTerminal", "console": "externalTerminal",
"MIMode": "gdb", "MIMode": "gdb",
@ -22,19 +22,6 @@
"ignoreFailures": true "ignoreFailures": true
} }
] ]
"preLaunchTask": "Build project",
},
{
"name": "Debug demo - Windows",
"type": "cppvsdbg",
"request": "launch",
"program": "${workspaceFolder}\\out\\debug\\demo.exe",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}\\out\\debug",
"environment": [],
"externalConsole": false,
"preLaunchTask": "Build project",
} }
] ]
} }

82
.vscode/settings.json vendored
View File

@ -1,82 +0,0 @@
{
"files.associations": {
"algorithm": "cpp",
"array": "cpp",
"atomic": "cpp",
"bit": "cpp",
"*.tcc": "cpp",
"cctype": "cpp",
"chrono": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"codecvt": "cpp",
"compare": "cpp",
"concepts": "cpp",
"condition_variable": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdint": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"deque": "cpp",
"list": "cpp",
"map": "cpp",
"set": "cpp",
"string": "cpp",
"unordered_map": "cpp",
"unordered_set": "cpp",
"vector": "cpp",
"exception": "cpp",
"functional": "cpp",
"iterator": "cpp",
"memory": "cpp",
"memory_resource": "cpp",
"numeric": "cpp",
"optional": "cpp",
"random": "cpp",
"ratio": "cpp",
"string_view": "cpp",
"system_error": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"utility": "cpp",
"fstream": "cpp",
"future": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"limits": "cpp",
"mutex": "cpp",
"new": "cpp",
"numbers": "cpp",
"ostream": "cpp",
"semaphore": "cpp",
"shared_mutex": "cpp",
"span": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"stop_token": "cpp",
"streambuf": "cpp",
"thread": "cpp",
"cinttypes": "cpp",
"typeinfo": "cpp",
"variant": "cpp"
}
"git.ignoreLimitWarning": true,
// These settings should really be in github sync or something, if only it
// worked in linux...
"debug.openExplorerOnEnd": true,
"debug.console.closeOnEnd": true,
"editor.tabSize": 2,
"editor.rulers": [
80,
],
"workbench.editor.closeEmptyGroups": false,
}

39
.vscode/tasks.json vendored
View File

@ -1,39 +0,0 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Build project",
"type": "shell",
"linux": {
"command": "~/code/work/chromium/src/third_party/ninja/ninja",
"problemMatcher": [
"$gcc"
]
},
"osx": {
"command": "ninja",
"problemMatcher": [
"$gcc"
]
},
"windows": {
"command": "ninja.exe",
"problemMatcher": [
"$msCompile"
]
},
"args": [
"-C",
"${workspaceFolder}/out/debug"
],
"options": {
"cwd": "${workspaceFolder}"
},
"group": {
"kind": "build",
"isDefault": true
},
"detail": "Build the project with ninja."
}
]
}

View File

@ -1,6 +0,0 @@
group("all") {
deps = [
"//src/demo",
"//src/hello_world",
]
}

View File

@ -1,87 +1,20 @@
# Kaliber
A simple, cross-platform 2D game engine with OpenGL and Vulkan renderers. A simple, cross-platform 2D game engine with OpenGL and Vulkan renderers.
Supports Linux, Windows and Android platforms. Supports Linux and Android platforms.
This is a personal hobby project. I've published a little game on This is a personal hobby project. I've published a little game on
[Google Play](https://play.google.com/store/apps/details?id=com.woom.game) [Google Play](https://play.google.com/store/apps/details?id=com.woom.game)
based on this engine. Full game code and assets are included in this repository. based on this engine. Full game code and assets are included in this repository.
#### Building the demo
## Pre-requisites: Linux:
GN build system is required for all platforms:\
https://gn.googlesource.com/gn/
## Building from the command-line:
### All platforms except Android:
#### Setup:
Generate build files for Ninja in release and debug modes.
```text ```text
gn gen out/release cd build/linux
gn gen --args='is_debug=true' out/debug make
``` ```
#### Building and running: Android:
Build all games in release mode and run "hello_world".
```text
ninja -C out/release
./out/release/hello_world
```
Build "demo" in debug mode and run.
```text
ninja -C out/debug demo
./out/debug/demo
```
### Android:
Build "hello_world" in debug mode for all ABIs and install. GN will be run by
Gradle so no setup is required. Location of gn and ninja executables can be
specified via "gn" and "ninja" properties (-Pgn="path/gn").
```text ```text
cd build/android cd build/android
./gradlew :app:installHelloWorldAllArchsDebug ./gradlew :app:assembleRelease
``` ```
Build "demo" in debug mode for x86_64 ABI and install. Valid ABI targets are #### Third-party libraries:
Arm7, Arm8, X86, X86_64, AllArchs, ArmOnly, X86Only.
```text
./gradlew :app:installDemoX86_64Debug
```
### Generate Visual Studio solution:
```text
gn.exe gen --args="is_debug=true" --ide=vs2022 out\vs
devenv out\vs\all.sln
```
## Hello World example:
Shows a smoothly rotating "Hello World".
```cpp
class HelloWorld final : public eng::Game {
public:
bool Initialize() final {
eng::Engine::Get().SetImageSource(
"hello_world_image",
std::bind(&eng::Engine::Print, &eng::Engine::Get(), "Hello World",
/*bg_color*/ base::Vector4f(1, 1, 1, 0)));
hello_world_.Create("hello_world_image").SetVisible(true);
animator_.Attach(&hello_world_);
animator_.SetRotation(base::PI2f, /*duration*/ 3,
std::bind(base::SmootherStep, std::placeholders::_1));
animator_.Play(eng::Animator::kRotation, /*loop*/ true);
return true;
}
private:
eng::ImageQuad hello_world_;
eng::Animator animator_;
};
GAME_FACTORIES{GAME_CLASS(HelloWorld)};
```
## Third-party libraries:
[glew](https://github.com/nigels-com/glew), [glew](https://github.com/nigels-com/glew),
[jsoncpp](https://github.com/open-source-parsers/jsoncpp), [jsoncpp](https://github.com/open-source-parsers/jsoncpp),
[minimp3](https://github.com/lieff/minimp3), [minimp3](https://github.com/lieff/minimp3),
@ -93,5 +26,4 @@ GAME_FACTORIES{GAME_CLASS(HelloWorld)};
[spirv-reflect](https://github.com/KhronosGroup/SPIRV-Reflect), [spirv-reflect](https://github.com/KhronosGroup/SPIRV-Reflect),
[vma](https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator), [vma](https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator),
[vulkan-sdk](https://vulkan.lunarg.com), [vulkan-sdk](https://vulkan.lunarg.com),
[volk](https://github.com/zeux/volk), [volk](https://github.com/zeux/volk)
[imgui](https://github.com/ocornut/imgui)

View File

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

View File

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 103 KiB

View File

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

View File

Before

Width:  |  Height:  |  Size: 507 B

After

Width:  |  Height:  |  Size: 507 B

View File

@ -1,44 +0,0 @@
copy("demo") {
sources = [
"bead.png",
"boss_explosion.mp3",
"boss_intro.mp3",
"Boss_ok_lvl2.png",
"Boss_ok_lvl3.png",
"Boss_ok.png",
"chromatic_aberration.glsl_fragment",
"chromatic_aberration.glsl_vertex",
"enemy_anims_01_frames_ok.png",
"enemy_anims_02_frames_ok.png",
"enemy_anims_blast_ok.png",
"enemy_anims_flare_ok.png",
"enemy_ray_ok.png",
"enemy_target_single_ok.png",
"explosion.mp3",
"Game_2_Boss.mp3",
"Game_2_Main.mp3",
"hit.mp3",
"laser.mp3",
"menu_click.mp3",
"menu_icons.png",
"no_nuke.mp3",
"nuke_frames.png",
"nuke_pack_OK.png",
"nuke.mp3",
"PixelCaps!.ttf",
"powerup-pick.mp3",
"powerup-spawn.mp3",
"renderer_logo.png",
"shield.mp3",
"sky_without_nebula.glsl_fragment",
"sky_without_nebula.glsl_vertex",
"sky.glsl_fragment",
"sky.glsl_vertex",
"stealth.mp3",
"woom_enemy_shield.png",
"woom_logo_start_frames_01.png",
"woom_logo_start_frames_02-03.png",
]
outputs = ["$root_out_dir/assets/demo/{{source_file_part}}"]
}

View File

Before

Width:  |  Height:  |  Size: 332 KiB

After

Width:  |  Height:  |  Size: 332 KiB

View File

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

View File

Before

Width:  |  Height:  |  Size: 156 KiB

After

Width:  |  Height:  |  Size: 156 KiB

View File

Before

Width:  |  Height:  |  Size: 219 KiB

After

Width:  |  Height:  |  Size: 219 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -1,13 +0,0 @@
copy("engine") {
sources = [
"RobotoMono-Regular.ttf",
"imgui.glsl_fragment",
"imgui.glsl_vertex",
"pass_through.glsl_fragment",
"pass_through.glsl_vertex",
"solid.glsl_fragment",
"solid.glsl_vertex",
]
outputs = [ "$root_out_dir/assets/engine/{{source_file_part}}" ]
}

View File

@ -1,18 +0,0 @@
#ifdef GL_ES
precision mediump float;
#endif
IN(0) vec2 tex_coord_0;
IN(1) vec4 color;
UNIFORM_BEGIN
UNIFORM_V(mat4 projection)
UNIFORM_END
SAMPLER(0, sampler2D texture_0)
FRAG_COLOR_OUT(frag_color)
void main() {
FRAG_COLOR(frag_color) = TEXTURE(texture_0, tex_coord_0) * color;
}

View File

@ -1,17 +0,0 @@
IN(0) vec2 in_position;
IN(1) vec2 in_tex_coord_0;
IN(2) vec4 in_color;
UNIFORM_BEGIN
UNIFORM_V(mat4 projection)
UNIFORM_END
OUT(0) vec2 tex_coord_0;
OUT(1) vec4 color;
void main() {
tex_coord_0 = in_tex_coord_0;
color = in_color;
gl_Position = PARAM(projection) * vec4(in_position, 0.0, 1.0);
}

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 102 KiB

View File

Before

Width:  |  Height:  |  Size: 143 KiB

After

Width:  |  Height:  |  Size: 143 KiB

View File

Before

Width:  |  Height:  |  Size: 255 KiB

After

Width:  |  Height:  |  Size: 255 KiB

View File

@ -1,280 +0,0 @@
config("default") {
if (is_android) {
defines = [ "OS_ANDROID" ]
} else if (is_ios) {
defines = [ "OS_IOS" ]
} else if (is_linux) {
defines = [ "OS_LINUX" ]
} else if (is_mac) {
defines = [ "OS_MAC" ]
} else if (is_win) {
defines = [ "OS_WIN" ]
}
if (is_ios || is_mac) {
defines += [ "OS_APPLE" ]
}
cflags_cc = []
if (is_win) {
cflags_cc += [ "/std:c++20" ]
} else {
cflags_cc += [ "-std=c++20" ]
}
cflags = []
if (is_win) {
cflags += [
"/permissive-", # Don't enable nonconforming code to compile.
"/EHsc", # Use C++ exception handling.
"/Gd", # __cdecl calling convention.
"/utf-8", # Set Source and Executable character set.
]
defines += [
# Windows headers from MS and third party expects these to be set.
"_WINDOWS",
"_UNICODE",
"UNICODE",
# Don't want MS specific extensions for C stdlib functions.
"_CRT_SECURE_NO_WARNINGS",
# Clean up the Windows headers.
"WIN32_LEAN_AND_MEAN",
"NOMINMAX",
]
} else {
# cflags += [
# "-fstrict-aliasing", # Optimizations allowed for type aliasing.
# "-fvisibility=hidden", # Only let symbols be visible inside it's own
# # library.
# ]
# cflags_cc += [
# "-fvisibility-inlines-hidden", # Inline methods may not generate symbols.
# # "-stdlib=libc++",
# ]
if (is_linux) {
cflags += [
"-fPIC", # Emit position-independent code suitable for dynamic linking.
]
libs = [
"pthread", # Use the POSIX thread library.
]
} else if (is_mac) {
asmflags = [
"-target",
"arm64-apple-macos11",
]
cflags += [
# "-fPIC", # Emit position-independent code suitable for dynamic linking.
"-target",
"arm64-apple-macos11",
]
ldflags = [
"-target",
"arm64-apple-macos11",
]
} else if (is_android) {
cflags += [
"--sysroot=$ndk/toolchains/llvm/prebuilt/$ndk_host/sysroot",
"-fPIC", # Emit position-independent code suitable for dynamic linking.
]
ldflags = [ "-static-libstdc++" ]
}
}
# Avoid relative paths everywhere.
include_dirs = [
"//src",
]
}
config("debug") {
defines = [ "_DEBUG" ]
if (is_win) {
cflags = [
"/GS", # Buffer security checks (buffer overruns).
"/sdl", # Enable additional security features and warnings.
"/RTC1", # Enable fast run-time checks.
"/Od", # Disable optimizations.
"/fp:precise", # Floating points are predictable.
"/MDd", # Link with multithread debug DLL run-time libraries.
"/Z7", # Produce obj files that contain full symbolic debugging
# information.
]
ldflags = [ "/DEBUG:FASTLINK" ] # Reference the debug symbols in the obj
# files instead of a PDB file.
} else {
cflags = [
"-g", # Debug symbols.
# TODO: This makes it hard to step through code on Linux.
# "-Og", # Optimizations that works well with debugger.
]
if (is_unix) {
defines += [
"_GLIBCXX_DEBUG=1", # Enable asserts in stl headers.
]
}
}
}
config("release") {
defines = [ "NDEBUG" ]
if (is_win) {
cflags = [
"/GL", # Link-time code generation / Whole program optimization.
"/Gy", # Allows converting inline functions to separate functions.
"/O2", # Optimize for maximum speed.
"/Oi", # Enable intrinsics.
"/fp:fast", # Floating points are fast.
"/MD", # Link with multithread release DLL run-time libraries.
# "/arch:AVX2", # Minimum CPU architecture.
]
ldflags = [
"/LTCG", # Link time code generation.
"/OPT:ICF", # COMDAT (common data) folding, e.g. vtables and big inline
# functions that can't be inlined, but also if identical code
# happens to be generated though common math operations or
# similar.
"/OPT:REF", # Eliminate unreferenced code and data.
]
} else {
cflags = [
"-Ofast", # Full optimization and disregard strict standard compliance.
"-fno-math-errno", # Do not check or set errno for invalid input to sqrt
# and other math functions.
]
if (is_apple) {
ldflags = [ "-dead-strip" ]
} else {
# Place both data and functions in it's their own sections.
# Linker optimization that allows for smaller binaries.
cflags += [
"-fdata-sections",
"-ffunction-sections",
]
ldflags = [ "-Wl,--gc-sections" ]
}
}
}
config("warnings") {
if (is_win) {
cflags = [
"/WX", # Treat warnings as errors.
"/Wall", # Enable all warnings.
# Disable the following warnings:
"/wd4061", # enumerator in switch with a default case not explicitly
# handled by a case label.
"/wd4100", # nonstandard extension used: nameless struct/union.
"/wd4201", # enumerator in switch with a default case not explicitly
# handled by a case label.
"/wd4324", # structure was padded due to alignment specifier
"/wd4371", # layout of class may have changed from a previous version of
# the compiler due to better packing of member
"/wd4514", # unreferenced inline function has been removed.
"/wd4710", # function not inlined
"/wd4711", # function selected for automatic inline expansion
"/wd4820", # padding added after data member.
"/wd5045", # compiler will insert Spectre mitigation for memory load if
# switch specified.
"/wd4355", # 'this': used in base member initializer list
# TODO: Not sure how I feel about these conversion warnings.
"/Wv:18",
"/wd4191", # 'type cast': unsafe conversion
"/wd4244", # conversion, possible loss of data. 'int' to 'float'
"/wd4245", # conversion from 'int' to 'const unsigned int'
"/wd4305", # truncation from 'double' to 'float'.
"/wd4365", # conversion, signed/unsigned mismatch.
"/wd4722", # destructor never returns, potential memory leak
"/wd4702", # unreachable code
"/wd4625", # copy constructor was implicitly defined as deleted
"/wd4626", # assignment operator was implicitly defined as deleted
# Possible compiler bug? Needs investigation.
"/wd4668", # '__STDC_WANT_SECURE_LIB__' is not defined as a preprocessor
# macro, replacing with '0' for '#if/#elif'
]
defines = [
"_CRT_NONSTDC_NO_DEPRECATE",
"_SILENCE_CXX17_STRSTREAM_DEPRECATION_WARNING",
"_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS",
]
} else if (is_mac) {
cflags = [
"-Wno-deprecated-declarations",
"-Wno-inconsistent-missing-override",
"-Wno-pointer-sign",
]
} else {
cflags = [
# Enable as much warnings as possible.
"-Werror", # Make all warnings into errors.
"-Wall", # Enable (almost) all warnings.
"-Wextra", # Enable even more warnings.
# forbidden extensions.
"-Wvla", # Warn if variable-length array is used in code.
# Disable the following warnings:
"-Wno-unused-parameter",
"-Wno-sign-compare",
]
}
}
config("executable") {
if (is_android) {
ldflags = [
"-pie",
"-rdynamic",
]
} else if (is_linux) {
ldflags = [
"-Wl,-rpath,\$ORIGIN", # Add directory to runtime library search path.
]
} else if (is_mac) {
ldflags = [ "-Wl,-rpath,@loader_path/." ]
}
}
config("shared_library") {
if (is_android) {
ldflags = [
"-Wl,--build-id=sha1",
"-Wl,--no-rosegment",
"-Wl,--fatal-warnings",
"-Wl,--no-undefined",
"-Qunused-arguments",
"-u ANativeActivity_onCreate",
]
} else if (is_linux) {
ldflags = [
"-Wl,-rpath,\$ORIGIN", # Add directory to runtime library search path.
]
} else if (is_mac) {
ldflags = [ "-Wl,-rpath,@loader_path/." ]
}
}
# TODO: This needs some more investigation.
# Is it possible to avoid setting it and rely on defaults?
# Some tools will be console apps while games will be gui apps.
# GLFW seems to be using console in debug mode and gui in release mode.
if (is_win) {
config("win_gui") {
ldflags = [ "/SUBSYSTEM:WINDOWS" ]
}
config("win_console") {
ldflags = [ "/SUBSYSTEM:CONSOLE" ]
}
}

View File

@ -1,113 +0,0 @@
declare_args() {
is_debug = false
# Note that this uses the 'cc' and 'c++' links that should map to GCC on linux
# systems and clang on macs.
ar = "ar"
cc = "cc"
cxx = "c++"
ndk = ""
ndk_api = 24
}
# Platform detection
if (target_os == "") {
target_os = host_os
}
if (current_os == "") {
current_os = target_os
}
is_android = current_os == "android"
is_ios = current_os == "ios"
is_linux = current_os == "linux"
is_mac = current_os == "mac"
is_win = current_os == "win"
is_apple = is_mac || is_ios
is_desktop = is_mac || is_linux || is_win
is_mobile = is_android || is_ios
is_unix = is_android || is_linux
if (target_cpu == "") {
if (is_mobile) {
target_cpu = "arm64"
} else {
target_cpu = host_cpu
}
}
if (target_cpu == "x86_64") {
target_cpu = "x64"
}
if (current_cpu == "") {
current_cpu = target_cpu
}
if (is_android) {
assert(ndk != "", "NDK path argument is empty")
ndk_host = ""
ndk_target = ""
if (host_os == "linux") {
ndk_host = "linux-x86_64"
} else if (host_os == "mac") {
ndk_host = "darwin-x86_64"
} else if (host_os == "win") {
ndk_host = "windows-x86_64"
}
if (target_cpu == "arm64") {
ndk_target = "aarch64-linux-android"
} else if (target_cpu == "arm") {
ndk_target = "armv7a-linux-androideabi"
} else if (target_cpu == "x64") {
ndk_target = "x86_64-linux-android"
} else if (target_cpu == "x86") {
ndk_target = "i686-linux-android"
}
_prefix = "$ndk/toolchains/llvm/prebuilt/$ndk_host/bin"
if (host_os == "win") {
ar = "$_prefix/llvm-ar.exe"
cc = "$_prefix/clang.exe --target=$ndk_target$ndk_api -fno-addrsig"
cxx = "$_prefix/clang++.exe --target=$ndk_target$ndk_api -fno-addrsig"
} else {
ar = "$_prefix/llvm-ar"
cc = "$_prefix/$ndk_target$ndk_api-clang"
cxx = "$_prefix/$ndk_target$ndk_api-clang++"
}
}
# Set defaults
_default_config = [
"//build:default",
"//build:warnings",
]
if (is_debug) {
_default_config += [ "//build:debug" ]
} else {
_default_config += [ "//build:release" ]
}
set_defaults("executable") {
configs = [ "//build:executable" ] + _default_config
}
set_defaults("static_library") {
configs = _default_config
}
set_defaults("shared_library") {
configs = [ "//build:shared_library" ] + _default_config
}
set_defaults("source_set") {
configs = _default_config
}
if (is_win) {
set_default_toolchain("//build/toolchain/msvc")
} else {
# Clang behaves like GCC in most cases.
set_default_toolchain("//build/toolchain/gcc")
}

View File

@ -0,0 +1,171 @@
cmake_minimum_required(VERSION 3.22.1)
# common_config
add_library(common_config INTERFACE)
target_compile_options(common_config INTERFACE
$<$<COMPILE_LANGUAGE:CXX>:-std=c++20>
#$<$<NOT:$<CONFIG:DEBUG>>:-Ofast>
-Wall
-Werror
-Wno-unused-but-set-variable
-Wno-deprecated-enum-enum-conversion
-Wno-unsequenced
-Wno-nullability-completeness
)
target_compile_definitions(common_config INTERFACE
VK_USE_PLATFORM_ANDROID_KHR
VMA_STATIC_VULKAN_FUNCTIONS=1
$<$<CONFIG:DEBUG>:_DEBUG>
)
target_include_directories(common_config INTERFACE
../../../src
../../../src/third_party/glslang
../../../src/third_party/vulkan/include
)
# oboe
set (OBOE_DIR ../../../src/third_party/oboe)
add_subdirectory(${OBOE_DIR} ./oboe-bin)
# cpufeatures
add_library(cpufeatures STATIC ${ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c)
target_include_directories(cpufeatures PUBLIC ${ANDROID_NDK}/sources/android/cpufeatures)
# native_app_glue
add_library(native_app_glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
target_include_directories(native_app_glue PUBLIC ${ANDROID_NDK}/sources/android/native_app_glue)
set_property(TARGET native_app_glue PROPERTY POSITION_INDEPENDENT_CODE ON)
# Export ANativeActivity_onCreate(),
# Refer to: https://github.com/android-ndk/ndk/issues/381.
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
# kaliber
project(kaliber)
add_library(kaliber SHARED
../../../src/base/collusion_test.cc
../../../src/base/log.cc
../../../src/base/task_runner.cc
../../../src/base/thread_pool.cc
../../../src/base/timer.cc
../../../src/demo/credits.cc
../../../src/demo/demo.cc
../../../src/demo/enemy.cc
../../../src/demo/hud.cc
../../../src/demo/menu.cc
../../../src/demo/player.cc
../../../src/demo/sky_quad.cc
../../../src/engine/animatable.cc
../../../src/engine/animator.cc
../../../src/engine/audio/audio_bus.cc
../../../src/engine/audio/audio_mixer.cc
../../../src/engine/audio/audio_sink_oboe.cc
../../../src/engine/audio/sinc_resampler.cc
../../../src/engine/drawable.cc
../../../src/engine/engine.cc
../../../src/engine/font.cc
../../../src/engine/image_quad.cc
../../../src/engine/image.cc
../../../src/engine/mesh.cc
../../../src/engine/persistent_data.cc
../../../src/engine/platform/asset_file_android.cc
../../../src/engine/platform/asset_file.cc
../../../src/engine/platform/platform_android.cc
../../../src/engine/renderer/geometry.cc
../../../src/engine/renderer/opengl/render_command.cc
../../../src/engine/renderer/opengl/renderer_opengl_android.cc
../../../src/engine/renderer/opengl/renderer_opengl.cc
../../../src/engine/renderer/renderer_types.cc
../../../src/engine/renderer/shader.cc
../../../src/engine/renderer/texture.cc
../../../src/engine/renderer/vulkan/renderer_vulkan_android.cc
../../../src/engine/renderer/vulkan/renderer_vulkan.cc
../../../src/engine/renderer/vulkan/vulkan_context_android.cc
../../../src/engine/renderer/vulkan/vulkan_context.cc
../../../src/engine/shader_source.cc
../../../src/engine/solid_quad.cc
../../../src/engine/sound_player.cc
../../../src/engine/sound.cc
../../../src/third_party/android/gl3stub.c
../../../src/third_party/android/GLContext.cpp
../../../src/third_party/glslang/glslang/GenericCodeGen/CodeGen.cpp
../../../src/third_party/glslang/glslang/GenericCodeGen/Link.cpp
../../../src/third_party/glslang/glslang/MachineIndependent/attribute.cpp
../../../src/third_party/glslang/glslang/MachineIndependent/Constant.cpp
../../../src/third_party/glslang/glslang/MachineIndependent/glslang_tab.cpp
../../../src/third_party/glslang/glslang/MachineIndependent/InfoSink.cpp
../../../src/third_party/glslang/glslang/MachineIndependent/Initialize.cpp
../../../src/third_party/glslang/glslang/MachineIndependent/Intermediate.cpp
../../../src/third_party/glslang/glslang/MachineIndependent/intermOut.cpp
../../../src/third_party/glslang/glslang/MachineIndependent/IntermTraverse.cpp
../../../src/third_party/glslang/glslang/MachineIndependent/iomapper.cpp
../../../src/third_party/glslang/glslang/MachineIndependent/limits.cpp
../../../src/third_party/glslang/glslang/MachineIndependent/linkValidate.cpp
../../../src/third_party/glslang/glslang/MachineIndependent/parseConst.cpp
../../../src/third_party/glslang/glslang/MachineIndependent/ParseContextBase.cpp
../../../src/third_party/glslang/glslang/MachineIndependent/ParseHelper.cpp
../../../src/third_party/glslang/glslang/MachineIndependent/PoolAlloc.cpp
../../../src/third_party/glslang/glslang/MachineIndependent/preprocessor/Pp.cpp
../../../src/third_party/glslang/glslang/MachineIndependent/preprocessor/PpAtom.cpp
../../../src/third_party/glslang/glslang/MachineIndependent/preprocessor/PpContext.cpp
../../../src/third_party/glslang/glslang/MachineIndependent/preprocessor/PpScanner.cpp
../../../src/third_party/glslang/glslang/MachineIndependent/preprocessor/PpTokens.cpp
../../../src/third_party/glslang/glslang/MachineIndependent/propagateNoContraction.cpp
../../../src/third_party/glslang/glslang/MachineIndependent/reflection.cpp
../../../src/third_party/glslang/glslang/MachineIndependent/RemoveTree.cpp
../../../src/third_party/glslang/glslang/MachineIndependent/Scan.cpp
../../../src/third_party/glslang/glslang/MachineIndependent/ShaderLang.cpp
../../../src/third_party/glslang/glslang/MachineIndependent/SpirvIntrinsics.cpp
../../../src/third_party/glslang/glslang/MachineIndependent/SymbolTable.cpp
../../../src/third_party/glslang/glslang/MachineIndependent/Versions.cpp
../../../src/third_party/glslang/glslang/OSDependent/Unix/ossource.cpp
../../../src/third_party/glslang/OGLCompilersDLL/InitializeDll.cpp
../../../src/third_party/glslang/SPIRV/disassemble.cpp
../../../src/third_party/glslang/SPIRV/doc.cpp
../../../src/third_party/glslang/SPIRV/GlslangToSpv.cpp
../../../src/third_party/glslang/SPIRV/InReadableOrder.cpp
../../../src/third_party/glslang/SPIRV/Logger.cpp
../../../src/third_party/glslang/SPIRV/SpvBuilder.cpp
../../../src/third_party/glslang/SPIRV/SpvPostProcess.cpp
../../../src/third_party/glslang/SPIRV/SPVRemapper.cpp
../../../src/third_party/glslang/SPIRV/SpvTools.cpp
../../../src/third_party/jsoncpp/jsoncpp.cpp
../../../src/third_party/minizip/ioapi.c
../../../src/third_party/minizip/unzip.c
../../../src/third_party/spirv-reflect/spirv_reflect.c
../../../src/third_party/stb/stb_image.c
../../../src/third_party/texture_compressor/dxt_encoder_internals.cc
../../../src/third_party/texture_compressor/dxt_encoder.cc
../../../src/third_party/texture_compressor/texture_compressor_etc1.cc
../../../src/third_party/texture_compressor/texture_compressor.cc
../../../src/third_party/vma/vk_mem_alloc.cpp
../../../src/third_party/volk/volk.c
)
if (ANDROID_ABI STREQUAL armeabi-v7a)
target_sources(kaliber PRIVATE ../../../src/third_party/texture_compressor/dxt_encoder_neon.cc)
target_sources(kaliber PRIVATE ../../../src/third_party/texture_compressor/texture_compressor_etc1_neon.cc)
set_source_files_properties(../../../src/third_party/texture_compressor/dxt_encoder_neon.cc PROPERTIES COMPILE_FLAGS -mfpu=neon)
set_source_files_properties(../../../src/third_party/texture_compressor/texture_compressor_etc1_neon.cc PROPERTIES COMPILE_FLAGS -mfpu=neon)
endif()
if (ANDROID_ABI STREQUAL arm64-v8a)
target_sources(kaliber PRIVATE ../../../src/third_party/texture_compressor/dxt_encoder_neon.cc)
target_sources(kaliber PRIVATE ../../../src/third_party/texture_compressor/texture_compressor_etc1_neon.cc)
endif()
target_link_libraries(kaliber PRIVATE
common_config
android
native_app_glue
oboe
cpufeatures
EGL
GLESv2
log
z
)

View File

@ -1,131 +1,45 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: Utils
android { android {
compileSdk rootProject.ext.compileSdk compileSdk 33
ndkVersion rootProject.ext.ndkVersion ndkVersion '25.2.9519653'
defaultConfig { defaultConfig {
minSdk rootProject.ext.minSdk applicationId = 'com.woom.game'
targetSdk rootProject.ext.targetSdk minSdk 24
targetSdk 33
externalNativeBuild {
cmake {
arguments '-DANDROID_STL=c++_static'
}
}
ndk { ndk {
abiFilters = [] abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64'
} }
} }
buildTypes { buildTypes {
release { release {
minifyEnabled false minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro' 'proguard-rules.pro'
}
}
flavorDimensions 'game', 'arch'
productFlavors {
helloWorld {
dimension 'game'
applicationId 'com.kaliber.helloworld'
resValue "string", "app_name", "Kaliber Hello World"
manifestPlaceholders = [appIcon: "@mipmap/ic_launcher"]
ext {
gnTarget = "hello_world"
}
}
demo {
dimension 'game'
applicationId 'com.kaliber.woom'
resValue "string", "app_name", "Kaliber Demo"
manifestPlaceholders = [appIcon: "@mipmap/ic_launcher"]
ext {
gnTarget = "demo"
}
}
woom {
dimension 'game'
applicationId 'com.woom.game'
resValue "string", "app_name", "woom"
resValue "string", "interstitial_ad_unit_id", "ca-app-pub-1321063817979967/8373182022"
resValue "string", "admob_application_id", "ca-app-pub-1321063817979967~1100949243"
manifestPlaceholders = [appIcon: "@mipmap/ic_launcher"]
ext {
gnTarget = "demo"
}
}
arm7 {
dimension 'arch'
ndk { ndk {
abiFilters = ["armeabi-v7a"] debugSymbolLevel 'FULL'
}
}
arm8 {
dimension 'arch'
ndk {
abiFilters = ["arm64-v8a"]
}
}
x86 {
dimension 'arch'
ndk {
abiFilters = ["x86"]
}
}
x86_64 {
dimension 'arch'
ndk {
abiFilters = ["x86_64"]
}
}
allArchs {
dimension 'arch'
ndk {
abiFilters = ["armeabi-v7a", "arm64-v8a", "x86_64", "x86"]
}
}
armOnly {
dimension 'arch'
ndk {
abiFilters = ["armeabi-v7a", "arm64-v8a"]
}
}
x86Only {
dimension 'arch'
ndk {
abiFilters = ["x86_64", "x86"]
}
}
// Native library name is same as GN target name.
android.productFlavors.each { flavor ->
if (flavor.dimension == 'game') {
"${flavor.name}" {
resValue "string", "lib_name", flavor.ext.gnTarget
buildConfigField 'String', 'NATIVE_LIBRARY', "\"${flavor.ext.gnTarget}\""
}
} }
} }
} }
externalNativeBuild {
cmake {
version '3.22.1'
path 'CMakeLists.txt'
}
}
sourceSets { sourceSets {
main { main {
java.srcDirs += ['../../../src/engine/platform/java/com/kaliber/base'] java.srcDirs += ['../../../src/engine/platform/java/com/kaliber/base']
android.buildTypes.each { buildType -> assets.srcDirs = ['../../../assets']
"${buildType.name}" {
assets.srcDirs = ["${utils.getGnOutDir(buildType.name)}/assets"]
}
}
}
android.buildTypes.each { buildType ->
"${buildType.name}" {
jniLibs.srcDirs = ["${utils.getGnOutDir(buildType.name)}/jniLibs"]
}
} }
} }
namespace 'com.woom.game'
namespace "com.kaliber.base"
} }
dependencies { dependencies {
@ -134,247 +48,3 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'com.google.android.gms:play-services-ads:22.0.0' implementation 'com.google.android.gms:play-services-ads:22.0.0'
} }
//
// Tasks for GN build
//
// Generate `args.gn` which is used by GN to take in build arguments.
utils.addTask('generateGnArgsFor') { String taskName, String buildType, String arch ->
task(taskName, type: WriteFileTask) {
content = utils.generateGnArgsContent(buildType, arch)
target = project.layout.file(provider { new File("${utils.getGnOutDir(buildType)}/${arch}", 'args.gn') })
}
}
// Run `gn gen` to generate ninja files.
utils.addTask('runGnFor') { String taskName, String buildType, String arch ->
task(taskName, type: Exec) {
dependsOn "generateGnArgsFor${arch}${buildType}"
executable rootProject.ext.gn
args '--fail-on-unused-args', 'gen', "${utils.getGnOutDir(buildType)}/${arch}"
// Need to run gn only once unless the configuration in `args.gn` changes.
inputs.file(new File("${utils.getGnOutDir(buildType)}/${arch}", 'args.gn'))
outputs.file(new File("${utils.getGnOutDir(buildType)}/${arch}", 'build.ninja'))
}
}
// Build the native library for the target game using ninja.
utils.addGameTask('runNinjaFor') { String taskName, String buildType, String arch, String game ->
task(taskName, type: Exec) {
dependsOn "runGnFor${arch}${buildType}"
executable rootProject.ext.ninja
args '-C', "${utils.getGnOutDir(buildType)}/${arch}", "src/${utils.getGnTargetFor(game)}"
// Always run ninja and let it figure out what needs to be compiled.
outputs.upToDateWhen { false }
}
}
// Assets can be obtained from the output directory of any arch but it would be good to combine them
// in a single directory in case we are buildig multi-arch and some build config have different assets.
utils.addGameTask('copyAssetsFor') { String taskName, String buildType, String arch, String game ->
task(taskName, type: Copy) {
dependsOn "runNinjaFor${game}${arch}${buildType}"
from "${utils.getGnOutDir(buildType)}/${arch}/assets"
into "${utils.getGnOutDir(buildType)}/assets"
}
}
// Copy the native library to a directory denoting its arch code as the Android Gradle plugin expects.
utils.addGameTask('copyJniLibsFor') { String taskName, String buildType, String arch, String game ->
task(taskName, type: Copy) {
dependsOn "runNinjaFor${game}${arch}${buildType}"
from("${utils.getGnOutDir(buildType)}/${arch}") {
include "lib${utils.getGnTargetFor(game)}.so"
}
into "${utils.getGnOutDir(buildType)}/jniLibs/${utils.getAbiCodeFor(arch)}"
}
}
tasks.configureEach { task ->
def variantPattern = /(\w+)(${utils.getArchTypesRegExp()})(${utils.getBuildTypesRegExp()})/
def match = task.name =~ /^merge/ + variantPattern + /JniLibFolders$/
if (match) {
utils.project.android.productFlavors.find { arch ->
if (arch.dimension == 'arch' && arch.name.capitalize() == match.group(2)) {
// Depends on each arch type for multi-arch build flavors.
arch.ndk.abiFilters.each { abi ->
task.dependsOn "copyJniLibsFor${match.group(1)}${utils.ARCH_CODES[abi]}${match.group(3)}"
}
return true
}
}
return
}
match = task.name =~ /^generate/ + variantPattern + /Assets$/
if (match) {
utils.project.android.productFlavors.find { arch ->
if (arch.dimension == 'arch' && arch.name.capitalize() == match.group(2)) {
// Depends on each arch type for multi-arch build flavors.
arch.ndk.abiFilters.each { abi ->
task.dependsOn "copyAssetsFor${match.group(1)}${utils.ARCH_CODES[abi]}${match.group(3)}"
}
return true
}
}
return
}
match = task.name =~ /^lintVitalAnalyze/ + variantPattern + /$/
if (match) {
utils.project.android.productFlavors.find { arch ->
if (arch.dimension == 'arch' && arch.name.capitalize() == match.group(2)) {
// Depends on each arch type for multi-arch build flavors.
arch.ndk.abiFilters.each { abi ->
task.dependsOn "copyAssetsFor${match.group(1)}${utils.ARCH_CODES[abi]}${match.group(3)}"
}
return true
}
}
return
}
}
//
// Utils plugin
//
class Utils implements Plugin<Project> {
final def ARCH_CODES = ["armeabi-v7a": "Arm7",
"arm64-v8a": "Arm8",
"x86_64": "X86_64",
"x86": "X86"].asImmutable()
final def GN_CPU_CODES = ["Arm7": "arm",
"Arm8": "arm64",
"X86_64": "x64",
"X86": "x86"].asImmutable()
def project
@Inject
Utils(Project project) {
this.project = project
}
void apply(Project project) {
project.extensions.create('utils', Utils)
}
// Add a task for archs and buildTypes variants.
void addTask(String prefix, Closure taskClosure) {
forEachBuildVariant { String arch, String buildType ->
def taskName = "${prefix}${arch}${buildType}"
taskClosure(taskName, buildType, arch)
}
}
// Add a task for games, archs and buildTypes variants.
void addGameTask(String prefix, Closure taskClosure) {
forEachGameBuildVariant { String game, String arch, String buildType ->
def taskName = "${prefix}${game}${arch}${buildType}"
taskClosure(taskName, buildType, arch, game)
}
}
void forEachBuildVariant(Closure callback) {
project.android.productFlavors.each { arch ->
// Only need to add tasks for arch types which maps to a single ABI
if (arch.dimension == 'arch' && arch.ndk.abiFilters.size() == 1) {
project.android.buildTypes.each { buildType ->
callback(arch.name.capitalize(), buildType.name.capitalize())
}
}
}
}
void forEachGameBuildVariant(Closure callback) {
project.android.productFlavors.each { game ->
if (game.dimension == 'game') {
project.android.productFlavors.each { arch ->
// Only need to add tasks for arch types which maps to a single ABI
if (arch.dimension == 'arch' && arch.ndk.abiFilters.size() == 1) {
project.android.buildTypes.each { buildType ->
callback(game.name.capitalize(), arch.name.capitalize(), buildType.name.capitalize())
}
}
}
}
}
}
def getBuildTypesRegExp() {
def outList = []
project.android.buildTypes.each { buildType ->
outList += buildType.name.capitalize()
}
return outList.join('|')
}
def getArchTypesRegExp() {
def outList = []
project.android.productFlavors.each { flavor ->
if (flavor.dimension == 'arch') {
outList += flavor.name.capitalize()
}
}
return outList.join('|')
}
def getAbiCodeFor(String arch) {
def outStr = ''
project.android.productFlavors.find { flavor ->
if (flavor.name.capitalize() == arch) {
outStr = flavor.ndk.abiFilters.first()
return true
}
}
return outStr
}
def getGnTargetFor(String game) {
def outStr = ''
project.android.productFlavors.find { flavor ->
if (flavor.dimension == 'game' && flavor.name.capitalize() == game) {
outStr = flavor.ext.gnTarget
return true
}
}
return outStr
}
def generateGnArgsContent(String buildType, String arch) {
def content = 'target_os="android"\n'
content += 'target_cpu="' + GN_CPU_CODES[arch] + '"\n'
content += "is_debug=${buildType != 'Release'}\n"
content += 'ndk="' + project.android.ndkDirectory + '"\n'
content += "ndk_api=${project.rootProject.ext.minSdk}\n"
return content
}
def getGnOutDir(String buildType) {
return "${project.buildDir}/gn_out/${buildType.toLowerCase()}"
}
}
abstract class WriteFileTask extends DefaultTask {
@Input
abstract Property<String> getContent()
@OutputFile
abstract RegularFileProperty getTarget()
@TaskAction
void run() {
def file = target.get().asFile
file.parentFile.mkdirs()
def text = content.get()
if (!file.exists() || text != file.text)
file.text = text
}
}

View File

@ -6,7 +6,7 @@
<application <application
android:allowBackup="false" android:allowBackup="false"
android:fullBackupContent="false" android:fullBackupContent="false"
android:icon="${appIcon}" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"> android:label="@string/app_name">
<activity <activity
@ -27,16 +27,16 @@
</intent-filter> </intent-filter>
<meta-data <meta-data
android:name="android.app.lib_name" android:name="android.app.lib_name"
android:value="@string/lib_name" /> android:value="kaliber" />
</activity> </activity>
<meta-data <meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID" android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="@string/admob_application_id" /> android:value="ca-app-pub-1321063817979967~1100949243" />
<provider <provider
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider" android:authorities="com.woom.game.fileprovider"
android:exported="false" android:exported="false"
android:grantUriPermissions="true"> android:grantUriPermissions="true">
<meta-data <meta-data

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name">Kaliber app</string> <string name="app_name">woom</string>
<string name="interstitial_ad_unit_id"></string> <string name="interstitial_ad_unit_id">ca-app-pub-1321063817979967/8373182022</string>
<string name="admob_application_id">ca-app-pub-3940256099942544~3347511713</string>
</resources> </resources>

View File

@ -5,7 +5,7 @@ buildscript {
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:8.1.0' classpath 'com.android.tools.build:gradle:8.0.1'
} }
} }
@ -19,17 +19,3 @@ allprojects {
task clean(type: Delete) { task clean(type: Delete) {
delete rootProject.buildDir delete rootProject.buildDir
} }
ext {
ndkVersion = '25.2.9519653'
minSdk = 24
compileSdk = 33
targetSdk = 33
if (!project.hasProperty('gn')) {
gn = "gn"
}
if (!project.hasProperty('ninja')) {
ninja = "ninja"
}
}

View File

@ -1,89 +0,0 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

283
build/linux/Makefile Normal file
View File

@ -0,0 +1,283 @@
.DEFAULT_GOAL := all
# --- Input variables ---
BUILD ?= release
ifeq ($(findstring $(BUILD),debug release),)
$(error BUILD must be set to debug or release)
endif
# Build all executables by default.
APPS ?= demo
# If the VERBOSE flag isn't set, then mute superfluous output.
ifeq ($(VERBOSE),)
HUSH_COMPILE = @echo "Compiling $<";
HUSH_LINK = @echo "Linking $@";
HUSH_GENERATE = @echo "Generating $@";
HUSH_CLEAN = @
endif
# --- Internal variables ---
ARCH := $(shell uname -p)
SRC_ROOT := $(abspath ../../src)
OUTPUT_DIR := $(abspath .)
INTERMEDIATE_DIR := $(OUTPUT_DIR)/obj
BUILD_DIR := $(INTERMEDIATE_DIR)/$(BUILD)
ARFLAGS = r
LDFLAGS = -lX11 -lGL -pthread -lasound -ldl
# Always enable debug information.
CFLAGS += -g
# Enable extra warnings and make all warnings into errors.
CFLAGS += -Wextra -Werror
# Flags to generate dependency information.
CFLAGS += -MD -MP -MT $@
# Predefined flags.
ifeq ($(BUILD), debug)
CFLAGS += -D_DEBUG
CFLAGS += -D_GLIBCXX_DEBUG
endif
# Enable compiler optimizations for everything except debug.
# Note that a very aggresssive optimization level is used and it may not be
# valid for all standard compliant programs. Reduce this level on individual
# files or modules as needed.
ifneq ($(BUILD), debug)
CFLAGS += -Ofast
endif
# Flag to turn on extended instruction sets for the compiler.
CFLAGS += -msse2
# Let C++ inherit all C flags.
CXXFLAGS = $(CFLAGS) -I$(SRC_ROOT)
# Enable C++20
CXXFLAGS += -std=c++20
# Vulkan config
CFLAGS += -DVK_USE_PLATFORM_XLIB_KHR
CFLAGS += -DVMA_STATIC_VULKAN_FUNCTIONS=1
CFLAGS += -I$(SRC_ROOT)/third_party/vulkan/include
CXXFLAGS += -I$(SRC_ROOT)/third_party/glslang
# --- Internal functions ---
app_exe = $(OUTPUT_DIR)/$(1)_$(ARCH)_$(BUILD)
objs_from_src = $(patsubst $(SRC_ROOT)/%, $(BUILD_DIR)/%.o, $(basename $(1)))
objs_from_src_in = $(call objs_from_src, $(shell find $(1) -name "*.cc" -o -name "*.cpp" -o -name "*.c"))
# --- Base ---
BASE_SRC := \
$(SRC_ROOT)/base/collusion_test.cc \
$(SRC_ROOT)/base/log.cc \
$(SRC_ROOT)/base/task_runner.cc \
$(SRC_ROOT)/base/thread_pool.cc \
$(SRC_ROOT)/base/timer.cc
BASE_LIB := $(BUILD_DIR)/libengine.a
BASE_OBJS := $(call objs_from_src, $(BASE_SRC))
LIBS += $(BASE_LIB)
OBJS += $(BASE_OBJS)
$(BASE_LIB): $(BASE_OBJS)
# --- Engine ---
ENGINE_SRC := \
$(SRC_ROOT)/engine/animatable.cc \
$(SRC_ROOT)/engine/animator.cc \
$(SRC_ROOT)/engine/audio/audio_bus.cc \
$(SRC_ROOT)/engine/audio/audio_mixer.cc \
$(SRC_ROOT)/engine/audio/audio_sink_alsa.cc \
$(SRC_ROOT)/engine/audio/sinc_resampler.cc \
$(SRC_ROOT)/engine/drawable.cc \
$(SRC_ROOT)/engine/engine.cc \
$(SRC_ROOT)/engine/font.cc \
$(SRC_ROOT)/engine/image_quad.cc \
$(SRC_ROOT)/engine/image.cc \
$(SRC_ROOT)/engine/mesh.cc \
$(SRC_ROOT)/engine/persistent_data.cc \
$(SRC_ROOT)/engine/platform/asset_file_linux.cc \
$(SRC_ROOT)/engine/platform/asset_file.cc \
$(SRC_ROOT)/engine/platform/platform_linux.cc \
$(SRC_ROOT)/engine/renderer/geometry.cc \
$(SRC_ROOT)/engine/renderer/opengl/render_command.cc \
$(SRC_ROOT)/engine/renderer/opengl/renderer_opengl_linux.cc \
$(SRC_ROOT)/engine/renderer/opengl/renderer_opengl.cc \
$(SRC_ROOT)/engine/renderer/renderer_types.cc \
$(SRC_ROOT)/engine/renderer/shader.cc \
$(SRC_ROOT)/engine/renderer/texture.cc \
$(SRC_ROOT)/engine/renderer/vulkan/renderer_vulkan_linux.cc \
$(SRC_ROOT)/engine/renderer/vulkan/renderer_vulkan.cc \
$(SRC_ROOT)/engine/renderer/vulkan/vulkan_context_linux.cc \
$(SRC_ROOT)/engine/renderer/vulkan/vulkan_context.cc \
$(SRC_ROOT)/engine/shader_source.cc \
$(SRC_ROOT)/engine/solid_quad.cc \
$(SRC_ROOT)/engine/sound_player.cc \
$(SRC_ROOT)/engine/sound.cc
ENGINE_LIB := $(BUILD_DIR)/libengine.a
ENGINE_OBJS := $(call objs_from_src, $(ENGINE_SRC))
LIBS += $(ENGINE_LIB)
OBJS += $(ENGINE_OBJS)
$(ENGINE_LIB): $(ENGINE_OBJS)
# --- Third-party ---
# Ignore warnings.
THIRD_PARTY_CFLAGS = $(CFLAGS)
THIRD_PARTY_CFLAGS := $(filter-out -Wextra -Werror, $(THIRD_PARTY_CFLAGS))
THIRD_PARTY_CXXFLAGS = $(CXXFLAGS)
THIRD_PARTY_CXXFLAGS := $(filter-out -Wextra -Werror, $(THIRD_PARTY_CXXFLAGS))
THIRD_PARTY_SRC := \
$(SRC_ROOT)/third_party/glew/glew.c \
$(SRC_ROOT)/third_party/glslang/glslang/GenericCodeGen/CodeGen.cpp \
$(SRC_ROOT)/third_party/glslang/glslang/GenericCodeGen/Link.cpp \
$(SRC_ROOT)/third_party/glslang/glslang/MachineIndependent/attribute.cpp \
$(SRC_ROOT)/third_party/glslang/glslang/MachineIndependent/Constant.cpp \
$(SRC_ROOT)/third_party/glslang/glslang/MachineIndependent/glslang_tab.cpp \
$(SRC_ROOT)/third_party/glslang/glslang/MachineIndependent/InfoSink.cpp \
$(SRC_ROOT)/third_party/glslang/glslang/MachineIndependent/Initialize.cpp \
$(SRC_ROOT)/third_party/glslang/glslang/MachineIndependent/Intermediate.cpp \
$(SRC_ROOT)/third_party/glslang/glslang/MachineIndependent/intermOut.cpp \
$(SRC_ROOT)/third_party/glslang/glslang/MachineIndependent/IntermTraverse.cpp \
$(SRC_ROOT)/third_party/glslang/glslang/MachineIndependent/iomapper.cpp \
$(SRC_ROOT)/third_party/glslang/glslang/MachineIndependent/limits.cpp \
$(SRC_ROOT)/third_party/glslang/glslang/MachineIndependent/linkValidate.cpp \
$(SRC_ROOT)/third_party/glslang/glslang/MachineIndependent/parseConst.cpp \
$(SRC_ROOT)/third_party/glslang/glslang/MachineIndependent/ParseContextBase.cpp \
$(SRC_ROOT)/third_party/glslang/glslang/MachineIndependent/ParseHelper.cpp \
$(SRC_ROOT)/third_party/glslang/glslang/MachineIndependent/PoolAlloc.cpp \
$(SRC_ROOT)/third_party/glslang/glslang/MachineIndependent/preprocessor/Pp.cpp \
$(SRC_ROOT)/third_party/glslang/glslang/MachineIndependent/preprocessor/PpAtom.cpp \
$(SRC_ROOT)/third_party/glslang/glslang/MachineIndependent/preprocessor/PpContext.cpp \
$(SRC_ROOT)/third_party/glslang/glslang/MachineIndependent/preprocessor/PpScanner.cpp \
$(SRC_ROOT)/third_party/glslang/glslang/MachineIndependent/preprocessor/PpTokens.cpp \
$(SRC_ROOT)/third_party/glslang/glslang/MachineIndependent/propagateNoContraction.cpp \
$(SRC_ROOT)/third_party/glslang/glslang/MachineIndependent/reflection.cpp \
$(SRC_ROOT)/third_party/glslang/glslang/MachineIndependent/RemoveTree.cpp \
$(SRC_ROOT)/third_party/glslang/glslang/MachineIndependent/Scan.cpp \
$(SRC_ROOT)/third_party/glslang/glslang/MachineIndependent/ShaderLang.cpp \
$(SRC_ROOT)/third_party/glslang/glslang/MachineIndependent/SpirvIntrinsics.cpp \
$(SRC_ROOT)/third_party/glslang/glslang/MachineIndependent/SymbolTable.cpp \
$(SRC_ROOT)/third_party/glslang/glslang/MachineIndependent/Versions.cpp \
$(SRC_ROOT)/third_party/glslang/glslang/OSDependent/Unix/ossource.cpp \
$(SRC_ROOT)/third_party/glslang/OGLCompilersDLL/InitializeDll.cpp \
$(SRC_ROOT)/third_party/glslang/SPIRV/disassemble.cpp \
$(SRC_ROOT)/third_party/glslang/SPIRV/doc.cpp \
$(SRC_ROOT)/third_party/glslang/SPIRV/GlslangToSpv.cpp \
$(SRC_ROOT)/third_party/glslang/SPIRV/InReadableOrder.cpp \
$(SRC_ROOT)/third_party/glslang/SPIRV/Logger.cpp \
$(SRC_ROOT)/third_party/glslang/SPIRV/SpvBuilder.cpp \
$(SRC_ROOT)/third_party/glslang/SPIRV/SpvPostProcess.cpp \
$(SRC_ROOT)/third_party/glslang/SPIRV/SPVRemapper.cpp \
$(SRC_ROOT)/third_party/glslang/SPIRV/SpvTools.cpp \
$(SRC_ROOT)/third_party/jsoncpp/jsoncpp.cpp \
$(SRC_ROOT)/third_party/spirv-reflect/spirv_reflect.c \
$(SRC_ROOT)/third_party/stb/stb_image.c \
$(SRC_ROOT)/third_party/texture_compressor/dxt_encoder_internals.cc \
$(SRC_ROOT)/third_party/texture_compressor/dxt_encoder.cc \
$(SRC_ROOT)/third_party/texture_compressor/texture_compressor_etc1.cc \
$(SRC_ROOT)/third_party/texture_compressor/texture_compressor.cc \
$(SRC_ROOT)/third_party/vma/vk_mem_alloc.cpp \
$(SRC_ROOT)/third_party/volk/volk.c
$(BUILD_DIR)/third_party/%.o: $(SRC_ROOT)/third_party/%.c
@mkdir -p $(@D)
$(HUSH_COMPILE) $(CC) -c $(THIRD_PARTY_CFLAGS) -o $@ $<
$(BUILD_DIR)/third_party/%.o: $(SRC_ROOT)/third_party/%.cc
@mkdir -p $(@D)
$(HUSH_COMPILE) $(CXX) -c $(THIRD_PARTY_CXXFLAGS) -o $@ $<
$(BUILD_DIR)/third_party/%.o: $(SRC_ROOT)/third_party/%.cpp
@mkdir -p $(@D)
$(HUSH_COMPILE) $(CXX) -c $(THIRD_PARTY_CXXFLAGS) -o $@ $<
THIRD_PARTY_LIB := $(BUILD_DIR)/libthirdparty.a
THIRD_PARTY_OBJS := $(call objs_from_src, $(THIRD_PARTY_SRC))
LIBS += $(THIRD_PARTY_LIB)
OBJS += $(THIRD_PARTY_OBJS)
$(THIRD_PARTY_LIB): $(THIRD_PARTY_OBJS)
# --- demo application ---
ifneq ($(filter demo,$(APPS)),)
DEMO_SRC := \
$(SRC_ROOT)/demo/credits.cc \
$(SRC_ROOT)/demo/demo.cc \
$(SRC_ROOT)/demo/enemy.cc \
$(SRC_ROOT)/demo/hud.cc \
$(SRC_ROOT)/demo/menu.cc \
$(SRC_ROOT)/demo/player.cc \
$(SRC_ROOT)/demo/sky_quad.cc
DEMO_EXE := $(call app_exe,demo)
DEMO_OBJS := $(call objs_from_src, $(DEMO_SRC))
EXES += $(DEMO_EXE)
OBJS += $(DEMO_OBJS)
$(DEMO_EXE): $(DEMO_OBJS) $(LIBS)
endif
# --- Build rules ---
# Dependencies.
DEPS = $(OBJS:.o=.d)
-include $(DEPS)
.PHONY: all clean cleanall help
all: $(EXES)
clean:
@echo "Cleaning..."
$(HUSH_CLEAN) $(RM) -r $(BUILD_DIR)
cleanall:
@echo "Cleaning all..."
$(HUSH_CLEAN) $(RM) -r $(INTERMEDIATE_DIR)
help:
@echo "BUILD = Build mode. One of:"
@echo " debug (no optimizations)"
@echo " release (optimizations, the default)"
@echo "APPS = Applications to build. Defaults to all."
@echo "VERBOSE = Full output from commands if set."
# It's important that libraries are specified last as Ubuntu uses "ld --as-needed" by default.
# Only the static libraries referenced by the object files will be linked into the executable.
# Beware that circular dependencies doesn't work with this flag.
$(EXES):
@mkdir -p $(@D)
$(HUSH_LINK) $(CXX) -o $@ $^ $(LDFLAGS)
$(BUILD_DIR)/%.a:
@mkdir -p $(@D)
$(HUSH_GENERATE) $(AR) $(ARFLAGS) $@ $^
$(BUILD_DIR)/%.o: $(SRC_ROOT)/%.c
@mkdir -p $(@D)
$(HUSH_COMPILE) $(CC) -c $(CFLAGS) -o $@ $<
$(BUILD_DIR)/%.o: $(SRC_ROOT)/%.cc
@mkdir -p $(@D)
$(HUSH_COMPILE) $(CXX) -c $(CXXFLAGS) -o $@ $<
$(BUILD_DIR)/%.o: $(SRC_ROOT)/%.cpp
@mkdir -p $(@D)
$(HUSH_COMPILE) $(CXX) -c $(CXXFLAGS) -o $@ $<

View File

@ -1,11 +0,0 @@
# Build a shared library for Android. Build an executable for other platforms.
template("game") {
if (target_os == "android") {
_target_type = "shared_library"
} else {
_target_type = "executable"
}
target(_target_type, target_name) {
forward_variables_from(invoker, "*")
}
}

View File

@ -1,21 +0,0 @@
#!/usr/bin/env python
import os
import shutil
import sys
src, dst = sys.argv[1:]
if os.path.exists(dst):
if os.path.isdir(dst):
shutil.rmtree(dst)
else:
os.remove(dst)
if os.path.isdir(src):
shutil.copytree(src, dst)
else:
shutil.copy2(src, dst)
# https://github.com/ninja-build/ninja/issues/1554
os.utime(dst, None)

View File

@ -1,130 +0,0 @@
toolchain("gcc") {
lib_switch = "-l"
lib_dir_switch = "-L"
tool("asm") {
depfile = "{{output}}.d"
command = "$cc -MD -MF $depfile {{defines}} {{include_dirs}} {{asmflags}} -c {{source}} -o {{output}}"
depsformat = "gcc"
outputs =
[ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ]
description = "assemble {{source}}"
}
tool("cc") {
depfile = "{{output}}.d"
command = "$cc -MMD -MF $depfile {{defines}} {{include_dirs}} {{cflags}} {{cflags_c}} -c {{source}} -o {{output}}"
depsformat = "gcc"
outputs =
[ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ]
description = "compile {{output}}"
}
tool("cxx") {
depfile = "{{output}}.d"
command = "$cxx -MMD -MF $depfile {{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}} -c {{source}} -o {{output}}"
depsformat = "gcc"
outputs =
[ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ]
description = "compile {{output}}"
}
tool("objc") {
depfile = "{{output}}.d"
command = "$cc -MMD -MF $depfile {{defines}} {{include_dirs}} {{framework_dirs}} {{cflags}} {{cflags_c}} {{cflags_objc}} -c {{source}} -o {{output}}"
depsformat = "gcc"
outputs =
[ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ]
description = "compile {{output}}"
}
tool("objcxx") {
depfile = "{{output}}.d"
command = "$cxx -MMD -MF $depfile {{defines}} {{include_dirs}} {{framework_dirs}} {{cflags}} {{cflags_cc}} {{cflags_objcc}} -c {{source}} -o {{output}}"
depsformat = "gcc"
outputs =
[ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ]
description = "compile {{output}}"
}
if (is_apple) {
not_needed([ "ar" ]) # libtool is used instead.
}
tool("alink") {
if (is_apple) {
command =
"libtool -static -o {{output}} -no_warning_for_no_symbols {{inputs}}"
} else {
command = "rm -f {{output}} && $ar rcs {{output}} {{inputs}}"
}
outputs =
[ "{{target_out_dir}}/{{target_output_name}}{{output_extension}}" ]
default_output_extension = ".a"
output_prefix = "lib"
description = "link {{target_output_name}}{{output_extension}}"
}
tool("solink") {
soname = "{{target_output_name}}{{output_extension}}"
sofile = "{{output_dir}}/$soname"
rspfile = soname + ".rsp"
if (is_apple) {
os_specific_option = "-install_name @executable_path/$sofile"
rspfile_content = "{{inputs}} {{solibs}} {{libs}}"
} else {
os_specific_option = "-Wl,-soname=$soname"
rspfile_content = "-Wl,--whole-archive {{inputs}} {{solibs}} -Wl,--no-whole-archive {{libs}}"
}
command = "$cxx -shared {{ldflags}} {{frameworks}} -o $sofile $os_specific_option @$rspfile"
default_output_extension = ".so"
default_output_dir = "{{root_out_dir}}"
outputs = [ sofile ]
link_output = sofile
depend_output = sofile
output_prefix = "lib"
description = "link $soname"
}
tool("link") {
outfile = "{{target_output_name}}{{output_extension}}"
rspfile = "$outfile.rsp"
rspfile_content = "{{inputs}}"
if (is_apple) {
command = "$cxx {{ldflags}} {{solibs}} {{libs}} {{frameworks}} -o $outfile @$rspfile"
} else {
command = "$cxx {{ldflags}} -o $outfile -Wl,--start-group @$rspfile {{solibs}} -Wl,--end-group {{libs}}"
}
default_output_dir = "{{root_out_dir}}"
outputs = [ outfile ]
description = "link $outfile"
}
if (host_os == "win") {
tool("stamp") {
command = "cmd.exe /c echo > {{output}}"
description = "stamp {{output}}"
}
tool("copy") {
# Note: The build in copy command can't handle forward slashes as path separators.
# Use a python script as a work around.
# command = "cmd.exe /c copy /Y {{source}} {{output}}"
copy_cmd = rebase_path("../copy.py")
command = "python \"$copy_cmd\" {{source}} {{output}}"
description = "copy {{source}} {{output}}"
}
} else {
tool("stamp") {
command = "touch {{output}}"
description = "stamp {{output}}"
}
tool("copy") {
command = "cp -af {{source}} {{output}}"
description = "copy {{source}} {{output}}"
}
}
}

View File

@ -1,90 +0,0 @@
toolchain("msvc") {
lib_switch = ""
lib_dir_switch = "/LIBPATH:"
tool("asm") {
command = "ml.exe {{asmflags}} /nologo /c /Fo {{output}} {{source}}"
outputs =
[ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.obj" ]
description = "assemble {{source}}"
}
tool("cc") {
pdbname = "{{target_out_dir}}/{{label_name}}_c.pdb"
command = "cl.exe /nologo /showIncludes /FC {{defines}} {{include_dirs}} {{cflags}} {{cflags_c}} /c {{source}} /Fo{{output}} /Fd\"$pdbname\""
depsformat = "msvc"
outputs =
[ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.obj" ]
description = "compile {{source}}"
}
tool("cxx") {
pdbname = "{{target_out_dir}}/{{label_name}}_c.pdb"
command = "cl.exe /nologo /showIncludes /FC {{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}} /c {{source}} /Fo{{output}} /Fd\"$pdbname\""
depsformat = "msvc"
outputs =
[ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.obj" ]
description = "compile {{source}}"
}
tool("alink") {
rspfile = "{{output}}.rsp"
command = "lib.exe /nologo {{arflags}} /OUT:{{output}} @$rspfile"
outputs = [ "{{root_out_dir}}/{{target_output_name}}{{output_extension}}" ]
default_output_extension = ".lib"
default_output_dir = "{{target_out_dir}}"
rspfile_content = "{{inputs_newline}}"
description = "link {{output}}"
}
tool("solink") {
dllname = "{{output_dir}}/{{target_output_name}}{{output_extension}}"
libname = "${dllname}.lib"
pdbname = "${dllname}.pdb"
rspfile = "${dllname}.rsp"
command = "link.exe /nologo /IMPLIB:$libname /DLL /OUT:$dllname /PDB:$pdbname @$rspfile"
outputs = [
dllname,
libname,
pdbname,
]
default_output_extension = ".dll"
default_output_dir = "{{root_out_dir}}"
link_output = libname
depend_output = libname
runtime_outputs = [
dllname,
pdbname,
]
rspfile_content = "{{inputs_newline}} {{libs}} {{solibs}} {{ldflags}}"
description = "link {{output}}"
}
tool("link") {
exename = "{{root_out_dir}}/{{target_output_name}}{{output_extension}}"
pdbname = "$exename.pdb"
rspfile = "$exename.rsp"
command = "link.exe /nologo /OUT:$exename /PDB:$pdbname @$rspfile"
default_output_extension = ".exe"
default_output_dir = "{{root_out_dir}}"
outputs = [ exename ]
rspfile_content = "{{inputs_newline}} {{libs}} {{solibs}} {{ldflags}}"
description = "link {{output}}"
}
tool("stamp") {
command = "cmd.exe /c echo > {{output}}"
description = "stamp {{output}}"
}
tool("copy") {
# Note: The build in copy command can't handle forward slashes as path separators.
# Use a python script as a work around.
# command = "cmd.exe /c copy /Y {{source}} {{output}}"
copy_cmd = rebase_path("../copy.py")
command = "python \"$copy_cmd\" {{source}} {{output}}"
description = "copy {{source}} {{output}}"
}
}

View File

@ -1,24 +0,0 @@
source_set("base") {
sources = [
"closure.h",
"collusion_test.cc",
"collusion_test.h",
"file.h",
"hash.h",
"interpolation.h",
"log.cc",
"log.h",
"mem.cc",
"mem.h",
"misc.h",
"random.h",
"task_runner.cc",
"task_runner.h",
"thread_pool.cc",
"thread_pool.h",
"timer.h",
"vecmath.h",
]
deps = []
}

View File

@ -10,13 +10,14 @@
#define HERE std::make_tuple(__func__, __FILE__, __LINE__) #define HERE std::make_tuple(__func__, __FILE__, __LINE__)
// Helper for logging location info, e.g. LOG(0) << LOCATION(from) // Helper for logging location info, e.g. LOG << LOCATION(from)
#define LOCATION(from) \ #define LOCATION(from) \
std::get<0>(from) << "() [" << [](std::string path) -> std::string { \ std::get<0>(from) << "() [" << [](const char* path) -> std::string { \
size_t last_slash_pos = path.find_last_of("\\/"); \ std::string file_name(path); \
size_t last_slash_pos = file_name.find_last_of("\\/"); \
if (last_slash_pos != std::string::npos) \ if (last_slash_pos != std::string::npos) \
path = path.substr(last_slash_pos + 1); \ file_name = file_name.substr(last_slash_pos + 1); \
return path; \ return file_name; \
}(std::get<1>(from)) << ":" \ }(std::get<1>(from)) << ":" \
<< std::get<2>(from) << "]" << std::get<2>(from) << "]"
@ -35,7 +36,7 @@ using Closure = std::function<void()>;
// Provides location info (function name, file name and line number) where of a // Provides location info (function name, file name and line number) where of a
// Closure was constructed. // Closure was constructed.
using Location = std::tuple<std::string, std::string, int>; using Location = std::tuple<const char*, const char*, int>;
#else #else

View File

@ -6,15 +6,17 @@
namespace base { namespace base {
// Compile-time string hashing function.
template <size_t N> template <size_t N>
constexpr inline size_t KR2Hash(const char (&str)[N], size_t Len = N - 1) { constexpr inline size_t Hash(const char (&str)[N], size_t Len = N - 1) {
size_t hash_value = 0; size_t hash_value = 0;
for (int i = 0; str[i] != '\0'; ++i) for (int i = 0; str[i] != '\0'; ++i)
hash_value = str[i] + 31 * hash_value; hash_value = str[i] + 31 * hash_value;
return hash_value; return hash_value;
} }
inline size_t KR2Hash(const std::string& str) { // The same hashing function for run-time.
inline size_t Hash(const std::string& str) {
size_t hash_value = 0; size_t hash_value = 0;
for (std::string::value_type c : str) for (std::string::value_type c : str)
hash_value = c + 31 * hash_value; hash_value = c + 31 * hash_value;

View File

@ -2,74 +2,52 @@
#if defined(__ANDROID__) #if defined(__ANDROID__)
#include <android/log.h> #include <android/log.h>
#elif defined(_WIN32)
#include <windows.h>
#include <format>
#else #else
#include <cstdio> #include <cstdio>
#endif #endif
#include <cstdlib> #include <cstdlib>
#include <string>
namespace base { namespace base {
namespace {
int g_max_log_verbosity_level = 0;
} // namespace
// This is never instantiated, it's just used for EAT_STREAM_PARAMETERS to have // This is never instantiated, it's just used for EAT_STREAM_PARAMETERS to have
// an object of the correct type on the LHS of the unused part of the ternary // an object of the correct type on the LHS of the unused part of the ternary
// operator. // operator.
std::ostream* LogMessage::swallow_stream; std::ostream* LogMessage::swallow_stream;
int GlobalMaxLogVerbosityLevel() { LogMessage::LogMessage(const char* file, int line) : file_(file), line_(line) {}
return g_max_log_verbosity_level;
}
LogMessage::LogMessage(const char* file, int line, int verbosity_level)
: file_(file), line_(line), verbosity_level_(verbosity_level) {}
LogMessage::~LogMessage() { LogMessage::~LogMessage() {
stream_ << std::endl; stream_ << std::endl;
std::string message(stream_.str()); std::string text(stream_.str());
std::string filename(file_); std::string filename(file_);
size_t last_slash_pos = filename.find_last_of("\\/"); size_t last_slash_pos = filename.find_last_of("\\/");
if (last_slash_pos != std::string::npos) if (last_slash_pos != std::string::npos)
filename = filename.substr(last_slash_pos + 1); filename = filename.substr(last_slash_pos + 1);
#if defined(__ANDROID__) #if defined(__ANDROID__)
__android_log_print(ANDROID_LOG_ERROR, "kaliber", "%d [%s:%d] %s", __android_log_print(ANDROID_LOG_ERROR, "kaliber", "[%s:%d] %s",
verbosity_level_, filename.c_str(), line_, filename.c_str(), line_, text.c_str());
message.c_str());
#elif defined(_WIN32)
OutputDebugStringA(
std::format("{} [{}:{}] {}", verbosity_level_, filename, line_, message)
.c_str());
#else #else
printf("%d [%s:%d] %s", verbosity_level_, filename.c_str(), line_, printf("[%s:%d] %s", filename.c_str(), line_, text.c_str());
message.c_str());
#endif #endif
} }
// static
LogAbort LogAbort::Check(const char* file, int line, const char* expr) { LogAbort LogAbort::Check(const char* file, int line, const char* expr) {
LogAbort instance(new LogMessage(file, line, 0)); LogAbort instance(new LogMessage(file, line));
instance.GetLog().stream() << "CHECK(" << expr << ") "; instance.GetLog().stream() << "CHECK: "
<< "(" << expr << ") ";
return instance; return instance;
} }
// static
LogAbort LogAbort::DCheck(const char* file, int line, const char* expr) { LogAbort LogAbort::DCheck(const char* file, int line, const char* expr) {
LogAbort instance(new LogMessage(file, line, 0)); LogAbort instance(new LogMessage(file, line));
instance.GetLog().stream() << "DCHECK(" << expr << ") "; instance.GetLog().stream() << "DCHECK: "
<< "(" << expr << ") ";
return instance; return instance;
} }
// static
LogAbort LogAbort::NotReached(const char* file, int line) { LogAbort LogAbort::NotReached(const char* file, int line) {
LogAbort instance(new LogMessage(file, line, 0)); LogAbort instance(new LogMessage(file, line));
instance.GetLog().stream() << "NOTREACHED() "; instance.GetLog().stream() << "NOTREACHED ";
return instance; return instance;
} }

View File

@ -11,66 +11,45 @@
// CHECK(condition) terminates the process if the condition is false. // CHECK(condition) terminates the process if the condition is false.
// NOTREACHED annotates unreachable codepaths and terminates the process if // NOTREACHED annotates unreachable codepaths and terminates the process if
// reached. // reached.
#define LOG(verbosity_level) \ #define LOG base::LogMessage(__FILE__, __LINE__).stream()
LAZY_STREAM( \ #define LOG_IF(condition) \
LOG_IS_ON(verbosity_level), \ LAZY_STREAM(condition, base::LogMessage(__FILE__, __LINE__).stream())
::base::LogMessage(__FILE__, __LINE__, verbosity_level).stream()) #define CHECK(condition) \
#define LOG_IF(verbosity_level, condition) \ LAZY_STREAM( \
LAZY_STREAM( \ !(condition), \
LOG_IS_ON(verbosity_level) && (condition), \ base::LogAbort::Check(__FILE__, __LINE__, #condition).GetLog().stream())
::base::LogMessage(__FILE__, __LINE__, verbosity_level).stream())
#define CHECK(condition) \
LAZY_STREAM(!(condition), \
::base::LogAbort::Check(__FILE__, __LINE__, #condition) \
.GetLog() \
.stream())
#define NOTREACHED() \ #define NOTREACHED \
::base::LogAbort::NotReached(__FILE__, __LINE__).GetLog().stream() base::LogAbort::NotReached(__FILE__, __LINE__).GetLog().stream()
// Macros for logging which are active only in debug builds. // Macros for logging which are active only in debug builds.
#ifdef _DEBUG #ifdef _DEBUG
#define DLOG(verbosity_level) \ #define DLOG base::LogMessage(__FILE__, __LINE__).stream()
LAZY_STREAM( \ #define DLOG_IF(condition) \
LOG_IS_ON(verbosity_level), \ LAZY_STREAM(condition, base::LogMessage(__FILE__, __LINE__).stream())
::base::LogMessage(__FILE__, __LINE__, verbosity_level).stream()) #define DCHECK(condition) \
#define DLOG_IF(verbosity_level, condition) \ LAZY_STREAM(!(condition), \
LAZY_STREAM( \ base::LogAbort::DCheck(__FILE__, __LINE__, #condition) \
LOG_IS_ON(verbosity_level) && (condition), \ .GetLog() \
::base::LogMessage(__FILE__, __LINE__, verbosity_level).stream())
#define DCHECK(condition) \
LAZY_STREAM(!(condition), \
::base::LogAbort::DCheck(__FILE__, __LINE__, #condition) \
.GetLog() \
.stream()) .stream())
#else #else
// "debug mode" logging is compiled away to nothing for release builds. // "debug mode" logging is compiled away to nothing for release builds.
#define DLOG(verbosity_level) EAT_STREAM_PARAMETERS #define DLOG EAT_STREAM_PARAMETERS
#define DLOG_IF(verbosity_level, condition) EAT_STREAM_PARAMETERS #define DLOG_IF(condition) EAT_STREAM_PARAMETERS
#define DCHECK(condition) EAT_STREAM_PARAMETERS #define DCHECK(condition) EAT_STREAM_PARAMETERS
#endif #endif
// Helper macro which avoids evaluating the arguments to a stream if // Helper macro which avoids evaluating the arguments to a stream if
// the condition doesn't hold. // the condition doesn't hold.
#define LAZY_STREAM(condition, stream) \ #define LAZY_STREAM(condition, stream) \
!(condition) ? (void)0 : ::base::LogMessage::Voidify() & (stream) !(condition) ? (void)0 : base::LogMessage::Voidify() & (stream)
// Avoid any pointless instructions to be emitted by the compiler. // Avoid any pointless instructions to be emitted by the compiler.
#define EAT_STREAM_PARAMETERS \ #define EAT_STREAM_PARAMETERS \
LAZY_STREAM(false, *::base::LogMessage::swallow_stream) LAZY_STREAM(false, *base::LogMessage::swallow_stream)
#if defined(MAX_LOG_VERBOSITY_LEVEL)
#define LOG_IS_ON(verbosity_level) \
((verbosity_level) <= MAX_LOG_VERBOSITY_LEVEL)
#else
#define LOG_IS_ON(verbosity_level) \
((verbosity_level) <= ::base::GlobalMaxLogVerbosityLevel())
#endif
namespace base { namespace base {
int GlobalMaxLogVerbosityLevel();
class LogMessage { class LogMessage {
public: public:
class Voidify { class Voidify {
@ -82,17 +61,18 @@ class LogMessage {
void operator&(std::ostream&) {} void operator&(std::ostream&) {}
}; };
LogMessage(const char* file, int line, int verbosity_level); LogMessage(const char* file, int line);
~LogMessage(); ~LogMessage();
LogMessage& base() { return *this; }
std::ostream& stream() { return stream_; } std::ostream& stream() { return stream_; }
static std::ostream* swallow_stream; static std::ostream* swallow_stream;
protected: protected:
const char* file_; const char* file_;
int line_; const int line_;
int verbosity_level_;
std::ostringstream stream_; std::ostringstream stream_;
}; };

View File

@ -1,46 +0,0 @@
#include "base/mem.h"
#include <cstring>
#if defined(__ANDROID__)
#include <malloc.h>
#endif
namespace base {
void* AlignedAlloc(size_t size, size_t alignment) {
DCHECK(size > 0U);
DCHECK(IsPow2(alignment));
DCHECK((alignment % sizeof(void*)) == 0U);
void* ptr = nullptr;
#if defined(_WIN32)
ptr = _aligned_malloc(size, alignment);
#elif defined(__ANDROID__)
ptr = memalign(alignment, size);
#else
int ret = posix_memalign(&ptr, alignment, size);
if (ret != 0) {
DLOG(0) << "posix_memalign() returned with error " << ret;
ptr = nullptr;
}
#endif
// Aligned allocations may fail for non-memory related reasons.
CHECK(ptr) << "Aligned allocation failed. "
<< "size=" << size << ", alignment=" << alignment;
DCHECK(IsAligned(ptr, alignment));
return ptr;
}
void* AlignedRealloc(void* ptr,
size_t old_size,
size_t new_size,
size_t alignment) {
auto* new_ptr = AlignedAlloc(new_size, alignment);
memmove(new_ptr, ptr, old_size);
AlignedFree(ptr);
return new_ptr;
}
} // namespace base

View File

@ -1,33 +1,26 @@
#ifndef BASE_MEM_H #ifndef BASE_MEM_H
#define BASE_MEM_H #define BASE_MEM_H
#include <stddef.h> #include <cstdlib>
#include <stdint.h>
#include <memory> #include <memory>
#if defined(_WIN32) #if defined(__ANDROID__)
#include <malloc.h> #include <malloc.h>
#else
#include <stdlib.h>
#endif #endif
#include "base/log.h" #include "base/log.h"
#include "base/misc.h"
#define ALIGN_MEM(alignment) __attribute__((aligned(alignment)))
namespace base { namespace base {
inline void AlignedFree(void* mem) {
#if defined(_WIN32)
_aligned_free(mem);
#else
free(mem);
#endif
}
namespace internal { namespace internal {
struct ScopedAlignedFree { struct ScopedAlignedFree {
inline void operator()(void* x) const { AlignedFree(x); } inline void operator()(void* x) const {
if (x)
free(x);
}
}; };
} // namespace internal } // namespace internal
@ -35,16 +28,27 @@ struct ScopedAlignedFree {
template <typename T> template <typename T>
using AlignedMemPtr = std::unique_ptr<T, internal::ScopedAlignedFree>; using AlignedMemPtr = std::unique_ptr<T, internal::ScopedAlignedFree>;
void* AlignedAlloc(size_t size, size_t alignment); template <int kAlignment>
inline void* AlignedAlloc(size_t size) {
void* ptr = NULL;
#if defined(__ANDROID__)
ptr = memalign(kAlignment, size);
#else
if (posix_memalign(&ptr, kAlignment, size))
ptr = NULL;
#endif
DCHECK(ptr);
// DCHECK(((unsigned)ptr & (kAlignment - 1)) == 0);
return ptr;
}
void* AlignedRealloc(void* ptr, inline void AlignedFree(void* mem) {
size_t old_size, free(mem);
size_t new_size, }
size_t alignment);
inline bool IsAligned(const void* val, size_t alignment) { template <int kAlignment>
DCHECK(IsPow2(alignment)) << alignment << " is not a power of 2"; inline bool IsAligned(void* ptr) {
return (reinterpret_cast<uintptr_t>(val) & (alignment - 1)) == 0; return (reinterpret_cast<uintptr_t>(ptr) & (kAlignment - 1)) == 0U;
} }
} // namespace base } // namespace base

View File

@ -1,42 +1,31 @@
#ifndef BASE_MISC_H #ifndef BASE_MISC_H
#define BASE_MISC_H #define BASE_MISC_H
#include <cstdint>
#define CRASH *((int*)nullptr) = 0; #define CRASH *((int*)nullptr) = 0;
namespace base { namespace base {
inline uint32_t GetHighestBitPos(uint32_t value) { // ToDo: x86 has the bsr instruction.
uint32_t result = 0; inline int GetHighestBitPos(int value) {
if (0xFFFF0000 & value) { return (0xFFFF0000 & value ? value &= 0xFFFF0000, 1 : 0) * 0x10 +
value &= 0xFFFF0000; (0xFF00FF00 & value ? value &= 0xFF00FF00, 1 : 0) * 0x08 +
result += 0x10; (0xF0F0F0F0 & value ? value &= 0xF0F0F0F0, 1 : 0) * 0x04 +
} (0xCCCCCCCC & value ? value &= 0xCCCCCCCC, 1 : 0) * 0x02 +
if (0xFF00FF00 & value) { (0xAAAAAAAA & value ? 1 : 0) * 0x01;
value &= 0xFF00FF00;
result += 0x8;
}
if (0xF0F0F0F0 & value) {
value &= 0xF0F0F0F0;
result += 0x04;
}
if (0xCCCCCCCC & value) {
value &= 0xCCCCCCCC;
result += 0x02;
}
if (0xAAAAAAAA & value) {
result += 0x01;
}
return result;
} }
// Get the highest set bit in an integer number
inline int GetHighestBit(int value) {
return 0x1 << GetHighestBitPos(value);
}
// Check if the given integer is a power of two, ie if only one bit is set.
inline bool IsPow2(int value) { inline bool IsPow2(int value) {
return ((value & (value - 1)) == 0); return GetHighestBit(value) == value;
} }
inline uint32_t RoundUpToPow2(uint32_t val) { inline int RoundUpToPow2(int val) {
uint32_t i = 1 << GetHighestBitPos(val); int i = GetHighestBit(val);
return val == i ? val : i << 1; return val == i ? val : i << 1;
} }

View File

@ -4,75 +4,55 @@
#include "base/log.h" #include "base/log.h"
namespace base {
namespace { namespace {
void PostTaskAndReplyRelay(Location from, void PostTaskAndReplyRelay(base::Location from,
Closure task_cb, base::Closure task_cb,
Closure reply_cb, base::Closure reply_cb,
std::shared_ptr<TaskRunner> destination, base::TaskRunner* destination) {
bool front) {
task_cb(); task_cb();
if (reply_cb) if (reply_cb)
destination->PostTask(from, std::move(reply_cb), front); destination->PostTask(from, std::move(reply_cb));
} }
} // namespace } // namespace
// The task runner that belongs to the thread it's created in. Tasks to be run namespace base {
// on a specific thread can be posted to this task runner.
// TaskRunner::GetThreadLocalTaskRunner()->RunTasks() is expected to be thread_local std::unique_ptr<TaskRunner> TaskRunner::thread_local_task_runner;
// periodically called.
thread_local std::shared_ptr<TaskRunner> TaskRunner::thread_local_task_runner;
void TaskRunner::CreateThreadLocalTaskRunner() { void TaskRunner::CreateThreadLocalTaskRunner() {
DCHECK(!thread_local_task_runner); DCHECK(!thread_local_task_runner);
thread_local_task_runner = std::make_shared<TaskRunner>(); thread_local_task_runner = std::make_unique<TaskRunner>();
} }
std::shared_ptr<TaskRunner> TaskRunner::GetThreadLocalTaskRunner() { TaskRunner* TaskRunner::GetThreadLocalTaskRunner() {
return thread_local_task_runner; return thread_local_task_runner.get();
} }
void TaskRunner::PostTask(Location from, Closure task, bool front) { void TaskRunner::PostTask(const Location& from, Closure task) {
DCHECK(task) << LOCATION(from); DCHECK(task) << LOCATION(from);
task_count_.fetch_add(1, std::memory_order_relaxed); task_count_.fetch_add(1, std::memory_order_relaxed);
std::lock_guard<std::mutex> scoped_lock(lock_); std::lock_guard<std::mutex> scoped_lock(lock_);
if (front) queue_.emplace_back(from, std::move(task));
queue_.emplace_front(from, std::move(task));
else
queue_.emplace_back(from, std::move(task));
} }
void TaskRunner::PostTaskAndReply(Location from, void TaskRunner::PostTaskAndReply(const Location& from,
Closure task, Closure task,
Closure reply, Closure reply) {
bool front) {
DCHECK(task) << LOCATION(from); DCHECK(task) << LOCATION(from);
DCHECK(reply) << LOCATION(from); DCHECK(reply) << LOCATION(from);
DCHECK(thread_local_task_runner) << LOCATION(from); DCHECK(thread_local_task_runner) << LOCATION(from);
auto relay = std::bind(PostTaskAndReplyRelay, from, std::move(task), auto relay = std::bind(::PostTaskAndReplyRelay, from, std::move(task),
std::move(reply), thread_local_task_runner, front); std::move(reply), thread_local_task_runner.get());
PostTask(from, std::move(relay), front); PostTask(from, std::move(relay));
} }
void TaskRunner::CancelTasks() { void TaskRunner::MultiConsumerRun() {
std::lock_guard<std::mutex> scoped_lock(lock_);
task_count_.fetch_sub(queue_.size(), std::memory_order_release);
queue_.clear();
}
void TaskRunner::WaitForCompletion() {
while (task_count_.load(std::memory_order_acquire) > 0)
std::this_thread::yield();
}
void TaskRunner::RunTasks() {
for (;;) { for (;;) {
Task task; Task task;
{ {
@ -86,12 +66,60 @@ void TaskRunner::RunTasks() {
auto [from, task_cb] = task; auto [from, task_cb] = task;
#if 0 #if 0
LOG(0) << __func__ << " from: " << LOCATION(from); LOG << __func__ << " from: " << LOCATION(from);
#endif #endif
task_cb(); task_cb();
task_count_.fetch_sub(1, std::memory_order_release); task_count_.fetch_sub(1, std::memory_order_release);
if (cancel_tasks_.load(std::memory_order_relaxed)) {
CancelTasksInternal();
break;
}
} }
} }
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();
task_count_.fetch_sub(1, std::memory_order_release);
if (cancel_tasks_.load(std::memory_order_relaxed)) {
CancelTasksInternal();
break;
}
}
}
void TaskRunner::CancelTasks() {
cancel_tasks_.store(true, std::memory_order_relaxed);
}
void TaskRunner::WaitForCompletion() {
while (task_count_.load(std::memory_order_acquire) > 0)
std::this_thread::yield();
}
void TaskRunner::CancelTasksInternal() {
cancel_tasks_.store(false, std::memory_order_relaxed);
task_count_.store(0, std::memory_order_relaxed);
std::lock_guard<std::mutex> scoped_lock(lock_);
queue_.clear();
}
} // namespace base } // namespace base

View File

@ -18,66 +18,55 @@ namespace internal {
// one that returns via an output parameter. // one that returns via an output parameter.
template <typename ReturnType> template <typename ReturnType>
void ReturnAsParamAdapter(std::function<ReturnType()> func, void ReturnAsParamAdapter(std::function<ReturnType()> func,
std::shared_ptr<ReturnType> result) { ReturnType* result) {
*result = func(); *result = func();
} }
// Adapts a ReturnType* result to a callback that expects a ReturnType. // Adapts a ReturnType* result to a callblack that expects a ReturnType.
template <typename ReturnType> template <typename ReturnType>
void ReplyAdapter(std::function<void(ReturnType)> callback, void ReplyAdapter(std::function<void(ReturnType)> callback,
std::shared_ptr<ReturnType> result) { ReturnType* result) {
callback(std::move(*result)); callback(*result);
delete result;
} }
} // namespace internal } // namespace internal
// Runs queued tasks (in the form of Closure objects). All methods are // Runs queued tasks (in the form of Closure objects). All methods are
// thread-safe and can be called on any thread. // thread-safe and can be called on any thread.
// Tasks run in FIFO order when consumed by a single thread. When consumed // Tasks run in FIFO order. When consumed concurrently by multiple threads, it
// concurrently by multiple threads, it doesn't guarantee whether tasks overlap, // doesn't guarantee whether tasks overlap, or whether they run on a particular
// or whether they run on a particular thread. // thread.
class TaskRunner { class TaskRunner {
public: public:
TaskRunner() = default; TaskRunner() = default;
~TaskRunner() = default; ~TaskRunner() = default;
static void CreateThreadLocalTaskRunner(); void PostTask(const Location& from, Closure task);
static std::shared_ptr<TaskRunner> GetThreadLocalTaskRunner();
void PostTask(Location from, Closure task, bool front = false); void PostTaskAndReply(const Location& from, Closure task, Closure reply);
void PostTaskAndReply(Location from,
Closure task,
Closure reply,
bool front = false);
template <typename ReturnType> template <typename ReturnType>
void PostTaskAndReplyWithResult(Location from, void PostTaskAndReplyWithResult(const Location& from,
std::function<ReturnType()> task, std::function<ReturnType()> task,
std::function<void(ReturnType)> reply, std::function<void(ReturnType)> reply) {
bool front = false) { auto* result = new ReturnType;
auto result = std::make_shared<ReturnType>();
return PostTaskAndReply( return PostTaskAndReply(
from, from,
std::bind(internal::ReturnAsParamAdapter<ReturnType>, std::move(task), std::bind(internal::ReturnAsParamAdapter<ReturnType>, std::move(task),
result), result),
std::bind(internal::ReplyAdapter<ReturnType>, std::move(reply), result), std::bind(internal::ReplyAdapter<ReturnType>, std::move(reply),
front); result));
} }
// Posts a task that deletes the given object. void MultiConsumerRun();
template <class T> void SingleConsumerRun();
void Delete(Location from, std::unique_ptr<T> object) {
// std::function target must be copy-constructible
std::shared_ptr<T> owned = std::move(object);
PostTask(HERE, [owned]() {});
}
void CancelTasks(); void CancelTasks();
void WaitForCompletion(); void WaitForCompletion();
void RunTasks(); static void CreateThreadLocalTaskRunner();
static TaskRunner* GetThreadLocalTaskRunner();
private: private:
using Task = std::tuple<Location, Closure>; using Task = std::tuple<Location, Closure>;
@ -85,8 +74,11 @@ class TaskRunner {
std::deque<Task> queue_; std::deque<Task> queue_;
mutable std::mutex lock_; mutable std::mutex lock_;
std::atomic<size_t> task_count_{0}; std::atomic<size_t> task_count_{0};
std::atomic<bool> cancel_tasks_{false};
static thread_local std::shared_ptr<TaskRunner> thread_local_task_runner; static thread_local std::unique_ptr<TaskRunner> thread_local_task_runner;
void CancelTasksInternal();
TaskRunner(TaskRunner const&) = delete; TaskRunner(TaskRunner const&) = delete;
TaskRunner& operator=(TaskRunner const&) = delete; TaskRunner& operator=(TaskRunner const&) = delete;

View File

@ -40,30 +40,30 @@ void ThreadPool::Shutdown() {
threads_.clear(); threads_.clear();
} }
void ThreadPool::PostTask(Location from, Closure task, bool front) { void ThreadPool::PostTask(const Location& from, Closure task) {
task_runner_.PostTask(from, std::move(task), front); DCHECK((!threads_.empty()));
task_runner_.PostTask(from, std::move(task));
semaphore_.release(); semaphore_.release();
} }
void ThreadPool::PostTaskAndReply(Location from, void ThreadPool::PostTaskAndReply(const Location& from,
Closure task, Closure task,
Closure reply, Closure reply) {
bool front) { DCHECK((!threads_.empty()));
task_runner_.PostTaskAndReply(from, std::move(task), std::move(reply), front);
semaphore_.release();
}
void ThreadPool::CancelTasks() { task_runner_.PostTaskAndReply(from, std::move(task), std::move(reply));
task_runner_.CancelTasks(); semaphore_.release();
} }
void ThreadPool::WorkerMain() { void ThreadPool::WorkerMain() {
for (;;) { for (;;) {
semaphore_.acquire(); semaphore_.acquire();
if (quit_.load(std::memory_order_relaxed)) if (quit_.load(std::memory_order_relaxed))
return; return;
task_runner_.RunTasks(); task_runner_.MultiConsumerRun();
} }
} }

View File

@ -11,6 +11,8 @@
namespace base { namespace base {
class TaskRunner;
// Feed the ThreadPool tasks (in the form of Closure objects) and they will be // Feed the ThreadPool tasks (in the form of Closure objects) and they will be
// called on any thread from the pool. // called on any thread from the pool.
class ThreadPool { class ThreadPool {
@ -24,25 +26,19 @@ class ThreadPool {
void Shutdown(); void Shutdown();
void PostTask(Location from, Closure task, bool front = false); void PostTask(const Location& from, Closure task);
void PostTaskAndReply(Location from, void PostTaskAndReply(const Location& from, Closure task, Closure reply);
Closure task,
Closure reply,
bool front = false);
template <typename ReturnType> template <typename ReturnType>
void PostTaskAndReplyWithResult(Location from, void PostTaskAndReplyWithResult(const Location& from,
std::function<ReturnType()> task, std::function<ReturnType()> task,
std::function<void(ReturnType)> reply, std::function<void(ReturnType)> reply) {
bool front = false) {
task_runner_.PostTaskAndReplyWithResult(from, std::move(task), task_runner_.PostTaskAndReplyWithResult(from, std::move(task),
std::move(reply), front); std::move(reply));
semaphore_.release(); semaphore_.release();
} }
void CancelTasks();
private: private:
std::vector<std::thread> threads_; std::vector<std::thread> threads_;

46
src/base/timer.cc Normal file
View File

@ -0,0 +1,46 @@
#include "base/timer.h"
#include <thread>
namespace base {
Timer::Timer() {
Reset();
}
void Timer::Reset() {
gettimeofday(&last_time_, nullptr);
seconds_passed_ = 0.0f;
seconds_accumulated_ = 0.0f;
}
void Timer::Update() {
timeval currentTime;
gettimeofday(&currentTime, nullptr);
seconds_passed_ =
(float)(currentTime.tv_sec - last_time_.tv_sec) +
0.000001f * (float)(currentTime.tv_usec - last_time_.tv_usec);
last_time_ = currentTime;
seconds_accumulated_ += seconds_passed_;
}
void Timer::Sleep(float duration) {
Timer timer;
float accumulator = 0.0;
constexpr float epsilon = 0.0001f;
while (accumulator < duration) {
timer.Update();
accumulator += timer.GetSecondsPassed();
if (duration - accumulator > epsilon) {
float sleep_time = duration - accumulator - epsilon;
std::this_thread::sleep_for(
std::chrono::microseconds((int)(sleep_time * 1000000.0f)));
}
};
}
} // namespace base

View File

@ -1,47 +1,31 @@
#ifndef BASE_TIMER_H #ifndef BASE_TIMER_H
#define BASE_TIMER_H #define BASE_TIMER_H
#include <chrono> #include <sys/time.h>
#include <thread>
namespace base { namespace base {
class ElapsedTimer { class Timer {
public: public:
ElapsedTimer() { time_ = std::chrono::high_resolution_clock::now(); } Timer();
~Timer() = default;
// Return seconds passed since creating the object. void Reset();
double Elapsed() const {
auto current_time = std::chrono::high_resolution_clock::now(); void Update();
std::chrono::duration<double> diff = current_time - time_;
return diff.count(); static void Sleep(float duration);
}
float GetSecondsPassed() const { return seconds_passed_; }
float GetSecondsAccumulated() const { return seconds_accumulated_; }
private: private:
std::chrono::time_point<std::chrono::high_resolution_clock> time_; float seconds_passed_ = 0.0f;
float seconds_accumulated_ = 0.0f;
timeval last_time_;
}; };
class DeltaTimer {
public:
DeltaTimer() { time_ = std::chrono::high_resolution_clock::now(); }
// Return seconds passed since the last call to this function.
double Delta() {
auto current_time = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> diff = current_time - time_;
time_ = current_time;
return diff.count();
}
private:
std::chrono::time_point<std::chrono::high_resolution_clock> time_;
};
inline void Sleep(double seconds) {
std::this_thread::sleep_for(
std::chrono::duration<double, std::milli>(seconds * 1000));
}
} // namespace base } // namespace base
#endif // BASE_TIMER_H #endif // BASE_TIMER_H

View File

@ -1247,8 +1247,8 @@ class Matrix4 {
T fov_aspect, T fov_aspect,
T width, T width,
T height, T height,
T near_plane, T near,
T far_plane) { T far) {
// Calc x and y scale from FOV. // Calc x and y scale from FOV.
T scale = T scale =
T(2.0) / T(2.0) /
@ -1257,8 +1257,8 @@ class Matrix4 {
T x_scale = y_scale / (width / height); T x_scale = y_scale / (width / height);
_M_SET_ROW(0, x_scale / T(2.0), 0, 0, 0); _M_SET_ROW(0, x_scale / T(2.0), 0, 0, 0);
_M_SET_ROW(1, 0, (-y_scale) / T(2.0), 0, 0); _M_SET_ROW(1, 0, (-y_scale) / T(2.0), 0, 0);
_M_SET_ROW(2, 0, 0, far_plane / (far_plane - near_plane), 1); _M_SET_ROW(2, 0, 0, far / (far - near), 1);
_M_SET_ROW(3, 0, 0, -near_plane * far_plane / (far_plane - near_plane), 0); _M_SET_ROW(3, 0, 0, -near * far / (far - near), 0);
} }
void CreateTranslation(const Vector3<T>& t) { void CreateTranslation(const Vector3<T>& t) {
@ -1343,7 +1343,7 @@ class Matrix4 {
M_x_RotZ(angles[2]); M_x_RotZ(angles[2]);
break; break;
default: default:
NOTREACHED(); NOTREACHED;
} }
} }
@ -1534,7 +1534,7 @@ class Matrix4 {
break; break;
} }
default: default:
NOTREACHED(); NOTREACHED;
} }
return -angles; return -angles;
} }

View File

@ -1,26 +0,0 @@
import("//build/rules.gni")
game("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 = [
"//assets/demo",
"//src/base",
"//src/engine",
]
}

View File

@ -3,9 +3,9 @@
#include "base/log.h" #include "base/log.h"
#include "base/vecmath.h" #include "base/vecmath.h"
#include "demo/demo.h" #include "demo/demo.h"
#include "engine/asset/font.h"
#include "engine/asset/image.h"
#include "engine/engine.h" #include "engine/engine.h"
#include "engine/font.h"
#include "engine/image.h"
#include "engine/input_event.h" #include "engine/input_event.h"
using namespace base; using namespace base;

View File

@ -13,8 +13,11 @@
#include "engine/engine.h" #include "engine/engine.h"
#include "engine/game_factory.h" #include "engine/game_factory.h"
#include "engine/input_event.h" #include "engine/input_event.h"
#include "engine/sound.h"
GAME_FACTORIES{GAME_CLASS(Demo)}; DECLARE_GAME_BEGIN
DECLARE_GAME(Demo)
DECLARE_GAME_END
// #define RECORD 15 // #define RECORD 15
// #define REPLAY // #define REPLAY
@ -44,73 +47,59 @@ Demo::~Demo() {
saved_data_.Save(); saved_data_.Save();
} }
bool Demo::PreInitialize() {
if (!font_.Load("demo/PixelCaps!.ttf"))
return false;
Engine::Get().SetShaderSource("sky_without_nebula",
"demo/sky_without_nebula.glsl");
Engine::Get().SetShaderSource("sky", "demo/sky.glsl");
Engine::Get().AsyncLoadSound("music", "demo/Game_2_Main.mp3", true);
Engine::Get().AsyncLoadSound("boss_music", "demo/Game_2_Boss.mp3", true);
if (!enemy_.PreInitialize()) {
LOG(0) << "Failed to create the enemy.";
return false;
}
if (!player_.PreInitialize()) {
LOG(0) << "Failed to create the enemy.";
return false;
}
if (!menu_.PreInitialize()) {
LOG(0) << "Failed to create the menu.";
return false;
}
return true;
}
bool Demo::Initialize() { bool Demo::Initialize() {
saved_data_.Load(kSaveFileName); saved_data_.Load(kSaveFileName);
Engine::Get().LoadCustomShader("sky_without_nebula",
"sky_without_nebula.glsl");
Engine::Get().LoadCustomShader("sky", "sky.glsl");
if (!font_.Load("PixelCaps!.ttf"))
return false;
if (!sky_.Create(false)) { if (!sky_.Create(false)) {
LOG(0) << "Could not create the sky."; LOG << "Could not create the sky.";
return false; return false;
} }
if (!enemy_.Initialize()) { if (!enemy_.Initialize()) {
LOG(0) << "Failed to create the enemy."; LOG << "Failed to create the enemy.";
return false; return false;
} }
if (!player_.Initialize()) { if (!player_.Initialize()) {
LOG(0) << "Failed to create the enemy."; LOG << "Failed to create the enemy.";
return false; return false;
} }
if (!hud_.Initialize()) { if (!hud_.Initialize()) {
LOG(0) << "Failed to create the hud."; LOG << "Failed to create the hud.";
return false; return false;
} }
if (!menu_.Initialize()) { if (!menu_.Initialize()) {
LOG(0) << "Failed to create the menu."; LOG << "Failed to create the menu.";
return false; return false;
} }
if (!credits_.Initialize()) { if (!credits_.Initialize()) {
LOG(0) << "Failed to create the credits."; LOG << "Failed to create the credits.";
return false; return false;
} }
music_.SetSound("music"); auto sound = std::make_unique<Sound>();
music_.SetMaxAmplitude(0.5f); if (!sound->Load("Game_2_Main.mp3", true))
return false;
boss_music_.SetSound("boss_music"); auto boss_sound = std::make_unique<Sound>();
boss_music_.SetMaxAmplitude(0.5f); if (!boss_sound->Load("Game_2_Boss.mp3", true))
return false;
music_.SetSound(std::move(sound));
music_.SetMaxAplitude(0.5f);
boss_music_.SetSound(std::move(boss_sound));
boss_music_.SetMaxAplitude(0.5f);
if (!saved_data_.root().get("audio", Json::Value(true)).asBool()) if (!saved_data_.root().get("audio", Json::Value(true)).asBool())
Engine::Get().SetEnableAudio(false); Engine::Get().SetEnableAudio(false);
@ -183,13 +172,12 @@ void Demo::ContextLost() {
num_benchmark_samples_ = 0; num_benchmark_samples_ = 0;
avarage_fps_ = 0; avarage_fps_ = 0;
} }
menu_.SetRendererType();
} }
void Demo::LostFocus() {} void Demo::LostFocus() {}
void Demo::GainedFocus(bool from_interstitial_ad) { void Demo::GainedFocus(bool from_interstitial_ad) {
DLOG(0) << __func__ << " from_interstitial_ad: " << from_interstitial_ad; DLOG << __func__ << " from_interstitial_ad: " << from_interstitial_ad;
if (!from_interstitial_ad) { if (!from_interstitial_ad) {
// if (saved_data_.root().get(kLaunchCount, Json::Value(0)).asInt() > // if (saved_data_.root().get(kLaunchCount, Json::Value(0)).asInt() >
// kLaunchCountBeforeAd) // kLaunchCountBeforeAd)
@ -325,7 +313,7 @@ void Demo::UpdateMenuState(float delta_time) {
Engine::Get().Exit(); Engine::Get().Exit();
break; break;
default: default:
NOTREACHED() << "- Unknown menu option: " << menu_.selected_option(); NOTREACHED << "- Unknown menu option: " << menu_.selected_option();
} }
} }
@ -409,7 +397,7 @@ void Demo::StartNextStage(bool boss) {
waiting_for_next_wave_ = true; waiting_for_next_wave_ = true;
hud_.SetProgress(wave_ > 0 ? 0 : 1); hud_.SetProgress(wave_ > 0 ? 0 : 1);
DLOG_IF(0, wave_ > 0 && stage_time_ > 0) DLOG_IF(wave_ > 0 && stage_time_ > 0)
<< "wave: " << wave_ << " time: " << stage_time_ / 60.0f; << "wave: " << wave_ << " time: " << stage_time_ / 60.0f;
stage_time_ = 0; stage_time_ = 0;
@ -430,7 +418,7 @@ void Demo::StartNextStage(bool boss) {
music_.Stop(10); music_.Stop(10);
} }
boss_fight_ = true; boss_fight_ = true;
DLOG(0) << "Boss fight."; DLOG << "Boss fight.";
} else { } else {
size_t bonus_factor = [&]() -> size_t { size_t bonus_factor = [&]() -> size_t {
if (wave_ <= 3) if (wave_ <= 3)
@ -442,8 +430,8 @@ void Demo::StartNextStage(bool boss) {
return 100; return 100;
}(); }();
size_t bonus_score = wave_score_ * (bonus_factor - 1); size_t bonus_score = wave_score_ * (bonus_factor - 1);
DLOG(0) << "total_score_" << total_score_ << " wave " << wave_ DLOG << "total_score_" << total_score_ << " wave " << wave_
<< " score: " << wave_score_ << " bonus: " << bonus_score; << " score: " << wave_score_ << " bonus: " << bonus_score;
if (bonus_score > 0) { if (bonus_score > 0) {
delta_score_ += bonus_score; delta_score_ += bonus_score;
@ -482,7 +470,7 @@ void Demo::StartNextStage(bool boss) {
total_enemies_ = 23.0897f * log((float)wave_ + 1.0f) - 10.0f; total_enemies_ = 23.0897f * log((float)wave_ + 1.0f) - 10.0f;
last_num_enemies_killed_ = 0; last_num_enemies_killed_ = 0;
boss_fight_ = false; boss_fight_ = false;
DLOG(0) << "wave: " << wave_ << " total_enemies_: " << total_enemies_; DLOG << "wave: " << wave_ << " total_enemies_: " << total_enemies_;
} }
hud_.SetScore(total_score_, true); hud_.SetScore(total_score_, true);
@ -533,7 +521,7 @@ void Demo::SetDelayedWork(float seconds, base::Closure cb) {
} }
void Demo::BenchmarkResult(int avarage_fps) { void Demo::BenchmarkResult(int avarage_fps) {
LOG(0) << __func__ << " avarage_fps: " << avarage_fps; LOG << __func__ << " avarage_fps: " << avarage_fps;
if (avarage_fps < 30) if (avarage_fps < 30)
sky_.Create(true); sky_.Create(true);
} }

View File

@ -3,7 +3,7 @@
#include "base/closure.h" #include "base/closure.h"
#include "engine/animator.h" #include "engine/animator.h"
#include "engine/asset/font.h" #include "engine/font.h"
#include "engine/game.h" #include "engine/game.h"
#include "engine/persistent_data.h" #include "engine/persistent_data.h"
#include "engine/solid_quad.h" #include "engine/solid_quad.h"
@ -23,12 +23,14 @@ class Demo final : public eng::Game {
Demo(); Demo();
~Demo() final; ~Demo() final;
// Game interface
bool PreInitialize() final;
bool Initialize() final; bool Initialize() final;
void Update(float delta_time) final; void Update(float delta_time) final;
void ContextLost() final; void ContextLost() final;
void LostFocus() final; void LostFocus() final;
void GainedFocus(bool from_interstitial_ad) final; void GainedFocus(bool from_interstitial_ad) final;
void AddScore(size_t score); void AddScore(size_t score);

View File

@ -9,10 +9,11 @@
#include "base/collusion_test.h" #include "base/collusion_test.h"
#include "base/interpolation.h" #include "base/interpolation.h"
#include "base/log.h" #include "base/log.h"
#include "engine/asset/font.h"
#include "engine/asset/image.h"
#include "engine/engine.h" #include "engine/engine.h"
#include "engine/font.h"
#include "engine/image.h"
#include "engine/renderer/geometry.h" #include "engine/renderer/geometry.h"
#include "engine/sound.h"
#include "demo/demo.h" #include "demo/demo.h"
@ -77,50 +78,46 @@ Enemy::Enemy() = default;
Enemy::~Enemy() = default; Enemy::~Enemy() = default;
bool Enemy::PreInitialize() {
Engine::Get().SetImageSource("skull_tex", "demo/enemy_anims_01_frames_ok.png",
true);
Engine::Get().SetImageSource("bug_tex", "demo/enemy_anims_02_frames_ok.png",
true);
Engine::Get().SetImageSource("boss_tex1", "demo/Boss_ok.png", true);
Engine::Get().SetImageSource("boss_tex2", "demo/Boss_ok_lvl2.png", true);
Engine::Get().SetImageSource("boss_tex3", "demo/Boss_ok_lvl3.png", true);
Engine::Get().SetImageSource("target_tex", "demo/enemy_target_single_ok.png",
true);
Engine::Get().SetImageSource("blast_tex", "demo/enemy_anims_blast_ok.png",
true);
Engine::Get().SetImageSource("shield_tex", "demo/woom_enemy_shield.png",
true);
Engine::Get().SetImageSource("crate_tex", "demo/nuke_pack_OK.png", true);
for (int i = 0; i < kEnemyType_Max; ++i) {
if (i == kEnemyType_PowerUp)
continue;
Engine::Get().SetImageSource(
"score_tex"s + std::to_string(i),
std::bind(&Enemy::GetScoreImage, this, (EnemyType)i), true);
}
Engine::Get().SetShaderSource("chromatic_aberration",
"demo/chromatic_aberration.glsl");
Engine::Get().AsyncLoadSound("boss_intro", "demo/boss_intro.mp3");
Engine::Get().AsyncLoadSound("boss_explosion", "demo/boss_explosion.mp3");
Engine::Get().AsyncLoadSound("explosion", "demo/explosion.mp3");
Engine::Get().AsyncLoadSound("stealth", "demo/stealth.mp3");
Engine::Get().AsyncLoadSound("shield", "demo/shield.mp3");
Engine::Get().AsyncLoadSound("hit", "demo/hit.mp3");
Engine::Get().AsyncLoadSound("powerup-spawn", "demo/powerup-spawn.mp3");
Engine::Get().AsyncLoadSound("powerup-pick", "demo/powerup-pick.mp3");
return true;
}
bool Enemy::Initialize() { bool Enemy::Initialize() {
boss_intro_sound_ = std::make_shared<Sound>();
if (!boss_intro_sound_->Load("boss_intro.mp3", false))
return false;
boss_explosion_sound_ = std::make_shared<Sound>();
if (!boss_explosion_sound_->Load("boss_explosion.mp3", false))
return false;
explosion_sound_ = std::make_shared<Sound>();
if (!explosion_sound_->Load("explosion.mp3", false))
return false;
stealth_sound_ = std::make_shared<Sound>();
if (!stealth_sound_->Load("stealth.mp3", false))
return false;
shield_on_sound_ = std::make_shared<Sound>();
if (!shield_on_sound_->Load("shield.mp3", false))
return false;
hit_sound_ = std::make_shared<Sound>();
if (!hit_sound_->Load("hit.mp3", false))
return false;
power_up_spawn_sound_ = std::make_shared<Sound>();
if (!power_up_spawn_sound_->Load("powerup-spawn.mp3", false))
return false;
power_up_pick_sound_ = std::make_shared<Sound>();
if (!power_up_pick_sound_->Load("powerup-pick.mp3", false))
return false;
if (!CreateRenderResources())
return false;
boss_.SetZOrder(10); boss_.SetZOrder(10);
boss_animator_.Attach(&boss_); boss_animator_.Attach(&boss_);
boss_intro_.SetSound("boss_intro"); boss_intro_.SetSound(boss_intro_sound_);
boss_intro_.SetVariate(false); boss_intro_.SetVariate(false);
boss_intro_.SetSimulateStereo(false); boss_intro_.SetSimulateStereo(false);
@ -516,7 +513,7 @@ void Enemy::OnWaveStarted(int wave, bool boss_fight) {
return 1.0f; return 1.0f;
return 1.6f; return 1.6f;
}(); }();
DLOG(0) << "boss_spawn_time_factor_: " << boss_spawn_time_factor_; DLOG << "boss_spawn_time_factor_: " << boss_spawn_time_factor_;
SpawnBoss(); SpawnBoss();
} }
} }
@ -607,7 +604,7 @@ void Enemy::SpawnUnit(EnemyType enemy_type,
e.sprite.Create("crate_tex", {8, 3}); e.sprite.Create("crate_tex", {8, 3});
break; break;
default: default:
NOTREACHED() << "- Unkown enemy type: " << enemy_type; NOTREACHED << "- Unkown enemy type: " << enemy_type;
} }
e.sprite.SetZOrder(11); e.sprite.SetZOrder(11);
@ -653,13 +650,11 @@ void Enemy::SpawnUnit(EnemyType enemy_type,
e.health_bar.PlaceToBottomOf(e.sprite); e.health_bar.PlaceToBottomOf(e.sprite);
e.health_bar.SetColor({0.161f, 0.89f, 0.322f, 1}); e.health_bar.SetColor({0.161f, 0.89f, 0.322f, 1});
if (enemy_type != kEnemyType_PowerUp) { e.score.Create("score_tex"s + std::to_string(e.enemy_type));
e.score.Create("score_tex"s + std::to_string(e.enemy_type)); e.score.SetZOrder(12);
e.score.SetZOrder(12); e.score.Scale(1.1f);
e.score.Scale(1.1f); e.score.SetColor({1, 1, 1, 1});
e.score.SetColor({1, 1, 1, 1}); e.score.SetPosition(spawn_pos);
e.score.SetPosition(spawn_pos);
}
e.target_animator.Attach(&e.target); e.target_animator.Attach(&e.target);
@ -734,32 +729,32 @@ void Enemy::SpawnUnit(EnemyType enemy_type,
e.movement_animator.Play(Animator::kMovement, false); e.movement_animator.Play(Animator::kMovement, false);
if (e.enemy_type == kEnemyType_PowerUp) { if (e.enemy_type == kEnemyType_PowerUp) {
e.explosion.SetSound("powerup-pick"); e.explosion.SetSound(power_up_pick_sound_);
e.spawn.SetSound("powerup-spawn"); e.spawn.SetSound(power_up_spawn_sound_);
e.spawn.SetMaxAmplitude(2.0f); e.spawn.SetMaxAplitude(2.0f);
e.spawn.Play(false); e.spawn.Play(false);
} else { } else {
e.explosion.SetSound("explosion"); e.explosion.SetSound(explosion_sound_);
e.explosion.SetVariate(true); e.explosion.SetVariate(true);
e.explosion.SetSimulateStereo(true); e.explosion.SetSimulateStereo(true);
e.explosion.SetMaxAmplitude(0.9f); e.explosion.SetMaxAplitude(0.9f);
} }
e.stealth.SetSound("stealth"); e.stealth.SetSound(stealth_sound_);
e.stealth.SetVariate(false); e.stealth.SetVariate(false);
e.stealth.SetSimulateStereo(false); e.stealth.SetSimulateStereo(false);
e.stealth.SetMaxAmplitude(0.7f); e.stealth.SetMaxAplitude(0.7f);
e.shield_on.SetSound("shield"); e.shield_on.SetSound(shield_on_sound_);
e.shield_on.SetVariate(false); e.shield_on.SetVariate(false);
e.shield_on.SetSimulateStereo(false); e.shield_on.SetSimulateStereo(false);
e.shield_on.SetMaxAmplitude(0.5f); e.shield_on.SetMaxAplitude(0.5f);
e.hit.SetSound("hit"); e.hit.SetSound(hit_sound_);
e.hit.SetVariate(true); e.hit.SetVariate(true);
e.hit.SetSimulateStereo(false); e.hit.SetSimulateStereo(false);
e.hit.SetMaxAmplitude(0.5f); e.hit.SetMaxAplitude(0.5f);
} }
void Enemy::SpawnBoss() { void Enemy::SpawnBoss() {
@ -786,7 +781,7 @@ void Enemy::SpawnBoss() {
e.enemy_type = kEnemyType_Boss; e.enemy_type = kEnemyType_Boss;
e.damage_type = kDamageType_Any; e.damage_type = kDamageType_Any;
e.total_health = e.hit_points = 41.1283f * log((float)game->wave()) - 20.0f; e.total_health = e.hit_points = 41.1283f * log((float)game->wave()) - 20.0f;
DLOG(0) << " Boss health: " << e.total_health; DLOG << " Boss health: " << e.total_health;
Vector2f hit_box_pos = Vector2f hit_box_pos =
boss_.GetPosition() - boss_.GetSize() * Vector2f(0, 0.2f); boss_.GetPosition() - boss_.GetSize() * Vector2f(0, 0.2f);
@ -830,14 +825,14 @@ void Enemy::SpawnBoss() {
Animator::kMovement, [&]() -> void { e.marked_for_removal = true; }); Animator::kMovement, [&]() -> void { e.marked_for_removal = true; });
e.score_animator.Attach(&e.score); e.score_animator.Attach(&e.score);
e.explosion.SetSound("boss_explosion"); e.explosion.SetSound(boss_explosion_sound_);
e.explosion.SetVariate(false); e.explosion.SetVariate(false);
e.explosion.SetSimulateStereo(false); e.explosion.SetSimulateStereo(false);
e.hit.SetSound("hit"); e.hit.SetSound(hit_sound_);
e.hit.SetVariate(true); e.hit.SetVariate(true);
e.hit.SetSimulateStereo(false); e.hit.SetSimulateStereo(false);
e.hit.SetMaxAmplitude(0.5f); e.hit.SetMaxAplitude(0.5f);
}); });
boss_animator_.Play(Animator::kFrames, true); boss_animator_.Play(Animator::kFrames, true);
boss_animator_.Play(Animator::kMovement, false); boss_animator_.Play(Animator::kMovement, false);
@ -1172,7 +1167,11 @@ int Enemy::GetScore(EnemyType enemy_type) {
std::unique_ptr<Image> Enemy::GetScoreImage(EnemyType enemy_type) { std::unique_ptr<Image> Enemy::GetScoreImage(EnemyType enemy_type) {
const Font& font = static_cast<Demo*>(Engine::Get().GetGame())->GetFont(); const Font& font = static_cast<Demo*>(Engine::Get().GetGame())->GetFont();
std::string text = std::to_string(GetScore(enemy_type)); int score = GetScore(enemy_type);
if (!score)
return nullptr;
std::string text = std::to_string(score);
int width, height; int width, height;
font.CalculateBoundingBox(text.c_str(), width, height); font.CalculateBoundingBox(text.c_str(), width, height);
@ -1186,6 +1185,30 @@ std::unique_ptr<Image> Enemy::GetScoreImage(EnemyType enemy_type) {
return image; return image;
} }
bool Enemy::CreateRenderResources() {
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("boss_tex1", "Boss_ok.png", true);
Engine::Get().SetImageSource("boss_tex2", "Boss_ok_lvl2.png", true);
Engine::Get().SetImageSource("boss_tex3", "Boss_ok_lvl3.png", true);
Engine::Get().SetImageSource("target_tex", "enemy_target_single_ok.png",
true);
Engine::Get().SetImageSource("blast_tex", "enemy_anims_blast_ok.png", true);
Engine::Get().SetImageSource("shield_tex", "woom_enemy_shield.png", true);
Engine::Get().SetImageSource("crate_tex", "nuke_pack_OK.png", true);
for (int i = 0; i < kEnemyType_Max; ++i)
Engine::Get().SetImageSource(
"score_tex"s + std::to_string(i),
std::bind(&Enemy::GetScoreImage, this, (EnemyType)i), true);
Engine::Get().LoadCustomShader("chromatic_aberration",
"chromatic_aberration.glsl");
return true;
}
void Enemy::TranslateEnemyUnit(EnemyUnit& e, const Vector2f& delta) { void Enemy::TranslateEnemyUnit(EnemyUnit& e, const Vector2f& delta) {
e.sprite.Translate(delta); e.sprite.Translate(delta);
e.target.Translate(delta); e.target.Translate(delta);

View File

@ -15,6 +15,7 @@
namespace eng { namespace eng {
class Image; class Image;
class Sound;
} // namespace eng } // namespace eng
class Enemy { class Enemy {
@ -22,7 +23,6 @@ class Enemy {
Enemy(); Enemy();
~Enemy(); ~Enemy();
bool PreInitialize();
bool Initialize(); bool Initialize();
void Update(float delta_time); void Update(float delta_time);
@ -109,6 +109,15 @@ class Enemy {
eng::Animator boss_animator_; eng::Animator boss_animator_;
eng::SoundPlayer boss_intro_; eng::SoundPlayer boss_intro_;
std::shared_ptr<eng::Sound> boss_intro_sound_;
std::shared_ptr<eng::Sound> boss_explosion_sound_;
std::shared_ptr<eng::Sound> explosion_sound_;
std::shared_ptr<eng::Sound> stealth_sound_;
std::shared_ptr<eng::Sound> shield_on_sound_;
std::shared_ptr<eng::Sound> hit_sound_;
std::shared_ptr<eng::Sound> power_up_spawn_sound_;
std::shared_ptr<eng::Sound> power_up_pick_sound_;
std::list<EnemyUnit> enemies_; std::list<EnemyUnit> enemies_;
int num_enemies_killed_in_current_wave_ = 0; int num_enemies_killed_in_current_wave_ = 0;
@ -155,6 +164,8 @@ class Enemy {
std::unique_ptr<eng::Image> GetScoreImage(EnemyType enemy_type); std::unique_ptr<eng::Image> GetScoreImage(EnemyType enemy_type);
bool CreateRenderResources();
void TranslateEnemyUnit(EnemyUnit& e, const base::Vector2f& delta); void TranslateEnemyUnit(EnemyUnit& e, const base::Vector2f& delta);
}; };

View File

@ -3,9 +3,9 @@
#include "base/interpolation.h" #include "base/interpolation.h"
#include "base/log.h" #include "base/log.h"
#include "base/vecmath.h" #include "base/vecmath.h"
#include "engine/asset/font.h"
#include "engine/asset/image.h"
#include "engine/engine.h" #include "engine/engine.h"
#include "engine/font.h"
#include "engine/image.h"
#include "demo/demo.h" #include "demo/demo.h"
@ -198,6 +198,7 @@ void Hud::ShowMessage(const std::string& text, float duration) {
message_text_ = text; message_text_ = text;
Engine::Get().RefreshImage("message"); Engine::Get().RefreshImage("message");
message_.AutoScale();
message_.Scale(1.5f); message_.Scale(1.5f);
message_.SetColor({1, 1, 1, 0}); message_.SetColor({1, 1, 1, 0});
message_.SetVisible(true); message_.SetVisible(true);
@ -214,6 +215,7 @@ void Hud::ShowMessage(const std::string& text, float duration) {
void Hud::ShowBonus(size_t bonus) { void Hud::ShowBonus(size_t bonus) {
bonus_score_ = bonus; bonus_score_ = bonus;
Engine::Get().RefreshImage("bonus_tex"); Engine::Get().RefreshImage("bonus_tex");
bonus_.AutoScale();
bonus_.Scale(1.3f); bonus_.Scale(1.3f);
bonus_.SetColor({1, 1, 1, 1}); bonus_.SetColor({1, 1, 1, 1});
bonus_.SetVisible(true); bonus_.SetVisible(true);
@ -243,20 +245,26 @@ std::unique_ptr<Image> Hud::CreateMessageImage() {
font.Print(x, 0, message_text_.c_str(), image->GetBuffer(), font.Print(x, 0, message_text_.c_str(), image->GetBuffer(),
image->GetWidth()); image->GetWidth());
image->Compress(); image->Compress();
return image; return image;
} }
std::unique_ptr<Image> Hud::CreateBonusImage() { std::unique_ptr<Image> Hud::CreateBonusImage() {
const Font& font = static_cast<Demo*>(Engine::Get().GetGame())->GetFont(); const Font& font = static_cast<Demo*>(Engine::Get().GetGame())->GetFont();
auto image = CreateImage(); if (bonus_score_ == 0)
return nullptr;
std::string text = std::to_string(bonus_score_); std::string text = std::to_string(bonus_score_);
int w, h; int width, height;
font.CalculateBoundingBox(text.c_str(), w, h); font.CalculateBoundingBox(text.c_str(), width, height);
float x = (image->GetWidth() - w) / 2;
auto image = std::make_unique<Image>();
image->Create(width, height);
image->Clear({1, 1, 1, 0});
font.Print(0, 0, text.c_str(), image->GetBuffer(), image->GetWidth());
font.Print(x, 0, text.c_str(), image->GetBuffer(), image->GetWidth());
image->Compress(); image->Compress();
return image; return image;
} }
@ -274,7 +282,7 @@ std::unique_ptr<Image> Hud::Print(int i, const std::string& text) {
} }
font.Print(x, 0, text.c_str(), image->GetBuffer(), image->GetWidth()); font.Print(x, 0, text.c_str(), image->GetBuffer(), image->GetWidth());
image->Compress();
return image; return image;
} }

View File

@ -7,12 +7,12 @@
#include "base/collusion_test.h" #include "base/collusion_test.h"
#include "base/interpolation.h" #include "base/interpolation.h"
#include "base/log.h" #include "base/log.h"
#include "engine/asset/font.h"
#include "engine/asset/image.h"
#include "engine/asset/sound.h"
#include "engine/engine.h" #include "engine/engine.h"
#include "engine/font.h"
#include "engine/image.h"
#include "engine/input_event.h" #include "engine/input_event.h"
#include "engine/renderer/renderer.h" #include "engine/renderer/renderer.h"
#include "engine/sound.h"
#include "demo/demo.h" #include "demo/demo.h"
@ -53,9 +53,9 @@ Menu::Menu() = default;
Menu::~Menu() = default; Menu::~Menu() = default;
bool Menu::PreInitialize() { bool Menu::Initialize() {
click_sound_ = std::make_shared<Sound>(); click_sound_ = std::make_shared<Sound>();
if (!click_sound_->Load("demo/menu_click.mp3", false)) if (!click_sound_->Load("menu_click.mp3", false))
return false; return false;
Demo* game = static_cast<Demo*>(Engine::Get().GetGame()); Demo* game = static_cast<Demo*>(Engine::Get().GetGame());
@ -70,59 +70,8 @@ bool Menu::PreInitialize() {
max_text_width_ = width; max_text_width_ = width;
} }
Engine::Get().SetImageSource("menu_tex", if (!CreateRenderResources())
std::bind(&Menu::CreateMenuImage, this), true); return false;
Engine::Get().SetImageSource("logo_tex0",
"demo/woom_logo_start_frames_01.png", true);
Engine::Get().SetImageSource("logo_tex1",
"demo/woom_logo_start_frames_02-03.png", true);
Engine::Get().SetImageSource("buttons_tex", "demo/menu_icons.png", true);
Engine::Get().SetImageSource("renderer_logo", "demo/renderer_logo.png", true);
Engine::Get().SetImageSource(
"version_tex",
[]() -> std::unique_ptr<Image> {
const Font* font = Engine::Get().GetSystemFont();
int w, h;
font->CalculateBoundingBox(kVersionStr, w, h);
auto image = std::make_unique<Image>();
image->Create(w, font->GetLineHeight());
image->Clear({1, 1, 1, 0});
font->Print(0, 0, kVersionStr, image->GetBuffer(), image->GetWidth());
image->Compress();
return image;
},
true);
Engine::Get().SetImageSource("high_score_tex",
std::bind(&Menu::CreateHighScoreImage, this));
Engine::Get().SetImageSource("wave_up_tex", []() -> std::unique_ptr<Image> {
const Font& font = static_cast<Demo*>(Engine::Get().GetGame())->GetFont();
constexpr char btn_text[] = "[ ]";
int w, h;
font.CalculateBoundingBox(btn_text, w, h);
auto image = std::make_unique<Image>();
image->Create(w, h);
image->Clear({1, 1, 1, 0});
font.Print(0, 0, btn_text, image->GetBuffer(), image->GetWidth());
image->Compress();
return image;
});
return true;
}
bool Menu::Initialize() {
Demo* game = static_cast<Demo*>(Engine::Get().GetGame());
for (int i = 0; i < kOption_Max; ++i) { for (int i = 0; i < kOption_Max; ++i) {
items_[i].text.Create("menu_tex", {1, 4}); items_[i].text.Create("menu_tex", {1, 4});
@ -149,7 +98,7 @@ bool Menu::Initialize() {
click_.SetSound(click_sound_); click_.SetSound(click_sound_);
click_.SetVariate(false); click_.SetVariate(false);
click_.SetSimulateStereo(false); click_.SetSimulateStereo(false);
click_.SetMaxAmplitude(1.5f); click_.SetMaxAplitude(1.5f);
logo_[0].Create("logo_tex0", {3, 8}); logo_[0].Create("logo_tex0", {3, 8});
logo_[0].SetZOrder(41); logo_[0].SetZOrder(41);
@ -250,6 +199,9 @@ bool Menu::Initialize() {
Engine::Get().CreateRenderer(renderer_type_.enabled() Engine::Get().CreateRenderer(renderer_type_.enabled()
? RendererType::kVulkan ? RendererType::kVulkan
: RendererType::kOpenGL); : RendererType::kOpenGL);
renderer_type_.SetEnabled(
(Engine::Get().GetRendererType() == RendererType::kVulkan));
Engine::Get().ConsumeInputEvents();
}, },
true, Engine::Get().GetRendererType() == RendererType::kVulkan, true, Engine::Get().GetRendererType() == RendererType::kVulkan,
kColorFadeOut, {Vector4f{1, 1, 1, 1}, Vector4f{1, 1, 1, 1}}); kColorFadeOut, {Vector4f{1, 1, 1, 1}, Vector4f{1, 1, 1, 1}});
@ -369,11 +321,6 @@ void Menu::SetOptionEnabled(Option o, bool enable) {
} }
} }
void Menu::SetRendererType() {
renderer_type_.SetEnabled(
(Engine::Get().GetRendererType() == RendererType::kVulkan));
}
void Menu::Show() { void Menu::Show() {
logo_[1].SetColor(kColorNormal); logo_[1].SetColor(kColorNormal);
logo_animator_[0].SetVisible(true); logo_animator_[0].SetVisible(true);
@ -494,6 +441,53 @@ void Menu::Hide(Closure cb) {
} }
} }
bool Menu::CreateRenderResources() {
Engine::Get().SetImageSource("menu_tex",
std::bind(&Menu::CreateMenuImage, this));
Engine::Get().SetImageSource("logo_tex0", "woom_logo_start_frames_01.png");
Engine::Get().SetImageSource("logo_tex1", "woom_logo_start_frames_02-03.png");
Engine::Get().SetImageSource("buttons_tex", "menu_icons.png");
Engine::Get().SetImageSource("high_score_tex",
std::bind(&Menu::CreateHighScoreImage, this));
Engine::Get().SetImageSource("renderer_logo", "renderer_logo.png");
Engine::Get().SetImageSource("wave_up_tex", []() -> std::unique_ptr<Image> {
const Font& font = static_cast<Demo*>(Engine::Get().GetGame())->GetFont();
constexpr char btn_text[] = "[ ]";
int w, h;
font.CalculateBoundingBox(btn_text, w, h);
auto image = std::make_unique<Image>();
image->Create(w, h);
image->Clear({1, 1, 1, 0});
font.Print(0, 0, btn_text, image->GetBuffer(), image->GetWidth());
image->Compress();
return image;
});
Engine::Get().SetImageSource("version_tex", []() -> std::unique_ptr<Image> {
const Font* font = Engine::Get().GetSystemFont();
int w, h;
font->CalculateBoundingBox(kVersionStr, w, h);
auto image = std::make_unique<Image>();
image->Create(w, font->GetLineHeight());
image->Clear({1, 1, 1, 0});
font->Print(0, 0, kVersionStr, image->GetBuffer(), image->GetWidth());
image->Compress();
return image;
});
return true;
}
std::unique_ptr<Image> Menu::CreateMenuImage() { std::unique_ptr<Image> Menu::CreateMenuImage() {
const Font& font = static_cast<Demo*>(Engine::Get().GetGame())->GetFont(); const Font& font = static_cast<Demo*>(Engine::Get().GetGame())->GetFont();

View File

@ -30,13 +30,11 @@ class Menu {
Menu(); Menu();
~Menu(); ~Menu();
bool PreInitialize();
bool Initialize(); bool Initialize();
void OnInputEvent(std::unique_ptr<eng::InputEvent> event); void OnInputEvent(std::unique_ptr<eng::InputEvent> event);
void SetOptionEnabled(Option o, bool enable); void SetOptionEnabled(Option o, bool enable);
void SetRendererType();
void Show(); void Show();
void Hide(base::Closure cb = nullptr); void Hide(base::Closure cb = nullptr);
@ -147,6 +145,9 @@ class Menu {
Radio starting_wave_; Radio starting_wave_;
Button wave_up_; Button wave_up_;
Button wave_down_;
bool CreateRenderResources();
std::unique_ptr<eng::Image> CreateMenuImage(); std::unique_ptr<eng::Image> CreateMenuImage();
std::unique_ptr<eng::Image> CreateHighScoreImage(); std::unique_ptr<eng::Image> CreateHighScoreImage();

View File

@ -2,10 +2,10 @@
#include "base/interpolation.h" #include "base/interpolation.h"
#include "base/log.h" #include "base/log.h"
#include "base/vecmath.h"
#include "engine/asset/font.h"
#include "engine/engine.h" #include "engine/engine.h"
#include "engine/font.h"
#include "engine/input_event.h" #include "engine/input_event.h"
#include "engine/sound.h"
#include "demo/demo.h" #include "demo/demo.h"
@ -29,21 +29,22 @@ Player::Player() = default;
Player::~Player() = default; Player::~Player() = default;
bool Player::PreInitialize() {
Engine::Get().SetImageSource("weapon_tex", "demo/enemy_anims_flare_ok.png",
true);
Engine::Get().SetImageSource("beam_tex", "demo/enemy_ray_ok.png", true);
Engine::Get().SetImageSource("nuke_symbol_tex", "demo/nuke_frames.png", true);
Engine::Get().SetImageSource("health_bead", "demo/bead.png", true);
Engine::Get().AsyncLoadSound("laser", "demo/laser.mp3");
Engine::Get().AsyncLoadSound("nuke", "demo/nuke.mp3");
Engine::Get().AsyncLoadSound("no_nuke", "demo/no_nuke.mp3");
return true;
}
bool Player::Initialize() { bool Player::Initialize() {
if (!CreateRenderResources())
return false;
laser_shot_sound_ = std::make_shared<Sound>();
if (!laser_shot_sound_->Load("laser.mp3", false))
return false;
nuke_explosion_sound_ = std::make_shared<Sound>();
if (!nuke_explosion_sound_->Load("nuke.mp3", false))
return false;
no_nuke_sound_ = std::make_shared<Sound>();
if (!no_nuke_sound_->Load("no_nuke.mp3", false))
return false;
SetupWeapons(); SetupWeapons();
Vector2f hb_pos = Engine::Get().GetScreenSize() / Vector2f(2, -2) + Vector2f hb_pos = Engine::Get().GetScreenSize() / Vector2f(2, -2) +
@ -74,12 +75,12 @@ bool Player::Initialize() {
nuke_symbol_animator_.Attach(&nuke_symbol_); nuke_symbol_animator_.Attach(&nuke_symbol_);
nuke_explosion_.SetSound("nuke"); nuke_explosion_.SetSound(nuke_explosion_sound_);
nuke_explosion_.SetVariate(false); nuke_explosion_.SetVariate(false);
nuke_explosion_.SetSimulateStereo(false); nuke_explosion_.SetSimulateStereo(false);
nuke_explosion_.SetMaxAmplitude(0.8f); nuke_explosion_.SetMaxAplitude(0.8f);
no_nuke_.SetSound("no_nuke"); no_nuke_.SetSound(no_nuke_sound_);
return true; return true;
} }
@ -152,7 +153,7 @@ void Player::AddNuke(int n) {
if (!nuke_symbol_animator_.IsPlaying(Animator::kRotation)) { if (!nuke_symbol_animator_.IsPlaying(Animator::kRotation)) {
nuke_symbol_animator_.SetRotation( nuke_symbol_animator_.SetRotation(
PIf * 5, 2, std::bind(SmootherStep, std::placeholders::_1)); M_PI * 5, 2, std::bind(SmootherStep, std::placeholders::_1));
nuke_symbol_animator_.Play(Animator::kRotation, false); nuke_symbol_animator_.Play(Animator::kRotation, false);
} }
} }
@ -222,7 +223,7 @@ void Player::Fire(DamageType type, Vector2f dir) {
dir.Normalize(); dir.Normalize();
float cos_theta = dir.DotProduct(Vector2f(1, 0)); float cos_theta = dir.DotProduct(Vector2f(1, 0));
float theta = acos(cos_theta) + PIf; float theta = acos(cos_theta) + M_PI;
beam_[type].SetTheta(theta); beam_[type].SetTheta(theta);
auto offset = beam_[type].GetRotation() * (len / 2); auto offset = beam_[type].GetRotation() * (len / 2);
beam_[type].Translate({offset.y, -offset.x}); beam_[type].Translate({offset.y, -offset.x});
@ -287,7 +288,7 @@ void Player::SetupWeapons() {
weapon_[i].SetFrame(wepon_warmup_frame[i]); weapon_[i].SetFrame(wepon_warmup_frame[i]);
warmup_animator_[i].SetFrames(wepon_warmup_frame_count, wepon_anim_speed); warmup_animator_[i].SetFrames(wepon_warmup_frame_count, wepon_anim_speed);
warmup_animator_[i].SetRotation(PIf * 2, 20.0f); warmup_animator_[i].SetRotation(M_PI * 2, 20.0f);
warmup_animator_[i].Attach(&weapon_[i]); warmup_animator_[i].Attach(&weapon_[i]);
warmup_animator_[i].Play(Animator::kRotation, true); warmup_animator_[i].Play(Animator::kRotation, true);
@ -305,10 +306,10 @@ void Player::SetupWeapons() {
beam_animator_[i].SetBlending({1, 1, 1, 0}, 0.16f); beam_animator_[i].SetBlending({1, 1, 1, 0}, 0.16f);
beam_animator_[i].Attach(&beam_[i]); beam_animator_[i].Attach(&beam_[i]);
laser_shot_[i].SetSound("laser"); laser_shot_[i].SetSound(laser_shot_sound_);
laser_shot_[i].SetVariate(true); laser_shot_[i].SetVariate(true);
laser_shot_[i].SetSimulateStereo(false); laser_shot_[i].SetSimulateStereo(false);
laser_shot_[i].SetMaxAmplitude(0.4f); laser_shot_[i].SetMaxAplitude(0.4f);
} }
} }
@ -484,3 +485,12 @@ void Player::NavigateBack() {
Engine& engine = Engine::Get(); Engine& engine = Engine::Get();
static_cast<Demo*>(engine.GetGame())->EnterMenuState(); static_cast<Demo*>(engine.GetGame())->EnterMenuState();
} }
bool Player::CreateRenderResources() {
Engine::Get().SetImageSource("weapon_tex", "enemy_anims_flare_ok.png", true);
Engine::Get().SetImageSource("beam_tex", "enemy_ray_ok.png", true);
Engine::Get().SetImageSource("nuke_symbol_tex", "nuke_frames.png", true);
Engine::Get().SetImageSource("health_bead", "bead.png", true);
return true;
}

View File

@ -13,6 +13,7 @@
namespace eng { namespace eng {
class InputEvent; class InputEvent;
class Sound;
} // namespace eng } // namespace eng
class Player { class Player {
@ -20,7 +21,6 @@ class Player {
Player(); Player();
~Player(); ~Player();
bool PreInitialize();
bool Initialize(); bool Initialize();
void Update(float delta_time); void Update(float delta_time);
@ -41,6 +41,10 @@ class Player {
int nuke_count() { return nuke_count_; } int nuke_count() { return nuke_count_; }
private: private:
std::shared_ptr<eng::Sound> nuke_explosion_sound_;
std::shared_ptr<eng::Sound> no_nuke_sound_;
std::shared_ptr<eng::Sound> laser_shot_sound_;
eng::ImageQuad drag_sign_[2]; eng::ImageQuad drag_sign_[2];
eng::ImageQuad weapon_[2]; eng::ImageQuad weapon_[2];
eng::ImageQuad beam_[2]; eng::ImageQuad beam_[2];
@ -97,6 +101,8 @@ class Player {
bool ValidateDrag(int i); bool ValidateDrag(int i);
void NavigateBack(); void NavigateBack();
bool CreateRenderResources();
}; };
#endif // DEMO_PLAYER_H #endif // DEMO_PLAYER_H

View File

@ -21,8 +21,8 @@ SkyQuad::~SkyQuad() = default;
bool SkyQuad::Create(bool without_nebula) { bool SkyQuad::Create(bool without_nebula) {
without_nebula_ = without_nebula; without_nebula_ = without_nebula;
scale_ = Engine::Get().GetScreenSize(); scale_ = Engine::Get().GetScreenSize();
shader_ = shader_ = Engine::Get().GetCustomShader(
Engine::Get().GetShader(without_nebula ? "sky_without_nebula" : "sky"); without_nebula ? "sky_without_nebula" : "sky");
color_animator_.Attach(this); color_animator_.Attach(this);

View File

@ -5,6 +5,11 @@
#include "engine/animatable.h" #include "engine/animatable.h"
#include "engine/animator.h" #include "engine/animator.h"
#include <array>
#include <memory>
#include <string>
#include <vector>
namespace eng { namespace eng {
class Shader; class Shader;
} // namespace eng } // namespace eng

View File

@ -1,48 +0,0 @@
source_set("engine") {
sources = [
"animatable.cc",
"animatable.h",
"animator.cc",
"animator.h",
"asset/font.cc",
"asset/font.h",
"asset/image.cc",
"asset/image.h",
"asset/mesh.cc",
"asset/mesh.h",
"asset/shader_source.cc",
"asset/shader_source.h",
"asset/sound.cc",
"asset/sound.h",
"drawable.cc",
"drawable.h",
"engine.cc",
"engine.h",
"game.h",
"game_factory.h",
"image_quad.cc",
"image_quad.h",
"imgui_backend.cc",
"imgui_backend.h",
"input_event.h",
"persistent_data.cc",
"persistent_data.h",
"solid_quad.cc",
"solid_quad.h",
"sound_player.cc",
"sound_player.h",
]
deps = [
"//assets/engine",
"//src/base",
"//src/engine/audio",
"//src/engine/platform",
"//src/engine/renderer",
"//src/third_party/imgui",
"//src/third_party/jsoncpp",
"//src/third_party/minimp3",
"//src/third_party/stb",
"//src/third_party/texture_compressor",
]
}

View File

@ -51,7 +51,7 @@ class Animatable : public Drawable {
protected: protected:
base::Vector2f position_ = {0, 0}; base::Vector2f position_ = {0, 0};
base::Vector2f size_ = {0, 0}; base::Vector2f size_ = {1, 1};
base::Vector2f scale_ = {1, 1}; base::Vector2f scale_ = {1, 1};
base::Vector2f rotation_ = {0, 1}; base::Vector2f rotation_ = {0, 1};
float theta_ = 0; float theta_ = 0;

View File

@ -1,35 +0,0 @@
source_set("audio") {
sources = [
"audio_bus.cc",
"audio_bus.h",
"audio_device.h",
"audio_mixer.cc",
"audio_mixer.h",
"mixer_input.cc",
"mixer_input.h",
"sinc_resampler.cc",
"sinc_resampler.h",
]
libs = []
deps = [ "//src/base" ]
if (target_os == "linux") {
sources += [
"audio_device_alsa.cc",
"audio_device_alsa.h",
]
libs += [ "asound" ]
} else if (target_os == "win") {
sources += [
"audio_device_wasapi.cc",
"audio_device_wasapi.h",
]
} else if (target_os == "android") {
sources += [
"audio_device_oboe.cc",
"audio_device_oboe.h",
]
deps += [ "//src/third_party/oboe" ]
}
}

View File

@ -1,37 +0,0 @@
#ifndef ENGINE_AUDIO_AUDIO_DEVICE_H
#define ENGINE_AUDIO_AUDIO_DEVICE_H
namespace eng {
// Models an audio device sending mixed audio to the audio driver. Audio data
// from the mixer source is delivered on a pull model using Delegate.
class AudioDevice {
public:
class Delegate {
public:
Delegate() = default;
virtual ~Delegate() = default;
virtual int GetChannelCount() = 0;
virtual void RenderAudio(float* output_buffer, size_t num_frames) = 0;
};
AudioDevice() = default;
virtual ~AudioDevice() = default;
virtual bool Initialize() = 0;
virtual void Suspend() = 0;
virtual void Resume() = 0;
virtual size_t GetHardwareSampleRate() = 0;
private:
AudioDevice(const AudioDevice&) = delete;
AudioDevice& operator=(const AudioDevice&) = delete;
};
} // namespace eng
#endif // ENGINE_AUDIO_AUDIO_DEVICE_H

View File

@ -1,85 +0,0 @@
#include "engine/audio/audio_device_oboe.h"
#include "base/log.h"
#include "third_party/oboe/include/oboe/Oboe.h"
using namespace base;
namespace eng {
AudioDeviceOboe::AudioDeviceOboe(AudioDevice::Delegate* delegate)
: callback_(std::make_unique<StreamCallback>(this)), delegate_(delegate) {}
AudioDeviceOboe::~AudioDeviceOboe() {
LOG(0) << "Shutting down audio.";
stream_->stop();
}
bool AudioDeviceOboe::Initialize() {
LOG(0) << "Initializing audio.";
return RestartStream();
}
void AudioDeviceOboe::Suspend() {
stream_->pause();
}
void AudioDeviceOboe::Resume() {
stream_->start();
}
size_t AudioDeviceOboe::GetHardwareSampleRate() {
return stream_->getSampleRate();
}
AudioDeviceOboe::StreamCallback::StreamCallback(AudioDeviceOboe* audio_device)
: audio_device_(audio_device) {}
AudioDeviceOboe::StreamCallback::~StreamCallback() = default;
oboe::DataCallbackResult AudioDeviceOboe::StreamCallback::onAudioReady(
oboe::AudioStream* oboe_stream,
void* audio_data,
int32_t num_frames) {
float* output_buffer = static_cast<float*>(audio_data);
audio_device_->delegate_->RenderAudio(output_buffer, num_frames);
return oboe::DataCallbackResult::Continue;
}
void AudioDeviceOboe::StreamCallback::onErrorAfterClose(
oboe::AudioStream* oboe_stream,
oboe::Result error) {
LOG(0) << "Error after close. Error: " << oboe::convertToText(error);
audio_device_->RestartStream();
}
bool AudioDeviceOboe::RestartStream() {
oboe::AudioStreamBuilder builder;
oboe::Result result =
builder.setSharingMode(oboe::SharingMode::Exclusive)
->setPerformanceMode(oboe::PerformanceMode::LowLatency)
->setFormat(oboe::AudioFormat::Float)
->setChannelCount(delegate_->GetChannelCount())
->setDirection(oboe::Direction::Output)
->setUsage(oboe::Usage::Game)
->setCallback(callback_.get())
->openManagedStream(stream_);
LOG(0) << "Oboe Audio Stream:";
LOG(0) << " performance mode: " << (int)stream_->getPerformanceMode();
LOG(0) << " format: " << (int)stream_->getFormat();
LOG(0) << " channel count: " << stream_->getChannelCount();
LOG(0) << " sample rate: " << stream_->getSampleRate();
if (result != oboe::Result::OK) {
LOG(0) << "Failed to create the playback stream. Error: "
<< oboe::convertToText(result);
return false;
}
stream_->start();
return true;
}
} // namespace eng

View File

@ -1,210 +0,0 @@
#include "engine/audio/audio_device_wasapi.h"
#include "base/log.h"
const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
const IID IID_IAudioClient = __uuidof(IAudioClient);
const IID IID_IAudioRenderClient = __uuidof(IAudioRenderClient);
using namespace base;
namespace eng {
AudioDeviceWASAPI::AudioDeviceWASAPI(AudioDevice::Delegate* delegate)
: delegate_(delegate) {}
AudioDeviceWASAPI::~AudioDeviceWASAPI() {
LOG(0) << "Shutting down audio.";
TerminateAudioThread();
if (shutdown_event_)
CloseHandle(shutdown_event_);
if (ready_event_)
CloseHandle(ready_event_);
if (device_)
device_->Release();
if (audio_client_)
audio_client_->Release();
if (render_client_)
render_client_->Release();
if (device_enumerator_)
device_enumerator_->Release();
}
bool AudioDeviceWASAPI::Initialize() {
LOG(0) << "Initializing audio.";
HRESULT hr;
do {
hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
IID_IMMDeviceEnumerator, (void**)&device_enumerator_);
if (FAILED(hr)) {
LOG(0) << "Unable to instantiate device enumerator: " << hr;
break;
}
hr = device_enumerator_->GetDefaultAudioEndpoint(eRender, eConsole,
&device_);
if (FAILED(hr)) {
LOG(0) << "Unable to get default audio endpoint: " << hr;
break;
}
hr = device_->Activate(IID_IAudioClient, CLSCTX_ALL, NULL,
(void**)&audio_client_);
if (FAILED(hr)) {
LOG(0) << "Unable to activate audio client: " << hr;
break;
}
// Use float format.
WAVEFORMATEX* closest_match = nullptr;
WAVEFORMATEXTENSIBLE wfxex = {0};
wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
wfxex.dwChannelMask = KSAUDIO_SPEAKER_STEREO;
wfxex.Format.nChannels = 2;
wfxex.Format.nSamplesPerSec = 48000;
wfxex.Format.wBitsPerSample = 32;
wfxex.Samples.wValidBitsPerSample = 32;
wfxex.Format.nBlockAlign =
wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
wfxex.Format.nAvgBytesPerSec =
wfxex.Format.nBlockAlign * wfxex.Format.nSamplesPerSec;
wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
hr = audio_client_->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED,
&wfxex.Format, &closest_match);
if (FAILED(hr)) {
LOG(0) << "Unsupported sample format.";
break;
}
WAVEFORMATEX* format = closest_match ? closest_match : &wfxex.Format;
if ((format->wFormatTag != WAVE_FORMAT_IEEE_FLOAT &&
(format->wFormatTag != WAVE_FORMAT_EXTENSIBLE ||
reinterpret_cast<WAVEFORMATEXTENSIBLE*>(format)->SubFormat !=
KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) ||
format->nChannels != 2) {
LOG(0) << "Unsupported sample format.";
break;
}
sample_rate_ = format->nSamplesPerSec;
HRESULT hr = audio_client_->Initialize(
AUDCLNT_SHAREMODE_SHARED,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST, 0, 0,
format, NULL);
if (closest_match)
CoTaskMemFree(closest_match);
if (FAILED(hr)) {
LOG(0) << "Unable to initialize audio client: " << hr;
break;
}
// Get the actual size of the allocated buffer.
hr = audio_client_->GetBufferSize(&buffer_size_);
if (FAILED(hr)) {
LOG(0) << "Unable to get audio client buffer size: " << hr;
break;
}
hr = audio_client_->GetService(IID_IAudioRenderClient,
(void**)&render_client_);
if (FAILED(hr)) {
LOG(0) << "Unable to get audio render client: " << hr;
break;
}
shutdown_event_ =
CreateEventEx(NULL, NULL, 0, EVENT_MODIFY_STATE | SYNCHRONIZE);
if (shutdown_event_ == NULL) {
LOG(0) << "Unable to create shutdown event: " << hr;
break;
}
ready_event_ =
CreateEventEx(NULL, NULL, 0, EVENT_MODIFY_STATE | SYNCHRONIZE);
if (ready_event_ == NULL) {
LOG(0) << "Unable to create samples ready event: " << hr;
break;
}
hr = audio_client_->SetEventHandle(ready_event_);
if (FAILED(hr)) {
LOG(0) << "Unable to set ready event: " << hr;
break;
}
StartAudioThread();
hr = audio_client_->Start();
if (FAILED(hr)) {
break;
}
return true;
} while (false);
return false;
}
void AudioDeviceWASAPI::Suspend() {
audio_client_->Stop();
}
void AudioDeviceWASAPI::Resume() {
audio_client_->Start();
}
size_t AudioDeviceWASAPI::GetHardwareSampleRate() {
return sample_rate_;
}
void AudioDeviceWASAPI::StartAudioThread() {
DCHECK(!audio_thread_.joinable());
LOG(0) << "Starting audio thread.";
audio_thread_ = std::thread(&AudioDeviceWASAPI::AudioThreadMain, this);
}
void AudioDeviceWASAPI::TerminateAudioThread() {
LOG(0) << "Terminating audio thread";
SetEvent(shutdown_event_);
audio_thread_.join();
}
void AudioDeviceWASAPI::AudioThreadMain() {
DCHECK(delegate_);
HANDLE wait_array[] = {shutdown_event_, ready_event_};
for (;;) {
switch (WaitForMultipleObjects(2, wait_array, FALSE, INFINITE)) {
case WAIT_OBJECT_0 + 0: // shutdown_event_
return;
case WAIT_OBJECT_0 + 1: { // ready_event_
UINT32 padding;
HRESULT hr = audio_client_->GetCurrentPadding(&padding);
if (SUCCEEDED(hr)) {
BYTE* pData;
UINT32 frames_available = buffer_size_ - padding;
hr = render_client_->GetBuffer(frames_available, &pData);
if (SUCCEEDED(hr)) {
delegate_->RenderAudio(reinterpret_cast<float*>(pData),
frames_available);
hr = render_client_->ReleaseBuffer(frames_available, 0);
if (!SUCCEEDED(hr))
DLOG(0) << "Unable to release buffer: " << hr;
} else {
DLOG(0) << "Unable to get buffer: " << hr;
}
}
} break;
}
}
}
} // namespace eng

View File

@ -1,49 +0,0 @@
#ifndef ENGINE_AUDIO_AUDIO_DEVICE_WASAPI_H
#define ENGINE_AUDIO_AUDIO_DEVICE_WASAPI_H
#include <thread>
#include <AudioClient.h>
#include <MMDeviceAPI.h>
#include "engine/audio/audio_device.h"
namespace eng {
class AudioDeviceWASAPI final : public AudioDevice {
public:
AudioDeviceWASAPI(AudioDevice::Delegate* delegate);
~AudioDeviceWASAPI() final;
bool Initialize() final;
void Suspend() final;
void Resume() final;
size_t GetHardwareSampleRate() final;
private:
IMMDevice* device_ = nullptr;
IAudioClient* audio_client_ = nullptr;
IAudioRenderClient* render_client_ = nullptr;
IMMDeviceEnumerator* device_enumerator_ = nullptr;
HANDLE shutdown_event_ = nullptr;
HANDLE ready_event_ = nullptr;
std::thread audio_thread_;
UINT32 buffer_size_ = 0;
size_t sample_rate_ = 0;
AudioDevice::Delegate* delegate_ = nullptr;
void StartAudioThread();
void TerminateAudioThread();
void AudioThreadMain();
};
} // namespace eng
#endif // ENGINE_AUDIO_AUDIO_DEVICE_WASAPI_H

View File

@ -4,15 +4,13 @@
#include "base/log.h" #include "base/log.h"
#include "base/task_runner.h" #include "base/task_runner.h"
#include "base/thread_pool.h"
#include "engine/audio/audio_bus.h" #include "engine/audio/audio_bus.h"
#include "engine/audio/mixer_input.h"
#if defined(__ANDROID__) #if defined(__ANDROID__)
#include "engine/audio/audio_device_oboe.h" #include "engine/audio/audio_sink_oboe.h"
#elif defined(__linux__) #elif defined(__linux__)
#include "engine/audio/audio_device_alsa.h" #include "engine/audio/audio_sink_alsa.h"
#elif defined(_WIN32)
#include "engine/audio/audio_device_wasapi.h"
#endif #endif
using namespace base; using namespace base;
@ -22,54 +20,168 @@ namespace eng {
AudioMixer::AudioMixer() AudioMixer::AudioMixer()
: main_thread_task_runner_(TaskRunner::GetThreadLocalTaskRunner()), : main_thread_task_runner_(TaskRunner::GetThreadLocalTaskRunner()),
#if defined(__ANDROID__) #if defined(__ANDROID__)
audio_device_{std::make_unique<AudioDeviceOboe>(this)} { audio_sink_{std::make_unique<AudioSinkOboe>(this)} {
#elif defined(__linux__) #elif defined(__linux__)
audio_device_{std::make_unique<AudioDeviceAlsa>(this)} { audio_sink_{std::make_unique<AudioSinkAlsa>(this)} {
#elif defined(_WIN32)
audio_device_{std::make_unique<AudioDeviceWASAPI>(this)} {
#endif #endif
bool res = audio_device_->Initialize(); bool res = audio_sink_->Initialize();
CHECK(res) << "Failed to initialize audio device."; CHECK(res) << "Failed to initialize audio sink.";
} }
AudioMixer::~AudioMixer() { AudioMixer::~AudioMixer() = default;
audio_device_.reset();
uint64_t AudioMixer::CreateResource() {
uint64_t resource_id = ++last_resource_id_;
resources_[resource_id] = std::make_shared<Resource>();
return resource_id;
} }
void AudioMixer::AddInput(std::shared_ptr<MixerInput> mixer_input) { void AudioMixer::DestroyResource(uint64_t resource_id) {
DCHECK(audio_enabled_); auto it = resources_.find(resource_id);
if (it == resources_.end())
return;
if (it->second->active) {
it->second->restart_cb = nullptr;
it->second->flags.fetch_or(kStopped, std::memory_order_relaxed);
}
resources_.erase(it);
}
void AudioMixer::Play(uint64_t resource_id,
std::shared_ptr<AudioBus> audio_bus,
float amplitude,
bool reset_pos) {
if (!audio_enabled_)
return;
auto it = resources_.find(resource_id);
if (it == resources_.end())
return;
if (it->second->active) {
if (reset_pos)
it->second->flags.fetch_or(kStopped, std::memory_order_relaxed);
if (it->second->flags.load(std::memory_order_relaxed) & kStopped)
it->second->restart_cb = [&, resource_id, audio_bus, amplitude,
reset_pos]() -> void {
Play(resource_id, audio_bus, amplitude, reset_pos);
};
return;
}
if (reset_pos) {
it->second->src_index = 0;
it->second->accumulator = 0;
audio_bus->ResetStream();
} else if (it->second->src_index >= audio_bus->samples_per_channel()) {
return;
}
it->second->active = true;
it->second->flags.fetch_and(~kStopped, std::memory_order_relaxed);
it->second->audio_bus = audio_bus;
if (amplitude >= 0)
it->second->amplitude = amplitude;
std::lock_guard<std::mutex> scoped_lock(lock_); std::lock_guard<std::mutex> scoped_lock(lock_);
inputs_[0].push_back(mixer_input); play_list_[0].push_back(it->second);
}
void AudioMixer::Stop(uint64_t resource_id) {
auto it = resources_.find(resource_id);
if (it == resources_.end())
return;
if (it->second->active) {
it->second->restart_cb = nullptr;
it->second->flags.fetch_or(kStopped, std::memory_order_relaxed);
}
}
void AudioMixer::SetLoop(uint64_t resource_id, bool loop) {
auto it = resources_.find(resource_id);
if (it == resources_.end())
return;
if (loop)
it->second->flags.fetch_or(kLoop, std::memory_order_relaxed);
else
it->second->flags.fetch_and(~kLoop, std::memory_order_relaxed);
}
void AudioMixer::SetSimulateStereo(uint64_t resource_id, bool simulate) {
auto it = resources_.find(resource_id);
if (it == resources_.end())
return;
if (simulate)
it->second->flags.fetch_or(kSimulateStereo, std::memory_order_relaxed);
else
it->second->flags.fetch_and(~kSimulateStereo, std::memory_order_relaxed);
}
void AudioMixer::SetResampleStep(uint64_t resource_id, size_t step) {
auto it = resources_.find(resource_id);
if (it == resources_.end())
return;
it->second->step.store(step + 100, std::memory_order_relaxed);
}
void AudioMixer::SetMaxAmplitude(uint64_t resource_id, float max_amplitude) {
auto it = resources_.find(resource_id);
if (it == resources_.end())
return;
it->second->max_amplitude.store(max_amplitude, std::memory_order_relaxed);
}
void AudioMixer::SetAmplitudeInc(uint64_t resource_id, float amplitude_inc) {
auto it = resources_.find(resource_id);
if (it == resources_.end())
return;
it->second->amplitude_inc.store(amplitude_inc, std::memory_order_relaxed);
}
void AudioMixer::SetEndCallback(uint64_t resource_id, base::Closure cb) {
auto it = resources_.find(resource_id);
if (it == resources_.end())
return;
it->second->end_cb = std::move(cb);
} }
void AudioMixer::Suspend() { void AudioMixer::Suspend() {
audio_device_->Suspend(); audio_sink_->Suspend();
} }
void AudioMixer::Resume() { void AudioMixer::Resume() {
audio_device_->Resume(); audio_sink_->Resume();
} }
size_t AudioMixer::GetHardwareSampleRate() { size_t AudioMixer::GetHardwareSampleRate() {
return audio_device_->GetHardwareSampleRate(); return audio_sink_->GetHardwareSampleRate();
} }
void AudioMixer::RenderAudio(float* output_buffer, size_t num_frames) { void AudioMixer::RenderAudio(float* output_buffer, size_t num_frames) {
{ {
std::unique_lock<std::mutex> scoped_lock(lock_, std::try_to_lock); std::unique_lock<std::mutex> scoped_lock(lock_, std::try_to_lock);
if (scoped_lock) if (scoped_lock)
inputs_[1].splice(inputs_[1].end(), inputs_[0]); play_list_[1].splice(play_list_[1].end(), play_list_[0]);
} }
memset(output_buffer, 0, sizeof(float) * num_frames * kChannelCount); memset(output_buffer, 0, sizeof(float) * num_frames * kChannelCount);
for (auto it = inputs_[1].begin(); it != inputs_[1].end();) { for (auto it = play_list_[1].begin(); it != play_list_[1].end();) {
auto* audio_bus = (*it)->GetAudioBus().get(); auto audio_bus = it->get()->audio_bus.get();
unsigned flags = (*it)->GetFlags(); unsigned flags = it->get()->flags.load(std::memory_order_relaxed);
bool marked_for_removal = false; bool marked_for_removal = false;
if (flags & MixerInput::kStopped) { if (flags & kStopped) {
marked_for_removal = true; marked_for_removal = true;
} else { } else {
const float* src[2] = {audio_bus->GetChannelData(0), const float* src[2] = {audio_bus->GetChannelData(0),
@ -78,89 +190,115 @@ void AudioMixer::RenderAudio(float* output_buffer, size_t num_frames) {
src[1] = src[0]; // mono. src[1] = src[0]; // mono.
size_t num_samples = audio_bus->samples_per_channel(); size_t num_samples = audio_bus->samples_per_channel();
size_t src_index = (*it)->GetSrcIndex(); size_t src_index = it->get()->src_index;
size_t step = (*it)->GetStep(); size_t step = it->get()->step.load(std::memory_order_relaxed);
size_t accumulator = (*it)->GetAccumulator(); size_t accumulator = it->get()->accumulator;
float amplitude = (*it)->GetAmplitude(); float amplitude = it->get()->amplitude;
float amplitude_inc = (*it)->GetAmplitudeInc(); float amplitude_inc =
float max_amplitude = (*it)->GetMaxAmplitude(); it->get()->amplitude_inc.load(std::memory_order_relaxed);
size_t channel_offset = (flags & MixerInput::kSimulateStereo) float max_amplitude =
? audio_bus->sample_rate() / 10 it->get()->max_amplitude.load(std::memory_order_relaxed);
: 0; size_t channel_offset =
(flags & kSimulateStereo) ? audio_bus->sample_rate() / 10 : 0;
DCHECK(num_samples > 0); DCHECK(num_samples > 0);
for (size_t i = 0; i < num_frames * kChannelCount;) { for (size_t i = 0; i < num_frames * kChannelCount;) {
if (src_index < num_samples) { // Mix the 1st channel.
// Mix the 1st channel. output_buffer[i++] += src[0][src_index] * amplitude;
output_buffer[i++] += src[0][src_index] * amplitude;
// Mix the 2nd channel. Offset the source index for stereo simulation. // Mix the 2nd channel. Offset the source index for stereo simulation.
size_t ind = channel_offset + src_index; size_t ind = channel_offset + src_index;
if (ind < num_samples) if (ind < num_samples)
output_buffer[i++] += src[1][ind] * amplitude; output_buffer[i++] += src[1][ind] * amplitude;
else if (flags & MixerInput::kLoop) else if (flags & kLoop)
output_buffer[i++] += src[1][ind % num_samples] * amplitude; output_buffer[i++] += src[1][ind % num_samples] * amplitude;
else else
i++; i++;
// Apply amplitude modification. // Apply amplitude modification.
amplitude += amplitude_inc; amplitude += amplitude_inc;
if (amplitude <= 0) { if (amplitude <= 0) {
marked_for_removal = true;
break;
} else if (amplitude > max_amplitude) {
amplitude = max_amplitude;
}
// Advance source index. Apply basic resampling for variations.
accumulator += step;
src_index += accumulator / 100;
accumulator %= 100;
// Remove, loop or stream if the source data is consumed
if (src_index >= num_samples) {
src_index %= num_samples;
if (audio_bus->EndOfStream()) {
marked_for_removal = true; marked_for_removal = true;
break; break;
} else if (amplitude > max_amplitude) {
amplitude = max_amplitude;
} }
// Advance source index. Apply basic resampling for variations. if (!it->get()->streaming_in_progress.load(
accumulator += step; std::memory_order_acquire)) {
src_index += accumulator / 100; it->get()->streaming_in_progress.store(true,
accumulator %= 100; std::memory_order_relaxed);
} else {
if (audio_bus->EndOfStream()) {
if (!(flags & MixerInput::kLoop))
marked_for_removal = true;
else
src_index %= num_samples;
break;
}
if ((*it)->OnMoreData(!!(flags & MixerInput::kLoop))) { // Swap buffers and start streaming in background.
src_index %= num_samples; audio_bus->SwapBuffers();
src[0] = audio_bus->GetChannelData(0); src[0] = audio_bus->GetChannelData(0);
src[1] = audio_bus->GetChannelData(1); src[1] = audio_bus->GetChannelData(1);
if (!src[1]) if (!src[1])
src[1] = src[0]; // mono. src[1] = src[0]; // mono.
num_samples = audio_bus->samples_per_channel(); num_samples = audio_bus->samples_per_channel();
ThreadPool::Get().PostTask(
HERE,
std::bind(&AudioMixer::DoStream, this, *it, flags & kLoop));
} else { } else {
DLOG(0) << "Mixer buffer underrun!"; DLOG << "Mixer buffer underrun!";
} }
} }
} }
// Remember last sample position and volume. it->get()->src_index = src_index;
(*it)->SetPosition(src_index, accumulator); it->get()->accumulator = accumulator;
(*it)->SetAmplitude(amplitude); it->get()->amplitude = amplitude;
} }
if (marked_for_removal) { if (marked_for_removal) {
removed_inputs_.push_back(*it); end_list_.push_back(*it);
it = inputs_[1].erase(it); it = play_list_[1].erase(it);
} else { } else {
++it; ++it;
} }
} }
for (auto it = removed_inputs_.begin(); it != removed_inputs_.end();) { for (auto it = end_list_.begin(); it != end_list_.end();) {
if (!(*it)->IsStreamingInProgress()) { if (!it->get()->streaming_in_progress.load(std::memory_order_relaxed)) {
main_thread_task_runner_->PostTask( main_thread_task_runner_->PostTask(
HERE, std::bind(&MixerInput::OnRemovedFromMixer, *it)); HERE, std::bind(&AudioMixer::EndCallback, this, *it));
it = removed_inputs_.erase(it); it = end_list_.erase(it);
} else { } else {
++it; ++it;
} }
} }
} }
void AudioMixer::DoStream(std::shared_ptr<Resource> resource, bool loop) {
resource->audio_bus->Stream(loop);
resource->streaming_in_progress.store(false, std::memory_order_release);
}
void AudioMixer::EndCallback(std::shared_ptr<Resource> resource) {
resource->active = false;
if (resource->end_cb)
resource->end_cb();
if (resource->restart_cb) {
resource->restart_cb();
resource->restart_cb = nullptr;
}
}
} // namespace eng } // namespace eng

Some files were not shown because too many files have changed in this diff Show More