From 9f2b6dc40b82f2797b6cfc20d8ac08c4f99403f9 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 16 Nov 2016 12:53:57 -0800 Subject: [PATCH 1/7] location cleanup --- interface/resources/qml/AddressBarDialog.qml | 4 ++-- libraries/networking/src/AddressManager.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/interface/resources/qml/AddressBarDialog.qml b/interface/resources/qml/AddressBarDialog.qml index 941099b7cc..38250b8be7 100644 --- a/interface/resources/qml/AddressBarDialog.qml +++ b/interface/resources/qml/AddressBarDialog.qml @@ -370,7 +370,7 @@ Window { if (place.action === 'snapshot') { return true; } - return (place.place_name !== AddressManager.hostname); // Not our entry, but do show other entry points to current domain. + return (place.place_name !== AddressManager.placename); // Not our entry, but do show other entry points to current domain. // could also require right protocolVersion } function getUserStoryPage(pageNumber, cb) { // cb(error) after all pages of domain data have been added to model @@ -436,7 +436,7 @@ Window { notice.text = AddressManager.isConnected ? "Your location:" : "Not Connected"; notice.color = AddressManager.isConnected ? hifiStyleConstants.colors.baseGrayHighlight : hifiStyleConstants.colors.redHighlight; // Display hostname, which includes ip address, localhost, and other non-placenames. - location.text = (AddressManager.hostname || '') + (AddressManager.pathname ? AddressManager.pathname.match(/\/[^\/]+/)[0] : ''); + location.text = (AddressManager.placename || AddressManager.hostname || '') + (AddressManager.pathname ? AddressManager.pathname.match(/\/[^\/]+/)[0] : ''); } } diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index f979002f7a..0c46f9688b 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -35,7 +35,7 @@ class AddressManager : public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY Q_PROPERTY(bool isConnected READ isConnected) - Q_PROPERTY(QUrl href READ currentAddress) + Q_PROPERTY(QUrl href READ currentShareableAddress) Q_PROPERTY(QString protocol READ getProtocol) Q_PROPERTY(QString hostname READ getHost) Q_PROPERTY(QString pathname READ currentPath) @@ -67,7 +67,7 @@ public: QString currentFacingPath() const; const QUuid& getRootPlaceID() const { return _rootPlaceID; } - const QString& getPlaceName() const { return _placeName; } + const QString& getPlaceName() const { return _shareablePlaceName.isEmpty() ? _placeName : _shareablePlaceName; } const QString& getHost() const { return _host; } From f1806eb64917687924aceed7165f42b5500a32ac Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 18 Nov 2016 17:05:14 -0800 Subject: [PATCH 2/7] server protocols --- interface/resources/qml/AddressBarDialog.qml | 3 +++ libraries/networking/src/AddressManager.cpp | 4 ++++ libraries/networking/src/AddressManager.h | 2 ++ scripts/system/snapshot.js | 22 +++++++++++++++++++- 4 files changed, 30 insertions(+), 1 deletion(-) diff --git a/interface/resources/qml/AddressBarDialog.qml b/interface/resources/qml/AddressBarDialog.qml index 38250b8be7..172245876a 100644 --- a/interface/resources/qml/AddressBarDialog.qml +++ b/interface/resources/qml/AddressBarDialog.qml @@ -375,7 +375,10 @@ Window { } function getUserStoryPage(pageNumber, cb) { // cb(error) after all pages of domain data have been added to model var options = [ + 'now=' + new Date().toISOString(), 'include_actions=snapshot,concurrency', + 'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'), + 'require_online=true', 'protocol=' + encodeURIComponent(AddressManager.protocolVersion()), 'page=' + pageNumber ]; diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 4089fd4966..05210012ba 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -750,6 +750,10 @@ void AddressManager::copyPath() { QApplication::clipboard()->setText(currentPath()); } +QString AddressManager::getDomainId() const { + return DependencyManager::get()->getDomainHandler().getUUID().toString(); +} + void AddressManager::handleShareableNameAPIResponse(QNetworkReply& requestReply) { // make sure that this response is for the domain we're currently connected to auto domainID = DependencyManager::get()->getDomainHandler().getUUID(); diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index 0c46f9688b..366fc5dfab 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -40,6 +40,7 @@ class AddressManager : public QObject, public Dependency { Q_PROPERTY(QString hostname READ getHost) Q_PROPERTY(QString pathname READ currentPath) Q_PROPERTY(QString placename READ getPlaceName) + Q_PROPERTY(QString domainId READ getDomainId) public: Q_INVOKABLE QString protocolVersion(); using PositionGetter = std::function; @@ -68,6 +69,7 @@ public: const QUuid& getRootPlaceID() const { return _rootPlaceID; } const QString& getPlaceName() const { return _shareablePlaceName.isEmpty() ? _placeName : _shareablePlaceName; } + QString getDomainId() const; const QString& getHost() const { return _host; } diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 5eebadd02f..daee40d576 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -124,6 +124,26 @@ function onClicked() { }, SNAPSHOT_DELAY); } +function isDomainOpen(id) { + var request = new XMLHttpRequest(); + var options = [ + 'now=' + new Date().toISOString(), + 'include_actions=concurrency', + 'domain_id=' + id.slice(1, -1), + 'restriction=open,hifi' // If we're sharing, we're logged in + // If we're here, protocol matches, and it is online + ]; + var url = "https://metaverse.highfidelity.com/api/v1/user_stories?" + options.join('&'); + request.open("GET", url, false); + request.send(); + if (request.status != 200) { + return false; + } + var response = JSON.parse(request.response); // Not parsed for us. + return (response.status === 'success') && + response.total_entries; +} + function resetButtons(path, notify) { // show overlays if they were on if (resetOverlays) { @@ -143,7 +163,7 @@ function resetButtons(path, notify) { confirmShare([ { localPath: path }, { - canShare: !!location.placename, + canShare: !!isDomainOpen(location.domainId), openFeedAfterShare: shouldOpenFeedAfterShare() } ]); From 8e1f03db430ab5c97affffc45d04210d46a899ef Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 21 Nov 2016 11:34:49 -0800 Subject: [PATCH 3/7] Add AC Audio searcher for tutorial --- tutorial/ACAudioSearchAndInject_tutorial.js | 283 ++++++++++++++++++++ 1 file changed, 283 insertions(+) create mode 100644 tutorial/ACAudioSearchAndInject_tutorial.js diff --git a/tutorial/ACAudioSearchAndInject_tutorial.js b/tutorial/ACAudioSearchAndInject_tutorial.js new file mode 100644 index 0000000000..70e936bb1c --- /dev/null +++ b/tutorial/ACAudioSearchAndInject_tutorial.js @@ -0,0 +1,283 @@ +"use strict"; +/*jslint nomen: true, plusplus: true, vars: true*/ +/*global AvatarList, Entities, EntityViewer, Script, SoundCache, Audio, print, randFloat*/ +// +// ACAudioSearchAndInject.js +// audio +// +// Created by Eric Levin and Howard Stearns 2/1/2016 +// Copyright 2016 High Fidelity, Inc. +// +// Keeps track of all sounds within QUERY_RADIUS of an avatar, where a "sound" is specified in entity userData. +// Inject as many as practical into the audio mixer. +// See acAudioSearchAndCompatibilityEntitySpawner.js. +// +// This implementation takes some precautions to scale well: +// - It doesn't hastle the entity server because it issues at most one octree query every UPDATE_TIME period, regardless of the number of avatars. +// - It does not load itself because it only gathers entities once every UPDATE_TIME period, and only +// checks entity properties for those small number of entities that are currently playing (plus a RECHECK_TIME period examination of all entities). +// This implementation tries to use all the available injectors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +var MSEC_PER_SEC = 1000; +var SOUND_DATA_KEY = "io.highfidelity.soundKey"; // Sound data is specified in userData under this key. +var old_sound_data_key = "soundKey"; // For backwards compatibility. +var QUERY_RADIUS = 50; // meters +var UPDATE_TIME = 100; // ms. We'll update just one thing on this period. +var EXPIRATION_TIME = 5 * MSEC_PER_SEC; // ms. Remove sounds that have been out of range for this time. +var RECHECK_TIME = 10 * MSEC_PER_SEC; // ms. Check for new userData properties this often when not currently playing. +// (By not checking most of the time when not playing, we can efficiently go through all entities without getEntityProperties.) +var UPDATES_PER_STATS_LOG = RECHECK_TIME / UPDATE_TIME; // (It's nice to smooth out the results by straddling a recheck.) + +var DEFAULT_SOUND_DATA = { + volume: 0.5, // userData cannot specify zero volume with our current method of defaulting. + loop: false, // Default must be false with our current method of defaulting, else there's no way to get a false value. + playbackGap: MSEC_PER_SEC, // in ms + playbackGapRange: 0 // in ms +}; + +//var isACScript = this.EntityViewer !== undefined; +var isACScript = true; + +Script.include("http://hifi-content.s3.amazonaws.com/ryan/development/utils_ryan.js"); +if (isACScript) { + Agent.isAvatar = true; // This puts a robot at 0,0,0, but is currently necessary in order to use AvatarList. + Avatar.skeletonModelURL = "http://hifi-content.s3.amazonaws.com/ozan/dev/avatars/invisible_avatar/invisible_avatar.fst"; +} +function ignore() {} +function debug() { // Display the arguments not just [Object object]. + //print.apply(null, [].map.call(arguments, JSON.stringify)); +} + +if (isACScript) { + EntityViewer.setCenterRadius(QUERY_RADIUS); +} + +// ENTITY DATA CACHE +// +var entityCache = {}; // A dictionary of unexpired EntityData objects. +var entityInvalidUserDataCache = {}; // A cache containing the entity IDs that have + // previously been identified as containing non-JSON userData. + // We use a dictionary here so id lookups are constant time. +var examinationCount = 0; +function EntityDatum(entityIdentifier) { // Just the data of an entity that we need to know about. + // This data is only use for our sound injection. There is no need to store such info in the replicated entity on everyone's computer. + var that = this; + that.lastUserDataUpdate = 0; // new entity is in need of rechecking user data + // State Transitions: + // no data => no data | sound data | expired + // expired => stop => remove + // sound data => downloading + // downloading => downloading | waiting + // waiting => playing | waiting (if too many already playing) + // playing => update position etc | no data + that.stop = function stop() { + if (!that.sound) { + return; + } + print("stopping sound", entityIdentifier, that.url); + delete that.sound; + delete that.url; + if (!that.injector) { + return; + } + that.injector.stop(); + delete that.injector; + }; + this.update = function stateTransitions(expirationCutoff, userDataCutoff, now) { + if (that.timestamp < expirationCutoff) { // EXPIRED => STOP => REMOVE + that.stop(); // Alternatively, we could fade out and then stop... + delete entityCache[entityIdentifier]; + return; + } + var properties, soundData; // Latest data, pulled from local octree. + // getEntityProperties locks the tree, which competes with the asynchronous processing of queryOctree results. + // Most entity updates are fast and only a very few do getEntityProperties. + function ensureSoundData() { // We only getEntityProperities when we need to. + if (properties) { + return; + } + properties = Entities.getEntityProperties(entityIdentifier, ['userData', 'position']); + examinationCount++; // Collect statistics on how many getEntityProperties we do. + debug("updating", that, properties); + try { + var userData = properties.userData && JSON.parse(properties.userData); + soundData = userData && (userData[SOUND_DATA_KEY] || userData[old_sound_data_key]); // Don't store soundData yet. Let state changes compare. + that.lastUserDataUpdate = now; // But do update these ... + that.url = soundData && soundData.url; + that.playAfter = that.url && now; + } catch (err) { + if (!(entityIdentifier in entityInvalidUserDataCache)) { + print(err, properties.userData); + entityInvalidUserDataCache[entityIdentifier] = true; + } + } + } + // Stumbling on big new pile of entities will do a lot of getEntityProperties. Once. + if (that.lastUserDataUpdate < userDataCutoff) { // NO DATA => SOUND DATA + ensureSoundData(); + } + if (!that.url) { // NO DATA => NO DATA + return that.stop(); + } + if (!that.sound) { // SOUND DATA => DOWNLOADING + that.sound = SoundCache.getSound(soundData.url); // SoundCache can manage duplicates better than we can. + } + if (!that.sound.downloaded) { // DOWNLOADING => DOWNLOADING + return; + } + if (that.playAfter > now) { // DOWNLOADING | WAITING => WAITING + return; + } + ensureSoundData(); // We'll try to play/setOptions and will need position, so we might as well get soundData, too. + if (soundData.url !== that.url) { // WAITING => NO DATA (update next time around) + return that.stop(); + } + var options = { + position: properties.position, + loop: soundData.loop || DEFAULT_SOUND_DATA.loop, + volume: soundData.volume || DEFAULT_SOUND_DATA.volume + }; + function repeat() { + return !options.loop && (soundData.playbackGap >= 0); + } + function randomizedNextPlay() { // time of next play or recheck, randomized to distribute the work + var range = soundData.playbackGapRange || DEFAULT_SOUND_DATA.playbackGapRange, + base = repeat() ? ((that.sound.duration * MSEC_PER_SEC) + (soundData.playbackGap || DEFAULT_SOUND_DATA.playbackGap)) : RECHECK_TIME; + return now + base + randFloat(-Math.min(base, range), range); + } + if (that.injector && soundData.playing === false) { + that.injector.stop(); + that.injector = null; + } + if (!that.injector) { + if (soundData.playing === false) { // WAITING => PLAYING | WAITING + return; + } + debug("starting", that, options); + that.injector = Audio.playSound(that.sound, options); // Might be null if at at injector limit. Will try again later. + if (that.injector) { + print("started", entityIdentifier, that.url); + } else { // Don't hammer ensureSoundData or injector manager. + that.playAfter = randomizedNextPlay(); + } + return; + } + that.injector.setOptions(options); // PLAYING => UPDATE POSITION ETC + if (!that.injector.playing) { // Subtle: a looping sound will not check playbackGap. + if (repeat()) { // WAITING => PLAYING + // Setup next play just once, now. Changes won't be looked at while we wait. + that.playAfter = randomizedNextPlay(); + // Subtle: if the restart fails b/c we're at injector limit, we won't try again until next playAfter. + that.injector.restart(); + } else { // PLAYING => NO DATA + that.playAfter = Infinity; // was one-shot and we're finished + } + } + }; +} +function internEntityDatum(entityIdentifier, timestamp, avatarPosition, avatar) { + ignore(avatarPosition, avatar); // We could use avatars and/or avatarPositions to prioritize which ones to play. + var entitySound = entityCache[entityIdentifier]; + if (!entitySound) { + entitySound = entityCache[entityIdentifier] = new EntityDatum(entityIdentifier); + } + entitySound.timestamp = timestamp; // Might be updated for multiple avatars. That's fine. +} +var nUpdates = UPDATES_PER_STATS_LOG, lastStats = Date.now(); +function updateAllEntityData() { // A fast update of all entities we know about. A few make sounds. + var now = Date.now(), + expirationCutoff = now - EXPIRATION_TIME, + userDataRecheckCutoff = now - RECHECK_TIME; + Object.keys(entityCache).forEach(function (entityIdentifier) { + entityCache[entityIdentifier].update(expirationCutoff, userDataRecheckCutoff, now); + }); + if (nUpdates-- <= 0) { // Report statistics. + // For example, with: + // injector-limit = 40 (in C++ code) + // N_SOUNDS = 1000 (from userData in, e.g., acAudioSearchCompatibleEntitySpawner.js) + // replay-period = 3 + 20 = 23 (seconds, ditto) + // stats-period = UPDATES_PER_STATS_LOG * UPDATE_TIME / MSEC_PER_SEC = 10 seconds + // The log should show between each stats report: + // "start" lines ~= injector-limit * P(finish) = injector-limit * stats-period/replay-period = 17 ? + // total attempts at starting ("start" lines + "could not thread" lines) ~= N_SOUNDS = 1000 ? + // entities > N_SOUNDS * (1+ N_SILENT_ENTITIES_PER_SOUND) = 11000 + whatever was in the scene before running spawner + // sounds = N_SOUNDS = 1000 + // getEntityPropertiesPerUpdate ~= playing + failed-starts/UPDATES_PER_STATS_LOG + other-rechecks-each-update + // = injector-limit + (total attempts - "start" lines)/UPDATES_PER_STATS__LOG + // + (entities - playing - failed-starts/UPDATES_PER_STATS_LOG) * P(recheck-in-update) + // where failed-starts/UPDATES_PER_STATS_LOG = (1000-17)/100 = 10 + // = 40 + 10 + (11000 - 40 - 10)*UPDATE_TIME/RECHECK_TIME + // = 40 + 10 + 10950*0.01 = 159 (mostly proportional to enties/RECHECK_TIME) + // millisecondsPerUpdate ~= UPDATE_TIME = 100 (+ some timer machinery time) + // this assignment client activity monitor < 100% cpu + var stats = { + entities: 0, + sounds: 0, + playing: 0, + getEntityPropertiesPerUpdate: examinationCount / UPDATES_PER_STATS_LOG, + millisecondsPerUpdate: (now - lastStats) / UPDATES_PER_STATS_LOG + }; + nUpdates = UPDATES_PER_STATS_LOG; + lastStats = now; + examinationCount = 0; + Object.keys(entityCache).forEach(function (entityIdentifier) { + var datum = entityCache[entityIdentifier]; + stats.entities++; + if (datum.url) { + stats.sounds++; + if (datum.injector && datum.injector.playing) { + stats.playing++; + } + } + }); + print(JSON.stringify(stats)); + } +} + +// Update the set of which EntityData we know about. +// +function updateEntiesForAvatar(avatarIdentifier) { // Just one piece of update work. + // This does at most: + // one queryOctree request of the entity server, and + // one findEntities geometry query of our own octree, and + // a quick internEntityDatum of each of what may be a large number of entityIdentifiers. + // The idea is that this is a nice bounded piece of work that should not be done too frequently. + // However, it means that we won't learn about new entities until, on average (nAvatars * UPDATE_TIME) + query round trip. + var avatar = AvatarList.getAvatar(avatarIdentifier), avatarPosition = avatar && avatar.position; + if (!avatarPosition) { // No longer here. + return; + } + var timestamp = Date.now(); + if (isACScript) { + EntityViewer.setPosition(avatarPosition); + EntityViewer.queryOctree(); // Requests an update, but there's no telling when we'll actually see different results. + } + var entities = Entities.findEntities(avatarPosition, QUERY_RADIUS); + debug("found", entities.length, "entities near", avatar.name || "unknown", "at", avatarPosition); + entities.forEach(function (entityIdentifier) { + internEntityDatum(entityIdentifier, timestamp, avatarPosition, avatar); + }); +} + +// Slowly update the set of data we have to work with. +// +var workQueue = []; +function updateWorkQueueForAvatarsPresent() { // when nothing else to do, fill queue with individual avatar updates + workQueue = AvatarList.getAvatarIdentifiers().map(function (avatarIdentifier) { + return function () { + updateEntiesForAvatar(avatarIdentifier); + }; + }); +} +Script.setInterval(function () { + // There might be thousands of EntityData known to us, but only a few will require any work to update. + updateAllEntityData(); // i.e., this better be pretty fast. + // Each interval, we do no more than one updateEntitiesforAvatar. + if (!workQueue.length) { + workQueue = [updateWorkQueueForAvatarsPresent]; + } + workQueue.pop()(); // There's always one +}, UPDATE_TIME); From 728f8aa2388fb50147d10c4e3ea4b4fb895dd615 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 21 Nov 2016 11:56:32 -0800 Subject: [PATCH 4/7] Reduce logspam, change some debug output to warning --- .../gpu-gl/src/gpu/gl/GLBackendOutput.cpp | 6 ++-- libraries/gpu-gl/src/gpu/gl/GLFramebuffer.cpp | 10 +++---- libraries/gpu-gl/src/gpu/gl/GLShader.cpp | 2 +- libraries/gpu-gl/src/gpu/gl/GLShared.cpp | 16 +++++----- libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp | 30 +++++++++---------- .../src/gpu/gl45/GL45BackendTexture.cpp | 18 ++--------- 6 files changed, 34 insertions(+), 48 deletions(-) diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendOutput.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendOutput.cpp index 2eadd4976a..1e6691538b 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendOutput.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendOutput.cpp @@ -141,19 +141,19 @@ void GLBackend::downloadFramebuffer(const FramebufferPointer& srcFramebuffer, co auto readFBO = getFramebufferID(srcFramebuffer); if (srcFramebuffer && readFBO) { if ((srcFramebuffer->getWidth() < (region.x + region.z)) || (srcFramebuffer->getHeight() < (region.y + region.w))) { - qCDebug(gpugllogging) << "GLBackend::downloadFramebuffer : srcFramebuffer is too small to provide the region queried"; + qCWarning(gpugllogging) << "GLBackend::downloadFramebuffer : srcFramebuffer is too small to provide the region queried"; return; } } if ((destImage.width() < region.z) || (destImage.height() < region.w)) { - qCDebug(gpugllogging) << "GLBackend::downloadFramebuffer : destImage is too small to receive the region of the framebuffer"; + qCWarning(gpugllogging) << "GLBackend::downloadFramebuffer : destImage is too small to receive the region of the framebuffer"; return; } GLenum format = GL_BGRA; if (destImage.format() != QImage::Format_ARGB32) { - qCDebug(gpugllogging) << "GLBackend::downloadFramebuffer : destImage format must be FORMAT_ARGB32 to receive the region of the framebuffer"; + qCWarning(gpugllogging) << "GLBackend::downloadFramebuffer : destImage format must be FORMAT_ARGB32 to receive the region of the framebuffer"; return; } diff --git a/libraries/gpu-gl/src/gpu/gl/GLFramebuffer.cpp b/libraries/gpu-gl/src/gpu/gl/GLFramebuffer.cpp index 0ff00697ca..85cf069062 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLFramebuffer.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLFramebuffer.cpp @@ -29,19 +29,19 @@ bool GLFramebuffer::checkStatus(GLenum target) const { result = true; break; case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: - qCDebug(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT."; + qCWarning(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT."; break; case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: - qCDebug(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT."; + qCWarning(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT."; break; case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: - qCDebug(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER."; + qCWarning(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER."; break; case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: - qCDebug(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER."; + qCWarning(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER."; break; case GL_FRAMEBUFFER_UNSUPPORTED: - qCDebug(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_UNSUPPORTED."; + qCWarning(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_UNSUPPORTED."; break; } return result; diff --git a/libraries/gpu-gl/src/gpu/gl/GLShader.cpp b/libraries/gpu-gl/src/gpu/gl/GLShader.cpp index 23439a640a..5020ad38a4 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLShader.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLShader.cpp @@ -119,7 +119,7 @@ GLShader* compileBackendProgram(GLBackend& backend, const Shader& program) { if (object) { shaderGLObjects.push_back(object->_shaderObjects[version].glshader); } else { - qCDebug(gpugllogging) << "GLShader::compileBackendProgram - One of the shaders of the program is not compiled?"; + qCWarning(gpugllogging) << "GLShader::compileBackendProgram - One of the shaders of the program is not compiled?"; return nullptr; } } diff --git a/libraries/gpu-gl/src/gpu/gl/GLShared.cpp b/libraries/gpu-gl/src/gpu/gl/GLShared.cpp index 943cb3c7b5..76457206e2 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLShared.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLShared.cpp @@ -26,25 +26,25 @@ bool checkGLError(const char* name) { } else { switch (error) { case GL_INVALID_ENUM: - qCDebug(gpugllogging) << "GLBackend::" << name << ": An unacceptable value is specified for an enumerated argument.The offending command is ignored and has no other side effect than to set the error flag."; + qCWarning(gpugllogging) << "GLBackend::" << name << ": An unacceptable value is specified for an enumerated argument.The offending command is ignored and has no other side effect than to set the error flag."; break; case GL_INVALID_VALUE: - qCDebug(gpugllogging) << "GLBackend" << name << ": A numeric argument is out of range.The offending command is ignored and has no other side effect than to set the error flag"; + qCWarning(gpugllogging) << "GLBackend" << name << ": A numeric argument is out of range.The offending command is ignored and has no other side effect than to set the error flag"; break; case GL_INVALID_OPERATION: - qCDebug(gpugllogging) << "GLBackend" << name << ": The specified operation is not allowed in the current state.The offending command is ignored and has no other side effect than to set the error flag.."; + qCWarning(gpugllogging) << "GLBackend" << name << ": The specified operation is not allowed in the current state.The offending command is ignored and has no other side effect than to set the error flag.."; break; case GL_INVALID_FRAMEBUFFER_OPERATION: - qCDebug(gpugllogging) << "GLBackend" << name << ": The framebuffer object is not complete.The offending command is ignored and has no other side effect than to set the error flag."; + qCWarning(gpugllogging) << "GLBackend" << name << ": The framebuffer object is not complete.The offending command is ignored and has no other side effect than to set the error flag."; break; case GL_OUT_OF_MEMORY: - qCDebug(gpugllogging) << "GLBackend" << name << ": There is not enough memory left to execute the command.The state of the GL is undefined, except for the state of the error flags, after this error is recorded."; + qCWarning(gpugllogging) << "GLBackend" << name << ": There is not enough memory left to execute the command.The state of the GL is undefined, except for the state of the error flags, after this error is recorded."; break; case GL_STACK_UNDERFLOW: - qCDebug(gpugllogging) << "GLBackend" << name << ": An attempt has been made to perform an operation that would cause an internal stack to underflow."; + qCWarning(gpugllogging) << "GLBackend" << name << ": An attempt has been made to perform an operation that would cause an internal stack to underflow."; break; case GL_STACK_OVERFLOW: - qCDebug(gpugllogging) << "GLBackend" << name << ": An attempt has been made to perform an operation that would cause an internal stack to overflow."; + qCWarning(gpugllogging) << "GLBackend" << name << ": An attempt has been made to perform an operation that would cause an internal stack to overflow."; break; } return true; @@ -751,7 +751,7 @@ void makeProgramBindings(ShaderObject& shaderObject) { GLint linked = 0; glGetProgramiv(glprogram, GL_LINK_STATUS, &linked); if (!linked) { - qCDebug(gpugllogging) << "GLShader::makeBindings - failed to link after assigning slotBindings?"; + qCWarning(gpugllogging) << "GLShader::makeBindings - failed to link after assigning slotBindings?"; } // now assign the ubo binding, then DON't relink! diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp index 39c3bd2f67..bd945cbaaa 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp @@ -120,7 +120,7 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) { break; default: - qCDebug(gpugllogging) << "Unknown combination of texel format"; + qCWarning(gpugllogging) << "Unknown combination of texel format"; } break; } @@ -132,7 +132,7 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) { result = GL_RG8; break; default: - qCDebug(gpugllogging) << "Unknown combination of texel format"; + qCWarning(gpugllogging) << "Unknown combination of texel format"; } break; @@ -155,7 +155,7 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) { result = GL_COMPRESSED_SRGB; break; default: - qCDebug(gpugllogging) << "Unknown combination of texel format"; + qCWarning(gpugllogging) << "Unknown combination of texel format"; } break; @@ -241,13 +241,13 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) { */ default: - qCDebug(gpugllogging) << "Unknown combination of texel format"; + qCWarning(gpugllogging) << "Unknown combination of texel format"; } break; } default: - qCDebug(gpugllogging) << "Unknown combination of texel format"; + qCWarning(gpugllogging) << "Unknown combination of texel format"; } return result; } @@ -280,7 +280,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E texel.internalFormat = GL_DEPTH24_STENCIL8; break; default: - qCDebug(gpugllogging) << "Unknown combination of texel format"; + qCWarning(gpugllogging) << "Unknown combination of texel format"; } break; } @@ -295,7 +295,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E texel.internalFormat = GL_RG8; break; default: - qCDebug(gpugllogging) << "Unknown combination of texel format"; + qCWarning(gpugllogging) << "Unknown combination of texel format"; } break; @@ -318,7 +318,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E texel.internalFormat = GL_COMPRESSED_SRGB; break; default: - qCDebug(gpugllogging) << "Unknown combination of texel format"; + qCWarning(gpugllogging) << "Unknown combination of texel format"; } break; @@ -381,13 +381,13 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E */ default: - qCDebug(gpugllogging) << "Unknown combination of texel format"; + qCWarning(gpugllogging) << "Unknown combination of texel format"; } break; } default: - qCDebug(gpugllogging) << "Unknown combination of texel format"; + qCWarning(gpugllogging) << "Unknown combination of texel format"; } return texel; } else { @@ -523,7 +523,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E texel.internalFormat = GL_DEPTH24_STENCIL8; break; default: - qCDebug(gpugllogging) << "Unknown combination of texel format"; + qCWarning(gpugllogging) << "Unknown combination of texel format"; } break; @@ -539,7 +539,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E texel.internalFormat = GL_RG8; break; default: - qCDebug(gpugllogging) << "Unknown combination of texel format"; + qCWarning(gpugllogging) << "Unknown combination of texel format"; } break; @@ -566,7 +566,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E texel.internalFormat = GL_COMPRESSED_SRGB; break; default: - qCDebug(gpugllogging) << "Unknown combination of texel format"; + qCWarning(gpugllogging) << "Unknown combination of texel format"; } break; } @@ -648,13 +648,13 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E texel.internalFormat = GL_COMPRESSED_SRGB_ALPHA; break; default: - qCDebug(gpugllogging) << "Unknown combination of texel format"; + qCWarning(gpugllogging) << "Unknown combination of texel format"; } break; } default: - qCDebug(gpugllogging) << "Unknown combination of texel format"; + qCWarning(gpugllogging) << "Unknown combination of texel format"; } return texel; } diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp index 0516bc6be0..fba2c138dc 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp @@ -61,7 +61,6 @@ static std::vector getPageDimensionsForFormat(const TextureTypeFormat& ty for (GLint i = 0; i < count; ++i) { result[i] = uvec3(x[i], y[i], z[i]); } - qCDebug(gpugl45logging) << "Got " << count << " page sizes"; } { @@ -91,7 +90,6 @@ SparseInfo::SparseInfo(GL45Texture& texture) 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; } @@ -285,11 +283,6 @@ GL45Texture::GL45Texture(const std::weak_ptr& backend, const Texture& } GL45Texture::~GL45Texture() { - // // External textures cycle very quickly, so don't spam the log with messages about them. - // if (!_gpuObject.getUsage().isExternal()) { - // qCDebug(gpugl45logging) << "Destroying texture " << _id << " from source " << _source.c_str(); - // } - // Remove this texture from the candidate list of derezzable textures if (_transferrable) { auto mipLevels = usedMipLevels(); @@ -350,9 +343,6 @@ void GL45Texture::withPreservedTexture(std::function f) const { } void GL45Texture::generateMips() const { - if (_transferrable) { - qCDebug(gpugl45logging) << "Generating mipmaps for " << _source.c_str(); - } glGenerateTextureMipmap(_id); (void)CHECK_GL_ERROR(); } @@ -628,19 +618,16 @@ void GL45Backend::derezTextures() const { Lock lock(texturesByMipCountsMutex); if (texturesByMipCounts.empty()) { - qCDebug(gpugl45logging) << "No available textures to derez"; + // No available textures to derez return; } auto mipLevel = texturesByMipCounts.rbegin()->first; if (mipLevel <= 1) { - qCDebug(gpugl45logging) << "Max mip levels " << mipLevel; + // No mips available to remove return; } - qCDebug(gpugl45logging) << "Allowed texture memory " << Texture::getAllowedGPUMemoryUsage(); - qCDebug(gpugl45logging) << "Used texture memory " << (Context::getTextureGPUMemoryUsage() - Context::getTextureGPUFramebufferMemoryUsage()); - GL45Texture* targetTexture = nullptr; { auto& textures = texturesByMipCounts[mipLevel]; @@ -649,5 +636,4 @@ void GL45Backend::derezTextures() const { } lock.unlock(); targetTexture->derez(); - qCDebug(gpugl45logging) << "New Used texture memory " << (Context::getTextureGPUMemoryUsage() - Context::getTextureGPUFramebufferMemoryUsage()); } From 066a5181421b1c91d8211b6210f99eaf337ae0ec Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Mon, 21 Nov 2016 13:07:38 -0800 Subject: [PATCH 5/7] expose metaverse url to javascript and use it rather than hardcoding --- libraries/networking/src/AddressManager.cpp | 5 +++++ libraries/networking/src/AddressManager.h | 2 ++ scripts/system/snapshot.js | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 05210012ba..240c722872 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -24,6 +24,7 @@ #include "AddressManager.h" #include "NodeList.h" +#include "NetworkingConstants.h" #include "NetworkLogging.h" #include "UserActivityLogger.h" #include "udt/PacketHeaders.h" @@ -754,6 +755,10 @@ QString AddressManager::getDomainId() const { return DependencyManager::get()->getDomainHandler().getUUID().toString(); } +const QUrl AddressManager::getMetaverseServerUrl() const { + return NetworkingConstants::METAVERSE_SERVER_URL; +} + void AddressManager::handleShareableNameAPIResponse(QNetworkReply& requestReply) { // make sure that this response is for the domain we're currently connected to auto domainID = DependencyManager::get()->getDomainHandler().getUUID(); diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index 366fc5dfab..c7d283ad02 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -41,6 +41,7 @@ class AddressManager : public QObject, public Dependency { Q_PROPERTY(QString pathname READ currentPath) Q_PROPERTY(QString placename READ getPlaceName) Q_PROPERTY(QString domainId READ getDomainId) + Q_PROPERTY(QUrl metaverseServerUrl READ getMetaverseServerUrl) public: Q_INVOKABLE QString protocolVersion(); using PositionGetter = std::function; @@ -70,6 +71,7 @@ public: const QUuid& getRootPlaceID() const { return _rootPlaceID; } const QString& getPlaceName() const { return _shareablePlaceName.isEmpty() ? _placeName : _shareablePlaceName; } QString getDomainId() const; + const QUrl getMetaverseServerUrl() const; const QString& getHost() const { return _host; } diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 6b8e2b32aa..f6ae6d153d 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -133,7 +133,7 @@ function isDomainOpen(id) { 'restriction=open,hifi' // If we're sharing, we're logged in // If we're here, protocol matches, and it is online ]; - var url = "https://metaverse.highfidelity.com/api/v1/user_stories?" + options.join('&'); + var url = location.metaverseServerUrl + "/api/v1/user_stories?" + options.join('&'); request.open("GET", url, false); request.send(); if (request.status != 200) { From 2beed7e70b66ded39acc8107741f2cd4d8ccfcd6 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Mon, 21 Nov 2016 16:22:02 -0800 Subject: [PATCH 6/7] proper include_actions for 'places' tab. --- interface/resources/qml/AddressBarDialog.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/AddressBarDialog.qml b/interface/resources/qml/AddressBarDialog.qml index a616b05394..db6b87220d 100644 --- a/interface/resources/qml/AddressBarDialog.qml +++ b/interface/resources/qml/AddressBarDialog.qml @@ -438,7 +438,7 @@ Window { function getUserStoryPage(pageNumber, cb) { // cb(error) after all pages of domain data have been added to model var options = [ 'now=' + new Date().toISOString(), - 'include_actions=snapshot,concurrency', + 'include_actions=' + selectedTab.includeActions, 'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'), 'require_online=true', 'protocol=' + encodeURIComponent(AddressManager.protocolVersion()), From 81786bf8197c4ae587c16bd17aa3cf47852957e5 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Mon, 21 Nov 2016 16:46:10 -0800 Subject: [PATCH 7/7] animate suggestions when available --- interface/resources/qml/AddressBarDialog.qml | 2 ++ interface/resources/qml/hifi/Card.qml | 11 ++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/interface/resources/qml/AddressBarDialog.qml b/interface/resources/qml/AddressBarDialog.qml index bfb5295512..aab912a201 100644 --- a/interface/resources/qml/AddressBarDialog.qml +++ b/interface/resources/qml/AddressBarDialog.qml @@ -112,6 +112,7 @@ Window { placeName: model.place_name; hifiUrl: model.place_name + model.path; thumbnail: model.thumbnail_url; + imageUrl: model.image_url; action: model.action; timestamp: model.created_at; onlineUsers: model.online_users; @@ -395,6 +396,7 @@ Window { created_at: data.created_at || "", action: data.action || "", thumbnail_url: resolveUrl(thumbnail_url), + image_url: resolveUrl(data.details.image_url), metaverseId: (data.id || "").toString(), // Some are strings from server while others are numbers. Model objects require uniformity. diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml index 9e9b1dff51..876be740cd 100644 --- a/interface/resources/qml/hifi/Card.qml +++ b/interface/resources/qml/hifi/Card.qml @@ -25,6 +25,7 @@ Rectangle { property string timestamp: ""; property string hifiUrl: ""; property string thumbnail: defaultThumbnail; + property string imageUrl: ""; property var goFunction: null; property string storyId: ""; @@ -67,13 +68,21 @@ Rectangle { return 'about a minute ago'; } + property bool hasGif: imageUrl.indexOf('.gif') === (imageUrl.length - 4); + AnimatedImage { + id: animation; + // Always visible, to drive loading, but initially covered up by lobby during load. + source: hasGif ? imageUrl : ""; + fillMode: lobby.fillMode; + anchors.fill: lobby; + } Image { id: lobby; + visible: !hasGif || (animation.status !== Image.Ready); width: parent.width - (isConcurrency ? 0 : (2 * smallMargin)); height: parent.height - messageHeight - (isConcurrency ? 0 : smallMargin); source: thumbnail || defaultThumbnail; fillMode: Image.PreserveAspectCrop; - // source gets filled in later anchors { horizontalCenter: parent.horizontalCenter; top: parent.top;