diff --git a/assignment-client/src/avatars/MixerAvatar.cpp b/assignment-client/src/avatars/MixerAvatar.cpp
index f5d598a5cf..6d6358ddf5 100644
--- a/assignment-client/src/avatars/MixerAvatar.cpp
+++ b/assignment-client/src/avatars/MixerAvatar.cpp
@@ -71,7 +71,7 @@ void MixerAvatar::fetchAvatarFST() {
connect(fstRequest, &ResourceRequest::finished, this, &MixerAvatar::fstRequestComplete);
fstRequest->send();
} else {
- qCDebug(avatars) << "Couldn't create FST request for" << avatarURL;
+ qCDebug(avatars) << "Couldn't create FST request for" << avatarURL << getSessionUUID();
_verifyState = error;
}
_needsIdentityUpdate = true;
@@ -84,7 +84,7 @@ void MixerAvatar::fstRequestComplete() {
auto result = fstRequest->getResult();
if (result != ResourceRequest::Success) {
_verifyState = error;
- qCDebug(avatars) << "FST request for" << fstRequest->getUrl() << "failed:" << result;
+ qCDebug(avatars) << "FST request for" << fstRequest->getUrl() << "(user " << getSessionUUID() << ") failed:" << result;
} else {
_avatarFSTContents = fstRequest->getData();
_verifyState = receivedFST;
@@ -178,7 +178,8 @@ void MixerAvatar::ownerRequestComplete() {
} else {
auto jsonData = QJsonDocument::fromJson(networkReply->readAll())["data"];
if (!jsonData.isUndefined() && !jsonData.toObject()["message"].isUndefined()) {
- qCDebug(avatars) << "Owner lookup failed for" << getDisplayName() << ":"
+ qCDebug(avatars) << "Owner lookup failed for" << getDisplayName() << "("
+ << getSessionUUID() << ") :"
<< jsonData.toObject()["message"].toString();
_verifyState = error;
_pendingEvent = false;
@@ -221,7 +222,7 @@ void MixerAvatar::processCertifyEvents() {
} else {
_needsIdentityUpdate = true;
_pendingEvent = false;
- qCDebug(avatars) << "Avatar" << getDisplayName() << "FAILED static certification";
+ qCDebug(avatars) << "Avatar" << getDisplayName() << "(" << getSessionUUID() << ") FAILED static certification";
}
} else { // FST doesn't have a certificate, so noncertified rather than failed:
_pendingEvent = false;
@@ -261,6 +262,8 @@ void MixerAvatar::processCertifyEvents() {
_pendingEvent = true;
} else {
_verifyState = error;
+ qCDebug(avatars) << "Get owner status - couldn't parse response for" << getSessionUUID()
+ << ":" << _dynamicMarketResponse;
}
} else {
qCDebug(avatars) << "Get owner status failed for " << getDisplayName() << _marketplaceIdFromURL <<
@@ -332,7 +335,7 @@ void MixerAvatar::sendOwnerChallenge() {
nonceHash.addData(nonce);
_challengeNonceHash = nonceHash.result();
- static constexpr int CHALLENGE_TIMEOUT_MS = 5 * 1000; // 5 s
+ static constexpr int CHALLENGE_TIMEOUT_MS = 10 * 1000; // 10 s
if (_challengeTimeout) {
_challengeTimeout->deleteLater();
}
@@ -344,6 +347,7 @@ void MixerAvatar::sendOwnerChallenge() {
_pendingEvent = false;
_verifyState = verificationFailed;
_needsIdentityUpdate = true;
+ qCDebug(avatars) << "Dynamic verification TIMED-OUT for " << getDisplayName() << getSessionUUID();
}
});
_challengeTimeout->start();
diff --git a/cmake/macros/TargetWebRTC.cmake b/cmake/macros/TargetWebRTC.cmake
new file mode 100644
index 0000000000..d2821528df
--- /dev/null
+++ b/cmake/macros/TargetWebRTC.cmake
@@ -0,0 +1,24 @@
+#
+# Copyright 2019 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
+#
+macro(TARGET_WEBRTC)
+ if (ANDROID)
+ # I don't yet have working libwebrtc for android
+ # include(SelectLibraryConfigurations)
+ # set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/webrtc/webrtc)
+ # set(WEBRTC_INCLUDE_DIRS "${INSTALL_DIR}/include/webrtc")
+ # set(WEBRTC_LIBRARY_DEBUG ${INSTALL_DIR}/debug/lib/libwebrtc.a)
+ # set(WEBRTC_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libwebrtc.a)
+ # select_library_configurations(WEBRTC)
+ else()
+ set(WEBRTC_INCLUDE_DIRS "${VCPKG_INSTALL_ROOT}/include/webrtc")
+ find_library(WEBRTC_LIBRARY NAMES webrtc PATHS ${VCPKG_INSTALL_ROOT}/lib/ NO_DEFAULT_PATH)
+ target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${WEBRTC_INCLUDE_DIRS})
+ target_link_libraries(${TARGET_NAME} ${WEBRTC_LIBRARY})
+ endif()
+
+
+endmacro()
diff --git a/cmake/ports/hifi-deps/CONTROL b/cmake/ports/hifi-deps/CONTROL
index 5f860a1620..2441de9002 100644
--- a/cmake/ports/hifi-deps/CONTROL
+++ b/cmake/ports/hifi-deps/CONTROL
@@ -1,4 +1,4 @@
Source: hifi-deps
Version: 0.1
Description: Collected dependencies for High Fidelity applications
-Build-Depends: bullet3, draco, etc2comp, glm, nvtt, openexr (!android), openssl (windows), tbb (!android&!osx), zlib
+Build-Depends: bullet3, draco, etc2comp, glm, nvtt, openexr (!android), openssl (windows), tbb (!android&!osx), zlib, webrtc (!android)
diff --git a/cmake/ports/webrtc/CONTROL b/cmake/ports/webrtc/CONTROL
new file mode 100644
index 0000000000..12a76920b9
--- /dev/null
+++ b/cmake/ports/webrtc/CONTROL
@@ -0,0 +1,3 @@
+Source: webrtc
+Version: 20190626
+Description: WebRTC
diff --git a/cmake/ports/webrtc/portfile.cmake b/cmake/ports/webrtc/portfile.cmake
new file mode 100644
index 0000000000..3f2fb7a6ab
--- /dev/null
+++ b/cmake/ports/webrtc/portfile.cmake
@@ -0,0 +1,36 @@
+include(vcpkg_common_functions)
+set(WEBRTC_VERSION 20190626)
+set(MASTER_COPY_SOURCE_PATH ${CURRENT_BUILDTREES_DIR}/src)
+
+if (ANDROID)
+ # this is handled by hifi_android.py
+elseif (WIN32)
+ vcpkg_download_distfile(
+ WEBRTC_SOURCE_ARCHIVE
+ URLS https://hifi-public.s3.amazonaws.com/seth/webrtc-20190626-windows.zip
+ SHA512 c0848eddb1579b3bb0496b8785e24f30470f3c477145035fd729264a326a467b9467ae9f426aa5d72d168ad9e9bf2c279150744832736bdf39064d24b04de1a3
+ FILENAME webrtc-20190626-windows.zip
+ )
+elseif (APPLE)
+ vcpkg_download_distfile(
+ WEBRTC_SOURCE_ARCHIVE
+ URLS https://hifi-public.s3.amazonaws.com/seth/webrtc-20190626-osx.tar.gz
+ SHA512 fc70cec1b5ee87395137b7090f424e2fc2300fc17d744d5ffa1cf7aa0e0f1a069a9d72ba1ad2fb4a640ebeb6c218bda24351ba0083e1ff96c4a4b5032648a9d2
+ FILENAME webrtc-20190626-osx.tar.gz
+ )
+else ()
+ # else Linux desktop
+ vcpkg_download_distfile(
+ WEBRTC_SOURCE_ARCHIVE
+ URLS https://hifi-public.s3.amazonaws.com/seth/webrtc-20190626-linux.tar.gz
+ SHA512 07d7776551aa78cb09a3ef088a8dee7762735c168c243053b262083d90a1d258cec66dc386f6903da5c4461921a3c2db157a1ee106a2b47e7756cb424b66cc43
+ FILENAME webrtc-20190626-linux.tar.gz
+ )
+endif ()
+
+vcpkg_extract_source_archive(${WEBRTC_SOURCE_ARCHIVE})
+
+file(COPY ${MASTER_COPY_SOURCE_PATH}/webrtc/include DESTINATION ${CURRENT_PACKAGES_DIR})
+file(COPY ${MASTER_COPY_SOURCE_PATH}/webrtc/lib DESTINATION ${CURRENT_PACKAGES_DIR})
+file(COPY ${MASTER_COPY_SOURCE_PATH}/webrtc/share DESTINATION ${CURRENT_PACKAGES_DIR})
+file(COPY ${MASTER_COPY_SOURCE_PATH}/webrtc/debug DESTINATION ${CURRENT_PACKAGES_DIR})
diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json
index 488b2091f3..9cb4c2cab9 100644
--- a/domain-server/resources/describe-settings.json
+++ b/domain-server/resources/describe-settings.json
@@ -1788,6 +1788,39 @@
"default": false
}
]
+ },
+ {
+ "name": "installed_content",
+ "label": "Installed Content",
+ "hidden": true,
+ "settings": [
+ {
+ "name": "filename",
+ "content_setting": true,
+ "default": ""
+ },
+ {
+ "name": "name",
+ "content_setting": true,
+ "default": ""
+ },
+ {
+ "name": "creation_time",
+ "content_setting": true,
+ "default": 0
+ },
+ {
+ "name": "install_time",
+ "type": "int",
+ "content_setting": true,
+ "default": 0
+ },
+ {
+ "name": "installed_by",
+ "content_setting": true,
+ "default": ""
+ }
+ ]
}
]
}
diff --git a/domain-server/resources/web/content/js/content.js b/domain-server/resources/web/content/js/content.js
index 85bd9e68b3..bf4cf07b26 100644
--- a/domain-server/resources/web/content/js/content.js
+++ b/domain-server/resources/web/content/js/content.js
@@ -4,8 +4,14 @@ $(document).ready(function(){
var RESTORE_SETTINGS_FILE_ID = 'restore-settings-file';
var UPLOAD_CONTENT_ALLOWED_DIV_ID = 'upload-content-allowed';
var UPLOAD_CONTENT_RECOVERING_DIV_ID = 'upload-content-recovering';
+ var INSTALLED_CONTENT_FILENAME_ID = 'installed-content-filename';
+ var INSTALLED_CONTENT_NAME_ID = 'installed-content-name';
+ var INSTALLED_CONTENT_CREATED_ID = 'installed-content-created';
+ var INSTALLED_CONTENT_INSTALLED_ID = 'installed-content-installed';
+ var INSTALLED_CONTENT_INSTALLED_BY_ID = 'installed-content-installed-by';
var isRestoring = false;
+ var restoreErrorShown = false;
function progressBarHTML(extraClass, label) {
var html = "
";
@@ -64,11 +70,23 @@ $(document).ready(function(){
var ajaxObject = $.ajax(ajaxParams);
ajaxObject.fail(function (jqXHR, textStatus, errorThrown) {
- showErrorMessage(
- "Error",
- "There was a problem restoring domain content.\n"
- + "Please ensure that the content archive or entity file is valid and try again."
- );
+ // status of 0 means the connection was reset, which
+ // happens after the content is parsed and the server restarts
+ // in the case of json and json.gz files
+ if (jqXHR.status != 0) {
+ showErrorMessage(
+ "Error",
+ "There was a problem restoring domain content.\n"
+ + "Please ensure that the content archive or entity file is valid and try again."
+ );
+ } else {
+ isRestoring = true;
+
+ // immediately reload backup information since one should be restoring now
+ reloadBackupInformation();
+
+ swal.close();
+ }
});
updateProgressBars($('.upload-content-progress'), (offset + nextChunkSize) * 100 / fileSize);
@@ -103,10 +121,25 @@ $(document).ready(function(){
html += "Restore in progress";
html += progressBarHTML('recovery', 'Restoring');
html += "
";
-
$('#' + Settings.UPLOAD_CONTENT_BACKUP_PANEL_ID + ' .panel-body').html(html);
}
+ function setupInstalledContentInfo() {
+ var html = "";
+ html += "";
+ html += " | ";
+ html += " | ";
+ html += " | ";
+ html += " | ";
+ html += " |
";
+ html += "
";
+ $('#' + Settings.INSTALLED_CONTENT + ' .panel-body').html(html);
+ }
+
// handle content archive or entity file upload
// when the selected file is changed, enable the button if there's a selected file
@@ -135,6 +168,7 @@ $(document).ready(function(){
var GENERATE_ARCHIVE_BUTTON_ID = 'generate-archive-button';
var CONTENT_ARCHIVES_NORMAL_ID = 'content-archives-success';
+ var CONTENT_ARCHIVES_CONTENT_INFO_ID = 'content-archives-content-info';
var CONTENT_ARCHIVES_ERROR_ID = 'content-archives-error';
var AUTOMATIC_ARCHIVES_TABLE_ID = 'automatic-archives-table';
var AUTOMATIC_ARCHIVES_TBODY_ID = 'automatic-archives-tbody';
@@ -230,13 +264,27 @@ $(document).ready(function(){
url: '/api/backups',
cache: false
}).done(function(data) {
-
// split the returned data into manual and automatic manual backups
var splitBackups = _.partition(data.backups, function(value, index) {
return value.isManualBackup;
});
-
- if (isRestoring && !data.status.isRecovering) {
+ if (data.status.recoveryError && !restoreErrorShown) {
+ restoreErrorShown = true;
+ swal({
+ title: "Error",
+ text: "There was a problem restoring domain content.\n"
+ + data.status.recoveryError,
+ type: "error",
+ showCancelButton: false,
+ confirmButtonText: "Restart",
+ closeOnConfirm: true,
+ },
+ function () {
+ $.get("/restart");
+ showRestartModal();
+ });
+ }
+ if (isRestoring && !data.status.isRecovering && !data.status.recoveryError) {
// we were recovering and we finished - the DS is going to restart so show the restart modal
showRestartModal();
return;
@@ -327,6 +375,12 @@ $(document).ready(function(){
$('#' + UPLOAD_CONTENT_ALLOWED_DIV_ID).toggle(!data.status.isRecovering);
$('#' + UPLOAD_CONTENT_RECOVERING_DIV_ID).toggle(data.status.isRecovering);
+ $('#' + INSTALLED_CONTENT_NAME_ID).text(data.installed_content.name);
+ $('#' + INSTALLED_CONTENT_FILENAME_ID).text(data.installed_content.filename);
+ $('#' + INSTALLED_CONTENT_CREATED_ID).text(data.installed_content.creation_time ? moment(data.installed_content.creation_time).format('lll') : "");
+ $('#' + INSTALLED_CONTENT_INSTALLED_ID).text(data.installed_content.install_time ? moment(data.installed_content.install_time).format('lll') : "");
+ $('#' + INSTALLED_CONTENT_INSTALLED_BY_ID).text(data.installed_content.installed_by);
+
// update the progress bars for current restore status
if (data.status.isRecovering) {
updateProgressBars($('.recovery.progress-bar'), data.status.recoveryProgress * 100);
@@ -514,6 +568,7 @@ $(document).ready(function(){
Settings.afterReloadActions = function() {
setupBackupUpload();
setupContentArchives();
+ setupInstalledContentInfo();
// load the latest backups immediately
reloadBackupInformation();
diff --git a/domain-server/resources/web/js/domain-server.js b/domain-server/resources/web/js/domain-server.js
index 2c12e2683a..a8b7267b88 100644
--- a/domain-server/resources/web/js/domain-server.js
+++ b/domain-server/resources/web/js/domain-server.js
@@ -57,10 +57,14 @@ $(document).ready(function(){
// define extra groups to add to setting panels, with their splice index
Settings.extraContentGroupsAtIndex = {
0: {
+ html_id: Settings.INSTALLED_CONTENT,
+ label: 'Installed Content'
+ },
+ 1: {
html_id: Settings.CONTENT_ARCHIVES_PANEL_ID,
label: 'Content Archives'
},
- 1: {
+ 2: {
html_id: Settings.UPLOAD_CONTENT_BACKUP_PANEL_ID,
label: 'Upload Content'
}
diff --git a/domain-server/resources/web/js/shared.js b/domain-server/resources/web/js/shared.js
index cdfcc40eab..abcb2cb9eb 100644
--- a/domain-server/resources/web/js/shared.js
+++ b/domain-server/resources/web/js/shared.js
@@ -44,7 +44,8 @@ $.extend(Settings, {
INVALID_ROW_CLASS: 'invalid-input',
DATA_ROW_INDEX: 'data-row-index',
CONTENT_ARCHIVES_PANEL_ID: 'content_archives',
- UPLOAD_CONTENT_BACKUP_PANEL_ID: 'upload_content'
+ UPLOAD_CONTENT_BACKUP_PANEL_ID: 'upload_content',
+ INSTALLED_CONTENT: 'installed_content'
});
var URLs = {
diff --git a/domain-server/src/AssetsBackupHandler.cpp b/domain-server/src/AssetsBackupHandler.cpp
index 0f63817d80..6ccfb2ad57 100644
--- a/domain-server/src/AssetsBackupHandler.cpp
+++ b/domain-server/src/AssetsBackupHandler.cpp
@@ -278,17 +278,19 @@ void AssetsBackupHandler::createBackup(const QString& backupName, QuaZip& zip) {
_backups.emplace_back(backupName, mappings, false);
}
-void AssetsBackupHandler::recoverBackup(const QString& backupName, QuaZip& zip) {
+std::pair AssetsBackupHandler::recoverBackup(const QString& backupName, QuaZip& zip, const QString& username, const QString& sourceFilename) {
Q_ASSERT(QThread::currentThread() == thread());
if (operationInProgress()) {
- qCWarning(asset_backup) << "There is already a backup/restore in progress.";
- return;
+ QString errorStr ("There is already a backup/restore in progress. Please wait.");
+ qWarning() << errorStr;
+ return { false, errorStr };
}
if (_lastMappingsRefresh.time_since_epoch().count() == 0) {
- qCWarning(asset_backup) << "Current mappings not yet loaded.";
- return;
+ QString errorStr ("Current mappings not yet loaded. Please wait.");
+ qWarning() << errorStr;
+ return { false, errorStr };
}
if ((p_high_resolution_clock::now() - _lastMappingsRefresh) > MAX_REFRESH_TIME) {
@@ -301,6 +303,16 @@ void AssetsBackupHandler::recoverBackup(const QString& backupName, QuaZip& zip)
if (it == end(_backups)) {
loadBackup(backupName, zip);
+ auto emplaced_backup = find_if(begin(_backups), end(_backups), [&](const AssetServerBackup& backup) {
+ return backup.name == backupName;
+ });
+
+ if(emplaced_backup->corruptedBackup) {
+ QString errorStr ("Current mappings file is corrupted.");
+ qWarning() << errorStr;
+ return { false, errorStr };
+ }
+
QuaZipDir zipDir { &zip, ZIP_ASSETS_FOLDER };
auto assetNames = zipDir.entryList(QDir::Files);
@@ -330,8 +342,9 @@ void AssetsBackupHandler::recoverBackup(const QString& backupName, QuaZip& zip)
});
if (it == end(_backups)) {
- qCCritical(asset_backup) << "Failed to recover backup:" << backupName;
- return;
+ QString errorStr ("Failed to recover backup: " + backupName);
+ qWarning() << errorStr;
+ return { false, errorStr };
}
}
@@ -339,6 +352,7 @@ void AssetsBackupHandler::recoverBackup(const QString& backupName, QuaZip& zip)
computeServerStateDifference(_currentMappings, newMappings);
restoreAllAssets();
+ return { true, QString() };
}
void AssetsBackupHandler::deleteBackup(const QString& backupName) {
diff --git a/domain-server/src/AssetsBackupHandler.h b/domain-server/src/AssetsBackupHandler.h
index 427dc6831a..703b844afc 100644
--- a/domain-server/src/AssetsBackupHandler.h
+++ b/domain-server/src/AssetsBackupHandler.h
@@ -38,7 +38,7 @@ public:
void loadBackup(const QString& backupName, QuaZip& zip) override;
void loadingComplete() override;
void createBackup(const QString& backupName, QuaZip& zip) override;
- void recoverBackup(const QString& backupName, QuaZip& zip) override;
+ std::pair recoverBackup(const QString& backupName, QuaZip& zip, const QString& username, const QString& sourceFilename) override;
void deleteBackup(const QString& backupName) override;
void consolidateBackup(const QString& backupName, QuaZip& zip) override;
bool isCorruptedBackup(const QString& backupName) override;
diff --git a/domain-server/src/BackupHandler.h b/domain-server/src/BackupHandler.h
index 547339e01b..8ef11b432a 100644
--- a/domain-server/src/BackupHandler.h
+++ b/domain-server/src/BackupHandler.h
@@ -30,7 +30,7 @@ public:
virtual void loadBackup(const QString& backupName, QuaZip& zip) = 0;
virtual void loadingComplete() = 0;
virtual void createBackup(const QString& backupName, QuaZip& zip) = 0;
- virtual void recoverBackup(const QString& backupName, QuaZip& zip) = 0;
+ virtual std::pair recoverBackup(const QString& backupName, QuaZip& zip, const QString& username, const QString& sourceFilename) = 0;
virtual void deleteBackup(const QString& backupName) = 0;
virtual void consolidateBackup(const QString& backupName, QuaZip& zip) = 0;
virtual bool isCorruptedBackup(const QString& backupName) = 0;
diff --git a/domain-server/src/ContentSettingsBackupHandler.cpp b/domain-server/src/ContentSettingsBackupHandler.cpp
index de7669b6a5..97a10e81c3 100644
--- a/domain-server/src/ContentSettingsBackupHandler.cpp
+++ b/domain-server/src/ContentSettingsBackupHandler.cpp
@@ -10,6 +10,7 @@
//
#include "ContentSettingsBackupHandler.h"
+#include "DomainContentBackupManager.h"
#if !defined(__clang__) && defined(__GNUC__)
#pragma GCC diagnostic push
@@ -24,6 +25,7 @@
#pragma GCC diagnostic pop
#endif
+static const QString DATETIME_FORMAT { "yyyy-MM-dd_HH-mm-ss" };
ContentSettingsBackupHandler::ContentSettingsBackupHandler(DomainServerSettingsManager& domainServerSettingsManager) :
_settingsManager(domainServerSettingsManager)
@@ -41,6 +43,26 @@ void ContentSettingsBackupHandler::createBackup(const QString& backupName, QuaZi
DomainServerSettingsManager::IncludeContentSettings, DomainServerSettingsManager::NoDefaultSettings,
DomainServerSettingsManager::ForBackup
);
+ QString prefixFormat = "(" + QRegExp::escape(AUTOMATIC_BACKUP_PREFIX) + "|" + QRegExp::escape(MANUAL_BACKUP_PREFIX) + ")";
+ QString nameFormat = "(.+)";
+ QString dateTimeFormat = "(" + DATETIME_FORMAT_RE + ")";
+ QRegExp backupNameFormat { prefixFormat + nameFormat + "-" + dateTimeFormat + "\\.zip" };
+
+ QString name{ "" };
+ QDateTime createdAt;
+
+ if (backupNameFormat.exactMatch(backupName)) {
+ name = backupNameFormat.cap(2);
+ auto dateTime = backupNameFormat.cap(3);
+ createdAt = QDateTime::fromString(dateTime, DATETIME_FORMAT);
+ }
+
+ QJsonObject installed_content {
+ { INSTALLED_CONTENT_NAME, name},
+ { INSTALLED_CONTENT_CREATION_TIME, createdAt.currentMSecsSinceEpoch()}
+ };
+
+ contentSettingsJSON.insert(INSTALLED_CONTENT, installed_content);
// make a QJsonDocument using the object
QJsonDocument contentSettingsDocument { contentSettingsJSON };
@@ -62,24 +84,48 @@ void ContentSettingsBackupHandler::createBackup(const QString& backupName, QuaZi
}
}
-void ContentSettingsBackupHandler::recoverBackup(const QString& backupName, QuaZip& zip) {
+std::pair ContentSettingsBackupHandler::recoverBackup(const QString& backupName, QuaZip& zip, const QString& username, const QString& sourceFilename) {
if (!zip.setCurrentFile(CONTENT_SETTINGS_BACKUP_FILENAME)) {
- qWarning() << "Failed to find" << CONTENT_SETTINGS_BACKUP_FILENAME << "while recovering backup";
- return;
+ QString errorStr("Failed to find " + CONTENT_SETTINGS_BACKUP_FILENAME + " while recovering backup");
+ qWarning() << errorStr;
+ return { false, errorStr };
}
QuaZipFile zipFile { &zip };
if (!zipFile.open(QIODevice::ReadOnly)) {
- qCritical() << "Failed to open" << CONTENT_SETTINGS_BACKUP_FILENAME << "in backup";
- return;
+ QString errorStr("Failed to open " + CONTENT_SETTINGS_BACKUP_FILENAME + " in backup");
+ qCritical() << errorStr;
+ return { false, errorStr };
}
auto rawData = zipFile.readAll();
zipFile.close();
- QJsonDocument jsonDocument = QJsonDocument::fromJson(rawData);
-
- if (!_settingsManager.restoreSettingsFromObject(jsonDocument.object(), ContentSettings)) {
- qCritical() << "Failed to restore settings from" << CONTENT_SETTINGS_BACKUP_FILENAME << "in content archive";
+ if (zipFile.getZipError() != UNZ_OK) {
+ QString errorStr("Failed to unzip " + CONTENT_SETTINGS_BACKUP_FILENAME + ": " + zipFile.getZipError());
+ qCritical() << errorStr;
+ return { false, errorStr };
}
+
+ QJsonDocument jsonDocument = QJsonDocument::fromJson(rawData);
+ QJsonObject jsonObject = jsonDocument.object();
+
+ auto archiveJson = jsonObject.find(INSTALLED_CONTENT)->toObject();
+
+ QJsonObject installed_content {
+ { INSTALLED_CONTENT_FILENAME, sourceFilename },
+ { INSTALLED_CONTENT_NAME, archiveJson[INSTALLED_CONTENT_NAME].toString()},
+ { INSTALLED_CONTENT_CREATION_TIME, archiveJson[INSTALLED_CONTENT_CREATION_TIME].toVariant().toLongLong() },
+ { INSTALLED_CONTENT_INSTALL_TIME, QDateTime::currentDateTime().currentMSecsSinceEpoch() },
+ { INSTALLED_CONTENT_INSTALLED_BY, username }
+ };
+
+ jsonObject.insert(INSTALLED_CONTENT, installed_content);
+
+ if (!_settingsManager.restoreSettingsFromObject(jsonObject, ContentSettings)) {
+ QString errorStr("Failed to restore settings from " + CONTENT_SETTINGS_BACKUP_FILENAME + " in content archive");
+ qCritical() << errorStr;
+ return { false, errorStr };
+ }
+ return { true, QString() };
}
diff --git a/domain-server/src/ContentSettingsBackupHandler.h b/domain-server/src/ContentSettingsBackupHandler.h
index 8454de0786..0872bce59a 100644
--- a/domain-server/src/ContentSettingsBackupHandler.h
+++ b/domain-server/src/ContentSettingsBackupHandler.h
@@ -28,7 +28,7 @@ public:
void createBackup(const QString& backupName, QuaZip& zip) override;
- void recoverBackup(const QString& backupName, QuaZip& zip) override;
+ std::pair recoverBackup(const QString& backupName, QuaZip& zip, const QString& username, const QString& sourceFilename) override;
void deleteBackup(const QString& backupName) override {}
diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp
index 52bc7d679f..3a7897ec61 100644
--- a/domain-server/src/DomainContentBackupManager.cpp
+++ b/domain-server/src/DomainContentBackupManager.cpp
@@ -42,9 +42,7 @@ const std::chrono::seconds DomainContentBackupManager::DEFAULT_PERSIST_INTERVAL
// Backup format looks like: daily_backup-TIMESTAMP.zip
static const QString DATETIME_FORMAT { "yyyy-MM-dd_HH-mm-ss" };
-static const QString DATETIME_FORMAT_RE { "\\d{4}-\\d{2}-\\d{2}_\\d{2}-\\d{2}-\\d{2}" };
-static const QString AUTOMATIC_BACKUP_PREFIX { "autobackup-" };
-static const QString MANUAL_BACKUP_PREFIX { "backup-" };
+static const QString PRE_UPLOAD_SUFFIX{ "pre_upload" };
static const QString MANUAL_BACKUP_NAME_RE { "[a-zA-Z0-9\\-_ ]+" };
void DomainContentBackupManager::addBackupHandler(BackupHandlerPointer handler) {
@@ -52,9 +50,10 @@ void DomainContentBackupManager::addBackupHandler(BackupHandlerPointer handler)
}
DomainContentBackupManager::DomainContentBackupManager(const QString& backupDirectory,
- const QVariantList& backupRules,
+ DomainServerSettingsManager& domainServerSettingsManager,
std::chrono::milliseconds persistInterval,
bool debugTimestampNow) :
+ _settingsManager(domainServerSettingsManager),
_consolidatedBackupDirectory(PathUtils::generateTemporaryDir()),
_backupDirectory(backupDirectory), _persistInterval(persistInterval), _lastCheck(p_high_resolution_clock::now())
{
@@ -63,7 +62,8 @@ DomainContentBackupManager::DomainContentBackupManager(const QString& backupDire
// Make sure the backup directory exists.
QDir(_backupDirectory).mkpath(".");
- parseBackupRules(backupRules);
+ static const QString BACKUP_RULES_KEYPATH = AUTOMATIC_CONTENT_ARCHIVES_GROUP + ".backup_rules";
+ parseBackupRules(_settingsManager.valueOrDefaultValueForKeyPath(BACKUP_RULES_KEYPATH).toList());
constexpr int CONSOLIDATED_BACKUP_CLEANER_INTERVAL_MSECS = 30 * 1000;
_consolidatedBackupCleanupTimer.setInterval(CONSOLIDATED_BACKUP_CLEANER_INTERVAL_MSECS);
@@ -170,7 +170,9 @@ bool DomainContentBackupManager::process() {
return handler->getRecoveryStatus().first;
});
- if (!isStillRecovering) {
+ // if an error occurred, don't restart the server so that the user
+ // can be notified of the error and take action.
+ if (!isStillRecovering && _recoveryError.isEmpty()) {
_isRecovering = false;
_recoveryFilename = "";
emit recoveryCompleted();
@@ -277,7 +279,7 @@ void DomainContentBackupManager::deleteBackup(MiniPromise::Promise promise, cons
});
}
-bool DomainContentBackupManager::recoverFromBackupZip(const QString& backupName, QuaZip& zip) {
+bool DomainContentBackupManager::recoverFromBackupZip(const QString& backupName, QuaZip& zip, const QString& username, const QString& sourceFilename, bool rollingBack) {
if (!zip.open(QuaZip::Mode::mdUnzip)) {
qWarning() << "Failed to unzip file: " << backupName;
return false;
@@ -286,7 +288,15 @@ bool DomainContentBackupManager::recoverFromBackupZip(const QString& backupName,
_recoveryFilename = backupName;
for (auto& handler : _backupHandlers) {
- handler->recoverBackup(backupName, zip);
+ bool success;
+ QString errorStr;
+ std::tie(success, errorStr) = handler->recoverBackup(backupName, zip, username, sourceFilename);
+ if (!success) {
+ if (!rollingBack) {
+ _recoveryError = errorStr;
+ }
+ return false;
+ }
}
qDebug() << "Successfully started recovering from " << backupName;
@@ -294,7 +304,7 @@ bool DomainContentBackupManager::recoverFromBackupZip(const QString& backupName,
}
}
-void DomainContentBackupManager::recoverFromBackup(MiniPromise::Promise promise, const QString& backupName) {
+void DomainContentBackupManager::recoverFromBackup(MiniPromise::Promise promise, const QString& backupName, const QString& username) {
if (_isRecovering) {
promise->resolve({
{ "success", false }
@@ -304,12 +314,12 @@ void DomainContentBackupManager::recoverFromBackup(MiniPromise::Promise promise,
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "recoverFromBackup", Q_ARG(MiniPromise::Promise, promise),
- Q_ARG(const QString&, backupName));
+ Q_ARG(const QString&, backupName), Q_ARG(const QString&, username));
return;
}
qDebug() << "Recovering from" << backupName;
-
+ _recoveryError = "";
bool success { false };
QDir backupDir { _backupDirectory };
auto backupFilePath { backupDir.filePath(backupName) };
@@ -317,7 +327,7 @@ void DomainContentBackupManager::recoverFromBackup(MiniPromise::Promise promise,
if (backupFile.open(QIODevice::ReadOnly)) {
QuaZip zip { &backupFile };
- success = recoverFromBackupZip(backupName, zip);
+ success = recoverFromBackupZip(backupName, zip, username, backupName);
backupFile.close();
} else {
@@ -330,11 +340,11 @@ void DomainContentBackupManager::recoverFromBackup(MiniPromise::Promise promise,
});
}
-void DomainContentBackupManager::recoverFromUploadedBackup(MiniPromise::Promise promise, QByteArray uploadedBackup) {
+void DomainContentBackupManager::recoverFromUploadedBackup(MiniPromise::Promise promise, QByteArray uploadedBackup, QString username) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "recoverFromUploadedBackup", Q_ARG(MiniPromise::Promise, promise),
- Q_ARG(QByteArray, uploadedBackup));
+ Q_ARG(QByteArray, uploadedBackup), Q_ARG(QString, username));
return;
}
@@ -345,29 +355,51 @@ void DomainContentBackupManager::recoverFromUploadedBackup(MiniPromise::Promise
QuaZip uploadedZip { &uploadedBackupBuffer };
QString backupName = MANUAL_BACKUP_PREFIX + "uploaded.zip";
- bool success = recoverFromBackupZip(backupName, uploadedZip);
+ bool success = recoverFromBackupZip(backupName, uploadedZip, username, QString());
promise->resolve({
{ "success", success }
});
}
-void DomainContentBackupManager::recoverFromUploadedFile(MiniPromise::Promise promise, QString uploadedFilename) {
+void DomainContentBackupManager::recoverFromUploadedFile(MiniPromise::Promise promise, QString uploadedFilename, const QString username, QString sourceFilename) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "recoverFromUploadedFile", Q_ARG(MiniPromise::Promise, promise),
- Q_ARG(QString, uploadedFilename));
+ Q_ARG(QString, uploadedFilename), Q_ARG(QString, username), Q_ARG(QString, sourceFilename));
return;
}
- qDebug() << "Recovering from uploaded file -" << uploadedFilename;
+ qDebug() << "Recovering from uploaded file -" << uploadedFilename << "source" << sourceFilename;
+ bool success;
+ QString path;
+ std::tie(success, path) = createBackup(AUTOMATIC_BACKUP_PREFIX, PRE_UPLOAD_SUFFIX);
+ if(!success) {
+ _recoveryError = "Failed to create backup for " + PRE_UPLOAD_SUFFIX + " at " + path;
+ qCWarning(domain_server) << _recoveryError;
+ } else {
+ QFile uploadedFile(uploadedFilename);
+ QuaZip uploadedZip { &uploadedFile };
- QFile uploadedFile(uploadedFilename);
- QuaZip uploadedZip { &uploadedFile };
+ QString backupName = MANUAL_BACKUP_PREFIX + "uploaded.zip";
- QString backupName = MANUAL_BACKUP_PREFIX + "uploaded.zip";
+ bool success = recoverFromBackupZip(backupName, uploadedZip, username, sourceFilename);
- bool success = recoverFromBackupZip(backupName, uploadedZip);
+ if (!success) {
+ // attempt to rollback to
+ QString filename;
+ QDateTime filetime;
+ if (getMostRecentBackup(PRE_UPLOAD_SUFFIX, filename, filetime)) {
+ QFile uploadedFile(uploadedFilename);
+ QuaZip uploadedZip { &uploadedFile };
+
+ QString backupName = MANUAL_BACKUP_PREFIX + "uploaded.zip";
+ recoverFromBackupZip(backupName, uploadedZip, username, sourceFilename, true);
+
+ }
+ }
+
+ }
promise->resolve({
{ "success", success }
});
@@ -455,9 +487,44 @@ void DomainContentBackupManager::getAllBackupsAndStatus(MiniPromise::Promise pro
{ "recoveryProgress", recoveryProgress }
};
+ if(!_recoveryError.isEmpty()) {
+ status["recoveryError"] = _recoveryError;
+ }
+
+
+ QString filename = _settingsManager.valueForKeyPath(CONTENT_SETTINGS_INSTALLED_CONTENT_FILENAME).toString();
+ QString name = _settingsManager.valueForKeyPath(CONTENT_SETTINGS_INSTALLED_CONTENT_NAME).toString();
+ auto creationTime = _settingsManager.valueForKeyPath(CONTENT_SETTINGS_INSTALLED_CONTENT_CREATION_TIME).toULongLong();
+
+ if (name.isEmpty() || creationTime == 0) {
+ QString prefixFormat = "(" + QRegExp::escape(AUTOMATIC_BACKUP_PREFIX) + "|" + QRegExp::escape(MANUAL_BACKUP_PREFIX) + ")";
+ QString nameFormat = "(.+)";
+ QString dateTimeFormat = "(" + DATETIME_FORMAT_RE + ")";
+ QRegExp backupNameFormat { prefixFormat + nameFormat + "-" + dateTimeFormat + "\\.zip" };
+
+
+ if (backupNameFormat.exactMatch(filename)) {
+ if (name.isEmpty()) {
+ name = backupNameFormat.cap(2);
+ }
+ if (creationTime == 0) {
+ auto dateTime = backupNameFormat.cap(3);
+ creationTime = QDateTime::fromString(dateTime, DATETIME_FORMAT).toMSecsSinceEpoch();
+ }
+ }
+ }
+
+ QVariantMap currentArchive;
+ currentArchive["filename"] = filename;
+ currentArchive["name"] = name;
+ currentArchive["creation_time"] = creationTime;
+ currentArchive["install_time"] = _settingsManager.valueForKeyPath(CONTENT_SETTINGS_INSTALLED_CONTENT_INSTALL_TIME).toULongLong();
+ currentArchive["installed_by"] = _settingsManager.valueForKeyPath(CONTENT_SETTINGS_INSTALLED_CONTENT_INSTALLED_BY).toString();
+
QVariantMap info {
{ "backups", variantBackups },
- { "status", status }
+ { "status", status },
+ { "installed_content", currentArchive }
};
promise->resolve(info);
diff --git a/domain-server/src/DomainContentBackupManager.h b/domain-server/src/DomainContentBackupManager.h
index 4af3ae5bfd..f2ee71d498 100644
--- a/domain-server/src/DomainContentBackupManager.h
+++ b/domain-server/src/DomainContentBackupManager.h
@@ -28,11 +28,22 @@
#include
#include "BackupHandler.h"
+#include "DomainServerSettingsManager.h"
#include
#include
+const QString DATETIME_FORMAT_RE { "\\d{4}-\\d{2}-\\d{2}_\\d{2}-\\d{2}-\\d{2}" };
+const QString AUTOMATIC_BACKUP_PREFIX { "autobackup-" };
+const QString MANUAL_BACKUP_PREFIX { "backup-" };
+const QString INSTALLED_CONTENT = "installed_content";
+const QString INSTALLED_CONTENT_FILENAME = "filename";
+const QString INSTALLED_CONTENT_NAME = "name";
+const QString INSTALLED_CONTENT_CREATION_TIME = "creation_time";
+const QString INSTALLED_CONTENT_INSTALL_TIME = "install_time";
+const QString INSTALLED_CONTENT_INSTALLED_BY = "installed_by";
+
struct BackupItemInfo {
BackupItemInfo(QString pId, QString pName, QString pAbsolutePath, QDateTime pCreatedAt, bool pIsManualBackup) :
id(pId), name(pName), absolutePath(pAbsolutePath), createdAt(pCreatedAt), isManualBackup(pIsManualBackup) { };
@@ -71,7 +82,7 @@ public:
static const std::chrono::seconds DEFAULT_PERSIST_INTERVAL;
DomainContentBackupManager(const QString& rootBackupDirectory,
- const QVariantList& settings,
+ DomainServerSettingsManager& domainServerSettingsManager,
std::chrono::milliseconds persistInterval = DEFAULT_PERSIST_INTERVAL,
bool debugTimestampNow = false);
@@ -84,9 +95,9 @@ public:
public slots:
void getAllBackupsAndStatus(MiniPromise::Promise promise);
void createManualBackup(MiniPromise::Promise promise, const QString& name);
- void recoverFromBackup(MiniPromise::Promise promise, const QString& backupName);
- void recoverFromUploadedBackup(MiniPromise::Promise promise, QByteArray uploadedBackup);
- void recoverFromUploadedFile(MiniPromise::Promise promise, QString uploadedFilename);
+ void recoverFromBackup(MiniPromise::Promise promise, const QString& backupName, const QString& username);
+ void recoverFromUploadedBackup(MiniPromise::Promise promise, QByteArray uploadedBackup, QString username);
+ void recoverFromUploadedFile(MiniPromise::Promise promise, QString uploadedFilename, QString username, QString sourceFilename);
void deleteBackup(MiniPromise::Promise promise, const QString& backupName);
signals:
@@ -108,13 +119,15 @@ protected:
std::pair createBackup(const QString& prefix, const QString& name);
- bool recoverFromBackupZip(const QString& backupName, QuaZip& backupZip);
+ bool recoverFromBackupZip(const QString& backupName, QuaZip& backupZip, const QString& username, const QString& sourceFilename, bool rollingBack = false);
private slots:
void removeOldConsolidatedBackups();
void consolidateBackupInternal(QString fileName);
private:
+ DomainServerSettingsManager& _settingsManager;
+
QTimer _consolidatedBackupCleanupTimer;
const QString _consolidatedBackupDirectory;
@@ -126,6 +139,7 @@ private:
std::unordered_map _consolidatedBackups;
std::atomic _isRecovering { false };
+ QString _recoveryError;
QString _recoveryFilename { };
p_high_resolution_clock::time_point _lastCheck;
diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp
index b7c723ab48..09438b31bc 100644
--- a/domain-server/src/DomainServer.cpp
+++ b/domain-server/src/DomainServer.cpp
@@ -307,11 +307,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
}
maybeHandleReplacementEntityFile();
-
- static const QString BACKUP_RULES_KEYPATH = AUTOMATIC_CONTENT_ARCHIVES_GROUP + ".backup_rules";
- auto backupRulesVariant = _settingsManager.valueOrDefaultValueForKeyPath(BACKUP_RULES_KEYPATH);
-
- _contentManager.reset(new DomainContentBackupManager(getContentBackupDir(), backupRulesVariant.toList()));
+ _contentManager.reset(new DomainContentBackupManager(getContentBackupDir(), _settingsManager));
connect(_contentManager.get(), &DomainContentBackupManager::started, _contentManager.get(), [this](){
_contentManager->addBackupHandler(BackupHandlerPointer(new EntitiesBackupHandler(getEntitiesFilePath(), getEntitiesReplacementFilePath())));
@@ -1961,6 +1957,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
QPointer connectionPtr { connection };
auto nodeList = DependencyManager::get();
+ QString username;
auto getSetting = [this](QString keyPath, QVariant& value) -> bool {
@@ -2028,7 +2025,9 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
}
// all requests below require a cookie to prove authentication so check that first
- if (!isAuthenticatedRequest(connection, url)) {
+ bool isAuthenticated { false };
+ std::tie(isAuthenticated, username) = isAuthenticatedRequest(connection);
+ if (!isAuthenticated) {
// this is not an authenticated request
// return true from the handler since it was handled with a 401 or re-direct to auth
return true;
@@ -2194,7 +2193,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
return true;
} else if (url.path() == URI_RESTART) {
- connection->respond(HTTPConnection::StatusCode200);
+ connection->respond(HTTPConnection::StatusCode204);
restart();
return true;
} else if (url.path() == URI_API_METAVERSE_INFO) {
@@ -2333,8 +2332,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
QJsonObject rootJSON;
auto success = result["success"].toBool();
- rootJSON["success"] = success;
- QJsonDocument docJSON(rootJSON);
+ QJsonDocument docJSON(QJsonObject::fromVariantMap(result));
connectionPtr->respond(success ? HTTPConnection::StatusCode200 : HTTPConnection::StatusCode400, docJSON.toJson(),
JSON_MIME_TYPE.toUtf8());
});
@@ -2362,12 +2360,11 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
QJsonObject rootJSON;
auto success = result["success"].toBool();
- rootJSON["success"] = success;
- QJsonDocument docJSON(rootJSON);
+ QJsonDocument docJSON(QJsonObject::fromVariantMap(result));
connectionPtr->respond(success ? HTTPConnection::StatusCode200 : HTTPConnection::StatusCode400, docJSON.toJson(),
JSON_MIME_TYPE.toUtf8());
});
- _contentManager->recoverFromBackup(deferred, id);
+ _contentManager->recoverFromBackup(deferred, id, username);
return true;
}
} else if (connection->requestOperation() == QNetworkAccessManager::PutOperation) {
@@ -2467,8 +2464,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
QJsonObject rootJSON;
auto success = result["success"].toBool();
- rootJSON["success"] = success;
- QJsonDocument docJSON(rootJSON);
+ QJsonDocument docJSON(QJsonObject::fromVariantMap(result));
connectionPtr->respond(success ? HTTPConnection::StatusCode200 : HTTPConnection::StatusCode400, docJSON.toJson(),
JSON_MIME_TYPE.toUtf8());
});
@@ -2564,6 +2560,9 @@ bool DomainServer::processPendingContent(HTTPConnection* connection, QString ite
int sessionId = sessionIdBytes.toInt();
bool newUpload = itemName == "restore-file" || itemName == "restore-file-chunk-initial" || itemName == "restore-file-chunk-only";
+ bool isAuthenticated;
+ QString username;
+ std::tie(isAuthenticated, username) = isAuthenticatedRequest(connection);
if (filename.endsWith(".zip", Qt::CaseInsensitive)) {
static const QString TEMPORARY_CONTENT_FILEPATH { QDir::tempPath() + "/hifiUploadContent_XXXXXX.zip" };
@@ -2590,7 +2589,7 @@ bool DomainServer::processPendingContent(HTTPConnection* connection, QString ite
_pendingFileContent.close();
// Respond immediately - will timeout if we wait for restore.
- connection->respond(HTTPConnection::StatusCode200);
+ connection->respond(HTTPConnection::StatusCode204);
if (itemName == "restore-file" || itemName == "restore-file-chunk-final" || itemName == "restore-file-chunk-only") {
auto deferred = makePromise("recoverFromUploadedBackup");
@@ -2598,7 +2597,7 @@ bool DomainServer::processPendingContent(HTTPConnection* connection, QString ite
_pendingContentFiles.erase(sessionId);
});
- _contentManager->recoverFromUploadedFile(deferred, _pendingFileContent.fileName());
+ _contentManager->recoverFromUploadedFile(deferred, _pendingFileContent.fileName(), username, filename);
}
} else if (filename.endsWith(".json", Qt::CaseInsensitive)
|| filename.endsWith(".json.gz", Qt::CaseInsensitive)) {
@@ -2608,14 +2607,16 @@ bool DomainServer::processPendingContent(HTTPConnection* connection, QString ite
}
QByteArray& _pendingUploadedContent = _pendingUploadedContents[sessionId];
_pendingUploadedContent += dataChunk;
- connection->respond(HTTPConnection::StatusCode200);
if (itemName == "restore-file" || itemName == "restore-file-chunk-final" || itemName == "restore-file-chunk-only") {
// invoke our method to hand the new octree file off to the octree server
- QMetaObject::invokeMethod(this, "handleOctreeFileReplacement",
- Qt::QueuedConnection, Q_ARG(QByteArray, _pendingUploadedContent));
+ if (!handleOctreeFileReplacement(_pendingUploadedContent, filename, QString(), username)) {
+ connection->respond(HTTPConnection::StatusCode400);
+ return false;
+ }
_pendingUploadedContents.erase(sessionId);
}
+ connection->respond(HTTPConnection::StatusCode204);
} else {
connection->respond(HTTPConnection::StatusCode400);
return false;
@@ -2685,13 +2686,14 @@ void DomainServer::profileRequestFinished() {
}
}
-bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl& url) {
+std::pair DomainServer::isAuthenticatedRequest(HTTPConnection* connection) {
- const QByteArray HTTP_COOKIE_HEADER_KEY = "Cookie";
- const QString ADMIN_USERS_CONFIG_KEY = "admin-users";
- const QString ADMIN_ROLES_CONFIG_KEY = "admin-roles";
- const QString BASIC_AUTH_USERNAME_KEY_PATH = "security.http_username";
- const QString BASIC_AUTH_PASSWORD_KEY_PATH = "security.http_password";
+ static const QByteArray HTTP_COOKIE_HEADER_KEY = "Cookie";
+ static const QString ADMIN_USERS_CONFIG_KEY = "admin-users";
+ static const QString ADMIN_ROLES_CONFIG_KEY = "admin-roles";
+ static const QString BASIC_AUTH_USERNAME_KEY_PATH = "security.http_username";
+ static const QString BASIC_AUTH_PASSWORD_KEY_PATH = "security.http_password";
+ const QString COOKIE_UUID_REGEX_STRING = HIFI_SESSION_COOKIE_KEY + "=([\\d\\w-]+)($|;)";
const QByteArray UNAUTHENTICATED_BODY = "You do not have permission to access this domain-server.";
@@ -2702,7 +2704,6 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
&& (adminUsersVariant.isValid() || adminRolesVariant.isValid())) {
QString cookieString = connection->requestHeader(HTTP_COOKIE_HEADER_KEY);
- const QString COOKIE_UUID_REGEX_STRING = HIFI_SESSION_COOKIE_KEY + "=([\\d\\w-]+)($|;)";
QRegExp cookieUUIDRegex(COOKIE_UUID_REGEX_STRING);
QUuid cookieUUID;
@@ -2722,7 +2723,7 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
if (_settingsManager.valueForKeyPath(ADMIN_USERS_CONFIG_KEY).toStringList().contains(profileUsername)) {
// this is an authenticated user
- return true;
+ return { true, profileUsername };
}
// loop the roles of this user and see if they are in the admin-roles array
@@ -2732,7 +2733,7 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
foreach(const QString& userRole, sessionData.getRoles()) {
if (adminRolesArray.contains(userRole)) {
// this user has a role that allows them to administer the domain-server
- return true;
+ return { true, profileUsername };
}
}
}
@@ -2740,7 +2741,7 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
connection->respond(HTTPConnection::StatusCode401, UNAUTHENTICATED_BODY);
// the user does not have allowed username or role, return 401
- return false;
+ return { false, QString() };
} else {
static const QByteArray REQUESTED_WITH_HEADER = "X-Requested-With";
static const QString XML_REQUESTED_WITH = "XMLHttpRequest";
@@ -2769,7 +2770,7 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
}
// we don't know about this user yet, so they are not yet authenticated
- return false;
+ return { false, QString() };
}
} else if (_settingsManager.valueForKeyPath(BASIC_AUTH_USERNAME_KEY_PATH).isValid()) {
// config file contains username and password combinations for basic auth
@@ -2798,7 +2799,7 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
"" : QCryptographicHash::hash(headerPassword.toUtf8(), QCryptographicHash::Sha256).toHex();
if (settingsUsername == headerUsername && hexHeaderPassword == settingsPassword) {
- return true;
+ return { true, headerUsername };
}
}
}
@@ -2820,11 +2821,11 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
HTTPConnection::DefaultContentType, basicAuthHeader);
// not authenticated, bubble up false
- return false;
+ return { false, QString() };
} else {
// we don't have an OAuth URL + admin roles/usernames, so all users are authenticated
- return true;
+ return { true, QString() };
}
}
@@ -3498,7 +3499,7 @@ void DomainServer::maybeHandleReplacementEntityFile() {
}
}
-void DomainServer::handleOctreeFileReplacement(QByteArray octreeFile) {
+bool DomainServer::handleOctreeFileReplacement(QByteArray octreeFile, QString sourceFilename, QString name, QString username) {
OctreeUtils::RawEntityData data;
if (data.readOctreeDataInfoFromData(octreeFile)) {
data.resetIdAndVersion();
@@ -3514,19 +3515,41 @@ void DomainServer::handleOctreeFileReplacement(QByteArray octreeFile) {
// process it when it comes back up
qInfo() << "Wrote octree replacement file to" << replacementFilePath << "- stopping server";
+ QJsonObject installed_content {
+ { INSTALLED_CONTENT_FILENAME, sourceFilename },
+ { INSTALLED_CONTENT_NAME, name },
+ { INSTALLED_CONTENT_CREATION_TIME, 0 },
+ { INSTALLED_CONTENT_INSTALL_TIME, QDateTime::currentDateTime().currentMSecsSinceEpoch() },
+ { INSTALLED_CONTENT_INSTALLED_BY, username }
+ };
+
+ QJsonObject jsonObject { { INSTALLED_CONTENT, installed_content } };
+
+ _settingsManager.recurseJSONObjectAndOverwriteSettings(jsonObject, ContentSettings);
+
QMetaObject::invokeMethod(this, "restart", Qt::QueuedConnection);
+ return true;
} else {
qWarning() << "Could not write replacement octree data to file - refusing to process";
+ return false;
}
} else {
qDebug() << "Received replacement octree file that is invalid - refusing to process";
+ return false;
}
}
+static const QString CONTENT_SET_NAME_QUERY_PARAM = "name";
+
void DomainServer::handleDomainContentReplacementFromURLRequest(QSharedPointer message) {
qInfo() << "Received request to replace content from a url";
auto node = DependencyManager::get()->findNodeWithAddr(message->getSenderSockAddr());
if (node && node->getCanReplaceContent()) {
+ DomainServerNodeData* nodeData = static_cast(node->getLinkedData());
+ QString username;
+ if (nodeData) {
+ username = nodeData->getUsername();
+ }
// Convert message data into our URL
QString url(message->getMessage());
QUrl modelsURL = QUrl(url, QUrl::StrictMode);
@@ -3534,16 +3557,19 @@ void DomainServer::handleDomainContentReplacementFromURLRequest(QSharedPointererror();
if (networkError == QNetworkReply::NoError) {
if (modelsURL.fileName().endsWith(".json.gz")) {
- handleOctreeFileReplacement(reply->readAll());
+ QUrlQuery urlQuery(modelsURL.query(QUrl::FullyEncoded));
+
+ QString itemName = urlQuery.queryItemValue(CONTENT_SET_NAME_QUERY_PARAM);
+ handleOctreeFileReplacement(reply->readAll(), modelsURL.fileName(), itemName, username);
} else if (modelsURL.fileName().endsWith(".zip")) {
auto deferred = makePromise("recoverFromUploadedBackup");
- _contentManager->recoverFromUploadedBackup(deferred, reply->readAll());
+ _contentManager->recoverFromUploadedBackup(deferred, reply->readAll(), username);
}
} else {
qDebug() << "Error downloading JSON from specified file: " << modelsURL;
@@ -3554,7 +3580,12 @@ void DomainServer::handleDomainContentReplacementFromURLRequest(QSharedPointer message) {
auto node = DependencyManager::get()->nodeWithLocalID(message->getSourceID());
- if (node->getCanReplaceContent()) {
- handleOctreeFileReplacement(message->readAll());
+ if (node && node->getCanReplaceContent()) {
+ QString username;
+ DomainServerNodeData* nodeData = static_cast(node->getLinkedData());
+ if (nodeData) {
+ username = nodeData->getUsername();
+ }
+ handleOctreeFileReplacement(message->readAll(), QString(), QString(), username);
}
}
diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h
index 54b7fbe466..02362abd7b 100644
--- a/domain-server/src/DomainServer.h
+++ b/domain-server/src/DomainServer.h
@@ -99,7 +99,7 @@ private slots:
void handleDomainContentReplacementFromURLRequest(QSharedPointer message);
void handleOctreeFileReplacementRequest(QSharedPointer message);
- void handleOctreeFileReplacement(QByteArray octreeFile);
+ bool handleOctreeFileReplacement(QByteArray octreeFile, QString sourceFilename, QString name, QString username);
void processOctreeDataRequestMessage(QSharedPointer message);
void processOctreeDataPersistMessage(QSharedPointer message);
@@ -194,7 +194,7 @@ private:
QUrl oauthRedirectURL();
QUrl oauthAuthorizationURL(const QUuid& stateUUID = QUuid::createUuid());
- bool isAuthenticatedRequest(HTTPConnection* connection, const QUrl& url);
+ std::pair isAuthenticatedRequest(HTTPConnection* connection);
QNetworkReply* profileRequestGivenTokenReply(QNetworkReply* tokenReply);
Headers setupCookieHeadersFromProfileReply(QNetworkReply* profileReply);
diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h
index 2020561205..e28b9f6cd1 100644
--- a/domain-server/src/DomainServerSettingsManager.h
+++ b/domain-server/src/DomainServerSettingsManager.h
@@ -35,6 +35,11 @@ const QString MACHINE_FINGERPRINT_PERMISSIONS_KEYPATH = "security.machine_finger
const QString GROUP_PERMISSIONS_KEYPATH = "security.group_permissions";
const QString GROUP_FORBIDDENS_KEYPATH = "security.group_forbiddens";
const QString AUTOMATIC_CONTENT_ARCHIVES_GROUP = "automatic_content_archives";
+const QString CONTENT_SETTINGS_INSTALLED_CONTENT_FILENAME = "installed_content.filename";
+const QString CONTENT_SETTINGS_INSTALLED_CONTENT_NAME = "installed_content.name";
+const QString CONTENT_SETTINGS_INSTALLED_CONTENT_CREATION_TIME = "installed_content.creation_time";
+const QString CONTENT_SETTINGS_INSTALLED_CONTENT_INSTALL_TIME = "installed_content.install_time";
+const QString CONTENT_SETTINGS_INSTALLED_CONTENT_INSTALLED_BY = "installed_content.installed_by";
using GroupByUUIDKey = QPair; // groupID, rankID
diff --git a/domain-server/src/EntitiesBackupHandler.cpp b/domain-server/src/EntitiesBackupHandler.cpp
index 90a066036d..e7e8b5a90d 100644
--- a/domain-server/src/EntitiesBackupHandler.cpp
+++ b/domain-server/src/EntitiesBackupHandler.cpp
@@ -57,36 +57,41 @@ void EntitiesBackupHandler::createBackup(const QString& backupName, QuaZip& zip)
}
}
-void EntitiesBackupHandler::recoverBackup(const QString& backupName, QuaZip& zip) {
+std::pair EntitiesBackupHandler::recoverBackup(const QString& backupName, QuaZip& zip, const QString& username, const QString& sourceFilename) {
if (!zip.setCurrentFile(ENTITIES_BACKUP_FILENAME)) {
- qWarning() << "Failed to find" << ENTITIES_BACKUP_FILENAME << "while recovering backup";
- return;
+ QString errorStr("Failed to find " + ENTITIES_BACKUP_FILENAME + " while recovering backup");
+ qWarning() << errorStr;
+ return { false, errorStr };
}
QuaZipFile zipFile { &zip };
if (!zipFile.open(QIODevice::ReadOnly)) {
- qCritical() << "Failed to open" << ENTITIES_BACKUP_FILENAME << "in backup";
- return;
+ QString errorStr("Failed to open " + ENTITIES_BACKUP_FILENAME + " in backup");
+ qCritical() << errorStr;
+ return { false, errorStr };
}
auto rawData = zipFile.readAll();
zipFile.close();
+ if (zipFile.getZipError() != UNZ_OK) {
+ QString errorStr("Failed to unzip " + ENTITIES_BACKUP_FILENAME + ": " + zipFile.getZipError());
+ qCritical() << errorStr;
+ return { false, errorStr };
+ }
+
OctreeUtils::RawEntityData data;
if (!data.readOctreeDataInfoFromData(rawData)) {
- qCritical() << "Unable to parse octree data during backup recovery";
- return;
+ QString errorStr("Unable to parse octree data during backup recovery");
+ qCritical() << errorStr;
+ return { false, errorStr };
}
data.resetIdAndVersion();
- if (zipFile.getZipError() != UNZ_OK) {
- qCritical().nospace() << "Failed to unzip " << ENTITIES_BACKUP_FILENAME << ": " << zipFile.getZipError();
- return;
- }
-
QFile entitiesFile { _entitiesReplacementFilePath };
if (entitiesFile.open(QIODevice::WriteOnly)) {
entitiesFile.write(data.toGzippedByteArray());
}
+ return { true, QString() };
}
diff --git a/domain-server/src/EntitiesBackupHandler.h b/domain-server/src/EntitiesBackupHandler.h
index d95ad695a8..1cdfdd89ed 100644
--- a/domain-server/src/EntitiesBackupHandler.h
+++ b/domain-server/src/EntitiesBackupHandler.h
@@ -29,7 +29,7 @@ public:
void createBackup(const QString& backupName, QuaZip& zip) override;
// Recover from a full backup
- void recoverBackup(const QString& backupName, QuaZip& zip) override;
+ std::pair recoverBackup(const QString& backupName, QuaZip& zip, const QString& username, const QString& sourceFilename) override;
// Delete a skeleton backup
void deleteBackup(const QString& backupName) override {}
diff --git a/hifi_android.py b/hifi_android.py
index 42b472e960..0c2ea07cc7 100644
--- a/hifi_android.py
+++ b/hifi_android.py
@@ -94,6 +94,10 @@ ANDROID_PACKAGES = {
'checksum': 'ddcb23df336b08017042ba4786db1d9e',
'sharedLibFolder': 'lib',
'includeLibs': {'libbreakpad_client.a'}
+ },
+ 'webrtc': {
+ 'file': 'webrtc-20190626-android.tar.gz',
+ 'checksum': 'e2dccd3d8efdcba6d428c87ba7fb2a53'
}
}
diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml
index af517be55d..9db19e72e2 100644
--- a/interface/resources/qml/hifi/audio/Audio.qml
+++ b/interface/resources/qml/hifi/audio/Audio.qml
@@ -166,16 +166,16 @@ Rectangle {
x: 2 * margins.paddings;
width: parent.width;
// switch heights + 2 * top margins
- height: (root.switchHeight) * 3 + 48;
+ height: (root.switchHeight) * 6 + 48;
anchors.top: firstSeparator.bottom;
anchors.topMargin: 10;
- // mute is in its own row
Item {
id: switchContainer;
x: margins.paddings;
width: parent.width / 2;
height: parent.height;
+ anchors.top: parent.top
anchors.left: parent.left;
HifiControlsUit.Switch {
id: muteMic;
@@ -222,12 +222,29 @@ Rectangle {
}
HifiControlsUit.Switch {
- id: pttSwitch
+ id: acousticEchoCancellationSwitch;
height: root.switchHeight;
switchWidth: root.switchWidth;
anchors.top: noiseReductionSwitch.bottom
anchors.topMargin: 24
anchors.left: parent.left
+ labelTextOn: "Echo Cancellation";
+ labelTextSize: 16;
+ backgroundOnColor: "#E3E3E3";
+ checked: AudioScriptingInterface.acousticEchoCancellation;
+ onCheckedChanged: {
+ AudioScriptingInterface.acousticEchoCancellation = checked;
+ checked = Qt.binding(function() { return AudioScriptingInterface.acousticEchoCancellation; });
+ }
+ }
+
+ HifiControlsUit.Switch {
+ id: pttSwitch
+ height: root.switchHeight;
+ switchWidth: root.switchWidth;
+ anchors.top: acousticEchoCancellationSwitch.bottom;
+ anchors.topMargin: 24
+ anchors.left: parent.left
labelTextOn: (bar.currentIndex === 0) ? qsTr("Push To Talk (T)") : qsTr("Push To Talk");
labelTextSize: 16;
backgroundOnColor: "#E3E3E3";
@@ -298,7 +315,6 @@ Rectangle {
checked = Qt.binding(function() { return AudioScriptingInterface.isStereoInput; }); // restore binding
}
}
-
}
}
diff --git a/interface/resources/qml/hifi/audio/MicBarApplication.qml b/interface/resources/qml/hifi/audio/MicBarApplication.qml
index 3070ba3bbd..76551be906 100644
--- a/interface/resources/qml/hifi/audio/MicBarApplication.qml
+++ b/interface/resources/qml/hifi/audio/MicBarApplication.qml
@@ -39,8 +39,8 @@ Rectangle {
}
Component.onCompleted: {
- AudioScriptingInterface.noiseGateOpened.connect(function() { root.gated = false; });
- AudioScriptingInterface.noiseGateClosed.connect(function() { root.gated = true; });
+ AudioScriptingInterface.noiseGateOpened.connect(function() { micBar.gated = false; });
+ AudioScriptingInterface.noiseGateClosed.connect(function() { micBar.gated = true; });
HMD.displayModeChanged.connect(function() {
muted = AudioScriptingInterface.muted;
pushToTalk = AudioScriptingInterface.pushToTalk;
@@ -151,7 +151,7 @@ Rectangle {
readonly property string yellow: "#C0C000";
readonly property string fill: "#55000000";
readonly property string border: standalone ? "#80FFFFFF" : "#55FFFFFF";
- readonly property string icon: (muted || clipping) ? mutedColor : root.gated ? gatedColor : unmutedColor;
+ readonly property string icon: (muted || clipping) ? mutedColor : micBar.gated ? gatedColor : unmutedColor;
}
Item {
@@ -169,7 +169,7 @@ Rectangle {
Image {
id: image;
source: (pushToTalk) ? pushToTalkIcon : muted ? mutedIcon :
- clipping ? clippingIcon : root.gated ? gatedIcon : unmutedIcon;
+ clipping ? clippingIcon : micBar.gated ? gatedIcon : unmutedIcon;
width: 29;
height: 32;
anchors {
diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml
index 3ace6f381f..41ac9232e2 100644
--- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml
+++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml
@@ -786,7 +786,7 @@ Rectangle {
}
lightboxPopup.button2text = "CONFIRM";
lightboxPopup.button2method = function() {
- Commerce.replaceContentSet(root.itemHref, root.certificateId);
+ Commerce.replaceContentSet(root.itemHref, root.certificateId, root.itemName);
lightboxPopup.visible = false;
rezzedNotifContainer.visible = true;
rezzedNotifContainerTimer.start();
diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml
index 966f359e92..157f3dda6a 100644
--- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml
+++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml
@@ -36,7 +36,7 @@ Rectangle {
break;
case "content set":
urlHandler.handleUrl("hifi://localhost/0,0,0");
- Commerce.replaceContentSet(toUrl(resource), "");
+ Commerce.replaceContentSet(toUrl(resource), "", "");
break;
case "entity":
case "wearable":
diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml
index 0e3402a6a9..1dbcd34cce 100644
--- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml
+++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml
@@ -729,7 +729,7 @@ Item {
onClicked: {
Tablet.playSound(TabletEnums.ButtonClick);
if (root.itemType === "contentSet") {
- sendToPurchases({method: 'showReplaceContentLightbox', itemHref: root.itemHref, certID: root.certificateId});
+ sendToPurchases({method: 'showReplaceContentLightbox', itemHref: root.itemHref, certID: root.certificateId, itemName: root.itemName});
} else if (root.itemType === "avatar") {
sendToPurchases({method: 'showChangeAvatarLightbox', itemName: root.itemName, itemHref: root.itemHref});
} else if (root.itemType === "app") {
diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml
index 9a2bf62e08..03fbbb178e 100644
--- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml
+++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml
@@ -609,7 +609,7 @@ Rectangle {
}
lightboxPopup.button2text = "CONFIRM";
lightboxPopup.button2method = function() {
- Commerce.replaceContentSet(msg.itemHref, msg.certID);
+ Commerce.replaceContentSet(msg.itemHref, msg.certID, msg.itemName);
lightboxPopup.visible = false;
};
lightboxPopup.visible = true;
diff --git a/interface/resources/qml/hifi/simplifiedUI/settingsApp/audio/Audio.qml b/interface/resources/qml/hifi/simplifiedUI/settingsApp/audio/Audio.qml
index 8827bb3834..bfc0bc5200 100644
--- a/interface/resources/qml/hifi/simplifiedUI/settingsApp/audio/Audio.qml
+++ b/interface/resources/qml/hifi/simplifiedUI/settingsApp/audio/Audio.qml
@@ -222,6 +222,17 @@ Flickable {
}
}
}
+
+ SimplifiedControls.Switch {
+ id: acousticEchoCancellationSwitch
+ Layout.preferredHeight: 18
+ Layout.preferredWidth: parent.width
+ labelTextOn: "Acoustic Echo Cancellation"
+ checked: AudioScriptingInterface.acousticEchoCancellation
+ onClicked: {
+ AudioScriptingInterface.acousticEchoCancellation = !AudioScriptingInterface.acousticEchoCancellation;
+ }
+ }
}
}
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 1872a03221..8789fcde1c 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -1225,8 +1225,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
#endif
bool isStore = property(hifi::properties::OCULUS_STORE).toBool();
-
- DependencyManager::get()->setLimitedCommerce(isStore); // Or we could make it a separate arg, or if either arg is set, etc. And should this instead by a hifi::properties?
+ // Or we could make it a separate arg, or if either arg is set, etc. And should this instead by a hifi::properties?
+ DependencyManager::get()->setLimitedCommerce(isStore || property(hifi::properties::STEAM).toBool());
updateHeartbeat();
@@ -1659,7 +1659,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
// The value will be 0 if the user blew away settings this session, which is both a feature and a bug.
static const QString TESTER = "HIFI_TESTER";
auto gpuIdent = GPUIdent::getInstance();
- auto glContextData = getGLContextData();
+ auto glContextData = gl::ContextInfo::get();
QJsonObject properties = {
{ "version", applicationVersion() },
{ "tester", QProcessEnvironment::systemEnvironment().contains(TESTER) || isTester },
@@ -1676,11 +1676,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
{ "gpu_name", gpuIdent->getName() },
{ "gpu_driver", gpuIdent->getDriver() },
{ "gpu_memory", static_cast(gpuIdent->getMemory()) },
- { "gl_version_int", glVersionToInteger(glContextData.value("version").toString()) },
- { "gl_version", glContextData["version"] },
- { "gl_vender", glContextData["vendor"] },
- { "gl_sl_version", glContextData["sl_version"] },
- { "gl_renderer", glContextData["renderer"] },
+ { "gl_version_int", glVersionToInteger(glContextData.version.c_str()) },
+ { "gl_version", glContextData.version.c_str() },
+ { "gl_vender", glContextData.vendor.c_str() },
+ { "gl_sl_version", glContextData.shadingLanguageVersion.c_str() },
+ { "gl_renderer", glContextData.renderer.c_str() },
{ "ideal_thread_count", QThread::idealThreadCount() }
};
auto macVersion = QSysInfo::macVersion();
@@ -2282,8 +2282,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
properties["active_display_plugin"] = getActiveDisplayPlugin()->getName();
properties["using_hmd"] = isHMDMode();
- auto glInfo = getGLContextData();
- properties["gl_info"] = glInfo;
+ auto contextInfo = gl::ContextInfo::get();
+ properties["gl_info"] = QJsonObject{
+ { "version", contextInfo.version.c_str() },
+ { "sl_version", contextInfo.shadingLanguageVersion.c_str() },
+ { "vendor", contextInfo.vendor.c_str() },
+ { "renderer", contextInfo.renderer.c_str() },
+ };
properties["gpu_used_memory"] = (int)BYTES_TO_MB(gpu::Context::getUsedGPUMemSize());
properties["gpu_free_memory"] = (int)BYTES_TO_MB(gpu::Context::getFreeGPUMemSize());
properties["gpu_frame_time"] = (float)(qApp->getGPUContext()->getFrameTimerGPUAverage());
@@ -2996,6 +3001,9 @@ void Application::initializeGL() {
qCWarning(interfaceapp, "Unable to make window context current");
}
+ // Populate the global OpenGL context based on the information for the primary window GL context
+ gl::ContextInfo::get(true);
+
#if !defined(DISABLE_QML)
QStringList chromiumFlags;
// HACK: re-expose mic and camera to prevent crash on domain-change in chromium's media::FakeAudioInputStream::ReadAudioFromSource()
@@ -7789,9 +7797,15 @@ bool Application::askToWearAvatarAttachmentUrl(const QString& url) {
return true;
}
-void Application::replaceDomainContent(const QString& url) {
+static const QString CONTENT_SET_NAME_QUERY_PARAM = "name";
+
+void Application::replaceDomainContent(const QString& url, const QString& itemName) {
qCDebug(interfaceapp) << "Attempting to replace domain content";
- QByteArray urlData(url.toUtf8());
+ QUrl msgUrl(url);
+ QUrlQuery urlQuery(msgUrl.query());
+ urlQuery.addQueryItem(CONTENT_SET_NAME_QUERY_PARAM, itemName);
+ msgUrl.setQuery(urlQuery.query(QUrl::QUrl::FullyEncoded));
+ QByteArray urlData(msgUrl.toString(QUrl::QUrl::FullyEncoded).toUtf8());
auto limitedNodeList = DependencyManager::get();
const auto& domainHandler = limitedNodeList->getDomainHandler();
@@ -7825,7 +7839,7 @@ bool Application::askToReplaceDomainContent(const QString& url) {
QString details;
if (static_cast(answer.toInt()) == QMessageBox::Yes) {
// Given confirmation, send request to domain server to replace content
- replaceDomainContent(url);
+ replaceDomainContent(url, QString());
details = "SuccessfulRequestToReplaceContent";
} else {
details = "UserDeclinedToReplaceContent";
diff --git a/interface/src/Application.h b/interface/src/Application.h
index 1e7180e203..913671473d 100644
--- a/interface/src/Application.h
+++ b/interface/src/Application.h
@@ -326,7 +326,7 @@ public:
bool isInterstitialMode() const { return _interstitialMode; }
bool failedToConnectToEntityServer() const { return _failedToConnectToEntityServer; }
- void replaceDomainContent(const QString& url);
+ void replaceDomainContent(const QString& url, const QString& itemName);
void loadAvatarScripts(const QVector& urls);
void unloadAvatarScripts();
diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp
index 0c9587d3ae..0cf795e35b 100644
--- a/interface/src/LODManager.cpp
+++ b/interface/src/LODManager.cpp
@@ -54,6 +54,7 @@ void LODManager::setRenderTimes(float presentTime, float engineRunTime, float ba
}
void LODManager::autoAdjustLOD(float realTimeDelta) {
+ std::lock_guard { _automaticLODLock };
// The "render time" is the worse of:
// - engineRunTime: Time spent in the render thread in the engine producing the gpu::Frame N
@@ -235,6 +236,7 @@ void LODManager::resetLODAdjust() {
}
void LODManager::setAutomaticLODAdjust(bool value) {
+ std::lock_guard { _automaticLODLock };
_automaticLODAdjust = value;
emit autoLODChanged();
}
@@ -426,7 +428,6 @@ float LODManager::getWorldDetailQuality() const {
return HIGH;
}
-
void LODManager::setLODQualityLevel(float quality) {
_lodQualityLevel = quality;
}
diff --git a/interface/src/LODManager.h b/interface/src/LODManager.h
index 77cb1a0d39..649e0e8e34 100644
--- a/interface/src/LODManager.h
+++ b/interface/src/LODManager.h
@@ -12,6 +12,8 @@
#ifndef hifi_LODManager_h
#define hifi_LODManager_h
+#include
+
#include
#include
#include
@@ -47,11 +49,6 @@ class AABox;
* @property {number} presentTime Read-only.
* @property {number} engineRunTime Read-only.
* @property {number} gpuTime Read-only.
- * @property {number} avgRenderTime Read-only.
- * @property {number} fps Read-only.
- * @property {number} lodLevel Read-only.
- * @property {number} lodDecreaseFPS Read-only.
- * @property {number} lodIncreaseFPS Read-only.
*/
class LODManager : public QObject, public Dependency {
@@ -240,6 +237,7 @@ signals:
private:
LODManager();
+ std::mutex _automaticLODLock;
bool _automaticLODAdjust = true;
float _presentTime{ 0.0f }; // msec
diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp
index 046e697b6d..c8ea044f4b 100644
--- a/interface/src/commerce/QmlCommerce.cpp
+++ b/interface/src/commerce/QmlCommerce.cpp
@@ -259,7 +259,7 @@ void QmlCommerce::authorizeAssetTransfer(const QString& couponID,
ledger->authorizeAssetTransfer(key, couponID, certificateID, amount, optionalMessage);
}
-void QmlCommerce::replaceContentSet(const QString& itemHref, const QString& certificateID) {
+void QmlCommerce::replaceContentSet(const QString& itemHref, const QString& certificateID, const QString& itemName) {
if (!certificateID.isEmpty()) {
auto ledger = DependencyManager::get();
ledger->updateLocation(
@@ -267,7 +267,7 @@ void QmlCommerce::replaceContentSet(const QString& itemHref, const QString& cert
DependencyManager::get()->getPlaceName(),
true);
}
- qApp->replaceDomainContent(itemHref);
+ qApp->replaceDomainContent(itemHref, itemName);
QJsonObject messageProperties = {
{ "status", "SuccessfulRequestToReplaceContent" },
{ "content_set_url", itemHref } };
diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h
index 99b3e32e8b..713a5ee94f 100644
--- a/interface/src/commerce/QmlCommerce.h
+++ b/interface/src/commerce/QmlCommerce.h
@@ -90,7 +90,7 @@ protected:
Q_INVOKABLE void transferAssetToUsername(const QString& username, const QString& certificateID, const int& amount, const QString& optionalMessage);
Q_INVOKABLE void authorizeAssetTransfer(const QString& couponID, const QString& certificateID, const int& amount, const QString& optionalMessage);
- Q_INVOKABLE void replaceContentSet(const QString& itemHref, const QString& certificateID);
+ Q_INVOKABLE void replaceContentSet(const QString& itemHref, const QString& certificateID, const QString& itemName);
Q_INVOKABLE QString getInstalledApps(const QString& justInstalledAppID = "");
Q_INVOKABLE bool installApp(const QString& appHref, const bool& alsoOpenImmediately = false);
diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp
index a0bea256ad..f5569a19b2 100644
--- a/interface/src/scripting/Audio.cpp
+++ b/interface/src/scripting/Audio.cpp
@@ -26,6 +26,7 @@ QString Audio::HMD { "VR" };
Setting::Handle enableNoiseReductionSetting { QStringList { Audio::AUDIO, "NoiseReduction" }, true };
Setting::Handle enableWarnWhenMutedSetting { QStringList { Audio::AUDIO, "WarnWhenMuted" }, true };
+Setting::Handle enableAcousticEchoCancellationSetting { QStringList { Audio::AUDIO, "AcousticEchoCancellation" }, true };
float Audio::loudnessToLevel(float loudness) {
@@ -40,12 +41,14 @@ Audio::Audio() : _devices(_contextIsHMD) {
connect(client, &AudioClient::muteToggled, this, &Audio::setMuted);
connect(client, &AudioClient::noiseReductionChanged, this, &Audio::enableNoiseReduction);
connect(client, &AudioClient::warnWhenMutedChanged, this, &Audio::enableWarnWhenMuted);
+ connect(client, &AudioClient::acousticEchoCancellationChanged, this, &Audio::enableAcousticEchoCancellation);
connect(client, &AudioClient::inputLoudnessChanged, this, &Audio::onInputLoudnessChanged);
connect(client, &AudioClient::inputVolumeChanged, this, &Audio::setInputVolume);
connect(this, &Audio::contextChanged, &_devices, &AudioDevices::onContextChanged);
connect(this, &Audio::pushingToTalkChanged, this, &Audio::handlePushedToTalk);
enableNoiseReduction(enableNoiseReductionSetting.get());
enableWarnWhenMuted(enableWarnWhenMutedSetting.get());
+ enableAcousticEchoCancellation(enableAcousticEchoCancellationSetting.get());
onContextChanged();
}
@@ -210,6 +213,11 @@ void Audio::setPTTHMD(bool enabled) {
}
void Audio::saveData() {
+ _avatarGainSetting.set(getAvatarGain());
+ _injectorGainSetting.set(getInjectorGain());
+ _localInjectorGainSetting.set(getLocalInjectorGain());
+ _systemInjectorGainSetting.set(getSystemInjectorGain());
+
_mutedDesktopSetting.set(getMutedDesktop());
_mutedHMDSetting.set(getMutedHMD());
_pttDesktopSetting.set(getPTTDesktop());
@@ -217,6 +225,11 @@ void Audio::saveData() {
}
void Audio::loadData() {
+ setAvatarGain(_avatarGainSetting.get());
+ setInjectorGain(_injectorGainSetting.get());
+ setLocalInjectorGain(_localInjectorGainSetting.get());
+ setSystemInjectorGain(_systemInjectorGainSetting.get());
+
setMutedDesktop(_mutedDesktopSetting.get());
setMutedHMD(_mutedHMDSetting.get());
setPTTDesktop(_pttDesktopSetting.get());
@@ -277,6 +290,28 @@ void Audio::enableWarnWhenMuted(bool enable) {
}
}
+bool Audio::acousticEchoCancellationEnabled() const {
+ return resultWithReadLock([&] {
+ return _enableAcousticEchoCancellation;
+ });
+}
+
+void Audio::enableAcousticEchoCancellation(bool enable) {
+ bool changed = false;
+ withWriteLock([&] {
+ if (_enableAcousticEchoCancellation != enable) {
+ _enableAcousticEchoCancellation = enable;
+ auto client = DependencyManager::get().data();
+ QMetaObject::invokeMethod(client, "setAcousticEchoCancellation", Q_ARG(bool, enable), Q_ARG(bool, false));
+ enableAcousticEchoCancellationSetting.set(enable);
+ changed = true;
+ }
+ });
+ if (changed) {
+ emit acousticEchoCancellationChanged(enable);
+ }
+}
+
float Audio::getInputVolume() const {
return resultWithReadLock([&] {
return _inputVolume;
diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h
index aab1ade95b..5baeee4176 100644
--- a/interface/src/scripting/Audio.h
+++ b/interface/src/scripting/Audio.h
@@ -72,6 +72,9 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable {
* @property {number} systemInjectorGain - The gain (relative volume) that system sounds are played at.
* @property {number} pushingToTalkOutputGainDesktop - The gain (relative volume) that all sounds are played at when the user is holding
* the push-to-talk key in Desktop mode.
+ * @property {boolean} acousticEchoCancellation - true
if audio-echo-cancellation is enabled, otherwise
+ * false
. When enabled, sound from the audio output will be suppressed when it echos back to the
+ * input audio signal.
*
* @comment The following properties are from AudioScriptingInterface.h.
* @property {boolean} isStereoInput - true
if the input audio is being used in stereo, otherwise
@@ -85,6 +88,8 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable {
Q_PROPERTY(bool muted READ isMuted WRITE setMuted NOTIFY mutedChanged)
Q_PROPERTY(bool noiseReduction READ noiseReductionEnabled WRITE enableNoiseReduction NOTIFY noiseReductionChanged)
Q_PROPERTY(bool warnWhenMuted READ warnWhenMutedEnabled WRITE enableWarnWhenMuted NOTIFY warnWhenMutedChanged)
+ Q_PROPERTY(bool acousticEchoCancellation
+ READ acousticEchoCancellationEnabled WRITE enableAcousticEchoCancellation NOTIFY acousticEchoCancellationChanged)
Q_PROPERTY(float inputVolume READ getInputVolume WRITE setInputVolume NOTIFY inputVolumeChanged)
Q_PROPERTY(float inputLevel READ getInputLevel NOTIFY inputLevelChanged)
Q_PROPERTY(bool clipping READ isClipping NOTIFY clippingChanged)
@@ -115,6 +120,7 @@ public:
bool isMuted() const;
bool noiseReductionEnabled() const;
bool warnWhenMutedEnabled() const;
+ bool acousticEchoCancellationEnabled() const;
float getInputVolume() const;
float getInputLevel() const;
bool isClipping() const;
@@ -396,6 +402,14 @@ signals:
*/
void warnWhenMutedChanged(bool isEnabled);
+ /**jsdoc
+ * Triggered when acoustic echo cancellation is enabled or disabled.
+ * @function Audio.acousticEchoCancellationChanged
+ * @param {boolean} isEnabled - true
if acoustic echo cancellation is enabled, otherwise false
.
+ * @returns {Signal}
+ */
+ void acousticEchoCancellationChanged(bool isEnabled);
+
/**jsdoc
* Triggered when the input audio volume changes.
* @function Audio.inputVolumeChanged
@@ -494,6 +508,7 @@ private slots:
void setMuted(bool muted);
void enableNoiseReduction(bool enable);
void enableWarnWhenMuted(bool enable);
+ void enableAcousticEchoCancellation(bool enable);
void setInputVolume(float volume);
void onInputLoudnessChanged(float loudness, bool isClipping);
@@ -506,12 +521,17 @@ private:
bool _settingsLoaded { false };
float _inputVolume { 1.0f };
float _inputLevel { 0.0f };
+ Setting::Handle _avatarGainSetting { QStringList { Audio::AUDIO, "AvatarGain" }, 0.0f };
+ Setting::Handle _injectorGainSetting { QStringList { Audio::AUDIO, "InjectorGain" }, 0.0f };
+ Setting::Handle _localInjectorGainSetting { QStringList { Audio::AUDIO, "LocalInjectorGain" }, 0.0f };
+ Setting::Handle _systemInjectorGainSetting { QStringList { Audio::AUDIO, "SystemInjectorGain" }, 0.0f };
float _localInjectorGain { 0.0f }; // in dB
float _systemInjectorGain { 0.0f }; // in dB
float _pttOutputGainDesktop { 0.0f }; // in dB
bool _isClipping { false };
bool _enableNoiseReduction { true }; // Match default value of AudioClient::_isNoiseGateEnabled.
bool _enableWarnWhenMuted { true };
+ bool _enableAcousticEchoCancellation { true }; // AudioClient::_isAECEnabled
bool _contextIsHMD { false };
AudioDevices* getDevices() { return &_devices; }
AudioDevices _devices;
diff --git a/interface/src/ui/InteractiveWindow.cpp b/interface/src/ui/InteractiveWindow.cpp
index 4359995448..c0f3e82b29 100644
--- a/interface/src/ui/InteractiveWindow.cpp
+++ b/interface/src/ui/InteractiveWindow.cpp
@@ -136,12 +136,16 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap
if (properties.contains(PRESENTATION_MODE_PROPERTY)) {
presentationMode = (InteractiveWindowPresentationMode) properties[PRESENTATION_MODE_PROPERTY].toInt();
}
+
+ _interactiveWindowProxy = std::unique_ptr>(new InteractiveWindowProxy, [](InteractiveWindowProxy *p) {
+ p->deleteLater();
+ });
- if (!_interactiveWindowProxy) {
- _interactiveWindowProxy = new InteractiveWindowProxy();
- QObject::connect(_interactiveWindowProxy, &InteractiveWindowProxy::webEventReceived, this, &InteractiveWindow::emitWebEvent, Qt::QueuedConnection);
- QObject::connect(this, &InteractiveWindow::scriptEventReceived, _interactiveWindowProxy, &InteractiveWindowProxy::emitScriptEvent, Qt::QueuedConnection);
- }
+ connect(_interactiveWindowProxy.get(), &InteractiveWindowProxy::webEventReceived,
+ this, &InteractiveWindow::emitWebEvent, Qt::QueuedConnection);
+ connect(this, &InteractiveWindow::scriptEventReceived, _interactiveWindowProxy.get(),
+ &InteractiveWindowProxy::emitScriptEvent, Qt::QueuedConnection);
if (properties.contains(DOCKED_PROPERTY) && presentationMode == InteractiveWindowPresentationMode::Native) {
QVariantMap nativeWindowInfo = properties[DOCKED_PROPERTY].toMap();
@@ -161,7 +165,7 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap
_dockWidget = std::shared_ptr(new DockWidget(title, mainWindow), dockWidgetDeleter);
auto quickView = _dockWidget->getQuickView();
- Application::setupQmlSurface(quickView->rootContext() , true);
+ Application::setupQmlSurface(quickView->rootContext(), true);
//add any whitelisted callbacks
OffscreenUi::applyWhiteList(sourceUrl, quickView->rootContext());
@@ -201,7 +205,7 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap
QObject::connect(quickView.get(), &QQuickView::statusChanged, [&, this] (QQuickView::Status status) {
if (status == QQuickView::Ready) {
QQuickItem* rootItem = _dockWidget->getRootItem();
- _dockWidget->getQuickView()->rootContext()->setContextProperty(EVENT_BRIDGE_PROPERTY, _interactiveWindowProxy);
+ _dockWidget->getQuickView()->rootContext()->setContextProperty(EVENT_BRIDGE_PROPERTY, _interactiveWindowProxy.get());
// The qmlToScript method handles the thread-safety of this call. Because the QVariant argument
// passed to the sendToScript signal may wrap an externally managed and thread-unsafe QJSValue,
// qmlToScript needs to be called directly, so the QJSValue can be immediately converted to a plain QVariant.
@@ -222,7 +226,7 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap
// Build the event bridge and wrapper on the main thread
offscreenUi->loadInNewContext(CONTENT_WINDOW_QML, [&](QQmlContext* context, QObject* object) {
_qmlWindowProxy = std::shared_ptr(new QmlWindowProxy(object, nullptr), qmlWindowProxyDeleter);
- context->setContextProperty(EVENT_BRIDGE_PROPERTY, _interactiveWindowProxy);
+ context->setContextProperty(EVENT_BRIDGE_PROPERTY, _interactiveWindowProxy.get());
if (properties.contains(ADDITIONAL_FLAGS_PROPERTY)) {
object->setProperty(ADDITIONAL_FLAGS_PROPERTY, properties[ADDITIONAL_FLAGS_PROPERTY].toUInt());
}
@@ -313,10 +317,6 @@ void InteractiveWindow::close() {
_qmlWindowProxy->deleteLater();
}
- if (_interactiveWindowProxy) {
- _interactiveWindowProxy->deleteLater();
- }
-
if (_dockWidget) {
auto window = qApp->getWindow();
if (QThread::currentThread() != window->thread()) {
diff --git a/interface/src/ui/InteractiveWindow.h b/interface/src/ui/InteractiveWindow.h
index a1eede8b8a..67f0a9ea2e 100644
--- a/interface/src/ui/InteractiveWindow.h
+++ b/interface/src/ui/InteractiveWindow.h
@@ -41,7 +41,6 @@ class InteractiveWindowProxy : public QObject {
Q_OBJECT
public:
InteractiveWindowProxy(){}
-
public slots:
void emitScriptEvent(const QVariant& scriptMessage);
@@ -53,7 +52,6 @@ signals:
void webEventReceived(const QVariant& message);
};
-
namespace InteractiveWindowEnums {
Q_NAMESPACE
@@ -325,7 +323,7 @@ protected slots:
private:
std::shared_ptr _qmlWindowProxy;
std::shared_ptr _dockWidget { nullptr };
- InteractiveWindowProxy *_interactiveWindowProxy{ nullptr };
+ std::unique_ptr> _interactiveWindowProxy;
};
typedef InteractiveWindow* InteractiveWindowPointer;
diff --git a/interface/src/ui/ResourceImageItem.cpp b/interface/src/ui/ResourceImageItem.cpp
index f14f4ca78e..46ffffe62d 100644
--- a/interface/src/ui/ResourceImageItem.cpp
+++ b/interface/src/ui/ResourceImageItem.cpp
@@ -11,11 +11,52 @@
#include "ResourceImageItem.h"
#include
-
+#include
#include
+#include
#include
+
+static const char* VERTEX_SHADER = R"SHADER(
+#version 450 core
+
+out vec2 vTexCoord;
+
+void main(void) {
+ const float depth = 0.0;
+ const vec4 UNIT_QUAD[4] = vec4[4](
+ vec4(-1.0, -1.0, depth, 1.0),
+ vec4(1.0, -1.0, depth, 1.0),
+ vec4(-1.0, 1.0, depth, 1.0),
+ vec4(1.0, 1.0, depth, 1.0)
+ );
+ vec4 pos = UNIT_QUAD[gl_VertexID];
+
+ gl_Position = pos;
+ vTexCoord = (pos.xy + 1.0) * 0.5;
+}
+)SHADER";
+
+static const char* FRAGMENT_SHADER = R"SHADER(
+#version 450 core
+
+uniform sampler2D sampler;
+
+in vec2 vTexCoord;
+
+out vec4 FragColor;
+
+vec3 color_LinearTosRGB(vec3 lrgb) {
+ return mix(vec3(1.055) * pow(vec3(lrgb), vec3(0.41666)) - vec3(0.055), vec3(lrgb) * vec3(12.92), vec3(lessThan(lrgb, vec3(0.0031308))));
+}
+
+void main() {
+ FragColor = vec4(color_LinearTosRGB(texture(sampler, vTexCoord).rgb), 1.0);
+}
+)SHADER";
+
+
ResourceImageItem::ResourceImageItem() : QQuickFramebufferObject() {
auto textureCache = DependencyManager::get();
connect(textureCache.data(), SIGNAL(spectatorCameraFramebufferReset()), this, SLOT(update()));
@@ -95,16 +136,29 @@ void ResourceImageItemRenderer::render() {
}
if (_ready) {
_fboMutex.lock();
- _copyFbo->bind();
- QOpenGLFramebufferObject::blitFramebuffer(framebufferObject(), _copyFbo, GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT, GL_NEAREST);
- // this clears the copyFbo texture
- // so next frame starts fresh - helps
- // when aspect ratio changes
- _copyFbo->takeTexture();
+
+ if (!_shader) {
+ _shader = new QOpenGLShaderProgram();
+ _shader->addCacheableShaderFromSourceCode(QOpenGLShader::Vertex, VERTEX_SHADER);
+ _shader->addCacheableShaderFromSourceCode(QOpenGLShader::Fragment, FRAGMENT_SHADER);
+ _shader->link();
+ glGenVertexArrays(1, &_vao);
+ }
+ framebufferObject()->bind();
+ _shader->bind();
+
+ auto sourceTextureId = _copyFbo->takeTexture();
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, sourceTextureId);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glBindVertexArray(_vao);
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+ glDeleteTextures(1, &sourceTextureId);
+
_copyFbo->bind();
_copyFbo->release();
-
_fboMutex.unlock();
}
glFlush();
diff --git a/interface/src/ui/ResourceImageItem.h b/interface/src/ui/ResourceImageItem.h
index d154588094..1a679ba2ee 100644
--- a/interface/src/ui/ResourceImageItem.h
+++ b/interface/src/ui/ResourceImageItem.h
@@ -22,6 +22,9 @@
#include
+class QOpenGLFramebufferObject;
+class QOpenGLShaderProgram;
+
class ResourceImageItemRenderer : public QObject, public QQuickFramebufferObject::Renderer {
Q_OBJECT
public:
@@ -30,14 +33,16 @@ public:
void synchronize(QQuickFramebufferObject* item) override;
void render() override;
private:
- bool _ready;
+ bool _ready{ false };
QString _url;
- bool _visible;
+ bool _visible{ false };
NetworkTexturePointer _networkTexture;
- QQuickWindow* _window;
+ QQuickWindow* _window{ nullptr };
QMutex _fboMutex;
+ uint32_t _vao{ 0 };
QOpenGLFramebufferObject* _copyFbo { nullptr };
+ QOpenGLShaderProgram* _shader{ nullptr };
GLsync _fenceSync { 0 };
QTimer _updateTimer;
public slots:
diff --git a/interface/src/ui/Snapshot.cpp b/interface/src/ui/Snapshot.cpp
index 926588e4ca..bb9971e582 100644
--- a/interface/src/ui/Snapshot.cpp
+++ b/interface/src/ui/Snapshot.cpp
@@ -41,7 +41,6 @@
#include "MainWindow.h"
#include "Snapshot.h"
#include "SnapshotUploader.h"
-#include "ToneMappingEffect.h"
// filename format: hifi-snap-by-%username%-on-%date%_%time%_@-%location%.jpg
// %1 <= username, %2 <= date and time, %3 <= current location
diff --git a/interface/src/ui/overlays/QmlOverlay.cpp b/interface/src/ui/overlays/QmlOverlay.cpp
index f301a23d49..2afb29bb91 100644
--- a/interface/src/ui/overlays/QmlOverlay.cpp
+++ b/interface/src/ui/overlays/QmlOverlay.cpp
@@ -9,18 +9,10 @@
#include "QmlOverlay.h"
#include
+#include
#include
-#include
-#include
#include
-#include
-#include
-#include
-#include
-
-#include "Application.h"
-#include "text/FontFamilies.h"
QmlOverlay::QmlOverlay(const QUrl& url) {
buildQmlElement(url);
diff --git a/interface/src/ui/overlays/QmlOverlay.h b/interface/src/ui/overlays/QmlOverlay.h
index 32badde28b..0d3c0982c3 100644
--- a/interface/src/ui/overlays/QmlOverlay.h
+++ b/interface/src/ui/overlays/QmlOverlay.h
@@ -9,10 +9,6 @@
#ifndef hifi_QmlOverlay_h
#define hifi_QmlOverlay_h
-#include
-#include
-
-#include
#include "Overlay2D.h"
class QQuickItem;
@@ -33,7 +29,7 @@ private:
Q_INVOKABLE void buildQmlElement(const QUrl& url);
protected:
- QQuickItem* _qmlElement{ nullptr };
+ QQuickItem* _qmlElement { nullptr };
};
#endif // hifi_QmlOverlay_h
diff --git a/interface/src/ui/overlays/TextOverlay.cpp b/interface/src/ui/overlays/TextOverlay.cpp
index 2f4353dae8..6760c918d3 100644
--- a/interface/src/ui/overlays/TextOverlay.cpp
+++ b/interface/src/ui/overlays/TextOverlay.cpp
@@ -11,18 +11,9 @@
#include "TextOverlay.h"
#include
+#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "Application.h"
-#include "text/FontFamilies.h"
+#include "FontFamilies.h"
QString const TextOverlay::TYPE = "text";
QUrl const TextOverlay::URL(QString("hifi/overlays/TextOverlay.qml"));
@@ -44,7 +35,7 @@ QSizeF TextOverlay::textSize(const QString& text) const {
++lines;
}
}
- QFont font(SANS_FONT_FAMILY);
+ QFont font(ROBOTO_FONT_FAMILY);
font.setPixelSize(18);
QFontMetrics fm(font);
QSizeF result = QSizeF(fm.width(text), 18 * lines);
diff --git a/launchers/darwin/CMakeLists.txt b/launchers/darwin/CMakeLists.txt
index 177a7722e5..fe7f4298ce 100644
--- a/launchers/darwin/CMakeLists.txt
+++ b/launchers/darwin/CMakeLists.txt
@@ -20,6 +20,8 @@ set(src_files
src/DownloadInterface.m
src/DownloadDomainContent.h
src/DownloadDomainContent.m
+ src/DownloadLauncher.h
+ src/DownloadLauncher.m
src/DownloadScripts.h
src/DownloadScripts.m
src/CredentialsRequest.h
@@ -34,6 +36,8 @@ set(src_files
src/Interface.m
src/ErrorViewController.h
src/ErrorViewController.m
+ src/LauncherCommandlineArgs.h
+ src/LauncherCommandlineArgs.m
src/Settings.h
src/Settings.m
src/LaunchInterface.h
@@ -47,6 +51,9 @@ set(src_files
nib/ProcessScreen.xib
nib/DisplayNameScreen.xib)
+set(updater_src_files
+ src/updater/main.m)
+
set(APP_NAME "HQ Launcher")
set(CMAKE_C_FLAGS "-x objective-c")
@@ -72,6 +79,7 @@ endfunction()
set_packaging_parameters()
add_executable(${PROJECT_NAME} MACOSX_BUNDLE ${src_files})
+add_executable("updater" ${updater_src_files})
set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME ${APP_NAME}
MACOSX_BUNDLE_BUNDLE_NAME ${APP_NAME})
set_from_env(LAUNCHER_HMAC_SECRET LAUNCHER_HMAC_SECRET "")
@@ -102,6 +110,10 @@ add_custom_command(TARGET ${PROJECT_NAME} PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/images "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${APP_NAME}.app/Contents/Resources/")
+add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
+ COMMAND updater
+ COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/updater" "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${APP_NAME}.app/Contents/Resources/")
+
install(
TARGETS HQLauncher
BUNDLE DESTINATION "."
diff --git a/launchers/darwin/src/DownloadLauncher.h b/launchers/darwin/src/DownloadLauncher.h
new file mode 100644
index 0000000000..2f97910cd8
--- /dev/null
+++ b/launchers/darwin/src/DownloadLauncher.h
@@ -0,0 +1,8 @@
+#import
+
+@interface DownloadLauncher : NSObject {
+}
+
+- (void) downloadLauncher:(NSString*) launcherUrl;
+
+@end
diff --git a/launchers/darwin/src/DownloadLauncher.m b/launchers/darwin/src/DownloadLauncher.m
new file mode 100644
index 0000000000..e7d293be43
--- /dev/null
+++ b/launchers/darwin/src/DownloadLauncher.m
@@ -0,0 +1,68 @@
+#import "DownloadLauncher.h"
+#import "Launcher.h"
+
+
+@implementation DownloadLauncher
+
+- (void) downloadLauncher:(NSString*)launcherUrl {
+ NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:launcherUrl]
+ cachePolicy:NSURLRequestUseProtocolCachePolicy
+ timeoutInterval:60.0];
+
+ NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
+ NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: self delegateQueue: [NSOperationQueue mainQueue]];
+ NSURLSessionDownloadTask *downloadTask = [defaultSession downloadTaskWithRequest:request];
+ [downloadTask resume];
+}
+
+-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
+ CGFloat prog = (float)totalBytesWritten/totalBytesExpectedToWrite;
+ NSLog(@"Launcher downloaded %f", (100.0*prog));
+
+}
+
+-(void)URLSession:(NSURLSession*)session downloadTask:(NSURLSessionDownloadTask*)downloadTask didFinishDownloadingToURL:(NSURL*)location {
+ NSLog(@"Did finish downloading to url");
+ NSError* error = nil;
+ NSFileManager* fileManager = [NSFileManager defaultManager];
+ NSString* destinationFileName = downloadTask.originalRequest.URL.lastPathComponent;
+ NSString* finalFilePath = [[[Launcher sharedLauncher] getDownloadPathForContentAndScripts] stringByAppendingPathComponent:destinationFileName];
+ NSURL *destinationURL = [NSURL URLWithString: [finalFilePath stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]] relativeToURL: [NSURL URLWithString:@"file://"]];
+ NSLog(@"desintation %@", destinationURL);
+ if([fileManager fileExistsAtPath:[destinationURL path]]) {
+ [fileManager removeItemAtURL:destinationURL error:nil];
+ }
+
+ NSLog(@"location: %@", location.path);
+ NSLog(@"destination: %@", destinationURL);
+ BOOL success = [fileManager moveItemAtURL:location toURL:destinationURL error:&error];
+
+
+ NSLog(success ? @"TRUE" : @"FALSE");
+ Launcher* sharedLauncher = [Launcher sharedLauncher];
+
+ if (error) {
+ NSLog(@"Download Launcher: failed to move file to destintation -> error: %@", error);
+ [sharedLauncher displayErrorPage];
+ return;
+ }
+ NSLog(@"extracting Launcher file");
+ BOOL extractionSuccessful = [sharedLauncher extractZipFileAtDestination:[sharedLauncher getDownloadPathForContentAndScripts] :[[sharedLauncher getDownloadPathForContentAndScripts] stringByAppendingString:@"HQ Launcher.zip"]];
+
+ if (!extractionSuccessful) {
+ [sharedLauncher displayErrorPage];
+ return;
+ }
+ NSLog(@"finished extracting Launcher file");
+
+
+ [[Launcher sharedLauncher] runAutoupdater];
+}
+
+- (void)URLSession:(NSURLSession*)session task:(NSURLSessionTask*)task didCompleteWithError:(NSError*)error {
+ NSLog(@"completed; error: %@", error);
+ if (error) {
+ [[Launcher sharedLauncher] displayErrorPage];
+ }
+}
+@end
diff --git a/launchers/darwin/src/LatestBuildRequest.m b/launchers/darwin/src/LatestBuildRequest.m
index deb6d9795b..a663200089 100644
--- a/launchers/darwin/src/LatestBuildRequest.m
+++ b/launchers/darwin/src/LatestBuildRequest.m
@@ -9,8 +9,8 @@
NSInteger currentVersion;
@try {
NSString* interfaceAppPath = [[Launcher.sharedLauncher getAppPath] stringByAppendingString:@"interface.app"];
- NSError * error = nil;
- Interface * interface = [[Interface alloc] initWith:interfaceAppPath];
+ NSError* error = nil;
+ Interface* interface = [[Interface alloc] initWith:interfaceAppPath];
currentVersion = [interface getVersion:&error];
if (currentVersion == 0 && error != nil) {
NSLog(@"can't get version from interface, falling back to settings: %@", error);
@@ -24,17 +24,17 @@
}
- (void) requestLatestBuildInfo {
- NSMutableURLRequest *request = [NSMutableURLRequest new];
+ NSMutableURLRequest* request = [NSMutableURLRequest new];
[request setURL:[NSURL URLWithString:@"https://thunder.highfidelity.com/builds/api/tags/latest?format=json"]];
[request setHTTPMethod:@"GET"];
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
// We're using an ephermeral session here to ensure the tags api response is never cached.
- NSURLSession * session = [NSURLSession sessionWithConfiguration:NSURLSessionConfiguration.ephemeralSessionConfiguration];
+ NSURLSession* session = [NSURLSession sessionWithConfiguration:NSURLSessionConfiguration.ephemeralSessionConfiguration];
NSURLSessionDataTask* dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(@"Latest Build Request error: %@", error);
NSLog(@"Latest Build Request Data: %@", data);
- NSHTTPURLResponse *ne = (NSHTTPURLResponse *)response;
+ NSHTTPURLResponse* ne = (NSHTTPURLResponse *)response;
NSLog(@"Latest Build Request Response: %ld", [ne statusCode]);
Launcher* sharedLauncher = [Launcher sharedLauncher];
@@ -47,9 +47,9 @@
NSMutableData* webData = [NSMutableData data];
[webData appendData:data];
NSString* jsonString = [[NSString alloc] initWithBytes: [webData mutableBytes] length:[data length] encoding:NSUTF8StringEncoding];
- NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
+ NSData* jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSLog(@"Latest Build Request -> json string: %@", jsonString);
- NSError *jsonError = nil;
+ NSError* jsonError = nil;
id json = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&jsonError];
if (jsonError) {
@@ -57,10 +57,12 @@
}
NSFileManager* fileManager = [NSFileManager defaultManager];
- NSArray *values = [json valueForKey:@"results"];
- NSDictionary *value = [values objectAtIndex:0];
-
+ NSArray* values = [json valueForKey:@"results"];
+ NSDictionary* launcherValues = [json valueForKey:@"launcher"];
+ NSDictionary* value = [values objectAtIndex:0];
+ NSString* launcherVersion = [launcherValues valueForKey:@"version"];
+ NSString* launcherUrl = [[launcherValues valueForKey:@"mac"] valueForKey:@"url"];
NSString* buildNumber = [value valueForKey:@"latest_version"];
NSDictionary* installers = [value objectForKey:@"installers"];
NSDictionary* macInstallerObject = [installers objectForKey:@"mac"];
@@ -71,16 +73,22 @@
dispatch_async(dispatch_get_main_queue(), ^{
NSInteger currentVersion = [self getCurrentVersion];
+ NSInteger currentLauncherVersion = atoi(LAUNCHER_BUILD_VERSION);
+ NSLog(@"Latest Build Request -> current launcher version %ld", currentLauncherVersion);
+ NSLog(@"Latest Build Request -> latest launcher version %ld", launcherVersion.integerValue);
+ NSLog(@"Latest Build Request -> launcher url %@", launcherUrl);
NSLog(@"Latest Build Request -> does build directory exist: %@", appDirectoryExist ? @"TRUE" : @"FALSE");
NSLog(@"Latest Build Request -> current version: %ld", currentVersion);
NSLog(@"Latest Build Request -> latest version: %ld", buildNumber.integerValue);
NSLog(@"Latest Build Request -> mac url: %@", macInstallerUrl);
BOOL latestVersionAvailable = (currentVersion != buildNumber.integerValue);
+ BOOL latestLauncherVersionAvailable = (currentLauncherVersion != launcherVersion.integerValue);
[[Settings sharedSettings] buildVersion:buildNumber.integerValue];
BOOL shouldDownloadInterface = (latestVersionAvailable || !appDirectoryExist);
NSLog(@"Latest Build Request -> SHOULD DOWNLOAD: %@", shouldDownloadInterface ? @"TRUE" : @"FALSE");
- [sharedLauncher shouldDownloadLatestBuild:shouldDownloadInterface :macInstallerUrl];
+ [sharedLauncher shouldDownloadLatestBuild:shouldDownloadInterface :macInstallerUrl
+ :latestLauncherVersionAvailable :launcherUrl];
});
}];
diff --git a/launchers/darwin/src/Launcher.h b/launchers/darwin/src/Launcher.h
index de67850bfe..d20f58dee7 100644
--- a/launchers/darwin/src/Launcher.h
+++ b/launchers/darwin/src/Launcher.h
@@ -2,6 +2,7 @@
#import "DownloadInterface.h"
#import "CredentialsRequest.h"
#import "DownloadDomainContent.h"
+#import "DownloadLauncher.h"
#import "LatestBuildRequest.h"
#import "OrganizationRequest.h"
#import "DownloadScripts.h"
@@ -44,6 +45,7 @@ struct LatestBuildInfo {
@property (nonatomic, retain) DownloadInterface* downloadInterface;
@property (nonatomic, retain) CredentialsRequest* credentialsRequest;
@property (nonatomic, retain) DownloadDomainContent* downloadDomainContent;
+@property (nonatomic, retain) DownloadLauncher* downloadLauncher;
@property (nonatomic, retain) DownloadScripts* downloadScripts;
@property (nonatomic, retain) LatestBuildRequest* latestBuildRequest;
@property (nonatomic, retain) OrganizationRequest* organizationRequest;
@@ -74,11 +76,12 @@ struct LatestBuildInfo {
- (void) showLoginScreen;
- (void) restart;
- (NSString*) getLauncherPath;
+- (void) runAutoupdater;
- (ProcessState) currentProccessState;
- (void) setCurrentProcessState:(ProcessState) aProcessState;
- (void) setLoginErrorState:(LoginError) aLoginError;
- (LoginError) getLoginErrorState;
-- (void) shouldDownloadLatestBuild:(BOOL) shouldDownload :(NSString*) downloadUrl;
+- (void) shouldDownloadLatestBuild:(BOOL) shouldDownload :(NSString*) downloadUrl :(BOOL) newLauncherAvailable :(NSString*) launcherUrl;
- (void) interfaceFinishedDownloading;
- (NSString*) getDownloadPathForContentAndScripts;
- (void) launchInterface;
diff --git a/launchers/darwin/src/Launcher.m b/launchers/darwin/src/Launcher.m
index 1a84e9143d..38027d6fd3 100644
--- a/launchers/darwin/src/Launcher.m
+++ b/launchers/darwin/src/Launcher.m
@@ -3,6 +3,7 @@
#import "SplashScreen.h"
#import "LoginScreen.h"
#import "DisplayNameScreen.h"
+#import "LauncherCommandlineArgs.h"
#import "ProcessScreen.h"
#import "ErrorViewController.h"
#import "Settings.h"
@@ -32,6 +33,7 @@ static BOOL const DELETE_ZIP_FILES = TRUE;
self.username = [[NSString alloc] initWithString:@"Default Property Value"];
self.downloadInterface = [DownloadInterface alloc];
self.downloadDomainContent = [DownloadDomainContent alloc];
+ self.downloadLauncher = [DownloadLauncher alloc];
self.credentialsRequest = [CredentialsRequest alloc];
self.latestBuildRequest = [LatestBuildRequest alloc];
self.organizationRequest = [OrganizationRequest alloc];
@@ -362,28 +364,44 @@ static BOOL const DELETE_ZIP_FILES = TRUE;
[[[[NSApplication sharedApplication] windows] objectAtIndex:0] setContentViewController: loginScreen];
}
-- (void) shouldDownloadLatestBuild:(BOOL) shouldDownload :(NSString*) downloadUrl
+- (void) shouldDownloadLatestBuild:(BOOL) shouldDownload :(NSString*) downloadUrl :(BOOL) newLauncherAvailable :(NSString*) launcherUrl
{
- self.shouldDownloadInterface = shouldDownload;
- self.interfaceDownloadUrl = downloadUrl;
- self.latestBuildRequestFinished = TRUE;
- if ([self isLoadedIn]) {
- Launcher* sharedLauncher = [Launcher sharedLauncher];
- [sharedLauncher setCurrentProcessState:CHECKING_UPDATE];
- if (shouldDownload) {
- ProcessScreen* processScreen = [[ProcessScreen alloc] initWithNibName:@"ProcessScreen" bundle:nil];
- [[[[NSApplication sharedApplication] windows] objectAtIndex:0] setContentViewController: processScreen];
- [self startUpdateProgressIndicatorTimer];
- [self.downloadInterface downloadInterface: downloadUrl];
- return;
- }
- [self interfaceFinishedDownloading];
+ NSDictionary* launcherArguments = [LauncherCommandlineArgs arguments];
+ if (newLauncherAvailable && ![launcherArguments valueForKey: @"--noUpdate"]) {
+ [self.downloadLauncher downloadLauncher: launcherUrl];
} else {
- [[NSApplication sharedApplication] activateIgnoringOtherApps:TRUE];
- [self showLoginScreen];
+ self.shouldDownloadInterface = shouldDownload;
+ self.interfaceDownloadUrl = downloadUrl;
+ self.latestBuildRequestFinished = TRUE;
+ if ([self isLoadedIn]) {
+ Launcher* sharedLauncher = [Launcher sharedLauncher];
+ [sharedLauncher setCurrentProcessState:CHECKING_UPDATE];
+ if (shouldDownload) {
+ ProcessScreen* processScreen = [[ProcessScreen alloc] initWithNibName:@"ProcessScreen" bundle:nil];
+ [[[[NSApplication sharedApplication] windows] objectAtIndex:0] setContentViewController: processScreen];
+ [self startUpdateProgressIndicatorTimer];
+ [self.downloadInterface downloadInterface: downloadUrl];
+ return;
+ }
+ [self interfaceFinishedDownloading];
+ } else {
+ [[NSApplication sharedApplication] activateIgnoringOtherApps:TRUE];
+ [self showLoginScreen];
+ }
}
}
+-(void)runAutoupdater
+{
+ NSTask* task = [[NSTask alloc] init];
+ NSString* newLauncher = [[[Launcher sharedLauncher] getDownloadPathForContentAndScripts] stringByAppendingPathComponent: @"HQ Launcher.app"];
+ task.launchPath = [newLauncher stringByAppendingString:@"/Contents/Resources/updater"];
+ task.arguments = @[[[NSBundle mainBundle] bundlePath], newLauncher];
+ [task launch];
+
+ [NSApp terminate:self];
+}
+
-(void)onSplashScreenTimerFinished:(NSTimer *)timer
{
[self.latestBuildRequest requestLatestBuildInfo];
diff --git a/launchers/darwin/src/LauncherCommandlineArgs.h b/launchers/darwin/src/LauncherCommandlineArgs.h
new file mode 100644
index 0000000000..c348e50987
--- /dev/null
+++ b/launchers/darwin/src/LauncherCommandlineArgs.h
@@ -0,0 +1,8 @@
+#import
+#import
+
+@interface LauncherCommandlineArgs : NSObject {
+}
++(NSDictionary*) arguments;
+@end
+
diff --git a/launchers/darwin/src/LauncherCommandlineArgs.m b/launchers/darwin/src/LauncherCommandlineArgs.m
new file mode 100644
index 0000000000..4229c91205
--- /dev/null
+++ b/launchers/darwin/src/LauncherCommandlineArgs.m
@@ -0,0 +1,29 @@
+#import "LauncherCommandlineArgs.h"
+
+@implementation LauncherCommandlineArgs
++(NSDictionary*) arguments {
+
+ NSArray* arguments = [[NSProcessInfo processInfo] arguments];
+ if (arguments.count < 2)
+ {
+ return nil;
+ }
+
+ NSMutableDictionary* argsDict = [[NSMutableDictionary alloc] init];
+ NSMutableArray* args = [arguments mutableCopy];
+
+ for (NSString* arg in args)
+ {
+ if ([arg rangeOfString:@"="].location != NSNotFound && [arg rangeOfString:@"--"].location != NSNotFound) {
+ NSArray* components = [arg componentsSeparatedByString:@"="];
+ NSString* key = [components objectAtIndex:0];
+ NSString* value = [components objectAtIndex:1];
+ [argsDict setObject:value forKey:key];
+ } else if ([arg rangeOfString:@"--"].location != NSNotFound) {
+ [argsDict setObject:@TRUE forKey:arg];
+ }
+ }
+ NSLog(@"AGS: %@", argsDict);
+ return argsDict;
+}
+@end
diff --git a/launchers/darwin/src/main.mm b/launchers/darwin/src/main.mm
index b6555aad87..8651ffe3ff 100644
--- a/launchers/darwin/src/main.mm
+++ b/launchers/darwin/src/main.mm
@@ -1,5 +1,6 @@
#import "Launcher.h"
#import "Settings.h"
+#import "LauncherCommandlineArgs.h"
void redirectLogToDocuments()
{
diff --git a/launchers/darwin/src/updater/main.m b/launchers/darwin/src/updater/main.m
new file mode 100644
index 0000000000..7c7ace6f70
--- /dev/null
+++ b/launchers/darwin/src/updater/main.m
@@ -0,0 +1,35 @@
+#import
+#import
+
+@interface UpdaterHelper : NSObject
++(NSURL*) NSStringToNSURL: (NSString*) path;
+@end
+
+@implementation UpdaterHelper
++(NSURL*) NSStringToNSURL: (NSString*) path
+{
+ return [NSURL URLWithString: [path stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]] relativeToURL: [NSURL URLWithString:@"file://"]];
+}
+@end
+
+int main(int argc, char const* argv[]) {
+ if (argc < 3) {
+ NSLog(@"Error: wrong number of arguments");
+ return 0;
+ }
+
+ for (int index = 0; index < argc; index++) {
+ NSLog(@"argv at index %d = %s", index, argv[index]);
+ }
+ NSString* oldLauncher = [NSString stringWithUTF8String:argv[1]];
+ NSString* newLauncher = [NSString stringWithUTF8String:argv[2]];
+ NSURL* destinationUrl = [UpdaterHelper NSStringToNSURL:newLauncher];
+ NSFileManager* fileManager = [NSFileManager defaultManager];
+ [fileManager replaceItemAtURL:[UpdaterHelper NSStringToNSURL:oldLauncher] withItemAtURL:[UpdaterHelper NSStringToNSURL:newLauncher] backupItemName:nil options:NSFileManagerItemReplacementUsingNewMetadataOnly resultingItemURL:&destinationUrl error:nil];
+ NSWorkspace* workspace = [NSWorkspace sharedWorkspace];
+ NSURL* applicationURL = [UpdaterHelper NSStringToNSURL: [oldLauncher stringByAppendingString: @"/Contents/MacOS/HQ Launcher"]];
+ NSArray* arguments =@[];
+ NSLog(@"Launcher agruments: %@", arguments);
+ [workspace launchApplicationAtURL:applicationURL options:NSWorkspaceLaunchNewInstance configuration:[NSDictionary dictionaryWithObject:arguments forKey:NSWorkspaceLaunchConfigurationArguments] error:nil];
+ return 0;
+}
diff --git a/launchers/win32/LauncherApp.cpp b/launchers/win32/LauncherApp.cpp
index 841a0bee3b..ff63841c5a 100644
--- a/launchers/win32/LauncherApp.cpp
+++ b/launchers/win32/LauncherApp.cpp
@@ -42,8 +42,7 @@ BOOL CLauncherApp::InitInstance() {
bool uninstalling = false;
bool restarting = false;
bool noUpdate = false;
- bool continueUpdating = false;
- bool skipSplash = false;
+ LauncherManager::ContinueActionOnStart continueAction = LauncherManager::ContinueActionOnStart::ContinueNone;
if (iNumOfArgs > 1) {
for (int i = 1; i < iNumOfArgs; i++) {
CString curArg = CString(pArgs[i]);
@@ -53,10 +52,10 @@ BOOL CLauncherApp::InitInstance() {
restarting = true;
} else if (curArg.Compare(_T("--noUpdate")) == 0) {
noUpdate = true;
- } else if (curArg.Compare(_T("--continueUpdating")) == 0) {
- continueUpdating = true;
- } else if (curArg.Compare(_T("--skipSplash")) == 0) {
- skipSplash = true;
+ } else if (curArg.Compare(_T("--continueAction")) == 0) {
+ if (i + 1 < iNumOfArgs) {
+ continueAction = LauncherManager::getContinueActionFromParam(pArgs[i + 1]);
+ }
}
}
}
@@ -71,7 +70,7 @@ BOOL CLauncherApp::InitInstance() {
if (uninstalling) {
_manager.uninstall();
} else {
- _manager.init(!noUpdate, continueUpdating, skipSplash);
+ _manager.init(!noUpdate, continueAction);
}
if (!_manager.hasFailed() && !_manager.installLauncher()) {
return FALSE;
diff --git a/launchers/win32/LauncherDlg.cpp b/launchers/win32/LauncherDlg.cpp
index d7d214842d..928bf7010f 100644
--- a/launchers/win32/LauncherDlg.cpp
+++ b/launchers/win32/LauncherDlg.cpp
@@ -357,7 +357,7 @@ void CLauncherDlg::drawVoxel(CHwndRenderTarget* pRenderTarget) {
}
void CLauncherDlg::drawProgress(CHwndRenderTarget* pRenderTarget, float progress, const D2D1::ColorF& color) {
- auto size = pRenderTarget->GetPixelSize();
+ auto size = pRenderTarget->GetSize();
if (progress == 0.0f) {
return;
} else {
@@ -693,22 +693,26 @@ void CLauncherDlg::OnTimer(UINT_PTR nIDEvent) {
}
}
}
+
+ LauncherManager::ContinueActionOnStart continueAction = theApp._manager.getContinueAction();
if (_showSplash) {
if (_splashStep == 0) {
if (theApp._manager.needsUninstall()) {
theApp._manager.addToLog(_T("Waiting to uninstall"));
setDrawDialog(DrawStep::DrawProcessUninstall);
- } else if (theApp._manager.shouldContinueUpdating()) {
- _splashStep = SPLASH_DURATION;
+ } else if (continueAction == LauncherManager::ContinueActionOnStart::ContinueUpdate) {
setDrawDialog(DrawStep::DrawProcessUpdate);
theApp._manager.updateProgress(LauncherManager::ProcessType::Uninstall, 0.0f);
+ } else if (continueAction == LauncherManager::ContinueActionOnStart::ContinueLogIn) {
+ _splashStep = SPLASH_DURATION;
+ } else if (continueAction == LauncherManager::ContinueActionOnStart::ContinueFinish) {
+ theApp._manager.updateProgress(LauncherManager::ProcessType::Uninstall, 1.0f);
+ setDrawDialog(DrawStep::DrawProcessFinishUpdate);
+ _splashStep = SPLASH_DURATION;
+ _showSplash = false;
} else {
- if (theApp._manager.shouldSkipSplashScreen()) {
- _splashStep = SPLASH_DURATION;
- } else {
- theApp._manager.addToLog(_T("Start splash screen"));
- setDrawDialog(DrawStep::DrawLogo);
- }
+ theApp._manager.addToLog(_T("Start splash screen"));
+ setDrawDialog(DrawStep::DrawLogo);
}
} else if (_splashStep > SPLASH_DURATION && !theApp._manager.needsToWait()) {
_showSplash = false;
diff --git a/launchers/win32/LauncherManager.cpp b/launchers/win32/LauncherManager.cpp
index d7d92a18fb..49ae058ef5 100644
--- a/launchers/win32/LauncherManager.cpp
+++ b/launchers/win32/LauncherManager.cpp
@@ -16,27 +16,55 @@
LauncherManager::LauncherManager() {
+ int tokenPos = 0;
+ _launcherVersion = CString(LAUNCHER_BUILD_VERSION).Tokenize(_T("-"), tokenPos);
}
LauncherManager::~LauncherManager() {
}
-void LauncherManager::init(BOOL allowUpdate, BOOL continueUpdating, BOOL skipSplashScreen) {
+void LauncherManager::init(BOOL allowUpdate, ContinueActionOnStart continueAction) {
initLog();
- int tokenPos = 0;
_updateLauncherAllowed = allowUpdate;
- _continueUpdating = continueUpdating;
- _skipSplashScreen = skipSplashScreen;
- _shouldWait = !skipSplashScreen;
- if (_continueUpdating) {
+ _continueAction = continueAction;
+ CString msg;
+ msg.Format(_T("Start Screen: %s"), getContinueActionParam(continueAction));
+ addToLog(msg);
+ _shouldWait = _continueAction == ContinueActionOnStart::ContinueNone;
+ if (_continueAction == ContinueActionOnStart::ContinueUpdate) {
_progressOffset = CONTINUE_UPDATING_GLOBAL_OFFSET;
}
- _launcherVersion = CString(LAUNCHER_BUILD_VERSION).Tokenize(_T("-"), tokenPos);
addToLog(_T("Launcher is running version: " + _launcherVersion));
addToLog(_T("Getting most recent builds"));
getMostRecentBuilds(_latestLauncherURL, _latestLauncherVersion, _latestApplicationURL, _latestVersion);
}
+CString LauncherManager::getContinueActionParam(LauncherManager::ContinueActionOnStart continueAction) {
+ switch (continueAction) {
+ case LauncherManager::ContinueActionOnStart::ContinueNone:
+ return _T("");
+ case LauncherManager::ContinueActionOnStart::ContinueLogIn:
+ return _T("LogIn");
+ case LauncherManager::ContinueActionOnStart::ContinueUpdate:
+ return _T("Update");
+ case LauncherManager::ContinueActionOnStart::ContinueFinish:
+ return _T("Finish");
+ default:
+ return _T("");
+ }
+}
+
+LauncherManager::ContinueActionOnStart LauncherManager::getContinueActionFromParam(const CString& param) {
+ if (param.Compare(_T("LogIn")) == 0) {
+ return ContinueActionOnStart::ContinueLogIn;
+ } else if (param.Compare(_T("Update")) == 0) {
+ return ContinueActionOnStart::ContinueUpdate;
+ } else if (param.Compare(_T("Finish")) == 0) {
+ return ContinueActionOnStart::ContinueFinish;
+ } else {
+ return ContinueActionOnStart::ContinueNone;
+ }
+}
BOOL LauncherManager::initLog() {
CString logPath;
auto result = getAndCreatePaths(PathType::Launcher_Directory, logPath);
@@ -432,7 +460,8 @@ void LauncherManager::onMostRecentBuildsReceived(const CString& response, Launch
addToLog(updatingMsg);
_shouldUpdateLauncher = TRUE;
_shouldDownloadLauncher = TRUE;
- _willContinueUpdating = isInstalled && newInterfaceVersion;
+ _keepLoggingIn = !isInstalled;
+ _keepUpdating = isInstalled && newInterfaceVersion;
} else {
if (_updateLauncherAllowed) {
addToLog(_T("Already running most recent build. Launching interface.exe"));
@@ -612,11 +641,15 @@ void LauncherManager::onFileDownloaded(ProcessType type) {
void LauncherManager::restartNewLauncher() {
closeLog();
- if (_willContinueUpdating) {
- LauncherUtils::launchApplication(_tempLauncherPath, _T(" --restart --noUpdate --continueUpdating"));
- } else {
- LauncherUtils::launchApplication(_tempLauncherPath, _T(" --restart --noUpdate --skipSplash"));
+ ContinueActionOnStart continueAction = ContinueActionOnStart::ContinueFinish;
+ if (_keepUpdating) {
+ continueAction = ContinueActionOnStart::ContinueUpdate;
+ } else if (_keepLoggingIn) {
+ continueAction = ContinueActionOnStart::ContinueLogIn;
}
+ CStringW params;
+ params.Format(_T(" --restart --noUpdate --continueAction %s"), getContinueActionParam(continueAction));
+ LauncherUtils::launchApplication(_tempLauncherPath, params.GetBuffer());
Sleep(500);
}
diff --git a/launchers/win32/LauncherManager.h b/launchers/win32/LauncherManager.h
index 108327469d..5169edfa75 100644
--- a/launchers/win32/LauncherManager.h
+++ b/launchers/win32/LauncherManager.h
@@ -11,6 +11,7 @@
#pragma once
#include "LauncherUtils.h"
+#include "LauncherDlg.h"
const CString DIRECTORY_NAME_APP = _T("HQ");
const CString DIRECTORY_NAME_DOWNLOADS = _T("downloads");
@@ -27,8 +28,7 @@ const float DOWNLOAD_APPLICATION_UPDATE_WEIGHT = 0.75f;
const float EXTRACT_APPLICATION_UPDATE_WEIGHT = 0.25f;
const float CONTINUE_UPDATING_GLOBAL_OFFSET = 0.2f;
-class LauncherManager
-{
+class LauncherManager {
public:
enum PathType {
Running_Path = 0,
@@ -57,22 +57,33 @@ public:
UnzipApplication,
Uninstall
};
+ enum ContinueActionOnStart {
+ ContinueNone = 0,
+ ContinueLogIn,
+ ContinueUpdate,
+ ContinueFinish
+ };
+
LauncherManager();
~LauncherManager();
- void init(BOOL allowUpdate, BOOL continueUpdating, BOOL skipSplashScreen);
+ void init(BOOL allowUpdate, ContinueActionOnStart continueAction);
+ static CString getContinueActionParam(ContinueActionOnStart continueAction);
+ static ContinueActionOnStart getContinueActionFromParam(const CString& param);
BOOL initLog();
BOOL addToLog(const CString& line);
void closeLog();
void saveErrorLog();
BOOL getAndCreatePaths(PathType type, CString& outPath);
BOOL getInstalledVersion(const CString& path, CString& version);
- BOOL isApplicationInstalled(CString& version, CString& domain,
+ BOOL isApplicationInstalled(CString& version, CString& domain,
CString& content, bool& loggedIn);
LauncherUtils::ResponseError getAccessTokenForCredentials(const CString& username, const CString& password);
- void getMostRecentBuilds(CString& launcherUrlOut, CString& launcherVersionOut,
- CString& interfaceUrlOut, CString& interfaceVersionOut);
+ void getMostRecentBuilds(CString& launcherUrlOut,
+ CString& launcherVersionOut,
+ CString& interfaceUrlOut,
+ CString& interfaceVersionOut);
LauncherUtils::ResponseError readOrganizationJSON(const CString& hash);
- LauncherUtils::ResponseError readConfigJSON(CString& version, CString& domain,
+ LauncherUtils::ResponseError readConfigJSON(CString& version, CString& domain,
CString& content, bool& loggedIn);
BOOL createConfigJSON();
BOOL createApplicationRegistryKeys(int size);
@@ -90,7 +101,6 @@ public:
const CString& getVersion() const { return _version; }
BOOL shouldShutDown() const { return _shouldShutdown; }
BOOL shouldLaunch() const { return _shouldLaunch; }
- BOOL shouldSkipSplashScreen() const { return _skipSplashScreen; }
BOOL needsUpdate() const { return _shouldUpdate; }
BOOL needsSelfUpdate() const { return _shouldUpdateLauncher; }
BOOL needsSelfDownload() const { return _shouldDownloadLauncher; }
@@ -98,14 +108,17 @@ public:
BOOL needsInstall() const { return _shouldInstall; }
BOOL needsToWait() const { return _shouldWait; }
BOOL needsRestartNewLauncher() const { return _shouldRestartNewLauncher; }
- BOOL shouldContinueUpdating() const { return _continueUpdating; }
- BOOL willContinueUpdating() const { return _willContinueUpdating; }
+ BOOL willContinueUpdating() const { return _keepUpdating; }
+ ContinueActionOnStart getContinueAction() { return _continueAction; }
void setDisplayName(const CString& displayName) { _displayName = displayName; }
bool isLoggedIn() const { return _loggedIn; }
bool hasFailed() const { return _hasFailed; }
void setFailed(bool hasFailed) { _hasFailed = hasFailed; }
const CString& getLatestInterfaceURL() const { return _latestApplicationURL; }
- void uninstall() { _shouldUninstall = true; _shouldWait = false; };
+ void uninstall() {
+ _shouldUninstall = true;
+ _shouldWait = false;
+ };
BOOL downloadFile(ProcessType type, const CString& url, CString& localPath);
BOOL downloadContent();
@@ -149,11 +162,10 @@ private:
BOOL _shouldDownloadLauncher { FALSE };
BOOL _updateLauncherAllowed { TRUE };
BOOL _shouldRestartNewLauncher { FALSE };
- BOOL _continueUpdating { FALSE };
- BOOL _willContinueUpdating { FALSE };
- BOOL _skipSplashScreen { FALSE };
+ BOOL _keepLoggingIn { FALSE };
+ BOOL _keepUpdating { FALSE };
+ ContinueActionOnStart _continueAction;
float _progressOffset { 0.0f };
float _progress { 0.0f };
CStdioFile _logFile;
};
-
diff --git a/libraries/audio-client/CMakeLists.txt b/libraries/audio-client/CMakeLists.txt
index 6ca7962c39..6b88292dd4 100644
--- a/libraries/audio-client/CMakeLists.txt
+++ b/libraries/audio-client/CMakeLists.txt
@@ -7,6 +7,11 @@ link_hifi_libraries(audio plugins)
include_hifi_library_headers(shared)
include_hifi_library_headers(networking)
+if (ANDROID)
+else ()
+ target_webrtc()
+endif ()
+
# append audio includes to our list of includes to bubble
target_include_directories(${TARGET_NAME} PUBLIC "${HIFI_LIBRARY_DIR}/audio/src")
diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp
index 04ab0f7973..c16e297c28 100644
--- a/libraries/audio-client/src/AudioClient.cpp
+++ b/libraries/audio-client/src/AudioClient.cpp
@@ -24,7 +24,7 @@
#endif
#ifdef WIN32
-#define WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN 1
#include
#include
#include
@@ -286,6 +286,7 @@ AudioClient::AudioClient() :
_shouldEchoLocally(false),
_shouldEchoToServer(false),
_isNoiseGateEnabled(true),
+ _isAECEnabled(true),
_reverb(false),
_reverbOptions(&_scriptReverbOptions),
_inputToNetworkResampler(NULL),
@@ -302,6 +303,7 @@ AudioClient::AudioClient() :
_isHeadsetPluggedIn(false),
#endif
_orientationGetter(DEFAULT_ORIENTATION_GETTER) {
+
// avoid putting a lock in the device callback
assert(_localSamplesAvailable.is_lock_free());
@@ -353,6 +355,10 @@ AudioClient::AudioClient() :
configureReverb();
+#if defined(WEBRTC_ENABLED)
+ configureWebrtc();
+#endif
+
auto nodeList = DependencyManager::get();
auto& packetReceiver = nodeList->getPacketReceiver();
packetReceiver.registerListener(PacketType::AudioStreamStats, &_stats, "processStreamStatsPacket");
@@ -1084,6 +1090,131 @@ void AudioClient::setReverbOptions(const AudioEffectOptions* options) {
}
}
+#if defined(WEBRTC_ENABLED)
+
+static void deinterleaveToFloat(const int16_t* src, float* const* dst, int numFrames, int numChannels) {
+ for (int i = 0; i < numFrames; i++) {
+ for (int ch = 0; ch < numChannels; ch++) {
+ float f = *src++;
+ f *= (1/32768.0f); // scale
+ dst[ch][i] = f; // deinterleave
+ }
+ }
+}
+
+static void interleaveToInt16(const float* const* src, int16_t* dst, int numFrames, int numChannels) {
+ for (int i = 0; i < numFrames; i++) {
+ for (int ch = 0; ch < numChannels; ch++) {
+ float f = src[ch][i];
+ f *= 32768.0f; // scale
+ f += (f < 0.0f) ? -0.5f : 0.5f; // round
+ f = std::max(std::min(f, 32767.0f), -32768.0f); // saturate
+ *dst++ = (int16_t)f; // interleave
+ }
+ }
+}
+
+void AudioClient::configureWebrtc() {
+ _apm = webrtc::AudioProcessingBuilder().Create();
+
+ webrtc::AudioProcessing::Config config;
+
+ config.pre_amplifier.enabled = false;
+ config.high_pass_filter.enabled = false;
+ config.echo_canceller.enabled = true;
+ config.echo_canceller.mobile_mode = false;
+ config.echo_canceller.use_legacy_aec = false;
+ config.noise_suppression.enabled = false;
+ config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kModerate;
+ config.voice_detection.enabled = false;
+ config.gain_controller1.enabled = false;
+ config.gain_controller2.enabled = false;
+ config.gain_controller2.fixed_digital.gain_db = 0.0f;
+ config.gain_controller2.adaptive_digital.enabled = false;
+ config.residual_echo_detector.enabled = true;
+ config.level_estimation.enabled = false;
+
+ _apm->ApplyConfig(config);
+}
+
+// rebuffer into 10ms chunks
+void AudioClient::processWebrtcFarEnd(const int16_t* samples, int numFrames, int numChannels, int sampleRate) {
+
+ const webrtc::StreamConfig streamConfig = webrtc::StreamConfig(sampleRate, numChannels);
+ const int numChunk = (int)streamConfig.num_frames();
+
+ if (sampleRate > WEBRTC_SAMPLE_RATE_MAX) {
+ qCWarning(audioclient) << "WebRTC does not support" << sampleRate << "output sample rate.";
+ return;
+ }
+ if (numChannels > WEBRTC_CHANNELS_MAX) {
+ qCWarning(audioclient) << "WebRTC does not support" << numChannels << "output channels.";
+ return;
+ }
+
+ while (numFrames > 0) {
+
+ // number of frames to fill
+ int numFill = std::min(numFrames, numChunk - _numFifoFarEnd);
+
+ // refill fifo
+ memcpy(&_fifoFarEnd[_numFifoFarEnd], samples, numFill * numChannels * sizeof(int16_t));
+ samples += numFill * numChannels;
+ numFrames -= numFill;
+ _numFifoFarEnd += numFill;
+
+ if (_numFifoFarEnd == numChunk) {
+
+ // convert audio format
+ float buffer[WEBRTC_CHANNELS_MAX][WEBRTC_FRAMES_MAX];
+ float* const buffers[WEBRTC_CHANNELS_MAX] = { buffer[0], buffer[1] };
+ deinterleaveToFloat(_fifoFarEnd, buffers, numChunk, numChannels);
+
+ // process one chunk
+ int error = _apm->ProcessReverseStream(buffers, streamConfig, streamConfig, buffers);
+ if (error != _apm->kNoError) {
+ qCWarning(audioclient) << "WebRTC ProcessReverseStream() returned ERROR:" << error;
+ }
+ _numFifoFarEnd = 0;
+ }
+ }
+}
+
+void AudioClient::processWebrtcNearEnd(int16_t* samples, int numFrames, int numChannels, int sampleRate) {
+
+ const webrtc::StreamConfig streamConfig = webrtc::StreamConfig(sampleRate, numChannels);
+ const int numChunk = (int)streamConfig.num_frames();
+
+ if (sampleRate > WEBRTC_SAMPLE_RATE_MAX) {
+ qCWarning(audioclient) << "WebRTC does not support" << sampleRate << "input sample rate.";
+ return;
+ }
+ if (numChannels > WEBRTC_CHANNELS_MAX) {
+ qCWarning(audioclient) << "WebRTC does not support" << numChannels << "input channels.";
+ return;
+ }
+ if (numFrames != numChunk) {
+ qCWarning(audioclient) << "WebRTC requires exactly 10ms of input.";
+ return;
+ }
+
+ // convert audio format
+ float buffer[WEBRTC_CHANNELS_MAX][WEBRTC_FRAMES_MAX];
+ float* const buffers[WEBRTC_CHANNELS_MAX] = { buffer[0], buffer[1] };
+ deinterleaveToFloat(samples, buffers, numFrames, numChannels);
+
+ // process one chunk
+ int error = _apm->ProcessStream(buffers, streamConfig, streamConfig, buffers);
+ if (error != _apm->kNoError) {
+ qCWarning(audioclient) << "WebRTC ProcessStream() returned ERROR:" << error;
+ } else {
+ // modify samples in-place
+ interleaveToInt16(buffers, samples, numFrames, numChannels);
+ }
+}
+
+#endif // WEBRTC_ENABLED
+
void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) {
// If there is server echo, reverb will be applied to the recieved audio stream so no need to have it here.
bool hasReverb = _reverb || _receivedAudioStream.hasReverb();
@@ -1262,6 +1393,13 @@ void AudioClient::handleMicAudioInput() {
_inputRingBuffer.readSamples(inputAudioSamples.get(), inputSamplesRequired);
+#if defined(WEBRTC_ENABLED)
+ if (_isAECEnabled) {
+ processWebrtcNearEnd(inputAudioSamples.get(), inputSamplesRequired / _inputFormat.channelCount(),
+ _inputFormat.channelCount(), _inputFormat.sampleRate());
+ }
+#endif
+
// detect loudness and clipping on the raw input
bool isClipping = false;
float loudness = computeLoudness(inputAudioSamples.get(), inputSamplesRequired, _inputFormat.channelCount(), isClipping);
@@ -1574,6 +1712,15 @@ void AudioClient::setWarnWhenMuted(bool enable, bool emitSignal) {
}
}
+void AudioClient::setAcousticEchoCancellation(bool enable, bool emitSignal) {
+ if (_isAECEnabled != enable) {
+ _isAECEnabled = enable;
+ if (emitSignal) {
+ emit acousticEchoCancellationChanged(_isAECEnabled);
+ }
+ }
+}
+
bool AudioClient::setIsStereoInput(bool isStereoInput) {
bool stereoInputChanged = false;
if (isStereoInput != _isStereoInput && _inputDeviceInfo.supportedChannelCounts().contains(2)) {
@@ -2107,15 +2254,16 @@ qint64 AudioClient::AudioOutputIODevice::readData(char * data, qint64 maxSize) {
return maxSize;
}
- // samples requested from OUTPUT_CHANNEL_COUNT
+ // max samples requested from OUTPUT_CHANNEL_COUNT
int deviceChannelCount = _audio->_outputFormat.channelCount();
- int samplesRequested = (int)(maxSize / AudioConstants::SAMPLE_SIZE) * OUTPUT_CHANNEL_COUNT / deviceChannelCount;
+ int maxSamplesRequested = (int)(maxSize / AudioConstants::SAMPLE_SIZE) * OUTPUT_CHANNEL_COUNT / deviceChannelCount;
// restrict samplesRequested to the size of our mix/scratch buffers
- samplesRequested = std::min(samplesRequested, _audio->_outputPeriod);
+ maxSamplesRequested = std::min(maxSamplesRequested, _audio->_outputPeriod);
int16_t* scratchBuffer = _audio->_outputScratchBuffer;
float* mixBuffer = _audio->_outputMixBuffer;
+ int samplesRequested = maxSamplesRequested;
int networkSamplesPopped;
if ((networkSamplesPopped = _receivedAudioStream.popSamples(samplesRequested, false)) > 0) {
qCDebug(audiostream, "Read %d samples from buffer (%d available, %d requested)", networkSamplesPopped, _receivedAudioStream.getSamplesAvailable(), samplesRequested);
@@ -2160,45 +2308,45 @@ qint64 AudioClient::AudioOutputIODevice::readData(char * data, qint64 maxSize) {
});
int samplesPopped = std::max(networkSamplesPopped, injectorSamplesPopped);
- int framesPopped = samplesPopped / AudioConstants::STEREO;
- int bytesWritten;
- if (samplesPopped > 0) {
-
- // apply output gain
- float newGain = _audio->_outputGain.load(std::memory_order_acquire);
- float oldGain = _audio->_lastOutputGain;
- _audio->_lastOutputGain = newGain;
-
- applyGainSmoothing(mixBuffer, framesPopped, oldGain, newGain);
-
- if (deviceChannelCount == OUTPUT_CHANNEL_COUNT) {
- // limit the audio
- _audio->_audioLimiter.render(mixBuffer, (int16_t*)data, framesPopped);
- } else {
- _audio->_audioLimiter.render(mixBuffer, scratchBuffer, framesPopped);
-
- // upmix or downmix to deviceChannelCount
- if (deviceChannelCount > OUTPUT_CHANNEL_COUNT) {
- int extraChannels = deviceChannelCount - OUTPUT_CHANNEL_COUNT;
- channelUpmix(scratchBuffer, (int16_t*)data, samplesPopped, extraChannels);
- } else {
- channelDownmix(scratchBuffer, (int16_t*)data, samplesPopped);
- }
- }
-
- bytesWritten = framesPopped * AudioConstants::SAMPLE_SIZE * deviceChannelCount;
- assert(bytesWritten <= maxSize);
-
- } else {
- // nothing on network, don't grab anything from injectors, and just return 0s
- memset(data, 0, maxSize);
- bytesWritten = maxSize;
+ if (samplesPopped == 0) {
+ // nothing on network, don't grab anything from injectors, and fill with silence
+ samplesPopped = maxSamplesRequested;
+ memset(mixBuffer, 0, samplesPopped * sizeof(float));
}
+ int framesPopped = samplesPopped / OUTPUT_CHANNEL_COUNT;
+
+ // apply output gain
+ float newGain = _audio->_outputGain.load(std::memory_order_acquire);
+ float oldGain = _audio->_lastOutputGain;
+ _audio->_lastOutputGain = newGain;
+
+ applyGainSmoothing(mixBuffer, framesPopped, oldGain, newGain);
+
+ // limit the audio
+ _audio->_audioLimiter.render(mixBuffer, scratchBuffer, framesPopped);
+
+#if defined(WEBRTC_ENABLED)
+ if (_audio->_isAECEnabled) {
+ _audio->processWebrtcFarEnd(scratchBuffer, framesPopped, OUTPUT_CHANNEL_COUNT, _audio->_outputFormat.sampleRate());
+ }
+#endif
+
+ // if required, upmix or downmix to deviceChannelCount
+ if (deviceChannelCount == OUTPUT_CHANNEL_COUNT) {
+ memcpy(data, scratchBuffer, samplesPopped * AudioConstants::SAMPLE_SIZE);
+ } else if (deviceChannelCount > OUTPUT_CHANNEL_COUNT) {
+ int extraChannels = deviceChannelCount - OUTPUT_CHANNEL_COUNT;
+ channelUpmix(scratchBuffer, (int16_t*)data, samplesPopped, extraChannels);
+ } else {
+ channelDownmix(scratchBuffer, (int16_t*)data, samplesPopped);
+ }
+ int bytesWritten = framesPopped * AudioConstants::SAMPLE_SIZE * deviceChannelCount;
+ assert(bytesWritten <= maxSize);
// send output buffer for recording
if (_audio->_isRecording) {
Lock lock(_recordMutex);
- _audio->_audioFileWav.addRawAudioChunk(reinterpret_cast(scratchBuffer), bytesWritten);
+ _audio->_audioFileWav.addRawAudioChunk(data, bytesWritten);
}
int bytesAudioOutputUnplayed = _audio->_audioOutput->bufferSize() - _audio->_audioOutput->bytesFree();
diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h
index decf0f7751..ab12393ebf 100644
--- a/libraries/audio-client/src/AudioClient.h
+++ b/libraries/audio-client/src/AudioClient.h
@@ -29,6 +29,7 @@
#include
#include
#include
+#include
#include
#include
@@ -215,6 +216,9 @@ public slots:
void setWarnWhenMuted(bool isNoiseGateEnabled, bool emitSignal = true);
bool isWarnWhenMutedEnabled() const { return _warnWhenMuted; }
+ void setAcousticEchoCancellation(bool isAECEnabled, bool emitSignal = true);
+ bool isAcousticEchoCancellationEnabled() const { return _isAECEnabled; }
+
virtual bool getLocalEcho() override { return _shouldEchoLocally; }
virtual void setLocalEcho(bool localEcho) override { _shouldEchoLocally = localEcho; }
virtual void toggleLocalEcho() override { _shouldEchoLocally = !_shouldEchoLocally; }
@@ -256,6 +260,7 @@ signals:
void muteToggled(bool muted);
void noiseReductionChanged(bool noiseReductionEnabled);
void warnWhenMutedChanged(bool warnWhenMutedEnabled);
+ void acousticEchoCancellationChanged(bool acousticEchoCancellationEnabled);
void mutedByMixer();
void inputReceived(const QByteArray& inputSamples);
void inputLoudnessChanged(float loudness, bool isClipping);
@@ -377,6 +382,7 @@ private:
bool _shouldEchoToServer;
bool _isNoiseGateEnabled;
bool _warnWhenMuted;
+ bool _isAECEnabled;
bool _reverb;
AudioEffectOptions _scriptReverbOptions;
@@ -414,9 +420,23 @@ private:
// Adds Reverb
void configureReverb();
void updateReverbOptions();
-
void handleLocalEchoAndReverb(QByteArray& inputByteArray);
+#if defined(WEBRTC_ENABLED)
+ static const int WEBRTC_SAMPLE_RATE_MAX = 96000;
+ static const int WEBRTC_CHANNELS_MAX = 2;
+ static const int WEBRTC_FRAMES_MAX = webrtc::AudioProcessing::kChunkSizeMs * WEBRTC_SAMPLE_RATE_MAX / 1000;
+
+ webrtc::AudioProcessing* _apm { nullptr };
+
+ int16_t _fifoFarEnd[WEBRTC_CHANNELS_MAX * WEBRTC_FRAMES_MAX] {};
+ int _numFifoFarEnd = 0; // numFrames saved in fifo
+
+ void configureWebrtc();
+ void processWebrtcFarEnd(const int16_t* samples, int numFrames, int numChannels, int sampleRate);
+ void processWebrtcNearEnd(int16_t* samples, int numFrames, int numChannels, int sampleRate);
+#endif
+
bool switchInputToAudioDevice(const QAudioDeviceInfo inputDeviceInfo, bool isShutdownRequest = false);
bool switchOutputToAudioDevice(const QAudioDeviceInfo outputDeviceInfo, bool isShutdownRequest = false);
diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp
index 5af98100dc..a7e4a0ae9f 100644
--- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp
+++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp
@@ -598,26 +598,6 @@ void Avatar::measureMotionDerivatives(float deltaTime) {
}
}
-enum TextRendererType {
- CHAT,
- DISPLAYNAME
-};
-
-static TextRenderer3D* textRenderer(TextRendererType type) {
- static TextRenderer3D* chatRenderer = TextRenderer3D::getInstance(SANS_FONT_FAMILY, -1,
- false, SHADOW_EFFECT);
- static TextRenderer3D* displayNameRenderer = TextRenderer3D::getInstance(SANS_FONT_FAMILY);
-
- switch(type) {
- case CHAT:
- return chatRenderer;
- case DISPLAYNAME:
- return displayNameRenderer;
- }
-
- return displayNameRenderer;
-}
-
void Avatar::metaBlendshapeOperator(render::ItemID renderItemID, int blendshapeNumber, const QVector& blendshapeOffsets,
const QVector& blendedMeshSizes, const render::ItemIDs& subItemIDs) {
render::Transaction transaction;
@@ -1050,7 +1030,6 @@ void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& view, const
|| (glm::dot(view.getDirection(), getDisplayNamePosition() - view.getPosition()) <= CLIP_DISTANCE)) {
return;
}
- auto renderer = textRenderer(DISPLAYNAME);
// optionally render timing stats for this avatar with the display name
QString renderedDisplayName = _displayName;
@@ -1065,7 +1044,8 @@ void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& view, const
}
// Compute display name extent/position offset
- const glm::vec2 extent = renderer->computeExtent(renderedDisplayName);
+ static TextRenderer3D* displayNameRenderer = TextRenderer3D::getInstance(ROBOTO_FONT_FAMILY);
+ const glm::vec2 extent = displayNameRenderer->computeExtent(renderedDisplayName);
if (!glm::any(glm::isCompNull(extent, EPSILON))) {
const QRect nameDynamicRect = QRect(0, 0, (int)extent.x, (int)extent.y);
const int text_x = -nameDynamicRect.width() / 2;
@@ -1104,11 +1084,11 @@ void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& view, const
QByteArray nameUTF8 = renderedDisplayName.toLocal8Bit();
// Render text slightly in front to avoid z-fighting
- textTransform.postTranslate(glm::vec3(0.0f, 0.0f, SLIGHTLY_IN_FRONT * renderer->getFontSize()));
+ textTransform.postTranslate(glm::vec3(0.0f, 0.0f, SLIGHTLY_IN_FRONT * displayNameRenderer->getFontSize()));
batch.setModelTransform(textTransform);
{
PROFILE_RANGE_BATCH(batch, __FUNCTION__":renderText");
- renderer->draw(batch, text_x, -text_y, nameUTF8.data(), textColor, glm::vec2(-1.0f), true, forward);
+ displayNameRenderer->draw(batch, text_x, -text_y, glm::vec2(-1.0f), nameUTF8.data(), textColor, true, forward);
}
}
}
diff --git a/libraries/embedded-webserver/src/HTTPConnection.cpp b/libraries/embedded-webserver/src/HTTPConnection.cpp
index f65cd87f6e..4c00ba676c 100644
--- a/libraries/embedded-webserver/src/HTTPConnection.cpp
+++ b/libraries/embedded-webserver/src/HTTPConnection.cpp
@@ -22,6 +22,7 @@
#include "HTTPManager.h"
const char* HTTPConnection::StatusCode200 = "200 OK";
+const char* HTTPConnection::StatusCode204 = "204 No Content";
const char* HTTPConnection::StatusCode301 = "301 Moved Permanently";
const char* HTTPConnection::StatusCode302 = "302 Found";
const char* HTTPConnection::StatusCode400 = "400 Bad Request";
diff --git a/libraries/embedded-webserver/src/HTTPConnection.h b/libraries/embedded-webserver/src/HTTPConnection.h
index 4b42acf296..eb4d2dd8c3 100644
--- a/libraries/embedded-webserver/src/HTTPConnection.h
+++ b/libraries/embedded-webserver/src/HTTPConnection.h
@@ -45,6 +45,7 @@ class HTTPConnection : public QObject {
public:
static const char* StatusCode200;
+ static const char* StatusCode204;
static const char* StatusCode301;
static const char* StatusCode302;
static const char* StatusCode400;
diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp
index 11e369b532..5ebb0e2fd0 100644
--- a/libraries/entities-renderer/src/RenderableEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp
@@ -483,3 +483,26 @@ glm::vec4 EntityRenderer::calculatePulseColor(const glm::vec4& color, const Puls
return result;
}
+
+glm::vec3 EntityRenderer::calculatePulseColor(const glm::vec3& color, const PulsePropertyGroup& pulseProperties, quint64 start) {
+ if (pulseProperties.getPeriod() == 0.0f || (pulseProperties.getColorMode() == PulseMode::NONE && pulseProperties.getAlphaMode() == PulseMode::NONE)) {
+ return color;
+ }
+
+ float t = ((float)(usecTimestampNow() - start)) / ((float)USECS_PER_SECOND);
+ float pulse = 0.5f * (cosf(t * (2.0f * (float)M_PI) / pulseProperties.getPeriod()) + 1.0f) * (pulseProperties.getMax() - pulseProperties.getMin()) + pulseProperties.getMin();
+ float outPulse = (1.0f - pulse);
+
+ glm::vec3 result = color;
+ if (pulseProperties.getColorMode() == PulseMode::IN_PHASE) {
+ result.r *= pulse;
+ result.g *= pulse;
+ result.b *= pulse;
+ } else if (pulseProperties.getColorMode() == PulseMode::OUT_PHASE) {
+ result.r *= outPulse;
+ result.g *= outPulse;
+ result.b *= outPulse;
+ }
+
+ return result;
+}
diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h
index 1db4cfdf53..7f06547cdc 100644
--- a/libraries/entities-renderer/src/RenderableEntityItem.h
+++ b/libraries/entities-renderer/src/RenderableEntityItem.h
@@ -61,6 +61,7 @@ public:
virtual scriptable::ScriptableModelBase getScriptableModel() override { return scriptable::ScriptableModelBase(); }
static glm::vec4 calculatePulseColor(const glm::vec4& color, const PulsePropertyGroup& pulseProperties, quint64 start);
+ static glm::vec3 calculatePulseColor(const glm::vec3& color, const PulsePropertyGroup& pulseProperties, quint64 start);
virtual uint32_t metaFetchMetaSubItems(ItemIDs& subItems) const override;
virtual Item::Bound getBound() override;
diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp
index 0ead1de71a..d8099df0fe 100644
--- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp
@@ -30,7 +30,7 @@ const float LINE_SCALE_RATIO = 1.2f;
TextEntityRenderer::TextEntityRenderer(const EntityItemPointer& entity) :
Parent(entity),
- _textRenderer(TextRenderer3D::getInstance(SANS_FONT_FAMILY, FIXED_FONT_POINT_SIZE / 2.0f)) {
+ _textRenderer(TextRenderer3D::getInstance(ROBOTO_FONT_FAMILY)) {
auto geometryCache = DependencyManager::get();
if (geometryCache) {
_geometryID = geometryCache->allocateID();
@@ -96,7 +96,6 @@ bool TextEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoint
if (_text != entity->getText()) {
return true;
}
-
if (_lineHeight != entity->getLineHeight()) {
return true;
}
@@ -104,15 +103,12 @@ bool TextEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoint
if (_textColor != toGlm(entity->getTextColor())) {
return true;
}
-
if (_textAlpha != entity->getTextAlpha()) {
return true;
}
-
if (_backgroundColor != toGlm(entity->getBackgroundColor())) {
return true;
}
-
if (_backgroundAlpha != entity->getBackgroundAlpha()) {
return true;
}
@@ -128,15 +124,12 @@ bool TextEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoint
if (_leftMargin != entity->getLeftMargin()) {
return true;
}
-
if (_rightMargin != entity->getRightMargin()) {
return true;
}
-
if (_topMargin != entity->getTopMargin()) {
return true;
}
-
if (_bottomMargin != entity->getBottomMargin()) {
return true;
}
@@ -145,6 +138,20 @@ bool TextEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoint
return true;
}
+ if (_font != entity->getFont()) {
+ return true;
+ }
+
+ if (_effect != entity->getTextEffect()) {
+ return true;
+ }
+ if (_effectColor != toGlm(entity->getTextEffectColor())) {
+ return true;
+ }
+ if (_effectThickness != entity->getTextEffectThickness()) {
+ return true;
+ }
+
if (_pulseProperties != entity->getPulseProperties()) {
return true;
}
@@ -179,6 +186,10 @@ void TextEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointe
_topMargin = entity->getTopMargin();
_bottomMargin = entity->getBottomMargin();
_unlit = entity->getUnlit();
+ _font = entity->getFont();
+ _effect = entity->getTextEffect();
+ _effectColor = toGlm(entity->getTextEffectColor());
+ _effectThickness = entity->getTextEffectThickness();
updateTextRenderItem();
});
}
@@ -282,7 +293,22 @@ ItemKey entities::TextPayload::getKey() const {
auto renderable = entityTreeRenderer->renderableForEntityId(_entityID);
if (renderable) {
auto textRenderable = std::static_pointer_cast(renderable);
- return ItemKey::Builder(textRenderable->getKey()).withoutMetaCullGroup().withSubMetaCulled();
+
+ // Similar to RenderableEntityItem::getKey()
+ ItemKey::Builder builder = ItemKey::Builder().withTypeShape().withTypeMeta().withTagBits(textRenderable->getTagMask()).withLayer(textRenderable->getHifiRenderLayer());
+ builder.withSubMetaCulled();
+
+ if (textRenderable->isTextTransparent()) {
+ builder.withTransparent();
+ } else if (textRenderable->_canCastShadow) {
+ builder.withShadowCaster();
+ }
+
+ if (!textRenderable->_visible) {
+ builder.withInvisible();
+ }
+
+ return builder;
}
}
return ItemKey::Builder::opaqueShape();
@@ -342,27 +368,40 @@ void entities::TextPayload::render(RenderArgs* args) {
}
auto textRenderable = std::static_pointer_cast(renderable);
- glm::vec4 textColor;
Transform modelTransform;
- BillboardMode billboardMode;
- float lineHeight, leftMargin, rightMargin, topMargin, bottomMargin;
- QString text;
glm::vec3 dimensions;
+ BillboardMode billboardMode;
+
+ QString text;
+ glm::vec4 textColor;
+ QString font;
+ TextEffect effect;
+ glm::vec3 effectColor;
+ float effectThickness;
+ float lineHeight, leftMargin, rightMargin, topMargin, bottomMargin;
bool forward;
textRenderable->withReadLock([&] {
modelTransform = textRenderable->_renderTransform;
+ dimensions = textRenderable->_dimensions;
billboardMode = textRenderable->_billboardMode;
+
+ text = textRenderable->_text;
+ font = textRenderable->_font;
+ effect = textRenderable->_effect;
+ effectThickness = textRenderable->_effectThickness;
+
lineHeight = textRenderable->_lineHeight;
leftMargin = textRenderable->_leftMargin;
rightMargin = textRenderable->_rightMargin;
topMargin = textRenderable->_topMargin;
bottomMargin = textRenderable->_bottomMargin;
- text = textRenderable->_text;
- dimensions = textRenderable->_dimensions;
float fadeRatio = textRenderable->_isFading ? Interpolate::calculateFadeRatio(textRenderable->_fadeStartTime) : 1.0f;
textColor = glm::vec4(textRenderable->_textColor, fadeRatio * textRenderable->_textAlpha);
textColor = EntityRenderer::calculatePulseColor(textColor, textRenderable->_pulseProperties, textRenderable->_created);
+
+ effectColor = EntityRenderer::calculatePulseColor(textRenderable->_effectColor, textRenderable->_pulseProperties, textRenderable->_created);
+
forward = textRenderable->_renderLayer != RenderLayer::WORLD || args->_renderMethod == render::Args::FORWARD;
});
@@ -378,7 +417,9 @@ void entities::TextPayload::render(RenderArgs* args) {
batch.setModelTransform(modelTransform);
glm::vec2 bounds = glm::vec2(dimensions.x - (leftMargin + rightMargin), dimensions.y - (topMargin + bottomMargin));
- textRenderer->draw(batch, leftMargin / scale, -topMargin / scale, text, textColor, bounds / scale, textRenderable->_unlit, forward);
+ textRenderer->draw(batch, leftMargin / scale, -topMargin / scale, bounds / scale, scale,
+ text, font, textColor, effectColor, effectThickness, effect,
+ textRenderable->_unlit, forward);
}
namespace render {
diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.h b/libraries/entities-renderer/src/RenderableTextEntityItem.h
index c62851a876..63cf3e6e9e 100644
--- a/libraries/entities-renderer/src/RenderableTextEntityItem.h
+++ b/libraries/entities-renderer/src/RenderableTextEntityItem.h
@@ -67,6 +67,11 @@ private:
BillboardMode _billboardMode;
glm::vec3 _dimensions;
+ QString _font { "" };
+ TextEffect _effect { TextEffect::NO_EFFECT };
+ glm::vec3 _effectColor { 0 };
+ float _effectThickness { 0.0f };
+
int _geometryID { 0 };
std::shared_ptr _textPayload;
diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp
index b89788040f..42ce276aca 100644
--- a/libraries/entities/src/EntityItemProperties.cpp
+++ b/libraries/entities/src/EntityItemProperties.cpp
@@ -299,6 +299,24 @@ void EntityItemProperties::setAvatarPriorityFromString(const QString& mode) {
}
}
+inline void addTextEffect(QHash& lookup, TextEffect effect) { lookup[TextEffectHelpers::getNameForTextEffect(effect)] = effect; }
+const QHash stringToTextEffectLookup = [] {
+ QHash toReturn;
+ addTextEffect(toReturn, TextEffect::NO_EFFECT);
+ addTextEffect(toReturn, TextEffect::OUTLINE_EFFECT);
+ addTextEffect(toReturn, TextEffect::OUTLINE_WITH_FILL_EFFECT);
+ addTextEffect(toReturn, TextEffect::SHADOW_EFFECT);
+ return toReturn;
+}();
+QString EntityItemProperties::getTextEffectAsString() const { return TextEffectHelpers::getNameForTextEffect(_textEffect); }
+void EntityItemProperties::setTextEffectFromString(const QString& effect) {
+ auto textEffectItr = stringToTextEffectLookup.find(effect.toLower());
+ if (textEffectItr != stringToTextEffectLookup.end()) {
+ _textEffect = textEffectItr.value();
+ _textEffectChanged = true;
+ }
+}
+
QString getCollisionGroupAsString(uint16_t group) {
switch (group) {
case USER_COLLISION_GROUP_DYNAMIC:
@@ -528,6 +546,10 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
CHECK_PROPERTY_CHANGE(PROP_TOP_MARGIN, topMargin);
CHECK_PROPERTY_CHANGE(PROP_BOTTOM_MARGIN, bottomMargin);
CHECK_PROPERTY_CHANGE(PROP_UNLIT, unlit);
+ CHECK_PROPERTY_CHANGE(PROP_FONT, font);
+ CHECK_PROPERTY_CHANGE(PROP_TEXT_EFFECT, textEffect);
+ CHECK_PROPERTY_CHANGE(PROP_TEXT_EFFECT_COLOR, textEffectColor);
+ CHECK_PROPERTY_CHANGE(PROP_TEXT_EFFECT_THICKNESS, textEffectThickness);
// Zone
changedProperties += _keyLight.getChangedProperties();
@@ -1287,6 +1309,11 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
* @property {number} bottomMargin=0.0 - The bottom margin, in meters.
* @property {boolean} unlit=false - true
if the entity should be unaffected by lighting. Otherwise, the text
* is lit by the keylight and local lights.
+ * @property {string} font="" - The text is rendered with this font. Can be one of the following: Courier
Inconsolata, Roboto
, Timeless
, or a path to a .sdff file.
+ * @property {TextEffect} textEffect="none" - The effect that is applied to the text.
+ * @property {Color} textEffectColor=255,255,255 - The color of the effect.
+ * @property {number} textEffectThickness=0.2 - The magnitude of the text effect, range 0.0
– 0.5
.
* @property {BillboardMode} billboardMode="none" - Whether the entity is billboarded to face the camera.
* @property {boolean} faceCamera - true
if billboardMode
is "yaw"
, false
* if it isn't. Setting this property to false
sets the billboardMode
to "none"
.
@@ -1727,6 +1754,10 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_TOP_MARGIN, topMargin);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_BOTTOM_MARGIN, bottomMargin);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_UNLIT, unlit);
+ COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_FONT, font);
+ COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_TEXT_EFFECT, textEffect, getTextEffectAsString());
+ COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_TEXT_EFFECT_COLOR, textEffectColor, u8vec3Color);
+ COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_TEXT_EFFECT_THICKNESS, textEffectThickness);
}
// Zones only
@@ -2103,6 +2134,10 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
COPY_PROPERTY_FROM_QSCRIPTVALUE(topMargin, float, setTopMargin);
COPY_PROPERTY_FROM_QSCRIPTVALUE(bottomMargin, float, setBottomMargin);
COPY_PROPERTY_FROM_QSCRIPTVALUE(unlit, bool, setUnlit);
+ COPY_PROPERTY_FROM_QSCRIPTVALUE(font, QString, setFont);
+ COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(textEffect, TextEffect);
+ COPY_PROPERTY_FROM_QSCRIPTVALUE(textEffectColor, u8vec3Color, setTextEffectColor);
+ COPY_PROPERTY_FROM_QSCRIPTVALUE(textEffectThickness, float, setTextEffectThickness);
// Zone
_keyLight.copyFromScriptValue(object, _defaultSettings);
@@ -2387,6 +2422,10 @@ void EntityItemProperties::merge(const EntityItemProperties& other) {
COPY_PROPERTY_IF_CHANGED(topMargin);
COPY_PROPERTY_IF_CHANGED(bottomMargin);
COPY_PROPERTY_IF_CHANGED(unlit);
+ COPY_PROPERTY_IF_CHANGED(font);
+ COPY_PROPERTY_IF_CHANGED(textEffect);
+ COPY_PROPERTY_IF_CHANGED(textEffectColor);
+ COPY_PROPERTY_IF_CHANGED(textEffectThickness);
// Zone
_keyLight.merge(other._keyLight);
@@ -2746,6 +2785,10 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr
ADD_PROPERTY_TO_MAP(PROP_TOP_MARGIN, TopMargin, topMargin, float);
ADD_PROPERTY_TO_MAP(PROP_BOTTOM_MARGIN, BottomMargin, bottomMargin, float);
ADD_PROPERTY_TO_MAP(PROP_UNLIT, Unlit, unlit, bool);
+ ADD_PROPERTY_TO_MAP(PROP_FONT, Font, font, QString);
+ ADD_PROPERTY_TO_MAP(PROP_TEXT_EFFECT, TextEffect, textEffect, TextEffect);
+ ADD_PROPERTY_TO_MAP(PROP_TEXT_EFFECT_COLOR, TextEffectColor, textEffectColor, u8vec3Color);
+ ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_TEXT_EFFECT_THICKNESS, TextEffectThickness, textEffectThickness, float, 0.0, 0.5);
// Zone
{ // Keylight
@@ -3178,6 +3221,10 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy
APPEND_ENTITY_PROPERTY(PROP_TOP_MARGIN, properties.getTopMargin());
APPEND_ENTITY_PROPERTY(PROP_BOTTOM_MARGIN, properties.getBottomMargin());
APPEND_ENTITY_PROPERTY(PROP_UNLIT, properties.getUnlit());
+ APPEND_ENTITY_PROPERTY(PROP_FONT, properties.getFont());
+ APPEND_ENTITY_PROPERTY(PROP_TEXT_EFFECT, (uint32_t)properties.getTextEffect());
+ APPEND_ENTITY_PROPERTY(PROP_TEXT_EFFECT_COLOR, properties.getTextEffectColor());
+ APPEND_ENTITY_PROPERTY(PROP_TEXT_EFFECT_THICKNESS, properties.getTextEffectThickness());
}
if (properties.getType() == EntityTypes::Zone) {
@@ -3657,6 +3704,10 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TOP_MARGIN, float, setTopMargin);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BOTTOM_MARGIN, float, setBottomMargin);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_UNLIT, bool, setUnlit);
+ READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_FONT, QString, setFont);
+ READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TEXT_EFFECT, TextEffect, setTextEffect);
+ READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TEXT_EFFECT_COLOR, u8vec3Color, setTextEffectColor);
+ READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TEXT_EFFECT_THICKNESS, float, setTextEffectThickness);
}
if (properties.getType() == EntityTypes::Zone) {
@@ -4050,6 +4101,10 @@ void EntityItemProperties::markAllChanged() {
_topMarginChanged = true;
_bottomMarginChanged = true;
_unlitChanged = true;
+ _fontChanged = true;
+ _textEffectChanged = true;
+ _textEffectColorChanged = true;
+ _textEffectThicknessChanged = true;
// Zone
_keyLight.markAllChanged();
@@ -4642,6 +4697,18 @@ QList EntityItemProperties::listChangedProperties() {
if (unlitChanged()) {
out += "unlit";
}
+ if (fontChanged()) {
+ out += "font";
+ }
+ if (textEffectChanged()) {
+ out += "textEffect";
+ }
+ if (textEffectColorChanged()) {
+ out += "textEffectColor";
+ }
+ if (textEffectThicknessChanged()) {
+ out += "textEffectThickness";
+ }
// Zone
getKeyLight().listChangedProperties(out);
diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h
index 456ee3cdec..63d8183899 100644
--- a/libraries/entities/src/EntityItemProperties.h
+++ b/libraries/entities/src/EntityItemProperties.h
@@ -32,6 +32,7 @@
#include
#include
#include
+#include "FontFamilies.h"
#include "EntityItemID.h"
#include "EntityItemPropertiesDefaults.h"
@@ -64,6 +65,7 @@
#include "PrimitiveMode.h"
#include "WebInputMode.h"
#include "GizmoType.h"
+#include "TextEffect.h"
const quint64 UNKNOWN_CREATED_TIME = 0;
@@ -315,6 +317,10 @@ public:
DEFINE_PROPERTY_REF(PROP_TOP_MARGIN, TopMargin, topMargin, float, TextEntityItem::DEFAULT_MARGIN);
DEFINE_PROPERTY_REF(PROP_BOTTOM_MARGIN, BottomMargin, bottomMargin, float, TextEntityItem::DEFAULT_MARGIN);
DEFINE_PROPERTY_REF(PROP_UNLIT, Unlit, unlit, bool, false);
+ DEFINE_PROPERTY_REF(PROP_FONT, Font, font, QString, ROBOTO_FONT_FAMILY);
+ DEFINE_PROPERTY_REF_ENUM(PROP_TEXT_EFFECT, TextEffect, textEffect, TextEffect, TextEffect::NO_EFFECT);
+ DEFINE_PROPERTY_REF(PROP_TEXT_EFFECT_COLOR, TextEffectColor, textEffectColor, u8vec3Color, TextEntityItem::DEFAULT_TEXT_COLOR);
+ DEFINE_PROPERTY(PROP_TEXT_EFFECT_THICKNESS, TextEffectThickness, textEffectThickness, float, TextEntityItem::DEFAULT_TEXT_EFFECT_THICKNESS);
// Zone
DEFINE_PROPERTY_GROUP(KeyLight, keyLight, KeyLightPropertyGroup);
diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h
index 8d3833f364..d5af337a7d 100644
--- a/libraries/entities/src/EntityPropertyFlags.h
+++ b/libraries/entities/src/EntityPropertyFlags.h
@@ -244,6 +244,10 @@ enum EntityPropertyList {
PROP_TOP_MARGIN = PROP_DERIVED_8,
PROP_BOTTOM_MARGIN = PROP_DERIVED_9,
PROP_UNLIT = PROP_DERIVED_10,
+ PROP_FONT = PROP_DERIVED_11,
+ PROP_TEXT_EFFECT = PROP_DERIVED_12,
+ PROP_TEXT_EFFECT_COLOR = PROP_DERIVED_13,
+ PROP_TEXT_EFFECT_THICKNESS = PROP_DERIVED_14,
// Zone
// Keylight
diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h
index 0442756300..ca9108852f 100644
--- a/libraries/entities/src/EntityScriptingInterface.h
+++ b/libraries/entities/src/EntityScriptingInterface.h
@@ -78,7 +78,7 @@ public:
bool accurate { true };
QUuid entityID;
float distance { 0.0f };
- BoxFace face;
+ BoxFace face { UNKNOWN_FACE };
glm::vec3 intersection;
glm::vec3 surfaceNormal;
QVariantMap extraInfo;
@@ -94,7 +94,7 @@ public:
QUuid entityID;
float distance { 0.0f };
float parabolicDistance { 0.0f };
- BoxFace face;
+ BoxFace face { UNKNOWN_FACE };
glm::vec3 intersection;
glm::vec3 surfaceNormal;
QVariantMap extraInfo;
diff --git a/libraries/entities/src/TextEntityItem.cpp b/libraries/entities/src/TextEntityItem.cpp
index 08200084f4..d237071a17 100644
--- a/libraries/entities/src/TextEntityItem.cpp
+++ b/libraries/entities/src/TextEntityItem.cpp
@@ -29,6 +29,7 @@ const glm::u8vec3 TextEntityItem::DEFAULT_TEXT_COLOR = { 255, 255, 255 };
const float TextEntityItem::DEFAULT_TEXT_ALPHA = 1.0f;
const glm::u8vec3 TextEntityItem::DEFAULT_BACKGROUND_COLOR = { 0, 0, 0};
const float TextEntityItem::DEFAULT_MARGIN = 0.0f;
+const float TextEntityItem::DEFAULT_TEXT_EFFECT_THICKNESS = 0.2f;
EntityItemPointer TextEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
EntityItemPointer entity(new TextEntityItem(entityID), [](EntityItem* ptr) { ptr->deleteLater(); });
@@ -65,6 +66,10 @@ EntityItemProperties TextEntityItem::getProperties(const EntityPropertyFlags& de
COPY_ENTITY_PROPERTY_TO_PROPERTIES(topMargin, getTopMargin);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(bottomMargin, getBottomMargin);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(unlit, getUnlit);
+ COPY_ENTITY_PROPERTY_TO_PROPERTIES(font, getFont);
+ COPY_ENTITY_PROPERTY_TO_PROPERTIES(textEffect, getTextEffect);
+ COPY_ENTITY_PROPERTY_TO_PROPERTIES(textEffectColor, getTextEffectColor);
+ COPY_ENTITY_PROPERTY_TO_PROPERTIES(textEffectThickness, getTextEffectThickness);
return properties;
}
@@ -89,6 +94,10 @@ bool TextEntityItem::setProperties(const EntityItemProperties& properties) {
SET_ENTITY_PROPERTY_FROM_PROPERTIES(topMargin, setTopMargin);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(bottomMargin, setBottomMargin);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(unlit, setUnlit);
+ SET_ENTITY_PROPERTY_FROM_PROPERTIES(font, setFont);
+ SET_ENTITY_PROPERTY_FROM_PROPERTIES(textEffect, setTextEffect);
+ SET_ENTITY_PROPERTY_FROM_PROPERTIES(textEffectColor, setTextEffectColor);
+ SET_ENTITY_PROPERTY_FROM_PROPERTIES(textEffectThickness, setTextEffectThickness);
if (somethingChanged) {
bool wantDebug = false;
@@ -132,6 +141,10 @@ int TextEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
READ_ENTITY_PROPERTY(PROP_TOP_MARGIN, float, setTopMargin);
READ_ENTITY_PROPERTY(PROP_BOTTOM_MARGIN, float, setBottomMargin);
READ_ENTITY_PROPERTY(PROP_UNLIT, bool, setUnlit);
+ READ_ENTITY_PROPERTY(PROP_FONT, QString, setFont);
+ READ_ENTITY_PROPERTY(PROP_TEXT_EFFECT, TextEffect, setTextEffect);
+ READ_ENTITY_PROPERTY(PROP_TEXT_EFFECT_COLOR, glm::u8vec3, setTextEffectColor);
+ READ_ENTITY_PROPERTY(PROP_TEXT_EFFECT_THICKNESS, float, setTextEffectThickness);
return bytesRead;
}
@@ -153,6 +166,10 @@ EntityPropertyFlags TextEntityItem::getEntityProperties(EncodeBitstreamParams& p
requestedProperties += PROP_TOP_MARGIN;
requestedProperties += PROP_BOTTOM_MARGIN;
requestedProperties += PROP_UNLIT;
+ requestedProperties += PROP_FONT;
+ requestedProperties += PROP_TEXT_EFFECT;
+ requestedProperties += PROP_TEXT_EFFECT_COLOR;
+ requestedProperties += PROP_TEXT_EFFECT_THICKNESS;
return requestedProperties;
}
@@ -184,6 +201,10 @@ void TextEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits
APPEND_ENTITY_PROPERTY(PROP_TOP_MARGIN, getTopMargin());
APPEND_ENTITY_PROPERTY(PROP_BOTTOM_MARGIN, getBottomMargin());
APPEND_ENTITY_PROPERTY(PROP_UNLIT, getUnlit());
+ APPEND_ENTITY_PROPERTY(PROP_FONT, getFont());
+ APPEND_ENTITY_PROPERTY(PROP_TEXT_EFFECT, (uint32_t)getTextEffect());
+ APPEND_ENTITY_PROPERTY(PROP_TEXT_EFFECT_COLOR, getTextEffectColor());
+ APPEND_ENTITY_PROPERTY(PROP_TEXT_EFFECT_THICKNESS, getTextEffectThickness());
}
glm::vec3 TextEntityItem::getRaycastDimensions() const {
@@ -255,12 +276,10 @@ void TextEntityItem::setText(const QString& value) {
});
}
-QString TextEntityItem::getText() const {
- QString result;
- withReadLock([&] {
- result = _text;
+QString TextEntityItem::getText() const {
+ return resultWithReadLock([&] {
+ return _text;
});
- return result;
}
void TextEntityItem::setLineHeight(float value) {
@@ -399,6 +418,54 @@ bool TextEntityItem::getUnlit() const {
});
}
+void TextEntityItem::setFont(const QString& value) {
+ withWriteLock([&] {
+ _font = value;
+ });
+}
+
+QString TextEntityItem::getFont() const {
+ return resultWithReadLock([&] {
+ return _font;
+ });
+}
+
+void TextEntityItem::setTextEffect(TextEffect value) {
+ withWriteLock([&] {
+ _effect = value;
+ });
+}
+
+TextEffect TextEntityItem::getTextEffect() const {
+ return resultWithReadLock([&] {
+ return _effect;
+ });
+}
+
+void TextEntityItem::setTextEffectColor(const glm::u8vec3& value) {
+ withWriteLock([&] {
+ _effectColor = value;
+ });
+}
+
+glm::u8vec3 TextEntityItem::getTextEffectColor() const {
+ return resultWithReadLock([&] {
+ return _effectColor;
+ });
+}
+
+void TextEntityItem::setTextEffectThickness(float value) {
+ withWriteLock([&] {
+ _effectThickness = value;
+ });
+}
+
+float TextEntityItem::getTextEffectThickness() const {
+ return resultWithReadLock([&] {
+ return _effectThickness;
+ });
+}
+
PulsePropertyGroup TextEntityItem::getPulseProperties() const {
return resultWithReadLock([&] {
return _pulseProperties;
diff --git a/libraries/entities/src/TextEntityItem.h b/libraries/entities/src/TextEntityItem.h
index a962482cde..5e3f6d7c02 100644
--- a/libraries/entities/src/TextEntityItem.h
+++ b/libraries/entities/src/TextEntityItem.h
@@ -15,6 +15,7 @@
#include "EntityItem.h"
#include "PulsePropertyGroup.h"
+#include "TextEffect.h"
class TextEntityItem : public EntityItem {
public:
@@ -100,6 +101,19 @@ public:
bool getUnlit() const;
void setUnlit(bool value);
+ void setFont(const QString& value);
+ QString getFont() const;
+
+ TextEffect getTextEffect() const;
+ void setTextEffect(TextEffect value);
+
+ glm::u8vec3 getTextEffectColor() const;
+ void setTextEffectColor(const glm::u8vec3& value);
+
+ static const float DEFAULT_TEXT_EFFECT_THICKNESS;
+ float getTextEffectThickness() const;
+ void setTextEffectThickness(float value);
+
PulsePropertyGroup getPulseProperties() const;
private:
@@ -117,6 +131,11 @@ private:
float _topMargin;
float _bottomMargin;
bool _unlit;
+
+ QString _font;
+ TextEffect _effect;
+ glm::u8vec3 _effectColor;
+ float _effectThickness;
};
#endif // hifi_TextEntityItem_h
diff --git a/libraries/gl/src/gl/GLHelpers.cpp b/libraries/gl/src/gl/GLHelpers.cpp
index c9f17b686d..b2c98e91d3 100644
--- a/libraries/gl/src/gl/GLHelpers.cpp
+++ b/libraries/gl/src/gl/GLHelpers.cpp
@@ -148,6 +148,33 @@ static bool setupPixelFormatSimple(HDC hdc) {
#endif
+
+const gl::ContextInfo& gl::ContextInfo::get(bool init) {
+ static gl::ContextInfo INSTANCE;
+ if (init) {
+ static std::once_flag once;
+ std::call_once(once, [&] {
+ INSTANCE.init();
+ });
+ }
+ return INSTANCE;
+}
+
+gl::ContextInfo& gl::ContextInfo::init() {
+ if (glGetString) {
+ version = (const char*)glGetString(GL_VERSION);
+ shadingLanguageVersion = (const char*)glGetString(GL_SHADING_LANGUAGE_VERSION);
+ vendor = (const char*)glGetString(GL_VENDOR);
+ renderer = (const char*)glGetString(GL_RENDERER);
+ GLint n = 0;
+ glGetIntegerv(GL_NUM_EXTENSIONS, &n);
+ for (GLint i = 0; i < n; ++i) {
+ extensions.emplace_back((const char*)glGetStringi(GL_EXTENSIONS, i));
+ }
+ }
+ return *this;
+}
+
uint16_t gl::getAvailableVersion() {
static uint8_t major = 0, minor = 0;
static std::once_flag once;
@@ -277,25 +304,6 @@ int glVersionToInteger(QString glVersion) {
return (majorNumber << 16) | minorNumber;
}
-QJsonObject getGLContextData() {
- static QJsonObject result;
- static std::once_flag once;
- std::call_once(once, [] {
- QString glVersion = QString((const char*)glGetString(GL_VERSION));
- QString glslVersion = QString((const char*) glGetString(GL_SHADING_LANGUAGE_VERSION));
- QString glVendor = QString((const char*) glGetString(GL_VENDOR));
- QString glRenderer = QString((const char*)glGetString(GL_RENDERER));
-
- result = QJsonObject {
- { "version", glVersion },
- { "sl_version", glslVersion },
- { "vendor", glVendor },
- { "renderer", glRenderer },
- };
- });
- return result;
-}
-
QThread* RENDER_THREAD = nullptr;
bool isRenderThread() {
diff --git a/libraries/gl/src/gl/GLHelpers.h b/libraries/gl/src/gl/GLHelpers.h
index c09f9d4963..05687b2a8a 100644
--- a/libraries/gl/src/gl/GLHelpers.h
+++ b/libraries/gl/src/gl/GLHelpers.h
@@ -29,7 +29,6 @@ class QGLFormat;
size_t evalGLFormatSwapchainPixelSize(const QSurfaceFormat& format);
const QSurfaceFormat& getDefaultOpenGLSurfaceFormat();
-QJsonObject getGLContextData();
int glVersionToInteger(QString glVersion);
bool isRenderThread();
@@ -39,6 +38,26 @@ bool isRenderThread();
#define GL_GET_MAJOR_VERSION(glversion) ((glversion & 0xFF00) >> 8)
namespace gl {
+ struct ContextInfo {
+ std::string version;
+ std::string shadingLanguageVersion;
+ std::string vendor;
+ std::string renderer;
+ std::vector extensions;
+
+ ContextInfo& init();
+ operator bool() const { return !version.empty(); }
+
+ static const ContextInfo& get(bool init = false);
+ };
+
+#define LOG_GL_CONTEXT_INFO(category, contextInfo) \
+ qCDebug(category) << "GL Version: " << contextInfo.version.c_str(); \
+ qCDebug(category) << "GL Shader Language Version: " << contextInfo.shadingLanguageVersion.c_str(); \
+ qCDebug(category) << "GL Vendor: " << contextInfo.vendor.c_str(); \
+ qCDebug(category) << "GL Renderer: " << contextInfo.renderer.c_str();
+
+
void globalLock();
void globalRelease(bool finish = true);
diff --git a/libraries/gl/src/gl/GLWindow.cpp b/libraries/gl/src/gl/GLWindow.cpp
index 7930e050de..19f8428d0a 100644
--- a/libraries/gl/src/gl/GLWindow.cpp
+++ b/libraries/gl/src/gl/GLWindow.cpp
@@ -33,14 +33,12 @@ GLWindow::~GLWindow() {
bool GLWindow::makeCurrent() {
bool makeCurrentResult = _context->makeCurrent();
Q_ASSERT(makeCurrentResult);
-
- std::call_once(_reportOnce, []{
- qCDebug(glLogging) << "GL Version: " << QString((const char*) glGetString(GL_VERSION));
- qCDebug(glLogging) << "GL Shader Language Version: " << QString((const char*) glGetString(GL_SHADING_LANGUAGE_VERSION));
- qCDebug(glLogging) << "GL Vendor: " << QString((const char*) glGetString(GL_VENDOR));
- qCDebug(glLogging) << "GL Renderer: " << QString((const char*) glGetString(GL_RENDERER));
- });
-
+
+ if (makeCurrentResult) {
+ std::call_once(_reportOnce, []{
+ LOG_GL_CONTEXT_INFO(glLogging, gl::ContextInfo().init());
+ });
+ }
return makeCurrentResult;
}
diff --git a/libraries/gl/src/gl/OffscreenGLCanvas.cpp b/libraries/gl/src/gl/OffscreenGLCanvas.cpp
index 69b41da821..c5b8baa6d4 100644
--- a/libraries/gl/src/gl/OffscreenGLCanvas.cpp
+++ b/libraries/gl/src/gl/OffscreenGLCanvas.cpp
@@ -73,13 +73,9 @@ bool OffscreenGLCanvas::create(QOpenGLContext* sharedContext) {
bool OffscreenGLCanvas::makeCurrent() {
bool result = _context->makeCurrent(_offscreenSurface);
- if (glGetString) {
+ if (result) {
std::call_once(_reportOnce, [] {
- qCDebug(glLogging) << "GL Version: " << QString((const char*)glGetString(GL_VERSION));
- qCDebug(glLogging) << "GL Shader Language Version: "
- << QString((const char*)glGetString(GL_SHADING_LANGUAGE_VERSION));
- qCDebug(glLogging) << "GL Vendor: " << QString((const char*)glGetString(GL_VENDOR));
- qCDebug(glLogging) << "GL Renderer: " << QString((const char*)glGetString(GL_RENDERER));
+ LOG_GL_CONTEXT_INFO(glLogging, gl::ContextInfo().init());
});
}
diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp
index fef458f536..3e5043003b 100644
--- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp
+++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp
@@ -117,9 +117,10 @@ void GLBackend::init() {
static std::once_flag once;
std::call_once(once, [] {
-
- QString vendor{ (const char*)glGetString(GL_VENDOR) };
- QString renderer{ (const char*)glGetString(GL_RENDERER) };
+ ::gl::ContextInfo contextInfo;
+ contextInfo.init();
+ QString vendor { contextInfo.vendor.c_str() };
+ QString renderer { contextInfo.renderer.c_str() };
// Textures
GL_GET_INTEGER(MAX_TEXTURE_IMAGE_UNITS);
@@ -131,10 +132,7 @@ void GLBackend::init() {
GL_GET_INTEGER(MAX_UNIFORM_BLOCK_SIZE);
GL_GET_INTEGER(UNIFORM_BUFFER_OFFSET_ALIGNMENT);
- qCDebug(gpugllogging) << "GL Version: " << QString((const char*) glGetString(GL_VERSION));
- qCDebug(gpugllogging) << "GL Shader Language Version: " << QString((const char*) glGetString(GL_SHADING_LANGUAGE_VERSION));
- qCDebug(gpugllogging) << "GL Vendor: " << vendor;
- qCDebug(gpugllogging) << "GL Renderer: " << renderer;
+ LOG_GL_CONTEXT_INFO(gpugllogging, contextInfo);
GPUIdent* gpu = GPUIdent::getInstance(vendor, renderer);
// From here on, GPUIdent::getInstance()->getMumble() should efficiently give the same answers.
qCDebug(gpugllogging) << "GPU:";
diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp
index 306e0155d3..63a36bc97b 100644
--- a/libraries/networking/src/NodeList.cpp
+++ b/libraries/networking/src/NodeList.cpp
@@ -1143,6 +1143,10 @@ void NodeList::maybeSendIgnoreSetToNode(SharedNodePointer newNode) {
}
void NodeList::setAvatarGain(const QUuid& nodeID, float gain) {
+ if (nodeID.isNull()) {
+ _avatarGain = gain;
+ }
+
// cannot set gain of yourself
if (getSessionUUID() != nodeID) {
auto audioMixer = soloNodeOfType(NodeType::AudioMixer);
@@ -1160,7 +1164,6 @@ void NodeList::setAvatarGain(const QUuid& nodeID, float gain) {
qCDebug(networking) << "Sending Set MASTER Avatar Gain packet with Gain:" << gain;
sendPacket(std::move(setAvatarGainPacket), *audioMixer);
- _avatarGain = gain;
} else {
qCDebug(networking) << "Sending Set Avatar Gain packet with UUID:" << uuidStringWithoutCurlyBraces(nodeID) << "Gain:" << gain;
@@ -1192,6 +1195,8 @@ float NodeList::getAvatarGain(const QUuid& nodeID) {
}
void NodeList::setInjectorGain(float gain) {
+ _injectorGain = gain;
+
auto audioMixer = soloNodeOfType(NodeType::AudioMixer);
if (audioMixer) {
// setup the packet
@@ -1203,7 +1208,6 @@ void NodeList::setInjectorGain(float gain) {
qCDebug(networking) << "Sending Set Injector Gain packet with Gain:" << gain;
sendPacket(std::move(setInjectorGainPacket), *audioMixer);
- _injectorGain = gain;
} else {
qWarning() << "Couldn't find audio mixer to send set gain request";
diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h
index 2d184fe592..dc1c79e0eb 100644
--- a/libraries/networking/src/udt/PacketHeaders.h
+++ b/libraries/networking/src/udt/PacketHeaders.h
@@ -273,6 +273,7 @@ enum class EntityVersion : PacketVersion {
PrivateUserData,
TextUnlit,
ShadowBiasAndDistance,
+ TextEntityFonts,
// Add new versions above here
NUM_PACKET_TYPE,
diff --git a/libraries/octree/src/OctreePacketData.h b/libraries/octree/src/OctreePacketData.h
index a2fe886810..46e5de9bda 100644
--- a/libraries/octree/src/OctreePacketData.h
+++ b/libraries/octree/src/OctreePacketData.h
@@ -40,6 +40,7 @@
#include "WebInputMode.h"
#include "PulseMode.h"
#include "GizmoType.h"
+#include "TextEffect.h"
#include "OctreeConstants.h"
#include "OctreeElement.h"
@@ -273,6 +274,7 @@ public:
static int unpackDataFromBytes(const unsigned char* dataBytes, WebInputMode& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); }
static int unpackDataFromBytes(const unsigned char* dataBytes, PulseMode& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); }
static int unpackDataFromBytes(const unsigned char* dataBytes, GizmoType& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); }
+ static int unpackDataFromBytes(const unsigned char* dataBytes, TextEffect& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); }
static int unpackDataFromBytes(const unsigned char* dataBytes, glm::vec2& result);
static int unpackDataFromBytes(const unsigned char* dataBytes, glm::vec3& result);
static int unpackDataFromBytes(const unsigned char* dataBytes, glm::u8vec3& result);
diff --git a/libraries/platform/CMakeLists.txt b/libraries/platform/CMakeLists.txt
index f0dce500f4..0d984f9684 100644
--- a/libraries/platform/CMakeLists.txt
+++ b/libraries/platform/CMakeLists.txt
@@ -1,7 +1,7 @@
set(TARGET_NAME platform)
setup_hifi_library()
-link_hifi_libraries(shared)
+link_hifi_libraries(shared gl)
GroupSources("src")
target_json()
@@ -15,4 +15,4 @@ if (APPLE)
elseif(WIN32)
target_link_libraries(${TARGET_NAME} Iphlpapi.lib)
-endif ()
\ No newline at end of file
+endif()
\ No newline at end of file
diff --git a/libraries/platform/src/platform/PlatformKeys.h b/libraries/platform/src/platform/PlatformKeys.h
index 5008a4f6ce..9bd9d47890 100644
--- a/libraries/platform/src/platform/PlatformKeys.h
+++ b/libraries/platform/src/platform/PlatformKeys.h
@@ -34,6 +34,43 @@ namespace platform { namespace keys{
extern const char* displays;
extern const char* isMaster;
}
+ namespace graphicsAPI {
+ extern const char* name;
+ extern const char* version;
+ extern const char* apiOpenGL;
+ extern const char* apiVulkan;
+ extern const char* apiDirect3D11;
+ extern const char* apiDirect3D12;
+ extern const char* apiMetal;
+ namespace gl {
+ extern const char* shadingLanguageVersion;
+ extern const char* vendor;
+ extern const char* renderer;
+ extern const char* extensions;
+ }
+ namespace vk {
+ extern const char* devices;
+ namespace device {
+ extern const char* apiVersion;
+ extern const char* driverVersion;
+ extern const char* deviceType;
+ extern const char* vendor;
+ extern const char* name;
+ extern const char* formats;
+ extern const char* extensions;
+ extern const char* queues;
+ extern const char* heaps;
+ namespace heap {
+ extern const char* flags;
+ extern const char* size;
+ }
+ namespace queue {
+ extern const char* flags;
+ extern const char* count;
+ }
+ }
+ }
+ }
namespace nic {
extern const char* mac;
extern const char* name;
@@ -78,6 +115,7 @@ namespace platform { namespace keys{
// Keys for categories used in json returned by getAll()
extern const char* CPUS;
extern const char* GPUS;
+ extern const char* GRAPHICS_APIS;
extern const char* DISPLAYS;
extern const char* NICS;
extern const char* MEMORY;
diff --git a/libraries/platform/src/platform/backend/MACOSPlatform.cpp b/libraries/platform/src/platform/backend/MACOSPlatform.cpp
index 66c9cd2c5d..5a7b40f71b 100644
--- a/libraries/platform/src/platform/backend/MACOSPlatform.cpp
+++ b/libraries/platform/src/platform/backend/MACOSPlatform.cpp
@@ -322,3 +322,8 @@ void MACOSInstance::enumerateComputer(){
}
+void MACOSInstance::enumerateGraphicsApis() {
+ Instance::enumerateGraphicsApis();
+
+ // TODO imeplement Metal query
+}
diff --git a/libraries/platform/src/platform/backend/MACOSPlatform.h b/libraries/platform/src/platform/backend/MACOSPlatform.h
index f249dad001..87c16972ec 100644
--- a/libraries/platform/src/platform/backend/MACOSPlatform.h
+++ b/libraries/platform/src/platform/backend/MACOSPlatform.h
@@ -19,6 +19,7 @@ namespace platform {
void enumerateGpusAndDisplays() override;
void enumerateMemory() override;
void enumerateComputer() override;
+ void enumerateGraphicsApis() override;
};
} // namespace platform
diff --git a/libraries/platform/src/platform/backend/Platform.cpp b/libraries/platform/src/platform/backend/Platform.cpp
index 17d9d8019e..c3abcce5f1 100644
--- a/libraries/platform/src/platform/backend/Platform.cpp
+++ b/libraries/platform/src/platform/backend/Platform.cpp
@@ -35,6 +35,45 @@ namespace platform { namespace keys {
const char* displays = "displays";
const char* isMaster = "isMaster";
}
+ namespace graphicsAPI {
+ const char* name = "name";
+ const char* version = "version";
+
+ const char* apiOpenGL = "OpenGL";
+ const char* apiVulkan = "Vulkan";
+ const char* apiDirect3D11 = "D3D11";
+ const char* apiDirect3D12 = "D3D12";
+ const char* apiMetal = "Metal";
+
+ namespace gl {
+ const char* shadingLanguageVersion = "shadingLanguageVersion";
+ const char* vendor = "vendor";
+ const char* renderer = "renderer";
+ const char* extensions = "extensions";
+ }
+ namespace vk {
+ const char* devices = "devices";
+ namespace device {
+ const char* apiVersion = "apiVersion";
+ const char* driverVersion = "driverVersion";
+ const char* deviceType = "deviceType";
+ const char* vendor = "vendor";
+ const char* name = "name";
+ const char* formats = "formats";
+ const char* extensions = "extensions";
+ const char* heaps = "heaps";
+ namespace heap {
+ const char* flags = "flags";
+ const char* size = "size";
+ }
+ const char* queues = "queues";
+ namespace queue {
+ const char* flags = "flags";
+ const char* count = "count";
+ }
+ }
+ }
+ }
namespace nic {
const char* mac = "mac";
const char* name = "name";
@@ -78,6 +117,7 @@ namespace platform { namespace keys {
const char* CPUS = "cpus";
const char* GPUS = "gpus";
+ const char* GRAPHICS_APIS = "graphicsAPIs";
const char* DISPLAYS = "displays";
const char* NICS = "nics";
const char* MEMORY = "memory";
diff --git a/libraries/platform/src/platform/backend/PlatformInstance.cpp b/libraries/platform/src/platform/backend/PlatformInstance.cpp
index d4cadba3b7..114a53d2cc 100644
--- a/libraries/platform/src/platform/backend/PlatformInstance.cpp
+++ b/libraries/platform/src/platform/backend/PlatformInstance.cpp
@@ -10,9 +10,18 @@
#include "PlatformInstance.h"
#include
+#include
#include "../PlatformKeys.h"
#include "../Profiler.h"
+// For testing the vulkan dump
+//#define HAVE_VULKAN 1
+//#pragma comment(lib, "C:\\VulkanSDK\\1.1.101.0\\Lib\\vulkan-1.lib")
+
+#ifdef HAVE_VULKAN
+#include
+#endif
+
using namespace platform;
bool Instance::enumeratePlatform() {
@@ -30,6 +39,7 @@ bool Instance::enumeratePlatform() {
enumerateCpus();
enumerateGpusAndDisplays();
enumerateNics();
+ enumerateGraphicsApis();
// eval the master index for each platform scopes
updateMasterIndices();
@@ -105,34 +115,102 @@ void Instance::enumerateNics() {
}
}
-json Instance::getCPU(int index) {
- assert(index <(int) _cpus.size());
+#if defined(HAVE_VULKAN)
+static std::string vkVersionToString(uint32_t version) {
+ return QString("%1.%2.%3").arg(VK_VERSION_MAJOR(version)).arg(VK_VERSION_MINOR(version)).arg(VK_VERSION_PATCH(version)).toStdString();
+}
+#endif
- if (index < 0 || (int) _cpus.size() <= index)
+
+void Instance::enumerateGraphicsApis() {
+ // OpenGL rendering API is supported on all platforms
+ {
+ auto& glContextInfo = gl::ContextInfo::get();
+ json gl;
+ gl[keys::graphicsAPI::name] = keys::graphicsAPI::apiOpenGL;
+ gl[keys::graphicsAPI::version] = glContextInfo.version;
+ gl[keys::graphicsAPI::gl::vendor] = glContextInfo.vendor;
+ gl[keys::graphicsAPI::gl::renderer] = glContextInfo.renderer;
+ gl[keys::graphicsAPI::gl::shadingLanguageVersion] = glContextInfo.shadingLanguageVersion;
+ gl[keys::graphicsAPI::gl::extensions] = glContextInfo.extensions;
+ _graphicsApis.push_back(gl);
+ }
+
+#if defined(HAVE_VULKAN)
+ // Vulkan rendering API is supported on all platforms (sort of)
+ {
+ try {
+ vk::ApplicationInfo appInfo{ "Interface", 1, "Luci", 1, VK_API_VERSION_1_1 };
+ auto instancePtr = vk::createInstanceUnique({ {}, &appInfo });
+ if (instancePtr) {
+ json vkinfo;
+ const auto& vkinstance = *instancePtr;
+ vkinfo[keys::graphicsAPI::name] = keys::graphicsAPI::apiVulkan;
+ vkinfo[keys::graphicsAPI::version] = vkVersionToString(VK_API_VERSION_1_1);
+ for (const auto& physicalDevice : vkinstance.enumeratePhysicalDevices()) {
+ json vkdevice;
+ auto properties = physicalDevice.getProperties();
+ vkdevice[keys::graphicsAPI::vk::device::driverVersion] = vkVersionToString(properties.driverVersion);
+ vkdevice[keys::graphicsAPI::vk::device::apiVersion] = vkVersionToString(properties.apiVersion);
+ vkdevice[keys::graphicsAPI::vk::device::deviceType] = vk::to_string(properties.deviceType);
+ vkdevice[keys::graphicsAPI::vk::device::vendor] = properties.vendorID;
+ vkdevice[keys::graphicsAPI::vk::device::name] = properties.deviceName;
+ for (const auto& extensionProperties : physicalDevice.enumerateDeviceExtensionProperties()) {
+ vkdevice[keys::graphicsAPI::vk::device::extensions].push_back(extensionProperties.extensionName);
+ }
+
+ for (const auto& queueFamilyProperties : physicalDevice.getQueueFamilyProperties()) {
+ json vkqueuefamily;
+ vkqueuefamily[keys::graphicsAPI::vk::device::queue::flags] = vk::to_string(queueFamilyProperties.queueFlags);
+ vkqueuefamily[keys::graphicsAPI::vk::device::queue::count] = queueFamilyProperties.queueCount;
+ vkdevice[keys::graphicsAPI::vk::device::queues].push_back(vkqueuefamily);
+ }
+ auto memoryProperties = physicalDevice.getMemoryProperties();
+ for (uint32_t heapIndex = 0; heapIndex < memoryProperties.memoryHeapCount; ++heapIndex) {
+ json vkmemoryheap;
+ const auto& heap = memoryProperties.memoryHeaps[heapIndex];
+ vkmemoryheap[keys::graphicsAPI::vk::device::heap::flags] = vk::to_string(heap.flags);
+ vkmemoryheap[keys::graphicsAPI::vk::device::heap::size] = heap.size;
+ vkdevice[keys::graphicsAPI::vk::device::heaps].push_back(vkmemoryheap);
+ }
+ vkinfo[keys::graphicsAPI::vk::devices].push_back(vkdevice);
+ }
+ _graphicsApis.push_back(vkinfo);
+ }
+ } catch (const std::runtime_error&) {
+ }
+ }
+#endif
+}
+
+json Instance::getCPU(int index) {
+ assert(index < (int)_cpus.size());
+
+ if (index < 0 || (int)_cpus.size() <= index)
return json();
return _cpus.at(index);
}
json Instance::getGPU(int index) {
- assert(index <(int) _gpus.size());
+ assert(index < (int)_gpus.size());
- if (index < 0 || (int) _gpus.size() <= index)
+ if (index < 0 || (int)_gpus.size() <= index)
return json();
-
+
return _gpus.at(index);
}
-
json Instance::getDisplay(int index) {
- assert(index <(int) _displays.size());
-
- if (index < 0 || (int) _displays.size() <= index)
+ assert(index < (int)_displays.size());
+
+ if (index < 0 || (int)_displays.size() <= index)
return json();
return _displays.at(index);
}
+
Instance::~Instance() {
if (_cpus.size() > 0) {
_cpus.clear();
@@ -147,7 +225,6 @@ Instance::~Instance() {
}
}
-
json Instance::listAllKeys() {
json allKeys;
allKeys.array({{
@@ -167,6 +244,14 @@ json Instance::listAllKeys() {
keys::gpu::driver,
keys::gpu::displays,
+ keys::graphicsAPI::version,
+ keys::graphicsAPI::name,
+
+ keys::graphicsAPI::gl::shadingLanguageVersion,
+ keys::graphicsAPI::gl::vendor,
+ keys::graphicsAPI::gl::renderer,
+ keys::graphicsAPI::gl::extensions,
+
keys::display::boundsLeft,
keys::display::boundsRight,
keys::display::boundsTop,
@@ -188,6 +273,7 @@ json Instance::listAllKeys() {
keys::CPUS,
keys::GPUS,
+ keys::GRAPHICS_APIS,
keys::DISPLAYS,
keys::MEMORY,
keys::COMPUTER,
@@ -219,6 +305,7 @@ json Instance::getAll() {
all[keys::MEMORY] = _memory;
all[keys::CPUS] = _cpus;
all[keys::GPUS] = _gpus;
+ all[keys::GRAPHICS_APIS] = _graphicsApis;
all[keys::DISPLAYS] = _displays;
all[keys::NICS] = _nics;
diff --git a/libraries/platform/src/platform/backend/PlatformInstance.h b/libraries/platform/src/platform/backend/PlatformInstance.h
index 069124853e..2b263c21ef 100644
--- a/libraries/platform/src/platform/backend/PlatformInstance.h
+++ b/libraries/platform/src/platform/backend/PlatformInstance.h
@@ -44,6 +44,7 @@ public:
void virtual enumerateNics();
void virtual enumerateMemory() = 0;
void virtual enumerateComputer()=0;
+ virtual void enumerateGraphicsApis();
virtual ~Instance();
@@ -57,6 +58,7 @@ protected:
std::vector _gpus;
std::vector _displays;
std::vector _nics;
+ json _graphicsApis;
json _memory;
json _computer;
diff --git a/libraries/platform/src/platform/backend/WINPlatform.cpp b/libraries/platform/src/platform/backend/WINPlatform.cpp
index e528618fe1..2958374451 100644
--- a/libraries/platform/src/platform/backend/WINPlatform.cpp
+++ b/libraries/platform/src/platform/backend/WINPlatform.cpp
@@ -27,7 +27,6 @@
#include
#pragma comment(lib, "dxgi.lib")
#include
-#pragma comment(lib, "Shcore.lib")
#endif
@@ -118,10 +117,19 @@ void WINInstance::enumerateGpusAndDisplays() {
// Grab the dpi info for the monitor
UINT dpiX{ 0 };
UINT dpiY{ 0 };
- GetDpiForMonitor(outputDesc.Monitor, MDT_RAW_DPI, &dpiX, &dpiY);
UINT dpiXScaled{ 0 };
UINT dpiYScaled{ 0 };
- GetDpiForMonitor(outputDesc.Monitor, MDT_EFFECTIVE_DPI, &dpiXScaled, &dpiYScaled);
+
+ // SHCore.dll is not available prior to Windows 8.1
+ HMODULE SHCoreDLL = LoadLibraryW(L"SHCore.dll");
+ if (SHCoreDLL) {
+ auto _GetDpiForMonitor = reinterpret_cast(GetProcAddress(SHCoreDLL, "GetDpiForMonitor"));
+ if (_GetDpiForMonitor) {
+ _GetDpiForMonitor(outputDesc.Monitor, MDT_RAW_DPI, &dpiX, &dpiY);
+ _GetDpiForMonitor(outputDesc.Monitor, MDT_EFFECTIVE_DPI, &dpiXScaled, &dpiYScaled);
+ }
+ FreeLibrary(SHCoreDLL);
+ }
// Current display mode
DEVMODEW devMode;
@@ -251,3 +259,9 @@ void WINInstance::enumerateNics() {
}
#endif
}
+
+void WINInstance::enumerateGraphicsApis() {
+ Instance::enumerateGraphicsApis();
+
+ // TODO imeplement D3D 11/12 queries
+}
diff --git a/libraries/platform/src/platform/backend/WINPlatform.h b/libraries/platform/src/platform/backend/WINPlatform.h
index cc56ebfbbc..dd29d696dc 100644
--- a/libraries/platform/src/platform/backend/WINPlatform.h
+++ b/libraries/platform/src/platform/backend/WINPlatform.h
@@ -20,6 +20,7 @@ namespace platform {
void enumerateMemory() override;
void enumerateComputer () override;
void enumerateNics() override;
+ void enumerateGraphicsApis() override;
};
} // namespace platform
diff --git a/libraries/render-utils/src/BloomEffect.cpp b/libraries/render-utils/src/BloomEffect.cpp
index 414a1c3f91..e58d07ac33 100644
--- a/libraries/render-utils/src/BloomEffect.cpp
+++ b/libraries/render-utils/src/BloomEffect.cpp
@@ -15,7 +15,6 @@
#include
#include
-#include
#include "render-utils/ShaderConstants.h"
#define BLOOM_BLUR_LEVEL_COUNT 3
diff --git a/libraries/render-utils/src/RenderCommonTask.cpp b/libraries/render-utils/src/RenderCommonTask.cpp
index 9ea4ac9f3c..7cf7f1129f 100644
--- a/libraries/render-utils/src/RenderCommonTask.cpp
+++ b/libraries/render-utils/src/RenderCommonTask.cpp
@@ -148,6 +148,30 @@ void Blit::run(const RenderContextPointer& renderContext, const gpu::Framebuffer
});
}
+NewFramebuffer::NewFramebuffer(gpu::Element pixelFormat) {
+ _pixelFormat = pixelFormat;
+}
+
+void NewFramebuffer::run(const render::RenderContextPointer& renderContext, Output& output) {
+ RenderArgs* args = renderContext->args;
+ glm::uvec2 frameSize(args->_viewport.z, args->_viewport.w);
+ output.reset();
+
+ if (_outputFramebuffer && _outputFramebuffer->getSize() != frameSize) {
+ _outputFramebuffer.reset();
+ }
+
+ if (!_outputFramebuffer) {
+ _outputFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("newFramebuffer.out"));
+ auto colorFormat = _pixelFormat;
+ auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR);
+ auto colorTexture = gpu::Texture::createRenderBuffer(colorFormat, frameSize.x, frameSize.y, gpu::Texture::SINGLE_MIP, defaultSampler);
+ _outputFramebuffer->setRenderBuffer(0, colorTexture);
+ }
+
+ output = _outputFramebuffer;
+}
+
void NewOrDefaultFramebuffer::run(const render::RenderContextPointer& renderContext, const Input& input, Output& output) {
RenderArgs* args = renderContext->args;
// auto frameSize = input;
@@ -167,7 +191,7 @@ void NewOrDefaultFramebuffer::run(const render::RenderContextPointer& renderCont
}
if (!_outputFramebuffer) {
- _outputFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("newFramebuffer.out"));
+ _outputFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("newOrDefaultFramebuffer.out"));
auto colorFormat = gpu::Element::COLOR_SRGBA_32;
auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR);
auto colorTexture = gpu::Texture::createRenderBuffer(colorFormat, frameSize.x, frameSize.y, gpu::Texture::SINGLE_MIP, defaultSampler);
diff --git a/libraries/render-utils/src/RenderCommonTask.h b/libraries/render-utils/src/RenderCommonTask.h
index ec50fbf2cc..15d6ff9895 100644
--- a/libraries/render-utils/src/RenderCommonTask.h
+++ b/libraries/render-utils/src/RenderCommonTask.h
@@ -83,6 +83,20 @@ public:
void run(const render::RenderContextPointer& renderContext, const gpu::FramebufferPointer& srcFramebuffer);
};
+class NewFramebuffer {
+public:
+ using Output = gpu::FramebufferPointer;
+ using JobModel = render::Job::ModelO;
+
+ NewFramebuffer(gpu::Element pixelFormat = gpu::Element::COLOR_SRGBA_32);
+
+ void run(const render::RenderContextPointer& renderContext, Output& output);
+protected:
+ gpu::Element _pixelFormat;
+private:
+ gpu::FramebufferPointer _outputFramebuffer;
+};
+
class NewOrDefaultFramebuffer {
public:
using Input = glm::uvec2;
diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp
index c26f3b613c..4561cf903d 100644
--- a/libraries/render-utils/src/RenderDeferredTask.cpp
+++ b/libraries/render-utils/src/RenderDeferredTask.cpp
@@ -29,7 +29,6 @@
#include
#include
#include
-#include
#include "RenderHifi.h"
#include "render-utils/ShaderConstants.h"
@@ -51,7 +50,7 @@
#include "AmbientOcclusionEffect.h"
#include "AntialiasingEffect.h"
-#include "ToneMappingEffect.h"
+#include "ToneMapAndResampleTask.h"
#include "SubsurfaceScattering.h"
#include "DrawHaze.h"
#include "BloomEffect.h"
@@ -96,7 +95,7 @@ RenderDeferredTask::RenderDeferredTask()
void RenderDeferredTask::configure(const Config& config) {
// Propagate resolution scale to sub jobs who need it
- auto preparePrimaryBufferConfig = config.getConfig("PreparePrimaryBuffer");
+ auto preparePrimaryBufferConfig = config.getConfig("PreparePrimaryBufferDeferred");
assert(preparePrimaryBufferConfig);
preparePrimaryBufferConfig->setResolutionScale(config.resolutionScale);
}
@@ -146,7 +145,7 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren
const auto jitter = task.addJob("JitterCam");
// GPU jobs: Start preparing the primary, deferred and lighting buffer
- const auto scaledPrimaryFramebuffer = task.addJob("PreparePrimaryBuffer");
+ const auto scaledPrimaryFramebuffer = task.addJob("PreparePrimaryBufferDeferred");
// Prepare deferred, generate the shared Deferred Frame Transform. Only valid with the scaled frame buffer
const auto deferredFrameTransform = task.addJob("DeferredFrameTransform", jitter);
@@ -238,23 +237,22 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren
const auto bloomInputs = BloomEffect::Inputs(deferredFrameTransform, lightingFramebuffer, bloomFrame).asVarying();
task.addJob("Bloom", bloomInputs);
+ const auto destFramebuffer = static_cast(nullptr);
+
// Lighting Buffer ready for tone mapping
- const auto toneMappingInputs = ToneMappingDeferred::Input(lightingFramebuffer, scaledPrimaryFramebuffer).asVarying();
- const auto toneMappedBuffer = task.addJob("ToneMapping", toneMappingInputs);
+ const auto toneMappingInputs = ToneMapAndResample::Input(lightingFramebuffer, destFramebuffer).asVarying();
+ const auto toneMappedBuffer = task.addJob("ToneMapping", toneMappingInputs);
// Debugging task is happening in the "over" layer after tone mapping and just before HUD
{ // Debug the bounds of the rendered items, still look at the zbuffer
const auto extraDebugBuffers = RenderDeferredTaskDebug::ExtraBuffers(linearDepthTarget, surfaceGeometryFramebuffer, ambientOcclusionFramebuffer, ambientOcclusionUniforms, scatteringResource, velocityBuffer);
const auto debugInputs = RenderDeferredTaskDebug::Input(fetchedItems, shadowTaskOutputs, lightingStageInputs, lightClusters, prepareDeferredOutputs, extraDebugBuffers,
- deferredFrameTransform, jitter, lightingModel).asVarying();
+ deferredFrameTransform, jitter, lightingModel).asVarying();
task.addJob("DebugRenderDeferredTask", debugInputs);
}
- // Upscale to finale resolution
- const auto primaryFramebuffer = task.addJob("PrimaryBufferUpscale", toneMappedBuffer);
-
// HUD Layer
- const auto renderHUDLayerInputs = RenderHUDLayerTask::Input(primaryFramebuffer, lightingModel, hudOpaque, hudTransparent, hazeFrame).asVarying();
+ const auto renderHUDLayerInputs = RenderHUDLayerTask::Input(toneMappedBuffer, lightingModel, hudOpaque, hudTransparent, hazeFrame).asVarying();
task.addJob("RenderHUDLayer", renderHUDLayerInputs);
}
@@ -415,7 +413,6 @@ void RenderDeferredTaskDebug::build(JobModel& task, const render::Varying& input
const auto debugZoneInputs = DebugZoneLighting::Inputs(deferredFrameTransform, lightFrame, backgroundFrame).asVarying();
task.addJob("DrawZoneStack", debugZoneInputs);
-
}
diff --git a/libraries/render-utils/src/RenderForwardTask.cpp b/libraries/render-utils/src/RenderForwardTask.cpp
index b6b17ee376..14f2e51697 100755
--- a/libraries/render-utils/src/RenderForwardTask.cpp
+++ b/libraries/render-utils/src/RenderForwardTask.cpp
@@ -19,7 +19,6 @@
#include
#include
#include
-#include
#include
@@ -28,7 +27,7 @@
#include "StencilMaskPass.h"
#include "ZoneRenderer.h"
#include "FadeEffect.h"
-#include "ToneMappingEffect.h"
+#include "ToneMapAndResampleTask.h"
#include "BackgroundStage.h"
#include "FramebufferCache.h"
#include "TextureCache.h"
@@ -51,7 +50,7 @@ extern void initForwardPipelines(ShapePlumber& plumber);
void RenderForwardTask::configure(const Config& config) {
// Propagate resolution scale to sub jobs who need it
- auto preparePrimaryBufferConfig = config.getConfig("PreparePrimaryBuffer");
+ auto preparePrimaryBufferConfig = config.getConfig("PreparePrimaryBufferForward");
assert(preparePrimaryBufferConfig);
preparePrimaryBufferConfig->setResolutionScale(config.resolutionScale);
}
@@ -99,7 +98,7 @@ void RenderForwardTask::build(JobModel& task, const render::Varying& input, rend
// GPU jobs: Start preparing the main framebuffer
- const auto scaledPrimaryFramebuffer = task.addJob("PreparePrimaryBuffer");
+ const auto scaledPrimaryFramebuffer = task.addJob("PreparePrimaryBufferForward");
// Prepare deferred, generate the shared Deferred Frame Transform. Only valid with the scaled frame buffer
const auto deferredFrameTransform = task.addJob("DeferredFrameTransform");
@@ -141,34 +140,17 @@ void RenderForwardTask::build(JobModel& task, const render::Varying& input, rend
task.addJob("DrawZoneStack", debugZoneInputs);
}
-#if defined(Q_OS_ANDROID)
+ const auto newResolvedFramebuffer = task.addJob("MakeResolvingFramebuffer", gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::R11G11B10));
- // Just resolve the msaa
- const auto resolveInputs = ResolveFramebuffer::Inputs(scaledPrimaryFramebuffer, static_cast(nullptr)).asVarying();
- const auto resolvedFramebuffer = task.addJob("Resolve", resolveInputs);
-
- const auto toneMappedBuffer = resolvedFramebuffer;
-#else
- const auto newResolvedFramebuffer = task.addJob("MakeResolvingFramebuffer");
-
-
- // Just resolve the msaa
const auto resolveInputs = ResolveFramebuffer::Inputs(scaledPrimaryFramebuffer, newResolvedFramebuffer).asVarying();
const auto resolvedFramebuffer = task.addJob("Resolve", resolveInputs);
- // Lighting Buffer ready for tone mapping
- // Forward rendering on GLES doesn't support tonemapping to and from the same FBO, so we specify
- // the output FBO as null, which causes the tonemapping to target the blit framebuffer
- const auto toneMappingInputs = ToneMappingDeferred::Input(resolvedFramebuffer, resolvedFramebuffer).asVarying();
- const auto toneMappedBuffer = task.addJob("ToneMapping", toneMappingInputs);
-
-#endif
-
- // Upscale to finale resolution
- const auto primaryFramebuffer = task.addJob("PrimaryBufferUpscale", toneMappedBuffer);
+ const auto destFramebuffer = static_cast(nullptr);
+ const auto toneMappingInputs = ToneMapAndResample::Input(resolvedFramebuffer, destFramebuffer).asVarying();
+ const auto toneMappedBuffer = task.addJob("ToneMapping", toneMappingInputs);
// HUD Layer
- const auto renderHUDLayerInputs = RenderHUDLayerTask::Input(primaryFramebuffer, lightingModel, hudOpaque, hudTransparent, hazeFrame).asVarying();
+ const auto renderHUDLayerInputs = RenderHUDLayerTask::Input(toneMappedBuffer, lightingModel, hudOpaque, hudTransparent, hazeFrame).asVarying();
task.addJob("RenderHUDLayer", renderHUDLayerInputs);
}
@@ -176,8 +158,8 @@ gpu::FramebufferPointer PreparePrimaryFramebufferMSAA::createFramebuffer(const c
gpu::FramebufferPointer framebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create(name));
auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR);
-
- auto colorFormat = gpu::Element::COLOR_SRGBA_32;
+
+ auto colorFormat = gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::R11G11B10);
auto colorTexture =
gpu::Texture::createRenderBufferMultisample(colorFormat, frameSize.x, frameSize.y, numSamples, defaultSampler);
framebuffer->setRenderBuffer(0, colorTexture);
diff --git a/libraries/render-utils/src/RenderForwardTask.h b/libraries/render-utils/src/RenderForwardTask.h
index 2abf248692..dd0abbc9ab 100755
--- a/libraries/render-utils/src/RenderForwardTask.h
+++ b/libraries/render-utils/src/RenderForwardTask.h
@@ -50,11 +50,13 @@ public:
const float SCALE_RANGE_MIN = 0.1f;
const float SCALE_RANGE_MAX = 2.0f;
resolutionScale = std::max(SCALE_RANGE_MIN, std::min(SCALE_RANGE_MAX, scale));
+ //emit dirty();
}
int getNumSamples() const { return numSamples; }
void setNumSamples(int num) {
numSamples = std::max(1, std::min(32, num));
+ emit dirty();
}
signals:
diff --git a/libraries/render-utils/src/TextRenderer3D.cpp b/libraries/render-utils/src/TextRenderer3D.cpp
index 8c514df91d..264ff409a6 100644
--- a/libraries/render-utils/src/TextRenderer3D.cpp
+++ b/libraries/render-utils/src/TextRenderer3D.cpp
@@ -10,45 +10,19 @@
//
#include "TextRenderer3D.h"
-#include
-#include
-
-#include
-#include
-#include
-#include
-
-#include
#include "text/Font.h"
-#include "GLMHelpers.h"
-#include "MatrixStack.h"
-#include "RenderUtilsLogging.h"
-
-#include "GeometryCache.h"
-
-const float TextRenderer3D::DEFAULT_POINT_SIZE = 12;
-
-TextRenderer3D* TextRenderer3D::getInstance(const char* family, float pointSize,
- bool bold, bool italic, EffectType effect, int effectThickness) {
- return new TextRenderer3D(family, pointSize, false, italic, effect, effectThickness);
+TextRenderer3D* TextRenderer3D::getInstance(const char* family) {
+ return new TextRenderer3D(family);
}
-TextRenderer3D::TextRenderer3D(const char* family, float pointSize, int weight, bool italic,
- EffectType effect, int effectThickness) :
- _effectType(effect),
- _effectThickness(effectThickness),
+TextRenderer3D::TextRenderer3D(const char* family) :
+ _family(family),
_font(Font::load(family)) {
if (!_font) {
qWarning() << "Unable to load font with family " << family;
- _font = Font::load("Courier");
- }
- if (1 != _effectThickness) {
- qWarning() << "Effect thickness not currently supported";
- }
- if (NO_EFFECT != _effectType && OUTLINE_EFFECT != _effectType) {
- qWarning() << "Effect type not currently supported";
+ _font = Font::load(ROBOTO_FONT_FAMILY);
}
}
@@ -66,12 +40,21 @@ float TextRenderer3D::getFontSize() const {
return 0.0f;
}
-void TextRenderer3D::draw(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color,
- const glm::vec2& bounds, bool unlit, bool forward) {
- // The font does all the OpenGL work
+void TextRenderer3D::draw(gpu::Batch& batch, float x, float y, const glm::vec2& bounds,
+ const QString& str, const glm::vec4& color, bool unlit, bool forward) {
if (_font) {
- _color = color;
- _font->drawString(batch, _drawInfo, str, _color, _effectType, { x, y }, bounds, unlit, forward);
+ _font->drawString(batch, _drawInfo, str, color, glm::vec3(0.0f), 0, TextEffect::NO_EFFECT, { x, y }, bounds, 1.0f, unlit, forward);
}
}
+void TextRenderer3D::draw(gpu::Batch& batch, float x, float y, const glm::vec2& bounds, float scale,
+ const QString& str, const QString& font, const glm::vec4& color, const glm::vec3& effectColor,
+ float effectThickness, TextEffect effect, bool unlit, bool forward) {
+ if (font != _family) {
+ _family = font;
+ _font = Font::load(_family);
+ }
+ if (_font) {
+ _font->drawString(batch, _drawInfo, str, color, effectColor, effectThickness, effect, { x, y }, bounds, scale, unlit, forward);
+ }
+}
\ No newline at end of file
diff --git a/libraries/render-utils/src/TextRenderer3D.h b/libraries/render-utils/src/TextRenderer3D.h
index 8118aa883c..3a88ed555c 100644
--- a/libraries/render-utils/src/TextRenderer3D.h
+++ b/libraries/render-utils/src/TextRenderer3D.h
@@ -14,48 +14,32 @@
#include
#include
-#include
-#include
-
-namespace gpu {
-class Batch;
-}
-class Font;
#include "text/Font.h"
-#include "text/EffectType.h"
-#include "text/FontFamilies.h"
+#include "TextEffect.h"
+#include "FontFamilies.h"
-// TextRenderer3D is actually a fairly thin wrapper around a Font class
-// defined in the cpp file.
class TextRenderer3D {
public:
- static const float DEFAULT_POINT_SIZE;
-
- static TextRenderer3D* getInstance(const char* family, float pointSize = DEFAULT_POINT_SIZE,
- bool bold = false, bool italic = false, EffectType effect = NO_EFFECT, int effectThickness = 1);
+ static TextRenderer3D* getInstance(const char* family);
glm::vec2 computeExtent(const QString& str) const;
float getFontSize() const; // Pixel size
- void draw(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color,
- const glm::vec2& bounds, bool unlit, bool forward);
+ void draw(gpu::Batch& batch, float x, float y, const glm::vec2& bounds,
+ const QString& str, const glm::vec4& color, bool unlit, bool forward);
+ void draw(gpu::Batch& batch, float x, float y, const glm::vec2& bounds, float scale,
+ const QString& str, const QString& font, const glm::vec4& color, const glm::vec3& effectColor,
+ float effectThickness, TextEffect effect, bool unlit, bool forward);
private:
- TextRenderer3D(const char* family, float pointSize, int weight = -1, bool italic = false,
- EffectType effect = NO_EFFECT, int effectThickness = 1);
+ TextRenderer3D(const char* family);
- // the type of effect to apply
- const EffectType _effectType;
+ QString _family;
- // the thickness of the effect
- const int _effectThickness;
-
- // text color
- glm::vec4 _color;
- Font::DrawInfo _drawInfo;
std::shared_ptr _font;
+ Font::DrawInfo _drawInfo;
+
};
-
#endif // hifi_TextRenderer3D_h
diff --git a/libraries/render-utils/src/ToneMapAndResampleTask.cpp b/libraries/render-utils/src/ToneMapAndResampleTask.cpp
new file mode 100644
index 0000000000..8d4a3c485d
--- /dev/null
+++ b/libraries/render-utils/src/ToneMapAndResampleTask.cpp
@@ -0,0 +1,110 @@
+//
+// ToneMapAndResampleTask.cpp
+// libraries/render-utils/src
+//
+// Created by Anna Brewer on 7/3/19.
+// Copyright 2019 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 "ToneMapAndResampleTask.h"
+
+#include
+#include
+
+#include "render-utils/ShaderConstants.h"
+#include "StencilMaskPass.h"
+#include "FramebufferCache.h"
+
+using namespace render;
+using namespace shader::gpu::program;
+using namespace shader::render_utils::program;
+
+gpu::PipelinePointer ToneMapAndResample::_pipeline;
+gpu::PipelinePointer ToneMapAndResample::_mirrorPipeline;
+
+ToneMapAndResample::ToneMapAndResample() {
+ Parameters parameters;
+ _parametersBuffer = gpu::BufferView(std::make_shared(sizeof(Parameters), (const gpu::Byte*) ¶meters));
+}
+
+void ToneMapAndResample::init() {
+ // shared_ptr to gpu::State
+ gpu::StatePointer blitState = gpu::StatePointer(new gpu::State());
+
+ blitState->setDepthTest(gpu::State::DepthTest(false, false));
+ blitState->setColorWriteMask(true, true, true, true);
+
+ _pipeline = gpu::PipelinePointer(gpu::Pipeline::create(gpu::Shader::createProgram(toneMapping), blitState));
+ _mirrorPipeline = gpu::PipelinePointer(gpu::Pipeline::create(gpu::Shader::createProgram(toneMapping_mirrored), blitState));
+}
+
+void ToneMapAndResample::setExposure(float exposure) {
+ auto& params = _parametersBuffer.get();
+ if (params._exposure != exposure) {
+ _parametersBuffer.edit()._exposure = exposure;
+ _parametersBuffer.edit()._twoPowExposure = pow(2.0, exposure);
+ }
+}
+
+void ToneMapAndResample::setToneCurve(ToneCurve curve) {
+ auto& params = _parametersBuffer.get();
+ if (params._toneCurve != (int)curve) {
+ _parametersBuffer.edit()._toneCurve = (int)curve;
+ }
+}
+
+void ToneMapAndResample::configure(const Config& config) {
+ setExposure(config.exposure);
+ setToneCurve((ToneCurve)config.curve);
+}
+
+void ToneMapAndResample::run(const RenderContextPointer& renderContext, const Input& input, Output& output) {
+ assert(renderContext->args);
+ assert(renderContext->args->hasViewFrustum());
+
+ RenderArgs* args = renderContext->args;
+
+ auto lightingBuffer = input.get0()->getRenderBuffer(0);
+ auto destinationFramebuffer = input.get1();
+
+ if (!destinationFramebuffer) {
+ destinationFramebuffer = args->_blitFramebuffer;
+ }
+
+ if (!lightingBuffer || !destinationFramebuffer) {
+ return;
+ }
+
+ if (!_pipeline) {
+ init();
+ }
+
+ const auto bufferSize = destinationFramebuffer->getSize();
+
+ auto srcBufferSize = glm::ivec2(lightingBuffer->getDimensions());
+
+ glm::ivec4 destViewport{ 0, 0, bufferSize.x, bufferSize.y };
+
+ gpu::doInBatch("Resample::run", args->_context, [&](gpu::Batch& batch) {
+ batch.enableStereo(false);
+ batch.setFramebuffer(destinationFramebuffer);
+
+ batch.setViewportTransform(destViewport);
+ batch.setProjectionTransform(glm::mat4());
+ batch.resetViewTransform();
+ batch.setPipeline(args->_renderMode == RenderArgs::MIRROR_RENDER_MODE ? _mirrorPipeline : _pipeline);
+
+ batch.setModelTransform(gpu::Framebuffer::evalSubregionTexcoordTransform(srcBufferSize, args->_viewport));
+ batch.setUniformBuffer(render_utils::slot::buffer::ToneMappingParams, _parametersBuffer);
+ batch.setResourceTexture(render_utils::slot::texture::ToneMappingColor, lightingBuffer);
+ batch.draw(gpu::TRIANGLE_STRIP, 4);
+ });
+
+ // Set full final viewport
+ args->_viewport = destViewport;
+
+ output = destinationFramebuffer;
+}
diff --git a/libraries/render-utils/src/ToneMappingEffect.h b/libraries/render-utils/src/ToneMapAndResampleTask.h
similarity index 65%
rename from libraries/render-utils/src/ToneMappingEffect.h
rename to libraries/render-utils/src/ToneMapAndResampleTask.h
index faf6e514e9..1c7ef2cf48 100644
--- a/libraries/render-utils/src/ToneMappingEffect.h
+++ b/libraries/render-utils/src/ToneMapAndResampleTask.h
@@ -1,16 +1,16 @@
//
-// ToneMappingEffect.h
+// ToneMapAndResample.h
// libraries/render-utils/src
//
-// Created by Sam Gateau on 12/7/2015.
-// Copyright 2015 High Fidelity, Inc.
+// Created by Anna Brewer on 7/3/19.
+// Copyright 2019 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_ToneMappingEffect_h
-#define hifi_ToneMappingEffect_h
+#ifndef hifi_ToneMapAndResample_h
+#define hifi_ToneMapAndResample_h
#include
#include
@@ -20,29 +20,66 @@
#include
#include
+enum class ToneCurve {
+ // Different tone curve available
+ None,
+ Gamma22,
+ Reinhard,
+ Filmic,
+};
+
+class ToneMappingConfig : public render::Job::Config {
+ Q_OBJECT
+ Q_PROPERTY(float exposure MEMBER exposure WRITE setExposure);
+ Q_PROPERTY(int curve MEMBER curve WRITE setCurve);
-class ToneMappingEffect {
public:
- ToneMappingEffect();
- virtual ~ToneMappingEffect() {}
+ ToneMappingConfig() : render::Job::Config(true) {}
- void render(RenderArgs* args, const gpu::TexturePointer& lightingBuffer, const gpu::FramebufferPointer& destinationBuffer);
+ void setExposure(float newExposure) { exposure = newExposure; emit dirty(); }
+ void setCurve(int newCurve) { curve = std::max((int)ToneCurve::None, std::min((int)ToneCurve::Filmic, newCurve)); emit dirty(); }
+
+
+ float exposure{ 0.0f };
+ int curve{ (int)ToneCurve::Gamma22 };
+
+signals:
+ void dirty();
+};
+
+class ToneMapAndResample {
+public:
+ ToneMapAndResample();
+ virtual ~ToneMapAndResample() {}
+
+ void render(RenderArgs* args, const gpu::TexturePointer& lightingBuffer, gpu::FramebufferPointer& destinationBuffer);
void setExposure(float exposure);
float getExposure() const { return _parametersBuffer.get()._exposure; }
- // Different tone curve available
- enum ToneCurve {
- None = 0,
- Gamma22,
- Reinhard,
- Filmic,
- };
void setToneCurve(ToneCurve curve);
ToneCurve getToneCurve() const { return (ToneCurve)_parametersBuffer.get()._toneCurve; }
-private:
+ // Inputs: lightingFramebuffer, destinationFramebuffer
+ using Input = render::VaryingSet2;
+ using Output = gpu::FramebufferPointer;
+ using Config = ToneMappingConfig;
+ using JobModel = render::Job::ModelIO;
+ void configure(const Config& config);
+ void run(const render::RenderContextPointer& renderContext, const Input& input, Output& output);
+
+protected:
+ static gpu::PipelinePointer _pipeline;
+ static gpu::PipelinePointer _mirrorPipeline;
+
+ gpu::FramebufferPointer _destinationFrameBuffer;
+
+ float _factor{ 2.0f };
+
+ gpu::FramebufferPointer getResampledFrameBuffer(const gpu::FramebufferPointer& sourceFramebuffer);
+
+private:
gpu::PipelinePointer _blitLightBuffer;
// Class describing the uniform buffer with all the parameters common to the tone mapping shaders
@@ -51,46 +88,16 @@ private:
float _exposure = 0.0f;
float _twoPowExposure = 1.0f;
glm::vec2 spareA;
- int _toneCurve = Gamma22;
+ int _toneCurve = (int)ToneCurve::Gamma22;
glm::vec3 spareB;
Parameters() {}
};
+
typedef gpu::BufferView UniformBufferView;
gpu::BufferView _parametersBuffer;
- void init(RenderArgs* args);
+ void init();
};
-class ToneMappingConfig : public render::Job::Config {
- Q_OBJECT
- Q_PROPERTY(float exposure MEMBER exposure WRITE setExposure);
- Q_PROPERTY(int curve MEMBER curve WRITE setCurve);
-public:
- ToneMappingConfig() : render::Job::Config(true) {}
-
- void setExposure(float newExposure) { exposure = newExposure; emit dirty(); }
- void setCurve(int newCurve) { curve = std::max((int)ToneMappingEffect::None, std::min((int)ToneMappingEffect::Filmic, newCurve)); emit dirty(); }
-
-
- float exposure{ 0.0f };
- int curve{ ToneMappingEffect::Gamma22 };
-signals:
- void dirty();
-};
-
-class ToneMappingDeferred {
-public:
- // Inputs: lightingFramebuffer, destinationFramebuffer
- using Input = render::VaryingSet2;
- using Output = gpu::FramebufferPointer;
- using Config = ToneMappingConfig;
- using JobModel = render::Job::ModelIO;
-
- void configure(const Config& config);
- void run(const render::RenderContextPointer& renderContext, const Input& input, Output& output);
-
- ToneMappingEffect _toneMappingEffect;
-};
-
-#endif // hifi_ToneMappingEffect_h
+#endif // hifi_ToneMapAndResample_h
diff --git a/libraries/render-utils/src/ToneMappingEffect.cpp b/libraries/render-utils/src/ToneMappingEffect.cpp
deleted file mode 100644
index b7cc5d3d80..0000000000
--- a/libraries/render-utils/src/ToneMappingEffect.cpp
+++ /dev/null
@@ -1,96 +0,0 @@
-//
-// ToneMappingEffect.cpp
-// libraries/render-utils/src
-//
-// Created by Sam Gateau on 12/7/2015.
-// 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 "ToneMappingEffect.h"
-
-#include
-#include
-
-#include "render-utils/ShaderConstants.h"
-#include "StencilMaskPass.h"
-#include "FramebufferCache.h"
-
-
-ToneMappingEffect::ToneMappingEffect() {
- Parameters parameters;
- _parametersBuffer = gpu::BufferView(std::make_shared(sizeof(Parameters), (const gpu::Byte*) ¶meters));
-}
-
-void ToneMappingEffect::init(RenderArgs* args) {
- auto blitProgram = gpu::Shader::createProgram(shader::render_utils::program::toneMapping);
-
- auto blitState = std::make_shared();
- blitState->setColorWriteMask(true, true, true, true);
- _blitLightBuffer = gpu::PipelinePointer(gpu::Pipeline::create(blitProgram, blitState));
-}
-
-void ToneMappingEffect::setExposure(float exposure) {
- auto& params = _parametersBuffer.get();
- if (params._exposure != exposure) {
- _parametersBuffer.edit()._exposure = exposure;
- _parametersBuffer.edit()._twoPowExposure = pow(2.0, exposure);
- }
-}
-
-void ToneMappingEffect::setToneCurve(ToneCurve curve) {
- auto& params = _parametersBuffer.get();
- if (params._toneCurve != curve) {
- _parametersBuffer.edit()._toneCurve = curve;
- }
-}
-
-void ToneMappingEffect::render(RenderArgs* args, const gpu::TexturePointer& lightingBuffer, const gpu::FramebufferPointer& destinationFramebuffer) {
- if (!_blitLightBuffer) {
- init(args);
- }
-
- if (!lightingBuffer || !destinationFramebuffer) {
- return;
- }
-
- auto framebufferSize = glm::ivec2(lightingBuffer->getDimensions());
- gpu::doInBatch("ToneMappingEffect::render", args->_context, [&](gpu::Batch& batch) {
- batch.enableStereo(false);
- batch.setFramebuffer(destinationFramebuffer);
-
- // FIXME: Generate the Luminosity map
- //batch.generateTextureMips(lightingBuffer);
-
- batch.setViewportTransform(args->_viewport);
- batch.setProjectionTransform(glm::mat4());
- batch.resetViewTransform();
- batch.setModelTransform(gpu::Framebuffer::evalSubregionTexcoordTransform(framebufferSize, args->_viewport));
- batch.setPipeline(_blitLightBuffer);
-
- batch.setUniformBuffer(render_utils::slot::buffer::ToneMappingParams, _parametersBuffer);
- batch.setResourceTexture(render_utils::slot::texture::ToneMappingColor, lightingBuffer);
- batch.draw(gpu::TRIANGLE_STRIP, 4);
- });
-}
-
-
-void ToneMappingDeferred::configure(const Config& config) {
- _toneMappingEffect.setExposure(config.exposure);
- _toneMappingEffect.setToneCurve((ToneMappingEffect::ToneCurve)config.curve);
-}
-
-void ToneMappingDeferred::run(const render::RenderContextPointer& renderContext, const Input& input, Output& output) {
-
- auto lightingBuffer = input.get0()->getRenderBuffer(0);
- auto destFbo = input.get1();
-
- if (!destFbo) {
- destFbo = renderContext->args->_blitFramebuffer;
- }
-
- _toneMappingEffect.render(renderContext->args, lightingBuffer, destFbo);
- output = destFbo;
-}
diff --git a/libraries/render-utils/src/render-utils/toneMapping.slp b/libraries/render-utils/src/render-utils/toneMapping.slp
index d4d8ec4b01..2bcb4497c4 100644
--- a/libraries/render-utils/src/render-utils/toneMapping.slp
+++ b/libraries/render-utils/src/render-utils/toneMapping.slp
@@ -1 +1,2 @@
VERTEX gpu::vertex::DrawViewportQuadTransformTexcoord
+DEFINES mirrored:f
diff --git a/libraries/render-utils/src/sdf_text3D.slf b/libraries/render-utils/src/sdf_text3D.slf
index ac064e5c8f..c5bed1ecab 100644
--- a/libraries/render-utils/src/sdf_text3D.slf
+++ b/libraries/render-utils/src/sdf_text3D.slf
@@ -36,29 +36,29 @@
layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES;
<@endif@>
layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS;
-layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color;
layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01;
#define _texCoord0 _texCoord01.xy
#define _texCoord1 _texCoord01.zw
+layout(location=RENDER_UTILS_ATTR_FADE1) flat in vec4 _glyphBounds; // we're reusing the fade texcoord locations here
void main() {
- float alpha = evalSDFSuperSampled(_texCoord0);
+ vec4 color = evalSDFSuperSampled(_texCoord0, _glyphBounds);
<@if HIFI_USE_TRANSLUCENT or HIFI_USE_FORWARD@>
- alpha *= _color.a;
- if (alpha <= 0.0) {
+ color.a *= params.color.a;
+ if (color.a <= 0.0) {
discard;
}
<@endif@>
<@if HIFI_USE_UNLIT@>
<@if HIFI_USE_TRANSLUCENT or HIFI_USE_FORWARD@>
- _fragColor0 = vec4(_color.rgb * isUnlitEnabled(), alpha);
+ _fragColor0 = vec4(color.rgb * isUnlitEnabled(), color.a);
<@else@>
packDeferredFragmentUnlit(
normalize(_normalWS),
- alpha,
- _color.rgb);
+ color.a,
+ color.rgb);
<@endif@>
<@else@>
<@if HIFI_USE_TRANSLUCENT or HIFI_USE_FORWARD@>
@@ -72,12 +72,12 @@ void main() {
DEFAULT_OCCLUSION,
fragPosition,
normalize(_normalWS),
- _color.rgb,
+ color.rgb,
DEFAULT_FRESNEL,
DEFAULT_METALLIC,
DEFAULT_EMISSIVE,
- DEFAULT_ROUGHNESS, alpha),
- alpha);
+ DEFAULT_ROUGHNESS, color.a),
+ color.a);
<@else@>
_fragColor0 = vec4(evalSkyboxGlobalColor(
cam._viewInverse,
@@ -85,17 +85,17 @@ void main() {
DEFAULT_OCCLUSION,
fragPosition,
normalize(_normalWS),
- _color.rgb,
+ color.rgb,
DEFAULT_FRESNEL,
DEFAULT_METALLIC,
DEFAULT_ROUGHNESS),
- alpha);
+ color.a);
<@endif@>
<@else@>
packDeferredFragment(
normalize(_normalWS),
- alpha,
- _color.rgb,
+ color.a,
+ color.rgb,
DEFAULT_ROUGHNESS,
DEFAULT_METALLIC,
DEFAULT_EMISSIVE,
diff --git a/libraries/render-utils/src/sdf_text3D.slh b/libraries/render-utils/src/sdf_text3D.slh
index 3297596efd..76ace99182 100644
--- a/libraries/render-utils/src/sdf_text3D.slh
+++ b/libraries/render-utils/src/sdf_text3D.slh
@@ -14,11 +14,16 @@
<@if not SDF_TEXT3D_SLH@>
<@def SDF_TEXT3D_SLH@>
-LAYOUT(binding=0) uniform sampler2D Font;
+LAYOUT(binding=0) uniform sampler2D fontTexture;
struct TextParams {
vec4 color;
- vec4 outline;
+
+ vec3 effectColor;
+ float effectThickness;
+
+ int effect;
+ vec3 spare;
};
LAYOUT(binding=0) uniform textParamsBuffer {
@@ -29,32 +34,57 @@ LAYOUT(binding=0) uniform textParamsBuffer {
#define TAA_TEXTURE_LOD_BIAS -3.0
-const float interiorCutoff = 0.8;
-const float outlineExpansion = 0.2;
+const float interiorCutoff = 0.5;
const float taaBias = pow(2.0, TAA_TEXTURE_LOD_BIAS);
-float evalSDF(vec2 texCoord) {
- // retrieve signed distance
- float sdf = textureLod(Font, texCoord, TAA_TEXTURE_LOD_BIAS).g;
- sdf = mix(sdf, mix(sdf + outlineExpansion, 1.0 - sdf, float(sdf > interiorCutoff)), float(params.outline.x > 0.0));
+vec4 evalSDF(vec2 texCoord, vec4 glyphBounds) {
+ vec3 color = params.color.rgb;
+ float sdf = textureLod(fontTexture, texCoord, TAA_TEXTURE_LOD_BIAS).g;
- // Rely on TAA for anti-aliasing
- return step(0.5, sdf);
+ // Outline
+ if (params.effect == 1 || params.effect == 2) {
+ float outline = float(sdf < interiorCutoff);
+ color = mix(color, params.effectColor, outline);
+
+ // with or without fill
+ sdf = mix(sdf, 0.0, float(params.effect == 1) * (1.0 - outline));
+
+ const float EPSILON = 0.00001;
+ sdf += mix(0.0, params.effectThickness - EPSILON, outline);
+ } else if (params.effect == 3) { // Shadow
+ // don't sample from outside of our glyph bounds
+ sdf *= mix(1.0, 0.0, float(clamp(texCoord, glyphBounds.xy, glyphBounds.xy + glyphBounds.zw) != texCoord));
+
+ if (sdf < interiorCutoff) {
+ color = params.effectColor;
+ const float DOUBLE_MAX_OFFSET_PIXELS = 20.0; // must match value in Font.cpp
+ // FIXME: TAA_TEXTURE_LOD_BIAS doesn't have any effect because we're only generating one mip, so here we need to use 0, but it should
+ // really match the LOD that we use in the textureLod call below
+ vec2 textureOffset = vec2(params.effectThickness * DOUBLE_MAX_OFFSET_PIXELS) / vec2(textureSize(fontTexture, 0/*int(TAA_TEXTURE_LOD_BIAS)*/));
+ vec2 shadowTexCoords = texCoord - textureOffset;
+ sdf = textureLod(fontTexture, shadowTexCoords, TAA_TEXTURE_LOD_BIAS).g;
+
+ // don't sample from outside of our glyph bounds
+ sdf *= mix(1.0, 0.0, float(clamp(shadowTexCoords, glyphBounds.xy, glyphBounds.xy + glyphBounds.zw) != shadowTexCoords));
+ }
+ }
+
+ return vec4(color, sdf);
}
-float evalSDFSuperSampled(vec2 texCoord) {
+vec4 evalSDFSuperSampled(vec2 texCoord, vec4 glyphBounds) {
vec2 dxTexCoord = dFdx(texCoord) * 0.5 * taaBias;
vec2 dyTexCoord = dFdy(texCoord) * 0.5 * taaBias;
// Perform 4x supersampling for anisotropic filtering
- float a;
- a = evalSDF(texCoord);
- a += evalSDF(texCoord + dxTexCoord);
- a += evalSDF(texCoord + dyTexCoord);
- a += evalSDF(texCoord + dxTexCoord + dyTexCoord);
- a *= 0.25;
+ vec4 color;
+ color = evalSDF(texCoord, glyphBounds);
+ color += evalSDF(texCoord + dxTexCoord, glyphBounds);
+ color += evalSDF(texCoord + dyTexCoord, glyphBounds);
+ color += evalSDF(texCoord + dxTexCoord + dyTexCoord, glyphBounds);
+ color *= 0.25;
- return a;
+ return vec4(color.rgb, step(interiorCutoff, color.a));
}
<@endfunc@>
diff --git a/libraries/render-utils/src/sdf_text3D.slv b/libraries/render-utils/src/sdf_text3D.slv
index 731cbc2cad..9ac3b871f9 100644
--- a/libraries/render-utils/src/sdf_text3D.slv
+++ b/libraries/render-utils/src/sdf_text3D.slv
@@ -23,19 +23,28 @@
layout(location=RENDER_UTILS_ATTR_POSITION_ES) out vec4 _positionES;
<@endif@>
layout(location=RENDER_UTILS_ATTR_NORMAL_WS) out vec3 _normalWS;
-layout(location=RENDER_UTILS_ATTR_COLOR) out vec4 _color;
layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01;
+layout(location=RENDER_UTILS_ATTR_FADE1) flat out vec4 _glyphBounds; // we're reusing the fade texcoord locations here
void main() {
_texCoord01 = vec4(inTexCoord0.st, 0.0, 0.0);
- _color = color_sRGBAToLinear(params.color);
+ _glyphBounds = inTexCoord1;
+
+ vec4 position = inPosition;
+ // if we're in shadow mode, we need to move each subsequent quad slightly forward so it doesn't z-fight
+ // with the shadows of the letters before it
+ if (params.effect == 3) { // Shadow
+ const int VERTICES_PER_QUAD = 4; // must match value in Font.cpp
+ const float EPSILON = 0.001;
+ position.z += float(gl_VertexID / VERTICES_PER_QUAD) * EPSILON;
+ }
TransformCamera cam = getTransformCamera();
TransformObject obj = getTransformObject();
<@if HIFI_USE_TRANSLUCENT or HIFI_USE_FORWARD@>
- <$transformModelToEyeAndClipPos(cam, obj, inPosition, _positionES, gl_Position)$>
+ <$transformModelToEyeAndClipPos(cam, obj, position, _positionES, gl_Position)$>
<@else@>
- <$transformModelToClipPos(cam, obj, inPosition, gl_Position)$>
+ <$transformModelToClipPos(cam, obj, position, gl_Position)$>
<@endif@>
const vec3 normal = vec3(0, 0, 1);
diff --git a/libraries/render-utils/src/text/EffectType.h b/libraries/render-utils/src/text/EffectType.h
deleted file mode 100644
index 63ec820036..0000000000
--- a/libraries/render-utils/src/text/EffectType.h
+++ /dev/null
@@ -1,15 +0,0 @@
-//
-// Created by Bradley Austin Davis on 2015/07/16
-// Copyright 2013 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_EffectType_h
-#define hifi_EffectType_h
-
-enum EffectType { NO_EFFECT, SHADOW_EFFECT, OUTLINE_EFFECT };
-
-#endif
diff --git a/libraries/render-utils/src/text/Font.cpp b/libraries/render-utils/src/text/Font.cpp
index eee6a7daea..f9ca9d4cae 100644
--- a/libraries/render-utils/src/text/Font.cpp
+++ b/libraries/render-utils/src/text/Font.cpp
@@ -2,6 +2,7 @@
#include
#include
+#include
#include
@@ -13,6 +14,8 @@
#include "FontFamilies.h"
#include "../StencilMaskPass.h"
+#include "NetworkAccessManager.h"
+
static std::mutex fontMutex;
std::map, gpu::PipelinePointer> Font::_pipelines;
@@ -21,67 +24,90 @@ gpu::Stream::FormatPointer Font::_format;
struct TextureVertex {
glm::vec2 pos;
glm::vec2 tex;
+ glm::vec4 bounds;
TextureVertex() {}
- TextureVertex(const glm::vec2& pos, const glm::vec2& tex) : pos(pos), tex(tex) {}
+ TextureVertex(const glm::vec2& pos, const glm::vec2& tex, const glm::vec4& bounds) : pos(pos), tex(tex), bounds(bounds) {}
};
-static const int NUMBER_OF_INDICES_PER_QUAD = 6; // 1 quad = 2 triangles
-static const int VERTICES_PER_QUAD = 4; // 1 quad = 4 vertices
+static const int NUMBER_OF_INDICES_PER_QUAD = 6; // 1 quad = 2 triangles
+static const int VERTICES_PER_QUAD = 4; // 1 quad = 4 vertices (must match value in sdf_text3D.slv)
+const float DOUBLE_MAX_OFFSET_PIXELS = 20.0f; // must match value in sdf_text3D.slh
struct QuadBuilder {
TextureVertex vertices[VERTICES_PER_QUAD];
- QuadBuilder(const glm::vec2& min, const glm::vec2& size,
- const glm::vec2& texMin, const glm::vec2& texSize) {
+ QuadBuilder(const Glyph& glyph, const glm::vec2& offset, float scale, bool enlargeForShadows) {
+ glm::vec2 min = offset + glm::vec2(glyph.offset.x, glyph.offset.y - glyph.size.y);
+ glm::vec2 size = glyph.size;
+ glm::vec2 texMin = glyph.texOffset;
+ glm::vec2 texSize = glyph.texSize;
+
+ // We need the pre-adjustment bounds for clamping
+ glm::vec4 bounds = glm::vec4(texMin, texSize);
+ if (enlargeForShadows) {
+ glm::vec2 imageSize = glyph.size / glyph.texSize;
+ glm::vec2 sizeDelta = 0.5f * DOUBLE_MAX_OFFSET_PIXELS * scale * imageSize;
+ glm::vec2 oldSize = size;
+ size += sizeDelta;
+ min.y -= sizeDelta.y;
+
+ texSize = texSize * (size / oldSize);
+ }
+
// min = bottomLeft
vertices[0] = TextureVertex(min,
- texMin + glm::vec2(0.0f, texSize.y));
+ texMin + glm::vec2(0.0f, texSize.y), bounds);
vertices[1] = TextureVertex(min + glm::vec2(size.x, 0.0f),
- texMin + texSize);
+ texMin + texSize, bounds);
vertices[2] = TextureVertex(min + glm::vec2(0.0f, size.y),
- texMin);
+ texMin, bounds);
vertices[3] = TextureVertex(min + size,
- texMin + glm::vec2(texSize.x, 0.0f));
+ texMin + glm::vec2(texSize.x, 0.0f), bounds);
}
- QuadBuilder(const Glyph& glyph, const glm::vec2& offset) :
- QuadBuilder(offset + glm::vec2(glyph.offset.x, glyph.offset.y - glyph.size.y), glyph.size,
- glyph.texOffset, glyph.texSize) {}
};
-
-
-static QHash LOADED_FONTS;
-
Font::Pointer Font::load(QIODevice& fontFile) {
Pointer font = std::make_shared();
font->read(fontFile);
return font;
}
+static QHash LOADED_FONTS;
+
Font::Pointer Font::load(const QString& family) {
std::lock_guard lock(fontMutex);
if (!LOADED_FONTS.contains(family)) {
-
- static const QString SDFF_COURIER_PRIME_FILENAME{ ":/CourierPrime.sdff" };
- static const QString SDFF_INCONSOLATA_MEDIUM_FILENAME{ ":/InconsolataMedium.sdff" };
- static const QString SDFF_ROBOTO_FILENAME{ ":/Roboto.sdff" };
- static const QString SDFF_TIMELESS_FILENAME{ ":/Timeless.sdff" };
-
QString loadFilename;
- if (family == MONO_FONT_FAMILY) {
- loadFilename = SDFF_COURIER_PRIME_FILENAME;
+ if (family == ROBOTO_FONT_FAMILY) {
+ loadFilename = ":/Roboto.sdff";
} else if (family == INCONSOLATA_FONT_FAMILY) {
- loadFilename = SDFF_INCONSOLATA_MEDIUM_FILENAME;
- } else if (family == SANS_FONT_FAMILY) {
- loadFilename = SDFF_ROBOTO_FILENAME;
+ loadFilename = ":/InconsolataMedium.sdff";
+ } else if (family == COURIER_FONT_FAMILY) {
+ loadFilename = ":/CourierPrime.sdff";
+ } else if (family == TIMELESS_FONT_FAMILY) {
+ loadFilename = ":/Timeless.sdff";
+ } else if (family.startsWith("http")) {
+ auto loadingFont = std::make_shared();
+ loadingFont->setLoaded(false);
+ LOADED_FONTS[family] = loadingFont;
+
+ auto& networkAccessManager = NetworkAccessManager::getInstance();
+
+ QNetworkRequest networkRequest;
+ networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
+ networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
+ networkRequest.setUrl(family);
+
+ auto networkReply = networkAccessManager.get(networkRequest);
+ connect(networkReply, &QNetworkReply::finished, loadingFont.get(), &Font::handleFontNetworkReply);
+ } else if (!LOADED_FONTS.contains(ROBOTO_FONT_FAMILY)) {
+ // Unrecognized font and we haven't loaded Roboto yet
+ loadFilename = ":/Roboto.sdff";
} else {
- if (!LOADED_FONTS.contains(SERIF_FONT_FAMILY)) {
- loadFilename = SDFF_TIMELESS_FILENAME;
- } else {
- LOADED_FONTS[family] = LOADED_FONTS[SERIF_FONT_FAMILY];
- }
+ // Unrecognized font but we've already loaded Roboto
+ LOADED_FONTS[family] = LOADED_FONTS[ROBOTO_FONT_FAMILY];
}
if (!loadFilename.isEmpty()) {
@@ -96,14 +122,24 @@ Font::Pointer Font::load(const QString& family) {
return LOADED_FONTS[family];
}
-Font::Font() {
- static bool fontResourceInitComplete = false;
- if (!fontResourceInitComplete) {
- Q_INIT_RESOURCE(fonts);
- fontResourceInitComplete = true;
+void Font::handleFontNetworkReply() {
+ auto requestReply = qobject_cast(sender());
+
+ if (requestReply->error() == QNetworkReply::NoError) {
+ setLoaded(true);
+ read(*requestReply);
+ } else {
+ qDebug() << "Error downloading " << requestReply->url() << " - " << requestReply->errorString();
}
}
+Font::Font() {
+ static std::once_flag once;
+ std::call_once(once, []{
+ Q_INIT_RESOURCE(fonts);
+ });
+}
+
// NERD RAGE: why doesn't QHash have a 'const T & operator[] const' member
const Glyph& Font::getGlyph(const QChar& c) const {
if (!_glyphs.contains(c)) {
@@ -139,7 +175,7 @@ glm::vec2 Font::computeTokenExtent(const QString& token) const {
glm::vec2 Font::computeExtent(const QString& str) const {
glm::vec2 extent = glm::vec2(0.0f, 0.0f);
- QStringList lines{ splitLines(str) };
+ QStringList lines = splitLines(str);
if (!lines.empty()) {
for(const auto& line : lines) {
glm::vec2 tokenExtent = computeTokenExtent(line);
@@ -154,7 +190,9 @@ void Font::read(QIODevice& in) {
uint8_t header[4];
readStream(in, header);
if (memcmp(header, "SDFF", 4)) {
- qFatal("Bad SDFF file");
+ qDebug() << "Bad SDFF file";
+ _loaded = false;
+ return;
}
uint16_t version;
@@ -191,7 +229,9 @@ void Font::read(QIODevice& in) {
// read image data
QImage image;
if (!image.loadFromData(in.readAll(), "PNG")) {
- qFatal("Failed to read SDFF image");
+ qDebug() << "Failed to read SDFF image";
+ _loaded = false;
+ return;
}
_glyphs.clear();
@@ -212,6 +252,9 @@ void Font::read(QIODevice& in) {
formatGPU = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA);
formatMip = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::BGRA);
}
+ // FIXME: We're forcing this to use only one mip, and then manually doing anisotropic filtering in the shader,
+ // and also calling textureLod. Shouldn't this just use anisotropic filtering and auto-generate mips?
+ // We should also use smoothstep for anti-aliasing, as explained here: https://github.com/libgdx/libgdx/wiki/Distance-field-fonts
_texture = gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::SINGLE_MIP,
gpu::Sampler(gpu::Sampler::FILTER_MIN_POINT_MAG_LINEAR));
_texture->setStoredMipFormat(formatMip);
@@ -244,20 +287,21 @@ void Font::setupGPU() {
}
// Sanity checks
- static const int OFFSET = offsetof(TextureVertex, tex);
- assert(OFFSET == sizeof(glm::vec2));
- assert(sizeof(glm::vec2) == 2 * sizeof(float));
- assert(sizeof(TextureVertex) == 2 * sizeof(glm::vec2));
+ static const int TEX_COORD_OFFSET = offsetof(TextureVertex, tex);
+ static const int TEX_BOUNDS_OFFSET = offsetof(TextureVertex, bounds);
+ assert(TEX_COORD_OFFSET == sizeof(glm::vec2));
+ assert(sizeof(TextureVertex) == 2 * sizeof(glm::vec2) + sizeof(glm::vec4));
assert(sizeof(QuadBuilder) == 4 * sizeof(TextureVertex));
// Setup rendering structures
_format = std::make_shared();
_format->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::XYZ), 0);
- _format->setAttribute(gpu::Stream::TEXCOORD, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), OFFSET);
+ _format->setAttribute(gpu::Stream::TEXCOORD, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), TEX_COORD_OFFSET);
+ _format->setAttribute(gpu::Stream::TEXCOORD1, 0, gpu::Element(gpu::VEC4, gpu::FLOAT, gpu::XYZW), TEX_BOUNDS_OFFSET);
}
}
-void Font::buildVertices(Font::DrawInfo& drawInfo, const QString& str, const glm::vec2& origin, const glm::vec2& bounds) {
+void Font::buildVertices(Font::DrawInfo& drawInfo, const QString& str, const glm::vec2& origin, const glm::vec2& bounds, float scale, bool enlargeForShadows) {
drawInfo.verticesBuffer = std::make_shared();
drawInfo.indicesBuffer = std::make_shared();
drawInfo.indexCount = 0;
@@ -267,6 +311,8 @@ void Font::buildVertices(Font::DrawInfo& drawInfo, const QString& str, const glm
drawInfo.bounds = bounds;
drawInfo.origin = origin;
+ float enlargedBoundsX = bounds.x - 0.5f * DOUBLE_MAX_OFFSET_PIXELS * float(enlargeForShadows);
+
// Top left of text
glm::vec2 advance = origin;
foreach(const QString& token, tokenizeForWrapping(str)) {
@@ -274,7 +320,7 @@ void Font::buildVertices(Font::DrawInfo& drawInfo, const QString& str, const glm
bool forceNewLine = false;
// Handle wrapping
- if (!isNewLine && (bounds.x != -1) && (advance.x + computeExtent(token).x > origin.x + bounds.x)) {
+ if (!isNewLine && (bounds.x != -1) && (advance.x + computeExtent(token).x > origin.x + enlargedBoundsX)) {
// We are out of the x bound, force new line
forceNewLine = true;
}
@@ -285,7 +331,7 @@ void Font::buildVertices(Font::DrawInfo& drawInfo, const QString& str, const glm
if (isNewLine) {
// No need to draw anything, go directly to next token
continue;
- } else if (computeExtent(token).x > bounds.x) {
+ } else if (computeExtent(token).x > enlargedBoundsX) {
// token will never fit, stop drawing
break;
}
@@ -301,10 +347,10 @@ void Font::buildVertices(Font::DrawInfo& drawInfo, const QString& str, const glm
auto glyph = _glyphs[c];
quint16 verticesOffset = numVertices;
- QuadBuilder qd(glyph, advance - glm::vec2(0.0f, _ascent));
+ QuadBuilder qd(glyph, advance - glm::vec2(0.0f, _ascent), scale, enlargeForShadows);
drawInfo.verticesBuffer->append(qd);
- numVertices += 4;
-
+ numVertices += VERTICES_PER_QUAD;
+
// Sam's recommended triangle slices
// Triangle tri1 = { v0, v1, v3 };
// Triangle tri2 = { v1, v2, v3 };
@@ -331,7 +377,6 @@ void Font::buildVertices(Font::DrawInfo& drawInfo, const QString& str, const glm
indices[5] = verticesOffset + 3;
drawInfo.indicesBuffer->append(sizeof(indices), (const gpu::Byte*)indices);
drawInfo.indexCount += NUMBER_OF_INDICES_PER_QUAD;
-
// Advance by glyph size
advance.x += glyph.d;
@@ -344,38 +389,49 @@ void Font::buildVertices(Font::DrawInfo& drawInfo, const QString& str, const glm
}
void Font::drawString(gpu::Batch& batch, Font::DrawInfo& drawInfo, const QString& str, const glm::vec4& color,
- EffectType effectType, const glm::vec2& origin, const glm::vec2& bounds, bool unlit, bool forward) {
- if (str == "") {
+ const glm::vec3& effectColor, float effectThickness, TextEffect effect,
+ const glm::vec2& origin, const glm::vec2& bounds, float scale, bool unlit, bool forward) {
+ if (!_loaded || str == "") {
return;
}
- if (str != drawInfo.string || bounds != drawInfo.bounds || origin != drawInfo.origin) {
- buildVertices(drawInfo, str, origin, bounds);
+ int textEffect = (int)effect;
+ const int SHADOW_EFFECT = (int)TextEffect::SHADOW_EFFECT;
+
+ // If we're switching to or from shadow effect mode, we need to rebuild the vertices
+ if (str != drawInfo.string || bounds != drawInfo.bounds || origin != drawInfo.origin ||
+ (drawInfo.params.effect != textEffect && (textEffect == SHADOW_EFFECT || drawInfo.params.effect == SHADOW_EFFECT)) ||
+ (textEffect == SHADOW_EFFECT && scale != _scale)) {
+ _scale = scale;
+ buildVertices(drawInfo, str, origin, bounds, scale, textEffect == SHADOW_EFFECT);
}
setupGPU();
- struct GpuDrawParams {
- glm::vec4 color;
- glm::vec4 outline;
- };
-
- if (!drawInfo.paramsBuffer || drawInfo.params.color != color || drawInfo.params.effect != effectType) {
+ if (!drawInfo.paramsBuffer || drawInfo.params.color != color || drawInfo.params.effectColor != effectColor ||
+ drawInfo.params.effectThickness != effectThickness || drawInfo.params.effect != textEffect) {
drawInfo.params.color = color;
- drawInfo.params.effect = effectType;
- GpuDrawParams gpuDrawParams;
+ drawInfo.params.effectColor = effectColor;
+ drawInfo.params.effectThickness = effectThickness;
+ drawInfo.params.effect = textEffect;
+
+ // need the gamma corrected color here
+ DrawParams gpuDrawParams;
gpuDrawParams.color = ColorUtils::sRGBToLinearVec4(drawInfo.params.color);
- gpuDrawParams.outline.x = (drawInfo.params.effect == OUTLINE_EFFECT) ? 1 : 0;
- drawInfo.paramsBuffer = std::make_shared(sizeof(GpuDrawParams), nullptr);
- drawInfo.paramsBuffer->setSubData(0, sizeof(GpuDrawParams), (const gpu::Byte*)&gpuDrawParams);
+ gpuDrawParams.effectColor = ColorUtils::sRGBToLinearVec3(drawInfo.params.effectColor);
+ gpuDrawParams.effectThickness = drawInfo.params.effectThickness;
+ gpuDrawParams.effect = drawInfo.params.effect;
+ if (!drawInfo.paramsBuffer) {
+ drawInfo.paramsBuffer = std::make_shared(sizeof(DrawParams), nullptr);
+ }
+ drawInfo.paramsBuffer->setSubData(0, sizeof(DrawParams), (const gpu::Byte*)&gpuDrawParams);
}
- // need the gamma corrected color here
batch.setPipeline(_pipelines[std::make_tuple(color.a < 1.0f, unlit, forward)]);
batch.setInputFormat(_format);
batch.setInputBuffer(0, drawInfo.verticesBuffer, 0, _format->getChannels().at(0)._stride);
batch.setResourceTexture(render_utils::slot::texture::TextFont, _texture);
- batch.setUniformBuffer(0, drawInfo.paramsBuffer, 0, sizeof(GpuDrawParams));
+ batch.setUniformBuffer(0, drawInfo.paramsBuffer, 0, sizeof(DrawParams));
batch.setIndexBuffer(gpu::UINT16, drawInfo.indicesBuffer, 0);
batch.drawIndexed(gpu::TRIANGLES, drawInfo.indexCount, 0);
}
diff --git a/libraries/render-utils/src/text/Font.h b/libraries/render-utils/src/text/Font.h
index be1e890e3d..c75f0f746f 100644
--- a/libraries/render-utils/src/text/Font.h
+++ b/libraries/render-utils/src/text/Font.h
@@ -10,12 +10,16 @@
#ifndef hifi_Font_h
#define hifi_Font_h
+#include
+
#include "Glyph.h"
-#include "EffectType.h"
+#include "TextEffect.h"
#include
#include
-class Font {
+class Font : public QObject {
+ Q_OBJECT
+
public:
using Pointer = std::shared_ptr;
@@ -24,14 +28,23 @@ public:
void read(QIODevice& path);
struct DrawParams {
- vec4 color{ -1 };
- EffectType effect;
+ vec4 color { 0 };
+
+ vec3 effectColor { 0 };
+ float effectThickness { 0 };
+
+ int effect { 0 };
+
+#if defined(__clang__)
+ __attribute__((unused))
+#endif
+ vec3 _spare;
};
struct DrawInfo {
- gpu::BufferPointer verticesBuffer;
- gpu::BufferPointer indicesBuffer;
- gpu::BufferPointer paramsBuffer;
+ gpu::BufferPointer verticesBuffer { nullptr };
+ gpu::BufferPointer indicesBuffer { nullptr };
+ gpu::BufferPointer paramsBuffer { nullptr };
uint32_t indexCount;
QString string;
@@ -44,12 +57,18 @@ public:
float getFontSize() const { return _fontSize; }
// Render string to batch
- void drawString(gpu::Batch& batch, DrawInfo& drawInfo, const QString& str,
- const glm::vec4& color, EffectType effectType,
- const glm::vec2& origin, const glm::vec2& bound, bool unlit, bool forward);
+ void drawString(gpu::Batch& batch, DrawInfo& drawInfo, const QString& str, const glm::vec4& color,
+ const glm::vec3& effectColor, float effectThickness, TextEffect effect,
+ const glm::vec2& origin, const glm::vec2& bound, float scale, bool unlit, bool forward);
static Pointer load(const QString& family);
+ bool isLoaded() const { return _loaded; }
+ void setLoaded(bool loaded) { _loaded = loaded; }
+
+public slots:
+ void handleFontNetworkReply();
+
private:
static Pointer load(QIODevice& fontFile);
QStringList tokenizeForWrapping(const QString& str) const;
@@ -57,7 +76,7 @@ private:
glm::vec2 computeTokenExtent(const QString& str) const;
const Glyph& getGlyph(const QChar& c) const;
- void buildVertices(DrawInfo& drawInfo, const QString& str, const glm::vec2& origin, const glm::vec2& bounds);
+ void buildVertices(DrawInfo& drawInfo, const QString& str, const glm::vec2& origin, const glm::vec2& bounds, float scale, bool enlargeForShadows);
void setupGPU();
@@ -70,11 +89,15 @@ private:
// Font characteristics
QString _family;
- float _fontSize = 0.0f;
- float _leading = 0.0f;
- float _ascent = 0.0f;
- float _descent = 0.0f;
- float _spaceWidth = 0.0f;
+ float _fontSize { 0.0f };
+ float _leading { 0.0f };
+ float _ascent { 0.0f };
+ float _descent { 0.0f };
+ float _spaceWidth { 0.0f };
+
+ float _scale { 0.0f };
+
+ bool _loaded { true };
gpu::TexturePointer _texture;
gpu::BufferStreamPointer _stream;
diff --git a/libraries/render-utils/src/text/FontFamilies.h b/libraries/render-utils/src/text/FontFamilies.h
deleted file mode 100644
index 3c4186f5c4..0000000000
--- a/libraries/render-utils/src/text/FontFamilies.h
+++ /dev/null
@@ -1,31 +0,0 @@
-//
-// Created by Bradley Austin Davis on 2015/07/16
-// Copyright 2013 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_FontFamilies_h
-#define hifi_FontFamilies_h
-
-// the standard sans serif font family
-#define SANS_FONT_FAMILY "Helvetica"
-
-// the standard sans serif font family
-#define SERIF_FONT_FAMILY "Timeless"
-
-// the standard mono font family
-#define MONO_FONT_FAMILY "Courier"
-
-// the Inconsolata font family
-#ifdef Q_OS_WIN
-#define INCONSOLATA_FONT_FAMILY "Fixedsys"
-#define INCONSOLATA_FONT_WEIGHT QFont::Normal
-#else
-#define INCONSOLATA_FONT_FAMILY "Inconsolata"
-#define INCONSOLATA_FONT_WEIGHT QFont::Bold
-#endif
-
-#endif
diff --git a/libraries/render-utils/src/text/Glyph.cpp b/libraries/render-utils/src/text/Glyph.cpp
index 0354b1057c..ee3656bc00 100644
--- a/libraries/render-utils/src/text/Glyph.cpp
+++ b/libraries/render-utils/src/text/Glyph.cpp
@@ -18,5 +18,6 @@ void Glyph::read(QIODevice& in) {
readStream(in, size);
readStream(in, offset);
readStream(in, d);
+ // texSize is divided by the image size later
texSize = size;
}
diff --git a/libraries/render-utils/src/toneMapping.slf b/libraries/render-utils/src/toneMapping.slf
index 4f7ed6374d..32aa2b0788 100644
--- a/libraries/render-utils/src/toneMapping.slf
+++ b/libraries/render-utils/src/toneMapping.slf
@@ -43,7 +43,11 @@ layout(location=0) in vec2 varTexCoord0;
layout(location=0) out vec4 outFragColor;
void main(void) {
+<@if HIFI_USE_MIRRORED@>
+ vec4 fragColorRaw = texture(colorMap, vec2(1.0 - varTexCoord0.x, varTexCoord0.y));
+<@else@>
vec4 fragColorRaw = texture(colorMap, varTexCoord0);
+<@endif@>
vec3 fragColor = fragColorRaw.xyz;
vec3 srcColor = fragColor * getTwoPowExposure();
diff --git a/libraries/script-engine/src/ScriptAudioInjector.cpp b/libraries/script-engine/src/ScriptAudioInjector.cpp
index ebc9287c2a..0e42ec31e7 100644
--- a/libraries/script-engine/src/ScriptAudioInjector.cpp
+++ b/libraries/script-engine/src/ScriptAudioInjector.cpp
@@ -30,6 +30,7 @@ ScriptAudioInjector::ScriptAudioInjector(const AudioInjectorPointer& injector) :
_injector(injector)
{
QObject::connect(injector.data(), &AudioInjector::finished, this, &ScriptAudioInjector::finished);
+ connect(injector.data(), &QObject::destroyed, this, &QObject::deleteLater);
}
ScriptAudioInjector::~ScriptAudioInjector() {
diff --git a/libraries/script-engine/src/ScriptAudioInjector.h b/libraries/script-engine/src/ScriptAudioInjector.h
index 3023623d72..5f086019de 100644
--- a/libraries/script-engine/src/ScriptAudioInjector.h
+++ b/libraries/script-engine/src/ScriptAudioInjector.h
@@ -137,7 +137,7 @@ signals:
void finished();
private:
- AudioInjectorPointer _injector;
+ QWeakPointer _injector;
friend QScriptValue injectorToScriptValue(QScriptEngine* engine, ScriptAudioInjector* const& in);
};
diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp
index da698c6e4e..381377c9f4 100644
--- a/libraries/script-engine/src/ScriptEngines.cpp
+++ b/libraries/script-engine/src/ScriptEngines.cpp
@@ -401,13 +401,14 @@ void ScriptEngines::stopAllScripts(bool restart) {
continue;
}
+ bool isOverrideScript = it.key().toString().compare(this->_defaultScriptsOverride.toString());
// queue user scripts if restarting
- if (restart && scriptEngine->isUserLoaded()) {
+ if (restart && (scriptEngine->isUserLoaded() || isOverrideScript)) {
_isReloading = true;
ScriptEngine::Type type = scriptEngine->getType();
- connect(scriptEngine.data(), &ScriptEngine::finished, this, [this, type] (QString scriptName) {
- reloadScript(scriptName, true)->setType(type);
+ connect(scriptEngine.data(), &ScriptEngine::finished, this, [this, type, isOverrideScript] (QString scriptName) {
+ reloadScript(scriptName, !isOverrideScript)->setType(type);
});
}
diff --git a/libraries/shared/src/FontFamilies.h b/libraries/shared/src/FontFamilies.h
new file mode 100644
index 0000000000..ba7af3ae7b
--- /dev/null
+++ b/libraries/shared/src/FontFamilies.h
@@ -0,0 +1,17 @@
+//
+// Created by Bradley Austin Davis on 2015/07/16
+// Copyright 2013 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_FontFamilies_h
+#define hifi_FontFamilies_h
+
+#define ROBOTO_FONT_FAMILY "Roboto"
+#define COURIER_FONT_FAMILY "Courier"
+#define INCONSOLATA_FONT_FAMILY "Inconsolata"
+#define TIMELESS_FONT_FAMILY "Timeless"
+
+#endif // hifi_FontFamilies_h
diff --git a/libraries/shared/src/TextEffect.cpp b/libraries/shared/src/TextEffect.cpp
new file mode 100644
index 0000000000..27ed1caa59
--- /dev/null
+++ b/libraries/shared/src/TextEffect.cpp
@@ -0,0 +1,26 @@
+//
+// Created by Sam Gondelman on 7/21/2019
+// Copyright 2019 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 "TextEffect.h"
+
+const char* textEffectNames[] = {
+ "none",
+ "outline",
+ "outline fill",
+ "shadow"
+};
+
+static const size_t TEXT_EFFECT_NAMES = (sizeof(textEffectNames) / sizeof(textEffectNames[0]));
+
+QString TextEffectHelpers::getNameForTextEffect(TextEffect effect) {
+ if (((int)effect <= 0) || ((int)effect >= (int)TEXT_EFFECT_NAMES)) {
+ effect = (TextEffect)0;
+ }
+
+ return textEffectNames[(int)effect];
+}
\ No newline at end of file
diff --git a/libraries/shared/src/TextEffect.h b/libraries/shared/src/TextEffect.h
new file mode 100644
index 0000000000..91bd5ec60c
--- /dev/null
+++ b/libraries/shared/src/TextEffect.h
@@ -0,0 +1,42 @@
+//
+// Created by Bradley Austin Davis on 2015/07/16
+// Copyright 2013 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_TextEffect_h
+#define hifi_TextEffect_h
+
+#include "QString"
+
+/**jsdoc
+ * A {@link Entities.EntityProperties-Text|Text} entity may use one of the following effects:
+ *
+ *
+ * Value | Description |
+ *
+ *
+ * "none" | No effect. |
+ * "outline" | An outline effect. |
+ * "outlineFill" | An outline effect, with fill. |
+ * "shadow" | A shadow effect. |
+ *
+ *
+ * @typedef {string} Entities.TextEffect
+ */
+
+enum class TextEffect {
+ NO_EFFECT = 0,
+ OUTLINE_EFFECT,
+ OUTLINE_WITH_FILL_EFFECT,
+ SHADOW_EFFECT
+};
+
+class TextEffectHelpers {
+public:
+ static QString getNameForTextEffect(TextEffect effect);
+};
+
+#endif // hifi_TextEffect_h
\ No newline at end of file
diff --git a/libraries/shared/src/shared/WebRTC.h b/libraries/shared/src/shared/WebRTC.h
new file mode 100644
index 0000000000..2f0e444bff
--- /dev/null
+++ b/libraries/shared/src/shared/WebRTC.h
@@ -0,0 +1,36 @@
+//
+// WebRTC.h
+// libraries/shared/src/shared/
+//
+// Copyright 2019 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_WebRTC_h
+#define hifi_WebRTC_h
+
+#if defined(Q_OS_MAC)
+# define WEBRTC_ENABLED 1
+# define WEBRTC_POSIX 1
+#elif defined(Q_OS_WIN)
+# define WEBRTC_ENABLED 1
+# define WEBRTC_WIN 1
+# define NOMINMAX 1
+# define WIN32_LEAN_AND_MEAN 1
+#elif defined(Q_OS_ANDROID)
+// I don't yet have a working libwebrtc for android
+// # define WEBRTC_ENABLED 1
+// # define WEBRTC_POSIX 1
+#elif defined(Q_OS_LINUX)
+# define WEBRTC_ENABLED 1
+# define WEBRTC_POSIX 1
+#endif
+
+#if defined(WEBRTC_ENABLED)
+# include
+# include "modules/audio_processing/audio_processing_impl.h"
+#endif
+
+#endif // hifi_WebRTC_h
diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp
index 34cac90a05..35fb92a086 100644
--- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp
+++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp
@@ -264,7 +264,19 @@ void OffscreenQmlSurface::initializeEngine(QQmlEngine* engine) {
}
auto rootContext = engine->rootContext();
- rootContext->setContextProperty("GL", ::getGLContextData());
+
+ static QJsonObject QML_GL_INFO;
+ static std::once_flag once_gl_info;
+ std::call_once(once_gl_info, [] {
+ const auto& contextInfo = gl::ContextInfo::get();
+ QML_GL_INFO = QJsonObject {
+ { "version", contextInfo.version.c_str() },
+ { "sl_version", contextInfo.shadingLanguageVersion.c_str() },
+ { "vendor", contextInfo.vendor.c_str() },
+ { "renderer", contextInfo.renderer.c_str() },
+ };
+ });
+ rootContext->setContextProperty("GL", QML_GL_INFO);
rootContext->setContextProperty("urlHandler", new UrlHandler(rootContext));
rootContext->setContextProperty("resourceDirectoryUrl", QUrl::fromLocalFile(PathUtils::resourcesPath()));
rootContext->setContextProperty("ApplicationInterface", qApp);
diff --git a/scripts/developer/utilities/render/deferredLighting.qml b/scripts/developer/utilities/render/deferredLighting.qml
index 80ca8b09e1..f2891ddc55 100644
--- a/scripts/developer/utilities/render/deferredLighting.qml
+++ b/scripts/developer/utilities/render/deferredLighting.qml
@@ -45,7 +45,7 @@ Rectangle {
anchors.right: parent.right
spacing: 5
Repeater {
- model: [ "MSAA:PrepareFramebuffer:numSamples:4:1"
+ model: [ "MSAA:PreparePrimaryBufferForward:numSamples:4:1"
]
ConfigSlider {
label: qsTr(modelData.split(":")[0])
diff --git a/scripts/simplifiedUI/ui/simplifiedUI.js b/scripts/simplifiedUI/ui/simplifiedUI.js
index 50c626b594..f4f2627c66 100644
--- a/scripts/simplifiedUI/ui/simplifiedUI.js
+++ b/scripts/simplifiedUI/ui/simplifiedUI.js
@@ -531,21 +531,21 @@ function maybeUpdateOutputDeviceMutedOverlay() {
var oldAutomaticLODAdjust;
-var oldLODLevel;
-var DEFAULT_AUTO_LOD_ADJUST = false;
-var DEFAULT_LOD_LEVEL = 0.5;
+var oldLODAngleDeg;
+var SIMPLIFIED_UI_AUTO_LOD_ADJUST = false;
+var SIMPLIFIED_UI_LOD_ANGLE_DEG = 0.5;
function modifyLODSettings() {
oldAutomaticLODAdjust = LODManager.automaticLODAdjust;
- oldLODLevel = LODManager.lodQualityLevel;
+ oldLODAngleDeg = LODManager.lodAngleDeg;
- LODManager.automaticLODAdjust = DEFAULT_AUTO_LOD_ADJUST;
- LODManager.lodQualityLevel = DEFAULT_LOD_LEVEL;
+ LODManager.automaticLODAdjust = SIMPLIFIED_UI_AUTO_LOD_ADJUST;
+ LODManager.lodAngleDeg = SIMPLIFIED_UI_LOD_ANGLE_DEG;
}
function restoreLODSettings() {
LODManager.automaticLODAdjust = oldAutomaticLODAdjust;
- LODManager.lodQualityLevel = oldLODLevel;
+ LODManager.lodAngleDeg = oldLODAngleDeg;
}
diff --git a/tools/oven/src/Oven.cpp b/tools/oven/src/Oven.cpp
index a680dd1f89..d7e0cec67b 100644
--- a/tools/oven/src/Oven.cpp
+++ b/tools/oven/src/Oven.cpp
@@ -21,6 +21,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -42,6 +43,7 @@ Oven::Oven() {
DependencyManager::set();
DependencyManager::set();
DependencyManager::set();
+ DependencyManager::set();
MaterialBaker::setNextOvenWorkerThreadOperator([] {
return Oven::instance().getNextWorkerThread();