diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000000..f000a27017 --- /dev/null +++ b/.clang-format @@ -0,0 +1,39 @@ +Language: Cpp +Standard: Cpp11 +BasedOnStyle: "Chromium" +ColumnLimit: 128 +IndentWidth: 4 +UseTab: Never + +BreakBeforeBraces: Custom +BraceWrapping: + AfterEnum: true + AfterClass: false + AfterControlStatement: false + AfterFunction: false + AfterNamespace: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + SplitEmptyFunction: false + SplitEmptyNamespace: true + + +AccessModifierOffset: -4 +AllowShortFunctionsOnASingleLine: InlineOnly +BreakConstructorInitializers: BeforeColon +BreakConstructorInitializersBeforeComma: true +IndentCaseLabels: true +ReflowComments: false +Cpp11BracedListStyle: false +ContinuationIndentWidth: 4 +ConstructorInitializerAllOnOneLineOrOnePerLine: false +CompactNamespaces: true +SortIncludes: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements + +PenaltyReturnTypeOnItsOwnLine: 1000 +PenaltyBreakBeforeFirstCallParameter: 1000 + diff --git a/.eslintrc.js b/.eslintrc.js index b4d88777f2..5667a04984 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 }], @@ -64,7 +68,7 @@ module.exports = { "eqeqeq": ["error", "always"], "indent": ["error", 4, { "SwitchCase": 1 }], "keyword-spacing": ["error", { "before": true, "after": true }], - "max-len": ["error", 192, 4], + "max-len": ["error", 128, 4], "new-cap": ["error"], "no-floating-decimal": ["error"], //"no-magic-numbers": ["error", { "ignore": [0, 1], "ignoreArrayIndexes": true }], 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..c1eef3817f 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,20 @@ ext/ Makefile *.user +# Android Studio +*.iml +local.properties +android/libraries + +# VSCode +# List taken from Github Global Ignores master@435c4d92 +# https://github.com/github/gitignore/commits/master/Global/VisualStudioCode.gitignore +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + # Xcode *.xcodeproj *.xcworkspace @@ -50,6 +64,10 @@ gvr-interface/libs/* # ignore files for various dev environments TAGS *.sw[po] +*.qmlc + +# ignore QML compilation output +*.qmlc # ignore node files for the console node_modules diff --git a/BUILD.md b/BUILD.md index c45b7cb636..feed677828 100644 --- a/BUILD.md +++ b/BUILD.md @@ -1,34 +1,31 @@ ### Dependencies -* [cmake](https://cmake.org/download/) ~> 3.3.2 -* [Qt](https://www.qt.io/download-open-source) ~> 5.6.2 -* [OpenSSL](https://www.openssl.org/community/binaries.html) - * IMPORTANT: Use the latest available version of OpenSSL to avoid security vulnerabilities. -* [VHACD](https://github.com/virneo/v-hacd)(clone this repository)(Optional) +- [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 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 +### CMake External Project Dependencies -* [boostconfig](https://github.com/boostorg/config) ~> 1.58 -* [Bullet Physics Engine](https://github.com/bulletphysics/bullet3/releases) ~> 2.83 -* [GLEW](http://glew.sourceforge.net/) -* [glm](https://glm.g-truc.net/0.9.5/index.html) ~> 0.9.5.4 -* [gverb](https://github.com/highfidelity/gverb) -* [Oculus SDK](https://developer.oculus.com/downloads/) ~> 0.6 (Win32) / 0.5 (Mac / Linux) -* [oglplus](http://oglplus.org/) ~> 0.63 -* [OpenVR](https://github.com/ValveSoftware/openvr) ~> 0.91 (Win32 only) -* [Polyvox](http://www.volumesoffun.com/) ~> 0.2.1 -* [QuaZip](https://sourceforge.net/projects/quazip/files/quazip/) ~> 0.7.1 -* [SDL2](https://www.libsdl.org/download-2.0.php) ~> 2.0.3 -* [soxr](https://sourceforge.net/p/soxr/wiki/Home/) ~> 0.1.1 -* [Intel Threading Building Blocks](https://www.threadingbuildingblocks.org/) ~> 4.3 -* [Sixense](http://sixense.com/) ~> 071615 -* [zlib](http://www.zlib.net/) ~> 1.28 (Win32 only) +These dependencies need not be installed manually. They are automatically downloaded on the platforms where they are required. +- [Bullet Physics Engine](https://github.com/bulletphysics/bullet3/releases): 2.83 +- [GLEW](http://glew.sourceforge.net/): 1.13 +- [glm](https://glm.g-truc.net/0.9.8/index.html): 0.9.8 +- [Oculus SDK](https://developer.oculus.com/downloads/): 1.11 (Win32) / 0.5 (Mac) +- [OpenVR](https://github.com/ValveSoftware/openvr): 1.0.6 (Win32 only) +- [Polyvox](http://www.volumesoffun.com/): 0.2.1 +- [QuaZip](https://sourceforge.net/projects/quazip/files/quazip/): 0.7.3 +- [SDL2](https://www.libsdl.org/download-2.0.php): 2.0.3 +- [Intel Threading Building Blocks](https://www.threadingbuildingblocks.org/): 4.3 +- [Sixense](http://sixense.com/): 071615 +- [zlib](http://www.zlib.net/): 1.28 (Win32 only) +- nVidia Texture Tools: 2.1 The above dependencies will be downloaded, built, linked and included automatically by CMake where we require them. The CMakeLists files that handle grabbing each of the following external dependencies can be found in the [cmake/externals folder](cmake/externals). The resulting downloads, source files and binaries will be placed in the `build/ext` folder in each of the subfolders for each external project. These are not placed in your normal build tree when doing an out of source build so that they do not need to be re-downloaded and re-compiled every time the CMake build folder is cleared. Should you want to force a re-download and re-compile of a specific external, you can simply remove that directory from the appropriate subfolder in `build/ext`. Should you want to force a re-download and re-compile of all externals, just remove the `build/ext` folder. -If you would like to use a specific install of a dependency instead of the version that would be grabbed as a CMake ExternalProject, you can pass -DUSE_LOCAL_$NAME=0 (where $NAME is the name of the subfolder in [cmake/externals](cmake/externals)) when you run CMake to tell it not to get that dependency as an external project. +If you would like to use a specific install of a dependency instead of the version that would be grabbed as a CMake ExternalProject, you can pass -DUSE\_LOCAL\_$NAME=0 (where $NAME is the name of the subfolder in [cmake/externals](cmake/externals)) when you run CMake to tell it not to get that dependency as an external project. ### OS Specific Build Guides 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_LINUX.md b/BUILD_LINUX.md index d40576a75d..038f53154c 100644 --- a/BUILD_LINUX.md +++ b/BUILD_LINUX.md @@ -1,7 +1,96 @@ +# Linux build guide + Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only Linux specific instructions are found in this file. -### Qt5 Dependencies +## Qt5 Dependencies Should you choose not to install Qt5 via a package manager that handles dependencies for you, you may be missing some Qt5 dependencies. On Ubuntu, for example, the following additional packages are required: libasound2 libxmu-dev libxi-dev freeglut3-dev libasound2-dev libjack0 libjack-dev libxrandr-dev libudev-dev libssl-dev + +## Ubuntu 16.04 specific build guide + +### Prepare environment + +Install qt: +```bash +wget http://debian.highfidelity.com/pool/h/hi/hifi-qt5.6.1_5.6.1_amd64.deb +sudo dpkg -i hifi-qt5.6.1_5.6.1_amd64.deb +``` + +Install build dependencies: +```bash +sudo apt-get install libasound2 libxmu-dev libxi-dev freeglut3-dev libasound2-dev libjack0 libjack-dev libxrandr-dev libudev-dev libssl-dev +``` + +To compile interface in a server you must install: +```bash +sudo apt -y install libpulse0 libnss3 libnspr4 libfontconfig1 libxcursor1 libxcomposite1 libxtst6 libxslt1.1 +``` + +Install build tools: +```bash +sudo apt install cmake +``` + +### Get code and checkout the tag you need + +Clone this repository: +```bash +git clone https://github.com/highfidelity/hifi.git +``` + +To compile a RELEASE version checkout the tag you need getting a list of all tags: +```bash +git fetch -a +git tags +``` + +Then checkout last tag with: +```bash +git checkout tags/RELEASE-6819 +``` + +Or go to the highfidelity download page (https://highfidelity.com/download) to get the release version. For example, if there is a BETA 6731 type: +```bash +git checkout tags/RELEASE-6731 +``` + +### Compiling + +Create the build directory: +```bash +mkdir -p hifi/build +cd hifi/build +``` + +Prepare makefiles: +```bash +cmake -DQT_CMAKE_PREFIX_PATH=/usr/local/Qt5.6.1/5.6/gcc_64/lib/cmake .. +``` + +Start compilation and get a cup of coffee: +```bash +make domain-server assignment-client interface +``` + +In a server does not make sense to compile interface + +### Running the software + +Running domain server: +```bash +./domain-server/domain-server +``` + +Running assignment client: +```bash +./assignment-client/assignment-client -n 6 +``` + +Running interface: +```bash +./interface/interface +``` + +Go to localhost in running interface. 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 818a176f75..eea1f85e5b 100644 --- a/BUILD_WIN.md +++ b/BUILD_WIN.md @@ -1,46 +1,58 @@ This is a stand-alone guide for creating your first High Fidelity build for Windows 64-bit. ## 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. -### Step 1. Installing Visual Studio 2013 +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. -If you don't already have the Community or Professional edition of Visual Studio 2013, download and install [Visual Studio Community 2013](https://www.visualstudio.com/en-us/news/releasenotes/vs2013-community-vs). You do not need to install any of the optional components when going through the installer. +### Step 1. Visual Studio 2017 -Note: Newer versions of Visual Studio are not yet compatible. +If you don’t have Community or Professional edition of Visual Studio 2017, download [Visual Studio Community 2017](https://www.visualstudio.com/downloads/). + +When selecting components, check "Desktop development with C++." Also check "Windows 8.1 SDK and UCRT SDK" and "VC++ 2015.3 v140 toolset (x86,x64)" on the Summary toolbar on the right. ### Step 2. Installing CMake -Download and install the [CMake 3.8.0 win64-x64 Installer](https://cmake.org/files/v3.8/cmake-3.8.0-win64-x64.msi). Make sure "Add CMake to system PATH for all users" is checked when going through the installer. +Download and install the latest version of CMake 3.9. Download the file named win64-x64 Installer from the [CMake Website](https://cmake.org/download/). Make sure to check "Add CMake to system PATH for all users" when prompted during installation. ### Step 3. Installing Qt -Download and install the [Qt 5.6.2 for Windows 64-bit (VS 2013)](http://download.qt.io/official_releases/qt/5.6/5.6.2/qt-opensource-windows-x86-msvc2013_64-5.6.2.exe). +Download and install the [Qt Online Installer](https://www.qt.io/download-open-source/?hsCtaTracking=f977210e-de67-475f-a32b-65cec207fd03%7Cd62710cd-e1db-46aa-8d4d-2f1c1ffdacea). While installing, you only need to have the following components checked under Qt 5.9.1: "msvc2017 64-bit", "Qt WebEngine", and "Qt Script (Deprecated)". -Keep the default components checked when going through the installer. +Note: Installing the Sources is optional but recommended if you have room for them (~2GB). ### Step 4. Setting Qt Environment Variable Go to `Control Panel > System > Advanced System Settings > Environment Variables > New...` (or search “Environment Variables” in Start Search). * Set "Variable name": `QT_CMAKE_PREFIX_PATH` -* Set "Variable value": `%QT_DIR%\5.6\msvc2013_64\lib\cmake` +* 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.2L Installer](https://slproweb.com/download/Win64OpenSSL-1_0_2L.exe). + * 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: -```` +``` cd "%HIFI_DIR%" mkdir build cd build -cmake .. -G "Visual Studio 12 Win64" -```` +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. @@ -48,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` @@ -62,24 +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. -#### nmake cannot be found +#### CMake can't find OpenSSL -Make sure nmake.exe is located at the following path: - - C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\bin - -If not, add the directory where nmake is located to the PATH environment variable. +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.6.2) installed and `QT_CMAKE_PREFIX_PATH` environment variable is set correctly. +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 3c90256134..2c10e714a3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,253 +1,95 @@ -cmake_minimum_required(VERSION 3.2) - -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") +# 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() 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) - add_definitions(-DNOMINMAX -D_CRT_SECURE_NO_WARNINGS) +include("cmake/compiler.cmake") - 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 (MSVC10) - set(WINDOW_SDK_PATH "C:\\Program Files\\Microsoft SDKs\\Windows\\v7.1 " CACHE PATH "Windows SDK PATH") - elseif (MSVC12) - if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") - set(WINDOW_SDK_FOLDER "x64") - else() - set(WINDOW_SDK_FOLDER "x86") - endif() - set(WINDOW_SDK_PATH "C:\\Program Files (x86)\\Windows Kits\\8.1\\Lib\\winv6.3\\um\\${WINDOW_SDK_FOLDER}" CACHE PATH "Windows SDK PATH") - 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++0x") - 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) @@ -255,28 +97,35 @@ if (NOT ANDROID) add_subdirectory(ice-server) set_target_properties(ice-server PROPERTIES FOLDER "Apps") add_subdirectory(server-console) - if (NOT SERVER_ONLY) - add_subdirectory(interface) - set_target_properties(interface PROPERTIES FOLDER "Apps") - add_subdirectory(tests) - endif() - add_subdirectory(plugins) - add_subdirectory(tools) endif() -if (ANDROID OR DESKTOP_GVR) - add_subdirectory(interface) - add_subdirectory(gvr-interface) - add_subdirectory(plugins) -endif () +if (BUILD_CLIENT) + add_subdirectory(interface) + set_target_properties(interface PROPERTIES FOLDER "Apps") + if (ANDROID) + add_subdirectory(gvr-interface) + set_target_properties(gvr-interface PROPERTIES FOLDER "Apps") + endif() +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 () +if (BUILD_CLIENT OR BUILD_SERVER) + add_subdirectory(plugins) +endif() -generate_installers() +# BUILD_TOOLS option will be handled inside the tools's CMakeLists.txt because 'scribe' tool is required for build anyway +add_subdirectory(tools) + +if (BUILD_TESTS) + add_subdirectory(tests) +endif() + +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/CONTRIBUTING.md b/CONTRIBUTING.md index a0d867ade9..4654c311cc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,7 +16,7 @@ Contributing git checkout -b new_branch_name ``` 4. Code - * Follow the [coding standard](https://wiki.highfidelity.com/wiki/Coding_Standards) + * Follow the [coding standard](https://docs.highfidelity.com/build-guide/coding-standards) 5. Commit * Use [well formed commit messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) 6. Update your branch 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 0ef2db3b9d..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 - physics plugins + controllers physics plugins midi baking image ) if (WIN32) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 8aec5adb1f..4efc3343d1 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -50,14 +51,14 @@ #include "RecordingScriptingInterface.h" #include "AbstractAudioInterface.h" -#include "AvatarAudioTimer.h" static const int RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES = 10; Agent::Agent(ReceivedMessage& message) : ThreadedAssignment(message), _receivedAudioStream(RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES, RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES), - _audioGate(AudioConstants::SAMPLE_RATE, AudioConstants::MONO) + _audioGate(AudioConstants::SAMPLE_RATE, AudioConstants::MONO), + _avatarAudioTimer(this) { _entityEditSender.setPacketsPerSecond(DEFAULT_ENTITY_PPS_PER_SCRIPT); DependencyManager::get()->setPacketSender(&_entityEditSender); @@ -81,6 +82,9 @@ Agent::Agent(ReceivedMessage& message) : DependencyManager::set(); DependencyManager::set(); + // Needed to ensure the creation of the DebugDraw instance on the main thread + DebugDraw::getInstance(); + auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); @@ -92,6 +96,14 @@ Agent::Agent(ReceivedMessage& message) : this, "handleOctreePacket"); packetReceiver.registerListener(PacketType::Jurisdiction, this, "handleJurisdictionPacket"); packetReceiver.registerListener(PacketType::SelectedAudioFormat, this, "handleSelectedAudioFormat"); + + + // 100Hz timer for audio + const int TARGET_INTERVAL_MSEC = 10; // 10ms + connect(&_avatarAudioTimer, &QTimer::timeout, this, &Agent::processAgentAvatarAudio); + _avatarAudioTimer.setSingleShot(false); + _avatarAudioTimer.setInterval(TARGET_INTERVAL_MSEC); + _avatarAudioTimer.setTimerType(Qt::PreciseTimer); } void Agent::playAvatarSound(SharedSoundPointer sound) { @@ -172,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 }); @@ -247,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); @@ -332,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 @@ -471,14 +494,7 @@ void Agent::executeScript() { DependencyManager::set(_entityViewer.getTree()); - // 100Hz timer for audio - AvatarAudioTimer* audioTimerWorker = new AvatarAudioTimer(); - audioTimerWorker->moveToThread(&_avatarAudioTimerThread); - connect(audioTimerWorker, &AvatarAudioTimer::avatarTick, this, &Agent::processAgentAvatarAudio); - connect(this, &Agent::startAvatarAudioTimer, audioTimerWorker, &AvatarAudioTimer::start); - connect(this, &Agent::stopAvatarAudioTimer, audioTimerWorker, &AvatarAudioTimer::stop); - connect(&_avatarAudioTimerThread, &QThread::finished, audioTimerWorker, &QObject::deleteLater); - _avatarAudioTimerThread.start(); + QMetaObject::invokeMethod(&_avatarAudioTimer, "start"); // Agents should run at 45hz static const int AVATAR_DATA_HZ = 45; @@ -557,7 +573,7 @@ void Agent::setIsAvatar(bool isAvatar) { _avatarIdentityTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS); // FIXME - we shouldn't really need to constantly send identity packets // tell the avatarAudioTimer to start ticking - emit startAvatarAudioTimer(); + QMetaObject::invokeMethod(&_avatarAudioTimer, "start"); } @@ -586,7 +602,7 @@ void Agent::setIsAvatar(bool isAvatar) { nodeList->sendPacket(std::move(packet), *node); }); } - emit stopAvatarAudioTimer(); + QMetaObject::invokeMethod(&_avatarAudioTimer, "stop"); } } @@ -604,6 +620,24 @@ void Agent::processAgentAvatar() { AvatarData::AvatarDataDetail dataDetail = (randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO) ? AvatarData::SendAllData : AvatarData::CullSmallData; QByteArray avatarByteArray = scriptedAvatar->toByteArrayStateful(dataDetail); + + int maximumByteArraySize = NLPacket::maxPayloadSize(PacketType::AvatarData) - sizeof(AvatarDataSequenceNumber); + + if (avatarByteArray.size() > maximumByteArraySize) { + qWarning() << " scriptedAvatar->toByteArrayStateful() resulted in very large buffer:" << avatarByteArray.size() << "... attempt to drop facial data"; + avatarByteArray = scriptedAvatar->toByteArrayStateful(dataDetail, true); + + if (avatarByteArray.size() > maximumByteArraySize) { + qWarning() << " scriptedAvatar->toByteArrayStateful() without facial data resulted in very large buffer:" << avatarByteArray.size() << "... reduce to MinimumData"; + avatarByteArray = scriptedAvatar->toByteArrayStateful(AvatarData::MinimumData, true); + + if (avatarByteArray.size() > maximumByteArraySize) { + qWarning() << " scriptedAvatar->toByteArrayStateful() MinimumData resulted in very large buffer:" << avatarByteArray.size() << "... FAIL!!"; + return; + } + } + } + scriptedAvatar->doneEncoding(true); static AvatarDataSequenceNumber sequenceNumber = 0; @@ -796,8 +830,7 @@ void Agent::aboutToFinish() { DependencyManager::destroy(); DependencyManager::destroy(); - emit stopAvatarAudioTimer(); - _avatarAudioTimerThread.quit(); + QMetaObject::invokeMethod(&_avatarAudioTimer, "stop"); // cleanup codec & encoder if (_codec && _encoder) { diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h index 549a0858b7..168da185b6 100644 --- a/assignment-client/src/Agent.h +++ b/assignment-client/src/Agent.h @@ -77,20 +77,18 @@ private slots: void handleSelectedAudioFormat(QSharedPointer message); void nodeActivated(SharedNodePointer activatedNode); + void nodeKilled(SharedNodePointer killedNode); void processAgentAvatar(); void processAgentAvatarAudio(); -signals: - void startAvatarAudioTimer(); - void stopAvatarAudioTimer(); private: void negotiateAudioFormat(); void selectAudioFormat(const QString& selectedCodecName); void encodeFrameOfZeros(QByteArray& encodedZeros); void computeLoudness(const QByteArray* decodedBuffer, QSharedPointer); - std::unique_ptr _scriptEngine; + ScriptEnginePointer _scriptEngine; EntityEditPacketSender _entityEditSender; EntityTreeHeadlessViewer _entityViewer; @@ -112,13 +110,13 @@ private: QHash _outgoingScriptAudioSequenceNumbers; AudioGate _audioGate; - bool _audioGateOpen { false }; + bool _audioGateOpen { true }; bool _isNoiseGateEnabled { false }; CodecPluginPointer _codec; QString _selectedCodecName; Encoder* _encoder { nullptr }; - QThread _avatarAudioTimerThread; + QTimer _avatarAudioTimer; bool _flushEncoder { false }; }; diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp index abfc66ac55..efced972a0 100644 --- a/assignment-client/src/AssignmentClient.cpp +++ b/assignment-client/src/AssignmentClient.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -141,7 +142,7 @@ void AssignmentClient::stopAssignmentClient() { QThread* currentAssignmentThread = _currentAssignment->thread(); // ask the current assignment to stop - QMetaObject::invokeMethod(_currentAssignment, "stop", Qt::BlockingQueuedConnection); + BLOCKING_INVOKE_METHOD(_currentAssignment, "stop"); // ask the current assignment to delete itself on its thread _currentAssignment->deleteLater(); diff --git a/assignment-client/src/AssignmentClientApp.cpp b/assignment-client/src/AssignmentClientApp.cpp index 7e9042e609..dd3050ba4e 100644 --- a/assignment-client/src/AssignmentClientApp.cpp +++ b/assignment-client/src/AssignmentClientApp.cpp @@ -9,21 +9,21 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include -#include +#include "AssignmentClientApp.h" + +#include +#include +#include +#include #include -#include #include +#include #include #include "Assignment.h" #include "AssignmentClient.h" #include "AssignmentClientMonitor.h" -#include "AssignmentClientApp.h" -#include -#include - AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) : QCoreApplication(argc, argv) @@ -87,6 +87,9 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) : const QCommandLineOption logDirectoryOption(ASSIGNMENT_LOG_DIRECTORY, "directory to store logs", "log-directory"); parser.addOption(logDirectoryOption); + const QCommandLineOption parentPIDOption(PARENT_PID_OPTION, "PID of the parent process", "parent-pid"); + parser.addOption(parentPIDOption); + if (!parser.parse(QCoreApplication::arguments())) { qCritical() << parser.errorText() << endl; parser.showHelp(); @@ -203,6 +206,16 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) : } } + if (parser.isSet(parentPIDOption)) { + bool ok = false; + int parentPID = parser.value(parentPIDOption).toInt(&ok); + + if (ok) { + qDebug() << "Parent process PID is" << parentPID; + watchParentProcess(parentPID); + } + } + QThread::currentThread()->setObjectName("main thread"); DependencyManager::registerInheritance(); diff --git a/assignment-client/src/AssignmentClientMonitor.cpp b/assignment-client/src/AssignmentClientMonitor.cpp index 1ee876ceea..1868ccfafe 100644 --- a/assignment-client/src/AssignmentClientMonitor.cpp +++ b/assignment-client/src/AssignmentClientMonitor.cpp @@ -28,6 +28,10 @@ const QString ASSIGNMENT_CLIENT_MONITOR_TARGET_NAME = "assignment-client-monitor"; const int WAIT_FOR_CHILD_MSECS = 1000; +#ifdef Q_OS_WIN +HANDLE PROCESS_GROUP = createProcessGroup(); +#endif + AssignmentClientMonitor::AssignmentClientMonitor(const unsigned int numAssignmentClientForks, const unsigned int minAssignmentClientForks, const unsigned int maxAssignmentClientForks, @@ -91,9 +95,22 @@ void AssignmentClientMonitor::simultaneousWaitOnChildren(int waitMsecs) { } } -void AssignmentClientMonitor::childProcessFinished(qint64 pid) { +void AssignmentClientMonitor::childProcessFinished(qint64 pid, int exitCode, QProcess::ExitStatus exitStatus) { + auto message = "Child process " + QString::number(pid) + " has %1 with exit code " + QString::number(exitCode) + "."; + if (_childProcesses.remove(pid)) { - qDebug() << "Child process" << pid << "has finished. Removed from internal map."; + message.append(" Removed from internal map."); + } else { + message.append(" Could not find process in internal map."); + } + + switch (exitStatus) { + case QProcess::NormalExit: + qDebug() << qPrintable(message.arg("returned")); + break; + case QProcess::CrashExit: + qCritical() << qPrintable(message.arg("crashed")); + break; } } @@ -131,7 +148,6 @@ void AssignmentClientMonitor::aboutToQuit() { void AssignmentClientMonitor::spawnChildClient() { QProcess* assignmentClient = new QProcess(this); - // unparse the parts of the command-line that the child cares about QStringList _childArguments; if (_assignmentPool != "") { @@ -160,6 +176,9 @@ void AssignmentClientMonitor::spawnChildClient() { _childArguments.append("--" + ASSIGNMENT_CLIENT_MONITOR_PORT_OPTION); _childArguments.append(QString::number(DependencyManager::get()->getLocalSockAddr().getPort())); + _childArguments.append("--" + PARENT_PID_OPTION); + _childArguments.append(QString::number(QCoreApplication::applicationPid())); + QString nowString, stdoutFilenameTemp, stderrFilenameTemp, stdoutPathTemp, stderrPathTemp; @@ -187,6 +206,10 @@ void AssignmentClientMonitor::spawnChildClient() { assignmentClient->setProcessChannelMode(QProcess::ForwardedChannels); assignmentClient->start(QCoreApplication::applicationFilePath(), _childArguments); +#ifdef Q_OS_WIN + addProcessToGroup(PROCESS_GROUP, assignmentClient->processId()); +#endif + QString stdoutPath, stderrPath; if (_wantsChildFileLogging) { @@ -219,7 +242,9 @@ void AssignmentClientMonitor::spawnChildClient() { auto pid = assignmentClient->processId(); // make sure we hear that this process has finished when it does connect(assignmentClient, static_cast(&QProcess::finished), - this, [this, pid]() { childProcessFinished(pid); }); + this, [this, pid](int exitCode, QProcess::ExitStatus exitStatus) { + childProcessFinished(pid, exitCode, exitStatus); + }); qDebug() << "Spawned a child client with PID" << assignmentClient->processId(); diff --git a/assignment-client/src/AssignmentClientMonitor.h b/assignment-client/src/AssignmentClientMonitor.h index a7f69a559b..8848d503ae 100644 --- a/assignment-client/src/AssignmentClientMonitor.h +++ b/assignment-client/src/AssignmentClientMonitor.h @@ -44,7 +44,7 @@ public: void stopChildProcesses(); private slots: void checkSpares(); - void childProcessFinished(qint64 pid); + void childProcessFinished(qint64 pid, int exitCode, QProcess::ExitStatus exitStatus); void handleChildStatusPacket(QSharedPointer message); bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false) override; diff --git a/assignment-client/src/AvatarAudioTimer.cpp b/assignment-client/src/AvatarAudioTimer.cpp deleted file mode 100644 index d031b9d9f6..0000000000 --- a/assignment-client/src/AvatarAudioTimer.cpp +++ /dev/null @@ -1,36 +0,0 @@ -// -// AvatarAudioTimer.cpp -// assignment-client/src -// -// Created by David Kelly on 10/12/13. -// Copyright 2016 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 -#include -#include "AvatarAudioTimer.h" - -// this should send a signal every 10ms, with pretty good precision. Hardcoding -// to 10ms since that's what you'd want for audio. -void AvatarAudioTimer::start() { - auto startTime = usecTimestampNow(); - quint64 frameCounter = 0; - const int TARGET_INTERVAL_USEC = 10000; // 10ms - while (!_quit) { - ++frameCounter; - - // tick every 10ms from startTime - quint64 targetTime = startTime + frameCounter * TARGET_INTERVAL_USEC; - quint64 now = usecTimestampNow(); - - // avoid quint64 underflow - if (now < targetTime) { - usleep(targetTime - now); - } - - emit avatarTick(); - } - qDebug() << "AvatarAudioTimer is finished"; -} diff --git a/assignment-client/src/AvatarAudioTimer.h b/assignment-client/src/AvatarAudioTimer.h deleted file mode 100644 index 1f6381b030..0000000000 --- a/assignment-client/src/AvatarAudioTimer.h +++ /dev/null @@ -1,31 +0,0 @@ -// -// AvatarAudioTimer.h -// assignment-client/src -// -// Created by David Kelly on 10/12/13. -// Copyright 2016 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_AvatarAudioTimer_h -#define hifi_AvatarAudioTimer_h - -#include - -class AvatarAudioTimer : public QObject { - Q_OBJECT - -signals: - void avatarTick(); - -public slots: - void start(); - void stop() { _quit = true; } - -private: - bool _quit { false }; -}; - -#endif //hifi_AvatarAudioTimer_h diff --git a/assignment-client/src/assets/AssetServer.cpp b/assignment-client/src/assets/AssetServer.cpp index 3886ff8d92..ca0f222e0c 100644 --- a/assignment-client/src/assets/AssetServer.cpp +++ b/assignment-client/src/assets/AssetServer.cpp @@ -13,24 +13,33 @@ #include "AssetServer.h" #include +#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 +50,157 @@ 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 QStringList BAKEABLE_SCRIPT_EXTENSIONS = {}; +static const QString BAKED_MODEL_SIMPLE_NAME = "asset.fbx"; +static const QString BAKED_TEXTURE_SIMPLE_NAME = "texture.ktx"; +static const QString BAKED_SCRIPT_SIMPLE_NAME = "asset.js"; + +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 if (BAKEABLE_SCRIPT_EXTENSIONS.contains(extension)) { + bakedFilename = BAKED_SCRIPT_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 if (BAKEABLE_SCRIPT_EXTENSIONS.contains(extension)) { + bakedFilename = BAKED_SCRIPT_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 +227,37 @@ 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), + _filesizeLimit(MAX_UPLOAD_SIZE) { + // 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 +280,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 +333,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; } @@ -137,11 +344,11 @@ void AssetServer::completeSetup() { auto maxBandwidthValue = assetServerObject[MAX_BANDWIDTH_OPTION]; auto maxBandwidthFloat = maxBandwidthValue.toDouble(-1); + const int BITS_PER_MEGABITS = 1000 * 1000; if (maxBandwidthFloat > 0.0) { - 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 +357,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 +374,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 +394,28 @@ 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); } + // get file size limit for an asset + static const QString ASSETS_FILESIZE_LIMIT_OPTION = "assets_filesize_limit"; + auto assetsFilesizeLimitJSONValue = assetServerObject[ASSETS_FILESIZE_LIMIT_OPTION]; + auto assetsFilesizeLimit = (uint64_t)assetsFilesizeLimitJSONValue.toInt(MAX_UPLOAD_SIZE); + + if (assetsFilesizeLimit != 0 && assetsFilesizeLimit < MAX_UPLOAD_SIZE) { + _filesizeLimit = assetsFilesizeLimit * BITS_PER_MEGABITS; + } } void AssetServer::cleanupUnmappedFiles() { @@ -206,21 +423,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 +462,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 +489,77 @@ 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; + } else if (BAKEABLE_SCRIPT_EXTENSIONS.contains(assetPathExtension)) { + bakedRootFile = BAKED_SCRIPT_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 +568,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 +594,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 +619,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 +644,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 +690,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 +709,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 +726,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); + auto task = new UploadAssetTask(message, senderNode, _filesDirectory, _filesizeLimit); + _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 +845,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 +895,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 +920,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 +939,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 +960,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 +989,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 +1001,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 +1025,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 +1039,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 +1063,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 +1073,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 +1085,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 +1100,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 +1157,255 @@ 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"; +static const QString BAKED_ASSET_SIMPLE_JS_NAME = "asset.js"; + +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 (relativeFilePath.endsWith(".js", Qt::CaseInsensitive)) { + relativeFilePath = BAKED_ASSET_SIMPLE_JS_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 if (BAKEABLE_SCRIPT_EXTENSIONS.contains(extension)) { + bakedFilename = BAKED_SCRIPT_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..e6393e6a98 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,57 @@ 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 }; + + uint64_t _filesizeLimit; }; #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..6c78d2baf3 --- /dev/null +++ b/assignment-client/src/assets/BakeAssetTask.cpp @@ -0,0 +1,107 @@ +// +// 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 +#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 if (_assetPath.endsWith(".js", Qt::CaseInsensitive)) { + _baker = std::unique_ptr{ + new JSBaker(QUrl("file:///" + _filePath), PathUtils::generateTemporaryDir()) + }; + } 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/assets/UploadAssetTask.cpp b/assignment-client/src/assets/UploadAssetTask.cpp index 7e8e94c34d..5e6d59d032 100644 --- a/assignment-client/src/assets/UploadAssetTask.cpp +++ b/assignment-client/src/assets/UploadAssetTask.cpp @@ -22,10 +22,11 @@ UploadAssetTask::UploadAssetTask(QSharedPointer receivedMessage, SharedNodePointer senderNode, - const QDir& resourcesDir) : + const QDir& resourcesDir, uint64_t filesizeLimit) : _receivedMessage(receivedMessage), _senderNode(senderNode), - _resourcesDir(resourcesDir) + _resourcesDir(resourcesDir), + _filesizeLimit(filesizeLimit) { } @@ -48,7 +49,7 @@ void UploadAssetTask::run() { auto replyPacket = NLPacket::create(PacketType::AssetUploadReply, -1, true); replyPacket->writePrimitive(messageID); - if (fileSize > MAX_UPLOAD_SIZE) { + if (fileSize > _filesizeLimit) { replyPacket->writePrimitive(AssetServerError::AssetTooLarge); } else { QByteArray fileData = buffer.read(fileSize); diff --git a/assignment-client/src/assets/UploadAssetTask.h b/assignment-client/src/assets/UploadAssetTask.h index 700eecbf9a..8c9e0d234a 100644 --- a/assignment-client/src/assets/UploadAssetTask.h +++ b/assignment-client/src/assets/UploadAssetTask.h @@ -26,7 +26,8 @@ class Node; class UploadAssetTask : public QRunnable { public: - UploadAssetTask(QSharedPointer message, QSharedPointer senderNode, const QDir& resourcesDir); + UploadAssetTask(QSharedPointer message, QSharedPointer senderNode, + const QDir& resourcesDir, uint64_t filesizeLimit); void run() override; @@ -34,6 +35,7 @@ private: QSharedPointer _receivedMessage; QSharedPointer _senderNode; QDir _resourcesDir; + uint64_t _filesizeLimit; }; #endif // hifi_UploadAssetTask_h diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 93b9b10eb7..9ed6c7fdbc 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -127,7 +127,7 @@ void AudioMixer::queueReplicatedAudioPacket(QSharedPointer mess // construct a "fake" audio received message from the byte array and packet list information auto audioData = message->getMessage().mid(NUM_BYTES_RFC4122_UUID); - PacketType rewrittenType = REPLICATED_PACKET_MAPPING.key(message->getType()); + PacketType rewrittenType = PacketTypeEnum::getReplicatedPacketMapping().key(message->getType()); if (rewrittenType == PacketType::Unknown) { qDebug() << "Cannot unwrap replicated packet type not present in REPLICATED_PACKET_WRAPPING"; diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index 408ddf038c..9bba9c7f30 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -125,11 +125,11 @@ void AudioMixerClientData::optionallyReplicatePacket(ReceivedMessage& message, c // now make sure it's a packet type that we want to replicate // first check if it is an original type that we should replicate - PacketType mirroredType = REPLICATED_PACKET_MAPPING.value(message.getType()); + PacketType mirroredType = PacketTypeEnum::getReplicatedPacketMapping().value(message.getType()); if (mirroredType == PacketType::Unknown) { // if it wasn't check if it is a replicated type that we should re-replicate - if (REPLICATED_PACKET_MAPPING.key(message.getType()) != PacketType::Unknown) { + if (PacketTypeEnum::getReplicatedPacketMapping().key(message.getType()) != PacketType::Unknown) { mirroredType = message.getType(); } else { qDebug() << "Packet passed to optionallyReplicatePacket was not a replicatable type - returning"; 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/audio/AudioMixerSlavePool.cpp b/assignment-client/src/audio/AudioMixerSlavePool.cpp index 643361ac5d..e28c96e259 100644 --- a/assignment-client/src/audio/AudioMixerSlavePool.cpp +++ b/assignment-client/src/audio/AudioMixerSlavePool.cpp @@ -76,7 +76,7 @@ void AudioMixerSlavePool::processPackets(ConstIter begin, ConstIter end) { void AudioMixerSlavePool::mix(ConstIter begin, ConstIter end, unsigned int frame, float throttlingRatio) { _function = &AudioMixerSlave::mix; - _configure = [&](AudioMixerSlave& slave) { + _configure = [=](AudioMixerSlave& slave) { slave.configureMix(_begin, _end, _frame, _throttlingRatio); }; _frame = frame; @@ -97,7 +97,11 @@ void AudioMixerSlavePool::run(ConstIter begin, ConstIter end) { #else // fill the queue std::for_each(_begin, _end, [&](const SharedNodePointer& node) { +#if defined(__clang__) && defined(Q_OS_LINUX) + _queue.push(node); +#else _queue.emplace(node); +#endif }); { diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index f218daed03..c67e998dd4 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -85,7 +85,22 @@ void AvatarMixer::handleReplicatedPacket(QSharedPointer message auto nodeList = DependencyManager::get(); auto nodeID = QUuid::fromRfc4122(message->peek(NUM_BYTES_RFC4122_UUID)); - auto replicatedNode = addOrUpdateReplicatedNode(nodeID, message->getSenderSockAddr()); + SharedNodePointer replicatedNode; + + if (message->getType() == PacketType::ReplicatedKillAvatar) { + // this is a kill packet, which we should only process if we already have the node in our list + // since it of course does not make sense to add a node just to remove it an instant later + replicatedNode = nodeList->nodeWithUUID(nodeID); + + if (!replicatedNode) { + return; + } + } else { + replicatedNode = addOrUpdateReplicatedNode(nodeID, message->getSenderSockAddr()); + } + + // we better have a node to work with at this point + assert(replicatedNode); if (message->getType() == PacketType::ReplicatedAvatarIdentity) { handleAvatarIdentityPacket(message, replicatedNode); @@ -129,10 +144,10 @@ void AvatarMixer::optionallyReplicatePacket(ReceivedMessage& message, const Node // check if this is a packet type we replicate // which means it must be a packet type present in REPLICATED_PACKET_MAPPING or must be the // replicated version of one of those packet types - PacketType replicatedType = REPLICATED_PACKET_MAPPING.value(message.getType()); + PacketType replicatedType = PacketTypeEnum::getReplicatedPacketMapping().value(message.getType()); if (replicatedType == PacketType::Unknown) { - if (REPLICATED_PACKET_MAPPING.key(message.getType()) != PacketType::Unknown) { + if (PacketTypeEnum::getReplicatedPacketMapping().key(message.getType()) != PacketType::Unknown) { replicatedType = message.getType(); } else { qDebug() << __FUNCTION__ << "called without replicatable packet type - returning"; @@ -285,6 +300,13 @@ void AvatarMixer::start() { // is guaranteed to not be accessed by other thread void AvatarMixer::manageIdentityData(const SharedNodePointer& node) { AvatarMixerClientData* nodeData = reinterpret_cast(node->getLinkedData()); + + // there is no need to manage identity data we haven't received yet + // so bail early if we've never received an identity packet for this avatar + if (!nodeData || !nodeData->getAvatar().hasProcessedFirstIdentity()) { + return; + } + bool sendIdentity = false; if (nodeData && nodeData->getAvatarSessionDisplayNameMustChange()) { AvatarData& avatar = nodeData->getAvatar(); diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index 4d80bc7d17..a4bf8fa253 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -108,9 +108,6 @@ void AvatarMixerClientData::ignoreOther(SharedNodePointer self, SharedNodePointe void AvatarMixerClientData::removeFromRadiusIgnoringSet(SharedNodePointer self, const QUuid& other) { if (isRadiusIgnoring(other)) { _radiusIgnoredOthers.erase(other); - auto exitingSpaceBubblePacket = NLPacket::create(PacketType::ExitingSpaceBubble, NUM_BYTES_RFC4122_UUID); - exitingSpaceBubblePacket->write(other.toRfc4122()); - DependencyManager::get()->sendUnreliablePacket(*exitingSpaceBubblePacket, *self); } } diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 4ff447a95a..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 @@ -320,18 +320,23 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) ++numOtherAvatars; const AvatarMixerClientData* otherNodeData = reinterpret_cast(otherNode->getLinkedData()); + const AvatarData* otherAvatar = otherNodeData->getConstAvatarData(); // If the time that the mixer sent AVATAR DATA about Avatar B to Avatar A is BEFORE OR EQUAL TO // the time that Avatar B flagged an IDENTITY DATA change, send IDENTITY DATA about Avatar B to Avatar A. - if (nodeData->getLastBroadcastTime(otherNode->getUUID()) <= otherNodeData->getIdentityChangeTimestamp()) { + if (otherAvatar->hasProcessedFirstIdentity() + && nodeData->getLastBroadcastTime(otherNode->getUUID()) <= otherNodeData->getIdentityChangeTimestamp()) { identityBytesSent += sendIdentityPacket(otherNodeData, node); + + // remember the last time we sent identity details about this other node to the receiver + nodeData->setLastBroadcastTime(otherNode->getUUID(), usecTimestampNow()); } - const AvatarData* otherAvatar = otherNodeData->getConstAvatarData(); 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); @@ -379,11 +384,11 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) qCWarning(avatars) << "otherAvatar.toByteArray() without facial data resulted in very large buffer:" << bytes.size() << "... reduce to MinimumData"; bytes = otherAvatar->toByteArray(AvatarData::MinimumData, lastEncodeForOther, lastSentJointsForOther, hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther); - } - if (bytes.size() > MAX_ALLOWED_AVATAR_DATA) { - qCWarning(avatars) << "otherAvatar.toByteArray() MinimumData resulted in very large buffer:" << bytes.size() << "... FAIL!!"; - includeThisAvatar = false; + if (bytes.size() > MAX_ALLOWED_AVATAR_DATA) { + qCWarning(avatars) << "otherAvatar.toByteArray() MinimumData resulted in very large buffer:" << bytes.size() << "... FAIL!!"; + includeThisAvatar = false; + } } } @@ -400,9 +405,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // set the last sent sequence number for this sender on the receiver nodeData->setLastBroadcastSequenceNumber(otherNode->getUUID(), otherNodeData->getLastReceivedSequenceNumber()); - - // remember the last time we sent details about this other node to the receiver - nodeData->setLastBroadcastTime(otherNode->getUUID(), start); } } diff --git a/assignment-client/src/avatars/AvatarMixerSlavePool.cpp b/assignment-client/src/avatars/AvatarMixerSlavePool.cpp index cb5ae7735a..25b88686b7 100644 --- a/assignment-client/src/avatars/AvatarMixerSlavePool.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlavePool.cpp @@ -69,7 +69,7 @@ static AvatarMixerSlave slave; void AvatarMixerSlavePool::processIncomingPackets(ConstIter begin, ConstIter end) { _function = &AvatarMixerSlave::processIncomingPackets; - _configure = [&](AvatarMixerSlave& slave) { + _configure = [=](AvatarMixerSlave& slave) { slave.configure(begin, end); }; run(begin, end); @@ -79,7 +79,7 @@ void AvatarMixerSlavePool::broadcastAvatarData(ConstIter begin, ConstIter end, p_high_resolution_clock::time_point lastFrameTimestamp, float maxKbpsPerNode, float throttlingRatio) { _function = &AvatarMixerSlave::broadcastAvatarData; - _configure = [&](AvatarMixerSlave& slave) { + _configure = [=](AvatarMixerSlave& slave) { slave.configureBroadcast(begin, end, lastFrameTimestamp, maxKbpsPerNode, throttlingRatio); }; run(begin, end); @@ -97,7 +97,11 @@ void AvatarMixerSlavePool::run(ConstIter begin, ConstIter end) { #else // fill the queue std::for_each(_begin, _end, [&](const SharedNodePointer& node) { +#if defined(__clang__) && defined(Q_OS_LINUX) + _queue.push(node); +#else _queue.emplace(node); +#endif }); { diff --git a/assignment-client/src/avatars/ScriptableAvatar.cpp b/assignment-client/src/avatars/ScriptableAvatar.cpp index 57456b00c3..5060891284 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.cpp +++ b/assignment-client/src/avatars/ScriptableAvatar.cpp @@ -13,12 +13,13 @@ #include #include +#include #include #include #include "ScriptableAvatar.h" -QByteArray ScriptableAvatar::toByteArrayStateful(AvatarDataDetail dataDetail) { +QByteArray ScriptableAvatar::toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking) { _globalPosition = getPosition(); return AvatarData::toByteArrayStateful(dataDetail); } @@ -34,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; } @@ -49,7 +50,7 @@ void ScriptableAvatar::stopAnimation() { AnimationDetails ScriptableAvatar::getAnimationDetails() { if (QThread::currentThread() != thread()) { AnimationDetails result; - QMetaObject::invokeMethod(this, "getAnimationDetails", Qt::BlockingQueuedConnection, + BLOCKING_INVOKE_METHOD(this, "getAnimationDetails", Q_RETURN_ARG(AnimationDetails, result)); return result; } diff --git a/assignment-client/src/avatars/ScriptableAvatar.h b/assignment-client/src/avatars/ScriptableAvatar.h index 1028912e55..b1039b5ac0 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.h +++ b/assignment-client/src/avatars/ScriptableAvatar.h @@ -28,7 +28,7 @@ public: Q_INVOKABLE AnimationDetails getAnimationDetails(); virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override; - virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail) override; + virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking = false) override; private slots: 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/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index ac686e2e0a..995a5bad27 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -16,6 +16,10 @@ #include #include #include +#include +#include +#include +#include #include "AssignmentParentFinder.h" #include "EntityNodeData.h" @@ -29,15 +33,26 @@ const char* LOCAL_MODELS_PERSIST_FILE = "resources/models.svo"; EntityServer::EntityServer(ReceivedMessage& message) : OctreeServer(message), - _entitySimulation(NULL) + _entitySimulation(NULL), + _dynamicDomainVerificationTimer(this) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); - packetReceiver.registerListenerForTypes({ PacketType::EntityAdd, PacketType::EntityEdit, PacketType::EntityErase, PacketType::EntityPhysics }, - this, "handleEntityPacket"); + packetReceiver.registerListenerForTypes({ PacketType::EntityAdd, + PacketType::EntityEdit, + PacketType::EntityErase, + PacketType::EntityPhysics, + PacketType::ChallengeOwnership, + PacketType::ChallengeOwnershipRequest, + PacketType::ChallengeOwnershipReply }, + this, + "handleEntityPacket"); + + connect(&_dynamicDomainVerificationTimer, &QTimer::timeout, this, &EntityServer::startDynamicDomainVerification); + _dynamicDomainVerificationTimer.setSingleShot(true); } EntityServer::~EntityServer() { @@ -50,6 +65,12 @@ EntityServer::~EntityServer() { tree->removeNewlyCreatedHook(this); } +void EntityServer::aboutToFinish() { + DependencyManager::get()->cleanup(); + + OctreeServer::aboutToFinish(); +} + void EntityServer::handleEntityPacket(QSharedPointer message, SharedNodePointer senderNode) { if (_octreeInboundPacketProcessor) { _octreeInboundPacketProcessor->queueReceivedPacket(message, senderNode); @@ -87,6 +108,9 @@ void EntityServer::beforeRun() { connect(_pruneDeletedEntitiesTimer, SIGNAL(timeout()), this, SLOT(pruneDeletedEntities())); const int PRUNE_DELETED_MODELS_INTERVAL_MSECS = 1 * 1000; // once every second _pruneDeletedEntitiesTimer->start(PRUNE_DELETED_MODELS_INTERVAL_MSECS); + + DomainHandler& domainHandler = DependencyManager::get()->getDomainHandler(); + connect(&domainHandler, &DomainHandler::settingsReceiveFail, this, &EntityServer::domainSettingsRequestFailed); } void EntityServer::entityCreated(const EntityItem& newEntity, const SharedNodePointer& senderNode) { @@ -290,6 +314,18 @@ void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectio tree->setEntityMaxTmpLifetime(EntityTree::DEFAULT_MAX_TMP_ENTITY_LIFETIME); } + int minTime; + if (readOptionInt("dynamicDomainVerificationTimeMin", settingsSectionObject, minTime)) { + _MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = minTime * 1000; + } + + int maxTime; + if (readOptionInt("dynamicDomainVerificationTimeMax", settingsSectionObject, maxTime)) { + _MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = maxTime * 1000; + } + + startDynamicDomainVerification(); + tree->setWantEditLogging(wantEditLogging); tree->setWantTerseEditLogging(wantTerseEditLogging); @@ -404,3 +440,79 @@ QString EntityServer::serverSubclassStats() { return statsString; } + +void EntityServer::domainSettingsRequestFailed() { + auto nodeList = DependencyManager::get(); + qCDebug(entities) << "The EntityServer couldn't get the Domain Settings. Starting dynamic domain verification with default values..."; + + _MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = DEFAULT_MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS; + _MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = DEFAULT_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS; + startDynamicDomainVerification(); +} + +void EntityServer::startDynamicDomainVerification() { + qCDebug(entities) << "Starting Dynamic Domain Verification..."; + + QString thisDomainID = DependencyManager::get()->getDomainId().remove(QRegExp("\\{|\\}")); + + EntityTreePointer tree = std::static_pointer_cast(_tree); + QHash localMap(tree->getEntityCertificateIDMap()); + + QHashIterator i(localMap); + qCDebug(entities) << localMap.size() << "entities in _entityCertificateIDMap"; + while (i.hasNext()) { + i.next(); + + EntityItemPointer entity = tree->findEntityByEntityItemID(i.value()); + + if (entity) { + if (!entity->getProperties().verifyStaticCertificateProperties()) { + qCDebug(entities) << "During Dynamic Domain Verification, a certified entity with ID" << i.value() << "failed" + << "static certificate verification."; + // Delete the entity if it doesn't pass static certificate verification + tree->deleteEntity(i.value(), true); + } else { + + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkRequest networkRequest; + networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL; + requestURL.setPath("/api/v1/commerce/proof_of_purchase_status/location"); + QJsonObject request; + request["certificate_id"] = i.key(); + networkRequest.setUrl(requestURL); + + QNetworkReply* networkReply = NULL; + networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson()); + + connect(networkReply, &QNetworkReply::finished, [=]() { + QJsonObject jsonObject = QJsonDocument::fromJson(networkReply->readAll()).object(); + jsonObject = jsonObject["data"].toObject(); + + if (networkReply->error() == QNetworkReply::NoError) { + if (jsonObject["domain_id"].toString() != thisDomainID) { + qCDebug(entities) << "Entity's cert's domain ID" << jsonObject["domain_id"].toString() + << "doesn't match the current Domain ID" << thisDomainID << "; deleting entity" << i.value(); + tree->deleteEntity(i.value(), true); + } else { + qCDebug(entities) << "Entity passed dynamic domain verification:" << i.value(); + } + } else { + qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << "; deleting entity" << i.value() + << "More info:" << jsonObject; + tree->deleteEntity(i.value(), true); + } + + networkReply->deleteLater(); + }); + } + } else { + qCWarning(entities) << "During DDV, an entity with ID" << i.value() << "was NOT found in the Entity Tree!"; + } + } + + int nextInterval = qrand() % ((_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS + 1) - _MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS) + _MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS; + qCDebug(entities) << "Restarting Dynamic Domain Verification timer for" << nextInterval / 1000 << "seconds"; + _dynamicDomainVerificationTimer.start(nextInterval); +} diff --git a/assignment-client/src/entities/EntityServer.h b/assignment-client/src/entities/EntityServer.h index 40676e79bd..05404b28c8 100644 --- a/assignment-client/src/entities/EntityServer.h +++ b/assignment-client/src/entities/EntityServer.h @@ -59,6 +59,8 @@ public: virtual void trackSend(const QUuid& dataID, quint64 dataLastEdited, const QUuid& sessionID) override; virtual void trackViewerGone(const QUuid& sessionID) override; + virtual void aboutToFinish() override; + public slots: virtual void nodeAdded(SharedNodePointer node) override; virtual void nodeKilled(SharedNodePointer node) override; @@ -71,6 +73,7 @@ protected: private slots: void handleEntityPacket(QSharedPointer message, SharedNodePointer senderNode); + void domainSettingsRequestFailed(); private: SimpleEntitySimulationPointer _entitySimulation; @@ -78,6 +81,13 @@ private: QReadWriteLock _viewerSendingStatsLock; QMap> _viewerSendingStats; + + static const int DEFAULT_MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = 45 * 60 * 1000; // 45m + static const int DEFAULT_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = 60 * 60 * 1000; // 1h + int _MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = DEFAULT_MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS; // 45m + int _MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = DEFAULT_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS; // 1h + QTimer _dynamicDomainVerificationTimer; + void startDynamicDomainVerification(); }; #endif // hifi_EntityServer_h diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index 7febdc67e1..11e4d533fb 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 @@ -100,7 +175,7 @@ bool EntityTreeSendThread::addAncestorsToExtraFlaggedEntities(const QUuid& filte return parentWasNew || ancestorsWereNew; } - // since we didn't have a parent niether of our parents or ancestors could be new additions + // since we didn't have a parent, neither of our parents or ancestors could be new additions return false; } @@ -129,4 +204,297 @@ 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(); + auto entityNode = _node.toStrongRef(); + auto entityNodeData = static_cast(entityNode->getLinkedData()); + 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 + bool entityMatchesFilters = entity->matchesJSONFilters(jsonFilters); + if (entityMatchesFilters || entityNodeData->isEntityFlaggedAsExtra(entity->getID())) { + if (!jsonFilters.isEmpty() && entityMatchesFilters) { + // Record explicitly filtered-in entity so that extra entities can be flagged. + entityNodeData->insertSentFilteredEntity(entity->getID()); + } + 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..a96a18494d 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.h +++ b/assignment-client/src/entities/EntityTreeSendThread.h @@ -12,24 +12,56 @@ #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/OctreeInboundPacketProcessor.cpp b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp index 04409b3b21..bce6e7fe44 100644 --- a/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp +++ b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp @@ -92,7 +92,19 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer // Ask our tree subclass if it can handle the incoming packet... PacketType packetType = message->getType(); - if (_myServer->getOctree()->handlesEditPacketType(packetType)) { + if (packetType == PacketType::ChallengeOwnership) { + _myServer->getOctree()->withWriteLock([&] { + _myServer->getOctree()->processChallengeOwnershipPacket(*message, sendingNode); + }); + } else if (packetType == PacketType::ChallengeOwnershipRequest) { + _myServer->getOctree()->withWriteLock([&] { + _myServer->getOctree()->processChallengeOwnershipRequestPacket(*message, sendingNode); + }); + } else if (packetType == PacketType::ChallengeOwnershipReply) { + _myServer->getOctree()->withWriteLock([&] { + _myServer->getOctree()->processChallengeOwnershipReplyPacket(*message, sendingNode); + }); + } else if (_myServer->getOctree()->handlesEditPacketType(packetType)) { PerformanceWarning warn(debugProcessPacket, "processPacket KNOWN TYPE", debugProcessPacket); _receivedPacketCount++; diff --git a/assignment-client/src/octree/OctreeSendThread.cpp b/assignment-client/src/octree/OctreeSendThread.cpp index 7db06f12c0..3ae653307f 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(); @@ -81,11 +80,14 @@ bool OctreeSendThread::process() { // don't do any send processing until the initial load of the octree is complete... if (_myServer->isInitialLoadComplete()) { if (auto node = _node.lock()) { - _nodeMissingCount = 0; OctreeQueryNode* nodeData = static_cast(node->getLinkedData()); - // Sometimes the node data has not yet been linked, in which case we can't really do anything - if (nodeData && !nodeData->isShuttingDown()) { + // If we don't have the OctreeQueryNode at all + // or it's uninitialized because we haven't received a query yet from the client + // or we don't know where we should send packets for this node + // or we're shutting down + // then we can't send an entity data packet + if (nodeData && nodeData->hasReceivedFirstQuery() && node->getActiveSocket() && !nodeData->isShuttingDown()) { bool viewFrustumChanged = nodeData->updateCurrentViewFrustum(); packetDistributor(node, nodeData, viewFrustumChanged); } @@ -129,8 +131,7 @@ AtomicUIntStat OctreeSendThread::_totalSpecialBytes { 0 }; AtomicUIntStat OctreeSendThread::_totalSpecialPackets { 0 }; -int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode* nodeData, int& trueBytesSent, - int& truePacketsSent, bool dontSuppressDuplicate) { +int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode* nodeData, bool dontSuppressDuplicate) { OctreeServer::didHandlePacketSend(this); // if we're shutting down, then exit early @@ -141,15 +142,14 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode* bool debug = _myServer->wantsDebugSending(); quint64 now = usecTimestampNow(); - bool packetSent = false; // did we send a packet? - int packetsSent = 0; + int numPackets = 0; // Here's where we check to see if this packet is a duplicate of the last packet. If it is, we will silently // obscure the packet and not send it. This allows the callers and upper level logic to not need to know about // this rate control savings. if (!dontSuppressDuplicate && nodeData->shouldSuppressDuplicatePacket()) { nodeData->resetOctreePacket(); // we still need to reset it though! - return packetsSent; // without sending... + return numPackets; // without sending... } // If we've got a stats message ready to send, then see if we can piggyback them together @@ -163,12 +163,15 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode* // copy octree message to back of stats message statsPacket.write(nodeData->getPacket().getData(), nodeData->getPacket().getDataSize()); - // since a stats message is only included on end of scene, don't consider any of these bytes "wasted", since + int numBytes = statsPacket.getDataSize(); + _totalBytes += numBytes; + _totalPackets++; + // since a stats message is only included on end of scene, don't consider any of these bytes "wasted" // there was nothing else to send. int thisWastedBytes = 0; - _totalWastedBytes += thisWastedBytes; - _totalBytes += statsPacket.getDataSize(); - _totalPackets++; + //_totalWastedBytes += 0; + _trueBytesSent += numBytes; + numPackets++; if (debug) { NLPacket& sentPacket = nodeData->getPacket(); @@ -191,18 +194,22 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode* // actually send it OctreeServer::didCallWriteDatagram(this); DependencyManager::get()->sendUnreliablePacket(statsPacket, *node); - packetSent = true; } else { // not enough room in the packet, send two packets + + // first packet OctreeServer::didCallWriteDatagram(this); DependencyManager::get()->sendUnreliablePacket(statsPacket, *node); - // since a stats message is only included on end of scene, don't consider any of these bytes "wasted", since + int numBytes = statsPacket.getDataSize(); + _totalBytes += numBytes; + _totalPackets++; + // since a stats message is only included on end of scene, don't consider any of these bytes "wasted" // there was nothing else to send. int thisWastedBytes = 0; - _totalWastedBytes += thisWastedBytes; - _totalBytes += statsPacket.getDataSize(); - _totalPackets++; + //_totalWastedBytes += 0; + _trueBytesSent += numBytes; + numPackets++; if (debug) { NLPacket& sentPacket = nodeData->getPacket(); @@ -221,19 +228,18 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode* "] wasted bytes:" << thisWastedBytes << " [" << _totalWastedBytes << "]"; } - trueBytesSent += statsPacket.getDataSize(); - truePacketsSent++; - packetsSent++; - + // second packet OctreeServer::didCallWriteDatagram(this); DependencyManager::get()->sendUnreliablePacket(nodeData->getPacket(), *node); - packetSent = true; - int packetSizeWithHeader = nodeData->getPacket().getDataSize(); - thisWastedBytes = udt::MAX_PACKET_SIZE - packetSizeWithHeader; - _totalWastedBytes += thisWastedBytes; - _totalBytes += nodeData->getPacket().getDataSize(); + numBytes = nodeData->getPacket().getDataSize(); + _totalBytes += numBytes; _totalPackets++; + // we count wasted bytes here because we were unable to fit the stats packet + thisWastedBytes = udt::MAX_PACKET_SIZE - numBytes; + _totalWastedBytes += thisWastedBytes; + _trueBytesSent += numBytes; + numPackets++; if (debug) { NLPacket& sentPacket = nodeData->getPacket(); @@ -259,13 +265,14 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode* // just send the octree packet OctreeServer::didCallWriteDatagram(this); DependencyManager::get()->sendUnreliablePacket(nodeData->getPacket(), *node); - packetSent = true; - int packetSizeWithHeader = nodeData->getPacket().getDataSize(); - int thisWastedBytes = udt::MAX_PACKET_SIZE - packetSizeWithHeader; - _totalWastedBytes += thisWastedBytes; - _totalBytes += packetSizeWithHeader; + int numBytes = nodeData->getPacket().getDataSize(); + _totalBytes += numBytes; _totalPackets++; + int thisWastedBytes = udt::MAX_PACKET_SIZE - numBytes; + _totalWastedBytes += thisWastedBytes; + numPackets++; + _trueBytesSent += numBytes; if (debug) { NLPacket& sentPacket = nodeData->getPacket(); @@ -280,28 +287,42 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode* qDebug() << "Sending packet at " << now << " [" << _totalPackets <<"]: sequence: " << sequence << " timestamp: " << timestamp << - " size: " << packetSizeWithHeader << " [" << _totalBytes << + " size: " << numBytes << " [" << _totalBytes << "] wasted bytes:" << thisWastedBytes << " [" << _totalWastedBytes << "]"; } } } // remember to track our stats - if (packetSent) { + if (numPackets > 0) { nodeData->stats.packetSent(nodeData->getPacket().getPayloadSize()); - trueBytesSent += nodeData->getPacket().getPayloadSize(); - truePacketsSent++; - packetsSent++; nodeData->octreePacketSent(); nodeData->resetOctreePacket(); } - return packetsSent; + _truePacketsSent += numPackets; + 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 @@ -309,19 +330,15 @@ 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(); } - // 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 truePacketsSent = 0; - int trueBytesSent = 0; - int packetsSentThisInterval = 0; + _truePacketsSent = 0; + _trueBytesSent = 0; + _packetsSentThisInterval = 0; bool isFullScene = nodeData->shouldForceFullScene(); if (isFullScene) { @@ -334,17 +351,9 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* && ((!viewFrustumChanged && nodeData->getViewFrustumJustStoppedChanging()) || nodeData->hasLodChanged())); } - bool somethingToSend = true; // assume we have something - - // If our packet already has content in it, then we must use the color choice of the waiting packet. - // If we're starting a fresh packet, then... - // If we're moving, and the client asked for low res, then we force monochrome, otherwise, use - // the clients requested color state. - - // If we have a packet waiting, and our desired want color, doesn't match the current waiting packets color - // then let's just send that waiting packet. if (nodeData->isPacketWaiting()) { - packetsSentThisInterval += handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent); + // send the waiting packet + _packetsSentThisInterval += handlePacketSend(node, nodeData); } else { nodeData->resetOctreePacket(); } @@ -355,7 +364,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) { @@ -375,13 +384,7 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* //unsigned long encodeTime = nodeData->stats.getTotalEncodeTime(); //unsigned long elapsedTime = nodeData->stats.getElapsedTime(); - int packetsJustSent = handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent, isFullScene); - packetsSentThisInterval += packetsJustSent; - - // If we're starting a full scene, then definitely we want to empty the elementBag - if (isFullScene) { - nodeData->elementBag.deleteAll(); - } + _packetsSentThisInterval += handlePacketSend(node, nodeData, isFullScene); // TODO: add these to stats page //::startSceneSleepTime = _usleepTime; @@ -391,198 +394,49 @@ 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()) { - int bytesWritten = 0; + if (shouldTraverseAndSend(nodeData)) { quint64 start = usecTimestampNow(); - // TODO: add these to stats page - //quint64 startCompressTimeMsecs = OctreePacketData::getCompressContentTime() / 1000; - //quint64 startCompressCalls = OctreePacketData::getCompressContentCalls(); - - int extraPackingAttempts = 0; - bool completedScene = false; - - 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()) { - - quint64 lockWaitStart = usecTimestampNow(); - _myServer->getOctree()->withReadLock([&]{ - quint64 lockWaitEnd = usecTimestampNow(); - lockWaitElapsedUsec = (float)(lockWaitEnd - lockWaitStart); - quint64 encodeStart = usecTimestampNow(); - - 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, node](const QUuid& dataID, quint64 dataEdited) { - _myServer->trackSend(dataID, dataEdited, node->getUUID()); - }; - - // 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(); - - bytesWritten = _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 { - // If the bag was empty then we didn't even attempt to encode, and so we know the bytesWritten were 0 - bytesWritten = 0; - somethingToSend = false; // this will cause us to drop out of the loop... - } - - // If the last node didn't fit, but we're in compressed mode, then we actually want to see if we can fit a - // little bit more in this packet. To do this we write into the packet, but don't send it yet, we'll - // keep attempting to write in compressed mode to add more compressed segments - - // We only consider sending anything if there is something in the _packetData to send... But - // if bytesWritten == 0 it means either the subTree couldn't fit or we had an empty bag... Both cases - // mean we should send the previous packet contents and reset it. - if (completedScene || lastNodeDidntFit) { - - if (_packetData.hasContent()) { - quint64 compressAndWriteStart = usecTimestampNow(); - - // if for some reason the finalized size is greater than our available size, then probably the "compressed" - // form actually inflated beyond our padding, and in this case we will send the current packet, then - // write to out new packet... - unsigned int writtenSize = _packetData.getFinalizedSize() + sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE); - - if (writtenSize > nodeData->getAvailable()) { - packetsSentThisInterval += handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent); - } - - nodeData->writeToPacket(_packetData.getFinalizedData(), _packetData.getFinalizedSize()); - quint64 compressAndWriteEnd = usecTimestampNow(); - compressAndWriteElapsedUsec = (float)(compressAndWriteEnd - compressAndWriteStart); - } - - // If we're not running compressed, then we know we can just send now. Or if we're running compressed, but - // the packet doesn't have enough space to bother attempting to pack more... - bool sendNow = true; - - if (!completedScene && (nodeData->getAvailable() >= MINIMUM_ATTEMPT_MORE_PACKING && - extraPackingAttempts <= REASONABLE_NUMBER_OF_PACKING_ATTEMPTS)) { - sendNow = false; // try to pack more - } - - int targetSize = MAX_OCTREE_PACKET_DATA_SIZE; - if (sendNow) { - quint64 packetSendingStart = usecTimestampNow(); - packetsSentThisInterval += handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent); - quint64 packetSendingEnd = usecTimestampNow(); - packetSendingElapsedUsec = (float)(packetSendingEnd - packetSendingStart); - - targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE); - extraPackingAttempts = 0; - } else { - // If we're in compressed mode, then we want to see if we have room for more in this wire packet. - // but we've finalized the _packetData, so we want to start a new section, we will do that by - // resetting the packet settings with the max uncompressed size of our current available space - // in the wire packet. We also include room for our section header, and a little bit of padding - // to account for the fact that whenc compressing small amounts of data, we sometimes end up with - // a larger compressed size then uncompressed size - targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE) - COMPRESS_PADDING; - } - _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); - } - - if (somethingToSend && _myServer->wantsVerboseDebug()) { - qCDebug(octree) << "Hit PPS Limit, packetsSentThisInterval =" << packetsSentThisInterval - << " maxPacketsPerInterval = " << maxPacketsPerInterval - << " clientMaxPacketsPerInterval = " << clientMaxPacketsPerInterval; - } + traverseTreeAndSendContents(node, nodeData, viewFrustumChanged, isFullScene); // Here's where we can/should allow the server to send other data... // send the environment packet // TODO: should we turn this into a while loop to better handle sending multiple special packets if (_myServer->hasSpecialPacketsToSend(node) && !nodeData->isShuttingDown()) { int specialPacketsSent = 0; - trueBytesSent += _myServer->sendSpecialPackets(node, nodeData, specialPacketsSent); + int specialBytesSent = _myServer->sendSpecialPackets(node, nodeData, specialPacketsSent); nodeData->resetOctreePacket(); // because nodeData's _sequenceNumber has changed - truePacketsSent += specialPacketsSent; - packetsSentThisInterval += specialPacketsSent; + _truePacketsSent += specialPacketsSent; + _trueBytesSent += specialBytesSent; + _packetsSentThisInterval += specialPacketsSent; _totalPackets += specialPacketsSent; - _totalBytes += trueBytesSent; + _totalBytes += specialBytesSent; _totalSpecialPackets += specialPacketsSent; - _totalSpecialBytes += trueBytesSent; + _totalSpecialBytes += specialBytesSent; } + // 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()); + // Re-send packets that were nacked by the client - while (nodeData->hasNextNackedPacket() && packetsSentThisInterval < maxPacketsPerInterval) { + while (nodeData->hasNextNackedPacket() && _packetsSentThisInterval < maxPacketsPerInterval) { const NLPacket* packet = nodeData->getNextNackedPacket(); if (packet) { DependencyManager::get()->sendUnreliablePacket(*packet, *node); - truePacketsSent++; - packetsSentThisInterval++; + int numBytes = packet->getDataSize(); + _truePacketsSent++; + _trueBytesSent += numBytes; + _packetsSentThisInterval++; - _totalBytes += packet->getDataSize(); _totalPackets++; + _totalBytes += numBytes; _totalWastedBytes += udt::MAX_PACKET_SIZE - packet->getDataSize(); } } @@ -591,32 +445,145 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* int elapsedmsec = (end - start) / USECS_PER_MSEC; OctreeServer::trackLoopTime(elapsedmsec); - // TODO: add these to stats page - //quint64 endCompressCalls = OctreePacketData::getCompressContentCalls(); - //int elapsedCompressCalls = endCompressCalls - startCompressCalls; - //quint64 endCompressTimeMsecs = OctreePacketData::getCompressContentTime() / 1000; - //int elapsedCompressTimeMsecs = endCompressTimeMsecs - startCompressTimeMsecs; - // 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); // If this was a full scene then make sure we really send out a stats packet at this point so that // the clients will know the scene is stable if (isFullScene) { - int thisTrueBytesSent = 0; - int thisTruePacketsSent = 0; nodeData->stats.sceneCompleted(); - int packetsJustSent = handlePacketSend(node, nodeData, thisTrueBytesSent, thisTruePacketsSent, true); - _totalBytes += thisTrueBytesSent; - _totalPackets += thisTruePacketsSent; - truePacketsSent += packetsJustSent; + handlePacketSend(node, nodeData, true); } } } // end if bag wasn't empty, and so we sent stuff... - return truePacketsSent; + 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; + + // 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 compressAndWriteElapsedUsec = OctreeServer::SKIP_TIME; + float packetSendingElapsedUsec = OctreeServer::SKIP_TIME; + + quint64 startInside = usecTimestampNow(); + + bool lastNodeDidntFit = false; // assume each node fits + params.stopReason = EncodeBitstreamParams::UNKNOWN; // reset params.stopReason before traversal + + somethingToSend = traverseTreeAndBuildNextPacketPayload(params, nodeData->getJSONParameters()); + + 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? + if (_packetData.hasContent()) { + // yes, more data to send + quint64 compressAndWriteStart = usecTimestampNow(); + unsigned int additionalSize = _packetData.getFinalizedSize() + sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE); + if (additionalSize > nodeData->getAvailable()) { + // no room --> flush what we've got + _packetsSentThisInterval += handlePacketSend(node, nodeData); + } + + // either there is room, or we've flushed and reset nodeData's data buffer + // so we can transfer whatever is in _packetData to nodeData + nodeData->writeToPacket(_packetData.getFinalizedData(), _packetData.getFinalizedSize()); + compressAndWriteElapsedUsec = (float)(usecTimestampNow()- compressAndWriteStart); + } + + bool sendNow = completedScene || + nodeData->getAvailable() < MINIMUM_ATTEMPT_MORE_PACKING || + extraPackingAttempts > REASONABLE_NUMBER_OF_PACKING_ATTEMPTS; + + int targetSize = MAX_OCTREE_PACKET_DATA_SIZE; + if (sendNow) { + quint64 packetSendingStart = usecTimestampNow(); + _packetsSentThisInterval += handlePacketSend(node, nodeData); + packetSendingElapsedUsec = (float)(usecTimestampNow() - packetSendingStart); + + targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE); + extraPackingAttempts = 0; + } else { + // We want to see if we have room for more in this wire packet but we've copied the _packetData, + // so we want to start a new section. We will do that by resetting the packet settings with the max + // size of our current available space in the wire packet plus room for our section header and a + // little bit of padding. + targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE) - COMPRESS_PADDING; + } + _packetData.changeSettings(true, targetSize); // will do reset - NOTE: Always compressed + } + OctreeServer::trackCompressAndWriteTime(compressAndWriteElapsedUsec); + OctreeServer::trackPacketSendingTime(packetSendingElapsedUsec); + OctreeServer::trackInsideTime((float)(usecTimestampNow() - startInside)); + } + + if (somethingToSend && _myServer->wantsVerboseDebug()) { + qCDebug(octree) << "Hit PPS Limit, packetsSentThisInterval =" << _packetsSentThisInterval + << " maxPacketsPerInterval = " << maxPacketsPerInterval + << " clientMaxPacketsPerInterval = " << clientMaxPacketsPerInterval; + } } diff --git a/assignment-client/src/octree/OctreeSendThread.h b/assignment-client/src/octree/OctreeSendThread.h index 06c9b5f1d6..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; @@ -34,7 +35,7 @@ public: void setIsShuttingDown(); bool isShuttingDown() { return _isShuttingDown; } - + QUuid getNodeUuid() const { return _nodeUuid; } static AtomicUIntStat _totalBytes; @@ -51,22 +52,30 @@ 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 bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters); - OctreeServer* _myServer { nullptr }; + OctreePacketData _packetData; QWeakPointer _node; + OctreeServer* _myServer { nullptr }; private: - int handlePacketSend(SharedNodePointer node, OctreeQueryNode* nodeData, int& trueBytesSent, int& truePacketsSent, bool dontSuppressDuplicate = false); + /// 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 _nodeMissingCount { 0 }; + int _truePacketsSent { 0 }; // available for debug stats + int _trueBytesSent { 0 }; // available for debug stats + int _packetsSentThisInterval { 0 }; // used for bandwidth throttle condition bool _isShuttingDown { false }; }; diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index af5f2c904e..c535c48dda 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -22,7 +22,6 @@ #include #include #include -#include #include #include @@ -35,7 +34,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 +59,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 +107,8 @@ void OctreeServer::resetSendingStats() { _shortTreeWait = 0; _noTreeWait = 0; + _averageTreeTraverseTime.reset(); + _averageNodeWaitTime.reset(); _averageCompressAndWriteTime.reset(); @@ -136,18 +139,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 +159,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 +179,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 +208,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 +525,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 +890,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 +912,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 +946,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 +956,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 +1092,7 @@ void OctreeServer::readConfiguration() { if (getPayload().size() > 0) { parsePayload(); } - + const QJsonObject& settingsObject = DependencyManager::get()->getDomainHandler().getSettingsObject(); QString settingsKey = getMyDomainSettingsKey(); @@ -1178,9 +1219,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 +1232,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 +1243,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 +1358,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 +1387,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 +1398,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 +1444,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 +1570,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 +1578,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 +1597,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 +1622,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 489478ff9a..c8067ce81f 100644 --- a/assignment-client/src/scripts/EntityScriptServer.cpp +++ b/assignment-client/src/scripts/EntityScriptServer.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -29,6 +30,8 @@ #include #include +#include // for EntityScriptServerServices + #include "EntityScriptServerLogging.h" #include "../entities/AssignmentParentFinder.h" @@ -67,6 +70,12 @@ EntityScriptServer::EntityScriptServer(ReceivedMessage& message) : ThreadedAssig DependencyManager::set(); DependencyManager::set(ScriptEngine::ENTITY_SERVER_SCRIPT); + DependencyManager::set(); + + + // Needed to ensure the creation of the DebugDraw instance on the main thread + DebugDraw::getInstance(); + auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); packetReceiver.registerListenerForTypes({ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase }, this, "handleOctreePacket"); @@ -81,6 +90,7 @@ EntityScriptServer::EntityScriptServer(ReceivedMessage& message) : ThreadedAssig packetReceiver.registerListener(PacketType::ReloadEntityServerScript, this, "handleReloadEntityServerScriptPacket"); packetReceiver.registerListener(PacketType::EntityScriptGetStatus, this, "handleEntityScriptGetStatusPacket"); packetReceiver.registerListener(PacketType::EntityServerScriptLog, this, "handleEntityServerScriptLogPacket"); + packetReceiver.registerListener(PacketType::EntityScriptCallMethod, this, "handleEntityScriptCallMethodPacket"); static const int LOG_INTERVAL = MSECS_PER_SECOND / 10; auto timer = new QTimer(this); @@ -98,7 +108,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) { @@ -112,7 +122,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)); @@ -227,6 +237,27 @@ void EntityScriptServer::pushLogs() { } } +void EntityScriptServer::handleEntityScriptCallMethodPacket(QSharedPointer receivedMessage, SharedNodePointer senderNode) { + + if (_entitiesScriptEngine && _entityViewer.getTree() && !_shuttingDown) { + auto entityID = QUuid::fromRfc4122(receivedMessage->read(NUM_BYTES_RFC4122_UUID)); + + auto method = receivedMessage->readString(); + + quint16 paramCount; + receivedMessage->readPrimitive(¶mCount); + + QStringList params; + for (int param = 0; param < paramCount; param++) { + auto paramString = receivedMessage->readString(); + params << paramString; + } + + _entitiesScriptEngine->callEntityScriptMethod(entityID, method, params, senderNode->getUUID()); + } +} + + void EntityScriptServer::run() { // make sure we request our script once the agent connects to the domain auto nodeList = DependencyManager::get(); @@ -411,8 +442,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); @@ -433,11 +463,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); } @@ -555,6 +588,7 @@ void EntityScriptServer::aboutToFinish() { // cleanup the AudioInjectorManager (and any still running injectors) DependencyManager::destroy(); DependencyManager::destroy(); + DependencyManager::destroy(); // cleanup codec & encoder if (_codec && _encoder) { diff --git a/assignment-client/src/scripts/EntityScriptServer.h b/assignment-client/src/scripts/EntityScriptServer.h index 84454375e5..f9c5e921f0 100644 --- a/assignment-client/src/scripts/EntityScriptServer.h +++ b/assignment-client/src/scripts/EntityScriptServer.h @@ -54,6 +54,9 @@ private slots: void pushLogs(); + void handleEntityScriptCallMethodPacket(QSharedPointer message, SharedNodePointer senderNode); + + private: void negotiateAudioFormat(); void selectAudioFormat(const QString& selectedCodecName); @@ -72,7 +75,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 " -o " ) -endif() - -# STL -if( EXISTS "${__libstl}" OR EXISTS "${__libsupcxx}" ) - if( EXISTS "${__libstl}" ) - set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} \"${__libstl}\"" ) - set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} \"${__libstl}\"" ) - set( CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} \"${__libstl}\"" ) - endif() - if( EXISTS "${__libsupcxx}" ) - set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} \"${__libsupcxx}\"" ) - set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} \"${__libsupcxx}\"" ) - set( CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} \"${__libsupcxx}\"" ) - # C objects: - set( CMAKE_C_CREATE_SHARED_LIBRARY " -o " ) - set( CMAKE_C_CREATE_SHARED_MODULE " -o " ) - set( CMAKE_C_LINK_EXECUTABLE " -o " ) - set( CMAKE_C_CREATE_SHARED_LIBRARY "${CMAKE_C_CREATE_SHARED_LIBRARY} \"${__libsupcxx}\"" ) - set( CMAKE_C_CREATE_SHARED_MODULE "${CMAKE_C_CREATE_SHARED_MODULE} \"${__libsupcxx}\"" ) - set( CMAKE_C_LINK_EXECUTABLE "${CMAKE_C_LINK_EXECUTABLE} \"${__libsupcxx}\"" ) - endif() - if( ANDROID_STL MATCHES "gnustl" ) - if( NOT EXISTS "${ANDROID_LIBM_PATH}" ) - set( ANDROID_LIBM_PATH -lm ) - endif() - set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} ${ANDROID_LIBM_PATH}" ) - set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} ${ANDROID_LIBM_PATH}" ) - set( CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} ${ANDROID_LIBM_PATH}" ) - endif() -endif() - -# variables controlling optional build flags -if( ANDROID_NDK_RELEASE_NUM LESS 7000 ) # before r7 - # libGLESv2.so in NDK's prior to r7 refers to missing external symbols. - # So this flag option is required for all projects using OpenGL from native. - __INIT_VARIABLE( ANDROID_SO_UNDEFINED VALUES ON ) -else() - __INIT_VARIABLE( ANDROID_SO_UNDEFINED VALUES OFF ) -endif() -__INIT_VARIABLE( ANDROID_NO_UNDEFINED VALUES ON ) -__INIT_VARIABLE( ANDROID_FUNCTION_LEVEL_LINKING VALUES ON ) -__INIT_VARIABLE( ANDROID_GOLD_LINKER VALUES ON ) -__INIT_VARIABLE( ANDROID_NOEXECSTACK VALUES ON ) -__INIT_VARIABLE( ANDROID_RELRO VALUES ON ) - -set( ANDROID_NO_UNDEFINED ${ANDROID_NO_UNDEFINED} CACHE BOOL "Show all undefined symbols as linker errors" ) -set( ANDROID_SO_UNDEFINED ${ANDROID_SO_UNDEFINED} CACHE BOOL "Allows or disallows undefined symbols in shared libraries" ) -set( ANDROID_FUNCTION_LEVEL_LINKING ${ANDROID_FUNCTION_LEVEL_LINKING} CACHE BOOL "Put each function in separate section and enable garbage collection of unused input sections at link time" ) -set( ANDROID_GOLD_LINKER ${ANDROID_GOLD_LINKER} CACHE BOOL "Enables gold linker" ) -set( ANDROID_NOEXECSTACK ${ANDROID_NOEXECSTACK} CACHE BOOL "Allows or disallows undefined symbols in shared libraries" ) -set( ANDROID_RELRO ${ANDROID_RELRO} CACHE BOOL "Enables RELRO - a memory corruption mitigation technique" ) -mark_as_advanced( ANDROID_NO_UNDEFINED ANDROID_SO_UNDEFINED ANDROID_FUNCTION_LEVEL_LINKING ANDROID_GOLD_LINKER ANDROID_NOEXECSTACK ANDROID_RELRO ) - -# linker flags -set( ANDROID_LINKER_FLAGS "" ) - -if( ARMEABI_V7A ) - # this is *required* to use the following linker flags that routes around - # a CPU bug in some Cortex-A8 implementations: - set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,--fix-cortex-a8" ) -endif() - -if( ANDROID_NO_UNDEFINED ) - if( MIPS ) - # there is some sysroot-related problem in mips linker... - if( NOT ANDROID_SYSROOT MATCHES "[ ;\"]" ) - set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,--no-undefined -Wl,-rpath-link,${ANDROID_SYSROOT}/usr/lib" ) - endif() - else() - set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,--no-undefined" ) - endif() -endif() - -if( ANDROID_SO_UNDEFINED ) - set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,-allow-shlib-undefined" ) -endif() - -if( ANDROID_FUNCTION_LEVEL_LINKING ) - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fdata-sections -ffunction-sections" ) - set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,--gc-sections" ) -endif() - -if( ANDROID_COMPILER_VERSION VERSION_EQUAL "4.6" ) - if( ANDROID_GOLD_LINKER AND (CMAKE_HOST_UNIX OR ANDROID_NDK_RELEASE_NUM GREATER 8002) AND (ARMEABI OR ARMEABI_V7A OR X86) ) - set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -fuse-ld=gold" ) - elseif( ANDROID_NDK_RELEASE_NUM GREATER 8002 ) # after r8b - set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -fuse-ld=bfd" ) - elseif( ANDROID_NDK_RELEASE STREQUAL "r8b" AND ARMEABI AND NOT _CMAKE_IN_TRY_COMPILE ) - message( WARNING "The default bfd linker from arm GCC 4.6 toolchain can fail with 'unresolvable R_ARM_THM_CALL relocation' error message. See https://code.google.com/p/android/issues/detail?id=35342 - On Linux and OS X host platform you can workaround this problem using gold linker (default). - Rerun cmake with -DANDROID_GOLD_LINKER=ON option in case of problems. -" ) - endif() -endif() # version 4.6 - -if( ANDROID_NOEXECSTACK ) - if( ANDROID_COMPILER_IS_CLANG ) - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -Xclang -mnoexecstack" ) - else() - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -Wa,--noexecstack" ) - endif() - set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,-z,noexecstack" ) -endif() - -if( ANDROID_RELRO ) - set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,-z,relro -Wl,-z,now" ) -endif() - -if( ANDROID_COMPILER_IS_CLANG ) - set( ANDROID_CXX_FLAGS "-target ${ANDROID_LLVM_TRIPLE} -Qunused-arguments ${ANDROID_CXX_FLAGS}" ) - if( BUILD_WITH_ANDROID_NDK ) - set( ANDROID_CXX_FLAGS "-gcc-toolchain ${ANDROID_TOOLCHAIN_ROOT} ${ANDROID_CXX_FLAGS}" ) - endif() -endif() - -# cache flags -set( CMAKE_CXX_FLAGS "" CACHE STRING "c++ flags" ) -set( CMAKE_C_FLAGS "" CACHE STRING "c flags" ) -set( CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG" CACHE STRING "c++ Release flags" ) -set( CMAKE_C_FLAGS_RELEASE "-O3 -DNDEBUG" CACHE STRING "c Release flags" ) -set( CMAKE_CXX_FLAGS_DEBUG "-O0 -g -DDEBUG -D_DEBUG" CACHE STRING "c++ Debug flags" ) -set( CMAKE_C_FLAGS_DEBUG "-O0 -g -DDEBUG -D_DEBUG" CACHE STRING "c Debug flags" ) -set( CMAKE_SHARED_LINKER_FLAGS "" CACHE STRING "shared linker flags" ) -set( CMAKE_MODULE_LINKER_FLAGS "" CACHE STRING "module linker flags" ) -set( CMAKE_EXE_LINKER_FLAGS "-Wl,-z,nocopyreloc" CACHE STRING "executable linker flags" ) - -# put flags to cache (for debug purpose only) -set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS}" CACHE INTERNAL "Android specific c/c++ flags" ) -set( ANDROID_CXX_FLAGS_RELEASE "${ANDROID_CXX_FLAGS_RELEASE}" CACHE INTERNAL "Android specific c/c++ Release flags" ) -set( ANDROID_CXX_FLAGS_DEBUG "${ANDROID_CXX_FLAGS_DEBUG}" CACHE INTERNAL "Android specific c/c++ Debug flags" ) -set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS}" CACHE INTERNAL "Android specific c/c++ linker flags" ) - -# finish flags -set( CMAKE_CXX_FLAGS "${ANDROID_CXX_FLAGS} ${CMAKE_CXX_FLAGS}" ) -set( CMAKE_C_FLAGS "${ANDROID_CXX_FLAGS} ${CMAKE_C_FLAGS}" ) -set( CMAKE_CXX_FLAGS_RELEASE "${ANDROID_CXX_FLAGS_RELEASE} ${CMAKE_CXX_FLAGS_RELEASE}" ) -set( CMAKE_C_FLAGS_RELEASE "${ANDROID_CXX_FLAGS_RELEASE} ${CMAKE_C_FLAGS_RELEASE}" ) -set( CMAKE_CXX_FLAGS_DEBUG "${ANDROID_CXX_FLAGS_DEBUG} ${CMAKE_CXX_FLAGS_DEBUG}" ) -set( CMAKE_C_FLAGS_DEBUG "${ANDROID_CXX_FLAGS_DEBUG} ${CMAKE_C_FLAGS_DEBUG}" ) -set( CMAKE_SHARED_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} ${CMAKE_SHARED_LINKER_FLAGS}" ) -set( CMAKE_MODULE_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} ${CMAKE_MODULE_LINKER_FLAGS}" ) -set( CMAKE_EXE_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} ${CMAKE_EXE_LINKER_FLAGS}" ) - -if( MIPS AND BUILD_WITH_ANDROID_NDK AND ANDROID_NDK_RELEASE STREQUAL "r8" ) - set( CMAKE_SHARED_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_GCC_TOOLCHAIN_NAME}/mipself.xsc ${CMAKE_SHARED_LINKER_FLAGS}" ) - set( CMAKE_MODULE_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_GCC_TOOLCHAIN_NAME}/mipself.xsc ${CMAKE_MODULE_LINKER_FLAGS}" ) - set( CMAKE_EXE_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_GCC_TOOLCHAIN_NAME}/mipself.x ${CMAKE_EXE_LINKER_FLAGS}" ) -endif() - -# pie/pic -if( NOT (ANDROID_NATIVE_API_LEVEL LESS 16) AND (NOT DEFINED ANDROID_APP_PIE OR ANDROID_APP_PIE) AND (CMAKE_VERSION VERSION_GREATER 2.8.8) ) - set( CMAKE_POSITION_INDEPENDENT_CODE TRUE ) - set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fPIE -pie") -else() - set( CMAKE_POSITION_INDEPENDENT_CODE FALSE ) - set( CMAKE_CXX_FLAGS "-fpic ${CMAKE_CXX_FLAGS}" ) - set( CMAKE_C_FLAGS "-fpic ${CMAKE_C_FLAGS}" ) -endif() - -# configure rtti -if( DEFINED ANDROID_RTTI AND ANDROID_STL_FORCE_FEATURES ) - if( ANDROID_RTTI ) - set( CMAKE_CXX_FLAGS "-frtti ${CMAKE_CXX_FLAGS}" ) - else() - set( CMAKE_CXX_FLAGS "-fno-rtti ${CMAKE_CXX_FLAGS}" ) - endif() -endif() - -# configure exceptios -if( DEFINED ANDROID_EXCEPTIONS AND ANDROID_STL_FORCE_FEATURES ) - if( ANDROID_EXCEPTIONS ) - set( CMAKE_CXX_FLAGS "-fexceptions ${CMAKE_CXX_FLAGS}" ) - set( CMAKE_C_FLAGS "-fexceptions ${CMAKE_C_FLAGS}" ) - else() - set( CMAKE_CXX_FLAGS "-fno-exceptions ${CMAKE_CXX_FLAGS}" ) - set( CMAKE_C_FLAGS "-fno-exceptions ${CMAKE_C_FLAGS}" ) - endif() -endif() - -# global includes and link directories -include_directories( SYSTEM "${ANDROID_SYSROOT}/usr/include" ${ANDROID_STL_INCLUDE_DIRS} ) -get_filename_component(__android_install_path "${CMAKE_INSTALL_PREFIX}/libs/${ANDROID_NDK_ABI_NAME}" ABSOLUTE) # avoid CMP0015 policy warning -link_directories( "${__android_install_path}" ) - -# detect if need link crtbegin_so.o explicitly -if( NOT DEFINED ANDROID_EXPLICIT_CRT_LINK ) - set( __cmd "${CMAKE_CXX_CREATE_SHARED_LIBRARY}" ) - string( REPLACE "" "${CMAKE_CXX_COMPILER} ${CMAKE_CXX_COMPILER_ARG1}" __cmd "${__cmd}" ) - string( REPLACE "" "${CMAKE_C_COMPILER} ${CMAKE_C_COMPILER_ARG1}" __cmd "${__cmd}" ) - string( REPLACE "" "${CMAKE_CXX_FLAGS}" __cmd "${__cmd}" ) - string( REPLACE "" "" __cmd "${__cmd}" ) - string( REPLACE "" "${CMAKE_SHARED_LINKER_FLAGS}" __cmd "${__cmd}" ) - string( REPLACE "" "-shared" __cmd "${__cmd}" ) - string( REPLACE "" "" __cmd "${__cmd}" ) - string( REPLACE "" "" __cmd "${__cmd}" ) - string( REPLACE "" "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/toolchain_crtlink_test.so" __cmd "${__cmd}" ) - string( REPLACE "" "\"${ANDROID_SYSROOT}/usr/lib/crtbegin_so.o\"" __cmd "${__cmd}" ) - string( REPLACE "" "" __cmd "${__cmd}" ) - separate_arguments( __cmd ) - foreach( __var ANDROID_NDK ANDROID_NDK_TOOLCHAINS_PATH ANDROID_STANDALONE_TOOLCHAIN ) - if( ${__var} ) - set( __tmp "${${__var}}" ) - separate_arguments( __tmp ) - string( REPLACE "${__tmp}" "${${__var}}" __cmd "${__cmd}") - endif() - endforeach() - string( REPLACE "'" "" __cmd "${__cmd}" ) - string( REPLACE "\"" "" __cmd "${__cmd}" ) - execute_process( COMMAND ${__cmd} RESULT_VARIABLE __cmd_result OUTPUT_QUIET ERROR_QUIET ) - if( __cmd_result EQUAL 0 ) - set( ANDROID_EXPLICIT_CRT_LINK ON ) - else() - set( ANDROID_EXPLICIT_CRT_LINK OFF ) - endif() -endif() - -if( ANDROID_EXPLICIT_CRT_LINK ) - set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} \"${ANDROID_SYSROOT}/usr/lib/crtbegin_so.o\"" ) - set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} \"${ANDROID_SYSROOT}/usr/lib/crtbegin_so.o\"" ) -endif() - -# setup output directories -set( CMAKE_INSTALL_PREFIX "${ANDROID_TOOLCHAIN_ROOT}/user" CACHE STRING "path for installing" ) - -if( DEFINED LIBRARY_OUTPUT_PATH_ROOT - OR EXISTS "${CMAKE_SOURCE_DIR}/AndroidManifest.xml" - OR (EXISTS "${CMAKE_SOURCE_DIR}/../AndroidManifest.xml" AND EXISTS "${CMAKE_SOURCE_DIR}/../jni/") ) - set( LIBRARY_OUTPUT_PATH_ROOT ${CMAKE_SOURCE_DIR} CACHE PATH "Root for binaries output, set this to change where Android libs are installed to" ) - if( NOT _CMAKE_IN_TRY_COMPILE ) - if( EXISTS "${CMAKE_SOURCE_DIR}/jni/CMakeLists.txt" ) - set( EXECUTABLE_OUTPUT_PATH "${LIBRARY_OUTPUT_PATH_ROOT}/bin/${ANDROID_NDK_ABI_NAME}" CACHE PATH "Output directory for applications" ) - else() - set( EXECUTABLE_OUTPUT_PATH "${LIBRARY_OUTPUT_PATH_ROOT}/bin" CACHE PATH "Output directory for applications" ) - endif() - set( LIBRARY_OUTPUT_PATH "${LIBRARY_OUTPUT_PATH_ROOT}/libs/${ANDROID_NDK_ABI_NAME}" CACHE PATH "Output directory for Android libs" ) - endif() -endif() - -# copy shaed stl library to build directory -if( NOT _CMAKE_IN_TRY_COMPILE AND __libstl MATCHES "[.]so$" AND DEFINED LIBRARY_OUTPUT_PATH ) - get_filename_component( __libstlname "${__libstl}" NAME ) - execute_process( COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${__libstl}" "${LIBRARY_OUTPUT_PATH}/${__libstlname}" RESULT_VARIABLE __fileCopyProcess ) - if( NOT __fileCopyProcess EQUAL 0 OR NOT EXISTS "${LIBRARY_OUTPUT_PATH}/${__libstlname}") - message( SEND_ERROR "Failed copying of ${__libstl} to the ${LIBRARY_OUTPUT_PATH}/${__libstlname}" ) - endif() - unset( __fileCopyProcess ) - unset( __libstlname ) -endif() - - -# set these global flags for cmake client scripts to change behavior -set( ANDROID True ) -set( BUILD_ANDROID True ) - -# where is the target environment -set( CMAKE_FIND_ROOT_PATH "${ANDROID_TOOLCHAIN_ROOT}/bin" "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}" "${ANDROID_SYSROOT}" "${CMAKE_INSTALL_PREFIX}" "${CMAKE_INSTALL_PREFIX}/share" ) - -# only search for libraries and includes in the ndk toolchain -set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY ) -set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY ) -set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY ) - - -# macro to find packages on the host OS -macro( find_host_package ) - set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER ) - set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER ) - set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER ) - if( CMAKE_HOST_WIN32 ) - SET( WIN32 1 ) - SET( UNIX ) - elseif( CMAKE_HOST_APPLE ) - SET( APPLE 1 ) - SET( UNIX ) - endif() - find_package( ${ARGN} ) - SET( WIN32 ) - SET( APPLE ) - SET( UNIX 1 ) - set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY ) - set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY ) - set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY ) -endmacro() - - -# macro to find programs on the host OS -macro( find_host_program ) - set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER ) - set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER ) - set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER ) - if( CMAKE_HOST_WIN32 ) - SET( WIN32 1 ) - SET( UNIX ) - elseif( CMAKE_HOST_APPLE ) - SET( APPLE 1 ) - SET( UNIX ) - endif() - find_program( ${ARGN} ) - SET( WIN32 ) - SET( APPLE ) - SET( UNIX 1 ) - set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY ) - set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY ) - set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY ) -endmacro() - - -# export toolchain settings for the try_compile() command -if( NOT _CMAKE_IN_TRY_COMPILE ) - set( __toolchain_config "") - foreach( __var NDK_CCACHE LIBRARY_OUTPUT_PATH_ROOT ANDROID_FORBID_SYGWIN - ANDROID_NDK_HOST_X64 - ANDROID_NDK - ANDROID_NDK_LAYOUT - ANDROID_STANDALONE_TOOLCHAIN - ANDROID_TOOLCHAIN_NAME - ANDROID_ABI - ANDROID_NATIVE_API_LEVEL - ANDROID_STL - ANDROID_STL_FORCE_FEATURES - ANDROID_FORCE_ARM_BUILD - ANDROID_NO_UNDEFINED - ANDROID_SO_UNDEFINED - ANDROID_FUNCTION_LEVEL_LINKING - ANDROID_GOLD_LINKER - ANDROID_NOEXECSTACK - ANDROID_RELRO - ANDROID_LIBM_PATH - ANDROID_EXPLICIT_CRT_LINK - ANDROID_APP_PIE - ) - if( DEFINED ${__var} ) - if( ${__var} MATCHES " ") - set( __toolchain_config "${__toolchain_config}set( ${__var} \"${${__var}}\" CACHE INTERNAL \"\" )\n" ) - else() - set( __toolchain_config "${__toolchain_config}set( ${__var} ${${__var}} CACHE INTERNAL \"\" )\n" ) - endif() - endif() - endforeach() - file( WRITE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/android.toolchain.config.cmake" "${__toolchain_config}" ) - unset( __toolchain_config ) -endif() - - -# force cmake to produce / instead of \ in build commands for Ninja generator -if( CMAKE_GENERATOR MATCHES "Ninja" AND CMAKE_HOST_WIN32 ) - # it is a bad hack after all - # CMake generates Ninja makefiles with UNIX paths only if it thinks that we are going to build with MinGW - set( CMAKE_COMPILER_IS_MINGW TRUE ) # tell CMake that we are MinGW - set( CMAKE_CROSSCOMPILING TRUE ) # stop recursion - enable_language( C ) - enable_language( CXX ) - # unset( CMAKE_COMPILER_IS_MINGW ) # can't unset because CMake does not convert back-slashes in response files without it - unset( MINGW ) -endif() - - -# Variables controlling behavior or set by cmake toolchain: -# ANDROID_ABI : "armeabi-v7a" (default), "armeabi", "armeabi-v7a with NEON", "armeabi-v7a with VFPV3", "armeabi-v6 with VFP", "x86", "mips", "arm64-v8a", "x86_64", "mips64" -# ANDROID_NATIVE_API_LEVEL : 3,4,5,8,9,14,15,16,17,18,19,21 (depends on NDK version) -# ANDROID_STL : gnustl_static/gnustl_shared/stlport_static/stlport_shared/gabi++_static/gabi++_shared/system_re/system/none -# ANDROID_FORBID_SYGWIN : ON/OFF -# ANDROID_NO_UNDEFINED : ON/OFF -# ANDROID_SO_UNDEFINED : OFF/ON (default depends on NDK version) -# ANDROID_FUNCTION_LEVEL_LINKING : ON/OFF -# ANDROID_GOLD_LINKER : ON/OFF -# ANDROID_NOEXECSTACK : ON/OFF -# ANDROID_RELRO : ON/OFF -# ANDROID_FORCE_ARM_BUILD : ON/OFF -# ANDROID_STL_FORCE_FEATURES : ON/OFF -# ANDROID_LIBM_PATH : path to libm.so (set to something like $(TOP)/out/target/product//obj/lib/libm.so) to workaround unresolved `sincos` -# Can be set only at the first run: -# ANDROID_NDK : path to your NDK install -# NDK_CCACHE : path to your ccache executable -# ANDROID_TOOLCHAIN_NAME : the NDK name of compiler toolchain -# ANDROID_NDK_HOST_X64 : try to use x86_64 toolchain (default for x64 host systems) -# ANDROID_NDK_LAYOUT : the inner NDK structure (RELEASE, LINARO, ANDROID) -# LIBRARY_OUTPUT_PATH_ROOT : -# ANDROID_STANDALONE_TOOLCHAIN -# -# Primary read-only variables: -# ANDROID : always TRUE -# ARMEABI : TRUE for arm v6 and older devices -# ARMEABI_V6 : TRUE for arm v6 -# ARMEABI_V7A : TRUE for arm v7a -# ARM64_V8A : TRUE for arm64-v8a -# NEON : TRUE if NEON unit is enabled -# VFPV3 : TRUE if VFP version 3 is enabled -# X86 : TRUE if configured for x86 -# X86_64 : TRUE if configured for x86_64 -# MIPS : TRUE if configured for mips -# MIPS64 : TRUE if configured for mips64 -# BUILD_WITH_ANDROID_NDK : TRUE if NDK is used -# BUILD_WITH_STANDALONE_TOOLCHAIN : TRUE if standalone toolchain is used -# ANDROID_NDK_HOST_SYSTEM_NAME : "windows", "linux-x86" or "darwin-x86" depending on host platform -# ANDROID_NDK_ABI_NAME : "armeabi", "armeabi-v7a", "x86", "mips", "arm64-v8a", "x86_64", "mips64" depending on ANDROID_ABI -# ANDROID_NDK_RELEASE : from r5 to r10d; set only for NDK -# ANDROID_NDK_RELEASE_NUM : numeric ANDROID_NDK_RELEASE version (1000*major+minor) -# ANDROID_ARCH_NAME : "arm", "x86", "mips", "arm64", "x86_64", "mips64" depending on ANDROID_ABI -# ANDROID_SYSROOT : path to the compiler sysroot -# TOOL_OS_SUFFIX : "" or ".exe" depending on host platform -# ANDROID_COMPILER_IS_CLANG : TRUE if clang compiler is used -# -# Secondary (less stable) read-only variables: -# ANDROID_COMPILER_VERSION : GCC version used (not Clang version) -# ANDROID_CLANG_VERSION : version of clang compiler if clang is used -# ANDROID_CXX_FLAGS : C/C++ compiler flags required by Android platform -# ANDROID_SUPPORTED_ABIS : list of currently allowed values for ANDROID_ABI -# ANDROID_TOOLCHAIN_MACHINE_NAME : "arm-linux-androideabi", "arm-eabi" or "i686-android-linux" -# ANDROID_TOOLCHAIN_ROOT : path to the top level of toolchain (standalone or placed inside NDK) -# ANDROID_CLANG_TOOLCHAIN_ROOT : path to clang tools -# ANDROID_SUPPORTED_NATIVE_API_LEVELS : list of native API levels found inside NDK -# ANDROID_STL_INCLUDE_DIRS : stl include paths -# ANDROID_RTTI : if rtti is enabled by the runtime -# ANDROID_EXCEPTIONS : if exceptions are enabled by the runtime -# ANDROID_GCC_TOOLCHAIN_NAME : read-only, differs from ANDROID_TOOLCHAIN_NAME only if clang is used -# -# Defaults: -# ANDROID_DEFAULT_NDK_API_LEVEL -# ANDROID_DEFAULT_NDK_API_LEVEL_${ARCH} -# ANDROID_NDK_SEARCH_PATHS -# ANDROID_SUPPORTED_ABIS_${ARCH} -# ANDROID_SUPPORTED_NDK_VERSIONS diff --git a/cmake/android/deployment-file.json.in b/cmake/android/deployment-file.json.in deleted file mode 100644 index 81ed8a6ecc..0000000000 --- a/cmake/android/deployment-file.json.in +++ /dev/null @@ -1,13 +0,0 @@ -{ - "qt": "@QT_DIR@", - "sdk": "@ANDROID_SDK_ROOT@", - "ndk": "@ANDROID_NDK@", - "toolchain-prefix": "@ANDROID_TOOLCHAIN_MACHINE_NAME@", - "tool-prefix": "@ANDROID_TOOLCHAIN_MACHINE_NAME@", - "toolchain-version": "@ANDROID_COMPILER_VERSION@", - "ndk-host": "@ANDROID_NDK_HOST_SYSTEM_NAME@", - "target-architecture": "@ANDROID_ABI@", - "application-binary": "@EXECUTABLE_DESTINATION_PATH@", - "android-extra-libs": "@_DEPS@", - "android-package-source-directory": "@ANDROID_APK_BUILD_DIR@" -} diff --git a/cmake/android/strings.xml.in b/cmake/android/strings.xml.in deleted file mode 100644 index 6e6ce7b12e..0000000000 --- a/cmake/android/strings.xml.in +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - ${ANDROID_APP_DISPLAY_NAME} - - Can\'t find Ministro service.\nThe application can\'t start. - This application requires Ministro service. Would you like to install it? - Your application encountered a fatal error and cannot continue. - diff --git a/cmake/compiler.cmake b/cmake/compiler.cmake new file mode 100644 index 0000000000..968a65a6dd --- /dev/null +++ b/cmake/compiler.cmake @@ -0,0 +1,106 @@ +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG") + +if (WIN32) + if (NOT "${CMAKE_SIZEOF_VOID_P}" EQUAL "8") + message( FATAL_ERROR "Only 64 bit builds supported." ) + endif() + + 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 () + + list(APPEND 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 () + 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() + endif () +endif() + +if (ANDROID) + # assume that the toolchain selected for android has C++11 support + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +elseif ((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(FATAL_ERROR "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.") + endif() +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 (APPLE) + 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}") + + set(OSX_SDK "${OSX_VERSION}" CACHE String "OS X SDK version to look for inside Xcode bundle or at OSX_SDK_PATH") + + # 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 () \ No newline at end of file diff --git a/cmake/externals/LibOVR/CMakeLists.txt b/cmake/externals/LibOVR/CMakeLists.txt index c98aa8a04a..97508be0c5 100644 --- a/cmake/externals/LibOVR/CMakeLists.txt +++ b/cmake/externals/LibOVR/CMakeLists.txt @@ -33,7 +33,6 @@ if (WIN32) include(SelectLibraryConfigurations) select_library_configurations(LIBOVR) set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${${EXTERNAL_NAME_UPPER}_LIBRARIES} CACHE TYPE INTERNAL) - message("Libs ${EXTERNAL_NAME_UPPER}_LIBRARIES ${${EXTERNAL_NAME_UPPER}_LIBRARIES}") elseif(APPLE) diff --git a/cmake/externals/draco/CMakeLists.txt b/cmake/externals/draco/CMakeLists.txt new file mode 100644 index 0000000000..6a894e76b6 --- /dev/null +++ b/cmake/externals/draco/CMakeLists.txt @@ -0,0 +1,41 @@ +set(EXTERNAL_NAME draco) + +if (ANDROID) + set(ANDROID_CMAKE_ARGS "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}" "-DANDROID_NATIVE_API_LEVEL=19") +endif () + +if (APPLE) + set(EXTRA_CMAKE_FLAGS -DCMAKE_CXX_FLAGS=-stdlib=libc++ -DCMAKE_EXE_LINKER_FLAGS=-stdlib=libc++) +endif () + +include(ExternalProject) +ExternalProject_Add( + ${EXTERNAL_NAME} + URL http://hifi-public.s3.amazonaws.com/dependencies/draco-1.1.0.zip + URL_MD5 208f8b04c91d5f1c73d731a3ea37c5bb + CONFIGURE_COMMAND CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH=-$ ${EXTRA_CMAKE_FLAGS} + LOG_DOWNLOAD 1 + LOG_CONFIGURE 1 + LOG_BUILD 1 +) + +# Hide this external target (for ide users) +set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") + +ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR) +set(SUFFIXED_INSTALL_DIR "${INSTALL_DIR}-$") + +string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) + +set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SUFFIXED_INSTALL_DIR}/include CACHE PATH "List of Draco include directories") + +if (UNIX) + set(LIB_PREFIX "lib") + set(LIB_EXT "a") +elseif (WIN32) + set(LIB_EXT "lib") +endif () + +set(${EXTERNAL_NAME_UPPER}_LIBRARY ${SUFFIXED_INSTALL_DIR}/lib/${LIB_PREFIX}draco.${LIB_EXT} CACHE FILEPATH "Path to Draco release library") +set(${EXTERNAL_NAME_UPPER}_ENCODER_LIBRARY ${SUFFIXED_INSTALL_DIR}/lib/${LIB_PREFIX}dracoenc.${LIB_EXT} CACHE FILEPATH "Path to Draco encoder release library") +set(${EXTERNAL_NAME_UPPER}_DECODER_LIBRARY ${SUFFIXED_INSTALL_DIR}/lib/${LIB_PREFIX}dracodec.${LIB_EXT} CACHE FILEPATH "Path to Draco decoder release library") diff --git a/cmake/externals/glew/CMakeLists.txt b/cmake/externals/glew/CMakeLists.txt index 28a599bfa6..6c3208981d 100644 --- a/cmake/externals/glew/CMakeLists.txt +++ b/cmake/externals/glew/CMakeLists.txt @@ -9,7 +9,7 @@ ExternalProject_Add( ${EXTERNAL_NAME} URL http://hifi-public.s3.amazonaws.com/dependencies/glew_simple_1.13.0.zip URL_MD5 73f833649e904257b35bf4e84f8bdfb5 - CONFIGURE_COMMAND CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH= + CONFIGURE_COMMAND CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH= -DCMAKE_POSITION_INDEPENDENT_CODE=ON LOG_DOWNLOAD 1 LOG_CONFIGURE 1 LOG_BUILD 1 diff --git a/cmake/externals/glm/CMakeLists.txt b/cmake/externals/glm/CMakeLists.txt index 79a44fa48e..bc8089074f 100644 --- a/cmake/externals/glm/CMakeLists.txt +++ b/cmake/externals/glm/CMakeLists.txt @@ -6,7 +6,7 @@ ExternalProject_Add( URL https://hifi-public.s3.amazonaws.com/dependencies/glm-0.9.8.zip URL_MD5 579ac77a3110befa3244d68c0ceb7281 BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build - CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH= + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH= ${EXTERNAL_ARGS} LOG_DOWNLOAD 1 LOG_CONFIGURE 1 LOG_BUILD 1 diff --git a/cmake/externals/hifiAudioCodec/CMakeLists.txt b/cmake/externals/hifiAudioCodec/CMakeLists.txt index a30396c6fd..e3ba36a440 100644 --- a/cmake/externals/hifiAudioCodec/CMakeLists.txt +++ b/cmake/externals/hifiAudioCodec/CMakeLists.txt @@ -5,43 +5,41 @@ set(EXTERNAL_NAME hifiAudioCodec) string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) -if (NOT ANDROID) - - if (WIN32 OR APPLE) - ExternalProject_Add( - ${EXTERNAL_NAME} - URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-1.zip - URL_MD5 23ec3fe51eaa155ea159a4971856fc13 - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND "" - LOG_DOWNLOAD 1 - ) - else () - ExternalProject_Add( - ${EXTERNAL_NAME} - URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-linux.zip - URL_MD5 7d37914a18aa4de971d2f45dd3043bde - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND "" - LOG_DOWNLOAD 1 - ) - endif() - - # Hide this external target (for ide users) - set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") - - ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR) - - set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE TYPE INTERNAL) - - if (WIN32) - set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/audio.lib CACHE TYPE INTERNAL) - elseif(APPLE) - set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/libaudio.a CACHE TYPE INTERNAL) - elseif(NOT ANDROID) - set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/libaudio.a CACHE TYPE INTERNAL) - endif() - +if (WIN32) + set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-win-2.0.zip) + set(DOWNLOAD_MD5 9199d4dbd6b16bed736b235efe980e67) +elseif (APPLE) + set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-mac-2.0.zip) + set(DOWNLOAD_MD5 21649881e7d5dc94f922179be96f76ba) +elseif (ANDROID) + set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-android-2.0.zip) + set(DOWNLOAD_MD5 aef2a852600d498d58aa586668191683) +elseif (UNIX) + set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-linux-2.0.zip) + set(DOWNLOAD_MD5 67fb7755f9bcafb98a9fceea53bc7481) +else() + return() +endif() + +ExternalProject_Add( + ${EXTERNAL_NAME} + URL ${DOWNLOAD_URL} + URL_MD5 ${DOWNLOAD_MD5} + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + LOG_DOWNLOAD 1 +) + +# Hide this external target (for ide users) +set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") + +ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR) + +set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE TYPE INTERNAL) + +if (WIN32) + set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/audio.lib CACHE TYPE INTERNAL) +else() + set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/libaudio.a CACHE TYPE INTERNAL) endif() diff --git a/cmake/externals/nvtt/CMakeLists.txt b/cmake/externals/nvtt/CMakeLists.txt index fa9d7b5ea1..00722bd1e0 100644 --- a/cmake/externals/nvtt/CMakeLists.txt +++ b/cmake/externals/nvtt/CMakeLists.txt @@ -31,7 +31,7 @@ else () ${EXTERNAL_NAME} URL http://hifi-public.s3.amazonaws.com/dependencies/nvidia-texture-tools-2.1.0.hifi.zip URL_MD5 5794b950f8b265a9a41b2839b3bf7ebb - CONFIGURE_COMMAND CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DNVTT_SHARED=1 -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH= + CONFIGURE_COMMAND CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DNVTT_SHARED=1 -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH= -DCMAKE_POSITION_INDEPENDENT_CODE=ON LOG_DOWNLOAD 1 LOG_CONFIGURE 1 LOG_BUILD 1 diff --git a/cmake/externals/quazip/CMakeLists.txt b/cmake/externals/quazip/CMakeLists.txt index 7af13dafa7..0d66b365a2 100644 --- a/cmake/externals/quazip/CMakeLists.txt +++ b/cmake/externals/quazip/CMakeLists.txt @@ -4,14 +4,6 @@ cmake_policy(SET CMP0046 OLD) include(ExternalProject) -if (WIN32) - # windows shell does not like backslashes expanded on the command line, - # so convert all backslashes in the QT path to forward slashes - string(REPLACE \\ / QT_CMAKE_PREFIX_PATH $ENV{QT_CMAKE_PREFIX_PATH}) -elseif ($ENV{QT_CMAKE_PREFIX_PATH}) - set(QT_CMAKE_PREFIX_PATH $ENV{QT_CMAKE_PREFIX_PATH}) -endif () - set(QUAZIP_CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH= -DCMAKE_PREFIX_PATH=${QT_CMAKE_PREFIX_PATH} -DCMAKE_INSTALL_NAME_DIR:PATH=/lib -DZLIB_ROOT=${ZLIB_ROOT} -DCMAKE_POSITION_INDEPENDENT_CODE=ON) if (APPLE) @@ -21,8 +13,8 @@ endif () ExternalProject_Add( ${EXTERNAL_NAME} - URL https://s3-us-west-1.amazonaws.com/hifi-production/dependencies/quazip-0.7.2.zip - URL_MD5 2955176048a31262c09259ca8d309d19 + URL https://hifi-public.s3.amazonaws.com/dependencies/quazip-0.7.3.zip + URL_MD5 ed03754d39b9da1775771819b8001d45 BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build CMAKE_ARGS ${QUAZIP_CMAKE_ARGS} LOG_DOWNLOAD 1 @@ -34,9 +26,9 @@ add_dependencies(quazip zlib) # Hide this external target (for ide users) set_target_properties(${EXTERNAL_NAME} PROPERTIES - FOLDER "hidden/externals" - INSTALL_NAME_DIR ${INSTALL_DIR}/lib - BUILD_WITH_INSTALL_RPATH True) + FOLDER "hidden/externals" + INSTALL_NAME_DIR ${INSTALL_DIR}/lib + BUILD_WITH_INSTALL_RPATH True) ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR) set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIR ${INSTALL_DIR}/include CACHE PATH "List of QuaZip include directories") diff --git a/cmake/externals/tbb/CMakeLists.txt b/cmake/externals/tbb/CMakeLists.txt index 74f7e10859..9664fe7250 100644 --- a/cmake/externals/tbb/CMakeLists.txt +++ b/cmake/externals/tbb/CMakeLists.txt @@ -2,57 +2,27 @@ set(EXTERNAL_NAME tbb) include(ExternalProject) -if (ANDROID) - - find_program(NDK_BUILD_COMMAND NAMES ndk-build DOC "Path to the ndk-build command") - - ExternalProject_Add( - ${EXTERNAL_NAME} - URL http://hifi-public.s3.amazonaws.com/dependencies/tbb43_20150316oss_src.tgz - URL_MD5 bf090eaa86cf89ea014b7b462786a440 - BUILD_COMMAND ${NDK_BUILD_COMMAND} --directory=jni target=android tbb tbbmalloc arch=arm - BUILD_IN_SOURCE 1 - CONFIGURE_COMMAND "" - INSTALL_COMMAND ${CMAKE_COMMAND} -DTBB_LIBS_SUFFIX=so -P ${CMAKE_CURRENT_SOURCE_DIR}/TBBLibCopy.cmake - LOG_DOWNLOAD 1 - LOG_CONFIGURE 1 - LOG_BUILD 1 - ) +if (WIN32) + set(DOWNLOAD_URL http://hifi-public.s3.amazonaws.com/dependencies/tbb2017_20170604oss_win_slim.zip) + set(DOWNLOAD_MD5 065934458e3db88397f3d10e7eea536c) elseif (APPLE) - find_program(MAKE_COMMAND NAMES make DOC "Path to the make command") - - ExternalProject_Add( - ${EXTERNAL_NAME} - URL http://hifi-public.s3.amazonaws.com/dependencies/tbb43_20150316oss_src.tgz - URL_MD5 bf090eaa86cf89ea014b7b462786a440 - BUILD_COMMAND ${MAKE_COMMAND} tbb_os=macos - BUILD_IN_SOURCE 1 - CONFIGURE_COMMAND "" - INSTALL_COMMAND ${CMAKE_COMMAND} -DTBB_LIBS_SUFFIX=dylib -P ${CMAKE_CURRENT_SOURCE_DIR}/TBBLibCopy.cmake - LOG_DOWNLOAD 1 - LOG_CONFIGURE 1 - LOG_BUILD 1 - ) + set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/tbb2017_20170604oss_mac_slim.tar.gz) + set(DOWNLOAD_MD5 62bde626b396f8e1a85c6a8ded1d8105) else () - if (WIN32) - set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/tbb43_20150316oss_win.zip) - set(DOWNLOAD_MD5 d250d40bb93b255f75bcbb19e976a440) - else () - set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/tbb43_20150316oss_lin.tgz) - set(DOWNLOAD_MD5 7830ba2bc62438325fba2ec0c95367a5) - endif () - - ExternalProject_Add( - ${EXTERNAL_NAME} - URL ${DOWNLOAD_URL} - URL_MD5 ${DOWNLOAD_MD5} - BUILD_COMMAND "" - CONFIGURE_COMMAND "" - INSTALL_COMMAND "" - LOG_DOWNLOAD ON - ) + set(DOWNLOAD_URL http://hifi-public.s3.amazonaws.com/dependencies/tbb2017_20170604oss_lin_slim.tar.gz) + set(DOWNLOAD_MD5 2a5c721f40fa3503ffc12c18dd00011c) endif () +ExternalProject_Add( + ${EXTERNAL_NAME} + URL ${DOWNLOAD_URL} + URL_MD5 ${DOWNLOAD_MD5} + BUILD_COMMAND "" + CONFIGURE_COMMAND "" + INSTALL_COMMAND "" + LOG_DOWNLOAD ON +) + # Hide this external target (for ide users) set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") @@ -70,22 +40,32 @@ if (APPLE) change-install-name COMMENT "Calling install_name_tool on TBB libraries to fix install name for dylib linking" COMMAND ${CMAKE_COMMAND} -DINSTALL_NAME_LIBRARY_DIR=${_TBB_LIB_DIR} -P ${EXTERNAL_PROJECT_DIR}/OSXInstallNameChange.cmake - DEPENDEES install + DEPENDEES download WORKING_DIRECTORY LOG 1 ) - elseif (WIN32) - if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") - set(_TBB_LIB_DIR "${SOURCE_DIR}/lib/intel64/vc12") - set(${EXTERNAL_NAME_UPPER}_DLL_PATH "${SOURCE_DIR}/bin/intel64/vc12" CACHE PATH "Path to TBB DLLs") + if (MSVC_VERSION GREATER_EQUAL 1900) + set(_TBB_MSVC_DIR "vc14") + elseif (MSVC_VERSION GREATER_EQUAL 1800) + set(_TBB_MSVC_DIR "vc12") + elseif (MSVC_VERSION GREATER_EQUAL 1700) + set(_TBB_MSVC_DIR "vc11") else() - set(_TBB_LIB_DIR "${SOURCE_DIR}/lib/ia32/vc12") - set(${EXTERNAL_NAME_UPPER}_DLL_PATH "${SOURCE_DIR}/bin/ia32/vc12" CACHE PATH "Path to TBB DLLs") + message(FATAL_ERROR "MSVC ${MSVC_VERSION} not supported by Intel TBB") endif() + + if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") + set(_TBB_LIB_DIR "${SOURCE_DIR}/lib/intel64/${_TBB_MSVC_DIR}") + set(${EXTERNAL_NAME_UPPER}_DLL_PATH "${SOURCE_DIR}/bin/intel64/${_TBB_MSVC_DIR}" CACHE PATH "Path to TBB DLLs") + else() + set(_TBB_LIB_DIR "${SOURCE_DIR}/lib/ia32/${_TBB_MSVC_DIR}") + set(${EXTERNAL_NAME_UPPER}_DLL_PATH "${SOURCE_DIR}/bin/ia32/${_TBB_MSVC_DIR}" CACHE PATH "Path to TBB DLLs") + endif() + set(_LIB_EXT "lib") elseif (ANDROID) - set(_TBB_LIB_DIR "${SOURCE_DIR}/lib") + set(_TBB_LIB_DIR "${SOURCE_DIR}/lib/android") set(_LIB_PREFIX "lib") set(_LIB_EXT "so") elseif (UNIX) @@ -103,15 +83,15 @@ elseif (UNIX) OUTPUT_VARIABLE GCC_VERSION ) - if (GCC_VERSION VERSION_GREATER 4.4 OR GCC_VERSION VERSION_EQUAL 4.4) + if (GCC_VERSION VERSION_GREATER 4.7 OR GCC_VERSION VERSION_EQUAL 4.7) + set(_TBB_LIB_DIR "${SOURCE_DIR}/lib/${_TBB_ARCH_DIR}/gcc4.7") + elseif (GCC_VERSION VERSION_GREATER 4.4 OR GCC_VERSION VERSION_EQUAL 4.4) set(_TBB_LIB_DIR "${SOURCE_DIR}/lib/${_TBB_ARCH_DIR}/gcc4.4") elseif (GCC_VERSION VERSION_GREATER 4.1 OR GCC_VERSION VERSION_EQUAL 4.1) set(_TBB_LIB_DIR "${SOURCE_DIR}/lib/${_TBB_ARCH_DIR}/gcc4.1") else () message(STATUS "Could not find a compatible version of Threading Building Blocks library for your compiler.") endif () - - endif () if (DEFINED _TBB_LIB_DIR) @@ -124,3 +104,4 @@ endif () if (DEFINED ${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE) set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE TYPE "List of tbb include directories") endif () + diff --git a/cmake/externals/wasapi/CMakeLists.txt b/cmake/externals/wasapi/CMakeLists.txt index 1bf195fc84..4c0ffaf88f 100644 --- a/cmake/externals/wasapi/CMakeLists.txt +++ b/cmake/externals/wasapi/CMakeLists.txt @@ -6,8 +6,8 @@ if (WIN32) include(ExternalProject) ExternalProject_Add( ${EXTERNAL_NAME} - URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi8.zip - URL_MD5 b01510437ea15527156bc25cdf733bd9 + URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi10.zip + URL_MD5 4f40e49715a420fb67b45b9cee19052c CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" diff --git a/cmake/init.cmake b/cmake/init.cmake new file mode 100644 index 0000000000..9d7b0fd94c --- /dev/null +++ b/cmake/init.cmake @@ -0,0 +1,56 @@ +if (WIN32) + cmake_policy(SET CMP0020 NEW) +endif (WIN32) + +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") +# Hide automoc folders (for IDEs) +set(AUTOGEN_TARGETS_FOLDER "hidden/generated") +# Find includes in corresponding build directories +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +if (CMAKE_BUILD_TYPE) + string(TOUPPER ${CMAKE_BUILD_TYPE} UPPER_CMAKE_BUILD_TYPE) +else () + set(UPPER_CMAKE_BUILD_TYPE DEBUG) +endif () + +# CMAKE_CURRENT_SOURCE_DIR is the parent folder here +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/") + +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() +unset(HIFI_CUSTOM_MACROS) + +if (ANDROID) + set(BUILD_SHARED_LIBS ON) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH) + + string(REGEX REPLACE "\\\\" "/" ANDROID_NDK ${ANDROID_NDK}) + string(REGEX REPLACE "\\\\" "/" CMAKE_TOOLCHAIN_FILE ${CMAKE_TOOLCHAIN_FILE}) + string(REGEX REPLACE "\\\\" "/" ANDROID_TOOLCHAIN ${ANDROID_TOOLCHAIN}) + string(REGEX REPLACE "\\\\" "/" CMAKE_MAKE_PROGRAM ${CMAKE_MAKE_PROGRAM}) + list(APPEND EXTERNAL_ARGS -DANDROID_ABI=${ANDROID_ABI}) + list(APPEND EXTERNAL_ARGS -DANDROID_NDK=${ANDROID_NDK}) + list(APPEND EXTERNAL_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}) + list(APPEND EXTERNAL_ARGS -DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}) + list(APPEND EXTERNAL_ARGS -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}) + list(APPEND EXTERNAL_ARGS -DHIFI_ANDROID=${HIFI_ANDROID}) + list(APPEND EXTERNAL_ARGS -DANDROID_PLATFORM=${ANDROID_PLATFORM}) + list(APPEND EXTERNAL_ARGS -DANDROID_TOOLCHAIN=${ANDROID_TOOLCHAIN}) + list(APPEND EXTERNAL_ARGS -DANDROID_STL=${ANDROID_STL}) +endif () diff --git a/cmake/macros/AutoScribeShader.cmake b/cmake/macros/AutoScribeShader.cmake index c43ade45d2..1919ecf00a 100755 --- a/cmake/macros/AutoScribeShader.cmake +++ b/cmake/macros/AutoScribeShader.cmake @@ -62,7 +62,9 @@ function(AUTOSCRIBE_SHADER SHADER_FILE) # since it's unrunnable by the cross-compiling build machine # so, we require the compiling user to point us at a compiled executable version for their native toolchain - find_program(NATIVE_SCRIBE scribe PATHS ${SCRIBE_PATH} ENV SCRIBE_PATH) + if (NOT NATIVE_SCRIBE) + find_program(NATIVE_SCRIBE scribe PATHS ${SCRIBE_PATH} ENV SCRIBE_PATH) + endif() if (NOT NATIVE_SCRIBE) message(FATAL_ERROR "The High Fidelity scribe tool is required for shader pre-processing. \ @@ -116,6 +118,7 @@ macro(AUTOSCRIBE_SHADER_LIB) foreach(SHADER_FILE ${SHADER_SOURCE_FILES}) AUTOSCRIBE_SHADER(${SHADER_FILE} ${SHADER_INCLUDE_FILES}) file(TO_CMAKE_PATH "${AUTOSCRIBE_SHADER_RETURN}" AUTOSCRIBE_GENERATED_FILE) + set_property(SOURCE ${AUTOSCRIBE_GENERATED_FILE} PROPERTY SKIP_AUTOMOC ON) list(APPEND AUTOSCRIBE_SHADER_SRC ${AUTOSCRIBE_GENERATED_FILE}) endforeach() #message(${TARGET_NAME} ${AUTOSCRIBE_SHADER_SRC}) diff --git a/cmake/macros/GenerateInstallers.cmake b/cmake/macros/GenerateInstallers.cmake index 0def701739..6c131168d5 100644 --- a/cmake/macros/GenerateInstallers.cmake +++ b/cmake/macros/GenerateInstallers.cmake @@ -22,23 +22,17 @@ macro(GENERATE_INSTALLERS) set(CPACK_PACKAGE_FILE_NAME "HighFidelity-Beta-${BUILD_VERSION}") set(CPACK_NSIS_DISPLAY_NAME ${_DISPLAY_NAME}) set(CPACK_NSIS_PACKAGE_NAME ${_DISPLAY_NAME}) + if (PR_BUILD) + set(CPACK_NSIS_COMPRESSOR "/SOLID bzip2") + endif () set(CPACK_PACKAGE_INSTALL_DIRECTORY ${_DISPLAY_NAME}) + if (WIN32) - # include CMake module that will install compiler system libraries - # so that we have msvcr120 and msvcp120 installed with targets - set(CMAKE_INSTALL_SYSTEM_RUNTIME_DESTINATION ${INTERFACE_INSTALL_DIR}) - - # as long as we're including sixense plugin with installer - # we need re-distributables for VS 2011 as well - # this should be removed if/when sixense support is pulled - set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS - "${EXTERNALS_BINARY_DIR}/sixense/project/src/sixense/samples/win64/msvcr100.dll" - "${EXTERNALS_BINARY_DIR}/sixense/project/src/sixense/samples/win64/msvcp100.dll" - ) - + # Do not install the Visual Studio C runtime libraries. The installer will do this automatically + set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP TRUE) + include(InstallRequiredSystemLibraries) - set(CPACK_NSIS_MUI_ICON "${HF_CMAKE_DIR}/installer/installer.ico") # install and reference the Add/Remove icon @@ -90,3 +84,4 @@ macro(GENERATE_INSTALLERS) include(CPack) endmacro() + diff --git a/cmake/macros/GenerateQrc.cmake b/cmake/macros/GenerateQrc.cmake new file mode 100644 index 0000000000..0283b3ea9b --- /dev/null +++ b/cmake/macros/GenerateQrc.cmake @@ -0,0 +1,20 @@ + +function(GENERATE_QRC) + set(oneValueArgs OUTPUT PREFIX PATH) + set(multiValueArgs GLOBS) + cmake_parse_arguments(GENERATE_QRC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} ) + if ("${GENERATE_QRC_PREFIX}" STREQUAL "") + set(QRC_PREFIX_PATH /) + else() + set(QRC_PREFIX_PATH ${GENERATE_QRC_PREFIX}) + endif() + + foreach(GLOB ${GENERATE_QRC_GLOBS}) + file(GLOB_RECURSE FOUND_FILES RELATIVE ${GENERATE_QRC_PATH} ${GLOB}) + foreach(FILENAME ${FOUND_FILES}) + set(QRC_CONTENTS "${QRC_CONTENTS}${GENERATE_QRC_PATH}/${FILENAME}\n") + endforeach() + endforeach() + + configure_file("${HF_CMAKE_DIR}/templates/resources.qrc.in" ${GENERATE_QRC_OUTPUT}) +endfunction() diff --git a/cmake/macros/InstallBesideConsole.cmake b/cmake/macros/InstallBesideConsole.cmake index d5777fff12..3c991acf86 100644 --- a/cmake/macros/InstallBesideConsole.cmake +++ b/cmake/macros/InstallBesideConsole.cmake @@ -22,9 +22,12 @@ macro(install_beside_console) else () # setup install of executable and things copied by fixup/windeployqt install( - FILES "$/" + DIRECTORY "$/" DESTINATION ${COMPONENT_INSTALL_DIR} COMPONENT ${SERVER_COMPONENT} + PATTERN "*.pdb" EXCLUDE + PATTERN "*.lib" EXCLUDE + PATTERN "*.exp" EXCLUDE ) # on windows for PR and production builds, sign the executable diff --git a/cmake/macros/MemoryDebugger.cmake b/cmake/macros/MemoryDebugger.cmake index 6df41257f2..ed80e03c6b 100644 --- a/cmake/macros/MemoryDebugger.cmake +++ b/cmake/macros/MemoryDebugger.cmake @@ -14,9 +14,17 @@ endif () if (HIFI_MEMORY_DEBUGGING) if (UNIX) - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -U_FORTIFY_SOURCE -fno-stack-protector -fno-omit-frame-pointer") - SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libasan -static-libstdc++ -fsanitize=address") - SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libasan -static-libstdc++ -fsanitize=address") + if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + # for clang on Linux + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer -fsanitize=undefined -fsanitize=address -fsanitize-recover=address") + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined -fsanitize=address -fsanitize-recover=address") + SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined -fsanitize=address -fsanitize-recover=address") + else () + # for gcc on Linux + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined -fsanitize=address -U_FORTIFY_SOURCE -fno-stack-protector -fno-omit-frame-pointer") + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libasan -static-libstdc++ -fsanitize=undefined -fsanitize=address") + SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libasan -static-libstdc++ -fsanitize=undefined -fsanitize=address") + endif() endif (UNIX) endif () endmacro(SETUP_MEMORY_DEBUGGER) diff --git a/cmake/macros/SetPackagingParameters.cmake b/cmake/macros/SetPackagingParameters.cmake index 82a4a7d080..8faa4e6d96 100644 --- a/cmake/macros/SetPackagingParameters.cmake +++ b/cmake/macros/SetPackagingParameters.cmake @@ -111,14 +111,14 @@ macro(SET_PACKAGING_PARAMETERS) # shortcut names if (PRODUCTION_BUILD) - set(INTERFACE_SHORTCUT_NAME "Interface") + set(INTERFACE_SHORTCUT_NAME "High Fidelity Interface") set(CONSOLE_SHORTCUT_NAME "Sandbox") else () - set(INTERFACE_SHORTCUT_NAME "Interface - ${BUILD_VERSION}") + set(INTERFACE_SHORTCUT_NAME "High Fidelity Interface - ${BUILD_VERSION}") set(CONSOLE_SHORTCUT_NAME "Sandbox - ${BUILD_VERSION}") endif () - set(INTERFACE_HF_SHORTCUT_NAME "High Fidelity ${INTERFACE_SHORTCUT_NAME}") + set(INTERFACE_HF_SHORTCUT_NAME "${INTERFACE_SHORTCUT_NAME}") set(CONSOLE_HF_SHORTCUT_NAME "High Fidelity ${CONSOLE_SHORTCUT_NAME}") set(PRE_SANDBOX_INTERFACE_SHORTCUT_NAME "High Fidelity") @@ -126,7 +126,13 @@ macro(SET_PACKAGING_PARAMETERS) # check if we need to find signtool if (PRODUCTION_BUILD OR PR_BUILD) - find_program(SIGNTOOL_EXECUTABLE signtool PATHS "C:/Program Files (x86)/Windows Kits/8.1" PATH_SUFFIXES "bin/x64") + if (MSVC_VERSION GREATER_EQUAL 1910) # VS 2017 + find_program(SIGNTOOL_EXECUTABLE signtool PATHS "C:/Program Files (x86)/Windows Kits/10" PATH_SUFFIXES "bin/${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}/x64") + elseif (MSVC_VERSION GREATER_EQUAL 1800) # VS 2013 + find_program(SIGNTOOL_EXECUTABLE signtool PATHS "C:/Program Files (x86)/Windows Kits/8.1" PATH_SUFFIXES "bin/x64") + else() + message( FATAL_ERROR "Visual Studio 2013 or higher required." ) + endif() if (NOT SIGNTOOL_EXECUTABLE) message(FATAL_ERROR "Code signing of executables was requested but signtool.exe could not be found.") @@ -141,6 +147,7 @@ macro(SET_PACKAGING_PARAMETERS) set(CONSOLE_STARTUP_REG_KEY "ConsoleStartupShortcut") set(CLIENT_LAUNCH_NOW_REG_KEY "ClientLaunchAfterInstall") set(SERVER_LAUNCH_NOW_REG_KEY "ServerLaunchAfterInstall") + set(CUSTOM_INSTALL_REG_KEY "CustomInstall") endif () # setup component categories for installer @@ -155,5 +162,6 @@ macro(SET_PACKAGING_PARAMETERS) # create a header file our targets can use to find out the application version file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/includes") configure_file("${HF_CMAKE_DIR}/templates/BuildInfo.h.in" "${CMAKE_BINARY_DIR}/includes/BuildInfo.h") + include_directories("${CMAKE_BINARY_DIR}/includes") endmacro(SET_PACKAGING_PARAMETERS) diff --git a/cmake/macros/SetupHifiClientServerPlugin.cmake b/cmake/macros/SetupHifiClientServerPlugin.cmake index 37b000efb5..bc66484c30 100644 --- a/cmake/macros/SetupHifiClientServerPlugin.cmake +++ b/cmake/macros/SetupHifiClientServerPlugin.cmake @@ -9,11 +9,13 @@ macro(SETUP_HIFI_CLIENT_SERVER_PLUGIN) set(${TARGET_NAME}_SHARED 1) setup_hifi_library(${ARGV}) - if (NOT DEFINED SERVER_ONLY) + if (BUILD_CLIENT) add_dependencies(interface ${TARGET_NAME}) endif() - add_dependencies(assignment-client ${TARGET_NAME}) + if (BUILD_SERVER) + add_dependencies(assignment-client ${TARGET_NAME}) + endif() set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Plugins") diff --git a/cmake/macros/SetupHifiLibrary.cmake b/cmake/macros/SetupHifiLibrary.cmake index d0fc58af0c..04687e2c84 100644 --- a/cmake/macros/SetupHifiLibrary.cmake +++ b/cmake/macros/SetupHifiLibrary.cmake @@ -12,7 +12,7 @@ macro(SETUP_HIFI_LIBRARY) project(${TARGET_NAME}) # grab the implementation and header files - file(GLOB_RECURSE LIB_SRCS "src/*.h" "src/*.cpp" "src/*.c") + file(GLOB_RECURSE LIB_SRCS "src/*.h" "src/*.cpp" "src/*.c" "src/*.qrc") list(APPEND ${TARGET_NAME}_SRCS ${LIB_SRCS}) # add compiler flags to AVX source files @@ -65,7 +65,7 @@ macro(SETUP_HIFI_LIBRARY) list(APPEND ${TARGET_NAME}_DEPENDENCY_QT_MODULES Core) # find these Qt modules and link them to our own target - find_package(Qt5 COMPONENTS ${${TARGET_NAME}_DEPENDENCY_QT_MODULES} REQUIRED) + find_package(Qt5 COMPONENTS ${${TARGET_NAME}_DEPENDENCY_QT_MODULES} REQUIRED CMAKE_FIND_ROOT_PATH_BOTH) foreach(QT_MODULE ${${TARGET_NAME}_DEPENDENCY_QT_MODULES}) target_link_libraries(${TARGET_NAME} Qt5::${QT_MODULE}) diff --git a/cmake/macros/SetupHifiPlugin.cmake b/cmake/macros/SetupHifiPlugin.cmake index 7e56ea3db2..023c7603dc 100644 --- a/cmake/macros/SetupHifiPlugin.cmake +++ b/cmake/macros/SetupHifiPlugin.cmake @@ -8,7 +8,9 @@ macro(SETUP_HIFI_PLUGIN) set(${TARGET_NAME}_SHARED 1) setup_hifi_library(${ARGV}) - add_dependencies(interface ${TARGET_NAME}) + if (BUILD_CLIENT) + add_dependencies(interface ${TARGET_NAME}) + endif() target_link_libraries(${TARGET_NAME} ${CMAKE_THREAD_LIBS_INIT}) set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Plugins") diff --git a/cmake/macros/SetupQt.cmake b/cmake/macros/SetupQt.cmake new file mode 100644 index 0000000000..ac67e12044 --- /dev/null +++ b/cmake/macros/SetupQt.cmake @@ -0,0 +1,90 @@ +# +# Copyright 2015 High Fidelity, Inc. +# Created by Bradley Austin Davis on 2015/10/10 +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# + +function(set_from_env _RESULT_NAME _ENV_VAR_NAME _DEFAULT_VALUE) + if (NOT DEFINED ${_RESULT_NAME}) + if ("$ENV{${_ENV_VAR_NAME}}" STREQUAL "") + set (${_RESULT_NAME} ${_DEFAULT_VALUE} PARENT_SCOPE) + else() + set (${_RESULT_NAME} $ENV{${_ENV_VAR_NAME}} PARENT_SCOPE) + endif() + endif() +endfunction() + +# Construct a default QT location from a root path, a version and an architecture +function(calculate_default_qt_dir _RESULT_NAME) + if (ANDROID) + set(QT_DEFAULT_ARCH "android_armv7") + elseif(UWP) + set(QT_DEFAULT_ARCH "winrt_x64_msvc2017") + elseif(APPLE) + set(QT_DEFAULT_ARCH "clang_64") + elseif(WIN32) + set(QT_DEFAULT_ARCH "msvc2017_64") + else() + set(QT_DEFAULT_ARCH "gcc_64") + endif() + + if (WIN32 OR (ANDROID AND ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows"))) + set(QT_DEFAULT_ROOT "c:/Qt") + else() + set(QT_DEFAULT_ROOT "$ENV{HOME}/Qt") + endif() + + set_from_env(QT_ROOT QT_ROOT ${QT_DEFAULT_ROOT}) + set_from_env(QT_VERSION QT_VERSION "5.9.1") + set_from_env(QT_ARCH QT_ARCH ${QT_DEFAULT_ARCH}) + + set(${_RESULT_NAME} "${QT_ROOT}/${QT_VERSION}/${QT_ARCH}" PARENT_SCOPE) +endfunction() + +# Sets the QT_CMAKE_PREFIX_PATH and QT_DIR variables +# Also enables CMAKE_AUTOMOC and CMAKE_AUTORCC +macro(setup_qt) + # if QT_CMAKE_PREFIX_PATH was not specified before hand, + # try to use the environment variable + if (NOT QT_CMAKE_PREFIX_PATH) + set(QT_CMAKE_PREFIX_PATH "$ENV{QT_CMAKE_PREFIX_PATH}") + endif() + if (("QT_CMAKE_PREFIX_PATH" STREQUAL "") OR (NOT EXISTS "${QT_CMAKE_PREFIX_PATH}")) + calculate_default_qt_dir(QT_DIR) + set(QT_CMAKE_PREFIX_PATH "${QT_DIR}/lib/cmake") + else() + # figure out where the qt dir is + get_filename_component(QT_DIR "${QT_CMAKE_PREFIX_PATH}/../../" ABSOLUTE) + endif() + + if (WIN32) + # windows shell does not like backslashes expanded on the command line, + # so convert all backslashes in the QT path to forward slashes + string(REPLACE \\ / QT_CMAKE_PREFIX_PATH ${QT_CMAKE_PREFIX_PATH}) + string(REPLACE \\ / QT_DIR ${QT_DIR}) + endif() + + # This check doesn't work on Mac + #if (NOT EXISTS "${QT_DIR}/include/QtCore/QtGlobal") + # message(FATAL_ERROR "Unable to locate Qt includes in ${QT_DIR}") + #endif() + + if (NOT EXISTS "${QT_CMAKE_PREFIX_PATH}/Qt5Core/Qt5CoreConfig.cmake") + message(FATAL_ERROR "Unable to locate Qt cmake config in ${QT_CMAKE_PREFIX_PATH}") + endif() + + message(STATUS "The Qt build in use is: \"${QT_DIR}\"") + + # Instruct CMake to run moc automatically when needed. + set(CMAKE_AUTOMOC ON) + + # Instruct CMake to run rcc automatically when needed + set(CMAKE_AUTORCC ON) + + if (WIN32) + add_paths_to_fixup_libs("${QT_DIR}/bin") + endif () + +endmacro() diff --git a/cmake/macros/TargetGlew.cmake b/cmake/macros/TargetGlew.cmake index 5f71f021ec..bc4d5cb033 100644 --- a/cmake/macros/TargetGlew.cmake +++ b/cmake/macros/TargetGlew.cmake @@ -6,9 +6,11 @@ # See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html # macro(TARGET_GLEW) - add_dependency_external_projects(glew) - find_package(GLEW REQUIRED) - add_definitions(-DGLEW_STATIC) - target_include_directories(${TARGET_NAME} PUBLIC ${GLEW_INCLUDE_DIRS}) - target_link_libraries(${TARGET_NAME} ${GLEW_LIBRARY}) + if (NOT ANDROID) + add_definitions(-DGLEW_STATIC) + add_dependency_external_projects(glew) + find_package(GLEW REQUIRED) + target_include_directories(${TARGET_NAME} PUBLIC ${GLEW_INCLUDE_DIRS}) + target_link_libraries(${TARGET_NAME} ${GLEW_LIBRARY}) + endif() endmacro() \ No newline at end of file diff --git a/cmake/macros/TargetLeapMotion.cmake b/cmake/macros/TargetLeapMotion.cmake new file mode 100644 index 0000000000..674ec8f62d --- /dev/null +++ b/cmake/macros/TargetLeapMotion.cmake @@ -0,0 +1,12 @@ +# +# Created by David Rowe on 16 Jun 2017. +# 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 +# + +macro(TARGET_LEAPMOTION) + target_include_directories(${TARGET_NAME} PRIVATE ${LEAPMOTION_INCLUDE_DIRS}) + target_link_libraries(${TARGET_NAME} ${LEAPMOTION_LIBRARIES}) +endmacro() diff --git a/cmake/macros/TargetOpenGL.cmake b/cmake/macros/TargetOpenGL.cmake index 73c92e651a..6ad92259bb 100644 --- a/cmake/macros/TargetOpenGL.cmake +++ b/cmake/macros/TargetOpenGL.cmake @@ -6,15 +6,13 @@ # See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html # macro(TARGET_OPENGL) - add_definitions(-DGLEW_STATIC) if (APPLE) # link in required OS X frameworks and include the right GL headers find_library(OpenGL OpenGL) target_link_libraries(${TARGET_NAME} ${OpenGL}) elseif(ANDROID) - target_link_libraries(${TARGET_NAME} "-lGLESv3" "-lEGL") + target_link_libraries(${TARGET_NAME} GLESv3 EGL) else() - target_nsight() find_package(OpenGL REQUIRED) if (${OPENGL_INCLUDE_DIR}) include_directories(SYSTEM "${OPENGL_INCLUDE_DIR}") @@ -22,4 +20,6 @@ macro(TARGET_OPENGL) target_link_libraries(${TARGET_NAME} "${OPENGL_LIBRARY}") target_include_directories(${TARGET_NAME} PUBLIC ${OPENGL_INCLUDE_DIR}) endif() + target_nsight() + target_glew() endmacro() diff --git a/cmake/macros/TargetOpenSSL.cmake b/cmake/macros/TargetOpenSSL.cmake new file mode 100644 index 0000000000..7ee0283a48 --- /dev/null +++ b/cmake/macros/TargetOpenSSL.cmake @@ -0,0 +1,32 @@ +# +# Copyright 2015 High Fidelity, Inc. +# Created by Bradley Austin Davis on 2015/10/10 +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# +macro(TARGET_OPENSSL) + + if (ANDROID) + + # FIXME use a distributable binary + set(OPENSSL_INSTALL_DIR C:/Android/openssl) + set(OPENSSL_INCLUDE_DIR "${OPENSSL_INSTALL_DIR}/include" CACHE TYPE INTERNAL) + set(OPENSSL_LIBRARIES "${OPENSSL_INSTALL_DIR}/lib/libcrypto.a;${OPENSSL_INSTALL_DIR}/lib/libssl.a" CACHE TYPE INTERNAL) + + else() + + find_package(OpenSSL REQUIRED) + + if (APPLE AND ${OPENSSL_INCLUDE_DIR} STREQUAL "/usr/include") + # this is a user on OS X using system OpenSSL, which is going to throw warnings since they're deprecating for their common crypto + message(WARNING "The found version of OpenSSL is the OS X system version. This will produce deprecation warnings." + "\nWe recommend you install a newer version (at least 1.0.1h) in a different directory and set OPENSSL_ROOT_DIR in your env so Cmake can find it.") + endif() + + endif() + + include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}") + target_link_libraries(${TARGET_NAME} ${OPENSSL_LIBRARIES}) + +endmacro() diff --git a/cmake/macros/TargetTBB.cmake b/cmake/macros/TargetTBB.cmake new file mode 100644 index 0000000000..e9c4639c3d --- /dev/null +++ b/cmake/macros/TargetTBB.cmake @@ -0,0 +1,24 @@ +# +# Copyright 2015 High Fidelity, Inc. +# Created by Bradley Austin Davis on 2015/10/10 +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# +macro(TARGET_TBB) + +if (ANDROID) + set(TBB_INSTALL_DIR C:/tbb-2018/built) + set(TBB_LIBRARY ${HIFI_ANDROID_PRECOMPILED}/libtbb.so CACHE FILEPATH "TBB library location") + set(TBB_MALLOC_LIBRARY ${HIFI_ANDROID_PRECOMPILED}/libtbbmalloc.so CACHE FILEPATH "TBB malloc library location") + set(TBB_INCLUDE_DIRS ${TBB_INSTALL_DIR}/include CACHE TYPE "List of tbb include directories" CACHE FILEPATH "TBB includes location") + set(TBB_LIBRARIES ${TBB_LIBRARY} ${TBB_MALLOC_LIBRARY}) +else() + add_dependency_external_projects(tbb) + find_package(TBB REQUIRED) +endif() + +target_link_libraries(${TARGET_NAME} ${TBB_LIBRARIES}) +target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${TBB_INCLUDE_DIRS}) + +endmacro() diff --git a/cmake/modules/FindDraco.cmake b/cmake/modules/FindDraco.cmake new file mode 100644 index 0000000000..342797b62e --- /dev/null +++ b/cmake/modules/FindDraco.cmake @@ -0,0 +1,30 @@ +# +# FindDraco.cmake +# +# Try to find Draco libraries and include path. +# Once done this will define +# +# DRACO_FOUND +# DRACO_INCLUDE_DIRS +# DRACO_LIBRARY +# DRACO_ENCODER_LIBRARY +# DRACO_DECODER_LIBRARY +# +# Created on 8/8/2017 by Stephen Birarda +# 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("${MACRO_DIR}/HifiLibrarySearchHints.cmake") +hifi_library_search_hints("draco") + +find_path(DRACO_INCLUDE_DIRS draco/core/draco_types.h PATH_SUFFIXES include/draco/src include HINTS ${DRACO_SEARCH_DIRS}) + +find_library(DRACO_LIBRARY draco PATH_SUFFIXES "lib" HINTS ${DRACO_SEARCH_DIRS}) +find_library(DRACO_ENCODER_LIBRARY draco PATH_SUFFIXES "lib" HINTS ${DRACO_SEARCH_DIRS}) +find_library(DRACO_DECODER_LIBRARY draco PATH_SUFFIXES "lib" HINTS ${DRACO_SEARCH_DIRS}) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(DRACO DEFAULT_MSG DRACO_INCLUDE_DIRS DRACO_LIBRARY DRACO_ENCODER_LIBRARY DRACO_DECODER_LIBRARY) diff --git a/cmake/modules/FindFBX.cmake b/cmake/modules/FindFBX.cmake deleted file mode 100644 index 2e84d1ea19..0000000000 --- a/cmake/modules/FindFBX.cmake +++ /dev/null @@ -1,117 +0,0 @@ -# Locate the FBX SDK -# -# Defines the following variables: -# -# FBX_FOUND - Found the FBX SDK -# FBX_VERSION - Version number -# FBX_INCLUDE_DIRS - Include directories -# FBX_LIBRARIES - The libraries to link to -# -# Accepts the following variables as input: -# -# FBX_VERSION - as a CMake variable, e.g. 2017.0.1 -# FBX_ROOT - (as a CMake or environment variable) -# The root directory of the FBX SDK install - -# adapted from https://github.com/ufz-vislab/VtkFbxConverter/blob/master/FindFBX.cmake -# which uses the MIT license (https://github.com/ufz-vislab/VtkFbxConverter/blob/master/LICENSE.txt) - -if (NOT FBX_VERSION) - if (WIN32) - set(FBX_VERSION 2017.1) - else() - set(FBX_VERSION 2017.0.1) - endif() -endif() - -string(REGEX REPLACE "^([0-9]+).*$" "\\1" FBX_VERSION_MAJOR "${FBX_VERSION}") -string(REGEX REPLACE "^[0-9]+\\.([0-9]+).*$" "\\1" FBX_VERSION_MINOR "${FBX_VERSION}") -string(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.([0-9]+).*$" "\\1" FBX_VERSION_PATCH "${FBX_VERSION}") - -set(FBX_MAC_LOCATIONS "/Applications/Autodesk/FBX\ SDK/${FBX_VERSION}") -set(FBX_LINUX_LOCATIONS "/usr/local/lib/gcc4/x64/debug/") - -if (WIN32) - string(REGEX REPLACE "\\\\" "/" WIN_PROGRAM_FILES_X64_DIRECTORY $ENV{ProgramW6432}) -endif() - -set(FBX_WIN_LOCATIONS "${WIN_PROGRAM_FILES_X64_DIRECTORY}/Autodesk/FBX/FBX SDK/${FBX_VERSION}") - -set(FBX_SEARCH_LOCATIONS $ENV{FBX_ROOT} ${FBX_ROOT} ${FBX_MAC_LOCATIONS} ${FBX_WIN_LOCATIONS} ${FBX_LINUX_LOCATIONS}) - -function(_fbx_append_debugs _endvar _library) - if (${_library} AND ${_library}_DEBUG) - set(_output optimized ${${_library}} debug ${${_library}_DEBUG}) - else() - set(_output ${${_library}}) - endif() - - set(${_endvar} ${_output} PARENT_SCOPE) -endfunction() - -if (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang") - set(fbx_compiler clang) -elseif (${CMAKE_CXX_COMPILER_ID} MATCHES "GNU") - set(fbx_compiler gcc4) -endif() - -function(_fbx_find_library _name _lib _suffix) - if (MSVC12) - set(VS_PREFIX vs2013) - endif() - - if (MSVC11) - set(VS_PREFIX vs2012) - endif() - - if (MSVC10) - set(VS_PREFIX vs2010) - endif() - - if (MSVC90) - set(VS_PREFIX vs2008) - endif() - - find_library(${_name} - NAMES ${_lib} - HINTS ${FBX_SEARCH_LOCATIONS} - PATH_SUFFIXES lib/${fbx_compiler}/${_suffix} lib/${fbx_compiler}/ub/${_suffix} lib/${VS_PREFIX}/x64/${_suffix} - ) - - mark_as_advanced(${_name}) -endfunction() - -find_path(FBX_INCLUDE_DIR fbxsdk.h - PATHS ${FBX_SEARCH_LOCATIONS} - PATH_SUFFIXES include -) -mark_as_advanced(FBX_INCLUDE_DIR) - -if (WIN32) - _fbx_find_library(FBX_LIBRARY libfbxsdk-md release) - _fbx_find_library(FBX_LIBRARY_DEBUG libfbxsdk-md debug) -elseif (APPLE) - find_library(CARBON NAMES Carbon) - find_library(SYSTEM_CONFIGURATION NAMES SystemConfiguration) - _fbx_find_library(FBX_LIBRARY libfbxsdk.a release) - _fbx_find_library(FBX_LIBRARY_DEBUG libfbxsdk.a debug) -else () - _fbx_find_library(FBX_LIBRARY libfbxsdk.a release) -endif() - -include(FindPackageHandleStandardArgs) -FIND_PACKAGE_HANDLE_STANDARD_ARGS(FBX DEFAULT_MSG FBX_LIBRARY FBX_INCLUDE_DIR) - -if (FBX_FOUND) - set(FBX_INCLUDE_DIRS ${FBX_INCLUDE_DIR}) - _fbx_append_debugs(FBX_LIBRARIES FBX_LIBRARY) - add_definitions(-DFBXSDK_NEW_API) - - if (WIN32) - add_definitions(-DK_PLUGIN -DK_FBXSDK -DK_NODLL) - set(CMAKE_EXE_LINKER_FLAGS /NODEFAULTLIB:\"LIBCMT\") - set(FBX_LIBRARIES ${FBX_LIBRARIES} Wininet.lib) - elseif (APPLE) - set(FBX_LIBRARIES ${FBX_LIBRARIES} ${CARBON} ${SYSTEM_CONFIGURATION}) - endif() -endif() diff --git a/cmake/modules/FindOpenSSL.cmake b/cmake/modules/FindOpenSSL.cmake index 69b6c367ca..0619c4d587 100644 --- a/cmake/modules/FindOpenSSL.cmake +++ b/cmake/modules/FindOpenSSL.cmake @@ -34,26 +34,11 @@ if (UNIX) endif () if (WIN32) - - file(TO_CMAKE_PATH "$ENV{PROGRAMFILES}" _programfiles) - - if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") - # http://www.slproweb.com/products/Win32OpenSSL.html - set(_OPENSSL_ROOT_HINTS ${OPENSSL_ROOT_DIR} $ENV{OPENSSL_ROOT_DIR} $ENV{HIFI_LIB_DIR}/openssl - "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (64-bit)_is1;Inno Setup: App Path]" - ) - set(_OPENSSL_ROOT_PATHS "${_programfiles}/OpenSSL" "${_programfiles}/OpenSSL-Win64" "C:/OpenSSL/" "C:/OpenSSL-Win64/") + if (("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")) + set(_OPENSSL_ROOT_HINTS_AND_PATHS $ENV{VCPKG_ROOT}/installed/x64-windows) else() - # http://www.slproweb.com/products/Win32OpenSSL.html - set(_OPENSSL_ROOT_HINTS ${OPENSSL_ROOT_DIR} $ENV{OPENSSL_ROOT_DIR} $ENV{HIFI_LIB_DIR}/openssl - "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (32-bit)_is1;Inno Setup: App Path]" - ) - set(_OPENSSL_ROOT_PATHS "${_programfiles}/OpenSSL" "${_programfiles}/OpenSSL-Win32" "C:/OpenSSL/" "C:/OpenSSL-Win32/") + set(_OPENSSL_ROOT_HINTS_AND_PATHS $ENV{VCPKG_ROOT}/installed/x86-windows) endif() - - unset(_programfiles) - set(_OPENSSL_ROOT_HINTS_AND_PATHS HINTS ${_OPENSSL_ROOT_HINTS} PATHS ${_OPENSSL_ROOT_PATHS}) - else () include("${MACRO_DIR}/HifiLibrarySearchHints.cmake") hifi_library_search_hints("openssl") @@ -67,48 +52,15 @@ find_path(OPENSSL_INCLUDE_DIR NAMES openssl/ssl.h HINTS ${_OPENSSL_ROOT_HINTS_AN if (WIN32 AND NOT CYGWIN) if (MSVC) - - # In Visual C++ naming convention each of these four kinds of Windows libraries has it's standard suffix: - # * MD for dynamic-release - # * MDd for dynamic-debug - # * MT for static-release - # * MTd for static-debug - - # Implementation details: - # We are using the libraries located in the VC subdir instead of the parent directory eventhough : - # libeay32MD.lib is identical to ../libeay32.lib, and - # ssleay32MD.lib is identical to ../ssleay32.lib - - # The Kitware FindOpenSSL module has been modified here by High Fidelity to look specifically for static libraries - - find_library(LIB_EAY_DEBUG NAMES libeay32MTd - ${_OPENSSL_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES "lib/VC/static" - ) - - find_library(LIB_EAY_RELEASE NAMES libeay32MT - ${_OPENSSL_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES "lib/VC/static" - ) - - find_library(SSL_EAY_DEBUG NAMES ssleay32MTd - ${_OPENSSL_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES "lib/VC/static" - ) - - find_library(SSL_EAY_RELEASE NAMES ssleay32MT - ${_OPENSSL_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES "lib/VC/static" - ) - - set(LIB_EAY_LIBRARY_DEBUG "${LIB_EAY_DEBUG}") - set(LIB_EAY_LIBRARY_RELEASE "${LIB_EAY_RELEASE}") - set(SSL_EAY_LIBRARY_DEBUG "${SSL_EAY_DEBUG}") - set(SSL_EAY_LIBRARY_RELEASE "${SSL_EAY_RELEASE}") + # Using vcpkg builds of openssl + find_library(LIB_EAY_LIBRARY_RELEASE NAMES libeay32 HINTS ${_OPENSSL_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES "lib") + find_library(SSL_EAY_LIBRARY_RELEASE NAMES ssleay32 HINTS ${_OPENSSL_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES "lib") include(SelectLibraryConfigurations) select_library_configurations(LIB_EAY) select_library_configurations(SSL_EAY) - set(OPENSSL_LIBRARIES ${SSL_EAY_LIBRARY} ${LIB_EAY_LIBRARY}) - - find_path(OPENSSL_DLL_PATH NAMES ssleay32.dll PATH_SUFFIXES "bin" ${_OPENSSL_ROOT_HINTS_AND_PATHS}) + find_path(OPENSSL_DLL_PATH NAMES ssleay32.dll PATH_SUFFIXES "bin" HINTS ${_OPENSSL_ROOT_HINTS_AND_PATHS} NO_DEFAULT_PATH) endif() else() diff --git a/cmake/modules/FindTBB.cmake b/cmake/modules/FindTBB.cmake index 1ccdcd792d..12d4deec36 100644 --- a/cmake/modules/FindTBB.cmake +++ b/cmake/modules/FindTBB.cmake @@ -1,6 +1,6 @@ -# +# # FindTBB.cmake -# +# # Try to find the Intel Threading Building Blocks library # # You can provide a TBB_ROOT_DIR which contains lib and include directories @@ -16,7 +16,7 @@ # # Distributed under the Apache License, Version 2.0. # See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -# +# include("${MACRO_DIR}/HifiLibrarySearchHints.cmake") hifi_library_search_hints("tbb") @@ -34,31 +34,43 @@ elseif (UNIX AND NOT ANDROID) else() set(_TBB_ARCH_DIR "ia32") endif() - + execute_process( COMMAND ${CMAKE_C_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION ) - - if (GCC_VERSION VERSION_GREATER 4.4 OR GCC_VERSION VERSION_EQUAL 4.4) + + if (GCC_VERSION VERSION_GREATER 4.7 OR GCC_VERSION VERSION_EQUAL 4.7) + set(_TBB_LIB_DIR "lib/${_TBB_ARCH_DIR}/gcc4.7") + elseif (GCC_VERSION VERSION_GREATER 4.4 OR GCC_VERSION VERSION_EQUAL 4.4) set(_TBB_LIB_DIR "lib/${_TBB_ARCH_DIR}/gcc4.4") elseif (GCC_VERSION VERSION_GREATER 4.1 OR GCC_VERSION VERSION_EQUAL 4.1) set(_TBB_LIB_DIR "lib/${_TBB_ARCH_DIR}/gcc4.1") else () message(FATAL_ERROR "Could not find a compatible version of Threading Building Blocks library for your compiler.") endif () - + elseif (WIN32) if(CMAKE_SIZEOF_VOID_P EQUAL 8) set(_TBB_ARCH_DIR "intel64") else() set(_TBB_ARCH_DIR "ia32") endif() - - set(_TBB_LIB_DIR "lib/${_TBB_ARCH_DIR}/vc12") - - find_path(TBB_DLL_PATH tbb_debug.dll PATH_SUFFIXES "bin/${_TBB_ARCH_DIR}/vc12" HINTS ${TBB_SEARCH_DIRS}) - + + if (MSVC_VERSION GREATER_EQUAL 1900) + set(_TBB_MSVC_DIR "vc14") + elseif (MSVC_VERSION GREATER_EQUAL 1800) + set(_TBB_MSVC_DIR "vc12") + elseif (MSVC_VERSION GREATER_EQUAL 1700) + set(_TBB_MSVC_DIR "vc11") + else() + message(FATAL_ERROR "MSVC ${MSVC_VERSION} not supported by Intel TBB") + endif() + + set(_TBB_LIB_DIR "lib/${_TBB_ARCH_DIR}/${_TBB_MSVC_DIR}") + + find_path(TBB_DLL_PATH tbb_debug.dll PATH_SUFFIXES "bin/${_TBB_ARCH_DIR}/${_TBB_MSVC_DIR}" HINTS ${TBB_SEARCH_DIRS}) + elseif (ANDROID) set(_TBB_DEFAULT_INSTALL_DIR "/tbb") set(_TBB_LIB_NAME "tbb") diff --git a/cmake/templates/CPackProperties.cmake.in b/cmake/templates/CPackProperties.cmake.in index c1e3d9d773..b91d78f628 100644 --- a/cmake/templates/CPackProperties.cmake.in +++ b/cmake/templates/CPackProperties.cmake.in @@ -40,6 +40,7 @@ set(CONSOLE_DESKTOP_SHORTCUT_REG_KEY "@CONSOLE_DESKTOP_SHORTCUT_REG_KEY@") set(CONSOLE_STARTUP_REG_KEY "@CONSOLE_STARTUP_REG_KEY@") set(SERVER_LAUNCH_NOW_REG_KEY "@SERVER_LAUNCH_NOW_REG_KEY@") set(CLIENT_LAUNCH_NOW_REG_KEY "@CLIENT_LAUNCH_NOW_REG_KEY@") +set(CUSTOM_INSTALL_REG_KEY "@CUSTOM_INSTALL_REG_KEY@") set(INSTALLER_HEADER_IMAGE "@INSTALLER_HEADER_IMAGE@") set(UNINSTALLER_HEADER_IMAGE "@UNINSTALLER_HEADER_IMAGE@") set(ADD_REMOVE_ICON_PATH "@ADD_REMOVE_ICON_PATH@") diff --git a/cmake/templates/FixupBundlePostBuild.cmake.in b/cmake/templates/FixupBundlePostBuild.cmake.in index 57d1fd787f..d4726884c2 100644 --- a/cmake/templates/FixupBundlePostBuild.cmake.in +++ b/cmake/templates/FixupBundlePostBuild.cmake.in @@ -11,34 +11,28 @@ include(BundleUtilities) -# replace copy_resolved_item_into_bundle -# -# The official version of copy_resolved_item_into_bundle will print out a "warning:" when -# the resolved item matches the resolved embedded item. This not not really an issue that -# should rise to the level of a "warning" so we replace this message with a "status:" -# -function(copy_resolved_item_into_bundle resolved_item resolved_embedded_item) - if (WIN32) - # ignore case on Windows - string(TOLOWER "${resolved_item}" resolved_item_compare) - string(TOLOWER "${resolved_embedded_item}" resolved_embedded_item_compare) - else() - set(resolved_item_compare "${resolved_item}") - set(resolved_embedded_item_compare "${resolved_embedded_item}") +function(gp_resolved_file_type_override resolved_file type_var) + if( file MATCHES ".*VCRUNTIME140.*" ) + set(type "system" PARENT_SCOPE) endif() - - if ("${resolved_item_compare}" STREQUAL "${resolved_embedded_item_compare}") - # this is our only change from the original version - message(STATUS "status: resolved_item == resolved_embedded_item - not copying...") - else() - #message(STATUS "copying COMMAND ${CMAKE_COMMAND} -E copy ${resolved_item} ${resolved_embedded_item}") - execute_process(COMMAND ${CMAKE_COMMAND} -E copy "${resolved_item}" "${resolved_embedded_item}") - if(UNIX AND NOT APPLE) - file(RPATH_REMOVE FILE "${resolved_embedded_item}") - endif() + if( file MATCHES ".*concrt140.*" ) + set(type "system" PARENT_SCOPE) + endif() + if( file MATCHES ".*msvcp140.*" ) + set(type "system" PARENT_SCOPE) + endif() + if( file MATCHES ".*vcruntime140.*" ) + set(type "system" PARENT_SCOPE) + endif() + if( file MATCHES ".*api-ms-win-crt-conio.*" ) + set(type "system" PARENT_SCOPE) + endif() + if( file MATCHES ".*api-ms-win-core-winrt.*" ) + set(type "system" PARENT_SCOPE) endif() endfunction() + message(STATUS "FIXUP_LIBS for fixup_bundle called for bundle ${BUNDLE_EXECUTABLE} are @FIXUP_LIBS@") message(STATUS "Scanning for plugins from ${BUNDLE_PLUGIN_DIR}") @@ -52,3 +46,4 @@ endif() file(GLOB EXTRA_PLUGINS "${BUNDLE_PLUGIN_DIR}/*.${PLUGIN_EXTENSION}") fixup_bundle("${BUNDLE_EXECUTABLE}" "${EXTRA_PLUGINS}" "@FIXUP_LIBS@") + diff --git a/cmake/templates/NSIS.template.in b/cmake/templates/NSIS.template.in index 710fd81316..8abb202bd4 100644 --- a/cmake/templates/NSIS.template.in +++ b/cmake/templates/NSIS.template.in @@ -345,14 +345,21 @@ SectionEnd !insertmacro MUI_PAGE_WELCOME !insertmacro MUI_PAGE_LICENSE "@CPACK_RESOURCE_FILE_LICENSE@" + + Page custom InstallTypesPage ReadInstallTypes + + !define MUI_PAGE_CUSTOMFUNCTION_PRE AbortFunction !insertmacro MUI_PAGE_DIRECTORY ;Start Menu Folder Page Configuration !define MUI_STARTMENUPAGE_REGISTRY_ROOT "HKLM" !define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" !define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder" + + !define MUI_PAGE_CUSTOMFUNCTION_PRE AbortFunction !insertmacro MUI_PAGE_STARTMENU Application $STARTMENU_FOLDER + !define MUI_PAGE_CUSTOMFUNCTION_PRE AbortFunction @CPACK_NSIS_PAGE_COMPONENTS@ Page custom PostInstallOptionsPage ReadPostInstallOptions @@ -438,12 +445,18 @@ Var DesktopServerCheckbox Var ServerStartupCheckbox Var LaunchServerNowCheckbox Var LaunchClientNowCheckbox +Var CleanInstallCheckbox Var CurrentOffset Var OffsetUnits Var CopyFromProductionCheckbox +Var ExpressInstallRadioButton +Var CustomInstallRadioButton +Var InstallTypeDialog +Var Express +Var CustomInstallTemporaryState -!macro SetPostInstallOption Checkbox OptionName Default - ; reads the value for the given post install option to the registry +!macro SetInstallOption Checkbox OptionName Default + ; reads the value for the given install option to the registry ReadRegStr $0 HKLM "@REGISTRY_HKLM_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@\@POST_INSTALL_OPTIONS_REG_GROUP@" "${OptionName}" ${If} $0 == "NO" @@ -458,6 +471,65 @@ Var CopyFromProductionCheckbox ${EndIf} !macroend +Function InstallTypesPage + !insertmacro MUI_HEADER_TEXT "Choose Installation Type" "Express or Custom Install" + + nsDialogs::Create 1018 + Pop $InstallTypeDialog + + ${If} $InstallTypeDialog == error + Abort + ${EndIf} + + StrCpy $CurrentOffset 0 + StrCpy $OffsetUnits u + StrCpy $Express "0" + + ${NSD_CreateRadioButton} 30% $CurrentOffset$OffsetUnits 100% 10u "Express Install (Recommended)"; $\nInstalls High Fidelity Interface and High Fidelity Sandbox" + pop $ExpressInstallRadioButton + ${NSD_OnClick} $ExpressInstallRadioButton ChangeExpressLabel + IntOp $CurrentOffset $CurrentOffset + 15 + + ${NSD_CreateRadiobutton} 30% $CurrentOffset$OffsetUnits 100% 10u "Custom Install (Advanced)" + pop $CustomInstallRadioButton + ${NSD_OnClick} $CustomInstallRadioButton ChangeCustomLabel + + ; check install type from the registry, express install by default + !insertmacro SetInstallOption $CustomInstallRadioButton @CUSTOM_INSTALL_REG_KEY@ ${BST_UNCHECKED} + + ; set the express install value based on the custom install value from registry + ${NSD_GetState} $CustomInstallRadioButton $CustomInstallTemporaryState + + ${If} $CustomInstallTemporaryState == ${BST_UNCHECKED} + ${NSD_Check} $ExpressInstallRadioButton + ${EndIf} + + Call ChangeExpressLabel + + nsDialogs::Show +FunctionEnd + +Function ChangeExpressLabel + Push $R1 + GetDlgItem $R1 $HWNDPARENT 1 + SendMessage $R1 ${WM_SETTEXT} 0 "STR:Install" + Pop $R1 +FunctionEnd + +Function ChangeCustomLabel + Push $R1 + GetDlgItem $R1 $HWNDPARENT 1 + SendMessage $R1 ${WM_SETTEXT} 0 "STR:Next >" + Pop $R1 +FunctionEnd + +Function AbortFunction + ; Check if Express is set, if so, abort the post install options page + StrCmp $Express "1" 0 end + Abort + end: +FunctionEnd + Function PostInstallOptionsPage !insertmacro MUI_HEADER_TEXT "Setup Options" "" @@ -468,6 +540,11 @@ Function PostInstallOptionsPage Abort ${EndIf} + ; Check if Express is set, if so, abort the post install options page + StrCmp $Express "1" 0 end + Abort + end: + StrCpy $CurrentOffset 0 StrCpy $OffsetUnits u @@ -477,25 +554,16 @@ Function PostInstallOptionsPage IntOp $CurrentOffset $CurrentOffset + 15 ; set the checkbox state depending on what is present in the registry - !insertmacro SetPostInstallOption $DesktopClientCheckbox @CLIENT_DESKTOP_SHORTCUT_REG_KEY@ ${BST_CHECKED} + !insertmacro SetInstallOption $DesktopClientCheckbox @CLIENT_DESKTOP_SHORTCUT_REG_KEY@ ${BST_CHECKED} ${EndIf} ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Create a desktop shortcut for @CONSOLE_HF_SHORTCUT_NAME@" Pop $DesktopServerCheckbox - - ; set the checkbox state depending on what is present in the registry - !insertmacro SetPostInstallOption $DesktopServerCheckbox @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ ${BST_UNCHECKED} - IntOp $CurrentOffset $CurrentOffset + 15 - ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch @CONSOLE_HF_SHORTCUT_NAME@ on startup" - Pop $ServerStartupCheckbox - ; set the checkbox state depending on what is present in the registry - !insertmacro SetPostInstallOption $ServerStartupCheckbox @CONSOLE_STARTUP_REG_KEY@ ${BST_CHECKED} - - IntOp $CurrentOffset $CurrentOffset + 15 + !insertmacro SetInstallOption $DesktopServerCheckbox @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ ${BST_UNCHECKED} ${EndIf} ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} @@ -503,7 +571,7 @@ Function PostInstallOptionsPage Pop $LaunchServerNowCheckbox ; set the checkbox state depending on what is present in the registry - !insertmacro SetPostInstallOption $LaunchServerNowCheckbox @SERVER_LAUNCH_NOW_REG_KEY@ ${BST_CHECKED} + !insertmacro SetInstallOption $LaunchServerNowCheckbox @SERVER_LAUNCH_NOW_REG_KEY@ ${BST_CHECKED} ${StrContains} $substringResult "/forceNoLaunchServer" $CMDLINE ${IfNot} $substringResult == "" ${NSD_SetState} $LaunchServerNowCheckbox ${BST_UNCHECKED} @@ -513,15 +581,31 @@ Function PostInstallOptionsPage ${EndIf} ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} - ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch @INTERFACE_HF_SHORTCUT_NAME@ after install" - Pop $LaunchClientNowCheckbox + ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch @INTERFACE_HF_SHORTCUT_NAME@ after install" + Pop $LaunchClientNowCheckbox + IntOp $CurrentOffset $CurrentOffset + 30 - ; set the checkbox state depending on what is present in the registry - !insertmacro SetPostInstallOption $LaunchClientNowCheckbox @CLIENT_LAUNCH_NOW_REG_KEY@ ${BST_CHECKED} - ${StrContains} $substringResult "/forceNoLaunchClient" $CMDLINE - ${IfNot} $substringResult == "" - ${NSD_SetState} $LaunchClientNowCheckbox ${BST_UNCHECKED} - ${EndIf} + ; set the checkbox state depending on what is present in the registry + !insertmacro SetInstallOption $LaunchClientNowCheckbox @CLIENT_LAUNCH_NOW_REG_KEY@ ${BST_CHECKED} + ${StrContains} $substringResult "/forceNoLaunchClient" $CMDLINE + ${IfNot} $substringResult == "" + ${NSD_SetState} $LaunchClientNowCheckbox ${BST_UNCHECKED} + ${EndIf} + ${EndIf} + + ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} + ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch @CONSOLE_HF_SHORTCUT_NAME@ on startup" + Pop $ServerStartupCheckbox + IntOp $CurrentOffset $CurrentOffset + 15 + + ; set the checkbox state depending on what is present in the registry + !insertmacro SetInstallOption $ServerStartupCheckbox @CONSOLE_STARTUP_REG_KEY@ ${BST_CHECKED} + ${EndIf} + + ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} + ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Perform a clean install (Delete older settings and content)" + Pop $CleanInstallCheckbox + IntOp $CurrentOffset $CurrentOffset + 15 ${EndIf} ${If} @PR_BUILD@ == 1 @@ -541,14 +625,14 @@ Function PostInstallOptionsPage ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Copy settings and content from production install" Pop $CopyFromProductionCheckbox - ${NSD_SetState} $CopyFromProductionCheckbox ${BST_CHECKED} + ${NSD_SetState} $CopyFromProductionCheckbox ${BST_UNCHECKED} ${EndIf} nsDialogs::Show FunctionEnd -!macro WritePostInstallOption OptionName Option - ; writes the value for the given post install option to the registry +!macro WriteInstallOption OptionName Option + ; writes the value for the given install option to the registry WriteRegStr HKLM "@REGISTRY_HKLM_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@\@POST_INSTALL_OPTIONS_REG_GROUP@" "${OptionName}" ${Option} !macroend @@ -558,6 +642,35 @@ Var ServerStartupState Var LaunchServerNowState Var LaunchClientNowState Var CopyFromProductionState +Var CleanInstallState +Var ExpressInstallState +Var CustomInstallState + +Function ReadInstallTypes + ; check if the user asked for express/custom install + ${NSD_GetState} $ExpressInstallRadioButton $ExpressInstallState + ${NSD_GetState} $CustomInstallRadioButton $CustomInstallState + + ${If} $ExpressInstallState == ${BST_CHECKED} + StrCpy $Express "1" + + StrCpy $DesktopClientState ${BST_CHECKED} + StrCpy $ServerStartupState ${BST_CHECKED} + StrCpy $LaunchServerNowState ${BST_CHECKED} + StrCpy $LaunchClientNowState ${BST_CHECKED} + StrCpy $CleanInstallState ${BST_UNCHECKED} + StrCpy $DesktopServerState ${BST_UNCHECKED} + + ${If} @PR_BUILD@ == 1 + StrCpy $CopyFromProductionState ${BST_UNCHECKED} + ${EndIf} + + !insertmacro WriteInstallOption "@CUSTOM_INSTALL_REG_KEY@" NO + ${Else} + !insertmacro WriteInstallOption "@CUSTOM_INSTALL_REG_KEY@" YES + ${EndIf} + +FunctionEnd Function ReadPostInstallOptions ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} @@ -579,13 +692,18 @@ Function ReadPostInstallOptions ${EndIf} ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} - ; check if we need to launch the server post-install - ${NSD_GetState} $LaunchServerNowCheckbox $LaunchServerNowState + ; check if we need to launch the server post-install + ${NSD_GetState} $LaunchServerNowCheckbox $LaunchServerNowState ${EndIf} ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} - ; check if we need to launch the client post-install - ${NSD_GetState} $LaunchClientNowCheckbox $LaunchClientNowState + ; check if we need to launch the client post-install + ${NSD_GetState} $LaunchClientNowCheckbox $LaunchClientNowState + ${EndIf} + + ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} + ; check if the user asked for a clean install + ${NSD_GetState} $CleanInstallCheckbox $CleanInstallState ${EndIf} FunctionEnd @@ -594,9 +712,9 @@ Function HandlePostInstallOptions ; check if the user asked for a desktop shortcut to High Fidelity ${If} $DesktopClientState == ${BST_CHECKED} CreateShortCut "$DESKTOP\@INTERFACE_HF_SHORTCUT_NAME@.lnk" "$INSTDIR\@INTERFACE_WIN_EXEC_NAME@" - !insertmacro WritePostInstallOption "@CLIENT_DESKTOP_SHORTCUT_REG_KEY@" YES + !insertmacro WriteInstallOption "@CLIENT_DESKTOP_SHORTCUT_REG_KEY@" YES ${Else} - !insertmacro WritePostInstallOption @CLIENT_DESKTOP_SHORTCUT_REG_KEY@ NO + !insertmacro WriteInstallOption @CLIENT_DESKTOP_SHORTCUT_REG_KEY@ NO ${EndIf} ${EndIf} @@ -605,11 +723,12 @@ Function HandlePostInstallOptions ; check if the user asked for a desktop shortcut to Sandbox ${If} $DesktopServerState == ${BST_CHECKED} CreateShortCut "$DESKTOP\@CONSOLE_HF_SHORTCUT_NAME@.lnk" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@" - !insertmacro WritePostInstallOption @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ YES + !insertmacro WriteInstallOption @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ YES ${Else} - !insertmacro WritePostInstallOption @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ NO + !insertmacro WriteInstallOption @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ NO ${EndIf} + ; check if the user asked to have Sandbox launched every startup ${If} $ServerStartupState == ${BST_CHECKED} ; in case we added a shortcut in the global context, pull that now @@ -623,9 +742,18 @@ Function HandlePostInstallOptions ; reset the shell var context back SetShellVarContext all - !insertmacro WritePostInstallOption @CONSOLE_STARTUP_REG_KEY@ YES + !insertmacro WriteInstallOption @CONSOLE_STARTUP_REG_KEY@ YES ${Else} - !insertmacro WritePostInstallOption @CONSOLE_STARTUP_REG_KEY@ NO + !insertmacro WriteInstallOption @CONSOLE_STARTUP_REG_KEY@ NO + ${EndIf} + ${EndIf} + + ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} + ; check if the user asked for a clean install + ${If} $CleanInstallState == ${BST_CHECKED} + SetShellVarContext current + RMDir /r "$APPDATA\@BUILD_ORGANIZATION@" + RMDir /r "$LOCALAPPDATA\@BUILD_ORGANIZATION@" ${EndIf} ${EndIf} @@ -658,28 +786,28 @@ Function HandlePostInstallOptions ${EndIf} ${If} $LaunchServerNowState == ${BST_CHECKED} - !insertmacro WritePostInstallOption @SERVER_LAUNCH_NOW_REG_KEY@ YES + !insertmacro WriteInstallOption @SERVER_LAUNCH_NOW_REG_KEY@ YES ; both launches use the explorer trick in case the user has elevated permissions for the installer ${If} $LaunchClientNowState == ${BST_CHECKED} - !insertmacro WritePostInstallOption @CLIENT_LAUNCH_NOW_REG_KEY@ YES + !insertmacro WriteInstallOption @CLIENT_LAUNCH_NOW_REG_KEY@ YES ; create shortcut with ARGUMENTS CreateShortCut "$TEMP\SandboxShortcut.lnk" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@" "-- --launchInterface" Exec '"$WINDIR\explorer.exe" "$TEMP\SandboxShortcut.lnk"' ${Else} - !insertmacro WritePostInstallOption @CLIENT_LAUNCH_NOW_REG_KEY@ NO + !insertmacro WriteInstallOption @CLIENT_LAUNCH_NOW_REG_KEY@ NO Exec '"$WINDIR\explorer.exe" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@"' ${EndIf} ${Else} - !insertmacro WritePostInstallOption @SERVER_LAUNCH_NOW_REG_KEY@ NO + !insertmacro WriteInstallOption @SERVER_LAUNCH_NOW_REG_KEY@ NO ; launch uses the explorer trick in case the user has elevated permissions for the installer ${If} $LaunchClientNowState == ${BST_CHECKED} - !insertmacro WritePostInstallOption @CLIENT_LAUNCH_NOW_REG_KEY@ YES + !insertmacro WriteInstallOption @CLIENT_LAUNCH_NOW_REG_KEY@ YES Exec '"$WINDIR\explorer.exe" "$INSTDIR\@INTERFACE_WIN_EXEC_NAME@"' ${Else} - !insertmacro WritePostInstallOption @CLIENT_LAUNCH_NOW_REG_KEY@ NO + !insertmacro WriteInstallOption @CLIENT_LAUNCH_NOW_REG_KEY@ NO ${EndIf} ${EndIf} @@ -708,6 +836,9 @@ Section "-Core installation" Delete "$INSTDIR\ui_resources_200_percent.pak" Delete "$INSTDIR\vccorlib120.dll" Delete "$INSTDIR\version" + Delete "$INSTDIR\msvcr140.dll" + Delete "$INSTDIR\msvcp140.dll" + Delete "$INSTDIR\vcruntime140.dll" Delete "$INSTDIR\xinput1_3.dll" ; Delete old desktop shortcuts before they were renamed during Sandbox rename @@ -726,6 +857,8 @@ Section "-Core installation" ; Rename the incorrectly cased Raleway font Rename "$INSTDIR\resources\qml\styles-uit\RalewaySemibold.qml" "$INSTDIR\resources\qml\styles-uit\RalewaySemiBold.qml" + ExecWait "$INSTDIR\vcredist_x64.exe /install /q /norestart" + ; Remove the Old Interface directory and vcredist_x64.exe (from installs prior to Server Console) RMDir /r "$INSTDIR\Interface" Delete "$INSTDIR\vcredist_x64.exe" @@ -826,7 +959,7 @@ Section "-Core installation" !insertmacro MUI_STARTMENU_WRITE_END -@CPACK_NSIS_EXTRA_INSTALL_COMMANDS@ + @CPACK_NSIS_EXTRA_INSTALL_COMMANDS@ ; Handle whichever post install options were set Call HandlePostInstallOptions @@ -845,9 +978,18 @@ SectionEnd ${If} $R0 == 0 ; the process is running, ask the user to close it + + ${If} "${displayName}" == "@CONSOLE_DISPLAY_NAME@" MessageBox MB_RETRYCANCEL|MB_ICONEXCLAMATION \ - "${displayName} cannot be ${action} while ${displayName} is running.$\r$\nPlease close it and click Retry to continue." \ + "${displayName} cannot be ${action} while ${displayName} is running.$\r$\nPlease close it in the system tray and click Retry to continue." \ /SD IDCANCEL IDRETRY Prompt_${UniqueID} IDCANCEL 0 + ${EndIf} + + ${If} "${displayName}" == "@INTERFACE_DISPLAY_NAME@" + MessageBox MB_RETRYCANCEL|MB_ICONEXCLAMATION \ + "${displayName} cannot be ${action} while ${displayName} is running.$\r$\nPlease close it in the task bar and click Retry to continue." \ + /SD IDCANCEL IDRETRY Prompt_${UniqueID} IDCANCEL 0 + ${EndIf} ; If the user decided to cancel, stop the current installer/uninstaller Abort diff --git a/cmake/templates/resources.qrc.in b/cmake/templates/resources.qrc.in new file mode 100644 index 0000000000..6466b9ec51 --- /dev/null +++ b/cmake/templates/resources.qrc.in @@ -0,0 +1,5 @@ + + +@QRC_CONTENTS@ + + diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index bc67a31c02..d55da6c848 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -1,6 +1,12 @@ { - "version": 1.7, + "version": 2.0, "settings": [ + { + "name": "label", + "label": "Label", + "settings": [ + ] + }, { "name": "metaverse", "label": "Metaverse / Networking", @@ -14,7 +20,8 @@ { "name": "id", "label": "Domain ID", - "help": "This is your High Fidelity domain ID. If you do not want your domain to be registered in the High Fidelity metaverse you can leave this blank." + "help": "This is your High Fidelity domain ID. If you do not want your domain to be registered in the High Fidelity metaverse you can leave this blank.", + "advanced": true }, { "name": "automatic_networking", @@ -50,6 +57,7 @@ { "label": "Places / Paths", "html_id": "places_paths", + "restart": false, "settings": [ { "name": "paths", @@ -75,16 +83,19 @@ { "name": "descriptors", "label": "Description", + "restart": false, "help": "This data will be queryable from your server. It may be collected by High Fidelity and used to share your domain with others.", "settings": [ { "name": "description", "label": "Description", + "advanced": true, "help": "A description of your domain (256 character limit)." }, { "name": "maturity", "label": "Maturity", + "advanced": true, "help": "A maturity rating, available as a guideline for content on your domain.", "default": "unrated", "type": "select", @@ -110,11 +121,11 @@ "label": "Adult (18+)" } ] - }, { "name": "hosts", "label": "Hosts", + "advanced": true, "type": "table", "can_add_new_rows": true, "help": "Usernames of hosts who can reliably show your domain to new visitors.", @@ -130,6 +141,7 @@ { "name": "tags", "label": "Tags", + "advanced": true, "type": "table", "can_add_new_rows": true, "help": "Common categories under which your domain falls.", @@ -159,15 +171,15 @@ "label": "HTTP Password", "type": "password", "help": "Password used for basic HTTP authentication. Leave this alone if you do not want to change it.", - "password_placeholder" : "******", + "password_placeholder": "******", "value-hidden": true }, { - "name": "verify_http_password", - "label": "Verify HTTP Password", - "type": "password", - "help": "Must match the password entered above for change to be saved.", - "value-hidden": true + "name": "verify_http_password", + "label": "Verify HTTP Password", + "type": "password", + "help": "Must match the password entered above for change to be saved.", + "value-hidden": true }, { "name": "maximum_user_capacity", @@ -206,21 +218,19 @@ "name": "standard_permissions", "type": "table", "label": "Domain-Wide User Permissions", - "help": "Indicate which types of users can have which domain-wide permissions.", + "help": "Indicate which types of users can have which domain-wide permissions.", "caption": "Standard Permissions", "can_add_new_rows": false, - "groups": [ { "label": "Type of User", "span": 1 }, { - "label": "Permissions ?", - "span": 7 + "label": "Permissions ?", + "span": 10 } ], - "columns": [ { "name": "permissions_id", @@ -254,6 +264,20 @@ "editable": true, "default": false }, + { + "name": "id_can_rez_certified", + "label": "Rez Certified", + "type": "checkbox", + "editable": true, + "default": false + }, + { + "name": "id_can_rez_tmp_certified", + "label": "Rez Temporary Certified", + "type": "checkbox", + "editable": true, + "default": false + }, { "name": "id_can_write_to_asset_server", "label": "Write Assets", @@ -274,11 +298,17 @@ "type": "checkbox", "editable": true, "default": false + }, + { + "name": "id_can_replace_content", + "label": "Replace Content", + "type": "checkbox", + "editable": true, + "default": false } ], - "non-deletable-row-key": "permissions_id", - "non-deletable-row-values": ["localhost", "anonymous", "logged-in"] + "non-deletable-row-values": [ "localhost", "anonymous", "logged-in" ] }, { "name": "group_permissions", @@ -289,18 +319,16 @@ "can_add_new_rows": false, "new_category_placeholder": "Add Group", "new_category_message": "Save and reload to see ranks", - "groups": [ { "label": "Rank", "span": 1 }, { - "label": "Permissions ?", - "span": 7 + "label": "Permissions ?", + "span": 10 } ], - "columns": [ { "name": "permissions_id", @@ -359,6 +387,20 @@ "editable": true, "default": false }, + { + "name": "id_can_rez_certified", + "label": "Rez Certified", + "type": "checkbox", + "editable": true, + "default": false + }, + { + "name": "id_can_rez_tmp_certified", + "label": "Rez Temporary Certified", + "type": "checkbox", + "editable": true, + "default": false + }, { "name": "id_can_write_to_asset_server", "label": "Write Assets", @@ -379,6 +421,13 @@ "type": "checkbox", "editable": true, "default": false + }, + { + "name": "id_can_replace_content", + "label": "Replace Content", + "type": "checkbox", + "editable": true, + "default": false } ] }, @@ -391,18 +440,16 @@ "can_add_new_rows": false, "new_category_placeholder": "Add Blacklist Group", "new_category_message": "Save and reload to see ranks", - "groups": [ { "label": "Rank", "span": 1 }, { - "label": "Permissions ?", - "span": 7 + "label": "Permissions ?", + "span": 10 } ], - "columns": [ { "name": "permissions_id", @@ -458,6 +505,20 @@ "editable": true, "default": false }, + { + "name": "id_can_rez_certified", + "label": "Rez Certified", + "type": "checkbox", + "editable": true, + "default": false + }, + { + "name": "id_can_rez_tmp_certified", + "label": "Rez Temporary Certified", + "type": "checkbox", + "editable": true, + "default": false + }, { "name": "id_can_write_to_asset_server", "label": "Write Assets", @@ -478,6 +539,13 @@ "type": "checkbox", "editable": true, "default": false + }, + { + "name": "id_can_replace_content", + "label": "Replace Content", + "type": "checkbox", + "editable": true, + "default": false } ] }, @@ -486,18 +554,16 @@ "type": "table", "caption": "Permissions for Specific Users", "can_add_new_rows": true, - "groups": [ { "label": "User", "span": 1 }, { - "label": "Permissions ?", - "span": 7 + "label": "Permissions ?", + "span": 10 } ], - "columns": [ { "name": "permissions_id", @@ -531,6 +597,20 @@ "editable": true, "default": false }, + { + "name": "id_can_rez_certified", + "label": "Rez Certified", + "type": "checkbox", + "editable": true, + "default": false + }, + { + "name": "id_can_rez_tmp_certified", + "label": "Rez Temporary Certified", + "type": "checkbox", + "editable": true, + "default": false + }, { "name": "id_can_write_to_asset_server", "label": "Write Assets", @@ -551,6 +631,13 @@ "type": "checkbox", "editable": true, "default": false + }, + { + "name": "id_can_replace_content", + "label": "Replace Content", + "type": "checkbox", + "editable": true, + "default": false } ] }, @@ -565,11 +652,10 @@ "span": 1 }, { - "label": "Permissions ?", - "span": 7 + "label": "Permissions ?", + "span": 10 } ], - "columns": [ { "name": "permissions_id", @@ -603,6 +689,20 @@ "editable": true, "default": false }, + { + "name": "id_can_rez_certified", + "label": "Rez Certified", + "type": "checkbox", + "editable": true, + "default": false + }, + { + "name": "id_can_rez_tmp_certified", + "label": "Rez Temporary Certified", + "type": "checkbox", + "editable": true, + "default": false + }, { "name": "id_can_write_to_asset_server", "label": "Write Assets", @@ -623,6 +723,13 @@ "type": "checkbox", "editable": true, "default": false + }, + { + "name": "id_can_replace_content", + "label": "Replace Content", + "type": "checkbox", + "editable": true, + "default": false } ] }, @@ -637,11 +744,10 @@ "span": 1 }, { - "label": "Permissions ?", - "span": 7 + "label": "Permissions ?", + "span": 10 } ], - "columns": [ { "name": "permissions_id", @@ -675,6 +781,20 @@ "editable": true, "default": false }, + { + "name": "id_can_rez_certified", + "label": "Rez Certified", + "type": "checkbox", + "editable": true, + "default": false + }, + { + "name": "id_can_rez_tmp_certified", + "label": "Rez Temporary Certified", + "type": "checkbox", + "editable": true, + "default": false + }, { "name": "id_can_write_to_asset_server", "label": "Write Assets", @@ -695,6 +815,13 @@ "type": "checkbox", "editable": true, "default": false + }, + { + "name": "id_can_replace_content", + "label": "Replace Content", + "type": "checkbox", + "editable": true, + "default": false } ] }, @@ -709,11 +836,10 @@ "span": 1 }, { - "label": "Permissions ?", - "span": 7 + "label": "Permissions ?", + "span": 10 } ], - "columns": [ { "name": "permissions_id", @@ -747,6 +873,20 @@ "editable": true, "default": false }, + { + "name": "id_can_rez_certified", + "label": "Rez Certified", + "type": "checkbox", + "editable": true, + "default": false + }, + { + "name": "id_can_rez_tmp_certified", + "label": "Rez Temporary Certified", + "type": "checkbox", + "editable": true, + "default": false + }, { "name": "id_can_write_to_asset_server", "label": "Write Assets", @@ -767,6 +907,13 @@ "type": "checkbox", "editable": true, "default": false + }, + { + "name": "id_can_replace_content", + "label": "Replace Content", + "type": "checkbox", + "editable": true, + "default": false } ] } @@ -782,7 +929,6 @@ "label": "Persistent Scripts", "help": "Add the URLs for scripts that you would like to ensure are always running in your domain.", "can_add_new_rows": true, - "columns": [ { "name": "url", @@ -804,7 +950,7 @@ { "name": "asset_server", "label": "Asset Server (ATP)", - "assignment-types": [3], + "assignment-types": [ 3 ], "settings": [ { "name": "enabled", @@ -821,13 +967,21 @@ "help": "The path to the directory assets are stored in.
If this path is relative, it will be relative to the application data directory.
If you change this path you will need to manually copy any existing assets from the previous directory.", "default": "", "advanced": true + }, + { + "name": "assets_filesize_limit", + "type": "int", + "label": "File Size Limit", + "help": "The file size limit of an asset that can be imported into the asset server in MBytes. 0 (default) means no limit on file size.", + "default": 0, + "advanced": true } ] }, { "name": "entity_script_server", "label": "Entity Script Server (ESS)", - "assignment-types": [5], + "assignment-types": [ 5 ], "settings": [ { "name": "entity_pps_per_script", @@ -850,7 +1004,7 @@ { "name": "avatars", "label": "Avatars", - "assignment-types": [1, 2], + "assignment-types": [ 1, 2 ], "settings": [ { "name": "min_avatar_scale", @@ -889,7 +1043,7 @@ { "name": "audio_threading", "label": "Audio Threading", - "assignment-types": [0], + "assignment-types": [ 0 ], "settings": [ { "name": "auto_threads", @@ -912,7 +1066,7 @@ { "name": "audio_env", "label": "Audio Environment", - "assignment-types": [0], + "assignment-types": [ 0 ], "settings": [ { "name": "attenuation_per_doubling_in_distance", @@ -944,7 +1098,6 @@ "help": "In this table you can define a set of zones in which you can specify various audio properties.", "numbered": false, "can_add_new_rows": true, - "key": { "name": "name", "label": "Name", @@ -997,7 +1150,6 @@ "numbered": true, "can_order": true, "can_add_new_rows": true, - "columns": [ { "name": "source", @@ -1026,7 +1178,6 @@ "help": "In this table you can set reverb levels for audio zones. For a medium-sized (e.g., 100 square meter) meeting room, try a decay time of around 1.5 seconds and a wet/dry mix of 25%. For an airplane hangar or cathedral, try a decay time of 4 seconds and a wet/dry mix of 50%.", "numbered": true, "can_add_new_rows": true, - "columns": [ { "name": "zone", @@ -1061,7 +1212,9 @@ { "name": "audio_buffer", "label": "Audio Buffers", - "assignment-types": [0], + "assignment-types": [ + 0 + ], "settings": [ { "name": "dynamic_jitter_buffer", @@ -1080,35 +1233,37 @@ "advanced": true }, { - "name": "max_frames_over_desired", - "deprecated": true + "name": "max_frames_over_desired", + "deprecated": true }, { - "name": "window_starve_threshold", - "deprecated": true + "name": "window_starve_threshold", + "deprecated": true }, { - "name": "window_seconds_for_desired_calc_on_too_many_starves", - "deprecated": true + "name": "window_seconds_for_desired_calc_on_too_many_starves", + "deprecated": true }, { - "name": "window_seconds_for_desired_reduction", - "deprecated": true + "name": "window_seconds_for_desired_reduction", + "deprecated": true }, { - "name": "use_stdev_for_desired_calc", - "deprecated": true + "name": "use_stdev_for_desired_calc", + "deprecated": true }, { - "name": "repetition_with_fade", - "deprecated": true + "name": "repetition_with_fade", + "deprecated": true } ] }, { "name": "entity_server_settings", "label": "Entity Server Settings", - "assignment-types": [6], + "assignment-types": [ + 6 + ], "settings": [ { "name": "maxTmpLifetime", @@ -1118,6 +1273,22 @@ "default": "3600", "advanced": true }, + { + "name": "dynamicDomainVerificationTimeMin", + "label": "Dynamic Domain Verification Time (seconds) - Minimum", + "help": "The lower limit on the amount of time that passes before Dynamic Domain Verification on entities occurs. Units are seconds.", + "placeholder": "2700", + "default": "2700", + "advanced": true + }, + { + "name": "dynamicDomainVerificationTimeMax", + "label": "Dynamic Domain Verification Time (seconds) - Maximum", + "help": "The upper limit on the amount of time that passes before Dynamic Domain Verification on entities occurs. Units are seconds.", + "placeholder": "3600", + "default": "3600", + "advanced": true + }, { "name": "entityScriptSourceWhitelist", "label": "Entity Scripts Allowed from:", @@ -1165,13 +1336,32 @@ "help": "In this table you can define a set of rules for how frequently to backup copies of your entites content file.", "numbered": false, "can_add_new_rows": true, - - "default": [ - {"Name":"Half Hourly Rolling","backupInterval":1800,"format":".backup.halfhourly.%N","maxBackupVersions":5}, - {"Name":"Daily Rolling","backupInterval":86400,"format":".backup.daily.%N","maxBackupVersions":7}, - {"Name":"Weekly Rolling","backupInterval":604800,"format":".backup.weekly.%N","maxBackupVersions":4}, - {"Name":"Thirty Day Rolling","backupInterval":2592000,"format":".backup.thirtyday.%N","maxBackupVersions":12} - ], + "default": [ + { + "Name": "Half Hourly Rolling", + "backupInterval": 1800, + "format": ".backup.halfhourly.%N", + "maxBackupVersions": 5 + }, + { + "Name": "Daily Rolling", + "backupInterval": 86400, + "format": ".backup.daily.%N", + "maxBackupVersions": 7 + }, + { + "Name": "Weekly Rolling", + "backupInterval": 604800, + "format": ".backup.weekly.%N", + "maxBackupVersions": 4 + }, + { + "Name": "Thirty Day Rolling", + "backupInterval": 2592000, + "format": ".backup.thirtyday.%N", + "maxBackupVersions": 12 + } + ], "columns": [ { "name": "Name", @@ -1307,7 +1497,9 @@ { "name": "avatar_mixer", "label": "Avatar Mixer", - "assignment-types": [1], + "assignment-types": [ + 1 + ], "settings": [ { "name": "max_node_send_bandwidth", @@ -1360,7 +1552,10 @@ { "name": "downstream_servers", "label": "Receiving Servers", - "assignment-types": [0,1], + "assignment-types": [ + 0, + 1 + ], "type": "table", "advanced": true, "can_add_new_rows": true, @@ -1400,7 +1595,10 @@ { "name": "upstream_servers", "label": "Broadcasting Servers", - "assignment-types": [0,1], + "assignment-types": [ + 0, + 1 + ], "type": "table", "advanced": true, "can_add_new_rows": true, @@ -1438,6 +1636,29 @@ ] } ] + }, + { + "name": "wizard", + "label": "Setup Wizard", + "restart": false, + "hidden": true, + "settings": [ + { + "name": "cloud_domain", + "type": "checkbox", + "default": false + }, + { + "name": "steps_completed", + "type": "int", + "default": 0 + }, + { + "name": "completed_once", + "type": "checkbox", + "default": false + } + ] } ] } diff --git a/domain-server/resources/web/assignment/placeholder.js b/domain-server/resources/web/assignment/placeholder.js index 2c1d8253aa..bf64539bea 100644 --- a/domain-server/resources/web/assignment/placeholder.js +++ b/domain-server/resources/web/assignment/placeholder.js @@ -1,3 +1,3 @@ // Here you can put a script that will be run by an assignment-client (AC) -// For examples, please go to http://public.highfidelity.io/scripts +// For examples, please go to https://github.com/highfidelity/hifi/tree/master/script-archive/acScripts // The directory named acScripts contains assignment-client specific scripts you can try. diff --git a/domain-server/resources/web/content/index.shtml b/domain-server/resources/web/content/index.shtml index e1ba5499b6..0e48c1eff8 100644 --- a/domain-server/resources/web/content/index.shtml +++ b/domain-server/resources/web/content/index.shtml @@ -19,12 +19,13 @@ Upload an entities file (e.g.: models.json.gz) to replace the content of this domain.
Note: Your domain's content will be replaced by the content you upload, but the backup files of your domain's content will not immediately be changed.

-

- If your domain has any content that you would like to re-use at a later date, save a manual backup of your models.json.gz file, which is usually stored at the following paths:
-

C:/Users/[username]/AppData/Roaming/High Fidelity/assignment-client/entities/models.json.gz
-
/Users/[username]/Library/Application Support/High Fidelity/assignment-client/entities/models.json.gz
-
/home/[username]/.local/share/High Fidelity/assignment-client/entities/models.json.gz
-

+

If your domain has any content that you would like to re-use at a later date, save a manual backup of your models.json.gz file, which is usually stored at the following paths:

+ +
C:/Users/[username]/AppData/Roaming/High Fidelity/assignment-client/entities/models.json.gz
+ +
/Users/[username]/Library/Application Support/High Fidelity/assignment-client/entities/models.json.gz
+ +
/home/[username]/.local/share/High Fidelity/assignment-client/entities/models.json.gz


diff --git a/domain-server/resources/web/css/style.css b/domain-server/resources/web/css/style.css index 553f408e15..547b903b15 100644 --- a/domain-server/resources/web/css/style.css +++ b/domain-server/resources/web/css/style.css @@ -1,6 +1,7 @@ body { position: relative; padding-bottom: 30px; + margin-top: 70px; } [hidden] { @@ -27,14 +28,14 @@ body { .table .value-row td, .table .value-category td, .table .inputs td { - vertical-align: middle; + vertical-align: middle; } .table .table-checkbox { - /* Fix IE sizing checkboxes to fill table cell */ - width: auto; - margin-left: auto; - margin-right: auto; + /* Fix IE sizing checkboxes to fill table cell */ + width: auto; + margin-left: auto; + margin-right: auto; } .value-category:not(.inputs) { @@ -80,8 +81,10 @@ span.port { } #setup-sidebar.affix { - position: fixed; - top: 15px; + /* This overrides a case where going to the bottom of the page, + * then scrolling up, causes `position: relative` to be added to the style + */ + position: fixed !important; } #setup-sidebar button { @@ -145,55 +148,55 @@ table { } caption { - color: #333; - font-weight: 700; - padding-top: 0; + color: #333; + font-weight: 700; + padding-top: 0; } table > tbody > .headers > td { - vertical-align: middle; + vertical-align: middle; } table .headers + .headers td { - font-size: 13px; - color: #222; + font-size: 13px; + color: #222; } #security table .headers td + td { - text-align: center; + text-align: center; } .tooltip.top .tooltip-arrow { - border-top-color: #fff; - border-width: 10px 10px 0; - margin-bottom: -5px; + border-top-color: #fff; + border-width: 10px 10px 0; + margin-bottom: -5px; } .tooltip-inner { - padding: 20px 20px 10px 20px; - font-size: 14px; - text-align: left; - color: #333; - background-color: #fff; - box-shadow: 0 3px 8px 8px #e8e8e8; + padding: 20px 20px 10px 20px; + font-size: 14px; + text-align: left; + color: #333; + background-color: #fff; + box-shadow: 0 3px 8px 8px #e8e8e8; } .tooltip.in { - opacity: 1; + opacity: 1; } .tooltip-inner ul { - padding-left: 0; - margin-bottom: 15px; + padding-left: 0; + margin-bottom: 15px; } .tooltip-inner li { - list-style-type: none; - margin-bottom: 5px; + list-style-type: none; + margin-bottom: 5px; } #security .tooltip-inner { - max-width: 520px; + max-width: 520px; } #xs-advanced-container { @@ -241,6 +244,20 @@ table .headers + .headers td { animation-delay: -0.16s; } +.col-centered { + float: none; + margin: 0 auto; +} + +.centered-hack-parent { + text-align: center; +} + +.centered-hack { + text-align: left; + display: inline-block; +} + @-webkit-keyframes bouncedelay { 0%, 80%, 100% { -webkit-transform: scale(0.0) } 40% { -webkit-transform: scale(1.0) } @@ -255,3 +272,49 @@ table .headers + .headers td { -webkit-transform: scale(1.0); } } + +/* From https://gist.github.com/alexandrevicenzi/680147013e902a4eaa5d */ +.glyphicon-refresh-animate { + -animation: spin .7s infinite linear; + -ms-animation: spin .7s infinite linear; + -webkit-animation: spinw .7s infinite linear; + -moz-animation: spinm .7s infinite linear; +} + +@keyframes spin { + from { transform: scale(1) rotate(0deg); } + to { transform: scale(1) rotate(360deg); } +} + +@-webkit-keyframes spinw { + from { -webkit-transform: rotate(0deg); } + to { -webkit-transform: rotate(360deg); } +} + +@-moz-keyframes spinm { + from { -moz-transform: rotate(0deg); } + to { -moz-transform: rotate(360deg); } +} + +.warning-text { + padding-top: 10px; + color: #EB5757; +} + +.account-connected-header { + color: #6FCF97; + font-size: 30px; + margin-right: 20px; +} + +.blue-link { + font-size: 14px; + text-decoration-line: underline; + font-weight: normal; + color: #2F80ED; +} + +#manage-cloud-domains-link { + text-align: center; + margin-top: 20px; +} diff --git a/domain-server/resources/web/favicon.ico b/domain-server/resources/web/favicon.ico new file mode 100644 index 0000000000..becc1b8e8b Binary files /dev/null and b/domain-server/resources/web/favicon.ico differ diff --git a/domain-server/resources/web/header.html b/domain-server/resources/web/header.html index a37e9a6ff0..1e32e9f02f 100644 --- a/domain-server/resources/web/header.html +++ b/domain-server/resources/web/header.html @@ -13,7 +13,7 @@ -