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 += "
NameFile NameCreatedInstalledInstalled By
"; + 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: CourierInconsolata, 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.00.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:

+ * + * + * + * + * + * + * + * + * + * + *
ValueDescription
"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();