diff --git a/assignment-client/src/assets/AssetServer.cpp b/assignment-client/src/assets/AssetServer.cpp index cad6a852cb..c2aec9b058 100644 --- a/assignment-client/src/assets/AssetServer.cpp +++ b/assignment-client/src/assets/AssetServer.cpp @@ -33,7 +33,7 @@ #include #include #include -#include +#include #include "AssetServerLogging.h" #include "BakeAssetTask.h" diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index 581d854909..06632dabb0 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -22,7 +22,6 @@ #include #include #include -#include #include #include "../AssignmentDynamicFactory.h" @@ -471,77 +470,7 @@ void EntityServer::startDynamicDomainVerification() { qCDebug(entities) << "Starting Dynamic Domain Verification..."; EntityTreePointer tree = std::static_pointer_cast(_tree); - QHash localMap(tree->getEntityCertificateIDMap()); - - QHashIterator i(localMap); - qCDebug(entities) << localMap.size() << "entities in _entityCertificateIDMap"; - while (i.hasNext()) { - i.next(); - const auto& certificateID = i.key(); - const auto& entityID = i.value(); - - EntityItemPointer entity = tree->findEntityByEntityItemID(entityID); - - if (entity) { - if (!entity->getProperties().verifyStaticCertificateProperties()) { - qCDebug(entities) << "During Dynamic Domain Verification, a certified entity with ID" << entityID << "failed" - << "static certificate verification."; - // Delete the entity if it doesn't pass static certificate verification - tree->withWriteLock([&] { - tree->deleteEntity(entityID, true); - }); - } else { - QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); - QNetworkRequest networkRequest; - networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); - networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL(); - requestURL.setPath("/api/v1/commerce/proof_of_purchase_status/location"); - QJsonObject request; - request["certificate_id"] = certificateID; - networkRequest.setUrl(requestURL); - - QNetworkReply* networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson()); - - connect(networkReply, &QNetworkReply::finished, this, [this, entityID, networkReply] { - EntityTreePointer tree = std::static_pointer_cast(_tree); - - QJsonObject jsonObject = QJsonDocument::fromJson(networkReply->readAll()).object(); - jsonObject = jsonObject["data"].toObject(); - - if (networkReply->error() == QNetworkReply::NoError) { - QString thisDomainID = DependencyManager::get()->getDomainID().remove(QRegExp("\\{|\\}")); - if (jsonObject["domain_id"].toString() != thisDomainID) { - EntityItemPointer entity = tree->findEntityByEntityItemID(entityID); - if (!entity) { - qCDebug(entities) << "Entity undergoing dynamic domain verification is no longer available:" << entityID; - networkReply->deleteLater(); - return; - } - if (entity->getAge() > (_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS/MSECS_PER_SECOND)) { - qCDebug(entities) << "Entity's cert's domain ID" << jsonObject["domain_id"].toString() - << "doesn't match the current Domain ID" << thisDomainID << "; deleting entity" << entityID; - tree->withWriteLock([&] { - tree->deleteEntity(entityID, true); - }); - } else { - qCDebug(entities) << "Entity failed dynamic domain verification, but was created too recently to necessitate deletion:" << entityID; - } - } else { - qCDebug(entities) << "Entity passed dynamic domain verification:" << entityID; - } - } else { - qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << "; NOT deleting entity" << entityID - << "More info:" << jsonObject; - } - - networkReply->deleteLater(); - }); - } - } else { - qCWarning(entities) << "During DDV, an entity with ID" << entityID << "was NOT found in the Entity Tree!"; - } - } + tree->startDynamicDomainVerificationOnServer((float) _MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS / MSECS_PER_SECOND); int nextInterval = qrand() % ((_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS + 1) - _MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS) + _MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS; qCDebug(entities) << "Restarting Dynamic Domain Verification timer for" << nextInterval / 1000 << "seconds"; diff --git a/cmake/externals/LibOVR/CMakeLists.txt b/cmake/externals/LibOVR/CMakeLists.txt index ae4cf6320e..481753f7e0 100644 --- a/cmake/externals/LibOVR/CMakeLists.txt +++ b/cmake/externals/LibOVR/CMakeLists.txt @@ -17,8 +17,8 @@ if (WIN32) ExternalProject_Add( ${EXTERNAL_NAME} - URL https://public.highfidelity.com/dependencies/ovr_sdk_win_1.26.0_public.zip - URL_MD5 06804ff9727b910dcd04a37c800053b5 + URL https://hifi-public.s3.amazonaws.com/dependencies/ovr_sdk_win_1.35.0.zip + URL_MD5 1e3e8b2101387af07ff9c841d0ea285e CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH= PATCH_COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/LibOVRCMakeLists.txt" /CMakeLists.txt LOG_DOWNLOAD 1 diff --git a/cmake/macros/AddCrashpad.cmake b/cmake/macros/AddCrashpad.cmake index 113ab53aae..bc070e057b 100644 --- a/cmake/macros/AddCrashpad.cmake +++ b/cmake/macros/AddCrashpad.cmake @@ -53,10 +53,5 @@ macro(add_crashpad) POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CRASHPAD_HANDLER_EXE_PATH} "$/" ) - install( - PROGRAMS ${CRASHPAD_HANDLER_EXE_PATH} - DESTINATION ${INTERFACE_INSTALL_DIR} - COMPONENT ${CLIENT_COMPONENT} - ) endif () endmacro() diff --git a/cmake/macros/TargetOpenEXR.cmake b/cmake/macros/TargetOpenEXR.cmake new file mode 100644 index 0000000000..8d61f216e7 --- /dev/null +++ b/cmake/macros/TargetOpenEXR.cmake @@ -0,0 +1,74 @@ +# +# Copyright 2015 High Fidelity, Inc. +# Created by Olivier Prat on 2019/03/26 +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# +macro(TARGET_OPENEXR) + if (NOT ANDROID) + set(openexr_config_file "${VCPKG_INSTALL_ROOT}/include/OpenEXR/OpenEXRConfig.h") + if(EXISTS ${openexr_config_file}) + file(STRINGS + ${openexr_config_file} + TMP + REGEX "#define OPENEXR_VERSION_STRING.*$") + string(REGEX MATCHALL "[0-9.]+" OPENEXR_VERSION ${TMP}) + + file(STRINGS + ${openexr_config_file} + TMP + REGEX "#define OPENEXR_VERSION_MAJOR.*$") + string(REGEX MATCHALL "[0-9]" OPENEXR_MAJOR_VERSION ${TMP}) + + file(STRINGS + ${openexr_config_file} + TMP + REGEX "#define OPENEXR_VERSION_MINOR.*$") + string(REGEX MATCHALL "[0-9]" OPENEXR_MINOR_VERSION ${TMP}) + endif() + + foreach(OPENEXR_LIB + IlmImf + IlmImfUtil + Half + Iex + IexMath + Imath + IlmThread) + + # OpenEXR libraries may be suffixed with the version number, so we search + # using both versioned and unversioned names. + find_library(OPENEXR_${OPENEXR_LIB}_LIBRARY_RELEASE + NAMES + ${OPENEXR_LIB}-${OPENEXR_MAJOR_VERSION}_${OPENEXR_MINOR_VERSION}_s + ${OPENEXR_LIB}_s + + PATHS ${VCPKG_INSTALL_ROOT}/lib NO_DEFAULT_PATH + ) + #mark_as_advanced(OPENEXR_${OPENEXR_LIB}_LIBRARY) + + if(OPENEXR_${OPENEXR_LIB}_LIBRARY_RELEASE) + list(APPEND OPENEXR_LIBRARY_RELEASE ${OPENEXR_${OPENEXR_LIB}_LIBRARY_RELEASE}) + endif() + + # OpenEXR libraries may be suffixed with the version number, so we search + # using both versioned and unversioned names. + find_library(OPENEXR_${OPENEXR_LIB}_LIBRARY_DEBUG + NAMES + ${OPENEXR_LIB}-${OPENEXR_MAJOR_VERSION}_${OPENEXR_MINOR_VERSION}_s_d + ${OPENEXR_LIB}_s_d + + PATHS ${VCPKG_INSTALL_ROOT}/debug/lib NO_DEFAULT_PATH + ) + #mark_as_advanced(OPENEXR_${OPENEXR_LIB}_DEBUG_LIBRARY) + + if(OPENEXR_${OPENEXR_LIB}_LIBRARY_DEBUG) + list(APPEND OPENEXR_LIBRARY_DEBUG ${OPENEXR_${OPENEXR_LIB}_LIBRARY_DEBUG}) + endif() + endforeach(OPENEXR_LIB) + + select_library_configurations(OPENEXR) + target_link_libraries(${TARGET_NAME} ${OPENEXR_LIBRARY}) + endif() +endmacro() diff --git a/cmake/ports/hifi-deps/CONTROL b/cmake/ports/hifi-deps/CONTROL index e202d0c8f8..5f860a1620 100644 --- a/cmake/ports/hifi-deps/CONTROL +++ b/cmake/ports/hifi-deps/CONTROL @@ -1,4 +1,4 @@ Source: hifi-deps -Version: 0 +Version: 0.1 Description: Collected dependencies for High Fidelity applications -Build-Depends: bullet3, draco, etc2comp, glm, nvtt, openssl (windows), tbb (!android&!osx), zlib +Build-Depends: bullet3, draco, etc2comp, glm, nvtt, openexr (!android), openssl (windows), tbb (!android&!osx), zlib diff --git a/cmake/ports/openexr/CONTROL b/cmake/ports/openexr/CONTROL new file mode 100644 index 0000000000..d59ab286e1 --- /dev/null +++ b/cmake/ports/openexr/CONTROL @@ -0,0 +1,4 @@ +Source: openexr +Version: 2.3.0-2 +Description: OpenEXR is a high dynamic-range (HDR) image file format developed by Industrial Light & Magic for use in computer imaging applications +Build-Depends: zlib \ No newline at end of file diff --git a/cmake/ports/openexr/FindOpenEXR.cmake b/cmake/ports/openexr/FindOpenEXR.cmake new file mode 100644 index 0000000000..a381c6db9a --- /dev/null +++ b/cmake/ports/openexr/FindOpenEXR.cmake @@ -0,0 +1,87 @@ +include(FindPackageHandleStandardArgs) + +find_path(OpenEXR_INCLUDE_DIRS OpenEXR/OpenEXRConfig.h) +find_path(OPENEXR_INCLUDE_PATHS NAMES ImfRgbaFile.h PATH_SUFFIXES OpenEXR) + +file(STRINGS "${OpenEXR_INCLUDE_DIRS}/OpenEXR/OpenEXRConfig.h" OPENEXR_CONFIG_H) + +string(REGEX REPLACE "^.*define OPENEXR_VERSION_MAJOR ([0-9]+).*$" "\\1" OpenEXR_VERSION_MAJOR "${OPENEXR_CONFIG_H}") +string(REGEX REPLACE "^.*define OPENEXR_VERSION_MINOR ([0-9]+).*$" "\\1" OpenEXR_VERSION_MINOR "${OPENEXR_CONFIG_H}") +set(OpenEXR_LIB_SUFFIX "${OpenEXR_VERSION_MAJOR}_${OpenEXR_VERSION_MINOR}") + +include(SelectLibraryConfigurations) + +if(NOT OpenEXR_BASE_LIBRARY) + find_library(OpenEXR_BASE_LIBRARY_RELEASE NAMES IlmImf-${OpenEXR_LIB_SUFFIX}) + find_library(OpenEXR_BASE_LIBRARY_DEBUG NAMES IlmImf-${OpenEXR_LIB_SUFFIX}_d) + select_library_configurations(OpenEXR_BASE) +endif() + +if(NOT OpenEXR_UTIL_LIBRARY) + find_library(OpenEXR_UTIL_LIBRARY_RELEASE NAMES IlmImfUtil-${OpenEXR_LIB_SUFFIX}) + find_library(OpenEXR_UTIL_LIBRARY_DEBUG NAMES IlmImfUtil-${OpenEXR_LIB_SUFFIX}_d) + select_library_configurations(OpenEXR_UTIL) +endif() + +if(NOT OpenEXR_HALF_LIBRARY) + find_library(OpenEXR_HALF_LIBRARY_RELEASE NAMES Half-${OpenEXR_LIB_SUFFIX}) + find_library(OpenEXR_HALF_LIBRARY_DEBUG NAMES Half-${OpenEXR_LIB_SUFFIX}_d) + select_library_configurations(OpenEXR_HALF) +endif() + +if(NOT OpenEXR_IEX_LIBRARY) + find_library(OpenEXR_IEX_LIBRARY_RELEASE NAMES Iex-${OpenEXR_LIB_SUFFIX}) + find_library(OpenEXR_IEX_LIBRARY_DEBUG NAMES Iex-${OpenEXR_LIB_SUFFIX}_d) + select_library_configurations(OpenEXR_IEX) +endif() + +if(NOT OpenEXR_MATH_LIBRARY) + find_library(OpenEXR_MATH_LIBRARY_RELEASE NAMES Imath-${OpenEXR_LIB_SUFFIX}) + find_library(OpenEXR_MATH_LIBRARY_DEBUG NAMES Imath-${OpenEXR_LIB_SUFFIX}_d) + select_library_configurations(OpenEXR_MATH) +endif() + +if(NOT OpenEXR_THREAD_LIBRARY) + find_library(OpenEXR_THREAD_LIBRARY_RELEASE NAMES IlmThread-${OpenEXR_LIB_SUFFIX}) + find_library(OpenEXR_THREAD_LIBRARY_DEBUG NAMES IlmThread-${OpenEXR_LIB_SUFFIX}_d) + select_library_configurations(OpenEXR_THREAD) +endif() + +if(NOT OpenEXR_IEXMATH_LIBRARY) + find_library(OpenEXR_IEXMATH_LIBRARY_RELEASE NAMES IexMath-${OpenEXR_LIB_SUFFIX}) + find_library(OpenEXR_IEXMATH_LIBRARY_DEBUG NAMES IexMath-${OpenEXR_LIB_SUFFIX}d) + select_library_configurations(OpenEXR_IEXMATH) +endif() + +set(OPENEXR_HALF_LIBRARY "${OpenEXR_HALF_LIBRARY}") +set(OPENEXR_IEX_LIBRARY "${OpenEXR_IEX_LIBRARY}") +set(OPENEXR_IMATH_LIBRARY "${OpenEXR_MATH_LIBRARY}") +set(OPENEXR_ILMIMF_LIBRARY "${OpenEXR_BASE_LIBRARY}") +set(OPENEXR_ILMIMFUTIL_LIBRARY "${OpenEXR_UTIL_LIBRARY}") +set(OPENEXR_ILMTHREAD_LIBRARY "${OpenEXR_THREAD_LIBRARY}") + +set(OpenEXR_LIBRARY "${OpenEXR_BASE_LIBRARY}") + +set(OpenEXR_LIBRARIES + ${OpenEXR_LIBRARY} + ${OpenEXR_MATH_LIBRARY} + ${OpenEXR_IEXMATH_LIBRARY} + ${OpenEXR_UTIL_LIBRARY} + ${OpenEXR_HALF_LIBRARY} + ${OpenEXR_IEX_LIBRARY} + ${OpenEXR_THREAD_LIBRARY} +) + +set(OPENEXR_LIBRARIES + ${OPENEXR_HALF_LIBRARY} + ${OPENEXR_IEX_LIBRARY} + ${OPENEXR_IMATH_LIBRARY} + ${OPENEXR_ILMIMF_LIBRARY} + ${OPENEXR_ILMTHREAD_LIBRARY} +) + +FIND_PACKAGE_HANDLE_STANDARD_ARGS(OpenEXR REQUIRED_VARS OpenEXR_LIBRARIES OpenEXR_INCLUDE_DIRS) + +if(OpenEXR_FOUND) + set(OPENEXR_FOUND 1) +endif() diff --git a/cmake/ports/openexr/fix_install_ilmimf.patch b/cmake/ports/openexr/fix_install_ilmimf.patch new file mode 100644 index 0000000000..db65be7368 --- /dev/null +++ b/cmake/ports/openexr/fix_install_ilmimf.patch @@ -0,0 +1,19 @@ +diff --git a/OpenEXR/IlmImf/CMakeLists.txt b/OpenEXR/IlmImf/CMakeLists.txt +index e1a8740..d31cf68 100644 +--- a/OpenEXR/IlmImf/CMakeLists.txt ++++ b/OpenEXR/IlmImf/CMakeLists.txt +@@ -2,14 +2,6 @@ + + SET(CMAKE_INCLUDE_CURRENT_DIR 1) + +-IF (WIN32) +- SET(RUNTIME_DIR ${OPENEXR_PACKAGE_PREFIX}/bin) +- SET(WORKING_DIR ${RUNTIME_DIR}) +-ELSE () +- SET(RUNTIME_DIR ${OPENEXR_PACKAGE_PREFIX}/lib) +- SET(WORKING_DIR .) +-ENDIF () +- + SET(BUILD_B44EXPLOGTABLE OFF) + IF (NOT EXISTS "${CMAKE_CURRENT_BINARY_DIR}/b44ExpLogTable.h") + SET(BUILD_B44EXPLOGTABLE ON) diff --git a/cmake/ports/openexr/portfile.cmake b/cmake/ports/openexr/portfile.cmake new file mode 100644 index 0000000000..72e1edb3e7 --- /dev/null +++ b/cmake/ports/openexr/portfile.cmake @@ -0,0 +1,74 @@ +include(vcpkg_common_functions) + +set(OPENEXR_VERSION 2.3.0) +set(OPENEXR_HASH 268ae64b40d21d662f405fba97c307dad1456b7d996a447aadafd41b640ca736d4851d9544b4741a94e7b7c335fe6e9d3b16180e710671abfc0c8b2740b147b2) + +vcpkg_from_github( + OUT_SOURCE_PATH SOURCE_PATH + REPO openexr/openexr + REF v${OPENEXR_VERSION} + SHA512 ${OPENEXR_HASH} + HEAD_REF master + PATCHES "fix_install_ilmimf.patch" +) + +set(OPENEXR_STATIC ON) +set(OPENEXR_SHARED OFF) + +vcpkg_configure_cmake(SOURCE_PATH ${SOURCE_PATH} + PREFER_NINJA + OPTIONS + -DOPENEXR_BUILD_PYTHON_LIBS=OFF + -DOPENEXR_BUILD_VIEWERS=OFF + -DOPENEXR_ENABLE_TESTS=OFF + -DOPENEXR_RUN_FUZZ_TESTS=OFF + -DOPENEXR_BUILD_SHARED=${OPENEXR_SHARED} + -DOPENEXR_BUILD_STATIC=${OPENEXR_STATIC} + OPTIONS_DEBUG + -DILMBASE_PACKAGE_PREFIX=${CURRENT_INSTALLED_DIR}/debug + OPTIONS_RELEASE + -DILMBASE_PACKAGE_PREFIX=${CURRENT_INSTALLED_DIR}) + +vcpkg_install_cmake() + +file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/include) +file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/share) + +# NOTE: Only use ".exe" extension on Windows executables. +# Is there a cleaner way to do this? +if(WIN32) + set(EXECUTABLE_SUFFIX ".exe") +else() + set(EXECUTABLE_SUFFIX "") +endif() +file(REMOVE ${CURRENT_PACKAGES_DIR}/debug/bin/exrenvmap${EXECUTABLE_SUFFIX}) +file(REMOVE ${CURRENT_PACKAGES_DIR}/debug/bin/exrheader${EXECUTABLE_SUFFIX}) +file(REMOVE ${CURRENT_PACKAGES_DIR}/debug/bin/exrmakepreview${EXECUTABLE_SUFFIX}) +file(REMOVE ${CURRENT_PACKAGES_DIR}/debug/bin/exrmaketiled${EXECUTABLE_SUFFIX}) +file(REMOVE ${CURRENT_PACKAGES_DIR}/debug/bin/exrmultipart${EXECUTABLE_SUFFIX}) +file(REMOVE ${CURRENT_PACKAGES_DIR}/debug/bin/exrmultiview${EXECUTABLE_SUFFIX}) +file(REMOVE ${CURRENT_PACKAGES_DIR}/debug/bin/exrstdattr${EXECUTABLE_SUFFIX}) +file(REMOVE ${CURRENT_PACKAGES_DIR}/bin/exrenvmap${EXECUTABLE_SUFFIX}) +file(REMOVE ${CURRENT_PACKAGES_DIR}/bin/exrheader${EXECUTABLE_SUFFIX}) +file(REMOVE ${CURRENT_PACKAGES_DIR}/bin/exrmakepreview${EXECUTABLE_SUFFIX}) +file(REMOVE ${CURRENT_PACKAGES_DIR}/bin/exrmaketiled${EXECUTABLE_SUFFIX}) +file(REMOVE ${CURRENT_PACKAGES_DIR}/bin/exrmultipart${EXECUTABLE_SUFFIX}) +file(REMOVE ${CURRENT_PACKAGES_DIR}/bin/exrmultiview${EXECUTABLE_SUFFIX}) +file(REMOVE ${CURRENT_PACKAGES_DIR}/bin/exrstdattr${EXECUTABLE_SUFFIX}) + +vcpkg_copy_pdbs() + +if (OPENEXR_STATIC) + file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/bin ${CURRENT_PACKAGES_DIR}/debug/bin) +endif() + +if (VCPKG_CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(OPENEXR_PORT_DIR "openexr") +else() + set(OPENEXR_PORT_DIR "OpenEXR") +endif() + +file(COPY ${SOURCE_PATH}/LICENSE DESTINATION ${CURRENT_PACKAGES_DIR}/share/${OPENEXR_PORT_DIR}) +file(RENAME ${CURRENT_PACKAGES_DIR}/share/${OPENEXR_PORT_DIR}/LICENSE ${CURRENT_PACKAGES_DIR}/share/${OPENEXR_PORT_DIR}/copyright) + +file(COPY ${CMAKE_CURRENT_LIST_DIR}/FindOpenEXR.cmake DESTINATION ${CURRENT_PACKAGES_DIR}/share/${OPENEXR_PORT_DIR}) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 5f82700e9c..400dc3642d 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1734,7 +1734,7 @@ void DomainServer::processOctreeDataPersistMessage(QSharedPointergetStencilMaskMode(); + if (appRenderArgs._renderArgs._stencilMode == StencilMode::MESH) { + appRenderArgs._renderArgs._stencilMaskOperator = getActiveDisplayPlugin()->getStencilMaskMeshOperator(); + } } { diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 38bdd061c0..7a64edae11 100755 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -497,7 +497,7 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar // it might not fire until after we create a new instance for the same remote avatar, which creates a race // on the creation of entities for that avatar instance and the deletion of entities for this instance avatar->removeAvatarEntitiesFromTree(); - if (removalReason == KillAvatarReason::TheirAvatarEnteredYourBubble) { + if (removalReason == KillAvatarReason::TheirAvatarEnteredYourBubble || removalReason == KillAvatarReason::NoReason) { emit AvatarInputs::getInstance()->avatarEnteredIgnoreRadius(avatar->getSessionUUID()); emit DependencyManager::get()->enteredIgnoreRadius(); diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index 2e18f34c8d..64528e617d 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -17,6 +17,7 @@ #include #include #include +#include #include "AccountManager.h" @@ -65,7 +66,7 @@ signals: void availableUpdatesResult(QJsonObject result); void updateItemResult(QJsonObject result); - void updateCertificateStatus(const QString& certID, uint certStatus); + void updateCertificateStatus(const EntityItemID& entityID, uint certStatus); public slots: void buySuccess(QNetworkReply* reply); diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index 3217b8a1f9..99b3e32e8b 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -19,6 +19,7 @@ #include +#include #include class QmlCommerce : public QObject, public Dependency { @@ -49,7 +50,7 @@ signals: void availableUpdatesResult(QJsonObject result); void updateItemResult(QJsonObject result); - void updateCertificateStatus(const QString& certID, uint certStatus); + void updateCertificateStatus(const EntityItemID& entityID, uint certStatus); void transferAssetToNodeResult(QJsonObject result); void transferAssetToUsernameResult(QJsonObject result); diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index 0e9ad7d79a..37f28960e5 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -816,18 +816,18 @@ void Wallet::handleChallengeOwnershipPacket(QSharedPointer pack bool challengeOriginatedFromClient = packet->getType() == PacketType::ChallengeOwnershipRequest; int status; - int certIDByteArraySize; + int idByteArraySize; int textByteArraySize; int challengingNodeUUIDByteArraySize; - packet->readPrimitive(&certIDByteArraySize); + packet->readPrimitive(&idByteArraySize); packet->readPrimitive(&textByteArraySize); // returns a cast char*, size if (challengeOriginatedFromClient) { packet->readPrimitive(&challengingNodeUUIDByteArraySize); } // "encryptedText" is now a series of random bytes, a nonce - QByteArray certID = packet->read(certIDByteArraySize); + QByteArray id = packet->read(idByteArraySize); QByteArray text = packet->read(textByteArraySize); QByteArray challengingNodeUUID; if (challengeOriginatedFromClient) { @@ -853,32 +853,32 @@ void Wallet::handleChallengeOwnershipPacket(QSharedPointer pack textByteArray = sig.toUtf8(); } textByteArraySize = textByteArray.size(); - int certIDSize = certID.size(); + int idSize = id.size(); // setup the packet if (challengeOriginatedFromClient) { auto textPacket = NLPacket::create(PacketType::ChallengeOwnershipReply, - certIDSize + textByteArraySize + challengingNodeUUIDByteArraySize + 3 * sizeof(int), + idSize + textByteArraySize + challengingNodeUUIDByteArraySize + 3 * sizeof(int), true); - textPacket->writePrimitive(certIDSize); + textPacket->writePrimitive(idSize); textPacket->writePrimitive(textByteArraySize); textPacket->writePrimitive(challengingNodeUUIDByteArraySize); - textPacket->write(certID); + textPacket->write(id); textPacket->write(textByteArray); textPacket->write(challengingNodeUUID); - qCDebug(commerce) << "Sending ChallengeOwnershipReply Packet containing signed text" << textByteArray << "for CertID" << certID; + qCDebug(commerce) << "Sending ChallengeOwnershipReply Packet containing signed text" << textByteArray << "for id" << id; nodeList->sendPacket(std::move(textPacket), *sendingNode); } else { - auto textPacket = NLPacket::create(PacketType::ChallengeOwnership, certIDSize + textByteArraySize + 2 * sizeof(int), true); + auto textPacket = NLPacket::create(PacketType::ChallengeOwnership, idSize + textByteArraySize + 2 * sizeof(int), true); - textPacket->writePrimitive(certIDSize); + textPacket->writePrimitive(idSize); textPacket->writePrimitive(textByteArraySize); - textPacket->write(certID); + textPacket->write(id); textPacket->write(textByteArray); - qCDebug(commerce) << "Sending ChallengeOwnership Packet containing signed text" << textByteArray << "for CertID" << certID; + qCDebug(commerce) << "Sending ChallengeOwnership Packet containing signed text" << textByteArray << "for id" << id; nodeList->sendPacket(std::move(textPacket), *sendingNode); } diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index e5cec70f64..1c8a9019ea 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -292,7 +292,7 @@ void ContextOverlayInterface::requestOwnershipVerification(const QUuid& entityID setLastInspectedEntity(entityID); - EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(_lastInspectedEntity, _entityPropertyFlags); + EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(entityID, _entityPropertyFlags); auto nodeList = DependencyManager::get(); @@ -328,31 +328,31 @@ void ContextOverlayInterface::requestOwnershipVerification(const QUuid& entityID } else { QString ownerKey = jsonObject["transfer_recipient_key"].toString(); - QByteArray certID = entityProperties.getCertificateID().toUtf8(); - QByteArray text = DependencyManager::get()->getTree()->computeNonce(certID, ownerKey); + QByteArray id = entityID.toByteArray(); + QByteArray text = DependencyManager::get()->getTree()->computeNonce(entityID, ownerKey); QByteArray nodeToChallengeByteArray = entityProperties.getOwningAvatarID().toRfc4122(); - int certIDByteArraySize = certID.length(); + int idByteArraySize = id.length(); int textByteArraySize = text.length(); int nodeToChallengeByteArraySize = nodeToChallengeByteArray.length(); auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnershipRequest, - certIDByteArraySize + textByteArraySize + nodeToChallengeByteArraySize + 3 * sizeof(int), + idByteArraySize + textByteArraySize + nodeToChallengeByteArraySize + 3 * sizeof(int), true); - challengeOwnershipPacket->writePrimitive(certIDByteArraySize); + challengeOwnershipPacket->writePrimitive(idByteArraySize); challengeOwnershipPacket->writePrimitive(textByteArraySize); challengeOwnershipPacket->writePrimitive(nodeToChallengeByteArraySize); - challengeOwnershipPacket->write(certID); + challengeOwnershipPacket->write(id); challengeOwnershipPacket->write(text); challengeOwnershipPacket->write(nodeToChallengeByteArray); nodeList->sendPacket(std::move(challengeOwnershipPacket), *entityServer); // Kickoff a 10-second timeout timer that marks the cert if we don't get an ownership response in time if (thread() != QThread::currentThread()) { - QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer"); + QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer", Q_ARG(const EntityItemID&, entityID)); return; } else { - startChallengeOwnershipTimer(); + startChallengeOwnershipTimer(entityID); } } } else { @@ -370,14 +370,14 @@ void ContextOverlayInterface::requestOwnershipVerification(const QUuid& entityID // so they always pass Ownership Verification. It's necessary to emit this signal // so that the Inspection Certificate can continue its information-grabbing process. auto ledger = DependencyManager::get(); - emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS)); + emit ledger->updateCertificateStatus(entityID, (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS)); } } else { auto ledger = DependencyManager::get(); _challengeOwnershipTimeoutTimer.stop(); - emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED)); - emit DependencyManager::get()->ownershipVerificationFailed(_lastInspectedEntity); - qCDebug(context_overlay) << "Entity" << _lastInspectedEntity << "failed static certificate verification!"; + emit ledger->updateCertificateStatus(entityID, (uint)(ledger->CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED)); + emit DependencyManager::get()->ownershipVerificationFailed(entityID); + qCDebug(context_overlay) << "Entity" << entityID << "failed static certificate verification!"; } } @@ -395,14 +395,14 @@ void ContextOverlayInterface::deletingEntity(const EntityItemID& entityID) { } } -void ContextOverlayInterface::startChallengeOwnershipTimer() { +void ContextOverlayInterface::startChallengeOwnershipTimer(const EntityItemID& entityItemID) { auto ledger = DependencyManager::get(); - EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(_lastInspectedEntity, _entityPropertyFlags); + EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(entityItemID, _entityPropertyFlags); connect(&_challengeOwnershipTimeoutTimer, &QTimer::timeout, this, [=]() { - qCDebug(entities) << "Ownership challenge timed out for" << _lastInspectedEntity; - emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_TIMEOUT)); - emit DependencyManager::get()->ownershipVerificationFailed(_lastInspectedEntity); + qCDebug(entities) << "Ownership challenge timed out for" << entityItemID; + emit ledger->updateCertificateStatus(entityItemID, (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_TIMEOUT)); + emit DependencyManager::get()->ownershipVerificationFailed(entityItemID); }); _challengeOwnershipTimeoutTimer.start(5000); @@ -413,23 +413,22 @@ void ContextOverlayInterface::handleChallengeOwnershipReplyPacket(QSharedPointer _challengeOwnershipTimeoutTimer.stop(); - int certIDByteArraySize; + int idByteArraySize; int textByteArraySize; - packet->readPrimitive(&certIDByteArraySize); + packet->readPrimitive(&idByteArraySize); packet->readPrimitive(&textByteArraySize); - QString certID(packet->read(certIDByteArraySize)); + EntityItemID id(packet->read(idByteArraySize)); QString text(packet->read(textByteArraySize)); - EntityItemID id; - bool verificationSuccess = DependencyManager::get()->getTree()->verifyNonce(certID, text, id); + bool verificationSuccess = DependencyManager::get()->getTree()->verifyNonce(id, text); if (verificationSuccess) { - emit ledger->updateCertificateStatus(certID, (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS)); - emit DependencyManager::get()->ownershipVerificationSuccess(_lastInspectedEntity); + emit ledger->updateCertificateStatus(id, (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS)); + emit DependencyManager::get()->ownershipVerificationSuccess(id); } else { - emit ledger->updateCertificateStatus(certID, (uint)(ledger->CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED)); - emit DependencyManager::get()->ownershipVerificationFailed(_lastInspectedEntity); + emit ledger->updateCertificateStatus(id, (uint)(ledger->CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED)); + emit DependencyManager::get()->ownershipVerificationFailed(id); } } diff --git a/interface/src/ui/overlays/ContextOverlayInterface.h b/interface/src/ui/overlays/ContextOverlayInterface.h index b1b62aa0c4..e688d1c115 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.h +++ b/interface/src/ui/overlays/ContextOverlayInterface.h @@ -46,7 +46,7 @@ public: ContextOverlayInterface(); Q_INVOKABLE QUuid getCurrentEntityWithContextOverlay() { return _currentEntityWithContextOverlay; } void setCurrentEntityWithContextOverlay(const QUuid& entityID) { _currentEntityWithContextOverlay = entityID; } - void setLastInspectedEntity(const QUuid& entityID) { _challengeOwnershipTimeoutTimer.stop(); _lastInspectedEntity = entityID; } + void setLastInspectedEntity(const QUuid& entityID) { _challengeOwnershipTimeoutTimer.stop(); } void setEnabled(bool enabled); bool getEnabled() { return _enabled; } bool getIsInMarketplaceInspectionMode() { return _isInMarketplaceInspectionMode; } @@ -83,7 +83,6 @@ private: EntityItemID _mouseDownEntity; quint64 _mouseDownEntityTimestamp; EntityItemID _currentEntityWithContextOverlay; - EntityItemID _lastInspectedEntity; QString _entityMarketplaceID; bool _contextOverlayJustClicked { false }; @@ -94,7 +93,7 @@ private: void deletingEntity(const EntityItemID& entityItemID); - Q_INVOKABLE void startChallengeOwnershipTimer(); + Q_INVOKABLE void startChallengeOwnershipTimer(const EntityItemID& entityItemID); QTimer _challengeOwnershipTimeoutTimer; }; diff --git a/libraries/baking/src/TextureBaker.cpp b/libraries/baking/src/TextureBaker.cpp index d097b4765b..3756ae86de 100644 --- a/libraries/baking/src/TextureBaker.cpp +++ b/libraries/baking/src/TextureBaker.cpp @@ -16,7 +16,7 @@ #include #include -#include +#include #include #include #include diff --git a/libraries/baking/src/TextureBaker.h b/libraries/baking/src/TextureBaker.h index 4fc9680653..13ad82cff4 100644 --- a/libraries/baking/src/TextureBaker.h +++ b/libraries/baking/src/TextureBaker.h @@ -18,7 +18,7 @@ #include #include -#include +#include #include "Baker.h" diff --git a/libraries/baking/src/baking/TextureFileNamer.h b/libraries/baking/src/baking/TextureFileNamer.h index 9049588ef1..319d943a03 100644 --- a/libraries/baking/src/baking/TextureFileNamer.h +++ b/libraries/baking/src/baking/TextureFileNamer.h @@ -15,7 +15,7 @@ #include #include -#include +#include class TextureFileNamer { public: diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp index f55f5919f5..9828a8beda 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp @@ -109,7 +109,7 @@ bool Basic2DWindowOpenGLDisplayPlugin::internalActivate() { return Parent::internalActivate(); } -void Basic2DWindowOpenGLDisplayPlugin::compositeExtra(const gpu::FramebufferPointer& compositeFramebuffer) { +void Basic2DWindowOpenGLDisplayPlugin::compositeExtra() { #if defined(Q_OS_ANDROID) auto& virtualPadManager = VirtualPad::Manager::instance(); if(virtualPadManager.getLeftVirtualPad()->isShown()) { @@ -121,7 +121,7 @@ void Basic2DWindowOpenGLDisplayPlugin::compositeExtra(const gpu::FramebufferPoin render([&](gpu::Batch& batch) { batch.enableStereo(false); - batch.setFramebuffer(compositeFramebuffer); + batch.setFramebuffer(_compositeFramebuffer); batch.resetViewTransform(); batch.setProjectionTransform(mat4()); batch.setPipeline(_cursorPipeline); @@ -140,7 +140,7 @@ void Basic2DWindowOpenGLDisplayPlugin::compositeExtra(const gpu::FramebufferPoin }); } #endif - Parent::compositeExtra(compositeFramebuffer); + Parent::compositeExtra(); } static const uint32_t MIN_THROTTLE_CHECK_FRAMES = 60; diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h index d4c321a571..cc304c19c2 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h @@ -33,7 +33,7 @@ public: virtual bool isThrottled() const override; - virtual void compositeExtra(const gpu::FramebufferPointer&) override; + virtual void compositeExtra() override; virtual void pluginUpdate() override {}; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 5bc84acc6a..c536e6b6e2 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -379,6 +379,14 @@ void OpenGLDisplayPlugin::customizeContext() { scissorState->setDepthTest(gpu::State::DepthTest(false)); scissorState->setScissorEnable(true); + { +#ifdef Q_OS_ANDROID + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTextureGammaLinearToSRGB); +#else + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTexture); +#endif + _simplePipeline = gpu::Pipeline::create(program, scissorState); + } { #ifdef Q_OS_ANDROID gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTextureGammaLinearToSRGB); @@ -388,59 +396,29 @@ void OpenGLDisplayPlugin::customizeContext() { _presentPipeline = gpu::Pipeline::create(program, scissorState); } - - // HUD operator { - gpu::PipelinePointer hudPipeline; - { - gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTexture); - hudPipeline = gpu::Pipeline::create(program, blendState); - } - - gpu::PipelinePointer hudMirrorPipeline; - { - gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTextureMirroredX); - hudMirrorPipeline = gpu::Pipeline::create(program, blendState); - } - - - _hudOperator = [=](gpu::Batch& batch, const gpu::TexturePointer& hudTexture, const gpu::FramebufferPointer& compositeFramebuffer, bool mirror) { - auto hudStereo = isStereo(); - auto hudCompositeFramebufferSize = compositeFramebuffer->getSize(); - std::array hudEyeViewports; - for_each_eye([&](Eye eye) { - hudEyeViewports[eye] = eyeViewport(eye); - }); - if (hudPipeline && hudTexture) { - batch.enableStereo(false); - batch.setPipeline(mirror ? hudMirrorPipeline : hudPipeline); - batch.setResourceTexture(0, hudTexture); - if (hudStereo) { - for_each_eye([&](Eye eye) { - batch.setViewportTransform(hudEyeViewports[eye]); - batch.draw(gpu::TRIANGLE_STRIP, 4); - }); - } else { - batch.setViewportTransform(ivec4(uvec2(0), hudCompositeFramebufferSize)); - batch.draw(gpu::TRIANGLE_STRIP, 4); - } - } - }; - + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTexture); + _hudPipeline = gpu::Pipeline::create(program, blendState); + } + { + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTextureMirroredX); + _mirrorHUDPipeline = gpu::Pipeline::create(program, blendState); } - { gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTransformedTexture); _cursorPipeline = gpu::Pipeline::create(program, blendState); } } + updateCompositeFramebuffer(); } void OpenGLDisplayPlugin::uncustomizeContext() { _presentPipeline.reset(); _cursorPipeline.reset(); - _hudOperator = DEFAULT_HUD_OPERATOR; + _hudPipeline.reset(); + _mirrorHUDPipeline.reset(); + _compositeFramebuffer.reset(); withPresentThreadLock([&] { _currentFrame.reset(); _lastFrame = nullptr; @@ -532,16 +510,24 @@ void OpenGLDisplayPlugin::captureFrame(const std::string& filename) const { }); } +void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer& texture, const glm::ivec4& viewport, const glm::ivec4& scissor) { + renderFromTexture(batch, texture, viewport, scissor, nullptr); +} -void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer& texture, const glm::ivec4& viewport, const glm::ivec4& scissor, const gpu::FramebufferPointer& destFbo, const gpu::FramebufferPointer& copyFbo /*=gpu::FramebufferPointer()*/) { +void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer& texture, const glm::ivec4& viewport, const glm::ivec4& scissor, const gpu::FramebufferPointer& copyFbo /*=gpu::FramebufferPointer()*/) { + auto fbo = gpu::FramebufferPointer(); batch.enableStereo(false); batch.resetViewTransform(); - batch.setFramebuffer(destFbo); + batch.setFramebuffer(fbo); batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, vec4(0)); batch.setStateScissorRect(scissor); batch.setViewportTransform(viewport); batch.setResourceTexture(0, texture); +#ifndef USE_GLES batch.setPipeline(_presentPipeline); +#else + batch.setPipeline(_simplePipeline); +#endif batch.draw(gpu::TRIANGLE_STRIP, 4); if (copyFbo) { gpu::Vec4i copyFboRect(0, 0, copyFbo->getWidth(), copyFbo->getHeight()); @@ -567,7 +553,7 @@ void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::Textur batch.setViewportTransform(copyFboRect); batch.setStateScissorRect(copyFboRect); batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, {0.0f, 0.0f, 0.0f, 1.0f}); - batch.blit(destFbo, sourceRect, copyFbo, copyRect); + batch.blit(fbo, sourceRect, copyFbo, copyRect); } } @@ -595,14 +581,41 @@ void OpenGLDisplayPlugin::updateFrameData() { }); } -void OpenGLDisplayPlugin::compositePointer(const gpu::FramebufferPointer& compositeFramebuffer) { +std::function OpenGLDisplayPlugin::getHUDOperator() { + auto hudPipeline = _hudPipeline; + auto hudMirrorPipeline = _mirrorHUDPipeline; + auto hudStereo = isStereo(); + auto hudCompositeFramebufferSize = _compositeFramebuffer->getSize(); + std::array hudEyeViewports; + for_each_eye([&](Eye eye) { + hudEyeViewports[eye] = eyeViewport(eye); + }); + return [=](gpu::Batch& batch, const gpu::TexturePointer& hudTexture, bool mirror) { + if (hudPipeline && hudTexture) { + batch.enableStereo(false); + batch.setPipeline(mirror ? hudMirrorPipeline : hudPipeline); + batch.setResourceTexture(0, hudTexture); + if (hudStereo) { + for_each_eye([&](Eye eye) { + batch.setViewportTransform(hudEyeViewports[eye]); + batch.draw(gpu::TRIANGLE_STRIP, 4); + }); + } else { + batch.setViewportTransform(ivec4(uvec2(0), hudCompositeFramebufferSize)); + batch.draw(gpu::TRIANGLE_STRIP, 4); + } + } + }; +} + +void OpenGLDisplayPlugin::compositePointer() { auto& cursorManager = Cursor::Manager::instance(); const auto& cursorData = _cursorsData[cursorManager.getCursor()->getIcon()]; auto cursorTransform = DependencyManager::get()->getReticleTransform(glm::mat4()); render([&](gpu::Batch& batch) { batch.enableStereo(false); batch.setProjectionTransform(mat4()); - batch.setFramebuffer(compositeFramebuffer); + batch.setFramebuffer(_compositeFramebuffer); batch.setPipeline(_cursorPipeline); batch.setResourceTexture(0, cursorData.texture); batch.resetViewTransform(); @@ -613,13 +626,34 @@ void OpenGLDisplayPlugin::compositePointer(const gpu::FramebufferPointer& compos batch.draw(gpu::TRIANGLE_STRIP, 4); }); } else { - batch.setViewportTransform(ivec4(uvec2(0), compositeFramebuffer->getSize())); + batch.setViewportTransform(ivec4(uvec2(0), _compositeFramebuffer->getSize())); batch.draw(gpu::TRIANGLE_STRIP, 4); } }); } -void OpenGLDisplayPlugin::compositeLayers(const gpu::FramebufferPointer& compositeFramebuffer) { +void OpenGLDisplayPlugin::compositeScene() { + render([&](gpu::Batch& batch) { + batch.enableStereo(false); + batch.setFramebuffer(_compositeFramebuffer); + batch.setViewportTransform(ivec4(uvec2(), _compositeFramebuffer->getSize())); + batch.setStateScissorRect(ivec4(uvec2(), _compositeFramebuffer->getSize())); + batch.resetViewTransform(); + batch.setProjectionTransform(mat4()); + batch.setPipeline(_simplePipeline); + batch.setResourceTexture(0, _currentFrame->framebuffer->getRenderBuffer(0)); + batch.draw(gpu::TRIANGLE_STRIP, 4); + }); +} + +void OpenGLDisplayPlugin::compositeLayers() { + updateCompositeFramebuffer(); + + { + PROFILE_RANGE_EX(render_detail, "compositeScene", 0xff0077ff, (uint64_t)presentCount()) + compositeScene(); + } + #ifdef HIFI_ENABLE_NSIGHT_DEBUG if (false) // do not draw the HUD if running nsight debug #endif @@ -633,35 +667,23 @@ void OpenGLDisplayPlugin::compositeLayers(const gpu::FramebufferPointer& composi { PROFILE_RANGE_EX(render_detail, "compositeExtra", 0xff0077ff, (uint64_t)presentCount()) - compositeExtra(compositeFramebuffer); + compositeExtra(); } // Draw the pointer last so it's on top of everything auto compositorHelper = DependencyManager::get(); if (compositorHelper->getReticleVisible()) { PROFILE_RANGE_EX(render_detail, "compositePointer", 0xff0077ff, (uint64_t)presentCount()) - compositePointer(compositeFramebuffer); + compositePointer(); } } -void OpenGLDisplayPlugin::internalPresent(const gpu::FramebufferPointer& compositeFramebuffer) { +void OpenGLDisplayPlugin::internalPresent() { render([&](gpu::Batch& batch) { // Note: _displayTexture must currently be the same size as the display. uvec2 dims = _displayTexture ? uvec2(_displayTexture->getDimensions()) : getSurfacePixels(); auto viewport = ivec4(uvec2(0), dims); - - gpu::TexturePointer finalTexture; - if (_displayTexture) { - finalTexture = _displayTexture; - } else if (compositeFramebuffer) { - finalTexture = compositeFramebuffer->getRenderBuffer(0); - } else { - qCWarning(displayPlugins) << "No valid texture for output"; - } - - if (finalTexture) { - renderFromTexture(batch, finalTexture, viewport, viewport); - } + renderFromTexture(batch, _displayTexture ? _displayTexture : _compositeFramebuffer->getRenderBuffer(0), viewport, viewport); }); swapBuffers(); _presentRate.increment(); @@ -678,7 +700,7 @@ void OpenGLDisplayPlugin::present() { } incrementPresentCount(); - if (_currentFrame && _currentFrame->framebuffer) { + if (_currentFrame) { auto correction = getViewCorrection(); getGLBackend()->setCameraCorrection(correction, _prevRenderView); _prevRenderView = correction * _currentFrame->view; @@ -698,18 +720,18 @@ void OpenGLDisplayPlugin::present() { // Write all layers to a local framebuffer { PROFILE_RANGE_EX(render, "composite", 0xff00ffff, frameId) - compositeLayers(_currentFrame->framebuffer); + compositeLayers(); } // Take the composite framebuffer and send it to the output device { PROFILE_RANGE_EX(render, "internalPresent", 0xff00ffff, frameId) - internalPresent(_currentFrame->framebuffer); + internalPresent(); } gpu::Backend::freeGPUMemSize.set(gpu::gl::getFreeDedicatedMemory()); } else if (alwaysPresent()) { - internalPresent(nullptr); + internalPresent(); } _movingAveragePresent.addSample((float)(usecTimestampNow() - startPresent)); } @@ -766,12 +788,7 @@ bool OpenGLDisplayPlugin::setDisplayTexture(const QString& name) { } QImage OpenGLDisplayPlugin::getScreenshot(float aspectRatio) const { - if (!_currentFrame || !_currentFrame->framebuffer) { - return QImage(); - } - - auto compositeFramebuffer = _currentFrame->framebuffer; - auto size = compositeFramebuffer->getSize(); + auto size = _compositeFramebuffer->getSize(); if (isHmd()) { size.x /= 2; } @@ -789,7 +806,7 @@ QImage OpenGLDisplayPlugin::getScreenshot(float aspectRatio) const { auto glBackend = const_cast(*this).getGLBackend(); QImage screenshot(bestSize.x, bestSize.y, QImage::Format_ARGB32); withOtherThreadContext([&] { - glBackend->downloadFramebuffer(compositeFramebuffer, ivec4(corner, bestSize), screenshot); + glBackend->downloadFramebuffer(_compositeFramebuffer, ivec4(corner, bestSize), screenshot); }); return screenshot.mirrored(false, true); } @@ -841,7 +858,7 @@ bool OpenGLDisplayPlugin::beginFrameRender(uint32_t frameIndex) { } ivec4 OpenGLDisplayPlugin::eyeViewport(Eye eye) const { - auto vpSize = glm::uvec2(getRecommendedRenderSize()); + uvec2 vpSize = _compositeFramebuffer->getSize(); vpSize.x /= 2; uvec2 vpPos; if (eye == Eye::Right) { @@ -874,6 +891,14 @@ void OpenGLDisplayPlugin::render(std::function f) { OpenGLDisplayPlugin::~OpenGLDisplayPlugin() { } +void OpenGLDisplayPlugin::updateCompositeFramebuffer() { + auto renderSize = glm::uvec2(getRecommendedRenderSize()); + if (!_compositeFramebuffer || _compositeFramebuffer->getSize() != renderSize) { + _compositeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("OpenGLDisplayPlugin::composite", gpu::Element::COLOR_RGBA_32, renderSize.x, renderSize.y)); + // _compositeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("OpenGLDisplayPlugin::composite", gpu::Element::COLOR_SRGBA_32, renderSize.x, renderSize.y)); + } +} + void OpenGLDisplayPlugin::copyTextureToQuickFramebuffer(NetworkTexturePointer networkTexture, QOpenGLFramebufferObject* target, GLsync* fenceSync) { #if !defined(USE_GLES) auto glBackend = const_cast(*this).getGLBackend(); diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index 3c48e8fc48..49a38ecb4c 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -94,10 +94,14 @@ protected: // is not populated virtual bool alwaysPresent() const { return false; } + void updateCompositeFramebuffer(); + virtual QThread::Priority getPresentPriority() { return QThread::HighPriority; } - virtual void compositeLayers(const gpu::FramebufferPointer&); - virtual void compositePointer(const gpu::FramebufferPointer&); - virtual void compositeExtra(const gpu::FramebufferPointer&) {}; + virtual void compositeLayers(); + virtual void compositeScene(); + virtual std::function getHUDOperator(); + virtual void compositePointer(); + virtual void compositeExtra() {}; // These functions must only be called on the presentation thread virtual void customizeContext(); @@ -112,10 +116,10 @@ protected: virtual void deactivateSession() {} // Plugin specific functionality to send the composed scene to the output window or device - virtual void internalPresent(const gpu::FramebufferPointer&); + virtual void internalPresent(); - - void renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer& texture, const glm::ivec4& viewport, const glm::ivec4& scissor, const gpu::FramebufferPointer& destFbo = nullptr, const gpu::FramebufferPointer& copyFbo = nullptr); + void renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer& texture, const glm::ivec4& viewport, const glm::ivec4& scissor, const gpu::FramebufferPointer& fbo); + void renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer& texture, const glm::ivec4& viewport, const glm::ivec4& scissor); virtual void updateFrameData(); virtual glm::mat4 getViewCorrection() { return glm::mat4(); } @@ -138,8 +142,14 @@ protected: gpu::FramePointer _currentFrame; gpu::Frame* _lastFrame { nullptr }; mat4 _prevRenderView; + gpu::FramebufferPointer _compositeFramebuffer; + gpu::PipelinePointer _hudPipeline; + gpu::PipelinePointer _mirrorHUDPipeline; + gpu::ShaderPointer _mirrorHUDPS; + gpu::PipelinePointer _simplePipeline; + gpu::PipelinePointer _presentPipeline; gpu::PipelinePointer _cursorPipeline; - gpu::TexturePointer _displayTexture; + gpu::TexturePointer _displayTexture{}; float _compositeHUDAlpha { 1.0f }; struct CursorData { @@ -175,9 +185,5 @@ protected: // be serialized through this mutex mutable Mutex _presentMutex; float _hudAlpha{ 1.0f }; - -private: - gpu::PipelinePointer _presentPipeline; - }; diff --git a/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.h index 95592cc490..f2b1f36419 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.h @@ -24,7 +24,7 @@ public: protected: void updatePresentPose() override; - void hmdPresent(const gpu::FramebufferPointer&) override {} + void hmdPresent() override {} bool isHmdMounted() const override { return true; } bool internalActivate() override; private: diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index a515232b3f..321bcc3fd2 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -114,23 +114,20 @@ void HmdDisplayPlugin::internalDeactivate() { void HmdDisplayPlugin::customizeContext() { Parent::customizeContext(); - _hudOperator = _hudRenderer.build(); + _hudRenderer.build(); } void HmdDisplayPlugin::uncustomizeContext() { // This stops the weirdness where if the preview was disabled, on switching back to 2D, // the vsync was stuck in the disabled state. No idea why that happens though. _disablePreview = false; - if (_currentFrame && _currentFrame->framebuffer) { - render([&](gpu::Batch& batch) { - batch.enableStereo(false); - batch.resetViewTransform(); - batch.setFramebuffer(_currentFrame->framebuffer); - batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, vec4(0)); - }); - - } - _hudRenderer = {}; + render([&](gpu::Batch& batch) { + batch.enableStereo(false); + batch.resetViewTransform(); + batch.setFramebuffer(_compositeFramebuffer); + batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, vec4(0)); + }); + _hudRenderer = HUDRenderer(); _previewTexture.reset(); Parent::uncustomizeContext(); } @@ -177,11 +174,11 @@ float HmdDisplayPlugin::getLeftCenterPixel() const { return leftCenterPixel; } -void HmdDisplayPlugin::internalPresent(const gpu::FramebufferPointer& compositeFramebuffer) { +void HmdDisplayPlugin::internalPresent() { PROFILE_RANGE_EX(render, __FUNCTION__, 0xff00ff00, (uint64_t)presentCount()) // Composite together the scene, hud and mouse cursor - hmdPresent(compositeFramebuffer); + hmdPresent(); if (_displayTexture) { // Note: _displayTexture must currently be the same size as the display. @@ -263,7 +260,7 @@ void HmdDisplayPlugin::internalPresent(const gpu::FramebufferPointer& compositeF viewport.z *= 2; } - renderFromTexture(batch, compositeFramebuffer->getRenderBuffer(0), viewport, scissor, nullptr, fbo); + renderFromTexture(batch, _compositeFramebuffer->getRenderBuffer(0), viewport, scissor, fbo); }); swapBuffers(); @@ -348,7 +345,7 @@ glm::mat4 HmdDisplayPlugin::getViewCorrection() { } } -DisplayPlugin::HUDOperator HmdDisplayPlugin::HUDRenderer::build() { +void HmdDisplayPlugin::HUDRenderer::build() { vertices = std::make_shared(); indices = std::make_shared(); @@ -383,7 +380,7 @@ DisplayPlugin::HUDOperator HmdDisplayPlugin::HUDRenderer::build() { indexCount = numberOfRectangles * TRIANGLE_PER_RECTANGLE * VERTEX_PER_TRANGLE; // Compute indices order - std::vector indexData; + std::vector indices; for (int i = 0; i < stacks - 1; i++) { for (int j = 0; j < slices - 1; j++) { GLushort bottomLeftIndex = i * slices + j; @@ -391,21 +388,24 @@ DisplayPlugin::HUDOperator HmdDisplayPlugin::HUDRenderer::build() { GLushort topLeftIndex = bottomLeftIndex + slices; GLushort topRightIndex = topLeftIndex + 1; // FIXME make a z-order curve for better vertex cache locality - indexData.push_back(topLeftIndex); - indexData.push_back(bottomLeftIndex); - indexData.push_back(topRightIndex); + indices.push_back(topLeftIndex); + indices.push_back(bottomLeftIndex); + indices.push_back(topRightIndex); - indexData.push_back(topRightIndex); - indexData.push_back(bottomLeftIndex); - indexData.push_back(bottomRightIndex); + indices.push_back(topRightIndex); + indices.push_back(bottomLeftIndex); + indices.push_back(bottomRightIndex); } } - indices->append(indexData); + this->indices->append(indices); format = std::make_shared(); // 1 for everyone format->setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0); format->setAttribute(gpu::Stream::TEXCOORD, gpu::Stream::TEXCOORD, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV)); uniformsBuffer = std::make_shared(sizeof(Uniforms), nullptr); + updatePipeline(); +} +void HmdDisplayPlugin::HUDRenderer::updatePipeline() { if (!pipeline) { auto program = gpu::Shader::createProgram(shader::render_utils::program::hmd_ui); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); @@ -416,6 +416,10 @@ DisplayPlugin::HUDOperator HmdDisplayPlugin::HUDRenderer::build() { pipeline = gpu::Pipeline::create(program, state); } +} + +std::function HmdDisplayPlugin::HUDRenderer::render(HmdDisplayPlugin& plugin) { + updatePipeline(); auto hudPipeline = pipeline; auto hudFormat = format; @@ -424,9 +428,9 @@ DisplayPlugin::HUDOperator HmdDisplayPlugin::HUDRenderer::build() { auto hudUniformBuffer = uniformsBuffer; auto hudUniforms = uniforms; auto hudIndexCount = indexCount; - return [=](gpu::Batch& batch, const gpu::TexturePointer& hudTexture, const gpu::FramebufferPointer&, const bool mirror) { - if (pipeline && hudTexture) { - batch.setPipeline(pipeline); + return [=](gpu::Batch& batch, const gpu::TexturePointer& hudTexture, bool mirror) { + if (hudPipeline && hudTexture) { + batch.setPipeline(hudPipeline); batch.setInputFormat(hudFormat); gpu::BufferView posView(hudVertices, VERTEX_OFFSET, hudVertices->getSize(), VERTEX_STRIDE, hudFormat->getAttributes().at(gpu::Stream::POSITION)._element); @@ -450,7 +454,7 @@ DisplayPlugin::HUDOperator HmdDisplayPlugin::HUDRenderer::build() { }; } -void HmdDisplayPlugin::compositePointer(const gpu::FramebufferPointer& compositeFramebuffer) { +void HmdDisplayPlugin::compositePointer() { auto& cursorManager = Cursor::Manager::instance(); const auto& cursorData = _cursorsData[cursorManager.getCursor()->getIcon()]; auto compositorHelper = DependencyManager::get(); @@ -459,7 +463,7 @@ void HmdDisplayPlugin::compositePointer(const gpu::FramebufferPointer& composite render([&](gpu::Batch& batch) { // FIXME use standard gpu stereo rendering for this. batch.enableStereo(false); - batch.setFramebuffer(compositeFramebuffer); + batch.setFramebuffer(_compositeFramebuffer); batch.setPipeline(_cursorPipeline); batch.setResourceTexture(0, cursorData.texture); batch.resetViewTransform(); @@ -474,6 +478,10 @@ void HmdDisplayPlugin::compositePointer(const gpu::FramebufferPointer& composite }); } +std::function HmdDisplayPlugin::getHUDOperator() { + return _hudRenderer.render(*this); +} + HmdDisplayPlugin::~HmdDisplayPlugin() { } diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h index 6755c5b7e0..4d2f1018a7 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h @@ -48,20 +48,23 @@ public: void pluginUpdate() override {}; + virtual StencilMode getStencilMaskMode() const override { return StencilMode::PAINT; } + signals: void hmdMountedChanged(); void hmdVisibleChanged(bool visible); protected: - virtual void hmdPresent(const gpu::FramebufferPointer&) = 0; + virtual void hmdPresent() = 0; virtual bool isHmdMounted() const = 0; virtual void postPreview() {}; virtual void updatePresentPose(); bool internalActivate() override; void internalDeactivate() override; - void compositePointer(const gpu::FramebufferPointer&) override; - void internalPresent(const gpu::FramebufferPointer&) override; + std::function getHUDOperator() override; + void compositePointer() override; + void internalPresent() override; void customizeContext() override; void uncustomizeContext() override; void updateFrameData() override; @@ -119,6 +122,8 @@ private: static const size_t TEXTURE_OFFSET { offsetof(Vertex, uv) }; static const int VERTEX_STRIDE { sizeof(Vertex) }; - HUDOperator build(); + void build(); + void updatePipeline(); + std::function render(HmdDisplayPlugin& plugin); } _hudRenderer; }; diff --git a/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.cpp index 69aa7fc344..0ae0f9b1b6 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.cpp @@ -37,13 +37,13 @@ glm::uvec2 InterleavedStereoDisplayPlugin::getRecommendedRenderSize() const { return result; } -void InterleavedStereoDisplayPlugin::internalPresent(const gpu::FramebufferPointer& compositeFramebuffer) { +void InterleavedStereoDisplayPlugin::internalPresent() { render([&](gpu::Batch& batch) { batch.enableStereo(false); batch.resetViewTransform(); batch.setFramebuffer(gpu::FramebufferPointer()); batch.setViewportTransform(ivec4(uvec2(0), getSurfacePixels())); - batch.setResourceTexture(0, compositeFramebuffer->getRenderBuffer(0)); + batch.setResourceTexture(0, _currentFrame->framebuffer->getRenderBuffer(0)); batch.setPipeline(_interleavedPresentPipeline); batch.draw(gpu::TRIANGLE_STRIP, 4); }); diff --git a/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.h index 52dfa8f402..debd340f24 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.h @@ -21,7 +21,7 @@ protected: // initialize OpenGL context settings needed by the plugin void customizeContext() override; void uncustomizeContext() override; - void internalPresent(const gpu::FramebufferPointer&) override; + void internalPresent() override; private: static const QString NAME; diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 3308ce020d..f921f6eca6 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -307,10 +307,6 @@ void RenderableModelEntityItem::setShapeType(ShapeType type) { } void RenderableModelEntityItem::setCompoundShapeURL(const QString& url) { - // because the caching system only allows one Geometry per url, and because this url might also be used - // as a visual model, we need to change this url in some way. We add a "collision-hull" query-arg so it - // will end up in a different hash-key in ResourceCache. TODO: It would be better to use the same URL and - // parse it twice. auto currentCompoundShapeURL = getCompoundShapeURL(); ModelEntityItem::setCompoundShapeURL(url); if (getCompoundShapeURL() != currentCompoundShapeURL || !getModel()) { diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index c139fbf320..d517ecd026 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -1,4 +1,4 @@ -// +// // RenderableParticleEffectEntityItem.cpp // interface/src // @@ -9,12 +9,12 @@ // #include "RenderableParticleEffectEntityItem.h" - #include #include #include +#include using namespace render; using namespace render::entities; @@ -79,6 +79,14 @@ bool ParticleEffectEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedE return true; } + if (_shapeType != entity->getShapeType()) { + return true; + } + + if (_compoundShapeURL != entity->getCompoundShapeURL()) { + return true; + } + return false; } @@ -87,10 +95,10 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi if (!newParticleProperties.valid()) { qCWarning(entitiesrenderer) << "Bad particle properties"; } - - if (resultWithReadLock([&]{ return _particleProperties != newParticleProperties; })) { + + if (resultWithReadLock([&] { return _particleProperties != newParticleProperties; })) { _timeUntilNextEmit = 0; - withWriteLock([&]{ + withWriteLock([&] { _particleProperties = newParticleProperties; if (!_prevEmitterShouldTrailInitialized) { _prevEmitterShouldTrailInitialized = true; @@ -101,13 +109,20 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi withWriteLock([&] { _pulseProperties = entity->getPulseProperties(); + _shapeType = entity->getShapeType(); + QString compoundShapeURL = entity->getCompoundShapeURL(); + if (_compoundShapeURL != compoundShapeURL) { + _compoundShapeURL = compoundShapeURL; + _hasComputedTriangles = false; + fetchGeometryResource(); + } }); _emitting = entity->getIsEmitting(); - bool textureEmpty = resultWithReadLock([&]{ return _particleProperties.textures.isEmpty(); }); + bool textureEmpty = resultWithReadLock([&] { return _particleProperties.textures.isEmpty(); }); if (textureEmpty) { if (_networkTexture) { - withWriteLock([&] { + withWriteLock([&] { _networkTexture.reset(); }); } @@ -116,11 +131,11 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi entity->setVisuallyReady(true); }); } else { - bool textureNeedsUpdate = resultWithReadLock([&]{ + bool textureNeedsUpdate = resultWithReadLock([&] { return !_networkTexture || _networkTexture->getURL() != QUrl(_particleProperties.textures); }); if (textureNeedsUpdate) { - withWriteLock([&] { + withWriteLock([&] { _networkTexture = DependencyManager::get()->getTexture(_particleProperties.textures); }); } @@ -144,7 +159,7 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi void ParticleEffectEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) { // Fill in Uniforms structure ParticleUniforms particleUniforms; - withReadLock([&]{ + withReadLock([&] { particleUniforms.radius.start = _particleProperties.radius.range.start; particleUniforms.radius.middle = _particleProperties.radius.gradient.target; particleUniforms.radius.finish = _particleProperties.radius.range.finish; @@ -181,9 +196,32 @@ Item::Bound ParticleEffectEntityRenderer::getBound() { return _bound; } -static const size_t VERTEX_PER_PARTICLE = 4; +// FIXME: these methods assume uniform emitDimensions, need to importance sample based on dimensions +float importanceSample2DDimension(float startDim) { + float dimension = 1.0f; + if (startDim < 1.0f) { + float innerDimensionSquared = startDim * startDim; + float outerDimensionSquared = 1.0f; // pow(particle::MAXIMUM_EMIT_RADIUS_START, 2); + float randDimensionSquared = randFloatInRange(innerDimensionSquared, outerDimensionSquared); + dimension = std::sqrt(randDimensionSquared); + } + return dimension; +} -ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createParticle(uint64_t now, const Transform& baseTransform, const particle::Properties& particleProperties) { +float importanceSample3DDimension(float startDim) { + float dimension = 1.0f; + if (startDim < 1.0f) { + float innerDimensionCubed = startDim * startDim * startDim; + float outerDimensionCubed = 1.0f; // pow(particle::MAXIMUM_EMIT_RADIUS_START, 3); + float randDimensionCubed = randFloatInRange(innerDimensionCubed, outerDimensionCubed); + dimension = std::cbrt(randDimensionCubed); + } + return dimension; +} + +ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createParticle(uint64_t now, const Transform& baseTransform, const particle::Properties& particleProperties, + const ShapeType& shapeType, const GeometryResource::Pointer& geometryResource, + const TriangleInfo& triangleInfo) { CpuParticle particle; const auto& accelerationSpread = particleProperties.emission.acceleration.spread; @@ -221,33 +259,130 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa float azimuth; if (azimuthFinish >= azimuthStart) { - azimuth = azimuthStart + (azimuthFinish - azimuthStart) * randFloat(); + azimuth = azimuthStart + (azimuthFinish - azimuthStart) * randFloat(); } else { azimuth = azimuthStart + (TWO_PI + azimuthFinish - azimuthStart) * randFloat(); } + // TODO: azimuth and elevation are only used for ellipsoids/circles, but could be used for other shapes too if (emitDimensions == Vectors::ZERO) { // Point emitDirection = glm::quat(glm::vec3(PI_OVER_TWO - elevation, 0.0f, azimuth)) * Vectors::UNIT_Z; } else { - // Ellipsoid - float radiusScale = 1.0f; - if (emitRadiusStart < 1.0f) { - float randRadius = - emitRadiusStart + randFloatInRange(0.0f, particle::MAXIMUM_EMIT_RADIUS_START - emitRadiusStart); - radiusScale = 1.0f - std::pow(1.0f - randRadius, 3.0f); + glm::vec3 emitPosition; + switch (shapeType) { + case SHAPE_TYPE_BOX: { + glm::vec3 dim = importanceSample3DDimension(emitRadiusStart) * 0.5f * emitDimensions; + + int side = randIntInRange(0, 5); + int axis = side % 3; + float direction = side > 2 ? 1.0f : -1.0f; + + emitDirection[axis] = direction; + emitPosition[axis] = direction * dim[axis]; + axis = (axis + 1) % 3; + emitPosition[axis] = dim[axis] * randFloatInRange(-1.0f, 1.0f); + axis = (axis + 1) % 3; + emitPosition[axis] = dim[axis] * randFloatInRange(-1.0f, 1.0f); + break; + } + + case SHAPE_TYPE_CYLINDER_X: + case SHAPE_TYPE_CYLINDER_Y: + case SHAPE_TYPE_CYLINDER_Z: { + glm::vec3 radii = importanceSample2DDimension(emitRadiusStart) * 0.5f * emitDimensions; + int axis = shapeType - SHAPE_TYPE_CYLINDER_X; + + emitPosition[axis] = emitDimensions[axis] * randFloatInRange(-0.5f, 0.5f); + emitDirection[axis] = 0.0f; + axis = (axis + 1) % 3; + emitPosition[axis] = radii[axis] * glm::cos(azimuth); + emitDirection[axis] = radii[axis] > 0.0f ? emitPosition[axis] / (radii[axis] * radii[axis]) : 0.0f; + axis = (axis + 1) % 3; + emitPosition[axis] = radii[axis] * glm::sin(azimuth); + emitDirection[axis] = radii[axis] > 0.0f ? emitPosition[axis] / (radii[axis] * radii[axis]) : 0.0f; + emitDirection = glm::normalize(emitDirection); + break; + } + + case SHAPE_TYPE_CIRCLE: { + glm::vec2 radii = importanceSample2DDimension(emitRadiusStart) * 0.5f * glm::vec2(emitDimensions.x, emitDimensions.z); + float x = radii.x * glm::cos(azimuth); + float z = radii.y * glm::sin(azimuth); + emitPosition = glm::vec3(x, 0.0f, z); + emitDirection = Vectors::UP; + break; + } + case SHAPE_TYPE_PLANE: { + glm::vec2 dim = importanceSample2DDimension(emitRadiusStart) * 0.5f * glm::vec2(emitDimensions.x, emitDimensions.z); + + int side = randIntInRange(0, 3); + int axis = side % 2; + float direction = side > 1 ? 1.0f : -1.0f; + + glm::vec2 pos; + pos[axis] = direction * dim[axis]; + axis = (axis + 1) % 2; + pos[axis] = dim[axis] * randFloatInRange(-1.0f, 1.0f); + + emitPosition = glm::vec3(pos.x, 0.0f, pos.y); + emitDirection = Vectors::UP; + break; + } + + case SHAPE_TYPE_COMPOUND: { + // if we get here we know that geometryResource is loaded + + size_t index = randFloat() * triangleInfo.totalSamples; + Triangle triangle; + for (size_t i = 0; i < triangleInfo.samplesPerTriangle.size(); i++) { + size_t numSamples = triangleInfo.samplesPerTriangle[i]; + if (index < numSamples) { + triangle = triangleInfo.triangles[i]; + break; + } + index -= numSamples; + } + + float edgeLength1 = glm::length(triangle.v1 - triangle.v0); + float edgeLength2 = glm::length(triangle.v2 - triangle.v1); + float edgeLength3 = glm::length(triangle.v0 - triangle.v2); + + float perimeter = edgeLength1 + edgeLength2 + edgeLength3; + float fraction1 = randFloatInRange(0.0f, 1.0f); + float fractionEdge1 = glm::min(fraction1 * perimeter / edgeLength1, 1.0f); + float fraction2 = fraction1 - edgeLength1 / perimeter; + float fractionEdge2 = glm::clamp(fraction2 * perimeter / edgeLength2, 0.0f, 1.0f); + float fraction3 = fraction2 - edgeLength2 / perimeter; + float fractionEdge3 = glm::clamp(fraction3 * perimeter / edgeLength3, 0.0f, 1.0f); + + float dim = importanceSample2DDimension(emitRadiusStart); + triangle = triangle * (glm::scale(emitDimensions) * triangleInfo.transform); + glm::vec3 center = (triangle.v0 + triangle.v1 + triangle.v2) / 3.0f; + glm::vec3 v0 = (dim * (triangle.v0 - center)) + center; + glm::vec3 v1 = (dim * (triangle.v1 - center)) + center; + glm::vec3 v2 = (dim * (triangle.v2 - center)) + center; + + emitPosition = glm::mix(v0, glm::mix(v1, glm::mix(v2, v0, fractionEdge3), fractionEdge2), fractionEdge1); + emitDirection = triangle.getNormal(); + break; + } + + case SHAPE_TYPE_SPHERE: + case SHAPE_TYPE_ELLIPSOID: + default: { + glm::vec3 radii = importanceSample3DDimension(emitRadiusStart) * 0.5f * emitDimensions; + float x = radii.x * glm::cos(elevation) * glm::cos(azimuth); + float y = radii.y * glm::cos(elevation) * glm::sin(azimuth); + float z = radii.z * glm::sin(elevation); + emitPosition = glm::vec3(x, y, z); + emitDirection = glm::normalize(glm::vec3(radii.x > 0.0f ? x / (radii.x * radii.x) : 0.0f, + radii.y > 0.0f ? y / (radii.y * radii.y) : 0.0f, + radii.z > 0.0f ? z / (radii.z * radii.z) : 0.0f)); + break; + } } - glm::vec3 radii = radiusScale * 0.5f * emitDimensions; - float x = radii.x * glm::cos(elevation) * glm::cos(azimuth); - float y = radii.y * glm::cos(elevation) * glm::sin(azimuth); - float z = radii.z * glm::sin(elevation); - glm::vec3 emitPosition = glm::vec3(x, y, z); - emitDirection = glm::normalize(glm::vec3( - radii.x > 0.0f ? x / (radii.x * radii.x) : 0.0f, - radii.y > 0.0f ? y / (radii.y * radii.y) : 0.0f, - radii.z > 0.0f ? z / (radii.z * radii.z) : 0.0f - )); particle.relativePosition += emitOrientation * emitPosition; } } @@ -267,20 +402,28 @@ void ParticleEffectEntityRenderer::stepSimulation() { const auto now = usecTimestampNow(); const auto interval = std::min(USECS_PER_SECOND / 60, now - _lastSimulated); _lastSimulated = now; - + particle::Properties particleProperties; - withReadLock([&]{ + ShapeType shapeType; + GeometryResource::Pointer geometryResource; + withReadLock([&] { particleProperties = _particleProperties; + shapeType = _shapeType; + geometryResource = _geometryResource; }); const auto& modelTransform = getModelTransform(); - if (_emitting && particleProperties.emitting()) { + if (_emitting && particleProperties.emitting() && + (shapeType != SHAPE_TYPE_COMPOUND || (geometryResource && geometryResource->isLoaded()))) { uint64_t emitInterval = particleProperties.emitIntervalUsecs(); if (emitInterval > 0 && interval >= _timeUntilNextEmit) { auto timeRemaining = interval; while (timeRemaining > _timeUntilNextEmit) { + if (_shapeType == SHAPE_TYPE_COMPOUND && !_hasComputedTriangles) { + computeTriangles(geometryResource->getHFMModel()); + } // emit particle - _cpuParticles.push_back(createParticle(now, modelTransform, particleProperties)); + _cpuParticles.push_back(createParticle(now, modelTransform, particleProperties, shapeType, geometryResource, _triangleInfo)); _timeUntilNextEmit = emitInterval; if (emitInterval < timeRemaining) { timeRemaining -= emitInterval; @@ -297,7 +440,7 @@ void ParticleEffectEntityRenderer::stepSimulation() { } const float deltaTime = (float)interval / (float)USECS_PER_SECOND; - // update the particles + // update the particles for (auto& particle : _cpuParticles) { if (_prevEmitterShouldTrail != particleProperties.emission.shouldTrail) { if (_prevEmitterShouldTrail) { @@ -313,7 +456,7 @@ void ParticleEffectEntityRenderer::stepSimulation() { static GpuParticles gpuParticles; gpuParticles.clear(); gpuParticles.reserve(_cpuParticles.size()); // Reserve space - std::transform(_cpuParticles.begin(), _cpuParticles.end(), std::back_inserter(gpuParticles), [&particleProperties, &modelTransform](const CpuParticle& particle) { + std::transform(_cpuParticles.begin(), _cpuParticles.end(), std::back_inserter(gpuParticles), [&particleProperties, &modelTransform] (const CpuParticle& particle) { glm::vec3 position = particle.relativePosition + (particleProperties.emission.shouldTrail ? particle.basePosition : modelTransform.getTranslation()); return GpuParticle(position, glm::vec2(particle.lifetime, particle.seed)); }); @@ -356,5 +499,131 @@ void ParticleEffectEntityRenderer::doRender(RenderArgs* args) { batch.setInputBuffer(0, _particleBuffer, 0, sizeof(GpuParticle)); auto numParticles = _particleBuffer->getSize() / sizeof(GpuParticle); + static const size_t VERTEX_PER_PARTICLE = 4; batch.drawInstanced((gpu::uint32)numParticles, gpu::TRIANGLE_STRIP, (gpu::uint32)VERTEX_PER_PARTICLE); } + +void ParticleEffectEntityRenderer::fetchGeometryResource() { + QUrl hullURL(_compoundShapeURL); + if (hullURL.isEmpty()) { + _geometryResource.reset(); + } else { + _geometryResource = DependencyManager::get()->getCollisionGeometryResource(hullURL); + } +} + +// FIXME: this is very similar to Model::calculateTriangleSets +void ParticleEffectEntityRenderer::computeTriangles(const hfm::Model& hfmModel) { + PROFILE_RANGE(render, __FUNCTION__); + + int numberOfMeshes = hfmModel.meshes.size(); + + _hasComputedTriangles = true; + _triangleInfo.triangles.clear(); + _triangleInfo.samplesPerTriangle.clear(); + + std::vector areas; + float minArea = FLT_MAX; + AABox bounds; + + for (int i = 0; i < numberOfMeshes; i++) { + const HFMMesh& mesh = hfmModel.meshes.at(i); + + const int numberOfParts = mesh.parts.size(); + for (int j = 0; j < numberOfParts; j++) { + const HFMMeshPart& part = mesh.parts.at(j); + + const int INDICES_PER_TRIANGLE = 3; + const int INDICES_PER_QUAD = 4; + const int TRIANGLES_PER_QUAD = 2; + + // tell our triangleSet how many triangles to expect. + int numberOfQuads = part.quadIndices.size() / INDICES_PER_QUAD; + int numberOfTris = part.triangleIndices.size() / INDICES_PER_TRIANGLE; + int totalTriangles = (numberOfQuads * TRIANGLES_PER_QUAD) + numberOfTris; + _triangleInfo.triangles.reserve(_triangleInfo.triangles.size() + totalTriangles); + areas.reserve(areas.size() + totalTriangles); + + auto meshTransform = hfmModel.offset * mesh.modelTransform; + + if (part.quadIndices.size() > 0) { + int vIndex = 0; + for (int q = 0; q < numberOfQuads; q++) { + int i0 = part.quadIndices[vIndex++]; + int i1 = part.quadIndices[vIndex++]; + int i2 = part.quadIndices[vIndex++]; + int i3 = part.quadIndices[vIndex++]; + + // track the model space version... these points will be transformed by the FST's offset, + // which includes the scaling, rotation, and translation specified by the FST/FBX, + // this can't change at runtime, so we can safely store these in our TriangleSet + glm::vec3 v0 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i0], 1.0f)); + glm::vec3 v1 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i1], 1.0f)); + glm::vec3 v2 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i2], 1.0f)); + glm::vec3 v3 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i3], 1.0f)); + + Triangle tri1 = { v0, v1, v3 }; + Triangle tri2 = { v1, v2, v3 }; + _triangleInfo.triangles.push_back(tri1); + _triangleInfo.triangles.push_back(tri2); + + float area1 = tri1.getArea(); + areas.push_back(area1); + if (area1 > EPSILON) { + minArea = std::min(minArea, area1); + } + + float area2 = tri2.getArea(); + areas.push_back(area2); + if (area2 > EPSILON) { + minArea = std::min(minArea, area2); + } + + bounds += v0; + bounds += v1; + bounds += v2; + bounds += v3; + } + } + + if (part.triangleIndices.size() > 0) { + int vIndex = 0; + for (int t = 0; t < numberOfTris; t++) { + int i0 = part.triangleIndices[vIndex++]; + int i1 = part.triangleIndices[vIndex++]; + int i2 = part.triangleIndices[vIndex++]; + + // track the model space version... these points will be transformed by the FST's offset, + // which includes the scaling, rotation, and translation specified by the FST/FBX, + // this can't change at runtime, so we can safely store these in our TriangleSet + glm::vec3 v0 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i0], 1.0f)); + glm::vec3 v1 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i1], 1.0f)); + glm::vec3 v2 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i2], 1.0f)); + + Triangle tri = { v0, v1, v2 }; + _triangleInfo.triangles.push_back(tri); + + float area = tri.getArea(); + areas.push_back(area); + if (area > EPSILON) { + minArea = std::min(minArea, area); + } + + bounds += v0; + bounds += v1; + bounds += v2; + } + } + } + } + + _triangleInfo.totalSamples = 0; + for (auto& area : areas) { + size_t numSamples = area / minArea; + _triangleInfo.samplesPerTriangle.push_back(numSamples); + _triangleInfo.totalSamples += numSamples; + } + + glm::vec3 scale = bounds.getScale(); + _triangleInfo.transform = glm::scale(1.0f / scale) * glm::translate(-bounds.calcCenter()); +} \ No newline at end of file diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h index 853d5cac29..d13c966e96 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h @@ -81,7 +81,18 @@ private: glm::vec2 spare; }; - static CpuParticle createParticle(uint64_t now, const Transform& baseTransform, const particle::Properties& particleProperties); + void computeTriangles(const hfm::Model& hfmModel); + bool _hasComputedTriangles{ false }; + struct TriangleInfo { + std::vector triangles; + std::vector samplesPerTriangle; + size_t totalSamples; + glm::mat4 transform; + } _triangleInfo; + + static CpuParticle createParticle(uint64_t now, const Transform& baseTransform, const particle::Properties& particleProperties, + const ShapeType& shapeType, const GeometryResource::Pointer& geometryResource, + const TriangleInfo& triangleInfo); void stepSimulation(); particle::Properties _particleProperties; @@ -90,11 +101,16 @@ private: CpuParticles _cpuParticles; bool _emitting { false }; uint64_t _timeUntilNextEmit { 0 }; - BufferPointer _particleBuffer{ std::make_shared() }; + BufferPointer _particleBuffer { std::make_shared() }; BufferView _uniformBuffer; quint64 _lastSimulated { 0 }; PulsePropertyGroup _pulseProperties; + ShapeType _shapeType; + QString _compoundShapeURL; + + void fetchGeometryResource(); + GeometryResource::Pointer _geometryResource; NetworkTexturePointer _networkTexture; ScenePointer _scene; diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index bd4c6e5c71..8a50c39da9 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1698,10 +1698,7 @@ AACube EntityItem::getQueryAACube(bool& success) const { } bool EntityItem::shouldPuffQueryAACube() const { - bool hasGrabs = _grabsLock.resultWithReadLock([&] { - return _grabs.count() > 0; - }); - return hasActions() || isChildOfMyAvatar() || isMovingRelativeToParent() || hasGrabs; + return hasActions() || isChildOfMyAvatar() || isMovingRelativeToParent(); } // TODO: get rid of all users of this function... @@ -1896,7 +1893,8 @@ void EntityItem::setScaledDimensions(const glm::vec3& value) { void EntityItem::setUnscaledDimensions(const glm::vec3& value) { glm::vec3 newDimensions = glm::max(value, glm::vec3(ENTITY_ITEM_MIN_DIMENSION)); - if (getUnscaledDimensions() != newDimensions) { + const float MIN_SCALE_CHANGE_SQUARED = 1.0e-6f; + if (glm::length2(getUnscaledDimensions() - newDimensions) > MIN_SCALE_CHANGE_SQUARED) { withWriteLock([&] { _unscaledDimensions = newDimensions; }); @@ -2086,7 +2084,7 @@ void EntityItem::computeCollisionGroupAndFinalMask(int32_t& group, int32_t& mask } else { if (getDynamic()) { group = BULLET_COLLISION_GROUP_DYNAMIC; - } else if (isMovingRelativeToParent() || hasActions()) { + } else if (hasActions() || isMovingRelativeToParent()) { group = BULLET_COLLISION_GROUP_KINEMATIC; } else { group = BULLET_COLLISION_GROUP_STATIC; @@ -3057,30 +3055,18 @@ bool EntityItem::getCollisionless() const { } uint16_t EntityItem::getCollisionMask() const { - uint16_t result; - withReadLock([&] { - result = _collisionMask; - }); - return result; + return _collisionMask; } bool EntityItem::getDynamic() const { if (SHAPE_TYPE_STATIC_MESH == getShapeType()) { return false; } - bool result; - withReadLock([&] { - result = _dynamic; - }); - return result; + return _dynamic; } bool EntityItem::getLocked() const { - bool result; - withReadLock([&] { - result = _locked; - }); - return result; + return _locked; } void EntityItem::setLocked(bool value) { @@ -3152,7 +3138,6 @@ uint32_t EntityItem::getDirtyFlags() const { return result; } - void EntityItem::markDirtyFlags(uint32_t mask) { withWriteLock([&] { mask &= Simulation::DIRTY_FLAGS; diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 01ed949a0c..c57fd16a2e 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -448,7 +448,7 @@ public: bool clearActions(EntitySimulationPointer simulation); void setDynamicData(QByteArray dynamicData); const QByteArray getDynamicData() const; - bool hasActions() const { return !_objectActions.empty(); } + bool hasActions() const { return !_objectActions.empty() || !_grabActions.empty(); } QList getActionIDs() const { return _objectActions.keys(); } QVariantMap getActionArguments(const QUuid& actionID) const; void deserializeActions(); diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 3efedf02ec..5958af66dd 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -127,6 +127,7 @@ void buildStringToShapeTypeLookup() { addShapeType(SHAPE_TYPE_SIMPLE_COMPOUND); addShapeType(SHAPE_TYPE_STATIC_MESH); addShapeType(SHAPE_TYPE_ELLIPSOID); + addShapeType(SHAPE_TYPE_CIRCLE); } QHash stringToMaterialMappingModeLookup; @@ -1114,23 +1115,28 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * default, particles emit along the entity's local z-axis, and azimuthStart and azimuthFinish * are relative to the entity's local x-axis. The default value is a rotation of -90 degrees about the local x-axis, i.e., * the particles emit vertically. - * @property {Vec3} emitDimensions=0,0,0 - The dimensions of the ellipsoid from which particles are emitted. - * @property {number} emitRadiusStart=1 - The starting radius within the ellipsoid at which particles start being emitted; - * range 0.01.0 for the ellipsoid center to the ellipsoid surface, respectively. - * Particles are emitted from the portion of the ellipsoid that lies between emitRadiusStart and the - * ellipsoid's surface. + * @property {Vec3} emitDimensions=0,0,0 - The dimensions of the shape from which particles are emitted. The shape is specified with + * shapeType. + * @property {number} emitRadiusStart=1 - The starting radius within the shape at which particles start being emitted; + * range 0.01.0 for the center to the surface, respectively. + * Particles are emitted from the portion of the shape that lies between emitRadiusStart and the + * shape's surface. * @property {number} polarStart=0 - The angle in radians from the entity's local z-axis at which particles start being emitted - * within the ellipsoid; range 0Math.PI. Particles are emitted from the portion of the - * ellipsoid that lies between polarStart and polarFinish. + * within the shape; range 0Math.PI. Particles are emitted from the portion of the + * shape that lies between polarStart and polarFinish. Only used if shapeType is + * ellipsoid or sphere. * @property {number} polarFinish=0 - The angle in radians from the entity's local z-axis at which particles stop being emitted - * within the ellipsoid; range 0Math.PI. Particles are emitted from the portion of the - * ellipsoid that lies between polarStart and polarFinish. + * within the shape; range 0Math.PI. Particles are emitted from the portion of the + * shape that lies between polarStart and polarFinish. Only used if shapeType is + * ellipsoid or sphere. * @property {number} azimuthStart=-Math.PI - The angle in radians from the entity's local x-axis about the entity's local * z-axis at which particles start being emitted; range -Math.PIMath.PI. Particles are - * emitted from the portion of the ellipsoid that lies between azimuthStart and azimuthFinish. + * emitted from the portion of the shape that lies between azimuthStart and azimuthFinish. + * Only used if shapeType is ellipsoid, sphere, or circle. * @property {number} azimuthFinish=Math.PI - The angle in radians from the entity's local x-axis about the entity's local * z-axis at which particles stop being emitted; range -Math.PIMath.PI. Particles are - * emitted from the portion of the ellipsoid that lies between azimuthStart and azimuthFinish. + * emitted from the portion of the shape that lies between azimuthStart and azimuthFinish. + * Only used if shapeType is ellipsoid, sphere, or circle.. * * @property {string} textures="" - The URL of a JPG or PNG image file to display for each particle. If you want transparency, * use PNG format. @@ -1170,7 +1176,9 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * up in the world. If true, they will point towards the entity's up vector, based on its orientation. * @property {Entities.Pulse} pulse - The pulse-related properties. Deprecated. * - * @property {ShapeType} shapeType="none" - Currently not used. Read-only. + * @property {ShapeType} shapeType="ellipsoid" - The shape of the collision hull used if collisions are enabled. + * @property {string} compoundShapeURL="" - The model file to use for the compound shape if shapeType is + * "compound". * * @example Create a ball of green smoke. * particles = Entities.addEntity({ @@ -1658,6 +1666,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool // Particles only if (_type == EntityTypes::ParticleEffect) { COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SHAPE_TYPE, shapeType, getShapeTypeAsString()); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COMPOUND_SHAPE_URL, compoundShapeURL); COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_COLOR, color, u8vec3Color); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA, alpha); _pulse.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); @@ -3104,6 +3113,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy if (properties.getType() == EntityTypes::ParticleEffect) { APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, (uint32_t)(properties.getShapeType())); + APPEND_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, properties.getCompoundShapeURL()); APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor()); APPEND_ENTITY_PROPERTY(PROP_ALPHA, properties.getAlpha()); _staticPulse.setProperties(properties); @@ -3584,6 +3594,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int if (properties.getType() == EntityTypes::ParticleEffect) { READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHAPE_TYPE, ShapeType, setShapeType); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, u8vec3Color, setColor); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA, float, setAlpha); properties.getPulse().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 8bf7c92b1f..1ccf3fcfa2 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include "EntitySimulation.h" #include "VariantMapToScriptValue.h" @@ -286,27 +287,7 @@ void EntityTree::postAddEntity(EntityItemPointer entity) { assert(entity); if (getIsServer()) { - QString certID(entity->getCertificateID()); - EntityItemID entityItemID = entity->getEntityItemID(); - EntityItemID existingEntityItemID; - - { - QWriteLocker locker(&_entityCertificateIDMapLock); - existingEntityItemID = _entityCertificateIDMap.value(certID); - if (!certID.isEmpty()) { - _entityCertificateIDMap.insert(certID, entityItemID); - qCDebug(entities) << "Certificate ID" << certID << "belongs to" << entityItemID; - } - } - - // Delete an already-existing entity from the tree if it has the same - // CertificateID as the entity we're trying to add. - if (!existingEntityItemID.isNull() && !entity->getCertificateType().contains(DOMAIN_UNLIMITED)) { - qCDebug(entities) << "Certificate ID" << certID << "already exists on entity with ID" - << existingEntityItemID << ". Deleting existing entity."; - deleteEntity(existingEntityItemID, true); - return; - } + addCertifiedEntityOnServer(entity); } // check to see if we need to simulate this entity.. @@ -764,13 +745,7 @@ void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator) theEntity->die(); if (getIsServer()) { - { - QWriteLocker entityCertificateIDMapLocker(&_entityCertificateIDMapLock); - QString certID = theEntity->getCertificateID(); - if (theEntity->getEntityItemID() == _entityCertificateIDMap.value(certID)) { - _entityCertificateIDMap.remove(certID); - } - } + removeCertifiedEntityOnServer(theEntity); // set up the deleted entities ID QWriteLocker recentlyDeletedEntitiesLocker(&_recentlyDeletedEntitiesLock); @@ -1421,11 +1396,123 @@ bool EntityTree::isScriptInWhitelist(const QString& scriptProperty) { return false; } +void EntityTree::addCertifiedEntityOnServer(EntityItemPointer entity) { + QString certID(entity->getCertificateID()); + EntityItemID existingEntityItemID; + if (!certID.isEmpty()) { + EntityItemID entityItemID = entity->getEntityItemID(); + QWriteLocker locker(&_entityCertificateIDMapLock); + QList& entityList = _entityCertificateIDMap[certID]; // inserts it if needed. + if (!entityList.isEmpty() && !entity->getCertificateType().contains(DOMAIN_UNLIMITED)) { + existingEntityItemID = entityList.first(); // we will only care about the first, if any, below. + entityList.removeOne(existingEntityItemID); + } + entityList << entityItemID; // adds to list within hash because entityList is a reference. + qCDebug(entities) << "Certificate ID" << certID << "belongs to" << entityItemID << "total" << entityList.size() << "entities."; + } + // Delete an already-existing entity from the tree if it has the same + // CertificateID as the entity we're trying to add. + if (!existingEntityItemID.isNull()) { + qCDebug(entities) << "Certificate ID" << certID << "already exists on entity with ID" + << existingEntityItemID << ". Deleting existing entity."; + withWriteLock([&] { + deleteEntity(existingEntityItemID, true); + }); + } +} + +void EntityTree::removeCertifiedEntityOnServer(EntityItemPointer entity) { + QString certID = entity->getCertificateID(); + if (!certID.isEmpty()) { + QWriteLocker entityCertificateIDMapLocker(&_entityCertificateIDMapLock); + QList& entityList = _entityCertificateIDMap[certID]; + entityList.removeOne(entity->getEntityItemID()); + if (entityList.isEmpty()) { + // hmmm, do we to make it be a hash instead of a list, so that this is faster if you stamp out 1000 of a domainUnlimited? + _entityCertificateIDMap.remove(certID); + } + } +} + +void EntityTree::startDynamicDomainVerificationOnServer(float minimumAgeToRemove) { + QReadLocker locker(&_entityCertificateIDMapLock); + QHashIterator> i(_entityCertificateIDMap); + qCDebug(entities) << _entityCertificateIDMap.size() << "certificates present."; + while (i.hasNext()) { + i.next(); + const auto& certificateID = i.key(); + const auto& entityIDs = i.value(); + if (entityIDs.isEmpty()) { + continue; + } + + // Examine each cert: + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkRequest networkRequest; + networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL(); + requestURL.setPath("/api/v1/commerce/proof_of_purchase_status/location"); + QJsonObject request; + request["certificate_id"] = certificateID; + networkRequest.setUrl(requestURL); + + QNetworkReply* networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson()); + + connect(networkReply, &QNetworkReply::finished, this, [this, entityIDs, networkReply, minimumAgeToRemove, &certificateID] { + + QJsonObject jsonObject = QJsonDocument::fromJson(networkReply->readAll()).object(); + jsonObject = jsonObject["data"].toObject(); + bool failure = networkReply->error() != QNetworkReply::NoError; + auto failureReason = networkReply->error(); + networkReply->deleteLater(); + if (failure) { + qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << failureReason + << "; NOT deleting cert" << certificateID << "More info:" << jsonObject; + return; + } + QString thisDomainID = DependencyManager::get()->getDomainID().remove(QRegExp("\\{|\\}")); + if (jsonObject["domain_id"].toString() == thisDomainID) { + // Entity belongs here. Nothing to do. + return; + } + // Entity does not belong here: + QList retained; + for (int i = 0; i < entityIDs.size(); i++) { + EntityItemID entityID = entityIDs.at(i); + EntityItemPointer entity = findEntityByEntityItemID(entityID); + if (!entity) { + qCDebug(entities) << "Entity undergoing dynamic domain verification is no longer available:" << entityID; + continue; + } + if (entity->getAge() <= minimumAgeToRemove) { + qCDebug(entities) << "Entity failed dynamic domain verification, but was created too recently to necessitate deletion:" << entityID; + retained << entityID; + continue; + } + qCDebug(entities) << "Entity's cert's domain ID" << jsonObject["domain_id"].toString() + << "doesn't match the current Domain ID" << thisDomainID << "; deleting entity" << entityID; + withWriteLock([&] { + deleteEntity(entityID, true); + }); + } + { + QWriteLocker entityCertificateIDMapLocker(&_entityCertificateIDMapLock); + if (retained.isEmpty()) { + qCDebug(entities) << "Removed" << certificateID; + _entityCertificateIDMap.remove(certificateID); + } else { + qCDebug(entities) << "Retained" << retained.size() << "young entities for" << certificateID; + _entityCertificateIDMap[certificateID] = retained; + } + } + }); + } +} + void EntityTree::startChallengeOwnershipTimer(const EntityItemID& entityItemID) { QTimer* _challengeOwnershipTimeoutTimer = new QTimer(this); - connect(this, &EntityTree::killChallengeOwnershipTimeoutTimer, this, [=](const QString& certID) { - QReadLocker locker(&_entityCertificateIDMapLock); - EntityItemID id = _entityCertificateIDMap.value(certID); + connect(this, &EntityTree::killChallengeOwnershipTimeoutTimer, this, [=](const EntityItemID& id) { if (entityItemID == id && _challengeOwnershipTimeoutTimer) { _challengeOwnershipTimeoutTimer->stop(); _challengeOwnershipTimeoutTimer->deleteLater(); @@ -1445,26 +1532,21 @@ void EntityTree::startChallengeOwnershipTimer(const EntityItemID& entityItemID) _challengeOwnershipTimeoutTimer->start(5000); } -QByteArray EntityTree::computeNonce(const QString& certID, const QString ownerKey) { +QByteArray EntityTree::computeNonce(const EntityItemID& entityID, const QString ownerKey) { QUuid nonce = QUuid::createUuid(); //random, 5-hex value, separated by "-" QByteArray nonceBytes = nonce.toByteArray(); - QWriteLocker locker(&_certNonceMapLock); - _certNonceMap.insert(certID, QPair(nonce, ownerKey)); + QWriteLocker locker(&_entityNonceMapLock); + _entityNonceMap.insert(entityID, QPair(nonce, ownerKey)); return nonceBytes; } -bool EntityTree::verifyNonce(const QString& certID, const QString& nonce, EntityItemID& id) { - { - QReadLocker certIdMapLocker(&_entityCertificateIDMapLock); - id = _entityCertificateIDMap.value(certID); - } - +bool EntityTree::verifyNonce(const EntityItemID& entityID, const QString& nonce) { QString actualNonce, key; { - QWriteLocker locker(&_certNonceMapLock); - QPair sent = _certNonceMap.take(certID); + QWriteLocker locker(&_entityNonceMapLock); + QPair sent = _entityNonceMap.take(entityID); actualNonce = sent.first.toString(); key = sent.second; } @@ -1474,9 +1556,9 @@ bool EntityTree::verifyNonce(const QString& certID, const QString& nonce, Entity bool verificationSuccess = EntityItemProperties::verifySignature(annotatedKey.toUtf8(), hashedActualNonce, QByteArray::fromBase64(nonce.toUtf8())); if (verificationSuccess) { - qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "succeeded."; + qCDebug(entities) << "Ownership challenge for Entity ID" << entityID << "succeeded."; } else { - qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "failed. Actual nonce:" << actualNonce << + qCDebug(entities) << "Ownership challenge for Entity ID" << entityID << "failed. Actual nonce:" << actualNonce << "\nHashed actual nonce (digest):" << hashedActualNonce << "\nSent nonce (signature)" << nonce << "\nKey" << key; } @@ -1484,42 +1566,42 @@ bool EntityTree::verifyNonce(const QString& certID, const QString& nonce, Entity } void EntityTree::processChallengeOwnershipRequestPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { - int certIDByteArraySize; + int idByteArraySize; int textByteArraySize; int nodeToChallengeByteArraySize; - message.readPrimitive(&certIDByteArraySize); + message.readPrimitive(&idByteArraySize); message.readPrimitive(&textByteArraySize); message.readPrimitive(&nodeToChallengeByteArraySize); - QByteArray certID(message.read(certIDByteArraySize)); + QByteArray id(message.read(idByteArraySize)); QByteArray text(message.read(textByteArraySize)); QByteArray nodeToChallenge(message.read(nodeToChallengeByteArraySize)); - sendChallengeOwnershipRequestPacket(certID, text, nodeToChallenge, sourceNode); + sendChallengeOwnershipRequestPacket(id, text, nodeToChallenge, sourceNode); } void EntityTree::processChallengeOwnershipReplyPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { auto nodeList = DependencyManager::get(); - int certIDByteArraySize; + int idByteArraySize; int textByteArraySize; int challengingNodeUUIDByteArraySize; - message.readPrimitive(&certIDByteArraySize); + message.readPrimitive(&idByteArraySize); message.readPrimitive(&textByteArraySize); message.readPrimitive(&challengingNodeUUIDByteArraySize); - QByteArray certID(message.read(certIDByteArraySize)); + QByteArray id(message.read(idByteArraySize)); QByteArray text(message.read(textByteArraySize)); QUuid challengingNode = QUuid::fromRfc4122(message.read(challengingNodeUUIDByteArraySize)); auto challengeOwnershipReplyPacket = NLPacket::create(PacketType::ChallengeOwnershipReply, - certIDByteArraySize + text.length() + 2 * sizeof(int), + idByteArraySize + text.length() + 2 * sizeof(int), true); - challengeOwnershipReplyPacket->writePrimitive(certIDByteArraySize); + challengeOwnershipReplyPacket->writePrimitive(idByteArraySize); challengeOwnershipReplyPacket->writePrimitive(text.length()); - challengeOwnershipReplyPacket->write(certID); + challengeOwnershipReplyPacket->write(id); challengeOwnershipReplyPacket->write(text); nodeList->sendPacket(std::move(challengeOwnershipReplyPacket), *(nodeList->nodeWithUUID(challengingNode))); @@ -1529,7 +1611,7 @@ void EntityTree::sendChallengeOwnershipPacket(const QString& certID, const QStri // 1. Obtain a nonce auto nodeList = DependencyManager::get(); - QByteArray text = computeNonce(certID, ownerKey); + QByteArray text = computeNonce(entityItemID, ownerKey); if (text == "") { qCDebug(entities) << "CRITICAL ERROR: Couldn't compute nonce. Deleting entity..."; @@ -1539,14 +1621,14 @@ void EntityTree::sendChallengeOwnershipPacket(const QString& certID, const QStri } else { qCDebug(entities) << "Challenging ownership of Cert ID" << certID; // 2. Send the nonce to the rezzing avatar's node - QByteArray certIDByteArray = certID.toUtf8(); - int certIDByteArraySize = certIDByteArray.size(); + QByteArray idByteArray = entityItemID.toByteArray(); + int idByteArraySize = idByteArray.size(); auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnership, - certIDByteArraySize + text.length() + 2 * sizeof(int), + idByteArraySize + text.length() + 2 * sizeof(int), true); - challengeOwnershipPacket->writePrimitive(certIDByteArraySize); + challengeOwnershipPacket->writePrimitive(idByteArraySize); challengeOwnershipPacket->writePrimitive(text.length()); - challengeOwnershipPacket->write(certIDByteArray); + challengeOwnershipPacket->write(idByteArray); challengeOwnershipPacket->write(text); nodeList->sendPacket(std::move(challengeOwnershipPacket), *senderNode); @@ -1560,24 +1642,24 @@ void EntityTree::sendChallengeOwnershipPacket(const QString& certID, const QStri } } -void EntityTree::sendChallengeOwnershipRequestPacket(const QByteArray& certID, const QByteArray& text, const QByteArray& nodeToChallenge, const SharedNodePointer& senderNode) { +void EntityTree::sendChallengeOwnershipRequestPacket(const QByteArray& id, const QByteArray& text, const QByteArray& nodeToChallenge, const SharedNodePointer& senderNode) { auto nodeList = DependencyManager::get(); // In this case, Client A is challenging Client B. Client A is inspecting a certified entity that it wants // to make sure belongs to Avatar B. QByteArray senderNodeUUID = senderNode->getUUID().toRfc4122(); - int certIDByteArraySize = certID.length(); + int idByteArraySize = id.length(); int TextByteArraySize = text.length(); int senderNodeUUIDSize = senderNodeUUID.length(); auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnershipRequest, - certIDByteArraySize + TextByteArraySize + senderNodeUUIDSize + 3 * sizeof(int), + idByteArraySize + TextByteArraySize + senderNodeUUIDSize + 3 * sizeof(int), true); - challengeOwnershipPacket->writePrimitive(certIDByteArraySize); + challengeOwnershipPacket->writePrimitive(idByteArraySize); challengeOwnershipPacket->writePrimitive(TextByteArraySize); challengeOwnershipPacket->writePrimitive(senderNodeUUIDSize); - challengeOwnershipPacket->write(certID); + challengeOwnershipPacket->write(id); challengeOwnershipPacket->write(text); challengeOwnershipPacket->write(senderNodeUUID); @@ -1636,22 +1718,21 @@ void EntityTree::validatePop(const QString& certID, const EntityItemID& entityIt } void EntityTree::processChallengeOwnershipPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { - int certIDByteArraySize; + int idByteArraySize; int textByteArraySize; - message.readPrimitive(&certIDByteArraySize); + message.readPrimitive(&idByteArraySize); message.readPrimitive(&textByteArraySize); - QString certID(message.read(certIDByteArraySize)); + EntityItemID id(message.read(idByteArraySize)); QString text(message.read(textByteArraySize)); - emit killChallengeOwnershipTimeoutTimer(certID); + emit killChallengeOwnershipTimeoutTimer(id); - EntityItemID id; - if (!verifyNonce(certID, text, id)) { - if (!id.isNull()) { + if (!verifyNonce(id, text)) { + withWriteLock([&] { deleteEntity(id, true); - } + }); } } diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index e627a07d13..c80517c82b 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -157,11 +157,6 @@ public: return _recentlyDeletedEntityItemIDs; } - QHash getEntityCertificateIDMap() const { - QReadLocker locker(&_entityCertificateIDMapLock); - return _entityCertificateIDMap; - } - void forgetEntitiesDeletedBefore(quint64 sinceTime); int processEraseMessage(ReceivedMessage& message, const SharedNodePointer& sourceNode); @@ -252,8 +247,8 @@ public: static const float DEFAULT_MAX_TMP_ENTITY_LIFETIME; - QByteArray computeNonce(const QString& certID, const QString ownerKey); - bool verifyNonce(const QString& certID, const QString& nonce, EntityItemID& id); + QByteArray computeNonce(const EntityItemID& entityID, const QString ownerKey); + bool verifyNonce(const EntityItemID& entityID, const QString& nonce); QUuid getMyAvatarSessionUUID() { return _myAvatar ? _myAvatar->getSessionUUID() : QUuid(); } void setMyAvatar(std::shared_ptr myAvatar) { _myAvatar = myAvatar; } @@ -279,6 +274,7 @@ public: void updateEntityQueryAACube(SpatiallyNestablePointer object, EntityEditPacketSender* packetSender, bool force, bool tellServer); + void startDynamicDomainVerificationOnServer(float minimumAgeToRemove); signals: void deletingEntity(const EntityItemID& entityID); @@ -290,7 +286,7 @@ signals: void entityServerScriptChanging(const EntityItemID& entityItemID, const bool reload); void newCollisionSoundURL(const QUrl& url, const EntityItemID& entityID); void clearingEntities(); - void killChallengeOwnershipTimeoutTimer(const QString& certID); + void killChallengeOwnershipTimeoutTimer(const EntityItemID& certID); protected: @@ -327,10 +323,10 @@ protected: QHash _entityMap; mutable QReadWriteLock _entityCertificateIDMapLock; - QHash _entityCertificateIDMap; + QHash> _entityCertificateIDMap; - mutable QReadWriteLock _certNonceMapLock; - QHash> _certNonceMap; + mutable QReadWriteLock _entityNonceMapLock; + QHash> _entityNonceMap; EntitySimulationPointer _simulation; @@ -377,8 +373,10 @@ protected: Q_INVOKABLE void startChallengeOwnershipTimer(const EntityItemID& entityItemID); private: + void addCertifiedEntityOnServer(EntityItemPointer entity); + void removeCertifiedEntityOnServer(EntityItemPointer entity); void sendChallengeOwnershipPacket(const QString& certID, const QString& ownerKey, const EntityItemID& entityItemID, const SharedNodePointer& senderNode); - void sendChallengeOwnershipRequestPacket(const QByteArray& certID, const QByteArray& text, const QByteArray& nodeToChallenge, const SharedNodePointer& senderNode); + void sendChallengeOwnershipRequestPacket(const QByteArray& id, const QByteArray& text, const QByteArray& nodeToChallenge, const SharedNodePointer& senderNode); void validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode); std::shared_ptr _myAvatar{ nullptr }; diff --git a/libraries/entities/src/LineEntityItem.h b/libraries/entities/src/LineEntityItem.h index 86ca6065bb..098183299f 100644 --- a/libraries/entities/src/LineEntityItem.h +++ b/libraries/entities/src/LineEntityItem.h @@ -49,8 +49,6 @@ class LineEntityItem : public EntityItem { QVector getLinePoints() const; - virtual ShapeType getShapeType() const override { return SHAPE_TYPE_NONE; } - // never have a ray intersection pick a LineEntityItem. virtual bool supportsDetailedIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index 08468617ba..d532fefe7e 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -175,7 +175,7 @@ protected: QString _textures; - ShapeType _shapeType = SHAPE_TYPE_NONE; + ShapeType _shapeType { SHAPE_TYPE_NONE }; private: uint64_t _lastAnimated{ 0 }; diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp index b916ecc3de..12119f1466 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.cpp +++ b/libraries/entities/src/ParticleEffectEntityItem.cpp @@ -410,6 +410,7 @@ EntityItemProperties ParticleEffectEntityItem::getProperties(const EntityPropert EntityItemProperties properties = EntityItem::getProperties(desiredProperties, allowEmptyDesiredProperties); // get the properties from our base class COPY_ENTITY_PROPERTY_TO_PROPERTIES(shapeType, getShapeType); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(compoundShapeURL, getCompoundShapeURL); COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getColor); COPY_ENTITY_PROPERTY_TO_PROPERTIES(alpha, getAlpha); withReadLock([&] { @@ -464,6 +465,7 @@ bool ParticleEffectEntityItem::setProperties(const EntityItemProperties& propert bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL); SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha); withWriteLock([&] { @@ -540,6 +542,7 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch const unsigned char* dataAt = data; READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, setShapeType); + READ_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL); READ_ENTITY_PROPERTY(PROP_COLOR, u8vec3Color, setColor); READ_ENTITY_PROPERTY(PROP_ALPHA, float, setAlpha); withWriteLock([&] { @@ -598,6 +601,7 @@ EntityPropertyFlags ParticleEffectEntityItem::getEntityProperties(EncodeBitstrea EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += PROP_SHAPE_TYPE; + requestedProperties += PROP_COMPOUND_SHAPE_URL; requestedProperties += PROP_COLOR; requestedProperties += PROP_ALPHA; requestedProperties += _pulseProperties.getEntityProperties(params); @@ -656,6 +660,7 @@ void ParticleEffectEntityItem::appendSubclassData(OctreePacketData* packetData, bool successPropertyFits = true; APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, (uint32_t)getShapeType()); + APPEND_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, getCompoundShapeURL()); APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor()); APPEND_ENTITY_PROPERTY(PROP_ALPHA, getAlpha()); withReadLock([&] { @@ -718,11 +723,42 @@ void ParticleEffectEntityItem::debugDump() const { } void ParticleEffectEntityItem::setShapeType(ShapeType type) { + switch (type) { + case SHAPE_TYPE_NONE: + case SHAPE_TYPE_CAPSULE_X: + case SHAPE_TYPE_CAPSULE_Y: + case SHAPE_TYPE_CAPSULE_Z: + case SHAPE_TYPE_HULL: + case SHAPE_TYPE_SIMPLE_HULL: + case SHAPE_TYPE_SIMPLE_COMPOUND: + case SHAPE_TYPE_STATIC_MESH: + // these types are unsupported for ParticleEffectEntity + type = particle::DEFAULT_SHAPE_TYPE; + break; + default: + break; + } + withWriteLock([&] { - if (type != _shapeType) { - _shapeType = type; - _flags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS; - } + _shapeType = type; + }); +} + +ShapeType ParticleEffectEntityItem::getShapeType() const { + return resultWithReadLock([&] { + return _shapeType; + }); +} + +void ParticleEffectEntityItem::setCompoundShapeURL(const QString& compoundShapeURL) { + withWriteLock([&] { + _compoundShapeURL = compoundShapeURL; + }); +} + +QString ParticleEffectEntityItem::getCompoundShapeURL() const { + return resultWithReadLock([&] { + return _compoundShapeURL; }); } diff --git a/libraries/entities/src/ParticleEffectEntityItem.h b/libraries/entities/src/ParticleEffectEntityItem.h index 0755d7868b..52f229201e 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.h +++ b/libraries/entities/src/ParticleEffectEntityItem.h @@ -79,6 +79,7 @@ namespace particle { static const QString DEFAULT_TEXTURES = ""; static const bool DEFAULT_EMITTER_SHOULD_TRAIL = false; static const bool DEFAULT_ROTATE_WITH_ENTITY = false; + static const ShapeType DEFAULT_SHAPE_TYPE = ShapeType::SHAPE_TYPE_ELLIPSOID; template struct Range { @@ -255,7 +256,10 @@ public: float getAlphaSpread() const { return _particleProperties.alpha.gradient.spread; } void setShapeType(ShapeType type) override; - virtual ShapeType getShapeType() const override { return _shapeType; } + virtual ShapeType getShapeType() const override; + + QString getCompoundShapeURL() const; + virtual void setCompoundShapeURL(const QString& url); virtual void debugDump() const override; @@ -349,7 +353,8 @@ protected: PulsePropertyGroup _pulseProperties; bool _isEmitting { true }; - ShapeType _shapeType { SHAPE_TYPE_NONE }; + ShapeType _shapeType{ particle::DEFAULT_SHAPE_TYPE }; + QString _compoundShapeURL { "" }; }; #endif // hifi_ParticleEffectEntityItem_h diff --git a/libraries/entities/src/ShapeEntityItem.h b/libraries/entities/src/ShapeEntityItem.h index 363a7f39d1..fc590e06a4 100644 --- a/libraries/entities/src/ShapeEntityItem.h +++ b/libraries/entities/src/ShapeEntityItem.h @@ -112,7 +112,7 @@ protected: //! This is SHAPE_TYPE_ELLIPSOID rather than SHAPE_TYPE_NONE to maintain //! prior functionality where new or unsupported shapes are treated as //! ellipsoids. - ShapeType _collisionShapeType{ ShapeType::SHAPE_TYPE_ELLIPSOID }; + ShapeType _collisionShapeType { ShapeType::SHAPE_TYPE_ELLIPSOID }; }; #endif // hifi_ShapeEntityItem_h diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 98b18869fc..0771d9ad54 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -13,7 +13,6 @@ #include #include -#include #include @@ -353,7 +352,7 @@ bool ZoneEntityItem::contains(const glm::vec3& point) const { Extents meshExtents = hfmModel.getMeshExtents(); glm::vec3 meshExtentsDiagonal = meshExtents.maximum - meshExtents.minimum; - glm::vec3 offset = -meshExtents.minimum- (meshExtentsDiagonal * getRegistrationPoint()); + glm::vec3 offset = -meshExtents.minimum - (meshExtentsDiagonal * getRegistrationPoint()); glm::vec3 scale(getScaledDimensions() / meshExtentsDiagonal); glm::mat4 hfmToEntityMatrix = glm::scale(scale) * glm::translate(offset); @@ -463,9 +462,6 @@ void ZoneEntityItem::fetchCollisionGeometryResource() { if (hullURL.isEmpty()) { _shapeResource.reset(); } else { - QUrlQuery queryArgs(hullURL); - queryArgs.addQueryItem("collision-hull", ""); - hullURL.setQuery(queryArgs); _shapeResource = DependencyManager::get()->getCollisionGeometryResource(hullURL); } } diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index df6ce50fd6..69e3227135 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -133,7 +133,7 @@ protected: KeyLightPropertyGroup _keyLightProperties; AmbientLightPropertyGroup _ambientLightProperties; - ShapeType _shapeType = DEFAULT_SHAPE_TYPE; + ShapeType _shapeType { DEFAULT_SHAPE_TYPE }; QString _compoundShapeURL; // The following 3 values are the defaults for zone creation diff --git a/libraries/fbx/src/GLTFSerializer.cpp b/libraries/fbx/src/GLTFSerializer.cpp index b8d4e53b65..1699722215 100755 --- a/libraries/fbx/src/GLTFSerializer.cpp +++ b/libraries/fbx/src/GLTFSerializer.cpp @@ -711,19 +711,19 @@ glm::mat4 GLTFSerializer::getModelTransform(const GLTFNode& node) { node.matrix[12], node.matrix[13], node.matrix[14], node.matrix[15]); } else { - if (node.defined["rotation"] && node.rotation.size() == 4) { - //quat(x,y,z,w) to quat(w,x,y,z) - glm::quat rotquat = glm::quat(node.rotation[3], node.rotation[0], node.rotation[1], node.rotation[2]); - tmat = glm::mat4_cast(rotquat) * tmat; - } - if (node.defined["scale"] && node.scale.size() == 3) { glm::vec3 scale = glm::vec3(node.scale[0], node.scale[1], node.scale[2]); glm::mat4 s = glm::mat4(1.0); s = glm::scale(s, scale); tmat = s * tmat; } - + + if (node.defined["rotation"] && node.rotation.size() == 4) { + //quat(x,y,z,w) to quat(w,x,y,z) + glm::quat rotquat = glm::quat(node.rotation[3], node.rotation[0], node.rotation[1], node.rotation[2]); + tmat = glm::mat4_cast(rotquat) * tmat; + } + if (node.defined["translation"] && node.translation.size() == 3) { glm::vec3 trans = glm::vec3(node.translation[0], node.translation[1], node.translation[2]); glm::mat4 t = glm::mat4(1.0); @@ -734,15 +734,54 @@ glm::mat4 GLTFSerializer::getModelTransform(const GLTFNode& node) { return tmat; } +void GLTFSerializer::getSkinInverseBindMatrices(std::vector>& inverseBindMatrixValues) { + for (auto &skin : _file.skins) { + GLTFAccessor& indicesAccessor = _file.accessors[skin.inverseBindMatrices]; + GLTFBufferView& indicesBufferview = _file.bufferviews[indicesAccessor.bufferView]; + GLTFBuffer& indicesBuffer = _file.buffers[indicesBufferview.buffer]; + int accBoffset = indicesAccessor.defined["byteOffset"] ? indicesAccessor.byteOffset : 0; + QVector matrices; + addArrayOfType(indicesBuffer.blob, + indicesBufferview.byteOffset + accBoffset, + indicesAccessor.count, + matrices, + indicesAccessor.type, + indicesAccessor.componentType); + inverseBindMatrixValues.push_back(matrices.toStdVector()); + } +} + +void GLTFSerializer::getNodeQueueByDepthFirstChildren(std::vector& children, int stride, std::vector& result) { + int startingIndex = 0; + int finalIndex = (int)children.size(); + if (stride == -1) { + startingIndex = (int)children.size() - 1; + finalIndex = -1; + } + for (int index = startingIndex; index != finalIndex; index += stride) { + int c = children[index]; + result.push_back(c); + std::vector nested = _file.nodes[c].children.toStdVector(); + if (nested.size() != 0) { + std::sort(nested.begin(), nested.end()); + for (int r : nested) { + if (result.end() == std::find(result.begin(), result.end(), r)) { + getNodeQueueByDepthFirstChildren(nested, stride, result); + } + } + } + } +} + + bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::URL& url) { + int numNodes = _file.nodes.size(); //Build dependencies - QVector> nodeDependencies(_file.nodes.size()); + QVector> nodeDependencies(numNodes); int nodecount = 0; - bool hasChildren = false; foreach(auto &node, _file.nodes) { //nodes_transforms.push_back(getModelTransform(node)); - hasChildren |= !node.children.isEmpty(); foreach(int child, node.children) nodeDependencies[child].push_back(nodecount); nodecount++; } @@ -764,26 +803,98 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::URL& url) { nodecount++; } - - HFMJoint joint; - joint.isSkeletonJoint = true; - joint.bindTransformFoundInCluster = false; - joint.distanceToParent = 0; - joint.parentIndex = -1; - hfmModel.joints.resize(_file.nodes.size()); - hfmModel.jointIndices["x"] = _file.nodes.size(); - int jointInd = 0; - for (auto& node : _file.nodes) { - int size = node.transforms.size(); - if (hasChildren) { size--; } - joint.preTransform = glm::mat4(1); - for (int i = 0; i < size; i++) { - joint.preTransform = node.transforms[i] * joint.preTransform; - } - joint.name = node.name; - hfmModel.joints[jointInd] = joint; - jointInd++; + + + // initialize order in which nodes will be parsed + std::vector nodeQueue; + nodeQueue.reserve(numNodes); + int rootNode = 0; + int finalNode = numNodes; + if (!_file.scenes[_file.scene].nodes.contains(0)) { + rootNode = numNodes - 1; + finalNode = -1; } + bool rootAtStartOfList = rootNode < finalNode; + int nodeListStride = 1; + if (!rootAtStartOfList) { nodeListStride = -1; } + + QVector initialSceneNodes = _file.scenes[_file.scene].nodes; + std::sort(initialSceneNodes.begin(), initialSceneNodes.end()); + int sceneRootNode = 0; + int sceneFinalNode = initialSceneNodes.size(); + if (!rootAtStartOfList) { + sceneRootNode = initialSceneNodes.size() - 1; + sceneFinalNode = -1; + } + for (int index = sceneRootNode; index != sceneFinalNode; index += nodeListStride) { + int i = initialSceneNodes[index]; + nodeQueue.push_back(i); + std::vector children = _file.nodes[i].children.toStdVector(); + std::sort(children.begin(), children.end()); + getNodeQueueByDepthFirstChildren(children, nodeListStride, nodeQueue); + } + + // Build joints + HFMJoint joint; + joint.distanceToParent = 0; + hfmModel.jointIndices["x"] = numNodes; + hfmModel.hasSkeletonJoints = false; + + for (int nodeIndex : nodeQueue) { + auto& node = _file.nodes[nodeIndex]; + + joint.parentIndex = -1; + if (!_file.scenes[_file.scene].nodes.contains(nodeIndex)) { + joint.parentIndex = std::distance(nodeQueue.begin(), std::find(nodeQueue.begin(), nodeQueue.end(), nodeDependencies[nodeIndex][0])); + } + joint.transform = node.transforms.first(); + joint.translation = extractTranslation(joint.transform); + joint.rotation = glmExtractRotation(joint.transform); + glm::vec3 scale = extractScale(joint.transform); + joint.postTransform = glm::scale(glm::mat4(), scale); + + joint.name = node.name; + joint.isSkeletonJoint = false; + hfmModel.joints.push_back(joint); + } + + + // Build skeleton + std::vector jointInverseBindTransforms; + jointInverseBindTransforms.resize(numNodes); + if (!_file.skins.isEmpty()) { + std::vector> inverseBindValues; + getSkinInverseBindMatrices(inverseBindValues); + + int jointIndex = finalNode; + while (jointIndex != rootNode) { + rootAtStartOfList ? jointIndex-- : jointIndex++; + int jOffset = nodeQueue[jointIndex]; + auto joint = hfmModel.joints[jointIndex]; + + hfmModel.hasSkeletonJoints = true; + for (int s = 0; s < _file.skins.size(); s++) { + auto skin = _file.skins[s]; + joint.isSkeletonJoint = skin.joints.contains(jOffset); + + if (joint.isSkeletonJoint) { + std::vector value = inverseBindValues[s]; + int matrixCount = 16 * skin.joints.indexOf(jOffset); + jointInverseBindTransforms[jointIndex] = + glm::mat4(value[matrixCount], value[matrixCount + 1], value[matrixCount + 2], value[matrixCount + 3], + value[matrixCount + 4], value[matrixCount + 5], value[matrixCount + 6], value[matrixCount + 7], + value[matrixCount + 8], value[matrixCount + 9], value[matrixCount + 10], value[matrixCount + 11], + value[matrixCount + 12], value[matrixCount + 13], value[matrixCount + 14], value[matrixCount + 15]); + } else { + jointInverseBindTransforms[jointIndex] = glm::mat4(); + } + glm::vec3 bindTranslation = extractTranslation(hfmModel.offset * glm::inverse(jointInverseBindTransforms[jointIndex])); + hfmModel.bindExtents.addPoint(bindTranslation); + } + hfmModel.joints[jointIndex] = joint; + } + } + //Build materials QVector materialIDs; @@ -803,23 +914,39 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::URL& url) { } - - nodecount = 0; // Build meshes - foreach(auto &node, _file.nodes) { + nodecount = 0; + for (int nodeIndex = rootNode; nodeIndex != finalNode; nodeIndex += nodeListStride) { + auto& node = _file.nodes[nodeIndex]; if (node.defined["mesh"]) { qCDebug(modelformat) << "node_transforms" << node.transforms; foreach(auto &primitive, _file.meshes[node.mesh].primitives) { hfmModel.meshes.append(HFMMesh()); HFMMesh& mesh = hfmModel.meshes[hfmModel.meshes.size() - 1]; - HFMCluster cluster; - cluster.jointIndex = nodecount; - cluster.inverseBindMatrix = glm::mat4(1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1); - mesh.clusters.append(cluster); + if (!hfmModel.hasSkeletonJoints) { + HFMCluster cluster; + cluster.jointIndex = nodecount; + cluster.inverseBindMatrix = glm::mat4(); + cluster.inverseBindTransform = Transform(cluster.inverseBindMatrix); + mesh.clusters.append(cluster); + } else { + for (int j = rootNode; j != finalNode; j += nodeListStride) { + HFMCluster cluster; + cluster.jointIndex = j; + cluster.inverseBindMatrix = jointInverseBindTransforms[j]; + cluster.inverseBindTransform = Transform(cluster.inverseBindMatrix); + mesh.clusters.append(cluster); + } + } + HFMCluster root; + root.jointIndex = rootNode; + if (root.jointIndex == -1) { + root.jointIndex = 0; + } + root.inverseBindMatrix = jointInverseBindTransforms[root.jointIndex]; + root.inverseBindTransform = Transform(root.inverseBindMatrix); + mesh.clusters.append(root); HFMMeshPart part = HFMMeshPart(); @@ -848,6 +975,8 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::URL& url) { } QList keys = primitive.attributes.values.keys(); + QVector clusterJoints; + QVector clusterWeights; foreach(auto &key, keys) { int accessorIdx = primitive.attributes.values[key]; @@ -949,8 +1078,69 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::URL& url) { for (int n = 0; n < texcoords.size(); n = n + 2) { mesh.texCoords1.push_back(glm::vec2(texcoords[n], texcoords[n + 1])); } + } else if (key == "JOINTS_0") { + QVector joints; + success = addArrayOfType(buffer.blob, + bufferview.byteOffset + accBoffset, + accessor.count, + joints, + accessor.type, + accessor.componentType); + if (!success) { + qWarning(modelformat) << "There was a problem reading glTF JOINTS_0 data for model " << _url; + continue; + } + for (int n = 0; n < joints.size(); n++) { + clusterJoints.push_back(joints[n]); + } + } else if (key == "WEIGHTS_0") { + QVector weights; + success = addArrayOfType(buffer.blob, + bufferview.byteOffset + accBoffset, + accessor.count, + weights, + accessor.type, + accessor.componentType); + if (!success) { + qWarning(modelformat) << "There was a problem reading glTF WEIGHTS_0 data for model " << _url; + continue; + } + for (int n = 0; n < weights.size(); n++) { + clusterWeights.push_back(weights[n]); + } + } + } + + // adapted from FBXSerializer.cpp + if (hfmModel.hasSkeletonJoints) { + int numClusterIndices = clusterJoints.size(); + const int WEIGHTS_PER_VERTEX = 4; + const float ALMOST_HALF = 0.499f; + int numVertices = mesh.vertices.size(); + mesh.clusterIndices.fill(mesh.clusters.size() - 1, numClusterIndices); + mesh.clusterWeights.fill(0, numClusterIndices); + + for (int c = 0; c < clusterJoints.size(); c++) { + mesh.clusterIndices[c] = _file.skins[node.skin].joints[clusterJoints[c]]; } + // normalize and compress to 16-bits + for (int i = 0; i < numVertices; ++i) { + int j = i * WEIGHTS_PER_VERTEX; + + float totalWeight = 0.0f; + for (int k = j; k < j + WEIGHTS_PER_VERTEX; ++k) { + totalWeight += clusterWeights[k]; + } + if (totalWeight > 0.0f) { + float weightScalingFactor = (float)(UINT16_MAX) / totalWeight; + for (int k = j; k < j + WEIGHTS_PER_VERTEX; ++k) { + mesh.clusterWeights[k] = (uint16_t)(weightScalingFactor * clusterWeights[k] + ALMOST_HALF); + } + } else { + mesh.clusterWeights[j] = (uint16_t)((float)(UINT16_MAX) + ALMOST_HALF); + } + } } if (primitive.defined["material"]) { @@ -959,8 +1149,8 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::URL& url) { mesh.parts.push_back(part); // populate the texture coordinates if they don't exist - if (mesh.texCoords.size() == 0) { - for (int i = 0; i < part.triangleIndices.size(); i++) mesh.texCoords.push_back(glm::vec2(0.0, 1.0)); + if (mesh.texCoords.size() == 0 && !hfmModel.hasSkeletonJoints) { + for (int i = 0; i < part.triangleIndices.size(); i++) { mesh.texCoords.push_back(glm::vec2(0.0, 1.0)); } } mesh.meshExtents.reset(); foreach(const glm::vec3& vertex, mesh.vertices) { diff --git a/libraries/fbx/src/GLTFSerializer.h b/libraries/fbx/src/GLTFSerializer.h index 05dc526f79..d9c477bd99 100755 --- a/libraries/fbx/src/GLTFSerializer.h +++ b/libraries/fbx/src/GLTFSerializer.h @@ -712,6 +712,8 @@ private: hifi::ByteArray _glbBinary; glm::mat4 getModelTransform(const GLTFNode& node); + void getSkinInverseBindMatrices(std::vector>& inverseBindMatrixValues); + void getNodeQueueByDepthFirstChildren(std::vector& children, int stride, std::vector& result); bool buildGeometry(HFMModel& hfmModel, const hifi::URL& url); bool parseGLTF(const hifi::ByteArray& data); diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp index fdd06ffa64..caee4ceb2a 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp @@ -15,7 +15,7 @@ #include "GraphicsScriptingUtil.h" #include "ScriptableMesh.h" #include "graphics/Material.h" -#include "image/Image.h" +#include "image/TextureProcessing.h" // #define SCRIPTABLE_MESH_DEBUG 1 diff --git a/libraries/graphics/src/graphics/Geometry.cpp b/libraries/graphics/src/graphics/Geometry.cpp index a983ba07b4..cee2b0e3c9 100755 --- a/libraries/graphics/src/graphics/Geometry.cpp +++ b/libraries/graphics/src/graphics/Geometry.cpp @@ -362,7 +362,6 @@ MeshPointer Mesh::createIndexedTriangles_P3F(uint32_t numVertices, uint32_t numI mesh->setIndexBuffer(gpu::BufferView(new gpu::Buffer(numIndices * sizeof(uint32_t), (gpu::Byte*) indices), gpu::Element::INDEX_INT32)); } - std::vector parts; parts.push_back(graphics::Mesh::Part(0, numIndices, 0, graphics::Mesh::TRIANGLES)); mesh->setPartBuffer(gpu::BufferView(new gpu::Buffer(parts.size() * sizeof(graphics::Mesh::Part), (gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL)); diff --git a/libraries/image/CMakeLists.txt b/libraries/image/CMakeLists.txt index 4db39f2152..0c733ae789 100644 --- a/libraries/image/CMakeLists.txt +++ b/libraries/image/CMakeLists.txt @@ -3,6 +3,7 @@ setup_hifi_library() link_hifi_libraries(shared gpu) target_nvtt() target_etc2comp() +target_openexr() if (UNIX AND NOT APPLE) set(THREADS_PREFER_PTHREAD_FLAG ON) diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp index 2488b15fcd..df5ed15867 100644 --- a/libraries/image/src/image/Image.cpp +++ b/libraries/image/src/image/Image.cpp @@ -1,1467 +1,78 @@ -// -// Image.cpp -// image/src/image -// -// Created by Clement Brisset on 4/5/2017. -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - #include "Image.h" - -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "TGAReader.h" - #include "ImageLogging.h" +#include "TextureProcessing.h" -using namespace gpu; - -#define CPU_MIPMAPS 1 #include -#undef _CRT_SECURE_NO_WARNINGS -#include -#include +using namespace image; -static const glm::uvec2 SPARSE_PAGE_SIZE(128); -static const glm::uvec2 MAX_TEXTURE_SIZE_GLES(2048); -static const glm::uvec2 MAX_TEXTURE_SIZE_GL(4096); -bool DEV_DECIMATE_TEXTURES = false; -std::atomic DECIMATED_TEXTURE_COUNT{ 0 }; -std::atomic RECTIFIED_TEXTURE_COUNT{ 0 }; +Image Image::getScaled(glm::uvec2 dstSize, AspectRatioMode ratioMode, TransformationMode transformMode) const { + if ((Image::Format)_data.format() == Image::Format_PACKED_FLOAT) { + // Start by converting to full float + glm::vec4* floatPixels = new glm::vec4[getWidth()*getHeight()]; + auto unpackFunc = getHDRUnpackingFunction(); + auto floatDataIt = floatPixels; + for (glm::uint32 lineNb = 0; lineNb < getHeight(); lineNb++) { + const glm::uint32* srcPixelIt = reinterpret_cast(getScanLine((int)lineNb)); + const glm::uint32* srcPixelEnd = srcPixelIt + getWidth(); -// we use a ref here to work around static order initialization -// possibly causing the element not to be constructed yet -static const auto& HDR_FORMAT = gpu::Element::COLOR_R11G11B10; - -uint rectifyDimension(const uint& dimension) { - if (dimension == 0) { - return 0; - } - if (dimension < SPARSE_PAGE_SIZE.x) { - uint newSize = SPARSE_PAGE_SIZE.x; - while (dimension <= newSize / 2) { - newSize /= 2; + while (srcPixelIt < srcPixelEnd) { + *floatDataIt = glm::vec4(unpackFunc(*srcPixelIt), 1.0f); + ++srcPixelIt; + ++floatDataIt; + } } - return newSize; + + // Perform filtered resize with NVTT + static_assert(sizeof(glm::vec4) == 4 * sizeof(float), "Assuming glm::vec4 holds 4 floats"); + nvtt::Surface surface; + surface.setImage(nvtt::InputFormat_RGBA_32F, getWidth(), getHeight(), 1, floatPixels); + delete[] floatPixels; + + nvtt::ResizeFilter filter = nvtt::ResizeFilter_Kaiser; + if (transformMode == Qt::TransformationMode::FastTransformation) { + filter = nvtt::ResizeFilter_Box; + } + surface.resize(dstSize.x, dstSize.y, 1, filter); + + // And convert back to original format + QImage resizedImage((int)dstSize.x, (int)dstSize.y, (QImage::Format)Image::Format_PACKED_FLOAT); + + auto packFunc = getHDRPackingFunction(); + auto srcRedIt = reinterpret_cast(surface.channel(0)); + auto srcGreenIt = reinterpret_cast(surface.channel(1)); + auto srcBlueIt = reinterpret_cast(surface.channel(2)); + for (glm::uint32 lineNb = 0; lineNb < dstSize.y; lineNb++) { + glm::uint32* dstPixelIt = reinterpret_cast(resizedImage.scanLine((int)lineNb)); + glm::uint32* dstPixelEnd = dstPixelIt + dstSize.x; + + while (dstPixelIt < dstPixelEnd) { + *dstPixelIt = packFunc(glm::vec3(*srcRedIt, *srcGreenIt, *srcBlueIt)); + ++srcRedIt; + ++srcGreenIt; + ++srcBlueIt; + ++dstPixelIt; + } + } + return resizedImage; } else { - uint pages = (dimension / SPARSE_PAGE_SIZE.x) + (dimension % SPARSE_PAGE_SIZE.x == 0 ? 0 : 1); - return pages * SPARSE_PAGE_SIZE.x; + return _data.scaled(fromGlm(dstSize), ratioMode, transformMode); } } -glm::uvec2 rectifySize(const glm::uvec2& size) { - return { rectifyDimension(size.x), rectifyDimension(size.y) }; +Image Image::getConvertedToFormat(Format newFormat) const { + assert(getFormat() != Format_PACKED_FLOAT); + return _data.convertToFormat((QImage::Format)newFormat); } - -namespace image { - -const QStringList getSupportedFormats() { - auto formats = QImageReader::supportedImageFormats(); - QStringList stringFormats; - std::transform(formats.begin(), formats.end(), std::back_inserter(stringFormats), - [](QByteArray& format) -> QString { return format; }); - return stringFormats; +void Image::invertPixels() { + _data.invertPixels(QImage::InvertRgba); } - -// On GLES, we don't use HDR skyboxes -QImage::Format hdrFormatForTarget(BackendTarget target) { - if (target == BackendTarget::GLES32) { - return QImage::Format_RGB32; - } - return QImage::Format_RGB30; +Image Image::getSubImage(QRect rect) const { + return _data.copy(rect); } -TextureUsage::TextureLoader TextureUsage::getTextureLoaderForType(Type type, const QVariantMap& options) { - switch (type) { - case ALBEDO_TEXTURE: - return image::TextureUsage::createAlbedoTextureFromImage; - case EMISSIVE_TEXTURE: - return image::TextureUsage::createEmissiveTextureFromImage; - case LIGHTMAP_TEXTURE: - return image::TextureUsage::createLightmapTextureFromImage; - case CUBE_TEXTURE: - if (options.value("generateIrradiance", true).toBool()) { - return image::TextureUsage::createCubeTextureFromImage; - } else { - return image::TextureUsage::createCubeTextureFromImageWithoutIrradiance; - } - case BUMP_TEXTURE: - return image::TextureUsage::createNormalTextureFromBumpImage; - case NORMAL_TEXTURE: - return image::TextureUsage::createNormalTextureFromNormalImage; - case ROUGHNESS_TEXTURE: - return image::TextureUsage::createRoughnessTextureFromImage; - case GLOSS_TEXTURE: - return image::TextureUsage::createRoughnessTextureFromGlossImage; - case SPECULAR_TEXTURE: - return image::TextureUsage::createMetallicTextureFromImage; - case STRICT_TEXTURE: - return image::TextureUsage::createStrict2DTextureFromImage; - - case DEFAULT_TEXTURE: - default: - return image::TextureUsage::create2DTextureFromImage; - } +Image Image::getMirrored(bool horizontal, bool vertical) const { + return _data.mirrored(horizontal, vertical); } - -gpu::TexturePointer TextureUsage::createStrict2DTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, BackendTarget target, const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, true, abortProcessing); -} - -gpu::TexturePointer TextureUsage::create2DTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, BackendTarget target, const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); -} - -gpu::TexturePointer TextureUsage::createAlbedoTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, BackendTarget target, const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); -} - -gpu::TexturePointer TextureUsage::createEmissiveTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, BackendTarget target, const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); -} - -gpu::TexturePointer TextureUsage::createLightmapTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, BackendTarget target, const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); -} - -gpu::TexturePointer TextureUsage::createNormalTextureFromNormalImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, BackendTarget target, const std::atomic& abortProcessing) { - return process2DTextureNormalMapFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); -} - -gpu::TexturePointer TextureUsage::createNormalTextureFromBumpImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, BackendTarget target, const std::atomic& abortProcessing) { - return process2DTextureNormalMapFromImage(std::move(srcImage), srcImageName, compress, target, true, abortProcessing); -} - -gpu::TexturePointer TextureUsage::createRoughnessTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, BackendTarget target, const std::atomic& abortProcessing) { - return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); -} - -gpu::TexturePointer TextureUsage::createRoughnessTextureFromGlossImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, BackendTarget target, const std::atomic& abortProcessing) { - return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, compress, target, true, abortProcessing); -} - -gpu::TexturePointer TextureUsage::createMetallicTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, BackendTarget target, const std::atomic& abortProcessing) { - return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); -} - -gpu::TexturePointer TextureUsage::createCubeTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, BackendTarget target, const std::atomic& abortProcessing) { - return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, true, abortProcessing); -} - -gpu::TexturePointer TextureUsage::createCubeTextureFromImageWithoutIrradiance(QImage&& srcImage, const std::string& srcImageName, - bool compress, BackendTarget target, const std::atomic& abortProcessing) { - return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); -} - -static float denormalize(float value, const float minValue) { - return value < minValue ? 0.0f : value; -} - -uint32 packR11G11B10F(const glm::vec3& color) { - // Denormalize else unpacking gives high and incorrect values - // See https://www.khronos.org/opengl/wiki/Small_Float_Formats for this min value - static const auto minValue = 6.10e-5f; - static const auto maxValue = 6.50e4f; - glm::vec3 ucolor; - ucolor.r = denormalize(color.r, minValue); - ucolor.g = denormalize(color.g, minValue); - ucolor.b = denormalize(color.b, minValue); - ucolor.r = std::min(ucolor.r, maxValue); - ucolor.g = std::min(ucolor.g, maxValue); - ucolor.b = std::min(ucolor.b, maxValue); - return glm::packF2x11_1x10(ucolor); -} - -QImage processRawImageData(QIODevice& content, const std::string& filename) { - // Help the QImage loader by extracting the image file format from the url filename ext. - // Some tga are not created properly without it. - auto filenameExtension = filename.substr(filename.find_last_of('.') + 1); - if (!content.isReadable()) { - content.open(QIODevice::ReadOnly); - } else { - content.reset(); - } - - if (filenameExtension == "tga") { - QImage image = image::readTGA(content); - if (!image.isNull()) { - return image; - } - content.reset(); - } - - QImageReader imageReader(&content, filenameExtension.c_str()); - - if (imageReader.canRead()) { - return imageReader.read(); - } else { - // Extension could be incorrect, try to detect the format from the content - QImageReader newImageReader; - newImageReader.setDecideFormatFromContent(true); - content.reset(); - newImageReader.setDevice(&content); - - if (newImageReader.canRead()) { - return newImageReader.read(); - } - } - - return QImage(); -} - -void mapToRedChannel(QImage& image, ColorChannel sourceChannel) { - // Change format of image so we know exactly how to process it - if (image.format() != QImage::Format_ARGB32) { - image = image.convertToFormat(QImage::Format_ARGB32); - } - - for (int i = 0; i < image.height(); i++) { - QRgb* pixel = reinterpret_cast(image.scanLine(i)); - // Past end pointer - QRgb* lineEnd = pixel + image.width(); - - // Transfer channel data from source to target - for (; pixel < lineEnd; pixel++) { - int colorValue; - switch (sourceChannel) { - case ColorChannel::RED: - colorValue = qRed(*pixel); - break; - case ColorChannel::GREEN: - colorValue = qGreen(*pixel); - break; - case ColorChannel::BLUE: - colorValue = qBlue(*pixel); - break; - case ColorChannel::ALPHA: - colorValue = qAlpha(*pixel); - break; - default: - colorValue = qRed(*pixel); - break; - } - - // Dump the color in the red channel, ignore the rest - *pixel = qRgba(colorValue, 0, 0, 255); - } - } -} - -gpu::TexturePointer processImage(std::shared_ptr content, const std::string& filename, ColorChannel sourceChannel, - int maxNumPixels, TextureUsage::Type textureType, - bool compress, BackendTarget target, const std::atomic& abortProcessing) { - - QImage image = processRawImageData(*content.get(), filename); - // Texture content can take up a lot of memory. Here we release our ownership of that content - // in case it can be released. - content.reset(); - - int imageWidth = image.width(); - int imageHeight = image.height(); - - // Validate that the image loaded - if (imageWidth == 0 || imageHeight == 0 || image.format() == QImage::Format_Invalid) { - QString reason(image.format() == QImage::Format_Invalid ? "(Invalid Format)" : "(Size is invalid)"); - qCWarning(imagelogging) << "Failed to load:" << qPrintable(reason); - return nullptr; - } - - // Validate the image is less than _maxNumPixels, and downscale if necessary - if (imageWidth * imageHeight > maxNumPixels) { - float scaleFactor = sqrtf(maxNumPixels / (float)(imageWidth * imageHeight)); - int originalWidth = imageWidth; - int originalHeight = imageHeight; - imageWidth = (int)(scaleFactor * (float)imageWidth + 0.5f); - imageHeight = (int)(scaleFactor * (float)imageHeight + 0.5f); - image = image.scaled(QSize(imageWidth, imageHeight), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); - qCDebug(imagelogging).nospace() << "Downscaled " << " (" << - QSize(originalWidth, originalHeight) << " to " << - QSize(imageWidth, imageHeight) << ")"; - } - - // Re-map to image with single red channel texture if requested - if (sourceChannel != ColorChannel::NONE) { - mapToRedChannel(image, sourceChannel); - } - - auto loader = TextureUsage::getTextureLoaderForType(textureType); - auto texture = loader(std::move(image), filename, compress, target, abortProcessing); - - return texture; -} - -QImage processSourceImage(QImage&& srcImage, bool cubemap, BackendTarget target) { - PROFILE_RANGE(resource_parse, "processSourceImage"); - - // Take a local copy to force move construction - // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter - QImage localCopy = std::move(srcImage); - - const glm::uvec2 srcImageSize = toGlm(localCopy.size()); - glm::uvec2 targetSize = srcImageSize; - - const auto maxTextureSize = target == BackendTarget::GLES32 ? MAX_TEXTURE_SIZE_GLES : MAX_TEXTURE_SIZE_GL; - while (glm::any(glm::greaterThan(targetSize, maxTextureSize))) { - targetSize /= 2; - } - if (targetSize != srcImageSize) { - ++DECIMATED_TEXTURE_COUNT; - } - - if (!cubemap) { - auto rectifiedSize = rectifySize(targetSize); - if (rectifiedSize != targetSize) { - ++RECTIFIED_TEXTURE_COUNT; - targetSize = rectifiedSize; - } - } - - if (DEV_DECIMATE_TEXTURES && glm::all(glm::greaterThanEqual(targetSize / SPARSE_PAGE_SIZE, glm::uvec2(2)))) { - targetSize /= 2; - } - - if (targetSize != srcImageSize) { - PROFILE_RANGE(resource_parse, "processSourceImage Rectify"); - qCDebug(imagelogging) << "Resizing texture from " << srcImageSize.x << "x" << srcImageSize.y << " to " << targetSize.x << "x" << targetSize.y; - return localCopy.scaled(fromGlm(targetSize), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); - } - - return localCopy; -} - -#if defined(NVTT_API) -struct OutputHandler : public nvtt::OutputHandler { - OutputHandler(gpu::Texture* texture, int face) : _texture(texture), _face(face) {} - - virtual void beginImage(int size, int width, int height, int depth, int face, int miplevel) override { - _size = size; - _miplevel = miplevel; - - _data = static_cast(malloc(size)); - _current = _data; - } - - virtual bool writeData(const void* data, int size) override { - assert(_current + size <= _data + _size); - memcpy(_current, data, size); - _current += size; - return true; - } - - virtual void endImage() override { - if (_face >= 0) { - _texture->assignStoredMipFace(_miplevel, _face, _size, static_cast(_data)); - } else { - _texture->assignStoredMip(_miplevel, _size, static_cast(_data)); - } - free(_data); - _data = nullptr; - } - - gpu::Byte* _data{ nullptr }; - gpu::Byte* _current{ nullptr }; - gpu::Texture* _texture{ nullptr }; - int _miplevel = 0; - int _size = 0; - int _face = -1; -}; - -struct PackedFloatOutputHandler : public OutputHandler { - PackedFloatOutputHandler(gpu::Texture* texture, int face, gpu::Element format) : OutputHandler(texture, face) { - if (format == gpu::Element::COLOR_RGB9E5) { - _packFunc = glm::packF3x9_E1x5; - } else if (format == gpu::Element::COLOR_R11G11B10) { - _packFunc = packR11G11B10F; - } else { - qCWarning(imagelogging) << "Unknown handler format"; - Q_UNREACHABLE(); - } - } - - virtual void beginImage(int size, int width, int height, int depth, int face, int miplevel) override { - // Divide by 3 because we will compress from 3*floats to 1 uint32 - OutputHandler::beginImage(size / 3, width, height, depth, face, miplevel); - } - virtual bool writeData(const void* data, int size) override { - // Expecting to write multiple of floats - if (_packFunc) { - assert((size % sizeof(float)) == 0); - auto floatCount = size / sizeof(float); - const float* floatBegin = (const float*)data; - const float* floatEnd = floatBegin + floatCount; - - while (floatBegin < floatEnd) { - _pixel[_coordIndex] = *floatBegin; - floatBegin++; - _coordIndex++; - if (_coordIndex == 3) { - uint32 packedRGB = _packFunc(_pixel); - _coordIndex = 0; - OutputHandler::writeData(&packedRGB, sizeof(packedRGB)); - } - } - return true; - } - return false; - } - - std::function _packFunc; - glm::vec3 _pixel; - int _coordIndex{ 0 }; -}; - -struct MyErrorHandler : public nvtt::ErrorHandler { - virtual void error(nvtt::Error e) override { - qCWarning(imagelogging) << "Texture compression error:" << nvtt::errorString(e); - } -}; - -class SequentialTaskDispatcher : public nvtt::TaskDispatcher { -public: - SequentialTaskDispatcher(const std::atomic& abortProcessing) : _abortProcessing(abortProcessing) {}; - - const std::atomic& _abortProcessing; - - virtual void dispatch(nvtt::Task* task, void* context, int count) override { - for (int i = 0; i < count; i++) { - if (!_abortProcessing.load()) { - task(context, i); - } else { - break; - } - } - } -}; - -void generateHDRMips(gpu::Texture* texture, QImage&& image, BackendTarget target, const std::atomic& abortProcessing, int face) { - // Take a local copy to force move construction - // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter - QImage localCopy = std::move(image); - - assert(localCopy.format() == hdrFormatForTarget(target)); - - const int width = localCopy.width(), height = localCopy.height(); - std::vector data; - std::vector::iterator dataIt; - auto mipFormat = texture->getStoredMipFormat(); - std::function unpackFunc; - - nvtt::InputFormat inputFormat = nvtt::InputFormat_RGBA_32F; - nvtt::WrapMode wrapMode = nvtt::WrapMode_Mirror; - nvtt::AlphaMode alphaMode = nvtt::AlphaMode_None; - - nvtt::CompressionOptions compressionOptions; - compressionOptions.setQuality(nvtt::Quality_Production); - - // TODO: gles: generate ETC mips instead? - if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_HDR_RGB) { - compressionOptions.setFormat(nvtt::Format_BC6); - } else if (mipFormat == gpu::Element::COLOR_RGB9E5) { - compressionOptions.setFormat(nvtt::Format_RGB); - compressionOptions.setPixelType(nvtt::PixelType_Float); - compressionOptions.setPixelFormat(32, 32, 32, 0); - } else if (mipFormat == gpu::Element::COLOR_R11G11B10) { - compressionOptions.setFormat(nvtt::Format_RGB); - compressionOptions.setPixelType(nvtt::PixelType_Float); - compressionOptions.setPixelFormat(32, 32, 32, 0); - } else { - qCWarning(imagelogging) << "Unknown mip format"; - Q_UNREACHABLE(); - return; - } - - if (HDR_FORMAT == gpu::Element::COLOR_RGB9E5) { - unpackFunc = glm::unpackF3x9_E1x5; - } else if (HDR_FORMAT == gpu::Element::COLOR_R11G11B10) { - unpackFunc = glm::unpackF2x11_1x10; - } else { - qCWarning(imagelogging) << "Unknown HDR encoding format in QImage"; - Q_UNREACHABLE(); - return; - } - - data.resize(width * height); - dataIt = data.begin(); - for (auto lineNb = 0; lineNb < height; lineNb++) { - const uint32* srcPixelIt = reinterpret_cast(localCopy.constScanLine(lineNb)); - const uint32* srcPixelEnd = srcPixelIt + width; - - while (srcPixelIt < srcPixelEnd) { - *dataIt = glm::vec4(unpackFunc(*srcPixelIt), 1.0f); - ++srcPixelIt; - ++dataIt; - } - } - assert(dataIt == data.end()); - - // We're done with the localCopy, free up the memory to avoid bloating the heap - localCopy = QImage(); // QImage doesn't have a clear function, so override it with an empty one. - - nvtt::OutputOptions outputOptions; - outputOptions.setOutputHeader(false); - std::unique_ptr outputHandler; - MyErrorHandler errorHandler; - outputOptions.setErrorHandler(&errorHandler); - nvtt::Context context; - int mipLevel = 0; - - if (mipFormat == gpu::Element::COLOR_RGB9E5 || mipFormat == gpu::Element::COLOR_R11G11B10) { - // Don't use NVTT (at least version 2.1) as it outputs wrong RGB9E5 and R11G11B10F values from floats - outputHandler.reset(new PackedFloatOutputHandler(texture, face, mipFormat)); - } else { - outputHandler.reset(new OutputHandler(texture, face)); - } - - outputOptions.setOutputHandler(outputHandler.get()); - - nvtt::Surface surface; - surface.setImage(inputFormat, width, height, 1, &(*data.begin())); - surface.setAlphaMode(alphaMode); - surface.setWrapMode(wrapMode); - - SequentialTaskDispatcher dispatcher(abortProcessing); - nvtt::Compressor compressor; - context.setTaskDispatcher(&dispatcher); - - context.compress(surface, face, mipLevel++, compressionOptions, outputOptions); - while (surface.canMakeNextMipmap() && !abortProcessing.load()) { - surface.buildNextMipmap(nvtt::MipmapFilter_Box); - context.compress(surface, face, mipLevel++, compressionOptions, outputOptions); - } -} - -void generateLDRMips(gpu::Texture* texture, QImage&& image, BackendTarget target, const std::atomic& abortProcessing, int face) { - // Take a local copy to force move construction - // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter - QImage localCopy = std::move(image); - - if (localCopy.format() != QImage::Format_ARGB32 && localCopy.format() != hdrFormatForTarget(target)) { - localCopy = localCopy.convertToFormat(QImage::Format_ARGB32); - } - - const int width = localCopy.width(), height = localCopy.height(); - auto mipFormat = texture->getStoredMipFormat(); - - if (target != BackendTarget::GLES32) { - const void* data = static_cast(localCopy.constBits()); - nvtt::TextureType textureType = nvtt::TextureType_2D; - nvtt::InputFormat inputFormat = nvtt::InputFormat_BGRA_8UB; - nvtt::WrapMode wrapMode = nvtt::WrapMode_Mirror; - nvtt::RoundMode roundMode = nvtt::RoundMode_None; - nvtt::AlphaMode alphaMode = nvtt::AlphaMode_None; - - float inputGamma = 2.2f; - float outputGamma = 2.2f; - - nvtt::InputOptions inputOptions; - inputOptions.setTextureLayout(textureType, width, height); - - inputOptions.setMipmapData(data, width, height); - // setMipmapData copies the memory, so free up the memory afterward to avoid bloating the heap - data = nullptr; - localCopy = QImage(); // QImage doesn't have a clear function, so override it with an empty one. - - inputOptions.setFormat(inputFormat); - inputOptions.setGamma(inputGamma, outputGamma); - inputOptions.setAlphaMode(alphaMode); - inputOptions.setWrapMode(wrapMode); - inputOptions.setRoundMode(roundMode); - - inputOptions.setMipmapGeneration(true); - inputOptions.setMipmapFilter(nvtt::MipmapFilter_Box); - - nvtt::CompressionOptions compressionOptions; - compressionOptions.setQuality(nvtt::Quality_Production); - - if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGB) { - compressionOptions.setFormat(nvtt::Format_BC1); - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGBA_MASK) { - alphaMode = nvtt::AlphaMode_Transparency; - compressionOptions.setFormat(nvtt::Format_BC1a); - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGBA) { - alphaMode = nvtt::AlphaMode_Transparency; - compressionOptions.setFormat(nvtt::Format_BC3); - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_RED) { - compressionOptions.setFormat(nvtt::Format_BC4); - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_XY) { - compressionOptions.setFormat(nvtt::Format_BC5); - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGBA_HIGH) { - alphaMode = nvtt::AlphaMode_Transparency; - compressionOptions.setFormat(nvtt::Format_BC7); - } else if (mipFormat == gpu::Element::COLOR_RGBA_32) { - compressionOptions.setFormat(nvtt::Format_RGBA); - compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); - compressionOptions.setPitchAlignment(4); - compressionOptions.setPixelFormat(32, - 0x000000FF, - 0x0000FF00, - 0x00FF0000, - 0xFF000000); - inputGamma = 1.0f; - outputGamma = 1.0f; - } else if (mipFormat == gpu::Element::COLOR_BGRA_32) { - compressionOptions.setFormat(nvtt::Format_RGBA); - compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); - compressionOptions.setPitchAlignment(4); - compressionOptions.setPixelFormat(32, - 0x00FF0000, - 0x0000FF00, - 0x000000FF, - 0xFF000000); - inputGamma = 1.0f; - outputGamma = 1.0f; - } else if (mipFormat == gpu::Element::COLOR_SRGBA_32) { - compressionOptions.setFormat(nvtt::Format_RGBA); - compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); - compressionOptions.setPitchAlignment(4); - compressionOptions.setPixelFormat(32, - 0x000000FF, - 0x0000FF00, - 0x00FF0000, - 0xFF000000); - } else if (mipFormat == gpu::Element::COLOR_SBGRA_32) { - compressionOptions.setFormat(nvtt::Format_RGBA); - compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); - compressionOptions.setPitchAlignment(4); - compressionOptions.setPixelFormat(32, - 0x00FF0000, - 0x0000FF00, - 0x000000FF, - 0xFF000000); - } else if (mipFormat == gpu::Element::COLOR_R_8) { - compressionOptions.setFormat(nvtt::Format_RGB); - compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); - compressionOptions.setPitchAlignment(4); - compressionOptions.setPixelFormat(8, 0, 0, 0); - } else if (mipFormat == gpu::Element::VEC2NU8_XY) { - inputOptions.setNormalMap(true); - compressionOptions.setFormat(nvtt::Format_RGBA); - compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); - compressionOptions.setPitchAlignment(4); - compressionOptions.setPixelFormat(8, 8, 0, 0); - } else { - qCWarning(imagelogging) << "Unknown mip format"; - Q_UNREACHABLE(); - return; - } - - nvtt::OutputOptions outputOptions; - outputOptions.setOutputHeader(false); - OutputHandler outputHandler(texture, face); - outputOptions.setOutputHandler(&outputHandler); - MyErrorHandler errorHandler; - outputOptions.setErrorHandler(&errorHandler); - - SequentialTaskDispatcher dispatcher(abortProcessing); - nvtt::Compressor compressor; - compressor.setTaskDispatcher(&dispatcher); - compressor.process(inputOptions, compressionOptions, outputOptions); - } else { - int numMips = 1 + (int)log2(std::max(width, height)); - Etc::RawImage *mipMaps = new Etc::RawImage[numMips]; - Etc::Image::Format etcFormat = Etc::Image::Format::DEFAULT; - - if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_RGB) { - etcFormat = Etc::Image::Format::RGB8; - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_SRGB) { - etcFormat = Etc::Image::Format::SRGB8; - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA) { - etcFormat = Etc::Image::Format::RGB8A1; - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA) { - etcFormat = Etc::Image::Format::SRGB8A1; - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_RGBA) { - etcFormat = Etc::Image::Format::RGBA8; - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_SRGBA) { - etcFormat = Etc::Image::Format::SRGBA8; - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_EAC_RED) { - etcFormat = Etc::Image::Format::R11; - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_EAC_RED_SIGNED) { - etcFormat = Etc::Image::Format::SIGNED_R11; - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_EAC_XY) { - etcFormat = Etc::Image::Format::RG11; - } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_EAC_XY_SIGNED) { - etcFormat = Etc::Image::Format::SIGNED_RG11; - } else { - qCWarning(imagelogging) << "Unknown mip format"; - Q_UNREACHABLE(); - return; - } - - const Etc::ErrorMetric errorMetric = Etc::ErrorMetric::RGBA; - const float effort = 1.0f; - const int numEncodeThreads = 4; - int encodingTime; - const float MAX_COLOR = 255.0f; - - std::vector floatData; - floatData.resize(width * height); - for (int y = 0; y < height; y++) { - QRgb *line = (QRgb *)localCopy.scanLine(y); - for (int x = 0; x < width; x++) { - QRgb &pixel = line[x]; - floatData[x + y * width] = vec4(qRed(pixel), qGreen(pixel), qBlue(pixel), qAlpha(pixel)) / MAX_COLOR; - } - } - - // free up the memory afterward to avoid bloating the heap - localCopy = QImage(); // QImage doesn't have a clear function, so override it with an empty one. - - Etc::EncodeMipmaps( - (float *)floatData.data(), width, height, - etcFormat, errorMetric, effort, - numEncodeThreads, numEncodeThreads, - numMips, Etc::FILTER_WRAP_NONE, - mipMaps, &encodingTime - ); - - for (int i = 0; i < numMips; i++) { - if (mipMaps[i].paucEncodingBits.get()) { - if (face >= 0) { - texture->assignStoredMipFace(i, face, mipMaps[i].uiEncodingBitsBytes, static_cast(mipMaps[i].paucEncodingBits.get())); - } else { - texture->assignStoredMip(i, mipMaps[i].uiEncodingBitsBytes, static_cast(mipMaps[i].paucEncodingBits.get())); - } - } - } - - delete[] mipMaps; - } -} - -#endif - -void generateMips(gpu::Texture* texture, QImage&& image, BackendTarget target, const std::atomic& abortProcessing = false, int face = -1) { -#if CPU_MIPMAPS - PROFILE_RANGE(resource_parse, "generateMips"); - - if (target == BackendTarget::GLES32) { - generateLDRMips(texture, std::move(image), target, abortProcessing, face); - } else { - if (image.format() == hdrFormatForTarget(target)) { - generateHDRMips(texture, std::move(image), target, abortProcessing, face); - } else { - generateLDRMips(texture, std::move(image), target, abortProcessing, face); - } - } -#else - texture->setAutoGenerateMips(true); -#endif -} - -void processTextureAlpha(const QImage& srcImage, bool& validAlpha, bool& alphaAsMask) { - PROFILE_RANGE(resource_parse, "processTextureAlpha"); - validAlpha = false; - alphaAsMask = true; - const uint8 OPAQUE_ALPHA = 255; - const uint8 TRANSPARENT_ALPHA = 0; - - // Figure out if we can use a mask for alpha or not - int numOpaques = 0; - int numTranslucents = 0; - const int NUM_PIXELS = srcImage.width() * srcImage.height(); - const int MAX_TRANSLUCENT_PIXELS_FOR_ALPHAMASK = (int)(0.05f * (float)(NUM_PIXELS)); - const QRgb* data = reinterpret_cast(srcImage.constBits()); - for (int i = 0; i < NUM_PIXELS; ++i) { - auto alpha = qAlpha(data[i]); - if (alpha == OPAQUE_ALPHA) { - numOpaques++; - } else if (alpha != TRANSPARENT_ALPHA) { - if (++numTranslucents > MAX_TRANSLUCENT_PIXELS_FOR_ALPHAMASK) { - alphaAsMask = false; - break; - } - } - } - validAlpha = (numOpaques != NUM_PIXELS); -} - -gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress, - BackendTarget target, bool isStrict, const std::atomic& abortProcessing) { - PROFILE_RANGE(resource_parse, "process2DTextureColorFromImage"); - QImage image = processSourceImage(std::move(srcImage), false, target); - - bool validAlpha = image.hasAlphaChannel(); - bool alphaAsMask = false; - - if (image.format() != QImage::Format_ARGB32) { - image = image.convertToFormat(QImage::Format_ARGB32); - } - - if (validAlpha) { - processTextureAlpha(image, validAlpha, alphaAsMask); - } - - gpu::TexturePointer theTexture = nullptr; - - if ((image.width() > 0) && (image.height() > 0)) { - gpu::Element formatMip; - gpu::Element formatGPU; - if (compress) { - if (target == BackendTarget::GLES32) { - // GLES does not support GL_BGRA - formatGPU = gpu::Element::COLOR_COMPRESSED_ETC2_SRGBA; - formatMip = formatGPU; - } else { - if (validAlpha) { - // NOTE: This disables BC1a compression because it was producing odd artifacts on text textures - // for the tutorial. Instead we use BC3 (which is larger) but doesn't produce the same artifacts). - formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_SRGBA; - } else { - formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_SRGB; - } - formatMip = formatGPU; - } - } else { - if (target == BackendTarget::GLES32) { - } else { - formatGPU = gpu::Element::COLOR_SRGBA_32; - formatMip = gpu::Element::COLOR_SBGRA_32; - } - } - - if (isStrict) { - theTexture = gpu::Texture::createStrict(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)); - } else { - theTexture = gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)); - } - theTexture->setSource(srcImageName); - auto usage = gpu::Texture::Usage::Builder().withColor(); - if (validAlpha) { - usage.withAlpha(); - if (alphaAsMask) { - usage.withAlphaMask(); - } - } - theTexture->setUsage(usage.build()); - theTexture->setStoredMipFormat(formatMip); - theTexture->assignStoredMip(0, image.byteCount(), image.constBits()); - generateMips(theTexture.get(), std::move(image), target, abortProcessing); - } - - return theTexture; -} - -int clampPixelCoordinate(int coordinate, int maxCoordinate) { - return coordinate - ((int)(coordinate < 0) * coordinate) + ((int)(coordinate > maxCoordinate) * (maxCoordinate - coordinate)); -} - -const int RGBA_MAX = 255; - -// transform -1 - 1 to 0 - 255 (from sobel value to rgb) -double mapComponent(double sobelValue) { - const double factor = RGBA_MAX / 2.0; - return (sobelValue + 1.0) * factor; -} - -QImage processBumpMap(QImage&& image) { - // Take a local copy to force move construction - // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter - QImage localCopy = std::move(image); - - if (localCopy.format() != QImage::Format_Grayscale8) { - localCopy = localCopy.convertToFormat(QImage::Format_Grayscale8); - } - - // PR 5540 by AlessandroSigna integrated here as a specialized TextureLoader for bumpmaps - // The conversion is done using the Sobel Filter to calculate the derivatives from the grayscale image - const double pStrength = 2.0; - int width = localCopy.width(); - int height = localCopy.height(); - - QImage result(width, height, QImage::Format_ARGB32); - - for (int i = 0; i < width; i++) { - const int iNextClamped = clampPixelCoordinate(i + 1, width - 1); - const int iPrevClamped = clampPixelCoordinate(i - 1, width - 1); - - for (int j = 0; j < height; j++) { - const int jNextClamped = clampPixelCoordinate(j + 1, height - 1); - const int jPrevClamped = clampPixelCoordinate(j - 1, height - 1); - - // surrounding pixels - const QRgb topLeft = localCopy.pixel(iPrevClamped, jPrevClamped); - const QRgb top = localCopy.pixel(iPrevClamped, j); - const QRgb topRight = localCopy.pixel(iPrevClamped, jNextClamped); - const QRgb right = localCopy.pixel(i, jNextClamped); - const QRgb bottomRight = localCopy.pixel(iNextClamped, jNextClamped); - const QRgb bottom = localCopy.pixel(iNextClamped, j); - const QRgb bottomLeft = localCopy.pixel(iNextClamped, jPrevClamped); - const QRgb left = localCopy.pixel(i, jPrevClamped); - - // take their gray intensities - // since it's a grayscale image, the value of each component RGB is the same - const double tl = qRed(topLeft); - const double t = qRed(top); - const double tr = qRed(topRight); - const double r = qRed(right); - const double br = qRed(bottomRight); - const double b = qRed(bottom); - const double bl = qRed(bottomLeft); - const double l = qRed(left); - - // apply the sobel filter - const double dX = (tr + pStrength * r + br) - (tl + pStrength * l + bl); - const double dY = (bl + pStrength * b + br) - (tl + pStrength * t + tr); - const double dZ = RGBA_MAX / pStrength; - - glm::vec3 v(dX, dY, dZ); - glm::normalize(v); - - // convert to rgb from the value obtained computing the filter - QRgb qRgbValue = qRgba(mapComponent(v.z), mapComponent(v.y), mapComponent(v.x), 1.0); - result.setPixel(i, j, qRgbValue); - } - } - - return result; -} -gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, BackendTarget target, bool isBumpMap, - const std::atomic& abortProcessing) { - PROFILE_RANGE(resource_parse, "process2DTextureNormalMapFromImage"); - QImage image = processSourceImage(std::move(srcImage), false, target); - - if (isBumpMap) { - image = processBumpMap(std::move(image)); - } - - // Make sure the normal map source image is ARGB32 - if (image.format() != QImage::Format_ARGB32) { - image = image.convertToFormat(QImage::Format_ARGB32); - } - - gpu::TexturePointer theTexture = nullptr; - if ((image.width() > 0) && (image.height() > 0)) { - gpu::Element formatMip; - gpu::Element formatGPU; - if (compress) { - if (target == BackendTarget::GLES32) { - formatGPU = gpu::Element::COLOR_COMPRESSED_EAC_XY; - } else { - formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_XY; - } - } else { - formatGPU = gpu::Element::VEC2NU8_XY; - } - formatMip = formatGPU; - - theTexture = gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)); - theTexture->setSource(srcImageName); - theTexture->setStoredMipFormat(formatMip); - theTexture->assignStoredMip(0, image.byteCount(), image.constBits()); - generateMips(theTexture.get(), std::move(image), target, abortProcessing); - } - - return theTexture; -} - -gpu::TexturePointer TextureUsage::process2DTextureGrayscaleFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, BackendTarget target, bool isInvertedPixels, - const std::atomic& abortProcessing) { - PROFILE_RANGE(resource_parse, "process2DTextureGrayscaleFromImage"); - QImage image = processSourceImage(std::move(srcImage), false, target); - - if (image.format() != QImage::Format_ARGB32) { - image = image.convertToFormat(QImage::Format_ARGB32); - } - - if (isInvertedPixels) { - // Gloss turned into Rough - image.invertPixels(QImage::InvertRgba); - } - - gpu::TexturePointer theTexture = nullptr; - if ((image.width() > 0) && (image.height() > 0)) { - gpu::Element formatMip; - gpu::Element formatGPU; - if (compress) { - if (target == BackendTarget::GLES32) { - formatGPU = gpu::Element::COLOR_COMPRESSED_EAC_RED; - } else { - formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_RED; - } - } else { - formatGPU = gpu::Element::COLOR_R_8; - } - formatMip = formatGPU; - - theTexture = gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)); - theTexture->setSource(srcImageName); - theTexture->setStoredMipFormat(formatMip); - theTexture->assignStoredMip(0, image.byteCount(), image.constBits()); - generateMips(theTexture.get(), std::move(image), target, abortProcessing); - } - - return theTexture; -} - -class CubeLayout { -public: - - enum SourceProjection { - FLAT = 0, - EQUIRECTANGULAR, - }; - int _type = FLAT; - int _widthRatio = 1; - int _heightRatio = 1; - - class Face { - public: - int _x = 0; - int _y = 0; - bool _horizontalMirror = false; - bool _verticalMirror = false; - - Face() {} - Face(int x, int y, bool horizontalMirror, bool verticalMirror) : _x(x), _y(y), _horizontalMirror(horizontalMirror), _verticalMirror(verticalMirror) {} - }; - - Face _faceXPos; - Face _faceXNeg; - Face _faceYPos; - Face _faceYNeg; - Face _faceZPos; - Face _faceZNeg; - - CubeLayout(int wr, int hr, Face fXP, Face fXN, Face fYP, Face fYN, Face fZP, Face fZN) : - _type(FLAT), - _widthRatio(wr), - _heightRatio(hr), - _faceXPos(fXP), - _faceXNeg(fXN), - _faceYPos(fYP), - _faceYNeg(fYN), - _faceZPos(fZP), - _faceZNeg(fZN) {} - - CubeLayout(int wr, int hr) : - _type(EQUIRECTANGULAR), - _widthRatio(wr), - _heightRatio(hr) {} - - - static const CubeLayout CUBEMAP_LAYOUTS[]; - static const int NUM_CUBEMAP_LAYOUTS; - - static int findLayout(int width, int height) { - // Find the layout of the cubemap in the 2D image - int foundLayout = -1; - for (int i = 0; i < NUM_CUBEMAP_LAYOUTS; i++) { - if ((height * CUBEMAP_LAYOUTS[i]._widthRatio) == (width * CUBEMAP_LAYOUTS[i]._heightRatio)) { - foundLayout = i; - break; - } - } - return foundLayout; - } - - static QImage extractEquirectangularFace(const QImage& source, gpu::Texture::CubeFace face, int faceWidth) { - QImage image(faceWidth, faceWidth, source.format()); - - glm::vec2 dstInvSize(1.0f / faceWidth); - - struct CubeToXYZ { - gpu::Texture::CubeFace _face; - CubeToXYZ(gpu::Texture::CubeFace face) : _face(face) {} - - glm::vec3 xyzFrom(const glm::vec2& uv) { - auto faceDir = glm::normalize(glm::vec3(-1.0f + 2.0f * uv.x, -1.0f + 2.0f * uv.y, 1.0f)); - - switch (_face) { - case gpu::Texture::CubeFace::CUBE_FACE_BACK_POS_Z: - return glm::vec3(-faceDir.x, faceDir.y, faceDir.z); - case gpu::Texture::CubeFace::CUBE_FACE_FRONT_NEG_Z: - return glm::vec3(faceDir.x, faceDir.y, -faceDir.z); - case gpu::Texture::CubeFace::CUBE_FACE_LEFT_NEG_X: - return glm::vec3(faceDir.z, faceDir.y, faceDir.x); - case gpu::Texture::CubeFace::CUBE_FACE_RIGHT_POS_X: - return glm::vec3(-faceDir.z, faceDir.y, -faceDir.x); - case gpu::Texture::CubeFace::CUBE_FACE_BOTTOM_NEG_Y: - return glm::vec3(-faceDir.x, -faceDir.z, faceDir.y); - case gpu::Texture::CubeFace::CUBE_FACE_TOP_POS_Y: - default: - return glm::vec3(-faceDir.x, faceDir.z, -faceDir.y); - } - } - }; - CubeToXYZ cubeToXYZ(face); - - struct RectToXYZ { - RectToXYZ() {} - - glm::vec2 uvFrom(const glm::vec3& xyz) { - auto flatDir = glm::normalize(glm::vec2(xyz.x, xyz.z)); - auto uvRad = glm::vec2(atan2(flatDir.x, flatDir.y), asin(xyz.y)); - - const float LON_TO_RECT_U = 1.0f / (glm::pi()); - const float LAT_TO_RECT_V = 2.0f / glm::pi(); - return glm::vec2(0.5f * uvRad.x * LON_TO_RECT_U + 0.5f, 0.5f * uvRad.y * LAT_TO_RECT_V + 0.5f); - } - }; - RectToXYZ rectToXYZ; - - int srcFaceHeight = source.height(); - int srcFaceWidth = source.width(); - - glm::vec2 dstCoord; - glm::ivec2 srcPixel; - for (int y = 0; y < faceWidth; ++y) { - QRgb* destScanLineBegin = reinterpret_cast( image.scanLine(y) ); - QRgb* destPixelIterator = destScanLineBegin; - - dstCoord.y = 1.0f - (y + 0.5f) * dstInvSize.y; // Fill cube face images from top to bottom - for (int x = 0; x < faceWidth; ++x) { - dstCoord.x = (x + 0.5f) * dstInvSize.x; - - auto xyzDir = cubeToXYZ.xyzFrom(dstCoord); - auto srcCoord = rectToXYZ.uvFrom(xyzDir); - - srcPixel.x = floor(srcCoord.x * srcFaceWidth); - // Flip the vertical axis to QImage going top to bottom - srcPixel.y = floor((1.0f - srcCoord.y) * srcFaceHeight); - - if (((uint32)srcPixel.x < (uint32)source.width()) && ((uint32)srcPixel.y < (uint32)source.height())) { - // We can't directly use the pixel() method because that launches a pixel color conversion to output - // a correct RGBA8 color. But in our case we may have stored HDR values encoded in a RGB30 format which - // are not convertible by Qt. The same goes with the setPixel method, by the way. - const QRgb* sourcePixelIterator = reinterpret_cast(source.scanLine(srcPixel.y)); - sourcePixelIterator += srcPixel.x; - *destPixelIterator = *sourcePixelIterator; - - // Keep for debug, this is showing the dir as a color - // glm::u8vec4 rgba((xyzDir.x + 1.0)*0.5 * 256, (xyzDir.y + 1.0)*0.5 * 256, (xyzDir.z + 1.0)*0.5 * 256, 256); - // unsigned int val = 0xff000000 | (rgba.r) | (rgba.g << 8) | (rgba.b << 16); - // *destPixelIterator = val; - } - ++destPixelIterator; - } - } - return image; - } -}; - -const CubeLayout CubeLayout::CUBEMAP_LAYOUTS[] = { - - // Here is the expected layout for the faces in an image with the 2/1 aspect ratio: - // THis is detected as an Equirectangular projection - // WIDTH - // <---------------------------> - // ^ +------+------+------+------+ - // H | | | | | - // E | | | | | - // I | | | | | - // G +------+------+------+------+ - // H | | | | | - // T | | | | | - // | | | | | | - // v +------+------+------+------+ - // - // FaceWidth = width = height / 6 - { 2, 1 }, - - // Here is the expected layout for the faces in an image with the 1/6 aspect ratio: - // - // WIDTH - // <------> - // ^ +------+ - // | | | - // | | +X | - // | | | - // H +------+ - // E | | - // I | -X | - // G | | - // H +------+ - // T | | - // | | +Y | - // | | | - // | +------+ - // | | | - // | | -Y | - // | | | - // H +------+ - // E | | - // I | +Z | - // G | | - // H +------+ - // T | | - // | | -Z | - // | | | - // V +------+ - // - // FaceWidth = width = height / 6 - { 1, 6, - { 0, 0, true, false }, - { 0, 1, true, false }, - { 0, 2, false, true }, - { 0, 3, false, true }, - { 0, 4, true, false }, - { 0, 5, true, false } - }, - - // Here is the expected layout for the faces in an image with the 3/4 aspect ratio: - // - // <-----------WIDTH-----------> - // ^ +------+------+------+------+ - // | | | | | | - // | | | +Y | | | - // | | | | | | - // H +------+------+------+------+ - // E | | | | | - // I | -X | -Z | +X | +Z | - // G | | | | | - // H +------+------+------+------+ - // T | | | | | - // | | | -Y | | | - // | | | | | | - // V +------+------+------+------+ - // - // FaceWidth = width / 4 = height / 3 - { 4, 3, - { 2, 1, true, false }, - { 0, 1, true, false }, - { 1, 0, false, true }, - { 1, 2, false, true }, - { 3, 1, true, false }, - { 1, 1, true, false } - }, - - // Here is the expected layout for the faces in an image with the 4/3 aspect ratio: - // - // <-------WIDTH--------> - // ^ +------+------+------+ - // | | | | | - // | | | +Y | | - // | | | | | - // H +------+------+------+ - // E | | | | - // I | -X | -Z | +X | - // G | | | | - // H +------+------+------+ - // T | | | | - // | | | -Y | | - // | | | | | - // | +------+------+------+ - // | | | | | - // | | | +Z! | | <+Z is upside down! - // | | | | | - // V +------+------+------+ - // - // FaceWidth = width / 3 = height / 4 - { 3, 4, - { 2, 1, true, false }, - { 0, 1, true, false }, - { 1, 0, false, true }, - { 1, 2, false, true }, - { 1, 3, false, true }, - { 1, 1, true, false } - } -}; -const int CubeLayout::NUM_CUBEMAP_LAYOUTS = sizeof(CubeLayout::CUBEMAP_LAYOUTS) / sizeof(CubeLayout); - -//#define DEBUG_COLOR_PACKING - -QImage convertToHDRFormat(QImage&& srcImage, gpu::Element format, BackendTarget target) { - // Take a local copy to force move construction - // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter - QImage localCopy = std::move(srcImage); - - QImage hdrImage(localCopy.width(), localCopy.height(), hdrFormatForTarget(target)); - std::function packFunc; -#ifdef DEBUG_COLOR_PACKING - std::function unpackFunc; -#endif - - switch (format.getSemantic()) { - case gpu::R11G11B10: - packFunc = packR11G11B10F; -#ifdef DEBUG_COLOR_PACKING - unpackFunc = glm::unpackF2x11_1x10; -#endif - break; - case gpu::RGB9E5: - packFunc = glm::packF3x9_E1x5; -#ifdef DEBUG_COLOR_PACKING - unpackFunc = glm::unpackF3x9_E1x5; -#endif - break; - default: - qCWarning(imagelogging) << "Unsupported HDR format"; - Q_UNREACHABLE(); - return localCopy; - } - - localCopy = localCopy.convertToFormat(QImage::Format_ARGB32); - for (auto y = 0; y < localCopy.height(); y++) { - const QRgb* srcLineIt = reinterpret_cast( localCopy.constScanLine(y) ); - const QRgb* srcLineEnd = srcLineIt + localCopy.width(); - uint32* hdrLineIt = reinterpret_cast( hdrImage.scanLine(y) ); - glm::vec3 color; - - while (srcLineIt < srcLineEnd) { - color.r = qRed(*srcLineIt); - color.g = qGreen(*srcLineIt); - color.b = qBlue(*srcLineIt); - // Normalize and apply gamma - color /= 255.0f; - color.r = powf(color.r, 2.2f); - color.g = powf(color.g, 2.2f); - color.b = powf(color.b, 2.2f); - *hdrLineIt = packFunc(color); -#ifdef DEBUG_COLOR_PACKING - glm::vec3 ucolor = unpackFunc(*hdrLineIt); - assert(glm::distance(color, ucolor) <= 5e-2); -#endif - ++srcLineIt; - ++hdrLineIt; - } - } - return hdrImage; -} - -gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, - bool compress, BackendTarget target, bool generateIrradiance, - const std::atomic& abortProcessing) { - PROFILE_RANGE(resource_parse, "processCubeTextureColorFromImage"); - - // Take a local copy to force move construction - // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter - QImage localCopy = std::move(srcImage); - - int originalWidth = localCopy.width(); - int originalHeight = localCopy.height(); - if ((originalWidth <= 0) && (originalHeight <= 0)) { - return nullptr; - } - - gpu::TexturePointer theTexture = nullptr; - - QImage image = processSourceImage(std::move(localCopy), true, target); - - if (image.format() != hdrFormatForTarget(target)) { - if (target == BackendTarget::GLES32) { - image = image.convertToFormat(QImage::Format_RGB32); - } else { - image = convertToHDRFormat(std::move(image), HDR_FORMAT, target); - } - } - - gpu::Element formatMip; - gpu::Element formatGPU; - if (compress) { - if (target == BackendTarget::GLES32) { - formatGPU = gpu::Element::COLOR_COMPRESSED_ETC2_SRGB; - } else { - formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_HDR_RGB; - } - } else { - formatGPU = HDR_FORMAT; - } - - formatMip = formatGPU; - - // Find the layout of the cubemap in the 2D image - // Use the original image size since processSourceImage may have altered the size / aspect ratio - int foundLayout = CubeLayout::findLayout(originalWidth, originalHeight); - - if (foundLayout < 0) { - qCDebug(imagelogging) << "Failed to find a known cube map layout from this image:" << QString(srcImageName.c_str()); - return nullptr; - } - - std::vector faces; - - // If found, go extract the faces as separate images - auto& layout = CubeLayout::CUBEMAP_LAYOUTS[foundLayout]; - if (layout._type == CubeLayout::FLAT) { - int faceWidth = image.width() / layout._widthRatio; - - faces.push_back(image.copy(QRect(layout._faceXPos._x * faceWidth, layout._faceXPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceXPos._horizontalMirror, layout._faceXPos._verticalMirror)); - faces.push_back(image.copy(QRect(layout._faceXNeg._x * faceWidth, layout._faceXNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceXNeg._horizontalMirror, layout._faceXNeg._verticalMirror)); - faces.push_back(image.copy(QRect(layout._faceYPos._x * faceWidth, layout._faceYPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceYPos._horizontalMirror, layout._faceYPos._verticalMirror)); - faces.push_back(image.copy(QRect(layout._faceYNeg._x * faceWidth, layout._faceYNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceYNeg._horizontalMirror, layout._faceYNeg._verticalMirror)); - faces.push_back(image.copy(QRect(layout._faceZPos._x * faceWidth, layout._faceZPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceZPos._horizontalMirror, layout._faceZPos._verticalMirror)); - faces.push_back(image.copy(QRect(layout._faceZNeg._x * faceWidth, layout._faceZNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceZNeg._horizontalMirror, layout._faceZNeg._verticalMirror)); - } else if (layout._type == CubeLayout::EQUIRECTANGULAR) { - // THe face width is estimated from the input image - const int EQUIRECT_FACE_RATIO_TO_WIDTH = 4; - const int EQUIRECT_MAX_FACE_WIDTH = 2048; - int faceWidth = std::min(image.width() / EQUIRECT_FACE_RATIO_TO_WIDTH, EQUIRECT_MAX_FACE_WIDTH); - for (int face = gpu::Texture::CUBE_FACE_RIGHT_POS_X; face < gpu::Texture::NUM_CUBE_FACES; face++) { - QImage faceImage = CubeLayout::extractEquirectangularFace(std::move(image), (gpu::Texture::CubeFace) face, faceWidth); - faces.push_back(std::move(faceImage)); - } - } - - // free up the memory afterward to avoid bloating the heap - image = QImage(); // QImage doesn't have a clear function, so override it with an empty one. - - // If the 6 faces have been created go on and define the true Texture - if (faces.size() == gpu::Texture::NUM_FACES_PER_TYPE[gpu::Texture::TEX_CUBE]) { - theTexture = gpu::Texture::createCube(formatGPU, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)); - theTexture->setSource(srcImageName); - theTexture->setStoredMipFormat(formatMip); - - // Generate irradiance while we are at it - if (generateIrradiance) { - PROFILE_RANGE(resource_parse, "generateIrradiance"); - gpu::Element irradianceFormat; - // TODO: we could locally compress the irradiance texture on Android, but we don't need to - if (target == BackendTarget::GLES32) { - irradianceFormat = gpu::Element::COLOR_SRGBA_32; - } else { - irradianceFormat = HDR_FORMAT; - } - - auto irradianceTexture = gpu::Texture::createCube(irradianceFormat, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)); - irradianceTexture->setSource(srcImageName); - irradianceTexture->setStoredMipFormat(irradianceFormat); - for (uint8 face = 0; face < faces.size(); ++face) { - irradianceTexture->assignStoredMipFace(0, face, faces[face].byteCount(), faces[face].constBits()); - } - - irradianceTexture->generateIrradiance(target); - - auto irradiance = irradianceTexture->getIrradiance(); - theTexture->overrideIrradiance(irradiance); - } - - for (uint8 face = 0; face < faces.size(); ++face) { - generateMips(theTexture.get(), std::move(faces[face]), target, abortProcessing, face); - } - } - - return theTexture; -} - -} // namespace image diff --git a/libraries/image/src/image/Image.h b/libraries/image/src/image/Image.h index 40c31eeeff..bfecf4f2a1 100644 --- a/libraries/image/src/image/Image.h +++ b/libraries/image/src/image/Image.h @@ -1,9 +1,10 @@ +#pragma once // // Image.h -// image/src/image +// image/src/Image // -// Created by Clement Brisset on 4/5/2017. -// Copyright 2017 High Fidelity, Inc. +// Created by Olivier Prat on 29/3/2019. +// Copyright 2019 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 @@ -12,80 +13,85 @@ #ifndef hifi_image_Image_h #define hifi_image_Image_h -#include - -#include +#include #include "ColorChannel.h" -class QByteArray; -class QImage; +#include +#include +#include namespace image { -namespace TextureUsage { + class Image { + public: -enum Type { - DEFAULT_TEXTURE, - STRICT_TEXTURE, - ALBEDO_TEXTURE, - NORMAL_TEXTURE, - BUMP_TEXTURE, - SPECULAR_TEXTURE, - METALLIC_TEXTURE = SPECULAR_TEXTURE, // for now spec and metallic texture are the same, converted to grey - ROUGHNESS_TEXTURE, - GLOSS_TEXTURE, - EMISSIVE_TEXTURE, - CUBE_TEXTURE, - OCCLUSION_TEXTURE, - SCATTERING_TEXTURE = OCCLUSION_TEXTURE, - LIGHTMAP_TEXTURE, - UNUSED_TEXTURE -}; + enum Format { + Format_Invalid = QImage::Format_Invalid, + Format_Mono = QImage::Format_Mono, + Format_MonoLSB = QImage::Format_MonoLSB, + Format_Indexed8 = QImage::Format_Indexed8, + Format_RGB32 = QImage::Format_RGB32, + Format_ARGB32 = QImage::Format_ARGB32, + Format_ARGB32_Premultiplied = QImage::Format_ARGB32_Premultiplied, + Format_RGB16 = QImage::Format_RGB16, + Format_ARGB8565_Premultiplied = QImage::Format_ARGB8565_Premultiplied, + Format_RGB666 = QImage::Format_RGB666, + Format_ARGB6666_Premultiplied = QImage::Format_ARGB6666_Premultiplied, + Format_RGB555 = QImage::Format_RGB555, + Format_ARGB8555_Premultiplied = QImage::Format_ARGB8555_Premultiplied, + Format_RGB888 = QImage::Format_RGB888, + Format_RGB444 = QImage::Format_RGB444, + Format_ARGB4444_Premultiplied = QImage::Format_ARGB4444_Premultiplied, + Format_RGBX8888 = QImage::Format_RGBX8888, + Format_RGBA8888 = QImage::Format_RGBA8888, + Format_RGBA8888_Premultiplied = QImage::Format_RGBA8888_Premultiplied, + Format_Grayscale8 = QImage::Format_Grayscale8, + Format_R11G11B10F = QImage::Format_RGB30, + Format_PACKED_FLOAT = Format_R11G11B10F + }; -using TextureLoader = std::function&)>; -TextureLoader getTextureLoaderForType(Type type, const QVariantMap& options = QVariantMap()); + using AspectRatioMode = Qt::AspectRatioMode; + using TransformationMode = Qt::TransformationMode; -gpu::TexturePointer create2DTextureFromImage(QImage&& image, const std::string& srcImageName, - bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); -gpu::TexturePointer createStrict2DTextureFromImage(QImage&& image, const std::string& srcImageName, - bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); -gpu::TexturePointer createAlbedoTextureFromImage(QImage&& image, const std::string& srcImageName, - bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); -gpu::TexturePointer createEmissiveTextureFromImage(QImage&& image, const std::string& srcImageName, - bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); -gpu::TexturePointer createNormalTextureFromNormalImage(QImage&& image, const std::string& srcImageName, - bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); -gpu::TexturePointer createNormalTextureFromBumpImage(QImage&& image, const std::string& srcImageName, - bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); -gpu::TexturePointer createRoughnessTextureFromImage(QImage&& image, const std::string& srcImageName, - bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); -gpu::TexturePointer createRoughnessTextureFromGlossImage(QImage&& image, const std::string& srcImageName, - bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); -gpu::TexturePointer createMetallicTextureFromImage(QImage&& image, const std::string& srcImageName, - bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); -gpu::TexturePointer createCubeTextureFromImage(QImage&& image, const std::string& srcImageName, - bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); -gpu::TexturePointer createCubeTextureFromImageWithoutIrradiance(QImage&& image, const std::string& srcImageName, - bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); -gpu::TexturePointer createLightmapTextureFromImage(QImage&& image, const std::string& srcImageName, - bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); -gpu::TexturePointer process2DTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress, - gpu::BackendTarget target, bool isStrict, const std::atomic& abortProcessing); -gpu::TexturePointer process2DTextureNormalMapFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress, - gpu::BackendTarget target, bool isBumpMap, const std::atomic& abortProcessing); -gpu::TexturePointer process2DTextureGrayscaleFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress, - gpu::BackendTarget target, bool isInvertedPixels, const std::atomic& abortProcessing); -gpu::TexturePointer processCubeTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress, - gpu::BackendTarget target, bool generateIrradiance, const std::atomic& abortProcessing); + Image() {} + Image(int width, int height, Format format) : _data(width, height, (QImage::Format)format) {} + Image(const QImage& data) : _data(data) {} + void operator=(const QImage& image) { + _data = image; + } -} // namespace TextureUsage + bool isNull() const { return _data.isNull(); } -const QStringList getSupportedFormats(); + Format getFormat() const { return (Format)_data.format(); } + bool hasAlphaChannel() const { return _data.hasAlphaChannel(); } -gpu::TexturePointer processImage(std::shared_ptr content, const std::string& url, ColorChannel sourceChannel, - int maxNumPixels, TextureUsage::Type textureType, - bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing = false); + glm::uint32 getWidth() const { return (glm::uint32)_data.width(); } + glm::uint32 getHeight() const { return (glm::uint32)_data.height(); } + glm::uvec2 getSize() const { return toGlm(_data.size()); } + size_t getByteCount() const { return _data.byteCount(); } + + QRgb getPixel(int x, int y) const { return _data.pixel(x, y); } + void setPixel(int x, int y, QRgb value) { + _data.setPixel(x, y, value); + } + + glm::uint8* editScanLine(int y) { return _data.scanLine(y); } + const glm::uint8* getScanLine(int y) const { return _data.scanLine(y); } + const glm::uint8* getBits() const { return _data.constBits(); } + + Image getScaled(glm::uvec2 newSize, AspectRatioMode ratioMode, TransformationMode transformationMode = Qt::SmoothTransformation) const; + Image getConvertedToFormat(Format newFormat) const; + Image getSubImage(QRect rect) const; + Image getMirrored(bool horizontal, bool vertical) const; + + // Inplace transformations + void invertPixels(); + + private: + + QImage _data; + }; } // namespace image diff --git a/libraries/image/src/image/OpenEXRReader.cpp b/libraries/image/src/image/OpenEXRReader.cpp new file mode 100644 index 0000000000..66e304e3fa --- /dev/null +++ b/libraries/image/src/image/OpenEXRReader.cpp @@ -0,0 +1,99 @@ +// +// OpenEXRReader.cpp +// image/src/image +// +// Created by Olivier Prat +// Copyright 2019 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 "OpenEXRReader.h" + +#include "TextureProcessing.h" +#include "ImageLogging.h" + +#include +#include + +#if !defined(Q_OS_ANDROID) + +#include +#include +#include +#include + +class QIODeviceImfStream : public Imf::IStream { +public: + + QIODeviceImfStream(QIODevice& device, const std::string& filename) : + Imf::IStream(filename.c_str()), _device(device) { + } + + bool read(char c[/*n*/], int n) override { + if (_device.read(c, n) <= 0) { + qWarning(imagelogging) << "OpenEXR - in file " << fileName() << " : " << _device.errorString(); + return false; + } + return true; + } + + Imf::Int64 tellg() override { + return _device.pos(); + } + + void seekg(Imf::Int64 pos) override { + _device.seek(pos); + } + + void clear() override { + // Not much to do + } + +private: + + QIODevice& _device; +}; + +#endif + +image::Image image::readOpenEXR(QIODevice& content, const std::string& filename) { +#if !defined(Q_OS_ANDROID) + QIODeviceImfStream device(content, filename); + + if (Imf::isOpenExrFile(device)) { + Imf::RgbaInputFile file(device); + Imath::Box2i viewport = file.dataWindow(); + Imf::Array2D pixels; + int width = viewport.max.x - viewport.min.x + 1; + int height = viewport.max.y - viewport.min.y + 1; + + pixels.resizeErase(height, width); + + file.setFrameBuffer(&pixels[0][0] - viewport.min.x - viewport.min.y * width, 1, width); + file.readPixels(viewport.min.y, viewport.max.y); + + Image image{ width, height, Image::Format_PACKED_FLOAT }; + auto packHDRPixel = getHDRPackingFunction(); + + for (int y = 0; y < height; y++) { + const auto srcScanline = pixels[y]; + gpu::uint32* dstScanline = (gpu::uint32*) image.editScanLine(y); + + for (int x = 0; x < width; x++) { + const auto& srcPixel = srcScanline[x]; + auto& dstPixel = dstScanline[x]; + glm::vec3 floatPixel{ srcPixel.r, srcPixel.g, srcPixel.b }; + + dstPixel = packHDRPixel(floatPixel); + } + } + return image; + } else { + qWarning(imagelogging) << "OpenEXR - File " << filename.c_str() << " doesn't have the proper format"; + } +#endif + + return QImage(); +} diff --git a/libraries/image/src/image/OpenEXRReader.h b/libraries/image/src/image/OpenEXRReader.h new file mode 100644 index 0000000000..232ba370e0 --- /dev/null +++ b/libraries/image/src/image/OpenEXRReader.h @@ -0,0 +1,24 @@ +// +// OpenEXRReader.h +// image/src/image +// +// Created by Olivier Prat +// Copyright 2019 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_image_OpenEXRReader_h +#define hifi_image_OpenEXRReader_h + +#include "Image.h" + +namespace image { + + // TODO Move this into a plugin that QImageReader can use + Image readOpenEXR(QIODevice& contents, const std::string& filename); + +} + +#endif // hifi_image_OpenEXRReader_h diff --git a/libraries/image/src/image/TGAReader.cpp b/libraries/image/src/image/TGAReader.cpp index 897d565eba..7948806f26 100644 --- a/libraries/image/src/image/TGAReader.cpp +++ b/libraries/image/src/image/TGAReader.cpp @@ -16,7 +16,7 @@ #include #include -QImage image::readTGA(QIODevice& content) { +image::Image image::readTGA(QIODevice& content) { enum class TGAImageType : uint8_t { NoImageData = 0, UncompressedColorMapped = 1, diff --git a/libraries/image/src/image/TGAReader.h b/libraries/image/src/image/TGAReader.h index 8019be7f0b..fc7bc1a101 100644 --- a/libraries/image/src/image/TGAReader.h +++ b/libraries/image/src/image/TGAReader.h @@ -12,12 +12,11 @@ #ifndef hifi_image_TGAReader_h #define hifi_image_TGAReader_h -#include +#include "Image.h" namespace image { -// TODO Move this into a plugin that QImageReader can use -QImage readTGA(QIODevice& contents); + Image readTGA(QIODevice& contents); } diff --git a/libraries/image/src/image/TextureProcessing.cpp b/libraries/image/src/image/TextureProcessing.cpp new file mode 100644 index 0000000000..037229ace5 --- /dev/null +++ b/libraries/image/src/image/TextureProcessing.cpp @@ -0,0 +1,1525 @@ +// +// TextureProcessing.cpp +// image/src/TextureProcessing +// +// Created by Clement Brisset on 4/5/2017. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "TextureProcessing.h" + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "TGAReader.h" +#if !defined(Q_OS_ANDROID) +#include "OpenEXRReader.h" +#endif +#include "ImageLogging.h" + +using namespace gpu; + +#define CPU_MIPMAPS 1 +#include + +#undef _CRT_SECURE_NO_WARNINGS +#include +#include + +static const glm::uvec2 SPARSE_PAGE_SIZE(128); +static const glm::uvec2 MAX_TEXTURE_SIZE_GLES(2048); +static const glm::uvec2 MAX_TEXTURE_SIZE_GL(4096); +bool DEV_DECIMATE_TEXTURES = false; +std::atomic DECIMATED_TEXTURE_COUNT{ 0 }; +std::atomic RECTIFIED_TEXTURE_COUNT{ 0 }; + +// we use a ref here to work around static order initialization +// possibly causing the element not to be constructed yet +static const auto& GPU_CUBEMAP_DEFAULT_FORMAT = gpu::Element::COLOR_SRGBA_32; +static const auto& GPU_CUBEMAP_HDR_FORMAT = gpu::Element::COLOR_R11G11B10; + +namespace image { + +uint rectifyDimension(const uint& dimension) { + if (dimension == 0) { + return 0; + } + if (dimension < SPARSE_PAGE_SIZE.x) { + uint newSize = SPARSE_PAGE_SIZE.x; + while (dimension <= newSize / 2) { + newSize /= 2; + } + return newSize; + } else { + uint pages = (dimension / SPARSE_PAGE_SIZE.x) + (dimension % SPARSE_PAGE_SIZE.x == 0 ? 0 : 1); + return pages * SPARSE_PAGE_SIZE.x; + } +} + +glm::uvec2 rectifySize(const glm::uvec2& size) { + return { rectifyDimension(size.x), rectifyDimension(size.y) }; +} + +const QStringList getSupportedFormats() { + auto formats = QImageReader::supportedImageFormats(); + QStringList stringFormats; + std::transform(formats.begin(), formats.end(), std::back_inserter(stringFormats), + [](QByteArray& format) -> QString { return format; }); + return stringFormats; +} + + +// On GLES, we don't use HDR skyboxes +bool isHDRTextureFormatEnabledForTarget(BackendTarget target) { + return target != BackendTarget::GLES32; +} + +gpu::Element getHDRTextureFormatForTarget(BackendTarget target, bool compressed) { + if (compressed) { + if (target == BackendTarget::GLES32) { + return gpu::Element::COLOR_COMPRESSED_ETC2_SRGB; + } else { + return gpu::Element::COLOR_COMPRESSED_BCX_HDR_RGB; + } + } else { + if (!isHDRTextureFormatEnabledForTarget(target)) { + return GPU_CUBEMAP_DEFAULT_FORMAT; + } else { + return GPU_CUBEMAP_HDR_FORMAT; + } + } +} + +TextureUsage::TextureLoader TextureUsage::getTextureLoaderForType(Type type, const QVariantMap& options) { + switch (type) { + case ALBEDO_TEXTURE: + return image::TextureUsage::createAlbedoTextureFromImage; + case EMISSIVE_TEXTURE: + return image::TextureUsage::createEmissiveTextureFromImage; + case LIGHTMAP_TEXTURE: + return image::TextureUsage::createLightmapTextureFromImage; + case CUBE_TEXTURE: + if (options.value("generateIrradiance", true).toBool()) { + return image::TextureUsage::createCubeTextureFromImage; + } else { + return image::TextureUsage::createCubeTextureFromImageWithoutIrradiance; + } + case BUMP_TEXTURE: + return image::TextureUsage::createNormalTextureFromBumpImage; + case NORMAL_TEXTURE: + return image::TextureUsage::createNormalTextureFromNormalImage; + case ROUGHNESS_TEXTURE: + return image::TextureUsage::createRoughnessTextureFromImage; + case GLOSS_TEXTURE: + return image::TextureUsage::createRoughnessTextureFromGlossImage; + case SPECULAR_TEXTURE: + return image::TextureUsage::createMetallicTextureFromImage; + case STRICT_TEXTURE: + return image::TextureUsage::createStrict2DTextureFromImage; + + case DEFAULT_TEXTURE: + default: + return image::TextureUsage::create2DTextureFromImage; + } +} + +gpu::TexturePointer TextureUsage::createStrict2DTextureFromImage(Image&& srcImage, const std::string& srcImageName, + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, true, abortProcessing); +} + +gpu::TexturePointer TextureUsage::create2DTextureFromImage(Image&& srcImage, const std::string& srcImageName, + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); +} + +gpu::TexturePointer TextureUsage::createAlbedoTextureFromImage(Image&& srcImage, const std::string& srcImageName, + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); +} + +gpu::TexturePointer TextureUsage::createEmissiveTextureFromImage(Image&& srcImage, const std::string& srcImageName, + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); +} + +gpu::TexturePointer TextureUsage::createLightmapTextureFromImage(Image&& srcImage, const std::string& srcImageName, + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); +} + +gpu::TexturePointer TextureUsage::createNormalTextureFromNormalImage(Image&& srcImage, const std::string& srcImageName, + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return process2DTextureNormalMapFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); +} + +gpu::TexturePointer TextureUsage::createNormalTextureFromBumpImage(Image&& srcImage, const std::string& srcImageName, + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return process2DTextureNormalMapFromImage(std::move(srcImage), srcImageName, compress, target, true, abortProcessing); +} + +gpu::TexturePointer TextureUsage::createRoughnessTextureFromImage(Image&& srcImage, const std::string& srcImageName, + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); +} + +gpu::TexturePointer TextureUsage::createRoughnessTextureFromGlossImage(Image&& srcImage, const std::string& srcImageName, + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, compress, target, true, abortProcessing); +} + +gpu::TexturePointer TextureUsage::createMetallicTextureFromImage(Image&& srcImage, const std::string& srcImageName, + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); +} + +gpu::TexturePointer TextureUsage::createCubeTextureFromImage(Image&& srcImage, const std::string& srcImageName, + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, true, abortProcessing); +} + +gpu::TexturePointer TextureUsage::createCubeTextureFromImageWithoutIrradiance(Image&& srcImage, const std::string& srcImageName, + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, compress, target, false, abortProcessing); +} + +static float denormalize(float value, const float minValue) { + return value < minValue ? 0.0f : value; +} + +static uint32 packR11G11B10F(const glm::vec3& color) { + // Denormalize else unpacking gives high and incorrect values + // See https://www.khronos.org/opengl/wiki/Small_Float_Formats for this min value + static const auto minValue = 6.10e-5f; + static const auto maxValue = 6.50e4f; + glm::vec3 ucolor; + ucolor.r = denormalize(color.r, minValue); + ucolor.g = denormalize(color.g, minValue); + ucolor.b = denormalize(color.b, minValue); + ucolor.r = std::min(ucolor.r, maxValue); + ucolor.g = std::min(ucolor.g, maxValue); + ucolor.b = std::min(ucolor.b, maxValue); + return glm::packF2x11_1x10(ucolor); +} + +static std::function getHDRPackingFunction(const gpu::Element& format) { + if (format == gpu::Element::COLOR_RGB9E5) { + return glm::packF3x9_E1x5; + } else if (format == gpu::Element::COLOR_R11G11B10) { + return packR11G11B10F; + } else { + qCWarning(imagelogging) << "Unknown handler format"; + Q_UNREACHABLE(); + return nullptr; + } +} + +std::function getHDRPackingFunction() { + return getHDRPackingFunction(GPU_CUBEMAP_HDR_FORMAT); +} + +std::function getHDRUnpackingFunction() { + if (GPU_CUBEMAP_HDR_FORMAT == gpu::Element::COLOR_RGB9E5) { + return glm::unpackF3x9_E1x5; + } else if (GPU_CUBEMAP_HDR_FORMAT == gpu::Element::COLOR_R11G11B10) { + return glm::unpackF2x11_1x10; + } else { + qCWarning(imagelogging) << "Unknown HDR encoding format in Image"; + Q_UNREACHABLE(); + return nullptr; + } +} + +Image processRawImageData(QIODevice& content, const std::string& filename) { + // Help the Image loader by extracting the image file format from the url filename ext. + // Some tga are not created properly without it. + auto filenameExtension = filename.substr(filename.find_last_of('.') + 1); + // Remove possible query part of the filename if it comes from an URL + filenameExtension = filenameExtension.substr(0, filenameExtension.find_first_of('?')); + if (!content.isReadable()) { + content.open(QIODevice::ReadOnly); + } else { + content.reset(); + } + + if (filenameExtension == "tga") { + Image image = image::readTGA(content); + if (!image.isNull()) { + return image; + } + content.reset(); + } +#if !defined(Q_OS_ANDROID) + else if (filenameExtension == "exr") { + Image image = image::readOpenEXR(content, filename); + if (!image.isNull()) { + return image; + } + } +#endif + + QImageReader imageReader(&content, filenameExtension.c_str()); + + if (imageReader.canRead()) { + return Image(imageReader.read()); + } else { + // Extension could be incorrect, try to detect the format from the content + QImageReader newImageReader; + newImageReader.setDecideFormatFromContent(true); + content.reset(); + newImageReader.setDevice(&content); + + if (newImageReader.canRead()) { + return Image(newImageReader.read()); + } + } + + return Image(); +} + +void mapToRedChannel(Image& image, ColorChannel sourceChannel) { + // Change format of image so we know exactly how to process it + if (image.getFormat() != Image::Format_ARGB32) { + image = image.getConvertedToFormat(Image::Format_ARGB32); + } + + for (glm::uint32 i = 0; i < image.getHeight(); i++) { + QRgb* pixel = reinterpret_cast(image.editScanLine(i)); + // Past end pointer + QRgb* lineEnd = pixel + image.getWidth(); + + // Transfer channel data from source to target + for (; pixel < lineEnd; pixel++) { + int colorValue; + switch (sourceChannel) { + case ColorChannel::RED: + colorValue = qRed(*pixel); + break; + case ColorChannel::GREEN: + colorValue = qGreen(*pixel); + break; + case ColorChannel::BLUE: + colorValue = qBlue(*pixel); + break; + case ColorChannel::ALPHA: + colorValue = qAlpha(*pixel); + break; + default: + colorValue = qRed(*pixel); + break; + } + + // Dump the color in the red channel, ignore the rest + *pixel = qRgba(colorValue, 0, 0, 255); + } + } +} + +gpu::TexturePointer processImage(std::shared_ptr content, const std::string& filename, ColorChannel sourceChannel, + int maxNumPixels, TextureUsage::Type textureType, + bool compress, BackendTarget target, const std::atomic& abortProcessing) { + + Image image = processRawImageData(*content.get(), filename); + // Texture content can take up a lot of memory. Here we release our ownership of that content + // in case it can be released. + content.reset(); + + int imageWidth = image.getWidth(); + int imageHeight = image.getHeight(); + + // Validate that the image loaded + if (imageWidth == 0 || imageHeight == 0 || image.getFormat() == Image::Format_Invalid) { + QString reason(image.getFormat() == Image::Format_Invalid ? "(Invalid Format)" : "(Size is invalid)"); + qCWarning(imagelogging) << "Failed to load:" << qPrintable(reason); + return nullptr; + } + + // Validate the image is less than _maxNumPixels, and downscale if necessary + if (imageWidth * imageHeight > maxNumPixels) { + float scaleFactor = sqrtf(maxNumPixels / (float)(imageWidth * imageHeight)); + int originalWidth = imageWidth; + int originalHeight = imageHeight; + imageWidth = (int)(scaleFactor * (float)imageWidth + 0.5f); + imageHeight = (int)(scaleFactor * (float)imageHeight + 0.5f); + image = image.getScaled(glm::uvec2(imageWidth, imageHeight), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + qCDebug(imagelogging).nospace() << "Downscaled " << " (" << + QSize(originalWidth, originalHeight) << " to " << + QSize(imageWidth, imageHeight) << ")"; + } + + // Re-map to image with single red channel texture if requested + if (sourceChannel != ColorChannel::NONE) { + mapToRedChannel(image, sourceChannel); + } + + auto loader = TextureUsage::getTextureLoaderForType(textureType); + auto texture = loader(std::move(image), filename, compress, target, abortProcessing); + + return texture; +} + +Image processSourceImage(Image&& srcImage, bool cubemap, BackendTarget target) { + PROFILE_RANGE(resource_parse, "processSourceImage"); + + // Take a local copy to force move construction + // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter + Image localCopy = std::move(srcImage); + + const glm::uvec2 srcImageSize = localCopy.getSize(); + glm::uvec2 targetSize = srcImageSize; + + const auto maxTextureSize = target == BackendTarget::GLES32 ? MAX_TEXTURE_SIZE_GLES : MAX_TEXTURE_SIZE_GL; + while (glm::any(glm::greaterThan(targetSize, maxTextureSize))) { + targetSize /= 2; + } + if (targetSize != srcImageSize) { + ++DECIMATED_TEXTURE_COUNT; + } + + if (!cubemap) { + auto rectifiedSize = rectifySize(targetSize); + if (rectifiedSize != targetSize) { + ++RECTIFIED_TEXTURE_COUNT; + targetSize = rectifiedSize; + } + } + + if (DEV_DECIMATE_TEXTURES && glm::all(glm::greaterThanEqual(targetSize / SPARSE_PAGE_SIZE, glm::uvec2(2)))) { + targetSize /= 2; + } + + if (targetSize != srcImageSize) { + PROFILE_RANGE(resource_parse, "processSourceImage Rectify"); + qCDebug(imagelogging) << "Resizing texture from " << srcImageSize.x << "x" << srcImageSize.y << " to " << targetSize.x << "x" << targetSize.y; + return localCopy.getScaled(targetSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + } + + return localCopy; +} + +#if defined(NVTT_API) +struct OutputHandler : public nvtt::OutputHandler { + OutputHandler(gpu::Texture* texture, int face) : _texture(texture), _face(face) {} + + virtual void beginImage(int size, int width, int height, int depth, int face, int miplevel) override { + _size = size; + _miplevel = miplevel; + + _data = static_cast(malloc(size)); + _current = _data; + } + + virtual bool writeData(const void* data, int size) override { + assert(_current + size <= _data + _size); + memcpy(_current, data, size); + _current += size; + return true; + } + + virtual void endImage() override { + if (_face >= 0) { + _texture->assignStoredMipFace(_miplevel, _face, _size, static_cast(_data)); + } else { + _texture->assignStoredMip(_miplevel, _size, static_cast(_data)); + } + free(_data); + _data = nullptr; + } + + gpu::Byte* _data{ nullptr }; + gpu::Byte* _current{ nullptr }; + gpu::Texture* _texture{ nullptr }; + int _miplevel = 0; + int _size = 0; + int _face = -1; +}; + +struct PackedFloatOutputHandler : public OutputHandler { + PackedFloatOutputHandler(gpu::Texture* texture, int face, gpu::Element format) : OutputHandler(texture, face) { + _packFunc = getHDRPackingFunction(format); + } + + virtual void beginImage(int size, int width, int height, int depth, int face, int miplevel) override { + // Divide by 3 because we will compress from 3*floats to 1 uint32 + OutputHandler::beginImage(size / 3, width, height, depth, face, miplevel); + } + virtual bool writeData(const void* data, int size) override { + // Expecting to write multiple of floats + if (_packFunc) { + assert((size % sizeof(float)) == 0); + auto floatCount = size / sizeof(float); + const float* floatBegin = (const float*)data; + const float* floatEnd = floatBegin + floatCount; + + while (floatBegin < floatEnd) { + _pixel[_coordIndex] = *floatBegin; + floatBegin++; + _coordIndex++; + if (_coordIndex == 3) { + uint32 packedRGB = _packFunc(_pixel); + _coordIndex = 0; + OutputHandler::writeData(&packedRGB, sizeof(packedRGB)); + } + } + return true; + } + return false; + } + + std::function _packFunc; + glm::vec3 _pixel; + int _coordIndex{ 0 }; +}; + +struct MyErrorHandler : public nvtt::ErrorHandler { + virtual void error(nvtt::Error e) override { + qCWarning(imagelogging) << "Texture compression error:" << nvtt::errorString(e); + } +}; + +class SequentialTaskDispatcher : public nvtt::TaskDispatcher { +public: + SequentialTaskDispatcher(const std::atomic& abortProcessing) : _abortProcessing(abortProcessing) {}; + + const std::atomic& _abortProcessing; + + virtual void dispatch(nvtt::Task* task, void* context, int count) override { + for (int i = 0; i < count; i++) { + if (!_abortProcessing.load()) { + task(context, i); + } else { + break; + } + } + } +}; + +void generateHDRMips(gpu::Texture* texture, Image&& image, BackendTarget target, const std::atomic& abortProcessing, int face) { + // Take a local copy to force move construction + // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter + Image localCopy = std::move(image); + + assert(localCopy.getFormat() == Image::Format_PACKED_FLOAT); + + const int width = localCopy.getWidth(), height = localCopy.getHeight(); + std::vector data; + std::vector::iterator dataIt; + auto mipFormat = texture->getStoredMipFormat(); + std::function unpackFunc = getHDRUnpackingFunction(); + + nvtt::InputFormat inputFormat = nvtt::InputFormat_RGBA_32F; + nvtt::WrapMode wrapMode = nvtt::WrapMode_Mirror; + nvtt::AlphaMode alphaMode = nvtt::AlphaMode_None; + + nvtt::CompressionOptions compressionOptions; + compressionOptions.setQuality(nvtt::Quality_Production); + + // TODO: gles: generate ETC mips instead? + if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_HDR_RGB) { + compressionOptions.setFormat(nvtt::Format_BC6); + } else if (mipFormat == gpu::Element::COLOR_RGB9E5) { + compressionOptions.setFormat(nvtt::Format_RGB); + compressionOptions.setPixelType(nvtt::PixelType_Float); + compressionOptions.setPixelFormat(32, 32, 32, 0); + } else if (mipFormat == gpu::Element::COLOR_R11G11B10) { + compressionOptions.setFormat(nvtt::Format_RGB); + compressionOptions.setPixelType(nvtt::PixelType_Float); + compressionOptions.setPixelFormat(32, 32, 32, 0); + } else { + qCWarning(imagelogging) << "Unknown mip format"; + Q_UNREACHABLE(); + return; + } + + data.resize(width * height); + dataIt = data.begin(); + for (auto lineNb = 0; lineNb < height; lineNb++) { + const uint32* srcPixelIt = reinterpret_cast(localCopy.getScanLine(lineNb)); + const uint32* srcPixelEnd = srcPixelIt + width; + + while (srcPixelIt < srcPixelEnd) { + *dataIt = glm::vec4(unpackFunc(*srcPixelIt), 1.0f); + ++srcPixelIt; + ++dataIt; + } + } + assert(dataIt == data.end()); + + // We're done with the localCopy, free up the memory to avoid bloating the heap + localCopy = Image(); // Image doesn't have a clear function, so override it with an empty one. + + nvtt::OutputOptions outputOptions; + outputOptions.setOutputHeader(false); + std::unique_ptr outputHandler; + MyErrorHandler errorHandler; + outputOptions.setErrorHandler(&errorHandler); + nvtt::Context context; + int mipLevel = 0; + + if (mipFormat == gpu::Element::COLOR_RGB9E5 || mipFormat == gpu::Element::COLOR_R11G11B10) { + // Don't use NVTT (at least version 2.1) as it outputs wrong RGB9E5 and R11G11B10F values from floats + outputHandler.reset(new PackedFloatOutputHandler(texture, face, mipFormat)); + } else { + outputHandler.reset(new OutputHandler(texture, face)); + } + + outputOptions.setOutputHandler(outputHandler.get()); + + nvtt::Surface surface; + surface.setImage(inputFormat, width, height, 1, &(*data.begin())); + surface.setAlphaMode(alphaMode); + surface.setWrapMode(wrapMode); + + SequentialTaskDispatcher dispatcher(abortProcessing); + nvtt::Compressor compressor; + context.setTaskDispatcher(&dispatcher); + + context.compress(surface, face, mipLevel++, compressionOptions, outputOptions); + while (surface.canMakeNextMipmap() && !abortProcessing.load()) { + surface.buildNextMipmap(nvtt::MipmapFilter_Box); + context.compress(surface, face, mipLevel++, compressionOptions, outputOptions); + } +} + +void generateLDRMips(gpu::Texture* texture, Image&& image, BackendTarget target, const std::atomic& abortProcessing, int face) { + // Take a local copy to force move construction + // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter + Image localCopy = std::move(image); + + assert(localCopy.getFormat() != Image::Format_PACKED_FLOAT); + if (localCopy.getFormat() != Image::Format_ARGB32) { + localCopy = localCopy.getConvertedToFormat(Image::Format_ARGB32); + } + + const int width = localCopy.getWidth(), height = localCopy.getHeight(); + auto mipFormat = texture->getStoredMipFormat(); + + if (target != BackendTarget::GLES32) { + const void* data = static_cast(localCopy.getBits()); + nvtt::TextureType textureType = nvtt::TextureType_2D; + nvtt::InputFormat inputFormat = nvtt::InputFormat_BGRA_8UB; + nvtt::WrapMode wrapMode = nvtt::WrapMode_Mirror; + nvtt::RoundMode roundMode = nvtt::RoundMode_None; + nvtt::AlphaMode alphaMode = nvtt::AlphaMode_None; + + float inputGamma = 2.2f; + float outputGamma = 2.2f; + + nvtt::InputOptions inputOptions; + inputOptions.setTextureLayout(textureType, width, height); + + inputOptions.setMipmapData(data, width, height); + // setMipmapData copies the memory, so free up the memory afterward to avoid bloating the heap + data = nullptr; + localCopy = Image(); // Image doesn't have a clear function, so override it with an empty one. + + inputOptions.setFormat(inputFormat); + inputOptions.setGamma(inputGamma, outputGamma); + inputOptions.setAlphaMode(alphaMode); + inputOptions.setWrapMode(wrapMode); + inputOptions.setRoundMode(roundMode); + + inputOptions.setMipmapGeneration(true); + inputOptions.setMipmapFilter(nvtt::MipmapFilter_Box); + + nvtt::CompressionOptions compressionOptions; + compressionOptions.setQuality(nvtt::Quality_Production); + + if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGB) { + compressionOptions.setFormat(nvtt::Format_BC1); + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGBA_MASK) { + alphaMode = nvtt::AlphaMode_Transparency; + compressionOptions.setFormat(nvtt::Format_BC1a); + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGBA) { + alphaMode = nvtt::AlphaMode_Transparency; + compressionOptions.setFormat(nvtt::Format_BC3); + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_RED) { + compressionOptions.setFormat(nvtt::Format_BC4); + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_XY) { + compressionOptions.setFormat(nvtt::Format_BC5); + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGBA_HIGH) { + alphaMode = nvtt::AlphaMode_Transparency; + compressionOptions.setFormat(nvtt::Format_BC7); + } else if (mipFormat == gpu::Element::COLOR_RGBA_32) { + compressionOptions.setFormat(nvtt::Format_RGBA); + compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); + compressionOptions.setPitchAlignment(4); + compressionOptions.setPixelFormat(32, + 0x000000FF, + 0x0000FF00, + 0x00FF0000, + 0xFF000000); + inputGamma = 1.0f; + outputGamma = 1.0f; + } else if (mipFormat == gpu::Element::COLOR_BGRA_32) { + compressionOptions.setFormat(nvtt::Format_RGBA); + compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); + compressionOptions.setPitchAlignment(4); + compressionOptions.setPixelFormat(32, + 0x00FF0000, + 0x0000FF00, + 0x000000FF, + 0xFF000000); + inputGamma = 1.0f; + outputGamma = 1.0f; + } else if (mipFormat == gpu::Element::COLOR_SRGBA_32) { + compressionOptions.setFormat(nvtt::Format_RGBA); + compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); + compressionOptions.setPitchAlignment(4); + compressionOptions.setPixelFormat(32, + 0x000000FF, + 0x0000FF00, + 0x00FF0000, + 0xFF000000); + } else if (mipFormat == gpu::Element::COLOR_SBGRA_32) { + compressionOptions.setFormat(nvtt::Format_RGBA); + compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); + compressionOptions.setPitchAlignment(4); + compressionOptions.setPixelFormat(32, + 0x00FF0000, + 0x0000FF00, + 0x000000FF, + 0xFF000000); + } else if (mipFormat == gpu::Element::COLOR_R_8) { + compressionOptions.setFormat(nvtt::Format_RGB); + compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); + compressionOptions.setPitchAlignment(4); + compressionOptions.setPixelFormat(8, 0, 0, 0); + } else if (mipFormat == gpu::Element::VEC2NU8_XY) { + inputOptions.setNormalMap(true); + compressionOptions.setFormat(nvtt::Format_RGBA); + compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); + compressionOptions.setPitchAlignment(4); + compressionOptions.setPixelFormat(8, 8, 0, 0); + } else { + qCWarning(imagelogging) << "Unknown mip format"; + Q_UNREACHABLE(); + return; + } + + nvtt::OutputOptions outputOptions; + outputOptions.setOutputHeader(false); + OutputHandler outputHandler(texture, face); + outputOptions.setOutputHandler(&outputHandler); + MyErrorHandler errorHandler; + outputOptions.setErrorHandler(&errorHandler); + + SequentialTaskDispatcher dispatcher(abortProcessing); + nvtt::Compressor compressor; + compressor.setTaskDispatcher(&dispatcher); + compressor.process(inputOptions, compressionOptions, outputOptions); + } else { + int numMips = 1 + (int)log2(std::max(width, height)); + Etc::RawImage *mipMaps = new Etc::RawImage[numMips]; + Etc::Image::Format etcFormat = Etc::Image::Format::DEFAULT; + + if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_RGB) { + etcFormat = Etc::Image::Format::RGB8; + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_SRGB) { + etcFormat = Etc::Image::Format::SRGB8; + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA) { + etcFormat = Etc::Image::Format::RGB8A1; + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA) { + etcFormat = Etc::Image::Format::SRGB8A1; + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_RGBA) { + etcFormat = Etc::Image::Format::RGBA8; + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_ETC2_SRGBA) { + etcFormat = Etc::Image::Format::SRGBA8; + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_EAC_RED) { + etcFormat = Etc::Image::Format::R11; + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_EAC_RED_SIGNED) { + etcFormat = Etc::Image::Format::SIGNED_R11; + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_EAC_XY) { + etcFormat = Etc::Image::Format::RG11; + } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_EAC_XY_SIGNED) { + etcFormat = Etc::Image::Format::SIGNED_RG11; + } else { + qCWarning(imagelogging) << "Unknown mip format"; + Q_UNREACHABLE(); + return; + } + + const Etc::ErrorMetric errorMetric = Etc::ErrorMetric::RGBA; + const float effort = 1.0f; + const int numEncodeThreads = 4; + int encodingTime; + const float MAX_COLOR = 255.0f; + + std::vector floatData; + floatData.resize(width * height); + for (int y = 0; y < height; y++) { + QRgb *line = (QRgb *)localCopy.editScanLine(y); + for (int x = 0; x < width; x++) { + QRgb &pixel = line[x]; + floatData[x + y * width] = vec4(qRed(pixel), qGreen(pixel), qBlue(pixel), qAlpha(pixel)) / MAX_COLOR; + } + } + + // free up the memory afterward to avoid bloating the heap + localCopy = Image(); // Image doesn't have a clear function, so override it with an empty one. + + Etc::EncodeMipmaps( + (float *)floatData.data(), width, height, + etcFormat, errorMetric, effort, + numEncodeThreads, numEncodeThreads, + numMips, Etc::FILTER_WRAP_NONE, + mipMaps, &encodingTime + ); + + for (int i = 0; i < numMips; i++) { + if (mipMaps[i].paucEncodingBits.get()) { + if (face >= 0) { + texture->assignStoredMipFace(i, face, mipMaps[i].uiEncodingBitsBytes, static_cast(mipMaps[i].paucEncodingBits.get())); + } else { + texture->assignStoredMip(i, mipMaps[i].uiEncodingBitsBytes, static_cast(mipMaps[i].paucEncodingBits.get())); + } + } + } + + delete[] mipMaps; + } +} + +#endif + +void generateMips(gpu::Texture* texture, Image&& image, BackendTarget target, const std::atomic& abortProcessing = false, int face = -1) { +#if CPU_MIPMAPS + PROFILE_RANGE(resource_parse, "generateMips"); + + if (target == BackendTarget::GLES32) { + generateLDRMips(texture, std::move(image), target, abortProcessing, face); + } else { + if (image.getFormat() == Image::Format_PACKED_FLOAT) { + generateHDRMips(texture, std::move(image), target, abortProcessing, face); + } else { + generateLDRMips(texture, std::move(image), target, abortProcessing, face); + } + } +#else + texture->setAutoGenerateMips(true); +#endif +} + +void processTextureAlpha(const Image& srcImage, bool& validAlpha, bool& alphaAsMask) { + PROFILE_RANGE(resource_parse, "processTextureAlpha"); + validAlpha = false; + alphaAsMask = true; + const uint8 OPAQUE_ALPHA = 255; + const uint8 TRANSPARENT_ALPHA = 0; + + // Figure out if we can use a mask for alpha or not + int numOpaques = 0; + int numTranslucents = 0; + const int NUM_PIXELS = srcImage.getWidth() * srcImage.getHeight(); + const int MAX_TRANSLUCENT_PIXELS_FOR_ALPHAMASK = (int)(0.05f * (float)(NUM_PIXELS)); + const QRgb* data = reinterpret_cast(srcImage.getBits()); + for (int i = 0; i < NUM_PIXELS; ++i) { + auto alpha = qAlpha(data[i]); + if (alpha == OPAQUE_ALPHA) { + numOpaques++; + } else if (alpha != TRANSPARENT_ALPHA) { + if (++numTranslucents > MAX_TRANSLUCENT_PIXELS_FOR_ALPHAMASK) { + alphaAsMask = false; + break; + } + } + } + validAlpha = (numOpaques != NUM_PIXELS); +} + +gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(Image&& srcImage, const std::string& srcImageName, bool compress, + BackendTarget target, bool isStrict, const std::atomic& abortProcessing) { + PROFILE_RANGE(resource_parse, "process2DTextureColorFromImage"); + Image image = processSourceImage(std::move(srcImage), false, target); + + bool validAlpha = image.hasAlphaChannel(); + bool alphaAsMask = false; + + if (image.getFormat() != Image::Format_ARGB32) { + image = image.getConvertedToFormat(Image::Format_ARGB32); + } + + if (validAlpha) { + processTextureAlpha(image, validAlpha, alphaAsMask); + } + + gpu::TexturePointer theTexture = nullptr; + + if ((image.getWidth() > 0) && (image.getHeight() > 0)) { + gpu::Element formatMip; + gpu::Element formatGPU; + if (compress) { + if (target == BackendTarget::GLES32) { + // GLES does not support GL_BGRA + formatGPU = gpu::Element::COLOR_COMPRESSED_ETC2_SRGBA; + formatMip = formatGPU; + } else { + if (validAlpha) { + // NOTE: This disables BC1a compression because it was producing odd artifacts on text textures + // for the tutorial. Instead we use BC3 (which is larger) but doesn't produce the same artifacts). + formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_SRGBA; + } else { + formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_SRGB; + } + formatMip = formatGPU; + } + } else { + if (target == BackendTarget::GLES32) { + } else { + formatGPU = gpu::Element::COLOR_SRGBA_32; + formatMip = gpu::Element::COLOR_SBGRA_32; + } + } + + if (isStrict) { + theTexture = gpu::Texture::createStrict(formatGPU, image.getWidth(), image.getHeight(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)); + } else { + theTexture = gpu::Texture::create2D(formatGPU, image.getWidth(), image.getHeight(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)); + } + theTexture->setSource(srcImageName); + auto usage = gpu::Texture::Usage::Builder().withColor(); + if (validAlpha) { + usage.withAlpha(); + if (alphaAsMask) { + usage.withAlphaMask(); + } + } + theTexture->setUsage(usage.build()); + theTexture->setStoredMipFormat(formatMip); + theTexture->assignStoredMip(0, image.getByteCount(), image.getBits()); + generateMips(theTexture.get(), std::move(image), target, abortProcessing); + } + + return theTexture; +} + +int clampPixelCoordinate(int coordinate, int maxCoordinate) { + return coordinate - ((int)(coordinate < 0) * coordinate) + ((int)(coordinate > maxCoordinate) * (maxCoordinate - coordinate)); +} + +const int RGBA_MAX = 255; + +// transform -1 - 1 to 0 - 255 (from sobel value to rgb) +double mapComponent(double sobelValue) { + const double factor = RGBA_MAX / 2.0; + return (sobelValue + 1.0) * factor; +} + +Image processBumpMap(Image&& image) { + // Take a local copy to force move construction + // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter + Image localCopy = std::move(image); + + if (localCopy.getFormat() != Image::Format_Grayscale8) { + localCopy = localCopy.getConvertedToFormat(Image::Format_Grayscale8); + } + + // PR 5540 by AlessandroSigna integrated here as a specialized TextureLoader for bumpmaps + // The conversion is done using the Sobel Filter to calculate the derivatives from the grayscale image + const double pStrength = 2.0; + int width = localCopy.getWidth(); + int height = localCopy.getHeight(); + + Image result(width, height, Image::Format_ARGB32); + + for (int i = 0; i < width; i++) { + const int iNextClamped = clampPixelCoordinate(i + 1, width - 1); + const int iPrevClamped = clampPixelCoordinate(i - 1, width - 1); + + for (int j = 0; j < height; j++) { + const int jNextClamped = clampPixelCoordinate(j + 1, height - 1); + const int jPrevClamped = clampPixelCoordinate(j - 1, height - 1); + + // surrounding pixels + const QRgb topLeft = localCopy.getPixel(iPrevClamped, jPrevClamped); + const QRgb top = localCopy.getPixel(iPrevClamped, j); + const QRgb topRight = localCopy.getPixel(iPrevClamped, jNextClamped); + const QRgb right = localCopy.getPixel(i, jNextClamped); + const QRgb bottomRight = localCopy.getPixel(iNextClamped, jNextClamped); + const QRgb bottom = localCopy.getPixel(iNextClamped, j); + const QRgb bottomLeft = localCopy.getPixel(iNextClamped, jPrevClamped); + const QRgb left = localCopy.getPixel(i, jPrevClamped); + + // take their gray intensities + // since it's a grayscale image, the value of each component RGB is the same + const double tl = qRed(topLeft); + const double t = qRed(top); + const double tr = qRed(topRight); + const double r = qRed(right); + const double br = qRed(bottomRight); + const double b = qRed(bottom); + const double bl = qRed(bottomLeft); + const double l = qRed(left); + + // apply the sobel filter + const double dX = (tr + pStrength * r + br) - (tl + pStrength * l + bl); + const double dY = (bl + pStrength * b + br) - (tl + pStrength * t + tr); + const double dZ = RGBA_MAX / pStrength; + + glm::vec3 v(dX, dY, dZ); + glm::normalize(v); + + // convert to rgb from the value obtained computing the filter + QRgb qRgbValue = qRgba(mapComponent(v.z), mapComponent(v.y), mapComponent(v.x), 1.0); + result.setPixel(i, j, qRgbValue); + } + } + + return result; +} +gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(Image&& srcImage, const std::string& srcImageName, + bool compress, BackendTarget target, bool isBumpMap, + const std::atomic& abortProcessing) { + PROFILE_RANGE(resource_parse, "process2DTextureNormalMapFromImage"); + Image image = processSourceImage(std::move(srcImage), false, target); + + if (isBumpMap) { + image = processBumpMap(std::move(image)); + } + + // Make sure the normal map source image is ARGB32 + if (image.getFormat() != Image::Format_ARGB32) { + image = image.getConvertedToFormat(Image::Format_ARGB32); + } + + gpu::TexturePointer theTexture = nullptr; + if ((image.getWidth() > 0) && (image.getHeight() > 0)) { + gpu::Element formatMip; + gpu::Element formatGPU; + if (compress) { + if (target == BackendTarget::GLES32) { + formatGPU = gpu::Element::COLOR_COMPRESSED_EAC_XY; + } else { + formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_XY; + } + } else { + formatGPU = gpu::Element::VEC2NU8_XY; + } + formatMip = formatGPU; + + theTexture = gpu::Texture::create2D(formatGPU, image.getWidth(), image.getHeight(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)); + theTexture->setSource(srcImageName); + theTexture->setStoredMipFormat(formatMip); + theTexture->assignStoredMip(0, image.getByteCount(), image.getBits()); + generateMips(theTexture.get(), std::move(image), target, abortProcessing); + } + + return theTexture; +} + +gpu::TexturePointer TextureUsage::process2DTextureGrayscaleFromImage(Image&& srcImage, const std::string& srcImageName, + bool compress, BackendTarget target, bool isInvertedPixels, + const std::atomic& abortProcessing) { + PROFILE_RANGE(resource_parse, "process2DTextureGrayscaleFromImage"); + Image image = processSourceImage(std::move(srcImage), false, target); + + if (image.getFormat() != Image::Format_ARGB32) { + image = image.getConvertedToFormat(Image::Format_ARGB32); + } + + if (isInvertedPixels) { + // Gloss turned into Rough + image.invertPixels(); + } + + gpu::TexturePointer theTexture = nullptr; + if ((image.getWidth() > 0) && (image.getHeight() > 0)) { + gpu::Element formatMip; + gpu::Element formatGPU; + if (compress) { + if (target == BackendTarget::GLES32) { + formatGPU = gpu::Element::COLOR_COMPRESSED_EAC_RED; + } else { + formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_RED; + } + } else { + formatGPU = gpu::Element::COLOR_R_8; + } + formatMip = formatGPU; + + theTexture = gpu::Texture::create2D(formatGPU, image.getWidth(), image.getHeight(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)); + theTexture->setSource(srcImageName); + theTexture->setStoredMipFormat(formatMip); + theTexture->assignStoredMip(0, image.getByteCount(), image.getBits()); + generateMips(theTexture.get(), std::move(image), target, abortProcessing); + } + + return theTexture; +} + +class CubeLayout { +public: + + enum SourceProjection { + FLAT = 0, + EQUIRECTANGULAR, + }; + int _type = FLAT; + int _widthRatio = 1; + int _heightRatio = 1; + + class Face { + public: + int _x = 0; + int _y = 0; + bool _horizontalMirror = false; + bool _verticalMirror = false; + + Face() {} + Face(int x, int y, bool horizontalMirror, bool verticalMirror) : _x(x), _y(y), _horizontalMirror(horizontalMirror), _verticalMirror(verticalMirror) {} + }; + + Face _faceXPos; + Face _faceXNeg; + Face _faceYPos; + Face _faceYNeg; + Face _faceZPos; + Face _faceZNeg; + + CubeLayout(int wr, int hr, Face fXP, Face fXN, Face fYP, Face fYN, Face fZP, Face fZN) : + _type(FLAT), + _widthRatio(wr), + _heightRatio(hr), + _faceXPos(fXP), + _faceXNeg(fXN), + _faceYPos(fYP), + _faceYNeg(fYN), + _faceZPos(fZP), + _faceZNeg(fZN) {} + + CubeLayout(int wr, int hr) : + _type(EQUIRECTANGULAR), + _widthRatio(wr), + _heightRatio(hr) {} + + + static const CubeLayout CUBEMAP_LAYOUTS[]; + static const int NUM_CUBEMAP_LAYOUTS; + + static int findLayout(int width, int height) { + // Find the layout of the cubemap in the 2D image + int foundLayout = -1; + for (int i = 0; i < NUM_CUBEMAP_LAYOUTS; i++) { + if ((height * CUBEMAP_LAYOUTS[i]._widthRatio) == (width * CUBEMAP_LAYOUTS[i]._heightRatio)) { + foundLayout = i; + break; + } + } + return foundLayout; + } + + static Image extractEquirectangularFace(const Image& source, gpu::Texture::CubeFace face, int faceWidth) { + Image image(faceWidth, faceWidth, source.getFormat()); + + glm::vec2 dstInvSize(1.0f / faceWidth); + + struct CubeToXYZ { + gpu::Texture::CubeFace _face; + CubeToXYZ(gpu::Texture::CubeFace face) : _face(face) {} + + glm::vec3 xyzFrom(const glm::vec2& uv) { + auto faceDir = glm::normalize(glm::vec3(-1.0f + 2.0f * uv.x, -1.0f + 2.0f * uv.y, 1.0f)); + + switch (_face) { + case gpu::Texture::CubeFace::CUBE_FACE_BACK_POS_Z: + return glm::vec3(-faceDir.x, faceDir.y, faceDir.z); + case gpu::Texture::CubeFace::CUBE_FACE_FRONT_NEG_Z: + return glm::vec3(faceDir.x, faceDir.y, -faceDir.z); + case gpu::Texture::CubeFace::CUBE_FACE_LEFT_NEG_X: + return glm::vec3(faceDir.z, faceDir.y, faceDir.x); + case gpu::Texture::CubeFace::CUBE_FACE_RIGHT_POS_X: + return glm::vec3(-faceDir.z, faceDir.y, -faceDir.x); + case gpu::Texture::CubeFace::CUBE_FACE_BOTTOM_NEG_Y: + return glm::vec3(-faceDir.x, -faceDir.z, faceDir.y); + case gpu::Texture::CubeFace::CUBE_FACE_TOP_POS_Y: + default: + return glm::vec3(-faceDir.x, faceDir.z, -faceDir.y); + } + } + }; + CubeToXYZ cubeToXYZ(face); + + struct RectToXYZ { + RectToXYZ() {} + + glm::vec2 uvFrom(const glm::vec3& xyz) { + auto flatDir = glm::normalize(glm::vec2(xyz.x, xyz.z)); + auto uvRad = glm::vec2(atan2(flatDir.x, flatDir.y), asin(xyz.y)); + + const float LON_TO_RECT_U = 1.0f / (glm::pi()); + const float LAT_TO_RECT_V = 2.0f / glm::pi(); + return glm::vec2(0.5f * uvRad.x * LON_TO_RECT_U + 0.5f, 0.5f * uvRad.y * LAT_TO_RECT_V + 0.5f); + } + }; + RectToXYZ rectToXYZ; + + int srcFaceHeight = source.getHeight(); + int srcFaceWidth = source.getWidth(); + + glm::vec2 dstCoord; + glm::ivec2 srcPixel; + for (int y = 0; y < faceWidth; ++y) { + QRgb* destScanLineBegin = reinterpret_cast( image.editScanLine(y) ); + QRgb* destPixelIterator = destScanLineBegin; + + dstCoord.y = 1.0f - (y + 0.5f) * dstInvSize.y; // Fill cube face images from top to bottom + for (int x = 0; x < faceWidth; ++x) { + dstCoord.x = (x + 0.5f) * dstInvSize.x; + + auto xyzDir = cubeToXYZ.xyzFrom(dstCoord); + auto srcCoord = rectToXYZ.uvFrom(xyzDir); + + srcPixel.x = floor(srcCoord.x * srcFaceWidth); + // Flip the vertical axis to Image going top to bottom + srcPixel.y = floor((1.0f - srcCoord.y) * srcFaceHeight); + + if (((uint32)srcPixel.x < (uint32)source.getWidth()) && ((uint32)srcPixel.y < (uint32)source.getHeight())) { + // We can't directly use the pixel() method because that launches a pixel color conversion to output + // a correct RGBA8 color. But in our case we may have stored HDR values encoded in a RGB30 format which + // are not convertible by Qt. The same goes with the setPixel method, by the way. + const QRgb* sourcePixelIterator = reinterpret_cast(source.getScanLine(srcPixel.y)); + sourcePixelIterator += srcPixel.x; + *destPixelIterator = *sourcePixelIterator; + + // Keep for debug, this is showing the dir as a color + // glm::u8vec4 rgba((xyzDir.x + 1.0)*0.5 * 256, (xyzDir.y + 1.0)*0.5 * 256, (xyzDir.z + 1.0)*0.5 * 256, 256); + // unsigned int val = 0xff000000 | (rgba.r) | (rgba.g << 8) | (rgba.b << 16); + // *destPixelIterator = val; + } + ++destPixelIterator; + } + } + return image; + } +}; + +const CubeLayout CubeLayout::CUBEMAP_LAYOUTS[] = { + + // Here is the expected layout for the faces in an image with the 2/1 aspect ratio: + // THis is detected as an Equirectangular projection + // WIDTH + // <---------------------------> + // ^ +------+------+------+------+ + // H | | | | | + // E | | | | | + // I | | | | | + // G +------+------+------+------+ + // H | | | | | + // T | | | | | + // | | | | | | + // v +------+------+------+------+ + // + // FaceWidth = width = height / 6 + { 2, 1 }, + + // Here is the expected layout for the faces in an image with the 1/6 aspect ratio: + // + // WIDTH + // <------> + // ^ +------+ + // | | | + // | | +X | + // | | | + // H +------+ + // E | | + // I | -X | + // G | | + // H +------+ + // T | | + // | | +Y | + // | | | + // | +------+ + // | | | + // | | -Y | + // | | | + // H +------+ + // E | | + // I | +Z | + // G | | + // H +------+ + // T | | + // | | -Z | + // | | | + // V +------+ + // + // FaceWidth = width = height / 6 + { 1, 6, + { 0, 0, true, false }, + { 0, 1, true, false }, + { 0, 2, false, true }, + { 0, 3, false, true }, + { 0, 4, true, false }, + { 0, 5, true, false } + }, + + // Here is the expected layout for the faces in an image with the 3/4 aspect ratio: + // + // <-----------WIDTH-----------> + // ^ +------+------+------+------+ + // | | | | | | + // | | | +Y | | | + // | | | | | | + // H +------+------+------+------+ + // E | | | | | + // I | -X | -Z | +X | +Z | + // G | | | | | + // H +------+------+------+------+ + // T | | | | | + // | | | -Y | | | + // | | | | | | + // V +------+------+------+------+ + // + // FaceWidth = width / 4 = height / 3 + { 4, 3, + { 2, 1, true, false }, + { 0, 1, true, false }, + { 1, 0, false, true }, + { 1, 2, false, true }, + { 3, 1, true, false }, + { 1, 1, true, false } + }, + + // Here is the expected layout for the faces in an image with the 4/3 aspect ratio: + // + // <-------WIDTH--------> + // ^ +------+------+------+ + // | | | | | + // | | | +Y | | + // | | | | | + // H +------+------+------+ + // E | | | | + // I | -X | -Z | +X | + // G | | | | + // H +------+------+------+ + // T | | | | + // | | | -Y | | + // | | | | | + // | +------+------+------+ + // | | | | | + // | | | +Z! | | <+Z is upside down! + // | | | | | + // V +------+------+------+ + // + // FaceWidth = width / 3 = height / 4 + { 3, 4, + { 2, 1, true, false }, + { 0, 1, true, false }, + { 1, 0, false, true }, + { 1, 2, false, true }, + { 1, 3, false, true }, + { 1, 1, true, false } + } +}; +const int CubeLayout::NUM_CUBEMAP_LAYOUTS = sizeof(CubeLayout::CUBEMAP_LAYOUTS) / sizeof(CubeLayout); + +//#define DEBUG_COLOR_PACKING +Image convertToLDRFormat(Image&& srcImage, Image::Format format) { + // Take a local copy to force move construction + // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter + Image localCopy = std::move(srcImage); + + Image ldrImage(localCopy.getWidth(), localCopy.getHeight(), format); + auto unpackFunc = getHDRUnpackingFunction(); + + for (glm::uint32 y = 0; y < localCopy.getHeight(); y++) { + const QRgb* srcLineIt = reinterpret_cast(localCopy.getScanLine(y)); + const QRgb* srcLineEnd = srcLineIt + localCopy.getWidth(); + uint32* ldrLineIt = reinterpret_cast(ldrImage.editScanLine(y)); + glm::vec3 color; + + while (srcLineIt < srcLineEnd) { + color = unpackFunc(*srcLineIt); + // Apply reverse gamma and clamp + color.r = std::pow(color.r, 1.0f / 2.2f); + color.g = std::pow(color.g, 1.0f / 2.2f); + color.b = std::pow(color.b, 1.0f / 2.2f); + color.r = std::min(1.0f, color.r) * 255.0f; + color.g = std::min(1.0f, color.g) * 255.0f; + color.b = std::min(1.0f, color.b) * 255.0f; + *ldrLineIt = qRgb((int)color.r, (int)color.g, (int)color.b); + + ++srcLineIt; + ++ldrLineIt; + } + } + return ldrImage; +} + +Image convertToHDRFormat(Image&& srcImage, gpu::Element format) { + // Take a local copy to force move construction + // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter + Image localCopy = std::move(srcImage); + + Image hdrImage(localCopy.getWidth(), localCopy.getHeight(), Image::Format_PACKED_FLOAT); + std::function packFunc; +#ifdef DEBUG_COLOR_PACKING + std::function unpackFunc; +#endif + + switch (format.getSemantic()) { + case gpu::R11G11B10: + packFunc = packR11G11B10F; +#ifdef DEBUG_COLOR_PACKING + unpackFunc = glm::unpackF2x11_1x10; +#endif + break; + case gpu::RGB9E5: + packFunc = glm::packF3x9_E1x5; +#ifdef DEBUG_COLOR_PACKING + unpackFunc = glm::unpackF3x9_E1x5; +#endif + break; + default: + qCWarning(imagelogging) << "Unsupported HDR format"; + Q_UNREACHABLE(); + return localCopy; + } + + localCopy = localCopy.getConvertedToFormat(Image::Format_ARGB32); + for (glm::uint32 y = 0; y < localCopy.getHeight(); y++) { + const QRgb* srcLineIt = reinterpret_cast( localCopy.getScanLine(y) ); + const QRgb* srcLineEnd = srcLineIt + localCopy.getWidth(); + uint32* hdrLineIt = reinterpret_cast( hdrImage.editScanLine(y) ); + glm::vec3 color; + + while (srcLineIt < srcLineEnd) { + color.r = qRed(*srcLineIt); + color.g = qGreen(*srcLineIt); + color.b = qBlue(*srcLineIt); + // Normalize and apply gamma + color /= 255.0f; + color.r = std::pow(color.r, 2.2f); + color.g = std::pow(color.g, 2.2f); + color.b = std::pow(color.b, 2.2f); + *hdrLineIt = packFunc(color); +#ifdef DEBUG_COLOR_PACKING + glm::vec3 ucolor = unpackFunc(*hdrLineIt); + assert(glm::distance(color, ucolor) <= 5e-2); +#endif + ++srcLineIt; + ++hdrLineIt; + } + } + return hdrImage; +} + +gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(Image&& srcImage, const std::string& srcImageName, + bool compress, BackendTarget target, bool generateIrradiance, + const std::atomic& abortProcessing) { + PROFILE_RANGE(resource_parse, "processCubeTextureColorFromImage"); + + // Take a local copy to force move construction + // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter + Image localCopy = std::move(srcImage); + + int originalWidth = localCopy.getWidth(); + int originalHeight = localCopy.getHeight(); + if ((originalWidth <= 0) && (originalHeight <= 0)) { + return nullptr; + } + + gpu::TexturePointer theTexture = nullptr; + + Image image = processSourceImage(std::move(localCopy), true, target); + auto hasTargetHDRFormat = isHDRTextureFormatEnabledForTarget(target); + if (hasTargetHDRFormat && image.getFormat() != Image::Format_PACKED_FLOAT) { + // If the target format is HDR but the image isn't, we need to convert the + // image to HDR. + image = convertToHDRFormat(std::move(image), GPU_CUBEMAP_HDR_FORMAT); + } else if (!hasTargetHDRFormat && image.getFormat() == Image::Format_PACKED_FLOAT) { + // If the target format isn't HDR (such as on GLES) but the image is, we need to + // convert the image to LDR + image = convertToLDRFormat(std::move(image), Image::Format_ARGB32); + } + + gpu::Element formatGPU = getHDRTextureFormatForTarget(target, compress); + gpu::Element formatMip = formatGPU; + + // Find the layout of the cubemap in the 2D image + // Use the original image size since processSourceImage may have altered the size / aspect ratio + int foundLayout = CubeLayout::findLayout(originalWidth, originalHeight); + + if (foundLayout < 0) { + qCDebug(imagelogging) << "Failed to find a known cube map layout from this image:" << QString(srcImageName.c_str()); + return nullptr; + } + + std::vector faces; + + // If found, go extract the faces as separate images + auto& layout = CubeLayout::CUBEMAP_LAYOUTS[foundLayout]; + if (layout._type == CubeLayout::FLAT) { + int faceWidth = image.getWidth() / layout._widthRatio; + + faces.push_back(image.getSubImage(QRect(layout._faceXPos._x * faceWidth, layout._faceXPos._y * faceWidth, faceWidth, faceWidth)).getMirrored(layout._faceXPos._horizontalMirror, layout._faceXPos._verticalMirror)); + faces.push_back(image.getSubImage(QRect(layout._faceXNeg._x * faceWidth, layout._faceXNeg._y * faceWidth, faceWidth, faceWidth)).getMirrored(layout._faceXNeg._horizontalMirror, layout._faceXNeg._verticalMirror)); + faces.push_back(image.getSubImage(QRect(layout._faceYPos._x * faceWidth, layout._faceYPos._y * faceWidth, faceWidth, faceWidth)).getMirrored(layout._faceYPos._horizontalMirror, layout._faceYPos._verticalMirror)); + faces.push_back(image.getSubImage(QRect(layout._faceYNeg._x * faceWidth, layout._faceYNeg._y * faceWidth, faceWidth, faceWidth)).getMirrored(layout._faceYNeg._horizontalMirror, layout._faceYNeg._verticalMirror)); + faces.push_back(image.getSubImage(QRect(layout._faceZPos._x * faceWidth, layout._faceZPos._y * faceWidth, faceWidth, faceWidth)).getMirrored(layout._faceZPos._horizontalMirror, layout._faceZPos._verticalMirror)); + faces.push_back(image.getSubImage(QRect(layout._faceZNeg._x * faceWidth, layout._faceZNeg._y * faceWidth, faceWidth, faceWidth)).getMirrored(layout._faceZNeg._horizontalMirror, layout._faceZNeg._verticalMirror)); + } else if (layout._type == CubeLayout::EQUIRECTANGULAR) { + // THe face width is estimated from the input image + const int EQUIRECT_FACE_RATIO_TO_WIDTH = 4; + const int EQUIRECT_MAX_FACE_WIDTH = 2048; + int faceWidth = std::min(image.getWidth() / EQUIRECT_FACE_RATIO_TO_WIDTH, EQUIRECT_MAX_FACE_WIDTH); + for (int face = gpu::Texture::CUBE_FACE_RIGHT_POS_X; face < gpu::Texture::NUM_CUBE_FACES; face++) { + Image faceImage = CubeLayout::extractEquirectangularFace(std::move(image), (gpu::Texture::CubeFace) face, faceWidth); + faces.push_back(std::move(faceImage)); + } + } + + // free up the memory afterward to avoid bloating the heap + image = Image(); // Image doesn't have a clear function, so override it with an empty one. + + // If the 6 faces have been created go on and define the true Texture + if (faces.size() == gpu::Texture::NUM_FACES_PER_TYPE[gpu::Texture::TEX_CUBE]) { + theTexture = gpu::Texture::createCube(formatGPU, faces[0].getWidth(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)); + theTexture->setSource(srcImageName); + theTexture->setStoredMipFormat(formatMip); + + // Generate irradiance while we are at it + if (generateIrradiance) { + PROFILE_RANGE(resource_parse, "generateIrradiance"); + gpu::Element irradianceFormat; + // TODO: we could locally compress the irradiance texture on Android, but we don't need to + if (target == BackendTarget::GLES32) { + irradianceFormat = gpu::Element::COLOR_SRGBA_32; + } else { + irradianceFormat = GPU_CUBEMAP_HDR_FORMAT; + } + + auto irradianceTexture = gpu::Texture::createCube(irradianceFormat, faces[0].getWidth(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)); + irradianceTexture->setSource(srcImageName); + irradianceTexture->setStoredMipFormat(irradianceFormat); + for (uint8 face = 0; face < faces.size(); ++face) { + irradianceTexture->assignStoredMipFace(0, face, faces[face].getByteCount(), faces[face].getBits()); + } + + irradianceTexture->generateIrradiance(target); + + auto irradiance = irradianceTexture->getIrradiance(); + theTexture->overrideIrradiance(irradiance); + } + + for (uint8 face = 0; face < faces.size(); ++face) { + generateMips(theTexture.get(), std::move(faces[face]), target, abortProcessing, face); + } + } + + return theTexture; +} + +} // namespace image diff --git a/libraries/image/src/image/TextureProcessing.h b/libraries/image/src/image/TextureProcessing.h new file mode 100644 index 0000000000..2fc23b136d --- /dev/null +++ b/libraries/image/src/image/TextureProcessing.h @@ -0,0 +1,92 @@ +// +// TextureProcessing.h +// image/src/TextureProcessing +// +// Created by Clement Brisset on 4/5/2017. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_image_TextureProcessing_h +#define hifi_image_TextureProcessing_h + +#include + +#include + +#include "Image.h" + +namespace image { + + std::function getHDRPackingFunction(); + std::function getHDRUnpackingFunction(); + +namespace TextureUsage { + +enum Type { + DEFAULT_TEXTURE, + STRICT_TEXTURE, + ALBEDO_TEXTURE, + NORMAL_TEXTURE, + BUMP_TEXTURE, + SPECULAR_TEXTURE, + METALLIC_TEXTURE = SPECULAR_TEXTURE, // for now spec and metallic texture are the same, converted to grey + ROUGHNESS_TEXTURE, + GLOSS_TEXTURE, + EMISSIVE_TEXTURE, + CUBE_TEXTURE, + OCCLUSION_TEXTURE, + SCATTERING_TEXTURE = OCCLUSION_TEXTURE, + LIGHTMAP_TEXTURE, + UNUSED_TEXTURE +}; + +using TextureLoader = std::function&)>; +TextureLoader getTextureLoaderForType(Type type, const QVariantMap& options = QVariantMap()); + +gpu::TexturePointer create2DTextureFromImage(Image&& image, const std::string& srcImageName, + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); +gpu::TexturePointer createStrict2DTextureFromImage(Image&& image, const std::string& srcImageName, + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); +gpu::TexturePointer createAlbedoTextureFromImage(Image&& image, const std::string& srcImageName, + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); +gpu::TexturePointer createEmissiveTextureFromImage(Image&& image, const std::string& srcImageName, + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); +gpu::TexturePointer createNormalTextureFromNormalImage(Image&& image, const std::string& srcImageName, + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); +gpu::TexturePointer createNormalTextureFromBumpImage(Image&& image, const std::string& srcImageName, + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); +gpu::TexturePointer createRoughnessTextureFromImage(Image&& image, const std::string& srcImageName, + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); +gpu::TexturePointer createRoughnessTextureFromGlossImage(Image&& image, const std::string& srcImageName, + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); +gpu::TexturePointer createMetallicTextureFromImage(Image&& image, const std::string& srcImageName, + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); +gpu::TexturePointer createCubeTextureFromImage(Image&& image, const std::string& srcImageName, + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); +gpu::TexturePointer createCubeTextureFromImageWithoutIrradiance(Image&& image, const std::string& srcImageName, + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); +gpu::TexturePointer createLightmapTextureFromImage(Image&& image, const std::string& srcImageName, + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing); +gpu::TexturePointer process2DTextureColorFromImage(Image&& srcImage, const std::string& srcImageName, bool compress, + gpu::BackendTarget target, bool isStrict, const std::atomic& abortProcessing); +gpu::TexturePointer process2DTextureNormalMapFromImage(Image&& srcImage, const std::string& srcImageName, bool compress, + gpu::BackendTarget target, bool isBumpMap, const std::atomic& abortProcessing); +gpu::TexturePointer process2DTextureGrayscaleFromImage(Image&& srcImage, const std::string& srcImageName, bool compress, + gpu::BackendTarget target, bool isInvertedPixels, const std::atomic& abortProcessing); +gpu::TexturePointer processCubeTextureColorFromImage(Image&& srcImage, const std::string& srcImageName, bool compress, + gpu::BackendTarget target, bool generateIrradiance, const std::atomic& abortProcessing); + +} // namespace TextureUsage + +const QStringList getSupportedFormats(); + +gpu::TexturePointer processImage(std::shared_ptr content, const std::string& url, ColorChannel sourceChannel, + int maxNumPixels, TextureUsage::Type textureType, + bool compress, gpu::BackendTarget target, const std::atomic& abortProcessing = false); + +} // namespace image + +#endif // hifi_image_TextureProcessing_h diff --git a/libraries/material-networking/src/material-networking/TextureCache.cpp b/libraries/material-networking/src/material-networking/TextureCache.cpp index 43f467266a..6af59930fa 100644 --- a/libraries/material-networking/src/material-networking/TextureCache.cpp +++ b/libraries/material-networking/src/material-networking/TextureCache.cpp @@ -34,7 +34,7 @@ #include #include -#include +#include #include #include diff --git a/libraries/material-networking/src/material-networking/TextureCache.h b/libraries/material-networking/src/material-networking/TextureCache.h index dcab527e4a..f8ddd77412 100644 --- a/libraries/material-networking/src/material-networking/TextureCache.h +++ b/libraries/material-networking/src/material-networking/TextureCache.h @@ -23,7 +23,7 @@ #include #include #include -#include +#include #include #include diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 780e3d8546..08abd40849 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -267,6 +267,7 @@ enum class EntityVersion : PacketVersion { ReOrderParentIDProperties, CertificateTypeProperty, DisableWebMedia, + ParticleShapeType, // Add new versions above here NUM_PACKET_TYPE, diff --git a/libraries/octree/src/OctreeEntitiesFileParser.cpp b/libraries/octree/src/OctreeEntitiesFileParser.cpp index 2f03eda286..e82201adfd 100644 --- a/libraries/octree/src/OctreeEntitiesFileParser.cpp +++ b/libraries/octree/src/OctreeEntitiesFileParser.cpp @@ -97,7 +97,7 @@ bool OctreeEntitiesFileParser::parseEntities(QVariantMap& parsedEntities) { _errorString = "Duplicate Id entries"; return false; } - + gotId = true; if (nextToken() != '"') { _errorString = "Invalid Id value"; return false; @@ -107,14 +107,21 @@ bool OctreeEntitiesFileParser::parseEntities(QVariantMap& parsedEntities) { _errorString = "Invalid Id string"; return false; } - QUuid idValue = QUuid::fromString(QLatin1String(idString.c_str()) ); - if (idValue.isNull()) { - _errorString = "Id value invalid UUID string: " + idString; - return false; - } - parsedEntities["Id"] = idValue; - gotId = true; + // some older archives may have a null string id, so + // return success without setting parsedEntities, + // which will result in a new uuid for the restored + // archive. (not parsing and using isNull as parsing + // results in null if there is a corrupt string) + + if (idString != "{00000000-0000-0000-0000-000000000000}") { + QUuid idValue = QUuid::fromString(QLatin1String(idString.c_str()) ); + if (idValue.isNull()) { + _errorString = "Id value invalid UUID string: " + idString; + return false; + } + parsedEntities["Id"] = idValue; + } } else if (key == "Version") { if (gotVersion) { _errorString = "Duplicate Version entries"; diff --git a/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp b/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp index 12a9b12adc..9809d02866 100644 --- a/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp +++ b/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.cpp @@ -245,7 +245,7 @@ void OculusMobileDisplayPlugin::updatePresentPose() { }); } -void OculusMobileDisplayPlugin::internalPresent(const gpu::FramebufferPointer& compsiteFramebuffer) { +void OculusMobileDisplayPlugin::internalPresent() { VrHandler::pollTask(); if (!vrActive()) { @@ -253,12 +253,8 @@ void OculusMobileDisplayPlugin::internalPresent(const gpu::FramebufferPointer& c return; } - GLuint sourceTexture = 0; - glm::uvec2 sourceSize; - if (compsiteFramebuffer) { - sourceTexture = getGLBackend()->getTextureID(compsiteFramebuffer->getRenderBuffer(0)); - sourceSize = { compsiteFramebuffer->getWidth(), compsiteFramebuffer->getHeight() }; - } + auto sourceTexture = getGLBackend()->getTextureID(_compositeFramebuffer->getRenderBuffer(0)); + glm::uvec2 sourceSize{ _compositeFramebuffer->getWidth(), _compositeFramebuffer->getHeight() }; VrHandler::presentFrame(sourceTexture, sourceSize, presentTracking); _presentRate.increment(); } diff --git a/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.h b/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.h index b5f7aa57b0..a98989655e 100644 --- a/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.h +++ b/libraries/oculusMobilePlugin/src/OculusMobileDisplayPlugin.h @@ -54,8 +54,8 @@ protected: void uncustomizeContext() override; void updatePresentPose() override; - void internalPresent(const gpu::FramebufferPointer&) override; - void hmdPresent(const gpu::FramebufferPointer&) override { throw std::runtime_error("Unused"); } + void internalPresent() override; + void hmdPresent() override { throw std::runtime_error("Unused"); } bool isHmdMounted() const override; bool alwaysPresent() const override { return true; } diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 4d210c96c5..6abe5c3899 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -217,9 +217,8 @@ PhysicsMotionType EntityMotionState::computePhysicsMotionType() const { } return MOTION_TYPE_DYNAMIC; } - if (_entity->isMovingRelativeToParent() || - _entity->hasActions() || - _entity->hasGrabs() || + if (_entity->hasActions() || + _entity->isMovingRelativeToParent() || _entity->hasAncestorOfType(NestableType::Avatar)) { return MOTION_TYPE_KINEMATIC; } diff --git a/libraries/plugins/src/plugins/DisplayPlugin.cpp b/libraries/plugins/src/plugins/DisplayPlugin.cpp index 71db87557c..47503e8f85 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.cpp +++ b/libraries/plugins/src/plugins/DisplayPlugin.cpp @@ -2,12 +2,6 @@ #include - -const DisplayPlugin::HUDOperator DisplayPlugin::DEFAULT_HUD_OPERATOR{ std::function() }; - -DisplayPlugin::DisplayPlugin() : _hudOperator{ DEFAULT_HUD_OPERATOR } { -} - int64_t DisplayPlugin::getPaintDelayUsecs() const { std::lock_guard lock(_paintDelayMutex); return _paintDelayTimer.isValid() ? _paintDelayTimer.nsecsElapsed() / NSECS_PER_USEC : 0; @@ -41,8 +35,8 @@ void DisplayPlugin::waitForPresent() { } } -std::function DisplayPlugin::getHUDOperator() { - HUDOperator hudOperator; +std::function DisplayPlugin::getHUDOperator() { + std::function hudOperator; { QMutexLocker locker(&_presentMutex); hudOperator = _hudOperator; @@ -54,5 +48,3 @@ glm::mat4 HmdDisplay::getEyeToHeadTransform(Eye eye) const { static const glm::mat4 xform; return xform; } - - diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index 9194fde3ac..48887d66b5 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -27,6 +27,7 @@ #include #include #include "Plugin.h" +#include "StencilMode.h" class QOpenGLFramebufferObject; @@ -121,8 +122,6 @@ class DisplayPlugin : public Plugin, public HmdDisplay { Q_OBJECT using Parent = Plugin; public: - DisplayPlugin(); - virtual int getRequiredThreadCount() const { return 0; } virtual bool isHmd() const { return false; } virtual int getHmdScreen() const { return -1; } @@ -216,14 +215,17 @@ public: void waitForPresent(); float getAveragePresentTime() { return _movingAveragePresent.average / (float)USECS_PER_MSEC; } // in msec - using HUDOperator = std::function; - virtual HUDOperator getHUDOperator() final; + std::function getHUDOperator(); static const QString& MENU_PATH(); // for updating plugin-related commands. Mimics the input plugin. virtual void pluginUpdate() = 0; + virtual StencilMode getStencilMaskMode() const { return StencilMode::NONE; } + using StencilMaskMeshOperator = std::function; + virtual StencilMaskMeshOperator getStencilMaskMeshOperator() { return nullptr; } + signals: void recommendedFramebufferSizeChanged(const QSize& size); void resetSensorsRequested(); @@ -234,8 +236,7 @@ protected: gpu::ContextPointer _gpuContext; - static const HUDOperator DEFAULT_HUD_OPERATOR; - HUDOperator _hudOperator; + std::function _hudOperator { std::function() }; MovingAverage _movingAveragePresent; diff --git a/libraries/render-utils/src/RenderCommonTask.cpp b/libraries/render-utils/src/RenderCommonTask.cpp index e021465ff3..b1a62625b2 100644 --- a/libraries/render-utils/src/RenderCommonTask.cpp +++ b/libraries/render-utils/src/RenderCommonTask.cpp @@ -126,8 +126,8 @@ void CompositeHUD::run(const RenderContextPointer& renderContext, const gpu::Fra if (inputs) { batch.setFramebuffer(inputs); } - if (renderContext->args->_hudOperator && renderContext->args->_blitFramebuffer) { - renderContext->args->_hudOperator(batch, renderContext->args->_hudTexture, renderContext->args->_blitFramebuffer, renderContext->args->_renderMode == RenderArgs::RenderMode::MIRROR_RENDER_MODE); + if (renderContext->args->_hudOperator) { + renderContext->args->_hudOperator(batch, renderContext->args->_hudTexture, renderContext->args->_renderMode == RenderArgs::RenderMode::MIRROR_RENDER_MODE); } }); #endif diff --git a/libraries/render-utils/src/StencilMaskPass.cpp b/libraries/render-utils/src/StencilMaskPass.cpp index 7217a3e5eb..6dfc1e50fd 100644 --- a/libraries/render-utils/src/StencilMaskPass.cpp +++ b/libraries/render-utils/src/StencilMaskPass.cpp @@ -19,7 +19,6 @@ using namespace render; void PrepareStencil::configure(const Config& config) { _maskMode = config.maskMode; - _forceDraw = config.forceDraw; } graphics::MeshPointer PrepareStencil::getMesh() { @@ -43,6 +42,7 @@ gpu::PipelinePointer PrepareStencil::getMeshStencilPipeline() { auto state = std::make_shared(); drawMask(*state); state->setColorWriteMask(gpu::State::WRITE_NONE); + state->setCullMode(gpu::State::CullMode::CULL_NONE); _meshStencilPipeline = gpu::Pipeline::create(program, state); } @@ -64,30 +64,37 @@ gpu::PipelinePointer PrepareStencil::getPaintStencilPipeline() { void PrepareStencil::run(const RenderContextPointer& renderContext, const gpu::FramebufferPointer& srcFramebuffer) { RenderArgs* args = renderContext->args; - // Only draw the stencil mask if in HMD mode or not forced. - if (!_forceDraw && (args->_displayMode != RenderArgs::STEREO_HMD)) { + StencilMode maskMode = _maskMode; + std::function maskOperator = [this](gpu::Batch& batch) { + auto mesh = getMesh(); + batch.setIndexBuffer(mesh->getIndexBuffer()); + batch.setInputFormat((mesh->getVertexFormat())); + batch.setInputStream(0, mesh->getVertexStream()); + + // Draw + auto part = mesh->getPartBuffer().get(0); + batch.drawIndexed(gpu::TRIANGLES, part._numIndices, part._startIndex); + }; + + if (maskMode == StencilMode::NONE) { + maskMode = args->_stencilMode; + maskOperator = args->_stencilMaskOperator; + } + + if (maskMode == StencilMode::NONE || (maskMode == StencilMode::MESH && !maskOperator)) { return; } doInBatch("PrepareStencil::run", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); - batch.setViewportTransform(args->_viewport); - if (_maskMode < 0) { - batch.setPipeline(getMeshStencilPipeline()); - - auto mesh = getMesh(); - batch.setIndexBuffer(mesh->getIndexBuffer()); - batch.setInputFormat((mesh->getVertexFormat())); - batch.setInputStream(0, mesh->getVertexStream()); - - // Draw - auto part = mesh->getPartBuffer().get(0); - batch.drawIndexed(gpu::TRIANGLES, part._numIndices, part._startIndex); - } else { + if (maskMode == StencilMode::PAINT) { batch.setPipeline(getPaintStencilPipeline()); batch.draw(gpu::TRIANGLE_STRIP, 4); + } else if (maskMode == StencilMode::MESH) { + batch.setPipeline(getMeshStencilPipeline()); + maskOperator(batch); } }); } diff --git a/libraries/render-utils/src/StencilMaskPass.h b/libraries/render-utils/src/StencilMaskPass.h index a8e4d1e1f2..410b7a5e0a 100644 --- a/libraries/render-utils/src/StencilMaskPass.h +++ b/libraries/render-utils/src/StencilMaskPass.h @@ -15,17 +15,19 @@ #include #include #include +#include class PrepareStencilConfig : public render::Job::Config { Q_OBJECT - Q_PROPERTY(int maskMode MEMBER maskMode NOTIFY dirty) - Q_PROPERTY(bool forceDraw MEMBER forceDraw NOTIFY dirty) + Q_PROPERTY(StencilMode maskMode MEMBER maskMode NOTIFY dirty) public: PrepareStencilConfig(bool enabled = true) : JobConfig(enabled) {} - int maskMode { 0 }; - bool forceDraw { false }; + // -1 -> don't force drawing (fallback to render args mode) + // 0 -> force draw without mesh + // 1 -> force draw with mesh + StencilMode maskMode { StencilMode::NONE }; signals: void dirty(); @@ -66,8 +68,7 @@ private: graphics::MeshPointer _mesh; graphics::MeshPointer getMesh(); - int _maskMode { 0 }; - bool _forceDraw { false }; + StencilMode _maskMode { StencilMode::NONE }; }; diff --git a/libraries/render/src/render/Args.h b/libraries/render/src/render/Args.h index 8b2fff68c6..d2354561e0 100644 --- a/libraries/render/src/render/Args.h +++ b/libraries/render/src/render/Args.h @@ -16,11 +16,11 @@ #include #include +#include #include #include "Forward.h" - class AABox; namespace render { @@ -131,8 +131,11 @@ namespace render { render::ScenePointer _scene; int8_t _cameraMode { -1 }; - std::function _hudOperator; + std::function _hudOperator; gpu::TexturePointer _hudTexture; + + StencilMode _stencilMode { StencilMode::NONE }; + std::function _stencilMaskOperator; }; } diff --git a/libraries/shared/src/GeometryUtil.cpp b/libraries/shared/src/GeometryUtil.cpp index 3f2f7cd7fb..b6fca03403 100644 --- a/libraries/shared/src/GeometryUtil.cpp +++ b/libraries/shared/src/GeometryUtil.cpp @@ -358,6 +358,12 @@ glm::vec3 Triangle::getNormal() const { return glm::normalize(glm::cross(edge1, edge2)); } +float Triangle::getArea() const { + glm::vec3 edge1 = v1 - v0; + glm::vec3 edge2 = v2 - v0; + return 0.5f * glm::length(glm::cross(edge1, edge2)); +} + Triangle Triangle::operator*(const glm::mat4& transform) const { return { glm::vec3(transform * glm::vec4(v0, 1.0f)), diff --git a/libraries/shared/src/GeometryUtil.h b/libraries/shared/src/GeometryUtil.h index 8ec75f71bd..04c54fc32e 100644 --- a/libraries/shared/src/GeometryUtil.h +++ b/libraries/shared/src/GeometryUtil.h @@ -125,6 +125,7 @@ public: glm::vec3 v1; glm::vec3 v2; glm::vec3 getNormal() const; + float getArea() const; Triangle operator*(const glm::mat4& transform) const; }; diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp index 597e537d8d..bb89ce306e 100644 --- a/libraries/shared/src/RegisteredMetaTypes.cpp +++ b/libraries/shared/src/RegisteredMetaTypes.cpp @@ -40,6 +40,7 @@ int qMapURLStringMetaTypeId = qRegisterMetaType>(); int socketErrorMetaTypeId = qRegisterMetaType(); int voidLambdaType = qRegisterMetaType>(); int variantLambdaType = qRegisterMetaType>(); +int stencilModeMetaTypeId = qRegisterMetaType(); void registerMetaTypes(QScriptEngine* engine) { qScriptRegisterMetaType(engine, vec2ToScriptValue, vec2FromScriptValue); @@ -64,6 +65,8 @@ void registerMetaTypes(QScriptEngine* engine) { qScriptRegisterMetaType(engine, collisionToScriptValue, collisionFromScriptValue); qScriptRegisterMetaType(engine, quuidToScriptValue, quuidFromScriptValue); qScriptRegisterMetaType(engine, aaCubeToScriptValue, aaCubeFromScriptValue); + + qScriptRegisterMetaType(engine, stencilModeToScriptValue, stencilModeFromScriptValue); } QScriptValue vec2ToScriptValue(QScriptEngine* engine, const glm::vec2& vec2) { @@ -1283,4 +1286,12 @@ QVariantMap parseTexturesToMap(QString newTextures, const QVariantMap& defaultTe } return toReturn; +} + +QScriptValue stencilModeToScriptValue(QScriptEngine* engine, const StencilMode& stencilMode) { + return engine->newVariant((int)stencilMode); +} + +void stencilModeFromScriptValue(const QScriptValue& object, StencilMode& stencilMode) { + stencilMode = StencilMode(object.toVariant().toInt()); } \ No newline at end of file diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index ea2c5b8354..13d7f81a2e 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -25,6 +25,7 @@ #include "shared/Bilateral.h" #include "Transform.h" #include "PhysicsCollisionGroups.h" +#include "StencilMode.h" class QColor; class QUrl; @@ -729,5 +730,8 @@ void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector QVariantMap parseTexturesToMap(QString textures, const QVariantMap& defaultTextures); +Q_DECLARE_METATYPE(StencilMode) +QScriptValue stencilModeToScriptValue(QScriptEngine* engine, const StencilMode& stencilMode); +void stencilModeFromScriptValue(const QScriptValue &object, StencilMode& stencilMode); #endif // hifi_RegisteredMetaTypes_h diff --git a/libraries/shared/src/StencilMode.h b/libraries/shared/src/StencilMode.h new file mode 100644 index 0000000000..412d33b319 --- /dev/null +++ b/libraries/shared/src/StencilMode.h @@ -0,0 +1,19 @@ +// +// Created by Sam Gondelman on 3/26/19. +// Copyright 2019 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_StencilMode_h +#define hifi_StencilMode_h + +enum class StencilMode { + NONE = -1, // for legacy reasons, this is -1 + PAINT = 0, + MESH = 1 +}; + +#endif // hifi_StencilMode_h + diff --git a/plugins/oculus/CMakeLists.txt b/plugins/oculus/CMakeLists.txt index abce753b4d..6ddc75e1e5 100644 --- a/plugins/oculus/CMakeLists.txt +++ b/plugins/oculus/CMakeLists.txt @@ -18,7 +18,7 @@ if (WIN32 AND (NOT USE_GLES)) link_hifi_libraries( shared task gl shaders gpu ${PLATFORM_GL_BACKEND} controllers ui qml plugins ui-plugins display-plugins input-plugins - audio-client networking render-utils + audio-client networking render-utils graphics ${PLATFORM_GL_BACKEND} ) include_hifi_library_headers(octree) diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp index a67e3127e5..db26537a19 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp @@ -227,3 +227,66 @@ QVector OculusBaseDisplayPlugin::getSensorPositions() { return result; } + +DisplayPlugin::StencilMaskMeshOperator OculusBaseDisplayPlugin::getStencilMaskMeshOperator() { + if (_session) { + if (!_stencilMeshesInitialized) { + _stencilMeshesInitialized = true; + ovr::for_each_eye([&](ovrEyeType eye) { + ovrFovStencilDesc stencilDesc = { + ovrFovStencil_HiddenArea, 0, eye, + _eyeRenderDescs[eye].Fov, _eyeRenderDescs[eye].HmdToEyePose.Orientation + }; + // First we get the size of the buffer we need + ovrFovStencilMeshBuffer buffer = { 0, 0, nullptr, 0, 0, nullptr }; + ovrResult result = ovr_GetFovStencil(_session, &stencilDesc, &buffer); + if (!OVR_SUCCESS(result)) { + _stencilMeshesInitialized = false; + return; + } + + std::vector ovrVertices(buffer.UsedVertexCount); + std::vector ovrIndices(buffer.UsedIndexCount); + + // Now we populate the actual buffer + buffer = { (int)ovrVertices.size(), 0, ovrVertices.data(), (int)ovrIndices.size(), 0, ovrIndices.data() }; + result = ovr_GetFovStencil(_session, &stencilDesc, &buffer); + + if (!OVR_SUCCESS(result)) { + _stencilMeshesInitialized = false; + return; + } + + std::vector vertices; + vertices.reserve(ovrVertices.size()); + for (auto& ovrVertex : ovrVertices) { + // We need the vertices in clip space + vertices.emplace_back(ovrVertex.x - (1.0f - (float)eye), 2.0f * ovrVertex.y - 1.0f, 0.0f); + } + + std::vector indices; + indices.reserve(ovrIndices.size()); + for (auto& ovrIndex : ovrIndices) { + indices.push_back(ovrIndex); + } + + _stencilMeshes[eye] = graphics::Mesh::createIndexedTriangles_P3F((uint32_t)vertices.size(), (uint32_t)indices.size(), vertices.data(), indices.data()); + }); + } + + if (_stencilMeshesInitialized) { + return [&](gpu::Batch& batch) { + for (auto& mesh : _stencilMeshes) { + batch.setIndexBuffer(mesh->getIndexBuffer()); + batch.setInputFormat((mesh->getVertexFormat())); + batch.setInputStream(0, mesh->getVertexStream()); + + // Draw + auto part = mesh->getPartBuffer().get(0); + batch.drawIndexed(gpu::TRIANGLES, part._numIndices, part._startIndex); + } + }; + } + } + return nullptr; +} \ No newline at end of file diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.h b/plugins/oculus/src/OculusBaseDisplayPlugin.h index 1abb7cdad7..42aa02a18f 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.h +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.h @@ -16,6 +16,8 @@ #define OVRPL_DISABLED #include +#include + class OculusBaseDisplayPlugin : public HmdDisplayPlugin { using Parent = HmdDisplayPlugin; public: @@ -34,6 +36,9 @@ public: QRectF getPlayAreaRect() override; QVector getSensorPositions() override; + virtual StencilMode getStencilMaskMode() const override { return StencilMode::MESH; } + virtual StencilMaskMeshOperator getStencilMaskMeshOperator() override; + protected: void customizeContext() override; void uncustomizeContext() override; @@ -52,4 +57,7 @@ protected: // ovrLayerEyeFovDepth _depthLayer; bool _hmdMounted { false }; bool _visible { true }; + + std::array _stencilMeshes; + bool _stencilMeshesInitialized { false }; }; diff --git a/plugins/oculus/src/OculusDebugDisplayPlugin.h b/plugins/oculus/src/OculusDebugDisplayPlugin.h index 690a488b34..ec05cd92e2 100644 --- a/plugins/oculus/src/OculusDebugDisplayPlugin.h +++ b/plugins/oculus/src/OculusDebugDisplayPlugin.h @@ -16,7 +16,7 @@ public: bool isSupported() const override; protected: - void hmdPresent(const gpu::FramebufferPointer&) override {} + void hmdPresent() override {} bool isHmdMounted() const override { return true; } private: diff --git a/plugins/oculus/src/OculusDisplayPlugin.cpp b/plugins/oculus/src/OculusDisplayPlugin.cpp index 48440ac80f..df01591639 100644 --- a/plugins/oculus/src/OculusDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusDisplayPlugin.cpp @@ -108,16 +108,13 @@ void OculusDisplayPlugin::customizeContext() { } void OculusDisplayPlugin::uncustomizeContext() { - #if 0 - if (_currentFrame && _currentFrame->framebuffer) { - // Present a final black frame to the HMD - _currentFrame->framebuffer->Bound(FramebufferTarget::Draw, [] { - Context::ClearColor(0, 0, 0, 1); - Context::Clear().ColorBuffer(); - }); - hmdPresent(); - } + // Present a final black frame to the HMD + _compositeFramebuffer->Bound(FramebufferTarget::Draw, [] { + Context::ClearColor(0, 0, 0, 1); + Context::Clear().ColorBuffer(); + }); + hmdPresent(); #endif ovr_DestroyTextureSwapChain(_session, _textureSwapChain); @@ -130,7 +127,7 @@ void OculusDisplayPlugin::uncustomizeContext() { static const uint64_t FRAME_BUDGET = (11 * USECS_PER_MSEC); static const uint64_t FRAME_OVER_BUDGET = (15 * USECS_PER_MSEC); -void OculusDisplayPlugin::hmdPresent(const gpu::FramebufferPointer& compositeFramebuffer) { +void OculusDisplayPlugin::hmdPresent() { static uint64_t lastSubmitEnd = 0; if (!_customized) { @@ -160,8 +157,15 @@ void OculusDisplayPlugin::hmdPresent(const gpu::FramebufferPointer& compositeFra auto fbo = getGLBackend()->getFramebufferID(_outputFramebuffer); glNamedFramebufferTexture(fbo, GL_COLOR_ATTACHMENT0, curTexId, 0); render([&](gpu::Batch& batch) { - auto viewport = ivec4(uvec2(), _outputFramebuffer->getSize()); - renderFromTexture(batch, compositeFramebuffer->getRenderBuffer(0), viewport, viewport, _outputFramebuffer); + batch.enableStereo(false); + batch.setFramebuffer(_outputFramebuffer); + batch.setViewportTransform(ivec4(uvec2(), _outputFramebuffer->getSize())); + batch.setStateScissorRect(ivec4(uvec2(), _outputFramebuffer->getSize())); + batch.resetViewTransform(); + batch.setProjectionTransform(mat4()); + batch.setPipeline(_presentPipeline); + batch.setResourceTexture(0, _compositeFramebuffer->getRenderBuffer(0)); + batch.draw(gpu::TRIANGLE_STRIP, 4); }); glNamedFramebufferTexture(fbo, GL_COLOR_ATTACHMENT0, 0, 0); } diff --git a/plugins/oculus/src/OculusDisplayPlugin.h b/plugins/oculus/src/OculusDisplayPlugin.h index a0126d2e58..9209fd373e 100644 --- a/plugins/oculus/src/OculusDisplayPlugin.h +++ b/plugins/oculus/src/OculusDisplayPlugin.h @@ -28,7 +28,7 @@ protected: QThread::Priority getPresentPriority() override { return QThread::TimeCriticalPriority; } bool internalActivate() override; - void hmdPresent(const gpu::FramebufferPointer&) override; + void hmdPresent() override; bool isHmdMounted() const override; void customizeContext() override; void uncustomizeContext() override; diff --git a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp index a928887866..e6b555443f 100644 --- a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp +++ b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp @@ -237,7 +237,7 @@ void OculusLegacyDisplayPlugin::uncustomizeContext() { Parent::uncustomizeContext(); } -void OculusLegacyDisplayPlugin::hmdPresent(const gpu::FramebufferPointer& compositeFramebuffer) { +void OculusLegacyDisplayPlugin::hmdPresent() { if (!_hswDismissed) { ovrHSWDisplayState hswState; ovrHmd_GetHSWDisplayState(_hmd, &hswState); @@ -252,7 +252,7 @@ void OculusLegacyDisplayPlugin::hmdPresent(const gpu::FramebufferPointer& compos memset(eyePoses, 0, sizeof(ovrPosef) * 2); eyePoses[0].Orientation = eyePoses[1].Orientation = ovrRotation; - GLint texture = getGLBackend()->getTextureID(compositeFramebuffer->getRenderBuffer(0)); + GLint texture = getGLBackend()->getTextureID(_compositeFramebuffer->getRenderBuffer(0)); auto sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); glFlush(); if (_hmdWindow->makeCurrent()) { diff --git a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h index 241d626f0c..36bdd1c792 100644 --- a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h +++ b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h @@ -39,7 +39,7 @@ protected: void customizeContext() override; void uncustomizeContext() override; - void hmdPresent(const gpu::FramebufferPointer&) override; + void hmdPresent() override; bool isHmdMounted() const override { return true; } private: diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index 3d22268472..b102722aea 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -511,13 +511,13 @@ void OpenVrDisplayPlugin::customizeContext() { Parent::customizeContext(); if (_threadedSubmit) { -// _compositeInfos[0].texture = _compositeFramebuffer->getRenderBuffer(0); + _compositeInfos[0].texture = _compositeFramebuffer->getRenderBuffer(0); for (size_t i = 0; i < COMPOSITING_BUFFER_SIZE; ++i) { -// if (0 != i) { + if (0 != i) { _compositeInfos[i].texture = gpu::Texture::createRenderBuffer(gpu::Element::COLOR_RGBA_32, _renderTargetSize.x, _renderTargetSize.y, gpu::Texture::SINGLE_MIP, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT)); -// } + } _compositeInfos[i].textureID = getGLBackend()->getTextureID(_compositeInfos[i].texture); } _submitThread->_canvas = _submitCanvas; @@ -613,17 +613,17 @@ bool OpenVrDisplayPlugin::beginFrameRender(uint32_t frameIndex) { return Parent::beginFrameRender(frameIndex); } -void OpenVrDisplayPlugin::compositeLayers(const gpu::FramebufferPointer& compositeFramebuffer) { +void OpenVrDisplayPlugin::compositeLayers() { if (_threadedSubmit) { ++_renderingIndex; _renderingIndex %= COMPOSITING_BUFFER_SIZE; auto& newComposite = _compositeInfos[_renderingIndex]; newComposite.pose = _currentPresentFrameInfo.presentPose; - compositeFramebuffer->setRenderBuffer(0, newComposite.texture); + _compositeFramebuffer->setRenderBuffer(0, newComposite.texture); } - Parent::compositeLayers(compositeFramebuffer); + Parent::compositeLayers(); if (_threadedSubmit) { auto& newComposite = _compositeInfos[_renderingIndex]; @@ -645,13 +645,13 @@ void OpenVrDisplayPlugin::compositeLayers(const gpu::FramebufferPointer& composi } } -void OpenVrDisplayPlugin::hmdPresent(const gpu::FramebufferPointer& compositeFramebuffer) { +void OpenVrDisplayPlugin::hmdPresent() { PROFILE_RANGE_EX(render, __FUNCTION__, 0xff00ff00, (uint64_t)_currentFrame->frameIndex) if (_threadedSubmit) { _submitThread->waitForPresent(); } else { - GLuint glTexId = getGLBackend()->getTextureID(compositeFramebuffer->getRenderBuffer(0)); + GLuint glTexId = getGLBackend()->getTextureID(_compositeFramebuffer->getRenderBuffer(0)); vr::Texture_t vrTexture{ (void*)(uintptr_t)glTexId, vr::TextureType_OpenGL, vr::ColorSpace_Auto }; vr::VRCompositor()->Submit(vr::Eye_Left, &vrTexture, &OPENVR_TEXTURE_BOUNDS_LEFT); vr::VRCompositor()->Submit(vr::Eye_Right, &vrTexture, &OPENVR_TEXTURE_BOUNDS_RIGHT); @@ -784,3 +784,49 @@ QRectF OpenVrDisplayPlugin::getPlayAreaRect() { return QRectF(center.x, center.y, dimensions.x, dimensions.y); } + + +DisplayPlugin::StencilMaskMeshOperator OpenVrDisplayPlugin::getStencilMaskMeshOperator() { + if (_system) { + if (!_stencilMeshesInitialized) { + _stencilMeshesInitialized = true; + for (auto eye : VR_EYES) { + vr::HiddenAreaMesh_t stencilMesh = _system->GetHiddenAreaMesh(eye); + if (stencilMesh.pVertexData && stencilMesh.unTriangleCount > 0) { + std::vector vertices; + std::vector indices; + + const int NUM_INDICES_PER_TRIANGLE = 3; + int numIndices = stencilMesh.unTriangleCount * NUM_INDICES_PER_TRIANGLE; + vertices.reserve(numIndices); + indices.reserve(numIndices); + for (int i = 0; i < numIndices; i++) { + vr::HmdVector2_t vertex2D = stencilMesh.pVertexData[i]; + // We need the vertices in clip space + vertices.emplace_back(vertex2D.v[0] - (1.0f - (float)eye), 2.0f * vertex2D.v[1] - 1.0f, 0.0f); + indices.push_back(i); + } + + _stencilMeshes[eye] = graphics::Mesh::createIndexedTriangles_P3F((uint32_t)vertices.size(), (uint32_t)indices.size(), vertices.data(), indices.data()); + } else { + _stencilMeshesInitialized = false; + } + } + } + + if (_stencilMeshesInitialized) { + return [&](gpu::Batch& batch) { + for (auto& mesh : _stencilMeshes) { + batch.setIndexBuffer(mesh->getIndexBuffer()); + batch.setInputFormat((mesh->getVertexFormat())); + batch.setInputStream(0, mesh->getVertexStream()); + + // Draw + auto part = mesh->getPartBuffer().get(0); + batch.drawIndexed(gpu::TRIANGLES, part._numIndices, part._startIndex); + } + }; + } + } + return nullptr; +} \ No newline at end of file diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.h b/plugins/openvr/src/OpenVrDisplayPlugin.h index 923a0f7a8f..aaec787893 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.h +++ b/plugins/openvr/src/OpenVrDisplayPlugin.h @@ -13,6 +13,8 @@ #include +#include + const float TARGET_RATE_OpenVr = 90.0f; // FIXME: get from sdk tracked device property? This number is vive-only. namespace gl { @@ -67,13 +69,16 @@ public: QRectF getPlayAreaRect() override; + virtual StencilMode getStencilMaskMode() const override{ return StencilMode::MESH; } + virtual StencilMaskMeshOperator getStencilMaskMeshOperator() override; + protected: bool internalActivate() override; void internalDeactivate() override; void updatePresentPose() override; - void compositeLayers(const gpu::FramebufferPointer&) override; - void hmdPresent(const gpu::FramebufferPointer&) override; + void compositeLayers() override; + void hmdPresent() override; bool isHmdMounted() const override; void postPreview() override; @@ -94,4 +99,7 @@ private: bool _asyncReprojectionActive { false }; bool _hmdMounted { false }; + + std::array _stencilMeshes; + bool _stencilMeshesInitialized { false }; }; diff --git a/scripts/system/assets/data/createAppTooltips.json b/scripts/system/assets/data/createAppTooltips.json index 7201cdecad..73c7504089 100644 --- a/scripts/system/assets/data/createAppTooltips.json +++ b/scripts/system/assets/data/createAppTooltips.json @@ -227,6 +227,14 @@ "speedSpread": { "tooltip": "The spread in speeds at which particles are emitted at, resulting in a variety of speeds." }, + "particleShapeType": { + "tooltip": "The shape of the surface from which to emit particles.", + "jsPropertyName": "shapeType" + }, + "particleCompoundShapeURL": { + "tooltip": "The model file to use for the particle emitter if Shape Type is \"Use Compound Shape URL\".", + "jsPropertyName": "compoundShapeURL" + }, "emitDimensions": { "tooltip": "The outer limit radius in dimensions that the particles can be emitted from." }, diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index f259b0a017..f450ff2036 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -807,6 +807,21 @@ const GROUPS = [ decimals: 2, propertyID: "speedSpread", }, + { + label: "Shape Type", + type: "dropdown", + options: { "box": "Box", "ellipsoid": "Ellipsoid", + "cylinder-y": "Cylinder", "circle": "Circle", "plane": "Plane", + "compound": "Use Compound Shape URL" }, + propertyID: "particleShapeType", + propertyName: "shapeType", + }, + { + label: "Compound Shape URL", + type: "string", + propertyID: "particleCompoundShapeURL", + propertyName: "compoundShapeURL", + }, { label: "Emit Dimensions", type: "vec3", diff --git a/tools/nitpick/src/AWSInterface.cpp b/tools/nitpick/src/AWSInterface.cpp index 19697d51dc..82b7dd4f7a 100644 --- a/tools/nitpick/src/AWSInterface.cpp +++ b/tools/nitpick/src/AWSInterface.cpp @@ -480,7 +480,7 @@ void AWSInterface::createEntry(const int index, const QString& testResult, QText if (differenceFileFound) { stream << "\t\t\t\t\n"; } else { - stream << "\t\t\t\t

