diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp index 4fc8975262..800f00b352 100644 --- a/assignment-client/src/AssignmentClient.cpp +++ b/assignment-client/src/AssignmentClient.cpp @@ -91,7 +91,7 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri // check for a wallet UUID on the command line or in the config // this would represent where the user running AC wants funds sent to if (!walletUUID.isNull()) { - qCDebug(assigmnentclient) << "The destination wallet UUID for credits is" << uuidStringWithoutCurlyBraces(walletUUID); + qCDebug(assignment_client) << "The destination wallet UUID for credits is" << uuidStringWithoutCurlyBraces(walletUUID); _requestAssignment.setWalletUUID(walletUUID); } @@ -102,16 +102,16 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri } _assignmentServerSocket = HifiSockAddr(_assignmentServerHostname, assignmentServerPort, true); - _assignmentServerSocket.setObjectName("AssigmentServer"); + _assignmentServerSocket.setObjectName("AssignmentServer"); nodeList->setAssignmentServerSocket(_assignmentServerSocket); - qCDebug(assigmnentclient) << "Assignment server socket is" << _assignmentServerSocket; + qCDebug(assignment_client) << "Assignment server socket is" << _assignmentServerSocket; // call a timer function every ASSIGNMENT_REQUEST_INTERVAL_MSECS to ask for assignment, if required - qCDebug(assigmnentclient) << "Waiting for assignment -" << _requestAssignment; + qCDebug(assignment_client) << "Waiting for assignment -" << _requestAssignment; if (_assignmentServerHostname != "localhost") { - qCDebug(assigmnentclient) << "- will attempt to connect to domain-server on" << _assignmentServerSocket.getPort(); + qCDebug(assignment_client) << "- will attempt to connect to domain-server on" << _assignmentServerSocket.getPort(); } connect(&_requestTimer, SIGNAL(timeout()), SLOT(sendAssignmentRequest())); @@ -129,7 +129,7 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri _assignmentClientMonitorSocket = HifiSockAddr(DEFAULT_ASSIGNMENT_CLIENT_MONITOR_HOSTNAME, assignmentMonitorPort); _assignmentClientMonitorSocket.setObjectName("AssignmentClientMonitor"); - qCDebug(assigmnentclient) << "Assignment-client monitor socket is" << _assignmentClientMonitorSocket; + qCDebug(assignment_client) << "Assignment-client monitor socket is" << _assignmentClientMonitorSocket; // Hook up a timer to send this child's status to the Monitor once per second setUpStatusToMonitor(); @@ -140,7 +140,7 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri } void AssignmentClient::stopAssignmentClient() { - qCDebug(assigmnentclient) << "Forced stop of assignment-client."; + qCDebug(assignment_client) << "Forced stop of assignment-client."; _requestTimer.stop(); _statsTimerACM.stop(); @@ -218,14 +218,14 @@ void AssignmentClient::sendAssignmentRequest() { quint16 localAssignmentServerPort; if (nodeList->getLocalServerPortFromSharedMemory(DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY, localAssignmentServerPort)) { if (localAssignmentServerPort != _assignmentServerSocket.getPort()) { - qCDebug(assigmnentclient) << "Port for local assignment server read from shared memory is" + qCDebug(assignment_client) << "Port for local assignment server read from shared memory is" << localAssignmentServerPort; _assignmentServerSocket.setPort(localAssignmentServerPort); nodeList->setAssignmentServerSocket(_assignmentServerSocket); } } else { - qCWarning(assigmnentclient) << "Failed to read local assignment server port from shared memory" + qCWarning(assignment_client) << "Failed to read local assignment server port from shared memory" << "- will send assignment request to previous assignment server socket."; } } @@ -235,10 +235,10 @@ void AssignmentClient::sendAssignmentRequest() { } void AssignmentClient::handleCreateAssignmentPacket(QSharedPointer message) { - qCDebug(assigmnentclient) << "Received a PacketType::CreateAssignment - attempting to unpack."; + qCDebug(assignment_client) << "Received a PacketType::CreateAssignment - attempting to unpack."; if (_currentAssignment) { - qCWarning(assigmnentclient) << "Received a PacketType::CreateAssignment while still running an active assignment. Ignoring."; + qCWarning(assignment_client) << "Received a PacketType::CreateAssignment while still running an active assignment. Ignoring."; return; } @@ -246,7 +246,7 @@ void AssignmentClient::handleCreateAssignmentPacket(QSharedPointer(); @@ -256,7 +256,7 @@ void AssignmentClient::handleCreateAssignmentPacket(QSharedPointergetDomainHandler().setSockAddr(message->getSenderSockAddr(), _assignmentServerHostname); nodeList->getDomainHandler().setAssignmentUUID(_currentAssignment->getUUID()); - qCDebug(assigmnentclient) << "Destination IP for assignment is" << nodeList->getDomainHandler().getIP().toString(); + qCDebug(assignment_client) << "Destination IP for assignment is" << nodeList->getDomainHandler().getIP().toString(); // start the deployed assignment QThread* workerThread = new QThread; @@ -284,7 +284,7 @@ void AssignmentClient::handleCreateAssignmentPacket(QSharedPointerstarted() workerThread->start(); } else { - qCWarning(assigmnentclient) << "Received an assignment that could not be unpacked. Re-requesting."; + qCWarning(assignment_client) << "Received an assignment that could not be unpacked. Re-requesting."; } } @@ -294,10 +294,10 @@ void AssignmentClient::handleStopNodePacket(QSharedPointer mess if (senderSockAddr.getAddress() == QHostAddress::LocalHost || senderSockAddr.getAddress() == QHostAddress::LocalHostIPv6) { - qCDebug(assigmnentclient) << "AssignmentClientMonitor at" << senderSockAddr << "requested stop via PacketType::StopNode."; + qCDebug(assignment_client) << "AssignmentClientMonitor at" << senderSockAddr << "requested stop via PacketType::StopNode."; QCoreApplication::quit(); } else { - qCWarning(assigmnentclient) << "Got a stop packet from other than localhost."; + qCWarning(assignment_client) << "Got a stop packet from other than localhost."; } } @@ -317,7 +317,7 @@ void AssignmentClient::handleAuthenticationRequest() { // ask the account manager to log us in from the env variables accountManager->requestAccessToken(username, password); } else { - qCWarning(assigmnentclient) << "Authentication was requested against" << qPrintable(accountManager->getAuthURL().toString()) + qCWarning(assignment_client) << "Authentication was requested against" << qPrintable(accountManager->getAuthURL().toString()) << "but both or one of" << qPrintable(DATA_SERVER_USERNAME_ENV) << "/" << qPrintable(DATA_SERVER_PASSWORD_ENV) << "are not set. Unable to authenticate."; @@ -335,7 +335,7 @@ void AssignmentClient::assignmentCompleted() { // reset the logging target to the the CHILD_TARGET_NAME LogHandler::getInstance().setTargetName(ASSIGNMENT_CLIENT_TARGET_NAME); - qCDebug(assigmnentclient) << "Assignment finished or never started - waiting for new assignment."; + qCDebug(assignment_client) << "Assignment finished or never started - waiting for new assignment."; auto nodeList = DependencyManager::get(); diff --git a/assignment-client/src/AssignmentClientLogging.cpp b/assignment-client/src/AssignmentClientLogging.cpp index 890187ecaa..9110dccc5f 100644 --- a/assignment-client/src/AssignmentClientLogging.cpp +++ b/assignment-client/src/AssignmentClientLogging.cpp @@ -11,4 +11,4 @@ #include "AssignmentClientLogging.h" -Q_LOGGING_CATEGORY(assigmnentclient, "hifi.assignment-client") \ No newline at end of file +Q_LOGGING_CATEGORY(assignment_client, "hifi.assignment-client") \ No newline at end of file diff --git a/assignment-client/src/AssignmentClientLogging.h b/assignment-client/src/AssignmentClientLogging.h index d6b5ee90e0..88e2add017 100644 --- a/assignment-client/src/AssignmentClientLogging.h +++ b/assignment-client/src/AssignmentClientLogging.h @@ -14,6 +14,6 @@ #include -Q_DECLARE_LOGGING_CATEGORY(assigmnentclient) +Q_DECLARE_LOGGING_CATEGORY(assignment_client) #endif // hifi_AssignmentClientLogging_h \ No newline at end of file diff --git a/cmake/externals/wasapi/CMakeLists.txt b/cmake/externals/wasapi/CMakeLists.txt index 019920df77..8ec327600f 100644 --- a/cmake/externals/wasapi/CMakeLists.txt +++ b/cmake/externals/wasapi/CMakeLists.txt @@ -6,8 +6,8 @@ if (WIN32) include(ExternalProject) ExternalProject_Add( ${EXTERNAL_NAME} - URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi.zip - URL_MD5 11c8a7728d6eda7223df800e10b70723 + URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi2.zip + URL_MD5 272b27bd6c211c45c0c23d4701b63b5e CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" diff --git a/cmake/macros/PackageLibrariesForDeployment.cmake b/cmake/macros/PackageLibrariesForDeployment.cmake index 7f826c1f8c..d8e895b7b0 100644 --- a/cmake/macros/PackageLibrariesForDeployment.cmake +++ b/cmake/macros/PackageLibrariesForDeployment.cmake @@ -44,12 +44,25 @@ macro(PACKAGE_LIBRARIES_FOR_DEPLOYMENT) set(QTAUDIO_PATH $/audio) - # if present, replace qtaudio_windows.dll with qtaudio_wasapi.dll - add_custom_command( - TARGET ${TARGET_NAME} - POST_BUILD - COMMAND if exist ${QTAUDIO_PATH}/qtaudio_windows.dll ( ${CMAKE_COMMAND} -E remove ${QTAUDIO_PATH}/qtaudio_windows.dll && ${CMAKE_COMMAND} -E copy ${WASAPI_DLL_PATH}/qtaudio_wasapi.dll ${QTAUDIO_PATH} && ${CMAKE_COMMAND} -E copy ${WASAPI_DLL_PATH}/qtaudio_wasapi.pdb ${QTAUDIO_PATH} ) - ) + if (DEPLOY_PACKAGE) + # copy qtaudio_wasapi.dll alongside qtaudio_windows.dll, and let the installer resolve + add_custom_command( + TARGET ${TARGET_NAME} + POST_BUILD + COMMAND if exist ${QTAUDIO_PATH}/qtaudio_windows.dll ( ${CMAKE_COMMAND} -E copy ${WASAPI_DLL_PATH}/qtaudio_wasapi.dll ${QTAUDIO_PATH} && ${CMAKE_COMMAND} -E copy ${WASAPI_DLL_PATH}/qtaudio_wasapi.pdb ${QTAUDIO_PATH} ) + COMMAND if exist ${QTAUDIO_PATH}/qtaudio_windowsd.dll ( ${CMAKE_COMMAND} -E copy ${WASAPI_DLL_PATH}/qtaudio_wasapid.dll ${QTAUDIO_PATH} && ${CMAKE_COMMAND} -E copy ${WASAPI_DLL_PATH}/qtaudio_wasapid.pdb ${QTAUDIO_PATH} ) + ) + elseif (${CMAKE_SYSTEM_VERSION} VERSION_LESS 6.2) + # continue using qtaudio_windows.dll on Windows 7 + else () + # replace qtaudio_windows.dll with qtaudio_wasapi.dll on Windows 8/8.1/10 + add_custom_command( + TARGET ${TARGET_NAME} + POST_BUILD + COMMAND if exist ${QTAUDIO_PATH}/qtaudio_windows.dll ( ${CMAKE_COMMAND} -E remove ${QTAUDIO_PATH}/qtaudio_windows.dll && ${CMAKE_COMMAND} -E copy ${WASAPI_DLL_PATH}/qtaudio_wasapi.dll ${QTAUDIO_PATH} && ${CMAKE_COMMAND} -E copy ${WASAPI_DLL_PATH}/qtaudio_wasapi.pdb ${QTAUDIO_PATH} ) + COMMAND if exist ${QTAUDIO_PATH}/qtaudio_windowsd.dll ( ${CMAKE_COMMAND} -E remove ${QTAUDIO_PATH}/qtaudio_windowsd.dll && ${CMAKE_COMMAND} -E copy ${WASAPI_DLL_PATH}/qtaudio_wasapid.dll ${QTAUDIO_PATH} && ${CMAKE_COMMAND} -E copy ${WASAPI_DLL_PATH}/qtaudio_wasapid.pdb ${QTAUDIO_PATH} ) + ) + endif () endif () endmacro() diff --git a/cmake/templates/NSIS.template.in b/cmake/templates/NSIS.template.in index a80367cee1..568418afe1 100644 --- a/cmake/templates/NSIS.template.in +++ b/cmake/templates/NSIS.template.in @@ -23,6 +23,11 @@ ;Default installation folder InstallDir "@CPACK_NSIS_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_DIRECTORY@" +;-------------------------------- +;Include WinVer to get Windows version + + !include "WinVer.nsh" + ;-------------------------------- ;General ; leverage the UAC NSIS plugin to promote uninstaller to elevated privileges @@ -600,8 +605,16 @@ Section "-Core installation" Delete "$INSTDIR\version" Delete "$INSTDIR\xinput1_3.dll" - ;Delete old Qt files - Delete "$INSTDIR\audio\qtaudio_windows.dll" + ; The installer includes two different Qt audio plugins. + ; On Windows 8 and above, only qtaudio_wasapi.dll should be installed. + ; On Windows 7 and below, only qtaudio_windows.dll should be installed. + ${If} ${AtLeastWin8} + Delete "$INSTDIR\audio\qtaudio_windows.dll" + Delete "$INSTDIR\audio\qtaudio_windows.pdb" + ${Else} + Delete "$INSTDIR\audio\qtaudio_wasapi.dll" + Delete "$INSTDIR\audio\qtaudio_wasapi.pdb" + ${EndIf} ; Delete old desktop shortcuts before they were renamed during Sandbox rename Delete "$DESKTOP\@PRE_SANDBOX_INTERFACE_SHORTCUT_NAME@.lnk" diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index e33cbe1755..bf9d7d04a6 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -771,6 +771,21 @@ void DomainGatekeeper::getGroupMemberships(const QString& username) { // loop through the groups mentioned on the settings page and ask if this user is in each. The replies // will be received asynchronously and permissions will be updated as the answers come in. + QJsonObject json; + QSet groupIDSet; + foreach (QUuid groupID, _server->_settingsManager.getGroupIDs() + _server->_settingsManager.getBlacklistGroupIDs()) { + groupIDSet += groupID.toString().mid(1,36); + } + + if (groupIDSet.isEmpty()) { + // if no groups are in the permissions settings, don't ask who is in which groups. + return; + } + + QJsonArray groupIDs = QJsonArray::fromStringList(groupIDSet.toList()); + json["groups"] = groupIDs; + + // if we've already asked, wait for the answer before asking again QString lowerUsername = username.toLower(); if (_inFlightGroupMembershipsRequests.contains(lowerUsername)) { @@ -779,13 +794,6 @@ void DomainGatekeeper::getGroupMemberships(const QString& username) { } _inFlightGroupMembershipsRequests += lowerUsername; - QJsonObject json; - QSet groupIDSet; - foreach (QUuid groupID, _server->_settingsManager.getGroupIDs() + _server->_settingsManager.getBlacklistGroupIDs()) { - groupIDSet += groupID.toString().mid(1,36); - } - QJsonArray groupIDs = QJsonArray::fromStringList(groupIDSet.toList()); - json["groups"] = groupIDs; JSONCallbackParameters callbackParams; callbackParams.jsonCallbackReceiver = this; diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index c1caa00d24..d7bcaa838e 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1749,7 +1749,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url _ephemeralACScripts[scriptAssignment->getUUID()] = formData[0].second; - // add the script assigment to the assignment queue + // add the script assignment to the assignment queue SharedAssignmentPointer sharedScriptedAssignment(scriptAssignment); _unfulfilledAssignments.enqueue(sharedScriptedAssignment); _allAssignments.insert(sharedScriptedAssignment->getUUID(), sharedScriptedAssignment); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c6cf412397..fef41ac179 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -68,6 +68,7 @@ #include #include #include +#include #include #include #include @@ -814,7 +815,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : { "gl_version", glContextData["version"] }, { "gl_vender", glContextData["vendor"] }, { "gl_sl_version", glContextData["slVersion"] }, - { "gl_renderer", glContextData["renderer"] } + { "gl_renderer", glContextData["renderer"] }, + { "ideal_thread_count", QThread::idealThreadCount() } }; auto macVersion = QSysInfo::macVersion(); if (macVersion != QSysInfo::MV_None) { @@ -824,6 +826,16 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : if (windowsVersion != QSysInfo::WV_None) { properties["os_win_version"] = QSysInfo::windowsVersion(); } + + ProcessorInfo procInfo; + if (getProcessorInfo(procInfo)) { + properties["processor_core_count"] = procInfo.numProcessorCores; + properties["logical_processor_count"] = procInfo.numLogicalProcessors; + properties["processor_l1_cache_count"] = procInfo.numProcessorCachesL1; + properties["processor_l2_cache_count"] = procInfo.numProcessorCachesL2; + properties["processor_l3_cache_count"] = procInfo.numProcessorCachesL3; + } + UserActivityLogger::getInstance().logAction("launch", properties); _connectionMonitor.init(); @@ -1122,6 +1134,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : auto displayPlugin = qApp->getActiveDisplayPlugin(); properties["fps"] = _frameCounter.rate(); + properties["target_frame_rate"] = getTargetFrameRate(); properties["present_rate"] = displayPlugin->presentRate(); properties["new_frame_present_rate"] = displayPlugin->newFramePresentRate(); properties["dropped_frame_rate"] = displayPlugin->droppedFrameRate(); @@ -1163,6 +1176,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : properties["deleted_entity_cnt"] = entityActivityTracking.deletedEntityCount; properties["edited_entity_cnt"] = entityActivityTracking.editedEntityCount; + properties["active_display_plugin"] = getActiveDisplayPlugin()->getName(); + properties["using_hmd"] = isHMDMode(); + auto hmdHeadPose = getHMDSensorPose(); properties["hmd_head_pose_changed"] = isHMDMode() && (hmdHeadPose != lastHMDHeadPose); lastHMDHeadPose = hmdHeadPose; @@ -2169,7 +2185,7 @@ bool Application::event(QEvent* event) { // handle custom URL if (event->type() == QEvent::FileOpen) { - QFileOpenEvent* fileEvent = static_cast(event); + QFileOpenEvent* fileEvent = static_cast(event); QUrl url = fileEvent->url(); @@ -4353,8 +4369,13 @@ namespace render { auto scene = DependencyManager::get()->getStage(); auto sceneKeyLight = scene->getKeyLight(); auto defaultSkyboxAmbientTexture = qApp->getDefaultSkyboxAmbientTexture(); - sceneKeyLight->setAmbientSphere(defaultSkyboxAmbientTexture->getIrradiance()); - sceneKeyLight->setAmbientMap(defaultSkyboxAmbientTexture); + if (defaultSkyboxAmbientTexture) { + sceneKeyLight->setAmbientSphere(defaultSkyboxAmbientTexture->getIrradiance()); + sceneKeyLight->setAmbientMap(defaultSkyboxAmbientTexture); + } else { + static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex( + "Failed to get a valid Default Skybox Ambient Texture ? probably because it couldn't be find during initialization step"); + } // fall through: render defaults skybox } else { break; @@ -4911,6 +4932,10 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri scriptEngine->registerGlobalObject("Users", DependencyManager::get().data()); scriptEngine->registerGlobalObject("Steam", new SteamScriptingInterface(scriptEngine)); + + auto scriptingInterface = DependencyManager::get(); + scriptEngine->registerGlobalObject("Controller", scriptingInterface.data()); + UserInputMapper::registerControllerTypes(scriptEngine); } bool Application::canAcceptURL(const QString& urlString) const { diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp index 8b8f8e8c2e..5f41bf40d4 100644 --- a/interface/src/avatar/AvatarActionHold.cpp +++ b/interface/src/avatar/AvatarActionHold.cpp @@ -25,14 +25,25 @@ AvatarActionHold::AvatarActionHold(const QUuid& id, EntityItemPointer ownerEntit { _type = ACTION_TYPE_HOLD; _measuredLinearVelocities.resize(AvatarActionHold::velocitySmoothFrames); + + auto myAvatar = DependencyManager::get()->getMyAvatar(); + if (myAvatar) { + myAvatar->addHoldAction(this); + } + #if WANT_DEBUG - qDebug() << "AvatarActionHold::AvatarActionHold"; + qDebug() << "AvatarActionHold::AvatarActionHold" << (void*)this; #endif } AvatarActionHold::~AvatarActionHold() { + auto myAvatar = DependencyManager::get()->getMyAvatar(); + if (myAvatar) { + myAvatar->removeHoldAction(this); + } + #if WANT_DEBUG - qDebug() << "AvatarActionHold::~AvatarActionHold"; + qDebug() << "AvatarActionHold::~AvatarActionHold" << (void*)this; #endif } @@ -460,3 +471,40 @@ void AvatarActionHold::deserialize(QByteArray serializedArguments) { forceBodyNonStatic(); } + +void AvatarActionHold::lateAvatarUpdate(const AnimPose& prePhysicsRoomPose, const AnimPose& postAvatarUpdateRoomPose) { + auto ownerEntity = _ownerEntity.lock(); + if (!ownerEntity) { + return; + } + void* physicsInfo = ownerEntity->getPhysicsInfo(); + if (!physicsInfo) { + return; + } + ObjectMotionState* motionState = static_cast(physicsInfo); + btRigidBody* rigidBody = motionState ? motionState->getRigidBody() : nullptr; + if (!rigidBody) { + return; + } + auto avatarManager = DependencyManager::get(); + auto holdingAvatar = std::static_pointer_cast(avatarManager->getAvatarBySessionID(_holderID)); + if (!holdingAvatar || !holdingAvatar->isMyAvatar()) { + return; + } + + btTransform worldTrans = rigidBody->getWorldTransform(); + AnimPose worldBodyPose(glm::vec3(1), bulletToGLM(worldTrans.getRotation()), bulletToGLM(worldTrans.getOrigin())); + + // transform the body transform into sensor space with the prePhysics sensor-to-world matrix. + // then transform it back into world uisng the postAvatarUpdate sensor-to-world matrix. + AnimPose newWorldBodyPose = postAvatarUpdateRoomPose * prePhysicsRoomPose.inverse() * worldBodyPose; + + worldTrans.setOrigin(glmToBullet(newWorldBodyPose.trans)); + worldTrans.setRotation(glmToBullet(newWorldBodyPose.rot)); + rigidBody->setWorldTransform(worldTrans); + + bool positionSuccess; + ownerEntity->setPosition(bulletToGLM(worldTrans.getOrigin()) + ObjectMotionState::getWorldOffset(), positionSuccess, false); + bool orientationSuccess; + ownerEntity->setOrientation(bulletToGLM(worldTrans.getRotation()), orientationSuccess, false); +} diff --git a/interface/src/avatar/AvatarActionHold.h b/interface/src/avatar/AvatarActionHold.h index bfa392172d..f0b42111ed 100644 --- a/interface/src/avatar/AvatarActionHold.h +++ b/interface/src/avatar/AvatarActionHold.h @@ -15,6 +15,7 @@ #include #include +#include #include #include "avatar/MyAvatar.h" @@ -41,6 +42,8 @@ public: virtual void prepareForPhysicsSimulation() override; + void lateAvatarUpdate(const AnimPose& prePhysicsRoomPose, const AnimPose& postAvatarUpdateRoomPose); + private: void doKinematicUpdate(float deltaTimeStep); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 7e3900e340..4587ef065f 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -46,6 +46,7 @@ #include "Application.h" #include "devices/Faceshift.h" #include "AvatarManager.h" +#include "AvatarActionHold.h" #include "Menu.h" #include "MyAvatar.h" #include "Physics.h" @@ -1376,6 +1377,8 @@ void MyAvatar::prepareForPhysicsSimulation(float deltaTime) { _follow.deactivate(); getCharacterController()->disableFollow(); } + + _prePhysicsRoomPose = AnimPose(_sensorToWorldMatrix); } void MyAvatar::harvestResultsFromPhysicsSimulation(float deltaTime) { @@ -1650,8 +1653,10 @@ void MyAvatar::postUpdate(float deltaTime) { } } } -} + AnimPose postUpdateRoomPose(_sensorToWorldMatrix); + updateHoldActions(_prePhysicsRoomPose, postUpdateRoomPose); +} void MyAvatar::preDisplaySide(RenderArgs* renderArgs) { @@ -2393,3 +2398,35 @@ glm::vec3 MyAvatar::getAbsoluteJointTranslationInObjectFrame(int index) const { } } } + +// thread-safe +void MyAvatar::addHoldAction(AvatarActionHold* holdAction) { + std::lock_guard guard(_holdActionsMutex); + _holdActions.push_back(holdAction); +} + +// thread-safe +void MyAvatar::removeHoldAction(AvatarActionHold* holdAction) { + std::lock_guard guard(_holdActionsMutex); + auto iter = std::find(std::begin(_holdActions), std::end(_holdActions), holdAction); + if (iter != std::end(_holdActions)) { + _holdActions.erase(iter); + } +} + +void MyAvatar::updateHoldActions(const AnimPose& prePhysicsPose, const AnimPose& postUpdatePose) { + EntityTreeRenderer* entityTreeRenderer = qApp->getEntities(); + EntityTreePointer entityTree = entityTreeRenderer ? entityTreeRenderer->getTree() : nullptr; + if (entityTree) { + // to prevent actions from adding or removing themselves from the _holdActions vector + // while we are iterating, we need to enter a critical section. + std::lock_guard guard(_holdActionsMutex); + + // lateAvatarUpdate will modify entity position & orientation, so we need an entity write lock + entityTree->withWriteLock([&] { + for (auto& holdAction : _holdActions) { + holdAction->lateAvatarUpdate(prePhysicsPose, postUpdatePose); + } + }); + } +} diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 554de24034..340e877dd4 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -26,6 +26,7 @@ #include "MyCharacterController.h" #include +class AvatarActionHold; class ModelItemID; enum DriveKeys { @@ -289,6 +290,10 @@ public: glm::vec3 getPreActionVelocity() const; + void addHoldAction(AvatarActionHold* holdAction); // thread-safe + void removeHoldAction(AvatarActionHold* holdAction); // thread-safe + void updateHoldActions(const AnimPose& prePhysicsPose, const AnimPose& postUpdatePose); + public slots: void increaseSize(); void decreaseSize(); @@ -511,6 +516,10 @@ private: bool _moveKinematically { false }; // KINEMATIC_CONTROLLER_HACK float _canonicalScale { 1.0f }; + AnimPose _prePhysicsRoomPose; + std::mutex _holdActionsMutex; + std::vector _holdActions; + float AVATAR_MOVEMENT_ENERGY_CONSTANT { 0.001f }; float AUDIO_ENERGY_CONSTANT { 0.000001f }; float MAX_AVATAR_MOVEMENT_PER_FRAME { 30.0f }; diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 1250f74061..16f4c35d21 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -817,6 +817,13 @@ void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) { return; } + // NOTE: we assume the inputFormat and the outputFormat are the same, since on any modern + // multimedia OS they should be. If there is a device that this is not true for, we can + // add back support to do resampling. + if (_inputFormat.sampleRate() != _outputFormat.sampleRate()) { + return; + } + // if this person wants local loopback add that to the locally injected audio // if there is reverb apply it to local audio and substract the origin samples @@ -833,11 +840,6 @@ void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) { } } - // NOTE: we assume the inputFormat and the outputFormat are the same, since on any modern - // multimedia OS they should be. If there is a device that this is not true for, we can - // add back support to do resampling. - Q_ASSERT(_inputFormat.sampleRate() == _outputFormat.sampleRate()); - static QByteArray loopBackByteArray; int numInputSamples = inputByteArray.size() / AudioConstants::SAMPLE_SIZE; @@ -1362,7 +1364,7 @@ int AudioClient::setOutputBufferSize(int numFrames, bool persist) { // proportional to the accelerator ratio. #ifdef Q_OS_WIN -const float AudioClient::CALLBACK_ACCELERATOR_RATIO = 1.0f; +const float AudioClient::CALLBACK_ACCELERATOR_RATIO = IsWindows8OrGreater() ? 1.0f : 0.25f; #endif #ifdef Q_OS_MAC diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 97b5c14512..1b2a2da296 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -87,7 +87,7 @@ public: AudioOutputIODevice(MixedProcessedAudioStream& receivedAudioStream, AudioClient* audio) : _receivedAudioStream(receivedAudioStream), _audio(audio), _unfulfilledReads(0) {}; - void start() { open(QIODevice::ReadOnly); } + void start() { open(QIODevice::ReadOnly | QIODevice::Unbuffered); } void stop() { close(); } qint64 readData(char * data, qint64 maxSize) override; qint64 writeData(const char * data, qint64 maxSize) override { return 0; } @@ -167,7 +167,8 @@ public slots: int setOutputBufferSize(int numFrames, bool persist = true); - virtual bool outputLocalInjector(bool isStereo, AudioInjector* injector) override; + bool outputLocalInjector(bool isStereo, AudioInjector* injector) override; + bool shouldLoopbackInjectors() override { return _shouldEchoToServer; } bool switchInputToAudioDevice(const QString& inputDeviceName); bool switchOutputToAudioDevice(const QString& outputDeviceName); diff --git a/libraries/audio/src/AbstractAudioInterface.h b/libraries/audio/src/AbstractAudioInterface.h index 223421a7ab..ec96462e73 100644 --- a/libraries/audio/src/AbstractAudioInterface.h +++ b/libraries/audio/src/AbstractAudioInterface.h @@ -33,6 +33,7 @@ public: public slots: virtual bool outputLocalInjector(bool isStereo, AudioInjector* injector) = 0; + virtual bool shouldLoopbackInjectors() { return false; } virtual void setIsStereoInput(bool stereo) = 0; }; diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp index 43701a51d8..6f6534e2d2 100644 --- a/libraries/audio/src/AudioInjector.cpp +++ b/libraries/audio/src/AudioInjector.cpp @@ -211,6 +211,7 @@ int64_t AudioInjector::injectNextFrame() { } // if we haven't setup the packet to send then do so now + static int loopbackOptionOffset = -1; static int positionOptionOffset = -1; static int volumeOptionOffset = -1; static int audioDataOffset = -1; @@ -260,10 +261,9 @@ int64_t AudioInjector::injectNextFrame() { // pack the stereo/mono type of the stream audioPacketStream << _options.stereo; - // pack the flag for loopback. Now, we don't loopback - // and _always_ play locally, so loopbackFlag should be - // false always. - uchar loopbackFlag = (uchar)false; + // pack the flag for loopback, if requested + loopbackOptionOffset = _currentPacket->pos(); + uchar loopbackFlag = (_localAudioInterface && _localAudioInterface->shouldLoopbackInjectors()); audioPacketStream << loopbackFlag; // pack the position for injected audio @@ -293,6 +293,7 @@ int64_t AudioInjector::injectNextFrame() { return NEXT_FRAME_DELTA_ERROR_OR_FINISHED; } } + if (!_frameTimer->isValid()) { // in the case where we have been restarted, the frame timer will be invalid and we need to start it back over here _frameTimer->restart(); @@ -317,6 +318,9 @@ int64_t AudioInjector::injectNextFrame() { // pack the sequence number _currentPacket->writePrimitive(_outgoingSequenceNumber); + _currentPacket->seek(loopbackOptionOffset); + _currentPacket->writePrimitive((uchar)(_localAudioInterface && _localAudioInterface->shouldLoopbackInjectors())); + _currentPacket->seek(positionOptionOffset); _currentPacket->writePrimitive(_options.position); _currentPacket->writePrimitive(_options.orientation); diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index ef15861843..74f8cdbc10 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -8,6 +8,7 @@ #pragma once #include "DisplayPlugin.h" +#include #include #include @@ -18,7 +19,6 @@ #include #include -#include #include namespace gpu { @@ -35,7 +35,6 @@ protected: using Mutex = std::mutex; using Lock = std::unique_lock; using Condition = std::condition_variable; - using TextureEscrow = GLEscrow; public: // These must be final to ensure proper ordering of operations // between the main thread and the presentation thread diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 1c177cffc4..86bce87ba2 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -58,9 +58,13 @@ void WebEntityAPIHelper::emitWebEvent(const QVariant& message) { } else { // special case to handle raising and lowering the virtual keyboard if (message.type() == QVariant::String && message.toString() == "_RAISE_KEYBOARD" && _renderableWebEntityItem) { - _renderableWebEntityItem->setKeyboardRaised(true); + if (_renderableWebEntityItem) { + _renderableWebEntityItem->setKeyboardRaised(true); + } } else if (message.type() == QVariant::String && message.toString() == "_LOWER_KEYBOARD" && _renderableWebEntityItem) { - _renderableWebEntityItem->setKeyboardRaised(false); + if (_renderableWebEntityItem) { + _renderableWebEntityItem->setKeyboardRaised(false); + } } else { emit webEventReceived(message); } @@ -343,7 +347,7 @@ void RenderableWebEntityItem::destroyWebSurface() { // The lifetime of the QML surface MUST be managed by the main thread // Additionally, we MUST use local variables copied by value, rather than - // member variables, since they would implicitly refer to a this that + // member variables, since they would implicitly refer to a this that // is no longer valid auto webSurface = _webSurface; AbstractViewStateInterface::instance()->postLambdaEvent([webSurface] { @@ -388,35 +392,40 @@ static bool equals(const QByteArray& byteArray, const uint8_t* ptr) { } void RenderableWebEntityItem::synthesizeKeyPress(QString key) { - auto utf8Key = key.toUtf8(); + auto eventHandler = getEventHandler(); + if (eventHandler) { + auto utf8Key = key.toUtf8(); - int scanCode = (int)utf8Key[0]; - QString keyString = key; - if (equals(utf8Key, UPWARDS_WHITE_ARROW_FROM_BAR) || equals(utf8Key, ASTERISIM) || - equals(utf8Key, (uint8_t*)PUNCTUATION_STRING) || equals(utf8Key, (uint8_t*)ALPHABET_STRING)) { - return; // ignore - } else if (equals(utf8Key, LEFT_ARROW)) { - scanCode = Qt::Key_Backspace; - keyString = "\x08"; - } else if (equals(utf8Key, RETURN_SYMBOL)) { - scanCode = Qt::Key_Return; - keyString = "\x0d"; - } else if (equals(utf8Key, LEFTWARD_WHITE_ARROW)) { - scanCode = Qt::Key_Left; - keyString = ""; - } else if (equals(utf8Key, RIGHTWARD_WHITE_ARROW)) { - scanCode = Qt::Key_Right; - keyString = ""; + int scanCode = (int)utf8Key[0]; + QString keyString = key; + if (equals(utf8Key, UPWARDS_WHITE_ARROW_FROM_BAR) || equals(utf8Key, ASTERISIM) || + equals(utf8Key, (uint8_t*)PUNCTUATION_STRING) || equals(utf8Key, (uint8_t*)ALPHABET_STRING)) { + return; // ignore + } else if (equals(utf8Key, LEFT_ARROW)) { + scanCode = Qt::Key_Backspace; + keyString = "\x08"; + } else if (equals(utf8Key, RETURN_SYMBOL)) { + scanCode = Qt::Key_Return; + keyString = "\x0d"; + } else if (equals(utf8Key, LEFTWARD_WHITE_ARROW)) { + scanCode = Qt::Key_Left; + keyString = ""; + } else if (equals(utf8Key, RIGHTWARD_WHITE_ARROW)) { + scanCode = Qt::Key_Right; + keyString = ""; + } + + QKeyEvent* pressEvent = new QKeyEvent(QEvent::KeyPress, scanCode, Qt::NoModifier, keyString); + QKeyEvent* releaseEvent = new QKeyEvent(QEvent::KeyRelease, scanCode, Qt::NoModifier, keyString); + QCoreApplication::postEvent(eventHandler, pressEvent); + QCoreApplication::postEvent(eventHandler, releaseEvent); } - - QKeyEvent* pressEvent = new QKeyEvent(QEvent::KeyPress, scanCode, Qt::NoModifier, keyString); - QKeyEvent* releaseEvent = new QKeyEvent(QEvent::KeyRelease, scanCode, Qt::NoModifier, keyString); - QCoreApplication::postEvent(getEventHandler(), pressEvent); - QCoreApplication::postEvent(getEventHandler(), releaseEvent); } void RenderableWebEntityItem::emitScriptEvent(const QVariant& message) { - _webEntityAPIHelper->emitScriptEvent(message); + if (_webEntityAPIHelper) { + _webEntityAPIHelper->emitScriptEvent(message); + } } void RenderableWebEntityItem::setKeyboardRaised(bool raised) { @@ -424,5 +433,10 @@ void RenderableWebEntityItem::setKeyboardRaised(bool raised) { // raise the keyboard only while in HMD mode and it's being requested. bool value = AbstractViewStateInterface::instance()->isHMDMode() && raised; - _webSurface->getRootItem()->setProperty("keyboardRaised", QVariant(value)); + if (_webSurface) { + auto rootItem = _webSurface->getRootItem(); + if (rootItem) { + rootItem->setProperty("keyboardRaised", QVariant(value)); + } + } } diff --git a/libraries/gl/src/gl/GLWidget.cpp b/libraries/gl/src/gl/GLWidget.cpp index f2b823a65e..23bdcff640 100644 --- a/libraries/gl/src/gl/GLWidget.cpp +++ b/libraries/gl/src/gl/GLWidget.cpp @@ -67,8 +67,8 @@ void GLWidget::createContext() { _context = new gl::Context(); _context->setWindow(windowHandle()); _context->create(); - _context->clear(); _context->makeCurrent(); + _context->clear(); } bool GLWidget::makeCurrent() { diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.cpp b/libraries/gl/src/gl/OffscreenQmlSurface.cpp index bdfa359b8b..d1c884f264 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.cpp +++ b/libraries/gl/src/gl/OffscreenQmlSurface.cpp @@ -6,7 +6,11 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "OffscreenQmlSurface.h" -#include "OglplusHelpers.h" +#include "Config.h" + +#include +#include +#include #include #include @@ -116,6 +120,108 @@ static const QEvent::Type RENDER = QEvent::Type(QEvent::User + 2); static const QEvent::Type RESIZE = QEvent::Type(QEvent::User + 3); static const QEvent::Type STOP = QEvent::Type(QEvent::User + 4); +class RawTextureRecycler { +public: + using TexturePtr = GLuint; + RawTextureRecycler(bool useMipmaps) : _useMipmaps(useMipmaps) {} + void setSize(const uvec2& size); + void clear(); + TexturePtr getNextTexture(); + void recycleTexture(GLuint texture); + +private: + + struct TexInfo { + TexturePtr _tex { 0 }; + uvec2 _size; + bool _active { false }; + + TexInfo() {} + TexInfo(TexturePtr tex, const uvec2& size) : _tex(tex), _size(size) {} + }; + + using Map = std::map; + using Queue = std::queue; + + Map _allTextures; + Queue _readyTextures; + uvec2 _size { 1920, 1080 }; + bool _useMipmaps; +}; + + +void RawTextureRecycler::setSize(const uvec2& size) { + if (size == _size) { + return; + } + _size = size; + while (!_readyTextures.empty()) { + _readyTextures.pop(); + } + std::set toDelete; + std::for_each(_allTextures.begin(), _allTextures.end(), [&](Map::const_reference item) { + if (!item.second._active && item.second._size != _size) { + toDelete.insert(item.first); + } + }); + std::for_each(toDelete.begin(), toDelete.end(), [&](Map::key_type key) { + _allTextures.erase(key); + }); +} + +void RawTextureRecycler::clear() { + while (!_readyTextures.empty()) { + _readyTextures.pop(); + } + _allTextures.clear(); +} + +RawTextureRecycler::TexturePtr RawTextureRecycler::getNextTexture() { + if (_readyTextures.empty()) { + TexturePtr newTexture; + glGenTextures(1, &newTexture); + + glBindTexture(GL_TEXTURE_2D, newTexture); + if (_useMipmaps) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + } else { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + } + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 8.0f); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -0.2f); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 8.0f); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, _size.x, _size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + _allTextures[newTexture] = TexInfo { newTexture, _size }; + _readyTextures.push(newTexture); + } + + TexturePtr result = _readyTextures.front(); + _readyTextures.pop(); + auto& item = _allTextures[result]; + item._active = true; + return result; +} + +void RawTextureRecycler::recycleTexture(GLuint texture) { + Q_ASSERT(_allTextures.count(texture)); + auto& item = _allTextures[texture]; + Q_ASSERT(item._active); + item._active = false; + if (item._size != _size) { + // Buh-bye + _allTextures.erase(texture); + return; + } + + _readyTextures.push(item._tex); +} + + class OffscreenQmlRenderThread : public QThread { public: OffscreenQmlRenderThread(OffscreenQmlSurface* surface, QOpenGLContext* shareContext); @@ -165,9 +271,9 @@ private: OffscreenQmlSurface* _surface{ nullptr }; QQuickWindow* _quickWindow{ nullptr }; QMyQuickRenderControl* _renderControl{ nullptr }; - FramebufferPtr _fbo; - RenderbufferPtr _depthStencil; - TextureRecycler _textures { true }; + GLuint _fbo { 0 }; + GLuint _depthStencil { 0 }; + RawTextureRecycler _textures { true }; GLTextureEscrow _escrow; uint64_t _lastRenderTime{ 0 }; @@ -253,24 +359,23 @@ bool OffscreenQmlRenderThread::event(QEvent *e) { } void OffscreenQmlRenderThread::setupFbo() { - using namespace oglplus; _textures.setSize(_size); - - try { - _depthStencil.reset(new Renderbuffer()); - Context::Bound(Renderbuffer::Target::Renderbuffer, *_depthStencil) - .Storage( - PixelDataInternalFormat::DepthComponent, - _size.x, _size.y); - - _fbo.reset(new Framebuffer()); - _fbo->Bind(Framebuffer::Target::Draw); - _fbo->AttachRenderbuffer(Framebuffer::Target::Draw, - FramebufferAttachment::Depth, *_depthStencil); - DefaultFramebuffer().Bind(Framebuffer::Target::Draw); - } catch (oglplus::Error& error) { - qWarning() << "OpenGL error in QML render setup: " << error.what(); + if (_depthStencil) { + glDeleteRenderbuffers(1, &_depthStencil); + _depthStencil = 0; } + glGenRenderbuffers(1, &_depthStencil); + glBindRenderbuffer(GL_RENDERBUFFER, _depthStencil); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, _size.x, _size.y); + + if (_fbo) { + glDeleteFramebuffers(1, &_fbo); + _fbo = 0; + } + glGenFramebuffers(1, &_fbo); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo); + glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthStencil); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); } QJsonObject OffscreenQmlRenderThread::getGLContextData() { @@ -309,8 +414,15 @@ void OffscreenQmlRenderThread::init() { void OffscreenQmlRenderThread::cleanup() { _renderControl->invalidate(); - _fbo.reset(); - _depthStencil.reset(); + if (_depthStencil) { + glDeleteRenderbuffers(1, &_depthStencil); + _depthStencil = 0; + } + if (_fbo) { + glDeleteFramebuffers(1, &_fbo); + _fbo = 0; + } + _textures.clear(); _canvas.doneCurrent(); @@ -371,42 +483,22 @@ void OffscreenQmlRenderThread::render() { releaseMainThread.trigger(); } - using namespace oglplus; - - _quickWindow->setRenderTarget(GetName(*_fbo), QSize(_size.x, _size.y)); + _quickWindow->setRenderTarget(_fbo, QSize(_size.x, _size.y)); try { - PROFILE_RANGE("qml_render") - - TexturePtr texture = _textures.getNextTexture(); - - try { - _fbo->Bind(Framebuffer::Target::Draw); - _fbo->AttachTexture(Framebuffer::Target::Draw, FramebufferAttachment::Color, *texture, 0); - _fbo->Complete(Framebuffer::Target::Draw); - } catch (oglplus::Error& error) { - qWarning() << "OpenGL error in QML render: " << error.what(); - - // In case we are failing from a failed setupFbo, reset fbo before next render - setupFbo(); - throw; - } - - { + GLuint texture = _textures.getNextTexture(); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo); + glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0); PROFILE_RANGE("qml_render->rendercontrol") _renderControl->render(); - // FIXME The web browsers seem to be leaving GL in an error state. - // Need a debug context with sync logging to figure out why. - // for now just clear the errors - glGetError(); - } - Context::Bound(oglplus::Texture::Target::_2D, *texture).GenerateMipmap(); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + glBindTexture(GL_TEXTURE_2D, texture); + glGenerateMipmap(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, 0); - // FIXME probably unecessary - DefaultFramebuffer().Bind(Framebuffer::Target::Draw); _quickWindow->resetOpenGLState(); - _escrow.submit(GetName(*texture)); + _escrow.submit(texture); _lastRenderTime = usecTimestampNow(); } catch (std::runtime_error& error) { qWarning() << "Failed to render QML: " << error.what(); diff --git a/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.cpp b/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.cpp index 75af08d9a3..45403f4d4d 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.cpp @@ -18,7 +18,6 @@ std::unordered_map _map; #endif -//#define TEXTURE_TRANSFER_PBOS #ifdef TEXTURE_TRANSFER_PBOS #define TEXTURE_TRANSFER_BLOCK_SIZE (64 * 1024) @@ -62,11 +61,16 @@ void GLTextureTransferHelper::transferTexture(const gpu::TexturePointer& texture void GLTextureTransferHelper::setup() { #ifdef THREADED_TEXTURE_TRANSFER _context.makeCurrent(); + +#ifdef TEXTURE_TRANSFER_FORCE_DRAW + // FIXME don't use opengl 4.5 DSA functionality without verifying it's present glCreateRenderbuffers(1, &_drawRenderbuffer); glNamedRenderbufferStorage(_drawRenderbuffer, GL_RGBA8, 128, 128); glCreateFramebuffers(1, &_drawFramebuffer); glNamedFramebufferRenderbuffer(_drawFramebuffer, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _drawRenderbuffer); glCreateFramebuffers(1, &_readFramebuffer); +#endif + #ifdef TEXTURE_TRANSFER_PBOS std::array pbos; glCreateBuffers(TEXTURE_TRANSFER_PBO_COUNT, &pbos[0]); @@ -84,7 +88,9 @@ void GLTextureTransferHelper::setup() { void GLTextureTransferHelper::shutdown() { #ifdef THREADED_TEXTURE_TRANSFER _context.makeCurrent(); +#endif +#ifdef TEXTURE_TRANSFER_FORCE_DRAW glNamedFramebufferRenderbuffer(_drawFramebuffer, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, 0); glDeleteFramebuffers(1, &_drawFramebuffer); _drawFramebuffer = 0; @@ -165,6 +171,11 @@ bool GLTextureTransferHelper::process() { } gltexture->finishTransfer(); + +#ifdef TEXTURE_TRANSFER_FORCE_DRAW + // FIXME force a draw on the texture transfer thread before passing the texture to the main thread for use +#endif + #ifdef THREADED_TEXTURE_TRANSFER clientWait(); #endif diff --git a/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.h b/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.h index 289aec40bb..a23c282fd4 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.h +++ b/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.h @@ -21,6 +21,14 @@ #define THREADED_TEXTURE_TRANSFER #endif +#ifdef THREADED_TEXTURE_TRANSFER +// FIXME when sparse textures are enabled, it's harder to force a draw on the transfer thread +// also, the current draw code is implicitly using OpenGL 4.5 functionality +//#define TEXTURE_TRANSFER_FORCE_DRAW +// FIXME PBO's increase the complexity and don't seem to work reliably +//#define TEXTURE_TRANSFER_PBOS +#endif + namespace gpu { namespace gl { using TextureList = std::list; @@ -43,11 +51,15 @@ public: private: #ifdef THREADED_TEXTURE_TRANSFER ::gl::OffscreenContext _context; +#endif + +#ifdef TEXTURE_TRANSFER_FORCE_DRAW // Framebuffers / renderbuffers for forcing access to the texture on the transfer thread GLuint _drawRenderbuffer { 0 }; GLuint _drawFramebuffer { 0 }; GLuint _readFramebuffer { 0 }; #endif + // A mutex for protecting items access on the render and transfer threads Mutex _mutex; // Commands that have been submitted for execution on the texture transfer thread diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp index 3e6e53ffc3..36b7b4886f 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp @@ -511,9 +511,7 @@ void GL45Texture::stripToMip(uint16_t newMinMip) { _minMip = newMinMip; // Re-sync the sampler to force access to the new mip level syncSampler(); - size_t oldSize = _size; updateSize(); - Q_ASSERT(_size > oldSize); // Re-insert into the texture-by-mips map if appropriate diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index 306a19c308..6eacbab46a 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -377,8 +377,10 @@ void GeometryResource::deleter() { } void GeometryResource::setTextures() { - for (const FBXMaterial& material : _fbxGeometry->materials) { - _materials.push_back(std::make_shared(material, _textureBaseUrl)); + if (_fbxGeometry) { + for (const FBXMaterial& material : _fbxGeometry->materials) { + _materials.push_back(std::make_shared(material, _textureBaseUrl)); + } } } @@ -457,7 +459,9 @@ model::TextureMapPointer NetworkMaterial::fetchTextureMap(const QUrl& baseUrl, c _textures[channel] = Texture { fbxTexture.name, texture }; auto map = std::make_shared(); - map->setTextureSource(texture->_textureSource); + if (texture) { + map->setTextureSource(texture->_textureSource); + } map->setTextureTransform(fbxTexture.transform); return map; diff --git a/libraries/model/src/model/Material.cpp b/libraries/model/src/model/Material.cpp index 6e7968a571..4e01c4b866 100755 --- a/libraries/model/src/model/Material.cpp +++ b/libraries/model/src/model/Material.cpp @@ -44,8 +44,11 @@ Material::Material(const Material& material) : } Material& Material::operator= (const Material& material) { + QMutexLocker locker(&_textureMapsMutex); + _key = (material._key); _textureMaps = (material._textureMaps); + _hasCalculatedTextureInfo = false; // copied: create the Buffer to store the properties, avoid holding a ref to the old Buffer Schema schema; @@ -112,6 +115,8 @@ void Material::setScattering(float scattering) { } void Material::setTextureMap(MapChannel channel, const TextureMapPointer& textureMap) { + QMutexLocker locker(&_textureMapsMutex); + if (textureMap) { _key.setMapChannel(channel, (true)); _textureMaps[channel] = textureMap; @@ -119,6 +124,7 @@ void Material::setTextureMap(MapChannel channel, const TextureMapPointer& textur _key.setMapChannel(channel, (false)); _textureMaps.erase(channel); } + _hasCalculatedTextureInfo = false; _schemaBuffer.edit()._key = (uint32)_key._flags.to_ulong(); @@ -173,6 +179,8 @@ void Material::resetOpacityMap() const { const TextureMapPointer Material::getTextureMap(MapChannel channel) const { + QMutexLocker locker(&_textureMapsMutex); + auto result = _textureMaps.find(channel); if (result != _textureMaps.end()) { return (result->second); @@ -180,3 +188,37 @@ const TextureMapPointer Material::getTextureMap(MapChannel channel) const { return TextureMapPointer(); } } + + +bool Material::calculateMaterialInfo() const { + if (!_hasCalculatedTextureInfo) { + QMutexLocker locker(&_textureMapsMutex); + + bool allTextures = true; // assume we got this... + _textureSize = 0; + _textureCount = 0; + + for (auto const &textureMapItem : _textureMaps) { + auto textureMap = textureMapItem.second; + if (textureMap) { + auto textureSoure = textureMap->getTextureSource(); + if (textureSoure) { + auto texture = textureSoure->getGPUTexture(); + if (texture) { + auto size = texture->getSize(); + _textureSize += size; + _textureCount++; + } else { + allTextures = false; + } + } else { + allTextures = false; + } + } else { + allTextures = false; + } + } + _hasCalculatedTextureInfo = allTextures; + } + return _hasCalculatedTextureInfo; +} diff --git a/libraries/model/src/model/Material.h b/libraries/model/src/model/Material.h index 304ef2e93b..8851ef4ce9 100755 --- a/libraries/model/src/model/Material.h +++ b/libraries/model/src/model/Material.h @@ -11,6 +11,8 @@ #ifndef hifi_model_Material_h #define hifi_model_Material_h +#include + #include #include @@ -324,7 +326,7 @@ public: // The texture map to channel association void setTextureMap(MapChannel channel, const TextureMapPointer& textureMap); - const TextureMaps& getTextureMaps() const { return _textureMaps; } + const TextureMaps& getTextureMaps() const { return _textureMaps; } // FIXME - not thread safe... const TextureMapPointer getTextureMap(MapChannel channel) const; // Albedo maps cannot have opacity detected until they are loaded @@ -344,12 +346,25 @@ public: }; const UniformBufferView& getTexMapArrayBuffer() const { return _texMapArrayBuffer; } + + int getTextureCount() const { calculateMaterialInfo(); return _textureCount; } + size_t getTextureSize() const { calculateMaterialInfo(); return _textureSize; } + bool hasTextureInfo() const { return _hasCalculatedTextureInfo; } + private: mutable MaterialKey _key; mutable UniformBufferView _schemaBuffer; mutable UniformBufferView _texMapArrayBuffer; TextureMaps _textureMaps; + + mutable QMutex _textureMapsMutex { QMutex::Recursive }; + mutable size_t _textureSize { 0 }; + mutable int _textureCount { 0 }; + mutable bool _hasCalculatedTextureInfo { false }; + bool calculateMaterialInfo() const; + + }; typedef std::shared_ptr< Material > MaterialPointer; diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 593a79b311..617ba85bad 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -536,15 +536,7 @@ void NodeList::processDomainServerList(QSharedPointer message) QUuid domainUUID; packetStream >> domainUUID; - // if this was the first domain-server list from this domain, we've now connected - if (!_domainHandler.isConnected()) { - _domainHandler.setUUID(domainUUID); - _domainHandler.setIsConnected(true); - - // in case we didn't use a place name to get to this domain, - // give the address manager a chance to lookup a default one now - DependencyManager::get()->lookupShareableNameForDomainID(domainUUID); - } else if (_domainHandler.getUUID() != domainUUID) { + if (_domainHandler.isConnected() && _domainHandler.getUUID() != domainUUID) { // Recieved packet from different domain. qWarning() << "IGNORING DomainList packet from" << domainUUID << "while connected to" << _domainHandler.getUUID(); return; @@ -555,6 +547,16 @@ void NodeList::processDomainServerList(QSharedPointer message) packetStream >> newUUID; setSessionUUID(newUUID); + // if this was the first domain-server list from this domain, we've now connected + if (!_domainHandler.isConnected()) { + _domainHandler.setUUID(domainUUID); + _domainHandler.setIsConnected(true); + + // in case we didn't use a place name to get to this domain, + // give the address manager a chance to lookup a default one now + DependencyManager::get()->lookupShareableNameForDomainID(domainUUID); + } + // pull the permissions/right/privileges for this node out of the stream NodePermissions newPermissions; packetStream >> newPermissions; diff --git a/libraries/networking/src/udt/Connection.cpp b/libraries/networking/src/udt/Connection.cpp index af70295840..dcceef0fdc 100644 --- a/libraries/networking/src/udt/Connection.cpp +++ b/libraries/networking/src/udt/Connection.cpp @@ -45,6 +45,20 @@ Connection::Connection(Socket* parentSocket, HifiSockAddr destination, std::uniq // set the initial RTT and flow window size on congestion control object _congestionControl->setRTT(_rtt); _congestionControl->setMaxCongestionWindowSize(_flowWindowSize); + + // Setup packets + static const int ACK_PACKET_PAYLOAD_BYTES = sizeof(_lastSentACK) + sizeof(_currentACKSubSequenceNumber) + + sizeof(_rtt) + sizeof(int32_t) + sizeof(int32_t) + sizeof(int32_t); + static const int LIGHT_ACK_PACKET_PAYLOAD_BYTES = sizeof(SequenceNumber); + static const int ACK2_PAYLOAD_BYTES = sizeof(SequenceNumber); + static const int NAK_PACKET_PAYLOAD_BYTES = 2 * sizeof(SequenceNumber); + static const int HANDSHAKE_ACK_PAYLOAD_BYTES = sizeof(SequenceNumber); + + _ackPacket = ControlPacket::create(ControlPacket::ACK, ACK_PACKET_PAYLOAD_BYTES); + _lightACKPacket = ControlPacket::create(ControlPacket::LightACK, LIGHT_ACK_PACKET_PAYLOAD_BYTES); + _ack2Packet = ControlPacket::create(ControlPacket::ACK2, ACK2_PAYLOAD_BYTES); + _lossReport = ControlPacket::create(ControlPacket::NAK, NAK_PACKET_PAYLOAD_BYTES); + _handshakeACK = ControlPacket::create(ControlPacket::HandshakeACK, HANDSHAKE_ACK_PAYLOAD_BYTES); } Connection::~Connection() { @@ -279,25 +293,22 @@ void Connection::sendACK(bool wasCausedBySyncTimeout) { // update the last sent ACK _lastSentACK = nextACKNumber; - - // setup the ACK packet, make it static so we can re-use it - static const int ACK_PACKET_PAYLOAD_BYTES = sizeof(_lastSentACK) + sizeof(_currentACKSubSequenceNumber) - + sizeof(_rtt) + sizeof(int32_t) + sizeof(int32_t) + sizeof(int32_t); - static auto ackPacket = ControlPacket::create(ControlPacket::ACK, ACK_PACKET_PAYLOAD_BYTES); - ackPacket->reset(); // We need to reset it every time. + + + _ackPacket->reset(); // We need to reset it every time. // pack in the ACK sub-sequence number - ackPacket->writePrimitive(++_currentACKSubSequenceNumber); + _ackPacket->writePrimitive(++_currentACKSubSequenceNumber); // pack in the ACK number - ackPacket->writePrimitive(nextACKNumber); + _ackPacket->writePrimitive(nextACKNumber); // pack in the RTT and variance - ackPacket->writePrimitive(_rtt); + _ackPacket->writePrimitive(_rtt); // pack the available buffer size, in packets // in our implementation we have no hard limit on receive buffer size, send the default value - ackPacket->writePrimitive((int32_t) udt::CONNECTION_RECEIVE_BUFFER_SIZE_PACKETS); + _ackPacket->writePrimitive((int32_t) udt::CONNECTION_RECEIVE_BUFFER_SIZE_PACKETS); if (wasCausedBySyncTimeout) { // grab the up to date packet receive speed and estimated bandwidth @@ -309,15 +320,15 @@ void Connection::sendACK(bool wasCausedBySyncTimeout) { _stats.recordEstimatedBandwidth(estimatedBandwidth); // pack in the receive speed and estimatedBandwidth - ackPacket->writePrimitive(packetReceiveSpeed); - ackPacket->writePrimitive(estimatedBandwidth); + _ackPacket->writePrimitive(packetReceiveSpeed); + _ackPacket->writePrimitive(estimatedBandwidth); } // record this as the last ACK send time lastACKSendTime = p_high_resolution_clock::now(); // have the socket send off our packet - _parentSocket->writeBasePacket(*ackPacket, _destination); + _parentSocket->writeBasePacket(*_ackPacket, _destination); Q_ASSERT_X(_sentACKs.empty() || _sentACKs.back().first + 1 == _currentACKSubSequenceNumber, "Connection::sendACK", "Adding an invalid ACK to _sentACKs"); @@ -339,35 +350,27 @@ void Connection::sendLightACK() { return; } - // create the light ACK packet, make it static so we can re-use it - static const int LIGHT_ACK_PACKET_PAYLOAD_BYTES = sizeof(SequenceNumber); - static auto lightACKPacket = ControlPacket::create(ControlPacket::LightACK, LIGHT_ACK_PACKET_PAYLOAD_BYTES); - // reset the lightACKPacket before we go to write the ACK to it - lightACKPacket->reset(); + _lightACKPacket->reset(); // pack in the ACK - lightACKPacket->writePrimitive(nextACKNumber); + _lightACKPacket->writePrimitive(nextACKNumber); // have the socket send off our packet immediately - _parentSocket->writeBasePacket(*lightACKPacket, _destination); + _parentSocket->writeBasePacket(*_lightACKPacket, _destination); _stats.record(ConnectionStats::Stats::SentLightACK); } void Connection::sendACK2(SequenceNumber currentACKSubSequenceNumber) { - // setup a static ACK2 packet we will re-use - static const int ACK2_PAYLOAD_BYTES = sizeof(SequenceNumber); - static auto ack2Packet = ControlPacket::create(ControlPacket::ACK2, ACK2_PAYLOAD_BYTES); - // reset the ACK2 Packet before writing the sub-sequence number to it - ack2Packet->reset(); + _ack2Packet->reset(); // write the sub sequence number for this ACK2 - ack2Packet->writePrimitive(currentACKSubSequenceNumber); + _ack2Packet->writePrimitive(currentACKSubSequenceNumber); // send the ACK2 packet - _parentSocket->writeBasePacket(*ack2Packet, _destination); + _parentSocket->writeBasePacket(*_ack2Packet, _destination); // update the last sent ACK2 and the last ACK2 send time _lastSentACK2 = currentACKSubSequenceNumber; @@ -376,19 +379,16 @@ void Connection::sendACK2(SequenceNumber currentACKSubSequenceNumber) { } void Connection::sendNAK(SequenceNumber sequenceNumberRecieved) { - // create the loss report packet, make it static so we can re-use it - static const int NAK_PACKET_PAYLOAD_BYTES = 2 * sizeof(SequenceNumber); - static auto lossReport = ControlPacket::create(ControlPacket::NAK, NAK_PACKET_PAYLOAD_BYTES); - lossReport->reset(); // We need to reset it every time. + _lossReport->reset(); // We need to reset it every time. // pack in the loss report - lossReport->writePrimitive(_lastReceivedSequenceNumber + 1); + _lossReport->writePrimitive(_lastReceivedSequenceNumber + 1); if (_lastReceivedSequenceNumber + 1 != sequenceNumberRecieved - 1) { - lossReport->writePrimitive(sequenceNumberRecieved - 1); + _lossReport->writePrimitive(sequenceNumberRecieved - 1); } // have the parent socket send off our packet immediately - _parentSocket->writeBasePacket(*lossReport, _destination); + _parentSocket->writeBasePacket(*_lossReport, _destination); // record our last NAK time _lastNAKTime = p_high_resolution_clock::now(); @@ -519,7 +519,7 @@ bool Connection::processReceivedSequenceNumber(SequenceNumber sequenceNumber, in return !wasDuplicate; } -void Connection::processControl(std::unique_ptr controlPacket) { +void Connection::processControl(ControlPacketPointer controlPacket) { // Simple dispatch to control packets processing methods based on their type. @@ -577,7 +577,7 @@ void Connection::processControl(std::unique_ptr controlPacket) { } } -void Connection::processACK(std::unique_ptr controlPacket) { +void Connection::processACK(ControlPacketPointer controlPacket) { // read the ACK sub-sequence number SequenceNumber currentACKSubSequenceNumber; controlPacket->readPrimitive(¤tACKSubSequenceNumber); @@ -678,7 +678,7 @@ void Connection::processACK(std::unique_ptr controlPacket) { _stats.record(ConnectionStats::Stats::ProcessedACK); } -void Connection::processLightACK(std::unique_ptr controlPacket) { +void Connection::processLightACK(ControlPacketPointer controlPacket) { // read the ACKed sequence number SequenceNumber ack; controlPacket->readPrimitive(&ack); @@ -702,7 +702,7 @@ void Connection::processLightACK(std::unique_ptr controlPacket) { _stats.record(ConnectionStats::Stats::ReceivedLightACK); } -void Connection::processACK2(std::unique_ptr controlPacket) { +void Connection::processACK2(ControlPacketPointer controlPacket) { // pull the sub sequence number from the packet SequenceNumber subSequenceNumber; controlPacket->readPrimitive(&subSequenceNumber); @@ -742,7 +742,7 @@ void Connection::processACK2(std::unique_ptr controlPacket) { _stats.record(ConnectionStats::Stats::ReceivedACK2); } -void Connection::processNAK(std::unique_ptr controlPacket) { +void Connection::processNAK(ControlPacketPointer controlPacket) { // read the loss report SequenceNumber start, end; controlPacket->readPrimitive(&start); @@ -764,7 +764,7 @@ void Connection::processNAK(std::unique_ptr controlPacket) { _stats.record(ConnectionStats::Stats::ReceivedNAK); } -void Connection::processHandshake(std::unique_ptr controlPacket) { +void Connection::processHandshake(ControlPacketPointer controlPacket) { SequenceNumber initialSequenceNumber; controlPacket->readPrimitive(&initialSequenceNumber); @@ -782,18 +782,16 @@ void Connection::processHandshake(std::unique_ptr controlPacket) _lastReceivedSequenceNumber = initialSequenceNumber - 1; _lastSentACK = initialSequenceNumber - 1; } - - // immediately respond with a handshake ACK - static auto handshakeACK = ControlPacket::create(ControlPacket::HandshakeACK, sizeof(SequenceNumber)); - handshakeACK->seek(0); - handshakeACK->writePrimitive(initialSequenceNumber); - _parentSocket->writeBasePacket(*handshakeACK, _destination); + + _handshakeACK->reset(); + _handshakeACK->writePrimitive(initialSequenceNumber); + _parentSocket->writeBasePacket(*_handshakeACK, _destination); // indicate that handshake has been received _hasReceivedHandshake = true; } -void Connection::processHandshakeACK(std::unique_ptr controlPacket) { +void Connection::processHandshakeACK(ControlPacketPointer controlPacket) { // if we've decided to clean up the send queue then this handshake ACK should be ignored, it's useless if (_sendQueue) { SequenceNumber initialSequenceNumber; @@ -807,7 +805,7 @@ void Connection::processHandshakeACK(std::unique_ptr controlPacke } } -void Connection::processTimeoutNAK(std::unique_ptr controlPacket) { +void Connection::processTimeoutNAK(ControlPacketPointer controlPacket) { // Override SendQueue's LossList with the timeout NAK list getSendQueue().overrideNAKListFromPacket(*controlPacket); @@ -817,7 +815,7 @@ void Connection::processTimeoutNAK(std::unique_ptr controlPacket) _stats.record(ConnectionStats::Stats::ReceivedTimeoutNAK); } -void Connection::processProbeTail(std::unique_ptr controlPacket) { +void Connection::processProbeTail(ControlPacketPointer controlPacket) { if (((uint32_t) _lastReceivedSequenceNumber & 0xF) == 0) { // this is the second packet in a probe set so we can estimate bandwidth // the sender sent this to us in lieu of sending new data (because they didn't have any) diff --git a/libraries/networking/src/udt/Connection.h b/libraries/networking/src/udt/Connection.h index 08a2df9b97..d6121d47b2 100644 --- a/libraries/networking/src/udt/Connection.h +++ b/libraries/networking/src/udt/Connection.h @@ -55,6 +55,7 @@ public: using SequenceNumberTimePair = std::pair; using ACKListPair = std::pair; using SentACKList = std::list; + using ControlPacketPointer = std::unique_ptr; Connection(Socket* parentSocket, HifiSockAddr destination, std::unique_ptr congestionControl); ~Connection(); @@ -66,7 +67,7 @@ public: // return indicates if this packet should be processed bool processReceivedSequenceNumber(SequenceNumber sequenceNumber, int packetSize, int payloadSize); - void processControl(std::unique_ptr controlPacket); + void processControl(ControlPacketPointer controlPacket); void queueReceivedMessagePacket(std::unique_ptr packet); @@ -96,14 +97,14 @@ private: void sendNAK(SequenceNumber sequenceNumberRecieved); void sendTimeoutNAK(); - void processACK(std::unique_ptr controlPacket); - void processLightACK(std::unique_ptr controlPacket); - void processACK2(std::unique_ptr controlPacket); - void processNAK(std::unique_ptr controlPacket); - void processTimeoutNAK(std::unique_ptr controlPacket); - void processHandshake(std::unique_ptr controlPacket); - void processHandshakeACK(std::unique_ptr controlPacket); - void processProbeTail(std::unique_ptr controlPacket); + void processACK(ControlPacketPointer controlPacket); + void processLightACK(ControlPacketPointer controlPacket); + void processACK2(ControlPacketPointer controlPacket); + void processNAK(ControlPacketPointer controlPacket); + void processTimeoutNAK(ControlPacketPointer controlPacket); + void processHandshake(ControlPacketPointer controlPacket); + void processHandshakeACK(ControlPacketPointer controlPacket); + void processProbeTail(ControlPacketPointer controlPacket); void resetReceiveState(); void resetRTT(); @@ -171,7 +172,14 @@ private: std::map _pendingReceivedMessages; int _packetsSinceACK { 0 }; // The number of packets that have been received during the current ACK interval - + + // Re-used control packets + ControlPacketPointer _ackPacket; + ControlPacketPointer _lightACKPacket; + ControlPacketPointer _ack2Packet; + ControlPacketPointer _lossReport; + ControlPacketPointer _handshakeACK; + ConnectionStats _stats; }; diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 50c0c869ff..c791c1f98e 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -71,39 +71,8 @@ void MeshPartPayload::updateTransform(const Transform& transform, const Transfor void MeshPartPayload::updateMaterial(model::MaterialPointer drawMaterial) { _drawMaterial = drawMaterial; - calculateMaterialSize(); } -bool MeshPartPayload::calculateMaterialSize() { - bool allTextures = true; // assume we got this... - _materialTextureSize = 0; - _materialTextureCount = 0; - auto textureMaps = _drawMaterial->getTextureMaps(); - for (auto const &textureMapItem : textureMaps) { - auto textureMap = textureMapItem.second; - if (textureMap) { - auto textureSoure = textureMap->getTextureSource(); - if (textureSoure) { - auto texture = textureSoure->getGPUTexture(); - if (texture) { - //auto storedSize = texture->getStoredSize(); - auto size = texture->getSize(); - _materialTextureSize += size; - _materialTextureCount++; - } else { - allTextures = false; - } - } else { - allTextures = false; - } - } else { - allTextures = false; - } - } - return allTextures; -} - - ItemKey MeshPartPayload::getKey() const { ItemKey::Builder builder; builder.withTypeShape(); @@ -378,7 +347,6 @@ void ModelMeshPartPayload::initCache() { auto networkMaterial = _model->getGeometry()->getShapeMaterial(_shapeID); if (networkMaterial) { _drawMaterial = networkMaterial; - calculateMaterialSize(); } } @@ -429,7 +397,12 @@ ItemKey ModelMeshPartPayload::getKey() const { } ShapeKey ModelMeshPartPayload::getShapeKey() const { - assert(_model->isLoaded()); + + // guard against partially loaded meshes + if (!_model || !_model->isLoaded() || !_model->getGeometry()) { + return ShapeKey::Builder::invalid(); + } + const FBXGeometry& geometry = _model->getFBXGeometry(); const auto& networkMeshes = _model->getGeometry()->getMeshes(); diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index 3ecd8da03e..04b63874cd 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -66,13 +66,9 @@ public: bool _hasColorAttrib = false; size_t getVerticesCount() const { return _drawMesh ? _drawMesh->getNumVertices() : 0; } - size_t getMaterialTextureSize() { return _materialTextureSize; } - int getMaterialTextureCount() { return _materialTextureCount; } - bool calculateMaterialSize(); - -protected: - size_t _materialTextureSize { 0 }; - int _materialTextureCount { 0 }; + size_t getMaterialTextureSize() { return _drawMaterial ? _drawMaterial->getTextureSize() : 0; } + int getMaterialTextureCount() { return _drawMaterial ? _drawMaterial->getTextureCount() : 0; } + bool hasTextureInfo() const { return _drawMaterial ? _drawMaterial->hasTextureInfo() : false; } }; namespace render { diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index f6caa7c3d3..28a1c3d579 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -168,10 +168,9 @@ void Model::calculateTextureInfo() { bool allTexturesLoaded = true; foreach(auto renderItem, _modelMeshRenderItemsSet) { auto meshPart = renderItem.get(); - bool allTexturesForThisMesh = meshPart->calculateMaterialSize(); - allTexturesLoaded = allTexturesLoaded & allTexturesForThisMesh; textureSize += meshPart->getMaterialTextureSize(); textureCount += meshPart->getMaterialTextureCount(); + allTexturesLoaded = allTexturesLoaded & meshPart->hasTextureInfo(); } _renderInfoTextureSize = textureSize; _renderInfoTextureCount = textureCount; diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 0ac2883cb5..160ad77197 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -517,10 +517,6 @@ void ScriptEngine::init() { // constants globalObject().setProperty("TREE_SCALE", newVariant(QVariant(TREE_SCALE))); - auto scriptingInterface = DependencyManager::get(); - registerGlobalObject("Controller", scriptingInterface.data()); - UserInputMapper::registerControllerTypes(this); - auto recordingInterface = DependencyManager::get(); registerGlobalObject("Recording", recordingInterface.data()); @@ -1021,9 +1017,12 @@ void ScriptEngine::updateMemoryCost(const qint64& deltaSize) { } void ScriptEngine::timerFired() { - if (DependencyManager::get()->isStopped()) { - qCDebug(scriptengine) << "Script.timerFired() while shutting down is ignored... parent script:" << getFilename(); - return; // bail early + { + auto engine = DependencyManager::get(); + if (!engine || engine->isStopped()) { + qCDebug(scriptengine) << "Script.timerFired() while shutting down is ignored... parent script:" << getFilename(); + return; // bail early + } } QTimer* callingTimer = reinterpret_cast(sender()); diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index dd93dc2e03..62db99a431 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -166,7 +166,7 @@ public: void setUserLoaded(bool isUserLoaded) { _isUserLoaded = isUserLoaded; } bool isUserLoaded() const { return _isUserLoaded; } - // NOTE - this is used by the TypedArray implemetation. we need to review this for thread safety + // NOTE - this is used by the TypedArray implementation. we need to review this for thread safety ArrayBufferClass* getArrayBufferClass() { return _arrayBufferClass; } void setEmitScriptUpdatesFunction(std::function func) { _emitScriptUpdates = func; } diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index bb5d326851..287c15e3a2 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -22,11 +22,8 @@ #include -#ifdef _WIN32 -#include -#endif - #ifdef Q_OS_WIN +#include #include "CPUIdent.h" #include #endif @@ -877,3 +874,143 @@ bool getMemoryInfo(MemoryInfo& info) { return false; } + +// Largely taken from: https://msdn.microsoft.com/en-us/library/windows/desktop/ms683194(v=vs.85).aspx + +#ifdef Q_OS_WIN +using LPFN_GLPI = BOOL(WINAPI*)( + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION, + PDWORD); + +DWORD CountSetBits(ULONG_PTR bitMask) +{ + DWORD LSHIFT = sizeof(ULONG_PTR) * 8 - 1; + DWORD bitSetCount = 0; + ULONG_PTR bitTest = (ULONG_PTR)1 << LSHIFT; + DWORD i; + + for (i = 0; i <= LSHIFT; ++i) { + bitSetCount += ((bitMask & bitTest) ? 1 : 0); + bitTest /= 2; + } + + return bitSetCount; +} +#endif + +bool getProcessorInfo(ProcessorInfo& info) { + +#ifdef Q_OS_WIN + LPFN_GLPI glpi; + bool done = false; + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION buffer = NULL; + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION ptr = NULL; + DWORD returnLength = 0; + DWORD logicalProcessorCount = 0; + DWORD numaNodeCount = 0; + DWORD processorCoreCount = 0; + DWORD processorL1CacheCount = 0; + DWORD processorL2CacheCount = 0; + DWORD processorL3CacheCount = 0; + DWORD processorPackageCount = 0; + DWORD byteOffset = 0; + PCACHE_DESCRIPTOR Cache; + + glpi = (LPFN_GLPI)GetProcAddress( + GetModuleHandle(TEXT("kernel32")), + "GetLogicalProcessorInformation"); + if (nullptr == glpi) { + qDebug() << "GetLogicalProcessorInformation is not supported."; + return false; + } + + while (!done) { + DWORD rc = glpi(buffer, &returnLength); + + if (FALSE == rc) { + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + if (buffer) { + free(buffer); + } + + buffer = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION)malloc( + returnLength); + + if (NULL == buffer) { + qDebug() << "Error: Allocation failure"; + return false; + } + } else { + qDebug() << "Error " << GetLastError(); + return false; + } + } else { + done = true; + } + } + + ptr = buffer; + + while (byteOffset + sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION) <= returnLength) { + switch (ptr->Relationship) { + case RelationNumaNode: + // Non-NUMA systems report a single record of this type. + numaNodeCount++; + break; + + case RelationProcessorCore: + processorCoreCount++; + + // A hyperthreaded core supplies more than one logical processor. + logicalProcessorCount += CountSetBits(ptr->ProcessorMask); + break; + + case RelationCache: + // Cache data is in ptr->Cache, one CACHE_DESCRIPTOR structure for each cache. + Cache = &ptr->Cache; + if (Cache->Level == 1) { + processorL1CacheCount++; + } else if (Cache->Level == 2) { + processorL2CacheCount++; + } else if (Cache->Level == 3) { + processorL3CacheCount++; + } + break; + + case RelationProcessorPackage: + // Logical processors share a physical package. + processorPackageCount++; + break; + + default: + qDebug() << "\nError: Unsupported LOGICAL_PROCESSOR_RELATIONSHIP value.\n"; + break; + } + byteOffset += sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION); + ptr++; + } + + qDebug() << "GetLogicalProcessorInformation results:"; + qDebug() << "Number of NUMA nodes:" << numaNodeCount; + qDebug() << "Number of physical processor packages:" << processorPackageCount; + qDebug() << "Number of processor cores:" << processorCoreCount; + qDebug() << "Number of logical processors:" << logicalProcessorCount; + qDebug() << "Number of processor L1/L2/L3 caches:" + << processorL1CacheCount + << "/" << processorL2CacheCount + << "/" << processorL3CacheCount; + + info.numPhysicalProcessorPackages = processorPackageCount; + info.numProcessorCores = processorCoreCount; + info.numLogicalProcessors = logicalProcessorCount; + info.numProcessorCachesL1 = processorL1CacheCount; + info.numProcessorCachesL2 = processorL2CacheCount; + info.numProcessorCachesL3 = processorL3CacheCount; + + free(buffer); + + return true; +#endif + + return false; +} \ No newline at end of file diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index f3e5625484..863c4d6dc5 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -214,4 +214,15 @@ struct MemoryInfo { bool getMemoryInfo(MemoryInfo& info); +struct ProcessorInfo { + int32_t numPhysicalProcessorPackages; + int32_t numProcessorCores; + int32_t numLogicalProcessors; + int32_t numProcessorCachesL1; + int32_t numProcessorCachesL2; + int32_t numProcessorCachesL3; +}; + +bool getProcessorInfo(ProcessorInfo& info); + #endif // hifi_SharedUtil_h diff --git a/scripts/developer/tests/performance/domain-check.js b/scripts/developer/tests/performance/domain-check.js new file mode 100644 index 0000000000..eceffa278b --- /dev/null +++ b/scripts/developer/tests/performance/domain-check.js @@ -0,0 +1,201 @@ +"use strict"; +/*jslint vars: true, plusplus: true*/ +/*globals Script, MyAvatar, Quat, Render, ScriptDiscoveryService, Window, LODManager, Entities, print*/ +// +// loadedMachine.js +// scripts/developer/tests/ +// +// Created by Howard Stearns on 6/6/16. +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +// Confirms that the specified domain is operating within specified constraints. + +var MINIMUM_DESKTOP_FRAMERATE = 57; // frames per second +var MINIMUM_HMD_FRAMERATE = 86; +var EXPECTED_DESKTOP_FRAMERATE = 60; +var EXPECTED_HMD_FRAMERATE = 90; +var MAXIMUM_LOAD_TIME = 60; // seconds +var MINIMUM_AVATARS = 25; // FIXME: not implemented yet. Requires agent scripts. Idea is to have them organize themselves to the right number. + +var version = 1; +function debug() { + print.apply(null, [].concat.apply(['hrs fixme', version], [].map.call(arguments, JSON.stringify))); +} + +var emptyishPlace = 'empty'; +var cachePlaces = ['localhost', 'Welcome']; +var isInCachePlace = cachePlaces.indexOf(location.hostname) >= 0; +var defaultPlace = isInCachePlace ? 'Playa' : location.hostname; +var prompt = "domain-check.js version " + version + "\n\nWhat place should we enter?"; +debug(cachePlaces, isInCachePlace, defaultPlace, prompt); +var entryPlace = Window.prompt(prompt, defaultPlace); + +var fail = false, results = ""; +function addResult(label, actual, minimum, maximum) { + if ((minimum !== undefined) && (actual < minimum)) { + fail = true; + } + if ((maximum !== undefined) && (actual > maximum)) { + fail = true; + } + results += "\n" + label + ": " + actual + " (" + ((100 * actual) / (maximum || minimum)).toFixed(0) + "%)"; +} +function giveReport() { + Window.alert(entryPlace + (fail ? " FAILED" : " OK") + "\n" + results); +} + +// Tests are performed domain-wide, at full LOD +var initialLodIsAutomatic = LODManager.getAutomaticLODAdjust(); +var LOD = 32768 * 400; +LODManager.setAutomaticLODAdjust(false); +LODManager.setOctreeSizeScale(LOD); +Script.scriptEnding.connect(function () { LODManager.setAutomaticLODAdjust(initialLodIsAutomatic); }); + +function startTwirl(targetRotation, degreesPerUpdate, interval, strafeDistance, optionalCallback) { + var initialRotation = Quat.safeEulerAngles(MyAvatar.orientation).y; + var accumulatedRotation = 0; + function tick() { + MyAvatar.orientation = Quat.fromPitchYawRollDegrees(0, accumulatedRotation + initialRotation, 0); + if (strafeDistance) { + MyAvatar.position = Vec3.sum(MyAvatar.position, Vec3.multiply(strafeDistance, Quat.getRight(MyAvatar.orientation))); + } + accumulatedRotation += degreesPerUpdate; + if (accumulatedRotation >= targetRotation) { + return optionalCallback && optionalCallback(); + } + Script.setTimeout(tick, interval); + } + tick(); +} + +function doLoad(place, continuationWithLoadTime) { // Go to place and call continuationWithLoadTime(loadTimeInSeconds) + var start = Date.now(), timeout, onDownloadUpdate, finishedTwirl = false, loadTime; + function clearHandlers() { + debug('clearHandlers'); + Stats.downloadsPendingChanged.disconnect(onDownloadUpdate); + Stats.downloadsChanged.disconnect(onDownloadUpdate); + } + function waitForLoad(flag) { + debug('entry', place, 'initial downloads/pending', Stats.downloads, Stats.downloadsPending); + location.hostChanged.disconnect(waitForLoad); + timeout = Script.setTimeout(function () { + debug('downloads timeout', Date()); + clearHandlers(); + Window.alert("Timeout during " + place + " load. FAILED"); + Script.stop(); + }, MAXIMUM_LOAD_TIME * 1000); + startTwirl(360, 6, 90, null, function () { + finishedTwirl = true; + if (loadTime) { + continuationWithLoadTime(loadTime); + } + }); + Stats.downloadsPendingChanged.connect(onDownloadUpdate); + Stats.downloadsChanged.connect(onDownloadUpdate); + } + function isLoading() { + // FIXME: This tells us when download are completed, but it doesn't tell us when the objects are parsed and loaded. + // We really want something like _physicsEnabled, but that isn't signalled. + return Stats.downloads || Stats.downloadsPending; + } + onDownloadUpdate = function onDownloadUpdate() { + debug('update downloads/pending', Stats.downloads, Stats.downloadsPending); + if (isLoading()) { + return; + } + Script.clearTimeout(timeout); + clearHandlers(); + loadTime = (Date.now() - start) / 1000; + if (finishedTwirl) { + continuationWithLoadTime(loadTime); + } + }; + + function doit() { + debug('go', place); + location.hostChanged.connect(waitForLoad); + location.handleLookupString(place); + } + if (location.placename.toLowerCase() === place.toLowerCase()) { + location.handleLookupString(emptyishPlace); + Script.setTimeout(doit, 1000); + } else { + doit(); + } +} + +var config = Render.getConfig("Stats"); +function doRender(continuation) { + var start = Date.now(), frames = 0; + function onNewStats() { // Accumulates frames on signal during load test + frames++; + } + config.newStats.connect(onNewStats); + startTwirl(720, 1, 15, 0.08, function () { + var end = Date.now(); + config.newStats.disconnect(onNewStats); + addResult('frame rate', 1000 * frames / (end - start), + HMD.active ? MINIMUM_HMD_FRAMERATE : MINIMUM_DESKTOP_FRAMERATE, + HMD.active ? EXPECTED_HMD_FRAMERATE : EXPECTED_DESKTOP_FRAMERATE); + continuation(); + }); +} + +function maybePrepareCache(continuation) { + var prepareCache = Window.confirm("Prepare cache?\n\n\ +Should we start with all and only those items cached that are encountered when visiting:\n" + cachePlaces.join(', ') + "\n\ +If 'yes', cache will be cleared and we will visit these two, with a turn in each, and wait for everything to be loaded.\n\ +You would want to say 'no' (and make other preparations) if you were testing these places."); + + if (prepareCache) { + location.handleLookupString(emptyishPlace); + Window.alert("Please do menu Edit->Reload Content (Clears all caches) and THEN press 'ok'."); + function loadNext() { + var place = cachePlaces.shift(); + doLoad(place, function (prepTime) { + debug(place, 'ready', prepTime); + if (cachePlaces.length) { + loadNext(); + } else { + continuation(); + } + }); + } + loadNext(); + } else { + continuation(); + } +} + +function maybeRunTribbles(continuation) { + if (Window.confirm("Run tribbles?\n\n\ +At most, only one participant should say yes.")) { + Script.load('http://howard-stearns.github.io/models/scripts/tests/performance/tribbles.js'); // FIXME: replace with AWS + Script.setTimeout(continuation, 3000); + } else { + continuation(); + } +} + +if (!entryPlace) { + Window.alert("domain-check.js cancelled"); + Script.stop(); +} else { + maybePrepareCache(function (prepTime) { + debug('cache ready', prepTime); + doLoad(entryPlace, function (loadTime) { + addResult("load time", loadTime, undefined, MAXIMUM_LOAD_TIME); + maybeRunTribbles(function () { + doRender(function () { + giveReport(); + Script.stop(); + }); + }); + }); + }); +} + +Script.scriptEnding.connect(function () { print("domain-check completed"); }); diff --git a/scripts/system/notifications.js b/scripts/system/notifications.js index f3ba466342..d89b532f31 100644 --- a/scripts/system/notifications.js +++ b/scripts/system/notifications.js @@ -17,11 +17,9 @@ // keystroke: // // CTRL/s for snapshot. -// CTRL/m for mic mute and unmute. // System generated notifications: -// If Screen is resized. -// If mic is muted for any reason. +// Connection refused. // // To add a new System notification type: // @@ -92,16 +90,12 @@ var lodTextID = false; var NotificationType = { UNKNOWN: 0, - MUTE_TOGGLE: 1, - SNAPSHOT: 2, - WINDOW_RESIZE: 3, - LOD_WARNING: 4, - CONNECTION_REFUSED: 5, - EDIT_ERROR: 6, + SNAPSHOT: 1, + LOD_WARNING: 2, + CONNECTION_REFUSED: 3, + EDIT_ERROR: 4, properties: [ - { text: "Mute Toggle" }, { text: "Snapshot" }, - { text: "Window Resize" }, { text: "Level of Detail" }, { text: "Connection Refused" }, { text: "Edit error" } @@ -446,19 +440,6 @@ function wordWrap(str) { return stringDivider(str, 43.0, "\n"); } -// This fires a notification on window resize -function checkSize() { - if ((Window.innerWidth !== ourWidth) || (Window.innerHeight !== ourHeight)) { - var windowResize = "Window has been resized"; - ourWidth = Window.innerWidth; - ourHeight = Window.innerHeight; - windowDimensions = Controller.getViewportDimensions(); - overlayLocationX = (windowDimensions.x - (width + 60.0)); - buttonLocationX = overlayLocationX + (width - 35.0); - createNotification(windowResize, NotificationType.WINDOW_RESIZE); - } -} - function update() { var nextOverlay, noticeOut, @@ -480,7 +461,6 @@ function update() { frame += 1; if ((frame % 60.0) === 0) { // only update once a second - checkSize(); // checks for size change to trigger windowResize notification locationY = 20.0; for (i = 0; i < arrays.length; i += 1) { //repositions overlays as others fade nextOverlay = Overlays.getOverlayAtPoint({ x: overlayLocationX, y: locationY }); @@ -533,16 +513,6 @@ function isStartingUp() { return startingUp; } -// Triggers mic mute notification -function onMuteStateChanged() { - var muteState, - muteString; - - muteState = AudioDevice.getMuted() ? "muted" : "unmuted"; - muteString = "Microphone is now " + muteState; - createNotification(muteString, NotificationType.MUTE_TOGGLE); -} - function onDomainConnectionRefused(reason) { createNotification("Connection refused: " + reason, NotificationType.CONNECTION_REFUSED); } @@ -653,7 +623,6 @@ LODManager.LODDecreased.connect(function() { } }); -AudioDevice.muteToggled.connect(onMuteStateChanged); Controller.keyPressEvent.connect(keyPressEvent); Controller.mousePressEvent.connect(mousePressEvent); Controller.keyReleaseEvent.connect(keyReleaseEvent); diff --git a/server-console/src/content-update.css b/server-console/src/content-update.css index ebfd8aeba0..1ac32bd220 100644 --- a/server-console/src/content-update.css +++ b/server-console/src/content-update.css @@ -56,98 +56,99 @@ a:hover { color: #2D88A4; } -.header{ +.header { width: 95%; left: 2.5% } -.colmask{ +.colmask { width: 95%; left: 2.5% } -.colmid{ right: 25% } -.colin{ right: 25% } -.colleft{ right: 25% } -.col1{ +.colmid { right: 25% } +.colin { right: 25% } +.colleft { right: 25% } +.col1 { width: 23%; left: 101% } -.col2{ +.col2 { width: 23%; left: 53% } -.col3{ +.col3 { width: 23%; left: 80% } -.col4{ +.col4 { width: 23%; left: 82% } -.footer{ +.footer { width: 95%; left: 2.5% } -.header{ +.header { clear: both; float: left; position: relative; border-bottom: #000 1px solid; background-color: #b4d2f7 } -.colmask{ +.colmask { clear: both; float: left; overflow: hidden; position: relative; } -.colmid{ +.colmid { float: left; width: 100%; position: relative; } -.colin{ +.colin { float: left; width: 100%; position: relative; } -.colleft{ +.colleft { float: left; width: 100%; position: relative; } -.col1{ +.col1 { + padding: 0px 0px 1em 0px; + overflow: hidden; + float: left; + position: relative; + word-wrap: break-word; + +} +.col2 { padding: 0px 0px 1em 0px; overflow: hidden; float: left; position: relative; } -.col2{ +.col3 { padding: 0px 0px 1em 0px; overflow: hidden; float: left; position: relative; } -.col3{ +.col4 { padding: 0px 0px 1em 0px; overflow: hidden; float: left; position: relative; } -.col4{ - padding: 0px 0px 1em 0px; - overflow: hidden; - float: left; - position: relative; - -} -.footer{ +.footer { clear: both; float: left; position: relative; @@ -155,7 +156,7 @@ a:hover { border-top: #000 1px solid; } -.bottom{ +.bottom { clear: both; width: 100%; float: left; diff --git a/server-console/src/main.js b/server-console/src/main.js index ea8ba6337e..b8e661ffbd 100644 --- a/server-console/src/main.js +++ b/server-console/src/main.js @@ -501,16 +501,15 @@ function backupResourceDirectories(folder) { fs.mkdirSync(folder); console.log("Created directory " + folder); - var dsBackup = path.join(folder, '/domain-server'); - fs.renameSync(getDomainServerClientResourcesDirectory(), dsBackup); - console.log("Moved directory " + getDomainServerClientResourcesDirectory()); - console.log("to " + dsBackup); - var acBackup = path.join(folder, '/assignment-client'); - fs.renameSync(getAssignmentClientResourcesDirectory(), acBackup); - console.log("Moved directory " + getDomainServerClientResourcesDirectory()); - console.log("to " + acBackup); + + fs.copySync(getDomainServerClientResourcesDirectory(), dsBackup); + fs.copySync(getAssignmentClientResourcesDirectory(), acBackup); + + fs.removeSync(getDomainServerClientResourcesDirectory()); + fs.removeSync(getAssignmentClientResourcesDirectory()); + return true; } catch (e) { console.log(e); @@ -519,23 +518,22 @@ function backupResourceDirectories(folder) { } function openBackupInstructions(folder) { - // Explain user how to restore server - var window = new BrowserWindow({ - icon: appIcon, - width: 800, - height: 520, - }); - window.loadURL('file://' + __dirname + '/content-update.html'); - if (!debug) { - window.setMenu(null); - } - window.show(); - - electron.ipcMain.on('ready', function() { - console.log("got ready"); - window.webContents.send('update', folder); - }); + // Explain user how to restore server + var window = new BrowserWindow({ + icon: appIcon, + width: 800, + height: 520, + }); + window.loadURL('file://' + __dirname + '/content-update.html'); + if (!debug) { + window.setMenu(null); + } + window.show(); + electron.ipcMain.on('ready', function() { + console.log("got ready"); + window.webContents.send('update', folder); + }); } function backupResourceDirectoriesAndRestart() { homeServer.stop(); @@ -576,15 +574,30 @@ function checkNewContent() { dialog.showMessageBox({ type: 'question', buttons: ['Yes', 'No'], + defaultId: 1, + cancelId: 1, title: 'New home content', - message: 'A newer version of the home content set is available.\nDo you wish to update?' + message: 'A newer version of the home content set is available.\nDo you wish to update?', + noLink: true, }, function(idx) { - if (idx === 0) { - backupResourceDirectoriesAndRestart(); - } else { - // They don't want to update, mark content set as current - userConfig.set('homeContentLastModified', new Date()); - } + if (idx === 0) { + dialog.showMessageBox({ + type: 'warning', + buttons: ['Yes', 'No'], + defaultId: 1, + cancelId: 1, + title: 'Are you sure?', + message: 'Updating with the new content will remove all your current content and settings and place them in a backup folder.\nAre you sure?', + noLink: true, + }, function(idx) { + if (idx === 0) { + backupResourceDirectoriesAndRestart(); + } + }); + } else { + // They don't want to update, mark content set as current + userConfig.set('homeContentLastModified', new Date()); + } }); } }