Merge branch 'orange' of https://github.com/samcake/hifi into orange

This commit is contained in:
Sam Gateau 2017-11-22 23:29:25 -08:00
commit 9c53ab719a
1520 changed files with 108702 additions and 32735 deletions
.clang-format.eslintrc.js.gitattributes.gitignoreBUILD.mdBUILD_ANDROID.mdBUILD_LINUX.mdBUILD_OSX.mdBUILD_WIN.mdCMakeLists.txtCONTRIBUTING.md
android
assignment-client
cmake

39
.clang-format Normal file
View file

@ -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

View file

@ -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 }],

1
.gitattributes vendored
View file

@ -10,6 +10,7 @@
*.json text
*.js text
*.qml text
*.qrc text
*.slf text
*.slh text
*.slv text

18
.gitignore vendored
View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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.

View file

@ -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

View file

@ -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 dont 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.

View file

@ -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()

View file

@ -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

View file

@ -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")

57
android/app/build.gradle Normal file
View file

@ -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')

25
android/app/proguard-rules.pro vendored Normal file
View file

@ -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

View file

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.saintandreas.testapp">
<uses-sdk android:minSdkVersion="24" android:targetSdkVersion="26" />
<uses-feature android:glEsVersion="0x00030001" android:required="true" />
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-feature android:name="android.hardware.sensor.accelerometer" android:required="true"/>
<uses-feature android:name="android.hardware.sensor.gyroscope" android:required="true"/>
<uses-feature android:name="android.software.vr.mode" android:required="false"/>
<uses-feature android:name="android.hardware.vr.high_performance" android:required="false"/>
<application
android:allowBackup="true"
android:theme="@style/VrActivityTheme"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round">
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:screenOrientation="landscape"
android:configChanges="orientation|keyboardHidden|screenSize"
android:enableVrMode="@string/gvr_vr_mode_component"
android:resizeableActivity="false">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="com.google.intent.category.DAYDREAM"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="com.google.intent.category.CARDBOARD" />
</intent-filter>
</activity>
</application>
</manifest>

View file

@ -0,0 +1,50 @@
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include <vr/gvr/capi/include/gvr.h>
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;
}
}

View file

