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/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 2d2f9c267e..ccd80a4a11 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -269,19 +269,17 @@ void AudioMixer::addStreamToMixForListeningNodeWithStream(AudioMixerClientData& bool forceSilentBlock = true; if (!streamToAdd.getLastPopOutput().isNull()) { + bool isInjector = dynamic_cast(&streamToAdd); - // reptition with fade is enabled, and we do have a valid previous frame to repeat - // so we mix the previously-mixed block - - // this is preferable to not mixing it at all to avoid the harsh jump to silence + // in an injector, just go silent - the injector has likely ended + // in other inputs (microphone, &c.), repeat with fade to avoid the harsh jump to silence // we'll repeat the last block until it has a block to mix // and we'll gradually fade that repeated block into silence. // calculate its fade factor, which depends on how many times it's already been repeated. - repeatedFrameFadeFactor = calculateRepeatedFrameFadeFactor(streamToAdd.getConsecutiveNotMixedCount() - 1); - if (repeatedFrameFadeFactor > 0.0f) { + if (!isInjector && repeatedFrameFadeFactor > 0.0f) { // apply the repeatedFrameFadeFactor to the gain gain *= repeatedFrameFadeFactor; 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..da0ee35769 100644 --- a/cmake/macros/PackageLibrariesForDeployment.cmake +++ b/cmake/macros/PackageLibrariesForDeployment.cmake @@ -49,6 +49,7 @@ macro(PACKAGE_LIBRARIES_FOR_DEPLOYMENT) 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 () 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 8b8d1e531b..ec6eed1c05 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(); @@ -4360,8 +4376,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; @@ -4918,6 +4939,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/Menu.cpp b/interface/src/Menu.cpp index 3c1aa26a4a..74490b6dd1 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -355,7 +355,7 @@ Menu::Menu() { //const QString = "1024 MB"; //const QString = "2048 MB"; - // Developer > Render > Resolution + // Developer > Render > Maximum Texture Memory MenuWrapper* textureMenu = renderOptionsMenu->addMenu(MenuOption::RenderMaxTextureMemory); QActionGroup* textureGroup = new QActionGroup(textureMenu); textureGroup->setExclusive(true); @@ -383,6 +383,43 @@ Menu::Menu() { gpu::Texture::setAllowedGPUMemoryUsage(newMaxTextureMemory); }); +#ifdef Q_OS_WIN + #define MIN_CORES_FOR_INCREMENTAL_TEXTURES 5 + bool recommendedIncrementalTransfers = (QThread::idealThreadCount() >= MIN_CORES_FOR_INCREMENTAL_TEXTURES); + bool recommendedSparseTextures = recommendedIncrementalTransfers; + + qDebug() << "[TEXTURE TRANSFER SUPPORT]" + << "\n\tidealThreadCount:" << QThread::idealThreadCount() + << "\n\tRECOMMENDED enableSparseTextures:" << recommendedSparseTextures + << "\n\tRECOMMENDED enableIncrementalTextures:" << recommendedIncrementalTransfers; + + gpu::Texture::setEnableIncrementalTextureTransfers(recommendedIncrementalTransfers); + gpu::Texture::setEnableSparseTextures(recommendedSparseTextures); + + // Developer > Render > Enable Dynamic Texture Management + { + auto action = addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::EnableDynamicTextureManagement, 0, recommendedSparseTextures); + connect(action, &QAction::triggered, [&](bool checked) { + qDebug() << "[TEXTURE TRANSFER SUPPORT] --- Enable Dynamic Texture Management menu option:" << checked; + gpu::Texture::setEnableSparseTextures(checked); + }); + } + + // Developer > Render > Enable Incremental Texture Transfer + { + auto action = addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::EnableIncrementalTextureTransfer, 0, recommendedIncrementalTransfers); + connect(action, &QAction::triggered, [&](bool checked) { + qDebug() << "[TEXTURE TRANSFER SUPPORT] --- Enable Incremental Texture Transfer menu option:" << checked; + gpu::Texture::setEnableIncrementalTextureTransfers(checked); + }); + } + +#else + qDebug() << "[TEXTURE TRANSFER SUPPORT] Incremental Texture Transfer and Dynamic Texture Management not supported on this platform."; +#endif + + + // Developer > Render > LOD Tools addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::LodTools, 0, dialogsManager.data(), SLOT(lodTools())); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index b25603caeb..95cd4c5aa6 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -98,6 +98,8 @@ namespace MenuOption { const QString EchoLocalAudio = "Echo Local Audio"; const QString EchoServerAudio = "Echo Server Audio"; const QString EnableCharacterController = "Enable avatar collisions"; + const QString EnableIncrementalTextureTransfer = "Enable Incremental Texture Transfer"; + const QString EnableDynamicTextureManagement = "Enable Dynamic Texture Management"; const QString EnableInverseKinematics = "Enable Inverse Kinematics"; const QString ExpandMyAvatarSimulateTiming = "Expand /myAvatar/simulation"; const QString ExpandMyAvatarTiming = "Expand /myAvatar"; 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 b9e6a92d8e..36b7b4886f 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp @@ -18,7 +18,6 @@ #include #include -#include #include "../gl/GLTexelFormat.h" @@ -26,34 +25,6 @@ using namespace gpu; using namespace gpu::gl; using namespace gpu::gl45; -#ifdef Q_OS_WIN -#define MIN_CORES_FOR_INCREMENTAL_TEXTURES 5 -static const QString DEBUG_FLAG_INCREMENTAL("HIFI_DISABLE_INCREMENTAL_TEXTURES"); -static const QString DEBUG_FLAG_SPARSE("HIFI_DISABLE_SPARSE_TEXTURES"); - -static const bool enableIncrementalTextures = (QThread::idealThreadCount() >= MIN_CORES_FOR_INCREMENTAL_TEXTURES) && - !QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG_INCREMENTAL); - -static const bool enableSparseTextures = enableIncrementalTextures && - !QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG_SPARSE); - -class TextureTransferDebug { -public: - TextureTransferDebug() { - qDebug() << "[TEXTURE TRANSFER SUPPORT]" - << "\n\tHIFI_DISABLE_INCREMENTAL_TEXTURES:" << QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG_INCREMENTAL) - << "\n\tHIFI_DISABLE_SPARSE_TEXTURES:" << QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG_SPARSE) - << "\n\tidealThreadCount:" << QThread::idealThreadCount() - << "\n\tenableSparseTextures:" << enableSparseTextures - << "\n\tenableIncrementalTextures:" << enableSparseTextures; - } -}; -TextureTransferDebug sparseTextureDebugInfo; -#else -static bool enableSparseTextures = false; -static bool enableIncrementalTextures = false; -#endif - // Allocate 1 MB of buffer space for paged transfers #define DEFAULT_PAGE_BUFFER_SIZE (1024*1024) #define DEFAULT_GL_PIXEL_ALIGNMENT 4 @@ -275,7 +246,7 @@ GLuint GL45Backend::getTextureID(const TexturePointer& texture, bool transfer) { GL45Texture::GL45Texture(const std::weak_ptr& backend, const Texture& texture, bool transferrable) : GLTexture(backend, texture, allocate(texture), transferrable), _sparseInfo(*this), _transferState(*this) { - if (enableSparseTextures && _transferrable) { + if (_transferrable && Texture::getEnableSparseTextures()) { _sparseInfo.maybeMakeSparse(); } } @@ -374,7 +345,7 @@ void GL45Texture::startTransfer() { } bool GL45Texture::continueTransfer() { - if (!enableIncrementalTextures) { + if (!Texture::getEnableIncrementalTextureTransfers()) { size_t maxFace = GL_TEXTURE_CUBE_MAP == _target ? CUBE_NUM_FACES : 1; for (uint8_t face = 0; face < maxFace; ++face) { for (uint16_t mipLevel = _minMip; mipLevel <= _maxMip; ++mipLevel) { @@ -540,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/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index b573c8e899..44804abebe 100755 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -9,6 +9,9 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // + +#include + #include "Texture.h" #include @@ -28,6 +31,34 @@ std::atomic Texture::_textureCPUCount{ 0 }; std::atomic Texture::_textureCPUMemoryUsage{ 0 }; std::atomic Texture::_allowedCPUMemoryUsage { 0 }; +std::atomic Texture::_enableSparseTextures { false }; +std::atomic Texture::_enableIncrementalTextureTransfers { false }; + +void Texture::setEnableSparseTextures(bool enabled) { +#ifdef Q_OS_WIN + qDebug() << "[TEXTURE TRANSFER SUPPORT] SETTING - Enable Sparse Textures and Dynamic Texture Management:" << enabled; + _enableSparseTextures = enabled; + if (!_enableIncrementalTextureTransfers && _enableSparseTextures) { + qDebug() << "[TEXTURE TRANSFER SUPPORT] WARNING - Sparse texture management requires incremental texture transfer enabled."; + } +#else + qDebug() << "[TEXTURE TRANSFER SUPPORT] Sparse Textures and Dynamic Texture Management not supported on this platform."; +#endif +} + +void Texture::setEnableIncrementalTextureTransfers(bool enabled) { +#ifdef Q_OS_WIN + qDebug() << "[TEXTURE TRANSFER SUPPORT] SETTING - Enable Incremental Texture Transfer:" << enabled; + _enableIncrementalTextureTransfers = enabled; + if (!_enableIncrementalTextureTransfers && _enableSparseTextures) { + qDebug() << "[TEXTURE TRANSFER SUPPORT] WARNING - Sparse texture management requires incremental texture transfer enabled."; + } +#else + qDebug() << "[TEXTURE TRANSFER SUPPORT] Incremental Texture Transfer not supported on this platform."; +#endif +} + + void Texture::updateTextureCPUMemoryUsage(Size prevObjectSize, Size newObjectSize) { if (prevObjectSize == newObjectSize) { return; diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index ae1afcafcb..61d03c070c 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -144,6 +144,9 @@ class Texture : public Resource { static std::atomic _textureCPUMemoryUsage; static std::atomic _allowedCPUMemoryUsage; static void updateTextureCPUMemoryUsage(Size prevObjectSize, Size newObjectSize); + + static std::atomic _enableSparseTextures; + static std::atomic _enableIncrementalTextureTransfers; public: static uint32_t getTextureCPUCount(); static Size getTextureCPUMemoryUsage(); @@ -154,6 +157,12 @@ public: static Size getAllowedGPUMemoryUsage(); static void setAllowedGPUMemoryUsage(Size size); + static bool getEnableSparseTextures() { return _enableSparseTextures.load(); } + static bool getEnableIncrementalTextureTransfers() { return _enableIncrementalTextureTransfers.load(); } + + static void setEnableSparseTextures(bool enabled); + static void setEnableIncrementalTextureTransfers(bool enabled); + class Usage { public: enum FlagBit { 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/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 new file mode 100644 index 0000000000..1ac32bd220 --- /dev/null +++ b/server-console/src/content-update.css @@ -0,0 +1,173 @@ +@font-face { + font-family: 'Raleway'; + src: url('vendor/Raleway/Raleway-Regular.ttf'); + font-weight: normal; + font-style: normal; +} +@font-face { + font-family: 'Raleway'; + src: url('vendor/Raleway/Raleway-ExtraLight.ttf'); + font-weight: 200; + font-style: normal; +} +@font-face { + font-family: 'Raleway'; + src: url('vendor/Raleway/Raleway-SemiBold.ttf'); + font-weight: bold; + font-style: normal; +} + + +* { + font-family: "Raleway", "Open Sans", Arial, Helvetica, sans-serif; + line-height: 130%; +} + +body { + margin: 0; + padding: 0; + color: #808785; + margin: 0 auto; + text-align: left; + font-size: 13.5pt; + -webkit-touch-callout: none; -webkit-user-select: none; + cursor: default; + overflow: hidden; + font-variant-numeric: lining-nums; + -moz-font-feature-settings: "lnum"; + -webkit-font-feature-settings: "lnum"; + font-feature-settings: "lnum"; +} + +.selectable { + -webkit-touch-callout: text; + -webkit-user-select: text; + cursor: text; +} + +a:link, +a:visited, +a:hover, +a:active { + color: #B4B4B4; +} + +a:hover { + color: #2D88A4; +} + +.header { + width: 95%; + left: 2.5% +} +.colmask { + width: 95%; + left: 2.5% +} +.colmid { right: 25% } +.colin { right: 25% } +.colleft { right: 25% } +.col1 { + width: 23%; + left: 101% +} +.col2 { + width: 23%; + left: 53% +} +.col3 { + width: 23%; + left: 80% +} +.col4 { + width: 23%; + left: 82% +} +.footer { + width: 95%; + left: 2.5% +} +.header { + clear: both; + float: left; + position: relative; + border-bottom: #000 1px solid; + background-color: #b4d2f7 +} +.colmask { + clear: both; + float: left; + overflow: hidden; + position: relative; + +} +.colmid { + float: left; + width: 100%; + position: relative; + +} +.colin { + float: left; + width: 100%; + position: relative; + +} +.colleft { + float: left; + width: 100%; + position: relative; + +} +.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; + +} +.col3 { + padding: 0px 0px 1em 0px; + overflow: hidden; + float: left; + position: relative; + +} +.col4 { + padding: 0px 0px 1em 0px; + overflow: hidden; + float: left; + position: relative; + +} +.footer { + clear: both; + float: left; + position: relative; + padding-top: 8px; + border-top: #000 1px solid; + +} +.bottom { + clear: both; + width: 100%; + float: left; + position: relative; + +} +body { + border-width: 0px; + padding: 0px; + margin: 0px; + font-size: 90%; + width: 100%; + min-width: 600px; +} diff --git a/server-console/src/content-update.html b/server-console/src/content-update.html new file mode 100644 index 0000000000..c4ed14473a --- /dev/null +++ b/server-console/src/content-update.html @@ -0,0 +1,53 @@ + + + + Server Backup + + + + +
+

We backed up your old Sandbox content, just in case.

+

To restore it, follow these steps: + +

+
+ +
+ + + +
+ Step 2 +

2. Go to your backup directory: + +

+ +
+ Step 1 +

1. Stop your Sandbox server. +

+ +
+ Step 3 +

3. Copy the backed up content and paste it into the parent directory. +

+ +
+ Step 4 +

4. Restart your Sandbox server. +

+ +
+ +
+ +
+ + +
+ + diff --git a/server-console/src/content-update.js b/server-console/src/content-update.js new file mode 100644 index 0000000000..c77cfc92c6 --- /dev/null +++ b/server-console/src/content-update.js @@ -0,0 +1,12 @@ +function ready() { + console.log("Ready"); + + const electron = require('electron'); + window.$ = require('./vendor/jquery/jquery-2.1.4.min.js'); + + electron.ipcRenderer.on('update', function(event, message) { + $('#directory').html(message); + }); + + electron.ipcRenderer.send('ready'); +} diff --git a/server-console/src/images/step1.jpg b/server-console/src/images/step1.jpg new file mode 100644 index 0000000000..cd80ae5537 Binary files /dev/null and b/server-console/src/images/step1.jpg differ diff --git a/server-console/src/images/step2.jpg b/server-console/src/images/step2.jpg new file mode 100644 index 0000000000..6bc285ac47 Binary files /dev/null and b/server-console/src/images/step2.jpg differ diff --git a/server-console/src/images/step3.jpg b/server-console/src/images/step3.jpg new file mode 100644 index 0000000000..a819dd2cdb Binary files /dev/null and b/server-console/src/images/step3.jpg differ diff --git a/server-console/src/images/step4.jpg b/server-console/src/images/step4.jpg new file mode 100644 index 0000000000..27694aee74 Binary files /dev/null and b/server-console/src/images/step4.jpg differ diff --git a/server-console/src/main.js b/server-console/src/main.js index 82fe6b6b4d..b8e661ffbd 100644 --- a/server-console/src/main.js +++ b/server-console/src/main.js @@ -408,6 +408,13 @@ var labels = { logWindow.open(); } }, + restoreBackup: { + label: 'Restore Backup Instructions', + click: function() { + var folder = getRootHifiDataDirectory() + "/Server Backup"; + openBackupInstructions(folder); + } + }, share: { label: 'Share', click: function() { @@ -443,6 +450,7 @@ function buildMenuArray(serverState) { menuArray.push(labels.stopServer); menuArray.push(labels.settings); menuArray.push(labels.viewLogs); + menuArray.push(labels.restoreBackup); menuArray.push(separator); menuArray.push(labels.share); menuArray.push(separator); @@ -488,27 +496,60 @@ function updateTrayMenu(serverState) { const httpStatusPort = 60332; -function deleteResourceDirectories() { - const dsResourceDirectory = getDomainServerClientResourcesDirectory(); +function backupResourceDirectories(folder) { try { - fs.removeSync(dsResourceDirectory); - console.log("Deleted directory " + dsResourceDirectory); - } catch (e) { - console.log(e); - } - const acResourceDirectory = getAssignmentClientResourcesDirectory(); - try { - fs.removeSync(acResourceDirectory); - console.log("Deleted directory " + acResourceDirectory); + fs.mkdirSync(folder); + console.log("Created directory " + folder); + + var dsBackup = path.join(folder, '/domain-server'); + var acBackup = path.join(folder, '/assignment-client'); + + fs.copySync(getDomainServerClientResourcesDirectory(), dsBackup); + fs.copySync(getAssignmentClientResourcesDirectory(), acBackup); + + fs.removeSync(getDomainServerClientResourcesDirectory()); + fs.removeSync(getAssignmentClientResourcesDirectory()); + + return true; } catch (e) { console.log(e); + return false; } } -function deleteResourceDirectoriesAndRestart() { +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); + }); +} +function backupResourceDirectoriesAndRestart() { homeServer.stop(); - deleteResourceDirectories(); - maybeInstallDefaultContentSet(onContentLoaded); + + var folder = getRootHifiDataDirectory() + "/Server Backup - " + Date.now(); + if (backupResourceDirectories(folder)) { + maybeInstallDefaultContentSet(onContentLoaded); + openBackupInstructions(folder); + } else { + dialog.showMessageBox({ + type: 'warning', + buttons: ['Ok'], + title: 'Update Error', + message: 'There was an error updating the content, aborting.' + }, function() {}); + } } function checkNewContent() { @@ -533,24 +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) { - dialog.showMessageBox({ - type: 'question', - buttons: ['Yes', 'No'], - title: 'Are you sure?', - message: 'This action will delete your current sandbox content.\nDo you wish to continue?' - }, function(idx) { - if (idx === 0 && homeServer) { - deleteResourceDirectoriesAndRestart(); - } - }); - } 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()); + } }); } }