diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index 48ffc2fdbc..3e36250a82 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 63ad0c5899..8a7d03ba75 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -380,6 +380,14 @@ "default": "0", "advanced": false }, + { + "name": "maximum_user_capacity_redirect_location", + "label": "Redirect to Location on Maximum Capacity", + "help": "Is there another domain, you'd like to redirect clients to when the maximum number of avatars are connected.", + "placeholder": "", + "default": "", + "advanced": false + }, { "name": "standard_permissions", "type": "table", diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 23a53c3eb0..e33cbe1755 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -317,6 +317,7 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo } const QString MAXIMUM_USER_CAPACITY = "security.maximum_user_capacity"; +const QString MAXIMUM_USER_CAPACITY_REDIRECT_LOCATION = "security.maximum_user_capacity_redirect_location"; SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnectionData& nodeConnection, const QString& username, @@ -363,7 +364,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect if (!userPerms.can(NodePermissions::Permission::canConnectToDomain)) { sendConnectionDeniedPacket("You lack the required permissions to connect to this domain.", - nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::TooManyUsers); + nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::NotAuthorized); #ifdef WANT_DEBUG qDebug() << "stalling login due to permissions:" << username; #endif @@ -372,8 +373,16 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect if (!userPerms.can(NodePermissions::Permission::canConnectPastMaxCapacity) && !isWithinMaxCapacity()) { // we can't allow this user to connect because we are at max capacity + QString redirectOnMaxCapacity; + const QVariant* redirectOnMaxCapacityVariant = + valueForKeyPath(_server->_settingsManager.getSettingsMap(), MAXIMUM_USER_CAPACITY_REDIRECT_LOCATION); + if (redirectOnMaxCapacityVariant && redirectOnMaxCapacityVariant->canConvert()) { + redirectOnMaxCapacity = redirectOnMaxCapacityVariant->toString(); + qDebug() << "Redirection domain:" << redirectOnMaxCapacity; + } + sendConnectionDeniedPacket("Too many connected users.", nodeConnection.senderSockAddr, - DomainHandler::ConnectionRefusedReason::TooManyUsers); + DomainHandler::ConnectionRefusedReason::TooManyUsers, redirectOnMaxCapacity); #ifdef WANT_DEBUG qDebug() << "stalling login due to max capacity:" << username; #endif @@ -623,22 +632,30 @@ void DomainGatekeeper::sendProtocolMismatchConnectionDenial(const HifiSockAddr& } void DomainGatekeeper::sendConnectionDeniedPacket(const QString& reason, const HifiSockAddr& senderSockAddr, - DomainHandler::ConnectionRefusedReason reasonCode) { + DomainHandler::ConnectionRefusedReason reasonCode, + QString extraInfo) { // this is an agent and we've decided we won't let them connect - send them a packet to deny connection - QByteArray utfString = reason.toUtf8(); - quint16 payloadSize = utfString.size(); + QByteArray utfReasonString = reason.toUtf8(); + quint16 reasonSize = utfReasonString.size(); + + QByteArray utfExtraInfo = extraInfo.toUtf8(); + quint16 extraInfoSize = utfExtraInfo.size(); // setup the DomainConnectionDenied packet auto connectionDeniedPacket = NLPacket::create(PacketType::DomainConnectionDenied, - payloadSize + sizeof(payloadSize) + sizeof(uint8_t)); + sizeof(uint8_t) + // reasonCode + reasonSize + sizeof(reasonSize) + + extraInfoSize + sizeof(extraInfoSize)); // pack in the reason the connection was denied (the client displays this) - if (payloadSize > 0) { - uint8_t reasonCodeWire = (uint8_t)reasonCode; - connectionDeniedPacket->writePrimitive(reasonCodeWire); - connectionDeniedPacket->writePrimitive(payloadSize); - connectionDeniedPacket->write(utfString); - } + uint8_t reasonCodeWire = (uint8_t)reasonCode; + connectionDeniedPacket->writePrimitive(reasonCodeWire); + connectionDeniedPacket->writePrimitive(reasonSize); + connectionDeniedPacket->write(utfReasonString); + + // write the extra info as well + connectionDeniedPacket->writePrimitive(extraInfoSize); + connectionDeniedPacket->write(utfExtraInfo); // send the packet off DependencyManager::get()->sendPacket(std::move(connectionDeniedPacket), senderSockAddr); diff --git a/domain-server/src/DomainGatekeeper.h b/domain-server/src/DomainGatekeeper.h index 06ecfcf285..b7d2a03af6 100644 --- a/domain-server/src/DomainGatekeeper.h +++ b/domain-server/src/DomainGatekeeper.h @@ -88,7 +88,8 @@ private: void sendConnectionTokenPacket(const QString& username, const HifiSockAddr& senderSockAddr); static void sendConnectionDeniedPacket(const QString& reason, const HifiSockAddr& senderSockAddr, - DomainHandler::ConnectionRefusedReason reasonCode = DomainHandler::ConnectionRefusedReason::Unknown); + DomainHandler::ConnectionRefusedReason reasonCode = DomainHandler::ConnectionRefusedReason::Unknown, + QString extraInfo = QString()); void pingPunchForConnectingPeer(const SharedNetworkPeer& peer); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index b18451a833..e3670108bb 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -66,8 +67,11 @@ DomainServer::DomainServer(int argc, char* argv[]) : _webAuthenticationStateSet(), _cookieSessionHash(), _automaticNetworkingSetting(), - _settingsManager() + _settingsManager(), + _iceServerAddr(ICE_SERVER_DEFAULT_HOSTNAME), + _iceServerPort(ICE_SERVER_DEFAULT_PORT) { + parseCommandLine(); qInstallMessageHandler(LogHandler::verboseMessageHandler); LogUtils::init(); @@ -159,6 +163,53 @@ DomainServer::DomainServer(int argc, char* argv[]) : qDebug() << "domain-server is running"; } +void DomainServer::parseCommandLine() { + QCommandLineParser parser; + parser.setApplicationDescription("High Fidelity Domain Server"); + parser.addHelpOption(); + + const QCommandLineOption iceServerAddressOption("i", "ice-server address", "IP:PORT or HOSTNAME:PORT"); + parser.addOption(iceServerAddressOption); + + const QCommandLineOption domainIDOption("d", "domain-server uuid"); + parser.addOption(domainIDOption); + + const QCommandLineOption getTempNameOption("get-temp-name", "Request a temporary domain-name"); + parser.addOption(getTempNameOption); + + const QCommandLineOption masterConfigOption("master-config", "Deprecated config-file option"); + parser.addOption(masterConfigOption); + + + if (!parser.parse(QCoreApplication::arguments())) { + qWarning() << parser.errorText() << endl; + parser.showHelp(); + Q_UNREACHABLE(); + } + + if (parser.isSet(iceServerAddressOption)) { + // parse the IP and port combination for this target + QString hostnamePortString = parser.value(iceServerAddressOption); + + _iceServerAddr = hostnamePortString.left(hostnamePortString.indexOf(':')); + _iceServerPort = (quint16) hostnamePortString.mid(hostnamePortString.indexOf(':') + 1).toUInt(); + if (_iceServerPort == 0) { + _iceServerPort = ICE_SERVER_DEFAULT_PORT; + } + + if (_iceServerAddr.isEmpty()) { + qWarning() << "Could not parse an IP address and port combination from" << hostnamePortString; + QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection); + } + } + + if (parser.isSet(domainIDOption)) { + _overridingDomainID = QUuid(parser.value(domainIDOption)); + _overrideDomainID = true; + qDebug() << "domain-server ID is" << _overridingDomainID; + } +} + DomainServer::~DomainServer() { // destroy the LimitedNodeList before the DomainServer QCoreApplication is down DependencyManager::destroy(); @@ -166,7 +217,7 @@ DomainServer::~DomainServer() { void DomainServer::queuedQuit(QString quitMessage, int exitCode) { if (!quitMessage.isEmpty()) { - qCritical() << qPrintable(quitMessage); + qWarning() << qPrintable(quitMessage); } QCoreApplication::exit(exitCode); @@ -307,7 +358,7 @@ void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) { auto domainObject = jsonObject[DATA_KEY].toObject()[DOMAIN_KEY].toObject(); if (!domainObject.isEmpty()) { - auto id = domainObject[ID_KEY].toString(); + auto id = _overrideDomainID ? _overridingDomainID.toString() : domainObject[ID_KEY].toString(); auto name = domainObject[NAME_KEY].toString(); auto key = domainObject[KEY_KEY].toString(); @@ -415,24 +466,30 @@ void DomainServer::setupNodeListAndAssignments() { quint16 localHttpsPort = DOMAIN_SERVER_HTTPS_PORT; nodeList->putLocalPortIntoSharedMemory(DOMAIN_SERVER_LOCAL_HTTPS_PORT_SMEM_KEY, this, localHttpsPort); - // set our LimitedNodeList UUID to match the UUID from our config // nodes will currently use this to add resources to data-web that relate to our domain - const QVariant* idValueVariant = valueForKeyPath(settingsMap, METAVERSE_DOMAIN_ID_KEY_PATH); - if (idValueVariant) { - nodeList->setSessionUUID(idValueVariant->toString()); + bool isMetaverseDomain = false; + if (_overrideDomainID) { + nodeList->setSessionUUID(_overridingDomainID); + isMetaverseDomain = true; // assume metaverse domain + } else { + const QVariant* idValueVariant = valueForKeyPath(settingsMap, METAVERSE_DOMAIN_ID_KEY_PATH); + if (idValueVariant) { + nodeList->setSessionUUID(idValueVariant->toString()); + isMetaverseDomain = true; // if we have an ID, we'll assume we're a metaverse domain + } else { + nodeList->setSessionUUID(QUuid::createUuid()); // Use random UUID + } + } - // if we have an ID, we'll assume we're a metaverse domain - // now see if we think we're a temp domain (we have an API key) or a full domain + if (isMetaverseDomain) { + // see if we think we're a temp domain (we have an API key) or a full domain const auto& temporaryDomainKey = DependencyManager::get()->getTemporaryDomainKey(getID()); if (temporaryDomainKey.isEmpty()) { _type = MetaverseDomain; } else { _type = MetaverseTemporaryDomain; } - - } else { - nodeList->setSessionUUID(QUuid::createUuid()); // Use random UUID } connect(nodeList.data(), &LimitedNodeList::nodeAdded, this, &DomainServer::nodeAdded); @@ -548,7 +605,6 @@ void DomainServer::setupAutomaticNetworking() { } else { qDebug() << "Cannot enable domain-server automatic networking without a domain ID." << "Please add an ID to your config file or via the web interface."; - return; } } @@ -606,12 +662,11 @@ void DomainServer::setupICEHeartbeatForFullNetworking() { void DomainServer::updateICEServerAddresses() { if (_iceAddressLookupID == -1) { - _iceAddressLookupID = QHostInfo::lookupHost(ICE_SERVER_DEFAULT_HOSTNAME, this, SLOT(handleICEHostInfo(QHostInfo))); + _iceAddressLookupID = QHostInfo::lookupHost(_iceServerAddr, this, SLOT(handleICEHostInfo(QHostInfo))); } } void DomainServer::parseAssignmentConfigs(QSet& excludedTypes) { - // check for configs from the command line, these take precedence const QString ASSIGNMENT_CONFIG_REGEX_STRING = "config-([\\d]+)"; QRegExp assignmentConfigRegex(ASSIGNMENT_CONFIG_REGEX_STRING); diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index e30f4515cc..066f2be0d1 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -105,6 +105,7 @@ signals: private: const QUuid& getID(); + void parseCommandLine(); void setupNodeListAndAssignments(); bool optionallySetupOAuth(); @@ -205,6 +206,11 @@ private: friend class DomainGatekeeper; friend class DomainMetadata; + + QString _iceServerAddr; + int _iceServerPort; + bool _overrideDomainID { false }; // should we override the domain-id from settings? + QUuid _overridingDomainID { QUuid() }; // what should we override it with? }; diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 8a37662ca9..97a10ea232 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1239,8 +1239,15 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : firstRun.set(false); } -void Application::domainConnectionRefused(const QString& reasonMessage, int reasonCode) { - switch (static_cast(reasonCode)) { +void Application::domainConnectionRefused(const QString& reasonMessage, int reasonCodeInt, const QString& extraInfo) { + DomainHandler::ConnectionRefusedReason reasonCode = static_cast(reasonCodeInt); + + if (reasonCode == DomainHandler::ConnectionRefusedReason::TooManyUsers && !extraInfo.isEmpty()) { + DependencyManager::get()->handleLookupString(extraInfo); + return; + } + + switch (reasonCode) { case DomainHandler::ConnectionRefusedReason::ProtocolMismatch: case DomainHandler::ConnectionRefusedReason::TooManyUsers: case DomainHandler::ConnectionRefusedReason::Unknown: { diff --git a/interface/src/Application.h b/interface/src/Application.h index 02682defca..2dc60c2438 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -44,12 +44,12 @@ #include #include #include +#include #include "avatar/MyAvatar.h" #include "Bookmarks.h" #include "Camera.h" #include "ConnectionMonitor.h" -#include "FileLogger.h" #include "gpu/Context.h" #include "Menu.h" #include "octree/OctreePacketProcessor.h" @@ -375,7 +375,7 @@ private slots: void nodeKilled(SharedNodePointer node); static void packetSent(quint64 length); void updateDisplayMode(); - void domainConnectionRefused(const QString& reasonMessage, int reason); + void domainConnectionRefused(const QString& reasonMessage, int reason, const QString& extraInfo); private: static void initDisplay(); diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 715d0657a3..9303636a1f 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -58,7 +58,7 @@ public slots: signals: void domainChanged(const QString& domainHostname); void svoImportRequested(const QString& url); - void domainConnectionRefused(const QString& reasonMessage, int reasonCode); + void domainConnectionRefused(const QString& reasonMessage, int reasonCode, const QString& extraInfo); void snapshotTaken(const QString& path, bool notify); void snapshotShared(const QString& error); diff --git a/interface/src/ui/CachesSizeDialog.cpp b/interface/src/ui/CachesSizeDialog.cpp index dee9452c73..935a6d126e 100644 --- a/interface/src/ui/CachesSizeDialog.cpp +++ b/interface/src/ui/CachesSizeDialog.cpp @@ -58,7 +58,10 @@ void CachesSizeDialog::confirmClicked(bool checked) { DependencyManager::get()->setUnusedResourceCacheSize(_animations->value() * BYTES_PER_MEGABYTES); DependencyManager::get()->setUnusedResourceCacheSize(_geometries->value() * BYTES_PER_MEGABYTES); DependencyManager::get()->setUnusedResourceCacheSize(_sounds->value() * BYTES_PER_MEGABYTES); + // Disabling the texture cache because it's a liability in cases where we're overcommiting GPU memory +#if 0 DependencyManager::get()->setUnusedResourceCacheSize(_textures->value() * BYTES_PER_MEGABYTES); +#endif QDialog::close(); } @@ -78,4 +81,4 @@ void CachesSizeDialog::reject() { void CachesSizeDialog::closeEvent(QCloseEvent* event) { QDialog::closeEvent(event); emit closed(); -} \ No newline at end of file +} diff --git a/interface/src/ui/LogDialog.h b/interface/src/ui/LogDialog.h index c38cf84f00..1493a43f01 100644 --- a/interface/src/ui/LogDialog.h +++ b/interface/src/ui/LogDialog.h @@ -20,7 +20,7 @@ #include #include -#include "AbstractLoggerInterface.h" +#include class KeywordHighlighter : public QSyntaxHighlighter { Q_OBJECT diff --git a/interface/src/ui/Snapshot.cpp b/interface/src/ui/Snapshot.cpp index c2fcafb2f3..a0ee260bcc 100644 --- a/interface/src/ui/Snapshot.cpp +++ b/interface/src/ui/Snapshot.cpp @@ -25,7 +25,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index cc022d9df2..1c177cffc4 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -198,15 +198,10 @@ void RenderableWebEntityItem::render(RenderArgs* args) { #endif if (!_webSurface) { - #if defined(Q_OS_LINUX) - // these don't seem to work on Linux - return; - #else if (!buildWebSurface(static_cast(args->_renderer))) { return; } _fadeStartTime = usecTimestampNow(); - #endif } _lastRenderTime = usecTimestampNow(); diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp index e3f391126b..f070dfe637 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp @@ -39,6 +39,10 @@ static GLBackend* INSTANCE{ nullptr }; static const char* GL_BACKEND_PROPERTY_NAME = "com.highfidelity.gl.backend"; BackendPointer GLBackend::createBackend() { + // The ATI memory info extension only exposes 'free memory' so we want to force it to + // cache the value as early as possible + getDedicatedMemory(); + // FIXME provide a mechanism to override the backend for testing // Where the gpuContext is initialized and where the TRUE Backend is created and assigned auto version = QOpenGLContextWrapper::currentContextVersion(); @@ -589,7 +593,23 @@ void GLBackend::releaseQuery(GLuint id) const { _queriesTrash.push_back(id); } +void GLBackend::queueLambda(const std::function lambda) const { + Lock lock(_trashMutex); + _lambdaQueue.push_back(lambda); +} + void GLBackend::recycle() const { + { + std::list> lamdbasTrash; + { + Lock lock(_trashMutex); + std::swap(_lambdaQueue, lamdbasTrash); + } + for (auto lambda : lamdbasTrash) { + lambda(); + } + } + { std::vector ids; std::list> buffersTrash; @@ -679,6 +699,10 @@ void GLBackend::recycle() const { glDeleteQueries((GLsizei)ids.size(), ids.data()); } } + +#ifndef THREADED_TEXTURE_TRANSFER + gl::GLTexture::_textureTransferHelper->process(); +#endif } void GLBackend::setCameraCorrection(const Mat4& correction) { diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackend.h b/libraries/gpu-gl/src/gpu/gl/GLBackend.h index bc348e3c3f..af4851a1b0 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackend.h +++ b/libraries/gpu-gl/src/gpu/gl/GLBackend.h @@ -175,6 +175,7 @@ public: virtual void releaseShader(GLuint id) const; virtual void releaseProgram(GLuint id) const; virtual void releaseQuery(GLuint id) const; + virtual void queueLambda(const std::function lambda) const; protected: @@ -197,6 +198,7 @@ protected: mutable std::list _shadersTrash; mutable std::list _programsTrash; mutable std::list _queriesTrash; + mutable std::list> _lambdaQueue; void renderPassTransfer(const Batch& batch); void renderPassDraw(const Batch& batch); @@ -365,6 +367,7 @@ protected: typedef void (GLBackend::*CommandCall)(const Batch&, size_t); static CommandCall _commandCalls[Batch::NUM_COMMANDS]; friend class GLState; + friend class GLTexture; }; } } diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendPipeline.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendPipeline.cpp index 906144e013..f18962976c 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendPipeline.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendPipeline.cpp @@ -165,7 +165,7 @@ void GLBackend::do_setUniformBuffer(const Batch& batch, size_t paramOffset) { _uniform._buffers[slot] = uniformBuffer; (void) CHECK_GL_ERROR(); } else { - releaseResourceTexture(slot); + releaseUniformBuffer(slot); return; } } diff --git a/libraries/gpu-gl/src/gpu/gl/GLShared.cpp b/libraries/gpu-gl/src/gpu/gl/GLShared.cpp index d59be0d9de..af03da1931 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLShared.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLShared.cpp @@ -60,6 +60,32 @@ bool checkGLErrorDebug(const char* name) { #endif } +gpu::Size getFreeDedicatedMemory() { + Size result { 0 }; + static bool nvidiaMemorySupported { true }; + static bool atiMemorySupported { true }; + if (nvidiaMemorySupported) { + + GLint nvGpuMemory { 0 }; + glGetIntegerv(GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX, &nvGpuMemory); + if (GL_NO_ERROR == glGetError()) { + result = KB_TO_BYTES(nvGpuMemory); + } else { + nvidiaMemorySupported = false; + } + } else if (atiMemorySupported) { + GLint atiGpuMemory[4]; + // not really total memory, but close enough if called early enough in the application lifecycle + glGetIntegerv(GL_TEXTURE_FREE_MEMORY_ATI, atiGpuMemory); + if (GL_NO_ERROR == glGetError()) { + result = KB_TO_BYTES(atiGpuMemory[0]); + } else { + atiMemorySupported = false; + } + } + return result; +} + gpu::Size getDedicatedMemory() { static Size dedicatedMemory { 0 }; static std::once_flag once; diff --git a/libraries/gpu-gl/src/gpu/gl/GLShared.h b/libraries/gpu-gl/src/gpu/gl/GLShared.h index 7ec6deeb9d..b5dece7cf4 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLShared.h +++ b/libraries/gpu-gl/src/gpu/gl/GLShared.h @@ -25,6 +25,7 @@ void serverWait(); void clientWait(); gpu::Size getDedicatedMemory(); +gpu::Size getFreeDedicatedMemory(); ComparisonFunction comparisonFuncFromGL(GLenum func); State::StencilOp stencilOpFromGL(GLenum stencilOp); State::BlendOp blendOpFromGL(GLenum blendOp); diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp index 4bff5c87bd..39c3bd2f67 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp @@ -12,9 +12,244 @@ using namespace gpu; using namespace gpu::gl; -GLTexelFormat GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) { - GLTexelFormat texel = { GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE }; - return texel; +GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) { + GLenum result = GL_RGBA8; + switch (dstFormat.getDimension()) { + case gpu::SCALAR: { + switch (dstFormat.getSemantic()) { + case gpu::RGB: + case gpu::RGBA: + case gpu::SRGB: + case gpu::SRGBA: + switch (dstFormat.getType()) { + case gpu::UINT32: + result = GL_R32UI; + break; + case gpu::INT32: + result = GL_R32I; + break; + case gpu::NUINT32: + result = GL_R8; + break; + case gpu::NINT32: + result = GL_R8_SNORM; + break; + case gpu::FLOAT: + result = GL_R32F; + break; + case gpu::UINT16: + result = GL_R16UI; + break; + case gpu::INT16: + result = GL_R16I; + break; + case gpu::NUINT16: + result = GL_R16; + break; + case gpu::NINT16: + result = GL_R16_SNORM; + break; + case gpu::HALF: + result = GL_R16F; + break; + case gpu::UINT8: + result = GL_R8UI; + break; + case gpu::INT8: + result = GL_R8I; + break; + case gpu::NUINT8: + if ((dstFormat.getSemantic() == gpu::SRGB || dstFormat.getSemantic() == gpu::SRGBA)) { + result = GL_SLUMINANCE8; + } else { + result = GL_R8; + } + break; + case gpu::NINT8: + result = GL_R8_SNORM; + break; + + default: + Q_UNREACHABLE(); + break; + } + break; + + case gpu::COMPRESSED_R: + result = GL_COMPRESSED_RED_RGTC1; + break; + + case gpu::R11G11B10: + // the type should be float + result = GL_R11F_G11F_B10F; + break; + + case gpu::DEPTH: + result = GL_DEPTH_COMPONENT32; + switch (dstFormat.getType()) { + case gpu::UINT32: + case gpu::INT32: + case gpu::NUINT32: + case gpu::NINT32: + result = GL_DEPTH_COMPONENT32; + break; + case gpu::FLOAT: + result = GL_DEPTH_COMPONENT32F; + break; + case gpu::UINT16: + case gpu::INT16: + case gpu::NUINT16: + case gpu::NINT16: + case gpu::HALF: + result = GL_DEPTH_COMPONENT16; + break; + case gpu::UINT8: + case gpu::INT8: + case gpu::NUINT8: + case gpu::NINT8: + result = GL_DEPTH_COMPONENT24; + break; + default: + Q_UNREACHABLE(); + break; + } + break; + + case gpu::DEPTH_STENCIL: + result = GL_DEPTH24_STENCIL8; + break; + + default: + qCDebug(gpugllogging) << "Unknown combination of texel format"; + } + break; + } + + case gpu::VEC2: { + switch (dstFormat.getSemantic()) { + case gpu::RGB: + case gpu::RGBA: + result = GL_RG8; + break; + default: + qCDebug(gpugllogging) << "Unknown combination of texel format"; + } + + break; + } + + case gpu::VEC3: { + switch (dstFormat.getSemantic()) { + case gpu::RGB: + case gpu::RGBA: + result = GL_RGB8; + break; + case gpu::SRGB: + case gpu::SRGBA: + result = GL_SRGB8; // standard 2.2 gamma correction color + break; + case gpu::COMPRESSED_RGB: + result = GL_COMPRESSED_RGB; + break; + case gpu::COMPRESSED_SRGB: + result = GL_COMPRESSED_SRGB; + break; + default: + qCDebug(gpugllogging) << "Unknown combination of texel format"; + } + + break; + } + + case gpu::VEC4: { + switch (dstFormat.getSemantic()) { + case gpu::RGB: + result = GL_RGB8; + break; + case gpu::RGBA: + switch (dstFormat.getType()) { + case gpu::UINT32: + result = GL_RGBA32UI; + break; + case gpu::INT32: + result = GL_RGBA32I; + break; + case gpu::FLOAT: + result = GL_RGBA32F; + break; + case gpu::UINT16: + result = GL_RGBA16UI; + break; + case gpu::INT16: + result = GL_RGBA16I; + break; + case gpu::NUINT16: + result = GL_RGBA16; + break; + case gpu::NINT16: + result = GL_RGBA16_SNORM; + break; + case gpu::HALF: + result = GL_RGBA16F; + break; + case gpu::UINT8: + result = GL_RGBA8UI; + break; + case gpu::INT8: + result = GL_RGBA8I; + break; + case gpu::NUINT8: + result = GL_RGBA8; + break; + case gpu::NINT8: + result = GL_RGBA8_SNORM; + break; + case gpu::NUINT32: + case gpu::NINT32: + case gpu::NUM_TYPES: // quiet compiler + Q_UNREACHABLE(); + } + break; + case gpu::SRGB: + result = GL_SRGB8; + break; + case gpu::SRGBA: + result = GL_SRGB8_ALPHA8; // standard 2.2 gamma correction color + break; + case gpu::COMPRESSED_RGBA: + result = GL_COMPRESSED_RGBA; + break; + case gpu::COMPRESSED_SRGBA: + result = GL_COMPRESSED_SRGB_ALPHA; + break; + + // FIXME: WE will want to support this later + /* + case gpu::COMPRESSED_BC3_RGBA: + result = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + break; + case gpu::COMPRESSED_BC3_SRGBA: + result = GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT; + break; + + case gpu::COMPRESSED_BC7_RGBA: + result = GL_COMPRESSED_RGBA_BPTC_UNORM_ARB; + break; + case gpu::COMPRESSED_BC7_SRGBA: + result = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM; + break; + */ + + default: + qCDebug(gpugllogging) << "Unknown combination of texel format"; + } + break; + } + + default: + qCDebug(gpugllogging) << "Unknown combination of texel format"; + } + return result; } GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const Element& srcFormat) { diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.h b/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.h index bc3ec55066..94ded3dc23 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.h +++ b/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.h @@ -21,7 +21,7 @@ public: static GLTexelFormat evalGLTexelFormat(const Element& dstFormat) { return evalGLTexelFormat(dstFormat, dstFormat); } - static GLTexelFormat evalGLTexelFormatInternal(const Element& dstFormat); + static GLenum evalGLTexelFormatInternal(const Element& dstFormat); static GLTexelFormat evalGLTexelFormat(const Element& dstFormat, const Element& srcFormat); }; diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp index 255258d762..56ff4166ea 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp @@ -17,11 +17,11 @@ using namespace gpu; using namespace gpu::gl; std::shared_ptr GLTexture::_textureTransferHelper; -static std::map _textureCountByMips; -static uint16 _currentMaxMipCount { 0 }; // FIXME placeholder for texture memory over-use #define DEFAULT_MAX_MEMORY_MB 256 +#define MIN_FREE_GPU_MEMORY_PERCENTAGE 0.25f +#define OVER_MEMORY_PRESSURE 2.0f const GLenum GLTexture::CUBE_FACE_LAYOUT[6] = { GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, @@ -94,6 +94,7 @@ const std::vector& GLTexture::getFaceTargets(GLenum target) { return faceTargets; } + float GLTexture::getMemoryPressure() { // Check for an explicit memory limit auto availableTextureMemory = Texture::getAllowedGPUMemoryUsage(); @@ -102,15 +103,28 @@ float GLTexture::getMemoryPressure() { if (!availableTextureMemory) { auto totalGpuMemory = getDedicatedMemory(); - // If no limit has been explicitly set, and the dedicated memory can't be determined, - // just use a fallback fixed value of 256 MB if (!totalGpuMemory) { + // If we can't query the dedicated memory just use a fallback fixed value of 256 MB totalGpuMemory = MB_TO_BYTES(DEFAULT_MAX_MEMORY_MB); + } else { + // Check the global free GPU memory + auto freeGpuMemory = getFreeDedicatedMemory(); + if (freeGpuMemory) { + static gpu::Size lastFreeGpuMemory = 0; + auto freePercentage = (float)freeGpuMemory / (float)totalGpuMemory; + if (freeGpuMemory != lastFreeGpuMemory) { + lastFreeGpuMemory = freeGpuMemory; + if (freePercentage < MIN_FREE_GPU_MEMORY_PERCENTAGE) { + qDebug() << "Exceeded max GPU memory"; + return OVER_MEMORY_PRESSURE; + } + } + } } - // Allow 75% of all available GPU memory to be consumed by textures + // Allow 50% of all available GPU memory to be consumed by textures // FIXME overly conservative? - availableTextureMemory = (totalGpuMemory >> 2) * 3; + availableTextureMemory = (totalGpuMemory >> 1); } // Return the consumed texture memory divided by the available texture memory. @@ -118,80 +132,27 @@ float GLTexture::getMemoryPressure() { return (float)consumedGpuMemory / (float)availableTextureMemory; } -GLTexture::DownsampleSource::DownsampleSource(const std::weak_ptr& backend, GLTexture* oldTexture) : - _backend(backend), - _size(oldTexture ? oldTexture->_size : 0), - _texture(oldTexture ? oldTexture->takeOwnership() : 0), - _minMip(oldTexture ? oldTexture->_minMip : 0), - _maxMip(oldTexture ? oldTexture->_maxMip : 0) -{ -} - -GLTexture::DownsampleSource::~DownsampleSource() { - if (_texture) { - auto backend = _backend.lock(); - if (backend) { - backend->releaseTexture(_texture, _size); - } - } -} - -GLTexture::GLTexture(const std::weak_ptr& backend, const gpu::Texture& texture, GLuint id, GLTexture* originalTexture, bool transferrable) : - GLObject(backend, texture, id), - _storageStamp(texture.getStamp()), - _target(getGLTextureType(texture)), - _maxMip(texture.maxMip()), - _minMip(texture.minMip()), - _virtualSize(texture.evalTotalSize()), - _transferrable(transferrable), - _downsampleSource(backend, originalTexture) -{ - if (_transferrable) { - uint16 mipCount = usedMipLevels(); - _currentMaxMipCount = std::max(_currentMaxMipCount, mipCount); - if (!_textureCountByMips.count(mipCount)) { - _textureCountByMips[mipCount] = 1; - } else { - ++_textureCountByMips[mipCount]; - } - } - Backend::incrementTextureGPUCount(); - Backend::updateTextureGPUVirtualMemoryUsage(0, _virtualSize); -} - // Create the texture and allocate storage GLTexture::GLTexture(const std::weak_ptr& backend, const Texture& texture, GLuint id, bool transferrable) : - GLTexture(backend, texture, id, nullptr, transferrable) + GLObject(backend, texture, id), + _source(texture.source()), + _storageStamp(texture.getStamp()), + _target(getGLTextureType(texture)), + _internalFormat(gl::GLTexelFormat::evalGLTexelFormatInternal(texture.getTexelFormat())), + _maxMip(texture.maxMip()), + _minMip(texture.minMip()), + _virtualSize(texture.evalTotalSize()), + _transferrable(transferrable) { - // FIXME, do during allocation - //Backend::updateTextureGPUMemoryUsage(0, _size); - Backend::setGPUObject(texture, this); -} - -// Create the texture and copy from the original higher resolution version -GLTexture::GLTexture(const std::weak_ptr& backend, const gpu::Texture& texture, GLuint id, GLTexture* originalTexture) : - GLTexture(backend, texture, id, originalTexture, originalTexture->_transferrable) -{ - Q_ASSERT(_minMip >= originalTexture->_minMip); - // Set the GPU object last because that implicitly destroys the originalTexture object + auto strongBackend = _backend.lock(); + strongBackend->recycle(); + Backend::incrementTextureGPUCount(); + Backend::updateTextureGPUVirtualMemoryUsage(0, _virtualSize); Backend::setGPUObject(texture, this); } GLTexture::~GLTexture() { - if (_transferrable) { - uint16 mipCount = usedMipLevels(); - Q_ASSERT(_textureCountByMips.count(mipCount)); - auto& numTexturesForMipCount = _textureCountByMips[mipCount]; - --numTexturesForMipCount; - if (0 == numTexturesForMipCount) { - _textureCountByMips.erase(mipCount); - if (mipCount == _currentMaxMipCount) { - _currentMaxMipCount = (_textureCountByMips.empty() ? 0 : _textureCountByMips.rbegin()->first); - } - } - } - if (_id) { auto backend = _backend.lock(); if (backend) { @@ -210,6 +171,28 @@ void GLTexture::createTexture() { }); } +void GLTexture::withPreservedTexture(std::function f) const { + GLint boundTex = -1; + switch (_target) { + case GL_TEXTURE_2D: + glGetIntegerv(GL_TEXTURE_BINDING_2D, &boundTex); + break; + + case GL_TEXTURE_CUBE_MAP: + glGetIntegerv(GL_TEXTURE_BINDING_CUBE_MAP, &boundTex); + break; + + default: + qFatal("Unsupported texture type"); + } + (void)CHECK_GL_ERROR(); + + glBindTexture(_target, _texture); + f(); + glBindTexture(_target, boundTex); + (void)CHECK_GL_ERROR(); +} + void GLTexture::setSize(GLuint size) const { Backend::updateTextureGPUMemoryUsage(_size, size); const_cast(_size) = size; @@ -223,20 +206,6 @@ bool GLTexture::isOutdated() const { return GLSyncState::Idle == _syncState && _contentStamp < _gpuObject.getDataStamp(); } -bool GLTexture::isOverMaxMemory() const { - // FIXME switch to using the max mip count used from the previous frame - if (usedMipLevels() < _currentMaxMipCount) { - return false; - } - Q_ASSERT(usedMipLevels() == _currentMaxMipCount); - - if (getMemoryPressure() < 1.0f) { - return false; - } - - return true; -} - bool GLTexture::isReady() const { // If we have an invalid texture, we're never ready if (isInvalid()) { @@ -257,11 +226,6 @@ void GLTexture::postTransfer() { setSyncState(GLSyncState::Idle); ++_transferCount; - //// The public gltexture becaomes available - //_id = _privateTexture; - - _downsampleSource.reset(); - // At this point the mip pixels have been loaded, we can notify the gpu texture to abandon it's memory switch (_gpuObject.getType()) { case Texture::TEX_2D: diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.h b/libraries/gpu-gl/src/gpu/gl/GLTexture.h index 742b223e36..4375d0644f 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTexture.h +++ b/libraries/gpu-gl/src/gpu/gl/GLTexture.h @@ -73,14 +73,7 @@ public: return nullptr; } - // Do we need to reduce texture memory usage? - if (object->isOverMaxMemory() && texturePointer->incremementMinMip()) { - // WARNING, this code path will essentially `delete this`, - // so no dereferencing of this instance should be done past this point - object = new GLTextureType(backend.shared_from_this(), texture, object); - _textureTransferHelper->transferTexture(texturePointer); - return nullptr; - } + ((GLTexture*)object)->updateMips(); return object; } @@ -96,57 +89,38 @@ public: } else { object = Backend::getGPUObject(*texture); } + if (!object) { return 0; } - GLuint result = object->_id; + if (!shouldSync) { + return object->_id; + } // Don't return textures that are in transfer state - if (shouldSync) { - if ((object->getSyncState() != GLSyncState::Idle) || - // Don't return transferrable textures that have never completed transfer - (!object->_transferrable || 0 != object->_transferCount)) { - // Will be either 0 or the original texture being downsampled. - result = object->_downsampleSource._texture; - } + if ((object->getSyncState() != GLSyncState::Idle) || + // Don't return transferrable textures that have never completed transfer + (!object->_transferrable || 0 != object->_transferCount)) { + return 0; } - return result; - } - - // Used by derived classes and helpers to ensure the actual GL object exceeds the lifetime of `this` - GLuint takeOwnership() { - GLuint result = _id; - const_cast(_id) = 0; - return result; + return object->_id; } ~GLTexture(); const GLuint& _texture { _id }; + const std::string _source; const Stamp _storageStamp; const GLenum _target; + const GLenum _internalFormat; const uint16 _maxMip; - const uint16 _minMip; + uint16 _minMip; const GLuint _virtualSize; // theoretical size as expected Stamp _contentStamp { 0 }; const bool _transferrable; Size _transferCount { 0 }; - - struct DownsampleSource { - using Pointer = std::shared_ptr; - DownsampleSource(const std::weak_ptr& backend) : _backend(backend), _size(0), _texture(0), _minMip(0), _maxMip(0) {} - DownsampleSource(const std::weak_ptr& backend, GLTexture* originalTexture); - ~DownsampleSource(); - void reset() const { const_cast(_texture) = 0; } - const std::weak_ptr& _backend; - const GLuint _size { 0 }; - const GLuint _texture { 0 }; - const uint16 _minMip { 0 }; - const uint16 _maxMip { 0 }; - } _downsampleSource; - GLuint size() const { return _size; } GLSyncState getSyncState() const { return _syncState; } @@ -160,9 +134,7 @@ public: bool isReady() const; // Execute any post-move operations that must occur only on the main thread - void postTransfer(); - - bool isOverMaxMemory() const; + virtual void postTransfer(); uint16 usedMipLevels() const { return (_maxMip - _minMip) + 1; } @@ -170,33 +142,34 @@ public: static const GLenum CUBE_FACE_LAYOUT[6]; static const GLFilterMode FILTER_MODES[Sampler::NUM_FILTERS]; static const GLenum WRAP_MODES[Sampler::NUM_WRAP_MODES]; -protected: - static const std::vector& getFaceTargets(GLenum textureType); - - static GLenum getGLTextureType(const Texture& texture); // Return a floating point value indicating how much of the allowed // texture memory we are currently consuming. A value of 0 indicates // no texture memory usage, while a value of 1 indicates all available / allowed memory // is consumed. A value above 1 indicates that there is a problem. static float getMemoryPressure(); +protected: + + static const std::vector& getFaceTargets(GLenum textureType); + + static GLenum getGLTextureType(const Texture& texture); const GLuint _size { 0 }; // true size as reported by the gl api std::atomic _syncState { GLSyncState::Idle }; GLTexture(const std::weak_ptr& backend, const Texture& texture, GLuint id, bool transferrable); - GLTexture(const std::weak_ptr& backend, const Texture& texture, GLuint id, GLTexture* originalTexture); void setSyncState(GLSyncState syncState) { _syncState = syncState; } void createTexture(); - + + virtual void updateMips() {} virtual void allocateStorage() const = 0; virtual void updateSize() const = 0; virtual void syncSampler() const = 0; virtual void generateMips() const = 0; - virtual void withPreservedTexture(std::function f) const = 0; + virtual void withPreservedTexture(std::function f) const; protected: void setSize(GLuint size) const; @@ -207,9 +180,6 @@ protected: virtual void finishTransfer(); private: - - GLTexture(const std::weak_ptr& backend, const gpu::Texture& gpuTexture, GLuint id, GLTexture* originalTexture, bool transferrable); - friend class GLTextureTransferHelper; friend class GLBackend; }; diff --git a/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.cpp b/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.cpp index ae8739bb3b..75af08d9a3 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.cpp @@ -35,6 +35,8 @@ GLTextureTransferHelper::GLTextureTransferHelper() { initialize(true, QThread::LowPriority); // Clean shutdown on UNIX, otherwise _canvas is freed early connect(qApp, &QCoreApplication::aboutToQuit, [&] { terminate(); }); +#else + initialize(false, QThread::LowPriority); #endif } @@ -43,23 +45,18 @@ GLTextureTransferHelper::~GLTextureTransferHelper() { if (isStillRunning()) { terminate(); } +#else + terminate(); #endif } void GLTextureTransferHelper::transferTexture(const gpu::TexturePointer& texturePointer) { GLTexture* object = Backend::getGPUObject(*texturePointer); -#ifdef THREADED_TEXTURE_TRANSFER Backend::incrementTextureGPUTransferCount(); object->setSyncState(GLSyncState::Pending); Lock lock(_mutex); _pendingTextures.push_back(texturePointer); -#else - for (object->startTransfer(); object->continueTransfer(); ) { } - object->finishTransfer(); - object->_contentStamp = texturePointer->getDataStamp(); - object->setSyncState(GLSyncState::Transferred); -#endif } void GLTextureTransferHelper::setup() { @@ -100,13 +97,28 @@ void GLTextureTransferHelper::shutdown() { #endif } +void GLTextureTransferHelper::queueExecution(VoidLambda lambda) { + Lock lock(_mutex); + _pendingCommands.push_back(lambda); +} + +#define MAX_TRANSFERS_PER_PASS 2 + bool GLTextureTransferHelper::process() { -#ifdef THREADED_TEXTURE_TRANSFER - // Take any new textures off the queue + // Take any new textures or commands off the queue + VoidLambdaList pendingCommands; TextureList newTransferTextures; { Lock lock(_mutex); newTransferTextures.swap(_pendingTextures); + pendingCommands.swap(_pendingCommands); + } + + if (!pendingCommands.empty()) { + for (auto command : pendingCommands) { + command(); + } + glFlush(); } if (!newTransferTextures.empty()) { @@ -119,11 +131,16 @@ bool GLTextureTransferHelper::process() { _transferringTextures.push_back(texturePointer); _textureIterator = _transferringTextures.begin(); } + _transferringTextures.sort([](const gpu::TexturePointer& a, const gpu::TexturePointer& b)->bool { + return a->getSize() < b->getSize(); + }); } // No transfers in progress, sleep if (_transferringTextures.empty()) { +#ifdef THREADED_TEXTURE_TRANSFER QThread::usleep(1); +#endif return true; } @@ -135,7 +152,11 @@ bool GLTextureTransferHelper::process() { qDebug() << "Texture list " << _transferringTextures.size(); } - for (auto _textureIterator = _transferringTextures.begin(); _textureIterator != _transferringTextures.end();) { + size_t transferCount = 0; + for (_textureIterator = _transferringTextures.begin(); _textureIterator != _transferringTextures.end();) { + if (++transferCount > MAX_TRANSFERS_PER_PASS) { + break; + } auto texture = *_textureIterator; GLTexture* gltexture = Backend::getGPUObject(*texture); if (gltexture->continueTransfer()) { @@ -144,9 +165,9 @@ bool GLTextureTransferHelper::process() { } gltexture->finishTransfer(); - glNamedFramebufferTexture(_readFramebuffer, GL_COLOR_ATTACHMENT0, gltexture->_id, 0); - glBlitNamedFramebuffer(_readFramebuffer, _drawFramebuffer, 0, 0, 1, 1, 0, 0, 1, 1, GL_COLOR_BUFFER_BIT, GL_NEAREST); +#ifdef THREADED_TEXTURE_TRANSFER clientWait(); +#endif gltexture->_contentStamp = gltexture->_gpuObject.getDataStamp(); gltexture->updateSize(); gltexture->setSyncState(gpu::gl::GLSyncState::Transferred); @@ -159,6 +180,7 @@ bool GLTextureTransferHelper::process() { _textureIterator = _transferringTextures.erase(_textureIterator); } +#ifdef THREADED_TEXTURE_TRANSFER if (!_transferringTextures.empty()) { // Don't saturate the GPU clientWait(); @@ -166,8 +188,7 @@ bool GLTextureTransferHelper::process() { // Don't saturate the CPU QThread::msleep(1); } -#else - QThread::msleep(1); #endif + return true; } diff --git a/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.h b/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.h index d2207df7e6..289aec40bb 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.h +++ b/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.h @@ -28,12 +28,14 @@ using TextureListIterator = TextureList::iterator; class GLTextureTransferHelper : public GenericThread { public: + using VoidLambda = std::function; + using VoidLambdaList = std::list; using Pointer = std::shared_ptr; GLTextureTransferHelper(); ~GLTextureTransferHelper(); void transferTexture(const gpu::TexturePointer& texturePointer); + void queueExecution(VoidLambda lambda); -protected: void setup() override; void shutdown() override; bool process() override; @@ -41,8 +43,15 @@ protected: private: #ifdef THREADED_TEXTURE_TRANSFER ::gl::OffscreenContext _context; + // 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 + VoidLambdaList _pendingCommands; // Textures that have been submitted for transfer TextureList _pendingTextures; // Textures currently in the transfer process @@ -50,11 +59,6 @@ private: TextureList _transferringTextures; TextureListIterator _textureIterator; - // Framebuffers / renderbuffers for forcing access to the texture on the transfer thread - GLuint _drawRenderbuffer { 0 }; - GLuint _drawFramebuffer { 0 }; - GLuint _readFramebuffer { 0 }; -#endif }; } } diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h index c89024b7e8..5d03997b44 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h +++ b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h @@ -43,7 +43,6 @@ public: GLuint allocate(); public: GL41Texture(const std::weak_ptr& backend, const Texture& buffer, bool transferrable); - GL41Texture(const std::weak_ptr& backend, const Texture& buffer, GL41Texture* original); protected: void transferMip(uint16_t mipLevel, uint8_t face) const; @@ -52,7 +51,6 @@ public: void updateSize() const override; void syncSampler() const override; void generateMips() const override; - void withPreservedTexture(std::function f) const override; }; diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp index 3d55802ec2..3fb729711d 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp @@ -40,30 +40,6 @@ GLTexture* GL41Backend::syncGPUObject(const TexturePointer& texture, bool transf GL41Texture::GL41Texture(const std::weak_ptr& backend, const Texture& texture, bool transferrable) : GLTexture(backend, texture, allocate(), transferrable) {} -GL41Texture::GL41Texture(const std::weak_ptr& backend, const Texture& texture, GL41Texture* original) : GLTexture(backend, texture, allocate(), original) {} - -void GL41Texture::withPreservedTexture(std::function f) const { - GLint boundTex = -1; - switch (_target) { - case GL_TEXTURE_2D: - glGetIntegerv(GL_TEXTURE_BINDING_2D, &boundTex); - break; - - case GL_TEXTURE_CUBE_MAP: - glGetIntegerv(GL_TEXTURE_BINDING_CUBE_MAP, &boundTex); - break; - - default: - qFatal("Unsupported texture type"); - } - (void)CHECK_GL_ERROR(); - - glBindTexture(_target, _texture); - f(); - glBindTexture(_target, boundTex); - (void)CHECK_GL_ERROR(); -} - void GL41Texture::generateMips() const { withPreservedTexture([&] { glGenerateMipmap(_target); @@ -147,35 +123,12 @@ void GL41Texture::startTransfer() { glBindTexture(_target, _id); (void)CHECK_GL_ERROR(); - if (_downsampleSource._texture) { - GLuint fbo { 0 }; - glGenFramebuffers(1, &fbo); - (void)CHECK_GL_ERROR(); - glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); - (void)CHECK_GL_ERROR(); - // Find the distance between the old min mip and the new one - uint16 mipOffset = _minMip - _downsampleSource._minMip; - for (uint16 i = _minMip; i <= _maxMip; ++i) { - uint16 targetMip = i - _minMip; - uint16 sourceMip = targetMip + mipOffset; - Vec3u dimensions = _gpuObject.evalMipDimensions(i); - for (GLenum target : getFaceTargets(_target)) { - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, _downsampleSource._texture, sourceMip); - (void)CHECK_GL_ERROR(); - glCopyTexSubImage2D(target, targetMip, 0, 0, 0, 0, dimensions.x, dimensions.y); - (void)CHECK_GL_ERROR(); - } - } - glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - glDeleteFramebuffers(1, &fbo); - } else { - // transfer pixels from each faces - uint8_t numFaces = (Texture::TEX_CUBE == _gpuObject.getType()) ? CUBE_NUM_FACES : 1; - for (uint8_t f = 0; f < numFaces; f++) { - for (uint16_t i = 0; i < Sampler::MAX_MIP_LEVEL; ++i) { - if (_gpuObject.isStoredMipFaceAvailable(i, f)) { - transferMip(i, f); - } + // transfer pixels from each faces + uint8_t numFaces = (Texture::TEX_CUBE == _gpuObject.getType()) ? CUBE_NUM_FACES : 1; + for (uint8_t f = 0; f < numFaces; f++) { + for (uint16_t i = 0; i < Sampler::MAX_MIP_LEVEL; ++i) { + if (_gpuObject.isStoredMipFaceAvailable(i, f)) { + transferMip(i, f); } } } diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.cpp index dad1f07ed3..bae6326e8f 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.cpp @@ -147,3 +147,8 @@ void GL45Backend::do_multiDrawIndexedIndirect(const Batch& batch, size_t paramOf _stats._DSNumAPIDrawcalls++; (void)CHECK_GL_ERROR(); } + +void GL45Backend::recycle() const { + Parent::recycle(); + derezTextures(); +} diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h index db297e77fd..f1c30b9382 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h +++ b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h @@ -18,29 +18,6 @@ namespace gpu { namespace gl45 { using namespace gpu::gl; -struct TransferState { - GLTexture& _texture; - GLenum _internalFormat { GL_RGBA8 }; - GLTexelFormat _texelFormat; - uint8_t _face { 0 }; - uint16_t _mipLevel { 0 }; - uint32_t _bytesPerLine { 0 }; - uint32_t _bytesPerPixel { 0 }; - uint32_t _bytesPerPage { 0 }; - GLuint _maxSparseLevel { 0 }; - - uvec3 _mipDimensions; - uvec3 _mipOffset; - uvec3 _pageSize; - const uint8_t* _srcPointer { nullptr }; - uvec3 currentPageSize() const; - void updateSparse(); - void updateMip(); - void populatePage(std::vector& dest); - bool increment(); - TransferState(GLTexture& texture); -}; - class GL45Backend : public GLBackend { using Parent = GLBackend; // Context Backend static interface required @@ -53,14 +30,54 @@ public: class GL45Texture : public GLTexture { using Parent = GLTexture; static GLuint allocate(const Texture& texture); + static const uint32_t DEFAULT_PAGE_DIMENSION = 128; + static const uint32_t DEFAULT_MAX_SPARSE_LEVEL = 0xFFFF; public: GL45Texture(const std::weak_ptr& backend, const Texture& texture, bool transferrable); - GL45Texture(const std::weak_ptr& backend, const Texture& texture, GLTexture* original); ~GL45Texture(); + void postTransfer() override; + + struct SparseInfo { + SparseInfo(GL45Texture& texture); + void maybeMakeSparse(); + void update(); + uvec3 getPageCounts(const uvec3& dimensions) const; + uint32_t getPageCount(const uvec3& dimensions) const; + + GL45Texture& texture; + bool sparse { false }; + uvec3 pageDimensions { DEFAULT_PAGE_DIMENSION }; + GLuint maxSparseLevel { DEFAULT_MAX_SPARSE_LEVEL }; + uint32_t maxPages { 0 }; + uint32_t pageBytes { 0 }; + GLint pageDimensionsIndex { 0 }; + }; + + struct TransferState { + TransferState(GL45Texture& texture); + uvec3 currentPageSize() const; + void updateMip(); + void populatePage(std::vector& dest); + bool increment(); + + GL45Texture& texture; + GLTexelFormat texelFormat; + uint8_t face { 0 }; + uint16_t mipLevel { 0 }; + uint32_t bytesPerLine { 0 }; + uint32_t bytesPerPixel { 0 }; + uint32_t bytesPerPage { 0 }; + uvec3 mipDimensions; + uvec3 mipOffset; + const uint8_t* srcPointer { nullptr }; + }; protected: + void updateMips() override; + void stripToMip(uint16_t newMinMip); void startTransfer() override; bool continueTransfer() override; + void finishTransfer() override; void incrementalTransfer(const uvec3& size, const gpu::Texture::PixelsPointer& mip, std::function f) const; void transferMip(uint16_t mipLevel, uint8_t face = 0) const; void allocateMip(uint16_t mipLevel, uint8_t face = 0) const; @@ -69,12 +86,20 @@ public: void syncSampler() const override; void generateMips() const override; void withPreservedTexture(std::function f) const override; + void derez(); + SparseInfo _sparseInfo; TransferState _transferState; + uint32_t _allocatedPages { 0 }; + uint32_t _lastMipAllocatedPages { 0 }; + friend class GL45Backend; }; protected: + void recycle() const override; + void derezTextures() const; + GLuint getFramebufferID(const FramebufferPointer& framebuffer) override; GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer) override; diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp index b511ed7811..6733475c01 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp @@ -17,6 +17,7 @@ #include #include +#include #include "../gl/GLTexelFormat.h" @@ -24,79 +25,191 @@ using namespace gpu; using namespace gpu::gl; using namespace gpu::gl45; -#define SPARSE_TEXTURES 1 +#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 using GL45Texture = GL45Backend::GL45Texture; +static std::map> texturesByMipCounts; +static Mutex texturesByMipCountsMutex; +using TextureTypeFormat = std::pair; +std::map> sparsePageDimensionsByFormat; +Mutex sparsePageDimensionsByFormatMutex; + +static std::vector getPageDimensionsForFormat(const TextureTypeFormat& typeFormat) { + { + Lock lock(sparsePageDimensionsByFormatMutex); + if (sparsePageDimensionsByFormat.count(typeFormat)) { + return sparsePageDimensionsByFormat[typeFormat]; + } + } + GLint count = 0; + glGetInternalformativ(typeFormat.first, typeFormat.second, GL_NUM_VIRTUAL_PAGE_SIZES_ARB, 1, &count); + + std::vector result; + if (count > 0) { + std::vector x, y, z; + x.resize(count); + glGetInternalformativ(typeFormat.first, typeFormat.second, GL_VIRTUAL_PAGE_SIZE_X_ARB, 1, &x[0]); + y.resize(count); + glGetInternalformativ(typeFormat.first, typeFormat.second, GL_VIRTUAL_PAGE_SIZE_Y_ARB, 1, &y[0]); + z.resize(count); + glGetInternalformativ(typeFormat.first, typeFormat.second, GL_VIRTUAL_PAGE_SIZE_Z_ARB, 1, &z[0]); + + result.resize(count); + for (GLint i = 0; i < count; ++i) { + result[i] = uvec3(x[i], y[i], z[i]); + } + qCDebug(gpugl45logging) << "Got " << count << " page sizes"; + } + + { + Lock lock(sparsePageDimensionsByFormatMutex); + if (0 == sparsePageDimensionsByFormat.count(typeFormat)) { + sparsePageDimensionsByFormat[typeFormat] = result; + } + } + + return result; +} + +static std::vector getPageDimensionsForFormat(GLenum target, GLenum format) { + return getPageDimensionsForFormat({ target, format }); +} + GLTexture* GL45Backend::syncGPUObject(const TexturePointer& texture, bool transfer) { return GL45Texture::sync(*this, texture, transfer); } -TransferState::TransferState(GLTexture& texture) : _texture(texture) { +using SparseInfo = GL45Backend::GL45Texture::SparseInfo; + +SparseInfo::SparseInfo(GL45Texture& texture) + : texture(texture) { } -void TransferState::updateSparse() { - glGetTextureParameterIuiv(_texture._id, GL_NUM_SPARSE_LEVELS_ARB, &_maxSparseLevel); - _internalFormat = gl::GLTexelFormat::evalGLTexelFormat(_texture._gpuObject.getTexelFormat(), _texture._gpuObject.getTexelFormat()).internalFormat; - ivec3 pageSize; - glGetInternalformativ(_texture._target, _internalFormat, GL_VIRTUAL_PAGE_SIZE_X_ARB, 1, &pageSize.x); - glGetInternalformativ(_texture._target, _internalFormat, GL_VIRTUAL_PAGE_SIZE_Y_ARB, 1, &pageSize.y); - glGetInternalformativ(_texture._target, _internalFormat, GL_VIRTUAL_PAGE_SIZE_Z_ARB, 1, &pageSize.z); - _pageSize = uvec3(pageSize); -} - -void TransferState::updateMip() { - _mipDimensions = _texture._gpuObject.evalMipDimensions(_mipLevel); - _mipOffset = uvec3(); - if (!_texture._gpuObject.isStoredMipFaceAvailable(_mipLevel, _face)) { - _srcPointer = nullptr; +void SparseInfo::maybeMakeSparse() { + // Don't enable sparse for objects with explicitly managed mip levels + if (!texture._gpuObject.isAutogenerateMips()) { + qCDebug(gpugl45logging) << "Don't enable sparse texture for explicitly generated mipmaps on texture " << texture._source.c_str(); return; } - auto mip = _texture._gpuObject.accessStoredMipFace(_mipLevel, _face); - _texelFormat = gl::GLTexelFormat::evalGLTexelFormat(_texture._gpuObject.getTexelFormat(), mip->getFormat()); - _srcPointer = mip->readData(); - _bytesPerLine = (uint32_t)mip->getSize() / _mipDimensions.y; - _bytesPerPixel = _bytesPerLine / _mipDimensions.x; + const uvec3 dimensions = texture._gpuObject.getDimensions(); + auto allowedPageDimensions = getPageDimensionsForFormat(texture._target, texture._internalFormat); + // In order to enable sparse the texture size must be an integer multiple of the page size + for (size_t i = 0; i < allowedPageDimensions.size(); ++i) { + pageDimensionsIndex = (uint32_t) i; + pageDimensions = allowedPageDimensions[i]; + // Is this texture an integer multiple of page dimensions? + if (uvec3(0) == (dimensions % pageDimensions)) { + qCDebug(gpugl45logging) << "Enabling sparse for texture " << texture._source.c_str(); + sparse = true; + break; + } + } + + if (sparse) { + glTextureParameteri(texture._id, GL_TEXTURE_SPARSE_ARB, GL_TRUE); + glTextureParameteri(texture._id, GL_VIRTUAL_PAGE_SIZE_INDEX_ARB, pageDimensionsIndex); + } else { + qCDebug(gpugl45logging) << "Size " << dimensions.x << " x " << dimensions.y << + " is not supported by any sparse page size for texture" << texture._source.c_str(); + } +} + +// This can only be called after we've established our storage size +void SparseInfo::update() { + if (!sparse) { + return; + } + glGetTextureParameterIuiv(texture._id, GL_NUM_SPARSE_LEVELS_ARB, &maxSparseLevel); + pageBytes = texture._gpuObject.getTexelFormat().getSize(); + pageBytes *= pageDimensions.x * pageDimensions.y * pageDimensions.z; + + for (uint16_t mipLevel = 0; mipLevel <= maxSparseLevel; ++mipLevel) { + auto mipDimensions = texture._gpuObject.evalMipDimensions(mipLevel); + auto mipPageCount = getPageCount(mipDimensions); + maxPages += mipPageCount; + } + if (texture._target == GL_TEXTURE_CUBE_MAP) { + maxPages *= GLTexture::CUBE_NUM_FACES; + } +} + +uvec3 SparseInfo::getPageCounts(const uvec3& dimensions) const { + auto result = (dimensions / pageDimensions) + + glm::clamp(dimensions % pageDimensions, glm::uvec3(0), glm::uvec3(1)); + return result; +} + +uint32_t SparseInfo::getPageCount(const uvec3& dimensions) const { + auto pageCounts = getPageCounts(dimensions); + return pageCounts.x * pageCounts.y * pageCounts.z; +} + +using TransferState = GL45Backend::GL45Texture::TransferState; + +TransferState::TransferState(GL45Texture& texture) : texture(texture) { +} + +void TransferState::updateMip() { + mipDimensions = texture._gpuObject.evalMipDimensions(mipLevel); + mipOffset = uvec3(); + if (!texture._gpuObject.isStoredMipFaceAvailable(mipLevel, face)) { + srcPointer = nullptr; + return; + } + + auto mip = texture._gpuObject.accessStoredMipFace(mipLevel, face); + texelFormat = gl::GLTexelFormat::evalGLTexelFormat(texture._gpuObject.getTexelFormat(), mip->getFormat()); + srcPointer = mip->readData(); + bytesPerLine = (uint32_t)mip->getSize() / mipDimensions.y; + bytesPerPixel = bytesPerLine / mipDimensions.x; } bool TransferState::increment() { - if ((_mipOffset.x + _pageSize.x) < _mipDimensions.x) { - _mipOffset.x += _pageSize.x; + const SparseInfo& sparse = texture._sparseInfo; + if ((mipOffset.x + sparse.pageDimensions.x) < mipDimensions.x) { + mipOffset.x += sparse.pageDimensions.x; return true; } - if ((_mipOffset.y + _pageSize.y) < _mipDimensions.y) { - _mipOffset.x = 0; - _mipOffset.y += _pageSize.y; + if ((mipOffset.y + sparse.pageDimensions.y) < mipDimensions.y) { + mipOffset.x = 0; + mipOffset.y += sparse.pageDimensions.y; return true; } - if (_mipOffset.z + _pageSize.z < _mipDimensions.z) { - _mipOffset.x = 0; - _mipOffset.y = 0; - ++_mipOffset.z; + if (mipOffset.z + sparse.pageDimensions.z < mipDimensions.z) { + mipOffset.x = 0; + mipOffset.y = 0; + ++mipOffset.z; return true; } // Done with this mip?, move on to the next mip - if (_mipLevel + 1 < _texture.usedMipLevels()) { - _mipOffset = uvec3(0); - ++_mipLevel; + if (mipLevel + 1 < texture.usedMipLevels()) { + mipOffset = uvec3(0); + ++mipLevel; updateMip(); return true; } - uint8_t maxFace = (uint8_t)((_texture._target == GL_TEXTURE_CUBE_MAP) ? GLTexture::CUBE_NUM_FACES : 1); - uint8_t nextFace = _face + 1; + uint8_t maxFace = (uint8_t)((texture._target == GL_TEXTURE_CUBE_MAP) ? GLTexture::CUBE_NUM_FACES : 1); + uint8_t nextFace = face + 1; // Done with this face? Move on to the next if (nextFace < maxFace) { - ++_face; - _mipOffset = uvec3(0); - _mipLevel = 0; + ++face; + mipOffset = uvec3(0); + mipLevel = 0; updateMip(); return true; } @@ -104,10 +217,9 @@ bool TransferState::increment() { return false; } -#define DEFAULT_GL_PIXEL_ALIGNMENT 4 void TransferState::populatePage(std::vector& buffer) { uvec3 pageSize = currentPageSize(); - auto bytesPerPageLine = _bytesPerPixel * pageSize.x; + auto bytesPerPageLine = bytesPerPixel * pageSize.x; if (0 != (bytesPerPageLine % DEFAULT_GL_PIXEL_ALIGNMENT)) { bytesPerPageLine += DEFAULT_GL_PIXEL_ALIGNMENT - (bytesPerPageLine % DEFAULT_GL_PIXEL_ALIGNMENT); assert(0 == (bytesPerPageLine % DEFAULT_GL_PIXEL_ALIGNMENT)); @@ -118,14 +230,14 @@ void TransferState::populatePage(std::vector& buffer) { } uint8_t* dst = &buffer[0]; for (uint32_t y = 0; y < pageSize.y; ++y) { - uint32_t srcOffset = (_bytesPerLine * (_mipOffset.y + y)) + (_bytesPerPixel * _mipOffset.x); + uint32_t srcOffset = (bytesPerLine * (mipOffset.y + y)) + (bytesPerPixel * mipOffset.x); uint32_t dstOffset = bytesPerPageLine * y; - memcpy(dst + dstOffset, _srcPointer + srcOffset, pageSize.x * _bytesPerPixel); + memcpy(dst + dstOffset, srcPointer + srcOffset, pageSize.x * bytesPerPixel); } } uvec3 TransferState::currentPageSize() const { - return glm::clamp(_mipDimensions - _mipOffset, uvec3(1), _pageSize); + return glm::clamp(mipDimensions - mipOffset, uvec3(1), texture._sparseInfo.pageDimensions); } GLuint GL45Texture::allocate(const Texture& texture) { @@ -139,25 +251,64 @@ 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), _transferState(*this) { + : GLTexture(backend, texture, allocate(texture), transferrable), _sparseInfo(*this), _transferState(*this) { -#if SPARSE_TEXTURES - if (transferrable) { - glTextureParameteri(_id, GL_TEXTURE_SPARSE_ARB, GL_TRUE); + if (enableSparseTextures && _transferrable) { + _sparseInfo.maybeMakeSparse(); } -#endif } -GL45Texture::GL45Texture(const std::weak_ptr& backend, const Texture& texture, GLTexture* original) - : GLTexture(backend, texture, allocate(texture), original), _transferState(*this) { } - GL45Texture::~GL45Texture() { - // FIXME do we need to explicitly deallocate the virtual memory here? - //if (_transferrable) { - // for (uint16_t mipLevel = 0; mipLevel < usedMipLevels(); ++i) { - // glTexturePageCommitmentEXT(_id, mipLevel, offset.x, offset.y, offset.z, size.x, size.y, size.z, GL_TRUE); - // } - //} + qCDebug(gpugl45logging) << "Destroying texture " << _id << " from source " << _source.c_str(); + if (_sparseInfo.sparse) { + // Remove this texture from the candidate list of derezzable textures + { + auto mipLevels = usedMipLevels(); + Lock lock(texturesByMipCountsMutex); + if (texturesByMipCounts.count(mipLevels)) { + auto& textures = texturesByMipCounts[mipLevels]; + textures.erase(this); + if (textures.empty()) { + texturesByMipCounts.erase(mipLevels); + } + } + } + + // Experimenation suggests that allocating sparse textures on one context/thread and deallocating + // them on another is buggy. So for sparse textures we need to queue a lambda with the deallocation + // callls to the transfer thread + auto id = _id; + // Set the class _id to 0 so we don't try to double delete + const_cast(_id) = 0; + std::list> destructionFunctions; + + uint8_t maxFace = (uint8_t)((_target == GL_TEXTURE_CUBE_MAP) ? GLTexture::CUBE_NUM_FACES : 1); + auto maxSparseMip = std::min(_maxMip, _sparseInfo.maxSparseLevel); + for (uint16_t mipLevel = _minMip; mipLevel <= maxSparseMip; ++mipLevel) { + auto mipDimensions = _gpuObject.evalMipDimensions(mipLevel); + destructionFunctions.push_back([id, maxFace, mipLevel, mipDimensions] { + glTexturePageCommitmentEXT(id, mipLevel, 0, 0, 0, mipDimensions.x, mipDimensions.y, maxFace, GL_FALSE); + }); + + auto deallocatedPages = _sparseInfo.getPageCount(mipDimensions) * maxFace; + assert(deallocatedPages <= _allocatedPages); + _allocatedPages -= deallocatedPages; + } + + if (0 != _allocatedPages) { + qCWarning(gpugl45logging) << "Allocated pages remaining " << _id << " " << _allocatedPages; + } + + auto size = _size; + _textureTransferHelper->queueExecution([id, size, destructionFunctions] { + for (auto function : destructionFunctions) { + function(); + } + glDeleteTextures(1, &id); + Backend::decrementTextureGPUCount(); + Backend::updateTextureGPUMemoryUsage(size, 0); + }); + } } void GL45Texture::withPreservedTexture(std::function f) const { @@ -165,37 +316,38 @@ void GL45Texture::withPreservedTexture(std::function f) const { } void GL45Texture::generateMips() const { + qCDebug(gpugl45logging) << "Generating mipmaps for " << _source.c_str(); glGenerateTextureMipmap(_id); (void)CHECK_GL_ERROR(); } void GL45Texture::allocateStorage() const { - GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat()); - glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, 0); - glTextureParameteri(_id, GL_TEXTURE_MAX_LEVEL, _maxMip - _minMip); if (_gpuObject.getTexelFormat().isCompressed()) { qFatal("Compressed textures not yet supported"); } + glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, 0); + glTextureParameteri(_id, GL_TEXTURE_MAX_LEVEL, _maxMip - _minMip); // Get the dimensions, accounting for the downgrade level Vec3u dimensions = _gpuObject.evalMipDimensions(_minMip); - glTextureStorage2D(_id, usedMipLevels(), texelFormat.internalFormat, dimensions.x, dimensions.y); + glTextureStorage2D(_id, usedMipLevels(), _internalFormat, dimensions.x, dimensions.y); (void)CHECK_GL_ERROR(); } void GL45Texture::updateSize() const { - setSize(_virtualSize); - if (!_id) { - return; - } - if (_gpuObject.getTexelFormat().isCompressed()) { qFatal("Compressed textures not yet supported"); } + + if (_transferrable) { + setSize(_allocatedPages * _sparseInfo.pageBytes); + } else { + setSize(_virtualSize); + } } void GL45Texture::startTransfer() { Parent::startTransfer(); - _transferState.updateSparse(); + _sparseInfo.update(); _transferState.updateMip(); } @@ -204,48 +356,65 @@ bool GL45Texture::continueTransfer() { if (buffer.empty()) { buffer.resize(DEFAULT_PAGE_BUFFER_SIZE); } - uvec3 pageSize = _transferState.currentPageSize(); - uvec3 offset = _transferState._mipOffset; + const uvec3 pageSize = _transferState.currentPageSize(); + const uvec3& offset = _transferState.mipOffset; -#if SPARSE_TEXTURES - if (_transferState._mipLevel <= _transferState._maxSparseLevel) { - glTexturePageCommitmentEXT(_id, _transferState._mipLevel, - offset.x, offset.y, _transferState._face, + if (_sparseInfo.sparse && _transferState.mipLevel <= _sparseInfo.maxSparseLevel) { + if (_allocatedPages > _sparseInfo.maxPages) { + qCWarning(gpugl45logging) << "Exceeded max page allocation!"; + } + glTexturePageCommitmentEXT(_id, _transferState.mipLevel, + offset.x, offset.y, _transferState.face, pageSize.x, pageSize.y, pageSize.z, GL_TRUE); + ++_allocatedPages; } -#endif - if (_transferState._srcPointer) { + if (_transferState.srcPointer) { // Transfer the mip data _transferState.populatePage(buffer); if (GL_TEXTURE_2D == _target) { - glTextureSubImage2D(_id, _transferState._mipLevel, + glTextureSubImage2D(_id, _transferState.mipLevel, offset.x, offset.y, pageSize.x, pageSize.y, - _transferState._texelFormat.format, _transferState._texelFormat.type, &buffer[0]); + _transferState.texelFormat.format, _transferState.texelFormat.type, &buffer[0]); } else if (GL_TEXTURE_CUBE_MAP == _target) { - auto target = CUBE_FACE_LAYOUT[_transferState._face]; + auto target = CUBE_FACE_LAYOUT[_transferState.face]; // 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()); - glTextureSubImage2DEXT(_id, target, _transferState._mipLevel, + glTextureSubImage2DEXT(_id, target, _transferState.mipLevel, offset.x, offset.y, pageSize.x, pageSize.y, - _transferState._texelFormat.format, _transferState._texelFormat.type, &buffer[0]); + _transferState.texelFormat.format, _transferState.texelFormat.type, &buffer[0]); } } serverWait(); - return _transferState.increment(); + auto currentMip = _transferState.mipLevel; + auto result = _transferState.increment(); + if (_sparseInfo.sparse && _transferState.mipLevel != currentMip && currentMip <= _sparseInfo.maxSparseLevel) { + auto mipDimensions = _gpuObject.evalMipDimensions(currentMip); + auto mipExpectedPages = _sparseInfo.getPageCount(mipDimensions); + auto newPages = _allocatedPages - _lastMipAllocatedPages; + if (newPages != mipExpectedPages) { + qCWarning(gpugl45logging) << "Unexpected page allocation size... " << newPages << " " << mipExpectedPages; + } + _lastMipAllocatedPages = _allocatedPages; + } + return result; } -void GL45Backend::GL45Texture::syncSampler() const { +void GL45Texture::finishTransfer() { + Parent::finishTransfer(); +} + +void GL45Texture::syncSampler() const { const Sampler& sampler = _gpuObject.getSampler(); const auto& fm = FILTER_MODES[sampler.getFilter()]; glTextureParameteri(_id, GL_TEXTURE_MIN_FILTER, fm.minFilter); glTextureParameteri(_id, GL_TEXTURE_MAG_FILTER, fm.magFilter); - + if (sampler.doComparison()) { glTextureParameteri(_id, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); glTextureParameteri(_id, GL_TEXTURE_COMPARE_FUNC, COMPARISON_TO_GL[sampler.getComparisonFunction()]); @@ -257,9 +426,131 @@ void GL45Backend::GL45Texture::syncSampler() const { glTextureParameteri(_id, GL_TEXTURE_WRAP_T, WRAP_MODES[sampler.getWrapModeV()]); glTextureParameteri(_id, GL_TEXTURE_WRAP_R, WRAP_MODES[sampler.getWrapModeW()]); glTextureParameterfv(_id, GL_TEXTURE_BORDER_COLOR, (const float*)&sampler.getBorderColor()); - glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, (uint16)sampler.getMipOffset()); + auto baseMip = std::max(sampler.getMipOffset(), _minMip); + glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, baseMip); glTextureParameterf(_id, GL_TEXTURE_MIN_LOD, (float)sampler.getMinMip()); glTextureParameterf(_id, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.f : sampler.getMaxMip())); glTextureParameterf(_id, GL_TEXTURE_MAX_ANISOTROPY_EXT, sampler.getMaxAnisotropy()); } +void GL45Texture::postTransfer() { + Parent::postTransfer(); + if (_sparseInfo.sparse) { + auto mipLevels = usedMipLevels(); + if (mipLevels > 1 && _minMip < _sparseInfo.maxSparseLevel) { + Lock lock(texturesByMipCountsMutex); + texturesByMipCounts[mipLevels].insert(this); + } + } +} + +void GL45Texture::stripToMip(uint16_t newMinMip) { + if (!_sparseInfo.sparse) { + return; + } + + if (newMinMip < _minMip) { + qCWarning(gpugl45logging) << "Cannot decrease the min mip"; + return; + } + + if (newMinMip > _sparseInfo.maxSparseLevel) { + qCWarning(gpugl45logging) << "Cannot increase the min mip into the mip tail"; + return; + } + + auto mipLevels = usedMipLevels(); + { + Lock lock(texturesByMipCountsMutex); + assert(0 != texturesByMipCounts.count(mipLevels)); + assert(0 != texturesByMipCounts[mipLevels].count(this)); + texturesByMipCounts[mipLevels].erase(this); + if (texturesByMipCounts[mipLevels].empty()) { + texturesByMipCounts.erase(mipLevels); + } + } + + // If we weren't generating mips before, we need to now that we're stripping down mip levels. + if (!_gpuObject.isAutogenerateMips()) { + qCDebug(gpugl45logging) << "Force mip generation for texture"; + glGenerateTextureMipmap(_id); + } + + + uint8_t maxFace = (uint8_t)((_target == GL_TEXTURE_CUBE_MAP) ? GLTexture::CUBE_NUM_FACES : 1); + for (uint16_t mip = _minMip; mip < newMinMip; ++mip) { + auto id = _id; + auto mipDimensions = _gpuObject.evalMipDimensions(mip); + _textureTransferHelper->queueExecution([id, mip, mipDimensions, maxFace] { + glTexturePageCommitmentEXT(id, mip, 0, 0, 0, mipDimensions.x, mipDimensions.y, maxFace, GL_FALSE); + }); + + auto deallocatedPages = _sparseInfo.getPageCount(mipDimensions) * maxFace; + assert(deallocatedPages < _allocatedPages); + _allocatedPages -= deallocatedPages; + } + + _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 + mipLevels = usedMipLevels(); + if (_sparseInfo.sparse && mipLevels > 1 && _minMip < _sparseInfo.maxSparseLevel) { + Lock lock(texturesByMipCountsMutex); + texturesByMipCounts[mipLevels].insert(this); + } +} + +void GL45Texture::updateMips() { + if (!_sparseInfo.sparse) { + return; + } + auto newMinMip = std::min(_gpuObject.minMip(), _sparseInfo.maxSparseLevel); + if (_minMip < newMinMip) { + stripToMip(newMinMip); + } +} + +void GL45Texture::derez() { + assert(_sparseInfo.sparse); + assert(_minMip < _sparseInfo.maxSparseLevel); + assert(_minMip < _maxMip); + assert(_transferrable); + stripToMip(_minMip + 1); +} + +void GL45Backend::derezTextures() const { + if (GLTexture::getMemoryPressure() < 1.0f) { + return; + } + + Lock lock(texturesByMipCountsMutex); + if (texturesByMipCounts.empty()) { + qCDebug(gpugl45logging) << "No available textures to derez"; + return; + } + + auto mipLevel = texturesByMipCounts.rbegin()->first; + if (mipLevel <= 1) { + qCDebug(gpugl45logging) << "Max mip levels " << mipLevel; + return; + } + + qCDebug(gpugl45logging) << "Allowed texture memory " << Texture::getAllowedGPUMemoryUsage(); + qCDebug(gpugl45logging) << "Used texture memory " << Context::getTextureGPUMemoryUsage(); + + GL45Texture* targetTexture = nullptr; + { + auto& textures = texturesByMipCounts[mipLevel]; + assert(!textures.empty()); + targetTexture = *textures.begin(); + } + lock.unlock(); + targetTexture->derez(); + qCDebug(gpugl45logging) << "New Used texture memory " << Context::getTextureGPUMemoryUsage(); +} diff --git a/libraries/gpu/src/gpu/Context.cpp b/libraries/gpu/src/gpu/Context.cpp index 68a6be1fe5..af3552911c 100644 --- a/libraries/gpu/src/gpu/Context.cpp +++ b/libraries/gpu/src/gpu/Context.cpp @@ -10,6 +10,7 @@ // #include "Context.h" #include "Frame.h" +#include "GPULogging.h" using namespace gpu; Context::CreateBackend Context::_createBackendCallback = nullptr; @@ -161,8 +162,9 @@ Backend::TransformCamera Backend::TransformCamera::getEyeCamera(int eye, const S } // Counters for Buffer and Texture usage in GPU/Context -std::atomic Context::_bufferGPUCount{ 0 }; -std::atomic Context::_bufferGPUMemoryUsage{ 0 }; +std::atomic Context::_fenceCount { 0 }; +std::atomic Context::_bufferGPUCount { 0 }; +std::atomic Context::_bufferGPUMemoryUsage { 0 }; std::atomic Context::_textureGPUCount{ 0 }; std::atomic Context::_textureGPUMemoryUsage{ 0 }; @@ -170,10 +172,15 @@ std::atomic Context::_textureGPUVirtualMemoryUsage{ 0 }; std::atomic Context::_textureGPUTransferCount{ 0 }; void Context::incrementBufferGPUCount() { - _bufferGPUCount++; + static std::atomic max { 0 }; + auto total = ++_bufferGPUCount; + if (total > max.load()) { + max = total; + qCDebug(gpulogging) << "New max GPU buffers " << total; + } } void Context::decrementBufferGPUCount() { - _bufferGPUCount--; + --_bufferGPUCount; } void Context::updateBufferGPUMemoryUsage(Size prevObjectSize, Size newObjectSize) { if (prevObjectSize == newObjectSize) { @@ -186,12 +193,30 @@ void Context::updateBufferGPUMemoryUsage(Size prevObjectSize, Size newObjectSize } } +void Context::incrementFenceCount() { + static std::atomic max { 0 }; + auto total = ++_fenceCount; + if (total > max.load()) { + max = total; + qCDebug(gpulogging) << "New max Fences " << total; + } +} +void Context::decrementFenceCount() { + --_fenceCount; +} + void Context::incrementTextureGPUCount() { - _textureGPUCount++; + static std::atomic max { 0 }; + auto total = ++_textureGPUCount; + if (total > max.load()) { + max = total; + qCDebug(gpulogging) << "New max GPU textures " << total; + } } void Context::decrementTextureGPUCount() { - _textureGPUCount--; + --_textureGPUCount; } + void Context::updateTextureGPUMemoryUsage(Size prevObjectSize, Size newObjectSize) { if (prevObjectSize == newObjectSize) { return; @@ -215,10 +240,15 @@ void Context::updateTextureGPUVirtualMemoryUsage(Size prevObjectSize, Size newOb } void Context::incrementTextureGPUTransferCount() { - _textureGPUTransferCount++; + static std::atomic max { 0 }; + auto total = ++_textureGPUTransferCount; + if (total > max.load()) { + max = total; + qCDebug(gpulogging) << "New max GPU textures transfers" << total; + } } void Context::decrementTextureGPUTransferCount() { - _textureGPUTransferCount--; + --_textureGPUTransferCount; } uint32_t Context::getBufferGPUCount() { diff --git a/libraries/gpu/src/gpu/Context.h b/libraries/gpu/src/gpu/Context.h index 42b81606db..4c701e170d 100644 --- a/libraries/gpu/src/gpu/Context.h +++ b/libraries/gpu/src/gpu/Context.h @@ -230,6 +230,9 @@ protected: static void decrementBufferGPUCount(); static void updateBufferGPUMemoryUsage(Size prevObjectSize, Size newObjectSize); + static void incrementFenceCount(); + static void decrementFenceCount(); + static void incrementTextureGPUCount(); static void decrementTextureGPUCount(); static void updateTextureGPUMemoryUsage(Size prevObjectSize, Size newObjectSize); @@ -237,7 +240,9 @@ protected: static void incrementTextureGPUTransferCount(); static void decrementTextureGPUTransferCount(); - // Buffer and Texture Counters + // Buffer, Texture and Fence Counters + static std::atomic _fenceCount; + static std::atomic _bufferGPUCount; static std::atomic _bufferGPUMemoryUsage; diff --git a/libraries/gpu/src/gpu/Framebuffer.cpp b/libraries/gpu/src/gpu/Framebuffer.cpp index beb0334208..2cd39d49ae 100755 --- a/libraries/gpu/src/gpu/Framebuffer.cpp +++ b/libraries/gpu/src/gpu/Framebuffer.cpp @@ -32,6 +32,7 @@ Framebuffer* Framebuffer::create( const Format& colorBufferFormat, uint16 width, auto framebuffer = Framebuffer::create(); auto colorTexture = TexturePointer(Texture::create2D(colorBufferFormat, width, height, Sampler(Sampler::FILTER_MIN_MAG_POINT))); + colorTexture->setSource("Framebuffer::colorTexture"); framebuffer->setRenderBuffer(0, colorTexture); @@ -42,8 +43,9 @@ Framebuffer* Framebuffer::create( const Format& colorBufferFormat, const Format& auto framebuffer = Framebuffer::create(); auto colorTexture = TexturePointer(Texture::create2D(colorBufferFormat, width, height, Sampler(Sampler::FILTER_MIN_MAG_POINT))); + colorTexture->setSource("Framebuffer::colorTexture"); auto depthTexture = TexturePointer(Texture::create2D(depthStencilBufferFormat, width, height, Sampler(Sampler::FILTER_MIN_MAG_POINT))); - + depthTexture->setSource("Framebuffer::depthTexture"); framebuffer->setRenderBuffer(0, colorTexture); framebuffer->setDepthStencilBuffer(depthTexture, depthStencilBufferFormat); @@ -55,7 +57,7 @@ Framebuffer* Framebuffer::createShadowmap(uint16 width) { auto depthFormat = Element(gpu::SCALAR, gpu::FLOAT, gpu::DEPTH); // Depth32 texel format auto depthTexture = TexturePointer(Texture::create2D(depthFormat, width, width)); - + depthTexture->setSource("Framebuffer::shadowMap"); Sampler::Desc samplerDesc; samplerDesc._borderColor = glm::vec4(1.0f); samplerDesc._wrapModeU = Sampler::WRAP_BORDER; @@ -113,30 +115,6 @@ void Framebuffer::updateSize(const TexturePointer& texture) { } } -void Framebuffer::resize(uint16 width, uint16 height, uint16 numSamples) { - if (width && height && numSamples && !isEmpty() && !isSwapchain()) { - if ((width != _width) || (height != _height) || (numSamples != _numSamples)) { - for (uint32 i = 0; i < _renderBuffers.size(); ++i) { - if (_renderBuffers[i]) { - _renderBuffers[i]._texture->resize2D(width, height, numSamples); - _numSamples = _renderBuffers[i]._texture->getNumSamples(); - ++_colorStamps[i]; - } - } - - if (_depthStencilBuffer) { - _depthStencilBuffer._texture->resize2D(width, height, numSamples); - _numSamples = _depthStencilBuffer._texture->getNumSamples(); - ++_depthStamp; - } - - _width = width; - _height = height; - // _numSamples = numSamples; - } - } -} - uint16 Framebuffer::getWidth() const { if (isSwapchain()) { return getSwapchain()->getWidth(); diff --git a/libraries/gpu/src/gpu/Framebuffer.h b/libraries/gpu/src/gpu/Framebuffer.h index 6fa6367c7d..8c26037ada 100755 --- a/libraries/gpu/src/gpu/Framebuffer.h +++ b/libraries/gpu/src/gpu/Framebuffer.h @@ -130,9 +130,6 @@ public: float getAspectRatio() const { return getWidth() / (float) getHeight() ; } - // If not a swapchain canvas, resize can resize all the render buffers and depth stencil attached in one call - void resize( uint16 width, uint16 height, uint16 samples = 1 ); - static const uint32 MAX_NUM_RENDER_BUFFERS = 8; static uint32 getMaxNumRenderBuffers() { return MAX_NUM_RENDER_BUFFERS; } diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index a7ac472922..ae1afcafcb 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -389,6 +389,8 @@ public: uint16 usedMipLevels() const { return (_maxMip - _minMip) + 1; } + const std::string& source() const { return _source; } + void setSource(const std::string& source) { _source = source; } bool setMinMip(uint16 newMinMip); bool incremementMinMip(uint16 count = 1); @@ -450,6 +452,8 @@ public: const GPUObjectPointer gpuObject {}; protected: + // Not strictly necessary, but incredibly useful for debugging + std::string _source; std::unique_ptr< Storage > _storage; Stamp _stamp = 0; diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index 32a6f4c323..b4803a7bcd 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -35,8 +35,7 @@ #include "ModelNetworkingLogging.h" TextureCache::TextureCache() { - const qint64 TEXTURE_DEFAULT_UNUSED_MAX_SIZE = DEFAULT_UNUSED_MAX_SIZE; - setUnusedResourceCacheSize(TEXTURE_DEFAULT_UNUSED_MAX_SIZE); + setUnusedResourceCacheSize(0); setObjectName("TextureCache"); // Expose enum Type to JS/QML via properties @@ -118,6 +117,7 @@ const unsigned char OPAQUE_BLACK[] = { 0x00, 0x00, 0x00, 0xFF }; const gpu::TexturePointer& TextureCache::getWhiteTexture() { if (!_whiteTexture) { _whiteTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element::COLOR_RGBA_32, 1, 1)); + _whiteTexture->setSource("TextureCache::_whiteTexture"); _whiteTexture->assignStoredMip(0, _whiteTexture->getTexelFormat(), sizeof(OPAQUE_WHITE), OPAQUE_WHITE); } return _whiteTexture; @@ -126,6 +126,7 @@ const gpu::TexturePointer& TextureCache::getWhiteTexture() { const gpu::TexturePointer& TextureCache::getGrayTexture() { if (!_grayTexture) { _grayTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element::COLOR_RGBA_32, 1, 1)); + _grayTexture->setSource("TextureCache::_grayTexture"); _grayTexture->assignStoredMip(0, _whiteTexture->getTexelFormat(), sizeof(OPAQUE_WHITE), OPAQUE_GRAY); } return _grayTexture; @@ -134,6 +135,7 @@ const gpu::TexturePointer& TextureCache::getGrayTexture() { const gpu::TexturePointer& TextureCache::getBlueTexture() { if (!_blueTexture) { _blueTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element::COLOR_RGBA_32, 1, 1)); + _blueTexture->setSource("TextureCache::_blueTexture"); _blueTexture->assignStoredMip(0, _blueTexture->getTexelFormat(), sizeof(OPAQUE_BLUE), OPAQUE_BLUE); } return _blueTexture; @@ -142,6 +144,7 @@ const gpu::TexturePointer& TextureCache::getBlueTexture() { const gpu::TexturePointer& TextureCache::getBlackTexture() { if (!_blackTexture) { _blackTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element::COLOR_RGBA_32, 1, 1)); + _blackTexture->setSource("TextureCache::_blackTexture"); _blackTexture->assignStoredMip(0, _whiteTexture->getTexelFormat(), sizeof(OPAQUE_BLACK), OPAQUE_BLACK); } return _blackTexture; diff --git a/libraries/model/src/model/TextureMap.cpp b/libraries/model/src/model/TextureMap.cpp index 9345124d54..30f176b5a6 100755 --- a/libraries/model/src/model/TextureMap.cpp +++ b/libraries/model/src/model/TextureMap.cpp @@ -176,7 +176,7 @@ void generateFaceMips(gpu::Texture* texture, QImage& image, gpu::Element formatM #endif } -gpu::Texture* TextureUsage::process2DTextureColorFromImage(const QImage& srcImage, bool isLinear, bool doCompress, bool generateMips) { +gpu::Texture* TextureUsage::process2DTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName, bool isLinear, bool doCompress, bool generateMips) { bool validAlpha = false; bool alphaAsMask = true; QImage image = process2DImageColor(srcImage, validAlpha, alphaAsMask); @@ -189,7 +189,7 @@ gpu::Texture* TextureUsage::process2DTextureColorFromImage(const QImage& srcImag defineColorTexelFormats(formatGPU, formatMip, image, isLinear, doCompress); theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR))); - + theTexture->setSource(srcImageName); auto usage = gpu::Texture::Usage::Builder().withColor(); if (validAlpha) { usage.withAlpha(); @@ -210,20 +210,20 @@ gpu::Texture* TextureUsage::process2DTextureColorFromImage(const QImage& srcImag } gpu::Texture* TextureUsage::create2DTextureFromImage(const QImage& srcImage, const std::string& srcImageName) { - return process2DTextureColorFromImage(srcImage, false, false, true); + return process2DTextureColorFromImage(srcImage, srcImageName, false, false, true); } gpu::Texture* TextureUsage::createAlbedoTextureFromImage(const QImage& srcImage, const std::string& srcImageName) { - return process2DTextureColorFromImage(srcImage, false, true, true); + return process2DTextureColorFromImage(srcImage, srcImageName, false, true, true); } gpu::Texture* TextureUsage::createEmissiveTextureFromImage(const QImage& srcImage, const std::string& srcImageName) { - return process2DTextureColorFromImage(srcImage, false, true, true); + return process2DTextureColorFromImage(srcImage, srcImageName, false, true, true); } gpu::Texture* TextureUsage::createLightmapTextureFromImage(const QImage& srcImage, const std::string& srcImageName) { - return process2DTextureColorFromImage(srcImage, false, true, true); + return process2DTextureColorFromImage(srcImage, srcImageName, false, true, true); } @@ -241,6 +241,7 @@ gpu::Texture* TextureUsage::createNormalTextureFromNormalImage(const QImage& src gpu::Element formatMip = gpu::Element(gpu::VEC3, gpu::NUINT8, gpu::RGB); theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR))); + theTexture->setSource(srcImageName); theTexture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits()); generateMips(theTexture, image, formatMip); } @@ -324,6 +325,7 @@ gpu::Texture* TextureUsage::createNormalTextureFromBumpImage(const QImage& srcIm gpu::Element formatMip = gpu::Element(gpu::VEC3, gpu::NUINT8, gpu::RGB); theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR))); + theTexture->setSource(srcImageName); theTexture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits()); generateMips(theTexture, image, formatMip); } @@ -355,6 +357,7 @@ gpu::Texture* TextureUsage::createRoughnessTextureFromImage(const QImage& srcIma gpu::Element formatMip = gpu::Element(gpu::SCALAR, gpu::NUINT8, gpu::RGB); theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR))); + theTexture->setSource(srcImageName); theTexture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits()); generateMips(theTexture, image, formatMip); @@ -392,6 +395,7 @@ gpu::Texture* TextureUsage::createRoughnessTextureFromGlossImage(const QImage& s gpu::Element formatMip = gpu::Element(gpu::SCALAR, gpu::NUINT8, gpu::RGB); theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR))); + theTexture->setSource(srcImageName); theTexture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits()); generateMips(theTexture, image, formatMip); @@ -426,6 +430,7 @@ gpu::Texture* TextureUsage::createMetallicTextureFromImage(const QImage& srcImag gpu::Element formatMip = gpu::Element(gpu::SCALAR, gpu::NUINT8, gpu::RGB); theTexture = (gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR))); + theTexture->setSource(srcImageName); theTexture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits()); generateMips(theTexture, image, formatMip); @@ -737,6 +742,7 @@ gpu::Texture* TextureUsage::processCubeTextureColorFromImage(const QImage& srcIm // If the 6 faces have been created go on and define the true Texture if (faces.size() == gpu::Texture::NUM_FACES_PER_TYPE[gpu::Texture::TEX_CUBE]) { theTexture = gpu::Texture::createCube(formatGPU, faces[0].width(), gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)); + theTexture->setSource(srcImageName); int f = 0; for (auto& face : faces) { theTexture->assignStoredMipFace(0, formatMip, face.byteCount(), face.constBits(), f); diff --git a/libraries/model/src/model/TextureMap.h b/libraries/model/src/model/TextureMap.h index ac35db2f03..220ee57a97 100755 --- a/libraries/model/src/model/TextureMap.h +++ b/libraries/model/src/model/TextureMap.h @@ -47,7 +47,7 @@ public: static const QImage process2DImageColor(const QImage& srcImage, bool& validAlpha, bool& alphaAsMask); static void defineColorTexelFormats(gpu::Element& formatGPU, gpu::Element& formatMip, const QImage& srcImage, bool isLinear, bool doCompress); - static gpu::Texture* process2DTextureColorFromImage(const QImage& srcImage, bool isLinear, bool doCompress, bool generateMips); + static gpu::Texture* process2DTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName, bool isLinear, bool doCompress, bool generateMips); static gpu::Texture* processCubeTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName, bool isLinear, bool doCompress, bool generateMips, bool generateIrradiance); }; diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 739c0f8f4a..eecc1515f5 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -402,13 +402,18 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointerreadWithoutCopy(reasonSize); QString reasonMessage = QString::fromUtf8(reasonText); + quint16 extraInfoSize; + message->readPrimitive(&extraInfoSize); + auto extraInfoUtf8= message->readWithoutCopy(extraInfoSize); + QString extraInfo = QString::fromUtf8(extraInfoUtf8); + // output to the log so the user knows they got a denied connection request // and check and signal for an access token so that we can make sure they are logged in - qCWarning(networking) << "The domain-server denied a connection request: " << reasonMessage; + qCWarning(networking) << "The domain-server denied a connection request: " << reasonMessage << " extraInfo:" << extraInfo; if (!_domainConnectionRefusals.contains(reasonMessage)) { _domainConnectionRefusals.insert(reasonMessage); - emit domainConnectionRefused(reasonMessage, (int)reasonCode); + emit domainConnectionRefused(reasonMessage, (int)reasonCode, extraInfo); } auto accountManager = DependencyManager::get(); diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 50639a4817..7f89b47197 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -123,7 +123,7 @@ signals: void settingsReceived(const QJsonObject& domainSettingsObject); void settingsReceiveFail(); - void domainConnectionRefused(QString reasonMessage, int reason); + void domainConnectionRefused(QString reasonMessage, int reason, const QString& extraInfo); private: bool reasonSuggestsLogin(ConnectionRefusedReason reasonCode); diff --git a/libraries/networking/src/HifiSockAddr.cpp b/libraries/networking/src/HifiSockAddr.cpp index bde0aca7b7..d6981f420d 100644 --- a/libraries/networking/src/HifiSockAddr.cpp +++ b/libraries/networking/src/HifiSockAddr.cpp @@ -124,36 +124,6 @@ QDataStream& operator>>(QDataStream& dataStream, HifiSockAddr& sockAddr) { return dataStream; } -QHostAddress getGuessedLocalAddress() { - - QHostAddress localAddress; - - foreach(const QNetworkInterface &networkInterface, QNetworkInterface::allInterfaces()) { - if (networkInterface.flags() & QNetworkInterface::IsUp - && networkInterface.flags() & QNetworkInterface::IsRunning - && networkInterface.flags() & ~QNetworkInterface::IsLoopBack) { - // we've decided that this is the active NIC - // enumerate it's addresses to grab the IPv4 address - foreach(const QNetworkAddressEntry &entry, networkInterface.addressEntries()) { - // make sure it's an IPv4 address that isn't the loopback - if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol && !entry.ip().isLoopback()) { - - // set our localAddress and break out - localAddress = entry.ip(); - break; - } - } - } - - if (!localAddress.isNull()) { - break; - } - } - - // return the looked up local address - return localAddress; -} - uint qHash(const HifiSockAddr& key, uint seed) { // use the existing QHostAddress and quint16 hash functions to get our hash return qHash(key.getAddress(), seed) ^ qHash(key.getPort(), seed); diff --git a/libraries/networking/src/HifiSockAddr.h b/libraries/networking/src/HifiSockAddr.h index 063a41a202..c4ff8cb246 100644 --- a/libraries/networking/src/HifiSockAddr.h +++ b/libraries/networking/src/HifiSockAddr.h @@ -91,9 +91,6 @@ namespace std { }; } - -QHostAddress getGuessedLocalAddress(); - Q_DECLARE_METATYPE(HifiSockAddr); #endif // hifi_HifiSockAddr_h diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 5aa31efea4..ce555315e8 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include #include @@ -745,8 +746,32 @@ void LimitedNodeList::removeSilentNodes() { const uint32_t RFC_5389_MAGIC_COOKIE = 0x2112A442; const int NUM_BYTES_STUN_HEADER = 20; -void LimitedNodeList::sendSTUNRequest() { +void LimitedNodeList::makeSTUNRequestPacket(char* stunRequestPacket) { + int packetIndex = 0; + + const uint32_t RFC_5389_MAGIC_COOKIE_NETWORK_ORDER = htonl(RFC_5389_MAGIC_COOKIE); + + // leading zeros + message type + const uint16_t REQUEST_MESSAGE_TYPE = htons(0x0001); + memcpy(stunRequestPacket + packetIndex, &REQUEST_MESSAGE_TYPE, sizeof(REQUEST_MESSAGE_TYPE)); + packetIndex += sizeof(REQUEST_MESSAGE_TYPE); + + // message length (no additional attributes are included) + uint16_t messageLength = 0; + memcpy(stunRequestPacket + packetIndex, &messageLength, sizeof(messageLength)); + packetIndex += sizeof(messageLength); + + memcpy(stunRequestPacket + packetIndex, &RFC_5389_MAGIC_COOKIE_NETWORK_ORDER, sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER)); + packetIndex += sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER); + + // transaction ID (random 12-byte unsigned integer) + const uint NUM_TRANSACTION_ID_BYTES = 12; + QUuid randomUUID = QUuid::createUuid(); + memcpy(stunRequestPacket + packetIndex, randomUUID.toRfc4122().data(), NUM_TRANSACTION_ID_BYTES); +} + +void LimitedNodeList::sendSTUNRequest() { if (!_stunSockAddr.getAddress().isNull()) { const int NUM_INITIAL_STUN_REQUESTS_BEFORE_FAIL = 10; @@ -762,36 +787,14 @@ void LimitedNodeList::sendSTUNRequest() { } char stunRequestPacket[NUM_BYTES_STUN_HEADER]; - - int packetIndex = 0; - - const uint32_t RFC_5389_MAGIC_COOKIE_NETWORK_ORDER = htonl(RFC_5389_MAGIC_COOKIE); - - // leading zeros + message type - const uint16_t REQUEST_MESSAGE_TYPE = htons(0x0001); - memcpy(stunRequestPacket + packetIndex, &REQUEST_MESSAGE_TYPE, sizeof(REQUEST_MESSAGE_TYPE)); - packetIndex += sizeof(REQUEST_MESSAGE_TYPE); - - // message length (no additional attributes are included) - uint16_t messageLength = 0; - memcpy(stunRequestPacket + packetIndex, &messageLength, sizeof(messageLength)); - packetIndex += sizeof(messageLength); - - memcpy(stunRequestPacket + packetIndex, &RFC_5389_MAGIC_COOKIE_NETWORK_ORDER, sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER)); - packetIndex += sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER); - - // transaction ID (random 12-byte unsigned integer) - const uint NUM_TRANSACTION_ID_BYTES = 12; - QUuid randomUUID = QUuid::createUuid(); - memcpy(stunRequestPacket + packetIndex, randomUUID.toRfc4122().data(), NUM_TRANSACTION_ID_BYTES); - + makeSTUNRequestPacket(stunRequestPacket); flagTimeForConnectionStep(ConnectionStep::SendSTUNRequest); - _nodeSocket.writeDatagram(stunRequestPacket, sizeof(stunRequestPacket), _stunSockAddr); } } -void LimitedNodeList::processSTUNResponse(std::unique_ptr packet) { +bool LimitedNodeList::parseSTUNResponse(udt::BasePacket* packet, + QHostAddress& newPublicAddress, uint16_t& newPublicPort) { // check the cookie to make sure this is actually a STUN response // and read the first attribute and make sure it is a XOR_MAPPED_ADDRESS const int NUM_BYTES_MESSAGE_TYPE_AND_LENGTH = 4; @@ -803,71 +806,79 @@ void LimitedNodeList::processSTUNResponse(std::unique_ptr packe if (memcmp(packet->getData() + NUM_BYTES_MESSAGE_TYPE_AND_LENGTH, &RFC_5389_MAGIC_COOKIE_NETWORK_ORDER, - sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER)) == 0) { + sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER)) != 0) { + return false; + } - // enumerate the attributes to find XOR_MAPPED_ADDRESS_TYPE - while (attributeStartIndex < packet->getDataSize()) { + // enumerate the attributes to find XOR_MAPPED_ADDRESS_TYPE + while (attributeStartIndex < packet->getDataSize()) { + if (memcmp(packet->getData() + attributeStartIndex, &XOR_MAPPED_ADDRESS_TYPE, sizeof(XOR_MAPPED_ADDRESS_TYPE)) == 0) { + const int NUM_BYTES_STUN_ATTR_TYPE_AND_LENGTH = 4; + const int NUM_BYTES_FAMILY_ALIGN = 1; + const uint8_t IPV4_FAMILY_NETWORK_ORDER = htons(0x01) >> 8; - if (memcmp(packet->getData() + attributeStartIndex, &XOR_MAPPED_ADDRESS_TYPE, sizeof(XOR_MAPPED_ADDRESS_TYPE)) == 0) { - const int NUM_BYTES_STUN_ATTR_TYPE_AND_LENGTH = 4; - const int NUM_BYTES_FAMILY_ALIGN = 1; - const uint8_t IPV4_FAMILY_NETWORK_ORDER = htons(0x01) >> 8; + int byteIndex = attributeStartIndex + NUM_BYTES_STUN_ATTR_TYPE_AND_LENGTH + NUM_BYTES_FAMILY_ALIGN; - int byteIndex = attributeStartIndex + NUM_BYTES_STUN_ATTR_TYPE_AND_LENGTH + NUM_BYTES_FAMILY_ALIGN; + uint8_t addressFamily = 0; + memcpy(&addressFamily, packet->getData() + byteIndex, sizeof(addressFamily)); - uint8_t addressFamily = 0; - memcpy(&addressFamily, packet->getData() + byteIndex, sizeof(addressFamily)); + byteIndex += sizeof(addressFamily); - byteIndex += sizeof(addressFamily); + if (addressFamily == IPV4_FAMILY_NETWORK_ORDER) { + // grab the X-Port + uint16_t xorMappedPort = 0; + memcpy(&xorMappedPort, packet->getData() + byteIndex, sizeof(xorMappedPort)); - if (addressFamily == IPV4_FAMILY_NETWORK_ORDER) { - // grab the X-Port - uint16_t xorMappedPort = 0; - memcpy(&xorMappedPort, packet->getData() + byteIndex, sizeof(xorMappedPort)); + newPublicPort = ntohs(xorMappedPort) ^ (ntohl(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER) >> 16); - uint16_t newPublicPort = ntohs(xorMappedPort) ^ (ntohl(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER) >> 16); + byteIndex += sizeof(xorMappedPort); - byteIndex += sizeof(xorMappedPort); + // grab the X-Address + uint32_t xorMappedAddress = 0; + memcpy(&xorMappedAddress, packet->getData() + byteIndex, sizeof(xorMappedAddress)); - // grab the X-Address - uint32_t xorMappedAddress = 0; - memcpy(&xorMappedAddress, packet->getData() + byteIndex, sizeof(xorMappedAddress)); + uint32_t stunAddress = ntohl(xorMappedAddress) ^ ntohl(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER); - uint32_t stunAddress = ntohl(xorMappedAddress) ^ ntohl(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER); - - QHostAddress newPublicAddress(stunAddress); - - if (newPublicAddress != _publicSockAddr.getAddress() || newPublicPort != _publicSockAddr.getPort()) { - _publicSockAddr = HifiSockAddr(newPublicAddress, newPublicPort); - - qCDebug(networking, "New public socket received from STUN server is %s:%hu", - _publicSockAddr.getAddress().toString().toLocal8Bit().constData(), - _publicSockAddr.getPort()); - - if (!_hasCompletedInitialSTUN) { - // if we're here we have definitely completed our initial STUN sequence - stopInitialSTUNUpdate(true); - } - - emit publicSockAddrChanged(_publicSockAddr); - - flagTimeForConnectionStep(ConnectionStep::SetPublicSocketFromSTUN); - } - - // we're done reading the packet so we can return now - return; - } - } else { - // push forward attributeStartIndex by the length of this attribute - const int NUM_BYTES_ATTRIBUTE_TYPE = 2; - - uint16_t attributeLength = 0; - memcpy(&attributeLength, packet->getData() + attributeStartIndex + NUM_BYTES_ATTRIBUTE_TYPE, - sizeof(attributeLength)); - attributeLength = ntohs(attributeLength); - - attributeStartIndex += NUM_BYTES_MESSAGE_TYPE_AND_LENGTH + attributeLength; + // QHostAddress newPublicAddress(stunAddress); + newPublicAddress = QHostAddress(stunAddress); + return true; } + } else { + // push forward attributeStartIndex by the length of this attribute + const int NUM_BYTES_ATTRIBUTE_TYPE = 2; + + uint16_t attributeLength = 0; + memcpy(&attributeLength, packet->getData() + attributeStartIndex + NUM_BYTES_ATTRIBUTE_TYPE, + sizeof(attributeLength)); + attributeLength = ntohs(attributeLength); + + attributeStartIndex += NUM_BYTES_MESSAGE_TYPE_AND_LENGTH + attributeLength; + } + } + return false; +} + + +void LimitedNodeList::processSTUNResponse(std::unique_ptr packet) { + uint16_t newPublicPort; + QHostAddress newPublicAddress; + if (parseSTUNResponse(packet.get(), newPublicAddress, newPublicPort)) { + + if (newPublicAddress != _publicSockAddr.getAddress() || newPublicPort != _publicSockAddr.getPort()) { + _publicSockAddr = HifiSockAddr(newPublicAddress, newPublicPort); + + qCDebug(networking, "New public socket received from STUN server is %s:%hu", + _publicSockAddr.getAddress().toString().toLocal8Bit().constData(), + _publicSockAddr.getPort()); + + if (!_hasCompletedInitialSTUN) { + // if we're here we have definitely completed our initial STUN sequence + stopInitialSTUNUpdate(true); + } + + emit publicSockAddrChanged(_publicSockAddr); + + flagTimeForConnectionStep(ConnectionStep::SetPublicSocketFromSTUN); } } } diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index cd343a5232..e74a6c49f8 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -146,6 +146,7 @@ public: const NodePermissions& permissions = DEFAULT_AGENT_PERMISSIONS, const QUuid& connectionSecret = QUuid()); + static bool parseSTUNResponse(udt::BasePacket* packet, QHostAddress& newPublicAddress, uint16_t& newPublicPort); bool hasCompletedInitialSTUN() const { return _hasCompletedInitialSTUN; } const HifiSockAddr& getLocalSockAddr() const { return _localSockAddr; } @@ -166,8 +167,8 @@ public: std::unique_ptr constructPingPacket(PingType_t pingType = PingType::Agnostic); std::unique_ptr constructPingReplyPacket(ReceivedMessage& message); - std::unique_ptr constructICEPingPacket(PingType_t pingType, const QUuid& iceID); - std::unique_ptr constructICEPingReplyPacket(ReceivedMessage& message, const QUuid& iceID); + static std::unique_ptr constructICEPingPacket(PingType_t pingType, const QUuid& iceID); + static std::unique_ptr constructICEPingReplyPacket(ReceivedMessage& message, const QUuid& iceID); void sendPeerQueryToIceServer(const HifiSockAddr& iceServerSockAddr, const QUuid& clientID, const QUuid& peerID); @@ -232,6 +233,9 @@ public: bool packetVersionMatch(const udt::Packet& packet); bool isPacketVerified(const udt::Packet& packet); + static void makeSTUNRequestPacket(char* stunRequestPacket); + + public slots: void reset(); void eraseAllNodes(); @@ -275,7 +279,7 @@ protected: LimitedNodeList(int socketListenPort = INVALID_PORT, int dtlsListenPort = INVALID_PORT); LimitedNodeList(LimitedNodeList const&) = delete; // Don't implement, needed to avoid copies of singleton void operator=(LimitedNodeList const&) = delete; // Don't implement, needed to avoid copies of singleton - + qint64 sendPacket(std::unique_ptr packet, const Node& destinationNode, const HifiSockAddr& overridenSockAddr); qint64 writePacket(const NLPacket& packet, const HifiSockAddr& destinationSockAddr, @@ -284,7 +288,7 @@ protected: void fillPacketHeader(const NLPacket& packet, const QUuid& connectionSecret = QUuid()); void setLocalSocket(const HifiSockAddr& sockAddr); - + bool packetSourceAndHashMatchAndTrackBandwidth(const udt::Packet& packet); void processSTUNResponse(std::unique_ptr packet); diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 0f3d5885ff..ec4e724c1b 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -64,7 +64,7 @@ PacketVersion versionForPacketType(PacketType packetType) { return 18; // Introduction of node ignore request (which replaced an unused packet tpye) case PacketType::DomainConnectionDenied: - return static_cast(DomainConnectionDeniedVersion::IncludesReasonCode); + return static_cast(DomainConnectionDeniedVersion::IncludesExtraInfo); case PacketType::DomainConnectRequest: return static_cast(DomainConnectRequestVersion::HasProtocolVersions); diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 25500b984f..aa775b9f53 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -206,7 +206,8 @@ enum class DomainConnectRequestVersion : PacketVersion { enum class DomainConnectionDeniedVersion : PacketVersion { ReasonMessageOnly = 17, - IncludesReasonCode + IncludesReasonCode, + IncludesExtraInfo }; enum class DomainServerAddedNodeVersion : PacketVersion { diff --git a/libraries/render-utils/src/AntialiasingEffect.cpp b/libraries/render-utils/src/AntialiasingEffect.cpp index b7995c1b47..8147b06846 100644 --- a/libraries/render-utils/src/AntialiasingEffect.cpp +++ b/libraries/render-utils/src/AntialiasingEffect.cpp @@ -34,7 +34,21 @@ Antialiasing::Antialiasing() { const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline() { int width = DependencyManager::get()->getFrameBufferSize().width(); int height = DependencyManager::get()->getFrameBufferSize().height(); - + + if (_antialiasingBuffer && _antialiasingBuffer->getSize() != uvec2(width, height)) { + _antialiasingBuffer.reset(); + } + + if (!_antialiasingBuffer) { + // Link the antialiasing FBO to texture + _antialiasingBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create()); + auto format = gpu::Element::COLOR_SRGBA_32; // DependencyManager::get()->getLightingTexture()->getTexelFormat(); + auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT); + _antialiasingTexture = gpu::TexturePointer(gpu::Texture::create2D(format, width, height, defaultSampler)); + _antialiasingTexture->setSource("Antialiasing::_antialiasingTexture"); + _antialiasingBuffer->setRenderBuffer(0, _antialiasingTexture); + } + if (!_antialiasingPipeline) { auto vs = gpu::Shader::createVertex(std::string(fxaa_vert)); auto ps = gpu::Shader::createPixel(std::string(fxaa_frag)); @@ -51,21 +65,10 @@ const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline() { state->setDepthTest(false, false, gpu::LESS_EQUAL); - // Link the antialiasing FBO to texture - _antialiasingBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create()); - auto format = gpu::Element::COLOR_SRGBA_32; // DependencyManager::get()->getLightingTexture()->getTexelFormat(); - auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT); - _antialiasingTexture = gpu::TexturePointer(gpu::Texture::create2D(format, width, height, defaultSampler)); - _antialiasingBuffer->setRenderBuffer(0, _antialiasingTexture); - // Good to go add the brand new pipeline _antialiasingPipeline = gpu::Pipeline::create(program, state); } - if (width != _antialiasingBuffer->getWidth() || height != _antialiasingBuffer->getHeight()) { - _antialiasingBuffer->resize(width, height); - } - return _antialiasingPipeline; } diff --git a/libraries/render-utils/src/DeferredFramebuffer.cpp b/libraries/render-utils/src/DeferredFramebuffer.cpp index 32f91f83d8..067687f9ef 100644 --- a/libraries/render-utils/src/DeferredFramebuffer.cpp +++ b/libraries/render-utils/src/DeferredFramebuffer.cpp @@ -54,9 +54,11 @@ void DeferredFramebuffer::allocate() { auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT); _deferredColorTexture = gpu::TexturePointer(gpu::Texture::create2D(colorFormat, width, height, defaultSampler)); - + _deferredColorTexture->setSource("DeferredFramebuffer::_deferredColorTexture"); _deferredNormalTexture = gpu::TexturePointer(gpu::Texture::create2D(linearFormat, width, height, defaultSampler)); + _deferredNormalTexture->setSource("DeferredFramebuffer::_deferredNormalTexture"); _deferredSpecularTexture = gpu::TexturePointer(gpu::Texture::create2D(colorFormat, width, height, defaultSampler)); + _deferredSpecularTexture->setSource("DeferredFramebuffer::_deferredSpecularTexture"); _deferredFramebuffer->setRenderBuffer(0, _deferredColorTexture); _deferredFramebuffer->setRenderBuffer(1, _deferredNormalTexture); @@ -67,6 +69,7 @@ void DeferredFramebuffer::allocate() { auto depthFormat = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::DEPTH_STENCIL); // Depth24_Stencil8 texel format if (!_primaryDepthTexture) { _primaryDepthTexture = gpu::TexturePointer(gpu::Texture::create2D(depthFormat, width, height, defaultSampler)); + _primaryDepthTexture->setSource("DeferredFramebuffer::_primaryDepthTexture"); } _deferredFramebuffer->setDepthStencilBuffer(_primaryDepthTexture, depthFormat); @@ -77,6 +80,7 @@ void DeferredFramebuffer::allocate() { auto smoothSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR); _lightingTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::R11G11B10), width, height, defaultSampler)); + _lightingTexture->setSource("DeferredFramebuffer::_lightingTexture"); _lightingFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create()); _lightingFramebuffer->setRenderBuffer(0, _lightingTexture); _lightingFramebuffer->setDepthStencilBuffer(_primaryDepthTexture, depthFormat); diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index 8e83f737ea..808d765dd7 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -337,7 +337,13 @@ void PreparePrimaryFramebuffer::run(const SceneContextPointer& sceneContext, con auto framebufferCache = DependencyManager::get(); auto framebufferSize = framebufferCache->getFrameBufferSize(); - glm::ivec2 frameSize(framebufferSize.width(), framebufferSize.height()); + glm::uvec2 frameSize(framebufferSize.width(), framebufferSize.height()); + + // Resizing framebuffers instead of re-building them seems to cause issues with threaded + // rendering + if (_primaryFramebuffer && _primaryFramebuffer->getSize() != frameSize) { + _primaryFramebuffer.reset(); + } if (!_primaryFramebuffer) { _primaryFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create()); @@ -345,6 +351,7 @@ void PreparePrimaryFramebuffer::run(const SceneContextPointer& sceneContext, con auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT); auto primaryColorTexture = gpu::TexturePointer(gpu::Texture::create2D(colorFormat, frameSize.x, frameSize.y, defaultSampler)); + primaryColorTexture->setSource("PreparePrimaryFramebuffer::primaryColorTexture"); _primaryFramebuffer->setRenderBuffer(0, primaryColorTexture); @@ -352,11 +359,10 @@ void PreparePrimaryFramebuffer::run(const SceneContextPointer& sceneContext, con auto depthFormat = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::DEPTH_STENCIL); // Depth24_Stencil8 texel format auto primaryDepthTexture = gpu::TexturePointer(gpu::Texture::create2D(depthFormat, frameSize.x, frameSize.y, defaultSampler)); + primaryDepthTexture->setSource("PreparePrimaryFramebuffer::primaryDepthTexture"); _primaryFramebuffer->setDepthStencilBuffer(primaryDepthTexture, depthFormat); - } - _primaryFramebuffer->resize(frameSize.x, frameSize.y); primaryFramebuffer = _primaryFramebuffer; } diff --git a/libraries/render-utils/src/SubsurfaceScattering.cpp b/libraries/render-utils/src/SubsurfaceScattering.cpp index f1aec66433..3e4ec50dee 100644 --- a/libraries/render-utils/src/SubsurfaceScattering.cpp +++ b/libraries/render-utils/src/SubsurfaceScattering.cpp @@ -415,6 +415,7 @@ gpu::TexturePointer SubsurfaceScatteringResource::generateScatteringProfile(Rend // const auto pixelFormat = gpu::Element::COLOR_SRGBA_32; const auto pixelFormat = gpu::Element::COLOR_R11G11B10; auto profileMap = gpu::TexturePointer(gpu::Texture::create2D(pixelFormat, PROFILE_RESOLUTION, 1, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP))); + profileMap->setSource("Generated Scattering Profile"); diffuseProfileGPU(profileMap, args); return profileMap; } @@ -426,6 +427,7 @@ gpu::TexturePointer SubsurfaceScatteringResource::generatePreIntegratedScatterin const auto pixelFormat = gpu::Element::COLOR_R11G11B10; auto scatteringLUT = gpu::TexturePointer(gpu::Texture::create2D(pixelFormat, TABLE_RESOLUTION, TABLE_RESOLUTION, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP))); //diffuseScatter(scatteringLUT); + scatteringLUT->setSource("Generated pre-integrated scattering"); diffuseScatterGPU(profile, scatteringLUT, args); return scatteringLUT; } @@ -433,6 +435,7 @@ gpu::TexturePointer SubsurfaceScatteringResource::generatePreIntegratedScatterin gpu::TexturePointer SubsurfaceScatteringResource::generateScatteringSpecularBeckmann(RenderArgs* args) { const int SPECULAR_RESOLUTION = 256; auto beckmannMap = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element::COLOR_RGBA_32 /*gpu::Element(gpu::SCALAR, gpu::HALF, gpu::RGB)*/, SPECULAR_RESOLUTION, SPECULAR_RESOLUTION, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP))); + beckmannMap->setSource("Generated beckmannMap"); computeSpecularBeckmannGPU(beckmannMap, args); return beckmannMap; } diff --git a/libraries/render-utils/src/SurfaceGeometryPass.cpp b/libraries/render-utils/src/SurfaceGeometryPass.cpp index 1957f8456a..e43afd94a9 100644 --- a/libraries/render-utils/src/SurfaceGeometryPass.cpp +++ b/libraries/render-utils/src/SurfaceGeometryPass.cpp @@ -74,6 +74,7 @@ void LinearDepthFramebuffer::allocate() { // For Linear Depth: _linearDepthTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::RGB), width, height, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT))); + _linearDepthTexture->setSource("LinearDepthFramebuffer::_linearDepthTexture"); _linearDepthFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create()); _linearDepthFramebuffer->setRenderBuffer(0, _linearDepthTexture); _linearDepthFramebuffer->setDepthStencilBuffer(_primaryDepthTexture, _primaryDepthTexture->getTexelFormat()); @@ -81,10 +82,12 @@ void LinearDepthFramebuffer::allocate() { // For Downsampling: _halfLinearDepthTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::RGB), _halfFrameSize.x, _halfFrameSize.y, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT))); + _halfLinearDepthTexture->setSource("LinearDepthFramebuffer::_halfLinearDepthTexture"); _halfLinearDepthTexture->autoGenerateMips(5); _halfNormalTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element(gpu::VEC3, gpu::NUINT8, gpu::RGB), _halfFrameSize.x, _halfFrameSize.y, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT))); + _halfNormalTexture->setSource("LinearDepthFramebuffer::_halfNormalTexture"); _downsampleFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create()); _downsampleFramebuffer->setRenderBuffer(0, _halfLinearDepthTexture); @@ -301,14 +304,17 @@ void SurfaceGeometryFramebuffer::allocate() { auto height = _frameSize.y; _curvatureTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element::COLOR_RGBA_32, width, height, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT))); + _curvatureTexture->setSource("SurfaceGeometryFramebuffer::_curvatureTexture"); _curvatureFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create()); _curvatureFramebuffer->setRenderBuffer(0, _curvatureTexture); _lowCurvatureTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element::COLOR_RGBA_32, width, height, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT))); + _lowCurvatureTexture->setSource("SurfaceGeometryFramebuffer::_lowCurvatureTexture"); _lowCurvatureFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create()); _lowCurvatureFramebuffer->setRenderBuffer(0, _lowCurvatureTexture); _blurringTexture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element::COLOR_RGBA_32, width, height, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT))); + _blurringTexture->setSource("SurfaceGeometryFramebuffer::_blurringTexture"); _blurringFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create()); _blurringFramebuffer->setRenderBuffer(0, _blurringTexture); } diff --git a/libraries/render/src/render/BlurTask.cpp b/libraries/render/src/render/BlurTask.cpp index 3849adf588..144e1e0058 100644 --- a/libraries/render/src/render/BlurTask.cpp +++ b/libraries/render/src/render/BlurTask.cpp @@ -96,6 +96,9 @@ bool BlurInOutResource::updateResources(const gpu::FramebufferPointer& sourceFra if (!sourceFramebuffer) { return false; } + if (_blurredFramebuffer && _blurredFramebuffer->getSize() != sourceFramebuffer->getSize()) { + _blurredFramebuffer.reset(); + } if (!_blurredFramebuffer) { _blurredFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create()); @@ -107,21 +110,17 @@ bool BlurInOutResource::updateResources(const gpu::FramebufferPointer& sourceFra auto blurringSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT); auto blurringTarget = gpu::TexturePointer(gpu::Texture::create2D(sourceFramebuffer->getRenderBuffer(0)->getTexelFormat(), sourceFramebuffer->getWidth(), sourceFramebuffer->getHeight(), blurringSampler)); _blurredFramebuffer->setRenderBuffer(0, blurringTarget); - } else { - // it would be easier to just call resize on the bluredFramebuffer and let it work if needed but the source might loose it's depth buffer when doing so - if ((_blurredFramebuffer->getWidth() != sourceFramebuffer->getWidth()) || (_blurredFramebuffer->getHeight() != sourceFramebuffer->getHeight())) { - _blurredFramebuffer->resize(sourceFramebuffer->getWidth(), sourceFramebuffer->getHeight(), sourceFramebuffer->getNumSamples()); - //if (sourceFramebuffer->hasDepthStencil()) { - // _blurredFramebuffer->setDepthStencilBuffer(sourceFramebuffer->getDepthStencilBuffer(), sourceFramebuffer->getDepthStencilBufferFormat()); - //} - } - } + } blurringResources.sourceTexture = sourceFramebuffer->getRenderBuffer(0); blurringResources.blurringFramebuffer = _blurredFramebuffer; blurringResources.blurringTexture = _blurredFramebuffer->getRenderBuffer(0); if (_generateOutputFramebuffer) { + if (_outputFramebuffer && _outputFramebuffer->getSize() != sourceFramebuffer->getSize()) { + _outputFramebuffer.reset(); + } + // The job output the blur result in a new Framebuffer spawning here. // Let s make sure it s ready for this if (!_outputFramebuffer) { @@ -134,13 +133,6 @@ bool BlurInOutResource::updateResources(const gpu::FramebufferPointer& sourceFra auto blurringSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT); auto blurringTarget = gpu::TexturePointer(gpu::Texture::create2D(sourceFramebuffer->getRenderBuffer(0)->getTexelFormat(), sourceFramebuffer->getWidth(), sourceFramebuffer->getHeight(), blurringSampler)); _outputFramebuffer->setRenderBuffer(0, blurringTarget); - } else { - if ((_outputFramebuffer->getWidth() != sourceFramebuffer->getWidth()) || (_outputFramebuffer->getHeight() != sourceFramebuffer->getHeight())) { - _outputFramebuffer->resize(sourceFramebuffer->getWidth(), sourceFramebuffer->getHeight(), sourceFramebuffer->getNumSamples()); - /* if (sourceFramebuffer->hasDepthStencil()) { - _outputFramebuffer->setDepthStencilBuffer(sourceFramebuffer->getDepthStencilBuffer(), sourceFramebuffer->getDepthStencilBufferFormat()); - }*/ - } } // Should be good to use the output Framebuffer as final diff --git a/interface/src/AbstractLoggerInterface.h b/libraries/shared/src/shared/AbstractLoggerInterface.h similarity index 100% rename from interface/src/AbstractLoggerInterface.h rename to libraries/shared/src/shared/AbstractLoggerInterface.h diff --git a/interface/src/FileLogger.cpp b/libraries/shared/src/shared/FileLogger.cpp similarity index 97% rename from interface/src/FileLogger.cpp rename to libraries/shared/src/shared/FileLogger.cpp index 754fa7f474..ef3436a8d7 100644 --- a/interface/src/FileLogger.cpp +++ b/libraries/shared/src/shared/FileLogger.cpp @@ -15,11 +15,12 @@ #include #include -#include -#include -#include +#include "FileUtils.h" +#include "NetworkUtils.h" + +#include "../NumericalConstants.h" +#include "../SharedUtil.h" -#include "HifiSockAddr.h" static const QString FILENAME_FORMAT = "hifi-log_%1_%2.txt"; static const QString DATETIME_FORMAT = "yyyy-MM-dd_hh.mm.ss"; diff --git a/interface/src/FileLogger.h b/libraries/shared/src/shared/FileLogger.h similarity index 97% rename from interface/src/FileLogger.h rename to libraries/shared/src/shared/FileLogger.h index 950590e789..697b96c6d8 100644 --- a/interface/src/FileLogger.h +++ b/libraries/shared/src/shared/FileLogger.h @@ -13,7 +13,7 @@ #define hifi_FileLogger_h #include "AbstractLoggerInterface.h" -#include +#include "../GenericQueueThread.h" #include diff --git a/interface/src/FileUtils.cpp b/libraries/shared/src/shared/FileUtils.cpp similarity index 76% rename from interface/src/FileUtils.cpp rename to libraries/shared/src/shared/FileUtils.cpp index a89a873bea..8c962dfd6d 100644 --- a/interface/src/FileUtils.cpp +++ b/libraries/shared/src/shared/FileUtils.cpp @@ -9,14 +9,29 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include -#include -#include -#include -#include #include "FileUtils.h" +#include +#include +#include +#include +#include +#include +#include + + +QString FileUtils::readFile(const QString& filename) { + QFile file(filename); + file.open(QFile::Text | QFile::ReadOnly); + QString result; + result.append(QTextStream(&file).readAll()); + return result; +} + +QStringList FileUtils::readLines(const QString& filename, QString::SplitBehavior splitBehavior) { + return readFile(filename).split(QRegularExpression("[\\r\\n]"), QString::SkipEmptyParts); +} void FileUtils::locateFile(QString filePath) { diff --git a/interface/src/FileUtils.h b/libraries/shared/src/shared/FileUtils.h similarity index 73% rename from interface/src/FileUtils.h rename to libraries/shared/src/shared/FileUtils.h index d2b30595a7..4f2c1b7af5 100644 --- a/interface/src/FileUtils.h +++ b/libraries/shared/src/shared/FileUtils.h @@ -19,7 +19,8 @@ class FileUtils { public: static void locateFile(QString fileName); static QString standardPath(QString subfolder); - + static QString readFile(const QString& filename); + static QStringList readLines(const QString& filename, QString::SplitBehavior splitBehavior = QString::KeepEmptyParts); }; #endif // hifi_FileUtils_h diff --git a/libraries/shared/src/shared/NetworkUtils.cpp b/libraries/shared/src/shared/NetworkUtils.cpp new file mode 100644 index 0000000000..50356d30fc --- /dev/null +++ b/libraries/shared/src/shared/NetworkUtils.cpp @@ -0,0 +1,42 @@ +// +// Created by Bradley Austin Davis on 2016/09/20 +// Copyright 2013-2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "NetworkUtils.h" +#include + +QHostAddress getGuessedLocalAddress() { + + QHostAddress localAddress; + + foreach(const QNetworkInterface &networkInterface, QNetworkInterface::allInterfaces()) { + if (networkInterface.flags() & QNetworkInterface::IsUp + && networkInterface.flags() & QNetworkInterface::IsRunning + && networkInterface.flags() & ~QNetworkInterface::IsLoopBack) { + // we've decided that this is the active NIC + // enumerate it's addresses to grab the IPv4 address + foreach(const QNetworkAddressEntry &entry, networkInterface.addressEntries()) { + // make sure it's an IPv4 address that isn't the loopback + if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol && !entry.ip().isLoopback()) { + + // set our localAddress and break out + localAddress = entry.ip(); + break; + } + } + } + + if (!localAddress.isNull()) { + break; + } + } + + // return the looked up local address + return localAddress; +} + + diff --git a/libraries/shared/src/shared/NetworkUtils.h b/libraries/shared/src/shared/NetworkUtils.h new file mode 100644 index 0000000000..b881441004 --- /dev/null +++ b/libraries/shared/src/shared/NetworkUtils.h @@ -0,0 +1,17 @@ +// +// Created by Bradley Austin Davis on 2016/09/20 +// Copyright 2013-2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#pragma once +#ifndef hifi_shared_NetworkUtils_h +#define hifi_shared_NetworkUtils_h + +#include + +QHostAddress getGuessedLocalAddress(); + +#endif // hifi_shared_NetworkUtils_h diff --git a/scripts/system/edit.js b/scripts/system/edit.js index d90566c619..d673bb4653 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -184,7 +184,7 @@ var toolBar = (function () { properties.position = position; entityID = Entities.addEntity(properties); } else { - Window.alert("Can't create " + properties.type + ": " + properties.type + " would be out of bounds."); + Window.notifyEditError("Can't create " + properties.type + ": " + properties.type + " would be out of bounds."); } selectionManager.clearSelections(); @@ -445,7 +445,7 @@ var toolBar = (function () { return; } if (active && !Entities.canRez() && !Entities.canRezTmp()) { - Window.alert(INSUFFICIENT_PERMISSIONS_ERROR_MSG); + Window.notifyEditError(INSUFFICIENT_PERMISSIONS_ERROR_MSG); return; } Messages.sendLocalMessage("edit-events", JSON.stringify({ @@ -1082,13 +1082,13 @@ function handeMenuEvent(menuItem) { deleteSelectedEntities(); } else if (menuItem === "Export Entities") { if (!selectionManager.hasSelection()) { - Window.alert("No entities have been selected."); + Window.notifyEditError("No entities have been selected."); } else { var filename = Window.save("Select Where to Save", "", "*.json"); if (filename) { var success = Clipboard.exportEntities(filename, selectionManager.selections); if (!success) { - Window.alert("Export failed."); + Window.notifyEditError("Export failed."); } } } @@ -1156,7 +1156,7 @@ function getPositionToImportEntity() { } function importSVO(importURL) { if (!Entities.canAdjustLocks()) { - Window.alert(INSUFFICIENT_PERMISSIONS_IMPORT_ERROR_MSG); + Window.notifyEditError(INSUFFICIENT_PERMISSIONS_IMPORT_ERROR_MSG); return; } @@ -1188,10 +1188,10 @@ function importSVO(importURL) { Window.raiseMainWindow(); } else { - Window.alert("Can't import objects: objects would be out of bounds."); + Window.notifyEditError("Can't import objects: objects would be out of bounds."); } } else { - Window.alert("There was an error importing the entity file."); + Window.notifyEditError("There was an error importing the entity file."); } Overlays.editOverlay(importingSVOTextOverlay, { @@ -1481,7 +1481,7 @@ var PropertiesTool = function (opts) { // If any of the natural dimensions are not 0, resize if (properties.type === "Model" && naturalDimensions.x === 0 && naturalDimensions.y === 0 && naturalDimensions.z === 0) { - Window.alert("Cannot reset entity to its natural dimensions: Model URL" + + Window.notifyEditError("Cannot reset entity to its natural dimensions: Model URL" + " is invalid or the model has not yet been loaded."); } else { Entities.editEntity(selectionManager.selections[i], { diff --git a/scripts/system/notifications.js b/scripts/system/notifications.js index f41b0502c8..f3ba466342 100644 --- a/scripts/system/notifications.js +++ b/scripts/system/notifications.js @@ -58,6 +58,8 @@ // } // } +/* global Script, Controller, Overlays, SoundArray, Quat, Vec3, MyAvatar, Menu, HMD, AudioDevice, LODManager, Settings, Camera */ + (function() { // BEGIN LOCAL_SCOPE Script.include("./libraries/soundArray.js"); @@ -76,11 +78,9 @@ var fontSize = 12.0; var PERSIST_TIME_2D = 10.0; // Time in seconds before notification fades var PERSIST_TIME_3D = 15.0; var persistTime = PERSIST_TIME_2D; -var clickedText = false; var frame = 0; var ourWidth = Window.innerWidth; var ourHeight = Window.innerHeight; -var text = "placeholder"; var ctrlIsPressed = false; var ready = true; var MENU_NAME = 'Tools > Notifications'; @@ -97,12 +97,14 @@ var NotificationType = { WINDOW_RESIZE: 3, LOD_WARNING: 4, CONNECTION_REFUSED: 5, + EDIT_ERROR: 6, properties: [ { text: "Mute Toggle" }, { text: "Snapshot" }, { text: "Window Resize" }, { text: "Level of Detail" }, - { text: "Connection Refused" } + { text: "Connection Refused" }, + { text: "Edit error" } ], getTypeFromMenuItem: function(menuItemName) { if (menuItemName.substr(menuItemName.length - NOTIFICATION_MENU_ITEM_POST.length) !== NOTIFICATION_MENU_ITEM_POST) { @@ -253,6 +255,9 @@ function notify(notice, button, height, imageProperties, image) { positions = calculate3DOverlayPositions(noticeWidth, noticeHeight, notice.y); + notice.parentID = MyAvatar.sessionUUID; + notice.parentJointIndex = -2; + if (!image) { notice.topMargin = 0.75 * notice.topMargin * NOTIFICATION_3D_SCALE; notice.leftMargin = 2 * notice.leftMargin * NOTIFICATION_3D_SCALE; @@ -270,6 +275,8 @@ function notify(notice, button, height, imageProperties, image) { button.url = button.imageURL; button.scale = button.width * NOTIFICATION_3D_SCALE; button.isFacingAvatar = false; + button.parentID = MyAvatar.sessionUUID; + button.parentJointIndex = -2; buttons.push((Overlays.addOverlay("image3d", button))); overlay3DDetails.push({ @@ -279,6 +286,34 @@ function notify(notice, button, height, imageProperties, image) { width: noticeWidth, height: noticeHeight }); + + + var defaultEyePosition, + avatarOrientation, + notificationPosition, + notificationOrientation, + buttonPosition; + + if (isOnHMD && notifications.length > 0) { + // Update 3D overlays to maintain positions relative to avatar + defaultEyePosition = MyAvatar.getDefaultEyePosition(); + avatarOrientation = MyAvatar.orientation; + + for (i = 0; i < notifications.length; i += 1) { + notificationPosition = Vec3.sum(defaultEyePosition, + Vec3.multiplyQbyV(avatarOrientation, + overlay3DDetails[i].notificationPosition)); + notificationOrientation = Quat.multiply(avatarOrientation, + overlay3DDetails[i].notificationOrientation); + buttonPosition = Vec3.sum(defaultEyePosition, + Vec3.multiplyQbyV(avatarOrientation, + overlay3DDetails[i].buttonPosition)); + Overlays.editOverlay(notifications[i], { position: notificationPosition, + rotation: notificationOrientation }); + Overlays.editOverlay(buttons[i], { position: buttonPosition, rotation: notificationOrientation }); + } + } + } else { if (!image) { notificationText = Overlays.addOverlay("text", notice); @@ -429,11 +464,6 @@ function update() { noticeOut, buttonOut, arraysOut, - defaultEyePosition, - avatarOrientation, - notificationPosition, - notificationOrientation, - buttonPosition, positions, i, j, @@ -457,7 +487,8 @@ function update() { Overlays.editOverlay(notifications[i], { x: overlayLocationX, y: locationY }); Overlays.editOverlay(buttons[i], { x: buttonLocationX, y: locationY + 12.0 }); if (isOnHMD) { - positions = calculate3DOverlayPositions(overlay3DDetails[i].width, overlay3DDetails[i].height, locationY); + positions = calculate3DOverlayPositions(overlay3DDetails[i].width, + overlay3DDetails[i].height, locationY); overlay3DDetails[i].notificationOrientation = positions.notificationOrientation; overlay3DDetails[i].notificationPosition = positions.notificationPosition; overlay3DDetails[i].buttonPosition = positions.buttonPosition; @@ -480,22 +511,6 @@ function update() { } } } - - if (isOnHMD && notifications.length > 0) { - // Update 3D overlays to maintain positions relative to avatar - defaultEyePosition = MyAvatar.getDefaultEyePosition(); - avatarOrientation = MyAvatar.orientation; - - for (i = 0; i < notifications.length; i += 1) { - notificationPosition = Vec3.sum(defaultEyePosition, - Vec3.multiplyQbyV(avatarOrientation, overlay3DDetails[i].notificationPosition)); - notificationOrientation = Quat.multiply(avatarOrientation, overlay3DDetails[i].notificationOrientation); - buttonPosition = Vec3.sum(defaultEyePosition, - Vec3.multiplyQbyV(avatarOrientation, overlay3DDetails[i].buttonPosition)); - Overlays.editOverlay(notifications[i], { position: notificationPosition, rotation: notificationOrientation }); - Overlays.editOverlay(buttons[i], { position: buttonPosition, rotation: notificationOrientation }); - } - } } var STARTUP_TIMEOUT = 500, // ms @@ -532,12 +547,17 @@ function onDomainConnectionRefused(reason) { createNotification("Connection refused: " + reason, NotificationType.CONNECTION_REFUSED); } +function onEditError(msg) { + createNotification(wordWrap(msg), NotificationType.EDIT_ERROR); +} + + function onSnapshotTaken(path, notify) { if (notify) { var imageProperties = { path: "file:///" + path, aspectRatio: Window.innerWidth / Window.innerHeight - } + }; createNotification(wordWrap("Snapshot saved to " + path), NotificationType.SNAPSHOT, imageProperties); } } @@ -571,8 +591,6 @@ function keyReleaseEvent(key) { // Triggers notification on specific key driven events function keyPressEvent(key) { - var noteString; - if (key.key === 16777249) { ctrlIsPressed = true; } @@ -622,13 +640,13 @@ function menuItemEvent(menuItem) { } LODManager.LODDecreased.connect(function() { - var warningText = "\n" - + "Due to the complexity of the content, the \n" - + "level of detail has been decreased. " - + "You can now see: \n" - + LODManager.getLODFeedbackText(); + var warningText = "\n" + + "Due to the complexity of the content, the \n" + + "level of detail has been decreased. " + + "You can now see: \n" + + LODManager.getLODFeedbackText(); - if (lodTextID == false) { + if (lodTextID === false) { lodTextID = createNotification(warningText, NotificationType.LOD_WARNING); } else { Overlays.editOverlay(lodTextID, { text: warningText }); @@ -644,6 +662,7 @@ Script.scriptEnding.connect(scriptEnding); Menu.menuItemEvent.connect(menuItemEvent); Window.domainConnectionRefused.connect(onDomainConnectionRefused); Window.snapshotTaken.connect(onSnapshotTaken); +Window.notifyEditError = onEditError; setup(); diff --git a/tests/render-perf/src/Camera.hpp b/tests/render-perf/src/Camera.hpp index ebfcfb9e32..6e1d95bfe1 100644 --- a/tests/render-perf/src/Camera.hpp +++ b/tests/render-perf/src/Camera.hpp @@ -14,9 +14,10 @@ protected: public: glm::quat getOrientation() const { - return glm::angleAxis(yaw, Vectors::UP); + return glm::angleAxis(yawPitch.x, Vectors::UP) * glm::angleAxis(yawPitch.y, Vectors::RIGHT); } - float yaw { 0 }; + + vec2 yawPitch { 0 }; glm::vec3 position; float rotationSpeed { 1.0f }; float movementSpeed { 1.0f }; @@ -76,7 +77,12 @@ public: }; void rotate(const float delta) { - yaw += delta; + yawPitch.x += delta; + updateViewMatrix(); + } + + void rotate(const glm::vec2& delta) { + yawPitch += delta; updateViewMatrix(); } @@ -84,7 +90,11 @@ public: glm::vec3 f = rotation * Vectors::UNIT_NEG_Z; f.y = 0; f = glm::normalize(f); - yaw = angleBetween(Vectors::UNIT_NEG_Z, f); + yawPitch.x = angleBetween(Vectors::UNIT_NEG_Z, f); + f = rotation * Vectors::UNIT_NEG_Z; + f.x = 0; + f = glm::normalize(f); + yawPitch.y = angleBetween(Vectors::UNIT_NEG_Z, f); updateViewMatrix(); } diff --git a/tests/render-perf/src/main.cpp b/tests/render-perf/src/main.cpp index 19bec7767d..987fbe33d5 100644 --- a/tests/render-perf/src/main.cpp +++ b/tests/render-perf/src/main.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -32,12 +33,12 @@ #include +#include +#include +#include +#include #include -//#include -//#include -//#include - #include #include #include @@ -99,56 +100,6 @@ public: } }; -#if 0 -class GlfwCamera : public Camera { - Key forKey(int key) { - switch (key) { - case GLFW_KEY_W: return FORWARD; - case GLFW_KEY_S: return BACK; - case GLFW_KEY_A: return LEFT; - case GLFW_KEY_D: return RIGHT; - case GLFW_KEY_E: return UP; - case GLFW_KEY_C: return DOWN; - case GLFW_MOUSE_BUTTON_LEFT: return MLEFT; - case GLFW_MOUSE_BUTTON_RIGHT: return MRIGHT; - case GLFW_MOUSE_BUTTON_MIDDLE: return MMIDDLE; - default: break; - } - return INVALID; - } - - vec2 _lastMouse; -public: - void keyHandler(int key, int scancode, int action, int mods) { - Key k = forKey(key); - if (k == INVALID) { - return; - } - if (action == GLFW_PRESS) { - keys.set(k); - } else if (action == GLFW_RELEASE) { - keys.reset(k); - } - } - - //static void MouseMoveHandler(GLFWwindow* window, double posx, double posy); - //static void MouseScrollHandler(GLFWwindow* window, double xoffset, double yoffset); - void onMouseMove(double posx, double posy) { - vec2 mouse = vec2(posx, posy); - vec2 delta = mouse - _lastMouse; - if (keys.at(Key::MRIGHT)) { - dolly(delta.y * 0.01f); - } else if (keys.at(Key::MLEFT)) { - rotate(delta.x * -0.01f); - } else if (keys.at(Key::MMIDDLE)) { - delta.y *= -1.0f; - translate(delta * -0.01f); - } - _lastMouse = mouse; - } - -}; -#else class QWindowCamera : public Camera { Key forKey(int key) { switch (key) { @@ -188,7 +139,8 @@ public: if (buttons & Qt::RightButton) { dolly(delta.y * 0.01f); } else if (buttons & Qt::LeftButton) { - rotate(delta.x * -0.01f); + //rotate(delta.x * -0.01f); + rotate(delta * -0.01f); } else if (buttons & Qt::MiddleButton) { delta.y *= -1.0f; translate(delta * -0.01f); @@ -197,7 +149,6 @@ public: _lastMouse = mouse; } }; -#endif static QString toHumanSize(size_t size, size_t maxUnit = std::numeric_limits::max()) { static const std::vector SUFFIXES{ { "B", "KB", "MB", "GB", "TB", "PB" } }; @@ -240,7 +191,7 @@ public: std::mutex _mutex; std::shared_ptr _backend; std::vector _frameTimes; - size_t _frameIndex; + size_t _frameIndex { 0 }; std::mutex _frameLock; std::queue _pendingFrames; gpu::FramePointer _activeFrame; @@ -252,7 +203,6 @@ public: _pendingFrames.push(frame); } - void initialize(QWindow* window, gl::Context& initContext) { setObjectName("RenderThread"); _context.setWindow(window); @@ -286,10 +236,6 @@ public: } _context.makeCurrent(); - glewExperimental = true; - glewInit(); - glGetError(); - _frameTimes.resize(FRAME_TIME_BUFFER_SIZE, 0); { auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); @@ -375,7 +321,6 @@ public: } } - bool process() override { std::queue pendingFrames; { @@ -420,6 +365,7 @@ public: }; render::ItemID BackgroundRenderData::_item = 0; +QSharedPointer logger; namespace render { template <> const ItemKey payloadGetKey(const BackgroundRenderData::Pointer& stuff) { @@ -552,36 +498,6 @@ public: _renderThread.initialize(this, _initContext); _initContext.makeCurrent(); -#if 0 - glfwInit(); - glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE); - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5); - glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API); - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - resizeWindow(QSize(800, 600)); - _window = glfwCreateWindow(_size.width(), _size.height(), "Window Title", NULL, NULL); - if (!_window) { - throw std::runtime_error("Could not create window"); - } - - glfwSetWindowUserPointer(_window, this); - glfwSetKeyCallback(_window, KeyboardHandler); - glfwSetMouseButtonCallback(_window, MouseHandler); - glfwSetCursorPosCallback(_window, MouseMoveHandler); - glfwSetWindowCloseCallback(_window, CloseHandler); - glfwSetFramebufferSizeCallback(_window, FramebufferSizeHandler); - glfwSetScrollCallback(_window, MouseScrollHandler); - - - glfwMakeContextCurrent(_window); - GLDebug::setupLogger(this); -#endif - -#ifdef Q_OS_WIN - //wglSwapIntervalEXT(0); -#endif - // FIXME use a wait condition QThread::msleep(1000); _renderThread.submitFrame(gpu::FramePointer()); @@ -622,6 +538,16 @@ public: DependencyManager::destroy(); } + void loadCommands(const QString& filename) { + QFileInfo fileInfo(filename); + if (!fileInfo.exists()) { + return; + } + _commandPath = fileInfo.absolutePath(); + _commands = FileUtils::readLines(filename); + _commandIndex = 0; + } + protected: bool eventFilter(QObject *obj, QEvent *event) override { @@ -666,6 +592,14 @@ protected: toggleCulling(); return; + case Qt::Key_Home: + gpu::Texture::setAllowedGPUMemoryUsage(0); + return; + + case Qt::Key_End: + gpu::Texture::setAllowedGPUMemoryUsage(MB_TO_BYTES(256)); + return; + default: break; @@ -776,10 +710,11 @@ private: }; void updateText() { - QString title = QString("FPS %1 Culling %2 TextureMemory GPU %3 CPU %4") + QString title = QString("FPS %1 Culling %2 TextureMemory GPU %3 CPU %4 Max GPU %5") .arg(_fps).arg(_cullingEnabled) .arg(toHumanSize(gpu::Context::getTextureGPUMemoryUsage(), 2)) - .arg(toHumanSize(gpu::Texture::getTextureCPUMemoryUsage(), 2)); + .arg(toHumanSize(gpu::Texture::getTextureCPUMemoryUsage(), 2)) + .arg(toHumanSize(gpu::Texture::getAllowedGPUMemoryUsage(), 2)); setTitle(title); #if 0 { @@ -803,10 +738,77 @@ private: #endif } + void runCommand(const QString& command) { + qDebug() << "Running command: " << command; + QStringList commandParams = command.split(QRegularExpression(QString("\\s"))); + QString verb = commandParams[0].toLower(); + if (verb == "loop") { + if (commandParams.length() > 1) { + int maxLoops = commandParams[1].toInt(); + if (maxLoops < ++_commandLoops) { + qDebug() << "Exceeded loop count"; + return; + } + } + _commandIndex = 0; + } else if (verb == "wait") { + if (commandParams.length() < 2) { + qDebug() << "No wait time specified"; + return; + } + int seconds = commandParams[1].toInt(); + _nextCommandTime = usecTimestampNow() + seconds * USECS_PER_SECOND; + } else if (verb == "load") { + if (commandParams.length() < 2) { + qDebug() << "No load file specified"; + return; + } + QString file = commandParams[1]; + if (QFileInfo(file).isRelative()) { + file = _commandPath + "/" + file; + } + if (!QFileInfo(file).exists()) { + qDebug() << "Cannot find scene file " + file; + return; + } + + importScene(file); + } else if (verb == "go") { + if (commandParams.length() < 2) { + qDebug() << "No destination specified for go command"; + return; + } + parsePath(commandParams[1]); + } else { + qDebug() << "Unknown command " << command; + } + } + + void runNextCommand(quint64 now) { + if (_commands.empty()) { + return; + } + + if (_commandIndex >= _commands.size()) { + _commands.clear(); + return; + } + + if (now < _nextCommandTime) { + return; + } + + _nextCommandTime = 0; + QString command = _commands[_commandIndex++]; + runCommand(command); + } + void update() { auto now = usecTimestampNow(); static auto last = now; + runNextCommand(now); + float delta = now - last; // Update the camera _camera.update(delta / USECS_PER_SECOND); @@ -959,7 +961,6 @@ private: QString atpUrl = QUrl::fromLocalFile(atpPath).toString(); ResourceManager::setUrlPrefixOverride("atp:/", atpUrl + "/"); } - _settings.setValue(LAST_SCENE_KEY, fileName); _octree->clear(); _octree->getTree()->readFromURL(fileName); } @@ -978,6 +979,7 @@ private: if (fileName.isNull()) { return; } + _settings.setValue(LAST_SCENE_KEY, fileName); importScene(fileName); } @@ -1015,7 +1017,7 @@ private: } void resetPosition() { - _camera.yaw = 0; + _camera.yawPitch = vec3(0); _camera.setPosition(vec3()); } @@ -1077,6 +1079,13 @@ private: model::SunSkyStage _sunSkyStage; model::LightPointer _globalLight { std::make_shared() }; bool _ready { false }; + + QStringList _commands; + QString _commandPath; + int _commandLoops { 0 }; + int _commandIndex { -1 }; + uint64_t _nextCommandTime { 0 }; + //TextOverlay* _textOverlay; static bool _cullingEnabled; @@ -1093,12 +1102,14 @@ private: bool QTestWindow::_cullingEnabled = true; void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { - if (!message.isEmpty()) { + QString logMessage = LogHandler::getInstance().printMessage((LogMsgType)type, context, message); + + if (!logMessage.isEmpty()) { #ifdef Q_OS_WIN - OutputDebugStringA(message.toLocal8Bit().constData()); + OutputDebugStringA(logMessage.toLocal8Bit().constData()); OutputDebugStringA("\n"); #endif - std::cout << message.toLocal8Bit().constData() << std::endl; + logger->addMessage(qPrintable(logMessage + "\n")); } } @@ -1106,16 +1117,19 @@ const char * LOG_FILTER_RULES = R"V0G0N( hifi.gpu=true )V0G0N"; + int main(int argc, char** argv) { QApplication app(argc, argv); QCoreApplication::setApplicationName("RenderPerf"); QCoreApplication::setOrganizationName("High Fidelity"); QCoreApplication::setOrganizationDomain("highfidelity.com"); + logger.reset(new FileLogger()); qInstallMessageHandler(messageHandler); QLoggingCategory::setFilterRules(LOG_FILTER_RULES); QTestWindow::setup(); QTestWindow window; + //window.loadCommands("C:/Users/bdavis/Git/dreaming/exports/commands.txt"); app.exec(); return 0; } diff --git a/tests/render-texture-load/src/main.cpp b/tests/render-texture-load/src/main.cpp index a1c5f74777..fb0b16d16c 100644 --- a/tests/render-texture-load/src/main.cpp +++ b/tests/render-texture-load/src/main.cpp @@ -297,8 +297,6 @@ public: }; QTestWindow() { - - _currentTexture = _textures.end(); { QStringList stringList; QFile textFile(DATA_DIR.path() + "/loads.txt"); @@ -318,12 +316,13 @@ public: QString timeStr = s.left(index); auto time = timeStr.toUInt(); QString path = DATA_DIR.path() + "/" + s.right(s.length() - index).trimmed(); - qDebug() << "Path " << path; if (!QFileInfo(path).exists()) { continue; } - _textureLoads.push({ time, path, s }); + qDebug() << "Path " << path; + _texturesFiles.push_back({ time, path, s }); } + _textures.resize(_texturesFiles.size()); } installEventFilter(this); @@ -383,6 +382,33 @@ protected: } void keyPressEvent(QKeyEvent* event) override { + switch (event->key()) { + case Qt::Key_Left: + prevTexture(); + break; + case Qt::Key_Right: + nextTexture(); + break; + case Qt::Key_Return: + reportMemory(); + break; + case Qt::Key_PageDown: + derezTexture(); + break; + case Qt::Key_Home: + unloadAll(); + break; + case Qt::Key_End: + loadAll(); + break; + case Qt::Key_Down: + loadTexture(); + break; + case Qt::Key_Up: + unloadTexture(); + break; + } + QWindow::keyPressEvent(event); } void keyReleaseEvent(QKeyEvent* event) override { @@ -395,10 +421,80 @@ protected: resizeWindow(ev->size()); } + void nextTexture() { + if (_textures.empty()) { + return; + } + auto textureCount = _textures.size(); + _currentTextureIndex = (_currentTextureIndex + 1) % textureCount; + loadTexture(); + } + + void prevTexture() { + if (_textures.empty()) { + return; + } + auto textureCount = _textures.size(); + _currentTextureIndex = (_currentTextureIndex + textureCount - 1) % textureCount; + loadTexture(); + } + + void reportMemory() { + static GLint lastMemory = 0; + GLint availableMem; + glGetIntegerv(GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX, &availableMem); + qDebug() << "Memory available " << availableMem; + if (lastMemory != 0) { + qDebug() << "Delta " << availableMem - lastMemory; + } + lastMemory = availableMem; + } + + void derezTexture() { + if (!_textures[_currentTextureIndex]) { + return; + } + auto texture = _textures[_currentTextureIndex]; + texture->setMinMip(texture->minMip() + 1); + } + + void loadTexture() { + if (_textures[_currentTextureIndex]) { + return; + } + auto file = _texturesFiles[_currentTextureIndex].file; + qDebug() << "Loading texture " << file; + _textures[_currentTextureIndex] = DependencyManager::get()->getImageTexture(file); + } + + void unloadTexture() { + if (_textures.empty()) { + return; + } + _textures[_currentTextureIndex].reset(); + } + + void loadAll() { + for (size_t i = 0; i < _texturesFiles.size(); ++i) { + if (_textures[i]) { + continue; + } + auto file = _texturesFiles[i].file; + qDebug() << "Loading texture " << file; + _textures[i] = DependencyManager::get()->getImageTexture(file); + } + } + + void unloadAll() { + for (auto& texture : _textures) { + texture.reset(); + } + } + private: - std::queue _textureLoads; - std::list _textures; - std::list::iterator _currentTexture; + size_t _currentTextureIndex { 0 }; + std::vector _texturesFiles; + std::vector _textures; uint16_t _fps; gpu::PipelinePointer _simplePipeline; @@ -438,7 +534,9 @@ private: auto now = usecTimestampNow(); static auto last = now; auto delta = (now - last) / USECS_PER_MSEC; - if (!_textureLoads.empty()) { + Q_UNUSED(delta); +#if 0 + if (!_textures.empty()) { const auto& front = _textureLoads.front(); if (delta >= front.time) { QFileInfo fileInfo(front.file); @@ -456,6 +554,7 @@ private: } } } +#endif } void render() { @@ -474,14 +573,8 @@ private: auto vpsize = framebuffer->getSize(); auto vppos = ivec2(0); batch.setViewportTransform(ivec4(vppos, vpsize)); - if (_currentTexture != _textures.end()) { - ++_currentTexture; - } - if (_currentTexture == _textures.end()) { - _currentTexture = _textures.begin(); - } - if (_currentTexture != _textures.end()) { - batch.setResourceTexture(0, *_currentTexture); + if (!_textures.empty()) { + batch.setResourceTexture(0, _textures[_currentTextureIndex]); } batch.setPipeline(_simplePipeline); batch.draw(gpu::TRIANGLE_STRIP, 4); @@ -564,7 +657,6 @@ int main(int argc, char** argv) { }).waitForDownload(); } - QTestWindow::setup(); QTestWindow window; app.exec(); diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index bf645f25c2..a077efc335 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -7,3 +7,6 @@ set_target_properties(udt-test PROPERTIES FOLDER "Tools") add_subdirectory(vhacd-util) set_target_properties(vhacd-util PROPERTIES FOLDER "Tools") + +add_subdirectory(ice-client) +set_target_properties(ice-client PROPERTIES FOLDER "Tools") diff --git a/tools/ice-client/CMakeLists.txt b/tools/ice-client/CMakeLists.txt new file mode 100644 index 0000000000..a80145974c --- /dev/null +++ b/tools/ice-client/CMakeLists.txt @@ -0,0 +1,3 @@ +set(TARGET_NAME ice-client) +setup_hifi_project(Core Widgets) +link_hifi_libraries(shared networking) diff --git a/tools/ice-client/src/ICEClientApp.cpp b/tools/ice-client/src/ICEClientApp.cpp new file mode 100644 index 0000000000..992014ad7d --- /dev/null +++ b/tools/ice-client/src/ICEClientApp.cpp @@ -0,0 +1,387 @@ +// +// ICEClientApp.cpp +// tools/ice-client/src +// +// Created by Seth Alves on 3/5/15. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include +#include +#include +#include +#include + +#include "ICEClientApp.h" + +ICEClientApp::ICEClientApp(int argc, char* argv[]) : + QCoreApplication(argc, argv) +{ + // parse command-line + QCommandLineParser parser; + parser.setApplicationDescription("High Fidelity ICE client"); + parser.addHelpOption(); + + const QCommandLineOption helpOption = parser.addHelpOption(); + + const QCommandLineOption verboseOutput("v", "verbose output"); + parser.addOption(verboseOutput); + + const QCommandLineOption iceServerAddressOption("i", "ice-server address", "IP:PORT or HOSTNAME:PORT"); + parser.addOption(iceServerAddressOption); + + const QCommandLineOption howManyTimesOption("n", "how many times to cycle", "1"); + parser.addOption(howManyTimesOption); + + const QCommandLineOption domainIDOption("d", "domain-server uuid", "00000000-0000-0000-0000-000000000000"); + parser.addOption(domainIDOption); + + const QCommandLineOption cacheSTUNOption("s", "cache stun-server response"); + parser.addOption(cacheSTUNOption); + + if (!parser.parse(QCoreApplication::arguments())) { + qCritical() << parser.errorText() << endl; + parser.showHelp(); + Q_UNREACHABLE(); + } + + if (parser.isSet(helpOption)) { + parser.showHelp(); + Q_UNREACHABLE(); + } + + _verbose = parser.isSet(verboseOutput); + if (!_verbose) { + const_cast(&networking())->setEnabled(QtDebugMsg, false); + const_cast(&networking())->setEnabled(QtInfoMsg, false); + const_cast(&networking())->setEnabled(QtWarningMsg, false); + } + + _stunSockAddr = HifiSockAddr(STUN_SERVER_HOSTNAME, STUN_SERVER_PORT, true); + + _cacheSTUNResult = parser.isSet(cacheSTUNOption); + + if (parser.isSet(howManyTimesOption)) { + _actionMax = parser.value(howManyTimesOption).toInt(); + } else { + _actionMax = 1; + } + + if (parser.isSet(domainIDOption)) { + _domainID = QUuid(parser.value(domainIDOption)); + if (_verbose) { + qDebug() << "domain-server ID is" << _domainID; + } + } + + _iceServerAddr = HifiSockAddr("127.0.0.1", ICE_SERVER_DEFAULT_PORT); + if (parser.isSet(iceServerAddressOption)) { + // parse the IP and port combination for this target + QString hostnamePortString = parser.value(iceServerAddressOption); + + QHostAddress address { hostnamePortString.left(hostnamePortString.indexOf(':')) }; + quint16 port { (quint16) hostnamePortString.mid(hostnamePortString.indexOf(':') + 1).toUInt() }; + if (port == 0) { + port = ICE_SERVER_DEFAULT_PORT; + } + + if (address.isNull()) { + qCritical() << "Could not parse an IP address and port combination from" << hostnamePortString << "-" << + "The parsed IP was" << address.toString() << "and the parsed port was" << port; + + QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection); + } else { + _iceServerAddr = HifiSockAddr(address, port); + } + } + + if (_verbose) { + qDebug() << "ICE-server address is" << _iceServerAddr; + } + + setState(lookUpStunServer); + + QTimer* doTimer = new QTimer(this); + connect(doTimer, &QTimer::timeout, this, &ICEClientApp::doSomething); + doTimer->start(200); +} + +ICEClientApp::~ICEClientApp() { + delete _socket; +} + +void ICEClientApp::setState(int newState) { + _state = newState; +} + +void ICEClientApp::closeSocket() { + _domainServerPeerSet = false; + delete _socket; + _socket = nullptr; +} + +void ICEClientApp::openSocket() { + if (_socket) { + return; + } + + _socket = new udt::Socket(); + unsigned int localPort = 0; + _socket->bind(QHostAddress::AnyIPv4, localPort); + _socket->setPacketHandler([this](std::unique_ptr packet) { processPacket(std::move(packet)); }); + _socket->addUnfilteredHandler(_stunSockAddr, + [this](std::unique_ptr packet) { + processSTUNResponse(std::move(packet)); + }); + + if (_verbose) { + qDebug() << "local port is" << _socket->localPort(); + } + _localSockAddr = HifiSockAddr("127.0.0.1", _socket->localPort()); + _publicSockAddr = HifiSockAddr("127.0.0.1", _socket->localPort()); + _domainPingCount = 0; +} + +void ICEClientApp::doSomething() { + if (_actionMax > 0 && _actionCount >= _actionMax) { + // time to stop. + QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection); + + } else if (_state == lookUpStunServer) { + // lookup STUN server address + if (!_stunSockAddr.getAddress().isNull()) { + if (_verbose) { + qDebug() << "stun server is" << _stunSockAddr; + } + setState(sendStunRequestPacket); + } else { + if (_verbose) { + qDebug() << "_stunSockAddr is" << _stunSockAddr.getAddress(); + } + QCoreApplication::exit(stunFailureExitStatus); + } + + } else if (_state == sendStunRequestPacket) { + // send STUN request packet + closeSocket(); + openSocket(); + + if (!_cacheSTUNResult || !_stunResultSet) { + const int NUM_BYTES_STUN_HEADER = 20; + char stunRequestPacket[NUM_BYTES_STUN_HEADER]; + LimitedNodeList::makeSTUNRequestPacket(stunRequestPacket); + if (_verbose) { + qDebug() << "sending STUN request"; + } + _socket->writeDatagram(stunRequestPacket, sizeof(stunRequestPacket), _stunSockAddr); + _stunResponseTimerCanceled = false; + _stunResponseTimer.singleShot(stunResponseTimeoutMilliSeconds, this, [&] { + if (_stunResponseTimerCanceled) { + return; + } + if (_verbose) { + qDebug() << "timeout waiting for stun-server response"; + } + QCoreApplication::exit(stunFailureExitStatus); + }); + + setState(waitForStunResponse); + } else { + if (_verbose) { + qDebug() << "using cached STUN response"; + } + _publicSockAddr.setPort(_socket->localPort()); + setState(talkToIceServer); + } + + } else if (_state == talkToIceServer) { + QUuid peerID; + if (_domainID == QUuid()) { + // pick a random domain-id which will fail + peerID = QUuid::createUuid(); + setState(pause0); + } else { + // use the domain UUID given on the command-line + peerID = _domainID; + setState(waitForIceReply); + } + _sessionUUID = QUuid::createUuid(); + if (_verbose) { + qDebug() << "I am" << _sessionUUID; + } + + sendPacketToIceServer(PacketType::ICEServerQuery, _iceServerAddr, _sessionUUID, peerID); + _iceResponseTimerCanceled = false; + _iceResponseTimer.singleShot(iceResponseTimeoutMilliSeconds, this, [=] { + if (_iceResponseTimerCanceled) { + return; + } + if (_verbose) { + qDebug() << "timeout waiting for ice-server response"; + } + QCoreApplication::exit(iceFailureExitStatus); + }); + } else if (_state == pause0) { + setState(pause1); + } else if (_state == pause1) { + if (_verbose) { + qDebug() << ""; + } + closeSocket(); + setState(sendStunRequestPacket); + _actionCount++; + } +} + +void ICEClientApp::sendPacketToIceServer(PacketType packetType, const HifiSockAddr& iceServerSockAddr, + const QUuid& clientID, const QUuid& peerID) { + std::unique_ptr icePacket = NLPacket::create(packetType); + + QDataStream iceDataStream(icePacket.get()); + iceDataStream << clientID << _publicSockAddr << _localSockAddr; + + if (packetType == PacketType::ICEServerQuery) { + assert(!peerID.isNull()); + + iceDataStream << peerID; + + if (_verbose) { + qDebug() << "Sending packet to ICE server to request connection info for peer with ID" + << uuidStringWithoutCurlyBraces(peerID); + } + } + + // fillPacketHeader(packet, connectionSecret); + _socket->writePacket(*icePacket, _iceServerAddr); +} + +void ICEClientApp::checkDomainPingCount() { + _domainPingCount++; + if (_domainPingCount > 5) { + if (_verbose) { + qDebug() << "too many unanswered pings to domain-server."; + } + QCoreApplication::exit(domainPingExitStatus); + } +} + +void ICEClientApp::icePingDomainServer() { + if (!_domainServerPeerSet) { + return; + } + + if (_verbose) { + qDebug() << "ice-pinging domain-server: " << _domainServerPeer; + } + + auto localPingPacket = LimitedNodeList::constructICEPingPacket(PingType::Local, _sessionUUID); + _socket->writePacket(*localPingPacket, _domainServerPeer.getLocalSocket()); + + auto publicPingPacket = LimitedNodeList::constructICEPingPacket(PingType::Public, _sessionUUID); + _socket->writePacket(*publicPingPacket, _domainServerPeer.getPublicSocket()); + checkDomainPingCount(); +} + +void ICEClientApp::processSTUNResponse(std::unique_ptr packet) { + if (_verbose) { + qDebug() << "got stun response"; + } + if (_state != waitForStunResponse) { + if (_verbose) { + qDebug() << "got unexpected stun response"; + } + QCoreApplication::exit(stunFailureExitStatus); + } + + _stunResponseTimer.stop(); + _stunResponseTimerCanceled = true; + + uint16_t newPublicPort; + QHostAddress newPublicAddress; + if (LimitedNodeList::parseSTUNResponse(packet.get(), newPublicAddress, newPublicPort)) { + _publicSockAddr = HifiSockAddr(newPublicAddress, newPublicPort); + if (_verbose) { + qDebug() << "My public address is" << _publicSockAddr; + } + _stunResultSet = true; + setState(talkToIceServer); + } else { + QCoreApplication::exit(stunFailureExitStatus); + } +} + + +void ICEClientApp::processPacket(std::unique_ptr packet) { + std::unique_ptr nlPacket = NLPacket::fromBase(std::move(packet)); + + if (nlPacket->getPayloadSize() < NLPacket::localHeaderSize(PacketType::ICEServerHeartbeat)) { + if (_verbose) { + qDebug() << "got a short packet."; + } + return; + } + + QSharedPointer message = QSharedPointer::create(*nlPacket); + const HifiSockAddr& senderAddr = message->getSenderSockAddr(); + + if (nlPacket->getType() == PacketType::ICEServerPeerInformation) { + // cancel the timeout timer + _iceResponseTimer.stop(); + _iceResponseTimerCanceled = true; + + QDataStream iceResponseStream(message->getMessage()); + if (!_domainServerPeerSet) { + iceResponseStream >> _domainServerPeer; + if (_verbose) { + qDebug() << "got ICEServerPeerInformation from" << _domainServerPeer; + } + _domainServerPeerSet = true; + + icePingDomainServer(); + _pingDomainTimer = new QTimer(this); + connect(_pingDomainTimer, &QTimer::timeout, this, &ICEClientApp::icePingDomainServer); + _pingDomainTimer->start(500); + } else { + NetworkPeer domainServerPeer; + iceResponseStream >> domainServerPeer; + if (_verbose) { + qDebug() << "got repeat ICEServerPeerInformation from" << domainServerPeer; + } + } + + } else if (nlPacket->getType() == PacketType::ICEPing) { + if (_verbose) { + qDebug() << "got packet: " << nlPacket->getType(); + } + auto replyPacket = LimitedNodeList::constructICEPingReplyPacket(*message, _sessionUUID); + _socket->writePacket(*replyPacket, senderAddr); + checkDomainPingCount(); + + } else if (nlPacket->getType() == PacketType::ICEPingReply) { + if (_verbose) { + qDebug() << "got packet: " << nlPacket->getType(); + } + if (_domainServerPeerSet && _state == waitForIceReply && + (senderAddr == _domainServerPeer.getLocalSocket() || + senderAddr == _domainServerPeer.getPublicSocket())) { + + delete _pingDomainTimer; + _pingDomainTimer = nullptr; + + setState(pause0); + } else { + if (_verbose) { + qDebug() << "got unexpected ICEPingReply" << senderAddr; + } + } + + } else { + if (_verbose) { + qDebug() << "got unexpected packet: " << nlPacket->getType(); + } + } +} diff --git a/tools/ice-client/src/ICEClientApp.h b/tools/ice-client/src/ICEClientApp.h new file mode 100644 index 0000000000..3635bc07f4 --- /dev/null +++ b/tools/ice-client/src/ICEClientApp.h @@ -0,0 +1,96 @@ +// +// ICEClientApp.h +// tools/ice-client/src +// +// Created by Seth Alves on 2016-9-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 +// + + +#ifndef hifi_ICEClientApp_h +#define hifi_ICEClientApp_h + +#include +#include +#include +#include +#include + + +class ICEClientApp : public QCoreApplication { + Q_OBJECT +public: + ICEClientApp(int argc, char* argv[]); + ~ICEClientApp(); + + const int stunFailureExitStatus { 1 }; + const int iceFailureExitStatus { 2 }; + const int domainPingExitStatus { 3 }; + + const int stunResponseTimeoutMilliSeconds { 2000 }; + const int iceResponseTimeoutMilliSeconds { 2000 }; + +private: + enum State { + lookUpStunServer, // 0 + sendStunRequestPacket, // 1 + waitForStunResponse, // 2 + talkToIceServer, // 3 + waitForIceReply, // 4 + pause0, // 5 + pause1 // 6 + }; + + void closeSocket(); + void openSocket(); + + void setState(int newState); + + void doSomething(); + void sendPacketToIceServer(PacketType packetType, const HifiSockAddr& iceServerSockAddr, + const QUuid& clientID, const QUuid& peerID); + void icePingDomainServer(); + void processSTUNResponse(std::unique_ptr packet); + void processPacket(std::unique_ptr packet); + void checkDomainPingCount(); + + bool _verbose; + bool _cacheSTUNResult; // should we only talk to stun server once? + bool _stunResultSet { false }; // have we already talked to stun server? + + HifiSockAddr _stunSockAddr; + + unsigned int _actionCount { 0 }; + unsigned int _actionMax { 0 }; + + QUuid _sessionUUID; + QUuid _domainID; + + QTimer* _pingDomainTimer { nullptr }; + + HifiSockAddr _iceServerAddr; + + HifiSockAddr _localSockAddr; + HifiSockAddr _publicSockAddr; + udt::Socket* _socket { nullptr }; + + bool _domainServerPeerSet { false }; + NetworkPeer _domainServerPeer; + + int _state { 0 }; + + QTimer _stunResponseTimer; + bool _stunResponseTimerCanceled { false }; + QTimer _iceResponseTimer; + bool _iceResponseTimerCanceled { false }; + int _domainPingCount { 0 }; +}; + + + + + +#endif //hifi_ICEClientApp_h diff --git a/tools/ice-client/src/main.cpp b/tools/ice-client/src/main.cpp new file mode 100644 index 0000000000..c70a7eb7d7 --- /dev/null +++ b/tools/ice-client/src/main.cpp @@ -0,0 +1,23 @@ +// +// main.cpp +// tools/ice-client/src +// +// Created by Seth Alves on 2016-9-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 + +#include +#include +#include +#include + +#include "ICEClientApp.h" + +using namespace std; + +int main(int argc, char * argv[]) { + ICEClientApp app(argc, argv); + return app.exec(); +}