@ -0,0 +1,78 @@
#include <jni.h>
#include <android/log.h>
#include <QtCore/QDebug>
#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<intptr_t>(renderer);
}
static NativeRenderer *fromJni(jlong renderer) {
return reinterpret_cast<NativeRenderer*>(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<gvr_context *>(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"

View file

@ -0,0 +1,636 @@
#include "renderer.h"
#include <mutex>
#include <QtCore/QDebug>
#include <gl/Config.h>
#include "GoogleVRHelpers.h"
#include <gl/GLShaders.h>
#include <shared/Bilateral.h>
#include <gpu/DrawTransformUnitQuad_vert.h>
#include <gpu/DrawTexcoordRectTransformUnitQuad_vert.h>
#include <gpu/DrawViewportQuadTransformTexcoord_vert.h>
#include <gpu/DrawTexture_frag.h>
#include <gpu/DrawTextureOpaque_frag.h>
#include <gpu/DrawColoredTexture_frag.h>
#include <render-utils/simple_vert.h>
#include <render-utils/simple_frag.h>
#include <render-utils/simple_textured_frag.h>
#include <render-utils/simple_textured_unlit_frag.h>
#include <render-utils/deferred_light_vert.h>
#include <render-utils/deferred_light_point_vert.h>
#include <render-utils/deferred_light_spot_vert.h>
#include <render-utils/directional_ambient_light_frag.h>
#include <render-utils/directional_skybox_light_frag.h>
#include <render-utils/standardTransformPNTC_vert.h>
#include <render-utils/standardDrawTexture_frag.h>
#include <render-utils/model_vert.h>
#include <render-utils/model_shadow_vert.h>
#include <render-utils/model_normal_map_vert.h>
#include <render-utils/model_lightmap_vert.h>
#include <render-utils/model_lightmap_normal_map_vert.h>
#include <render-utils/skin_model_vert.h>
#include <render-utils/skin_model_shadow_vert.h>
#include <render-utils/skin_model_normal_map_vert.h>
#include <render-utils/model_frag.h>
#include <render-utils/model_shadow_frag.h>
#include <render-utils/model_normal_map_frag.h>
#include <render-utils/model_normal_specular_map_frag.h>
#include <render-utils/model_specular_map_frag.h>
#include <render-utils/model_lightmap_frag.h>
#include <render-utils/model_lightmap_normal_map_frag.h>
#include <render-utils/model_lightmap_normal_specular_map_frag.h>
#include <render-utils/model_lightmap_specular_map_frag.h>
#include <render-utils/model_translucent_frag.h>
#include <render-utils/overlay3D_vert.h>
#include <render-utils/overlay3D_frag.h>
#include <render-utils/sdf_text3D_vert.h>
#include <render-utils/sdf_text3D_frag.h>
#if 0
#include <model/skybox_vert.h>
#include <model/skybox_frag.h>
#include <entities-renderer/textured_particle_frag.h>
#include <entities-renderer/textured_particle_vert.h>
#include <entities-renderer/paintStroke_vert.h>
#include <entities-renderer/paintStroke_frag.h>
#include <entities-renderer/polyvox_vert.h>
#include <entities-renderer/polyvox_frag.h>
#endif
template <typename F>
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<float, 108> 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<float, 108> 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<float, 108> 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<float, 9> TRIANGLE_VERTS {{
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
}};
}
std::array<gvr::BufferViewport, 2> buildViewports(const std::unique_ptr<gvr::GvrApi> &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<gvr::BufferSpec> 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::milliseconds>(
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();
}

View file

@ -0,0 +1,60 @@
#pragma once
#include <chrono>
#include <array>
#include <glm/glm.hpp>
#define GVR
#if defined(GVR)
#include <vr/gvr/capi/include/gvr.h>
#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<std::chrono::system_clock> start;
#if defined(GVR)
void InitializeVR();
void PrepareFramebuffer();
std::unique_ptr<gvr::GvrApi> _gvrapi;
gvr::ViewerType _gvr_viewer_type;
std::unique_ptr<gvr::BufferViewportList> _viewportlist;
std::unique_ptr<gvr::SwapChain> _swapchain;
std::array<gvr::BufferViewport, 2> _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<glm::mat4, 2> _modelview_cube;
std::array<glm::mat4, 2> _modelview_floor;
std::array<glm::mat4, 2> _modelview_projection_cube;
std::array<glm::mat4, 2> _modelview_projection_floor;
std::array<glm::vec3, 2> _light_pos_eye_space;
const glm::vec4 _light_pos_world_space{ 0, 2, 0, 1};
};

View file

@ -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);
}
}

Binary file not shown.

After

(image error) Size: 3.3 KiB

Binary file not shown.

After

(image error) Size: 4.1 KiB

Binary file not shown.

After

(image error) Size: 2.2 KiB

Binary file not shown.

After

(image error) Size: 2.5 KiB

Binary file not shown.

After

(image error) Size: 4.7 KiB

Binary file not shown.

After

(image error) Size: 6 KiB

Binary file not shown.

After

(image error) Size: 7.5 KiB

Binary file not shown.

After

(image error) Size: 9.8 KiB

Binary file not shown.

After

(image error) Size: 10 KiB

Binary file not shown.

After

(image error) Size: 14 KiB

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="white_opaque">#ffffff</color>
</resources>

View file

@ -0,0 +1,3 @@
<resources>
<string name="app_name">TestApp</string>
</resources>

View file

