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 4d4c63930e..6c80cfb9fd 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -68,6 +68,7 @@ #include #include #include +#include #include #include #include @@ -4918,6 +4919,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/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 74cdb78b46..164abbd671 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 6733475c01..3e6e53ffc3 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp @@ -16,8 +16,8 @@ #include #include +#include #include -#include #include "../gl/GLTexelFormat.h" @@ -25,13 +25,6 @@ using namespace gpu; using namespace gpu::gl; using namespace gpu::gl45; -#ifdef Q_OS_WIN -static const QString DEBUG_FLAG("HIFI_DISABLE_SPARSE_TEXTURES"); -static bool enableSparseTextures = !QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); -#else -static bool enableSparseTextures = false; -#endif - // Allocate 1 MB of buffer space for paged transfers #define DEFAULT_PAGE_BUFFER_SIZE (1024*1024) #define DEFAULT_GL_PIXEL_ALIGNMENT 4 @@ -253,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(); } } @@ -352,6 +345,31 @@ void GL45Texture::startTransfer() { } bool GL45Texture::continueTransfer() { + 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) { + if (_gpuObject.isStoredMipFaceAvailable(mipLevel, face)) { + auto mip = _gpuObject.accessStoredMipFace(mipLevel, face); + GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat(), mip->getFormat()); + auto size = _gpuObject.evalMipDimensions(mipLevel); + if (GL_TEXTURE_2D == _target) { + glTextureSubImage2D(_id, mipLevel, 0, 0, size.x, size.y, texelFormat.format, texelFormat.type, mip->readData()); + } else if (GL_TEXTURE_CUBE_MAP == _target) { + // DSA ARB does not work on AMD, so use EXT + // glTextureSubImage3D(_id, mipLevel, 0, 0, face, size.x, size.y, 1, texelFormat.format, texelFormat.type, mip->readData()); + auto target = CUBE_FACE_LAYOUT[face]; + glTextureSubImage2DEXT(_id, target, mipLevel, 0, 0, size.x, size.y, texelFormat.format, texelFormat.type, mip->readData()); + } else { + Q_ASSERT(false); + } + (void)CHECK_GL_ERROR(); + } + } + } + return false; + } + static std::vector buffer; if (buffer.empty()) { buffer.resize(DEFAULT_PAGE_BUFFER_SIZE); 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/server-console/src/content-update.css b/server-console/src/content-update.css new file mode 100644 index 0000000000..9303e450b5 --- /dev/null +++ b/server-console/src/content-update.css @@ -0,0 +1,172 @@ +@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; + +} +.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..e297ca3276 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() { @@ -537,16 +578,7 @@ function checkNewContent() { message: 'A newer version of the home content set is available.\nDo you wish to update?' }, 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(); - } - }); + backupResourceDirectoriesAndRestart(); } else { // They don't want to update, mark content set as current userConfig.set('homeContentLastModified', new Date());