No Image Found

\n"; + stream << "\t\t\t\t

Image size mismatch

\n"; } stream << "\t\t\t\n"; diff --git a/tools/nitpick/src/Nitpick.cpp b/tools/nitpick/src/Nitpick.cpp index 02ed120350..16cad8726c 100644 --- a/tools/nitpick/src/Nitpick.cpp +++ b/tools/nitpick/src/Nitpick.cpp @@ -9,6 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "Nitpick.h" +#include "Platform.h" #ifdef Q_OS_WIN #include @@ -40,9 +41,15 @@ Nitpick::Nitpick(QWidget* parent) : QMainWindow(parent) { setWindowTitle("Nitpick - " + nitpickVersion); - clientProfiles << "VR-High" << "Desktop-High" << "Desktop-Low" << "Mobile-Touch" << "VR-Standalone"; - _ui.clientProfileComboBox->insertItems(0, clientProfiles); + _GPUVendors << "Nvidia" << "AMD"; + _ui.gpuVendorComboBox->insertItems(0, _GPUVendors); + QString gpuVendor = Platform::getGraphicsCardType().toUpper(); + if (gpuVendor.contains("NVIDIA")) { + _ui.gpuVendorComboBox->setCurrentIndex(0); + } else { + _ui.gpuVendorComboBox->setCurrentIndex(1); + } } Nitpick::~Nitpick() { @@ -126,13 +133,14 @@ void Nitpick::setup() { ); } -void Nitpick::startTestsEvaluation(const bool isRunningFromCommandLine, - const bool isRunningInAutomaticTestRun, - const QString& snapshotDirectory, - const QString& branch, - const QString& user +void Nitpick::startTestsEvaluation( + const bool isRunningFromCommandLine, + const bool isRunningInAutomaticTestRun, + const QString& snapshotDirectory, + const QString& branch, + const QString& user ) { - _testCreator->startTestsEvaluation(isRunningFromCommandLine, isRunningInAutomaticTestRun, snapshotDirectory, branch, user); + _testCreator->startTestsEvaluation(_ui.gpuVendorComboBox, isRunningFromCommandLine, isRunningInAutomaticTestRun, snapshotDirectory, branch, user); } void Nitpick::on_tabWidget_currentChanged(int index) { @@ -144,9 +152,11 @@ void Nitpick::on_tabWidget_currentChanged(int index) { #endif _ui.userLineEdit->setDisabled(false); _ui.branchLineEdit->setDisabled(false); + _ui.gpuVendorComboBox->setDisabled(false); } else { _ui.userLineEdit->setDisabled(true); _ui.branchLineEdit->setDisabled(true); + _ui.gpuVendorComboBox->setDisabled(true); } } @@ -159,7 +169,7 @@ void Nitpick::on_createAllRecursiveScriptsPushbutton_clicked() { } void Nitpick::on_createTestsPushbutton_clicked() { - _testCreator->createTests(_ui.clientProfileComboBox->currentText()); + _testCreator->createTests(_ui.gpuVendorComboBox->currentText()); } void Nitpick::on_createMDFilePushbutton_clicked() { @@ -250,7 +260,7 @@ void Nitpick::on_showTaskbarPushbutton_clicked() { } void Nitpick::on_evaluateTestsPushbutton_clicked() { - _testCreator->startTestsEvaluation(false, false); + _testCreator->startTestsEvaluation(_ui.gpuVendorComboBox, false, false); } void Nitpick::on_closePushbutton_clicked() { diff --git a/tools/nitpick/src/Nitpick.h b/tools/nitpick/src/Nitpick.h index 42f55ee8b2..9aa0ea00ba 100644 --- a/tools/nitpick/src/Nitpick.h +++ b/tools/nitpick/src/Nitpick.h @@ -28,11 +28,13 @@ public: void setup(); - void startTestsEvaluation(const bool isRunningFromCommandLine, - const bool isRunningInAutomaticTestRun, - const QString& snapshotDirectory, - const QString& branch, - const QString& user); + void startTestsEvaluation( + const bool isRunningFromCommandLine, + const bool isRunningInAutomaticTestRun, + const QString& snapshotDirectory, + const QString& branch, + const QString& user + ); void automaticTestRunEvaluationComplete(QString zippedFolderName, int numberOfFailures); @@ -111,7 +113,7 @@ private: bool _isRunningFromCommandline{ false }; - QStringList clientProfiles; + QStringList _GPUVendors; }; #endif // hifi_Nitpick_h \ No newline at end of file diff --git a/tools/nitpick/src/Platform.cpp b/tools/nitpick/src/Platform.cpp new file mode 100644 index 0000000000..50beb543d4 --- /dev/null +++ b/tools/nitpick/src/Platform.cpp @@ -0,0 +1,121 @@ +// +// Platform.h +// +// Created by Nissim Hadar on 2 Apr 2019. +// 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 "Platform.h" + +#include + +#ifdef Q_OS_WIN +#include +#pragma comment(lib, "dxgi.lib") +#elif defined Q_OS_MAC +#include +#include +#include +#endif + +QString Platform::getGraphicsCardType() { +#ifdef Q_OS_WIN + IDXGIFactory1* pFactory = nullptr; + HRESULT hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)(&pFactory)); + if (hr != S_OK || pFactory == nullptr) { + return "Unable to create DXGI"; + } + std::vector validAdapterList; + + using AdapterEntry = std::pair, std::vector>; + std::vector adapterToOutputs; + // Enumerate adapters and outputs + { + UINT adapterNum = 0; + IDXGIAdapter1* pAdapter = nullptr; + while (pFactory->EnumAdapters1(adapterNum, &pAdapter) != DXGI_ERROR_NOT_FOUND) { + + // Found an adapter, get descriptor + DXGI_ADAPTER_DESC1 adapterDesc; + pAdapter->GetDesc1(&adapterDesc); + + LARGE_INTEGER version; + hr = pAdapter->CheckInterfaceSupport(__uuidof(IDXGIDevice), &version); + + std::wstring wDescription (adapterDesc.Description); + std::string description(wDescription.begin(), wDescription.end()); + + AdapterEntry adapterEntry; + adapterEntry.first.first = adapterDesc; + adapterEntry.first.second = version; + + + + UINT outputNum = 0; + IDXGIOutput * pOutput; + bool hasOutputConnectedToDesktop = false; + while (pAdapter->EnumOutputs(outputNum, &pOutput) != DXGI_ERROR_NOT_FOUND) { + + // FOund an output attached to the adapter, get descriptor + DXGI_OUTPUT_DESC outputDesc; + pOutput->GetDesc(&outputDesc); + + adapterEntry.second.push_back(outputDesc); + + std::wstring wDeviceName(outputDesc.DeviceName); + std::string deviceName(wDeviceName.begin(), wDeviceName.end()); + + hasOutputConnectedToDesktop |= (bool)outputDesc.AttachedToDesktop; + + pOutput->Release(); + outputNum++; + } + + adapterToOutputs.push_back(adapterEntry); + + // add this adapter to the valid list if has output + if (hasOutputConnectedToDesktop && !adapterEntry.second.empty()) { + validAdapterList.push_back(adapterNum); + } + + pAdapter->Release(); + adapterNum++; + } + } + pFactory->Release(); + + if (!validAdapterList.empty()) { + auto& adapterEntry = adapterToOutputs[validAdapterList.front()]; + + std::wstring wDescription(adapterEntry.first.first.Description); + std::string description(wDescription.begin(), wDescription.end()); + return QString(description.c_str()); + } + +#elif defined Q_OS_MAC + FILE* stream = popen("system_profiler SPDisplaysDataType | grep Chipset", "r"); + + std::ostringstream hostStream; + while (!feof(stream) && !ferror(stream)) { + char buf[128]; + int bytesRead = fread(buf, 1, 128, stream); + hostStream.write(buf, bytesRead); + } + + QString result = QString::fromStdString(hostStream.str()); + QStringList parts = result.split('\n'); + for (int i = 0; i < parts.size(); ++i) { + if (parts[i].toLower().contains("radeon") || parts[i].toLower().contains("nvidia")) { + return parts[i]; + } + } + + // unkown graphics card + return "UNKNOWN"; +#else + return QString("NO IMPLEMENTED"); +#endif + return ""; +} diff --git a/tools/nitpick/src/Platform.h b/tools/nitpick/src/Platform.h new file mode 100644 index 0000000000..da35580554 --- /dev/null +++ b/tools/nitpick/src/Platform.h @@ -0,0 +1,20 @@ +// +// Platform.h +// +// Created by Nissim Hadar on 2 Apr 2019. +// 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 +// +#ifndef hifi_Platform_h +#define hifi_Platform_h + +#include + +class Platform { +public: + static QString getGraphicsCardType(); +}; + +#endif \ No newline at end of file diff --git a/tools/nitpick/src/TestCreator.cpp b/tools/nitpick/src/TestCreator.cpp index 1e5b15360f..693f6085d2 100644 --- a/tools/nitpick/src/TestCreator.cpp +++ b/tools/nitpick/src/TestCreator.cpp @@ -67,7 +67,7 @@ QString TestCreator::zipAndDeleteTestResultsFolder() { return zippedResultsFileName; } -int TestCreator::compareImageLists() { +int TestCreator::compareImageLists(const QString& gpuVendor) { _progressBar->setMinimum(0); _progressBar->setMaximum(_expectedImagesFullFilenames.length() - 1); _progressBar->setValue(0); @@ -108,8 +108,16 @@ int TestCreator::compareImageLists() { }; _mismatchWindow.setTestResult(testResult); - - if (similarityIndex < THRESHOLD_GLOBAL || worstTileValue < THRESHOLD_LOCAL) { + + // Lower threshold for non-Nvidia GPUs + double thresholdGlobal{ 0.9995 }; + double thresholdLocal{ 0.6 }; + if (gpuVendor != "Nvidia") { + thresholdGlobal = 0.97; + thresholdLocal = 0.2; + } + + if (similarityIndex < thresholdGlobal || worstTileValue < thresholdLocal) { if (!isInteractiveMode) { ++numberOfFailures; appendTestResultsToFile(testResult, _mismatchWindow.getComparisonImage(), _mismatchWindow.getSSIMResultsImage(testResult._ssimResults), true); @@ -258,11 +266,13 @@ void::TestCreator::appendTestResultsToFile(QString testResultFilename, bool hasF } } -void TestCreator::startTestsEvaluation(const bool isRunningFromCommandLine, - const bool isRunningInAutomaticTestRun, - const QString& snapshotDirectory, - const QString& branchFromCommandLine, - const QString& userFromCommandLine +void TestCreator::startTestsEvaluation( + QComboBox *gpuVendor, + const bool isRunningFromCommandLine, + const bool isRunningInAutomaticTestRun, + const QString& snapshotDirectory, + const QString& branchFromCommandLine, + const QString& userFromCommandLine ) { _isRunningFromCommandLine = isRunningFromCommandLine; _isRunningInAutomaticTestRun = isRunningInAutomaticTestRun; @@ -332,12 +342,12 @@ void TestCreator::startTestsEvaluation(const bool isRunningFromCommandLine, } _downloader->downloadFiles(expectedImagesURLs, _snapshotDirectory, _expectedImagesFilenames, (void *)this); - finishTestsEvaluation(); + finishTestsEvaluation(gpuVendor->currentText()); } -void TestCreator::finishTestsEvaluation() { +void TestCreator::finishTestsEvaluation(const QString& gpuVendor) { // First - compare the pairs of images - int numberOfFailures = compareImageLists(); + int numberOfFailures = compareImageLists(gpuVendor); // Next - check text results numberOfFailures += checkTextResults(); @@ -430,8 +440,9 @@ void TestCreator::createTests(const QString& clientProfile) { parent += "/"; } - _testsRootDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select test root folder", parent, - QFileDialog::ShowDirsOnly); + if (!createAllFilesSetup()) { + return; + } // If user canceled then restore previous selection and return if (_testsRootDirectory == "") { @@ -881,23 +892,12 @@ void TestCreator::createRecursiveScript(const QString& directory, bool interacti } void TestCreator::createTestsOutline() { - QString previousSelection = _testDirectory; - QString parent = previousSelection.left(previousSelection.lastIndexOf('/')); - if (!parent.isNull() && parent.right(1) != "/") { - parent += "/"; - } - - _testDirectory = - QFileDialog::getExistingDirectory(nullptr, "Please select the tests root folder", parent, QFileDialog::ShowDirsOnly); - - // If user canceled then restore previous selection and return - if (_testDirectory == "") { - _testDirectory = previousSelection; + if (!createAllFilesSetup()) { return; } const QString testsOutlineFilename { "testsOutline.md" }; - QString mdFilename(_testDirectory + "/" + testsOutlineFilename); + QString mdFilename(_testsRootDirectory + "/" + testsOutlineFilename); QFile mdFile(mdFilename); if (!mdFile.open(QIODevice::WriteOnly)) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create file " + mdFilename); @@ -911,10 +911,10 @@ void TestCreator::createTestsOutline() { stream << "Directories with an appended (*) have an automatic test\n\n"; // We need to know our current depth, as this isn't given by QDirIterator - int rootDepth { _testDirectory.count('/') }; + int rootDepth { _testsRootDirectory.count('/') }; // Each test is shown as the folder name linking to the matching GitHub URL, and the path to the associated test.md file - QDirIterator it(_testDirectory, QDirIterator::Subdirectories); + QDirIterator it(_testsRootDirectory, QDirIterator::Subdirectories); while (it.hasNext()) { QString directory = it.next(); diff --git a/tools/nitpick/src/TestCreator.h b/tools/nitpick/src/TestCreator.h index e708529ba4..f995c06c94 100644 --- a/tools/nitpick/src/TestCreator.h +++ b/tools/nitpick/src/TestCreator.h @@ -45,13 +45,16 @@ class TestCreator { public: TestCreator(QProgressBar* progressBar, QCheckBox* checkBoxInteractiveMode); - void startTestsEvaluation(const bool isRunningFromCommandLine, - const bool isRunningInAutomaticTestRun, - const QString& snapshotDirectory = QString(), - const QString& branchFromCommandLine = QString(), - const QString& userFromCommandLine = QString()); + void startTestsEvaluation( + QComboBox *gpuVendor, + const bool isRunningFromCommandLine, + const bool isRunningInAutomaticTestRun, + const QString& snapshotDirectory = QString(), + const QString& branchFromCommandLine = QString(), + const QString& userFromCommandLine = QString() + ); - void finishTestsEvaluation(); + void finishTestsEvaluation(const QString& gpuVendor); void createTests(const QString& clientProfile); @@ -79,7 +82,7 @@ public: void createRecursiveScript(); void createRecursiveScript(const QString& directory, bool interactiveMode); - int compareImageLists(); + int compareImageLists(const QString& gpuVendor); int checkTextResults(); QStringList createListOfAll_imagesInDirectory(const QString& imageFormat, const QString& pathToImageDirectory); @@ -124,9 +127,6 @@ private: const QString TEST_RESULTS_FOLDER { "TestResults" }; const QString TEST_RESULTS_FILENAME { "TestResults.txt" }; - const double THRESHOLD_GLOBAL{ 0.9995 }; - const double THRESHOLD_LOCAL { 0.6 }; - QDir _imageDirectory; MismatchWindow _mismatchWindow; diff --git a/tools/nitpick/src/common.h b/tools/nitpick/src/common.h index 51f1f85113..dc1ca17d08 100644 --- a/tools/nitpick/src/common.h +++ b/tools/nitpick/src/common.h @@ -60,5 +60,5 @@ const double R_Y = 0.212655f; const double G_Y = 0.715158f; const double B_Y = 0.072187f; -const QString nitpickVersion{ "v3.1.5" }; +const QString nitpickVersion{ "v3.2.0" }; #endif // hifi_common_h \ No newline at end of file diff --git a/tools/nitpick/ui/Nitpick.ui b/tools/nitpick/ui/Nitpick.ui index e1d7699f22..bcd0101a4b 100644 --- a/tools/nitpick/ui/Nitpick.ui +++ b/tools/nitpick/ui/Nitpick.ui @@ -56,7 +56,7 @@ 70 - 40 + 120 220 40 @@ -69,7 +69,7 @@ 70 - 180 + 200 220 40 @@ -82,7 +82,7 @@ 320 - 180 + 200 220 40 @@ -94,7 +94,7 @@ - 210 + 320 120 220 40 @@ -108,7 +108,7 @@ 70 - 300 + 360 220 40 @@ -121,7 +121,7 @@ 320 - 300 + 360 220 40 @@ -134,7 +134,7 @@ 70 - 240 + 280 220 40 @@ -147,7 +147,7 @@ 320 - 240 + 280 220 40 @@ -156,16 +156,6 @@ Create all testAuto scripts - - - - 320 - 40 - 120 - 40 - - - @@ -927,8 +917,8 @@ 330 190 - 181 - 51 + 180 + 50 @@ -1186,6 +1176,39 @@ + + + + 450 + 60 + 180 + 40 + + + + + 14 + + + + + + + 505 + 40 + 81 + 16 + + + + + 10 + + + + GPU Vendor + + diff --git a/tools/oven/src/Oven.cpp b/tools/oven/src/Oven.cpp index 0a5a989cbf..a680dd1f89 100644 --- a/tools/oven/src/Oven.cpp +++ b/tools/oven/src/Oven.cpp @@ -14,7 +14,7 @@ #include #include -#include +#include #include #include diff --git a/tools/oven/src/OvenCLIApplication.cpp b/tools/oven/src/OvenCLIApplication.cpp index b4a011291d..640bfda64a 100644 --- a/tools/oven/src/OvenCLIApplication.cpp +++ b/tools/oven/src/OvenCLIApplication.cpp @@ -14,7 +14,7 @@ #include #include -#include +#include #include #include "BakerCLI.h"