@ -0,0 +1,15 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="android:style/Theme.NoTitleBar.Fullscreen">
<!-- Customize your theme here. -->
<!--item name="android:windowFullscreen">true</item-->
<!--item name="android:windowNoTitle">true</item-->
<!--item name="android:windowActionBar">false</item-->
<!--item name="android:windowContentOverlay">@null</item-->
<!--item name="android:background">@color/white_opaque</item-->
</style>
</resources>

91
android/build.gradle Normal file
View file

@ -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)

17
android/gradle.properties Normal file
View file

@ -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

1
android/settings.gradle Normal file
View file

@ -0,0 +1 @@
include ':app'

View file

@ -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)

View file

@ -23,6 +23,7 @@
#include <AvatarHashMap.h>
#include <AudioInjectorManager.h>
#include <AssetClient.h>
#include <DebugDraw.h>
#include <LocationScriptingInterface.h>
#include <MessagesClient.h>
#include <NetworkAccessManager.h>
@ -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<EntityScriptingInterface>()->setPacketSender(&_entityEditSender);
@ -81,6 +82,9 @@ Agent::Agent(ReceivedMessage& message) :
DependencyManager::set<RecordingScriptingInterface>();
DependencyManager::set<UsersScriptingInterface>();
// Needed to ensure the creation of the DebugDraw instance on the main thread
DebugDraw::getInstance();
auto& packetReceiver = DependencyManager::get<NodeList>()->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<NodeList>();
auto negotiateFormatPacket = NLPacket::create(PacketType::NegotiateAudioFormat);
@ -332,15 +354,16 @@ void Agent::scriptRequestFinished() {
void Agent::executeScript() {
_scriptEngine = std::unique_ptr<ScriptEngine>(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<RecordingScriptingInterface>()->setScriptEngine(_scriptEngine.get());
DependencyManager::get<RecordingScriptingInterface>()->setScriptEngine(_scriptEngine);
// setup an Avatar for the script to use
auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>();
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<AssignmentParentFinder>(_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<recording::Recorder>();
DependencyManager::destroy<recording::ClipCache>();
emit stopAvatarAudioTimer();
_avatarAudioTimerThread.quit();
QMetaObject::invokeMethod(&_avatarAudioTimer, "stop");
// cleanup codec & encoder
if (_codec && _encoder) {

View file

@ -77,20 +77,18 @@ private slots:
void handleSelectedAudioFormat(QSharedPointer<ReceivedMessage> 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<ScriptableAvatar>);
std::unique_ptr<ScriptEngine> _scriptEngine;
ScriptEnginePointer _scriptEngine;
EntityEditPacketSender _entityEditSender;
EntityTreeHeadlessViewer _entityViewer;
@ -112,13 +110,13 @@ private:
QHash<QUuid, quint16> _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 };
};

View file

@ -16,6 +16,7 @@
#include <QThread>
#include <QTimer>
#include <shared/QtHelpers.h>
#include <AccountManager.h>
#include <AddressManager.h>
#include <Assignment.h>
@ -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();

View file

@ -9,21 +9,21 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QCommandLineParser>
#include <QThread>
#include "AssignmentClientApp.h"
#include <QtCore/QCommandLineParser>
#include <QtCore/QDir>
#include <QtCore/QStandardPaths>
#include <QtCore/QThread>
#include <LogHandler.h>
#include <SharedUtil.h>
#include <HifiConfigVariantMap.h>
#include <SharedUtil.h>
#include <ShutdownEventListener.h>
#include "Assignment.h"
#include "AssignmentClient.h"
#include "AssignmentClientMonitor.h"
#include "AssignmentClientApp.h"
#include <QtCore/QDir>
#include <QtCore/QStandardPaths>
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<LimitedNodeList, NodeList>();

View file

@ -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<NodeList>()->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<void(QProcess::*)(int, QProcess::ExitStatus)>(&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();

View file

@ -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<ReceivedMessage> message);
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false) override;

View file

@ -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 <QDebug>
#include <SharedUtil.h>
#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";
}

View file

@ -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 <QtCore/QObject>
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

File diff suppressed because it is too large Load diff

View file

@ -14,17 +14,39 @@
#include <QtCore/QDir>
#include <QtCore/QThreadPool>
#include <QRunnable>
#include <ThreadedAssignment.h>
#include "AssetUtils.h"
#include "ReceivedMessage.h"
namespace std {
template <>
struct hash<QString> {
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<QString, QString>;
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<BakingStatus, QString> 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<QString> 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<bool, AssetMeta> 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<AssetHash, std::shared_ptr<BakeAssetTask>> _pendingBakes;
QThreadPool _bakingTaskPool;
bool _wasColorTextureCompressionEnabled { false };
bool _wasGrayscaleTextureCompressionEnabled { false };
bool _wasNormalTextureCompressionEnabled { false };
bool _wasCubeTextureCompressionEnabled { false };
uint64_t _filesizeLimit;
};
#endif

View file

@ -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")

View file

@ -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 <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(asset_server)
#endif // hifi_AssetServerLogging_h

View file

@ -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 <QtCore/QThread>
#include <FBXBaker.h>
#include <PathUtils.h>
#include <JSBaker.h>
BakeAssetTask::BakeAssetTask(const AssetHash& assetHash, const AssetPath& assetPath, const QString& filePath) :
_assetHash(assetHash),
_assetPath(assetPath),
_filePath(filePath)
{
}
void cleanupTempFiles(QString tempOutputDir, std::vector<QString> 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<QString> >("QVector<QString>");
TextureBakerThreadGetter fn = []() -> QThread* { return QThread::currentThread(); };
QString tempOutputDir;
if (_assetPath.endsWith(".fbx")) {
tempOutputDir = PathUtils::generateTemporaryDir();
_baker = std::unique_ptr<FBXBaker> {
new FBXBaker(QUrl("file:///" + _filePath), fn, tempOutputDir)
};
} else if (_assetPath.endsWith(".js", Qt::CaseInsensitive)) {
_baker = std::unique_ptr<JSBaker>{
new JSBaker(QUrl("file:///" + _filePath), PathUtils::generateTemporaryDir())
};
} else {
tempOutputDir = PathUtils::generateTemporaryDir();
_baker = std::unique_ptr<TextureBaker> {
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<QString>::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();
}
}

View file

@ -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 <memory>
#include <QtCore/QDebug>
#include <QtCore/QObject>
#include <QtCore/QRunnable>
#include <AssetUtils.h>
#include <Baker.h>
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<QString> outputFiles);
void bakeFailed(QString assetHash, QString assetPath, QString errors);
void bakeAborted(QString assetHash, QString assetPath);
private:
std::atomic<bool> _isBaking { false };
AssetHash _assetHash;
AssetPath _assetPath;
QString _filePath;
std::unique_ptr<Baker> _baker;
std::atomic<bool> _wasAborted { false };
std::atomic<bool> _didFinish { false };
};
#endif // hifi_BakeAssetTask_h

View file

@ -22,10 +22,11 @@
UploadAssetTask::UploadAssetTask(QSharedPointer<ReceivedMessage> 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);

View file

@ -26,7 +26,8 @@ class Node;
class UploadAssetTask : public QRunnable {
public:
UploadAssetTask(QSharedPointer<ReceivedMessage> message, QSharedPointer<Node> senderNode, const QDir& resourcesDir);
UploadAssetTask(QSharedPointer<ReceivedMessage> message, QSharedPointer<Node> senderNode,
const QDir& resourcesDir, uint64_t filesizeLimit);
void run() override;
@ -34,6 +35,7 @@ private:
QSharedPointer<ReceivedMessage> _receivedMessage;
QSharedPointer<Node> _senderNode;
QDir _resourcesDir;
uint64_t _filesizeLimit;
};
#endif // hifi_UploadAssetTask_h

View file

@ -127,7 +127,7 @@ void AudioMixer::queueReplicatedAudioPacket(QSharedPointer<ReceivedMessage> 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";

View file

@ -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";

View file

@ -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 {

View file

@ -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
});
{

View file

@ -85,7 +85,22 @@ void AvatarMixer::handleReplicatedPacket(QSharedPointer<ReceivedMessage> message
auto nodeList = DependencyManager::get<NodeList>();
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<AvatarMixerClientData*>(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();

View file

@ -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<NodeList>()->sendUnreliablePacket(*exitingSpaceBubblePacket, *self);
}
}

View file

@ -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<const AvatarMixerClientData*>(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);
}
}

