diff --git a/.eslintrc.js b/.eslintrc.js
index b4d88777f2..54ff0a1268 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -54,7 +54,11 @@ module.exports = {
"Window": false,
"XMLHttpRequest": false,
"location": false,
- "print": false
+ "print": false,
+ "RayPick": false,
+ "LaserPointers": false,
+ "ContextOverlay": false,
+ "module": false
},
"rules": {
"brace-style": ["error", "1tbs", { "allowSingleLine": false }],
diff --git a/.gitattributes b/.gitattributes
index 406780d20a..4a06c4288a 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -10,6 +10,7 @@
*.json text
*.js text
*.qml text
+*.qrc text
*.slf text
*.slh text
*.slv text
diff --git a/.gitignore b/.gitignore
index d6227f1f30..8aa82865a4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,11 @@ ext/
Makefile
*.user
+# Android Studio
+*.iml
+local.properties
+android/libraries
+
# Xcode
*.xcodeproj
*.xcworkspace
diff --git a/BUILD.md b/BUILD.md
index 30302d611b..4d321146c3 100644
--- a/BUILD.md
+++ b/BUILD.md
@@ -2,7 +2,7 @@
- [cmake](https://cmake.org/download/): 3.9
- [Qt](https://www.qt.io/download-open-source): 5.9.1
-- [OpenSSL](https://www.openssl.org/): Use the latest available version of OpenSSL to avoid security vulnerabilities.
+- [OpenSSL](https://www.openssl.org/): Use the latest available 1.0 version (**NOT** 1.1) of OpenSSL to avoid security vulnerabilities.
- [VHACD](https://github.com/virneo/v-hacd)(clone this repository)(Optional)
### CMake External Project Dependencies
diff --git a/BUILD_ANDROID.md b/BUILD_ANDROID.md
index d69d20ee8a..cc51e58b1d 100644
--- a/BUILD_ANDROID.md
+++ b/BUILD_ANDROID.md
@@ -1,19 +1,56 @@
Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only Android specific instructions are found in this file.
-### Android Dependencies
+# Android Dependencies
You will need the following tools to build our Android targets.
-* [cmake](http://www.cmake.org/download/) ~> 3.5.1
-* [Qt](http://www.qt.io/download-open-source/#) ~> 5.6.2
-* [ant](http://ant.apache.org/bindownload.cgi) ~> 1.9.4
-* [Android NDK](https://developer.android.com/tools/sdk/ndk/index.html) ~> r10d
-* [Android SDK](http://developer.android.com/sdk/installing/index.html) ~> 24.4.1.1
- * Install the latest Platform-tools
- * Install the latest Build-tools
- * Install the SDK Platform for API Level 19
- * Install Sources for Android SDK for API Level 19
- * Install the ARM EABI v7a System Image if you want to run an emulator.
+* [Qt](http://www.qt.io/download-open-source/#) ~> 5.9.1
+* [Android Studio](https://developer.android.com/studio/index.html)
+* [Google VR SDK](https://github.com/googlevr/gvr-android-sdk/releases)
+* [Gradle](https://gradle.org/releases/)
+
+### Qt
+
+Download the Qt online installer. Run the installer and select the android_armv7 binaries. Installing to the default path is recommended
+
+### Android Studio
+
+Download the Android Studio installer and run it. Once installed, at the welcome screen, click configure in the lower right corner and select SDK manager
+
+From the SDK Platforms tab, select API level 26.
+
+* Install the ARM EABI v7a System Image if you want to run an emulator.
+
+From the SDK Tools tab select the following
+
+* Android SDK Build-Tools
+* GPU Debugging Tools
+* CMake (even if you have a separate CMake installation)
+* LLDB
+* Android SDK Platform-Tools
+* Android SDK Tools
+* Android SDK Tools
+* NDK (even if you have the NDK installed separately)
+
+### Google VR SDK
+
+Download the 1.8 Google VR SDK [release](https://github.com/googlevr/gvr-android-sdk/archive/v1.80.0.zip). Unzip the archive to a location on your drive.
+
+### Gradle
+
+Download [Gradle 4.1](https://services.gradle.org/distributions/gradle-4.1-all.zip) and unzip it on your local drive. You may wish to add the location of the bin directory within the archive to your path
+
+#### Set up machine specific Gradle properties
+
+Create a `gradle.properties` file in ~/.gradle. Edit the file to contain the following
+
+ QT5_ROOT=C\:\\Qt\\5.9.1\\android_armv7
+ GVR_ROOT=C\:\\Android\\gvr-android-sdk
+
+Replace the paths with your local installations of Qt5 and the Google VR SDK
+
+
+# TODO fix the rest
You will also need to cross-compile the dependencies required for all platforms for Android, and help CMake find these compiled libraries on your machine.
diff --git a/BUILD_OSX.md b/BUILD_OSX.md
index 3365627b8c..6b66863534 100644
--- a/BUILD_OSX.md
+++ b/BUILD_OSX.md
@@ -1,29 +1,28 @@
-Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only OS X specific instructions are found in this file.
+Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only macOS specific instructions are found in this file.
### Homebrew
-[Homebrew](https://brew.sh/) is an excellent package manager for OS X. It makes install of some High Fidelity dependencies very simple.
+[Homebrew](https://brew.sh/) is an excellent package manager for macOS. It makes install of some High Fidelity dependencies very simple.
- brew tap homebrew/versions
- brew install cmake openssl
+ brew install cmake openssl qt
### OpenSSL
Assuming you've installed OpenSSL using the homebrew instructions above, you'll need to set OPENSSL_ROOT_DIR so CMake can find your installations.
For OpenSSL installed via homebrew, set OPENSSL_ROOT_DIR:
- export OPENSSL_ROOT_DIR=/usr/local/Cellar/openssl/1.0.2h_1/
+ export OPENSSL_ROOT_DIR=/usr/local/Cellar/openssl/1.0.2l
Note that this uses the version from the homebrew formula at the time of this writing, and the version in the path will likely change.
### Qt
-Download and install the [Qt 5.6.2 for macOS](http://download.qt.io/official_releases/qt/5.6/5.6.2/qt-opensource-mac-x64-clang-5.6.2.dmg).
+Assuming you've installed Qt using the homebrew instructions above, you'll need to set QT_CMAKE_PREFIX_PATH so CMake can find your installations.
+For Qt installed via homebrew, set QT_CMAKE_PREFIX_PATH:
-Keep the default components checked when going through the installer.
+ export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt/5.9.1/lib/cmake
-Once Qt is installed, you need to manually configure the following:
-* Set the QT_CMAKE_PREFIX_PATH environment variable to your `Qt5.6.2/5.6/clang_64/lib/cmake/` directory.
+Note that this uses the version from the homebrew formula at the time of this writing, and the version in the path will likely change.
### Xcode
diff --git a/BUILD_WIN.md b/BUILD_WIN.md
index 3e93656d45..eea1f85e5b 100644
--- a/BUILD_WIN.md
+++ b/BUILD_WIN.md
@@ -3,7 +3,7 @@ This is a stand-alone guide for creating your first High Fidelity build for Wind
## Building High Fidelity
Note: We are now using Visual Studio 2017 and Qt 5.9.1. If you are upgrading from Visual Studio 2013 and Qt 5.6.2, do a clean uninstall of those versions before going through this guide.
-Note: The prerequisites will require about 10 GB of space on your drive.
+Note: The prerequisites will require about 10 GB of space on your drive. You will also need a system with at least 8GB of main memory.
### Step 1. Visual Studio 2017
@@ -27,11 +27,20 @@ Go to `Control Panel > System > Advanced System Settings > Environment Variables
* Set "Variable name": `QT_CMAKE_PREFIX_PATH`
* Set "Variable value": `C:\Qt\5.9.1\msvc2017_64\lib\cmake`
-### Step 5. Installing OpenSSL
+### Step 5. Installing [vcpkg](https://github.com/Microsoft/vcpkg)
-Download and install the Win64 OpenSSL v1.0.2 Installer[https://slproweb.com/products/Win32OpenSSL.html].
+ * Clone the VCPKG [repository](https://github.com/Microsoft/vcpkg)
+ * Follow the instructions in the [readme](https://github.com/Microsoft/vcpkg/blob/master/README.md) to bootstrap vcpkg
+ * Note, you may need to do these in a _Developer Command Prompt_
+ * Set an environment variable VCPKG_ROOT to the location of the cloned repository
+ * Close and re-open any command prompts after setting the environment variable so that they will pick up the change
-### Step 6. Running CMake to Generate Build Files
+### Step 6. Installing OpenSSL via vcpkg
+
+ * In the vcpkg directory, install the 64 bit OpenSSL package with the command `vcpkg install openssl:x64-windows`
+ * Once the build completes you should have a file `ssl.h` in `${VCPKG_ROOT}/installed/x64-windows/include/openssl`
+
+### Step 7. Running CMake to Generate Build Files
Run Command Prompt from Start and run the following commands:
```
@@ -43,7 +52,7 @@ cmake .. -G "Visual Studio 15 Win64"
Where `%HIFI_DIR%` is the directory for the highfidelity repository.
-### Step 7. Making a Build
+### Step 8. Making a Build
Open `%HIFI_DIR%\build\hifi.sln` using Visual Studio.
@@ -51,7 +60,7 @@ Change the Solution Configuration (next to the green play button) from "Debug" t
Run `Build > Build Solution`.
-### Step 8. Testing Interface
+### Step 9. Testing Interface
Create another environment variable (see Step #4)
* Set "Variable name": `_NO_DEBUG_HEAP`
@@ -65,16 +74,20 @@ Note: You can also run Interface by launching it from command line or File Explo
## Troubleshooting
-For any problems after Step #6, first try this:
+For any problems after Step #7, first try this:
* Delete your locally cloned copy of the highfidelity repository
* Restart your computer
* Redownload the [repository](https://github.com/highfidelity/hifi)
-* Restart directions from Step #6
+* Restart directions from Step #7
#### CMake gives you the same error message repeatedly after the build fails
Remove `CMakeCache.txt` found in the `%HIFI_DIR%\build` directory.
+#### CMake can't find OpenSSL
+
+Remove `CMakeCache.txt` found in the `%HIFI_DIR%\build` directory. Verify that your VCPKG_ROOT environment variable is set and pointing to the correct location. Verify that the file `${VCPKG_ROOT}/installed/x64-windows/include/openssl/ssl.h` exists.
+
#### Qt is throwing an error
Make sure you have the correct version (5.9.1) installed and `QT_CMAKE_PREFIX_PATH` environment variable is set correctly.
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9712b2d32e..9d3296a168 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,258 +1,95 @@
-if (WIN32)
+# If we're running under the gradle build, HIFI_ANDROID will be set here, but
+# ANDROID will not be set until after the `project` statement. This is the *ONLY*
+# place you need to use `HIFI_ANDROID` instead of `ANDROID`
+if (WIN32 AND NOT HIFI_ANDROID)
cmake_minimum_required(VERSION 3.7)
else()
cmake_minimum_required(VERSION 3.2)
endif()
-if (USE_ANDROID_TOOLCHAIN)
- set(CMAKE_TOOLCHAIN_FILE "${CMAKE_CURRENT_SOURCE_DIR}/cmake/android/android.toolchain.cmake")
- set(ANDROID_NATIVE_API_LEVEL 19)
- set(ANDROID_TOOLCHAIN_NAME arm-linux-androideabi-clang3.5)
- set(ANDROID_STL c++_shared)
-endif ()
-
-if (WIN32)
- cmake_policy(SET CMP0020 NEW)
-endif (WIN32)
-
-if (POLICY CMP0028)
- cmake_policy(SET CMP0028 OLD)
-endif ()
-
-if (POLICY CMP0043)
- cmake_policy(SET CMP0043 OLD)
-endif ()
-
-if (POLICY CMP0042)
- cmake_policy(SET CMP0042 OLD)
-endif ()
-
-set_property(GLOBAL PROPERTY USE_FOLDERS ON)
-set_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER "CMakeTargets")
-
project(hifi)
-add_definitions(-DGLM_FORCE_RADIANS)
-set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG")
-find_package( Threads )
+include("cmake/init.cmake")
-if (WIN32)
- if (NOT "${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
- message( FATAL_ERROR "Only 64 bit builds supported." )
- endif()
+include("cmake/compiler.cmake")
- add_definitions(-DNOMINMAX -D_CRT_SECURE_NO_WARNINGS)
-
- if (NOT WINDOW_SDK_PATH)
- set(DEBUG_DISCOVERED_SDK_PATH TRUE)
- endif()
-
- # sets path for Microsoft SDKs
- # if you get build error about missing 'glu32' this path is likely wrong
- if (MSVC_VERSION GREATER_EQUAL 1910) # VS 2017
- set(WINDOW_SDK_PATH "C:/Program Files (x86)/Windows Kits/10/Lib/${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}/x64" CACHE PATH "Windows SDK PATH")
- elseif (MSVC_VERSION GREATER_EQUAL 1800) # VS 2013
- set(WINDOW_SDK_PATH "C:\\Program Files (x86)\\Windows Kits\\8.1\\Lib\\winv6.3\\um\\${WINDOW_SDK_FOLDER}" CACHE PATH "Windows SDK PATH")
- else()
- message( FATAL_ERROR "Visual Studio 2013 or higher required." )
- endif()
-
- if (DEBUG_DISCOVERED_SDK_PATH)
- message(STATUS "The discovered Windows SDK path is ${WINDOW_SDK_PATH}")
- endif ()
-
- set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${WINDOW_SDK_PATH})
- # /wd4351 disables warning C4351: new behavior: elements of array will be default initialized
- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP /wd4351")
- # /LARGEADDRESSAWARE enables 32-bit apps to use more than 2GB of memory.
- # Caveats: http://stackoverflow.com/questions/2288728/drawbacks-of-using-largeaddressaware-for-32-bit-windows-executables
- # TODO: Remove when building 64-bit.
- set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /LARGEADDRESSAWARE")
- # always produce symbols as PDB files
- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zi")
- set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /DEBUG /OPT:REF /OPT:ICF")
-else ()
- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -fno-strict-aliasing -Wno-unused-parameter")
- if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ggdb -Woverloaded-virtual -Wdouble-promotion")
- if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER "5.1") # gcc 5.1 and on have suggest-override
- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsuggest-override")
- endif ()
- endif ()
-endif(WIN32)
-
-if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
- if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "5.3")
- # GLM 0.9.8 on Ubuntu 14 (gcc 4.4) has issues with the simd declarations
- add_definitions(-DGLM_FORCE_PURE)
- endif()
+if (NOT DEFINED SERVER_ONLY)
+ set(SERVER_ONLY 0)
endif()
-if (NOT ANDROID)
- if ((NOT MSVC12) AND (NOT MSVC14))
- include(CheckCXXCompilerFlag)
- CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11)
- CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X)
-
- if (COMPILER_SUPPORTS_CXX11)
- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
- elseif(COMPILER_SUPPORTS_CXX0X)
- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
- else()
- message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.")
- endif()
- endif ()
-else ()
- # assume that the toolchain selected for android has C++11 support
- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
-endif ()
-
-if (APPLE)
- set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LANGUAGE_STANDARD "c++11")
- set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libc++")
- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --stdlib=libc++")
-endif ()
-
-if (NOT ANDROID_LIB_DIR)
- set(ANDROID_LIB_DIR $ENV{ANDROID_LIB_DIR})
-endif ()
-
-if (ANDROID)
- if (NOT ANDROID_QT_CMAKE_PREFIX_PATH)
- set(QT_CMAKE_PREFIX_PATH ${ANDROID_LIB_DIR}/Qt/5.5/android_armv7/lib/cmake)
- else ()
- set(QT_CMAKE_PREFIX_PATH ${ANDROID_QT_CMAKE_PREFIX_PATH})
- endif ()
-
- set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
- set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib)
-
- if (ANDROID_LIB_DIR)
- list(APPEND CMAKE_FIND_ROOT_PATH ${ANDROID_LIB_DIR})
- endif ()
-else ()
- if (NOT QT_CMAKE_PREFIX_PATH)
- set(QT_CMAKE_PREFIX_PATH $ENV{QT_CMAKE_PREFIX_PATH})
- endif ()
- if (NOT QT_CMAKE_PREFIX_PATH)
- get_filename_component(QT_CMAKE_PREFIX_PATH "${Qt5_DIR}/.." REALPATH)
- endif ()
-endif ()
-
-set(QT_DIR $ENV{QT_DIR})
-
-if (WIN32)
- if (NOT EXISTS ${QT_CMAKE_PREFIX_PATH})
- message(FATAL_ERROR "Could not determine QT_CMAKE_PREFIX_PATH.")
- endif ()
+if (ANDROID OR UWP)
+ set(MOBILE 1)
+else()
+ set(MOBILE 0)
endif()
-# figure out where the qt dir is
-get_filename_component(QT_DIR "${QT_CMAKE_PREFIX_PATH}/../../" ABSOLUTE)
+if (ANDROID OR UWP)
+ option(BUILD_SERVER "Build server components" OFF)
+ option(BUILD_TOOLS "Build tools" OFF)
+else()
+ option(BUILD_SERVER "Build server components" ON)
+ option(BUILD_TOOLS "Build tools" ON)
+endif()
-set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${QT_CMAKE_PREFIX_PATH})
+if (SERVER_ONLY)
+ option(BUILD_CLIENT "Build client components" OFF)
+ option(BUILD_TESTS "Build tests" OFF)
+else()
+ option(BUILD_CLIENT "Build client components" ON)
+ option(BUILD_TESTS "Build tests" ON)
+endif()
-if (APPLE)
+option(BUILD_INSTALLER "Build installer" ON)
- exec_program(sw_vers ARGS -productVersion OUTPUT_VARIABLE OSX_VERSION)
- string(REGEX MATCH "^[0-9]+\\.[0-9]+" OSX_VERSION ${OSX_VERSION})
- message(STATUS "Detected OS X version = ${OSX_VERSION}")
+MESSAGE(STATUS "Build server: " ${BUILD_SERVER})
+MESSAGE(STATUS "Build client: " ${BUILD_CLIENT})
+MESSAGE(STATUS "Build tests: " ${BUILD_TESTS})
+MESSAGE(STATUS "Build tools: " ${BUILD_TOOLS})
+MESSAGE(STATUS "Build installer: " ${BUILD_INSTALLER})
- set(OSX_SDK "${OSX_VERSION}" CACHE String "OS X SDK version to look for inside Xcode bundle or at OSX_SDK_PATH")
+if (UNIX AND DEFINED ENV{HIFI_MEMORY_DEBUGGING})
+ MESSAGE(STATUS "Memory debugging is enabled")
+endif()
- # set our OS X deployment target
- set(CMAKE_OSX_DEPLOYMENT_TARGET 10.8)
-
- # find the SDK path for the desired SDK
- find_path(
- _OSX_DESIRED_SDK_PATH
- NAME MacOSX${OSX_SDK}.sdk
- HINTS ${OSX_SDK_PATH}
- PATHS /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/
- /Applications/Xcode-beta.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/
- )
-
- if (NOT _OSX_DESIRED_SDK_PATH)
- message(STATUS "Could not find OS X ${OSX_SDK} SDK. Will fall back to default. If you want a specific SDK, please pass OSX_SDK and optionally OSX_SDK_PATH to CMake.")
- else ()
- message(STATUS "Found OS X ${OSX_SDK} SDK at ${_OSX_DESIRED_SDK_PATH}/MacOSX${OSX_SDK}.sdk")
-
- # set that as the SDK to use
- set(CMAKE_OSX_SYSROOT ${_OSX_DESIRED_SDK_PATH}/MacOSX${OSX_SDK}.sdk)
- endif ()
-
-endif ()
-
-# Hide automoc folders (for IDEs)
-set(AUTOGEN_TARGETS_FOLDER "hidden/generated")
-
-# Find includes in corresponding build directories
-set(CMAKE_INCLUDE_CURRENT_DIR ON)
-# Instruct CMake to run moc automatically when needed.
-set(CMAKE_AUTOMOC ON)
-# Instruct CMake to run rcc automatically when needed
-set(CMAKE_AUTORCC ON)
-
-set(HIFI_LIBRARY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libraries")
-
-# setup for find modules
-set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/")
-
-if (CMAKE_BUILD_TYPE)
- string(TOUPPER ${CMAKE_BUILD_TYPE} UPPER_CMAKE_BUILD_TYPE)
-else ()
- set(UPPER_CMAKE_BUILD_TYPE DEBUG)
-endif ()
-
-set(HF_CMAKE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
-set(MACRO_DIR "${HF_CMAKE_DIR}/macros")
-set(EXTERNAL_PROJECT_DIR "${HF_CMAKE_DIR}/externals")
-
-file(GLOB HIFI_CUSTOM_MACROS "cmake/macros/*.cmake")
-foreach(CUSTOM_MACRO ${HIFI_CUSTOM_MACROS})
- include(${CUSTOM_MACRO})
-endforeach()
+#
+# Helper projects
+#
+file(GLOB_RECURSE CMAKE_SRC cmake/*.cmake cmake/CMakeLists.txt)
+add_custom_target(cmake SOURCES ${CMAKE_SRC})
+GroupSources("cmake")
+unset(CMAKE_SRC)
file(GLOB_RECURSE JS_SRC scripts/*.js unpublishedScripts/*.js)
add_custom_target(js SOURCES ${JS_SRC})
GroupSources("scripts")
GroupSources("unpublishedScripts")
+unset(JS_SRC)
-if (UNIX)
- install(
- DIRECTORY "${CMAKE_SOURCE_DIR}/scripts"
- DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/interface
- COMPONENT ${CLIENT_COMPONENT}
- )
-endif()
+# Locate the required Qt build on the filesystem
+setup_qt()
+list(APPEND CMAKE_PREFIX_PATH "${QT_CMAKE_PREFIX_PATH}")
-if (ANDROID)
- file(GLOB ANDROID_CUSTOM_MACROS "cmake/android/*.cmake")
- foreach(CUSTOM_MACRO ${ANDROID_CUSTOM_MACROS})
- include(${CUSTOM_MACRO})
- endforeach()
-endif ()
+find_package( Threads )
+
+add_definitions(-DGLM_FORCE_RADIANS)
+set(HIFI_LIBRARY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libraries")
set(EXTERNAL_PROJECT_PREFIX "project")
set_property(DIRECTORY PROPERTY EP_PREFIX ${EXTERNAL_PROJECT_PREFIX})
setup_externals_binary_dir()
option(USE_NSIGHT "Attempt to find the nSight libraries" 1)
-option(GET_QUAZIP "Get QuaZip library automatically as external project" 1)
-
-
-if (WIN32)
- add_paths_to_fixup_libs("${QT_DIR}/bin")
-endif ()
-
-if (NOT DEFINED SERVER_ONLY)
- set(SERVER_ONLY 0)
-endif()
set_packaging_parameters()
+# FIXME hack to work on the proper Android toolchain
+if (ANDROID)
+ add_subdirectory(android/app)
+ return()
+endif()
+
# add subdirectories for all targets
-if (NOT ANDROID)
+if (BUILD_SERVER)
add_subdirectory(assignment-client)
set_target_properties(assignment-client PROPERTIES FOLDER "Apps")
add_subdirectory(domain-server)
@@ -260,28 +97,36 @@ if (NOT ANDROID)
add_subdirectory(ice-server)
set_target_properties(ice-server PROPERTIES FOLDER "Apps")
add_subdirectory(server-console)
- if (NOT SERVER_ONLY)
+endif()
+
+if (BUILD_CLIENT)
add_subdirectory(interface)
set_target_properties(interface PROPERTIES FOLDER "Apps")
- add_subdirectory(tests)
- endif()
- add_subdirectory(plugins)
+ if (ANDROID)
+ add_subdirectory(gvr-interface)
+ set_target_properties(gvr-interface PROPERTIES FOLDER "Apps")
+ endif()
+endif()
+
+if (BUILD_CLIENT OR BUILD_SERVER)
+ add_subdirectory(plugins)
+endif()
+
+if (BUILD_TOOLS)
add_subdirectory(tools)
endif()
-if (ANDROID OR DESKTOP_GVR)
- add_subdirectory(interface)
- add_subdirectory(gvr-interface)
- add_subdirectory(plugins)
-endif ()
+if (BUILD_TESTS)
+ add_subdirectory(tests)
+endif()
-if (DEFINED ENV{HIFI_MEMORY_DEBUGGING})
- SET( HIFI_MEMORY_DEBUGGING true )
-endif ()
-if (HIFI_MEMORY_DEBUGGING)
- if (UNIX)
- MESSAGE("-- Memory debugging is enabled")
- endif (UNIX)
-endif ()
-
-generate_installers()
+if (BUILD_INSTALLER)
+ if (UNIX)
+ install(
+ DIRECTORY "${CMAKE_SOURCE_DIR}/scripts"
+ DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/interface
+ COMPONENT ${CLIENT_COMPONENT}
+ )
+ endif()
+ generate_installers()
+endif()
diff --git a/Test Plan 2.docx b/Test Plan 2.docx
deleted file mode 100644
index da60821b53..0000000000
Binary files a/Test Plan 2.docx and /dev/null differ
diff --git a/android/app/CMakeLists.txt b/android/app/CMakeLists.txt
new file mode 100644
index 0000000000..2d6df925e9
--- /dev/null
+++ b/android/app/CMakeLists.txt
@@ -0,0 +1,8 @@
+set(TARGET_NAME native-lib)
+setup_hifi_library()
+link_hifi_libraries(shared networking gl gpu gpu-gles render-utils)
+autoscribe_shader_lib(gpu model render render-utils)
+target_opengl()
+target_link_libraries(native-lib android log m)
+target_include_directories(native-lib PRIVATE "${GVR_ROOT}/libraries/headers")
+target_link_libraries(native-lib "C:/Users/bdavis/Git/hifi/android/libraries/jni/armeabi-v7a/libgvr.so")
diff --git a/android/app/build.gradle b/android/app/build.gradle
new file mode 100644
index 0000000000..bd1c596bf3
--- /dev/null
+++ b/android/app/build.gradle
@@ -0,0 +1,57 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 26
+ buildToolsVersion "26.0.1"
+ defaultConfig {
+ applicationId "org.saintandreas.testapp"
+ minSdkVersion 24
+ targetSdkVersion 26
+ versionCode 1
+ versionName "1.0"
+ ndk { abiFilters 'armeabi-v7a' }
+ externalNativeBuild {
+ cmake {
+ arguments '-DHIFI_ANDROID=1',
+ '-DANDROID_PLATFORM=android-24',
+ '-DANDROID_TOOLCHAIN=clang',
+ '-DANDROID_STL=gnustl_shared',
+ '-DGVR_ROOT=' + GVR_ROOT,
+ '-DNATIVE_SCRIBE=c:/bin/scribe.exe',
+ "-DHIFI_ANDROID_PRECOMPILED=${project.rootDir}/libraries/jni/armeabi-v7a"
+ }
+ }
+ jackOptions { enabled true }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ sourceSets {
+ main {
+ jniLibs.srcDirs += '../libraries/jni';
+ }
+ }
+ externalNativeBuild {
+ cmake {
+ path '../../CMakeLists.txt'
+ }
+ }
+}
+
+dependencies {
+ compile fileTree(dir: "${project.rootDir}/libraries/jar", include: 'QtAndroid-bundled.jar')
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ compile 'com.google.vr:sdk-audio:1.80.0'
+ compile 'com.google.vr:sdk-base:1.80.0'
+}
+
+build.dependsOn(':extractQt5')
diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro
new file mode 100644
index 0000000000..b3c0078513
--- /dev/null
+++ b/android/app/proguard-rules.pro
@@ -0,0 +1,25 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in C:\Android\SDK/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..05547bd5ae
--- /dev/null
+++ b/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/cpp/GoogleVRHelpers.h b/android/app/src/main/cpp/GoogleVRHelpers.h
new file mode 100644
index 0000000000..10c46b036f
--- /dev/null
+++ b/android/app/src/main/cpp/GoogleVRHelpers.h
@@ -0,0 +1,50 @@
+#include
+#include
+#include
+
+namespace googlevr {
+
+ // Convert a GVR matrix to GLM matrix
+ glm::mat4 toGlm(const gvr::Mat4f &matrix) {
+ glm::mat4 result;
+ for (int i = 0; i < 4; ++i) {
+ for (int j = 0; j < 4; ++j) {
+ result[j][i] = matrix.m[i][j];
+ }
+ }
+ return result;
+ }
+
+ // Given a field of view in degrees, compute the corresponding projection
+// matrix.
+ glm::mat4 perspectiveMatrixFromView(const gvr::Rectf& fov, float z_near, float z_far) {
+ const float x_left = -std::tan(fov.left * M_PI / 180.0f) * z_near;
+ const float x_right = std::tan(fov.right * M_PI / 180.0f) * z_near;
+ const float y_bottom = -std::tan(fov.bottom * M_PI / 180.0f) * z_near;
+ const float y_top = std::tan(fov.top * M_PI / 180.0f) * z_near;
+ const float Y = (2 * z_near) / (y_top - y_bottom);
+ const float A = (x_right + x_left) / (x_right - x_left);
+ const float B = (y_top + y_bottom) / (y_top - y_bottom);
+ const float C = (z_near + z_far) / (z_near - z_far);
+ const float D = (2 * z_near * z_far) / (z_near - z_far);
+
+ glm::mat4 result { 0 };
+ result[2][0] = A;
+ result[1][1] = Y;
+ result[2][1] = B;
+ result[2][2] = C;
+ result[3][2] = D;
+ result[2][3] = -1;
+ return result;
+ }
+
+ glm::quat toGlm(const gvr::ControllerQuat& q) {
+ glm::quat result;
+ result.w = q.qw;
+ result.x = q.qx;
+ result.y = q.qy;
+ result.z = q.qz;
+ return result;
+ }
+
+}
diff --git a/android/app/src/main/cpp/native-lib.cpp b/android/app/src/main/cpp/native-lib.cpp
new file mode 100644
index 0000000000..156d43d849
--- /dev/null
+++ b/android/app/src/main/cpp/native-lib.cpp
@@ -0,0 +1,78 @@
+#include
+
+#include
+#include
+
+#include "renderer.h"
+
+int QtMsgTypeToAndroidPriority(QtMsgType type) {
+ int priority = ANDROID_LOG_UNKNOWN;
+ switch (type) {
+ case QtDebugMsg: priority = ANDROID_LOG_DEBUG; break;
+ case QtWarningMsg: priority = ANDROID_LOG_WARN; break;
+ case QtCriticalMsg: priority = ANDROID_LOG_ERROR; break;
+ case QtFatalMsg: priority = ANDROID_LOG_FATAL; break;
+ case QtInfoMsg: priority = ANDROID_LOG_INFO; break;
+ default: break;
+ }
+ return priority;
+}
+
+void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
+ __android_log_write(QtMsgTypeToAndroidPriority(type), "Interface", message.toStdString().c_str());
+}
+
+static jlong toJni(NativeRenderer *renderer) {
+ return reinterpret_cast(renderer);
+}
+
+static NativeRenderer *fromJni(jlong renderer) {
+ return reinterpret_cast(renderer);
+}
+
+#define JNI_METHOD(r, name) JNIEXPORT r JNICALL Java_org_saintandreas_testapp_MainActivity_##name
+
+extern "C" {
+
+JNI_METHOD(jlong, nativeCreateRenderer)
+(JNIEnv *env, jclass clazz, jobject class_loader, jobject android_context, jlong native_gvr_api) {
+ qInstallMessageHandler(messageHandler);
+#if defined(GVR)
+ auto gvrContext = reinterpret_cast(native_gvr_api);
+ return toJni(new NativeRenderer(gvrContext));
+#else
+ return toJni(new NativeRenderer(nullptr));
+#endif
+}
+
+JNI_METHOD(void, nativeDestroyRenderer)
+(JNIEnv *env, jclass clazz, jlong renderer) {
+ delete fromJni(renderer);
+}
+
+JNI_METHOD(void, nativeInitializeGl)
+(JNIEnv *env, jobject obj, jlong renderer) {
+ fromJni(renderer)->InitializeGl();
+}
+
+JNI_METHOD(void, nativeDrawFrame)
+(JNIEnv *env, jobject obj, jlong renderer) {
+ fromJni(renderer)->DrawFrame();
+}
+
+JNI_METHOD(void, nativeOnTriggerEvent)
+(JNIEnv *env, jobject obj, jlong renderer) {
+ fromJni(renderer)->OnTriggerEvent();
+}
+
+JNI_METHOD(void, nativeOnPause)
+(JNIEnv *env, jobject obj, jlong renderer) {
+ fromJni(renderer)->OnPause();
+}
+
+JNI_METHOD(void, nativeOnResume)
+(JNIEnv *env, jobject obj, jlong renderer) {
+ fromJni(renderer)->OnResume();
+}
+
+} // extern "C"
diff --git a/android/app/src/main/cpp/renderer.cpp b/android/app/src/main/cpp/renderer.cpp
new file mode 100644
index 0000000000..a877ebd777
--- /dev/null
+++ b/android/app/src/main/cpp/renderer.cpp
@@ -0,0 +1,636 @@
+#include "renderer.h"
+
+#include
+
+#include
+#include
+
+#include "GoogleVRHelpers.h"
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#include
+#include
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#if 0
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#endif
+
+
+template
+void withFrameBuffer(gvr::Frame& frame, int32_t index, F f) {
+ frame.BindBuffer(index);
+ f();
+ frame.Unbind();
+}
+
+
+static const uint64_t kPredictionTimeWithoutVsyncNanos = 50000000;
+
+// Each shader has two variants: a single-eye ES 2.0 variant, and a multiview
+// ES 3.0 variant. The multiview vertex shaders use transforms defined by
+// arrays of mat4 uniforms, using gl_ViewID_OVR to determine the array index.
+
+#define UNIFORM_LIGHT_POS 20
+#define UNIFORM_M 16
+#define UNIFORM_MV 8
+#define UNIFORM_MVP 0
+
+#if 0
+uniform Transform { // API uses “Transform[2]” to refer to instance 2
+ mat4 u_MVP[2];
+ mat4 u_MVMatrix[2];
+ mat4 u_Model;
+ vec3 u_LightPos[2];
+};
+static const char *kDiffuseLightingVertexShader = R"glsl(
+#version 300 es
+#extension GL_OVR_multiview2 : enable
+
+layout(num_views=2) in;
+
+layout(location = 0) uniform mat4 u_MVP[2];
+layout(location = 8) uniform mat4 u_MVMatrix[2];
+layout(location = 16) uniform mat4 u_Model;
+layout(location = 20) uniform vec3 u_LightPos[2];
+
+layout(location = 0) in vec4 a_Position;
+layout(location = 1) in vec4 a_Color;
+layout(location = 2) in vec3 a_Normal;
+
+out vec4 v_Color;
+out vec3 v_Grid;
+
+void main() {
+ mat4 mvp = u_MVP[gl_ViewID_OVR];
+ mat4 modelview = u_MVMatrix[gl_ViewID_OVR];
+ vec3 lightpos = u_LightPos[gl_ViewID_OVR];
+ v_Grid = vec3(u_Model * a_Position);
+ vec3 modelViewVertex = vec3(modelview * a_Position);
+ vec3 modelViewNormal = vec3(modelview * vec4(a_Normal, 0.0));
+ float distance = length(lightpos - modelViewVertex);
+ vec3 lightVector = normalize(lightpos - modelViewVertex);
+ float diffuse = max(dot(modelViewNormal, lightVector), 0.5);
+ diffuse = diffuse * (1.0 / (1.0 + (0.00001 * distance * distance)));
+ v_Color = vec4(a_Color.rgb * diffuse, a_Color.a);
+ gl_Position = mvp * a_Position;
+}
+)glsl";
+#endif
+
+
+static const char *kSimepleVertexShader = R"glsl(
+#version 300 es
+#extension GL_OVR_multiview2 : enable
+
+layout(num_views=2) in;
+
+layout(location = 0) in vec4 a_Position;
+
+out vec4 v_Color;
+
+void main() {
+ v_Color = vec4(a_Position.xyz, 1.0);
+ gl_Position = vec4(a_Position.xyz, 1.0);
+}
+)glsl";
+
+
+static const char *kPassthroughFragmentShader = R"glsl(
+#version 300 es
+precision mediump float;
+in vec4 v_Color;
+out vec4 FragColor;
+
+void main() { FragColor = v_Color; }
+)glsl";
+
+static void CheckGLError(const char* label) {
+ int gl_error = glGetError();
+ if (gl_error != GL_NO_ERROR) {
+ qWarning("GL error @ %s: %d", label, gl_error);
+ // Crash immediately to make OpenGL errors obvious.
+ abort();
+ }
+}
+
+// Contains vertex, normal and other data.
+namespace cube {
+ const std::array CUBE_COORDS{{
+ // Front face
+ -1.0f, 1.0f, 1.0f,
+ -1.0f, -1.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f,
+ -1.0f, -1.0f, 1.0f,
+ 1.0f, -1.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f,
+
+ // Right face
+ 1.0f, 1.0f, 1.0f,
+ 1.0f, -1.0f, 1.0f,
+ 1.0f, 1.0f, -1.0f,
+ 1.0f, -1.0f, 1.0f,
+ 1.0f, -1.0f, -1.0f,
+ 1.0f, 1.0f, -1.0f,
+
+ // Back face
+ 1.0f, 1.0f, -1.0f,
+ 1.0f, -1.0f, -1.0f,
+ -1.0f, 1.0f, -1.0f,
+ 1.0f, -1.0f, -1.0f,
+ -1.0f, -1.0f, -1.0f,
+ -1.0f, 1.0f, -1.0f,
+
+ // Left face
+ -1.0f, 1.0f, -1.0f,
+ -1.0f, -1.0f, -1.0f,
+ -1.0f, 1.0f, 1.0f,
+ -1.0f, -1.0f, -1.0f,
+ -1.0f, -1.0f, 1.0f,
+ -1.0f, 1.0f, 1.0f,
+
+ // Top face
+ -1.0f, 1.0f, -1.0f,
+ -1.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, -1.0f,
+ -1.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, -1.0f,
+
+ // Bottom face
+ 1.0f, -1.0f, -1.0f,
+ 1.0f, -1.0f, 1.0f,
+ -1.0f, -1.0f, -1.0f,
+ 1.0f, -1.0f, 1.0f,
+ -1.0f, -1.0f, 1.0f,
+ -1.0f, -1.0f, -1.0f
+ }};
+
+ const std::array CUBE_COLORS{{
+ // front, green
+ 0.0f, 0.5273f, 0.2656f,
+ 0.0f, 0.5273f, 0.2656f,
+ 0.0f, 0.5273f, 0.2656f,
+ 0.0f, 0.5273f, 0.2656f,
+ 0.0f, 0.5273f, 0.2656f,
+ 0.0f, 0.5273f, 0.2656f,
+
+ // right, blue
+ 0.0f, 0.3398f, 0.9023f,
+ 0.0f, 0.3398f, 0.9023f,
+ 0.0f, 0.3398f, 0.9023f,
+ 0.0f, 0.3398f, 0.9023f,
+ 0.0f, 0.3398f, 0.9023f,
+ 0.0f, 0.3398f, 0.9023f,
+
+ // back, also green
+ 0.0f, 0.5273f, 0.2656f,
+ 0.0f, 0.5273f, 0.2656f,
+ 0.0f, 0.5273f, 0.2656f,
+ 0.0f, 0.5273f, 0.2656f,
+ 0.0f, 0.5273f, 0.2656f,
+ 0.0f, 0.5273f, 0.2656f,
+
+ // left, also blue
+ 0.0f, 0.3398f, 0.9023f,
+ 0.0f, 0.3398f, 0.9023f,
+ 0.0f, 0.3398f, 0.9023f,
+ 0.0f, 0.3398f, 0.9023f,
+ 0.0f, 0.3398f, 0.9023f,
+ 0.0f, 0.3398f, 0.9023f,
+
+ // top, red
+ 0.8359375f, 0.17578125f, 0.125f,
+ 0.8359375f, 0.17578125f, 0.125f,
+ 0.8359375f, 0.17578125f, 0.125f,
+ 0.8359375f, 0.17578125f, 0.125f,
+ 0.8359375f, 0.17578125f, 0.125f,
+ 0.8359375f, 0.17578125f, 0.125f,
+
+ // bottom, also red
+ 0.8359375f, 0.17578125f, 0.125f,
+ 0.8359375f, 0.17578125f, 0.125f,
+ 0.8359375f, 0.17578125f, 0.125f,
+ 0.8359375f, 0.17578125f, 0.125f,
+ 0.8359375f, 0.17578125f, 0.125f,
+ 0.8359375f, 0.17578125f, 0.125f
+ }};
+
+ const std::array CUBE_NORMALS{{
+ // Front face
+ 0.0f, 0.0f, 1.0f,
+ 0.0f, 0.0f, 1.0f,
+ 0.0f, 0.0f, 1.0f,
+ 0.0f, 0.0f, 1.0f,
+ 0.0f, 0.0f, 1.0f,
+ 0.0f, 0.0f, 1.0f,
+
+ // Right face
+ 1.0f, 0.0f, 0.0f,
+ 1.0f, 0.0f, 0.0f,
+ 1.0f, 0.0f, 0.0f,
+ 1.0f, 0.0f, 0.0f,
+ 1.0f, 0.0f, 0.0f,
+ 1.0f, 0.0f, 0.0f,
+
+ // Back face
+ 0.0f, 0.0f, -1.0f,
+ 0.0f, 0.0f, -1.0f,
+ 0.0f, 0.0f, -1.0f,
+ 0.0f, 0.0f, -1.0f,
+ 0.0f, 0.0f, -1.0f,
+ 0.0f, 0.0f, -1.0f,
+
+ // Left face
+ -1.0f, 0.0f, 0.0f,
+ -1.0f, 0.0f, 0.0f,
+ -1.0f, 0.0f, 0.0f,
+ -1.0f, 0.0f, 0.0f,
+ -1.0f, 0.0f, 0.0f,
+ -1.0f, 0.0f, 0.0f,
+
+ // Top face
+ 0.0f, 1.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f,
+
+ // Bottom face
+ 0.0f, -1.0f, 0.0f,
+ 0.0f, -1.0f, 0.0f,
+ 0.0f, -1.0f, 0.0f,
+ 0.0f, -1.0f, 0.0f,
+ 0.0f, -1.0f, 0.0f,
+ 0.0f, -1.0f, 0.0f
+ }};
+}
+
+namespace triangle {
+ static std::array TRIANGLE_VERTS {{
+ -0.5f, -0.5f, 0.0f,
+ 0.5f, -0.5f, 0.0f,
+ 0.0f, 0.5f, 0.0f
+ }};
+}
+
+std::array buildViewports(const std::unique_ptr &gvrapi) {
+ return { {gvrapi->CreateBufferViewport(), gvrapi->CreateBufferViewport()} };
+};
+
+const std::string VERTEX_SHADER_DEFINES{ R"GLSL(
+#version 300 es
+#extension GL_EXT_clip_cull_distance : enable
+#define GPU_VERTEX_SHADER
+#define GPU_SSBO_TRANSFORM_OBJECT 1
+#define GPU_TRANSFORM_IS_STEREO
+#define GPU_TRANSFORM_STEREO_CAMERA
+#define GPU_TRANSFORM_STEREO_CAMERA_INSTANCED
+#define GPU_TRANSFORM_STEREO_SPLIT_SCREEN
+)GLSL" };
+
+const std::string PIXEL_SHADER_DEFINES{ R"GLSL(
+#version 300 es
+precision mediump float;
+#define GPU_PIXEL_SHADER
+#define GPU_TRANSFORM_IS_STEREO
+#define GPU_TRANSFORM_STEREO_CAMERA
+#define GPU_TRANSFORM_STEREO_CAMERA_INSTANCED
+#define GPU_TRANSFORM_STEREO_SPLIT_SCREEN
+)GLSL" };
+
+
+#if defined(GVR)
+NativeRenderer::NativeRenderer(gvr_context *vrContext) :
+ _gvrapi(new gvr::GvrApi(vrContext, false)),
+ _viewports(buildViewports(_gvrapi)),
+ _gvr_viewer_type(_gvrapi->GetViewerType())
+#else
+NativeRenderer::NativeRenderer(void *vrContext)
+#endif
+{
+ start = std::chrono::system_clock::now();
+ qDebug() << "QQQ" << __FUNCTION__;
+}
+
+
+/**
+ * Converts a raw text file, saved as a resource, into an OpenGL ES shader.
+ *
+ * @param type The type of shader we will be creating.
+ * @param resId The resource ID of the raw text file.
+ * @return The shader object handler.
+ */
+int LoadGLShader(int type, const char *shadercode) {
+ GLuint result = 0;
+ std::string shaderError;
+ static const std::string SHADER_DEFINES;
+ if (!gl::compileShader(type, shadercode, SHADER_DEFINES, result, shaderError)) {
+ qWarning() << "QQQ" << __FUNCTION__ << "Shader compile failure" << shaderError.c_str();
+ }
+ return result;
+}
+
+// Computes a texture size that has approximately half as many pixels. This is
+// equivalent to scaling each dimension by approximately sqrt(2)/2.
+static gvr::Sizei HalfPixelCount(const gvr::Sizei &in) {
+ // Scale each dimension by sqrt(2)/2 ~= 7/10ths.
+ gvr::Sizei out;
+ out.width = (7 * in.width) / 10;
+ out.height = (7 * in.height) / 10;
+ return out;
+}
+
+
+#if defined(GVR)
+void NativeRenderer::InitializeVR() {
+ _gvrapi->InitializeGl();
+ bool multiviewEnabled = _gvrapi->IsFeatureSupported(GVR_FEATURE_MULTIVIEW);
+ qWarning() << "QQQ" << __FUNCTION__ << "Multiview enabled " << multiviewEnabled;
+ // Because we are using 2X MSAA, we can render to half as many pixels and
+ // achieve similar quality.
+ _renderSize = HalfPixelCount(_gvrapi->GetMaximumEffectiveRenderTargetSize());
+
+ std::vector specs;
+ specs.push_back(_gvrapi->CreateBufferSpec());
+ specs[0].SetColorFormat(GVR_COLOR_FORMAT_RGBA_8888);
+ specs[0].SetDepthStencilFormat(GVR_DEPTH_STENCIL_FORMAT_DEPTH_16);
+ specs[0].SetSamples(2);
+ gvr::Sizei half_size = {_renderSize.width / 2, _renderSize.height};
+ specs[0].SetMultiviewLayers(2);
+ specs[0].SetSize(half_size);
+
+ _swapchain.reset(new gvr::SwapChain(_gvrapi->CreateSwapChain(specs)));
+ _viewportlist.reset(new gvr::BufferViewportList(_gvrapi->CreateEmptyBufferViewportList()));
+}
+void NativeRenderer::PrepareFramebuffer() {
+ const gvr::Sizei recommended_size = HalfPixelCount(
+ _gvrapi->GetMaximumEffectiveRenderTargetSize());
+ if (_renderSize.width != recommended_size.width ||
+ _renderSize.height != recommended_size.height) {
+ // We need to resize the framebuffer. Note that multiview uses two texture
+ // layers, each with half the render width.
+ gvr::Sizei framebuffer_size = recommended_size;
+ framebuffer_size.width /= 2;
+ _swapchain->ResizeBuffer(0, framebuffer_size);
+ _renderSize = recommended_size;
+ }
+}
+#endif
+
+void testShaderBuild(const char* vs_src, const char * fs_src) {
+ std::string error;
+ GLuint vs, fs;
+ if (!gl::compileShader(GL_VERTEX_SHADER, vs_src, VERTEX_SHADER_DEFINES, vs, error) ||
+ !gl::compileShader(GL_FRAGMENT_SHADER, fs_src, PIXEL_SHADER_DEFINES, fs, error)) {
+ throw std::runtime_error("Failed to compile shader");
+ }
+ auto pr = gl::compileProgram({ vs, fs }, error);
+ if (!pr) {
+ throw std::runtime_error("Failed to link shader");
+ }
+}
+
+void NativeRenderer::InitializeGl() {
+ qDebug() << "QQQ" << __FUNCTION__;
+ //gl::initModuleGl();
+#if defined(GVR)
+ InitializeVR();
+#endif
+
+ glDisable(GL_DEPTH_TEST);
+ glDisable(GL_CULL_FACE);
+ glDisable(GL_SCISSOR_TEST);
+ glDisable(GL_BLEND);
+
+
+
+ const uint32_t vertShader = LoadGLShader(GL_VERTEX_SHADER, kSimepleVertexShader);
+ //const uint32_t vertShader = LoadGLShader(GL_VERTEX_SHADER, kDiffuseLightingVertexShader);
+ const uint32_t fragShader = LoadGLShader(GL_FRAGMENT_SHADER, kPassthroughFragmentShader);
+ std::string error;
+ _cubeProgram = gl::compileProgram({ vertShader, fragShader }, error);
+ CheckGLError("build program");
+
+ glGenBuffers(1, &_cubeBuffer);
+ glBindBuffer(GL_ARRAY_BUFFER, _cubeBuffer);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 9, triangle::TRIANGLE_VERTS.data(), GL_STATIC_DRAW);
+ /*
+ glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 108 * 3, NULL, GL_STATIC_DRAW);
+ glBufferSubData(GL_ARRAY_BUFFER, sizeof(float) * 108 * 0, sizeof(float) * 108, cube::CUBE_COORDS.data());
+ glBufferSubData(GL_ARRAY_BUFFER, sizeof(float) * 108 * 1, sizeof(float) * 108, cube::CUBE_COLORS.data());
+ glBufferSubData(GL_ARRAY_BUFFER, sizeof(float) * 108 * 2, sizeof(float) * 108, cube::CUBE_NORMALS.data());
+ */
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ CheckGLError("upload vertices");
+
+ glGenVertexArrays(1, &_cubeVao);
+ glBindBuffer(GL_ARRAY_BUFFER, _cubeBuffer);
+ glBindVertexArray(_cubeVao);
+
+ glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
+ glEnableVertexAttribArray(0);
+ /*
+ glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0);
+ glEnableVertexAttribArray(0);
+ glVertexAttribPointer(1, 3, GL_FLOAT, false, 0, (const void*)(sizeof(float) * 108 * 1) );
+ glEnableVertexAttribArray(1);
+ glVertexAttribPointer(2, 3, GL_FLOAT, false, 0, (const void*)(sizeof(float) * 108 * 2));
+ glEnableVertexAttribArray(2);
+ */
+ glBindVertexArray(0);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ CheckGLError("build vao ");
+
+ static std::once_flag once;
+ std::call_once(once, [&]{
+ testShaderBuild(sdf_text3D_vert, sdf_text3D_frag);
+
+ testShaderBuild(DrawTransformUnitQuad_vert, DrawTexture_frag);
+ testShaderBuild(DrawTexcoordRectTransformUnitQuad_vert, DrawTexture_frag);
+ testShaderBuild(DrawViewportQuadTransformTexcoord_vert, DrawTexture_frag);
+ testShaderBuild(DrawTransformUnitQuad_vert, DrawTextureOpaque_frag);
+ testShaderBuild(DrawTransformUnitQuad_vert, DrawColoredTexture_frag);
+
+ testShaderBuild(simple_vert, simple_frag);
+ testShaderBuild(simple_vert, simple_textured_frag);
+ testShaderBuild(simple_vert, simple_textured_unlit_frag);
+ testShaderBuild(deferred_light_vert, directional_ambient_light_frag);
+ testShaderBuild(deferred_light_vert, directional_skybox_light_frag);
+ testShaderBuild(standardTransformPNTC_vert, standardDrawTexture_frag);
+ testShaderBuild(standardTransformPNTC_vert, DrawTextureOpaque_frag);
+
+ testShaderBuild(model_vert, model_frag);
+ testShaderBuild(model_normal_map_vert, model_normal_map_frag);
+ testShaderBuild(model_vert, model_specular_map_frag);
+ testShaderBuild(model_normal_map_vert, model_normal_specular_map_frag);
+ testShaderBuild(model_vert, model_translucent_frag);
+ testShaderBuild(model_normal_map_vert, model_translucent_frag);
+ testShaderBuild(model_lightmap_vert, model_lightmap_frag);
+ testShaderBuild(model_lightmap_normal_map_vert, model_lightmap_normal_map_frag);
+ testShaderBuild(model_lightmap_vert, model_lightmap_specular_map_frag);
+ testShaderBuild(model_lightmap_normal_map_vert, model_lightmap_normal_specular_map_frag);
+
+ testShaderBuild(skin_model_vert, model_frag);
+ testShaderBuild(skin_model_normal_map_vert, model_normal_map_frag);
+ testShaderBuild(skin_model_vert, model_specular_map_frag);
+ testShaderBuild(skin_model_normal_map_vert, model_normal_specular_map_frag);
+ testShaderBuild(skin_model_vert, model_translucent_frag);
+ testShaderBuild(skin_model_normal_map_vert, model_translucent_frag);
+
+ testShaderBuild(model_shadow_vert, model_shadow_frag);
+
+ testShaderBuild(overlay3D_vert, overlay3D_frag);
+
+#if 0
+ testShaderBuild(textured_particle_vert, textured_particle_frag);
+ testShaderBuild(skybox_vert, skybox_frag);
+ testShaderBuild(paintStroke_vert,paintStroke_frag);
+ testShaderBuild(polyvox_vert, polyvox_frag);
+#endif
+
+ });
+
+ qDebug() << "done";
+}
+
+static const float kZNear = 1.0f;
+static const float kZFar = 100.0f;
+static const gvr_rectf fullscreen = {0, 1, 0, 1};
+
+void NativeRenderer::DrawFrame() {
+ auto now = std::chrono::duration_cast(
+ std::chrono::system_clock::now() - start);
+ glm::vec3 v;
+ v.r = (float) (now.count() % 1000) / 1000.0f;
+ v.g = 1.0f - v.r;
+ v.b = 1.0f;
+
+ PrepareFramebuffer();
+
+ // A client app does its rendering here.
+ gvr::ClockTimePoint target_time = gvr::GvrApi::GetTimePointNow();
+ target_time.monotonic_system_time_nanos += kPredictionTimeWithoutVsyncNanos;
+
+ using namespace googlevr;
+ using namespace bilateral;
+ const auto gvrHeadPose = _gvrapi->GetHeadSpaceFromStartSpaceRotation(target_time);
+ _head_view = toGlm(gvrHeadPose);
+ _viewportlist->SetToRecommendedBufferViewports();
+
+ glm::mat4 eye_views[2];
+ for_each_side([&](bilateral::Side side) {
+ int eye = index(side);
+ const gvr::Eye gvr_eye = eye == 0 ? GVR_LEFT_EYE : GVR_RIGHT_EYE;
+ const auto& eyeView = eye_views[eye] = toGlm(_gvrapi->GetEyeFromHeadMatrix(gvr_eye)) * _head_view;
+ auto& viewport = _viewports[eye];
+
+ _viewportlist->GetBufferViewport(eye, &viewport);
+ viewport.SetSourceUv(fullscreen);
+ viewport.SetSourceLayer(eye);
+ _viewportlist->SetBufferViewport(eye, viewport);
+ const auto &mvc = _modelview_cube[eye] = eyeView * _model_cube;
+ const auto &mvf = _modelview_floor[eye] = eyeView * _model_floor;
+ const gvr_rectf fov = viewport.GetSourceFov();
+ const glm::mat4 perspective = perspectiveMatrixFromView(fov, kZNear, kZFar);
+ _modelview_projection_cube[eye] = perspective * mvc;
+ _modelview_projection_floor[eye] = perspective * mvf;
+ _light_pos_eye_space[eye] = glm::vec3(eyeView * _light_pos_world_space);
+ });
+
+
+ gvr::Frame frame = _swapchain->AcquireFrame();
+ withFrameBuffer(frame, 0, [&]{
+ glClearColor(v.r, v.g, v.b, 1);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ glViewport(0, 0, _renderSize.width / 2, _renderSize.height);
+ glUseProgram(_cubeProgram);
+ glBindVertexArray(_cubeVao);
+ glDrawArrays(GL_TRIANGLES, 0, 3);
+ /*
+ float* fp;
+ fp = (float*)&_light_pos_eye_space[0];
+ glUniform3fv(UNIFORM_LIGHT_POS, 2, fp);
+ fp = (float*)&_modelview_cube[0];
+ glUniformMatrix4fv(UNIFORM_MV, 2, GL_FALSE, fp);
+ fp = (float*)&_modelview_projection_cube[0];
+ glUniformMatrix4fv(UNIFORM_MVP, 2, GL_FALSE, fp);
+ fp = (float*)&_model_cube;
+ glUniformMatrix4fv(UNIFORM_M, 1, GL_FALSE, fp);
+ glDrawArrays(GL_TRIANGLES, 0, 36);
+ */
+ glBindVertexArray(0);
+ });
+
+ frame.Submit(*_viewportlist, gvrHeadPose);
+ CheckGLError("onDrawFrame");
+
+}
+
+void NativeRenderer::OnTriggerEvent() {
+ qDebug() << "QQQ" << __FUNCTION__;
+}
+
+void NativeRenderer::OnPause() {
+ qDebug() << "QQQ" << __FUNCTION__;
+ _gvrapi->PauseTracking();
+}
+
+void NativeRenderer::OnResume() {
+ qDebug() << "QQQ" << __FUNCTION__;
+ _gvrapi->ResumeTracking();
+ _gvrapi->RefreshViewerProfile();
+}
diff --git a/android/app/src/main/cpp/renderer.h b/android/app/src/main/cpp/renderer.h
new file mode 100644
index 0000000000..df7c51cab4
--- /dev/null
+++ b/android/app/src/main/cpp/renderer.h
@@ -0,0 +1,60 @@
+#pragma once
+
+#include
+#include
+#include
+
+#define GVR
+
+#if defined(GVR)
+#include
+#endif
+
+class NativeRenderer {
+public:
+
+#if defined(GVR)
+ NativeRenderer(gvr_context* vrContext);
+#else
+ NativeRenderer(void* vrContext);
+#endif
+
+ void InitializeGl();
+ void DrawFrame();
+ void OnTriggerEvent();
+ void OnPause();
+ void OnResume();
+
+private:
+
+
+ std::chrono::time_point start;
+#if defined(GVR)
+ void InitializeVR();
+ void PrepareFramebuffer();
+
+ std::unique_ptr _gvrapi;
+ gvr::ViewerType _gvr_viewer_type;
+ std::unique_ptr _viewportlist;
+ std::unique_ptr _swapchain;
+ std::array _viewports;
+ gvr::Sizei _renderSize;
+#endif
+
+ uint32_t _cubeBuffer { 0 };
+ uint32_t _cubeVao { 0 };
+ uint32_t _cubeProgram { 0 };
+
+ glm::mat4 _head_view;
+ glm::mat4 _model_cube;
+ glm::mat4 _camera;
+ glm::mat4 _view;
+ glm::mat4 _model_floor;
+
+ std::array _modelview_cube;
+ std::array _modelview_floor;
+ std::array _modelview_projection_cube;
+ std::array _modelview_projection_floor;
+ std::array _light_pos_eye_space;
+ const glm::vec4 _light_pos_world_space{ 0, 2, 0, 1};
+};
diff --git a/android/app/src/main/java/org/saintandreas/testapp/MainActivity.java b/android/app/src/main/java/org/saintandreas/testapp/MainActivity.java
new file mode 100644
index 0000000000..7eea14dce9
--- /dev/null
+++ b/android/app/src/main/java/org/saintandreas/testapp/MainActivity.java
@@ -0,0 +1,105 @@
+package org.saintandreas.testapp;
+
+import android.app.Activity;
+import android.content.Context;
+import android.opengl.GLSurfaceView;
+import android.os.Bundle;
+import android.view.View;
+
+import com.google.vr.ndk.base.AndroidCompat;
+import com.google.vr.ndk.base.GvrLayout;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+public class MainActivity extends Activity {
+ private final static int IMMERSIVE_STICKY_VIEW_FLAGS = View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
+ View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
+ View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_FULLSCREEN |
+ View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+
+ static {
+ System.loadLibrary("gvr");
+ System.loadLibrary("native-lib");
+ }
+
+ private long nativeRenderer;
+ private GvrLayout gvrLayout;
+ private GLSurfaceView surfaceView;
+
+ private native long nativeCreateRenderer(ClassLoader appClassLoader, Context context, long nativeGvrContext);
+ private native void nativeDestroyRenderer(long renderer);
+ private native void nativeInitializeGl(long renderer);
+ private native void nativeDrawFrame(long renderer);
+ private native void nativeOnTriggerEvent(long renderer);
+ private native void nativeOnPause(long renderer);
+ private native void nativeOnResume(long renderer);
+
+ class NativeRenderer implements GLSurfaceView.Renderer {
+ @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { nativeInitializeGl(nativeRenderer); }
+ @Override public void onSurfaceChanged(GL10 gl, int width, int height) { }
+ @Override public void onDrawFrame(GL10 gl) {
+ nativeDrawFrame(nativeRenderer);
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setImmersiveSticky();
+ getWindow()
+ .getDecorView()
+ .setOnSystemUiVisibilityChangeListener((int visibility)->{
+ if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) { setImmersiveSticky(); }
+ });
+
+ gvrLayout = new GvrLayout(this);
+ nativeRenderer = nativeCreateRenderer(
+ getClass().getClassLoader(),
+ getApplicationContext(),
+ gvrLayout.getGvrApi().getNativeGvrContext());
+
+ surfaceView = new GLSurfaceView(this);
+ surfaceView.setEGLContextClientVersion(3);
+ surfaceView.setEGLConfigChooser(8, 8, 8, 0, 0, 0);
+ surfaceView.setPreserveEGLContextOnPause(true);
+ surfaceView.setRenderer(new NativeRenderer());
+
+ gvrLayout.setPresentationView(surfaceView);
+ setContentView(gvrLayout);
+ if (gvrLayout.setAsyncReprojectionEnabled(true)) {
+ AndroidCompat.setSustainedPerformanceMode(this, true);
+ }
+ AndroidCompat.setVrModeEnabled(this, true);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ gvrLayout.shutdown();
+ nativeDestroyRenderer(nativeRenderer);
+ nativeRenderer = 0;
+ }
+
+ @Override
+ protected void onPause() {
+ surfaceView.queueEvent(()->nativeOnPause(nativeRenderer));
+ surfaceView.onPause();
+ gvrLayout.onPause();
+ super.onPause();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ gvrLayout.onResume();
+ surfaceView.onResume();
+ surfaceView.queueEvent(()->nativeOnResume(nativeRenderer));
+ }
+
+ private void setImmersiveSticky() {
+ getWindow().getDecorView().setSystemUiVisibility(IMMERSIVE_STICKY_VIEW_FLAGS);
+ }
+}
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000000..cde69bccce
Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000000..9a078e3e1a
Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000000..c133a0cbd3
Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000000..efc028a636
Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000000..bfa42f0e7b
Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000000..3af2608a44
Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000000..324e72cdd7
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000000..9bec2e6231
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000000..aee44e1384
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000000..34947cd6bb
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000000..344907f039
--- /dev/null
+++ b/android/app/src/main/res/values/colors.xml
@@ -0,0 +1,4 @@
+
+
+ #ffffff
+
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000000..5d6a4c1b99
--- /dev/null
+++ b/android/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ TestApp
+
diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000000..033324ac58
--- /dev/null
+++ b/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
diff --git a/android/build.gradle b/android/build.gradle
new file mode 100644
index 0000000000..77c3dd498c
--- /dev/null
+++ b/android/build.gradle
@@ -0,0 +1,91 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:2.3.3'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
+
+task extractQt5jars(type: Copy) {
+ from fileTree(QT5_ROOT + "/jar")
+ into("${project.rootDir}/libraries/jar")
+ include("*.jar")
+}
+
+task extractQt5so(type: Copy) {
+ from fileTree(QT5_ROOT + "/lib")
+ into("${project.rootDir}/libraries/jni/armeabi-v7a/")
+ include("libQt5AndroidExtras.so")
+ include("libQt5Concurrent.so")
+ include("libQt5Core.so")
+ include("libQt5Gamepad.so")
+ include("libQt5Gui.so")
+ include("libQt5Location.so")
+ include("libQt5Multimedia.so")
+ include("libQt5MultimediaQuick_p.so")
+ include("libQt5Network.so")
+ include("libQt5NetworkAuth.so")
+ include("libQt5OpenGL.so")
+ include("libQt5Positioning.so")
+ include("libQt5Qml.so")
+ include("libQt5Quick.so")
+ include("libQt5QuickControls2.so")
+ include("libQt5QuickParticles.so")
+ include("libQt5QuickTemplates2.so")
+ include("libQt5QuickWidgets.so")
+ include("libQt5Script.so")
+ include("libQt5ScriptTools.so")
+ include("libQt5Sensors.so")
+ include("libQt5Svg.so")
+ include("libQt5WebChannel.so")
+ include("libQt5WebSockets.so")
+ include("libQt5WebView.so")
+ include("libQt5Widgets.so")
+ include("libQt5Xml.so")
+ include("libQt5XmlPatterns.so")
+}
+
+task extractAudioSo(type: Copy) {
+ from zipTree(GVR_ROOT + "/libraries/sdk-audio-1.80.0.aar")
+ into "${project.rootDir}/libraries/"
+ include "jni/armeabi-v7a/libgvr_audio.so"
+}
+
+task extractGvrSo(type: Copy) {
+ from zipTree(GVR_ROOT + "/libraries/sdk-base-1.80.0.aar")
+ into "${project.rootDir}/libraries/"
+ include "jni/armeabi-v7a/libgvr.so"
+}
+
+task extractNdk { }
+extractNdk.dependsOn extractAudioSo
+extractNdk.dependsOn extractGvrSo
+
+task extractQt5 { }
+extractQt5.dependsOn extractQt5so
+extractQt5.dependsOn extractQt5jars
+
+task extractBinaries { }
+extractBinaries.dependsOn extractQt5
+extractBinaries.dependsOn extractNdk
+
+task deleteBinaries(type: Delete) {
+ delete "${project.rootDir}/libraries/jni"
+}
+
+//clean.dependsOn(deleteBinaries)
diff --git a/android/gradle.properties b/android/gradle.properties
new file mode 100644
index 0000000000..aac7c9b461
--- /dev/null
+++ b/android/gradle.properties
@@ -0,0 +1,17 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
diff --git a/android/settings.gradle b/android/settings.gradle
new file mode 100644
index 0000000000..e7b4def49c
--- /dev/null
+++ b/android/settings.gradle
@@ -0,0 +1 @@
+include ':app'
diff --git a/assignment-client/CMakeLists.txt b/assignment-client/CMakeLists.txt
index 1a27ddd479..0421195612 100644
--- a/assignment-client/CMakeLists.txt
+++ b/assignment-client/CMakeLists.txt
@@ -13,7 +13,7 @@ setup_memory_debugger()
link_hifi_libraries(
audio avatars octree gpu model fbx entities
networking animation recording shared script-engine embedded-webserver
- controllers physics plugins midi
+ controllers physics plugins midi baking image
)
if (WIN32)
diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp
index c44fdf74ff..4efc3343d1 100644
--- a/assignment-client/src/Agent.cpp
+++ b/assignment-client/src/Agent.cpp
@@ -184,6 +184,9 @@ void Agent::run() {
// make sure we hear about connected nodes so we can grab an ATP script if a request is pending
connect(nodeList.data(), &LimitedNodeList::nodeActivated, this, &Agent::nodeActivated);
+ // make sure we hear about dissappearing nodes so we can clear the entity tree if an entity server goes away
+ connect(nodeList.data(), &LimitedNodeList::nodeKilled, this, &Agent::nodeKilled);
+
nodeList->addSetOfNodeTypesToNodeInterestSet({
NodeType::AudioMixer, NodeType::AvatarMixer, NodeType::EntityServer, NodeType::MessagesMixer, NodeType::AssetServer
});
@@ -259,6 +262,13 @@ void Agent::nodeActivated(SharedNodePointer activatedNode) {
}
}
+void Agent::nodeKilled(SharedNodePointer killedNode) {
+ if (killedNode->getType() == NodeType::EntityServer) {
+ // an entity server has gone away, ask the headless viewer to clear its tree
+ _entityViewer.clear();
+ }
+}
+
void Agent::negotiateAudioFormat() {
auto nodeList = DependencyManager::get();
auto negotiateFormatPacket = NLPacket::create(PacketType::NegotiateAudioFormat);
@@ -344,15 +354,16 @@ void Agent::scriptRequestFinished() {
void Agent::executeScript() {
- _scriptEngine = std::unique_ptr(new ScriptEngine(ScriptEngine::AGENT_SCRIPT, _scriptContents, _payload));
+ _scriptEngine = scriptEngineFactory(ScriptEngine::AGENT_SCRIPT, _scriptContents, _payload);
_scriptEngine->setParent(this); // be the parent of the script engine so it gets moved when we do
- DependencyManager::get()->setScriptEngine(_scriptEngine.get());
+ DependencyManager::get()->setScriptEngine(_scriptEngine);
// setup an Avatar for the script to use
auto scriptedAvatar = DependencyManager::get();
- connect(_scriptEngine.get(), SIGNAL(update(float)), scriptedAvatar.data(), SLOT(update(float)), Qt::ConnectionType::QueuedConnection);
+ connect(_scriptEngine.data(), SIGNAL(update(float)),
+ scriptedAvatar.data(), SLOT(update(float)), Qt::ConnectionType::QueuedConnection);
scriptedAvatar->setForceFaceTrackerConnected(true);
// call model URL setters with empty URLs so our avatar, if user, will have the default models
diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h
index da60a07367..168da185b6 100644
--- a/assignment-client/src/Agent.h
+++ b/assignment-client/src/Agent.h
@@ -77,6 +77,7 @@ private slots:
void handleSelectedAudioFormat(QSharedPointer message);
void nodeActivated(SharedNodePointer activatedNode);
+ void nodeKilled(SharedNodePointer killedNode);
void processAgentAvatar();
void processAgentAvatarAudio();
@@ -87,7 +88,7 @@ private:
void encodeFrameOfZeros(QByteArray& encodedZeros);
void computeLoudness(const QByteArray* decodedBuffer, QSharedPointer);
- std::unique_ptr _scriptEngine;
+ ScriptEnginePointer _scriptEngine;
EntityEditPacketSender _entityEditSender;
EntityTreeHeadlessViewer _entityViewer;
diff --git a/assignment-client/src/assets/AssetServer.cpp b/assignment-client/src/assets/AssetServer.cpp
index 3886ff8d92..c03721d097 100644
--- a/assignment-client/src/assets/AssetServer.cpp
+++ b/assignment-client/src/assets/AssetServer.cpp
@@ -13,24 +13,32 @@
#include "AssetServer.h"
#include
+#include
#include
#include
#include
#include
+#include
#include
#include
#include
#include
+#include
+#include
+#include
+#include
+#include
+#include
#include
#include
-#include "NetworkLogging.h"
-#include "NodeType.h"
+#include "AssetServerLogging.h"
+#include "BakeAssetTask.h"
#include "SendAssetTask.h"
#include "UploadAssetTask.h"
-#include
+
static const uint8_t MIN_CORES_FOR_MULTICORE = 4;
static const uint8_t CPU_AFFINITY_COUNT_HIGH = 2;
@@ -41,6 +49,151 @@ static const int INTERFACE_RUNNING_CHECK_FREQUENCY_MS = 1000;
const QString ASSET_SERVER_LOGGING_TARGET_NAME = "asset-server";
+static const QStringList BAKEABLE_MODEL_EXTENSIONS = { "fbx" };
+static QStringList BAKEABLE_TEXTURE_EXTENSIONS;
+static const QString BAKED_MODEL_SIMPLE_NAME = "asset.fbx";
+static const QString BAKED_TEXTURE_SIMPLE_NAME = "texture.ktx";
+
+void AssetServer::bakeAsset(const AssetHash& assetHash, const AssetPath& assetPath, const QString& filePath) {
+ qDebug() << "Starting bake for: " << assetPath << assetHash;
+ auto it = _pendingBakes.find(assetHash);
+ if (it == _pendingBakes.end()) {
+ auto task = std::make_shared(assetHash, assetPath, filePath);
+ task->setAutoDelete(false);
+ _pendingBakes[assetHash] = task;
+
+ connect(task.get(), &BakeAssetTask::bakeComplete, this, &AssetServer::handleCompletedBake);
+ connect(task.get(), &BakeAssetTask::bakeFailed, this, &AssetServer::handleFailedBake);
+ connect(task.get(), &BakeAssetTask::bakeAborted, this, &AssetServer::handleAbortedBake);
+
+ _bakingTaskPool.start(task.get());
+ } else {
+ qDebug() << "Already in queue";
+ }
+}
+
+QString AssetServer::getPathToAssetHash(const AssetHash& assetHash) {
+ return _filesDirectory.absoluteFilePath(assetHash);
+}
+
+std::pair AssetServer::getAssetStatus(const AssetPath& path, const AssetHash& hash) {
+ auto it = _pendingBakes.find(hash);
+ if (it != _pendingBakes.end()) {
+ return { (*it)->isBaking() ? Baking : Pending, "" };
+ }
+
+ if (path.startsWith(HIDDEN_BAKED_CONTENT_FOLDER)) {
+ return { Baked, "" };
+ }
+
+ auto dotIndex = path.lastIndexOf(".");
+ if (dotIndex == -1) {
+ return { Irrelevant, "" };
+ }
+
+ auto extension = path.mid(dotIndex + 1);
+
+ QString bakedFilename;
+
+ if (BAKEABLE_MODEL_EXTENSIONS.contains(extension)) {
+ bakedFilename = BAKED_MODEL_SIMPLE_NAME;
+ } else if (BAKEABLE_TEXTURE_EXTENSIONS.contains(extension.toLocal8Bit()) && hasMetaFile(hash)) {
+ bakedFilename = BAKED_TEXTURE_SIMPLE_NAME;
+ } else {
+ return { Irrelevant, "" };
+ }
+
+ auto bakedPath = HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + bakedFilename;
+ auto jt = _fileMappings.find(bakedPath);
+ if (jt != _fileMappings.end()) {
+ if (jt->second == hash) {
+ return { NotBaked, "" };
+ } else {
+ return { Baked, "" };
+ }
+ } else {
+ bool loaded;
+ AssetMeta meta;
+
+ std::tie(loaded, meta) = readMetaFile(hash);
+ if (loaded && meta.failedLastBake) {
+ return { Error, meta.lastBakeErrors };
+ }
+ }
+
+ return { Pending, "" };
+}
+
+void AssetServer::bakeAssets() {
+ auto it = _fileMappings.cbegin();
+ for (; it != _fileMappings.cend(); ++it) {
+ auto path = it->first;
+ auto hash = it->second;
+ maybeBake(path, hash);
+ }
+}
+
+void AssetServer::maybeBake(const AssetPath& path, const AssetHash& hash) {
+ if (needsToBeBaked(path, hash)) {
+ qDebug() << "Queuing bake of: " << path;
+ bakeAsset(hash, path, getPathToAssetHash(hash));
+ }
+}
+
+void AssetServer::createEmptyMetaFile(const AssetHash& hash) {
+ QString metaFilePath = "atp:/" + hash + "/meta.json";
+ QFile metaFile { metaFilePath };
+
+ if (!metaFile.exists()) {
+ qDebug() << "Creating metafile for " << hash;
+ if (metaFile.open(QFile::WriteOnly)) {
+ qDebug() << "Created metafile for " << hash;
+ metaFile.write("{}");
+ }
+ }
+}
+
+bool AssetServer::hasMetaFile(const AssetHash& hash) {
+ QString metaFilePath = HIDDEN_BAKED_CONTENT_FOLDER + hash + "/meta.json";
+
+ return _fileMappings.find(metaFilePath) != _fileMappings.end();
+}
+
+bool AssetServer::needsToBeBaked(const AssetPath& path, const AssetHash& assetHash) {
+ if (path.startsWith(HIDDEN_BAKED_CONTENT_FOLDER)) {
+ return false;
+ }
+
+ auto dotIndex = path.lastIndexOf(".");
+ if (dotIndex == -1) {
+ return false;
+ }
+
+ auto extension = path.mid(dotIndex + 1);
+
+ QString bakedFilename;
+
+ bool loaded;
+ AssetMeta meta;
+ std::tie(loaded, meta) = readMetaFile(assetHash);
+
+ // TODO: Allow failed bakes that happened on old versions to be re-baked
+ if (loaded && meta.failedLastBake) {
+ return false;
+ }
+
+ if (BAKEABLE_MODEL_EXTENSIONS.contains(extension)) {
+ bakedFilename = BAKED_MODEL_SIMPLE_NAME;
+ } else if (loaded && BAKEABLE_TEXTURE_EXTENSIONS.contains(extension.toLocal8Bit())) {
+ bakedFilename = BAKED_TEXTURE_SIMPLE_NAME;
+ } else {
+ return false;
+ }
+
+ auto bakedPath = HIDDEN_BAKED_CONTENT_FOLDER + assetHash + "/" + bakedFilename;
+ return _fileMappings.find(bakedPath) == _fileMappings.end();
+}
+
bool interfaceRunning() {
bool result = false;
@@ -67,20 +220,36 @@ void updateConsumedCores() {
if (isInterfaceRunning) {
coreCount = coreCount > MIN_CORES_FOR_MULTICORE ? CPU_AFFINITY_COUNT_HIGH : CPU_AFFINITY_COUNT_LOW;
}
- qDebug() << "Setting max consumed cores to " << coreCount;
+ qCDebug(asset_server) << "Setting max consumed cores to " << coreCount;
setMaxCores(coreCount);
}
AssetServer::AssetServer(ReceivedMessage& message) :
ThreadedAssignment(message),
- _taskPool(this)
+ _transferTaskPool(this),
+ _bakingTaskPool(this)
{
+ // store the current state of image compression so we can reset it when this assignment is complete
+ _wasColorTextureCompressionEnabled = image::isColorTexturesCompressionEnabled();
+ _wasGrayscaleTextureCompressionEnabled = image::isGrayscaleTexturesCompressionEnabled();
+ _wasNormalTextureCompressionEnabled = image::isNormalTexturesCompressionEnabled();
+ _wasCubeTextureCompressionEnabled = image::isCubeTexturesCompressionEnabled();
+
+ // enable compression in image library
+ image::setColorTexturesCompressionEnabled(true);
+ image::setGrayscaleTexturesCompressionEnabled(true);
+ image::setNormalTexturesCompressionEnabled(true);
+ image::setCubeTexturesCompressionEnabled(true);
+
+ BAKEABLE_TEXTURE_EXTENSIONS = TextureBaker::getSupportedFormats();
+ qDebug() << "Supported baking texture formats:" << BAKEABLE_MODEL_EXTENSIONS;
// Most of the work will be I/O bound, reading from disk and constructing packet objects,
// so the ideal is greater than the number of cores on the system.
static const int TASK_POOL_THREAD_COUNT = 50;
- _taskPool.setMaxThreadCount(TASK_POOL_THREAD_COUNT);
+ _transferTaskPool.setMaxThreadCount(TASK_POOL_THREAD_COUNT);
+ _bakingTaskPool.setMaxThreadCount(1);
auto& packetReceiver = DependencyManager::get()->getPacketReceiver();
packetReceiver.registerListener(PacketType::AssetGet, this, "handleAssetGet");
@@ -103,9 +272,39 @@ AssetServer::AssetServer(ReceivedMessage& message) :
#endif
}
+void AssetServer::aboutToFinish() {
+
+ // remove pending transfer tasks
+ _transferTaskPool.clear();
+
+ // abort each of our still running bake tasks, remove pending bakes that were never put on the thread pool
+ auto it = _pendingBakes.begin();
+ while (it != _pendingBakes.end()) {
+ auto pendingRunnable = _bakingTaskPool.tryTake(it->get());
+
+ if (pendingRunnable) {
+ it = _pendingBakes.erase(it);
+ } else {
+ it.value()->abort();
+ ++it;
+ }
+ }
+
+ // make sure all bakers are finished or aborted
+ while (_pendingBakes.size() > 0) {
+ QCoreApplication::processEvents();
+ }
+
+ // re-set defaults in image library
+ image::setColorTexturesCompressionEnabled(_wasCubeTextureCompressionEnabled);
+ image::setGrayscaleTexturesCompressionEnabled(_wasGrayscaleTextureCompressionEnabled);
+ image::setNormalTexturesCompressionEnabled(_wasNormalTextureCompressionEnabled);
+ image::setCubeTexturesCompressionEnabled(_wasCubeTextureCompressionEnabled);
+}
+
void AssetServer::run() {
- qDebug() << "Waiting for connection to domain to request settings from domain-server.";
+ qCDebug(asset_server) << "Waiting for connection to domain to request settings from domain-server.";
// wait until we have the domain-server settings, otherwise we bail
DomainHandler& domainHandler = DependencyManager::get()->getDomainHandler();
@@ -126,7 +325,7 @@ void AssetServer::completeSetup() {
static const QString ASSET_SERVER_SETTINGS_KEY = "asset_server";
if (!settingsObject.contains(ASSET_SERVER_SETTINGS_KEY)) {
- qCritical() << "Received settings from the domain-server with no asset-server section. Stopping assignment.";
+ qCCritical(asset_server) << "Received settings from the domain-server with no asset-server section. Stopping assignment.";
setFinished(true);
return;
}
@@ -141,7 +340,7 @@ void AssetServer::completeSetup() {
const int BITS_PER_MEGABITS = 1000 * 1000;
int maxBandwidth = maxBandwidthFloat * BITS_PER_MEGABITS;
nodeList->setConnectionMaxBandwidth(maxBandwidth);
- qInfo() << "Set maximum bandwith per connection to" << maxBandwidthFloat << "Mb/s."
+ qCInfo(asset_server) << "Set maximum bandwith per connection to" << maxBandwidthFloat << "Mb/s."
" (" << maxBandwidth << "bits/s)";
}
@@ -150,7 +349,7 @@ void AssetServer::completeSetup() {
auto assetsJSONValue = assetServerObject[ASSETS_PATH_OPTION];
if (!assetsJSONValue.isString()) {
- qCritical() << "Received an assets path from the domain-server that could not be parsed. Stopping assignment.";
+ qCCritical(asset_server) << "Received an assets path from the domain-server that could not be parsed. Stopping assignment.";
setFinished(true);
return;
}
@@ -167,19 +366,19 @@ void AssetServer::completeSetup() {
_resourcesDirectory = QDir(absoluteFilePath);
- qDebug() << "Creating resources directory";
+ qCDebug(asset_server) << "Creating resources directory";
_resourcesDirectory.mkpath(".");
_filesDirectory = _resourcesDirectory;
if (!_resourcesDirectory.mkpath(ASSET_FILES_SUBDIR) || !_filesDirectory.cd(ASSET_FILES_SUBDIR)) {
- qCritical() << "Unable to create file directory for asset-server files. Stopping assignment.";
+ qCCritical(asset_server) << "Unable to create file directory for asset-server files. Stopping assignment.";
setFinished(true);
return;
}
// load whatever mappings we currently have from the local file
if (loadMappingsFromFile()) {
- qInfo() << "Serving files from: " << _filesDirectory.path();
+ qCInfo(asset_server) << "Serving files from: " << _filesDirectory.path();
// Check the asset directory to output some information about what we have
auto files = _filesDirectory.entryList(QDir::Files);
@@ -187,18 +386,19 @@ void AssetServer::completeSetup() {
QRegExp hashFileRegex { ASSET_HASH_REGEX_STRING };
auto hashedFiles = files.filter(hashFileRegex);
- qInfo() << "There are" << hashedFiles.size() << "asset files in the asset directory.";
+ qCInfo(asset_server) << "There are" << hashedFiles.size() << "asset files in the asset directory.";
- if (_fileMappings.count() > 0) {
+ if (_fileMappings.size() > 0) {
cleanupUnmappedFiles();
}
nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer });
+
+ bakeAssets();
} else {
- qCritical() << "Asset Server assignment will not continue because mapping file could not be loaded.";
+ qCCritical(asset_server) << "Asset Server assignment will not continue because mapping file could not be loaded.";
setFinished(true);
}
-
}
void AssetServer::cleanupUnmappedFiles() {
@@ -206,21 +406,28 @@ void AssetServer::cleanupUnmappedFiles() {
auto files = _filesDirectory.entryInfoList(QDir::Files);
- // grab the currently mapped hashes
- auto mappedHashes = _fileMappings.values();
-
- qInfo() << "Performing unmapped asset cleanup.";
+ qCInfo(asset_server) << "Performing unmapped asset cleanup.";
for (const auto& fileInfo : files) {
- if (hashFileRegex.exactMatch(fileInfo.fileName())) {
- if (!mappedHashes.contains(fileInfo.fileName())) {
+ auto filename = fileInfo.fileName();
+ if (hashFileRegex.exactMatch(filename)) {
+ bool matched { false };
+ for (auto& pair : _fileMappings) {
+ if (pair.second == filename) {
+ matched = true;
+ break;
+ }
+ }
+ if (!matched) {
// remove the unmapped file
QFile removeableFile { fileInfo.absoluteFilePath() };
if (removeableFile.remove()) {
- qDebug() << "\tDeleted" << fileInfo.fileName() << "from asset files directory since it is unmapped.";
+ qCDebug(asset_server) << "\tDeleted" << filename << "from asset files directory since it is unmapped.";
+
+ removeBakedPathsForDeletedAsset(filename);
} else {
- qDebug() << "\tAttempt to delete unmapped file" << fileInfo.fileName() << "failed";
+ qCDebug(asset_server) << "\tAttempt to delete unmapped file" << filename << "failed";
}
}
}
@@ -238,26 +445,24 @@ void AssetServer::handleAssetMappingOperation(QSharedPointer me
replyPacket->writePrimitive(messageID);
switch (operationType) {
- case AssetMappingOperationType::Get: {
+ case AssetMappingOperationType::Get:
handleGetMappingOperation(*message, senderNode, *replyPacket);
break;
- }
- case AssetMappingOperationType::GetAll: {
+ case AssetMappingOperationType::GetAll:
handleGetAllMappingOperation(*message, senderNode, *replyPacket);
break;
- }
- case AssetMappingOperationType::Set: {
+ case AssetMappingOperationType::Set:
handleSetMappingOperation(*message, senderNode, *replyPacket);
break;
- }
- case AssetMappingOperationType::Delete: {
+ case AssetMappingOperationType::Delete:
handleDeleteMappingsOperation(*message, senderNode, *replyPacket);
break;
- }
- case AssetMappingOperationType::Rename: {
+ case AssetMappingOperationType::Rename:
handleRenameMappingOperation(*message, senderNode, *replyPacket);
break;
- }
+ case AssetMappingOperationType::SetBakingEnabled:
+ handleSetBakingEnabledOperation(*message, senderNode, *replyPacket);
+ break;
}
auto nodeList = DependencyManager::get();
@@ -267,11 +472,75 @@ void AssetServer::handleAssetMappingOperation(QSharedPointer me
void AssetServer::handleGetMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) {
QString assetPath = message.readString();
+ QUrl url { assetPath };
+ assetPath = url.path();
+
auto it = _fileMappings.find(assetPath);
if (it != _fileMappings.end()) {
- auto assetHash = it->toString();
+
+ // check if we should re-direct to a baked asset
+
+ // first, figure out from the mapping extension what type of file this is
+ auto assetPathExtension = assetPath.mid(assetPath.lastIndexOf('.') + 1).toLower();
+ QString bakedRootFile;
+
+ if (BAKEABLE_MODEL_EXTENSIONS.contains(assetPathExtension)) {
+ bakedRootFile = BAKED_MODEL_SIMPLE_NAME;
+ } else if (BAKEABLE_TEXTURE_EXTENSIONS.contains(assetPathExtension.toLocal8Bit())) {
+ bakedRootFile = BAKED_TEXTURE_SIMPLE_NAME;
+ }
+
+ auto originalAssetHash = it->second;
+ QString redirectedAssetHash;
+ QString bakedAssetPath;
+ quint8 wasRedirected = false;
+ bool bakingDisabled = false;
+
+ if (!bakedRootFile.isEmpty()) {
+ // we ran into an asset for which we could have a baked version, let's check if it's ready
+ bakedAssetPath = HIDDEN_BAKED_CONTENT_FOLDER + originalAssetHash + "/" + bakedRootFile;
+ auto bakedIt = _fileMappings.find(bakedAssetPath);
+
+ if (bakedIt != _fileMappings.end()) {
+ if (bakedIt->second != originalAssetHash) {
+ qDebug() << "Did find baked version for: " << originalAssetHash << assetPath;
+ // we found a baked version of the requested asset to serve, redirect to that
+ redirectedAssetHash = bakedIt->second;
+ wasRedirected = true;
+ } else {
+ qDebug() << "Did not find baked version for: " << originalAssetHash << assetPath << " (disabled)";
+ bakingDisabled = true;
+ }
+ } else {
+ qDebug() << "Did not find baked version for: " << originalAssetHash << assetPath;
+ }
+ }
+
replyPacket.writePrimitive(AssetServerError::NoError);
- replyPacket.write(QByteArray::fromHex(assetHash.toUtf8()));
+
+ if (wasRedirected) {
+ qDebug() << "Writing re-directed hash for" << originalAssetHash << "to" << redirectedAssetHash;
+ replyPacket.write(QByteArray::fromHex(redirectedAssetHash.toUtf8()));
+
+ // add a flag saying that this mapping request was redirect
+ replyPacket.writePrimitive(wasRedirected);
+
+ // include the re-directed path in case the caller needs to make relative path requests for the baked asset
+ replyPacket.writeString(bakedAssetPath);
+
+ } else {
+ replyPacket.write(QByteArray::fromHex(originalAssetHash.toUtf8()));
+ replyPacket.writePrimitive(wasRedirected);
+
+ auto query = QUrlQuery(url.query());
+ bool isSkybox = query.hasQueryItem("skybox");
+ if (isSkybox) {
+ writeMetaFile(originalAssetHash);
+ if (!bakingDisabled) {
+ maybeBake(assetPath, originalAssetHash);
+ }
+ }
+ }
} else {
replyPacket.writePrimitive(AssetServerError::AssetNotFound);
}
@@ -280,13 +549,23 @@ void AssetServer::handleGetMappingOperation(ReceivedMessage& message, SharedNode
void AssetServer::handleGetAllMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) {
replyPacket.writePrimitive(AssetServerError::NoError);
- auto count = _fileMappings.size();
+ uint32_t count = (uint32_t)_fileMappings.size();
replyPacket.writePrimitive(count);
for (auto it = _fileMappings.cbegin(); it != _fileMappings.cend(); ++ it) {
- replyPacket.writeString(it.key());
- replyPacket.write(QByteArray::fromHex(it.value().toString().toUtf8()));
+ auto mapping = it->first;
+ auto hash = it->second;
+ replyPacket.writeString(mapping);
+ replyPacket.write(QByteArray::fromHex(hash.toUtf8()));
+
+ BakingStatus status;
+ QString lastBakeErrors;
+ std::tie(status, lastBakeErrors) = getAssetStatus(mapping, hash);
+ replyPacket.writePrimitive(status);
+ if (status == Error) {
+ replyPacket.writeString(lastBakeErrors);
+ }
}
}
@@ -296,11 +575,18 @@ void AssetServer::handleSetMappingOperation(ReceivedMessage& message, SharedNode
auto assetHash = message.read(SHA256_HASH_LENGTH).toHex();
- if (setMapping(assetPath, assetHash)) {
- replyPacket.writePrimitive(AssetServerError::NoError);
+ // don't process a set mapping operation that is inside the hidden baked folder
+ if (assetPath.startsWith(HIDDEN_BAKED_CONTENT_FOLDER)) {
+ qCDebug(asset_server) << "Refusing to process a set mapping operation inside" << HIDDEN_BAKED_CONTENT_FOLDER;
+ replyPacket.writePrimitive(AssetServerError::PermissionDenied);
} else {
- replyPacket.writePrimitive(AssetServerError::MappingOperationFailed);
+ if (setMapping(assetPath, assetHash)) {
+ replyPacket.writePrimitive(AssetServerError::NoError);
+ } else {
+ replyPacket.writePrimitive(AssetServerError::MappingOperationFailed);
+ }
}
+
} else {
replyPacket.writePrimitive(AssetServerError::PermissionDenied);
}
@@ -314,7 +600,14 @@ void AssetServer::handleDeleteMappingsOperation(ReceivedMessage& message, Shared
QStringList mappingsToDelete;
for (int i = 0; i < numberOfDeletedMappings; ++i) {
- mappingsToDelete << message.readString();
+ auto mapping = message.readString();
+
+ if (!mapping.startsWith(HIDDEN_BAKED_CONTENT_FOLDER)) {
+ mappingsToDelete << mapping;
+ } else {
+ qCDebug(asset_server) << "Refusing to delete mapping" << mapping
+ << "that is inside" << HIDDEN_BAKED_CONTENT_FOLDER;
+ }
}
if (deleteMappings(mappingsToDelete)) {
@@ -332,7 +625,38 @@ void AssetServer::handleRenameMappingOperation(ReceivedMessage& message, SharedN
QString oldPath = message.readString();
QString newPath = message.readString();
- if (renameMapping(oldPath, newPath)) {
+ if (oldPath.startsWith(HIDDEN_BAKED_CONTENT_FOLDER) || newPath.startsWith(HIDDEN_BAKED_CONTENT_FOLDER)) {
+ qCDebug(asset_server) << "Cannot rename" << oldPath << "to" << newPath
+ << "since one of the paths is inside" << HIDDEN_BAKED_CONTENT_FOLDER;
+ replyPacket.writePrimitive(AssetServerError::PermissionDenied);
+ } else {
+ if (renameMapping(oldPath, newPath)) {
+ replyPacket.writePrimitive(AssetServerError::NoError);
+ } else {
+ replyPacket.writePrimitive(AssetServerError::MappingOperationFailed);
+ }
+ }
+
+ } else {
+ replyPacket.writePrimitive(AssetServerError::PermissionDenied);
+ }
+}
+
+void AssetServer::handleSetBakingEnabledOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) {
+ if (senderNode->getCanWriteToAssetServer()) {
+ bool enabled { true };
+ message.readPrimitive(&enabled);
+
+ int numberOfMappings{ 0 };
+ message.readPrimitive(&numberOfMappings);
+
+ QStringList mappings;
+
+ for (int i = 0; i < numberOfMappings; ++i) {
+ mappings << message.readString();
+ }
+
+ if (setBakingEnabled(mappings, enabled)) {
replyPacket.writePrimitive(AssetServerError::NoError);
} else {
replyPacket.writePrimitive(AssetServerError::MappingOperationFailed);
@@ -347,7 +671,7 @@ void AssetServer::handleAssetGetInfo(QSharedPointer message, Sh
MessageID messageID;
if (message->getSize() < qint64(SHA256_HASH_LENGTH + sizeof(messageID))) {
- qDebug() << "ERROR bad file request";
+ qCDebug(asset_server) << "ERROR bad file request";
return;
}
@@ -366,11 +690,11 @@ void AssetServer::handleAssetGetInfo(QSharedPointer message, Sh
QFileInfo fileInfo { _filesDirectory.filePath(fileName) };
if (fileInfo.exists() && fileInfo.isReadable()) {
- qDebug() << "Opening file: " << fileInfo.filePath();
+ qCDebug(asset_server) << "Opening file: " << fileInfo.filePath();
replyPacket->writePrimitive(AssetServerError::NoError);
replyPacket->writePrimitive(fileInfo.size());
} else {
- qDebug() << "Asset not found: " << QString(hexHash);
+ qCDebug(asset_server) << "Asset not found: " << QString(hexHash);
replyPacket->writePrimitive(AssetServerError::AssetNotFound);
}
@@ -383,22 +707,22 @@ void AssetServer::handleAssetGet(QSharedPointer message, Shared
auto minSize = qint64(sizeof(MessageID) + SHA256_HASH_LENGTH + sizeof(DataOffset) + sizeof(DataOffset));
if (message->getSize() < minSize) {
- qDebug() << "ERROR bad file request";
+ qCDebug(asset_server) << "ERROR bad file request";
return;
}
// Queue task
auto task = new SendAssetTask(message, senderNode, _filesDirectory);
- _taskPool.start(task);
+ _transferTaskPool.start(task);
}
void AssetServer::handleAssetUpload(QSharedPointer message, SharedNodePointer senderNode) {
if (senderNode->getCanWriteToAssetServer()) {
- qDebug() << "Starting an UploadAssetTask for upload from" << uuidStringWithoutCurlyBraces(senderNode->getUUID());
+ qCDebug(asset_server) << "Starting an UploadAssetTask for upload from" << uuidStringWithoutCurlyBraces(senderNode->getUUID());
auto task = new UploadAssetTask(message, senderNode, _filesDirectory);
- _taskPool.start(task);
+ _transferTaskPool.start(task);
} else {
// this is a node the domain told us is not allowed to rez entities
// for now this also means it isn't allowed to add assets
@@ -502,39 +826,46 @@ bool AssetServer::loadMappingsFromFile() {
auto jsonDocument = QJsonDocument::fromJson(mapFile.readAll(), &error);
if (error.error == QJsonParseError::NoError) {
- _fileMappings = jsonDocument.object().toVariantHash();
-
- // remove any mappings that don't match the expected format
- auto it = _fileMappings.begin();
- while (it != _fileMappings.end()) {
- bool shouldDrop = false;
-
- if (!isValidFilePath(it.key())) {
- qWarning() << "Will not keep mapping for" << it.key() << "since it is not a valid path.";
- shouldDrop = true;
- }
-
- if (!isValidHash(it.value().toString())) {
- qWarning() << "Will not keep mapping for" << it.key() << "since it does not have a valid hash.";
- shouldDrop = true;
- }
-
- if (shouldDrop) {
- it = _fileMappings.erase(it);
- } else {
- ++it;
- }
+ if (!jsonDocument.isObject()) {
+ qCWarning(asset_server) << "Failed to read mapping file, root value in" << mapFilePath << "is not an object";
+ return false;
}
- qInfo() << "Loaded" << _fileMappings.count() << "mappings from map file at" << mapFilePath;
+ //_fileMappings = jsonDocument.object().toVariantHash();
+ auto root = jsonDocument.object();
+ for (auto it = root.begin(); it != root.end(); ++it) {
+ auto key = it.key();
+ auto value = it.value();
+
+ if (!value.isString()) {
+ qCWarning(asset_server) << "Skipping" << key << ":" << value << "because it is not a string";
+ continue;
+ }
+
+ if (!isValidFilePath(key)) {
+ qCWarning(asset_server) << "Will not keep mapping for" << key << "since it is not a valid path.";
+ continue;
+ }
+
+ if (!isValidHash(value.toString())) {
+ qCWarning(asset_server) << "Will not keep mapping for" << key << "since it does not have a valid hash.";
+ continue;
+ }
+
+
+ qDebug() << "Added " << key << value.toString();
+ _fileMappings[key] = value.toString();
+ }
+
+ qCInfo(asset_server) << "Loaded" << _fileMappings.size() << "mappings from map file at" << mapFilePath;
return true;
}
}
- qCritical() << "Failed to read mapping file at" << mapFilePath;
+ qCCritical(asset_server) << "Failed to read mapping file at" << mapFilePath;
return false;
} else {
- qInfo() << "No existing mappings loaded from file since no file was found at" << mapFilePath;
+ qCInfo(asset_server) << "No existing mappings loaded from file since no file was found at" << mapFilePath;
}
return true;
@@ -545,17 +876,22 @@ bool AssetServer::writeMappingsToFile() {
QFile mapFile { mapFilePath };
if (mapFile.open(QIODevice::WriteOnly)) {
- auto jsonObject = QJsonObject::fromVariantHash(_fileMappings);
- QJsonDocument jsonDocument { jsonObject };
+ QJsonObject root;
+
+ for (auto it : _fileMappings) {
+ root[it.first] = it.second;
+ }
+
+ QJsonDocument jsonDocument { root };
if (mapFile.write(jsonDocument.toJson()) != -1) {
- qDebug() << "Wrote JSON mappings to file at" << mapFilePath;
+ qCDebug(asset_server) << "Wrote JSON mappings to file at" << mapFilePath;
return true;
} else {
- qWarning() << "Failed to write JSON mappings to file at" << mapFilePath;
+ qCWarning(asset_server) << "Failed to write JSON mappings to file at" << mapFilePath;
}
} else {
- qWarning() << "Failed to open map file at" << mapFilePath;
+ qCWarning(asset_server) << "Failed to open map file at" << mapFilePath;
}
return false;
@@ -565,17 +901,18 @@ bool AssetServer::setMapping(AssetPath path, AssetHash hash) {
path = path.trimmed();
if (!isValidFilePath(path)) {
- qWarning() << "Cannot set a mapping for invalid path:" << path << "=>" << hash;
+ qCWarning(asset_server) << "Cannot set a mapping for invalid path:" << path << "=>" << hash;
return false;
}
if (!isValidHash(hash)) {
- qWarning() << "Cannot set a mapping for invalid hash" << path << "=>" << hash;
+ qCWarning(asset_server) << "Cannot set a mapping for invalid hash" << path << "=>" << hash;
return false;
}
// remember what the old mapping was in case persistence fails
- auto oldMapping = _fileMappings.value(path).toString();
+ auto it = _fileMappings.find(path);
+ auto oldMapping = it != _fileMappings.end() ? it->second : "";
// update the in memory QHash
_fileMappings[path] = hash;
@@ -583,17 +920,18 @@ bool AssetServer::setMapping(AssetPath path, AssetHash hash) {
// attempt to write to file
if (writeMappingsToFile()) {
// persistence succeeded, we are good to go
- qDebug() << "Set mapping:" << path << "=>" << hash;
+ qCDebug(asset_server) << "Set mapping:" << path << "=>" << hash;
+ maybeBake(path, hash);
return true;
} else {
// failed to persist this mapping to file - put back the old one in our in-memory representation
if (oldMapping.isEmpty()) {
- _fileMappings.remove(path);
+ _fileMappings.erase(_fileMappings.find(path));
} else {
_fileMappings[path] = oldMapping;
}
- qWarning() << "Failed to persist mapping:" << path << "=>" << hash;
+ qCWarning(asset_server) << "Failed to persist mapping:" << path << "=>" << hash;
return false;
}
@@ -603,16 +941,27 @@ bool pathIsFolder(const AssetPath& path) {
return path.endsWith('/');
}
-bool AssetServer::deleteMappings(AssetPathList& paths) {
+void AssetServer::removeBakedPathsForDeletedAsset(AssetHash hash) {
+ // we deleted the file with this hash
+
+ // check if we had baked content for that file that should also now be removed
+ // by calling deleteMappings for the hidden baked content folder for this hash
+ AssetPathList hiddenBakedFolder { HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" };
+
+ qCDebug(asset_server) << "Deleting baked content below" << hiddenBakedFolder << "since" << hash << "was deleted";
+
+ deleteMappings(hiddenBakedFolder);
+}
+
+bool AssetServer::deleteMappings(const AssetPathList& paths) {
// take a copy of the current mappings in case persistence of these deletes fails
auto oldMappings = _fileMappings;
QSet hashesToCheckForDeletion;
// enumerate the paths to delete and remove them all
- for (auto& path : paths) {
-
- path = path.trimmed();
+ for (const auto& rawPath : paths) {
+ auto path = rawPath.trimmed();
// figure out if this path will delete a file or folder
if (pathIsFolder(path)) {
@@ -621,9 +970,9 @@ bool AssetServer::deleteMappings(AssetPathList& paths) {
auto sizeBefore = _fileMappings.size();
while (it != _fileMappings.end()) {
- if (it.key().startsWith(path)) {
+ if (it->first.startsWith(path)) {
// add this hash to the list we need to check for asset removal from the server
- hashesToCheckForDeletion << it.value().toString();
+ hashesToCheckForDeletion << it->second;
it = _fileMappings.erase(it);
} else {
@@ -633,20 +982,22 @@ bool AssetServer::deleteMappings(AssetPathList& paths) {
auto sizeNow = _fileMappings.size();
if (sizeBefore != sizeNow) {
- qDebug() << "Deleted" << sizeBefore - sizeNow << "mappings in folder: " << path;
+ qCDebug(asset_server) << "Deleted" << sizeBefore - sizeNow << "mappings in folder: " << path;
} else {
- qDebug() << "Did not find any mappings to delete in folder:" << path;
+ qCDebug(asset_server) << "Did not find any mappings to delete in folder:" << path;
}
} else {
- auto oldMapping = _fileMappings.take(path);
- if (!oldMapping.isNull()) {
+ auto it = _fileMappings.find(path);
+ if (it != _fileMappings.end()) {
// add this hash to the list we need to check for asset removal from server
- hashesToCheckForDeletion << oldMapping.toString();
+ hashesToCheckForDeletion << it->second;
- qDebug() << "Deleted a mapping:" << path << "=>" << oldMapping.toString();
+ qCDebug(asset_server) << "Deleted a mapping:" << path << "=>" << it->second;
+
+ _fileMappings.erase(it);
} else {
- qDebug() << "Unable to delete a mapping that was not found:" << path;
+ qCDebug(asset_server) << "Unable to delete a mapping that was not found:" << path;
}
}
}
@@ -655,12 +1006,9 @@ bool AssetServer::deleteMappings(AssetPathList& paths) {
if (writeMappingsToFile()) {
// persistence succeeded we are good to go
- // grab the current mapped hashes
- auto mappedHashes = _fileMappings.values();
-
- // enumerate the mapped hashes and clear the list of hashes to check for anything that's present
- for (auto& hashVariant : mappedHashes) {
- auto it = hashesToCheckForDeletion.find(hashVariant.toString());
+ // TODO iterate through hashesToCheckForDeletion instead
+ for (auto& pair : _fileMappings) {
+ auto it = hashesToCheckForDeletion.find(pair.second);
if (it != hashesToCheckForDeletion.end()) {
hashesToCheckForDeletion.erase(it);
}
@@ -672,15 +1020,17 @@ bool AssetServer::deleteMappings(AssetPathList& paths) {
QFile removeableFile { _filesDirectory.absoluteFilePath(hash) };
if (removeableFile.remove()) {
- qDebug() << "\tDeleted" << hash << "from asset files directory since it is now unmapped.";
+ qCDebug(asset_server) << "\tDeleted" << hash << "from asset files directory since it is now unmapped.";
+
+ removeBakedPathsForDeletedAsset(hash);
} else {
- qDebug() << "\tAttempt to delete unmapped file" << hash << "failed";
+ qCDebug(asset_server) << "\tAttempt to delete unmapped file" << hash << "failed";
}
}
return true;
} else {
- qWarning() << "Failed to persist deleted mappings, rolling back";
+ qCWarning(asset_server) << "Failed to persist deleted mappings, rolling back";
// we didn't delete the previous mapping, put it back in our in-memory representation
_fileMappings = oldMappings;
@@ -694,7 +1044,7 @@ bool AssetServer::renameMapping(AssetPath oldPath, AssetPath newPath) {
newPath = newPath.trimmed();
if (!isValidFilePath(oldPath) || !isValidFilePath(newPath)) {
- qWarning() << "Cannot perform rename with invalid paths - both should have leading forward and no ending slashes:"
+ qCWarning(asset_server) << "Cannot perform rename with invalid paths - both should have leading forward and no ending slashes:"
<< oldPath << "=>" << newPath;
return false;
@@ -704,7 +1054,7 @@ bool AssetServer::renameMapping(AssetPath oldPath, AssetPath newPath) {
if (pathIsFolder(oldPath)) {
if (!pathIsFolder(newPath)) {
// we were asked to rename a path to a folder to a path that isn't a folder, this is a fail
- qWarning() << "Cannot rename mapping from folder path" << oldPath << "to file path" << newPath;
+ qCWarning(asset_server) << "Cannot rename mapping from folder path" << oldPath << "to file path" << newPath;
return false;
}
@@ -716,13 +1066,14 @@ bool AssetServer::renameMapping(AssetPath oldPath, AssetPath newPath) {
auto it = oldMappings.begin();
while (it != oldMappings.end()) {
- if (it.key().startsWith(oldPath)) {
- auto newKey = it.key();
+ auto& oldKey = it->first;
+ if (oldKey.startsWith(oldPath)) {
+ auto newKey = oldKey;
newKey.replace(0, oldPath.size(), newPath);
// remove the old version from the in memory file mappings
- _fileMappings.remove(it.key());
- _fileMappings.insert(newKey, it.value());
+ _fileMappings.erase(_fileMappings.find(oldKey));
+ _fileMappings[newKey] = it->second;
}
++it;
@@ -730,52 +1081,54 @@ bool AssetServer::renameMapping(AssetPath oldPath, AssetPath newPath) {
if (writeMappingsToFile()) {
// persisted the changed mappings, return success
- qDebug() << "Renamed folder mapping:" << oldPath << "=>" << newPath;
+ qCDebug(asset_server) << "Renamed folder mapping:" << oldPath << "=>" << newPath;
return true;
} else {
// couldn't persist the renamed paths, rollback and return failure
_fileMappings = oldMappings;
- qWarning() << "Failed to persist renamed folder mapping:" << oldPath << "=>" << newPath;
+ qCWarning(asset_server) << "Failed to persist renamed folder mapping:" << oldPath << "=>" << newPath;
return false;
}
} else {
if (pathIsFolder(newPath)) {
// we were asked to rename a path to a file to a path that is a folder, this is a fail
- qWarning() << "Cannot rename mapping from file path" << oldPath << "to folder path" << newPath;
+ qCWarning(asset_server) << "Cannot rename mapping from file path" << oldPath << "to folder path" << newPath;
return false;
}
// take the old hash to remove the old mapping
- auto oldSourceMapping = _fileMappings.take(oldPath).toString();
+ auto it = _fileMappings.find(oldPath);
+ auto oldSourceMapping = it->second;
+ _fileMappings.erase(it);
// in case we're overwriting, keep the current destination mapping for potential rollback
- auto oldDestinationMapping = _fileMappings.value(newPath);
+ auto oldDestinationIt = _fileMappings.find(newPath);
if (!oldSourceMapping.isEmpty()) {
_fileMappings[newPath] = oldSourceMapping;
if (writeMappingsToFile()) {
// persisted the renamed mapping, return success
- qDebug() << "Renamed mapping:" << oldPath << "=>" << newPath;
+ qCDebug(asset_server) << "Renamed mapping:" << oldPath << "=>" << newPath;
return true;
} else {
// we couldn't persist the renamed mapping, rollback and return failure
_fileMappings[oldPath] = oldSourceMapping;
- if (!oldDestinationMapping.isNull()) {
+ if (oldDestinationIt != _fileMappings.end()) {
// put back the overwritten mapping for the destination path
- _fileMappings[newPath] = oldDestinationMapping.toString();
+ _fileMappings[newPath] = oldDestinationIt->second;
} else {
// clear the new mapping
- _fileMappings.remove(newPath);
+ _fileMappings.erase(_fileMappings.find(newPath));
}
- qDebug() << "Failed to persist renamed mapping:" << oldPath << "=>" << newPath;
+ qCDebug(asset_server) << "Failed to persist renamed mapping:" << oldPath << "=>" << newPath;
return false;
}
@@ -785,3 +1138,252 @@ bool AssetServer::renameMapping(AssetPath oldPath, AssetPath newPath) {
}
}
}
+
+static const QString BAKED_ASSET_SIMPLE_FBX_NAME = "asset.fbx";
+static const QString BAKED_ASSET_SIMPLE_TEXTURE_NAME = "texture.ktx";
+
+QString getBakeMapping(const AssetHash& hash, const QString& relativeFilePath) {
+ return HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + relativeFilePath;
+}
+
+void AssetServer::handleFailedBake(QString originalAssetHash, QString assetPath, QString errors) {
+ qDebug() << "Failed: " << originalAssetHash << assetPath << errors;
+
+ bool loaded;
+ AssetMeta meta;
+
+ std::tie(loaded, meta) = readMetaFile(originalAssetHash);
+
+ meta.failedLastBake = true;
+ meta.lastBakeErrors = errors;
+
+ writeMetaFile(originalAssetHash, meta);
+
+ _pendingBakes.remove(originalAssetHash);
+}
+
+void AssetServer::handleCompletedBake(QString originalAssetHash, QString originalAssetPath,
+ QString bakedTempOutputDir, QVector bakedFilePaths) {
+ bool errorCompletingBake { false };
+ QString errorReason;
+
+ qDebug() << "Completing bake for " << originalAssetHash;
+
+ for (auto& filePath : bakedFilePaths) {
+ // figure out the hash for the contents of this file
+ QFile file(filePath);
+
+ qDebug() << "File path: " << filePath;
+
+ AssetHash bakedFileHash;
+
+ if (file.open(QIODevice::ReadOnly)) {
+ QCryptographicHash hasher(QCryptographicHash::Sha256);
+
+ if (hasher.addData(&file)) {
+ bakedFileHash = hasher.result().toHex();
+ } else {
+ // stop handling this bake, couldn't hash the contents of the file
+ errorCompletingBake = true;
+ errorReason = "Failed to finalize bake";
+ break;
+ }
+
+ // first check that we don't already have this bake file in our list
+ auto bakeFileDestination = _filesDirectory.absoluteFilePath(bakedFileHash);
+ if (!QFile::exists(bakeFileDestination)) {
+ // copy each to our files folder (with the hash as their filename)
+ if (!file.copy(_filesDirectory.absoluteFilePath(bakedFileHash))) {
+ // stop handling this bake, couldn't copy the bake file into our files directory
+ errorCompletingBake = true;
+ errorReason = "Failed to copy baked assets to asset server";
+ break;
+ }
+ }
+
+ // setup the mapping for this bake file
+ auto relativeFilePath = QUrl(filePath).fileName();
+ qDebug() << "Relative file path is: " << relativeFilePath;
+
+ if (relativeFilePath.endsWith(".fbx", Qt::CaseInsensitive)) {
+ // for an FBX file, we replace the filename with the simple name
+ // (to handle the case where two mapped assets have the same hash but different names)
+ relativeFilePath = BAKED_ASSET_SIMPLE_FBX_NAME;
+ } else if (!originalAssetPath.endsWith(".fbx", Qt::CaseInsensitive)) {
+ relativeFilePath = BAKED_ASSET_SIMPLE_TEXTURE_NAME;
+
+ }
+
+ QString bakeMapping = getBakeMapping(originalAssetHash, relativeFilePath);
+
+ // add a mapping (under the hidden baked folder) for this file resulting from the bake
+ if (setMapping(bakeMapping, bakedFileHash)) {
+ qDebug() << "Added" << bakeMapping << "for bake file" << bakedFileHash << "from bake of" << originalAssetHash;
+ } else {
+ qDebug() << "Failed to set mapping";
+ // stop handling this bake, couldn't add a mapping for this bake file
+ errorCompletingBake = true;
+ errorReason = "Failed to finalize bake";
+ break;
+ }
+ } else {
+ qDebug() << "Failed to open baked file: " << filePath;
+ // stop handling this bake, we couldn't open one of the files for reading
+ errorCompletingBake = true;
+ errorReason = "Failed to finalize bake";
+ break;
+ }
+ }
+
+ for (auto& filePath : bakedFilePaths) {
+ QFile file(filePath);
+ if (!file.remove()) {
+ qWarning() << "Failed to remove temporary file:" << filePath;
+ }
+ }
+ if (!QDir(bakedTempOutputDir).rmdir(".")) {
+ qWarning() << "Failed to remove temporary directory:" << bakedTempOutputDir;
+ }
+
+ if (!errorCompletingBake) {
+ // create the meta file to store which version of the baking process we just completed
+ writeMetaFile(originalAssetHash);
+ } else {
+ qWarning() << "Could not complete bake for" << originalAssetHash;
+ AssetMeta meta;
+ meta.failedLastBake = true;
+ meta.lastBakeErrors = errorReason;
+ writeMetaFile(originalAssetHash, meta);
+ }
+
+ _pendingBakes.remove(originalAssetHash);
+}
+
+void AssetServer::handleAbortedBake(QString originalAssetHash, QString assetPath) {
+ // for an aborted bake we don't do anything but remove the BakeAssetTask from our pending bakes
+ _pendingBakes.remove(originalAssetHash);
+}
+
+static const QString BAKE_VERSION_KEY = "bake_version";
+static const QString FAILED_LAST_BAKE_KEY = "failed_last_bake";
+static const QString LAST_BAKE_ERRORS_KEY = "last_bake_errors";
+
+std::pair AssetServer::readMetaFile(AssetHash hash) {
+ auto metaFilePath = HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + "meta.json";
+
+ auto it = _fileMappings.find(metaFilePath);
+ if (it == _fileMappings.end()) {
+ return { false, {} };
+ }
+
+ auto metaFileHash = it->second;
+
+ QFile metaFile(_filesDirectory.absoluteFilePath(metaFileHash));
+
+ if (metaFile.open(QIODevice::ReadOnly)) {
+ auto data = metaFile.readAll();
+ metaFile.close();
+
+ QJsonParseError error;
+ auto doc = QJsonDocument::fromJson(data, &error);
+
+ if (error.error == QJsonParseError::NoError && doc.isObject()) {
+ auto root = doc.object();
+
+ auto bakeVersion = root[BAKE_VERSION_KEY].toInt(-1);
+ auto failedLastBake = root[FAILED_LAST_BAKE_KEY];
+ auto lastBakeErrors = root[LAST_BAKE_ERRORS_KEY];
+
+ if (bakeVersion != -1
+ && failedLastBake.isBool()
+ && lastBakeErrors.isString()) {
+
+ AssetMeta meta;
+ meta.bakeVersion = bakeVersion;
+ meta.failedLastBake = failedLastBake.toBool();
+ meta.lastBakeErrors = lastBakeErrors.toString();
+
+ return { true, meta };
+ } else {
+ qCWarning(asset_server) << "Metafile for" << hash << "has either missing or malformed data.";
+ }
+ }
+ }
+
+ return { false, {} };
+}
+
+bool AssetServer::writeMetaFile(AssetHash originalAssetHash, const AssetMeta& meta) {
+ // construct the JSON that will be in the meta file
+ QJsonObject metaFileObject;
+
+ metaFileObject[BAKE_VERSION_KEY] = meta.bakeVersion;
+ metaFileObject[FAILED_LAST_BAKE_KEY] = meta.failedLastBake;
+ metaFileObject[LAST_BAKE_ERRORS_KEY] = meta.lastBakeErrors;
+
+ QJsonDocument metaFileDoc;
+ metaFileDoc.setObject(metaFileObject);
+
+ auto metaFileJSON = metaFileDoc.toJson();
+
+ // get a hash for the contents of the meta-file
+ AssetHash metaFileHash = QCryptographicHash::hash(metaFileJSON, QCryptographicHash::Sha256).toHex();
+
+ // create the meta file in our files folder, named by the hash of its contents
+ QFile metaFile(_filesDirectory.absoluteFilePath(metaFileHash));
+
+ if (metaFile.open(QIODevice::WriteOnly)) {
+ metaFile.write(metaFileJSON);
+ metaFile.close();
+
+ // add a mapping to the meta file so it doesn't get deleted because it is unmapped
+ auto metaFileMapping = HIDDEN_BAKED_CONTENT_FOLDER + originalAssetHash + "/" + "meta.json";
+
+ return setMapping(metaFileMapping, metaFileHash);
+ } else {
+ return false;
+ }
+}
+
+bool AssetServer::setBakingEnabled(const AssetPathList& paths, bool enabled) {
+ for (const auto& path : paths) {
+ auto it = _fileMappings.find(path);
+ if (it != _fileMappings.end()) {
+ auto hash = it->second;
+
+ auto dotIndex = path.lastIndexOf(".");
+ if (dotIndex == -1) {
+ continue;
+ }
+
+ auto extension = path.mid(dotIndex + 1);
+
+ QString bakedFilename;
+
+ if (BAKEABLE_MODEL_EXTENSIONS.contains(extension)) {
+ bakedFilename = BAKED_MODEL_SIMPLE_NAME;
+ } else if (BAKEABLE_TEXTURE_EXTENSIONS.contains(extension.toLocal8Bit()) && hasMetaFile(hash)) {
+ bakedFilename = BAKED_TEXTURE_SIMPLE_NAME;
+ } else {
+ continue;
+ }
+
+ auto bakedMapping = getBakeMapping(hash, bakedFilename);
+
+ auto it = _fileMappings.find(bakedMapping);
+ bool currentlyDisabled = (it != _fileMappings.end() && it->second == hash);
+
+ if (enabled && currentlyDisabled) {
+ QStringList bakedMappings{ bakedMapping };
+ deleteMappings(bakedMappings);
+ maybeBake(path, hash);
+ qDebug() << "Enabled baking for" << path;
+ } else if (!enabled && !currentlyDisabled) {
+ removeBakedPathsForDeletedAsset(hash);
+ setMapping(bakedMapping, hash);
+ qDebug() << "Disabled baking for" << path;
+ }
+ }
+ }
+ return true;
+}
diff --git a/assignment-client/src/assets/AssetServer.h b/assignment-client/src/assets/AssetServer.h
index 132fb51433..aeb40a416f 100644
--- a/assignment-client/src/assets/AssetServer.h
+++ b/assignment-client/src/assets/AssetServer.h
@@ -14,17 +14,39 @@
#include
#include
+#include
#include
#include "AssetUtils.h"
#include "ReceivedMessage.h"
+
+namespace std {
+ template <>
+ struct hash {
+ size_t operator()(const QString& v) const { return qHash(v); }
+ };
+}
+
+struct AssetMeta {
+ AssetMeta() {
+ }
+
+ int bakeVersion { 0 };
+ bool failedLastBake { false };
+ QString lastBakeErrors;
+};
+
+class BakeAssetTask;
+
class AssetServer : public ThreadedAssignment {
Q_OBJECT
public:
AssetServer(ReceivedMessage& message);
+ void aboutToFinish() override;
+
public slots:
void run() override;
@@ -39,13 +61,14 @@ private slots:
void sendStatsPacket() override;
private:
- using Mappings = QVariantHash;
+ using Mappings = std::unordered_map;
void handleGetMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket);
void handleGetAllMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket);
void handleSetMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket);
void handleDeleteMappingsOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket);
void handleRenameMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket);
+ void handleSetBakingEnabledOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket);
// Mapping file operations must be called from main assignment thread only
bool loadMappingsFromFile();
@@ -55,19 +78,55 @@ private:
bool setMapping(AssetPath path, AssetHash hash);
/// Delete mapping `path`. Returns `true` if deletion of mappings succeeds, else `false`.
- bool deleteMappings(AssetPathList& paths);
+ bool deleteMappings(const AssetPathList& paths);
/// Rename mapping from `oldPath` to `newPath`. Returns true if successful
bool renameMapping(AssetPath oldPath, AssetPath newPath);
- // deletes any unmapped files from the local asset directory
+ bool setBakingEnabled(const AssetPathList& paths, bool enabled);
+
+ /// Delete any unmapped files from the local asset directory
void cleanupUnmappedFiles();
+ QString getPathToAssetHash(const AssetHash& assetHash);
+
+ std::pair getAssetStatus(const AssetPath& path, const AssetHash& hash);
+
+ void bakeAssets();
+ void maybeBake(const AssetPath& path, const AssetHash& hash);
+ void createEmptyMetaFile(const AssetHash& hash);
+ bool hasMetaFile(const AssetHash& hash);
+ bool needsToBeBaked(const AssetPath& path, const AssetHash& assetHash);
+ void bakeAsset(const AssetHash& assetHash, const AssetPath& assetPath, const QString& filePath);
+
+ /// Move baked content for asset to baked directory and update baked status
+ void handleCompletedBake(QString originalAssetHash, QString assetPath, QString bakedTempOutputDir,
+ QVector bakedFilePaths);
+ void handleFailedBake(QString originalAssetHash, QString assetPath, QString errors);
+ void handleAbortedBake(QString originalAssetHash, QString assetPath);
+
+ /// Create meta file to describe baked content for original asset
+ std::pair readMetaFile(AssetHash hash);
+ bool writeMetaFile(AssetHash originalAssetHash, const AssetMeta& meta = AssetMeta());
+
+ /// Remove baked paths when the original asset is deleteds
+ void removeBakedPathsForDeletedAsset(AssetHash originalAssetHash);
+
Mappings _fileMappings;
QDir _resourcesDirectory;
QDir _filesDirectory;
- QThreadPool _taskPool;
+
+ /// Task pool for handling uploads and downloads of assets
+ QThreadPool _transferTaskPool;
+
+ QHash> _pendingBakes;
+ QThreadPool _bakingTaskPool;
+
+ bool _wasColorTextureCompressionEnabled { false };
+ bool _wasGrayscaleTextureCompressionEnabled { false };
+ bool _wasNormalTextureCompressionEnabled { false };
+ bool _wasCubeTextureCompressionEnabled { false };
};
#endif
diff --git a/assignment-client/src/assets/AssetServerLogging.cpp b/assignment-client/src/assets/AssetServerLogging.cpp
new file mode 100644
index 0000000000..39a02107ea
--- /dev/null
+++ b/assignment-client/src/assets/AssetServerLogging.cpp
@@ -0,0 +1,14 @@
+//
+// AssetServerLogging.cpp
+// assignment-client/src/assets
+//
+// Created by Clement Brisset on 8/9/17.
+// Copyright 2017 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#include "AssetServerLogging.h"
+
+Q_LOGGING_CATEGORY(asset_server, "hifi.asset-server")
diff --git a/assignment-client/src/assets/AssetServerLogging.h b/assignment-client/src/assets/AssetServerLogging.h
new file mode 100644
index 0000000000..986e01ecc5
--- /dev/null
+++ b/assignment-client/src/assets/AssetServerLogging.h
@@ -0,0 +1,19 @@
+//
+// AssetServerLogging.h
+// assignment-client/src/assets
+//
+// Created by Clement Brisset on 8/9/17.
+// Copyright 2017 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#ifndef hifi_AssetServerLogging_h
+#define hifi_AssetServerLogging_h
+
+#include
+
+Q_DECLARE_LOGGING_CATEGORY(asset_server)
+
+#endif // hifi_AssetServerLogging_h
diff --git a/assignment-client/src/assets/BakeAssetTask.cpp b/assignment-client/src/assets/BakeAssetTask.cpp
new file mode 100644
index 0000000000..94a0739612
--- /dev/null
+++ b/assignment-client/src/assets/BakeAssetTask.cpp
@@ -0,0 +1,102 @@
+//
+// BakeAssetTask.cpp
+// assignment-client/src/assets
+//
+// Created by Stephen Birarda on 9/18/17.
+// Copyright 2017 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#include "BakeAssetTask.h"
+
+#include
+
+#include
+#include
+
+BakeAssetTask::BakeAssetTask(const AssetHash& assetHash, const AssetPath& assetPath, const QString& filePath) :
+ _assetHash(assetHash),
+ _assetPath(assetPath),
+ _filePath(filePath)
+{
+
+}
+
+void cleanupTempFiles(QString tempOutputDir, std::vector files) {
+ for (const auto& filename : files) {
+ QFile f { filename };
+ if (!f.remove()) {
+ qDebug() << "Failed to remove:" << filename;
+ }
+ }
+ if (!tempOutputDir.isEmpty()) {
+ QDir dir { tempOutputDir };
+ if (!dir.rmdir(".")) {
+ qDebug() << "Failed to remove temporary directory:" << tempOutputDir;
+ }
+ }
+};
+
+void BakeAssetTask::run() {
+ _isBaking.store(true);
+
+ qRegisterMetaType >("QVector");
+ TextureBakerThreadGetter fn = []() -> QThread* { return QThread::currentThread(); };
+
+ QString tempOutputDir;
+
+ if (_assetPath.endsWith(".fbx")) {
+ tempOutputDir = PathUtils::generateTemporaryDir();
+ _baker = std::unique_ptr {
+ new FBXBaker(QUrl("file:///" + _filePath), fn, tempOutputDir)
+ };
+ } else {
+ tempOutputDir = PathUtils::generateTemporaryDir();
+ _baker = std::unique_ptr {
+ new TextureBaker(QUrl("file:///" + _filePath), image::TextureUsage::CUBE_TEXTURE,
+ tempOutputDir)
+ };
+ }
+
+ QEventLoop loop;
+ connect(_baker.get(), &Baker::finished, &loop, &QEventLoop::quit);
+ connect(_baker.get(), &Baker::aborted, &loop, &QEventLoop::quit);
+ QMetaObject::invokeMethod(_baker.get(), "bake", Qt::QueuedConnection);
+ loop.exec();
+
+ if (_baker->wasAborted()) {
+ qDebug() << "Aborted baking: " << _assetHash << _assetPath;
+
+ _wasAborted.store(true);
+
+ cleanupTempFiles(tempOutputDir, _baker->getOutputFiles());
+
+ emit bakeAborted(_assetHash, _assetPath);
+ } else if (_baker->hasErrors()) {
+ qDebug() << "Failed to bake: " << _assetHash << _assetPath << _baker->getErrors();
+
+ auto errors = _baker->getErrors().join('\n'); // Join error list into a single string for convenience
+
+ _didFinish.store(true);
+
+ cleanupTempFiles(tempOutputDir, _baker->getOutputFiles());
+
+ emit bakeFailed(_assetHash, _assetPath, errors);
+ } else {
+ auto vectorOutputFiles = QVector::fromStdVector(_baker->getOutputFiles());
+
+ qDebug() << "Finished baking: " << _assetHash << _assetPath << vectorOutputFiles;
+
+ _didFinish.store(true);
+
+ emit bakeComplete(_assetHash, _assetPath, tempOutputDir, vectorOutputFiles);
+ }
+}
+
+void BakeAssetTask::abort() {
+ if (_baker) {
+ _baker->abort();
+ }
+}
diff --git a/assignment-client/src/assets/BakeAssetTask.h b/assignment-client/src/assets/BakeAssetTask.h
new file mode 100644
index 0000000000..90458ac223
--- /dev/null
+++ b/assignment-client/src/assets/BakeAssetTask.h
@@ -0,0 +1,52 @@
+//
+// BakeAssetTask.h
+// assignment-client/src/assets
+//
+// Created by Stephen Birarda on 9/18/17.
+// Copyright 2017 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#ifndef hifi_BakeAssetTask_h
+#define hifi_BakeAssetTask_h
+
+#include
+
+#include
+#include
+#include
+
+#include
+#include
+
+class BakeAssetTask : public QObject, public QRunnable {
+ Q_OBJECT
+public:
+ BakeAssetTask(const AssetHash& assetHash, const AssetPath& assetPath, const QString& filePath);
+
+ bool isBaking() { return _isBaking.load(); }
+
+ void run() override;
+
+ void abort();
+ bool wasAborted() const { return _wasAborted.load(); }
+ bool didFinish() const { return _didFinish.load(); }
+
+signals:
+ void bakeComplete(QString assetHash, QString assetPath, QString tempOutputDir, QVector outputFiles);
+ void bakeFailed(QString assetHash, QString assetPath, QString errors);
+ void bakeAborted(QString assetHash, QString assetPath);
+
+private:
+ std::atomic _isBaking { false };
+ AssetHash _assetHash;
+ AssetPath _assetPath;
+ QString _filePath;
+ std::unique_ptr _baker;
+ std::atomic _wasAborted { false };
+ std::atomic _didFinish { false };
+};
+
+#endif // hifi_BakeAssetTask_h
diff --git a/assignment-client/src/audio/AudioMixerSlave.cpp b/assignment-client/src/audio/AudioMixerSlave.cpp
index ed63bbc298..a131e266d2 100644
--- a/assignment-client/src/audio/AudioMixerSlave.cpp
+++ b/assignment-client/src/audio/AudioMixerSlave.cpp
@@ -558,7 +558,7 @@ float computeAzimuth(const AvatarAudioStream& listeningNodeStream, const Positio
// produce an oriented angle about the y-axis
glm::vec3 direction = rotatedSourcePosition * (1.0f / fastSqrtf(rotatedSourcePositionLength2));
- float angle = fastAcosf(glm::clamp(-direction.z, -1.0f, 1.0f)); // UNIT_NEG_Z is "forward"
+ float angle = fastAcosf(glm::clamp(-direction.z, -1.0f, 1.0f)); // UNIT_NEG_Z is "forward"
return (direction.x < 0.0f) ? -angle : angle;
} else {
diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp
index 34feafbd4d..5d36a6d261 100644
--- a/assignment-client/src/avatars/AvatarMixerSlave.cpp
+++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp
@@ -170,9 +170,9 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
auto avatarPacketList = NLPacketList::create(PacketType::BulkAvatarData);
// Define the minimum bubble size
- static const glm::vec3 minBubbleSize = glm::vec3(0.3f, 1.3f, 0.3f);
+ static const glm::vec3 minBubbleSize = avatar.getSensorToWorldScale() * glm::vec3(0.3f, 1.3f, 0.3f);
// Define the scale of the box for the current node
- glm::vec3 nodeBoxScale = (nodeData->getPosition() - nodeData->getGlobalBoundingBoxCorner()) * 2.0f;
+ glm::vec3 nodeBoxScale = (nodeData->getPosition() - nodeData->getGlobalBoundingBoxCorner()) * 2.0f * avatar.getSensorToWorldScale();
// Set up the bounding box for the current node
AABox nodeBox(nodeData->getGlobalBoundingBoxCorner(), nodeBoxScale);
// Clamp the size of the bounding box to a minimum scale
@@ -209,7 +209,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
assert(avatarNode); // we can't have gotten here without the avatarData being a valid key in the map
return nodeData->getLastBroadcastTime(avatarNode->getUUID());
}, [&](AvatarSharedPointer avatar)->float{
- glm::vec3 nodeBoxHalfScale = (avatar->getPosition() - avatar->getGlobalBoundingBoxCorner());
+ glm::vec3 nodeBoxHalfScale = (avatar->getPosition() - avatar->getGlobalBoundingBoxCorner() * avatar->getSensorToWorldScale());
return glm::max(nodeBoxHalfScale.x, glm::max(nodeBoxHalfScale.y, nodeBoxHalfScale.z));
}, [&](AvatarSharedPointer avatar)->bool {
if (avatar == thisAvatar) {
@@ -244,9 +244,9 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
// Check to see if the space bubble is enabled
// Don't bother with these checks if the other avatar has their bubble enabled and we're gettingAnyIgnored
if (node->isIgnoreRadiusEnabled() || (avatarNode->isIgnoreRadiusEnabled() && !getsAnyIgnored)) {
-
+ float sensorToWorldScale = avatarNodeData->getAvatarSharedPointer()->getSensorToWorldScale();
// Define the scale of the box for the current other node
- glm::vec3 otherNodeBoxScale = (avatarNodeData->getPosition() - avatarNodeData->getGlobalBoundingBoxCorner()) * 2.0f;
+ glm::vec3 otherNodeBoxScale = (avatarNodeData->getPosition() - avatarNodeData->getGlobalBoundingBoxCorner()) * 2.0f * sensorToWorldScale;
// Set up the bounding box for the current other node
AABox otherNodeBox(avatarNodeData->getGlobalBoundingBoxCorner(), otherNodeBoxScale);
// Clamp the size of the bounding box to a minimum scale
@@ -334,8 +334,9 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
glm::vec3 otherPosition = otherAvatar->getClientGlobalPosition();
+
// determine if avatar is in view, to determine how much data to include...
- glm::vec3 otherNodeBoxScale = (otherPosition - otherNodeData->getGlobalBoundingBoxCorner()) * 2.0f;
+ glm::vec3 otherNodeBoxScale = (otherPosition - otherNodeData->getGlobalBoundingBoxCorner()) * 2.0f * otherAvatar->getSensorToWorldScale();
AABox otherNodeBox(otherNodeData->getGlobalBoundingBoxCorner(), otherNodeBoxScale);
bool isInView = nodeData->otherAvatarInView(otherNodeBox);
diff --git a/assignment-client/src/avatars/ScriptableAvatar.cpp b/assignment-client/src/avatars/ScriptableAvatar.cpp
index c7715d4014..5060891284 100644
--- a/assignment-client/src/avatars/ScriptableAvatar.cpp
+++ b/assignment-client/src/avatars/ScriptableAvatar.cpp
@@ -35,7 +35,7 @@ void ScriptableAvatar::startAnimation(const QString& url, float fps, float prior
return;
}
_animation = DependencyManager::get()->getAnimation(url);
- _animationDetails = AnimationDetails("", QUrl(url), fps, 0, loop, hold, false, firstFrame, lastFrame, true, firstFrame);
+ _animationDetails = AnimationDetails("", QUrl(url), fps, 0, loop, hold, false, firstFrame, lastFrame, true, firstFrame, false);
_maskedJoints = maskedJoints;
}
diff --git a/assignment-client/src/entities/EntityPriorityQueue.cpp b/assignment-client/src/entities/EntityPriorityQueue.cpp
new file mode 100644
index 0000000000..999a05f2e2
--- /dev/null
+++ b/assignment-client/src/entities/EntityPriorityQueue.cpp
@@ -0,0 +1,53 @@
+//
+// EntityPriorityQueue.cpp
+// assignment-client/src/entities
+//
+// Created by Andrew Meadows 2017.08.08
+// Copyright 2017 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#include "EntityPriorityQueue.h"
+
+const float PrioritizedEntity::DO_NOT_SEND = -1.0e-6f;
+const float PrioritizedEntity::FORCE_REMOVE = -1.0e-5f;
+const float PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY = 1.0f;
+
+void ConicalView::set(const ViewFrustum& viewFrustum) {
+ // The ConicalView has two parts: a central sphere (same as ViewFrustum) and a circular cone that bounds the frustum part.
+ // Why? Because approximate intersection tests are much faster to compute for a cone than for a frustum.
+ _position = viewFrustum.getPosition();
+ _direction = viewFrustum.getDirection();
+
+ // We cache the sin and cos of the half angle of the cone that bounds the frustum.
+ // (the math here is left as an exercise for the reader)
+ float A = viewFrustum.getAspectRatio();
+ float t = tanf(0.5f * viewFrustum.getFieldOfView());
+ _cosAngle = 1.0f / sqrtf(1.0f + (A * A + 1.0f) * (t * t));
+ _sinAngle = sqrtf(1.0f - _cosAngle * _cosAngle);
+
+ _radius = viewFrustum.getCenterRadius();
+}
+
+float ConicalView::computePriority(const AACube& cube) const {
+ glm::vec3 p = cube.calcCenter() - _position; // position of bounding sphere in view-frame
+ float d = glm::length(p); // distance to center of bounding sphere
+ float r = 0.5f * cube.getScale(); // radius of bounding sphere
+ if (d < _radius + r) {
+ return r;
+ }
+ // We check the angle between the center of the cube and the _direction of the view.
+ // If it is less than the sum of the half-angle from center of cone to outer edge plus
+ // the half apparent angle of the bounding sphere then it is in view.
+ //
+ // The math here is left as an exercise for the reader with the following hints:
+ // (1) We actually check the dot product of the cube's local position rather than the angle and
+ // (2) we take advantage of this trig identity: cos(A+B) = cos(A)*cos(B) - sin(A)*sin(B)
+ if (glm::dot(p, _direction) > sqrtf(d * d - r * r) * _cosAngle - r * _sinAngle) {
+ const float AVOID_DIVIDE_BY_ZERO = 0.001f;
+ return r / (d + AVOID_DIVIDE_BY_ZERO);
+ }
+ return PrioritizedEntity::DO_NOT_SEND;
+}
diff --git a/assignment-client/src/entities/EntityPriorityQueue.h b/assignment-client/src/entities/EntityPriorityQueue.h
new file mode 100644
index 0000000000..e308d9b549
--- /dev/null
+++ b/assignment-client/src/entities/EntityPriorityQueue.h
@@ -0,0 +1,66 @@
+//
+// EntityPriorityQueue.h
+// assignment-client/src/entities
+//
+// Created by Andrew Meadows 2017.08.08
+// Copyright 2017 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#ifndef hifi_EntityPriorityQueue_h
+#define hifi_EntityPriorityQueue_h
+
+#include
+
+#include
+#include
+
+const float SQRT_TWO_OVER_TWO = 0.7071067811865f;
+const float DEFAULT_VIEW_RADIUS = 10.0f;
+
+// ConicalView is an approximation of a ViewFrustum for fast calculation of sort priority.
+class ConicalView {
+public:
+ ConicalView() {}
+ ConicalView(const ViewFrustum& viewFrustum) { set(viewFrustum); }
+ void set(const ViewFrustum& viewFrustum);
+ float computePriority(const AACube& cube) const;
+private:
+ glm::vec3 _position { 0.0f, 0.0f, 0.0f };
+ glm::vec3 _direction { 0.0f, 0.0f, 1.0f };
+ float _sinAngle { SQRT_TWO_OVER_TWO };
+ float _cosAngle { SQRT_TWO_OVER_TWO };
+ float _radius { DEFAULT_VIEW_RADIUS };
+};
+
+// PrioritizedEntity is a placeholder in a sorted queue.
+class PrioritizedEntity {
+public:
+ static const float DO_NOT_SEND;
+ static const float FORCE_REMOVE;
+ static const float WHEN_IN_DOUBT_PRIORITY;
+
+ PrioritizedEntity(EntityItemPointer entity, float priority, bool forceRemove = false) : _weakEntity(entity), _rawEntityPointer(entity.get()), _priority(priority), _forceRemove(forceRemove) {}
+ EntityItemPointer getEntity() const { return _weakEntity.lock(); }
+ EntityItem* getRawEntityPointer() const { return _rawEntityPointer; }
+ float getPriority() const { return _priority; }
+ bool shouldForceRemove() const { return _forceRemove; }
+
+ class Compare {
+ public:
+ bool operator() (const PrioritizedEntity& A, const PrioritizedEntity& B) { return A._priority < B._priority; }
+ };
+ friend class Compare;
+
+private:
+ EntityItemWeakPointer _weakEntity;
+ EntityItem* _rawEntityPointer;
+ float _priority;
+ bool _forceRemove;
+};
+
+using EntityPriorityQueue = std::priority_queue< PrioritizedEntity, std::vector, PrioritizedEntity::Compare >;
+
+#endif // hifi_EntityPriorityQueue_h
diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp
index 7febdc67e1..03014bae6a 100644
--- a/assignment-client/src/entities/EntityTreeSendThread.cpp
+++ b/assignment-client/src/entities/EntityTreeSendThread.cpp
@@ -13,9 +13,18 @@
#include
#include
+#include
#include "EntityServer.h"
+
+EntityTreeSendThread::EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node) :
+ OctreeSendThread(myServer, node)
+{
+ connect(std::static_pointer_cast(myServer->getOctree()).get(), &EntityTree::editingEntityPointer, this, &EntityTreeSendThread::editingEntityPointer, Qt::QueuedConnection);
+ connect(std::static_pointer_cast(myServer->getOctree()).get(), &EntityTree::deletingEntityPointer, this, &EntityTreeSendThread::deletingEntityPointer, Qt::QueuedConnection);
+}
+
void EntityTreeSendThread::preDistributionProcessing() {
auto node = _node.toStrongRef();
auto nodeData = static_cast(node->getLinkedData());
@@ -80,6 +89,72 @@ void EntityTreeSendThread::preDistributionProcessing() {
}
}
+void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData,
+ bool viewFrustumChanged, bool isFullScene) {
+ if (viewFrustumChanged || _traversal.finished()) {
+ ViewFrustum viewFrustum;
+ nodeData->copyCurrentViewFrustum(viewFrustum);
+ EntityTreeElementPointer root = std::dynamic_pointer_cast(_myServer->getOctree()->getRoot());
+ int32_t lodLevelOffset = nodeData->getBoundaryLevelAdjust() + (viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST);
+ startNewTraversal(viewFrustum, root, lodLevelOffset, nodeData->getUsesFrustum());
+
+ // When the viewFrustum changed the sort order may be incorrect, so we re-sort
+ // and also use the opportunity to cull anything no longer in view
+ if (viewFrustumChanged && !_sendQueue.empty()) {
+ EntityPriorityQueue prevSendQueue;
+ _sendQueue.swap(prevSendQueue);
+ _entitiesInQueue.clear();
+ // Re-add elements from previous traversal if they still need to be sent
+ float lodScaleFactor = _traversal.getCurrentLODScaleFactor();
+ glm::vec3 viewPosition = _traversal.getCurrentView().getPosition();
+ while (!prevSendQueue.empty()) {
+ EntityItemPointer entity = prevSendQueue.top().getEntity();
+ bool forceRemove = prevSendQueue.top().shouldForceRemove();
+ prevSendQueue.pop();
+ if (entity) {
+ if (!forceRemove) {
+ bool success = false;
+ AACube cube = entity->getQueryAACube(success);
+ if (success) {
+ if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) {
+ float priority = _conicalView.computePriority(cube);
+ if (priority != PrioritizedEntity::DO_NOT_SEND) {
+ float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE;
+ float angularDiameter = cube.getScale() / distance;
+ if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) {
+ _sendQueue.push(PrioritizedEntity(entity, priority));
+ _entitiesInQueue.insert(entity.get());
+ }
+ }
+ }
+ } else {
+ _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
+ _entitiesInQueue.insert(entity.get());
+ }
+ } else {
+ _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::FORCE_REMOVE, true));
+ _entitiesInQueue.insert(entity.get());
+ }
+ }
+ }
+ }
+ }
+
+ if (!_traversal.finished()) {
+ quint64 startTime = usecTimestampNow();
+
+ #ifdef DEBUG
+ const uint64_t TIME_BUDGET = 400; // usec
+ #else
+ const uint64_t TIME_BUDGET = 200; // usec
+ #endif
+ _traversal.traverse(TIME_BUDGET);
+ OctreeServer::trackTreeTraverseTime((float)(usecTimestampNow() - startTime));
+ }
+
+ OctreeSendThread::traverseTreeAndSendContents(node, nodeData, viewFrustumChanged, isFullScene);
+}
+
bool EntityTreeSendThread::addAncestorsToExtraFlaggedEntities(const QUuid& filteredEntityID,
EntityItem& entityItem, EntityNodeData& nodeData) {
// check if this entity has a parent that is also an entity
@@ -129,4 +204,288 @@ bool EntityTreeSendThread::addDescendantsToExtraFlaggedEntities(const QUuid& fil
return hasNewChild || hasNewDescendants;
}
+void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTreeElementPointer root, int32_t lodLevelOffset, bool usesViewFrustum) {
+ DiffTraversal::Type type = _traversal.prepareNewTraversal(view, root, lodLevelOffset, usesViewFrustum);
+ // there are three types of traversal:
+ //
+ // (1) FirstTime = at login --> find everything in view
+ // (2) Repeat = view hasn't changed --> find what has changed since last complete traversal
+ // (3) Differential = view has changed --> find what has changed or in new view but not old
+ //
+ // The "scanCallback" we provide to the traversal depends on the type:
+ //
+ // The _conicalView is updated here as a cached view approximation used by the lambdas for efficient
+ // computation of entity sorting priorities.
+ //
+ _conicalView.set(_traversal.getCurrentView());
+ switch (type) {
+ case DiffTraversal::First:
+ // When we get to a First traversal, clear the _knownState
+ _knownState.clear();
+ if (usesViewFrustum) {
+ float lodScaleFactor = _traversal.getCurrentLODScaleFactor();
+ glm::vec3 viewPosition = _traversal.getCurrentView().getPosition();
+ _traversal.setScanCallback([=](DiffTraversal::VisibleElement& next) {
+ next.element->forEachEntity([=](EntityItemPointer entity) {
+ // Bail early if we've already checked this entity this frame
+ if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) {
+ return;
+ }
+ bool success = false;
+ AACube cube = entity->getQueryAACube(success);
+ if (success) {
+ if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) {
+ // Check the size of the entity, it's possible that a "too small to see" entity is included in a
+ // larger octree cell because of its position (for example if it crosses the boundary of a cell it
+ // pops to the next higher cell. So we want to check to see that the entity is large enough to be seen
+ // before we consider including it.
+ float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE;
+ float angularDiameter = cube.getScale() / distance;
+ if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) {
+ float priority = _conicalView.computePriority(cube);
+ _sendQueue.push(PrioritizedEntity(entity, priority));
+ _entitiesInQueue.insert(entity.get());
+ }
+ }
+ } else {
+ _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
+ _entitiesInQueue.insert(entity.get());
+ }
+ });
+ });
+ } else {
+ _traversal.setScanCallback([this](DiffTraversal::VisibleElement& next) {
+ next.element->forEachEntity([this](EntityItemPointer entity) {
+ // Bail early if we've already checked this entity this frame
+ if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) {
+ return;
+ }
+ _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
+ _entitiesInQueue.insert(entity.get());
+ });
+ });
+ }
+ break;
+ case DiffTraversal::Repeat:
+ if (usesViewFrustum) {
+ float lodScaleFactor = _traversal.getCurrentLODScaleFactor();
+ glm::vec3 viewPosition = _traversal.getCurrentView().getPosition();
+ _traversal.setScanCallback([=](DiffTraversal::VisibleElement& next) {
+ uint64_t startOfCompletedTraversal = _traversal.getStartOfCompletedTraversal();
+ if (next.element->getLastChangedContent() > startOfCompletedTraversal) {
+ next.element->forEachEntity([=](EntityItemPointer entity) {
+ // Bail early if we've already checked this entity this frame
+ if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) {
+ return;
+ }
+ auto knownTimestamp = _knownState.find(entity.get());
+ if (knownTimestamp == _knownState.end()) {
+ bool success = false;
+ AACube cube = entity->getQueryAACube(success);
+ if (success) {
+ if (next.intersection == ViewFrustum::INSIDE || _traversal.getCurrentView().cubeIntersectsKeyhole(cube)) {
+ // See the DiffTraversal::First case for an explanation of the "entity is too small" check
+ float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE;
+ float angularDiameter = cube.getScale() / distance;
+ if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) {
+ float priority = _conicalView.computePriority(cube);
+ _sendQueue.push(PrioritizedEntity(entity, priority));
+ _entitiesInQueue.insert(entity.get());
+ }
+ }
+ } else {
+ _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
+ _entitiesInQueue.insert(entity.get());
+ }
+ } else if (entity->getLastEdited() > knownTimestamp->second) {
+ // it is known and it changed --> put it on the queue with any priority
+ // TODO: sort these correctly
+ _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
+ _entitiesInQueue.insert(entity.get());
+ }
+ });
+ }
+ });
+ } else {
+ _traversal.setScanCallback([this](DiffTraversal::VisibleElement& next) {
+ uint64_t startOfCompletedTraversal = _traversal.getStartOfCompletedTraversal();
+ if (next.element->getLastChangedContent() > startOfCompletedTraversal) {
+ next.element->forEachEntity([this](EntityItemPointer entity) {
+ // Bail early if we've already checked this entity this frame
+ if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) {
+ return;
+ }
+ auto knownTimestamp = _knownState.find(entity.get());
+ if (knownTimestamp == _knownState.end() || entity->getLastEdited() > knownTimestamp->second) {
+ _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
+ _entitiesInQueue.insert(entity.get());
+ }
+ });
+ }
+ });
+ }
+ break;
+ case DiffTraversal::Differential:
+ assert(usesViewFrustum);
+ float lodScaleFactor = _traversal.getCurrentLODScaleFactor();
+ glm::vec3 viewPosition = _traversal.getCurrentView().getPosition();
+ float completedLODScaleFactor = _traversal.getCompletedLODScaleFactor();
+ glm::vec3 completedViewPosition = _traversal.getCompletedView().getPosition();
+ _traversal.setScanCallback([=] (DiffTraversal::VisibleElement& next) {
+ next.element->forEachEntity([=](EntityItemPointer entity) {
+ // Bail early if we've already checked this entity this frame
+ if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) {
+ return;
+ }
+ auto knownTimestamp = _knownState.find(entity.get());
+ if (knownTimestamp == _knownState.end()) {
+ bool success = false;
+ AACube cube = entity->getQueryAACube(success);
+ if (success) {
+ if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) {
+ // See the DiffTraversal::First case for an explanation of the "entity is too small" check
+ float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE;
+ float angularDiameter = cube.getScale() / distance;
+ if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) {
+ if (!_traversal.getCompletedView().cubeIntersectsKeyhole(cube)) {
+ float priority = _conicalView.computePriority(cube);
+ _sendQueue.push(PrioritizedEntity(entity, priority));
+ _entitiesInQueue.insert(entity.get());
+ } else {
+ // If this entity was skipped last time because it was too small, we still need to send it
+ distance = glm::distance(cube.calcCenter(), completedViewPosition) + MIN_VISIBLE_DISTANCE;
+ angularDiameter = cube.getScale() / distance;
+ if (angularDiameter <= MIN_ENTITY_ANGULAR_DIAMETER * completedLODScaleFactor) {
+ // this object was skipped in last completed traversal
+ float priority = _conicalView.computePriority(cube);
+ _sendQueue.push(PrioritizedEntity(entity, priority));
+ _entitiesInQueue.insert(entity.get());
+ }
+ }
+ }
+ }
+ } else {
+ _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
+ _entitiesInQueue.insert(entity.get());
+ }
+ } else if (entity->getLastEdited() > knownTimestamp->second) {
+ // it is known and it changed --> put it on the queue with any priority
+ // TODO: sort these correctly
+ _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));
+ _entitiesInQueue.insert(entity.get());
+ }
+ });
+ });
+ break;
+ }
+}
+
+bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) {
+ if (_sendQueue.empty()) {
+ OctreeServer::trackEncodeTime(OctreeServer::SKIP_TIME);
+ return false;
+ }
+ quint64 encodeStart = usecTimestampNow();
+ if (!_packetData.hasContent()) {
+ // This is the beginning of a new packet.
+ // We pack minimal data for this to be accepted as an OctreeElement payload for the root element.
+ // The Octree header bytes look like this:
+ //
+ // 0x00 octalcode for root
+ // 0x00 colors (1 bit where recipient should call: child->readElementDataFromBuffer())
+ // 0xXX childrenInTreeMask (when params.includeExistsBits is true: 1 bit where child is existant)
+ // 0x00 childrenInBufferMask (1 bit where recipient should call: child->readElementData() recursively)
+ const uint8_t zeroByte = 0;
+ _packetData.appendValue(zeroByte); // octalcode
+ _packetData.appendValue(zeroByte); // colors
+ if (params.includeExistsBits) {
+ uint8_t childrenExistBits = 0;
+ EntityTreeElementPointer root = std::dynamic_pointer_cast(_myServer->getOctree()->getRoot());
+ for (int32_t i = 0; i < NUMBER_OF_CHILDREN; ++i) {
+ if (root->getChildAtIndex(i)) {
+ childrenExistBits += (1 << i);
+ }
+ }
+ _packetData.appendValue(childrenExistBits); // childrenInTreeMask
+ }
+ _packetData.appendValue(zeroByte); // childrenInBufferMask
+
+ // Pack zero for numEntities.
+ // But before we do: grab current byteOffset so we can come back later
+ // and update this with the real number.
+ _numEntities = 0;
+ _numEntitiesOffset = _packetData.getUncompressedByteOffset();
+ _packetData.appendValue(_numEntities);
+ }
+
+ LevelDetails entitiesLevel = _packetData.startLevel();
+ uint64_t sendTime = usecTimestampNow();
+ auto nodeData = static_cast(params.nodeData);
+ nodeData->stats.encodeStarted();
+ while(!_sendQueue.empty()) {
+ PrioritizedEntity queuedItem = _sendQueue.top();
+ EntityItemPointer entity = queuedItem.getEntity();
+ if (entity) {
+ // Only send entities that match the jsonFilters, but keep track of everything we've tried to send so we don't try to send it again
+ if (entity->matchesJSONFilters(jsonFilters)) {
+ OctreeElement::AppendState appendEntityState = entity->appendEntityData(&_packetData, params, _extraEncodeData);
+
+ if (appendEntityState != OctreeElement::COMPLETED) {
+ if (appendEntityState == OctreeElement::PARTIAL) {
+ ++_numEntities;
+ }
+ params.stopReason = EncodeBitstreamParams::DIDNT_FIT;
+ break;
+ }
+ ++_numEntities;
+ }
+ if (queuedItem.shouldForceRemove()) {
+ _knownState.erase(entity.get());
+ } else {
+ _knownState[entity.get()] = sendTime;
+ }
+ }
+ _sendQueue.pop();
+ _entitiesInQueue.erase(entity.get());
+ }
+ nodeData->stats.encodeStopped();
+ if (_sendQueue.empty()) {
+ assert(_entitiesInQueue.empty());
+ params.stopReason = EncodeBitstreamParams::FINISHED;
+ _extraEncodeData->entities.clear();
+ }
+
+ if (_numEntities == 0) {
+ _packetData.discardLevel(entitiesLevel);
+ OctreeServer::trackEncodeTime((float)(usecTimestampNow() - encodeStart));
+ return false;
+ }
+ _packetData.endLevel(entitiesLevel);
+ _packetData.updatePriorBytes(_numEntitiesOffset, (const unsigned char*)&_numEntities, sizeof(_numEntities));
+ OctreeServer::trackEncodeTime((float)(usecTimestampNow() - encodeStart));
+ return true;
+}
+
+void EntityTreeSendThread::editingEntityPointer(const EntityItemPointer& entity) {
+ if (entity) {
+ if (_entitiesInQueue.find(entity.get()) == _entitiesInQueue.end() && _knownState.find(entity.get()) != _knownState.end()) {
+ bool success = false;
+ AACube cube = entity->getQueryAACube(success);
+ if (success) {
+ // We can force a removal from _knownState if the current view is used and entity is out of view
+ if (_traversal.doesCurrentUseViewFrustum() && !_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) {
+ _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::FORCE_REMOVE, true));
+ _entitiesInQueue.insert(entity.get());
+ }
+ } else {
+ _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY, true));
+ _entitiesInQueue.insert(entity.get());
+ }
+ }
+ }
+}
+
+void EntityTreeSendThread::deletingEntityPointer(EntityItem* entity) {
+ _knownState.erase(entity);
+}
diff --git a/assignment-client/src/entities/EntityTreeSendThread.h b/assignment-client/src/entities/EntityTreeSendThread.h
index bfb4c743f1..49901491ff 100644
--- a/assignment-client/src/entities/EntityTreeSendThread.h
+++ b/assignment-client/src/entities/EntityTreeSendThread.h
@@ -12,24 +12,55 @@
#ifndef hifi_EntityTreeSendThread_h
#define hifi_EntityTreeSendThread_h
+#include
+
#include "../octree/OctreeSendThread.h"
+#include
+
+#include "EntityPriorityQueue.h"
+
class EntityNodeData;
class EntityItem;
class EntityTreeSendThread : public OctreeSendThread {
+ Q_OBJECT
public:
- EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node) : OctreeSendThread(myServer, node) {};
+ EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node);
protected:
- virtual void preDistributionProcessing() override;
+ void traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData,
+ bool viewFrustumChanged, bool isFullScene) override;
private:
// the following two methods return booleans to indicate if any extra flagged entities were new additions to set
bool addAncestorsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData);
bool addDescendantsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData);
+ void startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset, bool usesViewFrustum);
+ bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) override;
+
+ void preDistributionProcessing() override;
+ bool hasSomethingToSend(OctreeQueryNode* nodeData) override { return !_sendQueue.empty(); }
+ bool shouldStartNewTraversal(OctreeQueryNode* nodeData, bool viewFrustumChanged) override { return viewFrustumChanged || _traversal.finished(); }
+ void preStartNewScene(OctreeQueryNode* nodeData, bool isFullScene) override {};
+ bool shouldTraverseAndSend(OctreeQueryNode* nodeData) override { return true; }
+
+ DiffTraversal _traversal;
+ EntityPriorityQueue _sendQueue;
+ std::unordered_set _entitiesInQueue;
+ std::unordered_map _knownState;
+ ConicalView _conicalView; // cached optimized view for fast priority calculations
+
+ // packet construction stuff
+ EntityTreeElementExtraEncodeDataPointer _extraEncodeData { new EntityTreeElementExtraEncodeData() };
+ int32_t _numEntitiesOffset { 0 };
+ uint16_t _numEntities { 0 };
+
+private slots:
+ void editingEntityPointer(const EntityItemPointer& entity);
+ void deletingEntityPointer(EntityItem* entity);
};
#endif // hifi_EntityTreeSendThread_h
diff --git a/assignment-client/src/octree/OctreeSendThread.cpp b/assignment-client/src/octree/OctreeSendThread.cpp
index 868b377ced..89e3d403fc 100644
--- a/assignment-client/src/octree/OctreeSendThread.cpp
+++ b/assignment-client/src/octree/OctreeSendThread.cpp
@@ -17,7 +17,6 @@
#include
#include
-#include "OctreeQueryNode.h"
#include "OctreeSendThread.h"
#include "OctreeServer.h"
#include "OctreeServerConsts.h"
@@ -27,8 +26,8 @@ quint64 startSceneSleepTime = 0;
quint64 endSceneSleepTime = 0;
OctreeSendThread::OctreeSendThread(OctreeServer* myServer, const SharedNodePointer& node) :
- _myServer(myServer),
_node(node),
+ _myServer(myServer),
_nodeUuid(node->getUUID())
{
QString safeServerName("Octree");
@@ -48,7 +47,7 @@ OctreeSendThread::OctreeSendThread(OctreeServer* myServer, const SharedNodePoint
OctreeSendThread::~OctreeSendThread() {
setIsShuttingDown();
-
+
QString safeServerName("Octree");
if (_myServer) {
safeServerName = _myServer->getMyServerName();
@@ -301,9 +300,25 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode*
return numPackets;
}
+void OctreeSendThread::preStartNewScene(OctreeQueryNode* nodeData, bool isFullScene) {
+ // If we're starting a full scene, then definitely we want to empty the elementBag
+ if (isFullScene) {
+ nodeData->elementBag.deleteAll();
+ }
+
+ // This is the start of "resending" the scene.
+ bool dontRestartSceneOnMove = false; // this is experimental
+ if (dontRestartSceneOnMove) {
+ if (nodeData->elementBag.isEmpty()) {
+ nodeData->elementBag.insert(_myServer->getOctree()->getRoot());
+ }
+ } else {
+ nodeData->elementBag.insert(_myServer->getOctree()->getRoot());
+ }
+}
+
/// Version of octree element distributor that sends the deepest LOD level at once
int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged) {
-
OctreeServer::didPacketDistributor(this);
// if shutting down, exit early
@@ -311,7 +326,7 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
return 0;
}
- if (nodeData->elementBag.isEmpty()) {
+ if (shouldStartNewTraversal(nodeData, viewFrustumChanged)) {
// if we're about to do a fresh pass,
// give our pre-distribution processing a chance to do what it needs
preDistributionProcessing();
@@ -345,7 +360,7 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
// If the current view frustum has changed OR we have nothing to send, then search against
// the current view frustum for things to send.
- if (viewFrustumChanged || nodeData->elementBag.isEmpty()) {
+ if (shouldStartNewTraversal(nodeData, viewFrustumChanged)) {
// if our view has changed, we need to reset these things...
if (viewFrustumChanged) {
@@ -367,11 +382,6 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
_packetsSentThisInterval += handlePacketSend(node, nodeData, isFullScene);
- // If we're starting a full scene, then definitely we want to empty the elementBag
- if (isFullScene) {
- nodeData->elementBag.deleteAll();
- }
-
// TODO: add these to stats page
//::startSceneSleepTime = _usleepTime;
@@ -380,19 +390,11 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
nodeData->stats.sceneStarted(isFullScene, viewFrustumChanged,
_myServer->getOctree()->getRoot(), _myServer->getJurisdiction());
- // This is the start of "resending" the scene.
- bool dontRestartSceneOnMove = false; // this is experimental
- if (dontRestartSceneOnMove) {
- if (nodeData->elementBag.isEmpty()) {
- nodeData->elementBag.insert(_myServer->getOctree()->getRoot());
- }
- } else {
- nodeData->elementBag.insert(_myServer->getOctree()->getRoot());
- }
+ preStartNewScene(nodeData, isFullScene);
}
// If we have something in our elementBag, then turn them into packets and send them out...
- if (!nodeData->elementBag.isEmpty()) {
+ if (shouldTraverseAndSend(nodeData)) {
quint64 start = usecTimestampNow();
traverseTreeAndSendContents(node, nodeData, viewFrustumChanged, isFullScene);
@@ -441,7 +443,7 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
// if after sending packets we've emptied our bag, then we want to remember that we've sent all
// the octree elements from the current view frustum
- if (nodeData->elementBag.isEmpty()) {
+ if (!hasSomethingToSend(nodeData)) {
nodeData->updateLastKnownViewFrustum();
nodeData->setViewSent(true);
@@ -458,84 +460,79 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
return _truePacketsSent;
}
+bool OctreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) {
+ bool somethingToSend = false;
+ OctreeQueryNode* nodeData = static_cast(params.nodeData);
+ if (!nodeData->elementBag.isEmpty()) {
+ quint64 encodeStart = usecTimestampNow();
+ quint64 lockWaitStart = encodeStart;
+
+ _myServer->getOctree()->withReadLock([&]{
+ OctreeServer::trackTreeWaitTime((float)(usecTimestampNow() - lockWaitStart));
+
+ OctreeElementPointer subTree = nodeData->elementBag.extract();
+ if (subTree) {
+ // NOTE: this is where the tree "contents" are actually packed
+ nodeData->stats.encodeStarted();
+ _myServer->getOctree()->encodeTreeBitstream(subTree, &_packetData, nodeData->elementBag, params);
+ nodeData->stats.encodeStopped();
+
+ somethingToSend = true;
+ }
+ });
+
+ OctreeServer::trackEncodeTime((float)(usecTimestampNow() - encodeStart));
+ } else {
+ OctreeServer::trackTreeWaitTime(OctreeServer::SKIP_TIME);
+ OctreeServer::trackEncodeTime(OctreeServer::SKIP_TIME);
+ }
+ return somethingToSend;
+}
+
void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged, bool isFullScene) {
// calculate max number of packets that can be sent during this interval
int clientMaxPacketsPerInterval = std::max(1, (nodeData->getMaxQueryPacketsPerSecond() / INTERVALS_PER_SECOND));
int maxPacketsPerInterval = std::min(clientMaxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval());
int extraPackingAttempts = 0;
- bool completedScene = false;
+
+ // init params once outside the while loop
+ int boundaryLevelAdjustClient = nodeData->getBoundaryLevelAdjust();
+ int boundaryLevelAdjust = boundaryLevelAdjustClient +
+ (viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST);
+ float octreeSizeScale = nodeData->getOctreeSizeScale();
+ EncodeBitstreamParams params(INT_MAX, WANT_EXISTS_BITS, DONT_CHOP,
+ viewFrustumChanged, boundaryLevelAdjust, octreeSizeScale,
+ isFullScene, _myServer->getJurisdiction(), nodeData);
+ // Our trackSend() function is implemented by the server subclass, and will be called back as new entities/data elements are sent
+ params.trackSend = [this](const QUuid& dataID, quint64 dataEdited) {
+ _myServer->trackSend(dataID, dataEdited, _nodeUuid);
+ };
+ nodeData->copyCurrentViewFrustum(params.viewFrustum);
+ if (viewFrustumChanged) {
+ nodeData->copyLastKnownViewFrustum(params.lastViewFrustum);
+ }
bool somethingToSend = true; // assume we have something
+ bool hadSomething = hasSomethingToSend(nodeData);
while (somethingToSend && _packetsSentThisInterval < maxPacketsPerInterval && !nodeData->isShuttingDown()) {
- float lockWaitElapsedUsec = OctreeServer::SKIP_TIME;
- float encodeElapsedUsec = OctreeServer::SKIP_TIME;
float compressAndWriteElapsedUsec = OctreeServer::SKIP_TIME;
float packetSendingElapsedUsec = OctreeServer::SKIP_TIME;
quint64 startInside = usecTimestampNow();
bool lastNodeDidntFit = false; // assume each node fits
- if (!nodeData->elementBag.isEmpty()) {
+ params.stopReason = EncodeBitstreamParams::UNKNOWN; // reset params.stopReason before traversal
- quint64 lockWaitStart = usecTimestampNow();
- _myServer->getOctree()->withReadLock([&]{
- quint64 lockWaitEnd = usecTimestampNow();
- lockWaitElapsedUsec = (float)(lockWaitEnd - lockWaitStart);
- quint64 encodeStart = usecTimestampNow();
+ somethingToSend = traverseTreeAndBuildNextPacketPayload(params, nodeData->getJSONParameters());
- OctreeElementPointer subTree = nodeData->elementBag.extract();
- if (!subTree) {
- return;
- }
-
- float octreeSizeScale = nodeData->getOctreeSizeScale();
- int boundaryLevelAdjustClient = nodeData->getBoundaryLevelAdjust();
-
- int boundaryLevelAdjust = boundaryLevelAdjustClient +
- (viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST);
-
- EncodeBitstreamParams params(INT_MAX, WANT_EXISTS_BITS, DONT_CHOP,
- viewFrustumChanged, boundaryLevelAdjust, octreeSizeScale,
- isFullScene, _myServer->getJurisdiction(), nodeData);
- nodeData->copyCurrentViewFrustum(params.viewFrustum);
- if (viewFrustumChanged) {
- nodeData->copyLastKnownViewFrustum(params.lastViewFrustum);
- }
-
- // Our trackSend() function is implemented by the server subclass, and will be called back
- // during the encodeTreeBitstream() as new entities/data elements are sent
- params.trackSend = [this](const QUuid& dataID, quint64 dataEdited) {
- _myServer->trackSend(dataID, dataEdited, _nodeUuid);
- };
-
- // TODO: should this include the lock time or not? This stat is sent down to the client,
- // it seems like it may be a good idea to include the lock time as part of the encode time
- // are reported to client. Since you can encode without the lock
- nodeData->stats.encodeStarted();
-
- // NOTE: this is where the tree "contents" are actaully packed
- _myServer->getOctree()->encodeTreeBitstream(subTree, &_packetData, nodeData->elementBag, params);
-
- quint64 encodeEnd = usecTimestampNow();
- encodeElapsedUsec = (float)(encodeEnd - encodeStart);
-
- // If after calling encodeTreeBitstream() there are no nodes left to send, then we know we've
- // sent the entire scene. We want to know this below so we'll actually write this content into
- // the packet and send it
- completedScene = nodeData->elementBag.isEmpty();
-
- if (params.stopReason == EncodeBitstreamParams::DIDNT_FIT) {
- lastNodeDidntFit = true;
- extraPackingAttempts++;
- }
-
- nodeData->stats.encodeStopped();
- });
- } else {
- somethingToSend = false; // this will cause us to drop out of the loop...
+ if (params.stopReason == EncodeBitstreamParams::DIDNT_FIT) {
+ lastNodeDidntFit = true;
+ extraPackingAttempts++;
}
+ // If the bag had contents but is now empty then we know we've sent the entire scene.
+ bool completedScene = hadSomething && nodeData->elementBag.isEmpty();
if (completedScene || lastNodeDidntFit) {
// we probably want to flush what has accumulated in nodeData but:
// do we have more data to send? and is there room?
@@ -562,8 +559,7 @@ void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, Octre
if (sendNow) {
quint64 packetSendingStart = usecTimestampNow();
_packetsSentThisInterval += handlePacketSend(node, nodeData);
- quint64 packetSendingEnd = usecTimestampNow();
- packetSendingElapsedUsec = (float)(packetSendingEnd - packetSendingStart);
+ packetSendingElapsedUsec = (float)(usecTimestampNow() - packetSendingStart);
targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE);
extraPackingAttempts = 0;
@@ -576,14 +572,9 @@ void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, Octre
}
_packetData.changeSettings(true, targetSize); // will do reset - NOTE: Always compressed
}
- OctreeServer::trackTreeWaitTime(lockWaitElapsedUsec);
- OctreeServer::trackEncodeTime(encodeElapsedUsec);
OctreeServer::trackCompressAndWriteTime(compressAndWriteElapsedUsec);
OctreeServer::trackPacketSendingTime(packetSendingElapsedUsec);
-
- quint64 endInside = usecTimestampNow();
- quint64 elapsedInsideUsecs = endInside - startInside;
- OctreeServer::trackInsideTime((float)elapsedInsideUsecs);
+ OctreeServer::trackInsideTime((float)(usecTimestampNow() - startInside));
}
if (somethingToSend && _myServer->wantsVerboseDebug()) {
diff --git a/assignment-client/src/octree/OctreeSendThread.h b/assignment-client/src/octree/OctreeSendThread.h
index d158539f57..bc7d2c2588 100644
--- a/assignment-client/src/octree/OctreeSendThread.h
+++ b/assignment-client/src/octree/OctreeSendThread.h
@@ -19,6 +19,7 @@
#include
#include
#include
+#include "OctreeQueryNode.h"
class OctreeQueryNode;
class OctreeServer;
@@ -51,22 +52,27 @@ protected:
/// Implements generic processing behavior for this thread.
virtual bool process() override;
- /// Called before a packetDistributor pass to allow for pre-distribution processing
- virtual void preDistributionProcessing() {};
- virtual void traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged, bool isFullScene);
+ virtual void traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData,
+ bool viewFrustumChanged, bool isFullScene);
+ virtual bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters);
- OctreeServer* _myServer { nullptr };
+ OctreePacketData _packetData;
QWeakPointer _node;
+ OctreeServer* _myServer { nullptr };
private:
+ /// Called before a packetDistributor pass to allow for pre-distribution processing
+ virtual void preDistributionProcessing() {};
int handlePacketSend(SharedNodePointer node, OctreeQueryNode* nodeData, bool dontSuppressDuplicate = false);
int packetDistributor(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged);
+ virtual bool hasSomethingToSend(OctreeQueryNode* nodeData) { return !nodeData->elementBag.isEmpty(); }
+ virtual bool shouldStartNewTraversal(OctreeQueryNode* nodeData, bool viewFrustumChanged) { return viewFrustumChanged || !hasSomethingToSend(nodeData); }
+ virtual void preStartNewScene(OctreeQueryNode* nodeData, bool isFullScene);
+ virtual bool shouldTraverseAndSend(OctreeQueryNode* nodeData) { return hasSomethingToSend(nodeData); }
QUuid _nodeUuid;
- OctreePacketData _packetData;
-
int _truePacketsSent { 0 }; // available for debug stats
int _trueBytesSent { 0 }; // available for debug stats
int _packetsSentThisInterval { 0 }; // used for bandwidth throttle condition
diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp
index af5f2c904e..4a1aade59d 100644
--- a/assignment-client/src/octree/OctreeServer.cpp
+++ b/assignment-client/src/octree/OctreeServer.cpp
@@ -35,7 +35,7 @@
#include
int OctreeServer::_clientCount = 0;
-const int MOVING_AVERAGE_SAMPLE_COUNTS = 1000000;
+const int MOVING_AVERAGE_SAMPLE_COUNTS = 1000;
float OctreeServer::SKIP_TIME = -1.0f; // use this for trackXXXTime() calls for non-times
@@ -60,6 +60,8 @@ int OctreeServer::_longTreeWait = 0;
int OctreeServer::_shortTreeWait = 0;
int OctreeServer::_noTreeWait = 0;
+SimpleMovingAverage OctreeServer::_averageTreeTraverseTime(MOVING_AVERAGE_SAMPLE_COUNTS);
+
SimpleMovingAverage OctreeServer::_averageNodeWaitTime(MOVING_AVERAGE_SAMPLE_COUNTS);
SimpleMovingAverage OctreeServer::_averageCompressAndWriteTime(MOVING_AVERAGE_SAMPLE_COUNTS);
@@ -106,6 +108,8 @@ void OctreeServer::resetSendingStats() {
_shortTreeWait = 0;
_noTreeWait = 0;
+ _averageTreeTraverseTime.reset();
+
_averageNodeWaitTime.reset();
_averageCompressAndWriteTime.reset();
@@ -136,18 +140,19 @@ void OctreeServer::trackEncodeTime(float time) {
if (time == SKIP_TIME) {
_noEncode++;
- time = 0.0f;
- } else if (time <= MAX_SHORT_TIME) {
- _shortEncode++;
- _averageShortEncodeTime.updateAverage(time);
- } else if (time <= MAX_LONG_TIME) {
- _longEncode++;
- _averageLongEncodeTime.updateAverage(time);
} else {
- _extraLongEncode++;
- _averageExtraLongEncodeTime.updateAverage(time);
+ if (time <= MAX_SHORT_TIME) {
+ _shortEncode++;
+ _averageShortEncodeTime.updateAverage(time);
+ } else if (time <= MAX_LONG_TIME) {
+ _longEncode++;
+ _averageLongEncodeTime.updateAverage(time);
+ } else {
+ _extraLongEncode++;
+ _averageExtraLongEncodeTime.updateAverage(time);
+ }
+ _averageEncodeTime.updateAverage(time);
}
- _averageEncodeTime.updateAverage(time);
}
void OctreeServer::trackTreeWaitTime(float time) {
@@ -155,18 +160,19 @@ void OctreeServer::trackTreeWaitTime(float time) {
const float MAX_LONG_TIME = 100.0f;
if (time == SKIP_TIME) {
_noTreeWait++;
- time = 0.0f;
- } else if (time <= MAX_SHORT_TIME) {
- _shortTreeWait++;
- _averageTreeShortWaitTime.updateAverage(time);
- } else if (time <= MAX_LONG_TIME) {
- _longTreeWait++;
- _averageTreeLongWaitTime.updateAverage(time);
} else {
- _extraLongTreeWait++;
- _averageTreeExtraLongWaitTime.updateAverage(time);
+ if (time <= MAX_SHORT_TIME) {
+ _shortTreeWait++;
+ _averageTreeShortWaitTime.updateAverage(time);
+ } else if (time <= MAX_LONG_TIME) {
+ _longTreeWait++;
+ _averageTreeLongWaitTime.updateAverage(time);
+ } else {
+ _extraLongTreeWait++;
+ _averageTreeExtraLongWaitTime.updateAverage(time);
+ }
+ _averageTreeWaitTime.updateAverage(time);
}
- _averageTreeWaitTime.updateAverage(time);
}
void OctreeServer::trackCompressAndWriteTime(float time) {
@@ -174,26 +180,27 @@ void OctreeServer::trackCompressAndWriteTime(float time) {
const float MAX_LONG_TIME = 100.0f;
if (time == SKIP_TIME) {
_noCompress++;
- time = 0.0f;
- } else if (time <= MAX_SHORT_TIME) {
- _shortCompress++;
- _averageShortCompressTime.updateAverage(time);
- } else if (time <= MAX_LONG_TIME) {
- _longCompress++;
- _averageLongCompressTime.updateAverage(time);
} else {
- _extraLongCompress++;
- _averageExtraLongCompressTime.updateAverage(time);
+ if (time <= MAX_SHORT_TIME) {
+ _shortCompress++;
+ _averageShortCompressTime.updateAverage(time);
+ } else if (time <= MAX_LONG_TIME) {
+ _longCompress++;
+ _averageLongCompressTime.updateAverage(time);
+ } else {
+ _extraLongCompress++;
+ _averageExtraLongCompressTime.updateAverage(time);
+ }
+ _averageCompressAndWriteTime.updateAverage(time);
}
- _averageCompressAndWriteTime.updateAverage(time);
}
void OctreeServer::trackPacketSendingTime(float time) {
if (time == SKIP_TIME) {
_noSend++;
- time = 0.0f;
+ } else {
+ _averagePacketSendingTime.updateAverage(time);
}
- _averagePacketSendingTime.updateAverage(time);
}
@@ -202,18 +209,19 @@ void OctreeServer::trackProcessWaitTime(float time) {
const float MAX_LONG_TIME = 100.0f;
if (time == SKIP_TIME) {
_noProcessWait++;
- time = 0.0f;
- } else if (time <= MAX_SHORT_TIME) {
- _shortProcessWait++;
- _averageProcessShortWaitTime.updateAverage(time);
- } else if (time <= MAX_LONG_TIME) {
- _longProcessWait++;
- _averageProcessLongWaitTime.updateAverage(time);
} else {
- _extraLongProcessWait++;
- _averageProcessExtraLongWaitTime.updateAverage(time);
+ if (time <= MAX_SHORT_TIME) {
+ _shortProcessWait++;
+ _averageProcessShortWaitTime.updateAverage(time);
+ } else if (time <= MAX_LONG_TIME) {
+ _longProcessWait++;
+ _averageProcessLongWaitTime.updateAverage(time);
+ } else {
+ _extraLongProcessWait++;
+ _averageProcessExtraLongWaitTime.updateAverage(time);
+ }
+ _averageProcessWaitTime.updateAverage(time);
}
- _averageProcessWaitTime.updateAverage(time);
}
OctreeServer::OctreeServer(ReceivedMessage& message) :
@@ -518,6 +526,10 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
(double)_averageTreeExtraLongWaitTime.getAverage(),
(double)(extraLongVsTotal * AS_PERCENT), _extraLongTreeWait);
+ // traverse
+ float averageTreeTraverseTime = getAverageTreeTraverseTime();
+ statsString += QString().sprintf(" Average tree traverse time: %9.2f usecs\r\n\r\n", (double)averageTreeTraverseTime);
+
// encode
float averageEncodeTime = getAverageEncodeTime();
statsString += QString().sprintf(" Average encode time: %9.2f usecs\r\n", (double)averageEncodeTime);
@@ -879,7 +891,7 @@ OctreeServer::UniqueSendThread OctreeServer::newSendThread(const SharedNodePoint
OctreeServer::UniqueSendThread OctreeServer::createSendThread(const SharedNodePointer& node) {
auto sendThread = newSendThread(node);
-
+
// we want to be notified when the thread finishes
connect(sendThread.get(), &GenericThread::finished, this, &OctreeServer::removeSendThread);
sendThread->initialize(true);
@@ -901,13 +913,13 @@ void OctreeServer::handleOctreeQueryPacket(QSharedPointer messa
// need to make sure we have it in our nodeList.
auto nodeList = DependencyManager::get();
nodeList->updateNodeWithDataFromPacket(message, senderNode);
-
+
auto it = _sendThreads.find(senderNode->getUUID());
if (it == _sendThreads.end()) {
_sendThreads.emplace(senderNode->getUUID(), createSendThread(senderNode));
} else if (it->second->isShuttingDown()) {
_sendThreads.erase(it); // Remove right away and wait on thread to be
-
+
_sendThreads.emplace(senderNode->getUUID(), createSendThread(senderNode));
}
}
@@ -935,39 +947,7 @@ void OctreeServer::handleOctreeFileReplacement(QSharedPointer m
// so here we just store a special file at our persist path
// and then force a stop of the server so that it can pick it up when it relaunches
if (!_persistAbsoluteFilePath.isEmpty()) {
-
- // before we restart the server and make it try and load this data, let's make sure it is valid
- auto compressedOctree = message->getMessage();
- QByteArray jsonOctree;
-
- // assume we have GZipped content
- bool wasCompressed = gunzip(compressedOctree, jsonOctree);
- if (!wasCompressed) {
- // the source was not compressed, assume we were sent regular JSON data
- jsonOctree = compressedOctree;
- }
-
- // check the JSON data to verify it is an object
- if (QJsonDocument::fromJson(jsonOctree).isObject()) {
- if (!wasCompressed) {
- // source was not compressed, we compress it before we write it locally
- gzip(jsonOctree, compressedOctree);
- }
-
- // write the compressed octree data to a special file
- auto replacementFilePath = _persistAbsoluteFilePath.append(OctreePersistThread::REPLACEMENT_FILE_EXTENSION);
- QFile replacementFile(replacementFilePath);
- if (replacementFile.open(QIODevice::WriteOnly) && replacementFile.write(compressedOctree) != -1) {
- // we've now written our replacement file, time to take the server down so it can
- // process it when it comes back up
- qInfo() << "Wrote octree replacement file to" << replacementFilePath << "- stopping server";
- setFinished(true);
- } else {
- qWarning() << "Could not write replacement octree data to file - refusing to process";
- }
- } else {
- qDebug() << "Received replacement octree file that is invalid - refusing to process";
- }
+ replaceContentFromMessageData(message->getMessage());
} else {
qDebug() << "Cannot perform octree file replacement since current persist file path is not yet known";
}
@@ -977,6 +957,68 @@ void OctreeServer::handleOctreeFileReplacement(QSharedPointer m
}
}
+// Message->getMessage() contains a QByteArray representation of the URL to download from
+void OctreeServer::handleOctreeFileReplacementFromURL(QSharedPointer message) {
+ qInfo() << "Received request to replace content from a url";
+ if (!_isFinished && !_isShuttingDown) {
+ // This call comes from Interface, so we skip our domain server check
+ // but confirm that we have permissions to replace content sets
+ if (DependencyManager::get()->getThisNodeCanReplaceContent()) {
+ if (!_persistAbsoluteFilePath.isEmpty()) {
+ // Convert message data into our URL
+ QString url(message->getMessage());
+ QUrl modelsURL = QUrl(url, QUrl::StrictMode);
+ QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
+ QNetworkRequest request(modelsURL);
+ QNetworkReply* reply = networkAccessManager.get(request);
+ connect(reply, &QNetworkReply::finished, [this, reply, modelsURL]() {
+ QNetworkReply::NetworkError networkError = reply->error();
+ if (networkError == QNetworkReply::NoError) {
+ QByteArray contents = reply->readAll();
+ replaceContentFromMessageData(contents);
+ } else {
+ qDebug() << "Error downloading JSON from specified file";
+ }
+ });
+ } else {
+ qDebug() << "Cannot perform octree file replacement since current persist file path is not yet known";
+ }
+ }
+ }
+}
+
+void OctreeServer::replaceContentFromMessageData(QByteArray content) {
+ //Assume we have compressed data
+ auto compressedOctree = content;
+ QByteArray jsonOctree;
+
+ bool wasCompressed = gunzip(compressedOctree, jsonOctree);
+ if (!wasCompressed) {
+ // the source was not compressed, assume we were sent regular JSON data
+ jsonOctree = compressedOctree;
+ }
+ // check the JSON data to verify it is an object
+ if (QJsonDocument::fromJson(jsonOctree).isObject()) {
+ if (!wasCompressed) {
+ // source was not compressed, we compress it before we write it locally
+ gzip(jsonOctree, compressedOctree);
+ }
+ // write the compressed octree data to a special file
+ auto replacementFilePath = _persistAbsoluteFilePath.append(OctreePersistThread::REPLACEMENT_FILE_EXTENSION);
+ QFile replacementFile(replacementFilePath);
+ if (replacementFile.open(QIODevice::WriteOnly) && replacementFile.write(compressedOctree) != -1) {
+ // we've now written our replacement file, time to take the server down so it can
+ // process it when it comes back up
+ qInfo() << "Wrote octree replacement file to" << replacementFilePath << "- stopping server";
+ setFinished(true);
+ } else {
+ qWarning() << "Could not write replacement octree data to file - refusing to process";
+ }
+ } else {
+ qDebug() << "Received replacement octree file that is invalid - refusing to process";
+ }
+}
+
bool OctreeServer::readOptionBool(const QString& optionName, const QJsonObject& settingsSectionObject, bool& result) {
result = false; // assume it doesn't exist
bool optionAvailable = false;
@@ -1051,7 +1093,7 @@ void OctreeServer::readConfiguration() {
if (getPayload().size() > 0) {
parsePayload();
}
-
+
const QJsonObject& settingsObject = DependencyManager::get()->getDomainHandler().getSettingsObject();
QString settingsKey = getMyDomainSettingsKey();
@@ -1178,9 +1220,9 @@ void OctreeServer::run() {
OctreeElement::resetPopulationStatistics();
_tree = createTree();
_tree->setIsServer(true);
-
+
qDebug() << "Waiting for connection to domain to request settings from domain-server.";
-
+
// wait until we have the domain-server settings, otherwise we bail
DomainHandler& domainHandler = DependencyManager::get()->getDomainHandler();
connect(&domainHandler, &DomainHandler::settingsReceived, this, &OctreeServer::domainSettingsRequestComplete);
@@ -1191,9 +1233,9 @@ void OctreeServer::run() {
}
void OctreeServer::domainSettingsRequestComplete() {
-
+
auto nodeList = DependencyManager::get();
-
+
// we need to ask the DS about agents so we can ping/reply with them
nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer });
@@ -1202,26 +1244,27 @@ void OctreeServer::domainSettingsRequestComplete() {
packetReceiver.registerListener(PacketType::OctreeDataNack, this, "handleOctreeDataNackPacket");
packetReceiver.registerListener(PacketType::JurisdictionRequest, this, "handleJurisdictionRequestPacket");
packetReceiver.registerListener(PacketType::OctreeFileReplacement, this, "handleOctreeFileReplacement");
-
+ packetReceiver.registerListener(PacketType::OctreeFileReplacementFromUrl, this, "handleOctreeFileReplacementFromURL");
+
readConfiguration();
-
+
beforeRun(); // after payload has been processed
-
+
connect(nodeList.data(), SIGNAL(nodeAdded(SharedNodePointer)), SLOT(nodeAdded(SharedNodePointer)));
connect(nodeList.data(), SIGNAL(nodeKilled(SharedNodePointer)), SLOT(nodeKilled(SharedNodePointer)));
#ifndef WIN32
setvbuf(stdout, NULL, _IOLBF, 0);
#endif
-
+
nodeList->linkedDataCreateCallback = [this](Node* node) {
auto queryNodeData = createOctreeQueryNode();
queryNodeData->init();
node->setLinkedData(std::move(queryNodeData));
};
-
+
srand((unsigned)time(0));
-
+
// if we want Persistence, set up the local file and persist thread
if (_wantPersist) {
// If persist filename does not exist, let's see if there is one beside the application binary
@@ -1316,24 +1359,24 @@ void OctreeServer::domainSettingsRequestComplete() {
}
}
qDebug() << "Backups will be stored in: " << _backupDirectoryPath;
-
+
// now set up PersistThread
_persistThread = new OctreePersistThread(_tree, _persistAbsoluteFilePath, _backupDirectoryPath, _persistInterval,
_wantBackup, _settings, _debugTimestampNow, _persistAsFileType);
_persistThread->initialize(true);
}
-
+
// set up our jurisdiction broadcaster...
if (_jurisdiction) {
_jurisdiction->setNodeType(getMyNodeType());
}
_jurisdictionSender = new JurisdictionSender(_jurisdiction, getMyNodeType());
_jurisdictionSender->initialize(true);
-
+
// set up our OctreeServerPacketProcessor
_octreeInboundPacketProcessor = new OctreeInboundPacketProcessor(this);
_octreeInboundPacketProcessor->initialize(true);
-
+
// Convert now to tm struct for local timezone
tm* localtm = localtime(&_started);
const int MAX_TIME_LENGTH = 128;
@@ -1345,7 +1388,7 @@ void OctreeServer::domainSettingsRequestComplete() {
if (gmtm) {
strftime(utcBuffer, MAX_TIME_LENGTH, " [%m/%d/%Y %X UTC]", gmtm);
}
-
+
qDebug() << "Now running... started at: " << localBuffer << utcBuffer;
}
@@ -1356,7 +1399,7 @@ void OctreeServer::nodeAdded(SharedNodePointer node) {
void OctreeServer::nodeKilled(SharedNodePointer node) {
quint64 start = usecTimestampNow();
-
+
// Shutdown send thread
auto it = _sendThreads.find(node->getUUID());
if (it != _sendThreads.end()) {
@@ -1402,13 +1445,13 @@ void OctreeServer::aboutToFinish() {
if (_jurisdictionSender) {
_jurisdictionSender->terminating();
}
-
+
// Shut down all the send threads
for (auto& it : _sendThreads) {
auto& sendThread = *it.second;
sendThread.setIsShuttingDown();
}
-
+
// Clear will destruct all the unique_ptr to OctreeSendThreads which will call the GenericThread's dtor
// which waits on the thread to be done before returning
_sendThreads.clear(); // Cleans up all the send threads.
@@ -1528,7 +1571,7 @@ void OctreeServer::sendStatsPacket() {
threadsStats["2. packetDistributor"] = (double)howManyThreadsDidPacketDistributor(oneSecondAgo);
threadsStats["3. handlePacektSend"] = (double)howManyThreadsDidHandlePacketSend(oneSecondAgo);
threadsStats["4. writeDatagram"] = (double)howManyThreadsDidCallWriteDatagram(oneSecondAgo);
-
+
QJsonObject statsArray1;
statsArray1["1. configuration"] = getConfiguration();
statsArray1["2. detailed_stats_url"] = getStatusLink();
@@ -1536,13 +1579,13 @@ void OctreeServer::sendStatsPacket() {
statsArray1["4. persistFileLoadTime"] = getFileLoadTime();
statsArray1["5. clients"] = getCurrentClientCount();
statsArray1["6. threads"] = threadsStats;
-
+
// Octree Stats
QJsonObject octreeStats;
octreeStats["1. elementCount"] = (double)OctreeElement::getNodeCount();
octreeStats["2. internalElementCount"] = (double)OctreeElement::getInternalNodeCount();
octreeStats["3. leafElementCount"] = (double)OctreeElement::getLeafNodeCount();
-
+
// Stats Object 2
QJsonObject dataObject1;
dataObject1["1. totalPackets"] = (double)OctreeSendThread::_totalPackets;
@@ -1555,12 +1598,12 @@ void OctreeServer::sendStatsPacket() {
QJsonObject timingArray1;
timingArray1["1. avgLoopTime"] = getAverageLoopTime();
timingArray1["2. avgInsideTime"] = getAverageInsideTime();
- timingArray1["3. avgTreeLockTime"] = getAverageTreeWaitTime();
+ timingArray1["3. avgTreeTraverseTime"] = getAverageTreeTraverseTime();
timingArray1["4. avgEncodeTime"] = getAverageEncodeTime();
timingArray1["5. avgCompressAndWriteTime"] = getAverageCompressAndWriteTime();
timingArray1["6. avgSendTime"] = getAveragePacketSendingTime();
timingArray1["7. nodeWaitTime"] = getAverageNodeWaitTime();
-
+
QJsonObject statsObject2;
statsObject2["data"] = dataObject1;
statsObject2["timing"] = timingArray1;
@@ -1580,18 +1623,18 @@ void OctreeServer::sendStatsPacket() {
timingArray2["4. avgProcessTimePerElement"] = (double)_octreeInboundPacketProcessor->getAverageProcessTimePerElement();
timingArray2["5. avgLockWaitTimePerElement"] = (double)_octreeInboundPacketProcessor->getAverageLockWaitTimePerElement();
}
-
+
QJsonObject statsObject3;
statsObject3["data"] = dataArray2;
statsObject3["timing"] = timingArray2;
-
+
// Merge everything
QJsonObject jsonArray;
jsonArray["1. misc"] = statsArray1;
jsonArray["2. octree"] = octreeStats;
jsonArray["3. outbound"] = statsObject2;
jsonArray["4. inbound"] = statsObject3;
-
+
QJsonObject statsObject;
statsObject[QString(getMyServerName()) + "Server"] = jsonArray;
addPacketStatsAndSendStatsPacket(statsObject);
diff --git a/assignment-client/src/octree/OctreeServer.h b/assignment-client/src/octree/OctreeServer.h
index 8db8d845de..f930f299f3 100644
--- a/assignment-client/src/octree/OctreeServer.h
+++ b/assignment-client/src/octree/OctreeServer.h
@@ -96,6 +96,9 @@ public:
static void trackTreeWaitTime(float time);
static float getAverageTreeWaitTime() { return _averageTreeWaitTime.getAverage(); }
+ static void trackTreeTraverseTime(float time) { _averageTreeTraverseTime.updateAverage(time); }
+ static float getAverageTreeTraverseTime() { return _averageTreeTraverseTime.getAverage(); }
+
static void trackNodeWaitTime(float time) { _averageNodeWaitTime.updateAverage(time); }
static float getAverageNodeWaitTime() { return _averageNodeWaitTime.getAverage(); }
@@ -137,6 +140,7 @@ private slots:
void handleOctreeDataNackPacket(QSharedPointer message, SharedNodePointer senderNode);
void handleJurisdictionRequestPacket(QSharedPointer message, SharedNodePointer senderNode);
void handleOctreeFileReplacement(QSharedPointer message);
+ void handleOctreeFileReplacementFromURL(QSharedPointer message);
void removeSendThread();
protected:
@@ -161,6 +165,8 @@ protected:
UniqueSendThread createSendThread(const SharedNodePointer& node);
virtual UniqueSendThread newSendThread(const SharedNodePointer& node);
+ void replaceContentFromMessageData(QByteArray content);
+
int _argc;
const char** _argv;
char** _parsedArgV;
@@ -225,6 +231,8 @@ protected:
static int _shortTreeWait;
static int _noTreeWait;
+ static SimpleMovingAverage _averageTreeTraverseTime;
+
static SimpleMovingAverage _averageNodeWaitTime;
static SimpleMovingAverage _averageCompressAndWriteTime;
diff --git a/assignment-client/src/scripts/EntityScriptServer.cpp b/assignment-client/src/scripts/EntityScriptServer.cpp
index e7433e7c05..2bfcdcdf8c 100644
--- a/assignment-client/src/scripts/EntityScriptServer.cpp
+++ b/assignment-client/src/scripts/EntityScriptServer.cpp
@@ -102,7 +102,7 @@ static const QString ENTITY_SCRIPT_SERVER_LOGGING_NAME = "entity-script-server";
void EntityScriptServer::handleReloadEntityServerScriptPacket(QSharedPointer message, SharedNodePointer senderNode) {
// These are temporary checks until we can ensure that nodes eventually disconnect if the Domain Server stops telling them
// about each other.
- if (senderNode->getCanRez() || senderNode->getCanRezTmp()) {
+ if (senderNode->getCanRez() || senderNode->getCanRezTmp() || senderNode->getCanRezCertified() || senderNode->getCanRezTmpCertified()) {
auto entityID = QUuid::fromRfc4122(message->read(NUM_BYTES_RFC4122_UUID));
if (_entityViewer.getTree() && !_shuttingDown) {
@@ -116,7 +116,7 @@ void EntityScriptServer::handleReloadEntityServerScriptPacket(QSharedPointer message, SharedNodePointer senderNode) {
// These are temporary checks until we can ensure that nodes eventually disconnect if the Domain Server stops telling them
// about each other.
- if (senderNode->getCanRez() || senderNode->getCanRezTmp()) {
+ if (senderNode->getCanRez() || senderNode->getCanRezTmp() || senderNode->getCanRezCertified() || senderNode->getCanRezTmpCertified()) {
MessageID messageID;
message->readPrimitive(&messageID);
auto entityID = QUuid::fromRfc4122(message->read(NUM_BYTES_RFC4122_UUID));
@@ -415,8 +415,7 @@ void EntityScriptServer::selectAudioFormat(const QString& selectedCodecName) {
void EntityScriptServer::resetEntitiesScriptEngine() {
auto engineName = QString("about:Entities %1").arg(++_entitiesScriptEngineCount);
- auto newEngine = QSharedPointer(new ScriptEngine(ScriptEngine::ENTITY_SERVER_SCRIPT, NO_SCRIPT, engineName),
- &ScriptEngine::deleteLater);
+ auto newEngine = scriptEngineFactory(ScriptEngine::ENTITY_SERVER_SCRIPT, NO_SCRIPT, engineName);
auto webSocketServerConstructorValue = newEngine->newFunction(WebSocketServerClass::constructor);
newEngine->globalObject().setProperty("WebSocketServer", webSocketServerConstructorValue);
@@ -437,11 +436,14 @@ void EntityScriptServer::resetEntitiesScriptEngine() {
newEngine->runInThread();
- DependencyManager::get()->setEntitiesScriptEngine(newEngine.data());
+ auto newEngineSP = qSharedPointerCast(newEngine);
+ DependencyManager::get()->setEntitiesScriptEngine(newEngineSP);
- disconnect(_entitiesScriptEngine.data(), &ScriptEngine::entityScriptDetailsUpdated, this, &EntityScriptServer::updateEntityPPS);
+ disconnect(_entitiesScriptEngine.data(), &ScriptEngine::entityScriptDetailsUpdated,
+ this, &EntityScriptServer::updateEntityPPS);
_entitiesScriptEngine.swap(newEngine);
- connect(_entitiesScriptEngine.data(), &ScriptEngine::entityScriptDetailsUpdated, this, &EntityScriptServer::updateEntityPPS);
+ connect(_entitiesScriptEngine.data(), &ScriptEngine::entityScriptDetailsUpdated,
+ this, &EntityScriptServer::updateEntityPPS);
}
diff --git a/assignment-client/src/scripts/EntityScriptServer.h b/assignment-client/src/scripts/EntityScriptServer.h
index 84454375e5..e6bd12460b 100644
--- a/assignment-client/src/scripts/EntityScriptServer.h
+++ b/assignment-client/src/scripts/EntityScriptServer.h
@@ -72,7 +72,7 @@ private:
bool _shuttingDown { false };
static int _entitiesScriptEngineCount;
- QSharedPointer _entitiesScriptEngine;
+ ScriptEnginePointer _entitiesScriptEngine;
EntityEditPacketSender _entityEditSender;
EntityTreeHeadlessViewer _entityViewer;
diff --git a/cmake/android/AndroidManifest.xml.in b/cmake/android/AndroidManifest.xml.in
deleted file mode 100755
index aa834f3384..0000000000
--- a/cmake/android/AndroidManifest.xml.in
+++ /dev/null
@@ -1,82 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ${ANDROID_EXTRA_ACTIVITY_XML}
-
-
-
-
-
- ${ANDROID_EXTRA_APPLICATION_XML}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/cmake/android/QtCreateAPK.cmake b/cmake/android/QtCreateAPK.cmake
deleted file mode 100644
index 30ee2f57bd..0000000000
--- a/cmake/android/QtCreateAPK.cmake
+++ /dev/null
@@ -1,159 +0,0 @@
-#
-# QtCreateAPK.cmake
-#
-# Created by Stephen Birarda on 11/18/14.
-# Copyright 2013 High Fidelity, Inc.
-#
-# Distributed under the Apache License, Version 2.0.
-# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
-#
-
-#
-# OPTIONS
-# These options will modify how QtCreateAPK behaves. May be useful if somebody wants to fork.
-# For High Fidelity purposes these should not need to be changed.
-#
-set(ANDROID_THIS_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) # Directory this CMake file is in
-
-if (POLICY CMP0026)
- cmake_policy(SET CMP0026 OLD)
-endif ()
-
-macro(qt_create_apk)
- if(ANDROID_APK_FULLSCREEN)
- set(ANDROID_APK_THEME "android:theme=\"@android:style/Theme.NoTitleBar.Fullscreen\"")
- else()
- set(ANDROID_APK_THEME "")
- endif()
-
- if (UPPER_CMAKE_BUILD_TYPE MATCHES RELEASE)
- set(ANDROID_APK_DEBUGGABLE "false")
- set(ANDROID_APK_RELEASE_LOCAL ${ANDROID_APK_RELEASE})
- else ()
- set(ANDROID_APK_DEBUGGABLE "true")
- set(ANDROID_APK_RELEASE_LOCAL "0")
- endif ()
-
- # Create "AndroidManifest.xml"
- configure_file("${ANDROID_THIS_DIRECTORY}/AndroidManifest.xml.in" "${ANDROID_APK_BUILD_DIR}/AndroidManifest.xml")
-
- # create "strings.xml"
- configure_file("${ANDROID_THIS_DIRECTORY}/strings.xml.in" "${ANDROID_APK_BUILD_DIR}/res/values/strings.xml")
-
- # find androiddeployqt
- find_program(ANDROID_DEPLOY_QT androiddeployqt HINTS "${QT_DIR}/bin")
-
- # set the path to our app shared library
- set(EXECUTABLE_DESTINATION_PATH "${ANDROID_APK_OUTPUT_DIR}/libs/${ANDROID_ABI}/lib${TARGET_NAME}.so")
-
- # add our dependencies to the deployment file
- get_property(_DEPENDENCIES TARGET ${TARGET_NAME} PROPERTY INTERFACE_LINK_LIBRARIES)
-
- foreach(_IGNORE_COPY IN LISTS IGNORE_COPY_LIBS)
- list(REMOVE_ITEM _DEPENDENCIES ${_IGNORE_COPY})
- endforeach()
-
- foreach(_DEP IN LISTS _DEPENDENCIES)
- if (NOT TARGET ${_DEP})
- list(APPEND _DEPS_LIST ${_DEP})
- else ()
- if(NOT _DEP MATCHES "Qt5::.*")
- get_property(_DEP_LOCATION TARGET ${_DEP} PROPERTY "LOCATION_${CMAKE_BUILD_TYPE}")
-
- # recurisvely add libraries which are dependencies of this target
- get_property(_DEP_DEPENDENCIES TARGET ${_DEP} PROPERTY INTERFACE_LINK_LIBRARIES)
-
- foreach(_SUB_DEP IN LISTS _DEP_DEPENDENCIES)
- if (NOT TARGET ${_SUB_DEP} AND NOT _SUB_DEP MATCHES "Qt5::.*")
- list(APPEND _DEPS_LIST ${_SUB_DEP})
- endif()
- endforeach()
-
- list(APPEND _DEPS_LIST ${_DEP_LOCATION})
- endif()
- endif ()
- endforeach()
-
- list(REMOVE_DUPLICATES _DEPS_LIST)
-
- # just copy static libs to apk libs folder - don't add to deps list
- foreach(_LOCATED_DEP IN LISTS _DEPS_LIST)
- if (_LOCATED_DEP MATCHES "\\.a$")
- add_custom_command(
- TARGET ${TARGET_NAME}
- POST_BUILD
- COMMAND ${CMAKE_COMMAND} -E copy ${_LOCATED_DEP} "${ANDROID_APK_OUTPUT_DIR}/libs/${ANDROID_ABI}"
- )
- list(REMOVE_ITEM _DEPS_LIST ${_LOCATED_DEP})
- endif ()
- endforeach()
-
- string(REPLACE ";" "," _DEPS "${_DEPS_LIST}")
-
- configure_file("${ANDROID_THIS_DIRECTORY}/deployment-file.json.in" "${TARGET_NAME}-deployment.json")
-
- # copy the res folder from the target to the apk build dir
- add_custom_target(
- ${TARGET_NAME}-copy-res
- COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/res" "${ANDROID_APK_BUILD_DIR}/res"
- )
-
- # copy the assets folder from the target to the apk build dir
- add_custom_target(
- ${TARGET_NAME}-copy-assets
- COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/assets" "${ANDROID_APK_BUILD_DIR}/assets"
- )
-
- # copy the java folder from src to the apk build dir
- add_custom_target(
- ${TARGET_NAME}-copy-java
- COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/src/java" "${ANDROID_APK_BUILD_DIR}/src"
- )
-
- # copy the libs folder from src to the apk build dir
- add_custom_target(
- ${TARGET_NAME}-copy-libs
- COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/libs" "${ANDROID_APK_BUILD_DIR}/libs"
- )
-
- # handle setup for ndk-gdb
- add_custom_target(${TARGET_NAME}-gdb DEPENDS ${TARGET_NAME})
-
- if (ANDROID_APK_DEBUGGABLE)
- get_property(TARGET_LOCATION TARGET ${TARGET_NAME} PROPERTY LOCATION)
-
- set(GDB_SOLIB_PATH ${ANDROID_APK_BUILD_DIR}/obj/local/${ANDROID_NDK_ABI_NAME}/)
-
- # generate essential Android Makefiles
- file(WRITE ${ANDROID_APK_BUILD_DIR}/jni/Android.mk "APP_ABI := ${ANDROID_NDK_ABI_NAME}\n")
- file(WRITE ${ANDROID_APK_BUILD_DIR}/jni/Application.mk "APP_ABI := ${ANDROID_NDK_ABI_NAME}\n")
-
- # create gdb.setup
- get_directory_property(PROJECT_INCLUDES DIRECTORY ${PROJECT_SOURCE_DIR} INCLUDE_DIRECTORIES)
- string(REGEX REPLACE ";" " " PROJECT_INCLUDES "${PROJECT_INCLUDES}")
- file(WRITE ${ANDROID_APK_BUILD_DIR}/libs/${ANDROID_NDK_ABI_NAME}/gdb.setup "set solib-search-path ${GDB_SOLIB_PATH}\n")
- file(APPEND ${ANDROID_APK_BUILD_DIR}/libs/${ANDROID_NDK_ABI_NAME}/gdb.setup "directory ${PROJECT_INCLUDES}\n")
-
- # copy lib to obj
- add_custom_command(TARGET ${TARGET_NAME}-gdb PRE_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory ${GDB_SOLIB_PATH})
- add_custom_command(TARGET ${TARGET_NAME}-gdb PRE_BUILD COMMAND cp ${TARGET_LOCATION} ${GDB_SOLIB_PATH})
-
- # strip symbols
- add_custom_command(TARGET ${TARGET_NAME}-gdb PRE_BUILD COMMAND ${CMAKE_STRIP} ${TARGET_LOCATION})
- endif ()
-
- # use androiddeployqt to create the apk
- add_custom_target(${TARGET_NAME}-apk
- COMMAND ${ANDROID_DEPLOY_QT} --input "${TARGET_NAME}-deployment.json" --output "${ANDROID_APK_OUTPUT_DIR}" --android-platform android-${ANDROID_API_LEVEL} ${ANDROID_DEPLOY_QT_INSTALL} --verbose --deployment bundled "\\$(ARGS)"
- DEPENDS ${TARGET_NAME} ${TARGET_NAME}-copy-res ${TARGET_NAME}-copy-assets ${TARGET_NAME}-copy-java ${TARGET_NAME}-copy-libs ${TARGET_NAME}-gdb
- )
-
- # rename the APK if the caller asked us to
- if (ANDROID_APK_CUSTOM_NAME)
- add_custom_command(
- TARGET ${TARGET_NAME}-apk
- POST_BUILD
- COMMAND ${CMAKE_COMMAND} -E rename "${ANDROID_APK_OUTPUT_DIR}/bin/QtApp-debug.apk" "${ANDROID_APK_OUTPUT_DIR}/bin/${ANDROID_APK_CUSTOM_NAME}"
- )
- endif ()
-endmacro()
\ No newline at end of file
diff --git a/cmake/android/android.toolchain.cmake b/cmake/android/android.toolchain.cmake
deleted file mode 100755
index 806cef6b18..0000000000
--- a/cmake/android/android.toolchain.cmake
+++ /dev/null
@@ -1,1725 +0,0 @@
-# Copyright (c) 2010-2011, Ethan Rublee
-# Copyright (c) 2011-2014, Andrey Kamaev
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-# 1. Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-#
-# 2. Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
-# and/or other materials provided with the distribution.
-#
-# 3. Neither the name of the copyright holder nor the names of its
-# contributors may be used to endorse or promote products derived from this
-# software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
-
-# ------------------------------------------------------------------------------
-# Android CMake toolchain file, for use with the Android NDK r5-r10d
-# Requires cmake 2.6.3 or newer (2.8.9 or newer is recommended).
-# See home page: https://github.com/taka-no-me/android-cmake
-#
-# Usage Linux:
-# $ export ANDROID_NDK=/absolute/path/to/the/android-ndk
-# $ mkdir build && cd build
-# $ cmake -DCMAKE_TOOLCHAIN_FILE=path/to/the/android.toolchain.cmake ..
-# $ make -j8
-#
-# Usage Windows:
-# You need native port of make to build your project.
-# Android NDK r7 (and newer) already has make.exe on board.
-# For older NDK you have to install it separately.
-# For example, this one: http://gnuwin32.sourceforge.net/packages/make.htm
-#
-# $ SET ANDROID_NDK=C:\absolute\path\to\the\android-ndk
-# $ mkdir build && cd build
-# $ cmake.exe -G"MinGW Makefiles"
-# -DCMAKE_TOOLCHAIN_FILE=path\to\the\android.toolchain.cmake
-# -DCMAKE_MAKE_PROGRAM="%ANDROID_NDK%\prebuilt\windows\bin\make.exe" ..
-# $ cmake.exe --build .
-#
-#
-# Options (can be set as cmake parameters: -D=):
-# ANDROID_NDK=/opt/android-ndk - path to the NDK root.
-# Can be set as environment variable. Can be set only at first cmake run.
-#
-# ANDROID_ABI=armeabi-v7a - specifies the target Application Binary
-# Interface (ABI). This option nearly matches to the APP_ABI variable
-# used by ndk-build tool from Android NDK.
-#
-# Possible targets are:
-# "armeabi" - ARMv5TE based CPU with software floating point operations
-# "armeabi-v7a" - ARMv7 based devices with hardware FPU instructions
-# this ABI target is used by default
-# "armeabi-v7a with NEON" - same as armeabi-v7a, but
-# sets NEON as floating-point unit
-# "armeabi-v7a with VFPV3" - same as armeabi-v7a, but
-# sets VFPV3 as floating-point unit (has 32 registers instead of 16)
-# "armeabi-v6 with VFP" - tuned for ARMv6 processors having VFP
-# "x86" - IA-32 instruction set
-# "mips" - MIPS32 instruction set
-#
-# 64-bit ABIs for NDK r10 and newer:
-# "arm64-v8a" - ARMv8 AArch64 instruction set
-# "x86_64" - Intel64 instruction set (r1)
-# "mips64" - MIPS64 instruction set (r6)
-#
-# ANDROID_NATIVE_API_LEVEL=android-8 - level of Android API compile for.
-# Option is read-only when standalone toolchain is used.
-# Note: building for "android-L" requires explicit configuration.
-#
-# ANDROID_TOOLCHAIN_NAME=arm-linux-androideabi-4.9 - the name of compiler
-# toolchain to be used. The list of possible values depends on the NDK
-# version. For NDK r10c the possible values are:
-#
-# * aarch64-linux-android-4.9
-# * aarch64-linux-android-clang3.4
-# * aarch64-linux-android-clang3.5
-# * arm-linux-androideabi-4.6
-# * arm-linux-androideabi-4.8
-# * arm-linux-androideabi-4.9 (default)
-# * arm-linux-androideabi-clang3.4
-# * arm-linux-androideabi-clang3.5
-# * mips64el-linux-android-4.9
-# * mips64el-linux-android-clang3.4
-# * mips64el-linux-android-clang3.5
-# * mipsel-linux-android-4.6
-# * mipsel-linux-android-4.8
-# * mipsel-linux-android-4.9
-# * mipsel-linux-android-clang3.4
-# * mipsel-linux-android-clang3.5
-# * x86-4.6
-# * x86-4.8
-# * x86-4.9
-# * x86-clang3.4
-# * x86-clang3.5
-# * x86_64-4.9
-# * x86_64-clang3.4
-# * x86_64-clang3.5
-#
-# ANDROID_FORCE_ARM_BUILD=OFF - set ON to generate 32-bit ARM instructions
-# instead of Thumb. Is not available for "armeabi-v6 with VFP"
-# (is forced to be ON) ABI.
-#
-# ANDROID_NO_UNDEFINED=ON - set ON to show all undefined symbols as linker
-# errors even if they are not used.
-#
-# ANDROID_SO_UNDEFINED=OFF - set ON to allow undefined symbols in shared
-# libraries. Automatically turned for NDK r5x and r6x due to GLESv2
-# problems.
-#
-# ANDROID_STL=gnustl_static - specify the runtime to use.
-#
-# Possible values are:
-# none -> Do not configure the runtime.
-# system -> Use the default minimal system C++ runtime library.
-# Implies -fno-rtti -fno-exceptions.
-# Is not available for standalone toolchain.
-# system_re -> Use the default minimal system C++ runtime library.
-# Implies -frtti -fexceptions.
-# Is not available for standalone toolchain.
-# gabi++_static -> Use the GAbi++ runtime as a static library.
-# Implies -frtti -fno-exceptions.
-# Available for NDK r7 and newer.
-# Is not available for standalone toolchain.
-# gabi++_shared -> Use the GAbi++ runtime as a shared library.
-# Implies -frtti -fno-exceptions.
-# Available for NDK r7 and newer.
-# Is not available for standalone toolchain.
-# stlport_static -> Use the STLport runtime as a static library.
-# Implies -fno-rtti -fno-exceptions for NDK before r7.
-# Implies -frtti -fno-exceptions for NDK r7 and newer.
-# Is not available for standalone toolchain.
-# stlport_shared -> Use the STLport runtime as a shared library.
-# Implies -fno-rtti -fno-exceptions for NDK before r7.
-# Implies -frtti -fno-exceptions for NDK r7 and newer.
-# Is not available for standalone toolchain.
-# gnustl_static -> Use the GNU STL as a static library.
-# Implies -frtti -fexceptions.
-# gnustl_shared -> Use the GNU STL as a shared library.
-# Implies -frtti -fno-exceptions.
-# Available for NDK r7b and newer.
-# Silently degrades to gnustl_static if not available.
-# c++_static -> Use the LLVM libc++ runtime as a static library.
-# c++_shared -> Use the LLVM libc++ runtime as a shared library.
-#
-# ANDROID_STL_FORCE_FEATURES=ON - turn rtti and exceptions support based on
-# chosen runtime. If disabled, then the user is responsible for settings
-# these options.
-#
-# What?:
-# android-cmake toolchain searches for NDK/toolchain in the following order:
-# ANDROID_NDK - cmake parameter
-# ANDROID_NDK - environment variable
-# ANDROID_STANDALONE_TOOLCHAIN - cmake parameter
-# ANDROID_STANDALONE_TOOLCHAIN - environment variable
-# ANDROID_NDK - default locations
-# ANDROID_STANDALONE_TOOLCHAIN - default locations
-#
-# Make sure to do the following in your scripts:
-# SET( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${my_cxx_flags}" )
-# SET( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${my_cxx_flags}" )
-# The flags will be prepopulated with critical flags, so don't loose them.
-# Also be aware that toolchain also sets configuration-specific compiler
-# flags and linker flags.
-#
-# ANDROID and BUILD_ANDROID will be set to true, you may test any of these
-# variables to make necessary Android-specific configuration changes.
-#
-# Also ARMEABI or ARMEABI_V7A or X86 or MIPS or ARM64_V8A or X86_64 or MIPS64
-# will be set true, mutually exclusive. NEON option will be set true
-# if VFP is set to NEON.
-#
-# ------------------------------------------------------------------------------
-
-cmake_minimum_required( VERSION 2.6.3 )
-
-if( DEFINED CMAKE_CROSSCOMPILING )
- # subsequent toolchain loading is not really needed
- return()
-endif()
-
-if( CMAKE_TOOLCHAIN_FILE )
- # touch toolchain variable to suppress "unused variable" warning
-endif()
-
-# inherit settings in recursive loads
-get_property( _CMAKE_IN_TRY_COMPILE GLOBAL PROPERTY IN_TRY_COMPILE )
-if( _CMAKE_IN_TRY_COMPILE )
- include( "${CMAKE_CURRENT_SOURCE_DIR}/../android.toolchain.config.cmake" OPTIONAL )
-endif()
-
-# this one is important
-if( CMAKE_VERSION VERSION_GREATER "3.0.99" )
- set( CMAKE_SYSTEM_NAME Android )
-else()
- set( CMAKE_SYSTEM_NAME Linux )
-endif()
-
-# this one not so much
-set( CMAKE_SYSTEM_VERSION 1 )
-
-# rpath makes low sense for Android
-set( CMAKE_SHARED_LIBRARY_RUNTIME_C_FLAG "" )
-set( CMAKE_SKIP_RPATH TRUE CACHE BOOL "If set, runtime paths are not added when using shared libraries." )
-
-# NDK search paths
-set( ANDROID_SUPPORTED_NDK_VERSIONS ${ANDROID_EXTRA_NDK_VERSIONS} -r10d -r10c -r10b -r10 -r9d -r9c -r9b -r9 -r8e -r8d -r8c -r8b -r8 -r7c -r7b -r7 -r6b -r6 -r5c -r5b -r5 "" )
-if( NOT DEFINED ANDROID_NDK_SEARCH_PATHS )
- if( CMAKE_HOST_WIN32 )
- file( TO_CMAKE_PATH "$ENV{PROGRAMFILES}" ANDROID_NDK_SEARCH_PATHS )
- set( ANDROID_NDK_SEARCH_PATHS "${ANDROID_NDK_SEARCH_PATHS}" "$ENV{SystemDrive}/NVPACK" )
- else()
- file( TO_CMAKE_PATH "$ENV{HOME}" ANDROID_NDK_SEARCH_PATHS )
- set( ANDROID_NDK_SEARCH_PATHS /opt "${ANDROID_NDK_SEARCH_PATHS}/NVPACK" )
- endif()
-endif()
-if( NOT DEFINED ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH )
- set( ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH /opt/android-toolchain )
-endif()
-
-# known ABIs
-set( ANDROID_SUPPORTED_ABIS_arm "armeabi-v7a;armeabi;armeabi-v7a with NEON;armeabi-v7a with VFPV3;armeabi-v6 with VFP" )
-set( ANDROID_SUPPORTED_ABIS_arm64 "arm64-v8a" )
-set( ANDROID_SUPPORTED_ABIS_x86 "x86" )
-set( ANDROID_SUPPORTED_ABIS_x86_64 "x86_64" )
-set( ANDROID_SUPPORTED_ABIS_mips "mips" )
-set( ANDROID_SUPPORTED_ABIS_mips64 "mips64" )
-
-# API level defaults
-set( ANDROID_DEFAULT_NDK_API_LEVEL 8 )
-set( ANDROID_DEFAULT_NDK_API_LEVEL_arm64 21 )
-set( ANDROID_DEFAULT_NDK_API_LEVEL_x86 9 )
-set( ANDROID_DEFAULT_NDK_API_LEVEL_x86_64 21 )
-set( ANDROID_DEFAULT_NDK_API_LEVEL_mips 9 )
-set( ANDROID_DEFAULT_NDK_API_LEVEL_mips64 21 )
-
-
-macro( __LIST_FILTER listvar regex )
- if( ${listvar} )
- foreach( __val ${${listvar}} )
- if( __val MATCHES "${regex}" )
- list( REMOVE_ITEM ${listvar} "${__val}" )
- endif()
- endforeach()
- endif()
-endmacro()
-
-macro( __INIT_VARIABLE var_name )
- set( __test_path 0 )
- foreach( __var ${ARGN} )
- if( __var STREQUAL "PATH" )
- set( __test_path 1 )
- break()
- endif()
- endforeach()
-
- if( __test_path AND NOT EXISTS "${${var_name}}" )
- unset( ${var_name} CACHE )
- endif()
-
- if( " ${${var_name}}" STREQUAL " " )
- set( __values 0 )
- foreach( __var ${ARGN} )
- if( __var STREQUAL "VALUES" )
- set( __values 1 )
- elseif( NOT __var STREQUAL "PATH" )
- if( __var MATCHES "^ENV_.*$" )
- string( REPLACE "ENV_" "" __var "${__var}" )
- set( __value "$ENV{${__var}}" )
- elseif( DEFINED ${__var} )
- set( __value "${${__var}}" )
- elseif( __values )
- set( __value "${__var}" )
- else()
- set( __value "" )
- endif()
-
- if( NOT " ${__value}" STREQUAL " " AND (NOT __test_path OR EXISTS "${__value}") )
- set( ${var_name} "${__value}" )
- break()
- endif()
- endif()
- endforeach()
- unset( __value )
- unset( __values )
- endif()
-
- if( __test_path )
- file( TO_CMAKE_PATH "${${var_name}}" ${var_name} )
- endif()
- unset( __test_path )
-endmacro()
-
-macro( __DETECT_NATIVE_API_LEVEL _var _path )
- set( __ndkApiLevelRegex "^[\t ]*#define[\t ]+__ANDROID_API__[\t ]+([0-9]+)[\t ]*.*$" )
- file( STRINGS ${_path} __apiFileContent REGEX "${__ndkApiLevelRegex}" )
- if( NOT __apiFileContent )
- message( SEND_ERROR "Could not get Android native API level. Probably you have specified invalid level value, or your copy of NDK/toolchain is broken." )
- endif()
- string( REGEX REPLACE "${__ndkApiLevelRegex}" "\\1" ${_var} "${__apiFileContent}" )
- unset( __apiFileContent )
- unset( __ndkApiLevelRegex )
-endmacro()
-
-macro( __DETECT_TOOLCHAIN_MACHINE_NAME _var _root )
- if( EXISTS "${_root}" )
- file( GLOB __gccExePath RELATIVE "${_root}/bin/" "${_root}/bin/*-gcc${TOOL_OS_SUFFIX}" )
- __LIST_FILTER( __gccExePath "^[.].*" )
- list( LENGTH __gccExePath __gccExePathsCount )
- if( NOT __gccExePathsCount EQUAL 1 AND NOT _CMAKE_IN_TRY_COMPILE )
- message( WARNING "Could not determine machine name for compiler from ${_root}" )
- set( ${_var} "" )
- else()
- get_filename_component( __gccExeName "${__gccExePath}" NAME_WE )
- string( REPLACE "-gcc" "" ${_var} "${__gccExeName}" )
- endif()
- unset( __gccExePath )
- unset( __gccExePathsCount )
- unset( __gccExeName )
- else()
- set( ${_var} "" )
- endif()
-endmacro()
-
-
-# fight against cygwin
-set( ANDROID_FORBID_SYGWIN TRUE CACHE BOOL "Prevent cmake from working under cygwin and using cygwin tools")
-mark_as_advanced( ANDROID_FORBID_SYGWIN )
-if( ANDROID_FORBID_SYGWIN )
- if( CYGWIN )
- message( FATAL_ERROR "Android NDK and android-cmake toolchain are not welcome Cygwin. It is unlikely that this cmake toolchain will work under cygwin. But if you want to try then you can set cmake variable ANDROID_FORBID_SYGWIN to FALSE and rerun cmake." )
- endif()
-
- if( CMAKE_HOST_WIN32 )
- # remove cygwin from PATH
- set( __new_path "$ENV{PATH}")
- __LIST_FILTER( __new_path "cygwin" )
- set(ENV{PATH} "${__new_path}")
- unset(__new_path)
- endif()
-endif()
-
-
-# detect current host platform
-if( NOT DEFINED ANDROID_NDK_HOST_X64 AND (CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "amd64|x86_64|AMD64" OR CMAKE_HOST_APPLE) )
- set( ANDROID_NDK_HOST_X64 1 CACHE BOOL "Try to use 64-bit compiler toolchain" )
- mark_as_advanced( ANDROID_NDK_HOST_X64 )
-endif()
-
-set( TOOL_OS_SUFFIX "" )
-if( CMAKE_HOST_APPLE )
- set( ANDROID_NDK_HOST_SYSTEM_NAME "darwin-x86_64" )
- set( ANDROID_NDK_HOST_SYSTEM_NAME2 "darwin-x86" )
-elseif( CMAKE_HOST_WIN32 )
- set( ANDROID_NDK_HOST_SYSTEM_NAME "windows-x86_64" )
- set( ANDROID_NDK_HOST_SYSTEM_NAME2 "windows" )
- set( TOOL_OS_SUFFIX ".exe" )
-elseif( CMAKE_HOST_UNIX )
- set( ANDROID_NDK_HOST_SYSTEM_NAME "linux-x86_64" )
- set( ANDROID_NDK_HOST_SYSTEM_NAME2 "linux-x86" )
-else()
- message( FATAL_ERROR "Cross-compilation on your platform is not supported by this cmake toolchain" )
-endif()
-
-if( NOT ANDROID_NDK_HOST_X64 )
- set( ANDROID_NDK_HOST_SYSTEM_NAME ${ANDROID_NDK_HOST_SYSTEM_NAME2} )
-endif()
-
-# see if we have path to Android NDK
-if( NOT ANDROID_NDK AND NOT ANDROID_STANDALONE_TOOLCHAIN )
- __INIT_VARIABLE( ANDROID_NDK PATH ENV_ANDROID_NDK )
-endif()
-if( NOT ANDROID_NDK )
- # see if we have path to Android standalone toolchain
- __INIT_VARIABLE( ANDROID_STANDALONE_TOOLCHAIN PATH ENV_ANDROID_STANDALONE_TOOLCHAIN )
-
- if( NOT ANDROID_STANDALONE_TOOLCHAIN )
- #try to find Android NDK in one of the the default locations
- set( __ndkSearchPaths )
- foreach( __ndkSearchPath ${ANDROID_NDK_SEARCH_PATHS} )
- foreach( suffix ${ANDROID_SUPPORTED_NDK_VERSIONS} )
- list( APPEND __ndkSearchPaths "${__ndkSearchPath}/android-ndk${suffix}" )
- endforeach()
- endforeach()
- __INIT_VARIABLE( ANDROID_NDK PATH VALUES ${__ndkSearchPaths} )
- unset( __ndkSearchPaths )
-
- if( ANDROID_NDK )
- message( STATUS "Using default path for Android NDK: ${ANDROID_NDK}" )
- message( STATUS " If you prefer to use a different location, please define a cmake or environment variable: ANDROID_NDK" )
- else()
- #try to find Android standalone toolchain in one of the the default locations
- __INIT_VARIABLE( ANDROID_STANDALONE_TOOLCHAIN PATH ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH )
-
- if( ANDROID_STANDALONE_TOOLCHAIN )
- message( STATUS "Using default path for standalone toolchain ${ANDROID_STANDALONE_TOOLCHAIN}" )
- message( STATUS " If you prefer to use a different location, please define the variable: ANDROID_STANDALONE_TOOLCHAIN" )
- endif( ANDROID_STANDALONE_TOOLCHAIN )
- endif( ANDROID_NDK )
- endif( NOT ANDROID_STANDALONE_TOOLCHAIN )
-endif( NOT ANDROID_NDK )
-
-# remember found paths
-if( ANDROID_NDK )
- get_filename_component( ANDROID_NDK "${ANDROID_NDK}" ABSOLUTE )
- set( ANDROID_NDK "${ANDROID_NDK}" CACHE INTERNAL "Path of the Android NDK" FORCE )
- set( BUILD_WITH_ANDROID_NDK True )
- if( EXISTS "${ANDROID_NDK}/RELEASE.TXT" )
- file( STRINGS "${ANDROID_NDK}/RELEASE.TXT" ANDROID_NDK_RELEASE_FULL LIMIT_COUNT 1 REGEX "r[0-9]+[a-z]?" )
- string( REGEX MATCH "r([0-9]+)([a-z]?)" ANDROID_NDK_RELEASE "${ANDROID_NDK_RELEASE_FULL}" )
- else()
- set( ANDROID_NDK_RELEASE "r1x" )
- set( ANDROID_NDK_RELEASE_FULL "unreleased" )
- endif()
- string( REGEX REPLACE "r([0-9]+)([a-z]?)" "\\1*1000" ANDROID_NDK_RELEASE_NUM "${ANDROID_NDK_RELEASE}" )
- string( FIND " abcdefghijklmnopqastuvwxyz" "${CMAKE_MATCH_2}" __ndkReleaseLetterNum )
- math( EXPR ANDROID_NDK_RELEASE_NUM "${ANDROID_NDK_RELEASE_NUM}+${__ndkReleaseLetterNum}" )
-elseif( ANDROID_STANDALONE_TOOLCHAIN )
- get_filename_component( ANDROID_STANDALONE_TOOLCHAIN "${ANDROID_STANDALONE_TOOLCHAIN}" ABSOLUTE )
- # try to detect change
- if( CMAKE_AR )
- string( LENGTH "${ANDROID_STANDALONE_TOOLCHAIN}" __length )
- string( SUBSTRING "${CMAKE_AR}" 0 ${__length} __androidStandaloneToolchainPreviousPath )
- if( NOT __androidStandaloneToolchainPreviousPath STREQUAL ANDROID_STANDALONE_TOOLCHAIN )
- message( FATAL_ERROR "It is not possible to change path to the Android standalone toolchain on subsequent run." )
- endif()
- unset( __androidStandaloneToolchainPreviousPath )
- unset( __length )
- endif()
- set( ANDROID_STANDALONE_TOOLCHAIN "${ANDROID_STANDALONE_TOOLCHAIN}" CACHE INTERNAL "Path of the Android standalone toolchain" FORCE )
- set( BUILD_WITH_STANDALONE_TOOLCHAIN True )
-else()
- list(GET ANDROID_NDK_SEARCH_PATHS 0 ANDROID_NDK_SEARCH_PATH)
- message( FATAL_ERROR "Could not find neither Android NDK nor Android standalone toolchain.
- You should either set an environment variable:
- export ANDROID_NDK=~/my-android-ndk
- or
- export ANDROID_STANDALONE_TOOLCHAIN=~/my-android-toolchain
- or put the toolchain or NDK in the default path:
- sudo ln -s ~/my-android-ndk ${ANDROID_NDK_SEARCH_PATH}/android-ndk
- sudo ln -s ~/my-android-toolchain ${ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH}" )
-endif()
-
-# android NDK layout
-if( BUILD_WITH_ANDROID_NDK )
- if( NOT DEFINED ANDROID_NDK_LAYOUT )
- # try to automatically detect the layout
- if( EXISTS "${ANDROID_NDK}/RELEASE.TXT")
- set( ANDROID_NDK_LAYOUT "RELEASE" )
- elseif( EXISTS "${ANDROID_NDK}/../../linux-x86/toolchain/" )
- set( ANDROID_NDK_LAYOUT "LINARO" )
- elseif( EXISTS "${ANDROID_NDK}/../../gcc/" )
- set( ANDROID_NDK_LAYOUT "ANDROID" )
- endif()
- endif()
- set( ANDROID_NDK_LAYOUT "${ANDROID_NDK_LAYOUT}" CACHE STRING "The inner layout of NDK" )
- mark_as_advanced( ANDROID_NDK_LAYOUT )
- if( ANDROID_NDK_LAYOUT STREQUAL "LINARO" )
- set( ANDROID_NDK_HOST_SYSTEM_NAME ${ANDROID_NDK_HOST_SYSTEM_NAME2} ) # only 32-bit at the moment
- set( ANDROID_NDK_TOOLCHAINS_PATH "${ANDROID_NDK}/../../${ANDROID_NDK_HOST_SYSTEM_NAME}/toolchain" )
- set( ANDROID_NDK_TOOLCHAINS_SUBPATH "" )
- set( ANDROID_NDK_TOOLCHAINS_SUBPATH2 "" )
- elseif( ANDROID_NDK_LAYOUT STREQUAL "ANDROID" )
- set( ANDROID_NDK_HOST_SYSTEM_NAME ${ANDROID_NDK_HOST_SYSTEM_NAME2} ) # only 32-bit at the moment
- set( ANDROID_NDK_TOOLCHAINS_PATH "${ANDROID_NDK}/../../gcc/${ANDROID_NDK_HOST_SYSTEM_NAME}/arm" )
- set( ANDROID_NDK_TOOLCHAINS_SUBPATH "" )
- set( ANDROID_NDK_TOOLCHAINS_SUBPATH2 "" )
- else() # ANDROID_NDK_LAYOUT STREQUAL "RELEASE"
- set( ANDROID_NDK_TOOLCHAINS_PATH "${ANDROID_NDK}/toolchains" )
- set( ANDROID_NDK_TOOLCHAINS_SUBPATH "/prebuilt/${ANDROID_NDK_HOST_SYSTEM_NAME}" )
- set( ANDROID_NDK_TOOLCHAINS_SUBPATH2 "/prebuilt/${ANDROID_NDK_HOST_SYSTEM_NAME2}" )
- endif()
- get_filename_component( ANDROID_NDK_TOOLCHAINS_PATH "${ANDROID_NDK_TOOLCHAINS_PATH}" ABSOLUTE )
-
- # try to detect change of NDK
- if( CMAKE_AR )
- string( LENGTH "${ANDROID_NDK_TOOLCHAINS_PATH}" __length )
- string( SUBSTRING "${CMAKE_AR}" 0 ${__length} __androidNdkPreviousPath )
- if( NOT __androidNdkPreviousPath STREQUAL ANDROID_NDK_TOOLCHAINS_PATH )
- message( FATAL_ERROR "It is not possible to change the path to the NDK on subsequent CMake run. You must remove all generated files from your build folder first.
- " )
- endif()
- unset( __androidNdkPreviousPath )
- unset( __length )
- endif()
-endif()
-
-
-# get all the details about standalone toolchain
-if( BUILD_WITH_STANDALONE_TOOLCHAIN )
- __DETECT_NATIVE_API_LEVEL( ANDROID_SUPPORTED_NATIVE_API_LEVELS "${ANDROID_STANDALONE_TOOLCHAIN}/sysroot/usr/include/android/api-level.h" )
- set( ANDROID_STANDALONE_TOOLCHAIN_API_LEVEL ${ANDROID_SUPPORTED_NATIVE_API_LEVELS} )
- set( __availableToolchains "standalone" )
- __DETECT_TOOLCHAIN_MACHINE_NAME( __availableToolchainMachines "${ANDROID_STANDALONE_TOOLCHAIN}" )
- if( NOT __availableToolchainMachines )
- message( FATAL_ERROR "Could not determine machine name of your toolchain. Probably your Android standalone toolchain is broken." )
- endif()
- if( __availableToolchainMachines MATCHES x86_64 )
- set( __availableToolchainArchs "x86_64" )
- elseif( __availableToolchainMachines MATCHES i686 )
- set( __availableToolchainArchs "x86" )
- elseif( __availableToolchainMachines MATCHES aarch64 )
- set( __availableToolchainArchs "arm64" )
- elseif( __availableToolchainMachines MATCHES arm )
- set( __availableToolchainArchs "arm" )
- elseif( __availableToolchainMachines MATCHES mips64el )
- set( __availableToolchainArchs "mips64" )
- elseif( __availableToolchainMachines MATCHES mipsel )
- set( __availableToolchainArchs "mips" )
- endif()
- execute_process( COMMAND "${ANDROID_STANDALONE_TOOLCHAIN}/bin/${__availableToolchainMachines}-gcc${TOOL_OS_SUFFIX}" -dumpversion
- OUTPUT_VARIABLE __availableToolchainCompilerVersions OUTPUT_STRIP_TRAILING_WHITESPACE )
- string( REGEX MATCH "[0-9]+[.][0-9]+([.][0-9]+)?" __availableToolchainCompilerVersions "${__availableToolchainCompilerVersions}" )
- if( EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/bin/clang${TOOL_OS_SUFFIX}" )
- list( APPEND __availableToolchains "standalone-clang" )
- list( APPEND __availableToolchainMachines ${__availableToolchainMachines} )
- list( APPEND __availableToolchainArchs ${__availableToolchainArchs} )
- list( APPEND __availableToolchainCompilerVersions ${__availableToolchainCompilerVersions} )
- endif()
-endif()
-
-macro( __GLOB_NDK_TOOLCHAINS __availableToolchainsVar __availableToolchainsLst __toolchain_subpath )
- foreach( __toolchain ${${__availableToolchainsLst}} )
- if( "${__toolchain}" MATCHES "-clang3[.][0-9]$" AND NOT EXISTS "${ANDROID_NDK_TOOLCHAINS_PATH}/${__toolchain}${__toolchain_subpath}" )
- SET( __toolchainVersionRegex "^TOOLCHAIN_VERSION[\t ]+:=[\t ]+(.*)$" )
- FILE( STRINGS "${ANDROID_NDK_TOOLCHAINS_PATH}/${__toolchain}/setup.mk" __toolchainVersionStr REGEX "${__toolchainVersionRegex}" )
- if( __toolchainVersionStr )
- string( REGEX REPLACE "${__toolchainVersionRegex}" "\\1" __toolchainVersionStr "${__toolchainVersionStr}" )
- string( REGEX REPLACE "-clang3[.][0-9]$" "-${__toolchainVersionStr}" __gcc_toolchain "${__toolchain}" )
- else()
- string( REGEX REPLACE "-clang3[.][0-9]$" "-4.6" __gcc_toolchain "${__toolchain}" )
- endif()
- unset( __toolchainVersionStr )
- unset( __toolchainVersionRegex )
- else()
- set( __gcc_toolchain "${__toolchain}" )
- endif()
- __DETECT_TOOLCHAIN_MACHINE_NAME( __machine "${ANDROID_NDK_TOOLCHAINS_PATH}/${__gcc_toolchain}${__toolchain_subpath}" )
- if( __machine )
- string( REGEX MATCH "[0-9]+[.][0-9]+([.][0-9x]+)?$" __version "${__gcc_toolchain}" )
- if( __machine MATCHES x86_64 )
- set( __arch "x86_64" )
- elseif( __machine MATCHES i686 )
- set( __arch "x86" )
- elseif( __machine MATCHES aarch64 )
- set( __arch "arm64" )
- elseif( __machine MATCHES arm )
- set( __arch "arm" )
- elseif( __machine MATCHES mips64el )
- set( __arch "mips64" )
- elseif( __machine MATCHES mipsel )
- set( __arch "mips" )
- else()
- set( __arch "" )
- endif()
- #message("machine: !${__machine}!\narch: !${__arch}!\nversion: !${__version}!\ntoolchain: !${__toolchain}!\n")
- if (__arch)
- list( APPEND __availableToolchainMachines "${__machine}" )
- list( APPEND __availableToolchainArchs "${__arch}" )
- list( APPEND __availableToolchainCompilerVersions "${__version}" )
- list( APPEND ${__availableToolchainsVar} "${__toolchain}" )
- endif()
- endif()
- unset( __gcc_toolchain )
- endforeach()
-endmacro()
-
-# get all the details about NDK
-if( BUILD_WITH_ANDROID_NDK )
- file( GLOB ANDROID_SUPPORTED_NATIVE_API_LEVELS RELATIVE "${ANDROID_NDK}/platforms" "${ANDROID_NDK}/platforms/android-*" )
- string( REPLACE "android-" "" ANDROID_SUPPORTED_NATIVE_API_LEVELS "${ANDROID_SUPPORTED_NATIVE_API_LEVELS}" )
- set( __availableToolchains "" )
- set( __availableToolchainMachines "" )
- set( __availableToolchainArchs "" )
- set( __availableToolchainCompilerVersions "" )
- if( ANDROID_TOOLCHAIN_NAME AND EXISTS "${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_TOOLCHAIN_NAME}/" )
- # do not go through all toolchains if we know the name
- set( __availableToolchainsLst "${ANDROID_TOOLCHAIN_NAME}" )
- __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst "${ANDROID_NDK_TOOLCHAINS_SUBPATH}" )
- if( NOT __availableToolchains AND NOT ANDROID_NDK_TOOLCHAINS_SUBPATH STREQUAL ANDROID_NDK_TOOLCHAINS_SUBPATH2 )
- __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst "${ANDROID_NDK_TOOLCHAINS_SUBPATH2}" )
- if( __availableToolchains )
- set( ANDROID_NDK_TOOLCHAINS_SUBPATH ${ANDROID_NDK_TOOLCHAINS_SUBPATH2} )
- endif()
- endif()
- endif()
- if( NOT __availableToolchains )
- file( GLOB __availableToolchainsLst RELATIVE "${ANDROID_NDK_TOOLCHAINS_PATH}" "${ANDROID_NDK_TOOLCHAINS_PATH}/*" )
- if( __availableToolchainsLst )
- list(SORT __availableToolchainsLst) # we need clang to go after gcc
- endif()
- __LIST_FILTER( __availableToolchainsLst "^[.]" )
- __LIST_FILTER( __availableToolchainsLst "llvm" )
- __LIST_FILTER( __availableToolchainsLst "renderscript" )
- __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst "${ANDROID_NDK_TOOLCHAINS_SUBPATH}" )
- if( NOT __availableToolchains AND NOT ANDROID_NDK_TOOLCHAINS_SUBPATH STREQUAL ANDROID_NDK_TOOLCHAINS_SUBPATH2 )
- __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst "${ANDROID_NDK_TOOLCHAINS_SUBPATH2}" )
- if( __availableToolchains )
- set( ANDROID_NDK_TOOLCHAINS_SUBPATH ${ANDROID_NDK_TOOLCHAINS_SUBPATH2} )
- endif()
- endif()
- endif()
- if( NOT __availableToolchains )
- message( FATAL_ERROR "Could not find any working toolchain in the NDK. Probably your Android NDK is broken." )
- endif()
-endif()
-
-# build list of available ABIs
-set( ANDROID_SUPPORTED_ABIS "" )
-set( __uniqToolchainArchNames ${__availableToolchainArchs} )
-list( REMOVE_DUPLICATES __uniqToolchainArchNames )
-list( SORT __uniqToolchainArchNames )
-foreach( __arch ${__uniqToolchainArchNames} )
- list( APPEND ANDROID_SUPPORTED_ABIS ${ANDROID_SUPPORTED_ABIS_${__arch}} )
-endforeach()
-unset( __uniqToolchainArchNames )
-if( NOT ANDROID_SUPPORTED_ABIS )
- message( FATAL_ERROR "No one of known Android ABIs is supported by this cmake toolchain." )
-endif()
-
-# choose target ABI
-__INIT_VARIABLE( ANDROID_ABI VALUES ${ANDROID_SUPPORTED_ABIS} )
-# verify that target ABI is supported
-list( FIND ANDROID_SUPPORTED_ABIS "${ANDROID_ABI}" __androidAbiIdx )
-if( __androidAbiIdx EQUAL -1 )
- string( REPLACE ";" "\", \"" PRINTABLE_ANDROID_SUPPORTED_ABIS "${ANDROID_SUPPORTED_ABIS}" )
- message( FATAL_ERROR "Specified ANDROID_ABI = \"${ANDROID_ABI}\" is not supported by this cmake toolchain or your NDK/toolchain.
- Supported values are: \"${PRINTABLE_ANDROID_SUPPORTED_ABIS}\"
- " )
-endif()
-unset( __androidAbiIdx )
-
-# set target ABI options
-if( ANDROID_ABI STREQUAL "x86" )
- set( X86 true )
- set( ANDROID_NDK_ABI_NAME "x86" )
- set( ANDROID_ARCH_NAME "x86" )
- set( ANDROID_LLVM_TRIPLE "i686-none-linux-android" )
- set( CMAKE_SYSTEM_PROCESSOR "i686" )
-elseif( ANDROID_ABI STREQUAL "x86_64" )
- set( X86 true )
- set( X86_64 true )
- set( ANDROID_NDK_ABI_NAME "x86_64" )
- set( ANDROID_ARCH_NAME "x86_64" )
- set( CMAKE_SYSTEM_PROCESSOR "x86_64" )
- set( ANDROID_LLVM_TRIPLE "x86_64-none-linux-android" )
-elseif( ANDROID_ABI STREQUAL "mips64" )
- set( MIPS64 true )
- set( ANDROID_NDK_ABI_NAME "mips64" )
- set( ANDROID_ARCH_NAME "mips64" )
- set( ANDROID_LLVM_TRIPLE "mips64el-none-linux-android" )
- set( CMAKE_SYSTEM_PROCESSOR "mips64" )
-elseif( ANDROID_ABI STREQUAL "mips" )
- set( MIPS true )
- set( ANDROID_NDK_ABI_NAME "mips" )
- set( ANDROID_ARCH_NAME "mips" )
- set( ANDROID_LLVM_TRIPLE "mipsel-none-linux-android" )
- set( CMAKE_SYSTEM_PROCESSOR "mips" )
-elseif( ANDROID_ABI STREQUAL "arm64-v8a" )
- set( ARM64_V8A true )
- set( ANDROID_NDK_ABI_NAME "arm64-v8a" )
- set( ANDROID_ARCH_NAME "arm64" )
- set( ANDROID_LLVM_TRIPLE "aarch64-none-linux-android" )
- set( CMAKE_SYSTEM_PROCESSOR "aarch64" )
- set( VFPV3 true )
- set( NEON true )
-elseif( ANDROID_ABI STREQUAL "armeabi" )
- set( ARMEABI true )
- set( ANDROID_NDK_ABI_NAME "armeabi" )
- set( ANDROID_ARCH_NAME "arm" )
- set( ANDROID_LLVM_TRIPLE "armv5te-none-linux-androideabi" )
- set( CMAKE_SYSTEM_PROCESSOR "armv5te" )
-elseif( ANDROID_ABI STREQUAL "armeabi-v6 with VFP" )
- set( ARMEABI_V6 true )
- set( ANDROID_NDK_ABI_NAME "armeabi" )
- set( ANDROID_ARCH_NAME "arm" )
- set( ANDROID_LLVM_TRIPLE "armv5te-none-linux-androideabi" )
- set( CMAKE_SYSTEM_PROCESSOR "armv6" )
- # need always fallback to older platform
- set( ARMEABI true )
-elseif( ANDROID_ABI STREQUAL "armeabi-v7a")
- set( ARMEABI_V7A true )
- set( ANDROID_NDK_ABI_NAME "armeabi-v7a" )
- set( ANDROID_ARCH_NAME "arm" )
- set( ANDROID_LLVM_TRIPLE "armv7-none-linux-androideabi" )
- set( CMAKE_SYSTEM_PROCESSOR "armv7-a" )
-elseif( ANDROID_ABI STREQUAL "armeabi-v7a with VFPV3" )
- set( ARMEABI_V7A true )
- set( ANDROID_NDK_ABI_NAME "armeabi-v7a" )
- set( ANDROID_ARCH_NAME "arm" )
- set( ANDROID_LLVM_TRIPLE "armv7-none-linux-androideabi" )
- set( CMAKE_SYSTEM_PROCESSOR "armv7-a" )
- set( VFPV3 true )
-elseif( ANDROID_ABI STREQUAL "armeabi-v7a with NEON" )
- set( ARMEABI_V7A true )
- set( ANDROID_NDK_ABI_NAME "armeabi-v7a" )
- set( ANDROID_ARCH_NAME "arm" )
- set( ANDROID_LLVM_TRIPLE "armv7-none-linux-androideabi" )
- set( CMAKE_SYSTEM_PROCESSOR "armv7-a" )
- set( VFPV3 true )
- set( NEON true )
-else()
- message( SEND_ERROR "Unknown ANDROID_ABI=\"${ANDROID_ABI}\" is specified." )
-endif()
-
-if( CMAKE_BINARY_DIR AND EXISTS "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeSystem.cmake" )
- # really dirty hack
- # it is not possible to change CMAKE_SYSTEM_PROCESSOR after the first run...
- file( APPEND "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeSystem.cmake" "SET(CMAKE_SYSTEM_PROCESSOR \"${CMAKE_SYSTEM_PROCESSOR}\")\n" )
-endif()
-
-if( ANDROID_ARCH_NAME STREQUAL "arm" AND NOT ARMEABI_V6 )
- __INIT_VARIABLE( ANDROID_FORCE_ARM_BUILD VALUES OFF )
- set( ANDROID_FORCE_ARM_BUILD ${ANDROID_FORCE_ARM_BUILD} CACHE BOOL "Use 32-bit ARM instructions instead of Thumb-1" FORCE )
- mark_as_advanced( ANDROID_FORCE_ARM_BUILD )
-else()
- unset( ANDROID_FORCE_ARM_BUILD CACHE )
-endif()
-
-# choose toolchain
-if( ANDROID_TOOLCHAIN_NAME )
- list( FIND __availableToolchains "${ANDROID_TOOLCHAIN_NAME}" __toolchainIdx )
- if( __toolchainIdx EQUAL -1 )
- list( SORT __availableToolchains )
- string( REPLACE ";" "\n * " toolchains_list "${__availableToolchains}" )
- set( toolchains_list " * ${toolchains_list}")
- message( FATAL_ERROR "Specified toolchain \"${ANDROID_TOOLCHAIN_NAME}\" is missing in your NDK or broken. Please verify that your NDK is working or select another compiler toolchain.
-To configure the toolchain set CMake variable ANDROID_TOOLCHAIN_NAME to one of the following values:\n${toolchains_list}\n" )
- endif()
- list( GET __availableToolchainArchs ${__toolchainIdx} __toolchainArch )
- if( NOT __toolchainArch STREQUAL ANDROID_ARCH_NAME )
- message( SEND_ERROR "Selected toolchain \"${ANDROID_TOOLCHAIN_NAME}\" is not able to compile binaries for the \"${ANDROID_ARCH_NAME}\" platform." )
- endif()
-else()
- set( __toolchainIdx -1 )
- set( __applicableToolchains "" )
- set( __toolchainMaxVersion "0.0.0" )
- list( LENGTH __availableToolchains __availableToolchainsCount )
- math( EXPR __availableToolchainsCount "${__availableToolchainsCount}-1" )
- foreach( __idx RANGE ${__availableToolchainsCount} )
- list( GET __availableToolchainArchs ${__idx} __toolchainArch )
- if( __toolchainArch STREQUAL ANDROID_ARCH_NAME )
- list( GET __availableToolchainCompilerVersions ${__idx} __toolchainVersion )
- string( REPLACE "x" "99" __toolchainVersion "${__toolchainVersion}")
- if( __toolchainVersion VERSION_GREATER __toolchainMaxVersion )
- set( __toolchainMaxVersion "${__toolchainVersion}" )
- set( __toolchainIdx ${__idx} )
- endif()
- endif()
- endforeach()
- unset( __availableToolchainsCount )
- unset( __toolchainMaxVersion )
- unset( __toolchainVersion )
-endif()
-unset( __toolchainArch )
-if( __toolchainIdx EQUAL -1 )
- message( FATAL_ERROR "No one of available compiler toolchains is able to compile for ${ANDROID_ARCH_NAME} platform." )
-endif()
-list( GET __availableToolchains ${__toolchainIdx} ANDROID_TOOLCHAIN_NAME )
-list( GET __availableToolchainMachines ${__toolchainIdx} ANDROID_TOOLCHAIN_MACHINE_NAME )
-list( GET __availableToolchainCompilerVersions ${__toolchainIdx} ANDROID_COMPILER_VERSION )
-
-unset( __toolchainIdx )
-unset( __availableToolchains )
-unset( __availableToolchainMachines )
-unset( __availableToolchainArchs )
-unset( __availableToolchainCompilerVersions )
-
-# choose native API level
-__INIT_VARIABLE( ANDROID_NATIVE_API_LEVEL ENV_ANDROID_NATIVE_API_LEVEL ANDROID_API_LEVEL ENV_ANDROID_API_LEVEL ANDROID_STANDALONE_TOOLCHAIN_API_LEVEL ANDROID_DEFAULT_NDK_API_LEVEL_${ANDROID_ARCH_NAME} ANDROID_DEFAULT_NDK_API_LEVEL )
-string( REPLACE "android-" "" ANDROID_NATIVE_API_LEVEL "${ANDROID_NATIVE_API_LEVEL}" )
-string( STRIP "${ANDROID_NATIVE_API_LEVEL}" ANDROID_NATIVE_API_LEVEL )
-# adjust API level
-set( __real_api_level ${ANDROID_DEFAULT_NDK_API_LEVEL_${ANDROID_ARCH_NAME}} )
-foreach( __level ${ANDROID_SUPPORTED_NATIVE_API_LEVELS} )
- if( (__level LESS ANDROID_NATIVE_API_LEVEL OR __level STREQUAL ANDROID_NATIVE_API_LEVEL) AND NOT __level LESS __real_api_level )
- set( __real_api_level ${__level} )
- endif()
-endforeach()
-if( __real_api_level AND NOT ANDROID_NATIVE_API_LEVEL STREQUAL __real_api_level )
- message( STATUS "Adjusting Android API level 'android-${ANDROID_NATIVE_API_LEVEL}' to 'android-${__real_api_level}'")
- set( ANDROID_NATIVE_API_LEVEL ${__real_api_level} )
-endif()
-unset(__real_api_level)
-# validate
-list( FIND ANDROID_SUPPORTED_NATIVE_API_LEVELS "${ANDROID_NATIVE_API_LEVEL}" __levelIdx )
-if( __levelIdx EQUAL -1 )
- message( SEND_ERROR "Specified Android native API level 'android-${ANDROID_NATIVE_API_LEVEL}' is not supported by your NDK/toolchain." )
-else()
- if( BUILD_WITH_ANDROID_NDK )
- __DETECT_NATIVE_API_LEVEL( __realApiLevel "${ANDROID_NDK}/platforms/android-${ANDROID_NATIVE_API_LEVEL}/arch-${ANDROID_ARCH_NAME}/usr/include/android/api-level.h" )
- if( NOT __realApiLevel EQUAL ANDROID_NATIVE_API_LEVEL AND NOT __realApiLevel GREATER 9000 )
- message( SEND_ERROR "Specified Android API level (${ANDROID_NATIVE_API_LEVEL}) does not match to the level found (${__realApiLevel}). Probably your copy of NDK is broken." )
- endif()
- unset( __realApiLevel )
- endif()
- set( ANDROID_NATIVE_API_LEVEL "${ANDROID_NATIVE_API_LEVEL}" CACHE STRING "Android API level for native code" FORCE )
- set( CMAKE_ANDROID_API ${ANDROID_NATIVE_API_LEVEL} )
- if( CMAKE_VERSION VERSION_GREATER "2.8" )
- list( SORT ANDROID_SUPPORTED_NATIVE_API_LEVELS )
- set_property( CACHE ANDROID_NATIVE_API_LEVEL PROPERTY STRINGS ${ANDROID_SUPPORTED_NATIVE_API_LEVELS} )
- endif()
-endif()
-unset( __levelIdx )
-
-
-# remember target ABI
-set( ANDROID_ABI "${ANDROID_ABI}" CACHE STRING "The target ABI for Android. If arm, then armeabi-v7a is recommended for hardware floating point." FORCE )
-if( CMAKE_VERSION VERSION_GREATER "2.8" )
- list( SORT ANDROID_SUPPORTED_ABIS_${ANDROID_ARCH_NAME} )
- set_property( CACHE ANDROID_ABI PROPERTY STRINGS ${ANDROID_SUPPORTED_ABIS_${ANDROID_ARCH_NAME}} )
-endif()
-
-
-# runtime choice (STL, rtti, exceptions)
-if( NOT ANDROID_STL )
- set( ANDROID_STL gnustl_static )
-endif()
-set( ANDROID_STL "${ANDROID_STL}" CACHE STRING "C++ runtime" )
-set( ANDROID_STL_FORCE_FEATURES ON CACHE BOOL "automatically configure rtti and exceptions support based on C++ runtime" )
-mark_as_advanced( ANDROID_STL ANDROID_STL_FORCE_FEATURES )
-
-if( BUILD_WITH_ANDROID_NDK )
- if( NOT "${ANDROID_STL}" MATCHES "^(none|system|system_re|gabi\\+\\+_static|gabi\\+\\+_shared|stlport_static|stlport_shared|gnustl_static|gnustl_shared|c\\+\\+_static|c\\+\\+_shared)$")
- message( FATAL_ERROR "ANDROID_STL is set to invalid value \"${ANDROID_STL}\".
-The possible values are:
- none -> Do not configure the runtime.
- system -> Use the default minimal system C++ runtime library.
- system_re -> Same as system but with rtti and exceptions.
- gabi++_static -> Use the GAbi++ runtime as a static library.
- gabi++_shared -> Use the GAbi++ runtime as a shared library.
- stlport_static -> Use the STLport runtime as a static library.
- stlport_shared -> Use the STLport runtime as a shared library.
- gnustl_static -> (default) Use the GNU STL as a static library.
- gnustl_shared -> Use the GNU STL as a shared library.
- c++_static -> Use the LLVM libc++ runtime as a static library.
- c++_shared -> Use the LLVM libc++ runtime as a shared library.
-" )
- endif()
-elseif( BUILD_WITH_STANDALONE_TOOLCHAIN )
- if( NOT "${ANDROID_STL}" MATCHES "^(none|gnustl_static|gnustl_shared)$")
- message( FATAL_ERROR "ANDROID_STL is set to invalid value \"${ANDROID_STL}\".
-The possible values are:
- none -> Do not configure the runtime.
- gnustl_static -> (default) Use the GNU STL as a static library.
- gnustl_shared -> Use the GNU STL as a shared library.
-" )
- endif()
-endif()
-
-unset( ANDROID_RTTI )
-unset( ANDROID_EXCEPTIONS )
-unset( ANDROID_STL_INCLUDE_DIRS )
-unset( __libstl )
-unset( __libsupcxx )
-
-if( NOT _CMAKE_IN_TRY_COMPILE AND ANDROID_NDK_RELEASE STREQUAL "r7b" AND ARMEABI_V7A AND NOT VFPV3 AND ANDROID_STL MATCHES "gnustl" )
- message( WARNING "The GNU STL armeabi-v7a binaries from NDK r7b can crash non-NEON devices. The files provided with NDK r7b were not configured properly, resulting in crashes on Tegra2-based devices and others when trying to use certain floating-point functions (e.g., cosf, sinf, expf).
-You are strongly recommended to switch to another NDK release.
-" )
-endif()
-
-if( NOT _CMAKE_IN_TRY_COMPILE AND X86 AND ANDROID_STL MATCHES "gnustl" AND ANDROID_NDK_RELEASE STREQUAL "r6" )
- message( WARNING "The x86 system header file from NDK r6 has incorrect definition for ptrdiff_t. You are recommended to upgrade to a newer NDK release or manually patch the header:
-See https://android.googlesource.com/platform/development.git f907f4f9d4e56ccc8093df6fee54454b8bcab6c2
- diff --git a/ndk/platforms/android-9/arch-x86/include/machine/_types.h b/ndk/platforms/android-9/arch-x86/include/machine/_types.h
- index 5e28c64..65892a1 100644
- --- a/ndk/platforms/android-9/arch-x86/include/machine/_types.h
- +++ b/ndk/platforms/android-9/arch-x86/include/machine/_types.h
- @@ -51,7 +51,11 @@ typedef long int ssize_t;
- #endif
- #ifndef _PTRDIFF_T
- #define _PTRDIFF_T
- -typedef long ptrdiff_t;
- +# ifdef __ANDROID__
- + typedef int ptrdiff_t;
- +# else
- + typedef long ptrdiff_t;
- +# endif
- #endif
-" )
-endif()
-
-
-# setup paths and STL for standalone toolchain
-if( BUILD_WITH_STANDALONE_TOOLCHAIN )
- set( ANDROID_TOOLCHAIN_ROOT "${ANDROID_STANDALONE_TOOLCHAIN}" )
- set( ANDROID_CLANG_TOOLCHAIN_ROOT "${ANDROID_STANDALONE_TOOLCHAIN}" )
- set( ANDROID_SYSROOT "${ANDROID_STANDALONE_TOOLCHAIN}/sysroot" )
-
- if( NOT ANDROID_STL STREQUAL "none" )
- set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_STANDALONE_TOOLCHAIN}/include/c++/${ANDROID_COMPILER_VERSION}" )
- if( NOT EXISTS "${ANDROID_STL_INCLUDE_DIRS}" )
- # old location ( pre r8c )
- set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/include/c++/${ANDROID_COMPILER_VERSION}" )
- endif()
- if( ARMEABI_V7A AND EXISTS "${ANDROID_STL_INCLUDE_DIRS}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/${CMAKE_SYSTEM_PROCESSOR}/bits" )
- list( APPEND ANDROID_STL_INCLUDE_DIRS "${ANDROID_STL_INCLUDE_DIRS}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/${CMAKE_SYSTEM_PROCESSOR}" )
- elseif( ARMEABI AND NOT ANDROID_FORCE_ARM_BUILD AND EXISTS "${ANDROID_STL_INCLUDE_DIRS}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/thumb/bits" )
- list( APPEND ANDROID_STL_INCLUDE_DIRS "${ANDROID_STL_INCLUDE_DIRS}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/thumb" )
- else()
- list( APPEND ANDROID_STL_INCLUDE_DIRS "${ANDROID_STL_INCLUDE_DIRS}/${ANDROID_TOOLCHAIN_MACHINE_NAME}" )
- endif()
- # always search static GNU STL to get the location of libsupc++.a
- if( ARMEABI_V7A AND NOT ANDROID_FORCE_ARM_BUILD AND EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/thumb/libstdc++.a" )
- set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/thumb" )
- elseif( ARMEABI_V7A AND EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/libstdc++.a" )
- set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}" )
- elseif( ARMEABI AND NOT ANDROID_FORCE_ARM_BUILD AND EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb/libstdc++.a" )
- set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb" )
- elseif( EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/libstdc++.a" )
- set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib" )
- endif()
- if( __libstl )
- set( __libsupcxx "${__libstl}/libsupc++.a" )
- set( __libstl "${__libstl}/libstdc++.a" )
- endif()
- if( NOT EXISTS "${__libsupcxx}" )
- message( FATAL_ERROR "The required libstdsupc++.a is missing in your standalone toolchain.
- Usually it happens because of bug in make-standalone-toolchain.sh script from NDK r7, r7b and r7c.
- You need to either upgrade to newer NDK or manually copy
- $ANDROID_NDK/sources/cxx-stl/gnu-libstdc++/libs/${ANDROID_NDK_ABI_NAME}/libsupc++.a
- to
- ${__libsupcxx}
- " )
- endif()
- if( ANDROID_STL STREQUAL "gnustl_shared" )
- if( ARMEABI_V7A AND EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/libgnustl_shared.so" )
- set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/libgnustl_shared.so" )
- elseif( ARMEABI AND NOT ANDROID_FORCE_ARM_BUILD AND EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb/libgnustl_shared.so" )
- set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb/libgnustl_shared.so" )
- elseif( EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/libgnustl_shared.so" )
- set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/libgnustl_shared.so" )
- endif()
- endif()
- endif()
-endif()
-
-# clang
-if( "${ANDROID_TOOLCHAIN_NAME}" STREQUAL "standalone-clang" )
- set( ANDROID_COMPILER_IS_CLANG 1 )
- execute_process( COMMAND "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/clang${TOOL_OS_SUFFIX}" --version OUTPUT_VARIABLE ANDROID_CLANG_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE )
- string( REGEX MATCH "[0-9]+[.][0-9]+" ANDROID_CLANG_VERSION "${ANDROID_CLANG_VERSION}")
-elseif( "${ANDROID_TOOLCHAIN_NAME}" MATCHES "-clang3[.][0-9]?$" )
- string( REGEX MATCH "3[.][0-9]$" ANDROID_CLANG_VERSION "${ANDROID_TOOLCHAIN_NAME}")
- string( REGEX REPLACE "-clang${ANDROID_CLANG_VERSION}$" "-${ANDROID_COMPILER_VERSION}" ANDROID_GCC_TOOLCHAIN_NAME "${ANDROID_TOOLCHAIN_NAME}" )
- if( NOT EXISTS "${ANDROID_NDK_TOOLCHAINS_PATH}/llvm-${ANDROID_CLANG_VERSION}${ANDROID_NDK_TOOLCHAINS_SUBPATH}/bin/clang${TOOL_OS_SUFFIX}" )
- message( FATAL_ERROR "Could not find the Clang compiler driver" )
- endif()
- set( ANDROID_COMPILER_IS_CLANG 1 )
- set( ANDROID_CLANG_TOOLCHAIN_ROOT "${ANDROID_NDK_TOOLCHAINS_PATH}/llvm-${ANDROID_CLANG_VERSION}${ANDROID_NDK_TOOLCHAINS_SUBPATH}" )
-else()
- set( ANDROID_GCC_TOOLCHAIN_NAME "${ANDROID_TOOLCHAIN_NAME}" )
- unset( ANDROID_COMPILER_IS_CLANG CACHE )
-endif()
-
-string( REPLACE "." "" _clang_name "clang${ANDROID_CLANG_VERSION}" )
-if( NOT EXISTS "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/${_clang_name}${TOOL_OS_SUFFIX}" )
- set( _clang_name "clang" )
-endif()
-
-
-# setup paths and STL for NDK
-if( BUILD_WITH_ANDROID_NDK )
- set( ANDROID_TOOLCHAIN_ROOT "${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_GCC_TOOLCHAIN_NAME}${ANDROID_NDK_TOOLCHAINS_SUBPATH}" )
- set( ANDROID_SYSROOT "${ANDROID_NDK}/platforms/android-${ANDROID_NATIVE_API_LEVEL}/arch-${ANDROID_ARCH_NAME}" )
-
- if( ANDROID_STL STREQUAL "none" )
- # do nothing
- elseif( ANDROID_STL STREQUAL "system" )
- set( ANDROID_RTTI OFF )
- set( ANDROID_EXCEPTIONS OFF )
- set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_NDK}/sources/cxx-stl/system/include" )
- elseif( ANDROID_STL STREQUAL "system_re" )
- set( ANDROID_RTTI ON )
- set( ANDROID_EXCEPTIONS ON )
- set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_NDK}/sources/cxx-stl/system/include" )
- elseif( ANDROID_STL MATCHES "gabi" )
- if( ANDROID_NDK_RELEASE_NUM LESS 7000 ) # before r7
- message( FATAL_ERROR "gabi++ is not available in your NDK. You have to upgrade to NDK r7 or newer to use gabi++.")
- endif()
- set( ANDROID_RTTI ON )
- set( ANDROID_EXCEPTIONS OFF )
- set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_NDK}/sources/cxx-stl/gabi++/include" )
- set( __libstl "${ANDROID_NDK}/sources/cxx-stl/gabi++/libs/${ANDROID_NDK_ABI_NAME}/libgabi++_static.a" )
- elseif( ANDROID_STL MATCHES "stlport" )
- if( NOT ANDROID_NDK_RELEASE_NUM LESS 8004 ) # before r8d
- set( ANDROID_EXCEPTIONS ON )
- else()
- set( ANDROID_EXCEPTIONS OFF )
- endif()
- if( ANDROID_NDK_RELEASE_NUM LESS 7000 ) # before r7
- set( ANDROID_RTTI OFF )
- else()
- set( ANDROID_RTTI ON )
- endif()
- set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_NDK}/sources/cxx-stl/stlport/stlport" )
- set( __libstl "${ANDROID_NDK}/sources/cxx-stl/stlport/libs/${ANDROID_NDK_ABI_NAME}/libstlport_static.a" )
- elseif( ANDROID_STL MATCHES "gnustl" )
- set( ANDROID_EXCEPTIONS ON )
- set( ANDROID_RTTI ON )
- if( EXISTS "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/${ANDROID_COMPILER_VERSION}" )
- if( ARMEABI_V7A AND ANDROID_COMPILER_VERSION VERSION_EQUAL "4.7" AND ANDROID_NDK_RELEASE STREQUAL "r8d" )
- # gnustl binary for 4.7 compiler is buggy :(
- # TODO: look for right fix
- set( __libstl "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/4.6" )
- else()
- set( __libstl "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/${ANDROID_COMPILER_VERSION}" )
- endif()
- else()
- set( __libstl "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++" )
- endif()
- set( ANDROID_STL_INCLUDE_DIRS "${__libstl}/include" "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/include" "${__libstl}/include/backward" )
- if( EXISTS "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/libgnustl_static.a" )
- set( __libstl "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/libgnustl_static.a" )
- else()
- set( __libstl "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/libstdc++.a" )
- endif()
- elseif( ANDROID_STL MATCHES "c\\+\\+_shared" OR ANDROID_STL MATCHES "c\\+\\+_static" )
- set( ANDROID_EXCEPTIONS ON )
- set( ANDROID_RTTI ON )
- set( ANDROID_CXX_ROOT "${ANDROID_NDK}/sources/cxx-stl/" )
- set( ANDROID_LLVM_ROOT "${ANDROID_CXX_ROOT}/llvm-libc++" )
-
- if( X86 )
- set( ANDROID_ABI_INCLUDE_DIRS "${ANDROID_CXX_ROOT}/gabi++/include" )
- else()
- set( ANDROID_ABI_INCLUDE_DIRS "${ANDROID_CXX_ROOT}/llvm-libc++abi/include" )
- endif()
-
- set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_LLVM_ROOT}/libcxx/include" "${ANDROID_ABI_INCLUDE_DIRS}" )
-
- # android support sfiles
- include_directories ( SYSTEM ${ANDROID_NDK}/sources/android/support/include )
-
- if(ANDROID_STL MATCHES "c\\+\\+_shared")
- set ( LLVM_LIBRARY_NAME "libc++_shared.so")
- else()
- set ( LLVM_LIBRARY_NAME "libc++_static.a" )
- endif ()
-
- if( EXISTS "${ANDROID_LLVM_ROOT}/libs/${ANDROID_NDK_ABI_NAME}/${LLVM_LIBRARY_NAME}" )
- set( __libstl "${ANDROID_LLVM_ROOT}/libs/${ANDROID_NDK_ABI_NAME}/${LLVM_LIBRARY_NAME}" )
- else()
- message( FATAL_ERROR "Could not find libc++ library" )
- endif()
- else()
- message( FATAL_ERROR "Unknown runtime: ${ANDROID_STL}" )
- endif()
- # find libsupc++.a - rtti & exceptions
- if( ANDROID_STL STREQUAL "system_re" OR ANDROID_STL MATCHES "gnustl" )
- set( __libsupcxx "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/${ANDROID_COMPILER_VERSION}/libs/${ANDROID_NDK_ABI_NAME}/libsupc++.a" ) # r8b or newer
- if( NOT EXISTS "${__libsupcxx}" )
- set( __libsupcxx "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/libs/${ANDROID_NDK_ABI_NAME}/libsupc++.a" ) # r7-r8
- endif()
- if( NOT EXISTS "${__libsupcxx}" ) # before r7
- if( ARMEABI_V7A )
- if( ANDROID_FORCE_ARM_BUILD )
- set( __libsupcxx "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/libsupc++.a" )
- else()
- set( __libsupcxx "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/thumb/libsupc++.a" )
- endif()
- elseif( ARMEABI AND NOT ANDROID_FORCE_ARM_BUILD )
- set( __libsupcxx "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb/libsupc++.a" )
- else()
- set( __libsupcxx "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/libsupc++.a" )
- endif()
- endif()
- if( NOT EXISTS "${__libsupcxx}")
- message( ERROR "Could not find libsupc++.a for a chosen platform. Either your NDK is not supported or is broken.")
- endif()
- endif()
-endif()
-
-
-# case of shared STL linkage
-if( ANDROID_STL MATCHES "shared" AND DEFINED __libstl )
- string( REPLACE "_static.a" "_shared.so" __libstl "${__libstl}" )
- # TODO: check if .so file exists before the renaming
-endif()
-
-
-# ccache support
-__INIT_VARIABLE( _ndk_ccache NDK_CCACHE ENV_NDK_CCACHE )
-if( _ndk_ccache )
- if( DEFINED NDK_CCACHE AND NOT EXISTS NDK_CCACHE )
- unset( NDK_CCACHE CACHE )
- endif()
- find_program( NDK_CCACHE "${_ndk_ccache}" DOC "The path to ccache binary")
-else()
- unset( NDK_CCACHE CACHE )
-endif()
-unset( _ndk_ccache )
-
-
-# setup the cross-compiler
-if( NOT CMAKE_C_COMPILER )
- if( NDK_CCACHE AND NOT ANDROID_SYSROOT MATCHES "[ ;\"]" )
- set( CMAKE_C_COMPILER "${NDK_CCACHE}" CACHE PATH "ccache as C compiler" )
- set( CMAKE_CXX_COMPILER "${NDK_CCACHE}" CACHE PATH "ccache as C++ compiler" )
- if( ANDROID_COMPILER_IS_CLANG )
- set( CMAKE_C_COMPILER_ARG1 "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/${_clang_name}${TOOL_OS_SUFFIX}" CACHE PATH "C compiler")
- set( CMAKE_CXX_COMPILER_ARG1 "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/${_clang_name}++${TOOL_OS_SUFFIX}" CACHE PATH "C++ compiler")
- else()
- set( CMAKE_C_COMPILER_ARG1 "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc${TOOL_OS_SUFFIX}" CACHE PATH "C compiler")
- set( CMAKE_CXX_COMPILER_ARG1 "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-g++${TOOL_OS_SUFFIX}" CACHE PATH "C++ compiler")
- endif()
- else()
- if( ANDROID_COMPILER_IS_CLANG )
- set( CMAKE_C_COMPILER "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/${_clang_name}${TOOL_OS_SUFFIX}" CACHE PATH "C compiler")
- set( CMAKE_CXX_COMPILER "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/${_clang_name}++${TOOL_OS_SUFFIX}" CACHE PATH "C++ compiler")
- else()
- set( CMAKE_C_COMPILER "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc${TOOL_OS_SUFFIX}" CACHE PATH "C compiler" )
- set( CMAKE_CXX_COMPILER "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-g++${TOOL_OS_SUFFIX}" CACHE PATH "C++ compiler" )
- endif()
- endif()
- set( CMAKE_ASM_COMPILER "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc${TOOL_OS_SUFFIX}" CACHE PATH "assembler" )
- set( CMAKE_STRIP "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-strip${TOOL_OS_SUFFIX}" CACHE PATH "strip" )
- if( EXISTS "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc-ar${TOOL_OS_SUFFIX}" )
- # Use gcc-ar if we have it for better LTO support.
- set( CMAKE_AR "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc-ar${TOOL_OS_SUFFIX}" CACHE PATH "archive" )
- else()
- set( CMAKE_AR "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-ar${TOOL_OS_SUFFIX}" CACHE PATH "archive" )
- endif()
- set( CMAKE_LINKER "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-ld${TOOL_OS_SUFFIX}" CACHE PATH "linker" )
- set( CMAKE_NM "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-nm${TOOL_OS_SUFFIX}" CACHE PATH "nm" )
- set( CMAKE_OBJCOPY "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-objcopy${TOOL_OS_SUFFIX}" CACHE PATH "objcopy" )
- set( CMAKE_OBJDUMP "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-objdump${TOOL_OS_SUFFIX}" CACHE PATH "objdump" )
- set( CMAKE_RANLIB "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-ranlib${TOOL_OS_SUFFIX}" CACHE PATH "ranlib" )
-endif()
-
-set( _CMAKE_TOOLCHAIN_PREFIX "${ANDROID_TOOLCHAIN_MACHINE_NAME}-" )
-if( CMAKE_VERSION VERSION_LESS 2.8.5 )
- set( CMAKE_ASM_COMPILER_ARG1 "-c" )
-endif()
-if( APPLE )
- find_program( CMAKE_INSTALL_NAME_TOOL NAMES install_name_tool )
- if( NOT CMAKE_INSTALL_NAME_TOOL )
- message( FATAL_ERROR "Could not find install_name_tool, please check your installation." )
- endif()
- mark_as_advanced( CMAKE_INSTALL_NAME_TOOL )
-endif()
-
-# Force set compilers because standard identification works badly for us
-include( CMakeForceCompiler )
-CMAKE_FORCE_C_COMPILER( "${CMAKE_C_COMPILER}" GNU )
-if( ANDROID_COMPILER_IS_CLANG )
- set( CMAKE_C_COMPILER_ID Clang )
-endif()
-set( CMAKE_C_PLATFORM_ID Linux )
-if( X86_64 OR MIPS64 OR ARM64_V8A )
- set( CMAKE_C_SIZEOF_DATA_PTR 8 )
-else()
- set( CMAKE_C_SIZEOF_DATA_PTR 4 )
-endif()
-set( CMAKE_C_HAS_ISYSROOT 1 )
-set( CMAKE_C_COMPILER_ABI ELF )
-CMAKE_FORCE_CXX_COMPILER( "${CMAKE_CXX_COMPILER}" GNU )
-if( ANDROID_COMPILER_IS_CLANG )
- set( CMAKE_CXX_COMPILER_ID Clang)
-endif()
-set( CMAKE_CXX_PLATFORM_ID Linux )
-set( CMAKE_CXX_SIZEOF_DATA_PTR ${CMAKE_C_SIZEOF_DATA_PTR} )
-set( CMAKE_CXX_HAS_ISYSROOT 1 )
-set( CMAKE_CXX_COMPILER_ABI ELF )
-set( CMAKE_CXX_SOURCE_FILE_EXTENSIONS cc cp cxx cpp CPP c++ C )
-# force ASM compiler (required for CMake < 2.8.5)
-set( CMAKE_ASM_COMPILER_ID_RUN TRUE )
-set( CMAKE_ASM_COMPILER_ID GNU )
-set( CMAKE_ASM_COMPILER_WORKS TRUE )
-set( CMAKE_ASM_COMPILER_FORCED TRUE )
-set( CMAKE_COMPILER_IS_GNUASM 1)
-set( CMAKE_ASM_SOURCE_FILE_EXTENSIONS s S asm )
-
-foreach( lang C CXX ASM )
- if( ANDROID_COMPILER_IS_CLANG )
- set( CMAKE_${lang}_COMPILER_VERSION ${ANDROID_CLANG_VERSION} )
- else()
- set( CMAKE_${lang}_COMPILER_VERSION ${ANDROID_COMPILER_VERSION} )
- endif()
-endforeach()
-
-# flags and definitions
-remove_definitions( -DANDROID )
-add_definitions( -DANDROID )
-
-if( ANDROID_SYSROOT MATCHES "[ ;\"]" )
- if( CMAKE_HOST_WIN32 )
- # try to convert path to 8.3 form
- file( WRITE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/cvt83.cmd" "@echo %~s1" )
- execute_process( COMMAND "$ENV{ComSpec}" /c "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/cvt83.cmd" "${ANDROID_SYSROOT}"
- OUTPUT_VARIABLE __path OUTPUT_STRIP_TRAILING_WHITESPACE
- RESULT_VARIABLE __result ERROR_QUIET )
- if( __result EQUAL 0 )
- file( TO_CMAKE_PATH "${__path}" ANDROID_SYSROOT )
- set( ANDROID_CXX_FLAGS "--sysroot=${ANDROID_SYSROOT}" )
- else()
- set( ANDROID_CXX_FLAGS "--sysroot=\"${ANDROID_SYSROOT}\"" )
- endif()
- else()
- set( ANDROID_CXX_FLAGS "'--sysroot=${ANDROID_SYSROOT}'" )
- endif()
- if( NOT _CMAKE_IN_TRY_COMPILE )
- # quotes can break try_compile and compiler identification
- message(WARNING "Path to your Android NDK (or toolchain) has non-alphanumeric symbols.\nThe build might be broken.\n")
- endif()
-else()
- set( ANDROID_CXX_FLAGS "--sysroot=${ANDROID_SYSROOT}" )
-endif()
-
-# NDK flags
-if (ARM64_V8A )
- set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funwind-tables" )
- set( ANDROID_CXX_FLAGS_RELEASE "-fomit-frame-pointer -fstrict-aliasing" )
- set( ANDROID_CXX_FLAGS_DEBUG "-fno-omit-frame-pointer -fno-strict-aliasing" )
- if( NOT ANDROID_COMPILER_IS_CLANG )
- set( ANDROID_CXX_FLAGS_RELEASE "${ANDROID_CXX_FLAGS_RELEASE} -funswitch-loops -finline-limit=300" )
- endif()
-elseif( ARMEABI OR ARMEABI_V7A)
- set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funwind-tables" )
- if( NOT ANDROID_FORCE_ARM_BUILD AND NOT ARMEABI_V6 )
- set( ANDROID_CXX_FLAGS_RELEASE "-mthumb -fomit-frame-pointer -fno-strict-aliasing" )
- set( ANDROID_CXX_FLAGS_DEBUG "-marm -fno-omit-frame-pointer -fno-strict-aliasing" )
- if( NOT ANDROID_COMPILER_IS_CLANG )
- set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -finline-limit=64" )
- endif()
- else()
- # always compile ARMEABI_V6 in arm mode; otherwise there is no difference from ARMEABI
- set( ANDROID_CXX_FLAGS_RELEASE "-marm -fomit-frame-pointer -fstrict-aliasing" )
- set( ANDROID_CXX_FLAGS_DEBUG "-marm -fno-omit-frame-pointer -fno-strict-aliasing" )
- if( NOT ANDROID_COMPILER_IS_CLANG )
- set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funswitch-loops -finline-limit=300" )
- endif()
- endif()
-elseif( X86 OR X86_64 )
- set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funwind-tables" )
- if( NOT ANDROID_COMPILER_IS_CLANG )
- set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funswitch-loops -finline-limit=300" )
- endif()
- set( ANDROID_CXX_FLAGS_RELEASE "-fomit-frame-pointer -fstrict-aliasing" )
- set( ANDROID_CXX_FLAGS_DEBUG "-fno-omit-frame-pointer -fno-strict-aliasing" )
-elseif( MIPS OR MIPS64 )
- set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fno-strict-aliasing -finline-functions -funwind-tables -fmessage-length=0" )
- set( ANDROID_CXX_FLAGS_RELEASE "-fomit-frame-pointer" )
- set( ANDROID_CXX_FLAGS_DEBUG "-fno-omit-frame-pointer" )
- if( NOT ANDROID_COMPILER_IS_CLANG )
- set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fno-inline-functions-called-once -fgcse-after-reload -frerun-cse-after-loop -frename-registers" )
- set( ANDROID_CXX_FLAGS_RELEASE "${ANDROID_CXX_FLAGS_RELEASE} -funswitch-loops -finline-limit=300" )
- endif()
-elseif()
- set( ANDROID_CXX_FLAGS_RELEASE "" )
- set( ANDROID_CXX_FLAGS_DEBUG "" )
-endif()
-
-set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fsigned-char" ) # good/necessary when porting desktop libraries
-
-if( NOT X86 AND NOT ANDROID_COMPILER_IS_CLANG )
- set( ANDROID_CXX_FLAGS "-Wno-psabi ${ANDROID_CXX_FLAGS}" )
-endif()
-
-if( NOT ANDROID_COMPILER_VERSION VERSION_LESS "4.6" )
- set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -no-canonical-prefixes" ) # see https://android-review.googlesource.com/#/c/47564/
-endif()
-
-# ABI-specific flags
-if( ARMEABI_V7A )
- set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -march=armv7-a -mfloat-abi=softfp" )
- if( NEON )
- set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -mfpu=neon" )
- elseif( VFPV3 )
- set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -mfpu=vfpv3" )
- else()
- set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -mfpu=vfpv3-d16" )
- endif()
-elseif( ARMEABI_V6 )
- set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -march=armv6 -mfloat-abi=softfp -mfpu=vfp" ) # vfp == vfpv2
-elseif( ARMEABI )
- set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -march=armv5te -mtune=xscale -msoft-float" )
-endif()
-
-if( ANDROID_STL MATCHES "gnustl" AND (EXISTS "${__libstl}" OR EXISTS "${__libsupcxx}") )
- set( CMAKE_CXX_CREATE_SHARED_LIBRARY " -o " )
- set( CMAKE_CXX_CREATE_SHARED_MODULE " -o " )
- set( CMAKE_CXX_LINK_EXECUTABLE " -o " )
-else()
- set( CMAKE_CXX_CREATE_SHARED_LIBRARY " -o " )
- set( CMAKE_CXX_CREATE_SHARED_MODULE " -o " )
- set( CMAKE_CXX_LINK_EXECUTABLE "