aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt8
-rw-r--r--README.md14
-rwxr-xr-xandroid_sample/AndroidManifest.xml45
-rw-r--r--android_sample/CMakeLists.txt53
-rw-r--r--android_sample/assets/amber/compute_ssbo.amber54
-rw-r--r--android_sample/assets/amber/compute_ssbo.amber.vk_shader_0.spvbin0 -> 1996 bytes
-rw-r--r--android_sample/jni/amber_script.cc157
-rw-r--r--android_sample/jni/amber_script.h75
-rw-r--r--android_sample/jni/main.cc106
-rwxr-xr-xandroid_sample/res/values/strings.xml19
-rw-r--r--src/CMakeLists.txt4
-rw-r--r--src/vulkan/CMakeLists.txt4
-rwxr-xr-xtools/build-amber-sample.sh120
13 files changed, 653 insertions, 6 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 01a1360..f0bdb54 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -77,8 +77,10 @@ if (${AMBER_ENABLE_SPIRV_TOOLS})
include_directories("${PROJECT_SOURCE_DIR}/third_party/spirv-tools/include")
endif()
-include(src/dawn/find_dawn.cmake)
-include(src/vulkan/find_vulkan.cmake)
+if (NOT ANDROID)
+ include(src/dawn/find_dawn.cmake)
+ include(src/vulkan/find_vulkan.cmake)
+endif()
add_definitions(-DAMBER_ENGINE_VULKAN=$<BOOL:${Vulkan_FOUND}>)
add_definitions(-DAMBER_ENGINE_DAWN=$<BOOL:${Dawn_FOUND}>)
@@ -187,6 +189,6 @@ endfunction()
add_subdirectory(third_party)
add_subdirectory(src)
-if (${AMBER_ENABLE_SAMPLES})
+if (${AMBER_ENABLE_SAMPLES} AND NOT ANDROID)
add_subdirectory(samples)
endif()
diff --git a/README.md b/README.md
index d75bcaa..c95b297 100644
--- a/README.md
+++ b/README.md
@@ -42,6 +42,20 @@ cmake -GNinja ../..
ninja
```
+### Android
+
+* Android build needs Android SDK 28, Android NDK 16, Java 8. If you prefer
+ other versions of Android SDK, Android NDK, Java, then you can change
+ `ANDROID_PLATFORM` and `ANDROID_BUILD_TOOL_VERSION` in
+ `tools/build-amber-sample.sh`.
+* Set up Android SDK path by running
+ `export ANDROID_SDK_HOME=path/to/Android/SDK` in your shell.
+* Set up Android NDK path by running
+ `export ANDROID_NDK_HOME=path/to/Android/NDK` in your shell.
+* Generate a KeyStore using `keytool` command and set up `KEY_STORE_PATH`
+ env variable for the KeyStore file path.
+* Run `./tools/build-amber-sample.sh [build output directory path]`.
+
### Optional Components
Amber, by default, enables testing, SPIRV-Tools and Shaderc. Each of these can
diff --git a/android_sample/AndroidManifest.xml b/android_sample/AndroidManifest.xml
new file mode 100755
index 0000000..a6c1f9d
--- /dev/null
+++ b/android_sample/AndroidManifest.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2019 The Amber Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<!-- BEGIN_INCLUDE(manifest) -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.google.amber">
+
+ <!-- This is the platform API where NativeActivity was introduced. -->
+ <uses-sdk />
+
+ <!-- This .apk has no Java code itself, so set hasCode to false. -->
+ <application android:label="@string/app_name"
+ android:hasCode="false"
+ android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+
+ <!-- Our activity is the built-in NativeActivity framework class.
+ This will take care of integrating with our NDK code. -->
+ <activity android:name="android.app.NativeActivity"
+ android:label="@string/app_name"
+ android:configChanges="orientation|keyboardHidden">
+ <!-- Tell NativeActivity the name of or .so -->
+ <meta-data android:name="android.app.lib_name"
+ android:value="amber_android" />
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
+<!-- END_INCLUDE(manifest) -->
diff --git a/android_sample/CMakeLists.txt b/android_sample/CMakeLists.txt
new file mode 100644
index 0000000..1853771
--- /dev/null
+++ b/android_sample/CMakeLists.txt
@@ -0,0 +1,53 @@
+# Copyright 2019 The Amber Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+cmake_minimum_required(VERSION 3.1)
+
+# build native_app_glue as a static lib
+set(APP_GLUE_DIR ${ANDROID_NDK}/sources/android/native_app_glue)
+include_directories(${APP_GLUE_DIR})
+add_library(app-glue STATIC
+ ${APP_GLUE_DIR}/android_native_app_glue.c)
+
+# build vulkan app
+set(SRC_DIR jni)
+
+add_library(amber_android SHARED
+ ${SRC_DIR}/main.cc
+ ${SRC_DIR}/amber_script.cc)
+
+include_directories(${SRC_DIR})
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror \
+ -DVK_USE_PLATFORM_ANDROID_KHR")
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} \
+ -u ANativeActivity_onCreate")
+
+# Begin: include Amber
+set(AMBER_DIR ${CMAKE_SOURCE_DIR}/..)
+set(AMBER_SKIP_TESTS TRUE)
+set(AMBER_SKIP_SPIRV_TOOLS TRUE)
+set(Vulkan_FOUND TRUE)
+set(Dawn_FOUND FALSE)
+
+include_directories(${AMBER_DIR}/include)
+message("Android Amber adds header path: ${AMBER_DIR}/include")
+include_directories(${AMBER_DIR})
+message("Android Amber adds header path: ${AMBER_DIR}")
+
+add_subdirectory(${AMBER_DIR} amber_vulkan)
+target_link_libraries(amber_android libamber vulkan)
+# End: include Amber
+
+target_link_libraries(amber_android app-glue log android)
diff --git a/android_sample/assets/amber/compute_ssbo.amber b/android_sample/assets/amber/compute_ssbo.amber
new file mode 100644
index 0000000..5aa5942
--- /dev/null
+++ b/android_sample/assets/amber/compute_ssbo.amber
@@ -0,0 +1,54 @@
+# Copyright 2019 The Amber Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+[compute shader]
+#version 430
+
+layout(set = 0, binding = 0) buffer block0 {
+ float data_set0_binding0[3];
+};
+
+layout(set = 1, binding = 2) buffer block1 {
+ float data_set1_binding2[3];
+};
+
+layout(set = 2, binding = 1) buffer block2 {
+ float data_set2_binding1[3];
+};
+
+layout(set = 2, binding = 3) buffer block3 {
+ float data_set2_binding3[3];
+};
+
+void main() {
+ const uint index = gl_WorkGroupID.x;
+ data_set0_binding0[index] = data_set0_binding0[index] + 1.0f;
+ data_set1_binding2[index] = data_set2_binding1[index] -
+ data_set1_binding2[index];
+ data_set2_binding1[index] = 10.0f * data_set2_binding3[index] +
+ data_set2_binding1[index];
+ data_set2_binding3[index] = 30.0f * data_set2_binding3[index];
+}
+
+[test]
+ssbo 0:0 subdata vec3 0 1.0 2.0 3.0
+ssbo 1:2 subdata vec3 0 4.0 5.0 6.0
+ssbo 2:1 subdata vec3 0 21.0 22.0 23.0
+ssbo 2:3 subdata vec3 0 0.7 0.8 0.9
+compute 3 1 1
+
+probe ssbo vec3 0:0 0 ~= 2.0 3.0 4.0
+probe ssbo vec3 1:2 0 ~= 17.0 17.0 17.0
+probe ssbo vec3 2:1 0 ~= 28.0 30.0 32.0
+probe ssbo vec3 2:3 0 ~= 21.0 24.0 27.0
diff --git a/android_sample/assets/amber/compute_ssbo.amber.vk_shader_0.spv b/android_sample/assets/amber/compute_ssbo.amber.vk_shader_0.spv
new file mode 100644
index 0000000..535357f
--- /dev/null
+++ b/android_sample/assets/amber/compute_ssbo.amber.vk_shader_0.spv
Binary files differ
diff --git a/android_sample/jni/amber_script.cc b/android_sample/jni/amber_script.cc
new file mode 100644
index 0000000..2d00716
--- /dev/null
+++ b/android_sample/jni/amber_script.cc
@@ -0,0 +1,157 @@
+// Copyright 2019 The Amber Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "amber_script.h"
+
+#include "src/make_unique.h"
+
+namespace amber {
+namespace android {
+namespace {
+
+const char kAmberDir[] = "amber/";
+const char kAmberScriptExtension[] = ".amber";
+const char kShaderNameSignature[] = ".vk_shader_";
+const char kShaderExtension[] = ".spv";
+
+bool IsEndedWith(const std::string& path, const std::string& end) {
+ const size_t path_size = path.size();
+ const size_t end_size = end.size();
+ if (path_size < end_size)
+ return false;
+
+ return path.compare(path_size - end_size, end_size, end) == 0;
+}
+
+bool IsStartedWith(const std::string& path, const std::string& start) {
+ const size_t path_size = path.size();
+ const size_t start_size = start.size();
+ if (path_size < start_size)
+ return false;
+
+ return path.compare(0, start_size, start) == 0;
+}
+
+std::string GetShaderID(const std::string& shader_name) {
+ size_t spv_extension_pos = shader_name.find_last_of('.');
+ if (spv_extension_pos == std::string::npos)
+ return std::string();
+
+ size_t shader_id_pos =
+ shader_name.find_last_of('.', spv_extension_pos - 1UL) + 1UL;
+ if (shader_id_pos == std::string::npos)
+ return std::string();
+
+ if (shader_id_pos >= spv_extension_pos || shader_name.size() <= shader_id_pos)
+ return std::string();
+
+ return shader_name.substr(shader_id_pos, spv_extension_pos - shader_id_pos);
+}
+
+} // namespace
+
+AmberScriptLoader::AmberScriptLoader(android_app* app) : app_context_(app) {}
+
+AmberScriptLoader::~AmberScriptLoader() = default;
+
+Result AmberScriptLoader::LoadAllScriptsFromAsset() {
+ auto shader_names = FindAllScriptsAndReturnShaderNames();
+ if (script_info_.empty())
+ return Result("No Amber script found");
+
+ for (auto& info : script_info_) {
+ info.script_content = ReadScript(info.asset_name);
+ if (info.script_content.empty())
+ return Result(info.asset_name + ":\n\tEmpty Amber script");
+ }
+
+ for (auto& info : script_info_) {
+ for (const auto& shader : shader_names) {
+ if (!IsStartedWith(shader, info.asset_name + kShaderNameSignature))
+ continue;
+
+ auto shader_content = ReadSpvShader(shader);
+ if (shader_content.empty())
+ return Result(shader + ":\n\tEmpty shader");
+
+ auto id = GetShaderID(shader);
+ if (id.empty())
+ return Result(shader + ":\n\tFail to get shader ID");
+
+ info.shader_map[id] = shader_content;
+ }
+ }
+
+ return {};
+}
+
+std::vector<std::string>
+AmberScriptLoader::FindAllScriptsAndReturnShaderNames() {
+ std::vector<std::string> shaders;
+
+ AAssetDir* asset =
+ AAssetManager_openDir(app_context_->activity->assetManager, kAmberDir);
+ for (const char* file_name = AAssetDir_getNextFileName(asset); file_name;
+ file_name = AAssetDir_getNextFileName(asset)) {
+ std::string file_name_in_string(file_name);
+ if (IsEndedWith(file_name_in_string, kAmberScriptExtension)) {
+ script_info_.emplace_back();
+ script_info_.back().asset_name = file_name_in_string;
+ }
+
+ if (IsEndedWith(file_name_in_string, kShaderExtension))
+ shaders.push_back(file_name_in_string);
+ }
+ AAssetDir_close(asset);
+
+ return shaders;
+}
+
+std::vector<uint8_t> AmberScriptLoader::ReadContent(
+ const std::string& asset_name) {
+ auto asset_path = kAmberDir + asset_name;
+ AAsset* asset = AAssetManager_open(app_context_->activity->assetManager,
+ asset_path.c_str(), AASSET_MODE_BUFFER);
+ if (!asset)
+ return std::vector<uint8_t>();
+
+ size_t size_in_bytes = AAsset_getLength(asset);
+
+ // Allocate a memory chunk whose size in bytes is |size_in_bytes|.
+ std::vector<uint8_t> content(size_in_bytes);
+
+ AAsset_read(asset, content.data(), size_in_bytes);
+ AAsset_close(asset);
+
+ return content;
+}
+
+std::string AmberScriptLoader::ReadScript(const std::string& script_name) {
+ auto content = ReadContent(script_name);
+ return std::string(reinterpret_cast<char*>(content.data()));
+}
+
+std::vector<uint32_t> AmberScriptLoader::ReadSpvShader(
+ const std::string& shader_name) {
+ auto content = ReadContent(shader_name);
+ if (content.size() % sizeof(uint32_t) != 0)
+ return std::vector<uint32_t>();
+
+ return std::vector<uint32_t>(
+ reinterpret_cast<uint32_t*>(content.data()),
+ reinterpret_cast<uint32_t*>(content.data() + content.size()));
+}
+
+} // namespace android
+} // namespace amber
diff --git a/android_sample/jni/amber_script.h b/android_sample/jni/amber_script.h
new file mode 100644
index 0000000..495555b
--- /dev/null
+++ b/android_sample/jni/amber_script.h
@@ -0,0 +1,75 @@
+// Copyright 2019 The Amber Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef ANDROID_AMBER_SCRIPT_H_
+#define ANDROID_AMBER_SCRIPT_H_
+
+#include <android_native_app_glue.h>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "amber/amber.h"
+#include "amber/result.h"
+
+namespace amber {
+namespace android {
+
+struct AmberScriptInfo {
+ std::string asset_name; // Script asset name. Note it is not a
+ // path and just the name of script file.
+ std::string script_content; // Script itself from the script file.
+ amber::ShaderMap shader_map;
+};
+
+// A class to load scripts for Amber under assets/amber/ into
+// |script_info_|. We assume that file extension of those scripts
+// is ".amber" and all files with the extension are scripts for
+// Amber.
+class AmberScriptLoader {
+ public:
+ explicit AmberScriptLoader(android_app* app);
+ ~AmberScriptLoader();
+
+ Result LoadAllScriptsFromAsset();
+ const std::vector<AmberScriptInfo>& GetScripts() const {
+ return script_info_;
+ }
+
+ private:
+ // Find all files with ".amber" extension and set |asset_name| of
+ // |script_info_| as their names. In addition, return all shader
+ // file names that have ".spv" extensions.
+ std::vector<std::string> FindAllScriptsAndReturnShaderNames();
+
+ // Return content of script named |script_name| under
+ // assets/amber/ as a std::string.
+ std::string ReadScript(const std::string& script_name);
+
+ // Return SPIRV binary of script named |shader_name| under
+ // assets/amber/ as a std::vector<uint32_t>.
+ std::vector<uint32_t> ReadSpvShader(const std::string& shader_name);
+
+ // Return content of asset named |asset_name| under assets/amber/
+ // as a std::vector<uint8_t>.
+ std::vector<uint8_t> ReadContent(const std::string& asset_name);
+
+ android_app* app_context_ = nullptr;
+ std::vector<AmberScriptInfo> script_info_;
+};
+
+} // namespace android
+} // namespace amber
+
+#endif // ANDROID_AMBER_SCRIPT_H_
diff --git a/android_sample/jni/main.cc b/android_sample/jni/main.cc
new file mode 100644
index 0000000..77d3157
--- /dev/null
+++ b/android_sample/jni/main.cc
@@ -0,0 +1,106 @@
+// Copyright 2019 The Amber Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <android/log.h>
+#include <android_native_app_glue.h>
+
+#include "amber/amber.h"
+#include "amber/recipe.h"
+#include "amber/result.h"
+#include "amber_script.h"
+
+namespace {
+
+// TODO(jaebaek): Change this as a method rather than macro.
+// Android log function wrappers
+const char* kTAG = "Amber";
+#define LOGE(...) \
+ ((void)__android_log_print(ANDROID_LOG_ERROR, kTAG, __VA_ARGS__))
+
+void amber_sample_main(android_app* app) {
+ amber::android::AmberScriptLoader loader(app);
+
+ amber::Result r = loader.LoadAllScriptsFromAsset();
+ if (!r.IsSuccess()) {
+ LOGE("%s", r.Error().c_str());
+ return;
+ }
+
+ const auto& script_info = loader.GetScripts();
+
+ std::vector<std::string> failures;
+ for (const auto& info : script_info) {
+ LOGE("\ncase %s: run...", info.asset_name.c_str());
+
+ amber::Amber am;
+ amber::Recipe recipe;
+ amber::Result r = am.Parse(info.script_content, &recipe);
+ if (!r.IsSuccess()) {
+ LOGE("\ncase %s: fail\n\t%s", info.asset_name.c_str(), r.Error().c_str());
+ failures.push_back(info.asset_name);
+ continue;
+ }
+
+ amber::Options amber_options;
+ r = am.ExecuteWithShaderData(&recipe, amber_options, info.shader_map);
+ if (!r.IsSuccess()) {
+ LOGE("\ncase %s: fail\n\t%s", info.asset_name.c_str(), r.Error().c_str());
+ failures.push_back(info.asset_name);
+ continue;
+ }
+
+ LOGE("\ncase %s: pass", info.asset_name.c_str());
+ }
+
+ if (!failures.empty()) {
+ LOGE("\nSummary of Failures:");
+ for (const auto& failure : failures)
+ LOGE("%s", failure.c_str());
+ }
+ LOGE("\nsummary: %u pass, %u fail",
+ static_cast<uint32_t>(script_info.size() - failures.size()),
+ static_cast<uint32_t>(failures.size()));
+}
+
+// Process the next main command.
+void handle_cmd(android_app* app, int32_t cmd) {
+ switch (cmd) {
+ case APP_CMD_INIT_WINDOW:
+ amber_sample_main(app);
+ break;
+ case APP_CMD_TERM_WINDOW:
+ break;
+ default:
+ break;
+ }
+}
+
+} // namespace
+
+void android_main(struct android_app* app) {
+ // Set the callback to process system events
+ app->onAppCmd = handle_cmd;
+
+ // Used to poll the events in the main loop
+ int events;
+ android_poll_source* source;
+
+ // Main loop
+ while (app->destroyRequested == 0) {
+ if (ALooper_pollAll(1, nullptr, &events, (void**)&source) >= 0) {
+ if (source != NULL)
+ source->process(app, source);
+ }
+ }
+}
diff --git a/android_sample/res/values/strings.xml b/android_sample/res/values/strings.xml
new file mode 100755
index 0000000..c34222d
--- /dev/null
+++ b/android_sample/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2019 The Amber Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<resources>
+ <string name="app_name">AmberSample</string>
+</resources>
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 67184d9..ad4aa2f 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -100,8 +100,8 @@ if (${AMBER_ENABLE_TESTS})
add_executable(amber_unittests ${TEST_SRCS})
if (NOT MSVC)
- target_compile_options(amber_unittests PRIVATE
- -Wno-global-constructors)
+ target_compile_options(amber_unittests PRIVATE
+ -Wno-global-constructors)
endif()
target_include_directories(amber_unittests PRIVATE
diff --git a/src/vulkan/CMakeLists.txt b/src/vulkan/CMakeLists.txt
index aa8bc74..164cf0a 100644
--- a/src/vulkan/CMakeLists.txt
+++ b/src/vulkan/CMakeLists.txt
@@ -36,7 +36,9 @@ amber_default_compile_options(libamberenginevulkan)
set_target_properties(libamberenginevulkan PROPERTIES
OUTPUT_NAME "amberenginevulkan"
)
-target_link_libraries(libamberenginevulkan ${VULKAN_LIB})
+if (NOT ANDROID)
+ target_link_libraries(libamberenginevulkan ${VULKAN_LIB})
+endif()
if(${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang")
# vulkan/vulkan.h defines VK_NULL_HANDLE as 0u and that also serves as a null pointer.
diff --git a/tools/build-amber-sample.sh b/tools/build-amber-sample.sh
new file mode 100755
index 0000000..7d9fb0a
--- /dev/null
+++ b/tools/build-amber-sample.sh
@@ -0,0 +1,120 @@
+#!/bin/bash
+
+# Copyright 2019 The Amber Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set -x
+
+if [[ $1 == "" ]]; then
+ echo "Usage: $0 [build directory]"
+ exit 1
+fi
+
+BUILD_DIR=$(readlink -f $1)
+if [[ $(ls $BUILD_DIR 2> /dev/null) == "" ]]; then
+ mkdir -p $BUILD_DIR
+fi
+
+if [[ $ANDROID_SDK_HOME == "" ]]; then
+ echo "Error: ANDROID_SDK_HOME missing, please set env variable e.g.,"
+ echo " $ export ANDROID_SDK_HOME=path/to/Android/SDK"
+ exit 1
+fi
+
+if [[ $ANDROID_NDK_HOME == "" ]]; then
+ echo "Error: ANDROID_NDK_HOME missing, please set env variable e.g.,"
+ echo " $ export ANDROID_NDK_HOME=path/to/Android/NDK"
+ exit 1
+fi
+
+if [[ $(command -v javac) == "" ]]; then
+ echo "Error: Install Java. Recommended version is Java 8."
+ exit 1
+fi
+
+if [[ $KEY_STORE_PATH == "" ]]; then
+ echo "Error: KEY_STORE_PATH missing, please set env variable."
+ exit 1
+fi
+
+ANDROID_SOURCE_DIR=$(dirname $(readlink -f $0))/../android_sample
+
+APK_NAME=AmberSample.apk
+ANDROID_PLATFORM=android-28
+ANDROID_BUILD_TOOL_VERSION=28.0.0
+ABI=arm64-v8a
+BUILD_TYPE=Release
+
+AAPT=$ANDROID_SDK_HOME/build-tools/$ANDROID_BUILD_TOOL_VERSION/aapt
+AAPT_ADD="$AAPT add"
+AAPT_PACK="$AAPT package -f -I
+ $ANDROID_SDK_HOME/platforms/$ANDROID_PLATFORM/android.jar"
+
+DX="$ANDROID_SDK_HOME/build-tools/$ANDROID_BUILD_TOOL_VERSION/dx --dex"
+
+JAVAC="javac -classpath
+ $ANDROID_SDK_HOME/platforms/$ANDROID_PLATFORM/android.jar
+ -sourcepath $BUILD_DIR/gen -d $BUILD_DIR"
+
+mkdir -p $BUILD_DIR/gen $BUILD_DIR/output/lib/$ABI $BUILD_DIR/$BUILD_TYPE
+
+pushd $BUILD_DIR/$BUILD_TYPE
+cmake \
+ -DANDROID_ABI=$ABI \
+ -DANDROID_PLATFORM=$ANDROID_PLATFORM \
+ -DCMAKE_LIBRARY_OUTPUT_DIRECTORY=$BUILD_DIR/output/lib/$ABI \
+ -DCMAKE_BUILD_TYPE=$BUILD_TYPE \
+ -DANDROID_NDK=$ANDROID_NDK_HOME \
+ -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake \
+ -DCMAKE_MAKE_PROGRAM=$(which ninja) \
+ -GNinja \
+ -DANDROID_TOOLCHAIN=clang \
+ -DANDROID_STL=c++_static \
+ $ANDROID_SOURCE_DIR
+ninja
+popd
+
+ANDROID_VULKAN=$ANDROID_NDK_HOME/sources/third_party/vulkan
+for f in $(find $ANDROID_VULKAN/src/build-android/jniLibs/$ABI/ -name '*.so')
+do
+ LINK=$BUILD_DIR/output/lib/$ABI/$(basename $f)
+ if [[ $(ls $LINK 2> /dev/null) == "" ]]; then
+ ln -s $f $LINK
+ fi
+done
+
+$AAPT_PACK --non-constant-id -m \
+ -M $ANDROID_SOURCE_DIR/AndroidManifest.xml \
+ -S $ANDROID_SOURCE_DIR/res \
+ -J $BUILD_DIR/gen/ \
+ --generate-dependencies
+
+$AAPT_PACK -m \
+ -M $ANDROID_SOURCE_DIR/AndroidManifest.xml \
+ -A $ANDROID_SOURCE_DIR/assets \
+ -S $ANDROID_SOURCE_DIR/res \
+ -J "$BUILD_DIR/gen" \
+ -F "$BUILD_DIR/$APK_NAME" \
+ --shared-lib $BUILD_DIR/output
+
+$JAVAC $BUILD_DIR/gen/com/google/amber/*.java
+$DX --output="$BUILD_DIR/classes.dex" $BUILD_DIR
+
+cd $BUILD_DIR
+$AAPT_ADD $APK_NAME classes.dex
+
+$ANDROID_SDK_HOME/build-tools/$ANDROID_BUILD_TOOL_VERSION/apksigner sign \
+--min-sdk-version 28 --ks $KEY_STORE_PATH $APK_NAME
+
+echo "Successfully built $BUILD_DIR/$APK_NAME"