View file

@ -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
});
{

View file

@ -13,12 +13,13 @@
#include <QThread>
#include <glm/gtx/transform.hpp>
#include <shared/QtHelpers.h>
#include <GLMHelpers.h>
#include <AnimUtil.h>
#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<AnimationCache>()->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;
}

View file

@ -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:

View file

@ -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;
}

View file

@ -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 <queue>
#include <AACube.h>
#include <EntityTreeElement.h>
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>, PrioritizedEntity::Compare >;
#endif // hifi_EntityPriorityQueue_h

View file

@ -16,6 +16,10 @@
#include <ResourceCache.h>
#include <ScriptCache.h>
#include <EntityEditFilters.h>
#include <NetworkingConstants.h>
#include <QJsonArray>
#include <QJsonDocument>
#include <AddressManager.h>
#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<ResourceManager>();
DependencyManager::set<ResourceCacheSharedItems>();
DependencyManager::set<ScriptCache>();
auto& packetReceiver = DependencyManager::get<NodeList>()->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<ResourceManager>()->cleanup();
OctreeServer::aboutToFinish();
}
void EntityServer::handleEntityPacket(QSharedPointer<ReceivedMessage> 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<NodeList>()->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<NodeList>();
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<AddressManager>()->getDomainId().remove(QRegExp("\\{|\\}"));
EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree);
QHash<QString, EntityItemID> localMap(tree->getEntityCertificateIDMap());
QHashIterator<QString, EntityItemID> 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);
}

