diff --git a/BUILD.md b/BUILD.md index c868a8e9d9..c51e40cb58 100644 --- a/BUILD.md +++ b/BUILD.md @@ -1,7 +1,7 @@ ###Dependencies * [cmake](http://www.cmake.org/cmake/resources/software.html) ~> 3.3.2 -* [Qt](http://www.qt.io/download-open-source) ~> 5.5.1 +* [Qt](http://www.qt.io/download-open-source) ~> 5.6.1 * [OpenSSL](https://www.openssl.org/community/binaries.html) ~> 1.0.1m * IMPORTANT: Using the recommended version of OpenSSL is critical to avoid security vulnerabilities. * [VHACD](https://github.com/virneo/v-hacd)(clone this repository)(Optional) @@ -41,14 +41,14 @@ If you would like to use a specific install of a dependency instead of the versi Hifi uses CMake to generate build files and project files for your platform. ####Qt -In order for CMake to find the Qt5 find modules, you will need to set an ENV variable pointing to your Qt installation. +In order for CMake to find the Qt5 find modules, you will need to set a QT_CMAKE_PREFIX_PATH environment variable pointing to your Qt installation. -For example, a Qt5 5.5.1 installation to /usr/local/qt5 would require that QT_CMAKE_PREFIX_PATH be set with the following command. This can either be entered directly into your shell session before you build or in your shell profile (e.g.: ~/.bash_profile, ~/.bashrc, ~/.zshrc - this depends on your shell and environment). +This can either be entered directly into your shell session before you build or in your shell profile (e.g.: ~/.bash_profile, ~/.bashrc, ~/.zshrc - this depends on your shell and environment). The path it needs to be set to will depend on where and how Qt5 was installed. e.g. - export QT_CMAKE_PREFIX_PATH=/usr/local/qt/5.5.1/clang_64/lib/cmake/ - export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.5.1/lib/cmake + export QT_CMAKE_PREFIX_PATH=/usr/local/qt/5.6.1/clang_64/lib/cmake/ + export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.6.1-1/lib/cmake export QT_CMAKE_PREFIX_PATH=/usr/local/opt/qt5/lib/cmake ####Generating build files @@ -65,7 +65,7 @@ Any variables that need to be set for CMake to find dependencies can be set as E For example, to pass the QT_CMAKE_PREFIX_PATH variable during build file generation: - cmake .. -DQT_CMAKE_PREFIX_PATH=/usr/local/qt/5.5.1/lib/cmake + cmake .. -DQT_CMAKE_PREFIX_PATH=/usr/local/qt/5.6.1/lib/cmake ####Finding Dependencies diff --git a/BUILD_ANDROID.md b/BUILD_ANDROID.md index 88294f3040..bb8de0cc9a 100644 --- a/BUILD_ANDROID.md +++ b/BUILD_ANDROID.md @@ -4,13 +4,11 @@ Please read the [general build guide](BUILD.md) for information on dependencies You will need the following tools to build our Android targets. -* [cmake](http://www.cmake.org/download/) ~> 3.1.0 - * Note that this is a newer version required than the minimum for hifi desktop targets. -* [Qt](http://www.qt.io/download-open-source/#) ~> 5.4.0 - * Note that this is a newer version required than the minimum for hifi desktop targets. +* [cmake](http://www.cmake.org/download/) ~> 3.5.1 +* [Qt](http://www.qt.io/download-open-source/#) ~> 5.5.1 * [ant](http://ant.apache.org/bindownload.cgi) ~> 1.9.4 -* [Android NDK](https://developer.android.com/tools/sdk/ndk/index.html) = r10c -* [Android SDK](http://developer.android.com/sdk/installing/index.html) ~> 24.0.2 +* [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 @@ -19,6 +17,12 @@ You will need the following tools to build our Android targets. 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. +####Scribe + +High Fidelity has a shader pre-processing tool called `scribe` that various libraries will call on during the build process. You must compile scribe using your native toolchain (following the build instructions for your platform) and then pass a CMake variable or set an ENV variable `SCRIBE_PATH` that is a path to the scribe executable. + +CMake will fatally error if it does not find the scribe executable while using the android toolchain. + ####Optional Components * [Oculus Mobile SDK](https://developer.oculus.com/downloads/#sdk=mobile) ~> 0.4.2 @@ -31,11 +35,11 @@ This is most easily accomplished by installing all Android dependencies in the s ####Qt -Install Qt 5.4 for Android for your host environment from the [Qt downloads page](http://www.qt.io/download/). Install Qt to ``$ANDROID_LIB_DIR/Qt``. This is required so that our root CMakeLists file can help CMake find your Android Qt installation. +Install Qt 5.5.1 for Android for your host environment from the [Qt downloads page](http://www.qt.io/download/). Install Qt to ``$ANDROID_LIB_DIR/Qt``. This is required so that our root CMakeLists file can help CMake find your Android Qt installation. The component required for the Android build is the `Android armv7` component. -If you would like to install Qt to a different location, or attempt to build with a different Qt version, you can pass `ANDROID_QT_CMAKE_PREFIX_PATH` to CMake. Point to the `cmake` folder inside `$VERSION_NUMBER/android_armv7/lib`. Otherwise, our root CMakeLists will set it to `$ANDROID_LIB_DIR/Qt/5.3/android_armv7/lib/cmake`. +If you would like to install Qt to a different location, or attempt to build with a different Qt version, you can pass `ANDROID_QT_CMAKE_PREFIX_PATH` to CMake. Point to the `cmake` folder inside `$VERSION_NUMBER/android_armv7/lib`. Otherwise, our root CMakeLists will set it to `$ANDROID_LIB_DIR/Qt/5.5/android_armv7/lib/cmake`. ####OpenSSL diff --git a/BUILD_OSX.md b/BUILD_OSX.md index 1c9c5a9796..55d4276aa0 100644 --- a/BUILD_OSX.md +++ b/BUILD_OSX.md @@ -1,25 +1,31 @@ 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. ###Homebrew -[Homebrew](http://brew.sh/) is an excellent package manager for OS X. It makes install of all High Fidelity dependencies very simple. +[Homebrew](http://brew.sh/) is an excellent package manager for OS X. It makes install of some High Fidelity dependencies very simple. brew tap homebrew/versions - brew install cmake openssl qt55 + brew install cmake openssl -We no longer require install of qt5 via our [homebrew formulas repository](https://github.com/highfidelity/homebrew-formulas). Versions of Qt that are 5.5.x provide a mechanism to disable the wireless scanning we previously had a custom patch for. +###OpenSSL -###OpenSSL and Qt - -Assuming you've installed OpenSSL or Qt 5 using the homebrew instructions above, you'll need to set OPENSSL_ROOT_DIR and QT_CMAKE_PREFIX_PATH so CMake can find your installations. +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/ - -For Qt 5.5.1 installed via homebrew, set QT_CMAKE_PREFIX_PATH as follows. - export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt55/5.5.1/lib/cmake +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. -Note that these use the versions from homebrew formulae at the time of this writing, and the version in the path will likely change. +###Qt +You can use the online installer or the offline installer. + +* [Download the online installer](http://www.qt.io/download-open-source/#section-2) + * When it asks you to select components, select the following: + * Qt > Qt 5.6 + +* [Download the offline installer](http://download.qt.io/official_releases/qt/5.6/5.6.1-1/qt-opensource-mac-x64-clang-5.6.1-1.dmg) + +Once Qt is installed, you need to manually configure the following: +* Set the QT_CMAKE_PREFIX_PATH environment variable to your `Qt5.6.1/5.6/clang_64/lib/cmake/` directory. ###Xcode If Xcode is your editor of choice, you can ask CMake to generate Xcode project files instead of Unix Makefiles. diff --git a/BUILD_WIN.md b/BUILD_WIN.md index 25e20952ca..b8adaad8d1 100644 --- a/BUILD_WIN.md +++ b/BUILD_WIN.md @@ -27,17 +27,17 @@ We expect nmake.exe to be located at the following path. ###Qt You can use the online installer or the offline installer. If you use the offline installer, be sure to select the "OpenGL" version. -* [Download the online installer](http://qt-project.org/downloads) +* [Download the online installer](http://www.qt.io/download-open-source/#section-2) * When it asks you to select components, ONLY select one of the following, 32- or 64-bit to match your build preference: - * Qt > Qt 5.5.1 > **msvc2013 32-bit** - * Qt > Qt 5.5.1 > **msvc2013 64-bit** + * Qt > Qt 5.6.1 > **msvc2013 32-bit** + * Qt > Qt 5.6.1 > **msvc2013 64-bit** * Download the offline installer, 32- or 64-bit to match your build preference: - * [32-bit](http://download.qt.io/official_releases/qt/5.5/5.5.1/qt-opensource-windows-x86-msvc2013-5.5.1.exe) - * [64-bit](http://download.qt.io/official_releases/qt/5.5/5.5.1/qt-opensource-windows-x86-msvc2013_64-5.5.1.exe) + * [32-bit](http://download.qt.io/official_releases/qt/5.6/5.6.1-1/qt-opensource-windows-x86-msvc2013-5.6.1-1.exe) + * [64-bit](http://download.qt.io/official_releases/qt/5.6/5.6.1-1/qt-opensource-windows-x86-msvc2013_64-5.6.1-1.exe) Once Qt is installed, you need to manually configure the following: -* Set the QT_CMAKE_PREFIX_PATH environment variable to your `Qt\5.5.1\msvc2013\lib\cmake` or `Qt\5.5.1\msvc2013_64\lib\cmake` directory. +* Set the QT_CMAKE_PREFIX_PATH environment variable to your `Qt\5.6.1\msvc2013\lib\cmake` or `Qt\5.6.1\msvc2013_64\lib\cmake` directory. * You can set an environment variable from Control Panel > System > Advanced System Settings > Environment Variables > New ###External Libraries diff --git a/CMakeLists.txt b/CMakeLists.txt index 2bcd2352de..93ad411229 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,8 @@ 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) @@ -64,7 +66,7 @@ if (WIN32) 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_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) + 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") @@ -72,18 +74,23 @@ else () endif () endif(WIN32) -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 (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() + 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) @@ -98,7 +105,7 @@ endif () if (ANDROID) if (NOT ANDROID_QT_CMAKE_PREFIX_PATH) - set(QT_CMAKE_PREFIX_PATH ${ANDROID_LIB_DIR}/Qt/5.4/android_armv7/lib/cmake) + 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 () @@ -236,7 +243,9 @@ if (NOT ANDROID) endif() if (ANDROID OR DESKTOP_GVR) + add_subdirectory(interface) add_subdirectory(gvr-interface) + add_subdirectory(plugins) endif () if (DEFINED ENV{HIFI_MEMORY_DEBUGGING}) diff --git a/assignment-client/CMakeLists.txt b/assignment-client/CMakeLists.txt index d0fd2c1176..54afabfd21 100644 --- a/assignment-client/CMakeLists.txt +++ b/assignment-client/CMakeLists.txt @@ -2,6 +2,11 @@ set(TARGET_NAME assignment-client) setup_hifi_project(Core Gui Network Script Quick Widgets WebSockets) +# Fix up the rpath so macdeployqt works +if (APPLE) + set_target_properties(${TARGET_NAME} PROPERTIES INSTALL_RPATH "@executable_path/../Frameworks") +endif () + # link in the shared libraries link_hifi_libraries( audio avatars octree gpu model fbx entities diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 65989b389e..d87a5f1cc9 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -413,6 +413,9 @@ void AvatarMixer::handleAvatarDataPacket(QSharedPointer message } void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer message, SharedNodePointer senderNode) { + auto nodeList = DependencyManager::get(); + nodeList->getOrCreateLinkedData(senderNode); + if (senderNode->getLinkedData()) { AvatarMixerClientData* nodeData = dynamic_cast(senderNode->getLinkedData()); if (nodeData != nullptr) { diff --git a/cmake/android/android.toolchain.cmake b/cmake/android/android.toolchain.cmake index e1f50abfa6..806cef6b18 100755 --- a/cmake/android/android.toolchain.cmake +++ b/cmake/android/android.toolchain.cmake @@ -29,8 +29,8 @@ # POSSIBILITY OF SUCH DAMAGE. # ------------------------------------------------------------------------------ -# Android CMake toolchain file, for use with the Android NDK r5-r10c -# Requires cmake 2.6.3 or newer (2.8.5 or newer is recommended). +# Android CMake toolchain file, for use with the Android NDK r5-r10d +# Requires cmake 2.6.3 or newer (2.8.9 or newer is recommended). # See home page: https://github.com/taka-no-me/android-cmake # # Usage Linux: @@ -39,12 +39,6 @@ # $ cmake -DCMAKE_TOOLCHAIN_FILE=path/to/the/android.toolchain.cmake .. # $ make -j8 # -# Usage Linux (using standalone toolchain): -# $ export ANDROID_STANDALONE_TOOLCHAIN=/absolute/path/to/android-toolchain -# $ mkdir build && cd build -# $ cmake -DCMAKE_TOOLCHAIN_FILE=path/to/the/android.toolchain.cmake .. -# $ make -j8 -# # Usage Windows: # You need native port of make to build your project. # Android NDK r7 (and newer) already has make.exe on board. @@ -63,11 +57,6 @@ # ANDROID_NDK=/opt/android-ndk - path to the NDK root. # Can be set as environment variable. Can be set only at first cmake run. # -# ANDROID_STANDALONE_TOOLCHAIN=/opt/android-toolchain - path to the -# standalone toolchain. This option is not used if full NDK is found -# (ignored if ANDROID_NDK is set). -# Can be set as environment variable. Can be set only at first cmake run. -# # ANDROID_ABI=armeabi-v7a - specifies the target Application Binary # Interface (ABI). This option nearly matches to the APP_ABI variable # used by ndk-build tool from Android NDK. @@ -123,8 +112,8 @@ # * x86_64-clang3.5 # # ANDROID_FORCE_ARM_BUILD=OFF - set ON to generate 32-bit ARM instructions -# instead of Thumb. Is not available for "x86" (inapplicable) and -# "armeabi-v6 with VFP" (is forced to be ON) ABIs. +# instead of Thumb. Is not available for "armeabi-v6 with VFP" +# (is forced to be ON) ABI. # # ANDROID_NO_UNDEFINED=ON - set ON to show all undefined symbols as linker # errors even if they are not used. @@ -133,13 +122,6 @@ # libraries. Automatically turned for NDK r5x and r6x due to GLESv2 # problems. # -# LIBRARY_OUTPUT_PATH_ROOT=${CMAKE_SOURCE_DIR} - where to output binary -# files. See additional details below. -# -# ANDROID_SET_OBSOLETE_VARIABLES=ON - if set, then toolchain defines some -# obsolete variables which were used by previous versions of this file for -# backward compatibility. -# # ANDROID_STL=gnustl_static - specify the runtime to use. # # Possible values are: @@ -172,6 +154,8 @@ # Implies -frtti -fno-exceptions. # Available for NDK r7b and newer. # Silently degrades to gnustl_static if not available. +# c++_static -> Use the LLVM libc++ runtime as a static library. +# c++_shared -> Use the LLVM libc++ runtime as a shared library. # # ANDROID_STL_FORCE_FEATURES=ON - turn rtti and exceptions support based on # chosen runtime. If disabled, then the user is responsible for settings @@ -200,12 +184,6 @@ # will be set true, mutually exclusive. NEON option will be set true # if VFP is set to NEON. # -# LIBRARY_OUTPUT_PATH_ROOT should be set in cache to determine where Android -# libraries will be installed. -# Default is ${CMAKE_SOURCE_DIR}, and the android libs will always be -# under the ${LIBRARY_OUTPUT_PATH_ROOT}/libs/${ANDROID_NDK_ABI_NAME} -# (depending on the target ABI). This is convenient for Android packaging. -# # ------------------------------------------------------------------------------ cmake_minimum_required( VERSION 2.6.3 ) @@ -235,22 +213,22 @@ endif() # this one not so much set( CMAKE_SYSTEM_VERSION 1 ) -# rpath makes low sence for Android +# rpath makes low sense for Android set( CMAKE_SHARED_LIBRARY_RUNTIME_C_FLAG "" ) set( CMAKE_SKIP_RPATH TRUE CACHE BOOL "If set, runtime paths are not added when using shared libraries." ) # NDK search paths -set( ANDROID_SUPPORTED_NDK_VERSIONS ${ANDROID_EXTRA_NDK_VERSIONS} -r10c -r10b -r10 -r9d -r9c -r9b -r9 -r8e -r8d -r8c -r8b -r8 -r7c -r7b -r7 -r6b -r6 -r5c -r5b -r5 "" ) -if(NOT DEFINED ANDROID_NDK_SEARCH_PATHS) +set( ANDROID_SUPPORTED_NDK_VERSIONS ${ANDROID_EXTRA_NDK_VERSIONS} -r10d -r10c -r10b -r10 -r9d -r9c -r9b -r9 -r8e -r8d -r8c -r8b -r8 -r7c -r7b -r7 -r6b -r6 -r5c -r5b -r5 "" ) +if( NOT DEFINED ANDROID_NDK_SEARCH_PATHS ) if( CMAKE_HOST_WIN32 ) file( TO_CMAKE_PATH "$ENV{PROGRAMFILES}" ANDROID_NDK_SEARCH_PATHS ) - set( ANDROID_NDK_SEARCH_PATHS "${ANDROID_NDK_SEARCH_PATHS}/android-ndk" "$ENV{SystemDrive}/NVPACK/android-ndk" ) + set( ANDROID_NDK_SEARCH_PATHS "${ANDROID_NDK_SEARCH_PATHS}" "$ENV{SystemDrive}/NVPACK" ) else() file( TO_CMAKE_PATH "$ENV{HOME}" ANDROID_NDK_SEARCH_PATHS ) - set( ANDROID_NDK_SEARCH_PATHS /opt/android-ndk "${ANDROID_NDK_SEARCH_PATHS}/NVPACK/android-ndk" ) + set( ANDROID_NDK_SEARCH_PATHS /opt "${ANDROID_NDK_SEARCH_PATHS}/NVPACK" ) endif() endif() -if(NOT DEFINED ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH) +if( NOT DEFINED ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH ) set( ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH /opt/android-toolchain ) endif() @@ -272,106 +250,90 @@ set( ANDROID_DEFAULT_NDK_API_LEVEL_mips64 21 ) macro( __LIST_FILTER listvar regex ) - if( ${listvar} ) - foreach( __val ${${listvar}} ) - if( __val MATCHES "${regex}" ) - list( REMOVE_ITEM ${listvar} "${__val}" ) - endif() - endforeach() - endif() + if( ${listvar} ) + foreach( __val ${${listvar}} ) + if( __val MATCHES "${regex}" ) + list( REMOVE_ITEM ${listvar} "${__val}" ) + endif() + endforeach() + endif() endmacro() macro( __INIT_VARIABLE var_name ) - set( __test_path 0 ) - foreach( __var ${ARGN} ) - if( __var STREQUAL "PATH" ) - set( __test_path 1 ) - break() - endif() - endforeach() - if( __test_path AND NOT EXISTS "${${var_name}}" ) - unset( ${var_name} CACHE ) - endif() - if( "${${var_name}}" STREQUAL "" ) - set( __values 0 ) + set( __test_path 0 ) foreach( __var ${ARGN} ) - if( __var STREQUAL "VALUES" ) - set( __values 1 ) - elseif( NOT __var STREQUAL "PATH" ) - set( __obsolete 0 ) - if( __var MATCHES "^OBSOLETE_.*$" ) - string( REPLACE "OBSOLETE_" "" __var "${__var}" ) - set( __obsolete 1 ) - endif() - if( __var MATCHES "^ENV_.*$" ) - string( REPLACE "ENV_" "" __var "${__var}" ) - set( __value "$ENV{${__var}}" ) - elseif( DEFINED ${__var} ) - set( __value "${${__var}}" ) - else() - if( __values ) - set( __value "${__var}" ) - else() - set( __value "" ) - endif() - endif() - if( NOT "${__value}" STREQUAL "" ) - if( __test_path ) - if( EXISTS "${__value}" ) - file( TO_CMAKE_PATH "${__value}" ${var_name} ) - if( __obsolete AND NOT _CMAKE_IN_TRY_COMPILE ) - message( WARNING "Using value of obsolete variable ${__var} as initial value for ${var_name}. Please note, that ${__var} can be completely removed in future versions of the toolchain." ) - endif() - break() - endif() - else() - set( ${var_name} "${__value}" ) - if( __obsolete AND NOT _CMAKE_IN_TRY_COMPILE ) - message( WARNING "Using value of obsolete variable ${__var} as initial value for ${var_name}. Please note, that ${__var} can be completely removed in future versions of the toolchain." ) - endif() + if( __var STREQUAL "PATH" ) + set( __test_path 1 ) break() - endif() endif() - endif() endforeach() - unset( __value ) - unset( __values ) - unset( __obsolete ) - elseif( __test_path ) - file( TO_CMAKE_PATH "${${var_name}}" ${var_name} ) - endif() - unset( __test_path ) + + if( __test_path AND NOT EXISTS "${${var_name}}" ) + unset( ${var_name} CACHE ) + endif() + + if( " ${${var_name}}" STREQUAL " " ) + set( __values 0 ) + foreach( __var ${ARGN} ) + if( __var STREQUAL "VALUES" ) + set( __values 1 ) + elseif( NOT __var STREQUAL "PATH" ) + if( __var MATCHES "^ENV_.*$" ) + string( REPLACE "ENV_" "" __var "${__var}" ) + set( __value "$ENV{${__var}}" ) + elseif( DEFINED ${__var} ) + set( __value "${${__var}}" ) + elseif( __values ) + set( __value "${__var}" ) + else() + set( __value "" ) + endif() + + if( NOT " ${__value}" STREQUAL " " AND (NOT __test_path OR EXISTS "${__value}") ) + set( ${var_name} "${__value}" ) + break() + endif() + endif() + endforeach() + unset( __value ) + unset( __values ) + endif() + + if( __test_path ) + file( TO_CMAKE_PATH "${${var_name}}" ${var_name} ) + endif() + unset( __test_path ) endmacro() macro( __DETECT_NATIVE_API_LEVEL _var _path ) - SET( __ndkApiLevelRegex "^[\t ]*#define[\t ]+__ANDROID_API__[\t ]+([0-9]+)[\t ]*.*$" ) - FILE( STRINGS ${_path} __apiFileContent REGEX "${__ndkApiLevelRegex}" ) - if( NOT __apiFileContent ) - message( SEND_ERROR "Could not get Android native API level. Probably you have specified invalid level value, or your copy of NDK/toolchain is broken." ) - endif() - string( REGEX REPLACE "${__ndkApiLevelRegex}" "\\1" ${_var} "${__apiFileContent}" ) - unset( __apiFileContent ) - unset( __ndkApiLevelRegex ) + set( __ndkApiLevelRegex "^[\t ]*#define[\t ]+__ANDROID_API__[\t ]+([0-9]+)[\t ]*.*$" ) + file( STRINGS ${_path} __apiFileContent REGEX "${__ndkApiLevelRegex}" ) + if( NOT __apiFileContent ) + message( SEND_ERROR "Could not get Android native API level. Probably you have specified invalid level value, or your copy of NDK/toolchain is broken." ) + endif() + string( REGEX REPLACE "${__ndkApiLevelRegex}" "\\1" ${_var} "${__apiFileContent}" ) + unset( __apiFileContent ) + unset( __ndkApiLevelRegex ) endmacro() macro( __DETECT_TOOLCHAIN_MACHINE_NAME _var _root ) if( EXISTS "${_root}" ) - file( GLOB __gccExePath RELATIVE "${_root}/bin/" "${_root}/bin/*-gcc${TOOL_OS_SUFFIX}" ) - __LIST_FILTER( __gccExePath "^[.].*" ) - list( LENGTH __gccExePath __gccExePathsCount ) - if( NOT __gccExePathsCount EQUAL 1 AND NOT _CMAKE_IN_TRY_COMPILE ) - message( WARNING "Could not determine machine name for compiler from ${_root}" ) - set( ${_var} "" ) + file( GLOB __gccExePath RELATIVE "${_root}/bin/" "${_root}/bin/*-gcc${TOOL_OS_SUFFIX}" ) + __LIST_FILTER( __gccExePath "^[.].*" ) + list( LENGTH __gccExePath __gccExePathsCount ) + if( NOT __gccExePathsCount EQUAL 1 AND NOT _CMAKE_IN_TRY_COMPILE ) + message( WARNING "Could not determine machine name for compiler from ${_root}" ) + set( ${_var} "" ) + else() + get_filename_component( __gccExeName "${__gccExePath}" NAME_WE ) + string( REPLACE "-gcc" "" ${_var} "${__gccExeName}" ) + endif() + unset( __gccExePath ) + unset( __gccExePathsCount ) + unset( __gccExeName ) else() - get_filename_component( __gccExeName "${__gccExePath}" NAME_WE ) - string( REPLACE "-gcc" "" ${_var} "${__gccExeName}" ) + set( ${_var} "" ) endif() - unset( __gccExePath ) - unset( __gccExePathsCount ) - unset( __gccExeName ) - else() - set( ${_var} "" ) - endif() endmacro() @@ -419,17 +381,19 @@ if( NOT ANDROID_NDK_HOST_X64 ) endif() # see if we have path to Android NDK -__INIT_VARIABLE( ANDROID_NDK PATH ENV_ANDROID_NDK ) +if( NOT ANDROID_NDK AND NOT ANDROID_STANDALONE_TOOLCHAIN ) + __INIT_VARIABLE( ANDROID_NDK PATH ENV_ANDROID_NDK ) +endif() if( NOT ANDROID_NDK ) # see if we have path to Android standalone toolchain - __INIT_VARIABLE( ANDROID_STANDALONE_TOOLCHAIN PATH ENV_ANDROID_STANDALONE_TOOLCHAIN OBSOLETE_ANDROID_NDK_TOOLCHAIN_ROOT OBSOLETE_ENV_ANDROID_NDK_TOOLCHAIN_ROOT ) + __INIT_VARIABLE( ANDROID_STANDALONE_TOOLCHAIN PATH ENV_ANDROID_STANDALONE_TOOLCHAIN ) if( NOT ANDROID_STANDALONE_TOOLCHAIN ) #try to find Android NDK in one of the the default locations set( __ndkSearchPaths ) foreach( __ndkSearchPath ${ANDROID_NDK_SEARCH_PATHS} ) foreach( suffix ${ANDROID_SUPPORTED_NDK_VERSIONS} ) - list( APPEND __ndkSearchPaths "${__ndkSearchPath}${suffix}" ) + list( APPEND __ndkSearchPaths "${__ndkSearchPath}/android-ndk${suffix}" ) endforeach() endforeach() __INIT_VARIABLE( ANDROID_NDK PATH VALUES ${__ndkSearchPaths} ) @@ -487,7 +451,7 @@ else() or export ANDROID_STANDALONE_TOOLCHAIN=~/my-android-toolchain or put the toolchain or NDK in the default path: - sudo ln -s ~/my-android-ndk ${ANDROID_NDK_SEARCH_PATH} + sudo ln -s ~/my-android-ndk ${ANDROID_NDK_SEARCH_PATH}/android-ndk sudo ln -s ~/my-android-toolchain ${ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH}" ) endif() @@ -636,7 +600,7 @@ if( BUILD_WITH_ANDROID_NDK ) endif() if( NOT __availableToolchains ) file( GLOB __availableToolchainsLst RELATIVE "${ANDROID_NDK_TOOLCHAINS_PATH}" "${ANDROID_NDK_TOOLCHAINS_PATH}/*" ) - if( __availableToolchains ) + if( __availableToolchainsLst ) list(SORT __availableToolchainsLst) # we need clang to go after gcc endif() __LIST_FILTER( __availableToolchainsLst "^[.]" ) @@ -669,7 +633,7 @@ if( NOT ANDROID_SUPPORTED_ABIS ) endif() # choose target ABI -__INIT_VARIABLE( ANDROID_ABI OBSOLETE_ARM_TARGET OBSOLETE_ARM_TARGETS VALUES ${ANDROID_SUPPORTED_ABIS} ) +__INIT_VARIABLE( ANDROID_ABI VALUES ${ANDROID_SUPPORTED_ABIS} ) # verify that target ABI is supported list( FIND ANDROID_SUPPORTED_ABIS "${ANDROID_ABI}" __androidAbiIdx ) if( __androidAbiIdx EQUAL -1 ) @@ -760,7 +724,7 @@ if( CMAKE_BINARY_DIR AND EXISTS "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMa endif() if( ANDROID_ARCH_NAME STREQUAL "arm" AND NOT ARMEABI_V6 ) - __INIT_VARIABLE( ANDROID_FORCE_ARM_BUILD OBSOLETE_FORCE_ARM VALUES OFF ) + __INIT_VARIABLE( ANDROID_FORCE_ARM_BUILD VALUES OFF ) set( ANDROID_FORCE_ARM_BUILD ${ANDROID_FORCE_ARM_BUILD} CACHE BOOL "Use 32-bit ARM instructions instead of Thumb-1" FORCE ) mark_as_advanced( ANDROID_FORCE_ARM_BUILD ) else() @@ -845,6 +809,7 @@ else() unset( __realApiLevel ) endif() set( ANDROID_NATIVE_API_LEVEL "${ANDROID_NATIVE_API_LEVEL}" CACHE STRING "Android API level for native code" FORCE ) + set( CMAKE_ANDROID_API ${ANDROID_NATIVE_API_LEVEL} ) if( CMAKE_VERSION VERSION_GREATER "2.8" ) list( SORT ANDROID_SUPPORTED_NATIVE_API_LEVELS ) set_property( CACHE ANDROID_NATIVE_API_LEVEL PROPERTY STRINGS ${ANDROID_SUPPORTED_NATIVE_API_LEVELS} ) @@ -863,23 +828,14 @@ endif() # runtime choice (STL, rtti, exceptions) if( NOT ANDROID_STL ) - # honor legacy ANDROID_USE_STLPORT - if( DEFINED ANDROID_USE_STLPORT ) - if( ANDROID_USE_STLPORT ) - set( ANDROID_STL stlport_static ) - endif() - message( WARNING "You are using an obsolete variable ANDROID_USE_STLPORT to select the STL variant. Use -DANDROID_STL=stlport_static instead." ) - endif() - if( NOT ANDROID_STL ) set( ANDROID_STL gnustl_static ) - endif() endif() set( ANDROID_STL "${ANDROID_STL}" CACHE STRING "C++ runtime" ) set( ANDROID_STL_FORCE_FEATURES ON CACHE BOOL "automatically configure rtti and exceptions support based on C++ runtime" ) mark_as_advanced( ANDROID_STL ANDROID_STL_FORCE_FEATURES ) if( BUILD_WITH_ANDROID_NDK ) - if( NOT "${ANDROID_STL}" MATCHES "^(none|system|system_re|gabi\\+\\+_static|gabi\\+\\+_shared|stlport_static|stlport_shared|gnustl_static|gnustl_shared)$") + if( NOT "${ANDROID_STL}" MATCHES "^(none|system|system_re|gabi\\+\\+_static|gabi\\+\\+_shared|stlport_static|stlport_shared|gnustl_static|gnustl_shared|c\\+\\+_static|c\\+\\+_shared)$") message( FATAL_ERROR "ANDROID_STL is set to invalid value \"${ANDROID_STL}\". The possible values are: none -> Do not configure the runtime. @@ -891,6 +847,8 @@ The possible values are: stlport_shared -> Use the STLport runtime as a shared library. gnustl_static -> (default) Use the GNU STL as a static library. gnustl_shared -> Use the GNU STL as a shared library. + c++_static -> Use the LLVM libc++ runtime as a static library. + c++_shared -> Use the LLVM libc++ runtime as a shared library. " ) endif() elseif( BUILD_WITH_STANDALONE_TOOLCHAIN ) @@ -1033,7 +991,7 @@ if( BUILD_WITH_ANDROID_NDK ) set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_NDK}/sources/cxx-stl/system/include" ) elseif( ANDROID_STL MATCHES "gabi" ) if( ANDROID_NDK_RELEASE_NUM LESS 7000 ) # before r7 - message( FATAL_ERROR "gabi++ is not awailable in your NDK. You have to upgrade to NDK r7 or newer to use gabi++.") + message( FATAL_ERROR "gabi++ is not available in your NDK. You have to upgrade to NDK r7 or newer to use gabi++.") endif() set( ANDROID_RTTI ON ) set( ANDROID_EXCEPTIONS OFF ) @@ -1066,12 +1024,40 @@ if( BUILD_WITH_ANDROID_NDK ) else() set( __libstl "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++" ) endif() - set( ANDROID_STL_INCLUDE_DIRS "${__libstl}/include" "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/include" ) + set( ANDROID_STL_INCLUDE_DIRS "${__libstl}/include" "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/include" "${__libstl}/include/backward" ) if( EXISTS "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/libgnustl_static.a" ) set( __libstl "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/libgnustl_static.a" ) else() set( __libstl "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/libstdc++.a" ) endif() + elseif( ANDROID_STL MATCHES "c\\+\\+_shared" OR ANDROID_STL MATCHES "c\\+\\+_static" ) + set( ANDROID_EXCEPTIONS ON ) + set( ANDROID_RTTI ON ) + set( ANDROID_CXX_ROOT "${ANDROID_NDK}/sources/cxx-stl/" ) + set( ANDROID_LLVM_ROOT "${ANDROID_CXX_ROOT}/llvm-libc++" ) + + if( X86 ) + set( ANDROID_ABI_INCLUDE_DIRS "${ANDROID_CXX_ROOT}/gabi++/include" ) + else() + set( ANDROID_ABI_INCLUDE_DIRS "${ANDROID_CXX_ROOT}/llvm-libc++abi/include" ) + endif() + + set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_LLVM_ROOT}/libcxx/include" "${ANDROID_ABI_INCLUDE_DIRS}" ) + + # android support sfiles + include_directories ( SYSTEM ${ANDROID_NDK}/sources/android/support/include ) + + if(ANDROID_STL MATCHES "c\\+\\+_shared") + set ( LLVM_LIBRARY_NAME "libc++_shared.so") + else() + set ( LLVM_LIBRARY_NAME "libc++_static.a" ) + endif () + + if( EXISTS "${ANDROID_LLVM_ROOT}/libs/${ANDROID_NDK_ABI_NAME}/${LLVM_LIBRARY_NAME}" ) + set( __libstl "${ANDROID_LLVM_ROOT}/libs/${ANDROID_NDK_ABI_NAME}/${LLVM_LIBRARY_NAME}" ) + else() + message( FATAL_ERROR "Could not find libc++ library" ) + endif() else() message( FATAL_ERROR "Unknown runtime: ${ANDROID_STL}" ) endif() @@ -1144,7 +1130,12 @@ if( NOT CMAKE_C_COMPILER ) endif() set( CMAKE_ASM_COMPILER "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc${TOOL_OS_SUFFIX}" CACHE PATH "assembler" ) set( CMAKE_STRIP "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-strip${TOOL_OS_SUFFIX}" CACHE PATH "strip" ) - set( CMAKE_AR "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-ar${TOOL_OS_SUFFIX}" CACHE PATH "archive" ) + if( EXISTS "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc-ar${TOOL_OS_SUFFIX}" ) + # Use gcc-ar if we have it for better LTO support. + set( CMAKE_AR "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc-ar${TOOL_OS_SUFFIX}" CACHE PATH "archive" ) + else() + set( CMAKE_AR "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-ar${TOOL_OS_SUFFIX}" CACHE PATH "archive" ) + endif() set( CMAKE_LINKER "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-ld${TOOL_OS_SUFFIX}" CACHE PATH "linker" ) set( CMAKE_NM "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-nm${TOOL_OS_SUFFIX}" CACHE PATH "nm" ) set( CMAKE_OBJCOPY "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-objcopy${TOOL_OS_SUFFIX}" CACHE PATH "objcopy" ) @@ -1168,7 +1159,7 @@ endif() include( CMakeForceCompiler ) CMAKE_FORCE_C_COMPILER( "${CMAKE_C_COMPILER}" GNU ) if( ANDROID_COMPILER_IS_CLANG ) - set( CMAKE_C_COMPILER_ID Clang) + set( CMAKE_C_COMPILER_ID Clang ) endif() set( CMAKE_C_PLATFORM_ID Linux ) if( X86_64 OR MIPS64 OR ARM64_V8A ) @@ -1195,6 +1186,14 @@ set( CMAKE_ASM_COMPILER_FORCED TRUE ) set( CMAKE_COMPILER_IS_GNUASM 1) set( CMAKE_ASM_SOURCE_FILE_EXTENSIONS s S asm ) +foreach( lang C CXX ASM ) + if( ANDROID_COMPILER_IS_CLANG ) + set( CMAKE_${lang}_COMPILER_VERSION ${ANDROID_CLANG_VERSION} ) + else() + set( CMAKE_${lang}_COMPILER_VERSION ${ANDROID_COMPILER_VERSION} ) + endif() +endforeach() + # flags and definitions remove_definitions( -DANDROID ) add_definitions( -DANDROID ) @@ -1225,14 +1224,14 @@ endif() # NDK flags if (ARM64_V8A ) - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fpic -ffunction-sections -funwind-tables" ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funwind-tables" ) set( ANDROID_CXX_FLAGS_RELEASE "-fomit-frame-pointer -fstrict-aliasing" ) set( ANDROID_CXX_FLAGS_DEBUG "-fno-omit-frame-pointer -fno-strict-aliasing" ) if( NOT ANDROID_COMPILER_IS_CLANG ) set( ANDROID_CXX_FLAGS_RELEASE "${ANDROID_CXX_FLAGS_RELEASE} -funswitch-loops -finline-limit=300" ) endif() elseif( ARMEABI OR ARMEABI_V7A) - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fpic -funwind-tables" ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funwind-tables" ) if( NOT ANDROID_FORCE_ARM_BUILD AND NOT ARMEABI_V6 ) set( ANDROID_CXX_FLAGS_RELEASE "-mthumb -fomit-frame-pointer -fno-strict-aliasing" ) set( ANDROID_CXX_FLAGS_DEBUG "-marm -fno-omit-frame-pointer -fno-strict-aliasing" ) @@ -1251,13 +1250,11 @@ elseif( X86 OR X86_64 ) set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funwind-tables" ) if( NOT ANDROID_COMPILER_IS_CLANG ) set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funswitch-loops -finline-limit=300" ) - else() - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fPIC" ) endif() set( ANDROID_CXX_FLAGS_RELEASE "-fomit-frame-pointer -fstrict-aliasing" ) set( ANDROID_CXX_FLAGS_DEBUG "-fno-omit-frame-pointer -fno-strict-aliasing" ) elseif( MIPS OR MIPS64 ) - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fpic -fno-strict-aliasing -finline-functions -ffunction-sections -funwind-tables -fmessage-length=0" ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fno-strict-aliasing -finline-functions -funwind-tables -fmessage-length=0" ) set( ANDROID_CXX_FLAGS_RELEASE "-fomit-frame-pointer" ) set( ANDROID_CXX_FLAGS_DEBUG "-fno-omit-frame-pointer" ) if( NOT ANDROID_COMPILER_IS_CLANG ) @@ -1342,7 +1339,7 @@ if( ANDROID_NDK_RELEASE_NUM LESS 7000 ) # before r7 else() __INIT_VARIABLE( ANDROID_SO_UNDEFINED VALUES OFF ) endif() -__INIT_VARIABLE( ANDROID_NO_UNDEFINED OBSOLETE_NO_UNDEFINED VALUES ON ) +__INIT_VARIABLE( ANDROID_NO_UNDEFINED VALUES ON ) __INIT_VARIABLE( ANDROID_FUNCTION_LEVEL_LINKING VALUES ON ) __INIT_VARIABLE( ANDROID_GOLD_LINKER VALUES ON ) __INIT_VARIABLE( ANDROID_NOEXECSTACK VALUES ON ) @@ -1350,7 +1347,7 @@ __INIT_VARIABLE( ANDROID_RELRO VALUES ON ) set( ANDROID_NO_UNDEFINED ${ANDROID_NO_UNDEFINED} CACHE BOOL "Show all undefined symbols as linker errors" ) set( ANDROID_SO_UNDEFINED ${ANDROID_SO_UNDEFINED} CACHE BOOL "Allows or disallows undefined symbols in shared libraries" ) -set( ANDROID_FUNCTION_LEVEL_LINKING ${ANDROID_FUNCTION_LEVEL_LINKING} CACHE BOOL "Allows or disallows undefined symbols in shared libraries" ) +set( ANDROID_FUNCTION_LEVEL_LINKING ${ANDROID_FUNCTION_LEVEL_LINKING} CACHE BOOL "Put each function in separate section and enable garbage collection of unused input sections at link time" ) set( ANDROID_GOLD_LINKER ${ANDROID_GOLD_LINKER} CACHE BOOL "Enables gold linker" ) set( ANDROID_NOEXECSTACK ${ANDROID_NOEXECSTACK} CACHE BOOL "Allows or disallows undefined symbols in shared libraries" ) set( ANDROID_RELRO ${ANDROID_RELRO} CACHE BOOL "Enables RELRO - a memory corruption mitigation technique" ) @@ -1452,6 +1449,16 @@ if( MIPS AND BUILD_WITH_ANDROID_NDK AND ANDROID_NDK_RELEASE STREQUAL "r8" ) set( CMAKE_EXE_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_GCC_TOOLCHAIN_NAME}/mipself.x ${CMAKE_EXE_LINKER_FLAGS}" ) endif() +# pie/pic +if( NOT (ANDROID_NATIVE_API_LEVEL LESS 16) AND (NOT DEFINED ANDROID_APP_PIE OR ANDROID_APP_PIE) AND (CMAKE_VERSION VERSION_GREATER 2.8.8) ) + set( CMAKE_POSITION_INDEPENDENT_CODE TRUE ) + set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fPIE -pie") +else() + set( CMAKE_POSITION_INDEPENDENT_CODE FALSE ) + set( CMAKE_CXX_FLAGS "-fpic ${CMAKE_CXX_FLAGS}" ) + set( CMAKE_C_FLAGS "-fpic ${CMAKE_C_FLAGS}" ) +endif() + # configure rtti if( DEFINED ANDROID_RTTI AND ANDROID_STL_FORCE_FEATURES ) if( ANDROID_RTTI ) @@ -1515,27 +1522,31 @@ if( ANDROID_EXPLICIT_CRT_LINK ) endif() # setup output directories -set( LIBRARY_OUTPUT_PATH_ROOT ${CMAKE_SOURCE_DIR} CACHE PATH "root for library output, set this to change where android libs are installed to" ) set( CMAKE_INSTALL_PREFIX "${ANDROID_TOOLCHAIN_ROOT}/user" CACHE STRING "path for installing" ) -if(NOT _CMAKE_IN_TRY_COMPILE) - if( EXISTS "${CMAKE_SOURCE_DIR}/jni/CMakeLists.txt" ) - set( EXECUTABLE_OUTPUT_PATH "${LIBRARY_OUTPUT_PATH_ROOT}/bin/${ANDROID_NDK_ABI_NAME}" CACHE PATH "Output directory for applications" ) - else() - set( EXECUTABLE_OUTPUT_PATH "${LIBRARY_OUTPUT_PATH_ROOT}/bin" CACHE PATH "Output directory for applications" ) - endif() - set( LIBRARY_OUTPUT_PATH "${LIBRARY_OUTPUT_PATH_ROOT}/libs/${ANDROID_NDK_ABI_NAME}" CACHE PATH "path for android libs" ) +if( DEFINED LIBRARY_OUTPUT_PATH_ROOT + OR EXISTS "${CMAKE_SOURCE_DIR}/AndroidManifest.xml" + OR (EXISTS "${CMAKE_SOURCE_DIR}/../AndroidManifest.xml" AND EXISTS "${CMAKE_SOURCE_DIR}/../jni/") ) + set( LIBRARY_OUTPUT_PATH_ROOT ${CMAKE_SOURCE_DIR} CACHE PATH "Root for binaries output, set this to change where Android libs are installed to" ) + if( NOT _CMAKE_IN_TRY_COMPILE ) + if( EXISTS "${CMAKE_SOURCE_DIR}/jni/CMakeLists.txt" ) + set( EXECUTABLE_OUTPUT_PATH "${LIBRARY_OUTPUT_PATH_ROOT}/bin/${ANDROID_NDK_ABI_NAME}" CACHE PATH "Output directory for applications" ) + else() + set( EXECUTABLE_OUTPUT_PATH "${LIBRARY_OUTPUT_PATH_ROOT}/bin" CACHE PATH "Output directory for applications" ) + endif() + set( LIBRARY_OUTPUT_PATH "${LIBRARY_OUTPUT_PATH_ROOT}/libs/${ANDROID_NDK_ABI_NAME}" CACHE PATH "Output directory for Android libs" ) + endif() endif() # copy shaed stl library to build directory -if( NOT _CMAKE_IN_TRY_COMPILE AND __libstl MATCHES "[.]so$" ) - get_filename_component( __libstlname "${__libstl}" NAME ) - execute_process( COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${__libstl}" "${LIBRARY_OUTPUT_PATH}/${__libstlname}" RESULT_VARIABLE __fileCopyProcess ) - if( NOT __fileCopyProcess EQUAL 0 OR NOT EXISTS "${LIBRARY_OUTPUT_PATH}/${__libstlname}") - message( SEND_ERROR "Failed copying of ${__libstl} to the ${LIBRARY_OUTPUT_PATH}/${__libstlname}" ) - endif() - unset( __fileCopyProcess ) - unset( __libstlname ) +if( NOT _CMAKE_IN_TRY_COMPILE AND __libstl MATCHES "[.]so$" AND DEFINED LIBRARY_OUTPUT_PATH ) + get_filename_component( __libstlname "${__libstl}" NAME ) + execute_process( COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${__libstl}" "${LIBRARY_OUTPUT_PATH}/${__libstlname}" RESULT_VARIABLE __fileCopyProcess ) + if( NOT __fileCopyProcess EQUAL 0 OR NOT EXISTS "${LIBRARY_OUTPUT_PATH}/${__libstlname}") + message( SEND_ERROR "Failed copying of ${__libstl} to the ${LIBRARY_OUTPUT_PATH}/${__libstlname}" ) + endif() + unset( __fileCopyProcess ) + unset( __libstlname ) endif() @@ -1596,28 +1607,10 @@ macro( find_host_program ) endmacro() -macro( ANDROID_GET_ABI_RAWNAME TOOLCHAIN_FLAG VAR ) - if( "${TOOLCHAIN_FLAG}" STREQUAL "ARMEABI" ) - set( ${VAR} "armeabi" ) - elseif( "${TOOLCHAIN_FLAG}" STREQUAL "ARMEABI_V7A" ) - set( ${VAR} "armeabi-v7a" ) - elseif( "${TOOLCHAIN_FLAG}" STREQUAL "X86" ) - set( ${VAR} "x86" ) - elseif( "${TOOLCHAIN_FLAG}" STREQUAL "MIPS" ) - set( ${VAR} "mips" ) - else() - set( ${VAR} "unknown" ) - endif() -endmacro() - -if (POLICY CMP0054) - cmake_policy(SET CMP0054 NEW) -endif () - # export toolchain settings for the try_compile() command -if( NOT PROJECT_NAME STREQUAL "CMAKE_TRY_COMPILE" ) +if( NOT _CMAKE_IN_TRY_COMPILE ) set( __toolchain_config "") - foreach( __var NDK_CCACHE LIBRARY_OUTPUT_PATH_ROOT ANDROID_FORBID_SYGWIN ANDROID_SET_OBSOLETE_VARIABLES + foreach( __var NDK_CCACHE LIBRARY_OUTPUT_PATH_ROOT ANDROID_FORBID_SYGWIN ANDROID_NDK_HOST_X64 ANDROID_NDK ANDROID_NDK_LAYOUT @@ -1636,9 +1629,10 @@ if( NOT PROJECT_NAME STREQUAL "CMAKE_TRY_COMPILE" ) ANDROID_RELRO ANDROID_LIBM_PATH ANDROID_EXPLICIT_CRT_LINK + ANDROID_APP_PIE ) if( DEFINED ${__var} ) - if( "${__var}" MATCHES " ") + if( ${__var} MATCHES " ") set( __toolchain_config "${__toolchain_config}set( ${__var} \"${${__var}}\" CACHE INTERNAL \"\" )\n" ) else() set( __toolchain_config "${__toolchain_config}set( ${__var} ${${__var}} CACHE INTERNAL \"\" )\n" ) @@ -1663,16 +1657,6 @@ if( CMAKE_GENERATOR MATCHES "Ninja" AND CMAKE_HOST_WIN32 ) endif() -# set some obsolete variables for backward compatibility -set( ANDROID_SET_OBSOLETE_VARIABLES ON CACHE BOOL "Define obsolete Andrid-specific cmake variables" ) -mark_as_advanced( ANDROID_SET_OBSOLETE_VARIABLES ) -if( ANDROID_SET_OBSOLETE_VARIABLES ) - set( ANDROID_API_LEVEL ${ANDROID_NATIVE_API_LEVEL} ) - set( ARM_TARGET "${ANDROID_ABI}" ) - set( ARMEABI_NDK_NAME "${ANDROID_NDK_ABI_NAME}" ) -endif() - - # Variables controlling behavior or set by cmake toolchain: # ANDROID_ABI : "armeabi-v7a" (default), "armeabi", "armeabi-v7a with NEON", "armeabi-v7a with VFPV3", "armeabi-v6 with VFP", "x86", "mips", "arm64-v8a", "x86_64", "mips64" # ANDROID_NATIVE_API_LEVEL : 3,4,5,8,9,14,15,16,17,18,19,21 (depends on NDK version) @@ -1686,22 +1670,15 @@ endif() # ANDROID_RELRO : ON/OFF # ANDROID_FORCE_ARM_BUILD : ON/OFF # ANDROID_STL_FORCE_FEATURES : ON/OFF -# ANDROID_SET_OBSOLETE_VARIABLES : ON/OFF +# ANDROID_LIBM_PATH : path to libm.so (set to something like $(TOP)/out/target/product//obj/lib/libm.so) to workaround unresolved `sincos` # Can be set only at the first run: -# ANDROID_NDK -# ANDROID_STANDALONE_TOOLCHAIN +# ANDROID_NDK : path to your NDK install +# NDK_CCACHE : path to your ccache executable # ANDROID_TOOLCHAIN_NAME : the NDK name of compiler toolchain # ANDROID_NDK_HOST_X64 : try to use x86_64 toolchain (default for x64 host systems) # ANDROID_NDK_LAYOUT : the inner NDK structure (RELEASE, LINARO, ANDROID) # LIBRARY_OUTPUT_PATH_ROOT : -# NDK_CCACHE : -# Obsolete: -# ANDROID_API_LEVEL : superseded by ANDROID_NATIVE_API_LEVEL -# ARM_TARGET : superseded by ANDROID_ABI -# ARM_TARGETS : superseded by ANDROID_ABI (can be set only) -# ANDROID_NDK_TOOLCHAIN_ROOT : superseded by ANDROID_STANDALONE_TOOLCHAIN (can be set only) -# ANDROID_USE_STLPORT : superseded by ANDROID_STL=stlport_static -# ANDROID_LEVEL : superseded by ANDROID_NATIVE_API_LEVEL (completely removed) +# ANDROID_STANDALONE_TOOLCHAIN # # Primary read-only variables: # ANDROID : always TRUE @@ -1715,19 +1692,16 @@ endif() # X86_64 : TRUE if configured for x86_64 # MIPS : TRUE if configured for mips # MIPS64 : TRUE if configured for mips64 -# BUILD_ANDROID : always TRUE # BUILD_WITH_ANDROID_NDK : TRUE if NDK is used # BUILD_WITH_STANDALONE_TOOLCHAIN : TRUE if standalone toolchain is used # ANDROID_NDK_HOST_SYSTEM_NAME : "windows", "linux-x86" or "darwin-x86" depending on host platform # ANDROID_NDK_ABI_NAME : "armeabi", "armeabi-v7a", "x86", "mips", "arm64-v8a", "x86_64", "mips64" depending on ANDROID_ABI -# ANDROID_NDK_RELEASE : from r5 to r10c; set only for NDK +# ANDROID_NDK_RELEASE : from r5 to r10d; set only for NDK # ANDROID_NDK_RELEASE_NUM : numeric ANDROID_NDK_RELEASE version (1000*major+minor) # ANDROID_ARCH_NAME : "arm", "x86", "mips", "arm64", "x86_64", "mips64" depending on ANDROID_ABI # ANDROID_SYSROOT : path to the compiler sysroot # TOOL_OS_SUFFIX : "" or ".exe" depending on host platform # ANDROID_COMPILER_IS_CLANG : TRUE if clang compiler is used -# Obsolete: -# ARMEABI_NDK_NAME : superseded by ANDROID_NDK_ABI_NAME # # Secondary (less stable) read-only variables: # ANDROID_COMPILER_VERSION : GCC version used (not Clang version) @@ -1742,12 +1716,10 @@ endif() # ANDROID_RTTI : if rtti is enabled by the runtime # ANDROID_EXCEPTIONS : if exceptions are enabled by the runtime # ANDROID_GCC_TOOLCHAIN_NAME : read-only, differs from ANDROID_TOOLCHAIN_NAME only if clang is used -# ANDROID_LIBM_PATH : path to libm.so (set to something like $(TOP)/out/target/product//obj/lib/libm.so) to workaround unresolved `sincos` # # Defaults: # ANDROID_DEFAULT_NDK_API_LEVEL # ANDROID_DEFAULT_NDK_API_LEVEL_${ARCH} # ANDROID_NDK_SEARCH_PATHS -# ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH # ANDROID_SUPPORTED_ABIS_${ARCH} -# ANDROID_SUPPORTED_NDK_VERSIONS \ No newline at end of file +# ANDROID_SUPPORTED_NDK_VERSIONS diff --git a/cmake/externals/glew/CMakeLists.txt b/cmake/externals/glew/CMakeLists.txt index c9ad5f837a..28a599bfa6 100644 --- a/cmake/externals/glew/CMakeLists.txt +++ b/cmake/externals/glew/CMakeLists.txt @@ -15,7 +15,6 @@ ExternalProject_Add( LOG_BUILD 1 ) - # Hide this external target (for ide users) set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") @@ -32,4 +31,4 @@ elseif (WIN32) endif () set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/${LIB_PREFIX}glew_d.${LIB_EXT} CACHE FILEPATH "Path to glew debug library") -set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/${LIB_PREFIX}glew.${LIB_EXT} CACHE FILEPATH "Path to glew release library") \ No newline at end of file +set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/${LIB_PREFIX}glew.${LIB_EXT} CACHE FILEPATH "Path to glew release library") diff --git a/cmake/externals/hifiAudioCodec/CMakeLists.txt b/cmake/externals/hifiAudioCodec/CMakeLists.txt index 87b5044b42..a30396c6fd 100644 --- a/cmake/externals/hifiAudioCodec/CMakeLists.txt +++ b/cmake/externals/hifiAudioCodec/CMakeLists.txt @@ -5,39 +5,43 @@ set(EXTERNAL_NAME hifiAudioCodec) string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) -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 - ) -elseif(NOT ANDROID) - 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) +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() + endif() diff --git a/cmake/externals/sdl2/CMakeLists.txt b/cmake/externals/sdl2/CMakeLists.txt index 4222cd41f0..cb61516b9a 100644 --- a/cmake/externals/sdl2/CMakeLists.txt +++ b/cmake/externals/sdl2/CMakeLists.txt @@ -46,7 +46,7 @@ else () if (ANDROID) set(ANDROID_CMAKE_ARGS "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}" "-DANDROID_NATIVE_API_LEVEL=19") endif () - + ExternalProject_Add( ${EXTERNAL_NAME} URL http://www.libsdl.org/release/SDL2-2.0.3.tar.gz @@ -61,7 +61,6 @@ endif () # Hide this external target (for ide users) set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") - if (APPLE) # NOOP @@ -78,9 +77,9 @@ elseif (WIN32) set(${EXTERNAL_NAME_UPPER}_LIBRARY_TEMP ${SOURCE_DIR}/lib/x86/SDL2.lib CACHE FILEPATH "Path to SDL2 library") set(${EXTERNAL_NAME_UPPER}_DLL_PATH ${SOURCE_DIR}/lib/x86 CACHE PATH "Location of SDL2 DLL") endif() - + add_paths_to_fixup_libs(${${EXTERNAL_NAME_UPPER}_DLL_PATH}) - + else () ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR) diff --git a/cmake/externals/tbb/CMakeLists.txt b/cmake/externals/tbb/CMakeLists.txt index 6ea03fd7a4..74f7e10859 100644 --- a/cmake/externals/tbb/CMakeLists.txt +++ b/cmake/externals/tbb/CMakeLists.txt @@ -3,9 +3,9 @@ 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 @@ -20,7 +20,7 @@ if (ANDROID) ) 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 @@ -37,11 +37,11 @@ else () if (WIN32) set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/tbb43_20150316oss_win.zip) set(DOWNLOAD_MD5 d250d40bb93b255f75bcbb19e976a440) - else () + 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} @@ -60,11 +60,11 @@ ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR) string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) -if (APPLE) +if (APPLE) set(_TBB_LIB_DIR "${SOURCE_DIR}/lib") set(_LIB_PREFIX "lib") set(_LIB_EXT "dylib") - + ExternalProject_Add_Step( ${EXTERNAL_NAME} change-install-name @@ -74,7 +74,7 @@ if (APPLE) WORKING_DIRECTORY LOG 1 ) - + elseif (WIN32) if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") set(_TBB_LIB_DIR "${SOURCE_DIR}/lib/intel64/vc12") @@ -91,18 +91,18 @@ elseif (ANDROID) elseif (UNIX) set(_LIB_PREFIX "lib") set(_LIB_EXT "so") - + if(CMAKE_SIZEOF_VOID_P EQUAL 8) set(_TBB_ARCH_DIR "intel64") else() set(_TBB_ARCH_DIR "ia32") endif() - + execute_process( COMMAND ${CMAKE_C_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION ) - + if (GCC_VERSION VERSION_GREATER 4.4 OR GCC_VERSION VERSION_EQUAL 4.4) 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) @@ -110,9 +110,9 @@ elseif (UNIX) else () message(STATUS "Could not find a compatible version of Threading Building Blocks library for your compiler.") endif () - - -endif () + + +endif () if (DEFINED _TBB_LIB_DIR) set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${_TBB_LIB_DIR}/${_LIB_PREFIX}tbb_debug.${_LIB_EXT} CACHE FILEPATH "TBB debug library location") diff --git a/cmake/macros/AutoMTC.cmake b/cmake/macros/AutoMTC.cmake deleted file mode 100644 index de60d5c21f..0000000000 --- a/cmake/macros/AutoMTC.cmake +++ /dev/null @@ -1,23 +0,0 @@ -# -# AutoMTC.cmake -# -# Created by Andrzej Kapolka on 12/31/13. -# 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 -# - -macro(AUTO_MTC) - set(AUTOMTC_SRC ${TARGET_NAME}_automtc.cpp) - - file(GLOB INCLUDE_FILES src/*.h) - - if (NOT ANDROID) - set(MTC_EXECUTABLE mtc) - else () - set(MTC_EXECUTABLE $ENV{MTC_PATH}/mtc) - endif () - - add_custom_command(OUTPUT ${AUTOMTC_SRC} COMMAND ${MTC_EXECUTABLE} -o ${AUTOMTC_SRC} ${INCLUDE_FILES} DEPENDS ${MTC_EXECUTABLE} ${INCLUDE_FILES}) -endmacro() diff --git a/cmake/macros/AutoScribeShader.cmake b/cmake/macros/AutoScribeShader.cmake index e586304503..c43ade45d2 100755 --- a/cmake/macros/AutoScribeShader.cmake +++ b/cmake/macros/AutoScribeShader.cmake @@ -1,77 +1,92 @@ -# +# # AutoScribeShader.cmake -# +# # Created by Sam Gateau on 12/17/14. # Copyright 2014 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 -# +# function(AUTOSCRIBE_SHADER SHADER_FILE) - - # Grab include files - foreach(includeFile ${ARGN}) - list(APPEND SHADER_INCLUDE_FILES ${includeFile}) - endforeach() + # Grab include files + foreach(includeFile ${ARGN}) + list(APPEND SHADER_INCLUDE_FILES ${includeFile}) + endforeach() - foreach(SHADER_INCLUDE ${SHADER_INCLUDE_FILES}) - get_filename_component(INCLUDE_DIR ${SHADER_INCLUDE} PATH) - list(APPEND SHADER_INCLUDES_PATHS ${INCLUDE_DIR}) - endforeach() + foreach(SHADER_INCLUDE ${SHADER_INCLUDE_FILES}) + get_filename_component(INCLUDE_DIR ${SHADER_INCLUDE} PATH) + list(APPEND SHADER_INCLUDES_PATHS ${INCLUDE_DIR}) + endforeach() + #Extract the unique include shader paths + set(INCLUDES ${HIFI_LIBRARIES_SHADER_INCLUDE_FILES}) + #message(${TARGET_NAME} Hifi for includes ${INCLUDES}) + foreach(EXTRA_SHADER_INCLUDE ${INCLUDES}) + list(APPEND SHADER_INCLUDES_PATHS ${EXTRA_SHADER_INCLUDE}) + endforeach() - #Extract the unique include shader paths - set(INCLUDES ${HIFI_LIBRARIES_SHADER_INCLUDE_FILES}) - #message(${TARGET_NAME} Hifi for includes ${INCLUDES}) - foreach(EXTRA_SHADER_INCLUDE ${INCLUDES}) - list(APPEND SHADER_INCLUDES_PATHS ${EXTRA_SHADER_INCLUDE}) - endforeach() + list(REMOVE_DUPLICATES SHADER_INCLUDES_PATHS) + #message(ready for includes ${SHADER_INCLUDES_PATHS}) - list(REMOVE_DUPLICATES SHADER_INCLUDES_PATHS) - #message(ready for includes ${SHADER_INCLUDES_PATHS}) + # make the scribe include arguments + set(SCRIBE_INCLUDES) + foreach(INCLUDE_PATH ${SHADER_INCLUDES_PATHS}) + set(SCRIBE_INCLUDES ${SCRIBE_INCLUDES} -I ${INCLUDE_PATH}/) + endforeach() - # make the scribe include arguments - set(SCRIBE_INCLUDES) - foreach(INCLUDE_PATH ${SHADER_INCLUDES_PATHS}) - set(SCRIBE_INCLUDES ${SCRIBE_INCLUDES} -I ${INCLUDE_PATH}/) - endforeach() + # Define the final name of the generated shader file + get_filename_component(SHADER_TARGET ${SHADER_FILE} NAME_WE) + get_filename_component(SHADER_EXT ${SHADER_FILE} EXT) + if(SHADER_EXT STREQUAL .slv) + set(SHADER_TARGET ${SHADER_TARGET}_vert.h) + elseif(${SHADER_EXT} STREQUAL .slf) + set(SHADER_TARGET ${SHADER_TARGET}_frag.h) + elseif(${SHADER_EXT} STREQUAL .slg) + set(SHADER_TARGET ${SHADER_TARGET}_geom.h) + endif() - # Define the final name of the generated shader file - get_filename_component(SHADER_TARGET ${SHADER_FILE} NAME_WE) - get_filename_component(SHADER_EXT ${SHADER_FILE} EXT) - if(SHADER_EXT STREQUAL .slv) - set(SHADER_TARGET ${SHADER_TARGET}_vert.h) - elseif(${SHADER_EXT} STREQUAL .slf) - set(SHADER_TARGET ${SHADER_TARGET}_frag.h) - elseif(${SHADER_EXT} STREQUAL .slg) - set(SHADER_TARGET ${SHADER_TARGET}_geom.h) - endif() + set(SHADER_TARGET "${SHADERS_DIR}/${SHADER_TARGET}") - set(SHADER_TARGET "${SHADERS_DIR}/${SHADER_TARGET}") + # Target dependant Custom rule on the SHADER_FILE + if (APPLE) + set(GLPROFILE MAC_GL) + set(SCRIBE_ARGS -c++ -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE}) - # Target dependant Custom rule on the SHADER_FILE - if (APPLE) - set(GLPROFILE MAC_GL) - set(SCRIBE_ARGS -c++ -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE}) + add_custom_command(OUTPUT ${SHADER_TARGET} COMMAND scribe ${SCRIBE_ARGS} DEPENDS scribe ${SHADER_INCLUDE_FILES} ${SHADER_FILE}) + elseif (ANDROID) + set(GLPROFILE LINUX_GL) + set(SCRIBE_ARGS -c++ -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE}) - add_custom_command(OUTPUT ${SHADER_TARGET} COMMAND scribe ${SCRIBE_ARGS} DEPENDS scribe ${SHADER_INCLUDE_FILES} ${SHADER_FILE}) - elseif (UNIX) - set(GLPROFILE LINUX_GL) - set(SCRIBE_ARGS -c++ -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE}) + # for an android build, we can't use the scribe that cmake would normally produce as a target, + # since it's unrunnable by the cross-compiling build machine - add_custom_command(OUTPUT ${SHADER_TARGET} COMMAND scribe ${SCRIBE_ARGS} DEPENDS scribe ${SHADER_INCLUDE_FILES} ${SHADER_FILE}) - else () - set(GLPROFILE PC_GL) - set(SCRIBE_ARGS -c++ -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE}) + # 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) - add_custom_command(OUTPUT ${SHADER_TARGET} COMMAND scribe ${SCRIBE_ARGS} DEPENDS scribe ${SHADER_INCLUDE_FILES} ${SHADER_FILE}) - endif() + if (NOT NATIVE_SCRIBE) + message(FATAL_ERROR "The High Fidelity scribe tool is required for shader pre-processing. \ + Please compile scribe using your native toolchain and set SCRIBE_PATH to the path containing the scribe executable in your ENV.\ + ") + endif () - #output the generated file name - set(AUTOSCRIBE_SHADER_RETURN ${SHADER_TARGET} PARENT_SCOPE) + add_custom_command(OUTPUT ${SHADER_TARGET} COMMAND ${NATIVE_SCRIBE} ${SCRIBE_ARGS} DEPENDS ${SHADER_INCLUDE_FILES} ${SHADER_FILE}) + elseif (UNIX) + set(GLPROFILE LINUX_GL) + set(SCRIBE_ARGS -c++ -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE}) - file(GLOB INCLUDE_FILES ${SHADER_TARGET}) + add_custom_command(OUTPUT ${SHADER_TARGET} COMMAND scribe ${SCRIBE_ARGS} DEPENDS scribe ${SHADER_INCLUDE_FILES} ${SHADER_FILE}) + else () + set(GLPROFILE PC_GL) + set(SCRIBE_ARGS -c++ -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE}) + + add_custom_command(OUTPUT ${SHADER_TARGET} COMMAND scribe ${SCRIBE_ARGS} DEPENDS scribe ${SHADER_INCLUDE_FILES} ${SHADER_FILE}) + endif() + + #output the generated file name + set(AUTOSCRIBE_SHADER_RETURN ${SHADER_TARGET} PARENT_SCOPE) + + file(GLOB INCLUDE_FILES ${SHADER_TARGET}) endfunction() @@ -79,11 +94,11 @@ endfunction() macro(AUTOSCRIBE_SHADER_LIB) set(HIFI_LIBRARIES_SHADER_INCLUDE_FILES "") file(RELATIVE_PATH RELATIVE_LIBRARY_DIR_PATH ${CMAKE_CURRENT_SOURCE_DIR} "${HIFI_LIBRARY_DIR}") - foreach(HIFI_LIBRARY ${ARGN}) + foreach(HIFI_LIBRARY ${ARGN}) #if (NOT TARGET ${HIFI_LIBRARY}) # file(GLOB_RECURSE HIFI_LIBRARIES_SHADER_INCLUDE_FILES ${RELATIVE_LIBRARY_DIR_PATH}/${HIFI_LIBRARY}/src/) #endif () - + #file(GLOB_RECURSE HIFI_LIBRARIES_SHADER_INCLUDE_FILES ${HIFI_LIBRARY_DIR}/${HIFI_LIBRARY}/src/*.slh) list(APPEND HIFI_LIBRARIES_SHADER_INCLUDE_FILES ${HIFI_LIBRARY_DIR}/${HIFI_LIBRARY}/src) endforeach() @@ -99,9 +114,9 @@ macro(AUTOSCRIBE_SHADER_LIB) #message("${TARGET_NAME} ${SHADER_INCLUDE_FILES}") set(AUTOSCRIBE_SHADER_SRC "") foreach(SHADER_FILE ${SHADER_SOURCE_FILES}) - AUTOSCRIBE_SHADER(${SHADER_FILE} ${SHADER_INCLUDE_FILES}) - file(TO_CMAKE_PATH "${AUTOSCRIBE_SHADER_RETURN}" AUTOSCRIBE_GENERATED_FILE) - list(APPEND AUTOSCRIBE_SHADER_SRC ${AUTOSCRIBE_GENERATED_FILE}) + AUTOSCRIBE_SHADER(${SHADER_FILE} ${SHADER_INCLUDE_FILES}) + file(TO_CMAKE_PATH "${AUTOSCRIBE_SHADER_RETURN}" AUTOSCRIBE_GENERATED_FILE) + list(APPEND AUTOSCRIBE_SHADER_SRC ${AUTOSCRIBE_GENERATED_FILE}) endforeach() #message(${TARGET_NAME} ${AUTOSCRIBE_SHADER_SRC}) @@ -118,4 +133,4 @@ macro(AUTOSCRIBE_SHADER_LIB) # Link library shaders, if they exist include_directories("${SHADERS_DIR}") -endmacro() \ No newline at end of file +endmacro() diff --git a/cmake/macros/FixupInterface.cmake b/cmake/macros/FixupInterface.cmake index 3e5ea7a3e2..93b5cc25fb 100644 --- a/cmake/macros/FixupInterface.cmake +++ b/cmake/macros/FixupInterface.cmake @@ -16,26 +16,6 @@ macro(fixup_interface) string(REPLACE " " "\\ " ESCAPED_INSTALL_PATH ${INTERFACE_INSTALL_DIR}) set(_INTERFACE_INSTALL_PATH "${ESCAPED_INSTALL_PATH}/${ESCAPED_BUNDLE_NAME}.app") - # install QtWebProcess from Qt to the application bundle - # since it is missed by macdeployqt - # https://bugreports.qt.io/browse/QTBUG-35211 - set(LIBEXEC_PATH "${_INTERFACE_INSTALL_PATH}/Contents/libexec") - install( - PROGRAMS "${QT_DIR}/libexec/QtWebProcess" - DESTINATION ${LIBEXEC_PATH} - COMPONENT ${CLIENT_COMPONENT} - ) - - set(QTWEBPROCESS_PATH "\${CMAKE_INSTALL_PREFIX}/${LIBEXEC_PATH}") - - # we also need a qt.conf in the directory of QtWebProcess - install(CODE " - file(WRITE ${QTWEBPROCESS_PATH}/qt.conf - \"[Paths]\nPlugins = ../PlugIns\nImports = ../Resources/qml\nQml2Imports = ../Resources/qml\" - )" - COMPONENT ${CLIENT_COMPONENT} - ) - find_program(MACDEPLOYQT_COMMAND macdeployqt PATHS "${QT_DIR}/bin" NO_DEFAULT_PATH) if (NOT MACDEPLOYQT_COMMAND AND (PRODUCTION_BUILD OR PR_BUILD)) @@ -49,7 +29,6 @@ macro(fixup_interface) execute_process(COMMAND ${MACDEPLOYQT_COMMAND}\ \${CMAKE_INSTALL_PREFIX}/${_INTERFACE_INSTALL_PATH}/\ -verbose=2 -qmldir=${CMAKE_SOURCE_DIR}/interface/resources/qml/\ - -executable=\${CMAKE_INSTALL_PREFIX}/${_INTERFACE_INSTALL_PATH}/Contents/libexec/QtWebProcess\ )" COMPONENT ${CLIENT_COMPONENT} ) diff --git a/cmake/macros/InstallBesideConsole.cmake b/cmake/macros/InstallBesideConsole.cmake index 0eb6025f38..d5777fff12 100644 --- a/cmake/macros/InstallBesideConsole.cmake +++ b/cmake/macros/InstallBesideConsole.cmake @@ -59,7 +59,12 @@ macro(install_beside_console) set(EXECUTABLE_NEEDING_FIXUP "\${CMAKE_INSTALL_PREFIX}/${COMPONENT_INSTALL_DIR}/${TARGET_NAME}") string(REPLACE " " "\\ " ESCAPED_EXECUTABLE_NAME ${EXECUTABLE_NEEDING_FIXUP}) + # configure Info.plist for COMPONENT_APP install(CODE " + set(MACOSX_BUNDLE_EXECUTABLE_NAME domain-server) + set(MACOSX_BUNDLE_GUI_IDENTIFIER com.highfidelity.server-components) + set(MACOSX_BUNDLE_BUNDLE_NAME Sandbox\\ Components) + configure_file(${HF_CMAKE_DIR}/templates/MacOSXBundleSandboxComponentsInfo.plist.in ${ESCAPED_BUNDLE_NAME}/Contents/Info.plist) execute_process(COMMAND ${MACDEPLOYQT_COMMAND} ${ESCAPED_BUNDLE_NAME} -verbose=2 -executable=${ESCAPED_EXECUTABLE_NAME})" COMPONENT ${SERVER_COMPONENT} ) diff --git a/cmake/macros/SetupHifiLibrary.cmake b/cmake/macros/SetupHifiLibrary.cmake index a10c7c11e6..e4a286cf3f 100644 --- a/cmake/macros/SetupHifiLibrary.cmake +++ b/cmake/macros/SetupHifiLibrary.cmake @@ -1,16 +1,16 @@ -# +# # SetupHifiLibrary.cmake -# +# # 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 -# +# macro(SETUP_HIFI_LIBRARY) - + project(${TARGET_NAME}) - + # grab the implementation and header files file(GLOB_RECURSE LIB_SRCS "src/*.h" "src/*.cpp" "src/*.c") list(APPEND ${TARGET_NAME}_SRCS ${LIB_SRCS}) @@ -34,19 +34,19 @@ macro(SETUP_HIFI_LIBRARY) set_source_files_properties(${SRC} PROPERTIES COMPILE_FLAGS "-mavx2 -mfma") endif() endforeach() - + setup_memory_debugger() # create a library and set the property so it can be referenced later if (${${TARGET_NAME}_SHARED}) - add_library(${TARGET_NAME} SHARED ${LIB_SRCS} ${AUTOMTC_SRC} ${AUTOSCRIBE_SHADER_LIB_SRC} ${QT_RESOURCES_FILE}) + add_library(${TARGET_NAME} SHARED ${LIB_SRCS} ${AUTOSCRIBE_SHADER_LIB_SRC} ${QT_RESOURCES_FILE}) else () - add_library(${TARGET_NAME} ${LIB_SRCS} ${AUTOMTC_SRC} ${AUTOSCRIBE_SHADER_LIB_SRC} ${QT_RESOURCES_FILE}) + add_library(${TARGET_NAME} ${LIB_SRCS} ${AUTOSCRIBE_SHADER_LIB_SRC} ${QT_RESOURCES_FILE}) endif () - + set(${TARGET_NAME}_DEPENDENCY_QT_MODULES ${ARGN}) 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) @@ -59,7 +59,7 @@ macro(SETUP_HIFI_LIBRARY) set(QT_RESOURCES_FILE "") target_glm() - + set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Libraries") - -endmacro(SETUP_HIFI_LIBRARY) \ No newline at end of file + +endmacro(SETUP_HIFI_LIBRARY) diff --git a/cmake/macros/SetupHifiProject.cmake b/cmake/macros/SetupHifiProject.cmake index 6c150b6c8d..8695063556 100644 --- a/cmake/macros/SetupHifiProject.cmake +++ b/cmake/macros/SetupHifiProject.cmake @@ -22,7 +22,7 @@ macro(SETUP_HIFI_PROJECT) endif () endforeach() - add_executable(${TARGET_NAME} ${TARGET_SRCS} ${AUTOMTC_SRC} ${AUTOSCRIBE_SHADER_LIB_SRC}) + add_executable(${TARGET_NAME} ${TARGET_SRCS} ${AUTOSCRIBE_SHADER_LIB_SRC}) # include the generated application version header target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/includes") diff --git a/cmake/templates/MacOSXBundleSandboxComponentsInfo.plist.in b/cmake/templates/MacOSXBundleSandboxComponentsInfo.plist.in new file mode 100644 index 0000000000..a466dc7c4e --- /dev/null +++ b/cmake/templates/MacOSXBundleSandboxComponentsInfo.plist.in @@ -0,0 +1,36 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleGetInfoString + ${MACOSX_BUNDLE_INFO_STRING} + CFBundleIconFile + ${MACOSX_BUNDLE_ICON_FILE} + CFBundleIdentifier + ${MACOSX_BUNDLE_GUI_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLongVersionString + ${MACOSX_BUNDLE_LONG_VERSION_STRING} + CFBundleName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + ${MACOSX_BUNDLE_SHORT_VERSION_STRING} + CFBundleSignature + ???? + CFBundleVersion + ${MACOSX_BUNDLE_BUNDLE_VERSION} + CSResourcesFileMapped + + LSRequiresCarbon + + NSHumanReadableCopyright + ${MACOSX_BUNDLE_COPYRIGHT} + + diff --git a/cmake/templates/NSIS.template.in b/cmake/templates/NSIS.template.in index 4786b12743..65e801d321 100644 --- a/cmake/templates/NSIS.template.in +++ b/cmake/templates/NSIS.template.in @@ -130,6 +130,11 @@ Var AR_RegFlags SectionSetFlags ${${SecName}} $AR_SecFlags "default_${SecName}:" + ; The client is always selected by default + ${If} ${SecName} == @CLIENT_COMPONENT_NAME@ + SectionSetFlags ${${SecName}} 17 + ${EndIf} + !insertmacro LoadSectionSelectedIntoVar ${SecName} ${SecName}_selected !macroend @@ -243,6 +248,12 @@ FunctionEnd ;-------------------------------- ; Installation types + +Section "-Previous Install Cleanup" + ; Remove the resources folder so we don't end up including removed QML files + RMDir /r "$INSTDIR\resources" +SectionEnd + @CPACK_NSIS_INSTALLATION_TYPES@ ;-------------------------------- diff --git a/domain-server/CMakeLists.txt b/domain-server/CMakeLists.txt index b7eb8c0138..746e599d4e 100644 --- a/domain-server/CMakeLists.txt +++ b/domain-server/CMakeLists.txt @@ -9,6 +9,11 @@ endif () # setup the project and link required Qt modules setup_hifi_project(Network) +# Fix up the rpath so macdeployqt works +if (APPLE) + set_target_properties(${TARGET_NAME} PROPERTIES INSTALL_RPATH "@executable_path/../Frameworks") +endif () + # TODO: find a solution that will handle web file changes in resources on windows without a re-build. # Currently the resources are only copied on post-build. If one is changed but the domain-server is not, they will # not be re-copied. This is worked-around on OS X/UNIX by using a symlink. diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index cbf533bf64..570db05871 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -121,9 +121,9 @@ DomainServer::DomainServer(int argc, char* argv[]) : if (_type != NonMetaverse) { // if we have a metaverse domain, we'll use an access token for API calls resetAccountManagerAccessToken(); - } - setupAutomaticNetworking(); + setupAutomaticNetworking(); + } if (!getID().isNull() && _type != NonMetaverse) { // setup periodic heartbeats to metaverse API @@ -1756,6 +1756,7 @@ bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &u .arg(authorizationCode, oauthRedirectURL().toString(), _oauthClientID, _oauthClientSecret); QNetworkRequest tokenRequest(tokenRequestUrl); + tokenRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); tokenRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); tokenRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); @@ -1949,6 +1950,7 @@ QNetworkReply* DomainServer::profileRequestGivenTokenReply(QNetworkReply* tokenR profileURL.setQuery(QString("%1=%2").arg(OAUTH_JSON_ACCESS_TOKEN_KEY, accessToken)); QNetworkRequest profileRequest(profileURL); + profileRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); profileRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); return NetworkAccessManager::getInstance().get(profileRequest); } diff --git a/gvr-interface/CMakeLists.txt b/gvr-interface/CMakeLists.txt index 175706a3ff..7d6655e875 100644 --- a/gvr-interface/CMakeLists.txt +++ b/gvr-interface/CMakeLists.txt @@ -3,7 +3,7 @@ set(TARGET_NAME gvr-interface) if (ANDROID) set(ANDROID_APK_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/apk-build") set(ANDROID_APK_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/apk") - + set(ANDROID_SDK_ROOT $ENV{ANDROID_HOME}) set(ANDROID_APP_DISPLAY_NAME Interface) set(ANDROID_API_LEVEL 19) @@ -13,10 +13,10 @@ if (ANDROID) set(ANDROID_APK_VERSION_CODE 1) set(ANDROID_APK_FULLSCREEN TRUE) set(ANDROID_DEPLOY_QT_INSTALL "--install") - + set(BUILD_SHARED_LIBS ON) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${ANDROID_APK_OUTPUT_DIR}/libs/${ANDROID_ABI}") - + setup_hifi_library(Gui Widgets AndroidExtras) else () setup_hifi_project(Gui Widgets) @@ -28,12 +28,12 @@ link_hifi_libraries(shared networking audio-client avatars) if (ANDROID) find_package(LibOVR) - + if (LIBOVR_FOUND) add_definitions(-DHAVE_LIBOVR) target_link_libraries(${TARGET_NAME} ${LIBOVR_LIBRARIES} ${LIBOVR_ANDROID_LIBRARIES} ${TURBOJPEG_LIBRARY}) include_directories(SYSTEM ${LIBOVR_INCLUDE_DIRS}) - + # we need VRLib, so add a project.properties to our apk build folder that says that file(RELATIVE_PATH RELATIVE_VRLIB_PATH ${ANDROID_APK_OUTPUT_DIR} "${LIBOVR_VRLIB_DIR}") file(WRITE "${ANDROID_APK_BUILD_DIR}/project.properties" "android.library.reference.1=${RELATIVE_VRLIB_PATH}") @@ -50,7 +50,7 @@ if (ANDROID AND HOCKEY_APP_ID) set(ANDROID_ACTIVITY_NAME io.highfidelity.gvrinterface.InterfaceBetaActivity) set(ANDROID_DEPLOY_QT_INSTALL "") set(ANDROID_APK_CUSTOM_NAME "Interface-beta.apk") - + # set the ANDROID_APK_VERSION_CODE to the number of git commits execute_process( COMMAND git rev-list --first-parent --count HEAD @@ -58,16 +58,16 @@ if (ANDROID AND HOCKEY_APP_ID) OUTPUT_VARIABLE GIT_COMMIT_COUNT OUTPUT_STRIP_TRAILING_WHITESPACE ) - + set(ANDROID_APK_VERSION_CODE ${GIT_COMMIT_COUNT}) - + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/templates/InterfaceBetaActivity.java.in" "${ANDROID_APK_BUILD_DIR}/src/io/highfidelity/gvrinterface/InterfaceBetaActivity.java") elseif (ANDROID) set(HOCKEY_APP_ENABLED false) endif () if (ANDROID) - + set(HIFI_URL_INTENT "\ \n \ \n \ @@ -75,13 +75,11 @@ if (ANDROID) \n \ \n " ) - + set(ANDROID_EXTRA_APPLICATION_XML "${HOCKEY_APP_ACTIVITY}") set(ANDROID_EXTRA_ACTIVITY_XML "${HIFI_URL_INTENT}") configure_file("${CMAKE_CURRENT_SOURCE_DIR}/templates/hockeyapp.xml.in" "${ANDROID_APK_BUILD_DIR}/res/values/hockeyapp.xml") qt_create_apk() - -endif (ANDROID) -copy_dlls_beside_windows_executable() +endif (ANDROID) diff --git a/ice-server/src/IceServer.cpp b/ice-server/src/IceServer.cpp index 84c48ac5f9..3f229d2f87 100644 --- a/ice-server/src/IceServer.cpp +++ b/ice-server/src/IceServer.cpp @@ -213,6 +213,7 @@ void IceServer::requestDomainPublicKey(const QUuid& domainID) { publicKeyURL.setPath(publicKeyPath); QNetworkRequest publicKeyRequest { publicKeyURL }; + publicKeyRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); publicKeyRequest.setAttribute(QNetworkRequest::User, domainID); qDebug() << "Requesting public key for domain with ID" << domainID; diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 27cba520bb..9c564f6518 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -39,9 +39,18 @@ else () list(REMOVE_ITEM INTERFACE_SRCS ${SPEECHRECOGNIZER_CPP}) endif () -find_package(Qt5 COMPONENTS - Gui Multimedia Network OpenGL Qml Quick Script ScriptTools Svg - WebChannel WebEngine WebEngineWidgets WebKitWidgets WebSockets) +if (ANDROID) + set(PLATFORM_QT_COMPONENTS AndroidExtras) +else () + set(PLATFORM_QT_COMPONENTS WebEngine WebEngineWidgets) +endif () + +find_package( + Qt5 COMPONENTS + Gui Multimedia Network OpenGL Qml Quick Script Svg + ${PLATFORM_QT_COMPONENTS} + WebChannel WebSockets +) # grab the ui files in resources/ui file (GLOB_RECURSE QT_UI_FILES ui/*.ui) @@ -57,6 +66,26 @@ set(INTERFACE_SRCS ${INTERFACE_SRCS} "${QT_UI_HEADERS}" "${QT_RESOURCES}") # set(TS ${TARGET_NAME}_en.ts) # qt5_create_translation_custom(${QM} ${INTERFACE_SRCS} ${QT_UI_FILES} ${TS}) +# setup the android parameters that will help us produce an APK +if (ANDROID) + set(ANDROID_APK_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/apk-build") + set(ANDROID_APK_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/apk") + + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${ANDROID_APK_OUTPUT_DIR}/libs/${ANDROID_ABI}") + + set(ANDROID_SDK_ROOT $ENV{ANDROID_HOME}) + set(ANDROID_APP_DISPLAY_NAME Interface) + set(ANDROID_API_LEVEL 19) + set(ANDROID_APK_PACKAGE io.highfidelity.interface) + set(ANDROID_ACTIVITY_NAME io.highfidelity.interface.InterfaceActivity) + set(ANDROID_APK_VERSION_NAME "0.1") + set(ANDROID_APK_VERSION_CODE 1) + set(ANDROID_APK_FULLSCREEN TRUE) + set(ANDROID_DEPLOY_QT_INSTALL "--install") + + set(BUILD_SHARED_LIBS ON) +endif () + if (APPLE) # configure CMake to use a custom Info.plist @@ -94,8 +123,9 @@ if (APPLE) add_executable(${TARGET_NAME} MACOSX_BUNDLE ${INTERFACE_SRCS} ${QM}) # make sure the output name for the .app bundle is correct - set_target_properties(${TARGET_NAME} PROPERTIES OUTPUT_NAME ${INTERFACE_BUNDLE_NAME}) -elseif(WIN32) + # Fix up the rpath so macdeployqt works + set_target_properties(${TARGET_NAME} PROPERTIES INSTALL_RPATH "@executable_path/../Frameworks") +elseif (WIN32) # configure an rc file for the chosen icon set(CONFIGURE_ICON_PATH "${CMAKE_CURRENT_SOURCE_DIR}/icon/${INTERFACE_ICON_FILENAME}") set(CONFIGURE_ICON_RC_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/Icon.rc") @@ -104,7 +134,7 @@ elseif(WIN32) # add an executable that also has the icon itself and the configured rc file as resources add_executable(${TARGET_NAME} WIN32 ${INTERFACE_SRCS} ${QM} ${CONFIGURE_ICON_RC_OUTPUT}) - if ( NOT DEV_BUILD ) + if (NOT DEV_BUILD) add_custom_command( TARGET ${TARGET_NAME} POST_BUILD @@ -113,9 +143,12 @@ elseif(WIN32) ) endif() -else() +elseif (ANDROID) + # on android the Interface target is a library that gets linked/used by the APK shell that qtcreateapk produces + add_library(${TARGET_NAME} ${INTERFACE_SRCS} ${QM}) +else () add_executable(${TARGET_NAME} ${INTERFACE_SRCS} ${QM}) -endif() +endif () target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/includes") @@ -136,21 +169,32 @@ if (WIN32) set_property(TARGET ${TARGET_NAME} APPEND_STRING PROPERTY LINK_FLAGS_DEBUG "/OPT:NOREF /OPT:NOICF") endif() +if (NOT ANDROID) + set(NON_ANDROID_LIBRARIES steamworks-wrapper) +endif () + # link required hifi libraries -link_hifi_libraries(shared octree gpu gl gpu-gl procedural model render - recording fbx networking model-networking entities avatars - audio audio-client animation script-engine physics - render-utils entities-renderer ui auto-updater - controllers plugins ui-plugins display-plugins input-plugins steamworks-wrapper) +link_hifi_libraries( + shared octree gpu gl gpu-gl procedural model render + recording fbx networking model-networking entities avatars + audio audio-client animation script-engine physics + render-utils entities-renderer ui auto-updater + controllers plugins + ui-plugins display-plugins input-plugins + ${NON_ANDROID_LIBRARIES} +) # include the binary directory of render-utils for shader includes target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/libraries/render-utils") #fixme find a way to express faceshift as a plugin target_bullet() -target_glew() target_opengl() +if (NOT ANDROID) + target_glew() +endif () + if (WIN32 OR APPLE) target_faceshift() endif() @@ -199,16 +243,10 @@ include_directories("${PROJECT_SOURCE_DIR}/src") target_link_libraries( ${TARGET_NAME} Qt5::Gui Qt5::Network Qt5::Multimedia Qt5::OpenGL - Qt5::Qml Qt5::Quick Qt5::Script Qt5::ScriptTools Qt5::Svg - Qt5::WebChannel Qt5::WebEngine Qt5::WebEngineWidgets Qt5::WebKitWidgets + Qt5::Qml Qt5::Quick Qt5::Script Qt5::Svg + Qt5::WebChannel Qt5::WebEngine ) -# Issue causes build failure unless we add this directory. -# See https://bugreports.qt.io/browse/QTBUG-43351 -if (WIN32) - add_paths_to_fixup_libs(${Qt5_DIR}/../../../plugins/qtwebengine) -endif() - if (UNIX) target_link_libraries(${TARGET_NAME} pthread) endif(UNIX) @@ -292,3 +330,17 @@ if (WIN32) package_libraries_for_deployment() endif() + +if (ANDROID) + set(HIFI_URL_INTENT "\ + \n \ + \n \ + \n \ + \n \ + \n " + ) + + set(ANDROID_EXTRA_ACTIVITY_XML "${HIFI_URL_INTENT}") + + qt_create_apk() +endif () diff --git a/interface/resources/qml/Browser.qml b/interface/resources/qml/Browser.qml index 050e10eead..62226859b6 100644 --- a/interface/resources/qml/Browser.qml +++ b/interface/resources/qml/Browser.qml @@ -223,12 +223,12 @@ ScrollingWindow { var newWindow = component.createObject(desktop); request.openIn(newWindow.webView) } - Component.onCompleted: { - desktop.initWebviewProfileHandlers(webview.profile) - } - - //profile: desktop.browserProfile + Component.onCompleted: { + desktop.initWebviewProfileHandlers(webview.profile) + } + + profile: desktop.browserProfile } } // item @@ -245,4 +245,4 @@ ScrollingWindow { break; } } -} // dialog \ No newline at end of file +} // dialog diff --git a/interface/resources/qml/MarketplaceComboBox.qml b/interface/resources/qml/MarketplaceComboBox.qml deleted file mode 100644 index f7f224485b..0000000000 --- a/interface/resources/qml/MarketplaceComboBox.qml +++ /dev/null @@ -1,103 +0,0 @@ -// -// MarketplaceComboBox.qml -// -// Created by Elisa Lupin-Jimenez on 3 Aug 2016 -// 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 -// - -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtWebChannel 1.0 -import QtWebEngine 1.1 -import QtWebSockets 1.0 -import "qrc:///qtwebchannel/qwebchannel.js" as WebChannel - -import "controls" -import "controls-uit" as Controls -import "styles" -import "styles-uit" - - -Rectangle { - HifiConstants { id: hifi } - id: marketplaceComboBox - anchors.fill: parent - color: hifi.colors.baseGrayShadow - property var currentUrl: "https://metaverse.highfidelity.com/marketplace" - - Controls.WebView { - id: webview - url: currentUrl - anchors.top: switchMarketView.bottom - width: parent.width - height: parent.height - 40 - focus: true - - Timer { - id: zipTimer - running: false - repeat: false - interval: 1500 - property var handler; - onTriggered: handler(); - } - - property var autoCancel: 'var element = $("a.btn.cancel"); - element.click();' - - onNewViewRequested: { - var component = Qt.createComponent("Browser.qml"); - var newWindow = component.createObject(desktop); - request.openIn(newWindow.webView); - if (File.isZippedFbx(desktop.currentUrl)) { - zipTimer.handler = function() { - newWindow.destroy(); - runJavaScript(autoCancel); - } - zipTimer.start(); - } - } - - property var simpleDownload: 'var element = $("a.download-file"); - element.removeClass("download-file"); - element.removeAttr("download");' - - onLinkHovered: { - desktop.currentUrl = hoveredUrl; - // add an error message for non-fbx files - if (File.isZippedFbx(desktop.currentUrl)) { - runJavaScript(simpleDownload, function(){console.log("ran the JS");}); - } - - } - - } - - Controls.ComboBox { - id: switchMarketView - anchors.top: parent.top - anchors.right: parent.right - colorScheme: hifi.colorSchemes.dark - width: 200 - height: 40 - visible: true - model: ["Marketplace", "Clara.io"] - onCurrentIndexChanged: { - if (currentIndex === 0) { webview.url = "https://metaverse.highfidelity.com/marketplace"; } - if (currentIndex === 1) { webview.url = "https://clara.io/library"; } - } - - } - - Controls.Label { - id: switchMarketLabel - anchors.verticalCenter: switchMarketView.verticalCenter - anchors.right: switchMarketView.left - color: hifi.colors.white - text: "Explore interesting content from: " - } - -} \ No newline at end of file diff --git a/interface/resources/qml/Marketplaces.qml b/interface/resources/qml/Marketplaces.qml new file mode 100644 index 0000000000..70a20286d3 --- /dev/null +++ b/interface/resources/qml/Marketplaces.qml @@ -0,0 +1,167 @@ +// +// Marketplaces.qml +// +// Created by Elisa Lupin-Jimenez on 3 Aug 2016 +// 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 +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtWebChannel 1.0 +import QtWebEngine 1.1 +import QtWebSockets 1.0 +import "qrc:///qtwebchannel/qwebchannel.js" as WebChannel + +import "controls" +import "controls-uit" as Controls +import "styles" +import "styles-uit" + + +Rectangle { + HifiConstants { id: hifi } + id: marketplace + anchors.fill: parent + property var marketplacesUrl: "../../scripts/system/html/marketplaces.html" + property int statusBarHeight: 50 + property int statusMargin: 50 + property string standardMessage: "Check out other marketplaces." + property string claraMessage: "Choose a model and click Download -> Autodesk FBX." + property string claraError: "High Fidelity only supports Autodesk FBX models." + + Controls.BaseWebView { + id: webview + url: marketplacesUrl + anchors.top: marketplace.top + width: parent.width + height: parent.height - statusBarHeight + focus: true + + Timer { + id: zipTimer + running: false + repeat: false + interval: 1500 + property var handler; + onTriggered: handler(); + } + + Timer { + id: alertTimer + running: false + repeat: false + interval: 9000 + property var handler; + onTriggered: handler(); + } + + property var autoCancel: 'var element = $("a.btn.cancel"); + element.click();' + + property var simpleDownload: 'var element = $("a.download-file"); + element.removeClass("download-file"); + element.removeAttr("download");' + + function displayErrorStatus() { + alertTimer.handler = function() { + statusLabel.text = claraMessage; + statusBar.color = hifi.colors.blueHighlight; + statusIcon.text = hifi.glyphs.info; + } + alertTimer.start(); + } + + property var notFbxHandler: 'var element = $("a.btn.btn-primary.viewer-button.download-file") + element.click();' + + // this code is for removing other file types from Clara.io's download options + //property var checkFileType: "$('[data-extension]:not([data-extension=\"fbx\"])').parent().remove()" + + onLinkHovered: { + desktop.currentUrl = hoveredUrl; + //runJavaScript(checkFileType, function(){console.log("Remove filetypes JS injection");}); + if (File.isZippedFbx(desktop.currentUrl)) { + runJavaScript(simpleDownload, function(){console.log("Download JS injection");}); + return; + } + + if (File.isZipped(desktop.currentUrl)) { + statusLabel.text = claraError; + statusBar.color = hifi.colors.redHighlight; + statusIcon.text = hifi.glyphs.alert; + runJavaScript(notFbxHandler, displayErrorStatus()); + } + + } + + onLoadingChanged: { + if (File.isClaraLink(webview.url)) { + statusLabel.text = claraMessage; + } else { + statusLabel.text = standardMessage; + } + statusBar.color = hifi.colors.blueHighlight; + statusIcon.text = hifi.glyphs.info; + } + + onNewViewRequested: { + var component = Qt.createComponent("Browser.qml"); + var newWindow = component.createObject(desktop); + request.openIn(newWindow.webView); + if (File.isZippedFbx(desktop.currentUrl)) { + runJavaScript(autoCancel); + zipTimer.handler = function() { + newWindow.destroy(); + } + zipTimer.start(); + } + } + + } + + Rectangle { + id: statusBar + anchors.top: webview.bottom + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + color: hifi.colors.blueHighlight + + Controls.Button { + id: switchMarketView + anchors.right: parent.right + anchors.rightMargin: statusMargin + anchors.verticalCenter: parent.verticalCenter + width: 150 + text: "See all markets" + onClicked: { + webview.url = "../../scripts/system/html/marketplaces.html"; + statusLabel.text = standardMessage; + } + } + + Controls.Label { + id: statusLabel + anchors.verticalCenter: switchMarketView.verticalCenter + anchors.left: parent.left + anchors.leftMargin: statusMargin + color: hifi.colors.white + text: standardMessage + size: 18 + } + + HiFiGlyphs { + id: statusIcon + anchors.right: statusLabel.left + anchors.verticalCenter: statusLabel.verticalCenter + text: hifi.glyphs.info + color: hifi.colors.white + size: hifi.fontSizes.tableHeadingIcon + } + + } + +} \ No newline at end of file diff --git a/interface/resources/qml/Stats.qml b/interface/resources/qml/Stats.qml index 180e5e1bcc..a0750c1f7f 100644 --- a/interface/resources/qml/Stats.qml +++ b/interface/resources/qml/Stats.qml @@ -59,6 +59,11 @@ Item { font.pixelSize: root.fontSize text: "Avatars: " + root.avatarCount } + Text { + color: root.fontColor; + font.pixelSize: root.fontSize + text: "Frame Rate: " + root.framerate.toFixed(2); + } Text { color: root.fontColor; font.pixelSize: root.fontSize diff --git a/interface/resources/qml/controls-uit/BaseWebView.qml b/interface/resources/qml/controls-uit/BaseWebView.qml new file mode 100644 index 0000000000..faf7f746a2 --- /dev/null +++ b/interface/resources/qml/controls-uit/BaseWebView.qml @@ -0,0 +1,68 @@ +// +// WebView.qml +// +// Created by Bradley Austin Davis on 12 Jan 2016 +// 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 +// + +import QtQuick 2.5 +import QtWebEngine 1.1 + +WebEngineView { + id: root + property var newUrl; + + profile.httpUserAgent: "Mozilla/5.0 Chrome/38.0 (HighFidelityInterface)" + + Component.onCompleted: { + console.log("Connecting JS messaging to Hifi Logging") + // Ensure the JS from the web-engine makes it to our logging + root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { + console.log("Web Window JS message: " + sourceID + " " + lineNumber + " " + message); + }); + } + + + + // FIXME hack to get the URL with the auth token included. Remove when we move to Qt 5.6 + Timer { + id: urlReplacementTimer + running: false + repeat: false + interval: 50 + onTriggered: url = newUrl; + } + + onUrlChanged: { + var originalUrl = url.toString(); + newUrl = urlHandler.fixupUrl(originalUrl).toString(); + if (newUrl !== originalUrl) { + root.stop(); + if (urlReplacementTimer.running) { + console.warn("Replacement timer already running"); + return; + } + urlReplacementTimer.start(); + } + } + + onLoadingChanged: { + // Required to support clicking on "hifi://" links + if (WebEngineView.LoadStartedStatus == loadRequest.status) { + var url = loadRequest.url.toString(); + if (urlHandler.canHandleUrl(url)) { + if (urlHandler.handleUrl(url)) { + root.stop(); + } + } + } + } + + + // This breaks the webchannel used for passing messages. Fixed in Qt 5.6 + // See https://bugreports.qt.io/browse/QTBUG-49521 + //profile: desktop.browserProfile +} diff --git a/interface/resources/qml/controls-uit/WebView.qml b/interface/resources/qml/controls-uit/WebView.qml index faf7f746a2..2ce007c42a 100644 --- a/interface/resources/qml/controls-uit/WebView.qml +++ b/interface/resources/qml/controls-uit/WebView.qml @@ -9,60 +9,12 @@ // import QtQuick 2.5 -import QtWebEngine 1.1 +import "." -WebEngineView { - id: root - property var newUrl; - - profile.httpUserAgent: "Mozilla/5.0 Chrome/38.0 (HighFidelityInterface)" - - Component.onCompleted: { - console.log("Connecting JS messaging to Hifi Logging") - // Ensure the JS from the web-engine makes it to our logging - root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { - console.log("Web Window JS message: " + sourceID + " " + lineNumber + " " + message); - }); +BaseWebView { + onNewViewRequested: { + var component = Qt.createComponent("../Browser.qml"); + var newWindow = component.createObject(desktop); + request.openIn(newWindow.webView) } - - - - // FIXME hack to get the URL with the auth token included. Remove when we move to Qt 5.6 - Timer { - id: urlReplacementTimer - running: false - repeat: false - interval: 50 - onTriggered: url = newUrl; - } - - onUrlChanged: { - var originalUrl = url.toString(); - newUrl = urlHandler.fixupUrl(originalUrl).toString(); - if (newUrl !== originalUrl) { - root.stop(); - if (urlReplacementTimer.running) { - console.warn("Replacement timer already running"); - return; - } - urlReplacementTimer.start(); - } - } - - onLoadingChanged: { - // Required to support clicking on "hifi://" links - if (WebEngineView.LoadStartedStatus == loadRequest.status) { - var url = loadRequest.url.toString(); - if (urlHandler.canHandleUrl(url)) { - if (urlHandler.handleUrl(url)) { - root.stop(); - } - } - } - } - - - // This breaks the webchannel used for passing messages. Fixed in Qt 5.6 - // See https://bugreports.qt.io/browse/QTBUG-49521 - //profile: desktop.browserProfile } diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml index 7285db22d2..84ef31e87f 100644 --- a/interface/resources/qml/controls/WebView.qml +++ b/interface/resources/qml/controls/WebView.qml @@ -5,7 +5,7 @@ WebEngineView { id: root property var newUrl; - profile.httpUserAgent: "Mozilla/5.0 Chrome (HighFidelityInterface)" + profile: desktop.browserProfile Component.onCompleted: { console.log("Connecting JS messaging to Hifi Logging") @@ -13,7 +13,6 @@ WebEngineView { root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { console.log("Web Window JS message: " + sourceID + " " + lineNumber + " " + message); }); - } // FIXME hack to get the URL with the auth token included. Remove when we move to Qt 5.6 @@ -61,8 +60,4 @@ WebEngineView { request.openIn(newWindow.webView); } } - - // This breaks the webchannel used for passing messages. Fixed in Qt 5.6 - // See https://bugreports.qt.io/browse/QTBUG-49521 - //profile: desktop.browserProfile } diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index 6a37886cb3..ff8be580db 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -52,7 +52,7 @@ ModalWindow { // Set from OffscreenUi::getOpenFile() property int options; // <-- FIXME unused - property string iconText: text !== "" ? hifi.glyphs.scriptUpload : "" + property string iconText: root.title !== "" ? hifi.glyphs.scriptUpload : "" property int iconSize: 40 property bool selectDirectory: false; diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml index 53829eed9e..5d8cbc97fe 100644 --- a/interface/resources/qml/hifi/Card.qml +++ b/interface/resources/qml/hifi/Card.qml @@ -89,6 +89,7 @@ Rectangle { property int dropSamples: 9; property int dropSpread: 0; DropShadow { + visible: desktop.gradientsSupported; source: place; anchors.fill: place; horizontalOffset: dropHorizontalOffset; @@ -99,6 +100,7 @@ Rectangle { spread: dropSpread; } DropShadow { + visible: desktop.gradientsSupported; source: users; anchors.fill: users; horizontalOffset: dropHorizontalOffset; diff --git a/interface/resources/qml/windows/DefaultFrameDecoration.qml b/interface/resources/qml/windows/DefaultFrameDecoration.qml index 40e32aaa6b..1ddd83976e 100644 --- a/interface/resources/qml/windows/DefaultFrameDecoration.qml +++ b/interface/resources/qml/windows/DefaultFrameDecoration.qml @@ -109,7 +109,7 @@ Decoration { verticalOffset: 2 samples: 2 color: hifi.colors.baseGrayShadow60 - visible: (window && window.focus) + visible: (desktop.gradientsSupported && window && window.focus) cached: true } } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index aaf38e826b..8620b384ec 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -133,7 +133,6 @@ #include "scripting/LocationScriptingInterface.h" #include "scripting/MenuScriptingInterface.h" #include "scripting/SettingsScriptingInterface.h" -#include "scripting/WebWindowClass.h" #include "scripting/WindowScriptingInterface.h" #include "scripting/ControllerScriptingInterface.h" #include "scripting/ToolbarScriptingInterface.h" @@ -176,8 +175,6 @@ static const int MAX_CONCURRENT_RESOURCE_DOWNLOADS = 16; // For processing on QThreadPool, target 2 less than the ideal number of threads, leaving // 2 logical cores available for time sensitive tasks. static const int MIN_PROCESSING_THREAD_POOL_SIZE = 2; -static const int PROCESSING_THREAD_POOL_SIZE = std::max(MIN_PROCESSING_THREAD_POOL_SIZE, - QThread::idealThreadCount() - 2); static const QString SNAPSHOT_EXTENSION = ".jpg"; static const QString SVO_EXTENSION = ".svo"; @@ -537,7 +534,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : PluginContainer* pluginContainer = dynamic_cast(this); // set the container for any plugins that care PluginManager::getInstance()->setContainer(pluginContainer); - QThreadPool::globalInstance()->setMaxThreadCount(PROCESSING_THREAD_POOL_SIZE); + QThreadPool::globalInstance()->setMaxThreadCount(MIN_PROCESSING_THREAD_POOL_SIZE); thread()->setPriority(QThread::HighPriority); thread()->setObjectName("Main Thread"); @@ -707,6 +704,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : connect(addressManager.data(), &AddressManager::hostChanged, this, &Application::updateWindowTitle); connect(this, &QCoreApplication::aboutToQuit, addressManager.data(), &AddressManager::storeCurrentAddress); + connect(this, &Application::activeDisplayPluginChanged, this, &Application::updateThreadPoolCount); + // Save avatar location immediately after a teleport. connect(getMyAvatar(), &MyAvatar::positionGoneTo, DependencyManager::get().data(), &AddressManager::storeCurrentAddress); @@ -778,8 +777,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : // enable mouse tracking; otherwise, we only get drag events _glWidget->setMouseTracking(true); + // Make sure the window is set to the correct size by processing the pending events + QCoreApplication::processEvents(); + _glWidget->createContext(); _glWidget->makeCurrent(); - _glWidget->initializeGL(); initializeGL(); // Make sure we don't time out during slow operations at startup @@ -1484,7 +1485,7 @@ void Application::initializeGL() { _glWidget->makeCurrent(); _chromiumShareContext = new OffscreenGLCanvas(); _chromiumShareContext->setObjectName("ChromiumShareContext"); - _chromiumShareContext->create(_glWidget->context()->contextHandle()); + _chromiumShareContext->create(_glWidget->qglContext()); _chromiumShareContext->makeCurrent(); qt_gl_set_global_share_context(_chromiumShareContext->getContext()); @@ -1531,7 +1532,7 @@ void Application::initializeGL() { _offscreenContext = new OffscreenGLCanvas(); _offscreenContext->setObjectName("MainThreadContext"); - _offscreenContext->create(_glWidget->context()->contextHandle()); + _offscreenContext->create(_glWidget->qglContext()); _offscreenContext->makeCurrent(); // update before the first render @@ -1553,7 +1554,7 @@ void Application::initializeUi() { auto offscreenUi = DependencyManager::get(); - offscreenUi->create(_glWidget->context()->contextHandle()); + offscreenUi->create(_glWidget->qglContext()); auto rootContext = offscreenUi->getRootContext(); @@ -1683,7 +1684,6 @@ void Application::paintGL() { Finally clearFlag([this] { _inPaint = false; }); _frameCount++; - _frameCounter.increment(); auto lastPaintBegin = usecTimestampNow(); PROFILE_RANGE_EX(__FUNCTION__, 0xff0000ff, (uint64_t)_frameCount); @@ -1920,6 +1920,7 @@ void Application::paintGL() { { PROFILE_RANGE(__FUNCTION__ "/pluginOutput"); PerformanceTimer perfTimer("pluginOutput"); + _frameCounter.increment(); displayPlugin->submitFrame(frame); } @@ -4316,18 +4317,14 @@ namespace render { switch (backgroundMode) { case model::SunSkyStage::SKY_DEFAULT: { - static const glm::vec3 DEFAULT_SKYBOX_COLOR{ 255.0f / 255.0f, 220.0f / 255.0f, 194.0f / 255.0f }; - static const float DEFAULT_SKYBOX_INTENSITY{ 0.2f }; - static const float DEFAULT_SKYBOX_AMBIENT_INTENSITY{ 2.0f }; - static const glm::vec3 DEFAULT_SKYBOX_DIRECTION{ 0.0f, 0.0f, -1.0f }; - auto scene = DependencyManager::get()->getStage(); auto sceneKeyLight = scene->getKeyLight(); + scene->setSunModelEnable(false); - sceneKeyLight->setColor(DEFAULT_SKYBOX_COLOR); - sceneKeyLight->setIntensity(DEFAULT_SKYBOX_INTENSITY); - sceneKeyLight->setAmbientIntensity(DEFAULT_SKYBOX_AMBIENT_INTENSITY); - sceneKeyLight->setDirection(DEFAULT_SKYBOX_DIRECTION); + sceneKeyLight->setColor(ColorUtils::toVec3(KeyLightPropertyGroup::DEFAULT_KEYLIGHT_COLOR)); + sceneKeyLight->setIntensity(KeyLightPropertyGroup::DEFAULT_KEYLIGHT_INTENSITY); + sceneKeyLight->setAmbientIntensity(KeyLightPropertyGroup::DEFAULT_KEYLIGHT_AMBIENT_INTENSITY); + sceneKeyLight->setDirection(KeyLightPropertyGroup::DEFAULT_KEYLIGHT_DIRECTION); // fall through: render a skybox (if available), or the defaults (if requested) } @@ -4346,8 +4343,7 @@ namespace render { auto scene = DependencyManager::get()->getStage(); auto sceneKeyLight = scene->getKeyLight(); auto defaultSkyboxAmbientTexture = qApp->getDefaultSkyboxAmbientTexture(); - // set the ambient sphere uniformly - the defaultSkyboxAmbientTexture has peaks that cause flashing when turning - sceneKeyLight->setAmbientSphere(DependencyManager::get()->getWhiteTexture()->getIrradiance()); + sceneKeyLight->setAmbientSphere(defaultSkyboxAmbientTexture->getIrradiance()); sceneKeyLight->setAmbientMap(defaultSkyboxAmbientTexture); // fall through: render defaults skybox } else { @@ -4861,7 +4857,6 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter, LocationScriptingInterface::locationSetter); - scriptEngine->registerFunction("WebWindow", WebWindowClass::constructor, 1); scriptEngine->registerFunction("OverlayWebWindow", QmlWebWindowClass::constructor); scriptEngine->registerFunction("OverlayWindow", QmlWindowClass::constructor); @@ -5037,6 +5032,7 @@ bool Application::askToLoadScript(const QString& scriptFilenameOrURL) { bool Application::askToWearAvatarAttachmentUrl(const QString& url) { QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest networkRequest = QNetworkRequest(url); + networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); QNetworkReply* reply = networkAccessManager.get(networkRequest); int requestNumber = ++_avatarAttachmentRequest; @@ -5675,7 +5671,7 @@ MainWindow* Application::getPrimaryWindow() { } QOpenGLContext* Application::getPrimaryContext() { - return _glWidget->context()->contextHandle(); + return _glWidget->qglContext(); } bool Application::makeRenderingContextCurrent() { @@ -5730,3 +5726,18 @@ void Application::sendHoverLeaveEntity(QUuid id, PointerEvent event) { EntityItemID entityItemID(id); emit getEntities()->hoverLeaveEntity(entityItemID, event); } + +// FIXME? perhaps two, one for the main thread and one for the offscreen UI rendering thread? +static const int UI_RESERVED_THREADS = 1; +// Windows won't let you have all the cores +static const int OS_RESERVED_THREADS = 1; + +void Application::updateThreadPoolCount() const { + auto reservedThreads = UI_RESERVED_THREADS + OS_RESERVED_THREADS + _displayPlugin->getRequiredThreadCount(); + auto availableThreads = QThread::idealThreadCount() - reservedThreads; + auto threadPoolSize = std::max(MIN_PROCESSING_THREAD_POOL_SIZE, availableThreads); + qDebug() << "Ideal Thread Count " << QThread::idealThreadCount(); + qDebug() << "Reserved threads " << reservedThreads; + qDebug() << "Setting thread pool size to " << threadPoolSize; + QThreadPool::globalInstance()->setMaxThreadCount(threadPoolSize); +} diff --git a/interface/src/Application.h b/interface/src/Application.h index a0c67a9e73..8bfae51179 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -286,6 +286,7 @@ public slots: bool exportEntities(const QString& filename, const QVector& entityIDs, const glm::vec3* givenOffset = nullptr); bool exportEntities(const QString& filename, float x, float y, float z, float scale); bool importEntities(const QString& url); + void updateThreadPoolCount() const; static void setLowVelocityFilter(bool lowVelocityFilter); Q_INVOKABLE void loadDialog(); diff --git a/interface/src/ConnectionMonitor.cpp b/interface/src/ConnectionMonitor.cpp index ec4e277fbd..bab626c0b9 100644 --- a/interface/src/ConnectionMonitor.cpp +++ b/interface/src/ConnectionMonitor.cpp @@ -29,7 +29,9 @@ void ConnectionMonitor::init() { _timer.setSingleShot(true); _timer.setInterval(DISPLAY_AFTER_DISCONNECTED_FOR_X_MS); - _timer.start(); + if (!domainHandler.isConnected()) { + _timer.start(); + } auto dialogsManager = DependencyManager::get(); connect(&_timer, &QTimer::timeout, dialogsManager.data(), &DialogsManager::showAddressBar); diff --git a/interface/src/DiscoverabilityManager.cpp b/interface/src/DiscoverabilityManager.cpp index dd80dadca7..eb9a7c7f6d 100644 --- a/interface/src/DiscoverabilityManager.cpp +++ b/interface/src/DiscoverabilityManager.cpp @@ -111,7 +111,7 @@ void DiscoverabilityManager::updateLocation() { } // Update Steam - SteamClient::updateLocation(domainHandler.getHostname(), addressManager->currentFacingAddress()); + SteamClient::updateLocation(domainHandler.getHostname(), addressManager->currentFacingShareableAddress()); } void DiscoverabilityManager::handleHeartbeatResponse(QNetworkReply& requestReply) { diff --git a/interface/src/InterfaceActionFactory.cpp b/interface/src/InterfaceActionFactory.cpp index 1869980270..2bc4608e86 100644 --- a/interface/src/InterfaceActionFactory.cpp +++ b/interface/src/InterfaceActionFactory.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include "InterfaceActionFactory.h" @@ -29,6 +30,8 @@ EntityActionPointer interfaceActionFactory(EntityActionType type, const QUuid& i return std::make_shared(id, ownerEntity); case ACTION_TYPE_HOLD: return std::make_shared(id, ownerEntity); + case ACTION_TYPE_TRAVEL_ORIENTED: + return std::make_shared(id, ownerEntity); } Q_ASSERT_X(false, Q_FUNC_INFO, "Unknown entity action type"); diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 31fb2abe53..717a0eeac7 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -59,8 +59,6 @@ const float DISPLAYNAME_ALPHA = 1.0f; const float DISPLAYNAME_BACKGROUND_ALPHA = 0.4f; const glm::vec3 HAND_TO_PALM_OFFSET(0.0f, 0.12f, 0.08f); -const int SENSOR_TO_WORLD_MATRIX_INDEX = 65534; - namespace render { template <> const ItemKey payloadGetKey(const AvatarSharedPointer& avatar) { return ItemKey::Builder::opaqueShape(); @@ -853,32 +851,54 @@ glm::vec3 Avatar::getDefaultJointTranslation(int index) const { } glm::quat Avatar::getAbsoluteJointRotationInObjectFrame(int index) const { - if (index == SENSOR_TO_WORLD_MATRIX_INDEX) { - glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix(); - bool success; - Transform avatarTransform; - Transform::mult(avatarTransform, getParentTransform(success), getLocalTransform()); - glm::mat4 invAvatarMat = avatarTransform.getInverseMatrix(); - return glmExtractRotation(invAvatarMat * sensorToWorldMatrix); - } else { - glm::quat rotation; - _skeletonModel->getAbsoluteJointRotationInRigFrame(index, rotation); - return Quaternions::Y_180 * rotation; + switch(index) { + case SENSOR_TO_WORLD_MATRIX_INDEX: { + glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix(); + bool success; + Transform avatarTransform; + Transform::mult(avatarTransform, getParentTransform(success), getLocalTransform()); + glm::mat4 invAvatarMat = avatarTransform.getInverseMatrix(); + return glmExtractRotation(invAvatarMat * sensorToWorldMatrix); + } + case CONTROLLER_LEFTHAND_INDEX: { + Transform controllerLeftHandTransform = Transform(getControllerLeftHandMatrix()); + return controllerLeftHandTransform.getRotation(); + } + case CONTROLLER_RIGHTHAND_INDEX: { + Transform controllerRightHandTransform = Transform(getControllerRightHandMatrix()); + return controllerRightHandTransform.getRotation(); + } + default: { + glm::quat rotation; + _skeletonModel->getAbsoluteJointRotationInRigFrame(index, rotation); + return Quaternions::Y_180 * rotation; + } } } glm::vec3 Avatar::getAbsoluteJointTranslationInObjectFrame(int index) const { - if (index == SENSOR_TO_WORLD_MATRIX_INDEX) { - glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix(); - bool success; - Transform avatarTransform; - Transform::mult(avatarTransform, getParentTransform(success), getLocalTransform()); - glm::mat4 invAvatarMat = avatarTransform.getInverseMatrix(); - return extractTranslation(invAvatarMat * sensorToWorldMatrix); - } else { - glm::vec3 translation; - _skeletonModel->getAbsoluteJointTranslationInRigFrame(index, translation); - return Quaternions::Y_180 * translation; + switch(index) { + case SENSOR_TO_WORLD_MATRIX_INDEX: { + glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix(); + bool success; + Transform avatarTransform; + Transform::mult(avatarTransform, getParentTransform(success), getLocalTransform()); + glm::mat4 invAvatarMat = avatarTransform.getInverseMatrix(); + return extractTranslation(invAvatarMat * sensorToWorldMatrix); + } + case CONTROLLER_LEFTHAND_INDEX: { + Transform controllerLeftHandTransform = Transform(getControllerLeftHandMatrix()); + return controllerLeftHandTransform.getTranslation(); + } + case CONTROLLER_RIGHTHAND_INDEX: { + Transform controllerRightHandTransform = Transform(getControllerRightHandMatrix()); + return controllerRightHandTransform.getTranslation(); + } + default: { + glm::vec3 translation; + _skeletonModel->getAbsoluteJointTranslationInRigFrame(index, translation); + return Quaternions::Y_180 * translation; + } } } @@ -889,6 +909,10 @@ int Avatar::getJointIndex(const QString& name) const { Q_RETURN_ARG(int, result), Q_ARG(const QString&, name)); return result; } + int result = getFauxJointIndex(name); + if (result != -1) { + return result; + } return _skeletonModel->isActive() ? _skeletonModel->getFBXGeometry().getJointIndex(name) : -1; } diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp index 5acee052f2..51171b9c6b 100644 --- a/interface/src/avatar/AvatarActionHold.cpp +++ b/interface/src/avatar/AvatarActionHold.cpp @@ -56,6 +56,10 @@ void AvatarActionHold::prepareForPhysicsSimulation() { } withWriteLock([&]{ + glm::vec3 avatarRigidBodyPosition; + glm::quat avatarRigidBodyRotation; + getAvatarRigidBodyLocation(avatarRigidBodyPosition, avatarRigidBodyRotation); + if (_ignoreIK) { return; } @@ -70,9 +74,6 @@ void AvatarActionHold::prepareForPhysicsSimulation() { palmRotation = holdingAvatar->getUncachedLeftPalmRotation(); } - glm::vec3 avatarRigidBodyPosition; - glm::quat avatarRigidBodyRotation; - getAvatarRigidBodyLocation(avatarRigidBodyPosition, avatarRigidBodyRotation); // determine the difference in translation and rotation between the avatar's // rigid body and the palm position. The avatar's rigid body will be moved by bullet @@ -124,13 +125,20 @@ bool AvatarActionHold::getTarget(float deltaTimeStep, glm::quat& rotation, glm:: if (pose.isValid()) { linearVelocity = pose.getVelocity(); angularVelocity = pose.getAngularVelocity(); + + if (isRightHand) { + pose = avatarManager->getMyAvatar()->getRightHandControllerPoseInAvatarFrame(); + } else { + pose = avatarManager->getMyAvatar()->getLeftHandControllerPoseInAvatarFrame(); + } } if (_ignoreIK && pose.isValid()) { - // We cannot ignore other avatars IK and this is not the point of this option - // This is meant to make the grabbing behavior more reactive. - palmPosition = pose.getTranslation(); - palmRotation = pose.getRotation(); + Transform avatarTransform; + auto myAvatar = DependencyManager::get()->getMyAvatar(); + avatarTransform = myAvatar->getTransform(); + palmPosition = avatarTransform.transform(pose.getTranslation() / myAvatar->getTargetScale()); + palmRotation = avatarTransform.getRotation() * pose.getRotation(); } else { glm::vec3 avatarRigidBodyPosition; glm::quat avatarRigidBodyRotation; @@ -159,11 +167,17 @@ bool AvatarActionHold::getTarget(float deltaTimeStep, glm::quat& rotation, glm:: } } else { // regular avatar if (isRightHand) { - palmPosition = holdingAvatar->getRightPalmPosition(); - palmRotation = holdingAvatar->getRightPalmRotation(); + Transform controllerRightTransform = Transform(holdingAvatar->getControllerRightHandMatrix()); + Transform avatarTransform = holdingAvatar->getTransform(); + palmRotation = avatarTransform.getRotation() * controllerRightTransform.getRotation(); + palmPosition = avatarTransform.getTranslation() + + (avatarTransform.getRotation() * controllerRightTransform.getTranslation()); } else { - palmPosition = holdingAvatar->getLeftPalmPosition(); - palmRotation = holdingAvatar->getLeftPalmRotation(); + Transform controllerLeftTransform = Transform(holdingAvatar->getControllerLeftHandMatrix()); + Transform avatarTransform = holdingAvatar->getTransform(); + palmRotation = avatarTransform.getRotation() * controllerLeftTransform.getRotation(); + palmPosition = avatarTransform.getTranslation() + + (avatarTransform.getRotation() * controllerLeftTransform.getTranslation()); } } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 5687b5025c..24dbc62318 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -532,11 +532,21 @@ void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix) { _hmdSensorFacing = getFacingDir2D(_hmdSensorOrientation); } +void MyAvatar::updateJointFromController(controller::Action poseKey, ThreadSafeValueCache& matrixCache) { + assert(QThread::currentThread() == thread()); + auto userInputMapper = DependencyManager::get(); + controller::Pose controllerPose = userInputMapper->getPoseState(poseKey); + Transform transform; + transform.setTranslation(controllerPose.getTranslation()); + transform.setRotation(controllerPose.getRotation()); + glm::mat4 controllerMatrix = transform.getMatrix(); + matrixCache.set(controllerMatrix); +} + // best called at end of main loop, after physics. // update sensor to world matrix from current body position and hmd sensor. // This is so the correct camera can be used for rendering. void MyAvatar::updateSensorToWorldMatrix() { - // update the sensor mat so that the body position will end up in the desired // position when driven from the head. glm::mat4 desiredMat = createMatFromQuatAndPos(getOrientation(), getPosition()); @@ -545,10 +555,14 @@ void MyAvatar::updateSensorToWorldMatrix() { lateUpdatePalms(); if (_enableDebugDrawSensorToWorldMatrix) { - DebugDraw::getInstance().addMarker("sensorToWorldMatrix", glmExtractRotation(_sensorToWorldMatrix), extractTranslation(_sensorToWorldMatrix), glm::vec4(1)); + DebugDraw::getInstance().addMarker("sensorToWorldMatrix", glmExtractRotation(_sensorToWorldMatrix), + extractTranslation(_sensorToWorldMatrix), glm::vec4(1)); } _sensorToWorldMatrixCache.set(_sensorToWorldMatrix); + + updateJointFromController(controller::Action::LEFT_HAND, _controllerLeftHandMatrixCache); + updateJointFromController(controller::Action::RIGHT_HAND, _controllerRightHandMatrixCache); } // Update avatar head rotation with sensor data @@ -2215,3 +2229,31 @@ bool MyAvatar::didTeleport() { bool MyAvatar::hasDriveInput() const { return fabsf(_driveKeys[TRANSLATE_X]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Y]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Z]) > 0.0f; } + +glm::quat MyAvatar::getAbsoluteJointRotationInObjectFrame(int index) const { + switch(index) { + case CONTROLLER_LEFTHAND_INDEX: { + return getLeftHandControllerPoseInAvatarFrame().getRotation(); + } + case CONTROLLER_RIGHTHAND_INDEX: { + return getRightHandControllerPoseInAvatarFrame().getRotation(); + } + default: { + return Avatar::getAbsoluteJointRotationInObjectFrame(index); + } + } +} + +glm::vec3 MyAvatar::getAbsoluteJointTranslationInObjectFrame(int index) const { + switch(index) { + case CONTROLLER_LEFTHAND_INDEX: { + return getLeftHandControllerPoseInAvatarFrame().getTranslation(); + } + case CONTROLLER_RIGHTHAND_INDEX: { + return getRightHandControllerPoseInAvatarFrame().getTranslation(); + } + default: { + return Avatar::getAbsoluteJointTranslationInObjectFrame(index); + } + } +} diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 1f212a1fec..c4ffc08cbc 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -19,6 +19,7 @@ #include #include +#include #include "Avatar.h" #include "AtRestDetector.h" @@ -117,6 +118,9 @@ public: // as it moves through the world. void updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix); + // read the location of a hand controller and save the transform + void updateJointFromController(controller::Action poseKey, ThreadSafeValueCache& matrixCache); + // best called at end of main loop, just before rendering. // update sensor to world matrix from current body position and hmd sensor. // This is so the correct camera can be used for rendering. @@ -270,6 +274,9 @@ public: Q_INVOKABLE void setCharacterControllerEnabled(bool enabled); Q_INVOKABLE bool getCharacterControllerEnabled(); + virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override; + virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override; + public slots: void increaseSize(); void decreaseSize(); @@ -410,9 +417,8 @@ private: bool _useSnapTurn { true }; bool _clearOverlayWhenMoving { true }; - // working copy of sensorToWorldMatrix. - // See AvatarData for thread-safe _sensorToWorldMatrixCache, used for outward facing access - glm::mat4 _sensorToWorldMatrix; + // working copies -- see AvatarData for thread-safe _sensorToWorldMatrixCache, used for outward facing access + glm::mat4 _sensorToWorldMatrix { glm::mat4() }; // cache of the current HMD sensor position and orientation // in sensor space. diff --git a/interface/src/java/io/highfidelity/interface/InterfaceActivity.java b/interface/src/java/io/highfidelity/interface/InterfaceActivity.java new file mode 100644 index 0000000000..c7cbdd3dff --- /dev/null +++ b/interface/src/java/io/highfidelity/interface/InterfaceActivity.java @@ -0,0 +1,41 @@ +// +// InterfaceActivity.java +// gvr-interface/java +// +// Created by Stephen Birarda on 1/26/15. +// Copyright 2015 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 +// + +package io.highfidelity.gvrinterface; + +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.view.WindowManager; +import android.util.Log; +import org.qtproject.qt5.android.bindings.QtActivity; + +public class InterfaceActivity extends QtActivity { + + public static native void handleHifiURL(String hifiURLString); + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + + // Get the intent that started this activity in case we have a hifi:// URL to parse + Intent intent = getIntent(); + if (intent.getAction() == Intent.ACTION_VIEW) { + Uri data = intent.getData(); + + if (data.getScheme().equals("hifi")) { + handleHifiURL(data.toString()); + } + } + + } +} \ No newline at end of file diff --git a/interface/src/scripting/WebWindowClass.cpp b/interface/src/scripting/WebWindowClass.cpp deleted file mode 100644 index 18beee4bbf..0000000000 --- a/interface/src/scripting/WebWindowClass.cpp +++ /dev/null @@ -1,182 +0,0 @@ -// -// WebWindowClass.cpp -// interface/src/scripting -// -// Created by Ryan Huffman on 11/06/14. -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "Application.h" -#include "ui/DataWebPage.h" -#include "MainWindow.h" -#include "WebWindowClass.h" -#include "WindowScriptingInterface.h" - -ScriptEventBridge::ScriptEventBridge(QObject* parent) : QObject(parent) { -} - -void ScriptEventBridge::emitWebEvent(const QString& data) { - emit webEventReceived(data); -} - -void ScriptEventBridge::emitScriptEvent(const QString& data) { - emit scriptEventReceived(data); -} - -WebWindowClass::WebWindowClass(const QString& title, const QString& url, int width, int height) - : QObject(NULL), _eventBridge(new ScriptEventBridge(this)) { - auto dialogWidget = new QDialog(qApp->getWindow(), Qt::Window); - dialogWidget->setWindowTitle(title); - dialogWidget->resize(width, height); - dialogWidget->installEventFilter(this); - connect(dialogWidget, &QDialog::finished, this, &WebWindowClass::hasClosed); - - auto layout = new QVBoxLayout(dialogWidget); - layout->setContentsMargins(0, 0, 0, 0); - dialogWidget->setLayout(layout); - - _webView = new QWebView(dialogWidget); - - layout->addWidget(_webView); - - addEventBridgeToWindowObject(); - - _windowWidget = dialogWidget; - - auto style = QStyleFactory::create("fusion"); - if (style) { - _webView->setStyle(style); - } - - _webView->setPage(new DataWebPage()); - if (!url.startsWith("http") && !url.startsWith("file://")) { - _webView->setUrl(QUrl::fromLocalFile(url)); - } else { - _webView->setUrl(url); - } - - connect(this, &WebWindowClass::destroyed, _windowWidget, &QWidget::deleteLater); - connect(_webView->page()->mainFrame(), &QWebFrame::javaScriptWindowObjectCleared, - this, &WebWindowClass::addEventBridgeToWindowObject); -} - -WebWindowClass::~WebWindowClass() { -} - -bool WebWindowClass::eventFilter(QObject* sender, QEvent* event) { - if (sender == _windowWidget) { - if (event->type() == QEvent::Move) { - emit moved(getPosition()); - } - if (event->type() == QEvent::Resize) { - emit resized(getSize()); - } - } - - return false; -} - -void WebWindowClass::hasClosed() { - emit closed(); -} - -void WebWindowClass::addEventBridgeToWindowObject() { - _webView->page()->mainFrame()->addToJavaScriptWindowObject("EventBridge", _eventBridge); -} - -void WebWindowClass::setVisible(bool visible) { - if (visible) { - QMetaObject::invokeMethod(_windowWidget, "showNormal", Qt::AutoConnection); - QMetaObject::invokeMethod(_windowWidget, "raise", Qt::AutoConnection); - } - QMetaObject::invokeMethod(_windowWidget, "setVisible", Qt::AutoConnection, Q_ARG(bool, visible)); -} - -void WebWindowClass::setURL(const QString& url) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "setURL", Qt::AutoConnection, Q_ARG(QString, url)); - return; - } - _webView->setUrl(url); -} - -QSizeF WebWindowClass::getSize() const { - QSizeF size = _windowWidget->size(); - return size; -} - -void WebWindowClass::setSize(QSizeF size) { - setSize(size.width(), size.height()); -} - -void WebWindowClass::setSize(int width, int height) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "setSize", Qt::AutoConnection, Q_ARG(int, width), Q_ARG(int, height)); - return; - } - _windowWidget->resize(width, height); -} - -glm::vec2 WebWindowClass::getPosition() const { - QPoint position = _windowWidget->pos(); - return glm::vec2(position.x(), position.y()); -} - -void WebWindowClass::setPosition(glm::vec2 position) { - setPosition(position.x, position.y); -} - -void WebWindowClass::setPosition(int x, int y) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "setPosition", Qt::AutoConnection, Q_ARG(int, x), Q_ARG(int, y)); - return; - } - _windowWidget->move(x, y); -} - -void WebWindowClass::raise() { - QMetaObject::invokeMethod(_windowWidget, "showNormal", Qt::AutoConnection); - QMetaObject::invokeMethod(_windowWidget, "raise", Qt::AutoConnection); -} - -QScriptValue WebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) { - WebWindowClass* retVal; - QString file = context->argument(0).toString(); - if (context->argument(4).toBool()) { - qWarning() << "ToolWindow views with WebWindow are no longer supported. Use OverlayWebWindow instead"; - return QScriptValue(); - } else { - qWarning() << "WebWindow views are deprecated. Use OverlayWebWindow instead"; - } - QMetaObject::invokeMethod(DependencyManager::get().data(), "doCreateWebWindow", Qt::BlockingQueuedConnection, - Q_RETURN_ARG(WebWindowClass*, retVal), - Q_ARG(const QString&, file), - Q_ARG(QString, context->argument(1).toString()), - Q_ARG(int, context->argument(2).toInteger()), - Q_ARG(int, context->argument(3).toInteger())); - - connect(engine, &QScriptEngine::destroyed, retVal, &WebWindowClass::deleteLater); - - return engine->newQObject(retVal); -} - -void WebWindowClass::setTitle(const QString& title) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "setTitle", Qt::AutoConnection, Q_ARG(QString, title)); - return; - } - _windowWidget->setWindowTitle(title); -} diff --git a/interface/src/scripting/WebWindowClass.h b/interface/src/scripting/WebWindowClass.h deleted file mode 100644 index d7a610dd39..0000000000 --- a/interface/src/scripting/WebWindowClass.h +++ /dev/null @@ -1,80 +0,0 @@ -// -// WebWindowClass.h -// interface/src/scripting -// -// Created by Ryan Huffman on 11/06/14. -// Copyright 2014 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_WebWindowClass_h -#define hifi_WebWindowClass_h - -#include -#include -#include - -class ScriptEventBridge : public QObject { - Q_OBJECT -public: - ScriptEventBridge(QObject* parent = NULL); - -public slots: - void emitWebEvent(const QString& data); - void emitScriptEvent(const QString& data); - -signals: - void webEventReceived(const QString& data); - void scriptEventReceived(const QString& data); - -}; - -class WebWindowClass : public QObject { - Q_OBJECT - Q_PROPERTY(QObject* eventBridge READ getEventBridge) - Q_PROPERTY(QString url READ getURL) - Q_PROPERTY(glm::vec2 position READ getPosition WRITE setPosition); - Q_PROPERTY(QSizeF size READ getSize WRITE setSize); - -public: - WebWindowClass(const QString& title, const QString& url, int width, int height); - ~WebWindowClass(); - - static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); - -public slots: - void setVisible(bool visible); - glm::vec2 getPosition() const; - void setPosition(int x, int y); - void setPosition(glm::vec2 position); - QSizeF getSize() const; - void setSize(QSizeF size); - void setSize(int width, int height); - QString getURL() const { return _webView->url().url(); } - void setURL(const QString& url); - void raise(); - ScriptEventBridge* getEventBridge() const { return _eventBridge; } - void addEventBridgeToWindowObject(); - void setTitle(const QString& title); - -signals: - void visibilityChanged(bool visible); // Tool window - void moved(glm::vec2 position); - void resized(QSizeF size); - void closed(); - -protected: - virtual bool eventFilter(QObject* sender, QEvent* event) override; - -private slots: - void hasClosed(); - -private: - QWidget* _windowWidget; - QWebView* _webView; - ScriptEventBridge* _eventBridge; -}; - -#endif diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 4eb8c67250..c528c26b99 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -21,7 +21,6 @@ #include "MainWindow.h" #include "Menu.h" #include "OffscreenUi.h" -#include "WebWindowClass.h" #include "WindowScriptingInterface.h" @@ -61,10 +60,6 @@ WindowScriptingInterface::WindowScriptingInterface() { }); } -WebWindowClass* WindowScriptingInterface::doCreateWebWindow(const QString& title, const QString& url, int width, int height) { - return new WebWindowClass(title, url, width, height); -} - QScriptValue WindowScriptingInterface::hasFocus() { return qApp->hasFocus(); } diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 7a01be7fac..715d0657a3 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -16,9 +16,6 @@ #include #include -class WebWindowClass; - - class CustomPromptResult { public: QVariant value; @@ -65,9 +62,6 @@ signals: void snapshotTaken(const QString& path, bool notify); void snapshotShared(const QString& error); -private slots: - WebWindowClass* doCreateWebWindow(const QString& title, const QString& url, int width, int height); - private: QString getPreviousBrowseLocation() const; void setPreviousBrowseLocation(const QString& location); diff --git a/interface/src/ui/DataWebPage.cpp b/interface/src/ui/DataWebPage.cpp deleted file mode 100644 index 01feacc393..0000000000 --- a/interface/src/ui/DataWebPage.cpp +++ /dev/null @@ -1,54 +0,0 @@ -// -// DataWebPage.cpp -// interface/src/ui -// -// Created by Stephen Birarda on 2014-09-22. -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include - -#include "Application.h" -#include -#include - -#include "DataWebPage.h" - -DataWebPage::DataWebPage(QObject* parent) : - QWebPage(parent) -{ - // use an OAuthNetworkAccessManager instead of regular QNetworkAccessManager so our requests are authed - setNetworkAccessManager(OAuthNetworkAccessManager::getInstance()); - - // give the page an empty stylesheet - settings()->setUserStyleSheetUrl(QUrl()); -} - -void DataWebPage::javaScriptConsoleMessage(const QString& message, int lineNumber, const QString& sourceID) { - qDebug() << "JS console message at line" << lineNumber << "from" << sourceID << "-" << message; -} - -bool DataWebPage::acceptNavigationRequest(QWebFrame* frame, const QNetworkRequest& request, QWebPage::NavigationType type) { - // Handle hifi:// links and links to files with particular extensions - QString urlString = request.url().toString(); - if (qApp->canAcceptURL(urlString)) { - if (qApp->acceptURL(urlString)) { - return false; // we handled it, so QWebPage doesn't need to handle it - } - } - - // Make hyperlinks with target="_blank" open in user's Web browser - if (type == QWebPage::NavigationTypeLinkClicked && frame == nullptr) { - qApp->openUrl(request.url()); - return false; // We handled it. - } - - return true; -} - -QString DataWebPage::userAgentForUrl(const QUrl& url) const { - return HIGH_FIDELITY_USER_AGENT; -} diff --git a/interface/src/ui/DataWebPage.h b/interface/src/ui/DataWebPage.h deleted file mode 100644 index f9aa5be8a8..0000000000 --- a/interface/src/ui/DataWebPage.h +++ /dev/null @@ -1,26 +0,0 @@ -// -// DataWebPage.h -// interface/src/ui -// -// Created by Stephen Birarda on 2014-09-22. -// Copyright 2014 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_DataWebPage_h -#define hifi_DataWebPage_h - -#include - -class DataWebPage : public QWebPage { -public: - DataWebPage(QObject* parent = 0); -protected: - void javaScriptConsoleMessage(const QString & message, int lineNumber, const QString & sourceID) override; - bool acceptNavigationRequest(QWebFrame* frame, const QNetworkRequest& request, QWebPage::NavigationType type) override; - virtual QString userAgentForUrl(const QUrl& url) const override; -}; - -#endif // hifi_DataWebPage_h diff --git a/interface/src/ui/ModelsBrowser.cpp b/interface/src/ui/ModelsBrowser.cpp index 91de4e36ac..430cc805ed 100644 --- a/interface/src/ui/ModelsBrowser.cpp +++ b/interface/src/ui/ModelsBrowser.cpp @@ -228,6 +228,7 @@ void ModelHandler::update() { QUrl url(_model.item(i,0)->data(Qt::UserRole).toString()); QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); request.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); QNetworkReply* reply = networkAccessManager.head(request); connect(reply, SIGNAL(finished()), SLOT(downloadFinished())); @@ -280,6 +281,7 @@ void ModelHandler::queryNewFiles(QString marker) { url.setQuery(query); QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); request.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); QNetworkReply* reply = networkAccessManager.get(request); connect(reply, SIGNAL(finished()), SLOT(downloadFinished())); diff --git a/interface/src/ui/ScriptEditorWidget.cpp b/interface/src/ui/ScriptEditorWidget.cpp index f76a5dff78..ada6b11355 100644 --- a/interface/src/ui/ScriptEditorWidget.cpp +++ b/interface/src/ui/ScriptEditorWidget.cpp @@ -161,6 +161,7 @@ void ScriptEditorWidget::loadFile(const QString& scriptPath) { } else { QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest networkRequest = QNetworkRequest(url); + networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); QNetworkReply* reply = networkAccessManager.get(networkRequest); qDebug() << "Downloading included script at" << scriptPath; diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 7fdf5cd57d..fbe272a562 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -117,10 +117,12 @@ void Stats::updateStats(bool force) { // we need to take one avatar out so we don't include ourselves STAT_UPDATE(avatarCount, avatarManager->size() - 1); STAT_UPDATE(serverCount, (int)nodeList->size()); - STAT_UPDATE(renderrate, qApp->getFps()); + STAT_UPDATE(framerate, qApp->getFps()); if (qApp->getActiveDisplayPlugin()) { - STAT_UPDATE(presentrate, qApp->getActiveDisplayPlugin()->presentRate()); - STAT_UPDATE(presentnewrate, qApp->getActiveDisplayPlugin()->newFramePresentRate()); + auto displayPlugin = qApp->getActiveDisplayPlugin(); + STAT_UPDATE(renderrate, displayPlugin->renderRate()); + STAT_UPDATE(presentrate, displayPlugin->presentRate()); + STAT_UPDATE(presentnewrate, displayPlugin->newFramePresentRate()); STAT_UPDATE(presentdroprate, qApp->getActiveDisplayPlugin()->droppedFrameRate()); } else { STAT_UPDATE(presentrate, -1); diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index 4be2d88d9e..138f24cf19 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -32,8 +32,13 @@ class Stats : public QQuickItem { Q_PROPERTY(float audioPacketlossDownstream READ getAudioPacketLossDownstream) STATS_PROPERTY(int, serverCount, 0) + // How often the app is creating new gpu::Frames + STATS_PROPERTY(float, framerate, 0) + // How often the display plugin is executing a given frame STATS_PROPERTY(float, renderrate, 0) + // How often the display plugin is presenting to the device STATS_PROPERTY(float, presentrate, 0) + STATS_PROPERTY(float, presentnewrate, 0) STATS_PROPERTY(float, presentdroprate, 0) STATS_PROPERTY(int, simrate, 0) @@ -116,6 +121,7 @@ public slots: void forceUpdateStats() { updateStats(true); } signals: + void framerateChanged(); void expandedChanged(); void timingExpandedChanged(); void serverCountChanged(); diff --git a/libraries/audio/CMakeLists.txt b/libraries/audio/CMakeLists.txt index 1e9360b9a2..a8a398c14f 100644 --- a/libraries/audio/CMakeLists.txt +++ b/libraries/audio/CMakeLists.txt @@ -1,3 +1,8 @@ set(TARGET_NAME audio) setup_hifi_library(Network) + +if (ANDROID) + add_definitions("-D__STDC_CONSTANT_MACROS") +endif () + link_hifi_libraries(networking shared plugins) diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp index d1fbed1bb1..c6368259c0 100644 --- a/libraries/audio/src/AudioInjector.cpp +++ b/libraries/audio/src/AudioInjector.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "AudioInjector.h" + #include #include @@ -24,8 +26,6 @@ #include "SoundCache.h" #include "AudioSRC.h" -#include "AudioInjector.h" - int audioInjectorPtrMetaTypeId = qRegisterMetaType(); AudioInjectorState operator& (AudioInjectorState lhs, AudioInjectorState rhs) { @@ -52,7 +52,7 @@ AudioInjector::AudioInjector(const Sound& sound, const AudioInjectorOptions& inj AudioInjector::AudioInjector(const QByteArray& audioData, const AudioInjectorOptions& injectorOptions) : _audioData(audioData), - _options(injectorOptions) + _options(injectorOptions) { } @@ -62,7 +62,7 @@ bool AudioInjector::stateHas(AudioInjectorState state) const { } void AudioInjector::setOptions(const AudioInjectorOptions& options) { - // since options.stereo is computed from the audio stream, + // since options.stereo is computed from the audio stream, // we need to copy it from existing options just in case. bool currentlyStereo = _options.stereo; _options = options; @@ -71,7 +71,7 @@ void AudioInjector::setOptions(const AudioInjectorOptions& options) { void AudioInjector::finishNetworkInjection() { _state |= AudioInjectorState::NetworkInjectionFinished; - + // if we are already finished with local // injection, then we are finished if(stateHas(AudioInjectorState::LocalInjectionFinished)) { @@ -154,13 +154,13 @@ void AudioInjector::restart() { _hasSetup = false; _shouldStop = false; _state = AudioInjectorState::NotFinished; - + // call inject audio to start injection over again setupInjection(); // inject locally if(injectLocally()) { - + // if not localOnly, wake the AudioInjectorManager back up if it is stuck waiting if (!_options.localOnly) { @@ -221,7 +221,7 @@ qint64 writeStringToStream(const QString& string, QDataStream& stream) { stream << static_cast(length); } else { // http://doc.qt.io/qt-5/datastreamformat.html - // QDataStream << QByteArray - + // QDataStream << QByteArray - // If the byte array is null : 0xFFFFFFFF (quint32) // Otherwise : the array size(quint32) followed by the array bytes, i.e.size bytes stream << data; @@ -239,7 +239,7 @@ int64_t AudioInjector::injectNextFrame() { static int positionOptionOffset = -1; static int volumeOptionOffset = -1; static int audioDataOffset = -1; - + if (!_currentPacket) { if (_currentSendOffset < 0 || _currentSendOffset >= _audioData.size()) { @@ -277,7 +277,7 @@ int64_t AudioInjector::injectNextFrame() { // current injectors don't use codecs, so pack in the unknown codec name QString noCodecForInjectors(""); - writeStringToStream(noCodecForInjectors, audioPacketStream); + writeStringToStream(noCodecForInjectors, audioPacketStream); // pack stream identifier (a generated UUID) audioPacketStream << QUuid::createUuid(); @@ -286,7 +286,7 @@ int64_t AudioInjector::injectNextFrame() { audioPacketStream << _options.stereo; // pack the flag for loopback. Now, we don't loopback - // and _always_ play locally, so loopbackFlag should be + // and _always_ play locally, so loopbackFlag should be // false always. uchar loopbackFlag = (uchar)false; audioPacketStream << loopbackFlag; @@ -365,7 +365,7 @@ int64_t AudioInjector::injectNextFrame() { _currentSendOffset = 0; } } - // FIXME -- good place to call codec encode here. We need to figure out how to tell the AudioInjector which + // FIXME -- good place to call codec encode here. We need to figure out how to tell the AudioInjector which // codec to use... possible through AbstractAudioInterface. QByteArray encodedAudio = decodedAudio; _currentPacket->write(encodedAudio.data(), encodedAudio.size()); @@ -407,7 +407,7 @@ int64_t AudioInjector::injectNextFrame() { } int64_t playNextFrameAt = ++_nextFrame * AudioConstants::NETWORK_FRAME_USECS; - + return std::max(INT64_C(0), playNextFrameAt - currentTime); } @@ -491,7 +491,7 @@ AudioInjector* AudioInjector::playSound(const QByteArray& buffer, const AudioInj // we always inject locally, except when there is no localInterface injector->injectLocally(); - + // if localOnly, we are done, just return injector. if (!options.localOnly) { diff --git a/libraries/audio/src/AudioRingBuffer.cpp b/libraries/audio/src/AudioRingBuffer.cpp index 96f1bbb9dd..260c682cde 100644 --- a/libraries/audio/src/AudioRingBuffer.cpp +++ b/libraries/audio/src/AudioRingBuffer.cpp @@ -24,33 +24,34 @@ #include "AudioRingBuffer.h" static const QString RING_BUFFER_OVERFLOW_DEBUG { "AudioRingBuffer::writeData has overflown the buffer. Overwriting old data." }; +static const QString DROPPED_SILENT_DEBUG { "AudioRingBuffer::addSilentSamples dropping silent samples to prevent overflow." }; -AudioRingBuffer::AudioRingBuffer(int numFrameSamples, bool randomAccessMode, int numFramesCapacity) : +AudioRingBuffer::AudioRingBuffer(int numFrameSamples, int numFramesCapacity) : + _numFrameSamples(numFrameSamples), _frameCapacity(numFramesCapacity), _sampleCapacity(numFrameSamples * numFramesCapacity), - _bufferLength(numFrameSamples * (numFramesCapacity + 1)), - _numFrameSamples(numFrameSamples), - _randomAccessMode(randomAccessMode), - _overflowCount(0) + _bufferLength(numFrameSamples * (numFramesCapacity + 1)) { if (numFrameSamples) { _buffer = new int16_t[_bufferLength]; memset(_buffer, 0, _bufferLength * sizeof(int16_t)); _nextOutput = _buffer; _endOfLastWrite = _buffer; - } else { - _buffer = NULL; - _nextOutput = NULL; - _endOfLastWrite = NULL; } - static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex(RING_BUFFER_OVERFLOW_DEBUG); + static QString repeatedOverflowMessage = LogHandler::getInstance().addRepeatedMessageRegex(RING_BUFFER_OVERFLOW_DEBUG); + static QString repeatedDroppedMessage = LogHandler::getInstance().addRepeatedMessageRegex(DROPPED_SILENT_DEBUG); }; AudioRingBuffer::~AudioRingBuffer() { delete[] _buffer; } +void AudioRingBuffer::clear() { + _endOfLastWrite = _buffer; + _nextOutput = _buffer; +} + void AudioRingBuffer::reset() { clear(); _overflowCount = 0; @@ -58,109 +59,82 @@ void AudioRingBuffer::reset() { void AudioRingBuffer::resizeForFrameSize(int numFrameSamples) { delete[] _buffer; + _numFrameSamples = numFrameSamples; _sampleCapacity = numFrameSamples * _frameCapacity; _bufferLength = numFrameSamples * (_frameCapacity + 1); - _numFrameSamples = numFrameSamples; - _buffer = new int16_t[_bufferLength]; - memset(_buffer, 0, _bufferLength * sizeof(int16_t)); - if (_randomAccessMode) { - memset(_buffer, 0, _bufferLength * sizeof(int16_t)); - } - reset(); -} -void AudioRingBuffer::clear() { - _endOfLastWrite = _buffer; - _nextOutput = _buffer; + if (numFrameSamples) { + _buffer = new int16_t[_bufferLength]; + memset(_buffer, 0, _bufferLength * sizeof(int16_t)); + } else { + _buffer = nullptr; + } + + reset(); } int AudioRingBuffer::readSamples(int16_t* destination, int maxSamples) { return readData((char*)destination, maxSamples * sizeof(int16_t)) / sizeof(int16_t); } +int AudioRingBuffer::writeSamples(const int16_t* source, int maxSamples) { + return writeData((char*)source, maxSamples * sizeof(int16_t)) / sizeof(int16_t); +} + int AudioRingBuffer::readData(char *data, int maxSize) { - // only copy up to the number of samples we have available - int numReadSamples = std::min((int)(maxSize / sizeof(int16_t)), samplesAvailable()); - - // If we're in random access mode, then we consider our number of available read samples slightly - // differently. Namely, if anything has been written, we say we have as many samples as they ask for - // otherwise we say we have nothing available - if (_randomAccessMode) { - numReadSamples = _endOfLastWrite ? (maxSize / sizeof(int16_t)) : 0; - } + int maxSamples = maxSize / sizeof(int16_t); + int numReadSamples = std::min(maxSamples, samplesAvailable()); if (_nextOutput + numReadSamples > _buffer + _bufferLength) { // we're going to need to do two reads to get this data, it wraps around the edge + int numSamplesToEnd = (_buffer + _bufferLength) - _nextOutput; // read to the end of the buffer - int numSamplesToEnd = (_buffer + _bufferLength) - _nextOutput; memcpy(data, _nextOutput, numSamplesToEnd * sizeof(int16_t)); - if (_randomAccessMode) { - memset(_nextOutput, 0, numSamplesToEnd * sizeof(int16_t)); // clear it - } // read the rest from the beginning of the buffer memcpy(data + (numSamplesToEnd * sizeof(int16_t)), _buffer, (numReadSamples - numSamplesToEnd) * sizeof(int16_t)); - if (_randomAccessMode) { - memset(_buffer, 0, (numReadSamples - numSamplesToEnd) * sizeof(int16_t)); // clear it - } } else { - // read the data memcpy(data, _nextOutput, numReadSamples * sizeof(int16_t)); - if (_randomAccessMode) { - memset(_nextOutput, 0, numReadSamples * sizeof(int16_t)); // clear it - } } - // push the position of _nextOutput by the number of samples read - _nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, numReadSamples); + shiftReadPosition(numReadSamples); return numReadSamples * sizeof(int16_t); } -int AudioRingBuffer::writeSamples(const int16_t* source, int maxSamples) { - return writeData((const char*)source, maxSamples * sizeof(int16_t)) / sizeof(int16_t); -} - int AudioRingBuffer::writeData(const char* data, int maxSize) { - // make sure we have enough bytes left for this to be the right amount of audio - // otherwise we should not copy that data, and leave the buffer pointers where they are - int samplesToCopy = std::min((int)(maxSize / sizeof(int16_t)), _sampleCapacity); - + // only copy up to the number of samples we have capacity for + int maxSamples = maxSize / sizeof(int16_t); + int numWriteSamples = std::min(maxSamples, _sampleCapacity); int samplesRoomFor = _sampleCapacity - samplesAvailable(); - if (samplesToCopy > samplesRoomFor) { - // there's not enough room for this write. erase old data to make room for this new data - int samplesToDelete = samplesToCopy - samplesRoomFor; + + if (numWriteSamples > samplesRoomFor) { + // there's not enough room for this write. erase old data to make room for this new data + int samplesToDelete = numWriteSamples - samplesRoomFor; _nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, samplesToDelete); _overflowCount++; qCDebug(audio) << qPrintable(RING_BUFFER_OVERFLOW_DEBUG); } - if (_endOfLastWrite + samplesToCopy <= _buffer + _bufferLength) { - memcpy(_endOfLastWrite, data, samplesToCopy * sizeof(int16_t)); - } else { + if (_endOfLastWrite + numWriteSamples > _buffer + _bufferLength) { + // we're going to need to do two writes to set this data, it wraps around the edge int numSamplesToEnd = (_buffer + _bufferLength) - _endOfLastWrite; + + // write to the end of the buffer memcpy(_endOfLastWrite, data, numSamplesToEnd * sizeof(int16_t)); - memcpy(_buffer, data + (numSamplesToEnd * sizeof(int16_t)), (samplesToCopy - numSamplesToEnd) * sizeof(int16_t)); + + // write the rest to the beginning of the buffer + memcpy(_buffer, data + (numSamplesToEnd * sizeof(int16_t)), (numWriteSamples - numSamplesToEnd) * sizeof(int16_t)); + } else { + memcpy(_endOfLastWrite, data, numWriteSamples * sizeof(int16_t)); } - _endOfLastWrite = shiftedPositionAccomodatingWrap(_endOfLastWrite, samplesToCopy); + _endOfLastWrite = shiftedPositionAccomodatingWrap(_endOfLastWrite, numWriteSamples); - return samplesToCopy * sizeof(int16_t); -} - -int16_t& AudioRingBuffer::operator[](const int index) { - return *shiftedPositionAccomodatingWrap(_nextOutput, index); -} - -const int16_t& AudioRingBuffer::operator[] (const int index) const { - return *shiftedPositionAccomodatingWrap(_nextOutput, index); -} - -void AudioRingBuffer::shiftReadPosition(unsigned int numSamples) { - _nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, numSamples); + return numWriteSamples * sizeof(int16_t); } int AudioRingBuffer::samplesAvailable() const { @@ -176,35 +150,31 @@ int AudioRingBuffer::samplesAvailable() const { } int AudioRingBuffer::addSilentSamples(int silentSamples) { - + // NOTE: This implementation is nearly identical to writeData save for s/memcpy/memset, refer to comments there + int numWriteSamples = std::min(silentSamples, _sampleCapacity); int samplesRoomFor = _sampleCapacity - samplesAvailable(); - if (silentSamples > samplesRoomFor) { - // there's not enough room for this write. write as many silent samples as we have room for - silentSamples = samplesRoomFor; - static const QString DROPPED_SILENT_DEBUG { - "AudioRingBuffer::addSilentSamples dropping silent samples to prevent overflow." - }; - static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex(DROPPED_SILENT_DEBUG); + if (numWriteSamples > samplesRoomFor) { + numWriteSamples = samplesRoomFor; + qCDebug(audio) << qPrintable(DROPPED_SILENT_DEBUG); } - // memset zeroes into the buffer, accomodate a wrap around the end - // push the _endOfLastWrite to the correct spot - if (_endOfLastWrite + silentSamples <= _buffer + _bufferLength) { - memset(_endOfLastWrite, 0, silentSamples * sizeof(int16_t)); - } else { + if (_endOfLastWrite + numWriteSamples > _buffer + _bufferLength) { int numSamplesToEnd = (_buffer + _bufferLength) - _endOfLastWrite; memset(_endOfLastWrite, 0, numSamplesToEnd * sizeof(int16_t)); - memset(_buffer, 0, (silentSamples - numSamplesToEnd) * sizeof(int16_t)); + memset(_buffer, 0, (numWriteSamples - numSamplesToEnd) * sizeof(int16_t)); + } else { + memset(_endOfLastWrite, 0, numWriteSamples * sizeof(int16_t)); } - _endOfLastWrite = shiftedPositionAccomodatingWrap(_endOfLastWrite, silentSamples); - return silentSamples; + _endOfLastWrite = shiftedPositionAccomodatingWrap(_endOfLastWrite, numWriteSamples); + + return numWriteSamples; } int16_t* AudioRingBuffer::shiftedPositionAccomodatingWrap(int16_t* position, int numSamplesShift) const { - + // NOTE: It is possible to shift out-of-bounds if (|numSamplesShift| > 2 * _bufferLength), but this should not occur if (numSamplesShift > 0 && position + numSamplesShift >= _buffer + _bufferLength) { // this shift will wrap the position around to the beginning of the ring return position + numSamplesShift - _bufferLength; @@ -217,13 +187,15 @@ int16_t* AudioRingBuffer::shiftedPositionAccomodatingWrap(int16_t* position, int } float AudioRingBuffer::getFrameLoudness(const int16_t* frameStart) const { + // FIXME: This is a bad measure of loudness - normal estimation uses sqrt(sum(x*x)) float loudness = 0.0f; const int16_t* sampleAt = frameStart; - const int16_t* _bufferLastAt = _buffer + _bufferLength - 1; + const int16_t* bufferLastAt = _buffer + _bufferLength - 1; for (int i = 0; i < _numFrameSamples; ++i) { loudness += (float) std::abs(*sampleAt); - sampleAt = sampleAt == _bufferLastAt ? _buffer : sampleAt + 1; + // wrap if necessary + sampleAt = sampleAt == bufferLastAt ? _buffer : sampleAt + 1; } loudness /= _numFrameSamples; loudness /= AudioConstants::MAX_SAMPLE_VALUE; @@ -238,10 +210,6 @@ float AudioRingBuffer::getFrameLoudness(ConstIterator frameStart) const { return getFrameLoudness(&(*frameStart)); } -float AudioRingBuffer::getNextOutputFrameLoudness() const { - return getFrameLoudness(_nextOutput); -} - int AudioRingBuffer::writeSamples(ConstIterator source, int maxSamples) { int samplesToCopy = std::min(maxSamples, _sampleCapacity); int samplesRoomFor = _sampleCapacity - samplesAvailable(); diff --git a/libraries/audio/src/AudioRingBuffer.h b/libraries/audio/src/AudioRingBuffer.h index 2b25b1044b..7ccb32ce10 100644 --- a/libraries/audio/src/AudioRingBuffer.h +++ b/libraries/audio/src/AudioRingBuffer.h @@ -23,73 +23,69 @@ const int DEFAULT_RING_BUFFER_FRAME_CAPACITY = 10; class AudioRingBuffer { public: - AudioRingBuffer(int numFrameSamples, bool randomAccessMode = false, int numFramesCapacity = DEFAULT_RING_BUFFER_FRAME_CAPACITY); + AudioRingBuffer(int numFrameSamples, int numFramesCapacity = DEFAULT_RING_BUFFER_FRAME_CAPACITY); ~AudioRingBuffer(); - void reset(); - void resizeForFrameSize(int numFrameSamples); + // disallow copying + AudioRingBuffer(const AudioRingBuffer&) = delete; + AudioRingBuffer(AudioRingBuffer&&) = delete; + AudioRingBuffer& operator=(const AudioRingBuffer&) = delete; + /// Invalidate any data in the buffer void clear(); - int getSampleCapacity() const { return _sampleCapacity; } - int getFrameCapacity() const { return _frameCapacity; } + /// Clear and reset the overflow count + void reset(); + /// Resize frame size (causes a reset()) + // FIXME: discards any data in the buffer + void resizeForFrameSize(int numFrameSamples); + + /// Read up to maxSamples into destination (will only read up to samplesAvailable()) + /// Returns number of read samples int readSamples(int16_t* destination, int maxSamples); + + /// Write up to maxSamples from source (will only write up to sample capacity) + /// Returns number of written samples int writeSamples(const int16_t* source, int maxSamples); - int readData(char* data, int maxSize); - int writeData(const char* data, int maxSize); + /// Write up to maxSamples silent samples (will only write until other data exists in the buffer) + /// This method will not overwrite existing data in the buffer, instead dropping silent samples that would overflow + /// Returns number of written silent samples + int addSilentSamples(int maxSamples); - int16_t& operator[](const int index); - const int16_t& operator[] (const int index) const; + /// Read up to maxSize into destination + /// Returns number of read bytes + int readData(char* destination, int maxSize); - void shiftReadPosition(unsigned int numSamples); + /// Write up to maxSize from source + /// Returns number of written bytes + int writeData(const char* source, int maxSize); - float getNextOutputFrameLoudness() const; + /// Returns a reference to the index-th sample offset from the current read sample + int16_t& operator[](const int index) { return *shiftedPositionAccomodatingWrap(_nextOutput, index); } + const int16_t& operator[] (const int index) const { return *shiftedPositionAccomodatingWrap(_nextOutput, index); } + + /// Essentially discards the next numSamples from the ring buffer + /// NOTE: This is not checked - it is possible to shift past written data + /// Use samplesAvailable() to see the distance a valid shift can go + void shiftReadPosition(unsigned int numSamples) { _nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, numSamples); } int samplesAvailable() const; int framesAvailable() const { return (_numFrameSamples == 0) ? 0 : samplesAvailable() / _numFrameSamples; } + float getNextOutputFrameLoudness() const { return getFrameLoudness(_nextOutput); } + int getNumFrameSamples() const { return _numFrameSamples; } + int getFrameCapacity() const { return _frameCapacity; } + int getSampleCapacity() const { return _sampleCapacity; } + /// Return times the ring buffer has overwritten old data + int getOverflowCount() const { return _overflowCount; } - int getOverflowCount() const { return _overflowCount; } /// how many times has the ring buffer has overwritten old data - - int addSilentSamples(int samples); - -private: - float getFrameLoudness(const int16_t* frameStart) const; - -protected: - // disallow copying of AudioRingBuffer objects - AudioRingBuffer(const AudioRingBuffer&); - AudioRingBuffer& operator= (const AudioRingBuffer&); - - int16_t* shiftedPositionAccomodatingWrap(int16_t* position, int numSamplesShift) const; - - int _frameCapacity; - int _sampleCapacity; - int _bufferLength; // actual length of _buffer: will be one frame larger than _sampleCapacity - int _numFrameSamples; - int16_t* _nextOutput; - int16_t* _endOfLastWrite; - int16_t* _buffer; - bool _randomAccessMode; /// will this ringbuffer be used for random access? if so, do some special processing - - int _overflowCount; /// how many times has the ring buffer has overwritten old data - -public: - class ConstIterator { //public std::iterator < std::forward_iterator_tag, int16_t > { + class ConstIterator { public: - ConstIterator() - : _bufferLength(0), - _bufferFirst(NULL), - _bufferLast(NULL), - _at(NULL) {} - ConstIterator(int16_t* bufferFirst, int capacity, int16_t* at) - : _bufferLength(capacity), - _bufferFirst(bufferFirst), - _bufferLast(bufferFirst + capacity - 1), - _at(at) {} + ConstIterator(); + ConstIterator(int16_t* bufferFirst, int capacity, int16_t* at); ConstIterator(const ConstIterator& rhs) = default; bool isNull() const { return _at == NULL; } @@ -98,95 +94,143 @@ public: bool operator!=(const ConstIterator& rhs) { return _at != rhs._at; } const int16_t& operator*() { return *_at; } - ConstIterator& operator=(const ConstIterator& rhs) { - _bufferLength = rhs._bufferLength; - _bufferFirst = rhs._bufferFirst; - _bufferLast = rhs._bufferLast; - _at = rhs._at; - return *this; - } + ConstIterator& operator=(const ConstIterator& rhs); + ConstIterator& operator++(); + ConstIterator operator++(int); + ConstIterator& operator--(); + ConstIterator operator--(int); + const int16_t& operator[] (int i); + ConstIterator operator+(int i); + ConstIterator operator-(int i); - ConstIterator& operator++() { - _at = (_at == _bufferLast) ? _bufferFirst : _at + 1; - return *this; - } - - ConstIterator operator++(int) { - ConstIterator tmp(*this); - ++(*this); - return tmp; - } - - ConstIterator& operator--() { - _at = (_at == _bufferFirst) ? _bufferLast : _at - 1; - return *this; - } - - ConstIterator operator--(int) { - ConstIterator tmp(*this); - --(*this); - return tmp; - } - - const int16_t& operator[] (int i) { - return *atShiftedBy(i); - } - - ConstIterator operator+(int i) { - return ConstIterator(_bufferFirst, _bufferLength, atShiftedBy(i)); - } - - ConstIterator operator-(int i) { - return ConstIterator(_bufferFirst, _bufferLength, atShiftedBy(-i)); - } - - void readSamples(int16_t* dest, int numSamples) { - auto samplesToEnd = _bufferLast - _at + 1; - - if (samplesToEnd >= numSamples) { - memcpy(dest, _at, numSamples * sizeof(int16_t)); - _at += numSamples; - } else { - auto samplesFromStart = numSamples - samplesToEnd; - memcpy(dest, _at, samplesToEnd * sizeof(int16_t)); - memcpy(dest + samplesToEnd, _bufferFirst, samplesFromStart * sizeof(int16_t)); - - _at = _bufferFirst + samplesFromStart; - } - } - - void readSamplesWithFade(int16_t* dest, int numSamples, float fade) { - int16_t* at = _at; - for (int i = 0; i < numSamples; i++) { - *dest = (float)*at * fade; - ++dest; - at = (at == _bufferLast) ? _bufferFirst : at + 1; - } - } + void readSamples(int16_t* dest, int numSamples); + void readSamplesWithFade(int16_t* dest, int numSamples, float fade); private: - int16_t* atShiftedBy(int i) { - i = (_at - _bufferFirst + i) % _bufferLength; - if (i < 0) { - i += _bufferLength; - } - return _bufferFirst + i; - } + int16_t* atShiftedBy(int i); - private: int _bufferLength; int16_t* _bufferFirst; int16_t* _bufferLast; int16_t* _at; }; - ConstIterator nextOutput() const { return ConstIterator(_buffer, _bufferLength, _nextOutput); } - ConstIterator lastFrameWritten() const { return ConstIterator(_buffer, _bufferLength, _endOfLastWrite) - _numFrameSamples; } - - float getFrameLoudness(ConstIterator frameStart) const; + ConstIterator nextOutput() const; + ConstIterator lastFrameWritten() const; int writeSamples(ConstIterator source, int maxSamples); int writeSamplesWithFade(ConstIterator source, int maxSamples, float fade); + + float getFrameLoudness(ConstIterator frameStart) const; + +protected: + int16_t* shiftedPositionAccomodatingWrap(int16_t* position, int numSamplesShift) const; + float getFrameLoudness(const int16_t* frameStart) const; + + int _numFrameSamples; + int _frameCapacity; + int _sampleCapacity; + int _bufferLength; // actual _buffer length (_sampleCapacity + 1) + int _overflowCount{ 0 }; // times the ring buffer has overwritten data + + int16_t* _nextOutput{ nullptr }; + int16_t* _endOfLastWrite{ nullptr }; + int16_t* _buffer{ nullptr }; }; +// inline the iterator: +inline AudioRingBuffer::ConstIterator::ConstIterator() : + _bufferLength(0), + _bufferFirst(NULL), + _bufferLast(NULL), + _at(NULL) {} + +inline AudioRingBuffer::ConstIterator::ConstIterator(int16_t* bufferFirst, int capacity, int16_t* at) : + _bufferLength(capacity), + _bufferFirst(bufferFirst), + _bufferLast(bufferFirst + capacity - 1), + _at(at) {} + +inline AudioRingBuffer::ConstIterator& AudioRingBuffer::ConstIterator::operator=(const ConstIterator& rhs) { + _bufferLength = rhs._bufferLength; + _bufferFirst = rhs._bufferFirst; + _bufferLast = rhs._bufferLast; + _at = rhs._at; + return *this; +} + +inline AudioRingBuffer::ConstIterator& AudioRingBuffer::ConstIterator::operator++() { + _at = (_at == _bufferLast) ? _bufferFirst : _at + 1; + return *this; +} + +inline AudioRingBuffer::ConstIterator AudioRingBuffer::ConstIterator::operator++(int) { + ConstIterator tmp(*this); + ++(*this); + return tmp; +} + +inline AudioRingBuffer::ConstIterator& AudioRingBuffer::ConstIterator::operator--() { + _at = (_at == _bufferFirst) ? _bufferLast : _at - 1; + return *this; +} + +inline AudioRingBuffer::ConstIterator AudioRingBuffer::ConstIterator::operator--(int) { + ConstIterator tmp(*this); + --(*this); + return tmp; +} + +inline const int16_t& AudioRingBuffer::ConstIterator::operator[] (int i) { + return *atShiftedBy(i); +} + +inline AudioRingBuffer::ConstIterator AudioRingBuffer::ConstIterator::operator+(int i) { + return ConstIterator(_bufferFirst, _bufferLength, atShiftedBy(i)); +} + +inline AudioRingBuffer::ConstIterator AudioRingBuffer::ConstIterator::operator-(int i) { + return ConstIterator(_bufferFirst, _bufferLength, atShiftedBy(-i)); +} + +inline int16_t* AudioRingBuffer::ConstIterator::atShiftedBy(int i) { + i = (_at - _bufferFirst + i) % _bufferLength; + if (i < 0) { + i += _bufferLength; + } + return _bufferFirst + i; +} + +inline void AudioRingBuffer::ConstIterator::readSamples(int16_t* dest, int numSamples) { + auto samplesToEnd = _bufferLast - _at + 1; + + if (samplesToEnd >= numSamples) { + memcpy(dest, _at, numSamples * sizeof(int16_t)); + _at += numSamples; + } else { + auto samplesFromStart = numSamples - samplesToEnd; + memcpy(dest, _at, samplesToEnd * sizeof(int16_t)); + memcpy(dest + samplesToEnd, _bufferFirst, samplesFromStart * sizeof(int16_t)); + + _at = _bufferFirst + samplesFromStart; + } +} + +inline void AudioRingBuffer::ConstIterator::readSamplesWithFade(int16_t* dest, int numSamples, float fade) { + int16_t* at = _at; + for (int i = 0; i < numSamples; i++) { + *dest = (float)*at * fade; + ++dest; + at = (at == _bufferLast) ? _bufferFirst : at + 1; + } +} + +inline AudioRingBuffer::ConstIterator AudioRingBuffer::nextOutput() const { + return ConstIterator(_buffer, _bufferLength, _nextOutput); +} + +inline AudioRingBuffer::ConstIterator AudioRingBuffer::lastFrameWritten() const { + return ConstIterator(_buffer, _bufferLength, _endOfLastWrite) - _numFrameSamples; +} + #endif // hifi_AudioRingBuffer_h diff --git a/libraries/audio/src/InboundAudioStream.cpp b/libraries/audio/src/InboundAudioStream.cpp index b908f57439..6b79879bb7 100644 --- a/libraries/audio/src/InboundAudioStream.cpp +++ b/libraries/audio/src/InboundAudioStream.cpp @@ -20,7 +20,7 @@ const int STARVE_HISTORY_CAPACITY = 50; InboundAudioStream::InboundAudioStream(int numFrameSamples, int numFramesCapacity, const Settings& settings) : - _ringBuffer(numFrameSamples, false, numFramesCapacity), + _ringBuffer(numFrameSamples, numFramesCapacity), _lastPopSucceeded(false), _lastPopOutput(), _dynamicJitterBuffers(settings._dynamicJitterBuffers), diff --git a/libraries/auto-updater/src/AutoUpdater.cpp b/libraries/auto-updater/src/AutoUpdater.cpp index ea4e43ff41..43563b1d71 100644 --- a/libraries/auto-updater/src/AutoUpdater.cpp +++ b/libraries/auto-updater/src/AutoUpdater.cpp @@ -33,6 +33,7 @@ void AutoUpdater::checkForUpdate() { void AutoUpdater::getLatestVersionData() { QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest latestVersionRequest(BUILDS_XML_URL); + latestVersionRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); latestVersionRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); QNetworkReply* reply = networkAccessManager.get(latestVersionRequest); connect(reply, &QNetworkReply::finished, this, &AutoUpdater::parseLatestVersionData); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 9088ee0577..413180738a 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -374,6 +374,16 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { } } + // faux joints + Transform controllerLeftHandTransform = Transform(getControllerLeftHandMatrix()); + destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, controllerLeftHandTransform.getRotation()); + destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, controllerLeftHandTransform.getTranslation(), + TRANSLATION_COMPRESSION_RADIX); + Transform controllerRightHandTransform = Transform(getControllerRightHandMatrix()); + destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, controllerRightHandTransform.getRotation()); + destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, controllerRightHandTransform.getTranslation(), + TRANSLATION_COMPRESSION_RADIX); + #ifdef WANT_DEBUG if (sendAll) { qDebug() << "AvatarData::toByteArray" << cullSmallChanges << sendAll @@ -429,6 +439,20 @@ bool AvatarData::shouldLogError(const quint64& now) { return false; } + +const unsigned char* unpackFauxJoint(const unsigned char* sourceBuffer, ThreadSafeValueCache& matrixCache) { + glm::quat orientation; + glm::vec3 position; + Transform transform; + sourceBuffer += unpackOrientationQuatFromSixBytes(sourceBuffer, orientation); + sourceBuffer += unpackFloatVec3FromSignedTwoByteFixed(sourceBuffer, position, TRANSLATION_COMPRESSION_RADIX); + transform.setTranslation(position); + transform.setRotation(orientation); + matrixCache.set(transform.getMatrix()); + return sourceBuffer; +} + + #define PACKET_READ_CHECK(ITEM_NAME, SIZE_TO_READ) \ if ((endPosition - sourceBuffer) < (int)SIZE_TO_READ) { \ if (shouldLogError(now)) { \ @@ -655,6 +679,10 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { } #endif + // faux joints + sourceBuffer = unpackFauxJoint(sourceBuffer, _controllerLeftHandMatrixCache); + sourceBuffer = unpackFauxJoint(sourceBuffer, _controllerRightHandMatrixCache); + int numBytesRead = sourceBuffer - startPosition; _averageBytesReceived.updateAverage(numBytesRead); return numBytesRead; @@ -915,7 +943,24 @@ void AvatarData::clearJointsData() { } } +int AvatarData::getFauxJointIndex(const QString& name) const { + if (name == "_SENSOR_TO_WORLD_MATRIX") { + return SENSOR_TO_WORLD_MATRIX_INDEX; + } + if (name == "_CONTROLLER_LEFTHAND") { + return CONTROLLER_LEFTHAND_INDEX; + } + if (name == "_CONTROLLER_RIGHTHAND") { + return CONTROLLER_RIGHTHAND_INDEX; + } + return -1; +} + int AvatarData::getJointIndex(const QString& name) const { + int result = getFauxJointIndex(name); + if (result != -1) { + return result; + } QReadLocker readLock(&_jointDataLock); return _jointIndices.value(name) - 1; } @@ -1161,6 +1206,7 @@ void AvatarData::updateJointMappings() { if (_skeletonModelURL.fileName().toLower().endsWith(".fst")) { QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest networkRequest = QNetworkRequest(_skeletonModelURL); + networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); QNetworkReply* networkReply = networkAccessManager.get(networkRequest); connect(networkReply, &QNetworkReply::finished, this, &AvatarData::setJointMappingsFromNetworkReply); @@ -1743,6 +1789,17 @@ glm::mat4 AvatarData::getSensorToWorldMatrix() const { return _sensorToWorldMatrixCache.get(); } +// thread-safe +glm::mat4 AvatarData::getControllerLeftHandMatrix() const { + return _controllerLeftHandMatrixCache.get(); +} + +// thread-safe +glm::mat4 AvatarData::getControllerRightHandMatrix() const { + return _controllerRightHandMatrixCache.get(); +} + + QScriptValue RayToAvatarIntersectionResultToScriptValue(QScriptEngine* engine, const RayToAvatarIntersectionResult& value) { QScriptValue obj = engine->newObject(); obj.setProperty("intersects", value.intersects); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 572657e921..715c24b8ee 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -173,6 +173,8 @@ class AvatarData : public QObject, public SpatiallyNestable { Q_PROPERTY(QUuid sessionUUID READ getSessionUUID) Q_PROPERTY(glm::mat4 sensorToWorldMatrix READ getSensorToWorldMatrix) + Q_PROPERTY(glm::mat4 controllerLeftHandMatrix READ getControllerLeftHandMatrix) + Q_PROPERTY(glm::mat4 controllerRightHandMatrix READ getControllerRightHandMatrix) public: @@ -356,6 +358,8 @@ public: // thread safe Q_INVOKABLE glm::mat4 getSensorToWorldMatrix() const; + Q_INVOKABLE glm::mat4 getControllerLeftHandMatrix() const; + Q_INVOKABLE glm::mat4 getControllerRightHandMatrix() const; public slots: void sendAvatarDataPacket(); @@ -369,6 +373,8 @@ public slots: virtual bool setAbsoluteJointRotationInObjectFrame(int index, const glm::quat& rotation) override { return false; } virtual bool setAbsoluteJointTranslationInObjectFrame(int index, const glm::vec3& translation) override { return false; } + float getTargetScale() { return _targetScale; } + protected: glm::vec3 _handPosition; @@ -433,6 +439,10 @@ protected: // used to transform any sensor into world space, including the _hmdSensorMat, or hand controllers. ThreadSafeValueCache _sensorToWorldMatrixCache { glm::mat4() }; + ThreadSafeValueCache _controllerLeftHandMatrixCache { glm::mat4() }; + ThreadSafeValueCache _controllerRightHandMatrixCache { glm::mat4() }; + + int getFauxJointIndex(const QString& name) const; private: friend void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar); @@ -519,5 +529,10 @@ Q_DECLARE_METATYPE(RayToAvatarIntersectionResult) QScriptValue RayToAvatarIntersectionResultToScriptValue(QScriptEngine* engine, const RayToAvatarIntersectionResult& results); void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, RayToAvatarIntersectionResult& results); +// faux joint indexes (-1 means invalid) +const int SENSOR_TO_WORLD_MATRIX_INDEX = 65534; // -2 +const int CONTROLLER_RIGHTHAND_INDEX = 65533; // -3 +const int CONTROLLER_LEFTHAND_INDEX = 65532; // -4 + #endif // hifi_AvatarData_h diff --git a/libraries/controllers/CMakeLists.txt b/libraries/controllers/CMakeLists.txt index 5beffce461..384218691a 100644 --- a/libraries/controllers/CMakeLists.txt +++ b/libraries/controllers/CMakeLists.txt @@ -1,7 +1,7 @@ set(TARGET_NAME controllers) # set a default root dir for each of our optional externals if it was not passed -setup_hifi_library(Script) +setup_hifi_library(Script Qml) # use setup_hifi_library macro to setup our project and link appropriate Qt modules link_hifi_libraries(shared) @@ -11,4 +11,3 @@ GroupSources("src/controllers") add_dependency_external_projects(glm) find_package(GLM REQUIRED) target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS}) - diff --git a/libraries/controllers/src/controllers/UserInputMapper.cpp b/libraries/controllers/src/controllers/UserInputMapper.cpp index 7490a44c11..ff44d5d13d 100755 --- a/libraries/controllers/src/controllers/UserInputMapper.cpp +++ b/libraries/controllers/src/controllers/UserInputMapper.cpp @@ -325,7 +325,6 @@ QString UserInputMapper::getActionName(Action action) const { return QString(); } - QVector UserInputMapper::getActionNames() const { Locker locker(_lock); QVector result; @@ -335,6 +334,12 @@ QVector UserInputMapper::getActionNames() const { return result; } +Pose UserInputMapper::getPoseState(Action action) const { + assert(QThread::currentThread() == thread()); + return _poseStates[toInt(action)]; +} + + bool UserInputMapper::triggerHapticPulse(float strength, float duration, controller::Hand hand) { Locker locker(_lock); bool toReturn = false; diff --git a/libraries/controllers/src/controllers/UserInputMapper.h b/libraries/controllers/src/controllers/UserInputMapper.h index 874e5054ea..baa05f2f9f 100644 --- a/libraries/controllers/src/controllers/UserInputMapper.h +++ b/libraries/controllers/src/controllers/UserInputMapper.h @@ -81,7 +81,7 @@ namespace controller { QVector getAllActions() const; QString getActionName(Action action) const; float getActionState(Action action) const { return _actionStates[toInt(action)]; } - Pose getPoseState(Action action) const { return _poseStates[toInt(action)]; } + Pose getPoseState(Action action) const; int findAction(const QString& actionName) const; QVector getActionNames() const; Input inputFromAction(Action action) const { return getActionInputs()[toInt(action)].first; } diff --git a/libraries/display-plugins/CMakeLists.txt b/libraries/display-plugins/CMakeLists.txt index 315e7510a5..5621a9c7b0 100644 --- a/libraries/display-plugins/CMakeLists.txt +++ b/libraries/display-plugins/CMakeLists.txt @@ -7,4 +7,6 @@ target_opengl() GroupSources("src/display-plugins") -target_oglplus() +if (NOT ANDROID) + target_oglplus() +endif () diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index b304b3802e..4c683a27f8 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -108,7 +109,7 @@ public: } } - void setContext(QGLContext * context) { + void setContext(gl::Context* context) { // Move the OpenGL context to the present thread // Extra code because of the widget 'wrapper' context _context = context; @@ -126,7 +127,6 @@ public: OpenGLDisplayPlugin* currentPlugin{ nullptr }; Q_ASSERT(_context); _context->makeCurrent(); - Q_ASSERT(isCurrentContext(_context->contextHandle())); while (!_shutdown) { if (_pendingMainThreadOperation) { PROFILE_RANGE("MainThreadOp") @@ -250,7 +250,7 @@ private: bool _finishedMainThreadOperation { false }; QThread* _mainThread { nullptr }; std::queue _newPluginQueue; - QGLContext* _context { nullptr }; + gl::Context* _context { nullptr }; }; bool OpenGLDisplayPlugin::activate() { @@ -475,32 +475,28 @@ bool OpenGLDisplayPlugin::eventFilter(QObject* receiver, QEvent* event) { } void OpenGLDisplayPlugin::submitFrame(const gpu::FramePointer& newFrame) { - if (_lockCurrentTexture) { - return; - } - withNonPresentThreadLock([&] { _newFrameQueue.push(newFrame); }); } void OpenGLDisplayPlugin::updateFrameData() { + if (_lockCurrentTexture) { + return; + } withPresentThreadLock([&] { - gpu::FramePointer oldFrame = _currentFrame; - uint32_t skippedCount = 0; if (!_newFrameQueue.empty()) { // We're changing frames, so we can cleanup any GL resources that might have been used by the old frame _gpuContext->recycle(); } + if (_newFrameQueue.size() > 1) { + _droppedFrameRate.increment(_newFrameQueue.size() - 1); + } while (!_newFrameQueue.empty()) { _currentFrame = _newFrameQueue.front(); _newFrameQueue.pop(); _gpuContext->consumeFrameUpdates(_currentFrame); - if (_currentFrame && oldFrame) { - skippedCount += (_currentFrame->frameIndex - oldFrame->frameIndex) - 1; - } } - _droppedFrameRate.increment(skippedCount); }); } @@ -598,6 +594,7 @@ void OpenGLDisplayPlugin::internalPresent() { batch.draw(gpu::TRIANGLE_STRIP, 4); }); swapBuffers(); + _presentRate.increment(); } void OpenGLDisplayPlugin::present() { @@ -612,6 +609,13 @@ void OpenGLDisplayPlugin::present() { if (_currentFrame) { { + withPresentThreadLock([&] { + _renderRate.increment(); + if (_currentFrame != _lastFrame) { + _newFrameRate.increment(); + } + _lastFrame = _currentFrame; + }); // Execute the frame rendering commands PROFILE_RANGE_EX("execute", 0xff00ff00, (uint64_t)presentCount()) _gpuContext->executeFrame(_currentFrame); @@ -628,7 +632,6 @@ void OpenGLDisplayPlugin::present() { PROFILE_RANGE_EX("internalPresent", 0xff00ffff, (uint64_t)presentCount()) internalPresent(); } - _presentRate.increment(); } } @@ -637,20 +640,21 @@ float OpenGLDisplayPlugin::newFramePresentRate() const { } float OpenGLDisplayPlugin::droppedFrameRate() const { - float result; - withNonPresentThreadLock([&] { - result = _droppedFrameRate.rate(); - }); - return result; + return _droppedFrameRate.rate(); } float OpenGLDisplayPlugin::presentRate() const { return _presentRate.rate(); } +float OpenGLDisplayPlugin::renderRate() const { + return _renderRate.rate(); +} + + void OpenGLDisplayPlugin::swapBuffers() { - static auto widget = _container->getPrimaryWidget(); - widget->swapBuffers(); + static auto context = _container->getPrimaryWidget()->context(); + context->swapBuffers(); } void OpenGLDisplayPlugin::withMainThreadContext(std::function f) const { diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index 51b33c9bcd..ef15861843 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -62,11 +62,15 @@ public: float droppedFrameRate() const override; + float renderRate() const override; + bool beginFrameRender(uint32_t frameIndex) override; virtual bool wantVsync() const { return true; } void setVsyncEnabled(bool vsyncEnabled) { _vsyncEnabled = vsyncEnabled; } bool isVsyncEnabled() const { return _vsyncEnabled; } + // Three threads, one for rendering, one for texture transfers, one reserved for the GL driver + int getRequiredThreadCount() const override { return 3; } protected: friend class PresentThread; @@ -109,8 +113,10 @@ protected: RateCounter<> _droppedFrameRate; RateCounter<> _newFrameRate; RateCounter<> _presentRate; + RateCounter<> _renderRate; gpu::FramePointer _currentFrame; + gpu::FramePointer _lastFrame; gpu::FramebufferPointer _compositeFramebuffer; gpu::PipelinePointer _overlayPipeline; gpu::PipelinePointer _simplePipeline; diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 6904700be5..01f607b0ed 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -527,9 +527,11 @@ void HmdDisplayPlugin::compositeExtra() { if (_presentHandPoses[index] == IDENTITY_MATRIX) { return; } - const auto& points = _presentHandLaserPoints[index]; - const auto& lasers = _presentHandLasers[index]; - geometryCache->renderGlowLine(batch, points.first, points.second, lasers.color); + const auto& laser = _presentHandLasers[index]; + if (laser.valid()) { + const auto& points = _presentHandLaserPoints[index]; + geometryCache->renderGlowLine(batch, points.first, points.second, laser.color); + } }); }); } diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index ece667422c..24d22fee96 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -52,7 +52,8 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf _viewState(viewState), _scriptingServices(scriptingServices), _displayModelBounds(false), - _dontDoPrecisionPicking(false) + _dontDoPrecisionPicking(false), + _layeredZones(this) { REGISTER_ENTITY_TYPE_WITH_FACTORY(Model, RenderableModelEntityItem::factory) REGISTER_ENTITY_TYPE_WITH_FACTORY(Light, RenderableLightEntityItem::factory) @@ -135,8 +136,8 @@ void EntityTreeRenderer::clear() { _entitiesInScene.clear(); // reset the zone to the default (while we load the next scene) - _bestZone = nullptr; - applyZonePropertiesToScene(_bestZone); + _layeredZones.clear(); + applyZoneAndHasSkybox(nullptr); OctreeRenderer::clear(); } @@ -192,10 +193,10 @@ void EntityTreeRenderer::update() { // If we haven't already updated and previously attempted to load a texture, // check if the texture loaded and apply it - if (!updated && ( - (_pendingSkyboxTexture && (!_skyboxTexture || _skyboxTexture->isLoaded())) || - (_pendingAmbientTexture && (!_ambientTexture || _ambientTexture->isLoaded())))) { - applyZonePropertiesToScene(_bestZone); + if (!updated && + ((_pendingAmbientTexture && (!_ambientTexture || _ambientTexture->isLoaded())) || + (_pendingSkyboxTexture && (!_skyboxTexture || _skyboxTexture->isLoaded())))) { + applySkyboxAndHasAmbient(); } // Even if we're not moving the mouse, if we started clicking on an entity and we have @@ -210,7 +211,7 @@ void EntityTreeRenderer::update() { deleteReleasedModels(); } -bool EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(const glm::vec3& avatarPosition, QVector* entitiesContainingAvatar) { +bool EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(QVector* entitiesContainingAvatar) { bool didUpdate = false; float radius = 0.01f; // for now, assume 0.01 meter radius, because we actually check the point inside later QVector foundEntities; @@ -220,12 +221,10 @@ bool EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(const glm::vec3& _tree->withReadLock([&] { // FIXME - if EntityTree had a findEntitiesContainingPoint() this could theoretically be a little faster - std::static_pointer_cast(_tree)->findEntities(avatarPosition, radius, foundEntities); + std::static_pointer_cast(_tree)->findEntities(_avatarPosition, radius, foundEntities); - // Whenever you're in an intersection between zones, we will always choose the smallest zone. - auto oldBestZone = _bestZone; - _bestZone = nullptr; // NOTE: Is this what we want? - _bestZoneVolume = std::numeric_limits::max(); + LayeredZones oldLayeredZones(std::move(_layeredZones)); + _layeredZones.clear(); // create a list of entities that actually contain the avatar's position for (auto& entity : foundEntities) { @@ -239,38 +238,34 @@ bool EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(const glm::vec3& if (isZone || hasScript) { // now check to see if the point contains our entity, this can be expensive if // the entity has a collision hull - if (entity->contains(avatarPosition)) { + if (entity->contains(_avatarPosition)) { if (entitiesContainingAvatar) { *entitiesContainingAvatar << entity->getEntityItemID(); } // if this entity is a zone and visible, determine if it is the bestZone if (isZone && entity->getVisible()) { - float entityVolumeEstimate = entity->getVolumeEstimate(); - if (entityVolumeEstimate < _bestZoneVolume) { - _bestZoneVolume = entityVolumeEstimate; - _bestZone = std::dynamic_pointer_cast(entity); - } else if (entityVolumeEstimate == _bestZoneVolume) { - // in the case of the volume being equal, we will use the - // EntityItemID to deterministically pick one entity over the other - if (!_bestZone) { - _bestZoneVolume = entityVolumeEstimate; - _bestZone = std::dynamic_pointer_cast(entity); - } else if (entity->getEntityItemID() < _bestZone->getEntityItemID()) { - _bestZoneVolume = entityVolumeEstimate; - _bestZone = std::dynamic_pointer_cast(entity); - } - } + auto zone = std::dynamic_pointer_cast(entity); + _layeredZones.insert(zone); } } } } - if (_bestZone != oldBestZone) { - applyZonePropertiesToScene(_bestZone); - didUpdate = true; + // check if our layered zones have changed + if (_layeredZones.empty()) { + if (oldLayeredZones.empty()) { + return; + } + } else if (!oldLayeredZones.empty()) { + if (_layeredZones.contains(oldLayeredZones)) { + return; + } } + _layeredZones.apply(); + didUpdate = true; }); + return didUpdate; } @@ -286,13 +281,14 @@ bool EntityTreeRenderer::checkEnterLeaveEntities() { // if some amount of time has elapsed since we last checked. We check the time // elapsed because zones or entities might have been created "around us" while we've // been stationary - auto movedEnough = glm::distance(avatarPosition, _lastAvatarPosition) > ZONE_CHECK_DISTANCE; + auto movedEnough = glm::distance(avatarPosition, _avatarPosition) > ZONE_CHECK_DISTANCE; auto enoughTimeElapsed = (now - _lastZoneCheck) > ZONE_CHECK_INTERVAL; if (movedEnough || enoughTimeElapsed) { + _avatarPosition = avatarPosition; _lastZoneCheck = now; QVector entitiesContainingAvatar; - didUpdate = findBestZoneAndMaybeContainingEntities(avatarPosition, &entitiesContainingAvatar); + didUpdate = findBestZoneAndMaybeContainingEntities(&entitiesContainingAvatar); // Note: at this point we don't need to worry about the tree being locked, because we only deal with // EntityItemIDs from here. The callEntityScriptMethod() method is robust against attempting to call scripts @@ -318,7 +314,6 @@ bool EntityTreeRenderer::checkEnterLeaveEntities() { } } _currentEntitiesInside = entitiesContainingAvatar; - _lastAvatarPosition = avatarPosition; } } return didUpdate; @@ -342,24 +337,20 @@ void EntityTreeRenderer::leaveAllEntities() { void EntityTreeRenderer::forceRecheckEntities() { // make sure our "last avatar position" is something other than our current position, // so that on our next chance, we'll check for enter/leave entity events. - _lastAvatarPosition = _viewState->getAvatarPosition() + glm::vec3((float)TREE_SCALE); + _avatarPosition = _viewState->getAvatarPosition() + glm::vec3((float)TREE_SCALE); } - -void EntityTreeRenderer::applyZonePropertiesToScene(std::shared_ptr zone) { +bool EntityTreeRenderer::applyZoneAndHasSkybox(const std::shared_ptr& zone) { auto textureCache = DependencyManager::get(); auto scene = DependencyManager::get(); auto sceneStage = scene->getStage(); auto skyStage = scene->getSkyStage(); auto sceneKeyLight = sceneStage->getKeyLight(); - // Skybox and procedural skybox data - auto skybox = std::dynamic_pointer_cast(skyStage->getSkybox()); - // If there is no zone, use the default background if (!zone) { _zoneUserData = QString(); - skybox->clear(); + skyStage->getSkybox()->clear(); _pendingSkyboxTexture = false; _skyboxTexture.clear(); @@ -371,7 +362,7 @@ void EntityTreeRenderer::applyZonePropertiesToScene(std::shared_ptrsetAmbientMap(nullptr); skyStage->setBackgroundMode(model::SunSkyStage::SKY_DEFAULT); - return; + return false; } // Set the keylight @@ -394,90 +385,127 @@ void EntityTreeRenderer::applyZonePropertiesToScene(std::shared_ptrgetKeyLightProperties().getAmbientURL().isEmpty()) { + _ambientTextureURL = zone->getKeyLightProperties().getAmbientURL(); + if (_ambientTextureURL.isEmpty()) { _pendingAmbientTexture = false; _ambientTexture.clear(); } else { - _ambientTexture = textureCache->getTexture(zone->getKeyLightProperties().getAmbientURL(), NetworkTexture::CUBE_TEXTURE); _pendingAmbientTexture = true; - - if (_ambientTexture && _ambientTexture->isLoaded()) { - _pendingAmbientTexture = false; - - auto texture = _ambientTexture->getGPUTexture(); - if (texture) { - sceneKeyLight->setAmbientSphere(texture->getIrradiance()); - sceneKeyLight->setAmbientMap(texture); - isAmbientTextureSet = true; - } else { - qCDebug(entitiesrenderer) << "Failed to load ambient texture:" << zone->getKeyLightProperties().getAmbientURL(); - } - } } // Set the skybox texture + return layerZoneAndHasSkybox(zone); +} + +bool EntityTreeRenderer::layerZoneAndHasSkybox(const std::shared_ptr& zone) { + assert(zone); + + auto textureCache = DependencyManager::get(); + auto scene = DependencyManager::get(); + auto skyStage = scene->getSkyStage(); + auto skybox = skyStage->getSkybox(); + + bool hasSkybox = false; + switch (zone->getBackgroundMode()) { - case BACKGROUND_MODE_SKYBOX: { + case BACKGROUND_MODE_SKYBOX: + hasSkybox = true; + skybox->setColor(zone->getSkyboxProperties().getColorVec3()); + if (_zoneUserData != zone->getUserData()) { _zoneUserData = zone->getUserData(); - skybox->parse(_zoneUserData); + std::dynamic_pointer_cast(skybox)->parse(_zoneUserData); } - if (zone->getSkyboxProperties().getURL().isEmpty()) { - skybox->setCubemap(nullptr); + + _skyboxTextureURL = zone->getSkyboxProperties().getURL(); + if (_skyboxTextureURL.isEmpty()) { _pendingSkyboxTexture = false; _skyboxTexture.clear(); } else { - // Update the Texture of the Skybox with the one pointed by this zone - _skyboxTexture = textureCache->getTexture(zone->getSkyboxProperties().getURL(), NetworkTexture::CUBE_TEXTURE); _pendingSkyboxTexture = true; - - if (_skyboxTexture && _skyboxTexture->isLoaded()) { - _pendingSkyboxTexture = false; - - auto texture = _skyboxTexture->getGPUTexture(); - if (texture) { - skybox->setCubemap(texture); - if (!isAmbientTextureSet) { - sceneKeyLight->setAmbientSphere(texture->getIrradiance()); - sceneKeyLight->setAmbientMap(texture); - isAmbientTextureSet = true; - } - } else { - qCDebug(entitiesrenderer) << "Failed to load skybox texture:" << zone->getSkyboxProperties().getURL(); - skybox->setCubemap(nullptr); - } - } else { - skybox->setCubemap(nullptr); - } } + + applySkyboxAndHasAmbient(); skyStage->setBackgroundMode(model::SunSkyStage::SKY_BOX); + break; - } case BACKGROUND_MODE_INHERIT: default: // Clear the skybox to release its textures - _zoneUserData = QString(); skybox->clear(); + _zoneUserData = QString(); - _skyboxTexture.clear(); _pendingSkyboxTexture = false; + _skyboxTexture.clear(); // Let the application background through - if (isAmbientTextureSet) { + if (applySkyboxAndHasAmbient()) { skyStage->setBackgroundMode(model::SunSkyStage::SKY_DEFAULT_TEXTURE); } else { skyStage->setBackgroundMode(model::SunSkyStage::SKY_DEFAULT_AMBIENT_TEXTURE); } + break; } - if (!isAmbientTextureSet) { + return hasSkybox; +} + +bool EntityTreeRenderer::applySkyboxAndHasAmbient() { + auto textureCache = DependencyManager::get(); + auto scene = DependencyManager::get(); + auto sceneStage = scene->getStage(); + auto skyStage = scene->getSkyStage(); + auto sceneKeyLight = sceneStage->getKeyLight(); + auto skybox = skyStage->getSkybox(); + + bool isAmbientSet = false; + if (_pendingAmbientTexture && !_ambientTexture) { + _ambientTexture = textureCache->getTexture(_ambientTextureURL, NetworkTexture::CUBE_TEXTURE); + } + if (_ambientTexture && _ambientTexture->isLoaded()) { + _pendingAmbientTexture = false; + + auto texture = _ambientTexture->getGPUTexture(); + if (texture) { + isAmbientSet = true; + sceneKeyLight->setAmbientSphere(texture->getIrradiance()); + sceneKeyLight->setAmbientMap(texture); + } else { + qCDebug(entitiesrenderer) << "Failed to load ambient texture:" << _ambientTexture->getURL(); + } + } + + if (_pendingSkyboxTexture && !_skyboxTexture) { + _skyboxTexture = textureCache->getTexture(_skyboxTextureURL, NetworkTexture::CUBE_TEXTURE); + } + if (_skyboxTexture && _skyboxTexture->isLoaded()) { + _pendingSkyboxTexture = false; + + auto texture = _skyboxTexture->getGPUTexture(); + if (texture) { + skybox->setCubemap(texture); + if (!isAmbientSet) { + sceneKeyLight->setAmbientSphere(texture->getIrradiance()); + sceneKeyLight->setAmbientMap(texture); + isAmbientSet = true; + } + } else { + qCDebug(entitiesrenderer) << "Failed to load skybox texture:" << _skyboxTexture->getURL(); + skybox->setCubemap(nullptr); + } + } else { + skybox->setCubemap(nullptr); + } + + if (!isAmbientSet) { sceneKeyLight->resetAmbientSphere(); sceneKeyLight->setAmbientMap(nullptr); } + + return isAmbientSet; } const FBXGeometry* EntityTreeRenderer::getGeometryForEntity(EntityItemPointer entityItem) { @@ -528,14 +556,13 @@ ModelPointer EntityTreeRenderer::allocateModel(const QString& url, float loading return model; } -ModelPointer EntityTreeRenderer::updateModel(ModelPointer model, const QString& newUrl, const QString& collisionUrl) { +ModelPointer EntityTreeRenderer::updateModel(ModelPointer model, const QString& newUrl) { // Only create and delete models on the thread that owns the EntityTreeRenderer if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "updateModel", Qt::BlockingQueuedConnection, Q_RETURN_ARG(ModelPointer, model), Q_ARG(ModelPointer, model), - Q_ARG(const QString&, newUrl), - Q_ARG(const QString&, collisionUrl)); + Q_ARG(const QString&, newUrl)); return model; } @@ -1046,21 +1073,128 @@ void EntityTreeRenderer::updateEntityRenderStatus(bool shouldRenderEntities) { } void EntityTreeRenderer::updateZone(const EntityItemID& id) { - if (!_bestZone) { - // Get in the zone! - auto zone = getTree()->findEntityByEntityItemID(id); - if (zone && zone->contains(_lastAvatarPosition)) { - _currentEntitiesInside << id; - emit enterEntity(id); - if (_entitiesScriptEngine) { - _entitiesScriptEngine->callEntityScriptMethod(id, "enterEntity"); - } - if (zone->getVisible()) { - _bestZone = std::dynamic_pointer_cast(zone); - } - } - } - if (_bestZone && _bestZone->getID() == id) { - applyZonePropertiesToScene(_bestZone); + // Get in the zone! + auto zone = std::dynamic_pointer_cast(getTree()->findEntityByEntityItemID(id)); + if (zone && zone->contains(_avatarPosition)) { + _layeredZones.update(zone); } } + +EntityTreeRenderer::LayeredZones::LayeredZones(LayeredZones&& other) { + // In a swap: + // > All iterators and references remain valid. The past-the-end iterator is invalidated. + bool isSkyboxLayerValid = (other._skyboxLayer != other.end()); + + swap(other); + _map.swap(other._map); + _skyboxLayer = other._skyboxLayer; + + if (!isSkyboxLayerValid) { + _skyboxLayer = end(); + } +} + +void EntityTreeRenderer::LayeredZones::clear() { + std::set::clear(); + _map.clear(); + _skyboxLayer = end(); +} + +std::pair EntityTreeRenderer::LayeredZones::insert(const LayeredZone& layer) { + iterator it; + bool success; + std::tie(it, success) = std::set::insert(layer); + + if (success) { + _map.emplace(it->id, it); + } + + return { it, success }; +} + +void EntityTreeRenderer::LayeredZones::apply() { + assert(_entityTreeRenderer); + + applyPartial(begin()); +} + +void EntityTreeRenderer::LayeredZones::update(std::shared_ptr zone) { + assert(_entityTreeRenderer); + bool isVisible = zone->isVisible(); + + if (empty() && isVisible) { + // there are no zones: set this one + insert(zone); + apply(); + return; + } else { + LayeredZone zoneLayer(zone); + + // should we update? only if this zone is tighter than the current skybox zone + bool shouldUpdate = false; + if (_skyboxLayer == end() || zoneLayer <= *_skyboxLayer) { + shouldUpdate = true; + } + + // find this zone's layer, if it exists + iterator layer = end(); + auto it = _map.find(zoneLayer.id); + if (it != _map.end()) { + layer = it->second; + // if the volume changed, we need to resort the layer (reinsertion) + // if the visibility changed, we need to erase the layer + if (zoneLayer.volume != layer->volume || !isVisible) { + erase(layer); + _map.erase(it); + layer = end(); + } + } + + // (re)insert this zone's layer if necessary + if (layer == end() && isVisible) { + std::tie(layer, std::ignore) = insert(zoneLayer); + _map.emplace(layer->id, layer); + } + + if (shouldUpdate) { + applyPartial(layer); + } + } +} + +void EntityTreeRenderer::LayeredZones::applyPartial(iterator layer) { + bool hasSkybox = false; + _skyboxLayer = end(); + + if (layer == end()) { + if (empty()) { + _entityTreeRenderer->applyZoneAndHasSkybox(nullptr); + return; + } else { // a layer was removed - reapply from beginning + layer = begin(); + } + } + + if (layer == begin()) { + hasSkybox = _entityTreeRenderer->applyZoneAndHasSkybox(layer->zone); + } else { + hasSkybox = _entityTreeRenderer->layerZoneAndHasSkybox(layer->zone); + } + + if (layer != end()) { + while (!hasSkybox && ++layer != end()) { + hasSkybox = _entityTreeRenderer->layerZoneAndHasSkybox(layer->zone); + } + } + + _skyboxLayer = layer; +} + +bool EntityTreeRenderer::LayeredZones::contains(const LayeredZones& other) { + bool result = std::equal(other.begin(), other._skyboxLayer, begin()); + if (result) { + // if valid, set the _skyboxLayer from the other LayeredZones + _skyboxLayer = std::next(begin(), std::distance(other.begin(), other._skyboxLayer)); + } + return result; +} diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index 7ca11ccdbb..b1d875c2fb 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -76,7 +76,7 @@ public: Q_INVOKABLE ModelPointer allocateModel(const QString& url, float loadingPriority = 0.0f); /// if a renderable entity item needs to update the URL of a model, we will handle that for the entity - Q_INVOKABLE ModelPointer updateModel(ModelPointer original, const QString& newUrl, const QString& collisionUrl); + Q_INVOKABLE ModelPointer updateModel(ModelPointer original, const QString& newUrl); /// if a renderable entity item is done with a model, it should return it to us void releaseModel(ModelPointer model); @@ -95,7 +95,7 @@ public: // For Scene.shouldRenderEntities QList& getEntitiesLastInScene() { return _entityIDsLastInScene; } - std::shared_ptr myAvatarZone() { return _bestZone; } + std::shared_ptr myAvatarZone() { return _layeredZones.getZone(); } signals: void mousePressOnEntity(const EntityItemID& entityItemID, const PointerEvent& event); @@ -138,9 +138,12 @@ private: void resetEntitiesScriptEngine(); void addEntityToScene(EntityItemPointer entity); - bool findBestZoneAndMaybeContainingEntities(const glm::vec3& avatarPosition, QVector* entitiesContainingAvatar); + bool findBestZoneAndMaybeContainingEntities(QVector* entitiesContainingAvatar = nullptr); + + bool applyZoneAndHasSkybox(const std::shared_ptr& zone); + bool layerZoneAndHasSkybox(const std::shared_ptr& zone); + bool applySkyboxAndHasAmbient(); - void applyZonePropertiesToScene(std::shared_ptr zone); void checkAndCallPreload(const EntityItemID& entityID, const bool reload = false); QList _releasedModels; @@ -156,15 +159,9 @@ private: void leaveAllEntities(); void forceRecheckEntities(); - glm::vec3 _lastAvatarPosition { 0.0f }; + glm::vec3 _avatarPosition { 0.0f }; QVector _currentEntitiesInside; - bool _pendingSkyboxTexture { false }; - NetworkTexturePointer _skyboxTexture; - - bool _pendingAmbientTexture { false }; - NetworkTexturePointer _ambientTexture; - bool _wantScripts; QSharedPointer _entitiesScriptEngine; @@ -185,26 +182,62 @@ private: QMultiMap _waitingOnPreload; - std::shared_ptr _bestZone; - float _bestZoneVolume; + class LayeredZone { + public: + LayeredZone(std::shared_ptr zone, QUuid id, float volume) : zone(zone), id(id), volume(volume) {} + LayeredZone(std::shared_ptr zone) : LayeredZone(zone, zone->getID(), zone->getVolumeEstimate()) {} + bool operator<(const LayeredZone& r) const { return std::tie(volume, id) < std::tie(r.volume, r.id); } + bool operator==(const LayeredZone& r) const { return id == r.id; } + bool operator<=(const LayeredZone& r) const { return (*this < r) || (*this == r); } + + std::shared_ptr zone; + QUuid id; + float volume; + }; + + class LayeredZones : public std::set { + public: + LayeredZones(EntityTreeRenderer* parent) : _entityTreeRenderer(parent) {} + LayeredZones(LayeredZones&& other); + + // avoid accidental misconstruction + LayeredZones() = delete; + LayeredZones(const LayeredZones&) = delete; + LayeredZones& operator=(const LayeredZones&) = delete; + LayeredZones& operator=(LayeredZones&&) = delete; + + void clear(); + std::pair insert(const LayeredZone& layer); + + void apply(); + void update(std::shared_ptr zone); + + bool contains(const LayeredZones& other); + + std::shared_ptr getZone() { return empty() ? nullptr : begin()->zone; } + + private: + void applyPartial(iterator layer); + + std::map _map; + iterator _skyboxLayer{ end() }; + EntityTreeRenderer* _entityTreeRenderer; + }; + + LayeredZones _layeredZones; QString _zoneUserData; + NetworkTexturePointer _ambientTexture; + NetworkTexturePointer _skyboxTexture; + QString _ambientTextureURL; + QString _skyboxTextureURL; + bool _pendingAmbientTexture { false }; + bool _pendingSkyboxTexture { false }; quint64 _lastZoneCheck { 0 }; const quint64 ZONE_CHECK_INTERVAL = USECS_PER_MSEC * 100; // ~10hz const float ZONE_CHECK_DISTANCE = 0.001f; - glm::vec3 _previousKeyLightColor; - float _previousKeyLightIntensity; - float _previousKeyLightAmbientIntensity; - glm::vec3 _previousKeyLightDirection; - bool _previousStageSunModelEnabled; - float _previousStageLongitude; - float _previousStageLatitude; - float _previousStageAltitude; - float _previousStageHour; - int _previousStageDay; - QHash _entitiesInScene; // For Scene.shouldRenderEntities QList _entityIDsLastInScene; diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index fc3245f322..abc4e1c767 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -548,6 +548,15 @@ EntityItemProperties RenderableModelEntityItem::getProperties(EntityPropertyFlag if (_originalTexturesRead) { properties.setTextureNames(_originalTextures); } + + if (_model) { + properties.setRenderInfoVertexCount(_model->getRenderInfoVertexCount()); + properties.setRenderInfoTextureCount(_model->getRenderInfoTextureCount()); + properties.setRenderInfoTextureSize(_model->getRenderInfoTextureSize()); + properties.setRenderInfoDrawCalls(_model->getRenderInfoDrawCalls()); + properties.setRenderInfoHasTransparent(_model->getRenderInfoHasTransparent()); + } + return properties; } diff --git a/libraries/entities/src/EntityActionInterface.cpp b/libraries/entities/src/EntityActionInterface.cpp index ce9a93a6ac..2ce4ce5555 100644 --- a/libraries/entities/src/EntityActionInterface.cpp +++ b/libraries/entities/src/EntityActionInterface.cpp @@ -100,6 +100,9 @@ EntityActionType EntityActionInterface::actionTypeFromString(QString actionTypeS if (normalizedActionTypeString == "hold") { return ACTION_TYPE_HOLD; } + if (normalizedActionTypeString == "traveloriented") { + return ACTION_TYPE_TRAVEL_ORIENTED; + } qDebug() << "Warning -- EntityActionInterface::actionTypeFromString got unknown action-type name" << actionTypeString; return ACTION_TYPE_NONE; @@ -115,6 +118,8 @@ QString EntityActionInterface::actionTypeToString(EntityActionType actionType) { return "spring"; case ACTION_TYPE_HOLD: return "hold"; + case ACTION_TYPE_TRAVEL_ORIENTED: + return "travel-oriented"; } assert(false); return "none"; diff --git a/libraries/entities/src/EntityActionInterface.h b/libraries/entities/src/EntityActionInterface.h index 9a881cf94c..d9a901f1f6 100644 --- a/libraries/entities/src/EntityActionInterface.h +++ b/libraries/entities/src/EntityActionInterface.h @@ -28,7 +28,8 @@ enum EntityActionType { ACTION_TYPE_NONE = 0, ACTION_TYPE_OFFSET = 1000, ACTION_TYPE_SPRING = 2000, - ACTION_TYPE_HOLD = 3000 + ACTION_TYPE_HOLD = 3000, + ACTION_TYPE_TRAVEL_ORIENTED = 4000 }; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 06c91b4f32..0e40278824 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -580,6 +580,24 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLIENT_ONLY, clientOnly); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_OWNING_AVATAR_ID, owningAvatarID); + // Rendering info + if (!skipDefaults) { + QScriptValue renderInfo = engine->newObject(); + + // currently only supported by models + if (_type == EntityTypes::Model) { + renderInfo.setProperty("verticesCount", (int)getRenderInfoVertexCount()); // FIXME - theoretically the number of vertex could be > max int + renderInfo.setProperty("texturesSize", (int)getRenderInfoTextureSize()); // FIXME - theoretically the size of textures could be > max int + renderInfo.setProperty("hasTransparent", getRenderInfoHasTransparent()); + renderInfo.setProperty("drawCalls", getRenderInfoDrawCalls()); + } + + if (_type == EntityTypes::Model || _type == EntityTypes::PolyLine || _type == EntityTypes::ParticleEffect) { + renderInfo.setProperty("texturesCount", QScriptValue(_textureNames.count())); + } + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_NO_SKIP(renderInfo, renderInfo); // Gettable but not settable + } + properties.setProperty("clientOnly", convertScriptValue(engine, getClientOnly())); properties.setProperty("owningAvatarID", convertScriptValue(engine, getOwningAvatarID())); diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 4591dabc51..473966dd60 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -285,6 +285,19 @@ public: void setJointRotationsDirty() { _jointRotationsSetChanged = true; _jointRotationsChanged = true; } void setJointTranslationsDirty() { _jointTranslationsSetChanged = true; _jointTranslationsChanged = true; } + // render info related items + size_t getRenderInfoVertexCount() const { return _renderInfoVertexCount; } + void setRenderInfoVertexCount(size_t value) { _renderInfoVertexCount = value; } + int getRenderInfoTextureCount() const { return _renderInfoTextureCount; } + void setRenderInfoTextureCount(int value) { _renderInfoTextureCount = value; } + size_t getRenderInfoTextureSize() const { return _renderInfoTextureSize; } + void setRenderInfoTextureSize(size_t value) { _renderInfoTextureSize = value; } + int getRenderInfoDrawCalls() const { return _renderInfoDrawCalls; } + void setRenderInfoDrawCalls(int value) { _renderInfoDrawCalls = value; } + bool getRenderInfoHasTransparent() const { return _renderInfoHasTransparent; } + void setRenderInfoHasTransparent(bool value) { _renderInfoHasTransparent = value; } + + protected: QString getCollisionMaskAsString() const; void setCollisionMaskFromString(const QString& maskString); @@ -308,6 +321,12 @@ private: glm::vec3 _naturalDimensions; glm::vec3 _naturalPosition; + size_t _renderInfoVertexCount { 0 }; + int _renderInfoTextureCount { 0 }; + size_t _renderInfoTextureSize { 0 }; + int _renderInfoDrawCalls { 0 }; + bool _renderInfoHasTransparent { false }; + EntityPropertyFlags _desiredProperties; // if set will narrow scopes of copy/to/from to just these properties }; diff --git a/libraries/fbx/src/FBXReader_Node.cpp b/libraries/fbx/src/FBXReader_Node.cpp index 68e9d6abac..7bacdbc607 100644 --- a/libraries/fbx/src/FBXReader_Node.cpp +++ b/libraries/fbx/src/FBXReader_Node.cpp @@ -140,17 +140,35 @@ QVariant parseBinaryFBXProperty(QDataStream& in, int& position) { } } -FBXNode parseBinaryFBXNode(QDataStream& in, int& position) { - qint32 endOffset; - quint32 propertyCount; - quint32 propertyListLength; +FBXNode parseBinaryFBXNode(QDataStream& in, int& position, bool has64BitPositions = false) { + qint64 endOffset; + quint64 propertyCount; + quint64 propertyListLength; quint8 nameLength; - in >> endOffset; - in >> propertyCount; - in >> propertyListLength; + // FBX 2016 and beyond uses 64bit positions in the node headers, pre-2016 used 32bit values + // our code generally doesn't care about the size that much, so we will use 64bit values + // from here on out, but if the file is an older format we read the stream into temp 32bit + // values and then assign to our actual 64bit values. + if (has64BitPositions) { + in >> endOffset; + in >> propertyCount; + in >> propertyListLength; + position += sizeof(quint64) * 3; + } else { + qint32 tempEndOffset; + quint32 tempPropertyCount; + quint32 tempPropertyListLength; + in >> tempEndOffset; + in >> tempPropertyCount; + in >> tempPropertyListLength; + position += sizeof(quint32) * 3; + endOffset = tempEndOffset; + propertyCount = tempPropertyCount; + propertyListLength = tempPropertyListLength; + } in >> nameLength; - position += sizeof(quint32) * 3 + sizeof(quint8); + position += sizeof(quint8); FBXNode node; const int MIN_VALID_OFFSET = 40; @@ -166,7 +184,7 @@ FBXNode parseBinaryFBXNode(QDataStream& in, int& position) { } while (endOffset > position) { - FBXNode child = parseBinaryFBXNode(in, position); + FBXNode child = parseBinaryFBXNode(in, position, has64BitPositions); if (child.name.isNull()) { return node; @@ -327,15 +345,24 @@ FBXNode FBXReader::parseFBX(QIODevice* device) { // see http://code.blender.org/index.php/2013/08/fbx-binary-file-format-specification/ for an explanation // of the FBX binary format - // skip the rest of the header - const int HEADER_SIZE = 27; - in.skipRawData(HEADER_SIZE); - int position = HEADER_SIZE; + // The first 27 bytes contain the header. + // Bytes 0 - 20: Kaydara FBX Binary \x00(file - magic, with 2 spaces at the end, then a NULL terminator). + // Bytes 21 - 22: [0x1A, 0x00](unknown but all observed files show these bytes). + // Bytes 23 - 26 : unsigned int, the version number. 7300 for version 7.3 for example. + const int HEADER_BEFORE_VERSION = 23; + const quint32 VERSION_FBX2016 = 7500; + in.skipRawData(HEADER_BEFORE_VERSION); + int position = HEADER_BEFORE_VERSION; + quint32 fileVersion; + in >> fileVersion; + position += sizeof(fileVersion); + qDebug() << "fileVersion:" << fileVersion; + bool has64BitPositions = (fileVersion >= VERSION_FBX2016); // parse the top-level node FBXNode top; while (device->bytesAvailable()) { - FBXNode next = parseBinaryFBXNode(in, position); + FBXNode next = parseBinaryFBXNode(in, position, has64BitPositions); if (next.name.isNull()) { return top; diff --git a/libraries/fbx/src/FSTReader.cpp b/libraries/fbx/src/FSTReader.cpp index 6f5d0d7ec5..1f7999bdaa 100644 --- a/libraries/fbx/src/FSTReader.cpp +++ b/libraries/fbx/src/FSTReader.cpp @@ -191,6 +191,7 @@ FSTReader::ModelType FSTReader::predictModelType(const QVariantHash& mapping) { QVariantHash FSTReader::downloadMapping(const QString& url) { QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest networkRequest = QNetworkRequest(url); + networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); QNetworkReply* reply = networkAccessManager.get(networkRequest); qDebug() << "Downloading avatar file at " << url; diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index 38121555ed..02b0afdf26 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -282,6 +282,7 @@ QNetworkReply* OBJReader::request(QUrl& url, bool isTest) { }); QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest netRequest(url); + netRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); QNetworkReply* netReply = isTest ? networkAccessManager.head(netRequest) : networkAccessManager.get(netRequest); if (!qApp || aboutToQuit) { netReply->deleteLater(); diff --git a/libraries/gl/CMakeLists.txt b/libraries/gl/CMakeLists.txt index f60e41739a..0c29bf691a 100644 --- a/libraries/gl/CMakeLists.txt +++ b/libraries/gl/CMakeLists.txt @@ -2,6 +2,9 @@ set(TARGET_NAME gl) setup_hifi_library(OpenGL Qml Quick) link_hifi_libraries(shared) -target_glew() target_opengl() -target_oglplus() \ No newline at end of file + +if (NOT ANDROID) + target_glew() + target_oglplus() +endif () diff --git a/libraries/gl/src/gl/Context.cpp b/libraries/gl/src/gl/Context.cpp new file mode 100644 index 0000000000..27e1e67560 --- /dev/null +++ b/libraries/gl/src/gl/Context.cpp @@ -0,0 +1,281 @@ +// +// Created by Bradley Austin Davis on 2016/08/21 +// Copyright 2013-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 "Context.h" + +#include + +#include + +#include +#include +#include + +#include +#include + +#include + +#ifdef Q_OS_WIN + +#ifdef DEBUG +static bool enableDebugLogger = true; +#else +static const QString DEBUG_FLAG("HIFI_DEBUG_OPENGL"); +static bool enableDebugLogger = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); +#endif + +#endif + +#include "Config.h" +#include "GLHelpers.h" + + +using namespace gl; + +Context* Context::PRIMARY = nullptr; + +Context::Context() {} + +Context::Context(QWindow* window) { + setWindow(window); +} + +#ifdef Q_OS_WIN +void Context::destroyWin32Context(HGLRC hglrc) { + wglDeleteContext(hglrc); +} +#endif + +void Context::release() { + doneCurrent(); +#ifdef Q_OS_WIN + if (_wrappedContext) { + destroyContext(_wrappedContext); + _wrappedContext = nullptr; + } + if (_hglrc) { + destroyWin32Context(_hglrc); + _hglrc = 0; + } + if (_hdc) { + ReleaseDC(_hwnd, _hdc); + _hdc = 0; + } + _hwnd = 0; +#else + destroyContext(_context); + _context = nullptr; +#endif + _window = nullptr; + if (PRIMARY == this) { + PRIMARY = nullptr; + } + } + +Context::~Context() { + release(); +} + +void Context::setWindow(QWindow* window) { + release(); + _window = window; +#ifdef Q_OS_WIN + _hwnd = (HWND)window->winId(); +#endif +} + +#ifdef Q_OS_WIN +static const char* PRIMARY_CONTEXT_PROPERTY_NAME = "com.highfidelity.gl.primaryContext"; + +bool Context::makeCurrent() { + BOOL result = wglMakeCurrent(_hdc, _hglrc); + assert(result); + return result; +} + +void Context::swapBuffers() { + SwapBuffers(_hdc); +} + +void Context::doneCurrent() { + wglMakeCurrent(0, 0); +} + +void GLAPIENTRY debugMessageCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) { + if (GL_DEBUG_SEVERITY_NOTIFICATION == severity) { + return; + } + qDebug() << "QQQ " << message; +} + +// FIXME build the PFD based on the +static const PIXELFORMATDESCRIPTOR pfd = // pfd Tells Windows How We Want Things To Be +{ + sizeof(PIXELFORMATDESCRIPTOR), // Size Of This Pixel Format Descriptor + 1, // Version Number + PFD_DRAW_TO_WINDOW | // Format Must Support Window + PFD_SUPPORT_OPENGL | // Format Must Support OpenGL + PFD_DOUBLEBUFFER, // Must Support Double Buffering + PFD_TYPE_RGBA, // Request An RGBA Format + 24, // Select Our Color Depth + 0, 0, 0, 0, 0, 0, // Color Bits Ignored + 1, // Alpha Buffer + 0, // Shift Bit Ignored + 0, // No Accumulation Buffer + 0, 0, 0, 0, // Accumulation Bits Ignored + 24, // 24 Bit Z-Buffer (Depth Buffer) + 8, // 8 Bit Stencil Buffer + 0, // No Auxiliary Buffer + PFD_MAIN_PLANE, // Main Drawing Layer + 0, // Reserved + 0, 0, 0 // Layer Masks Ignored +}; + +void setupPixelFormatSimple(HDC hdc) { + auto pixelFormat = ChoosePixelFormat(hdc, &pfd); + if (pixelFormat == 0) { + throw std::runtime_error("Unable to create initial context"); + } + + if (SetPixelFormat(hdc, pixelFormat, &pfd) == FALSE) { + throw std::runtime_error("Unable to create initial context"); + } +} + +void Context::create() { + if (!PRIMARY) { + PRIMARY = static_cast(qApp->property(PRIMARY_CONTEXT_PROPERTY_NAME).value()); + } + + if (PRIMARY) { + _version = PRIMARY->_version; + } + + assert(0 != _hwnd); + assert(0 == _hdc); + auto hwnd = _hwnd; + // Create a temporary context to initialize glew + static std::once_flag once; + std::call_once(once, [&] { + auto hdc = GetDC(hwnd); + setupPixelFormatSimple(hdc); + auto glrc = wglCreateContext(hdc); + BOOL makeCurrentResult; + makeCurrentResult = wglMakeCurrent(hdc, glrc); + if (!makeCurrentResult) { + throw std::runtime_error("Unable to create initial context"); + } + glewExperimental = true; + glewInit(); + if (glewIsSupported("GL_VERSION_4_5")) { + _version = 0x0405; + } else if (glewIsSupported("GL_VERSION_4_3")) { + _version = 0x0403; + } + glGetError(); + wglMakeCurrent(0, 0); + wglDeleteContext(glrc); + ReleaseDC(hwnd, hdc); + }); + + _hdc = GetDC(_hwnd); + static int pixelFormat = 0; + static PIXELFORMATDESCRIPTOR pfd; + if (!pixelFormat) { + memset(&pfd, 0, sizeof(pfd)); + pfd.nSize = sizeof(pfd); + std::vector formatAttribs; + formatAttribs.push_back(WGL_DRAW_TO_WINDOW_ARB); + formatAttribs.push_back(GL_TRUE); + formatAttribs.push_back(WGL_SUPPORT_OPENGL_ARB); + formatAttribs.push_back(GL_TRUE); + formatAttribs.push_back(WGL_DOUBLE_BUFFER_ARB); + formatAttribs.push_back(GL_TRUE); + formatAttribs.push_back(WGL_PIXEL_TYPE_ARB); + formatAttribs.push_back(WGL_TYPE_RGBA_ARB); + formatAttribs.push_back(WGL_COLOR_BITS_ARB); + formatAttribs.push_back(32); + formatAttribs.push_back(WGL_DEPTH_BITS_ARB); + formatAttribs.push_back(24); + formatAttribs.push_back(WGL_STENCIL_BITS_ARB); + formatAttribs.push_back(8); +#ifdef NATIVE_SRGB_FRAMEBUFFER + // formatAttribs.push_back(WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB); + // formatAttribs.push_back(GL_TRUE); +#endif + // terminate the list + formatAttribs.push_back(0); + UINT numFormats; + wglChoosePixelFormatARB(_hdc, &formatAttribs[0], NULL, 1, &pixelFormat, &numFormats); + DescribePixelFormat(_hdc, pixelFormat, sizeof(pfd), &pfd); + } + SetPixelFormat(_hdc, pixelFormat, &pfd); + { + std::vector contextAttribs; + uint32_t majorVersion = _version >> 8; + uint32_t minorVersion = _version & 0xFF; + contextAttribs.push_back(WGL_CONTEXT_MAJOR_VERSION_ARB); + contextAttribs.push_back(majorVersion); + contextAttribs.push_back(WGL_CONTEXT_MINOR_VERSION_ARB); + contextAttribs.push_back(minorVersion); + contextAttribs.push_back(WGL_CONTEXT_PROFILE_MASK_ARB); + contextAttribs.push_back(WGL_CONTEXT_CORE_PROFILE_BIT_ARB); + contextAttribs.push_back(WGL_CONTEXT_FLAGS_ARB); + if (enableDebugLogger) { + contextAttribs.push_back(WGL_CONTEXT_DEBUG_BIT_ARB); + } else { + contextAttribs.push_back(0); + } + contextAttribs.push_back(0); + auto shareHglrc = PRIMARY ? PRIMARY->_hglrc : 0; + _hglrc = wglCreateContextAttribsARB(_hdc, shareHglrc, &contextAttribs[0]); + } + + if (_hglrc == 0) { + throw std::runtime_error("Could not create GL context"); + } + + if (!PRIMARY) { + PRIMARY = this; + qApp->setProperty(PRIMARY_CONTEXT_PROPERTY_NAME, QVariant::fromValue((void*)PRIMARY)); + } + + if (enableDebugLogger) { + makeCurrent(); + glDebugMessageCallback(debugMessageCallback, NULL); + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB); + doneCurrent(); + } +} +#endif + +void Context::clear() { + glClearColor(0, 0, 0, 1); + QSize windowSize = _window->size() * _window->devicePixelRatio(); + glViewport(0, 0, windowSize.width(), windowSize.height()); + glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + swapBuffers(); +} + + +OffscreenContext::~OffscreenContext() { + _window->deleteLater(); +} + +void OffscreenContext::create() { + if (!_window) { + _window = new QWindow(); + _window->setFlags(Qt::MSWindowsOwnDC); + _window->setSurfaceType(QSurface::OpenGLSurface); + _window->create(); + setWindow(_window); + QGuiApplication::processEvents(); + } + Parent::create(); +} diff --git a/libraries/gl/src/gl/Context.h b/libraries/gl/src/gl/Context.h new file mode 100644 index 0000000000..96e6c693fb --- /dev/null +++ b/libraries/gl/src/gl/Context.h @@ -0,0 +1,72 @@ +// +// Created by Bradley Austin Davis on 2016/08/21 +// Copyright 2013-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_gl_context_h +#define hifi_gl_context_h + +#include +#include + +#if defined(Q_OS_WIN) +#include +#endif + +class QSurface; +class QWindow; +class QOpenGLContext; +class QThread; + +namespace gl { + +class Context { + protected: + QWindow* _window { nullptr }; + static Context* PRIMARY; + static void destroyContext(QOpenGLContext* context); +#if defined(Q_OS_WIN) + uint32_t _version { 0x0401 }; + HWND _hwnd { 0 }; + HDC _hdc { 0 }; + HGLRC _hglrc { 0 }; + static void destroyWin32Context(HGLRC hglrc); + QOpenGLContext* _wrappedContext { nullptr }; +#else + QOpenGLContext* _context { nullptr }; +#endif + + private: + Context(const Context& other); + + public: + Context(); + Context(QWindow* window); + void release(); + virtual ~Context(); + + void clear(); + void setWindow(QWindow* window); + bool makeCurrent(); + static void makeCurrent(QOpenGLContext* context, QSurface* surface); + void swapBuffers(); + void doneCurrent(); + virtual void create(); + QOpenGLContext* qglContext(); + void moveToThread(QThread* thread); + }; + + class OffscreenContext : public Context { + using Parent = Context; + protected: + QWindow* _window { nullptr }; + public: + virtual ~OffscreenContext(); + virtual void create(); + }; +} + +#endif // hifi_gpu_GPUConfig_h diff --git a/libraries/gl/src/gl/ContextQt.cpp b/libraries/gl/src/gl/ContextQt.cpp new file mode 100644 index 0000000000..ff59b8f1e1 --- /dev/null +++ b/libraries/gl/src/gl/ContextQt.cpp @@ -0,0 +1,75 @@ +// +// Created by Bradley Austin Davis on 2016/08/21 +// Copyright 2013-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 "Context.h" + +#include +#include + +#ifdef Q_OS_WIN +#include +#endif + +using namespace gl; + +void Context::destroyContext(QOpenGLContext* context) { + delete context; +} + +void Context::makeCurrent(QOpenGLContext* context, QSurface* surface) { + context->makeCurrent(surface); +} + +QOpenGLContext* Context::qglContext() { +#ifdef Q_OS_WIN + if (!_wrappedContext) { + _wrappedContext = new QOpenGLContext(); + _wrappedContext->setNativeHandle(QVariant::fromValue(QWGLNativeContext(_hglrc, _hwnd))); + _wrappedContext->create(); + } + return _wrappedContext; +#else + + return _context; +#endif +} + +void Context::moveToThread(QThread* thread) { + qglContext()->moveToThread(thread); +} + +#ifndef Q_OS_WIN +bool Context::makeCurrent() { + return _context->makeCurrent(_window); +} + +void Context::swapBuffers() { + _context->swapBuffers(_window); +} + +void Context::doneCurrent() { + if (_context) { + _context->doneCurrent(); + } +} + +const QSurfaceFormat& getDefaultOpenGLSurfaceFormat(); + + +void Context::create() { + _context = new QOpenGLContext(); + if (PRIMARY) { + _context->setShareContext(PRIMARY->qglContext()); + } else { + PRIMARY = this; + } + _context->setFormat(getDefaultOpenGLSurfaceFormat()); + _context->create(); +} + +#endif diff --git a/libraries/gl/src/gl/GLHelpers.cpp b/libraries/gl/src/gl/GLHelpers.cpp index f464001f60..dde1e0ec97 100644 --- a/libraries/gl/src/gl/GLHelpers.cpp +++ b/libraries/gl/src/gl/GLHelpers.cpp @@ -21,44 +21,20 @@ const QSurfaceFormat& getDefaultOpenGLSurfaceFormat() { format.setDepthBufferSize(DEFAULT_GL_DEPTH_BUFFER_BITS); format.setStencilBufferSize(DEFAULT_GL_STENCIL_BUFFER_BITS); setGLFormatVersion(format); - if (GLDebug::enabled()) { - qDebug() << "Enabling debug context"; - format.setOption(QSurfaceFormat::DebugContext); - } format.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile); QSurfaceFormat::setDefaultFormat(format); }); return format; } -const QGLFormat& getDefaultGLFormat() { - // Specify an OpenGL 3.3 format using the Core profile. - // That is, no old-school fixed pipeline functionality - static QGLFormat glFormat; - static std::once_flag once; - std::call_once(once, [] { - setGLFormatVersion(glFormat); - glFormat.setProfile(QGLFormat::CoreProfile); // Requires >=Qt-4.8.0 - glFormat.setSampleBuffers(false); - glFormat.setDepth(false); - glFormat.setStencil(false); - QGLFormat::setDefaultFormat(glFormat); - }); - return glFormat; -} - int glVersionToInteger(QString glVersion) { QStringList versionParts = glVersion.split(QRegularExpression("[\\.\\s]")); int majorNumber = versionParts[0].toInt(); int minorNumber = versionParts[1].toInt(); - return majorNumber * 100 + minorNumber * 10; + return (majorNumber << 16) | minorNumber; } QJsonObject getGLContextData() { - if (!QOpenGLContext::currentContext()) { - return QJsonObject(); - } - QString glVersion = QString((const char*)glGetString(GL_VERSION)); QString glslVersion = QString((const char*) glGetString(GL_SHADING_LANGUAGE_VERSION)); QString glVendor = QString((const char*) glGetString(GL_VENDOR)); @@ -77,31 +53,3 @@ QThread* RENDER_THREAD = nullptr; bool isRenderThread() { return QThread::currentThread() == RENDER_THREAD; } - - -#ifdef DEBUG -static bool enableDebugLogger = true; -#else -static const QString DEBUG_FLAG("HIFI_DEBUG_OPENGL"); -static bool enableDebugLogger = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); -#endif - -bool GLDebug::enabled() { - return enableDebugLogger; -} - -void GLDebug::log(const QOpenGLDebugMessage & debugMessage) { - qDebug() << debugMessage; -} - -void GLDebug::setupLogger(QObject* window) { - if (enabled()) { - QOpenGLDebugLogger* logger = new QOpenGLDebugLogger(window); - logger->initialize(); // initializes in the current context, i.e. ctx - logger->enableMessages(); - QObject::connect(logger, &QOpenGLDebugLogger::messageLogged, window, [&](const QOpenGLDebugMessage & debugMessage) { - GLDebug::log(debugMessage); - }); - logger->startLogging(QOpenGLDebugLogger::SynchronousLogging); - } -} \ No newline at end of file diff --git a/libraries/gl/src/gl/GLHelpers.h b/libraries/gl/src/gl/GLHelpers.h index 9e185f5845..8834a718fd 100644 --- a/libraries/gl/src/gl/GLHelpers.h +++ b/libraries/gl/src/gl/GLHelpers.h @@ -27,19 +27,9 @@ template void setGLFormatVersion(F& format, int major = 4, int minor = 5) { format.setVersion(major, minor); } const QSurfaceFormat& getDefaultOpenGLSurfaceFormat(); -const QGLFormat& getDefaultGLFormat(); QJsonObject getGLContextData(); int glVersionToInteger(QString glVersion); bool isRenderThread(); - -class GLDebug { -public: - static bool enabled(); - static void log(const QOpenGLDebugMessage& debugMessage); - static void setupLogger(QObject* window = nullptr); -}; - - #endif diff --git a/libraries/gl/src/gl/GLWidget.cpp b/libraries/gl/src/gl/GLWidget.cpp index 8b0bd9981f..f2b823a65e 100644 --- a/libraries/gl/src/gl/GLWidget.cpp +++ b/libraries/gl/src/gl/GLWidget.cpp @@ -7,30 +7,52 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include #include "GLWidget.h" +#include "Config.h" + #include +#include #include #include #include -#include #include +#include #include - +#include "Context.h" #include "GLHelpers.h" +class GLPaintEngine : public QPaintEngine { + bool begin(QPaintDevice *pdev) override { return true; } + bool end() override { return true; } + void updateState(const QPaintEngineState &state) override { } + void drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr) override { } + Type type() const override { return OpenGL2; } +}; -GLWidget::GLWidget() : QGLWidget(getDefaultGLFormat()) { +GLWidget::GLWidget() { #ifdef Q_OS_LINUX // Cause GLWidget::eventFilter to be called. // It wouldn't hurt to do this on Mac and PC too; but apparently it's only needed on linux. qApp->installEventFilter(this); #endif + setAttribute(Qt::WA_AcceptTouchEvents); + setAttribute(Qt::WA_NativeWindow); + setAttribute(Qt::WA_PaintOnScreen); + setAttribute(Qt::WA_NoSystemBackground); + setAutoFillBackground(false); + grabGesture(Qt::PinchGesture); + setAcceptDrops(true); + _paintEngine = new GLPaintEngine(); +} + +GLWidget::~GLWidget() { + delete _paintEngine; + _paintEngine = nullptr; } int GLWidget::getDeviceWidth() const { @@ -41,31 +63,25 @@ int GLWidget::getDeviceHeight() const { return height() * (windowHandle() ? (float)windowHandle()->devicePixelRatio() : 1.0f); } -void GLWidget::initializeGL() { - setAttribute(Qt::WA_AcceptTouchEvents); - grabGesture(Qt::PinchGesture); - setAcceptDrops(true); - // Note, we *DO NOT* want Qt to automatically swap buffers for us. This results in the "ringing" bug mentioned in WL#19514 when we're throttling the framerate. - setAutoBufferSwap(false); - - makeCurrent(); - if (isValid() && context() && context()->contextHandle()) { -#if defined(Q_OS_WIN) - _vsyncSupported = context()->contextHandle()->hasExtension("WGL_EXT_swap_control"); -#elif defined(Q_OS_MAC) - _vsyncSupported = true; -#else - // TODO: write the proper code for linux -#endif - } +void GLWidget::createContext() { + _context = new gl::Context(); + _context->setWindow(windowHandle()); + _context->create(); + _context->clear(); + _context->makeCurrent(); } -void GLWidget::paintEvent(QPaintEvent* event) { - QWidget::paintEvent(event); +bool GLWidget::makeCurrent() { + gl::Context::makeCurrent(_context->qglContext(), windowHandle()); + return _context->makeCurrent(); } -void GLWidget::resizeEvent(QResizeEvent* event) { - QWidget::resizeEvent(event); +QOpenGLContext* GLWidget::qglContext() { + return _context->qglContext(); +} + +void GLWidget::doneCurrent() { + _context->doneCurrent(); } bool GLWidget::event(QEvent* event) { @@ -94,10 +110,9 @@ bool GLWidget::event(QEvent* event) { default: break; } - return QGLWidget::event(event); + return QWidget::event(event); } - // Pressing Alt (and Meta) key alone activates the menubar because its style inherits the // SHMenuBarAltKeyNavigation from QWindowsStyle. This makes it impossible for a scripts to // receive keyPress events for the Alt (and Meta) key in a reliable manner. @@ -119,7 +134,7 @@ bool GLWidget::eventFilter(QObject*, QEvent* event) { } else if (event->type() == QEvent::KeyRelease) { keyReleaseEvent(keyEvent); } else { - QGLWidget::event(event); + QWidget::event(event); } return true; } @@ -130,7 +145,22 @@ bool GLWidget::eventFilter(QObject*, QEvent* event) { return false; } -bool GLWidget::isVsyncSupported() const { - return _vsyncSupported; + +bool GLWidget::nativeEvent(const QByteArray &eventType, void *message, long *result) { +#ifdef Q_OS_WIN32 + MSG* win32message = static_cast(message); + switch (win32message->message) { + case WM_ERASEBKGND: + *result = 1L; + return TRUE; + + default: + break; + } +#endif + return QWidget::nativeEvent(eventType, message, result); } +QPaintEngine* GLWidget::paintEngine() const { + return _paintEngine; +} diff --git a/libraries/gl/src/gl/GLWidget.h b/libraries/gl/src/gl/GLWidget.h index 5b391aa6cd..21dffc1b75 100644 --- a/libraries/gl/src/gl/GLWidget.h +++ b/libraries/gl/src/gl/GLWidget.h @@ -10,31 +10,43 @@ #ifndef hifi_GLWidget_h #define hifi_GLWidget_h -#include +#include + +namespace gl { + class Context; +} + +class QOpenGLContext; /// customized canvas that simply forwards requests/events to the singleton application -class GLWidget : public QGLWidget { +class GLWidget : public QWidget { Q_OBJECT public: GLWidget(); + ~GLWidget(); int getDeviceWidth() const; int getDeviceHeight() const; QSize getDeviceSize() const { return QSize(getDeviceWidth(), getDeviceHeight()); } - bool isVsyncSupported() const; - virtual void initializeGL() override; + QPaintEngine* paintEngine() const override; + void createContext(); + bool makeCurrent(); + void doneCurrent(); + gl::Context* context() { return _context; } + QOpenGLContext* qglContext(); + protected: + virtual bool nativeEvent(const QByteArray &eventType, void *message, long *result) override; virtual bool event(QEvent* event) override; - virtual void paintEvent(QPaintEvent* event) override; - virtual void resizeEvent(QResizeEvent* event) override; + gl::Context* _context { nullptr }; private slots: virtual bool eventFilter(QObject*, QEvent* event) override; private: + QPaintEngine* _paintEngine { nullptr }; bool _vsyncSupported { false }; }; - #endif // hifi_GLCanvas_h diff --git a/libraries/gl/src/gl/OffscreenGLCanvas.cpp b/libraries/gl/src/gl/OffscreenGLCanvas.cpp index 2f8b29325e..3204b50d19 100644 --- a/libraries/gl/src/gl/OffscreenGLCanvas.cpp +++ b/libraries/gl/src/gl/OffscreenGLCanvas.cpp @@ -60,7 +60,6 @@ bool OffscreenGLCanvas::makeCurrent() { qDebug() << "GL Shader Language Version: " << QString((const char*) glGetString(GL_SHADING_LANGUAGE_VERSION)); qDebug() << "GL Vendor: " << QString((const char*) glGetString(GL_VENDOR)); qDebug() << "GL Renderer: " << QString((const char*) glGetString(GL_RENDERER)); - GLDebug::setupLogger(this); }); return result; diff --git a/libraries/gl/src/gl/OpenGLVersionChecker.cpp b/libraries/gl/src/gl/OpenGLVersionChecker.cpp index ed33dc1ab9..6473b6bf2b 100644 --- a/libraries/gl/src/gl/OpenGLVersionChecker.cpp +++ b/libraries/gl/src/gl/OpenGLVersionChecker.cpp @@ -10,27 +10,46 @@ // #include "OpenGLVersionChecker.h" - -#include -#include -#include - #include "Config.h" -#include "GLWidget.h" + +#include + +#include +#include +#include +#include + #include "GLHelpers.h" -#define MINIMUM_GL_VERSION 410 +#define MINIMUM_GL_VERSION 0x0401 OpenGLVersionChecker::OpenGLVersionChecker(int& argc, char** argv) : QApplication(argc, argv) { } +const QGLFormat& getDefaultGLFormat() { + // Specify an OpenGL 3.3 format using the Core profile. + // That is, no old-school fixed pipeline functionality + static QGLFormat glFormat; + static std::once_flag once; + std::call_once(once, [] { + setGLFormatVersion(glFormat); + glFormat.setProfile(QGLFormat::CoreProfile); // Requires >=Qt-4.8.0 + glFormat.setSampleBuffers(false); + glFormat.setDepth(false); + glFormat.setStencil(false); + QGLFormat::setDefaultFormat(glFormat); + }); + return glFormat; +} + + QJsonObject OpenGLVersionChecker::checkVersion(bool& valid, bool& override) { valid = true; override = false; - GLWidget* glWidget = new GLWidget(); + QGLWidget* glWidget = new QGLWidget(); valid = glWidget->isValid(); // Inform user if no OpenGL support if (!valid) { @@ -46,7 +65,8 @@ QJsonObject OpenGLVersionChecker::checkVersion(bool& valid, bool& override) { } // Retrieve OpenGL version - glWidget->initializeGL(); + // glWidget->initializeGL(); + glWidget->makeCurrent(); QJsonObject glData = getGLContextData(); delete glWidget; @@ -60,8 +80,8 @@ QJsonObject OpenGLVersionChecker::checkVersion(bool& valid, bool& override) { QStringList versionParts = glVersion.split(QRegularExpression("[\\.\\s]")); int majorNumber = versionParts[0].toInt(); int minorNumber = versionParts[1].toInt(); - int minimumMajorNumber = MINIMUM_GL_VERSION / 100; - int minimumMinorNumber = (MINIMUM_GL_VERSION - minimumMajorNumber * 100) / 10; + int minimumMajorNumber = (MINIMUM_GL_VERSION >> 16); + int minimumMinorNumber = (MINIMUM_GL_VERSION & 0xFF); valid = (majorNumber > minimumMajorNumber || (majorNumber == minimumMajorNumber && minorNumber >= minimumMinorNumber)); diff --git a/libraries/gpu-gl/CMakeLists.txt b/libraries/gpu-gl/CMakeLists.txt index 398fdd04d6..320f9b3c71 100644 --- a/libraries/gpu-gl/CMakeLists.txt +++ b/libraries/gpu-gl/CMakeLists.txt @@ -3,5 +3,8 @@ setup_hifi_library() link_hifi_libraries(shared gl gpu) GroupSources("src") -target_glew() -target_opengl() \ No newline at end of file +target_opengl() + +if (NOT ANDROID) + target_glew() +endif () diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendQuery.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendQuery.cpp index 60b204ba60..19d008da2c 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendQuery.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendQuery.cpp @@ -25,6 +25,7 @@ void GLBackend::do_beginQuery(const Batch& batch, size_t paramOffset) { auto query = batch._queries.get(batch._params[paramOffset]._uint); GLQuery* glquery = syncGPUObject(*query); if (glquery) { + glGetInteger64v(GL_TIMESTAMP, (GLint64*)&glquery->_batchElapsedTime); if (timeElapsed) { glBeginQuery(GL_TIME_ELAPSED, glquery->_endqo); } else { @@ -43,6 +44,10 @@ void GLBackend::do_endQuery(const Batch& batch, size_t paramOffset) { } else { glQueryCounter(glquery->_endqo, GL_TIMESTAMP); } + GLint64 now; + glGetInteger64v(GL_TIMESTAMP, &now); + glquery->_batchElapsedTime = now - glquery->_batchElapsedTime; + (void)CHECK_GL_ERROR(); } } @@ -61,7 +66,7 @@ void GLBackend::do_getQuery(const Batch& batch, size_t paramOffset) { glGetQueryObjectui64v(glquery->_endqo, GL_QUERY_RESULT, &end); glquery->_result = end - start; } - query->triggerReturnHandler(glquery->_result); + query->triggerReturnHandler(glquery->_result, glquery->_batchElapsedTime); } (void)CHECK_GL_ERROR(); } diff --git a/libraries/gpu-gl/src/gpu/gl/GLQuery.h b/libraries/gpu-gl/src/gpu/gl/GLQuery.h index 4bed659ba3..bf10d002e6 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLQuery.h +++ b/libraries/gpu-gl/src/gpu/gl/GLQuery.h @@ -48,6 +48,7 @@ public: const GLuint& _endqo = { _id }; const GLuint _beginqo = { 0 }; GLuint64 _result { (GLuint64)-1 }; + GLuint64 _batchElapsedTime { (GLuint64) 0 }; protected: GLQuery(const std::weak_ptr& backend, const Query& query, GLuint endId, GLuint beginId) : Parent(backend, query, endId), _beginqo(beginId) {} diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp index 818d95d756..d90ca3bbd6 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp @@ -100,7 +100,7 @@ float GLTexture::getMemoryPressure() { // If no memory limit has been set, use a percentage of the total dedicated memory if (!availableTextureMemory) { - auto totalGpuMemory = gpu::gl::getDedicatedMemory(); + auto totalGpuMemory = getDedicatedMemory(); // If no limit has been explicitly set, and the dedicated memory can't be determined, // just use a fallback fixed value of 256 MB @@ -118,7 +118,7 @@ float GLTexture::getMemoryPressure() { return (float)consumedGpuMemory / (float)availableTextureMemory; } -GLTexture::DownsampleSource::DownsampleSource(const std::weak_ptr& backend, GLTexture* oldTexture) : +GLTexture::DownsampleSource::DownsampleSource(const std::weak_ptr& backend, GLTexture* oldTexture) : _backend(backend), _size(oldTexture ? oldTexture->_size : 0), _texture(oldTexture ? oldTexture->takeOwnership() : 0), @@ -161,7 +161,7 @@ GLTexture::GLTexture(const std::weak_ptr& backend, const gpu::Texture // Create the texture and allocate storage -GLTexture::GLTexture(const std::weak_ptr& backend, const Texture& texture, GLuint id, bool transferrable) : +GLTexture::GLTexture(const std::weak_ptr& backend, const Texture& texture, GLuint id, bool transferrable) : GLTexture(backend, texture, id, nullptr, transferrable) { // FIXME, do during allocation @@ -170,7 +170,7 @@ GLTexture::GLTexture(const std::weak_ptr& backend, const Texture& } // Create the texture and copy from the original higher resolution version -GLTexture::GLTexture(const std::weak_ptr& backend, const gpu::Texture& texture, GLuint id, GLTexture* originalTexture) : +GLTexture::GLTexture(const std::weak_ptr& backend, const gpu::Texture& texture, GLuint id, GLTexture* originalTexture) : GLTexture(backend, texture, id, originalTexture, originalTexture->_transferrable) { Q_ASSERT(_minMip >= originalTexture->_minMip); diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.h b/libraries/gpu-gl/src/gpu/gl/GLTexture.h index d050afbb59..4f67039aa8 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTexture.h +++ b/libraries/gpu-gl/src/gpu/gl/GLTexture.h @@ -58,7 +58,6 @@ public: // If we just did a transfer, return the object after doing post-transfer work if (GLSyncState::Transferred == object->getSyncState()) { object->postTransfer(); - return object; } if (object->isOutdated()) { diff --git a/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.cpp b/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.cpp index 766c134b16..1d22ae7a52 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.cpp @@ -7,10 +7,8 @@ // #include "GLTextureTransfer.h" -#ifdef THREADED_TEXTURE_TRANSFER -#include -#include -#endif +#include +#include #include "GLShared.h" #include "GLTexture.h" @@ -20,16 +18,9 @@ using namespace gpu::gl; GLTextureTransferHelper::GLTextureTransferHelper() { #ifdef THREADED_TEXTURE_TRANSFER - _canvas = QSharedPointer(new OffscreenGLCanvas(), &QObject::deleteLater); - _canvas->setObjectName("TextureTransferCanvas"); - _canvas->create(QOpenGLContextWrapper::currentContext()); - if (!_canvas->makeCurrent()) { - qFatal("Unable to create texture transfer context"); - } - _canvas->doneCurrent(); + setObjectName("TextureTransferThread"); + _context.create(); initialize(true, QThread::LowPriority); - _canvas->moveToThreadWithContext(_thread); - // Clean shutdown on UNIX, otherwise _canvas is freed early connect(qApp, &QCoreApplication::aboutToQuit, [&] { terminate(); }); #endif @@ -64,17 +55,9 @@ void GLTextureTransferHelper::transferTexture(const gpu::TexturePointer& texture } void GLTextureTransferHelper::setup() { -#ifdef THREADED_TEXTURE_TRANSFER - _canvas->makeCurrent(); -#endif } void GLTextureTransferHelper::shutdown() { -#ifdef THREADED_TEXTURE_TRANSFER - _canvas->doneCurrent(); - _canvas->moveToThreadWithContext(qApp->thread()); - _canvas.reset(); -#endif } void GLTextureTransferHelper::do_transfer(GLTexture& texture) { @@ -85,6 +68,9 @@ void GLTextureTransferHelper::do_transfer(GLTexture& texture) { } bool GLTextureTransferHelper::processQueueItems(const Queue& messages) { +#ifdef THREADED_TEXTURE_TRANSFER + _context.makeCurrent(); +#endif for (auto package : messages) { TexturePointer texturePointer = package.texture.lock(); // Texture no longer exists, move on to the next @@ -93,21 +79,39 @@ bool GLTextureTransferHelper::processQueueItems(const Queue& messages) { } if (package.fence) { - glClientWaitSync(package.fence, GL_SYNC_FLUSH_COMMANDS_BIT, GL_TIMEOUT_IGNORED); + auto result = glClientWaitSync(package.fence, 0, 0); + while (GL_TIMEOUT_EXPIRED == result || GL_WAIT_FAILED == result) { + // Minimum sleep + QThread::usleep(1); + result = glClientWaitSync(package.fence, 0, 0); + } + assert(GL_CONDITION_SATISFIED == result || GL_ALREADY_SIGNALED == result); glDeleteSync(package.fence); package.fence = 0; } GLTexture* object = Backend::getGPUObject(*texturePointer); + do_transfer(*object); glBindTexture(object->_target, 0); - auto writeSync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - glClientWaitSync(writeSync, GL_SYNC_FLUSH_COMMANDS_BIT, GL_TIMEOUT_IGNORED); - glDeleteSync(writeSync); + { + auto fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + assert(fence); + auto result = glClientWaitSync(fence, GL_SYNC_FLUSH_COMMANDS_BIT, 0); + while (GL_TIMEOUT_EXPIRED == result || GL_WAIT_FAILED == result) { + // Minimum sleep + QThread::usleep(1); + result = glClientWaitSync(fence, 0, 0); + } + glDeleteSync(fence); + } object->_contentStamp = texturePointer->getDataStamp(); object->setSyncState(GLSyncState::Transferred); } +#ifdef THREADED_TEXTURE_TRANSFER + _context.doneCurrent(); +#endif return true; } diff --git a/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.h b/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.h index 078ab40ee3..e64e204af3 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.h +++ b/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.h @@ -13,14 +13,14 @@ #include +#include + #include "GLShared.h" #ifdef Q_OS_WIN #define THREADED_TEXTURE_TRANSFER #endif -class OffscreenGLCanvas; - namespace gpu { namespace gl { struct TextureTransferPackage { @@ -43,7 +43,7 @@ protected: void do_transfer(GLTexture& texturePointer); private: - QSharedPointer _canvas; + ::gl::OffscreenContext _context; }; } } diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h index 37441c4ebb..6e15542310 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h +++ b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h @@ -27,8 +27,10 @@ namespace gpu { namespace gl41 { -class GL41Backend : public gl::GLBackend { - using Parent = gl::GLBackend; +using namespace gpu::gl; + +class GL41Backend : public GLBackend { + using Parent = GLBackend; // Context Backend static interface required friend class Context; @@ -36,12 +38,12 @@ public: explicit GL41Backend(bool syncCache) : Parent(syncCache) {} GL41Backend() : Parent() {} - class GL41Texture : public gpu::gl::GLTexture { - using Parent = gpu::gl::GLTexture; + class GL41Texture : public GLTexture { + using Parent = GLTexture; GLuint allocate(); public: - GL41Texture(const std::weak_ptr& backend, const Texture& buffer, bool transferrable); - GL41Texture(const std::weak_ptr& backend, const Texture& buffer, GL41Texture* original); + GL41Texture(const std::weak_ptr& backend, const Texture& buffer, bool transferrable); + GL41Texture(const std::weak_ptr& backend, const Texture& buffer, GL41Texture* original); protected: void transferMip(uint16_t mipLevel, uint8_t face = 0) const; @@ -56,16 +58,16 @@ public: protected: GLuint getFramebufferID(const FramebufferPointer& framebuffer) override; - gl::GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer) override; + GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer) override; GLuint getBufferID(const Buffer& buffer) override; - gl::GLBuffer* syncGPUObject(const Buffer& buffer) override; + GLBuffer* syncGPUObject(const Buffer& buffer) override; GLuint getTextureID(const TexturePointer& texture, bool needTransfer = true) override; - gl::GLTexture* syncGPUObject(const TexturePointer& texture, bool sync = true) override; + GLTexture* syncGPUObject(const TexturePointer& texture, bool sync = true) override; GLuint getQueryID(const QueryPointer& query) override; - gl::GLQuery* syncGPUObject(const Query& query) override; + GLQuery* syncGPUObject(const Query& query) override; // Draw Stage void do_draw(const Batch& batch, size_t paramOffset) override; diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendBuffer.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendBuffer.cpp index d9d7328bc8..b739bfaf89 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendBuffer.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendBuffer.cpp @@ -10,7 +10,7 @@ namespace gpu { namespace gl41 { - class GL41Buffer : public gl::GLBuffer { + class GL41Buffer : public gpu::gl::GLBuffer { using Parent = gpu::gl::GLBuffer; static GLuint allocate() { GLuint result; @@ -55,6 +55,7 @@ namespace gpu { } using namespace gpu; +using namespace gpu::gl; using namespace gpu::gl41; @@ -62,6 +63,6 @@ GLuint GL41Backend::getBufferID(const Buffer& buffer) { return GL41Buffer::getId(*this, buffer); } -gl::GLBuffer* GL41Backend::syncGPUObject(const Buffer& buffer) { +GLBuffer* GL41Backend::syncGPUObject(const Buffer& buffer) { return GL41Buffer::sync(*this, buffer); } diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendQuery.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendQuery.cpp index 342c4ba6c2..f712550973 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendQuery.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendQuery.cpp @@ -13,10 +13,11 @@ #include "../gl/GLQuery.h" using namespace gpu; +using namespace gpu::gl; using namespace gpu::gl41; -class GL41Query : public gpu::gl::GLQuery { - using Parent = gpu::gl::GLQuery; +class GL41Query : public GLQuery { + using Parent = GLQuery; public: static GLuint allocateQuery() { GLuint result; @@ -24,11 +25,11 @@ public: return result; } - GL41Query(const std::weak_ptr& backend, const Query& query) + GL41Query(const std::weak_ptr& backend, const Query& query) : Parent(backend, query, allocateQuery(), allocateQuery()) { } }; -gl::GLQuery* GL41Backend::syncGPUObject(const Query& query) { +GLQuery* GL41Backend::syncGPUObject(const Query& query) { return GL41Query::sync(*this, query); } diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp index 8f1248ef57..ff1a790ba5 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp @@ -17,9 +17,10 @@ #include "../gl/GLTexelFormat.h" using namespace gpu; +using namespace gpu::gl; using namespace gpu::gl41; -using GL41TexelFormat = gl::GLTexelFormat; +using GL41TexelFormat = GLTexelFormat; using GL41Texture = GL41Backend::GL41Texture; GLuint GL41Texture::allocate() { @@ -33,13 +34,13 @@ GLuint GL41Backend::getTextureID(const TexturePointer& texture, bool transfer) { return GL41Texture::getId(*this, texture, transfer); } -gl::GLTexture* GL41Backend::syncGPUObject(const TexturePointer& texture, bool transfer) { +GLTexture* GL41Backend::syncGPUObject(const TexturePointer& texture, bool transfer) { return GL41Texture::sync(*this, texture, transfer); } -GL41Texture::GL41Texture(const std::weak_ptr& backend, const Texture& texture, bool transferrable) : gl::GLTexture(backend, texture, allocate(), transferrable) {} +GL41Texture::GL41Texture(const std::weak_ptr& backend, const Texture& texture, bool transferrable) : GLTexture(backend, texture, allocate(), transferrable) {} -GL41Texture::GL41Texture(const std::weak_ptr& backend, const Texture& texture, GL41Texture* original) : gl::GLTexture(backend, texture, allocate(), original) {} +GL41Texture::GL41Texture(const std::weak_ptr& backend, const Texture& texture, GL41Texture* original) : GLTexture(backend, texture, allocate(), original) {} void GL41Backend::GL41Texture::withPreservedTexture(std::function f) const { GLint boundTex = -1; @@ -71,7 +72,7 @@ void GL41Backend::GL41Texture::generateMips() const { } void GL41Backend::GL41Texture::allocateStorage() const { - gl::GLTexelFormat texelFormat = gl::GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat()); + GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat()); glTexParameteri(_target, GL_TEXTURE_BASE_LEVEL, 0); (void)CHECK_GL_ERROR(); glTexParameteri(_target, GL_TEXTURE_MAX_LEVEL, _maxMip - _minMip); @@ -131,7 +132,7 @@ void GL41Backend::GL41Texture::updateSize() const { // Move content bits from the CPU to the GPU for a given mip / face void GL41Backend::GL41Texture::transferMip(uint16_t mipLevel, uint8_t face) const { auto mip = _gpuObject.accessStoredMipFace(mipLevel, face); - gl::GLTexelFormat texelFormat = gl::GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat(), mip->getFormat()); + GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat(), mip->getFormat()); //GLenum target = getFaceTargets()[face]; GLenum target = _target == GL_TEXTURE_2D ? GL_TEXTURE_2D : CUBE_FACE_LAYOUT[face]; auto size = _gpuObject.evalMipDimensions(mipLevel); @@ -216,7 +217,7 @@ void GL41Backend::GL41Texture::syncSampler() const { if (sampler.doComparison()) { glTexParameteri(_target, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); - glTexParameteri(_target, GL_TEXTURE_COMPARE_FUNC, gl::COMPARISON_TO_GL[sampler.getComparisonFunction()]); + glTexParameteri(_target, GL_TEXTURE_COMPARE_FUNC, COMPARISON_TO_GL[sampler.getComparisonFunction()]); } else { glTexParameteri(_target, GL_TEXTURE_COMPARE_MODE, GL_NONE); } diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTransform.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTransform.cpp index 45f48df310..ee803f28c1 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTransform.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTransform.cpp @@ -69,7 +69,9 @@ void GL41Backend::transferTransformState(const Batch& batch) const { #else glActiveTexture(GL_TEXTURE0 + TRANSFORM_OBJECT_SLOT); glBindTexture(GL_TEXTURE_BUFFER, _transform._objectBufferTexture); - glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, _transform._objectBuffer); + if (!batch._objects.empty()) { + glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, _transform._objectBuffer); + } #endif CHECK_GL_ERROR(); diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h index 679699129f..0d737ef3ba 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h +++ b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h @@ -15,9 +15,11 @@ #include "../gl/GLTexture.h" namespace gpu { namespace gl45 { + +using namespace gpu::gl; -class GL45Backend : public gl::GLBackend { - using Parent = gl::GLBackend; +class GL45Backend : public GLBackend { + using Parent = GLBackend; // Context Backend static interface required friend class Context; @@ -25,12 +27,12 @@ public: explicit GL45Backend(bool syncCache) : Parent(syncCache) {} GL45Backend() : Parent() {} - class GL45Texture : public gpu::gl::GLTexture { - using Parent = gpu::gl::GLTexture; + class GL45Texture : public GLTexture { + using Parent = GLTexture; GLuint allocate(const Texture& texture); public: - GL45Texture(const std::weak_ptr& backend, const Texture& texture, bool transferrable); - GL45Texture(const std::weak_ptr& backend, const Texture& texture, GLTexture* original); + GL45Texture(const std::weak_ptr& backend, const Texture& texture, bool transferrable); + GL45Texture(const std::weak_ptr& backend, const Texture& texture, GLTexture* original); protected: void transferMip(uint16_t mipLevel, uint8_t face = 0) const; @@ -45,16 +47,16 @@ public: protected: GLuint getFramebufferID(const FramebufferPointer& framebuffer) override; - gl::GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer) override; + GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer) override; GLuint getBufferID(const Buffer& buffer) override; - gl::GLBuffer* syncGPUObject(const Buffer& buffer) override; + GLBuffer* syncGPUObject(const Buffer& buffer) override; GLuint getTextureID(const TexturePointer& texture, bool needTransfer = true) override; - gl::GLTexture* syncGPUObject(const TexturePointer& texture, bool sync = true) override; + GLTexture* syncGPUObject(const TexturePointer& texture, bool sync = true) override; GLuint getQueryID(const QueryPointer& query) override; - gl::GLQuery* syncGPUObject(const Query& query) override; + GLQuery* syncGPUObject(const Query& query) override; // Draw Stage void do_draw(const Batch& batch, size_t paramOffset) override; diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendBuffer.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendBuffer.cpp index c7c9ec1b2e..cd69c03691 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendBuffer.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendBuffer.cpp @@ -8,41 +8,42 @@ #include "GL45Backend.h" #include "../gl/GLBuffer.h" -namespace gpu { - namespace gl45 { - class GL45Buffer : public gl::GLBuffer { - using Parent = gpu::gl::GLBuffer; - static GLuint allocate() { - GLuint result; - glCreateBuffers(1, &result); - return result; - } +namespace gpu { namespace gl45 { + using namespace gpu::gl; - public: - GL45Buffer(const std::weak_ptr& backend, const Buffer& buffer, GLBuffer* original) : Parent(backend, buffer, allocate()) { - glNamedBufferStorage(_buffer, _size == 0 ? 256 : _size, nullptr, GL_DYNAMIC_STORAGE_BIT); - if (original && original->_size) { - glCopyNamedBufferSubData(original->_buffer, _buffer, 0, 0, std::min(original->_size, _size)); - } - Backend::setGPUObject(buffer, this); - } + class GL45Buffer : public GLBuffer { + using Parent = GLBuffer; + static GLuint allocate() { + GLuint result; + glCreateBuffers(1, &result); + return result; + } - void transfer() override { - Size offset; - Size size; - Size currentPage { 0 }; - auto data = _gpuObject._renderSysmem.readData(); - while (_gpuObject._renderPages.getNextTransferBlock(offset, size, currentPage)) { - glNamedBufferSubData(_buffer, (GLintptr)offset, (GLsizeiptr)size, data + offset); - } - (void)CHECK_GL_ERROR(); - _gpuObject._renderPages._flags &= ~PageManager::DIRTY; + public: + GL45Buffer(const std::weak_ptr& backend, const Buffer& buffer, GLBuffer* original) : Parent(backend, buffer, allocate()) { + glNamedBufferStorage(_buffer, _size == 0 ? 256 : _size, nullptr, GL_DYNAMIC_STORAGE_BIT); + if (original && original->_size) { + glCopyNamedBufferSubData(original->_buffer, _buffer, 0, 0, std::min(original->_size, _size)); } - }; - } -} + Backend::setGPUObject(buffer, this); + } + + void transfer() override { + Size offset; + Size size; + Size currentPage { 0 }; + auto data = _gpuObject._renderSysmem.readData(); + while (_gpuObject._renderPages.getNextTransferBlock(offset, size, currentPage)) { + glNamedBufferSubData(_buffer, (GLintptr)offset, (GLsizeiptr)size, data + offset); + } + (void)CHECK_GL_ERROR(); + _gpuObject._renderPages._flags &= ~PageManager::DIRTY; + } + }; +} } using namespace gpu; +using namespace gpu::gl; using namespace gpu::gl45; @@ -50,6 +51,6 @@ GLuint GL45Backend::getBufferID(const Buffer& buffer) { return GL45Buffer::getId(*this, buffer); } -gl::GLBuffer* GL45Backend::syncGPUObject(const Buffer& buffer) { +GLBuffer* GL45Backend::syncGPUObject(const Buffer& buffer) { return GL45Buffer::sync(*this, buffer); } diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp index 7c3e362834..d3222b9acf 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp @@ -12,14 +12,15 @@ #include #include + #include #include "../gl/GLTexelFormat.h" using namespace gpu; +using namespace gpu::gl; using namespace gpu::gl45; -using GLTexelFormat = gl::GLTexelFormat; using GL45Texture = GL45Backend::GL45Texture; GLuint GL45Texture::allocate(const Texture& texture) { @@ -33,15 +34,15 @@ GLuint GL45Backend::getTextureID(const TexturePointer& texture, bool transfer) { return GL45Texture::getId(*this, texture, transfer); } -gl::GLTexture* GL45Backend::syncGPUObject(const TexturePointer& texture, bool transfer) { +GLTexture* GL45Backend::syncGPUObject(const TexturePointer& texture, bool transfer) { return GL45Texture::sync(*this, texture, transfer); } -GL45Backend::GL45Texture::GL45Texture(const std::weak_ptr& backend, const Texture& texture, bool transferrable) - : gl::GLTexture(backend, texture, allocate(texture), transferrable) {} +GL45Backend::GL45Texture::GL45Texture(const std::weak_ptr& backend, const Texture& texture, bool transferrable) + : GLTexture(backend, texture, allocate(texture), transferrable) {} -GL45Backend::GL45Texture::GL45Texture(const std::weak_ptr& backend, const Texture& texture, GLTexture* original) - : gl::GLTexture(backend, texture, allocate(texture), original) {} +GL45Backend::GL45Texture::GL45Texture(const std::weak_ptr& backend, const Texture& texture, GLTexture* original) + : GLTexture(backend, texture, allocate(texture), original) {} void GL45Backend::GL45Texture::withPreservedTexture(std::function f) const { f(); @@ -53,7 +54,7 @@ void GL45Backend::GL45Texture::generateMips() const { } void GL45Backend::GL45Texture::allocateStorage() const { - gl::GLTexelFormat texelFormat = gl::GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat()); + GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat()); glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, 0); glTextureParameteri(_id, GL_TEXTURE_MAX_LEVEL, _maxMip - _minMip); if (_gpuObject.getTexelFormat().isCompressed()) { @@ -79,7 +80,7 @@ void GL45Backend::GL45Texture::updateSize() const { // Move content bits from the CPU to the GPU for a given mip / face void GL45Backend::GL45Texture::transferMip(uint16_t mipLevel, uint8_t face) const { auto mip = _gpuObject.accessStoredMipFace(mipLevel, face); - gl::GLTexelFormat texelFormat = gl::GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat(), mip->getFormat()); + GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat(), mip->getFormat()); auto size = _gpuObject.evalMipDimensions(mipLevel); if (GL_TEXTURE_2D == _target) { glTextureSubImage2D(_id, mipLevel, 0, 0, size.x, size.y, texelFormat.format, texelFormat.type, mip->readData()); @@ -167,7 +168,7 @@ void GL45Backend::GL45Texture::syncSampler() const { if (sampler.doComparison()) { glTextureParameteri(_id, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); - glTextureParameteri(_id, GL_TEXTURE_COMPARE_FUNC, gl::COMPARISON_TO_GL[sampler.getComparisonFunction()]); + glTextureParameteri(_id, GL_TEXTURE_COMPARE_FUNC, COMPARISON_TO_GL[sampler.getComparisonFunction()]); } else { glTextureParameteri(_id, GL_TEXTURE_COMPARE_MODE, GL_NONE); } diff --git a/libraries/gpu/CMakeLists.txt b/libraries/gpu/CMakeLists.txt index ecddeb07ad..384c5709ee 100644 --- a/libraries/gpu/CMakeLists.txt +++ b/libraries/gpu/CMakeLists.txt @@ -1,5 +1,5 @@ set(TARGET_NAME gpu) -AUTOSCRIBE_SHADER_LIB(gpu) +autoscribe_shader_lib(gpu) setup_hifi_library() link_hifi_libraries(shared) diff --git a/libraries/gpu/src/gpu/Query.cpp b/libraries/gpu/src/gpu/Query.cpp index 76c239b1e0..51be04d682 100644 --- a/libraries/gpu/src/gpu/Query.cpp +++ b/libraries/gpu/src/gpu/Query.cpp @@ -24,12 +24,16 @@ Query::~Query() { } -double Query::getElapsedTime() const { +double Query::getGPUElapsedTime() const { return ((double)_queryResult) / 1000000.0; } +double Query::getBatchElapsedTime() const { + return ((double)_usecBatchElapsedTime) / 1000000.0; +} -void Query::triggerReturnHandler(uint64_t queryResult) { +void Query::triggerReturnHandler(uint64_t queryResult, uint64_t batchElapsedTime) { _queryResult = queryResult; + _usecBatchElapsedTime = batchElapsedTime; if (_returnHandler) { _returnHandler(*this); } @@ -40,8 +44,8 @@ RangeTimer::RangeTimer() { for (int i = 0; i < QUERY_QUEUE_SIZE; i++) { _timerQueries.push_back(std::make_shared([&, i] (const Query& query) { _tailIndex ++; - auto elapsedTime = query.getElapsedTime(); - _movingAverage.addSample(elapsedTime); + _movingAverageGPU.addSample(query.getGPUElapsedTime()); + _movingAverageBatch.addSample(query.getBatchElapsedTime()); })); } } @@ -66,6 +70,10 @@ void RangeTimer::end(gpu::Batch& batch) { } } -double RangeTimer::getAverage() const { - return _movingAverage.average; +double RangeTimer::getGPUAverage() const { + return _movingAverageGPU.average; +} + +double RangeTimer::getBatchAverage() const { + return _movingAverageBatch.average; } \ No newline at end of file diff --git a/libraries/gpu/src/gpu/Query.h b/libraries/gpu/src/gpu/Query.h index 48b9d0a0d5..ab259541b5 100644 --- a/libraries/gpu/src/gpu/Query.h +++ b/libraries/gpu/src/gpu/Query.h @@ -30,14 +30,17 @@ namespace gpu { Query(const Handler& returnHandler); ~Query(); - double getElapsedTime() const; + double getGPUElapsedTime() const; + double getBatchElapsedTime() const; + // Only for gpu::Context const GPUObjectPointer gpuObject {}; - void triggerReturnHandler(uint64_t queryResult); + void triggerReturnHandler(uint64_t queryResult, uint64_t batchElapsedTime); protected: Handler _returnHandler; - uint64_t _queryResult = 0; + uint64_t _queryResult { 0 }; + uint64_t _usecBatchElapsedTime { 0 }; }; typedef std::shared_ptr QueryPointer; @@ -53,7 +56,8 @@ namespace gpu { void begin(gpu::Batch& batch); void end(gpu::Batch& batch); - double getAverage() const; + double getGPUAverage() const; + double getBatchAverage() const; protected: @@ -62,7 +66,8 @@ namespace gpu { gpu::Queries _timerQueries; int _headIndex = -1; int _tailIndex = -1; - MovingAverage _movingAverage; + MovingAverage _movingAverageGPU; + MovingAverage _movingAverageBatch; int rangeIndex(int index) const { return (index % QUERY_QUEUE_SIZE); } }; diff --git a/libraries/model/src/model/Skybox.cpp b/libraries/model/src/model/Skybox.cpp index 4a66c18f98..4901a3c61b 100755 --- a/libraries/model/src/model/Skybox.cpp +++ b/libraries/model/src/model/Skybox.cpp @@ -27,10 +27,14 @@ Skybox::Skybox() { void Skybox::setColor(const Color& color) { _schemaBuffer.edit().color = color; + _empty = false; } void Skybox::setCubemap(const gpu::TexturePointer& cubemap) { _cubemap = cubemap; + if (cubemap) { + _empty = false; + } } void Skybox::updateSchemaBuffer() const { @@ -51,7 +55,8 @@ void Skybox::updateSchemaBuffer() const { void Skybox::clear() { _schemaBuffer.edit().color = vec3(0); - setCubemap(nullptr); + _cubemap = nullptr; + _empty = true; } void Skybox::prepare(gpu::Batch& batch, int textureSlot, int bufferSlot) const { diff --git a/libraries/model/src/model/Skybox.h b/libraries/model/src/model/Skybox.h index 1e6d30bbc1..d7d95fbe9e 100755 --- a/libraries/model/src/model/Skybox.h +++ b/libraries/model/src/model/Skybox.h @@ -35,7 +35,7 @@ public: void setCubemap(const gpu::TexturePointer& cubemap); const gpu::TexturePointer& getCubemap() const { return _cubemap; } - virtual bool empty() { return _schemaBuffer.get().color == vec3(0) && !_cubemap; } + virtual bool empty() { return _empty; } virtual void clear(); void prepare(gpu::Batch& batch, int textureSlot = SKYBOX_SKYMAP_SLOT, int bufferSlot = SKYBOX_CONSTANTS_SLOT) const; @@ -47,17 +47,17 @@ protected: static const int SKYBOX_SKYMAP_SLOT { 0 }; static const int SKYBOX_CONSTANTS_SLOT { 0 }; - gpu::TexturePointer _cubemap; - class Schema { public: glm::vec3 color { 0.0f, 0.0f, 0.0f }; float blend { 0.0f }; }; - mutable gpu::BufferView _schemaBuffer; - void updateSchemaBuffer() const; + + mutable gpu::BufferView _schemaBuffer; + gpu::TexturePointer _cubemap; + bool _empty{ true }; }; typedef std::shared_ptr SkyboxPointer; diff --git a/libraries/model/src/model/TextureMap.h b/libraries/model/src/model/TextureMap.h index 795b685f27..ac35db2f03 100755 --- a/libraries/model/src/model/TextureMap.h +++ b/libraries/model/src/model/TextureMap.h @@ -59,6 +59,7 @@ public: TextureMap() {} void setTextureSource(gpu::TextureSourcePointer& textureSource); + gpu::TextureSourcePointer getTextureSource() const { return _textureSource; } bool isDefined() const; gpu::TextureView getTextureView() const; diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index d89514b7cd..1c3850f8cb 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -56,7 +56,7 @@ JSONCallbackParameters::JSONCallbackParameters(QObject* jsonCallbackReceiver, co updateReciever(updateReceiver), updateSlot(updateSlot) { - + } QJsonObject AccountManager::dataObjectFromResponse(QNetworkReply &requestReply) { @@ -85,7 +85,7 @@ AccountManager::AccountManager(UserAgentGetter userAgentGetter) : qRegisterMetaType("QNetworkAccessManager::Operation"); qRegisterMetaType("JSONCallbackParameters"); - + qRegisterMetaType("QHttpMultiPart*"); qRegisterMetaType(); @@ -143,7 +143,7 @@ void AccountManager::setAuthURL(const QUrl& authURL) { _authURL = authURL; qCDebug(networking) << "AccountManager URL for authenticated requests has been changed to" << qPrintable(_authURL.toString()); - + // check if there are existing access tokens to load from settings QFile accountsFile { accountFilePath() }; bool loadedMap = false; @@ -218,7 +218,7 @@ void AccountManager::sendRequest(const QString& path, QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest networkRequest; - + networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); networkRequest.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter()); networkRequest.setRawHeader(METAVERSE_SESSION_ID_HEADER, @@ -434,9 +434,9 @@ void AccountManager::removeAccountFromFile() { } bool AccountManager::hasValidAccessToken() { - + if (_accountInfo.getAccessToken().token.isEmpty() || _accountInfo.getAccessToken().isExpired()) { - + if (VERBOSE_HTTP_REQUEST_DEBUGGING) { qCDebug(networking) << "An access token is required for requests to" << qPrintable(_authURL.toString()); } @@ -468,7 +468,7 @@ void AccountManager::setAccessTokenForCurrentAuthURL(const QString& accessToken) } else if (!_accountInfo.getAccessToken().token.isEmpty()) { qCDebug(networking) << "Clearing AccountManager OAuth token."; } - + _accountInfo.setAccessToken(newOAuthToken); persistAccountToFile(); @@ -484,6 +484,7 @@ void AccountManager::requestAccessToken(const QString& login, const QString& pas QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest request; + request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); request.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter()); QUrl grantURL = _authURL; @@ -553,7 +554,7 @@ void AccountManager::requestAccessTokenFinished() { _accountInfo.setAccessTokenFromJSON(rootObject); emit loginComplete(rootURL); - + persistAccountToFile(); requestProfile(); @@ -576,8 +577,9 @@ void AccountManager::requestProfile() { QUrl profileURL = _authURL; profileURL.setPath("/api/v1/user/profile"); - + QNetworkRequest profileRequest(profileURL); + profileRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); profileRequest.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter()); profileRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER, _accountInfo.getAccessToken().authorizationHeaderValue()); @@ -602,7 +604,7 @@ void AccountManager::requestProfileFinished() { // store the whole profile into the local settings persistAccountToFile(); - + } else { // TODO: error handling qCDebug(networking) << "Error in response for profile"; @@ -658,14 +660,14 @@ void AccountManager::generateNewKeypair(bool isUserKeypair, const QUuid& domainI connect(generateThread, &QThread::finished, generateThread, &QThread::deleteLater); keypairGenerator->moveToThread(generateThread); - + qCDebug(networking) << "Starting worker thread to generate 2048-bit RSA keypair."; generateThread->start(); } } void AccountManager::processGeneratedKeypair() { - + qCDebug(networking) << "Generated 2048-bit RSA keypair. Uploading public key now."; RSAKeypairGenerator* keypairGenerator = qobject_cast(sender()); @@ -716,7 +718,7 @@ void AccountManager::processGeneratedKeypair() { sendRequest(uploadPath, AccountManagerAuth::Optional, QNetworkAccessManager::PutOperation, callbackParameters, QByteArray(), requestMultiPart); - + keypairGenerator->deleteLater(); } else { qCWarning(networking) << "Expected processGeneratedKeypair to be called by a live RSAKeypairGenerator" diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 6760d44244..b4145c73f5 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -63,15 +63,31 @@ QUrl AddressManager::currentAddress() const { } QUrl AddressManager::currentFacingAddress() const { - QUrl hifiURL; + auto hifiURL = currentAddress(); + hifiURL.setPath(currentFacingPath()); - hifiURL.setScheme(HIFI_URL_SCHEME); - hifiURL.setHost(_host); + return hifiURL; +} - if (_port != 0 && _port != DEFAULT_DOMAIN_SERVER_PORT) { - hifiURL.setPort(_port); + +QUrl AddressManager::currentShareableAddress() const { + if (!_shareablePlaceName.isEmpty()) { + // if we have a shareable place name use that instead of whatever the current host is + QUrl hifiURL; + + hifiURL.setScheme(HIFI_URL_SCHEME); + hifiURL.setHost(_shareablePlaceName); + + hifiURL.setPath(currentPath()); + + return hifiURL; + } else { + return currentAddress(); } +} +QUrl AddressManager::currentFacingShareableAddress() const { + auto hifiURL = currentShareableAddress(); hifiURL.setPath(currentFacingPath()); return hifiURL; @@ -360,6 +376,7 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const LookupTrigger trigger = (LookupTrigger) reply.property(LOOKUP_TRIGGER_KEY).toInt(); + // set our current root place id to the ID that came back const QString PLACE_ID_KEY = "id"; _rootPlaceID = rootMap[PLACE_ID_KEY].toUuid(); @@ -368,6 +385,18 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const const QString PLACE_NAME_KEY = "name"; QString placeName = rootMap[PLACE_NAME_KEY].toString(); + if (placeName.isEmpty()) { + // we didn't get a set place name, check if there is a default or temporary domain name to use + const QString TEMPORARY_DOMAIN_NAME_KEY = "name"; + const QString DEFAULT_DOMAIN_NAME_KEY = "default_place_name"; + + if (domainObject.contains(TEMPORARY_DOMAIN_NAME_KEY)) { + placeName = domainObject[TEMPORARY_DOMAIN_NAME_KEY].toString(); + } else if (domainObject.contains(DEFAULT_DOMAIN_NAME_KEY)) { + placeName = domainObject[DEFAULT_DOMAIN_NAME_KEY].toString(); + } + } + if (!placeName.isEmpty()) { if (setHost(placeName, trigger)) { trigger = LookupTrigger::Internal; @@ -651,6 +680,9 @@ bool AddressManager::setHost(const QString& host, LookupTrigger trigger, quint16 _port = port; + // any host change should clear the shareable place name + _shareablePlaceName.clear(); + if (host != _host) { _host = host; emit hostChanged(_host); @@ -701,13 +733,67 @@ void AddressManager::refreshPreviousLookup() { } void AddressManager::copyAddress() { - QApplication::clipboard()->setText(currentAddress().toString()); + // assume that the address is being copied because the user wants a shareable address + QApplication::clipboard()->setText(currentShareableAddress().toString()); } void AddressManager::copyPath() { QApplication::clipboard()->setText(currentPath()); } +void AddressManager::handleShareableNameAPIResponse(QNetworkReply& requestReply) { + // make sure that this response is for the domain we're currently connected to + auto domainID = DependencyManager::get()->getDomainHandler().getUUID(); + + if (requestReply.url().toString().contains(uuidStringWithoutCurlyBraces(domainID))) { + // check for a name or default name in the API response + + QJsonObject responseObject = QJsonDocument::fromJson(requestReply.readAll()).object(); + QJsonObject domainObject = responseObject["domain"].toObject(); + + const QString DOMAIN_NAME_KEY = "name"; + const QString DOMAIN_DEFAULT_PLACE_NAME_KEY = "default_place_name"; + + bool shareableNameChanged { false }; + + if (domainObject[DOMAIN_NAME_KEY].isString()) { + _shareablePlaceName = domainObject[DOMAIN_NAME_KEY].toString(); + shareableNameChanged = true; + } else if (domainObject[DOMAIN_DEFAULT_PLACE_NAME_KEY].isString()) { + _shareablePlaceName = domainObject[DOMAIN_DEFAULT_PLACE_NAME_KEY].toString(); + shareableNameChanged = true; + } + + if (shareableNameChanged) { + qDebug() << "AddressManager shareable name changed to" << _shareablePlaceName; + } + } +} + +void AddressManager::lookupShareableNameForDomainID(const QUuid& domainID) { + + // if we get to a domain via IP/hostname, often the address is only reachable by this client + // and not by other clients on the LAN or Internet + + // to work around this we use the ID to lookup the default place name, and if it exists we + // then use that for Steam join/invite or copiable address + + // it only makes sense to lookup a shareable default name if we don't have a place name + if (_placeName.isEmpty()) { + JSONCallbackParameters callbackParams; + + // no error callback handling + // in the case of an error we simply assume there is no default place name + callbackParams.jsonCallbackReceiver = this; + callbackParams.jsonCallbackMethod = "handleShareableNameAPIResponse"; + + DependencyManager::get()->sendRequest(GET_DOMAIN_ID.arg(uuidStringWithoutCurlyBraces(domainID)), + AccountManagerAuth::None, + QNetworkAccessManager::GetOperation, + callbackParams); + } +} + void AddressManager::addCurrentAddressToHistory(LookupTrigger trigger) { // if we're cold starting and this is called for the first address (from settings) we don't do anything @@ -750,6 +836,7 @@ void AddressManager::ifLocalSandboxRunningElse(std::function localSandbo QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest sandboxStatus(SANDBOX_STATUS_URL); + sandboxStatus.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); sandboxStatus.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); QNetworkReply* reply = networkAccessManager.get(sandboxStatus); diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index 23df176d8b..248a1ef435 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -60,6 +60,8 @@ public: QUrl currentAddress() const; QUrl currentFacingAddress() const; + QUrl currentShareableAddress() const; + QUrl currentFacingShareableAddress() const; QString currentPath(bool withOrientation = true) const; QString currentFacingPath() const; @@ -102,6 +104,8 @@ public slots: void copyAddress(); void copyPath(); + void lookupShareableNameForDomainID(const QUuid& domainID); + signals: void lookupResultsFinished(); void lookupResultIsOffline(); @@ -125,6 +129,8 @@ private slots: void handleAPIResponse(QNetworkReply& requestReply); void handleAPIError(QNetworkReply& errorReply); + void handleShareableNameAPIResponse(QNetworkReply& requestReply); + private: void goToAddressFromObject(const QVariantMap& addressMap, const QNetworkReply& reply); @@ -155,6 +161,8 @@ private: PositionGetter _positionGetter; OrientationGetter _orientationGetter; + QString _shareablePlaceName; + QStack _backStack; QStack _forwardStack; quint64 _lastBackPush = 0; diff --git a/libraries/networking/src/HTTPResourceRequest.cpp b/libraries/networking/src/HTTPResourceRequest.cpp index 11ab436933..392654a419 100644 --- a/libraries/networking/src/HTTPResourceRequest.cpp +++ b/libraries/networking/src/HTTPResourceRequest.cpp @@ -49,6 +49,7 @@ void HTTPResourceRequest::cleanupTimer() { void HTTPResourceRequest::doSend() { QNetworkRequest networkRequest(_url); + networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); if (_cacheEnabled) { diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 781cc00c1c..3a07ea8b54 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -539,6 +539,10 @@ void NodeList::processDomainServerList(QSharedPointer message) if (!_domainHandler.isConnected()) { _domainHandler.setUUID(domainUUID); _domainHandler.setIsConnected(true); + + // in case we didn't use a place name to get to this domain, + // give the address manager a chance to lookup a default one now + DependencyManager::get()->lookupShareableNameForDomainID(domainUUID); } else if (_domainHandler.getUUID() != domainUUID) { // Recieved packet from different domain. qWarning() << "IGNORING DomainList packet from" << domainUUID << "while connected to" << _domainHandler.getUUID(); diff --git a/libraries/networking/src/OAuthNetworkAccessManager.cpp b/libraries/networking/src/OAuthNetworkAccessManager.cpp index 92e7a2ff4f..15d5acbc67 100644 --- a/libraries/networking/src/OAuthNetworkAccessManager.cpp +++ b/libraries/networking/src/OAuthNetworkAccessManager.cpp @@ -37,6 +37,7 @@ QNetworkReply* OAuthNetworkAccessManager::createRequest(QNetworkAccessManager::O if (accountManager->hasValidAccessToken() && req.url().host() == NetworkingConstants::METAVERSE_SERVER_URL.host()) { QNetworkRequest authenticatedRequest(req); + authenticatedRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); authenticatedRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); authenticatedRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER, accountManager->getAccountInfo().getAccessToken().authorizationHeaderValue()); diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 6e1826bdd1..0f3d5885ff 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -47,12 +47,12 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityAdd: case PacketType::EntityEdit: case PacketType::EntityData: - return VERSION_WEB_ENTITIES_SUPPORT_DPI; + return VERSION_ENTITIES_ARROW_ACTION; case PacketType::AvatarIdentity: case PacketType::AvatarData: case PacketType::BulkAvatarData: case PacketType::KillAvatar: - return static_cast(AvatarMixerPacketVersion::SensorToWorldMat); + return static_cast(AvatarMixerPacketVersion::HandControllerJoints); case PacketType::ICEServerHeartbeat: return 18; // ICE Server Heartbeat signing case PacketType::AssetGetInfo: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 01b672a416..25500b984f 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -187,13 +187,15 @@ const PacketVersion VERSION_ENTITIES_PROPERLY_ENCODE_SHAPE_EDITS = 60; const PacketVersion VERSION_MODEL_ENTITIES_SUPPORT_STATIC_MESH = 61; const PacketVersion VERSION_MODEL_ENTITIES_SUPPORT_SIMPLE_HULLS = 62; const PacketVersion VERSION_WEB_ENTITIES_SUPPORT_DPI = 63; +const PacketVersion VERSION_ENTITIES_ARROW_ACTION = 64; enum class AvatarMixerPacketVersion : PacketVersion { TranslationSupport = 17, SoftAttachmentSupport, AvatarEntities, AbsoluteSixByteRotations, - SensorToWorldMat + SensorToWorldMat, + HandControllerJoints }; enum class DomainConnectRequestVersion : PacketVersion { diff --git a/libraries/networking/src/udt/PacketQueue.cpp b/libraries/networking/src/udt/PacketQueue.cpp index 3684e5ba07..bb20982ca4 100644 --- a/libraries/networking/src/udt/PacketQueue.cpp +++ b/libraries/networking/src/udt/PacketQueue.cpp @@ -32,25 +32,25 @@ PacketQueue::PacketPointer PacketQueue::takePacket() { if (isEmpty()) { return PacketPointer(); } - + // Find next non empty channel if (_channels[nextIndex()].empty()) { nextIndex(); } auto& channel = _channels[_currentIndex]; Q_ASSERT(!channel.empty()); - + // Take front packet auto packet = std::move(channel.front()); channel.pop_front(); - + // Remove now empty channel (Don't remove the main channel) if (channel.empty() && _currentIndex != 0) { channel.swap(_channels.back()); _channels.pop_back(); --_currentIndex; } - + return packet; } @@ -68,7 +68,7 @@ void PacketQueue::queuePacketList(PacketListPointer packetList) { if (packetList->isOrdered()) { packetList->preparePackets(getNextMessageNumber()); } - + LockGuard locker(_packetsLock); _channels.push_back(std::move(packetList->_packets)); } diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index b3ddc24914..a39100f8d1 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -11,6 +11,10 @@ #include "Socket.h" +#ifdef Q_OS_ANDROID +#include +#endif + #include #include @@ -30,10 +34,10 @@ Socket::Socket(QObject* parent) : _synTimer(new QTimer(this)) { connect(&_udpSocket, &QUdpSocket::readyRead, this, &Socket::readPendingDatagrams); - + // make sure our synchronization method is called every SYN interval connect(_synTimer, &QTimer::timeout, this, &Socket::rateControlSync); - + // start our timer for the synchronization time interval _synTimer->start(_synInterval); @@ -60,7 +64,7 @@ void Socket::bind(const QHostAddress& address, quint16 port) { void Socket::rebind() { quint16 oldPort = _udpSocket.localPort(); - + _udpSocket.close(); bind(QHostAddress::AnyIPv4, oldPort); } @@ -69,26 +73,26 @@ void Socket::setSystemBufferSizes() { for (int i = 0; i < 2; i++) { QAbstractSocket::SocketOption bufferOpt; QString bufferTypeString; - + int numBytes = 0; - + if (i == 0) { bufferOpt = QAbstractSocket::SendBufferSizeSocketOption; numBytes = udt::UDP_SEND_BUFFER_SIZE_BYTES; bufferTypeString = "send"; - + } else { bufferOpt = QAbstractSocket::ReceiveBufferSizeSocketOption; numBytes = udt::UDP_RECEIVE_BUFFER_SIZE_BYTES; bufferTypeString = "receive"; } - + int oldBufferSize = _udpSocket.socketOption(bufferOpt).toInt(); - + if (oldBufferSize < numBytes) { _udpSocket.setSocketOption(bufferOpt, QVariant(numBytes)); int newBufferSize = _udpSocket.socketOption(bufferOpt).toInt(); - + qCDebug(networking) << "Changed socket" << bufferTypeString << "buffer size from" << oldBufferSize << "to" << newBufferSize << "bytes"; } else { @@ -101,29 +105,29 @@ void Socket::setSystemBufferSizes() { qint64 Socket::writeBasePacket(const udt::BasePacket& packet, const HifiSockAddr &sockAddr) { // Since this is a base packet we have no way to know if this is reliable or not - we just fire it off - + // this should not be called with an instance of Packet Q_ASSERT_X(!dynamic_cast(&packet), "Socket::writeBasePacket", "Cannot send a Packet/NLPacket via writeBasePacket"); - + return writeDatagram(packet.getData(), packet.getDataSize(), sockAddr); } qint64 Socket::writePacket(const Packet& packet, const HifiSockAddr& sockAddr) { Q_ASSERT_X(!packet.isReliable(), "Socket::writePacket", "Cannot send a reliable packet unreliably"); - + // write the correct sequence number to the Packet here packet.writeSequenceNumber(++_unreliableSequenceNumbers[sockAddr]); - + return writeDatagram(packet.getData(), packet.getDataSize(), sockAddr); } qint64 Socket::writePacket(std::unique_ptr packet, const HifiSockAddr& sockAddr) { - + if (packet->isReliable()) { // hand this packet off to writeReliablePacket // because Qt can't invoke with the unique_ptr we have to release it here and re-construct in writeReliablePacket - + if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "writeReliablePacket", Qt::QueuedConnection, Q_ARG(Packet*, packet.release()), @@ -131,10 +135,10 @@ qint64 Socket::writePacket(std::unique_ptr packet, const HifiSockAddr& s } else { writeReliablePacket(packet.release(), sockAddr); } - + return 0; } - + return writePacket(*packet, sockAddr); } @@ -142,7 +146,7 @@ qint64 Socket::writePacketList(std::unique_ptr packetList, const Hif if (packetList->isReliable()) { // hand this packetList off to writeReliablePacketList // because Qt can't invoke with the unique_ptr we have to release it here and re-construct in writeReliablePacketList - + if (QThread::currentThread() != thread()) { auto ptr = packetList.release(); QMetaObject::invokeMethod(this, "writeReliablePacketList", Qt::AutoConnection, @@ -151,7 +155,7 @@ qint64 Socket::writePacketList(std::unique_ptr packetList, const Hif } else { writeReliablePacketList(packetList.release(), sockAddr); } - + return 0; } @@ -177,18 +181,18 @@ qint64 Socket::writeDatagram(const char* data, qint64 size, const HifiSockAddr& } qint64 Socket::writeDatagram(const QByteArray& datagram, const HifiSockAddr& sockAddr) { - + qint64 bytesWritten = _udpSocket.writeDatagram(datagram, sockAddr.getAddress(), sockAddr.getPort()); - + if (bytesWritten < 0) { // when saturating a link this isn't an uncommon message - suppress it so it doesn't bomb the debug static const QString WRITE_ERROR_REGEX = "Socket::writeDatagram QAbstractSocket::NetworkError - Unable to send a message"; static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex(WRITE_ERROR_REGEX); - + qCDebug(networking) << "Socket::writeDatagram" << _udpSocket.error() << "-" << qPrintable(_udpSocket.errorString()); } - + return bytesWritten; } @@ -199,17 +203,17 @@ Connection& Socket::findOrCreateConnection(const HifiSockAddr& sockAddr) { auto congestionControl = _ccFactory->create(); congestionControl->setMaxBandwidth(_maxBandwidth); auto connection = std::unique_ptr(new Connection(this, sockAddr, std::move(congestionControl))); - + // we queue the connection to cleanup connection in case it asks for it during its own rate control sync QObject::connect(connection.get(), &Connection::connectionInactive, this, &Socket::cleanupConnection); - + #ifdef UDT_CONNECTION_DEBUG qCDebug(networking) << "Creating new connection to" << sockAddr; #endif - + it = _connectionsHash.insert(it, std::make_pair(sockAddr, std::move(connection))); } - + return *it->second; } @@ -228,7 +232,7 @@ void Socket::clearConnections() { void Socket::cleanupConnection(HifiSockAddr sockAddr) { auto numErased = _connectionsHash.erase(sockAddr); - + if (numErased > 0) { #ifdef UDT_CONNECTION_DEBUG qCDebug(networking) << "Socket::cleanupConnection called for UDT connection to" << sockAddr; @@ -253,10 +257,10 @@ void Socket::readPendingDatagrams() { while ((packetSizeWithHeader = _udpSocket.pendingDatagramSize()) != -1) { // setup a HifiSockAddr to read into HifiSockAddr senderSockAddr; - + // setup a buffer to read the packet into auto buffer = std::unique_ptr(new char[packetSizeWithHeader]); - + // pull the datagram auto sizeRead = _udpSocket.readDatagram(buffer.get(), packetSizeWithHeader, senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer()); @@ -266,34 +270,34 @@ void Socket::readPendingDatagrams() { // on windows even if there's not a packet available) continue; } - + auto it = _unfilteredHandlers.find(senderSockAddr); - + if (it != _unfilteredHandlers.end()) { // we have a registered unfiltered handler for this HifiSockAddr - call that and return if (it->second) { auto basePacket = BasePacket::fromReceivedPacket(std::move(buffer), packetSizeWithHeader, senderSockAddr); it->second(std::move(basePacket)); } - + continue; } - + // check if this was a control packet or a data packet bool isControlPacket = *reinterpret_cast(buffer.get()) & CONTROL_BIT_MASK; - + if (isControlPacket) { // setup a control packet from the data we just read auto controlPacket = ControlPacket::fromReceivedPacket(std::move(buffer), packetSizeWithHeader, senderSockAddr); - + // move this control packet to the matching connection auto& connection = findOrCreateConnection(senderSockAddr); connection.processControl(move(controlPacket)); - + } else { // setup a Packet from the data we just read auto packet = Packet::fromReceivedPacket(std::move(buffer), packetSizeWithHeader, senderSockAddr); - + // call our verification operator to see if this packet is verified if (!_packetFilterOperator || _packetFilterOperator(*packet)) { if (packet->isReliable()) { @@ -328,27 +332,27 @@ void Socket::connectToSendSignal(const HifiSockAddr& destinationAddr, QObject* r } void Socket::rateControlSync() { - + // enumerate our list of connections and ask each of them to send off periodic ACK packet for rate control - + // the way we do this is a little funny looking - we need to avoid the case where we call sync and // (because of our Qt direct connection to the Connection's signal that it has been deactivated) // an iterator on _connectionsHash would be invalidated by our own call to cleanupConnection - + // collect the sockets for all connections in a vector - + std::vector sockAddrVector; sockAddrVector.reserve(_connectionsHash.size()); - + for (auto& connection : _connectionsHash) { sockAddrVector.emplace_back(connection.first); } - + // enumerate that vector of HifiSockAddr objects for (auto& sockAddr : sockAddrVector) { // pull out the respective connection via a quick find on the unordered_map auto it = _connectionsHash.find(sockAddr); - + if (it != _connectionsHash.end()) { // if the connection is erased while calling sync since we are re-using the iterator that was invalidated // we're good to go @@ -356,7 +360,7 @@ void Socket::rateControlSync() { connection->sync(); } } - + if (_synTimer->interval() != _synInterval) { // if the _synTimer interval doesn't match the current _synInterval (changes when the CC factory is changed) // then restart it now with the right interval @@ -367,7 +371,7 @@ void Socket::rateControlSync() { void Socket::setCongestionControlFactory(std::unique_ptr ccFactory) { // swap the current unique_ptr for the new factory _ccFactory.swap(ccFactory); - + // update the _synInterval to the value from the factory _synInterval = _ccFactory->synInterval(); } @@ -402,10 +406,10 @@ Socket::StatsVector Socket::sampleStatsForAllConnections() { } -std::vector Socket::getConnectionSockAddrs() { +std::vector Socket::getConnectionSockAddrs() { std::vector addr; addr.reserve(_connectionsHash.size()); - + for (const auto& connectionPair : _connectionsHash) { addr.push_back(connectionPair.first); } diff --git a/libraries/physics/src/ObjectActionTravelOriented.cpp b/libraries/physics/src/ObjectActionTravelOriented.cpp new file mode 100644 index 0000000000..18d09d21d9 --- /dev/null +++ b/libraries/physics/src/ObjectActionTravelOriented.cpp @@ -0,0 +1,206 @@ +// +// ObjectActionTravelOriented.cpp +// libraries/physics/src +// +// Created by Seth Alves 2015-6-5 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +#include "QVariantGLM.h" +#include "ObjectActionTravelOriented.h" + +const uint16_t ObjectActionTravelOriented::actionVersion = 1; + + +ObjectActionTravelOriented::ObjectActionTravelOriented(const QUuid& id, EntityItemPointer ownerEntity) : + ObjectAction(ACTION_TYPE_TRAVEL_ORIENTED, id, ownerEntity) { + #if WANT_DEBUG + qDebug() << "ObjectActionTravelOriented::ObjectActionTravelOriented"; + #endif +} + +ObjectActionTravelOriented::~ObjectActionTravelOriented() { + #if WANT_DEBUG + qDebug() << "ObjectActionTravelOriented::~ObjectActionTravelOriented"; + #endif +} + +void ObjectActionTravelOriented::updateActionWorker(btScalar deltaTimeStep) { + withReadLock([&] { + auto ownerEntity = _ownerEntity.lock(); + if (!ownerEntity) { + return; + } + void* physicsInfo = ownerEntity->getPhysicsInfo(); + if (!physicsInfo) { + return; + } + ObjectMotionState* motionState = static_cast(physicsInfo); + btRigidBody* rigidBody = motionState->getRigidBody(); + if (!rigidBody) { + qDebug() << "ObjectActionTravelOriented::updateActionWorker no rigidBody"; + return; + } + const float MAX_TIMESCALE = 600.0f; // 10 min is a long time + if (_angularTimeScale > MAX_TIMESCALE) { + return; + } + + // find normalized velocity + glm::vec3 velocity = bulletToGLM(rigidBody->getLinearVelocity()); + float speed = glm::length(velocity); + const float TRAVEL_ORIENTED_TOO_SLOW = 0.001f; // meters / second + if (speed < TRAVEL_ORIENTED_TOO_SLOW) { + return; + } + glm::vec3 direction = glm::normalize(velocity); + + // find current angle of "forward" + btQuaternion bodyRotation = rigidBody->getOrientation(); + glm::quat orientation = bulletToGLM(bodyRotation); + glm::vec3 forwardInWorldFrame = glm::normalize(orientation * _forward); + + // find the rotation that would line up direction and forward + glm::quat neededRotation = glm::rotation(forwardInWorldFrame, direction); + glm::quat rotationalTarget = neededRotation * orientation; + + btVector3 targetAngularVelocity(0.0f, 0.0f, 0.0f); + + auto alignmentDot = bodyRotation.dot(glmToBullet(rotationalTarget)); + const float ALMOST_ONE = 0.99999f; + if (glm::abs(alignmentDot) < ALMOST_ONE) { + btQuaternion target = glmToBullet(rotationalTarget); + if (alignmentDot < 0.0f) { + target = -target; + } + // if dQ is the incremental rotation that gets an object from Q0 to Q1 then: + // + // Q1 = dQ * Q0 + // + // solving for dQ gives: + // + // dQ = Q1 * Q0^ + btQuaternion deltaQ = target * bodyRotation.inverse(); + float speed = deltaQ.getAngle() / _angularTimeScale; + targetAngularVelocity = speed * deltaQ.getAxis(); + if (speed > rigidBody->getAngularSleepingThreshold()) { + rigidBody->activate(); + } + } + // this action is aggresively critically damped and defeats the current velocity + rigidBody->setAngularVelocity(targetAngularVelocity); + }); +} + +const float MIN_TIMESCALE = 0.1f; + + +bool ObjectActionTravelOriented::updateArguments(QVariantMap arguments) { + glm::vec3 forward; + float angularTimeScale; + + bool needUpdate = false; + bool somethingChanged = ObjectAction::updateArguments(arguments); + withReadLock([&]{ + bool ok = true; + forward = EntityActionInterface::extractVec3Argument("travel oriented action", arguments, "forward", ok, true); + if (!ok) { + forward = _forward; + } + ok = true; + angularTimeScale = + EntityActionInterface::extractFloatArgument("travel oriented action", arguments, "angularTimeScale", ok, false); + if (!ok) { + angularTimeScale = _angularTimeScale; + } + + if (somethingChanged || + forward != _forward || + angularTimeScale != _angularTimeScale) { + // something changed + needUpdate = true; + } + }); + + if (needUpdate) { + withWriteLock([&] { + _forward = forward; + _angularTimeScale = glm::max(MIN_TIMESCALE, glm::abs(angularTimeScale)); + _active = (_forward != glm::vec3()); + + auto ownerEntity = _ownerEntity.lock(); + if (ownerEntity) { + ownerEntity->setActionDataDirty(true); + ownerEntity->setActionDataNeedsTransmit(true); + } + }); + activateBody(); + } + + return true; +} + +QVariantMap ObjectActionTravelOriented::getArguments() { + QVariantMap arguments = ObjectAction::getArguments(); + withReadLock([&] { + arguments["forward"] = glmToQMap(_forward); + arguments["angularTimeScale"] = _angularTimeScale; + }); + return arguments; +} + +QByteArray ObjectActionTravelOriented::serialize() const { + QByteArray serializedActionArguments; + QDataStream dataStream(&serializedActionArguments, QIODevice::WriteOnly); + + dataStream << ACTION_TYPE_TRAVEL_ORIENTED; + dataStream << getID(); + dataStream << ObjectActionTravelOriented::actionVersion; + + withReadLock([&] { + dataStream << _forward; + dataStream << _angularTimeScale; + + dataStream << localTimeToServerTime(_expires); + dataStream << _tag; + }); + + return serializedActionArguments; +} + +void ObjectActionTravelOriented::deserialize(QByteArray serializedArguments) { + QDataStream dataStream(serializedArguments); + + EntityActionType type; + dataStream >> type; + assert(type == getType()); + + QUuid id; + dataStream >> id; + assert(id == getID()); + + uint16_t serializationVersion; + dataStream >> serializationVersion; + if (serializationVersion != ObjectActionTravelOriented::actionVersion) { + assert(false); + return; + } + + withWriteLock([&] { + dataStream >> _forward; + dataStream >> _angularTimeScale; + + quint64 serverExpires; + dataStream >> serverExpires; + _expires = serverTimeToLocalTime(serverExpires); + + dataStream >> _tag; + + _active = (_forward != glm::vec3()); + }); +} diff --git a/libraries/physics/src/ObjectActionTravelOriented.h b/libraries/physics/src/ObjectActionTravelOriented.h new file mode 100644 index 0000000000..66a425b409 --- /dev/null +++ b/libraries/physics/src/ObjectActionTravelOriented.h @@ -0,0 +1,39 @@ +// +// ObjectActionTravelOriented.h +// libraries/physics/src +// +// Created by Seth Alves 2016-8-28 +// Copyright 2015 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_ObjectActionTravelOriented_h +#define hifi_ObjectActionTravelOriented_h + +#include "ObjectAction.h" + +class ObjectActionTravelOriented : public ObjectAction { +public: + ObjectActionTravelOriented(const QUuid& id, EntityItemPointer ownerEntity); + virtual ~ObjectActionTravelOriented(); + + virtual bool updateArguments(QVariantMap arguments) override; + virtual QVariantMap getArguments() override; + + virtual void updateActionWorker(float deltaTimeStep) override; + + virtual QByteArray serialize() const override; + virtual void deserialize(QByteArray serializedArguments) override; + +protected: + static const uint16_t actionVersion; + + glm::vec3 _forward { glm::vec3() }; // the vector in object space that should point in the direction of travel + float _angularTimeScale { 0.1f }; + + glm::vec3 _angularVelocityTarget; +}; + +#endif // hifi_ObjectActionTravelOriented_h diff --git a/libraries/physics/src/ShapeFactory.cpp b/libraries/physics/src/ShapeFactory.cpp index a67e0053da..9b9ee0e299 100644 --- a/libraries/physics/src/ShapeFactory.cpp +++ b/libraries/physics/src/ShapeFactory.cpp @@ -187,7 +187,7 @@ btTriangleIndexVertexArray* createStaticMeshArray(const ShapeInfo& info) { btIndexedMesh mesh; const int32_t VERTICES_PER_TRIANGLE = 3; mesh.m_numTriangles = numIndices / VERTICES_PER_TRIANGLE; - if (numIndices < INT16_MAX) { + if (numIndices < std::numeric_limits::max()) { // small number of points so we can use 16-bit indices mesh.m_triangleIndexBase = new unsigned char[sizeof(int16_t) * (size_t)numIndices]; mesh.m_indexType = PHY_SHORT; @@ -211,7 +211,7 @@ btTriangleIndexVertexArray* createStaticMeshArray(const ShapeInfo& info) { vertexData[j + 1] = point.y; vertexData[j + 2] = point.z; } - if (numIndices < INT16_MAX) { + if (numIndices < std::numeric_limits::max()) { int16_t* indices = static_cast((void*)(mesh.m_triangleIndexBase)); for (int32_t i = 0; i < numIndices; ++i) { indices[i] = (int16_t)triangleIndices[i]; diff --git a/libraries/plugins/CMakeLists.txt b/libraries/plugins/CMakeLists.txt index e7798e9f3d..6067c0fbed 100644 --- a/libraries/plugins/CMakeLists.txt +++ b/libraries/plugins/CMakeLists.txt @@ -1,4 +1,4 @@ set(TARGET_NAME plugins) setup_hifi_library(OpenGL) -link_hifi_libraries(shared) +link_hifi_libraries(shared networking) include_hifi_library_headers(gpu) diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index 288cee3223..eac08716a1 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -128,6 +128,7 @@ public: Present = QEvent::User + 1 }; + virtual int getRequiredThreadCount() const { return 0; } virtual bool isHmd() const { return false; } virtual int getHmdScreen() const { return -1; } /// By default, all HMDs are stereo @@ -178,6 +179,8 @@ public: virtual bool beginFrameRender(uint32_t frameIndex) { return true; } virtual float devicePixelRatio() { return 1.0f; } + // Rate at which we render frames + virtual float renderRate() const { return -1.0f; } // Rate at which we present to the display device virtual float presentRate() const { return -1.0f; } // Rate at which new frames are being presented to the display device diff --git a/libraries/plugins/src/plugins/PluginManager.cpp b/libraries/plugins/src/plugins/PluginManager.cpp index 0b4afe1be0..2e60eae678 100644 --- a/libraries/plugins/src/plugins/PluginManager.cpp +++ b/libraries/plugins/src/plugins/PluginManager.cpp @@ -115,10 +115,42 @@ const LoaderList& getLoadedPlugins() { PluginManager::PluginManager() { } +extern CodecPluginList getCodecPlugins(); + +const CodecPluginList& PluginManager::getCodecPlugins() { + static CodecPluginList codecPlugins; + static std::once_flag once; + std::call_once(once, [&] { + //codecPlugins = ::getCodecPlugins(); + + // Now grab the dynamic plugins + for (auto loader : getLoadedPlugins()) { + CodecProvider* codecProvider = qobject_cast(loader->instance()); + if (codecProvider) { + for (auto codecPlugin : codecProvider->getCodecPlugins()) { + if (codecPlugin->isSupported()) { + codecPlugins.push_back(codecPlugin); + } + } + } + } + + for (auto plugin : codecPlugins) { + plugin->setContainer(_container); + plugin->init(); + + qDebug() << "init codec:" << plugin->getName(); + } + }); + return codecPlugins; +} + +#ifndef Q_OS_ANDROID + // TODO migrate to a DLL model where plugins are discovered and loaded at runtime by the PluginManager class extern DisplayPluginList getDisplayPlugins(); extern InputPluginList getInputPlugins(); -extern CodecPluginList getCodecPlugins(); + extern void saveInputPluginSettings(const InputPluginList& plugins); static DisplayPluginList displayPlugins; @@ -137,6 +169,7 @@ const DisplayPluginList& PluginManager::getDisplayPlugins() { // Grab the built in plugins displayPlugins = ::getDisplayPlugins(); + // Now grab the dynamic plugins for (auto loader : getLoadedPlugins()) { DisplayProvider* displayProvider = qobject_cast(loader->instance()); @@ -204,35 +237,6 @@ const InputPluginList& PluginManager::getInputPlugins() { return inputPlugins; } -const CodecPluginList& PluginManager::getCodecPlugins() { - static CodecPluginList codecPlugins; - static std::once_flag once; - std::call_once(once, [&] { - //codecPlugins = ::getCodecPlugins(); - - // Now grab the dynamic plugins - for (auto loader : getLoadedPlugins()) { - CodecProvider* codecProvider = qobject_cast(loader->instance()); - if (codecProvider) { - for (auto codecPlugin : codecProvider->getCodecPlugins()) { - if (codecPlugin->isSupported()) { - codecPlugins.push_back(codecPlugin); - } - } - } - } - - for (auto plugin : codecPlugins) { - plugin->setContainer(_container); - plugin->init(); - - qDebug() << "init codec:" << plugin->getName(); - } - }); - return codecPlugins; -} - - void PluginManager::setPreferredDisplayPlugins(const QStringList& displays) { preferredDisplayPlugins = displays; } @@ -270,3 +274,5 @@ void PluginManager::disableInputs(const QStringList& inputs) { void PluginManager::saveSettings() { saveInputPluginSettings(getInputPlugins()); } + +#endif diff --git a/libraries/procedural/src/procedural/Procedural.cpp b/libraries/procedural/src/procedural/Procedural.cpp index 97bfcf4fe5..1c5ba40891 100644 --- a/libraries/procedural/src/procedural/Procedural.cpp +++ b/libraries/procedural/src/procedural/Procedural.cpp @@ -100,7 +100,9 @@ bool Procedural::parseVersion(const QJsonValue& version) { return (_version == 1 || _version == 2); } -bool Procedural::parseUrl(const QUrl& shaderUrl) { +bool Procedural::parseShader(const QUrl& shaderPath) { + auto shaderUrl = ResourceManager::normalizeURL(shaderPath); + if (!shaderUrl.isValid()) { if (!shaderUrl.isEmpty()) { qWarning() << "Invalid shader URL: " << shaderUrl; @@ -168,7 +170,6 @@ void Procedural::parse(const QJsonObject& proceduralData) { auto version = proceduralData[VERSION_KEY]; auto shaderUrl = proceduralData[URL_KEY].toString(); - shaderUrl = ResourceManager::normalizeURL(shaderUrl); auto uniforms = proceduralData[UNIFORMS_KEY].toObject(); auto channels = proceduralData[CHANNELS_KEY].toArray(); @@ -176,7 +177,7 @@ void Procedural::parse(const QJsonObject& proceduralData) { // Run through parsing regardless of validity to clear old cached resources isValid = parseVersion(version) && isValid; - isValid = parseUrl(shaderUrl) && isValid; + isValid = parseShader(shaderUrl) && isValid; isValid = parseUniforms(uniforms) && isValid; isValid = parseTextures(channels) && isValid; @@ -221,6 +222,7 @@ bool Procedural::ready() { _hasStartedFade = true; _isFading = true; } + return true; } diff --git a/libraries/procedural/src/procedural/Procedural.h b/libraries/procedural/src/procedural/Procedural.h index c128da0be0..ac71174727 100644 --- a/libraries/procedural/src/procedural/Procedural.h +++ b/libraries/procedural/src/procedural/Procedural.h @@ -107,7 +107,7 @@ private: // This should only be called from the render thread, as it shares data with Procedural::prepare void parse(const QJsonObject&); bool parseVersion(const QJsonValue& version); - bool parseUrl(const QUrl& url); + bool parseShader(const QUrl& shaderPath); bool parseUniforms(const QJsonObject& uniforms); bool parseTextures(const QJsonArray& channels); diff --git a/libraries/recording/src/recording/Frame.h b/libraries/recording/src/recording/Frame.h index 6c407063e3..b024fabc5d 100644 --- a/libraries/recording/src/recording/Frame.h +++ b/libraries/recording/src/recording/Frame.h @@ -13,7 +13,12 @@ #include "Forward.h" #include + +#ifdef Q_OS_WIN #include +#else +#include +#endif #include @@ -22,7 +27,13 @@ namespace recording { struct FrameHeader { using Time = uint32_t; +// until we use a version of visual studio that has constexpr support, we can't use numeric_limits at compile time +#ifdef Q_OS_WIN static const Time INVALID_TIME = UINT32_MAX; +#else + static const Time INVALID_TIME = std::numeric_limits::max(); +#endif + static const FrameType TYPE_INVALID = 0xFFFF; static const FrameType TYPE_HEADER = 0x0; @@ -51,7 +62,7 @@ public: QByteArray data; Frame() {} - Frame(FrameType type, float timeOffset, const QByteArray& data) + Frame(FrameType type, float timeOffset, const QByteArray& data) : FrameHeader(type, timeOffset), data(data) { } static FrameType registerFrameType(const QString& frameTypeName); diff --git a/libraries/render-utils/CMakeLists.txt b/libraries/render-utils/CMakeLists.txt index 7b272f7b7d..115f4bd83e 100644 --- a/libraries/render-utils/CMakeLists.txt +++ b/libraries/render-utils/CMakeLists.txt @@ -3,7 +3,9 @@ AUTOSCRIBE_SHADER_LIB(gpu model render) # pull in the resources.qrc file qt5_add_resources(QT_RESOURCES_FILE "${CMAKE_CURRENT_SOURCE_DIR}/res/fonts/fonts.qrc") setup_hifi_library(Widgets OpenGL Network Qml Quick Script) -link_hifi_libraries(shared gpu model model-networking render animation fbx) +link_hifi_libraries(shared gpu model model-networking render animation fbx entities) -target_nsight() -target_oglplus() +if (NOT ANDROID) + target_nsight() + target_oglplus() +endif () diff --git a/libraries/render-utils/src/AmbientOcclusionEffect.cpp b/libraries/render-utils/src/AmbientOcclusionEffect.cpp index 86223e9877..3bf887e1b6 100644 --- a/libraries/render-utils/src/AmbientOcclusionEffect.cpp +++ b/libraries/render-utils/src/AmbientOcclusionEffect.cpp @@ -432,7 +432,8 @@ void AmbientOcclusionEffect::run(const render::SceneContextPointer& sceneContext }); // Update the timer - std::static_pointer_cast(renderContext->jobConfig)->gpuTime = _gpuTimer.getAverage(); + auto config = std::static_pointer_cast(renderContext->jobConfig); + config->setGPUBatchRunTime(_gpuTimer.getGPUAverage(), _gpuTimer.getBatchAverage()); } diff --git a/libraries/render-utils/src/AmbientOcclusionEffect.h b/libraries/render-utils/src/AmbientOcclusionEffect.h index 8bb4600d3c..1a828cb2c0 100644 --- a/libraries/render-utils/src/AmbientOcclusionEffect.h +++ b/libraries/render-utils/src/AmbientOcclusionEffect.h @@ -53,7 +53,7 @@ protected: using AmbientOcclusionFramebufferPointer = std::shared_ptr; -class AmbientOcclusionEffectConfig : public render::Job::Config::Persistent { +class AmbientOcclusionEffectConfig : public render::GPUJobConfig::Persistent { Q_OBJECT Q_PROPERTY(bool enabled MEMBER enabled NOTIFY dirty) Q_PROPERTY(bool ditheringEnabled MEMBER ditheringEnabled NOTIFY dirty) @@ -68,9 +68,9 @@ class AmbientOcclusionEffectConfig : public render::Job::Config::Persistent { Q_PROPERTY(int numSamples MEMBER numSamples WRITE setNumSamples) Q_PROPERTY(int resolutionLevel MEMBER resolutionLevel WRITE setResolutionLevel) Q_PROPERTY(int blurRadius MEMBER blurRadius WRITE setBlurRadius) - Q_PROPERTY(double gpuTime READ getGpuTime) + public: - AmbientOcclusionEffectConfig() : render::Job::Config::Persistent("Ambient Occlusion", false) {} + AmbientOcclusionEffectConfig() : render::GPUJobConfig::Persistent("Ambient Occlusion", false) {} const int MAX_RESOLUTION_LEVEL = 4; const int MAX_BLUR_RADIUS = 6; @@ -84,7 +84,6 @@ public: void setNumSamples(int samples) { numSamples = std::max(1.0f, (float)samples); emit dirty(); } void setResolutionLevel(int level) { resolutionLevel = std::max(0, std::min(level, MAX_RESOLUTION_LEVEL)); emit dirty(); } void setBlurRadius(int radius) { blurRadius = std::max(0, std::min(MAX_BLUR_RADIUS, radius)); emit dirty(); } - double getGpuTime() { return gpuTime; } float radius{ 0.5f }; float perspectiveScale{ 1.0f }; @@ -99,7 +98,6 @@ public: bool ditheringEnabled{ true }; // randomize the distribution of taps per pixel, should always be true bool borderingEnabled{ true }; // avoid evaluating information from non existing pixels out of the frame, should always be true bool fetchMipsEnabled{ true }; // fetch taps in sub mips to otpimize cache, should always be true - double gpuTime{ 0.0 }; signals: void dirty(); diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index 5dd4c0a232..8e83f737ea 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -714,5 +714,5 @@ void RenderDeferred::run(const SceneContextPointer& sceneContext, const RenderCo }); auto config = std::static_pointer_cast(renderContext->jobConfig); - config->gpuTime = _gpuTimer.getAverage(); + config->setGPUBatchRunTime(_gpuTimer.getGPUAverage(), _gpuTimer.getBatchAverage()); } diff --git a/libraries/render-utils/src/DeferredLightingEffect.h b/libraries/render-utils/src/DeferredLightingEffect.h index 466c58c36e..61bac2f063 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.h +++ b/libraries/render-utils/src/DeferredLightingEffect.h @@ -161,21 +161,7 @@ public: void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext); }; - -class RenderDeferredConfig : public render::Job::Config { - Q_OBJECT - Q_PROPERTY(double gpuTime READ getGpuTime) -public: - RenderDeferredConfig() : render::Job::Config(true) {} - - double getGpuTime() { return gpuTime; } - - double gpuTime{ 0.0 }; - -signals: - void dirty(); -}; - +using RenderDeferredConfig = render::GPUJobConfig; class RenderDeferred { public: diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 42dd41c739..3e891bffe2 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -71,8 +71,37 @@ void MeshPartPayload::updateTransform(const Transform& transform, const Transfor void MeshPartPayload::updateMaterial(model::MaterialPointer drawMaterial) { _drawMaterial = drawMaterial; + calculateMaterialSize(); } +bool MeshPartPayload::calculateMaterialSize() { + bool allTextures = true; // assume we got this... + _materialTextureSize = 0; + auto textureMaps = _drawMaterial->getTextureMaps(); + for (auto const &textureMapItem : textureMaps) { + auto textureMap = textureMapItem.second; + if (textureMap) { + auto textureSoure = textureMap->getTextureSource(); + if (textureSoure) { + auto texture = textureSoure->getGPUTexture(); + if (texture) { + //auto storedSize = texture->getStoredSize(); + auto size = texture->getSize(); + _materialTextureSize += size; + } else { + allTextures = false; + } + } else { + allTextures = false; + } + } else { + allTextures = false; + } + } + return allTextures; +} + + ItemKey MeshPartPayload::getKey() const { ItemKey::Builder builder; builder.withTypeShape(); @@ -347,8 +376,8 @@ void ModelMeshPartPayload::initCache() { auto networkMaterial = _model->getGeometry()->getShapeMaterial(_shapeID); if (networkMaterial) { _drawMaterial = networkMaterial; - }; - + calculateMaterialSize(); + } } void ModelMeshPartPayload::notifyLocationChanged() { diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index a934863846..f7ea77beba 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -64,6 +64,13 @@ public: mutable model::Box _worldBound; bool _hasColorAttrib = false; + + size_t getVerticesCount() const { return _drawMesh ? _drawMesh->getNumVertices() : 0; } + size_t getMaterialTextureSize() { return _materialTextureSize; } + bool calculateMaterialSize(); + +protected: + size_t _materialTextureSize { 0 }; }; namespace render { diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 3a7308c277..ebf5cb4327 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -161,6 +161,23 @@ void Model::setOffset(const glm::vec3& offset) { _snappedToRegistrationPoint = false; } +size_t Model::getRenderInfoTextureSize() { + if (!_hasCalculatedTextureSize && isLoaded() && getGeometry()->areTexturesLoaded()) { + size_t textureSize = 0; + bool allTexturesLoaded = true; + foreach(auto renderItem, _modelMeshRenderItemsSet) { + auto meshPart = renderItem.get(); + bool allTexturesForThisMesh = meshPart->calculateMaterialSize(); + allTexturesLoaded = allTexturesLoaded & allTexturesForThisMesh; + textureSize += meshPart->getMaterialTextureSize(); + } + _renderInfoTextureSize = textureSize; + _hasCalculatedTextureSize = allTexturesLoaded; // only do this once + } + return _renderInfoTextureSize; +} + + void Model::updateRenderItems() { if (!_addedToScene) { return; @@ -615,16 +632,26 @@ bool Model::addToScene(std::shared_ptr scene, } } else { if (_modelMeshRenderItems.empty()) { - foreach (auto renderItem, _modelMeshRenderItemsSet) { + + bool hasTransparent = false; + size_t verticesCount = 0; + foreach(auto renderItem, _modelMeshRenderItemsSet) { auto item = scene->allocateID(); auto renderPayload = std::make_shared(renderItem); if (statusGetters.size()) { renderPayload->addStatusGetters(statusGetters); } pendingChanges.resetItem(item, renderPayload); + + hasTransparent = hasTransparent || renderItem.get()->getShapeKey().isTranslucent(); + verticesCount += renderItem.get()->getVerticesCount(); _modelMeshRenderItems.insert(item, renderPayload); } somethingAdded = !_modelMeshRenderItems.empty(); + + _renderInfoVertexCount = verticesCount; + _renderInfoDrawCalls = _modelMeshRenderItems.count(); + _renderInfoHasTransparent = hasTransparent; } } @@ -650,6 +677,11 @@ void Model::removeFromScene(std::shared_ptr scene, render::Pendin _collisionRenderItems.clear(); _collisionRenderItemsSet.clear(); _addedToScene = false; + + _renderInfoVertexCount = 0; + _renderInfoDrawCalls = 0; + _renderInfoTextureSize = 0; + _renderInfoHasTransparent = false; } void Model::renderDebugMeshBoxes(gpu::Batch& batch) { @@ -1332,13 +1364,21 @@ bool Model::initWhenReady(render::ScenePointer scene) { } addedPendingChanges = !_collisionRenderItems.empty(); } else { + bool hasTransparent = false; + size_t verticesCount = 0; foreach (auto renderItem, _modelMeshRenderItemsSet) { auto item = scene->allocateID(); auto renderPayload = std::make_shared(renderItem); + + hasTransparent = hasTransparent || renderItem.get()->getShapeKey().isTranslucent(); + verticesCount += renderItem.get()->getVerticesCount(); _modelMeshRenderItems.insert(item, renderPayload); pendingChanges.resetItem(item, renderPayload); } addedPendingChanges = !_modelMeshRenderItems.empty(); + _renderInfoVertexCount = verticesCount; + _renderInfoDrawCalls = _modelMeshRenderItems.count(); + _renderInfoHasTransparent = hasTransparent; } _addedToScene = addedPendingChanges; if (addedPendingChanges) { diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 7a193b1d47..1b16892296 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -232,6 +232,12 @@ public: void setLoadingPriority(float priority) { _loadingPriority = priority; } + size_t getRenderInfoVertexCount() const { return _renderInfoVertexCount; } + int getRenderInfoTextureCount() const { return _renderInfoTextureCount; } + size_t getRenderInfoTextureSize(); + int getRenderInfoDrawCalls() const { return _renderInfoDrawCalls; } + bool getRenderInfoHasTransparent() const { return _renderInfoHasTransparent; } + public slots: void loadURLFinished(bool success); @@ -400,6 +406,13 @@ protected: bool _renderItemsNeedUpdate { false }; + size_t _renderInfoVertexCount { 0 }; + int _renderInfoTextureCount { 0 }; + size_t _renderInfoTextureSize { 0 }; + bool _hasCalculatedTextureSize { false }; + int _renderInfoDrawCalls { 0 }; + int _renderInfoHasTransparent { false }; + private: float _loadingPriority { 0.0f }; diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index cedc2ef45e..1e63cba8da 100755 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -245,7 +245,7 @@ void EndGPURangeTimer::run(const render::SceneContextPointer& sceneContext, cons }); auto config = std::static_pointer_cast(renderContext->jobConfig); - config->gpuTime = timer->getAverage(); + config->setGPUBatchRunTime(timer->getGPUAverage(), timer->getBatchAverage()); } diff --git a/libraries/render-utils/src/RenderDeferredTask.h b/libraries/render-utils/src/RenderDeferredTask.h index 749cc09edc..e379e42445 100755 --- a/libraries/render-utils/src/RenderDeferredTask.h +++ b/libraries/render-utils/src/RenderDeferredTask.h @@ -29,18 +29,8 @@ protected: gpu::RangeTimerPointer _gpuTimer; }; +using GPURangeTimerConfig = render::GPUJobConfig; -class GPURangeTimerConfig : public render::Job::Config { - Q_OBJECT - Q_PROPERTY(double gpuTime READ getGpuTime) -public: - double getGpuTime() { return gpuTime; } - -protected: - friend class EndGPURangeTimer; - double gpuTime; -}; - class EndGPURangeTimer { public: using Config = GPURangeTimerConfig; @@ -143,16 +133,7 @@ protected: gpu::PipelinePointer getOpaquePipeline(); }; -class DrawBackgroundDeferredConfig : public render::Job::Config { - Q_OBJECT - Q_PROPERTY(double gpuTime READ getGpuTime) -public: - double getGpuTime() { return gpuTime; } - -protected: - friend class DrawBackgroundDeferred; - double gpuTime; -}; +using DrawBackgroundDeferredConfig = render::GPUJobConfig; class DrawBackgroundDeferred { public: @@ -211,16 +192,7 @@ public: void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const gpu::FramebufferPointer& srcFramebuffer); }; -class RenderDeferredTaskConfig : public render::Task::Config { - Q_OBJECT - Q_PROPERTY(double gpuTime READ getGpuTime) -public: - double getGpuTime() { return gpuTime; } - -protected: - friend class RenderDeferredTask; - double gpuTime; -}; +using RenderDeferredTaskConfig = render::GPUTaskConfig; class RenderDeferredTask : public render::Task { public: diff --git a/libraries/render-utils/src/SurfaceGeometryPass.cpp b/libraries/render-utils/src/SurfaceGeometryPass.cpp index 7a5a34c756..1957f8456a 100644 --- a/libraries/render-utils/src/SurfaceGeometryPass.cpp +++ b/libraries/render-utils/src/SurfaceGeometryPass.cpp @@ -201,7 +201,7 @@ void LinearDepthPass::run(const render::SceneContextPointer& sceneContext, const }); auto config = std::static_pointer_cast(renderContext->jobConfig); - config->gpuTime = _gpuTimer.getAverage(); + config->setGPUBatchRunTime(_gpuTimer.getGPUAverage(), _gpuTimer.getBatchAverage()); } @@ -524,7 +524,7 @@ void SurfaceGeometryPass::run(const render::SceneContextPointer& sceneContext, c auto config = std::static_pointer_cast(renderContext->jobConfig); - config->gpuTime = _gpuTimer.getAverage(); + config->setGPUBatchRunTime(_gpuTimer.getGPUAverage(), _gpuTimer.getBatchAverage()); } diff --git a/libraries/render-utils/src/SurfaceGeometryPass.h b/libraries/render-utils/src/SurfaceGeometryPass.h index 24f0c56cdd..3bc6b45c7c 100644 --- a/libraries/render-utils/src/SurfaceGeometryPass.h +++ b/libraries/render-utils/src/SurfaceGeometryPass.h @@ -62,20 +62,7 @@ protected: using LinearDepthFramebufferPointer = std::shared_ptr; - -class LinearDepthPassConfig : public render::Job::Config { - Q_OBJECT - Q_PROPERTY(double gpuTime READ getGpuTime) -public: - LinearDepthPassConfig() : render::Job::Config(true) {} - - double getGpuTime() { return gpuTime; } - - double gpuTime{ 0.0 }; - -signals: - void dirty(); -}; +using LinearDepthPassConfig = render::GPUJobConfig; class LinearDepthPass { public: @@ -148,7 +135,7 @@ protected: using SurfaceGeometryFramebufferPointer = std::shared_ptr; -class SurfaceGeometryPassConfig : public render::Job::Config { +class SurfaceGeometryPassConfig : public render::GPUJobConfig { Q_OBJECT Q_PROPERTY(float depthThreshold MEMBER depthThreshold NOTIFY dirty) Q_PROPERTY(float basisScale MEMBER basisScale NOTIFY dirty) @@ -158,9 +145,8 @@ class SurfaceGeometryPassConfig : public render::Job::Config { Q_PROPERTY(float diffuseFilterScale MEMBER diffuseFilterScale NOTIFY dirty) Q_PROPERTY(float diffuseDepthThreshold MEMBER diffuseDepthThreshold NOTIFY dirty) - Q_PROPERTY(double gpuTime READ getGpuTime) public: - SurfaceGeometryPassConfig() : render::Job::Config(true) {} + SurfaceGeometryPassConfig() : render::GPUJobConfig(true) {} float depthThreshold{ 5.0f }; // centimeters float basisScale{ 1.0f }; @@ -169,10 +155,6 @@ public: float diffuseFilterScale{ 0.2f }; float diffuseDepthThreshold{ 1.0f }; - double getGpuTime() { return gpuTime; } - - double gpuTime{ 0.0 }; - signals: void dirty(); }; diff --git a/libraries/render/CMakeLists.txt b/libraries/render/CMakeLists.txt index 76fc8303ce..735bb7f086 100644 --- a/libraries/render/CMakeLists.txt +++ b/libraries/render/CMakeLists.txt @@ -1,7 +1,8 @@ set(TARGET_NAME render) AUTOSCRIBE_SHADER_LIB(gpu model) setup_hifi_library() -link_hifi_libraries(shared gpu model) +# render needs octree only for getAccuracyAngle(float, int) +link_hifi_libraries(shared gpu model octree) target_nsight() diff --git a/libraries/render/src/render/Task.h b/libraries/render/src/render/Task.h index 6ba1e3c625..df24a08c05 100644 --- a/libraries/render/src/render/Task.h +++ b/libraries/render/src/render/Task.h @@ -334,9 +334,9 @@ protected: // A default Config is always on; to create an enableable Config, use the ctor JobConfig(bool enabled) class JobConfig : public QObject { Q_OBJECT - Q_PROPERTY(quint64 cpuRunTime READ getCPUTRunTime NOTIFY newStats()) + Q_PROPERTY(double cpuRunTime READ getCPURunTime NOTIFY newStats()) //ms - quint64 _CPURunTime{ 0 }; + double _msCPURunTime{ 0.0 }; public: using Persistent = PersistentConfig; @@ -364,8 +364,8 @@ public: // Running Time measurement // The new stats signal is emitted once per run time of a job when stats (cpu runtime) are updated - void setCPURunTime(quint64 ustime) { _CPURunTime = ustime; emit newStats(); } - quint64 getCPUTRunTime() const { return _CPURunTime; } + void setCPURunTime(double mstime) { _msCPURunTime = mstime; emit newStats(); } + double getCPURunTime() const { return _msCPURunTime; } public slots: void load(const QJsonObject& val) { qObjectFromJsonValue(val, *this); emit loaded(); } @@ -418,6 +418,46 @@ template void jobRun(T& data, const SceneContextPoin data.run(sceneContext, renderContext, input, output); } +class GPUJobConfig : public JobConfig { + Q_OBJECT + Q_PROPERTY(double gpuRunTime READ getGPURunTime) + Q_PROPERTY(double batchRunTime READ getBatchRunTime) + + double _msGPURunTime { 0.0 }; + double _msBatchRunTime { 0.0 }; +public: + using Persistent = PersistentConfig; + + GPUJobConfig() = default; + GPUJobConfig(bool enabled) : JobConfig(enabled) {} + + // Running Time measurement on GPU and for Batch execution + void setGPUBatchRunTime(double msGpuTime, double msBatchTime) { _msGPURunTime = msGpuTime; _msBatchRunTime = msBatchTime; } + double getGPURunTime() const { return _msGPURunTime; } + double getBatchRunTime() const { return _msBatchRunTime; } +}; + +class GPUTaskConfig : public TaskConfig { + Q_OBJECT + Q_PROPERTY(double gpuRunTime READ getGPURunTime) + Q_PROPERTY(double batchRunTime READ getBatchRunTime) + + double _msGPURunTime { 0.0 }; + double _msBatchRunTime { 0.0 }; +public: + + using Persistent = PersistentConfig; + + + GPUTaskConfig() = default; + GPUTaskConfig(bool enabled) : TaskConfig(enabled) {} + + // Running Time measurement on GPU and for Batch execution + void setGPUBatchRunTime(double msGpuTime, double msBatchTime) { _msGPURunTime = msGpuTime; _msBatchRunTime = msBatchTime; } + double getGPURunTime() const { return _msGPURunTime; } + double getBatchRunTime() const { return _msBatchRunTime; } +}; + class Job { public: using Config = JobConfig; @@ -439,7 +479,7 @@ public: virtual void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext) = 0; protected: - void setCPURunTime(quint64 ustime) { std::static_pointer_cast(_config)->setCPURunTime(ustime); } + void setCPURunTime(double mstime) { std::static_pointer_cast(_config)->setCPURunTime(mstime); } QConfigPointer _config; @@ -502,7 +542,7 @@ public: _concept->run(sceneContext, renderContext); - _concept->setCPURunTime(usecTimestampNow() - start); + _concept->setCPURunTime((double)(usecTimestampNow() - start) / 1000.0); } protected: diff --git a/libraries/script-engine/CMakeLists.txt b/libraries/script-engine/CMakeLists.txt index c3a9a9f38a..8626a98161 100644 --- a/libraries/script-engine/CMakeLists.txt +++ b/libraries/script-engine/CMakeLists.txt @@ -3,13 +3,17 @@ setup_hifi_library(Gui Network Script ScriptTools WebSockets Widgets) target_zlib() -add_dependency_external_projects(quazip) -find_package(QuaZip REQUIRED) -target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${QUAZIP_INCLUDE_DIRS}) -target_link_libraries(${TARGET_NAME} ${QUAZIP_LIBRARIES}) +if (NOT ANDROID) -if (WIN32) + add_dependency_external_projects(quazip) + find_package(QuaZip REQUIRED) + target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${QUAZIP_INCLUDE_DIRS}) + target_link_libraries(${TARGET_NAME} ${QUAZIP_LIBRARIES}) + + if (WIN32) add_paths_to_fixup_libs(${QUAZIP_DLL_PATH}) + endif () + endif () link_hifi_libraries(shared networking octree gpu ui procedural model model-networking recording avatars fbx entities controllers animation audio physics) diff --git a/libraries/script-engine/src/FileScriptingInterface.cpp b/libraries/script-engine/src/FileScriptingInterface.cpp index fa38e46d31..ad6a3cdf6f 100644 --- a/libraries/script-engine/src/FileScriptingInterface.cpp +++ b/libraries/script-engine/src/FileScriptingInterface.cpp @@ -65,13 +65,21 @@ bool FileScriptingInterface::isTempDir(QString tempDir) { folderName = "/" + testDir.section("/", -1); QString testContainer = testDir; testContainer.remove(folderName); - if (testContainer == tempContainer) return true; - return false; + return (testContainer == tempContainer); +} + +// checks whether the webview is displaying a Clara.io page for Marketplaces.qml +bool FileScriptingInterface::isClaraLink(QUrl url) { + return (url.toString().contains("clara.io") && !url.toString().contains("clara.io/signup")); } bool FileScriptingInterface::isZippedFbx(QUrl url) { - if (url.toString().contains(".zip") && url.toString().contains("fbx")) return true; - return false; + return (url.toString().endsWith("fbx.zip")); +} + +// checks whether a user tries to download a file that is not in .fbx format +bool FileScriptingInterface::isZipped(QUrl url) { + return (url.toString().endsWith(".zip")); } // this function is not in use diff --git a/libraries/script-engine/src/FileScriptingInterface.h b/libraries/script-engine/src/FileScriptingInterface.h index dd6ca3225b..d9a100b293 100644 --- a/libraries/script-engine/src/FileScriptingInterface.h +++ b/libraries/script-engine/src/FileScriptingInterface.h @@ -25,6 +25,8 @@ public: public slots: bool isZippedFbx(QUrl url); + bool isZipped(QUrl url); + bool isClaraLink(QUrl url); QString convertUrlToPath(QUrl url); void runUnzip(QString path, QUrl url); QString getTempDir(); diff --git a/libraries/script-engine/src/ScriptsModel.cpp b/libraries/script-engine/src/ScriptsModel.cpp index 0ad2ad01a7..8755195932 100644 --- a/libraries/script-engine/src/ScriptsModel.cpp +++ b/libraries/script-engine/src/ScriptsModel.cpp @@ -185,6 +185,7 @@ void ScriptsModel::requestDefaultFiles(QString marker) { QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); request.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); QNetworkReply* reply = networkAccessManager.get(request); connect(reply, SIGNAL(finished()), SLOT(downloadFinished())); diff --git a/libraries/script-engine/src/XMLHttpRequestClass.cpp b/libraries/script-engine/src/XMLHttpRequestClass.cpp index 15b2576331..818521ecc5 100644 --- a/libraries/script-engine/src/XMLHttpRequestClass.cpp +++ b/libraries/script-engine/src/XMLHttpRequestClass.cpp @@ -45,6 +45,7 @@ XMLHttpRequestClass::XMLHttpRequestClass(QScriptEngine* engine) : _timer(this), _numRedirects(0) { + _request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); _timer.setSingleShot(true); } diff --git a/libraries/shared/src/PointerEvent.cpp b/libraries/shared/src/PointerEvent.cpp index 66289a35dd..ed9acb9ada 100644 --- a/libraries/shared/src/PointerEvent.cpp +++ b/libraries/shared/src/PointerEvent.cpp @@ -81,28 +81,43 @@ QScriptValue PointerEvent::toScriptValue(QScriptEngine* engine, const PointerEve direction.setProperty("z", event._direction.z); obj.setProperty("pos3D", direction); + bool isPrimaryButton = false; + bool isSecondaryButton = false; + bool isTertiaryButton = false; switch (event._button) { case NoButtons: obj.setProperty("button", "None"); break; case PrimaryButton: obj.setProperty("button", "Primary"); + isPrimaryButton = true; break; case SecondaryButton: obj.setProperty("button", "Secondary"); + isSecondaryButton = true; break; case TertiaryButton: obj.setProperty("button", "Tertiary"); + isTertiaryButton = true; break; } - obj.setProperty("isLeftButton", areFlagsSet(event._buttons, PrimaryButton)); - obj.setProperty("isRightButton", areFlagsSet(event._buttons, SecondaryButton)); - obj.setProperty("isMiddleButton", areFlagsSet(event._buttons, TertiaryButton)); + if (isPrimaryButton) { + obj.setProperty("isPrimaryButton", isPrimaryButton); + obj.setProperty("isLeftButton", isPrimaryButton); + } + if (isSecondaryButton) { + obj.setProperty("isSecondaryButton", isSecondaryButton); + obj.setProperty("isRightButton", isSecondaryButton); + } + if (isTertiaryButton) { + obj.setProperty("isTertiaryButton", isTertiaryButton); + obj.setProperty("isMiddleButton", isTertiaryButton); + } - obj.setProperty("isPrimaryButton", areFlagsSet(event._buttons, PrimaryButton)); - obj.setProperty("isSecondaryButton", areFlagsSet(event._buttons, SecondaryButton)); - obj.setProperty("isTertiaryButton", areFlagsSet(event._buttons, TertiaryButton)); + obj.setProperty("isPrimaryHeld", areFlagsSet(event._buttons, PrimaryButton)); + obj.setProperty("isSecondaryHeld", areFlagsSet(event._buttons, SecondaryButton)); + obj.setProperty("isTertiaryHeld", areFlagsSet(event._buttons, TertiaryButton)); return obj; } @@ -146,9 +161,9 @@ void PointerEvent::fromScriptValue(const QScriptValue& object, PointerEvent& eve event._button = NoButtons; } - bool primary = object.property("isPrimary").toBool() || object.property("isLeftButton").toBool(); - bool secondary = object.property("isSecondary").toBool() || object.property("isRightButton").toBool(); - bool tertiary = object.property("isTertiary").toBool() || object.property("isMiddleButton").toBool(); + bool primary = object.property("isPrimaryHeld").toBool(); + bool secondary = object.property("isSecondaryHeld").toBool(); + bool tertiary = object.property("isTertiaryHeld").toBool(); event._buttons = 0; if (primary) { event._buttons |= PrimaryButton; diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp index 8e6c1ef6ed..171b58de17 100644 --- a/libraries/shared/src/RegisteredMetaTypes.cpp +++ b/libraries/shared/src/RegisteredMetaTypes.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include "RegisteredMetaTypes.h" @@ -32,6 +33,7 @@ int xColorMetaTypeId = qRegisterMetaType(); int pickRayMetaTypeId = qRegisterMetaType(); int collisionMetaTypeId = qRegisterMetaType(); int qMapURLStringMetaTypeId = qRegisterMetaType>(); +int socketErrorMetaTypeId = qRegisterMetaType(); void registerMetaTypes(QScriptEngine* engine) { qScriptRegisterMetaType(engine, mat4toScriptValue, mat4FromScriptValue); diff --git a/libraries/shared/src/shared/Bilateral.h b/libraries/shared/src/shared/Bilateral.h index c4daf60177..edcaa49540 100644 --- a/libraries/shared/src/shared/Bilateral.h +++ b/libraries/shared/src/shared/Bilateral.h @@ -28,7 +28,7 @@ namespace bilateral { case Side::Right: return 0x02; } - return UINT8_MAX; + return std::numeric_limits::max(); } inline uint8_t index(Side side) { @@ -38,7 +38,7 @@ namespace bilateral { case Side::Right: return 1; } - return UINT8_MAX; + return std::numeric_limits::max(); } template diff --git a/libraries/ui/src/MainWindow.cpp b/libraries/ui/src/MainWindow.cpp index bc67a726bd..545d2dc902 100644 --- a/libraries/ui/src/MainWindow.cpp +++ b/libraries/ui/src/MainWindow.cpp @@ -30,6 +30,7 @@ MainWindow::MainWindow(QWidget* parent) : _windowGeometry("WindowGeometry"), _windowState("WindowState", 0) { + setAttribute(Qt::WA_NoSystemBackground); setAcceptDrops(true); } diff --git a/libraries/ui/src/VrMenu.cpp b/libraries/ui/src/VrMenu.cpp index c2732197d3..f4b365265d 100644 --- a/libraries/ui/src/VrMenu.cpp +++ b/libraries/ui/src/VrMenu.cpp @@ -155,7 +155,7 @@ void bindActionToQmlAction(QObject* qmlAction, QAction* action) { QObject::connect(qmlAction, SIGNAL(triggered()), action, SLOT(trigger())); } -class QQuickMenuItem; +class QQuickMenuItem1; void VrMenu::addAction(QMenu* menu, QAction* action) { Q_ASSERT(!MenuUserData::hasData(action)); @@ -167,10 +167,9 @@ void VrMenu::addAction(QMenu* menu, QAction* action) { } QObject* menuQml = findMenuObject(userData->uuid.toString()); Q_ASSERT(menuQml); - QQuickMenuItem* returnedValue { nullptr }; - + QQuickMenuItem1* returnedValue { nullptr }; bool invokeResult = QMetaObject::invokeMethod(menuQml, "addItem", Qt::DirectConnection, - Q_RETURN_ARG(QQuickMenuItem*, returnedValue), + Q_RETURN_ARG(QQuickMenuItem1*, returnedValue), Q_ARG(QString, action->text())); Q_ASSERT(invokeResult); @@ -206,10 +205,10 @@ void VrMenu::insertAction(QAction* before, QAction* action) { beforeQml = findMenuObject(beforeUserData->uuid.toString()); } QObject* menu = beforeQml->parent(); - QQuickMenuItem* returnedValue { nullptr }; + QQuickMenuItem1* returnedValue { nullptr }; // FIXME this needs to find the index of the beforeQml item and call insertItem(int, object) bool invokeResult = QMetaObject::invokeMethod(menu, "addItem", Qt::DirectConnection, - Q_RETURN_ARG(QQuickMenuItem*, returnedValue), + Q_RETURN_ARG(QQuickMenuItem1*, returnedValue), Q_ARG(QString, action->text())); Q_ASSERT(invokeResult); QObject* result = reinterpret_cast(returnedValue); // returnedValue.value(); diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 1e2a672107..6dbc57431e 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -11,7 +11,7 @@ file(GLOB PLUGIN_SUBDIRS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT list(REMOVE_ITEM PLUGIN_SUBDIRS "CMakeFiles") # client-side plugins -if (NOT SERVER_ONLY) +if (NOT SERVER_ONLY AND NOT ANDROID) set(DIR "oculus") add_subdirectory(${DIR}) set(DIR "hifiSdl2") diff --git a/plugins/hifiSixense/CMakeLists.txt b/plugins/hifiSixense/CMakeLists.txt index f907d7865f..14676217db 100644 --- a/plugins/hifiSixense/CMakeLists.txt +++ b/plugins/hifiSixense/CMakeLists.txt @@ -6,7 +6,9 @@ # See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html # -set(TARGET_NAME hifiSixense) -setup_hifi_plugin(Script Qml Widgets) -link_hifi_libraries(shared controllers ui plugins ui-plugins input-plugins) -target_sixense() +if (NOT ANDROID) + set(TARGET_NAME hifiSixense) + setup_hifi_plugin(Script Qml Widgets) + link_hifi_libraries(shared controllers ui plugins ui-plugins input-plugins) + target_sixense() +endif () diff --git a/plugins/oculus/src/OculusDisplayPlugin.cpp b/plugins/oculus/src/OculusDisplayPlugin.cpp index f1cad94281..838a4121cd 100644 --- a/plugins/oculus/src/OculusDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusDisplayPlugin.cpp @@ -139,6 +139,7 @@ void OculusDisplayPlugin::hmdPresent() { logWarning("Failed to present"); } } + _presentRate.increment(); } bool OculusDisplayPlugin::isHmdMounted() const { diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index 4eb371719b..4c4fcbbd37 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -12,9 +12,12 @@ #include +#include + #include #include + #include #include #include @@ -56,7 +59,7 @@ public: using Condition = std::condition_variable; using Lock = std::unique_lock; friend class OpenVrDisplayPlugin; - std::shared_ptr _canvas; + std::shared_ptr _canvas; BasicFramebufferWrapperPtr _framebuffer; ProgramPtr _program; ShapeWrapperPtr _plane; @@ -129,7 +132,6 @@ public: void run() override { QThread::currentThread()->setPriority(QThread::Priority::TimeCriticalPriority); - assert(_canvas->thread() == QThread::currentThread()); _canvas->makeCurrent(); glDisable(GL_DEPTH_TEST); glViewport(0, 0, _plugin._renderTargetSize.x, _plugin._renderTargetSize.y); @@ -172,6 +174,7 @@ public: vr::Texture_t texture{ (void*)oglplus::GetName(_framebuffer->color), vr::API_OpenGL, vr::ColorSpace_Auto }; vr::VRCompositor()->Submit(vr::Eye_Left, &texture, &leftBounds); vr::VRCompositor()->Submit(vr::Eye_Right, &texture, &rightBounds); + _plugin._presentRate.increment(); PoseData nextRender, nextSim; nextRender.frameIndex = _plugin.presentCount(); vr::VRCompositor()->WaitGetPoses(nextRender.vrPoses, vr::k_unMaxTrackedDeviceCount, nextSim.vrPoses, vr::k_unMaxTrackedDeviceCount); @@ -190,7 +193,6 @@ public: nextRender.update(sensorResetMat); nextSim.update(sensorResetMat); - _plugin.withNonPresentThreadLock([&] { _nextRender = nextRender; _nextSim = nextSim; @@ -206,7 +208,6 @@ public: _program.reset(); _framebuffer.reset(); _canvas->doneCurrent(); - _canvas->moveToThreadWithContext(qApp->thread()); } void update(const CompositeInfo& newCompositeInfo) { @@ -309,14 +310,11 @@ bool OpenVrDisplayPlugin::internalActivate() { _submitThread = std::make_shared(*this); if (!_submitCanvas) { withMainThreadContext([&] { - _submitCanvas = std::make_shared(); - _submitCanvas->setObjectName("OpenVRSubmitContext"); - _submitCanvas->create(_container->getPrimaryContext()); + _submitCanvas = std::make_shared(); + _submitCanvas->create(); _submitCanvas->doneCurrent(); }); } - _submitCanvas->moveToThreadWithContext(_submitThread.get()); - assert(_submitCanvas->thread() == _submitThread.get()); #endif return Parent::internalActivate(); @@ -354,7 +352,6 @@ void OpenVrDisplayPlugin::customizeContext() { } _compositeInfos[i].textureID = getGLBackend()->getTextureID(_compositeInfos[i].texture, false); } - assert(_submitCanvas->thread() == _submitThread.get()); _submitThread->_canvas = _submitCanvas; _submitThread->start(QThread::HighPriority); #endif @@ -367,7 +364,6 @@ void OpenVrDisplayPlugin::uncustomizeContext() { _submitThread->_quit = true; _submitThread->wait(); _submitThread.reset(); - assert(_submitCanvas->thread() == qApp->thread()); #endif } diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.h b/plugins/openvr/src/OpenVrDisplayPlugin.h index 7b8869ae93..025f879d84 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.h +++ b/plugins/openvr/src/OpenVrDisplayPlugin.h @@ -18,6 +18,9 @@ const float TARGET_RATE_OpenVr = 90.0f; // FIXME: get from sdk tracked device p #define OPENVR_THREADED_SUBMIT 1 #if OPENVR_THREADED_SUBMIT +namespace gl { + class OffscreenContext; +} class OpenVrSubmitThread; class OffscreenGLCanvas; static const size_t COMPOSITING_BUFFER_SIZE = 3; @@ -55,6 +58,9 @@ public: void unsuppressKeyboard() override; bool isKeyboardVisible() override; + // Needs an additional thread for VR submission + int getRequiredThreadCount() const override { return Parent::getRequiredThreadCount() + 1; } + protected: bool internalActivate() override; void internalDeactivate() override; @@ -79,7 +85,7 @@ private: CompositeInfo::Array _compositeInfos; size_t _renderingIndex { 0 }; std::shared_ptr _submitThread; - std::shared_ptr _submitCanvas; + std::shared_ptr _submitCanvas; friend class OpenVrSubmitThread; #endif }; diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 98825c594d..f9cd32be46 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -37,49 +37,92 @@ var DEFAULT_SCRIPTS = [ // add a menu item for debugging var MENU_CATEGORY = "Developer"; var MENU_ITEM = "Debug defaultScripts.js"; -var debuggingDefaultScripts = false; + +var SETTINGS_KEY = '_debugDefaultScriptsIsChecked'; +var previousSetting = Settings.getValue(SETTINGS_KEY); + +if (previousSetting === '' || previousSetting === false || previousSetting === 'false') { + previousSetting = false; +} + +if (previousSetting === true || previousSetting === 'true') { + previousSetting = true; +} + + + if (Menu.menuExists(MENU_CATEGORY) && !Menu.menuItemExists(MENU_CATEGORY, MENU_ITEM)) { Menu.addMenuItem({ menuName: MENU_CATEGORY, menuItemName: MENU_ITEM, isCheckable: true, - isChecked: false, + isChecked: previousSetting, grouping: "Advanced" }); } -// start all scripts -if (Menu.isOptionChecked(MENU_ITEM)) { - // we're debugging individual default scripts - // so we load each into its own ScriptEngine instance - debuggingDefaultScripts = true; - for (var i in DEFAULT_SCRIPTS) { - Script.load(DEFAULT_SCRIPTS[i]); - } -} else { - // include all default scripts into this ScriptEngine +function runDefaultsTogether() { for (var j in DEFAULT_SCRIPTS) { Script.include(DEFAULT_SCRIPTS[j]); } } -function stopLoadedScripts() { - if (debuggingDefaultScripts) { - // remove debug script loads - var runningScripts = ScriptDiscoveryService.getRunning(); - for (var i in runningScripts) { - var scriptName = runningScripts[i].name; - for (var j in DEFAULT_SCRIPTS) { - if (DEFAULT_SCRIPTS[j].slice(-scriptName.length) === scriptName) { - ScriptDiscoveryService.stopScript(runningScripts[i].url); - } - } +function runDefaultsSeparately() { + for (var i in DEFAULT_SCRIPTS) { + Script.load(DEFAULT_SCRIPTS[i]); + } +} +// start all scripts +if (Menu.isOptionChecked(MENU_ITEM)) { + // we're debugging individual default scripts + // so we load each into its own ScriptEngine instance + debuggingDefaultScripts = true; + runDefaultsSeparately(); +} else { + // include all default scripts into this ScriptEngine + runDefaultsTogether(); +} + +function menuItemEvent(menuItem) { + if (menuItem == MENU_ITEM) { + + isChecked = Menu.isOptionChecked(MENU_ITEM); + if (isChecked === true) { + Settings.setValue(SETTINGS_KEY, true); + } else if (isChecked === false) { + Settings.setValue(SETTINGS_KEY, false); } - if (!Menu.isOptionChecked(MENU_ITEM)) { - Menu.removeMenuItem(MENU_CATEGORY, MENU_ITEM); + Window.alert('You must reload all scripts for this to take effect.') + } + + +} + + + +function stopLoadedScripts() { + // remove debug script loads + var runningScripts = ScriptDiscoveryService.getRunning(); + for (var i in runningScripts) { + var scriptName = runningScripts[i].name; + for (var j in DEFAULT_SCRIPTS) { + if (DEFAULT_SCRIPTS[j].slice(-scriptName.length) === scriptName) { + ScriptDiscoveryService.stopScript(runningScripts[i].url); + } } } } -Script.scriptEnding.connect(stopLoadedScripts); +function removeMenuItem() { + if (!Menu.isOptionChecked(MENU_ITEM)) { + Menu.removeMenuItem(MENU_CATEGORY, MENU_ITEM); + } +} + +Script.scriptEnding.connect(function() { + stopLoadedScripts(); + removeMenuItem(); +}); + +Menu.menuItemEvent.connect(menuItemEvent); \ No newline at end of file diff --git a/scripts/developer/utilities/render/ambientOcclusionPass.qml b/scripts/developer/utilities/render/ambientOcclusionPass.qml index a461c06a19..3ebc80d2f1 100644 --- a/scripts/developer/utilities/render/ambientOcclusionPass.qml +++ b/scripts/developer/utilities/render/ambientOcclusionPass.qml @@ -75,10 +75,10 @@ Column { object: Render.getConfig("AmbientOcclusion") valueUnit: "ms" valueScale: 1 - valueNumDigits: "4" + valueNumDigits: "3" plots: [ { - prop: "gpuTime", + prop: "gpuRunTime", label: "gpu", color: "#FFFFFF" } diff --git a/scripts/developer/utilities/render/stats.qml b/scripts/developer/utilities/render/stats.qml index d20efc1f10..f20c545503 100644 --- a/scripts/developer/utilities/render/stats.qml +++ b/scripts/developer/utilities/render/stats.qml @@ -213,8 +213,8 @@ Item { height: parent.evalEvenHeight() object: parent.drawOpaqueConfig valueUnit: "ms" - valueScale: 1000 - valueNumDigits: "1" + valueScale: 1 + valueNumDigits: "2" plots: [ { object: Render.getConfig("DrawOpaqueDeferred"), diff --git a/scripts/developer/utilities/render/statsGPU.qml b/scripts/developer/utilities/render/statsGPU.qml index 74bd376a00..3d23c2c6dc 100644 --- a/scripts/developer/utilities/render/statsGPU.qml +++ b/scripts/developer/utilities/render/statsGPU.qml @@ -30,7 +30,7 @@ Item { PlotPerf { - title: "Timing" + title: "GPU Timing" height: parent.evalEvenHeight() object: parent.drawOpaqueConfig valueUnit: "ms" @@ -39,31 +39,71 @@ Item { plots: [ { object: Render.getConfig("OpaqueRangeTimer"), - prop: "gpuTime", + prop: "gpuRunTime", label: "Opaque", color: "#FFFFFF" }, { object: Render.getConfig("LinearDepth"), - prop: "gpuTime", + prop: "gpuRunTime", label: "LinearDepth", color: "#00FF00" },{ object: Render.getConfig("SurfaceGeometry"), - prop: "gpuTime", + prop: "gpuRunTime", label: "SurfaceGeometry", color: "#00FFFF" }, { object: Render.getConfig("RenderDeferred"), - prop: "gpuTime", + prop: "gpuRunTime", label: "DeferredLighting", color: "#FF00FF" } , { object: Render.getConfig("ToneAndPostRangeTimer"), - prop: "gpuTime", + prop: "gpuRunTime", + label: "tone and post", + color: "#FF0000" + } + ] + } + PlotPerf { + title: "Batch Timing" + height: parent.evalEvenHeight() + object: parent.drawOpaqueConfig + valueUnit: "ms" + valueScale: 1 + valueNumDigits: "3" + plots: [ + { + object: Render.getConfig("OpaqueRangeTimer"), + prop: "batchRunTime", + label: "Opaque", + color: "#FFFFFF" + }, + { + object: Render.getConfig("LinearDepth"), + prop: "batchRunTime", + label: "LinearDepth", + color: "#00FF00" + },{ + object: Render.getConfig("SurfaceGeometry"), + prop: "batchRunTime", + label: "SurfaceGeometry", + color: "#00FFFF" + }, + { + object: Render.getConfig("RenderDeferred"), + prop: "batchRunTime", + label: "DeferredLighting", + color: "#FF00FF" + } + , + { + object: Render.getConfig("ToneAndPostRangeTimer"), + prop: "batchRunTime", label: "tone and post", color: "#FF0000" } diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 32e0b047de..28488c9f3b 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -11,6 +11,7 @@ // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +/* global setEntityCustomData, getEntityCustomData, flatten, Xform, Script, Quat, Vec3, MyAvatar, Entities, Overlays, Settings, Reticle, Controller, Camera, Messages, Mat4 */ (function() { // BEGIN LOCAL_SCOPE @@ -24,6 +25,9 @@ var WANT_DEBUG = false; var WANT_DEBUG_STATE = false; var WANT_DEBUG_SEARCH_NAME = null; +var FORCE_IGNORE_IK = true; +var SHOW_GRAB_POINT_SPHERE = true; + // // these tune time-averaging and "on" value for analog trigger // @@ -59,6 +63,7 @@ var EQUIP_SPHERE_COLOR = { var EQUIP_SPHERE_ALPHA = 0.15; var EQUIP_SPHERE_SCALE_FACTOR = 0.65; + // // distant manipulation // @@ -87,21 +92,21 @@ var COLORS_GRAB_DISTANCE_HOLD = { blue: 214 }; - var PICK_MAX_DISTANCE = 500; // max length of pick-ray // // near grabbing // -var EQUIP_RADIUS = 0.1; // radius used for palm vs equip-hotspot for equipping. +var EQUIP_RADIUS = 0.2; // radius used for palm vs equip-hotspot for equipping. // if EQUIP_HOTSPOT_RENDER_RADIUS is greater than zero, the hotspot will appear before the hand // has reached the required position, and then grow larger once the hand is close enough to equip. var EQUIP_HOTSPOT_RENDER_RADIUS = 0.0; // radius used for palm vs equip-hotspot for rendering hot-spots +var MAX_EQUIP_HOTSPOT_RADIUS = 1.0; var NEAR_GRABBING_ACTION_TIMEFRAME = 0.05; // how quickly objects move to their new position -var NEAR_GRAB_RADIUS = 0.15; // radius used for palm vs object for near grabbing. +var NEAR_GRAB_RADIUS = 0.07; // radius used for palm vs object for near grabbing. var NEAR_GRAB_MAX_DISTANCE = 1.0; // you cannot grab objects that are this far away from your hand var NEAR_GRAB_PICK_RADIUS = 0.25; // radius used for search ray vs object for near grabbing. @@ -112,6 +117,13 @@ var NEAR_GRABBING_KINEMATIC = true; // force objects to be kinematic when near-g // if an equipped item is "adjusted" to be too far from the hand it's in, it will be unequipped. var CHECK_TOO_FAR_UNEQUIP_TIME = 0.3; // seconds, duration between checks + +var GRAB_POINT_SPHERE_OFFSET = { x: 0.0, y: 0.2, z: 0.0 }; +var GRAB_POINT_SPHERE_RADIUS = NEAR_GRAB_RADIUS; +var GRAB_POINT_SPHERE_COLOR = { red: 20, green: 90, blue: 238 }; +var GRAB_POINT_SPHERE_ALPHA = 0.85; + + // // other constants // @@ -125,6 +137,12 @@ var ZERO_VEC = { z: 0 }; +var ONE_VEC = { + x: 1, + y: 1, + z: 1 +}; + var NULL_UUID = "{00000000-0000-0000-0000-000000000000}"; // these control how long an abandoned pointer line or action will hang around @@ -162,7 +180,7 @@ var USE_BLACKLIST = true; var blacklist = []; var FORBIDDEN_GRAB_NAMES = ["Grab Debug Entity", "grab pointer"]; -var FORBIDDEN_GRAB_TYPES = ['Unknown', 'Light', 'PolyLine', 'Zone']; +var FORBIDDEN_GRAB_TYPES = ["Unknown", "Light", "PolyLine", "Zone"]; // states for the state machine var STATE_OFF = 0; @@ -177,7 +195,6 @@ var STATE_ENTITY_TOUCHING = 7; // "collidesWith" is specified by comma-separated list of group names // the possible group names are: static, dynamic, kinematic, myAvatar, otherAvatar var COLLIDES_WITH_WHILE_GRABBED = "dynamic,otherAvatar"; -var COLLIDES_WITH_WHILE_MULTI_GRABBED = "dynamic"; var HEART_BEAT_INTERVAL = 5 * MSECS_PER_SEC; var HEART_BEAT_TIMEOUT = 15 * MSECS_PER_SEC; @@ -232,6 +249,25 @@ CONTROLLER_STATE_MACHINE[STATE_ENTITY_TOUCHING] = { updateMethod: "entityTouching" }; +function distanceBetweenPointAndEntityBoundingBox(point, entityProps) { + var entityXform = new Xform(entityProps.rotation, entityProps.position); + var localPoint = entityXform.inv().xformPoint(point); + var minOffset = Vec3.multiplyVbyV(entityProps.registrationPoint, entityProps.dimensions); + var maxOffset = Vec3.multiplyVbyV(Vec3.subtract(ONE_VEC, entityProps.registrationPoint), entityProps.dimensions); + var localMin = Vec3.subtract(entityXform.trans, minOffset); + var localMax = Vec3.sum(entityXform.trans, maxOffset); + + var v = {x: localPoint.x, y: localPoint.y, z: localPoint.z}; + v.x = Math.max(v.x, localMin.x); + v.x = Math.min(v.x, localMax.x); + v.y = Math.max(v.y, localMin.y); + v.y = Math.min(v.y, localMax.y); + v.z = Math.max(v.z, localMin.z); + v.z = Math.min(v.z, localMax.z); + + return Vec3.distance(v, localPoint); +} + function angleBetween(a, b) { return Math.acos(Vec3.dot(Vec3.normalize(a), Vec3.normalize(b))); } @@ -248,11 +284,9 @@ function projectOntoEntityXYPlane(entityID, worldPos) { y: (1 - normalizedPos.y) * props.dimensions.y }; // flip y-axis } -function handLaserIntersectEntity(entityID, hand) { - var standardControllerValue = (hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - var pose = Controller.getPoseValue(standardControllerValue); - var worldHandPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, pose.translation), MyAvatar.position); - var worldHandRotation = Quat.multiply(MyAvatar.orientation, pose.rotation); +function handLaserIntersectEntity(entityID, start) { + var worldHandPosition = start.position; + var worldHandRotation = start.orientation; var props = entityPropertiesCache.getProps(entityID); @@ -330,7 +364,7 @@ function entityIsGrabbedByOther(entityID) { for (var actionIndex = 0; actionIndex < actionIDs.length; actionIndex++) { var actionID = actionIDs[actionIndex]; var actionArguments = Entities.getActionArguments(entityID, actionID); - var tag = actionArguments["tag"]; + var tag = actionArguments.tag; if (tag == getTag()) { // we see a grab-*uuid* shaped tag, but it's our tag, so that's okay. continue; @@ -655,9 +689,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp) { if (overlayInfoSet.timestamp != timestamp && overlayInfoSet.currentSize <= 0.05) { // this is an old overlay, that has finished fading out, delete it! - overlayInfoSet.overlays.forEach(function(overlay) { - Overlays.deleteOverlay(overlay); - }); + overlayInfoSet.overlays.forEach(Overlays.deleteOverlay); delete this.map[keys[i]]; } else { // update overlay position, rotation to follow the object it's attached to. @@ -689,16 +721,36 @@ var equipHotspotBuddy = new EquipHotspotBuddy(); function MyController(hand) { this.hand = hand; - if (this.hand === RIGHT_HAND) { - this.getHandPosition = MyAvatar.getRightPalmPosition; - // this.getHandRotation = MyAvatar.getRightPalmRotation; - } else { - this.getHandPosition = MyAvatar.getLeftPalmPosition; - // this.getHandRotation = MyAvatar.getLeftPalmRotation; - } - this.getHandRotation = function() { - var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - return Quat.multiply(MyAvatar.orientation, Controller.getPoseValue(controllerHandInput).rotation); + this.autoUnequipCounter = 0; + + // handPosition is where the avatar's hand appears to be, in-world. + this.getHandPosition = function () { + if (this.hand === RIGHT_HAND) { + return MyAvatar.getRightPalmPosition(); + } else { + return MyAvatar.getLeftPalmPosition(); + } + }; + this.getHandRotation = function () { + if (this.hand === RIGHT_HAND) { + return MyAvatar.getRightPalmRotation(); + } else { + return MyAvatar.getLeftPalmRotation(); + } + }; + // controllerLocation is where the controller would be, in-world. + this.getControllerLocation = function (doOffset) { + var standardControllerValue = (hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; + var pose = Controller.getPoseValue(standardControllerValue); + + var orientation = Quat.multiply(MyAvatar.orientation, pose.rotation); + var position = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, pose.translation), MyAvatar.position); + // add to the real position so the grab-point is out in front of the hand, a bit + if (doOffset) { + position = Vec3.sum(position, Vec3.multiplyQbyV(orientation, GRAB_POINT_SPHERE_OFFSET)); + } + + return {position: position, orientation: orientation}; }; this.actionID = null; // action this script created... @@ -805,6 +857,39 @@ function MyController(hand) { } }; + this.grabPointSphereOn = function() { + if (!SHOW_GRAB_POINT_SPHERE) { + return; + } + if (!MyAvatar.sessionUUID) { + return; + } + if (!this.grabPointSphere) { + this.grabPointSphere = Overlays.addOverlay("sphere", { + localPosition: GRAB_POINT_SPHERE_OFFSET, + localRotation: { x: 0, y: 0, z: 0, w: 1 }, + dimensions: GRAB_POINT_SPHERE_RADIUS, + color: GRAB_POINT_SPHERE_COLOR, + alpha: GRAB_POINT_SPHERE_ALPHA, + solid: true, + visible: true, + ignoreRayIntersection: true, + drawInFront: false, + parentID: MyAvatar.sessionUUID, + parentJointIndex: MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? + "_CONTROLLER_RIGHTHAND" : + "_CONTROLLER_LEFTHAND") + }); + } + }; + + this.grabPointSphereOff = function() { + if (this.grabPointSphere) { + Overlays.deleteOverlay(this.grabPointSphere); + this.grabPointSphere = null; + } + }; + this.searchSphereOn = function(location, size, color) { var rotation = Quat.lookAt(location, Camera.getPosition(), Vec3.UP); @@ -880,10 +965,14 @@ function MyController(hand) { var searchSphereLocation = Vec3.sum(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, this.searchSphereDistance)); this.searchSphereOn(searchSphereLocation, SEARCH_SPHERE_SIZE * this.searchSphereDistance, - (this.triggerSmoothedGrab() || this.secondarySqueezed()) ? COLORS_GRAB_SEARCHING_FULL_SQUEEZE : COLORS_GRAB_SEARCHING_HALF_SQUEEZE); + (this.triggerSmoothedGrab() || this.secondarySqueezed()) ? + COLORS_GRAB_SEARCHING_FULL_SQUEEZE : + COLORS_GRAB_SEARCHING_HALF_SQUEEZE); if (PICK_WITH_HAND_RAY) { this.overlayLineOn(handPosition, searchSphereLocation, - (this.triggerSmoothedGrab() || this.secondarySqueezed()) ? COLORS_GRAB_SEARCHING_FULL_SQUEEZE : COLORS_GRAB_SEARCHING_HALF_SQUEEZE); + (this.triggerSmoothedGrab() || this.secondarySqueezed()) ? + COLORS_GRAB_SEARCHING_FULL_SQUEEZE : + COLORS_GRAB_SEARCHING_HALF_SQUEEZE); } }; @@ -933,7 +1022,8 @@ function MyController(hand) { this.turnOffVisualizations = function() { this.overlayLineOff(); - + this.grabPointSphereOff(); + this.lineOff(); this.searchSphereOff(); restore2DMode(); @@ -1004,19 +1094,20 @@ function MyController(hand) { } if (!this.waitForTriggerRelease && this.triggerSmoothedSqueezed()) { this.lastPickTime = 0; - var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - this.startingHandRotation = Controller.getPoseValue(controllerHandInput).rotation; + this.startingHandRotation = this.getControllerLocation(true).orientation; if (this.triggerSmoothedSqueezed()) { this.setState(STATE_SEARCHING, "trigger squeeze detected"); return; } } - var candidateEntities = Entities.findEntities(this.getHandPosition(), EQUIP_HOTSPOT_RENDER_RADIUS); + this.grabPointSphereOn(); + + var candidateEntities = Entities.findEntities(this.getControllerLocation(true).position, MAX_EQUIP_HOTSPOT_RADIUS); entityPropertiesCache.addEntities(candidateEntities); var potentialEquipHotspot = this.chooseBestEquipHotspot(candidateEntities); if (!this.waitForTriggerRelease) { - this.updateEquipHaptics(potentialEquipHotspot, this.getHandPosition()); + this.updateEquipHaptics(potentialEquipHotspot, this.getControllerLocation(true).position); } var nearEquipHotspots = this.chooseNearEquipHotspots(candidateEntities, EQUIP_HOTSPOT_RENDER_RADIUS); @@ -1035,23 +1126,27 @@ function MyController(hand) { !potentialEquipHotspot && this.prevPotentialEquipHotspot) { Controller.triggerHapticPulse(HAPTIC_TEXTURE_STRENGTH, HAPTIC_TEXTURE_DURATION, this.hand); this.lastHapticPulseLocation = currentLocation; - } else if (potentialEquipHotspot && Vec3.distance(this.lastHapticPulseLocation, currentLocation) > HAPTIC_TEXTURE_DISTANCE) { + } else if (potentialEquipHotspot && + Vec3.distance(this.lastHapticPulseLocation, currentLocation) > HAPTIC_TEXTURE_DISTANCE) { Controller.triggerHapticPulse(HAPTIC_TEXTURE_STRENGTH, HAPTIC_TEXTURE_DURATION, this.hand); this.lastHapticPulseLocation = currentLocation; } this.prevPotentialEquipHotspot = potentialEquipHotspot; }; + this.heartBeatIsStale = function(data) { + var now = Date.now(); + return data.heartBeat === undefined || now - data.heartBeat > HEART_BEAT_TIMEOUT; + }; + // Performs ray pick test from the hand controller into the world // @param {number} which hand to use, RIGHT_HAND or LEFT_HAND // @returns {object} returns object with two keys entityID and distance // this.calcRayPickInfo = function(hand) { - - var standardControllerValue = (hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - var pose = Controller.getPoseValue(standardControllerValue); - var worldHandPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, pose.translation), MyAvatar.position); - var worldHandRotation = Quat.multiply(MyAvatar.orientation, pose.rotation); + var controllerLocation = this.getControllerLocation(true); + var worldHandPosition = controllerLocation.position; + var worldHandRotation = controllerLocation.orientation; var pickRay = { origin: PICK_WITH_HAND_RAY ? worldHandPosition : Camera.position, @@ -1171,7 +1266,7 @@ function MyController(hand) { var okToEquipFromOtherHand = ((this.getOtherHandController().state == STATE_NEAR_GRABBING || this.getOtherHandController().state == STATE_DISTANCE_HOLDING) && this.getOtherHandController().grabbedEntity == hotspot.entityID); - if (refCount > 0 && !okToEquipFromOtherHand) { + if (refCount > 0 && !this.heartBeatIsStale(grabProps) && !okToEquipFromOtherHand) { if (debug) { print("equip is skipping '" + props.name + "': grabbed by someone else"); } @@ -1188,20 +1283,21 @@ function MyController(hand) { var physical = propsArePhysical(props); var grabbable = false; var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME); + var refCount = ("refCount" in grabProps) ? grabProps.refCount : 0; if (physical) { // physical things default to grabbable grabbable = true; } else { // non-physical things default to non-grabbable unless they are already grabbed - if ("refCount" in grabProps && grabProps.refCount > 0) { + if (refCount > 0) { grabbable = true; } else { grabbable = false; } } - if (grabbableProps.hasOwnProperty("grabbable")) { + if (grabbableProps.hasOwnProperty("grabbable") && refCount === 0) { grabbable = grabbableProps.grabbable; } @@ -1296,7 +1392,7 @@ function MyController(hand) { return _this.collectEquipHotspots(entityID); })).filter(function(hotspot) { return (_this.hotspotIsEquippable(hotspot) && - Vec3.distance(hotspot.worldPosition, _this.getHandPosition()) < hotspot.radius + distance); + Vec3.distance(hotspot.worldPosition, _this.getControllerLocation(true).position) < hotspot.radius + distance); }); return equippableHotspots; }; @@ -1307,8 +1403,8 @@ function MyController(hand) { if (equippableHotspots.length > 0) { // sort by distance equippableHotspots.sort(function(a, b) { - var aDistance = Vec3.distance(a.worldPosition, this.getHandPosition()); - var bDistance = Vec3.distance(b.worldPosition, this.getHandPosition()); + var aDistance = Vec3.distance(a.worldPosition, this.getControllerLocation(true).position); + var bDistance = Vec3.distance(b.worldPosition, this.getControllerLocation(true).position); return aDistance - bDistance; }); return equippableHotspots[0]; @@ -1319,6 +1415,11 @@ function MyController(hand) { this.searchEnter = function() { mostRecentSearchingHand = this.hand; + var rayPickInfo = this.calcRayPickInfo(this.hand); + if (rayPickInfo.entityID || rayPickInfo.overlayID) { + this.intersectionDistance = rayPickInfo.distance; + this.searchSphereDistance = this.intersectionDistance; + } }; this.search = function(deltaTime, timestamp) { @@ -1329,6 +1430,8 @@ function MyController(hand) { this.isInitialGrab = false; this.shouldResetParentOnRelease = false; + this.grabPointSphereOn(); + this.checkForStrayChildren(); if (this.triggerSmoothedReleased()) { @@ -1336,7 +1439,7 @@ function MyController(hand) { return; } - var handPosition = this.getHandPosition(); + var handPosition = this.getControllerLocation(true).position; var rayPickInfo = this.calcRayPickInfo(this.hand); @@ -1344,10 +1447,10 @@ function MyController(hand) { entityPropertiesCache.addEntity(rayPickInfo.entityID); } - var candidateEntities = Entities.findEntities(handPosition, NEAR_GRAB_RADIUS); - entityPropertiesCache.addEntities(candidateEntities); + var candidateHotSpotEntities = Entities.findEntities(handPosition, MAX_EQUIP_HOTSPOT_RADIUS); + entityPropertiesCache.addEntities(candidateHotSpotEntities); - var potentialEquipHotspot = this.chooseBestEquipHotspot(candidateEntities); + var potentialEquipHotspot = this.chooseBestEquipHotspot(candidateHotSpotEntities); if (potentialEquipHotspot) { if (this.triggerSmoothedGrab()) { this.grabbedHotspot = potentialEquipHotspot; @@ -1357,6 +1460,7 @@ function MyController(hand) { } } + var candidateEntities = Entities.findEntities(handPosition, NEAR_GRAB_RADIUS); var grabbableEntities = candidateEntities.filter(function(entity) { return _this.entityIsNearGrabbable(entity, handPosition, NEAR_GRAB_MAX_DISTANCE); }); @@ -1426,10 +1530,7 @@ function MyController(hand) { pos3D: rayPickInfo.intersection, normal: rayPickInfo.normal, direction: rayPickInfo.searchRay.direction, - button: "None", - isPrimaryButton: false, - isSecondaryButton: false, - isTertiaryButton: false + button: "None" }; this.hoverEntity = entity; @@ -1449,10 +1550,7 @@ function MyController(hand) { pos3D: rayPickInfo.intersection, normal: rayPickInfo.normal, direction: rayPickInfo.searchRay.direction, - button: "None", - isPrimaryButton: false, - isSecondaryButton: false, - isTertiaryButton: false + button: "None" }; Entities.sendMouseMoveOnEntity(entity, pointerEvent); @@ -1522,18 +1620,13 @@ function MyController(hand) { }; this.distanceHoldingEnter = function() { - Messages.sendLocalMessage('Hifi-Teleport-Disabler','both'); + Messages.sendLocalMessage('Hifi-Teleport-Disabler', 'both'); this.clearEquipHaptics(); + this.grabPointSphereOff(); - // controller pose is in avatar frame - var device = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - var avatarControllerPose = Controller.getPoseValue(device); + var worldControllerPosition = this.getControllerLocation(true).position; - // transform it into world frame - var worldControllerPosition = Vec3.sum(MyAvatar.position, - Vec3.multiplyQbyV(MyAvatar.orientation, avatarControllerPose.translation)); - - // also transform the position into room space + // transform the position into room space var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition); @@ -1596,14 +1689,10 @@ function MyController(hand) { this.heartBeat(this.grabbedEntity); - // controller pose is in avatar frame - var device = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - var avatarControllerPose = Controller.getPoseValue(device); - // transform it into world frame - var worldControllerPosition = Vec3.sum(MyAvatar.position, - Vec3.multiplyQbyV(MyAvatar.orientation, avatarControllerPose.translation)); - var worldControllerRotation = Quat.multiply(MyAvatar.orientation, avatarControllerPose.rotation); + var controllerLocation = this.getControllerLocation(true); + var worldControllerPosition = controllerLocation.position; + var worldControllerRotation = controllerLocation.orientation; // also transform the position into room space var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); @@ -1674,8 +1763,6 @@ function MyController(hand) { } } - var handPosition = this.getHandPosition(); - // visualizations var rayPickInfo = this.calcRayPickInfo(this.hand); @@ -1740,10 +1827,7 @@ function MyController(hand) { }; this.dropGestureProcess = function(deltaTime) { - var standardControllerValue = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - var pose = Controller.getPoseValue(standardControllerValue); - var worldHandRotation = Quat.multiply(MyAvatar.orientation, pose.rotation); - + var worldHandRotation = this.getControllerLocation(true).orientation; var localHandUpAxis = this.hand === RIGHT_HAND ? { x: 1, y: 0, @@ -1782,14 +1866,14 @@ function MyController(hand) { this.nearGrabbingEnter = function() { if (this.hand === 0) { Messages.sendLocalMessage('Hifi-Teleport-Disabler', 'left'); - } if (this.hand === 1) { Messages.sendLocalMessage('Hifi-Teleport-Disabler', 'right'); - } + this.grabPointSphereOff(); this.lineOff(); this.overlayLineOff(); + this.searchSphereOff(); this.dropGestureReset(); this.clearEquipHaptics(); @@ -1811,12 +1895,23 @@ function MyController(hand) { var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); this.activateEntity(this.grabbedEntity, grabbedProperties, false); - // var handRotation = this.getHandRotation(); - var handRotation = (this.hand === RIGHT_HAND) ? MyAvatar.getRightPalmRotation() : MyAvatar.getLeftPalmRotation(); - var handPosition = this.getHandPosition(); - var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); - this.ignoreIK = grabbableData.ignoreIK ? grabbableData.ignoreIK : false; + if (FORCE_IGNORE_IK) { + this.ignoreIK = true; + } else { + this.ignoreIK = grabbableData.ignoreIK ? grabbableData.ignoreIK : false; + } + + var handRotation; + var handPosition; + if (this.ignoreIK) { + var controllerLocation = this.getControllerLocation(false); + handRotation = controllerLocation.orientation; + handPosition = controllerLocation.position; + } else { + handRotation = this.getHandRotation(); + handPosition = this.getHandPosition(); + } var hasPresetPosition = false; if (this.state == STATE_HOLD && this.grabbedHotspot) { @@ -1855,12 +1950,21 @@ function MyController(hand) { } Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ action: 'grab', - grabbedEntity: this.grabbedEntity + grabbedEntity: this.grabbedEntity, + joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" })); } else { // grab entity via parenting this.actionID = null; - var handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); + var handJointIndex; + if (this.ignoreIK) { + handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? + "_CONTROLLER_RIGHTHAND" : + "_CONTROLLER_LEFTHAND"); + } else { + handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); + } + var reparentProps = { parentID: MyAvatar.sessionUUID, parentJointIndex: handJointIndex, @@ -1868,14 +1972,15 @@ function MyController(hand) { angularVelocity: {x: 0, y: 0, z: 0} }; if (hasPresetPosition) { - reparentProps["localPosition"] = this.offsetPosition; - reparentProps["localRotation"] = this.offsetRotation; + reparentProps.localPosition = this.offsetPosition; + reparentProps.localRotation = this.offsetRotation; } Entities.editEntity(this.grabbedEntity, reparentProps); Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ action: 'equip', - grabbedEntity: this.grabbedEntity + grabbedEntity: this.grabbedEntity, + joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" })); } @@ -1913,6 +2018,8 @@ function MyController(hand) { this.nearGrabbing = function(deltaTime, timestamp) { + this.grabPointSphereOff(); + if (this.state == STATE_NEAR_GRABBING && !this.triggerClicked) { this.callEntityMethodOnGrabbed("releaseGrab"); this.setState(STATE_OFF, "trigger released"); @@ -1943,9 +2050,10 @@ function MyController(hand) { // store the offset attach points into preferences. if (USE_ATTACH_POINT_SETTINGS && this.grabbedHotspot && this.grabbedEntity) { - var props = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "localRotation"]); - if (props && props.localPosition && props.localRotation) { - storeAttachPointForHotspotInSettings(this.grabbedHotspot, this.hand, props.localPosition, props.localRotation); + var prefprops = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "localRotation"]); + if (prefprops && prefprops.localPosition && prefprops.localRotation) { + storeAttachPointForHotspotInSettings(this.grabbedHotspot, this.hand, + prefprops.localPosition, prefprops.localRotation); } } @@ -1961,28 +2069,39 @@ function MyController(hand) { this.heartBeat(this.grabbedEntity); var props = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "parentID", - "position", "rotation", "dimensions"]); + "position", "rotation", "dimensions", + "registrationPoint"]); if (!props.position) { - // server may have reset, taking our equipped entity with it. move back to "off" stte + // server may have reset, taking our equipped entity with it. move back to "off" state this.callEntityMethodOnGrabbed("releaseGrab"); this.setState(STATE_OFF, "entity has no position property"); return; } var now = Date.now(); - if (now - this.lastUnequipCheckTime > MSECS_PER_SEC * CHECK_TOO_FAR_UNEQUIP_TIME) { + if (this.state == STATE_HOLD && now - this.lastUnequipCheckTime > MSECS_PER_SEC * CHECK_TOO_FAR_UNEQUIP_TIME) { this.lastUnequipCheckTime = now; if (props.parentID == MyAvatar.sessionUUID) { - var handPosition = this.getHandPosition(); - // the center of the equipped object being far from the hand isn't enough to auto-unequip -- we also - // need to fail the findEntities test. - var TEAR_AWAY_DISTANCE = 0.04; - var nearPickedCandidateEntities = Entities.findEntities(handPosition, NEAR_GRAB_RADIUS + TEAR_AWAY_DISTANCE); - if (nearPickedCandidateEntities.indexOf(this.grabbedEntity) == -1) { - // for whatever reason, the held/equipped entity has been pulled away. ungrab or unequip. + var handPosition; + if (this.ignoreIK) { + handPosition = this.getControllerLocation(false).position; + } else { + handPosition = this.getHandPosition(); + } + + var TEAR_AWAY_DISTANCE = 0.1; + var dist = distanceBetweenPointAndEntityBoundingBox(handPosition, props); + if (dist > TEAR_AWAY_DISTANCE) { + this.autoUnequipCounter += 1; + } else { + this.autoUnequipCounter = 0; + } + + if (this.autoUnequipCounter > 1) { + // for whatever reason, the held/equipped entity has been pulled away. ungrab or unequip. print("handControllerGrab -- autoreleasing held or equipped item because it is far from hand." + - props.parentID + " " + vec3toStr(props.position)); + props.parentID + ", dist = " + dist); if (this.state == STATE_NEAR_GRABBING) { this.callEntityMethodOnGrabbed("releaseGrab"); @@ -2053,16 +2172,15 @@ function MyController(hand) { }; this.nearTriggerEnter = function() { - this.clearEquipHaptics(); - + this.grabPointSphereOff(); Controller.triggerShortHapticPulse(1.0, this.hand); this.callEntityMethodOnGrabbed("startNearTrigger"); }; this.farTriggerEnter = function() { this.clearEquipHaptics(); - + this.grabPointSphereOff(); this.callEntityMethodOnGrabbed("startFarTrigger"); }; @@ -2082,10 +2200,9 @@ function MyController(hand) { return; } - var handPosition = this.getHandPosition(); var pickRay = { - origin: handPosition, - direction: Quat.getUp(this.getHandRotation()) + origin: this.getControllerLocation().position, + direction: Quat.getUp(this.getControllerLocation().orientation) }; var now = Date.now(); @@ -2114,7 +2231,7 @@ function MyController(hand) { this.entityTouchingEnter = function() { // test for intersection between controller laser and web entity plane. - var intersectInfo = handLaserIntersectEntity(this.grabbedEntity, this.hand); + var intersectInfo = handLaserIntersectEntity(this.grabbedEntity, this.getControllerLocation(true)); if (intersectInfo) { var pointerEvent = { type: "Press", @@ -2124,9 +2241,7 @@ function MyController(hand) { normal: intersectInfo.normal, direction: intersectInfo.searchRay.direction, button: "Primary", - isPrimaryButton: true, - isSecondaryButton: false, - isTertiaryButton: false + isPrimaryHeld: true }; Entities.sendMousePressOnEntity(this.grabbedEntity, pointerEvent); @@ -2141,7 +2256,7 @@ function MyController(hand) { this.entityTouchingExit = function() { // test for intersection between controller laser and web entity plane. - var intersectInfo = handLaserIntersectEntity(this.grabbedEntity, this.hand); + var intersectInfo = handLaserIntersectEntity(this.grabbedEntity, this.getControllerLocation(true)); if (intersectInfo) { var pointerEvent; if (this.deadspotExpired) { @@ -2152,15 +2267,12 @@ function MyController(hand) { pos3D: intersectInfo.point, normal: intersectInfo.normal, direction: intersectInfo.searchRay.direction, - button: "Primary", - isPrimaryButton: false, - isSecondaryButton: false, - isTertiaryButton: false + button: "Primary" }; } else { pointerEvent = this.touchingEnterPointerEvent; pointerEvent.button = "Primary"; - pointerEvent.isPrimaryButton = false; + pointerEvent.isPrimaryHeld = false; } Entities.sendMouseReleaseOnEntity(this.grabbedEntity, pointerEvent); @@ -2182,7 +2294,7 @@ function MyController(hand) { } // test for intersection between controller laser and web entity plane. - var intersectInfo = handLaserIntersectEntity(this.grabbedEntity, this.hand); + var intersectInfo = handLaserIntersectEntity(this.grabbedEntity, this.getControllerLocation(true)); if (intersectInfo) { if (Entities.keyboardFocusEntity != this.grabbedEntity) { @@ -2197,9 +2309,7 @@ function MyController(hand) { normal: intersectInfo.normal, direction: intersectInfo.searchRay.direction, button: "NoButtons", - isPrimaryButton: true, - isSecondaryButton: false, - isTertiaryButton: false + isPrimaryHeld: true }; var POINTER_PRESS_TO_MOVE_DELAY = 0.15; // seconds @@ -2221,7 +2331,7 @@ function MyController(hand) { }; this.release = function() { - Messages.sendLocalMessage('Hifi-Teleport-Disabler','none'); + Messages.sendLocalMessage('Hifi-Teleport-Disabler', 'none'); this.turnOffVisualizations(); var noVelocity = false; @@ -2233,16 +2343,7 @@ function MyController(hand) { // If this looks like the release after adjusting something still held in the other hand, print the position // and rotation of the held thing to help content creators set the userData. var grabData = getEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, {}); - if (grabData.refCount > 1) { - var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "localRotation"]); - if (grabbedProperties && grabbedProperties.localPosition && grabbedProperties.localRotation) { - print((this.hand === RIGHT_HAND ? '"LeftHand"' : '"RightHand"') + ":" + - '[{"x":' + grabbedProperties.localPosition.x + ', "y":' + grabbedProperties.localPosition.y + - ', "z":' + grabbedProperties.localPosition.z + '}, {"x":' + grabbedProperties.localRotation.x + - ', "y":' + grabbedProperties.localRotation.y + ', "z":' + grabbedProperties.localRotation.z + - ', "w":' + grabbedProperties.localRotation.w + '}]'); - } - } + this.printNewOffsets = (grabData.refCount > 1); if (this.actionID !== null) { Entities.deleteAction(this.grabbedEntity, this.actionID); @@ -2256,17 +2357,17 @@ function MyController(hand) { noVelocity = true; } } + + this.deactivateEntity(this.grabbedEntity, noVelocity); + + Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ + action: 'release', + grabbedEntity: this.grabbedEntity, + joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" + })); } - this.deactivateEntity(this.grabbedEntity, noVelocity); this.actionID = null; - - Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ - action: 'release', - grabbedEntity: this.grabbedEntity, - joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" - })); - this.grabbedEntity = null; this.grabbedHotspot = null; @@ -2277,13 +2378,14 @@ function MyController(hand) { this.cleanup = function() { this.release(); + this.grabPointSphereOff(); }; this.heartBeat = function(entityID) { var now = Date.now(); if (now - this.lastHeartBeat > HEART_BEAT_INTERVAL) { var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); - data["heartBeat"] = now; + data.heartBeat = now; setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data); this.lastHeartBeat = now; } @@ -2292,12 +2394,14 @@ function MyController(hand) { this.resetAbandonedGrab = function(entityID) { print("cleaning up abandoned grab on " + entityID); var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); - data["refCount"] = 1; + data.refCount = 1; setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data); this.deactivateEntity(entityID, false); }; this.activateEntity = function(entityID, grabbedProperties, wasLoaded) { + this.autoUnequipCounter = 0; + if (this.entityActivated) { return; } @@ -2311,29 +2415,29 @@ function MyController(hand) { // get re-instated after all the grabs have been released) be correct. Script.clearTimeout(delayedDeactivateTimeout); delayedDeactivateTimeout = null; - grabbedProperties["collidesWith"] = delayedDeactivateFunc(); + grabbedProperties.collidesWith = delayedDeactivateFunc(); } var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); var now = Date.now(); if (wasLoaded) { - data["refCount"] = 1; + data.refCount = 1; } else { - data["refCount"] = data["refCount"] ? data["refCount"] + 1 : 1; + data.refCount = data.refCount ? data.refCount + 1 : 1; // zero gravity and set ignoreForCollisions in a way that lets us put them back, after all grabs are done - if (data["refCount"] == 1) { - data["heartBeat"] = now; + if (data.refCount == 1) { + data.heartBeat = now; this.lastHeartBeat = now; this.isInitialGrab = true; - data["gravity"] = grabbedProperties.gravity; - data["collidesWith"] = grabbedProperties.collidesWith; - data["collisionless"] = grabbedProperties.collisionless; - data["dynamic"] = grabbedProperties.dynamic; - data["parentID"] = wasLoaded ? NULL_UUID : grabbedProperties.parentID; - data["parentJointIndex"] = grabbedProperties.parentJointIndex; + data.gravity = grabbedProperties.gravity; + data.collidesWith = grabbedProperties.collidesWith; + data.collisionless = grabbedProperties.collisionless; + data.dynamic = grabbedProperties.dynamic; + data.parentID = wasLoaded ? NULL_UUID : grabbedProperties.parentID; + data.parentJointIndex = grabbedProperties.parentJointIndex; var whileHeldProperties = { gravity: { @@ -2347,9 +2451,8 @@ function MyController(hand) { "collidesWith": COLLIDES_WITH_WHILE_GRABBED }; Entities.editEntity(entityID, whileHeldProperties); - } else if (data["refCount"] > 1) { - if (data["heartBeat"] === undefined || - now - data["heartBeat"] > HEART_BEAT_TIMEOUT) { + } else if (data.refCount > 1) { + if (this.heartBeatIsStale(data)) { // this entity has userData suggesting it is grabbed, but nobody is updating the hearbeat. // deactivate it before grabbing. this.resetAbandonedGrab(entityID); @@ -2364,7 +2467,8 @@ function MyController(hand) { // bootstrap themselves with the held object. This happens because the meaning of "otherAvatar" in // the collision mask hinges on who the physics simulation owner is. Entities.editEntity(entityID, { - "collidesWith": COLLIDES_WITH_WHILE_MULTI_GRABBED + // "collidesWith": removeAvatarsFromCollidesWith(grabbedProperties.collidesWith) + collisionless: true }); } } @@ -2377,6 +2481,10 @@ function MyController(hand) { // unhook them. var handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); var children = Entities.getChildrenIDsOfJoint(MyAvatar.sessionUUID, handJointIndex); + var controllerJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? + "_CONTROLLER_RIGHTHAND" : + "_CONTROLLER_LEFTHAND"); + children.concat(Entities.getChildrenIDsOfJoint(MyAvatar.sessionUUID, controllerJointIndex)); children.forEach(function(childID) { print("disconnecting stray child of hand: (" + _this.hand + ") " + childID); Entities.editEntity(childID, { @@ -2395,9 +2503,9 @@ function MyController(hand) { collidesWith: collidesWith }); var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); - if (data && data["refCount"]) { - data["refCount"] = data["refCount"] - 1; - if (data["refCount"] < 1) { + if (data && data.refCount) { + data.refCount = data.refCount - 1; + if (data.refCount < 1) { data = null; } } else { @@ -2417,24 +2525,24 @@ function MyController(hand) { var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); var doDelayedDeactivate = false; - if (data && data["refCount"]) { - data["refCount"] = data["refCount"] - 1; - if (data["refCount"] < 1) { + if (data && data.refCount) { + data.refCount = data.refCount - 1; + if (data.refCount < 1) { deactiveProps = { - gravity: data["gravity"], + gravity: data.gravity, // don't set collidesWith myAvatar back right away, because thrown things tend to bounce off the // avatar's capsule. - collidesWith: removeMyAvatarFromCollidesWith(data["collidesWith"]), - collisionless: data["collisionless"], - dynamic: data["dynamic"], - parentID: data["parentID"], - parentJointIndex: data["parentJointIndex"] + collidesWith: removeMyAvatarFromCollidesWith(data.collidesWith), + collisionless: data.collisionless, + dynamic: data.dynamic, + parentID: data.parentID, + parentJointIndex: data.parentJointIndex }; - doDelayedDeactivate = (data["collidesWith"].indexOf("myAvatar") >= 0); + doDelayedDeactivate = (data.collidesWith.indexOf("myAvatar") >= 0); if (doDelayedDeactivate) { - var delayedCollidesWith = data["collidesWith"]; + var delayedCollidesWith = data.collidesWith; var delayedEntityID = entityID; delayedDeactivateFunc = function() { // set collidesWith back to original value a bit later than the rest @@ -2454,19 +2562,19 @@ function MyController(hand) { if (!noVelocity && parentID == MyAvatar.sessionUUID && - Vec3.length(data["gravity"]) > 0.0 && - data["dynamic"] && - data["parentID"] == NULL_UUID && - !data["collisionless"]) { - deactiveProps["velocity"] = this.currentVelocity; + Vec3.length(data.gravity) > 0.0 && + data.dynamic && + data.parentID == NULL_UUID && + !data.collisionless) { + deactiveProps.velocity = this.currentVelocity; } if (noVelocity) { - deactiveProps["velocity"] = { + deactiveProps.velocity = { x: 0.0, y: 0.0, z: 0.0 }; - deactiveProps["angularVelocity"] = { + deactiveProps.angularVelocity = { x: 0.0, y: 0.0, z: 0.0 @@ -2492,6 +2600,17 @@ function MyController(hand) { } }; Entities.editEntity(entityID, deactiveProps); + + if (this.printNewOffsets) { + var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "localRotation"]); + if (grabbedProperties && grabbedProperties.localPosition && grabbedProperties.localRotation) { + print((this.hand === RIGHT_HAND ? '"LeftHand"' : '"RightHand"') + ":" + + '[{"x":' + grabbedProperties.localPosition.x + ', "y":' + grabbedProperties.localPosition.y + + ', "z":' + grabbedProperties.localPosition.z + '}, {"x":' + grabbedProperties.localRotation.x + + ', "y":' + grabbedProperties.localRotation.y + ', "z":' + grabbedProperties.localRotation.z + + ', "w":' + grabbedProperties.localRotation.w + '}]'); + } + } } else if (noVelocity) { Entities.editEntity(entityID, { velocity: { @@ -2504,7 +2623,7 @@ function MyController(hand) { y: 0.0, z: 0.0 }, - dynamic: data["dynamic"] + dynamic: data.dynamic }); } } else { @@ -2551,9 +2670,13 @@ function update(deltaTime) { if (handToDisable !== LEFT_HAND && handToDisable !== 'both') { leftController.update(deltaTime, timestamp); + } else { + leftController.release(); } if (handToDisable !== RIGHT_HAND && handToDisable !== 'both') { rightController.update(deltaTime, timestamp); + } else { + rightController.release(); } equipHotspotBuddy.update(deltaTime, timestamp); entityPropertiesCache.update(); @@ -2604,7 +2727,7 @@ var handleHandMessages = function(channel, message, sender) { selectedController.nearGrabbingEnter(); } catch (e) { - print("WARNING: error parsing Hifi-Hand-Grab message"); + print("WARNING: handControllerGrab.js -- error parsing Hifi-Hand-Grab message: " + message); } } else if (channel === 'Hifi-Hand-RayPick-Blacklist') { @@ -2624,7 +2747,7 @@ var handleHandMessages = function(channel, message, sender) { } } catch (e) { - print("WARNING: error parsing Hifi-Hand-RayPick-Blacklist message"); + print("WARNING: handControllerGrab.js -- error parsing Hifi-Hand-RayPick-Blacklist message: " + message); } } } diff --git a/scripts/system/controllers/teleport.js b/scripts/system/controllers/teleport.js index f3e94d23de..b4a8eefcd2 100644 --- a/scripts/system/controllers/teleport.js +++ b/scripts/system/controllers/teleport.js @@ -94,7 +94,6 @@ function Teleporter() { this.initialize = function() { this.createMappings(); - this.disableGrab(); }; this.createMappings = function() { @@ -218,10 +217,6 @@ function Teleporter() { this.updateConnected = null; this.inCoolIn = false; inTeleportMode = false; - - Script.setTimeout(function() { - _this.enableGrab(); - }, 200); }; this.update = function() { @@ -494,14 +489,6 @@ function Teleporter() { }); }; - this.disableGrab = function() { - Messages.sendLocalMessage('Hifi-Hand-Disabler', this.teleportHand); - }; - - this.enableGrab = function() { - Messages.sendLocalMessage('Hifi-Hand-Disabler', 'none'); - }; - this.triggerHaptics = function() { var hand = this.teleportHand === 'left' ? 0 : 1; var haptic = Controller.triggerShortHapticPulse(0.2, hand); diff --git a/scripts/system/directory.js b/scripts/system/directory.js index 2eae4fa9cf..ac65615a68 100644 --- a/scripts/system/directory.js +++ b/scripts/system/directory.js @@ -73,8 +73,7 @@ var toolBar = (function() { width: toolWidth, height: toolHeight, alpha: 0.9, - visible: true, - showButtonDown: true + visible: true }); toolBar.showTool(browseDirectoryButton, true); diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 1a50fb0d8b..d90566c619 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -164,9 +164,10 @@ function toggleMarketplace() { } } +var TOOLS_PATH = Script.resolvePath("assets/images/tools/"); + var toolBar = (function () { var EDIT_SETTING = "io.highfidelity.isEditting"; // for communication with other scripts - var TOOL_ICON_URL = Script.resolvePath("assets/images/tools/"); var that = {}, toolBar, systemToolbar, @@ -199,7 +200,7 @@ var toolBar = (function () { } function addButton(name, image, handler) { - var imageUrl = TOOL_ICON_URL + image; + var imageUrl = TOOLS_PATH + image; var button = toolBar.addButton({ objectName: name, imageURL: imageUrl, @@ -232,7 +233,7 @@ var toolBar = (function () { systemToolbar = Toolbars.getToolbar(SYSTEM_TOOLBAR); activeButton = systemToolbar.addButton({ objectName: EDIT_TOGGLE_BUTTON, - imageURL: TOOL_ICON_URL + "edit.svg", + imageURL: TOOLS_PATH + "edit.svg", visible: true, alpha: 0.9, buttonState: 1, @@ -803,7 +804,7 @@ function setupModelMenus() { menuName: "Edit", menuItemName: "Delete", shortcutKeyEvent: { - text: "backspace" + text: "delete" }, afterItem: "Entities", grouping: "Advanced" @@ -1215,7 +1216,7 @@ Controller.keyReleaseEvent.connect(function (event) { cameraManager.keyReleaseEvent(event); } // since sometimes our menu shortcut keys don't work, trap our menu items here also and fire the appropriate menu items - if (event.text === "BACKSPACE" || event.text === "DELETE") { + if (event.text === "DELETE") { deleteSelectedEntities(); } else if (event.text === "ESC") { selectionManager.clearSelections(); @@ -1326,13 +1327,14 @@ function pushCommandForSelections(createdEntityData, deletedEntityData) { UndoStack.pushCommand(applyEntityProperties, undoData, applyEntityProperties, redoData); } +var ENTITY_PROPERTIES_URL = Script.resolvePath('html/entityProperties.html'); + var PropertiesTool = function (opts) { var that = {}; - var url = Script.resolvePath('html/entityProperties.html'); var webView = new OverlayWebWindow({ title: 'Entity Properties', - source: url, + source: ENTITY_PROPERTIES_URL, toolWindow: true }); diff --git a/scripts/system/html/css/marketplaces.css b/scripts/system/html/css/marketplaces.css new file mode 100644 index 0000000000..734501f3fc --- /dev/null +++ b/scripts/system/html/css/marketplaces.css @@ -0,0 +1,98 @@ +/* +// +// 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 +*/ +body { + background: white; + padding: 0 0 0 0; + font-family:Raleway-SemiBold; +} +.marketplaces-container { + display: inline-block; + color: black; + width: 94%; + margin-left: 3%; + height: 100%; +} +.marketplaces-title { + margin-top: 45px; + margin-bottom: 20px; +} +.marketplaces-intro-text { + margin-bottom: 60px; +} +.marketplace-tile { + float:left; + width: 100%; +} +.marketplace-tile-first-column { + text-align: center; + float: left; + width: 33%; +} +.marketplace-tile-second-column { + float: left; + margin-left:4%; + width: 62%; +} +.exploreButton { + font-size: 16px !important; + width: 200px !important; + height: 45px !important; + margin-top: 20px; + margin-bottom: 30px; +} +.tile-divider { + width: 100%; + margin-left: 0%; + display: block; + height: 1px; + border: 0; + border-top: 1px solid lightgrey; + margin: 1em 0; + padding: 0; + margin-bottom: 30px; +} +.marketplace-tile-description { + margin-top: 15px; + margin-bottom: 30px; +} +.marketplace-tile-image { + margin-top:15px; + max-width: 256px; + height: 128px; + margin-bottom:60px; + -webkit-box-shadow: -1px 4px 16px 0px rgba(0, 0, 0, 0.48); + -moz-box-shadow: -1px 4px 16px 0px rgba(0, 0, 0, 0.48); + box-shadow: -1px 4px 16px 0px rgba(0, 0, 0, 0.48); +} +.marketplace-clara-steps { + padding-left: 15px; +} +.marketplace-clara-steps > li { + margin-top: 5px; +} +@media (max-width:768px) { + .marketplace-tile-first-column { + float: left; + width: 100%; + } + .marketplace-tile-second-column { + float: left; + width: 100%; + } + .exploreButton-holder { + width:100%; + text-align:center; + } + .tile-divider { + width: 100%; + margin-left: 0%; +} +.marketplace-tile-image{ + margin-bottom:15px; +} +} \ No newline at end of file diff --git a/scripts/system/html/img/clara-tile.png b/scripts/system/html/img/clara-tile.png new file mode 100644 index 0000000000..ae431dd510 Binary files /dev/null and b/scripts/system/html/img/clara-tile.png differ diff --git a/scripts/system/html/img/hifi-marketplace-tile.png b/scripts/system/html/img/hifi-marketplace-tile.png new file mode 100644 index 0000000000..9a95c081a0 Binary files /dev/null and b/scripts/system/html/img/hifi-marketplace-tile.png differ diff --git a/scripts/system/html/js/marketplaces.js b/scripts/system/html/js/marketplaces.js new file mode 100644 index 0000000000..a1b3847b3c --- /dev/null +++ b/scripts/system/html/js/marketplaces.js @@ -0,0 +1,12 @@ +function loaded() { +bindExploreButtons(); +} + +function bindExploreButtons() { + $('#exploreClaraMarketplace').on('click', function() { + window.location = "https://clara.io/library?public=true" + }) + $('#exploreHifiMarketplace').on('click', function() { + window.location = "http://www.highfidelity.com/marketplace" + }) +} \ No newline at end of file diff --git a/scripts/system/html/marketplaces.html b/scripts/system/html/marketplaces.html new file mode 100644 index 0000000000..ea85c7ec62 --- /dev/null +++ b/scripts/system/html/marketplaces.html @@ -0,0 +1,65 @@ + + + + Marketplaces + + + + + + + + + +
+

+ Marketplaces +

+
+

+ You can bring content into High Fidelity forom anywhere you want. Here are a few places that support direct import of content right now. If you'd like to suggest a Market to include here, let us know. +

+
+
+
+ +
+
+

This is the default High Fidelity marketplace. Viewing and downloading content from here is fully supported in Interface.

+
+
+
+
+
+
+
+ +
+
+

Clara.io has thousands of models available for importing into High Fidelity. Follow these steps for the best experience:

+
    +
  1. Create an account here or log in as an existing user.
  2. +
  3. Choose a model from the list and click Download -> Autodesk FBX.
  4. +
  5. After the file processes, click Download.
  6. +
  7. Add the model to your asset server, then find it from the list and choose Add To World.
  8. +
+
+ +
+
+
+
+
+ + + + + + \ No newline at end of file diff --git a/scripts/system/libraries/WebTablet.js b/scripts/system/libraries/WebTablet.js index 34368e475b..adbcd78381 100644 --- a/scripts/system/libraries/WebTablet.js +++ b/scripts/system/libraries/WebTablet.js @@ -11,6 +11,8 @@ var RAD_TO_DEG = 180 / Math.PI; var X_AXIS = {x: 1, y: 0, z: 0}; var Y_AXIS = {x: 0, y: 1, z: 0}; +var DEFAULT_DPI = 30; +var DEFAULT_WIDTH = 0.5; var TABLET_URL = "https://s3.amazonaws.com/hifi-public/tony/tablet.fbx"; @@ -37,12 +39,13 @@ function calcSpawnInfo() { } // ctor -WebTablet = function (url) { +WebTablet = function (url, width, dpi) { var ASPECT = 4.0 / 3.0; - var WIDTH = 0.4; + var WIDTH = width || DEFAULT_WIDTH; var HEIGHT = WIDTH * ASPECT; var DEPTH = 0.025; + var DPI = dpi || DEFAULT_DPI; var spawnInfo = calcSpawnInfo(); @@ -78,7 +81,7 @@ WebTablet = function (url) { position: webEntityPosition, rotation: webEntityRotation, shapeType: "box", - dpi: 45, + dpi: DPI, parentID: this.tabletEntityID, parentJointIndex: -1 }); diff --git a/scripts/system/libraries/toolBars.js b/scripts/system/libraries/toolBars.js index 5a84bf9027..e49f8c4004 100644 --- a/scripts/system/libraries/toolBars.js +++ b/scripts/system/libraries/toolBars.js @@ -130,8 +130,6 @@ Tool = function(properties, selectable, selected) { // selectable and selected a if (update) { if (selectable) { this.toggle(); - } else if (properties.showButtonDown) { - this.buttonDown(true); } } return true; diff --git a/scripts/system/marketplaces/clara.js b/scripts/system/marketplaces/clara.js index 67c2d5503c..a04400497f 100644 --- a/scripts/system/marketplaces/clara.js +++ b/scripts/system/marketplaces/clara.js @@ -14,15 +14,15 @@ (function() { // BEGIN LOCAL_SCOPE var toolIconUrl = Script.resolvePath("../assets/images/tools/"); -var qml = Script.resolvePath("../../../resources/qml/MarketplaceComboBox.qml") +var qml = Script.resolvePath("../../../resources/qml/Marketplaces.qml") var MARKETPLACE_URL = "https://metaverse.highfidelity.com/marketplace"; var marketplaceWindow = new OverlayWindow({ title: "Marketplace", source: qml, - width: 900, - height: 700, + width: 1000, + height: 900, toolWindow: false, visible: false, }); diff --git a/scripts/system/mod.js b/scripts/system/mod.js index e69c2ce2f6..b2c9785539 100644 --- a/scripts/system/mod.js +++ b/scripts/system/mod.js @@ -16,8 +16,11 @@ // grab the toolbar var toolbar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); +var ASSETS_PATH = Script.resolvePath("assets"); +var TOOLS_PATH = Script.resolvePath("assets/images/tools/"); + function buttonImageURL() { - return Script.resolvePath("assets/images/tools/" + (Users.canKick ? 'kick.svg' : 'ignore.svg')); + return TOOLS_PATH + (Users.canKick ? 'kick.svg' : 'ignore.svg'); } // setup the mod button and add it to the toolbar @@ -68,7 +71,7 @@ function buttonClicked(){ button.clicked.connect(buttonClicked); function overlayURL() { - return Script.resolvePath("assets") + "/images/" + (Users.canKick ? "kick-target.svg" : "ignore-target.svg"); + return ASSETS_PATH + "/images/" + (Users.canKick ? "kick-target.svg" : "ignore-target.svg"); } function updateOverlays() { diff --git a/scripts/system/notifications.js b/scripts/system/notifications.js index 673b48961d..f41b0502c8 100644 --- a/scripts/system/notifications.js +++ b/scripts/system/notifications.js @@ -124,7 +124,6 @@ var NotificationType = { var randomSounds = new SoundArray({ localOnly: true }, true); var numberOfSounds = 2; for (var i = 1; i <= numberOfSounds; i++) { - randomSounds.addSound(Script.resolvePath("assets/sounds/notification-general"+ i + ".raw")); } @@ -317,6 +316,8 @@ function notify(notice, button, height, imageProperties, image) { return notificationText; } +var CLOSE_NOTIFICATION_ICON = Script.resolvePath("assets/images/close-small-light.svg"); + // This function creates and sizes the overlays function createNotification(text, notificationType, imageProperties) { var count = (text.match(/\n/g) || []).length, @@ -363,7 +364,7 @@ function createNotification(text, notificationType, imageProperties) { width: 10.0, height: 10.0, subImage: { x: 0, y: 0, width: 10, height: 10 }, - imageURL: Script.resolvePath("assets/images/close-small-light.svg"), + imageURL: CLOSE_NOTIFICATION_ICON, color: { red: 255, green: 255, blue: 255}, visible: true, alpha: backgroundAlpha @@ -534,7 +535,7 @@ function onDomainConnectionRefused(reason) { function onSnapshotTaken(path, notify) { if (notify) { var imageProperties = { - path: Script.resolvePath("file:///" + path), + path: "file:///" + path, aspectRatio: Window.innerWidth / Window.innerHeight } createNotification(wordWrap("Snapshot saved to " + path), NotificationType.SNAPSHOT, imageProperties); diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index c9876af039..5eebadd02f 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -32,9 +32,11 @@ function showFeedWindow() { DialogsManager.showFeed(); } +var SNAPSHOT_REVIEW_URL = Script.resolvePath("html/SnapshotReview.html"); + var outstanding; function confirmShare(data) { - var dialog = new OverlayWebWindow('Snapshot Review', Script.resolvePath("html/SnapshotReview.html"), 800, 320); + var dialog = new OverlayWebWindow('Snapshot Review', SNAPSHOT_REVIEW_URL, 800, 320); function onMessage(message) { // Receives message from the html dialog via the qwebchannel EventBridge. This is complicated by the following: // 1. Although we can send POJOs, we cannot receive a toplevel object. (Arrays of POJOs are fine, though.) diff --git a/scripts/system/users.js b/scripts/system/users.js index a56656a074..76722bd58d 100644 --- a/scripts/system/users.js +++ b/scripts/system/users.js @@ -13,6 +13,10 @@ (function() { // BEGIN LOCAL_SCOPE +// resolve these paths immediately +var MIN_MAX_BUTTON_SVG = Script.resolvePath("assets/images/tools/min-max-toggle.svg"); +var BASE_URL = Script.resolvePath("assets/images/tools/"); + var PopUpMenu = function (properties) { var value = properties.value, promptOverlay, @@ -25,8 +29,7 @@ var PopUpMenu = function (properties) { MIN_MAX_BUTTON_SVG_WIDTH = 17.1, MIN_MAX_BUTTON_SVG_HEIGHT = 32.5, MIN_MAX_BUTTON_WIDTH = 14, - MIN_MAX_BUTTON_HEIGHT = MIN_MAX_BUTTON_WIDTH, - MIN_MAX_BUTTON_SVG = Script.resolvePath("assets/images/tools/min-max-toggle.svg"); + MIN_MAX_BUTTON_HEIGHT = MIN_MAX_BUTTON_WIDTH; function positionDisplayOptions() { var y, @@ -223,8 +226,7 @@ var PopUpMenu = function (properties) { var usersWindow = (function () { - var baseURL = Script.resolvePath("assets/images/tools/"), - WINDOW_WIDTH = 260, + var WINDOW_WIDTH = 260, WINDOW_MARGIN = 12, WINDOW_BASE_MARGIN = 6, // A little less is needed in order look correct WINDOW_FONT = { @@ -261,7 +263,7 @@ var usersWindow = (function () { WINDOW_BORDER_ALPHA = 0.5, windowBorder, - MIN_MAX_BUTTON_SVG = baseURL + "min-max-toggle.svg", + MIN_MAX_BUTTON_SVG = BASE_URL + "min-max-toggle.svg", MIN_MAX_BUTTON_SVG_WIDTH = 17.1, MIN_MAX_BUTTON_SVG_HEIGHT = 32.5, MIN_MAX_BUTTON_WIDTH = 14, @@ -293,7 +295,7 @@ var usersWindow = (function () { scrollbarBackgroundHeight, scrollbarBarHeight, FRIENDS_BUTTON_SPACER = 6, // Space before add/remove friends button - FRIENDS_BUTTON_SVG = baseURL + "add-remove-friends.svg", + FRIENDS_BUTTON_SVG = BASE_URL + "add-remove-friends.svg", FRIENDS_BUTTON_SVG_WIDTH = 107, FRIENDS_BUTTON_SVG_HEIGHT = 27, FRIENDS_BUTTON_WIDTH = FRIENDS_BUTTON_SVG_WIDTH, diff --git a/tests/gpu-test/src/TestFbx.cpp b/tests/gpu-test/src/TestFbx.cpp index cea356e125..538bb0a973 100644 --- a/tests/gpu-test/src/TestFbx.cpp +++ b/tests/gpu-test/src/TestFbx.cpp @@ -56,6 +56,7 @@ public: explicit FileDownloader(QUrl imageUrl, QObject *parent = 0) : QObject(parent) { connect(&m_WebCtrl, SIGNAL(finished(QNetworkReply*)), this, SLOT(fileDownloaded(QNetworkReply*))); QNetworkRequest request(imageUrl); + request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); m_WebCtrl.get(request); } diff --git a/tests/gpu-test/src/TestWindow.cpp b/tests/gpu-test/src/TestWindow.cpp index 6917cf027f..791bed773e 100644 --- a/tests/gpu-test/src/TestWindow.cpp +++ b/tests/gpu-test/src/TestWindow.cpp @@ -73,7 +73,6 @@ void TestWindow::initGl() { DependencyManager::set(); resize(QSize(800, 600)); - GLDebug::setupLogger(this); #ifdef DEFERRED_LIGHTING auto deferredLightingEffect = DependencyManager::get(); deferredLightingEffect->init(); diff --git a/tests/render-perf/src/Camera.hpp b/tests/render-perf/src/Camera.hpp index fbae04540b..ebfcfb9e32 100644 --- a/tests/render-perf/src/Camera.hpp +++ b/tests/render-perf/src/Camera.hpp @@ -32,6 +32,9 @@ public: DOWN, BACK, FORWARD, + MLEFT, + MMIDDLE, + MRIGHT, KEYS_SIZE, INVALID = -1, }; diff --git a/tests/render-perf/src/main.cpp b/tests/render-perf/src/main.cpp index f7e6b6a6d2..c6cca74c69 100644 --- a/tests/render-perf/src/main.cpp +++ b/tests/render-perf/src/main.cpp @@ -11,6 +11,9 @@ #include #include +#include +#include + #include #include #include @@ -27,12 +30,13 @@ #include #include + #include #include -#include -#include -#include +//#include +//#include +//#include #include #include @@ -61,6 +65,7 @@ #include "Camera.hpp" #include "TextOverlay.hpp" + static const QString LAST_SCENE_KEY = "lastSceneFile"; static const QString LAST_LOCATION_KEY = "lastLocation"; @@ -94,7 +99,56 @@ public: } }; +#if 0 +class GlfwCamera : public Camera { + Key forKey(int key) { + switch (key) { + case GLFW_KEY_W: return FORWARD; + case GLFW_KEY_S: return BACK; + case GLFW_KEY_A: return LEFT; + case GLFW_KEY_D: return RIGHT; + case GLFW_KEY_E: return UP; + case GLFW_KEY_C: return DOWN; + case GLFW_MOUSE_BUTTON_LEFT: return MLEFT; + case GLFW_MOUSE_BUTTON_RIGHT: return MRIGHT; + case GLFW_MOUSE_BUTTON_MIDDLE: return MMIDDLE; + default: break; + } + return INVALID; + } + vec2 _lastMouse; +public: + void keyHandler(int key, int scancode, int action, int mods) { + Key k = forKey(key); + if (k == INVALID) { + return; + } + if (action == GLFW_PRESS) { + keys.set(k); + } else if (action == GLFW_RELEASE) { + keys.reset(k); + } + } + + //static void MouseMoveHandler(GLFWwindow* window, double posx, double posy); + //static void MouseScrollHandler(GLFWwindow* window, double xoffset, double yoffset); + void onMouseMove(double posx, double posy) { + vec2 mouse = vec2(posx, posy); + vec2 delta = mouse - _lastMouse; + if (keys.at(Key::MRIGHT)) { + dolly(delta.y * 0.01f); + } else if (keys.at(Key::MLEFT)) { + rotate(delta.x * -0.01f); + } else if (keys.at(Key::MMIDDLE)) { + delta.y *= -1.0f; + translate(delta * -0.01f); + } + _lastMouse = mouse; + } + +}; +#else class QWindowCamera : public Camera { Key forKey(int key) { switch (key) { @@ -143,6 +197,7 @@ public: _lastMouse = mouse; } }; +#endif static QString toHumanSize(size_t size, size_t maxUnit = std::numeric_limits::max()) { static const std::vector SUFFIXES{ { "B", "KB", "MB", "GB", "TB", "PB" } }; @@ -175,8 +230,7 @@ extern QThread* RENDER_THREAD; class RenderThread : public GenericThread { using Parent = GenericThread; public: - QOpenGLContextWrapper* _displayContext{ nullptr }; - QSurface* _displaySurface{ nullptr }; + gl::Context _context; gpu::PipelinePointer _presentPipeline; gpu::ContextPointer _gpuContext; // initialized during window creation std::atomic _presentCount; @@ -190,6 +244,7 @@ public: std::mutex _frameLock; std::queue _pendingFrames; gpu::FramePointer _activeFrame; + QSize _size; static const size_t FRAME_TIME_BUFFER_SIZE{ 8192 }; void submitFrame(const gpu::FramePointer& frame) { @@ -198,38 +253,44 @@ public: } - void initialize(QOpenGLContextWrapper* displayContext, QWindow* surface) { + void initialize(QWindow* window, gl::Context& initContext) { setObjectName("RenderThread"); - _displayContext = displayContext; - _displaySurface = surface; - _displayContext->makeCurrent(_displaySurface); + _context.setWindow(window); + _context.create(); + _context.makeCurrent(); + window->setSurfaceType(QSurface::OpenGLSurface); + _context.makeCurrent(_context.qglContext(), window); +#ifdef Q_OS_WIN + wglSwapIntervalEXT(0); +#endif // GPU library init gpu::Context::init(); _gpuContext = std::make_shared(); _backend = _gpuContext->getBackend(); - _displayContext->makeCurrent(_displaySurface); + _context.makeCurrent(); DependencyManager::get()->init(); - _displayContext->doneCurrent(); + _context.makeCurrent(); + initContext.create(); + _context.doneCurrent(); std::unique_lock lock(_mutex); Parent::initialize(); - if (isThreaded()) { - _displayContext->moveToThread(thread()); - } + _context.moveToThread(_thread); } void setup() override { RENDER_THREAD = QThread::currentThread(); - QThread::currentThread()->setPriority(QThread::HighestPriority); + // Wait until the context has been moved to this thread { std::unique_lock lock(_mutex); } - _displayContext->makeCurrent(_displaySurface); + + _context.makeCurrent(); glewExperimental = true; glewInit(); glGetError(); - _frameTimes.resize(FRAME_TIME_BUFFER_SIZE, 0); + _frameTimes.resize(FRAME_TIME_BUFFER_SIZE, 0); { auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); auto ps = gpu::Shader::createPixel(std::string(SRGB_TO_LINEAR_FRAG)); @@ -254,22 +315,21 @@ public: } _presentPipeline.reset(); _gpuContext.reset(); - if (isThreaded()) { - _displayContext->moveToThread(qApp->thread()); - } } void renderFrame(gpu::FramePointer& frame) { ++_presentCount; - _displayContext->makeCurrent(_displaySurface); + _context.makeCurrent(); _backend->recycle(); _backend->syncCache(); if (frame && !frame->batches.empty()) { _gpuContext->executeFrame(frame); { + auto geometryCache = DependencyManager::get(); gpu::Batch presentBatch; + presentBatch.setViewportTransform({ 0, 0, _size.width(), _size.height() }); presentBatch.enableStereo(false); presentBatch.resetViewTransform(); presentBatch.setFramebuffer(gpu::FramebufferPointer()); @@ -280,7 +340,8 @@ public: } (void)CHECK_GL_ERROR(); } - _displayContext->swapBuffers(_displaySurface); + _context.makeCurrent(); + _context.swapBuffers(); _fpsCounter.increment(); static size_t _frameCount{ 0 }; ++_frameCount; @@ -290,7 +351,7 @@ public: _elapsed.restart(); } (void)CHECK_GL_ERROR(); - _displayContext->doneCurrent(); + _context.doneCurrent(); } void report() { @@ -350,7 +411,6 @@ public: } }; - // Background Render Data & rendering functions class BackgroundRenderData { public: @@ -396,7 +456,6 @@ namespace render { // Create a simple OpenGL window that renders text in various ways class QTestWindow : public QWindow, public AbstractViewStateInterface { - Q_OBJECT protected: void copyCurrentViewFrustum(ViewFrustum& viewOut) const override { @@ -467,7 +526,8 @@ public: _octree->init(); // Prevent web entities from rendering REGISTER_ENTITY_TYPE_WITH_FACTORY(Web, WebEntityItem::factory); - + REGISTER_ENTITY_TYPE_WITH_FACTORY(Light, LightEntityItem::factory); + DependencyManager::set(_octree->getTree()); getEntities()->setViewFrustum(_viewFrustum); auto nodeList = DependencyManager::get(); @@ -476,28 +536,48 @@ public: nodeList->setPermissions(permissions); ResourceManager::init(); - setSurfaceType(QSurface::OpenGLSurface); - auto format = getDefaultOpenGLSurfaceFormat(); - format.setOption(QSurfaceFormat::DebugContext); - setFormat(format); - resize(QSize(800, 600)); + setFlags(Qt::MSWindowsOwnDC | Qt::Window | Qt::Dialog | Qt::WindowMinMaxButtonsHint | Qt::WindowTitleHint); + _size = QSize(800, 600); + _renderThread._size = _size; + setGeometry(QRect(QPoint(), _size)); + create(); show(); + QCoreApplication::processEvents(); + // Create the initial context + _renderThread.initialize(this, _initContext); + _initContext.makeCurrent(); - _context.setFormat(format); - _context.create(); - _context.makeCurrent(this); - glewExperimental = true; - glewInit(); - glGetError(); +#if 0 + glfwInit(); + glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE); + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5); + glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + resizeWindow(QSize(800, 600)); + _window = glfwCreateWindow(_size.width(), _size.height(), "Window Title", NULL, NULL); + if (!_window) { + throw std::runtime_error("Could not create window"); + } + + glfwSetWindowUserPointer(_window, this); + glfwSetKeyCallback(_window, KeyboardHandler); + glfwSetMouseButtonCallback(_window, MouseHandler); + glfwSetCursorPosCallback(_window, MouseMoveHandler); + glfwSetWindowCloseCallback(_window, CloseHandler); + glfwSetFramebufferSizeCallback(_window, FramebufferSizeHandler); + glfwSetScrollCallback(_window, MouseScrollHandler); + + + glfwMakeContextCurrent(_window); GLDebug::setupLogger(this); -#ifdef Q_OS_WIN - wglSwapIntervalEXT(0); #endif - _context.doneCurrent(); - _initContext.create(_context.getContext()); - _renderThread.initialize(&_context, this); +#ifdef Q_OS_WIN + //wglSwapIntervalEXT(0); +#endif + // FIXME use a wait condition QThread::msleep(1000); _renderThread.submitFrame(gpu::FramePointer()); @@ -559,7 +639,7 @@ protected: return; case Qt::Key_F4: - toggleStereo(); + cycleMode(); return; case Qt::Key_F5: @@ -626,27 +706,43 @@ private: 0, RenderArgs::DEFAULT_RENDER_MODE, RenderArgs::MONO, RenderArgs::RENDER_DEBUG_NONE); - auto framebufferCache = DependencyManager::get(); - QSize windowSize = size(); - framebufferCache->setFrameBufferSize(windowSize); - renderArgs._blitFramebuffer = framebufferCache->getFramebuffer(); - // Viewport is assigned to the size of the framebuffer - renderArgs._viewport = ivec4(0, 0, windowSize.width(), windowSize.height()); - - renderArgs.setViewFrustum(_viewFrustum); - - renderArgs._context->enableStereo(_stereoEnabled); - if (_stereoEnabled) { + QSize windowSize = _size; + if (_renderMode == NORMAL) { + renderArgs._context->enableStereo(false); + } else { + renderArgs._context->enableStereo(true); mat4 eyeOffsets[2]; mat4 eyeProjections[2]; - for (size_t i = 0; i < 2; ++i) { - eyeProjections[i] = _viewFrustum.getProjection(); + if (_renderMode == STEREO) { + for (size_t i = 0; i < 2; ++i) { + eyeProjections[i] = _viewFrustum.getProjection(); + } + } else if (_renderMode == HMD) { + eyeOffsets[0][3] = vec4 { -0.0327499993, 0.0, 0.0149999997, 1.0 }; + eyeOffsets[1][3] = vec4 { 0.0327499993, 0.0, 0.0149999997, 1.0 }; + eyeProjections[0][0] = vec4 { 0.759056330, 0.000000000, 0.000000000, 0.000000000 }; + eyeProjections[0][1] = vec4 { 0.000000000, 0.682773232, 0.000000000, 0.000000000 }; + eyeProjections[0][2] = vec4 { -0.0580431037, -0.00619550655, -1.00000489, -1.00000000 }; + eyeProjections[0][3] = vec4 { 0.000000000, 0.000000000, -0.0800003856, 0.000000000 }; + eyeProjections[1][0] = vec4 { 0.752847493, 0.000000000, 0.000000000, 0.000000000 }; + eyeProjections[1][1] = vec4 { 0.000000000, 0.678060353, 0.000000000, 0.000000000 }; + eyeProjections[1][2] = vec4 { 0.0578232110, -0.00669418881, -1.00000489, -1.000000000 }; + eyeProjections[1][3] = vec4 { 0.000000000, 0.000000000, -0.0800003856, 0.000000000 }; + windowSize = { 2048, 2048 }; } renderArgs._context->setStereoProjections(eyeProjections); renderArgs._context->setStereoViews(eyeOffsets); } + auto framebufferCache = DependencyManager::get(); + framebufferCache->setFrameBufferSize(windowSize); + + renderArgs._blitFramebuffer = framebufferCache->getFramebuffer(); + // Viewport is assigned to the size of the framebuffer + renderArgs._viewport = ivec4(0, 0, windowSize.width(), windowSize.height()); + renderArgs.setViewFrustum(_viewFrustum); + // Final framebuffer that will be handled to the display-plugin render(&renderArgs); @@ -676,11 +772,11 @@ private: }; void updateText() { - setTitle(QString("FPS %1 Culling %2 TextureMemory GPU %3 CPU %4") + QString title = QString("FPS %1 Culling %2 TextureMemory GPU %3 CPU %4") .arg(_fps).arg(_cullingEnabled) .arg(toHumanSize(gpu::Context::getTextureGPUMemoryUsage(), 2)) - .arg(toHumanSize(gpu::Texture::getTextureCPUMemoryUsage(), 2))); - + .arg(toHumanSize(gpu::Texture::getTextureCPUMemoryUsage(), 2)); + setTitle(title); #if 0 { _textBlocks.erase(TextBlock::Info); @@ -801,18 +897,13 @@ private: } - bool makeCurrent() { - bool currentResult = _context.makeCurrent(this); - Q_ASSERT(currentResult); - return currentResult; - } - void resizeWindow(const QSize& size) { _size = size; _camera.setAspectRatio((float)_size.width() / (float)_size.height()); if (!_ready) { return; } + _renderThread._size = size; //_textOverlay->resize(toGlm(_size)); //glViewport(0, 0, size.width(), size.height()); } @@ -928,8 +1019,18 @@ private: _cullingEnabled = !_cullingEnabled; } - void toggleStereo() { - _stereoEnabled = !_stereoEnabled; + void cycleMode() { + static auto defaultProjection = Camera().matrices.perspective; + _renderMode = (RenderMode)((_renderMode + 1) % RENDER_MODE_COUNT); + if (_renderMode == HMD) { + _camera.matrices.perspective[0] = vec4 { 0.759056330, 0.000000000, 0.000000000, 0.000000000 }; + _camera.matrices.perspective[1] = vec4 { 0.000000000, 0.682773232, 0.000000000, 0.000000000 }; + _camera.matrices.perspective[2] = vec4 { -0.0580431037, -0.00619550655, -1.00000489, -1.00000000 }; + _camera.matrices.perspective[3] = vec4 { 0.000000000, 0.000000000, -0.0800003856, 0.000000000 }; + } else { + _camera.matrices.perspective = defaultProjection; + _camera.setAspectRatio((float)_size.width() / (float)_size.height()); + } } QSharedPointer getEntities() { @@ -960,12 +1061,11 @@ private: render::EnginePointer _renderEngine { new render::Engine() }; render::ScenePointer _main3DScene { new render::Scene(glm::vec3(-0.5f * (float)TREE_SCALE), (float)TREE_SCALE) }; - QOpenGLContextWrapper _context; QSize _size; QSettings _settings; std::atomic _renderCount; - OffscreenGLCanvas _initContext; + gl::OffscreenContext _initContext; RenderThread _renderThread; QWindowCamera _camera; ViewFrustum _viewFrustum; // current state of view frustum, perspective, orientation, etc. @@ -975,7 +1075,14 @@ private: bool _ready { false }; //TextOverlay* _textOverlay; static bool _cullingEnabled; - bool _stereoEnabled { false }; + + enum RenderMode { + NORMAL = 0, + STEREO, + HMD, + RENDER_MODE_COUNT + }; + RenderMode _renderMode { NORMAL }; QSharedPointer _octree; }; diff --git a/tests/render-texture-load/CMakeLists.txt b/tests/render-texture-load/CMakeLists.txt new file mode 100644 index 0000000000..ecf910f434 --- /dev/null +++ b/tests/render-texture-load/CMakeLists.txt @@ -0,0 +1,28 @@ + +set(TARGET_NAME render-texture-load) + +if (WIN32) + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /ignore:4049 /ignore:4217") +endif() + +# This is not a testcase -- just set it up as a regular hifi project +setup_hifi_project(Quick Gui OpenGL) +set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") + +# link in the shared libraries +link_hifi_libraries(shared octree gl gpu gpu-gl render model model-networking networking render-utils fbx entities entities-renderer animation audio avatars script-engine physics) + +package_libraries_for_deployment() + +target_zlib() +add_dependency_external_projects(quazip) +find_package(QuaZip REQUIRED) +target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${QUAZIP_INCLUDE_DIRS}) +target_link_libraries(${TARGET_NAME} ${QUAZIP_LIBRARIES}) + +if (WIN32) +add_paths_to_fixup_libs(${QUAZIP_DLL_PATH}) +endif () + + +target_bullet() diff --git a/tests/render-texture-load/src/main.cpp b/tests/render-texture-load/src/main.cpp new file mode 100644 index 0000000000..fd6885c381 --- /dev/null +++ b/tests/render-texture-load/src/main.cpp @@ -0,0 +1,585 @@ +// +// Created by Bradley Austin Davis on 2016/07/01 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +extern QThread* RENDER_THREAD; + +static const QString DATA_SET = "https://hifi-content.s3.amazonaws.com/austin/textures.zip"; +static const QTemporaryDir DATA_DIR; + + +class FileDownloader : public QObject { + Q_OBJECT +public: + using Handler = std::function; + + FileDownloader(QUrl url, const Handler& handler, QObject *parent = 0) : QObject(parent), _handler(handler) { + connect(&_accessManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(fileDownloaded(QNetworkReply*))); + _accessManager.get(QNetworkRequest(url)); + } + + void waitForDownload() { + while (!_complete) { + QCoreApplication::processEvents(); + } + } + +private slots: + void fileDownloaded(QNetworkReply* pReply) { + _handler(pReply->readAll()); + pReply->deleteLater(); + _complete = true; + } + +private: + QNetworkAccessManager _accessManager; + Handler _handler; + bool _complete { false }; +}; + +class RenderThread : public GenericThread { + using Parent = GenericThread; +public: + gl::Context _context; + gpu::PipelinePointer _presentPipeline; + gpu::ContextPointer _gpuContext; // initialized during window creation + std::atomic _presentCount; + QElapsedTimer _elapsed; + std::atomic _fps{ 1 }; + RateCounter<200> _fpsCounter; + std::mutex _mutex; + std::shared_ptr _backend; + std::vector _frameTimes; + size_t _frameIndex; + std::mutex _frameLock; + std::queue _pendingFrames; + gpu::FramePointer _activeFrame; + QSize _size; + static const size_t FRAME_TIME_BUFFER_SIZE{ 1024 }; + + void submitFrame(const gpu::FramePointer& frame) { + std::unique_lock lock(_frameLock); + _pendingFrames.push(frame); + } + + + void initialize(QWindow* window, gl::Context& initContext) { + setObjectName("RenderThread"); + _context.setWindow(window); + _context.create(); + _context.makeCurrent(); + window->setSurfaceType(QSurface::OpenGLSurface); + _context.makeCurrent(_context.qglContext(), window); + // GPU library init + gpu::Context::init(); + _gpuContext = std::make_shared(); + _backend = _gpuContext->getBackend(); + _context.makeCurrent(); + DependencyManager::get()->init(); + _context.makeCurrent(); + initContext.create(); + _context.doneCurrent(); + std::unique_lock lock(_mutex); + Parent::initialize(); + _context.moveToThread(_thread); + } + + void setup() override { + RENDER_THREAD = QThread::currentThread(); + + // Wait until the context has been moved to this thread + { + std::unique_lock lock(_mutex); + } + + _context.makeCurrent(); + glewExperimental = true; + glewInit(); + glGetError(); + + //wglSwapIntervalEXT(0); + _frameTimes.resize(FRAME_TIME_BUFFER_SIZE, 0); + { + auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); + auto ps = gpu::StandardShaderLib::getDrawTexturePS(); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + gpu::Shader::BindingSet slotBindings; + gpu::Shader::makeProgram(*program, slotBindings); + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + _presentPipeline = gpu::Pipeline::create(program, state); + } + + //_textOverlay = new TextOverlay(glm::uvec2(800, 600)); + glViewport(0, 0, 800, 600); + (void)CHECK_GL_ERROR(); + _elapsed.start(); + } + + void shutdown() override { + _activeFrame.reset(); + while (!_pendingFrames.empty()) { + _gpuContext->consumeFrameUpdates(_pendingFrames.front()); + _pendingFrames.pop(); + } + _presentPipeline.reset(); + _gpuContext.reset(); + } + + void renderFrame(gpu::FramePointer& frame) { + ++_presentCount; + _context.makeCurrent(); + _backend->recycle(); + _backend->syncCache(); + if (frame && !frame->batches.empty()) { + _gpuContext->executeFrame(frame); + + { + + auto geometryCache = DependencyManager::get(); + gpu::Batch presentBatch; + presentBatch.setViewportTransform({ 0, 0, _size.width(), _size.height() }); + presentBatch.enableStereo(false); + presentBatch.resetViewTransform(); + presentBatch.setFramebuffer(gpu::FramebufferPointer()); + presentBatch.setResourceTexture(0, frame->framebuffer->getRenderBuffer(0)); + presentBatch.setPipeline(_presentPipeline); + presentBatch.draw(gpu::TRIANGLE_STRIP, 4); + _gpuContext->executeBatch(presentBatch); + } + (void)CHECK_GL_ERROR(); + } + _context.makeCurrent(); + _context.swapBuffers(); + _fpsCounter.increment(); + static size_t _frameCount{ 0 }; + ++_frameCount; + if (_elapsed.elapsed() >= 500) { + _fps = _fpsCounter.rate(); + _frameCount = 0; + _elapsed.restart(); + } + (void)CHECK_GL_ERROR(); + _context.doneCurrent(); + } + + void report() { + uint64_t total = 0; + for (const auto& t : _frameTimes) { + total += t; + } + auto averageFrameTime = total / FRAME_TIME_BUFFER_SIZE; + qDebug() << "Average frame " << averageFrameTime; + + std::list> sortedHighFrames; + for (size_t i = 0; i < _frameTimes.size(); ++i) { + const auto& t = _frameTimes[i]; + if (t > averageFrameTime * 6) { + sortedHighFrames.push_back({ t, i } ); + } + } + + sortedHighFrames.sort(); + for (const auto& p : sortedHighFrames) { + qDebug() << "Long frame " << p.first << " " << p.second; + } + } + + + bool process() override { + std::queue pendingFrames; + { + std::unique_lock lock(_frameLock); + pendingFrames.swap(_pendingFrames); + } + + while (!pendingFrames.empty()) { + _activeFrame = pendingFrames.front(); + if (_activeFrame) { + _gpuContext->consumeFrameUpdates(_activeFrame); + } + pendingFrames.pop(); + } + + if (!_activeFrame) { + QThread::msleep(1); + return true; + } + + { + auto start = usecTimestampNow(); + renderFrame(_activeFrame); + auto duration = usecTimestampNow() - start; + auto frameBufferIndex = _frameIndex % FRAME_TIME_BUFFER_SIZE; + _frameTimes[frameBufferIndex] = duration; + ++_frameIndex; + if (0 == _frameIndex % FRAME_TIME_BUFFER_SIZE) { + report(); + } + } + return true; + } +}; + +QString fileForPath(const QString& name) { + QCryptographicHash hash(QCryptographicHash::Md5); + hash.addData(name.toLocal8Bit().data(), name.length()); + QString hashStr = QString(hash.result().toHex()); + auto dot = name.lastIndexOf('.'); + QString extension = name.right(name.length() - dot); + QString result = DATA_DIR.path() + "/" + hashStr + extension; + return result; +} + +// Create a simple OpenGL window that renders text in various ways +class QTestWindow : public QWindow { +public: + //"/-17.2049,-8.08629,-19.4153/0,0.881994,0,-0.47126" + static void setup() { + DependencyManager::registerInheritance(); + //DependencyManager::registerInheritance(); + DependencyManager::set(); + DependencyManager::set(NodeType::Agent, 0); + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + } + + struct TextureLoad { + uint32_t time; + QString file; + QString src; + }; + + QTestWindow() { + + _currentTexture = _textures.end(); + { + QStringList stringList; + QFile textFile("h:/textures/loads.txt"); + textFile.open(QFile::ReadOnly); + //... (open the file for reading, etc.) + QTextStream textStream(&textFile); + while (true) { + QString line = textStream.readLine(); + if (line.isNull()) + break; + else + stringList.append(line); + } + + for (QString s : stringList) { + auto index = s.indexOf(" "); + QString timeStr = s.left(index); + auto time = timeStr.toUInt(); + QString path = s.right(s.length() - index).trimmed(); + path = fileForPath(path); + qDebug() << "Path " << path; + if (!QFileInfo(path).exists()) { + continue; + } + _textureLoads.push({ time, path, s }); + } + } + + installEventFilter(this); + QThreadPool::globalInstance()->setMaxThreadCount(2); + QThread::currentThread()->setPriority(QThread::HighestPriority); + ResourceManager::init(); + setFlags(Qt::MSWindowsOwnDC | Qt::Window | Qt::Dialog | Qt::WindowMinMaxButtonsHint | Qt::WindowTitleHint); + _size = QSize(800, 600); + _renderThread._size = _size; + setGeometry(QRect(QPoint(), _size)); + create(); + show(); + QCoreApplication::processEvents(); + // Create the initial context + _renderThread.initialize(this, _initContext); + _initContext.makeCurrent(); + // FIXME use a wait condition + QThread::msleep(1000); + _renderThread.submitFrame(gpu::FramePointer()); + _initContext.makeCurrent(); + { + auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); + auto ps = gpu::StandardShaderLib::getDrawTexturePS(); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + gpu::Shader::makeProgram(*program); + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + state->setDepthTest(gpu::State::DepthTest(false)); + state->setScissorEnable(true); + _simplePipeline = gpu::Pipeline::create(program, state); + } + + QTimer* timer = new QTimer(this); + timer->setInterval(0); + connect(timer, &QTimer::timeout, this, [this] { + draw(); + }); + timer->start(); + _ready = true; + } + + virtual ~QTestWindow() { + DependencyManager::destroy(); + DependencyManager::destroy(); + DependencyManager::destroy(); + DependencyManager::destroy(); + ResourceManager::cleanup(); + } + +protected: + + bool eventFilter(QObject *obj, QEvent *event) override { + if (event->type() == QEvent::Close) { + _renderThread.terminate(); + } + + return QWindow::eventFilter(obj, event); + } + + void keyPressEvent(QKeyEvent* event) override { + } + + void keyReleaseEvent(QKeyEvent* event) override { + } + + void mouseMoveEvent(QMouseEvent* event) override { + } + + void resizeEvent(QResizeEvent* ev) override { + resizeWindow(ev->size()); + } + +private: + std::queue _textureLoads; + std::list _textures; + std::list::iterator _currentTexture; + + uint16_t _fps; + gpu::PipelinePointer _simplePipeline; + + void draw() { + if (!_ready) { + return; + } + if (!isVisible()) { + return; + } + if (_renderCount.load() != 0 && _renderCount.load() >= _renderThread._presentCount.load()) { + QThread::usleep(1); + return; + } + _renderCount = _renderThread._presentCount.load(); + update(); + + QSize windowSize = _size; + auto framebufferCache = DependencyManager::get(); + framebufferCache->setFrameBufferSize(windowSize); + + // Final framebuffer that will be handled to the display-plugin + render(); + + if (_fps != _renderThread._fps) { + _fps = _renderThread._fps; + updateText(); + } + } + + void updateText() { + setTitle(QString("FPS %1").arg(_fps)); + } + + void update() { + auto now = usecTimestampNow(); + static auto last = now; + auto delta = (now - last) / USECS_PER_MSEC; + if (!_textureLoads.empty()) { + const auto& front = _textureLoads.front(); + if (delta >= front.time) { + QFileInfo fileInfo(front.file); + if (!fileInfo.exists()) { + qDebug() << "Missing file " << front.file; + } else { + qDebug() << "Loading " << front.src; + _textures.push_back(DependencyManager::get()->getImageTexture(front.file)); + _currentTexture = _textures.begin(); + } + _textureLoads.pop(); + if (_textureLoads.empty()) { + qDebug() << "Done"; + } + } + } + } + + void render() { + auto& gpuContext = _renderThread._gpuContext; + gpuContext->beginFrame(); + gpu::doInBatch(gpuContext, [&](gpu::Batch& batch) { + batch.resetStages(); + }); + PROFILE_RANGE(__FUNCTION__); + auto framebuffer = DependencyManager::get()->getFramebuffer(); + + gpu::doInBatch(gpuContext, [&](gpu::Batch& batch) { + batch.enableStereo(false); + batch.setFramebuffer(framebuffer); + batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, vec4(1, 0, 0, 1)); + auto vpsize = framebuffer->getSize(); + auto vppos = ivec2(0); + batch.setViewportTransform(ivec4(vppos, vpsize)); + if (_currentTexture != _textures.end()) { + ++_currentTexture; + } + if (_currentTexture == _textures.end()) { + _currentTexture = _textures.begin(); + } + + if (_currentTexture != _textures.end()) { + batch.setResourceTexture(0, *_currentTexture); + } + batch.setPipeline(_simplePipeline); + batch.draw(gpu::TRIANGLE_STRIP, 4); + }); + + auto frame = gpuContext->endFrame(); + frame->framebuffer = framebuffer; + frame->framebufferRecycler = [](const gpu::FramebufferPointer& framebuffer){ + DependencyManager::get()->releaseFramebuffer(framebuffer); + }; + _renderThread.submitFrame(frame); + if (!_renderThread.isThreaded()) { + _renderThread.process(); + } + } + + void resizeWindow(const QSize& size) { + _size = size; + if (!_ready) { + return; + } + _renderThread._size = size; + } + +private: + QSize _size; + std::atomic _renderCount; + gl::OffscreenContext _initContext; + RenderThread _renderThread; + ViewFrustum _viewFrustum; // current state of view frustum, perspective, orientation, etc. + bool _ready { false }; +}; + +void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { + if (!message.isEmpty()) { +#ifdef Q_OS_WIN + OutputDebugStringA(message.toLocal8Bit().constData()); + OutputDebugStringA("\n"); +#endif + std::cout << message.toLocal8Bit().constData() << std::endl; + } +} + +const char * LOG_FILTER_RULES = R"V0G0N( +hifi.gpu=true +)V0G0N"; + +void unzipTestData(const QByteArray& zipData) { + QTemporaryFile zipFile; + if (zipFile.open()) { + zipFile.write(zipData); + zipFile.close(); + } + qDebug() << zipFile.fileName(); + if (!DATA_DIR.isValid()) { + qFatal("Unable to create temp dir"); + } + + //auto files = JlCompress::getFileList(zipData); + auto files = JlCompress::extractDir(zipFile.fileName(), DATA_DIR.path()); + qDebug() << DATA_DIR.path(); + +} + +int main(int argc, char** argv) { + QApplication app(argc, argv); + QCoreApplication::setApplicationName("RenderPerf"); + QCoreApplication::setOrganizationName("High Fidelity"); + QCoreApplication::setOrganizationDomain("highfidelity.com"); + qInstallMessageHandler(messageHandler); + QLoggingCategory::setFilterRules(LOG_FILTER_RULES); + + + FileDownloader(DATA_SET, [&](const QByteArray& data) { + qDebug() << "Fetched size " << data.size(); + unzipTestData(data); + }).waitForDownload(); + + QTestWindow::setup(); + QTestWindow window; + app.exec(); + return 0; +} + +#include "main.moc" diff --git a/tests/render-utils/src/main.cpp b/tests/render-utils/src/main.cpp index 4b7354f35d..c38bd6765c 100644 --- a/tests/render-utils/src/main.cpp +++ b/tests/render-utils/src/main.cpp @@ -112,8 +112,6 @@ public: gpu::Context::init(); - - GLDebug::setupLogger(this); qDebug() << (const char*)glGetString(GL_VERSION); //_textRenderer[0] = TextRenderer::getInstance(SANS_FONT_FAMILY, 12, false); diff --git a/tests/shaders/src/main.cpp b/tests/shaders/src/main.cpp index 5355f29f19..9744d8c78d 100644 --- a/tests/shaders/src/main.cpp +++ b/tests/shaders/src/main.cpp @@ -108,9 +108,7 @@ public: show(); makeCurrent(); - gpu::Context::init(); - GLDebug::setupLogger(this); makeCurrent(); resize(QSize(800, 600)); } diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 2056044a4b..bf645f25c2 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -1,7 +1,4 @@ # add the tool directories -add_subdirectory(mtc) -set_target_properties(mtc PROPERTIES FOLDER "Tools") - add_subdirectory(scribe) set_target_properties(scribe PROPERTIES FOLDER "Tools") diff --git a/tools/mtc/CMakeLists.txt b/tools/mtc/CMakeLists.txt deleted file mode 100644 index c7134a869f..0000000000 --- a/tools/mtc/CMakeLists.txt +++ /dev/null @@ -1,4 +0,0 @@ -set(TARGET_NAME mtc) -setup_hifi_project() - -package_libraries_for_deployment() diff --git a/tools/mtc/src/main.cpp b/tools/mtc/src/main.cpp deleted file mode 100644 index dd0ab837a5..0000000000 --- a/tools/mtc/src/main.cpp +++ /dev/null @@ -1,362 +0,0 @@ -// -// main.cpp -// tools/mtc/src -// -// Created by Andrzej Kapolka on 12/31/13. -// 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 - -#include - -#include -#include -#include -#include -#include -#include -#include - -using namespace std; - -class Class { -public: - QString name; - QStringList bases; -}; - -class Field { -public: - QString type; - QString name; -}; - -class Streamable { -public: - Class clazz; - QList fields; -}; - -void processInput(QTextStream& in, QList* streamables) { - Class clazz; - Streamable currentStreamable; - - QRegExp exp( - "(/\\*.*\\*/)|" // multi-line comments - "(//.*\n)|" // single-line comments - "(\\s*#.*\n)|" // preprocessor definitions - "(\\s*STREAMABLE\\s+)|" // STREAMABLE tag for classes - "(\\s*STREAM\\s+.*;)|" // STREAM tag for fields - "(\\s*class\\s+[^;]+\\{)" // class definition - ); - exp.setMinimal(true); - - QRegExp classExp("class (\\w+) ?:?([^:]*)\\{"); - - // read in the entire input and look for matches with our expression - QString all = in.readAll(); - for (int off = 0; (off = exp.indexIn(all, off)) != -1; off += exp.matchedLength()) { - QString match = exp.cap().simplified(); - if (match.startsWith("/*") || match.startsWith("//") || match.startsWith('#')) { - continue; // comment, preprocessor definition - } - if (match.startsWith("STREAMABLE")) { - if (clazz.name.isEmpty()) { - cerr << "Found STREAMABLE marker before class definition." << endl; - continue; - } - if (!currentStreamable.clazz.name.isEmpty()) { - streamables->append(currentStreamable); - } - currentStreamable.clazz = clazz; - currentStreamable.fields.clear(); - - } else if (match.startsWith("STREAM")) { - match.chop(1); // get rid of the semicolon - match = match.mid(match.indexOf(' ') + 1).trimmed(); // and STREAM, and any space before it - int index = match.lastIndexOf(' '); - Field field = { match.left(index).simplified(), match.mid(index + 1) }; - currentStreamable.fields.append(field); - - } else { // match.startsWith("class") - classExp.exactMatch(match); - clazz.name = classExp.cap(1); - clazz.bases.clear(); - foreach (const QString& bstr, classExp.cap(2).split(',')) { - QString base = bstr.trimmed(); - if (!base.isEmpty() && base.startsWith("STREAM")) { - clazz.bases.append(base.mid(base.lastIndexOf(' ') + 1)); - } - } - } - } - if (!currentStreamable.clazz.name.isEmpty()) { - streamables->append(currentStreamable); - } -} - -void generateOutput (QTextStream& out, const QList& streamables) { - foreach (const Streamable& str, streamables) { - const QString& name = str.clazz.name; - - out << "const int " << name << "::Type = registerStreamableMetaType<" << name << ">();\n"; - - out << "const QVector& " << name << "::getMetaFields() {\n"; - out << " static QVector metaFields = QVector()"; - foreach (const QString& base, str.clazz.bases) { - out << " << " << base << "::getMetaFields()"; - } - foreach (const Field& field, str.fields) { - out << "\n << MetaField(\"" << field.name << "\", Bitstream::getTypeStreamer(qMetaTypeId<" << - field.type << ">()))"; - } - out << ";\n"; - out << " return metaFields;\n"; - out << "}\n"; - - out << "int " << name << "::getFieldIndex(const QByteArray& name) {\n"; - out << " static QHash fieldIndices = createFieldIndices();\n"; - out << " return fieldIndices.value(name) - 1;\n"; - out << "}\n"; - - out << "QHash " << name << "::createFieldIndices() {\n"; - out << " QHash indices;\n"; - out << " int index = 1;\n"; - out << " foreach (const MetaField& field, getMetaFields()) {\n"; - out << " indices.insert(field.getName(), index++);\n"; - out << " }\n"; - out << " return indices;\n"; - out << "}\n"; - - out << "void " << name << "::setField(int index, const QVariant& value) {\n"; - if (!str.clazz.bases.isEmpty()) { - out << " int nextIndex;\n"; - } - foreach (const QString& base, str.clazz.bases) { - out << " if ((nextIndex = index - " << base << "::getMetaFields().size()) < 0) {\n"; - out << " " << base << "::setField(index, value);\n"; - out << " return;\n"; - out << " }\n"; - out << " index = nextIndex;\n"; - } - if (!str.fields.isEmpty()) { - out << " switch (index) {\n"; - for (int i = 0; i < str.fields.size(); i++) { - out << " case " << i << ":\n"; - out << " this->" << str.fields.at(i).name << " = value.value<" << str.fields.at(i).type << ">();\n"; - out << " break;\n"; - } - out << " }\n"; - } - out << "}\n"; - - out << "QVariant " << name << "::getField(int index) const {\n"; - if (!str.clazz.bases.isEmpty()) { - out << " int nextIndex;\n"; - } - foreach (const QString& base, str.clazz.bases) { - out << " if ((nextIndex = index - " << base << "::getMetaFields().size()) < 0) {\n"; - out << " return " << base << "::getField(index);\n"; - out << " }\n"; - out << " index = nextIndex;\n"; - } - if (!str.fields.isEmpty()) { - out << " switch (index) {\n"; - for (int i = 0; i < str.fields.size(); i++) { - out << " case " << i << ":\n"; - out << " return QVariant::fromValue(this->" << str.fields.at(i).name << ");\n"; - } - out << " }\n"; - } - out << " return QVariant();\n"; - out << "}\n"; - - out << "Bitstream& operator<<(Bitstream& out, const " << name << "& obj) {\n"; - foreach (const QString& base, str.clazz.bases) { - out << " out << static_cast(obj);\n"; - } - foreach (const Field& field, str.fields) { - out << " out << obj." << field.name << ";\n"; - } - out << " return out;\n"; - out << "}\n"; - - out << "Bitstream& operator>>(Bitstream& in, " << name << "& obj) {\n"; - foreach (const QString& base, str.clazz.bases) { - out << " in >> static_cast<" << base << "&>(obj);\n"; - } - foreach (const Field& field, str.fields) { - out << " in >> obj." << field.name << ";\n"; - } - out << " return in;\n"; - out << "}\n"; - - out << "template<> void Bitstream::writeRawDelta(const " << name << "& value, const " << name << "& reference) {\n"; - foreach (const QString& base, str.clazz.bases) { - out << " writeRawDelta(static_cast(value), static_cast(reference));\n"; - } - foreach (const Field& field, str.fields) { - out << " writeDelta(value." << field.name << ", reference." << field.name << ");\n"; - } - out << "}\n"; - - out << "template<> void Bitstream::readRawDelta(" << name << "& value, const " << name << "& reference) {\n"; - foreach (const QString& base, str.clazz.bases) { - out << " readRawDelta(static_cast<" << base << "&>(value), static_cast(reference));\n"; - } - foreach (const Field& field, str.fields) { - out << " readDelta(value." << field.name << ", reference." << field.name << ");\n"; - } - out << "}\n"; - - out << "template<> QJsonValue JSONWriter::getData(const " << name << "& value) {\n"; - out << " QJsonArray array;\n"; - foreach (const QString& base, str.clazz.bases) { - out << " foreach (const QJsonValue& element, getData(static_cast(value)).toArray()) {\n"; - out << " array.append(element);\n"; - out << " }\n"; - } - foreach (const Field& field, str.fields) { - out << " array.append(getData(value." << field.name << "));\n"; - } - out << " return array;\n"; - out << "}\n"; - - out << "template<> void JSONReader::putData(const QJsonValue& data, " << name << "& value) {\n"; - if (!(str.clazz.bases.isEmpty() && str.fields.isEmpty())) { - out << " QJsonArray array = data.toArray(), subarray;\n"; - out << " QJsonArray::const_iterator it = array.constBegin();\n"; - foreach (const QString& base, str.clazz.bases) { - out << " subarray = QJsonArray();\n"; - out << " for (int i = 0; i < " << base << "::getMetaFields().size(); i++) {\n"; - out << " subarray.append(*it++);\n"; - out << " }\n"; - out << " putData(subarray, static_cast<" << base << "&>(value));\n"; - } - foreach (const Field& field, str.fields) { - out << " putData(*it++, value." << field.name << ");\n"; - } - } - out << "}\n"; - - out << "bool operator==(const " << name << "& first, const " << name << "& second) {\n"; - if (str.clazz.bases.isEmpty() && str.fields.isEmpty()) { - out << " return true"; - } else { - out << " return "; - bool first = true; - foreach (const QString& base, str.clazz.bases) { - if (!first) { - out << " &&\n"; - out << " "; - } - out << "static_cast(first) == static_cast(second)"; - first = false; - } - foreach (const Field& field, str.fields) { - if (!first) { - out << " &&\n"; - out << " "; - } - out << "first." << field.name << " == second." << field.name; - first = false; - } - } - out << ";\n"; - out << "}\n"; - - out << "bool operator!=(const " << name << "& first, const " << name << "& second) {\n"; - if (str.clazz.bases.isEmpty() && str.fields.isEmpty()) { - out << " return false"; - } else { - out << " return "; - bool first = true; - foreach (const QString& base, str.clazz.bases) { - if (!first) { - out << " ||\n"; - out << " "; - } - out << "static_cast(first) != static_cast(second)"; - first = false; - } - foreach (const Field& field, str.fields) { - if (!first) { - out << " ||\n"; - out << " "; - } - out << "first." << field.name << " != second." << field.name; - first = false; - } - } - out << ";\n"; - out << "}\n\n"; - } -} - -int main (int argc, char** argv) { - // process the command line arguments - QStringList inputs; - QString output; - for (int ii = 1; ii < argc; ii++) { - QString arg(argv[ii]); - if (!arg.startsWith('-')) { - inputs.append(arg); - continue; - } - QStringRef name = arg.midRef(1); - if (name == "o") { - if (++ii == argc) { - cerr << "Missing file name argument for -o" << endl; - return 1; - } - output = argv[ii]; - - } else { - cerr << "Unknown option " << arg.toStdString() << endl; - return 1; - } - } - if (inputs.isEmpty()) { - cerr << "Usage: mtc [OPTION]... input files" << endl; - cerr << "Where options include:" << endl; - cerr << " -o filename: Send output to filename rather than standard output." << endl; - return 0; - } - - QList streamables; - foreach (const QString& input, inputs) { - QFile ifile(input); - if (!ifile.open(QIODevice::ReadOnly | QIODevice::Text)) { - cerr << ("Couldn't open " + input + ": " + ifile.errorString()).toStdString() << endl; - continue; - } - QTextStream istream(&ifile); - int oldSize = streamables.size(); - processInput(istream, &streamables); - if (streamables.size() == oldSize) { - // no streamables; remove from list - inputs.removeOne(input); - } - } - - QFile ofile(output); - if (output.isNull()) { - ofile.open(stdout, QIODevice::WriteOnly | QIODevice::Text); - - } else if (!ofile.open(QIODevice::WriteOnly | QIODevice::Text)) { - cerr << ("Couldn't open " + output + ": " + ofile.errorString()).toStdString() << endl; - return 1; - } - - QTextStream ostream(&ofile); - ostream << "// generated by mtc\n"; - foreach (const QString& input, inputs) { - ostream << "#include \"" << input << "\"\n"; - } - generateOutput(ostream, streamables); - - return 0; -}