View file

@ -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<ReceivedMessage> message, SharedNodePointer senderNode);
void domainSettingsRequestFailed();
private:
SimpleEntitySimulationPointer _entitySimulation;
@ -78,6 +81,13 @@ private:
QReadWriteLock _viewerSendingStatsLock;
QMap<QUuid, QMap<QUuid, ViewerSendingStats>> _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

View file

@ -13,9 +13,18 @@
#include <EntityNodeData.h>
#include <EntityTypes.h>
#include <OctreeUtils.h>
#include "EntityServer.h"
EntityTreeSendThread::EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node) :
OctreeSendThread(myServer, node)
{
connect(std::static_pointer_cast<EntityTree>(myServer->getOctree()).get(), &EntityTree::editingEntityPointer, this, &EntityTreeSendThread::editingEntityPointer, Qt::QueuedConnection);
connect(std::static_pointer_cast<EntityTree>(myServer->getOctree()).get(), &EntityTree::deletingEntityPointer, this, &EntityTreeSendThread::deletingEntityPointer, Qt::QueuedConnection);
}
void EntityTreeSendThread::preDistributionProcessing() {
auto node = _node.toStrongRef();
auto nodeData = static_cast<EntityNodeData*>(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<EntityTreeElement>(_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<EntityTreeElement>(_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<OctreeQueryNode*>(params.nodeData);
nodeData->stats.encodeStarted();
auto entityNode = _node.toStrongRef();
auto entityNodeData = static_cast<EntityNodeData*>(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);
}

View file

@ -12,24 +12,56 @@
#ifndef hifi_EntityTreeSendThread_h
#define hifi_EntityTreeSendThread_h
#include <unordered_set>
#include "../octree/OctreeSendThread.h"
#include <DiffTraversal.h>
#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<EntityItem*> _entitiesInQueue;
std::unordered_map<EntityItem*, uint64_t> _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

View file

@ -92,7 +92,19 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer<ReceivedMessage>
// 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++;

View file

@ -17,7 +17,6 @@
#include <udt/PacketHeaders.h>
#include <PerfStat.h>
#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<OctreeQueryNode*>(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<NodeList>()->sendUnreliablePacket(statsPacket, *node);
packetSent = true;
} else {
// not enough room in the packet, send two packets
// first packet
OctreeServer::didCallWriteDatagram(this);
DependencyManager::get<NodeList>()->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<NodeList>()->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<NodeList>()->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<NodeList>()->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<OctreeQueryNode*>(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;
}
}

View file

@ -19,6 +19,7 @@
#include <GenericThread.h>
#include <Node.h>
#include <OctreePacketData.h>
#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> _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 };
};

View file

@ -22,7 +22,6 @@
#include <HTTPConnection.h>
#include <LogHandler.h>
#include <shared/NetworkUtils.h>
#include <NetworkingConstants.h>
#include <NumericalConstants.h>
#include <UUID.h>
@ -35,7 +34,7 @@
#include <QtCore/QDir>
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<ReceivedMessage> messa
// need to make sure we have it in our nodeList.
auto nodeList = DependencyManager::get<NodeList>();
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<ReceivedMessage> 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<ReceivedMessage> m
}
}
// Message->getMessage() contains a QByteArray representation of the URL to download from
void OctreeServer::handleOctreeFileReplacementFromURL(QSharedPointer<ReceivedMessage> 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<NodeList>()->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<NodeList>()->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<NodeList>()->getDomainHandler();
connect(&domainHandler, &DomainHandler::settingsReceived, this, &OctreeServer::domainSettingsRequestComplete);
@ -1191,9 +1232,9 @@ void OctreeServer::run() {
}
void OctreeServer::domainSettingsRequestComplete() {
auto nodeList = DependencyManager::get<NodeList>();
// 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);

View file

@ -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<ReceivedMessage> message, SharedNodePointer senderNode);
void handleJurisdictionRequestPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void handleOctreeFileReplacement(QSharedPointer<ReceivedMessage> message);
void handleOctreeFileReplacementFromURL(QSharedPointer<ReceivedMessage> 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;

View file

@ -16,6 +16,7 @@
#include <AudioConstants.h>
#include <AudioInjectorManager.h>
#include <ClientServerUtils.h>
#include <DebugDraw.h>
#include <EntityNodeData.h>
#include <EntityScriptingInterface.h>
#include <LogHandler.h>
@ -29,6 +30,8 @@
#include <UUID.h>
#include <WebSocketServerClass.h>
#include <EntityScriptClient.h> // for EntityScriptServerServices
#include "EntityScriptServerLogging.h"
#include "../entities/AssignmentParentFinder.h"
@ -67,6 +70,12 @@ EntityScriptServer::EntityScriptServer(ReceivedMessage& message) : ThreadedAssig
DependencyManager::set<ScriptCache>();
DependencyManager::set<ScriptEngines>(ScriptEngine::ENTITY_SERVER_SCRIPT);
DependencyManager::set<EntityScriptServerServices>();
// Needed to ensure the creation of the DebugDraw instance on the main thread
DebugDraw::getInstance();
auto& packetReceiver = DependencyManager::get<NodeList>()->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<ReceivedMessage> 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<Rec
void EntityScriptServer::handleEntityScriptGetStatusPacket(QSharedPointer<ReceivedMessage> 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> 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(&paramCount);
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<NodeList>();
@ -411,8 +442,7 @@ void EntityScriptServer::selectAudioFormat(const QString& selectedCodecName) {
void EntityScriptServer::resetEntitiesScriptEngine() {
auto engineName = QString("about:Entities %1").arg(++_entitiesScriptEngineCount);
auto newEngine = QSharedPointer<ScriptEngine>(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<EntityScriptingInterface>()->setEntitiesScriptEngine(newEngine.data());
auto newEngineSP = qSharedPointerCast<EntitiesScriptEngineProvider>(newEngine);
DependencyManager::get<EntityScriptingInterface>()->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<AudioInjectorManager>();
DependencyManager::destroy<ScriptEngines>();
DependencyManager::destroy<EntityScriptServerServices>();
// cleanup codec & encoder
if (_codec && _encoder) {

View file

@ -54,6 +54,9 @@ private slots:
void pushLogs();
void handleEntityScriptCallMethodPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
private:
void negotiateAudioFormat();
void selectAudioFormat(const QString& selectedCodecName);
@ -72,7 +75,7 @@ private:
bool _shuttingDown { false };
static int _entitiesScriptEngineCount;
QSharedPointer<ScriptEngine> _entitiesScriptEngine;
ScriptEnginePointer _entitiesScriptEngine;
EntityEditPacketSender _entityEditSender;
EntityTreeHeadlessViewer _entityViewer;

View file

@ -1,82 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- IMPORTANT: Do not manually manipulate this automatically generated file, changes will be gone after the next build! -->
<manifest package="${ANDROID_APK_PACKAGE}" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="${ANDROID_APK_VERSION_NAME}" android:versionCode="${ANDROID_APK_VERSION_CODE}" android:installLocation="auto">
<application
android:hardwareAccelerated="true"
android:name="org.qtproject.qt5.android.bindings.QtApplication"
android:label="@string/AppDisplayName"
android:icon="@drawable/icon"
android:debuggable="${ANDROID_APK_DEBUGGABLE}">
<!-- VR MODE -->
<meta-data android:name="com.samsung.android.vr.application.mode" android:value="vr_only"/>
<activity
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|locale|fontScale|keyboard|keyboardHidden|navigation"
android:name="${ANDROID_ACTIVITY_NAME}"
android:label="@string/AppDisplayName"
android:screenOrientation="landscape"
android:launchMode="singleTop"
${ANDROID_APK_THEME}>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
<meta-data android:name="android.app.repository" android:value="default"/>
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
<meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/>
<!-- Deploy Qt libs as part of package -->
<meta-data android:name="android.app.bundle_local_qt_libs" android:value="-- %%BUNDLE_LOCAL_QT_LIBS%% --"/>
<meta-data android:name="android.app.bundled_in_lib_resource_id" android:resource="@array/bundled_in_lib"/>
<meta-data android:name="android.app.bundled_in_assets_resource_id" android:resource="@array/bundled_in_assets"/>
<!-- Run with local libs -->
<meta-data android:name="android.app.use_local_qt_libs" android:value="-- %%USE_LOCAL_QT_LIBS%% --"/>
<meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/>
<meta-data android:name="android.app.load_local_libs" android:value="-- %%INSERT_LOCAL_LIBS%% --"/>
<meta-data android:name="android.app.load_local_jars" android:value="-- %%INSERT_LOCAL_JARS%% --"/>
<meta-data android:name="android.app.static_init_classes" android:value="-- %%INSERT_INIT_CLASSES%% --"/>
<!-- Messages maps -->
<meta-data android:value="@string/ministro_not_found_msg" android:name="android.app.ministro_not_found_msg"/>
<meta-data android:value="@string/ministro_needed_msg" android:name="android.app.ministro_needed_msg"/>
<meta-data android:value="@string/fatal_error_msg" android:name="android.app.fatal_error_msg"/>
<!-- Messages maps -->
<!-- Splash screen -->
<!-- <meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/logo"/> -->
${ANDROID_EXTRA_ACTIVITY_XML}
</activity>
<activity
android:name="com.oculusvr.vrlib.PlatformActivity"
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"
android:launchMode="singleTask"
android:screenOrientation="landscape"
android:configChanges="screenSize|orientation|keyboardHidden|keyboard">
</activity>
${ANDROID_EXTRA_APPLICATION_XML}
</application>
<uses-sdk android:minSdkVersion="${ANDROID_API_LEVEL}" android:targetSdkVersion="${ANDROID_API_LEVEL}"/>
<!-- The following comment will be replaced upon deployment with default permissions based on the dependencies of the application.
Remove the comment if you do not require these default permissions. -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- camera permission required for GEAR VR passthrough camera -->
<uses-permission android:name="android.permission.CAMERA" />
<!-- The following comment will be replaced upon deployment with default features based on the dependencies of the application.
Remove the comment if you do not require these default features. -->
<!-- Tell the system this app requires OpenGL ES 3.0. -->
<uses-feature android:glEsVersion="0x00030000" android:required="true" />
</manifest>

View file

@ -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()

File diff suppressed because it is too large Load diff

View file

@ -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@"
}

View file

@ -1,11 +0,0 @@
<?xml version='1.0' encoding='utf-8'?>
<!-- IMPORTANT: Do not manually manipulate this automatically generated file, changes will be gone after the next build! -->
<resources>
<string name="AppDisplayName">${ANDROID_APP_DISPLAY_NAME}</string>
<string name="ministro_not_found_msg">Can\'t find Ministro service.\nThe application can\'t start.</string>
<string name="ministro_needed_msg">This application requires Ministro service. Would you like to install it?</string>
<string name="fatal_error_msg">Your application encountered a fatal error and cannot continue.</string>
</resources>

106
cmake/compiler.cmake Normal file
View file

@ -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 ()

View file

@ -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)

41
cmake/externals/draco/CMakeLists.txt vendored Normal file
View file

@ -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=<INSTALL_DIR>-$<CONFIG> ${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}-$<CONFIG>")
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")

View file

@ -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=<INSTALL_DIR>
CONFIGURE_COMMAND CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> -DCMAKE_POSITION_INDEPENDENT_CODE=ON
LOG_DOWNLOAD 1
LOG_CONFIGURE 1
LOG_BUILD 1

View file

@ -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=<INSTALL_DIR>
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> ${EXTERNAL_ARGS}
LOG_DOWNLOAD 1
LOG_CONFIGURE 1
LOG_BUILD 1

View file

@ -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()

View file

@ -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=<INSTALL_DIR>
CONFIGURE_COMMAND CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DNVTT_SHARED=1 -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> -DCMAKE_POSITION_INDEPENDENT_CODE=ON
LOG_DOWNLOAD 1
LOG_CONFIGURE 1
LOG_BUILD 1

View file

@ -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=<INSTALL_DIR> -DCMAKE_PREFIX_PATH=${QT_CMAKE_PREFIX_PATH} -DCMAKE_INSTALL_NAME_DIR:PATH=<INSTALL_DIR>/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")

View file

@ -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 <SOURCE_DIR>
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 ()

View file

@ -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 ""

56
cmake/init.cmake Normal file
View file

@ -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 ()

View file

@ -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})

View file

@ -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()

View file

@ -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}<file alias=\"${FILENAME}\">${GENERATE_QRC_PATH}/${FILENAME}</file>\n")
endforeach()
endforeach()
configure_file("${HF_CMAKE_DIR}/templates/resources.qrc.in" ${GENERATE_QRC_OUTPUT})
endfunction()

View file

@ -22,9 +22,12 @@ macro(install_beside_console)
else ()
# setup install of executable and things copied by fixup/windeployqt
install(
FILES "$<TARGET_FILE_DIR:${TARGET_NAME}>/"
DIRECTORY "$<TARGET_FILE_DIR:${TARGET_NAME}>/"
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

View file

@ -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)

View file

@ -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)

View file

@ -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")

View file

@ -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})

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